@fedify/fedify 2.4.0-dev.1417 → 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.
Files changed (104) hide show
  1. package/dist/{assert-OguE97r2.mjs → assert-DikXweDx.mjs} +1 -1
  2. package/dist/{assert_instance_of-DBC5X09g.mjs → assert_instance_of-C4Ri6VuN.mjs} +1 -1
  3. package/dist/{assert_not_equals-DkVK8oqV.mjs → assert_not_equals--wG9hV7u.mjs} +1 -1
  4. package/dist/{assert_rejects-DN60FHPX.mjs → assert_rejects-DQP-q39h.mjs} +2 -2
  5. package/dist/{assert_strict_equals-XEgZAlrj.mjs → assert_strict_equals-Dmjbg-bA.mjs} +1 -1
  6. package/dist/{assert_throws-BOkhLGYc.mjs → assert_throws-4NwKEy2q.mjs} +1 -1
  7. package/dist/{builder-CU98OOiD.mjs → builder-Gef_ZeDE.mjs} +23 -3
  8. package/dist/codec-Bf-rTqks.mjs +94 -0
  9. package/dist/compat/mod.d.cts +1 -1
  10. package/dist/compat/mod.d.ts +1 -1
  11. package/dist/compat/outgoing-jsonld.test.mjs +3 -3
  12. package/dist/compat/public-audience.test.mjs +3 -3
  13. package/dist/compat/transformers.test.mjs +5 -5
  14. package/dist/{context-DVoTs_wM.mjs → context-BrIOaBSs.mjs} +7 -1
  15. package/dist/{context-BBVLF7lx.d.cts → context-BuGH_Vft.d.cts} +286 -70
  16. package/dist/{context-BU6jSQdo.d.ts → context-CLtZxCu6.d.ts} +285 -70
  17. package/dist/{deno-Dhn909W6.mjs → deno-BmD7XRaO.mjs} +1 -1
  18. package/dist/{docloader-CX8Auxrr.mjs → docloader-B8AJowCx.mjs} +2 -2
  19. package/dist/federation/builder.test.mjs +5 -5
  20. package/dist/federation/circuit-breaker.test.mjs +5 -5
  21. package/dist/federation/collection.test.mjs +3 -3
  22. package/dist/federation/handler.test.mjs +13 -13
  23. package/dist/federation/idempotency.test.mjs +6 -6
  24. package/dist/federation/inbox.test.mjs +3 -3
  25. package/dist/federation/keycache.test.mjs +5 -5
  26. package/dist/federation/kv.test.mjs +3 -3
  27. package/dist/federation/metrics.test.mjs +4 -4
  28. package/dist/federation/middleware.test.mjs +200 -19
  29. package/dist/federation/mod.cjs +1 -1
  30. package/dist/federation/mod.d.cts +3 -3
  31. package/dist/federation/mod.d.ts +3 -3
  32. package/dist/federation/mod.js +1 -1
  33. package/dist/federation/mq.test.mjs +5 -260
  34. package/dist/federation/negotiation.test.mjs +4 -4
  35. package/dist/federation/retry.test.mjs +3 -3
  36. package/dist/federation/router.test.mjs +5 -5
  37. package/dist/federation/send.test.mjs +10 -10
  38. package/dist/federation/tasks/codec.test.d.mts +2 -0
  39. package/dist/federation/tasks/codec.test.mjs +292 -0
  40. package/dist/federation/tasks/tasks.test.d.mts +2 -0
  41. package/dist/federation/tasks/tasks.test.mjs +651 -0
  42. package/dist/federation/temporal.test.mjs +4 -4
  43. package/dist/federation/webfinger.test.mjs +6 -6
  44. package/dist/{http-CsSSDB5j.cjs → http-BCSQJ_gv.cjs} +1 -1
  45. package/dist/{http-DndWw3Rs.js → http-CJ0WNyqA.js} +1 -1
  46. package/dist/{http-iapcKTk-.mjs → http-GQoPA_YT.mjs} +4 -4
  47. package/dist/{key-b17Uu8wX.mjs → key-BLy_TeTg.mjs} +2 -2
  48. package/dist/{kv-cache-CLo3nRjl.js → kv-cache-CjEcb9qH.js} +1 -1
  49. package/dist/{kv-cache-BIngyLxt.cjs → kv-cache-DXwu7BtY.cjs} +1 -1
  50. package/dist/{kv-cache-DcsWACki.mjs → kv-cache-pw-kBdVz.mjs} +1 -1
  51. package/dist/{ld-B1503hdI.mjs → ld-CWgxPlZq.mjs} +3 -3
  52. package/dist/{metrics-DSV9HQUk.mjs → metrics-DUKoLd_E.mjs} +1 -1
  53. package/dist/{middleware-DT5sfJam.js → middleware-2Us0G3Iy.js} +277 -10
  54. package/dist/{middleware-DA8JjaXv.cjs → middleware-BiCk4wGY.cjs} +277 -10
  55. package/dist/{middleware-Bb9gR5Ro.mjs → middleware-Dq-HhJHd.mjs} +1 -1
  56. package/dist/{middleware-Dh1xDMbI.mjs → middleware-PZaroDUk.mjs} +182 -23
  57. package/dist/{mod-C0F6kvgS.d.cts → mod-DRokcHeU.d.cts} +1 -1
  58. package/dist/{mod-vPYVoa5n.d.ts → mod-r-1xOW_C.d.ts} +1 -1
  59. package/dist/mod.cjs +4 -4
  60. package/dist/mod.d.cts +4 -4
  61. package/dist/mod.d.ts +4 -4
  62. package/dist/mod.js +4 -4
  63. package/dist/mq-CvJAzRz5.mjs +260 -0
  64. package/dist/nodeinfo/client.test.mjs +3 -3
  65. package/dist/nodeinfo/handler.test.mjs +5 -5
  66. package/dist/nodeinfo/types.test.mjs +4 -4
  67. package/dist/otel/exporter.test.mjs +3 -3
  68. package/dist/{outgoing-jsonld-L_DbOaFe.mjs → outgoing-jsonld-BgFLCJQ_.mjs} +1 -1
  69. package/dist/{owner-PCw-OkL0.mjs → owner-U0Z8Ex6i.mjs} +2 -2
  70. package/dist/{proof-CJCLdaV3.cjs → proof-DA3hLGSO.cjs} +1 -1
  71. package/dist/{proof-BhbSD4bP.js → proof-f7K32nz7.js} +1 -1
  72. package/dist/{proof-Dacxh41h.mjs → proof-nb5tfciW.mjs} +5 -5
  73. package/dist/{send-Cdsbc_Vj.mjs → send-CLfW5Fdf.mjs} +3 -3
  74. package/dist/sig/accept.test.mjs +1 -1
  75. package/dist/sig/http.test.mjs +8 -8
  76. package/dist/sig/key.test.mjs +6 -6
  77. package/dist/sig/ld.test.mjs +7 -7
  78. package/dist/sig/mod.cjs +2 -2
  79. package/dist/sig/mod.js +2 -2
  80. package/dist/sig/owner.test.mjs +6 -6
  81. package/dist/sig/proof.test.mjs +8 -8
  82. package/dist/{std__assert-BBjXFNOb.mjs → std__assert-BTEgfoJo.mjs} +4 -4
  83. package/dist/{temporal-BMMqjhOp.mjs → temporal-2ry_jdCK.mjs} +1 -1
  84. package/dist/testing/mod.d.mts +378 -7
  85. package/dist/testing/mod.mjs +3 -2
  86. package/dist/testing-CjYPStvE.mjs +55 -0
  87. package/dist/utils/docloader.test.mjs +6 -6
  88. package/dist/utils/kv-cache.test.mjs +1 -1
  89. package/dist/utils/mod.cjs +1 -1
  90. package/dist/utils/mod.js +1 -1
  91. package/package.json +9 -7
  92. /package/dist/{accept-CPkZzmGN.mjs → accept-CceiKpCy.mjs} +0 -0
  93. /package/dist/{activity-listener-tztVvlNb.mjs → activity-listener-BeTGV3wc.mjs} +0 -0
  94. /package/dist/{assert_equals-C-ZRDbaf.mjs → assert_equals-Ew3jOFa3.mjs} +0 -0
  95. /package/dist/{circuit-breaker-CSWsyoef.mjs → circuit-breaker-hJBB6jwA.mjs} +0 -0
  96. /package/dist/{client-ByXmQhYD.mjs → client-B_A6mfn3.mjs} +0 -0
  97. /package/dist/{collection-Cc3DVAhE.mjs → collection-Dp5ky45w.mjs} +0 -0
  98. /package/dist/{keycache-BeU0LCII.mjs → keycache-CaOR6NYg.mjs} +0 -0
  99. /package/dist/{keys-DGu1NFwu.mjs → keys-C3kae-6B.mjs} +0 -0
  100. /package/dist/{kv-rV3vodCc.mjs → kv-x2IvBUyq.mjs} +0 -0
  101. /package/dist/{negotiation-DDstyBvc.mjs → negotiation-m_L2nyS3.mjs} +0 -0
  102. /package/dist/{public-audience-Cvbr2Gzt.mjs → public-audience-N3pyOx2p.mjs} +0 -0
  103. /package/dist/{retry-CXg_MBI-.mjs → retry-DI4O-zYl.mjs} +0 -0
  104. /package/dist/{types-J53Kw7so.mjs → types-BFowWFTT.mjs} +0 -0
