@bpmnkit/core 0.0.23 → 0.0.24
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 +104 -16
- package/dist/bpmn/bpmn-builder.js +560 -374
- 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,7 +1,9 @@
|
|
|
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
|
// ---------------------------------------------------------------------------
|
|
@@ -155,46 +157,6 @@ function buildAdHocLoopCharacteristics(lc) {
|
|
|
155
157
|
],
|
|
156
158
|
};
|
|
157
159
|
}
|
|
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
160
|
function recomputeIncomingOutgoing(elements, flows) {
|
|
199
161
|
for (const el of elements) {
|
|
200
162
|
el.incoming = [];
|
|
@@ -236,6 +198,181 @@ function makeConditionExpression(expression) {
|
|
|
236
198
|
};
|
|
237
199
|
}
|
|
238
200
|
// ---------------------------------------------------------------------------
|
|
201
|
+
// Element factory functions — shared by all three builder classes
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
function makeServiceTaskEl(id, options) {
|
|
204
|
+
const unknownAttributes = {};
|
|
205
|
+
if (options.modelerTemplate)
|
|
206
|
+
unknownAttributes["zeebe:modelerTemplate"] = options.modelerTemplate;
|
|
207
|
+
if (options.modelerTemplateVersion)
|
|
208
|
+
unknownAttributes["zeebe:modelerTemplateVersion"] = options.modelerTemplateVersion;
|
|
209
|
+
if (options.modelerTemplateIcon)
|
|
210
|
+
unknownAttributes["zeebe:modelerTemplateIcon"] = options.modelerTemplateIcon;
|
|
211
|
+
const el = makeFlowElement(id, "serviceTask", {
|
|
212
|
+
name: options.name,
|
|
213
|
+
extensionElements: buildServiceTaskExtensions(options),
|
|
214
|
+
});
|
|
215
|
+
el.unknownAttributes = unknownAttributes;
|
|
216
|
+
return el;
|
|
217
|
+
}
|
|
218
|
+
function makeScriptTaskEl(id, options) {
|
|
219
|
+
return makeFlowElement(id, "scriptTask", {
|
|
220
|
+
name: options.name,
|
|
221
|
+
extensionElements: zeebeExtensionsToXmlElements({
|
|
222
|
+
unknownElements: [
|
|
223
|
+
{
|
|
224
|
+
name: "zeebe:script",
|
|
225
|
+
attributes: { expression: options.expression, resultVariable: options.resultVariable },
|
|
226
|
+
children: [],
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
}),
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
function makeUserTaskEl(id, options) {
|
|
233
|
+
const ext = zeebeExtensionsToXmlElements({
|
|
234
|
+
...(options?.zeebeUserTask ? { userTask: true } : {}),
|
|
235
|
+
...(options?.formId ? { formDefinition: { formId: options.formId } } : {}),
|
|
236
|
+
});
|
|
237
|
+
return makeFlowElement(id, "userTask", { name: options?.name, extensionElements: ext });
|
|
238
|
+
}
|
|
239
|
+
function makeBusinessRuleTaskEl(id, options) {
|
|
240
|
+
const ext = [];
|
|
241
|
+
if (options?.taskType) {
|
|
242
|
+
ext.push(...zeebeExtensionsToXmlElements({ taskDefinition: { type: options.taskType } }));
|
|
243
|
+
}
|
|
244
|
+
if (options?.decisionId) {
|
|
245
|
+
ext.push(...zeebeExtensionsToXmlElements({
|
|
246
|
+
calledDecision: {
|
|
247
|
+
decisionId: options.decisionId,
|
|
248
|
+
resultVariable: options.resultVariable ?? "result",
|
|
249
|
+
},
|
|
250
|
+
}));
|
|
251
|
+
}
|
|
252
|
+
return makeFlowElement(id, "businessRuleTask", { name: options?.name, extensionElements: ext });
|
|
253
|
+
}
|
|
254
|
+
function makeCallActivityEl(id, options) {
|
|
255
|
+
const attrs = { processId: options.processId };
|
|
256
|
+
if (options.propagateAllChildVariables !== undefined) {
|
|
257
|
+
attrs.propagateAllChildVariables = String(options.propagateAllChildVariables);
|
|
258
|
+
}
|
|
259
|
+
return makeFlowElement(id, "callActivity", {
|
|
260
|
+
name: options.name,
|
|
261
|
+
extensionElements: zeebeExtensionsToXmlElements({
|
|
262
|
+
unknownElements: [{ name: "zeebe:calledElement", attributes: attrs, children: [] }],
|
|
263
|
+
}),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
function makeExclusiveGatewayEl(id, options) {
|
|
267
|
+
const el = makeFlowElement(id, "exclusiveGateway", options);
|
|
268
|
+
if (options?.defaultFlow && el.type === "exclusiveGateway") {
|
|
269
|
+
;
|
|
270
|
+
el.default = options.defaultFlow;
|
|
271
|
+
}
|
|
272
|
+
return el;
|
|
273
|
+
}
|
|
274
|
+
function makeInclusiveGatewayEl(id, options) {
|
|
275
|
+
const el = makeFlowElement(id, "inclusiveGateway", options);
|
|
276
|
+
if (options?.defaultFlow && el.type === "inclusiveGateway") {
|
|
277
|
+
;
|
|
278
|
+
el.default = options.defaultFlow;
|
|
279
|
+
}
|
|
280
|
+
return el;
|
|
281
|
+
}
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
// Shared graph helpers — used by ProcessBuilder and SubProcessContentBuilder
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
function insertJoinGateways(elements, flows) {
|
|
286
|
+
const GATEWAY_TYPES = new Set([
|
|
287
|
+
"exclusiveGateway",
|
|
288
|
+
"parallelGateway",
|
|
289
|
+
"inclusiveGateway",
|
|
290
|
+
"eventBasedGateway",
|
|
291
|
+
]);
|
|
292
|
+
const elementTypes = new Map();
|
|
293
|
+
for (const el of elements)
|
|
294
|
+
elementTypes.set(el.id, el.type);
|
|
295
|
+
const outCount = new Map();
|
|
296
|
+
for (const flow of flows) {
|
|
297
|
+
outCount.set(flow.sourceRef, (outCount.get(flow.sourceRef) ?? 0) + 1);
|
|
298
|
+
}
|
|
299
|
+
const splitGateways = new Set();
|
|
300
|
+
for (const [id, count] of outCount) {
|
|
301
|
+
const type = elementTypes.get(id);
|
|
302
|
+
if (type && GATEWAY_TYPES.has(type) && count >= 2)
|
|
303
|
+
splitGateways.add(id);
|
|
304
|
+
}
|
|
305
|
+
if (splitGateways.size === 0)
|
|
306
|
+
return;
|
|
307
|
+
const incoming = new Map();
|
|
308
|
+
for (const flow of flows) {
|
|
309
|
+
const arr = incoming.get(flow.targetRef);
|
|
310
|
+
if (arr)
|
|
311
|
+
arr.push(flow);
|
|
312
|
+
else
|
|
313
|
+
incoming.set(flow.targetRef, [flow]);
|
|
314
|
+
}
|
|
315
|
+
for (const [targetId, inFlows] of incoming) {
|
|
316
|
+
if (inFlows.length < 2)
|
|
317
|
+
continue;
|
|
318
|
+
const splitToFlows = new Map();
|
|
319
|
+
for (const flow of inFlows) {
|
|
320
|
+
const split = traceBackToSplit(flow.sourceRef, splitGateways, flows);
|
|
321
|
+
if (split) {
|
|
322
|
+
const arr = splitToFlows.get(split);
|
|
323
|
+
if (arr)
|
|
324
|
+
arr.push(flow);
|
|
325
|
+
else
|
|
326
|
+
splitToFlows.set(split, [flow]);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
for (const [splitId, convergingFlows] of splitToFlows) {
|
|
330
|
+
if (convergingFlows.length < 2)
|
|
331
|
+
continue;
|
|
332
|
+
const gwType = elementTypes.get(splitId);
|
|
333
|
+
if (!gwType)
|
|
334
|
+
continue;
|
|
335
|
+
const targetType = elementTypes.get(targetId);
|
|
336
|
+
if (targetType === gwType)
|
|
337
|
+
continue;
|
|
338
|
+
const joinId = `${splitId}_join`;
|
|
339
|
+
if (elementTypes.has(joinId))
|
|
340
|
+
continue;
|
|
341
|
+
const joinElement = makeFlowElement(joinId, gwType, {});
|
|
342
|
+
elements.push(joinElement);
|
|
343
|
+
elementTypes.set(joinId, gwType);
|
|
344
|
+
for (const flow of convergingFlows)
|
|
345
|
+
flow.targetRef = joinId;
|
|
346
|
+
flows.push({
|
|
347
|
+
id: generateId("Flow"),
|
|
348
|
+
sourceRef: joinId,
|
|
349
|
+
targetRef: targetId,
|
|
350
|
+
extensionElements: [],
|
|
351
|
+
unknownAttributes: {},
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function traceBackToSplit(nodeId, splitGateways, flows) {
|
|
357
|
+
const visited = new Set();
|
|
358
|
+
let current = nodeId;
|
|
359
|
+
while (current) {
|
|
360
|
+
if (visited.has(current))
|
|
361
|
+
return undefined;
|
|
362
|
+
visited.add(current);
|
|
363
|
+
if (splitGateways.has(current))
|
|
364
|
+
return current;
|
|
365
|
+
const inFlows = flows.filter((f) => f.targetRef === current);
|
|
366
|
+
if (inFlows.length !== 1)
|
|
367
|
+
return undefined;
|
|
368
|
+
const prev = inFlows[0];
|
|
369
|
+
if (!prev)
|
|
370
|
+
return undefined;
|
|
371
|
+
current = prev.sourceRef;
|
|
372
|
+
}
|
|
373
|
+
return undefined;
|
|
374
|
+
}
|
|
375
|
+
// ---------------------------------------------------------------------------
|
|
239
376
|
// Branch builder (used inside gateway branch callbacks)
|
|
240
377
|
// ---------------------------------------------------------------------------
|
|
241
378
|
/**
|
|
@@ -260,6 +397,11 @@ export class BranchBuilder {
|
|
|
260
397
|
/** @internal – true once connectTo() has been called, meaning the branch end is already wired */
|
|
261
398
|
_connected = false;
|
|
262
399
|
/** @internal */
|
|
400
|
+
_textAnnotations = [];
|
|
401
|
+
/** @internal */
|
|
402
|
+
_associations = [];
|
|
403
|
+
_annCounters = new Map();
|
|
404
|
+
/** @internal */
|
|
263
405
|
constructor(gatewayId, branchName) {
|
|
264
406
|
this.gatewayId = gatewayId;
|
|
265
407
|
this.branchName = branchName;
|
|
@@ -327,47 +469,35 @@ export class BranchBuilder {
|
|
|
327
469
|
get _lastNodeId() {
|
|
328
470
|
return this.lastNodeId;
|
|
329
471
|
}
|
|
472
|
+
// ---- Annotations ----
|
|
473
|
+
/** Attach a text annotation to the element at the current cursor position. */
|
|
474
|
+
textAnnotation(text) {
|
|
475
|
+
return this.annotate(this.lastNodeId, text);
|
|
476
|
+
}
|
|
477
|
+
/** Attach a text annotation to an element by explicit ID. */
|
|
478
|
+
annotate(elementId, text) {
|
|
479
|
+
const n = (this._annCounters.get(elementId) ?? 0) + 1;
|
|
480
|
+
this._annCounters.set(elementId, n);
|
|
481
|
+
const annId = `TextAnnotation_${elementId}_${n}`;
|
|
482
|
+
this._textAnnotations.push({ id: annId, text, unknownAttributes: {} });
|
|
483
|
+
this._associations.push({
|
|
484
|
+
id: `Association_${elementId}_${n}`,
|
|
485
|
+
sourceRef: elementId,
|
|
486
|
+
targetRef: annId,
|
|
487
|
+
associationDirection: "None",
|
|
488
|
+
unknownAttributes: {},
|
|
489
|
+
});
|
|
490
|
+
return this;
|
|
491
|
+
}
|
|
330
492
|
// ---- Flow-node methods (mirror ProcessBuilder) ----
|
|
331
493
|
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);
|
|
494
|
+
return this.addElement(makeServiceTaskEl(id, options));
|
|
345
495
|
}
|
|
346
496
|
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
|
-
}));
|
|
497
|
+
return this.addElement(makeUserTaskEl(id, options));
|
|
354
498
|
}
|
|
355
499
|
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
|
-
}));
|
|
500
|
+
return this.addElement(makeScriptTaskEl(id, options));
|
|
371
501
|
}
|
|
372
502
|
sendTask(id, options) {
|
|
373
503
|
return this.addElement(makeFlowElement(id, "sendTask", options));
|
|
@@ -376,36 +506,10 @@ export class BranchBuilder {
|
|
|
376
506
|
return this.addElement(makeFlowElement(id, "receiveTask", options));
|
|
377
507
|
}
|
|
378
508
|
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
|
-
}));
|
|
509
|
+
return this.addElement(makeBusinessRuleTaskEl(id, options));
|
|
391
510
|
}
|
|
392
511
|
callActivity(id, options) {
|
|
393
|
-
|
|
394
|
-
if (options.propagateAllChildVariables !== undefined) {
|
|
395
|
-
attrs.propagateAllChildVariables = String(options.propagateAllChildVariables);
|
|
396
|
-
}
|
|
397
|
-
return this.addElement(makeFlowElement(id, "callActivity", {
|
|
398
|
-
name: options.name,
|
|
399
|
-
extensionElements: zeebeExtensionsToXmlElements({
|
|
400
|
-
unknownElements: [
|
|
401
|
-
{
|
|
402
|
-
name: "zeebe:calledElement",
|
|
403
|
-
attributes: attrs,
|
|
404
|
-
children: [],
|
|
405
|
-
},
|
|
406
|
-
],
|
|
407
|
-
}),
|
|
408
|
-
}));
|
|
512
|
+
return this.addElement(makeCallActivityEl(id, options));
|
|
409
513
|
}
|
|
410
514
|
startEvent(id, options) {
|
|
411
515
|
const el = makeFlowElement(id ?? generateId("StartEvent"), "startEvent", options);
|
|
@@ -416,7 +520,11 @@ export class BranchBuilder {
|
|
|
416
520
|
return this.addElement(el);
|
|
417
521
|
}
|
|
418
522
|
endEvent(id, options) {
|
|
419
|
-
|
|
523
|
+
const el = makeFlowElement(id ?? generateId("EndEvent"), "endEvent", options);
|
|
524
|
+
if (el.type === "endEvent" && options) {
|
|
525
|
+
el.eventDefinitions = buildEventDefinitions(options);
|
|
526
|
+
}
|
|
527
|
+
return this.addElement(el);
|
|
420
528
|
}
|
|
421
529
|
intermediateThrowEvent(id, options) {
|
|
422
530
|
const el = makeFlowElement(id ?? generateId("IntermediateThrowEvent"), "intermediateThrowEvent", options);
|
|
@@ -433,23 +541,13 @@ export class BranchBuilder {
|
|
|
433
541
|
return this.addElement(el);
|
|
434
542
|
}
|
|
435
543
|
exclusiveGateway(id, options) {
|
|
436
|
-
|
|
437
|
-
if (options?.defaultFlow && el.type === "exclusiveGateway") {
|
|
438
|
-
;
|
|
439
|
-
el.default = options.defaultFlow;
|
|
440
|
-
}
|
|
441
|
-
return this.addElement(el);
|
|
544
|
+
return this.addElement(makeExclusiveGatewayEl(id, options));
|
|
442
545
|
}
|
|
443
546
|
parallelGateway(id, options) {
|
|
444
547
|
return this.addElement(makeFlowElement(id, "parallelGateway", options));
|
|
445
548
|
}
|
|
446
549
|
inclusiveGateway(id, options) {
|
|
447
|
-
|
|
448
|
-
if (options?.defaultFlow && el.type === "inclusiveGateway") {
|
|
449
|
-
;
|
|
450
|
-
el.default = options.defaultFlow;
|
|
451
|
-
}
|
|
452
|
-
return this.addElement(el);
|
|
550
|
+
return this.addElement(makeInclusiveGatewayEl(id, options));
|
|
453
551
|
}
|
|
454
552
|
eventBasedGateway(id, options) {
|
|
455
553
|
return this.addElement(makeFlowElement(id, "eventBasedGateway", options));
|
|
@@ -464,98 +562,182 @@ export class SubProcessContentBuilder {
|
|
|
464
562
|
_elements = [];
|
|
465
563
|
/** @internal */
|
|
466
564
|
_flows = [];
|
|
565
|
+
/** @internal */
|
|
566
|
+
_textAnnotations = [];
|
|
567
|
+
/** @internal */
|
|
568
|
+
_associations = [];
|
|
569
|
+
_annCounters = new Map();
|
|
467
570
|
lastNodeId;
|
|
571
|
+
currentGatewayId;
|
|
572
|
+
openBranchEnds = [];
|
|
468
573
|
addElement(element) {
|
|
574
|
+
if (this._elements.some((n) => n.id === element.id)) {
|
|
575
|
+
throw new Error(`Duplicate element ID "${element.id}" in sub-process`);
|
|
576
|
+
}
|
|
469
577
|
this._elements.push(element);
|
|
470
578
|
if (this.lastNodeId) {
|
|
471
|
-
const flowId = generateId("Flow");
|
|
472
579
|
this._flows.push({
|
|
473
|
-
id:
|
|
580
|
+
id: generateId("Flow"),
|
|
474
581
|
sourceRef: this.lastNodeId,
|
|
475
582
|
targetRef: element.id,
|
|
476
583
|
extensionElements: [],
|
|
477
584
|
unknownAttributes: {},
|
|
478
585
|
});
|
|
479
586
|
}
|
|
587
|
+
for (const branchEnd of this.openBranchEnds) {
|
|
588
|
+
this._flows.push({
|
|
589
|
+
id: generateId("Flow"),
|
|
590
|
+
sourceRef: branchEnd,
|
|
591
|
+
targetRef: element.id,
|
|
592
|
+
extensionElements: [],
|
|
593
|
+
unknownAttributes: {},
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
this.openBranchEnds = [];
|
|
480
597
|
this.lastNodeId = element.id;
|
|
481
598
|
return this;
|
|
482
599
|
}
|
|
600
|
+
// ---- Events ----
|
|
483
601
|
startEvent(id, options) {
|
|
484
602
|
const el = makeFlowElement(id ?? generateId("StartEvent"), "startEvent", options);
|
|
485
|
-
if (el.type === "startEvent" && options)
|
|
603
|
+
if (el.type === "startEvent" && options)
|
|
486
604
|
el.eventDefinitions = buildEventDefinitions(options);
|
|
487
|
-
}
|
|
488
605
|
return this.addElement(el);
|
|
489
606
|
}
|
|
490
607
|
endEvent(id, options) {
|
|
491
|
-
|
|
608
|
+
const el = makeFlowElement(id ?? generateId("EndEvent"), "endEvent", options);
|
|
609
|
+
if (el.type === "endEvent" && options)
|
|
610
|
+
el.eventDefinitions = buildEventDefinitions(options);
|
|
611
|
+
return this.addElement(el);
|
|
492
612
|
}
|
|
493
|
-
|
|
494
|
-
const
|
|
495
|
-
if (options
|
|
496
|
-
|
|
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;
|
|
613
|
+
intermediateThrowEvent(id, options) {
|
|
614
|
+
const el = makeFlowElement(id ?? generateId("IntermediateThrowEvent"), "intermediateThrowEvent", options);
|
|
615
|
+
if (el.type === "intermediateThrowEvent" && options)
|
|
616
|
+
el.eventDefinitions = buildEventDefinitions(options);
|
|
506
617
|
return this.addElement(el);
|
|
507
618
|
}
|
|
508
|
-
|
|
509
|
-
|
|
619
|
+
intermediateCatchEvent(id, options) {
|
|
620
|
+
const el = makeFlowElement(id ?? generateId("IntermediateCatchEvent"), "intermediateCatchEvent", options);
|
|
621
|
+
if (el.type === "intermediateCatchEvent" && options)
|
|
622
|
+
el.eventDefinitions = buildEventDefinitions(options);
|
|
623
|
+
return this.addElement(el);
|
|
624
|
+
}
|
|
625
|
+
// ---- Tasks ----
|
|
626
|
+
serviceTask(id, options) {
|
|
627
|
+
return this.addElement(makeServiceTaskEl(id, options));
|
|
510
628
|
}
|
|
511
629
|
scriptTask(id, options) {
|
|
512
|
-
return this.addElement(
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
expression: options.expression,
|
|
520
|
-
resultVariable: options.resultVariable,
|
|
521
|
-
},
|
|
522
|
-
children: [],
|
|
523
|
-
},
|
|
524
|
-
],
|
|
525
|
-
}),
|
|
526
|
-
}));
|
|
630
|
+
return this.addElement(makeScriptTaskEl(id, options));
|
|
631
|
+
}
|
|
632
|
+
userTask(id, options) {
|
|
633
|
+
return this.addElement(makeUserTaskEl(id, options));
|
|
634
|
+
}
|
|
635
|
+
businessRuleTask(id, options) {
|
|
636
|
+
return this.addElement(makeBusinessRuleTaskEl(id, options));
|
|
527
637
|
}
|
|
528
638
|
callActivity(id, options) {
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
639
|
+
return this.addElement(makeCallActivityEl(id, options));
|
|
640
|
+
}
|
|
641
|
+
sendTask(id, options) {
|
|
642
|
+
return this.addElement(makeFlowElement(id, "sendTask", options));
|
|
643
|
+
}
|
|
644
|
+
receiveTask(id, options) {
|
|
645
|
+
return this.addElement(makeFlowElement(id, "receiveTask", options));
|
|
646
|
+
}
|
|
647
|
+
// ---- Gateways ----
|
|
648
|
+
exclusiveGateway(id, options) {
|
|
649
|
+
this.currentGatewayId = id;
|
|
650
|
+
return this.addElement(makeExclusiveGatewayEl(id, options));
|
|
651
|
+
}
|
|
652
|
+
parallelGateway(id, options) {
|
|
653
|
+
this.currentGatewayId = id;
|
|
654
|
+
return this.addElement(makeFlowElement(id, "parallelGateway", options));
|
|
655
|
+
}
|
|
656
|
+
inclusiveGateway(id, options) {
|
|
657
|
+
this.currentGatewayId = id;
|
|
658
|
+
return this.addElement(makeInclusiveGatewayEl(id, options));
|
|
659
|
+
}
|
|
660
|
+
eventBasedGateway(id, options) {
|
|
661
|
+
this.currentGatewayId = id;
|
|
662
|
+
return this.addElement(makeFlowElement(id, "eventBasedGateway", options));
|
|
663
|
+
}
|
|
664
|
+
// ---- Annotations ----
|
|
665
|
+
/** Attach a text annotation to the element at the current cursor position. */
|
|
666
|
+
textAnnotation(text) {
|
|
667
|
+
if (!this.lastNodeId) {
|
|
668
|
+
throw new Error("textAnnotation() must follow a flow element");
|
|
532
669
|
}
|
|
533
|
-
return this.
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
670
|
+
return this.annotate(this.lastNodeId, text);
|
|
671
|
+
}
|
|
672
|
+
/** Attach a text annotation to an element by explicit ID. */
|
|
673
|
+
annotate(elementId, text) {
|
|
674
|
+
const n = (this._annCounters.get(elementId) ?? 0) + 1;
|
|
675
|
+
this._annCounters.set(elementId, n);
|
|
676
|
+
const annId = `TextAnnotation_${elementId}_${n}`;
|
|
677
|
+
this._textAnnotations.push({ id: annId, text, unknownAttributes: {} });
|
|
678
|
+
this._associations.push({
|
|
679
|
+
id: `Association_${elementId}_${n}`,
|
|
680
|
+
sourceRef: elementId,
|
|
681
|
+
targetRef: annId,
|
|
682
|
+
associationDirection: "None",
|
|
683
|
+
unknownAttributes: {},
|
|
684
|
+
});
|
|
685
|
+
return this;
|
|
545
686
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
if (
|
|
549
|
-
|
|
687
|
+
// ---- Branching & flow control ----
|
|
688
|
+
branch(name, callback) {
|
|
689
|
+
if (!this.currentGatewayId) {
|
|
690
|
+
throw new Error("branch() must be called after a gateway element");
|
|
550
691
|
}
|
|
551
|
-
|
|
692
|
+
const b = new BranchBuilder(this.currentGatewayId, name);
|
|
693
|
+
callback(b);
|
|
694
|
+
for (const el of b._elements) {
|
|
695
|
+
if (this._elements.some((n) => n.id === el.id)) {
|
|
696
|
+
throw new Error(`Duplicate element ID "${el.id}"`);
|
|
697
|
+
}
|
|
698
|
+
this._elements.push(el);
|
|
699
|
+
}
|
|
700
|
+
for (const fl of b._flows)
|
|
701
|
+
this._flows.push(fl);
|
|
702
|
+
for (const ann of b._textAnnotations)
|
|
703
|
+
this._textAnnotations.push(ann);
|
|
704
|
+
for (const assoc of b._associations)
|
|
705
|
+
this._associations.push(assoc);
|
|
706
|
+
if (b._defaultFlowId) {
|
|
707
|
+
const gw = this._elements.find((n) => n.id === this.currentGatewayId);
|
|
708
|
+
if (gw && (gw.type === "exclusiveGateway" || gw.type === "inclusiveGateway")) {
|
|
709
|
+
gw.default = b._defaultFlowId;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
if (!b._connected && b._elements.length > 0) {
|
|
713
|
+
const lastEl = b._elements[b._elements.length - 1];
|
|
714
|
+
if (lastEl && lastEl.type !== "endEvent") {
|
|
715
|
+
this.openBranchEnds.push(b._lastNodeId);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
this.lastNodeId = undefined;
|
|
719
|
+
return this;
|
|
552
720
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
721
|
+
connectTo(targetId) {
|
|
722
|
+
if (this.lastNodeId) {
|
|
723
|
+
this._flows.push({
|
|
724
|
+
id: generateId("Flow"),
|
|
725
|
+
sourceRef: this.lastNodeId,
|
|
726
|
+
targetRef: targetId,
|
|
727
|
+
extensionElements: [],
|
|
728
|
+
unknownAttributes: {},
|
|
729
|
+
});
|
|
557
730
|
}
|
|
558
|
-
|
|
731
|
+
this.lastNodeId = undefined;
|
|
732
|
+
return this;
|
|
733
|
+
}
|
|
734
|
+
element(elementId) {
|
|
735
|
+
if (!this._elements.some((n) => n.id === elementId)) {
|
|
736
|
+
throw new Error(`Element "${elementId}" not found in sub-process`);
|
|
737
|
+
}
|
|
738
|
+
this.lastNodeId = elementId;
|
|
739
|
+
this.currentGatewayId = undefined;
|
|
740
|
+
return this;
|
|
559
741
|
}
|
|
560
742
|
}
|
|
561
743
|
// ---------------------------------------------------------------------------
|
|
@@ -571,10 +753,15 @@ export class ProcessBuilder {
|
|
|
571
753
|
sequenceFlows = [];
|
|
572
754
|
rootErrors = [];
|
|
573
755
|
rootMessages = [];
|
|
756
|
+
_textAnnotations = [];
|
|
757
|
+
_associations = [];
|
|
758
|
+
_annCounters = new Map();
|
|
574
759
|
lastNodeId;
|
|
575
760
|
currentGatewayId;
|
|
576
761
|
openBranchEnds = [];
|
|
577
762
|
_autoLayout = false;
|
|
763
|
+
_executionPlatformVersion = "8.9.0";
|
|
764
|
+
_serviceTaskDefaults = {};
|
|
578
765
|
constructor(processId) {
|
|
579
766
|
this.processId = processId;
|
|
580
767
|
}
|
|
@@ -583,6 +770,17 @@ export class ProcessBuilder {
|
|
|
583
770
|
this._autoLayout = true;
|
|
584
771
|
return this;
|
|
585
772
|
}
|
|
773
|
+
/** Set the Camunda execution platform version stamped into the BPMN definitions. Defaults to `"8.9.0"`. */
|
|
774
|
+
executionPlatformVersion(version) {
|
|
775
|
+
this._executionPlatformVersion = version;
|
|
776
|
+
return this;
|
|
777
|
+
}
|
|
778
|
+
/** Set process-wide defaults applied to subsequently added elements. */
|
|
779
|
+
defaults(options) {
|
|
780
|
+
if (options.serviceTask)
|
|
781
|
+
this._serviceTaskDefaults = { ...this._serviceTaskDefaults, ...options.serviceTask };
|
|
782
|
+
return this;
|
|
783
|
+
}
|
|
586
784
|
/** Set the display name for this process. */
|
|
587
785
|
name(name) {
|
|
588
786
|
this.processName = name;
|
|
@@ -639,10 +837,22 @@ export class ProcessBuilder {
|
|
|
639
837
|
this.currentGatewayId = undefined;
|
|
640
838
|
return this.startEvent(id, options);
|
|
641
839
|
}
|
|
840
|
+
/**
|
|
841
|
+
* Alias for `addStartEvent()` — begins a new disconnected parallel path.
|
|
842
|
+
*
|
|
843
|
+
* Use this for readability when modeling processes with multiple independent paths.
|
|
844
|
+
*/
|
|
845
|
+
disconnectedStartEvent(id, options) {
|
|
846
|
+
return this.addStartEvent(id, options);
|
|
847
|
+
}
|
|
642
848
|
/** Add an end event. */
|
|
643
849
|
endEvent(id, options) {
|
|
644
850
|
const nodeId = id ?? generateId("EndEvent");
|
|
645
|
-
|
|
851
|
+
const element = makeFlowElement(nodeId, "endEvent", options);
|
|
852
|
+
if (element.type === "endEvent" && options) {
|
|
853
|
+
element.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages);
|
|
854
|
+
}
|
|
855
|
+
this.addFlowElement(element);
|
|
646
856
|
return this;
|
|
647
857
|
}
|
|
648
858
|
/** Add an intermediate throw event (none, message, signal, escalation). */
|
|
@@ -686,22 +896,41 @@ export class ProcessBuilder {
|
|
|
686
896
|
void prevLast;
|
|
687
897
|
return this;
|
|
688
898
|
}
|
|
899
|
+
/**
|
|
900
|
+
* Attach a boundary event to the preceding task and build its outgoing path,
|
|
901
|
+
* then restore the builder cursor to the preceding task so the main flow continues.
|
|
902
|
+
*
|
|
903
|
+
* @param id - ID for the boundary event element.
|
|
904
|
+
* @param options - Boundary event options (without `attachedTo` — inferred from cursor).
|
|
905
|
+
* @param handler - Callback that chains elements from the boundary event.
|
|
906
|
+
*/
|
|
907
|
+
withBoundary(id, options, handler) {
|
|
908
|
+
const attachedTo = this.lastNodeId;
|
|
909
|
+
if (!attachedTo) {
|
|
910
|
+
throw new Error("withBoundary() must follow a task element. Current builder position has no active element.");
|
|
911
|
+
}
|
|
912
|
+
const savedLast = this.lastNodeId;
|
|
913
|
+
const savedGateway = this.currentGatewayId;
|
|
914
|
+
const savedOpenEnds = [...this.openBranchEnds];
|
|
915
|
+
this.openBranchEnds = [];
|
|
916
|
+
// boundaryEvent() sets lastNodeId to the boundary event id
|
|
917
|
+
this.boundaryEvent(id, { ...options, attachedTo });
|
|
918
|
+
// Build the error/timeout path chaining from the boundary event
|
|
919
|
+
handler(this);
|
|
920
|
+
// Restore cursor to the original task so the main flow continues
|
|
921
|
+
this.lastNodeId = savedLast;
|
|
922
|
+
this.currentGatewayId = savedGateway;
|
|
923
|
+
this.openBranchEnds = savedOpenEnds;
|
|
924
|
+
return this;
|
|
925
|
+
}
|
|
689
926
|
// ---- Tasks ----
|
|
690
927
|
/** Add a service task with Zeebe task definition and optional IO mappings. */
|
|
691
928
|
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);
|
|
929
|
+
const merged = {
|
|
930
|
+
...options,
|
|
931
|
+
retries: options.retries ?? this._serviceTaskDefaults.retries,
|
|
932
|
+
};
|
|
933
|
+
this.addFlowElement(makeServiceTaskEl(id, merged));
|
|
705
934
|
return this;
|
|
706
935
|
}
|
|
707
936
|
/** Add a REST connector task — syntactic sugar over `serviceTask()`. */
|
|
@@ -732,32 +961,12 @@ export class ProcessBuilder {
|
|
|
732
961
|
}
|
|
733
962
|
/** Add a script task with a FEEL expression. */
|
|
734
963
|
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
|
-
}));
|
|
964
|
+
this.addFlowElement(makeScriptTaskEl(id, options));
|
|
750
965
|
return this;
|
|
751
966
|
}
|
|
752
967
|
/** Add a user task with optional form reference. */
|
|
753
968
|
userTask(id, options) {
|
|
754
|
-
|
|
755
|
-
? zeebeExtensionsToXmlElements({ formDefinition: { formId: options.formId } })
|
|
756
|
-
: [];
|
|
757
|
-
this.addFlowElement(makeFlowElement(id, "userTask", {
|
|
758
|
-
name: options?.name,
|
|
759
|
-
extensionElements,
|
|
760
|
-
}));
|
|
969
|
+
this.addFlowElement(makeUserTaskEl(id, options));
|
|
761
970
|
return this;
|
|
762
971
|
}
|
|
763
972
|
/** Add a send task (aspirational). */
|
|
@@ -772,55 +981,18 @@ export class ProcessBuilder {
|
|
|
772
981
|
}
|
|
773
982
|
/** Add a business rule task. */
|
|
774
983
|
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
|
-
}));
|
|
984
|
+
this.addFlowElement(makeBusinessRuleTaskEl(id, options));
|
|
791
985
|
return this;
|
|
792
986
|
}
|
|
793
987
|
/** Add a call activity referencing another process. */
|
|
794
988
|
callActivity(id, options) {
|
|
795
|
-
|
|
796
|
-
processId: options.processId,
|
|
797
|
-
};
|
|
798
|
-
if (options.propagateAllChildVariables !== undefined) {
|
|
799
|
-
attrs.propagateAllChildVariables = String(options.propagateAllChildVariables);
|
|
800
|
-
}
|
|
801
|
-
this.addFlowElement(makeFlowElement(id, "callActivity", {
|
|
802
|
-
name: options.name,
|
|
803
|
-
extensionElements: zeebeExtensionsToXmlElements({
|
|
804
|
-
unknownElements: [
|
|
805
|
-
{
|
|
806
|
-
name: "zeebe:calledElement",
|
|
807
|
-
attributes: attrs,
|
|
808
|
-
children: [],
|
|
809
|
-
},
|
|
810
|
-
],
|
|
811
|
-
}),
|
|
812
|
-
}));
|
|
989
|
+
this.addFlowElement(makeCallActivityEl(id, options));
|
|
813
990
|
return this;
|
|
814
991
|
}
|
|
815
992
|
// ---- Gateways ----
|
|
816
993
|
/** Add an exclusive gateway (XOR split/join). */
|
|
817
994
|
exclusiveGateway(id, options) {
|
|
818
|
-
|
|
819
|
-
if (options?.defaultFlow && element.type === "exclusiveGateway") {
|
|
820
|
-
;
|
|
821
|
-
element.default = options.defaultFlow;
|
|
822
|
-
}
|
|
823
|
-
this.addFlowElement(element);
|
|
995
|
+
this.addFlowElement(makeExclusiveGatewayEl(id, options));
|
|
824
996
|
this.currentGatewayId = id;
|
|
825
997
|
return this;
|
|
826
998
|
}
|
|
@@ -832,12 +1004,7 @@ export class ProcessBuilder {
|
|
|
832
1004
|
}
|
|
833
1005
|
/** Add an inclusive gateway (OR split/join). Aspirational. */
|
|
834
1006
|
inclusiveGateway(id, options) {
|
|
835
|
-
|
|
836
|
-
if (options?.defaultFlow && element.type === "inclusiveGateway") {
|
|
837
|
-
;
|
|
838
|
-
element.default = options.defaultFlow;
|
|
839
|
-
}
|
|
840
|
-
this.addFlowElement(element);
|
|
1007
|
+
this.addFlowElement(makeInclusiveGatewayEl(id, options));
|
|
841
1008
|
this.currentGatewayId = id;
|
|
842
1009
|
return this;
|
|
843
1010
|
}
|
|
@@ -880,6 +1047,10 @@ export class ProcessBuilder {
|
|
|
880
1047
|
for (const fl of b._flows) {
|
|
881
1048
|
this.sequenceFlows.push(fl);
|
|
882
1049
|
}
|
|
1050
|
+
for (const ann of b._textAnnotations)
|
|
1051
|
+
this._textAnnotations.push(ann);
|
|
1052
|
+
for (const assoc of b._associations)
|
|
1053
|
+
this._associations.push(assoc);
|
|
883
1054
|
// If the branch is the default flow, set the gateway's default
|
|
884
1055
|
if (b._defaultFlowId) {
|
|
885
1056
|
const gateway = this.flowElements.find((n) => n.id === this.currentGatewayId);
|
|
@@ -933,6 +1104,7 @@ export class ProcessBuilder {
|
|
|
933
1104
|
adHocSubProcess(id, content, options) {
|
|
934
1105
|
const sub = new SubProcessContentBuilder();
|
|
935
1106
|
content(sub);
|
|
1107
|
+
insertJoinGateways(sub._elements, sub._flows);
|
|
936
1108
|
recomputeIncomingOutgoing(sub._elements, sub._flows);
|
|
937
1109
|
const zeebeExt = {};
|
|
938
1110
|
if (options?.taskDefinition) {
|
|
@@ -984,6 +1156,8 @@ export class ProcessBuilder {
|
|
|
984
1156
|
if (element.type === "adHocSubProcess") {
|
|
985
1157
|
element.flowElements = sub._elements;
|
|
986
1158
|
element.sequenceFlows = sub._flows;
|
|
1159
|
+
element.textAnnotations = sub._textAnnotations;
|
|
1160
|
+
element.associations = sub._associations;
|
|
987
1161
|
if (options?.loopCharacteristics) {
|
|
988
1162
|
element.loopCharacteristics = buildAdHocLoopCharacteristics(options.loopCharacteristics);
|
|
989
1163
|
}
|
|
@@ -998,11 +1172,14 @@ export class ProcessBuilder {
|
|
|
998
1172
|
subProcess(id, content, options) {
|
|
999
1173
|
const sub = new SubProcessContentBuilder();
|
|
1000
1174
|
content(sub);
|
|
1175
|
+
insertJoinGateways(sub._elements, sub._flows);
|
|
1001
1176
|
recomputeIncomingOutgoing(sub._elements, sub._flows);
|
|
1002
1177
|
const element = makeFlowElement(id, "subProcess", options);
|
|
1003
1178
|
if (element.type === "subProcess") {
|
|
1004
1179
|
element.flowElements = sub._elements;
|
|
1005
1180
|
element.sequenceFlows = sub._flows;
|
|
1181
|
+
element.textAnnotations = sub._textAnnotations;
|
|
1182
|
+
element.associations = sub._associations;
|
|
1006
1183
|
if (options?.multiInstance) {
|
|
1007
1184
|
element.loopCharacteristics = buildMultiInstance(options.multiInstance);
|
|
1008
1185
|
}
|
|
@@ -1014,15 +1191,41 @@ export class ProcessBuilder {
|
|
|
1014
1191
|
eventSubProcess(id, content, options) {
|
|
1015
1192
|
const sub = new SubProcessContentBuilder();
|
|
1016
1193
|
content(sub);
|
|
1194
|
+
insertJoinGateways(sub._elements, sub._flows);
|
|
1017
1195
|
recomputeIncomingOutgoing(sub._elements, sub._flows);
|
|
1018
1196
|
const element = makeFlowElement(id, "eventSubProcess", options);
|
|
1019
1197
|
if (element.type === "eventSubProcess") {
|
|
1020
1198
|
element.flowElements = sub._elements;
|
|
1021
1199
|
element.sequenceFlows = sub._flows;
|
|
1200
|
+
element.textAnnotations = sub._textAnnotations;
|
|
1201
|
+
element.associations = sub._associations;
|
|
1022
1202
|
}
|
|
1023
1203
|
this.addFlowElement(element);
|
|
1024
1204
|
return this;
|
|
1025
1205
|
}
|
|
1206
|
+
// ---- Annotations ----
|
|
1207
|
+
/** Attach a text annotation to the element at the current cursor position. */
|
|
1208
|
+
textAnnotation(text) {
|
|
1209
|
+
if (!this.lastNodeId) {
|
|
1210
|
+
throw new Error("textAnnotation() must follow a flow element");
|
|
1211
|
+
}
|
|
1212
|
+
return this.annotate(this.lastNodeId, text);
|
|
1213
|
+
}
|
|
1214
|
+
/** Attach a text annotation to any flow element by explicit ID. */
|
|
1215
|
+
annotate(elementId, text) {
|
|
1216
|
+
const n = (this._annCounters.get(elementId) ?? 0) + 1;
|
|
1217
|
+
this._annCounters.set(elementId, n);
|
|
1218
|
+
const annId = `TextAnnotation_${elementId}_${n}`;
|
|
1219
|
+
this._textAnnotations.push({ id: annId, text, unknownAttributes: {} });
|
|
1220
|
+
this._associations.push({
|
|
1221
|
+
id: `Association_${elementId}_${n}`,
|
|
1222
|
+
sourceRef: elementId,
|
|
1223
|
+
targetRef: annId,
|
|
1224
|
+
associationDirection: "None",
|
|
1225
|
+
unknownAttributes: {},
|
|
1226
|
+
});
|
|
1227
|
+
return this;
|
|
1228
|
+
}
|
|
1026
1229
|
// ---- Build ----
|
|
1027
1230
|
/**
|
|
1028
1231
|
* Build the complete BPMN definitions model.
|
|
@@ -1030,8 +1233,17 @@ export class ProcessBuilder {
|
|
|
1030
1233
|
* Resolves all forward-referenced `incoming` / `outgoing` arrays and wraps
|
|
1031
1234
|
* the process in a {@link BpmnDefinitions} ready for XML serialization.
|
|
1032
1235
|
*/
|
|
1033
|
-
build() {
|
|
1034
|
-
this.
|
|
1236
|
+
build(options) {
|
|
1237
|
+
const beforeCount = this.flowElements.length;
|
|
1238
|
+
insertJoinGateways(this.flowElements, this.sequenceFlows);
|
|
1239
|
+
if (options?.strict && this.flowElements.length > beforeCount) {
|
|
1240
|
+
const inserted = this.flowElements
|
|
1241
|
+
.slice(beforeCount)
|
|
1242
|
+
.map((e) => e.id)
|
|
1243
|
+
.join(", ");
|
|
1244
|
+
throw new Error(`auto-join gateways were inserted: ${inserted}. Use explicit .connectTo(joinId) to make gateway topology explicit, or remove { strict: true }.`);
|
|
1245
|
+
}
|
|
1246
|
+
this.validate();
|
|
1035
1247
|
recomputeIncomingOutgoing(this.flowElements, this.sequenceFlows);
|
|
1036
1248
|
const extensionElements = [];
|
|
1037
1249
|
if (this._versionTag) {
|
|
@@ -1048,15 +1260,15 @@ export class ProcessBuilder {
|
|
|
1048
1260
|
extensionElements,
|
|
1049
1261
|
flowElements: this.flowElements,
|
|
1050
1262
|
sequenceFlows: this.sequenceFlows,
|
|
1051
|
-
textAnnotations:
|
|
1052
|
-
associations:
|
|
1263
|
+
textAnnotations: this._textAnnotations,
|
|
1264
|
+
associations: this._associations,
|
|
1053
1265
|
unknownAttributes: {},
|
|
1054
1266
|
};
|
|
1055
|
-
|
|
1267
|
+
const defs = {
|
|
1056
1268
|
id: "Definitions_1",
|
|
1057
1269
|
targetNamespace: "http://bpmn.io/schema/bpmn",
|
|
1058
1270
|
exporter: "@bpmnkit/core",
|
|
1059
|
-
exporterVersion:
|
|
1271
|
+
exporterVersion: EXPORTER_VERSION,
|
|
1060
1272
|
namespaces: {
|
|
1061
1273
|
bpmn: "http://www.omg.org/spec/BPMN/20100524/MODEL",
|
|
1062
1274
|
bpmndi: "http://www.omg.org/spec/BPMN/20100524/DI",
|
|
@@ -1068,7 +1280,7 @@ export class ProcessBuilder {
|
|
|
1068
1280
|
},
|
|
1069
1281
|
unknownAttributes: {
|
|
1070
1282
|
"modeler:executionPlatform": "Camunda Cloud",
|
|
1071
|
-
"modeler:executionPlatformVersion":
|
|
1283
|
+
"modeler:executionPlatformVersion": this._executionPlatformVersion,
|
|
1072
1284
|
},
|
|
1073
1285
|
errors: this.rootErrors,
|
|
1074
1286
|
escalations: [],
|
|
@@ -1076,121 +1288,36 @@ export class ProcessBuilder {
|
|
|
1076
1288
|
signals: [],
|
|
1077
1289
|
collaborations: [],
|
|
1078
1290
|
processes: [process],
|
|
1079
|
-
|
|
1291
|
+
// Seed diagram stub so applyAutoLayout preserves process-specific IDs.
|
|
1292
|
+
diagrams: this._autoLayout
|
|
1293
|
+
? [
|
|
1294
|
+
{
|
|
1295
|
+
id: `${this.processId}_di`,
|
|
1296
|
+
plane: {
|
|
1297
|
+
id: `${this.processId}_di_plane`,
|
|
1298
|
+
bpmnElement: this.processId,
|
|
1299
|
+
shapes: [],
|
|
1300
|
+
edges: [],
|
|
1301
|
+
},
|
|
1302
|
+
},
|
|
1303
|
+
]
|
|
1304
|
+
: [],
|
|
1080
1305
|
};
|
|
1306
|
+
return this._autoLayout ? applyAutoLayout(defs) : defs;
|
|
1081
1307
|
}
|
|
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();
|
|
1104
|
-
for (const flow of this.sequenceFlows) {
|
|
1105
|
-
outCount.set(flow.sourceRef, (outCount.get(flow.sourceRef) ?? 0) + 1);
|
|
1106
|
-
}
|
|
1107
|
-
const splitGateways = new Set();
|
|
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);
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
if (splitGateways.size === 0)
|
|
1115
|
-
return;
|
|
1116
|
-
// Build incoming flow map
|
|
1117
|
-
const incoming = new Map();
|
|
1308
|
+
validate() {
|
|
1309
|
+
const elementIds = new Set(this.flowElements.map((el) => el.id));
|
|
1118
1310
|
for (const flow of this.sequenceFlows) {
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
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
|
-
}
|
|
1311
|
+
if (!elementIds.has(flow.targetRef)) {
|
|
1312
|
+
throw new Error(`Sequence flow "${flow.id}" in process "${this.processId}" references unknown ` +
|
|
1313
|
+
`element "${flow.targetRef}". Check connectTo() calls — target must exist.`);
|
|
1141
1314
|
}
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
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
|
-
});
|
|
1315
|
+
if (!elementIds.has(flow.sourceRef)) {
|
|
1316
|
+
throw new Error(`Sequence flow "${flow.id}" in process "${this.processId}" references unknown ` +
|
|
1317
|
+
`source element "${flow.sourceRef}".`);
|
|
1170
1318
|
}
|
|
1171
1319
|
}
|
|
1172
1320
|
}
|
|
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
1321
|
addFlowElement(element) {
|
|
1195
1322
|
if (this.flowElements.some((n) => n.id === element.id)) {
|
|
1196
1323
|
throw new Error(`Duplicate element ID "${element.id}" in process "${this.processId}"`);
|
|
@@ -1221,4 +1348,63 @@ export class ProcessBuilder {
|
|
|
1221
1348
|
this.lastNodeId = element.id;
|
|
1222
1349
|
}
|
|
1223
1350
|
}
|
|
1351
|
+
// ---------------------------------------------------------------------------
|
|
1352
|
+
// Diagram builder — multi-process support
|
|
1353
|
+
// ---------------------------------------------------------------------------
|
|
1354
|
+
/**
|
|
1355
|
+
* Builder for a complete BPMN definitions document containing one or more processes.
|
|
1356
|
+
* Use `Bpmn.createDiagram(id?)` to obtain an instance.
|
|
1357
|
+
*/
|
|
1358
|
+
export class DiagramBuilder {
|
|
1359
|
+
_id;
|
|
1360
|
+
_processes = [];
|
|
1361
|
+
_errors = [];
|
|
1362
|
+
_messages = [];
|
|
1363
|
+
_executionPlatformVersion = "8.9.0";
|
|
1364
|
+
constructor(id) {
|
|
1365
|
+
this._id = id;
|
|
1366
|
+
}
|
|
1367
|
+
/** Set the Camunda execution platform version stamped into the BPMN definitions. Defaults to `"8.9.0"`. */
|
|
1368
|
+
executionPlatformVersion(version) {
|
|
1369
|
+
this._executionPlatformVersion = version;
|
|
1370
|
+
return this;
|
|
1371
|
+
}
|
|
1372
|
+
process(id, callback) {
|
|
1373
|
+
const builder = new ProcessBuilder(id);
|
|
1374
|
+
callback(builder);
|
|
1375
|
+
const defs = builder.build();
|
|
1376
|
+
this._processes.push(...defs.processes);
|
|
1377
|
+
this._errors.push(...defs.errors);
|
|
1378
|
+
this._messages.push(...defs.messages);
|
|
1379
|
+
return this;
|
|
1380
|
+
}
|
|
1381
|
+
build() {
|
|
1382
|
+
return {
|
|
1383
|
+
id: this._id,
|
|
1384
|
+
targetNamespace: "http://bpmn.io/schema/bpmn",
|
|
1385
|
+
exporter: "@bpmnkit/core",
|
|
1386
|
+
exporterVersion: EXPORTER_VERSION,
|
|
1387
|
+
namespaces: {
|
|
1388
|
+
bpmn: "http://www.omg.org/spec/BPMN/20100524/MODEL",
|
|
1389
|
+
bpmndi: "http://www.omg.org/spec/BPMN/20100524/DI",
|
|
1390
|
+
dc: "http://www.omg.org/spec/DD/20100524/DC",
|
|
1391
|
+
di: "http://www.omg.org/spec/DD/20100524/DI",
|
|
1392
|
+
zeebe: "http://camunda.org/schema/zeebe/1.0",
|
|
1393
|
+
modeler: "http://camunda.org/schema/modeler/1.0",
|
|
1394
|
+
xsi: "http://www.w3.org/2001/XMLSchema-instance",
|
|
1395
|
+
},
|
|
1396
|
+
unknownAttributes: {
|
|
1397
|
+
"modeler:executionPlatform": "Camunda Cloud",
|
|
1398
|
+
"modeler:executionPlatformVersion": this._executionPlatformVersion,
|
|
1399
|
+
},
|
|
1400
|
+
errors: this._errors,
|
|
1401
|
+
escalations: [],
|
|
1402
|
+
messages: this._messages,
|
|
1403
|
+
signals: [],
|
|
1404
|
+
collaborations: [],
|
|
1405
|
+
processes: this._processes,
|
|
1406
|
+
diagrams: [],
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1224
1410
|
//# sourceMappingURL=bpmn-builder.js.map
|