/** * @elly/core entrypoint — Phase 2 boot. * * Lifecycle: * 1. Load + validate `config.toml` (Zod). * 2. Load + validate environment (Zod). * 3. Build the root structured logger. * 4. Build the DI container (DB, migrations, KV, bus, HTTP server). * 5. Start the IPC HTTP server. * 6. Publish `server.ready` on the domain bus. * 7. Block on SIGINT/SIGTERM, then gracefully shut down the container. */ import { ConfigError, ConfigValidationError, CoreEnvSchema, createLogger, EnvValidationError, loadConfig, loadEnv, type Config, type CoreEnv, type Logger, } from "@elly/shared"; import { buildContainer, type CoreContainer } from "./container.ts"; const CONFIG_PATH = Deno.env.get("CONFIG_PATH") ?? "./config.toml"; const LOGGER_NAME = "@elly/core"; const VERSION = "0.1.0"; async function main(): Promise { const config = await loadConfigOrExit(); const env = loadEnvOrExit(); const logger = buildLogger(config, env); logger.info("phase 2 boot starting", { crate: "core", nodeEnv: env.NODE_ENV, version: VERSION, }); let container: CoreContainer | null = null; try { container = await buildContainer({ config, env, logger, version: VERSION }); } catch (err) { logger.fatal("container build failed", { err: err instanceof Error ? err : new Error(String(err)), }); await logger.flush(); Deno.exit(1); } installSignalHandlers(container, logger); try { await container.http.start(); } catch (err) { logger.fatal("ipc server failed to start", { err: err instanceof Error ? err : new Error(String(err)), }); await container.shutdown(); await logger.flush(); Deno.exit(1); } container.bus.publish({ type: "server.ready", payload: { version: VERSION, pid: Deno.pid }, }); logger.info("phase 2 boot complete", { pid: Deno.pid, ipc: `${config.ipc.host}:${config.ipc.port}`, }); // Hold the process open until a signal handler tears the container down. await new Promise(() => {}); } // ===================================================================== // Boot helpers // ===================================================================== async function loadConfigOrExit(): Promise { try { return await loadConfig(CONFIG_PATH); } catch (err) { bootFail(err, "config"); } } function loadEnvOrExit(): CoreEnv { try { return loadEnv(CoreEnvSchema); } catch (err) { bootFail(err, "env"); } } function buildLogger(config: Config, env: CoreEnv): Logger { const level = env.LOG_LEVEL ?? config.logging.level; const isProd = env.NODE_ENV === "production"; return createLogger({ name: LOGGER_NAME, level, format: isProd ? "json" : config.logging.format, file: config.logging.file ? { path: config.logging.file, maxBytes: config.logging.file_max_bytes, maxBackups: config.logging.file_max_backups, } : undefined, }); } function installSignalHandlers(container: CoreContainer, logger: Logger): void { let shuttingDown = false; const shutdown = (signal: string) => { if (shuttingDown) { logger.warn("shutdown already in progress; ignoring signal", { signal }); return; } shuttingDown = true; logger.info("shutdown signal received", { signal }); container .shutdown() .catch((err) => { logger.error("shutdown error", { err: err instanceof Error ? err : new Error(String(err)), }); }) .finally(() => { logger.flush().finally(() => Deno.exit(0)); }); }; Deno.addSignalListener("SIGINT", () => shutdown("SIGINT")); Deno.addSignalListener("SIGTERM", () => shutdown("SIGTERM")); } function bootFail(err: unknown, stage: "config" | "env"): never { if (err instanceof ConfigValidationError || err instanceof EnvValidationError) { console.error(`[@elly/core] ${stage} validation failed:`); for (const issue of err.issues) { console.error(` - ${issue.path}: ${issue.message}`); } Deno.exit(1); } if (err instanceof ConfigError) { console.error(`[@elly/core] ${stage} error: ${err.message}`); Deno.exit(1); } console.error(`[@elly/core] unexpected ${stage} error:`, err); Deno.exit(1); } if (import.meta.main) { main().catch((err) => { console.error("[@elly/core] fatal boot error:", err); Deno.exit(1); }); }