File size: 2,826 Bytes
88c4c60 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | // 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 transaction wrapper. Use SAVEPOINT for nested support.
const sp = `sp_${Math.random().toString(36).slice(2)}`;
db.exec(`SAVEPOINT ${sp}`);
try {
const r = fn();
db.exec(`RELEASE ${sp}`);
return r;
} catch (e) {
try { db.exec(`ROLLBACK TO ${sp}`); db.exec(`RELEASE ${sp}`); } catch {}
throw e;
}
},
checkpoint() { try { db.exec("PRAGMA wal_checkpoint(TRUNCATE)"); } catch {} },
close() {
clearInterval(checkpointTimer);
gracefulClose();
},
raw: db,
};
}
|