@fedify/fedify 2.4.0-dev.1490 → 2.4.0-dev.1508
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 +7 -5
- 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-CwwQRArg.mjs → builder-DlWCek6Z.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-Yohf1ISx.mjs → deno-nexucTAX.mjs} +1 -1
- package/dist/{docloader-B6DFWEOx.mjs → docloader-DAdEN549.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-C3eJp_Ha.cjs → http-BCj9dlA_.cjs} +1 -1
- package/dist/{http-BqSm-f2G.js → http-Dv3147TL.js} +1 -1
- package/dist/{http-BeDlAH97.mjs → http-inQqS3yx.mjs} +4 -4
- package/dist/{key-DNBTcjuV.mjs → key-CGrr6-I2.mjs} +2 -2
- package/dist/{kv-cache-Crq8nvSV.js → kv-cache-C2oTEXyb.js} +1 -1
- package/dist/{kv-cache-b2HaCLC-.mjs → kv-cache-C3qkFkbt.mjs} +1 -1
- package/dist/{kv-cache-CGJMNSs-.cjs → kv-cache-c8X6svwE.cjs} +1 -1
- package/dist/{ld-CQlFhsU6.mjs → ld-fnWaNT8y.mjs} +3 -3
- package/dist/{metrics-B6RKtTLt.mjs → metrics-C5hMINAa.mjs} +1 -1
- package/dist/{middleware-CQygcRYE.mjs → middleware-CQlq6ykI.mjs} +1 -1
- package/dist/{middleware-CBM9vGYg.cjs → middleware-DeVzbg1I.cjs} +690 -30
- package/dist/{middleware-CsxoclmN.mjs → middleware-Dl2nyVgB.mjs} +306 -46
- package/dist/{middleware-B3vFLcCh.js → middleware-vQ_ZpgcX.js} +679 -31
- 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-B1FnbZ1w.mjs → owner-Bq2eZyO_.mjs} +2 -2
- package/dist/{proof-ChmZL5rY.cjs → proof-Buz7I8wj.cjs} +1 -1
- package/dist/{proof-BhNTn0HE.js → proof-CTG9azxN.js} +1 -1
- package/dist/{proof-BUuvq0ia.mjs → proof-DZCjcFCd.mjs} +5 -5
- package/dist/{send-Dq-Ofquk.mjs → send-B253S5gN.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 -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-BBjXFNOb.mjs → std__assert-Dh6uLf_q.mjs} +4 -4
- package/dist/{temporal-C-vrfOld.mjs → temporal-d6TshGjq.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 +9 -7
- /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-Dl2nyVgB.mjs";
|
|
6
|
+
import { r as createFederationBuilder } from "../../builder-DlWCek6Z.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-d6TshGjq.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-Dl2nyVgB.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
|
|
@@ -11,7 +11,7 @@ let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conve
|
|
|
11
11
|
let byte_encodings_base64 = require("byte-encodings/base64");
|
|
12
12
|
//#region deno.json
|
|
13
13
|
var name = "@fedify/fedify";
|
|
14
|
-
var version = "2.4.0-dev.
|
|
14
|
+
var version = "2.4.0-dev.1508+68519bf4";
|
|
15
15
|
//#endregion
|
|
16
16
|
//#region src/sig/accept.ts
|
|
17
17
|
/**
|
|
@@ -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.1508+68519bf4";
|
|
14
14
|
//#endregion
|
|
15
15
|
//#region src/sig/accept.ts
|
|
16
16
|
/**
|
|
@@ -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-nexucTAX.mjs";
|
|
5
|
+
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-C5hMINAa.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-CGrr6-I2.mjs";
|
|
8
8
|
import { getLogger } from "@logtape/logtape";
|
|
9
9
|
import { CryptographicKey } from "@fedify/vocab";
|
|
10
10
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
-
import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-
|
|
4
|
+
import { n as version, t as name } from "./deno-nexucTAX.mjs";
|
|
5
|
+
import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-C5hMINAa.mjs";
|
|
6
6
|
import { getLogger } from "@logtape/logtape";
|
|
7
7
|
import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
|
|
8
8
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
|
-
import { C as recordDocumentCache, d as validateCryptoKey, t as doubleKnock } from "./http-
|
|
3
|
+
import { C as recordDocumentCache, d as validateCryptoKey, t as doubleKnock } from "./http-Dv3147TL.js";
|
|
4
4
|
import { getLogger } from "@logtape/logtape";
|
|
5
5
|
import { curry } from "es-toolkit";
|
|
6
6
|
import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { p as recordDocumentCache } from "./metrics-
|
|
4
|
+
import { p as recordDocumentCache } from "./metrics-C5hMINAa.mjs";
|
|
5
5
|
import { getLogger } from "@logtape/logtape";
|
|
6
6
|
import { preloadedContexts } from "@fedify/vocab-runtime";
|
|
7
7
|
//#region src/utils/kv-cache.ts
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { Temporal } = require("@js-temporal/polyfill");
|
|
2
2
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
3
3
|
require("./chunk-DDcVe30Y.cjs");
|
|
4
|
-
const require_http = require("./http-
|
|
4
|
+
const require_http = require("./http-BCj9dlA_.cjs");
|
|
5
5
|
let _logtape_logtape = require("@logtape/logtape");
|
|
6
6
|
let es_toolkit = require("es-toolkit");
|
|
7
7
|
let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import "@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 { n as fetchKey, o as validateCryptoKey } from "./key-
|
|
4
|
+
import { n as version, t as name } from "./deno-nexucTAX.mjs";
|
|
5
|
+
import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-C5hMINAa.mjs";
|
|
6
|
+
import { n as fetchKey, o as validateCryptoKey } from "./key-CGrr6-I2.mjs";
|
|
7
7
|
import { getLogger } from "@logtape/logtape";
|
|
8
8
|
import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
|
|
9
9
|
import { SpanStatusCode, trace } from "@opentelemetry/api";
|