@fedify/fedify 2.4.0-dev.1504 → 2.4.0-dev.1528
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 +10 -7
- package/dist/{assert-DvVHoIZ0.mjs → assert-OguE97r2.mjs} +1 -1
- package/dist/{assert_instance_of-CxoC8bVq.mjs → assert_instance_of-DBC5X09g.mjs} +1 -1
- package/dist/{assert_not_equals-C8vcTTA3.mjs → assert_not_equals-DkVK8oqV.mjs} +1 -1
- package/dist/{assert_rejects-DAdaaIWM.mjs → assert_rejects-DN60FHPX.mjs} +2 -2
- package/dist/{assert_strict_equals-BzqyKuYL.mjs → assert_strict_equals-XEgZAlrj.mjs} +1 -1
- package/dist/{assert_throws-zN79lgIC.mjs → assert_throws-BOkhLGYc.mjs} +1 -1
- package/dist/{builder-Ck0Fl_a5.mjs → builder-B6myMguW.mjs} +3 -23
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/outgoing-jsonld.test.mjs +3 -3
- package/dist/compat/public-audience.test.mjs +3 -3
- package/dist/compat/transformers.test.mjs +5 -5
- package/dist/{context-CYF8X0ft.d.cts → context-BBVLF7lx.d.cts} +272 -543
- package/dist/{context-BF5B4ZDk.d.ts → context-BU6jSQdo.d.ts} +272 -542
- package/dist/{context-BBRTgkrs.mjs → context-DVoTs_wM.mjs} +2 -8
- package/dist/{deno-D3gb4X9v.mjs → deno-CVNsRJe9.mjs} +1 -1
- package/dist/{docloader-DUKu0GzG.mjs → docloader-CB8AGpw3.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 +19 -200
- package/dist/federation/mod.cjs +259 -3
- package/dist/federation/mod.d.cts +4 -4
- package/dist/federation/mod.d.ts +4 -4
- package/dist/federation/mod.js +257 -1
- package/dist/federation/mq.test.mjs +261 -103
- package/dist/federation/negotiation.test.mjs +4 -4
- package/dist/federation/retry.test.mjs +3 -3
- package/dist/federation/router.test.mjs +5 -5
- package/dist/federation/send.test.mjs +10 -10
- package/dist/federation/temporal.test.mjs +4 -4
- package/dist/federation/webfinger.test.mjs +6 -6
- package/dist/{http-CrZzUAtV.js → http-B0-9u_1F.js} +17 -7
- package/dist/{http-D9p2_d2X.cjs → http-EkV7OslM.cjs} +17 -7
- package/dist/{http-BNFQkiEZ.mjs → http-L7wqkbZR.mjs} +4 -4
- package/dist/{key-BM_SRwBV.mjs → key-Ct8zNgGk.mjs} +18 -8
- package/dist/{kv-cache-BE1QIPdL.mjs → kv-cache-CUT9OGpX.mjs} +1 -1
- package/dist/{kv-cache-Cna211RR.cjs → kv-cache-CbB50co6.cjs} +1 -1
- package/dist/{kv-cache-Ch_Zv5Yq.js → kv-cache-Dh8ZoQfC.js} +1 -1
- package/dist/{ld-ubkf03eb.mjs → ld-D6I7H18J.mjs} +3 -3
- package/dist/{metrics-BXR36qzu.mjs → metrics-DvClPYaE.mjs} +1 -1
- package/dist/{middleware-DUNeIkXN.cjs → middleware-B0ujKP-0.cjs} +51 -694
- package/dist/{middleware-pzCP79He.mjs → middleware-CGqUj4XY.mjs} +1 -1
- package/dist/{middleware-lB4vtShP.js → middleware-DX9Fd37u.js} +52 -683
- package/dist/{middleware-iP9VRMus.mjs → middleware-RxFBNV2C.mjs} +67 -310
- package/dist/{mod-BHEB3xhE.d.cts → mod-C0F6kvgS.d.cts} +1 -1
- package/dist/{mod-4WGAuR6X.d.ts → mod-vPYVoa5n.d.ts} +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-DXMt_qCY.d.cts → mq-D-nlpY04.d.ts} +0 -34
- package/dist/{mq-B5_hohs7.d.ts → mq-D8uSFzxe.d.cts} +0 -34
- 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-BNL8AC14.mjs → outgoing-jsonld-L_DbOaFe.mjs} +1 -1
- package/dist/{owner-Bf1KkncT.mjs → owner-BqPUA8Z6.mjs} +2 -2
- package/dist/{proof-Q781Ks5q.mjs → proof-BJKoRFBc.mjs} +5 -5
- package/dist/{proof-BMcsi8q_.js → proof-DCCmmEuJ.js} +1 -1
- package/dist/{proof-yeAU1MyT.cjs → proof-eZme7HYR.cjs} +1 -1
- package/dist/{send-DFV0S_n9.mjs → send-DVtEjoJ6.mjs} +3 -3
- package/dist/sig/accept.test.mjs +1 -1
- package/dist/sig/http.test.mjs +8 -8
- package/dist/sig/key.test.mjs +56 -6
- package/dist/sig/ld.test.mjs +7 -7
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +6 -6
- package/dist/sig/proof.test.mjs +8 -8
- package/dist/{std__assert-Dh6uLf_q.mjs → std__assert-BBjXFNOb.mjs} +4 -4
- package/dist/{temporal-CyOjsqPj.mjs → temporal-B06FcmVK.mjs} +1 -1
- package/dist/testing/mod.d.mts +8 -1037
- package/dist/testing/mod.mjs +2 -3
- package/dist/utils/docloader.test.mjs +6 -6
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +7 -12
- package/dist/codec-CmlPqjNX.mjs +0 -103
- package/dist/federation/tasks/codec.test.d.mts +0 -2
- package/dist/federation/tasks/codec.test.mjs +0 -292
- package/dist/federation/tasks/enqueue.test.d.mts +0 -2
- package/dist/federation/tasks/enqueue.test.mjs +0 -992
- package/dist/federation/tasks/tasks.test.d.mts +0 -2
- package/dist/federation/tasks/tasks.test.mjs +0 -555
- package/dist/mq-03vS-C9P.mjs +0 -276
- package/dist/testing-Dy-WhqpN.mjs +0 -77
- /package/dist/{accept-CgDcxvjV.mjs → accept-CPkZzmGN.mjs} +0 -0
- /package/dist/{activity-listener-BeTGV3wc.mjs → activity-listener-tztVvlNb.mjs} +0 -0
- /package/dist/{assert_equals-PBVKNMJd.mjs → assert_equals-C-ZRDbaf.mjs} +0 -0
- /package/dist/{circuit-breaker-hJBB6jwA.mjs → circuit-breaker-CSWsyoef.mjs} +0 -0
- /package/dist/{client-B_A6mfn3.mjs → client-ByXmQhYD.mjs} +0 -0
- /package/dist/{collection-Dp5ky45w.mjs → collection-Cc3DVAhE.mjs} +0 -0
- /package/dist/{keycache-CaOR6NYg.mjs → keycache-BeU0LCII.mjs} +0 -0
- /package/dist/{keys-CSYsOMFG.mjs → keys-DGu1NFwu.mjs} +0 -0
- /package/dist/{kv-CIvR3Non.mjs → kv-rV3vodCc.mjs} +0 -0
- /package/dist/{negotiation-m_L2nyS3.mjs → negotiation-DDstyBvc.mjs} +0 -0
- /package/dist/{public-audience-c9zmYKgA.mjs → public-audience-Cvbr2Gzt.mjs} +0 -0
- /package/dist/{retry-DI4O-zYl.mjs → retry-CXg_MBI-.mjs} +0 -0
- /package/dist/{types-BFowWFTT.mjs → types-J53Kw7so.mjs} +0 -0
|
@@ -2,10 +2,10 @@ import { Temporal } from "@js-temporal/polyfill";
|
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
3
|
import { t as __exportAll } from "./chunk-CRNNMoPX.js";
|
|
4
4
|
import { r as getDefaultActivityTransformers } from "./transformers-BGMIq1cs.js";
|
|
5
|
-
import { A as formatAcceptSignature, D as recordOutboxEnqueue, E as recordOutboxActivity, F as version, O as recordWebFingerHandle, P as name, S as recordCollectionTotalItems, T as recordInboxActivity, a as verifyRequestDetailed, b as recordCollectionPageItems, d as validateCryptoKey, f as getDurationMs, g as isAbortError, h as instrumentDocumentLoader, i as verifyRequest, k as registerQueueDepthGauge, m as getRemoteHost, n as parseRfc9421SignatureInput, o as exportJwk, p as getFederationMetrics, t as doubleKnock, u as importJwk, v as recordCircuitBreakerStateChange, w as recordFanoutRecipients, x as recordCollectionRequest, y as recordCollectionDispatchDuration } from "./http-
|
|
6
|
-
import { _ as hasSignatureLike, b as signJsonLd, c as getKeyOwner, f as compactJsonLd, g as hasSignature, h as getNormalizationContextLoader, i as verifyObject, l as InvalidContextReferenceError, m as detachSignature, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, r as signObject, s as doesActorOwnKey, u as assertSafeJsonLd, v as isClearlyMalformedContextReference, w as wrapContextLoaderForJsonLd, x as verifyCompactJsonLd, y as isInvalidUrlTypeError } from "./proof-
|
|
5
|
+
import { A as formatAcceptSignature, D as recordOutboxEnqueue, E as recordOutboxActivity, F as version, O as recordWebFingerHandle, P as name, S as recordCollectionTotalItems, T as recordInboxActivity, a as verifyRequestDetailed, b as recordCollectionPageItems, d as validateCryptoKey, f as getDurationMs, g as isAbortError, h as instrumentDocumentLoader, i as verifyRequest, k as registerQueueDepthGauge, m as getRemoteHost, n as parseRfc9421SignatureInput, o as exportJwk, p as getFederationMetrics, t as doubleKnock, u as importJwk, v as recordCircuitBreakerStateChange, w as recordFanoutRecipients, x as recordCollectionRequest, y as recordCollectionDispatchDuration } from "./http-B0-9u_1F.js";
|
|
6
|
+
import { _ as hasSignatureLike, b as signJsonLd, c as getKeyOwner, f as compactJsonLd, g as hasSignature, h as getNormalizationContextLoader, i as verifyObject, l as InvalidContextReferenceError, m as detachSignature, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, r as signObject, s as doesActorOwnKey, u as assertSafeJsonLd, v as isClearlyMalformedContextReference, w as wrapContextLoaderForJsonLd, x as verifyCompactJsonLd, y as isInvalidUrlTypeError } from "./proof-DCCmmEuJ.js";
|
|
7
7
|
import { n as getNodeInfo, t as nodeInfoToJson } from "./types-CAY3OdLq.js";
|
|
8
|
-
import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-
|
|
8
|
+
import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-Dh8ZoQfC.js";
|
|
9
9
|
import { getLogger, withContext } from "@logtape/logtape";
|
|
10
10
|
import { Router, RouterError, assertPath } from "@fedify/uri-template";
|
|
11
11
|
import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
|
|
@@ -17,7 +17,6 @@ import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_
|
|
|
17
17
|
import jsonld from "@fedify/vocab-runtime/jsonld";
|
|
18
18
|
import { lookupWebFinger } from "@fedify/webfinger";
|
|
19
19
|
import { DataPointType, MeterProvider as MeterProvider$1, MetricReader } from "@opentelemetry/sdk-metrics";
|
|
20
|
-
import { parse, stringifyAsync } from "devalue";
|
|
21
20
|
import { domainToASCII } from "node:url";
|
|
22
21
|
//#region src/federation/activity-listener.ts
|
|
23
22
|
var ActivityListenerSet = class {
|
|
@@ -108,7 +107,6 @@ var FederationBuilderImpl = class {
|
|
|
108
107
|
idempotencyStrategy;
|
|
109
108
|
collectionTypeIds;
|
|
110
109
|
collectionCallbacks;
|
|
111
|
-
taskDefinitions;
|
|
112
110
|
/**
|
|
113
111
|
* Symbol registry for unique identification of unnamed symbols.
|
|
114
112
|
*/
|
|
@@ -119,7 +117,6 @@ var FederationBuilderImpl = class {
|
|
|
119
117
|
this.objectTypeIds = {};
|
|
120
118
|
this.collectionCallbacks = {};
|
|
121
119
|
this.collectionTypeIds = {};
|
|
122
|
-
this.taskDefinitions = /* @__PURE__ */ new Map();
|
|
123
120
|
}
|
|
124
121
|
/**
|
|
125
122
|
* Builds the federation object.
|
|
@@ -158,7 +155,6 @@ var FederationBuilderImpl = class {
|
|
|
158
155
|
f.unverifiedActivityHandler = this.unverifiedActivityHandler;
|
|
159
156
|
f.outboxPermanentFailureHandler = this.outboxPermanentFailureHandler;
|
|
160
157
|
f.idempotencyStrategy = this.idempotencyStrategy;
|
|
161
|
-
f.taskDefinitions = new Map(this.taskDefinitions);
|
|
162
158
|
return f;
|
|
163
159
|
}
|
|
164
160
|
_getTracer() {
|
|
@@ -291,23 +287,6 @@ var FederationBuilderImpl = class {
|
|
|
291
287
|
setWebFingerLinksDispatcher(dispatcher) {
|
|
292
288
|
this.webFingerLinksDispatcher = dispatcher;
|
|
293
289
|
}
|
|
294
|
-
defineTask(name, options) {
|
|
295
|
-
if (this.taskDefinitions.has(name)) throw new TypeError(`Task ${JSON.stringify(name)} is already defined.`);
|
|
296
|
-
const handle = {
|
|
297
|
-
name,
|
|
298
|
-
schema: options.schema
|
|
299
|
-
};
|
|
300
|
-
this.taskDefinitions.set(name, {
|
|
301
|
-
name,
|
|
302
|
-
schema: options.schema,
|
|
303
|
-
handle,
|
|
304
|
-
handler: options.handler,
|
|
305
|
-
onError: options.onError,
|
|
306
|
-
retryPolicy: options.retryPolicy,
|
|
307
|
-
queue: options.queue
|
|
308
|
-
});
|
|
309
|
-
return handle;
|
|
310
|
-
}
|
|
311
290
|
/**
|
|
312
291
|
* The RFC 6570 template-literal `path` overloads were removed for
|
|
313
292
|
* type-checking efficiency, so the URI variable types can no longer be
|
|
@@ -1413,7 +1392,7 @@ function acceptsJsonLd(request) {
|
|
|
1413
1392
|
}
|
|
1414
1393
|
//#endregion
|
|
1415
1394
|
//#region src/federation/temporal.ts
|
|
1416
|
-
function isPlainObject
|
|
1395
|
+
function isPlainObject(value) {
|
|
1417
1396
|
return typeof value === "object" && value != null && !Array.isArray(value);
|
|
1418
1397
|
}
|
|
1419
1398
|
function normalizeDateTimeLiteral(value) {
|
|
@@ -1451,11 +1430,11 @@ const QUESTION_CLOSED_IRI = "https://www.w3.org/ns/activitystreams#closed";
|
|
|
1451
1430
|
const XSD_DATE_TIME_IRI = "http://www.w3.org/2001/XMLSchema#dateTime";
|
|
1452
1431
|
function hasMalformedExpandedDateTimeLiteral(value) {
|
|
1453
1432
|
if (Array.isArray(value)) return value.some(hasMalformedExpandedDateTimeLiteral);
|
|
1454
|
-
return isPlainObject
|
|
1433
|
+
return isPlainObject(value) && "@value" in value && isMalformedDateTimeLiteral(value["@value"]);
|
|
1455
1434
|
}
|
|
1456
1435
|
function hasMalformedExpandedQuestionClosedLiteral(value) {
|
|
1457
1436
|
if (Array.isArray(value)) return value.some(hasMalformedExpandedQuestionClosedLiteral);
|
|
1458
|
-
if (!isPlainObject
|
|
1437
|
+
if (!isPlainObject(value) || !("@value" in value)) return false;
|
|
1459
1438
|
const literal = value["@value"];
|
|
1460
1439
|
if (typeof literal === "boolean") return false;
|
|
1461
1440
|
if (typeof literal !== "string") return false;
|
|
@@ -1465,11 +1444,11 @@ function hasMalformedExpandedQuestionClosedLiteral(value) {
|
|
|
1465
1444
|
}
|
|
1466
1445
|
function hasMalformedExpandedDurationLiteral(value) {
|
|
1467
1446
|
if (Array.isArray(value)) return value.some(hasMalformedExpandedDurationLiteral);
|
|
1468
|
-
return isPlainObject
|
|
1447
|
+
return isPlainObject(value) && "@value" in value && isMalformedDurationLiteral(value["@value"]);
|
|
1469
1448
|
}
|
|
1470
1449
|
function hasMalformedKnownTemporalLiteralInternal(value, visited) {
|
|
1471
1450
|
if (Array.isArray(value)) return value.some((item) => hasMalformedKnownTemporalLiteralInternal(item, visited));
|
|
1472
|
-
if (!isPlainObject
|
|
1451
|
+
if (!isPlainObject(value)) return false;
|
|
1473
1452
|
if (visited.has(value)) return false;
|
|
1474
1453
|
visited.add(value);
|
|
1475
1454
|
if ("@value" in value) return false;
|
|
@@ -3573,529 +3552,6 @@ function createExponentialBackoffPolicy(options = {}) {
|
|
|
3573
3552
|
};
|
|
3574
3553
|
}
|
|
3575
3554
|
//#endregion
|
|
3576
|
-
//#region src/federation/tasks/codec.ts
|
|
3577
|
-
/**
|
|
3578
|
-
* Serializes and deserializes task payloads for the queue, preserving
|
|
3579
|
-
* `@fedify/vocab` objects across the wire by reducing them to JSON-LD and
|
|
3580
|
-
* rebuilding them on the worker with the bound {@link TaskCodecLoaders}.
|
|
3581
|
-
* @internal
|
|
3582
|
-
*/
|
|
3583
|
-
var TaskCodec = class TaskCodec {
|
|
3584
|
-
options;
|
|
3585
|
-
constructor(options) {
|
|
3586
|
-
this.options = options;
|
|
3587
|
-
}
|
|
3588
|
-
/** Serializes `data`, encoding any vocabulary object as its JSON-LD. */
|
|
3589
|
-
serialize = (data) => stringifyAsync(data, { Vocab: this.#stringifyVocab });
|
|
3590
|
-
/** Deserializes `raw`, rebuilding any encoded vocabulary object. */
|
|
3591
|
-
deserialize = (raw) => this.#revive(/* @__PURE__ */ new Map())(parse(raw, { Vocab: VocabHolder.from }));
|
|
3592
|
-
/** Validates `data` against `schema`, then serializes it. */
|
|
3593
|
-
encode = async (schema, data) => this.serialize(await TaskCodec.validate(schema, data));
|
|
3594
|
-
/** Deserializes `raw`, then validates the result against `schema`. */
|
|
3595
|
-
decode = async (schema, raw) => TaskCodec.validate(schema, await this.deserialize(raw));
|
|
3596
|
-
/** Validates `data` against `schema`, returning its parsed output. */
|
|
3597
|
-
static validate = async (schema, data) => getValueIfSchema(await schema["~standard"].validate(data));
|
|
3598
|
-
#stringifyVocab = (value) => isVocab(value) && this.#toWire(value);
|
|
3599
|
-
#toWire = async (value) => ({
|
|
3600
|
-
kind: value instanceof Link ? "link" : "object",
|
|
3601
|
-
jsonLd: await value.toJsonLd({
|
|
3602
|
-
format: "expand",
|
|
3603
|
-
...this.options
|
|
3604
|
-
})
|
|
3605
|
-
});
|
|
3606
|
-
#revive = (seen) => {
|
|
3607
|
-
const inner = async (node) => {
|
|
3608
|
-
if (node === null || typeof node !== "object") return node;
|
|
3609
|
-
if (seen.has(node)) return seen.get(node);
|
|
3610
|
-
for (const reviver of this.#classRevivers) {
|
|
3611
|
-
const out = reviver(seen, inner, node);
|
|
3612
|
-
if (out !== void 0) return await out;
|
|
3613
|
-
}
|
|
3614
|
-
return node;
|
|
3615
|
-
};
|
|
3616
|
-
return inner;
|
|
3617
|
-
};
|
|
3618
|
-
#classRevivers = [
|
|
3619
|
-
classReviver(isInstanceOf(VocabHolder), ({ kind, jsonLd }) => kind === "link" ? Link.fromJsonLd(jsonLd, this.options) : Object$1.fromJsonLd(jsonLd, this.options), () => {}),
|
|
3620
|
-
classReviver(isInstanceOf(Array), () => [], async (revive, node, arr) => {
|
|
3621
|
-
for (const item of await Array.fromAsync(node, revive)) arr.push(item);
|
|
3622
|
-
}),
|
|
3623
|
-
classReviver(isInstanceOf(Map), () => /* @__PURE__ */ new Map(), async (revive, node, map) => {
|
|
3624
|
-
for (const [k, v] of node) map.set(await revive(k), await revive(v));
|
|
3625
|
-
}),
|
|
3626
|
-
classReviver(isInstanceOf(Set), () => /* @__PURE__ */ new Set(), async (revive, node, set) => {
|
|
3627
|
-
for (const v of await Array.fromAsync(node, revive)) set.add(v);
|
|
3628
|
-
}),
|
|
3629
|
-
classReviver(isPlainObject, () => ({}), async (revive, node, obj) => {
|
|
3630
|
-
for (const [k, v] of globalThis.Object.entries(node)) obj[k] = await revive(v);
|
|
3631
|
-
})
|
|
3632
|
-
];
|
|
3633
|
-
};
|
|
3634
|
-
const isVocab = (value) => value instanceof Object$1 || value instanceof Link;
|
|
3635
|
-
const isPlainObject = (value) => value === null || typeof value !== "object" ? false : isObjectPrototype(globalThis.Object.getPrototypeOf(value));
|
|
3636
|
-
const isObjectPrototype = (proto) => proto === null || proto === globalThis.Object.prototype;
|
|
3637
|
-
const isInstanceOf = (cls) => (v) => v instanceof cls;
|
|
3638
|
-
function getValueIfSchema(result) {
|
|
3639
|
-
assertSchema(result);
|
|
3640
|
-
return result.value;
|
|
3641
|
-
}
|
|
3642
|
-
function assertSchema(result) {
|
|
3643
|
-
if (result.issues && result.issues.length > 0) throw new TypeError(`Task data failed schema validation: ${JSON.stringify(result.issues)}`);
|
|
3644
|
-
}
|
|
3645
|
-
/**
|
|
3646
|
-
* A vocabulary object parked by the synchronous decode reviver, held until
|
|
3647
|
-
* the async revive pass can `fromJsonLd()` it back into an instance.
|
|
3648
|
-
*/
|
|
3649
|
-
var VocabHolder = class VocabHolder {
|
|
3650
|
-
kind;
|
|
3651
|
-
jsonLd;
|
|
3652
|
-
constructor(kind, jsonLd) {
|
|
3653
|
-
this.kind = kind;
|
|
3654
|
-
this.jsonLd = jsonLd;
|
|
3655
|
-
}
|
|
3656
|
-
static from = ({ kind, jsonLd }) => new VocabHolder(kind, jsonLd);
|
|
3657
|
-
};
|
|
3658
|
-
/**
|
|
3659
|
-
* Ties a container filter to its empty-shell `init` and child-filling `set`
|
|
3660
|
-
* through one type parameter—a correlation the heterogeneous reviver list
|
|
3661
|
-
* cannot carry, which previously forced `@ts-ignore` at the dispatch site.
|
|
3662
|
-
*/
|
|
3663
|
-
const classReviver = (filter, init, set) => (seen, revive, node) => {
|
|
3664
|
-
if (!filter(node)) return void 0;
|
|
3665
|
-
return (async () => {
|
|
3666
|
-
const out = await init(node);
|
|
3667
|
-
seen.set(node, out);
|
|
3668
|
-
await set(revive, node, out);
|
|
3669
|
-
return out;
|
|
3670
|
-
})();
|
|
3671
|
-
};
|
|
3672
|
-
//#endregion
|
|
3673
|
-
//#region src/federation/mq.ts
|
|
3674
|
-
/**
|
|
3675
|
-
* A message queue that processes messages in the same process.
|
|
3676
|
-
* Do not use this in production as it does neither persist messages nor
|
|
3677
|
-
* distribute them across multiple processes.
|
|
3678
|
-
*
|
|
3679
|
-
* @since 0.5.0
|
|
3680
|
-
*/
|
|
3681
|
-
var InProcessMessageQueue = class {
|
|
3682
|
-
#messages;
|
|
3683
|
-
#monitors;
|
|
3684
|
-
#pollIntervalMs;
|
|
3685
|
-
#delayedMessages;
|
|
3686
|
-
/**
|
|
3687
|
-
* Tracks which ordering keys are currently being processed to ensure
|
|
3688
|
-
* sequential processing for messages with the same key.
|
|
3689
|
-
*/
|
|
3690
|
-
#processingKeys;
|
|
3691
|
-
/**
|
|
3692
|
-
* In-process message queue does not provide native retry mechanisms.
|
|
3693
|
-
* @since 1.7.0
|
|
3694
|
-
*/
|
|
3695
|
-
nativeRetrial = false;
|
|
3696
|
-
/**
|
|
3697
|
-
* In-process message queue does not deduplicate messages natively.
|
|
3698
|
-
* @since 2.4.0
|
|
3699
|
-
*/
|
|
3700
|
-
nativeDeduplication = false;
|
|
3701
|
-
/**
|
|
3702
|
-
* Constructs a new {@link InProcessMessageQueue} with the given options.
|
|
3703
|
-
* @param options Additional options for the in-process message queue.
|
|
3704
|
-
*/
|
|
3705
|
-
constructor(options = {}) {
|
|
3706
|
-
this.#messages = [];
|
|
3707
|
-
this.#monitors = {};
|
|
3708
|
-
this.#pollIntervalMs = Temporal.Duration.from(options.pollInterval ?? { seconds: 5 }).total("millisecond");
|
|
3709
|
-
this.#delayedMessages = 0;
|
|
3710
|
-
this.#processingKeys = /* @__PURE__ */ new Set();
|
|
3711
|
-
}
|
|
3712
|
-
enqueue(message, options) {
|
|
3713
|
-
const delay = options?.delay == null ? 0 : Math.max(options.delay.total("millisecond"), 0);
|
|
3714
|
-
if (delay > 0) {
|
|
3715
|
-
this.#delayedMessages++;
|
|
3716
|
-
setTimeout(() => {
|
|
3717
|
-
this.#delayedMessages--;
|
|
3718
|
-
this.#enqueueReady(message, options);
|
|
3719
|
-
}, delay);
|
|
3720
|
-
return Promise.resolve();
|
|
3721
|
-
}
|
|
3722
|
-
this.#enqueueReady(message, options);
|
|
3723
|
-
return Promise.resolve();
|
|
3724
|
-
}
|
|
3725
|
-
#enqueueReady(message, options) {
|
|
3726
|
-
const orderingKey = options?.orderingKey ?? null;
|
|
3727
|
-
this.#messages.push({
|
|
3728
|
-
message,
|
|
3729
|
-
orderingKey
|
|
3730
|
-
});
|
|
3731
|
-
this.#notifyMonitors();
|
|
3732
|
-
}
|
|
3733
|
-
#notifyMonitors() {
|
|
3734
|
-
for (const monitorId in this.#monitors) this.#monitors[monitorId]();
|
|
3735
|
-
}
|
|
3736
|
-
enqueueMany(messages, options) {
|
|
3737
|
-
if (messages.length === 0) return Promise.resolve();
|
|
3738
|
-
const delay = options?.delay == null ? 0 : Math.max(options.delay.total("millisecond"), 0);
|
|
3739
|
-
if (delay > 0) {
|
|
3740
|
-
const delayedCount = messages.length;
|
|
3741
|
-
const deferredMessages = [...messages];
|
|
3742
|
-
this.#delayedMessages += delayedCount;
|
|
3743
|
-
setTimeout(() => {
|
|
3744
|
-
this.#delayedMessages -= delayedCount;
|
|
3745
|
-
this.#enqueueManyReady(deferredMessages, options);
|
|
3746
|
-
}, delay);
|
|
3747
|
-
return Promise.resolve();
|
|
3748
|
-
}
|
|
3749
|
-
this.#enqueueManyReady(messages, options);
|
|
3750
|
-
return Promise.resolve();
|
|
3751
|
-
}
|
|
3752
|
-
#enqueueManyReady(messages, options) {
|
|
3753
|
-
const orderingKey = options?.orderingKey ?? null;
|
|
3754
|
-
for (const message of messages) this.#messages.push({
|
|
3755
|
-
message,
|
|
3756
|
-
orderingKey
|
|
3757
|
-
});
|
|
3758
|
-
this.#notifyMonitors();
|
|
3759
|
-
}
|
|
3760
|
-
async listen(handler, options = {}) {
|
|
3761
|
-
const signal = options.signal;
|
|
3762
|
-
while (signal == null || !signal.aborted) {
|
|
3763
|
-
const idx = this.#messages.findIndex((m) => m.orderingKey == null || !this.#processingKeys.has(m.orderingKey));
|
|
3764
|
-
if (idx >= 0) {
|
|
3765
|
-
const { message, orderingKey } = this.#messages.splice(idx, 1)[0];
|
|
3766
|
-
if (orderingKey != null) this.#processingKeys.add(orderingKey);
|
|
3767
|
-
try {
|
|
3768
|
-
await handler(message);
|
|
3769
|
-
} finally {
|
|
3770
|
-
if (orderingKey != null) this.#processingKeys.delete(orderingKey);
|
|
3771
|
-
}
|
|
3772
|
-
} else if (this.#messages.length === 0) await this.#wait(this.#pollIntervalMs, signal);
|
|
3773
|
-
else await this.#wait(10, signal);
|
|
3774
|
-
}
|
|
3775
|
-
}
|
|
3776
|
-
getDepth() {
|
|
3777
|
-
const ready = this.#messages.length;
|
|
3778
|
-
const delayed = this.#delayedMessages;
|
|
3779
|
-
return Promise.resolve({
|
|
3780
|
-
queued: ready + delayed,
|
|
3781
|
-
ready,
|
|
3782
|
-
delayed
|
|
3783
|
-
});
|
|
3784
|
-
}
|
|
3785
|
-
#wait(ms, signal) {
|
|
3786
|
-
let timer = null;
|
|
3787
|
-
return Promise.any([new Promise((resolve) => {
|
|
3788
|
-
signal?.addEventListener("abort", () => {
|
|
3789
|
-
if (timer != null) clearTimeout(timer);
|
|
3790
|
-
resolve();
|
|
3791
|
-
}, { once: true });
|
|
3792
|
-
const monitorId = crypto.randomUUID();
|
|
3793
|
-
this.#monitors[monitorId] = () => {
|
|
3794
|
-
delete this.#monitors[monitorId];
|
|
3795
|
-
if (timer != null) clearTimeout(timer);
|
|
3796
|
-
resolve();
|
|
3797
|
-
};
|
|
3798
|
-
}), new Promise((resolve) => timer = setTimeout(resolve, ms))]);
|
|
3799
|
-
}
|
|
3800
|
-
};
|
|
3801
|
-
/**
|
|
3802
|
-
* A message queue that processes messages in parallel. It takes another
|
|
3803
|
-
* {@link MessageQueue}, and processes messages in parallel up to a certain
|
|
3804
|
-
* number of workers.
|
|
3805
|
-
*
|
|
3806
|
-
* Actually, it's rather a decorator than a queue itself.
|
|
3807
|
-
*
|
|
3808
|
-
* Note that the workers do not run in truly parallel, in the sense that they
|
|
3809
|
-
* are not running in separate threads or processes. They are running in the
|
|
3810
|
-
* same process, but are scheduled to run in parallel. Hence, this is useful
|
|
3811
|
-
* for I/O-bound tasks, but not for CPU-bound tasks, which is okay for Fedify's
|
|
3812
|
-
* workloads.
|
|
3813
|
-
*
|
|
3814
|
-
* When using `ParallelMessageQueue`, the ordering guarantee is preserved
|
|
3815
|
-
* *only if* the underlying queue implementation delivers messages in a wrapper
|
|
3816
|
-
* format that includes the `__fedify_ordering_key__` property. Currently,
|
|
3817
|
-
* only `DenoKvMessageQueue` and `WorkersMessageQueue` use this format.
|
|
3818
|
-
* For other queue implementations (e.g., `InProcessMessageQueue`,
|
|
3819
|
-
* `RedisMessageQueue`, `PostgresMessageQueue`, `SqliteMessageQueue`,
|
|
3820
|
-
* `AmqpMessageQueue`), the ordering key cannot be detected by
|
|
3821
|
-
* `ParallelMessageQueue`, so ordering guarantees are handled by those
|
|
3822
|
-
* implementations directly rather than at the `ParallelMessageQueue` level.
|
|
3823
|
-
*
|
|
3824
|
-
* Messages with the same ordering key will never be processed concurrently
|
|
3825
|
-
* by different workers, ensuring sequential processing within each key.
|
|
3826
|
-
* Messages with different ordering keys (or no ordering key) can still be
|
|
3827
|
-
* processed in parallel.
|
|
3828
|
-
*
|
|
3829
|
-
* @since 1.0.0
|
|
3830
|
-
*/
|
|
3831
|
-
var ParallelMessageQueue = class ParallelMessageQueue {
|
|
3832
|
-
queue;
|
|
3833
|
-
workers;
|
|
3834
|
-
/**
|
|
3835
|
-
* Inherits the native retry capability from the wrapped queue.
|
|
3836
|
-
* @since 1.7.0
|
|
3837
|
-
*/
|
|
3838
|
-
nativeRetrial;
|
|
3839
|
-
/**
|
|
3840
|
-
* Inherits the native deduplication capability from the wrapped queue.
|
|
3841
|
-
* @since 2.4.0
|
|
3842
|
-
*/
|
|
3843
|
-
nativeDeduplication;
|
|
3844
|
-
getDepth;
|
|
3845
|
-
/**
|
|
3846
|
-
* Tracks which ordering keys are currently being processed to ensure
|
|
3847
|
-
* sequential processing for messages with the same key.
|
|
3848
|
-
*/
|
|
3849
|
-
#processingKeys = /* @__PURE__ */ new Set();
|
|
3850
|
-
/**
|
|
3851
|
-
* Pending messages waiting for their ordering key to become available.
|
|
3852
|
-
*/
|
|
3853
|
-
#pendingMessages = [];
|
|
3854
|
-
/**
|
|
3855
|
-
* Constructs a new {@link ParallelMessageQueue} with the given queue and
|
|
3856
|
-
* number of workers.
|
|
3857
|
-
* @param queue The message queue to use under the hood. Note that
|
|
3858
|
-
* {@link ParallelMessageQueue} cannot be nested.
|
|
3859
|
-
* @param workers The number of workers to process messages in parallel.
|
|
3860
|
-
* @throws {TypeError} If the given queue is an instance of
|
|
3861
|
-
* {@link ParallelMessageQueue}.
|
|
3862
|
-
*/
|
|
3863
|
-
constructor(queue, workers) {
|
|
3864
|
-
if (queue instanceof ParallelMessageQueue) throw new TypeError("Cannot nest ParallelMessageQueue.");
|
|
3865
|
-
this.queue = queue;
|
|
3866
|
-
this.workers = workers;
|
|
3867
|
-
this.nativeRetrial = queue.nativeRetrial;
|
|
3868
|
-
this.nativeDeduplication = queue.nativeDeduplication;
|
|
3869
|
-
if (queue.getDepth != null) this.getDepth = () => queue.getDepth();
|
|
3870
|
-
}
|
|
3871
|
-
enqueue(message, options) {
|
|
3872
|
-
return this.queue.enqueue(message, options);
|
|
3873
|
-
}
|
|
3874
|
-
async enqueueMany(messages, options) {
|
|
3875
|
-
if (this.queue.enqueueMany == null) {
|
|
3876
|
-
if (messages.length === 1) {
|
|
3877
|
-
await this.queue.enqueue(messages[0], options);
|
|
3878
|
-
return;
|
|
3879
|
-
}
|
|
3880
|
-
if (options?.deduplicationKey != null) throw new TypeError("Cannot enqueue a batch with a deduplicationKey: the wrapped queue does not implement enqueueMany, so ParallelMessageQueue would have to fan out to individual enqueue() calls that cannot share one deduplicationKey atomically. Wrap a queue that implements enqueueMany instead.");
|
|
3881
|
-
const errors = (await Promise.allSettled(messages.map((message) => this.queue.enqueue(message, options)))).filter((r) => r.status === "rejected").map((r) => r.reason);
|
|
3882
|
-
if (errors.length > 1) throw new AggregateError(errors, "Failed to enqueue messages.");
|
|
3883
|
-
else if (errors.length === 1) throw errors[0];
|
|
3884
|
-
return;
|
|
3885
|
-
}
|
|
3886
|
-
await this.queue.enqueueMany(messages, options);
|
|
3887
|
-
}
|
|
3888
|
-
/**
|
|
3889
|
-
* Extracts ordering key from a message if present.
|
|
3890
|
-
*
|
|
3891
|
-
* This method only works for queue implementations that deliver messages
|
|
3892
|
-
* in the wrapper format with `__fedify_ordering_key__` property. Currently,
|
|
3893
|
-
* only `DenoKvMessageQueue` and `WorkersMessageQueue` use this format.
|
|
3894
|
-
*
|
|
3895
|
-
* For other queue implementations (`InProcessMessageQueue`,
|
|
3896
|
-
* `RedisMessageQueue`, `PostgresMessageQueue`, `SqliteMessageQueue`,
|
|
3897
|
-
* `AmqpMessageQueue`), messages are delivered as raw payloads without the
|
|
3898
|
-
* wrapper, so the ordering key cannot be detected here. Those
|
|
3899
|
-
* implementations handle ordering guarantees internally.
|
|
3900
|
-
*/
|
|
3901
|
-
#extractOrderingKey(message) {
|
|
3902
|
-
if (message != null && typeof message === "object") {
|
|
3903
|
-
if ("__fedify_ordering_key__" in message) return message.__fedify_ordering_key__;
|
|
3904
|
-
}
|
|
3905
|
-
}
|
|
3906
|
-
listen(handler, options = {}) {
|
|
3907
|
-
const workers = /* @__PURE__ */ new Map();
|
|
3908
|
-
return this.queue.listen(async (message) => {
|
|
3909
|
-
while (workers.size >= this.workers) {
|
|
3910
|
-
const consumedId = await Promise.any(workers.values());
|
|
3911
|
-
workers.delete(consumedId);
|
|
3912
|
-
}
|
|
3913
|
-
const workerId = crypto.randomUUID();
|
|
3914
|
-
const orderingKey = this.#extractOrderingKey(message);
|
|
3915
|
-
if (orderingKey != null && this.#processingKeys.has(orderingKey)) await new Promise((resolve) => {
|
|
3916
|
-
this.#pendingMessages.push({
|
|
3917
|
-
message,
|
|
3918
|
-
orderingKey,
|
|
3919
|
-
resolve
|
|
3920
|
-
});
|
|
3921
|
-
});
|
|
3922
|
-
if (orderingKey != null) this.#processingKeys.add(orderingKey);
|
|
3923
|
-
const promise = this.#work(workerId, handler, message, orderingKey);
|
|
3924
|
-
workers.set(workerId, promise);
|
|
3925
|
-
}, options);
|
|
3926
|
-
}
|
|
3927
|
-
async #work(workerId, handler, message, orderingKey) {
|
|
3928
|
-
await this.#sleep(0);
|
|
3929
|
-
try {
|
|
3930
|
-
await handler(message);
|
|
3931
|
-
} finally {
|
|
3932
|
-
if (orderingKey != null) {
|
|
3933
|
-
this.#processingKeys.delete(orderingKey);
|
|
3934
|
-
const pendingIdx = this.#pendingMessages.findIndex((p) => p.orderingKey === orderingKey);
|
|
3935
|
-
if (pendingIdx >= 0) this.#pendingMessages.splice(pendingIdx, 1)[0].resolve();
|
|
3936
|
-
}
|
|
3937
|
-
}
|
|
3938
|
-
return workerId;
|
|
3939
|
-
}
|
|
3940
|
-
#sleep(ms) {
|
|
3941
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3942
|
-
}
|
|
3943
|
-
};
|
|
3944
|
-
//#endregion
|
|
3945
|
-
//#region src/federation/tasks/enqueue.ts
|
|
3946
|
-
/**
|
|
3947
|
-
* The enqueue pipeline for custom background tasks. `ContextImpl.enqueueTask`
|
|
3948
|
-
* and `ContextImpl.enqueueTaskMany` delegate to {@link enqueueTasks} so the
|
|
3949
|
-
* handle validation, deduplication planning, payload encoding, and queue
|
|
3950
|
-
* dispatch live in one cohesive place instead of one oversized method.
|
|
3951
|
-
*
|
|
3952
|
-
* @module
|
|
3953
|
-
*/
|
|
3954
|
-
/**
|
|
3955
|
-
* Validates the task handle, plans deduplication, encodes every payload, then
|
|
3956
|
-
* dispatches the resulting messages to the queue. A single item flows through
|
|
3957
|
-
* the same pipeline as a batch, so {@link Context.enqueueTask} and
|
|
3958
|
-
* {@link Context.enqueueTaskMany} share one implementation.
|
|
3959
|
-
* @template TContextData The context data to pass to the {@link Context}.
|
|
3960
|
-
* @template TData The type of the task payload, inferred from the task's schema.
|
|
3961
|
-
* @param ctx The enqueueing dependencies assembled by `ContextImpl`.
|
|
3962
|
-
* @param task The handle returned by `defineTask()`.
|
|
3963
|
-
* @param items The payloads to enqueue, in order.
|
|
3964
|
-
* @param options The enqueue options governing delay, ordering, and dedup.
|
|
3965
|
-
* @internal
|
|
3966
|
-
*/
|
|
3967
|
-
const enqueueTasks = (ctx) => async function(task, items, options) {
|
|
3968
|
-
const def = ctx.federation.taskDefinitions.get(task.name);
|
|
3969
|
-
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().`);
|
|
3970
|
-
const queue = ctx.federation.resolveTaskQueue(task.name);
|
|
3971
|
-
if (queue == null) throw new TypeError("No message queue is configured for tasks; pass `queue` to createFederation() or to defineTask().");
|
|
3972
|
-
if (items.length < 1) return;
|
|
3973
|
-
const plan = planDeduplication(ctx, queue, task.name, options, items.length);
|
|
3974
|
-
const messages = await Promise.all(items.map(encodeTaskMessage(ctx.codec, ctx.origin, task, options)));
|
|
3975
|
-
const claim = await claimDeduplication(ctx, plan, task.name);
|
|
3976
|
-
if (!claim.proceed) return;
|
|
3977
|
-
if (!ctx.federation.manuallyStartQueue) ctx.federation._startQueueInternal(ctx.data);
|
|
3978
|
-
try {
|
|
3979
|
-
await dispatch(queue, messages, {
|
|
3980
|
-
delay: getDurationIfDefined(options.delay),
|
|
3981
|
-
orderingKey: options.orderingKey,
|
|
3982
|
-
deduplicationKey: claim.forwardedDeduplicationKey
|
|
3983
|
-
});
|
|
3984
|
-
} catch (error) {
|
|
3985
|
-
if (claim.rollback != null) try {
|
|
3986
|
-
await claim.rollback();
|
|
3987
|
-
} catch (rollbackError) {
|
|
3988
|
-
logger$1.warn("Failed to roll back the deduplication marker for task {taskName} after a failed enqueue; it will expire by TTL. {rollbackError}", {
|
|
3989
|
-
taskName: task.name,
|
|
3990
|
-
rollbackError
|
|
3991
|
-
});
|
|
3992
|
-
}
|
|
3993
|
-
throw error;
|
|
3994
|
-
}
|
|
3995
|
-
};
|
|
3996
|
-
const getDurationIfDefined = (item) => item == null ? void 0 : Temporal.Duration.from(item);
|
|
3997
|
-
/**
|
|
3998
|
-
* Decides how a `deduplicationKey` (if any) is honored: forwarded to a native
|
|
3999
|
-
* queue, claimed via `cas`, or—when neither is available—dropped or rejected
|
|
4000
|
-
* per the federation's `taskDeduplicationFallback`. Throws the fail-fast
|
|
4001
|
-
* `TypeError`s so they precede the encode.
|
|
4002
|
-
*/
|
|
4003
|
-
function planDeduplication(ctx, queue, taskName, options, itemCount) {
|
|
4004
|
-
if (options.deduplicationKey == null) return { kind: "none" };
|
|
4005
|
-
const key = options.deduplicationKey;
|
|
4006
|
-
const native = queue.nativeDeduplication === true;
|
|
4007
|
-
const canCas = ctx.federation.kv.cas != null;
|
|
4008
|
-
const canBatchAtomically = queue.enqueueMany != null && !(queue instanceof ParallelMessageQueue && queue.queue.enqueueMany == null);
|
|
4009
|
-
if (itemCount > 1 && !canBatchAtomically && (native || canCas)) throw new TypeError(`Task ${JSON.stringify(taskName)} was enqueued as a batch with a deduplicationKey, but its message queue does not implement enqueueMany; a multi-item batch cannot be deduplicated atomically without it. Implement enqueueMany on the queue, or enqueue the tasks individually with enqueueTask().`);
|
|
4010
|
-
if (native) return {
|
|
4011
|
-
kind: "native",
|
|
4012
|
-
key
|
|
4013
|
-
};
|
|
4014
|
-
if (canCas) return {
|
|
4015
|
-
kind: "cas",
|
|
4016
|
-
key
|
|
4017
|
-
};
|
|
4018
|
-
if (ctx.federation.taskDeduplicationFallback === "closed") throw new TypeError("deduplicationKey was set but the message queue does not declare nativeDeduplication and the key-value store exposes no conditional write (cas); set taskDeduplicationFallback to \"open\" to proceed without deduplication, or use a backend that supports it.");
|
|
4019
|
-
return {
|
|
4020
|
-
kind: "open",
|
|
4021
|
-
key
|
|
4022
|
-
};
|
|
4023
|
-
}
|
|
4024
|
-
/**
|
|
4025
|
-
* Executes the planned deduplication once the payloads are encoded. A native
|
|
4026
|
-
* plan forwards its key to the queue; a `cas` plan claims the marker and stops
|
|
4027
|
-
* the enqueue when it loses the race; an `open` plan logs and proceeds.
|
|
4028
|
-
* @returns Whether to proceed, and the key (if any) to forward to the queue.
|
|
4029
|
-
*/
|
|
4030
|
-
async function claimDeduplication(ctx, plan, taskName) {
|
|
4031
|
-
switch (plan.kind) {
|
|
4032
|
-
case "native": return {
|
|
4033
|
-
proceed: true,
|
|
4034
|
-
forwardedDeduplicationKey: plan.key
|
|
4035
|
-
};
|
|
4036
|
-
case "cas": {
|
|
4037
|
-
const cacheKey = [...ctx.federation.kvPrefixes.taskDeduplication, plan.key];
|
|
4038
|
-
const token = crypto.randomUUID();
|
|
4039
|
-
if (!await ctx.federation.kv.cas(cacheKey, void 0, token, { ttl: ctx.federation.taskDeduplicationTtl })) return { proceed: false };
|
|
4040
|
-
return {
|
|
4041
|
-
proceed: true,
|
|
4042
|
-
rollback: async () => {
|
|
4043
|
-
await ctx.federation.kv.cas(cacheKey, token, void 0);
|
|
4044
|
-
}
|
|
4045
|
-
};
|
|
4046
|
-
}
|
|
4047
|
-
case "open": logger$1.debug("deduplicationKey {deduplicationKey} for task {taskName} ignored: the message queue declares no nativeDeduplication and the key-value store has no cas; proceeding (taskDeduplicationFallback is \"open\").", {
|
|
4048
|
-
deduplicationKey: plan.key,
|
|
4049
|
-
taskName
|
|
4050
|
-
});
|
|
4051
|
-
case "none": return { proceed: true };
|
|
4052
|
-
default: throw new TypeError(`Unknown deduplication plan: ${JSON.stringify(plan)}`);
|
|
4053
|
-
}
|
|
4054
|
-
}
|
|
4055
|
-
/**
|
|
4056
|
-
* Sends the encoded messages to the queue, picking the bulk path when the
|
|
4057
|
-
* queue implements `enqueueMany` and otherwise fanning out parallel single
|
|
4058
|
-
* enqueues. The fan-out drops `deduplicationKey`, which is only ever set for a
|
|
4059
|
-
* native plan that the bulk paths already cover.
|
|
4060
|
-
*/
|
|
4061
|
-
async function dispatch(queue, messages, options) {
|
|
4062
|
-
if (messages.length === 1) await queue.enqueue(messages[0], options);
|
|
4063
|
-
else if (queue.enqueueMany != null) await queue.enqueueMany(messages, options);
|
|
4064
|
-
else {
|
|
4065
|
-
const fanoutOptions = {
|
|
4066
|
-
delay: options.delay,
|
|
4067
|
-
orderingKey: options.orderingKey
|
|
4068
|
-
};
|
|
4069
|
-
await Promise.all(messages.map((m) => queue.enqueue(m, fanoutOptions)));
|
|
4070
|
-
}
|
|
4071
|
-
}
|
|
4072
|
-
/**
|
|
4073
|
-
* Builds the per-payload encoder: validates and serializes the payload, then
|
|
4074
|
-
* stamps the message envelope with a fresh id, the context's origin, and the
|
|
4075
|
-
* active trace context. Curried so the batch encode reuses one bound encoder.
|
|
4076
|
-
*/
|
|
4077
|
-
const encodeTaskMessage = (codec, origin, task, options) => async (data) => {
|
|
4078
|
-
const encoded = await codec.encode(task.schema, data);
|
|
4079
|
-
const carrier = {};
|
|
4080
|
-
propagation.inject(context.active(), carrier);
|
|
4081
|
-
return {
|
|
4082
|
-
type: "task",
|
|
4083
|
-
id: crypto.randomUUID(),
|
|
4084
|
-
baseUrl: origin,
|
|
4085
|
-
taskName: task.name,
|
|
4086
|
-
data: encoded,
|
|
4087
|
-
started: Temporal.Now.instant().toString(),
|
|
4088
|
-
attempt: 0,
|
|
4089
|
-
orderingKey: options.orderingKey,
|
|
4090
|
-
traceContext: carrier
|
|
4091
|
-
};
|
|
4092
|
-
};
|
|
4093
|
-
const logger$1 = getLogger([
|
|
4094
|
-
"fedify",
|
|
4095
|
-
"federation",
|
|
4096
|
-
"task"
|
|
4097
|
-
]);
|
|
4098
|
-
//#endregion
|
|
4099
3555
|
//#region src/federation/webfinger.ts
|
|
4100
3556
|
const logger = getLogger([
|
|
4101
3557
|
"fedify",
|
|
@@ -4403,6 +3859,15 @@ function isPermanentRemoteContextError(error) {
|
|
|
4403
3859
|
function isPermanentInboxParseError(error) {
|
|
4404
3860
|
return error instanceof Error && (error.name === "UnsafeJsonLdError" || error instanceof InvalidContextReferenceError || isPermanentRemoteContextError(error) || error.name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure(error)) || error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:|Invalid @id:)/.test(error.message) || isInvalidUrlTypeError(error));
|
|
4405
3861
|
}
|
|
3862
|
+
function hasLinkedDataSignatureJsonLdProcessingError(error) {
|
|
3863
|
+
if (error.message.startsWith("Maximum deep iterations exceeded")) return true;
|
|
3864
|
+
const cause = error.cause instanceof Error ? error.cause : error.details?.cause;
|
|
3865
|
+
if (error.name === "jsonld.InvalidUrl" && error.details?.code === "loading remote context failed" && cause instanceof Error) return cause.message.startsWith("Maximum deep iterations exceeded") || cause.name.startsWith("jsonld.");
|
|
3866
|
+
return error.name !== "jsonld.InvalidUrl" && error.name.startsWith("jsonld.");
|
|
3867
|
+
}
|
|
3868
|
+
function isLinkedDataSignatureJsonLdProcessingError(error) {
|
|
3869
|
+
return error instanceof Error && hasLinkedDataSignatureJsonLdProcessingError(error);
|
|
3870
|
+
}
|
|
4406
3871
|
/**
|
|
4407
3872
|
* Create a new {@link Federation} instance.
|
|
4408
3873
|
* @param options Parameters for initializing the instance.
|
|
@@ -4420,8 +3885,9 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4420
3885
|
inboxQueue;
|
|
4421
3886
|
outboxQueue;
|
|
4422
3887
|
fanoutQueue;
|
|
4423
|
-
|
|
4424
|
-
|
|
3888
|
+
inboxQueueStarted;
|
|
3889
|
+
outboxQueueStarted;
|
|
3890
|
+
fanoutQueueStarted;
|
|
4425
3891
|
manuallyStartQueue;
|
|
4426
3892
|
origin;
|
|
4427
3893
|
documentLoaderFactory;
|
|
@@ -4435,10 +3901,6 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4435
3901
|
skipSignatureVerification;
|
|
4436
3902
|
outboxRetryPolicy;
|
|
4437
3903
|
inboxRetryPolicy;
|
|
4438
|
-
taskRetryPolicy;
|
|
4439
|
-
taskQueueResolution;
|
|
4440
|
-
taskDeduplicationTtl;
|
|
4441
|
-
taskDeduplicationFallback;
|
|
4442
3904
|
circuitBreaker;
|
|
4443
3905
|
activityTransformers;
|
|
4444
3906
|
_tracerProvider;
|
|
@@ -4478,24 +3940,20 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4478
3940
|
httpMessageSignaturesSpec: ["_fedify", "httpMessageSignaturesSpec"],
|
|
4479
3941
|
acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"],
|
|
4480
3942
|
circuitBreaker: ["_fedify", "circuit"],
|
|
4481
|
-
taskDeduplication: ["_fedify", "taskDeduplication"],
|
|
4482
3943
|
...options.kvPrefixes ?? {}
|
|
4483
3944
|
};
|
|
4484
3945
|
if (options.queue == null) {
|
|
4485
3946
|
this.inboxQueue = void 0;
|
|
4486
3947
|
this.outboxQueue = void 0;
|
|
4487
3948
|
this.fanoutQueue = void 0;
|
|
4488
|
-
this.taskQueue = void 0;
|
|
4489
3949
|
} else if ("enqueue" in options.queue && "listen" in options.queue) {
|
|
4490
3950
|
this.inboxQueue = options.queue;
|
|
4491
3951
|
this.outboxQueue = options.queue;
|
|
4492
3952
|
this.fanoutQueue = options.queue;
|
|
4493
|
-
this.taskQueue = void 0;
|
|
4494
3953
|
} else {
|
|
4495
3954
|
this.inboxQueue = options.queue.inbox;
|
|
4496
3955
|
this.outboxQueue = options.queue.outbox;
|
|
4497
3956
|
this.fanoutQueue = options.queue.fanout;
|
|
4498
|
-
this.taskQueue = options.queue.task;
|
|
4499
3957
|
}
|
|
4500
3958
|
if (options.circuitBreaker !== false && this.outboxQueue != null) {
|
|
4501
3959
|
this.circuitBreaker = new CircuitBreaker({
|
|
@@ -4516,7 +3974,9 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4516
3974
|
]).warn("The configured key-value store does not support CAS; outbound delivery circuit breaker updates may race under concurrent workers.");
|
|
4517
3975
|
}
|
|
4518
3976
|
}
|
|
4519
|
-
this.
|
|
3977
|
+
this.inboxQueueStarted = false;
|
|
3978
|
+
this.outboxQueueStarted = false;
|
|
3979
|
+
this.fanoutQueueStarted = false;
|
|
4520
3980
|
this.manuallyStartQueue = options.manuallyStartQueue ?? false;
|
|
4521
3981
|
if (options.origin != null) if (typeof options.origin === "string") {
|
|
4522
3982
|
if (!URL.canParse(options.origin) || !options.origin.match(/^https?:\/\//)) throw new TypeError(`Invalid origin: ${JSON.stringify(options.origin)}`);
|
|
@@ -4598,10 +4058,6 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4598
4058
|
this.inboxChallengePolicy = options.inboxChallengePolicy;
|
|
4599
4059
|
this.outboxRetryPolicy = options.outboxRetryPolicy ?? createExponentialBackoffPolicy();
|
|
4600
4060
|
this.inboxRetryPolicy = options.inboxRetryPolicy ?? createExponentialBackoffPolicy();
|
|
4601
|
-
this.taskRetryPolicy = options.taskRetryPolicy ?? createExponentialBackoffPolicy();
|
|
4602
|
-
this.taskQueueResolution = options.taskQueueResolution ?? "fallback";
|
|
4603
|
-
this.taskDeduplicationTtl = Temporal.Duration.from(options.taskDeduplicationTtl ?? { hours: 1 });
|
|
4604
|
-
this.taskDeduplicationFallback = options.taskDeduplicationFallback ?? "open";
|
|
4605
4061
|
this.activityTransformers = options.activityTransformers ?? getDefaultActivityTransformers();
|
|
4606
4062
|
this._tracerProvider = options.tracerProvider;
|
|
4607
4063
|
if (benchmarkMode) {
|
|
@@ -4650,33 +4106,28 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4650
4106
|
_getTracer() {
|
|
4651
4107
|
return this.tracerProvider.getTracer(name, version);
|
|
4652
4108
|
}
|
|
4653
|
-
resolveTaskQueue(taskName) {
|
|
4654
|
-
const resolved = this.taskDefinitions.get(taskName)?.queue ?? this.taskQueue;
|
|
4655
|
-
if (resolved != null) return resolved;
|
|
4656
|
-
return this.taskQueueResolution === "strict" ? void 0 : this.outboxQueue;
|
|
4657
|
-
}
|
|
4658
4109
|
async _startQueueInternal(ctxData, signal, queue) {
|
|
4659
|
-
|
|
4660
|
-
const customQueues = this.taskDefinitions.values().map((def) => ["task", def.queue]);
|
|
4661
|
-
const targets = [
|
|
4662
|
-
["inbox", this.inboxQueue],
|
|
4663
|
-
["outbox", this.outboxQueue],
|
|
4664
|
-
["fanout", this.fanoutQueue],
|
|
4665
|
-
["task", taskQueue],
|
|
4666
|
-
...customQueues
|
|
4667
|
-
];
|
|
4110
|
+
if (this.inboxQueue == null && this.outboxQueue == null) return;
|
|
4668
4111
|
const logger = getLogger([
|
|
4669
4112
|
"fedify",
|
|
4670
4113
|
"federation",
|
|
4671
4114
|
"queue"
|
|
4672
4115
|
]);
|
|
4673
4116
|
const promises = [];
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
this.
|
|
4678
|
-
|
|
4679
|
-
|
|
4117
|
+
if (this.inboxQueue != null && (queue == null || queue === "inbox") && !this.inboxQueueStarted) {
|
|
4118
|
+
logger.debug("Starting an inbox task worker.");
|
|
4119
|
+
this.inboxQueueStarted = true;
|
|
4120
|
+
promises.push(this.inboxQueue.listen((msg) => this.processQueuedTask(ctxData, msg), { signal }));
|
|
4121
|
+
}
|
|
4122
|
+
if (this.outboxQueue != null && this.outboxQueue !== this.inboxQueue && (queue == null || queue === "outbox") && !this.outboxQueueStarted) {
|
|
4123
|
+
logger.debug("Starting an outbox task worker.");
|
|
4124
|
+
this.outboxQueueStarted = true;
|
|
4125
|
+
promises.push(this.outboxQueue.listen((msg) => this.processQueuedTask(ctxData, msg), { signal }));
|
|
4126
|
+
}
|
|
4127
|
+
if (this.fanoutQueue != null && this.fanoutQueue !== this.inboxQueue && this.fanoutQueue !== this.outboxQueue && (queue == null || queue === "fanout") && !this.fanoutQueueStarted) {
|
|
4128
|
+
logger.debug("Starting a fanout task worker.");
|
|
4129
|
+
this.fanoutQueueStarted = true;
|
|
4130
|
+
promises.push(this.fanoutQueue.listen((msg) => this.processQueuedTask(ctxData, msg), { signal }));
|
|
4680
4131
|
}
|
|
4681
4132
|
await Promise.all(promises);
|
|
4682
4133
|
}
|
|
@@ -4799,7 +4250,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
4799
4250
|
}
|
|
4800
4251
|
});
|
|
4801
4252
|
});
|
|
4802
|
-
}
|
|
4253
|
+
}
|
|
4803
4254
|
});
|
|
4804
4255
|
}
|
|
4805
4256
|
async #listenFanoutMessage(data, message) {
|
|
@@ -5363,77 +4814,6 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5363
4814
|
listenerSpan.end();
|
|
5364
4815
|
});
|
|
5365
4816
|
}
|
|
5366
|
-
async #listenTaskMessage(contextData, message) {
|
|
5367
|
-
const logger = getLogger([
|
|
5368
|
-
"fedify",
|
|
5369
|
-
"federation",
|
|
5370
|
-
"task"
|
|
5371
|
-
]);
|
|
5372
|
-
const def = this.taskDefinitions.get(message.taskName);
|
|
5373
|
-
if (def == null) {
|
|
5374
|
-
logger.warn("Received a custom task {taskName} with no registered handler; dropping.", { taskName: message.taskName });
|
|
5375
|
-
return;
|
|
5376
|
-
}
|
|
5377
|
-
const context = this.#createContext(new URL(message.baseUrl), contextData);
|
|
5378
|
-
let data;
|
|
5379
|
-
try {
|
|
5380
|
-
data = await context.codec.decode(def.schema, message.data);
|
|
5381
|
-
} catch (error) {
|
|
5382
|
-
logger.error("Custom task {taskName} payload could not be decoded or validated; dropping:\n{error}", {
|
|
5383
|
-
taskName: message.taskName,
|
|
5384
|
-
error
|
|
5385
|
-
});
|
|
5386
|
-
return;
|
|
5387
|
-
}
|
|
5388
|
-
try {
|
|
5389
|
-
await def.handler(context, data);
|
|
5390
|
-
} catch (error) {
|
|
5391
|
-
if (def.onError != null) try {
|
|
5392
|
-
await def.onError(context, error, data);
|
|
5393
|
-
} catch (onErrorError) {
|
|
5394
|
-
logger.error("onError for custom task {taskName} threw:\n{error}", {
|
|
5395
|
-
taskName: message.taskName,
|
|
5396
|
-
error: onErrorError
|
|
5397
|
-
});
|
|
5398
|
-
}
|
|
5399
|
-
const queue = this.resolveTaskQueue(def.name);
|
|
5400
|
-
if (queue?.nativeRetrial) throw error;
|
|
5401
|
-
const retryPolicy = def.retryPolicy ?? this.taskRetryPolicy;
|
|
5402
|
-
let elapsedTime = Temporal.Duration.from({ seconds: 0 });
|
|
5403
|
-
try {
|
|
5404
|
-
elapsedTime = Temporal.Instant.from(message.started).until(Temporal.Now.instant());
|
|
5405
|
-
} catch (parseError) {
|
|
5406
|
-
logger.error("Custom task {taskName} has an unparsable started time {started}; treating elapsedTime as zero:\n{error}", {
|
|
5407
|
-
taskName: message.taskName,
|
|
5408
|
-
started: message.started,
|
|
5409
|
-
error: parseError
|
|
5410
|
-
});
|
|
5411
|
-
}
|
|
5412
|
-
const delay = retryPolicy({
|
|
5413
|
-
elapsedTime,
|
|
5414
|
-
attempts: message.attempt
|
|
5415
|
-
});
|
|
5416
|
-
if (delay != null && queue != null) {
|
|
5417
|
-
logger.error("Custom task {taskName} failed (attempt #{attempt}); retry...:\n{error}", {
|
|
5418
|
-
taskName: message.taskName,
|
|
5419
|
-
attempt: message.attempt,
|
|
5420
|
-
error
|
|
5421
|
-
});
|
|
5422
|
-
const retryMessage = {
|
|
5423
|
-
...message,
|
|
5424
|
-
attempt: message.attempt + 1
|
|
5425
|
-
};
|
|
5426
|
-
await queue.enqueue(retryMessage, {
|
|
5427
|
-
delay: clampNegativeDelay(delay),
|
|
5428
|
-
orderingKey: message.orderingKey
|
|
5429
|
-
});
|
|
5430
|
-
} else logger.error("Custom task {taskName} failed after {attempt} attempts; giving up:\n{error}", {
|
|
5431
|
-
taskName: message.taskName,
|
|
5432
|
-
attempt: message.attempt,
|
|
5433
|
-
error
|
|
5434
|
-
});
|
|
5435
|
-
}
|
|
5436
|
-
}
|
|
5437
4817
|
startQueue(contextData, options = {}) {
|
|
5438
4818
|
return this._startQueueInternal(contextData, options.signal, options.queue);
|
|
5439
4819
|
}
|
|
@@ -5522,10 +4902,18 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
5522
4902
|
privateKey: pair.privateKey
|
|
5523
4903
|
}))
|
|
5524
4904
|
});
|
|
5525
|
-
else
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
4905
|
+
else try {
|
|
4906
|
+
jsonLd = await signJsonLd(jsonLd, rsaKey.privateKey, rsaKey.keyId, {
|
|
4907
|
+
contextLoader,
|
|
4908
|
+
tracerProvider: this.tracerProvider
|
|
4909
|
+
});
|
|
4910
|
+
} catch (error) {
|
|
4911
|
+
if (!isLinkedDataSignatureJsonLdProcessingError(error)) throw error;
|
|
4912
|
+
logger.warn("Failed to create a Linked Data signature for the activity {activityId}. The activity will be sent without a Linked Data signature.", {
|
|
4913
|
+
activityId,
|
|
4914
|
+
error
|
|
4915
|
+
});
|
|
4916
|
+
}
|
|
5529
4917
|
if (!hasProof) logger.warn("No supported key found to create a proof for the activity {activityId}. The activity will be sent without a proof. In order to create a proof, at least one Ed25519 key must be provided.", {
|
|
5530
4918
|
activityId,
|
|
5531
4919
|
keys: keys.map((pair) => ({
|
|
@@ -6010,7 +5398,6 @@ var ContextImpl = class ContextImpl {
|
|
|
6010
5398
|
documentLoader;
|
|
6011
5399
|
contextLoader;
|
|
6012
5400
|
invokedFromActorKeyPairsDispatcher;
|
|
6013
|
-
#codec;
|
|
6014
5401
|
constructor({ url, federation, data, documentLoader, contextLoader, invokedFromActorKeyPairsDispatcher }) {
|
|
6015
5402
|
this.url = url;
|
|
6016
5403
|
this.federation = federation;
|
|
@@ -6019,18 +5406,6 @@ var ContextImpl = class ContextImpl {
|
|
|
6019
5406
|
this.contextLoader = contextLoader;
|
|
6020
5407
|
this.invokedFromActorKeyPairsDispatcher = invokedFromActorKeyPairsDispatcher;
|
|
6021
5408
|
}
|
|
6022
|
-
/**
|
|
6023
|
-
* A {@link TaskCodec} bound to this context's loaders, used to encode
|
|
6024
|
-
* and decode custom task payloads. Lazily created and cached so a context
|
|
6025
|
-
* that never enqueues or dispatches a task pays nothing.
|
|
6026
|
-
* @internal
|
|
6027
|
-
*/
|
|
6028
|
-
get codec() {
|
|
6029
|
-
return this.#codec ??= new TaskCodec(this);
|
|
6030
|
-
}
|
|
6031
|
-
get #enqueueTasks() {
|
|
6032
|
-
return enqueueTasks(this);
|
|
6033
|
-
}
|
|
6034
5409
|
clone(data) {
|
|
6035
5410
|
return new ContextImpl({
|
|
6036
5411
|
url: this.url,
|
|
@@ -6349,12 +5724,6 @@ var ContextImpl = class ContextImpl {
|
|
|
6349
5724
|
allowPrivateAddress: this.federation.allowPrivateAddress
|
|
6350
5725
|
});
|
|
6351
5726
|
}
|
|
6352
|
-
async enqueueTask(task, data, options = {}) {
|
|
6353
|
-
await this.#enqueueTasks(task, [data], options);
|
|
6354
|
-
}
|
|
6355
|
-
async enqueueTaskMany(task, payloads, options = {}) {
|
|
6356
|
-
await this.#enqueueTasks(task, payloads, options);
|
|
6357
|
-
}
|
|
6358
5727
|
sendActivity(sender, recipients, activity, options = {}) {
|
|
6359
5728
|
return this.tracerProvider.getTracer(name, version).startActiveSpan(this.federation.outboxQueue == null || options.immediate ? "activitypub.outbox" : "activitypub.fanout", {
|
|
6360
5729
|
kind: this.federation.outboxQueue == null || options.immediate ? SpanKind.CLIENT : SpanKind.PRODUCER,
|
|
@@ -7065,4 +6434,4 @@ function getRequestId(request) {
|
|
|
7065
6434
|
return `req_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
7066
6435
|
}
|
|
7067
6436
|
//#endregion
|
|
7068
|
-
export {
|
|
6437
|
+
export { SendActivityError as a, buildCollectionSynchronizationHeader as c, normalizeCircuitBreakerOptions as d, parseCircuitBreakerKvState as f, createExponentialBackoffPolicy as i, digest as l, middleware_exports as n, respondWithObject as o, createFederationBuilder as p, handleWebFinger as r, respondWithObjectIfAcceptable as s, createFederation as t, CircuitBreaker as u };
|