teach/src/migrate.js
bes-r 08fd9e11e3
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 17s
v0.2.01: automatische migraties bij opstarten (transactie per bestand, geen dataverlies)
2026-07-09 22:34:36 +02:00

48 lines
1.9 KiB
JavaScript

// Automatische, veilige migraties bij het opstarten.
//
// Veiligheid:
// - alleen vooruit: elk .sql-bestand in db/ wordt precies één keer uitgevoerd
// en daarna geregistreerd in schema_migrations;
// - elk bestand draait in zijn eigen transactie: mislukt er iets, dan wordt
// alles van dat bestand teruggedraaid en start de app NIET (bestaande data
// blijft onaangeroerd);
// - een advisory lock voorkomt dat twee instanties tegelijk migreren;
// - de migratiebestanden zelf zijn idempotent (IF NOT EXISTS), dus ook een
// database waarop ze al handmatig zijn uitgevoerd blijft gewoon werken.
import { readdir, readFile } from 'node:fs/promises';
import { join } from 'node:path';
const LOCK_KEY = 727271;
export async function runMigrations(pool, log, dir) {
const client = await pool.connect();
try {
await client.query('SELECT pg_advisory_lock($1)', [LOCK_KEY]);
await client.query(`CREATE TABLE IF NOT EXISTS schema_migrations (
filename TEXT PRIMARY KEY,
applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
)`);
const done = new Set(
(await client.query('SELECT filename FROM schema_migrations')).rows.map((r) => r.filename),
);
const files = (await readdir(dir)).filter((f) => f.endsWith('.sql')).sort();
for (const f of files) {
if (done.has(f)) continue;
const sql = await readFile(join(dir, f), 'utf8');
log.info(`migratie uitvoeren: ${f}`);
try {
await client.query('BEGIN');
await client.query(sql);
await client.query('INSERT INTO schema_migrations (filename) VALUES ($1)', [f]);
await client.query('COMMIT');
log.info(`migratie klaar: ${f}`);
} catch (e) {
await client.query('ROLLBACK');
throw new Error(`migratie ${f} mislukt (teruggedraaid, data ongewijzigd): ${e.message}`);
}
}
} finally {
await client.query('SELECT pg_advisory_unlock($1)', [LOCK_KEY]).catch(() => {});
client.release();
}
}