51 lines
1.6 KiB
TypeScript
51 lines
1.6 KiB
TypeScript
/**
|
|
* 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<T>(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;
|
|
}
|
|
}
|