-
Notifications
You must be signed in to change notification settings - Fork 0
API
tape-six-invariant exports a small, predicate-only surface. check is the default export and also a named export; everything else is named-only:
import check from 'tape-six-invariant'; // the common case
import {
check,
hasHost,
setAbsentBehavior,
throwOnFail,
warnOnFail,
InvariantError
} from 'tape-six-invariant'; // when you need more than checkThe library is zero-dependency and never imports tape-six. The two coordinate through a single global slot, Symbol.for('tape6.invariant.host.v1'), which tape-six installs at module load. See Home for the overview.
check(cond: unknown, message?: string | (() => string)): asserts cond;Records an invariant.
-
cond— pass/fail verdict; a falsy value fails the invariant. Normalized with!!when reported. -
message— failure message. May be a() => stringthunk so an expensive message is built only when it is resolved.
Behavior:
-
A tape-six run is hosting invariants — the check materializes into a counted assertion on the current test, with source location pointing at the
check()call site. This happens whenever the code is reached, directly or transitively, from a test body. -
No host (production) — on a falsy
cond, the configured absent behavior runs with(cond, resolvedMessage). A truthycondis an early return: the only cost when off is one global-property read.
The TypeScript asserts cond signature narrows the condition for callers.
import check from 'tape-six-invariant';
export function transfer(from, to, amount) {
check(amount > 0, 'amount must be positive');
check(from.currency === to.currency, 'currencies must match');
}To remove check() from release bundles entirely, see Stripping checks in builds.
Pass a () => string thunk to build an expensive message only when it is needed:
check(list.every(valid), () => `invalid items: ${list.filter(x => !valid(x)).join(', ')}`);const hasHost: boolean;Import-time snapshot of whether a tape-six host was installed when this module loaded. Reliable because tape-six sets the slot at module load and test files import tape-six first; in pure production the slot is absent and hasHost is false.
Use it to gate expensive pre-check computation — work not worth doing unless something is listening:
import {check, hasHost} from 'tape-six-invariant';
if (hasHost) check(expensiveToCompute(), 'invariant holds');Correctness never depends on the snapshot — check() reads the live slot per call.
type AbsentBehavior = (ok: unknown, message?: string) => void;
function setAbsentBehavior(fn: AbsentBehavior | null): void;Replaces the absent-path behavior — what a failing check does when no host is present. Pass a canned behavior or your own (ok, message) => void. The default is none — a failing check is a no-op; setAbsentBehavior(null) (or any non-function) clears a set behavior.
The setter is module-scoped: each copy of the library configures itself independently (only the host slot is global). Set it once at startup.
import {setAbsentBehavior, throwOnFail} from 'tape-six-invariant';
setAbsentBehavior(throwOnFail);
// or anything you like — log, sample, increment a metric:
setAbsentBehavior((ok, message) => {
if (!ok) metrics.increment('invariant.failed', {message});
});Exported functions to pass to setAbsentBehavior (not magic strings). With none set — the default — a failing check is a no-op:
| Behavior | Effect on a failing check | Use |
|---|---|---|
throwOnFail |
throw InvariantError(message)
|
fail-fast enforcement |
warnOnFail |
console.warn |
non-fatal telemetry signal |
There is deliberately no canned node:assert behavior: a synchronous check should not dynamically import a module, and keeping the import on the caller's side leaves this package zero-dependency. Deferring to node:assert (or any other assertion library) is a one-liner — bring your own import:
import assert from 'node:assert';
setAbsentBehavior((ok, message) => assert.ok(ok, message));class InvariantError extends Error {
constructor(message?: string);
}Thrown by throwOnFail. name is 'InvariantError'; the default message is 'Invariant failed'. On V8 (Node, Deno, Bun) the constructor frame is dropped from the stack so a thrown error points at the failing check.
For the design of the global slot, the marker-based source location, and cross-realm behavior, see the project's ARCHITECTURE.md. In short:
- tape-six installs
globalThis[Symbol.for('tape6.invariant.host.v1')] = {version, report}at module load (||=, first copy wins). -
reportresolves the live current tester via tape-six'sgetTester()and forwards toTester.reportAssertion(...). - A global (not a module variable) so duplicate library copies in a dependency graph share one coordination point. Each realm (worker, iframe, subprocess) has its own slot.