@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,1236 +1,16 @@
1
- import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-CZWDeBC-.mjs";
2
- import { n as MAX_BODY_SIZE, r as readBoundedText } from "./body-s65I5CCa.mjs";
3
- import { t as HttpHeaderLink } from "./link-NUUWCdnK.mjs";
4
- import { o as validatePublicUrl, t as UrlError } from "./url-DzlMS01z.mjs";
1
+ import { t as esm_default } from "./esm-CnLt0wAF.mjs";
2
+ import { a as name, i as logRequest, n as createActivityPubRequest, o as version, t as FetchError } from "./request-Dr5LoMPw.mjs";
3
+ import { n as MAX_BODY_SIZE, r as readBoundedText } from "./body-GD9HfwgN.mjs";
4
+ import { t as HttpHeaderLink } from "./link-CYszIegt.mjs";
5
+ import { t as UrlError } from "./url-DjsVF44Q.mjs";
6
+ import { n as openDirectFetch, r as openPublicFetch, t as createPublicFetch } from "./public-fetch-BUSPlh1C.mjs";
5
7
  import { gzipSync } from "node:zlib";
6
8
  import { configure, getLogger, reset } from "@logtape/logtape";
7
9
  import { deepStrictEqual, ok, rejects } from "node:assert";
8
- import dns from "node:dns/promises";
9
10
  import { test } from "@fedify/fixture";
10
11
  import { createServer } from "node:http";
11
12
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
12
13
  import jsonld from "jsonld/dist/jsonld.esm.js";
