bpmn-elements 18.0.0 → 18.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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;
@@ -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;
@@ -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.1",
4
4
  "description": "Executable workflow elements based on BPMN 2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
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
 
@@ -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;
@@ -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;
@@ -1046,8 +1048,9 @@ declare module 'bpmn-elements' {
1046
1048
  resume(): number | undefined;
1047
1049
  /**
1048
1050
  * Restore execution state captured by getState. Reinstates running processes from the snapshot.
1051
+ * @param recoveredVersion State version
1049
1052
  * */
1050
- recover(state?: DefinitionExecutionState): this;
1053
+ recover(state?: DefinitionExecutionState, recoveredVersion?: number): this;
1051
1054
  /**
1052
1055
  * Stop the running execution via the api.
1053
1056
  */
@@ -1472,9 +1475,10 @@ declare module 'bpmn-elements' {
1472
1475
  getState(): ProcessState;
1473
1476
  /**
1474
1477
  * Restore process state captured by getState.
1478
+ * @param recoveredVersion State version
1475
1479
  * @throws {Error} when called on a running process
1476
1480
  */
1477
- recover(state?: ProcessState): this;
1481
+ recover(state?: ProcessState, recoveredVersion?: number): this;
1478
1482
  /**
1479
1483
  * Walk activity graph from the given start id, or every start activity when omitted.
1480
1484
  * */
@@ -1660,8 +1664,9 @@ declare module 'bpmn-elements' {
1660
1664
  getState(): ProcessExecutionState;
1661
1665
  /**
1662
1666
  * Restore execution state captured by getState.
1667
+ * @param recoveredVersion State version
1663
1668
  * */
1664
- recover(state?: ProcessExecutionState): this;
1669
+ recover(state?: ProcessExecutionState, recoveredVersion?: number): this;
1665
1670
  /**
1666
1671
  * Walk activity graph from the given start id, or every start activity when omitted.
1667
1672
  * */
@@ -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;