@nanobpm/bojtos-kit
v0.11.0
Published
Framework-agnostic core of the Bojtos in-browser BPMN demo framework (ADR 0043): a single scenario runner over the @nanobpm/engine-wasm engine (deploy, start instances, complete/fail jobs, advance the clock, read snapshots and the event log), plus the eng
Readme
@nanobpm/bojtos-kit
Framework-agnostic core of the Bojtos in-browser BPMN demo framework (ADR 0043).
It wraps @nanobpm/engine-wasm as a single scenario runner —
the one runner the whole framework (and the console test-run panel) drives, so
there is no second, drift-prone engine harness — and re-exports the engine's
snapshot/event contract types.
import { createBojtosSession } from "@nanobpm/bojtos-kit";
const session = await createBojtosSession(); // loads the wasm engine once
const { processIds } = session.deploy(bpmnXml);
let snapshot = session.createInstance(processIds[0], "{}");
// a service task is now waiting as a job:
snapshot = session.completeJob(snapshot.jobs[0].key, JSON.stringify({ ok: true }));
const trace = session.events(); // WasmEvent[] for a step/trace view
session.free();Each state-mutating run command returns the post-run Snapshot:
activeElementIds / incidentElementIds drive the token/incident highlight, and
instances[].variables is the live payload that mutates as workers complete. The
deployment/evaluation entry points are the exceptions — deploy returns the
deployable process ids, deployDecision the registered decisions, and the
read-only evaluateDecision a decision output (see below).
For React, use @nanobpm/bojtos-react, which owns the session
lifecycle and reactive state on top of this kit.
DMN decisions — deployDecision / evaluateDecision
Besides BPMN, a session can deploy and evaluate DMN decisions standalone (the
counterpart to a businessRuleTask's in-line evaluation):
const { decisions } = session.deployDecision(dmnXml); // registers every <decision>
const { output } = session.evaluateDecision(
"jedi_or_sith",
JSON.stringify({ lightsaberColor: "blue" }),
); // => "Jedi"deployDecision returns the registered decisions' metadata; evaluateDecision
is read-only (it does not mutate engine state or record a decision instance).
deploy also accepts a DMN resource (the engine routes by content), but is
typed for BPMN — prefer deployDecision so the result is typed. A
businessRuleTask with a zeebe:calledDecision resolves against decisions
registered by either entry point, surfacing its result in
snapshot.decisionInstances.
Engine variants — lean (default) and readmodel
@nanobpm/engine-wasm ships two binaries; a session picks one via variant:
lean(default) — primary state only. Read it throughsnapshot()/events(). Loaded statically, so every consumer bundles it.readmodel— the lean surface plus the gateway's Camunda-parity REST read channel. Loaded via a dynamic import, so a lean-only page never downloads the heavier read-model binary (wasm can't be tree-shaken out of a single build — code-splitting is the only lever).
import {
createBojtosSession,
type UserTaskSearchQueryResult,
} from "@nanobpm/bojtos-kit";
// `variant: "readmodel"` widens the return type to `ReadModelBojtosSession`:
const session = await createBojtosSession({ variant: "readmodel" });
session.deploy(bpmnXml);
session.createInstance("review", "{}");
// Typed against @nanobpm/engine-wasm/readmodel-types (re-exported here):
const open: UserTaskSearchQueryResult = session.searchUserTasks(
JSON.stringify({ state: "CREATED" }),
);
const form = session.getFormByKey("2251799813685250"); // FormResult | nullThe read methods — searchUserTasks, searchProcessInstances,
searchVariables, getFormByKey, getResourceByKey — return DTOs re-exported
from @nanobpm/engine-wasm/readmodel-types, which are derived from the
Camunda-parity REST OpenAPI (one source of truth, not a hand-copy).
readModel() — the @nanobpm/engine-testkit assertion handle
A readmodel-variant session also exposes readModel(), which returns the
session's engine read model as @nanobpm/engine-testkit's structural
EngineReadModel port
(snapshot() + the user-task read channel). Hand it straight to the assertThat*
DSL — it reads the engine's canonical snapshot off this session, so there is no
re-derived copy of the state:
import { assertThatInstance, assertThatUserTask, byProcessId } from "@nanobpm/engine-testkit";
const rm = session.readModel(); // typed EngineReadModel — a compile-time guarantee
assertThatInstance(rm, byProcessId("review")).isActive().hasActiveElement("review-task");
await assertThatUserTask(rm, { elementId: "review-task" }).isCreated();The React binding surfaces the same handle as useBojtos({ variant: "readmodel" }).readModel()
(EngineReadModel | null until the engine is ready).
Trace model
The kit also holds the framework-agnostic trace model the shared
<TraceTimeline> (in @nanobpm/bojtos-react) renders — one normalized
row/turn-group model plus the two adapters that map a source into it, so the two
formerly forked timelines share one fold instead of drifting apart:
foldEngineEvents(events)— the engine-event fold: aWasmEvent[](fromsession.events()/useBojtos().events) → normalizedTraceRow[], keeping the run's milestones and dropping low-signal lifecycle noise. The non-agentic / test-view case.traceEntriesToRows(entries)— the handler-emitted adapter: agent/tool/turnTraceEntrylines (with the additiveturngrouping field) →TraceRow[]. The agentic web-demo case.buildTraceItems(rows)— folds consecutive same-turnrows intoTraceTurnGroups; rows with noturnstay flat.isTraceTurnGroupnarrows an item. This is the grouping the view consumes.
It is pure and React-free (no React import in the kit), keeping the presentational layer thin.
Build
dist/ (the tsc-emitted JS + .d.ts) is what ships, built by prepack on
publish and by npm test locally. It is not committed — .gitignore covers
it — so build before pointing a file: consumer at this workspace. Regenerate
with npm run build.
