Preflight checks
A pass that runs ahead of the migration, probing live data for the conditions that would make the DDL fail. Returns a list of PreflightFinding objects with sample offending rows — actionable enough to fix the data and re-run.
Why it exists
ALTER TABLE orders ADD CONSTRAINT … UNIQUE (sku) succeeds or fails on a single condition: are there duplicate sku values? The database tells you with an error. Preflight asks the same question first, with a probe query that returns up to sampleSize example duplicates — so you fix the data instead of staring at could not create unique index.
What it probes
| Op kind | Probe | Blocks when |
|---|---|---|
addUnique | SELECT cols, COUNT(*) … GROUP BY cols HAVING COUNT(*) > 1 | Duplicates exist on the new unique columns. |
addForeignKey | SELECT … WHERE child_col NOT IN (SELECT pk FROM parent) | Orphan rows reference a parent row that doesn't exist. |
alterColumnNullable (to NOT NULL) | SELECT … WHERE col IS NULL | NULL rows would violate the new constraint. |
Other op kinds aren't probed — there's nothing in the live data that could make addColumn fail, for instance. The check returns an empty array for those.
The signature
import { preflightCheck, describeFindings } from 'zanith'; const findings = await preflightCheck(adapter, plan.ops, { sampleSize: 5, // default skip: false, // default — set true when bootstrapping an empty DB}); const blockers = findings.filter((f) => f.blocking);if (blockers.length > 0) { console.error(describeFindings(blockers)); process.exit(1);}preflightChecktakes the plan's ops and an adapter, runs each relevant probe in turn, and resolves to PreflightFinding[]. describeFindings formats the blocking findings as a single human-readable string — useful for CI output.
PreflightFinding shape
interface PreflightFinding { op: MigrationOp; ok: boolean; // false when blocking check: string; // short description of what was checked blocking: boolean; // true → migration would fail sample?: unknown[]; // up to sampleSize offending values offendingCount?: number; // total count message?: string; // free-form, surfaced in error reports}Where preflight fits in the lifecycle
| Stage | What runs |
|---|---|
generate | Compare schemas → emit plan. |
plan | Risk-score every op. |
verify | Apply on a shadow database — see [Shadow-DB verify](/migrate/verify). |
**preflightCheck** | Probe live data on the real database before any DDL. |
up | Apply the plan — only proceeds if preflight had no blockers. |
Preflight and shadow verify are complementary — shadow verify catches schema-shape errors; preflight catches data-shape errors. Both run on the production database (or a disposable copy of it) before up commits anything.
When to skip
skip: trueis for bootstrapping a fresh database where there's no data to probe. The CLI sets it automatically when it detects an empty schema; you usually don't need to set it by hand.