Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"permissions": {
"allow": [
"Bash(npm test:*)",
"Bash(npm run coverage:*)",
"Bash(npm run test:mocha:report:*)",
"Bash(gh issue view:*)",
"Bash(npm install:*)",
"Bash(npm run build:*)",
"Bash(npm run test:coverage:*)",
"Bash(ls:*)",
"Bash(done)",
"Bash(gh pr view:*)",
"Bash(npm run test:one:*)",
"Bash(cat:*)",
"Bash(npx nyc report:*)",
"Bash(find:*)",
"Bash(npx nyc:*)"
],
"deny": [],
"ask": []
}
}
63 changes: 24 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,60 +21,36 @@ MSR provides a lightweight, flexible framework for managing database migrations

---

## 🎉 What's New in v0.7.0
## 🎉 What's New in v0.8.0

**Enhanced architecture, adapter extensibility, and improved maintainability:**
**Production-ready locking, enhanced type safety, and critical bug fixes:**

- **🎨 Facade Pattern** - Services grouped into 4 logical facades (core, execution, output, orchestration) for better code organization
- **🏭 Factory Pattern** - Service initialization extracted to dedicated factory, reducing constructor complexity by 83%
- **🔧 Protected Facades** - Adapters can extend MigrationScriptExecutor and access internal services through protected facades
- **✨ Extensible Configuration** - New `IConfigLoader` interface allows adapters to customize environment variable handling
- **🗂️ .env File Support** - Load configuration from `.env`, `.env.production`, `.env.local` files with configurable priority
- **🔨 Simplified Constructor** - Single parameter constructor with config moved into dependencies object (**BREAKING**)
- **🔒 Better Encapsulation** - Internal services no longer exposed as public properties (**BREAKING**)
- **⚡ Reduced Complexity** - Constructor reduced from 142 lines to 23 lines (83% reduction)
- **📐 Workflow Ownership** - `executeBeforeMigrate()` moved to MigrationWorkflowOrchestrator for cleaner architecture
- **✨ 100% Test Coverage** - All statements, branches, functions, and lines covered (1228/1228 tests passing)
- **🔒 Migration Locking** - Prevent concurrent migrations with database-level locking mechanism for multi-instance deployments
- **🛡️ Lock Ownership Verification** - Two-phase locking with ownership verification prevents race conditions and ensures safe execution
- **🖥️ Lock CLI Commands** - New `lock:status` and `lock:release` commands for managing locks in production environments
- **🔧 Handler Generic Type** - Type-safe handler access in adapters with optional second generic parameter (no more casting!)
- **🐛 Down Migration Fix** - Fixed TypeError when rolling back migrations (migration records now properly matched with filesystem scripts)
- **📦 npm Provenance** - Enhanced supply chain security with build provenance attestations
- **✨ 100% Backwards Compatible** - Zero breaking changes from v0.7.x, all features are opt-in

