@fedify/vocab-runtime 2.4.5-dev.2462 → 2.4.5

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 (120) hide show
  1. package/deno.json +5 -2
  2. package/dist/{docloader-BMHljyYb.d.cts → docloader-DOWjnNCz.d.cts} +4 -8
  3. package/dist/{docloader-BMHljyYb.d.ts → docloader-DOWjnNCz.d.ts} +4 -8
  4. package/dist/internal/jsonld-cache.cjs +1 -1
  5. package/dist/internal/jsonld-cache.d.cts +1 -1
  6. package/dist/internal/jsonld-cache.d.ts +1 -1
  7. package/dist/internal/jsonld-cache.js +1 -1
  8. package/dist/internal/portable-dereference.cjs +1 -1
  9. package/dist/internal/portable-dereference.d.cts +2 -2
  10. package/dist/internal/portable-dereference.d.ts +2 -2
  11. package/dist/internal/portable-dereference.js +1 -1
  12. package/dist/internal.cjs +6 -0
  13. package/dist/internal.d.cts +108 -0
  14. package/dist/internal.d.ts +108 -0
  15. package/dist/internal.js +3 -0
  16. package/dist/mod.cjs +104 -38
  17. package/dist/mod.d.cts +3 -336
  18. package/dist/mod.d.ts +4 -336
  19. package/dist/mod.js +104 -38
  20. package/dist/{portable-6Aj-7dZJ.d.cts → portable-CTnZWX7J.d.cts} +1 -1
  21. package/dist/{portable-nkTiqH0w.d.ts → portable-Cq-rhQgY.d.ts} +1 -1
  22. package/dist/public-fetch-Dvfvawei.cjs +527 -0
  23. package/dist/public-fetch-ziZ39L5I.js +510 -0
  24. package/dist/tests/{body-Dlnsb-QX.cjs → body-BO79kuv1.cjs} +1 -1
  25. package/dist/tests/{body-BXrFIcFa.mjs → body-CUzCsDZ4.mjs} +1 -1
  26. package/dist/tests/body.test.cjs +1 -1
  27. package/dist/tests/body.test.mjs +1 -1
  28. package/dist/tests/decimal.test.cjs +3 -3
  29. package/dist/tests/decimal.test.mjs +3 -3
  30. package/dist/tests/{docloader-c9BQWaF1.cjs → docloader-B79_YgeY.cjs} +111 -39
  31. package/dist/tests/{docloader-DsuF8YDf.mjs → docloader-C5ZHGEfO.mjs} +106 -40
  32. package/dist/tests/docloader.test.cjs +531 -1293
  33. package/dist/tests/docloader.test.mjs +472 -1252
  34. package/dist/tests/esm-Ds-eHTfY.mjs +1229 -0
  35. package/dist/tests/esm-Q2tdh5f3.cjs +1215 -0
  36. package/dist/tests/internal/portable-dereference.test.cjs +3 -3
  37. package/dist/tests/internal/portable-dereference.test.mjs +3 -3
  38. package/dist/tests/{jsonld-cache-BEXiiM-V.cjs → jsonld-cache-DKIWKxEX.cjs} +1 -1
  39. package/dist/tests/{jsonld-cache-DFnWsLuZ.mjs → jsonld-cache-KH40z9_7.mjs} +1 -1
  40. package/dist/tests/jsonld-cache.test.cjs +2 -2
  41. package/dist/tests/jsonld-cache.test.mjs +2 -2
  42. package/dist/tests/node-BvS3AhZ2.cjs +89 -0
  43. package/dist/tests/node-Cg4pRuYy.mjs +84 -0
  44. package/dist/tests/pin-grant-CPrpk1l4.cjs +70 -0
  45. package/dist/tests/pin-grant-H5XhM1Xs.mjs +53 -0
  46. package/dist/tests/pin-grant.test.cjs +87 -0
  47. package/dist/tests/pin-grant.test.d.cts +1 -0
  48. package/dist/tests/pin-grant.test.d.mts +1 -0
  49. package/dist/tests/pin-grant.test.mjs +88 -0
  50. package/dist/tests/pinners/bun.test.cjs +166 -0
  51. package/dist/tests/pinners/bun.test.d.cts +1 -0
  52. package/dist/tests/pinners/bun.test.d.mts +1 -0
  53. package/dist/tests/pinners/bun.test.mjs +167 -0
  54. package/dist/tests/pinners/node.test.cjs +269 -0
  55. package/dist/tests/pinners/node.test.d.cts +1 -0
  56. package/dist/tests/pinners/node.test.d.mts +1 -0
  57. package/dist/tests/pinners/node.test.mjs +268 -0
  58. package/dist/tests/pinners/pinners.test.cjs +196 -0
  59. package/dist/tests/pinners/pinners.test.d.cts +1 -0
  60. package/dist/tests/pinners/pinners.test.d.mts +1 -0
  61. package/dist/tests/pinners/pinners.test.mjs +197 -0
  62. package/dist/tests/pinners/testdata/node-child.cjs +135 -0
  63. package/dist/tests/pinners/testdata/node-child.d.cts +1 -0
  64. package/dist/tests/pinners/testdata/node-child.d.mts +1 -0
  65. package/dist/tests/pinners/testdata/node-child.mjs +134 -0
  66. package/dist/tests/pinners-CJi9ZkYs.cjs +220 -0
  67. package/dist/tests/pinners-zSofbiy2.mjs +203 -0
  68. package/dist/tests/{portable-dereference-DmTYDy2p.mjs → portable-dereference-BHRE5Pkm.mjs} +2 -2
  69. package/dist/tests/{portable-dereference-qTLMqe2_.cjs → portable-dereference-mRHrC7PI.cjs} +2 -2
  70. package/dist/tests/{portable-media-M7idvaUq.mjs → portable-media-BpVpe1gj.mjs} +3 -3
  71. package/dist/tests/{portable-media-CS4czG6X.cjs → portable-media-C3yY0W5E.cjs} +3 -3
  72. package/dist/tests/portable-media.test.cjs +1 -1
  73. package/dist/tests/portable-media.test.mjs +1 -1
  74. package/dist/tests/portable-workers.test.cjs +2 -2
  75. package/dist/tests/portable-workers.test.mjs +2 -2
  76. package/dist/tests/public-fetch-DNkPoks1.mjs +170 -0
  77. package/dist/tests/public-fetch-U822jbKB.cjs +186 -0
  78. package/dist/tests/public-fetch.test.cjs +1073 -0
  79. package/dist/tests/public-fetch.test.d.cts +1 -0
  80. package/dist/tests/public-fetch.test.d.mts +1 -0
  81. package/dist/tests/public-fetch.test.mjs +1074 -0
  82. package/dist/tests/{request-C336eT6A.mjs → request-3AP-HBJ6.mjs} +1 -1
  83. package/dist/tests/{request-CgTgsBUA.cjs → request-BhopJTZd.cjs} +1 -1
  84. package/dist/tests/request.test.cjs +1 -1
  85. package/dist/tests/request.test.mjs +1 -1
  86. package/dist/tests/support-BmzI3dRJ.cjs +363 -0
  87. package/dist/tests/support-CiHDVJgh.mjs +272 -0
  88. package/dist/tests/{url-BNakuZ8k.cjs → url-Bdj-rTcC.cjs} +88 -12
  89. package/dist/tests/{url-DMxmp7ZG.mjs → url-COj5Q8mb.mjs} +65 -13
  90. package/dist/tests/url.test.cjs +4 -1
  91. package/dist/tests/url.test.mjs +4 -1
  92. package/dist/url-4EE56sNP.d.cts +352 -0
  93. package/dist/url-4EE56sNP.d.ts +352 -0
  94. package/dist/{url-DrGTR8yv.cjs → url-DIKZnaJv.cjs} +88 -12
  95. package/dist/{url-Dzyp-NsC.js → url-mWveR5GA.js} +65 -13
  96. package/package.json +14 -3
  97. package/src/docloader.test.ts +696 -48
  98. package/src/docloader.ts +186 -75
  99. package/src/internal.ts +13 -0
  100. package/src/pin-grant.test.ts +118 -0
  101. package/src/pin-grant.ts +99 -0
  102. package/src/pinners/bun.test.ts +278 -0
  103. package/src/pinners/bun.ts +223 -0
  104. package/src/pinners/mod.ts +59 -0
  105. package/src/pinners/node.test.ts +415 -0
  106. package/src/pinners/node.ts +144 -0
  107. package/src/pinners/pinners.test.ts +316 -0
  108. package/src/pinners/testdata/node-child.ts +147 -0
  109. package/src/pinners/testdata/proxy-child.ts +82 -0
  110. package/src/pinners/testdata/support.ts +343 -0
  111. package/src/pinners/testdata/tls-cert.pem +12 -0
  112. package/src/pinners/testdata/tls-child.ts +88 -0
  113. package/src/pinners/testdata/tls-key.pem +5 -0
  114. package/src/pinners/types.ts +48 -0
  115. package/src/public-fetch.test.ts +1286 -0
  116. package/src/public-fetch.ts +338 -0
  117. package/src/testdata/public-fetch-deno-worker.ts +74 -0
  118. package/src/url.test.ts +11 -0
  119. package/src/url.ts +113 -22
  120. package/tsdown.config.ts +7 -2
