@fedify/fedify 2.4.0-dev.1508 → 2.4.0-dev.1531

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.
Files changed (109) hide show
  1. package/README.md +0 -4
  2. package/dist/{assert-DvVHoIZ0.mjs → assert-OguE97r2.mjs} +1 -1
  3. package/dist/{assert_instance_of-CxoC8bVq.mjs → assert_instance_of-DBC5X09g.mjs} +1 -1
  4. package/dist/{assert_not_equals-C8vcTTA3.mjs → assert_not_equals-DkVK8oqV.mjs} +1 -1
  5. package/dist/{assert_rejects-DAdaaIWM.mjs → assert_rejects-DN60FHPX.mjs} +2 -2
  6. package/dist/{assert_strict_equals-BzqyKuYL.mjs → assert_strict_equals-XEgZAlrj.mjs} +1 -1
  7. package/dist/{assert_throws-zN79lgIC.mjs → assert_throws-BOkhLGYc.mjs} +1 -1
  8. package/dist/{builder-DlWCek6Z.mjs → builder-CmQLlEE1.mjs} +3 -23
  9. package/dist/compat/mod.d.cts +1 -1
  10. package/dist/compat/mod.d.ts +1 -1
  11. package/dist/compat/outgoing-jsonld.test.mjs +3 -3
  12. package/dist/compat/public-audience.test.mjs +3 -3
  13. package/dist/compat/transformers.test.mjs +5 -5
  14. package/dist/{context-CYF8X0ft.d.cts → context-BBVLF7lx.d.cts} +272 -543
  15. package/dist/{context-BF5B4ZDk.d.ts → context-BU6jSQdo.d.ts} +272 -542
  16. package/dist/{context-BBRTgkrs.mjs → context-DVoTs_wM.mjs} +2 -8
  17. package/dist/{deno-nexucTAX.mjs → deno-bSDsva_4.mjs} +1 -1
  18. package/dist/{docloader-DAdEN549.mjs → docloader-DlmmTFJc.mjs} +3 -3
  19. package/dist/federation/builder.test.mjs +5 -5
  20. package/dist/federation/circuit-breaker.test.mjs +5 -5
  21. package/dist/federation/collection.test.mjs +3 -3
  22. package/dist/federation/handler.test.mjs +13 -13
  23. package/dist/federation/idempotency.test.mjs +6 -6
  24. package/dist/federation/inbox.test.mjs +3 -3
  25. package/dist/federation/keycache.test.mjs +5 -5
  26. package/dist/federation/kv.test.mjs +3 -3
  27. package/dist/federation/metrics.test.mjs +4 -4
  28. package/dist/federation/middleware.test.mjs +19 -200
  29. package/dist/federation/mod.cjs +259 -3
  30. package/dist/federation/mod.d.cts +4 -4
  31. package/dist/federation/mod.d.ts +4 -4
  32. package/dist/federation/mod.js +257 -1
  33. package/dist/federation/mq.test.mjs +261 -103
  34. package/dist/federation/negotiation.test.mjs +4 -4
  35. package/dist/federation/retry.test.mjs +3 -3
  36. package/dist/federation/router.test.mjs +5 -5
  37. package/dist/federation/send.test.mjs +10 -10
  38. package/dist/federation/temporal.test.mjs +4 -4
  39. package/dist/federation/webfinger.test.mjs +6 -6
  40. package/dist/{http-inQqS3yx.mjs → http-DwsK1LoE.mjs} +4 -4
  41. package/dist/{http-BCj9dlA_.cjs → http-bsrB-Tun.cjs} +1 -1
  42. package/dist/{http-Dv3147TL.js → http-yVXJ4RhY.js} +1 -1
  43. package/dist/{key-CGrr6-I2.mjs → key-C0aqQKRk.mjs} +2 -2
  44. package/dist/{kv-cache-C3qkFkbt.mjs → kv-cache-D3EQwzQ4.mjs} +1 -1
  45. package/dist/{kv-cache-c8X6svwE.cjs → kv-cache-Dv62PM2B.cjs} +1 -1
  46. package/dist/{kv-cache-C2oTEXyb.js → kv-cache-M2xxXg9M.js} +1 -1
  47. package/dist/{ld-fnWaNT8y.mjs → ld-CBIxjtov.mjs} +3 -3
  48. package/dist/{metrics-C5hMINAa.mjs → metrics-CwNvxtP6.mjs} +1 -1
  49. package/dist/{middleware-Dl2nyVgB.mjs → middleware-BWi34Ga3.mjs} +46 -306
  50. package/dist/{middleware-vQ_ZpgcX.js → middleware-BpMbjN9m.js} +31 -679
  51. package/dist/{middleware-DeVzbg1I.cjs → middleware-C27Pdo18.cjs} +30 -690
  52. package/dist/{middleware-CQlq6ykI.mjs → middleware-DiTi3jC5.mjs} +1 -1
  53. package/dist/{mod-BHEB3xhE.d.cts → mod-C0F6kvgS.d.cts} +1 -1
  54. package/dist/{mod-4WGAuR6X.d.ts → mod-vPYVoa5n.d.ts} +1 -1
  55. package/dist/mod.cjs +6 -6
  56. package/dist/mod.d.cts +5 -5
  57. package/dist/mod.d.ts +5 -5
  58. package/dist/mod.js +5 -5
  59. package/dist/{mq-DXMt_qCY.d.cts → mq-D-nlpY04.d.ts} +0 -34
  60. package/dist/{mq-B5_hohs7.d.ts → mq-D8uSFzxe.d.cts} +0 -34
  61. package/dist/nodeinfo/client.test.mjs +3 -3
  62. package/dist/nodeinfo/handler.test.mjs +5 -5
  63. package/dist/nodeinfo/types.test.mjs +4 -4
  64. package/dist/otel/exporter.test.mjs +3 -3
  65. package/dist/{outgoing-jsonld-BNL8AC14.mjs → outgoing-jsonld-L_DbOaFe.mjs} +1 -1
  66. package/dist/{owner-Bq2eZyO_.mjs → owner-SuWJf5wy.mjs} +2 -2
  67. package/dist/{proof-Buz7I8wj.cjs → proof-BclG-T7h.cjs} +1 -1
  68. package/dist/{proof-DZCjcFCd.mjs → proof-CZI5KQdk.mjs} +5 -5
  69. package/dist/{proof-CTG9azxN.js → proof-DMWr-n3N.js} +1 -1
  70. package/dist/{send-B253S5gN.mjs → send-1EADALaf.mjs} +3 -3
  71. package/dist/sig/accept.test.mjs +1 -1
  72. package/dist/sig/http.test.mjs +8 -8
  73. package/dist/sig/key.test.mjs +6 -6
  74. package/dist/sig/ld.test.mjs +7 -7
  75. package/dist/sig/mod.cjs +2 -2
  76. package/dist/sig/mod.js +2 -2
  77. package/dist/sig/owner.test.mjs +6 -6
  78. package/dist/sig/proof.test.mjs +8 -8
  79. package/dist/{std__assert-Dh6uLf_q.mjs → std__assert-BBjXFNOb.mjs} +4 -4
  80. package/dist/{temporal-d6TshGjq.mjs → temporal-GLaGoMUR.mjs} +1 -1
  81. package/dist/testing/mod.d.mts +8 -1037
  82. package/dist/testing/mod.mjs +2 -3
  83. package/dist/utils/docloader.test.mjs +6 -6
  84. package/dist/utils/kv-cache.test.mjs +1 -1
  85. package/dist/utils/mod.cjs +1 -1
  86. package/dist/utils/mod.js +1 -1
  87. package/package.json +7 -9
  88. package/dist/codec-CmlPqjNX.mjs +0 -103
  89. package/dist/federation/tasks/codec.test.d.mts +0 -2
  90. package/dist/federation/tasks/codec.test.mjs +0 -292
  91. package/dist/federation/tasks/enqueue.test.d.mts +0 -2
  92. package/dist/federation/tasks/enqueue.test.mjs +0 -992
  93. package/dist/federation/tasks/tasks.test.d.mts +0 -2
  94. package/dist/federation/tasks/tasks.test.mjs +0 -555
  95. package/dist/mq-03vS-C9P.mjs +0 -276
  96. package/dist/testing-Dy-WhqpN.mjs +0 -77
  97. /package/dist/{accept-CgDcxvjV.mjs → accept-CPkZzmGN.mjs} +0 -0
  98. /package/dist/{activity-listener-BeTGV3wc.mjs → activity-listener-tztVvlNb.mjs} +0 -0
  99. /package/dist/{assert_equals-PBVKNMJd.mjs → assert_equals-C-ZRDbaf.mjs} +0 -0
  100. /package/dist/{circuit-breaker-hJBB6jwA.mjs → circuit-breaker-CSWsyoef.mjs} +0 -0
  101. /package/dist/{client-B_A6mfn3.mjs → client-ByXmQhYD.mjs} +0 -0
  102. /package/dist/{collection-Dp5ky45w.mjs → collection-Cc3DVAhE.mjs} +0 -0
  103. /package/dist/{keycache-CaOR6NYg.mjs → keycache-BeU0LCII.mjs} +0 -0
  104. /package/dist/{keys-CSYsOMFG.mjs → keys-DGu1NFwu.mjs} +0 -0
  105. /package/dist/{kv-CIvR3Non.mjs → kv-rV3vodCc.mjs} +0 -0
  106. /package/dist/{negotiation-m_L2nyS3.mjs → negotiation-DDstyBvc.mjs} +0 -0
  107. /package/dist/{public-audience-c9zmYKgA.mjs → public-audience-Cvbr2Gzt.mjs} +0 -0
  108. /package/dist/{retry-DI4O-zYl.mjs → retry-CXg_MBI-.mjs} +0 -0
  109. /package/dist/{types-BFowWFTT.mjs → types-J53Kw7so.mjs} +0 -0
