import type { ApiEnvelope } from "@/types/api"; export class ApiError extends Error { status: number; body: string; errors: unknown[]; constructor(message: string, status: number, body: string, errors: unknown[] = []) { super(message); this.name = "ApiError"; this.status = status; this.body = body; this.errors = errors; } } export async function requestJson(input: RequestInfo | URL, init?: RequestInit): Promise { const res = await fetch(input, init); const payload = await parseJsonResponse(res); const envelope = isApiEnvelope(payload) ? payload : null; if (!res.ok) { const message = envelope?.message || `Request failed with status ${res.status}`; const body = envelope ? message : stringifyPayload(payload); throw new ApiError(message, res.status, body, envelope?.errors || []); } if (envelope) { if (envelope.status === "error") { throw new ApiError(envelope.message || "Request failed", res.status, JSON.stringify(envelope), envelope.errors); } return envelope.data; } return payload as T; } export function jsonRequestInit(method: string, body: unknown): RequestInit { return { method, headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }; } async function parseJsonResponse(res: Response): Promise { const text = await res.text(); if (!text.length) return null; try { return JSON.parse(text); } catch { return text; } } function isApiEnvelope(value: unknown): value is ApiEnvelope { if (!value || typeof value !== "object" || Array.isArray(value)) return false; const source = value as Record; return ( "status" in source && "data" in source && "message" in source && "errors" in source ); } function stringifyPayload(payload: unknown): string { if (typeof payload === "string") return payload; try { return JSON.stringify(payload); } catch { return String(payload); } }