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