mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 20:00:47 +00:00
feat(db): migrate from lowdb to SQLite with repos pattern
- Add modular DB layer (adapters, migrations, repos, helpers) - Replace localDb/usageDb/requestDetailsDb monoliths with repos - Add Tailscale tunnel integration & status check API - Add /api/cli-tools/all-statuses aggregated endpoint - Add settingsStore (Zustand) and mitm/dbReader - Add DB unit tests (benchmark, concurrent, migration, vs-lowdb)
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
import fs from "node:fs";
|
||||
import initSqlJs from "sql.js";
|
||||
import { PRAGMA_SQL } from "../schema.js";
|
||||
|
||||
let SQL = null;
|
||||
|
||||
async function loadSql() {
|
||||
if (SQL) return SQL;
|
||||
SQL = await initSqlJs();
|
||||
return SQL;
|
||||
}
|
||||
|
||||
export async function createSqlJsAdapter(filePath) {
|
||||
const SQLLib = await loadSql();
|
||||
const buf = fs.existsSync(filePath) ? fs.readFileSync(filePath) : null;
|
||||
const db = new SQLLib.Database(buf);
|
||||
db.exec(PRAGMA_SQL);
|
||||
// Schema is created/synced by migrate.js after adapter init
|
||||
|
||||
let dirty = false;
|
||||
let saveTimer = null;
|
||||
const SAVE_DEBOUNCE_MS = 100;
|
||||
|
||||
function persist() {
|
||||
const data = db.export();
|
||||
fs.writeFileSync(filePath, Buffer.from(data));
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
function scheduleSave() {
|
||||
dirty = true;
|
||||
if (saveTimer) clearTimeout(saveTimer);
|
||||
saveTimer = setTimeout(() => {
|
||||
saveTimer = null;
|
||||
if (dirty) {
|
||||
try { persist(); } catch (e) { console.error("[sqljs] save failed:", e); }
|
||||
}
|
||||
}, SAVE_DEBOUNCE_MS);
|
||||
}
|
||||
|
||||
function paramsObj(params) {
|
||||
if (!params || (Array.isArray(params) && params.length === 0)) return undefined;
|
||||
return params;
|
||||
}
|
||||
|
||||
function run(sql, params = []) {
|
||||
const stmt = db.prepare(sql);
|
||||
try {
|
||||
stmt.bind(paramsObj(params));
|
||||
stmt.step();
|
||||
const changes = db.getRowsModified();
|
||||
const lastInsertRowid = db.exec("SELECT last_insert_rowid() as id")[0]?.values?.[0]?.[0] ?? null;
|
||||
scheduleSave();
|
||||
return { changes, lastInsertRowid };
|
||||
} finally {
|
||||
stmt.free();
|
||||
}
|
||||
}
|
||||
|
||||
function get(sql, params = []) {
|
||||
const stmt = db.prepare(sql);
|
||||
try {
|
||||
stmt.bind(paramsObj(params));
|
||||
if (stmt.step()) return stmt.getAsObject();
|
||||
return undefined;
|
||||
} finally {
|
||||
stmt.free();
|
||||
}
|
||||
}
|
||||
|
||||
function all(sql, params = []) {
|
||||
const stmt = db.prepare(sql);
|
||||
try {
|
||||
stmt.bind(paramsObj(params));
|
||||
const rows = [];
|
||||
while (stmt.step()) rows.push(stmt.getAsObject());
|
||||
return rows;
|
||||
} finally {
|
||||
stmt.free();
|
||||
}
|
||||
}
|
||||
|
||||
function exec(sql) {
|
||||
db.exec(sql);
|
||||
scheduleSave();
|
||||
}
|
||||
|
||||
function transaction(fn) {
|
||||
db.exec("BEGIN");
|
||||
try {
|
||||
const result = fn();
|
||||
db.exec("COMMIT");
|
||||
scheduleSave();
|
||||
return result;
|
||||
} catch (e) {
|
||||
db.exec("ROLLBACK");
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (saveTimer) clearTimeout(saveTimer);
|
||||
if (dirty) persist();
|
||||
db.close();
|
||||
}
|
||||
|
||||
// Flush on shutdown
|
||||
const flush = () => { if (dirty) try { persist(); } catch {} };
|
||||
process.on("beforeExit", flush);
|
||||
process.on("SIGINT", flush);
|
||||
process.on("SIGTERM", flush);
|
||||
|
||||
return { driver: "sql.js", run, get, all, exec, transaction, close, raw: db };
|
||||
}
|
||||
Reference in New Issue
Block a user