Skip to content

Commit 9be635a

Browse files
committed
multiple start events are mutually exclusive entry points
1 parent b86fc21 commit 9be635a

13 files changed

Lines changed: 468 additions & 249 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## v18.0.0 - 2026-05-31
44

5-
Refactor parallel converging and forking gateways.
5+
Refactor parallel converging and forking gateways, and treat multiple start events as mutually exclusive entry points.
66

77
### Breaking
88

@@ -11,11 +11,15 @@ Refactor parallel converging and forking gateways.
1111
- IntermediateCatchEvent cannot be used as a starting element, or it can but will not be started by default
1212
- `Definition` must be called with `new`
1313
- non-gateway activities discard their outbound when all conditional flows are falsy instead of throwing; only exclusive and inclusive gateways still require a taken or default flow
14+
- multiple start events are mutually exclusive entry points — the first start event to fire discards the others still waiting to be triggered, so two start events can no longer both run (e.g. into a parallel join, or a joining task taken twice)
15+
- start activities that are not start events (e.g. a starting receive task, or an activity without an inbound flow) are no longer auto-discarded; they are genuine tokens that must be signalled or completed
16+
- multiple start events no longer trigger a graph shake on run/resume; only converging parallel gateways do, and the shake remains available on demand via `shake()`
1417

1518
### Additions
1619

1720
- expose throwable error classes via new `bpmn-elements/errors` subpath: `import { ActivityError, BpmnError, RunError } from 'bpmn-elements/errors'`
1821
- activity readonly property `isParallelJoin` indicating a parallel converging gateway
22+
- activity readonly property `isStartEvent` indicating a start event
1923
- new activity event `activity.converge` published when parallel gateway is executed
2024
- fix link event definition shaking
2125
- fix `Activity.recover()` to return the activity when called without state
@@ -25,6 +29,7 @@ Refactor parallel converging and forking gateways.
2529
### Types
2630