package/deno.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@fedify/vocab-runtime",
3
- "version": "2.4.5-dev.2462+46a3f2c5",
3
+ "version": "2.4.5",
4
4
  "license": "MIT",
5
5
  "exports": {
6
6
  ".": "./src/mod.ts",
7
7
  "./internal/jsonld-cache": "./src/internal/jsonld-cache.ts",
8
8
  "./internal/portable-dereference": "./src/internal/portable-dereference.ts",
9
9
  "./internal/signed-representation": "./src/internal/signed-representation.ts",
10
+ "./internal": "./src/internal.ts",
10
11
  "./jsonld": "./src/jsonld.ts",
11
12
  "./temporal": "./src/temporal.ts"
12
13
  },
@@ -22,7 +23,8 @@
22
23
  "byte-encodings": "npm:byte-encodings@^1.0.11",
23
24
  "fetch-mock": "npm:fetch-mock@^12.5.4",
24
25
  "jsonld": "npm:jsonld@^9.0.0",
25
- "pkijs": "npm:pkijs@^3.2.5"
26
+ "pkijs": "npm:pkijs@^3.2.5",
27
+ "undici": "npm:undici@^7.30.1"
26
28
  },
27
29
  "exclude": [
28
30
  "dist",
@@ -31,6 +33,7 @@
31
33
  "publish": {
32
34
  "exclude": [
33
35
  "**/*.test.ts",
36
+ "src/**/testdata/",
34
37
  "tsdown.config.ts"
35
38
  ]
36
39
  },
@@ -188,7 +188,9 @@ type AuthenticatedDocumentLoaderFactory = (identity: {
188
188
  * Gets a {@link RemoteDocument} from the given response.
189
189
  * @param url The URL of the document to load.
190
190
  * @param response The response to get the document from.
191
- * @param fetch The function to fetch the document.
191
+ * @param fetch The function to fetch the document. It is called with the URL
192
+ * of an alternate document, if any, after the response body has
193
+ * been either read or its cancellation has been started.
192
194
  * @param options The options for loading the document.
193
195
  * @returns The loaded remote document.
194
196
  * @throws {FetchError} If the response is not OK.
@@ -259,12 +261,6 @@ interface GetDocumentLoaderOptions extends DocumentLoaderFactoryOptions {
259
261
  * @returns The document loader.
260
262
  * @since 1.3.0
261
263
  */
262
- declare function getDocumentLoader({
263
- allowPrivateAddress,
264
- maxRedirection,
265
- skipPreloadedContexts,
266
- timeout,
267
- userAgent
268
- }?: GetDocumentLoaderOptions): DocumentLoader;
264
+ declare function getDocumentLoader(options?: GetDocumentLoaderOptions): DocumentLoader;
269
265
  //#endregion
270
266
  export { logRequest as _, DocumentLoaderOptions as a, getDocumentLoader as c, withDocumentLoaderTimeout as d, CreateRequestOptions as f, getUserAgent as g, createActivityPubRequest as h, DocumentLoaderFactoryOptions as i, getRemoteDocument as l, GetUserAgentOptions as m, DocumentLoader as n, GetDocumentLoaderOptions as o, FetchError as p, DocumentLoaderFactory as r, RemoteDocument as s, AuthenticatedDocumentLoaderFactory as t, resolveDocumentLoaderTimeout as u };
@@ -188,7 +188,9 @@ type AuthenticatedDocumentLoaderFactory = (identity: {
188
188
  * Gets a {@link RemoteDocument} from the given response.
189
189
  * @param url The URL of the document to load.
190
190
  * @param response The response to get the document from.
191
- * @param fetch The function to fetch the document.
191
+ * @param fetch The function to fetch the document. It is called with the URL
192
+ * of an alternate document, if any, after the response body has
193
+ * been either read or its cancellation has been started.
192
194
  * @param options The options for loading the document.
193
195
  * @returns The loaded remote document.
194
196
  * @throws {FetchError} If the response is not OK.
@@ -259,12 +261,6 @@ interface GetDocumentLoaderOptions extends DocumentLoaderFactoryOptions {
259
261
  * @returns The document loader.
260
262
  * @since 1.3.0
261
263
  */
262
- declare function getDocumentLoader({
263
- allowPrivateAddress,
264
- maxRedirection,
265
- skipPreloadedContexts,
266
- timeout,
267
- userAgent
268
- }?: GetDocumentLoaderOptions): DocumentLoader;
264
+ declare function getDocumentLoader(options?: GetDocumentLoaderOptions): DocumentLoader;
269
265
  //#endregion
270
266
  export { logRequest as _, DocumentLoaderOptions as a, getDocumentLoader as c, withDocumentLoaderTimeout as d, CreateRequestOptions as f, getUserAgent as g, createActivityPubRequest as h, DocumentLoaderFactoryOptions as i, getRemoteDocument as l, GetUserAgentOptions as m, DocumentLoader as n, GetDocumentLoaderOptions as o, FetchError as p, DocumentLoaderFactory as r, RemoteDocument as s, AuthenticatedDocumentLoaderFactory as t, resolveDocumentLoaderTimeout as u };
@@ -1,6 +1,6 @@
1
1
 
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const require_url = require("../url-DrGTR8yv.cjs");
3
+ const require_url = require("../url-DIKZnaJv.cjs");
4
4
  const require_jsonld = require("../jsonld.cjs");
5
5
  //#region src/internal/jsonld-cache.ts
6
6
  /**
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { n as DocumentLoader } from "../docloader-BMHljyYb.cjs";
2
+ import { n as DocumentLoader } from "../docloader-DOWjnNCz.cjs";
3
3
 
4
4
  //#region src/internal/jsonld-cache.d.ts
5
5
  /**
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { n as DocumentLoader } from "../docloader-BMHljyYb.js";
2
+ import { n as DocumentLoader } from "../docloader-DOWjnNCz.js";
3
3
 
4
4
  //#region src/internal/jsonld-cache.d.ts
5
5
  /**
@@ -1,5 +1,5 @@
1
1
 
2
- import { _ as parseIri, c as getFe34Origin, d as haveSameIriOrigin, o as formatIri, s as fromCompatibleEf61Id, u as haveSameFe34Origin } from "../url-Dzyp-NsC.js";
2
+ import { b as parseIri, c as fromCompatibleEf61Id, d as haveSameFe34Origin, f as haveSameIriOrigin, l as getFe34Origin, s as formatIri } from "../url-mWveR5GA.js";
3
3
  import jsonld_default from "../jsonld.js";
4
4
  //#region src/internal/jsonld-cache.ts
5
5
  /**
@@ -1,7 +1,7 @@
1
1
 
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const require_contexts = require("../contexts-B09jb9Pi.cjs");
4
- const require_url = require("../url-DrGTR8yv.cjs");
4
+ const require_url = require("../url-DIKZnaJv.cjs");
5
5
  const require_jsonld = require("../jsonld.cjs");
6
6
  const require_internal_jsonld_cache = require("./jsonld-cache.cjs");
7
7
  let _logtape_logtape = require("@logtape/logtape");
@@ -1,6 +1,6 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { n as DocumentLoader, s as RemoteDocument } from "../docloader-BMHljyYb.cjs";
3
- import { r as PortableObjectVerifier } from "../portable-6Aj-7dZJ.cjs";
2
+ import { n as DocumentLoader, s as RemoteDocument } from "../docloader-DOWjnNCz.cjs";
3
+ import { r as PortableObjectVerifier } from "../portable-CTnZWX7J.cjs";
4
4
  import { Span, TracerProvider } from "@opentelemetry/api";
5
5
 
6
6
  //#region src/internal/portable-dereference.d.ts
@@ -1,6 +1,6 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { n as DocumentLoader, s as RemoteDocument } from "../docloader-BMHljyYb.js";
3
- import { r as PortableObjectVerifier } from "../portable-nkTiqH0w.js";
2
+ import { n as DocumentLoader, s as RemoteDocument } from "../docloader-DOWjnNCz.js";
3
+ import { r as PortableObjectVerifier } from "../portable-Cq-rhQgY.js";
4
4
  import { Span, TracerProvider } from "@opentelemetry/api";
5
5
 
6
6
  //#region src/internal/portable-dereference.d.ts
@@ -1,6 +1,6 @@
1
1
 
2
2
  import { t as preloadedContexts } from "../contexts-DWrj4AGR.js";
3
- import { _ as parseIri, h as parseGatewayOrigin, i as canonicalizePortableUri, o as formatIri, s as fromCompatibleEf61Id, t as GATEWAY_HINT_PARAMETER, u as haveSameFe34Origin, y as toCompatibleEf61Id } from "../url-Dzyp-NsC.js";
3
+ import { C as toCompatibleEf61Id, a as canonicalizePortableUri, b as parseIri, c as fromCompatibleEf61Id, d as haveSameFe34Origin, s as formatIri, t as GATEWAY_HINT_PARAMETER, v as parseGatewayOrigin } from "../url-mWveR5GA.js";
4
4
  import jsonld_default from "../jsonld.js";
5
5
  import { createScopedContextLoader, registerDocumentLoaderWrapper, unwrapReleasedDocumentLoader } from "./jsonld-cache.js";
6
6
  import { getLogger } from "@logtape/logtape";
@@ -0,0 +1,6 @@
1
+
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const require_public_fetch = require("./public-fetch-Dvfvawei.cjs");
4
+ exports.openDirectFetch = require_public_fetch.openDirectFetch;
5
+ exports.openPublicFetch = require_public_fetch.openPublicFetch;
6
+ exports.withPublicFetch = require_public_fetch.withPublicFetch;
@@ -0,0 +1,108 @@
1
+ /// <reference lib="esnext.temporal" />
2
+ import { t as ResolvedAddress } from "./url-4EE56sNP.cjs";
3
+
4
+ //#region src/public-fetch.d.ts
5
+ /**
6
+ * Options for {@link openPublicFetch} and {@link withPublicFetch}.
7
+ */
8
+ interface PublicFetchOptions {
9
+ /**
10
+ * An `AbortSignal` for cancelling the DNS lookup.
11
+ */
12
+ readonly signal?: AbortSignal;
13
+ /**
14
+ * A function that resolves all addresses of a hostname, used instead of
15
+ * `node:dns.lookup()`. The addresses it returns are validated in the same
16
+ * way as the default lookup's.
17
+ */
18
+ readonly lookup?: (hostname: string) => Promise<readonly ResolvedAddress[]>;
19
+ }
20
+ /**
21
+ * A session for fetching resources from a single origin. Its `fetch()`
22
+ * connects only to the addresses validated when the session was opened, on
23
+ * runtimes that support it.
24
+ */
25
+ interface PublicFetchSession {
26
+ /**
27
+ * A copy of the URL the session was opened for.
28
+ */
29
+ readonly url: URL;
30
+ /**
31
+ * Fetches a resource from the session's origin. Redirects are never
32
+ * followed; the redirect mode is always `"manual"`.
33
+ * @throws {TypeError} If the input's origin differs from the session's, or
34
+ * if the session is closed.
35
+ */
36
+ fetch(input: Request | URL | string, init?: RequestInit): Promise<Response>;
37
+ /**
38
+ * Closes the session. The cancellation of response bodies that have been
39
+ * neither consumed nor cancelled is started (without waiting for it), and
40
+ * the session can no longer fetch. Calling this more than once has no
41
+ * effect.
42
+ */
43
+ close(): Promise<void>;
44
+ }
45
+ /**
46
+ * The functions created by {@link createPublicFetch}.
47
+ */
48
+ interface PublicFetch {
49
+ /**
50
+ * Validates the URL, resolves its hostname, checks that every resolved
51
+ * address is public, and opens a session that connects only to those
52
+ * addresses.
53
+ * @param url The URL to open a session for.
54
+ * @param options The options.
55
+ * @returns The session. It must be closed.
56
+ * @throws {UrlError} If the URL is not allowed, or its hostname cannot be
57
+ * resolved (with `reason: "dns"`).
58
+ */
59
+ openPublicFetch(url: URL | string, options?: PublicFetchOptions): Promise<PublicFetchSession>;
60
+ /**
61
+ * Opens a session with {@link PublicFetch.openPublicFetch}, runs the given
62
+ * function with its `fetch()`, and closes the session afterwards.
63
+ * @param url The URL to open a session for.
64
+ * @param fn The function to run. It must not return a `Response`, since
65
+ * the response bodies are cancelled when the session is closed.
66
+ * @param options The options.
67
+ * @returns The value returned by `fn`.
68
+ * @throws {UrlError} If the URL is not allowed.
69
+ * @throws {TypeError} If `fn` returns a `Response`.
70
+ */
71
+ withPublicFetch<T>(url: URL | string, fn: (fetch: PublicFetchSession["fetch"]) => Promise<T>, options?: PublicFetchOptions): Promise<T>;
72
+ }
73
+ /**
74
+ * Validates the URL, resolves its hostname, checks that every resolved
75
+ * address is public, and opens a session that connects only to those
76
+ * addresses. Connections are pinned on Node.js and Bun. On other runtimes,
77
+ * the session uses the global `fetch()` after the check.
78
+ * @param url The URL to open a session for.
79
+ * @param options The options.
80
+ * @returns The session. It must be closed.
81
+ * @throws {UrlError} If the URL is not allowed, or its hostname cannot be
82
+ * resolved (with `reason: "dns"`).
83
+ */
84
+ declare const openPublicFetch: PublicFetch["openPublicFetch"];
85
+ /**
86
+ * Opens a session with {@link openPublicFetch}, runs the given function with
87
+ * its `fetch()`, and closes the session afterwards.
88
+ * @param url The URL to open a session for.
89
+ * @param fn The function to run. It must not return a `Response`, since
90
+ * the response bodies are cancelled when the session is closed.
91
+ * @param options The options.
92
+ * @returns The value returned by `fn`.
93
+ * @throws {UrlError} If the URL is not allowed.
94
+ * @throws {TypeError} If `fn` returns a `Response`.
95
+ */
96
+ declare const withPublicFetch: PublicFetch["withPublicFetch"];
97
+ /**
98
+ * Opens a session that neither validates nor pins addresses, for callers that
99
+ * allow private addresses. It has the same rules as {@link openPublicFetch}
100
+ * sessions otherwise: requests are limited to the URL's origin, redirects are
101
+ * never followed, and unconsumed response bodies are cancelled on close.
102
+ * It grants nothing beyond calling the global `fetch()` directly.
103
+ * @param url The URL to open a session for.
104
+ * @returns The session. It must be closed.
105
+ */
106
+ declare function openDirectFetch(url: URL | string): Promise<PublicFetchSession>;
107
+ //#endregion
108
+ export { type PublicFetchOptions, type PublicFetchSession, openDirectFetch, openPublicFetch, withPublicFetch };
@@ -0,0 +1,108 @@
1
+ /// <reference lib="esnext.temporal" />
2
+ import { t as ResolvedAddress } from "./url-4EE56sNP.js";
3
+
4
+ //#region src/public-fetch.d.ts
5
+ /**
6
+ * Options for {@link openPublicFetch} and {@link withPublicFetch}.
7
+ */
8
+ interface PublicFetchOptions {
9
+ /**
10
+ * An `AbortSignal` for cancelling the DNS lookup.
11
+ */
12
+ readonly signal?: AbortSignal;
13
+ /**
14
+ * A function that resolves all addresses of a hostname, used instead of
15
+ * `node:dns.lookup()`. The addresses it returns are validated in the same
16
+ * way as the default lookup's.
17
+ */
18
+ readonly lookup?: (hostname: string) => Promise<readonly ResolvedAddress[]>;
19
+ }
20
+ /**
21
+ * A session for fetching resources from a single origin. Its `fetch()`
22
+ * connects only to the addresses validated when the session was opened, on
23
+ * runtimes that support it.
24
+ */
25
+ interface PublicFetchSession {
26
+ /**
27
+ * A copy of the URL the session was opened for.
28
+ */
29
+ readonly url: URL;
30
+ /**
31
+ * Fetches a resource from the session's origin. Redirects are never
32
+ * followed; the redirect mode is always `"manual"`.
33
+ * @throws {TypeError} If the input's origin differs from the session's, or
34
+ * if the session is closed.
35
+ */
36
+ fetch(input: Request | URL | string, init?: RequestInit): Promise<Response>;
37
+ /**
38
+ * Closes the session. The cancellation of response bodies that have been
39
+ * neither consumed nor cancelled is started (without waiting for it), and
40
+ * the session can no longer fetch. Calling this more than once has no
41
+ * effect.
42
+ */
43
+ close(): Promise<void>;
44
+ }
45
+ /**
46
+ * The functions created by {@link createPublicFetch}.
47
+ */
48
+ interface PublicFetch {
49
+ /**
50
+ * Validates the URL, resolves its hostname, checks that every resolved
51
+ * address is public, and opens a session that connects only to those
52
+ * addresses.
53
+ * @param url The URL to open a session for.
54
+ * @param options The options.
55
+ * @returns The session. It must be closed.
56
+ * @throws {UrlError} If the URL is not allowed, or its hostname cannot be
57
+ * resolved (with `reason: "dns"`).
58
+ */
59
+ openPublicFetch(url: URL | string, options?: PublicFetchOptions): Promise<PublicFetchSession>;
60
+ /**
61
+ * Opens a session with {@link PublicFetch.openPublicFetch}, runs the given
62
+ * function with its `fetch()`, and closes the session afterwards.
63
+ * @param url The URL to open a session for.
64
+ * @param fn The function to run. It must not return a `Response`, since
65
+ * the response bodies are cancelled when the session is closed.
66
+ * @param options The options.
67
+ * @returns The value returned by `fn`.
68
+ * @throws {UrlError} If the URL is not allowed.
69
+ * @throws {TypeError} If `fn` returns a `Response`.
70
+ */
71
+ withPublicFetch<T>(url: URL | string, fn: (fetch: PublicFetchSession["fetch"]) => Promise<T>, options?: PublicFetchOptions): Promise<T>;
72
+ }
73
+ /**
74
+ * Validates the URL, resolves its hostname, checks that every resolved
75
+ * address is public, and opens a session that connects only to those
76
+ * addresses. Connections are pinned on Node.js and Bun. On other runtimes,
77
+ * the session uses the global `fetch()` after the check.
78
+ * @param url The URL to open a session for.
79
+ * @param options The options.
80
+ * @returns The session. It must be closed.
81
+ * @throws {UrlError} If the URL is not allowed, or its hostname cannot be
82
+ * resolved (with `reason: "dns"`).
83
+ */
84
+ declare const openPublicFetch: PublicFetch["openPublicFetch"];
85
+ /**
86
+ * Opens a session with {@link openPublicFetch}, runs the given function with
87
+ * its `fetch()`, and closes the session afterwards.
88
+ * @param url The URL to open a session for.
89
+ * @param fn The function to run. It must not return a `Response`, since
90
+ * the response bodies are cancelled when the session is closed.
91
+ * @param options The options.
92
+ * @returns The value returned by `fn`.
93
+ * @throws {UrlError} If the URL is not allowed.
94
+ * @throws {TypeError} If `fn` returns a `Response`.
95
+ */
96
+ declare const withPublicFetch: PublicFetch["withPublicFetch"];
97
+ /**
98
+ * Opens a session that neither validates nor pins addresses, for callers that
99
+ * allow private addresses. It has the same rules as {@link openPublicFetch}
100
+ * sessions otherwise: requests are limited to the URL's origin, redirects are
101
+ * never followed, and unconsumed response bodies are cancelled on close.
102
+ * It grants nothing beyond calling the global `fetch()` directly.
103
+ * @param url The URL to open a session for.
104
+ * @returns The session. It must be closed.
105
+ */
106
+ declare function openDirectFetch(url: URL | string): Promise<PublicFetchSession>;
107
+ //#endregion
108
+ export { type PublicFetchOptions, type PublicFetchSession, openDirectFetch, openPublicFetch, withPublicFetch };
@@ -0,0 +1,3 @@
1
+
2
+ import { n as openPublicFetch, r as withPublicFetch, t as openDirectFetch } from "./public-fetch-ziZ39L5I.js";
3
+ export { openDirectFetch, openPublicFetch, withPublicFetch };
package/dist/mod.cjs CHANGED
@@ -2,7 +2,8 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const require_rolldown_runtime = require("./rolldown-runtime-B7lfambq.cjs");
4
4
  const require_contexts = require("./contexts-B09jb9Pi.cjs");
5
- const require_url = require("./url-DrGTR8yv.cjs");
5
+ const require_public_fetch = require("./public-fetch-Dvfvawei.cjs");
6
+ const require_url = require("./url-DIKZnaJv.cjs");
6
7
  let _logtape_logtape = require("@logtape/logtape");
7
8
  let _opentelemetry_api = require("@opentelemetry/api");
8
9
  let node_process = require("node:process");
@@ -16,7 +17,7 @@ let _multiformats_base_x = require("@multiformats/base-x");
16
17
  _multiformats_base_x = require_rolldown_runtime.__toESM(_multiformats_base_x, 1);
17
18
  //#region deno.json
18
19
  var name = "@fedify/vocab-runtime";
19
- var version = "2.4.5-dev.2462+46a3f2c5";
20
+ var version = "2.4.5";
20
21
  //#endregion
21
22
  //#region src/request.ts
22
23
  /**
@@ -441,7 +442,7 @@ async function bufferErrorResponse(response, url, signal) {
441
442
  try {
442
443
  await readBoundedBytes(response, MAX_ERROR_BODY_SIZE, url);
443
444
  } catch (error) {
444
- await clone.body?.cancel().catch(() => {});
445
+ clone.body?.cancel().catch(() => {});
445
446
  if (signal?.aborted) throw error;
446
447
  logger.debug("Failed to read the error response body from {url}: {error}", {
447
448
  url,
@@ -473,14 +474,48 @@ async function bufferErrorResponse(response, url, signal) {
473
474
  * Gets a {@link RemoteDocument} from the given response.
474
475
  * @param url The URL of the document to load.
475
476
  * @param response The response to get the document from.
476
- * @param fetch The function to fetch the document.
477
+ * @param fetch The function to fetch the document. It is called with the URL
478
+ * of an alternate document, if any, after the response body has
479
+ * been either read or its cancellation has been started.
477
480
  * @param options The options for loading the document.
478
481
  * @returns The loaded remote document.
479
482
  * @throws {FetchError} If the response is not OK.
480
483
  * @internal
481
484
  */
482
485
  async function getRemoteDocument(url, response, fetch, options) {
483
- const documentUrl = response.url === "" ? url : response.url;
486
+ const result = await parseRemoteDocument(url, response, options);
487
+ if ("alternate" in result) return await fetch(result.alternate, options);
488
+ return result.document;
489
+ }
490
+ /**
491
+ * Starts cancelling the body of a response without waiting for it to settle.
492
+ * The cancellation of a branch of a tee (e.g., a body cloned by a fetch mock)
493
+ * settles only after the other branch is cancelled too, per the Streams
494
+ * standard, so awaiting it could block forever. Failures are only logged.
495
+ * @param response The response whose body is cancelled.
496
+ * @param url The URL of the response, for logging.
497
+ */
498
+ function cancelResponseBody(response, url) {
499
+ response.body?.cancel().catch((error) => {
500
+ logger.debug("Failed to cancel the response body from {url}: {error}", {
501
+ url,
502
+ error
503
+ });
504
+ });
505
+ }
506
+ /**
507
+ * Gets a {@link RemoteDocument} from the given response, or the URL of an
508
+ * alternate document to load instead. When an alternate document is found,
509
+ * the response body has been either read or its cancellation has been started
510
+ * (without waiting for it), so that the alternate document can be loaded after
511
+ * the response's session is closed.
512
+ * @param url The URL of the document to load.
513
+ * @param response The response to get the document from.
514
+ * @returns The loaded remote document, or the alternate document URL.
515
+ * @throws {FetchError} If the response is not OK.
516
+ */
517
+ async function parseRemoteDocument(url, response, options) {
518
+ const documentUrl = url;
484
519
  const docUrl = new URL(documentUrl);
485
520
  if (!response.ok) {
486
521
  if (options?.suppressError) logger.warn("Failed to fetch document: {status} {url} {headers}", {
@@ -522,7 +557,8 @@ async function getRemoteDocument(url, response, fetch, options) {
522
557
  alternateUrl: altUri.href,
523
558
  url: documentUrl
524
559
  });
525
- return await fetch(altUri.href, options);
560
+ cancelResponseBody(response, documentUrl);
561
+ return { alternate: altUri.href };
526
562
  }
527
563
  }
528
564
  }
@@ -558,7 +594,7 @@ async function getRemoteDocument(url, response, fetch, options) {
558
594
  alternateUrl: attribs.href,
559
595
  url: documentUrl
560
596
  });
561
- return await fetch(new URL(attribs.href, docUrl).href, options);
597
+ return { alternate: new URL(attribs.href, docUrl).href };
562
598
  }
563
599
  }
564
600
  try {
@@ -573,11 +609,11 @@ async function getRemoteDocument(url, response, fetch, options) {
573
609
  url: documentUrl,
574
610
  headers: Object.fromEntries(response.headers.entries())
575
611
  });
576
- return {
612
+ return { document: {
577
613
  contextUrl,
578
614
  document,
579
615
  documentUrl
580
- };
616
+ } };
581
617
  }
582
618
  /**
583
619
  * Resolves {@link DocumentLoaderFactoryOptions.timeout} into milliseconds.
@@ -682,10 +718,59 @@ function withDocumentLoaderTimeout(loader, timeout) {
682
718
  * @returns The document loader.
683
719
  * @since 1.3.0
684
720
  */
685
- function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedContexts, timeout, userAgent } = {}) {
721
+ function getDocumentLoader(options = {}) {
722
+ return createDocumentLoader(options);
723
+ }
724
+ /**
725
+ * The implementation of {@link getDocumentLoader}, with injectable
726
+ * dependencies. This is not exported from any entry point.
727
+ * @param options Options for the document loader.
728
+ * @param deps The dependencies.
729
+ * @returns The document loader.
730
+ */
731
+ function createDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedContexts, timeout, userAgent } = {}, deps = {}) {
686
732
  const resolvedTimeout = resolveDocumentLoaderTimeout(timeout);
687
733
  const tracer = _opentelemetry_api.trace.getTracerProvider().getTracer(name, version);
688
734
  const maximumRedirection = maxRedirection ?? DEFAULT_MAX_REDIRECTION;
735
+ async function openHop(url, options) {
736
+ try {
737
+ const session = deps.openHop != null ? await deps.openHop(url) : allowPrivateAddress ? await require_public_fetch.openDirectFetch(url) : await require_public_fetch.openPublicFetch(url, { signal: options?.signal });
738
+ if (options?.signal?.aborted) {
739
+ await session.close();
740
+ options.signal.throwIfAborted();
741
+ }
742
+ return session;
743
+ } catch (error) {
744
+ if (error instanceof require_url.UrlError) if (error.reason === "dns") logger.debug("DNS lookup failed for {url}", {
745
+ url,
746
+ error
747
+ });
748
+ else logger[options?.suppressError ? "warn" : "error"]("Disallowed private URL: {url}", {
749
+ url,
750
+ error
751
+ });
752
+ throw error;
753
+ }
754
+ }
755
+ async function fetchHop(session, url, span, options) {
756
+ try {
757
+ const request = createActivityPubRequest(url, { userAgent });
758
+ logRequest(logger, request);
759
+ const response = await session.fetch(request, {
760
+ redirect: "manual",
761
+ signal: options?.signal
762
+ });
763
+ span.setAttribute("http.response.status_code", response.status);
764
+ if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
765
+ const location = response.headers.get("Location");
766
+ cancelResponseBody(response, url);
767
+ return { location };
768
+ }
769
+ return await parseRemoteDocument(url, response, options);
770
+ } finally {
771
+ await session.close();
772
+ }
773
+ }
689
774
  async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
