@fedify/fedify 2.4.0-dev.1668 → 2.4.0-dev.1740

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 (151) hide show
  1. package/README.md +1 -1
  2. package/dist/{assert-CJ1KXcvv.mjs → assert-CebeUf0C.mjs} +1 -1
  3. package/dist/assert_exists-DtzkXC47.mjs +28 -0
  4. package/dist/assert_false-3pLLLsO4.mjs +23 -0
  5. package/dist/assert_greater_or_equal-BApOTh1h.mjs +30 -0
  6. package/dist/{assert_instance_of-Q9KTBnrf.mjs → assert_instance_of-g3WcyYNR.mjs} +1 -1
  7. package/dist/{assert_not_equals-EPJPoxuf.mjs → assert_not_equals-BN7yDMWe.mjs} +1 -1
  8. package/dist/assert_rejects-wFbSPn1O.mjs +39 -0
  9. package/dist/{assert_strict_equals-Ec20AUwa.mjs → assert_strict_equals-Bpjl4Psw.mjs} +1 -1
  10. package/dist/{assert_throws-NiFfqbnH.mjs → assert_throws-BJ5wuvR2.mjs} +1 -1
  11. package/dist/{builder-iXZ5jNVg.mjs → builder-Bw6LVKGw.mjs} +5 -3
  12. package/dist/{circuit-breaker-CBGyz5F7.mjs → circuit-breaker-BT7CiPbz.mjs} +191 -9
  13. package/dist/circuit-breaker-test-utils-BYIHoliq.mjs +14 -0
  14. package/dist/{client-CjF5IYQS.mjs → client-CtxvmazV.mjs} +63 -5
  15. package/dist/{collection-BIhlldqK.mjs → collection-CnnP1UAv.mjs} +1 -1
  16. package/dist/compat/mod.cjs +1 -1
  17. package/dist/compat/mod.d.cts +1 -1
  18. package/dist/compat/mod.d.ts +1 -1
  19. package/dist/compat/mod.js +1 -1
  20. package/dist/compat/outgoing-jsonld.test.mjs +3 -3
  21. package/dist/compat/public-audience.test.mjs +3 -3
  22. package/dist/compat/transformers.test.mjs +5 -5
  23. package/dist/{context-C05Rgxmu.d.cts → context-B-dsBjZ9.d.cts} +281 -7
  24. package/dist/{context-Cjnr0ig1.d.ts → context-DrLXudbA.d.ts} +281 -7
  25. package/dist/{deno-DtHcfB0f.mjs → deno-tRdp8MCl.mjs} +1 -1
  26. package/dist/{docloader-0vAfF6XW.mjs → docloader-Dg_7gMC7.mjs} +2 -2
  27. package/dist/{esm-yYQfPhIY.mjs → esm-BHXXx4Wx.mjs} +13 -6
  28. package/dist/federation/builder.test.mjs +18 -5
  29. package/dist/federation/circuit-breaker.test.mjs +626 -7
  30. package/dist/federation/collection.test.mjs +3 -3
  31. package/dist/federation/handler.test.mjs +14 -13
  32. package/dist/federation/idempotency.test.mjs +6 -6
  33. package/dist/federation/inbox.test.mjs +3 -3
  34. package/dist/federation/keycache.test.mjs +5 -5
  35. package/dist/federation/kv.test.mjs +3 -3
  36. package/dist/federation/metrics.test.mjs +4 -4
  37. package/dist/federation/middleware.test.mjs +37 -28
  38. package/dist/federation/mod.cjs +18 -540
  39. package/dist/federation/mod.d.cts +3 -4
  40. package/dist/federation/mod.d.ts +3 -4
  41. package/dist/federation/mod.js +1 -1
  42. package/dist/federation/mq.test.mjs +32 -5
  43. package/dist/federation/negotiation.test.mjs +4 -4
  44. package/dist/federation/retry.test.mjs +3 -3
  45. package/dist/federation/router.test.mjs +7 -7
  46. package/dist/federation/send.test.mjs +28 -26
  47. package/dist/federation/tasks/codec.test.mjs +4 -4
  48. package/dist/federation/tasks/enqueue.test.mjs +4 -4
  49. package/dist/federation/tasks/tasks.test.mjs +6 -6
  50. package/dist/federation/temporal.test.mjs +4 -4
  51. package/dist/federation/webfinger.test.mjs +74 -12
  52. package/dist/{middleware-DzIUFRO2.cjs → federation-BJBpt_Am.cjs} +855 -114
  53. package/dist/{http-BbnOMQoy.js → http-BIxGa6AG.js} +3 -3
  54. package/dist/{http-DUmWq5Pw.mjs → http-Day1kcpT.mjs} +5 -5
  55. package/dist/{temporal-polyfill-BCdTHexd.cjs → http-DggG1scN.cjs} +2801 -104
  56. package/dist/{key-CM7wDnZG.mjs → key-igh0AMsL.mjs} +2 -2
  57. package/dist/{kv-cache-ZQ22hm44.js → kv-cache-BIAB0HKx.js} +1 -1
  58. package/dist/{kv-cache-DEXKemJ9.mjs → kv-cache-Bz6aQOOX.mjs} +1 -1
  59. package/dist/{kv-cache-zmgG1HUp.cjs → kv-cache-C4U_uBqD.cjs} +2 -4
  60. package/dist/{ld-BttH_-8K.mjs → ld-DnT9sCWa.mjs} +5 -5
  61. package/dist/metrics-DVKYY6K1.d.cts +36 -0
  62. package/dist/{metrics-DT-CnD1g.mjs → metrics-DbdNnWIt.mjs} +2 -2
  63. package/dist/metrics-DoRtI0qU.d.ts +36 -0
  64. package/dist/{middleware-ChlYS0Af.mjs → middleware-B6GweVcg.mjs} +1 -1
  65. package/dist/{middleware-D3IAVMcT.js → middleware-CnsPXG0O.js} +241 -40
  66. package/dist/{middleware-DrroX3aD.mjs → middleware-DPIS4fsB.mjs} +60 -43
  67. package/dist/{mod-BrpVsSfY.d.cts → mod-CLHkCwLm.d.cts} +3 -3
  68. package/dist/{mod-B0hW12_O.d.cts → mod-CnGLNoVw.d.cts} +86 -4
  69. package/dist/{mod-DdONo-ep.d.cts → mod-D-abvu2C.d.cts} +1 -1
  70. package/dist/{mod-BsCE5Ihq.d.ts → mod-D6O8C7eN.d.ts} +1 -1
  71. package/dist/{mod-CNef4in0.d.ts → mod-DypxPS9H.d.ts} +3 -3
  72. package/dist/{mod-COIAjwRS.d.ts → mod-DzRXACM5.d.ts} +86 -4
  73. package/dist/mod.cjs +25 -25
  74. package/dist/mod.d.cts +10 -11
  75. package/dist/mod.d.ts +10 -11
  76. package/dist/mod.js +8 -8
  77. package/dist/nodeinfo/client.test.mjs +64 -4
  78. package/dist/nodeinfo/handler.test.mjs +5 -5
  79. package/dist/nodeinfo/mod.cjs +1 -1
  80. package/dist/nodeinfo/mod.d.cts +1 -1
  81. package/dist/nodeinfo/mod.d.ts +1 -1
  82. package/dist/nodeinfo/mod.js +1 -1
  83. package/dist/nodeinfo/types.test.mjs +4 -4
  84. package/dist/otel/exporter.test.mjs +3 -3
  85. package/dist/otel/mod.cjs +0 -1
  86. package/dist/otel/mod.d.cts +1 -1
  87. package/dist/otel/mod.d.ts +1 -1
  88. package/dist/{outgoing-jsonld-URuqsICz.mjs → outgoing-jsonld-BQSHRLhg.mjs} +2 -2
  89. package/dist/{owner-DZALmhdl.mjs → owner-B3ef8GfJ.mjs} +2 -2
  90. package/dist/{proof-CqA-2nfW.js → proof-BBLQXRWw.js} +608 -36
  91. package/dist/{proof-a60oVJ_4.cjs → proof-Cqk2TcLX.cjs} +616 -39
  92. package/dist/proof-DVoDZ8d7.mjs +890 -0
  93. package/dist/{public-audience-Cr5MsnMq.mjs → public-audience--9hx7IGx.mjs} +1 -1
  94. package/dist/runtime/mod.cjs +1 -1
  95. package/dist/runtime/mod.js +1 -1
  96. package/dist/{send-DcwZnQWG.mjs → send-PbBSZ7Qk.mjs} +3 -3
  97. package/dist/sig/accept.test.mjs +1 -1
  98. package/dist/sig/http.test.mjs +37 -11
  99. package/dist/sig/key.test.mjs +6 -6
  100. package/dist/sig/ld.test.mjs +9 -7
  101. package/dist/sig/mod.cjs +3 -2
  102. package/dist/sig/mod.d.cts +4 -4
  103. package/dist/sig/mod.d.ts +4 -4
  104. package/dist/sig/mod.js +3 -3
  105. package/dist/sig/owner.test.mjs +6 -6
  106. package/dist/sig/proof.test.mjs +965 -15
  107. package/dist/std__assert-CxFT0MNt.mjs +8 -0
  108. package/dist/{temporal-Bpxpsl2k.mjs → temporal-DLQIjuTn.mjs} +3 -3
  109. package/dist/testing/mod.d.mts +10 -0
  110. package/dist/testing/mod.mjs +2 -2
  111. package/dist/{testing-CO7ftO8A.mjs → testing-BX0Nicyq.mjs} +2 -2
  112. package/dist/{transformers-DGwl-hD1.cjs → transformers-BalCrSsr.cjs} +0 -1
  113. package/dist/{types-DC1pNgJA.js → types-Dtyf8lVj.js} +63 -5
  114. package/dist/{types-CPym83zc.cjs → types-RXm5YxpC.cjs} +62 -5
  115. package/dist/utils/docloader.test.mjs +7 -7
  116. package/dist/utils/kv-cache.test.mjs +1 -1
  117. package/dist/utils/mod.cjs +1 -1
  118. package/dist/utils/mod.d.cts +1 -1
  119. package/dist/utils/mod.d.ts +1 -1
  120. package/dist/utils/mod.js +1 -1
  121. package/package.json +7 -7
  122. package/skills/fedify/SKILL.md +462 -0
  123. package/dist/assert_rejects-EgstcQy5.mjs +0 -82
  124. package/dist/http-BpT5cE6r.cjs +0 -2694
  125. package/dist/metrics-DN13mCAw.d.cts +0 -294
  126. package/dist/metrics-v_e0n5GQ.d.ts +0 -294
  127. package/dist/proof-DiQ0uBal.mjs +0 -316
  128. package/dist/std__assert-B2NnY9bq.mjs +0 -79
  129. /package/dist/{accept-8PiolDP7.mjs → accept-CPVqki78.mjs} +0 -0
  130. /package/dist/{activity-listener-BtTSxZWA.mjs → activity-listener-C-BSbKI7.mjs} +0 -0
  131. /package/dist/{assert_equals-BV7oLMxm.mjs → assert_equals-CBlRfA-_.mjs} +0 -0
  132. /package/dist/{client-CAM_bQXx.d.cts → client-D-XP_Ccb.d.cts} +0 -0
  133. /package/dist/{client-CSddvgWN.d.ts → client-D-XP_Ccb.d.ts} +0 -0
  134. /package/dist/{codec-BBga-FWI.mjs → codec-peqOdBaE.mjs} +0 -0
  135. /package/dist/{context-BJXxPElm.mjs → context-Dt36KFEJ.mjs} +0 -0
  136. /package/dist/{http-VyDTd4G3.d.cts → http-6TI0tI03.d.cts} +0 -0
  137. /package/dist/{http-lf8Hsd91.d.ts → http-CdrSn2ip.d.ts} +0 -0
  138. /package/dist/{keycache-_DHqz5z_.mjs → keycache-0Jk78XmB.mjs} +0 -0
  139. /package/dist/{keys-Dbyd7bhl.mjs → keys-DIJNHW-Y.mjs} +0 -0
  140. /package/dist/{kv-D6hNiMTK.d.ts → kv-BhNbN8Ed.d.cts} +0 -0
  141. /package/dist/{kv-gJ8LYbxX.d.cts → kv-BhNbN8Ed.d.ts} +0 -0
  142. /package/dist/{kv-CXBAok3k.mjs → kv-DCHkaJ9-.mjs} +0 -0
  143. /package/dist/{mq-Bo7Etgh6.mjs → mq-Dm1Wmtm-.mjs} +0 -0
  144. /package/dist/{negotiation-RuwrPFqi.mjs → negotiation-CCocWT_N.mjs} +0 -0
  145. /package/dist/{owner-CnngXDNJ.d.ts → owner-DGSfntI-.d.ts} +0 -0
  146. /package/dist/{owner-DEvZuyOE.d.cts → owner-r_waTiQj.d.cts} +0 -0
  147. /package/dist/{retry-DgFqeS5k.mjs → retry-zX5xw5e0.mjs} +0 -0
  148. /package/dist/{chunk-D5IdezsZ.js → rolldown-runtime-BC-Eo5Mj.js} +0 -0
  149. /package/dist/{chunk-Czpeue4M.cjs → rolldown-runtime-Dmm3t92W.cjs} +0 -0
  150. /package/dist/{transformers-DXze6FUB.js → transformers-DAtXl_Kc.js} +0 -0
  151. /package/dist/{types-CrtdVuDP.mjs → types-DE_LV1JB.mjs} +0 -0
