@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
JavaScript
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