@bpmnkit/core 0.0.24 → 0.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,9 @@
1
- import type { BpmnAssociation, BpmnDefinitions, BpmnFlowElement, BpmnSequenceFlow, BpmnTextAnnotation } from "./bpmn-model.js";
1
+ import type { BpmnAssociation, BpmnDefinitions, BpmnError, BpmnEscalation, BpmnFlowElement, BpmnMessage, BpmnSequenceFlow, BpmnSignal, BpmnTextAnnotation } from "./bpmn-model.js";
2
2
  import type { RestConnectorConfig } from "./rest-connector.js";
3
3
  /** Options shared by all element methods. */
4
4
  export interface ElementOptions {
5
5
  name?: string;
6
+ isForCompensation?: boolean;
6
7
  }
7
8
  /** Options for creating a start event. */
8
9
  export interface StartEventOptions extends ElementOptions {
@@ -25,6 +26,11 @@ export interface StartEventOptions extends ElementOptions {
25
26
  modelerTemplateVersion?: string;
26
27
  /** Zeebe modeler template icon (data URI). */
27
28
  modelerTemplateIcon?: string;
29
+ /**
30
+ * Non-interrupting flag — only meaningful for start events inside event sub-processes.
31
+ * Pass `false` to emit `isInterrupting="false"`. Omit for the default interrupting behavior.
32
+ */
33
+ isInterrupting?: boolean;
28
34
  }
29
35
  /** Options for creating a service task. */
30
36
  export interface ServiceTaskOptions {
@@ -53,6 +59,8 @@ export interface ServiceTaskOptions {
53
59
  modelerTemplateVersion?: string;
54
60
  /** Zeebe modeler template icon (data URI). */
55
61
  modelerTemplateIcon?: string;
62
+ /** Mark this task as a compensation handler. */
63
+ isForCompensation?: boolean;
56
64
  }
57
65
  /** Options for creating a script task. */
58
66
  export interface ScriptTaskOptions {
@@ -62,6 +70,8 @@ export interface ScriptTaskOptions {
62
70
  expression: string;
63
71
  /** Variable name to store the result. */
64
72
  resultVariable: string;
73
+ /** Mark this task as a compensation handler. */
74
+ isForCompensation?: boolean;
65
75
  }
66
76
  /** Options for creating a user task. */
67
77
  export interface UserTaskOptions {
@@ -71,6 +81,8 @@ export interface UserTaskOptions {
71
81
  formId?: string;
72
82
  /** Emit <zeebe:userTask /> to mark as a Camunda 8 native user task. */
73
83
  zeebeUserTask?: boolean;
84
+ /** Mark this task as a compensation handler. */
85
+ isForCompensation?: boolean;
74
86
  }
75
87
  /** Options for creating a call activity. */
76
88
  export interface CallActivityOptions {
@@ -80,6 +92,8 @@ export interface CallActivityOptions {
80
92
  processId: string;
81
93
  /** Whether to propagate all child variables. */
82
94
  propagateAllChildVariables?: boolean;
95
+ /** Mark this activity as a compensation handler. */
96
+ isForCompensation?: boolean;
83
97
  }
84
98
  /** Options for creating a business rule task. */
85
99
  export interface BusinessRuleTaskOptions {
@@ -91,6 +105,8 @@ export interface BusinessRuleTaskOptions {
91
105
  decisionId?: string;
92
106
  /** Variable to store the result. */
93
107
  resultVariable?: string;
108
+ /** Mark this task as a compensation handler. */
109
+ isForCompensation?: boolean;
94
110
  }
95
111
  /** Options for gateway elements. */
96
112
  export interface GatewayOptions extends ElementOptions {
@@ -118,12 +134,16 @@ export interface IntermediateThrowEventOptions extends ElementOptions {
118
134
  signalName?: string;
119
135
  /** Escalation code — creates an escalation throw event (aspirational). */
120
136
  escalationCode?: string;
137
+ /** Emit a compensateEventDefinition. */
138
+ compensation?: boolean;
139
+ /** Activity to compensate (activityRef attribute on compensateEventDefinition). */
140
+ activityRef?: string;
121
141
  }
122
142
  /** Options for an end event. */
123
143
  export interface EndEventOptions extends ElementOptions {
124
144
  /** Error code — creates an error end event. */
125
145
  errorCode?: string;
126
- /** Error reference ID. */
146
+ /** Error code — creates an error end event. Alias for errorCode. */
127
147
  errorRef?: string;
128
148
  /** Message name — creates a message end event. */
129
149
  messageName?: string;
@@ -140,7 +160,7 @@ export interface BoundaryEventOptions extends ElementOptions {
140
160
  cancelActivity?: boolean;
141
161
  /** Error code — creates an error boundary event. */
142
162
  errorCode?: string;
143
- /** Error reference ID. */
163
+ /** Error code — creates an error boundary event. Alias for errorCode. */
144
164
  errorRef?: string;
145
165
  /** Timer duration — creates a timer boundary event (aspirational). */
146
166
  timerDuration?: string;
@@ -152,6 +172,8 @@ export interface BoundaryEventOptions extends ElementOptions {
152
172
  messageName?: string;
153
173
  /** Signal name — creates a signal boundary event (aspirational). */
154
174
  signalName?: string;
175
+ /** Creates a compensation boundary event. */
176
+ compensation?: boolean;
155
177
  }
156
178
  /** Multi-instance loop configuration. */
157
179
  export interface MultiInstanceOptions {
@@ -231,8 +253,12 @@ export declare class BranchBuilder {
231
253
  /** @internal */
232
254
  readonly _associations: BpmnAssociation[];
233
255
  private readonly _annCounters;
256
+ private readonly rootErrors;
257
+ private readonly rootMessages;
258
+ private readonly rootSignals;
259
+ private readonly rootEscalations;
234
260
  /** @internal */
235
- constructor(gatewayId: string, branchName: string);
261
+ constructor(gatewayId: string, branchName: string, rootErrors?: BpmnError[], rootMessages?: BpmnMessage[], rootSignals?: BpmnSignal[], rootEscalations?: BpmnEscalation[]);
236
262
  /** Set a FEEL condition expression on this branch's outgoing sequence flow. */
237
263
  condition(expression: string): this;
238
264
  /** Mark this branch as the gateway's default (no-condition) flow. */
@@ -256,6 +282,8 @@ export declare class BranchBuilder {
256
282
  receiveTask(id: string, options?: ElementOptions): this;
257
283
  businessRuleTask(id: string, options?: BusinessRuleTaskOptions): this;
258
284
  callActivity(id: string, options: CallActivityOptions): this;
285
+ /** Add an abstract task with no Zeebe extensions. */
286
+ task(id: string, options?: ElementOptions): this;
259
287
  startEvent(id?: string, options?: StartEventOptions): this;
260
288
  endEvent(id?: string, options?: EndEventOptions): this;
261
289
  intermediateThrowEvent(id?: string, options?: IntermediateThrowEventOptions): this;
@@ -291,6 +319,8 @@ export declare class SubProcessContentBuilder {
291
319
  callActivity(id: string, options: CallActivityOptions): this;
292
320
  sendTask(id: string, options?: ElementOptions): this;
293
321
  receiveTask(id: string, options?: ElementOptions): this;
322
+ /** Add an abstract task with no Zeebe extensions. */
323
+ task(id: string, options?: ElementOptions): this;
294
324
  exclusiveGateway(id: string, options?: GatewayOptions): this;
295
325
  parallelGateway(id: string, options?: ElementOptions): this;
296
326
  inclusiveGateway(id: string, options?: GatewayOptions): this;
@@ -313,6 +343,8 @@ export declare class ProcessBuilder {
313
343
  private readonly sequenceFlows;
314
344
  private readonly rootErrors;
315
345
  private readonly rootMessages;
346
+ private readonly rootSignals;
347
+ private readonly rootEscalations;
316
348
  private readonly _textAnnotations;
317
349
  private readonly _associations;
318
350
  private readonly _annCounters;
@@ -322,6 +354,7 @@ export declare class ProcessBuilder {
322
354
  private _autoLayout;
323
355
  private _executionPlatformVersion;
324
356
  private _serviceTaskDefaults;
357
+ private _savedMainFlowId;
325
358
  constructor(processId: string);
326
359
  /** Enable auto-layout: `build()` will run the layout engine and populate diagram interchange data. */
327
360
  withAutoLayout(): this;
@@ -393,6 +426,8 @@ export declare class ProcessBuilder {
393
426
  businessRuleTask(id: string, options?: BusinessRuleTaskOptions): this;
394
427
  /** Add a call activity referencing another process. */
395
428
  callActivity(id: string, options: CallActivityOptions): this;
429
+ /** Add an abstract task with no Zeebe extensions. */
430
+ task(id: string, options?: ElementOptions): this;
396
431
  /** Add an exclusive gateway (XOR split/join). */
397
432
  exclusiveGateway(id: string, options?: GatewayOptions): this;
398
433
  /** Add a parallel gateway (AND split/join). */
@@ -432,7 +467,7 @@ export declare class ProcessBuilder {
432
467
  adHocSubProcess(id: string, content: (b: SubProcessContentBuilder) => void, options?: AdHocSubProcessOptions): this;
433
468
  /** Add a sub-process (aspirational). */
434
469
  subProcess(id: string, content: (b: SubProcessContentBuilder) => void, options?: SubProcessOptions): this;
435
- /** Add an event sub-process (aspirational). */
470
+ /** Add an event sub-process. Triggered by its start event — no incoming or outgoing sequence flows. */
436
471
  eventSubProcess(id: string, content: (b: SubProcessContentBuilder) => void, options?: ElementOptions): this;
437
472
  /** Attach a text annotation to the element at the current cursor position. */
438
473
  textAnnotation(text: string): this;
@@ -7,7 +7,7 @@ const EXPORTER_VERSION = "0.0.23";
7
7
  // ---------------------------------------------------------------------------
8
8
  // Internal helpers
9
9
  // ---------------------------------------------------------------------------
10
- function buildEventDefinitions(opts, rootErrors, rootMessages) {
10
+ function buildEventDefinitions(opts, rootErrors, rootMessages, rootSignals, rootEscalations) {
11
11
  const defs = [];
12
12
  if (opts.timerDuration || opts.timerDate || opts.timerCycle) {
13
13
  defs.push({
@@ -18,28 +18,63 @@ function buildEventDefinitions(opts, rootErrors, rootMessages) {
18
18
  });
19
19
  }
20
20
  if (opts.errorCode !== undefined || opts.errorRef !== undefined) {
21
- let errorRef = opts.errorRef;
22
- if (!errorRef && opts.errorCode !== undefined && rootErrors) {
23
- const errorId = generateId("Error");
24
- rootErrors.push({ id: errorId, name: opts.errorCode, errorCode: opts.errorCode });
25
- errorRef = errorId;
21
+ const codeOrRef = opts.errorCode ?? opts.errorRef;
22
+ let errorRef;
23
+ if (codeOrRef !== undefined && rootErrors) {
24
+ let existing = rootErrors.find((e) => e.errorCode === codeOrRef || e.name === codeOrRef);
25
+ if (!existing) {
26
+ existing = { id: generateId("Error"), name: codeOrRef, errorCode: codeOrRef };
27
+ rootErrors.push(existing);
28
+ }
29
+ errorRef = existing.id;
30
+ }
31
+ else {
32
+ errorRef = codeOrRef;
26
33
  }
27
34
  defs.push({ type: "error", errorRef });
28
35
  }
29
36
  if (opts.messageName !== undefined) {
30
37
  let messageRef = opts.messageName;
31
38
  if (rootMessages) {
32
- const messageId = generateId("Message");
33
- rootMessages.push({ id: messageId, name: opts.messageName, unknownAttributes: {} });
34
- messageRef = messageId;
39
+ let existing = rootMessages.find((m) => m.name === opts.messageName);
40
+ if (!existing) {
41
+ existing = { id: generateId("Message"), name: opts.messageName, unknownAttributes: {} };
42
+ rootMessages.push(existing);
43
+ }
44
+ messageRef = existing.id;
35
45
  }
36
46
  defs.push({ type: "message", messageRef });
37
47
  }
38
48
  if (opts.signalName !== undefined) {
39
- defs.push({ type: "signal", signalRef: opts.signalName });
49
+ let signalRef = opts.signalName;
50
+ if (rootSignals) {
51
+ let existing = rootSignals.find((s) => s.name === opts.signalName);
52
+ if (!existing) {
53
+ existing = { id: generateId("Signal"), name: opts.signalName };
54
+ rootSignals.push(existing);
55
+ }
56
+ signalRef = existing.id;
57
+ }
58
+ defs.push({ type: "signal", signalRef });
40
59
  }
41
60
  if (opts.escalationCode !== undefined) {
42
- defs.push({ type: "escalation", escalationRef: opts.escalationCode });
61
+ let escalationRef = opts.escalationCode;
62
+ if (rootEscalations) {
63
+ let existing = rootEscalations.find((e) => e.escalationCode === opts.escalationCode);
64
+ if (!existing) {
65
+ existing = {
66
+ id: generateId("Escalation"),
67
+ name: opts.escalationCode,
68
+ escalationCode: opts.escalationCode,
69
+ };
70
+ rootEscalations.push(existing);
71
+ }
72
+ escalationRef = existing.id;
73
+ }
74
+ defs.push({ type: "escalation", escalationRef });
75
+ }
76
+ if (opts.compensation) {
77
+ defs.push({ type: "compensate", activityRef: opts.activityRef });
43
78
  }
44
79
  return defs;
45
80
  }
@@ -213,10 +248,12 @@ function makeServiceTaskEl(id, options) {
213
248
  extensionElements: buildServiceTaskExtensions(options),
214
249
  });
215
250
  el.unknownAttributes = unknownAttributes;
251
+ if (options.isForCompensation)
252
+ el.isForCompensation = true;
216
253
  return el;
217
254
  }
218
255
  function makeScriptTaskEl(id, options) {
219
- return makeFlowElement(id, "scriptTask", {
256
+ const el = makeFlowElement(id, "scriptTask", {
220
257
  name: options.name,
221
258
  extensionElements: zeebeExtensionsToXmlElements({
222
259
  unknownElements: [
@@ -228,13 +265,19 @@ function makeScriptTaskEl(id, options) {
228
265
  ],
229
266
  }),
230
267
  });
268
+ if (options.isForCompensation)
269
+ el.isForCompensation = true;
270
+ return el;
231
271
  }
232
272
  function makeUserTaskEl(id, options) {
233
273
  const ext = zeebeExtensionsToXmlElements({
234
274
  ...(options?.zeebeUserTask ? { userTask: true } : {}),
235
275
  ...(options?.formId ? { formDefinition: { formId: options.formId } } : {}),
236
276
  });
237
- return makeFlowElement(id, "userTask", { name: options?.name, extensionElements: ext });
277
+ const el = makeFlowElement(id, "userTask", { name: options?.name, extensionElements: ext });
278
+ if (options?.isForCompensation)
279
+ el.isForCompensation = true;
280
+ return el;
238
281
  }
239
282
  function makeBusinessRuleTaskEl(id, options) {
240
283
  const ext = [];
@@ -249,19 +292,28 @@ function makeBusinessRuleTaskEl(id, options) {
249
292
  },
250
293
  }));
251
294
  }
252
- return makeFlowElement(id, "businessRuleTask", { name: options?.name, extensionElements: ext });
295
+ const el = makeFlowElement(id, "businessRuleTask", {
296
+ name: options?.name,
297
+ extensionElements: ext,
298
+ });
299
+ if (options?.isForCompensation)
300
+ el.isForCompensation = true;
301
+ return el;
253
302
  }
254
303
  function makeCallActivityEl(id, options) {
255
304
  const attrs = { processId: options.processId };
256
305
  if (options.propagateAllChildVariables !== undefined) {
257
306
  attrs.propagateAllChildVariables = String(options.propagateAllChildVariables);
258
307
  }
259
- return makeFlowElement(id, "callActivity", {
308
+ const el = makeFlowElement(id, "callActivity", {
260
309
  name: options.name,
261
310
  extensionElements: zeebeExtensionsToXmlElements({
262
311
  unknownElements: [{ name: "zeebe:calledElement", attributes: attrs, children: [] }],
263
312
  }),
264
313
  });
314
+ if (options.isForCompensation)
315
+ el.isForCompensation = true;
316
+ return el;
265
317
  }
266
318
  function makeExclusiveGatewayEl(id, options) {
267
319
  const el = makeFlowElement(id, "exclusiveGateway", options);
@@ -332,15 +384,17 @@ function insertJoinGateways(elements, flows) {
332
384
  const gwType = elementTypes.get(splitId);
333
385
  if (!gwType)
334
386
  continue;
387
+ // eventBasedGateway is split-only; converge through an XOR join instead
388
+ const joinType = gwType === "eventBasedGateway" ? "exclusiveGateway" : gwType;
335
389
  const targetType = elementTypes.get(targetId);
336
- if (targetType === gwType)
390
+ if (targetType === joinType)
337
391
  continue;
338
392
  const joinId = `${splitId}_join`;
339
393
  if (elementTypes.has(joinId))
340
394
  continue;
341
- const joinElement = makeFlowElement(joinId, gwType, {});
395
+ const joinElement = makeFlowElement(joinId, joinType, {});
342
396
  elements.push(joinElement);
343
- elementTypes.set(joinId, gwType);
397
+ elementTypes.set(joinId, joinType);
344
398
  for (const flow of convergingFlows)
345
399
  flow.targetRef = joinId;
346
400
  flows.push({
@@ -401,11 +455,19 @@ export class BranchBuilder {
401
455
  /** @internal */
402
456
  _associations = [];
403
457
  _annCounters = new Map();
458
+ rootErrors;
459
+ rootMessages;
460
+ rootSignals;
461
+ rootEscalations;
404
462
  /** @internal */
405
- constructor(gatewayId, branchName) {
463
+ constructor(gatewayId, branchName, rootErrors = [], rootMessages = [], rootSignals = [], rootEscalations = []) {
406
464
  this.gatewayId = gatewayId;
407
465
  this.branchName = branchName;
408
466
  this.lastNodeId = gatewayId;
467
+ this.rootErrors = rootErrors;
468
+ this.rootMessages = rootMessages;
469
+ this.rootSignals = rootSignals;
470
+ this.rootEscalations = rootEscalations;
409
471
  }
410
472
  /** Set a FEEL condition expression on this branch's outgoing sequence flow. */
411
473
  condition(expression) {
@@ -500,10 +562,16 @@ export class BranchBuilder {
500
562
  return this.addElement(makeScriptTaskEl(id, options));
501
563
  }
502
564
  sendTask(id, options) {
503
- return this.addElement(makeFlowElement(id, "sendTask", options));
565
+ const el = makeFlowElement(id, "sendTask", options);
566
+ if (options?.isForCompensation)
567
+ el.isForCompensation = true;
568
+ return this.addElement(el);
504
569
  }
505
570
  receiveTask(id, options) {
506
- return this.addElement(makeFlowElement(id, "receiveTask", options));
571
+ const el = makeFlowElement(id, "receiveTask", options);
572
+ if (options?.isForCompensation)
573
+ el.isForCompensation = true;
574
+ return this.addElement(el);
507
575
  }
508
576
  businessRuleTask(id, options) {
509
577
  return this.addElement(makeBusinessRuleTaskEl(id, options));
@@ -511,32 +579,38 @@ export class BranchBuilder {
511
579
  callActivity(id, options) {
512
580
  return this.addElement(makeCallActivityEl(id, options));
513
581
  }
582
+ /** Add an abstract task with no Zeebe extensions. */
583
+ task(id, options) {
584
+ const el = makeFlowElement(id, "task", options);
585
+ if (options?.isForCompensation)
586
+ el.isForCompensation = true;
587
+ return this.addElement(el);
588
+ }
514
589
  startEvent(id, options) {
515
590
  const el = makeFlowElement(id ?? generateId("StartEvent"), "startEvent", options);
516
- if (el.type === "startEvent" &&
517
- (options?.timerDuration || options?.timerCycle || options?.timerDate)) {
518
- el.eventDefinitions = buildEventDefinitions(options);
591
+ if (el.type === "startEvent" && options) {
592
+ el.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
519
593
  }
520
594
  return this.addElement(el);
521
595
  }
522
596
  endEvent(id, options) {
523
597
  const el = makeFlowElement(id ?? generateId("EndEvent"), "endEvent", options);
524
598
  if (el.type === "endEvent" && options) {
525
- el.eventDefinitions = buildEventDefinitions(options);
599
+ el.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
526
600
  }
527
601
  return this.addElement(el);
528
602
  }
529
603
  intermediateThrowEvent(id, options) {
530
604
  const el = makeFlowElement(id ?? generateId("IntermediateThrowEvent"), "intermediateThrowEvent", options);
531
605
  if (el.type === "intermediateThrowEvent" && options) {
532
- el.eventDefinitions = buildEventDefinitions(options);
606
+ el.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
533
607
  }
534
608
  return this.addElement(el);
535
609
  }
536
610
  intermediateCatchEvent(id, options) {
537
611
  const el = makeFlowElement(id ?? generateId("IntermediateCatchEvent"), "intermediateCatchEvent", options);
538
612
  if (el.type === "intermediateCatchEvent" && options) {
539
- el.eventDefinitions = buildEventDefinitions(options);
613
+ el.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
540
614
  }
541
615
  return this.addElement(el);
542
616
  }
@@ -600,8 +674,11 @@ export class SubProcessContentBuilder {
600
674
  // ---- Events ----
601
675
  startEvent(id, options) {
602
676
  const el = makeFlowElement(id ?? generateId("StartEvent"), "startEvent", options);
603
- if (el.type === "startEvent" && options)
677
+ if (el.type === "startEvent" && options) {
604
678
  el.eventDefinitions = buildEventDefinitions(options);
679
+ if (options.isInterrupting === false)
680
+ el.isInterrupting = false;
681
+ }
605
682
  return this.addElement(el);
606
683
  }
607
684
  endEvent(id, options) {
@@ -639,10 +716,23 @@ export class SubProcessContentBuilder {
639
716
  return this.addElement(makeCallActivityEl(id, options));
640
717
  }
641
718
  sendTask(id, options) {
642
- return this.addElement(makeFlowElement(id, "sendTask", options));
719
+ const el = makeFlowElement(id, "sendTask", options);
720
+ if (options?.isForCompensation)
721
+ el.isForCompensation = true;
722
+ return this.addElement(el);
643
723
  }
644
724
  receiveTask(id, options) {
645
- return this.addElement(makeFlowElement(id, "receiveTask", options));
725
+ const el = makeFlowElement(id, "receiveTask", options);
726
+ if (options?.isForCompensation)
727
+ el.isForCompensation = true;
728
+ return this.addElement(el);
729
+ }
730
+ /** Add an abstract task with no Zeebe extensions. */
731
+ task(id, options) {
732
+ const el = makeFlowElement(id, "task", options);
733
+ if (options?.isForCompensation)
734
+ el.isForCompensation = true;
735
+ return this.addElement(el);
646
736
  }
647
737
  // ---- Gateways ----
648
738
  exclusiveGateway(id, options) {
@@ -753,6 +843,8 @@ export class ProcessBuilder {
753
843
  sequenceFlows = [];
754
844
  rootErrors = [];
755
845
  rootMessages = [];
846
+ rootSignals = [];
847
+ rootEscalations = [];
756
848
  _textAnnotations = [];
757
849
  _associations = [];
758
850
  _annCounters = new Map();
@@ -762,6 +854,7 @@ export class ProcessBuilder {
762
854
  _autoLayout = false;
763
855
  _executionPlatformVersion = "8.9.0";
764
856
  _serviceTaskDefaults = {};
857
+ _savedMainFlowId = undefined;
765
858
  constructor(processId) {
766
859
  this.processId = processId;
767
860
  }
@@ -811,7 +904,9 @@ export class ProcessBuilder {
811
904
  extensionElements: extElements,
812
905
  });
813
906
  if (element.type === "startEvent" && options) {
814
- element.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages);
907
+ element.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
908
+ if (options.isInterrupting === false)
909
+ element.isInterrupting = false;
815
910
  }
816
911
  if (options?.modelerTemplate) {
817
912
  element.unknownAttributes["zeebe:modelerTemplate"] = options.modelerTemplate;
@@ -850,7 +945,7 @@ export class ProcessBuilder {
850
945
  const nodeId = id ?? generateId("EndEvent");
851
946
  const element = makeFlowElement(nodeId, "endEvent", options);
852
947
  if (element.type === "endEvent" && options) {
853
- element.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages);
948
+ element.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
854
949
  }
855
950
  this.addFlowElement(element);
856
951
  return this;
@@ -860,7 +955,7 @@ export class ProcessBuilder {
860
955
  const nodeId = id ?? generateId("IntermediateThrowEvent");
861
956
  const element = makeFlowElement(nodeId, "intermediateThrowEvent", options);
862
957
  if (element.type === "intermediateThrowEvent" && options) {
863
- element.eventDefinitions = buildEventDefinitions(options);
958
+ element.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
864
959
  }
865
960
  this.addFlowElement(element);
866
961
  return this;
@@ -870,7 +965,7 @@ export class ProcessBuilder {
870
965
  const nodeId = id ?? generateId("IntermediateCatchEvent");
871
966
  const element = makeFlowElement(nodeId, "intermediateCatchEvent", options);
872
967
  if (element.type === "intermediateCatchEvent" && options) {
873
- element.eventDefinitions = buildEventDefinitions(options);
968
+ element.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
874
969
  }
875
970
  this.addFlowElement(element);
876
971
  return this;
@@ -886,12 +981,17 @@ export class ProcessBuilder {
886
981
  if (element.type === "boundaryEvent") {
887
982
  element.attachedToRef = options.attachedTo;
888
983
  element.cancelActivity = options.cancelActivity;
889
- element.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages);
984
+ element.eventDefinitions = buildEventDefinitions(options, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
890
985
  }
891
986
  // Boundary events never auto-connect — temporarily clear lastNodeId
892
987
  const prevLast = this.lastNodeId;
893
988
  this.lastNodeId = undefined;
894
989
  this.addFlowElement(element);
990
+ // For compensation boundary events, save the main-flow cursor AFTER addFlowElement
991
+ // so subsequent normal elements don't accidentally clear it before it's consumed.
992
+ if (options.compensation) {
993
+ this._savedMainFlowId = prevLast;
994
+ }
895
995
  // Don't restore prevLast — the builder now chains from the boundary event
896
996
  void prevLast;
897
997
  return this;
@@ -921,6 +1021,7 @@ export class ProcessBuilder {
921
1021
  this.lastNodeId = savedLast;
922
1022
  this.currentGatewayId = savedGateway;
923
1023
  this.openBranchEnds = savedOpenEnds;
1024
+ this._savedMainFlowId = undefined;
924
1025
  return this;
925
1026
  }
926
1027
  // ---- Tasks ----
@@ -971,12 +1072,18 @@ export class ProcessBuilder {
971
1072
  }
972
1073
  /** Add a send task (aspirational). */
973
1074
  sendTask(id, options) {
974
- this.addFlowElement(makeFlowElement(id, "sendTask", options));
1075
+ const el = makeFlowElement(id, "sendTask", options);
1076
+ if (options?.isForCompensation)
1077
+ el.isForCompensation = true;
1078
+ this.addFlowElement(el);
975
1079
  return this;
976
1080
  }
977
1081
  /** Add a receive task (aspirational). */
978
1082
  receiveTask(id, options) {
979
- this.addFlowElement(makeFlowElement(id, "receiveTask", options));
1083
+ const el = makeFlowElement(id, "receiveTask", options);
1084
+ if (options?.isForCompensation)
1085
+ el.isForCompensation = true;
1086
+ this.addFlowElement(el);
980
1087
  return this;
981
1088
  }
982
1089
  /** Add a business rule task. */
@@ -989,6 +1096,14 @@ export class ProcessBuilder {
989
1096
  this.addFlowElement(makeCallActivityEl(id, options));
990
1097
  return this;
991
1098
  }
1099
+ /** Add an abstract task with no Zeebe extensions. */
1100
+ task(id, options) {
1101
+ const el = makeFlowElement(id, "task", options);
1102
+ if (options?.isForCompensation)
1103
+ el.isForCompensation = true;
1104
+ this.addFlowElement(el);
1105
+ return this;
1106
+ }
992
1107
  // ---- Gateways ----
993
1108
  /** Add an exclusive gateway (XOR split/join). */
994
1109
  exclusiveGateway(id, options) {
@@ -1036,7 +1151,7 @@ export class ProcessBuilder {
1036
1151
  if (!this.currentGatewayId) {
1037
1152
  throw new Error("branch() must be called after a gateway element");
1038
1153
  }
1039
- const b = new BranchBuilder(this.currentGatewayId, name);
1154
+ const b = new BranchBuilder(this.currentGatewayId, name, this.rootErrors, this.rootMessages, this.rootSignals, this.rootEscalations);
1040
1155
  callback(b);
1041
1156
  for (const el of b._elements) {
1042
1157
  if (this.flowElements.some((n) => n.id === el.id)) {
@@ -1187,20 +1302,29 @@ export class ProcessBuilder {
1187
1302
  this.addFlowElement(element);
1188
1303
  return this;
1189
1304
  }
1190
- /** Add an event sub-process (aspirational). */
1305
+ /** Add an event sub-process. Triggered by its start event — no incoming or outgoing sequence flows. */
1191
1306
  eventSubProcess(id, content, options) {
1192
1307
  const sub = new SubProcessContentBuilder();
1193
1308
  content(sub);
1194
1309
  insertJoinGateways(sub._elements, sub._flows);
1195
1310
  recomputeIncomingOutgoing(sub._elements, sub._flows);
1196
- const element = makeFlowElement(id, "eventSubProcess", options);
1197
- if (element.type === "eventSubProcess") {
1311
+ const element = makeFlowElement(id, "subProcess", options);
1312
+ if (element.type === "subProcess") {
1313
+ element.triggeredByEvent = true;
1198
1314
  element.flowElements = sub._elements;
1199
1315
  element.sequenceFlows = sub._flows;
1200
1316
  element.textAnnotations = sub._textAnnotations;
1201
1317
  element.associations = sub._associations;
1202
1318
  }
1203
- this.addFlowElement(element);
1319
+ // Event sub-processes have no incoming/outgoing sequence flows and must not
1320
+ // advance the flow cursor — the surrounding process wires around them.
1321
+ // openBranchEnds is intentionally NOT drained here; the next normal
1322
+ // addFlowElement call will drain it and connect branch ends to that element.
1323
+ if (this.flowElements.some((n) => n.id === element.id)) {
1324
+ throw new Error(`Duplicate element ID "${element.id}" in process "${this.processId}"`);
1325
+ }
1326
+ this._savedMainFlowId = undefined;
1327
+ this.flowElements.push(element);
1204
1328
  return this;
1205
1329
  }
1206
1330
  // ---- Annotations ----
@@ -1283,9 +1407,9 @@ export class ProcessBuilder {
1283
1407
  "modeler:executionPlatformVersion": this._executionPlatformVersion,
1284
1408
  },
1285
1409
  errors: this.rootErrors,
1286
- escalations: [],
1410
+ escalations: this.rootEscalations,
1287
1411
  messages: this.rootMessages,
1288
- signals: [],
1412
+ signals: this.rootSignals,
1289
1413
  collaborations: [],
1290
1414
  processes: [process],
1291
1415
  // Seed diagram stub so applyAutoLayout preserves process-specific IDs.
@@ -1323,6 +1447,24 @@ export class ProcessBuilder {
1323
1447
  throw new Error(`Duplicate element ID "${element.id}" in process "${this.processId}"`);
1324
1448
  }
1325
1449
  this.flowElements.push(element);
1450
+ // Compensation handlers are outside the normal token flow: link via association
1451
+ // from the preceding compensation boundary event, then restore the main-flow cursor.
1452
+ if (element.isForCompensation) {
1453
+ if (this.lastNodeId) {
1454
+ this._associations.push({
1455
+ id: generateId("Association"),
1456
+ sourceRef: this.lastNodeId,
1457
+ targetRef: element.id,
1458
+ associationDirection: "One",
1459
+ unknownAttributes: {},
1460
+ });
1461
+ }
1462
+ // Restore main-flow cursor (saved by boundaryEvent() when compensation: true)
1463
+ this.lastNodeId = this._savedMainFlowId;
1464
+ this._savedMainFlowId = undefined;
1465
+ // Do NOT connect open branch ends — handler is outside normal flow
1466
+ return;
1467
+ }
1326
1468
  if (this.lastNodeId) {
1327
1469
  const flowId = generateId("Flow");
1328
1470
  this.sequenceFlows.push({
@@ -1345,6 +1487,9 @@ export class ProcessBuilder {
1345
1487
  });
1346
1488
  }
1347
1489
  this.openBranchEnds = [];
1490
+ // Clear any saved compensation cursor — a normal element advancing the cursor
1491
+ // means the compensation boundary/handler pattern has been interrupted.
1492
+ this._savedMainFlowId = undefined;
1348
1493
  this.lastNodeId = element.id;
1349
1494
  }
1350
1495
  }
@@ -105,10 +105,12 @@ interface BpmnFlowNodeBase {
105
105
  documentation?: string;
106
106
  extensionElements: XmlElement[];
107
107
  unknownAttributes: Record<string, string>;
108
+ isForCompensation?: boolean;
108
109
  }
109
110
  export interface BpmnStartEvent extends BpmnFlowNodeBase {
110
111
  type: "startEvent";
111
112
  eventDefinitions: BpmnEventDefinition[];
113
+ isInterrupting?: boolean;
112
114
  }
113
115
  export interface BpmnEndEvent extends BpmnFlowNodeBase {
114
116
  type: "endEvent";
@@ -42,6 +42,7 @@ const KNOWN_ATTRS = new Set([
42
42
  "default",
43
43
  "attachedToRef",
44
44
  "cancelActivity",
45
+ "isForCompensation",
45
46
  "sourceRef",
46
47
  "targetRef",
47
48
  "associationDirection",
@@ -275,7 +276,15 @@ function parseFlowElement(element) {
275
276
  unknownAttributes: unknownAttrs(element),
276
277
  };
277
278
  switch (ln) {
278
- case "startEvent":
279
+ case "startEvent": {
280
+ const isInterruptingAttr = attr(element, "isInterrupting");
281
+ return {
282
+ ...base,
283
+ type: "startEvent",
284
+ eventDefinitions: parseEventDefinitions(element),
285
+ ...(isInterruptingAttr === "false" ? { isInterrupting: false } : {}),
286
+ };
287
+ }
279
288
  case "endEvent":
280
289
  case "intermediateCatchEvent":
281
290
  case "intermediateThrowEvent":
@@ -299,7 +308,12 @@ function parseFlowElement(element) {
299
308
  case "businessRuleTask":
300
309
  case "manualTask":
301
310
  case "callActivity":
302
- return { ...base, type: ln, loopCharacteristics: parseLoopCharacteristics(element) };
311
+ return {
312
+ ...base,
313
+ type: ln,
314
+ loopCharacteristics: parseLoopCharacteristics(element),
315
+ isForCompensation: attr(element, "isForCompensation") === "true" ? true : undefined,
316
+ };
303
317
  case "adHocSubProcess":
304
318
  return {
305
319
  ...base,
@@ -157,6 +157,8 @@ function serializeFlowElement(fe, ns) {
157
157
  const attrs = { id: fe.id, ...fe.unknownAttributes };
158
158
  if (fe.name !== undefined)
159
159
  attrs.name = fe.name;
160
+ if (fe.isForCompensation)
161
+ attrs.isForCompensation = "true";
160
162
  const children = [];
161
163
  // Documentation
162
164
  if (fe.documentation !== undefined) {
@@ -169,6 +171,10 @@ function serializeFlowElement(fe, ns) {
169
171
  children.push(...flowRefs(fe.outgoing, "outgoing", bp));
170
172
  switch (fe.type) {
171
173
  case "startEvent":
174
+ if (fe.isInterrupting === false)
175
+ attrs.isInterrupting = "false";
176
+ children.push(...serializeEventDefinitions(fe.eventDefinitions, bp));
177
+ break;
172
178
  case "endEvent":
173
179
  case "intermediateCatchEvent":
174
180
  case "intermediateThrowEvent":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bpmnkit/core",
3
- "version": "0.0.24",
3
+ "version": "0.0.25",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",