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

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-BN5x-zDQ.mjs → body-CNO0jMV8.mjs} +1 -1
  13. package/dist/tests/{body-CTGqQhNI.cjs → body-DXHOazEl.cjs} +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-JGSpG-3l.cjs → docloader-4Txefi7y.cjs} +2 -2
  19. package/dist/tests/{docloader-BFxwbqMx.mjs → docloader-C-nWtUWo.mjs} +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-jWhPNG9V.cjs → request-BOLanqAd.cjs} +1 -1
  25. package/dist/tests/{request-BT9FBhk6.mjs → request-dpFdQKXr.mjs} +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,6 +1,6 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { n as DocumentLoader } from "../docloader-Dr8wEkif.js";
3
- import { r as PortableObjectVerifier } from "../portable--NcyA1R_.js";
2
+ import { n as DocumentLoader, s as RemoteDocument } from "../docloader-Dr8wEkif.js";
3
+ import { r as PortableObjectVerifier } from "../portable-DH3947GK.js";
4
4
  import { Span, TracerProvider } from "@opentelemetry/api";
5
5
 
6
6
  //#region src/internal/portable-dereference.d.ts
@@ -69,22 +69,128 @@ declare function recordPortableReferrer(parent: object, child: unknown, property
69
69
  */
70
70
  declare function copyPortableProvenance(from: object, to: object): void;
71
71
  /**
72
- * Rejects a reference from a portable object that would be dereferenced as
73
- * an ordinary HTTP(S) object although it stands for a portable object, i.e.,
74
- * an FEP-ef61 compatible identifier, or a document whose final URL or `@id`
75
- * is a compatible identifier or a portable IRI. Such a document would skip
76
- * the portable object policy, so it is rejected regardless of the
77
- * `crossOrigin` option, except that `crossOrigin: "throw"` makes it throw.
72
+ * Recognizes an FEP-ef61 compatible identifier to dereference as a portable
73
+ * object.
74
+ *
75
+ * @returns `undefined` if the URL is not a compatible identifier; `null` if
76
+ * it is a malformed compatible identifier; otherwise, the portable
77
+ * ID it stands for and the gateway it names.
78
+ * @internal Technically exported for generated vocabulary classes, but not
79
+ * part of the public API contract. This is not considered public API for
80
+ * Semantic Versioning decisions.
81
+ */
82
+ declare function parseCompatibleEf61Reference(url: URL | string): {
83
+ readonly id: URL;
84
+ readonly gateway: URL;
85
+ } | null | undefined;
86
+ /**
87
+ * Checks whether a property accessor call on a vocabulary object applies the
88
+ * FEP-ef61 portable object policy, i.e., whether the `verifyPortableObject`
89
+ * option is given or the object is part of a chain of portable objects.
90
+ * In this mode, compatible identifiers are dereferenced as portable objects,
91
+ * and fetched documents that turn out to stand for portable objects are
92
+ * verified as such.
93
+ *
94
+ * @internal Technically exported for generated vocabulary classes, but not
95
+ * part of the public API contract. This is not considered public API for
96
+ * Semantic Versioning decisions.
97
+ */
98
+ declare function isPortableMode(object: object, options: {
99
+ readonly verifyPortableObject?: PortableObjectVerifier;
100
+ }): boolean;
101
+ /**
102
+ * Checks whether an object embedded in a property of a vocabulary object has
103
+ * to be dereferenced by its `@id` and verified as a portable object rather
104
+ * than trusted as embedded, even with `crossOrigin: "trust"`. This is the
105
+ * case in {@link isPortableMode | portable mode} if its `@id` is an FEP-ef61
106
+ * compatible identifier, or a portable IRI whose DID differs from the
107
+ * owner's portable ID.
108
+ *
109
+ * @param parent The object whose property embeds the object.
110
+ * @param ownerId The ID of the object that vouches for embedded objects, or
111
+ * `null` if embedded objects are not trusted by their owner.
112
+ * @param id The `@id` of the embedded object.
113
+ * @param options The options of the accessor.
114
+ * @internal Technically exported for generated vocabulary classes, but not
115
+ * part of the public API contract. This is not considered public API for
116
+ * Semantic Versioning decisions.
117
+ */
118
+ declare function mustDereferencePortableObject(parent: object, ownerId: URL | null, id: URL, options: {
119
+ readonly verifyPortableObject?: PortableObjectVerifier;
120
+ }): boolean;
121
+ /**
122
+ * The portable object that a document fetched as an ordinary HTTP(S)
123
+ * document claims to be.
124
+ *
125
+ * @internal
126
+ */
127
+ interface PortableResponseClaim {
128
+ /** The portable ID that the document has to have. */
129
+ readonly id: URL;
130
+ /**
131
+ * The gateways inferred from the document's URL or `@id`, which are
132
+ * reported to the verifier as gateway hints.
133
+ */
134
+ readonly inferredGateways: readonly URL[];
135
+ }
136
+ /**
137
+ * Finds out whether a document fetched as an ordinary HTTP(S) document
138
+ * claims to be a portable object: its final URL is a portable IRI or an
139
+ * FEP-ef61 compatible identifier, or else its `@id` is. The final URL takes
140
+ * precedence, so a document whose `@id` claims another object fails the
141
+ * identity check against the final URL.
142
+ *
143
+ * @param documentUrl The final URL of the document.
144
+ * @param objectId The `@id` of the parsed document.
145
+ * @returns `undefined` if the document does not claim to be a portable
146
+ * object; `null` if the claim is malformed; otherwise, the claim.
147
+ * @internal Technically exported for generated vocabulary classes, but not
148
+ * part of the public API contract. This is not considered public API for
149
+ * Semantic Versioning decisions.
150
+ */
151
+ declare function getPortableResponseClaim(documentUrl: string, objectId: URL | null): PortableResponseClaim | null | undefined;
152
+ /**
153
+ * Records that an object fetched as an ordinary HTTP(S) object stands for
154
+ * a portable object if its reference, its final URL, or its `@id` is
155
+ * a portable IRI or an FEP-ef61 compatible identifier. Accessors do not
156
+ * cache such objects in their parents, so that a later call with the
157
+ * `verifyPortableObject` option dereferences and verifies them.
158
+ *
159
+ * @internal Technically exported for generated vocabulary classes, but not
160
+ * part of the public API contract. This is not considered public API for
161
+ * Semantic Versioning decisions.
162
+ */
163
+ declare function markUnverifiedPortableClaim(object: object, reference: URL, documentUrl: string): void;
164
+ /**
165
+ * Checks whether an object was fetched as an ordinary HTTP(S) object although
166
+ * it stands for a portable object. See {@link markUnverifiedPortableClaim}.
167
+ *
168
+ * @internal Technically exported for generated vocabulary classes, but not
169
+ * part of the public API contract. This is not considered public API for
170
+ * Semantic Versioning decisions.
171
+ */
172
+ declare function isUnverifiedPortableClaim(object: object): boolean;
173
+ /**
174
+ * Rejects a malformed FEP-ef61 compatible identifier found in a reference,
175
+ * the final URL of a fetched document, or its `@id`, when it would be
176
+ * dereferenced as a portable object. It is rejected without a request (or
177
+ * without returning the fetched document), since it cannot be verified as
178
+ * a portable object, and fetching it as an ordinary HTTP(S) object would
179
+ * skip the portable object policy.
78
180
  *
79
181
  * @param url The rejected URL, for the log message.
80
- * @param crossOrigin The `crossOrigin` option of the accessor.
182
+ * @param options The options of the accessor.
81
183
  * @returns Always `null`.
82
- * @throws {Error} If `crossOrigin` is `"throw"`.
184
+ * @throws {Error} If `crossOrigin` is `"throw"` and `suppressError` is not
185
+ * set.
83
186
  * @internal Technically exported for generated vocabulary classes, but not
84
187
  * part of the public API contract. This is not considered public API for
85
188
  * Semantic Versioning decisions.
86
189
  */
87
- declare function rejectPortableChainReference(url: string, crossOrigin: "ignore" | "throw" | "trust" | undefined): null;
190
+ declare function rejectMalformedCompatibleReference(url: string, options: {
191
+ readonly crossOrigin?: "ignore" | "throw" | "trust";
192
+ readonly suppressError?: boolean;
193
+ }): null;
88
194
  /**
89
195
  * Logs that an embedded object without `@id` in an object accepted without
90
196
  * an integrity proof is dropped, because it cannot be dereferenced and
@@ -116,6 +222,25 @@ declare function isPortableIri(url: URL): boolean;
116
222
  */
117
223
  declare function getPortableGatewayCandidates(url: URL, gateways?: readonly (string | URL)[]): URL[];
118
224
  /**
225
+ * Creates a context loader that returns the same context documents for the
226
+ * whole dereference operation, so that the identity check, the proof
227
+ * verifier, and the parser interpret the fetched document identically even
228
+ * if the underlying loader is nondeterministic. Failed loads are not
229
+ * remembered, so a transient failure does not affect the next gateway.
230
+ *
231
+ * The parsed object keeps the loader for its own later dereferences, so
232
+ * `release()` turns it into a plain pass-through to the underlying loader
233
+ * once the operation is over.
234
+ *
235
+ * @internal Technically exported for generated vocabulary classes, but not
236
+ * part of the public API contract. This is not considered public API for
237
+ * Semantic Versioning decisions.
238
+ */
239
+ declare function createSnapshotContextLoader(contextLoader: DocumentLoader): {
240
+ loader: DocumentLoader;
241
+ release: () => void;
242
+ };
243
+ /**
119
244
  * Options for {@link dereferencePortableIri}.
120
245
  *
121
246
  * @internal Technically exported for generated vocabulary classes, but not
@@ -127,6 +252,20 @@ interface DereferencePortableIriOptions<T> {
127
252
  contextLoader: DocumentLoader;
128
253
  tracerProvider: TracerProvider;
129
254
  gateways?: readonly (string | URL)[];
255
+ /**
256
+ * The gateways inferred from where the reference came from, e.g., the
257
+ * gateway of a compatible identifier. When given, they replace the
258
+ * `@gateway` location hints of the IRI, are asked before the explicit
259
+ * `gateways`, and are reported to the verifier as gateway hints unless
260
+ * `gateways` is given.
261
+ */
262
+ inferredGateways?: readonly URL[];
263
+ /**
264
+ * An already fetched document to validate instead of fetching the object
265
+ * from gateways. It is validated the same way as a gateway's response,
266
+ * and nothing else is tried if it is rejected.
267
+ */
268
+ response?: RemoteDocument;
130
269
  verifyPortableObject?: PortableObjectVerifier;
131
270
  referrer?: PortableReferrerLink;
132
271
  suppressError?: boolean;
@@ -158,9 +297,10 @@ declare class PortableObjectRejectedError extends Error {
158
297
  *
159
298
  * Gateways are tried one by one until one of them returns a single-node
160
299
  * document whose `@id`, if any, canonically matches the requested IRI and
161
- * which passes `verifyPortableObject`. When there is no gateway to try, the document
162
- * loader is asked for the portable IRI itself, and its result is validated
163
- * the same way.
300
+ * which passes `verifyPortableObject`. When there is no gateway to try, the
301
+ * document loader is asked for the portable IRI itself, and its result is
302
+ * validated the same way. If the `response` option is given, only that
303
+ * document is validated, and nothing is fetched.
164
304
  *
165
305
  * @returns The parsed object, or `null` if no valid object was retrieved and
166
306
  * `suppressError` is set or the failure was a rejected object
@@ -176,4 +316,4 @@ declare function dereferencePortableIri<T extends {
176
316
  id: URL | null;
177
317
  }>(url: URL, options: DereferencePortableIriOptions<T>): Promise<T | null>;
178
318
  //#endregion
179
- export { DereferencePortableIriOptions, PortableObjectRejectedError, PortableReferrerLink, copyPortableProvenance, dereferencePortableIri, getPortableGatewayCandidates, isCompatibleEf61Iri, isInPortableChain, isPortableIri, isUnsecuredPortableObject, recordPortableReferrer, rejectPortableChainReference, warnUnverifiableEmbeddedObject };
319
+ export { DereferencePortableIriOptions, PortableObjectRejectedError, PortableReferrerLink, PortableResponseClaim, copyPortableProvenance, createSnapshotContextLoader, dereferencePortableIri, getPortableGatewayCandidates, getPortableResponseClaim, isCompatibleEf61Iri, isInPortableChain, isPortableIri, isPortableMode, isUnsecuredPortableObject, isUnverifiedPortableClaim, markUnverifiedPortableClaim, mustDereferencePortableObject, parseCompatibleEf61Reference, recordPortableReferrer, rejectMalformedCompatibleReference, warnUnverifiableEmbeddedObject };
@@ -1,6 +1,6 @@
1
1
 
2
2
  import { t as preloadedContexts } from "../contexts-DoZUIW7y.js";
3
- import { a as formatIri, g as toCompatibleEf61Id, m as parseIri, o as fromCompatibleEf61Id, r as canonicalizePortableUri } from "../url-CEhi4hb4.js";
3
+ import { a as formatIri, c as haveSameFe34Origin, g as toCompatibleEf61Id, m as parseIri, o as fromCompatibleEf61Id, r as canonicalizePortableUri } from "../url-CEhi4hb4.js";
4
4
  import jsonld_default from "../jsonld.js";
5
5
  import { getLogger } from "@logtape/logtape";
6
6
  import { SpanStatusCode } from "@opentelemetry/api";
@@ -117,24 +117,169 @@ function copyPortableProvenance(from, to) {
117
117
  });
118
118
  }
