Appearance
Bun — bun:sqlite
Bun's built-in bun:sqlite is fast and supports loadable extensions out of the box on Linux and macOS.
Load and set up
ts
import { Database } from "bun:sqlite";
const db = new Database("app.db");
db.loadExtension("./dist/rls.so");
db.run(`
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.query("SELECT rls_protect(?)").get(
JSON.stringify({ table: "documents", select: "owner_id = rls_ctx('user_id')" }));
db.query("SELECT rls_seal(?)").get(process.env.RLS_TOKEN!);
db.query("SELECT rls_guard()").get();Serve queries
ts
db.query("SELECT rls_set_context(?)").get(JSON.stringify({ user_id: 1 }));
db.query("SELECT title FROM documents").all(); // [{ title: "Alice" }]If loadExtension complains
Older Bun on macOS fell back to Apple's system SQLite, which is compiled without extension support. If loadExtension throws that extensions aren't enabled, point Bun at a full SQLite build once at startup:
ts
import { Database } from "bun:sqlite";
Database.setCustomSQLite("/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib");On Linux this isn't needed — Bun bundles an extension-capable SQLite.
Notes
db.query(...)returns a cached, reusable prepared statement;db.preparegives a one-off. Either works for the RLS calls.- Write results:
changesis0through a policy view (trigger work isn't counted) — useRETURNINGorrls_last_rowid(). See the caveats. - The per-query context is connection state; keep the bind + query together, or use a connection per concurrent unit of work.