@zimic/fetch 1.4.1 → 1.4.3-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,96 +2,7 @@
2
2
 
3
3
  var http = require('@zimic/http');
4
4
 
5
- // src/client/errors/FetchResponseError.ts
6
- var FetchResponseError = class extends Error {
7
- constructor(request, response) {
8
- super(`${request.method} ${request.url} failed with status ${response.status}: ${response.statusText}`);
9
- this.request = request;
10
- this.response = response;
11
- this.name = "FetchResponseError";
12
- }
13
- toObject({
14
- includeRequestBody = false,
15
- includeResponseBody = false
16
- } = {}) {
17
- const partialObject = {
18
- name: this.name,
19
- message: this.message
20
- };
21
- if (!includeRequestBody && !includeResponseBody) {
22
- return {
23
- ...partialObject,
24
- request: this.requestToObject({ includeBody: false }),
25
- response: this.responseToObject({ includeBody: false })
26
- };
27
- }
28
- return Promise.all([
29
- Promise.resolve(this.requestToObject({ includeBody: includeRequestBody })),
30
- Promise.resolve(this.responseToObject({ includeBody: includeResponseBody }))
31
- ]).then(([request, response]) => ({ ...partialObject, request, response }));
32
- }
33
- requestToObject(options) {
34
- const request = this.request;
35
- const requestObject = {
36
- url: request.url,
37
- path: request.path,
38
- method: request.method,
39
- headers: this.convertHeadersToObject(request),
40
- cache: request.cache,
41
- destination: request.destination,
42
- credentials: request.credentials,
43
- integrity: request.integrity,
44
- keepalive: request.keepalive,
45
- mode: request.mode,
46
- redirect: request.redirect,
47
- referrer: request.referrer,
48
- referrerPolicy: request.referrerPolicy
49
- };
50
- if (!options.includeBody) {
51
- return requestObject;
52
- }
53
- return this.withIncludedBodyIfAvailable("request", requestObject);
54
- }
55
- responseToObject(options) {
56
- const response = this.response;
57
- const responseObject = {
58
- url: response.url,
59
- type: response.type,
60
- status: response.status,
61
- statusText: response.statusText,
62
- ok: response.ok,
63
- headers: this.convertHeadersToObject(response),
64
- redirected: response.redirected
65
- };
66
- if (!options.includeBody) {
67
- return responseObject;
68
- }
69
- return this.withIncludedBodyIfAvailable("response", responseObject);
70
- }
71
- convertHeadersToObject(resource) {
72
- return http.HttpHeaders.prototype.toObject.call(resource.headers);
73
- }
74
- withIncludedBodyIfAvailable(resourceType, resourceObject) {
75
- const resource = this[resourceType];
76
- if (resource.bodyUsed) {
77
- console.warn(
78
- "[@zimic/fetch]",
79
- `Could not include the ${resourceType} body because it is already used. If you access the body before calling \`error.toObject()\`, consider reading it from a cloned ${resourceType}.
80
-
81
- Learn more: https://zimic.dev/docs/fetch/api/fetch-response-error#errortoobject`
82
- );
83
- return resourceObject;
84
- }
85
- return http.parseHttpBody(resource).then((body) => {
86
- resourceObject.body = body;
87
- return resourceObject;
88
- }).catch((error) => {
89
- console.error("[@zimic/fetch]", `Failed to parse ${resourceType} body:`, error);
90
- return resourceObject;
91
- });
92
- }
93
- };
94
- var FetchResponseError_default = FetchResponseError;
5
+ // src/client/request/FetchRequest.ts
95
6
 
96
7
  // ../zimic-utils/dist/url.mjs
97
8
  function createPathCharactersToEscapeRegex() {
@@ -187,141 +98,342 @@ function joinURL(...parts) {
187
98
  }).filter((part) => part.length > 0).join("/");
188
99
  }
189
100
  var joinURL_default = joinURL;
