import { createServer } from "node:http"; export class HttpError extends Error { constructor(statusCode, body) { super(body?.message ?? body?.error ?? `HTTP ${statusCode}`); this.statusCode = statusCode; this.body = body; } } export function jsonResponse(res, statusCode, body) { const payload = JSON.stringify(body, null, 2); res.writeHead(statusCode, { "content-type": "application/json; charset=utf-8", "content-length": Buffer.byteLength(payload) }); res.end(payload); } export async function readJson(req) { const chunks = []; for await (const chunk of req) { chunks.push(chunk); } const raw = Buffer.concat(chunks).toString("utf8").trim(); if (!raw) { return {}; } return JSON.parse(raw); } export function createJsonServer({ routes }) { return createServer(async (req, res) => { try { const url = new URL(req.url ?? "/", "http://localhost"); const key = `${req.method ?? "GET"} ${url.pathname}`; const handler = routes.get(key); if (!handler) { jsonResponse(res, 404, { error: "not_found", path: url.pathname }); return; } const body = await handler({ req, url }); jsonResponse(res, 200, body); } catch (error) { if (error instanceof HttpError) { jsonResponse(res, error.statusCode, error.body); return; } if (Number.isInteger(error?.statusCode) && error?.body) { jsonResponse(res, error.statusCode, error.body); return; } if (error instanceof SyntaxError) { jsonResponse(res, 400, { error: "invalid_json", message: error.message }); return; } jsonResponse(res, 500, { error: "internal_error", message: error instanceof Error ? error.message : String(error) }); } }); } export async function fetchJson(url, options = {}) { const body = options.body === undefined ? undefined : JSON.stringify(options.body); const response = await fetch(url, { method: options.method ?? (body === undefined ? "GET" : "POST"), headers: { ...(body === undefined ? {} : { "content-type": "application/json" }), ...(options.headers ?? {}) }, body, signal: options.signal }); const text = await response.text(); const json = text ? JSON.parse(text) : null; return { ok: response.ok, status: response.status, statusText: response.statusText, body: json }; } export function parsePort(value, fallback) { const parsed = Number.parseInt(value ?? "", 10); if (Number.isInteger(parsed) && parsed > 0 && parsed < 65536) { return parsed; } return fallback; } export function listen(server, port, host = "0.0.0.0") { server.listen(port, host, () => { const address = server.address(); const bound = typeof address === "object" && address ? `${address.address}:${address.port}` : String(address); console.log(`listening on ${bound}`); }); }