@onify/flow-extensions 2.0.0 → 3.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 ADDED
@@ -0,0 +1,31 @@
1
+ Changelog
2
+ =========
3
+
4
+ # 3.0.0
5
+
6
+ - Support activity script and expression execution listeners
7
+ - Remove default behaviour of saving activity output even if output is not declared. Output **was** saved to `environment.output._<Activity ID>` if no result variable or output was declared. The reason was that there was no easy way of getting output from SubProcess or a multi-instance task. Now SubProcess output can be defined and multi-instance output can be solved with execution listener
8
+
9
+ # 2.0.0
10
+
11
+ - fix resume extension when recovered mid formatting
12
+ - drop node 12 support
13
+
14
+ # 1.1.0
15
+
16
+ - allow ISO8601 repeating interval and duration in timeCycle
17
+
18
+ # 1.0.0
19
+
20
+ - proper routing keys for end formatting
21
+
22
+ # 0.1.0
23
+
24
+ - catch errors when resolving service expression
25
+ - test with bpmn-engine@14
26
+
27
+ # 0.0.2
28
+
29
+ - allow expressions in timeCycle
30
+ - test with bpmn-engine@13
31
+ - error message changed in node 16
package/dist/index.js CHANGED
@@ -6,11 +6,12 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.extendFn = extendFn;
7
7
  exports.extensions = extensions;
8
8
  var _cronParser = _interopRequireDefault(require("cron-parser"));
9
- var _IOProperties = _interopRequireDefault(require("./src/IOProperties"));
10
- var _IOForm = _interopRequireDefault(require("./src/IOForm"));
11
- var _Connector = _interopRequireDefault(require("./src/Connector"));
12
- var _ServiceExpression = _interopRequireDefault(require("./src/ServiceExpression"));
13
- var _IO = require("./src/IO");
9
+ var _IOProperties = _interopRequireDefault(require("./src/IOProperties.js"));
10
+ var _IOForm = _interopRequireDefault(require("./src/IOForm.js"));
11
+ var _Connector = _interopRequireDefault(require("./src/Connector.js"));
12
+ var _ServiceExpression = _interopRequireDefault(require("./src/ServiceExpression.js"));
13
+ var _IO = require("./src/IO.js");
14
+ var _ExecutionListeners = _interopRequireDefault(require("./src/ExecutionListeners.js"));
14
15
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
16
  const iso8601cycle = /^\s*(R\d+\/)?P\w+/i;
16
17
  class FormatError extends Error {
@@ -121,11 +122,68 @@ class OnifyElementExtensions {
121
122
  }, {
122
123
  consumerTag: '_onify-extension-on-executed'
123
124
  });
125
+ const executionListeners = this.extensions.listeners;
126
+ if (executionListeners !== null && executionListeners !== void 0 && executionListeners.onStart) {
127
+ activity.on('start', async elementApi => {
128
+ formatQ.queueMessage({
129
+ routingKey: 'run.listener.start'
130
+ }, {
131
+ endRoutingKey: 'run.listener.start.complete'
132
+ }, {
133
+ persistent: false
134
+ });
135
+ try {
136
+ var format = await executionListeners.execute('start', elementApi);
137
+ } catch (err) {
138
+ return broker.publish('format', 'run.listener.start.error', {
139
+ error: err
140
+ }, {
141
+ persistent: false
142
+ });
143
+ }
144
+ broker.publish('format', 'run.listener.start.complete', {
145
+ ...format
146
+ }, {
147
+ persistent: false
148
+ });
149
+ }, {
150
+ consumerTag: '_onify-extension-on-listenerstart'
151
+ });
152
+ }
153
+ if (executionListeners !== null && executionListeners !== void 0 && executionListeners.onEnd) {
154
+ activity.on('end', async elementApi => {
155
+ formatQ.queueMessage({
156
+ routingKey: 'run.listener.end'
157
+ }, {
158
+ endRoutingKey: 'run.listener.end.complete'
159
+ }, {
160
+ persistent: false
161
+ });
162
+ try {
163
+ var format = await executionListeners.execute('end', elementApi);
164
+ } catch (err) {
165
+ return broker.publish('format', 'run.listener.end.error', {
166
+ error: err
167
+ }, {
168
+ persistent: false
169
+ });
170
+ }
171
+ broker.publish('format', 'run.listener.end.complete', {
172
+ ...format
173
+ }, {
174
+ persistent: false
175
+ });
176
+ }, {
177
+ consumerTag: '_onify-extension-on-listenerend'
178
+ });
179
+ }
124
180
  }
