Migrating from v0.7.x to v0.8.0

Guide for upgrading from v0.7.x to v0.8.0 of Migration Script Runner.

Table of Contents

  1. Overview
    1. What’s New in v0.8.0
    2. Migration Effort
  2. Prerequisites
  3. Upgrade Steps
    1. 1. Update Package
    2. 2. No Code Changes Required
  4. New Features (Optional Adoption)
    1. Feature 1: Migration Locking
      1. 1.1 Implement ILockingService
      2. 1.2 Add Locking Service to Handler
      3. 1.3 Configure Locking (Optional)
      4. 1.4 Use Lock CLI Commands
    2. Feature 2: Handler Generic Type Parameter
      1. BEFORE (v0.7.x):
      2. AFTER (v0.8.0):
  5. Bug Fixes
    1. Down Migration TypeError Fixed
  6. Backwards Compatibility
  7. Common Migration Scenarios
    1. Scenario 1: Basic Upgrade (No New Features)
    2. Scenario 2: Enable Locking for Production
    3. Scenario 3: Improve Adapter Type Safety
  8. Testing Your Migration
    1. 1. Run Tests
    2. 2. Test Lock Commands (if enabled)
    3. 3. Test Down Migrations
  9. Rollback Plan
  10. Getting Help
  11. References
  12. Summary

Overview

v0.8.0 adds production-ready features for preventing concurrent migrations and improving adapter type safety. This release has no breaking changes - it’s fully backwards compatible with v0.7.x.

What’s New in v0.8.0

  • 🔒 Migration Locking - Prevent concurrent migrations with database-level locking mechanism
  • 🛡️ Lock Ownership Verification - Two-phase locking prevents race conditions
  • 🖥️ Lock CLI Commands - lock:status and lock:release commands for lock management
  • 🔧 Handler Generic Type - Type-safe handler access in adapters (no more casting!)
  • 🐛 Down Migration Fix - Fixed TypeError when rolling back migrations
  • 📦 npm Provenance - Enhanced supply chain security with build provenance

Migration Effort

Estimated Time: 5-10 minutes (optional feature adoption)

Complexity: Low

  • No breaking changes - fully backwards compatible
  • Locking is an optional feature you can enable when ready
  • Handler generic type is optional for better type safety

Prerequisites

Before upgrading, ensure:

  • You’re currently on v0.7.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.8.0

Or with yarn:

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

2. No Code Changes Required

Your existing code continues to work without modifications.

All v0.8.0 features are backwards compatible:

  • Locking is disabled by default
  • Handler generic type has a sensible default
  • Bug fixes are transparent

New Features (Optional Adoption)

Feature 1: Migration Locking

What it does: Prevents multiple migration processes from running simultaneously.

Why you need it:

  • Prevents race conditions in production
  • Avoids corrupted migration state
  • Essential for multi-instance deployments

1.1 Implement ILockingService

Create a locking service for your database:

import { ILockingService, ILockStatus, IDB } from '@migration-script-runner/core';

class MyDatabaseLockingService implements ILockingService<IMyDB> {
    constructor(private db: IMyDB) {}

    async acquireLock(executorId: string, timeout: number): Promise<boolean> {
        // Database-specific lock acquisition
        // Example for SQL databases:
        const result = await this.db.query(
            `INSERT INTO migration_locks (executor_id, locked_at, expires_at)
             VALUES ($1, NOW(), NOW() + INTERVAL '${timeout}ms')
             ON CONFLICT (id) DO NOTHING
             RETURNING id`,
            [executorId]
        );
        return result.rows.length > 0;
    }

    async verifyLockOwnership(executorId: string): Promise<boolean> {
        const result = await this.db.query(
            'SELECT executor_id FROM migration_locks WHERE id = 1',
            []
        );
        return result.rows[0]?.executor_id === executorId;
    }

    async releaseLock(executorId: string): Promise<void> {
        await this.db.query(
            'DELETE FROM migration_locks WHERE executor_id = $1',
            [executorId]
        );
    }

    async forceReleaseLock(): Promise<void> {
        await this.db.query('DELETE FROM migration_locks WHERE id = 1', []);
    }

    async checkAndReleaseExpiredLock(): Promise<void> {
        await this.db.query(
            'DELETE FROM migration_locks WHERE expires_at < NOW()',
            []
        );
    }

    async getLockStatus(): Promise<ILockStatus> {
        const result = await this.db.query(
            `SELECT executor_id, locked_at, expires_at
             FROM migration_locks WHERE id = 1`,
            []
        );

        if (result.rows.length === 0) {
            return {
                isLocked: false,
                lockedBy: null,
                lockedAt: null,
                expiresAt: null
            };
        }

        const row = result.rows[0];
        return {
            isLocked: true,
            lockedBy: row.executor_id,
            lockedAt: new Date(row.locked_at),
            expiresAt: new Date(row.expires_at),
            processId: row.executor_id.split('-')[1] // Extract PID from executor ID
        };
    }
}

1.2 Add Locking Service to Handler

import { IDatabaseMigrationHandler } from '@migration-script-runner/core';

class MyDatabaseHandler implements IDatabaseMigrationHandler<IMyDB> {
    // ... existing properties ...

