@fedify/vocab-runtime 2.0.35-dev.2480 → 2.0.35-dev.2485

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 (108) 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 -27
  8. package/dist/mod.d.ts +6 -27
  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-moFua5l6.cjs → body-1FbapsC3.cjs} +1 -1
  15. package/dist/tests/{body-s65I5CCa.mjs → body-GD9HfwgN.mjs} +1 -1
  16. package/dist/tests/body.test.cjs +1 -1
  17. package/dist/tests/body.test.mjs +1 -1
  18. package/dist/tests/docloader.test.cjs +616 -1306
  19. package/dist/tests/docloader.test.mjs +562 -1271
  20. package/dist/tests/esm-4DuocLvD.cjs +1208 -0
  21. package/dist/tests/esm-CnLt0wAF.mjs +1225 -0
  22. package/dist/tests/internal/multicodec.test.cjs +1 -1
  23. package/dist/tests/internal/multicodec.test.mjs +1 -1
  24. package/dist/tests/key.test.cjs +2 -2
  25. package/dist/tests/key.test.mjs +2 -2
  26. package/dist/tests/langstr.test.cjs +1 -1
  27. package/dist/tests/langstr.test.mjs +1 -1
  28. package/dist/tests/langtag.test.cjs +1 -1
  29. package/dist/tests/langtag.test.mjs +1 -1
  30. package/dist/tests/link.test.cjs +1 -1
  31. package/dist/tests/link.test.mjs +1 -1
  32. package/dist/tests/multibase/multibase.test.cjs +1 -1
  33. package/dist/tests/multibase/multibase.test.mjs +1 -1
  34. package/dist/tests/node-BTPYk2h-.mjs +84 -0
  35. package/dist/tests/node-DWD76vPo.cjs +90 -0
  36. package/dist/tests/pin-grant-BAlKqSph.mjs +53 -0
  37. package/dist/tests/pin-grant-CU-o367k.cjs +70 -0
  38. package/dist/tests/pin-grant.test.cjs +88 -0
  39. package/dist/tests/pin-grant.test.d.cts +1 -0
  40. package/dist/tests/pin-grant.test.d.mts +1 -0
  41. package/dist/tests/pin-grant.test.mjs +88 -0
  42. package/dist/tests/pinners/bun.test.cjs +167 -0
  43. package/dist/tests/pinners/bun.test.d.cts +1 -0
  44. package/dist/tests/pinners/bun.test.d.mts +1 -0
  45. package/dist/tests/pinners/bun.test.mjs +167 -0
  46. package/dist/tests/pinners/node.test.cjs +269 -0
  47. package/dist/tests/pinners/node.test.d.cts +1 -0
  48. package/dist/tests/pinners/node.test.d.mts +1 -0
  49. package/dist/tests/pinners/node.test.mjs +268 -0
  50. package/dist/tests/pinners/pinners.test.cjs +197 -0
  51. package/dist/tests/pinners/pinners.test.d.cts +1 -0
  52. package/dist/tests/pinners/pinners.test.d.mts +1 -0
  53. package/dist/tests/pinners/pinners.test.mjs +197 -0
  54. package/dist/tests/pinners/testdata/node-child.cjs +135 -0
  55. package/dist/tests/pinners/testdata/node-child.d.cts +1 -0
  56. package/dist/tests/pinners/testdata/node-child.d.mts +1 -0
  57. package/dist/tests/pinners/testdata/node-child.mjs +134 -0
  58. package/dist/tests/pinners-BjE-YJLL.cjs +221 -0
  59. package/dist/tests/pinners-C0SoXz-S.mjs +203 -0
  60. package/dist/tests/public-fetch-BUSPlh1C.mjs +170 -0
  61. package/dist/tests/public-fetch-Ddjfxbwc.cjs +187 -0
  62. package/dist/tests/public-fetch.test.cjs +1074 -0
  63. package/dist/tests/public-fetch.test.d.cts +1 -0
  64. package/dist/tests/public-fetch.test.d.mts +1 -0
  65. package/dist/tests/public-fetch.test.mjs +1074 -0
  66. package/dist/tests/{request-BnwgFF6c.cjs → request-C4q9phZR.cjs} +1 -1
  67. package/dist/tests/{request-CZWDeBC-.mjs → request-Dr5LoMPw.mjs} +1 -1
  68. package/dist/tests/request.test.cjs +1 -1
  69. package/dist/tests/request.test.mjs +1 -1
  70. package/dist/tests/support-EdBgVdNy.cjs +363 -0
  71. package/dist/tests/support-SnCB9BSR.mjs +272 -0
  72. package/dist/tests/{url-Qk1svKZU.cjs → url-DLuSj57U.cjs} +88 -12
  73. package/dist/tests/{url-DzlMS01z.mjs → url-DjsVF44Q.mjs} +65 -13
  74. package/dist/tests/url.test.cjs +4 -1
  75. package/dist/tests/url.test.mjs +4 -1
  76. package/package.json +13 -2
  77. package/src/docloader.test.ts +690 -31
  78. package/src/docloader.ts +174 -67
  79. package/src/internal.ts +13 -0
  80. package/src/pin-grant.test.ts +118 -0
  81. package/src/pin-grant.ts +99 -0
  82. package/src/pinners/bun.test.ts +278 -0
  83. package/src/pinners/bun.ts +223 -0
  84. package/src/pinners/mod.ts +59 -0
  85. package/src/pinners/node.test.ts +415 -0
  86. package/src/pinners/node.ts +144 -0
  87. package/src/pinners/pinners.test.ts +316 -0
  88. package/src/pinners/testdata/node-child.ts +147 -0
  89. package/src/pinners/testdata/proxy-child.ts +82 -0
  90. package/src/pinners/testdata/support.ts +343 -0
  91. package/src/pinners/testdata/tls-cert.pem +12 -0
  92. package/src/pinners/testdata/tls-child.ts +88 -0
  93. package/src/pinners/testdata/tls-key.pem +5 -0
  94. package/src/pinners/types.ts +48 -0
  95. package/src/public-fetch.test.ts +1286 -0
  96. package/src/public-fetch.ts +338 -0
  97. package/src/testdata/public-fetch-deno-worker.ts +74 -0
  98. package/src/url.test.ts +11 -0
  99. package/src/url.ts +113 -22
  100. package/tsdown.config.ts +12 -3
  101. /package/dist/tests/{langtag-Bm8oKhIO.cjs → langtag-B_fXk5oL.cjs} +0 -0
  102. /package/dist/tests/{langtag-QZZgy903.mjs → langtag-CwDxJ10m.mjs} +0 -0
  103. /package/dist/tests/{link-NUUWCdnK.mjs → link-CYszIegt.mjs} +0 -0
  104. /package/dist/tests/{link-FguCydMA.cjs → link-qDTmjPw_.cjs} +0 -0
  105. /package/dist/tests/{multibase-CnmModw0.cjs → multibase-BKJat02S.cjs} +0 -0
  106. /package/dist/tests/{multibase-DLBufb99.mjs → multibase-CNswrMoP.mjs} +0 -0
  107. /package/dist/tests/{multicodec-CxGVGa91.cjs → multicodec-B-_CSt1M.cjs} +0 -0
  108. /package/dist/tests/{multicodec-CyFp54fI.mjs → multicodec-CDEzQTyO.mjs} +0 -0
@@ -1,1219 +1,18 @@
1
1
  const require_chunk = require("./chunk-Do9eywBl.cjs");
2
- const require_request = require("./request-BnwgFF6c.cjs");
3
- const require_body = require("./body-moFua5l6.cjs");
4
- const require_link = require("./link-FguCydMA.cjs");
5
- const require_url = require("./url-Qk1svKZU.cjs");
2
+ const require_request = require("./request-C4q9phZR.cjs");
3
+ const require_body = require("./body-1FbapsC3.cjs");
4
+ const require_esm = require("./esm-4DuocLvD.cjs");
5
+ const require_link = require("./link-qDTmjPw_.cjs");
6
+ const require_url = require("./url-DLuSj57U.cjs");
7
+ const require_public_fetch = require("./public-fetch-Ddjfxbwc.cjs");
6
8
  let node_zlib = require("node:zlib");
7
9
  let _logtape_logtape = require("@logtape/logtape");
8
10
  let node_assert = require("node:assert");
9
- let node_dns_promises = require("node:dns/promises");
10
- node_dns_promises = require_chunk.__toESM(node_dns_promises);
11
11
  let _fedify_fixture = require("@fedify/fixture");
12
12
  let node_http = require("node:http");
13
13
  let _opentelemetry_api = require("@opentelemetry/api");
14
14
  let jsonld_dist_jsonld_esm_js = require("jsonld/dist/jsonld.esm.js");
15
15
  jsonld_dist_jsonld_esm_js = require_chunk.__toESM(jsonld_dist_jsonld_esm_js);
