/** /api/health, /api/ready, /api/metrics — no cache, no envelope. */ import type { FastifyInstance } from "fastify"; import { performance, type EventLoopUtilization } from "node:perf_hooks"; import { pg } from "../lib/sql.js"; import { metrics } from "../metrics.js"; import { cacheStats } from "../cache.js"; import { redisPing } from "../redis.js"; export const API_VERSION = "0.1.0"; export async function systemRoutes(app: FastifyInstance): Promise { app.get("/api/health", { schema: { summary: "Liveness (fast, no I/O)", tags: ["system"] } }, async (_req, reply) => { reply.header("cache-control", "no-store"); return { ok: true, service: "dci-api", version: API_VERSION, uptimeSec: Math.round(process.uptime()), now: new Date().toISOString() }; }); app.get("/api/ready", { schema: { summary: "Readiness: Postgres ping (Redis reported, not required)", tags: ["system"] } }, async (_req, reply) => { reply.header("cache-control", "no-store"); const started = Date.now(); let db = false; let dbError: string | null = null; try { const sql = pg(); await Promise.race([sql`select 1`, new Promise((_, rej) => setTimeout(() => rej(new Error("db timeout")), 3_000))]); db = true; } catch (e) { dbError = (e as Error).message; } const redis = await redisPing(); const body = { ok: db, db, dbError, redis, dbLatencyMs: Date.now() - started, now: new Date().toISOString() }; return reply.code(db ? 200 : 503).send(body); }); app.get("/api/metrics", { schema: { summary: "Prometheus metrics", tags: ["system"] } }, async (_req, reply) => { const cs = cacheStats(); const poolMax = Number(process.env.PG_POOL_MAX ?? 16); reply.header("cache-control", "no-store"); reply.type("text/plain; version=0.0.4; charset=utf-8"); return metrics.render({ dci_api_cache_memory_entries: cs.entries, dci_api_cache_memory_max: cs.max, dci_api_db_pool_max: poolMax, dci_api_process_rss_bytes: process.memoryUsage().rss, dci_api_event_loop_utilization: Math.round(performanceUtilization() * 1000) / 1000 }); }); } let lastElu: EventLoopUtilization | null = null; function performanceUtilization(): number { const cur = lastElu ? performance.eventLoopUtilization(lastElu) : performance.eventLoopUtilization(); lastElu = performance.eventLoopUtilization(); return cur.utilization; }