@@ -1,11 +1,10 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
- const require_chunk = require("./chunk-Czpeue4M.cjs");
3
- const require_transformers = require("./transformers-DGwl-hD1.cjs");
4
- const require_http = require("./http-BpT5cE6r.cjs");
5
- const require_temporal_polyfill = require("./temporal-polyfill-BCdTHexd.cjs");
6
- const require_proof = require("./proof-a60oVJ_4.cjs");
7
- const require_types = require("./types-CPym83zc.cjs");
8
- const require_kv_cache = require("./kv-cache-zmgG1HUp.cjs");
2
+ const require_rolldown_runtime = require("./rolldown-runtime-Dmm3t92W.cjs");
3
+ const require_transformers = require("./transformers-BalCrSsr.cjs");
4
+ const require_http = require("./http-DggG1scN.cjs");
5
+ const require_proof = require("./proof-Cqk2TcLX.cjs");
6
+ const require_types = require("./types-RXm5YxpC.cjs");
7
+ const require_kv_cache = require("./kv-cache-C4U_uBqD.cjs");
9
8
  let _logtape_logtape = require("@logtape/logtape");
10
9
  let _fedify_uri_template = require("@fedify/uri-template");
11
10
  let _fedify_vocab = require("@fedify/vocab");
@@ -15,7 +14,7 @@ let es_toolkit = require("es-toolkit");
15
14
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
16
15
  let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
17
16
  let _fedify_vocab_runtime_jsonld = require("@fedify/vocab-runtime/jsonld");
18
- _fedify_vocab_runtime_jsonld = require_chunk.__toESM(_fedify_vocab_runtime_jsonld, 1);
17
+ _fedify_vocab_runtime_jsonld = require_rolldown_runtime.__toESM(_fedify_vocab_runtime_jsonld, 1);
19
18
  let _fedify_webfinger = require("@fedify/webfinger");
20
19
  let _opentelemetry_sdk_metrics = require("@opentelemetry/sdk-metrics");
21
20
  let devalue = require("devalue");
