Migrating from v0.3.x to v0.4.0
Comprehensive guide for upgrading from v0.3.x to v0.4.0, including breaking changes, new features, and migration steps.
Table of contents
- Overview
- Breaking Changes Summary
- Breaking Change #1: filePattern → filePatterns
- Breaking Change #2: MigrationScriptExecutor API Methods
- Breaking Change #3: IDB.checkConnection() Required
- Breaking Change #4: ISchemaVersion Property Rename
- Breaking Change #5: IMigrationScript Method Rename
- Breaking Change #6: Service Method Renames
- New Features
- Migration Checklist
- Troubleshooting
- Getting Help
Overview
Version 0.4.0 introduces significant new features and API improvements:
- ✨ SQL migration files support (
.up.sql/.down.sql) - 🔄 Simplified API with clearer method names
- 🔍 Dry run mode for safe migration preview
- 📊 Execution summary logging with file rotation
- ✅ Connection validation with
checkConnection() - 🛡️ Improved type safety with loader architecture
Compatibility: v0.4.0 includes breaking changes that require code updates. Follow this guide to migrate smoothly.
Breaking Changes Summary
| Change | Impact | Migration Effort |
|---|---|---|
filePattern → filePatterns | High | Low - Simple property rename |
| API method renames | High | Medium - Update all call sites |
IDB.checkConnection() required | High | Low - Add one method |
ISchemaVersion.migrations → migrationRecords | Medium | Low - Property rename |
IMigrationScript.getAll() → getAllExecuted() | Low | Low - Method rename |
| Service method renames | Low | Low - Internal API changes |
Breaking Change #1: filePattern → filePatterns
What Changed
The filePattern property has been removed and replaced with filePatterns (array) to support multiple file types.
Reason: v0.4.0 adds SQL migration support, requiring matching multiple file patterns (.ts, .js, .up.sql).
Migration Steps
Before (v0.3.x):
const config = new Config();
config.filePattern = /^V(\d{12})_.*\.ts$/;
After (v0.4.0):
const config = new Config();
config.filePatterns = [
/^V(\d{12})_.*\.ts$/
];
Default Behavior
If you relied on the default filePattern, v0.4.0’s default filePatterns includes TypeScript, JavaScript, and SQL:
// Default in v0.4.0
config.filePatterns = [
/^V(\d{12})_.*\.ts$/,
/^V(\d{12})_.*\.js$/,
/^V(\d{12})_.*\.up\.sql$/
];
Migration Examples
TypeScript only:
// v0.3.x
config.filePattern = /^V(\d{12})_.*\.ts$/;
// v0.4.0
config.filePatterns = [/^V(\d{12})_.*\.ts$/];
TypeScript and JavaScript:
// v0.3.x (not possible without custom logic)
config.filePattern = /^V(\d{12})_.*\.(ts|js)$/;
// v0.4.0 (cleaner)
config.filePatterns = [
/^V(\d{12})_.*\.ts$/,
/^V(\d{12})_.*\.js$/
];
Custom prefix:
// v0.3.x
config.filePattern = /^MIG_(\d+)_(.+)\.ts$/;
// v0.4.0
config.filePatterns = [/^MIG_(\d+)_(.+)\.ts$/];
Breaking Change #2: MigrationScriptExecutor API Methods
What Changed
Method names have been simplified and clarified:
| v0.3.x Method | v0.4.0 Method | Notes |
|---|---|---|
migrate() | up() or migrate() | Both work, migrate() is alias |
migrateTo(version) | Removed | Use up(version) instead |
downTo(version) | down(version) | Clearer naming |
Reason: Consolidates migration methods and aligns with common migration tool conventions (up / down).
Migration Steps
Change 1: migrate() and migrateTo()
Before (v0.3.x):
// Run all pending migrations
await executor.migrate();
// Migrate to specific version
await executor.migrateTo(202501280300);
After (v0.4.0):
// Run all pending migrations (two options)
await executor.up(); // New preferred method
await executor.migrate(); // Still works (alias to up())
// Migrate to specific version
await executor.up(202501280300); // migrateTo() removed
Change 2: downTo() → down()
Before (v0.3.x):
await executor.downTo(202501280100);
After (v0.4.0):
await executor.down(202501280100);
Full Example
Before (v0.3.x):
import { MigrationScriptExecutor, Config } from '@migration-script-runner/core';
const executor = new MigrationScriptExecutor(handler, config);
// Run all migrations
const result = await executor.migrate();
// Migrate to version 5
await executor.migrateTo(202501280005);
// Rollback to version 2
await executor.downTo(202501280002);
After (v0.4.0):
import { MigrationScriptExecutor, Config } from '@migration-script-runner/core';
const executor = new MigrationScriptExecutor(handler, config);
// Run all migrations (choose one)
const result = await executor.up(); // Preferred
const result = await executor.migrate(); // Also works
// Migrate to version 5
await executor.up(202501280005);
// Rollback to version 2
await executor.down(202501280002);
Breaking Change #3: IDB.checkConnection() Required
What Changed
The IDB interface now requires a checkConnection() method. This method is called before migration operations to fail fast if the database connection is invalid.
interface IDB {
checkConnection(): Promise<void>;
[key: string]: unknown;
}
Reason: Provides early detection of connection issues, giving clearer error messages before attempting migrations.
Migration Steps
Add checkConnection() to your database implementation:
PostgreSQL Example:
import { Pool } from 'pg';
import { IDB } from '@migration-script-runner/core';
class PostgresDB implements IDB {
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;
}
}
MongoDB Example:
import { MongoClient } from 'mongodb';
import { 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);
}
}
MySQL Example:
import { Connection } from 'mysql2/promise';
import { IDB } from '@migration-script-runner/core';
class MySQLDB implements IDB {
constructor(private connection: Connection) {}
async checkConnection(): Promise<void> {
try {
await this.connection.ping();
} catch (error) {
throw new Error(`MySQL connection failed: ${error.message}`);
}
}
async query(sql: string): Promise<unknown> {
const [rows] = await this.connection.execute(sql);
return rows;
}
}
When It’s Called
checkConnection() is called automatically before:
executor.up()executor.down()executor.validate()
Breaking Change #4: ISchemaVersion Property Rename
What Changed
The migrations property has been renamed to migrationRecords for clarity.
// v0.3.x
interface ISchemaVersion {
migrations: IMigrationScript;
}
// v0.4.0
interface ISchemaVersion {
migrationRecords: IMigrationScript;
}
Reason: “migrationRecords” is more descriptive and distinguishes between migration files and execution records.
Migration Steps
Before (v0.3.x):
class MySchemaVersion implements ISchemaVersion {
migrations: IMigrationScript;
constructor() {
this.migrations = new MigrationScriptDAO();
}
}
After (v0.4.0):
class MySchemaVersion implements ISchemaVersion {
migrationRecords: IMigrationScript;
constructor() {
this.migrationRecords = new MigrationScriptDAO();
}
}
Impact: Only affects custom ISchemaVersion implementations. Most users use MSR’s built-in SchemaVersionService.
Breaking Change #5: IMigrationScript Method Rename
What Changed
// v0.3.x
interface IMigrationScript {
getAll(): Promise<IMigrationInfo[]>;
}
// v0.4.0
interface IMigrationScript {
getAllExecuted(): Promise<IMigrationInfo[]>;
}
Reason: Method name now explicitly describes what it returns (executed migrations only, not all migration files).
Migration Steps
Before (v0.3.x):
class MigrationScriptDAO implements IMigrationScript {
async getAll(): Promise<IMigrationInfo[]> {
return await this.db.query('SELECT * FROM schema_version');
}
}
After (v0.4.0):
class MigrationScriptDAO implements IMigrationScript {
async getAllExecuted(): Promise<IMigrationInfo[]> {
return await this.db.query('SELECT * FROM schema_version');
}
}
Impact: Only affects custom IMigrationScript implementations.
Breaking Change #6: Service Method Renames
What Changed
Internal service methods have been renamed for consistency:
| Interface | v0.3.x Method | v0.4.0 Method |
|---|---|---|
IMigrationService | readMigrationScripts() | findMigrationScripts() |
IMigrationService | getBeforeMigrateScript() | findBeforeMigrateScript() |
IBackup | restore(data) | restore(backupData) |
Reason: “find” is more accurate for discovery operations; “backupData” parameter is clearer.
Migration Steps
Only needed if you’re using these services directly or implementing custom services.
Before (v0.3.x):
const migrationService = new MigrationService();
const scripts = await migrationService.readMigrationScripts(config);
const beforeMigrate = await migrationService.getBeforeMigrateScript(config);
After (v0.4.0):
const migrationService = new MigrationService();
const scripts = await migrationService.findMigrationScripts(config);
const beforeMigrate = await migrationService.findBeforeMigrateScript(config);
Impact: Low - These are internal APIs rarely used directly.
New Features
1. SQL Migration Files
v0.4.0 adds first-class support for SQL migration files alongside TypeScript migrations.
Quick Start:
-- migrations/V202501280100_create_users.up.sql
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE
);
-- migrations/V202501280100_create_users.down.sql
DROP TABLE IF EXISTS users;
ISqlDB Interface:
For SQL migrations, implement ISqlDB:
import { ISqlDB } from '@migration-script-runner/core';
class PostgresDB implements ISqlDB {
async query(sql: string): Promise<unknown> {
const result = await this.pool.query(sql);
return result.rows;
}
async checkConnection(): Promise<void> {
await this.pool.query('SELECT 1');
}
}
Learn More: SQL Migrations Guide
2. Dry Run Mode
Preview migrations without executing them:
const config = new Config();
config.dryRun = true;
const result = await executor.up();
// Shows what would be executed, but doesn't run anything
Use cases:
- CI/CD validation
- Production safety checks
- Migration planning
Learn More: Configuration - dryRun
3. Execution Summary Logging
Automatic logging of migration execution to JSON/text files:
const config = new Config();
config.logging.enabled = true;
config.logging.folder = './logs';
config.logging.format = 'JSON';
config.logging.maxFiles = 10;
await executor.up();
// Creates: logs/migration-summary-2025-01-28T10-30-00.json
Log contents:
- All executed migrations
- Backup operations
- Rollback events
- Errors and timing
Learn More: Configuration - Logging Settings
4. Loader Architecture
Extensible system for supporting multiple file types:
import {
LoaderRegistry,
TypeScriptLoader,
SqlLoader
} from '@migration-script-runner/core';
// Default loaders
const registry = LoaderRegistry.createDefault();
// Includes: TypeScriptLoader, SqlLoader
// Custom loader
class PythonLoader implements IMigrationScriptLoader {
canHandle(filePath: string): boolean {
return filePath.endsWith('.py');
}
async load(script: MigrationScript): Promise<IRunnableScript> {
// Load and execute Python migration
}
getName(): string {
return 'PythonLoader';
}
}
registry.register(new PythonLoader());
Learn More: API Reference - Loaders
Migration Checklist
Use this checklist to ensure a complete migration:
Step 1: Update Configuration
- Change
config.filePatterntoconfig.filePatterns(array) - Update any custom file pattern logic
- Review default patterns if you relied on defaults
Step 2: Update API Calls
- Replace
migrateTo(version)withup(version) - Replace
downTo(version)withdown(version) - Update
migrate()toup()(optional, migrate() still works)
Step 3: Update Database Handler
- Add
checkConnection()method to IDB implementation - Test connection validation
- (Optional) Implement ISqlDB for SQL migrations
Step 4: Update Custom Implementations
- Rename
migrations→migrationRecordsin ISchemaVersion implementations - Rename
getAll()→getAllExecuted()in IMigrationScript implementations - Update service method calls if using internal APIs
Step 5: Test Migration
- Run migrations in development
- Test rollback functionality
- Verify all migrations execute correctly
- Test dry run mode
- Validate connection checking
Step 6: Update Documentation
- Update deployment scripts
- Update team documentation
- Update CI/CD pipelines
Troubleshooting
Error: “filePattern is not defined”
Problem: You’re still using config.filePattern in v0.4.0
Solution:
// Change this:
config.filePattern = /^V(\d{12})_.*\.ts$/;
// To this:
config.filePatterns = [/^V(\d{12})_.*\.ts$/];
Error: “migrateTo is not a function”
Problem: migrateTo() was removed in v0.4.0
Solution:
// Change this:
await executor.migrateTo(202501280005);
// To this:
await executor.up(202501280005);
Error: “checkConnection is not a function”
Problem: Your IDB implementation doesn’t have checkConnection()
Solution:
class MyDB implements IDB {
// Add this method:
async checkConnection(): Promise<void> {
// Verify your connection works
await this.yourDatabase.ping();
}
}
Error: “migrations property not found”
Problem: ISchemaVersion property was renamed
Solution:
// Change this:
class MySchemaVersion implements ISchemaVersion {
migrations: IMigrationScript;
}
// To this:
class MySchemaVersion implements ISchemaVersion {
migrationRecords: IMigrationScript;
}
Getting Help
If you encounter issues during migration:
- Check this guide - Most common issues are covered above
- Review the changelog - GitHub Releases
- Search issues - GitHub Issues
- Ask for help - GitHub Discussions
- Report bugs - Create an issue