@fedify/fedify 2.3.1 → 2.3.2-dev.1556

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 (62) hide show
  1. package/README.md +10 -7
  2. package/dist/{builder-hivpmhlD.mjs → builder-DosLp_w5.mjs} +2 -2
  3. package/dist/{circuit-breaker-CSWsyoef.mjs → circuit-breaker-DLS_n1x5.mjs} +190 -7
  4. package/dist/circuit-breaker-test-utils-BVnv3T76.mjs +14 -0
  5. package/dist/compat/mod.d.cts +1 -1
  6. package/dist/compat/mod.d.ts +1 -1
  7. package/dist/compat/transformers.test.mjs +1 -1
  8. package/dist/{context-BBVLF7lx.d.cts → context-Bjswu3eC.d.cts} +18 -1
  9. package/dist/{context-BU6jSQdo.d.ts → context-COxc_O-c.d.ts} +18 -1
  10. package/dist/{deno-C8RG0HM3.mjs → deno-Bg8I1L40.mjs} +1 -1
  11. package/dist/{docloader-YZA0m_3O.mjs → docloader-Dxsk3R8v.mjs} +2 -2
  12. package/dist/federation/builder.test.mjs +1 -1
  13. package/dist/federation/circuit-breaker.test.mjs +622 -3
  14. package/dist/federation/handler.test.mjs +3 -3
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/metrics.test.mjs +1 -1
  17. package/dist/federation/middleware.test.mjs +16 -8
  18. package/dist/federation/mod.cjs +1 -1
  19. package/dist/federation/mod.d.cts +2 -2
  20. package/dist/federation/mod.d.ts +2 -2
  21. package/dist/federation/mod.js +1 -1
  22. package/dist/federation/send.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-PCt0862M.mjs → http-BaUm6xbz.mjs} +3 -3
  26. package/dist/{http-DPOMa1DD.js → http-Bw8wN_Uk.js} +1 -1
  27. package/dist/{http-BajqV0pH.cjs → http-y8Cigpuq.cjs} +1 -1
  28. package/dist/{key-BKYwmbur.mjs → key-DOnwUeY9.mjs} +2 -2
  29. package/dist/{kv-cache-CVG3m2_-.cjs → kv-cache-B9AAXEOS.cjs} +1 -1
  30. package/dist/{kv-cache-Ceg0c1wh.mjs → kv-cache-CE_QUNa6.mjs} +1 -1
  31. package/dist/{kv-cache-IjRceERu.js → kv-cache-DilyIIfz.js} +1 -1
  32. package/dist/{ld-Dcw2skfn.mjs → ld-CiONXo2U.mjs} +3 -3
  33. package/dist/{metrics-Pue1x4ta.mjs → metrics-CmX7KEfY.mjs} +1 -1
  34. package/dist/{middleware-By8lqc18.js → middleware-BEhF8uxY.js} +193 -10
  35. package/dist/{middleware-CyxqoeW9.mjs → middleware-CRMohX8-.mjs} +1 -1
  36. package/dist/{middleware-WU_5fbGS.mjs → middleware-Cf0dFqr9.mjs} +13 -13
  37. package/dist/{middleware-BiXEAbn2.cjs → middleware-DZf3vcun.cjs} +193 -10
  38. package/dist/{mod-C0F6kvgS.d.cts → mod-0bIqGrKQ.d.cts} +1 -1
  39. package/dist/{mod-vPYVoa5n.d.ts → mod-3H9Gym5G.d.ts} +1 -1
  40. package/dist/mod.cjs +4 -4
  41. package/dist/mod.d.cts +2 -2
  42. package/dist/mod.d.ts +2 -2
  43. package/dist/mod.js +4 -4
  44. package/dist/nodeinfo/handler.test.mjs +1 -1
  45. package/dist/{owner-pVJ0Twi7.mjs → owner-C7h5_D1P.mjs} +2 -2
  46. package/dist/{proof-HzavUYxN.cjs → proof-BKOUYRRh.cjs} +1 -1
  47. package/dist/{proof-pXYODmNV.mjs → proof-BVuILGqP.mjs} +3 -3
  48. package/dist/{proof-DA8Kbchf.js → proof-DNlVt3rz.js} +1 -1
  49. package/dist/{send-BfAp60BJ.mjs → send-7kSXrsl4.mjs} +3 -3
  50. package/dist/sig/http.test.mjs +2 -2
  51. package/dist/sig/key.test.mjs +1 -1
  52. package/dist/sig/ld.test.mjs +2 -2
  53. package/dist/sig/mod.cjs +2 -2
  54. package/dist/sig/mod.js +2 -2
  55. package/dist/sig/owner.test.mjs +1 -1
  56. package/dist/sig/proof.test.mjs +1 -1
  57. package/dist/{temporal-HZHBbbk4.mjs → temporal-D3Bspq6d.mjs} +1 -1
  58. package/dist/utils/docloader.test.mjs +2 -2
  59. package/dist/utils/kv-cache.test.mjs +1 -1
  60. package/dist/utils/mod.cjs +1 -1
  61. package/dist/utils/mod.js +1 -1
  62. package/package.json +6 -6
