Cluster Storage

Cluster-aware framework-state SQL dispatch — runs against the framework's local SQLite in single-node mode and against the operator-supplied external DB in cluster mode. Distributed shared state for audit, consent, sessions, queue, and subject tables; write paths carry the cluster's fencing token so a stale leader cannot extend a chain after losing its lease.

Callers write SQL once using unprefixed logical table names (audit_log, consent_log, …) and ? placeholders. The dispatcher rewrites bare framework tables to their _blamejs_- prefixed cluster names and translates ? to $N for Postgres. Unknown identifiers pass through unchanged so operator-written migrations and app-data SQL are never touched.

The dispatcher is async-only. Even single-node SQLite calls return a resolved Promise so the call shape stays uniform across deployment topologies — callers await every method.

b.clusterStorage.tableName(local) #

stable0.1.9

Resolve a logical framework table name to the active backend's concrete name. In single-node mode returns the input unchanged; in cluster mode returns the _blamejs_-prefixed name from the framework-schema mapping (e.g. audit_log to _blamejs_audit_log). Use this when composing SQL by hand against framework tables — the execute family rewrites bare names automatically, but ad-hoc DDL or admin queries that reference a specific table need the resolved name explicitly.

var b = require("@blamejs/core");
var name = b.clusterStorage.tableName("audit_log");
// → "audit_log"             (single-node)
// → "_blamejs_audit_log"    (cluster mode)

b.clusterStorage.dialect() #

stable0.15.0

Resolve the SQL dialect every framework-table data-layer file must pass to b.sql so the emitted SQL matches the active backend. In cluster mode it returns the operator-configured backend dialect ("postgres" | "mysql" | "sqlite", set at b.cluster.init); in single-node mode the framework state lives in local node:sqlite, so it returns "sqlite". This is the canonical dialect source for framework-state SQL — b.sql defaults to "sqlite" when no dialect is passed, which is correct only on the single-node path and on Postgres by accident (both double-quote identifiers); on MySQL the default would emit double-quoted identifiers MySQL reads as string literals, so framework-table SQL must thread this value explicitly.

var b = require("@blamejs/core");
var dialect = b.clusterStorage.dialect();
// → "sqlite"    (single-node)
// → "postgres"  (cluster mode, postgres backend)
// → "mysql"     (cluster mode, mysql backend)
var built = b.sql.select("_blamejs_cache", { dialect: dialect })
  .where("cacheKey", "k").toSql();

b.clusterStorage.resolveTables(sql) #

stable0.1.9

Rewrite bare framework table names in a SQL string to their cluster-mode _blamejs_-prefixed equivalents. Word-boundary scan; only exact identifier matches are rewritten — substrings, column-qualified names, and operator app tables pass through untouched. In single-node mode the SQL is returned unchanged. The execute family calls this internally; callers reach for it directly only when running raw SQL through a different path (admin tooling, migration runners).

var b = require("@blamejs/core");
var sql = b.clusterStorage.resolveTables(
  "SELECT id FROM audit_log WHERE counter > ?"
);
// → "SELECT id FROM audit_log WHERE counter > ?"          (single-node)
// → "SELECT id FROM _blamejs_audit_log WHERE counter > ?" (cluster)

b.clusterStorage.placeholderize(sql, dialect) #

stable0.1.9

Translate ? placeholders to numbered $1, $2, … form for Postgres backends; passthrough for "sqlite" and "mysql". The walker skips a ? inside a single-quoted string literal (WHERE s = '?'), a double-quoted or backtick-quoted identifier ("c?l"), and a -- or block comment — so only a true bind marker is renumbered. This skip set is a SUPERSET of b.safeSql.countPlaceholders's, so the count used to size params and the renumbering done here can never diverge (a ? one scanner counts but the other rewrites would mis-align bound values). Doubled-quote escapes ('' / "") inside their span are recognized. The execute family calls this on every cluster-mode dispatch; reach for it directly only when shipping raw SQL through a non-execute path.

var b = require("@blamejs/core");
var sql = b.clusterStorage.placeholderize(
  "SELECT id FROM audit_log WHERE counter > ? AND actor = ?",
  "postgres"
);
// → "SELECT id FROM audit_log WHERE counter > $1 AND actor = $2"

b.clusterStorage.execute(sql, params) #

stable0.1.9soc2

Run framework-state SQL against the active backend. In cluster mode the SQL is routed through resolveTables + placeholderize, then dispatched to the operator-supplied external DB. In single-node mode it runs against the framework's local SQLite via db().prepare(...)SELECT and RETURNING queries use .all(), everything else uses .run(). The shape is uniform either way: resolves to { rows, rowCount } where rows is the array of result objects and rowCount is rows.length for selects or info.changes for writes. Throws ClusterStorageError (code cluster-storage/bad-arg) when sql is not a string.

var b = require("@blamejs/core");
var result = await b.clusterStorage.execute(
  "SELECT counter, row_hash FROM audit_log WHERE counter > ?",
  [42]
);
// → { rows: [ { counter: 43, row_hash: "..." } ], rowCount: 1 }

b.clusterStorage.transaction(fn) #

stable0.13.38

Run fn inside an atomic transaction against the active backend, so a multi-statement read-modify-write commits all-or-nothing. fn receives a transaction handle exposing the same execute / executeOne / executeAll surface as the module — but scoped to the open transaction. Use the handle's methods inside fn; calling the module-level b.clusterStorage.execute from within fn would deadlock single-node (it waits for the very transaction fn is running).

Cluster mode dispatches to the external DB's transaction (its own pooled connection + deadlock retry). Single-node serializes against other transactions and against execute on the shared SQLite connection.

await b.clusterStorage.transaction(async function (tx) {
  var row = await tx.executeOne("SELECT v FROM t WHERE k = ?", ["x"]);
  await tx.execute("UPDATE t SET v = ? WHERE k = ?", [row.v + 1, "x"]);
});

b.clusterStorage.executeOne(sql, params) #

stable0.1.9

Convenience over execute for queries expected to return at most one row. Returns the first row when the result set is non-empty, null otherwise. The same dispatch rules as execute apply — cluster mode routes to external DB, single-node hits local SQLite.

var b = require("@blamejs/core");
var row = await b.clusterStorage.executeOne(
  "SELECT counter, row_hash FROM audit_tip WHERE id = ?",
  [1]
);
// → { counter: 128, row_hash: "..." }
// → null when no row matches

b.clusterStorage.executeAll(sql, params) #

stable0.1.9

Convenience over execute for queries expected to return a row array. Returns the rows array directly without the surrounding { rows, rowCount } envelope. Empty result sets resolve to []. The same dispatch rules as execute apply.

var b = require("@blamejs/core");
var rows = await b.clusterStorage.executeAll(
  "SELECT id, status FROM queue_jobs WHERE status = ?",
  ["pending"]
);
// → [ { id: 1, status: "pending" }, { id: 2, status: "pending" } ]

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