125
181
  deactivate() {
126
182
  const broker = this.activity.broker;
127
183
  broker.cancel('_onify-extension-on-enter');
128
184
  broker.cancel('_onify-extension-on-executed');
185
+ broker.cancel('_onify-extension-on-listenerstart');
186
+ broker.cancel('_onify-extension-on-listenerend');
129
187
  }
130
188
  async _formatOnEnter(broker, formatQ, elementApi) {
131
189
  formatQ.queueMessage({
@@ -165,14 +223,15 @@ class OnifyElementExtensions {
165
223
  Object.assign(elementApi.content, result);
166
224
  }
167
225
  if (io) {
168
- if (io.input?.length) {
226
+ var _io$input, _io$output;
227
+ if ((_io$input = io.input) !== null && _io$input !== void 0 && _io$input.length) {
169
228
  const input = await io.getInput(this.activity, elementApi);
170
229
  Object.assign(result, {
171
230
  input
172
231
  });
173
232
  Object.assign(elementApi.content, result);
174
233
  }
175
- if (io.output?.length) {
234
+ if ((_io$output = io.output) !== null && _io$output !== void 0 && _io$output.length) {
176
235
  result.assignToOutput = true;
177
236
  }
178
237
  }
@@ -189,7 +248,7 @@ class OnifyElementExtensions {
189
248
  io
190
249
  } = this.extensions;
191
250
  const result = {};
192
- if (io?.output.length) {
251
+ if (io !== null && io !== void 0 && io.output.length) {
193
252
  const output = await io.getOutput(this.activity, elementApi);
194
253
  Object.assign(result, {
195
254
  output
@@ -222,23 +281,26 @@ class OnifyElementExtensions {
222
281
  }
223
282
  }
224
283
  function getExtensions(element, context) {
284
+ var _element$behaviour$ex, _ext$values, _ext$fields;
225
285
  const result = {
226
286
  format: element.type === 'bpmn:Process' ? new FormatProcess(element) : new FormatActivity(element)
227
287
  };
228
288
  const expression = element.behaviour.expression;
229
289
  if (expression) result.Service = _ServiceExpression.default;
230
- const extensions = element.behaviour.extensionElements?.values;
290
+ const extensions = (_element$behaviour$ex = element.behaviour.extensionElements) === null || _element$behaviour$ex === void 0 ? void 0 : _element$behaviour$ex.values;
231
291
  if (!extensions) return result;
292
+ const listeners = new _ExecutionListeners.default(element, context);
293
+ let listener = 0;
232
294
  for (const ext of extensions) {
233
295
  switch (ext.$type) {
234
296
  case 'camunda:Properties':
235
- if (ext.values?.length) result.properties = new _IOProperties.default(element, ext);
297
+ if ((_ext$values = ext.values) !== null && _ext$values !== void 0 && _ext$values.length) result.properties = new _IOProperties.default(element, ext);
236
298
  break;
237
299
  case 'camunda:InputOutput':
238
300
  result.io = new _IO.InputOutput(element.id, ext, context);
239
301
  break;
240
302
  case 'camunda:FormData':
241
- if (ext.fields?.length) result.form = new _IOForm.default(element, ext);
303
+ if ((_ext$fields = ext.fields) !== null && _ext$fields !== void 0 && _ext$fields.length) result.form = new _IOForm.default(element, ext);
242
304
  break;
243
305
  case 'camunda:Connector':
244
306
  {
@@ -250,14 +312,18 @@ function getExtensions(element, context) {
250
312
  result.Service = _Connector.default.bind(_Connector.default, connectorId, io);
251
313
  break;
252
314
  }
315
+ case 'camunda:ExecutionListener':
316
+ listeners.add(ext, listener++);
317
+ break;
253
318
  }
254
319
  }
320
+ if (listeners.length) result.listeners = listeners;
255
321
  return result;
256
322
  }
257
323
  class FormatActivity {
258
324
  constructor(activity) {
259
325
  this.activity = activity;
260
- this.resultVariable = activity.behaviour.resultVariable || '_' + activity.id;
326
+ this.resultVariable = activity.behaviour.resultVariable;
261
327
  let timeCycles;
262
328
  if (activity.eventDefinitions) {
263
329
  for (const ed of activity.eventDefinitions.filter(e => e.type === 'bpmn:TimerEventDefinition')) {
@@ -269,6 +335,7 @@ class FormatActivity {
269
335
  this.timeCycles = timeCycles;
270
336
  }
271
337
  resolve(elementApi) {
338
+ var _documentation$, _user, _groups;
272
339
  let user, groups, assigneeValue, description;
273
340
  const activity = this.activity;
274
341
  const {
@@ -281,7 +348,7 @@ class FormatActivity {
281
348
  if (candidateUsers) user = resolveAndSplit(elementApi, candidateUsers);
282
349
  if (candidateGroups) groups = resolveAndSplit(elementApi, candidateGroups);
283
350
  if (assignee) assigneeValue = elementApi.resolveExpression(assignee);
284
- if (documentation) description = documentation[0]?.text;
351
+ if (documentation) description = (_documentation$ = documentation[0]) === null || _documentation$ === void 0 ? void 0 : _documentation$.text;
285
352
  let expireAt;
286
353
  let timeCycles = this.timeCycles;
287
354
  if (timeCycles) {
@@ -293,14 +360,16 @@ class FormatActivity {
293
360
  }
294
361
  }
295
362
  return {
296
- resultVariable: this.resultVariable,
363
+ ...(this.resultVariable && {
364
+ resultVariable: this.resultVariable
365
+ }),
297
366
  ...(scheduledStart && activity.parent.type === 'bpmn:Process' && {
298
367
  scheduledStart
299
368
  }),
300
- ...(user?.length && {
369
+ ...(((_user = user) === null || _user === void 0 ? void 0 : _user.length) && {
301
370
  candidateUsers: user
302
371
  }),
303
- ...(groups?.length && {
372
+ ...(((_groups = groups) === null || _groups === void 0 ? void 0 : _groups.length) && {
304
373
  candidateGroups: groups
305
374
  }),
306
375
  ...(!elementApi.content.description && description && {
@@ -320,6 +389,7 @@ class FormatProcess {
320
389
  this.process = bp;
321
390
  }
322
391
  resolve(elementApi) {
392
+ var _documentation$2, _user2, _groups2;
323
393
  let user, groups, description;
324
394
  const bp = this.process;
325
395
  const {
@@ -329,13 +399,12 @@ class FormatProcess {
329
399
  } = bp.behaviour;
330
400
  if (candidateStarterUsers) user = resolveAndSplit(elementApi, candidateStarterUsers);
331
401
  if (candidateStarterGroups) groups = resolveAndSplit(elementApi, candidateStarterGroups);
332
- if (documentation) description = documentation[0]?.text;
402
+ if (documentation) description = (_documentation$2 = documentation[0]) === null || _documentation$2 === void 0 ? void 0 : _documentation$2.text;
333
403
  return {
334
- resultVariable: this.resultVariable,
335
- ...(user?.length && {
404
+ ...(((_user2 = user) === null || _user2 === void 0 ? void 0 : _user2.length) && {
336
405
  candidateStarterUsers: user
337
406
  }),
338
- ...(groups?.length && {
407
+ ...(((_groups2 = groups) === null || _groups2 === void 0 ? void 0 : _groups2.length) && {
339
408
  candidateStarterGroups: groups
340
409
  }),
341
410
  ...(!elementApi.content.description && description && {
@@ -353,6 +422,7 @@ function resolveAndSplit(elementApi, str) {
353
422
  return resolved.split(',').map(g => g.trim && g.trim().toLowerCase()).filter(Boolean);
354
423
  }
355
424
  function extendFn(behaviour, context) {
425
+ var _behaviour$extensionE;
356
426
  if (behaviour.$type === 'bpmn:StartEvent' && behaviour.eventDefinitions) {
357
427
  const timer = behaviour.eventDefinitions.find(({
358
428
  type,
@@ -362,11 +432,21 @@ function extendFn(behaviour, context) {
362
432
  scheduledStart: timer.behaviour.timeCycle
363
433
  });
364
434
  }
365
- if (!Array.isArray(behaviour.extensionElements?.values)) return;
366
- const inputOutput = behaviour.extensionElements.values.find(el => el.$type === 'camunda:InputOutput');
367
- const connector = behaviour.extensionElements.values.find(el => el.$type === 'camunda:Connector');
368
- if (inputOutput) registerIOScripts(behaviour.id, context, inputOutput.$type, inputOutput);
369
- if (connector) registerIOScripts(behaviour.id, context, connector.$type, connector.inputOutput);
435
+ if (!Array.isArray((_behaviour$extensionE = behaviour.extensionElements) === null || _behaviour$extensionE === void 0 ? void 0 : _behaviour$extensionE.values)) return;
436
+ let listener = 0;
437
+ for (const extension of behaviour.extensionElements.values) {
438
+ switch (extension.$type) {
439
+ case 'camunda:InputOutput':
440
+ registerIOScripts(behaviour.id, context, extension.$type, extension);
441
+ break;
442
+ case 'camunda:Connector':
443
+ registerIOScripts(behaviour.id, context, extension.$type, extension.inputOutput);
444
+ break;
445
+ case 'camunda:ExecutionListener':
446
+ registerListenerScript(behaviour.id, context, extension.$type, extension, listener++);
447
+ break;
448
+ }
449
+ }
370
450
  }
371
451
  function registerIOScripts(parentId, context, type, ioBehaviour) {
372
452
  if (!ioBehaviour) return;
@@ -393,4 +473,22 @@ function registerIOScripts(parentId, context, type, ioBehaviour) {
393
473
  })
394
474
  });
395
475
  }
476
+ }
477
+ function registerListenerScript(parentId, context, type, listener, pos) {
478
+ const {
479
+ event,
480
+ script
481
+ } = listener;
482
+ if (!script) return;
483
+ const id = `${parentId}/${type}/${event}/${pos}`;
484
+ context.addScript(id, {
485
+ id,
486
+ scriptFormat: script.scriptFormat,
487
+ ...(script.value && {
488
+ body: script.value
489
+ }),
490
+ ...(script.resource && {
491
+ resource: script.resource
492
+ })
493
+ });
396
494
  }
@@ -30,7 +30,7 @@ Connector.prototype.execute = async function execute(...args) {
30
30
  }
31
31
  async function formatCallback(serviceErr, result) {
32
32
  if (serviceErr) return callback(serviceErr);
33
- if (!io?.output.length) return callback(null, result);
33
+ if (!(io !== null && io !== void 0 && io.output.length)) return callback(null, result);
34
34
  try {
35
35
  const formattedResult = await io.getOutput(activity, {
36
36
  ...executionMessage,
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ class Listener {
8
+ constructor(activity, context, extension) {
9
+ this.activity = activity;
10
+ this.environment = this.activity.environment;
11
+ this.context = context;
12
+ this.extension = extension;
13
+ this.type = extension.$type;
14
+ this.event = extension.event;
15
+ }
16
+ _getScope(message, extend) {
17
+ const environment = this.environment;
18
+ const {
19
+ fields,
20
+ content,
21
+ properties
22
+ } = message;
23
+ const scope = {
24
+ ...extend,
25
+ type: this.type,
26
+ listener: {
27
+ event: this.event
28
+ },
29
+ fields,
30
+ content,
31
+ properties,
32
+ environment,
33
+ logger: this.activity.logger
34
+ };
35
+ const listenerFields = this._getFields(environment, scope);
36
+ if (listenerFields) scope.listener.fields = listenerFields;
37
+ return scope;
38
+ }
39
+ _getFields(environment, scope) {
40
+ const fields = this.extension.fields;
41
+ if (!(fields !== null && fields !== void 0 && fields.length)) return;
42
+ const result = {};
43
+ for (const {
44
+ name,
45
+ expression,
46
+ string
47
+ } of fields) {
48
+ result['' + name] = expression ? environment.resolveExpression(expression, scope) : string;
49
+ }
50
+ return result;
51
+ }
52
+ }
53
+ class ScriptListener extends Listener {
54
+ constructor(activity, context, extension, pos) {
55
+ super(activity, context, extension);
56
+ const id = this.id = `${activity.id}/${extension.script.$type}/${this.event}/${pos}`;
57
+ this._register(context, id, extension.script);
58
+ }
59
+ execute(api) {
60
+ const environment = this.environment;
61
+ const scope = this._getScope(api, {
62
+ id: this.id,
63
+ resolveExpression
64
+ });
65
+ const script = this.environment.scripts.getScript(this.extension.scriptFormat, {
66
+ id: this.id
67
+ });
68
+ return new Promise((resolve, reject) => {
69
+ return script.execute(scope, (err, result) => {
70
+ if (err) return reject(err);
71
+ resolve(result);
72
+ });
73
+ });
74
+ function resolveExpression(expression) {
75
+ return environment.resolveExpression(expression, scope);
76
+ }
77
+ }
78
+ _register(context, id, script) {
79
+ const {
80
+ scriptFormat,
81
+ value,
82
+ resource
83
+ } = script;
84
+ context.environment.scripts.register({
85
+ id,
86
+ type: this.type,
87
+ behaviour: {
88
+ scriptFormat,
89
+ ...(value && {
90
+ script: value
91
+ }),
92
+ ...(resource && {
93
+ resource
94
+ })
95
+ }
96
+ });
97
+ }
98
+ }
99
+ class ExpressionListener extends Listener {
100
+ execute(message) {
101
+ const scope = this._getScope(message);
102
+ return {
103
+ expression: this.environment.resolveExpression(this.extension.expression, scope)
104
+ };
105
+ }
106
+ }
107
+ class ExecutionListeners {
108
+ constructor(activity, context) {
109
+ this.activity = activity;
110
+ this.context = context;
111
+ this.listeners = [];
112
+ this.onStart = false;
113
+ this.onEnd = false;
114
+ }
115
+ get length() {
116
+ return this.listeners.length;
117
+ }
118
+ add(extension, pos) {
119
+ const {
120
+ event,
121
+ script,
122
+ expression
123
+ } = extension;
124
+ if (!script && !expression) return;
125
+ const list = this.listeners;
126
+ if (script && (script.value || script.resource)) list.push(new ScriptListener(this.activity, this.context, extension, pos));else if (expression) list.push(new ExpressionListener(this.activity, this.context, extension));
127
+ if (event === 'start') this.onStart = true;
128
+ if (event === 'end') this.onEnd = true;
129
+ }
130
+ async execute(event, message) {
131
+ const found = this.listeners.filter(l => l.event === event);
132
+ const format = {};
133
+ for (const f of found) {
134
+ const result = await f.execute(message);
135
+ if (result !== null && typeof result === 'object') Object.assign(format, result);
136
+ }
137
+ return format;
138
+ }
139
+ }
140
+ exports.default = ExecutionListeners;
@@ -105,7 +105,7 @@ FlowScripts.prototype.getScript = function getScript(scriptType, {
105
105
  function JavaScript(flowName, scriptBody, runContext, options) {
106
106
  this.flowName = flowName;
107
107
  this._runContext = runContext;
108
- this.timeout = options && options.timeout;
108
+ this.timeout = options === null || options === void 0 ? void 0 : options.timeout;
109
109
  try {
110
110
  this.script = new _vm.Script(scriptBody, options);
111
111
  } catch (err) {
@@ -151,17 +151,22 @@ function JavaScriptResource(flowName, resource, resourceBase, runContext, option
151
151
  this[kResources] = resourceBase;
152
152
  }
153
153
  JavaScriptResource.prototype.execute = async function execute(executionContext, callback) {
154
+ let resource;
154
155
  try {
155
- var scriptBody = await _fs.promises.readFile((0, _path.join)(this[kResources], executionContext.resolveExpression(this.resource))); // eslint-disable-line no-var
156
+ resource = executionContext.resolveExpression(this.resource);
157
+ var scriptBody = await _fs.promises.readFile((0, _path.join)(this[kResources], resource)); // eslint-disable-line no-var
156
158
  } catch (err) {
159
+ if (err instanceof SyntaxError) {
160
+ return callback(err);
161
+ }
157
162
  const {
158
163
  filename
159
164
  } = this.options;
160
- return callback(new Error(`${filename}: script resource ${this.resource} not found`));
165
+ return callback(new Error(`${filename}: script resource ${resource || this.resource} not found`));
161
166
  }
162
167
  const script = new JavaScript(this.flowName, scriptBody, this._runContext, {
163
168
  ...this.options,
164
- filename: this.resource
169
+ filename: `${this.options.filename}/${resource}`
165
170
  });
166
171
  return script.execute(executionContext, callback);
167
172
  };
package/dist/src/IO.js CHANGED
@@ -167,7 +167,7 @@ class IOScript extends IOBase {
167
167
  ...rest
168
168
  } = executionMessage;
169
169
  const scope = {
170
- id: this.scriptId,
170
+ id: this.id,
171
171
  type: this.type,
172
172
  name,
173
173
  fields,
package/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import cronParser from 'cron-parser';
2
- import IOProperties from './src/IOProperties';
3
- import IOForm from './src/IOForm';
4
- import Connector from './src/Connector';
5
- import ServiceExpression from './src/ServiceExpression';
6
- import {InputOutput} from './src/IO';
2
+ import IOProperties from './src/IOProperties.js';
3
+ import IOForm from './src/IOForm.js';
4
+ import Connector from './src/Connector.js';
5
+ import ServiceExpression from './src/ServiceExpression.js';
6
+ import {InputOutput} from './src/IO.js';
7
+ import ExtensionListeners from './src/ExecutionListeners.js';
7
8
 
8
9
  const iso8601cycle = /^\s*(R\d+\/)?P\w+/i;
9
10
 
@@ -98,11 +99,41 @@ class OnifyElementExtensions {
98
99
 
99
100
  broker.publish('format', 'run.end.complete', {...format}, {persistent: false});
100
101
  }, {consumerTag: '_onify-extension-on-executed'});
102
+
103
+ const executionListeners = this.extensions.listeners;
104
+ if (executionListeners?.onStart) {
105
+ activity.on('start', async (elementApi) => {
106
+ formatQ.queueMessage({routingKey: 'run.listener.start'}, {endRoutingKey: 'run.listener.start.complete'}, {persistent: false});
107
+
108
+ try {
109
+ var format = await executionListeners.execute('start', elementApi);
110
+ } catch (err) {
111
+ return broker.publish('format', 'run.listener.start.error', {error: err}, {persistent: false});
112
+ }
113
+
114
+ broker.publish('format', 'run.listener.start.complete', {...format}, {persistent: false});
115
+ }, {consumerTag: '_onify-extension-on-listenerstart'});
116
+ }
117
+ if (executionListeners?.onEnd) {
118
+ activity.on('end', async (elementApi) => {
119
+ formatQ.queueMessage({routingKey: 'run.listener.end'}, {endRoutingKey: 'run.listener.end.complete'}, {persistent: false});
120
+
121
+ try {
122
+ var format = await executionListeners.execute('end', elementApi);
123
+ } catch (err) {
124
+ return broker.publish('format', 'run.listener.end.error', {error: err}, {persistent: false});
125
+ }
126
+
127
+ broker.publish('format', 'run.listener.end.complete', {...format}, {persistent: false});
128
+ }, {consumerTag: '_onify-extension-on-listenerend'});
129
+ }
101
130
  }
102
131
  deactivate() {
103
132
  const broker = this.activity.broker;
104
133
  broker.cancel('_onify-extension-on-enter');
105
134
  broker.cancel('_onify-extension-on-executed');
135
+ broker.cancel('_onify-extension-on-listenerstart');
136
+ broker.cancel('_onify-extension-on-listenerend');
106
137
  }
107
138
  async _formatOnEnter(broker, formatQ, elementApi) {
108
139
  formatQ.queueMessage({routingKey: 'run.enter.format'}, {endRoutingKey: 'run.enter.complete'}, {persistent: false});
@@ -185,6 +216,9 @@ function getExtensions(element, context) {
185
216
  const extensions = element.behaviour.extensionElements?.values;
186
217
  if (!extensions) return result;
187
218
 
219
+ const listeners = new ExtensionListeners(element, context);
220
+
221
+ let listener = 0;
188
222
  for (const ext of extensions) {
189
223
  switch (ext.$type) {
190
224
  case 'camunda:Properties':
@@ -202,16 +236,21 @@ function getExtensions(element, context) {
202
236
  result.Service = Connector.bind(Connector, connectorId, io);
203
237
  break;
204
238
  }
239
+ case 'camunda:ExecutionListener':
240
+ listeners.add(ext, listener++);
241
+ break;
205
242
  }
206
243
  }
207
244
 
245
+ if (listeners.length) result.listeners = listeners;
246
+
208
247
  return result;
209
248
  }
210
249
 
211
250
  class FormatActivity {
212
251
  constructor(activity) {
213
252
  this.activity = activity;
214
- this.resultVariable = activity.behaviour.resultVariable || '_' + activity.id;
253
+ this.resultVariable = activity.behaviour.resultVariable;
215
254
 
216
255
  let timeCycles;
217
256
  if (activity.eventDefinitions) {
@@ -252,7 +291,7 @@ class FormatActivity {
252
291
  }
253
292
 
254
293
  return {
255
- resultVariable: this.resultVariable,
294
+ ...(this.resultVariable && {resultVariable: this.resultVariable}),
256
295
  ...(scheduledStart && activity.parent.type === 'bpmn:Process' && {scheduledStart}),
257
296
  ...(user?.length && {candidateUsers: user}),
258
297
  ...(groups?.length && {candidateGroups: groups}),
@@ -281,7 +320,6 @@ class FormatProcess {
281
320
  if (documentation) description = documentation[0]?.text;
282
321
 
283
322
  return {
284
- resultVariable: this.resultVariable,
285
323
  ...(user?.length && {candidateStarterUsers: user}),
286
324
  ...(groups?.length && {candidateStarterGroups: groups}),
287
325
  ...(!elementApi.content.description && description && {description: elementApi.resolveExpression(description)}),
@@ -311,11 +349,20 @@ function extendFn(behaviour, context) {
311
349
 
312
350
  if (!Array.isArray(behaviour.extensionElements?.values)) return;
313
351
 
314
- const inputOutput = behaviour.extensionElements.values.find((el) => el.$type === 'camunda:InputOutput');
315
- const connector = behaviour.extensionElements.values.find((el) => el.$type === 'camunda:Connector');
316
-
317
- if (inputOutput) registerIOScripts(behaviour.id, context, inputOutput.$type, inputOutput);
318
- if (connector) registerIOScripts(behaviour.id, context, connector.$type, connector.inputOutput);
352
+ let listener = 0;
353
+ for (const extension of behaviour.extensionElements.values) {
354
+ switch (extension.$type) {
355
+ case 'camunda:InputOutput':
356
+ registerIOScripts(behaviour.id, context, extension.$type, extension);
357
+ break;
358
+ case 'camunda:Connector':
359
+ registerIOScripts(behaviour.id, context, extension.$type, extension.inputOutput);
360
+ break;
361
+ case 'camunda:ExecutionListener':
362
+ registerListenerScript(behaviour.id, context, extension.$type, extension, listener++);
363
+ break;
364
+ }
365
+ }
319
366
  }
320
367
 
321
368
  function registerIOScripts(parentId, context, type, ioBehaviour) {
@@ -336,3 +383,16 @@ function registerIOScripts(parentId, context, type, ioBehaviour) {
336
383
  });
337
384
  }
338
385
  }
386
+
387
+ function registerListenerScript(parentId, context, type, listener, pos) {
388
+ const {event, script} = listener;
389
+ if (!script) return;
390
+
391
+ const id = `${parentId}/${type}/${event}/${pos}`;
392
+ context.addScript(id, {
393
+ id,
394
+ scriptFormat: script.scriptFormat,
395
+ ...(script.value && {body: script.value}),
396
+ ...(script.resource && {resource: script.resource}),
397
+ });
398
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onify/flow-extensions",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "Onify Flow extensions",
5
5
  "module": "index.js",
6
6
  "main": "dist/index.js",
@@ -40,7 +40,7 @@
40
40
  "@babel/preset-env": "^7.20.2",
41
41
  "@babel/register": "^7.18.9",
42
42
  "bpmn-elements-8-1": "npm:bpmn-elements@8.1",
43
- "bpmn-engine": "^15.1.0",
43
+ "bpmn-engine": "^15.1.2",
44
44
  "bpmn-engine-14": "npm:bpmn-engine@14",
45
45
  "bpmn-moddle": "^7.1.3",
46
46
  "c8": "^7.12.0",
@@ -48,13 +48,13 @@
48
48
  "chai": "^4.3.7",
49
49
  "chronokinesis": "^3.1.2",
50
50
  "debug": "^4.3.4",
51
- "eslint": "^8.27.0",
51
+ "eslint": "^8.28.0",
52
52
  "mocha": "^10.1.0",
53
53
  "mocha-cakes-2": "^3.3.0",
54
54
  "moddle-context-serializer": "^2.1.0"
55
55
  },
56
56
  "dependencies": {
57
- "cron-parser": "^4.6.0"
57
+ "cron-parser": "^4.7.0"
58
58
  },
59
59
  "files": [
60
60
  "src",
@@ -0,0 +1,126 @@
1
+ class Listener {
2
+ constructor(activity, context, extension) {
3
+ this.activity = activity;
4
+ this.environment = this.activity.environment;
5
+ this.context = context;
6
+ this.extension = extension;
7
+ this.type = extension.$type;
8
+ this.event = extension.event;
9
+ }
10
+ _getScope(message, extend) {
11
+ const environment = this.environment;
12
+
13
+ const {fields, content, properties} = message;
14
+ const scope = {
15
+ ...extend,
16
+ type: this.type,
17
+ listener: {
18
+ event: this.event,
19
+ },
20
+ fields,
21
+ content,
22
+ properties,
23
+ environment,
24
+ logger: this.activity.logger,
25
+ };
26
+
27
+ const listenerFields = this._getFields(environment, scope);
28
+ if (listenerFields) scope.listener.fields = listenerFields;
29
+
30
+ return scope;
31
+ }
32
+ _getFields(environment, scope) {
33
+ const fields = this.extension.fields;
34
+ if (!fields?.length) return;
35
+ const result = {};
36
+ for (const {name, expression, string} of fields) {
37
+ result['' + name] = expression ? environment.resolveExpression(expression, scope) : string;
38
+ }
39
+ return result;
40
+ }
41
+ }
42
+
43
+ class ScriptListener extends Listener {
44
+ constructor(activity, context, extension, pos) {
45
+ super(activity, context, extension);
46
+ const id = this.id = `${activity.id}/${extension.script.$type}/${this.event}/${pos}`;
47
+ this._register(context, id, extension.script);
48
+ }
49
+ execute(api) {
50
+ const environment = this.environment;
51
+ const scope = this._getScope(api, {id: this.id, resolveExpression});
52
+ const script = this.environment.scripts.getScript(this.extension.scriptFormat, {id: this.id});
53
+
54
+ return new Promise((resolve, reject) => {
55
+ return script.execute(scope, (err, result) => {
56
+ if (err) return reject(err);
57
+ resolve(result);
58
+ });
59
+ });
60
+
61
+ function resolveExpression(expression) {
62
+ return environment.resolveExpression(expression, scope);
63
+ }
64
+ }
65
+ _register(context, id, script) {
66
+ const {
67
+ scriptFormat,
68
+ value,
69
+ resource
70
+ } = script;
71
+
72
+ context.environment.scripts.register({
73
+ id,
74
+ type: this.type,
75
+ behaviour: {
76
+ scriptFormat,
77
+ ...(value && {
78
+ script: value
79
+ }),
80
+ ...(resource && {
81
+ resource
82
+ })
83
+ }
84
+ });
85
+ }
86
+ }
87
+
88
+ class ExpressionListener extends Listener {
89
+ execute(message) {
90
+ const scope = this._getScope(message);
91
+ return {expression: this.environment.resolveExpression(this.extension.expression, scope)};
92
+ }
93
+ }
94
+
95
+ export default class ExecutionListeners {
96
+ constructor(activity, context) {
97
+ this.activity = activity;
98
+ this.context = context;
99
+ this.listeners = [];
100
+ this.onStart = false;
101
+ this.onEnd = false;
102
+ }
103
+ get length() {
104
+ return this.listeners.length;
105
+ }
106
+ add(extension, pos) {
107
+ const {event, script, expression} = extension;
108
+ if (!script && !expression) return;
109
+
110
+ const list = this.listeners;
111
+ if (script && (script.value || script.resource)) list.push(new ScriptListener(this.activity, this.context, extension, pos));
112
+ else if (expression) list.push(new ExpressionListener(this.activity, this.context, extension));
113
+
114
+ if (event === 'start') this.onStart = true;
115
+ if (event === 'end') this.onEnd = true;
116
+ }
117
+ async execute(event, message) {
118
+ const found = this.listeners.filter((l) => l.event === event);
119
+ const format = {};
120
+ for (const f of found) {
121
+ const result = await f.execute(message);
122
+ if (result !== null && typeof result === 'object') Object.assign(format, result);
123
+ }
124
+ return format;
125
+ }
126
+ }
package/src/IO.js CHANGED
@@ -153,7 +153,7 @@ class IOScript extends IOBase {
153
153
 
154
154
  const {fields, content, properties, ...rest} = executionMessage;
155
155
  const scope = {
156
- id: this.scriptId,
156
+ id: this.id,
157
157
  type: this.type,
158
158
  name,
159
159
  fields,