Everything exported from @masterplaycoding/eventlab. Generated TypeDoc output
covers the same surface in more detail; this page is the map.
| Option | Required | Default | Meaning |
|---|---|---|---|
events |
yes | — | The fixtures. At least one, with unique ids not containing #. |
seed |
yes | — | Any integer. The same seed and inputs always expand identically. |
scenario |
no | "scenario" |
Names the run in reports. |
transforms |
no | [] |
Applied to all events, in declaration order. |
phases |
no | — | Ordered delivery groups and barriers. Mutually exclusive with transforms. |
concurrency |
no | 4 |
Maximum attempts in flight, within a phase. |
Only events and seed are required, despite every example passing more.
A barrier splits a plan. Everything before it completes before anything after it is released, and a named checkpoint runs in between:
const plan = createPlan({
events,
seed: 4242,
phases: [
{ deliver: ["evt_payment"], transforms: [duplicate({ copies: 2 })] },
{ barrier: "settlement drained", checkpoint: "settlementDrained" },
{ deliver: ["evt_refund"] },
],
});
await runPlan(plan, {
events,
target,
hooks: {
checkpoints: {
// Your application says when it is quiescent. Not a sleep.
settlementDrained: () => app.waitForQueueIdle(),
},
},
});Rules, all enforced when the plan is built:
- A barrier must have deliveries before it. Two in a row, or one at the start, is rejected — each is a scenario that cannot mean what it says.
- Barrier names are unique, because reports identify them by name.
- A phase may not list the same event twice; use
duplicate(), which records the intent in the plan. - Checkpoint names are validated against
hooks.checkpointsbefore setup, so a typo fails before anything is delivered rather than thirty seconds in.
Delay offsets are phase-relative: a barrier resets the clock, since what follows one did not start until what preceded it finished. Observed start times in the report stay run-relative, so it still reads as a single timeline.
A failing checkpoint stops the run — the application has said it never reached
the state the remaining phases assume. Barriers a run never reached are
reported as skipped, so a truncated run is visibly truncated.
See examples/barrier-settlement for a
refund that must not be applied before its payment has settled.
| Transform | Effect |
|---|---|
duplicate({ copies }) |
Deliver each event copies times in total |
shuffle() |
Reorder attempts, using the seed |
delay({ minMs, maxMs }) |
Draw a start offset per attempt |
burst() |
Reset every offset to zero |
Writing your own is a supported extension point — see extending.md.
serializePlan(plan): string— instructions only, never payloads.parsePlan(source): DeliveryPlan— validates, and refuses a plan built by a different planner version rather than regenerating it.assertFixturesMatch(plan, events): void— throws if the fixtures changed since the plan was saved.runPlancalls this for you; it is exported so you can check before doing expensive setup.
runPlan never throws. Every failure — an invalid scenario, a blocked
target, a failing setup — comes back as a report with harnessError set. If
a result surprises you, look there first.
| Option | Required | Default | Meaning |
|---|---|---|---|
target |
yes | — | baseUrl, a request builder, optional timeoutMs. |
events |
yes | — | The same fixtures the plan was built from. |
hooks |
no | — | setup, reset, teardown. |
assertions |
no | [] |
Your checks. |
expect |
no | { deliveries: "all-2xx" } |
See below. |
limits |
no | see below | Timeouts and body cap. |
allowRemoteTargets |
no | false |
Required for non-loopback hosts. |
signal |
no | — | Cancels the run; teardown still runs. |
| Limit | Default | Notes |
|---|---|---|
requestTimeoutMs |
5,000 | Per attempt. target.timeoutMs overrides. |
scenarioTimeoutMs |
30,000 | Whole run. A slow target under burst() will hit this, and it surfaces as a ScenarioTimeout harness error rather than an assertion failure. |
maxResponseBodyBytes |
65,536 | Captured preview; truncation is reported. |
With no expect, a run passes only if every delivery returned 2xx. Scenarios
that intend to provoke a rejection must say so:
expect: { deliveries: "declared" }Otherwise a run in which the server refused everything would pass, having asserted nothing.
{ name: string, check(context): void | Promise<void> }
{ name: string, eventually(context): void | Promise<void>, timeoutMs?: 2000, intervalMs?: 50 }A check that throws is failed. An eventually that never succeeded is
timed-out and carries the last failure. The two are different situations and
stay distinct in the report.
formatReport(report, options?): string— the human-readable rendering. Pure; returns a string.{ timings: true }adds wall-clock;digestCharscontrols the digest prefix length.assertRunPassed(report): void— throws withformatReport's output when the run did not pass. Prefer this overexpect(report.passed).toBe(true).deliveriesFor(report, eventId): AttemptReport[]countDeliveries(report, eventId): numbersummariseStatuses(report): Record<string, number>— counts by status, with transport failures in their own buckets rather than folded into a code they never had.
Four outcome categories, deliberately never blended:
| Field | Means |
|---|---|
attempts[].outcome |
What the transport did |
assertions[] |
What your check said about your application |
harnessError |
The experiment itself was invalid |
cleanup |
Whether teardown completed |
passed is true only when the delivery expectation held, every assertion
passed, and teardown did not fail.
HarnessError carries a code and an optional at pointing at the offending
input. isHarnessError(value) is a realm-safe type guard.
| Code | Cause |
|---|---|
InvalidScenario |
Bad createPlan input — duplicate ids, # in an id, non-integer seed, concurrency below 1 |
InvalidPlan |
A transform produced something invalid, or a saved plan is malformed or from another planner version |
InvalidTarget |
baseUrl is not an absolute http(s) URL, or a path could not be resolved |
RemoteTargetBlocked |
Non-loopback host without allowRemoteTargets: true |
MissingFixture |
The plan references an event the supplied fixtures do not define |
FixtureDigestMismatch |
The fixtures changed since the plan was saved |
SetupFailed |
hooks.setup or hooks.reset threw |
ScenarioTimeout |
The run exceeded scenarioTimeoutMs |
Cancelled |
The caller's signal aborted |
Troubleshooting maps these to fixes.
PLANNER_VERSION— a saved plan records this; replay refuses a mismatch.REPORT_SCHEMA_VERSION— branch on this when parsing reports, not on the package version.