spb/internetpressure
Public
TypeScript 36.3%
Python 31.8%
Go 18%
JavaScript 9.8%
Shell 1.9%
SQL 1.4%
CSS 0.5%
1import 'server-only';2import { notFound } from 'next/navigation';34/**5 * Server-side API client. Server components talk to the FastAPI service directly (internal URL), never through the6 * browser rewrite. Every call is `no-store`: pages are dynamic and render the live state at request time.7 */8const BASE = process.env.API_URL_INTERNAL ?? process.env.API_URL ?? 'http://127.0.0.1:8352';910export class ApiError extends Error {11 constructor(12 public status: number,13 public path: string,14 ) {15 super(`API ${status} for ${path}`);16 }17}1819export async function apiGet<T>(path: string, init?: { timeoutMs?: number }): Promise<T> {20 const ctrl = new AbortController();21 const timer = setTimeout(() => ctrl.abort(), init?.timeoutMs ?? 8000);22 try {23 const res = await fetch(BASE + path, { cache: 'no-store', signal: ctrl.signal, headers: { accept: 'application/json' } });24 if (res.status === 404) notFound();25 if (!res.ok) throw new ApiError(res.status, path);26 return (await res.json()) as T;27 } finally {28 clearTimeout(timer);29 }30}3132/** Like apiGet but returns null instead of throwing when the API is unreachable/erroring (used for optional panels). */33export async function apiTry<T>(path: string): Promise<T | null> {34 try {35 return await apiGet<T>(path);36 } catch (e) {37 if (e && typeof e === 'object' && 'digest' in e && String((e as { digest?: string }).digest).startsWith('NEXT_HTTP_ERROR_FALLBACK')) throw e;38 return null;39 }40}41