@nanobpm/bojtos-react 0.1.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 +56 -0
- package/dist/Bojtos.d.ts +51 -0
- package/dist/Bojtos.js +154 -0
- package/dist/BpmnRuntimeView.d.ts +23 -0
- package/dist/BpmnRuntimeView.js +109 -0
- package/dist/examples/orderFulfillment.d.ts +17 -0
- package/dist/examples/orderFulfillment.js +88 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +10 -0
- package/dist/useBojtos.d.ts +70 -0
- package/dist/useBojtos.js +177 -0
- package/package.json +47 -0
- package/src/Bojtos.tsx +263 -0
- package/src/BpmnRuntimeView.tsx +148 -0
- package/src/bpmn-js.d.ts +18 -0
- package/src/examples/orderFulfillment.tsx +98 -0
- package/src/index.ts +41 -0
- package/src/useBojtos.ts +295 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { JobHandler } from "@nanobpm/bojtos-kit";
|
|
2
|
+
import { Bojtos } from "../Bojtos.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A laid-out order-fulfillment diagram: start → reserve stock (inventory) →
|
|
6
|
+
* charge card (payment) → ship (shipping) → done. Unlike the headless engine
|
|
7
|
+
* fixtures, this carries `bpmndi` diagram-interchange so bpmn-js actually
|
|
8
|
+
* renders the shapes for the token to walk across.
|
|
9
|
+
*/
|
|
10
|
+
export const ORDER_FULFILLMENT_BPMN = `<?xml version="1.0" encoding="UTF-8"?>
|
|
11
|
+
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" id="order-fulfillment-defs" targetNamespace="http://bpmn.io/schema/bpmn">
|
|
12
|
+
<bpmn:process id="order-fulfillment" isExecutable="true">
|
|
13
|
+
<bpmn:startEvent id="start" name="Order placed">
|
|
14
|
+
<bpmn:outgoing>f1</bpmn:outgoing>
|
|
15
|
+
</bpmn:startEvent>
|
|
16
|
+
<bpmn:serviceTask id="reserve" name="Reserve stock">
|
|
17
|
+
<bpmn:extensionElements><zeebe:taskDefinition type="inventory" /></bpmn:extensionElements>
|
|
18
|
+
<bpmn:incoming>f1</bpmn:incoming>
|
|
19
|
+
<bpmn:outgoing>f2</bpmn:outgoing>
|
|
20
|
+
</bpmn:serviceTask>
|
|
21
|
+
<bpmn:serviceTask id="charge" name="Charge card">
|
|
22
|
+
<bpmn:extensionElements><zeebe:taskDefinition type="payment" /></bpmn:extensionElements>
|
|
23
|
+
<bpmn:incoming>f2</bpmn:incoming>
|
|
24
|
+
<bpmn:outgoing>f3</bpmn:outgoing>
|
|
25
|
+
</bpmn:serviceTask>
|
|
26
|
+
<bpmn:serviceTask id="ship" name="Ship">
|
|
27
|
+
<bpmn:extensionElements><zeebe:taskDefinition type="shipping" /></bpmn:extensionElements>
|
|
28
|
+
<bpmn:incoming>f3</bpmn:incoming>
|
|
29
|
+
<bpmn:outgoing>f4</bpmn:outgoing>
|
|
30
|
+
</bpmn:serviceTask>
|
|
31
|
+
<bpmn:endEvent id="done" name="Fulfilled">
|
|
32
|
+
<bpmn:incoming>f4</bpmn:incoming>
|
|
33
|
+
</bpmn:endEvent>
|
|
34
|
+
<bpmn:sequenceFlow id="f1" sourceRef="start" targetRef="reserve" />
|
|
35
|
+
<bpmn:sequenceFlow id="f2" sourceRef="reserve" targetRef="charge" />
|
|
36
|
+
<bpmn:sequenceFlow id="f3" sourceRef="charge" targetRef="ship" />
|
|
37
|
+
<bpmn:sequenceFlow id="f4" sourceRef="ship" targetRef="done" />
|
|
38
|
+
</bpmn:process>
|
|
39
|
+
<bpmndi:BPMNDiagram id="diagram">
|
|
40
|
+
<bpmndi:BPMNPlane id="plane" bpmnElement="order-fulfillment">
|
|
41
|
+
<bpmndi:BPMNShape id="start_di" bpmnElement="start">
|
|
42
|
+
<dc:Bounds x="150" y="102" width="36" height="36" />
|
|
43
|
+
</bpmndi:BPMNShape>
|
|
44
|
+
<bpmndi:BPMNShape id="reserve_di" bpmnElement="reserve">
|
|
45
|
+
<dc:Bounds x="240" y="80" width="100" height="80" />
|
|
46
|
+
</bpmndi:BPMNShape>
|
|
47
|
+
<bpmndi:BPMNShape id="charge_di" bpmnElement="charge">
|
|
48
|
+
<dc:Bounds x="400" y="80" width="100" height="80" />
|
|
49
|
+
</bpmndi:BPMNShape>
|
|
50
|
+
<bpmndi:BPMNShape id="ship_di" bpmnElement="ship">
|
|
51
|
+
<dc:Bounds x="560" y="80" width="100" height="80" />
|
|
52
|
+
</bpmndi:BPMNShape>
|
|
53
|
+
<bpmndi:BPMNShape id="done_di" bpmnElement="done">
|
|
54
|
+
<dc:Bounds x="720" y="102" width="36" height="36" />
|
|
55
|
+
</bpmndi:BPMNShape>
|
|
56
|
+
<bpmndi:BPMNEdge id="f1_di" bpmnElement="f1">
|
|
57
|
+
<di:waypoint x="186" y="120" />
|
|
58
|
+
<di:waypoint x="240" y="120" />
|
|
59
|
+
</bpmndi:BPMNEdge>
|
|
60
|
+
<bpmndi:BPMNEdge id="f2_di" bpmnElement="f2">
|
|
61
|
+
<di:waypoint x="340" y="120" />
|
|
62
|
+
<di:waypoint x="400" y="120" />
|
|
63
|
+
</bpmndi:BPMNEdge>
|
|
64
|
+
<bpmndi:BPMNEdge id="f3_di" bpmnElement="f3">
|
|
65
|
+
<di:waypoint x="500" y="120" />
|
|
66
|
+
<di:waypoint x="560" y="120" />
|
|
67
|
+
</bpmndi:BPMNEdge>
|
|
68
|
+
<bpmndi:BPMNEdge id="f4_di" bpmnElement="f4">
|
|
69
|
+
<di:waypoint x="660" y="120" />
|
|
70
|
+
<di:waypoint x="720" y="120" />
|
|
71
|
+
</bpmndi:BPMNEdge>
|
|
72
|
+
</bpmndi:BPMNPlane>
|
|
73
|
+
</bpmndi:BPMNDiagram>
|
|
74
|
+
</bpmn:definitions>`;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The in-browser workers for {@link ORDER_FULFILLMENT_BPMN}. Each reads the
|
|
78
|
+
* instance's live payload and returns the variables it adds — edit a handler and
|
|
79
|
+
* re-run to watch the payload change (ADR 0043 §8 step 4 makes these boxes
|
|
80
|
+
* editable in the browser).
|
|
81
|
+
*/
|
|
82
|
+
export const orderFulfillmentWorkers: Record<string, JobHandler> = {
|
|
83
|
+
inventory: (job) => ({ reserved: true, sku: job.variables.sku }),
|
|
84
|
+
payment: (job) => ({ charged: job.variables.amount ?? 0 }),
|
|
85
|
+
shipping: () => ({ tracking: `1Z${Math.floor(Math.random() * 1e9)}` }),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/** A ready-to-drop-in Bojtos demo — the canonical first example (ADR 0043 §8). */
|
|
89
|
+
export function OrderFulfillmentDemo() {
|
|
90
|
+
return (
|
|
91
|
+
<Bojtos
|
|
92
|
+
bpmn={ORDER_FULFILLMENT_BPMN}
|
|
93
|
+
workers={orderFulfillmentWorkers}
|
|
94
|
+
seed={{ sku: "WIDGET-1", amount: 4200 }}
|
|
95
|
+
autoplay
|
|
96
|
+
/>
|
|
97
|
+
);
|
|
98
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// @nanobpm/bojtos-react — the React binding for the Bojtos in-browser BPMN demo
|
|
2
|
+
// framework (ADR 0043). `useBojtos` owns the engine session and reactive state;
|
|
3
|
+
// `<BpmnRuntimeView>` renders the live token/incident diagram. The engine's
|
|
4
|
+
// snapshot/event contract types are re-exported from @nanobpm/bojtos-kit for
|
|
5
|
+
// convenience.
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
useBojtos,
|
|
9
|
+
type UseBojtosOptions,
|
|
10
|
+
type BojtosControls,
|
|
11
|
+
type BojtosPhase,
|
|
12
|
+
} from "./useBojtos.js";
|
|
13
|
+
export { Bojtos, type BojtosProps, type TraceEvent } from "./Bojtos.js";
|
|
14
|
+
export {
|
|
15
|
+
OrderFulfillmentDemo,
|
|
16
|
+
ORDER_FULFILLMENT_BPMN,
|
|
17
|
+
orderFulfillmentWorkers,
|
|
18
|
+
} from "./examples/orderFulfillment.js";
|
|
19
|
+
export {
|
|
20
|
+
BpmnRuntimeView,
|
|
21
|
+
type BpmnRuntimeViewProps,
|
|
22
|
+
} from "./BpmnRuntimeView.js";
|
|
23
|
+
export {
|
|
24
|
+
JobFailure,
|
|
25
|
+
type JobHandler,
|
|
26
|
+
type JobResult,
|
|
27
|
+
type DispatchOptions,
|
|
28
|
+
type DispatchResult,
|
|
29
|
+
type RoundResult,
|
|
30
|
+
} from "@nanobpm/bojtos-kit";
|
|
31
|
+
export type {
|
|
32
|
+
BojtosSession,
|
|
33
|
+
Snapshot,
|
|
34
|
+
InstanceDto,
|
|
35
|
+
JobDto,
|
|
36
|
+
ActivatedJob,
|
|
37
|
+
IncidentDto,
|
|
38
|
+
TimerDto,
|
|
39
|
+
ActiveEl,
|
|
40
|
+
WasmEvent,
|
|
41
|
+
} from "@nanobpm/bojtos-kit";
|
package/src/useBojtos.ts
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
type BojtosSession,
|
|
4
|
+
createBojtosSession,
|
|
5
|
+
type DispatchOptions,
|
|
6
|
+
dispatchRound,
|
|
7
|
+
dispatchWorkers,
|
|
8
|
+
type JobHandler,
|
|
9
|
+
type RoundResult,
|
|
10
|
+
type Snapshot,
|
|
11
|
+
type WasmEvent,
|
|
12
|
+
type WasmSource,
|
|
13
|
+
} from "@nanobpm/bojtos-kit";
|
|
14
|
+
|
|
15
|
+
/** Lifecycle of the in-browser engine load. */
|
|
16
|
+
export type BojtosPhase = "loading" | "ready" | "error";
|
|
17
|
+
|
|
18
|
+
export interface UseBojtosOptions {
|
|
19
|
+
/** The BPMN diagram XML to deploy. Re-deploys on a fresh engine when it changes. */
|
|
20
|
+
bpmn: string;
|
|
21
|
+
/**
|
|
22
|
+
* Optional engine wasm source. Pass a `URL` / bytes / `WebAssembly.Module`
|
|
23
|
+
* when the default `import.meta.url` loader can't resolve the binary (the
|
|
24
|
+
* external-`.wasm` "wasmUrl" mode, or a non-Vite bundler — ADR 0043 §3).
|
|
25
|
+
*
|
|
26
|
+
* Init-time only: the wasm module loads once per page (see `ensureWasm`), so
|
|
27
|
+
* changing `wasm` after the first successful init has no effect — it will not
|
|
28
|
+
* reload the module.
|
|
29
|
+
*/
|
|
30
|
+
wasm?: WasmSource;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface BojtosControls {
|
|
34
|
+
phase: BojtosPhase;
|
|
35
|
+
error: string | null;
|
|
36
|
+
/** Deployable process ids from the current deployment. */
|
|
37
|
+
processIds: string[];
|
|
38
|
+
/** The latest snapshot, or `null` before the first command / after a reset. */
|
|
39
|
+
snapshot: Snapshot | null;
|
|
40
|
+
/** The engine's full event log after the latest command. */
|
|
41
|
+
events: WasmEvent[];
|
|
42
|
+
/** Start an instance; returns the post-run snapshot (with `created`) or null. */
|
|
43
|
+
createInstance(processId: string, variablesJson: string): Snapshot | null;
|
|
44
|
+
/** Complete a waiting job, merging output variables. */
|
|
45
|
+
completeJob(jobKey: string, variablesJson: string): Snapshot | null;
|
|
46
|
+
/** Fail a waiting job (raises an incident with no retries left). */
|
|
47
|
+
failJob(jobKey: string, retries: number, message: string): Snapshot | null;
|
|
48
|
+
/**
|
|
49
|
+
* Correlate a message to an instance parked at a message catch/receive:
|
|
50
|
+
* publishes `messageName` with `correlationKey` and merges `variablesJson`.
|
|
51
|
+
* The in-browser equivalent of an app publishing a message — used to unblock
|
|
52
|
+
* a waiting loop (e.g. urban-pr-review's `review-ready`).
|
|
53
|
+
*/
|
|
54
|
+
correlateMessage(
|
|
55
|
+
messageName: string,
|
|
56
|
+
correlationKey: string,
|
|
57
|
+
variablesJson: string,
|
|
58
|
+
): Snapshot | null;
|
|
59
|
+
/** Advance the virtual clock. */
|
|
60
|
+
advanceTime(byMs: number): Snapshot | null;
|
|
61
|
+
/**
|
|
62
|
+
* Run the registered worker handlers until the process settles (activate →
|
|
63
|
+
* handler → complete/fail), then reflect the resulting snapshot/events.
|
|
64
|
+
* Resolves to the settled snapshot, or null if there is no live session.
|
|
65
|
+
*/
|
|
66
|
+
runWorkers(
|
|
67
|
+
workers: Record<string, JobHandler>,
|
|
68
|
+
opts?: DispatchOptions,
|
|
69
|
+
): Promise<Snapshot | null>;
|
|
70
|
+
/**
|
|
71
|
+
* Run a single activate-and-handle pass of the registered workers (one
|
|
72
|
+
* {@link dispatchRound}), reflecting the resulting snapshot/events. Returns
|
|
73
|
+
* how many jobs it handled (0 once the process is quiescent) plus the
|
|
74
|
+
* snapshot, or null if there is no live session — drive it on a timer to
|
|
75
|
+
* animate the token advancing one step at a time.
|
|
76
|
+
*/
|
|
77
|
+
stepWorkers(
|
|
78
|
+
workers: Record<string, JobHandler>,
|
|
79
|
+
opts?: DispatchOptions,
|
|
80
|
+
): Promise<RoundResult | null>;
|
|
81
|
+
/** Re-deploy the diagram on the existing engine, clearing run state. */
|
|
82
|
+
reset(): void;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* React binding over a headless {@link BojtosSession}: owns the engine's
|
|
87
|
+
* lifecycle and the reactive `snapshot` / `events` / `processIds` state, and
|
|
88
|
+
* exposes the engine commands. The consuming component owns its own form state
|
|
89
|
+
* (selected process, seed vars, per-job output) and drives the visual contract
|
|
90
|
+
* (`<BpmnRuntimeView>` + the variable payload) off `snapshot`.
|
|
91
|
+
*
|
|
92
|
+
* This is the reactive half of the Bojtos public API (ADR 0043 §2); the console
|
|
93
|
+
* test-run panel is its first consumer (§8 step 2 — dogfooding is the acceptance
|
|
94
|
+
* test).
|
|
95
|
+
*/
|
|
96
|
+
export function useBojtos({ bpmn, wasm }: UseBojtosOptions): BojtosControls {
|
|
97
|
+
const sessionRef = useRef<BojtosSession | null>(null);
|
|
98
|
+
const [phase, setPhase] = useState<BojtosPhase>("loading");
|
|
99
|
+
const [error, setError] = useState<string | null>(null);
|
|
100
|
+
const [processIds, setProcessIds] = useState<string[]>([]);
|
|
101
|
+
const [snapshot, setSnapshot] = useState<Snapshot | null>(null);
|
|
102
|
+
const [events, setEvents] = useState<WasmEvent[]>([]);
|
|
103
|
+
|
|
104
|
+
// The wasm source is an init-time concern (the first `ensureWasm` wins), so
|
|
105
|
+
// keep it in a ref rather than the mount effect's deps — a fresh URL/bytes
|
|
106
|
+
// identity each render must not re-create the session.
|
|
107
|
+
const wasmRef = useRef(wasm);
|
|
108
|
+
wasmRef.current = wasm;
|
|
109
|
+
|
|
110
|
+
const deployInto = useCallback(
|
|
111
|
+
(session: BojtosSession) => {
|
|
112
|
+
const res = session.deploy(bpmn);
|
|
113
|
+
setProcessIds(res.processIds);
|
|
114
|
+
setSnapshot(null);
|
|
115
|
+
setEvents([]);
|
|
116
|
+
setError(null);
|
|
117
|
+
},
|
|
118
|
+
[bpmn],
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
let cancelled = false;
|
|
123
|
+
// A new diagram means a fresh engine: drop back to `loading` and clear the
|
|
124
|
+
// previous session's state — including `processIds` — so consumers never
|
|
125
|
+
// see `ready` (or a stale process list) against a freed session while the
|
|
126
|
+
// new one is still loading.
|
|
127
|
+
setPhase("loading");
|
|
128
|
+
setProcessIds([]);
|
|
129
|
+
setSnapshot(null);
|
|
130
|
+
setEvents([]);
|
|
131
|
+
setError(null);
|
|
132
|
+
createBojtosSession({ wasm: wasmRef.current })
|
|
133
|
+
.then((session) => {
|
|
134
|
+
if (cancelled) {
|
|
135
|
+
session.free();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
deployInto(session);
|
|
140
|
+
} catch (e) {
|
|
141
|
+
// A failed deploy (e.g. invalid BPMN) must free the just-created
|
|
142
|
+
// engine rather than leak it until unmount, and must not be stored
|
|
143
|
+
// as the active session.
|
|
144
|
+
session.free();
|
|
145
|
+
setError(String(e));
|
|
146
|
+
setPhase("error");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
sessionRef.current = session;
|
|
150
|
+
setPhase("ready");
|
|
151
|
+
})
|
|
152
|
+
.catch((e) => {
|
|
153
|
+
if (cancelled) return;
|
|
154
|
+
setError(String(e));
|
|
155
|
+
setPhase("error");
|
|
156
|
+
});
|
|
157
|
+
return () => {
|
|
158
|
+
cancelled = true;
|
|
159
|
+
sessionRef.current?.free();
|
|
160
|
+
sessionRef.current = null;
|
|
161
|
+
};
|
|
162
|
+
}, [deployInto]);
|
|
163
|
+
|
|
164
|
+
const run = useCallback(
|
|
165
|
+
(fn: (s: BojtosSession) => Snapshot): Snapshot | null => {
|
|
166
|
+
const session = sessionRef.current;
|
|
167
|
+
if (!session) return null;
|
|
168
|
+
try {
|
|
169
|
+
const snap = fn(session);
|
|
170
|
+
setSnapshot(snap);
|
|
171
|
+
setEvents(session.events());
|
|
172
|
+
setError(null);
|
|
173
|
+
return snap;
|
|
174
|
+
} catch (e) {
|
|
175
|
+
setError(String(e));
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
[],
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const createInstance = useCallback(
|
|
183
|
+
(processId: string, variablesJson: string) =>
|
|
184
|
+
run((s) => s.createInstance(processId, variablesJson)),
|
|
185
|
+
[run],
|
|
186
|
+
);
|
|
187
|
+
const completeJob = useCallback(
|
|
188
|
+
(jobKey: string, variablesJson: string) =>
|
|
189
|
+
run((s) => s.completeJob(jobKey, variablesJson)),
|
|
190
|
+
[run],
|
|
191
|
+
);
|
|
192
|
+
const failJob = useCallback(
|
|
193
|
+
(jobKey: string, retries: number, message: string) =>
|
|
194
|
+
run((s) => s.failJob(jobKey, retries, message)),
|
|
195
|
+
[run],
|
|
196
|
+
);
|
|
197
|
+
const advanceTime = useCallback(
|
|
198
|
+
(byMs: number) => run((s) => s.advanceTime(byMs)),
|
|
199
|
+
[run],
|
|
200
|
+
);
|
|
201
|
+
const correlateMessage = useCallback(
|
|
202
|
+
(messageName: string, correlationKey: string, variablesJson: string) =>
|
|
203
|
+
run((s) => s.correlateMessage(messageName, correlationKey, variablesJson)),
|
|
204
|
+
[run],
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
const runWorkers = useCallback(
|
|
208
|
+
async (
|
|
209
|
+
workers: Record<string, JobHandler>,
|
|
210
|
+
opts?: DispatchOptions,
|
|
211
|
+
): Promise<Snapshot | null> => {
|
|
212
|
+
const session = sessionRef.current;
|
|
213
|
+
if (!session) return null;
|
|
214
|
+
try {
|
|
215
|
+
const { snapshot: settled } = await dispatchWorkers(
|
|
216
|
+
session,
|
|
217
|
+
workers,
|
|
218
|
+
opts,
|
|
219
|
+
);
|
|
220
|
+
// The session may have been torn down/replaced (bpmn change, unmount)
|
|
221
|
+
// while we awaited — don't publish stale state or read a freed session.
|
|
222
|
+
if (sessionRef.current !== session) return null;
|
|
223
|
+
setSnapshot(settled);
|
|
224
|
+
setEvents(session.events());
|
|
225
|
+
setError(null);
|
|
226
|
+
return settled;
|
|
227
|
+
} catch (e) {
|
|
228
|
+
if (sessionRef.current !== session) return null;
|
|
229
|
+
// Reflect whatever state the engine reached before the drain aborted
|
|
230
|
+
// (e.g. the maxRounds guard) so the view isn't left stale.
|
|
231
|
+
setSnapshot(session.snapshot());
|
|
232
|
+
setEvents(session.events());
|
|
233
|
+
setError(String(e));
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
[],
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
const stepWorkers = useCallback(
|
|
241
|
+
async (
|
|
242
|
+
workers: Record<string, JobHandler>,
|
|
243
|
+
opts?: DispatchOptions,
|
|
244
|
+
): Promise<RoundResult | null> => {
|
|
245
|
+
const session = sessionRef.current;
|
|
246
|
+
if (!session) return null;
|
|
247
|
+
try {
|
|
248
|
+
const round = await dispatchRound(session, workers, opts);
|
|
249
|
+
// Bail if the session was replaced/freed while we awaited the round.
|
|
250
|
+
if (sessionRef.current !== session) return null;
|
|
251
|
+
setSnapshot(round.snapshot);
|
|
252
|
+
setEvents(session.events());
|
|
253
|
+
setError(null);
|
|
254
|
+
return round;
|
|
255
|
+
} catch (e) {
|
|
256
|
+
if (sessionRef.current !== session) return null;
|
|
257
|
+
setSnapshot(session.snapshot());
|
|
258
|
+
setEvents(session.events());
|
|
259
|
+
setError(String(e));
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
[],
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
const reset = useCallback(() => {
|
|
267
|
+
const session = sessionRef.current;
|
|
268
|
+
if (!session) return;
|
|
269
|
+
try {
|
|
270
|
+
// Wipe the engine to its pristine state, then redeploy the diagram, so a
|
|
271
|
+
// re-run starts from zero instances/completions rather than accumulating
|
|
272
|
+
// across runs (a plain redeploy leaves prior instances resident).
|
|
273
|
+
session.reset();
|
|
274
|
+
deployInto(session);
|
|
275
|
+
} catch (e) {
|
|
276
|
+
setError(String(e));
|
|
277
|
+
}
|
|
278
|
+
}, [deployInto]);
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
phase,
|
|
282
|
+
error,
|
|
283
|
+
processIds,
|
|
284
|
+
snapshot,
|
|
285
|
+
events,
|
|
286
|
+
createInstance,
|
|
287
|
+
completeJob,
|
|
288
|
+
failJob,
|
|
289
|
+
advanceTime,
|
|
290
|
+
correlateMessage,
|
|
291
|
+
runWorkers,
|
|
292
|
+
stepWorkers,
|
|
293
|
+
reset,
|
|
294
|
+
};
|
|
295
|
+
}
|