83
src/lib/db/adapters/nodeSqliteAdapter.js
Normal file
83
src/lib/db/adapters/nodeSqliteAdapter.js
Normal file
@@ -0,0 +1,83 @@
|
||||
// Built-in node:sqlite adapter — available in Node >= 22.5.0.
|
||||
// No native build, no npm install. API mirrors betterSqliteAdapter.
|
||||
import { PRAGMA_SQL } from "../schema.js";
|
||||
|
||||
const CHECKPOINT_INTERVAL_MS = 60 * 1000;
|
||||
|
||||
export async function createNodeSqliteAdapter(filePath) {
|
||||
// Suppress "ExperimentalWarning: SQLite is an experimental feature" from node:sqlite.
|
||||
// Stable enough for production use as of Node 22.x (RC quality).
|
||||
const origEmit = process.emit;
|
||||
process.emit = function (name, data, ...rest) {
|
||||
if (name === "warning" && data?.name === "ExperimentalWarning" && /SQLite/i.test(data.message || "")) {
|
||||
return false;
|
||||
}
|
||||
return origEmit.call(process, name, data, ...rest);
|
||||
};
|
||||
|
||||
// Dynamic import — fails on Node < 22.5 → driver.js falls back to sql.js
|
||||
const sqlite = await import("node:sqlite");
|
||||
const Database = sqlite.DatabaseSync;
|
||||
const db = new Database(filePath);
|
||||
|
||||
db.exec(PRAGMA_SQL);
|
||||
|
||||
const stmtCache = new Map();
|
||||
function prepare(sql) {
|
||||
let stmt = stmtCache.get(sql);
|
||||
if (!stmt) {
|
||||
stmt = db.prepare(sql);
|
||||
stmtCache.set(sql, stmt);
|
||||
}
|
||||
return stmt;
|
||||
}
|
||||
|
||||
// Periodic WAL checkpoint to keep -wal/-shm small
|
||||
const checkpointTimer = setInterval(() => {
|
||||
try { db.exec("PRAGMA wal_checkpoint(TRUNCATE)"); } catch {}
|
||||
}, CHECKPOINT_INTERVAL_MS);
|
||||
if (typeof checkpointTimer.unref === "function") checkpointTimer.unref();
|
||||
|
||||
function gracefulClose() {
|
||||
try { db.exec("PRAGMA wal_checkpoint(TRUNCATE)"); } catch {}
|
||||
try { stmtCache.clear(); } catch {}
|
||||
try { db.close(); } catch {}
|
||||
}
|
||||
const onShutdown = () => gracefulClose();
|
||||
process.once("beforeExit", onShutdown);
|
||||
process.once("SIGINT", () => { onShutdown(); process.exit(0); });
|
||||
process.once("SIGTERM", () => { onShutdown(); process.exit(0); });
|
||||
|
||||
return {
|
||||
driver: "node:sqlite",
|
||||
run(sql, params = []) {
|
||||
const r = prepare(sql).run(...params);
|
||||
return { changes: Number(r.changes ?? 0), lastInsertRowid: Number(r.lastInsertRowid ?? 0) };
|
||||
},
|
||||
get(sql, params = []) {
|
||||
return prepare(sql).get(...params);
|
||||
},
|
||||
all(sql, params = []) {
|
||||
return prepare(sql).all(...params);
|
||||
},
|
||||
exec(sql) { return db.exec(sql); },
|
||||
transaction(fn) {
|
||||
// node:sqlite has no built-in transaction wrapper → manual BEGIN/COMMIT
|
||||
db.exec("BEGIN");
|
||||
try {
|
||||
const r = fn();
|
||||
db.exec("COMMIT");
|
||||
return r;
|
||||
} catch (e) {
|
||||
try { db.exec("ROLLBACK"); } catch {}
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
checkpoint() { try { db.exec("PRAGMA wal_checkpoint(TRUNCATE)"); } catch {} },
|
||||
close() {
|
||||
clearInterval(checkpointTimer);
|
||||
gracefulClose();
|
||||
},
|
||||
raw: db,
|
||||
};
|
||||
}
|
||||
@@ -14,6 +14,19 @@ async function tryBetterSqlite() {
|
||||
}
|
||||
}
|
||||
|
||||
async function tryNodeSqlite() {
|
||||
// Built-in since Node 22.5.0 — no install needed.
|
||||
const [maj, min] = process.versions.node.split(".").map(Number);
|
||||
if (maj < 22 || (maj === 22 && min < 5)) return null;
|
||||
try {
|
||||
const { createNodeSqliteAdapter } = await import("./adapters/nodeSqliteAdapter.js");
|
||||
return await createNodeSqliteAdapter(DATA_FILE);
|
||||
} catch (e) {
|
||||
console.warn(`[DB] node:sqlite unavailable: ${e.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function trySqlJs() {
|
||||
try {
|
||||
const { createSqlJsAdapter } = await import("./adapters/sqljsAdapter.js");
|
||||
@@ -26,9 +39,11 @@ async function trySqlJs() {
|
||||
|
||||
async function initAdapter() {
|
||||
ensureDirs();
|
||||
// Order: native (fastest) → built-in (no install) → pure JS (universal)
|
||||
let adapter = await tryBetterSqlite();
|
||||
if (!adapter) adapter = await tryNodeSqlite();
|
||||
if (!adapter) adapter = await trySqlJs();
|
||||
if (!adapter) throw new Error("[DB] No SQLite driver available (better-sqlite3 + sql.js both failed)");
|
||||
if (!adapter) throw new Error("[DB] No SQLite driver available (better-sqlite3 + node:sqlite + sql.js all failed)");
|
||||
|
||||
if (!state.logged) {
|
||||
console.log(`[DB] Driver: ${adapter.driver} | file: ${DATA_FILE}`);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { getAdapter } from "../driver.js";
|
||||
import { parseJson, stringifyJson } from "../helpers/jsonCol.js";
|
||||
|
||||
const DEFAULT_MITM_ROUTER_BASE = "http://127.0.0.1:20128";
|
||||
const DEFAULT_MITM_ROUTER_BASE = "http://localhost:20128";
|
||||
|
||||
const DEFAULT_SETTINGS = {
|
||||
cloudEnabled: false,
|
||||
|
||||
Reference in New Issue
Block a user