Migrating from v0.6.x to v0.7.0

Guide for upgrading from v0.6.x to v0.7.0 of Migration Script Runner.

Table of Contents

  1. Overview
    1. What’s New in v0.7.0
    2. Migration Effort
  2. Prerequisites
  3. Upgrade Steps
    1. 1. Update Package
    2. 2. Update Constructor Calls
      1. BEFORE (v0.6.x):
      2. AFTER (v0.7.0):
  4. Breaking Changes
    1. Constructor Signature Change
      1. Before (v0.6.x):
      2. After (v0.7.0):
    2. Service Property Removal (Facade Pattern)
      1. Before (v0.6.x):
      2. After (v0.7.0):
  5. New Features
    1. Extensible Configuration Loading
      1. IConfigLoader Interface
      2. Custom ConfigLoader Example
    2. Generic ConfigLoader
    3. IExecutorOptions Interface
  6. Testing After Migration
    1. Quick Validation
  7. Troubleshooting
    1. TypeScript Error: “Expected 1 arguments, but got 2”
    2. ConfigLoader Methods Not Found
    3. Custom Config Not Applied
    4. Property ‘backupService’ does not exist on type ‘MigrationScriptExecutor’
  8. API Changes Summary
  9. Need Help?
  10. What’s Next?

Overview

v0.7.0 improves adapter ergonomics, code maintainability, and architecture by implementing the Facade and Factory patterns. This release includes two breaking changes: constructor signature change and service property removal.

What’s New in v0.7.0

  • 🖥️ CLI Factory - Create command-line interfaces with built-in commands (migrate, list, down, validate, backup) - see CLI Adapter Development Guide
  • 🗂️ .env File Support - Automatically load configuration from .env, .env.production, .env.local files with priority control
  • 🔨 Breaking Change: Single Parameter Constructor - Config moved from second parameter into dependencies.config
  • 🔨 Breaking Change: Service Properties Removed - Internal services no longer exposed as public properties (use public API methods instead)
  • 🎨 Facade Pattern - Services grouped into 4 logical facades (core, execution, output, orchestration)
  • 🏭 Factory Pattern - Service initialization extracted to MigrationServicesFactory
  • 🔧 Protected Facades for Adapters - Adapters can extend executor and access protected service facades
  • Extensible Configuration Loading - New IConfigLoader interface and configLoader option
  • 🔧 Generic ConfigLoader - Adapters can extend ConfigLoader<C> with custom config types
  • 📦 Better Adapter Ergonomics - Simpler constructor signature and better extensibility
  • 🎯 Interface Segregation - New IExecutorOptions<DB> separates required from optional dependencies
  • Reduced Complexity - Constructor reduced from 142 lines to 23 lines (83% reduction)

Migration Effort

Estimated Time: 15-30 minutes

Complexity: Low-Medium

  • Low: Update constructor calls (simple parameter move)
  • Medium: Replace direct service access with public API methods (if you were accessing internal services)

Prerequisites

Before upgrading, ensure:

  • You’re currently on v0.6.x
  • Your tests are passing
  • You have TypeScript 4.5+ (for best generic inference)

Upgrade Steps

1. Update Package

npm install @migration-script-runner/core@^0.7.0

Or with yarn:

yarn upgrade @migration-script-runner/core@^0.7.0

2. Update Constructor Calls

REQUIRED: Move config from second parameter into dependencies object:

BEFORE (v0.6.x):

import { MigrationScriptExecutor, Config } from '@migration-script-runner/core';

const handler = new MyDatabaseHandler();
const config = new Config();

// Old: config as second parameter
const executor = new MigrationScriptExecutor<IDB>(
    { handler },
    config
);

AFTER (v0.7.0):

import { MigrationScriptExecutor, Config } from '@migration-script-runner/core';

const handler = new MyDatabaseHandler();
const config = new Config();

// New: config inside dependencies object
const executor = new MigrationScriptExecutor<IDB>({
    handler,
    config
});

Quick Find & Replace:

  • Find: }, config)
  • Replace: , config }

Breaking Changes

Constructor Signature Change

Impact: All code creating MigrationScriptExecutor instances

Details: The constructor now takes a single parameter. Config moved from second parameter into dependencies.config.

Before (v0.6.x):

constructor<DB extends IDB>(
    dependencies: IMigrationExecutorDependencies<DB>,
    config?: Config
)

After (v0.7.0):

constructor<DB extends IDB>(
    dependencies: IMigrationExecutorDependencies<DB>
)

Examples:

// Minimal usage (config auto-loaded)
const executor = new MigrationScriptExecutor<IDB>({ handler });

// With explicit config
const executor = new MigrationScriptExecutor<IDB>({
    handler,
    config: myConfig
});

