UNPKG

@metis-w/api-client

Version:

Modern TypeScript HTTP API client with dynamic routes, parameterized endpoints, interceptors, and advanced features

129 lines 5.13 kB
import { DataSerializer } from "../utils"; import { ResponseParser } from "../libs/parsers"; import { RequestBuilder } from "../libs/builders"; import { RetryManager, InterceptorManager } from "../libs/managers"; export class APIClient { /** * Returns the interceptor manager for adding request and response interceptors. * This allows modification of requests and responses globally. */ get interceptors() { return this.interceptorManager; } /** * Returns the current configuration. * This allows access to configuration from derived classes. */ get apiConfig() { return this.config; } constructor(config) { this.interceptorManager = new InterceptorManager(); this.config = { baseUrl: config.baseUrl, timeout: config.timeout || 5000, headers: config.headers || {}, withCredentials: config.withCredentials ?? false, retries: config.retries || 3, retryDelay: config.retryDelay || 1000, useKebabCase: config.useKebabCase || false, defaultMethod: config.defaultMethod || "POST", methodRules: config.methodRules || {}, }; } async get(url, config) { return this.request({ ...config, method: "GET", url }); } async post(url, data, config) { return this.request({ ...config, method: "POST", url, data }); } async put(url, data, config) { return this.request({ ...config, method: "PUT", url, data }); } async delete(url, config) { return this.request({ ...config, method: "DELETE", url }); } async patch(url, data, config) { return this.request({ ...config, method: "PATCH", url, data }); } /** * Destroys the APIClient instance, clearing all interceptors and references. * This is useful for cleanup when the client is no longer needed. */ destroy() { this.config = null; this.interceptorManager.clearAllInterceptors(); } /** * Sends an HTTP request with the specified configuration. * This method handles retries, interceptors, and error parsing. * * @param config - The request configuration object * @returns A Promise that resolves to an APIResponse object */ async request(config) { let finalConfig = RequestBuilder.mergeConfig(config, this.config); for (const { interceptor, } of this.interceptorManager.getRequestInterceptors()) { finalConfig = RequestBuilder.mergeConfig(await interceptor(finalConfig), finalConfig); } let attempt = 0; const maxAttempts = finalConfig.retries + 1; while (attempt < maxAttempts) { try { const response = await this.executeRequest(finalConfig); let finalResponse = response; for (const { interceptor, } of this.interceptorManager.getResponseInterceptors()) { finalResponse = await interceptor(finalResponse); } return finalResponse; } catch (error) { attempt++; const clientError = ResponseParser.createClientError(error); if (attempt >= maxAttempts || !RetryManager.shouldRetry(clientError)) { throw clientError; } const delay = RetryManager.calculateBackoffDelay(attempt - 1, finalConfig.retryDelay); await RetryManager.delay(delay); } } throw new Error("Request failed after maximum retry attempts."); } /** * Executes the HTTP request using the Fetch API. * This method handles serialization, headers, and error parsing. * * @param config - The request configuration object * @returns A Promise that resolves to an APIResponse object */ async executeRequest(config) { const url = RequestBuilder.buildUrl(this.config.baseUrl, config.url, config.params, this.config.useKebabCase); const body = config.data ? DataSerializer.serialize(config.data) : undefined; const headers = RequestBuilder.buildHeaders(config.headers, config.data); const controller = config.signal ? null : new AbortController(); const effectiveSignal = config.signal || controller?.signal; const timeoutId = setTimeout(() => { if (controller) controller.abort(); }, config.timeout); try { const response = await fetch(url, { method: config.method, headers, body, credentials: config.withCredentials ? "include" : "omit", signal: effectiveSignal, }); clearTimeout(timeoutId); return await ResponseParser.parseResponse(response); } catch (error) { clearTimeout(timeoutId); throw ResponseParser.createClientError(error); } } } //# sourceMappingURL=api-client.js.map