@fedify/vocab-runtime 2.4.0-dev.2122 → 2.4.0-dev.2129
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/{docloader-Dr8wEkif.d.cts → docloader-oRpq1nQ8.d.cts} +8 -1
- package/dist/{docloader-Dr8wEkif.d.ts → docloader-oRpq1nQ8.d.ts} +8 -1
- package/dist/internal/jsonld-cache.cjs +50 -0
- package/dist/internal/jsonld-cache.d.cts +28 -2
- package/dist/internal/jsonld-cache.d.ts +28 -2
- package/dist/internal/jsonld-cache.js +48 -1
- package/dist/internal/portable-dereference.cjs +28 -12
- package/dist/internal/portable-dereference.d.cts +12 -4
- package/dist/internal/portable-dereference.d.ts +12 -4
- package/dist/internal/portable-dereference.js +28 -12
- package/dist/mod.cjs +15 -9
- package/dist/mod.d.cts +8 -2
- package/dist/mod.d.ts +8 -2
- package/dist/mod.js +15 -9
- package/dist/{portable-comBI05Y.d.cts → portable-Drfl02vZ.d.cts} +1 -1
- package/dist/{portable-DH3947GK.d.ts → portable-rwhiyMvY.d.ts} +1 -1
- package/dist/tests/{body-tLaZa-9t.cjs → body-B98QT2cn.cjs} +1 -1
- package/dist/tests/{body-DkyJy_0c.mjs → body-CrD70FCs.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-DFs6K8ol.cjs → docloader-BVoNdutR.cjs} +16 -10
- package/dist/tests/{docloader-CSfFzxWm.mjs → docloader-CBscxl9w.mjs} +16 -10
- package/dist/tests/docloader.test.cjs +49 -2
- package/dist/tests/docloader.test.mjs +49 -2
- package/dist/tests/internal/portable-dereference.test.cjs +67 -6
- package/dist/tests/internal/portable-dereference.test.mjs +67 -6
- package/dist/tests/jsonld-cache-9jap4yM0.cjs +373 -0
- package/dist/tests/jsonld-cache-DnZS0z1G.mjs +324 -0
- package/dist/tests/jsonld-cache.test.cjs +20 -291
- package/dist/tests/jsonld-cache.test.mjs +2 -273
- package/dist/tests/{request-BLe2W5ua.cjs → request-BMNsRCJF.cjs} +1 -1
- package/dist/tests/{request-CmMy4YVK.mjs → request-CRVyLEa-.mjs} +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/docloader.test.ts +73 -0
- package/src/docloader.ts +46 -17
- package/src/internal/jsonld-cache.ts +60 -0
- package/src/internal/portable-dereference.test.ts +71 -0
- package/src/internal/portable-dereference.ts +37 -24
- package/src/preprocessor.ts +7 -0
- package/dist/tests/jsonld-CmDhHNTx.cjs +0 -12
- package/dist/tests/jsonld-DweIJ07b.mjs +0 -5
|
@@ -2,6 +2,7 @@ import { deepStrictEqual, ok, throws } from "node:assert/strict";
|
|
|
2
2
|
import { test } from "node:test";
|
|
3
3
|
import type { DocumentLoader } from "../docloader.ts";
|
|
4
4
|
import { parseIri } from "../url.ts";
|
|
5
|
+
import { createScopedContextLoader } from "./jsonld-cache.ts";
|
|
5
6
|
import {
|
|
6
7
|
createSnapshotContextLoader,
|
|
7
8
|
getPortableGatewayCandidates,
|
|
@@ -104,3 +105,73 @@ test("createSnapshotContextLoader() does not nest released snapshots", async ()
|
|
|
104
105
|
inner.release();
|
|
105
106
|
outer.release();
|
|
106
107
|
});
|
|
108
|
+
|
|
109
|
+
test("context loaders unwrap mixed released wrappers", async () => {
|
|
110
|
+
const calls: string[] = [];
|
|
111
|
+
const base: DocumentLoader = (url) => {
|
|
112
|
+
calls.push(url);
|
|
113
|
+
return Promise.resolve({
|
|
114
|
+
contextUrl: null,
|
|
115
|
+
documentUrl: url,
|
|
116
|
+
document: { "@context": {} },
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
for (const snapshotFirst of [false, true]) {
|
|
120
|
+
let loader = base;
|
|
121
|
+
for (let i = 0; i < 10000; i++) {
|
|
122
|
+
const wrapper = (i % 2 === 0) === snapshotFirst
|
|
123
|
+
? createSnapshotContextLoader(loader, true)
|
|
124
|
+
: createScopedContextLoader(loader, true);
|
|
125
|
+
wrapper.release();
|
|
126
|
+
loader = wrapper.loader;
|
|
127
|
+
}
|
|
128
|
+
const unwrapped = createScopedContextLoader(loader, false).loader;
|
|
129
|
+
deepStrictEqual(unwrapped, base);
|
|
130
|
+
await unwrapped("https://example.com/context");
|
|
131
|
+
}
|
|
132
|
+
deepStrictEqual(calls, [
|
|
133
|
+
"https://example.com/context",
|
|
134
|
+
"https://example.com/context",
|
|
135
|
+
]);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("mixed context wrappers preserve active suppression and snapshots", async () => {
|
|
139
|
+
for (const activeSnapshot of [false, true]) {
|
|
140
|
+
const calls: Parameters<DocumentLoader>[1][] = [];
|
|
141
|
+
const base: DocumentLoader = (url, options) => {
|
|
142
|
+
calls.push(options);
|
|
143
|
+
return Promise.resolve({
|
|
144
|
+
contextUrl: null,
|
|
145
|
+
documentUrl: url,
|
|
146
|
+
document: { "@context": {} },
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
const active = activeSnapshot
|
|
150
|
+
? createSnapshotContextLoader(base, true)
|
|
151
|
+
: createScopedContextLoader(base, true);
|
|
152
|
+
let loader = active.loader;
|
|
153
|
+
for (let i = 0; i < 4; i++) {
|
|
154
|
+
const wrapper = i % 2 === 0
|
|
155
|
+
? createScopedContextLoader(loader, true)
|
|
156
|
+
: createSnapshotContextLoader(loader, true);
|
|
157
|
+
wrapper.release();
|
|
158
|
+
loader = wrapper.loader;
|
|
159
|
+
}
|
|
160
|
+
const unwrapped = createScopedContextLoader(loader, false).loader;
|
|
161
|
+
deepStrictEqual(unwrapped, active.loader);
|
|
162
|
+
const options = { signal: new AbortController().signal };
|
|
163
|
+
const first = await unwrapped("https://example.com/context", options);
|
|
164
|
+
ok(first.document != null && typeof first.document === "object");
|
|
165
|
+
Reflect.set(first.document, "changed", true);
|
|
166
|
+
const second = await unwrapped("https://example.com/context", options);
|
|
167
|
+
deepStrictEqual(second.document, { "@context": {} });
|
|
168
|
+
deepStrictEqual(calls.length, activeSnapshot ? 1 : 2);
|
|
169
|
+
for (const call of calls) {
|
|
170
|
+
deepStrictEqual(call, { ...options, suppressError: true });
|
|
171
|
+
}
|
|
172
|
+
active.release();
|
|
173
|
+
deepStrictEqual(createScopedContextLoader(loader, false).loader, base);
|
|
174
|
+
await unwrapped("https://example.com/context", options);
|
|
175
|
+
deepStrictEqual(calls.at(-1), options);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
@@ -7,6 +7,11 @@ import {
|
|
|
7
7
|
import preloadedContexts from "../contexts.ts";
|
|
8
8
|
import type { DocumentLoader, RemoteDocument } from "../docloader.ts";
|
|
9
9
|
import jsonld from "../jsonld.ts";
|
|
10
|
+
import {
|
|
11
|
+
createScopedContextLoader,
|
|
12
|
+
registerDocumentLoaderWrapper,
|
|
13
|
+
unwrapReleasedDocumentLoader,
|
|
14
|
+
} from "./jsonld-cache.ts";
|
|
10
15
|
import type {
|
|
11
16
|
PortableObjectReferrer,
|
|
12
17
|
PortableObjectVerification,
|
|
@@ -98,7 +103,15 @@ function getObjectId(object: unknown): URL | null {
|
|
|
98
103
|
|
|
99
104
|
/**
|
|
100
105
|
* Checks whether a vocabulary object is part of a chain of portable objects,
|
|
101
|
-
* i.e., whether it has a portable ID
|
|
106
|
+
* i.e., whether it has a portable ID, has an FEP-ef61 compatible identifier
|
|
107
|
+
* as its ID (even a malformed one, so that it fails closed), or was obtained
|
|
108
|
+
* from a portable object. Accessors of such objects dereference references
|
|
109
|
+
* as portable objects.
|
|
110
|
+
*
|
|
111
|
+
* Being part of a chain does not mean that the object itself is verified:
|
|
112
|
+
* an object parsed from arbitrary JSON-LD is in a chain just because of its
|
|
113
|
+
* ID. Only {@link dereferencePortableIri} records that an object was
|
|
114
|
+
* accepted by a verifier.
|
|
102
115
|
*
|
|
103
116
|
* @internal Technically exported for generated vocabulary classes, but not
|
|
104
117
|
* part of the public API contract. This is not considered public API for
|
|
@@ -107,7 +120,7 @@ function getObjectId(object: unknown): URL | null {
|
|
|
107
120
|
export function isInPortableChain(object: object): boolean {
|
|
108
121
|
if (provenances.has(object)) return true;
|
|
109
122
|
const id = getObjectId(object);
|
|
110
|
-
return id != null && isPortableIri(id);
|
|
123
|
+
return id != null && (isPortableIri(id) || isCompatibleEf61Iri(id));
|
|
111
124
|
}
|
|
112
125
|
|
|
113
126
|
/**
|
|
@@ -562,17 +575,12 @@ function parseGatewayOrigin(gateway: string | URL): URL | null {
|
|
|
562
575
|
*/
|
|
563
576
|
export function createSnapshotContextLoader(
|
|
564
577
|
contextLoader: DocumentLoader,
|
|
578
|
+
suppressError?: boolean,
|
|
565
579
|
): { loader: DocumentLoader; release: () => void } {
|
|
566
580
|
// Objects parsed during a dereference keep its loader, so later
|
|
567
581
|
// dereferences from them would otherwise wrap released pass-throughs in
|
|
568
582
|
// ever deeper chains:
|
|
569
|
-
|
|
570
|
-
let wrapped = snapshotLoaders.get(contextLoader);
|
|
571
|
-
wrapped?.released;
|
|
572
|
-
wrapped = snapshotLoaders.get(contextLoader)
|
|
573
|
-
) {
|
|
574
|
-
contextLoader = wrapped.base;
|
|
575
|
-
}
|
|
583
|
+
contextLoader = unwrapReleasedDocumentLoader(contextLoader);
|
|
576
584
|
const cache = new Map<string, Promise<RemoteDocument>>();
|
|
577
585
|
let released = false;
|
|
578
586
|
const release = () => {
|
|
@@ -592,9 +600,10 @@ export function createSnapshotContextLoader(
|
|
|
592
600
|
}
|
|
593
601
|
let promise = cache.get(key);
|
|
594
602
|
if (promise == null) {
|
|
595
|
-
const loading = contextLoader(
|
|
596
|
-
|
|
597
|
-
|
|
603
|
+
const loading = contextLoader(
|
|
604
|
+
url,
|
|
605
|
+
suppressError ? { ...options, suppressError: true } : options,
|
|
606
|
+
).then((document) => structuredClone(document));
|
|
598
607
|
promise = loading;
|
|
599
608
|
cache.set(key, loading);
|
|
600
609
|
loading.catch(() => {
|
|
@@ -604,15 +613,10 @@ export function createSnapshotContextLoader(
|
|
|
604
613
|
return structuredClone(await promise);
|
|
605
614
|
};
|
|
606
615
|
const state = { base: contextLoader, released: false };
|
|
607
|
-
|
|
616
|
+
registerDocumentLoaderWrapper(loader, state);
|
|
608
617
|
return { loader, release };
|
|
609
618
|
}
|
|
610
619
|
|
|
611
|
-
const snapshotLoaders = new WeakMap<
|
|
612
|
-
DocumentLoader,
|
|
613
|
-
{ readonly base: DocumentLoader; released: boolean }
|
|
614
|
-
>();
|
|
615
|
-
|
|
616
620
|
/**
|
|
617
621
|
* Options for {@link dereferencePortableIri}.
|
|
618
622
|
*
|
|
@@ -721,7 +725,7 @@ export async function dereferencePortableIri<T extends { id: URL | null }>(
|
|
|
721
725
|
const fail = (error: unknown): null => {
|
|
722
726
|
span?.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
|
|
723
727
|
if (options.suppressError) {
|
|
724
|
-
logger.
|
|
728
|
+
logger.warn("Failed to dereference {url}: {error}", {
|
|
725
729
|
url: lookupUrl,
|
|
726
730
|
error,
|
|
727
731
|
});
|
|
@@ -734,8 +738,9 @@ export async function dereferencePortableIri<T extends { id: URL | null }>(
|
|
|
734
738
|
return fail(
|
|
735
739
|
new TypeError(
|
|
736
740
|
"Dereferencing the portable object " + lookupUrl + " requires the " +
|
|
737
|
-
"verifyPortableObject option, e.g.,
|
|
738
|
-
"
|
|
741
|
+
"verifyPortableObject option, e.g., verifyPortableObject() from " +
|
|
742
|
+
"@fedify/fedify. Passing a Fedify Context as the options gives " +
|
|
743
|
+
"it, and objects parsed with the option use it by default.",
|
|
739
744
|
),
|
|
740
745
|
);
|
|
741
746
|
}
|
|
@@ -769,8 +774,15 @@ export async function dereferencePortableIri<T extends { id: URL | null }>(
|
|
|
769
774
|
// retrieve the portable IRI itself:
|
|
770
775
|
requestUrls.push({ url: lookupUrl, gateway: null });
|
|
771
776
|
}
|
|
772
|
-
const snapshot = createSnapshotContextLoader(
|
|
777
|
+
const snapshot = createSnapshotContextLoader(
|
|
778
|
+
options.contextLoader,
|
|
779
|
+
options.suppressError,
|
|
780
|
+
);
|
|
773
781
|
const contextLoader = snapshot.loader;
|
|
782
|
+
const scopedDocument = createScopedContextLoader(
|
|
783
|
+
options.documentLoader,
|
|
784
|
+
options.suppressError,
|
|
785
|
+
);
|
|
774
786
|
const attempts: Attempt[] = [];
|
|
775
787
|
const { signal } = options;
|
|
776
788
|
const gatewayHints = explicitGateways == null && inferredGateways.length > 0
|
|
@@ -784,7 +796,7 @@ export async function dereferencePortableIri<T extends { id: URL | null }>(
|
|
|
784
796
|
try {
|
|
785
797
|
remoteDocument = response ?? await options.documentLoader(
|
|
786
798
|
requestUrl,
|
|
787
|
-
signal
|
|
799
|
+
{ signal, suppressError: options.suppressError },
|
|
788
800
|
);
|
|
789
801
|
} catch (error) {
|
|
790
802
|
signal?.throwIfAborted();
|
|
@@ -808,7 +820,7 @@ export async function dereferencePortableIri<T extends { id: URL | null }>(
|
|
|
808
820
|
let result: PortableObjectVerification;
|
|
809
821
|
try {
|
|
810
822
|
result = await verify(document, {
|
|
811
|
-
documentLoader:
|
|
823
|
+
documentLoader: scopedDocument.loader,
|
|
812
824
|
contextLoader,
|
|
813
825
|
tracerProvider: options.tracerProvider,
|
|
814
826
|
...(documentUrl == null ? {} : { documentUrl }),
|
|
@@ -866,6 +878,7 @@ export async function dereferencePortableIri<T extends { id: URL | null }>(
|
|
|
866
878
|
}
|
|
867
879
|
}
|
|
868
880
|
} finally {
|
|
881
|
+
scopedDocument.release();
|
|
869
882
|
snapshot.release();
|
|
870
883
|
}
|
|
871
884
|
const rejected = attempts.filter((a) => a.type === "rejected");
|
package/src/preprocessor.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DocumentLoader } from "./docloader.ts";
|
|
2
2
|
import type { TracerProvider } from "@opentelemetry/api";
|
|
3
|
+
import type { PortableObjectVerifier } from "./portable.ts";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* JSON value shape passed to property preprocessors.
|
|
@@ -24,6 +25,12 @@ export interface PropertyPreprocessorContext {
|
|
|
24
25
|
contextLoader?: DocumentLoader;
|
|
25
26
|
/** OpenTelemetry tracer provider for instrumentation. */
|
|
26
27
|
tracerProvider?: TracerProvider;
|
|
28
|
+
/**
|
|
29
|
+
* The default FEP-ef61 portable object verifier that objects returned by
|
|
30
|
+
* the preprocessor should use for their property accessors.
|
|
31
|
+
* @since 2.4.0
|
|
32
|
+
*/
|
|
33
|
+
verifyPortableObject?: PortableObjectVerifier;
|
|
27
34
|
/** Base URL for resolving relative references. */
|
|
28
35
|
baseUrl?: URL;
|
|
29
36
|
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
|
|
2
|
-
let jsonld_dist_jsonld_esm_js = require("jsonld/dist/jsonld.esm.js");
|
|
3
|
-
jsonld_dist_jsonld_esm_js = require_rolldown_runtime.__toESM(jsonld_dist_jsonld_esm_js, 1);
|
|
4
|
-
//#region src/jsonld.ts
|
|
5
|
-
var jsonld_default = jsonld_dist_jsonld_esm_js.default;
|
|
6
|
-
//#endregion
|
|
7
|
-
Object.defineProperty(exports, "jsonld_default", {
|
|
8
|
-
enumerable: true,
|
|
9
|
-
get: function() {
|
|
10
|
-
return jsonld_default;
|
|
11
|
-
}
|
|
12
|
-
});
|