@fedify/fedify 2.4.0-dev.1488 → 2.4.0-dev.1504
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 +3 -4
- package/dist/{assert-OguE97r2.mjs → assert-DvVHoIZ0.mjs} +1 -1
- package/dist/{assert_instance_of-DBC5X09g.mjs → assert_instance_of-CxoC8bVq.mjs} +1 -1
- package/dist/{assert_not_equals-DkVK8oqV.mjs → assert_not_equals-C8vcTTA3.mjs} +1 -1
- package/dist/{assert_rejects-DN60FHPX.mjs → assert_rejects-DAdaaIWM.mjs} +2 -2
- package/dist/{assert_strict_equals-XEgZAlrj.mjs → assert_strict_equals-BzqyKuYL.mjs} +1 -1
- package/dist/{assert_throws-BOkhLGYc.mjs → assert_throws-zN79lgIC.mjs} +1 -1
- package/dist/{builder-Dcr6y6XD.mjs → builder-Ck0Fl_a5.mjs} +23 -3
- package/dist/codec-CmlPqjNX.mjs +103 -0
- 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-DVoTs_wM.mjs → context-BBRTgkrs.mjs} +8 -2
- package/dist/{context-BU6jSQdo.d.ts → context-BF5B4ZDk.d.ts} +529 -259
- package/dist/{context-BBVLF7lx.d.cts → context-CYF8X0ft.d.cts} +530 -259
- package/dist/{deno-BCFGf8NR.mjs → deno-D3gb4X9v.mjs} +1 -1
- package/dist/{docloader-DWEwaGqF.mjs → docloader-DUKu0GzG.mjs} +3 -3
- 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 +200 -19
- package/dist/federation/mod.cjs +3 -259
- package/dist/federation/mod.d.cts +4 -4
- package/dist/federation/mod.d.ts +4 -4
- package/dist/federation/mod.js +1 -257
- package/dist/federation/mq.test.mjs +103 -261
- 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/tasks/codec.test.d.mts +2 -0
- package/dist/federation/tasks/codec.test.mjs +292 -0
- package/dist/federation/tasks/enqueue.test.d.mts +2 -0
- package/dist/federation/tasks/enqueue.test.mjs +992 -0
- package/dist/federation/tasks/tasks.test.d.mts +2 -0
- package/dist/federation/tasks/tasks.test.mjs +555 -0
- package/dist/federation/temporal.test.mjs +4 -4
- package/dist/federation/webfinger.test.mjs +6 -6
- package/dist/{http-RriziGDE.mjs → http-BNFQkiEZ.mjs} +4 -4
- package/dist/{http-DaVoLyyA.js → http-CrZzUAtV.js} +7 -17
- package/dist/{http-DLqxhQg1.cjs → http-D9p2_d2X.cjs} +7 -17
- package/dist/{key-Bkg8Zg36.mjs → key-BM_SRwBV.mjs} +8 -18
- package/dist/{kv-cache-BR--9V4L.mjs → kv-cache-BE1QIPdL.mjs} +1 -1
- package/dist/{kv-cache-DxlFLMiU.js → kv-cache-Ch_Zv5Yq.js} +1 -1
- package/dist/{kv-cache-Fw-iwKjy.cjs → kv-cache-Cna211RR.cjs} +1 -1
- package/dist/{ld-BjsmbqO7.mjs → ld-ubkf03eb.mjs} +3 -3
- package/dist/{metrics-BLoa2pab.mjs → metrics-BXR36qzu.mjs} +1 -1
- package/dist/{middleware-V-r1QH5Z.cjs → middleware-DUNeIkXN.cjs} +694 -51
- package/dist/{middleware-D3kuDjvb.mjs → middleware-iP9VRMus.mjs} +310 -67
- package/dist/{middleware-_9t5HVBi.js → middleware-lB4vtShP.js} +683 -52
- package/dist/{middleware-DNR3YBCi.mjs → middleware-pzCP79He.mjs} +1 -1
- package/dist/{mod-vPYVoa5n.d.ts → mod-4WGAuR6X.d.ts} +1 -1
- package/dist/{mod-C0F6kvgS.d.cts → mod-BHEB3xhE.d.cts} +1 -1
- package/dist/mod.cjs +6 -6
- package/dist/mod.d.cts +5 -5
- package/dist/mod.d.ts +5 -5
- package/dist/mod.js +5 -5
- package/dist/mq-03vS-C9P.mjs +276 -0
- package/dist/{mq-D8uSFzxe.d.cts → mq-B5_hohs7.d.ts} +34 -0
- package/dist/{mq-D-nlpY04.d.ts → mq-DXMt_qCY.d.cts} +34 -0
- 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-L_DbOaFe.mjs → outgoing-jsonld-BNL8AC14.mjs} +1 -1
- package/dist/{owner-NK2p1ihR.mjs → owner-Bf1KkncT.mjs} +2 -2
- package/dist/{proof-D7WVwQzs.js → proof-BMcsi8q_.js} +1 -1
- package/dist/{proof-CRXqo703.mjs → proof-Q781Ks5q.mjs} +5 -5
- package/dist/{proof-2WI6mMbC.cjs → proof-yeAU1MyT.cjs} +1 -1
- package/dist/{send-CTaPzl8L.mjs → send-DFV0S_n9.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 +6 -56
- 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-BBjXFNOb.mjs → std__assert-Dh6uLf_q.mjs} +4 -4
- package/dist/{temporal-C8M0Y7v9.mjs → temporal-CyOjsqPj.mjs} +1 -1
- package/dist/testing/mod.d.mts +1037 -8
- package/dist/testing/mod.mjs +3 -2
- package/dist/testing-Dy-WhqpN.mjs +77 -0
- 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 +13 -8
- /package/dist/{accept-CPkZzmGN.mjs → accept-CgDcxvjV.mjs} +0 -0
- /package/dist/{activity-listener-tztVvlNb.mjs → activity-listener-BeTGV3wc.mjs} +0 -0
- /package/dist/{assert_equals-C-ZRDbaf.mjs → assert_equals-PBVKNMJd.mjs} +0 -0
- /package/dist/{circuit-breaker-CSWsyoef.mjs → circuit-breaker-hJBB6jwA.mjs} +0 -0
- /package/dist/{client-ByXmQhYD.mjs → client-B_A6mfn3.mjs} +0 -0
- /package/dist/{collection-Cc3DVAhE.mjs → collection-Dp5ky45w.mjs} +0 -0
- /package/dist/{keycache-BeU0LCII.mjs → keycache-CaOR6NYg.mjs} +0 -0
- /package/dist/{keys-DGu1NFwu.mjs → keys-CSYsOMFG.mjs} +0 -0
- /package/dist/{kv-rV3vodCc.mjs → kv-CIvR3Non.mjs} +0 -0
- /package/dist/{negotiation-DDstyBvc.mjs → negotiation-m_L2nyS3.mjs} +0 -0
- /package/dist/{public-audience-Cvbr2Gzt.mjs → public-audience-c9zmYKgA.mjs} +0 -0
- /package/dist/{retry-CXg_MBI-.mjs → retry-DI4O-zYl.mjs} +0 -0
- /package/dist/{types-J53Kw7so.mjs → types-BFowWFTT.mjs} +0 -0
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import { Temporal } from "@js-temporal/polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, t as MockQueue } from "../../testing-Dy-WhqpN.mjs";
|
|
5
|
+
import { o as createFederation } from "../../middleware-iP9VRMus.mjs";
|
|
6
|
+
import { r as createFederationBuilder } from "../../builder-Ck0Fl_a5.mjs";
|
|
7
|
+
import { t as TaskCodec } from "../../codec-CmlPqjNX.mjs";
|
|
8
|
+
import { t as InProcessMessageQueue } from "../../mq-03vS-C9P.mjs";
|
|
9
|
+
import { Note } from "@fedify/vocab";
|
|
10
|
+
import { delay } from "es-toolkit";
|
|
11
|
+
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
12
|
+
import { deepStrictEqual, ok, rejects, strictEqual, throws } from "node:assert/strict";
|
|
13
|
+
//#region src/federation/tasks/tasks.test.ts
|
|
14
|
+
const envelopeSchema = makeSchema((data) => typeof data === "object" && data != null && data.note instanceof Note && typeof data.title === "string");
|
|
15
|
+
const makeTaskMessage = async (taskName, data, overrides = {}) => ({
|
|
16
|
+
type: "task",
|
|
17
|
+
id: crypto.randomUUID(),
|
|
18
|
+
baseUrl: "https://example.com/",
|
|
19
|
+
taskName,
|
|
20
|
+
data: await new TaskCodec({ contextLoader: mockDocumentLoader }).serialize(data),
|
|
21
|
+
started: Temporal.Now.instant().toString(),
|
|
22
|
+
attempt: 0,
|
|
23
|
+
traceContext: {},
|
|
24
|
+
...overrides
|
|
25
|
+
});
|
|
26
|
+
async function waitFor(predicate, timeoutMs) {
|
|
27
|
+
const started = Date.now();
|
|
28
|
+
while (!predicate()) {
|
|
29
|
+
await delay(50);
|
|
30
|
+
if (Date.now() - started > timeoutMs) throw new Error("Timeout");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
test("defineTask()", async (t) => {
|
|
34
|
+
await t.step("returns a handle carrying name and schema", () => {
|
|
35
|
+
const task = createFederation({
|
|
36
|
+
...baseOptions,
|
|
37
|
+
queue: { task: new MockQueue() }
|
|
38
|
+
}).defineTask("greet", {
|
|
39
|
+
schema: stringSchema,
|
|
40
|
+
handler: () => {}
|
|
41
|
+
});
|
|
42
|
+
strictEqual(task.name, "greet");
|
|
43
|
+
strictEqual(task.schema, stringSchema);
|
|
44
|
+
});
|
|
45
|
+
await t.step("throws on a duplicate name", () => {
|
|
46
|
+
const federation = createFederation({
|
|
47
|
+
...baseOptions,
|
|
48
|
+
queue: { task: new MockQueue() }
|
|
49
|
+
});
|
|
50
|
+
federation.defineTask("dup", {
|
|
51
|
+
schema: stringSchema,
|
|
52
|
+
handler: () => {}
|
|
53
|
+
});
|
|
54
|
+
throws(() => federation.defineTask("dup", {
|
|
55
|
+
schema: stringSchema,
|
|
56
|
+
handler: () => {}
|
|
57
|
+
}), {
|
|
58
|
+
name: "TypeError",
|
|
59
|
+
message: /already defined/
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
await t.step("accepts names that collide with Object.prototype", () => {
|
|
63
|
+
const federation = createFederation({
|
|
64
|
+
...baseOptions,
|
|
65
|
+
queue: { task: new MockQueue() }
|
|
66
|
+
});
|
|
67
|
+
for (const name of [
|
|
68
|
+
"constructor",
|
|
69
|
+
"toString",
|
|
70
|
+
"hasOwnProperty"
|
|
71
|
+
]) {
|
|
72
|
+
strictEqual(federation.defineTask(name, {
|
|
73
|
+
schema: stringSchema,
|
|
74
|
+
handler: () => {}
|
|
75
|
+
}).name, name);
|
|
76
|
+
strictEqual(federation.taskDefinitions.get(name)?.name, name);
|
|
77
|
+
}
|
|
78
|
+
throws(() => federation.defineTask("toString", {
|
|
79
|
+
schema: stringSchema,
|
|
80
|
+
handler: () => {}
|
|
81
|
+
}), {
|
|
82
|
+
name: "TypeError",
|
|
83
|
+
message: /already defined/
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
await t.step("build() clones the task registry", async () => {
|
|
87
|
+
const builder = createFederationBuilder();
|
|
88
|
+
builder.defineTask("first", {
|
|
89
|
+
schema: stringSchema,
|
|
90
|
+
handler: () => {}
|
|
91
|
+
});
|
|
92
|
+
const f1 = await builder.build({
|
|
93
|
+
...baseOptions,
|
|
94
|
+
queue: { task: new MockQueue() }
|
|
95
|
+
});
|
|
96
|
+
builder.defineTask("second", {
|
|
97
|
+
schema: stringSchema,
|
|
98
|
+
handler: () => {}
|
|
99
|
+
});
|
|
100
|
+
const f2 = await builder.build({
|
|
101
|
+
...baseOptions,
|
|
102
|
+
queue: { task: new MockQueue() }
|
|
103
|
+
});
|
|
104
|
+
deepStrictEqual([...f1.taskDefinitions.keys()], ["first"]);
|
|
105
|
+
deepStrictEqual([...f2.taskDefinitions.keys()], ["first", "second"]);
|
|
106
|
+
f1.defineTask("third", {
|
|
107
|
+
schema: stringSchema,
|
|
108
|
+
handler: () => {}
|
|
109
|
+
});
|
|
110
|
+
deepStrictEqual([...f2.taskDefinitions.keys()], ["first", "second"]);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
test("task type-level guards", () => {});
|
|
114
|
+
test("Context.enqueueTask() end-to-end", async (t) => {
|
|
115
|
+
await t.step("round-trips a typed payload to the handler", async () => {
|
|
116
|
+
const queue = new InProcessMessageQueue();
|
|
117
|
+
const federation = createFederation({
|
|
118
|
+
...baseOptions,
|
|
119
|
+
queue: { task: queue }
|
|
120
|
+
});
|
|
121
|
+
const received = [];
|
|
122
|
+
const task = federation.defineTask("notify", {
|
|
123
|
+
schema: envelopeSchema,
|
|
124
|
+
handler: (ctx, data) => {
|
|
125
|
+
received.push({
|
|
126
|
+
ctx,
|
|
127
|
+
data
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
await federation.createContext(new URL("https://example.com/"), void 0).enqueueTask(task, {
|
|
132
|
+
note: new Note({ content: "Hello, world!" }),
|
|
133
|
+
title: "greeting"
|
|
134
|
+
});
|
|
135
|
+
const controller = new AbortController();
|
|
136
|
+
const listening = federation.startQueue(void 0, {
|
|
137
|
+
signal: controller.signal,
|
|
138
|
+
queue: "task"
|
|
139
|
+
});
|
|
140
|
+
try {
|
|
141
|
+
await waitFor(() => received.length > 0, 15e3);
|
|
142
|
+
} finally {
|
|
143
|
+
controller.abort();
|
|
144
|
+
await listening;
|
|
145
|
+
}
|
|
146
|
+
strictEqual(received.length, 1);
|
|
147
|
+
const { ctx: handlerCtx, data } = received[0];
|
|
148
|
+
ok(data.note instanceof Note);
|
|
149
|
+
strictEqual(data.note.content?.toString(), "Hello, world!");
|
|
150
|
+
strictEqual(data.title, "greeting");
|
|
151
|
+
strictEqual(handlerCtx.origin, "https://example.com");
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
test("task queue routing", async (t) => {
|
|
155
|
+
await t.step("prefers the per-task queue", async () => {
|
|
156
|
+
const taskQueue = new MockQueue();
|
|
157
|
+
const perTaskQueue = new MockQueue();
|
|
158
|
+
const federation = createFederation({
|
|
159
|
+
...baseOptions,
|
|
160
|
+
queue: { task: taskQueue }
|
|
161
|
+
});
|
|
162
|
+
const task = federation.defineTask("isolated", {
|
|
163
|
+
schema: stringSchema,
|
|
164
|
+
handler: () => {},
|
|
165
|
+
queue: perTaskQueue
|
|
166
|
+
});
|
|
167
|
+
await federation.createContext(new URL("https://example.com/"), void 0).enqueueTask(task, "data");
|
|
168
|
+
strictEqual(perTaskQueue.enqueued.length, 1);
|
|
169
|
+
strictEqual(taskQueue.enqueued.length, 0);
|
|
170
|
+
});
|
|
171
|
+
await t.step("falls back to the outbox queue by default", async () => {
|
|
172
|
+
const outboxQueue = new MockQueue();
|
|
173
|
+
const federation = createFederation({
|
|
174
|
+
...baseOptions,
|
|
175
|
+
queue: { outbox: outboxQueue }
|
|
176
|
+
});
|
|
177
|
+
const task = federation.defineTask("fallback", {
|
|
178
|
+
schema: stringSchema,
|
|
179
|
+
handler: () => {}
|
|
180
|
+
});
|
|
181
|
+
await federation.createContext(new URL("https://example.com/"), void 0).enqueueTask(task, "data");
|
|
182
|
+
strictEqual(outboxQueue.enqueued.length, 1);
|
|
183
|
+
});
|
|
184
|
+
await t.step("taskQueueResolution: \"strict\" throws at enqueue instead", async () => {
|
|
185
|
+
const outboxQueue = new MockQueue();
|
|
186
|
+
const federation = createFederation({
|
|
187
|
+
...baseOptions,
|
|
188
|
+
queue: { outbox: outboxQueue },
|
|
189
|
+
taskQueueResolution: "strict"
|
|
190
|
+
});
|
|
191
|
+
const task = federation.defineTask("strict", {
|
|
192
|
+
schema: stringSchema,
|
|
193
|
+
handler: () => {}
|
|
194
|
+
});
|
|
195
|
+
const ctx = federation.createContext(new URL("https://example.com/"), void 0);
|
|
196
|
+
await rejects(() => ctx.enqueueTask(task, "data"), {
|
|
197
|
+
name: "TypeError",
|
|
198
|
+
message: /No message queue is configured/
|
|
199
|
+
});
|
|
200
|
+
strictEqual(outboxQueue.enqueued.length, 0);
|
|
201
|
+
});
|
|
202
|
+
await t.step("throws when no queue is configured at all", async () => {
|
|
203
|
+
const federation = createFederation({ ...baseOptions });
|
|
204
|
+
const task = federation.defineTask("queueless", {
|
|
205
|
+
schema: stringSchema,
|
|
206
|
+
handler: () => {}
|
|
207
|
+
});
|
|
208
|
+
const ctx = federation.createContext(new URL("https://example.com/"), void 0);
|
|
209
|
+
await rejects(() => ctx.enqueueTask(task, "data"), {
|
|
210
|
+
name: "TypeError",
|
|
211
|
+
message: /No message queue is configured/
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
test("startQueue() task worker", async (t) => {
|
|
216
|
+
await t.step("starts only the task worker for queue: \"task\"", async () => {
|
|
217
|
+
const inbox = new MockQueue();
|
|
218
|
+
const outbox = new MockQueue();
|
|
219
|
+
const fanout = new MockQueue();
|
|
220
|
+
const taskQueue = new MockQueue();
|
|
221
|
+
const federation = createFederation({
|
|
222
|
+
...baseOptions,
|
|
223
|
+
queue: {
|
|
224
|
+
inbox,
|
|
225
|
+
outbox,
|
|
226
|
+
fanout,
|
|
227
|
+
task: taskQueue
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
const controller = new AbortController();
|
|
231
|
+
const listening = federation.startQueue(void 0, {
|
|
232
|
+
signal: controller.signal,
|
|
233
|
+
queue: "task"
|
|
234
|
+
});
|
|
235
|
+
strictEqual(taskQueue.listenCount, 1);
|
|
236
|
+
strictEqual(inbox.listenCount, 0);
|
|
237
|
+
strictEqual(outbox.listenCount, 0);
|
|
238
|
+
strictEqual(fanout.listenCount, 0);
|
|
239
|
+
controller.abort();
|
|
240
|
+
await listening;
|
|
241
|
+
});
|
|
242
|
+
await t.step("starts the worker for a task-only deployment", async () => {
|
|
243
|
+
const taskQueue = new MockQueue();
|
|
244
|
+
const federation = createFederation({
|
|
245
|
+
...baseOptions,
|
|
246
|
+
queue: { task: taskQueue }
|
|
247
|
+
});
|
|
248
|
+
const controller = new AbortController();
|
|
249
|
+
const listening = federation.startQueue(void 0, { signal: controller.signal });
|
|
250
|
+
strictEqual(taskQueue.listenCount, 1);
|
|
251
|
+
controller.abort();
|
|
252
|
+
await listening;
|
|
253
|
+
});
|
|
254
|
+
await t.step("does not double-listen on a shared queue", async () => {
|
|
255
|
+
const shared = new MockQueue();
|
|
256
|
+
const federation = createFederation({
|
|
257
|
+
...baseOptions,
|
|
258
|
+
queue: {
|
|
259
|
+
outbox: shared,
|
|
260
|
+
task: shared
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
const controller = new AbortController();
|
|
264
|
+
const listening = federation.startQueue(void 0, { signal: controller.signal });
|
|
265
|
+
strictEqual(shared.listenCount, 1);
|
|
266
|
+
controller.abort();
|
|
267
|
+
await listening;
|
|
268
|
+
});
|
|
269
|
+
await t.step("starts a worker for a dedicated per-task queue", async () => {
|
|
270
|
+
const taskQueue = new MockQueue();
|
|
271
|
+
const dedicated = new MockQueue();
|
|
272
|
+
const federation = createFederation({
|
|
273
|
+
...baseOptions,
|
|
274
|
+
queue: { task: taskQueue }
|
|
275
|
+
});
|
|
276
|
+
federation.defineTask("dedicated", {
|
|
277
|
+
schema: stringSchema,
|
|
278
|
+
handler: () => {},
|
|
279
|
+
queue: dedicated
|
|
280
|
+
});
|
|
281
|
+
const controller = new AbortController();
|
|
282
|
+
const listening = federation.startQueue(void 0, { signal: controller.signal });
|
|
283
|
+
strictEqual(taskQueue.listenCount, 1);
|
|
284
|
+
strictEqual(dedicated.listenCount, 1);
|
|
285
|
+
controller.abort();
|
|
286
|
+
await listening;
|
|
287
|
+
});
|
|
288
|
+
await t.step("starts a per-task queue even without a federation queue", async () => {
|
|
289
|
+
const dedicated = new MockQueue();
|
|
290
|
+
const federation = createFederation({ ...baseOptions });
|
|
291
|
+
federation.defineTask("dedicated", {
|
|
292
|
+
schema: stringSchema,
|
|
293
|
+
handler: () => {},
|
|
294
|
+
queue: dedicated
|
|
295
|
+
});
|
|
296
|
+
const controller = new AbortController();
|
|
297
|
+
const listening = federation.startQueue(void 0, { signal: controller.signal });
|
|
298
|
+
strictEqual(dedicated.listenCount, 1);
|
|
299
|
+
controller.abort();
|
|
300
|
+
await listening;
|
|
301
|
+
});
|
|
302
|
+
await t.step("does not listen twice on a per-task queue shared with a standard queue", async () => {
|
|
303
|
+
const shared = new MockQueue();
|
|
304
|
+
const federation = createFederation({
|
|
305
|
+
...baseOptions,
|
|
306
|
+
queue: { task: shared }
|
|
307
|
+
});
|
|
308
|
+
federation.defineTask("reuses-task-queue", {
|
|
309
|
+
schema: stringSchema,
|
|
310
|
+
handler: () => {},
|
|
311
|
+
queue: shared
|
|
312
|
+
});
|
|
313
|
+
const controller = new AbortController();
|
|
314
|
+
const listening = federation.startQueue(void 0, { signal: controller.signal });
|
|
315
|
+
strictEqual(shared.listenCount, 1);
|
|
316
|
+
controller.abort();
|
|
317
|
+
await listening;
|
|
318
|
+
});
|
|
319
|
+
await t.step("routes an enqueued task on a dedicated queue to its handler", async () => {
|
|
320
|
+
const dedicated = new MockQueue();
|
|
321
|
+
const federation = createFederation({ ...baseOptions });
|
|
322
|
+
let received;
|
|
323
|
+
const task = federation.defineTask("dedicated-end-to-end", {
|
|
324
|
+
schema: stringSchema,
|
|
325
|
+
handler: (_ctx, data) => {
|
|
326
|
+
received = data;
|
|
327
|
+
},
|
|
328
|
+
queue: dedicated
|
|
329
|
+
});
|
|
330
|
+
await federation.createContext(new URL("https://example.com/"), void 0).enqueueTask(task, "payload");
|
|
331
|
+
strictEqual(dedicated.enqueued.length, 1);
|
|
332
|
+
await federation.processQueuedTask(void 0, dedicated.enqueued[0].message);
|
|
333
|
+
strictEqual(received, "payload");
|
|
334
|
+
});
|
|
335
|
+
await t.step("queue: \"task\" starts the outbox fallback when no task queue is set", async () => {
|
|
336
|
+
const inbox = new MockQueue();
|
|
337
|
+
const outbox = new MockQueue();
|
|
338
|
+
const fanout = new MockQueue();
|
|
339
|
+
const federation = createFederation({
|
|
340
|
+
...baseOptions,
|
|
341
|
+
queue: {
|
|
342
|
+
inbox,
|
|
343
|
+
outbox,
|
|
344
|
+
fanout
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
federation.defineTask("fallback", {
|
|
348
|
+
schema: stringSchema,
|
|
349
|
+
handler: () => {}
|
|
350
|
+
});
|
|
351
|
+
const controller = new AbortController();
|
|
352
|
+
const listening = federation.startQueue(void 0, {
|
|
353
|
+
signal: controller.signal,
|
|
354
|
+
queue: "task"
|
|
355
|
+
});
|
|
356
|
+
strictEqual(outbox.listenCount, 1);
|
|
357
|
+
strictEqual(inbox.listenCount, 0);
|
|
358
|
+
strictEqual(fanout.listenCount, 0);
|
|
359
|
+
controller.abort();
|
|
360
|
+
await listening;
|
|
361
|
+
});
|
|
362
|
+
await t.step("queue: \"task\" starts a task queue shared with the outbox once", async () => {
|
|
363
|
+
const inbox = new MockQueue();
|
|
364
|
+
const shared = new MockQueue();
|
|
365
|
+
const fanout = new MockQueue();
|
|
366
|
+
const federation = createFederation({
|
|
367
|
+
...baseOptions,
|
|
368
|
+
queue: {
|
|
369
|
+
inbox,
|
|
370
|
+
outbox: shared,
|
|
371
|
+
fanout,
|
|
372
|
+
task: shared
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
const controller = new AbortController();
|
|
376
|
+
const listening = federation.startQueue(void 0, {
|
|
377
|
+
signal: controller.signal,
|
|
378
|
+
queue: "task"
|
|
379
|
+
});
|
|
380
|
+
strictEqual(shared.listenCount, 1);
|
|
381
|
+
strictEqual(inbox.listenCount, 0);
|
|
382
|
+
strictEqual(fanout.listenCount, 0);
|
|
383
|
+
controller.abort();
|
|
384
|
+
await listening;
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
test("MockQueue.listen() resolves on a pre-aborted signal", async () => {
|
|
388
|
+
const queue = new MockQueue();
|
|
389
|
+
const controller = new AbortController();
|
|
390
|
+
controller.abort();
|
|
391
|
+
let timer;
|
|
392
|
+
const settled = await Promise.race([queue.listen(() => {}, { signal: controller.signal }).then(() => "resolved"), new Promise((resolve) => {
|
|
393
|
+
timer = setTimeout(() => resolve("pending"), 50);
|
|
394
|
+
})]);
|
|
395
|
+
clearTimeout(timer);
|
|
396
|
+
strictEqual(settled, "resolved");
|
|
397
|
+
});
|
|
398
|
+
test("processQueuedTask() task dispatch", async (t) => {
|
|
399
|
+
await t.step("drops an unknown task with a warning", async () => {
|
|
400
|
+
const queue = new MockQueue();
|
|
401
|
+
const federation = createFederation({
|
|
402
|
+
...baseOptions,
|
|
403
|
+
queue: { task: queue }
|
|
404
|
+
});
|
|
405
|
+
const message = await makeTaskMessage("never-defined", "data");
|
|
406
|
+
await federation.processQueuedTask(void 0, message);
|
|
407
|
+
strictEqual(queue.enqueued.length, 0);
|
|
408
|
+
});
|
|
409
|
+
await t.step("drops an undecodable payload without retry", async () => {
|
|
410
|
+
const queue = new MockQueue();
|
|
411
|
+
const federation = createFederation({
|
|
412
|
+
...baseOptions,
|
|
413
|
+
queue: { task: queue }
|
|
414
|
+
});
|
|
415
|
+
let called = 0;
|
|
416
|
+
federation.defineTask("broken-wire", {
|
|
417
|
+
schema: stringSchema,
|
|
418
|
+
handler: () => {
|
|
419
|
+
called++;
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
const message = await makeTaskMessage("broken-wire", "data");
|
|
423
|
+
await federation.processQueuedTask(void 0, {
|
|
424
|
+
...message,
|
|
425
|
+
data: "garbage that is not devalue"
|
|
426
|
+
});
|
|
427
|
+
strictEqual(called, 0);
|
|
428
|
+
strictEqual(queue.enqueued.length, 0);
|
|
429
|
+
});
|
|
430
|
+
await t.step("drops a drifted payload without retry", async () => {
|
|
431
|
+
const queue = new MockQueue();
|
|
432
|
+
const federation = createFederation({
|
|
433
|
+
...baseOptions,
|
|
434
|
+
queue: { task: queue }
|
|
435
|
+
});
|
|
436
|
+
let called = 0;
|
|
437
|
+
federation.defineTask("drifted", {
|
|
438
|
+
schema: numberSchema,
|
|
439
|
+
handler: () => {
|
|
440
|
+
called++;
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
const message = await makeTaskMessage("drifted", "stringly-typed");
|
|
444
|
+
await federation.processQueuedTask(void 0, message);
|
|
445
|
+
strictEqual(called, 0);
|
|
446
|
+
strictEqual(queue.enqueued.length, 0);
|
|
447
|
+
});
|
|
448
|
+
await t.step("re-enqueues with attempt + 1 when the handler throws", async () => {
|
|
449
|
+
const queue = new MockQueue();
|
|
450
|
+
const federation = createFederation({
|
|
451
|
+
...baseOptions,
|
|
452
|
+
queue: { task: queue }
|
|
453
|
+
});
|
|
454
|
+
const errors = [];
|
|
455
|
+
federation.defineTask("flaky", {
|
|
456
|
+
schema: stringSchema,
|
|
457
|
+
handler: () => {
|
|
458
|
+
throw new Error("boom");
|
|
459
|
+
},
|
|
460
|
+
retryPolicy: () => Temporal.Duration.from({ milliseconds: 1 }),
|
|
461
|
+
onError: (_ctx, error, data) => {
|
|
462
|
+
errors.push([error, data]);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
const message = await makeTaskMessage("flaky", "data", { orderingKey: "k" });
|
|
466
|
+
await federation.processQueuedTask(void 0, message);
|
|
467
|
+
strictEqual(queue.enqueued.length, 1);
|
|
468
|
+
const retry = queue.enqueued[0];
|
|
469
|
+
strictEqual(retry.message.attempt, 1);
|
|
470
|
+
strictEqual(retry.message.taskName, "flaky");
|
|
471
|
+
strictEqual(retry.message.orderingKey, "k");
|
|
472
|
+
strictEqual(retry.options?.orderingKey, "k");
|
|
473
|
+
ok(retry.options?.delay instanceof Temporal.Duration);
|
|
474
|
+
strictEqual(errors.length, 1);
|
|
475
|
+
deepStrictEqual(errors[0], [/* @__PURE__ */ new Error("boom"), "data"]);
|
|
476
|
+
});
|
|
477
|
+
await t.step("gives up when the retry policy returns null", async () => {
|
|
478
|
+
const queue = new MockQueue();
|
|
479
|
+
const federation = createFederation({
|
|
480
|
+
...baseOptions,
|
|
481
|
+
queue: { task: queue }
|
|
482
|
+
});
|
|
483
|
+
federation.defineTask("hopeless", {
|
|
484
|
+
schema: stringSchema,
|
|
485
|
+
handler: () => {
|
|
486
|
+
throw new Error("boom");
|
|
487
|
+
},
|
|
488
|
+
retryPolicy: () => null
|
|
489
|
+
});
|
|
490
|
+
const message = await makeTaskMessage("hopeless", "data");
|
|
491
|
+
await federation.processQueuedTask(void 0, message);
|
|
492
|
+
strictEqual(queue.enqueued.length, 0);
|
|
493
|
+
});
|
|
494
|
+
await t.step("per-task retryPolicy overrides the federation default", async () => {
|
|
495
|
+
const queue = new MockQueue();
|
|
496
|
+
const federation = createFederation({
|
|
497
|
+
...baseOptions,
|
|
498
|
+
queue: { task: queue },
|
|
499
|
+
taskRetryPolicy: () => null
|
|
500
|
+
});
|
|
501
|
+
federation.defineTask("override", {
|
|
502
|
+
schema: stringSchema,
|
|
503
|
+
handler: () => {
|
|
504
|
+
throw new Error("boom");
|
|
505
|
+
},
|
|
506
|
+
retryPolicy: () => Temporal.Duration.from({ milliseconds: 1 })
|
|
507
|
+
});
|
|
508
|
+
federation.defineTask("default", {
|
|
509
|
+
schema: stringSchema,
|
|
510
|
+
handler: () => {
|
|
511
|
+
throw new Error("boom");
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
await federation.processQueuedTask(void 0, await makeTaskMessage("override", "data"));
|
|
515
|
+
strictEqual(queue.enqueued.length, 1);
|
|
516
|
+
await federation.processQueuedTask(void 0, await makeTaskMessage("default", "data"));
|
|
517
|
+
strictEqual(queue.enqueued.length, 1);
|
|
518
|
+
});
|
|
519
|
+
await t.step("still retries when message.started is malformed", async () => {
|
|
520
|
+
const queue = new MockQueue();
|
|
521
|
+
const federation = createFederation({
|
|
522
|
+
...baseOptions,
|
|
523
|
+
queue: { task: queue }
|
|
524
|
+
});
|
|
525
|
+
federation.defineTask("bad-started", {
|
|
526
|
+
schema: stringSchema,
|
|
527
|
+
handler: () => {
|
|
528
|
+
throw new Error("boom");
|
|
529
|
+
},
|
|
530
|
+
retryPolicy: () => Temporal.Duration.from({ milliseconds: 1 })
|
|
531
|
+
});
|
|
532
|
+
const message = await makeTaskMessage("bad-started", "data", { started: "not-an-instant" });
|
|
533
|
+
await federation.processQueuedTask(void 0, message);
|
|
534
|
+
strictEqual(queue.enqueued.length, 1);
|
|
535
|
+
strictEqual(queue.enqueued[0].message.attempt, 1);
|
|
536
|
+
});
|
|
537
|
+
await t.step("rethrows on a nativeRetrial queue", async () => {
|
|
538
|
+
const queue = new MockQueue({ nativeRetrial: true });
|
|
539
|
+
const federation = createFederation({
|
|
540
|
+
...baseOptions,
|
|
541
|
+
queue: { task: queue }
|
|
542
|
+
});
|
|
543
|
+
federation.defineTask("native", {
|
|
544
|
+
schema: stringSchema,
|
|
545
|
+
handler: () => {
|
|
546
|
+
throw new Error("boom");
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
const message = await makeTaskMessage("native", "data");
|
|
550
|
+
await rejects(() => federation.processQueuedTask(void 0, message), { message: /boom/ });
|
|
551
|
+
strictEqual(queue.enqueued.length, 0);
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
//#endregion
|
|
555
|
+
export {};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import "../std__assert-
|
|
5
|
-
import { r as assertFalse } from "../assert_rejects-
|
|
6
|
-
import { t as assert } from "../assert-
|
|
7
|
-
import { t as hasMalformedKnownTemporalLiteral } from "../temporal-
|
|
4
|
+
import "../std__assert-Dh6uLf_q.mjs";
|
|
5
|
+
import { r as assertFalse } from "../assert_rejects-DAdaaIWM.mjs";
|
|
6
|
+
import { t as assert } from "../assert-DvVHoIZ0.mjs";
|
|
7
|
+
import { t as hasMalformedKnownTemporalLiteral } from "../temporal-CyOjsqPj.mjs";
|
|
8
8
|
import { test } from "@fedify/fixture";
|
|
9
9
|
//#region src/federation/temporal.test.ts
|
|
10
10
|
test("hasMalformedKnownTemporalLiteral() detects expanded proof timestamps", async () => {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { r as createRequestContext } from "../context-
|
|
5
|
-
import { t as
|
|
6
|
-
import "../
|
|
7
|
-
import
|
|
8
|
-
import { t as
|
|
9
|
-
import { o as createFederation, s as handleWebFinger } from "../middleware-
|
|
4
|
+
import { r as createRequestContext } from "../context-BBRTgkrs.mjs";
|
|
5
|
+
import { t as MemoryKvStore } from "../kv-CIvR3Non.mjs";
|
|
6
|
+
import { t as assertEquals } from "../assert_equals-PBVKNMJd.mjs";
|
|
7
|
+
import "../std__assert-Dh6uLf_q.mjs";
|
|
8
|
+
import { t as assertNotEquals } from "../assert_not_equals-C8vcTTA3.mjs";
|
|
9
|
+
import { o as createFederation, s as handleWebFinger } from "../middleware-iP9VRMus.mjs";
|
|
10
10
|
import { Image, Link, Person, Tombstone } from "@fedify/vocab";
|
|
11
11
|
import { createTestMeterProvider, test } from "@fedify/fixture";
|
|
12
12
|
//#region src/federation/webfinger.test.ts
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-
|
|
6
|
-
import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-
|
|
7
|
-
import { o as validateCryptoKey, r as fetchKeyDetailed } from "./key-
|
|
4
|
+
import { n as version, t as name } from "./deno-D3gb4X9v.mjs";
|
|
5
|
+
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-BXR36qzu.mjs";
|
|
6
|
+
import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CgDcxvjV.mjs";
|
|
7
|
+
import { o as validateCryptoKey, r as fetchKeyDetailed } from "./key-BM_SRwBV.mjs";
|
|
8
8
|
import { getLogger } from "@logtape/logtape";
|
|
9
9
|
import { CryptographicKey } from "@fedify/vocab";
|
|
10
10
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
@@ -10,7 +10,7 @@ import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } fro
|
|
|
10
10
|
import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
|
|
11
11
|
//#region deno.json
|
|
12
12
|
var name = "@fedify/fedify";
|
|
13
|
-
var version = "2.4.0-dev.
|
|
13
|
+
var version = "2.4.0-dev.1504+756217cc";
|
|
14
14
|
//#endregion
|
|
15
15
|
//#region src/sig/accept.ts
|
|
16
16
|
/**
|
|
@@ -1200,31 +1200,21 @@ async function resolveFetchedKey(document, cacheKey, keyId, cls, { documentLoade
|
|
|
1200
1200
|
const keys = cls === CryptographicKey ? object.getPublicKeys({
|
|
1201
1201
|
documentLoader,
|
|
1202
1202
|
contextLoader,
|
|
1203
|
-
suppressError: true,
|
|
1204
1203
|
tracerProvider
|
|
1205
1204
|
}) : object.getAssertionMethods({
|
|
1206
1205
|
documentLoader,
|
|
1207
1206
|
contextLoader,
|
|
1208
|
-
suppressError: true,
|
|
1209
1207
|
tracerProvider
|
|
1210
1208
|
});
|
|
1211
1209
|
let length = 0;
|
|
1212
1210
|
let lastKey = null;
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
break;
|
|
1220
|
-
}
|
|
1211
|
+
for await (const k of keys) {
|
|
1212
|
+
length++;
|
|
1213
|
+
lastKey = k;
|
|
1214
|
+
if (k.id?.href === keyId) {
|
|
1215
|
+
key = k;
|
|
1216
|
+
break;
|
|
1221
1217
|
}
|
|
1222
|
-
} catch (e) {
|
|
1223
|
-
if (!(e instanceof TypeError)) throw e;
|
|
1224
|
-
logger.debug("Failed to verify; a malformed key was encountered while iterating the keys of {keyId}; treating it as a missing key: {error}", {
|
|
1225
|
-
keyId,
|
|
1226
|
-
error: e
|
|
1227
|
-
});
|
|
1228
1218
|
}
|
|
1229
1219
|
const keyIdUrl = new URL(keyId);
|
|
1230
1220
|
if (key == null && keyIdUrl.hash === "" && length === 1) key = lastKey;
|