75 lines
2.1 KiB
TypeScript
75 lines
2.1 KiB
TypeScript
import type { ApiEnvelope } from "@/types/api";
|
|
|
|
export class ApiError extends Error {
|
|
status: number;
|
|
body: string;
|
|
errors: unknown[];
|
|
|
|
constructor(message: string, status: number, body: string, errors: unknown[] = []) {
|
|
super(message);
|
|
this.name = "ApiError";
|
|
this.status = status;
|
|
this.body = body;
|
|
this.errors = errors;
|
|
}
|
|
}
|
|
|
|
export async function requestJson<T>(input: RequestInfo | URL, init?: RequestInit): Promise<T> {
|
|
const res = await fetch(input, init);
|
|
const payload = await parseJsonResponse(res);
|
|
const envelope = isApiEnvelope<T>(payload) ? payload : null;
|
|
|
|
if (!res.ok) {
|
|
const message = envelope?.message || `Request failed with status ${res.status}`;
|
|
const body = envelope ? message : stringifyPayload(payload);
|
|
throw new ApiError(message, res.status, body, envelope?.errors || []);
|
|
}
|
|
|
|
if (envelope) {
|
|
if (envelope.status === "error") {
|
|
throw new ApiError(envelope.message || "Request failed", res.status, JSON.stringify(envelope), envelope.errors);
|
|
}
|
|
return envelope.data;
|
|
}
|
|
|
|
return payload as T;
|
|
}
|
|
|
|
export function jsonRequestInit(method: string, body: unknown): RequestInit {
|
|
return {
|
|
method,
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(body),
|
|
};
|
|
}
|
|
|
|
async function parseJsonResponse(res: Response): Promise<unknown> {
|
|
const text = await res.text();
|
|
if (!text.length) return null;
|
|
try {
|
|
return JSON.parse(text);
|
|
} catch {
|
|
return text;
|
|
}
|
|
}
|
|
|
|
function isApiEnvelope<T>(value: unknown): value is ApiEnvelope<T> {
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
const source = value as Record<string, unknown>;
|
|
return (
|
|
"status" in source &&
|
|
"data" in source &&
|
|
"message" in source &&
|
|
"errors" in source
|
|
);
|
|
}
|
|
|
|
function stringifyPayload(payload: unknown): string {
|
|
if (typeof payload === "string") return payload;
|
|
try {
|
|
return JSON.stringify(payload);
|
|
} catch {
|
|
return String(payload);
|
|
}
|
|
}
|