61 lines
2.0 KiB
TypeScript
61 lines
2.0 KiB
TypeScript
/**
|
|
* Opens the database at {@link DB_PATH} when first imported, creating it from
|
|
* schema.sql if the file doesn't exist, then 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";
|
|
|
|
const isNew = await Deno.stat(DB_PATH).then(() => false, () => true);
|
|
|
|
/**
|
|
* Serves every query of the server, as its only connection. Its queries are
|
|
* synchronous, so each one blocks the event loop while it runs.
|
|
*/
|
|
export const db: DatabaseSync = new DatabaseSync(DB_PATH);
|
|
// schema.sql holds every migration's change, so on a new database each
|
|
// migration below finds its work done and is only recorded.
|
|
if (isNew) {
|
|
db.exec(
|
|
await Deno.readTextFile(new URL("./schema.sql", import.meta.url)),
|
|
);
|
|
}
|
|
// 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;
|
|
}
|
|
}
|