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

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 (150) 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-f5-YwHfj.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-Deyoxesx.mjs} +1 -1
  26. package/dist/{docloader-0vAfF6XW.mjs → docloader-B3kuDBg9.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-BzRjeOky.cjs} +855 -114
  53. package/dist/{temporal-polyfill-BCdTHexd.cjs → http-AyKngJ_h.cjs} +2801 -104
  54. package/dist/{http-BbnOMQoy.js → http-Bw6s_dOB.js} +3 -3
  55. package/dist/{http-DUmWq5Pw.mjs → http-TjhZfiSN.mjs} +5 -5
  56. package/dist/{key-CM7wDnZG.mjs → key-b4kf4yZT.mjs} +2 -2
  57. package/dist/{kv-cache-zmgG1HUp.cjs → kv-cache-BTVaZrf-.cjs} +2 -4
  58. package/dist/{kv-cache-ZQ22hm44.js → kv-cache-CQsh-KUw.js} +1 -1
  59. package/dist/{kv-cache-DEXKemJ9.mjs → kv-cache-CpILvhUV.mjs} +1 -1
  60. package/dist/{ld-BttH_-8K.mjs → ld-CKujkAfG.mjs} +5 -5
  61. package/dist/{metrics-DT-CnD1g.mjs → metrics-AlCGTDrr.mjs} +2 -2
  62. package/dist/metrics-DVKYY6K1.d.cts +36 -0
  63. package/dist/metrics-DoRtI0qU.d.ts +36 -0
  64. package/dist/{middleware-DrroX3aD.mjs → middleware-C9Y5lLZN.mjs} +60 -43
  65. package/dist/{middleware-D3IAVMcT.js → middleware-CZcuedl-.js} +241 -40
  66. package/dist/{middleware-ChlYS0Af.mjs → middleware-LPbZz6-1.mjs} +1 -1
  67. package/dist/{mod-BrpVsSfY.d.cts → mod-CLHkCwLm.d.cts} +3 -3
  68. package/dist/{mod-B0hW12_O.d.cts → mod-CuDDaIIt.d.cts} +1 -1
  69. package/dist/{mod-COIAjwRS.d.ts → mod-D-IhOEOP.d.ts} +1 -1
  70. package/dist/{mod-DdONo-ep.d.cts → mod-D-abvu2C.d.cts} +1 -1
  71. package/dist/{mod-BsCE5Ihq.d.ts → mod-D6O8C7eN.d.ts} +1 -1
  72. package/dist/{mod-CNef4in0.d.ts → mod-DypxPS9H.d.ts} +3 -3
  73. package/dist/mod.cjs +24 -25
  74. package/dist/mod.d.cts +8 -9
  75. package/dist/mod.d.ts +8 -9
  76. package/dist/mod.js +7 -7
  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-D1O8v8FB.mjs} +2 -2
  90. package/dist/{proof-CqA-2nfW.js → proof-BTBolSnd.js} +6 -6
  91. package/dist/{proof-a60oVJ_4.cjs → proof-DcSm-2eG.cjs} +10 -11
  92. package/dist/{proof-DiQ0uBal.mjs → proof-DeMR7Akd.mjs} +6 -6
  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-DG7JG280.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 +2 -2
  102. package/dist/sig/mod.d.cts +3 -3
  103. package/dist/sig/mod.d.ts +3 -3
  104. package/dist/sig/mod.js +2 -2
  105. package/dist/sig/owner.test.mjs +6 -6
  106. package/dist/sig/proof.test.mjs +10 -8
  107. package/dist/std__assert-CxFT0MNt.mjs +8 -0
  108. package/dist/{temporal-Bpxpsl2k.mjs → temporal-DA3jx8x1.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/std__assert-B2NnY9bq.mjs +0 -79
  128. /package/dist/{accept-8PiolDP7.mjs → accept-CPVqki78.mjs} +0 -0
  129. /package/dist/{activity-listener-BtTSxZWA.mjs → activity-listener-C-BSbKI7.mjs} +0 -0
  130. /package/dist/{assert_equals-BV7oLMxm.mjs → assert_equals-CBlRfA-_.mjs} +0 -0
  131. /package/dist/{client-CAM_bQXx.d.cts → client-D-XP_Ccb.d.cts} +0 -0
  132. /package/dist/{client-CSddvgWN.d.ts → client-D-XP_Ccb.d.ts} +0 -0
  133. /package/dist/{codec-BBga-FWI.mjs → codec-peqOdBaE.mjs} +0 -0
  134. /package/dist/{context-BJXxPElm.mjs → context-Dt36KFEJ.mjs} +0 -0
  135. /package/dist/{http-VyDTd4G3.d.cts → http-6TI0tI03.d.cts} +0 -0
  136. /package/dist/{http-lf8Hsd91.d.ts → http-CdrSn2ip.d.ts} +0 -0
  137. /package/dist/{keycache-_DHqz5z_.mjs → keycache-0Jk78XmB.mjs} +0 -0
  138. /package/dist/{keys-Dbyd7bhl.mjs → keys-DIJNHW-Y.mjs} +0 -0
  139. /package/dist/{kv-D6hNiMTK.d.ts → kv-BhNbN8Ed.d.cts} +0 -0
  140. /package/dist/{kv-gJ8LYbxX.d.cts → kv-BhNbN8Ed.d.ts} +0 -0
  141. /package/dist/{kv-CXBAok3k.mjs → kv-DCHkaJ9-.mjs} +0 -0
  142. /package/dist/{mq-Bo7Etgh6.mjs → mq-Dm1Wmtm-.mjs} +0 -0
  143. /package/dist/{negotiation-RuwrPFqi.mjs → negotiation-CCocWT_N.mjs} +0 -0
  144. /package/dist/{owner-CnngXDNJ.d.ts → owner-DGSfntI-.d.ts} +0 -0
  145. /package/dist/{owner-DEvZuyOE.d.cts → owner-r_waTiQj.d.cts} +0 -0
  146. /package/dist/{retry-DgFqeS5k.mjs → retry-zX5xw5e0.mjs} +0 -0
  147. /package/dist/{chunk-D5IdezsZ.js → rolldown-runtime-BC-Eo5Mj.js} +0 -0
  148. /package/dist/{chunk-Czpeue4M.cjs → rolldown-runtime-Dmm3t92W.cjs} +0 -0
  149. /package/dist/{transformers-DXze6FUB.js → transformers-DAtXl_Kc.js} +0 -0
  150. /package/dist/{types-CrtdVuDP.mjs → types-DE_LV1JB.mjs} +0 -0
package/README.md CHANGED
@@ -207,7 +207,7 @@ This project exists thanks to all the people who contribute, donate, and sponsor
207
207
  it. We are grateful for their support. We would like to thank the following
208
208
  financial contributors:[^2]
209
209
 
210
- [^2]: Those lists are automatically updated every hour.
210
+ [^2]: Those lists are automatically updated twice a day.
211
211
 
212
212
  <!-- hongdown-disable -->
213
213
 
@@ -1,7 +1,7 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { l as AssertionError } from "./assert_equals-BV7oLMxm.mjs";
4
+ import { l as AssertionError } from "./assert_equals-CBlRfA-_.mjs";
5
5
  //#region ../../node_modules/.pnpm/@jsr+std__assert@0.226.0/node_modules/@jsr/std__assert/assert.js
6
6
  /**
7
7
  * Make an assertion, error will be thrown if `expr` does not have truthy value.
@@ -0,0 +1,28 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { l as AssertionError } from "./assert_equals-CBlRfA-_.mjs";
5
+ //#region ../../node_modules/.pnpm/@jsr+std__assert@0.226.0/node_modules/@jsr/std__assert/assert_exists.js
6
+ /**
7
+ * Make an assertion that actual is not null or undefined.
8
+ * If not then throw.
9
+ *
10
+ * @example Usage
11
+ * ```ts no-eval
12
+ * import { assertExists } from "@std/assert/assert-exists";
13
+ *
14
+ * assertExists("something"); // Doesn't throw
15
+ * assertExists(undefined); // Throws
16
+ * ```
17
+ *
18
+ * @typeParam T The type of the actual value.
19
+ * @param actual The actual value to check.
20
+ * @param msg The optional message to include in the error if the assertion fails.
21
+ */ function assertExists(actual, msg) {
22
+ if (actual === void 0 || actual === null) {
23
+ msg = `Expected actual: "${actual}" to not be null or undefined${msg ? `: ${msg}` : "."}`;
24
+ throw new AssertionError(msg);
25
+ }
26
+ }
27
+ //#endregion
28
+ export { assertExists as t };
@@ -0,0 +1,23 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { l as AssertionError } from "./assert_equals-CBlRfA-_.mjs";
5
+ //#region ../../node_modules/.pnpm/@jsr+std__assert@0.226.0/node_modules/@jsr/std__assert/assert_false.js
6
+ /**
7
+ * Make an assertion, error will be thrown if `expr` have truthy value.
8
+ *
9
+ * @example Usage
10
+ * ```ts no-eval
11
+ * import { assertFalse } from "@std/assert/assert-false";
12
+ *
13
+ * assertFalse(false); // Doesn't throw
14
+ * assertFalse(true); // Throws
15
+ * ```
16
+ *
17
+ * @param expr The expression to test.
18
+ * @param msg The optional message to display if the assertion fails.
19
+ */ function assertFalse(expr, msg = "") {
20
+ if (expr) throw new AssertionError(msg);
21
+ }
22
+ //#endregion
23
+ export { assertFalse as t };
@@ -0,0 +1,30 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { l as AssertionError, s as format } from "./assert_equals-CBlRfA-_.mjs";
5
+ //#region ../../node_modules/.pnpm/@jsr+std__assert@0.226.0/node_modules/@jsr/std__assert/assert_greater_or_equal.js
6
+ /**
7
+ * Make an assertion that `actual` is greater than or equal to `expected`.
8
+ * If not then throw.
9
+ *
10
+ * @example Usage
11
+ * ```ts no-eval
12
+ * import { assertGreaterOrEqual } from "@std/assert/assert-greater-or-equal";
13
+ *
14
+ * assertGreaterOrEqual(2, 1); // Doesn't throw
15
+ * assertGreaterOrEqual(1, 1); // Doesn't throw
16
+ * assertGreaterOrEqual(0, 1); // Throws
17
+ * ```
18
+ *
19
+ * @typeParam T The type of the values to compare.
20
+ * @param actual The actual value to compare.
21
+ * @param expected The expected value to compare.
22
+ * @param msg The optional message to display if the assertion fails.
23
+ */ function assertGreaterOrEqual(actual, expected, msg) {
24
+ if (actual >= expected) return;
25
+ const actualString = format(actual);
26
+ const expectedString = format(expected);
27
+ throw new AssertionError(msg ?? `Expect ${actualString} >= ${expectedString}`);
28
+ }
29
+ //#endregion
30
+ export { assertGreaterOrEqual as t };
@@ -1,7 +1,7 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { l as AssertionError } from "./assert_equals-BV7oLMxm.mjs";
4
+ import { l as AssertionError } from "./assert_equals-CBlRfA-_.mjs";
5
5
  //#region ../../node_modules/.pnpm/@jsr+std__assert@0.226.0/node_modules/@jsr/std__assert/assert_instance_of.js
6
6
  /**
7
7
  * Make an assertion that `obj` is an instance of `type`.
@@ -1,7 +1,7 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { c as equal, l as AssertionError } from "./assert_equals-BV7oLMxm.mjs";
4
+ import { c as equal, l as AssertionError } from "./assert_equals-CBlRfA-_.mjs";
5
5
  //#region ../../node_modules/.pnpm/@jsr+std__assert@0.226.0/node_modules/@jsr/std__assert/assert_not_equals.js
6
6
  /**
7
7
  * Make an assertion that `actual` and `expected` are not equal, deeply.
@@ -0,0 +1,39 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { l as AssertionError } from "./assert_equals-CBlRfA-_.mjs";
5
+ import { n as assertIsError } from "./assert_throws-BJ5wuvR2.mjs";
6
+ //#region ../../node_modules/.pnpm/@jsr+std__assert@0.226.0/node_modules/@jsr/std__assert/assert_rejects.js
7
+ async function assertRejects(fn, errorClassOrMsg, msgIncludesOrMsg, msg) {
8
+ let ErrorClass = void 0;
9
+ let msgIncludes = void 0;
10
+ let err;
11
+ if (typeof errorClassOrMsg !== "string") {
12
+ if (errorClassOrMsg === void 0 || errorClassOrMsg.prototype instanceof Error || errorClassOrMsg.prototype === Error.prototype) {
13
+ ErrorClass = errorClassOrMsg;
14
+ msgIncludes = msgIncludesOrMsg;
15
+ }
16
+ } else msg = errorClassOrMsg;
17
+ let doesThrow = false;
18
+ let isPromiseReturned = false;
19
+ const msgSuffix = msg ? `: ${msg}` : ".";
20
+ try {
21
+ const possiblePromise = fn();
22
+ if (possiblePromise && typeof possiblePromise === "object" && typeof possiblePromise.then === "function") {
23
+ isPromiseReturned = true;
24
+ await possiblePromise;
25
+ } else throw Error();
26
+ } catch (error) {
27
+ if (!isPromiseReturned) throw new AssertionError(`Function throws when expected to reject${msgSuffix}`);
28
+ if (ErrorClass) {
29
+ if (!(error instanceof Error)) throw new AssertionError(`A non-Error object was rejected${msgSuffix}`);
30
+ assertIsError(error, ErrorClass, msgIncludes, msg);
31
+ }
32
+ err = error;
33
+ doesThrow = true;
34
+ }
35
+ if (!doesThrow) throw new AssertionError(`Expected function to reject${msgSuffix}`);
36
+ return err;
37
+ }
38
+ //#endregion
39
+ export { assertRejects as t };
@@ -1,7 +1,7 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { a as red, i as buildMessage, l as AssertionError, n as diffStr, r as diff, s as format } from "./assert_equals-BV7oLMxm.mjs";
4
+ import { a as red, i as buildMessage, l as AssertionError, n as diffStr, r as diff, s as format } from "./assert_equals-CBlRfA-_.mjs";
5
5
  //#region ../../node_modules/.pnpm/@jsr+std__assert@0.226.0/node_modules/@jsr/std__assert/assert_strict_equals.js
6
6
  /**
7
7
  * Make an assertion that `actual` and `expected` are equal using
@@ -1,7 +1,7 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { l as AssertionError, o as stripAnsiCode } from "./assert_equals-BV7oLMxm.mjs";
4
+ import { l as AssertionError, o as stripAnsiCode } from "./assert_equals-CBlRfA-_.mjs";
5
5
  //#region ../../node_modules/.pnpm/@jsr+std__assert@0.226.0/node_modules/@jsr/std__assert/assert_is_error.js
6
6
  /**
7
7
  * Make an assertion that `error` is an `Error`.
@@ -1,8 +1,8 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-DtHcfB0f.mjs";
5
- import { t as ActivityListenerSet } from "./activity-listener-BtTSxZWA.mjs";
4
+ import { n as version, t as name } from "./deno-Deyoxesx.mjs";
5
+ import { t as ActivityListenerSet } from "./activity-listener-C-BSbKI7.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
7
7
  import { Router, RouterError, assertPath } from "@fedify/uri-template";
8
8
  import { Tombstone, getTypeId } from "@fedify/vocab";
@@ -85,7 +85,7 @@ var FederationBuilderImpl = class {
85
85
  * specified.
86
86
  */
87
87
  async build(options) {
88
- const { FederationImpl } = await import("./middleware-ChlYS0Af.mjs");
88
+ const { FederationImpl } = await import("./middleware-LPbZz6-1.mjs");
89
89
  const f = new FederationImpl(options);
90
90
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
91
91
  f.router = this.router.clone();
@@ -96,6 +96,8 @@ var FederationBuilderImpl = class {
96
96
  f.webFingerLinksDispatcher = this.webFingerLinksDispatcher;
97
97
  f.objectCallbacks = { ...this.objectCallbacks };
98
98
  f.objectTypeIds = { ...this.objectTypeIds };
99
+ f.collectionCallbacks = { ...this.collectionCallbacks };
100
+ f.collectionTypeIds = { ...this.collectionTypeIds };
99
101
  f.inboxPath = this.inboxPath;
100
102
  f.outboxPath = this.outboxPath;
101
103
  f.inboxCallbacks = this.inboxCallbacks == null ? void 0 : { ...this.inboxCallbacks };
@@ -3,7 +3,13 @@ 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_v1"];
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_RETRY_WINDOW = Temporal.Duration.from({ hours: 168 });
11
+ const LEGACY_SWEEP_WAIT_INTERVAL = 10;
12
+ const LEGACY_SWEEP_CAS_ATTEMPTS = 3;
7
13
  /**
8
14
  * Tracks reachability state for remote outbox delivery hosts.
9
15
  * @since 2.3.0
@@ -13,6 +19,8 @@ var CircuitBreaker = class {
13
19
  #prefix;
14
20
  #options;
15
21
  #now;
22
+ #legacySweep;
23
+ #legacySweepDone;
16
24
  #stateChangeObserver;
17
25
  constructor(options) {
18
26
  this.#kv = options.kv;
@@ -29,6 +37,7 @@ var CircuitBreaker = class {
29
37
  return now.until(this.#capHeldRetryAt(now, heldSince, now.add(delay)));
30
38
  }
31
39
  async beforeSend(remoteHost, message) {
40
+ this.#sweepLegacyStates();
32
41
  const heldSince = parseHeldSince(message.circuitHeldSince);
33
42
  const now = this.#now();
34
43
  if (heldSince != null && Temporal.Instant.compare(heldSince.add(this.#options.heldActivityTtl), now) <= 0) return {
@@ -110,6 +119,7 @@ var CircuitBreaker = class {
110
119
  throw new Error(`Failed to update circuit breaker state for ${remoteHost}`);
111
120
  }
112
121
  async recordSuccess(remoteHost) {
122
+ this.#sweepLegacyStates();
113
123
  for (let attempt = 0; attempt < 10; attempt++) {
114
124
  const oldState = await this.#get(remoteHost);
115
125
  if (oldState == null) return void 0;
@@ -130,6 +140,7 @@ var CircuitBreaker = class {
130
140
  return await this.recordSuccess(remoteHost);
131
141
  }
132
142
  async recordFailure(remoteHost) {
143
+ this.#sweepLegacyStates();
133
144
  const now = this.#now();
134
145
  for (let attempt = 0; attempt < 10; attempt++) {
135
146
  const oldState = await this.#get(remoteHost);
@@ -177,26 +188,122 @@ var CircuitBreaker = class {
177
188
  }
178
189
  }
179
190
  async getState(remoteHost) {
180
- return await this.#get(remoteHost);
191
+ this.#sweepLegacyStates();
192
+ return stripStoredCircuitBreakerState(await this.#get(remoteHost));
193
+ }
194
+ /**
195
+ * The currently running background legacy sweep, if any.
196
+ * @internal
197
+ */
198
+ get pendingSweep() {
199
+ return this.#legacySweep;
181
200
  }
182
201
  #key(remoteHost) {
183
202
  return [...this.#prefix, remoteHost];
184
203
  }
204
+ #legacySweepMarkerKey() {
205
+ return [...this.#prefix, ...LEGACY_SWEEP_MARKER];
206
+ }
207
+ #sweepLegacyStates() {
208
+ if (this.#kv.cas == null) return;
209
+ if (this.#options.stateTtl == null) return;
210
+ if (this.#legacySweep != null) return;
211
+ if (this.#isLegacySweepDone()) return;
212
+ this.#legacySweep = this.#sweepLegacyStatesImpl().catch((error) => {
213
+ getLogger([
214
+ "fedify",
215
+ "federation",
216
+ "circuit"
217
+ ]).warn("Failed to sweep legacy circuit breaker state:\n{error}", { error });
218
+ }).finally(() => {
219
+ this.#legacySweep = void 0;
220
+ });
221
+ }
222
+ async #sweepLegacyStatesImpl() {
223
+ const markerKey = this.#legacySweepMarkerKey();
224
+ const marker = await this.#acquireLegacySweep(markerKey);
225
+ if (marker === "done") return;
226
+ try {
227
+ for await (const { key, value } of this.#kv.list(this.#prefix)) {
228
+ if (key.length !== this.#prefix.length + 1) continue;
229
+ await this.#migrateLegacyState(key, value);
230
+ }
231
+ } catch (error) {
232
+ await this.#deleteIfUnchanged(markerKey, marker);
233
+ throw error;
234
+ }
235
+ const finishedMarker = this.#finishLegacySweepMarker(marker);
236
+ if (await this.#kv.cas(markerKey, marker, finishedMarker)) this.#rememberLegacySweepMarker(finishedMarker);
237
+ }
238
+ #finishLegacySweepMarker(marker) {
239
+ const retryUntil = getLegacySweepRetryUntil(marker);
240
+ if (retryUntil != null && Temporal.Instant.compare(this.#now(), Temporal.Instant.from(retryUntil)) < 0) return {
241
+ state: "done",
242
+ retryUntil
243
+ };
244
+ return { state: "final" };
245
+ }
246
+ async #migrateLegacyState(key, value) {
247
+ if (isCurrentCircuitBreakerState(value)) return;
248
+ const state = parseCircuitBreakerKvState(value);
249
+ if (state != null) {
250
+ await this.#kv.cas(key, value, markCircuitBreakerState(state), this.#setOptions());
251
+ return;
252
+ }
253
+ await this.#deleteIfUnchanged(key, value);
254
+ }
255
+ async #deleteIfUnchanged(key, value) {
256
+ if (await this.#kv.cas(key, value, LEGACY_SWEEP_DELETING_MARKER, { ttl: LEGACY_SWEEP_LOCK_TTL })) await this.#kv.delete(key);
257
+ }
258
+ async #acquireLegacySweep(markerKey) {
259
+ for (let attempt = 0; attempt < LEGACY_SWEEP_CAS_ATTEMPTS; attempt++) {
260
+ const marker = await this.#kv.get(markerKey);
261
+ const now = this.#now();
262
+ if (isLegacySweepActive(marker, now)) {
263
+ this.#rememberLegacySweepMarker(marker);
264
+ return "done";
265
+ }
266
+ const retryUntil = isLegacySweepRetryDue(marker, now) ? marker.retryUntil : getLegacySweepRetryUntil(marker) ?? now.add(LEGACY_SWEEP_RETRY_WINDOW).toString();
267
+ const sweeping = {
268
+ state: "sweeping",
269
+ started: now.toString(),
270
+ retryUntil
271
+ };
272
+ if (await this.#kv.cas(markerKey, marker ?? void 0, sweeping, { ttl: LEGACY_SWEEP_LOCK_TTL })) return sweeping;
273
+ await delay(LEGACY_SWEEP_WAIT_INTERVAL);
274
+ }
275
+ return "done";
276
+ }
277
+ #isLegacySweepDone() {
278
+ if (this.#legacySweepDone === "final") return true;
279
+ if (this.#legacySweepDone == null) return false;
280
+ if (Temporal.Instant.compare(this.#now(), this.#legacySweepDone) < 0) return true;
281
+ this.#legacySweepDone = void 0;
282
+ return false;
283
+ }
284
+ #rememberLegacySweepMarker(marker) {
285
+ const doneUntil = getLegacySweepDoneUntil(marker, this.#now());
286
+ if (doneUntil != null) this.#legacySweepDone = doneUntil;
287
+ }
185
288
  #capHeldRetryAt(now, heldSince, retryAt) {
186
289
  const expiresAt = (heldSince ?? now).add(this.#options.heldActivityTtl);
187
290
  return Temporal.Instant.compare(expiresAt, retryAt) < 0 ? expiresAt : retryAt;
188
291
  }
189
292
  async #get(remoteHost) {
190
- return parseCircuitBreakerKvState(await this.#kv.get(this.#key(remoteHost)));
293
+ return parseStoredCircuitBreakerKvState(await this.#kv.get(this.#key(remoteHost)));
191
294
  }
192
295
  async #replace(remoteHost, oldState, newState) {
193
296
  const key = this.#key(remoteHost);
297
+ const storedState = newState == null ? void 0 : markCircuitBreakerState(newState);
194
298
  if (this.#kv.cas == null) {
195
- if (newState == null) await this.#kv.delete(key);
196
- else await this.#kv.set(key, newState);
299
+ if (storedState == null) await this.#kv.delete(key);
300
+ else await this.#kv.set(key, storedState, this.#setOptions());
197
301
  return true;
198
302
  }
199
- return await this.#kv.cas(key, oldState, newState);
303
+ return await this.#kv.cas(key, oldState, storedState, storedState == null ? void 0 : this.#setOptions());
304
+ }
305
+ #setOptions() {
306
+ return this.#options.stateTtl == null ? void 0 : { ttl: this.#options.stateTtl };
200
307
  }
201
308
  async #notifyStateChange(remoteHost, previousState, newState) {
202
309
  try {
@@ -236,18 +343,25 @@ var CircuitBreaker = class {
236
343
  * @param options The public circuit breaker options supplied to Fedify.
237
344
  * @returns The normalized failure predicate, failure pruning function,
238
345
  * duration values, and optional callbacks with defaults applied.
239
- * @throws {RangeError} If any configured duration is not positive.
346
+ * @throws {RangeError} If any configured duration is not positive, or if
347
+ * `stateTtl` is shorter than `recoveryDelay`.
240
348
  * @throws {TypeError} If `failureThreshold` is not a positive integer.
241
349
  */
242
350
  function normalizeCircuitBreakerOptions(options) {
243
351
  const recoveryDelay = toInstantDuration(options.recoveryDelay ?? { minutes: 30 });
244
352
  const heldActivityTtl = toInstantDuration(options.heldActivityTtl ?? { hours: 168 });
245
353
  const releaseInterval = toInstantDuration(options.releaseInterval ?? { seconds: 1 });
354
+ const configuredStateTtl = options.stateTtl == null ? void 0 : toInstantDuration(options.stateTtl);
246
355
  assertPositiveDuration(recoveryDelay, "recoveryDelay");
247
356
  assertPositiveDuration(heldActivityTtl, "heldActivityTtl");
248
357
  assertPositiveDuration(releaseInterval, "releaseInterval");
358
+ if (configuredStateTtl != null) {
359
+ assertPositiveDuration(configuredStateTtl, "stateTtl");
360
+ if (Temporal.Duration.compare(configuredStateTtl, recoveryDelay) < 0) throw new RangeError("stateTtl must be at least recoveryDelay.");
361
+ }
249
362
  let failure;
250
363
  let pruneFailures;
364
+ let stateTtl;
251
365
  if (options.failure == null) {
252
366
  const failureThreshold = options.failureThreshold ?? 5;
253
367
  if (!Number.isInteger(failureThreshold) || failureThreshold <= 0) throw new TypeError("failureThreshold must be a positive integer.");
@@ -263,9 +377,11 @@ function normalizeCircuitBreakerOptions(options) {
263
377
  const last = timestamps[timestamps.length - 1];
264
378
  return Temporal.Duration.compare(first.until(last), failureWindow) <= 0;
265
379
  };
380
+ stateTtl = configuredStateTtl ?? maxDuration(recoveryDelay.add(failureWindow), recoveryDelay.add(heldActivityTtl));
266
381
  } else {
267
382
  failure = options.failure;
268
- pruneFailures = (timestamps) => timestamps.slice(-MAX_CUSTOM_FAILURE_HISTORY);
383
+ pruneFailures = (timestamps) => timestamps.slice(-100);
384
+ stateTtl = configuredStateTtl;
269
385
  }
270
386
  return {
271
387
  failure,
@@ -273,10 +389,72 @@ function normalizeCircuitBreakerOptions(options) {
273
389
  recoveryDelay,
274
390
  heldActivityTtl,
275
391
  releaseInterval,
392
+ stateTtl,
276
393
  onStateChange: options.onStateChange,
277
394
  onActivityDrop: options.onActivityDrop
278
395
  };
279
396
  }
397
+ function maxDuration(duration, ...durations) {
398
+ return durations.reduce((max, candidate) => Temporal.Duration.compare(candidate, max) > 0 ? candidate : max, duration);
399
+ }
400
+ function isLegacySweepDone(value, now) {
401
+ return getLegacySweepDoneUntil(value, now) != null;
402
+ }
403
+ function getLegacySweepDoneUntil(value, now) {
404
+ if (typeof value !== "object" || value == null || !("state" in value)) return;
405
+ if (value.state === "final") return "final";
406
+ if (value.state === "done" && "retryUntil" in value && typeof value.retryUntil === "string") try {
407
+ const retryUntil = Temporal.Instant.from(value.retryUntil);
408
+ return Temporal.Instant.compare(now, retryUntil) < 0 ? retryUntil : void 0;
409
+ } catch {
410
+ return;
411
+ }
412
+ }
413
+ function isLegacySweepRetryDue(value, now) {
414
+ if (typeof value !== "object" || value == null || !("state" in value) || value.state !== "done" || !("retryUntil" in value) || typeof value.retryUntil !== "string") return false;
415
+ try {
416
+ return Temporal.Instant.compare(now, Temporal.Instant.from(value.retryUntil)) >= 0;
417
+ } catch {
418
+ return false;
419
+ }
420
+ }
421
+ function isLegacySweepActive(value, now) {
422
+ return isLegacySweepDone(value, now) || isLegacySweepInProgress(value, now);
423
+ }
424
+ function isLegacySweepInProgress(value, now) {
425
+ if (typeof value !== "object" || value == null || !("state" in value) || value.state !== "sweeping" || !("started" in value) || typeof value.started !== "string") return false;
426
+ try {
427
+ return Temporal.Instant.compare(now, Temporal.Instant.from(value.started).add(LEGACY_SWEEP_LOCK_TTL)) < 0;
428
+ } catch {
429
+ return false;
430
+ }
431
+ }
432
+ function getLegacySweepRetryUntil(value) {
433
+ if (typeof value !== "object" || value == null || !("retryUntil" in value) || typeof value.retryUntil !== "string") return;
434
+ try {
435
+ Temporal.Instant.from(value.retryUntil);
436
+ return value.retryUntil;
437
+ } catch {
438
+ return;
439
+ }
440
+ }
441
+ function isCurrentCircuitBreakerState(value) {
442
+ return typeof value === "object" && value != null && "__fedifyCircuitBreakerStateVersion" in value && value.__fedifyCircuitBreakerStateVersion === CIRCUIT_BREAKER_STATE_VERSION;
443
+ }
444
+ function markCircuitBreakerState(state) {
445
+ return {
446
+ ...state,
447
+ __fedifyCircuitBreakerStateVersion: CIRCUIT_BREAKER_STATE_VERSION
448
+ };
449
+ }
450
+ function stripStoredCircuitBreakerState(state) {
451
+ if (state == null) return void 0;
452
+ const { __fedifyCircuitBreakerStateVersion: _, ...publicState } = state;
453
+ return publicState;
454
+ }
455
+ function delay(milliseconds) {
456
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
457
+ }
280
458
  function toInstantDuration(duration) {
281
459
  const parsed = Temporal.Duration.from(duration);
282
460
  return Temporal.Duration.from({ milliseconds: Math.trunc(parsed.total({
@@ -311,6 +489,9 @@ function parseHeldSince(value) {
311
489
  * valid instant strings, or `undefined` when the stored value is malformed.
312
490
  */
313
491
  function parseCircuitBreakerKvState(value) {
492
+ return stripStoredCircuitBreakerState(parseStoredCircuitBreakerKvState(value));
493
+ }
494
+ function parseStoredCircuitBreakerKvState(value) {
314
495
  const isInstantString = (v) => {
315
496
  if (typeof v !== "string") return false;
316
497
  try {
@@ -330,7 +511,8 @@ function parseCircuitBreakerKvState(value) {
330
511
  state: record.state,
331
512
  failures: record.failures,
332
513
  ...record.opened == null ? {} : { opened: record.opened },
333
- ...record.halfOpened == null ? {} : { halfOpened: record.halfOpened }
514
+ ...record.halfOpened == null ? {} : { halfOpened: record.halfOpened },
515
+ ...record.__fedifyCircuitBreakerStateVersion === CIRCUIT_BREAKER_STATE_VERSION ? { __fedifyCircuitBreakerStateVersion: CIRCUIT_BREAKER_STATE_VERSION } : {}
334
516
  };
335
517
  }
336
518
  //#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_v1"
11
+ ], { state: "final" });
12
+ }
13
+ //#endregion
14
+ export { markCircuitBreakerLegacySweepDone as t };
@@ -2,22 +2,79 @@ 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 { UrlError, getUserAgent, validatePublicUrl } from "@fedify/vocab-runtime";
6
6
  //#region src/nodeinfo/client.ts
7
7
  const logger = getLogger([
8
8
  "fedify",
9
9
  "nodeinfo",
10
10
  "client"
11
11
  ]);
12
+ const DEFAULT_MAX_REDIRECTION = 5;
13
+ /**
14
+ * Fetches a URL while guarding against SSRF. Before every request—including
15
+ * each redirect hop—the destination is validated with
16
+ * {@link validatePublicUrl}, so that loopback, link-local, and private
17
+ * addresses, as well as non-HTTP(S) schemes such as `data:`, are rejected.
18
+ * Redirects are followed manually with a cap, so that a public URL cannot
19
+ * bounce the request to an internal address.
20
+ * @param url The URL to fetch.
21
+ * @param headers The request headers.
22
+ * @returns The final response, or `null` if the URL is disallowed or there
23
+ * are too many redirects.
24
+ */
25
+ async function fetchPublicUrl(url, headers) {
26
+ let current = url instanceof URL ? url : new URL(url);
27
+ let redirected = 0;
28
+ while (true) {
29
+ try {
30
+ await validatePublicUrl(current.href);
31
+ } catch (e) {
32
+ if (e instanceof UrlError) {
33
+ logger.error("Refused to fetch a private or invalid URL {url}: {error}", {
34
+ url: current.href,
35
+ error: e
36
+ });
37
+ return null;
38
+ }
39
+ throw e;
40
+ }
41
+ const response = await fetch(current, {
42
+ headers,
43
+ redirect: "manual"
44
+ });
45
+ if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
46
+ redirected++;
47
+ if (redirected >= DEFAULT_MAX_REDIRECTION) {
48
+ logger.error("Too many redirections ({redirections}) while fetching {url}.", {
49
+ redirections: redirected,
50
+ url: current.href
51
+ });
52
+ return null;
53
+ }
54
+ const next = new URL(response.headers.get("Location"), response.url == null || response.url === "" ? current : response.url);
55
+ if (next.protocol !== current.protocol) {
56
+ logger.error("Refused a redirect to a different protocol ({protocol} to {redirectedProtocol}) while fetching NodeInfo.", {
57
+ protocol: current.protocol,
58
+ redirectedProtocol: next.protocol
59
+ });
60
+ return null;
61
+ }
62
+ current = next;
63
+ continue;
64
+ }
65
+ return response;
66
+ }
67
+ }
12
68
  async function getNodeInfo(url, options = {}) {
13
69
  try {
14
70
  let nodeInfoUrl = url;
15
71
  if (!options.direct) {
16
72
  const wellKnownUrl = new URL("/.well-known/nodeinfo", url);
17
- const wellKnownResponse = await fetch(wellKnownUrl, { headers: {
73
+ const wellKnownResponse = await fetchPublicUrl(wellKnownUrl, {
18
74
  Accept: "application/json",
19
75
  "User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
20
- } });
76
+ });
77
+ if (wellKnownResponse == null) return void 0;
21
78
  if (!wellKnownResponse.ok) {
22
79
  logger.error("Failed to fetch {url}: {status} {statusText}", {
23
80
  url: wellKnownUrl.href,
@@ -37,10 +94,11 @@ async function getNodeInfo(url, options = {}) {
37
94
  }
38
95
  nodeInfoUrl = link.href;
39
96
  }
40
- const response = await fetch(nodeInfoUrl, { headers: {
97
+ const response = await fetchPublicUrl(nodeInfoUrl, {
41
98
  Accept: "application/json",
42
99
  "User-Agent": typeof options.userAgent === "string" ? options.userAgent : getUserAgent(options.userAgent)
43
- } });
100
+ });
101
+ if (response == null) return void 0;
44
102
  if (!response.ok) {
45
103
  logger.error("Failed to fetch NodeInfo document from {url}: {status} {statusText}", {
46
104
  url: nodeInfoUrl.toString(),
@@ -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-C05Rgxmu.cjs";
2
+ import { Gt as ActivityTransformer, n as Context } from "../context-B-dsBjZ9.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-Cjnr0ig1.js";
2
+ import { Gt as ActivityTransformer, n as Context } from "../context-DrLXudbA.js";
3
3
  import { Activity } from "@fedify/vocab";
4
4
 
5
5
  //#region src/compat/transformers.d.ts