@fedify/vocab-runtime 2.4.0-dev.2063 → 2.4.0-dev.2071

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 (31) hide show
  1. package/deno.json +1 -1
  2. package/dist/internal/portable-dereference.cjs +193 -21
  3. package/dist/internal/portable-dereference.d.cts +155 -15
  4. package/dist/internal/portable-dereference.d.ts +155 -15
  5. package/dist/internal/portable-dereference.js +187 -22
  6. package/dist/mod.cjs +1 -1
  7. package/dist/mod.d.cts +1 -1
  8. package/dist/mod.d.ts +1 -1
  9. package/dist/mod.js +1 -1
  10. package/dist/{portable--NcyA1R_.d.ts → portable-DH3947GK.d.ts} +6 -2
  11. package/dist/{portable-BykOD45s.d.cts → portable-comBI05Y.d.cts} +6 -2
  12. package/dist/tests/{body-CTGqQhNI.cjs → body-BA5q10mr.cjs} +1 -1
  13. package/dist/tests/{body-BN5x-zDQ.mjs → body-XAE-ooHH.mjs} +1 -1
  14. package/dist/tests/body.test.cjs +1 -1
  15. package/dist/tests/body.test.mjs +1 -1
  16. package/dist/tests/decimal.test.cjs +1 -1
  17. package/dist/tests/decimal.test.mjs +1 -1
  18. package/dist/tests/{docloader-BFxwbqMx.mjs → docloader-CKZdC6iZ.mjs} +2 -2
  19. package/dist/tests/{docloader-JGSpG-3l.cjs → docloader-D6BFa7gx.cjs} +2 -2
  20. package/dist/tests/docloader.test.cjs +2 -2
  21. package/dist/tests/docloader.test.mjs +2 -2
  22. package/dist/tests/internal/portable-dereference.test.cjs +87 -0
  23. package/dist/tests/internal/portable-dereference.test.mjs +87 -0
  24. package/dist/tests/{request-BT9FBhk6.mjs → request-DXvwmbWU.mjs} +1 -1
  25. package/dist/tests/{request-jWhPNG9V.cjs → request-D_TFIuTp.cjs} +1 -1
  26. package/dist/tests/request.test.cjs +1 -1
  27. package/dist/tests/request.test.mjs +1 -1
  28. package/package.json +1 -1
  29. package/src/internal/portable-dereference.test.ts +32 -0
  30. package/src/internal/portable-dereference.ts +288 -28
  31. package/src/portable.ts +6 -2
@@ -1,7 +1,7 @@
1
- import { t as FetchError } from "./request-BT9FBhk6.mjs";
1
+ import { t as FetchError } from "./request-DXvwmbWU.mjs";
2
2
  import { n as cid_v1_default, t as preloadedContexts } from "./contexts-BBX_qhBL.mjs";
3
3
  import { t as UrlError } from "./url-DgJM9WJn.mjs";
4
- import { n as getRemoteDocument, t as getDocumentLoader } from "./docloader-BFxwbqMx.mjs";
4
+ import { n as getRemoteDocument, t as getDocumentLoader } from "./docloader-CKZdC6iZ.mjs";
5
5
  import { test } from "node:test";
6
6
  import { gzipSync } from "node:zlib";
7
7
  import { deepStrictEqual, ok, rejects } from "node:assert";
@@ -1,3 +1,4 @@
1
+ const require_contexts = require("../contexts-Ch3v2Cgu.cjs");
1
2
  const require_url = require("../url-BqlldzT8.cjs");
2
3
  require("../jsonld-CmDhHNTx.cjs");
3
4
  let node_assert_strict = require("node:assert/strict");
