@fedify/fedify 2.4.0-dev.1634 → 2.4.0-pr.934.40

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 (71) hide show
  1. package/README.md +3 -0
  2. package/dist/{builder-DYAAegyy.mjs → builder-BBPwtB2v.mjs} +2 -2
  3. package/dist/compat/mod.d.cts +1 -1
  4. package/dist/compat/mod.d.ts +1 -1
  5. package/dist/compat/transformers.test.mjs +1 -1
  6. package/dist/{context-lxX6I7as.d.cts → context-C05Rgxmu.d.cts} +1 -1
  7. package/dist/{context-Zii1f5xS.d.ts → context-Cjnr0ig1.d.ts} +1 -1
  8. package/dist/{deno-C30u61BX.mjs → deno-D3c0o_N0.mjs} +1 -1
  9. package/dist/{docloader-C8JfZ3bD.mjs → docloader-BCecHIxU.mjs} +2 -2
  10. package/dist/federation/builder.test.mjs +1 -1
  11. package/dist/federation/handler.test.mjs +3 -5
  12. package/dist/federation/idempotency.test.mjs +2 -2
  13. package/dist/federation/metrics.test.mjs +1 -1
  14. package/dist/federation/middleware.test.mjs +7 -7
  15. package/dist/federation/mod.cjs +288 -9
  16. package/dist/federation/mod.d.cts +3 -3
  17. package/dist/federation/mod.d.ts +3 -3
  18. package/dist/federation/mod.js +279 -1
  19. package/dist/federation/mq.test.mjs +20 -1
  20. package/dist/federation/send.test.mjs +3 -3
  21. package/dist/federation/tasks/enqueue.test.mjs +39 -2
  22. package/dist/federation/tasks/tasks.test.mjs +3 -3
  23. package/dist/federation/temporal.test.mjs +1 -1
  24. package/dist/federation/webfinger.test.mjs +1 -1
  25. package/dist/{http-D_cl_PbN.js → http-Ctb_KhZ8.js} +1 -1
  26. package/dist/{http-DJEaNMSB.mjs → http-Dm0JAUa_.mjs} +3 -3
  27. package/dist/{http-DiMJDFtd.cjs → http-jr0TkgZe.cjs} +1 -1
  28. package/dist/{key-C1tsoBAW.mjs → key-C_gNKtxm.mjs} +2 -2
  29. package/dist/{kv-cache-D9Tia3Td.mjs → kv-cache-B7H7lZ84.mjs} +1 -1
  30. package/dist/{kv-cache-EL9jIFqA.js → kv-cache-BxUZRBC2.js} +1 -1
  31. package/dist/{kv-cache-BInLi0Ro.cjs → kv-cache-D-hbjJAD.cjs} +1 -1
  32. package/dist/{ld-CDBNQmZD.mjs → ld-DAPAVL_7.mjs} +3 -3
  33. package/dist/{metrics-De7b7Hgb.mjs → metrics-BMaatuZi.mjs} +1 -1
  34. package/dist/{metrics-CoUJLQhZ.d.cts → metrics-DN13mCAw.d.cts} +19 -1
  35. package/dist/{metrics-Dc4jR-IC.d.ts → metrics-v_e0n5GQ.d.ts} +19 -1
  36. package/dist/{middleware-BPHpdYl4.cjs → middleware-BW7WQltk.cjs} +40 -325
  37. package/dist/{middleware-8oyKaRYF.js → middleware-CfZR_riY.js} +6 -278
  38. package/dist/{middleware-DloBgcWp.mjs → middleware-DfPBAh9L.mjs} +1 -1
  39. package/dist/{middleware-CPlgpBML.mjs → middleware-O2LHXFEk.mjs} +14 -15
  40. package/dist/{mod-BtEaIEcD.d.cts → mod-BrpVsSfY.d.cts} +1 -1
  41. package/dist/{mod-DLc2Ot6f.d.ts → mod-BsCE5Ihq.d.ts} +1 -1
  42. package/dist/{mod-UMmveiO0.d.ts → mod-CNef4in0.d.ts} +1 -1
  43. package/dist/{mod-gOlPDuT8.d.cts → mod-DdONo-ep.d.cts} +1 -1
  44. package/dist/mod.cjs +6 -6
  45. package/dist/mod.d.cts +4 -4
  46. package/dist/mod.d.ts +4 -4
  47. package/dist/mod.js +5 -5
  48. package/dist/{mq-llHKivST.mjs → mq-Bo7Etgh6.mjs} +6 -0
  49. package/dist/nodeinfo/handler.test.mjs +1 -1
  50. package/dist/{owner-CB3dPva0.mjs → owner-Dcx-czqn.mjs} +2 -2
  51. package/dist/{proof-BqDtIxGz.mjs → proof-BkAxvrjt.mjs} +3 -3
  52. package/dist/{proof-DT5SgzkH.js → proof-D-H_PnJW.js} +1 -1
  53. package/dist/{proof-BI4Wn6Ur.cjs → proof-NY0D8Oux.cjs} +1 -1
  54. package/dist/{send-ClWims8x.mjs → send-D_j7E-oC.mjs} +3 -3
  55. package/dist/sig/http.test.mjs +2 -2
  56. package/dist/sig/key.test.mjs +1 -1
  57. package/dist/sig/ld.test.mjs +2 -2
  58. package/dist/sig/mod.cjs +2 -2
  59. package/dist/sig/mod.js +2 -2
  60. package/dist/sig/owner.test.mjs +1 -1
  61. package/dist/sig/proof.test.mjs +1 -1
  62. package/dist/{temporal-DyITEDcU.mjs → temporal-yE5EcfNX.mjs} +1 -1
  63. package/dist/testing/mod.d.mts +14 -1
  64. package/dist/utils/docloader.test.mjs +2 -2
  65. package/dist/utils/kv-cache.test.mjs +1 -1
  66. package/dist/utils/mod.cjs +1 -1
  67. package/dist/utils/mod.d.cts +1 -1
  68. package/dist/utils/mod.d.ts +1 -1
  69. package/dist/utils/mod.js +1 -1
  70. package/package.json +6 -6
  71. package/skills/fedify/SKILL.md +462 -0
