next-unified-query-core
Version:
Framework-agnostic HTTP client with built-in query state management
1,110 lines (1,101 loc) • 35.7 kB
JavaScript
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