135 lines
3.0 KiB
TypeScript
135 lines
3.0 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
import { MaybePromise } from "@fabric/core";
|
|
import SQLite from "sqlite3";
|
|
|
|
export class SQLiteDatabase {
|
|
db: SQLite.Database;
|
|
|
|
private cachedStatements = new Map<string, SQLite.Statement>();
|
|
|
|
constructor(private readonly path: string) {
|
|
this.db = new SQLite.Database(path);
|
|
}
|
|
|
|
async init() {
|
|
await this.run("PRAGMA journal_mode = WAL");
|
|
await this.run("PRAGMA foreign_keys = ON");
|
|
}
|
|
|
|
async close() {
|
|
await this.finalizeStatements();
|
|
await new Promise<void>((resolve, reject) => {
|
|
this.db.close((err: Error | null) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
async withTransaction(fn: () => MaybePromise<void>) {
|
|
try {
|
|
await this.run("BEGIN TRANSACTION");
|
|
await fn();
|
|
await this.run("COMMIT");
|
|
} catch {
|
|
await this.run("ROLLBACK");
|
|
}
|
|
}
|
|
|
|
run(sql: string, params?: Record<string, any>) {
|
|
return new Promise<void>((resolve, reject) => {
|
|
this.db.run(sql, params, (err: Error | null) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
runPrepared(sql: string, params?: Record<string, any>) {
|
|
const cachedStmt = this.getCachedStatement(sql);
|
|
|
|
return new Promise<void>((resolve, reject) => {
|
|
cachedStmt.run(params, (err: Error | null) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
allPrepared(
|
|
sql: string,
|
|
params?: Record<string, any>,
|
|
transformer?: (row: any) => any,
|
|
) {
|
|
const cachedStmt = this.getCachedStatement(sql);
|
|
|
|
return new Promise<any>((resolve, reject) => {
|
|
cachedStmt.all(
|
|
params,
|
|
(err: Error | null, rows: Record<string, any>[]) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve(transformer ? rows.map(transformer) : rows);
|
|
}
|
|
},
|
|
);
|
|
});
|
|
}
|
|
|
|
onePrepared(
|
|
sql: string,
|
|
params?: Record<string, any>,
|
|
transformer?: (row: any) => any,
|
|
) {
|
|
const cachedStmt = this.getCachedStatement(sql);
|
|
|
|
return new Promise<any>((resolve, reject) => {
|
|
cachedStmt.all(
|
|
params,
|
|
(err: Error | null, rows: Record<string, any>[]) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve(transformer ? rows.map(transformer)[0] : rows[0]);
|
|
}
|
|
},
|
|
);
|
|
});
|
|
}
|
|
|
|
private getCachedStatement(sql: string) {
|
|
let cached = this.cachedStatements.get(sql);
|
|
|
|
if (!cached) {
|
|
const stmt = this.db.prepare(sql);
|
|
this.cachedStatements.set(sql, stmt);
|
|
cached = stmt;
|
|
}
|
|
return cached;
|
|
}
|
|
|
|
private async finalizeStatements() {
|
|
for (const stmt of this.cachedStatements.values()) {
|
|
await new Promise<void>((resolve, reject) => {
|
|
stmt.finalize((err: Error | null) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|