16
- //#region ../../node_modules/.pnpm/glob-to-regexp@0.4.1/node_modules/glob-to-regexp/index.js
17
- var require_glob_to_regexp = /* @__PURE__ */ require_chunk.__commonJSMin(((exports, module) => {
18
- module.exports = function(glob, opts) {
19
- if (typeof glob !== "string") throw new TypeError("Expected a string");
20
- var str = String(glob);
21
- var reStr = "";
22
- var extended = opts ? !!opts.extended : false;
23
- var globstar = opts ? !!opts.globstar : false;
24
- var inGroup = false;
25
- var flags = opts && typeof opts.flags === "string" ? opts.flags : "";
26
- var c;
27
- for (var i = 0, len = str.length; i < len; i++) {
28
- c = str[i];
29
- switch (c) {
30
- case "/":
31
- case "$":
32
- case "^":
33
- case "+":
34
- case ".":
35
- case "(":
36
- case ")":
37
- case "=":
38
- case "!":
39
- case "|":
40
- reStr += "\\" + c;
41
- break;
42
- case "?": if (extended) {
43
- reStr += ".";
44
- break;
45
- }
46
- case "[":
47
- case "]": if (extended) {
48
- reStr += c;
49
- break;
50
- }
51
- case "{": if (extended) {
52
- inGroup = true;
53
- reStr += "(";
54
- break;
55
- }
56
- case "}": if (extended) {
57
- inGroup = false;
58
- reStr += ")";
59
- break;
60
- }
61
- case ",":
62
- if (inGroup) {
63
- reStr += "|";
64
- break;
65
- }
66
- reStr += "\\" + c;
67
- break;
68
- case "*":
69
- var prevChar = str[i - 1];
70
- var starCount = 1;
71
- while (str[i + 1] === "*") {
72
- starCount++;
73
- i++;
74
- }
75
- var nextChar = str[i + 1];
76
- if (!globstar) reStr += ".*";
77
- else if (starCount > 1 && (prevChar === "/" || prevChar === void 0) && (nextChar === "/" || nextChar === void 0)) {
78
- reStr += "((?:[^/]*(?:/|$))*)";
79
- i++;
80
- } else reStr += "([^/]*)";
81
- break;
82
- default: reStr += c;
83
- }
84
- }
85
- if (!flags || !~flags.indexOf("g")) reStr = "^" + reStr + "$";
86
- return new RegExp(reStr, flags);
87
- };
88
- }));
89
- //#endregion
90
- //#region ../../node_modules/.pnpm/regexparam@3.0.0/node_modules/regexparam/dist/index.mjs
91
- var import_glob_to_regexp = /* @__PURE__ */ require_chunk.__toESM(require_glob_to_regexp(), 1);
92
- /**
93
- * @param {string|RegExp} input The route pattern
94
- * @param {boolean} [loose] Allow open-ended matching. Ignored with `RegExp` input.
95
- */
96
- function parse(input, loose) {
97
- if (input instanceof RegExp) return {
98
- keys: false,
99
- pattern: input
100
- };
101
- var c, o, tmp, ext, keys = [], pattern = "", arr = input.split("/");
102
- arr[0] || arr.shift();
103
- while (tmp = arr.shift()) {
104
- c = tmp[0];
105
- if (c === "*") {
106
- keys.push(c);
107
- pattern += tmp[1] === "?" ? "(?:/(.*))?" : "/(.*)";
108
- } else if (c === ":") {
109
- o = tmp.indexOf("?", 1);
110
- ext = tmp.indexOf(".", 1);
111
- keys.push(tmp.substring(1, !!~o ? o : !!~ext ? ext : tmp.length));
112
- pattern += !!~o && !~ext ? "(?:/([^/]+?))?" : "/([^/]+?)";
113
- if (!!~ext) pattern += (!!~o ? "?" : "") + "\\" + tmp.substring(ext);
114
- } else pattern += "/" + tmp;
115
- }
116
- return {
117
- keys,
118
- pattern: new RegExp("^" + pattern + (loose ? "(?=$|/)" : "/?$"), "i")
119
- };
120
- }
121
- //#endregion
122
- //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/TypeDescriptor.js
123
- const valueTypes = new Set([
124
- "boolean",
125
- "number",
126
- "null",
127
- "string",
128
- "undefined"
129
- ]);
130
- const referenceTypes = new Set([
131
- "array",
132
- "function",
133
- "object",
134
- "symbol"
135
- ]);
136
- const detectableTypes = new Set([
137
- "boolean",
138
- "function",
139
- "number",
140
- "string",
141
- "symbol"
142
- ]);
143
- const typeConstructors = new Set([
144
- Boolean,
145
- Number,
146
- String
147
- ]);
148
- var TypeDescriptor = class TypeDescriptor {
149
- constructor(value) {
150
- this.name = TypeDescriptor.of(value);
151
- this.isValueType = TypeDescriptor.isValueType(value);
152
- this.isReferenceType = TypeDescriptor.isReferenceType(value);
153
- this.isArray = TypeDescriptor.isArray(value);
154
- this.isBoolean = TypeDescriptor.isBoolean(value);
155
- this.isFunction = TypeDescriptor.isFunction(value);
156
- this.isNull = TypeDescriptor.isNull(value);
157
- this.isNumber = TypeDescriptor.isNumber(value);
158
- this.isObject = TypeDescriptor.isObject(value);
159
- this.isString = TypeDescriptor.isString(value);
160
- this.isSymbol = TypeDescriptor.isSymbol(value);
161
- this.isUndefined = TypeDescriptor.isUndefined(value);
162
- }
163
- static of(value) {
164
- if (value === null) return "null";
165
- if (value === void 0) return "undefined";
166
- const detectedType = typeof value;
167
- if (detectableTypes.has(detectedType)) return detectedType;
168
- if (detectedType === "object") {
169
- if (Array.isArray(value)) return "array";
170
- if (typeConstructors.has(value.constructor)) return value.constructor.name.toLowerCase();
171
- return detectedType;
172
- }
173
- throw new Error("Failed due to an unknown type.");
174
- }
175
- static from(value) {
176
- return new TypeDescriptor(value);
177
- }
178
- static isValueType(value) {
179
- return valueTypes.has(TypeDescriptor.of(value));
180
- }
181
- static isReferenceType(value) {
182
- return referenceTypes.has(TypeDescriptor.of(value));
183
- }
184
- static isArray(value) {
185
- return TypeDescriptor.of(value) === "array";
186
- }
187
- static isBoolean(value) {
188
- return TypeDescriptor.of(value) === "boolean";
189
- }
190
- static isFunction(value) {
191
- return TypeDescriptor.of(value) === "function";
192
- }
193
- static isNull(value) {
194
- return TypeDescriptor.of(value) === "null";
195
- }
196
- static isNumber(value) {
197
- return TypeDescriptor.of(value) === "number";
198
- }
199
- static isObject(value) {
200
- return TypeDescriptor.of(value) === "object";
201
- }
202
- static isString(value) {
203
- return TypeDescriptor.of(value) === "string";
204
- }
205
- static isSymbol(value) {
206
- return TypeDescriptor.of(value) === "symbol";
207
- }
208
- static isUndefined(value) {
209
- return TypeDescriptor.of(value) === "undefined";
210
- }
211
- };
212
- //#endregion
213
- //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/IsSubsetOf.js
214
- const allowedTypes = new Set([
215
- "array",
216
- "object",
217
- "function",
218
- "null"
219
- ]);
220
- const isSubsetOf = function(subset, superset, visited = []) {
221
- const subsetType = TypeDescriptor.of(subset);
222
- const supersetType = TypeDescriptor.of(superset);
223
- if (!allowedTypes.has(subsetType)) throw new Error(`Type '${subsetType}' is not supported.`);
224
- if (!allowedTypes.has(supersetType)) throw new Error(`Type '${supersetType}' is not supported.`);
225
- if (TypeDescriptor.isFunction(subset)) {
226
- if (!TypeDescriptor.isFunction(superset)) throw new Error(`Types '${subsetType}' and '${supersetType}' do not match.`);
227
- return subset.toString() === superset.toString();
228
- }
229
- if (TypeDescriptor.isArray(subset)) {
230
- if (!TypeDescriptor.isArray(superset)) throw new Error(`Types '${subsetType}' and '${supersetType}' do not match.`);
231
- if (subset.length > superset.length) return false;
232
- for (const subsetItem of subset) {
233
- const subsetItemType = TypeDescriptor.of(subsetItem);
234
- let isItemInSuperset;
235
- switch (subsetItemType) {
236
- case "array":
237
- case "object":
238
- case "function":
239
- if (visited.includes(subsetItem)) continue;
240
- visited.push(subsetItem);
241
- isItemInSuperset = superset.some((supersetItem) => {
242
- try {
243
- return isSubsetOf(subsetItem, supersetItem, visited);
244
- } catch {
245
- return false;
246
- }
247
- });
248
- break;
249
- default: isItemInSuperset = superset.includes(subsetItem);
250
- }
251
- if (!isItemInSuperset) return false;
252
- }
253
- return true;
254
- }
255
- if (TypeDescriptor.isObject(subset)) {
256
- if (!TypeDescriptor.isObject(superset) || TypeDescriptor.isArray(superset)) throw new Error(`Types '${subsetType}' and '${supersetType}' do not match.`);
257
- if (Object.keys(subset).length > Object.keys(superset).length) return false;
258
- for (const [subsetKey, subsetValue] of Object.entries(subset)) {
259
- const supersetValue = superset[subsetKey];
260
- switch (TypeDescriptor.of(subsetValue)) {
261
- case "array":
262
- case "object":
263
- case "function":
264
- if (visited.includes(subsetValue)) continue;
265
- visited.push(subsetValue);
266
- try {
267
- if (!isSubsetOf(subsetValue, supersetValue, visited)) return false;
268
- } catch {
269
- return false;
270
- }
271
- break;
272
- default: if (subsetValue !== supersetValue) return false;
273
- }
274
- }
275
- return true;
276
- }
277
- if (TypeDescriptor.isNull(subset)) {
278
- if (!TypeDescriptor.isNull(superset)) throw new Error(`Types '${subsetType}' and '${supersetType}' do not match.`);
279
- return true;
280
- }
281
- throw new Error("Invalid operation.");
282
- };
283
- isSubsetOf.structural = function(subset, superset, visited = []) {
284
- if (!TypeDescriptor.isObject(subset)) throw new Error(`Type '${TypeDescriptor.of(subset)}' is not supported.`);
285
- if (!TypeDescriptor.isObject(superset)) throw new Error(`Type '${TypeDescriptor.of(superset)}' is not supported.`);
286
- for (const [subsetKey, subsetValue] of Object.entries(subset)) {
287
- if (superset[subsetKey] === void 0) return false;
288
- const subsetValueType = TypeDescriptor.of(subsetValue);
289
- const supersetValue = superset[subsetKey];
290
- if (subsetValueType === "object") {
291
- if (visited.includes(subsetValue)) continue;
292
- visited.push(subsetValue);
293
- try {
294
- if (!isSubsetOf.structural(subsetValue, supersetValue, visited)) return false;
295
- } catch {
296
- return false;
297
- }
298
- }
299
- }
300
- return true;
301
- };
302
- //#endregion
303
- //#region ../../node_modules/.pnpm/dequal@2.0.3/node_modules/dequal/dist/index.mjs
304
- var has = Object.prototype.hasOwnProperty;
305
- function find(iter, tar, key) {
306
- for (key of iter.keys()) if (dequal(key, tar)) return key;
307
- }
308
- function dequal(foo, bar) {
309
- var ctor, len, tmp;
310
- if (foo === bar) return true;
311
- if (foo && bar && (ctor = foo.constructor) === bar.constructor) {
312
- if (ctor === Date) return foo.getTime() === bar.getTime();
313
- if (ctor === RegExp) return foo.toString() === bar.toString();
314
- if (ctor === Array) {
315
- if ((len = foo.length) === bar.length) while (len-- && dequal(foo[len], bar[len]));
316
- return len === -1;
317
- }
318
- if (ctor === Set) {
319
- if (foo.size !== bar.size) return false;
320
- for (len of foo) {
321
- tmp = len;
322
- if (tmp && typeof tmp === "object") {
323
- tmp = find(bar, tmp);
324
- if (!tmp) return false;
325
- }
326
- if (!bar.has(tmp)) return false;
327
- }
328
- return true;
329
- }
330
- if (ctor === Map) {
331
- if (foo.size !== bar.size) return false;
332
- for (len of foo) {
333
- tmp = len[0];
334
- if (tmp && typeof tmp === "object") {
335
- tmp = find(bar, tmp);
336
- if (!tmp) return false;
337
- }
338
- if (!dequal(len[1], bar.get(tmp))) return false;
339
- }
340
- return true;
341
- }
342
- if (ctor === ArrayBuffer) {
343
- foo = new Uint8Array(foo);
344
- bar = new Uint8Array(bar);
345
- } else if (ctor === DataView) {
346
- if ((len = foo.byteLength) === bar.byteLength) while (len-- && foo.getInt8(len) === bar.getInt8(len));
347
- return len === -1;
348
- }
349
- if (ArrayBuffer.isView(foo)) {
350
- if ((len = foo.byteLength) === bar.byteLength) while (len-- && foo[len] === bar[len]);
351
- return len === -1;
352
- }
353
- if (!ctor || typeof foo === "object") {
354
- len = 0;
355
- for (ctor in foo) {
356
- if (has.call(foo, ctor) && ++len && !has.call(bar, ctor)) return false;
357
- if (!(ctor in bar) || !dequal(foo[ctor], bar[ctor])) return false;
358
- }
359
- return Object.keys(bar).length === len;
360
- }
361
- }
362
- return foo !== foo && bar !== bar;
363
- }
364
- //#endregion
365
- //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/RequestUtils.js
366
- const absoluteUrlRX = /* @__PURE__ */ new RegExp("^[a-z]+://|^data:", "i");
367
- const protocolRelativeUrlRX = /* @__PURE__ */ new RegExp("^//", "i");
368
- function hasCredentialsInUrl(url) {
369
- const urlObject = new URL(url, !absoluteUrlRX.test(url) ? "http://dummy" : void 0);
370
- return Boolean(urlObject.username || urlObject.password);
371
- }
372
- function normalizeUrl(url, allowRelativeUrls) {
373
- if (url instanceof URL) return url.href;
374
- const primitiveUrl = String(url).valueOf();
375
- if (absoluteUrlRX.test(primitiveUrl)) return new URL(primitiveUrl).href;
376
- if (protocolRelativeUrlRX.test(primitiveUrl)) return new URL(primitiveUrl, "http://dummy").href.replace(/^[a-z]+:/, "");
377
- if ("location" in globalThis) if (primitiveUrl.startsWith("/")) return `${globalThis.location.origin}${primitiveUrl}`;
378
- else return `${globalThis.location.href}/${primitiveUrl}`;
379
- else if (allowRelativeUrls) {
380
- const urlInstance = new URL(primitiveUrl, "http://dummy");
381
- return urlInstance.pathname + urlInstance.search;
382
- } else throw new Error("Relative urls are not support by default in node.js tests. Either use a utility such as jsdom to define globalThis.location or set `fetchMock.config.allowRelativeUrls = true`");
383
- }
384
- function createCallLogFromUrlAndOptions(url, options) {
385
- const pendingPromises = [];
386
- if (typeof url === "string" || url instanceof String || url instanceof URL) {
387
- const normalizedUrl = normalizeUrl(url, true);
388
- const derivedOptions = options ? { ...options } : {};
389
- if (derivedOptions.headers) derivedOptions.headers = normalizeHeaders(derivedOptions.headers);
390
- derivedOptions.method = derivedOptions.method ? derivedOptions.method.toLowerCase() : "get";
391
- return {
392
- args: [url, options],
393
- url: normalizedUrl,
394
- queryParams: new URLSearchParams(getQuery(normalizedUrl)),
395
- options: derivedOptions,
396
- signal: derivedOptions.signal,
397
- pendingPromises
398
- };
399
- }
400
- if (typeof url === "object") throw new TypeError("fetch-mock: Unrecognised Request object. Read the Config and Installation sections of the docs");
401
- else throw new TypeError("fetch-mock: Invalid arguments passed to fetch");
402
- }
403
- async function createCallLogFromRequest(request, options) {
404
- const pendingPromises = [];
405
- const derivedOptions = { method: request.method };
406
- try {
407
- try {
408
- derivedOptions.body = await request.clone().formData();
409
- } catch {
410
- derivedOptions.body = await request.clone().text();
411
- }
412
- } catch {}
413
- if (request.headers) derivedOptions.headers = normalizeHeaders(request.headers);
414
- const url = normalizeUrl(request.url, true);
415
- return {
416
- args: [request, options],
417
- url,
418
- queryParams: new URLSearchParams(getQuery(url)),
419
- options: Object.assign(derivedOptions, options || {}),
420
- request,
421
- signal: options && options.signal || request.signal,
422
- pendingPromises
423
- };
424
- }
425
- function getPath(url) {
426
- return (absoluteUrlRX.test(url) ? new URL(url) : new URL(url, "http://dummy")).pathname;
427
- }
428
- function getHost(url) {
429
- if (absoluteUrlRX.test(url)) return new URL(url).host;
430
- else if ("location" in globalThis) return globalThis.location.host;
431
- return null;
432
- }
433
- function getQuery(url) {
434
- const u = absoluteUrlRX.test(url) ? new URL(url) : new URL(url, "http://dummy");
435
- return u.search ? u.search.substr(1) : "";
436
- }
437
- function normalizeHeaders(headers) {
438
- let entries;
439
- if (headers instanceof Headers) entries = [...headers.entries()];
440
- else if (Array.isArray(headers)) entries = headers;
441
- else entries = Object.entries(headers);
442
- return Object.fromEntries(entries.map(([key, val]) => [key.toLowerCase(), String(val).valueOf()]));
443
- }
444
- //#endregion
445
- //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/Matchers.js
446
- const isUrlMatcher = (matcher) => matcher instanceof RegExp || typeof matcher === "string" || typeof matcher === "object" && "href" in matcher;
447
- const isFunctionMatcher = (matcher) => typeof matcher === "function";
448
- const stringMatchers = {
449
- begin: (targetString) => ({ url }) => url.startsWith(targetString),
450
- end: (targetString) => ({ url }) => url.endsWith(targetString),
451
- include: (targetString) => ({ url }) => url.includes(targetString),
452
- glob: (targetString) => {
453
- const urlRX = (0, import_glob_to_regexp.default)(targetString);
454
- return ({ url }) => urlRX.test(url);
455
- },
456
- express: (targetString) => {
457
- const urlRX = parse(targetString);
458
- return (callLog) => {
459
- const vals = urlRX.pattern.exec(getPath(callLog.url));
460
- if (!vals) {
461
- callLog.expressParams = {};
462
- return false;
463
- }
464
- vals.shift();
465
- callLog.expressParams = urlRX.keys.reduce((map, paramName, i) => vals[i] ? Object.assign(map, { [paramName]: vals[i] }) : map, {});
466
- return true;
467
- };
468
- },
469
- path: (targetString) => {
470
- const dotlessTargetString = getPath(targetString);
471
- return ({ url }) => {
472
- const path = getPath(url);
473
- return path === targetString || path === dotlessTargetString;
474
- };
475
- },
476
- host: (targetString) => {
477
- return ({ url }) => targetString === getHost(url);
478
- }
479
- };
480
- const getHeaderMatcher = ({ headers: expectedHeaders }) => {
481
- if (!expectedHeaders) return;
482
- const expectation = normalizeHeaders(expectedHeaders);
483
- return ({ options: { headers = {} } }) => {
484
- const lowerCaseHeaders = normalizeHeaders(headers);
485
- return Object.keys(expectation).every((headerName) => lowerCaseHeaders[headerName] === expectation[headerName]);
486
- };
487
- };
488
- const getMissingHeaderMatcher = ({ missingHeaders: expectedMissingHeaders }) => {
489
- if (!expectedMissingHeaders) return;
490
- const expectation = expectedMissingHeaders.map((header) => header.toLowerCase());
491
- return ({ options: { headers = {} } }) => {
492
- const lowerCaseHeaders = normalizeHeaders(headers);
493
- return expectation.every((headerName) => !(headerName in lowerCaseHeaders));
494
- };
495
- };
496
- const getMethodMatcher = ({ method: expectedMethod }) => {
497
- if (!expectedMethod) return;
498
- return ({ options: { method } = {} }) => {
499
- return expectedMethod === (method ? method.toLowerCase() : "get");
500
- };
501
- };
502
- const getQueryParamsMatcher = ({ query: passedQuery }) => {
503
- if (!passedQuery) return;
504
- const expectedQuery = new URLSearchParams();
505
- for (const [key, value] of Object.entries(passedQuery)) if (Array.isArray(value)) for (const item of value) expectedQuery.append(key, typeof item === "object" || typeof item === "undefined" ? "" : item.toString());
506
- else expectedQuery.append(key, typeof value === "object" || typeof value === "undefined" ? "" : value.toString());
507
- const keys = Array.from(expectedQuery.keys());
508
- return ({ queryParams }) => {
509
- return keys.every((key) => {
510
- const expectedValues = expectedQuery.getAll(key).sort();
511
- const actualValues = queryParams.getAll(key).sort();
512
- if (expectedValues.length !== actualValues.length) return false;
513
- if (Array.isArray(passedQuery[key])) return expectedValues.every((expected, index) => expected === actualValues[index]);
514
- return dequal(actualValues, expectedValues);
515
- });
516
- };
517
- };
518
- const getExpressParamsMatcher = ({ params: expectedParams, url }) => {
519
- if (!expectedParams) return;
520
- if (!(typeof url === "string" && /express:/.test(url))) throw new Error("fetch-mock: matching on params is only possible when using an express: matcher");
521
- const expectedKeys = Object.keys(expectedParams);
522
- return ({ expressParams = {} }) => {
523
- return expectedKeys.every((key) => expressParams[key] === expectedParams[key]);
524
- };
525
- };
526
- const formDataToObject = (formData) => {
527
- const fields = [...formData];
528
- const result = {};
529
- fields.forEach(([key, value]) => {
530
- result[key] = result[key] || [];
531
- result[key].push(value);
532
- });
533
- return result;
534
- };
535
- const getBodyMatcher = (route) => {
536
- let { body: expectedBody } = route;
537
- let expectedBodyType = "json";
538
- if (!expectedBody) return;
539
- if (expectedBody instanceof FormData) {
540
- expectedBodyType = "formData";
541
- expectedBody = formDataToObject(expectedBody);
542
- }
543
- return ({ options: { body, method = "get" } }) => {
544
- if (["get", "head"].includes(method.toLowerCase())) return false;
545
- let sentBody;
546
- try {
547
- if (typeof body === "string") {
548
- sentBody = JSON.parse(body);
549
- if (expectedBodyType !== "json") return false;
550
- }
551
- } catch {}
552
- if (body instanceof FormData) {
553
- if (expectedBodyType !== "formData") return false;
554
- sentBody = formDataToObject(body);
555
- }
556
- return sentBody && (route.matchPartialBody ? isSubsetOf(expectedBody, sentBody) : dequal(expectedBody, sentBody));
557
- };
558
- };
559
- const getFunctionMatcher = ({ matcherFunction }) => matcherFunction;
560
- const getRegexpMatcher = (regexp) => ({ url }) => regexp.test(url);
561
- const getFullUrlMatcher = (route, matcherUrl, query) => {
562
- const expectedUrl = normalizeUrl(matcherUrl, route.allowRelativeUrls);
563
- if (route.url === matcherUrl) route.url = expectedUrl;
564
- return ({ url }) => {
565
- if (query && expectedUrl.indexOf("?")) return getPath(url) === getPath(expectedUrl);
566
- return normalizeUrl(url, true) === expectedUrl;
567
- };
568
- };
569
- const getUrlMatcher = (route) => {
570
- const { url: matcherUrl, query } = route;
571
- if (matcherUrl === "*") return () => true;
572
- if (matcherUrl instanceof RegExp) return getRegexpMatcher(matcherUrl);
573
- if (matcherUrl instanceof URL) {
574
- if (matcherUrl.href) return getFullUrlMatcher(route, matcherUrl.href, query);
575
- }
576
- if (typeof matcherUrl === "string") {
577
- for (const shorthand in stringMatchers) if (matcherUrl.indexOf(`${shorthand}:`) === 0) {
578
- const urlFragment = matcherUrl.replace(new RegExp(`^${shorthand}:`), "");
579
- return stringMatchers[shorthand](urlFragment);
580
- }
581
- return getFullUrlMatcher(route, matcherUrl, query);
582
- }
583
- if (typeof matcherUrl === "object") {
584
- const matchers = Object.entries(matcherUrl).map(([key, pattern]) => {
585
- if (key === "regexp") return getRegexpMatcher(pattern);
586
- else if (key in stringMatchers) return stringMatchers[key](pattern);
587
- else throw new Error(`unrecognised url matching pattern: ${key}`);
588
- });
589
- return (route) => matchers.every((matcher) => matcher(route));
590
- }
591
- };
592
- const builtInMatchers = [
593
- {
594
- name: "url",
595
- matcher: getUrlMatcher
596
- },
597
- {
598
- name: "query",
599
- matcher: getQueryParamsMatcher
600
- },
601
- {
602
- name: "method",
603
- matcher: getMethodMatcher
604
- },
605
- {
606
- name: "headers",
607
- matcher: getHeaderMatcher
608
- },
609
- {
610
- name: "missingHeaders",
611
- matcher: getMissingHeaderMatcher
612
- },
613
- {
614
- name: "params",
615
- matcher: getExpressParamsMatcher
616
- },
617
- {
618
- name: "body",
619
- matcher: getBodyMatcher,
620
- usesBody: true
621
- },
622
- {
623
- name: "matcherFunction",
624
- matcher: getFunctionMatcher
625
- }
626
- ];
627
- //#endregion
628
- //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/StatusTextMap.js
629
- const statusTextMap = {
630
- 100: "Continue",
631
- 101: "Switching Protocols",
632
- 102: "Processing",
633
- 200: "OK",
634
- 201: "Created",
635
- 202: "Accepted",
636
- 203: "Non-Authoritative Information",
637
- 204: "No Content",
638
- 205: "Reset Content",
639
- 206: "Partial Content",
640
- 207: "Multi-Status",
641
- 208: "Already Reported",
642
- 226: "IM Used",
643
- 300: "Multiple Choices",
644
- 301: "Moved Permanently",
645
- 302: "Found",
646
- 303: "See Other",
647
- 304: "Not Modified",
648
- 305: "Use Proxy",
649
- 307: "Temporary Redirect",
650
- 308: "Permanent Redirect",
651
- 400: "Bad Request",
652
- 401: "Unauthorized",
653
- 402: "Payment Required",
654
- 403: "Forbidden",
655
- 404: "Not Found",
656
- 405: "Method Not Allowed",
657
- 406: "Not Acceptable",
658
- 407: "Proxy Authentication Required",
659
- 408: "Request Timeout",
660
- 409: "Conflict",
661
- 410: "Gone",
662
- 411: "Length Required",
663
- 412: "Precondition Failed",
664
- 413: "Payload Too Large",
665
- 414: "URI Too Long",
666
- 415: "Unsupported Media Type",
667
- 416: "Range Not Satisfiable",
668
- 417: "Expectation Failed",
669
- 418: "I'm a teapot",
670
- 421: "Misdirected Request",
671
- 422: "Unprocessable Entity",
672
- 423: "Locked",
673
- 424: "Failed Dependency",
674
- 425: "Unordered Collection",
675
- 426: "Upgrade Required",
676
- 428: "Precondition Required",
677
- 429: "Too Many Requests",
678
- 431: "Request Header Fields Too Large",
679
- 451: "Unavailable For Legal Reasons",
680
- 500: "Internal Server Error",
681
- 501: "Not Implemented",
682
- 502: "Bad Gateway",
683
- 503: "Service Unavailable",
684
- 504: "Gateway Timeout",
685
- 505: "HTTP Version Not Supported",
686
- 506: "Variant Also Negotiates",
687
- 507: "Insufficient Storage",
688
- 508: "Loop Detected",
689
- 509: "Bandwidth Limit Exceeded",
690
- 510: "Not Extended",
691
- 511: "Network Authentication Required"
692
- };
693
- //#endregion
694
- //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/Route.js
695
- var __classPrivateFieldSet = function(receiver, state, value, kind, f) {
696
- if (kind === "m") throw new TypeError("Private method is not writable");
697
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
698
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
699
- return kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value), value;
700
- };
701
- var __classPrivateFieldGet = function(receiver, state, kind, f) {
702
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
703
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
704
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
705
- };
706
- var _Route_instances, _a, _Route_responseSubscriptions, _Route_validate, _Route_sanitize, _Route_generateMatcher, _Route_limit, _Route_delayResponse;
707
- var RouteConfigWrapper = class {
708
- constructor(config) {
709
- Object.assign(this, config);
710
- }
711
- };
712
- function isBodyInit(body) {
713
- return body instanceof Blob || body instanceof ArrayBuffer || ArrayBuffer.isView(body) || body instanceof DataView || body instanceof FormData || body instanceof ReadableStream || body instanceof URLSearchParams || body instanceof String || typeof body === "string" || body === null;
714
- }
715
- function sanitizeStatus(status) {
716
- if (status === 0) return 200;
717
- if (!status) return 200;
718
- if (typeof status === "number" && parseInt(String(status), 10) !== status && status >= 200 || status < 600) return status;
719
- throw new TypeError(`fetch-mock: Invalid status ${status} passed on response object.
720
- To respond with a JSON object that has status as a property assign the object to body
721
- e.g. {"body": {"status: "registered"}}`);
722
- }
723
- var Route = class {
724
- constructor(config) {
725
- _Route_instances.add(this);
726
- _Route_responseSubscriptions.set(this, void 0);
727
- this.init(config);
728
- }
729
- init(config) {
730
- this.config = config;
731
- __classPrivateFieldSet(this, _Route_responseSubscriptions, [], "f");
732
- __classPrivateFieldGet(this, _Route_instances, "m", _Route_sanitize).call(this);
733
- __classPrivateFieldGet(this, _Route_instances, "m", _Route_validate).call(this);
734
- __classPrivateFieldGet(this, _Route_instances, "m", _Route_generateMatcher).call(this);
735
- __classPrivateFieldGet(this, _Route_instances, "m", _Route_limit).call(this);
736
- __classPrivateFieldGet(this, _Route_instances, "m", _Route_delayResponse).call(this);
737
- }
738
- reset() {}
739
- waitFor(awaitedRoutes) {
740
- const { response } = this.config;
741
- this.config.response = Promise.all(awaitedRoutes.map((awaitedRoute) => new Promise((res) => awaitedRoute.onRespond(() => {
742
- res(void 0);
743
- })))).then(() => response);
744
- }
745
- onRespond(func) {
746
- __classPrivateFieldGet(this, _Route_responseSubscriptions, "f").push(func);
747
- }
748
- constructResponse(responseInput) {
749
- const responseOptions = this.constructResponseOptions(responseInput);
750
- const body = this.constructResponseBody(responseInput, responseOptions);
751
- const responsePackage = {
752
- response: new this.config.Response(body, responseOptions),
753
- responseOptions,
754
- responseInput
755
- };
756
- __classPrivateFieldGet(this, _Route_responseSubscriptions, "f").forEach((func) => func());
757
- return responsePackage;
758
- }
759
- constructResponseOptions(responseInput) {
760
- const options = responseInput.options || {};
761
- options.status = sanitizeStatus(responseInput.status);
762
- options.statusText = statusTextMap[options.status];
763
- options.headers = new this.config.Headers(responseInput.headers);
764
- return options;
765
- }
766
- constructResponseBody(responseInput, responseOptions) {
767
- let body = responseInput.body;
768
- if (!isBodyInit(body)) if (typeof body === "undefined") body = null;
769
- else if (typeof body === "object") {
770
- body = JSON.stringify(body);
771
- if (!responseOptions.headers.has("Content-Type")) responseOptions.headers.set("Content-Type", "application/json");
772
- } else throw new TypeError("Invalid body provided to construct response");
773
- if (this.config.includeContentLength && !responseOptions.headers.has("Content-Length") && !(body instanceof ReadableStream) && !(body instanceof FormData)) {
774
- let length = 0;
775
- if (body instanceof Blob) length = body.size;
776
- else if (body instanceof ArrayBuffer || ArrayBuffer.isView(body) || body instanceof DataView) length = body.byteLength;
777
- else if (body instanceof URLSearchParams) length = body.toString().length;
778
- else if (typeof body === "string" || body instanceof String) length = body.length;
779
- responseOptions.headers.set("Content-Length", length.toString());
780
- }
781
- return body;
782
- }
783
- static defineMatcher(matcher) {
784
- _a.registeredMatchers.push(matcher);
785
- }
786
- };
787
- _a = Route, _Route_responseSubscriptions = /* @__PURE__ */ new WeakMap(), _Route_instances = /* @__PURE__ */ new WeakSet(), _Route_validate = function _Route_validate() {
788
- if (["matched", "unmatched"].includes(this.config.name)) throw new Error(`fetch-mock: Routes cannot use the reserved name \`${this.config.name}\``);
789
- if (!("response" in this.config)) throw new Error("fetch-mock: Each route must define a response");
790
- if (!_a.registeredMatchers.some(({ name }) => name in this.config)) throw new Error("fetch-mock: Each route must specify some criteria for matching calls to fetch. To match all calls use '*'");
791
- }, _Route_sanitize = function _Route_sanitize() {
792
- if (this.config.method) this.config.method = this.config.method.toLowerCase();
793
- }, _Route_generateMatcher = function _Route_generateMatcher() {
794
- const activeMatchers = _a.registeredMatchers.filter(({ name }) => name in this.config).map(({ matcher, usesBody }) => ({
795
- matcher: matcher(this.config),
796
- usesBody
797
- }));
798
- this.config.usesBody = activeMatchers.some(({ usesBody }) => usesBody);
799
- this.matcher = (normalizedRequest) => activeMatchers.every(({ matcher }) => matcher(normalizedRequest));
800
- }, _Route_limit = function _Route_limit() {
801
- if (!this.config.repeat) return;
802
- const originalMatcher = this.matcher;
803
- let timesLeft = this.config.repeat;
804
- this.matcher = (callLog) => {
805
- if (timesLeft && originalMatcher(callLog)) {
806
- timesLeft--;
807
- return true;
808
- }
809
- };
810
- this.reset = () => {
811
- timesLeft = this.config.repeat;
812
- };
813
- }, _Route_delayResponse = function _Route_delayResponse() {
814
- if (this.config.delay) {
815
- const { response } = this.config;
816
- this.config.response = () => {
817
- return new Promise((res) => setTimeout(() => res(response), this.config.delay));
818
- };
819
- }
820
- };
821
- Route.registeredMatchers = [];
822
- builtInMatchers.forEach(Route.defineMatcher);
823
- //#endregion
824
- //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/Router.js
825
- const responseConfigProps = [
826
- "body",
827
- "headers",
828
- "throws",
829
- "status",
830
- "redirectUrl"
831
- ];
832
- function nameToOptions(options) {
833
- return typeof options === "string" ? { name: options } : options;
834
- }
835
- function isPromise(response) {
836
- return typeof response.then === "function";
837
- }
838
- function normalizeResponseInput(responseInput) {
839
- if (typeof responseInput === "number") return { status: responseInput };
840
- else if (typeof responseInput === "string" || shouldSendAsObject(responseInput)) return { body: responseInput };
841
- return responseInput;
842
- }
843
- function shouldSendAsObject(responseInput) {
844
- if (responseConfigProps.some((prop) => prop in responseInput)) {
845
- if (Object.keys(responseInput).every((key) => responseConfigProps.includes(key))) return false;
846
- return true;
847
- }
848
- return true;
849
- }
850
- function throwSpecExceptions({ url, options: { headers, method, body } }) {
851
- if (headers) Object.entries(headers).forEach(([key]) => {
852
- if (/\s/.test(key)) throw new TypeError("Invalid name");
853
- });
854
- if (hasCredentialsInUrl(url)) throw new TypeError(`Request cannot be constructed from a URL that includes credentials: ${url}`);
855
- if (["get", "head"].includes(method) && body) throw new TypeError("Request with GET/HEAD method cannot have body.");
856
- }
857
- const resolveUntilResponseConfig = async (callLog) => {
858
- let response = callLog.route.config.response;
859
- while (true) if (typeof response === "function") response = response(callLog);
860
- else if (isPromise(response)) response = await response;
861
- else return response;
862
- };
863
- var Router = class {
864
- constructor(fetchMockConfig, { routes, fallbackRoute } = {}) {
865
- this.config = fetchMockConfig;
866
- this.routes = routes || [];
867
- this.fallbackRoute = fallbackRoute;
868
- }
869
- needsToReadBody(request) {
870
- return Boolean(request && this.routes.some((route) => route.config.usesBody));
871
- }
872
- execute(callLog) {
873
- throwSpecExceptions(callLog);
874
- return new Promise(async (resolve, reject) => {
875
- const { url, options, request, pendingPromises } = callLog;
876
- let eventListenerAbortController;
877
- if (callLog.signal) {
878
- const abort = () => {
879
- const error = new DOMException("The operation was aborted.", "AbortError");
880
- const requestBody = request?.body || options?.body;
881
- if (requestBody instanceof ReadableStream) if (requestBody.locked) console.warn("fetch-mock: Locked request body can't be cancelled");
882
- else requestBody.cancel(error);
883
- if (callLog?.response?.body) if (callLog.response.body.locked) console.warn("fetch-mock: Locked response body can't be cancelled");
884
- else callLog.response.body.cancel(error);
885
- reject(error);
886
- };
887
- if (callLog.signal.aborted) abort();
888
- eventListenerAbortController = new AbortController();
889
- callLog.signal.addEventListener("abort", abort, {
890
- once: true,
891
- signal: eventListenerAbortController.signal
892
- });
893
- }
894
- if (this.needsToReadBody(request)) options.body = await options.body;
895
- const route = (this.fallbackRoute ? [...this.routes, this.fallbackRoute] : this.routes).find((route) => route.matcher(callLog));
896
- if (route) try {
897
- callLog.route = route;
898
- const { response, responseOptions, responseInput } = await this.generateResponse(callLog);
899
- const observableResponse = this.createObservableResponse(response, responseOptions, responseInput, url, pendingPromises);
900
- callLog.response = response;
901
- resolve(observableResponse);
902
- } catch (err) {
903
- reject(err);
904
- } finally {
905
- eventListenerAbortController?.abort();
906
- }
907
- else reject(/* @__PURE__ */ new Error(`fetch-mock: No response or fallback rule to cover ${options && options.method || "GET"} to ${url}`));
908
- });
909
- }
910
- async generateResponse(callLog) {
911
- const responseInput = await resolveUntilResponseConfig(callLog);
912
- if (responseInput instanceof Response) return {
913
- response: responseInput.clone(),
914
- responseOptions: {},
915
- responseInput: {}
916
- };
917
- const responseConfig = normalizeResponseInput(responseInput);
918
- if (responseConfig.throws) throw responseConfig.throws;
919
- return callLog.route.constructResponse(responseConfig);
920
- }
921
- createObservableResponse(response, responseConfig, responseInput, responseUrl, pendingPromises) {
922
- return new Proxy(response, { get: (originalResponse, name) => {
923
- if (responseInput.redirectUrl) {
924
- if (name === "url") return responseInput.redirectUrl;
925
- if (name === "redirected") return true;
926
- } else {
927
- if (name === "url") return responseUrl;
928
- if (name === "redirected") return false;
929
- }
930
- if (responseInput.status === 0) {
931
- if (name === "status") return 0;
932
- if (name === "statusText") return "";
933
- }
934
- if (typeof response[name] === "function") return new Proxy(response[name], { apply: (func, thisArg, args) => {
935
- const result = func.apply(response, args);
936
- if (result.then) pendingPromises.push(result.catch(() => void 0));
937
- return result;
938
- } });
939
- return originalResponse[name];
940
- } });
941
- }
942
- addRoute(matcher, response, nameOrOptions) {
943
- const config = {};
944
- if (matcher instanceof RouteConfigWrapper) Object.assign(config, matcher);
945
- if (isUrlMatcher(matcher)) config.url = matcher;
946
- else if (isFunctionMatcher(matcher)) config.matcherFunction = matcher;
947
- else Object.assign(config, matcher);
948
- if (typeof response !== "undefined") config.response = response;
949
- if (nameOrOptions) Object.assign(config, typeof nameOrOptions === "string" ? nameToOptions(nameOrOptions) : nameOrOptions);
950
- const route = new Route({
951
- ...this.config,
952
- ...config
953
- });
954
- if (route.config.name && this.routes.some(({ config: { name: existingName } }) => route.config.name === existingName)) throw new Error("fetch-mock: Adding route with same name as existing route.");
955
- if (route.config.waitFor) {
956
- const routeNamesToWaitFor = Array.isArray(route.config.waitFor) ? route.config.waitFor : [route.config.waitFor];
957
- const routesToAwait = [];
958
- routeNamesToWaitFor.forEach((routeName) => {
959
- const routeToAwait = this.routes.find(({ config: { name: existingName } }) => routeName === existingName);
960
- if (routeToAwait) routesToAwait.push(routeToAwait);
961
- else throw new Error(`Cannot wait for route \`${routeName}\`: route of that name does not exist`);
962
- });
963
- route.waitFor(routesToAwait);
964
- }
965
- this.routes.push(route);
966
- }
967
- setFallback(response) {
968
- if (this.fallbackRoute) console.warn("calling fetchMock.catch() twice - are you sure you want to overwrite the previous fallback response");
969
- this.fallbackRoute = new Route({
970
- matcherFunction: () => true,
971
- response: response || "ok",
972
- ...this.config
973
- });
974
- this.fallbackRoute.config.isFallback = true;
975
- }
976
- removeRoutes({ names, includeSticky, includeFallback } = {}) {
977
- includeFallback = includeFallback ?? true;
978
- this.routes = this.routes.filter(({ config: { sticky, name } }) => {
979
- if (sticky && !includeSticky) return true;
980
- if (!names) return false;
981
- return !names.includes(name);
982
- });
983
- if (includeFallback) delete this.fallbackRoute;
984
- }
985
- modifyRoute(routeName, options) {
986
- const route = this.routes.find(({ config: { name } }) => name === routeName);
987
- if (!route) throw new Error(`Cannot call modifyRoute() on route \`${routeName}\`: route of that name not found`);
988
- if (route.config.sticky) throw new Error(`Cannot call modifyRoute() on route \`${routeName}\`: route is sticky and cannot be modified`);
989
- if ("name" in options) throw new Error(`Cannot rename the route \`${routeName}\` as \`${options.name}\`: renaming routes is not supported`);
990
- if ("sticky" in options) throw new Error(`Altering the stickiness of route \`${routeName}\` is not supported`);
991
- const newConfig = {
992
- ...route.config,
993
- ...options
994
- };
995
- Object.entries(options).forEach(([key, value]) => {
996
- if (value === null) delete newConfig[key];
997
- });
998
- route.init(newConfig);
999
- }
1000
- };
1001
- //#endregion
1002
- //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/CallHistory.js
1003
- const isName = (filter) => typeof filter === "string" && /^[\da-zA-Z-]+$/.test(filter) && !["matched", "unmatched"].includes(filter);
1004
- const isMatchedOrUnmatched = (filter) => typeof filter === "boolean" || ["matched", "unmatched"].includes(filter);
1005
- var CallHistory = class {
1006
- constructor(config, router) {
1007
- this.callLogs = [];
1008
- this.config = config;
1009
- this.router = router;
1010
- }
1011
- recordCall(callLog) {
1012
- this.callLogs.push(callLog);
1013
- }
1014
- clear() {
1015
- this.callLogs.forEach(({ route }) => {
1016
- if (route) route.reset();
1017
- });
1018
- this.callLogs = [];
1019
- }
1020
- async flush(waitForResponseMethods) {
1021
- const queuedPromises = this.callLogs.flatMap((call) => call.pendingPromises);
1022
- await Promise.allSettled(queuedPromises);
1023
- if (waitForResponseMethods) {
1024
- await Promise.resolve();
1025
- await this.flush();
1026
- }
1027
- }
1028
- calls(filter, options) {
1029
- let calls = [...this.callLogs];
1030
- if (typeof filter === "undefined" && !options) return calls;
1031
- if (isMatchedOrUnmatched(filter)) {
1032
- if ([true, "matched"].includes(filter)) calls = calls.filter(({ route }) => !route?.config || !route.config.isFallback);
1033
- else if ([false, "unmatched"].includes(filter)) calls = calls.filter(({ route }) => Boolean(route?.config && route.config.isFallback));
1034
- if (!options) return calls;
1035
- } else if (isName(filter)) {
1036
- calls = calls.filter(({ route }) => route?.config?.name === filter);
1037
- if (!options) return calls;
1038
- } else if (isUrlMatcher(filter)) options = {
1039
- url: filter,
1040
- ...options || {}
1041
- };
1042
- else options = {
1043
- ...filter,
1044
- ...options || {}
1045
- };
1046
- const { matcher } = new Route({
1047
- response: "ok",
1048
- ...options
1049
- });
1050
- calls = calls.filter(({ url, options }) => {
1051
- return matcher(createCallLogFromUrlAndOptions(url, options));
1052
- });
1053
- return calls;
1054
- }
1055
- called(filter, options) {
1056
- return Boolean(this.calls(filter, options).length);
1057
- }
1058
- lastCall(filter, options) {
1059
- return this.calls(filter, options).pop();
1060
- }
1061
- done(routeNames) {
1062
- let routesToCheck = this.router.routes;
1063
- if (routeNames) {
1064
- routeNames = Array.isArray(routeNames) ? routeNames : [routeNames];
1065
- routesToCheck = this.router.routes.filter(({ config: { name } }) => routeNames.includes(name));
1066
- }
1067
- return routesToCheck.map((route) => {
1068
- const calls = this.callLogs.filter(({ route: routeApplied }) => routeApplied === route);
1069
- if (!calls.length) {
1070
- console.warn(`Warning: ${route.config.name} not called`);
1071
- return false;
1072
- }
1073
- const expectedTimes = route.config.repeat;
1074
- if (!expectedTimes) return true;
1075
- const actualTimes = calls.length;
1076
- if (expectedTimes > actualTimes) {
1077
- console.warn(`Warning: ${route.config.name} only called ${actualTimes} times, but ${expectedTimes} expected`);
1078
- return false;
1079
- }
1080
- return true;
1081
- }).every((isDone) => isDone);
1082
- }
1083
- };
1084
- //#endregion
1085
- //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/FetchMock.js
1086
- const defaultFetchMockConfig = {
1087
- includeContentLength: true,
1088
- matchPartialBody: false,
1089
- Request: globalThis.Request,
1090
- Response: globalThis.Response,
1091
- Headers: globalThis.Headers,
1092
- fetch: globalThis.fetch
1093
- };
1094
- const defineShorthand = (shorthandOptions) => {
1095
- function shorthand(matcher, response, options) {
1096
- return this.route(matcher, response, Object.assign(options || {}, shorthandOptions));
1097
- }
1098
- return shorthand;
1099
- };
1100
- const defineGreedyShorthand = (shorthandOptions) => {
1101
- return function(response, options) {
1102
- return this.route("*", response, Object.assign(options || {}, shorthandOptions));
1103
- };
1104
- };
1105
- //#endregion
1106
- //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/index.js
1107
- var esm_default = new class FetchMock {
1108
- constructor(config, router) {
1109
- this.sticky = defineShorthand({ sticky: true });
1110
- this.once = defineShorthand({ repeat: 1 });
1111
- this.any = defineGreedyShorthand({});
1112
- this.anyOnce = defineGreedyShorthand({ repeat: 1 });
1113
- this.get = defineShorthand({ method: "get" });
1114
- this.getOnce = defineShorthand({
1115
- method: "get",
1116
- repeat: 1
1117
- });
1118
- this.post = defineShorthand({ method: "post" });
1119
- this.postOnce = defineShorthand({
1120
- method: "post",
1121
- repeat: 1
1122
- });
1123
- this.put = defineShorthand({ method: "put" });
1124
- this.putOnce = defineShorthand({
1125
- method: "put",
1126
- repeat: 1
1127
- });
1128
- this.delete = defineShorthand({ method: "delete" });
1129
- this.deleteOnce = defineShorthand({
1130
- method: "delete",
1131
- repeat: 1
1132
- });
1133
- this.head = defineShorthand({ method: "head" });
1134
- this.headOnce = defineShorthand({
1135
- method: "head",
1136
- repeat: 1
1137
- });
1138
- this.patch = defineShorthand({ method: "patch" });
1139
- this.patchOnce = defineShorthand({
1140
- method: "patch",
1141
- repeat: 1
1142
- });
1143
- this.config = config;
1144
- this.router = new Router(this.config, {
1145
- routes: router ? [...router.routes] : [],
1146
- fallbackRoute: router ? router.fallbackRoute : null
1147
- });
1148
- this.callHistory = new CallHistory(this.config, this.router);
1149
- this.fetchHandler = this.fetchHandler.bind(this);
1150
- Object.assign(this.fetchHandler, { fetchMock: this });
1151
- }
1152
- createInstance() {
1153
- return new FetchMock({ ...this.config }, this.router);
1154
- }
1155
- async fetchHandler(requestInput, requestInit) {
1156
- let callLog;
1157
- if (requestInput instanceof this.config.Request) callLog = await createCallLogFromRequest(requestInput, requestInit);
1158
- else callLog = createCallLogFromUrlAndOptions(requestInput, requestInit);
1159
- this.callHistory.recordCall(callLog);
1160
- const responsePromise = this.router.execute(callLog);
1161
- callLog.pendingPromises.push(responsePromise);
1162
- return responsePromise;
1163
- }
1164
- route(matcher, response, options) {
1165
- this.router.addRoute(matcher, response, options);
1166
- return this;
1167
- }
1168
- catch(response) {
1169
- this.router.setFallback(response);
1170
- return this;
1171
- }
1172
- defineMatcher(matcher) {
1173
- Route.defineMatcher(matcher);
1174
- }
1175
- removeRoutes(options) {
1176
- this.router.removeRoutes(options);
1177
- return this;
1178
- }
1179
- removeRoute(routeName) {
1180
- this.router.removeRoutes({ names: [routeName] });
1181
- return this;
1182
- }
1183
- modifyRoute(routeName, options) {
1184
- this.router.modifyRoute(routeName, options);
1185
- return this;
1186
- }
1187
- clearHistory() {
1188
- this.callHistory.clear();
1189
- return this;
1190
- }
1191
- mockGlobal() {
1192
- globalThis.fetch = this.fetchHandler;
1193
- return this;
1194
- }
1195
- unmockGlobal() {
1196
- globalThis.fetch = this.config.fetch;
1197
- return this;
1198
- }
1199
- hardReset(options) {
1200
- this.clearHistory();
1201
- this.removeRoutes(options);
1202
- this.unmockGlobal();
1203
- return this;
1204
- }
1205
- spy(matcher, name) {
1206
- const boundFetch = this.config.fetch.bind(globalThis);
1207
- if (matcher) this.route(matcher, ({ args }) => boundFetch(...args), name);
1208
- else this.catch(({ args }) => boundFetch(...args));
1209
- return this;
1210
- }
1211
- spyGlobal() {
1212
- this.mockGlobal();
1213
- return this.spy();
1214
- }
1215
- }({ ...defaultFetchMockConfig });
1216
- //#endregion
1217
16
  //#region src/contexts/cid-v1.json
