Files
geointel/frontend/src/services/api/client.ts
T
Codex 7adcdd76c7
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled
Align API error envelopes
2026-06-18 02:04:31 +02:00

68 lines
2.1 KiB
TypeScript

const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "";
export function apiUrl(path: string): string {
return `${API_BASE_URL}${path}`;
}
export class ApiHttpError extends Error {
readonly code: string;
readonly details?: unknown;
constructor(message: string, code = "REQUEST_ERROR", details?: unknown) {
super(message);
this.name = "ApiHttpError";
this.code = code;
this.details = details;
}
}
async function parseResponse<T>(response: Response): Promise<T> {
const payload = await response.json().catch(() => ({}));
if (!response.ok) {
const legacyError = typeof payload?.error === "object" && payload?.error !== null ? payload.error : null;
const code = typeof payload?.error === "string" ? payload.error : legacyError?.code ?? "REQUEST_ERROR";
const message = payload?.message ?? legacyError?.message ?? `Request failed (${response.status})`;
const details = payload?.details ?? legacyError?.details;
throw new ApiHttpError(message, code, details);
}
return payload.data as T;
}
export async function apiGet<T>(path: string): Promise<T> {
const response = await fetch(apiUrl(path));
return parseResponse<T>(response);
}
export async function apiPost<T>(path: string, body?: object): Promise<T> {
const response = await fetch(apiUrl(path), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined,
});
return parseResponse<T>(response);
}
export async function apiPatch<T>(path: string, body?: object): Promise<T> {
const response = await fetch(apiUrl(path), {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: body ? JSON.stringify(body) : undefined,
});
return parseResponse<T>(response);
}
export async function apiDelete<T>(path: string): Promise<T> {
const response = await fetch(apiUrl(path), {
method: "DELETE",
});
return parseResponse<T>(response);
}
export async function apiMultipart<T>(path: string, form: FormData): Promise<T> {
const response = await fetch(apiUrl(path), {
method: "POST",
body: form,
});
return parseResponse<T>(response);
}