Skip to content

Commit c28b813

Browse files
authored
Merge pull request #526 from 2chanhaeng/smq
Add SqliteMessageQueue and related tests
2 parents f54af22 + 3a98dd5 commit c28b813

32 files changed

Lines changed: 1123 additions & 382 deletions

.github/workflows/main.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ jobs:
5353
- 6379:6379
5454
env:
5555
AMQP_URL: amqp://guest:guest@localhost:5672
56-
DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres
56+
POSTGRES_URL: postgres://postgres:postgres@localhost:5432/postgres
5757
REDIS_URL: redis://localhost:6379
5858
steps:
5959
- uses: actions/checkout@v4
@@ -116,7 +116,7 @@ jobs:
116116
- 6379:6379
117117
env:
118118
AMQP_URL: amqp://guest:guest@localhost:5672
119-
DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres
119+
POSTGRES_URL: postgres://postgres:postgres@localhost:5432/postgres
120120
REDIS_URL: redis://localhost:6379
121121
steps:
122122
- uses: actions/checkout@v4
@@ -157,7 +157,7 @@ jobs:
157157
- 6379:6379
158158
env:
159159
AMQP_URL: amqp://guest:guest@localhost:5672
160-
DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres
160+
POSTGRES_URL: postgres://postgres:postgres@localhost:5432/postgres
161161
REDIS_URL: redis://localhost:6379
162162
steps:
163163
- uses: actions/checkout@v4

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ package-lock.json
77
repomix-output.xml
88
t.ts
99
t2.ts
10+
plan.md

.hongdown.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ exclude = [
88
"GEMINI.md",
99
"WARP.md",
1010
"packages/fedify/src/cfworkers/**",
11+
"plan.md",
1112
]
1213

1314
[heading]

CHANGES.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,31 @@ To be released.
275275

