@fedify/fedify 2.3.0 → 2.4.0-dev.1422
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/dist/{assert-OguE97r2.mjs → assert-DikXweDx.mjs} +1 -1
- package/dist/{assert_instance_of-DBC5X09g.mjs → assert_instance_of-C4Ri6VuN.mjs} +1 -1
- package/dist/{assert_not_equals-DkVK8oqV.mjs → assert_not_equals--wG9hV7u.mjs} +1 -1
- package/dist/{assert_rejects-DN60FHPX.mjs → assert_rejects-DQP-q39h.mjs} +2 -2
- package/dist/{assert_strict_equals-XEgZAlrj.mjs → assert_strict_equals-Dmjbg-bA.mjs} +1 -1
- package/dist/{assert_throws-BOkhLGYc.mjs → assert_throws-4NwKEy2q.mjs} +1 -1
- package/dist/{builder-DYXTKqvC.mjs → builder-Gef_ZeDE.mjs} +23 -3
- package/dist/codec-Bf-rTqks.mjs +94 -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-BrIOaBSs.mjs} +7 -1
- package/dist/{context-BBVLF7lx.d.cts → context-BuGH_Vft.d.cts} +286 -70
- package/dist/{context-BU6jSQdo.d.ts → context-CLtZxCu6.d.ts} +285 -70
- package/dist/{deno-ChfuM30K.mjs → deno-BmD7XRaO.mjs} +1 -1
- package/dist/{docloader-Bu-Oecom.mjs → docloader-B8AJowCx.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 +200 -19
- 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 +5 -260
- 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/tasks.test.d.mts +2 -0
- package/dist/federation/tasks/tasks.test.mjs +651 -0
- package/dist/federation/temporal.test.mjs +4 -4
- package/dist/federation/webfinger.test.mjs +6 -6
- package/dist/{http-Q2nW0DHO.cjs → http-BCSQJ_gv.cjs} +1 -1
- package/dist/{http-CiPBp7Ax.js → http-CJ0WNyqA.js} +1 -1
- package/dist/{http-Cm_e9dY7.mjs → http-GQoPA_YT.mjs} +4 -4
- package/dist/{key-nrqRBwjx.mjs → key-BLy_TeTg.mjs} +2 -2
- package/dist/{kv-cache-BQLjC1KN.js → kv-cache-CjEcb9qH.js} +1 -1
- package/dist/{kv-cache-QkLiue07.cjs → kv-cache-DXwu7BtY.cjs} +1 -1
- package/dist/{kv-cache-B3Na5DMI.mjs → kv-cache-pw-kBdVz.mjs} +1 -1
- package/dist/{ld-CaRoGhYH.mjs → ld-CWgxPlZq.mjs} +3 -3
- package/dist/{metrics-BSUZSyQU.mjs → metrics-DUKoLd_E.mjs} +1 -1
- package/dist/{middleware-BIj0BBxb.js → middleware-2Us0G3Iy.js} +277 -10
- package/dist/{middleware-DXLo-UOS.cjs → middleware-BiCk4wGY.cjs} +277 -10
- package/dist/{middleware-BIctkZ9S.mjs → middleware-Dq-HhJHd.mjs} +1 -1
- package/dist/{middleware-CwJv5KxX.mjs → middleware-PZaroDUk.mjs} +182 -23
- package/dist/{mod-C0F6kvgS.d.cts → mod-DRokcHeU.d.cts} +1 -1
- package/dist/{mod-vPYVoa5n.d.ts → mod-r-1xOW_C.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/mq-CvJAzRz5.mjs +260 -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-BgFLCJQ_.mjs} +1 -1
- package/dist/{owner-CyK8YW7-.mjs → owner-U0Z8Ex6i.mjs} +2 -2
- package/dist/{proof-CIUSLcHg.cjs → proof-DA3hLGSO.cjs} +1 -1
- package/dist/{proof-Ba14ORbL.js → proof-f7K32nz7.js} +1 -1
- package/dist/{proof-DXHo4Yon.mjs → proof-nb5tfciW.mjs} +5 -5
- package/dist/{send-B4Ld91pX.mjs → send-CLfW5Fdf.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-BTEgfoJo.mjs} +4 -4
- package/dist/{temporal-E7cF2Hsh.mjs → temporal-2ry_jdCK.mjs} +1 -1
- package/dist/testing/mod.d.mts +378 -7
- package/dist/testing/mod.mjs +3 -2
- package/dist/testing-CjYPStvE.mjs +55 -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 +8 -6
- /package/dist/{accept-CPkZzmGN.mjs → accept-CceiKpCy.mjs} +0 -0
- /package/dist/{activity-listener-tztVvlNb.mjs → activity-listener-BeTGV3wc.mjs} +0 -0
- /package/dist/{assert_equals-C-ZRDbaf.mjs → assert_equals-Ew3jOFa3.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-C3kae-6B.mjs} +0 -0
- /package/dist/{kv-rV3vodCc.mjs → kv-x2IvBUyq.mjs} +0 -0
- /package/dist/{negotiation-DDstyBvc.mjs → negotiation-m_L2nyS3.mjs} +0 -0
- /package/dist/{public-audience-Cvbr2Gzt.mjs → public-audience-N3pyOx2p.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
|
@@ -2,10 +2,10 @@ const { Temporal } = require("@js-temporal/polyfill");
|
|
|
2
2
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
3
3
|
const require_chunk = require("./chunk-DDcVe30Y.cjs");
|
|
4
4
|
const require_transformers = require("./transformers-NeAONrAq.cjs");
|
|
5
|
-
const require_http = require("./http-
|
|
6
|
-
const require_proof = require("./proof-
|
|
5
|
+
const require_http = require("./http-BCSQJ_gv.cjs");
|
|
6
|
+
const require_proof = require("./proof-DA3hLGSO.cjs");
|
|
7
7
|
const require_types = require("./types-KC4QAoxe.cjs");
|
|
8
|
-
const require_kv_cache = require("./kv-cache-
|
|
8
|
+
const require_kv_cache = require("./kv-cache-DXwu7BtY.cjs");
|
|
9
9
|
let _logtape_logtape = require("@logtape/logtape");
|
|
10
10
|
let _fedify_uri_template = require("@fedify/uri-template");
|
|
11
11
|
let _fedify_vocab = require("@fedify/vocab");
|
|
@@ -18,6 +18,7 @@ let _fedify_vocab_runtime_jsonld = require("@fedify/vocab-runtime/jsonld");
|
|
|
18
18
|
_fedify_vocab_runtime_jsonld = require_chunk.__toESM(_fedify_vocab_runtime_jsonld, 1);
|
|
19
19
|
let _fedify_webfinger = require("@fedify/webfinger");
|
|
20
20
|
let _opentelemetry_sdk_metrics = require("@opentelemetry/sdk-metrics");
|
|
21
|
+
let devalue = require("devalue");
|
|
21
22
|
let node_url = require("node:url");
|
|
22
23
|
//#region src/federation/activity-listener.ts
|
|
23
24
|
var ActivityListenerSet = class {
|
|
@@ -108,6 +109,7 @@ var FederationBuilderImpl = class {
|
|
|
108
109
|
idempotencyStrategy;
|
|
109
110
|
collectionTypeIds;
|
|
110
111
|
collectionCallbacks;
|
|
112
|
+
taskDefinitions;
|
|
111
113
|
/**
|
|
112
114
|
* Symbol registry for unique identification of unnamed symbols.
|
|
113
115
|
*/
|
|
@@ -118,6 +120,7 @@ var FederationBuilderImpl = class {
|
|
|
118
120
|
this.objectTypeIds = {};
|
|
119
121
|
this.collectionCallbacks = {};
|
|
120
122
|
this.collectionTypeIds = {};
|
|
123
|
+
this.taskDefinitions = /* @__PURE__ */ new Map();
|
|
121
124
|
}
|
|
122
125
|
/**
|
|
123
126
|
* Builds the federation object.
|
|
@@ -156,6 +159,7 @@ var FederationBuilderImpl = class {
|
|
|
156
159
|
f.unverifiedActivityHandler = this.unverifiedActivityHandler;
|
|
157
160
|
f.outboxPermanentFailureHandler = this.outboxPermanentFailureHandler;
|
|
158
161
|
f.idempotencyStrategy = this.idempotencyStrategy;
|
|
162
|
+
f.taskDefinitions = new Map(this.taskDefinitions);
|
|
159
163
|
return f;
|
|
160
164
|
}
|
|
161
165
|
_getTracer() {
|
|
@@ -288,6 +292,23 @@ var FederationBuilderImpl = class {
|
|
|
288
292
|
setWebFingerLinksDispatcher(dispatcher) {
|
|
289
293
|
this.webFingerLinksDispatcher = dispatcher;
|
|
290
294
|
}
|
|
295
|
+
defineTask(name, options) {
|
|
296
|
+
if (this.taskDefinitions.has(name)) throw new TypeError(`Task ${JSON.stringify(name)} is already defined.`);
|
|
297
|
+
const handle = {
|
|
298
|
+
name,
|
|
299
|
+
schema: options.schema
|
|
300
|
+
};
|
|
301
|
+
this.taskDefinitions.set(name, {
|
|
302
|
+
name,
|
|
303
|
+
schema: options.schema,
|
|
304
|
+
handle,
|
|
305
|
+
handler: options.handler,
|
|
306
|
+
onError: options.onError,
|
|
307
|
+
retryPolicy: options.retryPolicy,
|
|
308
|
+
queue: options.queue
|
|
309
|
+
});
|
|
310
|
+
return handle;
|
|
311
|
+
}
|
|
291
312
|
/**
|
|
292
313
|
* The RFC 6570 template-literal `path` overloads were removed for
|
|
293
314
|
* type-checking efficiency, so the URI variable types can no longer be
|
|
@@ -1393,7 +1414,7 @@ function acceptsJsonLd(request) {
|
|
|
1393
1414
|
}
|
|
1394
1415
|
//#endregion
|
|
1395
1416
|
//#region src/federation/temporal.ts
|
|
1396
|
-
function isPlainObject(value) {
|
|
1417
|
+
function isPlainObject$1(value) {
|
|
1397
1418
|
return typeof value === "object" && value != null && !Array.isArray(value);
|
|
1398
1419
|
}
|
|
1399
1420
|
function normalizeDateTimeLiteral(value) {
|
|
@@ -1431,11 +1452,11 @@ const QUESTION_CLOSED_IRI = "https://www.w3.org/ns/activitystreams#closed";
|
|
|
1431
1452
|
const XSD_DATE_TIME_IRI = "http://www.w3.org/2001/XMLSchema#dateTime";
|
|
1432
1453
|
function hasMalformedExpandedDateTimeLiteral(value) {
|
|
1433
1454
|
if (Array.isArray(value)) return value.some(hasMalformedExpandedDateTimeLiteral);
|
|
1434
|
-
return isPlainObject(value) && "@value" in value && isMalformedDateTimeLiteral(value["@value"]);
|
|
1455
|
+
return isPlainObject$1(value) && "@value" in value && isMalformedDateTimeLiteral(value["@value"]);
|
|
1435
1456
|
}
|
|
1436
1457
|
function hasMalformedExpandedQuestionClosedLiteral(value) {
|
|
1437
1458
|
if (Array.isArray(value)) return value.some(hasMalformedExpandedQuestionClosedLiteral);
|
|
1438
|
-
if (!isPlainObject(value) || !("@value" in value)) return false;
|
|
1459
|
+
if (!isPlainObject$1(value) || !("@value" in value)) return false;
|
|
1439
1460
|
const literal = value["@value"];
|
|
1440
1461
|
if (typeof literal === "boolean") return false;
|
|
1441
1462
|
if (typeof literal !== "string") return false;
|
|
@@ -1445,11 +1466,11 @@ function hasMalformedExpandedQuestionClosedLiteral(value) {
|
|
|
1445
1466
|
}
|
|
1446
1467
|
function hasMalformedExpandedDurationLiteral(value) {
|
|
1447
1468
|
if (Array.isArray(value)) return value.some(hasMalformedExpandedDurationLiteral);
|
|
1448
|
-
return isPlainObject(value) && "@value" in value && isMalformedDurationLiteral(value["@value"]);
|
|
1469
|
+
return isPlainObject$1(value) && "@value" in value && isMalformedDurationLiteral(value["@value"]);
|
|
1449
1470
|
}
|
|
1450
1471
|
function hasMalformedKnownTemporalLiteralInternal(value, visited) {
|
|
1451
1472
|
if (Array.isArray(value)) return value.some((item) => hasMalformedKnownTemporalLiteralInternal(item, visited));
|
|
1452
|
-
if (!isPlainObject(value)) return false;
|
|
1473
|
+
if (!isPlainObject$1(value)) return false;
|
|
1453
1474
|
if (visited.has(value)) return false;
|
|
1454
1475
|
visited.add(value);
|
|
1455
1476
|
if ("@value" in value) return false;
|
|
@@ -3553,6 +3574,94 @@ function createExponentialBackoffPolicy(options = {}) {
|
|
|
3553
3574
|
};
|
|
3554
3575
|
}
|
|
3555
3576
|
//#endregion
|
|
3577
|
+
//#region src/federation/tasks/codec.ts
|
|
3578
|
+
var TaskCodec = class TaskCodec {
|
|
3579
|
+
options;
|
|
3580
|
+
constructor(options) {
|
|
3581
|
+
this.options = options;
|
|
3582
|
+
}
|
|
3583
|
+
serialize = (data) => (0, devalue.stringifyAsync)(data, { Vocab: this.#stringifyVocab });
|
|
3584
|
+
deserialize = (raw) => this.#revive(/* @__PURE__ */ new Map())((0, devalue.parse)(raw, { Vocab: VocabHolder.from }));
|
|
3585
|
+
/** Validates `data` against `schema`, then serializes it. */
|
|
3586
|
+
encode = async (schema, data) => this.serialize(await TaskCodec.validate(schema, data));
|
|
3587
|
+
/** Deserializes `raw`, then validates the result against `schema`. */
|
|
3588
|
+
decode = async (schema, raw) => TaskCodec.validate(schema, await this.deserialize(raw));
|
|
3589
|
+
static validate = async (schema, data) => getValueIfSchema(await schema["~standard"].validate(data));
|
|
3590
|
+
#stringifyVocab = (value) => isVocab(value) && this.#toWire(value);
|
|
3591
|
+
#toWire = async (value) => ({
|
|
3592
|
+
kind: value instanceof _fedify_vocab.Link ? "link" : "object",
|
|
3593
|
+
jsonLd: await value.toJsonLd({
|
|
3594
|
+
format: "expand",
|
|
3595
|
+
...this.options
|
|
3596
|
+
})
|
|
3597
|
+
});
|
|
3598
|
+
#revive = (seen) => {
|
|
3599
|
+
const inner = async (node) => {
|
|
3600
|
+
if (node === null || typeof node !== "object") return node;
|
|
3601
|
+
if (seen.has(node)) return seen.get(node);
|
|
3602
|
+
for (const reviver of this.#classRevivers) {
|
|
3603
|
+
const out = reviver(seen, inner, node);
|
|
3604
|
+
if (out !== void 0) return await out;
|
|
3605
|
+
}
|
|
3606
|
+
return node;
|
|
3607
|
+
};
|
|
3608
|
+
return inner;
|
|
3609
|
+
};
|
|
3610
|
+
#classRevivers = [
|
|
3611
|
+
classReviver(isInstanceOf(VocabHolder), ({ kind, jsonLd }) => kind === "link" ? _fedify_vocab.Link.fromJsonLd(jsonLd, this.options) : _fedify_vocab.Object.fromJsonLd(jsonLd, this.options), () => {}),
|
|
3612
|
+
classReviver(isInstanceOf(Array), () => [], async (revive, node, arr) => {
|
|
3613
|
+
for (const item of await Array.fromAsync(node, revive)) arr.push(item);
|
|
3614
|
+
}),
|
|
3615
|
+
classReviver(isInstanceOf(Map), () => /* @__PURE__ */ new Map(), async (revive, node, map) => {
|
|
3616
|
+
for (const [k, v] of node) map.set(await revive(k), await revive(v));
|
|
3617
|
+
}),
|
|
3618
|
+
classReviver(isInstanceOf(Set), () => /* @__PURE__ */ new Set(), async (revive, node, set) => {
|
|
3619
|
+
for (const v of await Array.fromAsync(node, revive)) set.add(v);
|
|
3620
|
+
}),
|
|
3621
|
+
classReviver(isPlainObject, () => ({}), async (revive, node, obj) => {
|
|
3622
|
+
for (const [k, v] of globalThis.Object.entries(node)) obj[k] = await revive(v);
|
|
3623
|
+
})
|
|
3624
|
+
];
|
|
3625
|
+
};
|
|
3626
|
+
const isVocab = (value) => value instanceof _fedify_vocab.Object || value instanceof _fedify_vocab.Link;
|
|
3627
|
+
const isPlainObject = (value) => value === null || typeof value !== "object" ? false : isObjectPrototype(globalThis.Object.getPrototypeOf(value));
|
|
3628
|
+
const isObjectPrototype = (proto) => proto === null || proto === globalThis.Object.prototype;
|
|
3629
|
+
const isInstanceOf = (cls) => (v) => v instanceof cls;
|
|
3630
|
+
function getValueIfSchema(result) {
|
|
3631
|
+
assertSchema(result);
|
|
3632
|
+
return result.value;
|
|
3633
|
+
}
|
|
3634
|
+
function assertSchema(result) {
|
|
3635
|
+
if (result.issues && result.issues.length > 0) throw new TypeError(`Task data failed schema validation: ${JSON.stringify(result.issues)}`);
|
|
3636
|
+
}
|
|
3637
|
+
/**
|
|
3638
|
+
* A vocabulary object parked by the synchronous decode reviver, held until
|
|
3639
|
+
* the async revive pass can `fromJsonLd()` it back into an instance.
|
|
3640
|
+
*/
|
|
3641
|
+
var VocabHolder = class VocabHolder {
|
|
3642
|
+
kind;
|
|
3643
|
+
jsonLd;
|
|
3644
|
+
constructor(kind, jsonLd) {
|
|
3645
|
+
this.kind = kind;
|
|
3646
|
+
this.jsonLd = jsonLd;
|
|
3647
|
+
}
|
|
3648
|
+
static from = ({ kind, jsonLd }) => new VocabHolder(kind, jsonLd);
|
|
3649
|
+
};
|
|
3650
|
+
/**
|
|
3651
|
+
* Ties a container filter to its empty-shell `init` and child-filling `set`
|
|
3652
|
+
* through one type parameter—a correlation the heterogeneous reviver list
|
|
3653
|
+
* cannot carry, which previously forced `@ts-ignore` at the dispatch site.
|
|
3654
|
+
*/
|
|
3655
|
+
const classReviver = (filter, init, set) => (seen, revive, node) => {
|
|
3656
|
+
if (!filter(node)) return void 0;
|
|
3657
|
+
return (async () => {
|
|
3658
|
+
const out = await init(node);
|
|
3659
|
+
seen.set(node, out);
|
|
3660
|
+
await set(revive, node, out);
|
|
3661
|
+
return out;
|
|
3662
|
+
})();
|
|
3663
|
+
};
|
|
3664
|
+
//#endregion
|
|
3556
3665
|
//#region src/federation/webfinger.ts
|
|
3557
3666
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
3558
3667
|
"fedify",
|
|
@@ -3877,9 +3986,12 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
3877
3986
|
inboxQueue;
|
|
3878
3987
|
outboxQueue;
|
|
3879
3988
|
fanoutQueue;
|
|
3989
|
+
taskQueue;
|
|
3880
3990
|
inboxQueueStarted;
|
|
3881
3991
|
outboxQueueStarted;
|
|
3882
3992
|
fanoutQueueStarted;
|
|
3993
|
+
taskQueueStarted;
|
|
3994
|
+
startedTaskQueues;
|
|
3883
3995
|
manuallyStartQueue;
|
|
3884
3996
|
origin;
|
|
3885
3997
|
documentLoaderFactory;
|
|
@@ -3893,6 +4005,8 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
3893
4005
|
skipSignatureVerification;
|
|
3894
4006
|
outboxRetryPolicy;
|
|
3895
4007
|
inboxRetryPolicy;
|
|
4008
|
+
taskRetryPolicy;
|
|
4009
|
+
taskQueueResolution;
|
|
3896
4010
|
circuitBreaker;
|
|
3897
4011
|
activityTransformers;
|
|
3898
4012
|
_tracerProvider;
|
|
@@ -3938,14 +4052,17 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
3938
4052
|
this.inboxQueue = void 0;
|
|
3939
4053
|
this.outboxQueue = void 0;
|
|
3940
4054
|
this.fanoutQueue = void 0;
|
|
4055
|
+
this.taskQueue = void 0;
|
|
3941
4056
|
} else if ("enqueue" in options.queue && "listen" in options.queue) {
|
|
3942
4057
|
this.inboxQueue = options.queue;
|
|
3943
4058
|
this.outboxQueue = options.queue;
|
|
3944
4059
|
this.fanoutQueue = options.queue;
|
|
4060
|
+
this.taskQueue = void 0;
|
|
3945
4061
|
} else {
|
|
3946
4062
|
this.inboxQueue = options.queue.inbox;
|
|
3947
4063
|
this.outboxQueue = options.queue.outbox;
|
|
3948
4064
|
this.fanoutQueue = options.queue.fanout;
|
|
4065
|
+
this.taskQueue = options.queue.task;
|
|
3949
4066
|
}
|
|
3950
4067
|
if (options.circuitBreaker !== false && this.outboxQueue != null) {
|
|
3951
4068
|
this.circuitBreaker = new CircuitBreaker({
|
|
@@ -3969,6 +4086,8 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
3969
4086
|
this.inboxQueueStarted = false;
|
|
3970
4087
|
this.outboxQueueStarted = false;
|
|
3971
4088
|
this.fanoutQueueStarted = false;
|
|
4089
|
+
this.taskQueueStarted = false;
|
|
4090
|
+
this.startedTaskQueues = /* @__PURE__ */ new Set();
|
|
3972
4091
|
this.manuallyStartQueue = options.manuallyStartQueue ?? false;
|
|
3973
4092
|
if (options.origin != null) if (typeof options.origin === "string") {
|
|
3974
4093
|
if (!URL.canParse(options.origin) || !options.origin.match(/^https?:\/\//)) throw new TypeError(`Invalid origin: ${JSON.stringify(options.origin)}`);
|
|
@@ -4050,6 +4169,8 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4050
4169
|
this.inboxChallengePolicy = options.inboxChallengePolicy;
|
|
4051
4170
|
this.outboxRetryPolicy = options.outboxRetryPolicy ?? createExponentialBackoffPolicy();
|
|
4052
4171
|
this.inboxRetryPolicy = options.inboxRetryPolicy ?? createExponentialBackoffPolicy();
|
|
4172
|
+
this.taskRetryPolicy = options.taskRetryPolicy ?? createExponentialBackoffPolicy();
|
|
4173
|
+
this.taskQueueResolution = options.taskQueueResolution ?? "fallback";
|
|
4053
4174
|
this.activityTransformers = options.activityTransformers ?? require_transformers.getDefaultActivityTransformers();
|
|
4054
4175
|
this._tracerProvider = options.tracerProvider;
|
|
4055
4176
|
if (benchmarkMode) {
|
|
@@ -4098,8 +4219,14 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4098
4219
|
_getTracer() {
|
|
4099
4220
|
return this.tracerProvider.getTracer(require_http.name, require_http.version);
|
|
4100
4221
|
}
|
|
4222
|
+
resolveTaskQueue(taskName) {
|
|
4223
|
+
const resolved = this.taskDefinitions.get(taskName)?.queue ?? this.taskQueue;
|
|
4224
|
+
if (resolved != null) return resolved;
|
|
4225
|
+
return this.taskQueueResolution === "strict" ? void 0 : this.outboxQueue;
|
|
4226
|
+
}
|
|
4101
4227
|
async _startQueueInternal(ctxData, signal, queue) {
|
|
4102
|
-
|
|
4228
|
+
const hasDedicatedTaskQueue = [...this.taskDefinitions.values()].some((def) => def.queue != null);
|
|
4229
|
+
if (this.inboxQueue == null && this.outboxQueue == null && this.fanoutQueue == null && this.taskQueue == null && !hasDedicatedTaskQueue) return;
|
|
4103
4230
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
4104
4231
|
"fedify",
|
|
4105
4232
|
"federation",
|
|
@@ -4121,6 +4248,26 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4121
4248
|
this.fanoutQueueStarted = true;
|
|
4122
4249
|
promises.push(this.fanoutQueue.listen((msg) => this.processQueuedTask(ctxData, msg), { signal }));
|
|
4123
4250
|
}
|
|
4251
|
+
if (this.taskQueue != null && this.taskQueue !== this.inboxQueue && this.taskQueue !== this.outboxQueue && this.taskQueue !== this.fanoutQueue && (queue == null || queue === "task") && !this.taskQueueStarted) {
|
|
4252
|
+
logger.debug("Starting a task worker.");
|
|
4253
|
+
this.taskQueueStarted = true;
|
|
4254
|
+
promises.push(this.taskQueue.listen((msg) => this.processQueuedTask(ctxData, msg), { signal }));
|
|
4255
|
+
}
|
|
4256
|
+
if (queue == null || queue === "task") {
|
|
4257
|
+
const standardQueues = new Set([
|
|
4258
|
+
this.inboxQueue,
|
|
4259
|
+
this.outboxQueue,
|
|
4260
|
+
this.fanoutQueue,
|
|
4261
|
+
this.taskQueue
|
|
4262
|
+
].filter((q) => q != null));
|
|
4263
|
+
for (const def of this.taskDefinitions.values()) {
|
|
4264
|
+
const taskQueue = def.queue;
|
|
4265
|
+
if (taskQueue == null || standardQueues.has(taskQueue) || this.startedTaskQueues.has(taskQueue)) continue;
|
|
4266
|
+
logger.debug("Starting a worker for a dedicated per-task queue.");
|
|
4267
|
+
this.startedTaskQueues.add(taskQueue);
|
|
4268
|
+
promises.push(taskQueue.listen((msg) => this.processQueuedTask(ctxData, msg), { signal }));
|
|
4269
|
+
}
|
|
4270
|
+
}
|
|
4124
4271
|
await Promise.all(promises);
|
|
4125
4272
|
}
|
|
4126
4273
|
processQueuedTask(contextData, message) {
|
|
@@ -4242,7 +4389,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4242
4389
|
}
|
|
4243
4390
|
});
|
|
4244
4391
|
});
|
|
4245
|
-
}
|
|
4392
|
+
} else if (message.type === "task") await this.#listenTaskMessage(contextData, message);
|
|
4246
4393
|
});
|
|
4247
4394
|
}
|
|
4248
4395
|
async #listenFanoutMessage(data, message) {
|
|
@@ -4806,6 +4953,77 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4806
4953
|
listenerSpan.end();
|
|
4807
4954
|
});
|
|
4808
4955
|
}
|
|
4956
|
+
async #listenTaskMessage(contextData, message) {
|
|
4957
|
+
const logger = (0, _logtape_logtape.getLogger)([
|
|
4958
|
+
"fedify",
|
|
4959
|
+
"federation",
|
|
4960
|
+
"task"
|
|
4961
|
+
]);
|
|
4962
|
+
const def = this.taskDefinitions.get(message.taskName);
|
|
4963
|
+
if (def == null) {
|
|
4964
|
+
logger.warn("Received a custom task {taskName} with no registered handler; dropping.", { taskName: message.taskName });
|
|
4965
|
+
return;
|
|
4966
|
+
}
|
|
4967
|
+
const context = this.#createContext(new URL(message.baseUrl), contextData);
|
|
4968
|
+
let data;
|
|
4969
|
+
try {
|
|
4970
|
+
data = await context.codec.decode(def.schema, message.data);
|
|
4971
|
+
} catch (error) {
|
|
4972
|
+
logger.error("Custom task {taskName} payload could not be decoded or validated; dropping:\n{error}", {
|
|
4973
|
+
taskName: message.taskName,
|
|
4974
|
+
error
|
|
4975
|
+
});
|
|
4976
|
+
return;
|
|
4977
|
+
}
|
|
4978
|
+
try {
|
|
4979
|
+
await def.handler(context, data);
|
|
4980
|
+
} catch (error) {
|
|
4981
|
+
if (def.onError != null) try {
|
|
4982
|
+
await def.onError(context, error, data);
|
|
4983
|
+
} catch (onErrorError) {
|
|
4984
|
+
logger.error("onError for custom task {taskName} threw:\n{error}", {
|
|
4985
|
+
taskName: message.taskName,
|
|
4986
|
+
error: onErrorError
|
|
4987
|
+
});
|
|
4988
|
+
}
|
|
4989
|
+
const queue = this.resolveTaskQueue(def.name);
|
|
4990
|
+
if (queue?.nativeRetrial) throw error;
|
|
4991
|
+
const retryPolicy = def.retryPolicy ?? this.taskRetryPolicy;
|
|
4992
|
+
let elapsedTime = Temporal.Duration.from({ seconds: 0 });
|
|
4993
|
+
try {
|
|
4994
|
+
elapsedTime = Temporal.Instant.from(message.started).until(Temporal.Now.instant());
|
|
4995
|
+
} catch (parseError) {
|
|
4996
|
+
logger.error("Custom task {taskName} has an unparsable started time {started}; treating elapsedTime as zero:\n{error}", {
|
|
4997
|
+
taskName: message.taskName,
|
|
4998
|
+
started: message.started,
|
|
4999
|
+
error: parseError
|
|
5000
|
+
});
|
|
5001
|
+
}
|
|
5002
|
+
const delay = retryPolicy({
|
|
5003
|
+
elapsedTime,
|
|
5004
|
+
attempts: message.attempt
|
|
5005
|
+
});
|
|
5006
|
+
if (delay != null && queue != null) {
|
|
5007
|
+
logger.error("Custom task {taskName} failed (attempt #{attempt}); retry...:\n{error}", {
|
|
5008
|
+
taskName: message.taskName,
|
|
5009
|
+
attempt: message.attempt,
|
|
5010
|
+
error
|
|
5011
|
+
});
|
|
5012
|
+
const retryMessage = {
|
|
5013
|
+
...message,
|
|
5014
|
+
attempt: message.attempt + 1
|
|
5015
|
+
};
|
|
5016
|
+
await queue.enqueue(retryMessage, {
|
|
5017
|
+
delay: clampNegativeDelay(delay),
|
|
5018
|
+
orderingKey: message.orderingKey
|
|
5019
|
+
});
|
|
5020
|
+
} else logger.error("Custom task {taskName} failed after {attempt} attempts; giving up:\n{error}", {
|
|
5021
|
+
taskName: message.taskName,
|
|
5022
|
+
attempt: message.attempt,
|
|
5023
|
+
error
|
|
5024
|
+
});
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
4809
5027
|
startQueue(contextData, options = {}) {
|
|
4810
5028
|
return this._startQueueInternal(contextData, options.signal, options.queue);
|
|
4811
5029
|
}
|
|
@@ -5382,6 +5600,7 @@ var ContextImpl = class ContextImpl {
|
|
|
5382
5600
|
documentLoader;
|
|
5383
5601
|
contextLoader;
|
|
5384
5602
|
invokedFromActorKeyPairsDispatcher;
|
|
5603
|
+
#codec;
|
|
5385
5604
|
constructor({ url, federation, data, documentLoader, contextLoader, invokedFromActorKeyPairsDispatcher }) {
|
|
5386
5605
|
this.url = url;
|
|
5387
5606
|
this.federation = federation;
|
|
@@ -5390,6 +5609,15 @@ var ContextImpl = class ContextImpl {
|
|
|
5390
5609
|
this.contextLoader = contextLoader;
|
|
5391
5610
|
this.invokedFromActorKeyPairsDispatcher = invokedFromActorKeyPairsDispatcher;
|
|
5392
5611
|
}
|
|
5612
|
+
/**
|
|
5613
|
+
* A {@link TaskCodec} bound to this context's loaders, used to encode
|
|
5614
|
+
* and decode custom task payloads. Lazily created and cached so a context
|
|
5615
|
+
* that never enqueues or dispatches a task pays nothing.
|
|
5616
|
+
* @internal
|
|
5617
|
+
*/
|
|
5618
|
+
get codec() {
|
|
5619
|
+
return this.#codec ??= new TaskCodec(this);
|
|
5620
|
+
}
|
|
5393
5621
|
clone(data) {
|
|
5394
5622
|
return new ContextImpl({
|
|
5395
5623
|
url: this.url,
|
|
@@ -5708,6 +5936,45 @@ var ContextImpl = class ContextImpl {
|
|
|
5708
5936
|
allowPrivateAddress: this.federation.allowPrivateAddress
|
|
5709
5937
|
});
|
|
5710
5938
|
}
|
|
5939
|
+
async enqueueTask(task, data, options = {}) {
|
|
5940
|
+
await this.#enqueueTasks(task, [data], options);
|
|
5941
|
+
}
|
|
5942
|
+
async enqueueTaskMany(task, payloads, options = {}) {
|
|
5943
|
+
await this.#enqueueTasks(task, payloads, options);
|
|
5944
|
+
}
|
|
5945
|
+
async #enqueueTasks(task, items, options) {
|
|
5946
|
+
const def = this.federation.taskDefinitions.get(task.name);
|
|
5947
|
+
if (def == null || def.handle !== task) throw new TypeError(`Task ${JSON.stringify(task.name)} is not defined on this federation; pass a handle returned by its defineTask().`);
|
|
5948
|
+
const queue = this.federation.resolveTaskQueue(task.name);
|
|
5949
|
+
if (queue == null) throw new TypeError("No message queue is configured for tasks; pass `queue` to createFederation() or to defineTask().");
|
|
5950
|
+
if (items.length < 1) return;
|
|
5951
|
+
const delay = options.delay == null ? void 0 : Temporal.Duration.from(options.delay);
|
|
5952
|
+
const messages = await Promise.all(items.map(this.#encodeTaskMessage(task, options)));
|
|
5953
|
+
if (!this.federation.manuallyStartQueue) this.federation._startQueueInternal(this.data);
|
|
5954
|
+
const enqueueOptions = {
|
|
5955
|
+
delay,
|
|
5956
|
+
orderingKey: options.orderingKey
|
|
5957
|
+
};
|
|
5958
|
+
if (messages.length === 1) await queue.enqueue(messages[0], enqueueOptions);
|
|
5959
|
+
else if (queue.enqueueMany != null) await queue.enqueueMany(messages, enqueueOptions);
|
|
5960
|
+
else await Promise.all(messages.map((m) => queue.enqueue(m, enqueueOptions)));
|
|
5961
|
+
}
|
|
5962
|
+
#encodeTaskMessage = (task, options) => async (data) => {
|
|
5963
|
+
const encoded = await this.codec.encode(task.schema, data);
|
|
5964
|
+
const carrier = {};
|
|
5965
|
+
_opentelemetry_api.propagation.inject(_opentelemetry_api.context.active(), carrier);
|
|
5966
|
+
return {
|
|
5967
|
+
type: "task",
|
|
5968
|
+
id: crypto.randomUUID(),
|
|
5969
|
+
baseUrl: this.origin,
|
|
5970
|
+
taskName: task.name,
|
|
5971
|
+
data: encoded,
|
|
5972
|
+
started: Temporal.Now.instant().toString(),
|
|
5973
|
+
attempt: 0,
|
|
5974
|
+
orderingKey: options.orderingKey,
|
|
5975
|
+
traceContext: carrier
|
|
5976
|
+
};
|
|
5977
|
+
};
|
|
5711
5978
|
sendActivity(sender, recipients, activity, options = {}) {
|
|
5712
5979
|
return this.tracerProvider.getTracer(require_http.name, require_http.version).startActiveSpan(this.federation.outboxQueue == null || options.immediate ? "activitypub.outbox" : "activitypub.fanout", {
|
|
5713
5980
|
kind: this.federation.outboxQueue == null || options.immediate ? _opentelemetry_api.SpanKind.CLIENT : _opentelemetry_api.SpanKind.PRODUCER,
|