/** * Runtime — manages application lifecycle phases. * * Usage: * import { Runtime } from "@systems/runtime"; * * // Register hooks in any module: * Runtime.phase("load", () => Config.load()); * Runtime.phase("restore", () => restoreLayout()); * Runtime.phase("ready", () => Scheduler.start(client)); * * // In index.ts: * await Runtime.start(); */ import { Logger } from "@systems/logger"; const log = Logger.for("runtime"); // ─── Types ──────────────────────────────────────────────────────────────────── export type RuntimePhase = | "load" // read data from disk | "restore" // restore in-memory state from loaded data | "connect" // external connections (Discord client login) | "schedule" // start cron jobs and timers | "ready" // bot is fully operational | "shutdown"; // graceful shutdown export type RuntimeState = | "idle" | "starting" | "ready" | "stopping" | "stopped"; interface Hook { phase: RuntimePhase; fn: () => void | Promise; priority: number; name?: string; } // ─── State ──────────────────────────────────────────────────────────────────── const PHASE_ORDER: RuntimePhase[] = [ "load", "restore", "connect", "schedule", "ready", ]; const SHUTDOWN_PHASES: RuntimePhase[] = ["shutdown"]; const _hooks: Hook[] = []; let _state: RuntimeState = "idle"; let _errorHandlers: ((err: Error) => void)[] = []; // ─── Runtime namespace ──────────────────────────────────────────────────────── export const Runtime = { /** * Register a hook for a lifecycle phase. * Hooks within the same phase run in priority order (ascending), * then registration order for equal priorities. */ phase( phase: RuntimePhase, fn: () => void | Promise, options?: { priority?: number; name?: string } ): void { _hooks.push({ phase, fn, priority: options?.priority ?? 0, name: options?.name, }); }, /** * Register a global error handler. */ on(event: "error", handler: (err: Error) => void): void { if (event === "error") _errorHandlers.push(handler); }, /** * Current runtime state. */ state(): RuntimeState { return _state; }, /** * Run all startup phases in order. */ async start(): Promise { if (_state !== "idle") { log.warn(`Runtime.start() called in state: ${_state}`); return; } _state = "starting"; log.info("Starting..."); for (const phase of PHASE_ORDER) { const hooks = _hooks .filter((h) => h.phase === phase) .sort((a, b) => a.priority - b.priority); if (hooks.length === 0) continue; log.debug(`Phase [${phase}] — ${hooks.length} hook(s)`); for (const hook of hooks) { try { await hook.fn(); if (hook.name) log.debug(` ✓ ${hook.name}`); } catch (err: any) { log.error(`Phase [${phase}] hook failed:`, err.message); _errorHandlers.forEach((h) => h(err)); throw err; // halt startup on error } } } _state = "ready"; log.info("Ready."); }, /** * Run shutdown hooks and stop the runtime. */ async stop(): Promise { if (_state === "stopped") return; _state = "stopping"; log.info("Shutting down..."); const hooks = _hooks .filter((h) => h.phase === "shutdown") .sort((a, b) => a.priority - b.priority); for (const hook of hooks) { try { await hook.fn(); if (hook.name) log.debug(` ✓ ${hook.name}`); } catch (err: any) { log.error("Shutdown hook failed:", err.message); } } _state = "stopped"; log.info("Stopped."); }, /** * Re-run a specific phase (e.g. "restore" on hot reload). */ async rerun(phase: RuntimePhase): Promise { const hooks = _hooks .filter((h) => h.phase === phase) .sort((a, b) => a.priority - b.priority); log.debug(`Rerunning phase [${phase}] — ${hooks.length} hook(s)`); for (const hook of hooks) { try { await hook.fn(); } catch (err: any) { log.error(`Rerun [${phase}] hook failed:`, err.message); } } }, };