L12 - Migrations
Synchronize your TypeScript schema with the database through diffing and DDL generation
Location: src/migrations/
Why This Layer Exists
Your TypeScript schema is the source of truth:
const user = s.model({
id: s.string().id().ulid(),
email: s.string().unique(),
name: s.string().nullable(), // Add this scalar
});
The migrations layer detects this change and generates:
ALTER TABLE "user" ADD COLUMN "name" VARCHAR NULL;
How It Works
1. Schema Snapshot
First, the current TypeScript schema is serialized into a database-agnostic snapshot:
{
tables: [{
name: "user",
columns: [{
name: "email",
type: "varchar",
nullable: false,
unique: true
}],
indexes: [...],
foreignKeys: [...]
}]
}
2. The Comparison Baseline
viborm push reads the current database state through the driver:
SELECT column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_name = 'user';
viborm migrate generate instead compares against the snapshot stored with the previous migration — no live database needed.
3. Diffing
The differ compares snapshots and produces operations:
[
{ type: "addColumn", table: "user", column: { name: "name", ... } },
{ type: "dropIndex", table: "user", indexName: "email_idx" }
]
4. DDL Generation
Each operation becomes database-specific DDL via the migration driver:
migrationDriver.generateDDL(operation) // → ALTER TABLE ... ADD COLUMN ...
Operation Types
The full DiffOperation union (see src/migrations/types.ts):
| Operation | Description |
|---|---|
createTable |
New table |
dropTable |
Remove table |
renameTable |
Rename table |
addColumn |
Add column to table |
dropColumn |
Remove column |
renameColumn |
Rename column |
alterColumn |
Change column type/constraints |
createIndex |
Add index |
dropIndex |
Remove index |
addForeignKey |
Add foreign key constraint |
dropForeignKey |
Remove foreign key constraint |
addUniqueConstraint |
Add unique constraint |
dropUniqueConstraint |
Remove unique constraint |
addPrimaryKey |
Add primary key |
dropPrimaryKey |
Remove primary key |
createEnum |
Create enum type |
dropEnum |
Remove enum type |
alterEnum |
Add/remove enum values |
Ambiguous Changes
Some changes can’t be automatically resolved:
// Did you rename "name" to "fullName"?
// Or delete "name" and create "fullName"?
The CLI prompts for clarification:
? Column "name" was removed and "fullName" was added.
○ Rename "name" to "fullName" (preserves data)
○ Drop "name" and create "fullName" (loses data)
Push vs Migrate
VibORM offers two approaches:
viborm push
Direct sync - applies changes immediately:
viborm push
# Compares schema → database
# Shows diff
# Applies changes
Best for: Development, prototyping
viborm migrate
Generate SQL migration files for review, then apply them:
viborm migrate generate
# Diffs schema against the last snapshot
# Writes an up/down migration to the migrations directory
viborm migrate apply
# Executes pending migrations (tracked in a migrations table)
viborm migrate down
# Rolls back applied migrations using the generated down SQL
viborm migrate status
# Shows applied vs pending migrations
viborm migrate drop
# Removes migration tracking (does NOT revert database changes)
Best for: Production, team workflows
See src/cli/commands/migrate.ts for the CLI and viborm/migrations for the programmatic API (generate, apply, down, status, push, reset, squash, …).
Connection to Other Layers
- L2 (Scalars): Scalar state determines column definitions
- L4 (Relations): Relations determine foreign key constraints
- L7 (Adapters): Adapters generate database-specific DDL
- L8 (Drivers): Drivers execute DDL and introspect schema