@@ -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-Dhn909W6.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-DSV9HQUk.mjs";
6
- import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CPkZzmGN.mjs";
7
- import { o as validateCryptoKey, r as fetchKeyDetailed } from "./key-b17Uu8wX.mjs";
4
+ import { n as version, t as name } from "./deno-BmD7XRaO.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-DUKoLd_E.mjs";
6
+ import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CceiKpCy.mjs";
7
+ import { o as validateCryptoKey, r as fetchKeyDetailed } from "./key-BLy_TeTg.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-Dhn909W6.mjs";
5
- import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-DSV9HQUk.mjs";
4
+ import { n as version, t as name } from "./deno-BmD7XRaO.mjs";
5
+ import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-DUKoLd_E.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-DndWw3Rs.js";
3
+ import { C as recordDocumentCache, d as validateCryptoKey, t as doubleKnock } from "./http-CJ0WNyqA.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
  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-CsSSDB5j.cjs");
4
+ const require_http = require("./http-BCSQJ_gv.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,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-DSV9HQUk.mjs";
4
+ import { p as recordDocumentCache } from "./metrics-DUKoLd_E.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,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-Dhn909W6.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-DSV9HQUk.mjs";
6
- import { n as fetchKey, o as validateCryptoKey } from "./key-b17Uu8wX.mjs";
4
+ import { n as version, t as name } from "./deno-BmD7XRaO.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-DUKoLd_E.mjs";
6
+ import { n as fetchKey, o as validateCryptoKey } from "./key-BLy_TeTg.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";
@@ -1,7 +1,7 @@
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-Dhn909W6.mjs";
4
+ import { n as version, t as name } from "./deno-BmD7XRaO.mjs";
5
5
  import { metrics } from "@opentelemetry/api";
6
6
  import { FetchError } from "@fedify/vocab-runtime";
7
7
  //#region src/federation/metrics.ts
@@ -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-DndWw3Rs.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-BhbSD4bP.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-CJ0WNyqA.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-f7K32nz7.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-CLo3nRjl.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-CjEcb9qH.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,6 +17,7 @@ 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";
20
21
  import { domainToASCII } from "node:url";
21
22
  //#region src/federation/activity-listener.ts
22
23
  var ActivityListenerSet = class {
@@ -107,6 +108,7 @@ var FederationBuilderImpl = class {
107
108
  idempotencyStrategy;
108
109
  collectionTypeIds;
109
110
  collectionCallbacks;
111
+ taskDefinitions;
110
112
  /**
111
113
  * Symbol registry for unique identification of unnamed symbols.
112
114
  */
@@ -117,6 +119,7 @@ var FederationBuilderImpl = class {
117
119
  this.objectTypeIds = {};
118
120
  this.collectionCallbacks = {};
119
121
  this.collectionTypeIds = {};
122
+ this.taskDefinitions = /* @__PURE__ */ new Map();
120
123
  }
121
124
  /**
122
125
  * Builds the federation object.
@@ -155,6 +158,7 @@ var FederationBuilderImpl = class {
155
158
  f.unverifiedActivityHandler = this.unverifiedActivityHandler;
156
159
  f.outboxPermanentFailureHandler = this.outboxPermanentFailureHandler;
157
160
  f.idempotencyStrategy = this.idempotencyStrategy;
161
+ f.taskDefinitions = new Map(this.taskDefinitions);
158
162
  return f;
159
163
  }
160
164
  _getTracer() {
@@ -287,6 +291,23 @@ var FederationBuilderImpl = class {
287
291
  setWebFingerLinksDispatcher(dispatcher) {
288
292
  this.webFingerLinksDispatcher = dispatcher;
289
293
  }
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
+ }
290
311
  /**
291
312
  * The RFC 6570 template-literal `path` overloads were removed for
292
313
  * type-checking efficiency, so the URI variable types can no longer be
@@ -1392,7 +1413,7 @@ function acceptsJsonLd(request) {
1392
1413
  }
1393
1414
  //#endregion
1394
1415
  //#region src/federation/temporal.ts
1395
- function isPlainObject(value) {
1416
+ function isPlainObject$1(value) {
1396
1417
  return typeof value === "object" && value != null && !Array.isArray(value);
1397
1418
  }
1398
1419
  function normalizeDateTimeLiteral(value) {
@@ -1430,11 +1451,11 @@ const QUESTION_CLOSED_IRI = "https://www.w3.org/ns/activitystreams#closed";
1430
1451
  const XSD_DATE_TIME_IRI = "http://www.w3.org/2001/XMLSchema#dateTime";
1431
1452
  function hasMalformedExpandedDateTimeLiteral(value) {
1432
1453
  if (Array.isArray(value)) return value.some(hasMalformedExpandedDateTimeLiteral);
1433
- return isPlainObject(value) && "@value" in value && isMalformedDateTimeLiteral(value["@value"]);
1454
+ return isPlainObject$1(value) && "@value" in value && isMalformedDateTimeLiteral(value["@value"]);
1434
1455
  }
1435
1456
  function hasMalformedExpandedQuestionClosedLiteral(value) {
1436
1457
  if (Array.isArray(value)) return value.some(hasMalformedExpandedQuestionClosedLiteral);
1437
- if (!isPlainObject(value) || !("@value" in value)) return false;
1458
+ if (!isPlainObject$1(value) || !("@value" in value)) return false;
1438
1459
  const literal = value["@value"];
1439
1460
  if (typeof literal === "boolean") return false;
1440
1461
  if (typeof literal !== "string") return false;
@@ -1444,11 +1465,11 @@ function hasMalformedExpandedQuestionClosedLiteral(value) {
1444
1465
  }
1445
1466
  function hasMalformedExpandedDurationLiteral(value) {
1446
1467
  if (Array.isArray(value)) return value.some(hasMalformedExpandedDurationLiteral);
1447
- return isPlainObject(value) && "@value" in value && isMalformedDurationLiteral(value["@value"]);
1468
+ return isPlainObject$1(value) && "@value" in value && isMalformedDurationLiteral(value["@value"]);
1448
1469
  }
1449
1470
  function hasMalformedKnownTemporalLiteralInternal(value, visited) {
1450
1471
  if (Array.isArray(value)) return value.some((item) => hasMalformedKnownTemporalLiteralInternal(item, visited));
1451
- if (!isPlainObject(value)) return false;
1472
+ if (!isPlainObject$1(value)) return false;
1452
1473
  if (visited.has(value)) return false;
1453
1474
  visited.add(value);
1454
1475
  if ("@value" in value) return false;
@@ -3552,6 +3573,94 @@ function createExponentialBackoffPolicy(options = {}) {
3552
3573
  };
3553
3574
  }
3554
3575
  //#endregion
3576
+ //#region src/federation/tasks/codec.ts
3577
+ var TaskCodec = class TaskCodec {
3578
+ options;
3579
+ constructor(options) {
3580
+ this.options = options;
3581
+ }
3582
+ serialize = (data) => stringifyAsync(data, { Vocab: this.#stringifyVocab });
3583
+ deserialize = (raw) => this.#revive(/* @__PURE__ */ new Map())(parse(raw, { Vocab: VocabHolder.from }));
3584
+ /** Validates `data` against `schema`, then serializes it. */
3585
+ encode = async (schema, data) => this.serialize(await TaskCodec.validate(schema, data));
3586
+ /** Deserializes `raw`, then validates the result against `schema`. */
3587
+ decode = async (schema, raw) => TaskCodec.validate(schema, await this.deserialize(raw));
3588
+ static validate = async (schema, data) => getValueIfSchema(await schema["~standard"].validate(data));
3589
+ #stringifyVocab = (value) => isVocab(value) && this.#toWire(value);
3590
+ #toWire = async (value) => ({
3591
+ kind: value instanceof Link ? "link" : "object",
3592
+ jsonLd: await value.toJsonLd({
3593
+ format: "expand",
3594
+ ...this.options
3595
+ })
3596
+ });
3597
+ #revive = (seen) => {
3598
+ const inner = async (node) => {
3599
+ if (node === null || typeof node !== "object") return node;
3600
+ if (seen.has(node)) return seen.get(node);
3601
+ for (const reviver of this.#classRevivers) {
3602
+ const out = reviver(seen, inner, node);
3603
+ if (out !== void 0) return await out;
3604
+ }
3605
+ return node;
3606
+ };
3607
+ return inner;
3608
+ };
3609
+ #classRevivers = [
3610
+ classReviver(isInstanceOf(VocabHolder), ({ kind, jsonLd }) => kind === "link" ? Link.fromJsonLd(jsonLd, this.options) : Object$1.fromJsonLd(jsonLd, this.options), () => {}),
3611
+ classReviver(isInstanceOf(Array), () => [], async (revive, node, arr) => {
3612
+ for (const item of await Array.fromAsync(node, revive)) arr.push(item);
3613
+ }),
3614
+ classReviver(isInstanceOf(Map), () => /* @__PURE__ */ new Map(), async (revive, node, map) => {
3615
+ for (const [k, v] of node) map.set(await revive(k), await revive(v));
3616
+ }),
3617
+ classReviver(isInstanceOf(Set), () => /* @__PURE__ */ new Set(), async (revive, node, set) => {
3618
+ for (const v of await Array.fromAsync(node, revive)) set.add(v);
3619
+ }),
3620
+ classReviver(isPlainObject, () => ({}), async (revive, node, obj) => {
3621
+ for (const [k, v] of globalThis.Object.entries(node)) obj[k] = await revive(v);
3622
+ })
3623
+ ];
3624
+ };
3625
+ const isVocab = (value) => value instanceof Object$1 || value instanceof Link;
3626
+ const isPlainObject = (value) => value === null || typeof value !== "object" ? false : isObjectPrototype(globalThis.Object.getPrototypeOf(value));
3627
+ const isObjectPrototype = (proto) => proto === null || proto === globalThis.Object.prototype;
3628
+ const isInstanceOf = (cls) => (v) => v instanceof cls;
3629
+ function getValueIfSchema(result) {
3630
+ assertSchema(result);
3631
+ return result.value;
3632
+ }
3633
+ function assertSchema(result) {
3634
+ if (result.issues && result.issues.length > 0) throw new TypeError(`Task data failed schema validation: ${JSON.stringify(result.issues)}`);
3635
+ }
3636
+ /**
3637
+ * A vocabulary object parked by the synchronous decode reviver, held until
3638
+ * the async revive pass can `fromJsonLd()` it back into an instance.
3639
+ */
3640
+ var VocabHolder = class VocabHolder {
3641
+ kind;
3642
+ jsonLd;
3643
+ constructor(kind, jsonLd) {
3644
+ this.kind = kind;
3645
+ this.jsonLd = jsonLd;
3646
+ }
3647
+ static from = ({ kind, jsonLd }) => new VocabHolder(kind, jsonLd);
3648
+ };
3649
+ /**
3650
+ * Ties a container filter to its empty-shell `init` and child-filling `set`
3651
+ * through one type parameter—a correlation the heterogeneous reviver list
3652
+ * cannot carry, which previously forced `@ts-ignore` at the dispatch site.
3653
+ */
3654
+ const classReviver = (filter, init, set) => (seen, revive, node) => {
3655
+ if (!filter(node)) return void 0;
3656
+ return (async () => {
3657
+ const out = await init(node);
3658
+ seen.set(node, out);
3659
+ await set(revive, node, out);
3660
+ return out;
3661
+ })();
3662
+ };
3663
+ //#endregion
3555
3664
  //#region src/federation/webfinger.ts
