apps/glyph Public The code is public; the app answers only to 1 person who can change it and the crew.

Files draw
97 lines · 3.5 KB · ~1.0k tok Raw History
1
/**
2
 * The process the forge runs. `serve` opens the database and listens:
3
 * every request gets the floor (`http.ts`), an identity (`who.ts`), the
4
 * door (`security.ts`) and one route (`routes.ts`) — so a request from
5
 * another site, from someone going too fast, or from someone the contract
6
 * turns away is refused before a byte of it is read. Run directly, it
7
 * serves what the forge said; a test calls `serve` with its own.
8
 */
9
10
import { openDb } from "./db.ts";
11
import { drawerFromEnv, recover, setDrawer } from "./draw.ts";
12
import { dataDir, identityKey, title, where } from "./env.ts";
13
import { html, text } from "./http.ts";
14
import { serveSpan } from "./otel.ts";
15
import { match } from "./routes.ts";
16
import { door } from "./security.ts";
17
import { who } from "./who.ts";
18
19
export interface ServeOptions {
20
  readonly port: number;
21
  readonly hostname?: string | undefined;
22
  readonly dataDir: string;
23
  readonly identityKey: string;
24
}
25
26
export function serve(options: ServeOptions) {
27
  const db = openDb(options.dataDir);
28
  // A drawing the last process left half done will never finish: say so.
29
  recover(db);
30
  return Bun.serve({
31
    port: options.port,
32
    hostname: options.hostname ?? "127.0.0.1",
33
    // A connection that says nothing for a minute is dropped. The event
34
    // stream heartbeats every 25 seconds to stay well inside that.
35
    idleTimeout: 60,
36
    // Every request is a span (src/otel.ts), a child of the forge's: it
37
    // shows in the platform's traces with its route, status and timing.
38
    // Wrap slow work in `span("name", () => ...)` to see it there too.
39
    fetch: (request, server) =>
40
      serveSpan(request, async (span) => {
41
        const url = new URL(request.url);
42
        const me = who(request, options.identityKey);
43
        const refused = door(
44
          db,
45
          request,
46
          url,
47
          me,
48
          server.requestIP(request)?.address ?? "?",
49
        );
50
        if (refused !== undefined) return head(request, refused);
51
        const found = match(
52
          request.method === "HEAD" ? "GET" : request.method,
53
          url.pathname,
54
        );
55
        if (found === undefined) return text("not found", 404);
56
        span.set("http.route", found.route.path);
57
        const response = await found.route.handle({
58
          request,
59
          url,
60
          db,
61
          who: me,
62
          params: found.params,
63
        });
64
        return head(request, response);
65
      }),
66
    // A bug answers with the floor and nothing else: never the runtime's
67
    // own error page, which carries source and paths, and never without
68
    // the headers. The detail goes to the log, where it belongs.
69
    error(error) {
70
      // Inside the generated app, which has no forge log module.
71
      console.error(error);
72
      return html("<h1>Something went wrong</h1>", 500);
73
    },
74
  });
75
}
76
77
// HEAD is GET without the body: a health check sees the app up.
78
const head = (request: Request, response: Response): Response =>
79
  request.method === "HEAD"
80
    ? new Response(null, { status: response.status, headers: response.headers })
81
    : response;
82
83
if (import.meta.main) {
84
  // With no token the default drawer explains what to set; see src/draw.ts.
85
  const drawer = drawerFromEnv(dataDir);
86
  if (drawer !== undefined) setDrawer(drawer);
87
  else console.warn("CLAUDE_CODE_OAUTH_TOKEN is not set: drawings will ask for it");
88
  const server = serve({
89
    port: Number(process.env.PORT ?? 8080),
90
    hostname: process.env.HOST,
91
    dataDir,
92
    identityKey,
93
  });
94
  console.log(
95
    `${title} listening on http://${server.hostname}:${server.port} (${where})`,
96
  );
97
}
↑↓ navigate ↵ open esc close
↑↓ navigate ↵ open esc close

Keyboard shortcuts