Cluster

Opt-in active/active leader election with fencing-tokenized writes. An external database is required: the framework's default provider stores the leader-election row, the per-chain tip rows, and the shared vault-key fingerprint in the same backend so every node sees one source of truth. When b.cluster.init is never called, the local process behaves as a permanent single leader: isLeader() always returns true, fencingToken() returns 0, no heartbeat runs, no DB is touched. Single-node deployments pay zero overhead.

When init IS called, the framework starts a heartbeat that renews the leader lease via the configured provider. On lease loss (network partition, takeover, lease expiry) the node transitions to follower and write-side framework primitives throw NotLeaderError. The audit + consent chains carry a fencing token alongside every row so a stale leader cannot silently extend the chain after losing its lease — the audit-tip CHECK constraint refuses the stale token at the database layer. The application-level requireLeader() gate is an early-rejection optimisation; the DB constraint is canonical.

Threat model: - Two leaders writing simultaneously — prevented by fencing tokens carried into the audit-tip row. - Follower receiving a write — rejected at the framework boundary via NotLeaderError. Operators front the cluster with a load balancer that routes write paths to the current leader; the discovery handler exposes which node holds the lease. - External-db unreachable — heartbeat fails; after leaseTtl no leader exists and writes fail closed. When the DB recovers, election resumes. - Vault-key drift — every node fingerprints its vault keys on boot and compares against a canonical fingerprint stored in the cluster-state row. A node holding a different key refuses to participate, preventing silent sealed-column corruption.

b.cluster.init(opts) #

stable0.4.0soc2dora
{
  nodeId:             string,            // required; stable identity
  role:               "leader"|"follower",
  leaseTtl:           number,            // ms; default 30000, min 10000
  heartbeatInterval:  number,            // ms; default 10000, min 1000
  endpoint:           string,            // routable URL of THIS node
  allowedProtocols:   number,            // safeUrl.ALLOW_HTTP_TLS by default
  provider:           object,            // custom election provider
  externalDbBackend:  object,            // required when no custom provider
  dialect:            "postgres"|"sqlite"|"mysql",
  acceptVaultKeyRotation: boolean,        // adopt a rotated vault-key
                                         // fingerprint instead of
                                         // refusing boot on mismatch
  expectedVaultKeyFp: string,            // optional; bless ONLY this
                                         // post-rotation fingerprint
  onTransition:       function (event),
}

One-time cluster bootstrap. Configures the leader-election provider, validates the operator-supplied endpoint, runs boot-time rollback detection on the audit + consent chains, fingerprints this node's vault keys against the canonical cluster-state row, then starts the heartbeat that acquires and renews the leader lease. Throws on second invocation, on missing nodeId, on a leaseTtl below 10s, on a heartbeat that doesn't fit comfortably inside the lease, on a role outside leader / follower, and on a chain or vault-key mismatch that would let this node corrupt cluster state.

After a vault-key rotation (b.vault.rotate) the public-key fingerprint changes, so the canonical cluster-state row no longer matches and every node would otherwise refuse boot with VAULT_KEY_DRIFT. Pass acceptVaultKeyRotation: true to declare the change legitimate: the node advances the canonical fingerprint and bumps a rotation epoch instead of refusing. expectedVaultKeyFp narrows the acceptance to a single blessed fingerprint so a typo'd / stale key file is still caught. The strict cross-node drift refusal stays in force whenever the rotation is NOT declared.

await b.cluster.init({
  nodeId:            "api-01",
  role:              "leader",
  leaseTtl:          30000,
  heartbeatInterval: 10000,
  endpoint:          "https://api-01.example.internal:8443",
  externalDbBackend: b.externalDb.backend("primary"),
  dialect:           "postgres",
  onTransition:      function (event) {
    // event.kind ∈ { "lease-acquired", "lease-lost", "lease-released" }
    console.log("cluster transition:", event.kind, event.fencingToken);
  },
});
// → undefined (heartbeat now running)

b.cluster.isLeader() #

stable0.4.0

Synchronous leader check. Returns true when this node currently holds a non-expired lease, OR when b.cluster.init was never called (single-node permanent-leader fallback). Returns false after a graceful shutdown(), after lease loss, or while a follower is waiting for its first lease. Cheap; safe to call on every request to branch leader-only work (scheduled jobs, cache warmers, write-side sweeps).

if (b.cluster.isLeader()) {
  // Run scheduled tick on the leader only.
  await runHourlyRollup();
}
// → undefined

b.cluster.isClusterMode() #

stable0.4.0

Returns true when b.cluster.init has been called AND an externalDbBackend is wired — i.e. framework state (audit, consent, fencing-tokenized writes) should route to the shared external DB. Returns false in single-node fallback or when a custom provider was supplied without an externalDbBackend; in that case the operator owns write-dispatch.

if (b.cluster.isClusterMode()) {
  console.log("framework state lives on", b.cluster.externalDbBackend());
}
// → undefined

b.cluster.externalDbBackend() #

stable0.4.0

