diff --git a/packages/api-client/src/runtime/client.ts b/packages/api-client/src/runtime/client.ts index 21f33a1c..5f4fbeb0 100644 --- a/packages/api-client/src/runtime/client.ts +++ b/packages/api-client/src/runtime/client.ts @@ -1,46 +1,83 @@ import createOpenApiClient from "openapi-fetch"; import type { paths } from "../__generated__/types"; +export class ApiError extends Error { + constructor( + message: string, + public readonly response: Response, + public readonly body?: unknown + ) { + super(message); + this.name = "ApiError"; + } +} + export type ApiClient = ReturnType>; export type AuthHeaderResolver = () => string | undefined; export interface CreateClientOptions { getAuthHeader?: AuthHeaderResolver; + handleError?: (response: Response) => void | Promise; +} + +async function defaultHandleError(response: Response) { + if (response.ok) return; + + let body: unknown; + let message = response.statusText || `Request failed with status ${response.status}`; + + try { + const cloned = response.clone(); + const contentType = cloned.headers.get("content-type"); + if (contentType?.includes("application/json")) { + body = await cloned.json(); + if (body && typeof body === "object" && "message" in body && typeof (body as any).message === "string") { + message = (body as any).message; + } + } else { + const text = await cloned.text(); + if (text) { + body = text; + message = text; + } + } + } catch { + // Ignore body parse errors; fall back to status text + } + + throw new ApiError(message, response, body); } export function createClient( baseUrl: string, options: CreateClientOptions = {} ): ApiClient { - const client = createOpenApiClient({ - baseUrl, - throwOnError: true, - }); + const client = createOpenApiClient({ baseUrl }); + const handleError = options.handleError ?? defaultHandleError; - if (typeof client.use === "function" && options.getAuthHeader) { + if (typeof client.use === "function") { const resolveAuthHeader = options.getAuthHeader; client.use({ - onRequest({ request }: { request: Request }) { - if (!request || typeof request.headers?.has !== "function") { - return; - } - - if (request.headers.has("Authorization")) { - return; - } + onRequest({ request }) { + if (!resolveAuthHeader) return; + if (!request || typeof request.headers?.has !== "function") return; + if (request.headers.has("Authorization")) return; const headerValue = resolveAuthHeader(); - if (!headerValue) { - return; - } + if (!headerValue) return; request.headers.set("Authorization", headerValue); }, + async onResponse({ response }) { + await handleError(response); + }, } as never); } return client; } + +export type { paths };