VibORM

Migrations

Manage database schema changes with VibORM migrations

VibORM provides two approaches for managing database schema changes:

  • Push - Direct schema synchronization, ideal for development
  • Migrate - File-based migrations with version control, ideal for production

Both approaches work by comparing your VibORM schema definitions against the current database state and generating the necessary DDL to synchronize them.

Choose Your Approach

PushMigrate
Use caseDevelopment, prototypingProduction, team collaboration
Version controlNo migration filesYes, SQL files committed to git
RollbackManualSupported via down migrations
CI/CDNot recommendedFully supported

CLI vs API

VibORM migrations can be used via the CLI or programmatically via the Migration Client API.

CLI

The CLI is the recommended way to manage migrations during development:

# Push schema directly to database
npx viborm push

# Generate a new migration file
npx viborm migrate generate --name add-users

# Apply pending migrations
npx viborm migrate apply

# Check migration status
npx viborm migrate status

Migration Client API

For programmatic control (scripts, CI/CD, custom tooling), use the migration client:

src/db/migrations.ts
import { createMigrationClient } from "viborm/migrations";
import { createFsStorageDriver } from "viborm/migrations/storage/fs";
import { client } from "./client";

const migrations = createMigrationClient(client, {
  storageDriver: createFsStorageDriver("./migrations"),
});

// File-based migration operations (require storage driver)
await migrations.generate({ name: "add-users" });
await migrations.apply();
await migrations.status();

// Push works with or without storage driver
await migrations.push({
  resolve: async (change) => {
    // Handle destructive or ambiguous changes
    if (change.type === "destructive") {
      return "proceed"; // or "reject"
    }
    return "rename"; // or "addAndDrop" or "reject"
  },
});

Storage Driver Required

File-based operations (generate, apply, status, reset, rollback) require a storage driver. The push() method works without a storage driver since it doesn't use migration files.

Migration Client Options

interface MigrationClientOptions {
  /** Storage driver for migration files (required for file-based operations) */
  storageDriver?: MigrationStorageDriver;
  /** Migration tracking table name (default: _viborm_migrations) */
  tableName?: string;
}

How It Works

Both push and migrate follow the same core process:

┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
│  VibORM Schema  │ ──▶ │   Schema Diff    │ ──▶ │  DDL Generation │
│   (your code)   │     │  (detect changes)│     │ (database SQL)  │
└─────────────────┘     └──────────────────┘     └─────────────────┘
  1. Serialize your VibORM models into a schema snapshot
  2. Compare against the current database state (push) or previous snapshot (migrate)
  3. Generate database-specific DDL using the appropriate migration driver
  4. Execute the DDL (push) or write to a migration file (migrate)

Atomicity

Migrations in VibORM are executed atomically - either all statements succeed or none are applied. This is critical for maintaining database integrity.

VibORM automatically uses the best available method for atomic execution based on your driver:

Driver TypeAtomicity Method
PostgreSQL, MySQL, SQLiteTransaction wrapper
D1, D1-HTTPNative batch() API
Neon-HTTPtransaction() function
PlanetScaleTransaction wrapper

All drivers provide atomicity (all-or-nothing) for migrations. Neon-HTTP uses transaction() with full PostgreSQL transaction semantics, providing transaction isolation by default (ReadCommitted) with configurable levels (ReadUncommitted, ReadCommitted, RepeatableRead, Serializable). Only D1 and D1-HTTP use batch execution without isolation guarantees, but this is rarely a concern since migrations typically run in controlled environments.

What Atomicity Means

  • If migration succeeds: All schema changes are applied
  • If migration fails: No schema changes are applied (rolled back)
  • Your database is never left in a partially migrated state

This applies to both push and migrate apply commands.

Next Steps

On this page