UNPKG

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
// 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 }); } }