84 lines
2.2 KiB
TypeScript

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<typeof createOpenApiClient<paths>>;
export type AuthHeaderResolver = () => string | undefined;
export interface CreateClientOptions {
getAuthHeader?: AuthHeaderResolver;
handleError?: (response: Response) => void | Promise<void>;
}
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<paths>({ baseUrl });
const handleError = options.handleError ?? defaultHandleError;
if (typeof client.use === "function") {
const resolveAuthHeader = options.getAuthHeader;
client.use({
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;
request.headers.set("Authorization", headerValue);
},
async onResponse({ response }) {
await handleError(response);
},
} as never);
}
return client;
}
export type { paths };