53 lines
2.3 KiB
TypeScript
53 lines
2.3 KiB
TypeScript
// 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<string, unknown>));
|
|
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");
|
|
}
|