@fedify/fedify 2.3.0-dev.994 → 2.3.0-pr.809.37

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 (138) hide show
  1. package/README.md +16 -7
  2. package/dist/{assert-DikXweDx.mjs → assert-OguE97r2.mjs} +1 -1
  3. package/dist/{assert_instance_of-C4Ri6VuN.mjs → assert_instance_of-DBC5X09g.mjs} +1 -1
  4. package/dist/{assert_not_equals--wG9hV7u.mjs → assert_not_equals-DkVK8oqV.mjs} +1 -1
  5. package/dist/{assert_rejects-B-qJtC9Z.mjs → assert_rejects-DN60FHPX.mjs} +28 -3
  6. package/dist/{assert_strict_equals-Dmjbg-bA.mjs → assert_strict_equals-XEgZAlrj.mjs} +1 -1
  7. package/dist/{assert_throws-4NwKEy2q.mjs → assert_throws-BOkhLGYc.mjs} +1 -1
  8. package/dist/{builder-yAlpiIqP.mjs → builder-Dl5Sdmc9.mjs} +96 -42
  9. package/dist/{chunk-nlSIicah.js → chunk-CRNNMoPX.js} +2 -2
  10. package/dist/circuit-breaker-CSWsyoef.mjs +337 -0
  11. package/dist/{client-z-8dc-e1.d.cts → client-CAM_bQXx.d.cts} +1 -0
  12. package/dist/{client-AtlibPOU.d.ts → client-CSddvgWN.d.ts} +1 -2
  13. package/dist/compat/mod.d.cts +2 -1
  14. package/dist/compat/mod.d.ts +2 -3
  15. package/dist/compat/mod.js +3 -3
  16. package/dist/compat/outgoing-jsonld.test.mjs +4 -4
  17. package/dist/compat/public-audience.test.mjs +4 -4
  18. package/dist/compat/transformers.test.mjs +5 -5
  19. package/dist/{context-BzH2-ajs.d.ts → context-BBVLF7lx.d.cts} +342 -269
  20. package/dist/{context-DJGagtNd.d.cts → context-BU6jSQdo.d.ts} +344 -266
  21. package/dist/{context-Dk_tacqz.mjs → context-DVoTs_wM.mjs} +64 -5
  22. package/dist/{deno-XBVlKnOX.mjs → deno-BID9qTPk.mjs} +1 -1
  23. package/dist/{docloader-DPp-X6gk.mjs → docloader-BYFuVwwi.mjs} +4 -4
  24. package/dist/{esm-DVILvP5e.mjs → esm-vrlUxr60.mjs} +4 -7
  25. package/dist/federation/builder.test.mjs +163 -11
  26. package/dist/federation/circuit-breaker.test.d.mts +2 -0
  27. package/dist/federation/circuit-breaker.test.mjs +446 -0
  28. package/dist/federation/collection.test.mjs +3 -3
  29. package/dist/federation/handler.test.mjs +1770 -29
  30. package/dist/federation/idempotency.test.mjs +5 -5
  31. package/dist/federation/inbox.test.mjs +4 -4
  32. package/dist/federation/keycache.test.mjs +5 -5
  33. package/dist/federation/kv.test.mjs +2 -2
  34. package/dist/federation/metrics.test.d.mts +2 -0
  35. package/dist/federation/metrics.test.mjs +634 -0
  36. package/dist/federation/middleware.test.mjs +4036 -146
  37. package/dist/federation/mod.cjs +195 -14
  38. package/dist/federation/mod.d.cts +6 -4
  39. package/dist/federation/mod.d.ts +6 -6
  40. package/dist/federation/mod.js +192 -14
  41. package/dist/federation/mq.test.mjs +176 -15
  42. package/dist/federation/negotiation.test.mjs +4 -4
  43. package/dist/federation/retry.test.mjs +3 -3
  44. package/dist/federation/router.test.mjs +190 -9
  45. package/dist/federation/send.test.mjs +153 -15
  46. package/dist/federation/temporal.test.d.mts +2 -0
  47. package/dist/federation/temporal.test.mjs +71 -0
  48. package/dist/federation/webfinger.test.mjs +150 -5
  49. package/dist/{http-B4rrzxtg.js → http-B47Jg9iX.js} +994 -64
  50. package/dist/{http-CSlLA54v.mjs → http-CO59u1Yk.mjs} +146 -47
  51. package/dist/{http-BrGqJDXL.cjs → http-CjZwUPiT.cjs} +1099 -61
  52. package/dist/{http-aQzN9Ayi.d.ts → http-VyDTd4G3.d.cts} +15 -3
  53. package/dist/{http-CrGuipxe.d.cts → http-lf8Hsd91.d.ts} +15 -1
  54. package/dist/{key-Bzx--sHD.mjs → key-Dqdlo3mG.mjs} +43 -18
  55. package/dist/{kv-CbLNp3zQ.d.cts → kv-D6hNiMTK.d.ts} +1 -0
  56. package/dist/{kv-cache-DI-Sk7-I.cjs → kv-cache-BQ1-zDKX.cjs} +20 -3
  57. package/dist/{kv-cache-EMIqoIuB.js → kv-cache-Dh2ZH9Gr.js} +20 -3
  58. package/dist/{kv-cache-U__xU4qR.mjs → kv-cache-DhLkLv0P.mjs} +21 -3
  59. package/dist/{kv-GFYnFoOl.d.ts → kv-gJ8LYbxX.d.cts} +1 -3
  60. package/dist/ld-JbCByP5P.mjs +626 -0
  61. package/dist/metrics-ColY3QKj.mjs +815 -0
  62. package/dist/{middleware-zjZ6AFCW.mjs → middleware-Buqzuiaf.mjs} +1 -1
  63. package/dist/{middleware-An4DjES4.cjs → middleware-CWQ3Yi07.cjs} +2318 -623
  64. package/dist/{middleware-K1g6bEkV.mjs → middleware-Cg4EyWsq.mjs} +1639 -373
  65. package/dist/{middleware-nidH7VZH.js → middleware-iAJY2aU-.js} +2260 -556
  66. package/dist/{mod-CR8soWa9.d.ts → mod-B0hW12_O.d.cts} +26 -4
  67. package/dist/mod-C0F6kvgS.d.cts +182 -0
  68. package/dist/{mod-Cr3f-ACa.d.cts → mod-COIAjwRS.d.ts} +26 -2
  69. package/dist/{mod-CMEbIaNh.d.cts → mod-DFvNJcNb.d.ts} +56 -4
  70. package/dist/mod-vPYVoa5n.d.ts +182 -0
  71. package/dist/{mod-CLgIXe9w.d.ts → mod-yvIXFAEi.d.cts} +56 -6
  72. package/dist/mod.cjs +9 -6
  73. package/dist/mod.d.cts +12 -10
  74. package/dist/mod.d.ts +12 -12
  75. package/dist/mod.js +11 -11
  76. package/dist/mq-D-nlpY04.d.ts +208 -0
  77. package/dist/mq-D8uSFzxe.d.cts +208 -0
  78. package/dist/{negotiation-SQvQgUqe.mjs → negotiation-DDstyBvc.mjs} +29 -0
  79. package/dist/nodeinfo/client.test.mjs +4 -4
  80. package/dist/nodeinfo/handler.test.mjs +4 -4
  81. package/dist/nodeinfo/mod.d.cts +2 -1
  82. package/dist/nodeinfo/mod.d.ts +2 -3
  83. package/dist/nodeinfo/mod.js +3 -3
  84. package/dist/nodeinfo/types.test.mjs +3 -3
  85. package/dist/otel/exporter.test.mjs +28 -25
  86. package/dist/otel/mod.cjs +6 -5
  87. package/dist/otel/mod.d.cts +5 -3
  88. package/dist/otel/mod.d.ts +5 -5
  89. package/dist/otel/mod.js +8 -7
  90. package/dist/{outgoing-jsonld-CNmZLixq.mjs → outgoing-jsonld-L_DbOaFe.mjs} +2 -2
  91. package/dist/{owner-C-tqfvxn.mjs → owner-B4LgOmzV.mjs} +2 -2
  92. package/dist/{owner-CptqhsOy.d.cts → owner-CnngXDNJ.d.ts} +2 -1
  93. package/dist/{owner-74ARJ5TL.d.ts → owner-DEvZuyOE.d.cts} +2 -3
  94. package/dist/{proof-BlmRPzrK.mjs → proof-BoSGOq9q.mjs} +26 -8
  95. package/dist/{proof-Bbbwf8_x.js → proof-CzhZuawt.js} +378 -15
  96. package/dist/{proof-BW89QMVB.cjs → proof-DvMI-3vq.cjs} +432 -15
  97. package/dist/runtime/mod.d.cts +1 -0
  98. package/dist/runtime/mod.d.ts +1 -2
  99. package/dist/runtime/mod.js +3 -3
  100. package/dist/{send-05pJLcb6.mjs → send-ByK1Q7DK.mjs} +72 -26
  101. package/dist/sig/http.test.mjs +356 -33
  102. package/dist/sig/key.test.mjs +103 -6
  103. package/dist/sig/ld.test.mjs +696 -7
  104. package/dist/sig/mod.cjs +2 -2
  105. package/dist/sig/mod.d.cts +4 -3
  106. package/dist/sig/mod.d.ts +4 -5
  107. package/dist/sig/mod.js +4 -4
  108. package/dist/sig/owner.test.mjs +6 -6
  109. package/dist/sig/proof.test.mjs +169 -8
  110. package/dist/{std__assert-CRDpx_HF.mjs → std__assert-BBjXFNOb.mjs} +5 -30
  111. package/dist/temporal-LpjyAjKb.mjs +95 -0
  112. package/dist/testing/mod.d.mts +110 -10
  113. package/dist/testing/mod.mjs +1 -2
  114. package/dist/{transformers-ve6e2xcg.js → transformers-BGMIq1cs.js} +2 -2
  115. package/dist/{types-hvL8ElAs.js → types-CAY3OdLq.js} +2 -2
  116. package/dist/utils/docloader.test.mjs +6 -6
  117. package/dist/utils/kv-cache.test.mjs +67 -2
  118. package/dist/utils/mod.cjs +1 -1
  119. package/dist/utils/mod.d.cts +2 -1
  120. package/dist/utils/mod.d.ts +2 -3
  121. package/dist/utils/mod.js +3 -3
  122. package/dist/vocab/cjs.test.mjs +1 -1
  123. package/dist/vocab/mod.d.cts +1 -0
  124. package/dist/vocab/mod.d.ts +1 -2
  125. package/dist/vocab/mod.js +2 -2
  126. package/package.json +16 -16
  127. package/dist/ld-DR78beiv.mjs +0 -279
  128. package/dist/middleware-BFBaR0mF.cjs +0 -4
  129. package/dist/mod-2d12ffz3.d.ts +0 -64
  130. package/dist/mod-D35TRn09.d.cts +0 -62
  131. package/dist/router-CrMLXoOr.mjs +0 -114
  132. package/dist/{activity-listener-ell7W1s9.mjs → activity-listener-tztVvlNb.mjs} +0 -0
  133. package/dist/{assert_equals-Ew3jOFa3.mjs → assert_equals-C-ZRDbaf.mjs} +0 -0
  134. package/dist/{client-D_1QpnWt.mjs → client-ByXmQhYD.mjs} +1 -1
  135. package/dist/{collection-D-HqUuA2.mjs → collection-Cc3DVAhE.mjs} +0 -0
  136. package/dist/{keycache-EGATflN-.mjs → keycache-BeU0LCII.mjs} +0 -0
  137. package/dist/{public-audience-DYFHzm_c.mjs → public-audience-Cvbr2Gzt.mjs} +1 -1
  138. /package/dist/{retry-bMXBL97A.mjs → retry-CXg_MBI-.mjs} +0 -0