3556
3665
  const logger = getLogger([
3557
3666
  "fedify",
@@ -3876,9 +3985,12 @@ var FederationImpl = class extends FederationBuilderImpl {
3876
3985
  inboxQueue;
3877
3986
  outboxQueue;
3878
3987
  fanoutQueue;
3988
+ taskQueue;
3879
3989
  inboxQueueStarted;
3880
3990
  outboxQueueStarted;
3881
3991
  fanoutQueueStarted;
3992
+ taskQueueStarted;
3993
+ startedTaskQueues;
3882
3994
  manuallyStartQueue;
3883
3995
  origin;
3884
3996
  documentLoaderFactory;
@@ -3892,6 +4004,8 @@ var FederationImpl = class extends FederationBuilderImpl {
3892
4004
  skipSignatureVerification;
3893
4005
  outboxRetryPolicy;
3894
4006
  inboxRetryPolicy;
4007
+ taskRetryPolicy;
4008
+ taskQueueResolution;
3895
4009
  circuitBreaker;
3896
4010
  activityTransformers;
3897
4011
  _tracerProvider;
@@ -3937,14 +4051,17 @@ var FederationImpl = class extends FederationBuilderImpl {
3937
4051
  this.inboxQueue = void 0;
3938
4052
  this.outboxQueue = void 0;
3939
4053
  this.fanoutQueue = void 0;
4054
+ this.taskQueue = void 0;
3940
4055
  } else if ("enqueue" in options.queue && "listen" in options.queue) {
3941
4056
  this.inboxQueue = options.queue;
3942
4057
  this.outboxQueue = options.queue;
3943
4058
  this.fanoutQueue = options.queue;
4059
+ this.taskQueue = void 0;
3944
4060
  } else {
3945
4061
  this.inboxQueue = options.queue.inbox;
3946
4062
  this.outboxQueue = options.queue.outbox;
3947
4063
  this.fanoutQueue = options.queue.fanout;
4064
+ this.taskQueue = options.queue.task;
3948
4065
  }
3949
4066
  if (options.circuitBreaker !== false && this.outboxQueue != null) {
3950
4067
  this.circuitBreaker = new CircuitBreaker({
@@ -3968,6 +4085,8 @@ var FederationImpl = class extends FederationBuilderImpl {
3968
4085
  this.inboxQueueStarted = false;
3969
4086
  this.outboxQueueStarted = false;
3970
4087
  this.fanoutQueueStarted = false;
4088
+ this.taskQueueStarted = false;
4089
+ this.startedTaskQueues = /* @__PURE__ */ new Set();
3971
4090
  this.manuallyStartQueue = options.manuallyStartQueue ?? false;
3972
4091
  if (options.origin != null) if (typeof options.origin === "string") {
3973
4092
  if (!URL.canParse(options.origin) || !options.origin.match(/^https?:\/\//)) throw new TypeError(`Invalid origin: ${JSON.stringify(options.origin)}`);
@@ -4049,6 +4168,8 @@ var FederationImpl = class extends FederationBuilderImpl {
4049
4168
  this.inboxChallengePolicy = options.inboxChallengePolicy;
4050
4169
  this.outboxRetryPolicy = options.outboxRetryPolicy ?? createExponentialBackoffPolicy();
4051
4170
  this.inboxRetryPolicy = options.inboxRetryPolicy ?? createExponentialBackoffPolicy();
4171
+ this.taskRetryPolicy = options.taskRetryPolicy ?? createExponentialBackoffPolicy();
4172
+ this.taskQueueResolution = options.taskQueueResolution ?? "fallback";
4052
4173
  this.activityTransformers = options.activityTransformers ?? getDefaultActivityTransformers();
4053
4174
  this._tracerProvider = options.tracerProvider;
4054
4175
  if (benchmarkMode) {
@@ -4097,8 +4218,14 @@ var FederationImpl = class extends FederationBuilderImpl {
4097
4218
  _getTracer() {
4098
4219
  return this.tracerProvider.getTracer(name, version);
4099
4220
  }
4221
+ resolveTaskQueue(taskName) {
4222
+ const resolved = this.taskDefinitions.get(taskName)?.queue ?? this.taskQueue;
4223
+ if (resolved != null) return resolved;
4224
+ return this.taskQueueResolution === "strict" ? void 0 : this.outboxQueue;
4225
+ }
4100
4226
  async _startQueueInternal(ctxData, signal, queue) {
4101
- if (this.inboxQueue == null && this.outboxQueue == null) return;
4227
+ const hasDedicatedTaskQueue = [...this.taskDefinitions.values()].some((def) => def.queue != null);
4228
+ if (this.inboxQueue == null && this.outboxQueue == null && this.fanoutQueue == null && this.taskQueue == null && !hasDedicatedTaskQueue) return;
4102
4229
  const logger = getLogger([
4103
4230
  "fedify",
4104
4231
  "federation",
@@ -4120,6 +4247,26 @@ var FederationImpl = class extends FederationBuilderImpl {
4120
4247
  this.fanoutQueueStarted = true;
4121
4248
  promises.push(this.fanoutQueue.listen((msg) => this.processQueuedTask(ctxData, msg), { signal }));
4122
4249
  }
4250
+ if (this.taskQueue != null && this.taskQueue !== this.inboxQueue && this.taskQueue !== this.outboxQueue && this.taskQueue !== this.fanoutQueue && (queue == null || queue === "task") && !this.taskQueueStarted) {
4251
+ logger.debug("Starting a task worker.");
4252
+ this.taskQueueStarted = true;
4253
+ promises.push(this.taskQueue.listen((msg) => this.processQueuedTask(ctxData, msg), { signal }));
4254
+ }
4255
+ if (queue == null || queue === "task") {
4256
+ const standardQueues = new Set([
4257
+ this.inboxQueue,
4258
+ this.outboxQueue,
4259
+ this.fanoutQueue,
4260
+ this.taskQueue
4261
+ ].filter((q) => q != null));
4262
+ for (const def of this.taskDefinitions.values()) {
4263
+ const taskQueue = def.queue;
4264
+ if (taskQueue == null || standardQueues.has(taskQueue) || this.startedTaskQueues.has(taskQueue)) continue;
4265
+ logger.debug("Starting a worker for a dedicated per-task queue.");
4266
+ this.startedTaskQueues.add(taskQueue);
4267
+ promises.push(taskQueue.listen((msg) => this.processQueuedTask(ctxData, msg), { signal }));
4268
+ }
4269
+ }
4123
4270
  await Promise.all(promises);
4124
4271
  }
4125
4272
  processQueuedTask(contextData, message) {
@@ -4241,7 +4388,7 @@ var FederationImpl = class extends FederationBuilderImpl {
4241
4388
  }
4242
4389
  });
4243
4390
  });
4244
- }
4391
+ } else if (message.type === "task") await this.#listenTaskMessage(contextData, message);
4245
4392
  });
4246
4393
  }
4247
4394
  async #listenFanoutMessage(data, message) {
@@ -4805,6 +4952,77 @@ var FederationImpl = class extends FederationBuilderImpl {
4805
4952
  listenerSpan.end();
4806
4953
  });
4807
4954
  }
4955
+ async #listenTaskMessage(contextData, message) {
4956
+ const logger = getLogger([
4957
+ "fedify",
4958
+ "federation",
4959
+ "task"
4960
+ ]);
4961
+ const def = this.taskDefinitions.get(message.taskName);
4962
+ if (def == null) {
4963
+ logger.warn("Received a custom task {taskName} with no registered handler; dropping.", { taskName: message.taskName });
4964
+ return;
4965
+ }
4966
+ const context = this.#createContext(new URL(message.baseUrl), contextData);
4967
+ let data;
4968
+ try {
4969
+ data = await context.codec.decode(def.schema, message.data);
4970
+ } catch (error) {
4971
+ logger.error("Custom task {taskName} payload could not be decoded or validated; dropping:\n{error}", {
4972
+ taskName: message.taskName,
4973
+ error
4974
+ });
4975
+ return;
4976
+ }
4977
+ try {
4978
+ await def.handler(context, data);
4979
+ } catch (error) {
4980
+ if (def.onError != null) try {
4981
+ await def.onError(context, error, data);
4982
+ } catch (onErrorError) {
4983
+ logger.error("onError for custom task {taskName} threw:\n{error}", {
4984
+ taskName: message.taskName,
4985
+ error: onErrorError
4986
+ });
4987
+ }
4988
+ const queue = this.resolveTaskQueue(def.name);
4989
+ if (queue?.nativeRetrial) throw error;
4990
+ const retryPolicy = def.retryPolicy ?? this.taskRetryPolicy;
4991
+ let elapsedTime = Temporal.Duration.from({ seconds: 0 });
4992
+ try {
4993
+ elapsedTime = Temporal.Instant.from(message.started).until(Temporal.Now.instant());
4994
+ } catch (parseError) {
4995
+ logger.error("Custom task {taskName} has an unparsable started time {started}; treating elapsedTime as zero:\n{error}", {
4996
+ taskName: message.taskName,
4997
+ started: message.started,
4998
+ error: parseError
4999
+ });
5000
+ }
5001
+ const delay = retryPolicy({
5002
+ elapsedTime,
5003
+ attempts: message.attempt
5004
+ });
5005
+ if (delay != null && queue != null) {
5006
+ logger.error("Custom task {taskName} failed (attempt #{attempt}); retry...:\n{error}", {
5007
+ taskName: message.taskName,
5008
+ attempt: message.attempt,
5009
+ error
5010
+ });
5011
+ const retryMessage = {
5012
+ ...message,
5013
+ attempt: message.attempt + 1
5014
+ };
5015
+ await queue.enqueue(retryMessage, {
5016
+ delay: clampNegativeDelay(delay),
5017
+ orderingKey: message.orderingKey
5018
+ });
5019
+ } else logger.error("Custom task {taskName} failed after {attempt} attempts; giving up:\n{error}", {
5020
+ taskName: message.taskName,
5021
+ attempt: message.attempt,
5022
+ error
5023
+ });
5024
+ }
5025
+ }
4808
5026
  startQueue(contextData, options = {}) {
4809
5027
  return this._startQueueInternal(contextData, options.signal, options.queue);
4810
5028
  }
@@ -5381,6 +5599,7 @@ var ContextImpl = class ContextImpl {
5381
5599
  documentLoader;
5382
5600
  contextLoader;
5383
5601
  invokedFromActorKeyPairsDispatcher;
5602
+ #codec;
5384
5603
  constructor({ url, federation, data, documentLoader, contextLoader, invokedFromActorKeyPairsDispatcher }) {
5385
5604
  this.url = url;
5386
5605
  this.federation = federation;
@@ -5389,6 +5608,15 @@ var ContextImpl = class ContextImpl {
5389
5608
  this.contextLoader = contextLoader;
5390
5609
  this.invokedFromActorKeyPairsDispatcher = invokedFromActorKeyPairsDispatcher;
5391
5610
  }
5611
+ /**
5612
+ * A {@link TaskCodec} bound to this context's loaders, used to encode
5613
+ * and decode custom task payloads. Lazily created and cached so a context
5614
+ * that never enqueues or dispatches a task pays nothing.
5615
+ * @internal
5616
+ */
5617
+ get codec() {
5618
+ return this.#codec ??= new TaskCodec(this);
5619
+ }
5392
5620
  clone(data) {
5393
5621
  return new ContextImpl({
5394
5622
  url: this.url,
@@ -5707,6 +5935,45 @@ var ContextImpl = class ContextImpl {
5707
5935
  allowPrivateAddress: this.federation.allowPrivateAddress
5708
5936
  });
5709
5937
  }
5938
+ async enqueueTask(task, data, options = {}) {
5939
+ await this.#enqueueTasks(task, [data], options);
5940
+ }
5941
+ async enqueueTaskMany(task, payloads, options = {}) {
5942
+ await this.#enqueueTasks(task, payloads, options);
5943
+ }
5944
+ async #enqueueTasks(task, items, options) {
5945
+ const def = this.federation.taskDefinitions.get(task.name);
5946
+ 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().`);
5947
+ const queue = this.federation.resolveTaskQueue(task.name);
5948
+ if (queue == null) throw new TypeError("No message queue is configured for tasks; pass `queue` to createFederation() or to defineTask().");
5949
+ if (items.length < 1) return;
5950
+ const delay = options.delay == null ? void 0 : Temporal.Duration.from(options.delay);
5951
+ const messages = await Promise.all(items.map(this.#encodeTaskMessage(task, options)));
5952
+ if (!this.federation.manuallyStartQueue) this.federation._startQueueInternal(this.data);
5953
+ const enqueueOptions = {
5954
+ delay,
5955
+ orderingKey: options.orderingKey
5956
+ };
5957
+ if (messages.length === 1) await queue.enqueue(messages[0], enqueueOptions);
5958
+ else if (queue.enqueueMany != null) await queue.enqueueMany(messages, enqueueOptions);
5959
+ else await Promise.all(messages.map((m) => queue.enqueue(m, enqueueOptions)));
5960
+ }
5961
+ #encodeTaskMessage = (task, options) => async (data) => {
5962
+ const encoded = await this.codec.encode(task.schema, data);
5963
+ const carrier = {};
5964
+ propagation.inject(context.active(), carrier);
5965
+ return {
5966
+ type: "task",
5967
+ id: crypto.randomUUID(),
5968
+ baseUrl: this.origin,
5969
+ taskName: task.name,
5970
+ data: encoded,
5971
+ started: Temporal.Now.instant().toString(),
5972
+ attempt: 0,
5973
+ orderingKey: options.orderingKey,
5974
+ traceContext: carrier
5975
+ };
5976
+ };
5710
5977
  sendActivity(sender, recipients, activity, options = {}) {
5711
5978
  return this.tracerProvider.getTracer(name, version).startActiveSpan(this.federation.outboxQueue == null || options.immediate ? "activitypub.outbox" : "activitypub.fanout", {
5712
5979
  kind: this.federation.outboxQueue == null || options.immediate ? SpanKind.CLIENT : SpanKind.PRODUCER,