119
119
  /**
120
- * Rejects a reference from a portable object that would be dereferenced as
121
- * an ordinary HTTP(S) object although it stands for a portable object, i.e.,
122
- * an FEP-ef61 compatible identifier, or a document whose final URL or `@id`
123
- * is a compatible identifier or a portable IRI. Such a document would skip
124
- * the portable object policy, so it is rejected regardless of the
125
- * `crossOrigin` option, except that `crossOrigin: "throw"` makes it throw.
120
+ * Recognizes an FEP-ef61 compatible identifier to dereference as a portable
121
+ * object.
122
+ *
123
+ * @returns `undefined` if the URL is not a compatible identifier; `null` if
124
+ * it is a malformed compatible identifier; otherwise, the portable
125
+ * ID it stands for and the gateway it names.
126
+ * @internal Technically exported for generated vocabulary classes, but not
127
+ * part of the public API contract. This is not considered public API for
128
+ * Semantic Versioning decisions.
129
+ */
130
+ function parseCompatibleEf61Reference(url) {
131
+ if (typeof url === "string") {
132
+ if (!URL.canParse(url)) return void 0;
133
+ url = new URL(url);
134
+ }
135
+ try {
136
+ const id = fromCompatibleEf61Id(url);
137
+ if (id == null) return void 0;
138
+ return {
139
+ id,
140
+ gateway: new URL(url.origin)
141
+ };
142
+ } catch (error) {
143
+ if (error instanceof TypeError) {
144
+ logger.debug("Invalid FEP-ef61 compatible identifier {url}: {error}", {
145
+ url: url.href,
146
+ error
147
+ });
148
+ return null;
149
+ }
150
+ throw error;
151
+ }
152
+ }
153
+ /**
154
+ * Checks whether a property accessor call on a vocabulary object applies the
155
+ * FEP-ef61 portable object policy, i.e., whether the `verifyPortableObject`
156
+ * option is given or the object is part of a chain of portable objects.
157
+ * In this mode, compatible identifiers are dereferenced as portable objects,
158
+ * and fetched documents that turn out to stand for portable objects are
159
+ * verified as such.
160
+ *
161
+ * @internal Technically exported for generated vocabulary classes, but not
162
+ * part of the public API contract. This is not considered public API for
163
+ * Semantic Versioning decisions.
164
+ */
165
+ function isPortableMode(object, options) {
166
+ return options.verifyPortableObject != null || isInPortableChain(object);
167
+ }
168
+ /**
169
+ * Checks whether an object embedded in a property of a vocabulary object has
170
+ * to be dereferenced by its `@id` and verified as a portable object rather
171
+ * than trusted as embedded, even with `crossOrigin: "trust"`. This is the
172
+ * case in {@link isPortableMode | portable mode} if its `@id` is an FEP-ef61
173
+ * compatible identifier, or a portable IRI whose DID differs from the
174
+ * owner's portable ID.
175
+ *
176
+ * @param parent The object whose property embeds the object.
177
+ * @param ownerId The ID of the object that vouches for embedded objects, or
178
+ * `null` if embedded objects are not trusted by their owner.
179
+ * @param id The `@id` of the embedded object.
180
+ * @param options The options of the accessor.
181
+ * @internal Technically exported for generated vocabulary classes, but not
182
+ * part of the public API contract. This is not considered public API for
183
+ * Semantic Versioning decisions.
184
+ */
185
+ function mustDereferencePortableObject(parent, ownerId, id, options) {
186
+ if (!isPortableMode(parent, options)) return false;
187
+ if (isCompatibleEf61Iri(id)) return true;
188
+ if (!isPortableIri(id)) return false;
189
+ return ownerId == null || !isPortableIri(ownerId) || !haveSameFe34Origin(ownerId, id);
190
+ }
191
+ const PORTABLE_IRI_PATTERN = /^ap(?:\+ef61)?:/i;
192
+ /**
193
+ * Finds out whether a document fetched as an ordinary HTTP(S) document
194
+ * claims to be a portable object: its final URL is a portable IRI or an
195
+ * FEP-ef61 compatible identifier, or else its `@id` is. The final URL takes
196
+ * precedence, so a document whose `@id` claims another object fails the
197
+ * identity check against the final URL.
198
+ *
199
+ * @param documentUrl The final URL of the document.
200
+ * @param objectId The `@id` of the parsed document.
201
+ * @returns `undefined` if the document does not claim to be a portable
202
+ * object; `null` if the claim is malformed; otherwise, the claim.
203
+ * @internal Technically exported for generated vocabulary classes, but not
204
+ * part of the public API contract. This is not considered public API for
205
+ * Semantic Versioning decisions.
206
+ */
207
+ function getPortableResponseClaim(documentUrl, objectId) {
208
+ if (PORTABLE_IRI_PATTERN.test(documentUrl)) {
209
+ let id;
210
+ try {
211
+ id = parseIri(documentUrl);
212
+ if (canonicalizePortableUri(documentUrl) !== canonicalizePortableUri(formatIri(id))) return null;
213
+ } catch (error) {
214
+ if (error instanceof TypeError) return null;
215
+ throw error;
216
+ }
217
+ return {
218
+ id,
219
+ inferredGateways: getPortableGatewayCandidates(id)
220
+ };
221
+ }
222
+ for (const url of [documentUrl, objectId]) {
223
+ if (url == null) continue;
224
+ const compatible = parseCompatibleEf61Reference(url);
225
+ if (compatible === null) return null;
226
+ if (compatible != null) return {
227
+ id: compatible.id,
228
+ inferredGateways: [compatible.gateway]
229
+ };
230
+ if (url instanceof URL && isPortableIri(url)) return {
231
+ id: url,
232
+ inferredGateways: getPortableGatewayCandidates(url)
233
+ };
234
+ }
235
+ }
236
+ const unverifiedPortableClaims = /* @__PURE__ */ new WeakSet();
237
+ /**
238
+ * Records that an object fetched as an ordinary HTTP(S) object stands for
239
+ * a portable object if its reference, its final URL, or its `@id` is
240
+ * a portable IRI or an FEP-ef61 compatible identifier. Accessors do not
241
+ * cache such objects in their parents, so that a later call with the
242
+ * `verifyPortableObject` option dereferences and verifies them.
243
+ *
244
+ * @internal Technically exported for generated vocabulary classes, but not
245
+ * part of the public API contract. This is not considered public API for
246
+ * Semantic Versioning decisions.
247
+ */
248
+ function markUnverifiedPortableClaim(object, reference, documentUrl) {
249
+ const objectId = getObjectId(object);
250
+ if (isPortableIri(reference) || isCompatibleEf61Iri(reference) || PORTABLE_IRI_PATTERN.test(documentUrl) || isCompatibleEf61Iri(documentUrl) || objectId != null && (isPortableIri(objectId) || isCompatibleEf61Iri(objectId))) unverifiedPortableClaims.add(object);
251
+ }
252
+ /**
253
+ * Checks whether an object was fetched as an ordinary HTTP(S) object although
254
+ * it stands for a portable object. See {@link markUnverifiedPortableClaim}.
255
+ *
256
+ * @internal Technically exported for generated vocabulary classes, but not
257
+ * part of the public API contract. This is not considered public API for
258
+ * Semantic Versioning decisions.
259
+ */
260
+ function isUnverifiedPortableClaim(object) {
261
+ return unverifiedPortableClaims.has(object);
262
+ }
263
+ /**
264
+ * Rejects a malformed FEP-ef61 compatible identifier found in a reference,
265
+ * the final URL of a fetched document, or its `@id`, when it would be
266
+ * dereferenced as a portable object. It is rejected without a request (or
267
+ * without returning the fetched document), since it cannot be verified as
268
+ * a portable object, and fetching it as an ordinary HTTP(S) object would
269
+ * skip the portable object policy.
126
270
  *
127
271
  * @param url The rejected URL, for the log message.
128
- * @param crossOrigin The `crossOrigin` option of the accessor.
272
+ * @param options The options of the accessor.
129
273
  * @returns Always `null`.
130
- * @throws {Error} If `crossOrigin` is `"throw"`.
274
+ * @throws {Error} If `crossOrigin` is `"throw"` and `suppressError` is not
275
+ * set.
131
276
  * @internal Technically exported for generated vocabulary classes, but not
132
277
  * part of the public API contract. This is not considered public API for
133
278
  * Semantic Versioning decisions.
134
279
  */
