tambi / src /lib /api-client.ts
mmt
Tambi β€” deploy to Hugging Face Spaces
fc04d53
Raw
History Blame Contribute Delete
2.12 kB
// @polsia:framework-owned β€” DO NOT EDIT. The ONLY data transport for client
// pages. Native fetch only. Do NOT add SWR/react-query/axios for app data β€”
// the data plane is apiFetch + zod contracts (see src/lib/contracts/). Client
// pages call apiFetch('/api/<resource>'); route handlers under src/app/api own
// the DB.
//
// Pass a zod `schema` to validate+parse the response so the typed result is
// PROVEN at runtime, not an unchecked cast β€” this is what catches client/server
// contract drift the tsc gate cannot. Import the schema from the shared
// contract for the resource (single source of truth shared with the handler).
import type { ZodType } from 'zod';
const BASE = process.env.NEXT_PUBLIC_API_URL ?? '';
type ApiFetchOptions<T> = RequestInit & {
// When provided, the JSON body is run through `schema.parse` on success so
// the returned value is a validated `T` rather than an unchecked cast.
schema?: ZodType<T>;
};
// Overload 1: no schema β€” unchecked cast (use only when there is no contract).
export async function apiFetch<T>(path: string, init?: RequestInit): Promise<T>;
// Overload 2: with schema β€” parsed + validated, T is inferred from the schema.
export async function apiFetch<T>(
path: string,
options: ApiFetchOptions<T> & { schema: ZodType<T> },
): Promise<T>;
export async function apiFetch<T>(path: string, options?: ApiFetchOptions<T>): Promise<T> {
const { schema, ...init } = options ?? {};
const headers = new Headers(init.headers);
headers.set('content-type', 'application/json');
// future: const token = ...; headers.set('Authorization', `Bearer ${token}`);
// Token-ready seam β€” NOT implemented now. No cookies, no CSRF.
const res = await fetch(`${BASE}${path}`, { ...init, headers });
const body = await res.json().catch(() => null);
if (!res.ok) {
throw new Error(`apiFetch ${path} failed (${res.status})`, { cause: body });
}
// schema.parse throws ZodError on drift β€” surfaces the mismatch instead of
// letting a wrong shape flow silently into typed UI code.
return schema ? schema.parse(body) : (body as T);
}