bpmn-elements 9.0.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +1 -1
  3. package/dist/EventBroker.js +4 -0
  4. package/dist/Tracker.js +89 -0
  5. package/dist/activity/Activity.js +47 -57
  6. package/dist/activity/ActivityExecution.js +6 -9
  7. package/dist/definition/Definition.js +23 -17
  8. package/dist/definition/DefinitionExecution.js +23 -0
  9. package/dist/eventDefinitions/CancelEventDefinition.js +20 -76
  10. package/dist/eventDefinitions/CompensateEventDefinition.js +54 -41
  11. package/dist/eventDefinitions/ConditionalEventDefinition.js +11 -2
  12. package/dist/eventDefinitions/ErrorEventDefinition.js +2 -0
  13. package/dist/events/BoundaryEvent.js +25 -10
  14. package/dist/flows/Association.js +0 -7
  15. package/dist/gateways/EventBasedGateway.js +1 -2
  16. package/dist/process/Process.js +5 -0
  17. package/dist/process/ProcessExecution.js +128 -36
  18. package/dist/tasks/SubProcess.js +2 -1
  19. package/package.json +4 -4
  20. package/src/EventBroker.js +1 -0
  21. package/src/Tracker.js +73 -0
  22. package/src/activity/Activity.js +45 -51
  23. package/src/activity/ActivityExecution.js +6 -8
  24. package/src/definition/Definition.js +24 -21
  25. package/src/definition/DefinitionExecution.js +26 -0
  26. package/src/eventDefinitions/CancelEventDefinition.js +22 -66
  27. package/src/eventDefinitions/CompensateEventDefinition.js +48 -40
  28. package/src/eventDefinitions/ConditionalEventDefinition.js +12 -2
  29. package/src/eventDefinitions/ErrorEventDefinition.js +2 -0
  30. package/src/events/BoundaryEvent.js +20 -8
  31. package/src/flows/Association.js +0 -10
  32. package/src/gateways/EventBasedGateway.js +1 -2
  33. package/src/process/Process.js +6 -0
  34. package/src/process/ProcessExecution.js +126 -36
  35. package/src/tasks/SubProcess.js +2 -1
  36. package/types/index.d.ts +29 -0
@@ -20,9 +20,10 @@ export default function CompensateEventDefinition(activity, eventDefinition, con
20
20
 
21
21
  if (!isThrowing) {
22
22
  this[kCompleted] = false;
23
- this[kAssociations] = context.getOutboundAssociations(id) || [];
23
+ this[kAssociations] = context.getOutboundAssociations(id);
24
24
  const messageQueueName = `${reference.referenceType}-${brokerSafeId(id)}-q`;
25
25
  this[kMessageQ] = broker.assertQueue(messageQueueName, {autoDelete: false, durable: true});
26
+ this[kCompensateQ] = broker.assertQueue('compensate-q', {autoDelete: false, durable: true});
26
27
  broker.bindQueue(messageQueueName, 'api', `*.${reference.referenceType}.#`, {durable: true, priority: 400});
27
28
  }
28
29
  }
