@fedify/vocab-runtime 2.2.19 → 2.2.20

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 (114) hide show
  1. package/deno.json +5 -2
  2. package/dist/internal.cjs +6 -0
  3. package/dist/internal.d.cts +3 -0
  4. package/dist/internal.d.ts +3 -0
  5. package/dist/internal.js +3 -0
  6. package/dist/mod.cjs +105 -253
  7. package/dist/mod.d.cts +5 -28
  8. package/dist/mod.d.ts +6 -28
  9. package/dist/mod.js +98 -245
  10. package/dist/public-fetch-BC-nWJNM.d.ts +143 -0
  11. package/dist/public-fetch-CFnW7XMn.cjs +821 -0
  12. package/dist/public-fetch-DBpt3xxf.d.cts +143 -0
  13. package/dist/public-fetch-Dfmc_Kfy.js +772 -0
  14. package/dist/tests/{body-BOnyCa1y.mjs → body-B9_hb-GO.mjs} +1 -1
  15. package/dist/tests/{body-DhbRZ6Ps.cjs → body-pOSG2RFb.cjs} +1 -1
  16. package/dist/tests/body.test.cjs +1 -1
  17. package/dist/tests/body.test.mjs +1 -1
  18. package/dist/tests/decimal.test.cjs +6 -6
  19. package/dist/tests/decimal.test.mjs +6 -6
  20. package/dist/tests/{docloader-BjyFsnAA.mjs → docloader-Bdlp7FkK.mjs} +102 -37
  21. package/dist/tests/{docloader-BhSiTajy.cjs → docloader-Ck3VfY29.cjs} +106 -35
  22. package/dist/tests/docloader.test.cjs +523 -1273
  23. package/dist/tests/docloader.test.mjs +469 -1238
  24. package/dist/tests/esm-CnLt0wAF.mjs +1225 -0
  25. package/dist/tests/esm-CxbqfkLP.cjs +1208 -0
  26. package/dist/tests/internal/multicodec.test.cjs +1 -1
  27. package/dist/tests/internal/multicodec.test.mjs +1 -1
  28. package/dist/tests/{key-BDIsC6hV.mjs → key-GEdpxA4M.mjs} +2 -2
  29. package/dist/tests/{key-BarKGLQz.cjs → key-L6ReMuUb.cjs} +2 -2
  30. package/dist/tests/key.test.cjs +2 -2
  31. package/dist/tests/key.test.mjs +2 -2
  32. package/dist/tests/{langstr-BsqFJupv.cjs → langstr-CZpWmpo5.cjs} +1 -1
  33. package/dist/tests/{langstr-ABdAOKlF.mjs → langstr-DTB4sl0a.mjs} +1 -1
  34. package/dist/tests/langstr.test.cjs +1 -1
  35. package/dist/tests/langstr.test.mjs +1 -1
  36. package/dist/tests/langtag.test.cjs +1 -1
  37. package/dist/tests/langtag.test.mjs +1 -1
  38. package/dist/tests/link.test.mjs +1 -1
  39. package/dist/tests/multibase/multibase.test.cjs +1 -1
  40. package/dist/tests/multibase/multibase.test.mjs +1 -1
  41. package/dist/tests/node-C3bjFjL1.mjs +84 -0
  42. package/dist/tests/node-DWD76vPo.cjs +90 -0
  43. package/dist/tests/pin-grant-CU-o367k.cjs +70 -0
  44. package/dist/tests/pin-grant-HW3B6SKv.mjs +53 -0
  45. package/dist/tests/pin-grant.test.cjs +88 -0
  46. package/dist/tests/pin-grant.test.d.cts +1 -0
  47. package/dist/tests/pin-grant.test.d.mts +1 -0
  48. package/dist/tests/pin-grant.test.mjs +88 -0
  49. package/dist/tests/pinners/bun.test.cjs +167 -0
  50. package/dist/tests/pinners/bun.test.d.cts +1 -0
  51. package/dist/tests/pinners/bun.test.d.mts +1 -0
  52. package/dist/tests/pinners/bun.test.mjs +167 -0
  53. package/dist/tests/pinners/node.test.cjs +269 -0
  54. package/dist/tests/pinners/node.test.d.cts +1 -0
  55. package/dist/tests/pinners/node.test.d.mts +1 -0
  56. package/dist/tests/pinners/node.test.mjs +268 -0
  57. package/dist/tests/pinners/pinners.test.cjs +197 -0
  58. package/dist/tests/pinners/pinners.test.d.cts +1 -0
  59. package/dist/tests/pinners/pinners.test.d.mts +1 -0
  60. package/dist/tests/pinners/pinners.test.mjs +197 -0
  61. package/dist/tests/pinners/testdata/node-child.cjs +135 -0
  62. package/dist/tests/pinners/testdata/node-child.d.cts +1 -0
  63. package/dist/tests/pinners/testdata/node-child.d.mts +1 -0
  64. package/dist/tests/pinners/testdata/node-child.mjs +134 -0
  65. package/dist/tests/pinners-BjE-YJLL.cjs +221 -0
  66. package/dist/tests/pinners-ZgZDx9y6.mjs +203 -0
  67. package/dist/tests/public-fetch-Ddjfxbwc.cjs +187 -0
  68. package/dist/tests/public-fetch-Yi7Vks9s.mjs +170 -0
  69. package/dist/tests/public-fetch.test.cjs +1074 -0
  70. package/dist/tests/public-fetch.test.d.cts +1 -0
  71. package/dist/tests/public-fetch.test.d.mts +1 -0
  72. package/dist/tests/public-fetch.test.mjs +1074 -0
  73. package/dist/tests/{request-DGtx5MkG.cjs → request-B3NUKbyk.cjs} +1 -1
  74. package/dist/tests/{request-yCis9VCB.mjs → request-BSg3IQzf.mjs} +1 -1
  75. package/dist/tests/request.test.cjs +1 -1
  76. package/dist/tests/request.test.mjs +1 -1
  77. package/dist/tests/support-D5-KVjWZ.cjs +363 -0
  78. package/dist/tests/support-M5ClN-no.mjs +272 -0
  79. package/dist/tests/{url-vZTgkIZg.cjs → url-DLuSj57U.cjs} +88 -12
  80. package/dist/tests/{url-3fyJuqC0.mjs → url-DQWrMGhu.mjs} +65 -13
  81. package/dist/tests/url.test.cjs +4 -1
  82. package/dist/tests/url.test.mjs +4 -1
  83. package/package.json +14 -3
  84. package/src/docloader.test.ts +700 -32
  85. package/src/docloader.ts +174 -67
  86. package/src/internal.ts +13 -0
  87. package/src/pin-grant.test.ts +118 -0
  88. package/src/pin-grant.ts +99 -0
  89. package/src/pinners/bun.test.ts +278 -0
  90. package/src/pinners/bun.ts +223 -0
  91. package/src/pinners/mod.ts +59 -0
  92. package/src/pinners/node.test.ts +415 -0
  93. package/src/pinners/node.ts +144 -0
  94. package/src/pinners/pinners.test.ts +316 -0
  95. package/src/pinners/testdata/node-child.ts +147 -0
  96. package/src/pinners/testdata/proxy-child.ts +82 -0
  97. package/src/pinners/testdata/support.ts +343 -0
  98. package/src/pinners/testdata/tls-cert.pem +12 -0
  99. package/src/pinners/testdata/tls-child.ts +88 -0
  100. package/src/pinners/testdata/tls-key.pem +5 -0
  101. package/src/pinners/types.ts +48 -0
  102. package/src/public-fetch.test.ts +1286 -0
  103. package/src/public-fetch.ts +338 -0
  104. package/src/testdata/public-fetch-deno-worker.ts +74 -0
  105. package/src/url.test.ts +11 -0
  106. package/src/url.ts +113 -22
  107. package/tsdown.config.ts +12 -3
  108. /package/dist/tests/{langtag-LDBqlxHv.mjs → langtag-D8FWxjv8.mjs} +0 -0
  109. /package/dist/tests/{langtag-DgySESmk.cjs → langtag-a2-s9uyi.cjs} +0 -0
  110. /package/dist/tests/{link-CYszIegt.mjs → link-CHMtH2-k.mjs} +0 -0
  111. /package/dist/tests/{multibase-8zhYPa7o.cjs → multibase-BmWaK56H.cjs} +0 -0
  112. /package/dist/tests/{multibase-Ds75BvXs.mjs → multibase-D642Uv3S.mjs} +0 -0
  113. /package/dist/tests/{multicodec-ByS9XLsw.cjs → multicodec-B-_CSt1M.cjs} +0 -0
  114. /package/dist/tests/{multicodec-BBPZjRfg.mjs → multicodec-caWdG98t.mjs} +0 -0
