Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | 257x 257x 34993x 35284x 18373x 18373x 16911x 16911x 16909x 2x 257x 2082x 10326x 34994x 34994x 2025x 1065x 1065x 1065x 1065x 1065x 1065x 1065x | /**
* SQLite adapter providing a unified API.
*
* This module is the single import point for all SQLite access in the
* codebase. It provides a `.query(sql).get()` / `.all()` / `.run()`
* interface and a manual `transaction()` wrapper.
*
* Uses `node:sqlite` (Node 22.15+ with `--experimental-sqlite` flag).
*/
import { createRequire } from "node:module";
import { logger } from "../logger.js";
const _require = createRequire(import.meta.url);
const log = logger.withTag("sqlite");
/** Valid SQLite binding value. */
export type SQLQueryBindings =
| string
| number
| bigint
| boolean
| null
| Uint8Array
| undefined;
/**
* Prepared statement wrapper exposing `.get()`, `.all()`, `.run()`.
*
* Uses a Proxy to pass through any additional methods while normalising
* `.get()` to return `null` (not `undefined`) for no-row results.
*/
type StatementWrapper = {
get(...params: SQLQueryBindings[]): Record<string, SQLQueryBindings> | null;
all(...params: SQLQueryBindings[]): Record<string, SQLQueryBindings>[];
run(...params: SQLQueryBindings[]): void;
[method: string]: unknown;
};
// biome-ignore lint/suspicious/noExplicitAny: backing driver types vary
function wrapStatement(stmt: any): StatementWrapper {
return new Proxy(stmt, {
get(target, prop) {
if (prop === "get") {
return (...params: SQLQueryBindings[]) =>
// Normalise no-row result to null (node:sqlite returns undefined).
(target.get(...params) as Record<string, SQLQueryBindings>) ?? null;
}
const value = Reflect.get(target, prop);
if (typeof value === "function") {
return value.bind(target);
}
return value;
},
}) as StatementWrapper;
}
/**
* Resolve the SQLite database constructor.
* Uses `node:sqlite` (Node 22.15+ with `--experimental-sqlite`).
*/
// biome-ignore lint/suspicious/noExplicitAny: driver types loaded lazily
const SqliteImpl: any = _require("node:sqlite").DatabaseSync;
/**
* SQLite database wrapper.
*
* - `exec(sql)` — execute raw SQL (DDL, multi-statement)
* - `query(sql)` — prepare a statement → `.get()` / `.all()` / `.run()`
* - `close()` — close the connection
* - `transaction(fn)` — wrap a function in BEGIN/COMMIT/ROLLBACK
*/
export class Database {
// biome-ignore lint/suspicious/noExplicitAny: backing driver resolved at runtime
private readonly db: any;
constructor(path: string) {
this.db = new SqliteImpl(path);
}
/** Execute raw SQL (DDL statements, multi-statement strings). */
exec(sql: string): void {
this.db.exec(sql);
}
/**
* Prepare a SQL statement.
* Returns a wrapper with `.get()`, `.all()`, `.run()`.
*/
query(sql: string): StatementWrapper {
const prepFn = this.db.query ?? this.db.prepare;
return wrapStatement(prepFn.call(this.db, sql));
}
/** Close the database connection. */
close(): void {
this.db.close();
}
/**
* Wrap a function in a transaction. Returns a callable that executes
* the function within BEGIN/COMMIT, with ROLLBACK on error.
*/
transaction<T>(fn: () => T): () => T {
Iif (typeof this.db.transaction === "function") {
return this.db.transaction(fn);
}
return () => {
this.db.exec("BEGIN");
try {
const result = fn();
this.db.exec("COMMIT");
return result;
} catch (error) {
try {
this.db.exec("ROLLBACK");
} catch (rollbackError) {
log.debug("ROLLBACK failed after transaction error", rollbackError);
}
throw error;
}
};
}
}
|