@nanobpm/bojtos-react 0.4.0 → 0.6.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 +32 -2
- package/dist/BpmnRuntimeView.d.ts +12 -1
- package/dist/BpmnRuntimeView.js +20 -4
- package/dist/TraceTimeline.d.ts +50 -0
- package/dist/TraceTimeline.js +81 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +5 -1
- package/dist/runState.d.ts +47 -0
- package/dist/runState.js +64 -0
- package/dist/useBojtos.d.ts +71 -4
- package/dist/useBojtos.js +51 -9
- package/package.json +5 -3
- package/src/BpmnRuntimeView.tsx +51 -4
- package/src/TraceTimeline.tsx +303 -0
- package/src/index.ts +30 -0
- package/src/runState.ts +72 -0
- package/src/useBojtos.ts +215 -11
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
|
-
/**
|
|
21
|
-
|
|
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({
|
|
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
|
|
123
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|