import { sql } from 'kysely'; import { pendingMigrations } from '../db/migrator'; import type { AppDeps } from './context'; export type CheckState = 'ok' | 'error' | 'pending'; /** * Liveness: the process is up and the event loop is turning. Never touches the * database, so a database blip does not get the container killed. */ export function liveness(draining: boolean): { ok: boolean } { return { ok: !draining }; } /** * Readiness: this replica can serve traffic. Checked by the orchestrator and flipped * to not-ready as soon as SIGTERM arrives, so the load balancer stops sending work * while in flight requests drain. */ export async function readiness( deps: AppDeps, draining: boolean, ): Promise<{ ok: boolean; checks: Record }> { if (draining) return { ok: false, checks: { draining: 'error' } }; const checks: Record = {}; try { await sql`select 1`.execute(deps.handle.db); checks['database'] = 'ok'; } catch { checks['database'] = 'error'; } if (checks['database'] === 'ok') { try { checks['migrations'] = (await pendingMigrations(deps.handle)).length === 0 ? 'ok' : 'pending'; } catch { checks['migrations'] = 'error'; } } else { checks['migrations'] = 'error'; } try { await deps.storage.check(); checks['storage'] = 'ok'; } catch { checks['storage'] = 'error'; } return { ok: Object.values(checks).every((state) => state === 'ok'), checks }; }