@@ -2,10 +2,10 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  import { t as __exportAll } from "./chunk-CRNNMoPX.js";
4
4
  import { r as getDefaultActivityTransformers } from "./transformers-BGMIq1cs.js";
5
- import { A as formatAcceptSignature, D as recordOutboxEnqueue, E as recordOutboxActivity, F as version, O as recordWebFingerHandle, P as name, S as recordCollectionTotalItems, T as recordInboxActivity, a as verifyRequestDetailed, b as recordCollectionPageItems, d as validateCryptoKey, f as getDurationMs, g as isAbortError, h as instrumentDocumentLoader, i as verifyRequest, k as registerQueueDepthGauge, m as getRemoteHost, n as parseRfc9421SignatureInput, o as exportJwk, p as getFederationMetrics, t as doubleKnock, u as importJwk, v as recordCircuitBreakerStateChange, w as recordFanoutRecipients, x as recordCollectionRequest, y as recordCollectionDispatchDuration } from "./http-DPOMa1DD.js";
6
- import { _ as hasSignatureLike, b as signJsonLd, c as getKeyOwner, f as compactJsonLd, g as hasSignature, h as getNormalizationContextLoader, i as verifyObject, l as InvalidContextReferenceError, m as detachSignature, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, r as signObject, s as doesActorOwnKey, u as assertSafeJsonLd, v as isClearlyMalformedContextReference, w as wrapContextLoaderForJsonLd, x as verifyCompactJsonLd, y as isInvalidUrlTypeError } from "./proof-DA8Kbchf.js";
5
+ import { A as formatAcceptSignature, D as recordOutboxEnqueue, E as recordOutboxActivity, F as version, O as recordWebFingerHandle, P as name, S as recordCollectionTotalItems, T as recordInboxActivity, a as verifyRequestDetailed, b as recordCollectionPageItems, d as validateCryptoKey, f as getDurationMs, g as isAbortError, h as instrumentDocumentLoader, i as verifyRequest, k as registerQueueDepthGauge, m as getRemoteHost, n as parseRfc9421SignatureInput, o as exportJwk, p as getFederationMetrics, t as doubleKnock, u as importJwk, v as recordCircuitBreakerStateChange, w as recordFanoutRecipients, x as recordCollectionRequest, y as recordCollectionDispatchDuration } from "./http-Bw8wN_Uk.js";
6
+ import { _ as hasSignatureLike, b as signJsonLd, c as getKeyOwner, f as compactJsonLd, g as hasSignature, h as getNormalizationContextLoader, i as verifyObject, l as InvalidContextReferenceError, m as detachSignature, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, r as signObject, s as doesActorOwnKey, u as assertSafeJsonLd, v as isClearlyMalformedContextReference, w as wrapContextLoaderForJsonLd, x as verifyCompactJsonLd, y as isInvalidUrlTypeError } from "./proof-DNlVt3rz.js";
7
7
  import { n as getNodeInfo, t as nodeInfoToJson } from "./types-CAY3OdLq.js";
8
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-IjRceERu.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-DilyIIfz.js";
9
9
  import { getLogger, withContext } from "@logtape/logtape";
10
10
  import { Router, RouterError, assertPath } from "@fedify/uri-template";
