/** * API Client Configuration */ export interface ClientConfig { baseUrl: string; apiKey?: string; bearerToken?: string; headers?: Record; onError?: (error: Error) => void; } /** * Create a typed API client * * @example * const client = createClient({ * baseUrl: 'https://api.example.com', * apiKey: 'your-api-key', * }); * * const users = await client.get('/users'); * const newUser = await client.post('/users', { name: 'John' }); */ export function createClient(config: ClientConfig) { const { baseUrl, apiKey, bearerToken, headers = {}, onError } = config; async function request( method: string, path: string, options: { body?: unknown; params?: Record; headers?: Record; } = {} ): Promise { const url = new URL(path, baseUrl); // Add query params if (options.params) { for (const [key, value] of Object.entries(options.params)) { if (value !== undefined) { url.searchParams.set(key, String(value)); } } } // Build headers const requestHeaders: Record = { 'Content-Type': 'application/json', ...headers, ...options.headers, }; if (apiKey) { requestHeaders['X-API-Key'] = apiKey; } if (bearerToken) { requestHeaders['Authorization'] = `Bearer ${bearerToken}`; } const response = await fetch(url.toString(), { method, headers: requestHeaders, body: options.body ? JSON.stringify(options.body) : undefined, }); if (!response.ok) { const error = new Error(`API error: ${response.status}`); if (onError) { onError(error); } throw error; } // Handle no-content responses if (response.status === 204) { return undefined as T; } return response.json(); } return { get: (path: string, params?: Record) => request('GET', path, { params }), post: (path: string, body?: unknown) => request('POST', path, { body }), put: (path: string, body?: unknown) => request('PUT', path, { body }), patch: (path: string, body?: unknown) => request('PATCH', path, { body }), delete: (path: string) => request('DELETE', path), }; }