101
+ var BODY_METHOD = ["json", "formData", "text", "arrayBuffer", "blob", "bytes"];
102
+ function isBodyMethod(property, value) {
103
+ return BODY_METHOD.includes(property) && typeof value === "function";
104
+ }
105
+ function getOrSetBoundBodyMethod(resource, property, value) {
106
+ const isValueAlreadyBound = Object.prototype.hasOwnProperty.call(resource, property);
107
+ if (isValueAlreadyBound) {
108
+ return value;
109
+ }
110
+ const boundValue = value.bind(resource);
111
+ Object.defineProperty(resource, property, {
112
+ value: boundValue,
113
+ configurable: true,
114
+ enumerable: false,
115
+ writable: true
116
+ });
117
+ return boundValue;
118
+ }
119
+ function withIncludedBodyIfAvailable(resource, resourceObject) {
120
+ const resourceType = resource instanceof Request ? "request" : "response";
121
+ if (resource.bodyUsed) {
122
+ console.warn(
123
+ "[@zimic/fetch]",
124
+ `Could not include the ${resourceType} body because it is already used. If you access the body before calling \`toObject()\`, consider reading it from a cloned ${resourceType}.
125
+
126
+ Learn more: https://zimic.dev/docs/fetch/api/fetch-response-error#errortoobject`
127
+ );
128
+ return resourceObject;
129
+ }
130
+ return http.parseHttpBody(resource.clone()).then((body) => {
131
+ resourceObject.body = body;
132
+ return resourceObject;
133
+ }).catch((error) => {
134
+ console.error("[@zimic/fetch]", `Failed to parse ${resourceType} body:`, error);
135
+ return resourceObject;
136
+ });
137
+ }
138
+
139
+ // src/client/request/FetchRequest.ts
140
+ var FETCH_REQUEST_BRAND = Symbol.for("FetchRequest");
141
+ var FETCH_REQUEST_EXTRA_PROPERTIES = [FETCH_REQUEST_BRAND, "raw", "path", "toObject"];
142
+ function createFetchRequestClass() {
143
+ const FetchRequestClass = function FetchRequest2(fetch, input, init) {
144
+ let actualInput;
145
+ const actualInit = {
146
+ baseURL: init?.baseURL ?? fetch.baseURL,
147
+ method: init?.method ?? fetch.method,
148
+ headers: new http.HttpHeaders(fetch.headers),
149
+ searchParams: new http.HttpSearchParams(fetch.searchParams),
150
+ body: init?.body ?? fetch.body,
151
+ mode: init?.mode ?? fetch.mode,
152
+ cache: init?.cache ?? fetch.cache,
153
+ credentials: init?.credentials ?? fetch.credentials,
154
+ integrity: init?.integrity ?? fetch.integrity,
155
+ keepalive: init?.keepalive ?? fetch.keepalive,
156
+ priority: init?.priority ?? fetch.priority,
157
+ redirect: init?.redirect ?? fetch.redirect,
158
+ referrer: init?.referrer ?? fetch.referrer,
159
+ referrerPolicy: init?.referrerPolicy ?? fetch.referrerPolicy,
160
+ signal: init?.signal ?? fetch.signal,
161
+ window: init?.window === void 0 ? fetch.window : init.window,
162
+ duplex: init?.duplex ?? fetch.duplex
163
+ };
164
+ if (init?.headers !== void 0) {
165
+ actualInit.headers.assign(new http.HttpHeaders(init.headers));
166
+ }
167
+ let url;
168
+ const baseURL = new URL(actualInit.baseURL);
169
+ if (input instanceof Request) {
170
+ const request2 = input;
171
+ actualInit.headers.assign(new http.HttpHeaders(request2.headers));
172
+ url = new URL(input.url);
173
+ actualInput = request2 instanceof FetchRequestClass ? request2.raw : request2;
174
+ } else {
175
+ url = new URL(input instanceof URL ? input : joinURL_default(baseURL, input));
176
+ actualInit.searchParams.assign(
177
+ new http.HttpSearchParams(url.searchParams)
178
+ );
179
+ if (init?.searchParams !== void 0) {
180
+ actualInit.searchParams.assign(new http.HttpSearchParams(init.searchParams));
181
+ }
182
+ url.search = actualInit.searchParams.toString();
183
+ actualInput = url;
184
+ }
185
+ const request = new Request(actualInput, actualInit);
186
+ const baseURLWithoutTrailingSlash = baseURL.toString().replace(/\/$/, "");
187
+ const path = excludeNonPathParams_default(url).toString().replace(baseURLWithoutTrailingSlash, "");
188
+ function clone() {
189
+ return new FetchRequestClass(fetch, request.clone());
190
+ }
191
+ function toObject(options) {
192
+ const requestObject = {
193
+ url: request.url,
194
+ path,
195
+ method: request.method,
196
+ headers: http.HttpHeaders.prototype.toObject.call(request.headers),
197
+ cache: request.cache,
198
+ destination: request.destination,
199
+ credentials: request.credentials,
200
+ integrity: request.integrity,
201
+ keepalive: request.keepalive,
202
+ mode: request.mode,
203
+ redirect: request.redirect,
204
+ referrer: request.referrer,
205
+ referrerPolicy: request.referrerPolicy
206
+ };
207
+ if (!options?.includeBody) {
208
+ return requestObject;
209
+ }
210
+ return withIncludedBodyIfAvailable(request, requestObject);
211
+ }
212
+ const fetchRequest = new Proxy(request, {
213
+ get(target, property) {
214
+ if (property === FETCH_REQUEST_BRAND) {
215
+ return true;
216
+ }
217
+ if (property === "raw") {
218
+ return request;
219
+ }
220
+ if (property === "path") {
221
+ return path;
222
+ }
223
+ if (property === "clone") {
224
+ return clone;
225
+ }
226
+ if (property === "toObject") {
227
+ return toObject;
228
+ }
229
+ const value = Reflect.get(target, property, target);
230
+ if (isBodyMethod(property, value)) {
231
+ return getOrSetBoundBodyMethod(request, property, value);
232
+ }
233
+ return value;
234
+ },
235
+ has(target, property) {
236
+ return FETCH_REQUEST_EXTRA_PROPERTIES.includes(property) || Reflect.has(target, property);
237
+ }
238
+ });
239
+ return fetchRequest;
240
+ };
241
+ Object.defineProperty(FetchRequestClass, Symbol.hasInstance, {
242
+ value(instance) {
243
+ return instance instanceof Request && FETCH_REQUEST_BRAND in instance && instance[FETCH_REQUEST_BRAND] === true;
244
+ },
245
+ writable: false,
246
+ enumerable: false,
247
+ configurable: false
248
+ });
249
+ Object.setPrototypeOf(FetchRequestClass.prototype, Request.prototype);
250
+ return FetchRequestClass;
251
+ }
252
+ var FetchRequest = createFetchRequestClass();
253
+
254
+ // src/client/response/error/FetchResponseError.ts
255
+ var FetchResponseError = class extends Error {
256
+ constructor(request, response) {
257
+ super(`${request.method} ${request.url} failed with status ${response.status}: ${response.statusText}`);
258
+ this.request = request;
259
+ this.response = response;
260
+ this.name = "FetchResponseError";
261
+ }
262
+ toObject(options) {
263
+ const partialObject = {
264
+ name: this.name,
265
+ message: this.message
266
+ };
267
+ if (!options?.includeRequestBody && !options?.includeResponseBody) {
268
+ return {
269
+ ...partialObject,
270
+ request: this.request.toObject({ includeBody: false }),
271
+ response: this.response.toObject({ includeBody: false })
272
+ };
273
+ }
274
+ return Promise.all([
275
+ Promise.resolve(this.request.toObject({ includeBody: options.includeRequestBody })),
276
+ Promise.resolve(this.response.toObject({ includeBody: options.includeResponseBody }))
277
+ ]).then(([request, response]) => ({ ...partialObject, request, response }));
278
+ }
279
+ };
280
+ var FetchResponseError_default = FetchResponseError;
281
+
282
+ // src/client/response/FetchResponse.ts
283
+ var FETCH_RESPONSE_BRAND = Symbol.for("FetchResponse");
284
+ var FETCH_RESPONSE_EXTRA_PROPERTIES = [FETCH_RESPONSE_BRAND, "raw", "request", "error", "toObject"];
285
+ function createFetchResponseClass() {
286
+ const FetchResponseClass = function FetchResponse2(fetchRequest, responseOrBody, init) {
287
+ const response = responseOrBody instanceof Response ? responseOrBody : new Response(responseOrBody, init);
288
+ let error = null;
289
+ function clone() {
290
+ return new FetchResponseClass(fetchRequest, response.clone());
291
+ }
292
+ function toObject(options) {
293
+ const responseObject = {
294
+ url: response.url,
295
+ type: response.type,
296
+ status: response.status,
297
+ statusText: response.statusText,
298
+ ok: response.ok,
299
+ headers: http.HttpHeaders.prototype.toObject.call(response.headers),
300
+ redirected: response.redirected
301
+ };
302
+ if (!options?.includeBody) {
303
+ return responseObject;
304
+ }
305
+ return withIncludedBodyIfAvailable(response, responseObject);
306
+ }
307
+ const fetchResponse = new Proxy(response, {
308
+ get(target, property, receiver) {
309
+ if (property === FETCH_RESPONSE_BRAND) {
310
+ return true;
311
+ }
312
+ if (property === "raw") {
313
+ return response;
314
+ }
315
+ if (property === "request") {
316
+ return fetchRequest;
317
+ }
318
+ if (property === "error") {
319
+ error ??= new FetchResponseError_default(fetchRequest, receiver);
320
+ return error;
321
+ }
322
+ if (property === "clone") {
323
+ return clone;
324
+ }
325
+ if (property === "toObject") {
326
+ return toObject;
327
+ }
328
+ const value = Reflect.get(target, property, target);
329
+ if (isBodyMethod(property, value)) {
330
+ return getOrSetBoundBodyMethod(response, property, value);
331
+ }
332
+ return value;
333
+ },
334
+ has(target, property) {
335
+ return FETCH_RESPONSE_EXTRA_PROPERTIES.includes(property) || Reflect.has(target, property);
336
+ }
337
+ });
338
+ return fetchResponse;
339
+ };
340
+ Object.defineProperty(FetchResponseClass, Symbol.hasInstance, {
341
+ value(instance) {
342
+ return instance instanceof Response && FETCH_RESPONSE_BRAND in instance && instance[FETCH_RESPONSE_BRAND] === true;
343
+ },
344
+ writable: false,
345
+ enumerable: false,
346
+ configurable: false
347
+ });
348
+ Object.setPrototypeOf(FetchResponseClass.prototype, Response.prototype);
349
+ return FetchResponseClass;
350
+ }
351
+ var FetchResponse = createFetchResponseClass();
352
+ Object.setPrototypeOf(FetchResponse.prototype, Response.prototype);
190
353
 
