db/schema_ready.ts

Readiness probe core: live-DB schema-drift detection.

/health is a dumb liveness probe (no DB). /ready is the deploy gate — it introspects the live database's column set and compares it against a committed expected column map (what a fresh full migration-chain bootstrap produces). A live DB missing an expected column is exactly the failure mode that silently broke login when the auth schema gained account.deleted_at via an in-place base-DDL edit instead of an appended migration: the deployed code required a column an older bootstrapped DB never got, and a SELECT * + JS deleted_at === null filter rejected every account. The /ready route (http/common_routes.ts) turns that drift into a loud 503 so a deploy poll rolls the release back instead of promoting code that can't authenticate anyone. The discipline that prevents the drift is the frozen append-only migration chain (auth/migrations.ts); this probe is the runtime net for a lapse.

The check is intentionally column-presence only — not type / constraint / index parity. Column names are DDL-deterministic and engine-portable, so a map generated against PGlite at gen-time compares exactly against a live Postgres at runtime; finer-grained parity would false-positive across the two engines, and a false positive here means a rolled-back deploy — an outage you caused. Full structural parity stays the dev-time cross-backend schema-snapshot suite's job (testing/schema_introspect.ts). In-place *type* changes (a column kept by name, retyped) are out of scope — they rely on the query-time column-named failures instead.

This module is pure DB introspection + comparison: no HTTP, no filesystem, no fixture-path knowledge. The route factory and the committed-fixture loader live in http/common_routes.ts; the gen-time fixture-regeneration helper lives in testing/schema_ready_fixture.ts.

view source

Declarations
#

7 declarations

check_schema_drift
#

db/schema_ready.ts view source

(db: Db, expected: ExpectedSchema): Promise<SchemaDriftResult> import {check_schema_drift} from '@fuzdev/fuz_app/db/schema_ready.js';

Compare the live DB's columns against expected. Reports tables and columns the running code expects that the live DB lacks — the drift that breaks queries. Extra live tables / columns are ignored: forward-compatible, and a newer-than-fixture DB shouldn't fail readiness.

db

live database to introspect

type Db

expected

the committed column map a fresh bootstrap produces

returns

Promise<SchemaDriftResult>

ExpectedSchema
#

db/schema_ready.ts view source

ExpectedSchema import type {ExpectedSchema} from '@fuzdev/fuz_app/db/schema_ready.js';

Expected schema: table name → sorted column names, from a fresh bootstrap.

[key: string]

type readonly string[]

format_schema_drift
#

db/schema_ready.ts view source

(drift: SchemaDriftResult): string import {format_schema_drift} from '@fuzdev/fuz_app/db/schema_ready.js';

Render a drift result as a one-issue-per-line operator string.

drift

returns

string

MissingColumns
#

db/schema_ready.ts view source

MissingColumns import type {MissingColumns} from '@fuzdev/fuz_app/db/schema_ready.js';

Columns the live DB is missing for a table the expected schema declares.

table

type string

columns

type Array<string>

query_public_columns
#

db/schema_ready.ts view source

(db: Db, options?: { table?: string | undefined; } | undefined): Promise<Record<string, string[]>> import {query_public_columns} from '@fuzdev/fuz_app/db/schema_ready.js';

Introspect every column in the public schema, grouped by relation. Shared by the runtime /ready check and the fixture-generating helper so both observe the exact same shape. information_schema.columns spans tables and views; for the drift check that's harmless (a never-bootstrapped schema has neither, and extra relations are ignored — see check_schema_drift).

Unlike query_schema_snapshot (which excludes the schema_version migration tracker as framework bookkeeping), this keeps schema_version — a never-migrated DB then correctly fails readiness instead of passing on an empty expectation.

db

the database to introspect

type Db

options?

table narrows the scan to one relation (the drift guard's one-table question); omitted, every public relation is returned

type { table?: string | undefined; } | undefined
optional

returns

Promise<Record<string, string[]>>

relation name → sorted column names

READY_ERROR
#

db/schema_ready.ts view source

{ readonly schema_drift: "schema_drift"; readonly db_unreachable: "db_unreachable"; } import {READY_ERROR} from '@fuzdev/fuz_app/db/schema_ready.js';

Error codes a readiness check returns at 503 (conforms to {error: string}).

SchemaDriftResult
#

db/schema_ready.ts view source

SchemaDriftResult import type {SchemaDriftResult} from '@fuzdev/fuz_app/db/schema_ready.js';

Outcome of a schema-drift check.

ok

type boolean

missing_tables

Expected tables absent from the live DB.

type Array<string>

missing_columns

Per-table columns the expected schema declares that the live DB lacks.

type Array<MissingColumns>

Depends on
#

Imported by
#