// Thin fetch wrapper. Throws on non-2xx with a structured ApiError. export class ApiError extends Error { status: number; field?: string; constructor(status: number, message: string, field?: string) { super(message); this.status = status; this.field = field; } } const INGEST_BASE = import.meta.env.VITE_API_BASE_URL ?? '/api/ingest'; const ROTOR_BASE = import.meta.env.VITE_ROTOR_BASE_URL ?? '/api/rotor'; const BULKER_BASE = import.meta.env.VITE_BULKER_BASE_URL ?? '/api/bulker'; async function request(base: string, path: string, init?: RequestInit): Promise { const res = await fetch(`${base}${path}`, { ...init, headers: { 'content-type': 'application/json', ...(init?.headers ?? {}), }, }); const text = await res.text(); const data = text ? safeJSON(text) : undefined; if (!res.ok) { const msg = (data as { error?: string })?.error ?? res.statusText; const field = (data as { field?: string })?.field; throw new ApiError(res.status, msg, field); } return data as T; } function safeJSON(text: string): unknown { try { return JSON.parse(text); } catch { return text; } } // --------------------------------------------------------------------------- // Ingest API // --------------------------------------------------------------------------- export const ingest = { health: () => request<{ status: string }>(INGEST_BASE, '/health'), ready: () => request<{ status: string }>(INGEST_BASE, '/ready'), track: (writeKey: string, body: Record) => request<{ ok: boolean }>(INGEST_BASE, '/track', { method: 'POST', headers: { Authorization: `Bearer ${writeKey}` }, body: JSON.stringify(body), }), }; // --------------------------------------------------------------------------- // Rotor API // --------------------------------------------------------------------------- export interface RunRequest { code: string; event: Record; } export interface RunResponse { result: unknown; } export const rotor = { run: (body: RunRequest) => request(ROTOR_BASE, '/v1/run', { method: 'POST', body: JSON.stringify(body), }), upsert: (body: { workspace_id: string; slug: string; code: string }) => request<{ ok: boolean }>(ROTOR_BASE, '/v1/functions', { method: 'POST', body: JSON.stringify(body), }), }; // --------------------------------------------------------------------------- // Bulker API // --------------------------------------------------------------------------- export const bulker = { health: () => request<{ status: string }>(BULKER_BASE, '/health'), };