@fedify/vocab-runtime 2.5.0-dev.2440 → 2.5.0-dev.2448

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-runtime",
3
- "version": "2.5.0-dev.2440+635913e8",
3
+ "version": "2.5.0-dev.2448+756933d0",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./src/mod.ts",
@@ -497,6 +497,35 @@ var PortableObjectRejectedError = class extends Error {
497
497
  this.name = "PortableObjectRejectedError";
498
498
  }
499
499
  };
500
+ /**
501
+ * Asks an observer whether to accept a candidate. A refusal, including an
502
+ * error thrown by the observer, is reported to the observer as an `accept`
503
+ * failure.
504
+ * @returns `true` if there is no observer, or it accepted the candidate.
505
+ * @internal Not part of the public API contract.
506
+ */
507
+ async function acceptCandidate(observer, candidate) {
508
+ if (observer?.accept == null) return true;
509
+ let accepted;
510
+ try {
511
+ accepted = await observer.accept(candidate);
512
+ } catch (error) {
513
+ observer.failed?.({
514
+ operation: "accept",
515
+ error
516
+ });
517
+ return false;
518
+ }
519
+ if (!accepted) observer.failed?.({ operation: "accept" });
520
+ return accepted;
521
+ }
522
+ /** Wraps an error that has already been reported to the observer. */
523
+ var ObservedFailure = class {
524
+ error;
525
+ constructor(error) {
526
+ this.error = error;
527
+ }
528
+ };
500
529
  var PortableObjectRejection = class {
501
530
  message;
502
531
  values;
@@ -592,6 +621,10 @@ async function dereferencePortableIri(url, options) {
592
621
  requestUrl,
593
622
  error
594
623
  });
624
+ options.observer?.failed?.({
625
+ operation: "fetch",
626
+ error
627
+ });
595
628
  attempts.push({
596
629
  type: "error",
597
630
  error
@@ -622,11 +655,33 @@ async function dereferencePortableIri(url, options) {
622
655
  throw new PortableObjectRejection("the proof verifier failed: {error}", { error });
623
656
  }
624
657
  if (!result.verified) throw new PortableObjectRejection("it does not satisfy the FEP-ef61 proof policy: {result}", { result });
625
- const object = await options.parse(document, {
626
- contextLoader,
627
- baseUrl: url
628
- });
658
+ let object;
659
+ try {
660
+ object = await options.parse(document, {
661
+ contextLoader,
662
+ baseUrl: url
663
+ });
664
+ } catch (error) {
665
+ options.observer?.failed?.({
666
+ operation: "parse",
667
+ error
668
+ });
669
+ throw new ObservedFailure(error);
670
+ }
629
671
  signal?.throwIfAborted();
672
+ if (!await acceptCandidate(options.observer, {
673
+ document,
674
+ documentUrl: remoteDocument.documentUrl,
675
+ baseUrl: url,
676
+ contextLoader,
677
+ object
678
+ })) {
679
+ attempts.push({
680
+ type: "rejected",
681
+ source: requestUrl
682
+ });
683
+ continue;
684
+ }
630
685
  if (object != null && typeof object === "object") provenances.set(object, {
631
686
  acceptance: result.unsecured === true ? "unsecured" : "verified",
632
687
  ...result.collectionContext === void 0 ? {} : { collectionContext: result.collectionContext },
@@ -642,20 +697,29 @@ async function dereferencePortableIri(url, options) {
642
697
  url: lookupUrl,
643
698
  requestUrl
644
699
  });
700
+ options.observer?.failed?.({
701
+ operation: "verify",
702
+ error: error.values.error
703
+ });
645
704
  attempts.push({
646
705
  type: "rejected",
647
706
  source: requestUrl
648
707
  });
649
708
  continue;
650
709
  }
710
+ const cause = error instanceof ObservedFailure ? error.error : error;
711
+ if (!(error instanceof ObservedFailure)) options.observer?.failed?.({
712
+ operation: "verify",
713
+ error
714
+ });
651
715
  logger.debug("Failed to parse {url} from {requestUrl}: {error}", {
652
716
  url: lookupUrl,
653
717
  requestUrl,
654
- error
718
+ error: cause
655
719
  });
656
720
  attempts.push({
657
721
  type: "error",
658
- error
722
+ error: cause
659
723
  });
660
724
  }
661
725
  }
@@ -701,6 +765,7 @@ async function checkPortableObjectId(document, expectedId, contextLoader) {
701
765
  }
702
766
  //#endregion
703
767
  exports.PortableObjectRejectedError = PortableObjectRejectedError;
