@nanobpm/bojtos-react 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -51,6 +51,7 @@ consumer must import bpmn-js's diagram CSS once and provide the `.nano-active` /
51
51
  ## Build
52
52
 
53
53
  `dist/` (the tsc-emitted JS + `.d.ts`, with JSX already compiled to
54
- `react/jsx-runtime` so consumers never re-transform node_modules) is committed
55
- so `file:` consumers and CI need no build-on-install step. Regenerate with
54
+ `react/jsx-runtime` so consumers never re-transform node_modules) is what ships,
55
+ built by `prepack` on publish. It is **not** committed `.gitignore` covers it —
56
+ so build before pointing a `file:` consumer at this workspace. Regenerate with
56
57
  `npm run build`.
@@ -7,6 +7,17 @@ export interface BpmnRuntimeViewProps {
7
7
  incidentIds: string[];
8
8
  /** Optional class for the container element (it always fills its parent). */
9
9
  className?: string;
10
+ /**
11
+ * Accessible name for the diagram. The token and incident highlights are
12
+ * purely visual, so without this a screen-reader user is told nothing at all
13
+ * about what is running.
14
+ */
15
+ label?: string;
16
+ /**
17
+ * Map an element id to a human name for the live status announcement — pass
18
+ * the diagram's element names if you have them. Defaults to the raw id.
19
+ */
20
+ elementName?: (elementId: string) => string;
10
21
  }
11
22
  /**
12
23
  * Read-only diagram that imports the XML once and updates token/incident markers
@@ -20,4 +31,4 @@ export interface BpmnRuntimeViewProps {
20
31
  * and provide the `.nano-active` / `.nano-incident` marker styles plus a
21
32
  * `.nano-token` style for the token badge overlaid on each active element.
22
33
  */
23
- export declare function BpmnRuntimeView({ xml, activeIds, incidentIds, className, }: BpmnRuntimeViewProps): import("react").JSX.Element;
34
+ export declare function BpmnRuntimeView({ xml, activeIds, incidentIds, className, label, elementName, }: BpmnRuntimeViewProps): import("react").JSX.Element;
@@ -1,6 +1,7 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useRef } from "react";
3
3
  import NavigatedViewer from "bpmn-js/lib/NavigatedViewer";
4
+ import { describeRunState, markerKey } from "./runState.js";
4
5
  /**
5
6
  * Read-only diagram that imports the XML once and updates token/incident markers
6
7
  * in place (no re-import, so the zoom/scroll position is preserved while
@@ -13,7 +14,7 @@ import NavigatedViewer from "bpmn-js/lib/NavigatedViewer";
13
14
  * and provide the `.nano-active` / `.nano-incident` marker styles plus a
14
15
  * `.nano-token` style for the token badge overlaid on each active element.
15
16
  */
16
- export function BpmnRuntimeView({ xml, activeIds, incidentIds, className, }) {
17
+ export function BpmnRuntimeView({ xml, activeIds, incidentIds, className, label = "BPMN process diagram", elementName, }) {
17
18
  const containerRef = useRef(null);
18
19
  const viewerRef = useRef(null);
19
20
  const importedRef = useRef(false);
@@ -101,9 +102,24 @@ export function BpmnRuntimeView({ xml, activeIds, incidentIds, className, }) {
101
102
  }
102
103
  tokenOverlaysRef.current = nextOverlays;
103
104
  }
105
+ // `activeIds` / `incidentIds` are almost always fresh arrays (`snapshot?.x ??
106
+ // []`), so depending on their identity re-painted every marker and re-created
107
+ // every token overlay on every render of the parent — visible churn on a busy
108
+ // diagram. Depend on the ids themselves instead.
109
+ const key = markerKey(activeIds, incidentIds);
104
110
  useEffect(() => {
105
111
  applyMarkers();
106
112
  // eslint-disable-next-line react-hooks/exhaustive-deps
107
- }, [activeIds, incidentIds]);
108
- return (_jsx("div", { ref: containerRef, className: className, style: { width: "100%", height: "100%" } }));
113
+ }, [key]);
114
+ return (_jsxs("div", { className: className, style: { width: "100%", height: "100%", position: "relative" }, children: [_jsx("div", { ref: containerRef, role: "img", "aria-label": label, style: { width: "100%", height: "100%" } }), _jsx("div", { role: "status", "aria-live": "polite", style: {
115
+ position: "absolute",
116
+ width: 1,
117
+ height: 1,
118
+ margin: -1,
119
+ padding: 0,
120
+ overflow: "hidden",
121
+ clip: "rect(0 0 0 0)",
122
+ whiteSpace: "nowrap",
123
+ border: 0,
124
+ }, children: describeRunState(activeIds, incidentIds, elementName) })] }));
109
125
  }
