bpmn-elements 18.0.0 → 18.0.2

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.
@@ -97,8 +97,9 @@ function Activity(Behaviour, activityDef, context) {
97
97
  outboundSequenceFlows,
98
98
  outboundEvaluator: new _outboundEvaluator.OutboundEvaluator(this, outboundSequenceFlows)
99
99
  };
100
+ const isThrowingLink = activityDef.isThrowing && activityDef.linkNames?.length;
100
101
  this[K_FLAGS] = {
101
- isEnd: !outboundSequenceFlows.length,
102
+ isEnd: !outboundSequenceFlows.length && !isThrowingLink,
102
103
  isStart: !hasInboundTrigger && !behaviour.triggeredByEvent && !activityDef.isCatching,
103
104
  isSubProcess: activityDef.isSubProcess,
104
105
  isMultiInstance: !!behaviour.loopCharacteristics,
@@ -268,7 +269,7 @@ Object.defineProperties(Activity.prototype, {
268
269
  },
269
270
  initialized: {
270
271
  get() {
271
- return !!this[K_EXEC]?.get('initExecutionId');
272
+ return this[K_EXEC].get('initialized') > 0;
272
273
  }
273
274
  }
274
275
  });
@@ -280,7 +281,23 @@ Object.defineProperties(Activity.prototype, {
280
281
  Activity.prototype.activate = function activate() {
281
282
  if (this[_constants.K_ACTIVATED]) return;
282
283
  this[_constants.K_ACTIVATED] = true;
283
- return this.addInboundListeners() && this._consumeInbound();
284
+ this.addInboundListeners();
285
+ return this.consumeInbound();
286
+ };
287
+
288
+ /**
289
+ * Assert the inbound queue consumer when the activity has a trigger or is initialized.
290
+ * Idempotent: asserting the consumer again while one is active is a no-op.
291
+ * @returns {void}
292
+ */
293
+ Activity.prototype.consumeInbound = function consumeInbound() {
294
+ if (!this[_constants.K_ACTIVATED]) return;
295
+ if (this.status) return;
296
+ if (!this._getInboundTriggers().length && !this.initialized) return;
297
+ const onInbound = this[_constants.K_MESSAGE_HANDLERS].onInbound;
298
+ return this.broker.getQueue('inbound-q').assertConsumer(onInbound, {
299
+ consumerTag: '_run-on-inbound'
300
+ });
284
301
  };
285
302
 
286
303
  /** @internal */
@@ -326,17 +343,28 @@ Activity.prototype.deactivate = function deactivate() {
326
343
  /**
327
344
  * Initialise activity executionId and emit init event without starting the run.
328
345
  * @param {Record<string, any>} [initContent] Optional content merged into the init message
346
+ * @param {import('smqp').MessageProperties} [properties] Optional message properties merged into the init message properties
329
347
  */
330
- Activity.prototype.init = function init(initContent) {
348
+ Activity.prototype.init = function init(initContent, properties) {
331
349
  const id = this.id;
332
350
  const exec = this[K_EXEC];
333
- const executionId = exec.has('initExecutionId') ? exec.get('initExecutionId') : (0, _shared.getUniqueId)(id);
334
- exec.set('initExecutionId', executionId);
351
+ exec.set('initialized', (exec.get('initialized') || 0) + 1);
352
+ const executionId = (0, _shared.getUniqueId)(id);
335
353
  this.logger.debug(`<${id}> initialized with executionId <${executionId}>`);
336
354
  this._publishEvent('init', this._createMessage({
337
355
  ...initContent,
338
356
  executionId
339
357
  }));
358
+ this.broker.getQueue('inbound-q').queueMessage({
359
+ routingKey: 'activity.init'
360
+ }, {
361
+ ...initContent,
362
+ id,
363
+ executionId
364
+ }, {
365
+ persistent: false,
366
+ ...properties
367
+ });
340
368
  };
341
369
 
342
370
  /**
@@ -347,13 +375,15 @@ Activity.prototype.init = function init(initContent) {
347
375
  Activity.prototype.run = function run(runContent) {
348
376
  const id = this.id;
349
377
  if (this.isRunning) throw new Error(`activity <${id}> is already running`);
350
- const exec = this[K_EXEC];
351
- const executionId = exec.get('initExecutionId') || (0, _shared.getUniqueId)(id);
352
- exec.set('executionId', executionId);
353
- exec.delete('initExecutionId');
378
+ const {
379
+ initExecutionId,
380
+ ...runMessage
381
+ } = runContent || {};
382
+ const executionId = runMessage?.id === id && initExecutionId ? initExecutionId : (0, _shared.getUniqueId)(id);
383
+ this[K_EXEC].set('executionId', executionId);
354
384
  this._consumeApi();
355
385
  const content = this._createMessage({
356
- ...runContent,
386
+ ...runMessage,
357
387
  executionId
358
388
  });
359
389
  const broker = this.broker;
@@ -558,10 +588,8 @@ Activity.prototype.getActivityById = function getActivityById(elementId) {
558
588
 
559
589
  /** @internal */
560
590
  Activity.prototype._runDiscard = function runDiscard(discardContent) {
561
- const exec = this[K_EXEC];
562
- const executionId = exec.get('initExecutionId') || (0, _shared.getUniqueId)(this.id);
563
- exec.set('executionId', executionId);
564
- exec.delete('initExecutionId');
591
+ const executionId = (0, _shared.getUniqueId)(this.id);
592
+ this[K_EXEC].set('executionId', executionId);
565
593
  this._consumeApi();
566
594
  const content = this._createMessage({
567
595
  ...discardContent,
@@ -613,7 +641,7 @@ Activity.prototype._onShakeMessage = function _onShakeMessage(sourceMessage) {
613
641
  id: this.id,
614
642
  type: this.type
615
643
  });
616
- return this.broker.publish('event', 'activity.shake.join', message.content, {
644
+ return this.broker.publish('event', 'activity.shake.converge', message.content, {
617
645
  persistent: false,
618
646
  type: 'shake'
619
647
  });
@@ -674,43 +702,38 @@ Activity.prototype._shakeOutbound = function shakeOutbound(sourceMessage) {
674
702
  for (const t of targets.values()) t.shake(message);
675
703
  };
676
704
 
677
- /** @internal */
678
- Activity.prototype._consumeInbound = function consumeInbound() {
679
- if (!this[_constants.K_ACTIVATED]) return;
680
- if (this.status || !this._getInboundTriggers().length) return;
681
- const inboundQ = this.broker.getQueue('inbound-q');
682
- const onInbound = this[_constants.K_MESSAGE_HANDLERS].onInbound;
683
- return inboundQ.assertConsumer(onInbound, {
684
- consumerTag: '_run-on-inbound'
685
- });
686
- };
687
-
688
705
  /** @internal */
689
706
  Activity.prototype._onInbound = function onInbound(routingKey, message) {
690
707
  message.ack();
691
708
  const broker = this.broker;
692
709
  broker.cancel('_run-on-inbound');
693
710
  const content = message.content;
694
- const inbound = [(0, _messageHelper.cloneContent)(content)];
695
711
  switch (routingKey) {
696
- case 'activity.relink':
697
- if (content.executionId) this[K_EXEC].set('initExecutionId', content.executionId);
698
- return this.run({
699
- message: content.message,
700
- inbound
701
- });
712
+ case 'activity.init':
713
+ {
714
+ const exec = this[K_EXEC];
715
+ exec.set('initialized', (exec.get('initialized') || 0) - 1);
716
+ return this.run({
717
+ initExecutionId: content.executionId,
718
+ id: content.id,
719
+ message: content.message,
720
+ ...(content.inbound?.length && {
721
+ inbound: content.inbound
722
+ })
723
+ });
724
+ }
702
725
  case 'association.take':
703
726
  case 'flow.take':
704
727
  case 'activity.restart':
705
728
  case 'activity.enter':
706
729
  return this.run({
707
730
  message: content.message,
708
- inbound
731
+ inbound: [(0, _messageHelper.cloneContent)(content)]
709
732
  });
710
733
  case 'activity.discard':
711
734
  {
712
735
  return this._runDiscard({
713
- inbound
736
+ inbound: [(0, _messageHelper.cloneContent)(content)]
714
737
  });
715
738
  }
716
739
  }
@@ -740,26 +763,9 @@ Activity.prototype._onInboundEvent = function onInboundEvent(routingKey, message
740
763
  {
741
764
  const linkName = content.message?.linkName;
742
765
  if (!this[K_FLAGS].linkNames?.includes(linkName)) break;
743
- const executionId = (0, _shared.getUniqueId)(this.id);
744
- this.broker.publish('event', 'activity.enter', (0, _messageHelper.cloneContent)(content, {
745
- id: this.id,
746
- type: this.type,
747
- executionId,
748
- state: 'enter',
749
- message: {
750
- ...content.message
751
- }
752
- }));
753
- inboundQ.queueMessage({
754
- routingKey: 'activity.relink'
755
- }, (0, _messageHelper.cloneContent)(content, {
756
- id: this.id,
757
- executionId,
758
- message: {
759
- ...content.message
760
- }
761
- }), properties);
762
- return;
766
+ return this.init({
767
+ inbound: [(0, _messageHelper.cloneContent)(content)]
768
+ });
763
769
  }
764
770
  case 'association.take':
765
771
  case 'flow.take':
@@ -942,7 +948,7 @@ Activity.prototype._continueRunMessage = function continueRunMessage(routingKey,
942
948
  case 'run.next':
943
949
  message.ack();
944
950
  this._pauseRunQ();
945
- return this._consumeInbound();
951
+ return this.consumeInbound();
946
952
  }
947
953
  if (!step) message.ack();
948
954
  };
package/dist/constants.js CHANGED
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.K_TARGETS = exports.K_STOPPED = exports.K_STATUS = exports.K_STATE_MESSAGE = exports.K_REFERENCE_INFO = exports.K_REFERENCE_ELEMENT = exports.K_MESSAGE_Q = exports.K_MESSAGE_HANDLERS = exports.K_EXTENSIONS = exports.K_EXECUTION = exports.K_EXECUTE_MESSAGE = exports.K_COUNTERS = exports.K_CONSUMING = exports.K_COMPLETED = exports.K_ACTIVATED = void 0;
6
+ exports.STATE_VERSION = exports.K_TARGETS = exports.K_STOPPED = exports.K_STATUS = exports.K_STATE_MESSAGE = exports.K_REFERENCE_INFO = exports.K_REFERENCE_ELEMENT = exports.K_MESSAGE_Q = exports.K_MESSAGE_HANDLERS = exports.K_EXTENSIONS = exports.K_EXECUTION = exports.K_EXECUTE_MESSAGE = exports.K_COUNTERS = exports.K_CONSUMING = exports.K_COMPLETED = exports.K_ACTIVATED = void 0;
7
7
  const K_ACTIVATED = exports.K_ACTIVATED = Symbol.for('activated');
8
8
  const K_COMPLETED = exports.K_COMPLETED = Symbol.for('completed');
9
9
  const K_CONSUMING = exports.K_CONSUMING = Symbol.for('consuming');
@@ -18,4 +18,10 @@ const K_REFERENCE_INFO = exports.K_REFERENCE_INFO = Symbol.for('referenceInfo');
18
18
  const K_STATE_MESSAGE = exports.K_STATE_MESSAGE = Symbol.for('stateMessage');
19
19
  const K_STATUS = exports.K_STATUS = Symbol.for('status');
20
20
  const K_STOPPED = exports.K_STOPPED = Symbol.for('stopped');
21
- const K_TARGETS = exports.K_TARGETS = Symbol.for('targets');
21
+ const K_TARGETS = exports.K_TARGETS = Symbol.for('targets');
22
+
23
+ /**
24
+ * State version. Tracks the package major; bump on each major. Recovering an older major triggers
25
+ * migrations. Unstamped legacy states are treated as version 0.
26
+ */
27
+ const STATE_VERSION = exports.STATE_VERSION = 18;
@@ -176,6 +176,7 @@ Definition.prototype.resume = function resume(callback) {
176
176
  */
177
177
  Definition.prototype.getState = function getState() {
178
178
  return this._createMessage({
179
+ stateVersion: _constants.STATE_VERSION,
179
180
  status: this.status,
180
181
  stopped: this.stopped,
181
182
  counters: this.counters,
@@ -194,6 +195,10 @@ Definition.prototype.getState = function getState() {
194
195
  Definition.prototype.recover = function recover(state) {
195
196
  if (this.isRunning) throw new Error('cannot recover running definition');
196
197
  if (!state) return this;
198
+ const recoveredVersion = state.stateVersion || 0;
199
+ if (recoveredVersion !== _constants.STATE_VERSION) {
200
+ this.logger.debug(`<${this.id}> recover state version ${recoveredVersion} into runtime state version ${_constants.STATE_VERSION}`);
201
+ }
197
202
  this[_constants.K_STOPPED] = !!state.stopped;
198
203
  this[_constants.K_STATUS] = state.status;
199
204
  const exec = this[_constants.K_EXECUTION];
@@ -206,7 +211,7 @@ Definition.prototype.recover = function recover(state) {
206
211
  }
207
212
  this.environment.recover(state.environment);
208
213
  if (state.execution) {
209
- exec.set('execution', new _DefinitionExecution.DefinitionExecution(this, this.context).recover(state.execution));
214
+ exec.set('execution', new _DefinitionExecution.DefinitionExecution(this, this.context).recover(state.execution, recoveredVersion));
210
215
  }
211
216
  this.broker.recover(state.broker);
212
217
  return this;
@@ -186,9 +186,10 @@ DefinitionExecution.prototype.resume = function resume() {
186
186
  /**
187
187
  * Restore execution state captured by getState. Reinstates running processes from the snapshot.
188
188
  * @param {import('#types').DefinitionExecutionState} [state]
189
+ * @param {number} [recoveredVersion] State version
189
190
  * @returns {this}
190
191
  */
191
- DefinitionExecution.prototype.recover = function recover(state) {
192
+ DefinitionExecution.prototype.recover = function recover(state, recoveredVersion) {
192
193
  if (!state) return this;
193
194
  this.executionId = state.executionId;
194
195
  this[_constants.K_STOPPED] = state.stopped;
@@ -208,7 +209,7 @@ DefinitionExecution.prototype.recover = function recover(state) {
208
209
  }
209
210
  if (!bp) continue;
210
211
  ids.add(bpid);
211
- bp.recover(bpState);
212
+ bp.recover(bpState, recoveredVersion);
212
213
  running.add(bp);
213
214
  }
214
215
  return this;
@@ -44,9 +44,9 @@ Object.defineProperty(BoundaryEventBehaviour.prototype, 'executionId', {
44
44
  }
45
45
  });
46
46
  Object.defineProperty(BoundaryEventBehaviour.prototype, 'cancelActivity', {
47
+ /** @returns {boolean} */
47
48
  get() {
48
- const behaviour = this.activity.behaviour || {};
49
- return 'cancelActivity' in behaviour ? behaviour.cancelActivity : true;
49
+ return this.activity.behaviour?.cancelActivity ?? true;
50
50
  }
51
51
  });
52
52
 
@@ -76,11 +76,15 @@ function ParallelGatewayBehaviour(activity) {
76
76
  this.type = activity.type;
77
77
  this.activity = activity;
78
78
  this.broker = activity.broker;
79
+ /**
80
+ * Inbound taken sequence flow sequences
81
+ * @type {Set<import('#types').ElementMessageContent}
82
+ */
79
83
  this.inbound = new Set();
80
- this.isConverging = true;
81
84
  this[_constants.K_EXECUTE_MESSAGE] = undefined;
82
85
  }
83
86
  Object.defineProperty(ParallelGatewayBehaviour.prototype, 'executionId', {
87
+ /** @returns {string | undefined} */
84
88
  get() {
85
89
  return this[_constants.K_EXECUTE_MESSAGE]?.content.executionId;
86
90
  }
@@ -110,6 +114,12 @@ ParallelGatewayBehaviour.prototype.execute = function execute(executeMessage) {
110
114
  }
111
115
  }
112
116
  };
117
+
118
+ /**
119
+ * Setup peer monitor
120
+ * @param {import('#types').ElementBrokerMessage} executeMessage
121
+ * @returns {void}
122
+ */
113
123
  ParallelGatewayBehaviour.prototype.setup = function setup(executeMessage) {
114
124
  const peerIds = new Set([...this.activity[K_PEERS].values()].map(v => [...v]).flat());
115
125
  this[_constants.K_TARGETS] = new Map([...peerIds].map(pid => [pid, this.activity.getActivityById(pid)]));
@@ -142,9 +152,7 @@ ParallelGatewayBehaviour.prototype.setup = function setup(executeMessage) {
142
152
  exclusive: true,
143
153
  prefetch: 10000
144
154
  });
145
- if (this.isConverging) {
146
- this.broker.publish('event', 'activity.converge', (0, _messageHelper.cloneContent)(executeContent));
147
- }
155
+ this.broker.publish('event', 'activity.converge', (0, _messageHelper.cloneContent)(executeContent));
148
156
  return this.broker.publish('execution', 'execute.start', (0, _messageHelper.cloneContent)(executeMessage.content, {
149
157
  preventComplete: true,
150
158
  state: STATE_SETUP
@@ -179,17 +187,19 @@ ParallelGatewayBehaviour.prototype._stop = function stop() {
179
187
  this.broker.cancel('_parallel-execution-peer-enter-tag');
180
188
  this.peerMonitor.stop();
181
189
  };
190
+
191
+ /**
192
+ * Peer monitor
193
+ * @param {import('#types').Activity} activity parallel gateway activity
194
+ * @param {Map<string, import('#types').Activity} targets parallel gateway peer target activities
195
+ */
182
196
  function PeerMonitor(activity, targets) {
183
197
  this.activity = activity;
184
198
  this.id = activity.id;
185
199
  this.broker = activity.broker;
186
- this.running = 0;
187
- this.index = 0;
188
- this.discarded = 0;
189
200
  this.running = new Map();
190
201
  this.watching = new Map();
191
202
  this.targets = targets;
192
- this.touched = new Set();
193
203
  this.inbound = [];
194
204
  }
195
205
  Object.defineProperty(PeerMonitor.prototype, 'isRunning', {
@@ -197,6 +207,12 @@ Object.defineProperty(PeerMonitor.prototype, 'isRunning', {
197
207
  return this.running.size > 0;
198
208
  }
199
209
  });
210
+
211
+ /**
212
+ * Execute peer monitor
213
+ * @param {import('#types').ElementBrokerMessage} executeMessage
214
+ * @returns {number} number of running peers
215
+ */
200
216
  PeerMonitor.prototype.execute = function execute(executeMessage) {
201
217
  const message = (0, _messageHelper.cloneMessage)(executeMessage);
202
218
  const inbound = message.content.inbound.pop();
@@ -208,12 +224,16 @@ PeerMonitor.prototype.execute = function execute(executeMessage) {
208
224
  state: STATE_MONTITORING,
209
225
  preventComplete: true
210
226
  });
211
- this.touched.add(inbound.sourceId);
212
227
  for (const target of this.targets.values()) {
213
228
  this.monitor(target);
214
229
  }
215
230
  return this.running.size;
216
231
  };
232
+
233
+ /**
234
+ * Monitor peer activity
235
+ * @param {import('#types').Activity} peerActivity
236
+ */
217
237
  PeerMonitor.prototype.monitor = function monitor(peerActivity) {
218
238
  if (this.watching.has(peerActivity.id)) return;
219
239
  this.activity.logger.debug(`<${this.id}> monitor <${peerActivity.id}> with status: ${peerActivity.status}`);
@@ -191,10 +191,11 @@ Process.prototype.getState = function getState() {
191
191
  /**
192
192
  * Restore process state captured by getState.
193
193
  * @param {import('#types').ProcessState} [state]
194
+ * @param {number} [recoveredVersion] State version
194
195
  * @returns {this}
195
196
  * @throws {Error} when called on a running process
196
197
  */
197
- Process.prototype.recover = function recover(state) {
198
+ Process.prototype.recover = function recover(state, recoveredVersion) {
198
199
  if (this.isRunning) throw new Error(`cannot recover running process <${this.id}>`);
199
200
  if (!state) return this;
200
201
  this[_constants.K_STOPPED] = !!state.stopped;
@@ -207,7 +208,7 @@ Process.prototype.recover = function recover(state) {
207
208
  };
208
209
  this.environment.recover(state.environment);
209
210
  if (state.execution) {
210
- exec.set('execution', new _ProcessExecution.ProcessExecution(this, this.context).recover(state.execution));
211
+ exec.set('execution', new _ProcessExecution.ProcessExecution(this, this.context).recover(state.execution, recoveredVersion));
211
212
  }
212
213
  this.broker.recover(state.broker);
213
214
  return this;
@@ -14,6 +14,7 @@ const K_ELEMENTS = Symbol.for('elements');
14
14
  const K_PARENT = Symbol.for('parent');
15
15
  const K_TRACKER = Symbol.for('activity tracker');
16
16
  const K_PEERS_DISCOVERED = Symbol.for('peers discovered');
17
+ const K_RECOVERED_VERSION = Symbol.for('recovered version');
17
18
 
18
19
  /**
19
20
  * Drives the execution of a single process or sub-process: activates children, routes activity
@@ -163,6 +164,8 @@ ProcessExecution.prototype.resume = function resume() {
163
164
  activity.resume();
164
165
  }
165
166
  if (this[_constants.K_COMPLETED]) return;
167
+ this._reconcileStartEvents();
168
+ if (this[_constants.K_COMPLETED]) return;
166
169
  if (!postponed.size && status === 'executing') return this._complete('completed');
167
170
  };
168
171
 
@@ -208,11 +211,13 @@ ProcessExecution.prototype.getState = function getState() {
208
211
  /**
209
212
  * Restore execution state captured by getState.
210
213
  * @param {import('#types').ProcessExecutionState} [state]
214
+ * @param {number} [recoveredVersion] State version
211
215
  * @returns {this}
212
216
  */
213
- ProcessExecution.prototype.recover = function recover(state) {
217
+ ProcessExecution.prototype.recover = function recover(state, recoveredVersion) {
214
218
  if (!state) return this;
215
219
  this.executionId = state.executionId;
220
+ this[K_RECOVERED_VERSION] = recoveredVersion;
216
221
  this[_constants.K_STOPPED] = state.stopped;
217
222
  this[_constants.K_COMPLETED] = state.completed;
218
223
  this[_constants.K_STATUS] = state.status;
@@ -386,7 +391,7 @@ ProcessExecution.prototype._start = function start() {
386
391
  this._shakeOnStart();
387
392
  for (const a of startActivities) a.init();
388
393
  this[_constants.K_STATUS] = 'executing';
389
- for (const a of startActivities) a.run();
394
+ for (const a of startActivities) a.consumeInbound();
390
395
  if (!startActivities.size) {
391
396
  for (const a of this[K_ELEMENTS].triggeredByEvent) {
392
397
  if (a.isCatching && !a.isRunning) a.run();
@@ -551,7 +556,7 @@ ProcessExecution.prototype._shakeElements = function shakeElements(fromId) {
551
556
  }) => {
552
557
  if (content.parent.id !== this.id) return;
553
558
  switch (routingKey) {
554
- case 'activity.shake.join':
559
+ case 'activity.shake.converge':
555
560
  {
556
561
  const join = convergingGateways.get(content.join);
557
562
  if (!join) {
@@ -740,16 +745,9 @@ ProcessExecution.prototype._onChildMessage = function onChildMessage(routingKey,
740
745
  }
741
746
  case 'activity.end':
742
747
  {
743
- if (!content.isStartEvent) break;
744
- const elements = this[K_ELEMENTS];
745
- if (elements.startEventCount <= 1) break;
746
- const startPeers = new Set();
747
- for (const msg of elements.postponed) {
748
- const peerId = msg.content.id;
749
- if (peerId !== content.id && msg.content.isStartEvent) startPeers.add(msg);
750
- }
751
- elements.startEventCount = 0;
752
- for (const msg of startPeers) this._getChildApi(msg).discard();
748
+ if (!(content.isStartEvent || this.getActivityById(content.id)?.isStartEvent)) break;
749
+ if (this[K_ELEMENTS].startEventCount <= 1) break;
750
+ this._discardArmedStartEvents(content.id);
753
751
  break;
754
752
  }
755
753
  case 'activity.error':
@@ -1041,6 +1039,42 @@ ProcessExecution.prototype._getChildById = function getChildById(childId) {
1041
1039
  return this.getActivityById(childId) || this._getFlowById(childId);
1042
1040
  };
1043
1041
 
1042
+ /**
1043
+ * Discard the other armed start events once one mutually exclusive entry point wins.
1044
+ * Resolves the start-event flag from the live activity so recovered pre-flag state is handled.
1045
+ * @internal
1046
+ */
1047
+ ProcessExecution.prototype._discardArmedStartEvents = function discardArmedStartEvents(winnerId) {
1048
+ const elements = this[K_ELEMENTS];
1049
+ const startPeers = [];
1050
+ for (const msg of elements.postponed) {
1051
+ const peerId = msg.content.id;
1052
+ if (peerId === winnerId) continue;
1053
+ if (this.getActivityById(peerId)?.isStartEvent) startPeers.push(msg);
1054
+ }
1055
+ if (!startPeers.length) return;
1056
+ elements.startEventCount = 0;
1057
+ for (const msg of startPeers) this._getChildApi(msg).discard();
1058
+ };
1059
+
1060
+ /**
1061
+ * On resume of a state from an older major, discard start events left armed when another entry
1062
+ * point already won before recovery. The winning start event's `activity.end` cannot replay, so
1063
+ * the live discard trigger never fires.
1064
+ * @internal
1065
+ */
1066
+ ProcessExecution.prototype._reconcileStartEvents = function reconcileStartEvents() {
1067
+ const elements = this[K_ELEMENTS];
1068
+ if (elements.startEventCount <= 1) return;
1069
+ if (!(this[K_RECOVERED_VERSION] < _constants.STATE_VERSION)) return;
1070
+ for (const child of elements.children) {
1071
+ if (child.isStartEvent && child.counters.taken) {
1072
+ this._discardArmedStartEvents();
1073
+ return;
1074
+ }
1075
+ }
1076
+ };
1077
+
1044
1078
  /** @internal */
1045
1079
  ProcessExecution.prototype._getChildApi = function getChildApi(message) {
1046
1080
  const content = message.content;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bpmn-elements",
3
- "version": "18.0.0",
3
+ "version": "18.0.2",
4
4
  "description": "Executable workflow elements based on BPMN 2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -91,8 +91,10 @@ export function Activity(Behaviour, activityDef, context) {
91
91
  outboundEvaluator: new OutboundEvaluator(this, outboundSequenceFlows),
92
92
  };
93
93
 
94
+ const isThrowingLink = activityDef.isThrowing && activityDef.linkNames?.length;
95
+
94
96
  this[K_FLAGS] = {
95
- isEnd: !outboundSequenceFlows.length,
97
+ isEnd: !outboundSequenceFlows.length && !isThrowingLink,
96
98
  isStart: !hasInboundTrigger && !behaviour.triggeredByEvent && !activityDef.isCatching,
97
99
  isSubProcess: activityDef.isSubProcess,
98
100
  isMultiInstance: !!behaviour.loopCharacteristics,
@@ -262,7 +264,7 @@ Object.defineProperties(Activity.prototype, {
262
264
  },
263
265
  initialized: {
264
266
  get() {
265
- return !!this[K_EXEC]?.get('initExecutionId');
267
+ return this[K_EXEC].get('initialized') > 0;
266
268
  },
267
269
  },
268
270
  });
@@ -274,7 +276,25 @@ Object.defineProperties(Activity.prototype, {
274
276
  Activity.prototype.activate = function activate() {
275
277
  if (this[K_ACTIVATED]) return;
276
278
  this[K_ACTIVATED] = true;
277
- return this.addInboundListeners() && this._consumeInbound();
279
+ this.addInboundListeners();
280
+ return this.consumeInbound();
281
+ };
282
+
283
+ /**
284
+ * Assert the inbound queue consumer when the activity has a trigger or is initialized.
285
+ * Idempotent: asserting the consumer again while one is active is a no-op.
286
+ * @returns {void}
287
+ */
288
+ Activity.prototype.consumeInbound = function consumeInbound() {
289
+ if (!this[K_ACTIVATED]) return;
290
+
291
+ if (this.status) return;
292
+
293
+ if (!this._getInboundTriggers().length && !this.initialized) return;
294
+
295
+ const onInbound = this[K_MESSAGE_HANDLERS].onInbound;
296
+
297
+ return this.broker.getQueue('inbound-q').assertConsumer(onInbound, { consumerTag: '_run-on-inbound' });
278
298
  };
279
299
 
280
300
  /** @internal */
@@ -319,14 +339,18 @@ Activity.prototype.deactivate = function deactivate() {
319
339
  /**
320
340
  * Initialise activity executionId and emit init event without starting the run.
321
341
  * @param {Record<string, any>} [initContent] Optional content merged into the init message
342
+ * @param {import('smqp').MessageProperties} [properties] Optional message properties merged into the init message properties
322
343
  */
323
- Activity.prototype.init = function init(initContent) {
344
+ Activity.prototype.init = function init(initContent, properties) {
324
345
  const id = this.id;
325
346
  const exec = this[K_EXEC];
326
- const executionId = exec.has('initExecutionId') ? exec.get('initExecutionId') : getUniqueId(id);
327
- exec.set('initExecutionId', executionId);
347
+ exec.set('initialized', (exec.get('initialized') || 0) + 1);
348
+ const executionId = getUniqueId(id);
328
349
  this.logger.debug(`<${id}> initialized with executionId <${executionId}>`);
329
350
  this._publishEvent('init', this._createMessage({ ...initContent, executionId }));
351
+ this.broker
352
+ .getQueue('inbound-q')
353
+ .queueMessage({ routingKey: 'activity.init' }, { ...initContent, id, executionId }, { persistent: false, ...properties });
330
354
  };
331
355
 
332
356
  /**
@@ -338,14 +362,13 @@ Activity.prototype.run = function run(runContent) {
338
362
  const id = this.id;
339
363
  if (this.isRunning) throw new Error(`activity <${id}> is already running`);
340
364
 
341
- const exec = this[K_EXEC];
342
- const executionId = exec.get('initExecutionId') || getUniqueId(id);
343
- exec.set('executionId', executionId);
344
- exec.delete('initExecutionId');
365
+ const { initExecutionId, ...runMessage } = runContent || {};
366
+ const executionId = runMessage?.id === id && initExecutionId ? initExecutionId : getUniqueId(id);
367
+ this[K_EXEC].set('executionId', executionId);
345
368
 
346
369
  this._consumeApi();
347
370
 
348
- const content = this._createMessage({ ...runContent, executionId });
371
+ const content = this._createMessage({ ...runMessage, executionId });
349
372
  const broker = this.broker;
350
373
 
351
374
  broker.publish('run', 'run.enter', content);
@@ -541,10 +564,8 @@ Activity.prototype.getActivityById = function getActivityById(elementId) {
541
564
 
542
565
  /** @internal */
543
566
  Activity.prototype._runDiscard = function runDiscard(discardContent) {
544
- const exec = this[K_EXEC];
545
- const executionId = exec.get('initExecutionId') || getUniqueId(this.id);
546
- exec.set('executionId', executionId);
547
- exec.delete('initExecutionId');
567
+ const executionId = getUniqueId(this.id);
568
+ this[K_EXEC].set('executionId', executionId);
548
569
 
549
570
  this._consumeApi();
550
571
 
@@ -594,7 +615,7 @@ Activity.prototype._onShakeMessage = function _onShakeMessage(sourceMessage) {
594
615
  if (this[K_FLAGS].isParallelGateway) {
595
616
  const message = cloneMessage(sourceMessage, { join: this.id });
596
617
  message.content.sequence.push({ id: this.id, type: this.type });
597
- return this.broker.publish('event', 'activity.shake.join', message.content, {
618
+ return this.broker.publish('event', 'activity.shake.converge', message.content, {
598
619
  persistent: false,
599
620
  type: 'shake',
600
621
  });
@@ -641,18 +662,6 @@ Activity.prototype._shakeOutbound = function shakeOutbound(sourceMessage) {
641
662
  for (const t of targets.values()) t.shake(message);
642
663
  };
643
664
 
644
- /** @internal */
645
- Activity.prototype._consumeInbound = function consumeInbound() {
646
- if (!this[K_ACTIVATED]) return;
647
-
648
- if (this.status || !this._getInboundTriggers().length) return;
649
-
650
- const inboundQ = this.broker.getQueue('inbound-q');
651
- const onInbound = this[K_MESSAGE_HANDLERS].onInbound;
652
-
653
- return inboundQ.assertConsumer(onInbound, { consumerTag: '_run-on-inbound' });
654
- };
655
-
656
665
  /** @internal */
657
666
  Activity.prototype._onInbound = function onInbound(routingKey, message) {
658
667
  message.ack();
@@ -660,25 +669,28 @@ Activity.prototype._onInbound = function onInbound(routingKey, message) {
660
669
  broker.cancel('_run-on-inbound');
661
670
 
662
671
  const content = message.content;
663
- const inbound = [cloneContent(content)];
664
672
 
665
673
  switch (routingKey) {
666
- case 'activity.relink':
667
- if (content.executionId) this[K_EXEC].set('initExecutionId', content.executionId);
674
+ case 'activity.init': {
675
+ const exec = this[K_EXEC];
676
+ exec.set('initialized', (exec.get('initialized') || 0) - 1);
668
677
  return this.run({
678
+ initExecutionId: content.executionId,
679
+ id: content.id,
669
680
  message: content.message,
670
- inbound,
681
+ ...(content.inbound?.length && { inbound: content.inbound }),
671
682
  });
683
+ }
672
684
  case 'association.take':
673
685
  case 'flow.take':
674
686
  case 'activity.restart':
675
687
  case 'activity.enter':
676
688
  return this.run({
677
689
  message: content.message,
678
- inbound,
690
+ inbound: [cloneContent(content)],
679
691
  });
680
692
  case 'activity.discard': {
681
- return this._runDiscard({ inbound });
693
+ return this._runDiscard({ inbound: [cloneContent(content)] });
682
694
  }
683
695
  }
684
696
  };
@@ -702,18 +714,7 @@ Activity.prototype._onInboundEvent = function onInboundEvent(routingKey, message
702
714
  case 'activity.link': {
703
715
  const linkName = content.message?.linkName;
704
716
  if (!this[K_FLAGS].linkNames?.includes(linkName)) break;
705
- const executionId = getUniqueId(this.id);
706
- this.broker.publish(
707
- 'event',
708
- 'activity.enter',
709
- cloneContent(content, { id: this.id, type: this.type, executionId, state: 'enter', message: { ...content.message } })
710
- );
711
- inboundQ.queueMessage(
712
- { routingKey: 'activity.relink' },
713
- cloneContent(content, { id: this.id, executionId, message: { ...content.message } }),
714
- properties
715
- );
716
- return;
717
+ return this.init({ inbound: [cloneContent(content)] });
717
718
  }
718
719
  case 'association.take':
719
720
  case 'flow.take':
@@ -890,7 +891,7 @@ Activity.prototype._continueRunMessage = function continueRunMessage(routingKey,
890
891
  case 'run.next':
891
892
  message.ack();
892
893
  this._pauseRunQ();
893
- return this._consumeInbound();
894
+ return this.consumeInbound();
894
895
  }
895
896
 
896
897
  if (!step) message.ack();
package/src/constants.js CHANGED
@@ -13,3 +13,9 @@ export const K_STATE_MESSAGE = Symbol.for('stateMessage');
13
13
  export const K_STATUS = Symbol.for('status');
14
14
  export const K_STOPPED = Symbol.for('stopped');
15
15
  export const K_TARGETS = Symbol.for('targets');
16
+
17
+ /**
18
+ * State version. Tracks the package major; bump on each major. Recovering an older major triggers
19
+ * migrations. Unstamped legacy states are treated as version 0.
20
+ */
21
+ export const STATE_VERSION = 18;
@@ -13,6 +13,7 @@ import {
13
13
  K_STATE_MESSAGE,
14
14
  K_STATUS,
15
15
  K_STOPPED,
16
+ STATE_VERSION,
16
17
  } from '../constants.js';
17
18
 
18
19
  /**
@@ -181,6 +182,7 @@ Definition.prototype.resume = function resume(callback) {
181
182
  */
182
183
  Definition.prototype.getState = function getState() {
183
184
  return this._createMessage({
185
+ stateVersion: STATE_VERSION,
184
186
  status: this.status,
185
187
  stopped: this.stopped,
186
188
  counters: this.counters,
@@ -200,6 +202,11 @@ Definition.prototype.recover = function recover(state) {
200
202
  if (this.isRunning) throw new Error('cannot recover running definition');
201
203
  if (!state) return this;
202
204
 
205
+ const recoveredVersion = state.stateVersion || 0;
206
+ if (recoveredVersion !== STATE_VERSION) {
207
+ this.logger.debug(`<${this.id}> recover state version ${recoveredVersion} into runtime state version ${STATE_VERSION}`);
208
+ }
209
+
203
210
  this[K_STOPPED] = !!state.stopped;
204
211
  this[K_STATUS] = state.status;
205
212
 
@@ -212,7 +219,7 @@ Definition.prototype.recover = function recover(state) {
212
219
  this.environment.recover(state.environment);
213
220
 
214
221
  if (state.execution) {
215
- exec.set('execution', new DefinitionExecution(this, this.context).recover(state.execution));
222
+ exec.set('execution', new DefinitionExecution(this, this.context).recover(state.execution, recoveredVersion));
216
223
  }
217
224
 
218
225
  this.broker.recover(state.broker);
@@ -189,9 +189,10 @@ DefinitionExecution.prototype.resume = function resume() {
189
189
  /**
190
190
  * Restore execution state captured by getState. Reinstates running processes from the snapshot.
191
191
  * @param {import('#types').DefinitionExecutionState} [state]
192
+ * @param {number} [recoveredVersion] State version
192
193
  * @returns {this}
193
194
  */
194
- DefinitionExecution.prototype.recover = function recover(state) {
195
+ DefinitionExecution.prototype.recover = function recover(state, recoveredVersion) {
195
196
  if (!state) return this;
196
197
  this.executionId = state.executionId;
197
198
 
@@ -216,7 +217,7 @@ DefinitionExecution.prototype.recover = function recover(state) {
216
217
  if (!bp) continue;
217
218
 
218
219
  ids.add(bpid);
219
- bp.recover(bpState);
220
+ bp.recover(bpState, recoveredVersion);
220
221
  running.add(bp);
221
222
  }
222
223
 
@@ -41,9 +41,9 @@ Object.defineProperty(BoundaryEventBehaviour.prototype, 'executionId', {
41
41
  });
42
42
 
43
43
  Object.defineProperty(BoundaryEventBehaviour.prototype, 'cancelActivity', {
44
+ /** @returns {boolean} */
44
45
  get() {
45
- const behaviour = this.activity.behaviour || {};
46
- return 'cancelActivity' in behaviour ? behaviour.cancelActivity : true;
46
+ return this.activity.behaviour?.cancelActivity ?? true;
47
47
  },
48
48
  });
49
49
 
@@ -74,13 +74,17 @@ export function ParallelGatewayBehaviour(activity) {
74
74
  this.type = activity.type;
75
75
  this.activity = activity;
76
76
  this.broker = activity.broker;
77
+ /**
78
+ * Inbound taken sequence flow sequences
79
+ * @type {Set<import('#types').ElementMessageContent}
80
+ */
77
81
  this.inbound = new Set();
78
82
 
79
- this.isConverging = true;
80
83
  this[K_EXECUTE_MESSAGE] = undefined;
81
84
  }
82
85
 
83
86
  Object.defineProperty(ParallelGatewayBehaviour.prototype, 'executionId', {
87
+ /** @returns {string | undefined} */
84
88
  get() {
85
89
  return this[K_EXECUTE_MESSAGE]?.content.executionId;
86
90
  },
@@ -112,6 +116,11 @@ ParallelGatewayBehaviour.prototype.execute = function execute(executeMessage) {
112
116
  }
113
117
  };
114
118
 
119
+ /**
120
+ * Setup peer monitor
121
+ * @param {import('#types').ElementBrokerMessage} executeMessage
122
+ * @returns {void}
123
+ */
115
124
  ParallelGatewayBehaviour.prototype.setup = function setup(executeMessage) {
116
125
  const peerIds = new Set([...this.activity[K_PEERS].values()].map((v) => [...v]).flat());
117
126
  this[K_TARGETS] = new Map([...peerIds].map((pid) => [pid, this.activity.getActivityById(pid)]));
@@ -152,9 +161,7 @@ ParallelGatewayBehaviour.prototype.setup = function setup(executeMessage) {
152
161
  { consumerTag: '_converging-inbound', exclusive: true, prefetch: 10000 }
153
162
  );
154
163
 
155
- if (this.isConverging) {
156
- this.broker.publish('event', 'activity.converge', cloneContent(executeContent));
157
- }
164
+ this.broker.publish('event', 'activity.converge', cloneContent(executeContent));
158
165
 
159
166
  return this.broker.publish(
160
167
  'execution',
@@ -197,17 +204,18 @@ ParallelGatewayBehaviour.prototype._stop = function stop() {
197
204
  this.peerMonitor.stop();
198
205
  };
199
206
 
207
+ /**
208
+ * Peer monitor
209
+ * @param {import('#types').Activity} activity parallel gateway activity
210
+ * @param {Map<string, import('#types').Activity} targets parallel gateway peer target activities
211
+ */
200
212
  function PeerMonitor(activity, targets) {
201
213
  this.activity = activity;
202
214
  this.id = activity.id;
203
215
  this.broker = activity.broker;
204
- this.running = 0;
205
- this.index = 0;
206
- this.discarded = 0;
207
216
  this.running = new Map();
208
217
  this.watching = new Map();
209
218
  this.targets = targets;
210
- this.touched = new Set();
211
219
  this.inbound = [];
212
220
  }
213
221
 
@@ -217,6 +225,11 @@ Object.defineProperty(PeerMonitor.prototype, 'isRunning', {
217
225
  },
218
226
  });
219
227
 
228
+ /**
229
+ * Execute peer monitor
230
+ * @param {import('#types').ElementBrokerMessage} executeMessage
231
+ * @returns {number} number of running peers
232
+ */
220
233
  PeerMonitor.prototype.execute = function execute(executeMessage) {
221
234
  const message = cloneMessage(executeMessage);
222
235
  const inbound = message.content.inbound.pop();
@@ -231,8 +244,6 @@ PeerMonitor.prototype.execute = function execute(executeMessage) {
231
244
  preventComplete: true,
232
245
  });
233
246
 
234
- this.touched.add(inbound.sourceId);
235
-
236
247
  for (const target of this.targets.values()) {
237
248
  this.monitor(target);
238
249
  }
@@ -240,6 +251,10 @@ PeerMonitor.prototype.execute = function execute(executeMessage) {
240
251
  return this.running.size;
241
252
  };
242
253
 
254
+ /**
255
+ * Monitor peer activity
256
+ * @param {import('#types').Activity} peerActivity
257
+ */
243
258
  PeerMonitor.prototype.monitor = function monitor(peerActivity) {
244
259
  if (this.watching.has(peerActivity.id)) return;
245
260
 
@@ -190,10 +190,11 @@ Process.prototype.getState = function getState() {
190
190
  /**
191
191
  * Restore process state captured by getState.
192
192
  * @param {import('#types').ProcessState} [state]
193
+ * @param {number} [recoveredVersion] State version
193
194
  * @returns {this}
194
195
  * @throws {Error} when called on a running process
195
196
  */
196
- Process.prototype.recover = function recover(state) {
197
+ Process.prototype.recover = function recover(state, recoveredVersion) {
197
198
  if (this.isRunning) throw new Error(`cannot recover running process <${this.id}>`);
198
199
  if (!state) return this;
199
200
 
@@ -205,7 +206,7 @@ Process.prototype.recover = function recover(state) {
205
206
  this.environment.recover(state.environment);
206
207
 
207
208
  if (state.execution) {
208
- exec.set('execution', new ProcessExecution(this, this.context).recover(state.execution));
209
+ exec.set('execution', new ProcessExecution(this, this.context).recover(state.execution, recoveredVersion));
209
210
  }
210
211
 
211
212
  this.broker.recover(state.broker);
@@ -2,13 +2,14 @@ import { ProcessApi } from '../Api.js';
2
2
  import { cloneContent, cloneMessage, pushParent } from '../messageHelper.js';
3
3
  import { getUniqueId } from '../shared.js';
4
4
  import { ActivityTracker } from '../Tracker.js';
5
- import { K_ACTIVATED, K_COMPLETED, K_EXECUTE_MESSAGE, K_MESSAGE_HANDLERS, K_STATUS, K_STOPPED } from '../constants.js';
5
+ import { K_ACTIVATED, K_COMPLETED, K_EXECUTE_MESSAGE, K_MESSAGE_HANDLERS, K_STATUS, K_STOPPED, STATE_VERSION } from '../constants.js';
6
6
 
7
7
  const K_ACTIVITY_Q = Symbol.for('activityQ');
8
8
  const K_ELEMENTS = Symbol.for('elements');
9
9
  const K_PARENT = Symbol.for('parent');
10
10
  const K_TRACKER = Symbol.for('activity tracker');
11
11
  const K_PEERS_DISCOVERED = Symbol.for('peers discovered');
12
+ const K_RECOVERED_VERSION = Symbol.for('recovered version');
12
13
 
13
14
  /**
14
15
  * Drives the execution of a single process or sub-process: activates children, routes activity
@@ -166,6 +167,10 @@ ProcessExecution.prototype.resume = function resume() {
166
167
 
167
168
  if (this[K_COMPLETED]) return;
168
169
 
170
+ this._reconcileStartEvents();
171
+
172
+ if (this[K_COMPLETED]) return;
173
+
169
174
  if (!postponed.size && status === 'executing') return this._complete('completed');
170
175
  };
171
176
 
@@ -204,11 +209,13 @@ ProcessExecution.prototype.getState = function getState() {
204
209
  /**
205
210
  * Restore execution state captured by getState.
206
211
  * @param {import('#types').ProcessExecutionState} [state]
212
+ * @param {number} [recoveredVersion] State version
207
213
  * @returns {this}
208
214
  */
209
- ProcessExecution.prototype.recover = function recover(state) {
215
+ ProcessExecution.prototype.recover = function recover(state, recoveredVersion) {
210
216
  if (!state) return this;
211
217
  this.executionId = state.executionId;
218
+ this[K_RECOVERED_VERSION] = recoveredVersion;
212
219
 
213
220
  this[K_STOPPED] = state.stopped;
214
221
  this[K_COMPLETED] = state.completed;
@@ -393,7 +400,7 @@ ProcessExecution.prototype._start = function start() {
393
400
 
394
401
  for (const a of startActivities) a.init();
395
402
  this[K_STATUS] = 'executing';
396
- for (const a of startActivities) a.run();
403
+ for (const a of startActivities) a.consumeInbound();
397
404
 
398
405
  if (!startActivities.size) {
399
406
  for (const a of this[K_ELEMENTS].triggeredByEvent) {
@@ -564,7 +571,7 @@ ProcessExecution.prototype._shakeElements = function shakeElements(fromId) {
564
571
  if (content.parent.id !== this.id) return;
565
572
 
566
573
  switch (routingKey) {
567
- case 'activity.shake.join': {
574
+ case 'activity.shake.converge': {
568
575
  const join = convergingGateways.get(content.join);
569
576
  if (!join) {
570
577
  convergingGateways.set(content.join, content);
@@ -738,17 +745,9 @@ ProcessExecution.prototype._onChildMessage = function onChildMessage(routingKey,
738
745
  break;
739
746
  }
740
747
  case 'activity.end': {
741
- if (!content.isStartEvent) break;
742
- const elements = this[K_ELEMENTS];
743
- if (elements.startEventCount <= 1) break;
744
-
745
- const startPeers = new Set();
746
- for (const msg of elements.postponed) {
747
- const peerId = msg.content.id;
748
- if (peerId !== content.id && msg.content.isStartEvent) startPeers.add(msg);
749
- }
750
- elements.startEventCount = 0;
751
- for (const msg of startPeers) this._getChildApi(msg).discard();
748
+ if (!(content.isStartEvent || this.getActivityById(content.id)?.isStartEvent)) break;
749
+ if (this[K_ELEMENTS].startEventCount <= 1) break;
750
+ this._discardArmedStartEvents(content.id);
752
751
  break;
753
752
  }
754
753
  case 'activity.error': {
@@ -1055,6 +1054,43 @@ ProcessExecution.prototype._getChildById = function getChildById(childId) {
1055
1054
  return this.getActivityById(childId) || this._getFlowById(childId);
1056
1055
  };
1057
1056
 
1057
+ /**
1058
+ * Discard the other armed start events once one mutually exclusive entry point wins.
1059
+ * Resolves the start-event flag from the live activity so recovered pre-flag state is handled.
1060
+ * @internal
1061
+ */
1062
+ ProcessExecution.prototype._discardArmedStartEvents = function discardArmedStartEvents(winnerId) {
1063
+ const elements = this[K_ELEMENTS];
1064
+ const startPeers = [];
1065
+ for (const msg of elements.postponed) {
1066
+ const peerId = msg.content.id;
1067
+ if (peerId === winnerId) continue;
1068
+ if (this.getActivityById(peerId)?.isStartEvent) startPeers.push(msg);
1069
+ }
1070
+ if (!startPeers.length) return;
1071
+ elements.startEventCount = 0;
1072
+ for (const msg of startPeers) this._getChildApi(msg).discard();
1073
+ };
1074
+
1075
+ /**
1076
+ * On resume of a state from an older major, discard start events left armed when another entry
1077
+ * point already won before recovery. The winning start event's `activity.end` cannot replay, so
1078
+ * the live discard trigger never fires.
1079
+ * @internal
1080
+ */
1081
+ ProcessExecution.prototype._reconcileStartEvents = function reconcileStartEvents() {
1082
+ const elements = this[K_ELEMENTS];
1083
+ if (elements.startEventCount <= 1) return;
1084
+ if (!(this[K_RECOVERED_VERSION] < STATE_VERSION)) return;
1085
+
1086
+ for (const child of elements.children) {
1087
+ if (child.isStartEvent && child.counters.taken) {
1088
+ this._discardArmedStartEvents();
1089
+ return;
1090
+ }
1091
+ }
1092
+ };
1093
+
1058
1094
  /** @internal */
1059
1095
  ProcessExecution.prototype._getChildApi = function getChildApi(message) {
1060
1096
  const content = message.content;
package/types/index.d.ts CHANGED
@@ -407,6 +407,8 @@ declare module 'bpmn-elements' {
407
407
  }
408
408
 
409
409
  export interface DefinitionState extends ElementState {
410
+ /** State version. Absent on states saved before versioning. */
411
+ stateVersion?: number;
410
412
  status: DefinitionStatus;
411
413
  stopped: boolean;
412
414
  executionId?: string;
@@ -578,6 +580,11 @@ declare module 'bpmn-elements' {
578
580
  * Subscribe to inbound flows and start consuming the inbound queue.
579
581
  * */
580
582
  activate(): void;
583
+ /**
584
+ * Assert the inbound queue consumer when the activity has a trigger or is initialized.
585
+ * Idempotent: asserting the consumer again while one is active is a no-op.
586
+ * */
587
+ consumeInbound(): void;
581
588
  /**
582
589
  * Cancel inbound subscriptions and any pending run/format consumers.
583
590
  */
@@ -585,8 +592,9 @@ declare module 'bpmn-elements' {
585
592
  /**
586
593
  * Initialise activity executionId and emit init event without starting the run.
587
594
  * @param initContent Optional content merged into the init message
595
+ * @param properties Optional message properties merged into the init message properties
588
596
  */
589
- init(initContent?: Record<string, any>): void;
597
+ init(initContent?: Record<string, any>, properties?: import("smqp").MessageProperties): void;
590
598
  /**
591
599
  * Start running the activity by publishing run.enter and run.start.
592
600
  * @param runContent Optional content merged into the run message
@@ -1046,8 +1054,9 @@ declare module 'bpmn-elements' {
1046
1054
  resume(): number | undefined;
1047
1055
  /**
1048
1056
  * Restore execution state captured by getState. Reinstates running processes from the snapshot.
1057
+ * @param recoveredVersion State version
1049
1058
  * */
1050
- recover(state?: DefinitionExecutionState): this;
1059
+ recover(state?: DefinitionExecutionState, recoveredVersion?: number): this;
1051
1060
  /**
1052
1061
  * Stop the running execution via the api.
1053
1062
  */
@@ -1472,9 +1481,10 @@ declare module 'bpmn-elements' {
1472
1481
  getState(): ProcessState;
1473
1482
  /**
1474
1483
  * Restore process state captured by getState.
1484
+ * @param recoveredVersion State version
1475
1485
  * @throws {Error} when called on a running process
1476
1486
  */
1477
- recover(state?: ProcessState): this;
1487
+ recover(state?: ProcessState, recoveredVersion?: number): this;
1478
1488
  /**
1479
1489
  * Walk activity graph from the given start id, or every start activity when omitted.
1480
1490
  * */
@@ -1660,8 +1670,9 @@ declare module 'bpmn-elements' {
1660
1670
  getState(): ProcessExecutionState;
1661
1671
  /**
1662
1672
  * Restore execution state captured by getState.
1673
+ * @param recoveredVersion State version
1663
1674
  * */
1664
- recover(state?: ProcessExecutionState): this;
1675
+ recover(state?: ProcessExecutionState, recoveredVersion?: number): this;
1665
1676
  /**
1666
1677
  * Walk activity graph from the given start id, or every start activity when omitted.
1667
1678
  * */
@@ -1945,7 +1956,7 @@ declare module 'bpmn-elements' {
1945
1956
  environment: Environment;
1946
1957
  broker: ElementBroker<Activity>;
1947
1958
  get executionId(): string | undefined;
1948
- get cancelActivity(): any;
1959
+ get cancelActivity(): boolean;
1949
1960
 
1950
1961
  execute(executeMessage: ElementBrokerMessage): void;
1951
1962
  }
@@ -2101,29 +2112,48 @@ declare module 'bpmn-elements' {
2101
2112
  type: string;
2102
2113
  activity: Activity;
2103
2114
  broker: ElementBroker<Activity>;
2104
- inbound: Set<any>;
2105
- isConverging: boolean;
2106
- get executionId(): any;
2115
+ /**
2116
+ * Inbound taken sequence flow sequences
2117
+ * */
2118
+ inbound: Set<ElementMessageContent>;
2119
+ get executionId(): string | undefined;
2107
2120
 
2108
2121
  execute(executeMessage: ElementBrokerMessage): void;
2109
- setup(executeMessage: any): number | undefined;
2122
+ /**
2123
+ * Setup peer monitor
2124
+ * */
2125
+ setup(executeMessage: ElementBrokerMessage): void;
2110
2126
  peerMonitor: PeerMonitor | undefined;
2111
2127
  }
2128
+ /**
2129
+ * Peer monitor
2130
+ * @param activity parallel gateway activity
2131
+ * @param targets parallel gateway peer target activities
2132
+ */
2112
2133
  class PeerMonitor {
2113
- constructor(activity: any, targets: any);
2114
- activity: any;
2115
- id: any;
2116
- broker: any;
2134
+ /**
2135
+ * Peer monitor
2136
+ * @param activity parallel gateway activity
2137
+ * @param targets parallel gateway peer target activities
2138
+ */
2139
+ constructor(activity: Activity, targets: Map<string, Activity>);
2140
+ activity: Activity;
2141
+ id: string | undefined;
2142
+ broker: ElementBroker<Activity>;
2117
2143
  running: Map<any, any>;
2118
- index: number;
2119
- discarded: number;
2120
2144
  watching: Map<any, any>;
2121
- targets: any;
2122
- touched: Set<any>;
2145
+ targets: Map<string, Activity>;
2123
2146
  inbound: any[];
2124
2147
  get isRunning(): boolean;
2125
- execute(executeMessage: any): number;
2126
- monitor(peerActivity: any): void;
2148
+ /**
2149
+ * Execute peer monitor
2150
+ * @returns number of running peers
2151
+ */
2152
+ execute(executeMessage: ElementBrokerMessage): number;
2153
+ /**
2154
+ * Monitor peer activity
2155
+ * */
2156
+ monitor(peerActivity: Activity): void;
2127
2157
  stop(): void;
2128
2158
  }
2129
2159
  /**
@@ -505,6 +505,8 @@ export interface DefinitionExecutionState {
505
505
  }
506
506
 
507
507
  export interface DefinitionState extends ElementState {
508
+ /** State version. Absent on states saved before versioning. */
509
+ stateVersion?: number;
508
510
  status: DefinitionStatus;
509
511
  stopped: boolean;
510
512
  executionId?: string;