768
+ exports.acceptCandidate = acceptCandidate;
704
769
  exports.copyPortableProvenance = copyPortableProvenance;
705
770
  exports.createSnapshotContextLoader = createSnapshotContextLoader;
706
771
  exports.dereferencePortableIri = dereferencePortableIri;
@@ -311,6 +311,58 @@ interface DereferencePortableIriOptions<T> {
311
311
  baseUrl: URL;
312
312
  }) => Promise<T>;
313
313
  span?: Span;
314
+ /**
315
+ * Observes each candidate document, and may refuse a candidate that was
316
+ * otherwise acceptable, in which case the next gateway is tried.
317
+ */
318
+ observer?: DereferenceObserver<T>;
319
+ }
320
+ /**
321
+ * A candidate document that a lookup or a dereference was about to return.
322
+ *
323
+ * @internal Not part of the public API contract.
324
+ */
325
+ interface DereferenceCandidate<T> {
326
+ /** The document as it was retrieved. */
327
+ readonly document: unknown;
328
+ /** The URL the document was finally retrieved from. */
329
+ readonly documentUrl: string;
330
+ /** The `baseUrl` option the document was parsed with. */
331
+ readonly baseUrl: URL;
332
+ /**
333
+ * The context loader the document was parsed with. It keeps serving the
334
+ * same context documents until the {@link DereferenceObserver.accept}
335
+ * call settles.
336
+ */
337
+ readonly contextLoader: DocumentLoader;
338
+ /** The parsed object. */
339
+ readonly object: T;
340
+ }
341
+ /**
342
+ * Observes the candidates and the swallowed failures of a lookup or
343
+ * a dereference.
344
+ *
345
+ * @internal Not part of the public API contract.
346
+ */
347
+ interface DereferenceObserver<T> {
348
+ /**
349
+ * Called for each candidate whose parse succeeded, right before it would be
350
+ * returned. If it resolves to `false`, the candidate is refused, and the
351
+ * next one is tried. An error it throws is treated as a refusal, and is
352
+ * reported to {@link failed}.
353
+ */
354
+ accept?(candidate: DereferenceCandidate<T>): Promise<boolean>;
355
+ /** Called for each failure that does not propagate by itself. */
356
+ failed?(failure: DereferenceFailure): void;
357
+ }
358
+ /**
359
+ * A failure observed by {@link DereferenceObserver.failed}.
360
+ *
361
+ * @internal Not part of the public API contract.
362
+ */
363
+ interface DereferenceFailure {
364
+ readonly operation: "fetch" | "verify" | "parse" | "origin" | "accept";
365
+ readonly error?: unknown;
314
366
  }