@@ -17,6 +18,12 @@ const logger = (0, _logtape_logtape.getLogger)([
17
18
  */
18
19
  const MAX_GATEWAY_HINTS = 5;
19
20
  const LOCATION_HINT_PARAMETER = "@gateway";
21
+ const BASELINE_CONTEXT_URLS = /* @__PURE__ */ new Set([
22
+ "https://w3id.org/identity/v1",
23
+ "https://www.w3.org/ns/activitystreams",
24
+ "https://w3id.org/security/v1",
25
+ "https://w3id.org/security/data-integrity/v1"
26
+ ]);
20
27
  /**
21
28
  * Checks whether a URL is an FEP-ef61 portable ActivityPub IRI.
22
29
  *
@@ -76,6 +83,60 @@ function parseGatewayOrigin(gateway) {
76
83
  if (url.protocol !== "http:" && url.protocol !== "https:" || url.href !== `${url.origin}/`) return null;
77
84
  return url;
78
85
  }
86
+ /**
87
+ * Creates a context loader that returns the same context documents for the
88
+ * whole dereference operation, so that the identity check, the proof
89
+ * verifier, and the parser interpret the fetched document identically even
90
+ * if the underlying loader is nondeterministic. Failed loads are not
91
+ * remembered, so a transient failure does not affect the next gateway.
92
+ *
93
+ * The parsed object keeps the loader for its own later dereferences, so
94
+ * `release()` turns it into a plain pass-through to the underlying loader
95
+ * once the operation is over.
96
+ *
97
+ * @internal Technically exported for generated vocabulary classes, but not
98
+ * part of the public API contract. This is not considered public API for
99
+ * Semantic Versioning decisions.
100
+ */
101
+ function createSnapshotContextLoader(contextLoader) {
102
+ for (let wrapped = snapshotLoaders.get(contextLoader); wrapped?.released; wrapped = snapshotLoaders.get(contextLoader)) contextLoader = wrapped.base;
103
+ const cache = /* @__PURE__ */ new Map();
104
+ let released = false;
105
+ const release = () => {
106
+ released = true;
107
+ state.released = true;
108
+ cache.clear();
109
+ };
110
+ const loader = async (url, options) => {
111
+ if (released) return await contextLoader(url, options);
112
+ const key = URL.canParse(url) ? new URL(url).href : url;
113
+ if (BASELINE_CONTEXT_URLS.has(key)) return {
114
+ contextUrl: null,
115
+ document: structuredClone(require_contexts.preloadedContexts[key]),
116
+ documentUrl: key
117
+ };
118
+ let promise = cache.get(key);
119
+ if (promise == null) {
120
+ const loading = contextLoader(url, options).then((document) => structuredClone(document));
121
+ promise = loading;
122
+ cache.set(key, loading);
123
+ loading.catch(() => {
124
+ if (cache.get(key) === loading) cache.delete(key);
125
+ });
126
+ }
127
+ return structuredClone(await promise);
128
+ };
129
+ const state = {
130
+ base: contextLoader,
131
+ released: false
132
+ };
133
+ snapshotLoaders.set(loader, state);
134
+ return {
135
+ loader,
136
+ release
137
+ };
138
+ }
139
+ const snapshotLoaders = /* @__PURE__ */ new WeakMap();
79
140
  //#endregion
80
141
  //#region src/internal/portable-dereference.test.ts
81
142
  const hrefs = (urls) => urls.map((url) => url.href);
@@ -120,4 +181,30 @@ const hrefs = (urls) => urls.map((url) => url.href);
120
181
  4
121
182
  ].map((i) => `https://g${i}.example/`));
122
183
  });
184
+ (0, node_test.test)("createSnapshotContextLoader() does not nest released snapshots", async () => {
185
+ const calls = [];
186
+ const base = (url) => {
187
+ calls.push(url);
188
+ return Promise.resolve({
189
+ contextUrl: null,
190
+ documentUrl: url,
191
+ document: { "@context": {} }
192
+ });
193
+ };
194
+ let loader = base;
195
+ for (let i = 0; i < 1e4; i++) {
196
+ const snapshot = createSnapshotContextLoader(loader);
197
+ snapshot.release();
198
+ loader = snapshot.loader;
199
+ }
200
+ await loader("https://example.com/context");
201
+ (0, node_assert_strict.deepStrictEqual)(calls, ["https://example.com/context"]);
202
+ const outer = createSnapshotContextLoader(base);
203
+ await outer.loader("https://example.com/context");
204
+ const inner = createSnapshotContextLoader(outer.loader);
205
+ await inner.loader("https://example.com/context");
206
+ (0, node_assert_strict.deepStrictEqual)(calls.length, 2);
207
+ inner.release();
208
+ outer.release();
209
+ });
123
210
  //#endregion
@@ -1,3 +1,4 @@
1
+ import { t as preloadedContexts } from "../contexts-BBX_qhBL.mjs";
1
2
  import { a as formatIri, m as parseIri } from "../url-DgJM9WJn.mjs";