@@ -1,11 +1,11 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  const require_chunk = require("./chunk-Czpeue4M.cjs");
3
3
  const require_transformers = require("./transformers-DGwl-hD1.cjs");
4
- const require_http = require("./http-DiMJDFtd.cjs");
4
+ const require_http = require("./http-jr0TkgZe.cjs");
5
5
  const require_temporal_polyfill = require("./temporal-polyfill-BCdTHexd.cjs");
6
- const require_proof = require("./proof-BI4Wn6Ur.cjs");
6
+ const require_proof = require("./proof-NY0D8Oux.cjs");
7
7
  const require_types = require("./types-CPym83zc.cjs");
8
- const require_kv_cache = require("./kv-cache-BInLi0Ro.cjs");
8
+ const require_kv_cache = require("./kv-cache-D-hbjJAD.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");
@@ -723,7 +723,7 @@ function createFederationBuilder() {
723
723
  }
724
724
  //#endregion
725
725
  //#region src/federation/circuit-breaker.ts
726
- const Temporal$8 = require_temporal_polyfill.Temporal;
726
+ const Temporal$7 = require_temporal_polyfill.Temporal;
727
727
  const MAX_CUSTOM_FAILURE_HISTORY = 100;
728
728
  /**
729
729
  * Tracks reachability state for remote outbox delivery hosts.
@@ -739,7 +739,7 @@ var CircuitBreaker = class {
739
739
  this.#kv = options.kv;
740
740
  this.#prefix = options.prefix;
741
741
  this.#options = normalizeCircuitBreakerOptions(options.options ?? {});
742
- this.#now = options.now ?? (() => Temporal$8.Now.instant());
742
+ this.#now = options.now ?? (() => Temporal$7.Now.instant());
743
743
  this.#stateChangeObserver = options.stateChangeObserver;
744
744
  }
745
745
  get options() {
@@ -752,7 +752,7 @@ var CircuitBreaker = class {
752
752
  async beforeSend(remoteHost, message) {
753
753
  const heldSince = parseHeldSince(message.circuitHeldSince);
754
754
  const now = this.#now();
755
- if (heldSince != null && Temporal$8.Instant.compare(heldSince.add(this.#options.heldActivityTtl), now) <= 0) return {
755
+ if (heldSince != null && Temporal$7.Instant.compare(heldSince.add(this.#options.heldActivityTtl), now) <= 0) return {
756
756
  type: "drop",
757
757
  heldSince
758
758
  };
@@ -764,12 +764,12 @@ var CircuitBreaker = class {
764
764
  probe: false
765
765
  };
766
766
  if (oldState.state === "half-open") {
767
- const halfOpened = oldState.halfOpened == null ? void 0 : Temporal$8.Instant.from(oldState.halfOpened);
767
+ const halfOpened = oldState.halfOpened == null ? void 0 : Temporal$7.Instant.from(oldState.halfOpened);
768
768
  if (halfOpened != null) {
769
769
  const staleAt = halfOpened.add(this.#options.recoveryDelay);
770
- if (Temporal$8.Instant.compare(now, staleAt) < 0) {
770
+ if (Temporal$7.Instant.compare(now, staleAt) < 0) {
771
771
  const releaseAt = now.add(this.#options.releaseInterval);
772
- const retryAt = Temporal$8.Instant.compare(releaseAt, staleAt) < 0 ? releaseAt : staleAt;
772
+ const retryAt = Temporal$7.Instant.compare(releaseAt, staleAt) < 0 ? releaseAt : staleAt;
773
773
  const cappedRetryAt = this.#capHeldRetryAt(now, heldSince, retryAt);
774
774
  return {
775
775
  type: "hold",
@@ -791,8 +791,8 @@ var CircuitBreaker = class {
791
791
  lastConflictingState = "half-open";
792
792
  continue;
793
793
  }
794
- const probeAt = (oldState.opened == null ? now : Temporal$8.Instant.from(oldState.opened)).add(this.#options.recoveryDelay);
795
- if (Temporal$8.Instant.compare(now, probeAt) < 0) {
794
+ const probeAt = (oldState.opened == null ? now : Temporal$7.Instant.from(oldState.opened)).add(this.#options.recoveryDelay);
795
+ if (Temporal$7.Instant.compare(now, probeAt) < 0) {
796
796
  const retryAt = this.#capHeldRetryAt(now, heldSince, probeAt);
797
797
  return {
798
798
  type: "hold",
@@ -855,7 +855,7 @@ var CircuitBreaker = class {
855
855
  for (let attempt = 0; attempt < 10; attempt++) {
856
856
  const oldState = await this.#get(remoteHost);
857
857
  if (oldState?.state === "open") return void 0;
858
- const oldFailures = oldState?.failures.map(Temporal$8.Instant.from) ?? [];
858
+ const oldFailures = oldState?.failures.map(Temporal$7.Instant.from) ?? [];
859
859
  const failures = this.#options.pruneFailures([...oldFailures, now], now);
860
860
  let newState;
861
861
  let transition;
@@ -905,7 +905,7 @@ var CircuitBreaker = class {
905
905
  }
906
906
  #capHeldRetryAt(now, heldSince, retryAt) {
907
907
  const expiresAt = (heldSince ?? now).add(this.#options.heldActivityTtl);
908
- return Temporal$8.Instant.compare(expiresAt, retryAt) < 0 ? expiresAt : retryAt;
908
+ return Temporal$7.Instant.compare(expiresAt, retryAt) < 0 ? expiresAt : retryAt;
909
909
  }
910
910
  async #get(remoteHost) {
911
911
  return parseCircuitBreakerKvState(await this.#kv.get(this.#key(remoteHost)));
@@ -976,13 +976,13 @@ function normalizeCircuitBreakerOptions(options) {
976
976
  assertPositiveDuration(failureWindow, "failureWindow");
977
977
  pruneFailures = (timestamps, now) => {
978
978
  const earliest = now.subtract(failureWindow);
979
- return timestamps.filter((timestamp) => Temporal$8.Instant.compare(timestamp, earliest) >= 0).slice(-failureThreshold);
979
+ return timestamps.filter((timestamp) => Temporal$7.Instant.compare(timestamp, earliest) >= 0).slice(-failureThreshold);
980
980
  };
981
981
  failure = (timestamps) => {
982
982
  if (timestamps.length < failureThreshold) return false;
983
983
  const first = timestamps[timestamps.length - failureThreshold];
984
984
  const last = timestamps[timestamps.length - 1];
985
- return Temporal$8.Duration.compare(first.until(last), failureWindow) <= 0;
985
+ return Temporal$7.Duration.compare(first.until(last), failureWindow) <= 0;
986
986
  };
987
987
  } else {
988
988
  failure = options.failure;
@@ -999,19 +999,19 @@ function normalizeCircuitBreakerOptions(options) {
999
999
  };
1000
1000
  }
1001
1001
  function toInstantDuration(duration) {
1002
- const parsed = Temporal$8.Duration.from(duration);
1003
- return Temporal$8.Duration.from({ milliseconds: Math.trunc(parsed.total({
1002
+ const parsed = Temporal$7.Duration.from(duration);
1003
+ return Temporal$7.Duration.from({ milliseconds: Math.trunc(parsed.total({
1004
1004
  unit: "millisecond",
1005
- relativeTo: Temporal$8.PlainDateTime.from("2026-01-01T00:00:00")
1005
+ relativeTo: Temporal$7.PlainDateTime.from("2026-01-01T00:00:00")
1006
1006
  })) });
1007
1007
  }
1008
1008
  function assertPositiveDuration(duration, name) {
1009
- if (Temporal$8.Duration.compare(duration, { seconds: 0 }) <= 0) throw new RangeError(`${name} must be a positive duration.`);
1009
+ if (Temporal$7.Duration.compare(duration, { seconds: 0 }) <= 0) throw new RangeError(`${name} must be a positive duration.`);
1010
1010
  }
1011
1011
  function parseHeldSince(value) {
1012
1012
  if (value == null) return void 0;
1013
1013
  try {
1014
- return Temporal$8.Instant.from(value);
1014
+ return Temporal$7.Instant.from(value);
1015
1015
  } catch (error) {
1016
1016
  (0, _logtape_logtape.getLogger)([
1017
1017
  "fedify",
@@ -1035,7 +1035,7 @@ function parseCircuitBreakerKvState(value) {
1035
1035
  const isInstantString = (v) => {
1036
1036
  if (typeof v !== "string") return false;
1037
1037
  try {
1038
- Temporal$8.Instant.from(v);
1038
+ Temporal$7.Instant.from(v);
1039
1039
  return true;
1040
1040
  } catch {
1041
1041
  return false;
@@ -1095,7 +1095,7 @@ async function buildCollectionSynchronizationHeader(collectionId, actorIds) {
1095
1095
  }
1096
1096
  //#endregion
1097
1097
  //#region src/federation/inbox.ts
1098
- const Temporal$7 = require_temporal_polyfill.Temporal;
1098
+ const Temporal$6 = require_temporal_polyfill.Temporal;
1099
1099
  async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
1100
1100
  const logger = (0, _logtape_logtape.getLogger)([
1101
1101
  "fedify",
@@ -1242,7 +1242,7 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
1242
1242
  return "error";
1243
1243
  }
1244
1244
  require_http.recordInboxActivity(meterProvider, "processed", (0, _fedify_vocab.getTypeId)(activity).href);
1245
- if (cacheKey != null) await kv.set(cacheKey, true, { ttl: Temporal$7.Duration.from({ days: 1 }) });
1245
+ if (cacheKey != null) await kv.set(cacheKey, true, { ttl: Temporal$6.Duration.from({ days: 1 }) });
1246
1246
  logger.info("Activity {activityId} has been processed.", {
1247
1247
  activityId: activity.id?.href,
1248
1248
  activity: json,
@@ -1254,7 +1254,7 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
1254
1254
  }
1255
1255
  //#endregion
1256
1256
  //#region src/federation/keycache.ts
1257
- const Temporal$6 = require_temporal_polyfill.Temporal;
1257
+ const Temporal$5 = require_temporal_polyfill.Temporal;
1258
1258
  var KvKeyCache = class {
1259
1259
  kv;
1260
1260
  prefix;
@@ -1265,7 +1265,7 @@ var KvKeyCache = class {
1265
1265
  this.kv = kv;
1266
1266
  this.prefix = prefix;
1267
1267
  this.options = options;
1268
- this.unavailableKeyTtl = options.unavailableKeyTtl ?? Temporal$6.Duration.from({ minutes: 10 });
1268
+ this.unavailableKeyTtl = options.unavailableKeyTtl ?? Temporal$5.Duration.from({ minutes: 10 });
1269
1269
  this.nullKeys = /* @__PURE__ */ new Map();
