Skip to content
Source
Sign inSign up

databaseMigrationHead.ts

49 lines1.8 KB

1// @effect-diagnostics nodeBuiltinImport:off - read synchronously by the preflight subprocess and at startup.
2import * as NodeFS from "node:fs";
3import * as NodeSqlite from "node:sqlite";
4​
5import { migrationManifest } from "./Migrations.ts";
6​
7export const BUILD_MIGRATION_HEAD = Math.max(...migrationManifest.map(([id]) => id));
8​
9/** Migrations the database records, read-only. A missing or new database records none; one that
10 * exists but cannot be read is null, since its history is unknown. */
11export function recordedMigrations(
12 databasePath: string,
13): ReadonlyArray<{ readonly id: number; readonly name: string }> | null {
14 let db: NodeSqlite.DatabaseSync;
15 try {
16 db = new NodeSqlite.DatabaseSync(databasePath, { readOnly: true });
17 } catch {
18 return NodeFS.existsSync(databasePath) ? null : [];
19 }
20 try {
21 const hasTable = db
22 .prepare(
23 "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'effect_sql_migrations'",
24 )
25 .get();
26 if (hasTable === undefined) return [];
27 return db
28 .prepare("SELECT migration_id, name FROM effect_sql_migrations ORDER BY migration_id")
29 .all()
30 .map((row) => ({ id: Number(row.migration_id), name: String(row.name) }));
31 } catch {
32 return null;
33 } finally {
34 try {
35 db.close();
36 } catch {
37 // A read-only handle has nothing to flush; the result above already stands.
38 }
39 }
41​
42/** Highest migration the database holds once this build's startup migrations have run. A newer
43 * build may have recorded more than this build knows. Undefined when the database is unreadable. */
44export function databaseMigrationHead(databasePath: string): number | undefined {
45 const recorded = recordedMigrations(databasePath);
46 return recorded === null
47 ? undefined
48 : Math.max(BUILD_MIGRATION_HEAD, ...recorded.map(({ id }) => id));