DB file lifecycle
b.db owns the entire data layer — schema reconcile, audit chain, sealed columns, the works. That's the right tradeoff when the framework owns the deployment. But operators with their own schema management, their own migration tool, or a Mongo-style document model still want the framework's at-rest encryption + periodic re-flush + WAL-checkpoint snapshot logic without giving up their own connection.
b.db.fileLifecycle(opts) packages just that slice:
1. Decrypt (or opts.encryptedDbPath) to a random tmpfs file (/dev/shm/). 2. Surface the plaintext path (lifecycle.dbPath) — operators open their own new DatabaseSync(dbPath). 3. Periodically (every flushIntervalMs) re-encrypt the plaintext file back to , after running PRAGMA wal_checkpoint(TRUNCATE) against the operator's connection so committed pages are folded in. 4. Provide snapshot(db) for backup callers — same envelope as the on-disk encPath, returned as a Buffer. 5. Provide flushAndCleanup(db, opts) for graceful shutdown — force-flush, optionally remove the plaintext sidecar.
The DB encryption key is read from / created at opts.dbKeyPath (default ). The key file is itself vault-sealed (operator's b.vault instance) — turning the key into per-row data still doesn't help an attacker without the vault keypair.
Composes: - b.crypto.encryptPacked / decryptPacked — same envelope b.db writes, including the deployment-bound AAD. - b.atomicFile — durable writes that don't leave a partial db.enc on a crashed flush. - operator's b.vault instance — seals the DB key on first generation and unseals it at boot.
The framework does NOT touch the SQLite handle — every method that needs to issue SQL takes the operator's db argument explicitly. This keeps the lifecycle primitive composable with any sqlite-shaped layer (node:sqlite, better-sqlite3, bun:sqlite).
b.db.fileLifecycle(opts) #
{
{
dataDir: string, // operator's data dir (used as AAD)
tmpDir?: string, // tmpfs path; default /dev/shm on Linux
allowDiskFallback?: boolean, // permit os.tmpdir() fallback (warns)
encryptedDbPath?: string, // default /db.enc
encryptedDbName?: string, // basename under dataDir (default "db.enc")
dbKeyPath?: string, // default /db.key.enc
vault: , // for sealing the DB key
label?: string, // AAD label (default "default")
flushIntervalMs?: number, // default 5 minutes
}
}
Returns an encrypted-DB-file lifecycle handle. Methods:
- decryptToTmp() — decrypt the encrypted DB file to a fresh tmpfs path and return the path. Idempotent: subsequent calls return the existing path. - dbPath — the resolved plaintext-tmpfs path (set after decryptToTmp() runs). - startFlushTimer(db, opts?) — start a periodic flush timer against the operator's SQLite handle. Returns a stop function. - flushNow(db) — force a single re-encrypt flush (WAL checkpoint + write encPath atomically). Used by backup paths. - snapshot(db) — return the encrypted Buffer (same envelope as flushNow writes), without touching the disk encPath. - flushAndCleanup(db, opts) — shutdown sequence: flushNow, close the handle, optionally remove the plaintext file + WAL/SHM sidecars.
var lc = b.db.fileLifecycle({ dataDir: "/var/lib/app", vault: b.vault });
var dbPath = lc.decryptToTmp();
var db = new (require("node:sqlite").DatabaseSync)(dbPath);
var stop = lc.startFlushTimer(db);
// ... operator runs the app ...
process.on("exit", function () { lc.flushAndCleanup(db, { removePlaintext: true }); });
Last updated 2026-08-08T16:39:49.652Z by seeder.