1270
1270
  }
1271
1271
  #getFetchErrorKey(keyId) {
@@ -1278,13 +1278,13 @@ var KvKeyCache = class {
1278
1278
  async get(keyId) {
1279
1279
  const negativeExpiration = this.nullKeys.get(keyId.href);
1280
1280
  if (negativeExpiration != null) {
1281
- if (Temporal$6.Now.instant().until(negativeExpiration).sign >= 0) return null;
1281
+ if (Temporal$5.Now.instant().until(negativeExpiration).sign >= 0) return null;
1282
1282
  this.nullKeys.delete(keyId.href);
1283
1283
  }
1284
1284
  const serialized = await this.kv.get([...this.prefix, keyId.href]);
1285
1285
  if (serialized === void 0) return void 0;
1286
1286
  if (serialized === null) {
1287
- this.nullKeys.set(keyId.href, Temporal$6.Now.instant().add(this.unavailableKeyTtl));
1287
+ this.nullKeys.set(keyId.href, Temporal$5.Now.instant().add(this.unavailableKeyTtl));
1288
1288
  return null;
1289
1289
  }
1290
1290
  try {
@@ -1300,7 +1300,7 @@ var KvKeyCache = class {
1300
1300
  }
1301
1301
  async set(keyId, key) {
1302
1302
  if (key == null) {
1303
- this.nullKeys.set(keyId.href, Temporal$6.Now.instant().add(this.unavailableKeyTtl));
1303
+ this.nullKeys.set(keyId.href, Temporal$5.Now.instant().add(this.unavailableKeyTtl));
1304
1304
  await this.kv.set([...this.prefix, keyId.href], null, { ttl: this.unavailableKeyTtl });
1305
1305
  return;
1306
1306
  }
@@ -1439,7 +1439,7 @@ function acceptsJsonLd(request) {
1439
1439
  }
1440
1440
  //#endregion
1441
1441
  //#region src/federation/temporal.ts
1442
- const Temporal$5 = require_temporal_polyfill.Temporal;
1442
+ const Temporal$4 = require_temporal_polyfill.Temporal;
1443
1443
  function isPlainObject$1(value) {
1444
1444
  return typeof value === "object" && value != null && !Array.isArray(value);
1445
1445
  }
@@ -1449,7 +1449,7 @@ function normalizeDateTimeLiteral(value) {
1449
1449
  function isMalformedDateTimeLiteral(value) {
1450
1450
  if (typeof value !== "string") return false;
1451
1451
  try {
1452
- Temporal$5.Instant.from(normalizeDateTimeLiteral(value));
1452
+ Temporal$4.Instant.from(normalizeDateTimeLiteral(value));
1453
1453
  return false;
1454
1454
  } catch {
1455
1455
  return true;
@@ -1458,7 +1458,7 @@ function isMalformedDateTimeLiteral(value) {
1458
1458
  function isMalformedDurationLiteral(value) {
1459
1459
  if (typeof value !== "string") return false;
1460
1460
  try {
1461
- Temporal$5.Duration.from(value);
1461
+ Temporal$4.Duration.from(value);
1462
1462
  return false;
1463
1463
  } catch {
1464
1464
  return true;
@@ -1529,7 +1529,7 @@ async function hasMalformedKnownTemporalLiteral(value, contextLoader) {
1529
1529
  }
1530
1530
  //#endregion
1531
1531
  //#region src/federation/handler.ts
1532
- const Temporal$4 = require_temporal_polyfill.Temporal;
1532
+ const Temporal$3 = require_temporal_polyfill.Temporal;
1533
1533
  const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
1534
1534
  function isRemoteContextLoadingFailure$1(error) {
1535
1535
  return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
@@ -3284,7 +3284,7 @@ async function buildAcceptSignatureHeader(policy, kv, noncePrefix) {
3284
3284
  }
3285
3285
  async function setKey(kv, key, policy) {
3286
3286
  const seconds = policy.nonceTtlSeconds ?? 300;
3287
- const ttl = Temporal$4.Duration.from({ seconds });
3287
+ const ttl = Temporal$3.Duration.from({ seconds });
3288
3288
  await kv.set(key, true, { ttl });
3289
3289
  }
3290
3290
  const DEF_COMPONENTS = [
@@ -3759,7 +3759,7 @@ function serializeDataPointType(dataPointType) {
3759
3759
  }
3760
3760
  //#endregion
3761
3761
  //#region src/federation/retry.ts
3762
- const Temporal$3 = require_temporal_polyfill.Temporal;
3762
+ const Temporal$2 = require_temporal_polyfill.Temporal;
3763
3763
  /**
3764
3764
  * Creates an exponential backoff retry policy. The delay between retries
3765
3765
  * starts at the `initialDelay` and is multiplied by the `factor` for each
@@ -3771,8 +3771,8 @@ const Temporal$3 = require_temporal_polyfill.Temporal;
3771
3771
  * @since 0.12.0
3772
3772
  */
3773
3773
  function createExponentialBackoffPolicy(options = {}) {
3774
- const initialDelay = Temporal$3.Duration.from(options.initialDelay ?? { seconds: 1 });
3775
- const maxDelay = Temporal$3.Duration.from(options.maxDelay ?? { hours: 12 });
3774
+ const initialDelay = Temporal$2.Duration.from(options.initialDelay ?? { seconds: 1 });
3775
+ const maxDelay = Temporal$2.Duration.from(options.maxDelay ?? { hours: 12 });
3776
3776
  const maxAttempts = options.maxAttempts ?? 10;
3777
3777
  const factor = options.factor ?? 2;
3778
3778
  const jitter = options.jitter ?? true;
@@ -3784,8 +3784,8 @@ function createExponentialBackoffPolicy(options = {}) {
3784
3784
  milliseconds *= 1 + Math.random();
3785
3785
  milliseconds = Math.round(milliseconds);
3786
3786
  }
3787
- const delay = Temporal$3.Duration.from({ milliseconds });
3788
- return Temporal$3.Duration.compare(delay, maxDelay) > 0 ? maxDelay : delay;
3787
+ const delay = Temporal$2.Duration.from({ milliseconds });
3788
+ return Temporal$2.Duration.compare(delay, maxDelay) > 0 ? maxDelay : delay;
3789
3789
  };
3790
3790
  }
3791
3791
  //#endregion
@@ -3907,279 +3907,6 @@ const classReviver = (filter, init, set) => (seen, revive, node) => {
3907
3907
  })();
3908
3908
  };
3909
3909
  //#endregion
3910
- //#region src/federation/mq.ts
3911
- const Temporal$2 = require_temporal_polyfill.Temporal;
3912
- /**
3913
- * A message queue that processes messages in the same process.
3914
- * Do not use this in production as it does neither persist messages nor
3915
- * distribute them across multiple processes.
3916
- *
3917
- * @since 0.5.0
3918
- */
3919
- var InProcessMessageQueue = class {
3920
- #messages;
3921
- #monitors;
3922
- #pollIntervalMs;
3923
- #delayedMessages;
3924
- /**
3925
- * Tracks which ordering keys are currently being processed to ensure
3926
- * sequential processing for messages with the same key.
3927
- */
3928
- #processingKeys;
3929
- /**
3930
- * In-process message queue does not provide native retry mechanisms.
3931
- * @since 1.7.0
3932
- */
3933
- nativeRetrial = false;
3934
- /**
3935
- * In-process message queue does not deduplicate messages natively.
3936
- * @since 2.4.0
3937
- */
3938
- nativeDeduplication = false;
3939
- /**
3940
- * Constructs a new {@link InProcessMessageQueue} with the given options.
3941
- * @param options Additional options for the in-process message queue.
3942
- */
3943
- constructor(options = {}) {
3944
- this.#messages = [];
3945
- this.#monitors = {};
3946
- this.#pollIntervalMs = Temporal$2.Duration.from(options.pollInterval ?? { seconds: 5 }).total("millisecond");
3947
- this.#delayedMessages = 0;
3948
- this.#processingKeys = /* @__PURE__ */ new Set();
3949
- }
3950
- enqueue(message, options) {
3951
- const delay = options?.delay == null ? 0 : Math.max(options.delay.total("millisecond"), 0);
3952
- if (delay > 0) {
3953
- this.#delayedMessages++;
3954
- setTimeout(() => {
3955
- this.#delayedMessages--;
3956
- this.#enqueueReady(message, options);
3957
- }, delay);
3958
- return Promise.resolve();
3959
- }
3960
- this.#enqueueReady(message, options);
3961
- return Promise.resolve();
3962
- }
3963
- #enqueueReady(message, options) {
3964
- const orderingKey = options?.orderingKey ?? null;
3965
- this.#messages.push({
3966
- message,
3967
- orderingKey
3968
- });
3969
- this.#notifyMonitors();
3970
- }
3971
- #notifyMonitors() {
3972
- for (const monitorId in this.#monitors) this.#monitors[monitorId]();
3973
- }
3974
- enqueueMany(messages, options) {
3975
- if (messages.length === 0) return Promise.resolve();
3976
- const delay = options?.delay == null ? 0 : Math.max(options.delay.total("millisecond"), 0);
3977
- if (delay > 0) {
3978
- const delayedCount = messages.length;
3979
- const deferredMessages = [...messages];
3980
- this.#delayedMessages += delayedCount;
3981
- setTimeout(() => {
3982
- this.#delayedMessages -= delayedCount;
3983
- this.#enqueueManyReady(deferredMessages, options);
3984
- }, delay);
3985
- return Promise.resolve();
3986
- }
3987
- this.#enqueueManyReady(messages, options);
3988
- return Promise.resolve();
3989
- }
3990
- #enqueueManyReady(messages, options) {
3991
- const orderingKey = options?.orderingKey ?? null;
3992
- for (const message of messages) this.#messages.push({
3993
- message,
3994
- orderingKey
3995
- });
3996
- this.#notifyMonitors();
3997
- }
3998
- async listen(handler, options = {}) {
3999
- const signal = options.signal;
4000
- while (signal == null || !signal.aborted) {
4001
- const idx = this.#messages.findIndex((m) => m.orderingKey == null || !this.#processingKeys.has(m.orderingKey));
4002
- if (idx >= 0) {
4003
- const { message, orderingKey } = this.#messages.splice(idx, 1)[0];
4004
- if (orderingKey != null) this.#processingKeys.add(orderingKey);
4005
- try {
4006
- await handler(message);
4007
- } finally {
4008
- if (orderingKey != null) this.#processingKeys.delete(orderingKey);
4009
- }
4010
- } else if (this.#messages.length === 0) await this.#wait(this.#pollIntervalMs, signal);
4011
- else await this.#wait(10, signal);
4012
- }
4013
- }
4014
- getDepth() {
4015
- const ready = this.#messages.length;
4016
- const delayed = this.#delayedMessages;
4017
- return Promise.resolve({
4018
- queued: ready + delayed,
4019
- ready,
4020
- delayed
4021
- });
4022
- }
4023
- #wait(ms, signal) {
4024
- let timer = null;
4025
- return Promise.any([new Promise((resolve) => {
4026
- signal?.addEventListener("abort", () => {
4027
- if (timer != null) clearTimeout(timer);
4028
- resolve();
4029
- }, { once: true });
4030
- const monitorId = crypto.randomUUID();
4031
- this.#monitors[monitorId] = () => {
4032
- delete this.#monitors[monitorId];
4033
- if (timer != null) clearTimeout(timer);
4034
- resolve();
4035
- };
4036
- }), new Promise((resolve) => timer = setTimeout(resolve, ms))]);
4037
- }
4038
- };
4039
- /**
4040
- * A message queue that processes messages in parallel. It takes another
4041
- * {@link MessageQueue}, and processes messages in parallel up to a certain
4042
- * number of workers.
4043
- *
4044
- * Actually, it's rather a decorator than a queue itself.
4045
- *
4046
- * Note that the workers do not run in truly parallel, in the sense that they
4047
- * are not running in separate threads or processes. They are running in the
4048
- * same process, but are scheduled to run in parallel. Hence, this is useful
4049
- * for I/O-bound tasks, but not for CPU-bound tasks, which is okay for Fedify's
4050
- * workloads.
4051
- *
4052
- * When using `ParallelMessageQueue`, the ordering guarantee is preserved
4053
- * *only if* the underlying queue implementation delivers messages in a wrapper
4054
- * format that includes the `__fedify_ordering_key__` property. Currently,
4055
- * only `DenoKvMessageQueue` and `WorkersMessageQueue` use this format.
4056
- * For other queue implementations (e.g., `InProcessMessageQueue`,
4057
- * `RedisMessageQueue`, `PostgresMessageQueue`, `SqliteMessageQueue`,
4058
- * `AmqpMessageQueue`), the ordering key cannot be detected by
4059
- * `ParallelMessageQueue`, so ordering guarantees are handled by those
4060
- * implementations directly rather than at the `ParallelMessageQueue` level.
4061
- *
4062
- * Messages with the same ordering key will never be processed concurrently
4063
- * by different workers, ensuring sequential processing within each key.
4064
- * Messages with different ordering keys (or no ordering key) can still be
4065
- * processed in parallel.
4066
- *
4067
- * @since 1.0.0
4068
- */
4069
- var ParallelMessageQueue = class ParallelMessageQueue {
4070
- queue;
4071
- workers;
4072
- /**
4073
- * Inherits the native retry capability from the wrapped queue.
4074
- * @since 1.7.0
4075
- */
4076
- nativeRetrial;
4077
- /**
4078
- * Inherits the native deduplication capability from the wrapped queue.
4079
- * @since 2.4.0
4080
- */
4081
- nativeDeduplication;
4082
- getDepth;
4083
- /**
4084
- * Tracks which ordering keys are currently being processed to ensure
4085
- * sequential processing for messages with the same key.
4086
- */
4087
- #processingKeys = /* @__PURE__ */ new Set();
4088
- /**
4089
- * Pending messages waiting for their ordering key to become available.
4090
- */
4091
- #pendingMessages = [];
4092
- /**
4093
- * Constructs a new {@link ParallelMessageQueue} with the given queue and
4094
- * number of workers.
4095
- * @param queue The message queue to use under the hood. Note that
4096
- * {@link ParallelMessageQueue} cannot be nested.
4097
- * @param workers The number of workers to process messages in parallel.
4098
- * @throws {TypeError} If the given queue is an instance of
4099
- * {@link ParallelMessageQueue}.
4100
- */
4101
- constructor(queue, workers) {
4102
- if (queue instanceof ParallelMessageQueue) throw new TypeError("Cannot nest ParallelMessageQueue.");
4103
- this.queue = queue;
4104
- this.workers = workers;
4105
- this.nativeRetrial = queue.nativeRetrial;
4106
- this.nativeDeduplication = queue.nativeDeduplication;
4107
- if (queue.getDepth != null) this.getDepth = () => queue.getDepth();
4108
- }
4109
- enqueue(message, options) {
4110
- return this.queue.enqueue(message, options);
4111
- }
4112
- async enqueueMany(messages, options) {
4113
- if (this.queue.enqueueMany == null) {
4114
- if (messages.length === 1) {
4115
- await this.queue.enqueue(messages[0], options);
4116
- return;
4117
- }
4118
- 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.");
4119
- const errors = (await Promise.allSettled(messages.map((message) => this.queue.enqueue(message, options)))).filter((r) => r.status === "rejected").map((r) => r.reason);
4120
- if (errors.length > 1) throw new AggregateError(errors, "Failed to enqueue messages.");
4121
- else if (errors.length === 1) throw errors[0];
4122
- return;
4123
- }
4124
- await this.queue.enqueueMany(messages, options);
4125
- }
4126
- /**
4127
- * Extracts ordering key from a message if present.
4128
- *
4129
- * This method only works for queue implementations that deliver messages
4130
- * in the wrapper format with `__fedify_ordering_key__` property. Currently,
4131
- * only `DenoKvMessageQueue` and `WorkersMessageQueue` use this format.
4132
- *
4133
- * For other queue implementations (`InProcessMessageQueue`,
4134
- * `RedisMessageQueue`, `PostgresMessageQueue`, `SqliteMessageQueue`,
4135
- * `AmqpMessageQueue`), messages are delivered as raw payloads without the
4136
- * wrapper, so the ordering key cannot be detected here. Those
4137
- * implementations handle ordering guarantees internally.
4138
- */
4139
- #extractOrderingKey(message) {
4140
- if (message != null && typeof message === "object") {
4141
- if ("__fedify_ordering_key__" in message) return message.__fedify_ordering_key__;
4142
- }
4143
- }
4144
- listen(handler, options = {}) {
4145
- const workers = /* @__PURE__ */ new Map();
4146
- return this.queue.listen(async (message) => {
4147
- while (workers.size >= this.workers) {
4148
- const consumedId = await Promise.any(workers.values());
4149
- workers.delete(consumedId);
4150
- }
4151
- const workerId = crypto.randomUUID();
4152
- const orderingKey = this.#extractOrderingKey(message);
4153
- if (orderingKey != null && this.#processingKeys.has(orderingKey)) await new Promise((resolve) => {
4154
- this.#pendingMessages.push({
4155
- message,
4156
- orderingKey,
4157
- resolve
4158
- });
4159
- });
4160
- if (orderingKey != null) this.#processingKeys.add(orderingKey);
4161
- const promise = this.#work(workerId, handler, message, orderingKey);
4162
- workers.set(workerId, promise);
4163
- }, options);
4164
- }
4165
- async #work(workerId, handler, message, orderingKey) {
4166
- await this.#sleep(0);
4167
- try {
4168
- await handler(message);
4169
- } finally {
4170
- if (orderingKey != null) {
4171
- this.#processingKeys.delete(orderingKey);
4172
- const pendingIdx = this.#pendingMessages.findIndex((p) => p.orderingKey === orderingKey);
4173
- if (pendingIdx >= 0) this.#pendingMessages.splice(pendingIdx, 1)[0].resolve();
4174
- }
4175
- }
4176
- return workerId;
4177
- }
4178
- #sleep(ms) {
4179
- return new Promise((resolve) => setTimeout(resolve, ms));
4180
- }
4181
- };
4182
- //#endregion
4183
3910
  //#region src/federation/tasks/enqueue.ts
