Spaces:
Sleeping
Sleeping
| 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 }; | |
| } | |