Framework Schema

Framework-defined SQL schema (audit / sessions / api_keys / cache / break-glass / scheduler-ticks / pubsub / rate-limit / seeders / etc.) — declarative, migration-aware, and dialect-portable across Postgres and SQLite.

When cluster mode is active the framework's audit chain, consent log, audit checkpoints, audit tip, scheduler ticks, rate-limit counters, pubsub fan-out, sessions, jobs, cache, seeders, and break-glass policies/grants live in the operator's external database (configured via b.externalDb.init). This module owns the DDL for those tables and exposes a single idempotent entry point — b.frameworkSchema.ensureSchema — that operators (or the framework's leader-acquire hook in a later release) call to create them at boot.

External-db tables are prefixed with _blamejs_ so they never collide with the operator's application tables:

audit_log — local-SQLite name _blamejs_audit_log — external-db name

b.frameworkSchema.tableName exposes the mapping so write- dispatch code (cluster-storage.js) can use a single name reference. b.frameworkSchema.LOCAL_TO_EXTERNAL is the frozen read-only mapping object.

Append-only WORM enforcement: ensureSchema installs BEFORE DELETE / BEFORE UPDATE triggers on audit_log, consent_log, and audit_checkpoints — Postgres via plpgsql RAISE EXCEPTION functions, MySQL via SIGNAL SQLSTATE '45000', SQLite via RAISE(ABORT, ...). Idempotent across reboots; any operator-applied DROP TRIGGER is restored on the next ensureSchema pass.

Dialect portability: postgres, mysql, and sqlite are all supported targets. The integer token is BIGINT on Postgres + MySQL (a 32-bit INTEGER overflows a Date.now() ms-epoch value) and INTEGER on SQLite; the binary token is BYTEA / LONGBLOB / BLOB. TEXT columns that participate in a PRIMARY KEY or index become VARCHAR(191) on MySQL (which refuses an unbounded TEXT/BLOB in a key) and stay plain TEXT on Postgres + SQLite.

b.frameworkSchema.setTablePrefix(prefix) #

stable0.14.30

Set the leading prefix applied to every framework-owned table name (audit / consent / sessions / jobs / cache / break-glass / …). The default is _blamejs_; pass a different value to namespace the framework's tables away from an operator schema that would otherwise collide. Config-time only — call it once, before schema creation (b.db.init calls it for you when you pass tablePrefix). Throws a FrameworkSchemaError ("framework-schema/invalid-prefix") when the prefix is not a non-empty SQL identifier, so a typo surfaces at boot rather than as a silently-misnamed table.

The default-prefix output is byte-identical to the historical names, so leaving the prefix unchanged is a no-op.

b.frameworkSchema.setTablePrefix("acme_");
b.frameworkSchema.tableName("audit_log");
// → "acme_audit_log"

try { b.frameworkSchema.setTablePrefix(""); }
catch (e) { e.code; } // → "framework-schema/invalid-prefix"

b.frameworkSchema.getTablePrefix() #

stable0.14.30

Return the prefix currently applied to framework-owned table names — _blamejs_ unless setTablePrefix changed it.

b.frameworkSchema.getTablePrefix();
// → "_blamejs_"

b.frameworkSchema.tableName(localName) #

stable0.5.0

Translate a local-SQLite table name into the external-db name. The mapping is the frozen LOCAL_TO_EXTERNAL object — tables that already carry the framework prefix locally pass through the mapping unchanged. The resolved name's leading prefix is then swapped to the configured prefix (setTablePrefix); with the default _blamejs_ prefix the output is byte-identical to the historical names. Cluster write-dispatch code uses this lookup so the same SQL works against both backends without per-call branching.

b.frameworkSchema.tableName("audit_log");
// → "_blamejs_audit_log"

b.frameworkSchema.tableName("_blamejs_sessions");
// → "_blamejs_sessions"

b.frameworkSchema.tableName("operator_app_table");
// → "operator_app_table"

b.frameworkSchema.coerceRow(row) #

stable0.14.29

Normalize one driver-returned framework row to a type-stable JS shape using COLUMN_TYPES, so a framework column reads identically on every backend: int columns become JS numbers (node-postgres hands BIGINT back as a string), blob columns become Buffers. text columns and any column NOT in the framework schema (operator tables, computed aliases) pass through untouched; null stays null. Idempotent — safe to call on an already-coerced or SQLite-shaped row. Mutates and returns the row.

A BIGINT beyond Number.MAX_SAFE_INTEGER is left as a string rather than silently losing precision (framework counters/timestamps stay well within 2^53, so this never bites in practice).

var row = frameworkSchema.coerceRow(driverRow);
typeof row.monotonicCounter;  // → "number" (was "1" on Postgres)
Buffer.isBuffer(row.nonce);   // → true

b.frameworkSchema.coerceRows(rows) #

stable0.14.29

Apply coerceRow to every row in an array (in place); returns the array. A non-array argument is returned unchanged.

var rows = frameworkSchema.coerceRows(await queryAll(sql));

b.frameworkSchema.ensureSchema(opts) #

stable0.5.0
{
  externalDbBackend: string,     // backend name registered with b.externalDb (required)
  dialect:           "postgres"|"mysql"|"sqlite",  // default: "postgres"
}

Create every framework-owned table + index in the operator's external database, then install append-only WORM triggers on _blamejs_audit_log, _blamejs_consent_log, and _blamejs_audit_checkpoints. Idempotent: every DDL uses IF NOT EXISTS and re-running is safe across reboots.

Returns { tables } with the set of CREATE TABLE names emitted so the operator can confirm the expected surface landed.

Throws FrameworkSchemaError("framework-schema/invalid-config") when externalDbBackend is missing and FrameworkSchemaError("framework-schema/unsupported-dialect") when dialect is anything other than postgres, mysql, or sqlite.

try {
  var report = await b.frameworkSchema.ensureSchema({
    externalDbBackend: "primary",
    dialect:           "postgres",
  });
  report.tables[0]; // → "_blamejs_audit_log"
} catch (e) {
  e.code; // → "framework-schema/unsupported-dialect"
}

Last updated 2026-08-08T16:39:49.652Z by seeder.