@@ -1,992 +0,0 @@
1
- import { Temporal } from "@js-temporal/polyfill";
2
- import "urlpattern-polyfill";
3
- globalThis.addEventListener = () => {};
4
- import { t as MemoryKvStore } from "../../kv-CIvR3Non.mjs";
5
- import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, t as MockQueue } from "../../testing-Dy-WhqpN.mjs";
6
- import { o as createFederation } from "../../middleware-Dl2nyVgB.mjs";
7
- import { n as ParallelMessageQueue } from "../../mq-03vS-C9P.mjs";
8
- import { configure, reset } from "@logtape/logtape";
9
- import { delay } from "es-toolkit";
10
- import { test } from "@fedify/fixture";
11
- import { deepStrictEqual, ok, rejects, strictEqual } from "node:assert/strict";
12
- //#region src/federation/tasks/enqueue.test.ts
13
- /**
14
- * A {@link KvStore} that delegates to an in-memory store but deliberately
15
- * omits `cas`, so that `kv.cas == null`. This drives the deduplication
16
- * fallback branches that fire when no conditional-write primitive exists.
17
- */
18
- var CaslessKvStore = class {
19
- inner = new MemoryKvStore();
20
- get(key) {
21
- return this.inner.get(key);
22
- }
23
- set(key, value, options) {
24
- return this.inner.set(key, value, options);
25
- }
26
- delete(key) {
27
- return this.inner.delete(key);
28
- }
29
- list(prefix) {
30
- return this.inner.list(prefix);
31
- }
32
- };
33
- async function collectKeys(kv, prefix) {
34
- const keys = [];
35
- for await (const { key } of kv.list(prefix)) keys.push(key);
36
- return keys;
37
- }
38
- const TASK_DEDUP_PREFIX = ["_fedify", "taskDeduplication"];
39
- const ACTIVITY_IDEMPOTENCE_PREFIX = ["_fedify", "activityIdempotence"];
40
- const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
41
- test("enqueueTasks() validation and dispatch", async (t) => {
42
- await t.step("rejects an invalid payload at enqueue", async () => {
43
- const queue = new MockQueue();
44
- const federation = createFederation({
45
- ...baseOptions,
46
- queue: { task: queue }
47
- });
48
- const task = federation.defineTask("strictly-typed", {
49
- schema: numberSchema,
50
- handler: () => {}
51
- });
52
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
53
- await rejects(() => ctx.enqueueTask(task, "not a number"), {
54
- name: "TypeError",
55
- message: /Task data failed schema validation/
56
- });
57
- strictEqual(queue.enqueued.length, 0);
58
- });
59
- await t.step("stamps the message envelope", async () => {
60
- const queue = new MockQueue();
61
- const federation = createFederation({
62
- ...baseOptions,
63
- queue: { task: queue }
64
- });
65
- const task = federation.defineTask("envelope", {
66
- schema: stringSchema,
67
- handler: () => {}
68
- });
69
- await federation.createContext(new URL("https://example.com/"), void 0).enqueueTask(task, "payload");
70
- strictEqual(queue.enqueued.length, 1);
71
- const { message } = queue.enqueued[0];
72
- strictEqual(message.type, "task");
73
- strictEqual(message.taskName, "envelope");
74
- strictEqual(message.baseUrl, "https://example.com");
75
- strictEqual(message.attempt, 0);
76
- ok(UUID_RE.test(message.id));
77
- ok(typeof message.data === "string" && message.data.length > 0);
78
- ok(Temporal.Instant.from(message.started) instanceof Temporal.Instant);
79
- ok(typeof message.traceContext === "object" && message.traceContext != null);
80
- });
81
- await t.step("passes delay and orderingKey through", async () => {
82
- const queue = new MockQueue();
83
- const federation = createFederation({
84
- ...baseOptions,
85
- queue: { task: queue }
86
- });
87
- const task = federation.defineTask("delayed", {
88
- schema: stringSchema,
89
- handler: () => {}
90
- });
91
- await federation.createContext(new URL("https://example.com/"), void 0).enqueueTask(task, "payload", {
92
- delay: { seconds: 30 },
93
- orderingKey: "user:alice"
94
- });
95
- strictEqual(queue.enqueued.length, 1);
96
- const { message, options } = queue.enqueued[0];
97
- strictEqual(message.taskName, "delayed");
98
- strictEqual(message.orderingKey, "user:alice");
99
- strictEqual(message.attempt, 0);
100
- ok(options?.delay instanceof Temporal.Duration);
101
- strictEqual(options.delay.total("second"), 30);
102
- strictEqual(options.orderingKey, "user:alice");
103
- });
104
- await t.step("starts the task worker on first enqueue without startQueue()", async () => {
105
- const queue = new MockQueue();
106
- const federation = createFederation({
107
- ...baseOptions,
108
- manuallyStartQueue: false,
109
- queue: { task: queue }
110
- });
111
- const task = federation.defineTask("auto-start", {
112
- schema: stringSchema,
113
- handler: () => {}
114
- });
115
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
116
- await ctx.enqueueTask(task, "first");
117
- strictEqual(queue.listenCount, 1);
118
- await ctx.enqueueTask(task, "second");
119
- strictEqual(queue.listenCount, 1);
120
- strictEqual(queue.enqueued.length, 2);
121
- });
122
- await t.step("throws when the resolved task queue is null", async () => {
123
- const federation = createFederation({ ...baseOptions });
124
- const task = federation.defineTask("queueless", {
125
- schema: stringSchema,
126
- handler: () => {}
127
- });
128
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
129
- await rejects(() => ctx.enqueueTask(task, "data"), {
130
- name: "TypeError",
131
- message: /No message queue is configured/
132
- });
133
- });
134
- await t.step("rejects a handle from another federation at enqueue", async () => {
135
- const queue = new MockQueue();
136
- const federation = createFederation({
137
- ...baseOptions,
138
- queue: { task: queue }
139
- });
140
- const foreignTask = createFederation({
141
- ...baseOptions,
142
- queue: { task: new MockQueue() }
143
- }).defineTask("foreign", {
144
- schema: stringSchema,
145
- handler: () => {}
146
- });
147
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
148
- await rejects(() => ctx.enqueueTask(foreignTask, "data"), {
149
- name: "TypeError",
150
- message: /is not defined on this federation/
151
- });
152
- strictEqual(queue.enqueued.length, 0);
153
- });
154
- await t.step("rejects a same-named handle from another federation", async () => {
155
- const queue = new MockQueue();
156
- const federation = createFederation({
157
- ...baseOptions,
158
- queue: { task: queue }
159
- });
160
- let called = 0;
161
- federation.defineTask("rename", {
162
- schema: numberSchema,
163
- handler: () => {
164
- called++;
165
- }
166
- });
167
- const foreignTask = createFederation({
168
- ...baseOptions,
169
- queue: { task: new MockQueue() }
170
- }).defineTask("rename", {
171
- schema: stringSchema,
172
- handler: () => {}
173
- });
174
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
175
- await rejects(() => ctx.enqueueTask(foreignTask, "not a number"), {
176
- name: "TypeError",
177
- message: /is not defined on this federation/
178
- });
179
- strictEqual(queue.enqueued.length, 0);
180
- strictEqual(called, 0);
181
- });
182
- await t.step("enqueueTaskMany() uses enqueueMany when available", async () => {
183
- const queue = new MockQueue({ supportsEnqueueMany: true });
184
- const federation = createFederation({
185
- ...baseOptions,
186
- queue: { task: queue }
187
- });
188
- const task = federation.defineTask("bulk", {
189
- schema: stringSchema,
190
- handler: () => {}
191
- });
192
- await federation.createContext(new URL("https://example.com/"), void 0).enqueueTaskMany(task, [
193
- "a",
194
- "b",
195
- "c"
196
- ]);
197
- strictEqual(queue.enqueued.length, 0);
198
- strictEqual(queue.enqueuedMany.length, 1);
199
- strictEqual(queue.enqueuedMany[0].messages.length, 3);
200
- });
201
- await t.step("enqueueTaskMany() falls back to parallel enqueues", async () => {
202
- const queue = new MockQueue();
203
- const federation = createFederation({
204
- ...baseOptions,
205
- queue: { task: queue }
206
- });
207
- const task = federation.defineTask("bulk-fallback", {
208
- schema: stringSchema,
209
- handler: () => {}
210
- });
211
- await federation.createContext(new URL("https://example.com/"), void 0).enqueueTaskMany(task, ["a", "b"]);
212
- strictEqual(queue.enqueued.length, 2);
213
- });
214
- await t.step("enqueueTaskMany() with no payloads touches no queue", async () => {
215
- const queue = new MockQueue({ supportsEnqueueMany: true });
216
- const federation = createFederation({
217
- ...baseOptions,
218
- queue: { task: queue }
219
- });
220
- const task = federation.defineTask("bulk-empty", {
221
- schema: stringSchema,
222
- handler: () => {}
223
- });
224
- await federation.createContext(new URL("https://example.com/"), void 0).enqueueTaskMany(task, []);
225
- strictEqual(queue.enqueued.length, 0);
226
- strictEqual(queue.enqueuedMany.length, 0);
227
- });
228
- });
229
- test("task deduplication", async (t) => {
230
- await t.step("forwards the key to a nativeDeduplication queue without writing KV", async () => {
231
- const queue = new MockQueue({ nativeDeduplication: true });
232
- const kv = new MemoryKvStore();
233
- const federation = createFederation({
234
- ...baseOptions,
235
- kv,
236
- queue: { task: queue }
237
- });
238
- const task = federation.defineTask("native-dedup", {
239
- schema: stringSchema,
240
- handler: () => {}
241
- });
242
- await federation.createContext(new URL("https://example.com/"), void 0).enqueueTask(task, "payload", { deduplicationKey: "k" });
243
- strictEqual(queue.enqueued.length, 1);
244
- strictEqual(queue.enqueued[0].options?.deduplicationKey, "k");
245
- strictEqual((await collectKeys(kv, TASK_DEDUP_PREFIX)).length, 0);
246
- });
247
- await t.step("skips a second enqueue with the same key within the TTL", async () => {
248
- const queue = new MockQueue();
249
- const federation = createFederation({
250
- ...baseOptions,
251
- kv: new MemoryKvStore(),
252
- queue: { task: queue }
253
- });
254
- const task = federation.defineTask("kv-dedup", {
255
- schema: stringSchema,
256
- handler: () => {}
257
- });
258
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
259
- await ctx.enqueueTask(task, "first", { deduplicationKey: "k" });
260
- await ctx.enqueueTask(task, "second", { deduplicationKey: "k" });
261
- strictEqual(queue.enqueued.length, 1);
262
- strictEqual(queue.enqueued[0].message.taskName, "kv-dedup");
263
- strictEqual(queue.enqueued[0].options?.deduplicationKey, void 0);
264
- });
265
- await t.step("re-enqueues with the same key after the TTL expires", async () => {
266
- const queue = new MockQueue();
267
- const federation = createFederation({
268
- ...baseOptions,
269
- kv: new MemoryKvStore(),
270
- queue: { task: queue },
271
- taskDeduplicationTtl: { milliseconds: 100 }
272
- });
273
- const task = federation.defineTask("kv-dedup-ttl", {
274
- schema: stringSchema,
275
- handler: () => {}
276
- });
277
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
278
- await ctx.enqueueTask(task, "first", { deduplicationKey: "k" });
279
- strictEqual(queue.enqueued.length, 1);
280
- await delay(300);
281
- await ctx.enqueueTask(task, "second", { deduplicationKey: "k" });
282
- strictEqual(queue.enqueued.length, 2);
283
- });
284
- await t.step("rejects with TypeError when fallback is \"closed\" and no cas exists", async () => {
285
- const queue = new MockQueue();
286
- const federation = createFederation({
287
- ...baseOptions,
288
- kv: new CaslessKvStore(),
289
- queue: { task: queue },
290
- taskDeduplicationFallback: "closed"
291
- });
292
- const task = federation.defineTask("closed-fallback", {
293
- schema: stringSchema,
294
- handler: () => {}
295
- });
296
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
297
- await rejects(() => ctx.enqueueTask(task, "payload", { deduplicationKey: "k" }), { name: "TypeError" });
298
- strictEqual(queue.enqueued.length, 0);
299
- });
300
- await t.step("proceeds when fallback is \"open\" and no cas exists", async () => {
301
- const queue = new MockQueue();
302
- const federation = createFederation({
303
- ...baseOptions,
304
- kv: new CaslessKvStore(),
305
- queue: { task: queue },
306
- taskDeduplicationFallback: "open"
307
- });
308
- const task = federation.defineTask("open-fallback", {
309
- schema: stringSchema,
310
- handler: () => {}
311
- });
312
- await federation.createContext(new URL("https://example.com/"), void 0).enqueueTask(task, "payload", { deduplicationKey: "k" });
313
- strictEqual(queue.enqueued.length, 1);
314
- strictEqual(queue.enqueued[0].options?.deduplicationKey, void 0);
315
- });
316
- await t.step("writes only under taskDeduplication, never activityIdempotence", async () => {
317
- const queue = new MockQueue();
318
- const kv = new MemoryKvStore();
319
- const federation = createFederation({
320
- ...baseOptions,
321
- kv,
322
- queue: { task: queue }
323
- });
324
- const task = federation.defineTask("prefix-isolation", {
325
- schema: stringSchema,
326
- handler: () => {}
327
- });
328
- await federation.createContext(new URL("https://example.com/"), void 0).enqueueTask(task, "payload", { deduplicationKey: "k" });
329
- strictEqual((await collectKeys(kv, TASK_DEDUP_PREFIX)).length, 1);
330
- strictEqual((await collectKeys(kv, ACTIVITY_IDEMPOTENCE_PREFIX)).length, 0);
331
- });
332
- await t.step("applies one batch-level key to enqueueTaskMany", async () => {
333
- const queue = new MockQueue({ supportsEnqueueMany: true });
334
- const federation = createFederation({
335
- ...baseOptions,
336
- kv: new MemoryKvStore(),
337
- queue: { task: queue }
338
- });
339
- const task = federation.defineTask("batch-dedup", {
340
- schema: stringSchema,
341
- handler: () => {}
342
- });
343
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
344
- await ctx.enqueueTaskMany(task, [
345
- "a",
346
- "b",
347
- "c"
348
- ], { deduplicationKey: "batch" });
349
- await ctx.enqueueTaskMany(task, [
350
- "a",
351
- "b",
352
- "c"
353
- ], { deduplicationKey: "batch" });
354
- strictEqual(queue.enqueuedMany.length, 1);
355
- strictEqual(queue.enqueuedMany[0].messages.length, 3);
356
- });
357
- });
358
- test("task deduplication validates every payload before reserving the key", async () => {
359
- const queue = new MockQueue();
360
- const kv = new MemoryKvStore();
361
- const federation = createFederation({
362
- ...baseOptions,
363
- kv,
364
- queue: { task: queue }
365
- });
366
- const task = federation.defineTask("dedup-validation", {
367
- schema: stringSchema,
368
- handler: () => {}
369
- });
370
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
371
- await rejects(() => ctx.enqueueTask(task, 123, { deduplicationKey: "k" }));
372
- strictEqual(queue.enqueued.length, 0);
373
- deepStrictEqual(await collectKeys(kv, TASK_DEDUP_PREFIX), []);
374
- await ctx.enqueueTask(task, "valid", { deduplicationKey: "k" });
375
- strictEqual(queue.enqueued.length, 1);
376
- deepStrictEqual(await collectKeys(kv, TASK_DEDUP_PREFIX), [[...TASK_DEDUP_PREFIX, "k"]]);
377
- await ctx.enqueueTask(task, "duplicate", { deduplicationKey: "k" });
378
- strictEqual(queue.enqueued.length, 1);
379
- });
380
- test("native task batch deduplication is one enqueueMany operation per call", async () => {
381
- class NativeBatchDeduplicatingQueue {
382
- nativeDeduplication = true;
383
- #seen = /* @__PURE__ */ new Set();
384
- attempts = [];
385
- accepted = [];
386
- enqueue() {
387
- throw new Error("A multi-item native batch must use enqueueMany().");
388
- }
389
- enqueueMany(messages, options) {
390
- const key = options?.deduplicationKey;
391
- if (key == null) throw new TypeError("Native batch enqueue requires a deduplication key.");
392
- this.attempts.push({
393
- messages,
394
- options
395
- });
396
- if (this.#seen.has(key)) return Promise.resolve();
397
- this.#seen.add(key);
398
- this.accepted.push({
399
- messages,
400
- options
401
- });
402
- return Promise.resolve();
403
- }
404
- listen() {
405
- return Promise.resolve();
406
- }
407
- }
408
- const queue = new NativeBatchDeduplicatingQueue();
409
- const kv = new MemoryKvStore();
410
- const federation = createFederation({
411
- ...baseOptions,
412
- kv,
413
- queue: { task: queue }
414
- });
415
- const task = federation.defineTask("native-batch-dedup", {
416
- schema: stringSchema,
417
- handler: () => {}
418
- });
419
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
420
- await ctx.enqueueTaskMany(task, [
421
- "a1",
422
- "a2",
423
- "a3"
424
- ], { deduplicationKey: "batch-a" });
425
- await ctx.enqueueTaskMany(task, [
426
- "duplicate1",
427
- "duplicate2",
428
- "duplicate3"
429
- ], { deduplicationKey: "batch-a" });
430
- await ctx.enqueueTaskMany(task, [
431
- "b1",
432
- "b2",
433
- "b3"
434
- ], { deduplicationKey: "batch-b" });
435
- strictEqual(queue.attempts.length, 3);
436
- deepStrictEqual(queue.attempts.map(({ messages }) => messages.length), [
437
- 3,
438
- 3,
439
- 3
440
- ]);
441
- deepStrictEqual(queue.attempts.map(({ options }) => options?.deduplicationKey), [
442
- "batch-a",
443
- "batch-a",
444
- "batch-b"
445
- ]);
446
- strictEqual(queue.accepted.length, 2);
447
- deepStrictEqual(queue.accepted.map(({ messages }) => messages.length), [3, 3]);
448
- deepStrictEqual(queue.accepted.map(({ options }) => options?.deduplicationKey), ["batch-a", "batch-b"]);
449
- deepStrictEqual(await collectKeys(kv, TASK_DEDUP_PREFIX), []);
450
- });
451
- test("native task batch deduplication rejects without enqueueMany", async () => {
452
- const queue = new MockQueue({ nativeDeduplication: true });
453
- const kv = new MemoryKvStore();
454
- const federation = createFederation({
455
- ...baseOptions,
456
- kv,
457
- queue: { task: queue }
458
- });
459
- const task = federation.defineTask("native-batch-without-enqueue-many", {
460
- schema: stringSchema,
461
- handler: () => {}
462
- });
463
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
464
- await rejects(() => ctx.enqueueTaskMany(task, [
465
- "a",
466
- "b",
467
- "c"
468
- ], { deduplicationKey: "batch" }), {
469
- name: "TypeError",
470
- message: /enqueueMany/
471
- });
472
- strictEqual(queue.enqueued.length, 0);
473
- strictEqual(queue.enqueuedMany.length, 0);
474
- deepStrictEqual(await collectKeys(kv, TASK_DEDUP_PREFIX), []);
475
- await ctx.enqueueTaskMany(task, ["single"], { deduplicationKey: "single" });
476
- strictEqual(queue.enqueued.length, 1);
477
- strictEqual(queue.enqueued[0].options?.deduplicationKey, "single");
478
- });
479
- test("deduplication - native batch capability errors precede payload validation", async () => {
480
- let validationCalls = 0;
481
- const schema = makeSchema((data) => {
482
- validationCalls++;
483
- return typeof data === "string";
484
- });
485
- const queue = new MockQueue({ nativeDeduplication: true });
486
- const kv = new MemoryKvStore();
487
- const federation = createFederation({
488
- ...baseOptions,
489
- kv,
490
- queue: { task: queue }
491
- });
492
- const task = federation.defineTask("native-batch-capability-order", {
493
- schema,
494
- handler: () => {}
495
- });
496
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
497
- let caught;
498
- try {
499
- await ctx.enqueueTaskMany(task, [
500
- 1,
501
- 2,
502
- 3
503
- ], { deduplicationKey: "batch" });
504
- } catch (error) {
505
- caught = error;
506
- }
507
- strictEqual(validationCalls, 0);
508
- ok(caught instanceof TypeError);
509
- ok(caught.message.includes("enqueueMany"));
510
- strictEqual(queue.enqueued.length, 0);
511
- strictEqual(queue.enqueuedMany.length, 0);
512
- deepStrictEqual(await collectKeys(kv, TASK_DEDUP_PREFIX), []);
513
- });
514
- test("closed deduplication fallback errors precede payload validation", async () => {
515
- let validationCalls = 0;
516
- const schema = makeSchema((data) => {
517
- validationCalls++;
518
- return typeof data === "string";
519
- });
520
- const queue = new MockQueue();
521
- const kv = new CaslessKvStore();
522
- const federation = createFederation({
523
- ...baseOptions,
524
- kv,
525
- queue: { task: queue },
526
- taskDeduplicationFallback: "closed"
527
- });
528
- const task = federation.defineTask("closed-fallback-order", {
529
- schema,
530
- handler: () => {}
531
- });
532
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
533
- let caught;
534
- try {
535
- await ctx.enqueueTask(task, 1, { deduplicationKey: "k" });
536
- } catch (error) {
537
- caught = error;
538
- }
539
- strictEqual(validationCalls, 0);
540
- ok(caught instanceof TypeError);
541
- ok(caught.message.includes("conditional write"));
542
- strictEqual(queue.enqueued.length, 0);
543
- strictEqual(queue.enqueuedMany.length, 0);
544
- deepStrictEqual(await collectKeys(kv, TASK_DEDUP_PREFIX), []);
545
- });
546
- /**
547
- * A {@link MessageQueue} that fails its first enqueue—single or batch—with a
548
- * transient error, then records every later enqueue. One class covers both the
549
- * `enqueue()` and `enqueueMany()` rollback paths; each test instantiates its own
550
- * copy, so the one-shot `#failNext` flag never leaks between them.
551
- */
552
- var FlakyQueue = class {
553
- nativeDeduplication = false;
554
- #failNext = true;
555
- enqueued = [];
556
- enqueuedMany = [];
557
- #failOnce() {
558
- if (!this.#failNext) return false;
559
- this.#failNext = false;
560
- return true;
561
- }
562
- enqueue(message) {
563
- if (this.#failOnce()) return Promise.reject(/* @__PURE__ */ new Error("transient backend failure"));
564
- this.enqueued.push(message);
565
- return Promise.resolve();
566
- }
567
- enqueueMany(messages) {
568
- if (this.#failOnce()) return Promise.reject(/* @__PURE__ */ new Error("transient backend failure"));
569
- this.enqueuedMany.push([...messages]);
570
- return Promise.resolve();
571
- }
572
- listen() {
573
- return Promise.resolve();
574
- }
575
- };
576
- test("a failed enqueue rolls back its dedup marker so the retry is not dropped", async () => {
577
- const queue = new FlakyQueue();
578
- const kv = new MemoryKvStore();
579
- const federation = createFederation({
580
- ...baseOptions,
581
- kv,
582
- queue: { task: queue }
583
- });
584
- const task = federation.defineTask("flaky-enqueue", {
585
- schema: stringSchema,
586
- handler: () => {}
587
- });
588
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
589
- const markerKey = [...TASK_DEDUP_PREFIX, "k"];
590
- await rejects(() => ctx.enqueueTask(task, "first", { deduplicationKey: "k" }), { message: /transient backend failure/ });
591
- strictEqual(queue.enqueued.length, 0);
592
- strictEqual(await kv.get(markerKey), void 0);
593
- await ctx.enqueueTask(task, "first-retry", { deduplicationKey: "k" });
594
- strictEqual(queue.enqueued.length, 1);
595
- ok(await kv.get(markerKey) != null);
596
- await ctx.enqueueTask(task, "duplicate", { deduplicationKey: "k" });
597
- strictEqual(queue.enqueued.length, 1);
598
- });
599
- test("a failed batch enqueue rolls back its dedup marker so the retry is not dropped", async () => {
600
- const queue = new FlakyQueue();
601
- const kv = new MemoryKvStore();
602
- const federation = createFederation({
603
- ...baseOptions,
604
- kv,
605
- queue: { task: queue }
606
- });
607
- const task = federation.defineTask("flaky-batch-enqueue", {
608
- schema: stringSchema,
609
- handler: () => {}
610
- });
611
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
612
- const markerKey = [...TASK_DEDUP_PREFIX, "batch"];
613
- await rejects(() => ctx.enqueueTaskMany(task, ["first", "second"], { deduplicationKey: "batch" }), { message: /transient backend failure/ });
614
- strictEqual(queue.enqueuedMany.length, 0);
615
- strictEqual(await kv.get(markerKey), void 0);
616
- await ctx.enqueueTaskMany(task, ["first-retry", "second-retry"], { deduplicationKey: "batch" });
617
- strictEqual(queue.enqueuedMany.length, 1);
618
- strictEqual(queue.enqueuedMany[0].length, 2);
619
- ok(await kv.get(markerKey) != null);
620
- await ctx.enqueueTaskMany(task, ["duplicate-first", "duplicate-second"], { deduplicationKey: "batch" });
621
- strictEqual(queue.enqueuedMany.length, 1);
622
- });
623
- test("a stale rollback does not clear a marker another enqueue re-claimed", async () => {
624
- const kv = new MemoryKvStore();
625
- const markerKey = [...TASK_DEDUP_PREFIX, "k"];
626
- let signalFirstEntered;
627
- const firstEntered = new Promise((resolve) => {
628
- signalFirstEntered = resolve;
629
- });
630
- let releaseFirst;
631
- const firstReleased = new Promise((resolve) => {
632
- releaseFirst = resolve;
633
- });
634
- class BlockingThenFailingQueue {
635
- nativeDeduplication = false;
636
- #calls = 0;
637
- async enqueue() {
638
- this.#calls++;
639
- if (this.#calls === 1) {
640
- signalFirstEntered();
641
- await firstReleased;
642
- throw new Error("transient backend failure");
643
- }
644
- }
645
- listen() {
646
- return Promise.resolve();
647
- }
648
- }
649
- const queue = new BlockingThenFailingQueue();
650
- const federation = createFederation({
651
- ...baseOptions,
652
- kv,
653
- queue: { task: queue },
654
- taskDeduplicationTtl: { milliseconds: 1 }
655
- });
656
- const task = federation.defineTask("stale-rollback", {
657
- schema: stringSchema,
658
- handler: () => {}
659
- });
660
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
661
- const first = ctx.enqueueTask(task, "first", { deduplicationKey: "k" });
662
- await firstEntered;
663
- await delay(20);
664
- await ctx.enqueueTask(task, "second", { deduplicationKey: "k" });
665
- const secondToken = await kv.get(markerKey);
666
- ok(secondToken != null);
667
- releaseFirst();
668
- await rejects(() => first, { message: /transient backend failure/ });
669
- strictEqual(await kv.get(markerKey), secondToken);
670
- });
671
- test("a multi-item batch dedup without enqueueMany is rejected on the cas path", async () => {
672
- const queue = new MockQueue();
673
- const kv = new MemoryKvStore();
674
- const federation = createFederation({
675
- ...baseOptions,
676
- kv,
677
- queue: { task: queue }
678
- });
679
- const task = federation.defineTask("cas-batch-without-enqueue-many", {
680
- schema: stringSchema,
681
- handler: () => {}
682
- });
683
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
684
- await rejects(() => ctx.enqueueTaskMany(task, [
685
- "a",
686
- "b",
687
- "c"
688
- ], { deduplicationKey: "batch" }), {
689
- name: "TypeError",
690
- message: /enqueueMany/
691
- });
692
- strictEqual(queue.enqueued.length, 0);
693
- strictEqual(queue.enqueuedMany.length, 0);
694
- deepStrictEqual(await collectKeys(kv, TASK_DEDUP_PREFIX), []);
695
- await ctx.enqueueTaskMany(task, ["single"], { deduplicationKey: "single" });
696
- strictEqual(queue.enqueued.length, 1);
697
- });
698
- test("a failed rollback is swallowed; the original enqueue error reaches the caller", async () => {
699
- class ClearFailingKvStore {
700
- inner = new MemoryKvStore();
701
- clearAttempts = 0;
702
- get(key) {
703
- return this.inner.get(key);
704
- }
705
- set(key, value, options) {
706
- return this.inner.set(key, value, options);
707
- }
708
- delete(key) {
709
- return this.inner.delete(key);
710
- }
711
- list(prefix) {
712
- return this.inner.list(prefix);
713
- }
714
- cas(key, expectedValue, newValue, options) {
715
- if (newValue === void 0) {
716
- this.clearAttempts++;
717
- return Promise.reject(/* @__PURE__ */ new Error("kv cas clear failed"));
718
- }
719
- return this.inner.cas(key, expectedValue, newValue, options);
720
- }
721
- }
722
- const queue = new FlakyQueue();
723
- const kv = new ClearFailingKvStore();
724
- const federation = createFederation({
725
- ...baseOptions,
726
- kv,
727
- queue: { task: queue }
728
- });
729
- const task = federation.defineTask("rollback-failure", {
730
- schema: stringSchema,
731
- handler: () => {}
732
- });
733
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
734
- await rejects(() => ctx.enqueueTask(task, "first", { deduplicationKey: "k" }), { message: /transient backend failure/ });
735
- strictEqual(queue.enqueued.length, 0);
736
- strictEqual(kv.clearAttempts, 1);
737
- });
738
- /**
739
- * A native-deduplication backend that drops repeat-key single enqueues and
740
- * does **not** implement `enqueueMany`. Wrapping it in
741
- * {@link ParallelMessageQueue} used to fan a batch out to one `enqueue()` per
742
- * message, all carrying the same `deduplicationKey`, so the backend collapsed
743
- * the whole batch onto its first message.
744
- */
745
- var NativeDedupNoBulkQueue = class {
746
- nativeDeduplication = true;
747
- #seen = /* @__PURE__ */ new Set();
748
- enqueued = [];
749
- enqueue(message, options) {
750
- const key = options?.deduplicationKey;
751
- if (key != null) {
752
- if (this.#seen.has(key)) return Promise.resolve();
753
- this.#seen.add(key);
754
- }
755
- this.enqueued.push({
756
- message,
757
- options
758
- });
759
- return Promise.resolve();
760
- }
761
- listen() {
762
- return Promise.resolve();
763
- }
764
- };
765
- test("a deduplicated batch over a ParallelMessageQueue wrapping a native, no-enqueueMany backend is rejected, not collapsed", async () => {
766
- const backend = new NativeDedupNoBulkQueue();
767
- const queue = new ParallelMessageQueue(backend, 5);
768
- const kv = new MemoryKvStore();
769
- const federation = createFederation({
770
- ...baseOptions,
771
- kv,
772
- queue: { task: queue }
773
- });
774
- const task = federation.defineTask("parallel-native-no-bulk", {
775
- schema: stringSchema,
776
- handler: () => {}
777
- });
778
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
779
- await rejects(() => ctx.enqueueTaskMany(task, [
780
- "a",
781
- "b",
782
- "c"
783
- ], { deduplicationKey: "batch" }), {
784
- name: "TypeError",
785
- message: /enqueueMany/
786
- });
787
- strictEqual(backend.enqueued.length, 0);
788
- deepStrictEqual(await collectKeys(kv, TASK_DEDUP_PREFIX), []);
789
- await ctx.enqueueTaskMany(task, ["solo"], { deduplicationKey: "solo" });
790
- strictEqual(backend.enqueued.length, 1);
791
- strictEqual(backend.enqueued[0].options?.deduplicationKey, "solo");
792
- });
793
- test("a deduplicated batch over a ParallelMessageQueue wrapping a non-native, no-enqueueMany backend is rejected on the cas path", async () => {
794
- const backend = new MockQueue();
795
- const queue = new ParallelMessageQueue(backend, 5);
796
- const kv = new MemoryKvStore();
797
- const federation = createFederation({
798
- ...baseOptions,
799
- kv,
800
- queue: { task: queue }
801
- });
802
- const task = federation.defineTask("parallel-cas-no-bulk", {
803
- schema: stringSchema,
804
- handler: () => {}
805
- });
806
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
807
- await rejects(() => ctx.enqueueTaskMany(task, [
808
- "a",
809
- "b",
810
- "c"
811
- ], { deduplicationKey: "batch" }), {
812
- name: "TypeError",
813
- message: /enqueueMany/
814
- });
815
- strictEqual(backend.enqueued.length, 0);
816
- deepStrictEqual(await collectKeys(kv, TASK_DEDUP_PREFIX), []);
817
- await ctx.enqueueTaskMany(task, ["solo"], { deduplicationKey: "solo" });
818
- strictEqual(backend.enqueued.length, 1);
819
- });
820
- test("a deduplicated batch over a ParallelMessageQueue wrapping a native enqueueMany backend forwards the key atomically", async () => {
821
- class NativeBatchQueue {
822
- nativeDeduplication = true;
823
- #seen = /* @__PURE__ */ new Set();
824
- batches = [];
825
- enqueue() {
826
- throw new Error("A multi-item native batch must use enqueueMany().");
827
- }
828
- enqueueMany(messages, options) {
829
- const key = options?.deduplicationKey;
830
- if (key != null && this.#seen.has(key)) return Promise.resolve();
831
- if (key != null) this.#seen.add(key);
832
- this.batches.push({
833
- messages,
834
- options
835
- });
836
- return Promise.resolve();
837
- }
838
- listen() {
839
- return Promise.resolve();
840
- }
841
- }
842
- const backend = new NativeBatchQueue();
843
- const queue = new ParallelMessageQueue(backend, 5);
844
- const kv = new MemoryKvStore();
845
- const federation = createFederation({
846
- ...baseOptions,
847
- kv,
848
- queue: { task: queue }
849
- });
850
- const task = federation.defineTask("parallel-native-bulk", {
851
- schema: stringSchema,
852
- handler: () => {}
853
- });
854
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
855
- await ctx.enqueueTaskMany(task, [
856
- "a",
857
- "b",
858
- "c"
859
- ], { deduplicationKey: "batch" });
860
- await ctx.enqueueTaskMany(task, [
861
- "x",
862
- "y",
863
- "z"
864
- ], { deduplicationKey: "batch" });
865
- strictEqual(backend.batches.length, 1);
866
- strictEqual(backend.batches[0].messages.length, 3);
867
- strictEqual(backend.batches[0].options?.deduplicationKey, "batch");
868
- deepStrictEqual(await collectKeys(kv, TASK_DEDUP_PREFIX), []);
869
- });
870
- test("an \"open\" fallback fans out a multi-item batch without enqueueMany instead of rejecting it", async () => {
871
- const queue = new MockQueue();
872
- const kv = new CaslessKvStore();
873
- const federation = createFederation({
874
- ...baseOptions,
875
- kv,
876
- queue: { task: queue },
877
- taskDeduplicationFallback: "open"
878
- });
879
- const task = federation.defineTask("open-batch-fanout", {
880
- schema: stringSchema,
881
- handler: () => {}
882
- });
883
- await federation.createContext(new URL("https://example.com/"), void 0).enqueueTaskMany(task, [
884
- "a",
885
- "b",
886
- "c"
887
- ], { deduplicationKey: "batch" });
888
- strictEqual(queue.enqueued.length, 3);
889
- for (const { options } of queue.enqueued) strictEqual(options?.deduplicationKey, void 0);
890
- deepStrictEqual(await collectKeys(kv.inner, TASK_DEDUP_PREFIX), []);
891
- });
892
- test("an \"open\" fallback logs a debug record when it ignores the key", async () => {
893
- const records = [];
894
- await reset();
895
- try {
896
- await configure({
897
- sinks: { buffer(record) {
898
- records.push(record);
899
- } },
900
- filters: {},
901
- loggers: [{
902
- category: [],
903
- lowestLevel: "debug",
904
- sinks: ["buffer"]
905
- }]
906
- });
907
- const queue = new MockQueue();
908
- const federation = createFederation({
909
- ...baseOptions,
910
- kv: new CaslessKvStore(),
911
- queue: { task: queue },
912
- taskDeduplicationFallback: "open"
913
- });
914
- const task = federation.defineTask("open-debug-log", {
915
- schema: stringSchema,
916
- handler: () => {}
917
- });
918
- await federation.createContext(new URL("https://example.com/"), void 0).enqueueTask(task, "payload", { deduplicationKey: "k" });
919
- strictEqual(records.filter((record) => record.level === "debug" && record.properties.deduplicationKey === "k" && record.properties.taskName === "open-debug-log").length, 1);
920
- } finally {
921
- await reset();
922
- }
923
- });
924
- test("two concurrent enqueues sharing a key: exactly one wins the cas claim", async () => {
925
- let signalEntered;
926
- const entered = new Promise((resolve) => {
927
- signalEntered = resolve;
928
- });
929
- let release;
930
- const released = new Promise((resolve) => {
931
- release = resolve;
932
- });
933
- class BlockingQueue {
934
- nativeDeduplication = false;
935
- enqueued = [];
936
- #first = true;
937
- async enqueue(message) {
938
- if (this.#first) {
939
- this.#first = false;
940
- signalEntered();
941
- await released;
942
- }
943
- this.enqueued.push(message);
944
- }
945
- listen() {
946
- return Promise.resolve();
947
- }
948
- }
949
- const queue = new BlockingQueue();
950
- const kv = new MemoryKvStore();
951
- const federation = createFederation({
952
- ...baseOptions,
953
- kv,
954
- queue: { task: queue }
955
- });
956
- const task = federation.defineTask("concurrent-claim", {
957
- schema: stringSchema,
958
- handler: () => {}
959
- });
960
- const ctx = federation.createContext(new URL("https://example.com/"), void 0);
961
- const first = ctx.enqueueTask(task, "first", { deduplicationKey: "k" });
962
- await entered;
963
- await ctx.enqueueTask(task, "second", { deduplicationKey: "k" });
964
- release();
965
- await first;
966
- strictEqual(queue.enqueued.length, 1);
967
- await ctx.enqueueTask(task, "third", { deduplicationKey: "k" });
968
- strictEqual(queue.enqueued.length, 1);
969
- });
970
- test("a native enqueue forwards orderingKey and deduplicationKey together", async () => {
971
- const queue = new MockQueue({ nativeDeduplication: true });
972
- const federation = createFederation({
973
- ...baseOptions,
974
- kv: new MemoryKvStore(),
975
- queue: { task: queue }
976
- });
977
- const task = federation.defineTask("native-both-keys", {
978
- schema: stringSchema,
979
- handler: () => {}
980
- });
981
- await federation.createContext(new URL("https://example.com/"), void 0).enqueueTask(task, "payload", {
982
- orderingKey: "user:alice",
983
- deduplicationKey: "dedup:alice"
984
- });
985
- strictEqual(queue.enqueued.length, 1);
986
- const { message, options } = queue.enqueued[0];
987
- strictEqual(message.orderingKey, "user:alice");
988
- strictEqual(options?.orderingKey, "user:alice");
989
- strictEqual(options?.deduplicationKey, "dedup:alice");
990
- });
991
- //#endregion
992
- export {};