**⚠️ BREAKING CHANGES in v0.7.0:** Constructor signature changed (config moved into dependencies), and service properties removed (use public API methods instead). Migration takes 15-30 minutes. See the [v0.6.x → v0.7.0 Migration Guide](https://migration-script-runner.github.io/msr-core/version-migration/v0.6-to-v0.7) for step-by-step instructions.
**✅ NO BREAKING CHANGES in v0.8.0:** Fully backwards compatible with v0.7.x. Locking is disabled by default and can be enabled when ready. Handler generic type is optional with sensible defaults. See the [v0.7.x → v0.8.0 Migration Guide](https://migration-script-runner.github.io/msr-core/version-migration/v0.7-to-v0.8) for upgrade instructions and new features.

**[→ View architecture docs](https://migration-script-runner.github.io/msr-core/development/architecture/design-patterns)**
**[→ View locking documentation](https://migration-script-runner.github.io/msr-core/configuration/locking-settings)**

---

## 📜 Previous Releases

### v0.6.0

**Enhanced type safety, metrics collection, and multi-format configuration:**

- **🛡️ Generic Type Parameters** - Database-specific type safety with `<DB extends IDB>` generics throughout the API (**BREAKING**: type parameters now required)
- **📊 Metrics Collection** - Built-in collectors for observability with console, JSON, CSV, and logger-based output
- **📄 YAML, TOML, and XML Support** - Use your preferred config format (`.yaml`, `.toml`, `.xml`) alongside JS/JSON
- **🔌 Plugin Architecture** - Extensible loader system with optional peer dependencies keeps core lightweight
- **🎚️ Log Level Control** - Configurable log levels (`error`, `warn`, `info`, `debug`) to control output verbosity
- **💡 Better Error Messages** - Actionable error messages with installation instructions when formats aren't available

**[→ View migration guide](https://migration-script-runner.github.io/msr-core/version-migration/v0.5-to-v0.6)**

### v0.5.0

**Production-grade transaction management and cloud-native configuration:**

- **🔒 Transaction Management** - Configurable modes (per-migration, per-batch, none) with automatic retry logic and isolation level control
- **⚙️ Environment Variables** - Complete MSR_* configuration support following 12-factor app principles for Docker, Kubernetes, and CI/CD
- **📊 Enhanced Hooks** - Transaction lifecycle hooks for monitoring and metrics collection
- **🚀 100% Backward Compatible** - Zero breaking changes from v0.4.x

**[→ View migration guide](https://migration-script-runner.github.io/msr-core/version-migration/v0.4-to-v0.5)**
For information about previous releases, see the [Version Migration Guide](https://migration-script-runner.github.io/msr-core/version-migration/).

---

## ✨ Features

- **🖥️ CLI Factory** - Built-in command-line interface with migrate, list, down, validate, and backup commands (v0.7.0)
- **🔒 Migration Locking** - Database-level locking prevents concurrent migrations in multi-instance deployments (v0.8.0)
- **🖥️ CLI Factory** - Built-in command-line interface with migrate, list, down, validate, backup, and lock commands (v0.7.0+)
- **🔌 Database Agnostic** - Works with any database (SQL, NoSQL, NewSQL) by implementing a simple interface
- **🛡️ Type Safe** - Full TypeScript support with complete type definitions
- **🛡️ Type Safe** - Full TypeScript support with complete type definitions and handler generics (v0.8.0)
- **💾 Smart Rollback** - Multiple strategies: backup/restore, down() methods, both, or none
- **🔒 Transaction Control** - Configurable transaction modes with automatic retry and isolation levels (v0.5.0)
- **⚙️ Environment Variables** - Full 12-factor app configuration support with MSR_* variables (v0.5.0)
Expand Down Expand Up @@ -243,6 +219,15 @@ npx my-db-migrate validate
# Create database backup
npx my-db-migrate backup

# Check lock status (v0.8.0)
npx my-db-migrate lock:status

# Force-release stuck lock (v0.8.0)
npx my-db-migrate lock:release --force

# Run migration without locking (v0.8.0)
npx my-db-migrate migrate --no-lock

# Use environment-specific config
npx my-db-migrate migrate --config-file .env.production
```
Expand Down
25 changes: 23 additions & 2 deletions docs/api/core-classes.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ The main classes for executing and managing migrations.

The main class for executing database migrations.

**New in v0.6.0:** Generic type parameters provide full type safety for database-specific operations.
**Generic Type Parameters:**
- **v0.6.0:** Database type parameter (`DB extends IDB`) provides database-specific type safety
- **v0.8.0:** Handler type parameter (`THandler extends IDatabaseMigrationHandler<DB>`) provides type-safe handler access in adapters

```typescript
import { MigrationScriptExecutor, IDatabaseMigrationHandler, Config, IDB } from '@migration-script-runner/core';
Expand All @@ -33,14 +35,33 @@ interface IMyDatabase extends IDB {
query(sql: string): Promise<any>;
}

// Basic usage - handler type inferred
const handler = new MyDatabaseHandler(); // implements IDatabaseMigrationHandler<IMyDatabase>
const config = new Config();
const executor = new MigrationScriptExecutor<IMyDatabase>({ handler , config });

// v0.8.0: Type-safe adapter with handler generic (for custom adapters)
class MyAdapter extends MigrationScriptExecutor<IMyDatabase, MyDatabaseHandler> {
// this.handler is now typed as MyDatabaseHandler (no casting needed!)
getConnectionInfo() {
return this.handler.customProperty; // Full IDE autocomplete
}
}
```

#### Constructor

**Signature (v0.7.0+):**
**Signature (v0.8.0+):**
```typescript
constructor<
DB extends IDB,
THandler extends IDatabaseMigrationHandler<DB> = IDatabaseMigrationHandler<DB>
>(
dependencies: IMigrationExecutorDependencies<DB, THandler>
)
```

**Signature (v0.7.0 - v0.7.x):**
```typescript
constructor<DB extends IDB>(
dependencies: IMigrationExecutorDependencies<DB>
Expand Down
119 changes: 118 additions & 1 deletion docs/api/interfaces/database-handler.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Interface that must be implemented for your specific database.
{: .note }
> **Design Origin**: The handler pattern emerged from MSR's 2017 Firebase prototype, where an `EntityService` provided clean helper methods like `updateAll(callback)` and `findAllBy(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](../../about/origin-story).

**Signature (v0.6.0+):**
**Signature (v0.8.0+):**
```typescript
interface IDatabaseMigrationHandler<DB extends IDB> {
getName(): string;
Expand All @@ -36,6 +36,7 @@ interface IDatabaseMigrationHandler<DB extends IDB> {
schemaVersion: ISchemaVersion<DB>;
backup?: IBackup<DB>; // Optional - only needed for BACKUP or BOTH strategies
transactionManager?: ITransactionManager<DB>; // Optional - auto-created if db supports transactions
lockingService?: ILockingService<DB>; // Optional (v0.8.0) - enables concurrent migration prevention
}
```

Expand Down Expand Up @@ -124,6 +125,122 @@ Handles database backup and restore operations. See [`IBackup` interface](backup

---

### lockingService

Locking interface for preventing concurrent migrations (optional, v0.8.0).

```typescript
lockingService?: ILockingService<DB>
```

**Optional (v0.8.0):** Provides database-level locking to prevent multiple processes from running migrations simultaneously. When not provided, locking is disabled.

**Use Cases:**
- **Multi-instance deployments** - Multiple application servers running migrations
- **Kubernetes/Docker** - Prevent concurrent migrations during rolling deployments
- **CI/CD pipelines** - Avoid conflicts when parallel builds trigger migrations

**Example:**
```typescript
import { ILockingService } from '@migration-script-runner/core';

class PostgresLockingService implements ILockingService<PostgresDB> {
constructor(private db: PostgresDB) {}

async acquireLock(executorId: string, timeout: number): Promise<boolean> {
// Use SELECT FOR UPDATE NOWAIT or INSERT with ON CONFLICT
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)
};
}
}

// Add to handler
class PostgresHandler implements IDatabaseMigrationHandler<PostgresDB> {
db: PostgresDB;
schemaVersion: PostgresSchemaVersion;
backup?: PostgresBackup;
lockingService?: ILockingService<PostgresDB>;

constructor(pool: Pool, options: { useBackup?: boolean; useLocking?: boolean } = {}) {
this.db = new PostgresDB(pool);
this.schemaVersion = new PostgresSchemaVersion(this.db);

if (options.useBackup) {
this.backup = new PostgresBackup(this.db, config);
}

if (options.useLocking) {
this.lockingService = new PostgresLockingService(this.db);
}
}

getName(): string {
return 'PostgreSQL Handler';
}

getVersion(): string {
return '1.0.0';
}
}
```

See [Locking Configuration](../configuration/locking-settings) for configuration options and [Lock Commands](../guides/lock-commands) for CLI usage.

---

## Complete Example

### PostgreSQL Handler
Expand Down
2 changes: 1 addition & 1 deletion docs/api/interfaces/metrics-collector.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Interface for collecting metrics during migration execution.
- Track migration performance
- Monitor execution times in production
- Debug slow migrations
- Send metrics to APM tools (Datadog, CloudWatch, Prometheus)
- Send metrics to APM tools (Datadog, CloudWatch)
- Create audit trails

---
Expand Down
Loading