Db
Database core — SQLite (node:sqlite) wrapped in encrypted-at-rest storage, sealed-column field-level crypto, append-only audit-chain integration, declarative schema reconcile, and run-once migrations. Default at-rest posture is encrypted: the live .db lives in tmpfs (/dev/shm), is decrypted from at boot, periodically re-encrypted every five minutes, and re- encrypted again at shutdown. The DB encryption key is sealed by b.vault at . Operators who want a plain on-disk SQLite file pass atRest: "plain" and accept a boot warning — sealed columns still protect PII, but schema and row counts are visible to a forensic disk image.
Beyond the storage shell, the module owns the framework's data contract: audit_log / consent_log / audit_checkpoints and the _blamejs_* reserved tables are provisioned before any operator schema reconciles, append-only triggers refuse UPDATE/DELETE on the chain tables, and boot refuses to continue on chain breakage, checkpoint signature failure, audit-log rollback, or PRAGMA integrity_check corruption. WORM declarations (declareWorm) and dual-control gates (declareRequireDualControl) layer SEC 17a-4(f) / FINRA 4511 / 21 CFR Part 11 §11.10(c) record-preservation invariants on operator tables.
The query surface is db.from(table) (chainable), db.prepare (LRU-cached node:sqlite Statement), db.stream (object-mode Readable for million-row exports with auto-unseal), and db.transaction (BEGIN/COMMIT/ROLLBACK around a callback). Postgres-only declarative migrations (declareView / declareRowPolicy) emit migration-shape objects consumed by b.externalDb.migrate.
b.db.collection(name, opts?) #
{
{
overflow?: string, // JSON-text column for unknown fields (off when absent)
jsonColumns?: string[], // auto-stringify on write, auto-parse on read
sealedFields?: { [plain: string]: string }, // plain column → hash column; registers via b.cryptoField
columns?: string[], // explicit column whitelist (defaults to PRAGMA introspection)
}
}
Returns a Mongo-style adapter for the named table. Each method dispatches to b.db.from(name) under the hood; sealed-column semantics, derived-hash translation, and audit emission carry through unchanged.
Pass opts to enable schemaless-document features:
- overflow: "data" — unknown insert/update fields fold into the named JSON-text column. find / findOne parse that column and merge its keys back onto the row. WHERE on an unknown field rewrites to JSON_EXTRACT( ($eq / $ne / $in only — range / LIKE require a real column with an index). - jsonColumns: ["roles", "metadata"] — listed columns are JSON.stringify'd on write and parsed via b.safeJson on read. - sealedFields: { email: "emailHash" } — co-locates a sealed- column / derived-hash declaration with the collection. The plaintext field is registered as sealed; the hash column is registered as a derivedHashes[hashCol] = { from: plain } mapping in b.cryptoField. Subsequent where({ email: "x" }) calls automatically rewrite to where({ emailHash: via the existing query-builder rewrite path. - columns: ["_id", "email", ...] — explicit column whitelist. If omitted, the framework introspects via PRAGMA table_info once at first use and caches.
// requires: a process that has not already opened a database
var b = require("@blamejs/core");
await b.db.init({ dataDir: "/tmp/data", schema: [{
name: "users",
columns: {
_id: "TEXT PRIMARY KEY",
email: "TEXT",
emailHash: "TEXT",
roles: "TEXT",
data: "TEXT",
},
}] });
var users = b.db.collection("users", {
overflow: "data",
jsonColumns: ["roles"],
sealedFields: { email: "emailHash" },
});
users.insert({ _id: "u1", email: "alice@x.com", roles: ["admin"], dept: "eng", joined: "2026-01-01" });
// → roles is JSON-stringified; dept + joined fold into data; email seals + emailHash derives
users.findOne({ email: "alice@x.com" });
// → { _id: "u1", email: "alice@x.com", roles: ["admin"], dept: "eng", joined: "2026-01-01" }
users.find({ dept: "eng" });
// → JSON_EXTRACT(data, '$.dept') = 'eng'
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.
// requires: write access to the dataDir below
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 }); });
b.db.snapshot() #
In-memory encrypted snapshot — same envelope shape that flushToDisk writes, just held in memory. Operators capturing a backup mid-flight (b.backup wrapping a hot DB) get a Buffer they can stream onward to object storage without touching the on-disk encPath. Forces a WAL checkpoint first so the snapshot reflects committed state, not pre-WAL pages.
Under atRest: 'plain' returns the raw plaintext SQLite file as a Buffer (no envelope), since there's no encryption key to apply — operators wanting an encrypted snapshot under plain mode wrap with their own b.crypto.encryptPacked at the call site.
// requires: write access to the backup directory below
var b = require("@blamejs/core");
var snap = b.db.snapshot();
var store = b.objectStore.buildBackend({ protocol: "local", rootDir: "/srv/backups" });
await store.put("backups/" + Date.now() + ".enc", snap);
b.db.init(opts) #
{
dataDir: string, // required — where db.enc + db.key.enc live
schema: Array, // required — [{ name, columns, indexes, sealedFields, derivedHashes, foreignKeys, primaryKey, subjectField, personalDataCategories }, ...]
atRest: "encrypted"|"plain", // default "encrypted"
immutable: boolean, // default false — requires `readOnly` and `atRest: "plain"`. Declares that NOTHING writes this volume while it is open, so SQLite reads it without creating the `-wal` / `-shm` pair it otherwise needs for a WAL-mode database. That pair is what makes a plain read-only open fail on a read-only mount. Only the operator can make this claim: if a writer is in fact active, the reader's view is undefined rather than merely stale, which is why it is never inferred.
readOnly: boolean, // default false — open without ever writing back. Under `atRest: "encrypted"` two processes sharing one volume each decrypt their own working copy, so whichever flushes last overwrites the other's writes; a reader takes no part in that. SQLite refuses writes too, so a stray one fails where it is issued.
tmpDir: string, // the encrypted-mode tmpfs path. Defaults to `BLAMEJS_TMPDIR`, then to `/dev/shm` on Linux only — off Linux nothing is inferred and encrypted mode refuses with `db/no-tmpfs` until one of the two names a mount.
allowNonTmpfsTmpDir: boolean, // default false — on Linux, where the mount table can be compared against, encrypted mode THROWS when tmpDir resolves outside the recognized tmpfs mounts (plaintext-on-disk leak); pass true to downgrade that to a warning when the mount is verified in-memory out-of-band. Off Linux the mount cannot be classified, so an operator-named path is always taken with a warning and this option changes nothing.
migrationDir: string, // optional — path to ./migrations/ (run-once each)
streamLimit: number, // default 1_000_000 — db.stream row ceiling
columnGate: "reject"|"warn"|"off", // default "reject" — refuse queries on columns not declared in the table schema
skipBootIntegrityCheck: boolean, // default false — skip PRAGMA integrity_check
skipIntegrityCheck: boolean, // default false — alias
auditSigning: { mode, algorithm }, // default { mode: "wrapped" }
acceptUnsignedPurgeAnchor: boolean, // default: false — one boot only; signs an anchor left unsigned by an earlier version, then does nothing. Refused with `readOnly` — it writes
acceptRotatedPurgeAnchorKey: boolean, // default: false — one boot only; re-signs, under the live key, an anchor still naming a key that was rotated out. For a rotation interrupted before `b.auditTools.signExistingPurgeAnchor()` ran. The existing signature must verify under the rotated key first, so a tampered anchor is refused rather than laundered. Refused with `readOnly` — it writes
purgeAnchorPublicKey: string, // PEM. The key a purge anchor must be signed under, for a deployment running `auditSigning: false`: no key is loaded there, and the volume's own public-key history is unsealed, so nothing on disk can say which key was AUTHORIZED. Supply one and the anchor is verified against it — a trust root the operator chose, bound to the fingerprint the anchor names. Without it the anchor reports as unchecked rather than verified
resolvePurgeArchive: function, // (archiveBundleId, { firstCounter, lastCounter, lastRowHash }) => truthy if that archive can still be produced; boot refuses a purge boundary whose archive has gone missing. Several slices can share the checkpoint that covers them, and so share the identifier — match the range too, or a surviving sibling answers for the one that went missing
ntpServers: string[], // override NTP server list
ntpTimeoutMs: number, // override NTP timeout
dataResidency: object, // operator's region declaration
}
Boot the database. Provisions the framework-baked tables (audit_log / consent_log / audit_checkpoints / _blamejs_*), reconciles the operator schema, installs append- only triggers on chain tables, runs any pending file-based migrations, verifies the audit + consent chains end-to-end, verifies every audit checkpoint signature, runs PRAGMA integrity_check, performs a rollback-detection check against audit.tip, and runs a best-effort SNTP boot drift check. Refuses to boot on any chain breakage, signature mismatch, or rollback — compliance posture demands fail-closed at the earliest signal.
Each of those boot checks has its own code, so a refusal says which one failed: db/audit-chain-break and db/consent-chain-break for a chain whose row hashes do not link, db/audit-checkpoint-break for a checkpoint signature that does not verify, db/audit-tip-unreadable when the recorded tip cannot be read, db/audit-rollback-detected when the chain is shorter than that tip, db/integrity-check-failed for a failed PRAGMA integrity_check, and db/enc-truncated or db/key-unseal-empty for a data file or sealed key that does not open.
db/working-copy-missing, db/flush-pending-wal and db/immutable-pending-wal are raised by the encrypt-to-disk path rather than by this call, which runs on the flush timer, at process exit and on b.db.close, so init does not refuse with them.
The configuration is refused before any of that: db/bad-init for an opts it cannot read, db/bad-at-rest, db/bad-immutable, db/bad-read-only, db/bad-seal-migration, db/bad-min-free-bytes, db/bad-personal-data-categories and db/bad-resolve-purge-archive for a setting whose shape is wrong, db/bad-personal-data-category for one field's category within that map that is not a non-empty string, db/reserved-table-name for a schema table in the framework's own namespace, db/read-only-no-key for a read-only boot with no key to open the data with, and db/posture-violation when the compliance posture forbids the configuration asked for. The purge-anchor settings refuse with db/bad-purge-anchor-public-key, db/bad-accept-unsigned-purge-anchor and db/bad-accept-rotated-purge-anchor-key. The time settings refuse with db/bad-ntp-setting, and the boot drift check with db/ntp-unreachable, db/ntp-drift-fatal or db/nts-required when authenticated time is demanded and unavailable.
Encrypted mode needs a memory-backed temporary directory, and the two ways that can fail are separate codes: db/no-tmpfs when no path is named at all, and db/tmpdir-not-tmpfs when one is named but is not a tmpfs mount, which allowNonTmpfsTmpDir: true accepts with the disk-residency tradeoff.
var b = require("blamejs");
await b.db.init({
dataDir: "/var/lib/myapp",
atRest: "encrypted",
schema: [
{
name: "orders",
columns: {
_id: "TEXT PRIMARY KEY",
customerId: "TEXT NOT NULL",
totalCents: "INTEGER NOT NULL",
note: "TEXT",
createdAt: "INTEGER NOT NULL",
},
indexes: ["customerId"],
sealedFields: ["note"],
derivedHashes: { customerIdHash: { from: "customerId" } },
subjectField: "customerId",
},
],
});
b.db.from(tableName) #
Open a chainable Query against a registered table. Sealed columns auto-encrypt on insert/update and auto-decrypt on read; derived- hash columns auto-populate from their source field on insert. Identifier safety, parameter binding, row-policy gates, and audit-emission are wired into the chain so operator code never concatenates SQL by hand.
.asActor(actor) names the principal the read is for, which is what b.cryptoField.configureUnsealRateCap counts unseal failures per. Without it every read shares one bucket, so a forged value in a sealed column puts every reader of that column into the same cooldown. The actor is the object the request carries, or a string or number naming the principal directly. The chain refuses a value that names no principal, which includes an object carrying none of the identity fields, an empty string, and a non-finite number.
var b = require("blamejs");
await b.db.init({ dataDir: "/tmp/data", schema: [
{ name: "orders",
columns: { _id: "TEXT PRIMARY KEY", customerId: "TEXT NOT NULL", totalCents: "INTEGER NOT NULL" },
sealedFields: ["customerId"] },
] });
b.db.from("orders").insertOne({
_id: b.uuid.v7(), customerId: "cust_123", totalCents: 4999,
});
var rows = b.db.from("orders").where({ customerId: "cust_123" }).all();
rows.length;
// → 1
b.db.getDeclaredColumns(tableName) #
Returns the declared column names for a table as an array, or null when the table has no registered schema metadata (a cross- or attached-schema table — the column-membership gate is a no-op for those). The declared set includes _id and any derived-hash columns, so sealed-field queries (which rewrite to the hash column) and _id lookups pass the gate. Backs the db.init({ columnGate }) gate that refuses queries ordering / selecting / filtering on an undeclared column before the identifier interpolates into SQL.
b.db.getDeclaredColumns("orders");
// → the columns that table was declared with, ["_id", "customerId",
// "total", "createdAt"] say, and null for a table nothing declared
b.db.prepare(sql) #
Raw-escape-hatch wrapper around node:sqlite's Statement preparation, with an LRU cache keyed by SQL string (cap 256 distinct shapes). Reuse of the same SQL returns the cached Statement so a hot path doesn't churn file descriptors. Use b.db.from(table) for the typical chainable surface; prepare is for the rare cases where the chainable Query doesn't cover the shape.
var b = require("blamejs");
await b.db.init({ dataDir: "/tmp/data", schema: [
{ name: "orders",
columns: { _id: "TEXT PRIMARY KEY", totalCents: "INTEGER NOT NULL" } },
] });
var stmt = b.db.prepare("SELECT SUM(totalCents) AS total FROM orders");
var row = stmt.get();
typeof row.total;
// → "object"
b.db.stream(sql) #
Object-mode Readable that yields rows as node:sqlite's iterate() produces them. Unlike .all(), the engine never materializes the full result set, so audit exports, backup table dumps, and million-row reports finish without OOM pressure. Variadic: positional parameter bindings come after sql; an optional final plain-object argument carries opts.table (enables sealed-column auto-unseal) and opts.streamLimit (per-call row ceiling override). Default ceiling is the module-level streamLimit (1_000_000); the stream destroys with a db/stream-limit-exceeded error past the cap rather than accumulating unboundedly. A streamLimit that is not a positive integer throws db/bad-stream-limit before the stream opens, and calling it before b.db.init() has been awaited throws db/not-initialized.
var b = require("blamejs");
await b.db.init({ dataDir: "/tmp/data", schema: [
{ name: "events",
columns: { _id: "TEXT PRIMARY KEY", payload: "TEXT" },
sealedFields: ["payload"] },
] });
var count = 0;
var s = b.db.stream("SELECT * FROM events", { table: "events" });
await new Promise(function (resolve, reject) {
s.on("data", function (_row) { count += 1; });
s.on("end", resolve);
s.on("error", reject);
});
count >= 0;
// → true
b.db.transaction(fn) #
Run fn(db) inside a BEGIN ... COMMIT block; any throw inside fn triggers ROLLBACK and re-propagates the error. Returns the value fn returned. Transactions compose with the chainable Query surface and with audit-chain emissions inside the body — the audit row's chain hash is computed from the value at COMMIT time, so a rolled-back transaction never leaves a phantom row in audit_log.
var b = require("blamejs");
await b.db.init({ dataDir: "/tmp/data", schema: [
{ name: "ledger",
columns: { _id: "TEXT PRIMARY KEY", balanceCents: "INTEGER NOT NULL" } },
] });
b.db.from("ledger").insertOne({ _id: "acct_1", balanceCents: 100 });
b.db.from("ledger").insertOne({ _id: "acct_2", balanceCents: 0 });
b.db.transaction(function (db) {
db.from("ledger").where({ _id: "acct_1" }).update({ balanceCents: 50 });
db.from("ledger").where({ _id: "acct_2" }).update({ balanceCents: 50 });
});
b.db.from("ledger").where({ _id: "acct_2" }).first().balanceCents;
// → 50
b.db.hashFor(table, field, value) #
Look up the deterministic SHA3 hash a sealed-source field maps to via the table's registered derivedHashes. Used to query a sealed column without unsealing every row — operator code passes the cleartext, the framework hashes it through the same namespaced derivation, and a WHERE lookup returns the matching rows. Returns null when the field has no derived-hash declaration on the table.
var b = require("blamejs");
await b.db.init({ dataDir: "/tmp/data", schema: [
{ name: "users",
columns: { _id: "TEXT PRIMARY KEY", email: "TEXT", emailHash: "TEXT" },
sealedFields: ["email"],
derivedHashes: { emailHash: { from: "email" } } },
] });
b.db.from("users").insertOne({ _id: "u1", email: "alice@example.com" });
var h = b.db.hashFor("users", "email", "alice@example.com");
typeof h;
// → "string"
b.db.hashCandidatesFor(table, field, value) #
Dual-read sibling of hashFor. Returns { field, values } where values holds the active derived-hash digest AND — across the v0.15.0 keyed-MAC default flip — the legacy salted-sha3 digest a row written before the flip carries. A WHERE lookup over values matches both keyed-indexed and legacy-indexed rows, so the flip never silently drops an un-migrated row. Returns null when the field has no derived-hash declaration on the table.
// requires: a "users" table declaring email as a sealed field
var c = b.db.hashCandidatesFor("users", "email", "alice@example.com");
b.db.from("users").whereIn(c.field, c.values).all();
// → rows matching either the keyed-MAC or the legacy digest
b.db.exportCsv(opts) #
{
table: string, // required — registered table name
columns: string[], // optional column projection (default: all)
where: object, // optional Query.where(...) filter
bom: boolean, // default false; emit U+FEFF prefix
format: "rfc4180", // default "rfc4180" (only supported value)
timestampFields: string[], // ms-int columns to cast to ISO-8601
signWith: object, // signer with sign / getPublicKey / getAlgorithm / getPublicKeyFingerprint
}
RFC 4180 strict CSV export of a single registered table, with sealed-column auto-unseal (rides the chainable Query), optional WHERE filter, optional column projection, optional UTF-8 BOM, ISO-8601 cast for declared timestamp fields, SHA3-512 manifest of the byte stream, and an optional detached signature via any b.auditSign-shaped signer. Refuses unknown table names, refuses arbitrary column strings (every column must belong to the table), and emits a db.export.csv audit row.
var b = require("blamejs");
await b.db.init({ dataDir: "/tmp/data", schema: [
{ name: "orders",
columns: { _id: "TEXT PRIMARY KEY", totalCents: "INTEGER NOT NULL", createdAt: "INTEGER NOT NULL" } },
] });
b.db.from("orders").insertOne({ _id: "o1", totalCents: 4999, createdAt: Date.now() });
var out = b.db.exportCsv({
table: "orders",
columns: ["_id", "totalCents", "createdAt"],
bom: true,
timestampFields: ["createdAt"],
});
typeof out.sha3_512;
// → "string"
out.rowCount >= 1;
// → true
b.db.close() #
Idempotent shutdown. Stops the periodic encrypt timer, fires a best-effort final audit checkpoint when the local node is the cluster leader, re-encrypts the live tmpfs database back to , closes the SQLite handle (releasing the file lock on Windows), then unlinks the plaintext sidecar files in tmpnodeFs. Safe to call multiple times — no-ops after the first successful close.
var b = require("blamejs");
await b.db.init({ dataDir: "/tmp/data", schema: [] });
b.db.close();
b.db.close();
// → undefined
b.db.declareWorm(args) #
{
tables: string[], // required — non-empty array of operator table names
posture: string, // optional — posture label recorded on each row
}
Install row-level WORM (write-once-read-many) triggers on operator-named business-record tables. Per SEC Rule 17a-4(f), FINRA Rule 4511, and 21 CFR Part 11 §11.10(c). UPDATE and DELETE are refused at the SQLite-trigger level, independent of the application's discipline. Each declared table is registered in _blamejs_worm_tables; under sec-17a-4 / finra-4511 / fda-21cfr11 postures the boot-time assertion refuses to start if the registry is empty. Cluster mode (external-db) refuses the call — operators install WORM via b.externalDb.migrate instead.
var b = require("blamejs");
await b.db.init({ dataDir: "/tmp/data", schema: [
{ name: "trade_blotter",
columns: { _id: "TEXT PRIMARY KEY", symbol: "TEXT NOT NULL", qty: "INTEGER NOT NULL" } },
] });
var declared = b.db.declareWorm({
tables: ["trade_blotter"],
posture: "sec-17a-4",
});
declared.tables;
// → ["trade_blotter"]
b.db.declareRequireDualControl(args) #
{
tables: string[], // required — non-empty array of table names
m: number, // default 2 — minimum approvals
n: number, // default max(2, m) — total approver pool
posture: string, // optional — posture label recorded with the gate
}
Gate destructive operations (b.db.eraseHard, retention sweeps, audit purges) on operator-named tables behind an m-of-n dual- control grant. Each declared table is registered in _blamejs_dual_control_gates with its quorum tuple (m, n); the gate consult on eraseHard refuses execution unless the caller passes opts.dualControlGrant returned by b.dualControl.consume().
var b = require("blamejs");
await b.db.init({ dataDir: "/tmp/data", schema: [
{ name: "patient_records",
columns: { _id: "TEXT PRIMARY KEY", chartJson: "TEXT" } },
] });
var gate = b.db.declareRequireDualControl({
tables: ["patient_records"],
m: 2,
n: 3,
posture: "hipaa",
});
gate.m;
// → 2
b.db.eraseHard(tableName, rowId, opts) #
{
reason: string, // required — non-empty rationale recorded in audit
subjectId: string, // optional — consults legal-hold registry
dualControlGrant: object, // required when the table is gated; from b.dualControl.consume()
}
Crypto-erase one row plus a REINDEX on the table so freed B-tree pages can't reconstruct the deleted row's index entries. Closes the F-RTBF B-tree-residual class on a per-row basis. Consults the legal-hold registry (refuses on subjectId held) and the dual- control gate registry (refuses unless opts.dualControlGrant is a consumed grant); emits a db.erase_hard audit row on success or a db.erase_hard.denied audit row on either gate refusal.
var b = require("blamejs");
await b.db.init({ dataDir: "/tmp/data", schema: [
{ name: "stale_pii",
columns: { _id: "TEXT PRIMARY KEY", ssn: "TEXT" },
sealedFields: ["ssn"] },
] });
b.db.from("stale_pii").insertOne({ _id: "row1", ssn: "123-45-6789" });
var result = b.db.eraseHard("stale_pii", "row1", {
reason: "subject erasure under GDPR Art 17",
});
result.rowsDeleted;
// → 1
b.db.vacuumAfterErase(opts) #
{
mode: "incremental"|"full", // default "incremental"
pages: number, // incremental only; default 1000
}
Run after a large-scale erase (b.subject.erase batch, b.retention sweep) so SQLite's freed pages don't linger with sealed-column ciphertext that a forensic disk image could recover. incremental mode runs PRAGMA incremental_vacuum(N) (default 1000 pages) — fast, doesn't rewrite the whole file. full mode runs VACUUM — rewrites every page; the database is locked for the duration.
var b = require("blamejs");
await b.db.init({ dataDir: "/tmp/data", schema: [] });
b.db.vacuumAfterErase({ mode: "incremental", pages: 500 });
// → undefined
b.db.applyPosture(posture) #
Record the active compliance posture for the database subsystem. Called by b.compliance.set(p) during posture cascade so the downstream cryptoField.eraseRow path can consult getActivePosture() and auto-vacuum under postures whose defaults set requireVacuumAfterErase: true. Returns null for empty input; otherwise { posture, dbInitialized }.
var b = require("blamejs");
var result = b.db.applyPosture("hipaa");
result.posture;
// → "hipaa"
b.db.getActivePosture() #
Read the posture last installed via applyPosture. Used by downstream subsystems (cryptoField.eraseRow, retention sweeps) to branch on posture-driven defaults. Returns null before any posture has been set.
var b = require("blamejs");
b.db.applyPosture("pci-dss");
b.db.getActivePosture();
// → "pci-dss"
b.db.runSql(sql) #
Execute a raw SQL string with no result-set return — DDL (CREATE TABLE / DROP TABLE / ALTER / etc.), DML where the caller doesn't need rows back, and BEGIN / COMMIT / ROLLBACK outside of transaction(). Slow-query observability buckets fire on every call. DDL statements emit a db.ddl.executed audit row with the leading keyword extracted so a forensic review can reconstruct schema evolution from the audit chain alone.
var b = require("blamejs");
await b.db.init({ dataDir: "/tmp/data", schema: [] });
b.db.runSql("CREATE TABLE IF NOT EXISTS scratch (id INTEGER PRIMARY KEY)");
// → undefined
b.db.flushToDisk() #
Force the live tmpfs SQLite to be re-encrypted to immediately. The framework already does this every five minutes and at clean shutdown; operators running a backup workflow call flushToDisk() first so the snapshot source reflects the most recent committed state. No-op in atRest: "plain" mode (no db.enc exists).
var b = require("blamejs");
await b.db.init({ dataDir: "/tmp/data", atRest: "encrypted", schema: [] });
b.db.flushToDisk();
// → undefined
b.db.getStreamLimit() #
Read the module-level streamLimit ceiling (default 1_000_000). Per-call opts.streamLimit on db.stream overrides this; db.init({ streamLimit }) raises or lowers it for the process.
var b = require("blamejs");
await b.db.init({ dataDir: "/tmp/data", schema: [] });
b.db.getStreamLimit() > 0;
// → true
b.db.integrityCheck() #
Run PRAGMA integrity_check on the live database. Returns the string "ok" on a clean check or an array of corruption descriptions otherwise. Operators wire this into a /healthz handler or a periodic monitor.
var b = require("blamejs");
await b.db.init({ dataDir: "/tmp/data", schema: [] });
b.db.integrityCheck();
// → "ok"
b.db.integrityMonitor(opts) #
{
intervalMs: number, // default C.TIME.hours(24)
audit: boolean, // default true; emit audit rows on every check
onCorruption: Function, // (issues) => void; fires on corruption
}
Periodic PRAGMA integrity_check runner. Returns a handle with .stop() for graceful shutdown. Emits system.db.integrity_ok / system.db.integrity_corrupt audit rows and matching observability counters on every check. Operators pass onCorruption to receive the issues array on detection (alerts, page outs, kill-switches).
var b = require("blamejs");
await b.db.init({ dataDir: "/tmp/data", schema: [] });
var mon = b.db.integrityMonitor({
intervalMs: 60000,
onCorruption: function (_issues) { },
});
mon.stop();
b.db.purgeAuditChain(args) #
{
lastPurgedCounter: number, // required — non-negative; rows at or below this counter are deleted
}
Narrow-purpose DELETE against audit_log + audit_checkpoints for use by audit-tools.purge. Drops the BEFORE-DELETE append- only triggers inside a transaction, executes the deletion against rows with monotonicCounter <= lastPurgedCounter, then re- installs the triggers so the append-only invariant resumes. Cluster mode delegates to cluster-storage (no triggers in external-db). The caller is responsible for verifying purge legitimacy via audit-tools.verifyBundle before invoking.
var b = require("blamejs");
await b.db.init({ dataDir: "/tmp/data", schema: [] });
var result = await b.db.purgeAuditChain({ lastPurgedCounter: 0 });
typeof result.rowsDeleted;
// → "number"
b.db.getMode() #
Diagnostic accessor — returns the active at-rest posture ("encrypted" or "plain") chosen at init time. The posture is the one the FIRST init selected: a later init on an already-initialized process returns without reading its options, so this reports that first choice.
// requires: a process in which b.db.init has not already run, because the
// posture reported is the one the first init selected
var b = require("blamejs");
await b.db.init({ dataDir: "/tmp/data", atRest: "plain", schema: [] });
b.db.getMode();
// → "plain"
b.db.getDbPath() #
Diagnostic accessor — returns the absolute path of the live SQLite file. In encrypted mode this is a tmpfs path (e.g. /dev/shm/blamejs-); in plain mode it's .
var b = require("blamejs");
await b.db.init({ dataDir: "/tmp/data", atRest: "plain", schema: [] });
typeof b.db.getDbPath();
// → "string"
b.db.getDataResidency() #
Read the operator's declared data-residency configuration (passed via db.init({ dataResidency })). Storage / mail / log destinations consult this to refuse cross-region writes.
// requires: a process that has not already opened a database
var b = require("blamejs");
await b.db.init({
dataDir: "/tmp/data",
dataResidency: { region: "eu-west-1" },
schema: [],
});
b.db.getDataResidency().region;
// → "eu-west-1"
b.db.getTableMetadata(nameOrOpts) #
Reflective metadata for one or every registered table — primary- key columns, foreign keys, sealed-field list, derived-hash declarations, subject mapping, personal-data categories. Returns a deep-copied snapshot; mutations don't affect framework state. Two-arg form supports format dispatch: getTableMetadata({ table, format: "json-schema-2020-12" }) emits a JSON Schema 2020-12 document with sealed columns annotated x-blamejs-sealed: true and derived-hash columns annotated x-blamejs-derived-from: ".
// requires: a process that has not already opened a database
var b = require("blamejs");
await b.db.init({ dataDir: "/tmp/data", schema: [
{ name: "users",
columns: { _id: "TEXT PRIMARY KEY", email: "TEXT" },
sealedFields: ["email"] },
] });
var meta = b.db.getTableMetadata("users");
meta.sealedFields;
// → ["email"]
var schema = b.db.getTableMetadata({
table: "users",
format: "json-schema-2020-12",
});
schema.properties.email["x-blamejs-sealed"];
// → true
b.db.declareView(opts) #
{
schema: string, // required — schema the view is created in
name: string, // required — view identifier
source: string | string[], // required — source table, "schema.table" or ["schema","table"]; a single segment resolves under "public"
redactColumns: string[], // excluded from the view; each must exist on the source at apply time (default: none)
sealedColumns: string[], // excluded from the view; absent from the source without error (default: none)
hashColumns: object, // { hashColumn: sourcePlaintextColumn }; both must exist on the source (default: none)
whereClause: string, // boolean expression appended as WHERE; a ";" raises declare-view/bad-where (default: none)
grantTo: string[], // roles granted SELECT; each must exist in pg_roles at apply time (default: none)
backend: string, // pins the spec to one externalDb backend; applying it against another raises declare-view/backend-mismatch (default: the runner's backend)
}
Declarative CREATE VIEW + GRANT migration spec for a Postgres-backed b.externalDb deployment. Returns a migration- shape object (description, target, up, down) consumed by b.externalDb.migrate. Postgres-only; fail-fast at apply time on other dialects.
The caller does not write the column list. At apply time the source table's columns are read from information_schema.columns and the excluded ones are removed, so redactColumns, sealedColumns and the source columns named by hashColumns do not reach the view. A spec whose exclusions remove every column raises declare-view/empty-select.
var spec = b.db.declareView({
schema: "public",
name: "active_users",
source: "users",
redactColumns: ["ssn"],
grantTo: ["app_reader"],
});
spec.description;
// → "declareView public.active_users"
b.db.declareRowPolicy(opts) #
{
schema: string, // required — schema holding the table
table: string, // required — target table
name: string, // required — policy identifier
using: string, // required — USING boolean expression; a ";" raises declare-row-policy/bad-expression
withCheck: string, // WITH CHECK expression for writes; same ";" refusal (default: none, so USING alone governs)
role: string, // one role the policy applies TO (default: none, so it applies to every role)
command: string, // "ALL" | "SELECT" | "INSERT" | "UPDATE" | "DELETE", matched case-insensitively (default: "ALL")
permissive: boolean, // false emits a RESTRICTIVE policy, which narrows rather than widens access (default: true)
backend: string, // pins the spec to one externalDb backend; applying it against another raises declare-row-policy/backend-mismatch (default: the runner's backend)
}
Declarative Postgres ROW LEVEL SECURITY migration spec. Pairs with b.externalDb.transaction({ sessionGucs }) for the per- request SET LOCAL plumbing that scopes the policy. Returns a migration-shape object (description, target, up, down) consumed by b.externalDb.migrate. Postgres-only; fail-fast on other dialects.
Applying the spec enables row-level security on the table when it is not enabled already, then creates the policy through b.sql.createPolicy. A table that is not visible to the migration role raises declare-row-policy/table-not-found.
var spec = b.db.declareRowPolicy({
schema: "public",
table: "orders",
name: "tenant_isolation",
command: "ALL",
using: "tenant_id = current_setting('app.tenant_id')::uuid",
role: "app_user",
});
spec.description;
// → "declareRowPolicy public.orders.tenant_isolation"