import { QueryExecResult } from "../interfaces/database"; interface Migration { name: string; sql: string; } export class MigrationService { private static instance: MigrationService; private migrations: Migration[] = []; private constructor() {} static getInstance(): MigrationService { if (!MigrationService.instance) { MigrationService.instance = new MigrationService(); } return MigrationService.instance; } async registerMigration(migration: Migration): Promise { this.migrations.push(migration); } async runMigrations( sqlExec: (sql: string, params?: any[]) => Promise>, ): Promise { // Create migrations table if it doesn't exist await sqlExec(` CREATE TABLE IF NOT EXISTS migrations ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); `); // Get list of executed migrations const result: QueryExecResult[] = await sqlExec( "SELECT name FROM migrations;", ); let executedMigrations: Set = new Set(); // Even with that query, the QueryExecResult may be [] (which doesn't make sense to me). if (result.length > 0) { const singleResult = result[0]; executedMigrations = new Set( singleResult.values.map((row: any[]) => row[0]), ); } // Run pending migrations in order for (const migration of this.migrations) { if (!executedMigrations.has(migration.name)) { try { await sqlExec(migration.sql); await sqlExec("INSERT INTO migrations (name) VALUES (?)", [ migration.name, ]); console.log(`Migration ${migration.name} executed successfully`); } catch (error) { console.error(`Error executing migration ${migration.name}:`, error); throw error; } } } } } export default MigrationService.getInstance();