Skip to content

Commit 0ab64cb

Browse files
committed
remove intermediate skipDiscard flag
1 parent 9be635a commit 0ab64cb

31 files changed

Lines changed: 1166 additions & 1361 deletions

CHANGELOG.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
# Changelog
22

3-
## v18.0.0 - 2026-05-31
3+
## v18.0.0 - 2026-06-11
44

5-
Refactor parallel converging and forking gateways, and treat multiple start events as mutually exclusive entry points.
5+
Refactor parallel converging and forking gateways, and treat multiple start events as mutually exclusive entry points. As a result of the parallel gateway keeping track of peers there is no need for discarding a sequence flows.
66

77
### Breaking
88

9+
- `Definition` must be called with `new`
910
- parallel gateways now enter execution as soon as the first inbound sequence flow is touched
10-
- shake sequence has changed
11+
- removed discarding of outbound sequence flows altogether — activities no longer publish flow discards, so sequence flow and downstream activity `discarded` counters stay at `0`
1112
- IntermediateCatchEvent cannot be used as a starting element, or it can but will not be started by default
12-
- `Definition` must be called with `new`
13-
- 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
13+
- non-gateway activities end the branch when all conditional outbound flows are falsy instead of throwing; only exclusive and inclusive gateways still require a taken or default flow
1414
- 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)
1515
- 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()`
16+
- shake sequence has changed
1717

1818
### Additions
1919

@@ -28,7 +28,7 @@ Refactor parallel converging and forking gateways, and treat multiple start even
2828

2929
### Types
3030

31-
- 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.
31+
- runtime types are now generated from JSDoc and bundled with [dts-buddy](https://github.com/Rich-Harris/dts-buddy)
3232
- expose `isStartEvent` and `isParallelGateway` on the `Activity` interface
3333

3434
## v17.3.0 - 2025-12-03

dist/Environment.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ function Environment(options = {}) {
2929
this.timers = options.timers || new _Timers.Timers();
3030
/** @type {import('#types').EnvironmentSettings} */
3131
this.settings = {
32-
skipDiscard: true,
3332
...options.settings
3433
};
3534
/** @type {import('#types').LoggerFactory} */

dist/activity/Activity.js

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -793,7 +793,6 @@ Activity.prototype._pauseRunQ = function pauseRunQ() {
793793
Activity.prototype._onRunMessage = function onRunMessage(routingKey, message, messageProperties) {
794794
switch (routingKey) {
795795
case 'run.execute.passthrough':
796-
case 'run.outbound.discard':
797796
case 'run.outbound.take':
798797
case 'run.next':
799798
return this._continueRunMessage(routingKey, message, messageProperties);
@@ -932,12 +931,6 @@ Activity.prototype._continueRunMessage = function continueRunMessage(routingKey,
932931
message.ack();
933932
return flow.take(content.flow);
934933
}
935-
case 'run.outbound.discard':
936-
{
937-
const flow = this._getOutboundSequenceFlowById(content.flow.id);
938-
message.ack();
939-
return flow.discard(content.flow);
940-
}
941934
case 'run.leave':
942935
{
943936
this.status = undefined;
@@ -1065,10 +1058,6 @@ Activity.prototype._doOutbound = function doOutbound(fromMessage, isDiscarded, c
10651058
const outboundSequenceFlows = this[K_FLOWS].outboundSequenceFlows;
10661059
if (!outboundSequenceFlows.length) return callback(null, []);
10671060
const fromContent = fromMessage.content;
1068-
let discardSequence = fromContent.discardSequence;
1069-
if (isDiscarded && !discardSequence && this[K_FLAGS].attachedTo && fromContent.inbound?.[0]) {
1070-
discardSequence = [fromContent.inbound[0].id];
1071-
}
10721061
let outboundFlows;
10731062
if (isDiscarded) {
10741063
outboundFlows = outboundSequenceFlows.map(flow => (0, _outboundEvaluator.formatFlowAction)(flow, {
@@ -1078,20 +1067,20 @@ Activity.prototype._doOutbound = function doOutbound(fromMessage, isDiscarded, c
10781067
outboundFlows = outboundSequenceFlows.map(flow => (0, _outboundEvaluator.formatFlowAction)(flow, fromContent.outbound.filter(f => f.id === flow.id).pop()));
10791068
}
10801069
if (outboundFlows) {
1081-
this._doRunOutbound(outboundFlows, fromContent, discardSequence);
1070+
this._doRunOutbound(outboundFlows, fromContent);
10821071
return callback(null, outboundFlows);
10831072
}
10841073
return this.evaluateOutbound(fromMessage, fromContent.outboundTakeOne, (err, evaluatedOutbound) => {
10851074
if (err) return callback(new _Errors.ActivityError(err.message, fromMessage, err));
1086-
const outbound = this._doRunOutbound(evaluatedOutbound, fromContent, discardSequence);
1075+
const outbound = this._doRunOutbound(evaluatedOutbound, fromContent);
10871076
return callback(null, outbound);
10881077
});
10891078
};
10901079

10911080
/** @internal */
1092-
Activity.prototype._doRunOutbound = function doRunOutbound(outboundList, content, discardSequence) {
1081+
Activity.prototype._doRunOutbound = function doRunOutbound(outboundList, content) {
10931082
if (outboundList.length === 1) {
1094-
this._publishRunOutbound(outboundList[0], content, discardSequence);
1083+
this._publishRunOutbound(outboundList[0], content);
10951084
} else {
10961085
const targets = new Map();
10971086
for (const outboundFlow of outboundList) {
@@ -1103,30 +1092,27 @@ Activity.prototype._doRunOutbound = function doRunOutbound(outboundList, content
11031092
}
11041093
}
11051094
for (const outboundFlow of targets.values()) {
1106-
this._publishRunOutbound(outboundFlow, content, discardSequence);
1095+
this._publishRunOutbound(outboundFlow, content);
11071096
}
11081097
}
11091098
return outboundList;
11101099
};
11111100

11121101
/** @internal */
1113-
Activity.prototype._publishRunOutbound = function publishRunOutbound(outboundFlow, content, discardSequence) {
1102+
Activity.prototype._publishRunOutbound = function publishRunOutbound(outboundFlow, content) {
11141103
const {
11151104
id: flowId,
11161105
action,
11171106
result
11181107
} = outboundFlow;
1119-
if (action === 'discard' && this.environment.settings.skipDiscard) {
1108+
if (action === 'discard') {
11201109
return;
11211110
}
11221111
this.broker.publish('run', 'run.outbound.' + action, (0, _messageHelper.cloneContent)(content, {
11231112
flow: {
11241113
...(result && typeof result === 'object' && result),
11251114
...outboundFlow,
1126-
sequenceId: (0, _shared.getUniqueId)(`${flowId}_${action}`),
1127-
...(discardSequence && {
1128-
discardSequence: discardSequence.slice()
1129-
})
1115+
sequenceId: (0, _shared.getUniqueId)(`${flowId}_${action}`)
11301116
}
11311117
}));
11321118
};

docs/Environment.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ Arguments:
1616
- `strict`: boolean, [strict mode](#strict-mode) defaults to false
1717
- `batchSize`: optional positive integer to control parallel loop batch size, defaults to 50
1818
- `disableTrackState`: optional boolean to disable tracking of element counters between recover and resume. State of idle elements are not returned when getting state. Recommended if running and recovering really large flows
19-
- `skipDiscard`: boolean, when true the runtime omits unnecessary discard messages for flows that no converging join is waiting on, defaults to true. Set to false for behaviour parity with older versions that always published discards
2019
- `scripts`: [Scripts instance](/docs/Scripts.md)
2120
- `timers`: [Timers instance](/docs/Timers.md)
2221
- `expressions`: expressions handler, defaults to [Expressions instance](/docs/Expression.md)

docs/SequenceFlow.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,6 @@ An expression condition can resolve to a service function instead of a plain val
6666
If every conditional outbound flow evaluates to falsy, the behaviour depends on the element:
6767
6868
- a diverging **exclusive or inclusive gateway** takes its `default` flow when declared, otherwise it raises an `<id> no conditional flow taken` error — these gateways require exactly one (exclusive) or at least one (inclusive) outbound to be taken;
69-
- any **other activity** (task, event, …) simply discards its outbound — the branch ends, no error is raised.
69+
- any **other activity** (task, event, …) simply ends the branch, no error is raised.
7070
71-
> With the default `skipDiscard` setting the discards are not published, so the outbound flow `discard` counters stay at `0`. Set `skipDiscard` to `false` to observe the discards.
71+
> The runtime no longer publishes outbound flow discards, so outbound flow `discard` counters stay at `0`.

src/Environment.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export function Environment(options = {}) {
2424
/** @type {import('#types').ITimers} */
2525
this.timers = options.timers || new Timers();
2626
/** @type {import('#types').EnvironmentSettings} */
27-
this.settings = { skipDiscard: true, ...options.settings };
27+
this.settings = { ...options.settings };
2828
/** @type {import('#types').LoggerFactory} */
2929
this.Logger = options.Logger || DummyLogger;
3030
this[K_SERVICES] = options.services || {};

src/activity/Activity.js

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,6 @@ Activity.prototype._pauseRunQ = function pauseRunQ() {
744744
Activity.prototype._onRunMessage = function onRunMessage(routingKey, message, messageProperties) {
745745
switch (routingKey) {
746746
case 'run.execute.passthrough':
747-
case 'run.outbound.discard':
748747
case 'run.outbound.take':
749748
case 'run.next':
750749
return this._continueRunMessage(routingKey, message, messageProperties);
@@ -881,11 +880,6 @@ Activity.prototype._continueRunMessage = function continueRunMessage(routingKey,
881880
message.ack();
882881
return flow.take(content.flow);
883882
}
884-
case 'run.outbound.discard': {
885-
const flow = this._getOutboundSequenceFlowById(content.flow.id);
886-
message.ack();
887-
return flow.discard(content.flow);
888-
}
889883
case 'run.leave': {
890884
this.status = undefined;
891885

@@ -994,11 +988,6 @@ Activity.prototype._doOutbound = function doOutbound(fromMessage, isDiscarded, c
994988

995989
const fromContent = fromMessage.content;
996990

997-
let discardSequence = fromContent.discardSequence;
998-
if (isDiscarded && !discardSequence && this[K_FLAGS].attachedTo && fromContent.inbound?.[0]) {
999-
discardSequence = [fromContent.inbound[0].id];
1000-
}
1001-
1002991
let outboundFlows;
1003992
if (isDiscarded) {
1004993
outboundFlows = outboundSequenceFlows.map((flow) => formatFlowAction(flow, { action: 'discard' }));
@@ -1007,21 +996,21 @@ Activity.prototype._doOutbound = function doOutbound(fromMessage, isDiscarded, c
1007996
}
1008997

1009998
if (outboundFlows) {
1010-
this._doRunOutbound(outboundFlows, fromContent, discardSequence);
999+
this._doRunOutbound(outboundFlows, fromContent);
10111000
return callback(null, outboundFlows);
10121001
}
10131002

10141003
return this.evaluateOutbound(fromMessage, fromContent.outboundTakeOne, (err, evaluatedOutbound) => {
10151004
if (err) return callback(new ActivityError(err.message, fromMessage, err));
1016-
const outbound = this._doRunOutbound(evaluatedOutbound, fromContent, discardSequence);
1005+
const outbound = this._doRunOutbound(evaluatedOutbound, fromContent);
10171006
return callback(null, outbound);
10181007
});
10191008
};
10201009

10211010
/** @internal */
1022-
Activity.prototype._doRunOutbound = function doRunOutbound(outboundList, content, discardSequence) {
1011+
Activity.prototype._doRunOutbound = function doRunOutbound(outboundList, content) {
10231012
if (outboundList.length === 1) {
1024-
this._publishRunOutbound(outboundList[0], content, discardSequence);
1013+
this._publishRunOutbound(outboundList[0], content);
10251014
} else {
10261015
const targets = new Map();
10271016

@@ -1035,18 +1024,18 @@ Activity.prototype._doRunOutbound = function doRunOutbound(outboundList, content
10351024
}
10361025

10371026
for (const outboundFlow of targets.values()) {
1038-
this._publishRunOutbound(outboundFlow, content, discardSequence);
1027+
this._publishRunOutbound(outboundFlow, content);
10391028
}
10401029
}
10411030

10421031
return outboundList;
10431032
};
10441033

10451034
/** @internal */
1046-
Activity.prototype._publishRunOutbound = function publishRunOutbound(outboundFlow, content, discardSequence) {
1035+
Activity.prototype._publishRunOutbound = function publishRunOutbound(outboundFlow, content) {
10471036
const { id: flowId, action, result } = outboundFlow;
10481037

1049-
if (action === 'discard' && this.environment.settings.skipDiscard) {
1038+
if (action === 'discard') {
10501039
return;
10511040
}
10521041

@@ -1058,7 +1047,6 @@ Activity.prototype._publishRunOutbound = function publishRunOutbound(outboundFlo
10581047
...(result && typeof result === 'object' && result),
10591048
...outboundFlow,
10601049
sequenceId: getUniqueId(`${flowId}_${action}`),
1061-
...(discardSequence && { discardSequence: discardSequence.slice() }),
10621050
},
10631051
})
10641052
);

test/Environment-test.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { Environment, Timers } from 'bpmn-elements';
22

33
describe('Environment', () => {
44
describe('ctor', () => {
5-
it('sets settings with skipDiscard default', () => {
6-
expect(new Environment()).to.have.property('settings').that.deep.equal({ skipDiscard: true });
5+
it('defaults settings to an empty object', () => {
6+
expect(new Environment()).to.have.property('settings').that.deep.equal({});
77
expect(
88
new Environment({
99
settings: {
@@ -13,12 +13,11 @@ describe('Environment', () => {
1313
)
1414
.to.have.property('settings')
1515
.that.eql({
16-
skipDiscard: true,
1716
test: 1,
1817
});
1918
});
2019

21-
it('shallow clones settings while preserving skipDiscard default', () => {
20+
it('shallow clones settings', () => {
2221
const settings = {
2322
test: 1,
2423
};
@@ -28,7 +27,6 @@ describe('Environment', () => {
2827
settings.test = 2;
2928

3029
expect(environment).to.have.property('settings').that.eql({
31-
skipDiscard: true,
3230
test: 1,
3331
});
3432
});
@@ -268,13 +266,13 @@ describe('Environment', () => {
268266
it('ignored if non-object is passed', () => {
269267
const environment = new Environment({ settings: { before: true } });
270268
environment.assignSettings();
271-
expect(environment.settings).to.eql({ skipDiscard: true, before: true });
269+
expect(environment.settings).to.eql({ before: true });
272270
environment.assignSettings(null);
273-
expect(environment.settings).to.eql({ skipDiscard: true, before: true });
271+
expect(environment.settings).to.eql({ before: true });
274272
environment.assignSettings('null');
275-
expect(environment.settings).to.eql({ skipDiscard: true, before: true });
273+
expect(environment.settings).to.eql({ before: true });
276274
environment.assignSettings(1);
277-
expect(environment.settings).to.eql({ skipDiscard: true, before: true });
275+
expect(environment.settings).to.eql({ before: true });
278276
});
279277
});
280278

test/feature/BoundaryEvent-feature.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,8 @@ Feature('BoundaryEvent', () => {
280280
expect(initTask.counters).to.have.property('taken', 2);
281281
});
282282

283-
And('end was discarded', () => {
284-
expect(bp.getActivityById('end').counters).to.have.property('discarded', bp.environment.settings.skipDiscard ? 0 : 1);
283+
And('end was not discarded', () => {
284+
expect(bp.getActivityById('end').counters).to.have.property('discarded', 0);
285285
expect(bp.getActivityById('end').counters).to.have.property('taken', 0);
286286
});
287287

@@ -302,8 +302,8 @@ Feature('BoundaryEvent', () => {
302302
expect(bound.owner.counters).to.have.property('discarded', 1);
303303
});
304304

305-
And('init task is discarded', () => {
306-
expect(initTask.counters).to.have.property('discarded', bp.environment.settings.skipDiscard ? 0 : 1);
305+
And('init task is not discarded', () => {
306+
expect(initTask.counters).to.have.property('discarded', 0);
307307
expect(initTask.counters).to.have.property('taken', 2);
308308
});
309309

@@ -314,7 +314,7 @@ Feature('BoundaryEvent', () => {
314314

315315
And('end was taken', () => {
316316
expect(bp.getActivityById('end').counters).to.have.property('taken', 1);
317-
expect(bp.getActivityById('end').counters).to.have.property('discarded', bp.environment.settings.skipDiscard ? 0 : 1);
317+
expect(bp.getActivityById('end').counters).to.have.property('discarded', 0);
318318
});
319319

320320
And('process completes', () => {

0 commit comments

Comments
 (0)