/** * Search-box.ai * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * File: packages/db/src/migrate.ts * Description: Idempotent migration runner (applies packages/db/migrations in order). */ import { readdirSync, readFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { getPool } from "./pool.js"; const here = dirname(fileURLToPath(import.meta.url)); const migrationsDir = join(here, "..", "migrations"); export async function migrate(): Promise { const pool = getPool(); await pool.query( `CREATE TABLE IF NOT EXISTS schema_migrations ( name text PRIMARY KEY, applied_at timestamptz NOT NULL DEFAULT now() )` ); const files = readdirSync(migrationsDir).filter((f) => f.endsWith(".sql")).sort(); for (const file of files) { const { rows } = await pool.query("SELECT 1 FROM schema_migrations WHERE name = $1", [file]); if (rows.length > 0) continue; const sql = readFileSync(join(migrationsDir, file), "utf8"); const client = await pool.connect(); try { await client.query("BEGIN"); await client.query(sql); await client.query("INSERT INTO schema_migrations (name) VALUES ($1)", [file]); await client.query("COMMIT"); console.log(`applied ${file}`); } catch (err) { await client.query("ROLLBACK"); throw err; } finally { client.release(); } } } // Run directly: `pnpm migrate` if (process.argv[1] && process.argv[1].endsWith("migrate.ts")) { migrate() .then(() => process.exit(0)) .catch((err) => { console.error(err); process.exit(1); }); }