api-wizard
Version:
A powerful TypeScript wrapper for native Fetch API with token management, interceptors, and type-safe HTTP requests
170 lines (169 loc) • 5.35 kB
JavaScript
// Fetch 에러 클래스 (axios 호환)
export class FetchError extends Error {
constructor(status, statusText, originalResponse, url, data, requestConfig) {
super(`HTTP Error ${status}: ${statusText}`);
this.name = 'FetchError';
this.status = status;
// axios 호환 response 구조
this.response = {
data,
status,
statusText,
headers: originalResponse.headers
};
// request 정보 (axios 호환)
this.request = {
url,
method: requestConfig?.method || 'GET'
};
this.config = requestConfig;
}
}
// Fetch 클라이언트 구현
export class FetchClientImpl {
constructor(config) {
this.baseURL = config?.baseURL || '';
this.defaultHeaders = {
'Content-Type': 'application/json',
...config?.headers
};
this.defaultConfig = {
credentials: 'include',
...config?.requestConfig
};
}
// URL 생성 헬퍼
buildURL(url, params) {
const fullURL = this.baseURL + url;
if (!params || Object.keys(params).length === 0) {
return fullURL;
}
const searchParams = new URLSearchParams(params);
return `${fullURL}?${searchParams.toString()}`;
}
// 헤더 병합 헬퍼
mergeHeaders(...headers) {
const result = new Headers();
headers.forEach(headerInit => {
if (!headerInit)
return;
if (headerInit instanceof Headers) {
headerInit.forEach((value, key) => result.set(key, value));
}
else if (Array.isArray(headerInit)) {
headerInit.forEach(([key, value]) => result.set(key, value));
}
else {
Object.entries(headerInit).forEach(([key, value]) => {
if (value !== undefined)
result.set(key, value);
});
}
});
return result;
}
// Response를 FetchResponse로 변환 (axios 호환 에러 처리)
async transformResponse(response, requestConfig) {
let data;
const contentType = response.headers.get('content-type');
try {
if (contentType && contentType.includes('application/json')) {
data = await response.json();
}
else {
data = await response.text();
}
}
catch (parseError) {
data = null;
}
if (!response.ok) {
throw new FetchError(response.status, response.statusText, response, response.url, data, requestConfig);
}
return {
data,
status: response.status,
statusText: response.statusText,
headers: response.headers,
url: response.url,
ok: response.ok
};
}
// 공통 request 메서드
async request(config) {
const { url, params, timeout, baseURL, ...fetchConfig } = config;
const finalURL = this.buildURL(url, params);
const headers = this.mergeHeaders(this.defaultHeaders, fetchConfig.headers);
const requestConfig = {
...this.defaultConfig,
...fetchConfig,
headers
};
// 타임아웃 처리
const controller = new AbortController();
if (timeout) {
setTimeout(() => controller.abort(), timeout);
}
requestConfig.signal = controller.signal;
try {
const response = await fetch(finalURL, requestConfig);
return await this.transformResponse(response, { ...config, url: finalURL });
}
catch (error) {
if (error instanceof FetchError) {
throw error;
}
// 네트워크 에러 (axios 호환)
const networkError = new Error(`Network Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
networkError.request = {
url: finalURL,
method: requestConfig.method || 'GET'
};
networkError.config = { ...config, url: finalURL };
throw networkError;
}
}
// GET 메서드
async get(url, config) {
return this.request({
url,
method: 'GET',
...config
});
}
// POST 메서드
async post(url, data, config) {
return this.request({
url,
method: 'POST',
body: data ? JSON.stringify(data) : undefined,
...config
});
}
// PUT 메서드
async put(url, data, config) {
return this.request({
url,
method: 'PUT',
body: data ? JSON.stringify(data) : undefined,
...config
});
}
// PATCH 메서드
async patch(url, data, config) {
return this.request({
url,
method: 'PATCH',
body: data ? JSON.stringify(data) : undefined,
...config
});
}
// DELETE 메서드
async delete(url, config) {
return this.request({
url,
method: 'DELETE',
...config
});
}
}