UNPKG

next-unified-query-core

Version:

Framework-agnostic HTTP client with built-in query state management

1,110 lines (1,101 loc) 35.7 kB
import { z } from 'zod/v4'; import { isNumber, isObject, isFunction, isEmpty, isString as isString$1 } from 'es-toolkit/compat'; import { trim, pickBy, isNil as isNil$1, compact, isString, isFunction as isFunction$1 } from 'es-toolkit'; import { isNil } from 'es-toolkit/predicate'; // src/interceptors.ts var interceptorTypeSymbols = { // 기본 인터셉터 유형 default: Symbol("default-interceptor"), // 인증 관련 인터셉터 - 자동 교체 설정 가능 auth: Symbol("auth-interceptor"), // 로깅 관련 인터셉터 - 자동 교체 설정 가능 logging: Symbol("logging-interceptor"), // 에러 처리 인터셉터 - 자동 교체 설정 가능 errorHandler: Symbol("error-handler-interceptor") }; var InterceptorManager = class { constructor() { this.handlers = []; this.idCounter = 0; } /** * 인터셉터 추가 * @param handler 인터셉터 핸들러 함수 * @param options 인터셉터 등록 옵션 * @returns 제거 함수가 포함된 핸들 객체 * @note 여러 번 use를 호출하면 등록한 모든 인터셉터가 순차적으로 실행됩니다. (Axios 등과 동일) */ use(handler, options2) { const id = this.idCounter++; const type = options2?.type || interceptorTypeSymbols.default; const tag = options2?.tag || "unnamed-interceptor"; this.handlers.push({ id, handler, type, tag }); return { remove: () => this.eject(id) }; } /** * 인터셉터 제거 * @param id 제거할 인터셉터 ID */ eject(id) { const index = this.handlers.findIndex((h) => h !== null && h.id === id); if (index !== -1) { this.handlers[index] = null; } } /** * 특정 유형의 인터셉터 모두 제거 * @param type 제거할 인터셉터 유형 */ ejectByType(type) { this.handlers.forEach((item, index) => { if (item !== null && item.type === type) { this.handlers[index] = null; } }); } /** * 모든 인터셉터 제거 */ clear() { this.handlers = []; } /** * 모든 인터셉터 실행 * @param value 인터셉터에 전달할 값 * @returns 처리된 값 */ async forEach(value) { let result = value; for (const handler of this.handlers) { if (handler !== null) { result = await handler.handler(result); } } return result; } /** * 디버깅 용도로 현재 등록된 인터셉터 정보 반환 */ getRegisteredInterceptors() { return this.handlers.filter((h) => h !== null).map((h) => ({ id: h?.id, tag: h?.tag || "(unnamed)", type: h?.type?.description || "default" })); } }; var RequestInterceptorManager = class { constructor() { this.manager = new InterceptorManager(); } /** * 요청 인터셉터 추가 * @param interceptor 요청 인터셉터 함수 * @param type 인터셉터 유형 - 기본값은 auth 타입 (자동 교체됨) * @returns 제거 함수가 포함된 핸들 객체 */ use(interceptor, type = interceptorTypeSymbols.auth) { return this.manager.use(interceptor, { type, tag: "request-interceptor" }); } /** * 모든 요청 인터셉터 제거 */ clear() { this.manager.clear(); } /** * 특정 유형의 인터셉터 모두 제거 * @param type 제거할 인터셉터 유형 */ clearByType(type) { this.manager.ejectByType(type); } /** * 요청 인터셉터 실행 */ async run(config) { return this.manager.forEach(config); } /** * 디버깅 용도로 현재 등록된 인터셉터 정보 반환 */ getRegisteredInterceptors() { return this.manager.getRegisteredInterceptors(); } }; var ResponseInterceptorManager = class { constructor() { this.manager = new InterceptorManager(); } /** * 응답 인터셉터 추가 * @param interceptor 응답 인터셉터 함수 * @param type 인터셉터 유형 - 기본값은 auth 타입 (자동 교체됨) * @returns 제거 함수가 포함된 핸들 객체 */ use(interceptor, type = interceptorTypeSymbols.auth) { return this.manager.use(interceptor, { type, tag: "response-interceptor" }); } /** * 모든 응답 인터셉터 제거 */ clear() { this.manager.clear(); } /** * 특정 유형의 인터셉터 제거 * @param type 제거할 인터셉터 유형 */ clearByType(type) { this.manager.ejectByType(type); } /** * 응답 인터셉터 실행 (내부용) */ async run(response) { return this.manager.forEach(response); } /** * 디버깅 용도로 현재 등록된 인터셉터 정보 반환 */ getRegisteredInterceptors() { return this.manager.getRegisteredInterceptors(); } }; var ErrorInterceptorManager = class { constructor() { this.manager = new InterceptorManager(); } /** * 에러 인터셉터 추가 * @param interceptor 에러 인터셉터 함수 * @param type 인터셉터 유형 - 기본값은 errorHandler 타입 (자동 교체됨) * @returns 제거 함수가 포함된 핸들 객체 */ use(interceptor, type = interceptorTypeSymbols.errorHandler) { return this.manager.use(interceptor, { type, tag: "error-interceptor" }); } /** * 모든 에러 인터셉터 제거 */ clear() { this.manager.clear(); } /** * 특정 유형의 인터셉터 제거 * @param type 제거할 인터셉터 유형 */ clearByType(type) { this.manager.ejectByType(type); } /** * 에러 인터셉터 실행 (내부용) */ async run(error) { return this.manager.forEach(error); } /** * 디버깅 용도로 현재 등록된 인터셉터 정보 반환 */ getRegisteredInterceptors() { return this.manager.getRegisteredInterceptors(); } }; function createInterceptors() { const requestInterceptors = new RequestInterceptorManager(); const responseInterceptors = new ResponseInterceptorManager(); const errorInterceptors = new ErrorInterceptorManager(); return { request: { /** * 요청 인터셉터 추가 - 자동으로 같은 유형의 이전 인터셉터 교체 * @param interceptor 요청 처리 함수 * @returns 제거 함수가 포함된 핸들 * @example * ```typescript * // 인터셉터 추가 (auth 유형 기본값) * const authInterceptor = api.interceptors.request.use(config => { * config.headers = config.headers || {}; * config.headers['Authorization'] = `Bearer ${getToken()}`; * return config; * }); * * // 다른 인터셉터 추가 시 이전 auth 유형 인터셉터는 자동 제거됨 * api.interceptors.request.use(config => { * config.headers = config.headers || {}; * config.headers['Authorization'] = `Bearer ${getNewToken()}`; * return config; * }); * ``` */ use: (interceptor) => requestInterceptors.use(interceptor), /** * 인터셉터 제거 (하위 호환성) * @deprecated InterceptorHandle.remove() 사용 권장 */ eject: (id) => { console.warn("eject() \uBA54\uC11C\uB4DC\uB294 \uC0AC\uC6A9\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. use()\uAC00 \uBC18\uD658\uD558\uB294 InterceptorHandle.remove()\uB97C \uC0AC\uC6A9\uD558\uC138\uC694."); }, /** * 모든 요청 인터셉터 제거 * @example * ```typescript * // 모든 요청 인터셉터 제거 * api.interceptors.request.clear(); * ``` */ clear: () => requestInterceptors.clear(), /** * 특정 유형의 인터셉터 제거 * @param type 제거할 인터셉터 유형 * @example * ```typescript * // 인증 관련 인터셉터만 제거 * api.interceptors.request.clearByType(interceptorTypes.auth); * ``` */ clearByType: (type) => requestInterceptors.clearByType(type), /** * 요청 인터셉터 실행 (내부용) */ run: requestInterceptors.run.bind(requestInterceptors), /** * 디버깅 용도로 현재 등록된 인터셉터 정보 조회 */ getRegistered: () => requestInterceptors.getRegisteredInterceptors() }, response: { /** * 응답 인터셉터 추가 - 자동으로 같은 유형의 이전 인터셉터 교체 * @param onFulfilled 성공 응답 처리 함수 * @returns 제거 함수가 포함된 핸들 */ use: (onFulfilled) => responseInterceptors.use(onFulfilled), /** * 인터셉터 제거 (하위 호환성) * @deprecated InterceptorHandle.remove() 사용 권장 */ eject: (id) => { console.warn("eject() \uBA54\uC11C\uB4DC\uB294 \uC0AC\uC6A9\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. use()\uAC00 \uBC18\uD658\uD558\uB294 InterceptorHandle.remove()\uB97C \uC0AC\uC6A9\uD558\uC138\uC694."); }, /** * 모든 응답 인터셉터 제거 */ clear: () => responseInterceptors.clear(), /** * 특정 유형의 인터셉터 제거 * @param type 제거할 인터셉터 유형 */ clearByType: (type) => responseInterceptors.clearByType(type), /** * 응답 인터셉터 실행 (내부용) */ run: responseInterceptors.run.bind(responseInterceptors), /** * 디버깅 용도로 현재 등록된 인터셉터 정보 조회 */ getRegistered: () => responseInterceptors.getRegisteredInterceptors() }, error: { /** * 에러 인터셉터 추가 - 자동으로 같은 유형의 이전 인터셉터 교체 * @param onRejected 에러 처리 함수 * @returns 제거 함수가 포함된 핸들 */ use: (onRejected) => errorInterceptors.use(onRejected), /** * 인터셉터 제거 (하위 호환성) * @deprecated InterceptorHandle.remove() 사용 권장 */ eject: (id) => { console.warn("eject() \uBA54\uC11C\uB4DC\uB294 \uC0AC\uC6A9\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. use()\uAC00 \uBC18\uD658\uD558\uB294 InterceptorHandle.remove()\uB97C \uC0AC\uC6A9\uD558\uC138\uC694."); }, /** * 모든 에러 인터셉터 제거 */ clear: () => errorInterceptors.clear(), /** * 특정 유형의 인터셉터 제거 * @param type 제거할 인터셉터 유형 */ clearByType: (type) => errorInterceptors.clearByType(type), /** * 에러 인터셉터 실행 (내부용) */ run: errorInterceptors.run.bind(errorInterceptors), /** * 디버깅 용도로 현재 등록된 인터셉터 정보 조회 */ getRegistered: () => errorInterceptors.getRegisteredInterceptors() } }; } var interceptorTypes = interceptorTypeSymbols; // src/types/index.ts var ContentType = /* @__PURE__ */ ((ContentType2) => { ContentType2["JSON"] = "application/json"; ContentType2["FORM"] = "application/x-www-form-urlencoded"; ContentType2["TEXT"] = "text/plain"; ContentType2["BLOB"] = "application/octet-stream"; ContentType2["MULTIPART"] = "multipart/form-data"; ContentType2["XML"] = "application/xml"; ContentType2["HTML"] = "text/html"; return ContentType2; })(ContentType || {}); var ResponseType = /* @__PURE__ */ ((ResponseType2) => { ResponseType2["JSON"] = "json"; ResponseType2["TEXT"] = "text"; ResponseType2["BLOB"] = "blob"; ResponseType2["ARRAY_BUFFER"] = "arraybuffer"; ResponseType2["RAW"] = "raw"; return ResponseType2; })(ResponseType || {}); var FetchError = class extends Error { /** * FetchError 생성자 * @param message 에러 메시지 * @param config 요청 설정 * @param code 에러 코드 * @param request 요청 객체 * @param response 응답 객체 * @param responseData 응답 데이터 */ constructor(message, config, code, request2, response, responseData) { super(message); /** * 에러 이름 */ this.name = "FetchError"; this.config = config; this.code = code; this.request = request2; if (response) { this.response = { data: responseData, status: response.status, statusText: response.statusText, headers: response.headers }; } } }; function appendQueryParams(url, params) { const cleanUrl = trim(url); if (!params || isEmpty(params)) return cleanUrl; const validParams = pickBy(params, (value) => !isNil$1(value)); if (isEmpty(validParams)) return cleanUrl; const [baseUrl, fragment] = cleanUrl.split("#"); const [path, existingQuery] = baseUrl.split("?"); const existingParams = new URLSearchParams(existingQuery || ""); Object.entries(validParams).forEach(([key, value]) => { existingParams.set(key, String(value)); }); const queryString = existingParams.toString(); const urlParts = compact([path, queryString ? `?${queryString}` : null, fragment ? `#${fragment}` : null]); return urlParts.join(""); } function combineURLs(baseURL, url) { const cleanBaseURL = baseURL ? trim(baseURL) : ""; const cleanUrl = url ? trim(url) : ""; if (!cleanBaseURL) return cleanUrl; if (!cleanUrl) return cleanBaseURL; const baseEndsWithSlash = cleanBaseURL.endsWith("/"); const urlStartsWithSlash = cleanUrl.startsWith("/"); if (baseEndsWithSlash && urlStartsWithSlash) { return cleanBaseURL + cleanUrl.substring(1); } if (!baseEndsWithSlash && !urlStartsWithSlash) { return `${cleanBaseURL}/${cleanUrl}`; } return cleanBaseURL + cleanUrl; } // src/utils/config.ts function mergeConfigs(defaultConfig = {}, requestConfig = {}) { const mergedConfig = { ...defaultConfig, ...requestConfig }; mergedConfig.headers = { ...defaultConfig.headers, ...requestConfig.headers }; mergedConfig.params = { ...defaultConfig.params, ...requestConfig.params }; if (defaultConfig.next || requestConfig.next) { mergedConfig.next = { ...defaultConfig.next, ...requestConfig.next }; } return mergedConfig; } function stringifyData(data) { if (isNil$1(data)) return null; if (isString(data)) return data; try { return JSON.stringify(data); } catch (e) { console.error("Failed to stringify data:", e); return null; } } // src/utils/timeout.ts function createTimeoutPromise(ms) { if (!ms || ms <= 0) return null; const controller = new AbortController(); const promise = new Promise((_, reject) => { setTimeout(() => { controller.abort(); reject(new Error(`Request timeout of ${ms}ms exceeded`)); }, ms); }); return { promise, controller }; } // src/utils/response.ts function unwrap(response) { return response.data; } function getStatus(response) { return response.status; } function getHeaders(response) { return response.headers; } function hasStatus(response, code) { return response.status === code; } function createError(message, config, code = "ERR_UNKNOWN", response, data) { return new FetchError(message, config, code, void 0, response, data); } function isFetchError(error) { return error instanceof FetchError; } function isValidationError(error) { return isFetchError(error) && error.code === "ERR_VALIDATION" && error.cause instanceof z.ZodError; } function getValidationErrors(error) { if (!isValidationError(error)) { return []; } return error.cause.issues.map((issue) => ({ path: issue.path.join("."), message: issue.message })); } function hasErrorCode(error, code) { return isFetchError(error) && error.code === code; } var ErrorCode = { /** 네트워크 에러 */ NETWORK: "ERR_NETWORK", /** 요청 취소됨 */ CANCELED: "ERR_CANCELED", /** 요청 타임아웃 */ TIMEOUT: "ERR_TIMEOUT", /** 서버 응답 에러 (4xx, 5xx) */ BAD_RESPONSE: "ERR_BAD_RESPONSE", /** 데이터 검증 실패 */ VALIDATION: "ERR_VALIDATION", /** 알 수 없는 검증 오류 */ VALIDATION_UNKNOWN: "ERR_VALIDATION_UNKNOWN", /** 알 수 없는 에러 */ UNKNOWN: "ERR_UNKNOWN" }; function handleFetchError(error, handlers) { if (isFetchError(error) && error.code) { const errorCode = error.code; const handler = handlers[errorCode]; if (handler) { return handler(error); } } if (handlers.default) { return handlers.default(error); } throw error; } function handleHttpError(error, handlers) { if (isFetchError(error) && error.response && isFunction$1(handlers[error.response.status])) { return handlers[error.response.status](error); } if (handlers.default) { return handlers.default(error); } throw error; } function errorToResponse(error, data) { return { data, status: error.response?.status || 500, statusText: error.response?.statusText || error.message, headers: error.response?.headers || new Headers(), config: error.config, request: error.request }; } function isJsonContentType(contentType) { return contentType === "application/json" /* JSON */ || contentType.includes("application/json"); } function isFormContentType(contentType) { return contentType === "application/x-www-form-urlencoded" /* FORM */ || contentType.includes("application/x-www-form-urlencoded"); } function isXmlContentType(contentType) { return contentType === "application/xml" /* XML */ || contentType.includes("application/xml"); } function isHtmlContentType(contentType) { return contentType === "text/html" /* HTML */ || contentType.includes("text/html"); } function isTextContentType(contentType) { return contentType === "text/plain" /* TEXT */ || contentType.includes("text/plain"); } function isBlobContentType(contentType) { return contentType === "application/octet-stream" /* BLOB */ || contentType.includes("application/octet-stream"); } function createFormBody(data) { if (isObject(data) && !(data instanceof URLSearchParams)) { const params = new URLSearchParams(); for (const [key, value] of Object.entries(data)) { if (!isNil(value)) { params.append(key, String(value)); } } return params; } if (data instanceof URLSearchParams) { return data; } return String(data || ""); } function createTextBody(data) { return isString$1(data) ? data : String(data); } function createBlobBody(data) { if (data instanceof Blob || data instanceof ArrayBuffer) { return data; } return isString$1(data) ? data : String(data); } function isPlainObjectForJson(data) { return isObject(data) && !(data instanceof FormData) && !(data instanceof URLSearchParams) && !(data instanceof Blob); } function shouldDefaultToJson(effectiveContentType, data) { return effectiveContentType === "" && isPlainObjectForJson(data); } function createRetrySettings(retryConfig) { const defaultBackoff = (count) => Math.min(1e3 * 2 ** (count - 1), 1e4); if (isNumber(retryConfig)) { return { maxRetries: retryConfig, retryStatusCodes: [], retryBackoff: defaultBackoff }; } if (retryConfig && isObject(retryConfig)) { let retryBackoff = defaultBackoff; if (retryConfig.backoff === "linear") { retryBackoff = (count) => 1e3 * count; } else if (retryConfig.backoff === "exponential") { retryBackoff = (count) => Math.min(1e3 * 2 ** (count - 1), 1e4); } else if (isFunction(retryConfig.backoff)) { retryBackoff = retryConfig.backoff; } return { maxRetries: retryConfig.limit, retryStatusCodes: retryConfig.statusCodes || [], retryBackoff }; } return { maxRetries: 0, retryStatusCodes: [], retryBackoff: defaultBackoff }; } function setupAbortSignal(signal, onAbort) { if (!signal) return; if (signal.aborted) { onAbort(); } else { signal.addEventListener("abort", onAbort); } } function throwIfCanceled(isCanceled, config) { if (isCanceled) { throw new FetchError("Request was canceled", config, "ERR_CANCELED"); } } function createRequestInit(requestConfig, abortController) { const { method = "GET", headers = {}, cache, credentials, integrity, keepalive, mode, redirect, referrer, referrerPolicy, next } = requestConfig; const requestInit = { method, headers, signal: abortController.signal, cache, credentials, integrity, keepalive, mode, redirect, referrer, referrerPolicy }; if (next) { requestInit.next = next; } return requestInit; } function prepareRequestBody(data, contentType, headers) { const headersCopy = { ...headers }; if (data instanceof FormData || data instanceof URLSearchParams || data instanceof Blob) { if (data instanceof FormData && (contentType === "" || contentType === "multipart/form-data" /* MULTIPART */)) { const { "Content-Type": _, ...remainingHeaders } = headersCopy; return { body: data, headers: remainingHeaders }; } return { body: data, headers: headersCopy }; } const contentTypeStr = String(contentType); if (isJsonContentType(contentTypeStr)) { return { body: stringifyData(data), headers: { ...headersCopy, "Content-Type": "application/json" /* JSON */ } }; } if (isFormContentType(contentTypeStr)) { return { body: createFormBody(data), headers: { ...headersCopy, "Content-Type": "application/x-www-form-urlencoded" /* FORM */ } }; } if (isXmlContentType(contentTypeStr)) { return { body: createTextBody(data), headers: { ...headersCopy, "Content-Type": "application/xml" /* XML */ } }; } if (isHtmlContentType(contentTypeStr)) { return { body: createTextBody(data), headers: { ...headersCopy, "Content-Type": "text/html" /* HTML */ } }; } if (isTextContentType(contentTypeStr)) { return { body: createTextBody(data), headers: { ...headersCopy, "Content-Type": "text/plain" /* TEXT */ } }; } if (isBlobContentType(contentTypeStr)) { return { body: createBlobBody(data), headers: { ...headersCopy, "Content-Type": "application/octet-stream" /* BLOB */ } }; } const body = isObject(data) ? stringifyData(data) : String(data); return { body, headers: { ...headersCopy, "Content-Type": contentTypeStr } }; } async function processResponseByType(response, responseType, contentTypeHeader, parseJSON = true) { const effectiveResponseType = responseType || (contentTypeHeader.includes("application/json") && parseJSON !== false ? "json" /* JSON */ : "text" /* TEXT */); const isEmptyResponse = response.status === 204 || response.headers.get("content-length") === "0"; const safeCall = async (method, fallback) => { if (!response[method] || !isFunction(response[method])) { if (typeof process !== "undefined" && process.env.NODE_ENV === "test") { try { if (response[method] && isFunction(response[method])) { return await response[method](); } } catch (e) { } } return fallback; } try { return await response[method](); } catch (e) { console.warn(`Failed to process response with ${method}:`, e); return fallback; } }; switch (effectiveResponseType) { case "json" /* JSON */: if (isEmptyResponse) { return {}; } try { return await response.json(); } catch (e) { return await safeCall("text", ""); } case "blob" /* BLOB */: if (isEmptyResponse) { return new Blob(); } return await safeCall("blob", new Blob()); case "arraybuffer" /* ARRAY_BUFFER */: if (isEmptyResponse) { return new ArrayBuffer(0); } return await safeCall("arrayBuffer", new ArrayBuffer(0)); case "raw" /* RAW */: return response; default: if (isEmptyResponse) { return ""; } return await safeCall("text", ""); } } function canRetry(retryCount, maxRetries, isCanceled) { return retryCount < maxRetries && !isCanceled; } function shouldRetryForHttpStatus(status, retryStatusCodes) { return retryStatusCodes.length === 0 || retryStatusCodes.includes(status); } function isNetworkError(error) { return !(error instanceof FetchError); } async function executeRetry(retryBackoff, retryCount, performRequest) { const delay = retryBackoff(retryCount); await new Promise((resolve) => setTimeout(resolve, delay)); return performRequest(); } function hasValidAuthRetry(authRetryOption) { return !isNil(authRetryOption) && isFunction(authRetryOption.handler); } function shouldAuthRetryForStatus(status, authRetryOption) { const statusCodes = authRetryOption.statusCodes ?? [401]; return statusCodes.includes(status); } function shouldExecuteAuthRetry(fetchError, config, authRetryOption) { return !authRetryOption.shouldRetry || authRetryOption.shouldRetry(fetchError, config); } function canAuthRetry(authRetryCount, authRetryOption) { return authRetryCount < (authRetryOption.limit ?? 1); } async function processErrorWithInterceptor(error, interceptors2) { const processedError = await interceptors2.error.run(error); if ("data" in processedError && "status" in processedError) { return processedError; } throw processedError; } function createRequestFunction(defaultConfig, interceptors2) { const activeRequests = /* @__PURE__ */ new Map(); function request2(config) { const requestKey = JSON.stringify({ url: config.url, method: config.method || "GET", params: config.params, data: config.data, baseURL: config.baseURL, _authRetryCount: config._authRetryCount || 0 }); const existingRequest = activeRequests.get(requestKey); if (existingRequest) { return existingRequest; } let isCanceled = false; let abortController = new AbortController(); const cancel = () => { isCanceled = true; abortController.abort(); }; const { maxRetries, retryStatusCodes, retryBackoff } = createRetrySettings(config.retry); let retryCount = 0; let authRetryCount = config._authRetryCount || 0; const authRetryOption = config.authRetry || defaultConfig.authRetry; setupAbortSignal(config.signal, () => { isCanceled = true; abortController.abort(); }); async function performRequest() { try { throwIfCanceled(isCanceled, config); const { schema, ...restConfig } = config; const requestConfig = await interceptors2.request.run(restConfig); const url = combineURLs(requestConfig.baseURL, requestConfig.url); const fullUrl = appendQueryParams(url, requestConfig.params); const timeoutResult = createTimeoutPromise(requestConfig.timeout); throwIfCanceled(isCanceled, config); const { contentType, responseType, data, headers = {} } = requestConfig; const requestInit = createRequestInit(requestConfig, abortController); if (!isNil(data)) { const effectiveContentType = contentType || headers["Content-Type"] || ""; if (shouldDefaultToJson(effectiveContentType, data)) { requestInit.body = stringifyData(data); requestInit.headers = { ...headers, "Content-Type": "application/json" /* JSON */ }; } else { const { body, headers: processedHeaders } = prepareRequestBody( data, effectiveContentType, headers ); requestInit.body = body; requestInit.headers = processedHeaders; } } throwIfCanceled(isCanceled, config); const fetchPromise = fetch(fullUrl, requestInit); const response = await (timeoutResult ? Promise.race([fetchPromise, timeoutResult.promise]) : fetchPromise); const contentTypeHeader = response.headers.get("content-type") || ""; const responseData = await processResponseByType( response, responseType, contentTypeHeader, requestConfig.parseJSON ); if (!response.ok) { const fetchError = new FetchError( response.statusText || `HTTP error ${response.status}`, requestConfig, "ERR_BAD_RESPONSE", requestInit, response, responseData ); if (hasValidAuthRetry(authRetryOption)) { const statusMatch = shouldAuthRetryForStatus(response.status, authRetryOption); const shouldRetryResult = shouldExecuteAuthRetry(fetchError, config, authRetryOption); if (statusMatch && shouldRetryResult) { authRetryCount = config._authRetryCount || 0; if (canAuthRetry(authRetryCount, authRetryOption)) { const shouldRetry = await authRetryOption.handler(fetchError, config); if (shouldRetry) { return request2({ ...config, _authRetryCount: authRetryCount + 1 }); } } } } if (!canRetry(retryCount, maxRetries, isCanceled)) { return processErrorWithInterceptor(fetchError, interceptors2); } if (shouldRetryForHttpStatus(response.status, retryStatusCodes)) { retryCount++; abortController = new AbortController(); return executeRetry(retryBackoff, retryCount, performRequest); } return processErrorWithInterceptor(fetchError, interceptors2); } const NextTypeResponse = { data: responseData, status: response.status, statusText: response.statusText, headers: response.headers, config: requestConfig, request: requestInit }; const processedResponse = await interceptors2.response.run(NextTypeResponse); if (schema) { try { const validatedData = schema.parse(processedResponse.data); processedResponse.data = validatedData; return processedResponse; } catch (validationError) { if (validationError instanceof z.ZodError) { const fetchError2 = new FetchError( "Validation failed", requestConfig, "ERR_VALIDATION", requestInit, response, processedResponse.data ); fetchError2.name = "ValidationError"; fetchError2.cause = validationError; return processErrorWithInterceptor(fetchError2, interceptors2); } const fetchError = new FetchError( "Unknown validation error", requestConfig, "ERR_VALIDATION_UNKNOWN", requestInit, response, processedResponse.data ); return processErrorWithInterceptor(fetchError, interceptors2); } } return processedResponse; } catch (error) { if (error instanceof FetchError) { throw error; } if (error instanceof Error && error.name === "AbortError") { const fetchError2 = new FetchError( isCanceled ? "Request was canceled" : "Request timed out", config, isCanceled ? "ERR_CANCELED" : "ERR_TIMEOUT" ); return processErrorWithInterceptor(fetchError2, interceptors2); } if (canRetry(retryCount, maxRetries, isCanceled) && isNetworkError(error)) { retryCount++; abortController = new AbortController(); return executeRetry(retryBackoff, retryCount, performRequest); } const fetchError = new FetchError( error instanceof Error ? error.message : "Request failed", config, "ERR_NETWORK" ); return processErrorWithInterceptor(fetchError, interceptors2); } } const requestPromise = performRequest().finally(() => { activeRequests.delete(requestKey); }); activeRequests.set(requestKey, requestPromise); const cancelablePromise = Object.assign(requestPromise, { cancel, isCanceled: () => isCanceled }); return cancelablePromise; } return request2; } // src/methods/index.ts function createHttpMethods(request2, defaultConfig) { return { get(url, config = {}) { return request2( mergeConfigs(defaultConfig, { ...config, url, method: "GET" }) ); }, post(url, data, config = {}) { return request2( mergeConfigs(defaultConfig, { ...config, url, method: "POST", data }) ); }, put(url, data, config = {}) { return request2( mergeConfigs(defaultConfig, { ...config, url, method: "PUT", data }) ); }, delete(url, config = {}) { return request2( mergeConfigs(defaultConfig, { ...config, url, method: "DELETE" }) ); }, patch(url, data, config = {}) { return request2( mergeConfigs(defaultConfig, { ...config, url, method: "PATCH", data }) ); }, head(url, config = {}) { return request2( mergeConfigs(defaultConfig, { ...config, url, method: "HEAD" }) ); }, options(url, config = {}) { return request2( mergeConfigs(defaultConfig, { ...config, url, method: "OPTIONS" }) ); } }; } // src/core/client.ts function createFetch(defaultConfig = {}) { const mergedConfig = { ...defaultConfig }; const interceptors2 = createInterceptors(); const request2 = createRequestFunction(mergedConfig, interceptors2); const methods = createHttpMethods(request2, mergedConfig); const instance = { defaults: mergedConfig, interceptors: interceptors2, request: request2, ...methods }; return instance; } // src/fetch.ts var defaultInstance = createFetch(); function updateDefaultInstance(config = {}) { defaultInstance = createFetch(config); } var request = (...args) => defaultInstance.request(...args); var get = (...args) => defaultInstance.get(...args); var post = (...args) => defaultInstance.post(...args); var put = (...args) => defaultInstance.put(...args); var del = (...args) => defaultInstance.delete(...args); var patch = (...args) => defaultInstance.patch(...args); var head = (...args) => defaultInstance.head(...args); var options = (...args) => defaultInstance.options(...args); var ntFetch = new Proxy({}, { get: (_, prop) => defaultInstance.defaults[prop], set: (_, prop, value) => { defaultInstance.defaults[prop] = value; return true; } }); var interceptors = new Proxy({}, { get: (_, prop) => defaultInstance.interceptors[prop] }); var defaultInstanceProxy = new Proxy({}, { get: (_, prop) => defaultInstance[prop] }); var fetch_default = defaultInstanceProxy; export { ContentType, ErrorCode, FetchError, ResponseType, createError, createFetch, del, errorToResponse, fetch_default, get, getHeaders, getStatus, getValidationErrors, handleFetchError, handleHttpError, hasErrorCode, hasStatus, head, interceptorTypes, interceptors, isFetchError, isValidationError, ntFetch, options, patch, post, put, request, unwrap, updateDefaultInstance }; //# sourceMappingURL=chunk-KRKSGPO7.mjs.map //# sourceMappingURL=chunk-KRKSGPO7.mjs.map