@fedify/fedify 2.4.0-pr.936.41 → 2.5.0-dev.2271

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 (244) hide show
  1. package/README.md +50 -39
  2. package/dist/{assert-CJ1KXcvv.mjs → assert-D1Eafmd_.mjs} +1 -1
  3. package/dist/{assert_equals-BV7oLMxm.mjs → assert_equals-Z6IcEbWU.mjs} +2 -28
  4. package/dist/assert_exists-cffSpJ7X.mjs +28 -0
  5. package/dist/assert_false-CPdRnc3E.mjs +23 -0
  6. package/dist/assert_greater_or_equal-BlafOF4-.mjs +31 -0
  7. package/dist/{assert_instance_of-Q9KTBnrf.mjs → assert_instance_of-mrXWWNY-.mjs} +1 -1
  8. package/dist/{assert_not_equals-EPJPoxuf.mjs → assert_not_equals-CpPK2t89.mjs} +2 -1
  9. package/dist/assert_rejects-CTptdYck.mjs +39 -0
  10. package/dist/{assert_strict_equals-Ec20AUwa.mjs → assert_strict_equals-CYvBmkpC.mjs} +2 -1
  11. package/dist/{assert_throws-NiFfqbnH.mjs → assert_throws-BYITOvPO.mjs} +2 -1
  12. package/dist/assertion_error-B-cx0fFA.mjs +31 -0
  13. package/dist/audience-CHQ-FlLD.mjs +95 -0
  14. package/dist/{builder-DonuxRl_.mjs → builder-Bda6LK7A.mjs} +169 -14
  15. package/dist/{circuit-breaker-CBGyz5F7.mjs → circuit-breaker-XJ53md4S.mjs} +224 -17
  16. package/dist/circuit-breaker-test-utils-DW6YC_Pe.mjs +14 -0
  17. package/dist/{client-CjF5IYQS.mjs → client-CEnNz8O7.mjs} +144 -8
  18. package/dist/{codec-BBga-FWI.mjs → codec-C6rTrpIp.mjs} +26 -3
  19. package/dist/{collection-BIhlldqK.mjs → collection-CnnP1UAv.mjs} +1 -1
  20. package/dist/compat/mod.cjs +1 -1
  21. package/dist/compat/mod.d.cts +1 -1
  22. package/dist/compat/mod.d.ts +1 -1
  23. package/dist/compat/mod.js +1 -1
  24. package/dist/compat/outgoing-jsonld.test.mjs +71 -3
  25. package/dist/compat/public-audience.test.mjs +14 -5
  26. package/dist/compat/secured-document.test.d.mts +2 -0
  27. package/dist/compat/secured-document.test.mjs +58 -0
  28. package/dist/compat/transformers.test.mjs +5 -5
  29. package/dist/compound-proof-B-9mXxFW.mjs +989 -0
  30. package/dist/{context-Zii1f5xS.d.ts → context-7NyiqIcg.d.ts} +1600 -25
  31. package/dist/{context-BJXxPElm.mjs → context-8Tu9kea0.mjs} +21 -63
  32. package/dist/{context-lxX6I7as.d.cts → context-tsYPJ2Ew.d.cts} +1600 -25
  33. package/dist/{deno-BHzKn9Ig.mjs → deno-oR5aJKpU.mjs} +1 -1
  34. package/dist/dist-DJQM4u7i.mjs +189 -0
  35. package/dist/docloader-DBoYbvYW.mjs +87 -0
  36. package/dist/{esm-yYQfPhIY.mjs → esm-BHXXx4Wx.mjs} +13 -6
  37. package/dist/federation/audience.test.d.mts +2 -0
  38. package/dist/federation/audience.test.mjs +130 -0
  39. package/dist/federation/builder.test.mjs +54 -5
  40. package/dist/federation/circuit-breaker.test.mjs +838 -7
  41. package/dist/federation/collection.test.mjs +3 -3
  42. package/dist/federation/compound-fanout.test.d.mts +2 -0
  43. package/dist/federation/compound-fanout.test.mjs +228 -0
  44. package/dist/federation/handler-compound-proof.test.d.mts +2 -0
  45. package/dist/federation/handler-compound-proof.test.mjs +384 -0
  46. package/dist/federation/handler.test.mjs +385 -15
  47. package/dist/federation/hashlink-media.test.d.mts +2 -0
  48. package/dist/federation/hashlink-media.test.mjs +422 -0
  49. package/dist/federation/idempotency.test.mjs +6 -6
  50. package/dist/federation/inbox-report.test.d.mts +2 -0
  51. package/dist/federation/inbox-report.test.mjs +475 -0
  52. package/dist/federation/inbox.test.mjs +3 -3
  53. package/dist/federation/keycache.test.mjs +215 -14
  54. package/dist/federation/kv.test.mjs +3 -3
  55. package/dist/federation/metrics.test.mjs +5 -5
  56. package/dist/federation/middleware.test.mjs +967 -35
  57. package/dist/federation/mod.cjs +18 -261
  58. package/dist/federation/mod.d.cts +5 -5
  59. package/dist/federation/mod.d.ts +5 -5
  60. package/dist/federation/mod.js +279 -1
  61. package/dist/federation/mq.test.mjs +123 -47
  62. package/dist/federation/negotiation.test.mjs +4 -4
  63. package/dist/federation/outgoing-proof.test.d.mts +2 -0
  64. package/dist/federation/outgoing-proof.test.mjs +931 -0
  65. package/dist/federation/portable-actor.test.d.mts +2 -0
  66. package/dist/federation/portable-actor.test.mjs +496 -0
  67. package/dist/federation/portable-chain.test.d.mts +2 -0
  68. package/dist/federation/portable-chain.test.mjs +370 -0
  69. package/dist/federation/portable-collection.test.d.mts +2 -0
  70. package/dist/federation/portable-collection.test.mjs +832 -0
  71. package/dist/federation/portable-delivery.test.d.mts +2 -0
  72. package/dist/federation/portable-delivery.test.mjs +746 -0
  73. package/dist/federation/portable-gateway-keys.test.d.mts +2 -0
  74. package/dist/federation/portable-gateway-keys.test.mjs +686 -0
  75. package/dist/federation/portable-inbox.test.d.mts +2 -0
  76. package/dist/federation/portable-inbox.test.mjs +1535 -0
  77. package/dist/federation/portable-parse-uri.test.d.mts +2 -0
  78. package/dist/federation/portable-parse-uri.test.mjs +348 -0
  79. package/dist/federation/portable-route-activity.test.d.mts +2 -0
  80. package/dist/federation/portable-route-activity.test.mjs +259 -0
  81. package/dist/federation/portable.test.d.mts +2 -0
  82. package/dist/federation/portable.test.mjs +1072 -0
  83. package/dist/federation/retry.test.mjs +4 -3
  84. package/dist/federation/router.test.mjs +9 -159
  85. package/dist/federation/send.test.mjs +245 -27
  86. package/dist/federation/tasks/codec.test.mjs +4 -4
  87. package/dist/federation/tasks/enqueue.test.mjs +42 -4
  88. package/dist/federation/tasks/tasks.test.mjs +7 -6
  89. package/dist/federation/temporal.test.mjs +4 -4
  90. package/dist/federation/webfinger.test.mjs +275 -12
  91. package/dist/{middleware-FrSMAYEx.cjs → federation-CkZiAcWY.cjs} +8640 -3722
  92. package/dist/http-B5JT115n.cjs +10630 -0
  93. package/dist/http-DydWCB7R.js +6527 -0
  94. package/dist/{http-CPVMUgsW.mjs → http-siciWkmx.mjs} +397 -168
  95. package/dist/key-BspPiXqn.mjs +951 -0
  96. package/dist/key-C1nv7_jD.d.ts +1125 -0
  97. package/dist/key-DYxuyd-s.d.cts +1125 -0
  98. package/dist/key-owner-evidence-APB6NpSk.mjs +108 -0
  99. package/dist/keycache-Da8A8mZa.mjs +200 -0
  100. package/dist/{kv-D6hNiMTK.d.ts → kv-B2mzK5tl.d.cts} +3 -2
  101. package/dist/{kv-gJ8LYbxX.d.cts → kv-B2mzK5tl.d.ts} +3 -2
  102. package/dist/kv-DBRWVx3Q.mjs +104 -0
  103. package/dist/{kv-cache-KyGqJqSp.mjs → kv-cache-BF1hnW65.mjs} +1 -1
  104. package/dist/{kv-cache-5bnX3-Jq.js → kv-cache-CTQr1tbn.js} +54 -16
  105. package/dist/{kv-cache-DQ29C4XL.cjs → kv-cache-DCac1nEU.cjs} +55 -19
  106. package/dist/{ld-Cj9F_5AP.mjs → ld-CzaP-hJl.mjs} +44 -12
  107. package/dist/map-local-context-conflict-DOYEEJ3u.mjs +204 -0
  108. package/dist/map-local-create-note-B0SeXJms.mjs +214 -0
  109. package/dist/{metrics-DMk-nTxI.mjs → metrics-CYg9yxBL.mjs} +3 -2
  110. package/dist/metrics-DVKYY6K1.d.cts +36 -0
  111. package/dist/metrics-DoRtI0qU.d.ts +36 -0
  112. package/dist/{middleware-Y-kpsp6S.js → middleware-CsDDXKuL.js} +5493 -1103
  113. package/dist/{middleware-CvwTUgnP.mjs → middleware-D7_LO-Vs.mjs} +3157 -272
  114. package/dist/{middleware-CEctxBN6.mjs → middleware-DWjrfpcz.mjs} +1 -1
  115. package/dist/{mod-gOlPDuT8.d.cts → mod-BojP2Bw3.d.cts} +1 -1
  116. package/dist/mod-CavhAgLw.d.cts +320 -0
  117. package/dist/mod-CtpF2BU5.d.ts +320 -0
  118. package/dist/{mod-BtEaIEcD.d.cts → mod-DEZXwV6J.d.ts} +10 -4
  119. package/dist/{mod-UMmveiO0.d.ts → mod-Dw-nPxQh.d.cts} +10 -4
  120. package/dist/{mod-DLc2Ot6f.d.ts → mod-Xj5P4s4d.d.ts} +1 -1
  121. package/dist/mod.cjs +40 -39
  122. package/dist/mod.d.cts +10 -11
  123. package/dist/mod.d.ts +10 -11
  124. package/dist/mod.js +9 -9
  125. package/dist/{mq-llHKivST.mjs → mq-Dm1Wmtm-.mjs} +6 -0
  126. package/dist/nodeinfo/client.test.mjs +139 -4
  127. package/dist/nodeinfo/handler.test.mjs +5 -5
  128. package/dist/nodeinfo/mod.cjs +1 -1
  129. package/dist/nodeinfo/mod.d.cts +1 -1
  130. package/dist/nodeinfo/mod.d.ts +1 -1
  131. package/dist/nodeinfo/mod.js +1 -1
  132. package/dist/nodeinfo/types.test.mjs +4 -4
  133. package/dist/otel/exporter.test.mjs +3 -3
  134. package/dist/otel/mod.cjs +0 -1
  135. package/dist/otel/mod.d.cts +1 -1
  136. package/dist/otel/mod.d.ts +1 -1
  137. package/dist/{outgoing-jsonld-URuqsICz.mjs → outgoing-jsonld-4IJqPYos.mjs} +12 -10
  138. package/dist/{owner-DEvZuyOE.d.cts → owner-BF0L9cMt.d.cts} +11 -0
  139. package/dist/{owner-CnngXDNJ.d.ts → owner-BSzr5-I8.d.ts} +11 -0
  140. package/dist/owner-DYgfTFHy.mjs +267 -0
  141. package/dist/portable-collection-BpsVsRov.js +670 -0
  142. package/dist/portable-collection-C74P1vtC.mjs +418 -0
  143. package/dist/portable-collection-DZd2bm7M.cjs +686 -0
  144. package/dist/portable-key-BIMLrAP4.mjs +549 -0
  145. package/dist/portable-key-id-Ca30-hOa.mjs +248 -0
  146. package/dist/proof-Dxy6ByqI.mjs +1299 -0
  147. package/dist/{public-audience-Cr5MsnMq.mjs → public-audience--klBIOk1.mjs} +15 -10
  148. package/dist/router-u5O5KCQr.mjs +156 -0
  149. package/dist/runtime/mod.cjs +1 -1
  150. package/dist/runtime/mod.js +1 -1
  151. package/dist/secured-document-CxzZZ9nL.mjs +35 -0
  152. package/dist/send-C4dDpAxm.mjs +495 -0
  153. package/dist/sig/accept.test.mjs +1 -1
  154. package/dist/sig/compound-proof-discovery.test.d.mts +2 -0
  155. package/dist/sig/compound-proof-discovery.test.mjs +331 -0
  156. package/dist/sig/compound-proof-producer.test.d.mts +2 -0
  157. package/dist/sig/compound-proof-producer.test.mjs +107 -0
  158. package/dist/sig/compound-proof-verification.test.d.mts +2 -0
  159. package/dist/sig/compound-proof-verification.test.mjs +833 -0
  160. package/dist/sig/compound-proof.test.d.mts +2 -0
  161. package/dist/sig/compound-proof.test.mjs +411 -0
  162. package/dist/sig/http.test.mjs +702 -31
  163. package/dist/sig/key-owner-evidence.test.d.mts +2 -0
  164. package/dist/sig/key-owner-evidence.test.mjs +100 -0
  165. package/dist/sig/key.test.mjs +220 -8
  166. package/dist/sig/ld.test.mjs +65 -7
  167. package/dist/sig/mitra-receive-interop.test.d.mts +2 -0
  168. package/dist/sig/mitra-receive-interop.test.mjs +260 -0
  169. package/dist/sig/mitra-send-interop.test.d.mts +2 -0
  170. package/dist/sig/mitra-send-interop.test.mjs +461 -0
  171. package/dist/sig/mod.cjs +18 -16
  172. package/dist/sig/mod.d.cts +4 -4
  173. package/dist/sig/mod.d.ts +4 -4
  174. package/dist/sig/mod.js +3 -3
  175. package/dist/sig/owner.test.mjs +108 -9
  176. package/dist/sig/portable-collection.test.d.mts +2 -0
  177. package/dist/sig/portable-collection.test.mjs +697 -0
  178. package/dist/sig/portable-interop.test.d.mts +2 -0
  179. package/dist/sig/portable-interop.test.mjs +156 -0
  180. package/dist/sig/portable-key-id.test.d.mts +2 -0
  181. package/dist/sig/portable-key-id.test.mjs +67 -0
  182. package/dist/sig/portable-key.test.d.mts +2 -0
  183. package/dist/sig/portable-key.test.mjs +1439 -0
  184. package/dist/sig/proof.test.mjs +1753 -16
  185. package/dist/sig/tootik-request-interop.test.d.mts +2 -0
  186. package/dist/sig/tootik-request-interop.test.mjs +190 -0
  187. package/dist/sig/tootik-send-interop.test.d.mts +2 -0
  188. package/dist/sig/tootik-send-interop.test.mjs +331 -0
  189. package/dist/sig/verification.test.d.mts +2 -0
  190. package/dist/sig/verification.test.mjs +310 -0
  191. package/dist/std__assert-DvMDRPes.mjs +8 -0
  192. package/dist/{testing-CO7ftO8A.mjs → tasks-IG3pHfvZ.mjs} +2 -2
  193. package/dist/{temporal-fsXO5IvD.mjs → temporal-D0KJI4NF.mjs} +3 -3
  194. package/dist/testing/context.test.d.mts +2 -0
  195. package/dist/testing/context.test.mjs +19 -0
  196. package/dist/testing/kv.test.d.mts +2 -0
  197. package/dist/testing/kv.test.mjs +101 -0
  198. package/dist/testing/mod.d.mts +1617 -48
  199. package/dist/testing/mod.mjs +6 -3
  200. package/dist/testing-t8xDeJZ5.mjs +8 -0
  201. package/dist/{transformers-DGwl-hD1.cjs → transformers-BalCrSsr.cjs} +0 -1
  202. package/dist/{types-DC1pNgJA.js → types-_S2_MeLF.js} +144 -8
  203. package/dist/{types-CPym83zc.cjs → types-poIao40f.cjs} +160 -7
  204. package/dist/utils/docloader.test.mjs +530 -8
  205. package/dist/utils/kv-cache.test.mjs +2 -2
  206. package/dist/utils/mod.cjs +1 -1
  207. package/dist/utils/mod.d.cts +1 -1
  208. package/dist/utils/mod.d.ts +1 -1
  209. package/dist/utils/mod.js +1 -1
  210. package/dist/verification-C3954hQh.mjs +5 -0
  211. package/dist/verification-Dpfcw8ml.mjs +105 -0
  212. package/dist/vocab/cjs.test.mjs +1 -1
  213. package/package.json +8 -8
  214. package/dist/assert_rejects-EgstcQy5.mjs +0 -82
  215. package/dist/docloader-e1khNUKy.mjs +0 -49
  216. package/dist/http-CsmQ-BQ4.js +0 -2483
  217. package/dist/http-VyDTd4G3.d.cts +0 -553
  218. package/dist/http-hlIjmewS.cjs +0 -2694
  219. package/dist/http-lf8Hsd91.d.ts +0 -553
  220. package/dist/key-DfPsX8hv.mjs +0 -453
  221. package/dist/keycache-_DHqz5z_.mjs +0 -97
  222. package/dist/metrics-CoUJLQhZ.d.cts +0 -276
  223. package/dist/metrics-Dc4jR-IC.d.ts +0 -276
  224. package/dist/mod-B0hW12_O.d.cts +0 -307
  225. package/dist/mod-COIAjwRS.d.ts +0 -307
  226. package/dist/owner-Br9YYzn_.mjs +0 -120
  227. package/dist/proof-B4ngoaNQ.mjs +0 -316
  228. package/dist/proof-B6E0XUXJ.js +0 -1415
  229. package/dist/proof-NN4xCQGL.cjs +0 -1562
  230. package/dist/send-CxHKUNzv.mjs +0 -239
  231. package/dist/std__assert-B2NnY9bq.mjs +0 -79
  232. package/dist/temporal-polyfill-BCdTHexd.cjs +0 -3565
  233. /package/dist/{accept-8PiolDP7.mjs → accept-CPVqki78.mjs} +0 -0
  234. /package/dist/{activity-listener-BtTSxZWA.mjs → activity-listener-C-BSbKI7.mjs} +0 -0
  235. /package/dist/{client-CAM_bQXx.d.cts → client-D-XP_Ccb.d.cts} +0 -0
  236. /package/dist/{client-CSddvgWN.d.ts → client-D-XP_Ccb.d.ts} +0 -0
  237. /package/dist/{keys-Dbyd7bhl.mjs → keys-DIJNHW-Y.mjs} +0 -0
  238. /package/dist/{kv-CXBAok3k.mjs → kv-DCHkaJ9-.mjs} +0 -0
  239. /package/dist/{negotiation-RuwrPFqi.mjs → negotiation-CCocWT_N.mjs} +0 -0
  240. /package/dist/{retry-DgFqeS5k.mjs → retry-zX5xw5e0.mjs} +0 -0
  241. /package/dist/{chunk-D5IdezsZ.js → rolldown-runtime-BC-Eo5Mj.js} +0 -0
  242. /package/dist/{chunk-Czpeue4M.cjs → rolldown-runtime-Dmm3t92W.cjs} +0 -0
  243. /package/dist/{transformers-DXze6FUB.js → transformers-DAtXl_Kc.js} +0 -0
  244. /package/dist/{types-CrtdVuDP.mjs → types-DE_LV1JB.mjs} +0 -0
