/** * The process the forge runs. `serve` opens the database and listens: * every request gets the floor (`http.ts`), an identity (`who.ts`), the * door (`security.ts`) and one route (`routes.ts`) — so a request from * another site, from someone going too fast, or from someone the contract * turns away is refused before a byte of it is read. Run directly, it * serves what the forge said; a test calls `serve` with its own. */ import { openDb } from "./db.ts"; import { dataDir, identityKey, title, where } from "./env.ts"; import { html, text } from "./http.ts"; import { match } from "./routes.ts"; import { door } from "./security.ts"; import { who } from "./who.ts"; export interface ServeOptions { readonly port: number; readonly hostname?: string | undefined; readonly dataDir: string; readonly identityKey: string; } export function serve(options: ServeOptions) { const db = openDb(options.dataDir); return Bun.serve({ port: options.port, hostname: options.hostname ?? "127.0.0.1", // A connection that says nothing for a minute is dropped. The event // stream heartbeats every 25 seconds to stay well inside that. idleTimeout: 60, async fetch(request, server) { const url = new URL(request.url); const me = who(request, options.identityKey); const refused = door( db, request, url, me, server.requestIP(request)?.address ?? "?", ); if (refused !== undefined) return head(request, refused); const found = match( request.method === "HEAD" ? "GET" : request.method, url.pathname, ); if (found === undefined) return text("not found", 404); const response = await found.route.handle({ request, url, db, who: me, params: found.params, }); return head(request, response); }, // A bug answers with the floor and nothing else: never the runtime's // own error page, which carries source and paths, and never without // the headers. The detail goes to the log, where it belongs. error(error) { console.error(error); return html("