@@ -1,14 +1,72 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as RouterError } from "./router-CrMLXoOr.mjs";
5
- import { mockDocumentLoader } from "@fedify/fixture";
4
+ import { getLogger } from "@logtape/logtape";
5
+ import "@opentelemetry/sdk-trace-base";
6
+ import "@opentelemetry/core";
7
+ import { RouterError } from "@fedify/uri-template";
6
8
  import { lookupObject, traverseCollection } from "@fedify/vocab";
7
9
  import { lookupWebFinger } from "@fedify/webfinger";
8
- import { trace } from "@opentelemetry/api";
10
+ import { metrics, trace } from "@opentelemetry/api";
11
+ //#region ../fixture/dist/mod.js
12
+ const logger = getLogger(["fixture", "docloader"]);
13
+ /**
14
+ * A mock of the document loader. This does not make any actual HTTP requests
15
+ * towards the remote server, but looks up the local fixture files instead.
16
+ *
17
+ * For instance, `mockDocumentLoader("http://example.com/foo/bar")` will look up
18
+ * the file `testing/fixtures/http/example.com/foo/bar` (no suffix) and return
19
+ * its content as the response.
20
+ */
21
+ async function mockDocumentLoader(resource, _options) {
22
+ const url = new URL(resource);
23
+ if ("navigator" in globalThis && navigator.userAgent === "Cloudflare-Workers") {
24
+ const testUrl = new URL(url);
25
+ testUrl.hostname += ".test";
26
+ const resp = await fetch(testUrl);
27
+ if (resp.ok) {
28
+ const document = await resp.json();
29
+ logger.debug("Successfully fetched fixture {resource}: {status} {statusText}\n{body}", {
30
+ resource,
31
+ status: resp.status,
32
+ statusText: resp.statusText,
33
+ body: document
34
+ });
35
+ return {
36
+ contextUrl: null,
37
+ document,
38
+ documentUrl: resource
39
+ };
40
+ }
41
+ const error = await resp.text();
42
+ logger.error("Failed to fetch fixture {resource}: {error}", {
43
+ resource,
44
+ error
45
+ });
46
+ throw new Error(error);
47
+ }
48
+ const fixturePath = `${"Deno" in globalThis ? "." : "@fedify/fixture"}/fixtures/${url.host}${url.pathname}.json`;
49
+ let document;
50
+ try {
51
+ document = (await import(fixturePath, { with: { type: "json" } })).default;
52
+ } catch (error) {
53
+ logger.error("Failed to read fixture file {path}: {error}", {
54
+ path: fixturePath,
55
+ error
56
+ });
57
+ throw error;
58
+ }
59
+ return {
60
+ contextUrl: null,
61
+ document,
62
+ documentUrl: resource
63
+ };
64
+ }
65
+ const testDefinitions = [];
66
+ //#endregion
9
67
  //#region src/testing/context.ts
