@fedify/amqp 0.3.0-dev.16 → 0.3.0-dev.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/deno.json +3 -3
- package/deno.lock +14 -14
- package/package.json +6 -6
- package/src/mq.test.ts +80 -7
- package/src/mq.ts +41 -5
package/README.md
CHANGED
|
@@ -30,6 +30,17 @@ const federation = createFederation({
|
|
|
30
30
|
});
|
|
31
31
|
~~~~
|
|
32
32
|
|
|
33
|
+
The `AmqpMessageQueue` constructor accepts options as the second
|
|
34
|
+
parameter, which can be used to configure the message queue:
|
|
35
|
+
|
|
36
|
+
~~~~ typescript
|
|
37
|
+
new AmqpMessageQueue(await connect("amqp://localhost"), {
|
|
38
|
+
queue: "my_queue",
|
|
39
|
+
})
|
|
40
|
+
~~~~
|
|
41
|
+
|
|
42
|
+
For more details, please refer to the docs of [`AmqpMessageQueueOptions`].
|
|
43
|
+
|
|
33
44
|
[JSR]: https://jsr.io/@fedify/amqp
|
|
34
45
|
[JSR badge]: https://jsr.io/badges/@fedify/amqp
|
|
35
46
|
[npm]: https://www.npmjs.com/package/@fedify/amqp
|
|
@@ -41,6 +52,7 @@ const federation = createFederation({
|
|
|
41
52
|
[`KvStore`]: https://jsr.io/@fedify/fedify/doc/federation/~/KvStore
|
|
42
53
|
[`MessageQueue`]: https://jsr.io/@fedify/fedify/doc/federation/~/MessageQueue
|
|
43
54
|
[`AmqpMessageQueue`]: https://jsr.io/@fedify/amqp/doc/mq/~/AmqpMessageQueue
|
|
55
|
+
[`AmqpMessageQueueOptions`] https://jsr.io/@fedify/amqp/doc/mq/~/AmqpMessageQueueOptions
|
|
44
56
|
|
|
45
57
|
|
|
46
58
|
Installation
|
package/deno.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fedify/amqp",
|
|
3
|
-
"version": "0.3.0-dev.
|
|
3
|
+
"version": "0.3.0-dev.17+484159bd",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./mod.ts",
|
|
7
7
|
"./mq": "./src/mq.ts"
|
|
8
8
|
},
|
|
9
9
|
"imports": {
|
|
10
|
-
"@
|
|
11
|
-
"@
|
|
10
|
+
"@fedify/fedify": "jsr:@fedify/fedify@^1.7.0-dev.887+013dabb5",
|
|
11
|
+
"@hongminhee/suite": "jsr:@hongminhee/suite@^0.6.3",
|
|
12
12
|
"@std/assert": "jsr:@std/assert@^1.0.13",
|
|
13
13
|
"@std/async": "jsr:@std/async@^1.0.13",
|
|
14
14
|
"amqplib": "npm:amqplib@^0.10.8",
|
package/deno.lock
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "5",
|
|
3
3
|
"specifiers": {
|
|
4
|
-
"jsr:@alinea/suite@~0.6.2": "0.6.2",
|
|
5
4
|
"jsr:@es-toolkit/es-toolkit@^1.38.0": "1.39.3",
|
|
6
|
-
"jsr:@fedify/fedify@^1.
|
|
5
|
+
"jsr:@fedify/fedify@^1.7.0-dev.887+013dabb5": "1.7.0-pr.251.885+560fd10f",
|
|
6
|
+
"jsr:@hongminhee/suite@~0.6.3": "0.6.3",
|
|
7
7
|
"jsr:@hugoalh/http-header-link@^1.0.2": "1.0.3",
|
|
8
8
|
"jsr:@hugoalh/is-string-singleline@^1.0.4": "1.0.5",
|
|
9
9
|
"jsr:@logtape/logtape@0.11": "0.11.0",
|
|
10
10
|
"jsr:@std/assert@^1.0.13": "1.0.13",
|
|
11
11
|
"jsr:@std/async@^1.0.13": "1.0.13",
|
|
12
12
|
"jsr:@std/internal@^1.0.6": "1.0.8",
|
|
13
|
-
"npm:@
|
|
13
|
+
"npm:@hongminhee/suite@~0.6.3": "0.6.3",
|
|
14
14
|
"npm:@js-temporal/polyfill@~0.5.1": "0.5.1",
|
|
15
15
|
"npm:@multiformats/base-x@^4.0.1": "4.0.1",
|
|
16
16
|
"npm:@opentelemetry/api@^1.9.0": "1.9.0",
|
|
@@ -33,14 +33,11 @@
|
|
|
33
33
|
"npm:url-template@^3.1.1": "3.1.1"
|
|
34
34
|
},
|
|
35
35
|
"jsr": {
|
|
36
|
-
"@alinea/suite@0.6.2": {
|
|
37
|
-
"integrity": "067948595ee65876c3d76ae40fdf6f321a4afab3566da5ca259559490c95da23"
|
|
38
|
-
},
|
|
39
36
|
"@es-toolkit/es-toolkit@1.39.3": {
|
|
40
37
|
"integrity": "edd98c3eac732730b61bac8952a31633a778a236e1be144aa32b220c152d1fbe"
|
|
41
38
|
},
|
|
42
|
-
"@fedify/fedify@1.
|
|
43
|
-
"integrity": "
|
|
39
|
+
"@fedify/fedify@1.7.0-pr.251.885+560fd10f": {
|
|
40
|
+
"integrity": "fb60eaac1b902f32bddbc1ea0171b2bf4903345a497d3aff693ea290f1abc585",
|
|
44
41
|
"dependencies": [
|
|
45
42
|
"jsr:@es-toolkit/es-toolkit",
|
|
46
43
|
"jsr:@hugoalh/http-header-link",
|
|
@@ -60,6 +57,9 @@
|
|
|
60
57
|
"npm:url-template"
|
|
61
58
|
]
|
|
62
59
|
},
|
|
60
|
+
"@hongminhee/suite@0.6.3": {
|
|
61
|
+
"integrity": "429be25d44f30525fcc7ee60e4879e8abc2e09ab625d006c5bd0605e9e8e393c"
|
|
62
|
+
},
|
|
63
63
|
"@hugoalh/http-header-link@1.0.3": {
|
|
64
64
|
"integrity": "3372096a73d755e3351f7fbd7155db7725874c2682a594a655580e3866563024",
|
|
65
65
|
"dependencies": [
|
|
@@ -86,9 +86,6 @@
|
|
|
86
86
|
}
|
|
87
87
|
},
|
|
88
88
|
"npm": {
|
|
89
|
-
"@alinea/suite@0.6.2": {
|
|
90
|
-
"integrity": "sha512-UEYs2OLLb5DCMVE/0C8AAhP+xjKdOujbfL+NIXtcCW0dyeQqxjRVVFu+JH5VsjNxkx9ZNgId0fU4HkoKRU49CQ=="
|
|
91
|
-
},
|
|
92
89
|
"@babel/generator@7.27.5": {
|
|
93
90
|
"integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==",
|
|
94
91
|
"dependencies": [
|
|
@@ -149,6 +146,9 @@
|
|
|
149
146
|
"@fastify/busboy@2.1.1": {
|
|
150
147
|
"integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="
|
|
151
148
|
},
|
|
149
|
+
"@hongminhee/suite@0.6.3": {
|
|
150
|
+
"integrity": "sha512-jW0scPczSP5Mksq49c9stYIApHGijodCFvpKRcFAWImn5WRs4kVlzNwz9QpblaAvoErN8UsfP7OZl3mIk0VoLA=="
|
|
151
|
+
},
|
|
152
152
|
"@jridgewell/gen-mapping@0.3.8": {
|
|
153
153
|
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
|
|
154
154
|
"dependencies": [
|
|
@@ -664,8 +664,8 @@
|
|
|
664
664
|
},
|
|
665
665
|
"workspace": {
|
|
666
666
|
"dependencies": [
|
|
667
|
-
"jsr:@
|
|
668
|
-
"jsr:@
|
|
667
|
+
"jsr:@fedify/fedify@^1.7.0-dev.887+013dabb5",
|
|
668
|
+
"jsr:@hongminhee/suite@~0.6.3",
|
|
669
669
|
"jsr:@std/assert@^1.0.13",
|
|
670
670
|
"jsr:@std/async@^1.0.13",
|
|
671
671
|
"npm:amqplib@~0.10.8",
|
|
@@ -673,7 +673,7 @@
|
|
|
673
673
|
],
|
|
674
674
|
"packageJson": {
|
|
675
675
|
"dependencies": [
|
|
676
|
-
"npm:@
|
|
676
|
+
"npm:@hongminhee/suite@~0.6.3",
|
|
677
677
|
"npm:@js-temporal/polyfill@~0.5.1",
|
|
678
678
|
"npm:@types/amqplib@~0.10.7",
|
|
679
679
|
"npm:tsdown@~0.12.7",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fedify/amqp",
|
|
3
|
-
"version": "0.3.0-dev.
|
|
3
|
+
"version": "0.3.0-dev.17+484159bd",
|
|
4
4
|
"description": "AMQP/RabbitMQ driver for Fedify",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fedify",
|
|
@@ -35,8 +35,12 @@
|
|
|
35
35
|
},
|
|
36
36
|
"./package.json": "./package.json"
|
|
37
37
|
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@fedify/fedify": "^1.7.0-dev.887",
|
|
40
|
+
"amqplib": "^0.10.8"
|
|
41
|
+
},
|
|
38
42
|
"devDependencies": {
|
|
39
|
-
"@
|
|
43
|
+
"@hongminhee/suite": "^0.6.3",
|
|
40
44
|
"@js-temporal/polyfill": "^0.5.1",
|
|
41
45
|
"@std/assert": "npm:@jsr/std__assert@^1.0.13",
|
|
42
46
|
"@std/async": "npm:@jsr/std__async@^1.0.13",
|
|
@@ -44,10 +48,6 @@
|
|
|
44
48
|
"tsdown": "^0.12.7",
|
|
45
49
|
"typescript": "^5.8.3"
|
|
46
50
|
},
|
|
47
|
-
"peerDependencies": {
|
|
48
|
-
"@fedify/fedify": "^1.5.0",
|
|
49
|
-
"amqplib": "^0.10.8"
|
|
50
|
-
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"build": "tsdown",
|
|
53
53
|
"prepublish": "tsdown",
|
package/src/mq.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { suite } from "@
|
|
1
|
+
import { suite } from "@hongminhee/suite";
|
|
2
2
|
import * as temporal from "@js-temporal/polyfill";
|
|
3
|
-
import { assertEquals, assertGreater } from "@std/assert";
|
|
3
|
+
import { assert, assertEquals, assertFalse, assertGreater } from "@std/assert";
|
|
4
4
|
import { delay } from "@std/async/delay";
|
|
5
5
|
// @deno-types="npm:@types/amqplib"
|
|
6
6
|
import { type ChannelModel, connect } from "amqplib";
|
|
@@ -21,13 +21,16 @@ function getConnection(): Promise<ChannelModel> {
|
|
|
21
21
|
return connect(url ?? "amqp://localhost");
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
test("AmqpMessageQueue",
|
|
24
|
+
test("AmqpMessageQueue", {
|
|
25
|
+
sanitizeOps: false,
|
|
26
|
+
sanitizeExit: false,
|
|
27
|
+
sanitizeResources: false,
|
|
28
|
+
}, async () => {
|
|
25
29
|
const conn = await getConnection();
|
|
26
30
|
const conn2 = await getConnection();
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
}_`;
|
|
31
|
+
const randomSuffix = Math.random().toString(36).substring(2);
|
|
32
|
+
const queue = `fedify_queue_${randomSuffix}`;
|
|
33
|
+
const delayedQueuePrefix = `fedify_delayed_${randomSuffix}_`;
|
|
31
34
|
const mq = new AmqpMessageQueue(conn, { queue, delayedQueuePrefix });
|
|
32
35
|
const mq2 = new AmqpMessageQueue(conn2, { queue, delayedQueuePrefix });
|
|
33
36
|
|
|
@@ -99,6 +102,76 @@ test("AmqpMessageQueue", async () => {
|
|
|
99
102
|
await conn2.close();
|
|
100
103
|
});
|
|
101
104
|
|
|
105
|
+
test(
|
|
106
|
+
"AmqpMessageQueue [nativeRetrial: false]",
|
|
107
|
+
{ sanitizeOps: false, sanitizeExit: false, sanitizeResources: false },
|
|
108
|
+
async () => {
|
|
109
|
+
const conn = await getConnection();
|
|
110
|
+
const randomSuffix = Math.random().toString(36).substring(2);
|
|
111
|
+
const queue = `fedify_queue_${randomSuffix}`;
|
|
112
|
+
const delayedQueuePrefix = `fedify_delayed_${randomSuffix}_`;
|
|
113
|
+
const mq = new AmqpMessageQueue(conn, { queue, delayedQueuePrefix });
|
|
114
|
+
assertFalse(mq.nativeRetrial);
|
|
115
|
+
|
|
116
|
+
const controller = new AbortController();
|
|
117
|
+
let i = 0;
|
|
118
|
+
const listening = mq.listen((message: string) => {
|
|
119
|
+
if (message !== "Hello, world!") return;
|
|
120
|
+
if (i++ < 1) {
|
|
121
|
+
throw new Error("Test error to check native retrial");
|
|
122
|
+
}
|
|
123
|
+
}, { signal: controller.signal });
|
|
124
|
+
|
|
125
|
+
await mq.enqueue("Hello, world!");
|
|
126
|
+
|
|
127
|
+
await waitFor(() => i >= 1, 15_000);
|
|
128
|
+
assertEquals(i, 1);
|
|
129
|
+
await delay(5_000);
|
|
130
|
+
|
|
131
|
+
controller.abort();
|
|
132
|
+
await listening;
|
|
133
|
+
await conn.close();
|
|
134
|
+
|
|
135
|
+
assertEquals(i, 1);
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
test(
|
|
140
|
+
"AmqpMessageQueue [nativeRetrial: true]",
|
|
141
|
+
{ sanitizeOps: false, sanitizeExit: false, sanitizeResources: false },
|
|
142
|
+
async () => {
|
|
143
|
+
const conn = await getConnection();
|
|
144
|
+
const randomSuffix = Math.random().toString(36).substring(2);
|
|
145
|
+
const queue = `fedify_queue_${randomSuffix}`;
|
|
146
|
+
const delayedQueuePrefix = `fedify_delayed_${randomSuffix}_`;
|
|
147
|
+
const mq = new AmqpMessageQueue(conn, {
|
|
148
|
+
queue,
|
|
149
|
+
delayedQueuePrefix,
|
|
150
|
+
nativeRetrial: true,
|
|
151
|
+
});
|
|
152
|
+
assert(mq.nativeRetrial);
|
|
153
|
+
|
|
154
|
+
const controller = new AbortController();
|
|
155
|
+
let i = 0;
|
|
156
|
+
const listening = mq.listen((message: string) => {
|
|
157
|
+
if (message !== "Hello, world!") return;
|
|
158
|
+
if (i++ < 1) {
|
|
159
|
+
throw new Error("Test error to check native retrial");
|
|
160
|
+
}
|
|
161
|
+
}, { signal: controller.signal });
|
|
162
|
+
|
|
163
|
+
await mq.enqueue("Hello, world!");
|
|
164
|
+
|
|
165
|
+
await waitFor(() => i > 1, 15_000);
|
|
166
|
+
|
|
167
|
+
controller.abort();
|
|
168
|
+
await listening;
|
|
169
|
+
await conn.close();
|
|
170
|
+
|
|
171
|
+
assertGreater(i, 1);
|
|
172
|
+
},
|
|
173
|
+
);
|
|
174
|
+
|
|
102
175
|
async function waitFor(
|
|
103
176
|
predicate: () => boolean,
|
|
104
177
|
timeoutMs: number,
|
package/src/mq.ts
CHANGED
|
@@ -29,6 +29,22 @@ export interface AmqpMessageQueueOptions {
|
|
|
29
29
|
* @default `true`
|
|
30
30
|
*/
|
|
31
31
|
durable?: boolean;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Whether to use native retrial mechanism. If set to `true`, the queue will
|
|
35
|
+
* not acknowledge messages that are not processed successfully, allowing
|
|
36
|
+
* them to be retried later. If set to `false`, messages will be acknowledged
|
|
37
|
+
* whether they are processed successfully or not.
|
|
38
|
+
*
|
|
39
|
+
* Both approaches have their own advantages and disadvantages. With native
|
|
40
|
+
* retrials, much less chance of losing messages, but timing of retrials is
|
|
41
|
+
* less predictable. With non-native retrials, retrials are handled by Fedify
|
|
42
|
+
* itself, which allows for more control over the timing and behavior of
|
|
43
|
+
* retrials, but may result in lost messages if the process crashes before
|
|
44
|
+
* acknowledging the message.
|
|
45
|
+
* @default `false`
|
|
46
|
+
*/
|
|
47
|
+
nativeRetrial?: boolean;
|
|
32
48
|
}
|
|
33
49
|
|
|
34
50
|
/**
|
|
@@ -53,6 +69,8 @@ export class AmqpMessageQueue implements MessageQueue {
|
|
|
53
69
|
#durable: boolean;
|
|
54
70
|
#senderChannel?: Channel;
|
|
55
71
|
|
|
72
|
+
readonly nativeRetrial: boolean;
|
|
73
|
+
|
|
56
74
|
/**
|
|
57
75
|
* Creates a new `AmqpMessageQueue`.
|
|
58
76
|
* @param connection A connection to the AMQP server.
|
|
@@ -66,6 +84,7 @@ export class AmqpMessageQueue implements MessageQueue {
|
|
|
66
84
|
this.#queue = options.queue ?? "fedify_queue";
|
|
67
85
|
this.#delayedQueuePrefix = options.delayedQueuePrefix ?? "fedify_delayed_";
|
|
68
86
|
this.#durable = options.durable ?? true;
|
|
87
|
+
this.nativeRetrial = options.nativeRetrial ?? false;
|
|
69
88
|
}
|
|
70
89
|
|
|
71
90
|
async #prepareQueue(channel: Channel): Promise<void> {
|
|
@@ -158,16 +177,33 @@ export class AmqpMessageQueue implements MessageQueue {
|
|
|
158
177
|
const reply = await channel.consume(this.#queue, (msg) => {
|
|
159
178
|
if (msg == null) return;
|
|
160
179
|
const message = JSON.parse(msg.content.toString("utf-8"));
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
result
|
|
164
|
-
|
|
165
|
-
|
|
180
|
+
try {
|
|
181
|
+
const result = handler(message);
|
|
182
|
+
if (result instanceof Promise) {
|
|
183
|
+
if (this.nativeRetrial) {
|
|
184
|
+
result
|
|
185
|
+
.then(() => channel.ack(msg))
|
|
186
|
+
.catch(() => channel.nack(msg, undefined, true));
|
|
187
|
+
} else {
|
|
188
|
+
result.finally(() => channel.ack(msg));
|
|
189
|
+
}
|
|
190
|
+
} else if (this.nativeRetrial) {
|
|
191
|
+
channel.ack(msg);
|
|
192
|
+
}
|
|
193
|
+
} catch {
|
|
194
|
+
if (this.nativeRetrial) {
|
|
195
|
+
channel.nack(msg, undefined, true);
|
|
196
|
+
}
|
|
197
|
+
} finally {
|
|
198
|
+
if (!this.nativeRetrial) {
|
|
199
|
+
channel.ack(msg);
|
|
200
|
+
}
|
|
166
201
|
}
|
|
167
202
|
}, {
|
|
168
203
|
noAck: false,
|
|
169
204
|
});
|
|
170
205
|
return await new Promise((resolve) => {
|
|
206
|
+
if (options.signal?.aborted) resolve();
|
|
171
207
|
options.signal?.addEventListener("abort", () => {
|
|
172
208
|
channel.cancel(reply.consumerTag).then(() => {
|
|
173
209
|
channel.close().then(() => resolve());
|