/** * Shared API client targeting Query API, Symbol Registry, and Risk Engine. * Base URLs are configurable via env vars (VITE_*). */ const QUERY_API_BASE = import.meta.env.VITE_QUERY_API_URL ?? ''; const SYMBOL_REGISTRY_BASE = import.meta.env.VITE_SYMBOL_REGISTRY_URL ?? '/registry'; const RISK_ENGINE_BASE = import.meta.env.VITE_RISK_ENGINE_URL ?? '/risk'; export type ApiBase = 'query' | 'registry' | 'risk'; function baseUrl(api: ApiBase): string { switch (api) { case 'query': return QUERY_API_BASE; case 'registry': return SYMBOL_REGISTRY_BASE; case 'risk': return RISK_ENGINE_BASE; } } export class ApiError extends Error { status: number; body: unknown; constructor(status: number, body: unknown) { super(`API error ${status}`); this.name = 'ApiError'; this.status = status; this.body = body; } } export async function apiFetch( api: ApiBase, path: string, init?: RequestInit, ): Promise { const url = `${baseUrl(api)}${path}`; const res = await fetch(url, { ...init, headers: { 'Content-Type': 'application/json', ...init?.headers, }, }); if (!res.ok) { const body = await res.json().catch(() => null); throw new ApiError(res.status, body); } return res.json() as Promise; } /** Convenience GET */ export function apiGet(api: ApiBase, path: string): Promise { return apiFetch(api, path); } /** Convenience POST */ export function apiPost(api: ApiBase, path: string, body: unknown): Promise { return apiFetch(api, path, { method: 'POST', body: JSON.stringify(body), }); } /** Convenience PUT */ export function apiPut(api: ApiBase, path: string, body: unknown): Promise { return apiFetch(api, path, { method: 'PUT', body: JSON.stringify(body), }); } /** Convenience DELETE */ export function apiDelete(api: ApiBase, path: string): Promise { return apiFetch(api, path, { method: 'DELETE' }); }