@bpmnkit/core 0.0.23 → 0.0.25
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/dist/bpmn/auto-layout.js +1 -131
- package/dist/bpmn/bpmn-builder.d.ts +142 -19
- package/dist/bpmn/bpmn-builder.js +736 -405
- package/dist/bpmn/bpmn-model.d.ts +2 -0
- package/dist/bpmn/bpmn-parser.js +16 -2
- package/dist/bpmn/bpmn-serializer.js +6 -0
- package/dist/bpmn/index.d.ts +15 -1
- package/dist/bpmn/index.js +17 -1
- package/dist/bpmn/zeebe-extensions.d.ts +2 -0
- package/dist/bpmn/zeebe-extensions.js +3 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/layout/layout-engine.d.ts +2 -1
- package/dist/layout/layout-engine.js +130 -1
- package/dist/layout/overlap.js +3 -0
- package/package.json +2 -2
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { layoutProcess } from "../layout/layout-engine.js";
|
|
2
1
|
import { generateId } from "../types/id-generator.js";
|
|
2
|
+
import { applyAutoLayout } from "./auto-layout.js";
|
|
3
3
|
import { restConnectorRetries, restConnectorTaskType, restConnectorToIoMappingInputs, restConnectorToTaskHeaders, } from "./rest-connector.js";
|
|
4
4
|
import { zeebeExtensionsToXmlElements } from "./zeebe-extensions.js";
|
|
5
|
+
// Keep in sync with packages/core/package.json version
|
|
6
|
+
const EXPORTER_VERSION = "0.0.23";
|
|
5
7
|
// ---------------------------------------------------------------------------
|
|
6
8
|
// Internal helpers
|
|
7
9
|
// ---------------------------------------------------------------------------
|
|
8
|
-
function buildEventDefinitions(opts, rootErrors, rootMessages) {
|
|
10
|
+
function buildEventDefinitions(opts, rootErrors, rootMessages, rootSignals, rootEscalations) {
|
|
9
11
|
const defs = [];
|
|
10
12
|
if (opts.timerDuration || opts.timerDate || opts.timerCycle) {
|
|
11
13
|
defs.push({
|
|
@@ -16,28 +18,63 @@ function buildEventDefinitions(opts, rootErrors, rootMessages) {
|
|
|
16
18
|
});
|
|
17
19
|
}
|
|
18
20
|
if (opts.errorCode !== undefined || opts.errorRef !== undefined) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
rootErrors.
|
|
23
|
-
|
|
21
|
+
const codeOrRef = opts.errorCode ?? opts.errorRef;
|
|
22
|
+
let errorRef;
|
|
23
|
+
if (codeOrRef !== undefined && rootErrors) {
|
|
24
|
+
let existing = rootErrors.find((e) => e.errorCode === codeOrRef || e.name === codeOrRef);
|
|
25
|
+
if (!existing) {
|
|
26
|
+
existing = { id: generateId("Error"), name: codeOrRef, errorCode: codeOrRef };
|
|
27
|
+
rootErrors.push(existing);
|
|
28
|
+
}
|
|
29
|
+
errorRef = existing.id;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
errorRef = codeOrRef;
|
|
24
33
|
}
|
|
25
34
|
defs.push({ type: "error", errorRef });
|
|
26
35
|
}
|
|
27
36
|
if (opts.messageName !== undefined) {
|
|
28
37
|
let messageRef = opts.messageName;
|
|
29
38
|
if (rootMessages) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
39
|
+
let existing = rootMessages.find((m) => m.name === opts.messageName);
|
|
40
|
+
if (!existing) {
|
|
41
|
+
existing = { id: generateId("Message"), name: opts.messageName, unknownAttributes: {} };
|
|
42
|
+
rootMessages.push(existing);
|
|
43
|
+
}
|
|
44
|
+
messageRef = existing.id;
|
|
33
45
|
}
|
|
34
46
|
defs.push({ type: "message", messageRef });
|
|
35
47
|
}
|
|
36
48
|
if (opts.signalName !== undefined) {
|
|
37
|
-
|
|
49
|
+
let signalRef = opts.signalName;
|
|
50
|
+
if (rootSignals) {
|
|
51
|
+
let existing = rootSignals.find((s) => s.name === opts.signalName);
|
|
52
|
+
if (!existing) {
|
|
53
|
+
existing = { id: generateId("Signal"), name: opts.signalName };
|
|
54
|
+
rootSignals.push(existing);
|
|
55
|
+
}
|
|
56
|
+
signalRef = existing.id;
|
|
57
|
+
}
|
|
58
|
+
defs.push({ type: "signal", signalRef });
|
|
38
59
|
}
|
|
39
60
|
if (opts.escalationCode !== undefined) {
|
|
40
|
-
|
|
61
|
+
let escalationRef = opts.escalationCode;
|
|
62
|
+
if (rootEscalations) {
|
|
63
|
+
let existing = rootEscalations.find((e) => e.escalationCode === opts.escalationCode);
|
|
64
|
+
if (!existing) {
|
|
65
|
+
existing = {
|
|
66
|
+
id: generateId("Escalation"),
|
|
67
|
+
name: opts.escalationCode,
|
|
68
|
+
escalationCode: opts.escalationCode,
|
|
69
|
+
};
|
|
70
|
+
rootEscalations.push(existing);
|
|
71
|
+
}
|
|
72
|
+
escalationRef = existing.id;
|
|
73
|
+
}
|
|
74
|
+
defs.push({ type: "escalation", escalationRef });
|
|
75
|
+
}
|
|
76
|
+
if (opts.compensation) {
|
|
77
|
+
defs.push({ type: "compensate", activityRef: opts.activityRef });
|
|
41
78
|
}
|
|
42
79
|
return defs;
|
|
43
80
|
}
|
|
@@ -155,46 +192,6 @@ function buildAdHocLoopCharacteristics(lc) {
|
|
|
155
192
|
],
|
|
156
193
|
};
|
|
157
194
|
}
|
|
158
|
-
/** Convert a layout engine result into a BPMN diagram interchange structure. */
|
|
159
|
-
function layoutResultToDiagram(processId, layout) {
|
|
160
|
-
const shapes = layout.nodes.map((node) => {
|
|
161
|
-
const shape = {
|
|
162
|
-
id: `${node.id}_di`,
|
|
163
|
-
bpmnElement: node.id,
|
|
164
|
-
bounds: { ...node.bounds },
|
|
165
|
-
unknownAttributes: {},
|
|
166
|
-
};
|
|
167
|
-
if (node.isExpanded !== undefined) {
|
|
168
|
-
shape.isExpanded = node.isExpanded;
|
|
169
|
-
}
|
|
170
|
-
if (node.labelBounds) {
|
|
171
|
-
shape.label = { bounds: { ...node.labelBounds } };
|
|
172
|
-
}
|
|
173
|
-
return shape;
|
|
174
|
-
});
|
|
175
|
-
const edges = layout.edges.map((edge) => {
|
|
176
|
-
const diEdge = {
|
|
177
|
-
id: `${edge.id}_di`,
|
|
178
|
-
bpmnElement: edge.id,
|
|
179
|
-
waypoints: edge.waypoints.map((wp) => ({ ...wp })),
|
|
180
|
-
unknownAttributes: {},
|
|
181
|
-
};
|
|
182
|
-
if (edge.labelBounds) {
|
|
183
|
-
diEdge.label = { bounds: { ...edge.labelBounds } };
|
|
184
|
-
}
|
|
185
|
-
return diEdge;
|
|
186
|
-
});
|
|
187
|
-
const plane = {
|
|
188
|
-
id: `${processId}_di_plane`,
|
|
189
|
-
bpmnElement: processId,
|
|
190
|
-
shapes,
|
|
191
|
-
edges,
|
|
192
|
-
};
|
|
193
|
-
return {
|
|
194
|
-
id: `${processId}_di`,
|
|
195
|
-
plane,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
195
|
function recomputeIncomingOutgoing(elements, flows) {
|
|
199
196
|
for (const el of elements) {
|
|
200
197
|
el.incoming = [];
|
|
@@ -236,6 +233,200 @@ function makeConditionExpression(expression) {
|
|
|
236
233
|
};
|
|
237
234
|
}
|
|
238
235
|
// ---------------------------------------------------------------------------
|
|
236
|
+
// Element factory functions — shared by all three builder classes
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
function makeServiceTaskEl(id, options) {
|
|
239
|
+
const unknownAttributes = {};
|
|
240
|
+
if (options.modelerTemplate)
|
|
241
|
+
unknownAttributes["zeebe:modelerTemplate"] = options.modelerTemplate;
|
|
242
|
+
if (options.modelerTemplateVersion)
|
|
243
|
+
unknownAttributes["zeebe:modelerTemplateVersion"] = options.modelerTemplateVersion;
|
|
244
|
+
if (options.modelerTemplateIcon)
|
|
245
|
+
unknownAttributes["zeebe:modelerTemplateIcon"] = options.modelerTemplateIcon;
|
|
246
|
+
const el = makeFlowElement(id, "serviceTask", {
|
|
247
|
+
name: options.name,
|
|
248
|
+
extensionElements: buildServiceTaskExtensions(options),
|
|
249
|
+
});
|
|
250
|
+
el.unknownAttributes = unknownAttributes;
|
|
251
|
+
if (options.isForCompensation)
|
|
252
|
+
el.isForCompensation = true;
|
|
253
|
+
return el;
|
|
254
|
+
}
|
|
255
|
+
function makeScriptTaskEl(id, options) {
|
|
256
|
+
const el = makeFlowElement(id, "scriptTask", {
|
|
257
|
+
name: options.name,
|
|
258
|
+
extensionElements: zeebeExtensionsToXmlElements({
|
|
259
|
+
unknownElements: [
|
|
260
|
+
{
|
|
261
|
+
name: "zeebe:script",
|
|
262
|
+
attributes: { expression: options.expression, resultVariable: options.resultVariable },
|
|
263
|
+
children: [],
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
}),
|
|
267
|
+
});
|
|
268
|
+
if (options.isForCompensation)
|
|
269
|
+
el.isForCompensation = true;
|
|
270
|
+
return el;
|
|
271
|
+
}
|
|
272
|
+
function makeUserTaskEl(id, options) {
|
|
273
|
+
const ext = zeebeExtensionsToXmlElements({
|
|
274
|
+
...(options?.zeebeUserTask ? { userTask: true } : {}),
|
|
275
|
+
...(options?.formId ? { formDefinition: { formId: options.formId } } : {}),
|
|
276
|
+
});
|
|
277
|
+
const el = makeFlowElement(id, "userTask", { name: options?.name, extensionElements: ext });
|
|
278
|
+
if (options?.isForCompensation)
|
|
279
|
+
el.isForCompensation = true;
|
|
280
|
+
return el;
|
|
281
|
+
}
|
|
282
|
+
function makeBusinessRuleTaskEl(id, options) {
|
|
283
|
+
const ext = [];
|
|
284
|
+
if (options?.taskType) {
|
|
285
|
+
ext.push(...zeebeExtensionsToXmlElements({ taskDefinition: { type: options.taskType } }));
|
|
286
|
+
}
|
|
287
|
+
if (options?.decisionId) {
|
|
288
|
+
ext.push(...zeebeExtensionsToXmlElements({
|
|
289
|
+
calledDecision: {
|
|
290
|
+
decisionId: options.decisionId,
|
|
291
|
+
resultVariable: options.resultVariable ?? "result",
|
|
292
|
+
},
|
|
293
|
+
}));
|
|
294
|
+
}
|
|
295
|
+
const el = makeFlowElement(id, "businessRuleTask", {
|
|
296
|
+
name: options?.name,
|
|
297
|
+
extensionElements: ext,
|
|
298
|
+
});
|
|
299
|
+
if (options?.isForCompensation)
|
|
300
|
+
el.isForCompensation = true;
|
|
301
|
+
return el;
|
|
302
|
+
}
|
|
303
|
+
function makeCallActivityEl(id, options) {
|
|
304
|
+
const attrs = { processId: options.processId };
|
|
305
|
+
if (options.propagateAllChildVariables !== undefined) {
|
|
306
|
+
attrs.propagateAllChildVariables = String(options.propagateAllChildVariables);
|
|
307
|
+
}
|
|
308
|
+
const el = makeFlowElement(id, "callActivity", {
|
|
309
|
+
name: options.name,
|
|
310
|
+
extensionElements: zeebeExtensionsToXmlElements({
|
|
311
|
+
unknownElements: [{ name: "zeebe:calledElement", attributes: attrs, children: [] }],
|
|
312
|
+
}),
|
|
313
|
+
});
|
|
314
|
+
if (options.isForCompensation)
|
|
315
|
+
el.isForCompensation = true;
|
|
316
|
+
return el;
|
|
317
|
+
}
|
|
318
|
+
function makeExclusiveGatewayEl(id, options) {
|
|
319
|
+
const el = makeFlowElement(id, "exclusiveGateway", options);
|
|
320
|
+
if (options?.defaultFlow && el.type === "exclusiveGateway") {
|
|
321
|
+
;
|
|
322
|
+
el.default = options.defaultFlow;
|
|
323
|
+
}
|
|
324
|
+
return el;
|
|
325
|
+
}
|
|
326
|
+
function makeInclusiveGatewayEl(id, options) {
|
|
327
|
+
const el = makeFlowElement(id, "inclusiveGateway", options);
|
|
328
|
+
if (options?.defaultFlow && el.type === "inclusiveGateway") {
|
|
329
|
+
;
|
|
330
|
+
el.default = options.defaultFlow;
|
|
331
|
+
}
|
|
332
|
+
return el;
|
|
333
|
+
}
|
|
334
|
+
// ---------------------------------------------------------------------------
|
|
335
|
+
// Shared graph helpers — used by ProcessBuilder and SubProcessContentBuilder
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
function insertJoinGateways(elements, flows) {
|
|
338
|
+
const GATEWAY_TYPES = new Set([
|
|
339
|
+
"exclusiveGateway",
|
|
340
|
+
"parallelGateway",
|
|
341
|
+
"inclusiveGateway",
|
|
342
|
+
"eventBasedGateway",
|
|
343
|
+
]);
|
|
344
|
+
const elementTypes = new Map();
|
|
345
|
+
for (const el of elements)
|
|
346
|
+
elementTypes.set(el.id, el.type);
|
|
347
|
+
const outCount = new Map();
|
|
348
|
+
for (const flow of flows) {
|
|
349
|
+
outCount.set(flow.sourceRef, (outCount.get(flow.sourceRef) ?? 0) + 1);
|
|
350
|
+
}
|
|
351
|
+
const splitGateways = new Set();
|
|
352
|
+
for (const [id, count] of outCount) {
|
|
353
|
+
const type = elementTypes.get(id);
|
|
354
|
+
if (type && GATEWAY_TYPES.has(type) && count >= 2)
|
|
355
|
+
splitGateways.add(id);
|
|
356
|
+
}
|
|
357
|
+
if (splitGateways.size === 0)
|
|
358
|
+
return;
|
|
359
|
+
const incoming = new Map();
|
|
360
|
+
for (const flow of flows) {
|
|
361
|
+
const arr = incoming.get(flow.targetRef);
|
|
362
|
+
if (arr)
|
|
363
|
+
arr.push(flow);
|
|
364
|
+
else
|
|
365
|
+
incoming.set(flow.targetRef, [flow]);
|
|
366
|
+
}
|
|
367
|
+
for (const [targetId, inFlows] of incoming) {
|
|
368
|
+
if (inFlows.length < 2)
|
|
369
|
+
continue;
|
|
370
|
+
const splitToFlows = new Map();
|
|
371
|
+
for (const flow of inFlows) {
|
|
372
|
+
const split = traceBackToSplit(flow.sourceRef, splitGateways, flows);
|
|
373
|
+
if (split) {
|
|
374
|
+
const arr = splitToFlows.get(split);
|
|
375
|
+
if (arr)
|
|
376
|
+
arr.push(flow);
|
|
377
|
+
else
|
|
378
|
+
splitToFlows.set(split, [flow]);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
for (const [splitId, convergingFlows] of splitToFlows) {
|
|
382
|
+
if (convergingFlows.length < 2)
|
|
383
|
+
continue;
|
|
384
|
+
const gwType = elementTypes.get(splitId);
|
|
385
|
+
if (!gwType)
|
|
386
|
+
continue;
|
|
387
|
+
// eventBasedGateway is split-only; converge through an XOR join instead
|
|
388
|
+
const joinType = gwType === "eventBasedGateway" ? "exclusiveGateway" : gwType;
|
|
389
|
+
const targetType = elementTypes.get(targetId);
|
|
390
|
+
if (targetType === joinType)
|
|
391
|
+
continue;
|
|
392
|
+
const joinId = `${splitId}_join`;
|
|
393
|
+
if (elementTypes.has(joinId))
|
|
394
|
+
continue;
|
|
395
|
+
const joinElement = makeFlowElement(joinId, joinType, {});
|
|
396
|
+
elements.push(joinElement);
|
|
397
|
+
elementTypes.set(joinId, joinType);
|
|
398
|
+
for (const flow of convergingFlows)
|
|
399
|
+
flow.targetRef = joinId;
|
|
400
|
+
flows.push({
|
|
401
|
+
id: generateId("Flow"),
|
|
402
|
+
sourceRef: joinId,
|
|
403
|
+
targetRef: targetId,
|
|
404
|
+
extensionElements: [],
|
|
405
|
+
unknownAttributes: {},
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
function traceBackToSplit(nodeId, splitGateways, flows) {
|
|
411
|
+
const visited = new Set();
|
|
412
|
+
let current = nodeId;
|
|
413
|
+
while (current) {
|
|
414
|
+
if (visited.has(current))
|
|
415
|
+
return undefined;
|
|
416
|
+
visited.add(current);
|
|
417
|
+
if (splitGateways.has(current))
|
|
418
|
+
return current;
|
|
419
|
+
const inFlows = flows.filter((f) => f.targetRef === current);
|
|
420
|
+
if (inFlows.length !== 1)
|
|
421
|
+
return undefined;
|
|
422
|
+
const prev = inFlows[0];
|
|
423
|
+
if (!prev)
|
|
424
|
+
return undefined;
|
|
425
|
+
current = prev.sourceRef;
|
|
426
|
+
}
|
|
427
|
+
return undefined;
|
|
428
|
+
}
|
|
429
|
+
// ---------------------------------------------------------------------------
|
|
239
430
|
// Branch builder (used inside gateway branch callbacks)
|
|
240
431
|
// ---------------------------------------------------------------------------
|
|
241
432
|
/**
|
|
@@ -260,10 +451,23 @@ export class BranchBuilder {
|
|
|
260
451
|
/** @internal – true once connectTo() has been called, meaning the branch end is already wired */
|
|
261
452
|
_connected = false;
|
|
262
453
|
/** @internal */
|
|
263
|
-
|
|
454
|
+
_textAnnotations = [];
|
|
455
|
+
/** @internal */
|
|
456
|
+
_associations = [];
|
|
457
|
+
_annCounters = new Map();
|
|
458
|
+
rootErrors;
|
|
459
|
+
rootMessages;
|
|
460
|
+
rootSignals;
|
|
461
|
+
rootEscalations;
|
|
462
|
+
/** @internal */
|
|
463
|
+
constructor(gatewayId, branchName, rootErrors = [], rootMessages = [], rootSignals = [], rootEscalations = []) {
|
|
264
464
|
this.gatewayId = gatewayId;
|
|
265
465
|
this.branchName = branchName;
|
|
266
466
|
this.lastNodeId = gatewayId;
|
|
467
|
+
this.rootErrors = rootErrors;
|
|
468
|
+
this.rootMessages = rootMessages;
|
|
469
|
+
this.rootSignals = rootSignals;
|
|
470
|
+
this.rootEscalations = rootEscalations;
|
|
267
471
|
}
|
|
268
472
|
/** Set a FEEL condition expression on this branch's outgoing sequence flow. */
|
|
269
473
|
condition(expression) {
|
|
@@ -327,129 +531,97 @@ export class BranchBuilder {
|
|
|
327
531
|
get _lastNodeId() {
|
|
328
532
|
return this.lastNodeId;
|
|
329
533
|
}
|
|
534
|
+
// ---- Annotations ----
|
|
535
|
+
/** Attach a text annotation to the element at the current cursor position. */
|
|
536
|
+
textAnnotation(text) {
|
|
537
|
+
return this.annotate(this.lastNodeId, text);
|
|
538
|
+
}
|
|
539
|
+
/** Attach a text annotation to an element by explicit ID. */
|
|
540
|
+
annotate(elementId, text) {
|
|
541
|
+
const n = (this._annCounters.get(elementId) ?? 0) + 1;
|
|
542
|
+
this._annCounters.set(elementId, n);
|
|
543
|
+
const annId = `TextAnnotation_${elementId}_${n}`;
|
|
544
|
+
this._textAnnotations.push({ id: annId, text, unknownAttributes: {} });
|
|
545
|
+
this._associations.push({
|
|
546
|
+
id: `Association_${elementId}_${n}`,
|
|
547
|
+
sourceRef: elementId,
|
|
548
|
+
targetRef: annId,
|
|
549
|
+
associationDirection: "None",
|
|
550
|
+
unknownAttributes: {},
|
|
551
|
+
});
|
|
552
|
+
return this;
|
|
553
|
+
}
|
|
330
554
|
// ---- Flow-node methods (mirror ProcessBuilder) ----
|
|
331
555
|
serviceTask(id, options) {
|
|
332
|
-
|
|
333
|
-
if (options.modelerTemplate)
|
|
334
|
-
unknownAttributes["zeebe:modelerTemplate"] = options.modelerTemplate;
|
|
335
|
-
if (options.modelerTemplateVersion)
|
|
336
|
-
unknownAttributes["zeebe:modelerTemplateVersion"] = options.modelerTemplateVersion;
|
|
337
|
-
if (options.modelerTemplateIcon)
|
|
338
|
-
unknownAttributes["zeebe:modelerTemplateIcon"] = options.modelerTemplateIcon;
|
|
339
|
-
const el = makeFlowElement(id, "serviceTask", {
|
|
340
|
-
name: options.name,
|
|
341
|
-
extensionElements: buildServiceTaskExtensions(options),
|
|
342
|
-
});
|
|
343
|
-
el.unknownAttributes = unknownAttributes;
|
|
344
|
-
return this.addElement(el);
|
|
556
|
+
return this.addElement(makeServiceTaskEl(id, options));
|
|
345
557
|
}
|
|
346
558
|
userTask(id, options) {
|
|
347
|
-
|
|
348
|
-
? zeebeExtensionsToXmlElements({ formDefinition: { formId: options.formId } })
|
|
349
|
-
: [];
|
|
350
|
-
return this.addElement(makeFlowElement(id, "userTask", {
|
|
351
|
-
name: options?.name,
|
|
352
|
-
extensionElements: ext,
|
|
353
|
-
}));
|
|
559
|
+
return this.addElement(makeUserTaskEl(id, options));
|
|
354
560
|
}
|
|
355
561
|
scriptTask(id, options) {
|
|
356
|
-
return this.addElement(
|
|
357
|
-
name: options.name,
|
|
358
|
-
extensionElements: zeebeExtensionsToXmlElements({
|
|
359
|
-
unknownElements: [
|
|
360
|
-
{
|
|
361
|
-
name: "zeebe:script",
|
|
362
|
-
attributes: {
|
|
363
|
-
expression: options.expression,
|
|
364
|
-
resultVariable: options.resultVariable,
|
|
365
|
-
},
|
|
366
|
-
children: [],
|
|
367
|
-
},
|
|
368
|
-
],
|
|
369
|
-
}),
|
|
370
|
-
}));
|
|
562
|
+
return this.addElement(makeScriptTaskEl(id, options));
|
|
371
563
|
}
|
|
372
564
|
sendTask(id, options) {
|
|
373
|
-
|
|
565
|
+
const el = makeFlowElement(id, "sendTask", options);
|
|
566
|
+
if (options?.isForCompensation)
|
|
567
|
+
el.isForCompensation = true;
|
|
568
|
+
return this.addElement(el);
|
|
374
569
|
}
|
|
375
570
|
receiveTask(id, options) {
|
|
376
|
-
|
|
571
|
+
const el = makeFlowElement(id, "receiveTask", options);
|
|
572
|
+
if (options?.isForCompensation)
|
|
573
|
+
el.isForCompensation = true;
|
|
574
|
+
return this.addElement(el);
|
|
377
575
|
}
|
|
378
576
|
businessRuleTask(id, options) {
|
|
379
|
-
|
|
380
|
-
? zeebeExtensionsToXmlElements({
|
|
381
|
-
calledDecision: {
|
|
382
|
-
decisionId: options.decisionId,
|
|
383
|
-
resultVariable: options.resultVariable ?? "result",
|
|
384
|
-
},
|
|
385
|
-
})
|
|
386
|
-
: [];
|
|
387
|
-
return this.addElement(makeFlowElement(id, "businessRuleTask", {
|
|
388
|
-
name: options?.name,
|
|
389
|
-
extensionElements: ext,
|
|
390
|
-
}));
|
|
577
|
+
return this.addElement(makeBusinessRuleTaskEl(id, options));
|
|
391
578
|
}
|
|
392
579
|
callActivity(id, options) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
{
|
|
402
|
-
name: "zeebe:calledElement",
|
|
403
|
-
attributes: attrs,
|
|
404
|
-
children: [],
|
|
405
|
-
},
|
|
406
|
-
],
|
|
407
|
-
}),
|
|
408
|
-
}));
|
|
580
|
+
return this.addElement(makeCallActivityEl(id, options));
|
|
581
|
+
}
|
|
582
|
+
/** Add an abstract task with no Zeebe extensions. */
|
|
583
|
+
task(id, options) {
|
|
584
|
+
const el = makeFlowElement(id, "task", options);
|
|
585
|
+
if (options?.isForCompensation)
|
|
586
|
+
el.isForCompensation = true;
|
|
587
|
+
return this.addElement(el);
|
|
409
588
|
}
|
|
410
589
|
startEvent(id, options) {
|
|
411
590
|
const el = makeFlowElement(id ?? generateId("StartEvent"), "startEvent", options);
|
|
412
|
-
if (el.type === "startEvent" &&
|
|
413
|
-
(options
|
|
414
|
-
el.eventDefinitions = buildEventDefinitions(options);
|
|
591
|
+
if (el.type === "startEvent" && options) {
|
|
592
|
+
el.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
|
|
415
593
|
}
|
|
416
594
|
return this.addElement(el);
|
|
417
595
|
}
|
|
418
596
|
endEvent(id, options) {
|
|
419
|
-
|
|
597
|
+
const el = makeFlowElement(id ?? generateId("EndEvent"), "endEvent", options);
|
|
598
|
+
if (el.type === "endEvent" && options) {
|
|
599
|
+
el.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
|
|
600
|
+
}
|
|
601
|
+
return this.addElement(el);
|
|
420
602
|
}
|
|
421
603
|
intermediateThrowEvent(id, options) {
|
|
422
604
|
const el = makeFlowElement(id ?? generateId("IntermediateThrowEvent"), "intermediateThrowEvent", options);
|
|
423
605
|
if (el.type === "intermediateThrowEvent" && options) {
|
|
424
|
-
el.eventDefinitions = buildEventDefinitions(options);
|
|
606
|
+
el.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
|
|
425
607
|
}
|
|
426
608
|
return this.addElement(el);
|
|
427
609
|
}
|
|
428
610
|
intermediateCatchEvent(id, options) {
|
|
429
611
|
const el = makeFlowElement(id ?? generateId("IntermediateCatchEvent"), "intermediateCatchEvent", options);
|
|
430
612
|
if (el.type === "intermediateCatchEvent" && options) {
|
|
431
|
-
el.eventDefinitions = buildEventDefinitions(options);
|
|
613
|
+
el.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
|
|
432
614
|
}
|
|
433
615
|
return this.addElement(el);
|
|
434
616
|
}
|
|
435
617
|
exclusiveGateway(id, options) {
|
|
436
|
-
|
|
437
|
-
if (options?.defaultFlow && el.type === "exclusiveGateway") {
|
|
438
|
-
;
|
|
439
|
-
el.default = options.defaultFlow;
|
|
440
|
-
}
|
|
441
|
-
return this.addElement(el);
|
|
618
|
+
return this.addElement(makeExclusiveGatewayEl(id, options));
|
|
442
619
|
}
|
|
443
620
|
parallelGateway(id, options) {
|
|
444
621
|
return this.addElement(makeFlowElement(id, "parallelGateway", options));
|
|
445
622
|
}
|
|
446
623
|
inclusiveGateway(id, options) {
|
|
447
|
-
|
|
448
|
-
if (options?.defaultFlow && el.type === "inclusiveGateway") {
|
|
449
|
-
;
|
|
450
|
-
el.default = options.defaultFlow;
|
|
451
|
-
}
|
|
452
|
-
return this.addElement(el);
|
|
624
|
+
return this.addElement(makeInclusiveGatewayEl(id, options));
|
|
453
625
|
}
|
|
454
626
|
eventBasedGateway(id, options) {
|
|
455
627
|
return this.addElement(makeFlowElement(id, "eventBasedGateway", options));
|
|
@@ -464,99 +636,199 @@ export class SubProcessContentBuilder {
|
|
|
464
636
|
_elements = [];
|
|
465
637
|
/** @internal */
|
|
466
638
|
_flows = [];
|
|
639
|
+
/** @internal */
|
|
640
|
+
_textAnnotations = [];
|
|
641
|
+
/** @internal */
|
|
642
|
+
_associations = [];
|
|
643
|
+
_annCounters = new Map();
|
|
467
644
|
lastNodeId;
|
|
645
|
+
currentGatewayId;
|
|
646
|
+
openBranchEnds = [];
|
|
468
647
|
addElement(element) {
|
|
648
|
+
if (this._elements.some((n) => n.id === element.id)) {
|
|
649
|
+
throw new Error(`Duplicate element ID "${element.id}" in sub-process`);
|
|
650
|
+
}
|
|
469
651
|
this._elements.push(element);
|
|
470
652
|
if (this.lastNodeId) {
|
|
471
|
-
const flowId = generateId("Flow");
|
|
472
653
|
this._flows.push({
|
|
473
|
-
id:
|
|
654
|
+
id: generateId("Flow"),
|
|
474
655
|
sourceRef: this.lastNodeId,
|
|
475
656
|
targetRef: element.id,
|
|
476
657
|
extensionElements: [],
|
|
477
658
|
unknownAttributes: {},
|
|
478
659
|
});
|
|
479
660
|
}
|
|
661
|
+
for (const branchEnd of this.openBranchEnds) {
|
|
662
|
+
this._flows.push({
|
|
663
|
+
id: generateId("Flow"),
|
|
664
|
+
sourceRef: branchEnd,
|
|
665
|
+
targetRef: element.id,
|
|
666
|
+
extensionElements: [],
|
|
667
|
+
unknownAttributes: {},
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
this.openBranchEnds = [];
|
|
480
671
|
this.lastNodeId = element.id;
|
|
481
672
|
return this;
|
|
482
673
|
}
|
|
674
|
+
// ---- Events ----
|
|
483
675
|
startEvent(id, options) {
|
|
484
676
|
const el = makeFlowElement(id ?? generateId("StartEvent"), "startEvent", options);
|
|
485
677
|
if (el.type === "startEvent" && options) {
|
|
486
678
|
el.eventDefinitions = buildEventDefinitions(options);
|
|
679
|
+
if (options.isInterrupting === false)
|
|
680
|
+
el.isInterrupting = false;
|
|
487
681
|
}
|
|
488
682
|
return this.addElement(el);
|
|
489
683
|
}
|
|
490
684
|
endEvent(id, options) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
const unknownAttributes = {};
|
|
495
|
-
if (options.modelerTemplate)
|
|
496
|
-
unknownAttributes["zeebe:modelerTemplate"] = options.modelerTemplate;
|
|
497
|
-
if (options.modelerTemplateVersion)
|
|
498
|
-
unknownAttributes["zeebe:modelerTemplateVersion"] = options.modelerTemplateVersion;
|
|
499
|
-
if (options.modelerTemplateIcon)
|
|
500
|
-
unknownAttributes["zeebe:modelerTemplateIcon"] = options.modelerTemplateIcon;
|
|
501
|
-
const el = makeFlowElement(id, "serviceTask", {
|
|
502
|
-
name: options.name,
|
|
503
|
-
extensionElements: buildServiceTaskExtensions(options),
|
|
504
|
-
});
|
|
505
|
-
el.unknownAttributes = unknownAttributes;
|
|
685
|
+
const el = makeFlowElement(id ?? generateId("EndEvent"), "endEvent", options);
|
|
686
|
+
if (el.type === "endEvent" && options)
|
|
687
|
+
el.eventDefinitions = buildEventDefinitions(options);
|
|
506
688
|
return this.addElement(el);
|
|
507
689
|
}
|
|
508
|
-
userTask(id, options) {
|
|
509
|
-
return this.addElement(makeFlowElement(id, "userTask", { name: options?.name }));
|
|
510
|
-
}
|
|
511
|
-
scriptTask(id, options) {
|
|
512
|
-
return this.addElement(makeFlowElement(id, "scriptTask", {
|
|
513
|
-
name: options.name,
|
|
514
|
-
extensionElements: zeebeExtensionsToXmlElements({
|
|
515
|
-
unknownElements: [
|
|
516
|
-
{
|
|
517
|
-
name: "zeebe:script",
|
|
518
|
-
attributes: {
|
|
519
|
-
expression: options.expression,
|
|
520
|
-
resultVariable: options.resultVariable,
|
|
521
|
-
},
|
|
522
|
-
children: [],
|
|
523
|
-
},
|
|
524
|
-
],
|
|
525
|
-
}),
|
|
526
|
-
}));
|
|
527
|
-
}
|
|
528
|
-
callActivity(id, options) {
|
|
529
|
-
const attrs = { processId: options.processId };
|
|
530
|
-
if (options.propagateAllChildVariables !== undefined) {
|
|
531
|
-
attrs.propagateAllChildVariables = String(options.propagateAllChildVariables);
|
|
532
|
-
}
|
|
533
|
-
return this.addElement(makeFlowElement(id, "callActivity", {
|
|
534
|
-
name: options.name,
|
|
535
|
-
extensionElements: zeebeExtensionsToXmlElements({
|
|
536
|
-
unknownElements: [
|
|
537
|
-
{
|
|
538
|
-
name: "zeebe:calledElement",
|
|
539
|
-
attributes: attrs,
|
|
540
|
-
children: [],
|
|
541
|
-
},
|
|
542
|
-
],
|
|
543
|
-
}),
|
|
544
|
-
}));
|
|
545
|
-
}
|
|
546
690
|
intermediateThrowEvent(id, options) {
|
|
547
691
|
const el = makeFlowElement(id ?? generateId("IntermediateThrowEvent"), "intermediateThrowEvent", options);
|
|
548
|
-
if (el.type === "intermediateThrowEvent" && options)
|
|
692
|
+
if (el.type === "intermediateThrowEvent" && options)
|
|
549
693
|
el.eventDefinitions = buildEventDefinitions(options);
|
|
550
|
-
}
|
|
551
694
|
return this.addElement(el);
|
|
552
695
|
}
|
|
553
696
|
intermediateCatchEvent(id, options) {
|
|
554
697
|
const el = makeFlowElement(id ?? generateId("IntermediateCatchEvent"), "intermediateCatchEvent", options);
|
|
555
|
-
if (el.type === "intermediateCatchEvent" && options)
|
|
698
|
+
if (el.type === "intermediateCatchEvent" && options)
|
|
556
699
|
el.eventDefinitions = buildEventDefinitions(options);
|
|
557
|
-
}
|
|
558
700
|
return this.addElement(el);
|
|
559
701
|
}
|
|
702
|
+
// ---- Tasks ----
|
|
703
|
+
serviceTask(id, options) {
|
|
704
|
+
return this.addElement(makeServiceTaskEl(id, options));
|
|
705
|
+
}
|
|
706
|
+
scriptTask(id, options) {
|
|
707
|
+
return this.addElement(makeScriptTaskEl(id, options));
|
|
708
|
+
}
|
|
709
|
+
userTask(id, options) {
|
|
710
|
+
return this.addElement(makeUserTaskEl(id, options));
|
|
711
|
+
}
|
|
712
|
+
businessRuleTask(id, options) {
|
|
713
|
+
return this.addElement(makeBusinessRuleTaskEl(id, options));
|
|
714
|
+
}
|
|
715
|
+
callActivity(id, options) {
|
|
716
|
+
return this.addElement(makeCallActivityEl(id, options));
|
|
717
|
+
}
|
|
718
|
+
sendTask(id, options) {
|
|
719
|
+
const el = makeFlowElement(id, "sendTask", options);
|
|
720
|
+
if (options?.isForCompensation)
|
|
721
|
+
el.isForCompensation = true;
|
|
722
|
+
return this.addElement(el);
|
|
723
|
+
}
|
|
724
|
+
receiveTask(id, options) {
|
|
725
|
+
const el = makeFlowElement(id, "receiveTask", options);
|
|
726
|
+
if (options?.isForCompensation)
|
|
727
|
+
el.isForCompensation = true;
|
|
728
|
+
return this.addElement(el);
|
|
729
|
+
}
|
|
730
|
+
/** Add an abstract task with no Zeebe extensions. */
|
|
731
|
+
task(id, options) {
|
|
732
|
+
const el = makeFlowElement(id, "task", options);
|
|
733
|
+
if (options?.isForCompensation)
|
|
734
|
+
el.isForCompensation = true;
|
|
735
|
+
return this.addElement(el);
|
|
736
|
+
}
|
|
737
|
+
// ---- Gateways ----
|
|
738
|
+
exclusiveGateway(id, options) {
|
|
739
|
+
this.currentGatewayId = id;
|
|
740
|
+
return this.addElement(makeExclusiveGatewayEl(id, options));
|
|
741
|
+
}
|
|
742
|
+
parallelGateway(id, options) {
|
|
743
|
+
this.currentGatewayId = id;
|
|
744
|
+
return this.addElement(makeFlowElement(id, "parallelGateway", options));
|
|
745
|
+
}
|
|
746
|
+
inclusiveGateway(id, options) {
|
|
747
|
+
this.currentGatewayId = id;
|
|
748
|
+
return this.addElement(makeInclusiveGatewayEl(id, options));
|
|
749
|
+
}
|
|
750
|
+
eventBasedGateway(id, options) {
|
|
751
|
+
this.currentGatewayId = id;
|
|
752
|
+
return this.addElement(makeFlowElement(id, "eventBasedGateway", options));
|
|
753
|
+
}
|
|
754
|
+
// ---- Annotations ----
|
|
755
|
+
/** Attach a text annotation to the element at the current cursor position. */
|
|
756
|
+
textAnnotation(text) {
|
|
757
|
+
if (!this.lastNodeId) {
|
|
758
|
+
throw new Error("textAnnotation() must follow a flow element");
|
|
759
|
+
}
|
|
760
|
+
return this.annotate(this.lastNodeId, text);
|
|
761
|
+
}
|
|
762
|
+
/** Attach a text annotation to an element by explicit ID. */
|
|
763
|
+
annotate(elementId, text) {
|
|
764
|
+
const n = (this._annCounters.get(elementId) ?? 0) + 1;
|
|
765
|
+
this._annCounters.set(elementId, n);
|
|
766
|
+
const annId = `TextAnnotation_${elementId}_${n}`;
|
|
767
|
+
this._textAnnotations.push({ id: annId, text, unknownAttributes: {} });
|
|
768
|
+
this._associations.push({
|
|
769
|
+
id: `Association_${elementId}_${n}`,
|
|
770
|
+
sourceRef: elementId,
|
|
771
|
+
targetRef: annId,
|
|
772
|
+
associationDirection: "None",
|
|
773
|
+
unknownAttributes: {},
|
|
774
|
+
});
|
|
775
|
+
return this;
|
|
776
|
+
}
|
|
777
|
+
// ---- Branching & flow control ----
|
|
778
|
+
branch(name, callback) {
|
|
779
|
+
if (!this.currentGatewayId) {
|
|
780
|
+
throw new Error("branch() must be called after a gateway element");
|
|
781
|
+
}
|
|
782
|
+
const b = new BranchBuilder(this.currentGatewayId, name);
|
|
783
|
+
callback(b);
|
|
784
|
+
for (const el of b._elements) {
|
|
785
|
+
if (this._elements.some((n) => n.id === el.id)) {
|
|
786
|
+
throw new Error(`Duplicate element ID "${el.id}"`);
|
|
787
|
+
}
|
|
788
|
+
this._elements.push(el);
|
|
789
|
+
}
|
|
790
|
+
for (const fl of b._flows)
|
|
791
|
+
this._flows.push(fl);
|
|
792
|
+
for (const ann of b._textAnnotations)
|
|
793
|
+
this._textAnnotations.push(ann);
|
|
794
|
+
for (const assoc of b._associations)
|
|
795
|
+
this._associations.push(assoc);
|
|
796
|
+
if (b._defaultFlowId) {
|
|
797
|
+
const gw = this._elements.find((n) => n.id === this.currentGatewayId);
|
|
798
|
+
if (gw && (gw.type === "exclusiveGateway" || gw.type === "inclusiveGateway")) {
|
|
799
|
+
gw.default = b._defaultFlowId;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
if (!b._connected && b._elements.length > 0) {
|
|
803
|
+
const lastEl = b._elements[b._elements.length - 1];
|
|
804
|
+
if (lastEl && lastEl.type !== "endEvent") {
|
|
805
|
+
this.openBranchEnds.push(b._lastNodeId);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
this.lastNodeId = undefined;
|
|
809
|
+
return this;
|
|
810
|
+
}
|
|
811
|
+
connectTo(targetId) {
|
|
812
|
+
if (this.lastNodeId) {
|
|
813
|
+
this._flows.push({
|
|
814
|
+
id: generateId("Flow"),
|
|
815
|
+
sourceRef: this.lastNodeId,
|
|
816
|
+
targetRef: targetId,
|
|
817
|
+
extensionElements: [],
|
|
818
|
+
unknownAttributes: {},
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
this.lastNodeId = undefined;
|
|
822
|
+
return this;
|
|
823
|
+
}
|
|
824
|
+
element(elementId) {
|
|
825
|
+
if (!this._elements.some((n) => n.id === elementId)) {
|
|
826
|
+
throw new Error(`Element "${elementId}" not found in sub-process`);
|
|
827
|
+
}
|
|
828
|
+
this.lastNodeId = elementId;
|
|
829
|
+
this.currentGatewayId = undefined;
|
|
830
|
+
return this;
|
|
831
|
+
}
|
|
560
832
|
}
|
|
561
833
|
// ---------------------------------------------------------------------------
|
|
562
834
|
// Process builder (top-level entry point)
|
|
@@ -571,10 +843,18 @@ export class ProcessBuilder {
|
|
|
571
843
|
sequenceFlows = [];
|
|
572
844
|
rootErrors = [];
|
|
573
845
|
rootMessages = [];
|
|
846
|
+
rootSignals = [];
|
|
847
|
+
rootEscalations = [];
|
|
848
|
+
_textAnnotations = [];
|
|
849
|
+
_associations = [];
|
|
850
|
+
_annCounters = new Map();
|
|
574
851
|
lastNodeId;
|
|
575
852
|
currentGatewayId;
|
|
576
853
|
openBranchEnds = [];
|
|
577
854
|
_autoLayout = false;
|
|
855
|
+
_executionPlatformVersion = "8.9.0";
|
|
856
|
+
_serviceTaskDefaults = {};
|
|
857
|
+
_savedMainFlowId = undefined;
|
|
578
858
|
constructor(processId) {
|
|
579
859
|
this.processId = processId;
|
|
580
860
|
}
|
|
@@ -583,6 +863,17 @@ export class ProcessBuilder {
|
|
|
583
863
|
this._autoLayout = true;
|
|
584
864
|
return this;
|
|
585
865
|
}
|
|
866
|
+
/** Set the Camunda execution platform version stamped into the BPMN definitions. Defaults to `"8.9.0"`. */
|
|
867
|
+
executionPlatformVersion(version) {
|
|
868
|
+
this._executionPlatformVersion = version;
|
|
869
|
+
return this;
|
|
870
|
+
}
|
|
871
|
+
/** Set process-wide defaults applied to subsequently added elements. */
|
|
872
|
+
defaults(options) {
|
|
873
|
+
if (options.serviceTask)
|
|
874
|
+
this._serviceTaskDefaults = { ...this._serviceTaskDefaults, ...options.serviceTask };
|
|
875
|
+
return this;
|
|
876
|
+
}
|
|
586
877
|
/** Set the display name for this process. */
|
|
587
878
|
name(name) {
|
|
588
879
|
this.processName = name;
|
|
@@ -613,7 +904,9 @@ export class ProcessBuilder {
|
|
|
613
904
|
extensionElements: extElements,
|
|
614
905
|
});
|
|
615
906
|
if (element.type === "startEvent" && options) {
|
|
616
|
-
element.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages);
|
|
907
|
+
element.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
|
|
908
|
+
if (options.isInterrupting === false)
|
|
909
|
+
element.isInterrupting = false;
|
|
617
910
|
}
|
|
618
911
|
if (options?.modelerTemplate) {
|
|
619
912
|
element.unknownAttributes["zeebe:modelerTemplate"] = options.modelerTemplate;
|
|
@@ -639,10 +932,22 @@ export class ProcessBuilder {
|
|
|
639
932
|
this.currentGatewayId = undefined;
|
|
640
933
|
return this.startEvent(id, options);
|
|
641
934
|
}
|
|
935
|
+
/**
|
|
936
|
+
* Alias for `addStartEvent()` — begins a new disconnected parallel path.
|
|
937
|
+
*
|
|
938
|
+
* Use this for readability when modeling processes with multiple independent paths.
|
|
939
|
+
*/
|
|
940
|
+
disconnectedStartEvent(id, options) {
|
|
941
|
+
return this.addStartEvent(id, options);
|
|
942
|
+
}
|
|
642
943
|
/** Add an end event. */
|
|
643
944
|
endEvent(id, options) {
|
|
644
945
|
const nodeId = id ?? generateId("EndEvent");
|
|
645
|
-
|
|
946
|
+
const element = makeFlowElement(nodeId, "endEvent", options);
|
|
947
|
+
if (element.type === "endEvent" && options) {
|
|
948
|
+
element.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
|
|
949
|
+
}
|
|
950
|
+
this.addFlowElement(element);
|
|
646
951
|
return this;
|
|
647
952
|
}
|
|
648
953
|
/** Add an intermediate throw event (none, message, signal, escalation). */
|
|
@@ -650,7 +955,7 @@ export class ProcessBuilder {
|
|
|
650
955
|
const nodeId = id ?? generateId("IntermediateThrowEvent");
|
|
651
956
|
const element = makeFlowElement(nodeId, "intermediateThrowEvent", options);
|
|
652
957
|
if (element.type === "intermediateThrowEvent" && options) {
|
|
653
|
-
element.eventDefinitions = buildEventDefinitions(options);
|
|
958
|
+
element.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
|
|
654
959
|
}
|
|
655
960
|
this.addFlowElement(element);
|
|
656
961
|
return this;
|
|
@@ -660,7 +965,7 @@ export class ProcessBuilder {
|
|
|
660
965
|
const nodeId = id ?? generateId("IntermediateCatchEvent");
|
|
661
966
|
const element = makeFlowElement(nodeId, "intermediateCatchEvent", options);
|
|
662
967
|
if (element.type === "intermediateCatchEvent" && options) {
|
|
663
|
-
element.eventDefinitions = buildEventDefinitions(options);
|
|
968
|
+
element.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
|
|
664
969
|
}
|
|
665
970
|
this.addFlowElement(element);
|
|
666
971
|
return this;
|
|
@@ -676,32 +981,57 @@ export class ProcessBuilder {
|
|
|
676
981
|
if (element.type === "boundaryEvent") {
|
|
677
982
|
element.attachedToRef = options.attachedTo;
|
|
678
983
|
element.cancelActivity = options.cancelActivity;
|
|
679
|
-
element.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages);
|
|
984
|
+
element.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
|
|
680
985
|
}
|
|
681
986
|
// Boundary events never auto-connect — temporarily clear lastNodeId
|
|
682
987
|
const prevLast = this.lastNodeId;
|
|
683
988
|
this.lastNodeId = undefined;
|
|
684
989
|
this.addFlowElement(element);
|
|
990
|
+
// For compensation boundary events, save the main-flow cursor AFTER addFlowElement
|
|
991
|
+
// so subsequent normal elements don't accidentally clear it before it's consumed.
|
|
992
|
+
if (options.compensation) {
|
|
993
|
+
this._savedMainFlowId = prevLast;
|
|
994
|
+
}
|
|
685
995
|
// Don't restore prevLast — the builder now chains from the boundary event
|
|
686
996
|
void prevLast;
|
|
687
997
|
return this;
|
|
688
998
|
}
|
|
999
|
+
/**
|
|
1000
|
+
* Attach a boundary event to the preceding task and build its outgoing path,
|
|
1001
|
+
* then restore the builder cursor to the preceding task so the main flow continues.
|
|
1002
|
+
*
|
|
1003
|
+
* @param id - ID for the boundary event element.
|
|
1004
|
+
* @param options - Boundary event options (without `attachedTo` — inferred from cursor).
|
|
1005
|
+
* @param handler - Callback that chains elements from the boundary event.
|
|
1006
|
+
*/
|
|
1007
|
+
withBoundary(id, options, handler) {
|
|
1008
|
+
const attachedTo = this.lastNodeId;
|
|
1009
|
+
if (!attachedTo) {
|
|
1010
|
+
throw new Error("withBoundary() must follow a task element. Current builder position has no active element.");
|
|
1011
|
+
}
|
|
1012
|
+
const savedLast = this.lastNodeId;
|
|
1013
|
+
const savedGateway = this.currentGatewayId;
|
|
1014
|
+
const savedOpenEnds = [...this.openBranchEnds];
|
|
1015
|
+
this.openBranchEnds = [];
|
|
1016
|
+
// boundaryEvent() sets lastNodeId to the boundary event id
|
|
1017
|
+
this.boundaryEvent(id, { ...options, attachedTo });
|
|
1018
|
+
// Build the error/timeout path chaining from the boundary event
|
|
1019
|
+
handler(this);
|
|
1020
|
+
// Restore cursor to the original task so the main flow continues
|
|
1021
|
+
this.lastNodeId = savedLast;
|
|
1022
|
+
this.currentGatewayId = savedGateway;
|
|
1023
|
+
this.openBranchEnds = savedOpenEnds;
|
|
1024
|
+
this._savedMainFlowId = undefined;
|
|
1025
|
+
return this;
|
|
1026
|
+
}
|
|
689
1027
|
// ---- Tasks ----
|
|
690
1028
|
/** Add a service task with Zeebe task definition and optional IO mappings. */
|
|
691
1029
|
serviceTask(id, options) {
|
|
692
|
-
const
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
if (options.modelerTemplateIcon)
|
|
698
|
-
unknownAttributes["zeebe:modelerTemplateIcon"] = options.modelerTemplateIcon;
|
|
699
|
-
const el = makeFlowElement(id, "serviceTask", {
|
|
700
|
-
name: options.name,
|
|
701
|
-
extensionElements: buildServiceTaskExtensions(options),
|
|
702
|
-
});
|
|
703
|
-
el.unknownAttributes = unknownAttributes;
|
|
704
|
-
this.addFlowElement(el);
|
|
1030
|
+
const merged = {
|
|
1031
|
+
...options,
|
|
1032
|
+
retries: options.retries ?? this._serviceTaskDefaults.retries,
|
|
1033
|
+
};
|
|
1034
|
+
this.addFlowElement(makeServiceTaskEl(id, merged));
|
|
705
1035
|
return this;
|
|
706
1036
|
}
|
|
707
1037
|
/** Add a REST connector task — syntactic sugar over `serviceTask()`. */
|
|
@@ -732,95 +1062,52 @@ export class ProcessBuilder {
|
|
|
732
1062
|
}
|
|
733
1063
|
/** Add a script task with a FEEL expression. */
|
|
734
1064
|
scriptTask(id, options) {
|
|
735
|
-
this.addFlowElement(
|
|
736
|
-
name: options.name,
|
|
737
|
-
extensionElements: zeebeExtensionsToXmlElements({
|
|
738
|
-
unknownElements: [
|
|
739
|
-
{
|
|
740
|
-
name: "zeebe:script",
|
|
741
|
-
attributes: {
|
|
742
|
-
expression: options.expression,
|
|
743
|
-
resultVariable: options.resultVariable,
|
|
744
|
-
},
|
|
745
|
-
children: [],
|
|
746
|
-
},
|
|
747
|
-
],
|
|
748
|
-
}),
|
|
749
|
-
}));
|
|
1065
|
+
this.addFlowElement(makeScriptTaskEl(id, options));
|
|
750
1066
|
return this;
|
|
751
1067
|
}
|
|
752
1068
|
/** Add a user task with optional form reference. */
|
|
753
1069
|
userTask(id, options) {
|
|
754
|
-
|
|
755
|
-
? zeebeExtensionsToXmlElements({ formDefinition: { formId: options.formId } })
|
|
756
|
-
: [];
|
|
757
|
-
this.addFlowElement(makeFlowElement(id, "userTask", {
|
|
758
|
-
name: options?.name,
|
|
759
|
-
extensionElements,
|
|
760
|
-
}));
|
|
1070
|
+
this.addFlowElement(makeUserTaskEl(id, options));
|
|
761
1071
|
return this;
|
|
762
1072
|
}
|
|
763
1073
|
/** Add a send task (aspirational). */
|
|
764
1074
|
sendTask(id, options) {
|
|
765
|
-
|
|
1075
|
+
const el = makeFlowElement(id, "sendTask", options);
|
|
1076
|
+
if (options?.isForCompensation)
|
|
1077
|
+
el.isForCompensation = true;
|
|
1078
|
+
this.addFlowElement(el);
|
|
766
1079
|
return this;
|
|
767
1080
|
}
|
|
768
1081
|
/** Add a receive task (aspirational). */
|
|
769
1082
|
receiveTask(id, options) {
|
|
770
|
-
|
|
1083
|
+
const el = makeFlowElement(id, "receiveTask", options);
|
|
1084
|
+
if (options?.isForCompensation)
|
|
1085
|
+
el.isForCompensation = true;
|
|
1086
|
+
this.addFlowElement(el);
|
|
771
1087
|
return this;
|
|
772
1088
|
}
|
|
773
1089
|
/** Add a business rule task. */
|
|
774
1090
|
businessRuleTask(id, options) {
|
|
775
|
-
|
|
776
|
-
if (options?.taskType) {
|
|
777
|
-
ext.push(...zeebeExtensionsToXmlElements({ taskDefinition: { type: options.taskType } }));
|
|
778
|
-
}
|
|
779
|
-
if (options?.decisionId) {
|
|
780
|
-
ext.push(...zeebeExtensionsToXmlElements({
|
|
781
|
-
calledDecision: {
|
|
782
|
-
decisionId: options.decisionId,
|
|
783
|
-
resultVariable: options.resultVariable ?? "result",
|
|
784
|
-
},
|
|
785
|
-
}));
|
|
786
|
-
}
|
|
787
|
-
this.addFlowElement(makeFlowElement(id, "businessRuleTask", {
|
|
788
|
-
name: options?.name,
|
|
789
|
-
extensionElements: ext,
|
|
790
|
-
}));
|
|
1091
|
+
this.addFlowElement(makeBusinessRuleTaskEl(id, options));
|
|
791
1092
|
return this;
|
|
792
1093
|
}
|
|
793
1094
|
/** Add a call activity referencing another process. */
|
|
794
1095
|
callActivity(id, options) {
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
unknownElements: [
|
|
805
|
-
{
|
|
806
|
-
name: "zeebe:calledElement",
|
|
807
|
-
attributes: attrs,
|
|
808
|
-
children: [],
|
|
809
|
-
},
|
|
810
|
-
],
|
|
811
|
-
}),
|
|
812
|
-
}));
|
|
1096
|
+
this.addFlowElement(makeCallActivityEl(id, options));
|
|
1097
|
+
return this;
|
|
1098
|
+
}
|
|
1099
|
+
/** Add an abstract task with no Zeebe extensions. */
|
|
1100
|
+
task(id, options) {
|
|
1101
|
+
const el = makeFlowElement(id, "task", options);
|
|
1102
|
+
if (options?.isForCompensation)
|
|
1103
|
+
el.isForCompensation = true;
|
|
1104
|
+
this.addFlowElement(el);
|
|
813
1105
|
return this;
|
|
814
1106
|
}
|
|
815
1107
|
// ---- Gateways ----
|
|
816
1108
|
/** Add an exclusive gateway (XOR split/join). */
|
|
817
1109
|
exclusiveGateway(id, options) {
|
|
818
|
-
|
|
819
|
-
if (options?.defaultFlow && element.type === "exclusiveGateway") {
|
|
820
|
-
;
|
|
821
|
-
element.default = options.defaultFlow;
|
|
822
|
-
}
|
|
823
|
-
this.addFlowElement(element);
|
|
1110
|
+
this.addFlowElement(makeExclusiveGatewayEl(id, options));
|
|
824
1111
|
this.currentGatewayId = id;
|
|
825
1112
|
return this;
|
|
826
1113
|
}
|
|
@@ -832,12 +1119,7 @@ export class ProcessBuilder {
|
|
|
832
1119
|
}
|
|
833
1120
|
/** Add an inclusive gateway (OR split/join). Aspirational. */
|
|
834
1121
|
inclusiveGateway(id, options) {
|
|
835
|
-
|
|
836
|
-
if (options?.defaultFlow && element.type === "inclusiveGateway") {
|
|
837
|
-
;
|
|
838
|
-
element.default = options.defaultFlow;
|
|
839
|
-
}
|
|
840
|
-
this.addFlowElement(element);
|
|
1122
|
+
this.addFlowElement(makeInclusiveGatewayEl(id, options));
|
|
841
1123
|
this.currentGatewayId = id;
|
|
842
1124
|
return this;
|
|
843
1125
|
}
|
|
@@ -869,7 +1151,7 @@ export class ProcessBuilder {
|
|
|
869
1151
|
if (!this.currentGatewayId) {
|
|
870
1152
|
throw new Error("branch() must be called after a gateway element");
|
|
871
1153
|
}
|
|
872
|
-
const b = new BranchBuilder(this.currentGatewayId, name);
|
|
1154
|
+
const b = new BranchBuilder(this.currentGatewayId, name, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
|
|
873
1155
|
callback(b);
|
|
874
1156
|
for (const el of b._elements) {
|
|
875
1157
|
if (this.flowElements.some((n) => n.id === el.id)) {
|
|
@@ -880,6 +1162,10 @@ export class ProcessBuilder {
|
|
|
880
1162
|
for (const fl of b._flows) {
|
|
881
1163
|
this.sequenceFlows.push(fl);
|
|
882
1164
|
}
|
|
1165
|
+
for (const ann of b._textAnnotations)
|
|
1166
|
+
this._textAnnotations.push(ann);
|
|
1167
|
+
for (const assoc of b._associations)
|
|
1168
|
+
this._associations.push(assoc);
|
|
883
1169
|
// If the branch is the default flow, set the gateway's default
|
|
884
1170
|
if (b._defaultFlowId) {
|
|
885
1171
|
const gateway = this.flowElements.find((n) => n.id === this.currentGatewayId);
|
|
@@ -933,6 +1219,7 @@ export class ProcessBuilder {
|
|
|
933
1219
|
adHocSubProcess(id, content, options) {
|
|
934
1220
|
const sub = new SubProcessContentBuilder();
|
|
935
1221
|
content(sub);
|
|
1222
|
+
insertJoinGateways(sub._elements, sub._flows);
|
|
936
1223
|
recomputeIncomingOutgoing(sub._elements, sub._flows);
|
|
937
1224
|
const zeebeExt = {};
|
|
938
1225
|
if (options?.taskDefinition) {
|
|
@@ -984,6 +1271,8 @@ export class ProcessBuilder {
|
|
|
984
1271
|
if (element.type === "adHocSubProcess") {
|
|
985
1272
|
element.flowElements = sub._elements;
|
|
986
1273
|
element.sequenceFlows = sub._flows;
|
|
1274
|
+
element.textAnnotations = sub._textAnnotations;
|
|
1275
|
+
element.associations = sub._associations;
|
|
987
1276
|
if (options?.loopCharacteristics) {
|
|
988
1277
|
element.loopCharacteristics = buildAdHocLoopCharacteristics(options.loopCharacteristics);
|
|
989
1278
|
}
|
|
@@ -998,11 +1287,14 @@ export class ProcessBuilder {
|
|
|
998
1287
|
subProcess(id, content, options) {
|
|
999
1288
|
const sub = new SubProcessContentBuilder();
|
|
1000
1289
|
content(sub);
|
|
1290
|
+
insertJoinGateways(sub._elements, sub._flows);
|
|
1001
1291
|
recomputeIncomingOutgoing(sub._elements, sub._flows);
|
|
1002
1292
|
const element = makeFlowElement(id, "subProcess", options);
|
|
1003
1293
|
if (element.type === "subProcess") {
|
|
1004
1294
|
element.flowElements = sub._elements;
|
|
1005
1295
|
element.sequenceFlows = sub._flows;
|
|
1296
|
+
element.textAnnotations = sub._textAnnotations;
|
|
1297
|
+
element.associations = sub._associations;
|
|
1006
1298
|
if (options?.multiInstance) {
|
|
1007
1299
|
element.loopCharacteristics = buildMultiInstance(options.multiInstance);
|
|
1008
1300
|
}
|
|
@@ -1010,17 +1302,52 @@ export class ProcessBuilder {
|
|
|
1010
1302
|
this.addFlowElement(element);
|
|
1011
1303
|
return this;
|
|
1012
1304
|
}
|
|
1013
|
-
/** Add an event sub-process
|
|
1305
|
+
/** Add an event sub-process. Triggered by its start event — no incoming or outgoing sequence flows. */
|
|
1014
1306
|
eventSubProcess(id, content, options) {
|
|
1015
1307
|
const sub = new SubProcessContentBuilder();
|
|
1016
1308
|
content(sub);
|
|
1309
|
+
insertJoinGateways(sub._elements, sub._flows);
|
|
1017
1310
|
recomputeIncomingOutgoing(sub._elements, sub._flows);
|
|
1018
|
-
const element = makeFlowElement(id, "
|
|
1019
|
-
if (element.type === "
|
|
1311
|
+
const element = makeFlowElement(id, "subProcess", options);
|
|
1312
|
+
if (element.type === "subProcess") {
|
|
1313
|
+
element.triggeredByEvent = true;
|
|
1020
1314
|
element.flowElements = sub._elements;
|
|
1021
1315
|
element.sequenceFlows = sub._flows;
|
|
1316
|
+
element.textAnnotations = sub._textAnnotations;
|
|
1317
|
+
element.associations = sub._associations;
|
|
1022
1318
|
}
|
|
1023
|
-
|
|
1319
|
+
// Event sub-processes have no incoming/outgoing sequence flows and must not
|
|
1320
|
+
// advance the flow cursor — the surrounding process wires around them.
|
|
1321
|
+
// openBranchEnds is intentionally NOT drained here; the next normal
|
|
1322
|
+
// addFlowElement call will drain it and connect branch ends to that element.
|
|
1323
|
+
if (this.flowElements.some((n) => n.id === element.id)) {
|
|
1324
|
+
throw new Error(`Duplicate element ID "${element.id}" in process "${this.processId}"`);
|
|
1325
|
+
}
|
|
1326
|
+
this._savedMainFlowId = undefined;
|
|
1327
|
+
this.flowElements.push(element);
|
|
1328
|
+
return this;
|
|
1329
|
+
}
|
|
1330
|
+
// ---- Annotations ----
|
|
1331
|
+
/** Attach a text annotation to the element at the current cursor position. */
|
|
1332
|
+
textAnnotation(text) {
|
|
1333
|
+
if (!this.lastNodeId) {
|
|
1334
|
+
throw new Error("textAnnotation() must follow a flow element");
|
|
1335
|
+
}
|
|
1336
|
+
return this.annotate(this.lastNodeId, text);
|
|
1337
|
+
}
|
|
1338
|
+
/** Attach a text annotation to any flow element by explicit ID. */
|
|
1339
|
+
annotate(elementId, text) {
|
|
1340
|
+
const n = (this._annCounters.get(elementId) ?? 0) + 1;
|
|
1341
|
+
this._annCounters.set(elementId, n);
|
|
1342
|
+
const annId = `TextAnnotation_${elementId}_${n}`;
|
|
1343
|
+
this._textAnnotations.push({ id: annId, text, unknownAttributes: {} });
|
|
1344
|
+
this._associations.push({
|
|
1345
|
+
id: `Association_${elementId}_${n}`,
|
|
1346
|
+
sourceRef: elementId,
|
|
1347
|
+
targetRef: annId,
|
|
1348
|
+
associationDirection: "None",
|
|
1349
|
+
unknownAttributes: {},
|
|
1350
|
+
});
|
|
1024
1351
|
return this;
|
|
1025
1352
|
}
|
|
1026
1353
|
// ---- Build ----
|
|
@@ -1030,8 +1357,17 @@ export class ProcessBuilder {
|
|
|
1030
1357
|
* Resolves all forward-referenced `incoming` / `outgoing` arrays and wraps
|
|
1031
1358
|
* the process in a {@link BpmnDefinitions} ready for XML serialization.
|
|
1032
1359
|
*/
|
|
1033
|
-
build() {
|
|
1034
|
-
this.
|
|
1360
|
+
build(options) {
|
|
1361
|
+
const beforeCount = this.flowElements.length;
|
|
1362
|
+
insertJoinGateways(this.flowElements, this.sequenceFlows);
|
|
1363
|
+
if (options?.strict && this.flowElements.length > beforeCount) {
|
|
1364
|
+
const inserted = this.flowElements
|
|
1365
|
+
.slice(beforeCount)
|
|
1366
|
+
.map((e) => e.id)
|
|
1367
|
+
.join(", ");
|
|
1368
|
+
throw new Error(`auto-join gateways were inserted: ${inserted}. Use explicit .connectTo(joinId) to make gateway topology explicit, or remove { strict: true }.`);
|
|
1369
|
+
}
|
|
1370
|
+
this.validate();
|
|
1035
1371
|
recomputeIncomingOutgoing(this.flowElements, this.sequenceFlows);
|
|
1036
1372
|
const extensionElements = [];
|
|
1037
1373
|
if (this._versionTag) {
|
|
@@ -1048,15 +1384,15 @@ export class ProcessBuilder {
|
|
|
1048
1384
|
extensionElements,
|
|
1049
1385
|
flowElements: this.flowElements,
|
|
1050
1386
|
sequenceFlows: this.sequenceFlows,
|
|
1051
|
-
textAnnotations:
|
|
1052
|
-
associations:
|
|
1387
|
+
textAnnotations: this._textAnnotations,
|
|
1388
|
+
associations: this._associations,
|
|
1053
1389
|
unknownAttributes: {},
|
|
1054
1390
|
};
|
|
1055
|
-
|
|
1391
|
+
const defs = {
|
|
1056
1392
|
id: "Definitions_1",
|
|
1057
1393
|
targetNamespace: "http://bpmn.io/schema/bpmn",
|
|
1058
1394
|
exporter: "@bpmnkit/core",
|
|
1059
|
-
exporterVersion:
|
|
1395
|
+
exporterVersion: EXPORTER_VERSION,
|
|
1060
1396
|
namespaces: {
|
|
1061
1397
|
bpmn: "http://www.omg.org/spec/BPMN/20100524/MODEL",
|
|
1062
1398
|
bpmndi: "http://www.omg.org/spec/BPMN/20100524/DI",
|
|
@@ -1068,134 +1404,67 @@ export class ProcessBuilder {
|
|
|
1068
1404
|
},
|
|
1069
1405
|
unknownAttributes: {
|
|
1070
1406
|
"modeler:executionPlatform": "Camunda Cloud",
|
|
1071
|
-
"modeler:executionPlatformVersion":
|
|
1407
|
+
"modeler:executionPlatformVersion": this._executionPlatformVersion,
|
|
1072
1408
|
},
|
|
1073
1409
|
errors: this.rootErrors,
|
|
1074
|
-
escalations:
|
|
1410
|
+
escalations: this.rootEscalations,
|
|
1075
1411
|
messages: this.rootMessages,
|
|
1076
|
-
signals:
|
|
1412
|
+
signals: this.rootSignals,
|
|
1077
1413
|
collaborations: [],
|
|
1078
1414
|
processes: [process],
|
|
1079
|
-
|
|
1415
|
+
// Seed diagram stub so applyAutoLayout preserves process-specific IDs.
|
|
1416
|
+
diagrams: this._autoLayout
|
|
1417
|
+
? [
|
|
1418
|
+
{
|
|
1419
|
+
id: `${this.processId}_di`,
|
|
1420
|
+
plane: {
|
|
1421
|
+
id: `${this.processId}_di_plane`,
|
|
1422
|
+
bpmnElement: this.processId,
|
|
1423
|
+
shapes: [],
|
|
1424
|
+
edges: [],
|
|
1425
|
+
},
|
|
1426
|
+
},
|
|
1427
|
+
]
|
|
1428
|
+
: [],
|
|
1080
1429
|
};
|
|
1430
|
+
return this._autoLayout ? applyAutoLayout(defs) : defs;
|
|
1081
1431
|
}
|
|
1082
|
-
|
|
1083
|
-
const
|
|
1084
|
-
return layoutResultToDiagram(this.processId, layoutResult);
|
|
1085
|
-
}
|
|
1086
|
-
// ---- Internal ----
|
|
1087
|
-
/**
|
|
1088
|
-
* Insert matching join gateways where split-gateway branches converge
|
|
1089
|
-
* on a non-gateway target. BPMN best practice: every split has a join.
|
|
1090
|
-
*/
|
|
1091
|
-
insertJoinGateways() {
|
|
1092
|
-
const GATEWAY_TYPES = new Set([
|
|
1093
|
-
"exclusiveGateway",
|
|
1094
|
-
"parallelGateway",
|
|
1095
|
-
"inclusiveGateway",
|
|
1096
|
-
"eventBasedGateway",
|
|
1097
|
-
]);
|
|
1098
|
-
const elementTypes = new Map();
|
|
1099
|
-
for (const el of this.flowElements) {
|
|
1100
|
-
elementTypes.set(el.id, el.type);
|
|
1101
|
-
}
|
|
1102
|
-
// Find split gateways (2+ outgoing flows)
|
|
1103
|
-
const outCount = new Map();
|
|
1432
|
+
validate() {
|
|
1433
|
+
const elementIds = new Set(this.flowElements.map((el) => el.id));
|
|
1104
1434
|
for (const flow of this.sequenceFlows) {
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
for (const [id, count] of outCount) {
|
|
1109
|
-
const type = elementTypes.get(id);
|
|
1110
|
-
if (type && GATEWAY_TYPES.has(type) && count >= 2) {
|
|
1111
|
-
splitGateways.add(id);
|
|
1435
|
+
if (!elementIds.has(flow.targetRef)) {
|
|
1436
|
+
throw new Error(`Sequence flow "${flow.id}" in process "${this.processId}" references unknown ` +
|
|
1437
|
+
`element "${flow.targetRef}". Check connectTo() calls — target must exist.`);
|
|
1112
1438
|
}
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
// Build incoming flow map
|
|
1117
|
-
const incoming = new Map();
|
|
1118
|
-
for (const flow of this.sequenceFlows) {
|
|
1119
|
-
const arr = incoming.get(flow.targetRef);
|
|
1120
|
-
if (arr)
|
|
1121
|
-
arr.push(flow);
|
|
1122
|
-
else
|
|
1123
|
-
incoming.set(flow.targetRef, [flow]);
|
|
1124
|
-
}
|
|
1125
|
-
// For each target with 2+ incoming flows, check if they trace back
|
|
1126
|
-
// to the same split gateway → insert a join gateway if needed
|
|
1127
|
-
for (const [targetId, inFlows] of incoming) {
|
|
1128
|
-
if (inFlows.length < 2)
|
|
1129
|
-
continue;
|
|
1130
|
-
// Group incoming flows by originating split gateway
|
|
1131
|
-
const splitToFlows = new Map();
|
|
1132
|
-
for (const flow of inFlows) {
|
|
1133
|
-
const split = this.traceBackToSplit(flow.sourceRef, splitGateways);
|
|
1134
|
-
if (split) {
|
|
1135
|
-
const arr = splitToFlows.get(split);
|
|
1136
|
-
if (arr)
|
|
1137
|
-
arr.push(flow);
|
|
1138
|
-
else
|
|
1139
|
-
splitToFlows.set(split, [flow]);
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
for (const [splitId, convergingFlows] of splitToFlows) {
|
|
1143
|
-
if (convergingFlows.length < 2)
|
|
1144
|
-
continue;
|
|
1145
|
-
const gwType = elementTypes.get(splitId);
|
|
1146
|
-
if (!gwType)
|
|
1147
|
-
continue;
|
|
1148
|
-
// Don't insert if target is already a matching gateway type
|
|
1149
|
-
const targetType = elementTypes.get(targetId);
|
|
1150
|
-
if (targetType === gwType)
|
|
1151
|
-
continue;
|
|
1152
|
-
const joinId = `${splitId}_join`;
|
|
1153
|
-
if (elementTypes.has(joinId))
|
|
1154
|
-
continue;
|
|
1155
|
-
const joinElement = makeFlowElement(joinId, gwType, {});
|
|
1156
|
-
this.flowElements.push(joinElement);
|
|
1157
|
-
elementTypes.set(joinId, gwType);
|
|
1158
|
-
// Re-route converging flows to the join gateway
|
|
1159
|
-
for (const flow of convergingFlows) {
|
|
1160
|
-
flow.targetRef = joinId;
|
|
1161
|
-
}
|
|
1162
|
-
// Add flow from join to original target
|
|
1163
|
-
this.sequenceFlows.push({
|
|
1164
|
-
id: generateId("Flow"),
|
|
1165
|
-
sourceRef: joinId,
|
|
1166
|
-
targetRef: targetId,
|
|
1167
|
-
extensionElements: [],
|
|
1168
|
-
unknownAttributes: {},
|
|
1169
|
-
});
|
|
1439
|
+
if (!elementIds.has(flow.sourceRef)) {
|
|
1440
|
+
throw new Error(`Sequence flow "${flow.id}" in process "${this.processId}" references unknown ` +
|
|
1441
|
+
`source element "${flow.sourceRef}".`);
|
|
1170
1442
|
}
|
|
1171
1443
|
}
|
|
1172
1444
|
}
|
|
1173
|
-
/** Trace backward from a node to find which split gateway it belongs to. */
|
|
1174
|
-
traceBackToSplit(nodeId, splitGateways) {
|
|
1175
|
-
const visited = new Set();
|
|
1176
|
-
let current = nodeId;
|
|
1177
|
-
while (current) {
|
|
1178
|
-
if (visited.has(current))
|
|
1179
|
-
return undefined;
|
|
1180
|
-
visited.add(current);
|
|
1181
|
-
if (splitGateways.has(current))
|
|
1182
|
-
return current;
|
|
1183
|
-
// Follow single incoming flow backward
|
|
1184
|
-
const inFlows = this.sequenceFlows.filter((f) => f.targetRef === current);
|
|
1185
|
-
if (inFlows.length !== 1)
|
|
1186
|
-
return undefined;
|
|
1187
|
-
const prev = inFlows[0];
|
|
1188
|
-
if (!prev)
|
|
1189
|
-
return undefined;
|
|
1190
|
-
current = prev.sourceRef;
|
|
1191
|
-
}
|
|
1192
|
-
return undefined;
|
|
1193
|
-
}
|
|
1194
1445
|
addFlowElement(element) {
|
|
1195
1446
|
if (this.flowElements.some((n) => n.id === element.id)) {
|
|
1196
1447
|
throw new Error(`Duplicate element ID "${element.id}" in process "${this.processId}"`);
|
|
1197
1448
|
}
|
|
1198
1449
|
this.flowElements.push(element);
|
|
1450
|
+
// Compensation handlers are outside the normal token flow: link via association
|
|
1451
|
+
// from the preceding compensation boundary event, then restore the main-flow cursor.
|
|
1452
|
+
if (element.isForCompensation) {
|
|
1453
|
+
if (this.lastNodeId) {
|
|
1454
|
+
this._associations.push({
|
|
1455
|
+
id: generateId("Association"),
|
|
1456
|
+
sourceRef: this.lastNodeId,
|
|
1457
|
+
targetRef: element.id,
|
|
1458
|
+
associationDirection: "One",
|
|
1459
|
+
unknownAttributes: {},
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
// Restore main-flow cursor (saved by boundaryEvent() when compensation: true)
|
|
1463
|
+
this.lastNodeId = this._savedMainFlowId;
|
|
1464
|
+
this._savedMainFlowId = undefined;
|
|
1465
|
+
// Do NOT connect open branch ends — handler is outside normal flow
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1199
1468
|
if (this.lastNodeId) {
|
|
1200
1469
|
const flowId = generateId("Flow");
|
|
1201
1470
|
this.sequenceFlows.push({
|
|
@@ -1218,7 +1487,69 @@ export class ProcessBuilder {
|
|
|
1218
1487
|
});
|
|
1219
1488
|
}
|
|
1220
1489
|
this.openBranchEnds = [];
|
|
1490
|
+
// Clear any saved compensation cursor — a normal element advancing the cursor
|
|
1491
|
+
// means the compensation boundary/handler pattern has been interrupted.
|
|
1492
|
+
this._savedMainFlowId = undefined;
|
|
1221
1493
|
this.lastNodeId = element.id;
|
|
1222
1494
|
}
|
|
1223
1495
|
}
|
|
1496
|
+
// ---------------------------------------------------------------------------
|
|
1497
|
+
// Diagram builder — multi-process support
|
|
1498
|
+
// ---------------------------------------------------------------------------
|
|
1499
|
+
/**
|
|
1500
|
+
* Builder for a complete BPMN definitions document containing one or more processes.
|
|
1501
|
+
* Use `Bpmn.createDiagram(id?)` to obtain an instance.
|
|
1502
|
+
*/
|
|
1503
|
+
export class DiagramBuilder {
|
|
1504
|
+
_id;
|
|
1505
|
+
_processes = [];
|
|
1506
|
+
_errors = [];
|
|
1507
|
+
_messages = [];
|
|
1508
|
+
_executionPlatformVersion = "8.9.0";
|
|
1509
|
+
constructor(id) {
|
|
1510
|
+
this._id = id;
|
|
1511
|
+
}
|
|
1512
|
+
/** Set the Camunda execution platform version stamped into the BPMN definitions. Defaults to `"8.9.0"`. */
|
|
1513
|
+
executionPlatformVersion(version) {
|
|
1514
|
+
this._executionPlatformVersion = version;
|
|
1515
|
+
return this;
|
|
1516
|
+
}
|
|
1517
|
+
process(id, callback) {
|
|
1518
|
+
const builder = new ProcessBuilder(id);
|
|
1519
|
+
callback(builder);
|
|
1520
|
+
const defs = builder.build();
|
|
1521
|
+
this._processes.push(...defs.processes);
|
|
1522
|
+
this._errors.push(...defs.errors);
|
|
1523
|
+
this._messages.push(...defs.messages);
|
|
1524
|
+
return this;
|
|
1525
|
+
}
|
|
1526
|
+
build() {
|
|
1527
|
+
return {
|
|
1528
|
+
id: this._id,
|
|
1529
|
+
targetNamespace: "http://bpmn.io/schema/bpmn",
|
|
1530
|
+
exporter: "@bpmnkit/core",
|
|
1531
|
+
exporterVersion: EXPORTER_VERSION,
|
|
1532
|
+
namespaces: {
|
|
1533
|
+
bpmn: "http://www.omg.org/spec/BPMN/20100524/MODEL",
|
|
1534
|
+
bpmndi: "http://www.omg.org/spec/BPMN/20100524/DI",
|
|
1535
|
+
dc: "http://www.omg.org/spec/DD/20100524/DC",
|
|
1536
|
+
di: "http://www.omg.org/spec/DD/20100524/DI",
|
|
1537
|
+
zeebe: "http://camunda.org/schema/zeebe/1.0",
|
|
1538
|
+
modeler: "http://camunda.org/schema/modeler/1.0",
|
|
1539
|
+
xsi: "http://www.w3.org/2001/XMLSchema-instance",
|
|
1540
|
+
},
|
|
1541
|
+
unknownAttributes: {
|
|
1542
|
+
"modeler:executionPlatform": "Camunda Cloud",
|
|
1543
|
+
"modeler:executionPlatformVersion": this._executionPlatformVersion,
|
|
1544
|
+
},
|
|
1545
|
+
errors: this._errors,
|
|
1546
|
+
escalations: [],
|
|
1547
|
+
messages: this._messages,
|
|
1548
|
+
signals: [],
|
|
1549
|
+
collaborations: [],
|
|
1550
|
+
processes: this._processes,
|
|
1551
|
+
diagrams: [],
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1224
1555
|
//# sourceMappingURL=bpmn-builder.js.map
|