52 lines
1.4 KiB
TypeScript
52 lines
1.4 KiB
TypeScript
const apiBase = (): string => import.meta.env.PUBLIC_API_URL || '';
|
|
|
|
export function apiUrl(path: string): string {
|
|
return path.startsWith('http') ? path : `${apiBase()}${path}`;
|
|
}
|
|
|
|
export class ApiError extends Error {
|
|
status: number;
|
|
code?: string;
|
|
detail?: string;
|
|
|
|
constructor(status: number, title: string, code?: string, detail?: string) {
|
|
super(title);
|
|
this.name = 'ApiError';
|
|
this.status = status;
|
|
this.code = code;
|
|
this.detail = detail;
|
|
}
|
|
}
|
|
|
|
export async function toApiError(res: Response): Promise<ApiError> {
|
|
try {
|
|
const body = await res.json();
|
|
return new ApiError(
|
|
res.status,
|
|
body.title || body.detail || `Request failed (${res.status})`,
|
|
body.code,
|
|
body.detail,
|
|
);
|
|
} catch {
|
|
return new ApiError(res.status, `Request failed (${res.status})`);
|
|
}
|
|
}
|
|
|
|
export function jsonHeaders(options?: RequestInit): Headers {
|
|
const headers = new Headers(options?.headers);
|
|
if (!headers.has('Content-Type') && !(options?.body instanceof FormData)) {
|
|
headers.set('Content-Type', 'application/json');
|
|
}
|
|
return headers;
|
|
}
|
|
|
|
export async function apiRequest<T>(path: string, options?: RequestInit): Promise<T> {
|
|
const res = await fetch(apiUrl(path), {
|
|
...options,
|
|
headers: jsonHeaders(options),
|
|
});
|
|
if (!res.ok) throw await toApiError(res);
|
|
if (res.status === 204) return undefined as T;
|
|
return res.json() as Promise<T>;
|
|
}
|