import 'server-only'; import { notFound } from 'next/navigation'; /** * Server-side API client. Server components talk to the FastAPI service directly (internal URL), never through the * browser rewrite. Every call is `no-store`: pages are dynamic and render the live state at request time. */ const BASE = process.env.API_URL_INTERNAL ?? process.env.API_URL ?? 'http://127.0.0.1:8352'; export class ApiError extends Error { constructor( public status: number, public path: string, ) { super(`API ${status} for ${path}`); } } export async function apiGet(path: string, init?: { timeoutMs?: number }): Promise { const ctrl = new AbortController(); const timer = setTimeout(() => ctrl.abort(), init?.timeoutMs ?? 8000); try { const res = await fetch(BASE + path, { cache: 'no-store', signal: ctrl.signal, headers: { accept: 'application/json' } }); if (res.status === 404) notFound(); if (!res.ok) throw new ApiError(res.status, path); return (await res.json()) as T; } finally { clearTimeout(timer); } } /** Like apiGet but returns null instead of throwing when the API is unreachable/erroring (used for optional panels). */ export async function apiTry(path: string): Promise { try { return await apiGet(path); } catch (e) { if (e && typeof e === 'object' && 'digest' in e && String((e as { digest?: string }).digest).startsWith('NEXT_HTTP_ERROR_FALLBACK')) throw e; return null; } }