/** * 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, forgeApi, identityKey, repoToken, title, where } from "./env.ts"; import { sessionWho } from "./accounts.ts"; import { html, readCookie, text } from "./http.ts"; import { SESSION_COOKIE } from "./routes.ts"; import { match } from "./routes.ts"; import { door } from "./security.ts"; import { httpForge, startSync, type Forge } from "./sync.ts"; import { who } from "./who.ts"; import { traceServe } from "./otel.ts"; export interface ServeOptions { readonly port: number; readonly hostname?: string | undefined; readonly dataDir: string; readonly identityKey: string; /** Where shared folders commit; without one there are none. */ readonly forge?: Forge | undefined; } export function serve(options: ServeOptions) { const db = openDb(options.dataDir); const stopSync = startSync(db, options.forge); const server = Bun.serve(traceServe({ 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); // The forge's signed word first; else the app's own session, if any. const signed = who(request, options.identityKey); const me = signed.role === "anon" ? sessionWho(db, readCookie(request, SESSION_COOKIE)) : signed; 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, forge: options.forge, }); 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("