@bpmnkit/engine
v1.2.1
Published
Lightweight BPMN 2.0 process simulator for tests and demos in browsers and Node.js — zero dependencies
Maintainers
Readme
Website · Documentation · GitHub · Changelog
Core tier. Semver at 1.0: nothing breaks without a major release. See product tiers.
Overview
@bpmnkit/engine simulates BPMN 2.0 process execution — for tests, demos and debugging, not for running production processes. Deploy a diagram, start instances, track active elements, evaluate DMN decisions, and step through execution — all without a Camunda cluster.
Perfect for: workflow testing, visual debugging, interactive demos, offline simulation, and process-driven UI flows.
Features
- Gateways — exclusive (with default flow), parallel, inclusive, event-based; complex splits like inclusive
- Variables — Zeebe-style scopes: input mappings are local to the element, results propagate to the nearest scope that defines them (else the process), output mappings pick what leaves an element
- Events — timer (ISO 8601 duration/date/cycle), message (by name, optional correlation key), signal, error, escalation, link, compensation and terminate
- Boundary events — timer, message, signal and escalation, interrupting or not; error; a job's
throwErroris caught like an error end event - Sub-processes — embedded sub-processes, transactions, and event sub-processes (message, timer, signal, error, escalation start)
- Call activities — run a process deployed in the same engine as a child instance, with Zeebe variable propagation
- Multi-instance — parallel and sequential,
inputCollection/outputCollection,loopCardinality,completionCondition - AI agents — an ad-hoc sub-process with a job worker (the AI Agent Sub-process connector) runs its tools: the worker completes its job with an
adHocSubProcessjob result (activateElements,isCompletionConditionFulfilled), and each tool's result is collected intooutputCollection - DMN decisions — inline decision table evaluation via
@bpmnkit/feel - Job workers — register handlers for service and user tasks by job type
- Step-by-step —
beforeCompletehook pauses between elements for debugging UIs - Zero dependencies — browser + Node.js, no server required
Not executed
- Ad-hoc sub-processes without a job worker, and a call activity whose process is not
deployed in the same engine, complete without running anything (the latter emits an
element:warningevent). - Conditional events are not evaluated, a message start event of a top-level process does not start an instance, and transaction cancel events are not modelled.
- Inclusive and complex joins do not wait for the other branches, and a complex gateway's activation condition is ignored.
- Compensation handlers run one after another in reverse completion order, as BPMN specifies; Zeebe invokes them all at once. Compensation event sub-processes are not modelled.
For Zeebe semantics, @bpmnkit/engine/wasm-runner runs the same scenarios on the Reebe engine
compiled to WebAssembly (experimental). See Conformance.
Installation
npm install @bpmnkit/engineQuick Start
import { Engine } from "@bpmnkit/engine"
const engine = new Engine()
// Deploy a BPMN process
engine.deploy({ bpmn: xml })
// Register job workers
engine.registerJobWorker("payment-service", async (job) => {
const result = await processPayment(job.variables)
return { success: result.ok }
})
// Start an instance
const instance = engine.start("order-process", {
orderId: "ORD-001",
amount: 99.99,
})
// Track execution
instance.onChange((state) => {
console.log("Active:", state.activeElements)
console.log("Vars:", state.variables_snapshot)
})Step-by-step execution
const steps: Array<() => void> = []
const instance = engine.start("my-process", {}, {
beforeComplete: (elementId) =>
new Promise((resolve) => {
console.log("Paused at:", elementId)
steps.push(resolve) // advance by calling steps.pop()()
}),
})Testing processes in Vitest or Jest
@bpmnkit/engine/testing wraps the simulator in a test fixture — no Docker, no cluster:
job and connector mocks, manual job completion, a virtual clock for timers, BPMN matchers
and path coverage.
import "@bpmnkit/engine/testing/vitest" // registers the matchers (Jest: expect.extend(bpmnMatchers))
import { createProcessTest, formatCoverage } from "@bpmnkit/engine/testing"
const t = await createProcessTest({ bpmn: new URL("./order.bpmn", import.meta.url) })
t.mockJob("payment", { result: { paid: true } })
t.mockConnector("io.camunda:http-json:1", { response: { status: 200, body: {} } })
const run = await t.start("order-process", { amount: 10 })
await run.completeJob("ship", { shipped: true }) // unmocked jobs wait for you
await run.publishMessage("payment-confirmed")
await run.advanceTime("PT1H") // fires timers instantly
expect(run).toHaveCompleted()
expect(run).toHavePassed(["payment", "ship"])
expect(run).toHaveVariables({ paid: true })
console.log(formatCoverage(t.coverage())) // flow nodes and sequence flows reached
t.dispose()vitest is an optional peer dependency, needed only for the /testing/vitest entry. See
Testing processes.
AI agents under deterministic tests
mockAiAgent plays the AI Agent connector from a script of turns: which tools the model
calls, with which fromAi() arguments, then its answer. The tools run for real; unknown tools,
wrong arguments and a script that runs out fail the run. Record a transcript to a JSON
cassette once and replay it — no model, no network.
import { readAgentCassette } from "@bpmnkit/engine/testing"
const agent = t.mockAiAgent("support-agent", [
{ toolCalls: [{ name: "lookup-order", arguments: { orderId: "1042" } }] },
{ responseJson: { answer: "It ships tomorrow.", resolved: true } },
])
// or: t.mockAiAgent("support-agent", await readAgentCassette(new URL("./order.cassette.json", import.meta.url)))
const run = await t.start("support", { customerMessage: "Where is order 1042?" })
expect(agent).toHaveCalledTools([{ name: "lookup-order", arguments: { orderId: "1042" } }])
t.coverage().tools // which of the agent's tools the tests calledSee Testing AI agents.
API Reference
Engine
| Method | Description |
|--------|-------------|
| deploy({ bpmn, forms?, decisions? }) | Register BPMN (+ optional DMN/form assets) |
| start(processId, variables?, options?) | Start a new instance; returns ProcessInstance |
| registerJobWorker(type, handler) | Handle service tasks with a given job type |
| broadcastSignal(name, variables?) | Deliver a signal to every running instance; returns instances its signal start events started |
| getDeployedProcesses() | List all deployed process IDs |
ProcessInstance
| Member | Description |
|--------|-------------|
| state | "running" \| "completed" \| "terminated" \| "failed" |
| activeElements | IDs of currently active flow nodes |
| variables_snapshot | Flat snapshot of current variable scope |
| onChange(cb) | Subscribe to state changes |
| cancel() | Terminate the instance |
| deliverMessage(name, variables?, correlationKey?) | Correlate a message to the oldest waiting subscription; returns whether one received it |
| deliverSignal(name, variables?) | Deliver a signal to this instance only |
| beforeComplete? | Optional step hook (set after start()) |
Besides the element and variable events, onChange reports element:terminated when an
interrupting event, a terminate end event or a completion condition cancels an element, and
element:warning when the simulator skips something it cannot run.
Related Packages
| Package | Description |
|---------|-------------|
| @bpmnkit/core | BPMN/DMN/Form parser, builder, layout engine |
| @bpmnkit/canvas | Zero-dependency SVG BPMN viewer |
| @bpmnkit/editor | Full-featured interactive BPMN editor |
| @bpmnkit/feel | FEEL expression language parser & evaluator |
| @bpmnkit/plugins | 34 composable canvas plugins |
| @bpmnkit/api | Camunda 8 REST API TypeScript client |
| @bpmnkit/ascii | Render BPMN diagrams as Unicode ASCII art |
| @bpmnkit/markdown | BPMN diagrams in Markdown — remark, markdown-it and README pre-rendering |
| @bpmnkit/docspack | BPMN Kit docs as an offline docspack package for AI agents |
| @bpmnkit/camunda-docspack | Camunda 8 docs as an offline docspack package for AI agents |
| @bpmnkit/ui | Shared design tokens and UI components |
| @bpmnkit/profiles | Shared auth, profile storage, and client factories for CLI & proxy |
| @bpmnkit/operate | Monitoring & operations frontend for Camunda clusters |
| @bpmnkit/connector-gen | Generate connector templates from OpenAPI specs |
| @bpmnkit/connectors | Camunda 8 OOTB connector catalog and deterministic template application |
| @bpmnkit/cli | Camunda 8 command-line interface (casen) |
| @bpmnkit/proxy | Local AI bridge and Camunda API proxy server |
| @bpmnkit/patterns | Domain process patterns for BPMNKit AIKit |
| @bpmnkit/reebe-wasm | WebAssembly BPMN engine for browser simulation |
| @bpmnkit/worker-client | Thin Zeebe REST client for standalone workers |
| @bpmnkit/flow | Code-first durable flows that derive BPMN, job types and workers |
| @bpmnkit/user-tasks | Embeddable user task widget for Camunda 8 |
| @bpmnkit/cli-sdk | Plugin authoring SDK for the casen CLI |
| @bpmnkit/create-casen-plugin | Scaffold a new casen CLI plugin in seconds |
| @bpmnkit/casen-report | HTML reports from Camunda 8 incident and SLA data |
| @bpmnkit/casen-worker-http | Example HTTP worker plugin — completes jobs with live JSONPlaceholder API data |
| @bpmnkit/casen-worker-ai | AI task worker — classify, summarize, extract, and decide using Claude |
