export interface ApiRequest { method?: string; params?: Record; query?: Record; body?: Input; } export interface ApiClientOptions { baseUrl?: string; fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise; } /** Runtime used by generated API clients; throws a typed response error on non-2xx results. */ export function createApiClient(options: ApiClientOptions = {}) { const fetcher = options.fetch ?? globalThis.fetch; return async function call( template: string, request: ApiRequest = {}, ): Promise { let path = template; for (const [name, value] of Object.entries(request.params ?? {})) { path = path.replace(`[${name}]`, encodeURIComponent(String(value))); } const url = new URL(path, options.baseUrl ?? globalThis.location?.origin ?? "http://localhost"); for (const [name, value] of Object.entries(request.query ?? {})) if (value !== undefined) url.searchParams.set(name, String(value)); const response = await fetcher(url, { method: request.method ?? (request.body === undefined ? "GET" : "POST"), credentials: "same-origin", headers: request.body === undefined ? undefined : { "content-type": "application/json" }, body: request.body === undefined ? undefined : JSON.stringify(request.body), }); const data = response.status === 204 ? undefined : await response.json(); if (!response.ok) throw Object.assign( new Error((data as { error?: string })?.error ?? `HTTP ${response.status}`), { status: response.status, data }, ); return data as Output; }; }