4184
3911
  /**
4185
3912
  * The enqueue pipeline for custom background tasks. `ContextImpl.enqueueTask`
@@ -4247,8 +3974,8 @@ function planDeduplication(ctx, queue, taskName, options, itemCount) {
4247
3974
  const key = options.deduplicationKey;
4248
3975
  const native = queue.nativeDeduplication === true;
4249
3976
  const canCas = ctx.federation.kv.cas != null;
4250
- const canBatchAtomically = queue.enqueueMany != null && !(queue instanceof ParallelMessageQueue && queue.queue.enqueueMany == null);
4251
- 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().`);
3977
+ const canBatchAtomically = queue.enqueueMany != null && queue.atomicEnqueueMany !== false;
3978
+ 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 provide an atomic enqueueMany implementation; a multi-item batch cannot be deduplicated atomically without it. Use a queue with atomic batch enqueueing, or enqueue the tasks individually with enqueueTask().`);
4252
3979
  if (native) return {
4253
3980
  kind: "native",
4254
3981
  key
@@ -7420,18 +7147,6 @@ Object.defineProperty(exports, "CircuitBreaker", {
7420
7147
  return CircuitBreaker;
7421
7148
  }
7422
7149
  });
7423
- Object.defineProperty(exports, "InProcessMessageQueue", {
7424
- enumerable: true,
7425
- get: function() {
7426
- return InProcessMessageQueue;
7427
- }
7428
- });
7429
- Object.defineProperty(exports, "ParallelMessageQueue", {
7430
- enumerable: true,
7431
- get: function() {
7432
- return ParallelMessageQueue;
7433
- }
7434
- });
7435
7150
  Object.defineProperty(exports, "SendActivityError", {
7436
7151
  enumerable: true,
7437
7152
  get: function() {