Skip to content
VibORM
Esc
navigateopen⌘Jpreview
On this page

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

Push Migrate
Use case Development, prototyping Production, team collaboration
Version control No migration files Yes, SQL files committed to git
Rollback Manual Supported via down migrations
CI/CD Not recommended Fully 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:

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"
  },
});

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

Migration atomicity is a DDL-specific guarantee and is not implied by support for client transactions. VibORM uses a real transaction or atomic batch where the database can honor it. D1 binding and Neon apply paths reject when that substrate is absent; MySQL-family DDL is the documented limited exception because its statements implicitly commit:

Driver push migrate apply
PostgreSQL, SQLite family Transaction (all-or-nothing) Transaction (all-or-nothing)
MySQL, PlanetScale Limited — DDL statements implicitly commit, so a failed run can leave earlier statements applied Same limitation
D1 bindings, Neon HTTP Atomic batch Rejects — requires callback transactions, which these drivers don’t support

On the atomic paths, a successful migration applies every statement and a failed one applies none. The portable API exposes no transaction isolation options.

Next Steps

Was this page helpful?