// With custom logger
const executor = new MigrationScriptExecutor<IDB>({
    handler,
    config: myConfig,
    logger: new FileLogger()
});

// With all options
const executor = new MigrationScriptExecutor<IDB>({
    handler,
    config: myConfig,
    configLoader: new CustomConfigLoader(),
    logger: new FileLogger(),
    hooks: myHooks
});

Service Property Removal (Facade Pattern)

Impact: Code directly accessing internal service properties

Details: MigrationScriptExecutor no longer exposes internal services as public properties. Services are now grouped into protected facades for better encapsulation and adapter extensibility.

Before (v0.6.x):

const executor = new MigrationScriptExecutor<IDB>({ handler }, config);

// ✅ Public service access (v0.6.x)
const backupPath = await executor.backupService.backup();
const scripts = await executor.migrationScanner.scan();
const results = await executor.validationService.validateAll(scripts, config);
executor.logger.info('Migration complete');

After (v0.7.0):

const executor = new MigrationScriptExecutor<IDB>({ handler, config });

// ❌ Direct service access removed (v0.7.0)
// executor.backupService         // Property does not exist
// executor.migrationScanner      // Property does not exist
// executor.validationService     // Property does not exist
// executor.logger                // Property does not exist

// ✅ Use public API methods instead:
const backupPath = await executor.createBackup();         // Public method
await executor.list();                                     // Public method
await executor.validate();                                 // Public method
await executor.up();                                       // Public method

Rationale: This change improves encapsulation and reduces the public API surface. Instead of exposing 11+ internal services, MigrationScriptExecutor now only exposes high-level migration operations.

Migration Strategy:

  1. Replace direct service access with public API methods:
    • executor.backupService.backup()executor.createBackup()
    • executor.backupService.restore()executor.restoreFromBackup()
    • executor.backupService.deleteBackup()executor.deleteBackup()
    • executor.migrationScanner.scan() → Use executor.list() or internal logic
    • executor.validationService.validateAll()executor.validate()
  2. For adapters needing service access, extend MigrationScriptExecutor:
// v0.7.0: Adapters can access protected facades
export class PostgresExecutor<DB extends IDB> extends MigrationScriptExecutor<DB> {
    public async customBackup(): Promise<string> {
        // Access protected facades
        this.output.logger.info('Creating Postgres backup...');
        const scripts = await this.core.scanner.scan();
        return this.core.backup.backup();
    }

    public async migrateWithMetrics(): Promise<IMigrationResult<DB>> {
        const startTime = Date.now();

        // Access protected orchestrators
        const result = await this.orchestration.workflow.migrateAll();

        const duration = Date.now() - startTime;
        this.output.logger.info(`Migration completed in ${duration}ms`);

        return result;
    }
}

Protected Facades (v0.7.0):

  • core: Business logic services (scanner, schemaVersion, migration, validation, backup, rollback)
  • execution: Execution services (selector, runner, transactionManager)
  • output: Output services (logger, renderer)
  • orchestration: Orchestrators (workflow, validation, reporting, error, hooks, rollback)

New Features

Extensible Configuration Loading

New in v0.7.0: Database adapters can now provide custom configuration loaders to handle database-specific environment variables.

IConfigLoader Interface

interface IConfigLoader<C extends Config = Config> {
    load(overrides?: Partial<C>, options?: ConfigLoaderOptions): C;
    applyEnvironmentVariables(config: C): void;
}

Custom ConfigLoader Example

Adapters can extend ConfigLoader to add custom environment variable handling:

import { ConfigLoader, Config } from '@migration-script-runner/core';

class PostgreSqlConfigLoader extends ConfigLoader {
    applyEnvironmentVariables(config: Config): void {
        // Apply base MSR_* variables
        super.applyEnvironmentVariables(config);

        // Add POSTGRES_* variables
        if (process.env.POSTGRES_HOST) {
            (config as any).host = process.env.POSTGRES_HOST;
        }
        if (process.env.POSTGRES_PORT) {
            (config as any).port = parseInt(process.env.POSTGRES_PORT);
        }
        if (process.env.POSTGRES_DATABASE) {
            (config as any).database = process.env.POSTGRES_DATABASE;
        }
    }
}

// Use custom loader
const executor = new MigrationScriptExecutor<IDB>({
    handler: myPostgresHandler,
    configLoader: new PostgreSqlConfigLoader()
});

Generic ConfigLoader

New in v0.7.0: ConfigLoader is now generic, allowing adapters to extend it with custom configuration types:

class ConfigLoader<C extends Config = Config> implements IConfigLoader<C> {
    load(overrides?: Partial<C>, options?: ConfigLoaderOptions): C { ... }
    applyEnvironmentVariables(config: C): void { ... }
}