2
3
  import "../jsonld-DweIJ07b.mjs";
3
4
  import { deepStrictEqual, ok, throws } from "node:assert/strict";
@@ -17,6 +18,12 @@ const logger = getLogger([
17
18
  */
18
19
  const MAX_GATEWAY_HINTS = 5;
19
20
  const LOCATION_HINT_PARAMETER = "@gateway";
21
+ const BASELINE_CONTEXT_URLS = /* @__PURE__ */ new Set([
22
+ "https://w3id.org/identity/v1",
23
+ "https://www.w3.org/ns/activitystreams",
24
+ "https://w3id.org/security/v1",
25
+ "https://w3id.org/security/data-integrity/v1"
26
+ ]);
20
27
  /**
21
28
  * Checks whether a URL is an FEP-ef61 portable ActivityPub IRI.
22
29
  *
@@ -76,6 +83,60 @@ function parseGatewayOrigin(gateway) {
76
83
  if (url.protocol !== "http:" && url.protocol !== "https:" || url.href !== `${url.origin}/`) return null;
77
84
  return url;
78
85
  }
86
+ /**
87
+ * Creates a context loader that returns the same context documents for the
88
+ * whole dereference operation, so that the identity check, the proof
89
+ * verifier, and the parser interpret the fetched document identically even
90
+ * if the underlying loader is nondeterministic. Failed loads are not
91
+ * remembered, so a transient failure does not affect the next gateway.
92
+ *
93
+ * The parsed object keeps the loader for its own later dereferences, so
94
+ * `release()` turns it into a plain pass-through to the underlying loader
95
+ * once the operation is over.
96
+ *
97
+ * @internal Technically exported for generated vocabulary classes, but not
98
+ * part of the public API contract. This is not considered public API for
99
+ * Semantic Versioning decisions.
100
+ */
101
+ function createSnapshotContextLoader(contextLoader) {
102
+ for (let wrapped = snapshotLoaders.get(contextLoader); wrapped?.released; wrapped = snapshotLoaders.get(contextLoader)) contextLoader = wrapped.base;
103
+ const cache = /* @__PURE__ */ new Map();
104
+ let released = false;
105
+ const release = () => {
106
+ released = true;
107
+ state.released = true;
108
+ cache.clear();
109
+ };
110
+ const loader = async (url, options) => {
111
+ if (released) return await contextLoader(url, options);
112
+ const key = URL.canParse(url) ? new URL(url).href : url;
113
+ if (BASELINE_CONTEXT_URLS.has(key)) return {
114
+ contextUrl: null,
115
+ document: structuredClone(preloadedContexts[key]),
116
+ documentUrl: key
117
+ };
118
+ let promise = cache.get(key);
119
+ if (promise == null) {
120
+ const loading = contextLoader(url, options).then((document) => structuredClone(document));
121
+ promise = loading;
122
+ cache.set(key, loading);
123
+ loading.catch(() => {
124
+ if (cache.get(key) === loading) cache.delete(key);
125
+ });
126
+ }
127
+ return structuredClone(await promise);
128
+ };
129
+ const state = {
130
+ base: contextLoader,
131
+ released: false
132
+ };
133
+ snapshotLoaders.set(loader, state);
134
+ return {
135
+ loader,
136
+ release
137
+ };
138
+ }
139
+ const snapshotLoaders = /* @__PURE__ */ new WeakMap();
79
140
  //#endregion
80
141
  //#region src/internal/portable-dereference.test.ts
81
142
  const hrefs = (urls) => urls.map((url) => url.href);
@@ -120,5 +181,31 @@ test("getPortableGatewayCandidates() reads @gateway hints", () => {
120
181
  4
121
182
  ].map((i) => `https://g${i}.example/`));
122
183
  });
184
+ test("createSnapshotContextLoader() does not nest released snapshots", async () => {
185
+ const calls = [];
186
+ const base = (url) => {
187
+ calls.push(url);
188
+ return Promise.resolve({
189
+ contextUrl: null,
190
+ documentUrl: url,
191
+ document: { "@context": {} }
192
+ });
193
+ };
194
+ let loader = base;
195
+ for (let i = 0; i < 1e4; i++) {
196
+ const snapshot = createSnapshotContextLoader(loader);
197
+ snapshot.release();
198
+ loader = snapshot.loader;
199
+ }
200
+ await loader("https://example.com/context");
201
+ deepStrictEqual(calls, ["https://example.com/context"]);
202
+ const outer = createSnapshotContextLoader(base);
203
+ await outer.loader("https://example.com/context");
204
+ const inner = createSnapshotContextLoader(outer.loader);
205
+ await inner.loader("https://example.com/context");
206
+ deepStrictEqual(calls.length, 2);
207
+ inner.release();
208
+ outer.release();
209
+ });
123
210
  //#endregion
