9router / src /lib /db /adapters /sqljsAdapter.js
2api
feat: configurable stream stall timeout + per-provider UI
88c4c60
Raw
History Blame Contribute Delete
2.83 kB
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) {
const sp = `sp_${Math.random().toString(36).slice(2)}`;
db.exec(`SAVEPOINT ${sp}`);
try {
const result = fn();
db.exec(`RELEASE ${sp}`);
scheduleSave();
return result;
} catch (e) {
try { db.exec(`ROLLBACK TO ${sp}`); db.exec(`RELEASE ${sp}`); } catch {}
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 };
}