Skip to content
VibORM
Esc
navigateopen⌘Jpreview
On this page

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

Was this page helpful?