1218
17
  var cid_v1_default = { "@context": {
1219
18
  "@protected": true,
@@ -5712,13 +4511,47 @@ const DEFAULT_MAX_REDIRECTION = 20;
5712
4511
  * Gets a {@link RemoteDocument} from the given response.
5713
4512
  * @param url The URL of the document to load.
5714
4513
  * @param response The response to get the document from.
5715
- * @param fetch The function to fetch the document.
4514
+ * @param fetch The function to fetch the document. It is called with the URL
4515
+ * of an alternate document, if any, after the response body has
4516
+ * been either read or its cancellation has been started.
5716
4517
  * @returns The loaded remote document.
5717
4518
  * @throws {FetchError} If the response is not OK.
5718
4519
  * @internal
5719
4520
  */
5720
4521
  async function getRemoteDocument(url, response, fetch) {
5721
- const documentUrl = response.url === "" ? url : response.url;
4522
+ const result = await parseRemoteDocument(url, response);
4523
+ if ("alternate" in result) return await fetch(result.alternate);
4524
+ return result.document;
4525
+ }
4526
+ /**
4527
+ * Starts cancelling the body of a response without waiting for it to settle.
4528
+ * The cancellation of a branch of a tee (e.g., a body cloned by a fetch mock)
4529
+ * settles only after the other branch is cancelled too, per the Streams
4530
+ * standard, so awaiting it could block forever. Failures are only logged.
4531
+ * @param response The response whose body is cancelled.
4532
+ * @param url The URL of the response, for logging.
4533
+ */
4534
+ function cancelResponseBody(response, url) {
4535
+ response.body?.cancel().catch((error) => {
4536
+ logger.debug("Failed to cancel the response body from {url}: {error}", {
4537
+ url,
4538
+ error
4539
+ });
4540
+ });
4541
+ }
4542
+ /**
4543
+ * Gets a {@link RemoteDocument} from the given response, or the URL of an
4544
+ * alternate document to load instead. When an alternate document is found,
4545
+ * the response body has been either read or its cancellation has been started
4546
+ * (without waiting for it), so that the alternate document can be loaded after
4547
+ * the response's session is closed.
4548
+ * @param url The URL of the document to load.
4549
+ * @param response The response to get the document from.
4550
+ * @returns The loaded remote document, or the alternate document URL.
4551
+ * @throws {FetchError} If the response is not OK.
4552
+ */
4553
+ async function parseRemoteDocument(url, response) {
4554
+ const documentUrl = url;
5722
4555
  const docUrl = new URL(documentUrl);
5723
4556
  if (!response.ok) {
5724
4557
  logger.error("Failed to fetch document: {status} {url} {headers}", {
@@ -5755,7 +4588,8 @@ async function getRemoteDocument(url, response, fetch) {
5755
4588
  alternateUrl: altUri.href,
5756
4589
  url: documentUrl
5757
4590
  });
5758
- return await fetch(altUri.href);
4591
+ cancelResponseBody(response, documentUrl);
4592
+ return { alternate: altUri.href };
5759
4593
  }
5760
4594
  }
5761
4595
  }
@@ -5780,7 +4614,7 @@ async function getRemoteDocument(url, response, fetch) {
5780
4614
  alternateUrl: attribs.href,
5781
4615
  url: documentUrl
5782
4616
  });
5783
- return await fetch(new URL(attribs.href, docUrl).href);
4617
+ return { alternate: new URL(attribs.href, docUrl).href };
5784
4618
  }