10
68
  function createContext(values) {
11
- const { federation, url = new URL("http://example.com/"), canonicalOrigin, data, documentLoader, contextLoader, tracerProvider, clone, getNodeInfoUri, getActorUri, getObjectUri, getCollectionUri, getOutboxUri, getInboxUri, getFollowingUri, getFollowersUri, getLikedUri, getFeaturedUri, getFeaturedTagsUri, parseUri, getActorKeyPairs, getDocumentLoader, lookupObject: lookupObject$1, traverseCollection: traverseCollection$1, lookupNodeInfo, lookupWebFinger: lookupWebFinger$1, sendActivity, routeActivity } = values;
69
+ const { federation, url = new URL("http://example.com/"), canonicalOrigin, data, documentLoader, contextLoader, meterProvider, tracerProvider, clone, getNodeInfoUri, getActorUri, getObjectUri, getCollectionUri, getOutboxUri, getInboxUri, getFollowingUri, getFollowersUri, getLikedUri, getFeaturedUri, getFeaturedTagsUri, parseUri, getActorKeyPairs, getDocumentLoader, lookupObject: lookupObject$1, traverseCollection: traverseCollection$1, lookupNodeInfo, lookupWebFinger: lookupWebFinger$1, sendActivity, routeActivity } = values;
12
70
  function throwRouterError() {
13
71
  throw new RouterError("Not implemented");
14
72
  }
@@ -21,6 +79,7 @@ function createContext(values) {
21
79
  hostname: url.hostname,
22
80
  documentLoader: documentLoader ?? mockDocumentLoader,
23
81
  contextLoader: contextLoader ?? mockDocumentLoader,
82
+ meterProvider: meterProvider ?? metrics.getMeterProvider(),
24
83
  tracerProvider: tracerProvider ?? trace.getTracerProvider(),
25
84
  clone: clone ?? ((data) => createContext({
26
85
  ...values,
@@ -117,4 +176,4 @@ function createOutboxContext(args) {
117
176
  };
118
177
  }
119
178
  //#endregion
120
- export { createOutboxContext as n, createRequestContext as r, createInboxContext as t };
179
+ export { testDefinitions as i, createOutboxContext as n, createRequestContext as r, createInboxContext as t };
@@ -3,6 +3,6 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  //#region deno.json
5
5
  var name = "@fedify/fedify";
6
- var version = "2.3.0-dev.994+9071ca0a";
6
+ var version = "2.3.0-pr.809.37+0574ba5c";
7
7
  //#endregion
8
8
  export { version as n, name as t };
@@ -1,11 +1,11 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { o as validateCryptoKey } from "./key-Bzx--sHD.mjs";
5
- import { n as doubleKnock } from "./http-CSlLA54v.mjs";
6
- import { curry } from "es-toolkit";
7
- import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, validatePublicUrl } from "@fedify/vocab-runtime";
4
+ import { o as validateCryptoKey } from "./key-Dqdlo3mG.mjs";
5
+ import { n as doubleKnock } from "./http-CO59u1Yk.mjs";
8
6
  import { getLogger } from "@logtape/logtape";
7
+ import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, validatePublicUrl } from "@fedify/vocab-runtime";
8
+ import { curry } from "es-toolkit";
9
9
  //#region src/utils/docloader.ts
10
10
  const logger = getLogger([
11
11
  "fedify",
@@ -9,7 +9,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
9
  var __getOwnPropNames = Object.getOwnPropertyNames;
10
10
  var __getProtoOf = Object.getPrototypeOf;
11
11
  var __hasOwnProp = Object.prototype.hasOwnProperty;
12
- var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
12
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
13
13
  var __copyProps = (to, from, except, desc) => {
14
14
  if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
15
15
  key = keys[i];
@@ -25,8 +25,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
  enumerable: true
26
26
  }) : target, mod));
27
27
  //#endregion
28
- //#region ../../node_modules/.pnpm/glob-to-regexp@0.4.1/node_modules/glob-to-regexp/index.js
29
- var require_glob_to_regexp = /* @__PURE__ */ __commonJSMin(((exports, module) => {
28
+ //#region ../../node_modules/.pnpm/regexparam@3.0.0/node_modules/regexparam/dist/index.mjs
29
+ var import_glob_to_regexp = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
30
30
  module.exports = function(glob, opts) {
31
31
  if (typeof glob !== "string") throw new TypeError("Expected a string");
32
32
  var str = String(glob);
@@ -97,9 +97,7 @@ var require_glob_to_regexp = /* @__PURE__ */ __commonJSMin(((exports, module) =>
97
97
  if (!flags || !~flags.indexOf("g")) reStr = "^" + reStr + "$";
98
98
  return new RegExp(reStr, flags);
99
99
  };
100
- }));
101
- //#endregion
102
- //#region ../../node_modules/.pnpm/regexparam@3.0.0/node_modules/regexparam/dist/index.mjs
100
+ })))(), 1);
103
101
  /**
104
102
  * @param {string|RegExp} input The route pattern
105
103
  * @param {boolean} [loose] Allow open-ended matching. Ignored with `RegExp` input.
@@ -454,7 +452,6 @@ function normalizeHeaders(headers) {
454
452
  }
455
453
  //#endregion
456
454
  //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/Matchers.js
457
- var import_glob_to_regexp = /* @__PURE__ */ __toESM(require_glob_to_regexp(), 1);
458
455
  const isUrlMatcher = (matcher) => matcher instanceof RegExp || typeof matcher === "string" || typeof matcher === "object" && "href" in matcher;
