@kurrent/projections-testing
v0.1.1
Published
Test library for KurrentDB projections
Downloads
256
Keywords
Readme
@kurrent/projections-testing
Test KurrentDB projections locally with any test runner (vitest, jest, mocha).
Wraps the gaffer runtime to execute projections against test events with the same behaviour as a real KurrentDB instance.
Install
npm install --save-dev @kurrent/projections-testingRequires Node.js 22 or later. @kurrent/kurrentdb-client is a peer dependency.
Quick start
Run a projection over an array of events:
import { createProjection } from "@kurrent/projections-testing";
import { readFile } from "fs/promises";
const source = await readFile("./projections/cart.js", "utf8");
const projection = createProjection<{ count: number }>(source);
for (const { state } of projection.run([
{
eventType: "ItemAdded",
streamId: "cart-1",
sequenceNumber: 0,
isJson: true,
data: { id: 1 },
},
{
eventType: "ItemAdded",
streamId: "cart-1",
sequenceNumber: 1,
isJson: true,
data: { id: 2 },
},
])) {
console.log(state); // { count: 1 }, { count: 2 }
}API
createProjection<TState>(source, options?)
Create a projection from JavaScript source. Does not compile until validate, run, or test is called.
Options:
version-"v1"or"v2"(default"v2")config- per-projection settingsexecutionTimeoutMs- max handler execution time per event in ms (default 5000)
databaseConfig- database-wide settingscompilationTimeoutMs- max compilation time in ms (default 5000)executionTimeoutMs- default max handler execution time in ms (default 5000)
projection.validate()
Compile the projection and return its source definition. Throws if the source is invalid.
const info = projection.validate();
console.log(info.source); // { type: "all" }
console.log(info.events); // ["ItemAdded"] or "all"projection.run(events)
Run the projection over events, yielding a StepResult after each one. Accepts:
Iterable<EventInput>- arrays, generatorsAsyncIterable<EventInput>- async generators, client streamsKurrentDBClient- subscribes to the appropriate streams based on the projection's source definition
// Sync
for (const { state, emitted, logs } of projection.run(events)) { ... }
// Async
for await (const { state } of projection.run(asyncEvents)) { ... }
// KurrentDB client
for await (const { state } of projection.run(client)) { ... }projection.test()
Create an interactive test session for feeding events one at a time.
const test = projection.test();
const step = test.feed({
eventType: "ItemAdded",
streamId: "cart-1",
sequenceNumber: 0,
isJson: true,
data: { id: 1 },
});
expect(step.state).toEqual({ count: 1 });
expect(step.emitted).toHaveLength(0);
expect(step.logs).toEqual([]);
test.dispose(); // or use `using test = projection.test()`Querying state
For partitioned projections, query state by partition:
test.feed({
eventType: "ItemAdded",
streamId: "cart-1",
sequenceNumber: 0,
isJson: true,
data: {},
});
test.feed({
eventType: "ItemAdded",
streamId: "cart-2",
sequenceNumber: 1,
isJson: true,
data: {},
});
test.getState("cart-1"); // state for cart-1
test.getState("cart-2"); // state for cart-2
test.getSharedState(); // shared state (biState projections)
test.getResult("cart-1"); // result for cart-1 (V1: post-transform; V2: post-handler state)systemEvents
Helpers for constructing KurrentDB system events:
import { systemEvents } from "@kurrent/projections-testing";
test.feed(systemEvents.streamDeleted("cart-123", 5));Event input
Three event shapes are accepted:
// Manual test events (isJson is required)
{ eventType: 'OrderPlaced', streamId: 'order-1', sequenceNumber: 0, isJson: true, data: { amount: 99 } }
// KurrentDB RecordedEvent (from client)
{ type: 'OrderPlaced', streamId: 'order-1', revision: 0n, isJson: true, id: '...', created: new Date(), ... }
// KurrentDB ResolvedEvent (from subscriptions)
{ event: { type: 'OrderPlaced', streamId: 'order-1', revision: 0n, isJson: true, ... } }data and metadata accept objects (auto-stringified to JSON) or strings (passed through).
Errors
Errors from the runtime propagate as typed ProjectionError subclasses with structured fields and formatted messages:
import {
ProjectionHandlerError,
InvalidProjectionError,
ProjectionError,
} from "@kurrent/projections-testing";
try {
test.feed(event);
} catch (err) {
if (err instanceof ProjectionHandlerError) {
err.description; // "boom"
err.event.eventType; // "OrderPlaced"
err.event.streamId; // "order-1"
err.event.sequenceNumber; // 42
err.message; // formatted with source snippet and caret
}
// or catch all projection errors
if (err instanceof ProjectionError) {
err.code; // "handler-error", "malformed-event", etc.
err.description; // human-readable description
}
}Related packages
| Package | What it is |
| ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| @kurrent/gaffer | CLI to scaffold, run, debug, and deploy projections |
| KurrentDB Projections for VS Code | Editor integration with debugger, codelens, and MCP server |
Documentation
Full documentation at https://docs.kurrent.io/gaffer/testing/.
Bugs go to GitHub Issues. Questions and feature requests to Discussions.
License
Apache License 2.0. Depends on @kurrent/gaffer-runtime, which is distributed under the Kurrent License v1.
