// Health / readiness helpers for the vendored Hasna storage kit. // // `checkHealth` proves the database is reachable (a cheap `SELECT 1`). // `checkReady` additionally proves the schema is migrated up to date, so a // service can gate its `/ready` probe on pending migrations. These map to the // contract's health/ready/version response shapes. // // PURE REMOTE (Amendment A1): health checks the cloud Postgres directly. import type { TypedQueryClient } from "./query.js"; import { MigrationLedger, type Migration, type MigrationRunnerOptions } from "./migrations.js"; export interface HealthResult { ok: boolean; /** Round-trip latency of the probe query, in milliseconds. */ latencyMs: number; error?: string; } /** Cheap reachability probe: `SELECT 1`. Never throws — reports `ok: false`. */ export async function checkHealth(client: TypedQueryClient): Promise { const start = Date.now(); try { await client.get<{ ok: number }>("SELECT 1 AS ok"); return { ok: true, latencyMs: Date.now() - start }; } catch (error) { return { ok: false, latencyMs: Date.now() - start, error: error instanceof Error ? error.message : String(error), }; } } export interface ReadyResult extends HealthResult { /** Migration ids that are defined but not yet applied. */ pendingMigrations: string[]; } /** * Readiness probe: reachable AND fully migrated. Reports `ok: false` with the * list of pending migration ids when the schema is behind. */ export async function checkReady( client: TypedQueryClient, migrations: readonly Migration[], options: MigrationRunnerOptions = {}, ): Promise { const start = Date.now(); try { const ledger = new MigrationLedger(client, migrations, options); const result = await ledger.migrate({ dryRun: true }); const pending = result.plan.filter((item) => item.state === "pending").map((item) => item.migration.id); return { ok: pending.length === 0, latencyMs: Date.now() - start, pendingMigrations: pending }; } catch (error) { return { ok: false, latencyMs: Date.now() - start, pendingMigrations: [], error: error instanceof Error ? error.message : String(error), }; } }