11
11
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
@@ -680,6 +680,13 @@ function createFederationBuilder() {
680
680
  //#endregion
681
681
  //#region src/federation/circuit-breaker.ts
682
682
  const MAX_CUSTOM_FAILURE_HISTORY = 100;
683
+ const LEGACY_SWEEP_MARKER = ["__fedify_meta", "circuit_breaker_state_ttl_sweep_v1"];
684
+ const LEGACY_SWEEP_DELETING_MARKER = { __fedifyDeletingCircuitBreakerLegacyState: true };
685
+ const CIRCUIT_BREAKER_STATE_VERSION = 1;
686
+ const LEGACY_SWEEP_LOCK_TTL = Temporal.Duration.from({ minutes: 5 });
687
+ const LEGACY_SWEEP_RETRY_WINDOW = Temporal.Duration.from({ hours: 168 });
688
+ const LEGACY_SWEEP_WAIT_INTERVAL = 10;
689
+ const LEGACY_SWEEP_CAS_ATTEMPTS = 3;
683
690
  /**
684
691
  * Tracks reachability state for remote outbox delivery hosts.
685
692
  * @since 2.3.0
@@ -689,6 +696,8 @@ var CircuitBreaker = class {
689
696
  #prefix;
690
697
  #options;
691
698
  #now;
699
+ #legacySweep;
700
+ #legacySweepDone;
692
701
  #stateChangeObserver;
693
702
  constructor(options) {
694
703
  this.#kv = options.kv;
@@ -705,6 +714,7 @@ var CircuitBreaker = class {
705
714
  return now.until(this.#capHeldRetryAt(now, heldSince, now.add(delay)));
706
715
  }
707
716
  async beforeSend(remoteHost, message) {
717
+ this.#sweepLegacyStates();
708
718
  const heldSince = parseHeldSince(message.circuitHeldSince);
709
719
  const now = this.#now();
710
720
  if (heldSince != null && Temporal.Instant.compare(heldSince.add(this.#options.heldActivityTtl), now) <= 0) return {
@@ -786,6 +796,7 @@ var CircuitBreaker = class {
786
796
  throw new Error(`Failed to update circuit breaker state for ${remoteHost}`);
787
797
  }
788
798
  async recordSuccess(remoteHost) {
799
+ this.#sweepLegacyStates();
789
800
  for (let attempt = 0; attempt < 10; attempt++) {
790
801
  const oldState = await this.#get(remoteHost);
791
802
  if (oldState == null) return void 0;
@@ -806,6 +817,7 @@ var CircuitBreaker = class {
806
817
  return await this.recordSuccess(remoteHost);
807
818
  }
808
819
  async recordFailure(remoteHost) {
820
+ this.#sweepLegacyStates();
809
821
  const now = this.#now();
810
822
  for (let attempt = 0; attempt < 10; attempt++) {
811
823
  const oldState = await this.#get(remoteHost);
@@ -853,26 +865,122 @@ var CircuitBreaker = class {
853
865
  }
854
866
  }
855
867
  async getState(remoteHost) {
856
- return await this.#get(remoteHost);
868
+ this.#sweepLegacyStates();
869
+ return stripStoredCircuitBreakerState(await this.#get(remoteHost));
870
+ }
871
+ /**
872
+ * The currently running background legacy sweep, if any.
873
+ * @internal
874
+ */
875
+ get pendingSweep() {
876
+ return this.#legacySweep;
857
877
  }
858
878
  #key(remoteHost) {
859
879
  return [...this.#prefix, remoteHost];
860
880
  }
881
+ #legacySweepMarkerKey() {
882
+ return [...this.#prefix, ...LEGACY_SWEEP_MARKER];
883
+ }
884
+ #sweepLegacyStates() {
885
+ if (this.#kv.cas == null) return;
886
+ if (this.#options.stateTtl == null) return;
887
+ if (this.#legacySweep != null) return;
888
+ if (this.#isLegacySweepDone()) return;
889
+ this.#legacySweep = this.#sweepLegacyStatesImpl().catch((error) => {
890
+ getLogger([
891
+ "fedify",
892
+ "federation",
893
+ "circuit"
894
+ ]).warn("Failed to sweep legacy circuit breaker state:\n{error}", { error });
895
+ }).finally(() => {
896
+ this.#legacySweep = void 0;
897
+ });
898
+ }
899
+ async #sweepLegacyStatesImpl() {
900
+ const markerKey = this.#legacySweepMarkerKey();
901
+ const marker = await this.#acquireLegacySweep(markerKey);
902
+ if (marker === "done") return;
903
+ try {
904
+ for await (const { key, value } of this.#kv.list(this.#prefix)) {
905
+ if (key.length !== this.#prefix.length + 1) continue;
906
+ await this.#migrateLegacyState(key, value);
907
+ }
908
+ } catch (error) {
909
+ await this.#deleteIfUnchanged(markerKey, marker);
910
+ throw error;
911
+ }
912
+ const finishedMarker = this.#finishLegacySweepMarker(marker);
913
+ if (await this.#kv.cas(markerKey, marker, finishedMarker)) this.#rememberLegacySweepMarker(finishedMarker);
914
+ }
915
+ #finishLegacySweepMarker(marker) {
916
+ const retryUntil = getLegacySweepRetryUntil(marker);
917
+ if (retryUntil != null && Temporal.Instant.compare(this.#now(), Temporal.Instant.from(retryUntil)) < 0) return {
918
+ state: "done",
919
+ retryUntil
920
+ };
921
+ return { state: "final" };
922
+ }
923
+ async #migrateLegacyState(key, value) {
924
+ if (isCurrentCircuitBreakerState(value)) return;
925
+ const state = parseCircuitBreakerKvState(value);
926
+ if (state != null) {
927
+ await this.#kv.cas(key, value, markCircuitBreakerState(state), this.#setOptions());
928
+ return;
929
+ }
930
+ await this.#deleteIfUnchanged(key, value);
931
+ }
932
+ async #deleteIfUnchanged(key, value) {
933
+ if (await this.#kv.cas(key, value, LEGACY_SWEEP_DELETING_MARKER, { ttl: LEGACY_SWEEP_LOCK_TTL })) await this.#kv.delete(key);
934
+ }
935
+ async #acquireLegacySweep(markerKey) {
936
+ for (let attempt = 0; attempt < LEGACY_SWEEP_CAS_ATTEMPTS; attempt++) {
937
+ const marker = await this.#kv.get(markerKey);
938
+ const now = this.#now();
939
+ if (isLegacySweepActive(marker, now)) {
940
+ this.#rememberLegacySweepMarker(marker);
941
+ return "done";
942
+ }
943
+ const retryUntil = isLegacySweepRetryDue(marker, now) ? marker.retryUntil : getLegacySweepRetryUntil(marker) ?? now.add(LEGACY_SWEEP_RETRY_WINDOW).toString();
944
+ const sweeping = {
945
+ state: "sweeping",
946
+ started: now.toString(),
947
+ retryUntil
948
+ };
949
+ if (await this.#kv.cas(markerKey, marker ?? void 0, sweeping, { ttl: LEGACY_SWEEP_LOCK_TTL })) return sweeping;
950
+ await delay(LEGACY_SWEEP_WAIT_INTERVAL);
951
+ }
952
+ return "done";
953
+ }
954
+ #isLegacySweepDone() {
955
+ if (this.#legacySweepDone === "final") return true;
956
+ if (this.#legacySweepDone == null) return false;
957
+ if (Temporal.Instant.compare(this.#now(), this.#legacySweepDone) < 0) return true;
958
+ this.#legacySweepDone = void 0;
959
+ return false;
960
+ }
961
+ #rememberLegacySweepMarker(marker) {
962
+ const doneUntil = getLegacySweepDoneUntil(marker, this.#now());
963
+ if (doneUntil != null) this.#legacySweepDone = doneUntil;
964
+ }
861
965
  #capHeldRetryAt(now, heldSince, retryAt) {
862
966
  const expiresAt = (heldSince ?? now).add(this.#options.heldActivityTtl);
863
967
  return Temporal.Instant.compare(expiresAt, retryAt) < 0 ? expiresAt : retryAt;
864
968
  }
865
969
  async #get(remoteHost) {
866
- return parseCircuitBreakerKvState(await this.#kv.get(this.#key(remoteHost)));
970
+ return parseStoredCircuitBreakerKvState(await this.#kv.get(this.#key(remoteHost)));
867
971
  }
868
972
  async #replace(remoteHost, oldState, newState) {
869
973
  const key = this.#key(remoteHost);
974
+ const storedState = newState == null ? void 0 : markCircuitBreakerState(newState);
870
975
  if (this.#kv.cas == null) {
871
- if (newState == null) await this.#kv.delete(key);
872
- else await this.#kv.set(key, newState);
976
+ if (storedState == null) await this.#kv.delete(key);
977
+ else await this.#kv.set(key, storedState, this.#setOptions());
873
978
  return true;
874
979
  }
875
- return await this.#kv.cas(key, oldState, newState);
980
+ return await this.#kv.cas(key, oldState, storedState, storedState == null ? void 0 : this.#setOptions());
981
+ }
982
+ #setOptions() {
983
+ return this.#options.stateTtl == null ? void 0 : { ttl: this.#options.stateTtl };
876
984
  }
877
985
  async #notifyStateChange(remoteHost, previousState, newState) {
878
986
  try {
@@ -912,18 +1020,25 @@ var CircuitBreaker = class {
912
1020
  * @param options The public circuit breaker options supplied to Fedify.
913
1021
  * @returns The normalized failure predicate, failure pruning function,
914
1022
  * duration values, and optional callbacks with defaults applied.
915
- * @throws {RangeError} If any configured duration is not positive.
1023
+ * @throws {RangeError} If any configured duration is not positive, or if
1024
+ * `stateTtl` is shorter than `recoveryDelay`.
916
1025
  * @throws {TypeError} If `failureThreshold` is not a positive integer.
917
1026
  */
918
1027
  function normalizeCircuitBreakerOptions(options) {
919
1028
  const recoveryDelay = toInstantDuration(options.recoveryDelay ?? { minutes: 30 });
920
1029
  const heldActivityTtl = toInstantDuration(options.heldActivityTtl ?? { hours: 168 });
921
1030
  const releaseInterval = toInstantDuration(options.releaseInterval ?? { seconds: 1 });
1031
+ const configuredStateTtl = options.stateTtl == null ? void 0 : toInstantDuration(options.stateTtl);
922
1032
  assertPositiveDuration(recoveryDelay, "recoveryDelay");
923
1033
  assertPositiveDuration(heldActivityTtl, "heldActivityTtl");
924
1034
  assertPositiveDuration(releaseInterval, "releaseInterval");
1035
+ if (configuredStateTtl != null) {
1036
+ assertPositiveDuration(configuredStateTtl, "stateTtl");
1037
+ if (Temporal.Duration.compare(configuredStateTtl, recoveryDelay) < 0) throw new RangeError("stateTtl must be at least recoveryDelay.");
1038
+ }
925
1039
  let failure;
926
1040
  let pruneFailures;
1041
+ let stateTtl;
927
1042
  if (options.failure == null) {
928
1043
  const failureThreshold = options.failureThreshold ?? 5;
929
1044
  if (!Number.isInteger(failureThreshold) || failureThreshold <= 0) throw new TypeError("failureThreshold must be a positive integer.");
@@ -939,9 +1054,11 @@ function normalizeCircuitBreakerOptions(options) {
939
1054
  const last = timestamps[timestamps.length - 1];
940
1055
  return Temporal.Duration.compare(first.until(last), failureWindow) <= 0;
941
1056
  };
1057
+ stateTtl = configuredStateTtl ?? maxDuration(recoveryDelay.add(failureWindow), recoveryDelay.add(heldActivityTtl));
942
1058
  } else {
943
1059
  failure = options.failure;
944
1060
  pruneFailures = (timestamps) => timestamps.slice(-MAX_CUSTOM_FAILURE_HISTORY);
1061
+ stateTtl = configuredStateTtl;
945
1062
  }
946
1063
  return {
947
1064
  failure,
@@ -949,10 +1066,72 @@ function normalizeCircuitBreakerOptions(options) {
949
1066
  recoveryDelay,
950
1067
  heldActivityTtl,
951
1068
  releaseInterval,
1069
+ stateTtl,
952
1070
  onStateChange: options.onStateChange,
953
1071
  onActivityDrop: options.onActivityDrop
954
1072
  };
955
1073
  }
1074
+ function maxDuration(duration, ...durations) {
1075
+ return durations.reduce((max, candidate) => Temporal.Duration.compare(candidate, max) > 0 ? candidate : max, duration);
1076
+ }
1077
+ function isLegacySweepDone(value, now) {
1078
+ return getLegacySweepDoneUntil(value, now) != null;
1079
+ }
1080
+ function getLegacySweepDoneUntil(value, now) {
1081
+ if (typeof value !== "object" || value == null || !("state" in value)) return;
1082
+ if (value.state === "final") return "final";
1083
+ if (value.state === "done" && "retryUntil" in value && typeof value.retryUntil === "string") try {
1084
+ const retryUntil = Temporal.Instant.from(value.retryUntil);
1085
+ return Temporal.Instant.compare(now, retryUntil) < 0 ? retryUntil : void 0;
1086
+ } catch {
1087
+ return;
1088
+ }
1089
+ }
1090
+ function isLegacySweepRetryDue(value, now) {
1091
+ if (typeof value !== "object" || value == null || !("state" in value) || value.state !== "done" || !("retryUntil" in value) || typeof value.retryUntil !== "string") return false;
1092
+ try {
1093
+ return Temporal.Instant.compare(now, Temporal.Instant.from(value.retryUntil)) >= 0;
1094
+ } catch {
1095
+ return false;
1096
+ }
1097
+ }
1098
+ function isLegacySweepActive(value, now) {
1099
+ return isLegacySweepDone(value, now) || isLegacySweepInProgress(value, now);
1100
+ }
1101
+ function isLegacySweepInProgress(value, now) {
1102
+ if (typeof value !== "object" || value == null || !("state" in value) || value.state !== "sweeping" || !("started" in value) || typeof value.started !== "string") return false;
1103
+ try {
1104
+ return Temporal.Instant.compare(now, Temporal.Instant.from(value.started).add(LEGACY_SWEEP_LOCK_TTL)) < 0;
1105
+ } catch {
1106
+ return false;
1107
+ }
1108
+ }
1109
+ function getLegacySweepRetryUntil(value) {
1110
+ if (typeof value !== "object" || value == null || !("retryUntil" in value) || typeof value.retryUntil !== "string") return;
1111
+ try {
1112
+ Temporal.Instant.from(value.retryUntil);
1113
+ return value.retryUntil;
1114
+ } catch {
1115
+ return;
1116
+ }
1117
+ }
1118
+ function isCurrentCircuitBreakerState(value) {
1119
+ return typeof value === "object" && value != null && "__fedifyCircuitBreakerStateVersion" in value && value.__fedifyCircuitBreakerStateVersion === CIRCUIT_BREAKER_STATE_VERSION;
1120
+ }
1121
+ function markCircuitBreakerState(state) {
1122
+ return {
1123
+ ...state,
1124
+ __fedifyCircuitBreakerStateVersion: CIRCUIT_BREAKER_STATE_VERSION
1125
+ };
1126
+ }
1127
+ function stripStoredCircuitBreakerState(state) {
1128
+ if (state == null) return void 0;
1129
+ const { __fedifyCircuitBreakerStateVersion: _, ...publicState } = state;
1130
+ return publicState;
1131
+ }
1132
+ function delay(milliseconds) {
1133
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
1134
+ }
956
1135
  function toInstantDuration(duration) {
957
1136
  const parsed = Temporal.Duration.from(duration);
958
1137
  return Temporal.Duration.from({ milliseconds: Math.trunc(parsed.total({
@@ -987,6 +1166,9 @@ function parseHeldSince(value) {
987
1166
  * valid instant strings, or `undefined` when the stored value is malformed.
988
1167
  */
989
1168
  function parseCircuitBreakerKvState(value) {
1169
+ return stripStoredCircuitBreakerState(parseStoredCircuitBreakerKvState(value));
1170
+ }
1171
+ function parseStoredCircuitBreakerKvState(value) {
990
1172
  const isInstantString = (v) => {
991
1173
  if (typeof v !== "string") return false;
992
1174
  try {
@@ -1006,7 +1188,8 @@ function parseCircuitBreakerKvState(value) {
1006
1188
  state: record.state,
1007
1189
  failures: record.failures,
1008
1190
  ...record.opened == null ? {} : { opened: record.opened },
1009
- ...record.halfOpened == null ? {} : { halfOpened: record.halfOpened }
1191
+ ...record.halfOpened == null ? {} : { halfOpened: record.halfOpened },
1192
+ ...record.__fedifyCircuitBreakerStateVersion === CIRCUIT_BREAKER_STATE_VERSION ? { __fedifyCircuitBreakerStateVersion: CIRCUIT_BREAKER_STATE_VERSION } : {}
1010
1193
  };
1011
1194
  }
1012
1195
  //#endregion
@@ -1,5 +1,5 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as FederationImpl } from "./middleware-WU_5fbGS.mjs";
4
+ import { n as FederationImpl } from "./middleware-Cf0dFqr9.mjs";
5
5
  export { FederationImpl };
@@ -1,26 +1,26 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-C8RG0HM3.mjs";
5
- import { a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, n as getDurationMs, o as isAbortError, r as getFederationMetrics, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "./metrics-Pue1x4ta.mjs";
4
+ import { n as version, t as name } from "./deno-Bg8I1L40.mjs";
5
+ import { a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, n as getDurationMs, o as isAbortError, r as getFederationMetrics, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "./metrics-CmX7KEfY.mjs";
6
6
  import { t as formatAcceptSignature } from "./accept-CPkZzmGN.mjs";
7
- import { a as importJwk, o as validateCryptoKey, t as exportJwk } from "./key-BKYwmbur.mjs";
8
- import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-PCt0862M.mjs";
9
- import { t as getAuthenticatedDocumentLoader } from "./docloader-YZA0m_3O.mjs";
10
- import { n as kvCache } from "./kv-cache-Ceg0c1wh.mjs";
11
- import { _ as wrapContextLoaderForJsonLd, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, l as hasSignature, m as verifyCompactJsonLd, p as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike } from "./ld-Dcw2skfn.mjs";
12
- import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-pVJ0Twi7.mjs";
7
+ import { a as importJwk, o as validateCryptoKey, t as exportJwk } from "./key-DOnwUeY9.mjs";
8
+ import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-BaUm6xbz.mjs";
9
+ import { t as getAuthenticatedDocumentLoader } from "./docloader-Dxsk3R8v.mjs";
10
+ import { n as kvCache } from "./kv-cache-CE_QUNa6.mjs";
11
+ import { _ as wrapContextLoaderForJsonLd, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, l as hasSignature, m as verifyCompactJsonLd, p as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike } from "./ld-CiONXo2U.mjs";
12
+ import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-C7h5_D1P.mjs";
13
13
  import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-L_DbOaFe.mjs";
14
- import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-pXYODmNV.mjs";
14
+ import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-BVuILGqP.mjs";
15
15
  import { t as getNodeInfo } from "./client-ByXmQhYD.mjs";
16
16
  import { t as nodeInfoToJson } from "./types-J53Kw7so.mjs";
17
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-BfAp60BJ.mjs";
18
- import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-hivpmhlD.mjs";
19
- import { t as CircuitBreaker } from "./circuit-breaker-CSWsyoef.mjs";
17
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-7kSXrsl4.mjs";
18
+ import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-DosLp_w5.mjs";
19
+ import { t as CircuitBreaker } from "./circuit-breaker-DLS_n1x5.mjs";
20
20
  import { t as buildCollectionSynchronizationHeader } from "./collection-Cc3DVAhE.mjs";
21
21
  import { t as KvKeyCache } from "./keycache-BeU0LCII.mjs";
22
22
  import { t as acceptsJsonLd } from "./negotiation-DDstyBvc.mjs";
23
- import { t as hasMalformedKnownTemporalLiteral } from "./temporal-HZHBbbk4.mjs";
23
+ import { t as hasMalformedKnownTemporalLiteral } from "./temporal-D3Bspq6d.mjs";
24
24
  import { t as createExponentialBackoffPolicy } from "./retry-CXg_MBI-.mjs";
25
25
  import { getLogger, withContext } from "@logtape/logtape";
26
26
  import { RouterError } from "@fedify/uri-template";