bpmn-elements 9.2.0 → 10.0.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.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,12 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ # 10.0.0
5
+
6
+ - drop iso8601-duration dependency and copy source (with licence). Export as `ISODuration`. Extend with repeat pattern parsing, e.g. `R3/PT1H` that corresponds to three repetitions every one hour
7
+ - expose `TimerEventDefinition.parse(timerType, value)` function for extension purposes
8
+ - prototype and export built-in `Timers`
9
+
4
10
  # 9.2.0
5
11
 
6
12
  - move outbound sequence flow evaluation logic from activity to sequence flow, where it belongs
package/README.md CHANGED
@@ -77,3 +77,7 @@ The following elements are tested and supported.
77
77
  - Transaction
78
78
 
79
79
  All activities share the same [base](/docs/Activity.md) and and [api](/docs/SharedApi.md).
80
+
81
+ # Acknowledgments
82
+
83
+ ISO 8601 duration parser [iso8601-duration](https://www.npmjs.com/package/iso8601-duration) source is copied and extended with repeat pattern. License [MIT @ tolu](https://tolu.mit-license.org/)
@@ -17,7 +17,7 @@ function Environment(options = {}) {
17
17
  this.extensions = options.extensions;
18
18
  this.output = options.output || {};
19
19
  this.scripts = options.scripts || (0, _Scripts.Scripts)();
20
- this.timers = options.timers || (0, _Timers.Timers)();
20
+ this.timers = options.timers || new _Timers.Timers();
21
21
  this.settings = {
22
22
  ...options.settings
23
23
  };
package/dist/Timers.js CHANGED
@@ -4,57 +4,77 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.Timers = Timers;
7
+ const kExecuting = Symbol.for('executing');
8
+ const kTimerApi = Symbol.for('timers api');
9
+ const MAX_DELAY = 2147483647;
7
10
  function Timers(options) {
8
- let count = 0;
9
- const executing = [];
10
- options = {
11
+ this.count = 0;
12
+ this.options = {
11
13
  setTimeout,
12
14
  clearTimeout,
13
15
  ...options
14
16
  };
15
- const timersApi = {
16
- get executing() {
17
- return executing.slice();
18
- },
19
- register,
20
- setTimeout: wrappedSetTimeout,
21
- clearTimeout: wrappedClearTimeout
22
- };
23
- return timersApi;
24
- function register(owner) {
25
- return {
26
- setTimeout: registerTimeout(owner),
27
- clearTimeout: timersApi.clearTimeout
28
- };
17
+ this[kExecuting] = [];
18
+ this.setTimeout = this.setTimeout.bind(this);
19
+ this.clearTimeout = this.clearTimeout.bind(this);
20
+ }
21
+ Object.defineProperty(Timers.prototype, 'executing', {
22
+ get() {
23
+ return this[kExecuting].slice();
29
24
  }
30
- function registerTimeout(owner) {
31
- return function registeredSetTimeout(...args) {
32
- return timersApi.setTimeout.call(owner, ...args);
33
- };
25
+ });
26
+ Timers.prototype.register = function register(owner) {
27
+ return new RegisteredTimers(this, owner);
28
+ };
29
+ Timers.prototype.setTimeout = function wrappedSetTimeout(callback, delay, ...args) {
30
+ return this._setTimeout(null, callback, delay, ...args);
31
+ };
32
+ Timers.prototype.clearTimeout = function wrappedClearTimeout(ref) {
33
+ const executing = this[kExecuting];
34
+ const idx = executing.indexOf(ref);
35
+ if (idx > -1) {
36
+ executing.splice(idx, 1);
37
+ ref.timerRef = this.options.clearTimeout(ref.timerRef);
38
+ return;
34
39
  }
35
- function wrappedSetTimeout(callback, delay, ...args) {
36
- const ref = {
37
- timerId: `timer_${count++}`,
38
- callback,
39
- delay,
40
- args,
41
- owner: this
42
- };
43
- executing.push(ref);
44
- ref.timerRef = options.setTimeout.call(null, onTimeout, delay, ...args);
45
- return ref;
46
- function onTimeout(...rargs) {
47
- const idx = executing.indexOf(ref);
48
- if (idx > -1) executing.splice(idx, 1);
49
- return callback(...rargs);
50
- }
40
+ return this.options.clearTimeout(ref);
41
+ };
42
+ Timers.prototype._setTimeout = function setTimeout(owner, callback, delay, ...args) {
43
+ const executing = this[kExecuting];
44
+ const ref = this._getReference(owner, callback, delay, args);
45
+ executing.push(ref);
46
+ if (delay < MAX_DELAY) {
47
+ ref.timerRef = this.options.setTimeout(onTimeout, ref.delay, ...ref.args);
51
48
  }
52
- function wrappedClearTimeout(ref) {
49
+ return ref;
50
+ function onTimeout(...rargs) {
53
51
  const idx = executing.indexOf(ref);
54
- if (idx > -1) {
55
- executing.splice(idx, 1);
56
- return options.clearTimeout.call(null, ref.timerRef);
57
- }
58
- return options.clearTimeout.call(null, ref);
52
+ if (idx > -1) executing.splice(idx, 1);
53
+ return callback(...rargs);
59
54
  }
55
+ };
56
+ Timers.prototype._getReference = function getReference(owner, callback, delay, args) {
57
+ return new Timer(owner, `timer_${this.count++}`, callback, delay, args);
58
+ };
59
+ function RegisteredTimers(timersApi, owner) {
60
+ this[kTimerApi] = timersApi;
61
+ this.owner = owner;
62
+ this.setTimeout = this.setTimeout.bind(this);
63
+ this.clearTimeout = this.clearTimeout.bind(this);
64
+ }
65
+ RegisteredTimers.prototype.setTimeout = function registeredSetTimeout(callback, delay, ...args) {
66
+ const timersApi = this[kTimerApi];
67
+ return timersApi._setTimeout(this.owner, callback, delay, ...args);
68
+ };
69
+ RegisteredTimers.prototype.clearTimeout = function registeredClearTimeout(ref) {
70
+ this[kTimerApi].clearTimeout(ref);
71
+ };
72
+ function Timer(owner, timerId, callback, delay, args) {
73
+ this.callback = callback;
74
+ this.delay = delay;
75
+ this.args = args;
76
+ this.owner = owner;
77
+ this.timerId = timerId;
78
+ this.expireAt = new Date(Date.now() + delay);
79
+ this.timerRef = null;
60
80
  }
@@ -5,13 +5,12 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = TimerEventDefinition;
7
7
  var _messageHelper = require("../messageHelper.js");
8
- var _iso8601Duration = require("iso8601-duration");
8
+ var _isoDuration = require("../iso-duration.js");
9
9
  const kStopped = Symbol.for('stopped');
10
10
  const kTimerContent = Symbol.for('timerContent');
11
11
  const kTimer = Symbol.for('timer');
12
- const repeatPattern = /^\s*R(\d+)\//;
13
12
  function TimerEventDefinition(activity, eventDefinition) {
14
- const type = this.type = eventDefinition.type || 'TimerEventDefinition.js';
13
+ const type = this.type = eventDefinition.type || 'TimerEventDefinition';
15
14
  this.activity = activity;
16
15
  const environment = this.environment = activity.environment;
17
16
  this.eventDefinition = eventDefinition;
@@ -85,12 +84,14 @@ TimerEventDefinition.prototype.execute = function execute(executeMessage) {
85
84
  if (timerContent.timeout === undefined) return this._debug(`waiting for ${timerContent.timerType || 'signal'}`);
86
85
  if (timerContent.timeout <= 0) return this._completed();
87
86
  const timers = this.environment.timers.register(timerContent);
88
- this[kTimer] = timers.setTimeout(this._completed.bind(this), timerContent.timeout, {
87
+ const delay = timerContent.timeout;
88
+ this[kTimer] = timers.setTimeout(this._completed.bind(this), delay, {
89
89
  id: content.id,
90
90
  type: this.type,
91
91
  executionId,
92
92
  state: 'timeout'
93
93
  });
94
+ this._debug(`set timeout with delay ${delay}`);
94
95
  };
95
96
  TimerEventDefinition.prototype.stop = function stopTimer() {
96
97
  const timer = this[kTimer];
@@ -188,75 +189,84 @@ TimerEventDefinition.prototype._stop = function stop() {
188
189
  broker.cancel(`_api-${this.executionId}`);
189
190
  broker.cancel(`_api-delegated-${this.executionId}`);
190
191
  };
192
+ TimerEventDefinition.prototype.parse = function parse(timerType, value) {
193
+ let repeat, delay, expireAt;
194
+ switch (timerType) {
195
+ case 'timeCycle':
196
+ case 'timeDuration':
197
+ {
198
+ const parsed = (0, _isoDuration.parse)(value);
199
+ if (parsed.repeat) repeat = parsed.repeat;
200
+ delay = (0, _isoDuration.toSeconds)(parsed) * 1000;
201
+ expireAt = new Date(Date.now() + delay);
202
+ break;
203
+ }
204
+ case 'timeDate':
205
+ {
206
+ const ms = Date.parse(value);
207
+ if (!isNaN(ms)) {
208
+ expireAt = new Date(ms);
209
+ delay = Date.now() - expireAt;
210
+ } else {
211
+ throw new TypeError(`invalid timeDate >${value}<`);
212
+ }
213
+ break;
214
+ }
215
+ }
216
+ return {
217
+ expireAt,
218
+ repeat,
219
+ delay
220
+ };
221
+ };
191
222
  TimerEventDefinition.prototype._getTimers = function getTimers(executeMessage) {
192
223
  const content = executeMessage.content;
193
- const now = Date.now();
194
224
  const result = {
195
225
  ...('expireAt' in content && {
196
226
  expireAt: new Date(content.expireAt)
197
227
  })
198
228
  };
229
+ let parseErr;
199
230
  for (const t of ['timeDuration', 'timeDate', 'timeCycle']) {
200
231
  if (t in content) result[t] = content[t];else if (t in this) result[t] = this.environment.resolveExpression(this[t], executeMessage);else continue;
201
232
  let expireAtDate, repeat;
202
233
  const timerStr = result[t];
203
234
  if (timerStr) {
204
- switch (t) {
205
- case 'timeCycle':
206
- {
207
- const mRepeat = timerStr.match(repeatPattern);
208
- if (mRepeat && mRepeat.length) repeat = parseInt(mRepeat[1]);
209
- }
210
- case 'timeDuration':
211
- {
212
- const delay = this._getDurationInMilliseconds(timerStr);
213
- if (delay !== undefined) expireAtDate = new Date(now + delay);
214
- break;
215
- }
216
- case 'timeDate':
217
- {
218
- const dateStr = result[t];
219
- const ms = Date.parse(dateStr);
220
- if (!isNaN(ms)) {
221
- expireAtDate = new Date(ms);
222
- } else {
223
- this._warn(`invalid timeDate >${dateStr}<`);
224
- }
225
- break;
226
- }
235
+ try {
236
+ const {
237
+ repeat: parsedRepeat,
238
+ expireAt: parsedExpireAt
239
+ } = this.parse(t, timerStr);
240
+ repeat = parsedRepeat;
241
+ expireAtDate = parsedExpireAt;
242
+ } catch (err) {
243
+ parseErr = err;
227
244
  }
228
245
  } else {
229
- expireAtDate = new Date(now);
246
+ expireAtDate = new Date();
230
247
  }
231
248
  if (!expireAtDate) continue;
232
249
  if (!('expireAt' in result) || result.expireAt > expireAtDate) {
233
250
  result.timerType = t;
234
251
  result.expireAt = expireAtDate;
235
- if (repeat) result.repeat = repeat;
252
+ result.repeat = repeat;
236
253
  }
237
254
  }
238
255
  if ('expireAt' in result) {
239
- result.timeout = result.expireAt - now;
256
+ result.timeout = result.expireAt - Date.now();
240
257
  } else if ('timeout' in content) {
241
258
  result.timeout = content.timeout;
242
259
  } else if (!Object.keys(result).length) {
243
260
  result.timeout = 0;
244
261
  }
262
+ if (!('timeout' in result) && parseErr) {
263
+ this.logger.warn(`<${this.activity.id}> failed to parse timer: ${parseErr.message}`);
264
+ }
245
265
  if (content.inbound && 'repeat' in content.inbound[0]) {
246
266
  result.repeat = content.inbound[0].repeat;
247
267
  }
248
268
  return result;
249
269
  };
250
- TimerEventDefinition.prototype._getDurationInMilliseconds = function getDurationInMilliseconds(duration) {
251
- try {
252
- return (0, _iso8601Duration.toSeconds)((0, _iso8601Duration.parse)(duration)) * 1000;
253
- } catch (err) {
254
- this._warn(`failed to parse ${this.timerType} >${duration}<: ${err.message}`);
255
- }
256
- };
257
270
  TimerEventDefinition.prototype._debug = function debug(msg) {
258
271
  this.logger.debug(`<${this.executionId} (${this.activity.id})> ${msg}`);
259
- };
260
- TimerEventDefinition.prototype._warn = function debug(msg) {
261
- this.logger.warn(`<${this.executionId} (${this.activity.id})> ${msg}`);
262
272
  };
package/dist/index.js CHANGED
@@ -147,6 +147,7 @@ Object.defineProperty(exports, "Group", {
147
147
  return _Dummy.default;
148
148
  }
149
149
  });
150
+ exports.ISODuration = void 0;
150
151
  Object.defineProperty(exports, "InclusiveGateway", {
151
152
  enumerable: true,
152
153
  get: function () {
@@ -321,6 +322,12 @@ Object.defineProperty(exports, "TimerEventDefinition", {
321
322
  return _TimerEventDefinition.default;
322
323
  }
323
324
  });
325
+ Object.defineProperty(exports, "Timers", {
326
+ enumerable: true,
327
+ get: function () {
328
+ return _Timers.Timers;
329
+ }
330
+ });
324
331
  Object.defineProperty(exports, "Transaction", {
325
332
  enumerable: true,
326
333
  get: function () {
@@ -381,4 +388,9 @@ var _Task = _interopRequireDefault(require("./tasks/Task.js"));
381
388
  var _TerminateEventDefinition = _interopRequireDefault(require("./eventDefinitions/TerminateEventDefinition.js"));
382
389
  var _TimerEventDefinition = _interopRequireDefault(require("./eventDefinitions/TimerEventDefinition.js"));
383
390
  var _Transaction = _interopRequireDefault(require("./tasks/Transaction.js"));
391
+ var _Timers = require("./Timers.js");
392
+ var ISODuration = _interopRequireWildcard(require("./iso-duration.js"));
393
+ exports.ISODuration = ISODuration;
394
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
395
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
384
396
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.end = end;
7
+ exports.parse = parse;
8
+ exports.toSeconds = toSeconds;
9
+ // License MIT @ https://tolu.mit-license.org/
10
+
11
+ const numbers = '\\d+';
12
+ const fractionalNumbers = ''.concat(numbers, '(?:[\\.,]').concat(numbers, ')?');
13
+ const datePattern = '('.concat(numbers, 'Y)?(').concat(numbers, 'M)?(').concat(numbers, 'W)?(').concat(fractionalNumbers, 'D)?');
14
+ const timePattern = 'T('.concat(fractionalNumbers, 'H)?(').concat(fractionalNumbers, 'M)?(').concat(fractionalNumbers, 'S)?');
15
+ const rPattern = '(?:R('.concat(numbers).concat(')/)?');
16
+ const iso8601 = rPattern.concat('P(?:').concat(datePattern, '(?:').concat(timePattern, ')?)');
17
+ const objMap = ['years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds'];
18
+ const defaultDuration = Object.freeze({
19
+ years: 0,
20
+ months: 0,
21
+ weeks: 0,
22
+ days: 0,
23
+ hours: 0,
24
+ minutes: 0,
25
+ seconds: 0
26
+ });
27
+
28
+ /**
29
+ * The ISO8601 regex for matching / testing durations
30
+ */
31
+ const pattern = new RegExp(iso8601);
32
+
33
+ /** Parse PnYnMnDTnHnMnS format to object */
34
+ function parse(durationString) {
35
+ const matches = durationString.replace(/,/g, '.').match(pattern);
36
+ if (!matches) {
37
+ throw new RangeError('invalid duration: ' + durationString);
38
+ }
39
+
40
+ // Slice away repeat and first entry in match-array (the input string)
41
+ const slicedMatches = matches.slice(2);
42
+ if (slicedMatches.filter(Boolean).length === 0) {
43
+ throw new RangeError('invalid duration: ' + durationString);
44
+ }
45
+ // Check only one fraction is used
46
+ if (slicedMatches.filter(v => {
47
+ return /\./.test(v || '');
48
+ }).length > 1) {
49
+ throw new RangeError('Fractions are allowed on the smallest unit in the string, e.g. P0.5D or PT1.0001S but not PT0.5M0.1S: ' + durationString);
50
+ }
51
+ const result = {};
52
+ if (matches[1]) result.repeat = Number(matches[1]);
53
+ return slicedMatches.reduce((prev, next, idx) => {
54
+ prev[objMap[idx]] = parseFloat(next || '0') || 0;
55
+ return prev;
56
+ }, result);
57
+ }
58
+
59
+ /** Convert ISO8601 duration object to an end Date. */
60
+ function end(durationInput, startDate) {
61
+ const duration = Object.assign({}, defaultDuration, durationInput);
62
+ // Create two equal timestamps, add duration to 'then' and return time difference
63
+ const timestamp = startDate.getTime();
64
+ const then = new Date(timestamp);
65
+ then.setFullYear(then.getFullYear() + duration.years);
66
+ then.setMonth(then.getMonth() + duration.months);
67
+ then.setDate(then.getDate() + duration.days);
68
+ // set time as milliseconds to get fractions working for minutes/hours
69
+ const hoursInMs = duration.hours * 3600 * 1000;
70
+ const minutesInMs = duration.minutes * 60 * 1000;
71
+ then.setMilliseconds(then.getMilliseconds() + duration.seconds * 1000 + hoursInMs + minutesInMs);
72
+ // Special case weeks
73
+ then.setDate(then.getDate() + duration.weeks * 7);
74
+ return then;
75
+ }
76
+
77
+ /** Convert ISO8601 duration object to seconds */
78
+ function toSeconds(durationInput, startDate) {
79
+ if (startDate === void 0) {
80
+ startDate = new Date();
81
+ }
82
+ const duration = Object.assign({}, defaultDuration, durationInput);
83
+ const timestamp = startDate.getTime();
84
+ const now = new Date(timestamp);
85
+ const then = end(duration, now);
86
+ const seconds = (then.getTime() - now.getTime()) / 1000;
87
+ return seconds;
88
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bpmn-elements",
3
- "version": "9.2.0",
3
+ "version": "10.0.0",
4
4
  "description": "Executable workflow elements based on BPMN 2.0",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -45,9 +45,9 @@
45
45
  ],
46
46
  "devDependencies": {
47
47
  "@aircall/expression-parser": "^1.0.4",
48
- "@babel/cli": "^7.21.0",
49
- "@babel/core": "^7.21.4",
50
- "@babel/preset-env": "^7.21.4",
48
+ "@babel/cli": "^7.21.5",
49
+ "@babel/core": "^7.21.8",
50
+ "@babel/preset-env": "^7.21.5",
51
51
  "@babel/register": "^7.21.0",
52
52
  "bpmn-moddle": "^8.0.1",
53
53
  "c8": "^7.13.0",
@@ -55,16 +55,15 @@
55
55
  "chai": "^4.3.7",
56
56
  "chronokinesis": "^5.0.2",
57
57
  "debug": "^4.3.4",
58
- "eslint": "^8.39.0",
58
+ "eslint": "^8.41.0",
59
59
  "eslint-plugin-import": "^2.27.5",
60
60
  "got": "^12.6.0",
61
61
  "mocha": "^10.1.0",
62
62
  "mocha-cakes-2": "^3.3.0",
63
63
  "moddle-context-serializer": "^3.2.2",
64
- "nock": "^13.2.8"
64
+ "nock": "^13.3.1"
65
65
  },
66
66
  "dependencies": {
67
- "iso8601-duration": "^2.1.1",
68
67
  "smqp": "^7.1.4"
69
68
  }
70
69
  }
@@ -24,7 +24,7 @@ export default function Environment(options = {}) {
24
24
  this.extensions = options.extensions;
25
25
  this.output = options.output || {};
26
26
  this.scripts = options.scripts || IScripts();
27
- this.timers = options.timers || Timers();
27
+ this.timers = options.timers || new Timers();
28
28
  this.settings = {...options.settings};
29
29
  this.Logger = options.Logger || DummyLogger;
30
30
  this[kServices] = options.services || {};
package/src/Timers.js CHANGED
@@ -1,56 +1,87 @@
1
- export function Timers(options) {
2
- let count = 0;
3
- const executing = [];
1
+ const kExecuting = Symbol.for('executing');
2
+ const kTimerApi = Symbol.for('timers api');
3
+
4
+ const MAX_DELAY = 2147483647;
4
5
 
5
- options = {
6
+ export function Timers(options) {
7
+ this.count = 0;
8
+ this.options = {
6
9
  setTimeout,
7
10
  clearTimeout,
8
11
  ...options,
9
12
  };
13
+ this[kExecuting] = [];
14
+ this.setTimeout = this.setTimeout.bind(this);
15
+ this.clearTimeout = this.clearTimeout.bind(this);
16
+ }
10
17
 
11
- const timersApi = {
12
- get executing() {
13
- return executing.slice();
14
- },
15
- register,
16
- setTimeout: wrappedSetTimeout,
17
- clearTimeout: wrappedClearTimeout,
18
- };
18
+ Object.defineProperty(Timers.prototype, 'executing', {
19
+ get() {
20
+ return this[kExecuting].slice();
21
+ },
22
+ });
19
23
 
20
- return timersApi;
24
+ Timers.prototype.register = function register(owner) {
25
+ return new RegisteredTimers(this, owner);
26
+ };
21
27
 
22
- function register(owner) {
23
- return {
24
- setTimeout: registerTimeout(owner),
25
- clearTimeout: timersApi.clearTimeout,
26
- };
27
- }
28
+ Timers.prototype.setTimeout = function wrappedSetTimeout(callback, delay, ...args) {
29
+ return this._setTimeout(null, callback, delay, ...args);
30
+ };
28
31
 
29
- function registerTimeout(owner) {
30
- return function registeredSetTimeout(...args) {
31
- return timersApi.setTimeout.call(owner, ...args);
32
- };
32
+ Timers.prototype.clearTimeout = function wrappedClearTimeout(ref) {
33
+ const executing = this[kExecuting];
34
+ const idx = executing.indexOf(ref);
35
+ if (idx > -1) {
36
+ executing.splice(idx, 1);
37
+ ref.timerRef = this.options.clearTimeout(ref.timerRef);
38
+ return;
33
39
  }
40
+ return this.options.clearTimeout(ref);
41
+ };
34
42
 
35
- function wrappedSetTimeout(callback, delay, ...args) {
36
- const ref = {timerId: `timer_${count++}`, callback, delay, args, owner: this};
37
- executing.push(ref);
38
- ref.timerRef = options.setTimeout.call(null, onTimeout, delay, ...args);
39
- return ref;
40
-
41
- function onTimeout(...rargs) {
42
- const idx = executing.indexOf(ref);
43
- if (idx > -1) executing.splice(idx, 1);
44
- return callback(...rargs);
45
- }
43
+ Timers.prototype._setTimeout = function setTimeout(owner, callback, delay, ...args) {
44
+ const executing = this[kExecuting];
45
+ const ref = this._getReference(owner, callback, delay, args);
46
+ executing.push(ref);
47
+ if (delay < MAX_DELAY) {
48
+ ref.timerRef = this.options.setTimeout(onTimeout, ref.delay, ...ref.args);
46
49
  }
50
+ return ref;
47
51
 
48
- function wrappedClearTimeout(ref) {
52
+ function onTimeout(...rargs) {
49
53
  const idx = executing.indexOf(ref);
50
- if (idx > -1) {
51
- executing.splice(idx, 1);
52
- return options.clearTimeout.call(null, ref.timerRef);
53
- }
54
- return options.clearTimeout.call(null, ref);
54
+ if (idx > -1) executing.splice(idx, 1);
55
+ return callback(...rargs);
55
56
  }
57
+ };
58
+
59
+ Timers.prototype._getReference = function getReference(owner, callback, delay, args) {
60
+ return new Timer(owner, `timer_${this.count++}`, callback, delay, args);
61
+ };
62
+
63
+ function RegisteredTimers(timersApi, owner) {
64
+ this[kTimerApi] = timersApi;
65
+ this.owner = owner;
66
+ this.setTimeout = this.setTimeout.bind(this);
67
+ this.clearTimeout = this.clearTimeout.bind(this);
68
+ }
69
+
70
+ RegisteredTimers.prototype.setTimeout = function registeredSetTimeout(callback, delay, ...args) {
71
+ const timersApi = this[kTimerApi];
72
+ return timersApi._setTimeout(this.owner, callback, delay, ...args);
73
+ };
74
+
75
+ RegisteredTimers.prototype.clearTimeout = function registeredClearTimeout(ref) {
76
+ this[kTimerApi].clearTimeout(ref);
77
+ };
78
+
79
+ function Timer(owner, timerId, callback, delay, args) {
80
+ this.callback = callback;
81
+ this.delay = delay;
82
+ this.args = args;
83
+ this.owner = owner;
84
+ this.timerId = timerId;
85
+ this.expireAt = new Date(Date.now() + delay);
86
+ this.timerRef = null;
56
87
  }
@@ -1,13 +1,12 @@
1
1
  import {cloneContent} from '../messageHelper.js';
2
- import {toSeconds, parse} from 'iso8601-duration';
2
+ import {toSeconds, parse as parseIsoDuration} from '../iso-duration.js';
3
3
 
4
4
  const kStopped = Symbol.for('stopped');
5
5
  const kTimerContent = Symbol.for('timerContent');
6
6
  const kTimer = Symbol.for('timer');
7
- const repeatPattern = /^\s*R(\d+)\//;
8
7
 
9
8
  export default function TimerEventDefinition(activity, eventDefinition) {
10
- const type = this.type = eventDefinition.type || 'TimerEventDefinition.js';
9
+ const type = this.type = eventDefinition.type || 'TimerEventDefinition';
11
10
  this.activity = activity;
12
11
  const environment = this.environment = activity.environment;
13
12
  this.eventDefinition = eventDefinition;
@@ -85,12 +84,14 @@ TimerEventDefinition.prototype.execute = function execute(executeMessage) {
85
84
  if (timerContent.timeout <= 0) return this._completed();
86
85
 
87
86
  const timers = this.environment.timers.register(timerContent);
88
- this[kTimer] = timers.setTimeout(this._completed.bind(this), timerContent.timeout, {
87
+ const delay = timerContent.timeout;
88
+ this[kTimer] = timers.setTimeout(this._completed.bind(this), delay, {
89
89
  id: content.id,
90
90
  type: this.type,
91
91
  executionId,
92
92
  state: 'timeout',
93
93
  });
94
+ this._debug(`set timeout with delay ${delay}`);
94
95
  };
95
96
 
96
97
  TimerEventDefinition.prototype.stop = function stopTimer() {
@@ -175,14 +176,44 @@ TimerEventDefinition.prototype._stop = function stop() {
175
176
  broker.cancel(`_api-delegated-${this.executionId}`);
176
177
  };
177
178
 
179
+ TimerEventDefinition.prototype.parse = function parse(timerType, value) {
180
+ let repeat, delay, expireAt;
181
+ switch (timerType) {
182
+ case 'timeCycle':
183
+ case 'timeDuration': {
184
+ const parsed = parseIsoDuration(value);
185
+ if (parsed.repeat) repeat = parsed.repeat;
186
+ delay = toSeconds(parsed) * 1000;
187
+ expireAt = new Date(Date.now() + delay);
188
+ break;
189
+ }
190
+ case 'timeDate': {
191
+ const ms = Date.parse(value);
192
+ if (!isNaN(ms)) {
193
+ expireAt = new Date(ms);
194
+ delay = Date.now() - expireAt;
195
+ } else {
196
+ throw new TypeError(`invalid timeDate >${value}<`);
197
+ }
198
+ break;
199
+ }
200
+ }
201
+
202
+ return {
203
+ expireAt,
204
+ repeat,
205
+ delay,
206
+ };
207
+ };
208
+
178
209
  TimerEventDefinition.prototype._getTimers = function getTimers(executeMessage) {
179
210
  const content = executeMessage.content;
180
211
 
181
- const now = Date.now();
182
212
  const result = {
183
213
  ...('expireAt' in content && {expireAt: new Date(content.expireAt)}),
184
214
  };
185
215
 
216
+ let parseErr;
186
217
  for (const t of ['timeDuration', 'timeDate', 'timeCycle']) {
187
218
  if (t in content) result[t] = content[t];
188
219
  else if (t in this) result[t] = this.environment.resolveExpression(this[t], executeMessage);
@@ -191,47 +222,37 @@ TimerEventDefinition.prototype._getTimers = function getTimers(executeMessage) {
191
222
  let expireAtDate, repeat;
192
223
  const timerStr = result[t];
193
224
  if (timerStr) {
194
- switch (t) {
195
- case 'timeCycle': {
196
- const mRepeat = timerStr.match(repeatPattern);
197
- if (mRepeat && mRepeat.length) repeat = parseInt(mRepeat[1]);
198
- }
199
- case 'timeDuration': {
200
- const delay = this._getDurationInMilliseconds(timerStr);
201
- if (delay !== undefined) expireAtDate = new Date(now + delay);
202
- break;
203
- }
204
- case 'timeDate': {
205
- const dateStr = result[t];
206
- const ms = Date.parse(dateStr);
207
- if (!isNaN(ms)) {
208
- expireAtDate = new Date(ms);
209
- } else {
210
- this._warn(`invalid timeDate >${dateStr}<`);
211
- }
212
- break;
213
- }
225
+ try {
226
+ const {repeat: parsedRepeat, expireAt: parsedExpireAt} = this.parse(t, timerStr);
227
+ repeat = parsedRepeat;
228
+ expireAtDate = parsedExpireAt;
229
+ } catch (err) {
230
+ parseErr = err;
214
231
  }
215
232
  } else {
216
- expireAtDate = new Date(now);
233
+ expireAtDate = new Date();
217
234
  }
218
235
 
219
236
  if (!expireAtDate) continue;
220
237
  if (!('expireAt' in result) || result.expireAt > expireAtDate) {
221
238
  result.timerType = t;
222
239
  result.expireAt = expireAtDate;
223
- if (repeat) result.repeat = repeat;
240
+ result.repeat = repeat;
224
241
  }
225
242
  }
226
243
 
227
244
  if ('expireAt' in result) {
228
- result.timeout = result.expireAt - now;
245
+ result.timeout = result.expireAt - Date.now();
229
246
  } else if ('timeout' in content) {
230
247
  result.timeout = content.timeout;
231
248
  } else if (!Object.keys(result).length) {
232
249
  result.timeout = 0;
233
250
  }
234
251
 
252
+ if (!('timeout' in result) && parseErr) {
253
+ this.logger.warn(`<${this.activity.id}> failed to parse timer: ${parseErr.message}`);
254
+ }
255
+
235
256
  if (content.inbound && 'repeat' in content.inbound[0]) {
236
257
  result.repeat = content.inbound[0].repeat;
237
258
  }
@@ -239,18 +260,6 @@ TimerEventDefinition.prototype._getTimers = function getTimers(executeMessage) {
239
260
  return result;
240
261
  };
241
262
 
242
- TimerEventDefinition.prototype._getDurationInMilliseconds = function getDurationInMilliseconds(duration) {
243
- try {
244
- return toSeconds(parse(duration)) * 1000;
245
- } catch (err) {
246
- this._warn(`failed to parse ${this.timerType} >${duration}<: ${err.message}`);
247
- }
248
- };
249
-
250
263
  TimerEventDefinition.prototype._debug = function debug(msg) {
251
264
  this.logger.debug(`<${this.executionId} (${this.activity.id})> ${msg}`);
252
265
  };
253
-
254
- TimerEventDefinition.prototype._warn = function debug(msg) {
255
- this.logger.warn(`<${this.executionId} (${this.activity.id})> ${msg}`);
256
- };
package/src/index.js CHANGED
@@ -46,6 +46,8 @@ import Task from './tasks/Task.js';
46
46
  import TerminateEventDefinition from './eventDefinitions/TerminateEventDefinition.js';
47
47
  import TimerEventDefinition from './eventDefinitions/TimerEventDefinition.js';
48
48
  import Transaction from './tasks/Transaction.js';
49
+ import {Timers} from './Timers.js';
50
+ import * as ISODuration from './iso-duration.js';
49
51
 
50
52
  export {
51
53
  Association,
@@ -103,4 +105,6 @@ export {
103
105
  TerminateEventDefinition,
104
106
  TimerEventDefinition,
105
107
  Transaction,
108
+ Timers,
109
+ ISODuration,
106
110
  };
@@ -0,0 +1,91 @@
1
+ // License MIT @ https://tolu.mit-license.org/
2
+
3
+ const numbers = '\\d+';
4
+ const fractionalNumbers = ''.concat(numbers, '(?:[\\.,]').concat(numbers, ')?');
5
+ const datePattern = '('.concat(numbers, 'Y)?(').concat(numbers, 'M)?(').concat(numbers, 'W)?(').concat(fractionalNumbers, 'D)?');
6
+ const timePattern = 'T('.concat(fractionalNumbers, 'H)?(').concat(fractionalNumbers, 'M)?(').concat(fractionalNumbers, 'S)?');
7
+
8
+ const rPattern = '(?:R('.concat(numbers).concat(')/)?');
9
+ const iso8601 = rPattern.concat('P(?:').concat(datePattern, '(?:').concat(timePattern, ')?)');
10
+ const objMap = [
11
+ 'years',
12
+ 'months',
13
+ 'weeks',
14
+ 'days',
15
+ 'hours',
16
+ 'minutes',
17
+ 'seconds',
18
+ ];
19
+ const defaultDuration = Object.freeze({
20
+ years: 0,
21
+ months: 0,
22
+ weeks: 0,
23
+ days: 0,
24
+ hours: 0,
25
+ minutes: 0,
26
+ seconds: 0,
27
+ });
28
+
29
+ /**
30
+ * The ISO8601 regex for matching / testing durations
31
+ */
32
+ const pattern = new RegExp(iso8601);
33
+
34
+ /** Parse PnYnMnDTnHnMnS format to object */
35
+ export function parse(durationString) {
36
+ const matches = durationString.replace(/,/g, '.').match(pattern);
37
+ if (!matches) {
38
+ throw new RangeError('invalid duration: ' + durationString);
39
+ }
40
+
41
+ // Slice away repeat and first entry in match-array (the input string)
42
+ const slicedMatches = matches.slice(2);
43
+ if (slicedMatches.filter(Boolean).length === 0) {
44
+ throw new RangeError('invalid duration: ' + durationString);
45
+ }
46
+ // Check only one fraction is used
47
+ if (slicedMatches.filter((v) => {
48
+ return /\./.test(v || '');
49
+ }).length > 1) {
50
+ throw new RangeError('Fractions are allowed on the smallest unit in the string, e.g. P0.5D or PT1.0001S but not PT0.5M0.1S: ' + durationString);
51
+ }
52
+
53
+ const result = {};
54
+ if (matches[1]) result.repeat = Number(matches[1]);
55
+
56
+ return slicedMatches.reduce((prev, next, idx) => {
57
+ prev[objMap[idx]] = parseFloat(next || '0') || 0;
58
+ return prev;
59
+ }, result);
60
+ }
61
+
62
+ /** Convert ISO8601 duration object to an end Date. */
63
+ export function end(durationInput, startDate) {
64
+ const duration = Object.assign({}, defaultDuration, durationInput);
65
+ // Create two equal timestamps, add duration to 'then' and return time difference
66
+ const timestamp = startDate.getTime();
67
+ const then = new Date(timestamp);
68
+ then.setFullYear(then.getFullYear() + duration.years);
69
+ then.setMonth(then.getMonth() + duration.months);
70
+ then.setDate(then.getDate() + duration.days);
71
+ // set time as milliseconds to get fractions working for minutes/hours
72
+ const hoursInMs = duration.hours * 3600 * 1000;
73
+ const minutesInMs = duration.minutes * 60 * 1000;
74
+ then.setMilliseconds(then.getMilliseconds() + duration.seconds * 1000 + hoursInMs + minutesInMs);
75
+ // Special case weeks
76
+ then.setDate(then.getDate() + duration.weeks * 7);
77
+ return then;
78
+ }
79
+
80
+ /** Convert ISO8601 duration object to seconds */
81
+ export function toSeconds(durationInput, startDate) {
82
+ if (startDate === void 0) {
83
+ startDate = new Date();
84
+ }
85
+ const duration = Object.assign({}, defaultDuration, durationInput);
86
+ const timestamp = startDate.getTime();
87
+ const now = new Date(timestamp);
88
+ const then = end(duration, now);
89
+ const seconds = (then.getTime() - now.getTime()) / 1000;
90
+ return seconds;
91
+ }
package/types/index.d.ts CHANGED
@@ -37,7 +37,8 @@ declare module 'bpmn-elements' {
37
37
  content: ElementMessageContent,
38
38
  }
39
39
 
40
- interface EventDefinition {
40
+ class EventDefinition {
41
+ constructor(activity: Activity, eventDefinitionElement: SerializableElement)
41
42
  get id(): string;
42
43
  get type(): string;
43
44
  get executionId(): string;
@@ -521,15 +522,53 @@ declare module 'bpmn-elements' {
521
522
  [x: string]: any,
522
523
  }
523
524
 
524
- type wrappedSetTimeout = (handler: CallableFunction, timeout: number, ...args: unknown[]) => any;
525
- type wrappedClearTimeout = (id?: any) => void;
525
+ type wrappedSetTimeout = (handler: TimerHandler, delay: number, ...args: any[]) => Timer;
526
+ type wrappedClearTimeout = (ref: any) => void;
527
+
528
+ interface Timer {
529
+ /** The function to call when the timer elapses */
530
+ readonly callback: TimerHandler;
531
+ /** The number of milliseconds to wait before calling the callback */
532
+ readonly delay: number;
533
+ /** Optional arguments to pass when the callback is called */
534
+ readonly args?: any[];
535
+ /** Timer owner if any */
536
+ readonly owner?: any;
537
+ /** Timer Id */
538
+ readonly timerId: string;
539
+ /** Timeout, return from setTimeout */
540
+ readonly timerRef: any;
541
+ [x: string]: any;
542
+ }
543
+
544
+ interface RegisteredTimer {
545
+ owner?: any;
546
+ get setTimeout(): wrappedSetTimeout;
547
+ get clearTimeout(): wrappedClearTimeout;
548
+ }
526
549
 
527
550
  interface ITimers {
528
- get executing(): any[];
529
551
  get setTimeout(): wrappedSetTimeout;
530
552
  get clearTimeout(): wrappedClearTimeout;
531
- register(owner?: any): { setTimeout: wrappedSetTimeout, clearTimeout: wrappedClearTimeout };
532
- [x: string]: any,
553
+ register(owner?: any): RegisteredTimer;
554
+ [x: string]: any;
555
+ }
556
+
557
+ interface TimersOptions {
558
+ /** Defaults to builtin setTimeout */
559
+ setTimeout?: typeof setTimeout;
560
+ /** Defaults to builtin clearTimeout */
561
+ clearTimeout?: typeof clearTimeout;
562
+ [x: string]: any;
563
+ }
564
+
565
+ class Timers implements ITimers {
566
+ options: TimersOptions;
567
+ constructor(options?: TimersOptions);
568
+ get executing(): Timer[];
569
+ get setTimeout(): wrappedSetTimeout;
570
+ get clearTimeout(): wrappedClearTimeout;
571
+ register(owner?: any): RegisteredTimer;
533
572
  }
534
573
 
535
574
  interface IScripts {
@@ -602,7 +641,30 @@ declare module 'bpmn-elements' {
602
641
  var MessageEventDefinition: EventDefinition;
603
642
  var SignalEventDefinition: EventDefinition;
604
643
  var TerminateEventDefinition: EventDefinition;
605
- var TimerEventDefinition: EventDefinition;
644
+
645
+ const enum TimerType {
646
+ TimeCycle = 'timeCycle',
647
+ TimeDuration = 'timeDuration',
648
+ TimeDate = 'timeDate',
649
+ }
650
+
651
+ type parsedTimer = {
652
+ /** Expires at date time */
653
+ expireAt?: Date,
654
+ /** Repeat number of times */
655
+ repeat?: number,
656
+ /** Delay in milliseconds */
657
+ delay?: number,
658
+ };
659
+
660
+ class TimerEventDefinition extends EventDefinition {
661
+ /**
662
+ * Parse timer type
663
+ * @param timerType type of timer
664
+ * @param timerValue resolved expression timer string
665
+ */
666
+ parse(timerType: TimerType, timerValue: string): parsedTimer;
667
+ }
606
668
 
607
669
  class BpmnError {
608
670
  get id(): string;
@@ -641,6 +703,28 @@ declare module 'bpmn-elements' {
641
703
  code?: string;
642
704
  constructor(description: string, sourceMessage: MessageMessage, inner?: Error);
643
705
  }
706
+
707
+ interface Duration {
708
+ years?: number;
709
+ months?: number;
710
+ weeks?: number;
711
+ days?: number;
712
+ hours?: number;
713
+ minutes?: number;
714
+ seconds?: number;
715
+ repeat?: number;
716
+ }
717
+
718
+ type ISODurationApi = {
719
+ /** Parse PnYnMnDTnHnMnS format to object */
720
+ parse: (durationString: string) => Duration,
721
+ /** Convert ISO8601 duration object to an end Date. */
722
+ end: (durationInput: Duration, startDate?: Date) => Date,
723
+ /** Convert ISO8601 duration object to seconds */
724
+ toSeconds: (durationInput: Duration, startDate?: Date) => number,
725
+ }
726
+
727
+ const ISODuration: ISODurationApi;
644
728
  }
645
729
 
646
730
  /**