@@ -3,7 +3,14 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  //#region src/federation/circuit-breaker.ts
6
- const MAX_CUSTOM_FAILURE_HISTORY = 100;
6
+ const LEGACY_SWEEP_MARKER = ["__fedify_meta", "circuit_breaker_state_ttl_sweep_v2"];
7
+ const LEGACY_SWEEP_DELETING_MARKER = { __fedifyDeletingCircuitBreakerLegacyState: true };
8
+ const CIRCUIT_BREAKER_STATE_VERSION = 1;
9
+ const LEGACY_SWEEP_LOCK_TTL = Temporal.Duration.from({ minutes: 5 });
10
+ const LEGACY_SWEEP_RENEW_INTERVAL = Temporal.Duration.from({ minutes: 1 });
11
+ const LEGACY_SWEEP_RETRY_WINDOW = Temporal.Duration.from({ hours: 168 });
12
+ const LEGACY_SWEEP_WAIT_INTERVAL = 10;
13
+ const LEGACY_SWEEP_CAS_ATTEMPTS = 3;
7
14
  /**
8
15
  * Tracks reachability state for remote outbox delivery hosts.
9
16
  * @since 2.3.0
@@ -13,6 +20,8 @@ var CircuitBreaker = class {
13
20
  #prefix;
14
21
  #options;
15
22
  #now;
23
+ #legacySweep;
24
+ #legacySweepDone;
16
25
  #stateChangeObserver;
17
26
  constructor(options) {
18
27
  this.#kv = options.kv;
@@ -29,6 +38,7 @@ var CircuitBreaker = class {
29
38
  return now.until(this.#capHeldRetryAt(now, heldSince, now.add(delay)));
30
39
  }
31
40
  async beforeSend(remoteHost, message) {
41
+ this.#sweepLegacyStates();
32
42
  const heldSince = parseHeldSince(message.circuitHeldSince);
33
43
  const now = this.#now();
34
44
  if (heldSince != null && Temporal.Instant.compare(heldSince.add(this.#options.heldActivityTtl), now) <= 0) return {
@@ -37,7 +47,7 @@ var CircuitBreaker = class {
37
47
  };
38
48
  let lastConflictingState;
39
49
  for (let attempt = 0; attempt < 10; attempt++) {
40
- const oldState = await this.#get(remoteHost);
50
+ const { state: oldState, raw: oldValue } = await this.#get(remoteHost);
41
51
  if (oldState == null || oldState.state === "closed") return {
42
52
  type: "send",
43
53
  probe: false
@@ -63,7 +73,7 @@ var CircuitBreaker = class {
63
73
  state: "half-open",
64
74
  halfOpened: now.toString()
65
75
  };
66
- if (await this.#replace(remoteHost, oldState, newState)) return {
76
+ if (await this.#replace(remoteHost, oldValue, newState)) return {
67
77
  type: "send",
68
78
  probe: true
69
79
  };
@@ -85,7 +95,7 @@ var CircuitBreaker = class {
85
95
  state: "half-open",
86
96
  halfOpened: now.toString()
87
97
  };
88
- if (await this.#replace(remoteHost, oldState, newState)) {
98
+ if (await this.#replace(remoteHost, oldValue, newState)) {
89
99
  await this.#notifyStateChange(remoteHost, "open", "half-open");
90
100
  return {
91
101
  type: "send",
@@ -110,10 +120,11 @@ var CircuitBreaker = class {
110
120
  throw new Error(`Failed to update circuit breaker state for ${remoteHost}`);
111
121
  }
112
122
  async recordSuccess(remoteHost) {
123
+ this.#sweepLegacyStates();
113
124
  for (let attempt = 0; attempt < 10; attempt++) {
114
- const oldState = await this.#get(remoteHost);
125
+ const { state: oldState, raw: oldValue } = await this.#get(remoteHost);
115
126
  if (oldState == null) return void 0;
116
- if (await this.#replace(remoteHost, oldState, void 0)) {
127
+ if (await this.#replace(remoteHost, oldValue, void 0)) {
117
128
  if (oldState.state !== "closed") {
118
129
  await this.#notifyStateChange(remoteHost, oldState.state, "closed");
119
130
  return {
@@ -130,9 +141,10 @@ var CircuitBreaker = class {
130
141
  return await this.recordSuccess(remoteHost);
131
142
  }
132
143
  async recordFailure(remoteHost) {
144
+ this.#sweepLegacyStates();
133
145
  const now = this.#now();
134
146
  for (let attempt = 0; attempt < 10; attempt++) {
135
- const oldState = await this.#get(remoteHost);
147
+ const { state: oldState, raw: oldValue } = await this.#get(remoteHost);
136
148
  if (oldState?.state === "open") return void 0;
137
149
  const oldFailures = oldState?.failures.map(Temporal.Instant.from) ?? [];
138
150
  const failures = this.#options.pruneFailures([...oldFailures, now], now);
@@ -149,7 +161,7 @@ var CircuitBreaker = class {
149
161
  state: "closed",
150
162
  failures: failures.map((t) => t.toString())
151
163
  };
152
- if (await this.#replace(remoteHost, oldState, newState)) {
164
+ if (await this.#replace(remoteHost, oldValue, newState)) {
153
165
  if (transition != null) {
154
166
  await this.#notifyStateChange(remoteHost, transition[0], transition[1]);
155
167
  return {
@@ -177,26 +189,146 @@ var CircuitBreaker = class {
177
189
  }
178
190
  }
179
191
  async getState(remoteHost) {
180
- return await this.#get(remoteHost);
192
+ this.#sweepLegacyStates();
193
+ return stripStoredCircuitBreakerState((await this.#get(remoteHost)).state);
194
+ }
195
+ /**
196
+ * The currently running background legacy sweep, if any.
197
+ * @internal
198
+ */
199
+ get pendingSweep() {
200
+ return this.#legacySweep;
181
201
  }