Returns the externalDb backend handle wired at init, or null in single-node fallback / when a custom provider was supplied without one. Internal write-dispatch code (audit, consent, fencing-tokenized primitives) calls this to route framework state to the shared backend; operator code rarely needs it directly.

var backend = b.cluster.externalDbBackend();
if (backend) {
  // Framework state lands on the shared cluster DB.
}
// → undefined

b.cluster.dialect() #

stable0.4.0

Returns the SQL dialect string wired at init — "postgres", "sqlite", or "mysql". Used by write-dispatch code that emits raw placeholder syntax ($1 vs ?) against the shared backend.

var ph = b.cluster.dialect() === "postgres" ? "$1" : "?";
// → undefined

b.cluster.currentNodeId() #

stable0.4.0

Returns this node's configured nodeId, or "single-node-local" in the permanent-leader fallback when init was never called. Stable across the lifetime of the process — operators use it to tag audit metadata and observability events with the node identity.

b.audit.safeEmit({
  action:   "system.bootstrapped",
  actor:    { systemNode: b.cluster.currentNodeId() },
  outcome:  "success",
});
// → undefined

b.cluster.endpoint() #

stable0.7.30

This node's routable endpoint URL — the value supplied as opts.endpoint to b.cluster.init. Returns null when not configured or in single-node fallback. External observers wanting to learn the leader's URL should call discoveryHandler() / currentLeader() instead; this getter is for the local node's own self-identity.

var here = b.cluster.endpoint();
// → "https://api-01.example.internal:8443"

b.cluster.fencingToken() #

stable0.4.0soc2

Current monotonic fencing token for this node's lease. Increments with every successful acquisition; a stale leader's token is strictly less than the new leader's, and the audit-tip CHECK constraint refuses inserts carrying a stale token. Returns 0 when no lease is held (follower, between leases, single-node fallback).

var token = b.cluster.fencingToken();
// → 42

b.cluster.requireLeader() #

stable0.4.0

Throws NotLeaderError (statusCode 503) when this node is not the current leader. Use at the top of write-side handlers so a follower receiving a misrouted request rejects fast instead of producing a downstream fencing-token rejection. Single-node deployments where init was never called short-circuit through isLeader() === true and never throw.

try {
  b.cluster.requireLeader();
  await runHourlyRollup();
} catch (e) {
  if (e.isNotLeaderError) {
    // Operator's load balancer should retry on the leader.
    res.writeHead(503).end();
    return;
  }
  throw e;
}
// → undefined

b.cluster.currentLeader() #

stable0.4.0

Async snapshot of the cluster's current leader. Returns { nodeId, leaseExpiresAt, fencingToken, endpoint } when a leader holds a non-expired lease, or null when no node currently holds the lease (election in progress, DB unreachable, lease expired). In single-node fallback, returns the synthetic { nodeId: "single-node-local", leaseExpiresAt: Infinity, ... } record so callers don't need a second branch.

var leader = await b.cluster.currentLeader();
if (leader && leader.endpoint) {
  console.log("forward write to", leader.endpoint);
}
// → undefined

b.cluster.discoveryHandler() #

stable0.7.30

Returns an HTTP (req, res) handler suitable for mounting on any route (e.g. /cluster/leader). Replies 200 JSON with { leader, self } when a leader holds the lease, 503 JSON with { leader: null, self } when no leader exists or the DB is unreachable. Method-agnostic; emits Cache-Control: no-store so caching proxies don't pin a stale leader during a takeover. No auth — intended for infrastructure inside the trust boundary (load balancers, healthchecks, dashboards). Operators exposing the endpoint externally should layer auth via their own middleware.

var leaderProbe = b.cluster.discoveryHandler();
server.on("request", function (req, res) {
  if (req.url === "/cluster/leader") return leaderProbe(req, res);
  // ... rest of routing
});
// → undefined

b.cluster.onTransition(handler) #

stable0.4.0

Register a callback fired on every cluster role transition. Event shape: { kind, nodeId, at, fencingToken? } where kind is one of "lease-acquired", "lease-lost", "lease-released". Multiple handlers can be registered; each runs in registration order and a throwing handler is logged but doesn't break the chain. Throws synchronously when handler is not a function.

b.cluster.onTransition(function (event) {
  b.audit.safeEmit({
    action:   "system.cluster_transition",
    actor:    { systemNode: event.nodeId },
    outcome:  "success",
    metadata: { kind: event.kind, fencingToken: event.fencingToken },
  });
});
// → undefined

b.cluster.shutdown() #

stable0.4.0

Graceful cluster exit. Stops the heartbeat, releases the lease via the provider so the next election round can fire immediately (instead of waiting for leaseTtl to expire), emits a lease-released transition, and resets internal state. Idempotent when init was never called. After shutdown, isLeader() returns false permanently for this process; a fresh init() is required to participate again. Wire into the framework's appShutdown hook so SIGTERM frees the lease before the new replica boots.

process.on("SIGTERM", async function () {
  await b.cluster.shutdown();
  process.exit(0);
});
// → undefined

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