/** * Minimal HTTP server shared by the worker (`main.ts`) and the standalone scheduler (`scheduler.ts`): * GET /healthz → JSON from `health()`; 503 while `ready()` is false (shutting down / not started) * GET /metrics → Prometheus text from prom.ts after `beforeScrape()` refreshed the gauges * Never throws into the caller; errors become 500 JSON responses. */ import { createServer, type Server } from "node:http"; import { renderMetrics } from "./prom.js"; export interface HealthHttpOptions { port: number; role: string; health: () => Promise>; ready: () => boolean; /** refresh snapshot gauges (queue depth, budgets…) right before rendering */ beforeScrape?: () => Promise; log?: (msg: string) => void; /** extra JSON routes keyed by path prefix ("/trace" matches "/trace/") */ routes?: Record Promise<{ status: number; body: unknown }>>; } export function startHealthHttp(o: HealthHttpOptions): Server { const log = o.log ?? ((m: string) => console.error(`[${o.role}] ${m}`)); const server = createServer(async (req, res) => { try { const url = new URL(req.url ?? "/", "http://localhost"); if (url.pathname === "/healthz" || url.pathname === "/") { const body = await o.health(); const ok = o.ready(); res.writeHead(ok ? 200 : 503, { "content-type": "application/json", "cache-control": "no-store" }); res.end(JSON.stringify({ ok, role: o.role, ...body }, null, 2)); return; } if (url.pathname === "/metrics") { if (o.beforeScrape) await o.beforeScrape().catch((e: Error) => log(`metrics refresh failed: ${e.message}`)); res.writeHead(200, { "content-type": "text/plain; version=0.0.4; charset=utf-8", "cache-control": "no-store" }); res.end(renderMetrics()); return; } for (const [prefix, handler] of Object.entries(o.routes ?? {})) { if (url.pathname === prefix || url.pathname.startsWith(`${prefix}/`)) { const r = await handler(url); res.writeHead(r.status, { "content-type": "application/json", "cache-control": "no-store" }); res.end(JSON.stringify(r.body)); return; } } res.writeHead(404, { "content-type": "application/json" }); res.end(JSON.stringify({ error: "not found" })); } catch (e) { res.writeHead(500, { "content-type": "application/json" }); res.end(JSON.stringify({ error: (e as Error).message })); } }); server.on("error", (e) => log(`http error: ${e.message}`)); server.listen(o.port, "0.0.0.0", () => log(`http :${o.port} (/healthz, /metrics)`)); return server; }