VibORM
Migration storage

Storage Drivers

Abstract storage for migration files, journals, and snapshots

What Storage Drivers Do

  • Store migration SQL files - Up and down migrations
  • Store the journal - Index of all migrations with checksums
  • Store schema snapshots - Current schema state for diffing
  • Handle backups - Archive migrations before modification

Available Drivers

DriverStorageBest For
FilesystemLocal filesDevelopment, CI/CD, version control

Abstract Interface

All storage drivers implement three core methods:

abstract class MigrationStorageDriver {
  abstract get(path: string): Promise<string | null>;
  abstract put(path: string, content: string): Promise<void>;
  abstract delete(path: string): Promise<void>;
}
MethodDescription
get(path)Read content from path, returns null if not found
put(path, content)Write content to path, creates directories as needed
delete(path)Delete file at path, no-op if not found

High-Level Operations

The base class provides high-level operations built on the three core methods:

Journal Management

// Read the migration journal
const journal = await storage.readJournal();

// Write the journal
await storage.writeJournal(journal);

// Get or create journal
const journal = await storage.getOrCreateJournal("postgresql");

Snapshot Management

// Read the schema snapshot
const snapshot = await storage.readSnapshot();

// Write the snapshot
await storage.writeSnapshot(snapshot);

// Get snapshot or empty
const snapshot = await storage.getSnapshotOrEmpty();

Migration Files

// Read a migration
const sql = await storage.readMigration(entry);

// Write a migration
await storage.writeMigration(entry, sql);

// Delete a migration
await storage.deleteMigration(entry);

// Check if migration exists
const exists = await storage.migrationExists(entry);

Down Migrations

// Read down migration
const downSql = await storage.readDownMigration(entry);

// Write down migration
await storage.writeDownMigration(entry, downSql);

Backup Operations

// Backup a migration (copies to _backup/)
await storage.backupMigration(entry);

// Archive a migration (moves to _archive/)
await storage.archiveMigration(entry);

Directory Structure

The filesystem driver uses this structure:

migrations/
├── 0000_initial.sql              # Up migrations
├── 0001_add-users.sql
├── 0002_add-posts.sql
└── meta/
    ├── _journal.json             # Migration index
    ├── _snapshot.json            # Current schema
    ├── _down/                    # Down migrations
    │   ├── 0000_initial.sql
    │   ├── 0001_add-users.sql
    │   └── 0002_add-posts.sql
    ├── _backup/                  # Backups before modification
    └── _archive/                 # Archived migrations

Custom Storage Drivers

To implement a custom storage driver, extend MigrationStorageDriver:

import { MigrationStorageDriver } from "viborm/migrations";

class S3StorageDriver extends MigrationStorageDriver {
  constructor(
    private bucket: string,
    private prefix: string = "migrations"
  ) {
    super("s3");
  }

  async get(path: string): Promise<string | null> {
    const key = `${this.prefix}/${path}`;
    try {
      const response = await s3.getObject({ Bucket: this.bucket, Key: key });
      return response.Body?.toString("utf-8") ?? null;
    } catch (e) {
      if (e.name === "NoSuchKey") return null;
      throw e;
    }
  }

  async put(path: string, content: string): Promise<void> {
    const key = `${this.prefix}/${path}`;
    await s3.putObject({
      Bucket: this.bucket,
      Key: key,
      Body: content,
      ContentType: "text/plain",
    });
  }

  async delete(path: string): Promise<void> {
    const key = `${this.prefix}/${path}`;
    try {
      await s3.deleteObject({ Bucket: this.bucket, Key: key });
    } catch (e) {
      if (e.name !== "NoSuchKey") throw e;
    }
  }
}

Use your custom driver:

const storage = new S3StorageDriver("my-bucket", "migrations");

const migrations = createMigrationClient(client, {
  storageDriver: storage,
});

Next Steps

On this page