This enables type-safe configuration loading for adapter-specific config properties.

IExecutorOptions Interface

New in v0.7.0: Separates optional dependencies for cleaner interface design:

interface IExecutorOptions<DB extends IDB> {
    config?: Config;
    configLoader?: IConfigLoader;
    logger?: ILogger;
    hooks?: IMigrationHooks<DB>;
    backupService?: IBackupService;
    schemaVersionService?: ISchemaVersionService<DB>;
    migrationRenderer?: IMigrationRenderer<DB>;
    renderStrategy?: IRenderStrategy<DB>;
    migrationService?: IMigrationService<DB>;
    migrationScanner?: IMigrationScanner<DB>;
    validationService?: IMigrationValidationService<DB>;
    metricsCollectors?: IMetricsCollector[];
    rollbackService?: IRollbackService<DB>;
    loaderRegistry?: ILoaderRegistry<DB>;
}

interface IMigrationExecutorDependencies<DB extends IDB> extends IExecutorOptions<DB> {
    handler: IDatabaseMigrationHandler<DB>;  // Required
    configLoader?: IConfigLoader;             // Optional (v0.7.0)
}

This makes it easier for adapters to work with optional services while keeping handler as the only required dependency.


Testing After Migration

After updating your code, verify:

  1. Build succeeds: npm run build or tsc
  2. Tests pass: npm test
  3. Migrations run: Test your migration execution
  4. Config loading works: Verify environment variables are applied correctly

Quick Validation

// Test that constructor works with new signature
const executor = new MigrationScriptExecutor<IDB>({
    handler: myHandler,
    config: new Config()
});

// Verify config is loaded
console.log(executor['config'].folder); // Should print configured folder

// Test auto-loading (config not provided)
const autoExecutor = new MigrationScriptExecutor<IDB>({
    handler: myHandler
});
console.log(autoExecutor['config']); // Should have auto-loaded config

Troubleshooting

TypeScript Error: “Expected 1 arguments, but got 2”

Cause: You’re still using the old v0.6.x constructor signature.

Fix: Move config from second parameter into dependencies object:

// ❌ Old (v0.6.x)
new MigrationScriptExecutor({ handler }, config)

// ✅ New (v0.7.0)
new MigrationScriptExecutor({ handler, config })

ConfigLoader Methods Not Found

Cause: Trying to use ConfigLoader.load() as a static method.

Fix: Use instance methods instead:

// ❌ Old (v0.6.x)
const config = ConfigLoader.load();

// ✅ New (v0.7.0)
const loader = new ConfigLoader();
const config = loader.load();

Custom Config Not Applied

Cause: Config not passed correctly in dependencies object.

Fix: Ensure config is passed as config property:

// ✅ Correct
new MigrationScriptExecutor({
    handler,
    config: myConfig  // Must be named 'config'
})

Property ‘backupService’ does not exist on type ‘MigrationScriptExecutor’

Cause: Trying to access internal service properties that were removed in v0.7.0.

Fix: Use public API methods or extend the executor:

// ❌ Old (v0.6.x)
const backupPath = await executor.backupService.backup();
await executor.migrationScanner.scan();

// ✅ New (v0.7.0) - Use public API
const backupPath = await executor.createBackup();
await executor.list();

// ✅ Or extend for adapter-specific needs
class CustomExecutor extends MigrationScriptExecutor {
    async customBackup() {
        return this.core.backup.backup();  // Protected access
    }
}

API Changes Summary

Change v0.6.x v0.7.0 Breaking?
Constructor Parameters (dependencies, config?) (dependencies) ✅ Yes
Config Location Second parameter Inside dependencies ✅ Yes
Service Properties Public (11+ properties) Removed ✅ Yes
Service Access executor.backupService, executor.migrationScanner, etc. Use public methods or extend executor ✅ Yes
Facade Pattern N/A 4 protected facades No (internal)
Factory Pattern N/A MigrationServicesFactory No (internal)
executeBeforeMigrate Executor method MigrationWorkflowOrchestrator No (internal)
ConfigLoader Methods Static Instance ⚠️ Minor
ConfigLoader Generic Not generic ConfigLoader<C> No
IConfigLoader Interface N/A New No
IExecutorOptions Interface N/A New No
configLoader Option N/A New No

Need Help?


What’s Next?

After successfully migrating to v0.7.0, you can:

  1. Explore configLoader: Create custom configuration loaders for your database adapter
  2. Simplify your code: Remove any config-passing boilerplate
  3. Build adapters: Use the improved ergonomics to build database-specific adapters
  4. Stay updated: Watch the repository for v0.8.0 announcements