Appearance
Node — node:sqlite
Node ships a built-in SQLite module, node:sqlite, since Node 22.5 (still flagged experimental, but usable — it prints an ExperimentalWarning). No native dependency to install.
Enabling extension loading
node:sqlite gates extension loading twice — you must opt in when constructing the database and enable the loader:
ts
import { DatabaseSync } from "node:sqlite";
const db = new DatabaseSync("app.db", { allowExtension: true }); // 1: opt in
db.enableLoadExtension(true); // 2: enable
db.loadExtension("./dist/rls.so");
db.enableLoadExtension(false); // re-disableFull example
ts
import { DatabaseSync } from "node:sqlite";
const db = new DatabaseSync("app.db", { allowExtension: true });
db.enableLoadExtension(true);
db.loadExtension("./dist/rls.so");
db.enableLoadExtension(false);
db.exec(`
CREATE TABLE documents(id INTEGER PRIMARY KEY, owner_id INT NOT NULL, title TEXT);
INSERT INTO documents(owner_id, title) VALUES (1,'Alice'), (2,'Bob');
`);
db.prepare("SELECT rls_protect(?)").get(
JSON.stringify({ table: "documents", select: "owner_id = rls_ctx('user_id')" }));
db.prepare("SELECT rls_seal(?)").get(process.env.RLS_TOKEN);
db.prepare("SELECT rls_guard()").get();
// per query:
db.prepare("SELECT rls_set_context(?)").get(JSON.stringify({ user_id: 1 }));
db.prepare("SELECT title FROM documents").all(); // [{ title: 'Alice' }]The API surface (prepare, get, all, run, exec) is nearly identical to better-sqlite3, so patterns from that guide — write results reporting changes = 0, using rls_last_rowid(), connection-pool split — carry over unchanged.
Using the shim
The RlsConnection shim is typed against better-sqlite3's interface, but the shape it needs (loadExtension, prepare().run/get/all) matches node:sqlite too. If you prefer the built-in driver, a thin adapter around DatabaseSync works; the extension itself behaves identically either way.
Notes
- Keep
allowExtension: trueand theenableLoadExtension(true/false)bracket tight — enable only long enough to loadrls.so, then disable, so application SQL can't load anything else. (The authorizer also blocks theload_extension()SQL function once guarded.) - The experimental warning is Node's, not the extension's; silence it with
--no-warnings=ExperimentalWarningif it's noisy in logs.