191
354
  // src/client/FetchClient.ts
355
+ var FETCH_OPTIONS_DEFAULT_PROPERTIES = [
356
+ "baseURL",
357
+ "onRequest",
358
+ "onResponse",
359
+ "body",
360
+ "cache",
361
+ "credentials",
362
+ "integrity",
363
+ "keepalive",
364
+ "mode",
365
+ "priority",
366
+ "redirect",
367
+ "referrer",
368
+ "referrerPolicy",
369
+ "signal",
370
+ "window",
371
+ "duplex"
372
+ ];
192
373
  var FetchClient = class {
193
374
  fetch;
194
- constructor({ headers = {}, searchParams = {}, ...otherOptions }) {
375
+ constructor(options) {
195
376
  this.fetch = this.createFetchFunction();
196
- this.fetch.headers = headers;
197
- this.fetch.searchParams = searchParams;
198
- Object.assign(this.fetch, otherOptions);
377
+ this.assignDefaults(this.fetch, options);
199
378
  this.fetch.loose = this.fetch;
200
- this.fetch.Request = this.createRequestClass(this.fetch);
379
+ this.fetch.Request = this.createFetchRequestConstructor(this.fetch);
380
+ }
381
+ assignDefaults(fetch, { headers = {}, searchParams = {}, ...otherOptions }) {
382
+ fetch.headers = headers;
383
+ fetch.searchParams = searchParams;
384
+ for (const property of FETCH_OPTIONS_DEFAULT_PROPERTIES) {
385
+ const propertyValue = otherOptions[property];
386
+ if (propertyValue !== void 0) {
387
+ fetch[property] = propertyValue;
388
+ }
389
+ }
201
390
  }
202
391
  get defaults() {
203
392
  return this.fetch;
204
393
  }
205
394
  createFetchFunction() {
206
395
  const fetch = async (input, init) => {
207
- const request = await this.createFetchRequest(input, init);
208
- const requestClone = request.clone();
209
- const rawResponse = await globalThis.fetch(
210
- // Optimize type checking by narrowing the type of request
211
- requestClone
212
- );
213
- const response = await this.createFetchResponse(request, rawResponse);
214
- return response;
396
+ const fetchRequest = await this.createFetchRequest(input, init);
397
+ const response = await globalThis.fetch(fetchRequest.raw.clone());
398
+ const fetchResponse = await this.createFetchResponse(fetchRequest, response);
399
+ return fetchResponse;
215
400
  };
216
- Object.setPrototypeOf(fetch, this);
217
- return fetch;
401
+ return Object.setPrototypeOf(fetch, this);
402
+ }
403
+ createFetchRequestConstructor(fetch) {
404
+ function Request2(input, init) {
405
+ return new FetchRequest(fetch, input, init);
406
+ }
407
+ Object.setPrototypeOf(Request2.prototype, FetchRequest.prototype);
408
+ return Request2;
218
409
  }
219
410
  async createFetchRequest(input, init) {
220
- let request = input instanceof Request ? input : new this.fetch.Request(input, init);
411
+ let fetchRequest = new this.fetch.Request(input, init);
221
412
  if (this.fetch.onRequest) {
222
- const requestAfterInterceptor = await this.fetch.onRequest(
223
- // Optimize type checking by narrowing the type of request
224
- request
225
- );
226
- if (requestAfterInterceptor !== request) {
227
- const isFetchRequest = requestAfterInterceptor instanceof this.fetch.Request;
228
- request = isFetchRequest ? requestAfterInterceptor : new this.fetch.Request(requestAfterInterceptor, init);
413
+ const newRequest = await this.fetch.onRequest(fetchRequest);
414
+ if (newRequest !== fetchRequest) {
415
+ if (newRequest instanceof FetchRequest) {
416
+ fetchRequest = newRequest;
417
+ } else {
418
+ fetchRequest = new this.fetch.Request(newRequest, init);
419
+ }
229
420
  }
230
421
  }
231
- return request;
422
+ return fetchRequest;
232
423
  }
233
- async createFetchResponse(fetchRequest, rawResponse) {
234
- let response = this.defineFetchResponseProperties(fetchRequest, rawResponse);
424
+ async createFetchResponse(fetchRequest, response) {
425
+ let fetchResponse = new FetchResponse(fetchRequest, response);
235
426
  if (this.fetch.onResponse) {
236
- const responseAfterInterceptor = await this.fetch.onResponse(
237
- // Optimize type checking by narrowing the type of response
238
- response
239
- );
240
- const isFetchResponse = responseAfterInterceptor instanceof Response && "request" in responseAfterInterceptor && responseAfterInterceptor.request instanceof this.fetch.Request;
241
- response = isFetchResponse ? responseAfterInterceptor : this.defineFetchResponseProperties(fetchRequest, responseAfterInterceptor);
427
+ const newResponse = await this.fetch.onResponse(fetchResponse);
428
+ fetchResponse = newResponse instanceof FetchResponse ? newResponse : new FetchResponse(fetchRequest, newResponse);
242
429
  }
243
- return response;
244
- }
245
- defineFetchResponseProperties(fetchRequest, response) {
246
- const fetchResponse = response;
247
- Object.defineProperty(fetchResponse, "request", {
248
- value: fetchRequest,
249
- writable: false,
250
- enumerable: true,
251
- configurable: false
252
- });
253
- let responseError;
254
- Object.defineProperty(fetchResponse, "error", {
255
- get() {
256
- responseError ??= new FetchResponseError_default(fetchRequest, fetchResponse);
257
- return responseError;
258
- },
259
- enumerable: true,
260
- configurable: false
261
- });
262
430
  return fetchResponse;
263
431
  }
264
- createRequestClass(fetch) {
265
- class Request2 extends globalThis.Request {
266
- path;
267
- constructor(input, init) {
268
- let actualInput;
269
- const actualInit = {
270
- baseURL: init?.baseURL ?? fetch.baseURL,
271
- method: init?.method ?? fetch.method,
272
- headers: new http.HttpHeaders(fetch.headers),
273
- searchParams: new http.HttpSearchParams(fetch.searchParams),
274
- body: init?.body ?? fetch.body,
275
- mode: init?.mode ?? fetch.mode,
276
- cache: init?.cache ?? fetch.cache,
277
- credentials: init?.credentials ?? fetch.credentials,
278
- integrity: init?.integrity ?? fetch.integrity,
279
- keepalive: init?.keepalive ?? fetch.keepalive,
280
- priority: init?.priority ?? fetch.priority,
281
- redirect: init?.redirect ?? fetch.redirect,
282
- referrer: init?.referrer ?? fetch.referrer,
283
- referrerPolicy: init?.referrerPolicy ?? fetch.referrerPolicy,
284
- signal: init?.signal ?? fetch.signal,
285
- window: init?.window === void 0 ? fetch.window : init.window,
286
- duplex: init?.duplex ?? fetch.duplex
287
- };
288
- if (init?.headers) {
289
- actualInit.headers.assign(new http.HttpHeaders(init.headers));
290
- }
291
- let url;
292
- const baseURL = new URL(actualInit.baseURL);
293
- if (input instanceof globalThis.Request) {
294
- const request = input;
295
- actualInit.headers.assign(new http.HttpHeaders(request.headers));
296
- url = new URL(input.url);
297
- actualInput = request;
298
- } else {
299
- url = new URL(input instanceof URL ? input : joinURL_default(baseURL, input));
300
- actualInit.searchParams.assign(
301
- new http.HttpSearchParams(url.searchParams)
302
- );
303
- if (init?.searchParams) {
304
- actualInit.searchParams.assign(new http.HttpSearchParams(init.searchParams));
305
- }
306
- url.search = actualInit.searchParams.toString();
307
- actualInput = url;
308
- }
309
- super(actualInput, actualInit);
310
- const baseURLWithoutTrailingSlash = baseURL.toString().replace(/\/$/, "");
311
- this.path = excludeNonPathParams_default(url).toString().replace(baseURLWithoutTrailingSlash, "");
312
- }
313
- clone() {
314
- const rawClone = super.clone();
315
- return new Request2(rawClone);
316
- }
317
- }
318
- return Request2;
319
- }
320
432
  isRequest(request, method, path) {
321
- return request instanceof Request && request.method === method && "path" in request && typeof request.path === "string" && createRegexFromPath_default(path).test(request.path);
433
+ return request instanceof FetchRequest && request.method === method && typeof request.path === "string" && createRegexFromPath_default(path).test(request.path);
322
434
  }
323
435
  isResponse(response, method, path) {
324
- return response instanceof Response && "request" in response && this.isRequest(response.request, method, path) && "error" in response && (response.error === null || response.error instanceof FetchResponseError_default);
436
+ return response instanceof FetchResponse && this.isRequest(response.request, method, path);
325
437
  }
326
438
  isResponseError(error, method, path) {
327
439
  return error instanceof FetchResponseError_default && this.isRequest(error.request, method, path) && this.isResponse(error.response, method, path);
@@ -336,6 +448,8 @@ function createFetch(options) {
336
448
  }
337
449
  var factory_default = createFetch;
338
450
 
451
+ exports.FetchRequest = FetchRequest;
452
+ exports.FetchResponse = FetchResponse;
339
453
  exports.FetchResponseError = FetchResponseError_default;
340
454
  exports.createFetch = factory_default;
341
455
  //# sourceMappingURL=index.js.map