13
- //#region \0rolldown/runtime.js
14
- var __create = Object.create;
15
- var __defProp = Object.defineProperty;
16
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
17
- var __getOwnPropNames = Object.getOwnPropertyNames;
18
- var __getProtoOf = Object.getPrototypeOf;
19
- var __hasOwnProp = Object.prototype.hasOwnProperty;
20
- var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
21
- var __copyProps = (to, from, except, desc) => {
22
- if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
23
- key = keys[i];
24
- if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
25
- get: ((k) => from[k]).bind(null, key),
26
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
27
- });
28
- }
29
- return to;
30
- };
31
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
32
- value: mod,
33
- enumerable: true
34
- }) : target, mod));
35
- //#endregion
36
- //#region ../../node_modules/.pnpm/regexparam@3.0.0/node_modules/regexparam/dist/index.mjs
37
- var import_glob_to_regexp = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
38
- module.exports = function(glob, opts) {
39
- if (typeof glob !== "string") throw new TypeError("Expected a string");
40
- var str = String(glob);
41
- var reStr = "";
42
- var extended = opts ? !!opts.extended : false;
43
- var globstar = opts ? !!opts.globstar : false;
44
- var inGroup = false;
45
- var flags = opts && typeof opts.flags === "string" ? opts.flags : "";
46
- var c;
47
- for (var i = 0, len = str.length; i < len; i++) {
48
- c = str[i];
49
- switch (c) {
50
- case "/":
51
- case "$":
52
- case "^":
53
- case "+":
54
- case ".":
55
- case "(":
56
- case ")":
57
- case "=":
58
- case "!":
59
- case "|":
60
- reStr += "\\" + c;
61
- break;
62
- case "?": if (extended) {
63
- reStr += ".";
64
- break;
65
- }
66
- case "[":
67
- case "]": if (extended) {
68
- reStr += c;
69
- break;
70
- }
71
- case "{": if (extended) {
72
- inGroup = true;
73
- reStr += "(";
74
- break;
75
- }
76
- case "}": if (extended) {
77
- inGroup = false;
78
- reStr += ")";
79
- break;
80
- }
81
- case ",":
82
- if (inGroup) {
83
- reStr += "|";
84
- break;
85
- }
86
- reStr += "\\" + c;
87
- break;
88
- case "*":
89
- var prevChar = str[i - 1];
90
- var starCount = 1;
91
- while (str[i + 1] === "*") {
92
- starCount++;
93
- i++;
94
- }
95
- var nextChar = str[i + 1];
96
- if (!globstar) reStr += ".*";
97
- else if (starCount > 1 && (prevChar === "/" || prevChar === void 0) && (nextChar === "/" || nextChar === void 0)) {
98
- reStr += "((?:[^/]*(?:/|$))*)";
99
- i++;
100
- } else reStr += "([^/]*)";
101
- break;
102
- default: reStr += c;
103
- }
104
- }
105
- if (!flags || !~flags.indexOf("g")) reStr = "^" + reStr + "$";
106
- return new RegExp(reStr, flags);
107
- };
108
- })))(), 1);
109
- /**
110
- * @param {string|RegExp} input The route pattern
111
- * @param {boolean} [loose] Allow open-ended matching. Ignored with `RegExp` input.
112
- */
113
- function parse(input, loose) {
114
- if (input instanceof RegExp) return {
115
- keys: false,
116
- pattern: input
117
- };
118
- var c, o, tmp, ext, keys = [], pattern = "", arr = input.split("/");
119
- arr[0] || arr.shift();
120
- while (tmp = arr.shift()) {
121
- c = tmp[0];
122
- if (c === "*") {
123
- keys.push(c);
124
- pattern += tmp[1] === "?" ? "(?:/(.*))?" : "/(.*)";
125
- } else if (c === ":") {
126
- o = tmp.indexOf("?", 1);
127
- ext = tmp.indexOf(".", 1);
128
- keys.push(tmp.substring(1, !!~o ? o : !!~ext ? ext : tmp.length));
129
- pattern += !!~o && !~ext ? "(?:/([^/]+?))?" : "/([^/]+?)";
130
- if (!!~ext) pattern += (!!~o ? "?" : "") + "\\" + tmp.substring(ext);
131
- } else pattern += "/" + tmp;
132
- }
133
- return {
134
- keys,
135
- pattern: new RegExp("^" + pattern + (loose ? "(?=$|/)" : "/?$"), "i")
136
- };
137
- }
138
- //#endregion
139
- //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/TypeDescriptor.js
140
- const valueTypes = new Set([
141
- "boolean",
142
- "number",
143
- "null",
144
- "string",
145
- "undefined"
146
- ]);
147
- const referenceTypes = new Set([
148
- "array",
149
- "function",
150
- "object",
151
- "symbol"
152
- ]);
153
- const detectableTypes = new Set([
154
- "boolean",
155
- "function",
156
- "number",
157
- "string",
158
- "symbol"
159
- ]);
160
- const typeConstructors = new Set([
161
- Boolean,
162
- Number,
163
- String
164
- ]);
165
- var TypeDescriptor = class TypeDescriptor {
166
- constructor(value) {
167
- this.name = TypeDescriptor.of(value);
168
- this.isValueType = TypeDescriptor.isValueType(value);
169
- this.isReferenceType = TypeDescriptor.isReferenceType(value);
170
- this.isArray = TypeDescriptor.isArray(value);
171
- this.isBoolean = TypeDescriptor.isBoolean(value);
172
- this.isFunction = TypeDescriptor.isFunction(value);
173
- this.isNull = TypeDescriptor.isNull(value);
174
- this.isNumber = TypeDescriptor.isNumber(value);
175
- this.isObject = TypeDescriptor.isObject(value);
176
- this.isString = TypeDescriptor.isString(value);
177
- this.isSymbol = TypeDescriptor.isSymbol(value);
178
- this.isUndefined = TypeDescriptor.isUndefined(value);
179
- }
180
- static of(value) {
181
- if (value === null) return "null";
182
- if (value === void 0) return "undefined";
183
- const detectedType = typeof value;
184
- if (detectableTypes.has(detectedType)) return detectedType;
185
- if (detectedType === "object") {
186
- if (Array.isArray(value)) return "array";
187
- if (typeConstructors.has(value.constructor)) return value.constructor.name.toLowerCase();
188
- return detectedType;
189
- }
190
- throw new Error("Failed due to an unknown type.");
191
- }
192
- static from(value) {
193
- return new TypeDescriptor(value);
194
- }
195
- static isValueType(value) {
196
- return valueTypes.has(TypeDescriptor.of(value));
197
- }
198
- static isReferenceType(value) {
199
- return referenceTypes.has(TypeDescriptor.of(value));
200
- }
201
- static isArray(value) {
202
- return TypeDescriptor.of(value) === "array";
203
- }
204
- static isBoolean(value) {
205
- return TypeDescriptor.of(value) === "boolean";
206
- }
207
- static isFunction(value) {
208
- return TypeDescriptor.of(value) === "function";
209
- }
210
- static isNull(value) {
211
- return TypeDescriptor.of(value) === "null";
212
- }
213
- static isNumber(value) {
214
- return TypeDescriptor.of(value) === "number";
215
- }
216
- static isObject(value) {
217
- return TypeDescriptor.of(value) === "object";
218
- }
219
- static isString(value) {
220
- return TypeDescriptor.of(value) === "string";
221
- }
222
- static isSymbol(value) {
223
- return TypeDescriptor.of(value) === "symbol";
224
- }
225
- static isUndefined(value) {
226
- return TypeDescriptor.of(value) === "undefined";
227
- }
228
- };
229
- //#endregion
230
- //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/IsSubsetOf.js
231
- const allowedTypes = new Set([
232
- "array",
233
- "object",
234
- "function",
235
- "null"
236
- ]);
237
- const isSubsetOf = function(subset, superset, visited = []) {
238
- const subsetType = TypeDescriptor.of(subset);
239
- const supersetType = TypeDescriptor.of(superset);
240
- if (!allowedTypes.has(subsetType)) throw new Error(`Type '${subsetType}' is not supported.`);
241
- if (!allowedTypes.has(supersetType)) throw new Error(`Type '${supersetType}' is not supported.`);
242
- if (TypeDescriptor.isFunction(subset)) {
243
- if (!TypeDescriptor.isFunction(superset)) throw new Error(`Types '${subsetType}' and '${supersetType}' do not match.`);
244
- return subset.toString() === superset.toString();
245
- }
246
- if (TypeDescriptor.isArray(subset)) {
247
- if (!TypeDescriptor.isArray(superset)) throw new Error(`Types '${subsetType}' and '${supersetType}' do not match.`);
248
- if (subset.length > superset.length) return false;
249
- for (const subsetItem of subset) {
250
- const subsetItemType = TypeDescriptor.of(subsetItem);
251
- let isItemInSuperset;
252
- switch (subsetItemType) {
253
- case "array":
254
- case "object":
255
- case "function":
256
- if (visited.includes(subsetItem)) continue;
257
- visited.push(subsetItem);
258
- isItemInSuperset = superset.some((supersetItem) => {
259
- try {
260
- return isSubsetOf(subsetItem, supersetItem, visited);
261
- } catch {
262
- return false;
263
- }
264
- });
265
- break;
266
- default: isItemInSuperset = superset.includes(subsetItem);
267
- }
268
- if (!isItemInSuperset) return false;
269
- }
270
- return true;
271
- }
272
- if (TypeDescriptor.isObject(subset)) {
273
- if (!TypeDescriptor.isObject(superset) || TypeDescriptor.isArray(superset)) throw new Error(`Types '${subsetType}' and '${supersetType}' do not match.`);
274
- if (Object.keys(subset).length > Object.keys(superset).length) return false;
275
- for (const [subsetKey, subsetValue] of Object.entries(subset)) {
276
- const supersetValue = superset[subsetKey];
277
- switch (TypeDescriptor.of(subsetValue)) {
278
- case "array":
279
- case "object":
280
- case "function":
281
- if (visited.includes(subsetValue)) continue;
282
- visited.push(subsetValue);
283
- try {
284
- if (!isSubsetOf(subsetValue, supersetValue, visited)) return false;
285
- } catch {
286
- return false;
287
- }
288
- break;
289
- default: if (subsetValue !== supersetValue) return false;
290
- }
291
- }
292
- return true;
293
- }
294
- if (TypeDescriptor.isNull(subset)) {
295
- if (!TypeDescriptor.isNull(superset)) throw new Error(`Types '${subsetType}' and '${supersetType}' do not match.`);
296
- return true;
297
- }
298
- throw new Error("Invalid operation.");
299
- };
300
- isSubsetOf.structural = function(subset, superset, visited = []) {
301
- if (!TypeDescriptor.isObject(subset)) throw new Error(`Type '${TypeDescriptor.of(subset)}' is not supported.`);
302
- if (!TypeDescriptor.isObject(superset)) throw new Error(`Type '${TypeDescriptor.of(superset)}' is not supported.`);
303
- for (const [subsetKey, subsetValue] of Object.entries(subset)) {
304
- if (superset[subsetKey] === void 0) return false;
305
- const subsetValueType = TypeDescriptor.of(subsetValue);
306
- const supersetValue = superset[subsetKey];
307
- if (subsetValueType === "object") {
308
- if (visited.includes(subsetValue)) continue;
309
- visited.push(subsetValue);
310
- try {
311
- if (!isSubsetOf.structural(subsetValue, supersetValue, visited)) return false;
312
- } catch {
313
- return false;
314
- }
315
- }
316
- }
317
- return true;
318
- };
319
- //#endregion
320
- //#region ../../node_modules/.pnpm/dequal@2.0.3/node_modules/dequal/dist/index.mjs
321
- var has = Object.prototype.hasOwnProperty;
322
- function find(iter, tar, key) {
323
- for (key of iter.keys()) if (dequal(key, tar)) return key;
324
- }
325
- function dequal(foo, bar) {
326
- var ctor, len, tmp;
327
- if (foo === bar) return true;
328
- if (foo && bar && (ctor = foo.constructor) === bar.constructor) {
329
- if (ctor === Date) return foo.getTime() === bar.getTime();
330
- if (ctor === RegExp) return foo.toString() === bar.toString();
331
- if (ctor === Array) {
332
- if ((len = foo.length) === bar.length) while (len-- && dequal(foo[len], bar[len]));
333
- return len === -1;
334
- }
335
- if (ctor === Set) {
336
- if (foo.size !== bar.size) return false;
337
- for (len of foo) {
338
- tmp = len;
339
- if (tmp && typeof tmp === "object") {
340
- tmp = find(bar, tmp);
341
- if (!tmp) return false;
342
- }
343
- if (!bar.has(tmp)) return false;
344
- }
345
- return true;
346
- }
347
- if (ctor === Map) {
348
- if (foo.size !== bar.size) return false;
349
- for (len of foo) {
350
- tmp = len[0];
351
- if (tmp && typeof tmp === "object") {
352
- tmp = find(bar, tmp);
353
- if (!tmp) return false;
354
- }
355
- if (!dequal(len[1], bar.get(tmp))) return false;
356
- }
357
- return true;
358
- }
359
- if (ctor === ArrayBuffer) {
360
- foo = new Uint8Array(foo);
361
- bar = new Uint8Array(bar);
362
- } else if (ctor === DataView) {
363
- if ((len = foo.byteLength) === bar.byteLength) while (len-- && foo.getInt8(len) === bar.getInt8(len));
364
- return len === -1;
365
- }
366
- if (ArrayBuffer.isView(foo)) {
367
- if ((len = foo.byteLength) === bar.byteLength) while (len-- && foo[len] === bar[len]);
368
- return len === -1;
369
- }
370
- if (!ctor || typeof foo === "object") {
371
- len = 0;
372
- for (ctor in foo) {
373
- if (has.call(foo, ctor) && ++len && !has.call(bar, ctor)) return false;
374
- if (!(ctor in bar) || !dequal(foo[ctor], bar[ctor])) return false;
375
- }
376
- return Object.keys(bar).length === len;
377
- }
378
- }
379
- return foo !== foo && bar !== bar;
380
- }
381
- //#endregion
382
- //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/RequestUtils.js
383
- const absoluteUrlRX = /* @__PURE__ */ new RegExp("^[a-z]+://|^data:", "i");
384
- const protocolRelativeUrlRX = /* @__PURE__ */ new RegExp("^//", "i");
385
- function hasCredentialsInUrl(url) {
386
- const urlObject = new URL(url, !absoluteUrlRX.test(url) ? "http://dummy" : void 0);
387
- return Boolean(urlObject.username || urlObject.password);
388
- }
389
- function normalizeUrl(url, allowRelativeUrls) {
390
- if (url instanceof URL) return url.href;
391
- const primitiveUrl = String(url).valueOf();
392
- if (absoluteUrlRX.test(primitiveUrl)) return new URL(primitiveUrl).href;
393
- if (protocolRelativeUrlRX.test(primitiveUrl)) return new URL(primitiveUrl, "http://dummy").href.replace(/^[a-z]+:/, "");
394
- if ("location" in globalThis) if (primitiveUrl.startsWith("/")) return `${globalThis.location.origin}${primitiveUrl}`;
395
- else return `${globalThis.location.href}/${primitiveUrl}`;
396
- else if (allowRelativeUrls) {
397
- const urlInstance = new URL(primitiveUrl, "http://dummy");
398
- return urlInstance.pathname + urlInstance.search;
399
- } 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`");
400
- }
401
- function createCallLogFromUrlAndOptions(url, options) {
402
- const pendingPromises = [];
403
- if (typeof url === "string" || url instanceof String || url instanceof URL) {
404
- const normalizedUrl = normalizeUrl(url, true);
405
- const derivedOptions = options ? { ...options } : {};
406
- if (derivedOptions.headers) derivedOptions.headers = normalizeHeaders(derivedOptions.headers);
407
- derivedOptions.method = derivedOptions.method ? derivedOptions.method.toLowerCase() : "get";
408
- return {
409
- args: [url, options],
410
- url: normalizedUrl,
411
- queryParams: new URLSearchParams(getQuery(normalizedUrl)),
412
- options: derivedOptions,
413
- signal: derivedOptions.signal,
414
- pendingPromises
415
- };
416
- }
417
- if (typeof url === "object") throw new TypeError("fetch-mock: Unrecognised Request object. Read the Config and Installation sections of the docs");
418
- else throw new TypeError("fetch-mock: Invalid arguments passed to fetch");
419
- }
420
- async function createCallLogFromRequest(request, options) {
421
- const pendingPromises = [];
422
- const derivedOptions = { method: request.method };
423
- try {
424
- try {
425
- derivedOptions.body = await request.clone().formData();
426
- } catch {
427
- derivedOptions.body = await request.clone().text();
428
- }
429
- } catch {}
430
- if (request.headers) derivedOptions.headers = normalizeHeaders(request.headers);
431
- const url = normalizeUrl(request.url, true);
432
- return {
433
- args: [request, options],
434
- url,
435
- queryParams: new URLSearchParams(getQuery(url)),
436
- options: Object.assign(derivedOptions, options || {}),
437
- request,
438
- signal: options && options.signal || request.signal,
439
- pendingPromises
440
- };
441
- }
442
- function getPath(url) {
443
- return (absoluteUrlRX.test(url) ? new URL(url) : new URL(url, "http://dummy")).pathname;
444
- }
445
- function getHost(url) {
446
- if (absoluteUrlRX.test(url)) return new URL(url).host;
447
- else if ("location" in globalThis) return globalThis.location.host;
448
- return null;
449
- }
450
- function getQuery(url) {
451
- const u = absoluteUrlRX.test(url) ? new URL(url) : new URL(url, "http://dummy");
452
- return u.search ? u.search.substr(1) : "";
453
- }
454
- function normalizeHeaders(headers) {
455
- let entries;
456
- if (headers instanceof Headers) entries = [...headers.entries()];
457
- else if (Array.isArray(headers)) entries = headers;
458
- else entries = Object.entries(headers);
459
- return Object.fromEntries(entries.map(([key, val]) => [key.toLowerCase(), String(val).valueOf()]));
460
- }
461
- //#endregion
462
- //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/Matchers.js
463
- const isUrlMatcher = (matcher) => matcher instanceof RegExp || typeof matcher === "string" || typeof matcher === "object" && "href" in matcher;
464
- const isFunctionMatcher = (matcher) => typeof matcher === "function";
465
- const stringMatchers = {
466
- begin: (targetString) => ({ url }) => url.startsWith(targetString),
467
- end: (targetString) => ({ url }) => url.endsWith(targetString),
468
- include: (targetString) => ({ url }) => url.includes(targetString),
469
- glob: (targetString) => {
470
- const urlRX = (0, import_glob_to_regexp.default)(targetString);
471
- return ({ url }) => urlRX.test(url);
472
- },
473
- express: (targetString) => {
474
- const urlRX = parse(targetString);
475
- return (callLog) => {
476
- const vals = urlRX.pattern.exec(getPath(callLog.url));
477
- if (!vals) {
478
- callLog.expressParams = {};
479
- return false;
480
- }
481
- vals.shift();
482
- callLog.expressParams = urlRX.keys.reduce((map, paramName, i) => vals[i] ? Object.assign(map, { [paramName]: vals[i] }) : map, {});
483
- return true;
484
- };
485
- },
486
- path: (targetString) => {
487
- const dotlessTargetString = getPath(targetString);
488
- return ({ url }) => {
489
- const path = getPath(url);
490
- return path === targetString || path === dotlessTargetString;
491
- };
492
- },
493
- host: (targetString) => {
494
- return ({ url }) => targetString === getHost(url);
495
- }
496
- };
497
- const getHeaderMatcher = ({ headers: expectedHeaders }) => {
498
- if (!expectedHeaders) return;
499
- const expectation = normalizeHeaders(expectedHeaders);
500
- return ({ options: { headers = {} } }) => {
501
- const lowerCaseHeaders = normalizeHeaders(headers);
502
- return Object.keys(expectation).every((headerName) => lowerCaseHeaders[headerName] === expectation[headerName]);
503
- };
504
- };
505
- const getMissingHeaderMatcher = ({ missingHeaders: expectedMissingHeaders }) => {
506
- if (!expectedMissingHeaders) return;
507
- const expectation = expectedMissingHeaders.map((header) => header.toLowerCase());
508
- return ({ options: { headers = {} } }) => {
509
- const lowerCaseHeaders = normalizeHeaders(headers);
510
- return expectation.every((headerName) => !(headerName in lowerCaseHeaders));
511
- };
512
- };
513
- const getMethodMatcher = ({ method: expectedMethod }) => {
514
- if (!expectedMethod) return;
515
- return ({ options: { method } = {} }) => {
516
- return expectedMethod === (method ? method.toLowerCase() : "get");
517
- };
518
- };
519
- const getQueryParamsMatcher = ({ query: passedQuery }) => {
520
- if (!passedQuery) return;
521
- const expectedQuery = new URLSearchParams();
522
- 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());
523
- else expectedQuery.append(key, typeof value === "object" || typeof value === "undefined" ? "" : value.toString());
524
- const keys = Array.from(expectedQuery.keys());
525
- return ({ queryParams }) => {
526
- return keys.every((key) => {
527
- const expectedValues = expectedQuery.getAll(key).sort();
528
- const actualValues = queryParams.getAll(key).sort();
529
- if (expectedValues.length !== actualValues.length) return false;
530
- if (Array.isArray(passedQuery[key])) return expectedValues.every((expected, index) => expected === actualValues[index]);
531
- return dequal(actualValues, expectedValues);
532
- });
533
- };
534
- };
535
- const getExpressParamsMatcher = ({ params: expectedParams, url }) => {
536
- if (!expectedParams) return;
537
- if (!(typeof url === "string" && /express:/.test(url))) throw new Error("fetch-mock: matching on params is only possible when using an express: matcher");
538
- const expectedKeys = Object.keys(expectedParams);
539
- return ({ expressParams = {} }) => {
540
- return expectedKeys.every((key) => expressParams[key] === expectedParams[key]);
541
- };
542
- };
543
- const formDataToObject = (formData) => {
544
- const fields = [...formData];
545
- const result = {};
546
- fields.forEach(([key, value]) => {
547
- result[key] = result[key] || [];
548
- result[key].push(value);
549
- });
550
- return result;
551
- };
552
- const getBodyMatcher = (route) => {
553
- let { body: expectedBody } = route;
554
- let expectedBodyType = "json";
555
- if (!expectedBody) return;
556
- if (expectedBody instanceof FormData) {
557
- expectedBodyType = "formData";
558
- expectedBody = formDataToObject(expectedBody);
559
- }
560
- return ({ options: { body, method = "get" } }) => {
561
- if (["get", "head"].includes(method.toLowerCase())) return false;
562
- let sentBody;
563
- try {
564
- if (typeof body === "string") {
565
- sentBody = JSON.parse(body);
566
- if (expectedBodyType !== "json") return false;
567
- }
568
- } catch {}
569
- if (body instanceof FormData) {
570
- if (expectedBodyType !== "formData") return false;
571
- sentBody = formDataToObject(body);
572
- }
573
- return sentBody && (route.matchPartialBody ? isSubsetOf(expectedBody, sentBody) : dequal(expectedBody, sentBody));
574
- };
575
- };
576
- const getFunctionMatcher = ({ matcherFunction }) => matcherFunction;
577
- const getRegexpMatcher = (regexp) => ({ url }) => regexp.test(url);
578
- const getFullUrlMatcher = (route, matcherUrl, query) => {
579
- const expectedUrl = normalizeUrl(matcherUrl, route.allowRelativeUrls);
580
- if (route.url === matcherUrl) route.url = expectedUrl;
581
- return ({ url }) => {
582
- if (query && expectedUrl.indexOf("?")) return getPath(url) === getPath(expectedUrl);
583
- return normalizeUrl(url, true) === expectedUrl;
584
- };
585
- };
586
- const getUrlMatcher = (route) => {
587
- const { url: matcherUrl, query } = route;
588
- if (matcherUrl === "*") return () => true;
589
- if (matcherUrl instanceof RegExp) return getRegexpMatcher(matcherUrl);
590
- if (matcherUrl instanceof URL) {
591
- if (matcherUrl.href) return getFullUrlMatcher(route, matcherUrl.href, query);
592
- }
593
- if (typeof matcherUrl === "string") {
594
- for (const shorthand in stringMatchers) if (matcherUrl.indexOf(`${shorthand}:`) === 0) {
595
- const urlFragment = matcherUrl.replace(new RegExp(`^${shorthand}:`), "");
596
- return stringMatchers[shorthand](urlFragment);
597
- }
598
- return getFullUrlMatcher(route, matcherUrl, query);
599
- }
600
- if (typeof matcherUrl === "object") {
601
- const matchers = Object.entries(matcherUrl).map(([key, pattern]) => {
602
- if (key === "regexp") return getRegexpMatcher(pattern);
603
- else if (key in stringMatchers) return stringMatchers[key](pattern);
604
- else throw new Error(`unrecognised url matching pattern: ${key}`);
605
- });
606
- return (route) => matchers.every((matcher) => matcher(route));
607
- }
608
- };
609
- const builtInMatchers = [
610
- {
611
- name: "url",
612
- matcher: getUrlMatcher
613
- },
614
- {
615
- name: "query",
616
- matcher: getQueryParamsMatcher
617
- },
618
- {
619
- name: "method",
620
- matcher: getMethodMatcher
621
- },
622
- {
623
- name: "headers",
624
- matcher: getHeaderMatcher
625
- },
626
- {
627
- name: "missingHeaders",
628
- matcher: getMissingHeaderMatcher
629
- },
630
- {
631
- name: "params",
632
- matcher: getExpressParamsMatcher
633
- },
634
- {
635
- name: "body",
636
- matcher: getBodyMatcher,
637
- usesBody: true
638
- },
639
- {
640
- name: "matcherFunction",
641
- matcher: getFunctionMatcher
642
- }
643
- ];
644
- //#endregion
645
- //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/StatusTextMap.js
646
- const statusTextMap = {
647
- 100: "Continue",
648
- 101: "Switching Protocols",
649
- 102: "Processing",
650
- 200: "OK",
651
- 201: "Created",
652
- 202: "Accepted",
653
- 203: "Non-Authoritative Information",
654
- 204: "No Content",
655
- 205: "Reset Content",
656
- 206: "Partial Content",
657
- 207: "Multi-Status",
658
- 208: "Already Reported",
659
- 226: "IM Used",
660
- 300: "Multiple Choices",
661
- 301: "Moved Permanently",
662
- 302: "Found",
663
- 303: "See Other",
664
- 304: "Not Modified",
665
- 305: "Use Proxy",
666
- 307: "Temporary Redirect",
667
- 308: "Permanent Redirect",
668
- 400: "Bad Request",
669
- 401: "Unauthorized",
670
- 402: "Payment Required",
671
- 403: "Forbidden",
672
- 404: "Not Found",
673
- 405: "Method Not Allowed",
674
- 406: "Not Acceptable",
675
- 407: "Proxy Authentication Required",
676
- 408: "Request Timeout",
677
- 409: "Conflict",
678
- 410: "Gone",
679
- 411: "Length Required",
680
- 412: "Precondition Failed",
681
- 413: "Payload Too Large",
682
- 414: "URI Too Long",
683
- 415: "Unsupported Media Type",
684
- 416: "Range Not Satisfiable",
685
- 417: "Expectation Failed",
686
- 418: "I'm a teapot",
687
- 421: "Misdirected Request",
688
- 422: "Unprocessable Entity",
689
- 423: "Locked",
690
- 424: "Failed Dependency",
691
- 425: "Unordered Collection",
692
- 426: "Upgrade Required",
693
- 428: "Precondition Required",
694
- 429: "Too Many Requests",
695
- 431: "Request Header Fields Too Large",
696
- 451: "Unavailable For Legal Reasons",
697
- 500: "Internal Server Error",
698
- 501: "Not Implemented",
699
- 502: "Bad Gateway",
700
- 503: "Service Unavailable",
701
- 504: "Gateway Timeout",
702
- 505: "HTTP Version Not Supported",
703
- 506: "Variant Also Negotiates",
704
- 507: "Insufficient Storage",
705
- 508: "Loop Detected",
706
- 509: "Bandwidth Limit Exceeded",
707
- 510: "Not Extended",
708
- 511: "Network Authentication Required"
709
- };
710
- //#endregion
711
- //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/Route.js
712
- var __classPrivateFieldSet = function(receiver, state, value, kind, f) {
713
- if (kind === "m") throw new TypeError("Private method is not writable");
714
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
715
- 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");
716
- return kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value), value;
717
- };
718
- var __classPrivateFieldGet = function(receiver, state, kind, f) {
719
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
720
- 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");
721
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
722
- };
723
- var _Route_instances, _a, _Route_responseSubscriptions, _Route_validate, _Route_sanitize, _Route_generateMatcher, _Route_limit, _Route_delayResponse;
724
- var RouteConfigWrapper = class {
725
- constructor(config) {
726
- Object.assign(this, config);
727
- }
728
- };
729
- function isBodyInit(body) {
730
- 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;
731
- }
732
- function sanitizeStatus(status) {
733
- if (status === 0) return 200;
734
- if (!status) return 200;
735
- if (typeof status === "number" && parseInt(String(status), 10) !== status && status >= 200 || status < 600) return status;
736
- throw new TypeError(`fetch-mock: Invalid status ${status} passed on response object.
737
- To respond with a JSON object that has status as a property assign the object to body
738
- e.g. {"body": {"status: "registered"}}`);
739
- }
740
- var Route = class {
741
- constructor(config) {
742
- _Route_instances.add(this);
743
- _Route_responseSubscriptions.set(this, void 0);
744
- this.init(config);
745
- }
746
- init(config) {
747
- this.config = config;
748
- __classPrivateFieldSet(this, _Route_responseSubscriptions, [], "f");
749
- __classPrivateFieldGet(this, _Route_instances, "m", _Route_sanitize).call(this);
750
- __classPrivateFieldGet(this, _Route_instances, "m", _Route_validate).call(this);
751
- __classPrivateFieldGet(this, _Route_instances, "m", _Route_generateMatcher).call(this);
752
- __classPrivateFieldGet(this, _Route_instances, "m", _Route_limit).call(this);
753
- __classPrivateFieldGet(this, _Route_instances, "m", _Route_delayResponse).call(this);
754
- }
755
- reset() {}
756
- waitFor(awaitedRoutes) {
757
- const { response } = this.config;
758
- this.config.response = Promise.all(awaitedRoutes.map((awaitedRoute) => new Promise((res) => awaitedRoute.onRespond(() => {
759
- res(void 0);
760
- })))).then(() => response);
761
- }
762
- onRespond(func) {
763
- __classPrivateFieldGet(this, _Route_responseSubscriptions, "f").push(func);
764
- }
765
- constructResponse(responseInput) {
766
- const responseOptions = this.constructResponseOptions(responseInput);
767
- const body = this.constructResponseBody(responseInput, responseOptions);
768
- const responsePackage = {
769
- response: new this.config.Response(body, responseOptions),
770
- responseOptions,
771
- responseInput
772
- };
773
- __classPrivateFieldGet(this, _Route_responseSubscriptions, "f").forEach((func) => func());
774
- return responsePackage;
775
- }
776
- constructResponseOptions(responseInput) {
777
- const options = responseInput.options || {};
778
- options.status = sanitizeStatus(responseInput.status);
779
- options.statusText = statusTextMap[options.status];
780
- options.headers = new this.config.Headers(responseInput.headers);
781
- return options;
782
- }
783
- constructResponseBody(responseInput, responseOptions) {
784
- let body = responseInput.body;
785
- if (!isBodyInit(body)) if (typeof body === "undefined") body = null;
786
- else if (typeof body === "object") {
787
- body = JSON.stringify(body);
788
- if (!responseOptions.headers.has("Content-Type")) responseOptions.headers.set("Content-Type", "application/json");
789
- } else throw new TypeError("Invalid body provided to construct response");
790
- if (this.config.includeContentLength && !responseOptions.headers.has("Content-Length") && !(body instanceof ReadableStream) && !(body instanceof FormData)) {
791
- let length = 0;
792
- if (body instanceof Blob) length = body.size;
793
- else if (body instanceof ArrayBuffer || ArrayBuffer.isView(body) || body instanceof DataView) length = body.byteLength;
794
- else if (body instanceof URLSearchParams) length = body.toString().length;
795
- else if (typeof body === "string" || body instanceof String) length = body.length;
796
- responseOptions.headers.set("Content-Length", length.toString());
797
- }
798
- return body;
799
- }
800
- static defineMatcher(matcher) {
801
- _a.registeredMatchers.push(matcher);
802
- }
803
- };
804
- _a = Route, _Route_responseSubscriptions = /* @__PURE__ */ new WeakMap(), _Route_instances = /* @__PURE__ */ new WeakSet(), _Route_validate = function _Route_validate() {
805
- if (["matched", "unmatched"].includes(this.config.name)) throw new Error(`fetch-mock: Routes cannot use the reserved name \`${this.config.name}\``);
806
- if (!("response" in this.config)) throw new Error("fetch-mock: Each route must define a response");
807
- 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 '*'");
808
- }, _Route_sanitize = function _Route_sanitize() {
809
- if (this.config.method) this.config.method = this.config.method.toLowerCase();
810
- }, _Route_generateMatcher = function _Route_generateMatcher() {
811
- const activeMatchers = _a.registeredMatchers.filter(({ name }) => name in this.config).map(({ matcher, usesBody }) => ({
812
- matcher: matcher(this.config),
813
- usesBody
814
- }));
815
- this.config.usesBody = activeMatchers.some(({ usesBody }) => usesBody);
816
- this.matcher = (normalizedRequest) => activeMatchers.every(({ matcher }) => matcher(normalizedRequest));
817
- }, _Route_limit = function _Route_limit() {
818
- if (!this.config.repeat) return;
819
- const originalMatcher = this.matcher;
820
- let timesLeft = this.config.repeat;
821
- this.matcher = (callLog) => {
822
- if (timesLeft && originalMatcher(callLog)) {
823
- timesLeft--;
824
- return true;
825
- }
826
- };
827
- this.reset = () => {
828
- timesLeft = this.config.repeat;
829
- };
830
- }, _Route_delayResponse = function _Route_delayResponse() {
831
- if (this.config.delay) {
832
- const { response } = this.config;
833
- this.config.response = () => {
834
- return new Promise((res) => setTimeout(() => res(response), this.config.delay));
835
- };
836
- }
837
- };
838
- Route.registeredMatchers = [];
839
- builtInMatchers.forEach(Route.defineMatcher);
840
- //#endregion
841
- //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/Router.js
842
- const responseConfigProps = [
843
- "body",
844
- "headers",
845
- "throws",
846
- "status",
847
- "redirectUrl"
848
- ];
849
- function nameToOptions(options) {
850
- return typeof options === "string" ? { name: options } : options;
851
- }
852
- function isPromise(response) {
853
- return typeof response.then === "function";
854
- }
855
- function normalizeResponseInput(responseInput) {
856
- if (typeof responseInput === "number") return { status: responseInput };
857
- else if (typeof responseInput === "string" || shouldSendAsObject(responseInput)) return { body: responseInput };
858
- return responseInput;
859
- }
860
- function shouldSendAsObject(responseInput) {
861
- if (responseConfigProps.some((prop) => prop in responseInput)) {
862
- if (Object.keys(responseInput).every((key) => responseConfigProps.includes(key))) return false;
863
- return true;
864
- }
865
- return true;
866
- }
867
- function throwSpecExceptions({ url, options: { headers, method, body } }) {
868
- if (headers) Object.entries(headers).forEach(([key]) => {
869
- if (/\s/.test(key)) throw new TypeError("Invalid name");
870
- });
871
- if (hasCredentialsInUrl(url)) throw new TypeError(`Request cannot be constructed from a URL that includes credentials: ${url}`);
872
- if (["get", "head"].includes(method) && body) throw new TypeError("Request with GET/HEAD method cannot have body.");
873
- }
874
- const resolveUntilResponseConfig = async (callLog) => {
875
- let response = callLog.route.config.response;
876
- while (true) if (typeof response === "function") response = response(callLog);
877
- else if (isPromise(response)) response = await response;
878
- else return response;
879
- };
880
- var Router = class {
881
- constructor(fetchMockConfig, { routes, fallbackRoute } = {}) {
882
- this.config = fetchMockConfig;
883
- this.routes = routes || [];
884
- this.fallbackRoute = fallbackRoute;
885
- }
886
- needsToReadBody(request) {
887
- return Boolean(request && this.routes.some((route) => route.config.usesBody));
888
- }
889
- execute(callLog) {
890
- throwSpecExceptions(callLog);
891
- return new Promise(async (resolve, reject) => {
892
- const { url, options, request, pendingPromises } = callLog;
893
- let eventListenerAbortController;
894
- if (callLog.signal) {
895
- const abort = () => {
896
- const error = new DOMException("The operation was aborted.", "AbortError");
897
- const requestBody = request?.body || options?.body;
898
- if (requestBody instanceof ReadableStream) if (requestBody.locked) console.warn("fetch-mock: Locked request body can't be cancelled");
899
- else requestBody.cancel(error);
900
- if (callLog?.response?.body) if (callLog.response.body.locked) console.warn("fetch-mock: Locked response body can't be cancelled");
901
- else callLog.response.body.cancel(error);
902
- reject(error);
903
- };
904
- if (callLog.signal.aborted) abort();
905
- eventListenerAbortController = new AbortController();
906
- callLog.signal.addEventListener("abort", abort, {
907
- once: true,
908
- signal: eventListenerAbortController.signal
909
- });
910
- }
911
- if (this.needsToReadBody(request)) options.body = await options.body;
912
- const route = (this.fallbackRoute ? [...this.routes, this.fallbackRoute] : this.routes).find((route) => route.matcher(callLog));
913
- if (route) try {
914
- callLog.route = route;
915
- const { response, responseOptions, responseInput } = await this.generateResponse(callLog);
916
- const observableResponse = this.createObservableResponse(response, responseOptions, responseInput, url, pendingPromises);
917
- callLog.response = response;
918
- resolve(observableResponse);
919
- } catch (err) {
920
- reject(err);
921
- } finally {
922
- eventListenerAbortController?.abort();
923
- }
924
- else reject(/* @__PURE__ */ new Error(`fetch-mock: No response or fallback rule to cover ${options && options.method || "GET"} to ${url}`));
925
- });
926
- }
927
- async generateResponse(callLog) {
928
- const responseInput = await resolveUntilResponseConfig(callLog);
929
- if (responseInput instanceof Response) return {
930
- response: responseInput.clone(),
931
- responseOptions: {},
932
- responseInput: {}
933
- };
934
- const responseConfig = normalizeResponseInput(responseInput);
935
- if (responseConfig.throws) throw responseConfig.throws;
936
- return callLog.route.constructResponse(responseConfig);
937
- }
938
- createObservableResponse(response, responseConfig, responseInput, responseUrl, pendingPromises) {
939
- return new Proxy(response, { get: (originalResponse, name) => {
940
- if (responseInput.redirectUrl) {
941
- if (name === "url") return responseInput.redirectUrl;
942
- if (name === "redirected") return true;
943
- } else {
944
- if (name === "url") return responseUrl;
945
- if (name === "redirected") return false;
946
- }
947
- if (responseInput.status === 0) {
948
- if (name === "status") return 0;
949
- if (name === "statusText") return "";
950
- }
951
- if (typeof response[name] === "function") return new Proxy(response[name], { apply: (func, thisArg, args) => {
952
- const result = func.apply(response, args);
953
- if (result.then) pendingPromises.push(result.catch(() => void 0));
954
- return result;
955
- } });
956
- return originalResponse[name];
957
- } });
958
- }
959
- addRoute(matcher, response, nameOrOptions) {
960
- const config = {};
961
- if (matcher instanceof RouteConfigWrapper) Object.assign(config, matcher);
962
- if (isUrlMatcher(matcher)) config.url = matcher;
963
- else if (isFunctionMatcher(matcher)) config.matcherFunction = matcher;
964
- else Object.assign(config, matcher);
965
- if (typeof response !== "undefined") config.response = response;
966
- if (nameOrOptions) Object.assign(config, typeof nameOrOptions === "string" ? nameToOptions(nameOrOptions) : nameOrOptions);
967
- const route = new Route({
968
- ...this.config,
969
- ...config
970
- });
971
- 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.");
972
- if (route.config.waitFor) {
973
- const routeNamesToWaitFor = Array.isArray(route.config.waitFor) ? route.config.waitFor : [route.config.waitFor];
974
- const routesToAwait = [];
975
- routeNamesToWaitFor.forEach((routeName) => {
976
- const routeToAwait = this.routes.find(({ config: { name: existingName } }) => routeName === existingName);
977
- if (routeToAwait) routesToAwait.push(routeToAwait);
978
- else throw new Error(`Cannot wait for route \`${routeName}\`: route of that name does not exist`);
979
- });
980
- route.waitFor(routesToAwait);
981
- }
982
- this.routes.push(route);
983
- }
984
- setFallback(response) {
985
- if (this.fallbackRoute) console.warn("calling fetchMock.catch() twice - are you sure you want to overwrite the previous fallback response");
986
- this.fallbackRoute = new Route({
987
- matcherFunction: () => true,
988
- response: response || "ok",
989
- ...this.config
990
- });
991
- this.fallbackRoute.config.isFallback = true;
992
- }
993
- removeRoutes({ names, includeSticky, includeFallback } = {}) {
994
- includeFallback = includeFallback ?? true;
995
- this.routes = this.routes.filter(({ config: { sticky, name } }) => {
996
- if (sticky && !includeSticky) return true;
997
- if (!names) return false;
998
- return !names.includes(name);
999
- });
1000
- if (includeFallback) delete this.fallbackRoute;
1001
- }
1002
- modifyRoute(routeName, options) {
1003
- const route = this.routes.find(({ config: { name } }) => name === routeName);
1004
- if (!route) throw new Error(`Cannot call modifyRoute() on route \`${routeName}\`: route of that name not found`);
1005
- if (route.config.sticky) throw new Error(`Cannot call modifyRoute() on route \`${routeName}\`: route is sticky and cannot be modified`);
1006
- if ("name" in options) throw new Error(`Cannot rename the route \`${routeName}\` as \`${options.name}\`: renaming routes is not supported`);
1007
- if ("sticky" in options) throw new Error(`Altering the stickiness of route \`${routeName}\` is not supported`);
1008
- const newConfig = {
1009
- ...route.config,
1010
- ...options
1011
- };
1012
- Object.entries(options).forEach(([key, value]) => {
1013
- if (value === null) delete newConfig[key];
1014
- });
1015
- route.init(newConfig);
1016
- }
1017
- };
1018
- //#endregion
1019
- //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/CallHistory.js
1020
- const isName = (filter) => typeof filter === "string" && /^[\da-zA-Z-]+$/.test(filter) && !["matched", "unmatched"].includes(filter);
1021
- const isMatchedOrUnmatched = (filter) => typeof filter === "boolean" || ["matched", "unmatched"].includes(filter);
1022
- var CallHistory = class {
1023
- constructor(config, router) {
1024
- this.callLogs = [];
1025
- this.config = config;
1026
- this.router = router;
1027
- }
1028
- recordCall(callLog) {
1029
- this.callLogs.push(callLog);
1030
- }
1031
- clear() {
1032
- this.callLogs.forEach(({ route }) => {
1033
- if (route) route.reset();
1034
- });
1035
- this.callLogs = [];
1036
- }
1037
- async flush(waitForResponseMethods) {
1038
- const queuedPromises = this.callLogs.flatMap((call) => call.pendingPromises);
1039
- await Promise.allSettled(queuedPromises);
1040
- if (waitForResponseMethods) {
1041
- await Promise.resolve();
1042
- await this.flush();
1043
- }
1044
- }
1045
- calls(filter, options) {
1046
- let calls = [...this.callLogs];
1047
- if (typeof filter === "undefined" && !options) return calls;
1048
- if (isMatchedOrUnmatched(filter)) {
1049
- if ([true, "matched"].includes(filter)) calls = calls.filter(({ route }) => !route?.config || !route.config.isFallback);
1050
- else if ([false, "unmatched"].includes(filter)) calls = calls.filter(({ route }) => Boolean(route?.config && route.config.isFallback));
1051
- if (!options) return calls;
1052
- } else if (isName(filter)) {
1053
- calls = calls.filter(({ route }) => route?.config?.name === filter);
1054
- if (!options) return calls;
1055
- } else if (isUrlMatcher(filter)) options = {
1056
- url: filter,
1057
- ...options || {}
1058
- };
1059
- else options = {
1060
- ...filter,
1061
- ...options || {}
1062
- };
1063
- const { matcher } = new Route({
1064
- response: "ok",
1065
- ...options
1066
- });
1067
- calls = calls.filter(({ url, options }) => {
1068
- return matcher(createCallLogFromUrlAndOptions(url, options));
1069
- });
1070
- return calls;
1071
- }
1072
- called(filter, options) {
1073
- return Boolean(this.calls(filter, options).length);
1074
- }
1075
- lastCall(filter, options) {
1076
- return this.calls(filter, options).pop();
1077
- }
1078
- done(routeNames) {
1079
- let routesToCheck = this.router.routes;
1080
- if (routeNames) {
1081
- routeNames = Array.isArray(routeNames) ? routeNames : [routeNames];
1082
- routesToCheck = this.router.routes.filter(({ config: { name } }) => routeNames.includes(name));
1083
- }
1084
- return routesToCheck.map((route) => {
1085
- const calls = this.callLogs.filter(({ route: routeApplied }) => routeApplied === route);
1086
- if (!calls.length) {
1087
- console.warn(`Warning: ${route.config.name} not called`);
1088
- return false;
1089
- }
1090
- const expectedTimes = route.config.repeat;
1091
- if (!expectedTimes) return true;
1092
- const actualTimes = calls.length;
1093
- if (expectedTimes > actualTimes) {
1094
- console.warn(`Warning: ${route.config.name} only called ${actualTimes} times, but ${expectedTimes} expected`);
1095
- return false;
1096
- }
1097
- return true;
1098
- }).every((isDone) => isDone);
1099
- }
1100
- };
1101
- //#endregion
1102
- //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/FetchMock.js
1103
- const defaultFetchMockConfig = {
1104
- includeContentLength: true,
1105
- matchPartialBody: false,
1106
- Request: globalThis.Request,
1107
- Response: globalThis.Response,
1108
- Headers: globalThis.Headers,
1109
- fetch: globalThis.fetch
1110
- };
1111
- const defineShorthand = (shorthandOptions) => {
1112
- function shorthand(matcher, response, options) {
1113
- return this.route(matcher, response, Object.assign(options || {}, shorthandOptions));
1114
- }
1115
- return shorthand;
1116
- };
1117
- const defineGreedyShorthand = (shorthandOptions) => {
1118
- return function(response, options) {
1119
- return this.route("*", response, Object.assign(options || {}, shorthandOptions));
1120
- };
1121
- };
1122
- //#endregion
1123
- //#region ../../node_modules/.pnpm/fetch-mock@12.6.0/node_modules/fetch-mock/dist/esm/index.js
1124
- var esm_default = new class FetchMock {
1125
- constructor(config, router) {
1126
- this.sticky = defineShorthand({ sticky: true });
1127
- this.once = defineShorthand({ repeat: 1 });
1128
- this.any = defineGreedyShorthand({});
1129
- this.anyOnce = defineGreedyShorthand({ repeat: 1 });
1130
- this.get = defineShorthand({ method: "get" });
1131
- this.getOnce = defineShorthand({
1132
- method: "get",
1133
- repeat: 1
1134
- });
1135
- this.post = defineShorthand({ method: "post" });
1136
- this.postOnce = defineShorthand({
1137
- method: "post",
1138
- repeat: 1
1139
- });
1140
- this.put = defineShorthand({ method: "put" });
1141
- this.putOnce = defineShorthand({
1142
- method: "put",
1143
- repeat: 1
1144
- });
1145
- this.delete = defineShorthand({ method: "delete" });
1146
- this.deleteOnce = defineShorthand({
1147
- method: "delete",
1148
- repeat: 1
1149
- });
1150
- this.head = defineShorthand({ method: "head" });
1151
- this.headOnce = defineShorthand({
1152
- method: "head",
1153
- repeat: 1
1154
- });
1155
- this.patch = defineShorthand({ method: "patch" });
1156
- this.patchOnce = defineShorthand({
1157
- method: "patch",
1158
- repeat: 1
1159
- });
1160
- this.config = config;
1161
- this.router = new Router(this.config, {
1162
- routes: router ? [...router.routes] : [],
1163
- fallbackRoute: router ? router.fallbackRoute : null
1164
- });
1165
- this.callHistory = new CallHistory(this.config, this.router);
1166
- this.fetchHandler = this.fetchHandler.bind(this);
1167
- Object.assign(this.fetchHandler, { fetchMock: this });
1168
- }
1169
- createInstance() {
1170
- return new FetchMock({ ...this.config }, this.router);
1171
- }
1172
- async fetchHandler(requestInput, requestInit) {
1173
- let callLog;
1174
- if (requestInput instanceof this.config.Request) callLog = await createCallLogFromRequest(requestInput, requestInit);
1175
- else callLog = createCallLogFromUrlAndOptions(requestInput, requestInit);
1176
- this.callHistory.recordCall(callLog);
1177
- const responsePromise = this.router.execute(callLog);
1178
- callLog.pendingPromises.push(responsePromise);
1179
- return responsePromise;
1180
- }
1181
- route(matcher, response, options) {
1182
- this.router.addRoute(matcher, response, options);
1183
- return this;
1184
- }
1185
- catch(response) {
1186
- this.router.setFallback(response);
1187
- return this;
1188
- }
1189
- defineMatcher(matcher) {
1190
- Route.defineMatcher(matcher);
1191
- }
1192
- removeRoutes(options) {
1193
- this.router.removeRoutes(options);
1194
- return this;
1195
- }
1196
- removeRoute(routeName) {
1197
- this.router.removeRoutes({ names: [routeName] });
1198
- return this;
1199
- }
1200
- modifyRoute(routeName, options) {
1201
- this.router.modifyRoute(routeName, options);
1202
- return this;
1203
- }
1204
- clearHistory() {
1205
- this.callHistory.clear();
1206
- return this;
1207
- }
1208
- mockGlobal() {
1209
- globalThis.fetch = this.fetchHandler;
1210
- return this;
1211
- }
1212
- unmockGlobal() {
1213
- globalThis.fetch = this.config.fetch;
1214
- return this;
1215
- }
1216
- hardReset(options) {
1217
- this.clearHistory();
1218
- this.removeRoutes(options);
1219
- this.unmockGlobal();
1220
- return this;
1221
- }
1222
- spy(matcher, name) {
1223
- const boundFetch = this.config.fetch.bind(globalThis);
1224
- if (matcher) this.route(matcher, ({ args }) => boundFetch(...args), name);
1225
- else this.catch(({ args }) => boundFetch(...args));
1226
- return this;
1227
- }
1228
- spyGlobal() {
1229
- this.mockGlobal();
1230
- return this.spy();
1231
- }
1232
- }({ ...defaultFetchMockConfig });
1233
- //#endregion
1234
14
  //#region src/contexts/cid-v1.json
1235
15
  var cid_v1_default = { "@context": {
1236
16
  "@protected": true,
@@ -5729,13 +4509,47 @@ const DEFAULT_MAX_REDIRECTION = 20;
5729
4509
  * Gets a {@link RemoteDocument} from the given response.
5730
4510
  * @param url The URL of the document to load.
5731
4511
  * @param response The response to get the document from.
5732
- * @param fetch The function to fetch the document.
4512
+ * @param fetch The function to fetch the document. It is called with the URL
4513
+ * of an alternate document, if any, after the response body has
4514
+ * been either read or its cancellation has been started.
5733
4515
  * @returns The loaded remote document.
5734
4516
  * @throws {FetchError} If the response is not OK.
5735
4517
  * @internal
5736
4518
  */
5737
4519
  async function getRemoteDocument(url, response, fetch) {
5738
- const documentUrl = response.url === "" ? url : response.url;
4520
+ const result = await parseRemoteDocument(url, response);
4521
+ if ("alternate" in result) return await fetch(result.alternate);
4522
+ return result.document;
4523
+ }
4524
+ /**
4525
+ * Starts cancelling the body of a response without waiting for it to settle.
4526
+ * The cancellation of a branch of a tee (e.g., a body cloned by a fetch mock)
4527
+ * settles only after the other branch is cancelled too, per the Streams
4528
+ * standard, so awaiting it could block forever. Failures are only logged.
4529
+ * @param response The response whose body is cancelled.
4530
+ * @param url The URL of the response, for logging.
4531
+ */
4532
+ function cancelResponseBody(response, url) {
4533
+ response.body?.cancel().catch((error) => {
4534
+ logger.debug("Failed to cancel the response body from {url}: {error}", {
4535
+ url,
4536
+ error
4537
+ });
4538
+ });
4539
+ }
4540
+ /**
4541
+ * Gets a {@link RemoteDocument} from the given response, or the URL of an
4542
+ * alternate document to load instead. When an alternate document is found,
4543
+ * the response body has been either read or its cancellation has been started
4544
+ * (without waiting for it), so that the alternate document can be loaded after
4545
+ * the response's session is closed.
4546
+ * @param url The URL of the document to load.
4547
+ * @param response The response to get the document from.
4548
+ * @returns The loaded remote document, or the alternate document URL.
4549
+ * @throws {FetchError} If the response is not OK.
4550
+ */
4551
+ async function parseRemoteDocument(url, response) {
4552
+ const documentUrl = url;
5739
4553
  const docUrl = new URL(documentUrl);
5740
4554
  if (!response.ok) {
5741
4555
  logger.error("Failed to fetch document: {status} {url} {headers}", {
@@ -5772,7 +4586,8 @@ async function getRemoteDocument(url, response, fetch) {
5772
4586
  alternateUrl: altUri.href,
5773
4587
  url: documentUrl
5774
4588
  });
5775
- return await fetch(altUri.href);
4589
+ cancelResponseBody(response, documentUrl);
4590
+ return { alternate: altUri.href };
5776
4591
  }
5777
4592
  }
5778
4593
  }
@@ -5797,7 +4612,7 @@ async function getRemoteDocument(url, response, fetch) {
5797
4612
  alternateUrl: attribs.href,
5798
4613
  url: documentUrl
5799
4614
  });
5800
- return await fetch(new URL(attribs.href, docUrl).href);
4615
+ return { alternate: new URL(attribs.href, docUrl).href };
5801
4616
  }
5802
4617
  }
5803
4618
  document = JSON.parse(html);
@@ -5807,11 +4622,11 @@ async function getRemoteDocument(url, response, fetch) {
5807
4622
  url: documentUrl,
5808
4623
  headers: Object.fromEntries(response.headers.entries())
5809
4624
  });