2731
- runtime types are now generated from JSDoc and bundled with [dts-buddy](https://github.com/Rich-Harris/dts-buddy); status enums (`ActivityStatus`, `DefinitionStatus`, `ProcessStatus`) and `TimerType` accept both enum members and their string literals.
32+
- expose `isStartEvent` and `isParallelGateway` on the `Activity` interface
2833

2934
## v17.3.0 - 2025-12-03
3035

dist/activity/Activity.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ function Activity(Behaviour, activityDef, context) {
107107
isTransaction: activityDef.isTransaction,
108108
isParallelJoin,
109109
isParallelGateway: activityDef.isParallelGateway,
110+
isStartEvent: !!activityDef.isStartEvent,
110111
isThrowing: activityDef.isThrowing,
111112
linkNames: activityDef.linkNames,
112113
linkBehaviour: activityDef.linkBehaviour,
@@ -230,6 +231,11 @@ Object.defineProperties(Activity.prototype, {
230231
return this[K_FLAGS].isParallelGateway;
231232
}
232233
},
234+
isStartEvent: {
235+
get() {
236+
return this[K_FLAGS].isStartEvent;
237+
}
238+
},
233239
triggeredByEvent: {
234240
get() {
235241
return this[K_ACTIVITY_DEF].triggeredByEvent;

dist/events/StartEvent.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ var _constants = require("../constants.js");
1515
* @param {import('#types').ContextInstance} context
1616
*/
1717
function StartEvent(activityDef, context) {
18-
return new _Activity.Activity(StartEventBehaviour, activityDef, context);
18+
return new _Activity.Activity(StartEventBehaviour, {
19+
...activityDef,
20+
isStartEvent: true
21+
}, context);
1922
}
2023

2124
/**

dist/process/ProcessExecution.js

Lines changed: 26 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ function ProcessExecution(parentActivity, context) {
4444
flows: context.getSequenceFlows(id),
4545
outboundMessageFlows: context.getMessageFlows(id),
4646
startActivities: new Set(),
47+
startEventCount: 0,
4748
triggeredByEvent: new Set(),
4849
detachedActivities: new Set(),
49-
convergingGateways: new Set(),
50-
startSequences: new Map()
50+
convergingGateways: new Set()
5151
};
5252
const exchangeName = this._exchangeName = isSubProcess ? 'subprocess-execution' : 'execution';
5353
broker.assertExchange(exchangeName, 'topic', {
@@ -129,8 +129,7 @@ ProcessExecution.prototype.execute = function execute(executeMessage) {
129129
};
130130

131131
/**
132-
* Resume after recover. Reshakes elements when there are converging gateways or multiple
133-
* start activities, then resumes any postponed children.
132+
* Resume after recover, resuming any postponed children.
134133
*/
135134
ProcessExecution.prototype.resume = function resume() {
136135
this._debug(`resume process execution at ${this.status}`);
@@ -426,7 +425,6 @@ ProcessExecution.prototype._activate = function activate() {
426425
flows,
427426
associations,
428427
startActivities,
429-
startSequences,
430428
triggeredByEvent,
431429
convergingGateways,
432430
children
@@ -455,6 +453,7 @@ ProcessExecution.prototype._activate = function activate() {
455453
}
456454
startActivities.clear();
457455
triggeredByEvent.clear();
456+
let startEventCount = 0;
458457
for (const activity of children) {
459458
if (activity.placeholder) continue;
460459
activity.activate(this);
@@ -465,15 +464,12 @@ ProcessExecution.prototype._activate = function activate() {
465464
});
466465
if (activity.isStart) {
467466
startActivities.add(activity);
467+
if (activity.isStartEvent) startEventCount++;
468468
}
469469
if (activity.triggeredByEvent || activity.isCatching) triggeredByEvent.add(activity);
470470
if (activity.isParallelGateway) convergingGateways.add(activity);
471471
}
472-
if (startActivities.size > 1) {
473-
for (const activity of startActivities) {
474-
startSequences.set(activity.id, new Set());
475-
}
476-
}
472+
this[K_ELEMENTS].startEventCount = startEventCount;
477473
this[_constants.K_ACTIVATED] = true;
478474
};
479475

@@ -508,22 +504,16 @@ ProcessExecution.prototype._deactivate = function deactivate() {
508504
};
509505

510506
/**
511-
* Shake on start/resume when there are converging gateways or multiple start activities.
512-
* Reuses already discovered parallel gateway peers to skip the graph shake on repeated runs.
513-
* The shake only discovers parallel gateway peers for the peer monitor; converging joins no
514-
* longer rely on discarded flows, so skipDiscard is left untouched.
507+
* Discover converging parallel gateway peers for the peer monitor, reusing already discovered ones.
515508
* @internal
516509
*/
517510
ProcessExecution.prototype._shakeOnStart = function shakeOnStart() {
518-
const {
519-
startActivities,
520-
convergingGateways
521-
} = this[K_ELEMENTS];
522-
if (startActivities.size <= 1 && this._peersDiscovered()) {
511+
const convergingGateways = this[K_ELEMENTS].convergingGateways;
512+
if (!convergingGateways.size) return;
513+
if (this._peersDiscovered()) {
523514
this._debug(`reuse discovered parallel gateway peers (${convergingGateways.size})`);
524515
return;
525516
}
526-
if (startActivities.size <= 1 && !convergingGateways.size) return;
527517
this._shakeElements();
528518
this._debug(`forced shake to discover converging gateway peers (${convergingGateways.size})`);
529519
};
@@ -535,7 +525,6 @@ ProcessExecution.prototype._shakeOnStart = function shakeOnStart() {
535525
*/
536526
ProcessExecution.prototype._peersDiscovered = function peersDiscovered() {
537527
const convergingGateways = this[K_ELEMENTS].convergingGateways;
538-
if (!convergingGateways.size) return false;
539528
for (const gateway of convergingGateways) {
540529
if (!gateway[K_PEERS_DISCOVERED]) return false;
541530
}
@@ -667,7 +656,7 @@ ProcessExecution.prototype._onActivityEvent = function onActivityEvent(routingKe
667656
delegate,
668657
mandatory: false
669658
});
670-
if (shaking) return this._onShakeMessage(message);
659+
if (shaking) return;
671660
if (!isDirectChild) return;
672661
switch (routingKey) {
673662
case 'process.terminate':
@@ -750,6 +739,20 @@ ProcessExecution.prototype._onChildMessage = function onChildMessage(routingKey,
750739
}
751740
break;
752741
}
742+
case 'activity.end':
743+
{
744+
if (!content.isStartEvent) break;
745+
const elements = this[K_ELEMENTS];
746+
if (elements.startEventCount <= 1) break;
747+
const startPeers = new Set();
748+
for (const msg of elements.postponed) {
749+
const peerId = msg.content.id;
750+
if (peerId !== content.id && msg.content.isStartEvent) startPeers.add(msg);
751+
}
752+
elements.startEventCount = 0;
753+
for (const msg of startPeers) this._getChildApi(msg).discard();
754+
break;
755+
}
753756
case 'activity.error':
754757
{
755758
let eventCaughtBy;
@@ -822,7 +825,6 @@ ProcessExecution.prototype._onChildCompleted = function onChildCompleted(message
822825
const {
823826
id,
824827
type,
825-
isEnd,
826828
isParallelGateway
827829
} = message.content;
828830
if (isParallelGateway) {
@@ -832,8 +834,7 @@ ProcessExecution.prototype._onChildCompleted = function onChildCompleted(message
832834
}
833835
const {
834836
postponed,
835-
detachedActivities,
836-
startActivities
837+
detachedActivities
837838
} = this[K_ELEMENTS];
838839
const postponedCount = postponed.size;
839840
if (!postponedCount) {
@@ -854,17 +855,6 @@ ProcessExecution.prototype._onChildCompleted = function onChildCompleted(message
854855
type: 'cancel'
855856
});
856857
}
857-
if (isEnd && startActivities.size) {
858-
const startSequences = this[K_ELEMENTS].startSequences;
859-
for (const msg of postponed) {
860-
const postponedId = msg.content.id;
861-
const startSequence = startSequences.get(postponedId);
862-
if (!startSequence) continue;
863-
if (startSequence.has(id)) {
864-
this._getChildApi(msg).discard();
865-
}
866-
}
867-
}
868858
};
869859

870860
/** @internal */
@@ -1067,17 +1057,6 @@ ProcessExecution.prototype._getChildApi = function getChildApi(message) {
10671057
}
10681058
};
10691059

1070-
/** @internal */
1071-
ProcessExecution.prototype._onShakeMessage = function onShakeMessage(message) {
1072-
if (message.fields.routingKey !== 'activity.shake.end') return;
1073-
let seq;
1074-
if (!(seq = this[K_ELEMENTS].startSequences.get(message.content.id))) return;
1075-
for (const s of message.content.sequence) {
1076-
if (s.isSequenceFlow) continue;
1077-
seq.add(s.id);
1078-
}
1079-
};
1080-
10811060
/** @internal */
10821061
ProcessExecution.prototype._debug = function debugMessage(logMessage) {
10831062
this[K_PARENT].logger.debug(`<${this.executionId} (${this.id})> ${logMessage}`);

src/activity/Activity.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export function Activity(Behaviour, activityDef, context) {
101101
isTransaction: activityDef.isTransaction,
102102
isParallelJoin,
103103
isParallelGateway: activityDef.isParallelGateway,
104+
isStartEvent: !!activityDef.isStartEvent,
104105
isThrowing: activityDef.isThrowing,
105106
linkNames: activityDef.linkNames,
106107
linkBehaviour: activityDef.linkBehaviour,
@@ -224,6 +225,11 @@ Object.defineProperties(Activity.prototype, {
224225
return this[K_FLAGS].isParallelGateway;
225226
},
226227
},
228+
isStartEvent: {
229+
get() {
230+
return this[K_FLAGS].isStartEvent;
231+
},
232+
},
227233
triggeredByEvent: {
228234
get() {
229235
return this[K_ACTIVITY_DEF].triggeredByEvent;

src/events/StartEvent.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { K_EXECUTE_MESSAGE, K_EXECUTION } from '../constants.js';
99
* @param {import('#types').ContextInstance} context
1010
*/
1111
export function StartEvent(activityDef, context) {
12-
return new Activity(StartEventBehaviour, activityDef, context);
12+
return new Activity(StartEventBehaviour, { ...activityDef, isStartEvent: true }, context);
1313
}
1414

1515
/**

0 commit comments

Comments
 (0)