/** * HTTP API client with error handling * * Provides a typed wrapper around fetch for JSON APIs. * Automatically handles JSON serialization and error responses. * * @example * ```ts * import { api } from '$shared/api'; * * // GET request * const users = await api.get('/api/users'); * * // POST request * const newUser = await api.post('/api/users', { name: 'Alice' }); * * // Error handling * try { * const data = await api.get('/api/data'); * } catch (error) { * if (error instanceof ApiError) { * console.error(error.status, error.message); * } * } * ``` */ import type { ApiResponse } from '$shared/types/common'; /** * Custom error class for API failures * * Includes HTTP status code and the original Response object * for debugging and error handling. */ export class ApiError extends Error { /** * Creates a new API error * @param status - HTTP status code * @param message - Error message * @param response - Original fetch Response object */ constructor( /** * HTTP status code */ public status: number, message: string, /** * Original Response object for inspection */ public response?: Response, ) { super(message); this.name = 'ApiError'; } } /** * Internal request handler * * Performs fetch with JSON headers and throws ApiError on failure. * * @param url - Request URL * @param options - Fetch options (method, headers, body, etc.) * @returns Response data and status code * @throws ApiError when response is not OK */ async function request( url: string, options?: RequestInit, ): Promise> { const response = await fetch(url, { headers: { 'Content-Type': 'application/json', ...options?.headers, }, ...options, }); if (!response.ok) { throw new ApiError( response.status, `Request failed: ${response.statusText}`, response, ); } const data = await response.json() as T; return { data, status: response.status, }; } /** * API client methods * * Provides typed methods for common HTTP verbs. * All methods return ApiResponse with data and status. */ export const api = { /** * Performs a GET request * @param url - Request URL * @param options - Additional fetch options * @returns Response data */ get: (url: string, options?: RequestInit) => request(url, { ...options, method: 'GET' }), /** * Performs a POST request with JSON body * @param url - Request URL * @param body - Request body (will be JSON stringified) * @param options - Additional fetch options * @returns Response data */ post: (url: string, body?: unknown, options?: RequestInit) => request(url, { ...options, method: 'POST', body: JSON.stringify(body), }), /** * Performs a PUT request with JSON body * @param url - Request URL * @param body - Request body (will be JSON stringified) * @param options - Additional fetch options * @returns Response data */ put: (url: string, body?: unknown, options?: RequestInit) => request(url, { ...options, method: 'PUT', body: JSON.stringify(body), }), /** * Performs a DELETE request * @param url - Request URL * @param options - Additional fetch options * @returns Response data */ delete: (url: string, options?: RequestInit) => request(url, { ...options, method: 'DELETE' }), };