@@ -144,6 +143,8 @@ var FederationBuilderImpl = class {
144
143
  f.webFingerLinksDispatcher = this.webFingerLinksDispatcher;
145
144
  f.objectCallbacks = { ...this.objectCallbacks };
146
145
  f.objectTypeIds = { ...this.objectTypeIds };
146
+ f.collectionCallbacks = { ...this.collectionCallbacks };
147
+ f.collectionTypeIds = { ...this.collectionTypeIds };
147
148
  f.inboxPath = this.inboxPath;
148
149
  f.outboxPath = this.outboxPath;
149
150
  f.inboxCallbacks = this.inboxCallbacks == null ? void 0 : { ...this.inboxCallbacks };
@@ -723,8 +724,14 @@ function createFederationBuilder() {
723
724
  }
724
725
  //#endregion
725
726
  //#region src/federation/circuit-breaker.ts
726
- const Temporal$7 = require_temporal_polyfill.Temporal;
727
- const MAX_CUSTOM_FAILURE_HISTORY = 100;
727
+ const Temporal$9 = require_http.Temporal;
728
+ const LEGACY_SWEEP_MARKER = ["__fedify_meta", "circuit_breaker_state_ttl_sweep_v1"];
729
+ const LEGACY_SWEEP_DELETING_MARKER = { __fedifyDeletingCircuitBreakerLegacyState: true };
730
+ const CIRCUIT_BREAKER_STATE_VERSION = 1;
731
+ const LEGACY_SWEEP_LOCK_TTL = Temporal$9.Duration.from({ minutes: 5 });
732
+ const LEGACY_SWEEP_RETRY_WINDOW = Temporal$9.Duration.from({ hours: 168 });
733
+ const LEGACY_SWEEP_WAIT_INTERVAL = 10;
734
+ const LEGACY_SWEEP_CAS_ATTEMPTS = 3;
728
735
  /**
729
736
  * Tracks reachability state for remote outbox delivery hosts.
730
737
  * @since 2.3.0
@@ -734,12 +741,14 @@ var CircuitBreaker = class {
734
741
  #prefix;
735
742
  #options;
736
743
  #now;
744
+ #legacySweep;
745
+ #legacySweepDone;
737
746
  #stateChangeObserver;
738
747
  constructor(options) {
739
748
  this.#kv = options.kv;
740
749
  this.#prefix = options.prefix;
741
750
  this.#options = normalizeCircuitBreakerOptions(options.options ?? {});
742
- this.#now = options.now ?? (() => Temporal$7.Now.instant());
751
+ this.#now = options.now ?? (() => Temporal$9.Now.instant());
743
752
  this.#stateChangeObserver = options.stateChangeObserver;
744
753
  }
745
754
  get options() {
@@ -750,9 +759,10 @@ var CircuitBreaker = class {
750
759
  return now.until(this.#capHeldRetryAt(now, heldSince, now.add(delay)));
751
760
  }
752
761
  async beforeSend(remoteHost, message) {
762
+ this.#sweepLegacyStates();
753
763
  const heldSince = parseHeldSince(message.circuitHeldSince);
754
764
  const now = this.#now();
755
- if (heldSince != null && Temporal$7.Instant.compare(heldSince.add(this.#options.heldActivityTtl), now) <= 0) return {
765
+ if (heldSince != null && Temporal$9.Instant.compare(heldSince.add(this.#options.heldActivityTtl), now) <= 0) return {
756
766
  type: "drop",
757
767
  heldSince
758
768
  };
@@ -764,12 +774,12 @@ var CircuitBreaker = class {
764
774
  probe: false
765
775
  };
766
776
  if (oldState.state === "half-open") {
767
- const halfOpened = oldState.halfOpened == null ? void 0 : Temporal$7.Instant.from(oldState.halfOpened);
777
+ const halfOpened = oldState.halfOpened == null ? void 0 : Temporal$9.Instant.from(oldState.halfOpened);
768
778
  if (halfOpened != null) {
769
779
  const staleAt = halfOpened.add(this.#options.recoveryDelay);
770
- if (Temporal$7.Instant.compare(now, staleAt) < 0) {
780
+ if (Temporal$9.Instant.compare(now, staleAt) < 0) {
771
781
  const releaseAt = now.add(this.#options.releaseInterval);
772
- const retryAt = Temporal$7.Instant.compare(releaseAt, staleAt) < 0 ? releaseAt : staleAt;
782
+ const retryAt = Temporal$9.Instant.compare(releaseAt, staleAt) < 0 ? releaseAt : staleAt;
773
783
  const cappedRetryAt = this.#capHeldRetryAt(now, heldSince, retryAt);
774
784
  return {
775
785
  type: "hold",
@@ -791,8 +801,8 @@ var CircuitBreaker = class {
791
801
  lastConflictingState = "half-open";
792
802
  continue;
793
803
  }
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) {
804
+ const probeAt = (oldState.opened == null ? now : Temporal$9.Instant.from(oldState.opened)).add(this.#options.recoveryDelay);
805
+ if (Temporal$9.Instant.compare(now, probeAt) < 0) {
796
806
  const retryAt = this.#capHeldRetryAt(now, heldSince, probeAt);
797
807
  return {
798
808
  type: "hold",
@@ -831,6 +841,7 @@ var CircuitBreaker = class {
831
841
  throw new Error(`Failed to update circuit breaker state for ${remoteHost}`);
832
842
  }
833
843
  async recordSuccess(remoteHost) {
844
+ this.#sweepLegacyStates();
834
845
  for (let attempt = 0; attempt < 10; attempt++) {
835
846
  const oldState = await this.#get(remoteHost);
836
847
  if (oldState == null) return void 0;
@@ -851,11 +862,12 @@ var CircuitBreaker = class {
851
862
  return await this.recordSuccess(remoteHost);
852
863
  }
853
864
  async recordFailure(remoteHost) {
865
+ this.#sweepLegacyStates();
854
866
  const now = this.#now();
855
867
  for (let attempt = 0; attempt < 10; attempt++) {
856
868
  const oldState = await this.#get(remoteHost);
857
869
  if (oldState?.state === "open") return void 0;
858
- const oldFailures = oldState?.failures.map(Temporal$7.Instant.from) ?? [];
870
+ const oldFailures = oldState?.failures.map(Temporal$9.Instant.from) ?? [];
859
871
  const failures = this.#options.pruneFailures([...oldFailures, now], now);
860
872
  let newState;
861
873
  let transition;
@@ -898,26 +910,122 @@ var CircuitBreaker = class {
898
910
  }
899
911
  }
900
912
  async getState(remoteHost) {
901
- return await this.#get(remoteHost);
913
+ this.#sweepLegacyStates();
914
+ return stripStoredCircuitBreakerState(await this.#get(remoteHost));
915
+ }
916
+ /**
917
+ * The currently running background legacy sweep, if any.
918
+ * @internal
919
+ */
920
+ get pendingSweep() {
921
+ return this.#legacySweep;
902
922
  }
903
923
  #key(remoteHost) {
904
924
  return [...this.#prefix, remoteHost];
905
925
  }
926
+ #legacySweepMarkerKey() {
927
+ return [...this.#prefix, ...LEGACY_SWEEP_MARKER];
928
+ }
929
+ #sweepLegacyStates() {
930
+ if (this.#kv.cas == null) return;
931
+ if (this.#options.stateTtl == null) return;
932
+ if (this.#legacySweep != null) return;
933
+ if (this.#isLegacySweepDone()) return;
934
+ this.#legacySweep = this.#sweepLegacyStatesImpl().catch((error) => {
935
+ (0, _logtape_logtape.getLogger)([
936
+ "fedify",
937
+ "federation",
938
+ "circuit"
939
+ ]).warn("Failed to sweep legacy circuit breaker state:\n{error}", { error });
940
+ }).finally(() => {
941
+ this.#legacySweep = void 0;
942
+ });
943
+ }
944
+ async #sweepLegacyStatesImpl() {
945
+ const markerKey = this.#legacySweepMarkerKey();
946
+ const marker = await this.#acquireLegacySweep(markerKey);
947
+ if (marker === "done") return;
948
+ try {
949
+ for await (const { key, value } of this.#kv.list(this.#prefix)) {
950
+ if (key.length !== this.#prefix.length + 1) continue;
951
+ await this.#migrateLegacyState(key, value);
952
+ }
953
+ } catch (error) {
954
+ await this.#deleteIfUnchanged(markerKey, marker);
955
+ throw error;
956
+ }
957
+ const finishedMarker = this.#finishLegacySweepMarker(marker);
958
+ if (await this.#kv.cas(markerKey, marker, finishedMarker)) this.#rememberLegacySweepMarker(finishedMarker);
959
+ }
960
+ #finishLegacySweepMarker(marker) {
961
+ const retryUntil = getLegacySweepRetryUntil(marker);
962
+ if (retryUntil != null && Temporal$9.Instant.compare(this.#now(), Temporal$9.Instant.from(retryUntil)) < 0) return {
963
+ state: "done",
964
+ retryUntil
965
+ };
966
+ return { state: "final" };
967
+ }
968
+ async #migrateLegacyState(key, value) {
969
+ if (isCurrentCircuitBreakerState(value)) return;
970
+ const state = parseCircuitBreakerKvState(value);
971
+ if (state != null) {
972
+ await this.#kv.cas(key, value, markCircuitBreakerState(state), this.#setOptions());
973
+ return;
974
+ }
975
+ await this.#deleteIfUnchanged(key, value);
976
+ }
977
+ async #deleteIfUnchanged(key, value) {
978
+ if (await this.#kv.cas(key, value, LEGACY_SWEEP_DELETING_MARKER, { ttl: LEGACY_SWEEP_LOCK_TTL })) await this.#kv.delete(key);
979
+ }
980
+ async #acquireLegacySweep(markerKey) {
981
+ for (let attempt = 0; attempt < LEGACY_SWEEP_CAS_ATTEMPTS; attempt++) {
982
+ const marker = await this.#kv.get(markerKey);
983
+ const now = this.#now();
984
+ if (isLegacySweepActive(marker, now)) {
985
+ this.#rememberLegacySweepMarker(marker);
986
+ return "done";
987
+ }
988
+ const retryUntil = isLegacySweepRetryDue(marker, now) ? marker.retryUntil : getLegacySweepRetryUntil(marker) ?? now.add(LEGACY_SWEEP_RETRY_WINDOW).toString();
989
+ const sweeping = {
990
+ state: "sweeping",
991
+ started: now.toString(),
992
+ retryUntil
993
+ };
994
+ if (await this.#kv.cas(markerKey, marker ?? void 0, sweeping, { ttl: LEGACY_SWEEP_LOCK_TTL })) return sweeping;
995
+ await delay(LEGACY_SWEEP_WAIT_INTERVAL);
996
+ }
997
+ return "done";
998
+ }
999
+ #isLegacySweepDone() {
1000
+ if (this.#legacySweepDone === "final") return true;
1001
+ if (this.#legacySweepDone == null) return false;
1002
+ if (Temporal$9.Instant.compare(this.#now(), this.#legacySweepDone) < 0) return true;
1003
+ this.#legacySweepDone = void 0;
1004
+ return false;
1005
+ }
1006
+ #rememberLegacySweepMarker(marker) {
1007
+ const doneUntil = getLegacySweepDoneUntil(marker, this.#now());
1008
+ if (doneUntil != null) this.#legacySweepDone = doneUntil;
1009
+ }
906
1010
  #capHeldRetryAt(now, heldSince, retryAt) {
907
1011
  const expiresAt = (heldSince ?? now).add(this.#options.heldActivityTtl);
908
- return Temporal$7.Instant.compare(expiresAt, retryAt) < 0 ? expiresAt : retryAt;
1012
+ return Temporal$9.Instant.compare(expiresAt, retryAt) < 0 ? expiresAt : retryAt;
909
1013
  }
910
1014
  async #get(remoteHost) {
911
- return parseCircuitBreakerKvState(await this.#kv.get(this.#key(remoteHost)));
1015
+ return parseStoredCircuitBreakerKvState(await this.#kv.get(this.#key(remoteHost)));
912
1016
  }
913
1017
  async #replace(remoteHost, oldState, newState) {
914
1018
  const key = this.#key(remoteHost);
1019
+ const storedState = newState == null ? void 0 : markCircuitBreakerState(newState);
915
1020
  if (this.#kv.cas == null) {
916
- if (newState == null) await this.#kv.delete(key);
917
- else await this.#kv.set(key, newState);
1021
+ if (storedState == null) await this.#kv.delete(key);
1022
+ else await this.#kv.set(key, storedState, this.#setOptions());
918
1023
  return true;
919
1024
  }
920
- return await this.#kv.cas(key, oldState, newState);
1025
+ return await this.#kv.cas(key, oldState, storedState, storedState == null ? void 0 : this.#setOptions());
1026
+ }
1027
+ #setOptions() {
1028
+ return this.#options.stateTtl == null ? void 0 : { ttl: this.#options.stateTtl };
921
1029
  }
922
1030
  async #notifyStateChange(remoteHost, previousState, newState) {
923
1031
  try {
@@ -957,18 +1065,25 @@ var CircuitBreaker = class {
957
1065
  * @param options The public circuit breaker options supplied to Fedify.
958
1066
  * @returns The normalized failure predicate, failure pruning function,
959
1067
  * duration values, and optional callbacks with defaults applied.
960
- * @throws {RangeError} If any configured duration is not positive.
1068
+ * @throws {RangeError} If any configured duration is not positive, or if
1069
+ * `stateTtl` is shorter than `recoveryDelay`.
961
1070
  * @throws {TypeError} If `failureThreshold` is not a positive integer.
962
1071
  */
963
1072
  function normalizeCircuitBreakerOptions(options) {
964
1073
  const recoveryDelay = toInstantDuration(options.recoveryDelay ?? { minutes: 30 });
965
1074
  const heldActivityTtl = toInstantDuration(options.heldActivityTtl ?? { hours: 168 });
966
1075
  const releaseInterval = toInstantDuration(options.releaseInterval ?? { seconds: 1 });
1076
+ const configuredStateTtl = options.stateTtl == null ? void 0 : toInstantDuration(options.stateTtl);
967
1077
  assertPositiveDuration(recoveryDelay, "recoveryDelay");
968
1078
  assertPositiveDuration(heldActivityTtl, "heldActivityTtl");
969
1079
  assertPositiveDuration(releaseInterval, "releaseInterval");
1080
+ if (configuredStateTtl != null) {
1081
+ assertPositiveDuration(configuredStateTtl, "stateTtl");
1082
+ if (Temporal$9.Duration.compare(configuredStateTtl, recoveryDelay) < 0) throw new RangeError("stateTtl must be at least recoveryDelay.");
1083
+ }
970
1084
  let failure;
971
1085
  let pruneFailures;
1086
+ let stateTtl;
972
1087
  if (options.failure == null) {
973
1088
  const failureThreshold = options.failureThreshold ?? 5;
974
1089
  if (!Number.isInteger(failureThreshold) || failureThreshold <= 0) throw new TypeError("failureThreshold must be a positive integer.");
@@ -976,17 +1091,19 @@ function normalizeCircuitBreakerOptions(options) {
976
1091
  assertPositiveDuration(failureWindow, "failureWindow");
977
1092
  pruneFailures = (timestamps, now) => {
978
1093
  const earliest = now.subtract(failureWindow);
979
- return timestamps.filter((timestamp) => Temporal$7.Instant.compare(timestamp, earliest) >= 0).slice(-failureThreshold);
1094
+ return timestamps.filter((timestamp) => Temporal$9.Instant.compare(timestamp, earliest) >= 0).slice(-failureThreshold);
980
1095
  };
981
1096
  failure = (timestamps) => {
982
1097
  if (timestamps.length < failureThreshold) return false;
983
1098
  const first = timestamps[timestamps.length - failureThreshold];
984
1099
  const last = timestamps[timestamps.length - 1];
985
- return Temporal$7.Duration.compare(first.until(last), failureWindow) <= 0;
1100
+ return Temporal$9.Duration.compare(first.until(last), failureWindow) <= 0;
986
1101
  };
1102
+ stateTtl = configuredStateTtl ?? maxDuration(recoveryDelay.add(failureWindow), recoveryDelay.add(heldActivityTtl));
987
1103
  } else {
988
1104
  failure = options.failure;
989
- pruneFailures = (timestamps) => timestamps.slice(-MAX_CUSTOM_FAILURE_HISTORY);
1105
+ pruneFailures = (timestamps) => timestamps.slice(-100);
1106
+ stateTtl = configuredStateTtl;
990
1107
  }
991
1108
  return {
992
1109
  failure,
@@ -994,24 +1111,86 @@ function normalizeCircuitBreakerOptions(options) {
994
1111
  recoveryDelay,
995
1112
  heldActivityTtl,
996
1113
  releaseInterval,
1114
+ stateTtl,
997
1115
  onStateChange: options.onStateChange,
998
1116
  onActivityDrop: options.onActivityDrop
999
1117
  };
1000
1118
  }
1119
+ function maxDuration(duration, ...durations) {
1120
+ return durations.reduce((max, candidate) => Temporal$9.Duration.compare(candidate, max) > 0 ? candidate : max, duration);
1121
+ }
1122
+ function isLegacySweepDone(value, now) {
1123
+ return getLegacySweepDoneUntil(value, now) != null;
1124
+ }
1125
+ function getLegacySweepDoneUntil(value, now) {
1126
+ if (typeof value !== "object" || value == null || !("state" in value)) return;
1127
+ if (value.state === "final") return "final";
1128
+ if (value.state === "done" && "retryUntil" in value && typeof value.retryUntil === "string") try {
1129
+ const retryUntil = Temporal$9.Instant.from(value.retryUntil);
1130
+ return Temporal$9.Instant.compare(now, retryUntil) < 0 ? retryUntil : void 0;
1131
+ } catch {
1132
+ return;
1133
+ }
1134
+ }
1135
+ function isLegacySweepRetryDue(value, now) {
1136
+ if (typeof value !== "object" || value == null || !("state" in value) || value.state !== "done" || !("retryUntil" in value) || typeof value.retryUntil !== "string") return false;
1137
+ try {
1138
+ return Temporal$9.Instant.compare(now, Temporal$9.Instant.from(value.retryUntil)) >= 0;
1139
+ } catch {
1140
+ return false;
1141
+ }
1142
+ }
1143
+ function isLegacySweepActive(value, now) {
1144
+ return isLegacySweepDone(value, now) || isLegacySweepInProgress(value, now);
1145
+ }
1146
+ function isLegacySweepInProgress(value, now) {
1147
+ if (typeof value !== "object" || value == null || !("state" in value) || value.state !== "sweeping" || !("started" in value) || typeof value.started !== "string") return false;
1148
+ try {
1149
+ return Temporal$9.Instant.compare(now, Temporal$9.Instant.from(value.started).add(LEGACY_SWEEP_LOCK_TTL)) < 0;
1150
+ } catch {
1151
+ return false;
1152
+ }
1153
+ }
1154
+ function getLegacySweepRetryUntil(value) {
1155
+ if (typeof value !== "object" || value == null || !("retryUntil" in value) || typeof value.retryUntil !== "string") return;
1156
+ try {
1157
+ Temporal$9.Instant.from(value.retryUntil);
1158
+ return value.retryUntil;
1159
+ } catch {
1160
+ return;
1161
+ }
1162
+ }
1163
+ function isCurrentCircuitBreakerState(value) {
1164
+ return typeof value === "object" && value != null && "__fedifyCircuitBreakerStateVersion" in value && value.__fedifyCircuitBreakerStateVersion === CIRCUIT_BREAKER_STATE_VERSION;
1165
+ }
1166
+ function markCircuitBreakerState(state) {
1167
+ return {
1168
+ ...state,
1169
+ __fedifyCircuitBreakerStateVersion: CIRCUIT_BREAKER_STATE_VERSION
1170
+ };
1171
+ }
1172
+ function stripStoredCircuitBreakerState(state) {
1173
+ if (state == null) return void 0;
1174
+ const { __fedifyCircuitBreakerStateVersion: _, ...publicState } = state;
1175
+ return publicState;
1176
+ }
1177
+ function delay(milliseconds) {
1178
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
1179
+ }
1001
1180
  function toInstantDuration(duration) {
1002
- const parsed = Temporal$7.Duration.from(duration);
1003
- return Temporal$7.Duration.from({ milliseconds: Math.trunc(parsed.total({
1181
+ const parsed = Temporal$9.Duration.from(duration);
1182
+ return Temporal$9.Duration.from({ milliseconds: Math.trunc(parsed.total({
1004
1183
  unit: "millisecond",
1005
- relativeTo: Temporal$7.PlainDateTime.from("2026-01-01T00:00:00")
1184
+ relativeTo: Temporal$9.PlainDateTime.from("2026-01-01T00:00:00")
1006
1185
  })) });
1007
1186
  }
1008
1187
  function assertPositiveDuration(duration, name) {
1009
- if (Temporal$7.Duration.compare(duration, { seconds: 0 }) <= 0) throw new RangeError(`${name} must be a positive duration.`);
1188
+ if (Temporal$9.Duration.compare(duration, { seconds: 0 }) <= 0) throw new RangeError(`${name} must be a positive duration.`);
1010
1189
  }
1011
1190
  function parseHeldSince(value) {
1012
1191
  if (value == null) return void 0;
1013
1192
  try {
1014
- return Temporal$7.Instant.from(value);
1193
+ return Temporal$9.Instant.from(value);
1015
1194
  } catch (error) {
1016
1195
  (0, _logtape_logtape.getLogger)([
1017
1196
  "fedify",
@@ -1032,10 +1211,13 @@ function parseHeldSince(value) {
1032
1211
  * valid instant strings, or `undefined` when the stored value is malformed.
1033
1212
  */
1034
1213
  function parseCircuitBreakerKvState(value) {
1214
+ return stripStoredCircuitBreakerState(parseStoredCircuitBreakerKvState(value));
1215
+ }
1216
+ function parseStoredCircuitBreakerKvState(value) {
1035
1217
  const isInstantString = (v) => {
1036
1218
  if (typeof v !== "string") return false;
1037
1219
  try {
1038
- Temporal$7.Instant.from(v);
1220
+ Temporal$9.Instant.from(v);
1039
1221
  return true;
1040
1222
  } catch {
1041
1223
  return false;
@@ -1051,7 +1233,8 @@ function parseCircuitBreakerKvState(value) {
1051
1233
  state: record.state,
1052
1234
  failures: record.failures,
1053
1235
  ...record.opened == null ? {} : { opened: record.opened },
1054
- ...record.halfOpened == null ? {} : { halfOpened: record.halfOpened }
1236
+ ...record.halfOpened == null ? {} : { halfOpened: record.halfOpened },
1237
+ ...record.__fedifyCircuitBreakerStateVersion === CIRCUIT_BREAKER_STATE_VERSION ? { __fedifyCircuitBreakerStateVersion: CIRCUIT_BREAKER_STATE_VERSION } : {}
1055
1238
  };
1056
1239
  }
1057
1240
  //#endregion
@@ -1066,7 +1249,7 @@ function parseCircuitBreakerKvState(value) {
1066
1249
  async function digest(uris) {
1067
1250
  const processed = /* @__PURE__ */ new Set();
1068
1251
  const encoder = new TextEncoder();
1069
- const result = new Uint8Array(32);
1252
+ const result = /* @__PURE__ */ new Uint8Array(32);
1070
1253
  for (const uri of uris) {
1071
1254
  const u = uri instanceof URL ? uri.href : uri;
1072
1255
  if (processed.has(u)) continue;
@@ -1095,7 +1278,7 @@ async function buildCollectionSynchronizationHeader(collectionId, actorIds) {
1095
1278
  }
1096
1279
  //#endregion
1097
1280
  //#region src/federation/inbox.ts
1098
- const Temporal$6 = require_temporal_polyfill.Temporal;
1281
+ const Temporal$8 = require_http.Temporal;
1099
1282
  async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
1100
1283
  const logger = (0, _logtape_logtape.getLogger)([
1101
1284
  "fedify",
@@ -1242,7 +1425,7 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
1242
1425
  return "error";
1243
1426
  }
1244
1427
  require_http.recordInboxActivity(meterProvider, "processed", (0, _fedify_vocab.getTypeId)(activity).href);
1245
- if (cacheKey != null) await kv.set(cacheKey, true, { ttl: Temporal$6.Duration.from({ days: 1 }) });
1428
+ if (cacheKey != null) await kv.set(cacheKey, true, { ttl: Temporal$8.Duration.from({ days: 1 }) });
1246
1429
  logger.info("Activity {activityId} has been processed.", {
1247
1430
  activityId: activity.id?.href,
1248
1431
  activity: json,
@@ -1254,7 +1437,7 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
1254
1437
  }
1255
1438
  //#endregion
1256
1439
  //#region src/federation/keycache.ts
1257
- const Temporal$5 = require_temporal_polyfill.Temporal;
1440
+ const Temporal$7 = require_http.Temporal;
1258
1441
  var KvKeyCache = class {
1259
1442
  kv;
1260
1443
  prefix;
@@ -1265,7 +1448,7 @@ var KvKeyCache = class {
1265
1448
  this.kv = kv;
1266
1449
  this.prefix = prefix;
1267
1450
  this.options = options;
1268
- this.unavailableKeyTtl = options.unavailableKeyTtl ?? Temporal$5.Duration.from({ minutes: 10 });
1451
+ this.unavailableKeyTtl = options.unavailableKeyTtl ?? Temporal$7.Duration.from({ minutes: 10 });
1269
1452
  this.nullKeys = /* @__PURE__ */ new Map();
1270
1453
  }
1271
1454
  #getFetchErrorKey(keyId) {
@@ -1278,13 +1461,13 @@ var KvKeyCache = class {
1278
1461
  async get(keyId) {
1279
1462
  const negativeExpiration = this.nullKeys.get(keyId.href);
1280
1463
  if (negativeExpiration != null) {
1281
- if (Temporal$5.Now.instant().until(negativeExpiration).sign >= 0) return null;
1464
+ if (Temporal$7.Now.instant().until(negativeExpiration).sign >= 0) return null;
1282
1465
  this.nullKeys.delete(keyId.href);
1283
1466
  }
1284
1467
  const serialized = await this.kv.get([...this.prefix, keyId.href]);
1285
1468
  if (serialized === void 0) return void 0;
1286
1469
  if (serialized === null) {
1287
- this.nullKeys.set(keyId.href, Temporal$5.Now.instant().add(this.unavailableKeyTtl));
1470
+ this.nullKeys.set(keyId.href, Temporal$7.Now.instant().add(this.unavailableKeyTtl));
1288
1471
  return null;
1289
1472
  }
1290
1473
  try {
@@ -1300,7 +1483,7 @@ var KvKeyCache = class {
1300
1483
  }
1301
1484
  async set(keyId, key) {
1302
1485
  if (key == null) {
1303
- this.nullKeys.set(keyId.href, Temporal$5.Now.instant().add(this.unavailableKeyTtl));
1486
+ this.nullKeys.set(keyId.href, Temporal$7.Now.instant().add(this.unavailableKeyTtl));
1304
1487
  await this.kv.set([...this.prefix, keyId.href], null, { ttl: this.unavailableKeyTtl });
1305
1488
  return;
1306
1489
  }
@@ -1439,7 +1622,7 @@ function acceptsJsonLd(request) {
1439
1622
  }
1440
1623
  //#endregion
1441
1624
  //#region src/federation/temporal.ts
1442
- const Temporal$4 = require_temporal_polyfill.Temporal;
1625
+ const Temporal$6 = require_http.Temporal;
1443
1626
  function isPlainObject$1(value) {
1444
1627
  return typeof value === "object" && value != null && !Array.isArray(value);
1445
1628
  }
@@ -1449,7 +1632,7 @@ function normalizeDateTimeLiteral(value) {
1449
1632
  function isMalformedDateTimeLiteral(value) {
1450
1633
  if (typeof value !== "string") return false;
1451
1634
  try {
1452
- Temporal$4.Instant.from(normalizeDateTimeLiteral(value));
1635
+ Temporal$6.Instant.from(normalizeDateTimeLiteral(value));
1453
1636
  return false;
1454
1637
  } catch {
1455
1638
  return true;
@@ -1458,13 +1641,13 @@ function isMalformedDateTimeLiteral(value) {
1458
1641
  function isMalformedDurationLiteral(value) {
1459
1642
  if (typeof value !== "string") return false;
1460
1643
  try {
1461
- Temporal$4.Duration.from(value);
1644
+ Temporal$6.Duration.from(value);
1462
1645
  return false;
1463
1646
  } catch {
1464
1647
  return true;
1465
1648
  }
1466
1649
  }
1467
- const TEMPORAL_DATE_TIME_IRIS = new Set([
1650
+ const TEMPORAL_DATE_TIME_IRIS = /* @__PURE__ */ new Set([
1468
1651
  "https://www.w3.org/ns/activitystreams#deleted",
1469
1652
  "https://www.w3.org/ns/activitystreams#endTime",
1470
1653
  "https://www.w3.org/ns/activitystreams#published",
@@ -1473,7 +1656,7 @@ const TEMPORAL_DATE_TIME_IRIS = new Set([
1473
1656
  "http://purl.org/dc/terms/created",
1474
1657
  "https://w3id.org/security#created"
1475
1658
  ]);
1476
- const TEMPORAL_DURATION_IRIS = new Set(["https://www.w3.org/ns/activitystreams#duration"]);
1659
+ const TEMPORAL_DURATION_IRIS = /* @__PURE__ */ new Set(["https://www.w3.org/ns/activitystreams#duration"]);
1477
1660
  const QUESTION_CLOSED_IRI = "https://www.w3.org/ns/activitystreams#closed";
1478
1661
  const XSD_DATE_TIME_IRI = "http://www.w3.org/2001/XMLSchema#dateTime";
1479
1662
  function hasMalformedExpandedDateTimeLiteral(value) {
@@ -1529,7 +1712,7 @@ async function hasMalformedKnownTemporalLiteral(value, contextLoader) {
1529
1712
  }
1530
1713
  //#endregion
1531
1714
  //#region src/federation/handler.ts
1532
- const Temporal$3 = require_temporal_polyfill.Temporal;
1715
+ const Temporal$5 = require_http.Temporal;
1533
1716
  const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
1534
1717
  function isRemoteContextLoadingFailure$1(error) {
1535
1718
  return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
@@ -1618,7 +1801,7 @@ async function handleObject(request, { values, context, objectDispatcher, author
1618
1801
  Vary: "Accept"
1619
1802
  } });
1620
1803
  }
1621
- const BUILT_IN_COLLECTION_METRIC_KINDS = new Set([
1804
+ const BUILT_IN_COLLECTION_METRIC_KINDS = /* @__PURE__ */ new Set([
1622
1805
  "inbox",
1623
1806
  "outbox",
1624
1807
  "following",
@@ -1655,13 +1838,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
1655
1838
  * @param parameters The parameters for handling the collection.
1656
1839
  * @returns A promise that resolves to an HTTP response.
1657
1840
  */
1658
- async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
1659
- const spanName = name$1.trim().replace(/\s+/g, "_");
1841
+ async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
1842
+ const spanName = name$2.trim().replace(/\s+/g, "_");
1660
1843
  tracerProvider = tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
1661
1844
  const tracer = tracerProvider.getTracer(require_http.name, require_http.version);
1662
1845
  const cursor = new URL(request.url).searchParams.get("cursor");
1663
1846
  const metricBase = {
1664
- kind: getCollectionMetricKind(name$1),
1847
+ kind: getCollectionMetricKind(name$2),
1665
1848
  page: cursor != null,
1666
1849
  dispatcher: "built_in"
1667
1850
  };
@@ -1702,7 +1885,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
1702
1885
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
1703
1886
  return await onNotFound(request);
1704
1887
  }
1705
- const items = filterCollectionItems(page.items, name$1, filterPredicate);
1888
+ const items = filterCollectionItems(page.items, name$2, filterPredicate);
1706
1889
  itemCount = items.length;
1707
1890
  span.setAttribute("fedify.collection.items", itemCount);
1708
1891
  return items;
@@ -1742,7 +1925,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
1742
1925
  } else {
1743
1926
  const uri = new URL(baseUri);
1744
1927
  uri.searchParams.set("cursor", cursor);
1745
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
1928
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
1746
1929
  kind: _opentelemetry_api.SpanKind.SERVER,
1747
1930
  attributes: {
1748
1931
  "activitypub.collection.id": uri.href,
@@ -1758,7 +1941,7 @@ async function handleCollection(request, { name: name$1, identifier, uriGetter,
1758
1941
  span.setStatus({ code: _opentelemetry_api.SpanStatusCode.ERROR });
1759
1942
  return await onNotFound(request);
1760
1943
  }
1761
- const items = filterCollectionItems(page.items, name$1, filterPredicate);
1944
+ const items = filterCollectionItems(page.items, name$2, filterPredicate);
1762
1945
  itemCount = items.length;
1763
1946
  span.setAttribute("fedify.collection.items", itemCount);
1764
1947
  return {
@@ -2813,8 +2996,8 @@ var CustomCollectionHandler = class {
2813
2996
  * @param CollectionPage The CollectionPage constructor.
2814
2997
  * @param filterPredicate Optional filter predicate for items.
2815
2998
  */
2816
- constructor(name$2, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
2817
- this.name = name$2;
2999
+ constructor(name$1, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate) {
3000
+ this.name = name$1;
2818
3001
  this.values = values;
2819
3002
  this.context = context;
2820
3003
  this.callbacks = callbacks;
@@ -3229,7 +3412,7 @@ async function respondWithObjectIfAcceptable(object, request, options) {
3229
3412
  return response;
3230
3413
  }
3231
3414
  function generateNonce() {
3232
- const bytes = new Uint8Array(16);
3415
+ const bytes = /* @__PURE__ */ new Uint8Array(16);
3233
3416
  crypto.getRandomValues(bytes);
3234
3417
  return btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
3235
3418
  }
@@ -3284,7 +3467,7 @@ async function buildAcceptSignatureHeader(policy, kv, noncePrefix) {
3284
3467
  }
3285
3468
  async function setKey(kv, key, policy) {
3286
3469
  const seconds = policy.nonceTtlSeconds ?? 300;
3287
- const ttl = Temporal$3.Duration.from({ seconds });
3470
+ const ttl = Temporal$5.Duration.from({ seconds });
3288
3471
  await kv.set(key, true, { ttl });
3289
3472
  }
3290
3473
  const DEF_COMPONENTS = [
@@ -3299,6 +3482,94 @@ const MIN_COMPONENTS = [
3299
3482
  "@authority"
3300
3483
  ];
3301
3484
  //#endregion
3485
+ //#region src/federation/kv.ts
3486
+ const Temporal$4 = require_http.Temporal;
3487
+ /**
3488
+ * A key–value store that stores values in memory.
3489
+ * Do not use this in production as it does not persist values.
3490
+ *
3491
+ * @since 0.5.0
3492
+ */
3493
+ var MemoryKvStore = class {
3494
+ #values = {};
3495
+ #encodeKey(key) {
3496
+ return JSON.stringify(key);
3497
+ }
3498
+ /**
3499
+ * {@inheritDoc KvStore.get}
3500
+ */
3501
+ get(key) {
3502
+ const encodedKey = this.#encodeKey(key);
3503
+ const entry = this.#values[encodedKey];
3504
+ if (entry == null) return Promise.resolve(void 0);
3505
+ const [value, expiration] = entry;
3506
+ if (expiration != null && Temporal$4.Now.instant().until(expiration).sign < 0) {
3507
+ delete this.#values[encodedKey];
3508
+ return Promise.resolve(void 0);
3509
+ }
3510
+ return Promise.resolve(value);
3511
+ }
3512
+ /**
3513
+ * {@inheritDoc KvStore.set}
3514
+ */
3515
+ set(key, value, options) {
3516
+ const encodedKey = this.#encodeKey(key);
3517
+ const expiration = options?.ttl == null ? null : Temporal$4.Now.instant().add(options.ttl.round({ largestUnit: "hour" }));
3518
+ this.#values[encodedKey] = [value, expiration];
3519
+ return Promise.resolve();
3520
+ }
3521
+ /**
3522
+ * {@inheritDoc KvStore.delete}
3523
+ */
3524
+ delete(key) {
3525
+ const encodedKey = this.#encodeKey(key);
3526
+ delete this.#values[encodedKey];
3527
+ return Promise.resolve();
3528
+ }
3529
+ /**
3530
+ * {@inheritDoc KvStore.cas}
3531
+ */
3532
+ cas(key, expectedValue, newValue, options) {
3533
+ const encodedKey = this.#encodeKey(key);
3534
+ const entry = this.#values[encodedKey];
3535
+ let currentValue;
3536
+ if (entry == null) currentValue = void 0;
3537
+ else {
3538
+ const [value, expiration] = entry;
3539
+ if (expiration != null && Temporal$4.Now.instant().until(expiration).sign < 0) {
3540
+ delete this.#values[encodedKey];
3541
+ currentValue = void 0;
3542
+ } else currentValue = value;
3543
+ }
3544
+ if (!(0, es_toolkit.isEqual)(currentValue, expectedValue)) return Promise.resolve(false);
3545
+ const expiration = options?.ttl == null ? null : Temporal$4.Now.instant().add(options.ttl.round({ largestUnit: "hour" }));
3546
+ this.#values[encodedKey] = [newValue, expiration];
3547
+ return Promise.resolve(true);
3548
+ }
3549
+ /**
3550
+ * {@inheritDoc KvStore.list}
3551
+ */
3552
+ async *list(prefix) {
3553
+ const now = Temporal$4.Now.instant();
3554
+ for (const [encodedKey, entry] of Object.entries(this.#values)) {
3555
+ const key = JSON.parse(encodedKey);
3556
+ if (prefix != null) {
3557
+ if (key.length < prefix.length) continue;
3558
+ if (!prefix.every((p, i) => key[i] === p)) continue;
3559
+ }
3560
+ const [value, expiration] = entry;
3561
+ if (expiration != null && now.until(expiration).sign < 0) {
3562
+ delete this.#values[encodedKey];
3563
+ continue;
3564
+ }
3565
+ yield {
3566
+ key,
3567
+ value
3568
+ };
3569
+ }
3570
+ }
3571
+ };
3572
+ //#endregion
3302
3573
  //#region src/nodeinfo/handler.ts
3303
3574
  /**
3304
3575
  * Handles a NodeInfo request. You would not typically call this function
@@ -3759,7 +4030,7 @@ function serializeDataPointType(dataPointType) {
3759
4030
  }
3760
4031
  //#endregion
3761
4032
  //#region src/federation/retry.ts
3762
- const Temporal$2 = require_temporal_polyfill.Temporal;
4033
+ const Temporal$3 = require_http.Temporal;
3763
4034
  /**
3764
4035
  * Creates an exponential backoff retry policy. The delay between retries
3765
4036
  * starts at the `initialDelay` and is multiplied by the `factor` for each
@@ -3771,8 +4042,8 @@ const Temporal$2 = require_temporal_polyfill.Temporal;
3771
4042
  * @since 0.12.0
3772
4043
  */
3773
4044
  function createExponentialBackoffPolicy(options = {}) {
3774
- const initialDelay = Temporal$2.Duration.from(options.initialDelay ?? { seconds: 1 });
3775
- const maxDelay = Temporal$2.Duration.from(options.maxDelay ?? { hours: 12 });
4045
+ const initialDelay = Temporal$3.Duration.from(options.initialDelay ?? { seconds: 1 });
4046
+ const maxDelay = Temporal$3.Duration.from(options.maxDelay ?? { hours: 12 });
3776
4047
  const maxAttempts = options.maxAttempts ?? 10;
3777
4048
  const factor = options.factor ?? 2;
3778
4049
  const jitter = options.jitter ?? true;
@@ -3784,8 +4055,8 @@ function createExponentialBackoffPolicy(options = {}) {
3784
4055
  milliseconds *= 1 + Math.random();
3785
4056
  milliseconds = Math.round(milliseconds);
3786
4057
  }
3787
- const delay = Temporal$2.Duration.from({ milliseconds });
3788
- return Temporal$2.Duration.compare(delay, maxDelay) > 0 ? maxDelay : delay;
4058
+ const delay = Temporal$3.Duration.from({ milliseconds });
4059
+ return Temporal$3.Duration.compare(delay, maxDelay) > 0 ? maxDelay : delay;
3789
4060
  };
3790
4061
  }
3791
4062
  //#endregion
@@ -3916,7 +4187,7 @@ const classReviver = (filter, init, set) => (seen, revive, node) => {
3916
4187
  *
3917
4188
  * @module
3918
4189
  */
3919
- const Temporal$1 = require_temporal_polyfill.Temporal;
4190
+ const Temporal$2 = require_http.Temporal;
3920
4191
  /**
3921
4192
  * Validates the task handle, plans deduplication, encodes every payload, then
3922
4193
  * dispatches the resulting messages to the queue. A single item flows through
@@ -3954,7 +4225,7 @@ const enqueueTasks = (ctx) => async function(task, items, options) {
3954
4225
  if (claim.rollback != null) try {
3955
4226
  await claim.rollback();
3956
4227
  } catch (rollbackError) {
3957
- logger$1.warn("Failed to roll back the deduplication marker for task {taskName} after a failed enqueue; it will expire by TTL. {rollbackError}", {
4228
+ logger$2.warn("Failed to roll back the deduplication marker for task {taskName} after a failed enqueue; it will expire by TTL. {rollbackError}", {
3958
4229
  taskName: task.name,
3959
4230
  rollbackError
3960
4231
  });
@@ -3962,7 +4233,7 @@ const enqueueTasks = (ctx) => async function(task, items, options) {
3962
4233
  throw error;
3963
4234
  }
3964
4235
  };
3965
- const getDurationIfDefined = (item) => item == null ? void 0 : Temporal$1.Duration.from(item);
4236
+ const getDurationIfDefined = (item) => item == null ? void 0 : Temporal$2.Duration.from(item);
3966
4237
  /**
3967
4238
  * Decides how a `deduplicationKey` (if any) is honored: forwarded to a native
3968
4239
  * queue, claimed via `cas`, or—when neither is available—dropped or rejected
@@ -4013,7 +4284,7 @@ async function claimDeduplication(ctx, plan, taskName) {
4013
4284
  }
4014
4285
  };
4015
4286
  }
4016
- case "open": logger$1.debug("deduplicationKey {deduplicationKey} for task {taskName} ignored: the message queue declares no nativeDeduplication and the key-value store has no cas; proceeding (taskDeduplicationFallback is \"open\").", {
4287
+ case "open": logger$2.debug("deduplicationKey {deduplicationKey} for task {taskName} ignored: the message queue declares no nativeDeduplication and the key-value store has no cas; proceeding (taskDeduplicationFallback is \"open\").", {
4017
4288
  deduplicationKey: plan.key,
4018
4289
  taskName
4019
4290
  });
@@ -4068,13 +4339,13 @@ const encodeTaskMessage = (codec, origin, task, options) => async (data) => {
4068
4339
  baseUrl: origin,
4069
4340
  taskName: task.name,
4070
4341
  data: encoded,
4071
- started: Temporal$1.Now.instant().toString(),
4342
+ started: Temporal$2.Now.instant().toString(),
4072
4343
  attempt: 0,
4073
4344
  orderingKey: options.orderingKey,
4074
4345
  traceContext: carrier
4075
4346
  };
4076
4347
  };
4077
- const logger$1 = (0, _logtape_logtape.getLogger)([
4348
+ const logger$2 = (0, _logtape_logtape.getLogger)([
4078
4349
  "fedify",
4079
4350
  "federation",
4080
4351
  "task"
@@ -4102,11 +4373,29 @@ const classifyTaskError = (error) => require_http.isAbortError(error) ? { outcom
4102
4373
  };
4103
4374
  //#endregion
4104
4375
  //#region src/federation/webfinger.ts
4105
- const logger = (0, _logtape_logtape.getLogger)([
4376
+ const logger$1 = (0, _logtape_logtape.getLogger)([
4106
4377
  "fedify",
4107
4378
  "webfinger",
4108
4379
  "server"
4109
4380
  ]);
4381
+ function getWebFingerSubjectAndAliases({ resourceUrl, actorUri, preferredUsername, acctUsername, host, contextHost }) {
4382
+ const aliases = [];
4383
+ let subject = resourceUrl.href;
4384
+ if (resourceUrl.protocol != "acct:" && preferredUsername != null) {
4385
+ aliases.push(`acct:${preferredUsername}@${host ?? contextHost}`);
4386
+ if (host != null && host !== contextHost) aliases.push(`acct:${preferredUsername}@${contextHost}`);
4387
+ }
4388
+ if (resourceUrl.href !== actorUri.href) aliases.push(actorUri.href);
4389
+ if (resourceUrl.protocol === "acct:" && host != null && host !== contextHost && !resourceUrl.href.endsWith(`@${host}`)) {
4390
+ subject = `acct:${preferredUsername ?? acctUsername}@${host}`;
4391
+ aliases.push(resourceUrl.href);
4392
+ if (preferredUsername != null && preferredUsername !== "" && preferredUsername !== acctUsername) aliases.push(`acct:${preferredUsername}@${contextHost}`);
4393
+ }
4394
+ return {
4395
+ subject,
4396
+ aliases
4397
+ };
4398
+ }
4110
4399
  /**
4111
4400
  * Handles a WebFinger request. You would not typically call this function
4112
4401
  * directly, but instead use {@link Federation.fetch} method.
@@ -4155,7 +4444,7 @@ async function handleWebFinger(request, options) {
4155
4444
  });
4156
4445
  }
4157
4446
  }
4158
- const WEBFINGER_HANDLE_SCHEME_WHITELIST = new Set([
4447
+ const WEBFINGER_HANDLE_SCHEME_WHITELIST = /* @__PURE__ */ new Set([
4159
4448
  "acct",
4160
4449
  "http",
4161
4450
  "https",
@@ -4184,7 +4473,7 @@ function classifyWebFingerHandleResult(response, notFoundResponse) {
4184
4473
  }
4185
4474
  async function handleWebFingerInternal(request, { context, host, actorDispatcher, actorHandleMapper, actorAliasMapper, onNotFound, span, webFingerLinksDispatcher }) {
4186
4475
  if (actorDispatcher == null) {
4187
- logger.error("Actor dispatcher is not set.");
4476
+ logger$1.error("Actor dispatcher is not set.");
4188
4477
  return await onNotFound(request);
4189
4478
  }
4190
4479
  const resource = context.url.searchParams.get("resource");
@@ -4200,17 +4489,18 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
4200
4489
  span?.setAttribute("webfinger.resource.scheme", resourceUrl.protocol.replace(/:$/, ""));
4201
4490
  async function mapUsernameToIdentifier(username) {
4202
4491
  if (actorHandleMapper == null) {
4203
- logger.error("No actor handle mapper is set; use the WebFinger username {username} as the actor's internal identifier.", { username });
4492
+ logger$1.error("No actor handle mapper is set; use the WebFinger username {username} as the actor's internal identifier.", { username });
4204
4493
  return username;
4205
4494
  }
4206
4495
  const identifier = await actorHandleMapper(context, username);
4207
4496
  if (identifier == null) {
4208
- logger.error("Actor {username} not found.", { username });
4497
+ logger$1.error("Actor {username} not found.", { username });
4209
4498
  return null;
4210
4499
  }
4211
4500
  return identifier;
4212
4501
  }
4213
4502
  let identifier = null;
4503
+ let acctUsername = null;
4214
4504
  const uriParsed = context.parseUri(resourceUrl);
4215
4505
  if (uriParsed?.type != "actor") {
4216
4506
  const match = /^acct:([^@]+)@([^@]+)$/.exec(resource);
@@ -4224,24 +4514,26 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
4224
4514
  const normalizedHost = portMatch == null ? (0, node_url.domainToASCII)(match[2].toLowerCase()) : (0, node_url.domainToASCII)(match[2].substring(0, portMatch.index).toLowerCase()) + portMatch[0];
4225
4515
  if (normalizedHost != context.url.host && normalizedHost != host) return await onNotFound(request);
4226
4516
  else {
4227
- identifier = await mapUsernameToIdentifier(match[1]);
4228
- resourceUrl = new URL(`acct:${match[1]}@${normalizedHost}`);
4517
+ acctUsername = match[1];
4518
+ identifier = await mapUsernameToIdentifier(acctUsername);
4519
+ resourceUrl = new URL(`acct:${acctUsername}@${normalizedHost}`);
4229
4520
  }
4230
4521
  }
4231
4522
  } else identifier = uriParsed.identifier;
4232
4523
  if (identifier == null) return await onNotFound(request);
4233
4524
  const actor = await actorDispatcher(context, identifier);
4234
4525
  if (actor == null) {
4235
- logger.error("Actor {identifier} not found.", { identifier });
4526
+ logger$1.error("Actor {identifier} not found.", { identifier });
4236
4527
  return await onNotFound(request);
4237
4528
  }
4238
4529
  if (actor instanceof _fedify_vocab.Tombstone) return new Response(null, {
4239
4530
  status: 410,
4240
4531
  headers: { "Access-Control-Allow-Origin": "*" }
4241
4532
  });
4533
+ const actorUri = context.getActorUri(identifier);
4242
4534
  const links = [{
4243
4535
  rel: "self",
4244
- href: context.getActorUri(identifier).href,
4536
+ href: actorUri.href,
4245
4537
  type: "application/activity+json"
4246
4538
  }];
4247
4539
  for (const url of actor.urls) if (url instanceof _fedify_vocab.Link && url.href != null) links.push({
@@ -4265,30 +4557,26 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
4265
4557
  const customLinks = await webFingerLinksDispatcher(context, resourceUrl);
4266
4558
  if (customLinks != null) for (const link of customLinks) links.push(link);
4267
4559
  }
4268
- const aliases = [];
4269
- const preferredUsername = actor.preferredUsername;
4270
- if (resourceUrl.protocol != "acct:" && preferredUsername != null) {
4271
- aliases.push(`acct:${preferredUsername}@${host ?? context.url.host}`);
4272
- if (host != null && host !== context.url.host) aliases.push(`acct:${preferredUsername}@${context.url.host}`);
4273
- }
4274
- if (resourceUrl.href !== context.getActorUri(identifier).href) aliases.push(context.getActorUri(identifier).href);
4275
- if (resourceUrl.protocol === "acct:" && host != null && host !== context.url.host && !resourceUrl.href.endsWith(`@${host}`)) {
4276
- const username = resourceUrl.href.replace(/^acct:/, "").replace(/@.*$/, "");
4277
- aliases.push(`acct:${username}@${host}`);
4278
- }
4279
- const jrd = {
4280
- subject: resourceUrl.href,
4560
+ const { subject, aliases } = getWebFingerSubjectAndAliases({
4561
+ resourceUrl,
4562
+ actorUri,
4563
+ preferredUsername: actor.preferredUsername,
4564
+ acctUsername,
4565
+ host,
4566
+ contextHost: context.url.host
4567
+ });
4568
+ return new Response(JSON.stringify({
4569
+ subject,
4281
4570
  aliases,
4282
4571
  links
4283
- };
4284
- return new Response(JSON.stringify(jrd), { headers: {
4572
+ }), { headers: {
4285
4573
  "Content-Type": "application/jrd+json",
4286
4574
  "Access-Control-Allow-Origin": "*"
4287
4575
  } });
4288
4576
  }
4289
4577
  //#endregion
4290
4578
  //#region src/federation/middleware.ts
4291
- var middleware_exports = /* @__PURE__ */ require_chunk.__exportAll({
4579
+ var middleware_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
4292
4580
  ContextImpl: () => ContextImpl,
4293
4581
  FederationImpl: () => FederationImpl,
4294
4582
  InboxContextImpl: () => InboxContextImpl,
@@ -4296,11 +4584,11 @@ var middleware_exports = /* @__PURE__ */ require_chunk.__exportAll({
4296
4584
  OutboxContextImpl: () => OutboxContextImpl,
4297
4585
  createFederation: () => createFederation
4298
4586
  });
4299
- const Temporal = require_temporal_polyfill.Temporal;
4587
+ const Temporal$1 = require_http.Temporal;
4300
4588
  const circuitBreakerCasWarningKvStores = /* @__PURE__ */ new WeakSet();
4301
4589
  let nextQueueDepthGaugeSourceId = 0;
4302
4590
  const retryAfterHttpDate = /* @__PURE__ */ new RegExp("^(?:(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun), \\d{2} (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \\d{4} \\d{2}:\\d{2}:\\d{2} GMT|(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), \\d{2}-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-\\d{2} \\d{2}:\\d{2}:\\d{2} GMT|(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (?: \\d|\\d{2}) \\d{2}:\\d{2}:\\d{2} \\d{4})$");
4303
- function parseRetryAfter(headers, now = Temporal.Now.instant()) {
4591
+ function parseRetryAfter(headers, now = Temporal$1.Now.instant()) {
4304
4592
  const value = headers.get("Retry-After");
4305
4593
  if (value == null) return void 0;
4306
4594
  const trimmed = value.trim();
@@ -4318,14 +4606,14 @@ function parseRetryAfter(headers, now = Temporal.Now.instant()) {
4318
4606
  }
4319
4607
  function parseRetryAfterDuration(durationLike) {
4320
4608
  try {
4321
- return Temporal.Duration.from(durationLike);
4609
+ return Temporal$1.Duration.from(durationLike);
4322
4610
  } catch (error) {
4323
4611
  if (error instanceof RangeError) return void 0;
4324
4612
  throw error;
4325
4613
  }
4326
4614
  }
4327
4615
  function clampNegativeDelay(delay) {
4328
- return delay.sign < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay;
4616
+ return delay.sign < 0 ? Temporal$1.Duration.from({ seconds: 0 }) : delay;
4329
4617
  }
4330
4618
  function getBenchmarkRelaxations(allowPrivateAddress, signatureTimeWindow) {
4331
4619
  const relaxations = [];
@@ -4341,7 +4629,7 @@ function getBenchmarkRelaxations(allowPrivateAddress, signatureTimeWindow) {
4341
4629
  secureDefaultSeconds: 3600
4342
4630
  });
4343
4631
  else try {
4344
- const seconds = Temporal.Duration.from(signatureTimeWindow).total({ unit: "seconds" });
4632
+ const seconds = Temporal$1.Duration.from(signatureTimeWindow).total({ unit: "seconds" });
4345
4633
  if (seconds !== 3600) relaxations.push({
4346
4634
  protection: "http_signature_time_window",
4347
4635
  effect: "changed",
@@ -4376,7 +4664,7 @@ function getBenchmarkTriggerOptions(benchmarkOptions) {
4376
4664
  };
4377
4665
  }
4378
4666
  function maxDelay(first, second) {
4379
- return Temporal.Duration.compare(first, second) >= 0 ? first : second;
4667
+ return Temporal$1.Duration.compare(first, second) >= 0 ? first : second;
4380
4668
  }
4381
4669
  function isTransportDeliveryError(error) {
4382
4670
  return error instanceof _fedify_vocab_runtime.FetchError || require_http.isAbortError(error);
@@ -4616,7 +4904,7 @@ var FederationImpl = class extends FederationBuilderImpl {
4616
4904
  this.inboxRetryPolicy = options.inboxRetryPolicy ?? createExponentialBackoffPolicy();
4617
4905
  this.taskRetryPolicy = options.taskRetryPolicy ?? createExponentialBackoffPolicy();
4618
4906
  this.taskQueueResolution = options.taskQueueResolution ?? "fallback";
4619
- this.taskDeduplicationTtl = Temporal.Duration.from(options.taskDeduplicationTtl ?? { hours: 1 });
4907
+ this.taskDeduplicationTtl = Temporal$1.Duration.from(options.taskDeduplicationTtl ?? { hours: 1 });
4620
4908
  this.taskDeduplicationFallback = options.taskDeduplicationFallback ?? "open";
4621
4909
  this.activityTransformers = options.activityTransformers ?? require_transformers.getDefaultActivityTransformers();
4622
4910
  this._tracerProvider = options.tracerProvider;
@@ -5014,7 +5302,7 @@ var FederationImpl = class extends FederationBuilderImpl {
5014
5302
  const getPolicyDelay = () => {
5015
5303
  if (!policyDelayCalculated) {
5016
5304
  retryPolicyDelay = this.outboxRetryPolicy({
5017
- elapsedTime: Temporal.Instant.from(message.started).until(Temporal.Now.instant()),
5305
+ elapsedTime: Temporal$1.Instant.from(message.started).until(Temporal$1.Now.instant()),
5018
5306
  attempts: message.attempt
5019
5307
  });
5020
5308
  policyDelayCalculated = true;
@@ -5203,7 +5491,7 @@ var FederationImpl = class extends FederationBuilderImpl {
5203
5491
  throw error;
5204
5492
  }
5205
5493
  const delay = this.inboxRetryPolicy({
5206
- elapsedTime: Temporal.Instant.from(message.started).until(Temporal.Now.instant()),
5494
+ elapsedTime: Temporal$1.Instant.from(message.started).until(Temporal$1.Now.instant()),
5207
5495
  attempts: message.attempt
5208
5496
  });
5209
5497
  if (delay != null) {
@@ -5362,7 +5650,7 @@ var FederationImpl = class extends FederationBuilderImpl {
5362
5650
  await handleRetriableFailure(error);
5363
5651
  return;
5364
5652
  }
5365
- if (cacheKey != null) await this.kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
5653
+ if (cacheKey != null) await this.kv.set(cacheKey, true, { ttl: Temporal$1.Duration.from({ days: 1 }) });
5366
5654
  logger.info("Activity {activityId} has been processed.", {
5367
5655
  activityId: activity?.id?.href,
5368
5656
  activity: message.activity,
@@ -5416,9 +5704,9 @@ var FederationImpl = class extends FederationBuilderImpl {
5416
5704
  const queue = this.resolveTaskQueue(def.name);
5417
5705
  if (queue?.nativeRetrial) throw error;
5418
5706
  const retryPolicy = def.retryPolicy ?? this.taskRetryPolicy;
5419
- let elapsedTime = Temporal.Duration.from({ seconds: 0 });
5707
+ let elapsedTime = Temporal$1.Duration.from({ seconds: 0 });
5420
5708
  try {
5421
- elapsedTime = Temporal.Instant.from(message.started).until(Temporal.Now.instant());
5709
+ elapsedTime = Temporal$1.Instant.from(message.started).until(Temporal$1.Now.instant());
5422
5710
  } catch (parseError) {
5423
5711
  logger.error("Custom task {taskName} has an unparsable started time {started}; treating elapsedTime as zero:\n{error}", {
5424
5712
  taskName: message.taskName,
@@ -7141,12 +7429,471 @@ function getRequestId(request) {
7141
7429
  return `req_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
7142
7430
  }
7143
7431
  //#endregion
7432
+ //#region src/federation/mq.ts
7433
+ const Temporal = require_http.Temporal;
7434
+ /**
7435
+ * A message queue that processes messages in the same process.
7436
+ * Do not use this in production as it does neither persist messages nor
7437
+ * distribute them across multiple processes.
7438
+ *
7439
+ * @since 0.5.0
7440
+ */
7441
+ var InProcessMessageQueue = class {
7442
+ #messages;
7443
+ #monitors;
7444
+ #pollIntervalMs;
7445
+ #delayedMessages;
7446
+ /**
7447
+ * Tracks which ordering keys are currently being processed to ensure
7448
+ * sequential processing for messages with the same key.
7449
+ */
7450
+ #processingKeys;
7451
+ /**
7452
+ * In-process message queue does not provide native retry mechanisms.
7453
+ * @since 1.7.0
7454
+ */
7455
+ nativeRetrial = false;
7456
+ /**
7457
+ * In-process message queue does not deduplicate messages natively.
7458
+ * @since 2.4.0
7459
+ */
7460
+ nativeDeduplication = false;
7461
+ /**
7462
+ * Constructs a new {@link InProcessMessageQueue} with the given options.
7463
+ * @param options Additional options for the in-process message queue.
7464
+ */
7465
+ constructor(options = {}) {
7466
+ this.#messages = [];
7467
+ this.#monitors = {};
7468
+ this.#pollIntervalMs = Temporal.Duration.from(options.pollInterval ?? { seconds: 5 }).total("millisecond");
7469
+ this.#delayedMessages = 0;
7470
+ this.#processingKeys = /* @__PURE__ */ new Set();
7471
+ }
7472
+ enqueue(message, options) {
7473
+ const delay = options?.delay == null ? 0 : Math.max(options.delay.total("millisecond"), 0);
7474
+ if (delay > 0) {
7475
+ this.#delayedMessages++;
7476
+ setTimeout(() => {
7477
+ this.#delayedMessages--;
7478
+ this.#enqueueReady(message, options);
7479
+ }, delay);
7480
+ return Promise.resolve();
7481
+ }
7482
+ this.#enqueueReady(message, options);
7483
+ return Promise.resolve();
7484
+ }
7485
+ #enqueueReady(message, options) {
7486
+ const orderingKey = options?.orderingKey ?? null;
7487
+ this.#messages.push({
7488
+ message,
7489
+ orderingKey
7490
+ });
7491
+ this.#notifyMonitors();
7492
+ }
7493
+ #notifyMonitors() {
7494
+ for (const monitorId in this.#monitors) this.#monitors[monitorId]();
7495
+ }
7496
+ enqueueMany(messages, options) {
7497
+ if (messages.length === 0) return Promise.resolve();
7498
+ const delay = options?.delay == null ? 0 : Math.max(options.delay.total("millisecond"), 0);
7499
+ if (delay > 0) {
7500
+ const delayedCount = messages.length;
7501
+ const deferredMessages = [...messages];
7502
+ this.#delayedMessages += delayedCount;
7503
+ setTimeout(() => {
7504
+ this.#delayedMessages -= delayedCount;
7505
+ this.#enqueueManyReady(deferredMessages, options);
7506
+ }, delay);
7507
+ return Promise.resolve();
7508
+ }
7509
+ this.#enqueueManyReady(messages, options);
7510
+ return Promise.resolve();
7511
+ }
7512
+ #enqueueManyReady(messages, options) {
7513
+ const orderingKey = options?.orderingKey ?? null;
7514
+ for (const message of messages) this.#messages.push({
7515
+ message,
7516
+ orderingKey
7517
+ });
7518
+ this.#notifyMonitors();
7519
+ }
7520
+ async listen(handler, options = {}) {
7521
+ const signal = options.signal;
7522
+ while (signal == null || !signal.aborted) {
7523
+ const idx = this.#messages.findIndex((m) => m.orderingKey == null || !this.#processingKeys.has(m.orderingKey));
7524
+ if (idx >= 0) {
7525
+ const { message, orderingKey } = this.#messages.splice(idx, 1)[0];
7526
+ if (orderingKey != null) this.#processingKeys.add(orderingKey);
7527
+ try {
7528
+ await handler(message);
7529
+ } finally {
7530
+ if (orderingKey != null) this.#processingKeys.delete(orderingKey);
7531
+ }
7532
+ } else if (this.#messages.length === 0) await this.#wait(this.#pollIntervalMs, signal);
7533
+ else await this.#wait(10, signal);
7534
+ }
7535
+ }
7536
+ getDepth() {
7537
+ const ready = this.#messages.length;
7538
+ const delayed = this.#delayedMessages;
7539
+ return Promise.resolve({
7540
+ queued: ready + delayed,
7541
+ ready,
7542
+ delayed
7543
+ });
7544
+ }
7545
+ #wait(ms, signal) {
7546
+ let timer = null;
7547
+ return Promise.any([new Promise((resolve) => {
7548
+ signal?.addEventListener("abort", () => {
7549
+ if (timer != null) clearTimeout(timer);
7550
+ resolve();
7551
+ }, { once: true });
7552
+ const monitorId = crypto.randomUUID();
7553
+ this.#monitors[monitorId] = () => {
7554
+ delete this.#monitors[monitorId];
7555
+ if (timer != null) clearTimeout(timer);
7556
+ resolve();
7557
+ };
7558
+ }), new Promise((resolve) => timer = setTimeout(resolve, ms))]);
7559
+ }
7560
+ };
7561
+ /**
7562
+ * A message queue that processes messages in parallel. It takes another
7563
+ * {@link MessageQueue}, and processes messages in parallel up to a certain
7564
+ * number of workers.
7565
+ *
7566
+ * Actually, it's rather a decorator than a queue itself.
7567
+ *
7568
+ * Note that the workers do not run in truly parallel, in the sense that they
7569
+ * are not running in separate threads or processes. They are running in the
7570
+ * same process, but are scheduled to run in parallel. Hence, this is useful
7571
+ * for I/O-bound tasks, but not for CPU-bound tasks, which is okay for Fedify's
7572
+ * workloads.
7573
+ *
7574
+ * When using `ParallelMessageQueue`, the ordering guarantee is preserved
7575
+ * *only if* the underlying queue implementation delivers messages in a wrapper
7576
+ * format that includes the `__fedify_ordering_key__` property. Currently,
7577
+ * only `DenoKvMessageQueue` and `WorkersMessageQueue` use this format.
7578
+ * For other queue implementations (e.g., `InProcessMessageQueue`,
7579
+ * `RedisMessageQueue`, `PostgresMessageQueue`, `SqliteMessageQueue`,
7580
+ * `AmqpMessageQueue`), the ordering key cannot be detected by
7581
+ * `ParallelMessageQueue`, so ordering guarantees are handled by those
7582
+ * implementations directly rather than at the `ParallelMessageQueue` level.
7583
+ *
7584
+ * Messages with the same ordering key will never be processed concurrently
7585
+ * by different workers, ensuring sequential processing within each key.
7586
+ * Messages with different ordering keys (or no ordering key) can still be
7587
+ * processed in parallel.
7588
+ *
7589
+ * @since 1.0.0
7590
+ */
7591
+ var ParallelMessageQueue = class ParallelMessageQueue {
7592
+ queue;
7593
+ workers;
7594
+ /**
7595
+ * Inherits the native retry capability from the wrapped queue.
7596
+ * @since 1.7.0
7597
+ */
7598
+ nativeRetrial;
7599
+ /**
7600
+ * Inherits the native deduplication capability from the wrapped queue.
7601
+ * @since 2.4.0
7602
+ */
7603
+ nativeDeduplication;
7604
+ /**
7605
+ * Inherits the atomic batch capability from the wrapped queue.
7606
+ * @since 2.4.0
7607
+ */
7608
+ atomicEnqueueMany;
7609
+ getDepth;
7610
+ /**
7611
+ * Tracks which ordering keys are currently being processed to ensure
7612
+ * sequential processing for messages with the same key.
7613
+ */
7614
+ #processingKeys = /* @__PURE__ */ new Set();
7615
+ /**
7616
+ * Pending messages waiting for their ordering key to become available.
7617
+ */
7618
+ #pendingMessages = [];
7619
+ /**
7620
+ * Constructs a new {@link ParallelMessageQueue} with the given queue and
7621
+ * number of workers.
7622
+ * @param queue The message queue to use under the hood. Note that
7623
+ * {@link ParallelMessageQueue} cannot be nested.
7624
+ * @param workers The number of workers to process messages in parallel.
7625
+ * @throws {TypeError} If the given queue is an instance of
7626
+ * {@link ParallelMessageQueue}.
7627
+ */
7628
+ constructor(queue, workers) {
7629
+ if (queue instanceof ParallelMessageQueue) throw new TypeError("Cannot nest ParallelMessageQueue.");
7630
+ this.queue = queue;
7631
+ this.workers = workers;
7632
+ this.nativeRetrial = queue.nativeRetrial;
7633
+ this.nativeDeduplication = queue.nativeDeduplication;
7634
+ this.atomicEnqueueMany = queue.enqueueMany == null ? false : queue.atomicEnqueueMany;
7635
+ if (queue.getDepth != null) this.getDepth = () => queue.getDepth();
7636
+ }
7637
+ enqueue(message, options) {
7638
+ return this.queue.enqueue(message, options);
7639
+ }
7640
+ async enqueueMany(messages, options) {
7641
+ if (this.queue.enqueueMany == null) {
7642
+ if (messages.length === 1) {
7643
+ await this.queue.enqueue(messages[0], options);
7644
+ return;
7645
+ }
7646
+ 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.");
7647
+ const errors = (await Promise.allSettled(messages.map((message) => this.queue.enqueue(message, options)))).filter((r) => r.status === "rejected").map((r) => r.reason);
7648
+ if (errors.length > 1) throw new AggregateError(errors, "Failed to enqueue messages.");
7649
+ else if (errors.length === 1) throw errors[0];
7650
+ return;
7651
+ }
7652
+ await this.queue.enqueueMany(messages, options);
7653
+ }
7654
+ /**
7655
+ * Extracts ordering key from a message if present.
7656
+ *
7657
+ * This method only works for queue implementations that deliver messages
7658
+ * in the wrapper format with `__fedify_ordering_key__` property. Currently,
7659
+ * only `DenoKvMessageQueue` and `WorkersMessageQueue` use this format.
7660
+ *
7661
+ * For other queue implementations (`InProcessMessageQueue`,
7662
+ * `RedisMessageQueue`, `PostgresMessageQueue`, `SqliteMessageQueue`,
7663
+ * `AmqpMessageQueue`), messages are delivered as raw payloads without the
7664
+ * wrapper, so the ordering key cannot be detected here. Those
7665
+ * implementations handle ordering guarantees internally.
7666
+ */
7667
+ #extractOrderingKey(message) {
7668
+ if (message != null && typeof message === "object") {
7669
+ if ("__fedify_ordering_key__" in message) return message.__fedify_ordering_key__;
7670
+ }
7671
+ }
7672
+ listen(handler, options = {}) {
7673
+ const workers = /* @__PURE__ */ new Map();
7674
+ return this.queue.listen(async (message) => {
7675
+ while (workers.size >= this.workers) {
7676
+ const consumedId = await Promise.any(workers.values());
7677
+ workers.delete(consumedId);
7678
+ }
7679
+ const workerId = crypto.randomUUID();
7680
+ const orderingKey = this.#extractOrderingKey(message);
7681
+ if (orderingKey != null && this.#processingKeys.has(orderingKey)) await new Promise((resolve) => {
7682
+ this.#pendingMessages.push({
7683
+ message,
7684
+ orderingKey,
7685
+ resolve
7686
+ });
7687
+ });
7688
+ if (orderingKey != null) this.#processingKeys.add(orderingKey);
7689
+ const promise = this.#work(workerId, handler, message, orderingKey);
7690
+ workers.set(workerId, promise);
7691
+ }, options);
7692
+ }
7693
+ async #work(workerId, handler, message, orderingKey) {
7694
+ await this.#sleep(0);
7695
+ try {
7696
+ await handler(message);
7697
+ } finally {
7698
+ if (orderingKey != null) {
7699
+ this.#processingKeys.delete(orderingKey);
7700
+ const pendingIdx = this.#pendingMessages.findIndex((p) => p.orderingKey === orderingKey);
7701
+ if (pendingIdx >= 0) this.#pendingMessages.splice(pendingIdx, 1)[0].resolve();
7702
+ }
7703
+ }
7704
+ return workerId;
7705
+ }
7706
+ #sleep(ms) {
7707
+ return new Promise((resolve) => setTimeout(resolve, ms));
7708
+ }
7709
+ };
7710
+ //#endregion
7711
+ //#region src/federation/router.ts
7712
+ const logger = (0, _logtape_logtape.getLogger)([
7713
+ "fedify",
7714
+ "federation",
7715
+ "router",
7716
+ "deprecated"
7717
+ ]);
7718
+ let deprecationWarned = false;
7719
+ function warnDeprecated() {
7720
+ if (deprecationWarned) return;
7721
+ deprecationWarned = true;
7722
+ logger.warn("The `Router` and `RouterError` classes from `@fedify/fedify` are deprecated. Please use `Router` from `@fedify/uri-template` instead.");
7723
+ }
7724
+ /**
7725
+ * URL router and constructor based on URI Template
7726
+ * ([RFC 6570](https://tools.ietf.org/html/rfc6570)).
7727
+ *
7728
+ * @deprecated Import `Router` from `@fedify/uri-template` instead. This class
7729
+ * remains only for compatibility with older Fedify code. The
7730
+ * `@fedify/uri-template` router is the replacement implementation
7731
+ * and should be used directly in new code.
7732
+ */
7733
+ var Router = class Router {
7734
+ #router;
7735
+ /**
7736
+ * Create a new {@link Router}.
7737
+ * @param options Options for the router.
7738
+ * @deprecated Use `new Router(options)` from `@fedify/uri-template`
7739
+ * instead.
7740
+ */
7741
+ constructor(options) {
7742
+ this.#router = convertRouterError(() => new _fedify_uri_template.Router(options));
7743
+ }
7744
+ /**
7745
+ * Whether to ignore trailing slashes when matching paths.
7746
+ * @deprecated Use `Router` from `@fedify/uri-template` instead. This
7747
+ * accessor forwards to the underlying `@fedify/uri-template`
7748
+ * router so that post-construction mutation keeps working as
7749
+ * in older Fedify code.
7750
+ */
7751
+ get trailingSlashInsensitive() {
7752
+ return this.#router.trailingSlashInsensitive;
7753
+ }
7754
+ set trailingSlashInsensitive(value) {
7755
+ this.#router.trailingSlashInsensitive = value;
7756
+ }
7757
+ /**
7758
+ * Clones this router.
7759
+ * @deprecated Use `Router` from `@fedify/uri-template` instead.
7760
+ */
7761
+ clone() {
7762
+ return convertRouterError(() => {
7763
+ const clone = new Router();
7764
+ clone.#router = this.#router.clone();
7765
+ return clone;
7766
+ });
7767
+ }
7768
+ /**
7769
+ * Checks if a path name exists in the router.
7770
+ * @param name The name of the path.
7771
+ * @returns `true` if the path name exists, otherwise `false`.
7772
+ * @deprecated Use `Router` from `@fedify/uri-template` instead.
7773
+ */
7774
+ has(name) {
7775
+ return convertRouterError(() => this.#router.has(name));
7776
+ }
7777
+ /**
7778
+ * Adds a new path rule to the router.
7779
+ * @param template The path pattern.
7780
+ * @param name The name of the path.
7781
+ * @returns The names of the variables in the path pattern.
7782
+ * @deprecated Use `Router` from `@fedify/uri-template` instead. In this
7783
+ * compatibility class, `add()` both registers the route and
7784
+ * returns the variables in the path pattern. In
7785
+ * `@fedify/uri-template`, these two responsibilities are split:
7786
+ * `router.add(template, name)` registers the route and returns
7787
+ * `void`, while the pure static method
7788
+ * `Router.variables(template)` returns the variable names. To
7789
+ * migrate, call `Router.variables(template)` when variables are
7790
+ * needed, then call `router.add(template, name)` to register the
7791
+ * route.
7792
+ */
7793
+ add(template, name) {
7794
+ return convertRouterError(() => {
7795
+ (0, _fedify_uri_template.assertPath)(template);
7796
+ this.#router.add(template, name);
7797
+ return _fedify_uri_template.Router.variables(template);
7798
+ });
7799
+ }
7800
+ /**
7801
+ * Resolves a path name and values from a URL, if any match.
7802
+ * @param url The URL to resolve.
7803
+ * @returns The name of the path and its values, if any match. Otherwise,
7804
+ * `null`.
7805
+ * @deprecated Use `Router` from `@fedify/uri-template` instead. Unlike the
7806
+ * stricter `@fedify/uri-template` router, this compatibility
7807
+ * method keeps the old Fedify 2.x contract of returning `null`
7808
+ * (rather than throwing) for inputs that are not router paths.
7809
+ */
7810
+ route(url) {
7811
+ return convertRouterError(() => {
7812
+ if (!(0, _fedify_uri_template.isPath)(url)) return null;
7813
+ return this.#router.route(url);
7814
+ });
7815
+ }
7816
+ /**
7817
+ * Constructs a URL/path from a path name and values.
7818
+ * @param name The name of the path.
7819
+ * @param values The values to expand the path with.
7820
+ * @returns The URL/path, if the name exists. Otherwise, `null`.
7821
+ * @deprecated Use `Router` from `@fedify/uri-template` instead.
7822
+ */
7823
+ build(name, values) {
7824
+ return convertRouterError(() => this.#router.build(name, values));
7825
+ }
7826
+ };
7827
+ /**
7828
+ * An error thrown by the {@link Router}.
7829
+ * @deprecated Import `RouterError` from `@fedify/uri-template` instead.
7830
+ */
7831
+ var RouterError = class extends _fedify_uri_template.RouterError {
7832
+ /**
7833
+ * Treats every `RouterError` from `@fedify/uri-template` as an instance of
7834
+ * this deprecated class.
7835
+ *
7836
+ * @deprecated Import `RouterError` from `@fedify/uri-template` instead.
7837
+ */
7838
+ static [Symbol.hasInstance](instance) {
7839
+ return instance instanceof _fedify_uri_template.RouterError;
7840
+ }
7841
+ /**
7842
+ * Create a new {@link RouterError}.
7843
+ * @param message The error message.
7844
+ * @deprecated Import `RouterError` from `@fedify/uri-template` instead.
7845
+ */
7846
+ constructor(message) {
7847
+ super(message);
7848
+ warnDeprecated();
7849
+ }
7850
+ };
7851
+ function convertRouterError(func) {
7852
+ try {
7853
+ warnDeprecated();
7854
+ return func();
7855
+ } catch (error) {
7856
+ if (error instanceof _fedify_uri_template.RouterError) throw new RouterError(error.message);
7857
+ throw error;
7858
+ }
7859
+ }
7860
+ //#endregion
7144
7861
  Object.defineProperty(exports, "CircuitBreaker", {
7145
7862
  enumerable: true,
7146
7863
  get: function() {
7147
7864
  return CircuitBreaker;
7148
7865
  }
7149
7866
  });
7867
+ Object.defineProperty(exports, "InProcessMessageQueue", {
7868
+ enumerable: true,
7869
+ get: function() {
7870
+ return InProcessMessageQueue;
7871
+ }
7872
+ });
7873
+ Object.defineProperty(exports, "MemoryKvStore", {
7874
+ enumerable: true,
7875
+ get: function() {
7876
+ return MemoryKvStore;
7877
+ }
7878
+ });
7879
+ Object.defineProperty(exports, "ParallelMessageQueue", {
7880
+ enumerable: true,
7881
+ get: function() {
7882
+ return ParallelMessageQueue;
7883
+ }
7884
+ });
7885
+ Object.defineProperty(exports, "Router", {
7886
+ enumerable: true,
7887
+ get: function() {
7888
+ return Router;
7889
+ }
7890
+ });
7891
+ Object.defineProperty(exports, "RouterError", {
7892
+ enumerable: true,
7893
+ get: function() {
7894
+ return RouterError;
7895
+ }
7896
+ });
7150
7897
  Object.defineProperty(exports, "SendActivityError", {
7151
7898
  enumerable: true,
7152
7899
  get: function() {
@@ -7189,12 +7936,6 @@ Object.defineProperty(exports, "handleWebFinger", {
7189
7936
  return handleWebFinger;
7190
7937
  }
7191
7938
  });
7192
- Object.defineProperty(exports, "middleware_exports", {
7193
- enumerable: true,
7194
- get: function() {
7195
- return middleware_exports;
7196
- }
7197
- });
7198
7939
  Object.defineProperty(exports, "normalizeCircuitBreakerOptions", {
7199
7940
  enumerable: true,
7200
7941
  get: function() {