@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.
- package/deno.json +1 -1
- package/dist/internal/portable-dereference.cjs +193 -21
- package/dist/internal/portable-dereference.d.cts +155 -15
- package/dist/internal/portable-dereference.d.ts +155 -15
- package/dist/internal/portable-dereference.js +187 -22
- package/dist/mod.cjs +1 -1
- package/dist/mod.d.cts +1 -1
- package/dist/mod.d.ts +1 -1
- package/dist/mod.js +1 -1
- package/dist/{portable--NcyA1R_.d.ts → portable-DH3947GK.d.ts} +6 -2
- package/dist/{portable-BykOD45s.d.cts → portable-comBI05Y.d.cts} +6 -2
- package/dist/tests/{body-CTGqQhNI.cjs → body-BA5q10mr.cjs} +1 -1
- package/dist/tests/{body-BN5x-zDQ.mjs → body-XAE-ooHH.mjs} +1 -1
- package/dist/tests/body.test.cjs +1 -1
- package/dist/tests/body.test.mjs +1 -1
- package/dist/tests/decimal.test.cjs +1 -1
- package/dist/tests/decimal.test.mjs +1 -1
- package/dist/tests/{docloader-BFxwbqMx.mjs → docloader-CKZdC6iZ.mjs} +2 -2
- package/dist/tests/{docloader-JGSpG-3l.cjs → docloader-D6BFa7gx.cjs} +2 -2
- package/dist/tests/docloader.test.cjs +2 -2
- package/dist/tests/docloader.test.mjs +2 -2
- package/dist/tests/internal/portable-dereference.test.cjs +87 -0
- package/dist/tests/internal/portable-dereference.test.mjs +87 -0
- package/dist/tests/{request-BT9FBhk6.mjs → request-DXvwmbWU.mjs} +1 -1
- package/dist/tests/{request-jWhPNG9V.cjs → request-D_TFIuTp.cjs} +1 -1
- package/dist/tests/request.test.cjs +1 -1
- package/dist/tests/request.test.mjs +1 -1
- package/package.json +1 -1
- package/src/internal/portable-dereference.test.ts +32 -0
- package/src/internal/portable-dereference.ts +288 -28
- package/src/portable.ts +6 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { t as FetchError } from "./request-
|
|
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-
|
|
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 {};
|
|
@@ -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.
|
|
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-
|
|
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-
|
|
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,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
|
+
});
|