315
367
  /**
316
368
  * The error thrown by {@link dereferencePortableIri} under
@@ -323,6 +375,14 @@ declare class PortableObjectRejectedError extends Error {
323
375
  constructor(message: string);
324
376
  }
325
377
  /**
378
+ * Asks an observer whether to accept a candidate. A refusal, including an
379
+ * error thrown by the observer, is reported to the observer as an `accept`
380
+ * failure.
381
+ * @returns `true` if there is no observer, or it accepted the candidate.
382
+ * @internal Not part of the public API contract.
383
+ */
384
+ declare function acceptCandidate<T>(observer: DereferenceObserver<T> | undefined, candidate: DereferenceCandidate<T>): Promise<boolean>;
385
+ /**
326
386
  * Dereferences an FEP-ef61 portable IRI through its gateways.
327
387
  *
328
388
  * Gateways are tried one by one until one of them returns a single-node
@@ -346,4 +406,4 @@ declare function dereferencePortableIri<T extends {
346
406
  id: URL | null;
347
407
  }>(url: URL, options: DereferencePortableIriOptions<T>): Promise<T | null>;
348
408
  //#endregion
349
- export { DereferencePortableIriOptions, PortableObjectRejectedError, PortableReferrerLink, PortableResponseClaim, copyPortableProvenance, createSnapshotContextLoader, dereferencePortableIri, getPortableGatewayCandidates, getPortableResponseClaim, getReferrerGateways, isCompatibleEf61Iri, isInPortableChain, isPortableIri, isPortableMode, isUnsecuredPortableObject, isUnverifiedPortableClaim, markUnverifiedPortableClaim, mustDereferencePortableObject, parseCompatibleEf61Reference, recordPortableReferrer, rejectMalformedCompatibleReference, warnUnverifiableEmbeddedObject };
409
+ export { DereferenceCandidate, DereferenceFailure, DereferenceObserver, DereferencePortableIriOptions, PortableObjectRejectedError, PortableReferrerLink, PortableResponseClaim, acceptCandidate, copyPortableProvenance, createSnapshotContextLoader, dereferencePortableIri, getPortableGatewayCandidates, getPortableResponseClaim, getReferrerGateways, isCompatibleEf61Iri, isInPortableChain, isPortableIri, isPortableMode, isUnsecuredPortableObject, isUnverifiedPortableClaim, markUnverifiedPortableClaim, mustDereferencePortableObject, parseCompatibleEf61Reference, recordPortableReferrer, rejectMalformedCompatibleReference, warnUnverifiableEmbeddedObject };
@@ -311,6 +311,58 @@ interface DereferencePortableIriOptions<T> {
311
311
  baseUrl: URL;
312
312
  }) => Promise<T>;
313
313
  span?: Span;
314
+ /**
315
+ * Observes each candidate document, and may refuse a candidate that was
316
+ * otherwise acceptable, in which case the next gateway is tried.
317
+ */
318
+ observer?: DereferenceObserver<T>;
319
+ }
320
+ /**
321
+ * A candidate document that a lookup or a dereference was about to return.
322
+ *
323
+ * @internal Not part of the public API contract.
324
+ */
325
+ interface DereferenceCandidate<T> {
326
+ /** The document as it was retrieved. */
327
+ readonly document: unknown;
328
+ /** The URL the document was finally retrieved from. */
329
+ readonly documentUrl: string;
330
+ /** The `baseUrl` option the document was parsed with. */
331
+ readonly baseUrl: URL;
332
+ /**
333
+ * The context loader the document was parsed with. It keeps serving the
334
+ * same context documents until the {@link DereferenceObserver.accept}
335
+ * call settles.
336
+ */
337
+ readonly contextLoader: DocumentLoader;
338
+ /** The parsed object. */
339
+ readonly object: T;
340
+ }
341
+ /**
342
+ * Observes the candidates and the swallowed failures of a lookup or
343
+ * a dereference.
344
+ *
345
+ * @internal Not part of the public API contract.
346
+ */
347
+ interface DereferenceObserver<T> {
348
+ /**
349
+ * Called for each candidate whose parse succeeded, right before it would be
350
+ * returned. If it resolves to `false`, the candidate is refused, and the
351
+ * next one is tried. An error it throws is treated as a refusal, and is
352
+ * reported to {@link failed}.
353
+ */
354
+ accept?(candidate: DereferenceCandidate<T>): Promise<boolean>;
355
+ /** Called for each failure that does not propagate by itself. */
356
+ failed?(failure: DereferenceFailure): void;
357
+ }
358
+ /**
359
+ * A failure observed by {@link DereferenceObserver.failed}.
360
+ *
361
+ * @internal Not part of the public API contract.
362
+ */
363
+ interface DereferenceFailure {
364
+ readonly operation: "fetch" | "verify" | "parse" | "origin" | "accept";
365
+ readonly error?: unknown;
314
366
  }