@@ -0,0 +1,187 @@
1
+ require("./chunk-Do9eywBl.cjs");
2
+ const require_pin_grant = require("./pin-grant-CU-o367k.cjs");
3
+ const require_url = require("./url-DLuSj57U.cjs");
4
+ const require_pinners = require("./pinners-BjE-YJLL.cjs");
5
+ //#region src/public-fetch.ts
6
+ const logger = (0, require("@logtape/logtape").getLogger)([
7
+ "fedify",
8
+ "runtime",
9
+ "fetch"
10
+ ]);
11
+ /**
12
+ * Creates {@link PublicFetch.openPublicFetch} and
13
+ * {@link PublicFetch.withPublicFetch} bound to the given pinner and lookup.
14
+ * This is exported only for tests; it is not exported from any entry point.
15
+ * @param dependencies The pinner and the default lookup function.
16
+ * @returns The functions.
17
+ */
18
+ function createPublicFetch(dependencies) {
19
+ async function openPublicFetch(url, options = {}) {
20
+ const { url: parsed, hostname, family } = require_url.parsePublicUrl(url);
21
+ if (parsed.username !== "" || parsed.password !== "") throw new require_url.UrlError("URLs with credentials are not allowed");
22
+ const lookup = options.lookup ?? dependencies.lookup;
23
+ let addresses;
24
+ if (family !== 0) addresses = [{
25
+ address: hostname,
26
+ family
27
+ }];
28
+ else if (lookup === require_url.lookupAllAddresses && !await canResolve()) return createSession(parsed, globalFetch);
29
+ else {
30
+ options.signal?.throwIfAborted();
31
+ addresses = await require_url.lookupPublicAddresses(hostname, lookup);
32
+ options.signal?.throwIfAborted();
33
+ }
34
+ const pinner = await dependencies.pinner();
35
+ if (pinner == null || !pinner.supports(parsed)) {
36
+ logUnpinned(parsed);
37
+ return createSession(parsed, globalFetch);
38
+ }
39
+ const grant = require_pin_grant.issuePinGrant(parsed, addresses);
40
+ return createSession(parsed, (input, init) => pinner.fetch(grant, new Request(input, init), init.signal ?? void 0), {
41
+ grant,
42
+ pinner
43
+ });
44
+ }
45
+ async function withPublicFetch(url, fn, options) {
46
+ const session = await openPublicFetch(url, options);
47
+ try {
48
+ const result = await fn(session.fetch);
49
+ if (result instanceof Response) throw new TypeError("withPublicFetch() callbacks must not return a Response, since its body is cancelled when the session is closed.");
50
+ return result;
51
+ } catch (error) {
52
+ logger.debug("Failed while fetching {url}; closing the session: {error}", {
53
+ url: session.url.href,
54
+ error
55
+ });
56
+ throw error;
57
+ } finally {
58
+ await session.close();
59
+ }
60
+ }
61
+ return {
62
+ openPublicFetch,
63
+ withPublicFetch
64
+ };
65
+ }
66
+ const runtimePublicFetch = createPublicFetch({
67
+ pinner: require_pinners.getRuntimePinner,
68
+ lookup: require_url.lookupAllAddresses
69
+ });
70
+ /**
71
+ * Validates the URL, resolves its hostname, checks that every resolved
72
+ * address is public, and opens a session that connects only to those
73
+ * addresses. Connections are pinned on Node.js and Bun. On other runtimes,
74
+ * the session uses the global `fetch()` after the check.
75
+ * @param url The URL to open a session for.
76
+ * @param options The options.
77
+ * @returns The session. It must be closed.
78
+ * @throws {UrlError} If the URL is not allowed, or its hostname cannot be
79
+ * resolved (with `reason: "dns"`).
80
+ */
81
+ const openPublicFetch = runtimePublicFetch.openPublicFetch;
82
+ runtimePublicFetch.withPublicFetch;
83
+ /**
84
+ * Opens a session that neither validates nor pins addresses, for callers that
85
+ * allow private addresses. It has the same rules as {@link openPublicFetch}
86
+ * sessions otherwise: requests are limited to the URL's origin, redirects are
87
+ * never followed, and unconsumed response bodies are cancelled on close.
88
+ * It grants nothing beyond calling the global `fetch()` directly.
89
+ * @param url The URL to open a session for.
90
+ * @returns The session. It must be closed.
91
+ */
92
+ function openDirectFetch(url) {
93
+ return Promise.resolve(createSession(new URL(url), globalFetch));
94
+ }
95
+ /**
96
+ * Starts cancelling the body of a response without waiting for it to settle.
97
+ * The cancellation of a branch of a tee (e.g., a body cloned by a fetch mock)
98
+ * settles only after the other branch is cancelled too, per the Streams
99
+ * standard, so awaiting it could block forever. Failures are only logged.
100
+ */
101
+ function cancelResponseBody(response, url) {
102
+ response.body?.cancel().catch((error) => {
103
+ logger.debug("Failed to cancel a response body from {url}: {error}", {
104
+ url: url.href,
105
+ error
106
+ });
107
+ });
108
+ }
109
+ function globalFetch(input, init) {
110
+ return fetch(input, init);
111
+ }
112
+ function createSession(url, send, pin) {
113
+ const origin = url.origin;
114
+ const tracked = [];
115
+ let closed = false;
116
+ async function sessionFetch(input, init) {
117
+ if (closed) throw new TypeError("The fetch session is closed.");
118
+ const target = new URL(input instanceof Request ? input.url : input);
119
+ if (target.origin !== origin) throw new TypeError(`A fetch session for ${origin} cannot fetch ${target.origin}.`);
120
+ const response = await send(input, {
121
+ ...init,
122
+ redirect: "manual"
123
+ });
124
+ if (closed) {
125
+ cancelResponseBody(response, url);
126
+ throw new TypeError("The fetch session is closed.");
127
+ }
128
+ tracked.push(response);
129
+ return response;
130
+ }
131
+ async function close() {
132
+ if (closed) return;
133
+ closed = true;
134
+ for (const response of tracked) {
135
+ if (response.body == null || response.bodyUsed) continue;
136
+ if (response.body.locked) {
137
+ logger.debug("Cannot cancel a locked response body from {url}.", { url: url.href });
138
+ continue;
139
+ }
140
+ cancelResponseBody(response, url);
141
+ }
142
+ if (pin == null) return;
143
+ require_pin_grant.revokePinGrant(pin.grant);
144
+ await pin.pinner.release(pin.grant);
145
+ }
146
+ return {
147
+ get url() {
148
+ return new URL(url);
149
+ },
150
+ fetch: sessionFetch,
151
+ close
152
+ };
153
+ }
154
+ async function canResolve() {
155
+ if (!("Deno" in globalThis)) return true;
156
+ return (await Deno.permissions.query({ name: "net" })).state === "granted";
157
+ }
158
+ const loggedUnpinned = /* @__PURE__ */ new Set();
159
+ function logUnpinned(url) {
160
+ const runtime = require_pinners.detectRuntime();
161
+ const key = `${runtime} ${url.protocol}`;
162
+ if (loggedUnpinned.has(key)) return;
163
+ loggedUnpinned.add(key);
164
+ logger.debug("Outbound {protocol} connections are not pinned to validated addresses on {runtime}; the hostname is resolved again when connecting.", {
165
+ runtime,
166
+ protocol: url.protocol
167
+ });
168
+ }
169
+ //#endregion
170
+ Object.defineProperty(exports, "createPublicFetch", {
171
+ enumerable: true,
172
+ get: function() {
173
+ return createPublicFetch;
174
+ }
175
+ });
176
+ Object.defineProperty(exports, "openDirectFetch", {
177
+ enumerable: true,
178
+ get: function() {
179
+ return openDirectFetch;
180
+ }
181
+ });
182
+ Object.defineProperty(exports, "openPublicFetch", {
183
+ enumerable: true,
184
+ get: function() {
185
+ return openPublicFetch;
186
+ }
187
+ });
@@ -0,0 +1,170 @@
1
+ import { r as revokePinGrant, t as issuePinGrant } from "./pin-grant-HW3B6SKv.mjs";
2
+ import { c as parsePublicUrl, o as lookupAllAddresses, s as lookupPublicAddresses, t as UrlError } from "./url-DQWrMGhu.mjs";
3
+ import { n as getRuntimePinner, t as detectRuntime } from "./pinners-ZgZDx9y6.mjs";
4
+ import { getLogger } from "@logtape/logtape";
5
+ //#region src/public-fetch.ts
6
+ const logger = getLogger([
7
+ "fedify",
8
+ "runtime",
9
+ "fetch"
10
+ ]);
11
+ /**
12
+ * Creates {@link PublicFetch.openPublicFetch} and
13
+ * {@link PublicFetch.withPublicFetch} bound to the given pinner and lookup.
14
+ * This is exported only for tests; it is not exported from any entry point.
15
+ * @param dependencies The pinner and the default lookup function.
16
+ * @returns The functions.
17
+ */
18
+ function createPublicFetch(dependencies) {
19
+ async function openPublicFetch(url, options = {}) {
20
+ const { url: parsed, hostname, family } = parsePublicUrl(url);
21
+ if (parsed.username !== "" || parsed.password !== "") throw new UrlError("URLs with credentials are not allowed");
22
+ const lookup = options.lookup ?? dependencies.lookup;
23
+ let addresses;
24
+ if (family !== 0) addresses = [{
25
+ address: hostname,
26
+ family
27
+ }];
28
+ else if (lookup === lookupAllAddresses && !await canResolve()) return createSession(parsed, globalFetch);
29
+ else {
30
+ options.signal?.throwIfAborted();
31
+ addresses = await lookupPublicAddresses(hostname, lookup);
32
+ options.signal?.throwIfAborted();
33
+ }
34
+ const pinner = await dependencies.pinner();
35
+ if (pinner == null || !pinner.supports(parsed)) {
36
+ logUnpinned(parsed);
37
+ return createSession(parsed, globalFetch);
38
+ }
39
+ const grant = issuePinGrant(parsed, addresses);
40
+ return createSession(parsed, (input, init) => pinner.fetch(grant, new Request(input, init), init.signal ?? void 0), {
41
+ grant,
42
+ pinner
43
+ });
44
+ }
45
+ async function withPublicFetch(url, fn, options) {
46
+ const session = await openPublicFetch(url, options);
47
+ try {
48
+ const result = await fn(session.fetch);
49
+ if (result instanceof Response) throw new TypeError("withPublicFetch() callbacks must not return a Response, since its body is cancelled when the session is closed.");
50
+ return result;
51
+ } catch (error) {
52
+ logger.debug("Failed while fetching {url}; closing the session: {error}", {
53
+ url: session.url.href,
54
+ error
55
+ });
56
+ throw error;
57
+ } finally {
58
+ await session.close();
59
+ }
60
+ }
61
+ return {
62
+ openPublicFetch,
63
+ withPublicFetch
64
+ };
65
+ }
66
+ const runtimePublicFetch = createPublicFetch({
67
+ pinner: getRuntimePinner,
68
+ lookup: lookupAllAddresses
69
+ });
70
+ /**
71
+ * Validates the URL, resolves its hostname, checks that every resolved
72
+ * address is public, and opens a session that connects only to those
73
+ * addresses. Connections are pinned on Node.js and Bun. On other runtimes,
74
+ * the session uses the global `fetch()` after the check.
75
+ * @param url The URL to open a session for.
76
+ * @param options The options.
77
+ * @returns The session. It must be closed.
78
+ * @throws {UrlError} If the URL is not allowed, or its hostname cannot be
79
+ * resolved (with `reason: "dns"`).
80
+ */
81
+ const openPublicFetch = runtimePublicFetch.openPublicFetch;
82
+ runtimePublicFetch.withPublicFetch;
83
+ /**
84
+ * Opens a session that neither validates nor pins addresses, for callers that
85
+ * allow private addresses. It has the same rules as {@link openPublicFetch}
86
+ * sessions otherwise: requests are limited to the URL's origin, redirects are
87
+ * never followed, and unconsumed response bodies are cancelled on close.
88
+ * It grants nothing beyond calling the global `fetch()` directly.
89
+ * @param url The URL to open a session for.
90
+ * @returns The session. It must be closed.
91
+ */
92
+ function openDirectFetch(url) {
93
+ return Promise.resolve(createSession(new URL(url), globalFetch));
94
+ }
95
+ /**
96
+ * Starts cancelling the body of a response without waiting for it to settle.
97
+ * The cancellation of a branch of a tee (e.g., a body cloned by a fetch mock)
98
+ * settles only after the other branch is cancelled too, per the Streams
99
+ * standard, so awaiting it could block forever. Failures are only logged.
100
+ */
101
+ function cancelResponseBody(response, url) {
102
+ response.body?.cancel().catch((error) => {
103
+ logger.debug("Failed to cancel a response body from {url}: {error}", {
104
+ url: url.href,
105
+ error
106
+ });
107
+ });
108
+ }
109
+ function globalFetch(input, init) {
110
+ return fetch(input, init);
111
+ }
112
+ function createSession(url, send, pin) {
113
+ const origin = url.origin;
114
+ const tracked = [];
115
+ let closed = false;
116
+ async function sessionFetch(input, init) {
117
+ if (closed) throw new TypeError("The fetch session is closed.");
118
+ const target = new URL(input instanceof Request ? input.url : input);
119
+ if (target.origin !== origin) throw new TypeError(`A fetch session for ${origin} cannot fetch ${target.origin}.`);
120
+ const response = await send(input, {
121
+ ...init,
122
+ redirect: "manual"
123
+ });
124
+ if (closed) {
125
+ cancelResponseBody(response, url);
126
+ throw new TypeError("The fetch session is closed.");
127
+ }
128
+ tracked.push(response);
129
+ return response;
130
+ }
131
+ async function close() {
132
+ if (closed) return;
133
+ closed = true;
134
+ for (const response of tracked) {
135
+ if (response.body == null || response.bodyUsed) continue;
136
+ if (response.body.locked) {
137
+ logger.debug("Cannot cancel a locked response body from {url}.", { url: url.href });
138
+ continue;
139
+ }
140
+ cancelResponseBody(response, url);
141
+ }
142
+ if (pin == null) return;
143
+ revokePinGrant(pin.grant);
144
+ await pin.pinner.release(pin.grant);
145
+ }
146
+ return {
147
+ get url() {
148
+ return new URL(url);
149
+ },
150
+ fetch: sessionFetch,
151
+ close
152
+ };
153
+ }
154
+ async function canResolve() {
155
+ if (!("Deno" in globalThis)) return true;
156
+ return (await Deno.permissions.query({ name: "net" })).state === "granted";
157
+ }
158
+ const loggedUnpinned = /* @__PURE__ */ new Set();
159
+ function logUnpinned(url) {
160
+ const runtime = detectRuntime();
161
+ const key = `${runtime} ${url.protocol}`;
162
+ if (loggedUnpinned.has(key)) return;
163
+ loggedUnpinned.add(key);
164
+ logger.debug("Outbound {protocol} connections are not pinned to validated addresses on {runtime}; the hostname is resolved again when connecting.", {
165
+ runtime,
166
+ protocol: url.protocol
167
+ });
168
+ }
169
+ //#endregion
170
+ export { openDirectFetch as n, openPublicFetch as r, createPublicFetch as t };