bpmn-elements 18.0.1 → 18.0.3

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
  };
@@ -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}`);
@@ -8,10 +8,12 @@ var _ProcessExecution = require("./ProcessExecution.js");
8
8
  var _shared = require("../shared.js");
9
9
  var _Api = require("../Api.js");
10
10
  var _EventBroker = require("../EventBroker.js");
11
+ var _MessageFormatter = require("../MessageFormatter.js");
11
12
  var _messageHelper = require("../messageHelper.js");
12
13
  var _Errors = require("../error/Errors.js");
13
14
  var _constants = require("../constants.js");
14
15
  const K_LANES = Symbol.for('lanes');
16
+ const K_FORMATTER = Symbol.for('formatter');
15
17
 
16
18
  /**
17
19
  * Owns one `<bpmn:process>`. Wraps the structural definition and orchestrates flow traversal,
@@ -49,12 +51,14 @@ function Process(processDef, context) {
49
51
  broker,
50
52
  on,
51
53
  once,
52
- waitFor
54
+ waitFor,
55
+ emitFatal
53
56
  } = (0, _EventBroker.ProcessBroker)(this);
54
57
  this.broker = broker;
55
58
  this.on = on;
56
59
  this.once = once;
57
60
  this.waitFor = waitFor;
61
+ this.emitFatal = emitFatal;
58
62
  this[_constants.K_MESSAGE_HANDLERS] = {
59
63
  onApiMessage: this._onApiMessage.bind(this),
60
64
  onRunMessage: this._onRunMessage.bind(this),
@@ -84,6 +88,14 @@ Object.defineProperties(Process.prototype, {
84
88
  return this[_constants.K_EXTENSIONS];
85
89
  }
86
90
  },
91
+ formatter: {
92
+ get() {
93
+ let formatter = this[K_FORMATTER];
94
+ if (formatter) return formatter;
95
+ formatter = this[K_FORMATTER] = new _MessageFormatter.Formatter(this);
96
+ return formatter;
97
+ }
98
+ },
87
99
  stopped: {
88
100
  get() {
89
101
  return this[_constants.K_STOPPED];
@@ -292,14 +304,28 @@ Process.prototype._deactivateRunConsumers = function deactivateRunConsumers() {
292
304
  };
293
305
 
294
306
  /** @internal */
295
- Process.prototype._onRunMessage = function onRunMessage(routingKey, message) {
307
+ Process.prototype._onRunMessage = function onRunMessage(routingKey, message, messageProperties) {
308
+ if (routingKey === 'run.resume') {
309
+ return this._onResumeMessage(message);
310
+ }
311
+ const preStatus = this[_constants.K_STATUS];
312
+ this[_constants.K_STATUS] = 'formatting';
313
+ return this.formatter.format(message, (err, formattedContent, formatted) => {
314
+ this[_constants.K_STATUS] = preStatus;
315
+ if (err) {
316
+ return this.emitFatal(err, message.content);
317
+ }
318
+ if (formatted) message.content = formattedContent;
319
+ this._continueRunMessage(routingKey, message, messageProperties);
320
+ });
321
+ };
322
+
323
+ /** @internal */
324
+ Process.prototype._continueRunMessage = function continueRunMessage(routingKey, message) {
296
325
  const {
297
326
  content,
298
327
  fields
299
328
  } = message;
300
- if (routingKey === 'run.resume') {
301
- return this._onResumeMessage(message);
302
- }
303
329
  this[_constants.K_STATE_MESSAGE] = message;
304
330
  switch (routingKey) {
305
331
  case 'run.enter':
@@ -308,6 +334,7 @@ Process.prototype._onRunMessage = function onRunMessage(routingKey, message) {
308
334
  this[_constants.K_STATUS] = 'entered';
309
335
  if (fields.redelivered) break;
310
336
  this[_constants.K_EXECUTION].delete('execution');
337
+ if (this.extensions) this.extensions.activate((0, _messageHelper.cloneMessage)(message));
311
338
  this._publishEvent('enter', content);
312
339
  break;
313
340
  }
@@ -322,6 +349,7 @@ Process.prototype._onRunMessage = function onRunMessage(routingKey, message) {
322
349
  {
323
350
  const exec = this[_constants.K_EXECUTION];
324
351
  this[_constants.K_STATUS] = 'executing';
352
+ if (fields.redelivered && this.extensions) this.extensions.activate((0, _messageHelper.cloneMessage)(message));
325
353
  const executeMessage = (0, _messageHelper.cloneMessage)(message);
326
354
  let execution = exec.get('execution');
327
355
  if (fields.redelivered && !execution) {
@@ -366,6 +394,7 @@ Process.prototype._onRunMessage = function onRunMessage(routingKey, message) {
366
394
  case 'run.leave':
367
395
  {
368
396
  this[_constants.K_STATUS] = undefined;
397
+ if (this.extensions) this.extensions.deactivate((0, _messageHelper.cloneMessage)(message));
369
398
  message.ack();
370
399
  this._deactivateRunConsumers();
371
400
  const {
@@ -394,6 +423,7 @@ Process.prototype._onResumeMessage = function onResumeMessage(message) {
394
423
  return;
395
424
  }
396
425
  if (!stateMessage.fields.redelivered) return;
426
+ if (this.extensions) this.extensions.activate((0, _messageHelper.cloneMessage)(stateMessage));
397
427
  this._debug(`resume from ${this.status}`);
398
428
  return this.broker.publish('run', stateMessage.fields.routingKey, (0, _messageHelper.cloneContent)(stateMessage.content), stateMessage.properties);
399
429
  };
@@ -530,6 +560,7 @@ Process.prototype._onApiMessage = function onApiMessage(routingKey, message) {
530
560
  Process.prototype._onStop = function onStop() {
531
561
  this[_constants.K_STOPPED] = true;
532
562
  this._deactivateRunConsumers();
563
+ if (this.extensions) this.extensions.deactivate((0, _messageHelper.cloneMessage)(this[_constants.K_STATE_MESSAGE]));
533
564
  return this._publishEvent('stop');
534
565
  };
535
566
 
@@ -391,7 +391,7 @@ ProcessExecution.prototype._start = function start() {
391
391
  this._shakeOnStart();
392
392
  for (const a of startActivities) a.init();
393
393
  this[_constants.K_STATUS] = 'executing';
394
- for (const a of startActivities) a.run();
394
+ for (const a of startActivities) a.consumeInbound();
395
395
  if (!startActivities.size) {
396
396
  for (const a of this[K_ELEMENTS].triggeredByEvent) {
397
397
  if (a.isCatching && !a.isRunning) a.run();
@@ -556,7 +556,7 @@ ProcessExecution.prototype._shakeElements = function shakeElements(fromId) {
556
556
  }) => {
557
557
  if (content.parent.id !== this.id) return;
558
558
  switch (routingKey) {
559
- case 'activity.shake.join':
559
+ case 'activity.shake.converge':
560
560
  {
561
561
  const join = convergingGateways.get(content.join);
562
562
  if (!join) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bpmn-elements",
3
- "version": "18.0.1",
3
+ "version": "18.0.3",
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();
@@ -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
 
@@ -2,6 +2,7 @@ import { ProcessExecution } from './ProcessExecution.js';
2
2
  import { getUniqueId } from '../shared.js';
3
3
  import { ProcessApi } from '../Api.js';
4
4
  import { ProcessBroker } from '../EventBroker.js';
5
+ import { Formatter } from '../MessageFormatter.js';
5
6
  import { cloneMessage, cloneContent, cloneParent } from '../messageHelper.js';
6
7
  import { makeErrorFromMessage } from '../error/Errors.js';
7
8
  import {
@@ -17,6 +18,7 @@ import {
17
18
  } from '../constants.js';
18
19
 
19
20
  const K_LANES = Symbol.for('lanes');
21
+ const K_FORMATTER = Symbol.for('formatter');
20
22
 
21
23
  /**
22
24
  * Owns one `<bpmn:process>`. Wraps the structural definition and orchestrates flow traversal,
@@ -47,11 +49,12 @@ export function Process(processDef, context) {
47
49
  this[K_STATUS] = undefined;
48
50
  this[K_STOPPED] = false;
49
51
 
50
- const { broker, on, once, waitFor } = ProcessBroker(this);
52
+ const { broker, on, once, waitFor, emitFatal } = ProcessBroker(this);
51
53
  this.broker = broker;
52
54
  this.on = on;
53
55
  this.once = once;
54
56
  this.waitFor = waitFor;
57
+ this.emitFatal = emitFatal;
55
58
 
56
59
  this[K_MESSAGE_HANDLERS] = {
57
60
  onApiMessage: this._onApiMessage.bind(this),
@@ -83,6 +86,14 @@ Object.defineProperties(Process.prototype, {
83
86
  return this[K_EXTENSIONS];
84
87
  },
85
88
  },
89
+ formatter: {
90
+ get() {
91
+ let formatter = this[K_FORMATTER];
92
+ if (formatter) return formatter;
93
+ formatter = this[K_FORMATTER] = new Formatter(this);
94
+ return formatter;
95
+ },
96
+ },
86
97
  stopped: {
87
98
  get() {
88
99
  return this[K_STOPPED];
@@ -278,13 +289,28 @@ Process.prototype._deactivateRunConsumers = function deactivateRunConsumers() {
278
289
  };
279
290
 
280
291
  /** @internal */
281
- Process.prototype._onRunMessage = function onRunMessage(routingKey, message) {
282
- const { content, fields } = message;
283
-
292
+ Process.prototype._onRunMessage = function onRunMessage(routingKey, message, messageProperties) {
284
293
  if (routingKey === 'run.resume') {
285
294
  return this._onResumeMessage(message);
286
295
  }
287
296
 
297
+ const preStatus = this[K_STATUS];
298
+ this[K_STATUS] = 'formatting';
299
+
300
+ return this.formatter.format(message, (err, formattedContent, formatted) => {
301
+ this[K_STATUS] = preStatus;
302
+ if (err) {
303
+ return this.emitFatal(err, message.content);
304
+ }
305
+ if (formatted) message.content = formattedContent;
306
+ this._continueRunMessage(routingKey, message, messageProperties);
307
+ });
308
+ };
309
+
310
+ /** @internal */
311
+ Process.prototype._continueRunMessage = function continueRunMessage(routingKey, message) {
312
+ const { content, fields } = message;
313
+
288
314
  this[K_STATE_MESSAGE] = message;
289
315
 
290
316
  switch (routingKey) {
@@ -295,6 +321,7 @@ Process.prototype._onRunMessage = function onRunMessage(routingKey, message) {
295
321
  if (fields.redelivered) break;
296
322
 
297
323
  this[K_EXECUTION].delete('execution');
324
+ if (this.extensions) this.extensions.activate(cloneMessage(message));
298
325
  this._publishEvent('enter', content);
299
326
 
300
327
  break;
@@ -308,6 +335,7 @@ Process.prototype._onRunMessage = function onRunMessage(routingKey, message) {
308
335
  case 'run.execute': {
309
336
  const exec = this[K_EXECUTION];
310
337
  this[K_STATUS] = 'executing';
338
+ if (fields.redelivered && this.extensions) this.extensions.activate(cloneMessage(message));
311
339
  const executeMessage = cloneMessage(message);
312
340
  let execution = exec.get('execution');
313
341
  if (fields.redelivered && !execution) {
@@ -360,6 +388,7 @@ Process.prototype._onRunMessage = function onRunMessage(routingKey, message) {
360
388
  }
361
389
  case 'run.leave': {
362
390
  this[K_STATUS] = undefined;
391
+ if (this.extensions) this.extensions.deactivate(cloneMessage(message));
363
392
  message.ack();
364
393
  this._deactivateRunConsumers();
365
394
  const { output, ...rest } = content;
@@ -389,6 +418,8 @@ Process.prototype._onResumeMessage = function onResumeMessage(message) {
389
418
 
390
419
  if (!stateMessage.fields.redelivered) return;
391
420
 
421
+ if (this.extensions) this.extensions.activate(cloneMessage(stateMessage));
422
+
392
423
  this._debug(`resume from ${this.status}`);
393
424
 
394
425
  return this.broker.publish('run', stateMessage.fields.routingKey, cloneContent(stateMessage.content), stateMessage.properties);
@@ -518,6 +549,7 @@ Process.prototype._onApiMessage = function onApiMessage(routingKey, message) {
518
549
  Process.prototype._onStop = function onStop() {
519
550
  this[K_STOPPED] = true;
520
551
  this._deactivateRunConsumers();
552
+ if (this.extensions) this.extensions.deactivate(cloneMessage(this[K_STATE_MESSAGE]));
521
553
  return this._publishEvent('stop');
522
554
  };
523
555
 
@@ -400,7 +400,7 @@ ProcessExecution.prototype._start = function start() {
400
400
 
401
401
  for (const a of startActivities) a.init();
402
402
  this[K_STATUS] = 'executing';
403
- for (const a of startActivities) a.run();
403
+ for (const a of startActivities) a.consumeInbound();
404
404
 
405
405
  if (!startActivities.size) {
406
406
  for (const a of this[K_ELEMENTS].triggeredByEvent) {
@@ -571,7 +571,7 @@ ProcessExecution.prototype._shakeElements = function shakeElements(fromId) {
571
571
  if (content.parent.id !== this.id) return;
572
572
 
573
573
  switch (routingKey) {
574
- case 'activity.shake.join': {
574
+ case 'activity.shake.converge': {
575
575
  const join = convergingGateways.get(content.join);
576
576
  if (!join) {
577
577
  convergingGateways.set(content.join, content);
package/types/index.d.ts CHANGED
@@ -261,6 +261,8 @@ declare module 'bpmn-elements' {
261
261
  export enum ProcessStatusValue {
262
262
  /** ProcessExecution constructed, not yet started */
263
263
  Init = 'init',
264
+ /** Formatting next run message */
265
+ Formatting = 'formatting',
264
266
  /** Process run entered */
265
267
  Entered = 'entered',
266
268
  /** Process run started */
@@ -580,6 +582,11 @@ declare module 'bpmn-elements' {
580
582
  * Subscribe to inbound flows and start consuming the inbound queue.
581
583
  * */
582
584
  activate(): void;
585
+ /**
586
+ * Assert the inbound queue consumer when the activity has a trigger or is initialized.
587
+ * Idempotent: asserting the consumer again while one is active is a no-op.
588
+ * */
589
+ consumeInbound(): void;
583
590
  /**
584
591
  * Cancel inbound subscriptions and any pending run/format consumers.
585
592
  */
@@ -587,8 +594,9 @@ declare module 'bpmn-elements' {
587
594
  /**
588
595
  * Initialise activity executionId and emit init event without starting the run.
589
596
  * @param initContent Optional content merged into the init message
597
+ * @param properties Optional message properties merged into the init message properties
590
598
  */
591
- init(initContent?: Record<string, any>): void;
599
+ init(initContent?: Record<string, any>, properties?: import("smqp").MessageProperties): void;
592
600
  /**
593
601
  * Start running the activity by publishing run.enter and run.start.
594
602
  * @param runContent Optional content merged into the run message
@@ -1452,6 +1460,7 @@ declare module 'bpmn-elements' {
1452
1460
  [x: string]: any;
1453
1461
  }) => import("smqp").Consumer;
1454
1462
  waitFor: (eventName: string, onMessage?: ((routingKey: string, message: ElementBrokerMessage, owner: Process) => boolean) | undefined) => Promise<IApi<Process>>;
1463
+ emitFatal: (error: Error, content?: Record<string, any>) => void;
1455
1464
  logger: ILogger;
1456
1465
  /**
1457
1466
  * Allocate an executionId and emit init event without starting the run.
@@ -1950,7 +1959,7 @@ declare module 'bpmn-elements' {
1950
1959
  environment: Environment;
1951
1960
  broker: ElementBroker<Activity>;
1952
1961
  get executionId(): string | undefined;
1953
- get cancelActivity(): any;
1962
+ get cancelActivity(): boolean;
1954
1963
 
1955
1964
  execute(executeMessage: ElementBrokerMessage): void;
1956
1965
  }
@@ -2106,29 +2115,48 @@ declare module 'bpmn-elements' {
2106
2115
  type: string;
2107
2116
  activity: Activity;
2108
2117
  broker: ElementBroker<Activity>;
2109
- inbound: Set<any>;
2110
- isConverging: boolean;
2111
- get executionId(): any;
2118
+ /**
2119
+ * Inbound taken sequence flow sequences
2120
+ * */
2121
+ inbound: Set<ElementMessageContent>;
2122
+ get executionId(): string | undefined;
2112
2123
 
2113
2124
  execute(executeMessage: ElementBrokerMessage): void;
2114
- setup(executeMessage: any): number | undefined;
2125
+ /**
2126
+ * Setup peer monitor
2127
+ * */
2128
+ setup(executeMessage: ElementBrokerMessage): void;
2115
2129
  peerMonitor: PeerMonitor | undefined;
2116
2130
  }
2131
+ /**
2132
+ * Peer monitor
2133
+ * @param activity parallel gateway activity
2134
+ * @param targets parallel gateway peer target activities
2135
+ */
2117
2136
  class PeerMonitor {
2118
- constructor(activity: any, targets: any);
2119
- activity: any;
2120
- id: any;
2121
- broker: any;
2137
+ /**
2138
+ * Peer monitor
2139
+ * @param activity parallel gateway activity
2140
+ * @param targets parallel gateway peer target activities
2141
+ */
2142
+ constructor(activity: Activity, targets: Map<string, Activity>);
2143
+ activity: Activity;
2144
+ id: string | undefined;
2145
+ broker: ElementBroker<Activity>;
2122
2146
  running: Map<any, any>;
2123
- index: number;
2124
- discarded: number;
2125
2147
  watching: Map<any, any>;
2126
- targets: any;
2127
- touched: Set<any>;
2148
+ targets: Map<string, Activity>;
2128
2149
  inbound: any[];
2129
2150
  get isRunning(): boolean;
2130
- execute(executeMessage: any): number;
2131
- monitor(peerActivity: any): void;
2151
+ /**
2152
+ * Execute peer monitor
2153
+ * @returns number of running peers
2154
+ */
2155
+ execute(executeMessage: ElementBrokerMessage): number;
2156
+ /**
2157
+ * Monitor peer activity
2158
+ * */
2159
+ monitor(peerActivity: Activity): void;
2132
2160
  stop(): void;
2133
2161
  }
2134
2162
  /**
@@ -359,6 +359,8 @@ export type DefinitionStatus = DefinitionStatusValue | `${DefinitionStatusValue}
359
359
  export const enum ProcessStatusValue {
360
360
  /** ProcessExecution constructed, not yet started */
361
361
  Init = 'init',
362
+ /** Formatting next run message */
363
+ Formatting = 'formatting',
362
364
  /** Process run entered */
363
365
  Entered = 'entered',
364
366
  /** Process run started */