182
202
  #key(remoteHost) {
183
203
  return [...this.#prefix, remoteHost];
184
204
  }
205
+ #legacySweepMarkerKey() {
206
+ return [...this.#prefix, ...LEGACY_SWEEP_MARKER];
207
+ }
208
+ #sweepLegacyStates() {
209
+ if (this.#kv.cas == null) return;
210
+ if (this.#options.stateTtl == null) return;
211
+ if (this.#legacySweep != null) return;
212
+ if (this.#isLegacySweepDone()) return;
213
+ this.#legacySweep = this.#sweepLegacyStatesImpl().catch((error) => {
214
+ getLogger([
215
+ "fedify",
216
+ "federation",
217
+ "circuit"
218
+ ]).warn("Failed to sweep legacy circuit breaker state:\n{error}", { error });
219
+ }).finally(() => {
220
+ this.#legacySweep = void 0;
221
+ });
222
+ }
223
+ async #sweepLegacyStatesImpl() {
224
+ const markerKey = this.#legacySweepMarkerKey();
225
+ const acquired = await this.#acquireLegacySweep(markerKey);
226
+ if (acquired === "done") return;
227
+ let marker = acquired;
228
+ let leaseRenewedAt = this.#now();
229
+ try {
230
+ for await (const { key, value } of this.#kv.list(this.#prefix)) {
231
+ if (key.length !== this.#prefix.length + 1) continue;
232
+ await this.#migrateLegacyState(key, value);
233
+ if (Temporal.Instant.compare(this.#now(), leaseRenewedAt.add(LEGACY_SWEEP_RENEW_INTERVAL)) >= 0) {
234
+ const renewed = await this.#renewLegacySweep(markerKey, marker);
235
+ if (renewed == null) return;
236
+ marker = renewed;
237
+ leaseRenewedAt = this.#now();
238
+ }
239
+ }
240
+ } catch (error) {
241
+ await this.#deleteIfUnchanged(markerKey, marker);
242
+ throw error;
243
+ }
244
+ const finishedMarker = this.#finishLegacySweepMarker(marker);
245
+ if (await this.#kv.cas(markerKey, marker, finishedMarker)) this.#rememberLegacySweepMarker(finishedMarker);
246
+ }
247
+ async #renewLegacySweep(markerKey, marker) {
248
+ const renewed = {
249
+ state: "sweeping",
250
+ started: this.#now().toString(),
251
+ retryUntil: getLegacySweepRetryUntil(marker) ?? this.#now().add(LEGACY_SWEEP_RETRY_WINDOW).toString()
252
+ };
253
+ if (await this.#kv.cas(markerKey, marker, renewed, { ttl: LEGACY_SWEEP_LOCK_TTL })) return renewed;
254
+ }
255
+ #finishLegacySweepMarker(marker) {
256
+ const retryUntil = getLegacySweepRetryUntil(marker);
257
+ if (retryUntil != null && Temporal.Instant.compare(this.#now(), Temporal.Instant.from(retryUntil)) < 0) return {
258
+ state: "done",
259
+ retryUntil
260
+ };
261
+ return { state: "final" };
262
+ }
263
+ async #migrateLegacyState(key, value) {
264
+ if (isCurrentCircuitBreakerState(value)) {
265
+ await this.#kv.cas(key, value, value, this.#setOptions());
266
+ return;
267
+ }
268
+ const state = parseCircuitBreakerKvState(value);
269
+ if (state != null) {
270
+ await this.#kv.cas(key, value, markCircuitBreakerState(state), this.#setOptions());
271
+ return;
272
+ }
273
+ await this.#deleteIfUnchanged(key, value);
274
+ }
275
+ async #deleteIfUnchanged(key, value) {
276
+ if (await this.#kv.cas(key, value, LEGACY_SWEEP_DELETING_MARKER, { ttl: LEGACY_SWEEP_LOCK_TTL })) await this.#kv.delete(key);
277
+ }
278
+ async #acquireLegacySweep(markerKey) {
279
+ for (let attempt = 0; attempt < LEGACY_SWEEP_CAS_ATTEMPTS; attempt++) {
280
+ const marker = await this.#kv.get(markerKey);
281
+ const now = this.#now();
282
+ if (isLegacySweepActive(marker, now)) {
283
+ this.#rememberLegacySweepMarker(marker);
284
+ return "done";
285
+ }
286
+ const retryUntil = isLegacySweepRetryDue(marker, now) ? marker.retryUntil : getLegacySweepRetryUntil(marker) ?? now.add(LEGACY_SWEEP_RETRY_WINDOW).toString();
287
+ const sweeping = {
288
+ state: "sweeping",
289
+ started: now.toString(),
290
+ retryUntil
291
+ };
292
+ if (await this.#kv.cas(markerKey, marker ?? void 0, sweeping, { ttl: LEGACY_SWEEP_LOCK_TTL })) return sweeping;
293
+ await delay(LEGACY_SWEEP_WAIT_INTERVAL);
294
+ }
295
+ return "done";
296
+ }
297
+ #isLegacySweepDone() {
298
+ if (this.#legacySweepDone === "final") return true;
299
+ if (this.#legacySweepDone == null) return false;
300
+ if (Temporal.Instant.compare(this.#now(), this.#legacySweepDone) < 0) return true;
301
+ this.#legacySweepDone = void 0;
302
+ return false;
303
+ }
304
+ #rememberLegacySweepMarker(marker) {
305
+ const doneUntil = getLegacySweepDoneUntil(marker, this.#now());
306
+ if (doneUntil != null) this.#legacySweepDone = doneUntil;
307
+ }
185
308
  #capHeldRetryAt(now, heldSince, retryAt) {
186
309
  const expiresAt = (heldSince ?? now).add(this.#options.heldActivityTtl);
187
310
  return Temporal.Instant.compare(expiresAt, retryAt) < 0 ? expiresAt : retryAt;
188
311
  }
189
312
  async #get(remoteHost) {
190
- return parseCircuitBreakerKvState(await this.#kv.get(this.#key(remoteHost)));
313
+ const value = await this.#kv.get(this.#key(remoteHost));
314
+ const state = parseStoredCircuitBreakerKvState(value);
315
+ return {
316
+ state,
317
+ raw: state == null ? void 0 : value
318
+ };
191
319
  }
192
- async #replace(remoteHost, oldState, newState) {
320
+ async #replace(remoteHost, oldValue, newState) {
193
321
  const key = this.#key(remoteHost);
322
+ const storedState = newState == null ? void 0 : markCircuitBreakerState(newState);
194
323
  if (this.#kv.cas == null) {
195
- if (newState == null) await this.#kv.delete(key);
196
- else await this.#kv.set(key, newState);
324
+ if (storedState == null) await this.#kv.delete(key);
325
+ else await this.#kv.set(key, storedState, this.#setOptions());
197
326
  return true;
198
327
  }
199
- return await this.#kv.cas(key, oldState, newState);
328
+ return await this.#kv.cas(key, oldValue, storedState, storedState == null ? void 0 : this.#setOptions());
329
+ }
330
+ #setOptions() {
331
+ return this.#options.stateTtl == null ? void 0 : { ttl: this.#options.stateTtl };
200
332
  }
201
333
  async #notifyStateChange(remoteHost, previousState, newState) {
202
334
  try {
@@ -236,18 +368,25 @@ var CircuitBreaker = class {
236
368
  * @param options The public circuit breaker options supplied to Fedify.
237
369
  * @returns The normalized failure predicate, failure pruning function,
238
370
  * duration values, and optional callbacks with defaults applied.
239
- * @throws {RangeError} If any configured duration is not positive.
371
+ * @throws {RangeError} If any configured duration is not positive, or if
372
+ * `stateTtl` is shorter than `recoveryDelay`.
240
373
  * @throws {TypeError} If `failureThreshold` is not a positive integer.
241
374
  */
242
375
  function normalizeCircuitBreakerOptions(options) {
243
376
  const recoveryDelay = toInstantDuration(options.recoveryDelay ?? { minutes: 30 });
244
377
  const heldActivityTtl = toInstantDuration(options.heldActivityTtl ?? { hours: 168 });
245
378
  const releaseInterval = toInstantDuration(options.releaseInterval ?? { seconds: 1 });
379
+ const configuredStateTtl = options.stateTtl == null ? void 0 : toInstantDuration(options.stateTtl);
246
380
  assertPositiveDuration(recoveryDelay, "recoveryDelay");
247
381
  assertPositiveDuration(heldActivityTtl, "heldActivityTtl");
248
382
  assertPositiveDuration(releaseInterval, "releaseInterval");
383
+ if (configuredStateTtl != null) {
384
+ assertPositiveDuration(configuredStateTtl, "stateTtl");
385
+ if (Temporal.Duration.compare(configuredStateTtl, recoveryDelay) < 0) throw new RangeError("stateTtl must be at least recoveryDelay.");
386
+ }
249
387
  let failure;
250
388
  let pruneFailures;
389
+ let stateTtl;
251
390
  if (options.failure == null) {
252
391
  const failureThreshold = options.failureThreshold ?? 5;
253
392
  if (!Number.isInteger(failureThreshold) || failureThreshold <= 0) throw new TypeError("failureThreshold must be a positive integer.");
@@ -263,9 +402,11 @@ function normalizeCircuitBreakerOptions(options) {
263
402
  const last = timestamps[timestamps.length - 1];
264
403
  return Temporal.Duration.compare(first.until(last), failureWindow) <= 0;
265
404
  };
405
+ stateTtl = configuredStateTtl ?? maxDuration(recoveryDelay.add(failureWindow), recoveryDelay.add(heldActivityTtl));
266
406
  } else {
267
407
  failure = options.failure;
268
- pruneFailures = (timestamps) => timestamps.slice(-MAX_CUSTOM_FAILURE_HISTORY);
408
+ pruneFailures = (timestamps) => timestamps.slice(-100);
409
+ stateTtl = configuredStateTtl ?? recoveryDelay.add(heldActivityTtl);
269
410
  }
270
411
  return {
271
412
  failure,
@@ -273,10 +414,72 @@ function normalizeCircuitBreakerOptions(options) {
273
414
  recoveryDelay,
274
415
  heldActivityTtl,
275
416
  releaseInterval,
417
+ stateTtl,
276
418
  onStateChange: options.onStateChange,
277
419
  onActivityDrop: options.onActivityDrop
278
420
  };
279
421
  }
422
+ function maxDuration(duration, ...durations) {
423
+ return durations.reduce((max, candidate) => Temporal.Duration.compare(candidate, max) > 0 ? candidate : max, duration);
424
+ }
425
+ function isLegacySweepDone(value, now) {
426
+ return getLegacySweepDoneUntil(value, now) != null;
427
+ }
428
+ function getLegacySweepDoneUntil(value, now) {
429
+ if (typeof value !== "object" || value == null || !("state" in value)) return;
430
+ if (value.state === "final") return "final";
431
+ if (value.state === "done" && "retryUntil" in value && typeof value.retryUntil === "string") try {
432
+ const retryUntil = Temporal.Instant.from(value.retryUntil);
433
+ return Temporal.Instant.compare(now, retryUntil) < 0 ? retryUntil : void 0;
434
+ } catch {
435
+ return;
436
+ }
437
+ }
438
+ function isLegacySweepRetryDue(value, now) {
439
+ if (typeof value !== "object" || value == null || !("state" in value) || value.state !== "done" || !("retryUntil" in value) || typeof value.retryUntil !== "string") return false;
440
+ try {
441
+ return Temporal.Instant.compare(now, Temporal.Instant.from(value.retryUntil)) >= 0;
442
+ } catch {
443
+ return false;
444
+ }
445
+ }
446
+ function isLegacySweepActive(value, now) {
447
+ return isLegacySweepDone(value, now) || isLegacySweepInProgress(value, now);
448
+ }
449
+ function isLegacySweepInProgress(value, now) {
450
+ if (typeof value !== "object" || value == null || !("state" in value) || value.state !== "sweeping" || !("started" in value) || typeof value.started !== "string") return false;
451
+ try {
452
+ return Temporal.Instant.compare(now, Temporal.Instant.from(value.started).add(LEGACY_SWEEP_LOCK_TTL)) < 0;
453
+ } catch {
454
+ return false;
455
+ }
456
+ }
457
+ function getLegacySweepRetryUntil(value) {
458
+ if (typeof value !== "object" || value == null || !("retryUntil" in value) || typeof value.retryUntil !== "string") return;
459
+ try {
460
+ Temporal.Instant.from(value.retryUntil);
461
+ return value.retryUntil;
462
+ } catch {
463
+ return;
464
+ }
465
+ }
466
+ function isCurrentCircuitBreakerState(value) {
467
+ return typeof value === "object" && value != null && "__fedifyCircuitBreakerStateVersion" in value && value.__fedifyCircuitBreakerStateVersion === CIRCUIT_BREAKER_STATE_VERSION;
468
+ }
469
+ function markCircuitBreakerState(state) {
470
+ return {
471
+ ...state,
472
+ __fedifyCircuitBreakerStateVersion: CIRCUIT_BREAKER_STATE_VERSION
473
+ };
474
+ }
475
+ function stripStoredCircuitBreakerState(state) {
476
+ if (state == null) return void 0;
477
+ const { __fedifyCircuitBreakerStateVersion: _, ...publicState } = state;
478
+ return publicState;
479
+ }
480
+ function delay(milliseconds) {
481
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
482
+ }
280
483
  function toInstantDuration(duration) {
281
484
  const parsed = Temporal.Duration.from(duration);
282
485
  return Temporal.Duration.from({ milliseconds: Math.trunc(parsed.total({
@@ -311,6 +514,9 @@ function parseHeldSince(value) {
311
514
  * valid instant strings, or `undefined` when the stored value is malformed.
312
515
  */
313
516
  function parseCircuitBreakerKvState(value) {
517
+ return stripStoredCircuitBreakerState(parseStoredCircuitBreakerKvState(value));
518
+ }
519
+ function parseStoredCircuitBreakerKvState(value) {
314
520
  const isInstantString = (v) => {
315
521
  if (typeof v !== "string") return false;
316
522
  try {
@@ -330,7 +536,8 @@ function parseCircuitBreakerKvState(value) {
330
536
  state: record.state,
331
537
  failures: record.failures,
332
538
  ...record.opened == null ? {} : { opened: record.opened },
333
- ...record.halfOpened == null ? {} : { halfOpened: record.halfOpened }
539
+ ...record.halfOpened == null ? {} : { halfOpened: record.halfOpened },
540
+ ...record.__fedifyCircuitBreakerStateVersion === CIRCUIT_BREAKER_STATE_VERSION ? { __fedifyCircuitBreakerStateVersion: CIRCUIT_BREAKER_STATE_VERSION } : {}
334
541
  };
335
542
  }
336
543
  //#endregion
@@ -0,0 +1,14 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ //#region src/federation/circuit-breaker-test-utils.ts
5
+ function markCircuitBreakerLegacySweepDone(kv) {
6
+ return kv.set([
7
+ "_fedify",
8
+ "circuit",
9
+ "__fedify_meta",
10
+ "circuit_breaker_state_ttl_sweep_v2"
11
+ ], { state: "final" });
12
+ }
13
+ //#endregion
14
+ export { markCircuitBreakerLegacySweepDone as t };
@@ -2,22 +2,157 @@ import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { getLogger } from "@logtape/logtape";
5
- import { getUserAgent } from "@fedify/vocab-runtime";
5
+ import { FetchError, UrlError, getUserAgent, validatePublicUrl } from "@fedify/vocab-runtime";
6
+ //#region src/utils/body.ts
7
+ /** Decoded JSON body limit: 16 MiB. @internal */
8
+ const MAX_BODY_SIZE = 16 * 1024 * 1024;
9
+ /** A body exceeded the byte limit. @internal */
10
+ var BodyTooLargeError = class extends FetchError {
11
+ /** Creates an error for a body exceeding the given limit. */
12
+ constructor(url, maxBytes) {
13
+ super(url, `Body exceeds the limit of ${maxBytes} bytes`);
14
+ this.name = "BodyTooLargeError";
15
+ }
16
+ };
17
+ /** Validates a finite, positive byte limit. @internal */
18
+ function validateBodySizeLimit(maxBytes) {
19
+ if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("The body size limit must be a positive safe integer.");
20
+ }
21
+ /**
22
+ * Reads UTF-8 text while limiting bytes received from the body stream.
23
+ * Fetch responses are already decompressed, so the stream count is authoritative.
24
+ * @param message The response or request to read.
25
+ * @param maxBytes The maximum number of decoded bytes to read.
26
+ * @param url The URL to include in errors and logs.
27
+ * @returns The decoded body text.
28
+ * @throws {BodyTooLargeError} If the body exceeds the limit.
29
+ * @internal
30
+ */
31
+ async function readBoundedText(message, maxBytes, url) {
32
+ validateBodySizeLimit(maxBytes);
33
+ const reader = message.body?.getReader();
34
+ const tooLarge = () => {
35
+ getLogger([
36
+ "fedify",
37
+ "runtime",
38
+ "body"
39
+ ]).warn("Body from {url} exceeds the limit of {maxBytes} bytes.", {
40
+ url: url.toString(),
41
+ maxBytes
42
+ });
43
+ throw new BodyTooLargeError(url, maxBytes);
44
+ };
45
+ try {
46
+ const length = message.headers.get("Content-Length");
47
+ const encoding = message.headers.get("Content-Encoding");
48
+ if ((encoding == null || encoding.toLowerCase() === "identity") && length != null && /^\d+$/.test(length) && Number(length) > maxBytes) tooLarge();
49
+ if (reader == null) return "";
50
+ let size = 0;
51
+ const textReader = new ReadableStream({ async pull(controller) {
52
+ const { done, value } = await reader.read();
53
+ if (done) {
54
+ controller.close();
55
+ return;
56
+ }
57
+ size += value.byteLength;
58
+ if (size > maxBytes) tooLarge();
59
+ controller.enqueue(value);
60
+ } }, { highWaterMark: 0 }).pipeThrough(new TextDecoderStream()).getReader();
61
+ try {
62
+ const chunks = [];
63
+ while (true) {
64
+ const { done, value } = await textReader.read();
65
+ if (done) break;
66
+ chunks.push(value);
67
+ }
68
+ return chunks.join("");
69
+ } finally {
70
+ textReader.releaseLock();
71
+ }
72
+ } catch (error) {
73
+ if (reader != null) reader.cancel(error).catch(() => {});
74
+ throw error;
75
+ } finally {
76
+ reader?.releaseLock();
77
+ }
78
+ }
79
+ //#endregion
6
80
  //#region src/nodeinfo/client.ts
7
81
  const logger = getLogger([
8
82
  "fedify",
9
83
  "nodeinfo",
10
84
  "client"
11
85
  ]);
86
+ const DEFAULT_MAX_REDIRECTION = 5;
87
+ /**
88
+ * Fetches a URL while guarding against SSRF. Before every request—including
89
+ * each redirect hop—the destination is validated with
90
+ * {@link validatePublicUrl}, so that loopback, link-local, and private
91
+ * addresses, as well as non-HTTP(S) schemes such as `data:`, are rejected.
92
+ * Redirects are followed manually with a cap, so that a public URL cannot
93
+ * bounce the request to an internal address.
94
+ * @param url The URL to fetch.
95
+ * @param headers The request headers.
96
+ * @returns The final response, or `null` if the URL is disallowed, its
97
+ * hostname cannot be resolved, or there are too many redirects.
98
+ */
99
+ async function fetchPublicUrl(url, headers) {
100
+ let current = url instanceof URL ? url : new URL(url);
101
+ let redirected = 0;
102
+ while (true) {
103
+ try {
104
+ await validatePublicUrl(current.href);
105
+ } catch (e) {
106
+ if (e instanceof UrlError) {
107
+ if (e.reason === "dns") logger.error("DNS lookup failed for {url}: {error}", {
108
+ url: current.href,
109
+ error: e
110
+ });
111
+ else logger.error("Refused to fetch a private or invalid URL {url}: {error}", {
112
+ url: current.href,
113
+ error: e
114
+ });
115
+ return null;
116
+ }
117
+ throw e;
118
+ }
119
+ const response = await fetch(current, {
120
+ headers,
121
+ redirect: "manual"
122
+ });
123
+ if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
124
+ redirected++;
125
+ if (redirected >= DEFAULT_MAX_REDIRECTION) {
126
+ logger.error("Too many redirections ({redirections}) while fetching {url}.", {
127
+ redirections: redirected,
128
+ url: current.href
129
+ });
130
+ return null;
131
+ }
132
+ const next = new URL(response.headers.get("Location"), response.url == null || response.url === "" ? current : response.url);
133
+ if (next.protocol !== current.protocol) {
134
+ logger.error("Refused a redirect to a different protocol ({protocol} to {redirectedProtocol}) while fetching NodeInfo.", {
135
+ protocol: current.protocol,
136
+ redirectedProtocol: next.protocol
137
+ });
138
+ return null;
139
+ }
140
+ current = next;
141
+ continue;
142
+ }
143
+ return response;
144
+ }
145
+ }
12
146
  async function getNodeInfo(url, options = {}) {
13
147
  try {
14
148
  let nodeInfoUrl = url;
15
149
  if (!options.direct) {
16
150
  const wellKnownUrl = new URL("/.well-known/nodeinfo", url);
17
- const wellKnownResponse = await fetch(wellKnownUrl, { headers: {
151
+ const wellKnownResponse = await fetchPublicUrl(wellKnownUrl, {
18
152
  Accept: "application/json",
19
153
  "User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
20
- } });
154
+ });
155
+ if (wellKnownResponse == null) return void 0;
21
156
  if (!wellKnownResponse.ok) {
22
157
  logger.error("Failed to fetch {url}: {status} {statusText}", {
23
158
  url: wellKnownUrl.href,
@@ -26,7 +161,7 @@ async function getNodeInfo(url, options = {}) {
26
161
  });
27
162
  return;
28
163
  }
29
- const wellKnownRd = await wellKnownResponse.json();
164
+ const wellKnownRd = JSON.parse(await readBoundedText(wellKnownResponse, MAX_BODY_SIZE, wellKnownUrl));
30
165
  const link = wellKnownRd?.links?.find((link) => link != null && "rel" in link && (link.rel === "http://nodeinfo.diaspora.software/ns/schema/2.0" || link.rel === "http://nodeinfo.diaspora.software/ns/schema/2.1") && "href" in link && link.href != null);
31
166
  if (link == null || link.href == null) {
32
167
  logger.error("Failed to find a NodeInfo document link from {url}: {resourceDescriptor}", {
@@ -37,10 +172,11 @@ async function getNodeInfo(url, options = {}) {
37
172
  }
38
173
  nodeInfoUrl = link.href;
39
174
  }
40
- const response = await fetch(nodeInfoUrl, { headers: {
175
+ const response = await fetchPublicUrl(nodeInfoUrl, {
41
176
  Accept: "application/json",
42
177
  "User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
43
- } });
178
+ });
179
+ if (response == null) return void 0;
44
180
  if (!response.ok) {
45
181
  logger.error("Failed to fetch NodeInfo document from {url}: {status} {statusText}", {
46
182
  url: nodeInfoUrl.toString(),
@@ -49,7 +185,7 @@ async function getNodeInfo(url, options = {}) {
49
185
  });
50
186
  return;
51
187
  }
52
- const data = await response.json();
188
+ const data = JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, nodeInfoUrl));
53
189
  if (options.parse === "none") return data;
54
190
  return parseNodeInfo(data, { tryBestEffort: options.parse === "best-effort" }) ?? void 0;
55
191
  } catch (error) {
@@ -253,4 +389,4 @@ function parseUsage(data, options = {}) {
253
389
  };
254
390
  }
255
391
  //#endregion
256
- export { parseProtocol as a, parseUsage as c, parseOutboundService as i, parseInboundService as n, parseServices as o, parseNodeInfo as r, parseSoftware as s, getNodeInfo as t };
392
+ export { parseProtocol as a, parseUsage as c, readBoundedText as d, parseOutboundService as i, BodyTooLargeError as l, parseInboundService as n, parseServices as o, parseNodeInfo as r, parseSoftware as s, getNodeInfo as t, MAX_BODY_SIZE as u };
@@ -1,4 +1,4 @@
1
- import "temporal-polyfill";
1
+ import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { Link, Object as Object$1 } from "@fedify/vocab";
@@ -16,8 +16,14 @@ var TaskCodec = class TaskCodec {
16
16
  this.options = options;
17
17
  }
18
18
  /** Serializes `data`, encoding any vocabulary object as its JSON-LD. */
19
- serialize = (data) => stringifyAsync(data, { Vocab: this.#stringifyVocab });
20
- deserialize = async (raw) => await this.#revive(/* @__PURE__ */ new Map())(parse(raw, { Vocab: VocabHolder.from }));
19
+ serialize = (data) => stringifyAsync(data, {
20
+ Vocab: this.#stringifyVocab,
21
+ FedifyTemporal: stringifyTemporal
22
+ });
23
+ deserialize = async (raw) => await this.#revive(/* @__PURE__ */ new Map())(parse(raw, {
24
+ Vocab: VocabHolder.from,
25
+ FedifyTemporal: parseTemporal
26
+ }));
21
27
  /** Validates `data` against `schema`, then serializes it. */
22
28
  encode = async (schema, data) => this.serialize(await TaskCodec.validate(schema, data));
23
29
  /** Deserializes `raw`, then validates the result against `schema`. */
@@ -83,6 +89,23 @@ var TaskCodec = class TaskCodec {
83
89
  ];
84
90
  };
85
91
  const isVocab = (value) => value instanceof Object$1 || value instanceof Link;
92
+ const temporalTypeNames = [
93
+ "Duration",
94
+ "Instant",
95
+ "PlainDate",
96
+ "PlainTime",
97
+ "PlainDateTime",
98
+ "PlainMonthDay",
99
+ "PlainYearMonth",
100
+ "ZonedDateTime"
101
+ ];
102
+ function stringifyTemporal(value) {
103
+ for (const name of temporalTypeNames) if (value instanceof Temporal[name]) return [name, value.toString()];
104
+ return false;
105
+ }
106
+ function parseTemporal([name, value]) {
107
+ return Temporal[name].from(value);
108
+ }
86
109
  const isPlainObject = (value) => value === null || typeof value !== "object" ? false : isObjectPrototype(globalThis.Object.getPrototypeOf(value));
87
110
  const isObjectPrototype = (proto) => proto === null || proto === globalThis.Object.prototype;
88
111
  const isInstanceOf = (cls) => (v) => v instanceof cls;
@@ -13,7 +13,7 @@ import { encodeHex } from "byte-encodings/hex";
13
13
  async function digest(uris) {
14
14
  const processed = /* @__PURE__ */ new Set();
15
15
  const encoder = new TextEncoder();
16
- const result = new Uint8Array(32);
16
+ const result = /* @__PURE__ */ new Uint8Array(32);
17
17
  for (const uri of uris) {
18
18
  const u = uri instanceof URL ? uri.href : uri;
19
19
  if (processed.has(u)) continue;
@@ -1,6 +1,6 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const require_transformers = require("../transformers-DGwl-hD1.cjs");
3
+ const require_transformers = require("../transformers-BalCrSsr.cjs");
4
4
  exports.actorDehydrator = require_transformers.actorDehydrator;
5
5
  exports.autoIdAssigner = require_transformers.autoIdAssigner;
6
6
  exports.getDefaultActivityTransformers = require_transformers.getDefaultActivityTransformers;
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { Rt as ActivityTransformer, n as Context } from "../context-lxX6I7as.cjs";
2
+ import { en as ActivityTransformer, n as Context } from "../context-tsYPJ2Ew.cjs";
3
3
  import { Activity } from "@fedify/vocab";
4
4
 
5
5
  //#region src/compat/transformers.d.ts
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { Rt as ActivityTransformer, n as Context } from "../context-Zii1f5xS.js";
2
+ import { en as ActivityTransformer, n as Context } from "../context-7NyiqIcg.js";
3
3
  import { Activity } from "@fedify/vocab";
4
4
 
5
5
  //#region src/compat/transformers.d.ts
@@ -1,4 +1,4 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { n as autoIdAssigner, r as getDefaultActivityTransformers, t as actorDehydrator } from "../transformers-DXze6FUB.js";
3
+ import { n as autoIdAssigner, r as getDefaultActivityTransformers, t as actorDehydrator } from "../transformers-DAtXl_Kc.js";
4
4
  export { actorDehydrator, autoIdAssigner, getDefaultActivityTransformers };