5785
4619
  }
5786
4620
  document = JSON.parse(html);
@@ -5790,11 +4624,11 @@ async function getRemoteDocument(url, response, fetch) {
5790
4624
  url: documentUrl,
5791
4625
  headers: Object.fromEntries(response.headers.entries())
5792
4626
  });
5793
- return {
4627
+ return { document: {
5794
4628
  contextUrl,
5795
4629
  document,
5796
4630
  documentUrl
5797
- };
4631
+ } };
5798
4632
  }
5799
4633
  /**
5800
4634
  * Creates a JSON-LD document loader that utilizes the browser's `fetch` API.
@@ -5820,8 +4654,53 @@ async function getRemoteDocument(url, response, fetch) {
5820
4654
  * @returns The document loader.
5821
4655
  * @since 1.3.0
5822
4656
  */
5823
- function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAgent } = {}) {
4657
+ function getDocumentLoader(options = {}) {
4658
+ return createDocumentLoader(options);
4659
+ }
4660
+ /**
4661
+ * The implementation of {@link getDocumentLoader}, with injectable
4662
+ * dependencies. This is not exported from any entry point.
4663
+ * @param options Options for the document loader.
4664
+ * @param deps The dependencies.
4665
+ * @returns The document loader.
4666
+ */
4667
+ function createDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAgent } = {}, deps = {}) {
5824
4668
  const tracer = _opentelemetry_api.trace.getTracerProvider().getTracer(require_request.name, require_request.version);
4669
+ async function openHop(url, signal) {
4670
+ try {
4671
+ if (deps.openHop != null) return await deps.openHop(url);
4672
+ return allowPrivateAddress ? await require_public_fetch.openDirectFetch(url) : await require_public_fetch.openPublicFetch(url, { signal });
4673
+ } catch (error) {
4674
+ if (error instanceof require_url.UrlError) if (error.reason === "dns") logger.debug("DNS lookup failed for {url}", {
4675
+ url,
4676
+ error
4677
+ });
4678
+ else logger.error("Disallowed private URL: {url}", {
4679
+ url,
4680
+ error
4681
+ });
4682
+ throw error;
4683
+ }
4684
+ }
4685
+ async function fetchHop(session, url, span, options) {
4686
+ try {
4687
+ const request = require_request.createActivityPubRequest(url, { userAgent });
4688
+ require_request.logRequest(logger, request);
4689
+ const response = await session.fetch(request, {
4690
+ redirect: "manual",
4691
+ signal: options?.signal
4692
+ });
4693
+ span.setAttribute("http.response.status_code", response.status);
4694
+ if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
4695
+ const location = response.headers.get("Location");
4696
+ cancelResponseBody(response, url);
4697
+ return { location };
4698
+ }
4699
+ return await parseRemoteDocument(url, response);
4700
+ } finally {
4701
+ await session.close();
4702
+ }
4703
+ }
5825
4704
  async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
