Reference implementation for the Phoenix Architecture. Work in progress. aicoding.leaflet.pub/
ai coding crazy
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 123 lines 4.4 kB view raw
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}