Problem
Currently, MSR uses the generic IDB interface throughout the codebase, which provides limited type safety and forces users to cast to specific database types when accessing database-specific features.
Current Implementation:
interface IDatabaseMigrationHandler {
db: IDB; // Generic IDB type - no type safety
// ...
}
interface IRunnableScript {
up(db: IDB, info: IMigrationInfo, handler: IDatabaseMigrationHandler): Promise<string>;
down(db: IDB, info: IMigrationInfo, handler: IDatabaseMigrationHandler): Promise<string>;
}
Issues:
- ❌ No IDE autocomplete for database-specific methods
- ❌ Requires
as any or type casting in migration scripts
- ❌ No compile-time validation of database operations
- ❌ Cannot access PostgreSQL-specific features (COPY, etc.)
- ❌ Cannot access MySQL-specific features without casting
Proposed Solution
Introduce generic type parameters to make database types strongly typed throughout the interface hierarchy.
Proposed Implementation:
interface IDatabaseMigrationHandler<DB extends IDB = IDB> {
db: DB; // Specific database type (IPostgresDB, IMySQLDB, etc.)
schemaVersion: ISchemaVersion<DB>;
backup?: IBackup<DB>;
transactionManager?: ITransactionManager<DB>;
// ...
}
interface IRunnableScript<DB extends IDB = IDB> {
up(
db: DB,
info: IMigrationInfo,
handler: IDatabaseMigrationHandler<DB>
): Promise<string>;
down(
db: DB,
info: IMigrationInfo,
handler: IDatabaseMigrationHandler<DB>
): Promise<string>;
}
interface ISchemaVersion<DB extends IDB = IDB> {
migrationRecords: IMigrationRecords<DB>;
// ...
}
// Usage example:
class PostgreSQLHandler implements IDatabaseMigrationHandler<IPostgresDB> {
db: IPostgresDB; // Strongly typed!
// ...
}
// In migration script:
const migration: IRunnableScript<IPostgresDB> = {
async up(db, info, handler) {
// db is IPostgresDB - full autocomplete and type safety!
await db.query('COPY users FROM ...'); // PostgreSQL-specific
}
};
Benefits
1. Type Safety
- ✅ Compile-time validation of database-specific operations
- ✅ Prevent runtime errors from calling unsupported methods
- ✅ No more
as any casting in migration scripts
2. Developer Experience
- ✅ Full IDE autocomplete for database-specific methods
- ✅ Inline documentation for available operations
- ✅ Better refactoring support
3. Code Quality
- ✅ Self-documenting code (types show what's available)
- ✅ Easier to maintain and extend
- ✅ Catches errors at compile time, not runtime
4. Database-Specific Features
- ✅ Access PostgreSQL features (COPY, LISTEN/NOTIFY, etc.)
- ✅ Access MySQL features (specific functions, etc.)
- ✅ Access SQLite features without casting
Implementation Considerations
1. Backward Compatibility
Challenge: This is a breaking change for existing handlers and migration scripts.
Mitigation:
- Use default type parameter
<DB extends IDB = IDB> for backward compatibility
- Existing code without generics continues to work with base
IDB type
- Provide migration guide for upgrading to typed versions
- Consider making this a major version bump (v1.0.0?)
2. Cascade Effect
Challenge: Generic type needs to flow through multiple interfaces.
Affected interfaces:
IDatabaseMigrationHandler<DB>
IRunnableScript<DB>
ISchemaVersion<DB>
IMigrationRecords<DB>
IBackup<DB>
ITransactionManager<DB>
MigrationScriptExecutor constructor
MigrationScript<DB> class
3. API Complexity
Challenge: Adds complexity to public API.
Considerations:
- Keep default parameter for simple use cases
- Document when generics are needed vs optional
- Provide examples for common patterns
- Balance type safety vs API simplicity
4. Testing Impact
Challenge: All tests need to be updated.
Scope:
- Update all test mocks to use generics
- Test with different database types
- Verify backward compatibility
- Maintain 100% coverage
Implementation Phases
Phase 1: Analysis & Design (🔍 Investigation Required)
Phase 2: Core Interfaces
Phase 3: Supporting Interfaces
Phase 4: Executor & Services
Phase 5: Documentation & Migration
Examples
PostgreSQL Handler
import { IDatabaseMigrationHandler, IPostgresDB } from '@migration-script-runner/core';
class PostgreSQLHandler implements IDatabaseMigrationHandler<IPostgresDB> {
db: IPostgresDB;
constructor(db: IPostgresDB) {
this.db = db;
}
}
Strongly-Typed Migration Script
import { IRunnableScript, IPostgresDB, IMigrationInfo } from '@migration-script-runner/core';
const migration: IRunnableScript<IPostgresDB> = {
async up(db, info, handler) {
// Full autocomplete for PostgreSQL-specific features!
await db.query('CREATE INDEX CONCURRENTLY idx_email ON users(email)');
await db.copy('COPY users FROM STDIN'); // PostgreSQL COPY command
// TypeScript knows about PostgreSQL-specific methods
const result = await db.query<{count: number}>('SELECT COUNT(*) FROM users');
},
async down(db, info, handler) {
await db.query('DROP INDEX idx_email');
}
};
export default migration;
Backward Compatible (Default Generic)
// Still works - uses default IDB type
const migration: IRunnableScript = {
async up(db, info, handler) {
// db is IDB (base interface)
await db.checkConnection();
}
};
Open Questions
- Breaking Change Strategy: Should this be v1.0.0 or can we maintain backward compatibility?
- Opt-in vs Mandatory: Should generics be optional (with defaults) or required?
- Database Interface Hierarchy: Do we need
IPostgresDB extends IDB, IMySQLDB extends IDB, etc.?
- Migration Script Typing: How do we infer database type in migration files without explicit typing?
- Loader Impact: How do loaders (TypeScriptLoader, SqlLoader) handle generic types?
Success Criteria
Related Issues
Priority
Backlog - Requires deep analysis during implementation to ensure:
- Minimal breaking changes
- Clean generic type propagation
- Backward compatibility where possible
- Clear migration path for existing users
Note: This is a significant architectural change that will improve type safety across the entire framework but requires careful planning and execution.
Problem
Currently, MSR uses the generic
IDBinterface throughout the codebase, which provides limited type safety and forces users to cast to specific database types when accessing database-specific features.Current Implementation:
Issues:
as anyor type casting in migration scriptsProposed Solution
Introduce generic type parameters to make database types strongly typed throughout the interface hierarchy.
Proposed Implementation:
Benefits
1. Type Safety
as anycasting in migration scripts2. Developer Experience
3. Code Quality
4. Database-Specific Features
Implementation Considerations
1. Backward Compatibility
Challenge: This is a breaking change for existing handlers and migration scripts.
Mitigation:
<DB extends IDB = IDB>for backward compatibilityIDBtype2. Cascade Effect
Challenge: Generic type needs to flow through multiple interfaces.
Affected interfaces:
IDatabaseMigrationHandler<DB>IRunnableScript<DB>ISchemaVersion<DB>IMigrationRecords<DB>IBackup<DB>ITransactionManager<DB>MigrationScriptExecutorconstructorMigrationScript<DB>class3. API Complexity
Challenge: Adds complexity to public API.
Considerations:
4. Testing Impact
Challenge: All tests need to be updated.
Scope:
Implementation Phases
Phase 1: Analysis & Design (🔍 Investigation Required)
@migration-script-runner/postgresql, etc.)Phase 2: Core Interfaces
IDBand related interfacesIDatabaseMigrationHandler<DB>IRunnableScript<DB>ISchemaVersion<DB>Phase 3: Supporting Interfaces
IMigrationRecords<DB>IBackup<DB>ITransactionManager<DB>MigrationScript<DB>Phase 4: Executor & Services
MigrationScriptExecutorPhase 5: Documentation & Migration
Examples
PostgreSQL Handler
Strongly-Typed Migration Script
Backward Compatible (Default Generic)
Open Questions
IPostgresDB extends IDB,IMySQLDB extends IDB, etc.?Success Criteria
as anycasting needed in migrationsRelated Issues
@migration-script-runner/postgresql, etc.)Priority
Backlog - Requires deep analysis during implementation to ensure:
Note: This is a significant architectural change that will improve type safety across the entire framework but requires careful planning and execution.