315
367
  /**
316
368
  * The error thrown by {@link dereferencePortableIri} under
@@ -323,6 +375,14 @@ declare class PortableObjectRejectedError extends Error {
323
375
  constructor(message: string);
324
376
  }
325
377
  /**
378
+ * Asks an observer whether to accept a candidate. A refusal, including an
379
+ * error thrown by the observer, is reported to the observer as an `accept`
380
+ * failure.
381
+ * @returns `true` if there is no observer, or it accepted the candidate.
382
+ * @internal Not part of the public API contract.
383
+ */
384
+ declare function acceptCandidate<T>(observer: DereferenceObserver<T> | undefined, candidate: DereferenceCandidate<T>): Promise<boolean>;
385
+ /**
326
386
  * Dereferences an FEP-ef61 portable IRI through its gateways.
327
387
  *
328
388
  * Gateways are tried one by one until one of them returns a single-node
@@ -346,4 +406,4 @@ declare function dereferencePortableIri<T extends {
346
406
  id: URL | null;
347
407
  }>(url: URL, options: DereferencePortableIriOptions<T>): Promise<T | null>;
348
408
  //#endregion
349
- export { DereferencePortableIriOptions, PortableObjectRejectedError, PortableReferrerLink, PortableResponseClaim, copyPortableProvenance, createSnapshotContextLoader, dereferencePortableIri, getPortableGatewayCandidates, getPortableResponseClaim, getReferrerGateways, isCompatibleEf61Iri, isInPortableChain, isPortableIri, isPortableMode, isUnsecuredPortableObject, isUnverifiedPortableClaim, markUnverifiedPortableClaim, mustDereferencePortableObject, parseCompatibleEf61Reference, recordPortableReferrer, rejectMalformedCompatibleReference, warnUnverifiableEmbeddedObject };
409
+ export { DereferenceCandidate, DereferenceFailure, DereferenceObserver, DereferencePortableIriOptions, PortableObjectRejectedError, PortableReferrerLink, PortableResponseClaim, acceptCandidate, copyPortableProvenance, createSnapshotContextLoader, dereferencePortableIri, getPortableGatewayCandidates, getPortableResponseClaim, getReferrerGateways, isCompatibleEf61Iri, isInPortableChain, isPortableIri, isPortableMode, isUnsecuredPortableObject, isUnverifiedPortableClaim, markUnverifiedPortableClaim, mustDereferencePortableObject, parseCompatibleEf61Reference, recordPortableReferrer, rejectMalformedCompatibleReference, warnUnverifiableEmbeddedObject };
@@ -496,6 +496,35 @@ var PortableObjectRejectedError = class extends Error {
496
496
  this.name = "PortableObjectRejectedError";
497
497
  }
498
498
  };
499
+ /**
500
+ * Asks an observer whether to accept a candidate. A refusal, including an
501
+ * error thrown by the observer, is reported to the observer as an `accept`
502
+ * failure.
503
+ * @returns `true` if there is no observer, or it accepted the candidate.
504
+ * @internal Not part of the public API contract.
505
+ */
506
+ async function acceptCandidate(observer, candidate) {
507
+ if (observer?.accept == null) return true;
508
+ let accepted;
509
+ try {
510
+ accepted = await observer.accept(candidate);
511
+ } catch (error) {
512
+ observer.failed?.({
513
+ operation: "accept",
514
+ error
515
+ });
516
+ return false;
517
+ }
518
+ if (!accepted) observer.failed?.({ operation: "accept" });
519
+ return accepted;
520
+ }
521
+ /** Wraps an error that has already been reported to the observer. */
522
+ var ObservedFailure = class {
523
+ error;
524
+ constructor(error) {
525
+ this.error = error;
526
+ }
527
+ };
499
528
  var PortableObjectRejection = class {
500
529
  message;
501
530
  values;
@@ -591,6 +620,10 @@ async function dereferencePortableIri(url, options) {
591
620
  requestUrl,
592
621
  error
593
622
  });
623
+ options.observer?.failed?.({
624
+ operation: "fetch",
625
+ error
626
+ });
594
627
  attempts.push({
595
628
  type: "error",
596
629
  error
@@ -621,11 +654,33 @@ async function dereferencePortableIri(url, options) {
621
654
  throw new PortableObjectRejection("the proof verifier failed: {error}", { error });
622
655
  }
623
656
  if (!result.verified) throw new PortableObjectRejection("it does not satisfy the FEP-ef61 proof policy: {result}", { result });
624
- const object = await options.parse(document, {
625
- contextLoader,
626
- baseUrl: url
627
- });
657
+ let object;
658
+ try {
659
+ object = await options.parse(document, {
660
+ contextLoader,
661
+ baseUrl: url
662
+ });
663
+ } catch (error) {
664
+ options.observer?.failed?.({
665
+ operation: "parse",
666
+ error
667
+ });
668
+ throw new ObservedFailure(error);
669
+ }
628
670
  signal?.throwIfAborted();
671
+ if (!await acceptCandidate(options.observer, {
672
+ document,
673
+ documentUrl: remoteDocument.documentUrl,
674
+ baseUrl: url,
675
+ contextLoader,
676
+ object
677
+ })) {
678
+ attempts.push({
679
+ type: "rejected",
680
+ source: requestUrl
681
+ });
682
+ continue;
683
+ }
629
684
  if (object != null && typeof object === "object") provenances.set(object, {
630
685
  acceptance: result.unsecured === true ? "unsecured" : "verified",
631
686
  ...result.collectionContext === void 0 ? {} : { collectionContext: result.collectionContext },
@@ -641,20 +696,29 @@ async function dereferencePortableIri(url, options) {
641
696
  url: lookupUrl,
642
697
  requestUrl
643
698
  });
699
+ options.observer?.failed?.({
700
+ operation: "verify",
701
+ error: error.values.error
702
+ });
644
703
  attempts.push({
645
704
  type: "rejected",
646
705
  source: requestUrl
647
706
  });
648
707
  continue;
649
708
  }
709
+ const cause = error instanceof ObservedFailure ? error.error : error;
710
+ if (!(error instanceof ObservedFailure)) options.observer?.failed?.({
711
+ operation: "verify",
712
+ error
713
+ });
650
714
  logger.debug("Failed to parse {url} from {requestUrl}: {error}", {
651
715
  url: lookupUrl,
652
716
  requestUrl,
653
- error
717
+ error: cause
654
718
  });
655
719
  attempts.push({
656
720
  type: "error",
657
- error
721
+ error: cause
658
722
  });
659
723
  }
660
724
  }
