42 lines
1.7 KiB
TypeScript
42 lines
1.7 KiB
TypeScript
export interface ApiRequest<Input = unknown> {
|
|
method?: string;
|
|
params?: Record<string, string | number>;
|
|
query?: Record<string, string | number | boolean | undefined>;
|
|
body?: Input;
|
|
}
|
|
|
|
export interface ApiClientOptions {
|
|
baseUrl?: string;
|
|
fetch?: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
|
|
}
|
|
|
|
/** 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<Output, Input = unknown>(
|
|
template: string,
|
|
request: ApiRequest<Input> = {},
|
|
): Promise<Output> {
|
|
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;
|
|
};
|
|
}
|