135
- function rejectPortableChainReference(url, crossOrigin) {
136
- const message = "Refusing to dereference {url} from a portable object as an ordinary HTTP(S) object, because it stands for an FEP-ef61 portable object that is not verified this way.";
137
- if (crossOrigin === "throw") throw new Error(message.replace("{url}", url));
280
+ function rejectMalformedCompatibleReference(url, options) {
281
+ const message = "Refusing to dereference {url}, because it is a malformed FEP-ef61 compatible identifier, which cannot be verified as a portable object.";
282
+ if (options.crossOrigin === "throw" && !options.suppressError) throw new PortableObjectRejectedError(message.replace("{url}", url));
138
283
  logger.warn(message, { url });
139
284
  return null;
140
285
  }
@@ -242,12 +387,18 @@ function parseGatewayOrigin(gateway) {
242
387
  * The parsed object keeps the loader for its own later dereferences, so
243
388
  * `release()` turns it into a plain pass-through to the underlying loader
244
389
  * once the operation is over.
390
+ *
391
+ * @internal Technically exported for generated vocabulary classes, but not
392
+ * part of the public API contract. This is not considered public API for
393
+ * Semantic Versioning decisions.
245
394
  */
246
395
  function createSnapshotContextLoader(contextLoader) {
396
+ for (let wrapped = snapshotLoaders.get(contextLoader); wrapped?.released; wrapped = snapshotLoaders.get(contextLoader)) contextLoader = wrapped.base;
247
397
  const cache = /* @__PURE__ */ new Map();
248
398
  let released = false;
249
399
  const release = () => {
250
400
  released = true;
401
+ state.released = true;
251
402
  cache.clear();
252
403
  };
253
404
  const loader = async (url, options) => {
@@ -269,11 +420,17 @@ function createSnapshotContextLoader(contextLoader) {
269
420
  }
270
421
  return structuredClone(await promise);
271
422
  };
423
+ const state = {
424
+ base: contextLoader,
425
+ released: false
426
+ };
427
+ snapshotLoaders.set(loader, state);
272
428
  return {
273
429
  loader,
274
430
  release
275
431
  };
276
432
  }
433
+ const snapshotLoaders = /* @__PURE__ */ new WeakMap();
277
434
  /**
278
435
  * The error thrown by {@link dereferencePortableIri} under
279
436
  * `crossOrigin: "throw"` when gateways returned objects but none of them
@@ -300,9 +457,10 @@ var PortableObjectRejection = class {
300
457
  *
301
458
  * Gateways are tried one by one until one of them returns a single-node
302
459
  * document whose `@id`, if any, canonically matches the requested IRI and
303
- * which passes `verifyPortableObject`. When there is no gateway to try, the document
304
- * loader is asked for the portable IRI itself, and its result is validated
305
- * the same way.
460
+ * which passes `verifyPortableObject`. When there is no gateway to try, the
461
+ * document loader is asked for the portable IRI itself, and its result is
462
+ * validated the same way. If the `response` option is given, only that
463
+ * document is validated, and nothing is fetched.
306
464
  *
307
465
  * @returns The parsed object, or `null` if no valid object was retrieved and
308
466
  * `suppressError` is set or the failure was a rejected object
@@ -318,7 +476,10 @@ async function dereferencePortableIri(url, options) {
318
476
  url = parseIri(url);
319
477
  const lookupUrl = formatIri(url);
320
478
  const { span } = options;
321
- const gateways = getPortableGatewayCandidates(url, options.gateways);
479
+ const explicitGateways = options.gateways == null ? void 0 : getPortableGatewayCandidates(url, options.gateways);
480
+ const inferredGateways = options.inferredGateways ?? (explicitGateways == null ? getPortableGatewayCandidates(url) : []);
481
+ const gateways = [...inferredGateways];
482
+ for (const gateway of explicitGateways ?? []) if (!gateways.some((g) => g.href === gateway.href)) gateways.push(gateway);
322
483
  const fail = (error) => {
323
484
  span?.setStatus({
324
485
  code: SpanStatusCode.ERROR,
@@ -339,7 +500,12 @@ async function dereferencePortableIri(url, options) {
339
500
  let expectedId;
340
501
  try {
341
502
  expectedId = canonicalizePortableUri(lookupUrl);
342
- for (const gateway of gateways) requestUrls.push({
503
+ if (options.response != null) requestUrls.push({
504
+ url: options.response.documentUrl,
505
+ gateway: null,
506
+ response: options.response
507
+ });
508
+ else for (const gateway of gateways) requestUrls.push({
343
509
  url: toCompatibleEf61Id(url, gateway).href,
344
510
  gateway
345
511
  });
@@ -354,15 +520,14 @@ async function dereferencePortableIri(url, options) {
354
520
  const contextLoader = snapshot.loader;
355
521
  const attempts = [];
356
522
  const { signal } = options;
357
- const explicitGateways = options.gateways == null ? void 0 : gateways;
358
- const gatewayHints = options.gateways == null && gateways.length > 0 ? gateways : void 0;
523
+ const gatewayHints = explicitGateways == null && inferredGateways.length > 0 ? inferredGateways : void 0;
359
524
  const referrer = buildReferrerChain(options.referrer);
360
525
  try {
361
- for (const { url: requestUrl, gateway } of requestUrls) {
526
+ for (const { url: requestUrl, gateway, response } of requestUrls) {
362
527
  signal?.throwIfAborted();
363
528
  let remoteDocument;
364
529
  try {
365
- remoteDocument = await options.documentLoader(requestUrl, signal == null ? void 0 : { signal });
530
+ remoteDocument = response ?? await options.documentLoader(requestUrl, signal == null ? void 0 : { signal });
366
531
  } catch (error) {
367
532
  signal?.throwIfAborted();
368
533
  logger.debug("Failed to fetch {url} from {requestUrl}: {error}", {
@@ -476,4 +641,4 @@ async function checkPortableObjectId(document, expectedId, contextLoader) {
476
641
  if (actualId !== expectedId) throw new PortableObjectRejection("its @id ({objectId}) does not match the requested portable ID", { objectId: id });
477
642
  }
478
643
  //#endregion
479
- export { PortableObjectRejectedError, copyPortableProvenance, dereferencePortableIri, getPortableGatewayCandidates, isCompatibleEf61Iri, isInPortableChain, isPortableIri, isUnsecuredPortableObject, recordPortableReferrer, rejectPortableChainReference, warnUnverifiableEmbeddedObject };
644
+ export { PortableObjectRejectedError, copyPortableProvenance, createSnapshotContextLoader, dereferencePortableIri, getPortableGatewayCandidates, getPortableResponseClaim, isCompatibleEf61Iri, isInPortableChain, isPortableIri, isPortableMode, isUnsecuredPortableObject, isUnverifiedPortableClaim, markUnverifiedPortableClaim, mustDereferencePortableObject, parseCompatibleEf61Reference, recordPortableReferrer, rejectMalformedCompatibleReference, warnUnverifiableEmbeddedObject };
package/dist/mod.cjs CHANGED
@@ -16,7 +16,7 @@ let _multiformats_base_x = require("@multiformats/base-x");
16
16
  _multiformats_base_x = require_rolldown_runtime.__toESM(_multiformats_base_x, 1);
17
17
  //#region deno.json
18
18
  var name = "@fedify/vocab-runtime";
19
- var version = "2.4.0-dev.2063+366c0362";
19
+ var version = "2.4.0-dev.2065+b09fdd33";
20
20
  //#endregion
21
21
  //#region src/request.ts
22
22
  /**
package/dist/mod.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  /// <reference lib="esnext.temporal" />
2
2
  import { a as DocumentLoaderOptions, c as getDocumentLoader, d as FetchError, f as GetUserAgentOptions, h as logRequest, i as DocumentLoaderFactoryOptions, l as getRemoteDocument, m as getUserAgent, n as DocumentLoader, o as GetDocumentLoaderOptions, p as createActivityPubRequest, r as DocumentLoaderFactory, s as RemoteDocument, t as AuthenticatedDocumentLoaderFactory, u as CreateRequestOptions } from "./docloader-Dr8wEkif.cjs";
3
- import { i as PortableObjectVerifierOptions, n as PortableObjectVerification, r as PortableObjectVerifier, t as PortableObjectReferrer } from "./portable-BykOD45s.cjs";
3
+ import { i as PortableObjectVerifierOptions, n as PortableObjectVerification, r as PortableObjectVerifier, t as PortableObjectReferrer } from "./portable-comBI05Y.cjs";
4
4
  import { TracerProvider } from "@opentelemetry/api";
5
5
 
6
6
  //#region src/contexts.d.ts
package/dist/mod.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /// <reference lib="esnext.temporal" />
2
2
  import { a as DocumentLoaderOptions, c as getDocumentLoader, d as FetchError, f as GetUserAgentOptions, h as logRequest, i as DocumentLoaderFactoryOptions, l as getRemoteDocument, m as getUserAgent, n as DocumentLoader, o as GetDocumentLoaderOptions, p as createActivityPubRequest, r as DocumentLoaderFactory, s as RemoteDocument, t as AuthenticatedDocumentLoaderFactory, u as CreateRequestOptions } from "./docloader-Dr8wEkif.js";
3
- import { i as PortableObjectVerifierOptions, n as PortableObjectVerification, r as PortableObjectVerifier, t as PortableObjectReferrer } from "./portable--NcyA1R_.js";
3
+ import { i as PortableObjectVerifierOptions, n as PortableObjectVerification, r as PortableObjectVerifier, t as PortableObjectReferrer } from "./portable-DH3947GK.js";
4
4
  import { TracerProvider } from "@opentelemetry/api";
5
5
  //#region src/contexts.d.ts
6
6
  declare const preloadedContexts: Record<string, unknown>;
package/dist/mod.js CHANGED
@@ -12,7 +12,7 @@ import { PublicKeyInfo } from "pkijs";
12
12
  import baseX from "@multiformats/base-x";
13
13
  //#region deno.json
14
14
  var name = "@fedify/vocab-runtime";
15
- var version = "2.4.0-dev.2063+366c0362";
15
+ var version = "2.4.0-dev.2065+b09fdd33";
16
16
  //#endregion
17
17
  //#region src/request.ts
18
18
  /**
@@ -42,8 +42,12 @@ interface PortableObjectVerifierOptions {
42
42
  */
43
43
  readonly gateways?: readonly URL[];
44
44
  /**
45
- * The gateways taken from the `@gateway` location hints of the reference,
46
- * in order, when the caller did not pass the `gateways` option.
45
+ * The gateways inferred from the reference, in order, when the caller did
46
+ * not pass the `gateways` option: the `@gateway` location hints of
47
+ * a portable IRI, the gateway named by an FEP-ef61 compatible identifier,
48
+ * or the WebFinger server of a portable actor. They only tell where the
49
+ * document and related objects might be retrieved, and must not be
50
+ * trusted to act for the portable object's DID.
47
51
  * @since 2.4.0
48
52
  */
49
53
  readonly gatewayHints?: readonly URL[];
@@ -42,8 +42,12 @@ interface PortableObjectVerifierOptions {
42
42
  */
43
43
  readonly gateways?: readonly URL[];
44
44
  /**
45
- * The gateways taken from the `@gateway` location hints of the reference,
46
- * in order, when the caller did not pass the `gateways` option.
45
+ * The gateways inferred from the reference, in order, when the caller did
46
+ * not pass the `gateways` option: the `@gateway` location hints of
47
+ * a portable IRI, the gateway named by an FEP-ef61 compatible identifier,
48
+ * or the WebFinger server of a portable actor. They only tell where the
49
+ * document and related objects might be retrieved, and must not be
50
+ * trusted to act for the portable object's DID.
47
51
  * @since 2.4.0
48
52
  */
49
53
  readonly gatewayHints?: readonly URL[];
@@ -1,4 +1,4 @@
1
- import { t as FetchError } from "./request-BT9FBhk6.mjs";
1
+ import { t as FetchError } from "./request-dpFdQKXr.mjs";
2
2
  import { getLogger } from "@logtape/logtape";
3
3
  //#region src/body.ts
4
4
  /** Decoded JSON body limit: 16 MiB. @internal */
@@ -1,4 +1,4 @@
1
- const require_request = require("./request-jWhPNG9V.cjs");
1
+ const require_request = require("./request-BOLanqAd.cjs");
2
2
  let _logtape_logtape = require("@logtape/logtape");
3
3
  //#region src/body.ts
4
4
  /** Decoded JSON body limit: 16 MiB. @internal */
@@ -1,4 +1,4 @@
1
- const require_body = require("./body-CTGqQhNI.cjs");
1
+ const require_body = require("./body-DXHOazEl.cjs");
2
2
  let node_assert_strict = require("node:assert/strict");
3
3
  let node_test = require("node:test");
4
4
  let node_zlib = require("node:zlib");
@@ -1,4 +1,4 @@
1
- import { r as readBoundedText, t as BodyTooLargeError } from "./body-BN5x-zDQ.mjs";
1
+ import { r as readBoundedText, t as BodyTooLargeError } from "./body-CNO0jMV8.mjs";
2
2
  import { deepStrictEqual, ok, rejects } from "node:assert/strict";
3
3
  import { test } from "node:test";
4
4
  import { gunzipSync, gzipSync } from "node:zlib";
@@ -1,5 +1,5 @@
1
1
  require("./url-BqlldzT8.cjs");
2
- require("./docloader-JGSpG-3l.cjs");
2
+ require("./docloader-4Txefi7y.cjs");
3
3
  require("./key-8Qg2SbZi.cjs");
4
4
  require("./multibase-B5Mea7Ip.cjs");
5
5
  require("./digest-3FeH2Y-Q.cjs");
@@ -1,5 +1,5 @@
1
1
  import "./url-DgJM9WJn.mjs";
2
- import "./docloader-BFxwbqMx.mjs";
2
+ import "./docloader-C-nWtUWo.mjs";
3
3
  import "./key-C7MNhlkO.mjs";
4
4
  import "./multibase-BPnF_L4e.mjs";
5
5
  import "./digest-COC7xDiQ.mjs";
@@ -1,5 +1,5 @@
1
- const require_request = require("./request-jWhPNG9V.cjs");
2
- const require_body = require("./body-CTGqQhNI.cjs");
1
+ const require_request = require("./request-BOLanqAd.cjs");
2
+ const require_body = require("./body-DXHOazEl.cjs");
3
3
  const require_contexts = require("./contexts-Ch3v2Cgu.cjs");
4
4
  const require_link = require("./link-DlKm8bEr.cjs");
5
5
  const require_url = require("./url-BqlldzT8.cjs");
@@ -1,5 +1,5 @@
1
- import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-BT9FBhk6.mjs";
2
- import { n as MAX_BODY_SIZE, r as readBoundedText, t as BodyTooLargeError } from "./body-BN5x-zDQ.mjs";
1
+ import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-dpFdQKXr.mjs";
2
+ import { n as MAX_BODY_SIZE, r as readBoundedText, t as BodyTooLargeError } from "./body-CNO0jMV8.mjs";
3
3
  import { t as preloadedContexts } from "./contexts-BBX_qhBL.mjs";
4
4
  import { t as HttpHeaderLink } from "./link-Cevmc87v.mjs";
5
5
  import { t as UrlError, v as validatePublicUrl } from "./url-DgJM9WJn.mjs";
@@ -1,8 +1,8 @@
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-BOLanqAd.cjs");
3
3
  const require_contexts = require("./contexts-Ch3v2Cgu.cjs");
4
4
  const require_url = require("./url-BqlldzT8.cjs");
5
- const require_docloader = require("./docloader-JGSpG-3l.cjs");
5
+ const require_docloader = require("./docloader-4Txefi7y.cjs");
6
6
  let node_test = require("node:test");
7
7
  let node_zlib = require("node:zlib");
8
8
  let node_assert = require("node:assert");