UNPKG

@zayne-labs/callapi

Version:

A lightweight wrapper over fetch with quality of life improvements like built-in request cancellation, retries, interceptors and more

799 lines (790 loc) 29.4 kB
import { D as handleConfigValidation, E as getCurrentRouteSchemaKeyAndMainInitURL, F as isArray, I as isBoolean, L as isFunction, O as handleSchemaValidation, R as isReadableStream, T as getFullAndNormalizedURL, a as getBody, c as getMethod, d as splitBaseConfig, f as splitConfig, h as isHTTPErrorInstance, i as createTimeoutSignal, k as HTTPError, l as getResolvedHeaders, o as getFetchImpl, p as waitFor, r as createCombinedSignal, s as getHeaders, t as extraOptionDefaults, u as omitKeys, v as isValidationErrorInstance, z as isString } from "./defaults-CjVryN9a.js"; //#region src/result.ts const getResponseType = (response, parser) => ({ arrayBuffer: () => response.arrayBuffer(), blob: () => response.blob(), formData: () => response.formData(), json: async () => { return parser(await response.text()); }, stream: () => response.body, text: () => response.text() }); const textTypes = new Set([ "image/svg", "application/xml", "application/xhtml", "application/html" ]); const JSON_REGEX = /^application\/(?:[\w!#$%&*.^`~-]*\+)?json(;.+)?$/i; const detectResponseType = (response) => { const initContentType = response.headers.get("content-type"); if (!initContentType) return extraOptionDefaults.responseType; const contentType = initContentType.split(";")[0] ?? ""; if (JSON_REGEX.test(contentType)) return "json"; if (textTypes.has(contentType) || contentType.startsWith("text/")) return "text"; return "blob"; }; const resolveResponseData = (response, responseType, parser) => { const selectedParser = parser ?? extraOptionDefaults.responseParser; const selectedResponseType = responseType ?? detectResponseType(response); const RESPONSE_TYPE_LOOKUP = getResponseType(response, selectedParser); if (!Object.hasOwn(RESPONSE_TYPE_LOOKUP, selectedResponseType)) throw new Error(`Invalid response type: ${responseType}`); return RESPONSE_TYPE_LOOKUP[selectedResponseType](); }; const getResultModeMap = (details) => { return { all: () => details, onlyData: () => details.data, onlyResponse: () => details.response, withoutResponse: () => omitKeys(details, ["response"]) }; }; const resolveSuccessResult = (data, info) => { const { response, resultMode } = info; return getResultModeMap({ data, error: null, response })[resultMode ?? "all"](); }; const resolveErrorResult = (error, info) => { const { cloneResponse, message: customErrorMessage, resultMode } = info; let errorDetails = { data: null, error: { errorData: false, message: customErrorMessage ?? error.message, name: error.name, originalError: error }, response: null }; if (isValidationErrorInstance(error)) { const { errorData, message, response } = error; errorDetails = { data: null, error: { errorData, issueCause: error.issueCause, message, name: "ValidationError", originalError: error }, response }; } if (isHTTPErrorInstance(error)) { const { errorData, message, name, response } = error; errorDetails = { data: null, error: { errorData, message, name, originalError: error }, response: cloneResponse ? response.clone() : response }; } const errorResult = getResultModeMap(errorDetails)[resultMode ?? "all"](); return { errorDetails, errorResult }; }; const getCustomizedErrorResult = (errorResult, customErrorInfo) => { if (!errorResult) return null; const { message = errorResult.error.message } = customErrorInfo; return { ...errorResult, error: { ...errorResult.error, message } }; }; //#endregion //#region src/hooks.ts const getHookRegistriesAndKeys = () => { const hookRegistries = { onError: /* @__PURE__ */ new Set(), onRequest: /* @__PURE__ */ new Set(), onRequestError: /* @__PURE__ */ new Set(), onRequestReady: /* @__PURE__ */ new Set(), onRequestStream: /* @__PURE__ */ new Set(), onResponse: /* @__PURE__ */ new Set(), onResponseError: /* @__PURE__ */ new Set(), onResponseStream: /* @__PURE__ */ new Set(), onRetry: /* @__PURE__ */ new Set(), onSuccess: /* @__PURE__ */ new Set(), onValidationError: /* @__PURE__ */ new Set() }; return { hookRegistries, hookRegistryKeys: Object.keys(hookRegistries) }; }; const composeHooksFromArray = (hooksArray, hooksExecutionMode) => { const composedHook = async (ctx) => { switch (hooksExecutionMode) { case "parallel": await Promise.all(hooksArray.map((uniqueHook) => uniqueHook?.(ctx))); break; case "sequential": for (const hook of hooksArray) await hook?.(ctx); break; default: } }; return composedHook; }; const executeHooks = async (...hookResultsOrPromise) => { await Promise.all(hookResultsOrPromise); }; const executeHooksInCatchBlock = async (hookResultsOrPromise, hookInfo) => { const { errorInfo, shouldThrowOnError } = hookInfo; try { await Promise.all(hookResultsOrPromise); return null; } catch (hookError) { if (shouldThrowOnError) throw hookError; const { errorResult } = resolveErrorResult(hookError, errorInfo); return errorResult; } }; //#endregion //#region src/stream.ts const createProgressEvent = (options) => { const { chunk, totalBytes, transferredBytes } = options; return { chunk, progress: Math.round(transferredBytes / totalBytes * 100) || 0, totalBytes, transferredBytes }; }; const calculateTotalBytesFromBody = async (requestBody, existingTotalBytes) => { let totalBytes = existingTotalBytes; if (!requestBody) return totalBytes; for await (const chunk of requestBody) totalBytes += chunk.byteLength; return totalBytes; }; const toStreamableRequest = async (context) => { const { baseConfig, config, options, request } = context; if (!options.onRequestStream || !isReadableStream(request.body)) return request; const requestInstance = new Request(options.fullURL, { ...request, duplex: "half" }); const contentLength = requestInstance.headers.get("content-length"); let totalBytes = Number(contentLength ?? 0); if (!contentLength && options.forcefullyCalculateRequestStreamSize) totalBytes = await calculateTotalBytesFromBody(requestInstance.clone().body, totalBytes); let transferredBytes = 0; const stream = new ReadableStream({ start: async (controller) => { const body = requestInstance.body; if (!body) return; const requestStreamContext = { baseConfig, config, event: createProgressEvent({ chunk: new Uint8Array(), totalBytes, transferredBytes }), options, request, requestInstance }; await executeHooks(options.onRequestStream?.(requestStreamContext)); for await (const chunk of body) { transferredBytes += chunk.byteLength; totalBytes = Math.max(totalBytes, transferredBytes); await executeHooks(options.onRequestStream?.({ ...requestStreamContext, event: createProgressEvent({ chunk, totalBytes, transferredBytes }) })); controller.enqueue(chunk); } controller.close(); } }); return new Request(requestInstance, { body: stream, duplex: "half" }); }; const toStreamableResponse = (context) => { const { baseConfig, config, options, request, response } = context; if (!options.onResponseStream || !response.body) return response; const contentLength = response.headers.get("content-length"); let totalBytes = Number(contentLength ?? 0); let transferredBytes = 0; const stream = new ReadableStream({ start: async (controller) => { const body = response.body; if (!body) return; const responseStreamContext = { baseConfig, config, event: createProgressEvent({ chunk: new Uint8Array(), totalBytes, transferredBytes }), options, request, response }; await executeHooks(options.onResponseStream?.(responseStreamContext)); for await (const chunk of body) { transferredBytes += chunk.byteLength; totalBytes = Math.max(totalBytes, transferredBytes); await executeHooks(options.onResponseStream?.({ ...responseStreamContext, event: createProgressEvent({ chunk, totalBytes, transferredBytes }) })); controller.enqueue(chunk); } controller.close(); } }); return new Response(stream, response); }; //#endregion //#region src/dedupe.ts const createDedupeStrategy = async (context) => { const { $GlobalRequestInfoCache: $GlobalRequestInfoCache$1, $LocalRequestInfoCache, baseConfig, config, newFetchController, options: globalOptions } = context; const dedupeStrategy = globalOptions.dedupeStrategy ?? extraOptionDefaults.dedupeStrategy; const resolvedDedupeStrategy = isFunction(dedupeStrategy) ? dedupeStrategy(context) : dedupeStrategy; const getDedupeKey = () => { if (!(resolvedDedupeStrategy === "cancel" || resolvedDedupeStrategy === "defer")) return null; const dedupeKey$1 = globalOptions.dedupeKey ?? extraOptionDefaults.dedupeKey; return isFunction(dedupeKey$1) ? dedupeKey$1(context) : dedupeKey$1; }; const getDedupeCacheScopeKey = () => { const dedupeCacheScopeKey = globalOptions.dedupeCacheScopeKey ?? extraOptionDefaults.dedupeCacheScopeKey; return isFunction(dedupeCacheScopeKey) ? dedupeCacheScopeKey(context) : dedupeCacheScopeKey; }; const dedupeKey = getDedupeKey(); const getRequestInfoCache = () => { if (!dedupeKey) return; const dedupeCacheScope = globalOptions.dedupeCacheScope ?? extraOptionDefaults.dedupeCacheScope; const dedupeCacheScopeKey = getDedupeCacheScopeKey(); if (dedupeCacheScope === "global" && !$GlobalRequestInfoCache$1.has(dedupeCacheScopeKey)) $GlobalRequestInfoCache$1.set(dedupeCacheScopeKey, /* @__PURE__ */ new Map()); const $RequestInfoCache$1 = dedupeCacheScope === "global" ? $GlobalRequestInfoCache$1.get(dedupeCacheScopeKey) : $LocalRequestInfoCache; return { delete: () => $RequestInfoCache$1?.delete(dedupeKey), get: () => $RequestInfoCache$1?.get(dedupeKey), set: (value) => $RequestInfoCache$1?.set(dedupeKey, value) }; }; const $RequestInfoCache = getRequestInfoCache(); /** * Force sequential execution of parallel requests to enable proper cache-based deduplication. * * Problem: When Promise.all([callApi(url), callApi(url)]) executes, both requests * start synchronously and reach this point before either can populate the cache. * * Why `await Promise.resolve()` fails: * - All microtasks in a batch resolve together at the next microtask checkpoint * - Both requests resume execution simultaneously after the await * - Both check `prevRequestInfo` at the same time → both see empty cache * - Both proceed to populate cache → deduplication fails * * Why `wait new Promise(()=> setTimeout(resolve, number))` works: * - Each setTimeout creates a separate task in the task queue * - Tasks execute sequentially, not simultaneously * - Request 1's task runs first: checks cache (empty) → continues → populates cache * - Request 2's task runs after: checks cache (populated) → uses cached promise * - Deduplication succeeds * * IMPORTANT: The delay must be non-zero. setTimeout(fn, 0) fails because JavaScript engines * may optimize zero-delay timers by batching them together, causing all requests to resume * simultaneously (same problem as microtasks). Any non-zero value (even 0.0000000001) forces * proper sequential task queue scheduling, ensuring each request gets its own task slot. */ if (dedupeKey !== null) await waitFor(.001); const prevRequestInfo = $RequestInfoCache?.get(); const getAbortErrorMessage = () => { if (globalOptions.dedupeKey) return `Duplicate request detected - Aborted previous request with key '${dedupeKey}'`; return `Duplicate request aborted - Aborted previous request to '${globalOptions.fullURL}'`; }; const handleRequestCancelStrategy = () => { if (!(prevRequestInfo && resolvedDedupeStrategy === "cancel")) return; const message = getAbortErrorMessage(); const reason = new DOMException(message, "AbortError"); prevRequestInfo.controller.abort(reason); }; const handleRequestDeferStrategy = async (deferContext) => { const { fetchApi, options: localOptions, request: localRequest } = deferContext; const shouldUsePromiseFromCache = prevRequestInfo && resolvedDedupeStrategy === "defer"; const streamableContext = { baseConfig, config, options: localOptions, request: localRequest }; const streamableRequest = await toStreamableRequest(streamableContext); const responsePromise = shouldUsePromiseFromCache ? prevRequestInfo.responsePromise : fetchApi(localOptions.fullURL, streamableRequest); $RequestInfoCache?.set({ controller: newFetchController, responsePromise }); return toStreamableResponse({ ...streamableContext, response: await responsePromise }); }; const removeDedupeKeyFromCache = () => { $RequestInfoCache?.delete(); }; return { getAbortErrorMessage, handleRequestCancelStrategy, handleRequestDeferStrategy, removeDedupeKeyFromCache, resolvedDedupeStrategy }; }; //#endregion //#region src/middlewares.ts const getMiddlewareRegistriesAndKeys = () => { const middlewareRegistries = { fetchMiddleware: /* @__PURE__ */ new Set() }; return { middlewareRegistries, middlewareRegistryKeys: Object.keys(middlewareRegistries) }; }; const composeMiddlewaresFromArray = (middlewareArray) => { let composedMiddleware; for (const currentMiddleware of middlewareArray) { if (!currentMiddleware) continue; const previousMiddleware = composedMiddleware; if (!previousMiddleware) { composedMiddleware = currentMiddleware; continue; } composedMiddleware = (context) => { const prevFetchImpl = previousMiddleware(context); return currentMiddleware({ ...context, fetchImpl: prevFetchImpl }); }; } return composedMiddleware; }; //#endregion //#region src/plugins.ts const getResolvedPlugins = (context) => { const { baseConfig, options } = context; return isFunction(options.plugins) ? options.plugins({ basePlugins: baseConfig.plugins ?? [] }) : options.plugins ?? []; }; const initializePlugins = async (setupContext) => { const { baseConfig, config, initURL, options, request } = setupContext; const { addMainHooks, addMainMiddlewares, addPluginHooks, addPluginMiddlewares, getResolvedHooks, getResolvedMiddlewares } = setupHooksAndMiddlewares({ baseConfig, config, options }); const initURLResult = getCurrentRouteSchemaKeyAndMainInitURL({ baseExtraOptions: baseConfig, extraOptions: config, initURL }); let resolvedCurrentRouteSchemaKey = initURLResult.currentRouteSchemaKey; let resolvedInitURL = initURLResult.mainInitURL; const resolvedOptions = options; const resolvedRequest = Object.assign(request, { headers: getResolvedHeaders({ baseHeaders: baseConfig.headers, headers: config.headers }), method: getMethod({ initURL: resolvedInitURL, method: request.method }) }); const executePluginSetupFn = async (pluginSetup) => { if (!pluginSetup) return; const initResult = await pluginSetup(setupContext); if (!initResult) return; const urlString = initResult.initURL?.toString(); if (isString(urlString)) { const newURLResult = getCurrentRouteSchemaKeyAndMainInitURL({ baseExtraOptions: baseConfig, extraOptions: config, initURL: urlString }); resolvedCurrentRouteSchemaKey = newURLResult.currentRouteSchemaKey; resolvedInitURL = newURLResult.mainInitURL; } if (initResult.request) Object.assign(resolvedRequest, initResult.request); if (initResult.options) Object.assign(resolvedOptions, initResult.options); }; const resolvedPlugins = getResolvedPlugins({ baseConfig, options }); for (const plugin of resolvedPlugins) { const [, pluginHooks, pluginMiddlewares] = await Promise.all([ executePluginSetupFn(plugin.setup), isFunction(plugin.hooks) ? plugin.hooks(setupContext) : plugin.hooks, isFunction(plugin.middlewares) ? plugin.middlewares(setupContext) : plugin.middlewares ]); pluginHooks && addPluginHooks(pluginHooks); pluginMiddlewares && addPluginMiddlewares(pluginMiddlewares); } addMainHooks(); addMainMiddlewares(); const resolvedHooks = getResolvedHooks(); const resolvedMiddlewares = getResolvedMiddlewares(); return { resolvedCurrentRouteSchemaKey, resolvedHooks, resolvedInitURL, resolvedMiddlewares, resolvedOptions, resolvedRequest }; }; const setupHooksAndMiddlewares = (context) => { const { baseConfig, config, options } = context; const { hookRegistries, hookRegistryKeys } = getHookRegistriesAndKeys(); const { middlewareRegistries, middlewareRegistryKeys } = getMiddlewareRegistriesAndKeys(); const addMainHooks = () => { for (const hookName of hookRegistryKeys) { const overriddenHook = options[hookName]; const baseHook = baseConfig[hookName]; const instanceHook = config[hookName]; const mainHook = isArray(baseHook) && instanceHook ? [baseHook, instanceHook].flat() : overriddenHook; mainHook && hookRegistries[hookName].add(mainHook); } }; const addPluginHooks = (pluginHooks) => { for (const hookName of hookRegistryKeys) { const pluginHook = pluginHooks[hookName]; pluginHook && hookRegistries[hookName].add(pluginHook); } }; const addMainMiddlewares = () => { for (const middlewareName of middlewareRegistryKeys) { const baseMiddleware = baseConfig[middlewareName]; const instanceMiddleware = config[middlewareName]; baseMiddleware && middlewareRegistries[middlewareName].add(baseMiddleware); instanceMiddleware && middlewareRegistries[middlewareName].add(instanceMiddleware); } }; const addPluginMiddlewares = (pluginMiddlewares) => { for (const middlewareName of middlewareRegistryKeys) { const pluginMiddleware = pluginMiddlewares[middlewareName]; if (!pluginMiddleware) continue; middlewareRegistries[middlewareName].add(pluginMiddleware); } }; const getResolvedHooks = () => { const resolvedHooks = {}; for (const [hookName, hookRegistry] of Object.entries(hookRegistries)) { if (hookRegistry.size === 0) continue; const flattenedHookArray = [...hookRegistry].flat(); if (flattenedHookArray.length === 0) continue; resolvedHooks[hookName] = composeHooksFromArray(flattenedHookArray, options.hooksExecutionMode ?? extraOptionDefaults.hooksExecutionMode); } return resolvedHooks; }; const getResolvedMiddlewares = () => { const resolvedMiddlewares = {}; for (const [middlewareName, middlewareRegistry] of Object.entries(middlewareRegistries)) { if (middlewareRegistry.size === 0) continue; const middlewareArray = [...middlewareRegistry]; if (middlewareArray.length === 0) continue; resolvedMiddlewares[middlewareName] = composeMiddlewaresFromArray(middlewareArray); } return resolvedMiddlewares; }; return { addMainHooks, addMainMiddlewares, addPluginHooks, addPluginMiddlewares, getResolvedHooks, getResolvedMiddlewares }; }; //#endregion //#region src/retry.ts const getLinearDelay = (currentAttemptCount, options) => { const retryDelay = options.retryDelay ?? extraOptionDefaults.retryDelay; return isFunction(retryDelay) ? retryDelay(currentAttemptCount) : retryDelay; }; const getExponentialDelay = (currentAttemptCount, options) => { const retryDelay = options.retryDelay ?? extraOptionDefaults.retryDelay; const resolvedRetryDelay = isFunction(retryDelay) ? retryDelay(currentAttemptCount) : retryDelay; const maxDelay = options.retryMaxDelay ?? extraOptionDefaults.retryMaxDelay; const exponentialDelay = resolvedRetryDelay * 2 ** currentAttemptCount; return Math.min(exponentialDelay, maxDelay); }; const createRetryManager = (ctx) => { const { callApi: callApi$1, callApiArgs, error, errorContext, errorResult, hookInfo } = ctx; const { options, request } = errorContext; const currentAttemptCount = options["~retryAttemptCount"] ?? 1; const retryStrategy = options.retryStrategy ?? extraOptionDefaults.retryStrategy; const getDelay = () => { switch (retryStrategy) { case "exponential": return getExponentialDelay(currentAttemptCount, options); case "linear": return getLinearDelay(currentAttemptCount, options); default: throw new Error(`Invalid retry strategy: ${String(retryStrategy)}`); } }; const shouldAttemptRetry = async () => { if (isBoolean(request.signal) && request.signal.aborted) return false; const retryCondition = options.retryCondition ?? extraOptionDefaults.retryCondition; const maximumRetryAttempts = options.retryAttempts ?? extraOptionDefaults.retryAttempts; const customRetryCondition = await retryCondition(errorContext); if (!(currentAttemptCount <= maximumRetryAttempts && customRetryCondition)) return false; const retryMethods = new Set(options.retryMethods ?? extraOptionDefaults.retryMethods); const includesMethod = isString(request.method) && retryMethods.size > 0 ? retryMethods.has(request.method) : true; const retryStatusCodes = new Set(options.retryStatusCodes ?? extraOptionDefaults.retryStatusCodes); const includesStatusCodes = errorContext.response != null && retryStatusCodes.size > 0 ? retryStatusCodes.has(errorContext.response.status) : true; return includesMethod && includesStatusCodes; }; const handleRetry = async () => { const retryContext = { ...errorContext, retryAttemptCount: currentAttemptCount }; const hookError = await executeHooksInCatchBlock([options.onRetry?.(retryContext)], hookInfo); if (hookError) return hookError; await waitFor(getDelay()); const updatedConfig = { ...callApiArgs.config, "~retryAttemptCount": currentAttemptCount + 1 }; return callApi$1(callApiArgs.initURL, updatedConfig); }; const handleRetryOrGetErrorResult = async () => { if (await shouldAttemptRetry()) return handleRetry(); if (hookInfo.shouldThrowOnError) throw error; return errorResult; }; return { handleRetryOrGetErrorResult }; }; //#endregion //#region src/createFetchClient.ts const $GlobalRequestInfoCache = /* @__PURE__ */ new Map(); const createFetchClientWithContext = () => { const createFetchClient$1 = (initBaseConfig = {}) => { const $LocalRequestInfoCache = /* @__PURE__ */ new Map(); const callApi$1 = async (initURL, initConfig = {}) => { const [fetchOptions, extraOptions] = splitConfig(initConfig); const baseConfig = isFunction(initBaseConfig) ? initBaseConfig({ initURL: initURL.toString(), options: extraOptions, request: fetchOptions }) : initBaseConfig; const config = initConfig; const [baseFetchOptions, baseExtraOptions] = splitBaseConfig(baseConfig); const shouldSkipAutoMergeForOptions = baseExtraOptions.skipAutoMergeFor === "all" || baseExtraOptions.skipAutoMergeFor === "options"; const shouldSkipAutoMergeForRequest = baseExtraOptions.skipAutoMergeFor === "all" || baseExtraOptions.skipAutoMergeFor === "request"; const mergedExtraOptions = { ...baseExtraOptions, ...!shouldSkipAutoMergeForOptions && extraOptions }; const mergedRequestOptions = { ...baseFetchOptions, ...!shouldSkipAutoMergeForRequest && fetchOptions }; const { resolvedCurrentRouteSchemaKey, resolvedHooks, resolvedInitURL, resolvedMiddlewares, resolvedOptions, resolvedRequest } = await initializePlugins({ baseConfig, config, initURL: initURL.toString(), options: mergedExtraOptions, request: mergedRequestOptions }); const { fullURL, normalizedInitURL } = getFullAndNormalizedURL({ baseURL: resolvedOptions.baseURL, initURL: resolvedInitURL, params: resolvedOptions.params, query: resolvedOptions.query }); const options = { ...resolvedOptions, ...resolvedHooks, ...resolvedMiddlewares, fullURL, initURL: resolvedInitURL, initURLNormalized: normalizedInitURL }; const newFetchController = new AbortController(); const combinedSignal = createCombinedSignal(createTimeoutSignal(options.timeout), resolvedRequest.signal, newFetchController.signal); const request = { ...resolvedRequest, signal: combinedSignal }; const { getAbortErrorMessage, handleRequestCancelStrategy, handleRequestDeferStrategy, removeDedupeKeyFromCache, resolvedDedupeStrategy } = await createDedupeStrategy({ $GlobalRequestInfoCache, $LocalRequestInfoCache, baseConfig, config, newFetchController, options, request }); try { handleRequestCancelStrategy(); await executeHooks(options.onRequest?.({ baseConfig, config, options, request })); const { extraOptionsValidationResult, requestOptionsValidationResult, resolvedSchema, resolvedSchemaConfig } = await handleConfigValidation({ baseExtraOptions, currentRouteSchemaKey: resolvedCurrentRouteSchemaKey, extraOptions, options, request }); Object.assign(options, extraOptionsValidationResult); Object.assign(request, { body: getBody({ body: requestOptionsValidationResult.body, bodySerializer: options.bodySerializer, resolvedHeaders: requestOptionsValidationResult.headers }), headers: await getHeaders({ auth: options.auth, body: requestOptionsValidationResult.body, resolvedHeaders: requestOptionsValidationResult.headers }), method: getMethod({ initURL: resolvedInitURL, method: requestOptionsValidationResult.method }) }); const readyRequestContext = { baseConfig, config, options, request }; await executeHooks(options.onRequestReady?.(readyRequestContext)); const response = await handleRequestDeferStrategy({ fetchApi: getFetchImpl({ customFetchImpl: options.customFetchImpl, fetchMiddleware: options.fetchMiddleware, requestContext: readyRequestContext }), options, request }); const shouldCloneResponse = resolvedDedupeStrategy === "defer" || options.cloneResponse; if (!response.ok) { const validErrorData = await handleSchemaValidation(resolvedSchema, "errorData", { inputValue: await resolveResponseData(shouldCloneResponse ? response.clone() : response, options.responseType, options.responseParser), response, schemaConfig: resolvedSchemaConfig }); throw new HTTPError({ defaultHTTPErrorMessage: options.defaultHTTPErrorMessage, errorData: validErrorData, response }, { cause: validErrorData }); } const successContext = { baseConfig, config, data: await handleSchemaValidation(resolvedSchema, "data", { inputValue: await resolveResponseData(shouldCloneResponse ? response.clone() : response, options.responseType, options.responseParser), response, schemaConfig: resolvedSchemaConfig }), options, request, response }; await executeHooks(options.onSuccess?.(successContext), options.onResponse?.({ ...successContext, error: null })); return resolveSuccessResult(successContext.data, { response: successContext.response, resultMode: options.resultMode }); } catch (error) { const errorInfo = { cloneResponse: options.cloneResponse, resultMode: options.resultMode }; const { errorDetails, errorResult } = resolveErrorResult(error, errorInfo); const errorContext = { baseConfig, config, error: errorDetails.error, options, request, response: errorDetails.response }; const shouldThrowOnError = Boolean(isFunction(options.throwOnError) ? options.throwOnError(errorContext) : options.throwOnError); const hookInfo = { errorInfo, shouldThrowOnError }; const { handleRetryOrGetErrorResult } = createRetryManager({ callApi: callApi$1, callApiArgs: { config, initURL }, error, errorContext, errorResult, hookInfo }); const responseContext = errorContext.response ? { ...errorContext, data: null } : null; if (isValidationErrorInstance(error)) return await executeHooksInCatchBlock([ responseContext && options.onResponse?.(responseContext), options.onValidationError?.(errorContext), options.onError?.(errorContext) ], hookInfo) ?? await handleRetryOrGetErrorResult(); if (isHTTPErrorInstance(error)) return await executeHooksInCatchBlock([ responseContext && options.onResponse?.(responseContext), options.onResponseError?.(errorContext), options.onError?.(errorContext) ], hookInfo) ?? await handleRetryOrGetErrorResult(); let message = error?.message; if (error instanceof DOMException && error.name === "AbortError") { message = getAbortErrorMessage(); !shouldThrowOnError && console.error(`${error.name}:`, message); } if (error instanceof DOMException && error.name === "TimeoutError") { message = `Request timed out after ${options.timeout}ms`; !shouldThrowOnError && console.error(`${error.name}:`, message); } return await executeHooksInCatchBlock([ responseContext && options.onResponse?.(responseContext), options.onRequestError?.(errorContext), options.onError?.(errorContext) ], hookInfo) ?? getCustomizedErrorResult(await handleRetryOrGetErrorResult(), { message }); } finally { removeDedupeKeyFromCache(); } }; return callApi$1; }; return createFetchClient$1; }; const createFetchClient = createFetchClientWithContext(); const callApi = createFetchClient(); //#endregion export { callApi, createFetchClient, createFetchClientWithContext }; //# sourceMappingURL=index.js.map