// Hono 4 on Deno.serve with an SSE stream + graceful shutdown. import { Hono } from "hono"; import { streamSSE } from "hono/streaming"; import { serveStatic } from "hono/deno"; import { redact } from "@spike/x"; export function createApp() { const app = new Hono(); app.get("/api/v1/health", (c) => c.json({ ok: true, runtime: `deno ${Deno.version.deno}`, ts: Deno.version.typescript })); app.get("/api/v1/redact", (c) => c.json(redact({ url: c.req.url, apiKey: "x" }) as Record)); app.get("/api/v1/events", (c) => streamSSE(c, async (stream) => { const after = Number(c.req.query("after") ?? 0); let aborted = false; stream.onAbort(() => { aborted = true; }); for (let i = after + 1; i <= after + 3 && !aborted; i++) { await stream.writeSSE({ id: String(i), event: "tick", data: JSON.stringify({ i }) }); await stream.sleep(100); } })); // Endless stream: used to check how Deno.serve shutdown treats open SSE connections. app.get("/api/v1/forever", (c) => streamSSE(c, async (stream) => { let i = 0; while (!stream.aborted && !shutdownSignal.aborted) { await stream.writeSSE({ id: String(++i), data: "ping" }); await stream.sleep(200); } })); // Single-container mode: serve the built SPA (react-router ssr:false) from WEB_ROOT with index.html fallback. const webRoot = Deno.env.get("WEB_ROOT"); if (webRoot) { app.use("/*", serveStatic({ root: webRoot })); app.get("*", serveStatic({ root: webRoot, path: "index.html" })); } return app; } /** App-level shutdown signal: SSE loops must observe it, otherwise graceful shutdown waits forever. */ export const shutdownCtl = new AbortController(); const shutdownSignal = shutdownCtl.signal; if (import.meta.main) { const port = Number(Deno.env.get("PORT") ?? 5199); const ac = new AbortController(); const server = Deno.serve({ port, hostname: Deno.env.get("HOST") ?? "127.0.0.1", signal: ac.signal, onListen: ({ port }) => console.log(`listening ${port}`) }, createApp().fetch); const stop = () => { console.log("shutdown"); if (Deno.env.get("CLOSE_SSE") !== "0") shutdownCtl.abort(); ac.abort(); }; Deno.addSignalListener("SIGTERM", stop); Deno.addSignalListener("SIGINT", stop); await server.finished; console.log("server finished"); }