const fs = require('fs'); const path = require('path'); const DatabaseClient = require('./db'); const config = require('../../config/app'); const LOCK_MIGRATION_FILE = '002_outbox_locks.sql'; async function ensureMigrationTable(db) { await db.exec(` CREATE TABLE IF NOT EXISTS schema_migrations ( filename TEXT PRIMARY KEY, applied_at TEXT NOT NULL DEFAULT (datetime('now')) ); `); } async function migrationIsApplied(db, filename) { const row = await db.get( 'SELECT filename FROM schema_migrations WHERE filename = ? LIMIT 1', [filename] ); return Boolean(row); } async function markMigrationApplied(db, filename) { await db.run( 'INSERT OR IGNORE INTO schema_migrations (filename) VALUES (?)', [filename] ); } async function applyOutboxLockMigration(db) { const columns = await db.all('PRAGMA table_info(outbox_result)'); const columnNames = new Set(columns.map((column) => column.name)); if (!columnNames.has('locked_at')) { await db.exec('ALTER TABLE outbox_result ADD COLUMN locked_at INTEGER NULL;'); } if (!columnNames.has('locked_by')) { await db.exec('ALTER TABLE outbox_result ADD COLUMN locked_by TEXT NULL;'); } } async function migrate() { const db = new DatabaseClient(config.db); const migrationsDir = path.join(__dirname, '..', '..', 'db', 'migrations'); const files = fs .readdirSync(migrationsDir) .filter((name) => name.endsWith('.sql')) .sort(); await ensureMigrationTable(db); for (const file of files) { if (await migrationIsApplied(db, file)) { continue; } if (file === LOCK_MIGRATION_FILE) { await applyOutboxLockMigration(db); await markMigrationApplied(db, file); continue; } const payload = fs.readFileSync(path.join(migrationsDir, file), 'utf8'); await db.exec(payload); await markMigrationApplied(db, file); } await db.close(); } if (require.main === module) { migrate() .then(() => console.log('migrations applied')) .catch((err) => { console.error('migration failed', err); process.exit(1); }); } module.exports = migrate;