276276
[#437]: https://github.com/fedify-dev/fedify/issues/437
277277

278+
### @fedify/sqlite
279+
280+
- Added `SqliteMessageQueue` class implementing `MessageQueue` interface
281+
using SQLite as the backing store. This implementation uses polling to
282+
check for new messages and is suitable for single-node deployments and
283+
development environments. [[#477], [#526] by ChanHaeng Lee]
284+
285+
- Added `SqliteMessageQueue` class.
286+
- Added `SqliteMessageQueueOptions` interface.
287+
288+
[#477]: https://github.com/fedify-dev/fedify/issues/477
289+
[#526]: https://github.com/fedify-dev/fedify/pull/526
290+
291+
### @fedify/testing
292+
293+
- Added `testMessageQueue()` utility function for standardized testing of
294+
`MessageQueue` implementations. This function provides a reusable test
295+
harness that covers common message queue operations including `enqueue()`,
296+
`enqueue()` with delay, `enqueueMany()`, and multiple listener scenarios.
297+
[[#477], [#526] by ChanHaeng Lee]
298+
299+
- Added `testMessageQueue()` function.
300+
- Added `waitFor()` helper function.
301+
- Added `getRandomKey()` helper function.
302+
278303

279304
Version 1.10.1
280305
--------------

docs/manual/mq.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,115 @@ const federation = createFederation({
293293
[`AmqpMessageQueue`]: https://jsr.io/@fedify/amqp/doc/mq/~/AmqpMessageQueue
294294
[RabbitMQ]: https://www.rabbitmq.com/
295295

296+
### `SqliteMessageQueue`
297+
298+
*This API is available since Fedify 2.0.0.*
299+
300+
To use [`SqliteMessageQueue`], you need to install the *@fedify/sqlite* package
301+
first:
302+
303+
::: code-group
304+
305+
~~~~ bash [Deno]
306+
deno add jsr:@fedify/sqlite
307+
~~~~
308+
309+
~~~~ bash [npm]
310+
npm add @fedify/sqlite
311+
~~~~
312+
313+
~~~~ bash [pnpm]
314+
pnpm add @fedify/sqlite
315+
~~~~
316+
317+
~~~~ bash [Yarn]
318+
yarn add @fedify/sqlite
319+
~~~~
320+
321+
~~~~ bash [Bun]
322+
bun add @fedify/sqlite
323+
~~~~
324+
325+
:::
326+
327+
[`SqliteMessageQueue`] is a message queue implementation that uses SQLite as
328+
the backend. It uses polling to check for new messages and is designed for
329+
single-node deployments. It's suitable for development, testing, and
330+
small-scale production use where simplicity is preferred over high throughput.
331+
It uses native sqlite modules, [`node:sqlite`] for Node.js and Deno,
332+
[`bun:sqlite`] for Bun.
333+
334+
Best for
335+
: Development and testing.
336+
337+
Pros
338+
: Simple, persistent with minimal configuration.
339+
340+
Cons
341+
: Limited scalability, not suitable for high-traffic production.
342+
343+
> [!NOTE]
344+
> `SqliteMessageQueue` uses `DELETE ... RETURNING` to atomically fetch and
345+
> delete the oldest message that is ready to be processed. This requires
346+
> SQLite 3.35.0 or later.
347+
348+
::: code-group
349+
350+
~~~~ typescript twoslash [Deno]
351+
import type { KvStore } from "@fedify/fedify";
352+
// ---cut-before---
353+
import { DatabaseSync } from "node:sqlite";
354+
import { createFederation } from "@fedify/fedify";
355+
import { SqliteMessageQueue } from "@fedify/sqlite";
356+
357+
const db = new DatabaseSync(":memory:");
358+
const federation = createFederation<void>({
359+
// ...
360+
// ---cut-start---
361+
kv: null as unknown as KvStore,
362+
// ---cut-end---
363+
queue: new SqliteMessageQueue(db), // [!code highlight]
364+
});
365+
~~~~
366+
367+
~~~~ typescript twoslash [Node.js]
368+
import type { KvStore } from "@fedify/fedify";
369+
// ---cut-before---
370+
import { DatabaseSync } from "node:sqlite";
371+
import { createFederation } from "@fedify/fedify";
372+
import { SqliteMessageQueue } from "@fedify/sqlite";
373+
374+
const db = new DatabaseSync(":memory:");
375+
const federation = createFederation<void>({
376+
// ...
377+
// ---cut-start---
378+
kv: null as unknown as KvStore,
379+
// ---cut-end---
380+
queue: new SqliteMessageQueue(db), // [!code highlight]
381+
});
382+
~~~~
383+
384+
~~~~ typescript [Bun]
385+
import type { KvStore } from "@fedify/fedify";
386+
// ---cut-before---
387+
import { Database } from "bun:sqlite";
388+
import { createFederation } from "@fedify/fedify";
389+
import { SqliteMessageQueue } from "@fedify/sqlite";
390+
391+
const db = new Database(":memory:");
392+
const federation = createFederation<void>({
393+
// ...
394+
// ---cut-start---
395+
kv: null as unknown as KvStore,
396+
// ---cut-end---
397+
queue: new SqliteMessageQueue(db), // [!code highlight]
398+
});
399+
~~~~
400+
401+
:::
402+
403+
[`SqliteMessageQueue`]: https://jsr.io/@fedify/sqlite/doc/mq/~/SqliteMessageQueue
404+
296405
### `WorkersMessageQueue` (Cloudflare Workers only)
297406

298407
*This API is available since Fedify 1.6.0.*
@@ -659,6 +768,9 @@ The following implementations do not yet support native retry:
659768
[`AmqpMessageQueue`]
660769
: Native retry support planned for future release.
661770

771+
[`SqliteMessageQueue`]
772+
: No native retry support (`~MessageQueue.nativeRetrial` is `false`).
773+
662774
`ParallelMessageQueue` inherits the `~MessageQueue.nativeRetrial` value from
663775
the wrapped queue.
664776

mise.toml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ env = { CI = "true" } # Prevent pnpm from prompting for confirmation
2626
depends = ["install"]
2727
run = "pnpm --filter '@fedify/*' --recursive --parallel build"
2828

29+
[tasks.prepare-each]
30+
description = "Prepare specific package(s)"
31+
usage = 'arg "<packages>" help="Package name(s) (without @fedify/ prefix)" var=#true var_min=1'
32+
env = { CI = "true" }
33+
run = '''
34+
for PACKAGE in ${usage_packages}; do
35+
echo "Preparing package: $PACKAGE"
36+
pnpm --filter "@fedify/$PACKAGE" build
37+
done
38+
'''
39+
2940
# Code quality
3041
[tasks.check]
3142
description = "Check code formatting, linting, and type checking"
@@ -65,6 +76,24 @@ fi
6576
description = "Format the codebase"
6677
run = "deno fmt && hongdown --write"
6778

79+
[tasks.check-each]
80+
description = "Check code quality for specific package(s)"
81+
usage = 'arg "<packages>" help="Package name(s) (without @fedify/ prefix)" var=#true var_min=1'
82+
run = '''
83+
for PACKAGE in ${usage_packages}; do
84+
echo "Checking package: $PACKAGE"
85+
PACKAGE_DIR="packages/$PACKAGE"
86+
if [ ! -d "$PACKAGE_DIR" ]; then
87+
echo "Error: Package directory $PACKAGE_DIR not found"
88+
exit 1
89+
fi
90+
91+
deno fmt --check "$PACKAGE_DIR"
92+
deno lint "$PACKAGE_DIR"
93+
deno check "$PACKAGE_DIR"/**/*.ts
94+
done
95+
'''
96+
6897
# Testing
6998
[tasks."test:deno"]
7099
description = "Run the test suite using Deno"
@@ -85,6 +114,21 @@ run = "pnpm run --recursive --filter '!{docs}' test:bun"
85114
description = "Run the test suite across all environments (Deno, Node.js, Bun)"
86115
depends = ["check", "test:deno", "test:node", "test:bun"]
87116

117+
[tasks.test-each]
118+
description = "Run tests for a specific package across all environments"
119+
usage = 'arg "<packages>" help="Package name(s) (without @fedify/ prefix)" var=#true var_min=1'
120+
run = '''
121+
mise run prepare-each ${usage_packages}
122+
mise run check-each ${usage_packages}
123+
124+
for PACKAGE in ${usage_packages}; do
125+
echo "Running tests for package: $PACKAGE"
126+
deno task --filter "@fedify/$PACKAGE" test
127+
pnpm --filter "@fedify/$PACKAGE" test
128+
pnpm --filter "@fedify/$PACKAGE" test:bun
129+
done
130+
'''
131+
88132
# Documentation
89133
[tasks.docs]
90134
description = "Start the documentation development server"

packages/amqp/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
},
6060
"devDependencies": {
6161
"@alinea/suite": "^0.6.3",
62+
"@fedify/testing": "workspace:^",
6263
"@js-temporal/polyfill": "catalog:",
6364
"@std/assert": "catalog:",
6465
"@std/async": "catalog:",

0 commit comments

Comments
 (0)