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