"use strict"; const http = require("node:http"); const { buildApp } = require("./app"); const { config } = require("./config"); const { ConvexStateStore } = require("./lib/convex-state-store"); const { InMemoryStateStore } = require("./lib/state-store"); const { createLogger } = require("./lib/logger"); function readBody(req) { return new Promise((resolve, reject) => { let data = ""; req.setEncoding("utf8"); req.on("data", (chunk) => { data += chunk; if (data.length > 2_000_000) { reject(new Error("payload_too_large")); } }); req.on("end", () => resolve(data)); req.on("error", reject); }); } function normalizeHeaders(rawHeaders) { return Object.fromEntries( Object.entries(rawHeaders || {}).map(([k, v]) => [ String(k).toLowerCase(), Array.isArray(v) ? v.join(",") : (v || ""), ]), ); } function mapToAppRequest({ req, rawBody }) { const url = new URL(req.url, "http://localhost"); const query = Object.fromEntries(url.searchParams.entries()); return { method: req.method || "GET", path: url.pathname, query, headers: normalizeHeaders(req.headers), rawBody, }; } function createHttpServer({ app }) { return http.createServer(async (req, res) => { try { const rawBody = await readBody(req); const response = await app.handleRequest(mapToAppRequest({ req, rawBody })); res.statusCode = response.status; for (const [key, value] of Object.entries(response.headers || {})) { res.setHeader(key, value); } res.end(response.body || ""); } catch (error) { res.statusCode = 500; res.setHeader("content-type", "application/json; charset=utf-8"); res.end(JSON.stringify({ error: error.message || "internal_error" })); } }); } function createMutationPersister({ stateStore, logger = console }) { let queue = Promise.resolve(); let lastError = null; return { enqueue(state) { queue = queue .then( () => stateStore.save(state), () => stateStore.save(state), ) .catch((error) => { lastError = error; logger.error({ err: error }, "failed to persist state"); throw error; }); return queue; }, flush() { return queue; }, getLastError() { return lastError; }, }; } async function createRuntime({ runtimeConfig = config, logger = console, stateStore = null } = {}) { let effectiveStateStore = stateStore || new ConvexStateStore({ deploymentUrl: runtimeConfig.convexDeploymentUrl, authToken: runtimeConfig.convexAuthToken, readFunction: runtimeConfig.convexStateQuery, writeFunction: runtimeConfig.convexStateMutation, }); let initialState; try { initialState = await effectiveStateStore.load(); } catch (error) { const allowFallback = runtimeConfig.allowInMemoryStateFallback !== undefined ? Boolean(runtimeConfig.allowInMemoryStateFallback) : true; if (!allowFallback) { throw new Error("state_store_unavailable_without_fallback", { cause: error }); } logger.warn({ err: error }, "failed to initialize configured state store; falling back to in-memory state"); effectiveStateStore = new InMemoryStateStore(); initialState = await effectiveStateStore.load(); } const persister = createMutationPersister({ stateStore: effectiveStateStore, logger }); const app = buildApp({ config: runtimeConfig, initialState, logger, onMutation(state) { void persister.enqueue(state); }, }); const server = createHttpServer({ app }); return { app, server, persister, }; } async function start() { const logger = createLogger({ level: config.logLevel, name: "xartaudio", }); const runtime = await createRuntime({ runtimeConfig: config, logger }); const { server, persister } = runtime; let shuttingDown = false; async function shutdown(signal) { if (shuttingDown) { return; } shuttingDown = true; logger.info({ signal }, "received shutdown signal"); server.close(); await persister.flush(); } process.on("SIGTERM", () => { void shutdown("SIGTERM"); }); process.on("SIGINT", () => { void shutdown("SIGINT"); }); server.listen(config.port, () => { logger.info({ port: config.port }, "xartaudio server listening"); }); } if (require.main === module) { start().catch((error) => { const logger = createLogger({ level: config.logLevel, name: "xartaudio", }); logger.error({ err: error }, "failed to start server"); process.exitCode = 1; }); } module.exports = { mapToAppRequest, normalizeHeaders, createHttpServer, createMutationPersister, createRuntime, start, };