package/dist/index.d.ts CHANGED
@@ -2,5 +2,6 @@ export { useBojtos, type UseBojtosOptions, type BojtosControls, type BojtosPhase
2
2
  export { Bojtos, type BojtosProps, type TraceEvent } from "./Bojtos.js";
3
3
  export { OrderFulfillmentDemo, ORDER_FULFILLMENT_BPMN, orderFulfillmentWorkers, } from "./examples/orderFulfillment.js";
4
4
  export { BpmnRuntimeView, type BpmnRuntimeViewProps, } from "./BpmnRuntimeView.js";
5
- export { JobFailure, type JobHandler, type JobResult, type AgentHandler, type DispatchOptions, type DispatchResult, type RoundResult, } from "@nanobpm/bojtos-kit";
6
- export type { BojtosSession, Snapshot, InstanceDto, JobDto, ActivatedJob, IncidentDto, TimerDto, ActiveEl, AgentActivation, AgentResult, WasmEvent, } from "@nanobpm/bojtos-kit";
5
+ export { describeRunState, markerKey, bpmnKey, resourceList, capEvents, } from "./runState.js";
6
+ export { JobFailure, settleReason, unhandledJobTypes, type JobHandler, type JobResult, type AgentHandler, type DispatchOptions, type DispatchResult, type RoundResult, type SettleReason, } from "@nanobpm/bojtos-kit";
7
+ export type { BojtosSession, Snapshot, InstanceDto, JobDto, ActivatedJob, IncidentDto, TimerDto, UserTaskDto, MessageSubscriptionDto, SignalSubscriptionDto, ElementStatDto, SequenceFlowDto, DecisionInstanceDto, ActiveEl, ActivateInstruction, AgentActivation, AgentResult, WasmEvent, } from "@nanobpm/bojtos-kit";
package/dist/index.js CHANGED
@@ -7,4 +7,5 @@ export { useBojtos, } from "./useBojtos.js";
7
7
  export { Bojtos } from "./Bojtos.js";
8
8
  export { OrderFulfillmentDemo, ORDER_FULFILLMENT_BPMN, orderFulfillmentWorkers, } from "./examples/orderFulfillment.js";
9
9
  export { BpmnRuntimeView, } from "./BpmnRuntimeView.js";
10
- export { JobFailure, } from "@nanobpm/bojtos-kit";
10
+ export { describeRunState, markerKey, bpmnKey, resourceList, capEvents, } from "./runState.js";
11
+ export { JobFailure, settleReason, unhandledJobTypes, } from "@nanobpm/bojtos-kit";
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Pure helpers behind the React binding's decisions.
3
+ *
4
+ * Kept out of the component and the hook — and out of any module that imports
5
+ * bpmn-js — so they can be tested without a DOM or the peer dependency. The
6
+ * component and hook hold rendering and lifecycle; what counts as a change, how
7
+ * a run reads aloud, which resources to deploy and how far to trim the log are
8
+ * decisions, and decisions are worth testing.
9
+ */
10
+ /**
11
+ * Stable key for a marker set, so unchanged ids don't re-paint the diagram.
12
+ *
13
+ * Relies on BPMN element ids being XML NCNames (so they can't contain `,` or
14
+ * `|`) — that invariant is what keeps the two-field join collision-free. If ids
15
+ * could contain the delimiters, `["a,b"],[]` and `["a"],["b"]`-style pairs
16
+ * would key alike.
17
+ */
18
+ export declare function markerKey(activeIds: string[], incidentIds: string[]): string;
19
+ /**
20
+ * Content key for the `bpmn` prop, so a fresh array identity each render doesn't
21
+ * re-create the engine but a real content change still does. Only the array
22
+ * case is serialized (with a boundary-preserving `JSON.stringify`, not a
23
+ * `join` — a delimiter join lets two different resource arrays collapse to one
24
+ * key when a resource borders/contains the delimiter, silently missing a real
25
+ * change). A lone string can't have array-boundary collisions and React deps
26
+ * already compare strings by value, so it passes through untouched — no needless
27
+ * re-walk of potentially large BPMN XML each render.
28
+ */
29
+ export declare function bpmnKey(bpmn: string | string[]): string;
30
+ /**
31
+ * Normalize the `bpmn` prop to an ordered resource list for deployment. Deploy
32
+ * order is significant: a later resource can reference an earlier one (a call
33
+ * activity's child), so the array order is preserved verbatim.
34
+ */
35
+ export declare function resourceList(bpmn: string | string[]): string[];
36
+ /**
37
+ * Trim an event log to the consumer's cap, keeping the most recent events.
38
+ * `undefined` or a negative cap means "no cap"; `0` means "keep nothing". The
39
+ * input array is never mutated.
40
+ */
41
+ export declare function capEvents<T>(all: T[], cap: number | undefined): T[];
42
+ /**
43
+ * A one-line description of run state, for the diagram's live region. The token
44
+ * and incident highlights are purely visual; this is the same information as
45
+ * text.
46
+ */
47
+ export declare function describeRunState(activeIds: string[], incidentIds: string[], name?: (id: string) => string): string;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Pure helpers behind the React binding's decisions.
3
+ *
4
+ * Kept out of the component and the hook — and out of any module that imports
5
+ * bpmn-js — so they can be tested without a DOM or the peer dependency. The
6
+ * component and hook hold rendering and lifecycle; what counts as a change, how
7
+ * a run reads aloud, which resources to deploy and how far to trim the log are
8
+ * decisions, and decisions are worth testing.
9
+ */
10
+ /**
11
+ * Stable key for a marker set, so unchanged ids don't re-paint the diagram.
12
+ *
13
+ * Relies on BPMN element ids being XML NCNames (so they can't contain `,` or
14
+ * `|`) — that invariant is what keeps the two-field join collision-free. If ids
15
+ * could contain the delimiters, `["a,b"],[]` and `["a"],["b"]`-style pairs
16
+ * would key alike.
17
+ */
18
+ export function markerKey(activeIds, incidentIds) {
19
+ return `${activeIds.join(",")}|${incidentIds.join(",")}`;
20
+ }
21
+ /**
22
+ * Content key for the `bpmn` prop, so a fresh array identity each render doesn't
23
+ * re-create the engine but a real content change still does. Only the array
24
+ * case is serialized (with a boundary-preserving `JSON.stringify`, not a
25
+ * `join` — a delimiter join lets two different resource arrays collapse to one
26
+ * key when a resource borders/contains the delimiter, silently missing a real
27
+ * change). A lone string can't have array-boundary collisions and React deps
28
+ * already compare strings by value, so it passes through untouched — no needless
29
+ * re-walk of potentially large BPMN XML each render.
30
+ */
31
+ export function bpmnKey(bpmn) {
32
+ return Array.isArray(bpmn) ? JSON.stringify(bpmn) : bpmn;
33
+ }
34
+ /**
35
+ * Normalize the `bpmn` prop to an ordered resource list for deployment. Deploy
36
+ * order is significant: a later resource can reference an earlier one (a call
37
+ * activity's child), so the array order is preserved verbatim.
38
+ */
39
+ export function resourceList(bpmn) {
40
+ return Array.isArray(bpmn) ? bpmn : [bpmn];
41
+ }
42
+ /**
43
+ * Trim an event log to the consumer's cap, keeping the most recent events.
44
+ * `undefined` or a negative cap means "no cap"; `0` means "keep nothing". The
45
+ * input array is never mutated.
46
+ */
47
+ export function capEvents(all, cap) {
48
+ return cap !== undefined && cap >= 0 && all.length > cap
49
+ ? all.slice(all.length - cap)
50
+ : all;
51
+ }
52
+ /**
53
+ * A one-line description of run state, for the diagram's live region. The token
54
+ * and incident highlights are purely visual; this is the same information as
55
+ * text.
56
+ */
57
+ export function describeRunState(activeIds, incidentIds, name = (id) => id) {
58
+ const parts = [];
59
+ if (activeIds.length)
60
+ parts.push(`Running: ${activeIds.map(name).join(", ")}`);
61
+ if (incidentIds.length)
62
+ parts.push(`Incident: ${incidentIds.map(name).join(", ")}`);
63
+ return parts.length ? parts.join(". ") : "Nothing running";
64
+ }
@@ -1,9 +1,15 @@
1
- import { type AgentResult, type DispatchOptions, type JobHandler, type RoundResult, type Snapshot, type WasmEvent, type WasmSource } from "@nanobpm/bojtos-kit";
1
+ import { type ActivateInstruction, type AgentResult, type DispatchOptions, type JobHandler, type RoundResult, type Snapshot, type WasmEvent, type WasmSource } from "@nanobpm/bojtos-kit";
2
2
  /** Lifecycle of the in-browser engine load. */
3
3
  export type BojtosPhase = "loading" | "ready" | "error";
4
4
  export interface UseBojtosOptions {
5
- /** The BPMN diagram XML to deploy. Re-deploys on a fresh engine when it changes. */
6
- bpmn: string;
5
+ /**
6
+ * The BPMN to deploy. Re-deploys on a fresh engine when it changes.
7
+ *
8
+ * Pass an array to deploy several resources into one engine — a called
9
+ * process alongside its parent, say. `processIds` then lists every deployable
10
+ * process across all of them, in deployment order.
11
+ */
12
+ bpmn: string | string[];
7
13
  /**
8
14
  * Optional engine wasm source. Pass a `URL` / bytes / `WebAssembly.Module`
9
15
  * when the default `import.meta.url` loader can't resolve the binary (the
@@ -14,6 +20,15 @@ export interface UseBojtosOptions {
14
20
  * reload the module.
15
21
  */
16
22
  wasm?: WasmSource;
23
+ /**
24
+ * Cap the reactive `events` log at the most recent N entries.
25
+ *
26
+ * Every command re-reads the engine's full event log into React state, so a
27
+ * long-running demo copies an ever-growing array on each step. Set this when
28
+ * a page runs for a while and only shows a tail; leave it unset to keep the
29
+ * whole log, which stays the default so existing consumers are unaffected.
30
+ */
31
+ maxEvents?: number;
17
32
  }
18
33
  export interface BojtosControls {
19
34
  phase: BojtosPhase;
@@ -46,6 +61,58 @@ export interface BojtosControls {
46
61
  correlateMessage(messageName: string, correlationKey: string, variablesJson: string): Snapshot | null;
47
62
  /** Advance the virtual clock. */
48
63
  advanceTime(byMs: number): Snapshot | null;
64
+ /**
65
+ * Throw a BPMN business error from a waiting job: interrupts the activity via
66
+ * a matching error boundary/event-subprocess catch, or raises an incident if
67
+ * uncaught. The job is consumed either way.
68
+ */
69
+ throwError(jobKey: string, errorCode: string, errorMessage: string): Snapshot | null;
70
+ /**
71
+ * Set a job's remaining retries. Used to recover a job parked on a no-retries
72
+ * incident before resolving that incident; does not itself unblock the job.
73
+ */
74
+ updateRetries(jobKey: string, retries: number): Snapshot | null;
75
+ /**
76
+ * Resolve an open incident by key, retrying the work that failed. Pair with
77
+ * {@link updateRetries} to make a failed job activatable again — the
78
+ * incident/retry loop a demo needs to show recovery.
79
+ */
80
+ resolveIncident(incidentKey: string): Snapshot | null;
81
+ /**
82
+ * Merge variables into a scope (a process-instance or element-instance key).
83
+ * With `local`, they are written strictly into that scope; otherwise they
84
+ * propagate up to the nearest ancestor defining each name.
85
+ */
86
+ setVariables(scopeKey: string, variablesJson: string, local: boolean): Snapshot | null;
87
+ /** Broadcast a signal by name to every matching open subscription. */
88
+ broadcastSignal(signalName: string, variablesJson: string): Snapshot | null;
89
+ /** Cancel (terminate) a running process instance. */
90
+ cancelInstance(instanceKey: string): Snapshot | null;
91
+ /**
92
+ * Modify a running instance: terminate element instances and/or activate new
93
+ * ones (Zeebe "modify process instance").
94
+ */
95
+ modify(instanceKey: string, activateInstructions: ActivateInstruction[], terminateElementInstanceKeys: string[]): Snapshot | null;
96
+ /**
97
+ * Complete a waiting user task, merging output variables.
98
+ *
99
+ * A `userTask` produces no job, so the dispatch loop cannot advance one: this
100
+ * is the only way a model with a human step reaches its end event. Drive it
101
+ * from `snapshot.userTasks`.
102
+ */
103
+ completeUserTask(userTaskKey: string, variablesJson: string): Snapshot | null;
104
+ /**
105
+ * Assign a user task. With `allowOverride` false the command is rejected if
106
+ * the task already has an assignee.
107
+ */
108
+ assignUserTask(userTaskKey: string, assignee: string, allowOverride: boolean): Snapshot | null;
109
+ /** Clear a user task's assignee. */
110
+ unassignUserTask(userTaskKey: string): Snapshot | null;
111
+ /**
112
+ * Update a user task's attributes from a JSON changeset (`candidateGroups`,
113
+ * `candidateUsers`, `dueDate`, `followUpDate`, `priority`).
114
+ */
115
+ updateUserTask(userTaskKey: string, changesetJson: string): Snapshot | null;
49
116
  /**
50
117
  * Run the registered worker handlers until the process settles (activate →
51
118
  * handler → complete/fail), then reflect the resulting snapshot/events.
@@ -76,4 +143,4 @@ export interface BojtosControls {
76
143
  * test-run panel is its first consumer (§8 step 2 — dogfooding is the acceptance
77
144
  * test).
78
145
  */
79
- export declare function useBojtos({ bpmn, wasm }: UseBojtosOptions): BojtosControls;
146
+ export declare function useBojtos({ bpmn, wasm, maxEvents, }: UseBojtosOptions): BojtosControls;
package/dist/useBojtos.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
2
  import { createBojtosSession, dispatchRound, dispatchWorkers, } from "@nanobpm/bojtos-kit";
3
+ import { bpmnKey, capEvents, resourceList } from "./runState.js";
3
4
  /**
4
5
  * React binding over a headless {@link BojtosSession}: owns the engine's
5
6
  * lifecycle and the reactive `snapshot` / `events` / `processIds` state, and
@@ -11,7 +12,7 @@ import { createBojtosSession, dispatchRound, dispatchWorkers, } from "@nanobpm/b
11
12
  * test-run panel is its first consumer (§8 step 2 — dogfooding is the acceptance
12
13
  * test).
13
14
  */
14
- export function useBojtos({ bpmn, wasm }) {
15
+ export function useBojtos({ bpmn, wasm, maxEvents, }) {
15
16
  const sessionRef = useRef(null);
16
17
  const [phase, setPhase] = useState("loading");
17
18
  const [error, setError] = useState(null);
@@ -23,13 +24,32 @@ export function useBojtos({ bpmn, wasm }) {
23
24
  // identity each render must not re-create the session.
24
25
  const wasmRef = useRef(wasm);
25
26
  wasmRef.current = wasm;
27
+ // An array prop has a fresh identity every render, which would re-create the
28
+ // engine on each one. Key the deploy effect on the content instead — see
29
+ // `bpmnKey` for why this is a boundary-preserving serialization, not a join.
30
+ const deployKey = bpmnKey(bpmn);
31
+ const bpmnRef = useRef(bpmn);
32
+ bpmnRef.current = bpmn;
33
+ // Trim the reactive event log when the consumer asked for a cap.
34
+ const maxEventsRef = useRef(maxEvents);
35
+ maxEventsRef.current = maxEvents;
36
+ const readEvents = useCallback((session) => {
37
+ return capEvents(session.events(), maxEventsRef.current);
38
+ }, []);
26
39
  const deployInto = useCallback((session) => {
27
- const res = session.deploy(bpmn);
28
- setProcessIds(res.processIds);
40
+ const resources = resourceList(bpmnRef.current);
41
+ // Deploy in order, collecting every deployable process id. A later
42
+ // resource can reference an earlier one (a call activity's child).
43
+ const ids = [];
44
+ for (const xml of resources)
45
+ ids.push(...session.deploy(xml).processIds);
46
+ setProcessIds(ids);
29
47
  setSnapshot(null);
30
48
  setEvents([]);
31
49
  setError(null);
32
- }, [bpmn]);
50
+ },
51
+ // eslint-disable-next-line react-hooks/exhaustive-deps
52
+ [deployKey]);
33
53
  useEffect(() => {
34
54
  let cancelled = false;
35
55
  // A new diagram means a fresh engine: drop back to `loading` and clear the
@@ -81,7 +101,7 @@ export function useBojtos({ bpmn, wasm }) {
81
101
  try {
82
102
  const snap = fn(session);
83
103
  setSnapshot(snap);
84
- setEvents(session.events());
104
+ setEvents(readEvents(session));
85
105
  setError(null);
86
106
  return snap;
87
107
  }
@@ -96,6 +116,17 @@ export function useBojtos({ bpmn, wasm }) {
96
116
  const failJob = useCallback((jobKey, retries, message) => run((s) => s.failJob(jobKey, retries, message)), [run]);
97
117
  const advanceTime = useCallback((byMs) => run((s) => s.advanceTime(byMs)), [run]);
98
118
  const correlateMessage = useCallback((messageName, correlationKey, variablesJson) => run((s) => s.correlateMessage(messageName, correlationKey, variablesJson)), [run]);
119
+ const throwError = useCallback((jobKey, errorCode, errorMessage) => run((s) => s.throwError(jobKey, errorCode, errorMessage)), [run]);
120
+ const updateRetries = useCallback((jobKey, retries) => run((s) => s.updateRetries(jobKey, retries)), [run]);
121
+ const resolveIncident = useCallback((incidentKey) => run((s) => s.resolveIncident(incidentKey)), [run]);
122
+ const setVariables = useCallback((scopeKey, variablesJson, local) => run((s) => s.setVariables(scopeKey, variablesJson, local)), [run]);
123
+ const broadcastSignal = useCallback((signalName, variablesJson) => run((s) => s.broadcastSignal(signalName, variablesJson)), [run]);
124
+ const cancelInstance = useCallback((instanceKey) => run((s) => s.cancelInstance(instanceKey)), [run]);
125
+ const modify = useCallback((instanceKey, activateInstructions, terminateElementInstanceKeys) => run((s) => s.modify(instanceKey, activateInstructions, terminateElementInstanceKeys)), [run]);
126
+ const completeUserTask = useCallback((userTaskKey, variablesJson) => run((s) => s.completeUserTask(userTaskKey, variablesJson)), [run]);
127
+ const assignUserTask = useCallback((userTaskKey, assignee, allowOverride) => run((s) => s.assignUserTask(userTaskKey, assignee, allowOverride)), [run]);
128
+ const unassignUserTask = useCallback((userTaskKey) => run((s) => s.unassignUserTask(userTaskKey)), [run]);
129
+ const updateUserTask = useCallback((userTaskKey, changesetJson) => run((s) => s.updateUserTask(userTaskKey, changesetJson)), [run]);
99
130
  const runWorkers = useCallback(async (workers, opts) => {
100
131
  const session = sessionRef.current;
101
132
  if (!session)
@@ -107,7 +138,7 @@ export function useBojtos({ bpmn, wasm }) {
107
138
  if (sessionRef.current !== session)
108
139
  return null;
109
140
  setSnapshot(settled);
110
- setEvents(session.events());
141
+ setEvents(readEvents(session));
111
142
  setError(null);
112
143
  return settled;
113
144
  }
@@ -117,7 +148,7 @@ export function useBojtos({ bpmn, wasm }) {
117
148
  // Reflect whatever state the engine reached before the drain aborted
118
149
  // (e.g. the maxRounds guard) so the view isn't left stale.
119
150
  setSnapshot(session.snapshot());
120
- setEvents(session.events());
151
+ setEvents(readEvents(session));
121
152
  setError(String(e));
122
153
  return null;
123
154
  }
@@ -132,7 +163,7 @@ export function useBojtos({ bpmn, wasm }) {
132
163
  if (sessionRef.current !== session)
133
164
  return null;
134
165
  setSnapshot(round.snapshot);
135
- setEvents(session.events());
166
+ setEvents(readEvents(session));
136
167
  setError(null);
137
168
  return round;
138
169
  }
@@ -140,7 +171,7 @@ export function useBojtos({ bpmn, wasm }) {
140
171
  if (sessionRef.current !== session)
141
172
  return null;
142
173
  setSnapshot(session.snapshot());
143
- setEvents(session.events());
174
+ setEvents(readEvents(session));
144
175
  setError(String(e));
145
176
  return null;
146
177
  }
@@ -172,6 +203,17 @@ export function useBojtos({ bpmn, wasm }) {
172
203
  failJob,
173
204
  advanceTime,
174
205
  correlateMessage,
206
+ throwError,
207
+ updateRetries,
208
+ resolveIncident,
209
+ setVariables,
210
+ broadcastSignal,
211
+ cancelInstance,
212
+ modify,
213
+ completeUserTask,
214
+ assignUserTask,
215
+ unassignUserTask,
216
+ updateUserTask,
175
217
  runWorkers,
176
218
  stepWorkers,
177
219
  reset,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanobpm/bojtos-react",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "React binding for the Bojtos in-browser BPMN demo framework (ADR 0043): the useBojtos hook (owns the engine session + reactive snapshot/event state) and the <BpmnRuntimeView> live token/incident diagram. Built on @nanobpm/bojtos-kit; the console test-run panel is its first consumer.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -27,10 +27,12 @@
27
27
  "scripts": {
28
28
  "build": "tsc -p tsconfig.json",
29
29
  "typecheck": "tsc -p tsconfig.json --noEmit",
30
- "prepack": "npm run build"
30
+ "prepack": "npm run build",
31
+ "test": "npm run build && npm run test:ci",
32
+ "test:ci": "node --experimental-strip-types --test test/*.test.ts"
31
33
  },
32
34
  "dependencies": {
33
- "@nanobpm/bojtos-kit": "^0.4.0"
35
+ "@nanobpm/bojtos-kit": "^0.5.0"
34
36
  },
35
37
  "peerDependencies": {
36
38
  "bpmn-js": ">=17",
@@ -1,5 +1,6 @@
1
1
  import { useEffect, useRef } from "react";
2
2
  import NavigatedViewer from "bpmn-js/lib/NavigatedViewer";
3
+ import { describeRunState, markerKey } from "./runState.js";
3
4
 
4
5
  interface Canvas {
5
6
  zoom(mode: string): void;
@@ -27,6 +28,17 @@ export interface BpmnRuntimeViewProps {
27
28
  incidentIds: string[];
28
29
  /** Optional class for the container element (it always fills its parent). */
29
30
  className?: string;
31
+ /**
32
+ * Accessible name for the diagram. The token and incident highlights are
33
+ * purely visual, so without this a screen-reader user is told nothing at all
34
+ * about what is running.
35
+ */
36
+ label?: string;
37
+ /**
38
+ * Map an element id to a human name for the live status announcement — pass
39
+ * the diagram's element names if you have them. Defaults to the raw id.
40
+ */
41
+ elementName?: (elementId: string) => string;
30
42
  }
31
43
 
32
44
  /**
@@ -46,6 +58,8 @@ export function BpmnRuntimeView({
46
58
  activeIds,
47
59
  incidentIds,
48
60
  className,
61
+ label = "BPMN process diagram",
62
+ elementName,
49
63
  }: BpmnRuntimeViewProps) {
50
64
  const containerRef = useRef<HTMLDivElement>(null);
51
65
  const viewerRef = useRef<NavigatedViewer | null>(null);
@@ -133,16 +147,49 @@ export function BpmnRuntimeView({
133
147
  tokenOverlaysRef.current = nextOverlays;
134
148
  }
135
149
 
150
+ // `activeIds` / `incidentIds` are almost always fresh arrays (`snapshot?.x ??
151
+ // []`), so depending on their identity re-painted every marker and re-created
152
+ // every token overlay on every render of the parent — visible churn on a busy
153
+ // diagram. Depend on the ids themselves instead.
154
+ const key = markerKey(activeIds, incidentIds);
136
155
  useEffect(() => {
137
156
  applyMarkers();
138
157
  // eslint-disable-next-line react-hooks/exhaustive-deps
139
- }, [activeIds, incidentIds]);
158
+ }, [key]);
140
159
 
141
160
  return (
142
161
  <div
143
- ref={containerRef}
144
162
  className={className}
145
- style={{ width: "100%", height: "100%" }}
146
- />
163
+ style={{ width: "100%", height: "100%", position: "relative" }}
164
+ >
165
+ <div
166
+ ref={containerRef}
167
+ role="img"
168
+ aria-label={label}
169
+ style={{ width: "100%", height: "100%" }}
170
+ />
171
+ {/* The highlights are visual only. Mirror them as text, politely
172
+ announced, so the run is followable without seeing the diagram. No
173
+ `aria-label` here: on a live region it would override the changing
174
+ text in the accessible-name computation, so screen readers would
175
+ announce the static label instead of the run state. */}
176
+ <div
177
+ role="status"
178
+ aria-live="polite"
179
+ style={{
180
+ position: "absolute",
181
+ width: 1,
182
+ height: 1,
183
+ margin: -1,
184
+ padding: 0,
185
+ overflow: "hidden",
186
+ clip: "rect(0 0 0 0)",
187
+ whiteSpace: "nowrap",
188
+ border: 0,
189
+ }}
190
+ >
191
+ {describeRunState(activeIds, incidentIds, elementName)}
192
+ </div>
193
+ </div>
147
194
  );
148
195
  }
package/src/index.ts CHANGED
@@ -20,14 +20,24 @@ export {
20
20
  BpmnRuntimeView,
21
21
  type BpmnRuntimeViewProps,
22
22
  } from "./BpmnRuntimeView.js";
23
+ export {
24
+ describeRunState,
25
+ markerKey,
26
+ bpmnKey,
27
+ resourceList,
28
+ capEvents,
29
+ } from "./runState.js";
23
30
  export {
24
31
  JobFailure,
32
+ settleReason,
33
+ unhandledJobTypes,
25
34
  type JobHandler,
26
35
  type JobResult,
27
36
  type AgentHandler,
28
37
  type DispatchOptions,
29
38
  type DispatchResult,
30
39
  type RoundResult,
40
+ type SettleReason,
31
41
  } from "@nanobpm/bojtos-kit";
32
42
  export type {
33
43
  BojtosSession,
@@ -37,7 +47,14 @@ export type {
37
47
  ActivatedJob,
38
48
  IncidentDto,
39
49
  TimerDto,
50
+ UserTaskDto,
51
+ MessageSubscriptionDto,
52
+ SignalSubscriptionDto,
53
+ ElementStatDto,
54
+ SequenceFlowDto,
55
+ DecisionInstanceDto,
40
56
  ActiveEl,
57
+ ActivateInstruction,
41
58
  AgentActivation,
42
59
  AgentResult,
43
60
  WasmEvent,
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Pure helpers behind the React binding's decisions.
3
+ *
4
+ * Kept out of the component and the hook — and out of any module that imports
5
+ * bpmn-js — so they can be tested without a DOM or the peer dependency. The
6
+ * component and hook hold rendering and lifecycle; what counts as a change, how
7
+ * a run reads aloud, which resources to deploy and how far to trim the log are
8
+ * decisions, and decisions are worth testing.
9
+ */
10
+
11
+ /**
12
+ * Stable key for a marker set, so unchanged ids don't re-paint the diagram.
13
+ *
14
+ * Relies on BPMN element ids being XML NCNames (so they can't contain `,` or
15
+ * `|`) — that invariant is what keeps the two-field join collision-free. If ids
16
+ * could contain the delimiters, `["a,b"],[]` and `["a"],["b"]`-style pairs
17
+ * would key alike.
18
+ */
19
+ export function markerKey(activeIds: string[], incidentIds: string[]): string {
20
+ return `${activeIds.join(",")}|${incidentIds.join(",")}`;
21
+ }
22
+
23
+ /**
24
+ * Content key for the `bpmn` prop, so a fresh array identity each render doesn't
25
+ * re-create the engine but a real content change still does. Only the array
26
+ * case is serialized (with a boundary-preserving `JSON.stringify`, not a
27
+ * `join` — a delimiter join lets two different resource arrays collapse to one
28
+ * key when a resource borders/contains the delimiter, silently missing a real
29
+ * change). A lone string can't have array-boundary collisions and React deps
30
+ * already compare strings by value, so it passes through untouched — no needless
31
+ * re-walk of potentially large BPMN XML each render.
32
+ */
33
+ export function bpmnKey(bpmn: string | string[]): string {
34
+ return Array.isArray(bpmn) ? JSON.stringify(bpmn) : bpmn;
35
+ }
36
+
37
+ /**
38
+ * Normalize the `bpmn` prop to an ordered resource list for deployment. Deploy
39
+ * order is significant: a later resource can reference an earlier one (a call
40
+ * activity's child), so the array order is preserved verbatim.
41
+ */
42
+ export function resourceList(bpmn: string | string[]): string[] {
43
+ return Array.isArray(bpmn) ? bpmn : [bpmn];
44
+ }
45
+
46
+ /**
47
+ * Trim an event log to the consumer's cap, keeping the most recent events.
48
+ * `undefined` or a negative cap means "no cap"; `0` means "keep nothing". The
49
+ * input array is never mutated.
50
+ */
51
+ export function capEvents<T>(all: T[], cap: number | undefined): T[] {
52
+ return cap !== undefined && cap >= 0 && all.length > cap
53
+ ? all.slice(all.length - cap)
54
+ : all;
55
+ }
56
+
57
+ /**
58
+ * A one-line description of run state, for the diagram's live region. The token
59
+ * and incident highlights are purely visual; this is the same information as
60
+ * text.
61
+ */
62
+ export function describeRunState(
63
+ activeIds: string[],
64
+ incidentIds: string[],
65
+ name: (id: string) => string = (id) => id,
66
+ ): string {
67
+ const parts: string[] = [];
68
+ if (activeIds.length) parts.push(`Running: ${activeIds.map(name).join(", ")}`);
69
+ if (incidentIds.length)
70
+ parts.push(`Incident: ${incidentIds.map(name).join(", ")}`);
71
+ return parts.length ? parts.join(". ") : "Nothing running";
72
+ }
package/src/useBojtos.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
2
  import {
3
+ type ActivateInstruction,
3
4
  type AgentResult,
4
5
  type BojtosSession,
5
6
  createBojtosSession,
@@ -12,13 +13,20 @@ import {
12
13
  type WasmEvent,
13
14
  type WasmSource,
14
15
  } from "@nanobpm/bojtos-kit";
16
+ import { bpmnKey, capEvents, resourceList } from "./runState.js";
15
17
 
16
18
  /** Lifecycle of the in-browser engine load. */
17
19
  export type BojtosPhase = "loading" | "ready" | "error";
18
20
 
19
21
  export interface UseBojtosOptions {
20
- /** The BPMN diagram XML to deploy. Re-deploys on a fresh engine when it changes. */
21
- bpmn: string;
22
+ /**
23
+ * The BPMN to deploy. Re-deploys on a fresh engine when it changes.
24
+ *
25
+ * Pass an array to deploy several resources into one engine — a called
26
+ * process alongside its parent, say. `processIds` then lists every deployable
27
+ * process across all of them, in deployment order.
28
+ */
29
+ bpmn: string | string[];
22
30
  /**
23
31
  * Optional engine wasm source. Pass a `URL` / bytes / `WebAssembly.Module`
24
32
  * when the default `import.meta.url` loader can't resolve the binary (the
@@ -29,6 +37,15 @@ export interface UseBojtosOptions {
29
37
  * reload the module.
30
38
  */
31
39
  wasm?: WasmSource;
40
+ /**
41
+ * Cap the reactive `events` log at the most recent N entries.
42
+ *
43
+ * Every command re-reads the engine's full event log into React state, so a
44
+ * long-running demo copies an ever-growing array on each step. Set this when
45
+ * a page runs for a while and only shows a tail; leave it unset to keep the
46
+ * whole log, which stays the default so existing consumers are unaffected.
47
+ */
48
+ maxEvents?: number;
32
49
  }
33
50
 
34
51
  export interface BojtosControls {
@@ -66,6 +83,74 @@ export interface BojtosControls {
66
83
  ): Snapshot | null;
67
84
  /** Advance the virtual clock. */
68
85
  advanceTime(byMs: number): Snapshot | null;
86
+ /**
87
+ * Throw a BPMN business error from a waiting job: interrupts the activity via
88
+ * a matching error boundary/event-subprocess catch, or raises an incident if
89
+ * uncaught. The job is consumed either way.
90
+ */
91
+ throwError(
92
+ jobKey: string,
93
+ errorCode: string,
94
+ errorMessage: string,
95
+ ): Snapshot | null;
96
+ /**
97
+ * Set a job's remaining retries. Used to recover a job parked on a no-retries
98
+ * incident before resolving that incident; does not itself unblock the job.
99
+ */
100
+ updateRetries(jobKey: string, retries: number): Snapshot | null;
101
+ /**
102
+ * Resolve an open incident by key, retrying the work that failed. Pair with
103
+ * {@link updateRetries} to make a failed job activatable again — the
104
+ * incident/retry loop a demo needs to show recovery.
105
+ */
106
+ resolveIncident(incidentKey: string): Snapshot | null;
107
+ /**
108
+ * Merge variables into a scope (a process-instance or element-instance key).
109
+ * With `local`, they are written strictly into that scope; otherwise they
110
+ * propagate up to the nearest ancestor defining each name.
111
+ */
112
+ setVariables(
113
+ scopeKey: string,
114
+ variablesJson: string,
115
+ local: boolean,
116
+ ): Snapshot | null;
117
+ /** Broadcast a signal by name to every matching open subscription. */
118
+ broadcastSignal(signalName: string, variablesJson: string): Snapshot | null;
119
+ /** Cancel (terminate) a running process instance. */
120
+ cancelInstance(instanceKey: string): Snapshot | null;
121
+ /**
122
+ * Modify a running instance: terminate element instances and/or activate new
123
+ * ones (Zeebe "modify process instance").
124
+ */
125
+ modify(
126
+ instanceKey: string,
127
+ activateInstructions: ActivateInstruction[],
128
+ terminateElementInstanceKeys: string[],
129
+ ): Snapshot | null;
130
+ /**
131
+ * Complete a waiting user task, merging output variables.
132
+ *
133
+ * A `userTask` produces no job, so the dispatch loop cannot advance one: this
134
+ * is the only way a model with a human step reaches its end event. Drive it
135
+ * from `snapshot.userTasks`.
136
+ */
137
+ completeUserTask(userTaskKey: string, variablesJson: string): Snapshot | null;
138
+ /**
139
+ * Assign a user task. With `allowOverride` false the command is rejected if
140
+ * the task already has an assignee.
141
+ */
142
+ assignUserTask(
143
+ userTaskKey: string,
144
+ assignee: string,
145
+ allowOverride: boolean,
146
+ ): Snapshot | null;
147
+ /** Clear a user task's assignee. */
148
+ unassignUserTask(userTaskKey: string): Snapshot | null;
149
+ /**
150
+ * Update a user task's attributes from a JSON changeset (`candidateGroups`,
151
+ * `candidateUsers`, `dueDate`, `followUpDate`, `priority`).
152
+ */
153
+ updateUserTask(userTaskKey: string, changesetJson: string): Snapshot | null;
69
154
  /**
70
155
  * Run the registered worker handlers until the process settles (activate →
71
156
  * handler → complete/fail), then reflect the resulting snapshot/events.
@@ -92,6 +177,29 @@ export interface BojtosControls {
92
177
  reset(): void;
93
178
  }
94
179
 
180
+ /**
181
+ * Session members the hook deliberately does not re-export: the deployment
182
+ * lifecycle it owns itself, and the low-level activate primitive the dispatch
183
+ * loop owns.
184
+ */
185
+ type NotReExported = "deploy" | "free" | "activateJobs";
186
+
187
+ /**
188
+ * Compile-time guard. `useBojtos` keeps its session private, so a command it
189
+ * doesn't re-export is *unreachable* for a consumer rather than merely
190
+ * inconvenient — which is how `completeUserTask` went missing and left any model
191
+ * with a user task unfinishable (#1).
192
+ *
193
+ * Adding a command to {@link BojtosSession} without a binding here now fails the
194
+ * build with the offending name, instead of shipping a hole.
195
+ */
196
+ type UnboundCommands = Exclude<
197
+ keyof BojtosSession,
198
+ NotReExported | keyof BojtosControls
199
+ >;
200
+ type AssertNever<T extends never> = T;
201
+ type _EverySessionCommandIsBound = AssertNever<UnboundCommands>;
202
+
95
203
  /**
96
204
  * React binding over a headless {@link BojtosSession}: owns the engine's
97
205
  * lifecycle and the reactive `snapshot` / `events` / `processIds` state, and
@@ -103,7 +211,11 @@ export interface BojtosControls {
103
211
  * test-run panel is its first consumer (§8 step 2 — dogfooding is the acceptance
104
212
  * test).
105
213
  */
106
- export function useBojtos({ bpmn, wasm }: UseBojtosOptions): BojtosControls {
214
+ export function useBojtos({
215
+ bpmn,
216
+ wasm,
217
+ maxEvents,
218
+ }: UseBojtosOptions): BojtosControls {
107
219
  const sessionRef = useRef<BojtosSession | null>(null);
108
220
  const [phase, setPhase] = useState<BojtosPhase>("loading");
109
221
  const [error, setError] = useState<string | null>(null);
@@ -117,15 +229,34 @@ export function useBojtos({ bpmn, wasm }: UseBojtosOptions): BojtosControls {
117
229
  const wasmRef = useRef(wasm);
118
230
  wasmRef.current = wasm;
119
231
 
232
+ // An array prop has a fresh identity every render, which would re-create the
233
+ // engine on each one. Key the deploy effect on the content instead — see
234
+ // `bpmnKey` for why this is a boundary-preserving serialization, not a join.
235
+ const deployKey = bpmnKey(bpmn);
236
+ const bpmnRef = useRef(bpmn);
237
+ bpmnRef.current = bpmn;
238
+
239
+ // Trim the reactive event log when the consumer asked for a cap.
240
+ const maxEventsRef = useRef(maxEvents);
241
+ maxEventsRef.current = maxEvents;
242
+ const readEvents = useCallback((session: BojtosSession): WasmEvent[] => {
243
+ return capEvents(session.events(), maxEventsRef.current);
244
+ }, []);
245
+
120
246
  const deployInto = useCallback(
121
247
  (session: BojtosSession) => {
122
- const res = session.deploy(bpmn);
123
- setProcessIds(res.processIds);
248
+ const resources = resourceList(bpmnRef.current);
249
+ // Deploy in order, collecting every deployable process id. A later
250
+ // resource can reference an earlier one (a call activity's child).
251
+ const ids: string[] = [];
252
+ for (const xml of resources) ids.push(...session.deploy(xml).processIds);
253
+ setProcessIds(ids);
124
254
  setSnapshot(null);
125
255
  setEvents([]);
126
256
  setError(null);
127
257
  },
128
- [bpmn],
258
+ // eslint-disable-next-line react-hooks/exhaustive-deps
259
+ [deployKey],
129
260
  );
130
261
 
131
262
  useEffect(() => {
@@ -178,7 +309,7 @@ export function useBojtos({ bpmn, wasm }: UseBojtosOptions): BojtosControls {
178
309
  try {
179
310
  const snap = fn(session);
180
311
  setSnapshot(snap);
181
- setEvents(session.events());
312
+ setEvents(readEvents(session));
182
313
  setError(null);
183
314
  return snap;
184
315
  } catch (e) {
@@ -218,6 +349,68 @@ export function useBojtos({ bpmn, wasm }: UseBojtosOptions): BojtosControls {
218
349
  run((s) => s.correlateMessage(messageName, correlationKey, variablesJson)),
219
350
  [run],
220
351
  );
352
+ const throwError = useCallback(
353
+ (jobKey: string, errorCode: string, errorMessage: string) =>
354
+ run((s) => s.throwError(jobKey, errorCode, errorMessage)),
355
+ [run],
356
+ );
357
+ const updateRetries = useCallback(
358
+ (jobKey: string, retries: number) =>
359
+ run((s) => s.updateRetries(jobKey, retries)),
360
+ [run],
361
+ );
362
+ const resolveIncident = useCallback(
363
+ (incidentKey: string) => run((s) => s.resolveIncident(incidentKey)),
364
+ [run],
365
+ );
366
+ const setVariables = useCallback(
367
+ (scopeKey: string, variablesJson: string, local: boolean) =>
368
+ run((s) => s.setVariables(scopeKey, variablesJson, local)),
369
+ [run],
370
+ );
371
+ const broadcastSignal = useCallback(
372
+ (signalName: string, variablesJson: string) =>
373
+ run((s) => s.broadcastSignal(signalName, variablesJson)),
374
+ [run],
375
+ );
376
+ const cancelInstance = useCallback(
377
+ (instanceKey: string) => run((s) => s.cancelInstance(instanceKey)),
378
+ [run],
379
+ );
380
+ const modify = useCallback(
381
+ (
382
+ instanceKey: string,
383
+ activateInstructions: ActivateInstruction[],
384
+ terminateElementInstanceKeys: string[],
385
+ ) =>
386
+ run((s) =>
387
+ s.modify(
388
+ instanceKey,
389
+ activateInstructions,
390
+ terminateElementInstanceKeys,
391
+ ),
392
+ ),
393
+ [run],
394
+ );
395
+ const completeUserTask = useCallback(
396
+ (userTaskKey: string, variablesJson: string) =>
397
+ run((s) => s.completeUserTask(userTaskKey, variablesJson)),
398
+ [run],
399
+ );
400
+ const assignUserTask = useCallback(
401
+ (userTaskKey: string, assignee: string, allowOverride: boolean) =>
402
+ run((s) => s.assignUserTask(userTaskKey, assignee, allowOverride)),
403
+ [run],
404
+ );
405
+ const unassignUserTask = useCallback(
406
+ (userTaskKey: string) => run((s) => s.unassignUserTask(userTaskKey)),
407
+ [run],
408
+ );
409
+ const updateUserTask = useCallback(
410
+ (userTaskKey: string, changesetJson: string) =>
411
+ run((s) => s.updateUserTask(userTaskKey, changesetJson)),
412
+ [run],
413
+ );
221
414
 
222
415
  const runWorkers = useCallback(
223
416
  async (
@@ -236,7 +429,7 @@ export function useBojtos({ bpmn, wasm }: UseBojtosOptions): BojtosControls {
236
429
  // while we awaited — don't publish stale state or read a freed session.
237
430
  if (sessionRef.current !== session) return null;
238
431
  setSnapshot(settled);
239
- setEvents(session.events());
432
+ setEvents(readEvents(session));
240
433
  setError(null);
241
434
  return settled;
242
435
  } catch (e) {
@@ -244,7 +437,7 @@ export function useBojtos({ bpmn, wasm }: UseBojtosOptions): BojtosControls {
244
437
  // Reflect whatever state the engine reached before the drain aborted
245
438
  // (e.g. the maxRounds guard) so the view isn't left stale.
246
439
  setSnapshot(session.snapshot());
247
- setEvents(session.events());
440
+ setEvents(readEvents(session));
248
441
  setError(String(e));
249
442
  return null;
250
443
  }
@@ -264,13 +457,13 @@ export function useBojtos({ bpmn, wasm }: UseBojtosOptions): BojtosControls {
264
457
  // Bail if the session was replaced/freed while we awaited the round.
265
458
  if (sessionRef.current !== session) return null;
266
459
  setSnapshot(round.snapshot);
267
- setEvents(session.events());
460
+ setEvents(readEvents(session));
268
461
  setError(null);
269
462
  return round;
270
463
  } catch (e) {
271
464
  if (sessionRef.current !== session) return null;
272
465
  setSnapshot(session.snapshot());
273
- setEvents(session.events());
466
+ setEvents(readEvents(session));
274
467
  setError(String(e));
275
468
  return null;
276
469
  }
@@ -304,6 +497,17 @@ export function useBojtos({ bpmn, wasm }: UseBojtosOptions): BojtosControls {
304
497
  failJob,
305
498
  advanceTime,
306
499
  correlateMessage,
500
+ throwError,
501
+ updateRetries,
502
+ resolveIncident,
503
+ setVariables,
504
+ broadcastSignal,
505
+ cancelInstance,
506
+ modify,
507
+ completeUserTask,
508
+ assignUserTask,
509
+ unassignUserTask,
510
+ updateUserTask,
307
511
  runWorkers,
308
512
  stepWorkers,
309
513
  reset,