@@ -699,4 +763,4 @@ async function checkPortableObjectId(document, expectedId, contextLoader) {
699
763
  if (actualId !== expectedId) throw new PortableObjectRejection("its @id ({objectId}) does not match the requested portable ID", { objectId: id });
700
764
  }
701
765
  //#endregion
702
- export { PortableObjectRejectedError, copyPortableProvenance, createSnapshotContextLoader, dereferencePortableIri, getPortableGatewayCandidates, getPortableResponseClaim, getReferrerGateways, isCompatibleEf61Iri, isInPortableChain, isPortableIri, isPortableMode, isUnsecuredPortableObject, isUnverifiedPortableClaim, markUnverifiedPortableClaim, mustDereferencePortableObject, parseCompatibleEf61Reference, recordPortableReferrer, rejectMalformedCompatibleReference, warnUnverifiableEmbeddedObject };
766
+ export { PortableObjectRejectedError, acceptCandidate, copyPortableProvenance, createSnapshotContextLoader, dereferencePortableIri, getPortableGatewayCandidates, getPortableResponseClaim, getReferrerGateways, 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.5.0-dev.2440+635913e8";
19
+ var version = "2.5.0-dev.2448+756933d0";
20
20
  //#endregion
21
21
  //#region src/request.ts
22
22
  /**
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.5.0-dev.2440+635913e8";
15
+ var version = "2.5.0-dev.2448+756933d0";
16
16
  //#endregion
17
17
  //#region src/request.ts
18
18
  /**
@@ -1,4 +1,4 @@
1
- const require_request = require("./request--b4DIWS8.cjs");
1
+ const require_request = require("./request-B77_oZnk.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
- import { t as FetchError } from "./request-BjkWaU70.mjs";
1
+ import { t as FetchError } from "./request-BlLYI4x3.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_body = require("./body-BWDDr6sg.cjs");
1
+ const require_body = require("./body-DP2u2wUX.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 { i as readBoundedText, t as BodyTooLargeError } from "./body-Dczt5Onk.mjs";
1
+ import { i as readBoundedText, t as BodyTooLargeError } from "./body-oV99TVdA.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,12 +1,12 @@
1
- require("./request--b4DIWS8.cjs");
1
+ require("./request-B77_oZnk.cjs");
2
2
  require("./url-BNakuZ8k.cjs");
3
- require("./docloader-HeQpZSID.cjs");
3
+ require("./docloader-CwxZguqN.cjs");
4
4
  require("./key-C-AYkdJJ.cjs");
5
5
  require("./multibase-B5Mea7Ip.cjs");
6
6
  require("./digest-3FeH2Y-Q.cjs");
7
7
  require("./langtag-DASCPgl3.cjs");
8
8
  require("./langstr-h89Kjekh.cjs");
9
- require("./portable-media-u-SdGDpH.cjs");
9
+ require("./portable-media-D7ClSjFO.cjs");
10
10
  let node_test = require("node:test");
11
11
  let node_assert = require("node:assert");
12
12
  //#region src/decimal.ts
@@ -1,12 +1,12 @@
1
- import "./request-BjkWaU70.mjs";
1
+ import "./request-BlLYI4x3.mjs";
2
2
  import "./url-DMxmp7ZG.mjs";
3
- import "./docloader-Cuwex3Gd.mjs";
3
+ import "./docloader-D3mA7aPJ.mjs";
4
4
  import "./key-C2Db_TAJ.mjs";
5
5
  import "./multibase-BPnF_L4e.mjs";
6
6
  import "./digest-COC7xDiQ.mjs";
7
7
  import "./langtag-CD3zHKPN.mjs";
8
8
  import "./langstr-Dw8IBDZl.mjs";
9
- import "./portable-media-DfujyG_f.mjs";
9
+ import "./portable-media-B-oCeotd.mjs";
10
10
  import { test } from "node:test";
11
11
  import { deepStrictEqual, throws } from "node:assert";
12
12
  //#region src/decimal.ts
@@ -1,6 +1,6 @@
1
- const require_request = require("./request--b4DIWS8.cjs");
1
+ const require_request = require("./request-B77_oZnk.cjs");
2
2
  const require_url = require("./url-BNakuZ8k.cjs");
3
- const require_body = require("./body-BWDDr6sg.cjs");
3
+ const require_body = require("./body-DP2u2wUX.cjs");
4
4
  const require_contexts = require("./contexts-C9Lj11SC.cjs");
5
5
  const require_link = require("./link-DlKm8bEr.cjs");
6
6
  let _logtape_logtape = require("@logtape/logtape");
@@ -1,6 +1,6 @@
1
- import { a as logRequest, n as createActivityPubRequest, o as name, s as version, t as FetchError } from "./request-BjkWaU70.mjs";
1
+ import { a as logRequest, n as createActivityPubRequest, o as name, s as version, t as FetchError } from "./request-BlLYI4x3.mjs";
2
2
  import { n as UrlError, x as validatePublicUrl } from "./url-DMxmp7ZG.mjs";
3
- import { i as readBoundedText, n as MAX_BODY_SIZE, r as readBoundedBytes, t as BodyTooLargeError } from "./body-Dczt5Onk.mjs";
3
+ import { i as readBoundedText, n as MAX_BODY_SIZE, r as readBoundedBytes, t as BodyTooLargeError } from "./body-oV99TVdA.mjs";
4
4
  import { t as preloadedContexts } from "./contexts-duRA_dDC.mjs";
5
5
  import { t as HttpHeaderLink } from "./link-Cevmc87v.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
@@ -1,8 +1,8 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
2
- const require_request = require("./request--b4DIWS8.cjs");
2
+ const require_request = require("./request-B77_oZnk.cjs");
3
3
  const require_url = require("./url-BNakuZ8k.cjs");
4
4
  const require_contexts = require("./contexts-C9Lj11SC.cjs");
5
- const require_docloader = require("./docloader-HeQpZSID.cjs");
5
+ const require_docloader = require("./docloader-CwxZguqN.cjs");
6
6
  const require_jsonld = require("./jsonld-CmDhHNTx.cjs");
7
7
  let node_zlib = require("node:zlib");
8
8
  let _logtape_logtape = require("@logtape/logtape");
@@ -1,7 +1,7 @@
1
- import { t as FetchError } from "./request-BjkWaU70.mjs";
1
+ import { t as FetchError } from "./request-BlLYI4x3.mjs";
2
2
  import { n as UrlError } from "./url-DMxmp7ZG.mjs";
3
3
  import { n as cid_v1_default, t as preloadedContexts } from "./contexts-duRA_dDC.mjs";
4
- import { i as withDocumentLoaderTimeout, n as getRemoteDocument, r as resolveDocumentLoaderTimeout, t as getDocumentLoader } from "./docloader-Cuwex3Gd.mjs";
4
+ import { i as withDocumentLoaderTimeout, n as getRemoteDocument, r as resolveDocumentLoaderTimeout, t as getDocumentLoader } from "./docloader-D3mA7aPJ.mjs";
5
5
  import { t as jsonld_default } from "./jsonld-DweIJ07b.mjs";
6
6
  import { gzipSync } from "node:zlib";
7
7
  import { configure, reset } from "@logtape/logtape";
@@ -1,6 +1,6 @@
1
- import { r as getUserAgent } from "./request-BjkWaU70.mjs";
1
+ import { r as getUserAgent } from "./request-BlLYI4x3.mjs";
2
2
  import { g as parseGatewayUrl, x as validatePublicUrl } from "./url-DMxmp7ZG.mjs";
3
- import { r as readBoundedBytes } from "./body-Dczt5Onk.mjs";
3
+ import { r as readBoundedBytes } from "./body-oV99TVdA.mjs";
4
4
  import { a as verifyDigestMultibase, i as parseHashlink, r as parseDigestMultibase } from "./digest-COC7xDiQ.mjs";
5
5
  //#region src/portable-media.ts
6
6
  function resolveTimeout(value, fallback) {
@@ -1,6 +1,6 @@
1
- const require_request = require("./request--b4DIWS8.cjs");
1
+ const require_request = require("./request-B77_oZnk.cjs");
2
2
  const require_url = require("./url-BNakuZ8k.cjs");
3
- const require_body = require("./body-BWDDr6sg.cjs");
3
+ const require_body = require("./body-DP2u2wUX.cjs");
4
4
  const require_digest = require("./digest-3FeH2Y-Q.cjs");
5
5
  //#region src/portable-media.ts
6
6
  function resolveTimeout(value, fallback) {
@@ -1,5 +1,5 @@
1
1
  const require_digest = require("./digest-3FeH2Y-Q.cjs");
2
- const require_portable_media = require("./portable-media-u-SdGDpH.cjs");
2
+ const require_portable_media = require("./portable-media-D7ClSjFO.cjs");
3
3
  let node_assert_strict = require("node:assert/strict");
4
4
  let node_test = require("node:test");
5
5
  let node_http = require("node:http");
@@ -1,5 +1,5 @@
1
1
  import { n as createHashlink, t as computeDigestMultibase } from "./digest-COC7xDiQ.mjs";
2
- import { t as fetchPortableMedia } from "./portable-media-DfujyG_f.mjs";
2
+ import { t as fetchPortableMedia } from "./portable-media-B-oCeotd.mjs";
3
3
  import { deepStrictEqual, equal, rejects } from "node:assert/strict";
4
4
  import { test } from "node:test";
5
5
  import { createServer } from "node:http";
@@ -4,7 +4,7 @@ let node_process = require("node:process");
4
4
  node_process = require_rolldown_runtime.__toESM(node_process, 1);
5
5
  //#region deno.json
6
6
  var name = "@fedify/vocab-runtime";
7
- var version = "2.5.0-dev.2440+635913e8";
7
+ var version = "2.5.0-dev.2448+756933d0";
8
8
  //#endregion
9
9
  //#region src/request.ts
10
10
  /**
@@ -2,7 +2,7 @@ import { n as UrlError } from "./url-DMxmp7ZG.mjs";
2
2
  import process from "node:process";
3
3
  //#region deno.json
4
4
  var name = "@fedify/vocab-runtime";
5
- var version = "2.5.0-dev.2440+635913e8";
5
+ var version = "2.5.0-dev.2448+756933d0";
6
6
  //#endregion
7
7
  //#region src/request.ts
8
8
  /**
@@ -1,5 +1,5 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-emK7D4bc.cjs");
2
- const require_request = require("./request--b4DIWS8.cjs");
2
+ const require_request = require("./request-B77_oZnk.cjs");
3
3
  const require_url = require("./url-BNakuZ8k.cjs");
4
4
  let node_test = require("node:test");
5
5
  let node_process = require("node:process");
@@ -1,4 +1,4 @@
1
- import { i as isTransientFetchError, n as createActivityPubRequest, r as getUserAgent, s as version, t as FetchError } from "./request-BjkWaU70.mjs";
1
+ import { i as isTransientFetchError, n as createActivityPubRequest, r as getUserAgent, s as version, t as FetchError } from "./request-BlLYI4x3.mjs";
2
2
  import { n as UrlError } from "./url-DMxmp7ZG.mjs";
3
3
  import { test } from "node:test";
4
4
  import process from "node:process";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/vocab-runtime",
3
- "version": "2.5.0-dev.2440+635913e8",
3
+ "version": "2.5.0-dev.2448+756933d0",
4
4
  "homepage": "https://fedify.dev/",
5
5
  "repository": {
6
6
  "type": "git",
@@ -705,6 +705,61 @@ export interface DereferencePortableIriOptions<T> {
705
705
  options: { contextLoader: DocumentLoader; baseUrl: URL },
706
706
  ) => Promise<T>;
707
707
  span?: Span;
708
+ /**
709
+ * Observes each candidate document, and may refuse a candidate that was
710
+ * otherwise acceptable, in which case the next gateway is tried.
711
+ */
712
+ observer?: DereferenceObserver<T>;
713
+ }
714
+
715
+ /**
716
+ * A candidate document that a lookup or a dereference was about to return.
717
+ *
718
+ * @internal Not part of the public API contract.
719
+ */
720
+ export interface DereferenceCandidate<T> {
721
+ /** The document as it was retrieved. */
722
+ readonly document: unknown;
723
+ /** The URL the document was finally retrieved from. */
724
+ readonly documentUrl: string;
725
+ /** The `baseUrl` option the document was parsed with. */
726
+ readonly baseUrl: URL;
727
+ /**
728
+ * The context loader the document was parsed with. It keeps serving the
729
+ * same context documents until the {@link DereferenceObserver.accept}
730
+ * call settles.
731
+ */
732
+ readonly contextLoader: DocumentLoader;
733
+ /** The parsed object. */
734
+ readonly object: T;
735
+ }
736
+
737
+ /**
738
+ * Observes the candidates and the swallowed failures of a lookup or
739
+ * a dereference.
740
+ *
741
+ * @internal Not part of the public API contract.
742
+ */
743
+ export interface DereferenceObserver<T> {
744
+ /**
745
+ * Called for each candidate whose parse succeeded, right before it would be
746
+ * returned. If it resolves to `false`, the candidate is refused, and the
747
+ * next one is tried. An error it throws is treated as a refusal, and is
748
+ * reported to {@link failed}.
749
+ */
750
+ accept?(candidate: DereferenceCandidate<T>): Promise<boolean>;
751
+ /** Called for each failure that does not propagate by itself. */
752
+ failed?(failure: DereferenceFailure): void;
753
+ }
754
+
755
+ /**
756
+ * A failure observed by {@link DereferenceObserver.failed}.
757
+ *
758
+ * @internal Not part of the public API contract.
759
+ */
760
+ export interface DereferenceFailure {
761
+ readonly operation: "fetch" | "verify" | "parse" | "origin" | "accept";
762
+ readonly error?: unknown;
708
763
  }
