IDatabaseMigrationHandler
Main interface for database integration.
Table of contents
Overview
Interface that must be implemented for your specific database.
Design Origin: The handler pattern emerged from MSR’s 2017 Firebase prototype, where an
EntityServiceprovided clean helper methods likeupdateAll(callback)andfindAllBy(propertyName, value). Instead of raw database SDK calls in every migration, this service layer made migrations declarative and maintainable. This pattern proved so valuable it became core to MSR’s architecture - allowing you to inject your own services, repositories, and business logic into migrations. Read more in the origin story.
Signature (v0.6.0+):
interface IDatabaseMigrationHandler<DB extends IDB> {
getName(): string;
getVersion(): string;
db: DB; // Your specific database type
schemaVersion: ISchemaVersion<DB>;
backup?: IBackup<DB>; // Optional - only needed for BACKUP or BOTH strategies
transactionManager?: ITransactionManager<DB>; // Optional - auto-created if db supports transactions
}
Breaking Change (v0.6.0): Generic type parameter
<DB extends IDB>is now required for full type safety. You must explicitly specify the type parameter in your implementations (e.g.,IDatabaseMigrationHandler<IDB>). See v0.6.0 migration guide for detailed examples.
Breaking Changes (v0.3.0):
- The
cfg: Configproperty has been removed from this interface. Config is now passed separately to service constructors.- The
backupproperty is now optional. Only implement it if using BACKUP or BOTH rollback strategies.See the migration guide for details.
Properties
getName()
Returns the name of the database handler for logging and display purposes.
getName(): string
Example:
getName(): string {
return 'PostgreSQL Handler';
}
db
Database connection and query interface.
db: IDB
Provides methods for executing queries and managing transactions. See IDB interface for details.
Type Safety:
Extend IDB with your database-specific methods:
interface IPostgresDB extends IDB {
query<T>(sql: string, params?: unknown[]): Promise<T[]>;
transaction(callback: (client: IPostgresDB) => Promise<void>): Promise<void>;
}
class PostgresHandler implements IDatabaseMigrationHandler<IDB> {
db: IPostgresDB; // Your extended type
// ...
}
schemaVersion
Schema version tracking interface.
schemaVersion: ISchemaVersion
Manages the schema version tracking table. See ISchemaVersion interface for details.
backup
Backup and restore interface (optional).
backup?: IBackup
Optional: Only required for BACKUP or BOTH rollback strategies. Can be omitted when using DOWN or NONE strategies.
Handles database backup and restore operations. See IBackup interface for details.
Complete Example
PostgreSQL Handler
import { Pool } from 'pg';
import {
IDatabaseMigrationHandler,
ISchemaVersion,
IBackup,
ISqlDB
} from '@migration-script-runner/core';
// Database connection with ISqlDB
class PostgresDB implements ISqlDB {
constructor(private pool: Pool) {}
async checkConnection(): Promise<void> {
try {
await this.pool.query('SELECT 1');
} catch (error) {
throw new Error(`PostgreSQL connection failed: ${error.message}`);
}
}
async query(sql: string): Promise<unknown> {
const result = await this.pool.query(sql);
return result.rows;
}
}
// Schema version tracking
class PostgresSchemaVersion implements ISchemaVersion {
migrationRecords: IMigrationScript;
constructor(private db: PostgresDB) {
this.migrationRecords = new PostgresMigrationScript(db);
}
async isInitialized(): Promise<boolean> {
const result = await this.db.query(`
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_name = 'schema_version'
)
`);
return result[0].exists;
}
async createTable(): Promise<void> {
await this.db.query(`
CREATE TABLE IF NOT EXISTS schema_version (
timestamp BIGINT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
executed BIGINT NOT NULL,
duration INTEGER NOT NULL,
username VARCHAR(255) NOT NULL,
result TEXT
)
`);
}
async validateTable(): Promise<void> {
// Validate table structure
}
}
// Backup implementation (optional)
class PostgresBackup implements IBackup {
constructor(private db: PostgresDB, private config: Config) {}
async backup(): Promise<string> {
const timestamp = Date.now();
const filename = `backup-${timestamp}.sql`;
const filepath = path.join(this.config.backup.folder, filename);
// Use pg_dump to create backup
await exec(`pg_dump ${dbUrl} > ${filepath}`);
return filepath;
}
async restore(backupData: string): Promise<void> {
// Use psql to restore backup
await exec(`psql ${dbUrl} < ${backupData}`);
}
}
// Complete handler implementation
export class PostgresHandler implements IDatabaseMigrationHandler<IDB> {
db: PostgresDB;
schemaVersion: PostgresSchemaVersion;
backup?: PostgresBackup;
constructor(pool: Pool, useBackup: boolean = false) {
this.db = new PostgresDB(pool);
this.schemaVersion = new PostgresSchemaVersion(this.db);
if (useBackup) {
this.backup = new PostgresBackup(this.db, config);
}
}
getName(): string {
return 'PostgreSQL Handler';
}
}
MongoDB Handler
import { MongoClient } from 'mongodb';
import {
IDatabaseMigrationHandler,
ISchemaVersion,
IDB
} from '@migration-script-runner/core';
class MongoDB implements IDB {
constructor(private client: MongoClient) {}
async checkConnection(): Promise<void> {
try {
await this.client.db('admin').command({ ping: 1 });
} catch (error) {
throw new Error(`MongoDB connection failed: ${error.message}`);
}
}
getCollection(name: string) {
return this.client.db().collection(name);
}
}
class MongoSchemaVersion implements ISchemaVersion {
migrationRecords: IMigrationScript;
constructor(private db: MongoDB) {
this.migrationRecords = new MongoMigrationScript(db);
}
async isInitialized(): Promise<boolean> {
const collections = await this.db.getCollection('schema_version').find().limit(1).toArray();
return collections.length > 0;
}
async createTable(): Promise<void> {
await this.db.getCollection('schema_version').createIndex({ timestamp: 1 }, { unique: true });
}
async validateTable(): Promise<void> {
// Validate collection structure
}
}
export class MongoHandler implements IDatabaseMigrationHandler<IDB> {
db: MongoDB;
schemaVersion: MongoSchemaVersion;
constructor(client: MongoClient) {
this.db = new MongoDB(client);
this.schemaVersion = new MongoSchemaVersion(this.db);
}
getName(): string {
return 'MongoDB Handler';
}
}
Usage
import { MigrationScriptExecutor, Config } from '@migration-script-runner/core';
import { PostgresHandler } from './postgres-handler';
import { Pool } from 'pg';
// Create database connection
const pool = new Pool({
host: 'localhost',
database: 'myapp',
user: 'postgres',
password: 'password'
});
// Create handler
const handler = new PostgresHandler(pool, true); // with backup
// Create executor
const config = new Config();
const executor = new MigrationScriptExecutor({ handler }, config);
// Run migrations
const result = await executor.up();