runBootOnce() in boot.tsGateway Process Initialization
The startup sequence describes how the Gateway process boots from cold start to fully operational. The runBootOnce() function orchestrates every phase — from reading BOOT.md instructions to initializing services, restoring sessions, and broadcasting readiness.
runBootOnce() in boot.tsBOOT_FILENAME = "BOOT.md"BootRunResult — "ran" | "skipped" | "failed"GatewayRequestContext with 25+ fieldsWhen the Gateway process starts, runBootOnce() is invoked exactly once. It reads the BOOT.md instruction file from the project root, parses its directives, and executes each phase sequentially. The function returns a BootRunResult indicating whether the boot ran successfully ("ran"), was skipped because a previous boot already completed ("skipped"), or encountered an error ("failed").
runBootOnce() in parallel. If a second process detects the lock, it receives "skipped" and waits for the first to complete.
The following flowchart illustrates the high-level phases executed by runBootOnce(). Each phase must complete before the next begins.
flowchart TD
A[Process Start] --> B{Lock acquired?}
B -- No --> S[Return "skipped"]
B -- Yes --> C[Read BOOT.md]
C --> D{BOOT.md exists?}
D -- No --> E[Use default config]
D -- Yes --> F[Parse boot directives]
F --> E
E --> G[Build GatewayRequestContext]
G --> H[Initialize CronService]
H --> I[Initialize NodeRegistry]
I --> J[Register broadcast functions]
J --> K[Restore SessionMappingSnapshot]
K --> L{Restore success?}
L -- No --> M[Log warning, fresh state]
L -- Yes --> N[Sessions restored]
M --> O[Run post-boot hooks]
N --> O
O --> P[Release lock]
P --> Q[Return "ran"]
The Gateway looks for a file named BOOT.md (defined by the constant BOOT_FILENAME) in the project root directory. This Markdown file contains structured directives that configure boot behavior, including environment overrides, feature flags, and service endpoints.
// Constant defining the boot instruction filename
const BOOT_FILENAME = "BOOT.md";
interface BootDirective {
key: string;
value: string;
section: "env" | "flags" | "endpoints" | "hooks";
}
async function readBootFile(rootDir: string): Promise<BootDirective[]> {
const bootPath = path.join(rootDir, BOOT_FILENAME);
if (!fs.existsSync(bootPath)) {
return []; // No BOOT.md — use defaults
}
const raw = await fs.promises.readFile(bootPath, "utf-8");
return parseBootDirectives(raw);
}
function parseBootDirectives(content: string): BootDirective[] {
const directives: BootDirective[] = [];
let currentSection: BootDirective["section"] = "env";
for (const line of content.split("\n")) {
if (line.startsWith("## ")) {
currentSection = line.slice(3).trim().toLowerCase() as BootDirective["section"];
continue;
}
const match = line.match(/^- \*\*(\w+)\*\*:\s*(.+)$/);
if (match) {
directives.push({ key: match[1], value: match[2], section: currentSection });
}
}
return directives;
}
BOOT.md contains syntax errors, the parser silently ignores malformed lines rather than failing the entire boot. Check the Gateway logs for boot:directive-skip entries to diagnose missing configuration.
Before any work begins, runBootOnce() attempts to acquire an exclusive lock. This prevents race conditions when multiple Gateway instances start simultaneously (e.g., in a cluster deployment).
type BootRunResult = {
status: "ran" | "skipped" | "failed";
durationMs: number;
error?: string;
};
async function runBootOnce(config: GatewayConfig): Promise<BootRunResult> {
const startTime = Date.now();
const lock = await acquireBootLock();
if (!lock.acquired) {
return { status: "skipped", durationMs: Date.now() - startTime };
}
try {
// Phase 1: Read BOOT.md
const directives = await readBootFile(config.rootDir);
// Phase 2: Build context
const ctx = buildGatewayRequestContext(config, directives);
// Phase 3: Initialize services
await initializeServices(ctx);
// Phase 4: Restore sessions
await restoreSessions(ctx);
// Phase 5: Post-boot hooks
await runPostBootHooks(ctx, directives);
return { status: "ran", durationMs: Date.now() - startTime };
} catch (err) {
return { status: "failed", durationMs: Date.now() - startTime, error: String(err) };
} finally {
lock.release();
}
}
The GatewayRequestContext is the central state object passed to every subsystem during boot. It contains 25+ fields covering authentication, routing, storage, cron scheduling, and WebSocket management.
interface GatewayRequestContext {
// Identity & auth
gatewayId: string;
authConfig: AuthConfig;
rateLimiter: RateLimiter;
credentials: CredentialStore;
// Networking
wsServer: WebSocketServer;
httpRouter: HttpRouter;
broadcastFn: BroadcastFunction;
// Services
cronService: CronService;
nodeRegistry: NodeRegistry;
healthCache: HealthCache;
// Storage
sessionStore: SessionStore;
snapshotManager: SnapshotManager;
bootLog: BootLog;
// Configuration
config: GatewayConfig;
featureFlags: FeatureFlags;
environment: string;
// Telemetry
logger: Logger;
metrics: MetricsCollector;
traceContext: TraceContext;
// Runtime
startedAtMs: number;
bootResult: BootRunResult;
shutdownSignal: AbortSignal;
}
function buildGatewayRequestContext(
config: GatewayConfig,
directives: BootDirective[],
): GatewayRequestContext {
const flags = resolveFeatureFlags(config.defaults, directives);
return {
gatewayId: generateGatewayId(),
authConfig: buildAuthConfig(directives),
rateLimiter: new SlidingWindowRateLimiter(config.rateLimit),
credentials: new FileCredentialStore(config.credentialsPath),
wsServer: new WebSocketServer({ port: config.wsPort }),
httpRouter: createHttpRouter(config),
broadcastFn: createBroadcastFunction(),
cronService: new CronService(),
nodeRegistry: new NodeRegistry(),
healthCache: new HealthCache(),
sessionStore: new SessionStore(config.sessionDir),
snapshotManager: new SnapshotManager(config.snapshotDir),
bootLog: new BootLog(),
config,
featureFlags: flags,
environment: config.env ?? "production",
logger: createLogger(config.logLevel),
metrics: new MetricsCollector(config.metricsEndpoint),
traceContext: new TraceContext(),
startedAtMs: Date.now(),
bootResult: { status: "ran", durationMs: 0 },
shutdownSignal: new AbortController().signal,
};
}
With the context built, three critical services are initialized in order: the CronService for scheduled tasks, the NodeRegistry for tracking connected clients, and the broadcast subsystem for pushing updates to all connected nodes.
The final boot phase attempts to restore previously persisted session data from a SessionMappingSnapshot. If the snapshot is missing or corrupt, the Gateway logs a warning and starts with a clean session state.
This sequence diagram shows the temporal ordering of startup events, including inter-service communication.
sequenceDiagram
participant P as Process
participant BL as Boot Lock
participant BF as BOOT.md
participant CTX as GatewayRequestContext
participant CS as CronService
participant NR as NodeRegistry
participant BC as Broadcast
participant SS as SessionStore
participant SM as SnapshotManager
P->>BL: acquireBootLock()
BL-->>P: {acquired: true}
P->>BF: readBootFile(rootDir)
BF-->>P: BootDirective[]
P->>CTX: buildGatewayRequestContext(config, directives)
CTX-->>P: context (25+ fields)
P->>CS: cronService.start(ctx)
CS-->>P: ready
P->>NR: nodeRegistry.initialize(ctx)
NR-->>P: ready
P->>BC: registerBroadcastFunctions(ctx)
BC-->>P: broadcastFn bound
P->>SM: snapshotManager.loadLatest()
alt snapshot found
SM-->>P: SessionMappingSnapshot
P->>SS: sessionStore.restore(snapshot)
SS-->>P: sessions restored
else no snapshot
SM-->>P: null
P->>P: log warning, fresh state
end
P->>BL: lock.release()
Note over P: Return BootRunResult "ran"
| Status | Meaning | Typical Cause |
|---|---|---|
"ran" | Boot completed successfully | Normal cold start |
"skipped" | Another instance already booted | Cluster race condition; lock not acquired |
"failed" | Boot encountered a fatal error | Missing config, corrupt snapshot, port conflict |
bootResult.status before accepting traffic. A Gateway that returned "failed" should not be added to the load balancer — use the health check endpoint to gate readiness.
The SessionMappingSnapshot is a serialized representation of all active sessions at the time of the last graceful shutdown. It maps session IDs to their associated client metadata, connection parameters, and in-flight request state.
interface SessionMappingSnapshot {
version: number;
createdAtMs: number;
sessions: Map<string, SessionEntry>;
}
interface SessionEntry {
sessionId: string;
clientId: string;
connectedAtMs: number;
lastActiveMs: number;
authMethod: string;
capabilities: string[];
pendingRequests: number;
}
async function restoreSessions(ctx: GatewayRequestContext): Promise<void> {
const snapshot = await ctx.snapshotManager.loadLatest();
if (!snapshot) {
ctx.logger.warn("No session snapshot found — starting with empty state");
return;
}
ctx.logger.info(`Restoring ${snapshot.sessions.size} sessions from snapshot v${snapshot.version}`);
for (const [id, entry] of snapshot.sessions) {
// Skip sessions older than the configured max age
if (Date.now() - entry.lastActiveMs > ctx.config.maxSessionAgeMs) {
ctx.logger.debug(`Skipping stale session ${id}`);
continue;
}
ctx.sessionStore.put(id, entry);
}
}
The CronService is started during boot to schedule periodic maintenance tasks such as health checks, session pruning, and metrics flushing. Each task is registered with a cron expression and a callback.
| Task | Schedule | Description |
|---|---|---|
| Health check | Every 30s | Updates healthCache with current status |
| Session prune | Every 5m | Removes expired sessions from store |
| Metrics flush | Every 60s | Pushes collected metrics to endpoint |
| Snapshot persist | Every 10m | Saves SessionMappingSnapshot to disk |
| Node heartbeat | Every 15s | Pings registered nodes for liveness |
| Error | Phase | Recovery |
|---|---|---|
| Lock timeout | Lock acquisition | Return "skipped", let other instance handle boot |
| BOOT.md parse error | Read boot file | Skip malformed lines, log warnings |
| Port already in use | Context build | Return "failed" with EADDRINUSE error |
| Corrupt snapshot | Session restore | Log error, start with empty state |
| CronService crash | Service init | Return "failed", escalate to process supervisor |
runBootOnce() returns "failed", the process should exit with a non-zero code so the orchestrator (systemd, Kubernetes, etc.) can restart it. Never silently continue with a failed boot.
| Parameter | Type | Default | Description |
|---|---|---|---|
boot.lockTimeoutMs | number | 10000 | Max time to wait for boot lock |
boot.maxSessionAgeMs | number | 86400000 | Max session age for snapshot restore (24h) |
boot.snapshotDir | string | "./snapshots" | Directory for session snapshots |
boot.hooksEnabled | boolean | true | Whether to run post-boot hooks |
cron.healthIntervalSec | number | 30 | Health check interval |
src/gateway/
├── boot.ts ← runBootOnce(), BootRunResult, BOOT_FILENAME
├── boot-lock.ts ← acquireBootLock(), lock file management
├── context.ts ← buildGatewayRequestContext(), GatewayRequestContext
├── cron.ts ← CronService initialization and task registry
├── node-registry.ts ← NodeRegistry for connected client tracking
├── broadcast.ts ← registerBroadcastFunctions(), BroadcastFunction
├── session-store.ts ← SessionStore, SessionEntry
└── snapshot/
├── manager.ts ← SnapshotManager, loadLatest(), persist()
└── types.ts ← SessionMappingSnapshot interface