709
764
 
710
765
  /**
@@ -725,6 +780,34 @@ type Attempt =
725
780
  | { readonly type: "rejected"; readonly source: string }
726
781
  | { readonly type: "error"; readonly error: unknown };
727
782
 
783
+ /**
784
+ * Asks an observer whether to accept a candidate. A refusal, including an
785
+ * error thrown by the observer, is reported to the observer as an `accept`
786
+ * failure.
787
+ * @returns `true` if there is no observer, or it accepted the candidate.
788
+ * @internal Not part of the public API contract.
789
+ */
790
+ export async function acceptCandidate<T>(
791
+ observer: DereferenceObserver<T> | undefined,
792
+ candidate: DereferenceCandidate<T>,
793
+ ): Promise<boolean> {
794
+ if (observer?.accept == null) return true;
795
+ let accepted: boolean;
796
+ try {
797
+ accepted = await observer.accept(candidate);
798
+ } catch (error) {
799
+ observer.failed?.({ operation: "accept", error });
800
+ return false;
801
+ }
802
+ if (!accepted) observer.failed?.({ operation: "accept" });
803
+ return accepted;
804
+ }
805
+
806
+ /** Wraps an error that has already been reported to the observer. */
807
+ class ObservedFailure {
808
+ constructor(readonly error: unknown) {}
809
+ }
810
+
728
811
  class PortableObjectRejection {
729
812
  constructor(
730
813
  readonly message: string,
@@ -852,6 +935,7 @@ export async function dereferencePortableIri<T extends { id: URL | null }>(
852
935
  requestUrl,
853
936
  error,
854
937
  });
938
+ options.observer?.failed?.({ operation: "fetch", error });
855
939
  attempts.push({ type: "error", error });
856
940
  continue;
857
941
  }
