| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import fs from "node:fs"; |
| import path from "node:path"; |
| import { fileURLToPath } from "node:url"; |
| import type Database from "better-sqlite3"; |
|
|
| const __filename = fileURLToPath(import.meta.url); |
| const __dirname = path.dirname(__filename); |
| const MIGRATIONS_DIR = path.join(__dirname, "migrations"); |
|
|
| |
| |
| |
| function ensureMigrationsTable(db: Database.Database): void { |
| db.exec(` |
| CREATE TABLE IF NOT EXISTS _omniroute_migrations ( |
| version TEXT PRIMARY KEY, |
| name TEXT NOT NULL, |
| applied_at TEXT NOT NULL DEFAULT (datetime('now')) |
| ); |
| `); |
| } |
|
|
| |
| |
| |
| function getMigrationFiles(): Array<{ version: string; name: string; path: string }> { |
| if (!fs.existsSync(MIGRATIONS_DIR)) return []; |
|
|
| return fs |
| .readdirSync(MIGRATIONS_DIR) |
| .filter((f) => f.endsWith(".sql")) |
| .sort() |
| .map((filename) => { |
| const match = filename.match(/^(\d+)_(.+)\.sql$/); |
| if (!match) return null; |
| return { |
| version: match[1], |
| name: match[2], |
| path: path.join(MIGRATIONS_DIR, filename), |
| }; |
| }) |
| .filter(Boolean) as Array<{ version: string; name: string; path: string }>; |
| } |
|
|
| |
| |
| |
| function getAppliedVersions(db: Database.Database): Set<string> { |
| const rows = db.prepare("SELECT version FROM _omniroute_migrations").all() as Array<{ |
| version: string; |
| }>; |
| return new Set(rows.map((r) => r.version)); |
| } |
|
|
| |
| |
| |
| |
| export function runMigrations(db: Database.Database): number { |
| ensureMigrationsTable(db); |
|
|
| const files = getMigrationFiles(); |
| const applied = getAppliedVersions(db); |
| let count = 0; |
|
|
| for (const migration of files) { |
| if (applied.has(migration.version)) continue; |
|
|
| const sql = fs.readFileSync(migration.path, "utf-8"); |
|
|
| const applyMigration = db.transaction(() => { |
| db.exec(sql); |
| db.prepare("INSERT INTO _omniroute_migrations (version, name) VALUES (?, ?)").run( |
| migration.version, |
| migration.name |
| ); |
| }); |
|
|
| try { |
| applyMigration(); |
| count++; |
| console.log(`[Migration] Applied: ${migration.version}_${migration.name}`); |
| } catch (err: unknown) { |
| const message = err instanceof Error ? err.message : String(err); |
| console.error(`[Migration] FAILED: ${migration.version}_${migration.name} — ${message}`); |
| throw err; |
| } |
| } |
|
|
| if (count > 0) { |
| console.log(`[Migration] ${count} migration(s) applied successfully.`); |
| } |
|
|
| return count; |
| } |
|
|
| |
| |
| |
| export function getMigrationStatus(db: Database.Database): { |
| applied: Array<{ version: string; name: string; applied_at: string }>; |
| pending: Array<{ version: string; name: string }>; |
| } { |
| ensureMigrationsTable(db); |
|
|
| const appliedRows = db |
| .prepare("SELECT version, name, applied_at FROM _omniroute_migrations ORDER BY version") |
| .all() as Array<{ version: string; name: string; applied_at: string }>; |
|
|
| const appliedVersions = new Set(appliedRows.map((r) => r.version)); |
| const allFiles = getMigrationFiles(); |
| const pending = allFiles.filter((f) => !appliedVersions.has(f.version)); |
|
|
| return { applied: appliedRows, pending }; |
| } |
|
|