Simulation
The @bpmn-sdk/engine package is a zero-dependency BPMN simulation engine that runs in
browsers and Node.js. It’s useful for:
- Testing process logic before deploying
- Prototyping without a Camunda cluster
- CI pipelines — unit-test your process definitions
- Demos — run live in a browser
Basic Usage
import { Engine } from "@bpmn-sdk/engine";import { Bpmn } from "@bpmn-sdk/core";
// Build a processconst xml = Bpmn.export( Bpmn.createProcess("order") .startEvent("start") .serviceTask("charge", { taskType: "payment" }) .serviceTask("ship", { taskType: "shipping" }) .endEvent("end") .build());
// Create engine and deployconst engine = new Engine();await engine.deploy({ bpmn: xml });
// Register workersengine.registerJobWorker("payment", async (job) => { console.log("Charging card...", job.variables); await job.complete({ transactionId: "txn-123" });});
engine.registerJobWorker("shipping", async (job) => { console.log("Shipping order...", job.variables); await job.complete({ trackingNumber: "1Z999AA1" });});
// Start an instanceconst instance = engine.start("order", { orderId: "ord-456", amount: 99.99,});Instance State
The ProcessInstance object tracks the running state:
const instance = engine.start("order");
console.log(instance.state); // "running" | "completed" | "cancelled"console.log(instance.activeElements); // Set<string> — currently active element IDs
// Subscribe to state changesinstance.onChange((newState) => { console.log("State changed to:", newState);});Variables
Variables flow through the process via IO mappings. You can read the current snapshot:
const vars = instance.variables_snapshot;console.log(vars.orderId); // input variableconsole.log(vars.transactionId); // output from a taskMessage Correlation
Deliver a message to a waiting intermediateCatchEvent:
// Process has a catch event waiting for "payment-confirmed"await instance.deliverMessage("payment-confirmed", { paymentMethod: "card", confirmedAt: new Date().toISOString(),});Step-by-Step Execution
Use the beforeComplete hook to pause execution between elements — useful for debugging
and building animated process runners:
const instance = engine.start("order", {}, { beforeComplete: async (elementId) => { console.log("About to complete:", elementId); // Inspect state, update UI, etc. // Resume by returning from this function },});DMN Decision Evaluation
Deploy decision tables alongside the process:
await engine.deploy({ bpmn: xml, decisions: [dmnXml], // DMN 1.3 XML strings});Testing with Vitest
Write deterministic unit tests for your process logic:
import { describe, it, expect } from "vitest";import { Engine } from "@bpmn-sdk/engine";import { buildOrderProcess } from "./processes.js";
describe("order process", () => { it("completes when payment succeeds", async () => { const engine = new Engine(); await engine.deploy({ bpmn: buildOrderProcess() });
let completed = false;
engine.registerJobWorker("payment", async (job) => { await job.complete({ success: true }); });
engine.registerJobWorker("shipping", async (job) => { await job.complete({ trackingNumber: "TRK-001" }); });
const instance = engine.start("order", { amount: 50 }); await new Promise<void>((resolve) => { instance.onChange((state) => { if (state === "completed") { completed = true; resolve(); } }); });
expect(completed).toBe(true); expect(instance.variables_snapshot.trackingNumber).toBe("TRK-001"); });});Multiple Deployments
The engine supports multiple deployed processes. Use engine.getDeployedProcesses() to list them:
await engine.deploy({ bpmn: processAXml });await engine.deploy({ bpmn: processBXml });
const processes = engine.getDeployedProcesses();// [{ id: "process-a", name: "..." }, { id: "process-b", name: "..." }]