@@ -887,11 +971,29 @@ export async function dereferencePortableIri<T extends { id: URL | null }>(
887
971
  { result },
888
972
  );
889
973
  }
890
- const object = await options.parse(document, {
891
- contextLoader,
892
- baseUrl: url,
893
- });
974
+ let object: T;
975
+ try {
976
+ object = await options.parse(document, {
977
+ contextLoader,
978
+ baseUrl: url,
979
+ });
980
+ } catch (error) {
981
+ options.observer?.failed?.({ operation: "parse", error });
982
+ throw new ObservedFailure(error);
983
+ }
894
984
  signal?.throwIfAborted();
985
+ if (
986
+ !await acceptCandidate(options.observer, {
987
+ document,
988
+ documentUrl: remoteDocument.documentUrl,
989
+ baseUrl: url,
990
+ contextLoader,
991
+ object,
992
+ })
993
+ ) {
994
+ attempts.push({ type: "rejected", source: requestUrl });
995
+ continue;
996
+ }
895
997
  if (object != null && typeof object === "object") {
896
998
  provenances.set(object, {
897
999
  acceptance: result.unsecured === true ? "unsecured" : "verified",
@@ -913,15 +1015,23 @@ export async function dereferencePortableIri<T extends { id: URL | null }>(
913
1015
  "because " + error.message,
914
1016
  { ...error.values, url: lookupUrl, requestUrl },
915
1017
  );
1018
+ options.observer?.failed?.({
1019
+ operation: "verify",
1020
+ error: error.values.error,
1021
+ });
916
1022
  attempts.push({ type: "rejected", source: requestUrl });
917
1023
  continue;
918
1024
  }
1025
+ const cause = error instanceof ObservedFailure ? error.error : error;
1026
+ if (!(error instanceof ObservedFailure)) {
1027
+ options.observer?.failed?.({ operation: "verify", error });
1028
+ }
919
1029
  logger.debug("Failed to parse {url} from {requestUrl}: {error}", {
920
1030
  url: lookupUrl,
921
1031
  requestUrl,
922
- error,
1032
+ error: cause,
923
1033
  });
924
- attempts.push({ type: "error", error });
1034
+ attempts.push({ type: "error", error: cause });
925
1035
  }
926
1036
  }
927
1037
  } finally {