459
456
  const isFunctionMatcher = (matcher) => typeof matcher === "function";
460
457
  const stringMatchers = {
@@ -1,14 +1,14 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as RouterError } from "../router-CrMLXoOr.mjs";
5
- import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
- import { i as assertExists } from "../std__assert-CRDpx_HF.mjs";
7
- import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
4
+ import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
+ import { r as assertExists } from "../std__assert-BBjXFNOb.mjs";
6
+ import { t as assertThrows } from "../assert_throws-BOkhLGYc.mjs";
8
7
  import { t as MemoryKvStore } from "../kv-rV3vodCc.mjs";
9
- import { n as createFederationBuilder } from "../builder-yAlpiIqP.mjs";
10
- import { test } from "@fedify/fixture";
8
+ import { r as createFederationBuilder } from "../builder-Dl5Sdmc9.mjs";
9
+ import { DisallowedOperatorError, DisallowedVarSpecModifierError, DuplicateRouteVariableError, RouteTemplateOptionsNotMatchedError, RouterError } from "@fedify/uri-template";
11
10
  import { Activity, Note, Person } from "@fedify/vocab";
11
+ import { test } from "@fedify/fixture";
12
12
  //#region src/federation/builder.test.ts
13
13
  test("FederationBuilder", async (t) => {
14
14
  await t.step("should build a Federation object with registered components", async () => {
@@ -17,7 +17,11 @@ test("FederationBuilder", async (t) => {
17
17
  const actorDispatcher = (_ctx, _identifier) => {
18
18
  return null;
19
19
  };
20
- builder.setActorDispatcher("/users/{identifier}", actorDispatcher);
20
+ assertThrows(() => createFederationBuilder().setActorDispatcher("/users/{identifier}", actorDispatcher).mapActorAlias("/actor/{id}", "instance"), RouterError, "Path for actor alias must have no variables.");
21
+ assertThrows(() => createFederationBuilder().setActorDispatcher("/users/{identifier}", actorDispatcher).mapActorAlias("/actor", "instance").mapActorAlias("/bot", "instance"), RouterError, "Actor alias for \"instance\" already set.");
22
+ assertThrows(() => createFederationBuilder().setActorDispatcher("/users/{identifier}", actorDispatcher).mapActorAlias("/actor", "instance").mapActorAlias("/actor", "bot"), RouterError, "Actor alias path \"/actor\" conflicts with existing route \"actorAlias:instance\".");
23
+ assertThrows(() => createFederationBuilder().setActorDispatcher("/users/{identifier}", actorDispatcher).mapActorAlias("/actor", ""), RouterError, "Identifier cannot be empty.");
24
+ builder.setActorDispatcher("/users/{identifier}", actorDispatcher).mapActorAlias("/actor", "instance");
21
25
  const inboxListener = (_ctx, _activity) => {};
22
26
  builder.setInboxListeners("/users/{identifier}/inbox").on(Activity, inboxListener);
23
27
  const outboxListener = (_ctx, _activity) => {};
@@ -52,6 +56,7 @@ test("FederationBuilder", async (t) => {
52
56
  const impl = federation;
53
57
  assertEquals(impl.router.route("/.well-known/webfinger")?.name, "webfinger");
54
58
  assertEquals(impl.router.route("/users/test123")?.name, "actor");
59
+ assertEquals(impl.router.route("/actor")?.name, "actorAlias:instance");
55
60
  assertEquals(impl.router.route("/users/test123/inbox")?.name, "inbox");
56
61
  assertEquals(impl.router.route("/users/test123/outbox")?.name, "outbox");
57
62
  assertEquals(impl.router.route("/notes/456")?.name, `object:${Note.typeId.href}`);
@@ -69,6 +74,40 @@ test("FederationBuilder", async (t) => {
69
74
  assertEquals(impl.router.build("inbox", { identifier: "user1" }), "/users/user1/inbox");
70
75
  await builder.build({ kv });
71
76
  });
77
+ await t.step("passes benchmarkMode to the built federation", async () => {
78
+ const builder = createFederationBuilder();
79
+ const impl = await builder.build({
80
+ kv: new MemoryKvStore(),
81
+ benchmarkMode: true
82
+ });
83
+ assertEquals(impl.benchmarkMode, true);
84
+ assertEquals(impl.allowPrivateAddress, true);
85
+ assertEquals(impl.signatureTimeWindow, false);
86
+ const overriddenImpl = await builder.build({
87
+ kv: new MemoryKvStore(),
88
+ benchmarkMode: true,
89
+ allowPrivateAddress: false,
90
+ signatureTimeWindow: { minutes: 10 }
91
+ });
92
+ assertEquals(overriddenImpl.benchmarkMode, true);
93
+ assertEquals(overriddenImpl.allowPrivateAddress, false);
94
+ assertEquals(overriddenImpl.signatureTimeWindow, { minutes: 10 });
95
+ });
96
+ await t.step("should snapshot router state on build", async () => {
97
+ const builder = createFederationBuilder();
98
+ const kv = new MemoryKvStore();
99
+ const noteRouteName = `object:${Note.typeId.href}`;
100
+ builder.setActorDispatcher("/users/{identifier}", () => null);
101
+ const impl1 = await builder.build({ kv });
102
+ builder.setObjectDispatcher(Note, "/notes/{id}", () => null);
103
+ assertEquals(impl1.router.route("/notes/1"), null);
104
+ const impl2 = await builder.build({ kv });
105
+ assertEquals(impl2.router.route("/notes/1")?.name, noteRouteName);
106
+ impl1.router.add("/leaked/{id}", "leaked");
107
+ assertEquals(impl1.router.route("/leaked/1")?.name, "leaked");
108
+ assertEquals(impl2.router.route("/leaked/1"), null);
109
+ assertEquals((await builder.build({ kv })).router.route("/leaked/1"), null);
110
+ });
72
111
  await t.step("should build with default options", async () => {
73
112
  const builder = createFederationBuilder();
74
113
  const kv = new MemoryKvStore();
@@ -84,15 +123,128 @@ test("FederationBuilder", async (t) => {
84
123
  assertThrows(() => builder.setOutboxListeners("/users/{identifier}/outbox/{extra}"), RouterError);
85
124
  assertThrows(() => builder.setOutboxListeners("/users/{identifier}/outbox/{identifier}"), RouterError);
86
125
  const builderAfterInvalid = createFederationBuilder();
87
- assertThrows(() => builderAfterInvalid.setOutboxListeners("/users/{identifier}/outbox/{extra}"), RouterError);
126
+ assertThrows(() => builderAfterInvalid.setOutboxListeners("/users/{identifier}/outbox/{extra}"), RouteTemplateOptionsNotMatchedError);
127
+ assertThrows(() => builderAfterInvalid.setOutboxListeners("/users/{identifier:3}/outbox"), DisallowedVarSpecModifierError);
128
+ assertThrows(() => builderAfterInvalid.setOutboxListeners("/users/{identifier*}/outbox"), DisallowedVarSpecModifierError);
129
+ assertThrows(() => builderAfterInvalid.setOutboxListeners("/users/{identifier,identifier}/outbox"), DuplicateRouteVariableError);
88
130
  builderAfterInvalid.setOutboxListeners("/users/{identifier}/outbox");
89
131
  const builder2 = createFederationBuilder();
90
- builder2.setOutboxListeners("/users{/identifier}/outbox");
132
+ builder2.setOutboxListeners("/users/{identifier}/outbox");
91
133
  assertThrows(() => builder2.setOutboxDispatcher("/actors/{identifier}/outbox", () => ({ items: [] })), RouterError);
92
134
  const builder3 = createFederationBuilder();
93
- assertThrows(() => builder3.setOutboxListeners("/users{?identifier}/outbox"), RouterError);
135
+ assertThrows(() => builder3.setOutboxListeners("/users{?identifier}/outbox"), DisallowedOperatorError);
136
+ const builder3a = createFederationBuilder();
137
+ assertThrows(() => builder3a.setOutboxListeners("/users{;identifier}/outbox"), DisallowedOperatorError);
138
+ const builder3b = createFederationBuilder();
139
+ assertThrows(() => builder3b.setOutboxListeners("/users{.identifier}/outbox"), DisallowedOperatorError);
94
140
  const builder4 = createFederationBuilder();
95
- assertThrows(() => builder4.setOutboxDispatcher("/users{?identifier}/outbox", () => ({ items: [] })), RouterError);
141
+ assertThrows(() => builder4.setOutboxDispatcher("/users{?identifier}/outbox", () => ({ items: [] })), DisallowedOperatorError);
142
+ const builder5 = createFederationBuilder();
143
+ assertThrows(() => builder5.setOutboxDispatcher("/users/{identifier:3}/outbox", () => ({ items: [] })), DisallowedVarSpecModifierError);
144
+ });
145
+ await t.step("rejects non-segment-boundary identifier operators at registration for required-identifier routes", () => {
146
+ assertThrows(() => createFederationBuilder().setActorDispatcher("/users{/identifier}", () => null), DisallowedOperatorError);
147
+ assertThrows(() => createFederationBuilder().setActorDispatcher("{/identifier}", () => null), DisallowedOperatorError);
148
+ assertThrows(() => createFederationBuilder().setActorDispatcher("/users/{/identifier}", () => null), DisallowedOperatorError);
149
+ assertThrows(() => createFederationBuilder().setActorDispatcher("/users{?identifier}", () => null), DisallowedOperatorError);
150
+ assertThrows(() => createFederationBuilder().setInboxListeners("/users{/identifier}/inbox"), DisallowedOperatorError);
151
+ assertThrows(() => createFederationBuilder().setOutboxListeners("/users{/identifier}/outbox"), DisallowedOperatorError);
152
+ assertThrows(() => createFederationBuilder().setActorDispatcher("/users/{identifier:3}", () => null), DisallowedVarSpecModifierError);
153
+ assertThrows(() => createFederationBuilder().setActorDispatcher("/users/{identifier*}", () => null), DisallowedVarSpecModifierError);
154
+ createFederationBuilder().setActorDispatcher("/users/{identifier}", () => null);
155
+ createFederationBuilder().setInboxListeners("/users/{identifier}/inbox");
156
+ });
157
+ await t.step("every required-identifier setter rejects every omissible operator", () => {
158
+ const omissibleExprs = [
159
+ "{/identifier}",
160
+ "{?identifier}",
161
+ "{;identifier}",
162
+ "{.identifier}"
163
+ ];
164
+ const registrars = [
165
+ ["setActorDispatcher", (expr) => createFederationBuilder().setActorDispatcher(`/users${expr}`, () => null)],
166
+ ["setInboxListeners", (expr) => createFederationBuilder().setInboxListeners(`/users${expr}/inbox`)],
167
+ ["setOutboxListeners", (expr) => createFederationBuilder().setOutboxListeners(`/users${expr}/outbox`)],
168
+ ["setOutboxDispatcher", (expr) => createFederationBuilder().setOutboxDispatcher(`/users${expr}/outbox`, () => ({ items: [] }))],
169
+ ["setFollowingDispatcher", (expr) => createFederationBuilder().setFollowingDispatcher(`/users${expr}/following`, () => ({ items: [] }))],
170
+ ["setFollowersDispatcher", (expr) => createFederationBuilder().setFollowersDispatcher(`/users${expr}/followers`, () => ({ items: [] }))],
171
+ ["setLikedDispatcher", (expr) => createFederationBuilder().setLikedDispatcher(`/users${expr}/liked`, () => ({ items: [] }))],
172
+ ["setFeaturedDispatcher", (expr) => createFederationBuilder().setFeaturedDispatcher(`/users${expr}/featured`, () => ({ items: [] }))],
173
+ ["setFeaturedTagsDispatcher", (expr) => createFederationBuilder().setFeaturedTagsDispatcher(`/users${expr}/tags`, () => ({ items: [] }))]
174
+ ];
175
+ for (const [name, register] of registrars) {
176
+ for (const expr of omissibleExprs) assertThrows(() => register(expr), DisallowedOperatorError, void 0, `${name} must reject ${expr} at registration`);
177
+ register("{identifier}");
178
+ }
179
+ });
180
+ await t.step("empty or missing identifier segments produce a runtime no-match", async () => {
181
+ const kv = new MemoryKvStore();
182
+ const builder = createFederationBuilder();
183
+ builder.setActorDispatcher("/users/{identifier}", () => null);
184
+ builder.setInboxListeners("/users/{identifier}/inbox");
185
+ builder.setOutboxDispatcher("/users/{identifier}/outbox", () => ({ items: [] }));
186
+ builder.setObjectDispatcher(Note, "/notes/{id}", () => null);
187
+ const impl = await builder.build({ kv });
188
+ assertEquals(impl.router.route("/users/alice")?.name, "actor");
189
+ assertEquals(impl.router.route("/users/alice/inbox")?.name, "inbox");
190
+ assertEquals(impl.router.route("/users/alice/outbox")?.name, "outbox");
191
+ assertEquals(impl.router.route("/notes/1")?.name, `object:${Note.typeId.href}`);
192
+ assertEquals(impl.router.route("/users/"), null);
193
+ assertEquals(impl.router.route("/users//inbox"), null);
194
+ assertEquals(impl.router.route("/users//outbox"), null);
195
+ assertEquals(impl.router.route("/notes/"), null);
196
+ });
197
+ await t.step("object dispatcher optional-operator routes no-match when unbound", async () => {
198
+ const kv = new MemoryKvStore();
199
+ const objectName = `object:${Note.typeId.href}`;
200
+ const query = createFederationBuilder();
201
+ query.setObjectDispatcher(Note, "/notes{?id}", () => null);
202
+ const queryImpl = await query.build({ kv });
203
+ assertEquals(queryImpl.router.route("/notes"), null);
204
+ assertEquals(queryImpl.router.route("/notes?id=1")?.name, objectName);
205
+ const matrix = createFederationBuilder();
206
+ matrix.setObjectDispatcher(Note, "/notes{;id}", () => null);
207
+ const matrixImpl = await matrix.build({ kv });
208
+ assertEquals(matrixImpl.router.route("/notes"), null);
209
+ assertEquals(matrixImpl.router.route("/notes;id=1")?.name, objectName);
210
+ const label = createFederationBuilder();
211
+ label.setObjectDispatcher(Note, "/notes{.id}", () => null);
212
+ const labelImpl = await label.build({ kv });
213
+ assertEquals(labelImpl.router.route("/notes"), null);
214
+ assertEquals(labelImpl.router.route("/notes.1")?.name, objectName);
215
+ });
216
+ await t.step("custom collection routes no-match empty or unbound variables", async () => {
217
+ const kv = new MemoryKvStore();
218
+ const builder = createFederationBuilder();
219
+ builder.setCollectionDispatcher("samples", Note, "/groups/{id}", () => ({ items: [] }));
220
+ builder.setCollectionDispatcher("optionals", Note, "/optional-groups{?id}", () => ({ items: [] }));
221
+ builder.setCollectionDispatcher("matrixOptionals", Note, "/matrix-groups{;id}", () => ({ items: [] }));
222
+ builder.setCollectionDispatcher("labelOptionals", Note, "/label-groups{.id}", () => ({ items: [] }));
223
+ const impl = await builder.build({ kv });
224
+ assertEquals(impl.router.route("/groups/1"), {
225
+ name: "collection:samples",
226
+ values: { id: "1" },
227
+ template: "/groups/{id}"
228
+ });
229
+ assertEquals(impl.router.route("/optional-groups?id=1"), {
230
+ name: "collection:optionals",
231
+ values: { id: "1" },
232
+ template: "/optional-groups{?id}"
233
+ });
234
+ assertEquals(impl.router.route("/matrix-groups;id=1"), {
235
+ name: "collection:matrixOptionals",
236
+ values: { id: "1" },
237
+ template: "/matrix-groups{;id}"
238
+ });
239
+ assertEquals(impl.router.route("/label-groups.1"), {
240
+ name: "collection:labelOptionals",
241
+ values: { id: "1" },
242
+ template: "/label-groups{.id}"
243
+ });
244
+ assertEquals(impl.router.route("/groups/"), null);
245
+ assertEquals(impl.router.route("/optional-groups"), null);
246
+ assertEquals(impl.router.route("/matrix-groups"), null);
247
+ assertEquals(impl.router.route("/label-groups"), null);
96
248
  });
97
249
  await t.step("should pass build options correctly", async () => {
98
250
  const builder = createFederationBuilder();
@@ -0,0 +1,2 @@
1
+ import { Temporal } from "@js-temporal/polyfill";
2
+ import { URLPattern } from "urlpattern-polyfill";