@fedify/fedify 2.4.0-dev.1422 → 2.4.0-dev.1488
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 +4 -3
- package/dist/{assert-DikXweDx.mjs → assert-OguE97r2.mjs} +1 -1
- package/dist/{assert_instance_of-C4Ri6VuN.mjs → assert_instance_of-DBC5X09g.mjs} +1 -1
- package/dist/{assert_not_equals--wG9hV7u.mjs → assert_not_equals-DkVK8oqV.mjs} +1 -1
- package/dist/{assert_rejects-DQP-q39h.mjs → assert_rejects-DN60FHPX.mjs} +2 -2
- package/dist/{assert_strict_equals-Dmjbg-bA.mjs → assert_strict_equals-XEgZAlrj.mjs} +1 -1
- package/dist/{assert_throws-4NwKEy2q.mjs → assert_throws-BOkhLGYc.mjs} +1 -1
- package/dist/{builder-Gef_ZeDE.mjs → builder-Dcr6y6XD.mjs} +3 -23
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/outgoing-jsonld.test.mjs +3 -3
- package/dist/compat/public-audience.test.mjs +3 -3
- package/dist/compat/transformers.test.mjs +5 -5
- package/dist/{context-BuGH_Vft.d.cts → context-BBVLF7lx.d.cts} +70 -286
- package/dist/{context-CLtZxCu6.d.ts → context-BU6jSQdo.d.ts} +70 -285
- package/dist/{context-BrIOaBSs.mjs → context-DVoTs_wM.mjs} +1 -7
- package/dist/{deno-BmD7XRaO.mjs → deno-BCFGf8NR.mjs} +1 -1
- package/dist/{docloader-B8AJowCx.mjs → docloader-DWEwaGqF.mjs} +2 -2
- package/dist/federation/builder.test.mjs +5 -5
- package/dist/federation/circuit-breaker.test.mjs +5 -5
- package/dist/federation/collection.test.mjs +3 -3
- package/dist/federation/handler.test.mjs +13 -13
- package/dist/federation/idempotency.test.mjs +6 -6
- package/dist/federation/inbox.test.mjs +3 -3
- package/dist/federation/keycache.test.mjs +5 -5
- package/dist/federation/kv.test.mjs +3 -3
- package/dist/federation/metrics.test.mjs +4 -4
- package/dist/federation/middleware.test.mjs +19 -200
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +3 -3
- package/dist/federation/mod.d.ts +3 -3
- package/dist/federation/mod.js +1 -1
- package/dist/federation/mq.test.mjs +260 -5
- package/dist/federation/negotiation.test.mjs +4 -4
- package/dist/federation/retry.test.mjs +3 -3
- package/dist/federation/router.test.mjs +5 -5
- package/dist/federation/send.test.mjs +10 -10
- package/dist/federation/temporal.test.mjs +4 -4
- package/dist/federation/webfinger.test.mjs +6 -6
- package/dist/{http-BCSQJ_gv.cjs → http-DLqxhQg1.cjs} +17 -7
- package/dist/{http-CJ0WNyqA.js → http-DaVoLyyA.js} +17 -7
- package/dist/{http-GQoPA_YT.mjs → http-RriziGDE.mjs} +4 -4
- package/dist/{key-BLy_TeTg.mjs → key-Bkg8Zg36.mjs} +18 -8
- package/dist/{kv-cache-pw-kBdVz.mjs → kv-cache-BR--9V4L.mjs} +1 -1
- package/dist/{kv-cache-CjEcb9qH.js → kv-cache-DxlFLMiU.js} +1 -1
- package/dist/{kv-cache-DXwu7BtY.cjs → kv-cache-Fw-iwKjy.cjs} +1 -1
- package/dist/{ld-CWgxPlZq.mjs → ld-BjsmbqO7.mjs} +3 -3
- package/dist/{metrics-DUKoLd_E.mjs → metrics-BLoa2pab.mjs} +1 -1
- package/dist/{middleware-PZaroDUk.mjs → middleware-D3kuDjvb.mjs} +44 -186
- package/dist/{middleware-Dq-HhJHd.mjs → middleware-DNR3YBCi.mjs} +1 -1
- package/dist/{middleware-BiCk4wGY.cjs → middleware-V-r1QH5Z.cjs} +31 -281
- package/dist/{middleware-2Us0G3Iy.js → middleware-_9t5HVBi.js} +31 -281
- package/dist/{mod-DRokcHeU.d.cts → mod-C0F6kvgS.d.cts} +1 -1
- package/dist/{mod-r-1xOW_C.d.ts → mod-vPYVoa5n.d.ts} +1 -1
- package/dist/mod.cjs +4 -4
- package/dist/mod.d.cts +4 -4
- package/dist/mod.d.ts +4 -4
- package/dist/mod.js +4 -4
- package/dist/nodeinfo/client.test.mjs +3 -3
- package/dist/nodeinfo/handler.test.mjs +5 -5
- package/dist/nodeinfo/types.test.mjs +4 -4
- package/dist/otel/exporter.test.mjs +3 -3
- package/dist/{outgoing-jsonld-BgFLCJQ_.mjs → outgoing-jsonld-L_DbOaFe.mjs} +1 -1
- package/dist/{owner-U0Z8Ex6i.mjs → owner-NK2p1ihR.mjs} +2 -2
- package/dist/{proof-DA3hLGSO.cjs → proof-2WI6mMbC.cjs} +1 -1
- package/dist/{proof-nb5tfciW.mjs → proof-CRXqo703.mjs} +5 -5
- package/dist/{proof-f7K32nz7.js → proof-D7WVwQzs.js} +1 -1
- package/dist/{send-CLfW5Fdf.mjs → send-CTaPzl8L.mjs} +3 -3
- package/dist/sig/accept.test.mjs +1 -1
- package/dist/sig/http.test.mjs +8 -8
- package/dist/sig/key.test.mjs +56 -6
- package/dist/sig/ld.test.mjs +7 -7
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +6 -6
- package/dist/sig/proof.test.mjs +8 -8
- package/dist/{std__assert-BTEgfoJo.mjs → std__assert-BBjXFNOb.mjs} +4 -4
- package/dist/{temporal-2ry_jdCK.mjs → temporal-C8M0Y7v9.mjs} +1 -1
- package/dist/testing/mod.d.mts +7 -378
- package/dist/testing/mod.mjs +2 -3
- package/dist/utils/docloader.test.mjs +6 -6
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +8 -13
- package/dist/codec-Bf-rTqks.mjs +0 -94
- package/dist/federation/tasks/codec.test.d.mts +0 -2
- package/dist/federation/tasks/codec.test.mjs +0 -292
- package/dist/federation/tasks/tasks.test.d.mts +0 -2
- package/dist/federation/tasks/tasks.test.mjs +0 -651
- package/dist/mq-CvJAzRz5.mjs +0 -260
- package/dist/testing-CjYPStvE.mjs +0 -55
- /package/dist/{accept-CceiKpCy.mjs → accept-CPkZzmGN.mjs} +0 -0
- /package/dist/{activity-listener-BeTGV3wc.mjs → activity-listener-tztVvlNb.mjs} +0 -0
- /package/dist/{assert_equals-Ew3jOFa3.mjs → assert_equals-C-ZRDbaf.mjs} +0 -0
- /package/dist/{circuit-breaker-hJBB6jwA.mjs → circuit-breaker-CSWsyoef.mjs} +0 -0
- /package/dist/{client-B_A6mfn3.mjs → client-ByXmQhYD.mjs} +0 -0
- /package/dist/{collection-Dp5ky45w.mjs → collection-Cc3DVAhE.mjs} +0 -0
- /package/dist/{keycache-CaOR6NYg.mjs → keycache-BeU0LCII.mjs} +0 -0
- /package/dist/{keys-C3kae-6B.mjs → keys-DGu1NFwu.mjs} +0 -0
- /package/dist/{kv-x2IvBUyq.mjs → kv-rV3vodCc.mjs} +0 -0
- /package/dist/{negotiation-m_L2nyS3.mjs → negotiation-DDstyBvc.mjs} +0 -0
- /package/dist/{public-audience-N3pyOx2p.mjs → public-audience-Cvbr2Gzt.mjs} +0 -0
- /package/dist/{retry-DI4O-zYl.mjs → retry-CXg_MBI-.mjs} +0 -0
- /package/dist/{types-BFowWFTT.mjs → types-J53Kw7so.mjs} +0 -0
|
@@ -1,651 +0,0 @@
|
|
|
1
|
-
import { Temporal } from "@js-temporal/polyfill";
|
|
2
|
-
import "urlpattern-polyfill";
|
|
3
|
-
globalThis.addEventListener = () => {};
|
|
4
|
-
import { a as stringSchema, i as numberSchema, n as envelopeSchema, t as MockQueue } from "../../testing-CjYPStvE.mjs";
|
|
5
|
-
import { t as MemoryKvStore } from "../../kv-x2IvBUyq.mjs";
|
|
6
|
-
import { o as createFederation } from "../../middleware-PZaroDUk.mjs";
|
|
7
|
-
import { r as createFederationBuilder } from "../../builder-Gef_ZeDE.mjs";
|
|
8
|
-
import { t as TaskCodec } from "../../codec-Bf-rTqks.mjs";
|
|
9
|
-
import { t as InProcessMessageQueue } from "../../mq-CvJAzRz5.mjs";
|
|
10
|
-
import { Note } from "@fedify/vocab";
|
|
11
|
-
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
12
|
-
import { delay } from "es-toolkit";
|
|
13
|
-
import { deepStrictEqual, ok, rejects, strictEqual, throws } from "node:assert/strict";
|
|
14
|
-
//#region src/federation/tasks/tasks.test.ts
|
|
15
|
-
const baseOptions = {
|
|
16
|
-
kv: new MemoryKvStore(),
|
|
17
|
-
documentLoaderFactory: () => mockDocumentLoader,
|
|
18
|
-
contextLoaderFactory: () => mockDocumentLoader,
|
|
19
|
-
manuallyStartQueue: true
|
|
20
|
-
};
|
|
21
|
-
const makeTaskMessage = async (taskName, data, overrides = {}) => ({
|
|
22
|
-
type: "task",
|
|
23
|
-
id: crypto.randomUUID(),
|
|
24
|
-
baseUrl: "https://example.com/",
|
|
25
|
-
taskName,
|
|
26
|
-
data: await new TaskCodec({ contextLoader: mockDocumentLoader }).serialize(data),
|
|
27
|
-
started: Temporal.Now.instant().toString(),
|
|
28
|
-
attempt: 0,
|
|
29
|
-
traceContext: {},
|
|
30
|
-
...overrides
|
|
31
|
-
});
|
|
32
|
-
async function waitFor(predicate, timeoutMs) {
|
|
33
|
-
const started = Date.now();
|
|
34
|
-
while (!predicate()) {
|
|
35
|
-
await delay(50);
|
|
36
|
-
if (Date.now() - started > timeoutMs) throw new Error("Timeout");
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
test("defineTask()", async (t) => {
|
|
40
|
-
await t.step("returns a handle carrying name and schema", () => {
|
|
41
|
-
const task = createFederation({
|
|
42
|
-
...baseOptions,
|
|
43
|
-
queue: { task: new MockQueue() }
|
|
44
|
-
}).defineTask("greet", {
|
|
45
|
-
schema: stringSchema,
|
|
46
|
-
handler: () => {}
|
|
47
|
-
});
|
|
48
|
-
strictEqual(task.name, "greet");
|
|
49
|
-
strictEqual(task.schema, stringSchema);
|
|
50
|
-
});
|
|
51
|
-
await t.step("throws on a duplicate name", () => {
|
|
52
|
-
const federation = createFederation({
|
|
53
|
-
...baseOptions,
|
|
54
|
-
queue: { task: new MockQueue() }
|
|
55
|
-
});
|
|
56
|
-
federation.defineTask("dup", {
|
|
57
|
-
schema: stringSchema,
|
|
58
|
-
handler: () => {}
|
|
59
|
-
});
|
|
60
|
-
throws(() => federation.defineTask("dup", {
|
|
61
|
-
schema: stringSchema,
|
|
62
|
-
handler: () => {}
|
|
63
|
-
}), {
|
|
64
|
-
name: "TypeError",
|
|
65
|
-
message: /already defined/
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
await t.step("accepts names that collide with Object.prototype", () => {
|
|
69
|
-
const federation = createFederation({
|
|
70
|
-
...baseOptions,
|
|
71
|
-
queue: { task: new MockQueue() }
|
|
72
|
-
});
|
|
73
|
-
for (const name of [
|
|
74
|
-
"constructor",
|
|
75
|
-
"toString",
|
|
76
|
-
"hasOwnProperty"
|
|
77
|
-
]) {
|
|
78
|
-
strictEqual(federation.defineTask(name, {
|
|
79
|
-
schema: stringSchema,
|
|
80
|
-
handler: () => {}
|
|
81
|
-
}).name, name);
|
|
82
|
-
strictEqual(federation.taskDefinitions.get(name)?.name, name);
|
|
83
|
-
}
|
|
84
|
-
throws(() => federation.defineTask("toString", {
|
|
85
|
-
schema: stringSchema,
|
|
86
|
-
handler: () => {}
|
|
87
|
-
}), {
|
|
88
|
-
name: "TypeError",
|
|
89
|
-
message: /already defined/
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
await t.step("build() clones the task registry", async () => {
|
|
93
|
-
const builder = createFederationBuilder();
|
|
94
|
-
builder.defineTask("first", {
|
|
95
|
-
schema: stringSchema,
|
|
96
|
-
handler: () => {}
|
|
97
|
-
});
|
|
98
|
-
const f1 = await builder.build({
|
|
99
|
-
...baseOptions,
|
|
100
|
-
queue: { task: new MockQueue() }
|
|
101
|
-
});
|
|
102
|
-
builder.defineTask("second", {
|
|
103
|
-
schema: stringSchema,
|
|
104
|
-
handler: () => {}
|
|
105
|
-
});
|
|
106
|
-
const f2 = await builder.build({
|
|
107
|
-
...baseOptions,
|
|
108
|
-
queue: { task: new MockQueue() }
|
|
109
|
-
});
|
|
110
|
-
deepStrictEqual([...f1.taskDefinitions.keys()], ["first"]);
|
|
111
|
-
deepStrictEqual([...f2.taskDefinitions.keys()], ["first", "second"]);
|
|
112
|
-
f1.defineTask("third", {
|
|
113
|
-
schema: stringSchema,
|
|
114
|
-
handler: () => {}
|
|
115
|
-
});
|
|
116
|
-
deepStrictEqual([...f2.taskDefinitions.keys()], ["first", "second"]);
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
test("task type-level guards", () => {});
|
|
120
|
-
test("Context.enqueueTask() end-to-end", async (t) => {
|
|
121
|
-
await t.step("round-trips a typed payload to the handler", async () => {
|
|
122
|
-
const queue = new InProcessMessageQueue();
|
|
123
|
-
const federation = createFederation({
|
|
124
|
-
...baseOptions,
|
|
125
|
-
queue: { task: queue }
|
|
126
|
-
});
|
|
127
|
-
const received = [];
|
|
128
|
-
const task = federation.defineTask("notify", {
|
|
129
|
-
schema: envelopeSchema,
|
|
130
|
-
handler: (ctx, data) => {
|
|
131
|
-
received.push({
|
|
132
|
-
ctx,
|
|
133
|
-
data
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
await federation.createContext(new URL("https://example.com/"), void 0).enqueueTask(task, {
|
|
138
|
-
note: new Note({ content: "Hello, world!" }),
|
|
139
|
-
title: "greeting"
|
|
140
|
-
});
|
|
141
|
-
const controller = new AbortController();
|
|
142
|
-
const listening = federation.startQueue(void 0, {
|
|
143
|
-
signal: controller.signal,
|
|
144
|
-
queue: "task"
|
|
145
|
-
});
|
|
146
|
-
try {
|
|
147
|
-
await waitFor(() => received.length > 0, 15e3);
|
|
148
|
-
} finally {
|
|
149
|
-
controller.abort();
|
|
150
|
-
await listening;
|
|
151
|
-
}
|
|
152
|
-
strictEqual(received.length, 1);
|
|
153
|
-
const { ctx: handlerCtx, data } = received[0];
|
|
154
|
-
ok(data.note instanceof Note);
|
|
155
|
-
strictEqual(data.note.content?.toString(), "Hello, world!");
|
|
156
|
-
strictEqual(data.title, "greeting");
|
|
157
|
-
strictEqual(handlerCtx.origin, "https://example.com");
|
|
158
|
-
});
|
|
159
|
-
await t.step("rejects an invalid payload at enqueue", async () => {
|
|
160
|
-
const queue = new MockQueue();
|
|
161
|
-
const federation = createFederation({
|
|
162
|
-
...baseOptions,
|
|
163
|
-
queue: { task: queue }
|
|
164
|
-
});
|
|
165
|
-
const task = federation.defineTask("strictly-typed", {
|
|
166
|
-
schema: numberSchema,
|
|
167
|
-
handler: () => {}
|
|
168
|
-
});
|
|
169
|
-
const ctx = federation.createContext(new URL("https://example.com/"), void 0);
|
|
170
|
-
await rejects(() => ctx.enqueueTask(task, "not a number"), {
|
|
171
|
-
name: "TypeError",
|
|
172
|
-
message: /Task data failed schema validation/
|
|
173
|
-
});
|
|
174
|
-
strictEqual(queue.enqueued.length, 0);
|
|
175
|
-
});
|
|
176
|
-
await t.step("starts the task worker on first enqueue without startQueue()", async () => {
|
|
177
|
-
const queue = new MockQueue();
|
|
178
|
-
const federation = createFederation({
|
|
179
|
-
...baseOptions,
|
|
180
|
-
manuallyStartQueue: false,
|
|
181
|
-
queue: { task: queue }
|
|
182
|
-
});
|
|
183
|
-
const task = federation.defineTask("auto-start", {
|
|
184
|
-
schema: stringSchema,
|
|
185
|
-
handler: () => {}
|
|
186
|
-
});
|
|
187
|
-
const ctx = federation.createContext(new URL("https://example.com/"), void 0);
|
|
188
|
-
await ctx.enqueueTask(task, "first");
|
|
189
|
-
strictEqual(queue.listenCount, 1);
|
|
190
|
-
await ctx.enqueueTask(task, "second");
|
|
191
|
-
strictEqual(queue.listenCount, 1);
|
|
192
|
-
strictEqual(queue.enqueued.length, 2);
|
|
193
|
-
});
|
|
194
|
-
await t.step("rejects a handle from another federation at enqueue", async () => {
|
|
195
|
-
const queue = new MockQueue();
|
|
196
|
-
const federation = createFederation({
|
|
197
|
-
...baseOptions,
|
|
198
|
-
queue: { task: queue }
|
|
199
|
-
});
|
|
200
|
-
const foreignTask = createFederation({
|
|
201
|
-
...baseOptions,
|
|
202
|
-
queue: { task: new MockQueue() }
|
|
203
|
-
}).defineTask("foreign", {
|
|
204
|
-
schema: stringSchema,
|
|
205
|
-
handler: () => {}
|
|
206
|
-
});
|
|
207
|
-
const ctx = federation.createContext(new URL("https://example.com/"), void 0);
|
|
208
|
-
await rejects(() => ctx.enqueueTask(foreignTask, "data"), {
|
|
209
|
-
name: "TypeError",
|
|
210
|
-
message: /is not defined on this federation/
|
|
211
|
-
});
|
|
212
|
-
strictEqual(queue.enqueued.length, 0);
|
|
213
|
-
});
|
|
214
|
-
await t.step("rejects a same-named handle from another federation", async () => {
|
|
215
|
-
const queue = new MockQueue();
|
|
216
|
-
const federation = createFederation({
|
|
217
|
-
...baseOptions,
|
|
218
|
-
queue: { task: queue }
|
|
219
|
-
});
|
|
220
|
-
let called = 0;
|
|
221
|
-
federation.defineTask("rename", {
|
|
222
|
-
schema: numberSchema,
|
|
223
|
-
handler: () => {
|
|
224
|
-
called++;
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
const foreignTask = createFederation({
|
|
228
|
-
...baseOptions,
|
|
229
|
-
queue: { task: new MockQueue() }
|
|
230
|
-
}).defineTask("rename", {
|
|
231
|
-
schema: stringSchema,
|
|
232
|
-
handler: () => {}
|
|
233
|
-
});
|
|
234
|
-
const ctx = federation.createContext(new URL("https://example.com/"), void 0);
|
|
235
|
-
await rejects(() => ctx.enqueueTask(foreignTask, "not a number"), {
|
|
236
|
-
name: "TypeError",
|
|
237
|
-
message: /is not defined on this federation/
|
|
238
|
-
});
|
|
239
|
-
strictEqual(queue.enqueued.length, 0);
|
|
240
|
-
strictEqual(called, 0);
|
|
241
|
-
});
|
|
242
|
-
await t.step("passes delay and orderingKey through", async () => {
|
|
243
|
-
const queue = new MockQueue();
|
|
244
|
-
const federation = createFederation({
|
|
245
|
-
...baseOptions,
|
|
246
|
-
queue: { task: queue }
|
|
247
|
-
});
|
|
248
|
-
const task = federation.defineTask("delayed", {
|
|
249
|
-
schema: stringSchema,
|
|
250
|
-
handler: () => {}
|
|
251
|
-
});
|
|
252
|
-
await federation.createContext(new URL("https://example.com/"), void 0).enqueueTask(task, "payload", {
|
|
253
|
-
delay: { seconds: 30 },
|
|
254
|
-
orderingKey: "user:alice"
|
|
255
|
-
});
|
|
256
|
-
strictEqual(queue.enqueued.length, 1);
|
|
257
|
-
const { message, options } = queue.enqueued[0];
|
|
258
|
-
strictEqual(message.taskName, "delayed");
|
|
259
|
-
strictEqual(message.orderingKey, "user:alice");
|
|
260
|
-
strictEqual(message.attempt, 0);
|
|
261
|
-
ok(options?.delay instanceof Temporal.Duration);
|
|
262
|
-
strictEqual(options.delay.total("second"), 30);
|
|
263
|
-
strictEqual(options.orderingKey, "user:alice");
|
|
264
|
-
});
|
|
265
|
-
await t.step("enqueueTaskMany() uses enqueueMany when available", async () => {
|
|
266
|
-
const queue = new MockQueue({ supportsEnqueueMany: true });
|
|
267
|
-
const federation = createFederation({
|
|
268
|
-
...baseOptions,
|
|
269
|
-
queue: { task: queue }
|
|
270
|
-
});
|
|
271
|
-
const task = federation.defineTask("bulk", {
|
|
272
|
-
schema: stringSchema,
|
|
273
|
-
handler: () => {}
|
|
274
|
-
});
|
|
275
|
-
await federation.createContext(new URL("https://example.com/"), void 0).enqueueTaskMany(task, [
|
|
276
|
-
"a",
|
|
277
|
-
"b",
|
|
278
|
-
"c"
|
|
279
|
-
]);
|
|
280
|
-
strictEqual(queue.enqueued.length, 0);
|
|
281
|
-
strictEqual(queue.enqueuedMany.length, 1);
|
|
282
|
-
strictEqual(queue.enqueuedMany[0].messages.length, 3);
|
|
283
|
-
});
|
|
284
|
-
await t.step("enqueueTaskMany() falls back to parallel enqueues", async () => {
|
|
285
|
-
const queue = new MockQueue();
|
|
286
|
-
const federation = createFederation({
|
|
287
|
-
...baseOptions,
|
|
288
|
-
queue: { task: queue }
|
|
289
|
-
});
|
|
290
|
-
const task = federation.defineTask("bulk-fallback", {
|
|
291
|
-
schema: stringSchema,
|
|
292
|
-
handler: () => {}
|
|
293
|
-
});
|
|
294
|
-
await federation.createContext(new URL("https://example.com/"), void 0).enqueueTaskMany(task, ["a", "b"]);
|
|
295
|
-
strictEqual(queue.enqueued.length, 2);
|
|
296
|
-
});
|
|
297
|
-
await t.step("enqueueTaskMany() with no payloads touches no queue", async () => {
|
|
298
|
-
const queue = new MockQueue({ supportsEnqueueMany: true });
|
|
299
|
-
const federation = createFederation({
|
|
300
|
-
...baseOptions,
|
|
301
|
-
queue: { task: queue }
|
|
302
|
-
});
|
|
303
|
-
const task = federation.defineTask("bulk-empty", {
|
|
304
|
-
schema: stringSchema,
|
|
305
|
-
handler: () => {}
|
|
306
|
-
});
|
|
307
|
-
await federation.createContext(new URL("https://example.com/"), void 0).enqueueTaskMany(task, []);
|
|
308
|
-
strictEqual(queue.enqueued.length, 0);
|
|
309
|
-
strictEqual(queue.enqueuedMany.length, 0);
|
|
310
|
-
});
|
|
311
|
-
});
|
|
312
|
-
test("task queue routing", async (t) => {
|
|
313
|
-
await t.step("prefers the per-task queue", async () => {
|
|
314
|
-
const taskQueue = new MockQueue();
|
|
315
|
-
const perTaskQueue = new MockQueue();
|
|
316
|
-
const federation = createFederation({
|
|
317
|
-
...baseOptions,
|
|
318
|
-
queue: { task: taskQueue }
|
|
319
|
-
});
|
|
320
|
-
const task = federation.defineTask("isolated", {
|
|
321
|
-
schema: stringSchema,
|
|
322
|
-
handler: () => {},
|
|
323
|
-
queue: perTaskQueue
|
|
324
|
-
});
|
|
325
|
-
await federation.createContext(new URL("https://example.com/"), void 0).enqueueTask(task, "data");
|
|
326
|
-
strictEqual(perTaskQueue.enqueued.length, 1);
|
|
327
|
-
strictEqual(taskQueue.enqueued.length, 0);
|
|
328
|
-
});
|
|
329
|
-
await t.step("falls back to the outbox queue by default", async () => {
|
|
330
|
-
const outboxQueue = new MockQueue();
|
|
331
|
-
const federation = createFederation({
|
|
332
|
-
...baseOptions,
|
|
333
|
-
queue: { outbox: outboxQueue }
|
|
334
|
-
});
|
|
335
|
-
const task = federation.defineTask("fallback", {
|
|
336
|
-
schema: stringSchema,
|
|
337
|
-
handler: () => {}
|
|
338
|
-
});
|
|
339
|
-
await federation.createContext(new URL("https://example.com/"), void 0).enqueueTask(task, "data");
|
|
340
|
-
strictEqual(outboxQueue.enqueued.length, 1);
|
|
341
|
-
});
|
|
342
|
-
await t.step("taskQueueResolution: \"strict\" throws at enqueue instead", async () => {
|
|
343
|
-
const outboxQueue = new MockQueue();
|
|
344
|
-
const federation = createFederation({
|
|
345
|
-
...baseOptions,
|
|
346
|
-
queue: { outbox: outboxQueue },
|
|
347
|
-
taskQueueResolution: "strict"
|
|
348
|
-
});
|
|
349
|
-
const task = federation.defineTask("strict", {
|
|
350
|
-
schema: stringSchema,
|
|
351
|
-
handler: () => {}
|
|
352
|
-
});
|
|
353
|
-
const ctx = federation.createContext(new URL("https://example.com/"), void 0);
|
|
354
|
-
await rejects(() => ctx.enqueueTask(task, "data"), {
|
|
355
|
-
name: "TypeError",
|
|
356
|
-
message: /No message queue is configured/
|
|
357
|
-
});
|
|
358
|
-
strictEqual(outboxQueue.enqueued.length, 0);
|
|
359
|
-
});
|
|
360
|
-
await t.step("throws when no queue is configured at all", async () => {
|
|
361
|
-
const federation = createFederation({ ...baseOptions });
|
|
362
|
-
const task = federation.defineTask("queueless", {
|
|
363
|
-
schema: stringSchema,
|
|
364
|
-
handler: () => {}
|
|
365
|
-
});
|
|
366
|
-
const ctx = federation.createContext(new URL("https://example.com/"), void 0);
|
|
367
|
-
await rejects(() => ctx.enqueueTask(task, "data"), {
|
|
368
|
-
name: "TypeError",
|
|
369
|
-
message: /No message queue is configured/
|
|
370
|
-
});
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
test("startQueue() task worker", async (t) => {
|
|
374
|
-
await t.step("starts only the task worker for queue: \"task\"", async () => {
|
|
375
|
-
const inbox = new MockQueue();
|
|
376
|
-
const outbox = new MockQueue();
|
|
377
|
-
const fanout = new MockQueue();
|
|
378
|
-
const taskQueue = new MockQueue();
|
|
379
|
-
const federation = createFederation({
|
|
380
|
-
...baseOptions,
|
|
381
|
-
queue: {
|
|
382
|
-
inbox,
|
|
383
|
-
outbox,
|
|
384
|
-
fanout,
|
|
385
|
-
task: taskQueue
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
const controller = new AbortController();
|
|
389
|
-
const listening = federation.startQueue(void 0, {
|
|
390
|
-
signal: controller.signal,
|
|
391
|
-
queue: "task"
|
|
392
|
-
});
|
|
393
|
-
strictEqual(taskQueue.listenCount, 1);
|
|
394
|
-
strictEqual(inbox.listenCount, 0);
|
|
395
|
-
strictEqual(outbox.listenCount, 0);
|
|
396
|
-
strictEqual(fanout.listenCount, 0);
|
|
397
|
-
controller.abort();
|
|
398
|
-
await listening;
|
|
399
|
-
});
|
|
400
|
-
await t.step("starts the worker for a task-only deployment", async () => {
|
|
401
|
-
const taskQueue = new MockQueue();
|
|
402
|
-
const federation = createFederation({
|
|
403
|
-
...baseOptions,
|
|
404
|
-
queue: { task: taskQueue }
|
|
405
|
-
});
|
|
406
|
-
const controller = new AbortController();
|
|
407
|
-
const listening = federation.startQueue(void 0, { signal: controller.signal });
|
|
408
|
-
strictEqual(taskQueue.listenCount, 1);
|
|
409
|
-
controller.abort();
|
|
410
|
-
await listening;
|
|
411
|
-
});
|
|
412
|
-
await t.step("does not double-listen on a shared queue", async () => {
|
|
413
|
-
const shared = new MockQueue();
|
|
414
|
-
const federation = createFederation({
|
|
415
|
-
...baseOptions,
|
|
416
|
-
queue: {
|
|
417
|
-
outbox: shared,
|
|
418
|
-
task: shared
|
|
419
|
-
}
|
|
420
|
-
});
|
|
421
|
-
const controller = new AbortController();
|
|
422
|
-
const listening = federation.startQueue(void 0, { signal: controller.signal });
|
|
423
|
-
strictEqual(shared.listenCount, 1);
|
|
424
|
-
controller.abort();
|
|
425
|
-
await listening;
|
|
426
|
-
});
|
|
427
|
-
await t.step("starts a worker for a dedicated per-task queue", async () => {
|
|
428
|
-
const taskQueue = new MockQueue();
|
|
429
|
-
const dedicated = new MockQueue();
|
|
430
|
-
const federation = createFederation({
|
|
431
|
-
...baseOptions,
|
|
432
|
-
queue: { task: taskQueue }
|
|
433
|
-
});
|
|
434
|
-
federation.defineTask("dedicated", {
|
|
435
|
-
schema: stringSchema,
|
|
436
|
-
handler: () => {},
|
|
437
|
-
queue: dedicated
|
|
438
|
-
});
|
|
439
|
-
const controller = new AbortController();
|
|
440
|
-
const listening = federation.startQueue(void 0, { signal: controller.signal });
|
|
441
|
-
strictEqual(taskQueue.listenCount, 1);
|
|
442
|
-
strictEqual(dedicated.listenCount, 1);
|
|
443
|
-
controller.abort();
|
|
444
|
-
await listening;
|
|
445
|
-
});
|
|
446
|
-
await t.step("starts a per-task queue even without a federation queue", async () => {
|
|
447
|
-
const dedicated = new MockQueue();
|
|
448
|
-
const federation = createFederation({ ...baseOptions });
|
|
449
|
-
federation.defineTask("dedicated", {
|
|
450
|
-
schema: stringSchema,
|
|
451
|
-
handler: () => {},
|
|
452
|
-
queue: dedicated
|
|
453
|
-
});
|
|
454
|
-
const controller = new AbortController();
|
|
455
|
-
const listening = federation.startQueue(void 0, { signal: controller.signal });
|
|
456
|
-
strictEqual(dedicated.listenCount, 1);
|
|
457
|
-
controller.abort();
|
|
458
|
-
await listening;
|
|
459
|
-
});
|
|
460
|
-
await t.step("does not listen twice on a per-task queue shared with a standard queue", async () => {
|
|
461
|
-
const shared = new MockQueue();
|
|
462
|
-
const federation = createFederation({
|
|
463
|
-
...baseOptions,
|
|
464
|
-
queue: { task: shared }
|
|
465
|
-
});
|
|
466
|
-
federation.defineTask("reuses-task-queue", {
|
|
467
|
-
schema: stringSchema,
|
|
468
|
-
handler: () => {},
|
|
469
|
-
queue: shared
|
|
470
|
-
});
|
|
471
|
-
const controller = new AbortController();
|
|
472
|
-
const listening = federation.startQueue(void 0, { signal: controller.signal });
|
|
473
|
-
strictEqual(shared.listenCount, 1);
|
|
474
|
-
controller.abort();
|
|
475
|
-
await listening;
|
|
476
|
-
});
|
|
477
|
-
await t.step("routes an enqueued task on a dedicated queue to its handler", async () => {
|
|
478
|
-
const dedicated = new MockQueue();
|
|
479
|
-
const federation = createFederation({ ...baseOptions });
|
|
480
|
-
let received;
|
|
481
|
-
const task = federation.defineTask("dedicated-end-to-end", {
|
|
482
|
-
schema: stringSchema,
|
|
483
|
-
handler: (_ctx, data) => {
|
|
484
|
-
received = data;
|
|
485
|
-
},
|
|
486
|
-
queue: dedicated
|
|
487
|
-
});
|
|
488
|
-
await federation.createContext(new URL("https://example.com/"), void 0).enqueueTask(task, "payload");
|
|
489
|
-
strictEqual(dedicated.enqueued.length, 1);
|
|
490
|
-
await federation.processQueuedTask(void 0, dedicated.enqueued[0].message);
|
|
491
|
-
strictEqual(received, "payload");
|
|
492
|
-
});
|
|
493
|
-
});
|
|
494
|
-
test("processQueuedTask() task dispatch", async (t) => {
|
|
495
|
-
await t.step("drops an unknown task with a warning", async () => {
|
|
496
|
-
const queue = new MockQueue();
|
|
497
|
-
const federation = createFederation({
|
|
498
|
-
...baseOptions,
|
|
499
|
-
queue: { task: queue }
|
|
500
|
-
});
|
|
501
|
-
const message = await makeTaskMessage("never-defined", "data");
|
|
502
|
-
await federation.processQueuedTask(void 0, message);
|
|
503
|
-
strictEqual(queue.enqueued.length, 0);
|
|
504
|
-
});
|
|
505
|
-
await t.step("drops an undecodable payload without retry", async () => {
|
|
506
|
-
const queue = new MockQueue();
|
|
507
|
-
const federation = createFederation({
|
|
508
|
-
...baseOptions,
|
|
509
|
-
queue: { task: queue }
|
|
510
|
-
});
|
|
511
|
-
let called = 0;
|
|
512
|
-
federation.defineTask("broken-wire", {
|
|
513
|
-
schema: stringSchema,
|
|
514
|
-
handler: () => {
|
|
515
|
-
called++;
|
|
516
|
-
}
|
|
517
|
-
});
|
|
518
|
-
const message = await makeTaskMessage("broken-wire", "data");
|
|
519
|
-
await federation.processQueuedTask(void 0, {
|
|
520
|
-
...message,
|
|
521
|
-
data: "garbage that is not devalue"
|
|
522
|
-
});
|
|
523
|
-
strictEqual(called, 0);
|
|
524
|
-
strictEqual(queue.enqueued.length, 0);
|
|
525
|
-
});
|
|
526
|
-
await t.step("drops a drifted payload without retry", async () => {
|
|
527
|
-
const queue = new MockQueue();
|
|
528
|
-
const federation = createFederation({
|
|
529
|
-
...baseOptions,
|
|
530
|
-
queue: { task: queue }
|
|
531
|
-
});
|
|
532
|
-
let called = 0;
|
|
533
|
-
federation.defineTask("drifted", {
|
|
534
|
-
schema: numberSchema,
|
|
535
|
-
handler: () => {
|
|
536
|
-
called++;
|
|
537
|
-
}
|
|
538
|
-
});
|
|
539
|
-
const message = await makeTaskMessage("drifted", "stringly-typed");
|
|
540
|
-
await federation.processQueuedTask(void 0, message);
|
|
541
|
-
strictEqual(called, 0);
|
|
542
|
-
strictEqual(queue.enqueued.length, 0);
|
|
543
|
-
});
|
|
544
|
-
await t.step("re-enqueues with attempt + 1 when the handler throws", async () => {
|
|
545
|
-
const queue = new MockQueue();
|
|
546
|
-
const federation = createFederation({
|
|
547
|
-
...baseOptions,
|
|
548
|
-
queue: { task: queue }
|
|
549
|
-
});
|
|
550
|
-
const errors = [];
|
|
551
|
-
federation.defineTask("flaky", {
|
|
552
|
-
schema: stringSchema,
|
|
553
|
-
handler: () => {
|
|
554
|
-
throw new Error("boom");
|
|
555
|
-
},
|
|
556
|
-
retryPolicy: () => Temporal.Duration.from({ milliseconds: 1 }),
|
|
557
|
-
onError: (_ctx, error, data) => {
|
|
558
|
-
errors.push([error, data]);
|
|
559
|
-
}
|
|
560
|
-
});
|
|
561
|
-
const message = await makeTaskMessage("flaky", "data", { orderingKey: "k" });
|
|
562
|
-
await federation.processQueuedTask(void 0, message);
|
|
563
|
-
strictEqual(queue.enqueued.length, 1);
|
|
564
|
-
const retry = queue.enqueued[0];
|
|
565
|
-
strictEqual(retry.message.attempt, 1);
|
|
566
|
-
strictEqual(retry.message.taskName, "flaky");
|
|
567
|
-
strictEqual(retry.message.orderingKey, "k");
|
|
568
|
-
strictEqual(retry.options?.orderingKey, "k");
|
|
569
|
-
ok(retry.options?.delay instanceof Temporal.Duration);
|
|
570
|
-
strictEqual(errors.length, 1);
|
|
571
|
-
deepStrictEqual(errors[0], [/* @__PURE__ */ new Error("boom"), "data"]);
|
|
572
|
-
});
|
|
573
|
-
await t.step("gives up when the retry policy returns null", async () => {
|
|
574
|
-
const queue = new MockQueue();
|
|
575
|
-
const federation = createFederation({
|
|
576
|
-
...baseOptions,
|
|
577
|
-
queue: { task: queue }
|
|
578
|
-
});
|
|
579
|
-
federation.defineTask("hopeless", {
|
|
580
|
-
schema: stringSchema,
|
|
581
|
-
handler: () => {
|
|
582
|
-
throw new Error("boom");
|
|
583
|
-
},
|
|
584
|
-
retryPolicy: () => null
|
|
585
|
-
});
|
|
586
|
-
const message = await makeTaskMessage("hopeless", "data");
|
|
587
|
-
await federation.processQueuedTask(void 0, message);
|
|
588
|
-
strictEqual(queue.enqueued.length, 0);
|
|
589
|
-
});
|
|
590
|
-
await t.step("per-task retryPolicy overrides the federation default", async () => {
|
|
591
|
-
const queue = new MockQueue();
|
|
592
|
-
const federation = createFederation({
|
|
593
|
-
...baseOptions,
|
|
594
|
-
queue: { task: queue },
|
|
595
|
-
taskRetryPolicy: () => null
|
|
596
|
-
});
|
|
597
|
-
federation.defineTask("override", {
|
|
598
|
-
schema: stringSchema,
|
|
599
|
-
handler: () => {
|
|
600
|
-
throw new Error("boom");
|
|
601
|
-
},
|
|
602
|
-
retryPolicy: () => Temporal.Duration.from({ milliseconds: 1 })
|
|
603
|
-
});
|
|
604
|
-
federation.defineTask("default", {
|
|
605
|
-
schema: stringSchema,
|
|
606
|
-
handler: () => {
|
|
607
|
-
throw new Error("boom");
|
|
608
|
-
}
|
|
609
|
-
});
|
|
610
|
-
await federation.processQueuedTask(void 0, await makeTaskMessage("override", "data"));
|
|
611
|
-
strictEqual(queue.enqueued.length, 1);
|
|
612
|
-
await federation.processQueuedTask(void 0, await makeTaskMessage("default", "data"));
|
|
613
|
-
strictEqual(queue.enqueued.length, 1);
|
|
614
|
-
});
|
|
615
|
-
await t.step("still retries when message.started is malformed", async () => {
|
|
616
|
-
const queue = new MockQueue();
|
|
617
|
-
const federation = createFederation({
|
|
618
|
-
...baseOptions,
|
|
619
|
-
queue: { task: queue }
|
|
620
|
-
});
|
|
621
|
-
federation.defineTask("bad-started", {
|
|
622
|
-
schema: stringSchema,
|
|
623
|
-
handler: () => {
|
|
624
|
-
throw new Error("boom");
|
|
625
|
-
},
|
|
626
|
-
retryPolicy: () => Temporal.Duration.from({ milliseconds: 1 })
|
|
627
|
-
});
|
|
628
|
-
const message = await makeTaskMessage("bad-started", "data", { started: "not-an-instant" });
|
|
629
|
-
await federation.processQueuedTask(void 0, message);
|
|
630
|
-
strictEqual(queue.enqueued.length, 1);
|
|
631
|
-
strictEqual(queue.enqueued[0].message.attempt, 1);
|
|
632
|
-
});
|
|
633
|
-
await t.step("rethrows on a nativeRetrial queue", async () => {
|
|
634
|
-
const queue = new MockQueue({ nativeRetrial: true });
|
|
635
|
-
const federation = createFederation({
|
|
636
|
-
...baseOptions,
|
|
637
|
-
queue: { task: queue }
|
|
638
|
-
});
|
|
639
|
-
federation.defineTask("native", {
|
|
640
|
-
schema: stringSchema,
|
|
641
|
-
handler: () => {
|
|
642
|
-
throw new Error("boom");
|
|
643
|
-
}
|
|
644
|
-
});
|
|
645
|
-
const message = await makeTaskMessage("native", "data");
|
|
646
|
-
await rejects(() => federation.processQueuedTask(void 0, message), { message: /boom/ });
|
|
647
|
-
strictEqual(queue.enqueued.length, 0);
|
|
648
|
-
});
|
|
649
|
-
});
|
|
650
|
-
//#endregion
|
|
651
|
-
export {};
|