type SqlValue = string | number | null | Uint8Array; export interface QueryExecResult { columns: Array; values: Array>; } 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 = await sqlExec('SELECT name FROM migrations'); const singleResult = result[0]; const executedMigrations = new Set(singleResult.values.map(row => 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();