Reference implementation for the Phoenix Architecture. Work in progress.
aicoding.leaflet.pub/
ai
coding
crazy
1/**
2 * Game — HTTP Server
3 *
4 * AUTO-GENERATED by Phoenix VCS
5 * Provides health check, metrics, and module endpoints.
6 */
7
8import { createServer, IncomingMessage, ServerResponse } from 'node:http';
9
10import * as grid from './grid.js';
11import * as painting from './painting.js';
12import * as scoringAndRounds from './scoring-and-rounds.js';
13
14// ─── Metrics ─────────────────────────────────────────────────────────────────
15
16const _svcMetrics = {
17 requests_total: 0,
18 requests_by_path: {} as Record<string, number>,
19 errors_total: 0,
20 uptime_start: Date.now(),
21};
22
23// ─── Module Registry ─────────────────────────────────────────────────────────
24
25const _svcModules = {
26 'grid': grid,
27 'painting': painting,
28 'scoring-and-rounds': scoringAndRounds,
29};
30
31// ─── Router ──────────────────────────────────────────────────────────────────
32
33type Handler = (req: IncomingMessage, res: ServerResponse) => void | Promise<void>;
34
35const routes: Record<string, Handler> = {
36 '/health': (_req, res) => {
37 res.writeHead(200, { 'Content-Type': 'application/json' });
38 res.end(JSON.stringify({
39 status: 'ok',
40 service: 'Game',
41 uptime: Math.floor((Date.now() - _svcMetrics.uptime_start) / 1000),
42 modules: Object.keys(_svcModules),
43 }));
44 },
45
46 '/metrics': (_req, res) => {
47 res.writeHead(200, { 'Content-Type': 'application/json' });
48 res.end(JSON.stringify({
49 ..._svcMetrics,
50 uptime_seconds: Math.floor((Date.now() - _svcMetrics.uptime_start) / 1000),
51 }, null, 2));
52 },
53
54 '/modules': (_req, res) => {
55 const info = Object.entries(_svcModules).map(([name, mod]) => {
56 const phoenix = (mod as Record<string, unknown>)._phoenix as Record<string, unknown> | undefined;
57 return {
58 name,
59 risk_tier: phoenix?.risk_tier ?? 'unknown',
60 exports: Object.keys(mod).filter(k => k !== '_phoenix'),
61 };
62 });
63 res.writeHead(200, { 'Content-Type': 'application/json' });
64 res.end(JSON.stringify(info, null, 2));
65 },
66};
67
68// ─── Server ──────────────────────────────────────────────────────────────────
69
70function handleRequest(req: IncomingMessage, res: ServerResponse): void {
71 const url = req.url ?? '/';
72 const path = url.split('?')[0];
73
74 _svcMetrics.requests_total++;
75 _svcMetrics.requests_by_path[path] = (_svcMetrics.requests_by_path[path] ?? 0) + 1;
76
77 const handler = routes[path];
78 if (handler) {
79 try {
80 handler(req, res);
81 } catch (err) {
82 _svcMetrics.errors_total++;
83 res.writeHead(500, { 'Content-Type': 'application/json' });
84 res.end(JSON.stringify({ error: String(err) }));
85 }
86 } else {
87 res.writeHead(404, { 'Content-Type': 'application/json' });
88 res.end(JSON.stringify({
89 error: 'Not Found',
90 path,
91 available: Object.keys(routes),
92 }));
93 }
94}
95
96export function startServer(port?: number): { server: ReturnType<typeof createServer>; port: number; ready: Promise<void> } {
97 const requestedPort = port ?? parseInt(process.env.GAME_PORT ?? process.env.PORT ?? '3000', 10);
98 const server = createServer(handleRequest);
99 let actualPort = requestedPort;
100
101 const ready = new Promise<void>(resolve => {
102 server.listen(requestedPort, () => {
103 const addr = server.address();
104 if (addr && typeof addr === 'object') actualPort = addr.port;
105 result.port = actualPort;
106 console.log(`Game listening on http://localhost:${actualPort}`);
107 console.log(` /health — health check`);
108 console.log(` /metrics — request metrics`);
109 console.log(` /modules — registered modules`);
110 resolve();
111 });
112 });
113
114 const result = { server, port: actualPort, ready };
115 return result;
116}
117
118// Start when run directly
119const isMain = process.argv[1]?.endsWith('/game/server.js') ||
120 process.argv[1]?.endsWith('/game/server.ts');
121if (isMain) {
122 startServer();
123}