5826
4705
  options?.signal?.throwIfAborted();
5827
4706
  const parsedUrl = new URL(url);
@@ -5836,33 +4715,15 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
5836
4715
  documentUrl: currentUrl
5837
4716
  };
5838
4717
  }
5839
- if (!allowPrivateAddress) try {
5840
- await require_url.validatePublicUrl(currentUrl);
5841
- } catch (error) {
5842
- if (error instanceof require_url.UrlError) if (error.reason === "dns") logger.debug("DNS lookup failed for {url}", {
5843
- url: currentUrl,
5844
- error
5845
- });
5846
- else logger.error("Disallowed private URL: {url}", {
5847
- url: currentUrl,
5848
- error
5849
- });
5850
- throw error;
5851
- }
4718
+ const session = await openHop(currentUrl, options?.signal);
5852
4719
  visited.add(currentUrl);
5853
4720
  return await tracer.startActiveSpan("activitypub.fetch_document", {
5854
4721
  kind: _opentelemetry_api.SpanKind.CLIENT,
5855
4722
  attributes: { "url.full": currentUrl }
5856
4723
  }, async (span) => {
5857
4724
  try {
5858
- const request = require_request.createActivityPubRequest(currentUrl, { userAgent });
5859
- require_request.logRequest(logger, request);
5860
- const response = await fetch(request, {
5861
- redirect: "manual",
5862
- signal: options?.signal
5863
- });
5864
- span.setAttribute("http.response.status_code", response.status);
5865
- if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
4725
+ const outcome = await fetchHop(session, currentUrl, span, options);
4726
+ if ("location" in outcome) {
5866
4727
  if (redirected >= DEFAULT_MAX_REDIRECTION) {
5867
4728
  logger.error("Too many redirections ({redirections}) while fetching document.", {
5868
4729
  redirections: redirected + 1,
@@ -5870,7 +4731,7 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
5870
4731
  });
5871
4732
  throw new require_request.FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
5872
4733
  }
5873
- const redirectUrl = new URL(response.headers.get("Location"), response.url === "" ? currentUrl : response.url).href;
4734
+ const redirectUrl = new URL(outcome.location, currentUrl).href;
5874
4735
  span.setAttribute("http.redirect.url", redirectUrl);
5875
4736
  if (visited.has(redirectUrl)) {
5876
4737
  logger.error("Detected a redirect loop while fetching document: {url} -> {redirectUrl}", {
@@ -5881,12 +4742,14 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
5881
4742
  }
5882
4743
  return await load(redirectUrl, options, redirected + 1, visited);
5883
4744
  }
5884
- const result = await getRemoteDocument(currentUrl, response, async (alternateUrl) => {
4745
+ let result;
4746
+ if ("alternate" in outcome) {
4747
+ const alternateUrl = outcome.alternate;
5885
4748
  options?.signal?.throwIfAborted();
5886
4749
  if (redirected >= DEFAULT_MAX_REDIRECTION) throw new require_request.FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
5887
4750
  if (visited.has(alternateUrl)) throw new require_request.FetchError(currentUrl, `Redirect loop detected: ${alternateUrl}`);
5888
- return await load(alternateUrl, options, redirected + 1, visited);
5889
- });
4751
+ result = await load(alternateUrl, options, redirected + 1, visited);
4752
+ } else result = outcome.document;
5890
4753
  span.setAttribute("docloader.document_url", result.documentUrl);
5891
4754
  if (result.contextUrl != null) span.setAttribute("docloader.context_url", result.contextUrl);
5892
4755
  return result;
@@ -5909,6 +4772,24 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
5909
4772
  var jsonld_default = jsonld_dist_jsonld_esm_js.default;
5910
4773
  //#endregion
5911
4774
  //#region src/docloader.test.ts
4775
+ const unpinnedPublicFetch = require_public_fetch.createPublicFetch({
4776
+ pinner: () => Promise.resolve(null),
4777
+ lookup: () => Promise.reject(/* @__PURE__ */ new Error("Unexpected default lookup"))
4778
+ });
4779
+ const PUBLIC_ADDRESS = {
4780
+ address: "93.184.215.14",
4781
+ family: 4
4782
+ };
4783
+ const PRIVATE_ADDRESS = {
4784
+ address: "10.0.0.1",
4785
+ family: 4
4786
+ };
4787
+ function lookupTestAddresses(hostname) {
4788
+ return Promise.resolve([hostname === "private.example" ? PRIVATE_ADDRESS : PUBLIC_ADDRESS]);
4789
+ }
4790
+ function openUnpinnedHop(url) {
4791
+ return unpinnedPublicFetch.openPublicFetch(url, { lookup: lookupTestAddresses });
4792
+ }
5912
4793
  (0, _fedify_fixture.test)("new FetchError()", () => {
5913
4794
  const e = new require_request.FetchError("https://example.com/", "An error message.");
5914
4795
  (0, node_assert.deepStrictEqual)(e.name, "FetchError");
@@ -5919,9 +4800,9 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
5919
4800
  (0, node_assert.deepStrictEqual)(e2.message, "https://example.org/");
5920
4801
  });
5921
4802
  (0, _fedify_fixture.test)("getDocumentLoader()", async (t) => {
5922
- const fetchDocumentLoader = getDocumentLoader();
5923
- esm_default.spyGlobal();
5924
- esm_default.get("https://example.com/object", { body: {
4803
+ const fetchDocumentLoader = createDocumentLoader({}, { openHop: openUnpinnedHop });
4804
+ require_esm.esm_default.spyGlobal();
4805
+ require_esm.esm_default.get("https://example.com/object", { body: {
5925
4806
  "@context": "https://www.w3.org/ns/activitystreams",
5926
4807
  id: "https://example.com/object",
5927
4808
  name: "Fetched object",
@@ -5939,7 +4820,7 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
5939
4820
  }
5940
4821
  });
5941
4822
  });
5942
- esm_default.get("https://example.com/link-ctx", {
4823
+ require_esm.esm_default.get("https://example.com/link-ctx", {
5943
4824
  body: {
5944
4825
  id: "https://example.com/link-ctx",
5945
4826
  name: "Fetched object",
@@ -5950,15 +4831,15 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
5950
4831
  Link: "<https://www.w3.org/ns/activitystreams>; rel=\"http://www.w3.org/ns/json-ld#context\"; type=\"application/ld+json\""
5951
4832
  }
5952
4833
  });
5953
- esm_default.get("https://example.com/link-obj", { headers: {
4834
+ require_esm.esm_default.get("https://example.com/link-obj", { headers: {
5954
4835
  "Content-Type": "text/html; charset=utf-8",
5955
4836
  Link: "<https://example.com/object>; rel=\"alternate\"; type=\"application/activity+json\""
5956
4837
  } });
5957
- esm_default.get("https://example.com/link-obj-relative", { headers: {
4838
+ require_esm.esm_default.get("https://example.com/link-obj-relative", { headers: {
5958
4839
  "Content-Type": "text/html; charset=utf-8",
5959
4840
  Link: "</object>; rel=\"alternate\"; type=\"application/activity+json\""
5960
4841
  } });
5961
- esm_default.get("https://example.com/obj-w-wrong-link", {
4842
+ require_esm.esm_default.get("https://example.com/obj-w-wrong-link", {
5962
4843
  body: {
5963
4844
  "@context": "https://www.w3.org/ns/activitystreams",
5964
4845
  id: "https://example.com/obj-w-wrong-link",
@@ -6024,7 +4905,7 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6024
4905
  }
6025
4906
  });
6026
4907
  });
6027
- esm_default.get("https://example.com/html-link", {
4908
+ require_esm.esm_default.get("https://example.com/html-link", {
6028
4909
  body: `<html>
6029
4910
  <head>
6030
4911
  <meta charset=utf-8>
@@ -6048,7 +4929,7 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6048
4929
  }
6049
4930
  });
6050
4931
  });
6051
- esm_default.get("https://example.com/xhtml-link", {
4932
+ require_esm.esm_default.get("https://example.com/xhtml-link", {
6052
4933
  body: `<html>
6053
4934
  <head>
6054
4935
  <meta charset="utf-8" />
@@ -6072,7 +4953,7 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6072
4953
  }
6073
4954
  });
6074
4955
  });
6075
- esm_default.get("https://example.com/html-a", {
4956
+ require_esm.esm_default.get("https://example.com/html-a", {
6076
4957
  body: `<html>
6077
4958
  <head>
6078
4959
  <meta charset=utf-8>
@@ -6098,7 +4979,7 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6098
4979
  }
6099
4980
  });
6100
4981
  });
6101
- esm_default.get("https://example.com/wrong-content-type", {
4982
+ require_esm.esm_default.get("https://example.com/wrong-content-type", {
6102
4983
  body: {
6103
4984
  "@context": "https://www.w3.org/ns/activitystreams",
6104
4985
  id: "https://example.com/wrong-content-type",
@@ -6119,7 +5000,7 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6119
5000
  }
6120
5001
  });
6121
5002
  });
6122
- esm_default.get("https://example.com/404", { status: 404 });
5003
+ require_esm.esm_default.get("https://example.com/404", { status: 404 });
6123
5004
  await t.step("not ok", async () => {
6124
5005
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/404"), require_request.FetchError, "HTTP 404: https://example.com/404");
6125
5006
  });
@@ -6145,7 +5026,7 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6145
5026
  }]);
6146
5027
  (0, node_assert.deepStrictEqual)((await jsonld_default.compact(expanded, context, { documentLoader: fetchDocumentLoader })).attributionDomains, ["blog.example"]);
6147
5028
  });
6148
- esm_default.get("https://www.w3.org/ns/cid/v1", { status: 503 });
5029
+ require_esm.esm_default.get("https://www.w3.org/ns/cid/v1", { status: 503 });
6149
5030
  await t.step("preloaded CID v1 context", async () => {
6150
5031
  const url = "https://www.w3.org/ns/cid/v1";
6151
5032
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
@@ -6170,7 +5051,7 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6170
5051
  } }
6171
5052
  });
6172
5053
  });
6173
- esm_default.get("https://w3id.org/fep/7aa9", { status: 502 });
5054
+ require_esm.esm_default.get("https://w3id.org/fep/7aa9", { status: 502 });
6174
5055
  await t.step("preloaded FEP-7aa9 context", async () => {
6175
5056
  const url = "https://w3id.org/fep/7aa9";
6176
5057
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
@@ -6199,9 +5080,9 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6199
5080
  }
6200
5081
  } }
6201
5082
  });
6202
- (0, node_assert.deepStrictEqual)(esm_default.callHistory.calls(url).length, 0);
5083
+ (0, node_assert.deepStrictEqual)(require_esm.esm_default.callHistory.calls(url).length, 0);
6203
5084
  });
6204
- esm_default.get("https://w3id.org/fep/6757", { status: 502 });
5085
+ require_esm.esm_default.get("https://w3id.org/fep/6757", { status: 502 });
6205
5086
  await t.step("preloaded FEP-6757 context", async () => {
6206
5087
  const url = "https://w3id.org/fep/6757";
6207
5088
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader(url), {
@@ -6228,9 +5109,9 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6228
5109
  "http://purl.org/dc/terms/license": [{ "@id": "https://creativecommons.org/licenses/by/4.0/" }],
6229
5110
  "https://w3id.org/fep/6757#preferredLicense": [{ "@id": "https://creativecommons.org/licenses/by-nc/4.0/" }]
6230
5111
  }]);
6231
- (0, node_assert.deepStrictEqual)(esm_default.callHistory.calls(url).length, 0);
5112
+ (0, node_assert.deepStrictEqual)(require_esm.esm_default.callHistory.calls(url).length, 0);
6232
5113
  });
6233
- esm_default.get("https://w3id.org/fep/22cd", { status: 502 });
5114
+ require_esm.esm_default.get("https://w3id.org/fep/22cd", { status: 502 });
6234
5115
  await t.step("preloaded FEP-22cd context", async () => {
6235
5116
  const url = "https://w3id.org/fep/22cd";
6236
5117
  const articleId = "https://example.com/articles/1";
@@ -6306,17 +5187,17 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6306
5187
  "https://schema.org/isBasedOn": [{ "@id": revisionId }]
6307
5188
  }]
6308
5189
  }]);
6309
- (0, node_assert.deepStrictEqual)(esm_default.callHistory.calls(url).length, 0);
6310
- (0, node_assert.deepStrictEqual)(esm_default.callHistory.calls("https://www.w3.org/ns/activitystreams").length, 0);
5190
+ (0, node_assert.deepStrictEqual)(require_esm.esm_default.callHistory.calls(url).length, 0);
5191
+ (0, node_assert.deepStrictEqual)(require_esm.esm_default.callHistory.calls("https://www.w3.org/ns/activitystreams").length, 0);
6311
5192
  });
6312
5193
  await t.step("deny non-HTTP/HTTPS", async () => {
6313
5194
  await (0, node_assert.rejects)(() => fetchDocumentLoader("ftp://localhost"), require_url.UrlError);
6314
5195
  });
6315
- esm_default.get("https://example.com/localhost-redirect", {
5196
+ require_esm.esm_default.get("https://example.com/localhost-redirect", {
6316
5197
  status: 302,
6317
5198
  headers: { Location: "https://localhost/object" }
6318
5199
  });
6319
- esm_default.get("https://example.com/localhost-link", {
5200
+ require_esm.esm_default.get("https://example.com/localhost-link", {
6320
5201
  body: `<html>
6321
5202
  <head>
6322
5203
  <meta charset=utf-8>
@@ -6328,16 +5209,32 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6328
5209
  </html>`,
6329
5210
  headers: { "Content-Type": "text/html; charset=utf-8" }
6330
5211
  });
6331
- esm_default.get("https://localhost/object", { body: {
5212
+ require_esm.esm_default.get("https://localhost/object", { body: {
6332
5213
  "@context": "https://www.w3.org/ns/activitystreams",
6333
5214
  id: "https://localhost/object",
6334
5215
  name: "Fetched object",
6335
5216
  type: "Object"
6336
5217
  } });
5218
+ require_esm.esm_default.get("https://example.com/private-redirect", {
5219
+ status: 302,
5220
+ headers: { Location: "https://private.example/object" }
5221
+ });
5222
+ require_esm.esm_default.get("https://private.example/object", { body: {
5223
+ "@context": "https://www.w3.org/ns/activitystreams",
5224
+ id: "https://private.example/object",
5225
+ type: "Object"
5226
+ } });
6337
5227
  await t.step("allowPrivateAddress: false", async () => {
6338
5228
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://localhost/object"), require_url.UrlError);
6339
5229
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/localhost-redirect"), require_url.UrlError);
6340
5230
  await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/localhost-link"), require_url.UrlError);
5231
+ await (0, node_assert.rejects)(() => fetchDocumentLoader("https://private.example/object"), require_url.UrlError);
5232
+ await (0, node_assert.rejects)(() => fetchDocumentLoader("https://example.com/private-redirect"), require_url.UrlError);
5233
+ (0, node_assert.deepStrictEqual)(require_esm.esm_default.callHistory.calls("https://private.example/object").length, 0);
5234
+ const defaultLoader = getDocumentLoader();
5235
+ await (0, node_assert.rejects)(() => defaultLoader("https://localhost/object"), require_url.UrlError);
5236
+ await (0, node_assert.rejects)(() => defaultLoader("https://127.0.0.1/object"), require_url.UrlError);
5237
+ (0, node_assert.deepStrictEqual)(require_esm.esm_default.callHistory.calls("https://localhost/object").length, 0);
6341
5238
  });
6342
5239
  const fetchDocumentLoader2 = getDocumentLoader({ allowPrivateAddress: true });
6343
5240
  await t.step("allowPrivateAddress: true", async () => {
@@ -6356,7 +5253,7 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6356
5253
  (0, node_assert.deepStrictEqual)(await fetchDocumentLoader2("https://example.com/localhost-link"), expected);
6357
5254
  });
6358
5255
  let redirectAttempts = 0;
6359
- esm_default.get("begin:https://example.com/too-many-redirects/", (cl) => {
5256
+ require_esm.esm_default.get("begin:https://example.com/too-many-redirects/", (cl) => {
6360
5257
  redirectAttempts++;
6361
5258
  return {
6362
5259
  status: 302,
@@ -6369,14 +5266,14 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6369
5266
  (0, node_assert.deepStrictEqual)(redirectAttempts, 21);
6370
5267
  });
6371
5268
  let loopAttempts = 0;
6372
- esm_default.get("https://example.com/redirect-loop-a", () => {
5269
+ require_esm.esm_default.get("https://example.com/redirect-loop-a", () => {
6373
5270
  loopAttempts++;
6374
5271
  return {
6375
5272
  status: 302,
6376
5273
  headers: { Location: "https://example.com/redirect-loop-b" }
6377
5274
  };
6378
5275
  });
6379
- esm_default.get("https://example.com/redirect-loop-b", () => {
5276
+ require_esm.esm_default.get("https://example.com/redirect-loop-b", () => {
6380
5277
  loopAttempts++;
6381
5278
  return {
6382
5279
  status: 302,
@@ -6389,7 +5286,7 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6389
5286
  (0, node_assert.deepStrictEqual)(loopAttempts, 2);
6390
5287
  });
6391
5288
  let relativeLoopAttempts = 0;
6392
- esm_default.get("https://example.com/redirect-loop-relative", () => {
5289
+ require_esm.esm_default.get("https://example.com/redirect-loop-relative", () => {
6393
5290
  relativeLoopAttempts++;
6394
5291
  return {
6395
5292
  status: 302,
@@ -6402,7 +5299,7 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6402
5299
  (0, node_assert.deepStrictEqual)(relativeLoopAttempts, 1);
6403
5300
  });
6404
5301
  const maliciousPayload = "<a" + " a=\"b\"".repeat(30) + " ";
6405
- esm_default.get("https://example.com/redos", {
5302
+ require_esm.esm_default.get("https://example.com/redos", {
6406
5303
  body: maliciousPayload,
6407
5304
  headers: { "Content-Type": "text/html; charset=utf-8" }
6408
5305
  });
@@ -6412,14 +5309,14 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6412
5309
  const elapsed = performance.now() - start;
6413
5310
  (0, node_assert.ok)(elapsed < 1e3, `Potential ReDoS vulnerability detected: ${elapsed}ms (expected < 1000ms)`);
6414
5311
  });
6415
- esm_default.hardReset();
5312
+ require_esm.esm_default.hardReset();
6416
5313
  });
6417
5314
  (0, _fedify_fixture.test)("getDocumentLoader() bounds JSON, HTML, and alternate documents", async () => {
6418
5315
  const url = "https://example.com/bounded";
6419
- esm_default.mockGlobal();
5316
+ require_esm.esm_default.mockGlobal();
6420
5317
  let oversized = true;
6421
5318
  try {
6422
- esm_default.get(url, () => new Response("{\"name\":\"hello\"}", { headers: {
5319
+ require_esm.esm_default.get(url, () => new Response("{\"name\":\"hello\"}", { headers: {
6423
5320
  "Content-Type": "application/activity+json",
6424
5321
  ...oversized ? { "Content-Length": String(16777217) } : {}
6425
5322
  } }));
@@ -6428,20 +5325,20 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6428
5325
  oversized = false;
6429
5326
  (0, node_assert.deepStrictEqual)((await loader(url)).document, { name: "hello" });
6430
5327
  oversized = true;
6431
- esm_default.get(`${url}/html`, () => new Response(" ".repeat(1048577), { headers: { "Content-Type": "text/html" } }));
5328
+ require_esm.esm_default.get(`${url}/html`, () => new Response(" ".repeat(1048577), { headers: { "Content-Type": "text/html" } }));
6432
5329
  await (0, node_assert.rejects)(getDocumentLoader({ allowPrivateAddress: true })(`${url}/html`), require_request.FetchError);
6433
- esm_default.get(`${url}/alternate`, () => new Response("", { headers: {
5330
+ require_esm.esm_default.get(`${url}/alternate`, () => new Response("", { headers: {
6434
5331
  "Content-Type": "text/html",
6435
5332
  Link: `<${url}>; rel="alternate"; type="application/activity+json"`
6436
5333
  } }));
6437
5334
  await (0, node_assert.rejects)(loader(`${url}/alternate`), require_request.FetchError);
6438
- esm_default.get(`${url}/redirect`, {
5335
+ require_esm.esm_default.get(`${url}/redirect`, {
6439
5336
  status: 302,
6440
5337
  headers: { Location: url }
6441
5338
  });
6442
5339
  await (0, node_assert.rejects)(loader(`${url}/redirect`), require_request.FetchError);
6443
5340
  } finally {
6444
- esm_default.hardReset();
5341
+ require_esm.esm_default.hardReset();
6445
5342
  }
6446
5343
  });
6447
5344
  (0, _fedify_fixture.test)("getDocumentLoader() rejects oversized gzip responses from fetch", async () => {
@@ -6492,18 +5389,14 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6492
5389
  (0, node_assert.deepStrictEqual)(pulls, 257);
6493
5390
  });
6494
5391
  (0, _fedify_fixture.test)("getDocumentLoader() logs DNS failures as such", async (t) => {
6495
- if ("Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted") throw new Error("This DNS test requires Deno network permission (--allow-net).");
6496
- const loader = getDocumentLoader();
6497
5392
  for (const result of [
6498
5393
  "throws",
6499
5394
  "empty",
6500
- "private"
5395
+ "private",
5396
+ "default"
6501
5397
  ]) await t.step(result, async () => {
6502
- const originalLookup = node_dns_promises.default.lookup;
6503
- node_dns_promises.default.lookup = (() => result === "throws" ? Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")) : Promise.resolve(result === "empty" ? [] : [{
6504
- address: "127.0.0.1",
6505
- family: 4
6506
- }]));
5398
+ const lookup = () => result === "throws" ? Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")) : Promise.resolve(result === "empty" ? [] : [PRIVATE_ADDRESS]);
5399
+ const loader = result === "default" ? getDocumentLoader() : createDocumentLoader({}, { openHop: (url) => unpinnedPublicFetch.openPublicFetch(url, { lookup }) });
6507
5400
  const records = [];
6508
5401
  await (0, _logtape_logtape.configure)({
6509
5402
  sinks: { buffer: (record) => records.push(record) },
@@ -6518,19 +5411,20 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6518
5411
  reset: true
6519
5412
  });
6520
5413
  try {
6521
- const url = "https://dns-failure.invalid/object";
5414
+ const url = result === "default" ? "https://10.0.0.1/object" : "https://dns-failure.invalid/object";
6522
5415
  let error;
6523
5416
  await (0, node_assert.rejects)(() => loader(url), (e) => {
6524
5417
  error = e;
6525
5418
  return true;
6526
5419
  });
6527
5420
  (0, node_assert.ok)(error instanceof require_url.UrlError);
6528
- (0, node_assert.deepStrictEqual)(error.reason, result === "private" ? "disallowed" : "dns");
5421
+ const disallowed = result === "private" || result === "default";
5422
+ (0, node_assert.deepStrictEqual)(error.reason, disallowed ? "disallowed" : "dns");
6529
5423
  (0, node_assert.deepStrictEqual)(records.map((r) => [
6530
5424
  r.level,
6531
5425
  r.rawMessage,
6532
5426
  r.properties.url
6533
- ]), [result === "private" ? [
5427
+ ]), [disallowed ? [
6534
5428
  "error",
6535
5429
  "Disallowed private URL: {url}",
6536
5430
  url
@@ -6542,7 +5436,6 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6542
5436
  (0, node_assert.ok)(records[0].properties.error === error);
6543
5437
  } finally {
6544
5438
  await (0, _logtape_logtape.reset)();
6545
- node_dns_promises.default.lookup = originalLookup;
6546
5439
  }
6547
5440
  });
6548
5441
  });
@@ -6554,9 +5447,9 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6554
5447
  "html",
6555
5448
  "mixed"
6556
5449
  ]) for (const hops of [20, 21]) await t.step(`${mode}: ${hops} hops`, async () => {
6557
- esm_default.mockGlobal();
5450
+ require_esm.esm_default.mockGlobal();
6558
5451
  let requests = 0;
6559
- esm_default.get(`begin:${base}`, ({ url }) => {
5452
+ require_esm.esm_default.get(`begin:${base}`, ({ url }) => {
6560
5453
  requests++;
6561
5454
  const index = Number(new URL(url).pathname.split("/").at(-1));
6562
5455
  if (index === hops || requests > 30) return Response.json({ done: true });
@@ -6573,7 +5466,7 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6573
5466
  });
6574
5467
  (0, node_assert.deepStrictEqual)(requests, 21);
6575
5468
  } finally {
6576
- esm_default.hardReset();
5469
+ require_esm.esm_default.hardReset();
6577
5470
  }
6578
5471
  });
6579
5472
  for (const mode of [
@@ -6583,9 +5476,9 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6583
5476
  "redirect-alternate",
6584
5477
  "redirect-intermediate"
6585
5478
  ]) await t.step(`${mode}: cycle`, async () => {
6586
- esm_default.mockGlobal();
5479
+ require_esm.esm_default.mockGlobal();
6587
5480
  let requests = 0;
6588
- esm_default.get(`begin:${base}`, ({ url }) => {
5481
+ require_esm.esm_default.get(`begin:${base}`, ({ url }) => {
6589
5482
  requests++;
6590
5483
  if (requests > 30) return Response.json({ stopped: true });
6591
5484
  const index = Number(new URL(url).pathname.split("/").at(-1));
@@ -6601,13 +5494,13 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6601
5494
  });
6602
5495
  (0, node_assert.deepStrictEqual)(requests, mode === "redirect-intermediate" ? 3 : 2);
6603
5496
  } finally {
6604
- esm_default.hardReset();
5497
+ require_esm.esm_default.hardReset();
6605
5498
  }
6606
5499
  });
6607
5500
  await t.step("alternate followed by 20 redirects shares the limit", async () => {
6608
- esm_default.mockGlobal();
5501
+ require_esm.esm_default.mockGlobal();
6609
5502
  let requests = 0;
6610
- esm_default.get(`begin:${base}`, ({ url }) => {
5503
+ require_esm.esm_default.get(`begin:${base}`, ({ url }) => {
6611
5504
  requests++;
6612
5505
  const index = Number(new URL(url).pathname.split("/").at(-1));
6613
5506
  if (index === 21) return Response.json({ done: true });
@@ -6621,14 +5514,14 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6621
5514
  });
6622
5515
  (0, node_assert.deepStrictEqual)(requests, 21);
6623
5516
  } finally {
6624
- esm_default.hardReset();
5517
+ require_esm.esm_default.hardReset();
6625
5518
  }
6626
5519
  });
6627
5520
  await t.step("relative alternate after redirect and isolated calls", async () => {
6628
- esm_default.mockGlobal();
6629
- esm_default.get(`${base}start`, Response.redirect(`${base}html`, 302));
6630
- esm_default.get(`${base}html`, alternate("./document", true));
6631
- esm_default.get(`${base}document`, Response.json({ done: true }));
5521
+ require_esm.esm_default.mockGlobal();
5522
+ require_esm.esm_default.get(`${base}start`, Response.redirect(`${base}html`, 302));
5523
+ require_esm.esm_default.get(`${base}html`, alternate("./document", true));
5524
+ require_esm.esm_default.get(`${base}document`, Response.json({ done: true }));
6632
5525
  try {
6633
5526
  for (let i = 0; i < 2; i++) {
6634
5527
  const results = await Promise.all([loader(`${base}start`), loader(`${base}start`)]);
@@ -6638,7 +5531,7 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6638
5531
  }
6639
5532
  }
6640
5533
  } finally {
6641
- esm_default.hardReset();
5534
+ require_esm.esm_default.hardReset();
6642
5535
  }
6643
5536
  });
6644
5537
  function alternate(next, html) {
@@ -6652,10 +5545,10 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6652
5545
  const base = "https://example.com/alternate-abort/";
6653
5546
  const loader = getDocumentLoader({ allowPrivateAddress: true });
6654
5547
  for (const html of [false, true]) await t.step(html ? "HTML" : "Link header", async () => {
6655
- esm_default.mockGlobal();
5548
+ require_esm.esm_default.mockGlobal();
6656
5549
  const controller = new AbortController();
6657
5550
  let requests = 0;
6658
- esm_default.get(`begin:${base}`, ({ url }) => {
5551
+ require_esm.esm_default.get(`begin:${base}`, ({ url }) => {
6659
5552
  requests++;
6660
5553
  const index = Number(new URL(url).pathname.split("/").at(-1));
6661
5554
  if (index === 2) return Response.json({ done: true });
@@ -6670,15 +5563,15 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6670
5563
  await (0, node_assert.rejects)(loader(`${base}0`, { signal: controller.signal }), { name: "AbortError" });
6671
5564
  (0, node_assert.deepStrictEqual)(requests, 2);
6672
5565
  } finally {
6673
- esm_default.hardReset();
5566
+ require_esm.esm_default.hardReset();
6674
5567
  }
6675
5568
  });
6676
5569
  });
6677
5570
  (0, _fedify_fixture.test)("getDocumentLoader() rejects cancellation before fetching", async () => {
6678
- esm_default.mockGlobal();
5571
+ require_esm.esm_default.mockGlobal();
6679
5572
  let requests = 0;
6680
5573
  const url = "https://example.com/pre-aborted-alternate";
6681
- esm_default.get(url, () => {
5574
+ require_esm.esm_default.get(url, () => {
6682
5575
  requests++;
6683
5576
  return Response.json({ done: true });
6684
5577
  });
@@ -6689,7 +5582,7 @@ var jsonld_default = jsonld_dist_jsonld_esm_js.default;
6689
5582
  await (0, node_assert.rejects)(loader(url, { signal: controller.signal }), { name: "AbortError" });
6690
5583
  (0, node_assert.deepStrictEqual)(requests, 0);
6691
5584
  } finally {
6692
- esm_default.hardReset();
5585
+ require_esm.esm_default.hardReset();
6693
5586
  }
6694
5587
  });
6695
5588
  const unsupportedUrls = [
@@ -6786,4 +5679,421 @@ for (const url of unsupportedUrls) {
6786
5679
  });
6787
5680
  });
6788
5681
  }
5682
+ function createHopRecorder(responder) {
5683
+ const events = [];
5684
+ const states = /* @__PURE__ */ new Map();
5685
+ const violations = [];
5686
+ let responses = 0;
5687
+ let opens = 0;
5688
+ let closes = 0;
5689
+ function respond(url) {
5690
+ const n = ++responses;
5691
+ events.push(`fetch ${url} #${n}`);
5692
+ const { status = 200, headers, body = "", failCancel, hangCancel, responseUrl } = responder(url);
5693
+ const state = {
5694
+ n,
5695
+ settled: false
5696
+ };
5697
+ let sent = false;
5698
+ const stream = new ReadableStream({
5699
+ pull(controller) {
5700
+ if (!sent && body !== "") {
5701
+ sent = true;
5702
+ controller.enqueue(new TextEncoder().encode(body));
5703
+ return;
5704
+ }
5705
+ controller.close();
5706
+ state.settled = true;
5707
+ events.push(`consumed #${n}`);
5708
+ },
5709
+ cancel() {
5710
+ state.settled = true;
5711
+ events.push(`cancel #${n}`);
5712
+ if (failCancel) throw new Error(`Failed to cancel #${n}`);
5713
+ if (hangCancel) return new Promise(() => {});
5714
+ }
5715
+ }, { highWaterMark: 0 });
5716
+ const response = new Response(stream, {
5717
+ status,
5718
+ headers
5719
+ });
5720
+ if (responseUrl != null) Object.defineProperty(response, "url", { value: responseUrl });
5721
+ states.set(response, state);
5722
+ return response;
5723
+ }
5724
+ function record(url, session) {
5725
+ opens++;
5726
+ events.push(`open ${url}`);
5727
+ const fetched = [];
5728
+ let closed = false;
5729
+ return {
5730
+ get url() {
5731
+ return session.url;
5732
+ },
5733
+ async fetch(input, init) {
5734
+ const response = await session.fetch(input, init);
5735
+ fetched.push(response);
5736
+ return response;
5737
+ },
5738
+ async close() {
5739
+ if (closed) return;
5740
+ closed = true;
5741
+ for (const response of fetched) {
5742
+ const state = states.get(response);
5743
+ if (state != null && !state.settled) violations.push(`#${state.n} is still open when closing ${url}`);
5744
+ }
5745
+ closes++;
5746
+ events.push(`close ${url}`);
5747
+ await session.close();
5748
+ }
5749
+ };
5750
+ }
5751
+ function openHop(url) {
5752
+ const origin = new URL(url).origin;
5753
+ return Promise.resolve(record(url, {
5754
+ url: new URL(url),
5755
+ fetch(input) {
5756
+ const target = input instanceof Request ? input.url : String(input);
5757
+ (0, node_assert.ok)(new URL(target).origin === origin, `${target} is not on ${origin}`);
5758
+ return Promise.resolve(respond(target));
5759
+ },
5760
+ close: () => Promise.resolve()
5761
+ }));
5762
+ }
5763
+ return {
5764
+ events,
5765
+ respond,
5766
+ record,
5767
+ openHop,
5768
+ assertInvariants() {
5769
+ (0, node_assert.deepStrictEqual)(violations, []);
5770
+ (0, node_assert.deepStrictEqual)(opens, closes);
5771
+ }
5772
+ };
5773
+ }
5774
+ const lifecycleDocument = {
5775
+ "@context": "https://www.w3.org/ns/activitystreams",
5776
+ id: "https://b.example/object",
5777
+ type: "Object"
5778
+ };
5779
+ (0, _fedify_fixture.test)("getDocumentLoader() closes a redirect hop before the next hop", async () => {
5780
+ const recorder = createHopRecorder((url) => url === "https://a.example/start" ? {
5781
+ status: 302,
5782
+ headers: { Location: "https://b.example/object" }
5783
+ } : { body: JSON.stringify(lifecycleDocument) });
5784
+ (0, node_assert.deepStrictEqual)(await createDocumentLoader({}, { openHop: recorder.openHop })("https://a.example/start"), {
5785
+ contextUrl: null,
5786
+ documentUrl: "https://b.example/object",
5787
+ document: lifecycleDocument
5788
+ });
5789
+ (0, node_assert.deepStrictEqual)(recorder.events, [
5790
+ "open https://a.example/start",
5791
+ "fetch https://a.example/start #1",
5792
+ "cancel #1",
5793
+ "close https://a.example/start",
5794
+ "open https://b.example/object",
5795
+ "fetch https://b.example/object #2",
5796
+ "consumed #2",
5797
+ "close https://b.example/object"
5798
+ ]);
5799
+ recorder.assertInvariants();
5800
+ });
5801
+ (0, _fedify_fixture.test)("getDocumentLoader() cancels the body before a Link header alternate", async () => {
5802
+ const recorder = createHopRecorder((url) => url === "https://a.example/start" ? {
5803
+ headers: {
5804
+ "Content-Type": "text/plain",
5805
+ Link: "<https://b.example/object>; rel=\"alternate\"; type=\"application/activity+json\""
5806
+ },
5807
+ body: "not JSON"
5808
+ } : { body: JSON.stringify(lifecycleDocument) });
5809
+ (0, node_assert.deepStrictEqual)((await createDocumentLoader({}, { openHop: recorder.openHop })("https://a.example/start")).documentUrl, "https://b.example/object");
5810
+ (0, node_assert.deepStrictEqual)(recorder.events, [
5811
+ "open https://a.example/start",
5812
+ "fetch https://a.example/start #1",
5813
+ "cancel #1",
5814
+ "close https://a.example/start",
5815
+ "open https://b.example/object",
5816
+ "fetch https://b.example/object #2",
5817
+ "consumed #2",
5818
+ "close https://b.example/object"
5819
+ ]);
5820
+ recorder.assertInvariants();
5821
+ });
5822
+ (0, _fedify_fixture.test)("getDocumentLoader() reads the body before an HTML alternate", async () => {
5823
+ const recorder = createHopRecorder((url) => url === "https://a.example/start" ? {
5824
+ headers: { "Content-Type": "text/html" },
5825
+ body: "<link rel=\"alternate\" type=\"application/activity+json\" href=\"https://b.example/object\">"
5826
+ } : { body: JSON.stringify(lifecycleDocument) });
5827
+ (0, node_assert.deepStrictEqual)((await createDocumentLoader({}, { openHop: recorder.openHop })("https://a.example/start")).documentUrl, "https://b.example/object");
5828
+ (0, node_assert.deepStrictEqual)(recorder.events, [
5829
+ "open https://a.example/start",
5830
+ "fetch https://a.example/start #1",
5831
+ "consumed #1",
5832
+ "close https://a.example/start",
5833
+ "open https://b.example/object",
5834
+ "fetch https://b.example/object #2",
5835
+ "consumed #2",
5836
+ "close https://b.example/object"
5837
+ ]);
5838
+ recorder.assertInvariants();
5839
+ });
5840
+ /**
5841
+ * Fails instead of blocking the test if the given promise does not settle
5842
+ * within the given time.
5843
+ */
5844
+ async function settlesWithin(promise, milliseconds = 1e3) {
5845
+ let timer;
5846
+ try {
5847
+ return await Promise.race([promise, new Promise((_, reject) => {
5848
+ timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Did not settle in ${milliseconds} ms.`)), milliseconds);
5849
+ })]);
5850
+ } finally {
5851
+ clearTimeout(timer);
5852
+ }
5853
+ }
5854
+ /**
5855
+ * Runs the given function while capturing the document loader's logs, and
5856
+ * waits until the rejections of body cancellations started without being
5857
+ * awaited have been logged.
5858
+ */
5859
+ async function captureDocumentLoaderLogs(fn) {
5860
+ const records = [];
5861
+ await (0, _logtape_logtape.configure)({
5862
+ sinks: { buffer: (record) => records.push(record) },
5863
+ loggers: [{
5864
+ category: [
5865
+ "fedify",
5866
+ "runtime",
5867
+ "docloader"
5868
+ ],
5869
+ sinks: ["buffer"],
5870
+ lowestLevel: "debug"
5871
+ }, {
5872
+ category: ["logtape", "meta"],
5873
+ sinks: []
5874
+ }],
5875
+ reset: true
5876
+ });
5877
+ try {
5878
+ const result = await fn();
5879
+ await new Promise((resolve) => setTimeout(resolve, 0));
5880
+ return {
5881
+ result,
5882
+ records
5883
+ };
5884
+ } finally {
5885
+ await (0, _logtape_logtape.reset)();
5886
+ }
5887
+ }
5888
+ function cancelFailureLogs(records) {
5889
+ return records.filter((record) => record.rawMessage === "Failed to cancel the response body from {url}: {error}").map((record) => [
5890
+ record.level,
5891
+ record.properties.url,
5892
+ String(record.properties.error)
5893
+ ]);
5894
+ }
5895
+ function linkHeaderAlternate(next) {
5896
+ return {
5897
+ headers: {
5898
+ "Content-Type": "text/plain",
5899
+ Link: `<${next}>; rel="alternate"; type="application/activity+json"`
5900
+ },
5901
+ body: "not JSON"
5902
+ };
5903
+ }
5904
+ (0, _fedify_fixture.test)("getDocumentLoader() continues if an intermediate body fails to cancel", async (t) => {
5905
+ for (const kind of ["redirect", "Link header alternate"]) await t.step(kind, async () => {
5906
+ const recorder = createHopRecorder((url) => url === "https://a.example/start" ? {
5907
+ ...kind === "redirect" ? {
5908
+ status: 302,
5909
+ headers: { Location: "https://b.example/object" }
5910
+ } : linkHeaderAlternate("https://b.example/object"),
5911
+ failCancel: true
5912
+ } : { body: JSON.stringify(lifecycleDocument) });
5913
+ const loader = createDocumentLoader({}, { openHop: recorder.openHop });
5914
+ const { result, records } = await captureDocumentLoaderLogs(() => loader("https://a.example/start"));
5915
+ (0, node_assert.deepStrictEqual)(result, {
5916
+ contextUrl: null,
5917
+ documentUrl: "https://b.example/object",
5918
+ document: lifecycleDocument
5919
+ });
5920
+ (0, node_assert.deepStrictEqual)(recorder.events, [
5921
+ "open https://a.example/start",
5922
+ "fetch https://a.example/start #1",
5923
+ "cancel #1",
5924
+ "close https://a.example/start",
5925
+ "open https://b.example/object",
5926
+ "fetch https://b.example/object #2",
5927
+ "consumed #2",
5928
+ "close https://b.example/object"
5929
+ ]);
5930
+ recorder.assertInvariants();
5931
+ (0, node_assert.deepStrictEqual)(cancelFailureLogs(records), [[
5932
+ "debug",
5933
+ "https://a.example/start",
5934
+ "Error: Failed to cancel #1"
5935
+ ]]);
5936
+ });
5937
+ });
5938
+ (0, _fedify_fixture.test)("getDocumentLoader() does not wait for intermediate bodies to be cancelled", async (t) => {
5939
+ for (const kind of ["redirect", "Link header alternate"]) await t.step(kind, async () => {
5940
+ const recorder = createHopRecorder((url) => url === "https://a.example/start" ? {
5941
+ ...kind === "redirect" ? {
5942
+ status: 302,
5943
+ headers: { Location: "https://b.example/object" }
5944
+ } : linkHeaderAlternate("https://b.example/object"),
5945
+ hangCancel: true
5946
+ } : { body: JSON.stringify(lifecycleDocument) });
5947
+ (0, node_assert.deepStrictEqual)((await settlesWithin(createDocumentLoader({}, { openHop: recorder.openHop })("https://a.example/start"))).documentUrl, "https://b.example/object");
5948
+ (0, node_assert.deepStrictEqual)(recorder.events, [
5949
+ "open https://a.example/start",
5950
+ "fetch https://a.example/start #1",
5951
+ "cancel #1",
5952
+ "close https://a.example/start",
5953
+ "open https://b.example/object",
5954
+ "fetch https://b.example/object #2",
5955
+ "consumed #2",
5956
+ "close https://b.example/object"
5957
+ ]);
5958
+ recorder.assertInvariants();
5959
+ });
5960
+ await t.step("public session with a never-settling cancel", async () => {
5961
+ let cancelled = 0;
5962
+ const pinner = {
5963
+ supports: () => true,
5964
+ fetch: () => Promise.resolve(new Response(new ReadableStream({ cancel() {
5965
+ cancelled++;
5966
+ return new Promise(() => {});
5967
+ } }), { status: 404 })),
5968
+ release() {}
5969
+ };
5970
+ const publicFetch = require_public_fetch.createPublicFetch({
5971
+ pinner: () => Promise.resolve(pinner),
5972
+ lookup: () => Promise.reject(/* @__PURE__ */ new Error("Unexpected default lookup"))
5973
+ });
5974
+ await settlesWithin((0, node_assert.rejects)(createDocumentLoader({}, { openHop: (url) => publicFetch.openPublicFetch(url, { lookup: lookupTestAddresses }) })("https://a.example/missing"), require_request.FetchError));
5975
+ (0, node_assert.deepStrictEqual)(cancelled, 1);
5976
+ });
5977
+ });
5978
+ (0, _fedify_fixture.test)("getDocumentLoader() completes with tee'd fetch-mock responses", async (t) => {
5979
+ const url = "https://example.com/teed";
5980
+ function stream(text) {
5981
+ return new ReadableStream({ start(controller) {
5982
+ controller.enqueue(new TextEncoder().encode(text));
5983
+ controller.close();
5984
+ } });
5985
+ }
5986
+ const loaders = {
5987
+ unpinned: createDocumentLoader({}, { openHop: openUnpinnedHop }),
5988
+ "private addresses allowed": getDocumentLoader({ allowPrivateAddress: true })
5989
+ };
5990
+ for (const [name, loader] of Object.entries(loaders)) for (const body of ["string", "stream"]) await t.step(`${name}: ${body} bodies`, async () => {
5991
+ const make = (text) => body === "string" ? text : stream(text);
5992
+ require_esm.esm_default.mockGlobal();
5993
+ try {
5994
+ require_esm.esm_default.get(`${url}/object`, () => new Response(make(JSON.stringify(lifecycleDocument)), { headers: { "Content-Type": "application/activity+json" } }));
5995
+ require_esm.esm_default.get(`${url}/redirect`, () => new Response(make("redirect"), {
5996
+ status: 302,
5997
+ headers: { Location: `${url}/object` }
5998
+ }));
5999
+ require_esm.esm_default.get(`${url}/alternate`, () => new Response(make("not JSON"), { headers: {
6000
+ "Content-Type": "text/plain",
6001
+ Link: `<${url}/object>; rel="alternate"; type="application/activity+json"`
6002
+ } }));
6003
+ require_esm.esm_default.get(`${url}/missing`, () => new Response(make("not found"), { status: 404 }));
6004
+ for (const path of ["redirect", "alternate"]) (0, node_assert.deepStrictEqual)((await settlesWithin(loader(`${url}/${path}`))).document, lifecycleDocument);
6005
+ await settlesWithin((0, node_assert.rejects)(loader(`${url}/missing`), require_request.FetchError));
6006
+ (0, node_assert.deepStrictEqual)(require_esm.esm_default.callHistory.calls().map((call) => call.url), [
6007
+ `${url}/redirect`,
6008
+ `${url}/object`,
6009
+ `${url}/alternate`,
6010
+ `${url}/object`,
6011
+ `${url}/missing`
6012
+ ]);
6013
+ } finally {
6014
+ require_esm.esm_default.hardReset();
6015
+ }
6016
+ });
6017
+ });
6018
+ (0, _fedify_fixture.test)("getDocumentLoader() resolves each hop once", async () => {
6019
+ const lookups = [];
6020
+ const recorder = createHopRecorder((url) => {
6021
+ const responseUrl = `https://${PUBLIC_ADDRESS.address}${new URL(url).pathname}`;
6022
+ if (url === "https://a.example/start") return {
6023
+ status: 302,
6024
+ headers: { Location: "https://b.example/next" },
6025
+ responseUrl
6026
+ };
6027
+ else if (url === "https://b.example/next") return {
6028
+ status: 301,
6029
+ headers: { Location: "/object" },
6030
+ responseUrl
6031
+ };
6032
+ return {
6033
+ body: JSON.stringify(lifecycleDocument),
6034
+ responseUrl
6035
+ };
6036
+ });
6037
+ const pinner = {
6038
+ supports: () => true,
6039
+ fetch: (_grant, request) => Promise.resolve(recorder.respond(request.url)),
6040
+ release() {}
6041
+ };
6042
+ const publicFetch = require_public_fetch.createPublicFetch({
6043
+ pinner: () => Promise.resolve(pinner),
6044
+ lookup: () => Promise.reject(/* @__PURE__ */ new Error("Unexpected default lookup"))
6045
+ });
6046
+ (0, node_assert.deepStrictEqual)(await createDocumentLoader({}, { openHop: async (url) => recorder.record(url, await publicFetch.openPublicFetch(url, { lookup(hostname) {
6047
+ lookups.push(hostname);
6048
+ return Promise.resolve([PUBLIC_ADDRESS]);
6049
+ } })) })("https://a.example/start"), {
6050
+ contextUrl: null,
6051
+ documentUrl: "https://b.example/object",
6052
+ document: lifecycleDocument
6053
+ });
6054
+ (0, node_assert.deepStrictEqual)(lookups, [
6055
+ "a.example",
6056
+ "b.example",
6057
+ "b.example"
6058
+ ]);
6059
+ (0, node_assert.deepStrictEqual)(recorder.events, [
6060
+ "open https://a.example/start",
6061
+ "fetch https://a.example/start #1",
6062
+ "cancel #1",
6063
+ "close https://a.example/start",
6064
+ "open https://b.example/next",
6065
+ "fetch https://b.example/next #2",
6066
+ "cancel #2",
6067
+ "close https://b.example/next",
6068
+ "open https://b.example/object",
6069
+ "fetch https://b.example/object #3",
6070
+ "consumed #3",
6071
+ "close https://b.example/object"
6072
+ ]);
6073
+ recorder.assertInvariants();
6074
+ });
6075
+ (0, _fedify_fixture.test)("getRemoteDocument() cancels the body before loading a Link header alternate", async () => {
6076
+ const recorder = createHopRecorder(() => ({
6077
+ headers: {
6078
+ "Content-Type": "text/html",
6079
+ Link: "<https://b.example/object>; rel=\"alternate\"; type=\"application/activity+json\""
6080
+ },
6081
+ body: "<html></html>"
6082
+ }));
6083
+ const response = recorder.respond("https://a.example/start");
6084
+ const placeholder = {
6085
+ contextUrl: null,
6086
+ document: {},
6087
+ documentUrl: "https://b.example/object"
6088
+ };
6089
+ const alternates = [];
6090
+ (0, node_assert.ok)(await getRemoteDocument("https://a.example/start", response, (url) => {
6091
+ alternates.push(url);
6092
+ (0, node_assert.deepStrictEqual)(recorder.events.at(-1), "cancel #1");
6093
+ (0, node_assert.ok)(response.bodyUsed);
6094
+ return Promise.resolve(placeholder);
6095
+ }) === placeholder);
6096
+ (0, node_assert.deepStrictEqual)(alternates, ["https://b.example/object"]);
6097
+ (0, node_assert.deepStrictEqual)(recorder.events, ["fetch https://a.example/start #1", "cancel #1"]);
6098
+ });
6789
6099
  //#endregion