    // Add locking service (NEW in v0.8.0)
    lockingService?: ILockingService<IMyDB>;

    constructor(db: IMyDB) {
        this.db = db;
        this.schemaVersion = new MySchemaVersion(db);
        this.backup = new MyBackupService(db);

        // Enable locking
        this.lockingService = new MyDatabaseLockingService(db);
    }

    // ... existing methods ...
}

1.3 Configure Locking (Optional)

Default settings work for most cases, but you can customize:

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

const config = new Config();

// Locking configuration (all optional - these are defaults)
config.locking.enabled = true;           // Enable/disable locking
config.locking.timeout = 600000;         // 10 minutes in milliseconds
config.locking.retryAttempts = 0;        // Fail immediately (no retries)
config.locking.retryDelay = 1000;        // 1 second between retries

1.4 Use Lock CLI Commands

# Check current lock status
msr lock:status

# Force-release a stuck lock (requires --force flag for safety)
msr lock:release --force

# Disable locking for a single run
msr migrate --no-lock

Example output:

$ msr lock:status

Lock Status: LOCKED
Locked by: server-prod-12345-a1b2c3d4-e5f6-7890-abcd-ef1234567890
Locked at: 2025-12-18T10:00:00.000Z
Expires at: 2025-12-18T10:10:00.000Z
Process ID: 12345

Another migration is currently running.
If you believe this is a stale lock, use: msr lock:release --force

Feature 2: Handler Generic Type Parameter

What it does: Provides type-safe access to handler properties in adapters.

Why you need it: Eliminates casting and bracket notation workarounds in adapter code.

BEFORE (v0.7.x):

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

class MyAdapter extends MigrationScriptExecutor<IMyDB> {
    // Workaround: need to cast handler to access custom properties
    private getHandler(): MyHandler {
        return this['handler'] as MyHandler;  // ❌ Bracket notation + casting
    }

    getConnectionInfo() {
        const handler = this.getHandler();
        return {
            host: handler.config.host,
            port: handler.config.port
        };
    }
}

AFTER (v0.8.0):

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

// Specify handler type as second generic parameter
class MyAdapter extends MigrationScriptExecutor<IMyDB, MyHandler> {
    getConnectionInfo() {
        // ✅ Type-safe access - no casting needed!
        return {
            host: this.handler.config.host,
            port: this.handler.config.port
        };
    }

    useCustomMethod() {
        // ✅ IDE autocomplete works!
        this.handler.customMethod();
    }
}

Benefits:

  • ✅ Full IDE autocomplete support
  • ✅ Compile-time type checking
  • ✅ No casting or workarounds needed
  • ✅ Cleaner, more maintainable code

Bug Fixes

Down Migration TypeError Fixed

Issue: Calling executor.down(targetVersion) threw TypeError: s.init is not a function.

Fix: Migrations from database are now properly matched with filesystem scripts before rollback.

Impact: Down migrations now work correctly. No code changes needed - fix is automatic.


Backwards Compatibility

100% backwards compatible with v0.7.x

  • All existing code works without changes
  • Locking is opt-in (disabled by default)
  • Handler generic type has sensible default
  • No deprecated features

Common Migration Scenarios

Scenario 1: Basic Upgrade (No New Features)

npm install @migration-script-runner/core@^0.8.0
# Done! Everything continues to work.

Scenario 2: Enable Locking for Production

  1. Implement ILockingService for your database
  2. Add lockingService to your handler
  3. (Optional) Configure locking timeout/retries
  4. Deploy to production

Estimated time: 30-45 minutes

Scenario 3: Improve Adapter Type Safety

  1. Add second generic parameter to your adapter class
  2. Remove casting workarounds
  3. Enjoy improved IDE support

Estimated time: 5-10 minutes


Testing Your Migration

1. Run Tests

npm test

Ensure all tests pass after upgrading.

2. Test Lock Commands (if enabled)

# Check lock status
msr lock:status

# Try concurrent migrations (should fail)
msr migrate &  # Start first migration
msr migrate    # Second should fail with lock error

3. Test Down Migrations

# Run migrations
msr migrate

# Roll back to previous version
msr down 202501010001

# Should work without errors

Rollback Plan

If you encounter issues, rollback is simple:

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

Since there are no breaking changes, rolling back is safe and won’t require code changes.


Getting Help

If you encounter issues during migration:

  1. Check GitHub Issues
  2. Review Locking Documentation
  3. See Lock Commands Guide
  4. Open a new issue with v0.8.0 label

References


Summary

v0.8.0 is a production-ready release that adds essential features for multi-instance deployments while maintaining full backwards compatibility.

Key Takeaways:

  • ✅ No breaking changes - upgrade is safe
  • 🔒 Enable locking to prevent concurrent migrations
  • 🔧 Use handler generic type for better type safety
  • 🐛 Down migrations now work correctly
  • 📦 npm provenance for supply chain security

Recommended Actions:

  1. Upgrade immediately (backwards compatible)
  2. Enable locking for production environments
  3. Consider handler generic type for adapters
  4. Test down migrations if you use them

Happy migrating! 🚀