const apiBase = (): string => import.meta.env.PUBLIC_API_URL || ''; export function apiUrl(path: string): string { return path.startsWith('http') ? path : `${apiBase()}${path}`; } export class ApiError extends Error { status: number; code?: string; detail?: string; constructor(status: number, title: string, code?: string, detail?: string) { super(title); this.name = 'ApiError'; this.status = status; this.code = code; this.detail = detail; } } export async function toApiError(res: Response): Promise { try { const body = await res.json(); return new ApiError( res.status, body.title || body.detail || `Request failed (${res.status})`, body.code, body.detail, ); } catch { return new ApiError(res.status, `Request failed (${res.status})`); } } export function jsonHeaders(options?: RequestInit): Headers { const headers = new Headers(options?.headers); if (!headers.has('Content-Type') && !(options?.body instanceof FormData)) { headers.set('Content-Type', 'application/json'); } return headers; } export async function apiRequest(path: string, options?: RequestInit): Promise { const res = await fetch(apiUrl(path), { ...options, headers: jsonHeaders(options), }); if (!res.ok) throw await toApiError(res); if (res.status === 204) return undefined as T; return res.json() as Promise; }