690
775
  options?.signal?.throwIfAborted();
691
776
  const parsedUrl = new URL(url);
@@ -700,36 +785,15 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
700
785
  documentUrl: currentUrl
701
786
  };
702
787
  }
703
- if (!allowPrivateAddress) {
704
- try {
705
- await require_url.validatePublicUrl(currentUrl);
706
- } catch (error) {
707
- if (error instanceof require_url.UrlError) if (error.reason === "dns") logger.debug("DNS lookup failed for {url}", {
708
- url: currentUrl,
709
- error
710
- });
711
- else logger[options?.suppressError ? "warn" : "error"]("Disallowed private URL: {url}", {
712
- url: currentUrl,
713
- error
714
- });
715
- throw error;
716
- }
717
- options?.signal?.throwIfAborted();
718
- }
788
+ const session = await openHop(currentUrl, options);
719
789
  visited.add(currentUrl);
720
790
  return await tracer.startActiveSpan("activitypub.fetch_document", {
721
791
  kind: _opentelemetry_api.SpanKind.CLIENT,
722
792
  attributes: { "url.full": currentUrl }
723
793
  }, async (span) => {
724
794
  try {
725
- const request = createActivityPubRequest(currentUrl, { userAgent });
726
- logRequest(logger, request);
727
- const response = await fetch(request, {
728
- redirect: "manual",
729
- signal: options?.signal
730
- });
731
- span.setAttribute("http.response.status_code", response.status);
732
- if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
795
+ const outcome = await fetchHop(session, currentUrl, span, options);
796
+ if ("location" in outcome) {
733
797
  if (redirected >= maximumRedirection) {
734
798
  logger[options?.suppressError ? "warn" : "error"]("Too many redirections ({redirections}) while fetching document.", {
735
799
  redirections: redirected + 1,
@@ -737,7 +801,7 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
737
801
  });
738
802
  throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
739
803
  }
740
- const redirectUrl = new URL(response.headers.get("Location"), response.url === "" ? currentUrl : response.url).href;
804
+ const redirectUrl = new URL(outcome.location, currentUrl).href;
741
805
  span.setAttribute("http.redirect.url", redirectUrl);
742
806
  if (visited.has(redirectUrl)) {
743
807
  logger[options?.suppressError ? "warn" : "error"]("Detected a redirect loop while fetching document: {url} -> {redirectUrl}", {
@@ -748,12 +812,14 @@ function getDocumentLoader({ allowPrivateAddress, maxRedirection, skipPreloadedC
748
812
  }
749
813
  return await load(redirectUrl, options, redirected + 1, visited);
750
814
  }
751
- const result = await getRemoteDocument(currentUrl, response, async (alternateUrl) => {
815
+ let result;
816
+ if ("alternate" in outcome) {
817
+ const alternateUrl = outcome.alternate;
752
818
  options?.signal?.throwIfAborted();
753
819
  if (redirected >= DEFAULT_MAX_REDIRECTION) throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
754
820
  if (visited.has(alternateUrl)) throw new FetchError(currentUrl, `Redirect loop detected: ${alternateUrl}`);
755
- return await load(alternateUrl, options, redirected + 1, visited);
756
- }, options);
821
+ result = await load(alternateUrl, options, redirected + 1, visited);
822
+ } else result = outcome.document;
757
823
  span.setAttribute("docloader.document_url", result.documentUrl);
758
824
  if (result.contextUrl != null) span.setAttribute("docloader.context_url", result.contextUrl);
759
825
  return result;