@@ -41,6 +42,11 @@ CompensateEventDefinition.prototype.execute = function execute(executeMessage) {
41
42
  CompensateEventDefinition.prototype.executeCatch = function executeCatch(executeMessage) {
42
43
  this[kExecuteMessage] = executeMessage;
43
44
  this[kCompleted] = false;
45
+ if (executeMessage.fields.routingKey === 'execute.compensating') {
46
+ this._debug('resumed at compensating');
47
+ this[kCompleted] = true;
48
+ return this._compensate();
49
+ }
44
50
 
45
51
  const executeContent = executeMessage.content;
46
52
  const {executionId, parent} = executeContent;
@@ -48,18 +54,13 @@ CompensateEventDefinition.prototype.executeCatch = function executeCatch(execute
48
54
  this._debug('expect compensate');
49
55
 
50
56
  const broker = this.broker;
57
+ broker.cancel('_convey-messages');
51
58
  broker.assertExchange('compensate', 'topic');
52
- this[kCompensateQ] = broker.assertQueue('compensate-q', {durable: true, autoDelete: false});
53
59
  broker.subscribeTmp('compensate', 'execute.#', this._onCollect.bind(this), {
54
60
  noAck: true,
55
61
  consumerTag: '_oncollect-messages',
56
62
  });
57
63
 
58
- broker.publish('execution', 'execute.detach', cloneContent(executeContent, {
59
- sourceExchange: 'execution',
60
- bindExchange: 'compensate',
61
- }));
62
-
63
64
  this[kMessageQ].consume(this._onCompensateApiMessage.bind(this), {
64
65
  noAck: true,
65
66
  consumerTag: `_oncompensate-${executionId}`,
@@ -67,27 +68,24 @@ CompensateEventDefinition.prototype.executeCatch = function executeCatch(execute
67
68
 
68
69
  if (this[kCompleted]) return;
69
70
 
70
- const onApiMessage = this._onApiMessage.bind(this);
71
- broker.subscribeTmp('api', `activity.#.${executionId}`, onApiMessage, {
71
+ broker.subscribeTmp('api', `activity.#.${parent.executionId}#`, this._onApiMessage.bind(this), {
72
72
  noAck: true,
73
73
  consumerTag: `_api-${executionId}`,
74
74
  });
75
75
 
76
- const detachContent = cloneContent(executeContent, {
77
- executionId: parent.executionId,
76
+ broker.publish('execution', 'execute.detach', cloneContent(executeContent, {
77
+ sourceExchange: 'execution',
78
78
  bindExchange: 'compensate',
79
- });
80
- detachContent.parent = shiftParent(parent);
81
-
82
- broker.publish('event', 'activity.detach', detachContent);
79
+ expect: 'compensate',
80
+ }));
83
81
  };
84
82
 
85
83
  CompensateEventDefinition.prototype.executeThrow = function executeThrow(executeMessage) {
86
84
  const executeContent = executeMessage.content;
87
- const {executionId, parent} = executeContent;
85
+ const {parent} = executeContent;
88
86
  const parentExecutionId = parent && parent.executionId;
89
87
 
90
- this.logger.debug(`<${executionId} (${this.activity.id})> throw compensate`);
88
+ this.logger.debug(`<${parentExecutionId} (${this.id})> throw compensate`);
91
89
 
92
90
  const broker = this.broker;
93
91
  const throwContent = cloneContent(executeContent, {
@@ -101,7 +99,6 @@ CompensateEventDefinition.prototype.executeThrow = function executeThrow(execute
101
99
  };
102
100
 
103
101
  CompensateEventDefinition.prototype._onCollect = function onCollect(routingKey, message) {
104
-
105
102
  switch (routingKey) {
106
103
  case 'execute.error':
107
104
  case 'execute.completed': {
@@ -111,67 +108,78 @@ CompensateEventDefinition.prototype._onCollect = function onCollect(routingKey,
111
108
  };
112
109
 
113
110
  CompensateEventDefinition.prototype._onCompensateApiMessage = function onCompensateApiMessage(routingKey, message) {
114
- const output = message.content.message;
115
111
  this[kCompleted] = true;
112
+ const output = message.content.message;
113
+ const broker = this.broker;
114
+ const executeContent = this[kExecuteMessage].content;
116
115
 
117
- this._stop();
116
+ this._stopCollect();
118
117
 
119
118
  this._debug('caught compensate event');
120
- const broker = this.broker;
121
- const executeContent = this[kExecuteMessage].content;
119
+
122
120
  const catchContent = cloneContent(executeContent, {
123
121
  message: {...output},
124
122
  executionId: executeContent.parent.executionId,
125
123
  });
126
124
  catchContent.parent = shiftParent(catchContent.parent);
127
125
 
128
- broker.publish('event', 'activity.catch', catchContent, {type: 'catch'});
126
+ this[kCompensateQ].queueMessage({routingKey: 'execute.compensated'}, cloneContent(executeContent));
129
127
 
130
- const compensateQ = this[kCompensateQ];
131
- compensateQ.on('depleted', onDepleted);
132
- compensateQ.consume(this._onCollected.bind(this), {noAck: true, consumerTag: '_convey-messages'});
128
+ broker.publish('execution', 'execute.compensating', cloneContent(executeContent, {message: {...output}}));
129
+ broker.publish('event', 'activity.catch', catchContent, {type: 'catch'});
133
130
 
134
- for (const association of this[kAssociations]) association.complete(cloneMessage(message));
131
+ return this._compensate();
132
+ };
135
133
 
136
- function onDepleted() {
137
- compensateQ.off('depleted', onDepleted);
138
- return broker.publish('execution', 'execute.completed', cloneContent(executeContent, {output, state: 'catch'}));
139
- }
134
+ CompensateEventDefinition.prototype._compensate = function compensate() {
135
+ return this[kCompensateQ].consume(this._onCollected.bind(this), {noAck: true, consumerTag: '_convey-messages'});
140
136
  };
141
137
 
142
138
  CompensateEventDefinition.prototype._onCollected = function onCollected(routingKey, message) {
139
+ if (routingKey === 'execute.compensated') {
140
+ const broker = this.broker;
141
+ broker.cancel('_convey-messages');
142
+ return this.broker.publish('execution', 'execute.completed', cloneContent(message.content, {cancelActivity: false}));
143
+ }
143
144
  for (const association of this[kAssociations]) association.take(cloneMessage(message));
144
145
  };
145
146
 
147
+ CompensateEventDefinition.prototype._onDiscardApiMessage = function onDiscardApiMessage(routingKey, message) {
148
+ this[kCompleted] = true;
149
+ this._stop();
150
+ this[kCompensateQ].purge();
151
+ for (const association of this[kAssociations]) association.discard(cloneMessage(message));
152
+ return this.broker.publish('execution', 'execute.discard', cloneContent(this[kExecuteMessage].content));
153
+ };
154
+
146
155
  CompensateEventDefinition.prototype._onApiMessage = function onApiMessage(routingKey, message) {
147
156
  const messageType = message.properties.type;
148
-
149
157
  switch (messageType) {
150
158
  case 'compensate': {
151
159
  return this._onCompensateApiMessage(routingKey, message);
152
160
  }
153
161
  case 'discard': {
154
- this[kCompleted] = true;
155
- this._stop();
156
- for (const association of this[kAssociations]) association.discard(cloneMessage(message));
157
- return this.broker.publish('execution', 'execute.discard', cloneContent(this[kExecuteMessage].content));
162
+ return this._onDiscardApiMessage(routingKey, message);
158
163
  }
159
164
  case 'stop': {
160
- this._stop();
161
- break;
165
+ return this._stop();
162
166
  }
163
167
  }
164
168
  };
165
169
 
166
- CompensateEventDefinition.prototype._stop = function stop() {
170
+ CompensateEventDefinition.prototype._stopCollect = function stopCollect() {
167
171
  const broker = this.broker, executionId = this.executionId;
168
172
  broker.cancel(`_api-${executionId}`);
169
173
  broker.cancel(`_oncompensate-${executionId}`);
170
174
  broker.cancel('_oncollect-messages');
171
- broker.cancel('_convey-messages');
172
175
  this[kMessageQ].purge();
173
176
  };
174
177
 
178
+ CompensateEventDefinition.prototype._stop = function stop() {
179
+ this._stopCollect();
180
+ this.broker.cancel('_convey-messages');
181
+ };
182
+
175
183
  CompensateEventDefinition.prototype._debug = function debug(msg) {
176
184
  this.logger.debug(`<${this.executionId} (${this.activity.id})> ${msg}`);
177
185
  };
@@ -59,9 +59,11 @@ ConditionalEventDefinition.prototype.executeWait = function executeWait(executeM
59
59
 
60
60
  ConditionalEventDefinition.prototype.executeCatch = function executeCatch(executeMessage) {
61
61
  const executeContent = executeMessage.content;
62
- const {executionId, index} = executeContent;
62
+ const {executionId, index, parent} = executeContent;
63
+ const parentExecutionId = parent.executionId;
63
64
 
64
- this.broker.subscribeTmp('api', `activity.#.${executionId}`, this._onCatchApiMessage.bind(this), {
65
+ const broker = this.broker;
66
+ broker.subscribeTmp('api', `activity.#.${executionId}`, this._onCatchApiMessage.bind(this), {
65
67
  noAck: true,
66
68
  consumerTag: `_api-${executionId}_${index}`,
67
69
  });
@@ -74,6 +76,14 @@ ConditionalEventDefinition.prototype.executeCatch = function executeCatch(execut
74
76
  priority: 300,
75
77
  consumerTag: `_onend-${executionId}_${index}`,
76
78
  });
79
+
80
+ const waitContent = cloneContent(executeContent, {
81
+ executionId: parentExecutionId,
82
+ condition: this.condition,
83
+ });
84
+ waitContent.parent = shiftParent(parent);
85
+
86
+ broker.publish('event', 'activity.wait', waitContent);
77
87
  };
78
88
 
79
89
  ConditionalEventDefinition.prototype._onWaitApiMessage = function onWaitApiMessage(routingKey, message) {
@@ -79,6 +79,8 @@ ErrorEventDefinition.prototype.executeCatch = function executeCatch(executeMessa
79
79
  consumerTag: `_onerror-${executionId}`,
80
80
  });
81
81
  broker.publish('execution', 'execute.expect', cloneContent(executeContent, {
82
+ pattern: 'activity.error',
83
+ exchange: 'execution',
82
84
  expectRoutingKey,
83
85
  expect: {...info.message},
84
86
  }));
@@ -96,7 +96,7 @@ BoundaryEventBehaviour.prototype._onExecutionMessage = function onExecutionMessa
96
96
  };
97
97
 
98
98
  BoundaryEventBehaviour.prototype._onCompleted = function onCompleted(_, {content}) {
99
- if (!this.cancelActivity && !content.cancelActivity) {
99
+ if (content.cancelActivity === false || !this.cancelActivity && !content.cancelActivity) {
100
100
  this._stop();
101
101
  return this.broker.publish('execution', 'execute.completed', cloneContent(content, {isDefinitionScope: false, cancelActivity: false}));
102
102
  }
@@ -106,6 +106,7 @@ BoundaryEventBehaviour.prototype._onCompleted = function onCompleted(_, {content
106
106
  const inbound = this[kExecuteMessage].content.inbound;
107
107
  const attachedToContent = inbound && inbound[0];
108
108
  const attachedTo = this.attachedTo;
109
+
109
110
  this.activity.logger.debug(`<${this.executionId} (${this.id})> cancel ${attachedTo.status} activity <${attachedToContent.executionId} (${attachedToContent.id})>`);
110
111
 
111
112
  attachedTo.getApi({content: attachedToContent}).discard();
@@ -113,6 +114,7 @@ BoundaryEventBehaviour.prototype._onCompleted = function onCompleted(_, {content
113
114
 
114
115
  BoundaryEventBehaviour.prototype._onAttachedLeave = function onAttachedLeave(_, {content}) {
115
116
  if (content.id !== this.attachedTo.id) return;
117
+
116
118
  this._stop();
117
119
  const completeContent = this[kCompleteContent];
118
120
  if (!completeContent) return this.broker.publish('execution', 'execute.discard', this[kExecuteMessage].content);
@@ -120,24 +122,26 @@ BoundaryEventBehaviour.prototype._onAttachedLeave = function onAttachedLeave(_,
120
122
  };
121
123
 
122
124
  BoundaryEventBehaviour.prototype._onExpectMessage = function onExpectMessage(_, {content}) {
123
- const {executionId, expectRoutingKey} = content;
125
+ const {executionId, expectRoutingKey, pattern, exchange} = content;
124
126
  const attachedTo = this.attachedTo;
125
127
 
126
128
  const errorConsumerTag = `_bound-error-listener-${executionId}`;
127
129
  this[kAttachedTags].push(errorConsumerTag);
128
130
 
129
- attachedTo.broker.subscribeTmp('event', 'activity.error', (__, errorMessage) => {
130
- if (errorMessage.content.id !== attachedTo.id) return;
131
- this.broker.publish('execution', expectRoutingKey, cloneContent(errorMessage.content));
131
+ attachedTo.broker.subscribeTmp('event', pattern, (__, message) => {
132
+ if (message.content.id !== attachedTo.id) return;
133
+ this.broker.publish(exchange, expectRoutingKey, cloneContent(message.content, {attachedTo: attachedTo.id}), {...message.properties, mandatory: false});
132
134
  }, {
133
135
  noAck: true,
134
136
  consumerTag: errorConsumerTag,
135
- priority: 300,
137
+ priority: 400,
136
138
  });
137
139
  };
138
140
 
139
- BoundaryEventBehaviour.prototype._onDetachMessage = function onDetachMessage(_, {content}) {
140
- const id = this.id, executionId = this.executionId, attachedTo = this.attachedTo;
141
+ BoundaryEventBehaviour.prototype._onDetachMessage = function onDetachMessage(_, message) {
142
+ const content = message.content;
143
+ const {executionId, parent} = this[kExecuteMessage].content;
144
+ const id = this.id, attachedTo = this.attachedTo;
141
145
  this.activity.logger.debug(`<${executionId} (${id})> detach from activity <${attachedTo.id}>`);
142
146
  this._stop(true);
143
147
 
@@ -157,6 +161,14 @@ BoundaryEventBehaviour.prototype._onDetachMessage = function onDetachMessage(_,
157
161
  cloneMessage,
158
162
  });
159
163
 
164
+ const detachContent = cloneContent(content, {
165
+ executionId,
166
+ });
167
+ detachContent.parent = parent;
168
+
169
+ this.activity.removeInboundListeners();
170
+ broker.publish('event', 'activity.detach', detachContent);
171
+
160
172
  broker.subscribeOnce('execution', 'execute.bound.completed', (__, {content: completeContent}) => {
161
173
  this._stop();
162
174
  this.broker.publish('execution', 'execute.completed', cloneContent(completeContent));
@@ -20,7 +20,6 @@ export default function Association(associationDef, {environment}) {
20
20
  const logger = this.logger = environment.Logger(type.toLowerCase());
21
21
 
22
22
  this[kCounters] = {
23
- complete: 0,
24
23
  take: 0,
25
24
  discard: 0,
26
25
  };
@@ -59,15 +58,6 @@ Association.prototype.discard = function discard(content = {}) {
59
58
  return true;
60
59
  };
61
60
 
62
- Association.prototype.complete = function complete(content = {}) {
63
- this.logger.debug(`<${this.id}> completed target <${this.targetId}>`);
64
- ++this[kCounters].complete;
65
-
66
- this._publishEvent('complete', content);
67
-
68
- return true;
69
- };
70
-
71
61
  Association.prototype.getState = function getState() {
72
62
  return this._createMessageContent({
73
63
  counters: this.counters,
@@ -40,7 +40,6 @@ EventBasedGatewayBehaviour.prototype.execute = function execute(executeMessage)
40
40
 
41
41
  const broker = this.activity.broker;
42
42
  broker.subscribeOnce('api', `activity.stop.${executionId}`, () => this._stop(), {
43
- noAck: true,
44
43
  consumerTag: '_api-stop-execution',
45
44
  });
46
45
 
@@ -49,7 +48,7 @@ EventBasedGatewayBehaviour.prototype.execute = function execute(executeMessage)
49
48
  };
50
49
 
51
50
  EventBasedGatewayBehaviour.prototype._onTargetCompleted = function onTargetCompleted(executeMessage, _, message, owner) {
52
- const {id: targetId, exexutionId: targetExecutionId} = message.content;
51
+ const {id: targetId, executionId: targetExecutionId} = message.content;
53
52
  const executeContent = executeMessage.content;
54
53
  const executionId = executeContent.executionId;
55
54
  this.activity.logger.debug(`<${executionId} (${this.id})> <${targetExecutionId}> completed run, discarding the rest`);
@@ -107,6 +107,12 @@ Object.defineProperty(Process.prototype, 'status', {
107
107
  },
108
108
  });
109
109
 
110
+ Object.defineProperty(Process.prototype, 'activityStatus', {
111
+ get() {
112
+ return this[kExec].execution && this[kExec].execution.activityStatus || 'idle';
113
+ },
114
+ });
115
+
110
116
  Process.prototype.init = function init(useAsExecutionId) {
111
117
  const exec = this[kExec];
112
118
  const initExecutionId = exec.initExecutionId = useAsExecutionId || getUniqueId(this.id);
@@ -1,6 +1,7 @@
1
1
  import {ProcessApi} from '../Api.js';
2
2
  import {cloneContent, cloneMessage, pushParent} from '../messageHelper.js';
3
3
  import {getUniqueId} from '../shared.js';
4
+ import {ActivityTracker} from '../Tracker.js';
4
5
 
5
6
  export default ProcessExecution;
6
7
 
@@ -13,14 +14,16 @@ const kMessageHandlers = Symbol.for('messageHandlers');
13
14
  const kParent = Symbol.for('parent');
14
15
  const kStatus = Symbol.for('status');
15
16
  const kStopped = Symbol.for('stopped');
17
+ const kTracker = Symbol.for('activity tracker');
16
18
 
17
19
  function ProcessExecution(parentActivity, context) {
18
- const {id, type, broker, isSubProcess} = parentActivity;
20
+ const {id, type, broker, isSubProcess, isTransaction} = parentActivity;
19
21
 
20
22
  this[kParent] = parentActivity;
21
23
  this.id = id;
22
24
  this.type = type;
23
25
  this.isSubProcess = isSubProcess;
26
+ this.isTransaction = isSubProcess && isTransaction;
24
27
  this.broker = broker;
25
28
  this.environment = context.environment;
26
29
  this.context = context;
@@ -44,6 +47,7 @@ function ProcessExecution(parentActivity, context) {
44
47
  this[kStopped] = false;
45
48
  this[kActivated] = false;
46
49
  this[kStatus] = 'init';
50
+ this[kTracker] = new ActivityTracker(id);
47
51
  this.executionId = undefined;
48
52
 
49
53
  this[kMessageHandlers] = {
@@ -87,6 +91,12 @@ Object.defineProperty(ProcessExecution.prototype, 'isRunning', {
87
91
  },
88
92
  });
89
93
 
94
+ Object.defineProperty(ProcessExecution.prototype, 'activityStatus', {
95
+ get() {
96
+ return this[kTracker].activityStatus;
97
+ },
98
+ });
99
+
90
100
  ProcessExecution.prototype.execute = function execute(executeMessage) {
91
101
  if (!executeMessage) throw new Error('Process execution requires message');
92
102
  if (!executeMessage.content || !executeMessage.content.executionId) throw new Error('Process execution requires execution id');
@@ -139,6 +149,7 @@ ProcessExecution.prototype.resume = function resume() {
139
149
  const status = this.status;
140
150
  if (status === 'init') return this._start();
141
151
 
152
+ const tracker = this[kTracker];
142
153
  for (const msg of postponed.slice()) {
143
154
  const activity = this.getActivityById(msg.content.id);
144
155
  if (!activity) continue;
@@ -148,6 +159,8 @@ ProcessExecution.prototype.resume = function resume() {
148
159
  msg.ack();
149
160
  continue;
150
161
  }
162
+
163
+ tracker.track(msg.fields.routingKey, msg);
151
164
  activity.resume();
152
165
  }
153
166
 
@@ -261,6 +274,14 @@ ProcessExecution.prototype.discard = function discard() {
261
274
  }, {type: 'discard'});
262
275
  };
263
276
 
277
+ ProcessExecution.prototype.cancel = function discard() {
278
+ return this[kActivityQ].queueMessage({ routingKey: 'execution.cancel' }, {
279
+ id: this.id,
280
+ type: this.type,
281
+ executionId: this.executionId,
282
+ }, { type: 'cancel' });
283
+ };
284
+
264
285
  ProcessExecution.prototype.getState = function getState() {
265
286
  const {children, flows, outboundMessageFlows, associations} = this[kElements];
266
287
  return {
@@ -291,6 +312,10 @@ ProcessExecution.prototype.getSequenceFlows = function getSequenceFlows() {
291
312
  return this[kElements].flows.slice();
292
313
  };
293
314
 
315
+ ProcessExecution.prototype.getAssociations = function getAssociations() {
316
+ return this[kElements].associations.slice();
317
+ };
318
+
294
319
  ProcessExecution.prototype.getApi = function getApi(message) {
295
320
  if (!message) return ProcessApi(this.broker, this[kExecuteMessage]);
296
321
 
@@ -332,6 +357,7 @@ ProcessExecution.prototype._start = function start() {
332
357
  }
333
358
 
334
359
  for (const a of startActivities) a.init();
360
+ this[kStatus] = 'executing';
335
361
  for (const a of startActivities) a.run();
336
362
 
337
363
  postponed.splice(0);
@@ -478,10 +504,10 @@ ProcessExecution.prototype._onActivityEvent = function onActivityEvent(routingKe
478
504
 
479
505
  if (delegate) delegate = this._onDelegateEvent(message);
480
506
 
507
+ this[kTracker].track(routingKey, message);
481
508
  this.broker.publish('event', routingKey, content, {...message.properties, delegate, mandatory: false});
482
509
  if (shaking) return this._onShookEnd(message);
483
510
  if (!isDirectChild) return;
484
- if (content.isAssociation) return;
485
511
 
486
512
  switch (routingKey) {
487
513
  case 'process.terminate':
@@ -508,6 +534,16 @@ ProcessExecution.prototype._onChildMessage = function onChildMessage(routingKey,
508
534
  case 'execution.discard':
509
535
  message.ack();
510
536
  return this._onDiscard(message);
537
+ case 'execution.discard.detached': {
538
+ message.ack();
539
+ for (const detached of this[kElements].detachedActivities) {
540
+ this._getChildApi(detached).discard();
541
+ }
542
+ return;
543
+ }
544
+ case 'execution.cancel':
545
+ message.ack();
546
+ return this._onCancel(message);
511
547
  case 'activity.error.caught': {
512
548
  const prevMsg = this[kElements].postponed.find((msg) => {
513
549
  return msg.content.executionId === content.executionId;
@@ -515,7 +551,6 @@ ProcessExecution.prototype._onChildMessage = function onChildMessage(routingKey,
515
551
  if (!prevMsg) return message.ack();
516
552
  break;
517
553
  }
518
- case 'activity.compensation.end':
519
554
  case 'flow.looped':
520
555
  case 'activity.leave':
521
556
  return this._onChildCompleted(message);
@@ -528,14 +563,16 @@ ProcessExecution.prototype._onChildMessage = function onChildMessage(routingKey,
528
563
  this[kElements].detachedActivities.push(cloneMessage(message));
529
564
  break;
530
565
  }
566
+ case 'activity.cancel': {
567
+ if (this.isTransaction) this._onCancel(message);
568
+ break;
569
+ }
531
570
  case 'activity.discard':
532
- case 'activity.compensation.start':
533
571
  case 'activity.enter': {
534
- this[kStatus] = 'executing';
535
572
  if (!content.inbound) break;
536
573
 
537
574
  for (const inbound of content.inbound) {
538
- if (!inbound.isSequenceFlow) continue;
575
+ if (!inbound.isSequenceFlow && !inbound.isAssociation) continue;
539
576
  const inboundMessage = this._popPostponed(inbound);
540
577
  if (inboundMessage) inboundMessage.ack();
541
578
  }
@@ -566,7 +603,7 @@ ProcessExecution.prototype._popPostponed = function popPostponed(byContent) {
566
603
  const {postponed, detachedActivities} = this[kElements];
567
604
 
568
605
  const postponedIdx = postponed.findIndex((msg) => {
569
- if (msg.content.isSequenceFlow) return msg.content.sequenceId === byContent.sequenceId;
606
+ if (msg.content.isSequenceFlow || msg.content.isAssociation) return msg.content.sequenceId === byContent.sequenceId;
570
607
  return msg.content.executionId === byContent.executionId;
571
608
  });
572
609
 
@@ -598,9 +635,12 @@ ProcessExecution.prototype._onChildCompleted = function onChildCompleted(message
598
635
 
599
636
  this._debug(`left <${id}> (${type}), pending runs ${postponedCount}, ${postponed.map((a) => a.content.id).join(',')}`);
600
637
 
601
- if (postponedCount === detachedActivities.length) {
602
- for (const api of this.getPostponed()) api.discard();
603
- return;
638
+ if (postponedCount && postponedCount === detachedActivities.length) {
639
+ return this[kActivityQ].queueMessage({ routingKey: 'execution.discard.detached' }, {
640
+ id: this.id,
641
+ type: this.type,
642
+ executionId: this.executionId,
643
+ }, { type: 'cancel' });
604
644
  }
605
645
 
606
646
  if (isEnd && startActivities.length) {
@@ -636,35 +676,47 @@ ProcessExecution.prototype._onDiscard = function onDiscard() {
636
676
  const running = this[kElements].postponed.splice(0);
637
677
  this._debug(`discard process execution (discard child executions ${running.length})`);
638
678
 
639
- for (const flow of this.getSequenceFlows()) flow.stop();
640
- for (const msg of running) this._getChildApi(msg).discard();
679
+ if (this.isSubProcess) {
680
+ this.stop();
681
+ } else {
682
+ for (const flow of this.getSequenceFlows()) flow.stop();
683
+ for (const flow of this.getAssociations()) flow.stop();
684
+ for (const msg of running) this._getChildApi(msg).discard();
685
+ }
641
686
 
642
687
  this[kActivityQ].purge();
643
688
  return this._complete('discard');
644
689
  };
645
690
 
646
- ProcessExecution.prototype._onApiMessage = function onApiMessage(routingKey, message) {
647
- const executionId = this.executionId;
648
- const broker = this.broker;
649
- if (message.properties.delegate) {
650
- const correlationId = message.properties.correlationId || getUniqueId(executionId);
651
- this._debug(`delegate api ${routingKey} message to children, with correlationId <${correlationId}>`);
652
-
653
- let consumed = false;
654
- broker.subscribeTmp('event', 'activity.consumed', (_, msg) => {
655
- if (msg.properties.correlationId === correlationId) {
656
- consumed = true;
657
- this._debug(`delegated api message was consumed by ${msg.content ? msg.content.executionId : 'unknown'}`);
658
- }
659
- }, {consumerTag: `_ct-delegate-${correlationId}`, noAck: true});
691
+ ProcessExecution.prototype._onCancel = function onCancel() {
692
+ const running = this[kElements].postponed.slice(0);
693
+ const isTransaction = this.isTransaction;
660
694
 
661
- for (const child of this[kElements].children) {
662
- if (child.placeholder) continue;
663
- child.broker.publish('api', routingKey, cloneContent(message.content), message.properties);
664
- if (consumed) break;
695
+ if (isTransaction) {
696
+ this._debug(`cancel transaction execution (cancel child executions ${running.length})`);
697
+ this[kStatus] = 'cancel';
698
+ this.broker.publish('event', 'transaction.cancel', cloneMessage(this[kExecuteMessage], {
699
+ state: 'cancel',
700
+ }));
701
+
702
+ for (const msg of running) {
703
+ if (msg.content.expect === 'compensate') {
704
+ this._getChildApi(msg).sendApiMessage('compensate');
705
+ } else if (!msg.content.isForCompensation) {
706
+ this._getChildApi(msg).discard();
707
+ }
665
708
  }
709
+ } else {
710
+ this._debug(`cancel process execution (cancel child executions ${running.length})`);
711
+ for (const msg of running) {
712
+ this._getChildApi(msg).discard();
713
+ }
714
+ }
715
+ };
666
716
 
667
- return broker.cancel(`_ct-delegate-${correlationId}`);
717
+ ProcessExecution.prototype._onApiMessage = function onApiMessage(routingKey, message) {
718
+ if (message.properties.delegate) {
719
+ return this._delegateApiMessage(routingKey, message);
668
720
  }
669
721
 
670
722
  if (this.id !== message.content.id) {
@@ -676,19 +728,56 @@ ProcessExecution.prototype._onApiMessage = function onApiMessage(routingKey, mes
676
728
  if (this.executionId !== message.content.executionId) return;
677
729
 
678
730
  switch (message.properties.type) {
731
+ case 'cancel':
732
+ return this.cancel(message);
679
733
  case 'discard':
680
734
  return this.discard(message);
681
735
  case 'stop':
682
- this[kActivityQ].queueMessage({routingKey: 'execution.stop'}, cloneContent(message.content), {persistent: false});
736
+ this[kActivityQ].queueMessage({ routingKey: 'execution.stop' }, cloneContent(message.content), { persistent: false });
683
737
  break;
684
738
  }
685
739
  };
686
740
 
741
+ ProcessExecution.prototype._delegateApiMessage = function delegateApiMessage(routingKey, message, continueOnConsumed) {
742
+ const correlationId = message.properties.correlationId || getUniqueId(this.executionId);
743
+ this._debug(`delegate api ${routingKey} message to children, with correlationId <${correlationId}>`);
744
+
745
+ const broker = this.broker;
746
+ let consumed = false;
747
+ broker.subscribeTmp('event', 'activity.consumed', (_, msg) => {
748
+ if (msg.properties.correlationId === correlationId) {
749
+ consumed = true;
750
+ this._debug(`delegated api message was consumed by ${msg.content ? msg.content.executionId : 'unknown'}`);
751
+ }
752
+ }, { consumerTag: `_ct-delegate-${correlationId}`, noAck: true });
753
+
754
+ for (const child of this[kElements].children) {
755
+ if (child.placeholder) continue;
756
+ child.broker.publish('api', routingKey, cloneContent(message.content), message.properties);
757
+ if (consumed && !continueOnConsumed) break;
758
+ }
759
+
760
+ return broker.cancel(`_ct-delegate-${correlationId}`);
761
+ };
762
+
687
763
  ProcessExecution.prototype._complete = function complete(completionType, content) {
688
764
  this._deactivate();
689
- this._debug(`process execution ${completionType}`);
690
765
  this[kCompleted] = true;
691
- if (this.status !== 'terminated') this[kStatus] = completionType;
766
+
767
+ const status = this.status;
768
+ switch (this.status) {
769
+ case 'cancel':
770
+ this._debug('process execution cancelled');
771
+ case 'discard':
772
+ completionType = status;
773
+ break;
774
+ case 'terminated':
775
+ break;
776
+ default:
777
+ this._debug(`process execution ${completionType}`);
778
+ this[kStatus] = completionType;
779
+ }
780
+
692
781
  const broker = this.broker;
693
782
  this[kActivityQ].delete();
694
783
 
@@ -705,11 +794,12 @@ ProcessExecution.prototype._terminate = function terminate(message) {
705
794
 
706
795
  const running = this[kElements].postponed.splice(0);
707
796
  for (const flow of this.getSequenceFlows()) flow.stop();
797
+ for (const flow of this.getAssociations()) flow.stop();
708
798
 
709
799
  for (const msg of running) {
710
- const {id: postponedId, isSequenceFlow} = msg.content;
800
+ const {id: postponedId, isSequenceFlow, isAssociation} = msg.content;
711
801
  if (postponedId === message.content.id) continue;
712
- if (isSequenceFlow) continue;
802
+ if (isSequenceFlow || isAssociation) continue;
713
803
  this._getChildApi(msg).stop();
714
804
  msg.ack();
715
805
  }