/** * Boot-time convergence for columns Drizzle's schema declares but a * pre-existing table is missing. * * Lives in its own module (like `./widen-columns.js`) so stores/plugins can * import it without every `vi.mock("../db/client.js")` test needing to stub * it: the helper resolves `isPostgres()` / `getDbExec()` through `client.js`, * so a test that mocks the client to SQLite makes the Postgres introspection * path a no-op automatically. * * ## Why * * `CREATE TABLE IF NOT EXISTS` only runs the CREATE the first time a table is * created — once it exists, adding a column to the Drizzle schema (e.g. * `schema.ts`) does nothing to already-existing rows unless a hand-written * `ALTER TABLE ... ADD COLUMN` migration ships alongside it. Fresh dev * databases mask this because the CREATE always includes every declared * column; only long-lived, pre-existing production tables are missing the * new column. Forgetting the migration turns every query that references the * column into a Postgres `42703 (undefined_column)` — e.g. * `session_recordings.network_error_count`, which 500'd every * `list-session-recordings` call. * * `ensureAdditiveColumns()` is belt-and-braces for that failure mode: after * the authoritative hand-written migrations run, diff each Drizzle table's * declared columns against the live table and additively patch any gap. It * is NOT a replacement for migrations — it never touches indexes, data * transforms, or existing columns, and a hand-written migration should still * ship for every schema change. This is a safety net for the case where one * doesn't. * * ## Safety rules (hard) * * - Additive only: never drops, renames, retypes, or otherwise touches a * column that already exists on the live table. * - A `NOT NULL` column is only ever added when it has a renderable default * (so existing rows get a valid value in the same statement). If it has no * renderable default, it is added as nullable when the Drizzle declaration * allows null, else the column is SKIPPED entirely with a loud log line * naming the column and the reason. * - Only simple literal defaults are rendered: numbers, strings, booleans, * and `sql` template defaults that stringify to one of a small allow-listed * set of safe constants (see `renderDefaultLiteral`). Any other `sql` * default is NOT rendered — the column is added without a `DEFAULT` (and * without `NOT NULL`, per the rule above) rather than risk interpolating * unsafe SQL. * - All identifiers (table/column names) are validated against a strict * `[A-Za-z_][A-Za-z0-9_]*` pattern and double-quoted; no value from a row or * from user data is ever interpolated into the generated SQL. * - If the table itself does not exist yet, this is a no-op — table creation * owns bringing every declared column into existence for a brand-new table. * - Idempotent and best-effort: a failure on one column is logged and does * not abort the rest, and a total failure (e.g. `information_schema` * unreadable) must never crash boot — the caller decides whether/how to * surface `errors` in the returned summary. */ import { type DbExec } from "./client.js"; export interface EnsureAdditiveColumnsLogger { info: (message: string) => void; warn: (message: string) => void; error: (message: string) => void; } export interface EnsureAdditiveColumnsOptions { db: DbExec; /** Drizzle table objects (from `pgTable`/`sqliteTable`, or the dialect-agnostic `table()` helper). */ tables: unknown[]; logger?: EnsureAdditiveColumnsLogger; } export interface EnsureAdditiveColumnsResult { /** `"table.column"` entries that were successfully added. */ applied: string[]; /** Declared columns that were intentionally left unpatched, with why. */ skipped: Array<{ column: string; reason: string; }>; /** `"table.column"` entries whose ALTER failed unexpectedly (logged, non-fatal). */ errors: Array<{ column: string; error: string; }>; } /** * Diff each declared Drizzle table's columns against the live database and * additively `ALTER TABLE ... ADD COLUMN` any that are missing. See the * module docstring for the full safety-rule contract. * * Call this once at boot, immediately after the authoritative hand-written * migrations have run. Never throws — every failure path is captured in the * returned summary instead so a boot-time caller can log-and-continue. */ export declare function ensureAdditiveColumns(options: EnsureAdditiveColumnsOptions): Promise; //# sourceMappingURL=ensure-additive-columns.d.ts.map