@fedify/vocab-runtime 2.4.0-dev.2122 → 2.4.0-dev.2127

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 (46) hide show
  1. package/deno.json +1 -1
  2. package/dist/{docloader-Dr8wEkif.d.cts → docloader-oRpq1nQ8.d.cts} +8 -1
  3. package/dist/{docloader-Dr8wEkif.d.ts → docloader-oRpq1nQ8.d.ts} +8 -1
  4. package/dist/internal/jsonld-cache.cjs +50 -0
  5. package/dist/internal/jsonld-cache.d.cts +28 -2
  6. package/dist/internal/jsonld-cache.d.ts +28 -2
  7. package/dist/internal/jsonld-cache.js +48 -1
  8. package/dist/internal/portable-dereference.cjs +28 -12
  9. package/dist/internal/portable-dereference.d.cts +12 -4
  10. package/dist/internal/portable-dereference.d.ts +12 -4
  11. package/dist/internal/portable-dereference.js +28 -12
  12. package/dist/mod.cjs +15 -9
  13. package/dist/mod.d.cts +8 -2
  14. package/dist/mod.d.ts +8 -2
  15. package/dist/mod.js +15 -9
  16. package/dist/{portable-comBI05Y.d.cts → portable-Drfl02vZ.d.cts} +1 -1
  17. package/dist/{portable-DH3947GK.d.ts → portable-rwhiyMvY.d.ts} +1 -1
  18. package/dist/tests/{body-tLaZa-9t.cjs → body-BCEAkwBy.cjs} +1 -1
  19. package/dist/tests/{body-DkyJy_0c.mjs → body-BUO7KBud.mjs} +1 -1
  20. package/dist/tests/body.test.cjs +1 -1
  21. package/dist/tests/body.test.mjs +1 -1
  22. package/dist/tests/decimal.test.cjs +1 -1
  23. package/dist/tests/decimal.test.mjs +1 -1
  24. package/dist/tests/{docloader-CSfFzxWm.mjs → docloader-BBjIii4r.mjs} +16 -10
  25. package/dist/tests/{docloader-DFs6K8ol.cjs → docloader-DOtl16c7.cjs} +16 -10
  26. package/dist/tests/docloader.test.cjs +49 -2
  27. package/dist/tests/docloader.test.mjs +49 -2
  28. package/dist/tests/internal/portable-dereference.test.cjs +67 -6
  29. package/dist/tests/internal/portable-dereference.test.mjs +67 -6
  30. package/dist/tests/jsonld-cache-9jap4yM0.cjs +373 -0
  31. package/dist/tests/jsonld-cache-DnZS0z1G.mjs +324 -0
  32. package/dist/tests/jsonld-cache.test.cjs +20 -291
  33. package/dist/tests/jsonld-cache.test.mjs +2 -273
  34. package/dist/tests/{request-CmMy4YVK.mjs → request-C6bviL-E.mjs} +1 -1
  35. package/dist/tests/{request-BLe2W5ua.cjs → request-DzzSwifZ.cjs} +1 -1
  36. package/dist/tests/request.test.cjs +1 -1
  37. package/dist/tests/request.test.mjs +1 -1
  38. package/package.json +1 -1
  39. package/src/docloader.test.ts +73 -0
  40. package/src/docloader.ts +46 -17
  41. package/src/internal/jsonld-cache.ts +60 -0
  42. package/src/internal/portable-dereference.test.ts +71 -0
  43. package/src/internal/portable-dereference.ts +37 -24
  44. package/src/preprocessor.ts +7 -0
  45. package/dist/tests/jsonld-CmDhHNTx.cjs +0 -12
  46. 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 or was obtained from a portable object.
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
- for (
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(url, options).then((document) =>
596
- structuredClone(document)
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
- snapshotLoaders.set(loader, state);
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.error("Failed to dereference {url}: {error}", {
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., verifyPortableObjectProof() " +
738
- "from @fedify/fedify.",
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(options.contextLoader);
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 == null ? undefined : { 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: options.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");
@@ -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
- });
@@ -1,5 +0,0 @@
1
- import jsonld from "jsonld/dist/jsonld.esm.js";
2
- //#region src/jsonld.ts
3
- var jsonld_default = jsonld;
4
- //#endregion
5
- export { jsonld_default as t };