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.missingRelationCode(err) #

stable0.18.58

Does this driver error's own structured fields say the table is absent? Reads errno, code and sqlState, so it holds across SQLite, Postgres and MySQL regardless of how each worded the message or what locale it was worded in.

It answers only what the driver stated. A caller that also wants to recognize a driver carrying no code adds its own message test on top — and that test is the caller's, because how much wording to accept differs: Postgres words a missing COLUMN and a missing TABLE identically apart from the noun, so a reader that falls back to an older projection when a column is absent must not treat a bare "does not exist" as an absent table.

try { await b.clusterStorage.execute(sql); }
catch (e) {
  if (b.clusterStorage.missingRelationCode(e)) return null;   // not created yet
  throw e;                                                    // a real failure
}

b.clusterStorage.missingColumnCode(err) #

stable0.18.58

Does this driver error's own structured fields say the column is absent? The column-level sibling of b.clusterStorage.missingRelationCode, for a reader that widens or narrows its projection to match a table older than the code reading it. The two are separate because they mean opposite things to such a reader: an absent table ends the read, an absent column sends it to an earlier projection.

if (b.clusterStorage.missingColumnCode(e)) return readOlderShape();
throw e;

b.clusterStorage.duplicateIndexCode(err) #

stable0.18.58

Does this driver error's own structured fields say the index name is already taken? For a reconciler re-issuing CREATE INDEX on MySQL, which has no IF NOT EXISTS form, so a second pass has to tell "the index is already there" — the intended end state — from a statement that genuinely failed.

Answering that from the message cannot work: a test for exist also matches Table 'db.X' doesn't exist, so a CREATE INDEX that failed because its table was missing got swallowed and the schema was reported reconciled with the index never created.

try { await b.clusterStorage.execute(createIndexSql); }
catch (e) {
  if (b.clusterStorage.duplicateIndexCode(e)) return;   // already present
  throw e;                                              // a real failure
}

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" } ]

b.clusterStorage.fencedUpsert(opts) #

stable0.18.58
{
  table:       string,    // bare logical table name
  keyColumns:  string[],  // the conflict target
  values:      object,    // every column to write, including the fence column
  fenceColumn:  string,   // default: "fencingToken"
  fenceColumns: string[], // several, all of which must be non-decreasing — for a value like a purge boundary that may only ever advance
  label:       string,    // for the query timeout's diagnostics
  timeoutMs:   number,    // per-statement ceiling
}

Upsert a single row only if the caller's fencing token is at least the one already stored, and report whether it was refused. This is how a single-writer table stays single-writer across processes: a leader that has been superseded still holds a valid-looking handle and can still issue writes, and only the stored token says its turn has passed. Returns { fenced: true } when a higher token has been seen, meaning the write did not happen and this node should step down rather than retry.

The three dialects disagree about how to express that, which is the reason this is one primitive rather than a shape each caller re-derives. Postgres and SQLite take a WHERE on the conflict clause and report the refusal by returning no rows; MySQL's ON DUPLICATE KEY UPDATE has neither, so the fence becomes a per-column IF() and the statement "succeeds" while changing nothing — detection there has to read the stored token back.

var r = await b.clusterStorage.fencedUpsert({
  table: "_blamejs_audit_tip", keyColumns: ["scope"],
  values: { scope: "audit", atMonotonicCounter: 42, fencingToken: 7 },
  label: "audit.tip",
});
if (r.fenced) throw new Error("superseded by a higher-token leader");

Last updated 2026-10-12T14:21:56.879Z by seeder.