import type { Socket } from "node:net"; import { loadRootEnv } from "@rakazo/core/node/load-root-env"; loadRootEnv(); import { serve } from "@hono/node-server"; import { SERVICE_NAMES } from "@rakazo/logging"; import { createRootLogger } from "@rakazo/logging/axiom"; import { createApp } from "./app.js"; import { API_SERVER_OPTIONS } from "./artifact-http.js"; import { loadEnv } from "./env.js"; const logger = createRootLogger(SERVICE_NAMES.api); try { const env = loadEnv(); const { app, stop } = await createApp({ ...env, logger }); const server = serve( { fetch: app.fetch, port: env.port, hostname: env.apiHost, serverOptions: API_SERVER_OPTIONS, }, () => { logger.info("api listening", { "http.host": env.apiHost, "http.port": env.port }); }, ); // Long-lived connections (threads.subscribe SSE streams) never end on their // own, so server.close() alone waits forever for them. Track sockets and // force-close any still open after a short grace period for in-flight // requests, or every restart/shutdown hangs until something force-kills it. const sockets = new Set(); server.on("connection", (socket) => { sockets.add(socket); socket.once("close", () => sockets.delete(socket)); }); let stopping = false; const shutdown = async () => { if (stopping) return; stopping = true; const closed = new Promise((resolve) => server.close(() => resolve())); const grace = setTimeout(() => { for (const socket of sockets) socket.destroy(); }, 2_000); await closed; clearTimeout(grace); await stop(); await logger.flush({ timeoutMs: 2_000 }); }; process.once("SIGTERM", () => void shutdown()); process.once("SIGINT", () => void shutdown()); } catch (error) { logger.error("api startup failed", error); await logger.flush({ timeoutMs: 2_000 }); process.exit(1); }