@bpmnkit/core 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -1
- package/dist/bpmn/agentic.d.ts +121 -0
- package/dist/bpmn/agentic.js +97 -0
- package/dist/bpmn/auto-layout.d.ts +5 -5
- package/dist/bpmn/auto-layout.js +592 -36
- package/dist/bpmn/bpmn-builder.d.ts +265 -3
- package/dist/bpmn/bpmn-builder.js +603 -197
- package/dist/bpmn/bpmn-model.d.ts +114 -0
- package/dist/bpmn/bpmn-parser.js +1414 -521
- package/dist/bpmn/bpmn-serializer.js +107 -19
- package/dist/bpmn/compact.d.ts +17 -2
- package/dist/bpmn/compact.js +3 -3
- package/dist/bpmn/full-operations.d.ts +89 -0
- package/dist/bpmn/full-operations.js +478 -0
- package/dist/bpmn/index.d.ts +19 -0
- package/dist/bpmn/index.js +21 -0
- package/dist/bpmn/optimize/agentic.d.ts +10 -0
- package/dist/bpmn/optimize/agentic.js +88 -0
- package/dist/bpmn/optimize/deploy.d.ts +16 -0
- package/dist/bpmn/optimize/deploy.js +143 -0
- package/dist/bpmn/optimize/feel-syntax.d.ts +12 -0
- package/dist/bpmn/optimize/feel-syntax.js +87 -0
- package/dist/bpmn/optimize/feel.js +7 -4
- package/dist/bpmn/optimize/flow.js +22 -2
- package/dist/bpmn/optimize/index.js +20 -9
- package/dist/bpmn/optimize/patterns.js +23 -16
- package/dist/bpmn/optimize/tasks.js +30 -7
- package/dist/bpmn/optimize/types.d.ts +10 -1
- package/dist/bpmn/optimize/utils.js +2 -4
- package/dist/bpmn/optimize/variable-flow.js +58 -67
- package/dist/bpmn/semantic-hash.d.ts +93 -0
- package/dist/bpmn/semantic-hash.js +155 -0
- package/dist/bpmn/sha256.d.ts +17 -0
- package/dist/bpmn/sha256.js +95 -0
- package/dist/bpmn/zeebe-extensions.d.ts +83 -0
- package/dist/bpmn/zeebe-extensions.js +117 -0
- package/dist/bpmn/zeebe-placement.d.ts +12 -0
- package/dist/bpmn/zeebe-placement.js +140 -0
- package/dist/errors.d.ts +40 -1
- package/dist/errors.js +41 -0
- package/dist/index.d.ts +16 -5
- package/dist/index.js +9 -3
- package/dist/layout/annotations.js +36 -1
- package/dist/layout/collaboration/alignment.d.ts +26 -0
- package/dist/layout/collaboration/alignment.js +66 -0
- package/dist/layout/collaboration/ordering.d.ts +21 -0
- package/dist/layout/collaboration/ordering.js +102 -0
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/layout-engine.d.ts +13 -3
- package/dist/layout/layout-engine.js +9 -4
- package/dist/layout/semantic/bands.d.ts +19 -0
- package/dist/layout/semantic/bands.js +324 -0
- package/dist/layout/semantic/graph.d.ts +37 -0
- package/dist/layout/semantic/graph.js +242 -0
- package/dist/layout/semantic/index.d.ts +13 -0
- package/dist/layout/semantic/index.js +181 -0
- package/dist/layout/semantic/place.d.ts +40 -0
- package/dist/layout/semantic/place.js +271 -0
- package/dist/layout/semantic/route.d.ts +14 -0
- package/dist/layout/semantic/route.js +514 -0
- package/dist/layout/types.d.ts +17 -0
- package/dist/node/index.d.ts +10 -0
- package/dist/node/index.js +9 -0
- package/dist/node/write.d.ts +81 -0
- package/dist/node/write.js +167 -0
- package/dist/plan/compile.d.ts +39 -0
- package/dist/plan/compile.js +380 -0
- package/dist/plan/extract.d.ts +31 -0
- package/dist/plan/extract.js +248 -0
- package/dist/plan/index.d.ts +6 -0
- package/dist/plan/index.js +5 -0
- package/dist/plan/merge.d.ts +13 -0
- package/dist/plan/merge.js +80 -0
- package/dist/plan/slug.d.ts +5 -0
- package/dist/plan/slug.js +22 -0
- package/dist/plan/types.d.ts +225 -0
- package/dist/plan/types.js +13 -0
- package/dist/types/id-generator.js +11 -3
- package/dist/xml/index.d.ts +3 -1
- package/dist/xml/index.js +2 -1
- package/dist/xml/xml-parser.d.ts +32 -0
- package/dist/xml/xml-parser.js +394 -143
- package/package.json +9 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { generateId } from "../types/id-generator.js";
|
|
2
2
|
import { applyAutoLayout } from "./auto-layout.js";
|
|
3
3
|
import { restConnectorRetries, restConnectorTaskType, restConnectorToIoMappingInputs, restConnectorToTaskHeaders, } from "./rest-connector.js";
|
|
4
|
-
import { zeebeExtensionsToXmlElements } from "./zeebe-extensions.js";
|
|
4
|
+
import { ensureZeebeExtension, zeebeExtensionsToXmlElements, } from "./zeebe-extensions.js";
|
|
5
5
|
// Keep in sync with packages/core/package.json version
|
|
6
6
|
const EXPORTER_VERSION = "0.0.23";
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
@@ -15,6 +15,12 @@ function resolveMessage(messageName, rootMessages) {
|
|
|
15
15
|
}
|
|
16
16
|
return existing.id;
|
|
17
17
|
}
|
|
18
|
+
/** Builds the `zeebe:subscription` extension element for a correlated message catch. */
|
|
19
|
+
function buildMessageSubscriptionExt(correlationKey) {
|
|
20
|
+
if (!correlationKey)
|
|
21
|
+
return [];
|
|
22
|
+
return zeebeExtensionsToXmlElements({ subscription: { correlationKey } });
|
|
23
|
+
}
|
|
18
24
|
function buildEventDefinitions(opts, rootErrors, rootMessages, rootSignals, rootEscalations) {
|
|
19
25
|
const defs = [];
|
|
20
26
|
if (opts.timerDuration || opts.timerDate || opts.timerCycle) {
|
|
@@ -86,6 +92,7 @@ function makeFlowElement(id, type, options) {
|
|
|
86
92
|
name: options?.name,
|
|
87
93
|
incoming: [],
|
|
88
94
|
outgoing: [],
|
|
95
|
+
documentation: options?.documentation,
|
|
89
96
|
extensionElements: options?.extensionElements ?? [],
|
|
90
97
|
unknownAttributes: {},
|
|
91
98
|
};
|
|
@@ -200,6 +207,17 @@ function buildAdHocLoopCharacteristics(lc) {
|
|
|
200
207
|
],
|
|
201
208
|
};
|
|
202
209
|
}
|
|
210
|
+
const GATEWAY_ELEMENT_TYPES = new Set([
|
|
211
|
+
"exclusiveGateway",
|
|
212
|
+
"parallelGateway",
|
|
213
|
+
"inclusiveGateway",
|
|
214
|
+
"eventBasedGateway",
|
|
215
|
+
"complexGateway",
|
|
216
|
+
]);
|
|
217
|
+
/** Gateways are the elements for which several outgoing flows are the point. */
|
|
218
|
+
function isGatewayType(type) {
|
|
219
|
+
return GATEWAY_ELEMENT_TYPES.has(type);
|
|
220
|
+
}
|
|
203
221
|
function recomputeIncomingOutgoing(elements, flows) {
|
|
204
222
|
for (const el of elements) {
|
|
205
223
|
el.incoming = [];
|
|
@@ -232,6 +250,9 @@ function buildServiceTaskExtensions(options) {
|
|
|
232
250
|
})),
|
|
233
251
|
};
|
|
234
252
|
}
|
|
253
|
+
if (options.zeebeProperties) {
|
|
254
|
+
extensions.properties = { properties: options.zeebeProperties };
|
|
255
|
+
}
|
|
235
256
|
return zeebeExtensionsToXmlElements(extensions);
|
|
236
257
|
}
|
|
237
258
|
function makeConditionExpression(expression) {
|
|
@@ -240,6 +261,72 @@ function makeConditionExpression(expression) {
|
|
|
240
261
|
attributes: { "xsi:type": "bpmn:tFormalExpression" },
|
|
241
262
|
};
|
|
242
263
|
}
|
|
264
|
+
/** Builds the Zeebe extension elements + completion condition shared by all `adHocSubProcess()` overloads. */
|
|
265
|
+
function buildAdHocSubProcessExtensions(options) {
|
|
266
|
+
const zeebeExt = {};
|
|
267
|
+
if (options?.taskDefinition) {
|
|
268
|
+
zeebeExt.taskDefinition = options.taskDefinition;
|
|
269
|
+
}
|
|
270
|
+
if (options?.ioMapping) {
|
|
271
|
+
zeebeExt.ioMapping = {
|
|
272
|
+
inputs: options.ioMapping.inputs ?? [],
|
|
273
|
+
outputs: options.ioMapping.outputs ?? [],
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
if (options?.taskHeaders) {
|
|
277
|
+
zeebeExt.taskHeaders = {
|
|
278
|
+
headers: Object.entries(options.taskHeaders).map(([key, value]) => ({ key, value })),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
if (options?.zeebeProperties) {
|
|
282
|
+
zeebeExt.properties = { properties: options.zeebeProperties };
|
|
283
|
+
}
|
|
284
|
+
const extensionElements = zeebeExtensionsToXmlElements(zeebeExt);
|
|
285
|
+
const adHocAttrs = {};
|
|
286
|
+
if (options?.activeElementsCollection) {
|
|
287
|
+
adHocAttrs.activeElementsCollection = options.activeElementsCollection;
|
|
288
|
+
}
|
|
289
|
+
if (options?.outputCollection)
|
|
290
|
+
adHocAttrs.outputCollection = options.outputCollection;
|
|
291
|
+
if (options?.outputElement)
|
|
292
|
+
adHocAttrs.outputElement = options.outputElement;
|
|
293
|
+
if (Object.keys(adHocAttrs).length > 0) {
|
|
294
|
+
extensionElements.push({ name: "zeebe:adHoc", attributes: adHocAttrs, children: [] });
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
extensionElements,
|
|
298
|
+
completionCondition: options?.completionCondition
|
|
299
|
+
? makeConditionExpression(options.completionCondition)
|
|
300
|
+
: undefined,
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
/** Applies modeler-template attrs, sub-process contents, loop characteristics, and completion condition. */
|
|
304
|
+
function finalizeAdHocSubProcessEl(element, options, sub, completionCondition) {
|
|
305
|
+
if (options?.modelerTemplate)
|
|
306
|
+
element.unknownAttributes["zeebe:modelerTemplate"] = options.modelerTemplate;
|
|
307
|
+
if (options?.modelerTemplateVersion) {
|
|
308
|
+
element.unknownAttributes["zeebe:modelerTemplateVersion"] = options.modelerTemplateVersion;
|
|
309
|
+
}
|
|
310
|
+
if (options?.modelerTemplateIcon) {
|
|
311
|
+
element.unknownAttributes["zeebe:modelerTemplateIcon"] = options.modelerTemplateIcon;
|
|
312
|
+
}
|
|
313
|
+
if (element.type !== "adHocSubProcess")
|
|
314
|
+
return;
|
|
315
|
+
element.flowElements = sub._elements;
|
|
316
|
+
element.sequenceFlows = sub._flows;
|
|
317
|
+
element.textAnnotations = sub._textAnnotations;
|
|
318
|
+
element.associations = sub._associations;
|
|
319
|
+
if (options?.loopCharacteristics) {
|
|
320
|
+
element.loopCharacteristics = buildAdHocLoopCharacteristics(options.loopCharacteristics);
|
|
321
|
+
}
|
|
322
|
+
else if (options?.multiInstance) {
|
|
323
|
+
element.loopCharacteristics = buildMultiInstance(options.multiInstance);
|
|
324
|
+
}
|
|
325
|
+
element.completionCondition = completionCondition;
|
|
326
|
+
if (options?.cancelRemainingInstances !== undefined) {
|
|
327
|
+
element.cancelRemainingInstances = options.cancelRemainingInstances;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
243
330
|
// ---------------------------------------------------------------------------
|
|
244
331
|
// Element factory functions — shared by all three builder classes
|
|
245
332
|
// ---------------------------------------------------------------------------
|
|
@@ -253,6 +340,7 @@ function makeServiceTaskEl(id, options) {
|
|
|
253
340
|
unknownAttributes["zeebe:modelerTemplateIcon"] = options.modelerTemplateIcon;
|
|
254
341
|
const el = makeFlowElement(id, "serviceTask", {
|
|
255
342
|
name: options.name,
|
|
343
|
+
documentation: options.documentation,
|
|
256
344
|
extensionElements: buildServiceTaskExtensions(options),
|
|
257
345
|
});
|
|
258
346
|
el.unknownAttributes = unknownAttributes;
|
|
@@ -263,6 +351,7 @@ function makeServiceTaskEl(id, options) {
|
|
|
263
351
|
function makeScriptTaskEl(id, options) {
|
|
264
352
|
const el = makeFlowElement(id, "scriptTask", {
|
|
265
353
|
name: options.name,
|
|
354
|
+
documentation: options.documentation,
|
|
266
355
|
extensionElements: zeebeExtensionsToXmlElements({
|
|
267
356
|
unknownElements: [
|
|
268
357
|
{
|
|
@@ -281,8 +370,27 @@ function makeUserTaskEl(id, options) {
|
|
|
281
370
|
const ext = zeebeExtensionsToXmlElements({
|
|
282
371
|
...(options?.zeebeUserTask ? { userTask: true } : {}),
|
|
283
372
|
...(options?.formId ? { formDefinition: { formId: options.formId } } : {}),
|
|
373
|
+
...(options?.assignee || options?.candidateGroups || options?.candidateUsers
|
|
374
|
+
? {
|
|
375
|
+
assignmentDefinition: {
|
|
376
|
+
assignee: options?.assignee,
|
|
377
|
+
candidateGroups: options?.candidateGroups,
|
|
378
|
+
candidateUsers: options?.candidateUsers,
|
|
379
|
+
},
|
|
380
|
+
}
|
|
381
|
+
: {}),
|
|
382
|
+
...(options?.dueDate || options?.followUpDate
|
|
383
|
+
? { taskSchedule: { dueDate: options?.dueDate, followUpDate: options?.followUpDate } }
|
|
384
|
+
: {}),
|
|
385
|
+
...(options?.priority !== undefined
|
|
386
|
+
? { priorityDefinition: { priority: String(options.priority) } }
|
|
387
|
+
: {}),
|
|
388
|
+
});
|
|
389
|
+
const el = makeFlowElement(id, "userTask", {
|
|
390
|
+
name: options?.name,
|
|
391
|
+
documentation: options?.documentation,
|
|
392
|
+
extensionElements: ext,
|
|
284
393
|
});
|
|
285
|
-
const el = makeFlowElement(id, "userTask", { name: options?.name, extensionElements: ext });
|
|
286
394
|
if (options?.isForCompensation)
|
|
287
395
|
el.isForCompensation = true;
|
|
288
396
|
return el;
|
|
@@ -302,6 +410,7 @@ function makeBusinessRuleTaskEl(id, options) {
|
|
|
302
410
|
}
|
|
303
411
|
const el = makeFlowElement(id, "businessRuleTask", {
|
|
304
412
|
name: options?.name,
|
|
413
|
+
documentation: options?.documentation,
|
|
305
414
|
extensionElements: ext,
|
|
306
415
|
});
|
|
307
416
|
if (options?.isForCompensation)
|
|
@@ -315,6 +424,7 @@ function makeCallActivityEl(id, options) {
|
|
|
315
424
|
}
|
|
316
425
|
const el = makeFlowElement(id, "callActivity", {
|
|
317
426
|
name: options.name,
|
|
427
|
+
documentation: options.documentation,
|
|
318
428
|
extensionElements: zeebeExtensionsToXmlElements({
|
|
319
429
|
unknownElements: [{ name: "zeebe:calledElement", attributes: attrs, children: [] }],
|
|
320
430
|
}),
|
|
@@ -471,6 +581,8 @@ function traceBackToSplit(nodeId, splitGateways, flows) {
|
|
|
471
581
|
export class BranchBuilder {
|
|
472
582
|
/** @internal */
|
|
473
583
|
_elements = [];
|
|
584
|
+
/** Ids in `_elements`, for O(1) duplicate checks. */
|
|
585
|
+
_ids = new Set();
|
|
474
586
|
/** @internal */
|
|
475
587
|
_flows = [];
|
|
476
588
|
/** @internal */
|
|
@@ -518,6 +630,7 @@ export class BranchBuilder {
|
|
|
518
630
|
}
|
|
519
631
|
addElement(element) {
|
|
520
632
|
this._elements.push(element);
|
|
633
|
+
this._ids.add(element.id);
|
|
521
634
|
if (this.lastNodeId) {
|
|
522
635
|
const flowId = generateId("Flow");
|
|
523
636
|
const flow = {
|
|
@@ -667,7 +780,10 @@ export class BranchBuilder {
|
|
|
667
780
|
return this.addElement(el);
|
|
668
781
|
}
|
|
669
782
|
intermediateCatchEvent(id, options) {
|
|
670
|
-
const el = makeFlowElement(id ?? generateId("IntermediateCatchEvent"), "intermediateCatchEvent",
|
|
783
|
+
const el = makeFlowElement(id ?? generateId("IntermediateCatchEvent"), "intermediateCatchEvent", {
|
|
784
|
+
...options,
|
|
785
|
+
extensionElements: buildMessageSubscriptionExt(options?.correlationKey),
|
|
786
|
+
});
|
|
671
787
|
if (el.type === "intermediateCatchEvent" && options) {
|
|
672
788
|
el.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
|
|
673
789
|
}
|
|
@@ -698,7 +814,10 @@ export class BranchBuilder {
|
|
|
698
814
|
* cursor to return to the task afterward.
|
|
699
815
|
*/
|
|
700
816
|
boundaryEvent(id, options) {
|
|
701
|
-
const element = makeFlowElement(id, "boundaryEvent",
|
|
817
|
+
const element = makeFlowElement(id, "boundaryEvent", {
|
|
818
|
+
...options,
|
|
819
|
+
extensionElements: buildMessageSubscriptionExt(options.correlationKey),
|
|
820
|
+
});
|
|
702
821
|
if (element.type === "boundaryEvent") {
|
|
703
822
|
element.attachedToRef = options.attachedTo;
|
|
704
823
|
element.cancelActivity = options.cancelActivity;
|
|
@@ -706,6 +825,7 @@ export class BranchBuilder {
|
|
|
706
825
|
}
|
|
707
826
|
// Push directly — no sequence flow, boundary events attach via attachedToRef
|
|
708
827
|
this._elements.push(element);
|
|
828
|
+
this._ids.add(element.id);
|
|
709
829
|
this.lastNodeId = element.id;
|
|
710
830
|
this.isFirstElement = false;
|
|
711
831
|
return this;
|
|
@@ -770,68 +890,13 @@ export class BranchBuilder {
|
|
|
770
890
|
resolved.content(sub);
|
|
771
891
|
insertJoinGateways(sub._elements, sub._flows);
|
|
772
892
|
recomputeIncomingOutgoing(sub._elements, sub._flows);
|
|
773
|
-
const
|
|
774
|
-
if (resolved.options?.taskDefinition) {
|
|
775
|
-
zeebeExt.taskDefinition = resolved.options.taskDefinition;
|
|
776
|
-
}
|
|
777
|
-
if (resolved.options?.ioMapping) {
|
|
778
|
-
zeebeExt.ioMapping = {
|
|
779
|
-
inputs: resolved.options.ioMapping.inputs ?? [],
|
|
780
|
-
outputs: resolved.options.ioMapping.outputs ?? [],
|
|
781
|
-
};
|
|
782
|
-
}
|
|
783
|
-
if (resolved.options?.taskHeaders) {
|
|
784
|
-
zeebeExt.taskHeaders = {
|
|
785
|
-
headers: Object.entries(resolved.options.taskHeaders).map(([key, value]) => ({
|
|
786
|
-
key,
|
|
787
|
-
value,
|
|
788
|
-
})),
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
|
-
const extensionElements = zeebeExtensionsToXmlElements(zeebeExt);
|
|
792
|
-
const adHocAttrs = {};
|
|
793
|
-
if (resolved.options?.activeElementsCollection) {
|
|
794
|
-
adHocAttrs.activeElementsCollection = resolved.options.activeElementsCollection;
|
|
795
|
-
}
|
|
796
|
-
if (resolved.options?.outputCollection) {
|
|
797
|
-
adHocAttrs.outputCollection = resolved.options.outputCollection;
|
|
798
|
-
}
|
|
799
|
-
if (resolved.options?.outputElement) {
|
|
800
|
-
adHocAttrs.outputElement = resolved.options.outputElement;
|
|
801
|
-
}
|
|
802
|
-
if (Object.keys(adHocAttrs).length > 0) {
|
|
803
|
-
extensionElements.push({
|
|
804
|
-
name: "zeebe:adHoc",
|
|
805
|
-
attributes: adHocAttrs,
|
|
806
|
-
children: [],
|
|
807
|
-
});
|
|
808
|
-
}
|
|
893
|
+
const { extensionElements, completionCondition } = buildAdHocSubProcessExtensions(resolved.options);
|
|
809
894
|
const element = makeFlowElement(id, "adHocSubProcess", {
|
|
810
895
|
name: resolved.options?.name,
|
|
896
|
+
documentation: resolved.options?.documentation,
|
|
811
897
|
extensionElements,
|
|
812
898
|
});
|
|
813
|
-
|
|
814
|
-
element.unknownAttributes["zeebe:modelerTemplate"] = resolved.options.modelerTemplate;
|
|
815
|
-
}
|
|
816
|
-
if (resolved.options?.modelerTemplateVersion) {
|
|
817
|
-
element.unknownAttributes["zeebe:modelerTemplateVersion"] =
|
|
818
|
-
resolved.options.modelerTemplateVersion;
|
|
819
|
-
}
|
|
820
|
-
if (resolved.options?.modelerTemplateIcon) {
|
|
821
|
-
element.unknownAttributes["zeebe:modelerTemplateIcon"] = resolved.options.modelerTemplateIcon;
|
|
822
|
-
}
|
|
823
|
-
if (element.type === "adHocSubProcess") {
|
|
824
|
-
element.flowElements = sub._elements;
|
|
825
|
-
element.sequenceFlows = sub._flows;
|
|
826
|
-
element.textAnnotations = sub._textAnnotations;
|
|
827
|
-
element.associations = sub._associations;
|
|
828
|
-
if (resolved.options?.loopCharacteristics) {
|
|
829
|
-
element.loopCharacteristics = buildAdHocLoopCharacteristics(resolved.options.loopCharacteristics);
|
|
830
|
-
}
|
|
831
|
-
else if (resolved.options?.multiInstance) {
|
|
832
|
-
element.loopCharacteristics = buildMultiInstance(resolved.options.multiInstance);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
899
|
+
finalizeAdHocSubProcessEl(element, resolved.options, sub, completionCondition);
|
|
835
900
|
return this.addElement(element);
|
|
836
901
|
}
|
|
837
902
|
/** Add an event sub-process. Triggered by its start event — no incoming or outgoing sequence flows. */
|
|
@@ -851,10 +916,11 @@ export class BranchBuilder {
|
|
|
851
916
|
}
|
|
852
917
|
// Event sub-processes have no incoming/outgoing sequence flows and must not
|
|
853
918
|
// advance the branch cursor — mirrors ProcessBuilder.eventSubProcess.
|
|
854
|
-
if (this.
|
|
919
|
+
if (this._ids.has(element.id)) {
|
|
855
920
|
throw new Error(`Duplicate element ID "${element.id}"`);
|
|
856
921
|
}
|
|
857
922
|
this._elements.push(element);
|
|
923
|
+
this._ids.add(element.id);
|
|
858
924
|
return this;
|
|
859
925
|
}
|
|
860
926
|
/**
|
|
@@ -871,10 +937,11 @@ export class BranchBuilder {
|
|
|
871
937
|
const b = new BranchBuilder(this.currentGatewayId, name, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
|
|
872
938
|
callback(b);
|
|
873
939
|
for (const el of b._elements) {
|
|
874
|
-
if (this.
|
|
940
|
+
if (this._ids.has(el.id)) {
|
|
875
941
|
throw new Error(`Duplicate element ID "${el.id}"`);
|
|
876
942
|
}
|
|
877
943
|
this._elements.push(el);
|
|
944
|
+
this._ids.add(el.id);
|
|
878
945
|
}
|
|
879
946
|
for (const fl of b._flows)
|
|
880
947
|
this._flows.push(fl);
|
|
@@ -911,6 +978,8 @@ export class BranchBuilder {
|
|
|
911
978
|
export class SubProcessContentBuilder {
|
|
912
979
|
/** @internal */
|
|
913
980
|
_elements = [];
|
|
981
|
+
/** Ids in `_elements`, for O(1) duplicate checks. */
|
|
982
|
+
_ids = new Set();
|
|
914
983
|
/** @internal */
|
|
915
984
|
_flows = [];
|
|
916
985
|
/** @internal */
|
|
@@ -927,10 +996,11 @@ export class SubProcessContentBuilder {
|
|
|
927
996
|
this.rootMessages = rootMessages;
|
|
928
997
|
}
|
|
929
998
|
addElement(element) {
|
|
930
|
-
if (this.
|
|
999
|
+
if (this._ids.has(element.id)) {
|
|
931
1000
|
throw new Error(`Duplicate element ID "${element.id}" in sub-process`);
|
|
932
1001
|
}
|
|
933
1002
|
this._elements.push(element);
|
|
1003
|
+
this._ids.add(element.id);
|
|
934
1004
|
if (this.lastNodeId) {
|
|
935
1005
|
this._flows.push({
|
|
936
1006
|
id: generateId("Flow"),
|
|
@@ -953,6 +1023,22 @@ export class SubProcessContentBuilder {
|
|
|
953
1023
|
this.lastNodeId = element.id;
|
|
954
1024
|
return this;
|
|
955
1025
|
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Add an element with no sequence-flow wiring and no cursor movement.
|
|
1028
|
+
*
|
|
1029
|
+
* Used for ad-hoc sub-process tool activities, which the AI Agent connector
|
|
1030
|
+
* requires to be root nodes (no incoming sequence flow, not a boundary event).
|
|
1031
|
+
* Unlike `serviceTask()`/`userTask()`/etc., this does not auto-connect from
|
|
1032
|
+
* the current cursor and does not move it.
|
|
1033
|
+
*/
|
|
1034
|
+
addDisconnected(element) {
|
|
1035
|
+
if (this._ids.has(element.id)) {
|
|
1036
|
+
throw new Error(`Duplicate element ID "${element.id}" in sub-process`);
|
|
1037
|
+
}
|
|
1038
|
+
this._elements.push(element);
|
|
1039
|
+
this._ids.add(element.id);
|
|
1040
|
+
return this;
|
|
1041
|
+
}
|
|
956
1042
|
// ---- Events ----
|
|
957
1043
|
startEvent(id, options) {
|
|
958
1044
|
const el = makeFlowElement(id ?? generateId("StartEvent"), "startEvent", options);
|
|
@@ -976,7 +1062,10 @@ export class SubProcessContentBuilder {
|
|
|
976
1062
|
return this.addElement(el);
|
|
977
1063
|
}
|
|
978
1064
|
intermediateCatchEvent(id, options) {
|
|
979
|
-
const el = makeFlowElement(id ?? generateId("IntermediateCatchEvent"), "intermediateCatchEvent",
|
|
1065
|
+
const el = makeFlowElement(id ?? generateId("IntermediateCatchEvent"), "intermediateCatchEvent", {
|
|
1066
|
+
...options,
|
|
1067
|
+
extensionElements: buildMessageSubscriptionExt(options?.correlationKey),
|
|
1068
|
+
});
|
|
980
1069
|
if (el.type === "intermediateCatchEvent" && options)
|
|
981
1070
|
el.eventDefinitions = buildEventDefinitions(options);
|
|
982
1071
|
return this.addElement(el);
|
|
@@ -1068,10 +1157,11 @@ export class SubProcessContentBuilder {
|
|
|
1068
1157
|
const b = new BranchBuilder(this.currentGatewayId, name);
|
|
1069
1158
|
callback(b);
|
|
1070
1159
|
for (const el of b._elements) {
|
|
1071
|
-
if (this.
|
|
1160
|
+
if (this._ids.has(el.id)) {
|
|
1072
1161
|
throw new Error(`Duplicate element ID "${el.id}"`);
|
|
1073
1162
|
}
|
|
1074
1163
|
this._elements.push(el);
|
|
1164
|
+
this._ids.add(el.id);
|
|
1075
1165
|
}
|
|
1076
1166
|
for (const fl of b._flows)
|
|
1077
1167
|
this._flows.push(fl);
|
|
@@ -1114,7 +1204,7 @@ export class SubProcessContentBuilder {
|
|
|
1114
1204
|
return this;
|
|
1115
1205
|
}
|
|
1116
1206
|
element(elementId) {
|
|
1117
|
-
if (!this.
|
|
1207
|
+
if (!this._ids.has(elementId)) {
|
|
1118
1208
|
throw new Error(`Element "${elementId}" not found in sub-process`);
|
|
1119
1209
|
}
|
|
1120
1210
|
this.lastNodeId = elementId;
|
|
@@ -1131,7 +1221,10 @@ export class SubProcessContentBuilder {
|
|
|
1131
1221
|
* cursor to return to the task afterward.
|
|
1132
1222
|
*/
|
|
1133
1223
|
boundaryEvent(id, options) {
|
|
1134
|
-
const element = makeFlowElement(id, "boundaryEvent",
|
|
1224
|
+
const element = makeFlowElement(id, "boundaryEvent", {
|
|
1225
|
+
...options,
|
|
1226
|
+
extensionElements: buildMessageSubscriptionExt(options.correlationKey),
|
|
1227
|
+
});
|
|
1135
1228
|
if (element.type === "boundaryEvent") {
|
|
1136
1229
|
element.attachedToRef = options.attachedTo;
|
|
1137
1230
|
element.cancelActivity = options.cancelActivity;
|
|
@@ -1139,6 +1232,7 @@ export class SubProcessContentBuilder {
|
|
|
1139
1232
|
}
|
|
1140
1233
|
// Push directly — no sequence flow, boundary events attach via attachedToRef
|
|
1141
1234
|
this._elements.push(element);
|
|
1235
|
+
this._ids.add(element.id);
|
|
1142
1236
|
this.lastNodeId = element.id;
|
|
1143
1237
|
return this;
|
|
1144
1238
|
}
|
|
@@ -1200,68 +1294,13 @@ export class SubProcessContentBuilder {
|
|
|
1200
1294
|
resolved.content(sub);
|
|
1201
1295
|
insertJoinGateways(sub._elements, sub._flows);
|
|
1202
1296
|
recomputeIncomingOutgoing(sub._elements, sub._flows);
|
|
1203
|
-
const
|
|
1204
|
-
if (resolved.options?.taskDefinition) {
|
|
1205
|
-
zeebeExt.taskDefinition = resolved.options.taskDefinition;
|
|
1206
|
-
}
|
|
1207
|
-
if (resolved.options?.ioMapping) {
|
|
1208
|
-
zeebeExt.ioMapping = {
|
|
1209
|
-
inputs: resolved.options.ioMapping.inputs ?? [],
|
|
1210
|
-
outputs: resolved.options.ioMapping.outputs ?? [],
|
|
1211
|
-
};
|
|
1212
|
-
}
|
|
1213
|
-
if (resolved.options?.taskHeaders) {
|
|
1214
|
-
zeebeExt.taskHeaders = {
|
|
1215
|
-
headers: Object.entries(resolved.options.taskHeaders).map(([key, value]) => ({
|
|
1216
|
-
key,
|
|
1217
|
-
value,
|
|
1218
|
-
})),
|
|
1219
|
-
};
|
|
1220
|
-
}
|
|
1221
|
-
const extensionElements = zeebeExtensionsToXmlElements(zeebeExt);
|
|
1222
|
-
const adHocAttrs = {};
|
|
1223
|
-
if (resolved.options?.activeElementsCollection) {
|
|
1224
|
-
adHocAttrs.activeElementsCollection = resolved.options.activeElementsCollection;
|
|
1225
|
-
}
|
|
1226
|
-
if (resolved.options?.outputCollection) {
|
|
1227
|
-
adHocAttrs.outputCollection = resolved.options.outputCollection;
|
|
1228
|
-
}
|
|
1229
|
-
if (resolved.options?.outputElement) {
|
|
1230
|
-
adHocAttrs.outputElement = resolved.options.outputElement;
|
|
1231
|
-
}
|
|
1232
|
-
if (Object.keys(adHocAttrs).length > 0) {
|
|
1233
|
-
extensionElements.push({
|
|
1234
|
-
name: "zeebe:adHoc",
|
|
1235
|
-
attributes: adHocAttrs,
|
|
1236
|
-
children: [],
|
|
1237
|
-
});
|
|
1238
|
-
}
|
|
1297
|
+
const { extensionElements, completionCondition } = buildAdHocSubProcessExtensions(resolved.options);
|
|
1239
1298
|
const element = makeFlowElement(id, "adHocSubProcess", {
|
|
1240
1299
|
name: resolved.options?.name,
|
|
1300
|
+
documentation: resolved.options?.documentation,
|
|
1241
1301
|
extensionElements,
|
|
1242
1302
|
});
|
|
1243
|
-
|
|
1244
|
-
element.unknownAttributes["zeebe:modelerTemplate"] = resolved.options.modelerTemplate;
|
|
1245
|
-
}
|
|
1246
|
-
if (resolved.options?.modelerTemplateVersion) {
|
|
1247
|
-
element.unknownAttributes["zeebe:modelerTemplateVersion"] =
|
|
1248
|
-
resolved.options.modelerTemplateVersion;
|
|
1249
|
-
}
|
|
1250
|
-
if (resolved.options?.modelerTemplateIcon) {
|
|
1251
|
-
element.unknownAttributes["zeebe:modelerTemplateIcon"] = resolved.options.modelerTemplateIcon;
|
|
1252
|
-
}
|
|
1253
|
-
if (element.type === "adHocSubProcess") {
|
|
1254
|
-
element.flowElements = sub._elements;
|
|
1255
|
-
element.sequenceFlows = sub._flows;
|
|
1256
|
-
element.textAnnotations = sub._textAnnotations;
|
|
1257
|
-
element.associations = sub._associations;
|
|
1258
|
-
if (resolved.options?.loopCharacteristics) {
|
|
1259
|
-
element.loopCharacteristics = buildAdHocLoopCharacteristics(resolved.options.loopCharacteristics);
|
|
1260
|
-
}
|
|
1261
|
-
else if (resolved.options?.multiInstance) {
|
|
1262
|
-
element.loopCharacteristics = buildMultiInstance(resolved.options.multiInstance);
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1303
|
+
finalizeAdHocSubProcessEl(element, resolved.options, sub, completionCondition);
|
|
1265
1304
|
return this.addElement(element);
|
|
1266
1305
|
}
|
|
1267
1306
|
/** Add an event sub-process. Triggered by its start event — no incoming or outgoing sequence flows. */
|
|
@@ -1281,10 +1320,11 @@ export class SubProcessContentBuilder {
|
|
|
1281
1320
|
}
|
|
1282
1321
|
// Event sub-processes have no incoming/outgoing sequence flows and must not
|
|
1283
1322
|
// advance the cursor — mirrors ProcessBuilder.eventSubProcess.
|
|
1284
|
-
if (this.
|
|
1323
|
+
if (this._ids.has(element.id)) {
|
|
1285
1324
|
throw new Error(`Duplicate element ID "${element.id}" in sub-process`);
|
|
1286
1325
|
}
|
|
1287
1326
|
this._elements.push(element);
|
|
1327
|
+
this._ids.add(element.id);
|
|
1288
1328
|
return this;
|
|
1289
1329
|
}
|
|
1290
1330
|
}
|
|
@@ -1296,8 +1336,12 @@ export class ProcessBuilder {
|
|
|
1296
1336
|
processId;
|
|
1297
1337
|
processName;
|
|
1298
1338
|
_isExecutable = true;
|
|
1339
|
+
/** Whether `executable()` was called, so continue-mode leaves it alone if not. */
|
|
1340
|
+
_executableSet = false;
|
|
1299
1341
|
_versionTag;
|
|
1300
1342
|
flowElements = [];
|
|
1343
|
+
/** Ids in `flowElements`, for O(1) duplicate and existence checks. */
|
|
1344
|
+
elementIds = new Set();
|
|
1301
1345
|
sequenceFlows = [];
|
|
1302
1346
|
rootErrors = [];
|
|
1303
1347
|
rootMessages = [];
|
|
@@ -1313,9 +1357,140 @@ export class ProcessBuilder {
|
|
|
1313
1357
|
_executionPlatformVersion = "8.9.0";
|
|
1314
1358
|
_serviceTaskDefaults = {};
|
|
1315
1359
|
_savedMainFlowId = undefined;
|
|
1360
|
+
/** Set by {@link ProcessBuilder.from}; makes `build()` update rather than generate. */
|
|
1361
|
+
_source;
|
|
1362
|
+
/** Pre-existing flow endpoints, so `build()` can prove it did not rewire them. */
|
|
1363
|
+
_sourceFlowTargets = new Map();
|
|
1364
|
+
/** An `insertAfter` flow waiting to be reattached to what gets built next. */
|
|
1365
|
+
_pendingSpliceFlowId;
|
|
1366
|
+
/** Flows the caller deliberately spliced, exempt from the rewiring guard. */
|
|
1367
|
+
_splicedFlowIds = new Set();
|
|
1316
1368
|
constructor(processId) {
|
|
1317
1369
|
this.processId = processId;
|
|
1318
1370
|
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Continues an existing model rather than generating a new one.
|
|
1373
|
+
*
|
|
1374
|
+
* `build()` then returns *that document* with this process's contents
|
|
1375
|
+
* replaced, so everything the builder has no opinion about — other processes,
|
|
1376
|
+
* the collaboration, lanes, diagram interchange, root elements, imports,
|
|
1377
|
+
* unmodelled content — is still there afterwards. Generating a replacement
|
|
1378
|
+
* from scratch is what loses those.
|
|
1379
|
+
*
|
|
1380
|
+
* The input is not mutated; the builder works on a copy.
|
|
1381
|
+
*
|
|
1382
|
+
* @param definitions - The parsed model to continue.
|
|
1383
|
+
* @param processId - Which process to continue. Named explicitly, because
|
|
1384
|
+
* "the first process" is a guess that goes wrong on a collaboration.
|
|
1385
|
+
* @example
|
|
1386
|
+
* ```typescript
|
|
1387
|
+
* const updated = ProcessBuilder.from(Bpmn.parse(xml), "order")
|
|
1388
|
+
* .at("validate")
|
|
1389
|
+
* .serviceTask("notify", { name: "Notify", taskType: "notify" })
|
|
1390
|
+
* .build()
|
|
1391
|
+
* ```
|
|
1392
|
+
*/
|
|
1393
|
+
static from(definitions, processId) {
|
|
1394
|
+
const copy = structuredClone(definitions);
|
|
1395
|
+
const process = copy.processes.find((candidate) => candidate.id === processId);
|
|
1396
|
+
if (process === undefined) {
|
|
1397
|
+
const available = copy.processes.map((candidate) => candidate.id);
|
|
1398
|
+
throw new Error(`Process "${processId}" is not in this document. It contains: ${available.length > 0 ? available.map((id) => `"${id}"`).join(", ") : "no processes"}`);
|
|
1399
|
+
}
|
|
1400
|
+
const builder = new ProcessBuilder(processId);
|
|
1401
|
+
builder._source = { definitions: copy, process };
|
|
1402
|
+
builder.processName = process.name;
|
|
1403
|
+
builder.flowElements.push(...process.flowElements);
|
|
1404
|
+
for (const element of process.flowElements)
|
|
1405
|
+
builder.elementIds.add(element.id);
|
|
1406
|
+
builder.sequenceFlows.push(...process.sequenceFlows);
|
|
1407
|
+
builder._textAnnotations.push(...process.textAnnotations);
|
|
1408
|
+
builder._associations.push(...process.associations);
|
|
1409
|
+
// Seeded so a message event reuses the document's existing message rather
|
|
1410
|
+
// than declaring a second one with the same name.
|
|
1411
|
+
builder.rootErrors.push(...copy.errors);
|
|
1412
|
+
builder.rootMessages.push(...copy.messages);
|
|
1413
|
+
builder.rootSignals.push(...copy.signals);
|
|
1414
|
+
builder.rootEscalations.push(...copy.escalations);
|
|
1415
|
+
builder._sourceFlowTargets = new Map(process.sequenceFlows.map((flow) => [flow.id, `${flow.sourceRef}→${flow.targetRef}`]));
|
|
1416
|
+
return builder;
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* Moves the cursor to an existing flow node, so the next call chains from it.
|
|
1420
|
+
*
|
|
1421
|
+
* @param nodeId - A flow node directly contained by this process. Nodes inside
|
|
1422
|
+
* a sub-process are not reachable: continuing into one means building that
|
|
1423
|
+
* sub-process, not this one.
|
|
1424
|
+
*/
|
|
1425
|
+
at(nodeId) {
|
|
1426
|
+
const node = this.flowElements.find((element) => element.id === nodeId);
|
|
1427
|
+
if (node === undefined) {
|
|
1428
|
+
throw new Error(`"${nodeId}" is not a flow node in process "${this.processId}". Nodes inside a sub-process cannot be continued from here.`);
|
|
1429
|
+
}
|
|
1430
|
+
if (node.type === "endEvent") {
|
|
1431
|
+
throw new Error(`Cannot continue from end event "${nodeId}": an end event has no outgoing sequence flow.`);
|
|
1432
|
+
}
|
|
1433
|
+
const hasOutgoing = this.sequenceFlows.some((flow) => flow.sourceRef === nodeId);
|
|
1434
|
+
if (hasOutgoing && !isGatewayType(node.type)) {
|
|
1435
|
+
throw new Error(`"${nodeId}" already has an outgoing sequence flow. Continuing from it would give a ${node.type} two outgoing flows, which is an uncontrolled split. Use insertAfter("${nodeId}") to splice into that path instead.`);
|
|
1436
|
+
}
|
|
1437
|
+
this.moveCursor(node);
|
|
1438
|
+
return this;
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Splices what you build next into the path leaving an existing node.
|
|
1442
|
+
*
|
|
1443
|
+
* `insertAfter("validate")` followed by `.serviceTask("notify", …)` turns
|
|
1444
|
+
* `validate → end` into `validate → notify → end`. The existing flow keeps its
|
|
1445
|
+
* id and its target and only changes where it starts, so an edge nobody asked
|
|
1446
|
+
* to move keeps its identity in the diagram and in a diff.
|
|
1447
|
+
*
|
|
1448
|
+
* This is the counterpart to {@link at}, which continues from a node whose
|
|
1449
|
+
* path is open. Which one you mean is not guessable, so it is not guessed.
|
|
1450
|
+
*
|
|
1451
|
+
* @param nodeId - A flow node with exactly one outgoing sequence flow.
|
|
1452
|
+
*/
|
|
1453
|
+
insertAfter(nodeId) {
|
|
1454
|
+
const node = this.flowElements.find((element) => element.id === nodeId);
|
|
1455
|
+
if (node === undefined) {
|
|
1456
|
+
throw new Error(`"${nodeId}" is not a flow node in process "${this.processId}".`);
|
|
1457
|
+
}
|
|
1458
|
+
const outgoing = this.sequenceFlows.filter((flow) => flow.sourceRef === nodeId);
|
|
1459
|
+
if (outgoing.length === 0) {
|
|
1460
|
+
throw new Error(`"${nodeId}" has no outgoing sequence flow, so there is nothing to insert into. Use at("${nodeId}") to continue from it.`);
|
|
1461
|
+
}
|
|
1462
|
+
if (outgoing.length > 1) {
|
|
1463
|
+
throw new Error(`"${nodeId}" has ${outgoing.length} outgoing sequence flows, so "after" is ambiguous. Name the flow's target and insert before that instead.`);
|
|
1464
|
+
}
|
|
1465
|
+
this.resolvePendingSplice();
|
|
1466
|
+
const flow = outgoing[0];
|
|
1467
|
+
this._pendingSpliceFlowId = flow.id;
|
|
1468
|
+
this._splicedFlowIds.add(flow.id);
|
|
1469
|
+
this.moveCursor(node);
|
|
1470
|
+
return this;
|
|
1471
|
+
}
|
|
1472
|
+
/** Points the cursor at an existing node and forgets any branch state. */
|
|
1473
|
+
moveCursor(node) {
|
|
1474
|
+
this.lastNodeId = node.id;
|
|
1475
|
+
this.currentGatewayId = isGatewayType(node.type) ? node.id : undefined;
|
|
1476
|
+
this.openBranchEnds = [];
|
|
1477
|
+
this._savedMainFlowId = undefined;
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Reattaches the flow a pending `insertAfter` detached, to whatever the cursor
|
|
1481
|
+
* has reached. With nothing built in between the cursor has not moved and this
|
|
1482
|
+
* is a no-op, which is the right answer for `insertAfter(x)` followed by
|
|
1483
|
+
* nothing.
|
|
1484
|
+
*/
|
|
1485
|
+
resolvePendingSplice() {
|
|
1486
|
+
const flowId = this._pendingSpliceFlowId;
|
|
1487
|
+
if (flowId === undefined)
|
|
1488
|
+
return;
|
|
1489
|
+
this._pendingSpliceFlowId = undefined;
|
|
1490
|
+
const flow = this.sequenceFlows.find((candidate) => candidate.id === flowId);
|
|
1491
|
+
if (flow !== undefined && this.lastNodeId !== undefined)
|
|
1492
|
+
flow.sourceRef = this.lastNodeId;
|
|
1493
|
+
}
|
|
1319
1494
|
/** Enable auto-layout: `build()` will run the layout engine and populate diagram interchange data. */
|
|
1320
1495
|
withAutoLayout() {
|
|
1321
1496
|
this._autoLayout = true;
|
|
@@ -1340,6 +1515,7 @@ export class ProcessBuilder {
|
|
|
1340
1515
|
/** Set whether this process is executable. */
|
|
1341
1516
|
executable(value) {
|
|
1342
1517
|
this._isExecutable = value;
|
|
1518
|
+
this._executableSet = true;
|
|
1343
1519
|
return this;
|
|
1344
1520
|
}
|
|
1345
1521
|
/** Set the process version tag. */
|
|
@@ -1421,7 +1597,10 @@ export class ProcessBuilder {
|
|
|
1421
1597
|
/** Add an intermediate catch event (timer, message, signal). */
|
|
1422
1598
|
intermediateCatchEvent(id, options) {
|
|
1423
1599
|
const nodeId = id ?? generateId("IntermediateCatchEvent");
|
|
1424
|
-
const element = makeFlowElement(nodeId, "intermediateCatchEvent",
|
|
1600
|
+
const element = makeFlowElement(nodeId, "intermediateCatchEvent", {
|
|
1601
|
+
...options,
|
|
1602
|
+
extensionElements: buildMessageSubscriptionExt(options?.correlationKey),
|
|
1603
|
+
});
|
|
1425
1604
|
if (element.type === "intermediateCatchEvent" && options) {
|
|
1426
1605
|
element.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
|
|
1427
1606
|
}
|
|
@@ -1435,7 +1614,10 @@ export class ProcessBuilder {
|
|
|
1435
1614
|
* They start a new outgoing chain from the boundary event itself.
|
|
1436
1615
|
*/
|
|
1437
1616
|
boundaryEvent(id, options) {
|
|
1438
|
-
const element = makeFlowElement(id, "boundaryEvent",
|
|
1617
|
+
const element = makeFlowElement(id, "boundaryEvent", {
|
|
1618
|
+
...options,
|
|
1619
|
+
extensionElements: buildMessageSubscriptionExt(options.correlationKey),
|
|
1620
|
+
});
|
|
1439
1621
|
if (element.type === "boundaryEvent") {
|
|
1440
1622
|
element.attachedToRef = options.attachedTo;
|
|
1441
1623
|
element.cancelActivity = options.cancelActivity;
|
|
@@ -1616,10 +1798,11 @@ export class ProcessBuilder {
|
|
|
1616
1798
|
const b = new BranchBuilder(this.currentGatewayId, name, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
|
|
1617
1799
|
callback(b);
|
|
1618
1800
|
for (const el of b._elements) {
|
|
1619
|
-
if (this.
|
|
1801
|
+
if (this.elementIds.has(el.id)) {
|
|
1620
1802
|
throw new Error(`Duplicate element ID "${el.id}"`);
|
|
1621
1803
|
}
|
|
1622
1804
|
this.flowElements.push(el);
|
|
1805
|
+
this.elementIds.add(el.id);
|
|
1623
1806
|
}
|
|
1624
1807
|
for (const fl of b._flows) {
|
|
1625
1808
|
this.sequenceFlows.push(fl);
|
|
@@ -1674,7 +1857,7 @@ export class ProcessBuilder {
|
|
|
1674
1857
|
* @throws If no element with the given ID exists.
|
|
1675
1858
|
*/
|
|
1676
1859
|
element(elementId) {
|
|
1677
|
-
const found = this.
|
|
1860
|
+
const found = this.elementIds.has(elementId);
|
|
1678
1861
|
if (!found) {
|
|
1679
1862
|
throw new Error(`Element "${elementId}" not found in process "${this.processId}"`);
|
|
1680
1863
|
}
|
|
@@ -1690,69 +1873,13 @@ export class ProcessBuilder {
|
|
|
1690
1873
|
resolved.content(sub);
|
|
1691
1874
|
insertJoinGateways(sub._elements, sub._flows);
|
|
1692
1875
|
recomputeIncomingOutgoing(sub._elements, sub._flows);
|
|
1693
|
-
const
|
|
1694
|
-
if (resolved.options?.taskDefinition) {
|
|
1695
|
-
zeebeExt.taskDefinition = resolved.options.taskDefinition;
|
|
1696
|
-
}
|
|
1697
|
-
if (resolved.options?.ioMapping) {
|
|
1698
|
-
zeebeExt.ioMapping = {
|
|
1699
|
-
inputs: resolved.options.ioMapping.inputs ?? [],
|
|
1700
|
-
outputs: resolved.options.ioMapping.outputs ?? [],
|
|
1701
|
-
};
|
|
1702
|
-
}
|
|
1703
|
-
if (resolved.options?.taskHeaders) {
|
|
1704
|
-
zeebeExt.taskHeaders = {
|
|
1705
|
-
headers: Object.entries(resolved.options.taskHeaders).map(([key, value]) => ({
|
|
1706
|
-
key,
|
|
1707
|
-
value,
|
|
1708
|
-
})),
|
|
1709
|
-
};
|
|
1710
|
-
}
|
|
1711
|
-
const extensionElements = zeebeExtensionsToXmlElements(zeebeExt);
|
|
1712
|
-
// zeebe:adHoc element
|
|
1713
|
-
const adHocAttrs = {};
|
|
1714
|
-
if (resolved.options?.activeElementsCollection) {
|
|
1715
|
-
adHocAttrs.activeElementsCollection = resolved.options.activeElementsCollection;
|
|
1716
|
-
}
|
|
1717
|
-
if (resolved.options?.outputCollection) {
|
|
1718
|
-
adHocAttrs.outputCollection = resolved.options.outputCollection;
|
|
1719
|
-
}
|
|
1720
|
-
if (resolved.options?.outputElement) {
|
|
1721
|
-
adHocAttrs.outputElement = resolved.options.outputElement;
|
|
1722
|
-
}
|
|
1723
|
-
if (Object.keys(adHocAttrs).length > 0) {
|
|
1724
|
-
extensionElements.push({
|
|
1725
|
-
name: "zeebe:adHoc",
|
|
1726
|
-
attributes: adHocAttrs,
|
|
1727
|
-
children: [],
|
|
1728
|
-
});
|
|
1729
|
-
}
|
|
1876
|
+
const { extensionElements, completionCondition } = buildAdHocSubProcessExtensions(resolved.options);
|
|
1730
1877
|
const element = makeFlowElement(id, "adHocSubProcess", {
|
|
1731
1878
|
name: resolved.options?.name,
|
|
1879
|
+
documentation: resolved.options?.documentation,
|
|
1732
1880
|
extensionElements,
|
|
1733
1881
|
});
|
|
1734
|
-
|
|
1735
|
-
element.unknownAttributes["zeebe:modelerTemplate"] = resolved.options.modelerTemplate;
|
|
1736
|
-
}
|
|
1737
|
-
if (resolved.options?.modelerTemplateVersion) {
|
|
1738
|
-
element.unknownAttributes["zeebe:modelerTemplateVersion"] =
|
|
1739
|
-
resolved.options.modelerTemplateVersion;
|
|
1740
|
-
}
|
|
1741
|
-
if (resolved.options?.modelerTemplateIcon) {
|
|
1742
|
-
element.unknownAttributes["zeebe:modelerTemplateIcon"] = resolved.options.modelerTemplateIcon;
|
|
1743
|
-
}
|
|
1744
|
-
if (element.type === "adHocSubProcess") {
|
|
1745
|
-
element.flowElements = sub._elements;
|
|
1746
|
-
element.sequenceFlows = sub._flows;
|
|
1747
|
-
element.textAnnotations = sub._textAnnotations;
|
|
1748
|
-
element.associations = sub._associations;
|
|
1749
|
-
if (resolved.options?.loopCharacteristics) {
|
|
1750
|
-
element.loopCharacteristics = buildAdHocLoopCharacteristics(resolved.options.loopCharacteristics);
|
|
1751
|
-
}
|
|
1752
|
-
else if (resolved.options?.multiInstance) {
|
|
1753
|
-
element.loopCharacteristics = buildMultiInstance(resolved.options.multiInstance);
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1882
|
+
finalizeAdHocSubProcessEl(element, resolved.options, sub, completionCondition);
|
|
1756
1883
|
this.addFlowElement(element);
|
|
1757
1884
|
return this;
|
|
1758
1885
|
}
|
|
@@ -1795,11 +1922,12 @@ export class ProcessBuilder {
|
|
|
1795
1922
|
// advance the flow cursor — the surrounding process wires around them.
|
|
1796
1923
|
// openBranchEnds is intentionally NOT drained here; the next normal
|
|
1797
1924
|
// addFlowElement call will drain it and connect branch ends to that element.
|
|
1798
|
-
if (this.
|
|
1925
|
+
if (this.elementIds.has(element.id)) {
|
|
1799
1926
|
throw new Error(`Duplicate element ID "${element.id}" in process "${this.processId}"`);
|
|
1800
1927
|
}
|
|
1801
1928
|
this._savedMainFlowId = undefined;
|
|
1802
1929
|
this.flowElements.push(element);
|
|
1930
|
+
this.elementIds.add(element.id);
|
|
1803
1931
|
return this;
|
|
1804
1932
|
}
|
|
1805
1933
|
// ---- Annotations ----
|
|
@@ -1831,19 +1959,50 @@ export class ProcessBuilder {
|
|
|
1831
1959
|
*
|
|
1832
1960
|
* Resolves all forward-referenced `incoming` / `outgoing` arrays and wraps
|
|
1833
1961
|
* the process in a {@link BpmnDefinitions} ready for XML serialization.
|
|
1962
|
+
*
|
|
1963
|
+
* **The join contract.** Branches built with `.branch()` converge implicitly:
|
|
1964
|
+
* where several paths from one gateway reach the same element, a matching join
|
|
1965
|
+
* gateway is inserted for you. That is convenient by hand and a trap for
|
|
1966
|
+
* generated code, which cannot see the element it did not write. Pass
|
|
1967
|
+
* `{ explicitJoins: true }` to be told instead of helped — the build throws,
|
|
1968
|
+
* naming the gateways it would have inserted, and you declare them yourself
|
|
1969
|
+
* with `.connectTo(joinId)`.
|
|
1970
|
+
*
|
|
1971
|
+
* A join you declare only counts if it *matches the split*: an exclusive split
|
|
1972
|
+
* converging on a parallel gateway is not the gateway inference would have
|
|
1973
|
+
* added, so it is still inferred — and with `explicitJoins` that refusal is the
|
|
1974
|
+
* only thing that tells you.
|
|
1975
|
+
*
|
|
1976
|
+
* {@link ProcessBuilder.from} never infers joins at all, whatever this option
|
|
1977
|
+
* says: inference reads the whole topology, and on a document you were handed
|
|
1978
|
+
* that means rewriting edges you never touched.
|
|
1979
|
+
*
|
|
1980
|
+
* @param options - `explicitJoins` refuses inferred join gateways. `strict` is
|
|
1981
|
+
* the former name for it and still works.
|
|
1834
1982
|
*/
|
|
1835
1983
|
build(options) {
|
|
1984
|
+
this.resolvePendingSplice();
|
|
1836
1985
|
const beforeCount = this.flowElements.length;
|
|
1837
|
-
|
|
1838
|
-
|
|
1986
|
+
// Continuing a document never infers joins. `insertJoinGateways` reads the
|
|
1987
|
+
// whole topology, so on a parsed model it retargets edges the caller never
|
|
1988
|
+
// touched — the corpus has a document where a no-op continue would have
|
|
1989
|
+
// invented a join gateway and rerouted two flows into it. Building a branch
|
|
1990
|
+
// that needs a join here means saying so with `.connectTo(joinId)`.
|
|
1991
|
+
if (this._source === undefined)
|
|
1992
|
+
insertJoinGateways(this.flowElements, this.sequenceFlows);
|
|
1993
|
+
const explicitJoins = options?.explicitJoins ?? options?.strict ?? false;
|
|
1994
|
+
if (explicitJoins && this.flowElements.length > beforeCount) {
|
|
1839
1995
|
const inserted = this.flowElements
|
|
1840
1996
|
.slice(beforeCount)
|
|
1841
1997
|
.map((e) => e.id)
|
|
1842
1998
|
.join(", ");
|
|
1843
|
-
throw new Error(`
|
|
1999
|
+
throw new Error(`Inferred join gateways: ${inserted}. Declare them with .connectTo(joinId), or drop { explicitJoins: true } to keep the inference.`);
|
|
1844
2000
|
}
|
|
1845
2001
|
this.validate();
|
|
2002
|
+
this.assertSourceTopologyIntact();
|
|
1846
2003
|
recomputeIncomingOutgoing(this.flowElements, this.sequenceFlows);
|
|
2004
|
+
if (this._source !== undefined)
|
|
2005
|
+
return this.buildOntoSource(this._source);
|
|
1847
2006
|
const extensionElements = [];
|
|
1848
2007
|
if (this._versionTag) {
|
|
1849
2008
|
extensionElements.push({
|
|
@@ -1905,6 +2064,78 @@ export class ProcessBuilder {
|
|
|
1905
2064
|
};
|
|
1906
2065
|
return this._autoLayout ? applyAutoLayout(defs) : defs;
|
|
1907
2066
|
}
|
|
2067
|
+
/**
|
|
2068
|
+
* Writes this process's contents back into the document it came from.
|
|
2069
|
+
*
|
|
2070
|
+
* Everything not listed here is kept by identity — other processes, the
|
|
2071
|
+
* collaboration, diagram interchange, root elements, the process's own lanes,
|
|
2072
|
+
* documentation, extensions and unmodelled content. That is the whole point of
|
|
2073
|
+
* continuing rather than regenerating.
|
|
2074
|
+
*
|
|
2075
|
+
* Diagram interchange is *not* regenerated: existing shapes keep their
|
|
2076
|
+
* positions, and elements added here have none until `withAutoLayout()` or a
|
|
2077
|
+
* later `applyAutoLayout()` gives them one.
|
|
2078
|
+
*/
|
|
2079
|
+
buildOntoSource(source) {
|
|
2080
|
+
const { definitions, process } = source;
|
|
2081
|
+
process.flowElements = this.flowElements;
|
|
2082
|
+
process.sequenceFlows = this.sequenceFlows;
|
|
2083
|
+
process.textAnnotations = this._textAnnotations;
|
|
2084
|
+
process.associations = this._associations;
|
|
2085
|
+
if (this.processName !== undefined)
|
|
2086
|
+
process.name = this.processName;
|
|
2087
|
+
// Only when asked. BPMN reads an absent `isExecutable` as false, so writing
|
|
2088
|
+
// the builder's `true` default onto a process that never carried it makes a
|
|
2089
|
+
// non-executable process executable — a change nobody requested.
|
|
2090
|
+
if (this._executableSet)
|
|
2091
|
+
process.isExecutable = this._isExecutable;
|
|
2092
|
+
if (this._versionTag !== undefined) {
|
|
2093
|
+
// Set the attribute on the existing element rather than replacing the
|
|
2094
|
+
// bag: a real process carries other extensions next to it.
|
|
2095
|
+
ensureZeebeExtension({ type: "process", extensionElements: process.extensionElements }, "zeebe:versionTag").attributes.value = this._versionTag;
|
|
2096
|
+
}
|
|
2097
|
+
definitions.errors = this.rootErrors;
|
|
2098
|
+
definitions.messages = this.rootMessages;
|
|
2099
|
+
definitions.signals = this.rootSignals;
|
|
2100
|
+
definitions.escalations = this.rootEscalations;
|
|
2101
|
+
return this._autoLayout ? applyAutoLayout(definitions) : definitions;
|
|
2102
|
+
}
|
|
2103
|
+
/**
|
|
2104
|
+
* Refuses to rewire a flow that was already in the document.
|
|
2105
|
+
*
|
|
2106
|
+
* `insertJoinGateways` retargets converging flows, which is right for a
|
|
2107
|
+
* topology this builder just created and wrong for one it was handed: a
|
|
2108
|
+
* document would come back with edges the caller never touched pointing
|
|
2109
|
+
* somewhere else. Continuing a model has to leave the model alone.
|
|
2110
|
+
*/
|
|
2111
|
+
assertSourceTopologyIntact() {
|
|
2112
|
+
if (this._source === undefined)
|
|
2113
|
+
return;
|
|
2114
|
+
const rewired = [];
|
|
2115
|
+
const byId = new Map(this.sequenceFlows.map((flow) => [flow.id, flow]));
|
|
2116
|
+
for (const [id, endpoints] of this._sourceFlowTargets) {
|
|
2117
|
+
const flow = byId.get(id);
|
|
2118
|
+
if (flow === undefined) {
|
|
2119
|
+
rewired.push(`${id} (removed)`);
|
|
2120
|
+
continue;
|
|
2121
|
+
}
|
|
2122
|
+
// A spliced flow was moved on purpose, but only its source may move:
|
|
2123
|
+
// `insertAfter` inserts into a path, it does not redirect one.
|
|
2124
|
+
const [, target] = endpoints.split("→");
|
|
2125
|
+
if (this._splicedFlowIds.has(id)) {
|
|
2126
|
+
if (flow.targetRef !== target) {
|
|
2127
|
+
rewired.push(`${id} (spliced flow now targets ${flow.targetRef}, not ${target})`);
|
|
2128
|
+
}
|
|
2129
|
+
continue;
|
|
2130
|
+
}
|
|
2131
|
+
const now = `${flow.sourceRef}→${flow.targetRef}`;
|
|
2132
|
+
if (now !== endpoints)
|
|
2133
|
+
rewired.push(`${id} (${endpoints} became ${now})`);
|
|
2134
|
+
}
|
|
2135
|
+
if (rewired.length > 0) {
|
|
2136
|
+
throw new Error(`Building would rewire sequence flows this document already had: ${rewired.join(", ")}. Continuing a model must not change the part you did not touch.`);
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
1908
2139
|
validate() {
|
|
1909
2140
|
const elementIds = new Set(this.flowElements.map((el) => el.id));
|
|
1910
2141
|
for (const flow of this.sequenceFlows) {
|
|
@@ -1919,10 +2150,11 @@ export class ProcessBuilder {
|
|
|
1919
2150
|
}
|
|
1920
2151
|
}
|
|
1921
2152
|
addFlowElement(element) {
|
|
1922
|
-
if (this.
|
|
2153
|
+
if (this.elementIds.has(element.id)) {
|
|
1923
2154
|
throw new Error(`Duplicate element ID "${element.id}" in process "${this.processId}"`);
|
|
1924
2155
|
}
|
|
1925
2156
|
this.flowElements.push(element);
|
|
2157
|
+
this.elementIds.add(element.id);
|
|
1926
2158
|
// Compensation handlers are outside the normal token flow: link via association
|
|
1927
2159
|
// from the preceding compensation boundary event, then restore the main-flow cursor.
|
|
1928
2160
|
if (element.isForCompensation) {
|
|
@@ -1981,6 +2213,9 @@ export class DiagramBuilder {
|
|
|
1981
2213
|
_processes = [];
|
|
1982
2214
|
_errors = [];
|
|
1983
2215
|
_messages = [];
|
|
2216
|
+
_participants = [];
|
|
2217
|
+
_messageFlows = [];
|
|
2218
|
+
_collaborationId = "Collaboration_1";
|
|
1984
2219
|
_executionPlatformVersion = "8.9.0";
|
|
1985
2220
|
constructor(id) {
|
|
1986
2221
|
this._id = id;
|
|
@@ -1999,6 +2234,152 @@ export class DiagramBuilder {
|
|
|
1999
2234
|
this._messages.push(...defs.messages);
|
|
2000
2235
|
return this;
|
|
2001
2236
|
}
|
|
2237
|
+
/** Renames the collaboration element. Defaults to `"Collaboration_1"`. */
|
|
2238
|
+
collaborationId(id) {
|
|
2239
|
+
this._collaborationId = id;
|
|
2240
|
+
return this;
|
|
2241
|
+
}
|
|
2242
|
+
/**
|
|
2243
|
+
* Adds a pool.
|
|
2244
|
+
*
|
|
2245
|
+
* Omit `processId` for a black box — a participant whose internals are not
|
|
2246
|
+
* modelled. That is a real BPMN construct, not an incomplete one: it is how
|
|
2247
|
+
* you draw the counterparty you exchange messages with but do not execute.
|
|
2248
|
+
*
|
|
2249
|
+
* @param id - The participant's element id, used verbatim.
|
|
2250
|
+
*/
|
|
2251
|
+
participant(id, options = {}) {
|
|
2252
|
+
const participant = { id, unknownAttributes: {} };
|
|
2253
|
+
if (options.name !== undefined)
|
|
2254
|
+
participant.name = options.name;
|
|
2255
|
+
if (options.processId !== undefined)
|
|
2256
|
+
participant.processRef = options.processId;
|
|
2257
|
+
this._participants.push(participant);
|
|
2258
|
+
return this;
|
|
2259
|
+
}
|
|
2260
|
+
/**
|
|
2261
|
+
* Declares a root-level message, which a message flow may name and a Camunda 8
|
|
2262
|
+
* message subscription correlates on.
|
|
2263
|
+
*
|
|
2264
|
+
* `ProcessBuilder` already creates messages by name for message events, so
|
|
2265
|
+
* only call this for a message no event declared — typically one carried by a
|
|
2266
|
+
* message flow between pools.
|
|
2267
|
+
*
|
|
2268
|
+
* @param id - The message's element id, used verbatim.
|
|
2269
|
+
*/
|
|
2270
|
+
message(id, options = {}) {
|
|
2271
|
+
const message = { id, unknownAttributes: {} };
|
|
2272
|
+
if (options.name !== undefined)
|
|
2273
|
+
message.name = options.name;
|
|
2274
|
+
const extensions = zeebeExtensionsToXmlElements(options.correlationKey === undefined
|
|
2275
|
+
? {}
|
|
2276
|
+
: { subscription: { correlationKey: options.correlationKey } });
|
|
2277
|
+
if (extensions.length > 0)
|
|
2278
|
+
message.extensionElements = extensions;
|
|
2279
|
+
this._messages.push(message);
|
|
2280
|
+
return this;
|
|
2281
|
+
}
|
|
2282
|
+
/**
|
|
2283
|
+
* Connects two pools.
|
|
2284
|
+
*
|
|
2285
|
+
* `source` and `target` name either participants or flow nodes inside them.
|
|
2286
|
+
* Both forms are valid BPMN and the layout engine reads either, but they must
|
|
2287
|
+
* be in *different* pools — a message flow is what crosses a pool boundary,
|
|
2288
|
+
* and one that does not is the error this catches.
|
|
2289
|
+
*
|
|
2290
|
+
* @param id - The message flow's element id, used verbatim.
|
|
2291
|
+
*/
|
|
2292
|
+
messageFlow(id, options) {
|
|
2293
|
+
const flow = {
|
|
2294
|
+
id,
|
|
2295
|
+
sourceRef: options.source,
|
|
2296
|
+
targetRef: options.target,
|
|
2297
|
+
unknownAttributes: {},
|
|
2298
|
+
};
|
|
2299
|
+
if (options.name !== undefined)
|
|
2300
|
+
flow.name = options.name;
|
|
2301
|
+
if (options.messageRef !== undefined)
|
|
2302
|
+
flow.messageRef = options.messageRef;
|
|
2303
|
+
this._messageFlows.push(flow);
|
|
2304
|
+
return this;
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2307
|
+
* Reports every way the declared collaboration would not survive contact with
|
|
2308
|
+
* a modeler, so `build()` can refuse rather than emit a file that opens broken.
|
|
2309
|
+
*/
|
|
2310
|
+
collaborationProblems() {
|
|
2311
|
+
const problems = [];
|
|
2312
|
+
const seen = new Set();
|
|
2313
|
+
for (const id of [
|
|
2314
|
+
...this._participants.map((p) => p.id),
|
|
2315
|
+
...this._messageFlows.map((f) => f.id),
|
|
2316
|
+
]) {
|
|
2317
|
+
if (seen.has(id))
|
|
2318
|
+
problems.push(`Duplicate element ID "${id}"`);
|
|
2319
|
+
seen.add(id);
|
|
2320
|
+
}
|
|
2321
|
+
const processIds = new Set(this._processes.map((process) => process.id));
|
|
2322
|
+
const claimed = new Map();
|
|
2323
|
+
for (const participant of this._participants) {
|
|
2324
|
+
const processId = participant.processRef;
|
|
2325
|
+
if (processId === undefined)
|
|
2326
|
+
continue;
|
|
2327
|
+
if (!processIds.has(processId)) {
|
|
2328
|
+
problems.push(`Participant "${participant.id}" references process "${processId}", which this diagram does not contain`);
|
|
2329
|
+
continue;
|
|
2330
|
+
}
|
|
2331
|
+
const owner = claimed.get(processId);
|
|
2332
|
+
if (owner !== undefined) {
|
|
2333
|
+
problems.push(`Participants "${owner}" and "${participant.id}" both reference process "${processId}"; a process belongs to one pool`);
|
|
2334
|
+
}
|
|
2335
|
+
claimed.set(processId, participant.id);
|
|
2336
|
+
}
|
|
2337
|
+
const owningParticipant = this.participantIndex();
|
|
2338
|
+
const messageIds = new Set(this._messages.map((message) => message.id));
|
|
2339
|
+
for (const flow of this._messageFlows) {
|
|
2340
|
+
for (const [role, ref] of [
|
|
2341
|
+
["source", flow.sourceRef],
|
|
2342
|
+
["target", flow.targetRef],
|
|
2343
|
+
]) {
|
|
2344
|
+
if (!owningParticipant.has(ref)) {
|
|
2345
|
+
problems.push(`Message flow "${flow.id}" names ${role} "${ref}", which is not a participant or a flow node in one`);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
const from = owningParticipant.get(flow.sourceRef);
|
|
2349
|
+
const to = owningParticipant.get(flow.targetRef);
|
|
2350
|
+
if (from !== undefined && from === to) {
|
|
2351
|
+
problems.push(`Message flow "${flow.id}" starts and ends in participant "${from}"; a message flow crosses pools, a sequence flow stays inside one`);
|
|
2352
|
+
}
|
|
2353
|
+
if (flow.messageRef !== undefined && !messageIds.has(flow.messageRef)) {
|
|
2354
|
+
problems.push(`Message flow "${flow.id}" references message "${flow.messageRef}", which this diagram does not declare`);
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
return problems;
|
|
2358
|
+
}
|
|
2359
|
+
/** Maps every participant id and every flow node id to its owning participant. */
|
|
2360
|
+
participantIndex() {
|
|
2361
|
+
const index = new Map();
|
|
2362
|
+
const byProcess = new Map();
|
|
2363
|
+
for (const participant of this._participants) {
|
|
2364
|
+
index.set(participant.id, participant.id);
|
|
2365
|
+
if (participant.processRef !== undefined)
|
|
2366
|
+
byProcess.set(participant.processRef, participant.id);
|
|
2367
|
+
}
|
|
2368
|
+
const walk = (elements, owner) => {
|
|
2369
|
+
for (const element of elements) {
|
|
2370
|
+
index.set(element.id, owner);
|
|
2371
|
+
if ("flowElements" in element && Array.isArray(element.flowElements)) {
|
|
2372
|
+
walk(element.flowElements, owner);
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
};
|
|
2376
|
+
for (const process of this._processes) {
|
|
2377
|
+
const owner = byProcess.get(process.id);
|
|
2378
|
+
if (owner !== undefined)
|
|
2379
|
+
walk(process.flowElements, owner);
|
|
2380
|
+
}
|
|
2381
|
+
return index;
|
|
2382
|
+
}
|
|
2002
2383
|
build() {
|
|
2003
2384
|
return {
|
|
2004
2385
|
id: this._id,
|
|
@@ -2022,10 +2403,35 @@ export class DiagramBuilder {
|
|
|
2022
2403
|
escalations: [],
|
|
2023
2404
|
messages: this._messages,
|
|
2024
2405
|
signals: [],
|
|
2025
|
-
collaborations:
|
|
2406
|
+
collaborations: this.buildCollaborations(),
|
|
2026
2407
|
processes: this._processes,
|
|
2027
2408
|
diagrams: [],
|
|
2028
2409
|
};
|
|
2029
2410
|
}
|
|
2411
|
+
/**
|
|
2412
|
+
* A document with no participants has no collaboration — an empty
|
|
2413
|
+
* `<bpmn:collaboration/>` is not a neutral addition, it makes every process a
|
|
2414
|
+
* pool-less participant in a modeler.
|
|
2415
|
+
*/
|
|
2416
|
+
buildCollaborations() {
|
|
2417
|
+
if (this._participants.length === 0 && this._messageFlows.length === 0)
|
|
2418
|
+
return [];
|
|
2419
|
+
const problems = this.collaborationProblems();
|
|
2420
|
+
if (problems.length > 0) {
|
|
2421
|
+
throw new Error(`Invalid collaboration:\n ${problems.join("\n ")}`);
|
|
2422
|
+
}
|
|
2423
|
+
return [
|
|
2424
|
+
{
|
|
2425
|
+
id: this._collaborationId,
|
|
2426
|
+
participants: this._participants,
|
|
2427
|
+
messageFlows: this._messageFlows,
|
|
2428
|
+
textAnnotations: [],
|
|
2429
|
+
associations: [],
|
|
2430
|
+
groups: [],
|
|
2431
|
+
extensionElements: [],
|
|
2432
|
+
unknownAttributes: {},
|
|
2433
|
+
},
|
|
2434
|
+
];
|
|
2435
|
+
}
|
|
2030
2436
|
}
|
|
2031
2437
|
//# sourceMappingURL=bpmn-builder.js.map
|