5810
- return {
4625
+ return { document: {
5811
4626
  contextUrl,
5812
4627
  document,
5813
4628
  documentUrl
5814
- };
4629
+ } };
5815
4630
  }
5816
4631
  /**
5817
4632
  * Creates a JSON-LD document loader that utilizes the browser's `fetch` API.
@@ -5837,8 +4652,53 @@ async function getRemoteDocument(url, response, fetch) {
5837
4652
  * @returns The document loader.
5838
4653
  * @since 1.3.0
5839
4654
  */
5840
- function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAgent } = {}) {
4655
+ function getDocumentLoader(options = {}) {
4656
+ return createDocumentLoader(options);
4657
+ }
4658
+ /**
4659
+ * The implementation of {@link getDocumentLoader}, with injectable
4660
+ * dependencies. This is not exported from any entry point.
4661
+ * @param options Options for the document loader.
4662
+ * @param deps The dependencies.
4663
+ * @returns The document loader.
4664
+ */
4665
+ function createDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAgent } = {}, deps = {}) {
5841
4666
  const tracer = trace.getTracerProvider().getTracer(name, version);
4667
+ async function openHop(url, signal) {
4668
+ try {
4669
+ if (deps.openHop != null) return await deps.openHop(url);
4670
+ return allowPrivateAddress ? await openDirectFetch(url) : await openPublicFetch(url, { signal });
4671
+ } catch (error) {
4672
+ if (error instanceof UrlError) if (error.reason === "dns") logger.debug("DNS lookup failed for {url}", {
4673
+ url,
4674
+ error
4675
+ });
4676
+ else logger.error("Disallowed private URL: {url}", {
4677
+ url,
4678
+ error
4679
+ });
4680
+ throw error;
4681
+ }
4682
+ }
4683
+ async function fetchHop(session, url, span, options) {
4684
+ try {
4685
+ const request = createActivityPubRequest(url, { userAgent });
4686
+ logRequest(logger, request);
4687
+ const response = await session.fetch(request, {
4688
+ redirect: "manual",
4689
+ signal: options?.signal
4690
+ });
4691
+ span.setAttribute("http.response.status_code", response.status);
4692
+ if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
4693
+ const location = response.headers.get("Location");
4694
+ cancelResponseBody(response, url);
4695
+ return { location };
4696
+ }
4697
+ return await parseRemoteDocument(url, response);
4698
+ } finally {
4699
+ await session.close();
4700
+ }
4701
+ }
5842
4702
  async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
