API Snapshot
Public-API surface walker plus breaking-change detector — the framework's LTS-contract enforcement at the type level. Operators capture a snapshot of the framework's module.exports tree, commit it alongside the version bump, and the release workflow's check-api-snapshot.js gate fails CI when any subsequent change removes or retypes a previously-shipped public member.
Three diff classes:
- removed a member present in the old snapshot but not the new (BREAKING — fails CI) - typeChanged a member's category flipped, e.g. function → object, primitive → instance (BREAKING — fails CI) - additive a new member that wasn't in the old snapshot (informational — signals the snapshot is out of date and the operator should rerun capture)
Walker rules: functions record as { type: "function", arity: fn.length }; plain objects recurse into enumerable string keys; primitives record as { type: "primitive", valueType } without capturing the literal value (so a version-string change in b.version doesn't fail CI); non-plain objects (Map, Set, Buffer, Date, RegExp, Error instances) record as { type: "instance", ctorName } without recursion; cycles short-circuit as { type: "cycle" }; depth is capped at opts.maxDepth (default 8). Members whose key starts with _ are skipped — the framework convention for test seams and internal helpers.
Function-arity changes: a DECREASE in fn.length is breaking (the operator removed a required parameter). An INCREASE is not flagged because adding an optional trailing parameter is additive to existing callers.
On-disk format: stable canonical JSON ordered as { version, frameworkVersion, createdAt, exports }. The format version (b.apiSnapshot.SNAPSHOT_FORMAT_VERSION) is checked on read so a future schema bump can't silently mis-compare against an older baseline.
b.apiSnapshot.capture(target, opts) #
{
maxDepth: 8, // recursion ceiling
skipUnderscore: true, // skip `_internal` keys
frameworkVersion: "0.8.48", // override target.version
createdAt: "2026-05-09T...", // pin for deterministic snapshots
}
Walk a module's exports tree and produce a snapshot object { version, frameworkVersion, createdAt, exports } suitable for round-tripping through write / read. The walk is recursive with cycle detection and a depth cap; underscore-prefixed keys are skipped by default (override with skipUnderscore: false). Throws ApiSnapshotError when the top-level target is not a plain object — class instances and runtime-built exports can't be walked by category.
var snap = b.apiSnapshot.capture(require("@blamejs/core"));
// → { version: 1, frameworkVersion: "0.8.48",
// createdAt: "2026-05-09T12:00:00.000Z",
// exports: { uuid: { type: "object", members: {...} }, ... } }
b.apiSnapshot.write(snapshot, filePath) #
Serialize a snapshot to disk in canonical JSON form (stable { version, frameworkVersion, createdAt, exports } ordering, mode 0o644). Returns the filePath written. Throws ApiSnapshotError when the snapshot or path is missing — the release workflow surfaces typos at commit time instead of writing to an unintended location.
var snap = b.apiSnapshot.capture(require("@blamejs/core"));
var written = b.apiSnapshot.write(snap, "./api-snapshot.json");
// → "./api-snapshot.json"
b.apiSnapshot.read(filePath) #
Load a snapshot from disk and validate its envelope. Throws ApiSnapshotError with a specific code on each failure mode — api-snapshot/missing (no file), api-snapshot/read-failed (I/O error), api-snapshot/bad-json (parse failure), or api-snapshot/bad-version (format-version mismatch — the baseline was written by a different snapshot major) — so the release workflow can surface a precise reason instead of a generic "snapshot broken" message.
var loaded = b.apiSnapshot.read("./api-snapshot.json");
var current = b.apiSnapshot.capture(require("@blamejs/core"));
var diff = b.apiSnapshot.compare(loaded, current);
// → { breaking: [], additive: [], typeChanged: [] }
b.apiSnapshot.compare(oldSnapshot, newSnapshot) #
Diff two snapshots and return { breaking, additive, typeChanged }. breaking carries every member that was removed, retyped, lost arity, swapped its constructor name, or changed primitive valueType; the release workflow exits non-zero when this list is non-empty. additive lists new members (informational — operator should rerun capture and commit the refreshed baseline). typeChanged is a subset of breaking surfaced separately for easier triage.
var loaded = b.apiSnapshot.read("./api-snapshot.json");
var current = b.apiSnapshot.capture(require("@blamejs/core"));
var diff = b.apiSnapshot.compare(loaded, current);
if (diff.breaking.length > 0) {
console.error(b.apiSnapshot.formatDiff(diff));
process.exit(1);
}
b.apiSnapshot.formatDiff(diff) #
Render a diff result from compare into a human-readable multi-line string suitable for console.error in a CI script. Breaking entries are flagged with -, additive entries with +, and the was / is types are JSON-quoted so the operator can paste the line verbatim into the migration notes.
var diff = {
breaking: [{ path: "uuid.v3", kind: "removed", was: "function" }],
additive: [{ path: "uuid.v8", type: "function" }],
typeChanged: [],
};
var rendered = b.apiSnapshot.formatDiff(diff);
// → "[api-snapshot] BREAKING (1):\n - uuid.v3 (removed) was=\"function\"\n..."
Last updated 2026-08-08T16:39:49.652Z by seeder.