/** * Opens the database at {@link DB_PATH} when first imported, applies the * pending migrations and runs the startup tasks before any importer runs. Only * the services import it. * * @module */ import { DatabaseSync } from "node:sqlite"; import { DB_PATH } from "../config.ts"; import { runMigrations } from "./migrate.ts"; import { runStartupTasks } from "./startup.ts"; /** * The server's only connection. Its queries are synchronous, so each one * blocks the event loop while it runs. */ export const db: DatabaseSync = new DatabaseSync(DB_PATH); // WAL lets readers run alongside a writer, and with synchronous = NORMAL a // commit doesn't wait on an fsync: a power loss can lose the last commits but // never corrupts the file. busy_timeout makes another connection to the file // wait for the lock instead of failing. SQLite enforces foreign keys, and so // the schema's ON DELETE cascades, only when each connection asks. db.exec(` PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL; PRAGMA busy_timeout = 5000; PRAGMA foreign_keys = ON; `); runMigrations(db); await runStartupTasks(db); /** * Runs `fn` in a transaction, so its writes commit together, in one commit * rather than one each, and a throw rolls them all back and rethrows. A call * inside another joins the outer transaction. `fn` must be synchronous: an * `await` would commit before the rest of it runs. */ export function transaction(fn: () => T): T { if (db.isTransaction) return fn(); db.exec("BEGIN;"); try { const result = fn(); db.exec("COMMIT;"); return result; } catch (err) { db.exec("ROLLBACK;"); throw err; } }