124
211
  export {};
@@ -1,7 +1,7 @@
1
1
  import process from "node:process";
2
2
  //#region deno.json
3
3
  var name = "@fedify/vocab-runtime";
4
- var version = "2.4.0-dev.2063+366c0362";
4
+ var version = "2.4.0-dev.2071+8b9dccb9";
5
5
  //#endregion
6
6
  //#region src/request.ts
7
7
  /**
@@ -3,7 +3,7 @@ let node_process = require("node:process");
3
3
  node_process = require_rolldown_runtime.__toESM(node_process, 1);
4
4
  //#region deno.json
5
5
  var name = "@fedify/vocab-runtime";
6
- var version = "2.4.0-dev.2063+366c0362";
6
+ var version = "2.4.0-dev.2071+8b9dccb9";
7
7
  //#endregion
8
8
  //#region src/request.ts
9
9
  /**
@@ -1,5 +1,5 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
2
- const require_request = require("./request-jWhPNG9V.cjs");
2
+ const require_request = require("./request-D_TFIuTp.cjs");
3
3
  let node_test = require("node:test");
4
4
  let node_process = require("node:process");
5
5
  node_process = require_rolldown_runtime.__toESM(node_process, 1);
@@ -1,4 +1,4 @@
1
- import { n as createActivityPubRequest, o as version, r as getUserAgent } from "./request-BT9FBhk6.mjs";
1
+ import { n as createActivityPubRequest, o as version, r as getUserAgent } from "./request-DXvwmbWU.mjs";
2
2
  import { test } from "node:test";
3
3
  import process from "node:process";
4
4
  import { deepStrictEqual } from "node:assert";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-runtime",
3
- "version": "2.4.0-dev.2063+366c0362",
3
+ "version": "2.4.0-dev.2071+8b9dccb9",
4
4
  "homepage": "https://fedify.dev/",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,7 +1,9 @@
1
1
  import { deepStrictEqual, ok, throws } from "node:assert/strict";
2
2
  import { test } from "node:test";
3
+ import type { DocumentLoader } from "../docloader.ts";
3
4
  import { parseIri } from "../url.ts";
4
5
  import {
6
+ createSnapshotContextLoader,
5
7
  getPortableGatewayCandidates,
6
8
  isPortableIri,
7
9
  } from "./portable-dereference.ts";
@@ -72,3 +74,33 @@ test("getPortableGatewayCandidates() reads @gateway hints", () => {
72
74
  [0, 1, 2, 3, 4].map((i) => `https://g${i}.example/`),
73
75
  );
74
76
  });
77
+
78
+ test("createSnapshotContextLoader() does not nest released snapshots", async () => {
79
+ const calls: string[] = [];
80
+ const base: DocumentLoader = (url) => {
81
+ calls.push(url);
82
+ return Promise.resolve({
83
+ contextUrl: null,
84
+ documentUrl: url,
85
+ document: { "@context": {} },
86
+ });
87
+ };
88
+ let loader = base;
89
+ for (let i = 0; i < 10000; i++) {
90
+ const snapshot = createSnapshotContextLoader(loader);
91
+ snapshot.release();
92
+ loader = snapshot.loader;
93
+ }
94
+ // A deep chain of wrappers would overflow the stack here:
95
+ await loader("https://example.com/context");
96
+ deepStrictEqual(calls, ["https://example.com/context"]);
97
+
98
+ // Snapshots still in use are kept, so a nested snapshot sees their cache:
99
+ const outer = createSnapshotContextLoader(base);
100
+ await outer.loader("https://example.com/context");
101
+ const inner = createSnapshotContextLoader(outer.loader);
102
+ await inner.loader("https://example.com/context");
103
+ deepStrictEqual(calls.length, 2);
104
+ inner.release();
105
+ outer.release();
106
+ });