5843
4703
  options?.signal?.throwIfAborted();
5844
4704
  const parsedUrl = new URL(url);
@@ -5853,33 +4713,15 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
5853
4713
  documentUrl: currentUrl
5854
4714
  };
5855
4715
  }
5856
- if (!allowPrivateAddress) try {
5857
- await validatePublicUrl(currentUrl);
5858
- } catch (error) {
5859
- if (error instanceof UrlError) if (error.reason === "dns") logger.debug("DNS lookup failed for {url}", {
5860
- url: currentUrl,
5861
- error
5862
- });
5863
- else logger.error("Disallowed private URL: {url}", {
5864
- url: currentUrl,
5865
- error
5866
- });
5867
- throw error;
5868
- }
4716
+ const session = await openHop(currentUrl, options?.signal);
5869
4717
  visited.add(currentUrl);
5870
4718
  return await tracer.startActiveSpan("activitypub.fetch_document", {
5871
4719
  kind: SpanKind.CLIENT,
5872
4720
  attributes: { "url.full": currentUrl }
5873
4721
  }, async (span) => {
5874
4722
  try {
5875
- const request = createActivityPubRequest(currentUrl, { userAgent });
5876
- logRequest(logger, request);
5877
- const response = await fetch(request, {
5878
- redirect: "manual",
5879
- signal: options?.signal
5880
- });
5881
- span.setAttribute("http.response.status_code", response.status);
5882
- if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
4723
+ const outcome = await fetchHop(session, currentUrl, span, options);
4724
+ if ("location" in outcome) {
5883
4725
  if (redirected >= DEFAULT_MAX_REDIRECTION) {
5884
4726
  logger.error("Too many redirections ({redirections}) while fetching document.", {
5885
4727
  redirections: redirected + 1,
@@ -5887,7 +4729,7 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
5887
4729
  });
5888
4730
  throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
5889
4731
  }
5890
- const redirectUrl = new URL(response.headers.get("Location"), response.url === "" ? currentUrl : response.url).href;
4732
+ const redirectUrl = new URL(outcome.location, currentUrl).href;
5891
4733
  span.setAttribute("http.redirect.url", redirectUrl);
5892
4734
  if (visited.has(redirectUrl)) {
5893
4735
  logger.error("Detected a redirect loop while fetching document: {url} -> {redirectUrl}", {
@@ -5898,12 +4740,14 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
5898
4740
  }
5899
4741
  return await load(redirectUrl, options, redirected + 1, visited);
5900
4742
  }
5901
- const result = await getRemoteDocument(currentUrl, response, async (alternateUrl) => {
4743
+ let result;
4744
+ if ("alternate" in outcome) {
4745
+ const alternateUrl = outcome.alternate;
5902
4746
  options?.signal?.throwIfAborted();
5903
4747
  if (redirected >= DEFAULT_MAX_REDIRECTION) throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
5904
4748
  if (visited.has(alternateUrl)) throw new FetchError(currentUrl, `Redirect loop detected: ${alternateUrl}`);
5905
- return await load(alternateUrl, options, redirected + 1, visited);
5906
- });
4749
+ result = await load(alternateUrl, options, redirected + 1, visited);
4750
+ } else result = outcome.document;
5907
4751
  span.setAttribute("docloader.document_url", result.documentUrl);
5908
4752
  if (result.contextUrl != null) span.setAttribute("docloader.context_url", result.contextUrl);
5909
4753
  return result;
@@ -5926,6 +4770,24 @@ function getDocumentLoader({ allowPrivateAddress, skipPreloadedContexts, userAge
5926
4770
  var jsonld_default = jsonld;
5927
4771
  //#endregion
5928
4772
  //#region src/docloader.test.ts
4773
+ const unpinnedPublicFetch = createPublicFetch({
4774
+ pinner: () => Promise.resolve(null),
4775
+ lookup: () => Promise.reject(/* @__PURE__ */ new Error("Unexpected default lookup"))
4776
+ });
4777
+ const PUBLIC_ADDRESS = {
4778
+ address: "93.184.215.14",
4779
+ family: 4
4780
+ };
4781
+ const PRIVATE_ADDRESS = {
4782
+ address: "10.0.0.1",
4783
+ family: 4
4784
+ };
4785
+ function lookupTestAddresses(hostname) {
4786
+ return Promise.resolve([hostname === "private.example" ? PRIVATE_ADDRESS : PUBLIC_ADDRESS]);
4787
+ }
4788
+ function openUnpinnedHop(url) {
4789
+ return unpinnedPublicFetch.openPublicFetch(url, { lookup: lookupTestAddresses });
4790
+ }
5929
4791
  test("new FetchError()", () => {
5930
4792
  const e = new FetchError("https://example.com/", "An error message.");
5931
4793
  deepStrictEqual(e.name, "FetchError");
@@ -5936,7 +4798,7 @@ test("new FetchError()", () => {
5936
4798
  deepStrictEqual(e2.message, "https://example.org/");
5937
4799
  });
5938
4800
  test("getDocumentLoader()", async (t) => {
5939
- const fetchDocumentLoader = getDocumentLoader();
4801
+ const fetchDocumentLoader = createDocumentLoader({}, { openHop: openUnpinnedHop });
5940
4802
  esm_default.spyGlobal();
5941
4803
  esm_default.get("https://example.com/object", { body: {
5942
4804
  "@context": "https://www.w3.org/ns/activitystreams",
@@ -6351,10 +5213,26 @@ test("getDocumentLoader()", async (t) => {
6351
5213
  name: "Fetched object",
6352
5214
  type: "Object"
6353
5215
  } });
5216
+ esm_default.get("https://example.com/private-redirect", {
5217
+ status: 302,
5218
+ headers: { Location: "https://private.example/object" }
5219
+ });
5220
+ esm_default.get("https://private.example/object", { body: {
5221
+ "@context": "https://www.w3.org/ns/activitystreams",
5222
+ id: "https://private.example/object",
5223
+ type: "Object"
5224
+ } });
6354
5225
  await t.step("allowPrivateAddress: false", async () => {
6355
5226
  await rejects(() => fetchDocumentLoader("https://localhost/object"), UrlError);
6356
5227
  await rejects(() => fetchDocumentLoader("https://example.com/localhost-redirect"), UrlError);
6357
5228
  await rejects(() => fetchDocumentLoader("https://example.com/localhost-link"), UrlError);
5229
+ await rejects(() => fetchDocumentLoader("https://private.example/object"), UrlError);
5230
+ await rejects(() => fetchDocumentLoader("https://example.com/private-redirect"), UrlError);
5231
+ deepStrictEqual(esm_default.callHistory.calls("https://private.example/object").length, 0);
5232
+ const defaultLoader = getDocumentLoader();
5233
+ await rejects(() => defaultLoader("https://localhost/object"), UrlError);
5234
+ await rejects(() => defaultLoader("https://127.0.0.1/object"), UrlError);
5235
+ deepStrictEqual(esm_default.callHistory.calls("https://localhost/object").length, 0);
6358
5236
  });
6359
5237
  const fetchDocumentLoader2 = getDocumentLoader({ allowPrivateAddress: true });
6360
5238
  await t.step("allowPrivateAddress: true", async () => {
@@ -6509,18 +5387,14 @@ test("getRemoteDocument() bounds JSON by default", async () => {
6509
5387
  deepStrictEqual(pulls, 257);
6510
5388
  });
6511
5389
  test("getDocumentLoader() logs DNS failures as such", async (t) => {
6512
- if ("Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted") throw new Error("This DNS test requires Deno network permission (--allow-net).");
6513
- const loader = getDocumentLoader();
6514
5390
  for (const result of [
6515
5391
  "throws",
6516
5392
  "empty",
6517
- "private"
5393
+ "private",
5394
+ "default"
6518
5395
  ]) await t.step(result, async () => {
6519
- const originalLookup = dns.lookup;
6520
- dns.lookup = (() => result === "throws" ? Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")) : Promise.resolve(result === "empty" ? [] : [{
6521
- address: "127.0.0.1",
6522
- family: 4
6523
- }]));
5396
+ const lookup = () => result === "throws" ? Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")) : Promise.resolve(result === "empty" ? [] : [PRIVATE_ADDRESS]);
5397
+ const loader = result === "default" ? getDocumentLoader() : createDocumentLoader({}, { openHop: (url) => unpinnedPublicFetch.openPublicFetch(url, { lookup }) });
6524
5398
  const records = [];
6525
5399
  await configure({
6526
5400
  sinks: { buffer: (record) => records.push(record) },
@@ -6535,19 +5409,20 @@ test("getDocumentLoader() logs DNS failures as such", async (t) => {
6535
5409
  reset: true
6536
5410
  });
6537
5411
  try {
6538
- const url = "https://dns-failure.invalid/object";
5412
+ const url = result === "default" ? "https://10.0.0.1/object" : "https://dns-failure.invalid/object";
6539
5413
  let error;
6540
5414
  await rejects(() => loader(url), (e) => {
6541
5415
  error = e;
6542
5416
  return true;
6543
5417
  });
6544
5418
  ok(error instanceof UrlError);
6545
- deepStrictEqual(error.reason, result === "private" ? "disallowed" : "dns");
5419
+ const disallowed = result === "private" || result === "default";
5420
+ deepStrictEqual(error.reason, disallowed ? "disallowed" : "dns");
6546
5421
  deepStrictEqual(records.map((r) => [
6547
5422
  r.level,
6548
5423
  r.rawMessage,
6549
5424
  r.properties.url
6550
- ]), [result === "private" ? [
5425
+ ]), [disallowed ? [
6551
5426
  "error",
6552
5427
  "Disallowed private URL: {url}",
6553
5428
  url
@@ -6559,7 +5434,6 @@ test("getDocumentLoader() logs DNS failures as such", async (t) => {
6559
5434
  ok(records[0].properties.error === error);
6560
5435
  } finally {
6561
5436
  await reset();
6562
- dns.lookup = originalLookup;
6563
5437
  }
6564
5438
  });
6565
5439
  });
@@ -6803,5 +5677,422 @@ for (const url of unsupportedUrls) {
6803
5677
  });
6804
5678
  });
6805
5679
  }
5680
+ function createHopRecorder(responder) {
5681
+ const events = [];
5682
+ const states = /* @__PURE__ */ new Map();
5683
+ const violations = [];
5684
+ let responses = 0;
5685
+ let opens = 0;
5686
+ let closes = 0;
5687
+ function respond(url) {
5688
+ const n = ++responses;
5689
+ events.push(`fetch ${url} #${n}`);
5690
+ const { status = 200, headers, body = "", failCancel, hangCancel, responseUrl } = responder(url);
5691
+ const state = {
5692
+ n,
5693
+ settled: false
5694
+ };
5695
+ let sent = false;
5696
+ const stream = new ReadableStream({
5697
+ pull(controller) {
5698
+ if (!sent && body !== "") {
5699
+ sent = true;
5700
+ controller.enqueue(new TextEncoder().encode(body));
5701
+ return;
5702
+ }
5703
+ controller.close();
5704
+ state.settled = true;
5705
+ events.push(`consumed #${n}`);
5706
+ },
5707
+ cancel() {
5708
+ state.settled = true;
5709
+ events.push(`cancel #${n}`);
5710
+ if (failCancel) throw new Error(`Failed to cancel #${n}`);
5711
+ if (hangCancel) return new Promise(() => {});
5712
+ }
5713
+ }, { highWaterMark: 0 });
5714
+ const response = new Response(stream, {
5715
+ status,
5716
+ headers
5717
+ });
5718
+ if (responseUrl != null) Object.defineProperty(response, "url", { value: responseUrl });
5719
+ states.set(response, state);
5720
+ return response;
5721
+ }
5722
+ function record(url, session) {
5723
+ opens++;
5724
+ events.push(`open ${url}`);
5725
+ const fetched = [];
5726
+ let closed = false;
5727
+ return {
5728
+ get url() {
5729
+ return session.url;
5730
+ },
5731
+ async fetch(input, init) {
5732
+ const response = await session.fetch(input, init);
5733
+ fetched.push(response);
5734
+ return response;
5735
+ },
5736
+ async close() {
5737
+ if (closed) return;
5738
+ closed = true;
5739
+ for (const response of fetched) {
5740
+ const state = states.get(response);
5741
+ if (state != null && !state.settled) violations.push(`#${state.n} is still open when closing ${url}`);
5742
+ }
5743
+ closes++;
5744
+ events.push(`close ${url}`);
5745
+ await session.close();
5746
+ }
5747
+ };
5748
+ }
5749
+ function openHop(url) {
5750
+ const origin = new URL(url).origin;
5751
+ return Promise.resolve(record(url, {
5752
+ url: new URL(url),
5753
+ fetch(input) {
5754
+ const target = input instanceof Request ? input.url : String(input);
5755
+ ok(new URL(target).origin === origin, `${target} is not on ${origin}`);
5756
+ return Promise.resolve(respond(target));
5757
+ },
5758
+ close: () => Promise.resolve()
5759
+ }));
5760
+ }
5761
+ return {
5762
+ events,
5763
+ respond,
5764
+ record,
5765
+ openHop,
5766
+ assertInvariants() {
5767
+ deepStrictEqual(violations, []);
5768
+ deepStrictEqual(opens, closes);
5769
+ }
5770
+ };
5771
+ }
5772
+ const lifecycleDocument = {
5773
+ "@context": "https://www.w3.org/ns/activitystreams",
5774
+ id: "https://b.example/object",
5775
+ type: "Object"
5776
+ };
5777
+ test("getDocumentLoader() closes a redirect hop before the next hop", async () => {
5778
+ const recorder = createHopRecorder((url) => url === "https://a.example/start" ? {
5779
+ status: 302,
5780
+ headers: { Location: "https://b.example/object" }
5781
+ } : { body: JSON.stringify(lifecycleDocument) });
5782
+ deepStrictEqual(await createDocumentLoader({}, { openHop: recorder.openHop })("https://a.example/start"), {
5783
+ contextUrl: null,
5784
+ documentUrl: "https://b.example/object",
5785
+ document: lifecycleDocument
5786
+ });
5787
+ deepStrictEqual(recorder.events, [
5788
+ "open https://a.example/start",
5789
+ "fetch https://a.example/start #1",
5790
+ "cancel #1",
5791
+ "close https://a.example/start",
5792
+ "open https://b.example/object",
5793
+ "fetch https://b.example/object #2",
5794
+ "consumed #2",
5795
+ "close https://b.example/object"
5796
+ ]);
5797
+ recorder.assertInvariants();
5798
+ });
5799
+ test("getDocumentLoader() cancels the body before a Link header alternate", async () => {
5800
+ const recorder = createHopRecorder((url) => url === "https://a.example/start" ? {
5801
+ headers: {
5802
+ "Content-Type": "text/plain",
5803
+ Link: "<https://b.example/object>; rel=\"alternate\"; type=\"application/activity+json\""
5804
+ },
5805
+ body: "not JSON"
5806
+ } : { body: JSON.stringify(lifecycleDocument) });
5807
+ deepStrictEqual((await createDocumentLoader({}, { openHop: recorder.openHop })("https://a.example/start")).documentUrl, "https://b.example/object");
5808
+ deepStrictEqual(recorder.events, [
5809
+ "open https://a.example/start",
5810
+ "fetch https://a.example/start #1",
5811
+ "cancel #1",
5812
+ "close https://a.example/start",
5813
+ "open https://b.example/object",
5814
+ "fetch https://b.example/object #2",
5815
+ "consumed #2",
5816
+ "close https://b.example/object"
5817
+ ]);
5818
+ recorder.assertInvariants();
5819
+ });
5820
+ test("getDocumentLoader() reads the body before an HTML alternate", async () => {
5821
+ const recorder = createHopRecorder((url) => url === "https://a.example/start" ? {
5822
+ headers: { "Content-Type": "text/html" },
5823
+ body: "<link rel=\"alternate\" type=\"application/activity+json\" href=\"https://b.example/object\">"
5824
+ } : { body: JSON.stringify(lifecycleDocument) });
5825
+ deepStrictEqual((await createDocumentLoader({}, { openHop: recorder.openHop })("https://a.example/start")).documentUrl, "https://b.example/object");
5826
+ deepStrictEqual(recorder.events, [
5827
+ "open https://a.example/start",
5828
+ "fetch https://a.example/start #1",
5829
+ "consumed #1",
5830
+ "close https://a.example/start",
5831
+ "open https://b.example/object",
5832
+ "fetch https://b.example/object #2",
5833
+ "consumed #2",
5834
+ "close https://b.example/object"
5835
+ ]);
5836
+ recorder.assertInvariants();
5837
+ });
5838
+ /**
5839
+ * Fails instead of blocking the test if the given promise does not settle
5840
+ * within the given time.
5841
+ */
5842
+ async function settlesWithin(promise, milliseconds = 1e3) {
5843
+ let timer;
5844
+ try {
5845
+ return await Promise.race([promise, new Promise((_, reject) => {
5846
+ timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Did not settle in ${milliseconds} ms.`)), milliseconds);
5847
+ })]);
5848
+ } finally {
5849
+ clearTimeout(timer);
5850
+ }
5851
+ }
5852
+ /**
5853
+ * Runs the given function while capturing the document loader's logs, and
5854
+ * waits until the rejections of body cancellations started without being
5855
+ * awaited have been logged.
5856
+ */
5857
+ async function captureDocumentLoaderLogs(fn) {
5858
+ const records = [];
5859
+ await configure({
5860
+ sinks: { buffer: (record) => records.push(record) },
5861
+ loggers: [{
5862
+ category: [
5863
+ "fedify",
5864
+ "runtime",
5865
+ "docloader"
5866
+ ],
5867
+ sinks: ["buffer"],
5868
+ lowestLevel: "debug"
5869
+ }, {
5870
+ category: ["logtape", "meta"],
5871
+ sinks: []
5872
+ }],
5873
+ reset: true
5874
+ });
5875
+ try {
5876
+ const result = await fn();
5877
+ await new Promise((resolve) => setTimeout(resolve, 0));
5878
+ return {
5879
+ result,
5880
+ records
5881
+ };
5882
+ } finally {
5883
+ await reset();
5884
+ }
5885
+ }
5886
+ function cancelFailureLogs(records) {
5887
+ return records.filter((record) => record.rawMessage === "Failed to cancel the response body from {url}: {error}").map((record) => [
5888
+ record.level,
5889
+ record.properties.url,
5890
+ String(record.properties.error)
5891
+ ]);
5892
+ }
5893
+ function linkHeaderAlternate(next) {
5894
+ return {
5895
+ headers: {
5896
+ "Content-Type": "text/plain",
5897
+ Link: `<${next}>; rel="alternate"; type="application/activity+json"`
5898
+ },
5899
+ body: "not JSON"
5900
+ };
5901
+ }
5902
+ test("getDocumentLoader() continues if an intermediate body fails to cancel", async (t) => {
5903
+ for (const kind of ["redirect", "Link header alternate"]) await t.step(kind, async () => {
5904
+ const recorder = createHopRecorder((url) => url === "https://a.example/start" ? {
5905
+ ...kind === "redirect" ? {
5906
+ status: 302,
5907
+ headers: { Location: "https://b.example/object" }
5908
+ } : linkHeaderAlternate("https://b.example/object"),
5909
+ failCancel: true
5910
+ } : { body: JSON.stringify(lifecycleDocument) });
5911
+ const loader = createDocumentLoader({}, { openHop: recorder.openHop });
5912
+ const { result, records } = await captureDocumentLoaderLogs(() => loader("https://a.example/start"));
5913
+ deepStrictEqual(result, {
5914
+ contextUrl: null,
5915
+ documentUrl: "https://b.example/object",
5916
+ document: lifecycleDocument
5917
+ });
5918
+ deepStrictEqual(recorder.events, [
5919
+ "open https://a.example/start",
5920
+ "fetch https://a.example/start #1",
5921
+ "cancel #1",
5922
+ "close https://a.example/start",
5923
+ "open https://b.example/object",
5924
+ "fetch https://b.example/object #2",
5925
+ "consumed #2",
5926
+ "close https://b.example/object"
5927
+ ]);
5928
+ recorder.assertInvariants();
5929
+ deepStrictEqual(cancelFailureLogs(records), [[
5930
+ "debug",
5931
+ "https://a.example/start",
5932
+ "Error: Failed to cancel #1"
5933
+ ]]);
5934
+ });
5935
+ });
5936
+ test("getDocumentLoader() does not wait for intermediate bodies to be cancelled", async (t) => {
5937
+ for (const kind of ["redirect", "Link header alternate"]) await t.step(kind, async () => {
5938
+ const recorder = createHopRecorder((url) => url === "https://a.example/start" ? {
5939
+ ...kind === "redirect" ? {
5940
+ status: 302,
5941
+ headers: { Location: "https://b.example/object" }
5942
+ } : linkHeaderAlternate("https://b.example/object"),
5943
+ hangCancel: true
5944
+ } : { body: JSON.stringify(lifecycleDocument) });
5945
+ deepStrictEqual((await settlesWithin(createDocumentLoader({}, { openHop: recorder.openHop })("https://a.example/start"))).documentUrl, "https://b.example/object");
5946
+ deepStrictEqual(recorder.events, [
5947
+ "open https://a.example/start",
5948
+ "fetch https://a.example/start #1",
5949
+ "cancel #1",
5950
+ "close https://a.example/start",
5951
+ "open https://b.example/object",
5952
+ "fetch https://b.example/object #2",
5953
+ "consumed #2",
5954
+ "close https://b.example/object"
5955
+ ]);
5956
+ recorder.assertInvariants();
5957
+ });
5958
+ await t.step("public session with a never-settling cancel", async () => {
5959
+ let cancelled = 0;
5960
+ const pinner = {
5961
+ supports: () => true,
5962
+ fetch: () => Promise.resolve(new Response(new ReadableStream({ cancel() {
5963
+ cancelled++;
5964
+ return new Promise(() => {});
5965
+ } }), { status: 404 })),
5966
+ release() {}
5967
+ };
5968
+ const publicFetch = createPublicFetch({
5969
+ pinner: () => Promise.resolve(pinner),
5970
+ lookup: () => Promise.reject(/* @__PURE__ */ new Error("Unexpected default lookup"))
5971
+ });
5972
+ await settlesWithin(rejects(createDocumentLoader({}, { openHop: (url) => publicFetch.openPublicFetch(url, { lookup: lookupTestAddresses }) })("https://a.example/missing"), FetchError));
5973
+ deepStrictEqual(cancelled, 1);
5974
+ });
5975
+ });
5976
+ test("getDocumentLoader() completes with tee'd fetch-mock responses", async (t) => {
5977
+ const url = "https://example.com/teed";
5978
+ function stream(text) {
5979
+ return new ReadableStream({ start(controller) {
5980
+ controller.enqueue(new TextEncoder().encode(text));
5981
+ controller.close();
5982
+ } });
5983
+ }
5984
+ const loaders = {
5985
+ unpinned: createDocumentLoader({}, { openHop: openUnpinnedHop }),
5986
+ "private addresses allowed": getDocumentLoader({ allowPrivateAddress: true })
5987
+ };
5988
+ for (const [name, loader] of Object.entries(loaders)) for (const body of ["string", "stream"]) await t.step(`${name}: ${body} bodies`, async () => {
5989
+ const make = (text) => body === "string" ? text : stream(text);
5990
+ esm_default.mockGlobal();
5991
+ try {
5992
+ esm_default.get(`${url}/object`, () => new Response(make(JSON.stringify(lifecycleDocument)), { headers: { "Content-Type": "application/activity+json" } }));
5993
+ esm_default.get(`${url}/redirect`, () => new Response(make("redirect"), {
5994
+ status: 302,
5995
+ headers: { Location: `${url}/object` }
5996
+ }));
5997
+ esm_default.get(`${url}/alternate`, () => new Response(make("not JSON"), { headers: {
5998
+ "Content-Type": "text/plain",
5999
+ Link: `<${url}/object>; rel="alternate"; type="application/activity+json"`
6000
+ } }));
6001
+ esm_default.get(`${url}/missing`, () => new Response(make("not found"), { status: 404 }));
6002
+ for (const path of ["redirect", "alternate"]) deepStrictEqual((await settlesWithin(loader(`${url}/${path}`))).document, lifecycleDocument);
6003
+ await settlesWithin(rejects(loader(`${url}/missing`), FetchError));
6004
+ deepStrictEqual(esm_default.callHistory.calls().map((call) => call.url), [
6005
+ `${url}/redirect`,
6006
+ `${url}/object`,
6007
+ `${url}/alternate`,
6008
+ `${url}/object`,
6009
+ `${url}/missing`
6010
+ ]);
6011
+ } finally {
6012
+ esm_default.hardReset();
6013
+ }
6014
+ });
6015
+ });
6016
+ test("getDocumentLoader() resolves each hop once", async () => {
6017
+ const lookups = [];
6018
+ const recorder = createHopRecorder((url) => {
6019
+ const responseUrl = `https://${PUBLIC_ADDRESS.address}${new URL(url).pathname}`;
6020
+ if (url === "https://a.example/start") return {
6021
+ status: 302,
6022
+ headers: { Location: "https://b.example/next" },
6023
+ responseUrl
6024
+ };
6025
+ else if (url === "https://b.example/next") return {
6026
+ status: 301,
6027
+ headers: { Location: "/object" },
6028
+ responseUrl
6029
+ };
6030
+ return {
6031
+ body: JSON.stringify(lifecycleDocument),
6032
+ responseUrl
6033
+ };
6034
+ });
6035
+ const pinner = {
6036
+ supports: () => true,
6037
+ fetch: (_grant, request) => Promise.resolve(recorder.respond(request.url)),
6038
+ release() {}
6039
+ };
6040
+ const publicFetch = createPublicFetch({
6041
+ pinner: () => Promise.resolve(pinner),
6042
+ lookup: () => Promise.reject(/* @__PURE__ */ new Error("Unexpected default lookup"))
6043
+ });
6044
+ deepStrictEqual(await createDocumentLoader({}, { openHop: async (url) => recorder.record(url, await publicFetch.openPublicFetch(url, { lookup(hostname) {
6045
+ lookups.push(hostname);
6046
+ return Promise.resolve([PUBLIC_ADDRESS]);
6047
+ } })) })("https://a.example/start"), {
6048
+ contextUrl: null,
6049
+ documentUrl: "https://b.example/object",
6050
+ document: lifecycleDocument
6051
+ });
6052
+ deepStrictEqual(lookups, [
6053
+ "a.example",
6054
+ "b.example",
6055
+ "b.example"
6056
+ ]);
6057
+ deepStrictEqual(recorder.events, [
6058
+ "open https://a.example/start",
6059
+ "fetch https://a.example/start #1",
6060
+ "cancel #1",
6061
+ "close https://a.example/start",
6062
+ "open https://b.example/next",
6063
+ "fetch https://b.example/next #2",
6064
+ "cancel #2",
6065
+ "close https://b.example/next",
6066
+ "open https://b.example/object",
6067
+ "fetch https://b.example/object #3",
6068
+ "consumed #3",
6069
+ "close https://b.example/object"
6070
+ ]);
6071
+ recorder.assertInvariants();
6072
+ });
6073
+ test("getRemoteDocument() cancels the body before loading a Link header alternate", async () => {
6074
+ const recorder = createHopRecorder(() => ({
6075
+ headers: {
6076
+ "Content-Type": "text/html",
6077
+ Link: "<https://b.example/object>; rel=\"alternate\"; type=\"application/activity+json\""
6078
+ },
6079
+ body: "<html></html>"
6080
+ }));
6081
+ const response = recorder.respond("https://a.example/start");
6082
+ const placeholder = {
6083
+ contextUrl: null,
6084
+ document: {},
6085
+ documentUrl: "https://b.example/object"
6086
+ };
6087
+ const alternates = [];
6088
+ ok(await getRemoteDocument("https://a.example/start", response, (url) => {
6089
+ alternates.push(url);
6090
+ deepStrictEqual(recorder.events.at(-1), "cancel #1");
6091
+ ok(response.bodyUsed);
6092
+ return Promise.resolve(placeholder);
6093
+ }) === placeholder);
6094
+ deepStrictEqual(alternates, ["https://b.example/object"]);
6095
+ deepStrictEqual(recorder.events, ["fetch https://a.example/start #1", "cancel #1"]);
6096
+ });
6806
6097
  //#endregion
6807
6098
  export {};