UNPKG

@zimic/fetch

Version:

Next-gen TypeScript-first fetch API client

282 lines (273 loc) 10.7 kB
'use strict'; var http = require('@zimic/http'); var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var FetchResponseError = class extends Error { constructor(request, response) { super(`${request.method} ${request.url} failed with status ${response.status}: ${response.statusText}`); this.request = request; this.response = response; this.name = "FetchResponseError"; } static { __name(this, "FetchResponseError"); } toObject(options) { const includeRequestBody = options?.includeRequestBody ?? false; const includeResponseBody = options?.includeResponseBody ?? false; const partialObject = { name: this.name, message: this.message }; if (!includeRequestBody && !includeResponseBody) { const request = this.convertRequestToObject({ includeBody: false }); const response = this.convertResponseToObject({ includeBody: false }); return { ...partialObject, request, response }; } return Promise.all([ this.convertRequestToObject({ includeBody: includeRequestBody }), this.convertResponseToObject({ includeBody: includeResponseBody }) ]).then(([request, response]) => ({ ...partialObject, request, response })); } convertRequestToObject(options) { const requestObject = { url: this.request.url, path: this.request.path, method: this.request.method, headers: http.HttpHeaders.prototype.toObject.call(this.request.headers), cache: this.request.cache, destination: this.request.destination, credentials: this.request.credentials, integrity: this.request.integrity, keepalive: this.request.keepalive, mode: this.request.mode, redirect: this.request.redirect, referrer: this.request.referrer, referrerPolicy: this.request.referrerPolicy }; if (!options.includeBody) { return requestObject; } const bodyAsTextPromise = this.request.text(); return bodyAsTextPromise.then((bodyAsText) => { requestObject.body = bodyAsText.length > 0 ? bodyAsText : null; return requestObject; }); } convertResponseToObject(options) { const responseObject = { url: this.response.url, type: this.response.type, status: this.response.status, statusText: this.response.statusText, ok: this.response.ok, headers: http.HttpHeaders.prototype.toObject.call(this.response.headers), redirected: this.response.redirected }; if (!options.includeBody) { return responseObject; } const bodyAsTextPromise = this.response.text(); return bodyAsTextPromise.then((bodyAsText) => { responseObject.body = bodyAsText.length > 0 ? bodyAsText : null; return responseObject; }); } }; var FetchResponseError_default = FetchResponseError; // ../zimic-utils/dist/chunk-2D3UJWOA.mjs var __defProp2 = Object.defineProperty; var __name2 = /* @__PURE__ */ __name((target, value) => __defProp2(target, "name", { value, configurable: true }), "__name"); // ../zimic-utils/dist/chunk-4RR2YNYT.mjs var URL_PATH_PARAM_REGEX = /\/:([^/]+)/g; function createRegExpFromURL(url) { URL_PATH_PARAM_REGEX.lastIndex = 0; const urlWithReplacedPathParams = encodeURI(url).replace(/([.()*?+$\\])/g, "\\$1").replace(URL_PATH_PARAM_REGEX, "/(?<$1>[^/]+)").replace(/^(\/)|(\/)$/g, ""); return new RegExp(`^(?:/)?${urlWithReplacedPathParams}(?:/)?$`); } __name(createRegExpFromURL, "createRegExpFromURL"); __name2(createRegExpFromURL, "createRegExpFromURL"); var createRegExpFromURL_default = createRegExpFromURL; // ../zimic-utils/dist/url/excludeURLParams.mjs function excludeURLParams(url) { url.hash = ""; url.search = ""; url.username = ""; url.password = ""; return url; } __name(excludeURLParams, "excludeURLParams"); __name2(excludeURLParams, "excludeURLParams"); var excludeURLParams_default = excludeURLParams; // ../zimic-utils/dist/url/joinURL.mjs function joinURL(...parts) { return parts.map((part, index) => { const isFirstPart = index === 0; const isLastPart = index === parts.length - 1; let partAsString = part.toString(); if (!isFirstPart) { partAsString = partAsString.replace(/^\//, ""); } if (!isLastPart) { partAsString = partAsString.replace(/\/$/, ""); } return partAsString; }).filter((part) => part.length > 0).join("/"); } __name(joinURL, "joinURL"); __name2(joinURL, "joinURL"); var joinURL_default = joinURL; // src/client/FetchClient.ts var FetchClient = class { static { __name(this, "FetchClient"); } fetch; constructor({ onRequest, onResponse, ...defaults }) { this.fetch = this.createFetchFunction(); this.fetch.defaults = { ...defaults, headers: defaults.headers ?? {}, searchParams: defaults.searchParams ?? {} }; this.fetch.loose = this.fetch; this.fetch.Request = this.createRequestClass(this.fetch.defaults); this.fetch.onRequest = onRequest; this.fetch.onResponse = onResponse; } createFetchFunction() { const fetch = /* @__PURE__ */ __name(async (input, init) => { const request = await this.createFetchRequest(input, init); const requestClone = request.clone(); const rawResponse = await globalThis.fetch( // Optimize type checking by narrowing the type of request requestClone ); const response = await this.createFetchResponse(request, rawResponse); return response; }, "fetch"); Object.setPrototypeOf(fetch, this); return fetch; } async createFetchRequest(input, init) { let request = input instanceof Request ? input : new this.fetch.Request(input, init); if (this.fetch.onRequest) { const requestAfterInterceptor = await this.fetch.onRequest( // Optimize type checking by narrowing the type of request request ); if (requestAfterInterceptor !== request) { const isFetchRequest = requestAfterInterceptor instanceof this.fetch.Request; request = isFetchRequest ? requestAfterInterceptor : new this.fetch.Request(requestAfterInterceptor, init); } } return request; } async createFetchResponse(fetchRequest, rawResponse) { let response = this.defineFetchResponseProperties(fetchRequest, rawResponse); if (this.fetch.onResponse) { const responseAfterInterceptor = await this.fetch.onResponse( // Optimize type checking by narrowing the type of response response ); const isFetchResponse = responseAfterInterceptor instanceof Response && "request" in responseAfterInterceptor && responseAfterInterceptor.request instanceof this.fetch.Request; response = isFetchResponse ? responseAfterInterceptor : this.defineFetchResponseProperties(fetchRequest, responseAfterInterceptor); } return response; } defineFetchResponseProperties(fetchRequest, response) { const fetchResponse = response; Object.defineProperty(fetchResponse, "request", { value: fetchRequest, writable: false, enumerable: true, configurable: false }); let responseError; Object.defineProperty(fetchResponse, "error", { get() { if (responseError === void 0) { responseError = fetchResponse.ok ? null : new FetchResponseError_default( fetchRequest, fetchResponse ); } return responseError; }, enumerable: true, configurable: false }); return fetchResponse; } createRequestClass(defaults) { class Request2 extends globalThis.Request { static { __name(this, "Request"); } path; constructor(input, init) { const initWithDefaults = { ...defaults, ...init }; const headersFromDefaults = new http.HttpHeaders(defaults.headers); const headersFromInit = new http.HttpHeaders(init.headers); let url; const baseURL = new URL(initWithDefaults.baseURL); if (input instanceof globalThis.Request) { const request = input; const headersFromRequest = new http.HttpHeaders(input.headers); initWithDefaults.headers = { ...headersFromDefaults.toObject(), ...headersFromRequest.toObject(), ...headersFromInit.toObject() }; super(request, initWithDefaults); url = new URL(input.url); } else { initWithDefaults.headers = { ...headersFromDefaults.toObject(), ...headersFromInit.toObject() }; url = input instanceof URL ? new URL(input) : new URL(joinURL_default(baseURL, input)); const searchParamsFromDefaults = new http.HttpSearchParams(defaults.searchParams); const searchParamsFromInit = new http.HttpSearchParams(initWithDefaults.searchParams); initWithDefaults.searchParams = { ...searchParamsFromDefaults.toObject(), ...searchParamsFromInit.toObject() }; url.search = new http.HttpSearchParams(initWithDefaults.searchParams).toString(); super(url, initWithDefaults); } const baseURLWithoutTrailingSlash = baseURL.toString().replace(/\/$/, ""); this.path = excludeURLParams_default(url).toString().replace(baseURLWithoutTrailingSlash, ""); } clone() { const rawClone = super.clone(); return new Request2( rawClone, rawClone ); } } return Request2; } isRequest(request, method, path) { return request instanceof Request && request.method === method && "path" in request && typeof request.path === "string" && createRegExpFromURL_default(path).test(request.path); } isResponse(response, method, path) { return response instanceof Response && "request" in response && this.isRequest(response.request, method, path) && "error" in response && (response.error === null || response.error instanceof FetchResponseError_default); } isResponseError(error, method, path) { return error instanceof FetchResponseError_default && this.isRequest(error.request, method, path) && this.isResponse(error.response, method, path); } }; var FetchClient_default = FetchClient; // src/client/factory.ts function createFetch(options) { const { fetch } = new FetchClient_default(options); return fetch; } __name(createFetch, "createFetch"); var factory_default = createFetch; exports.FetchResponseError = FetchResponseError_default; exports.createFetch = factory_default; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map