@onify/flow-extensions 1.1.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/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
 
@@ -72,26 +73,22 @@ class OnifyElementExtensions {
72
73
  activity.behaviour.Service = Service;
73
74
  }
74
75
  }
75
- activate() {
76
+ activate(message) {
76
77
  const activity = this.activity;
77
78
  const broker = activity.broker;
78
79
  const formatQ = activity.broker.getQueue('format-run-q');
79
80
 
80
- activity.on('enter', async (elementApi) => {
81
- formatQ.queueMessage({routingKey: 'run.enter.format'}, {endRoutingKey: 'run.enter.complete'}, {persistent: false});
82
-
83
- try {
84
- var format = await this._onEnter(elementApi);
85
- } catch (err) {
86
- return broker.publish('format', 'run.enter.error', {error: err}, {persistent: false});
87
- }
88
-
89
- broker.publish('format', 'run.enter.complete', format, {persistent: false});
90
- }, {consumerTag: '_onify-extension-on-enter'});
81
+ if (message.fields.redelivered && message.fields.routingKey === 'run.start') {
82
+ activity.on('start', (elementApi) => {
83
+ this._formatOnEnter(broker, formatQ, elementApi);
84
+ }, {consumerTag: '_onify-extension-on-enter'});
85
+ } else {
86
+ activity.on('enter', (elementApi) => {
87
+ this._formatOnEnter(broker, formatQ, elementApi);
88
+ }, {consumerTag: '_onify-extension-on-enter'});
89
+ }
91
90
 
92
91
  activity.on('activity.execution.completed', async (elementApi) => {
93
- if (elementApi.fields.redelivered) return;
94
-
95
92
  formatQ.queueMessage({routingKey: 'run.end.format'}, {endRoutingKey: 'run.end.complete'}, {persistent: false});
96
93
 
97
94
  try {
@@ -102,11 +99,52 @@ class OnifyElementExtensions {
102
99
 
103
100
  broker.publish('format', 'run.end.complete', {...format}, {persistent: false});
104
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
+ }
105
130
  }
106
131
  deactivate() {
107
132
  const broker = this.activity.broker;
108
133
  broker.cancel('_onify-extension-on-enter');
109
134
  broker.cancel('_onify-extension-on-executed');
135
+ broker.cancel('_onify-extension-on-listenerstart');
136
+ broker.cancel('_onify-extension-on-listenerend');
137
+ }
138
+ async _formatOnEnter(broker, formatQ, elementApi) {
139
+ formatQ.queueMessage({routingKey: 'run.enter.format'}, {endRoutingKey: 'run.enter.complete'}, {persistent: false});
140
+
141
+ try {
142
+ var format = await this._onEnter(elementApi);
143
+ } catch (err) {
144
+ return broker.publish('format', 'run.enter.error', {error: err}, {persistent: false});
145
+ }
146
+
147
+ broker.publish('format', 'run.enter.complete', format, {persistent: false});
110
148
  }
111
149
  async _onEnter(elementApi) {
112
150
  const {format, properties, io, form} = this.extensions;
@@ -178,7 +216,9 @@ function getExtensions(element, context) {
178
216
  const extensions = element.behaviour.extensionElements?.values;
179
217
  if (!extensions) return result;
180
218
 
219
+ const listeners = new ExtensionListeners(element, context);
181
220
 
221
+ let listener = 0;
182
222
  for (const ext of extensions) {
183
223
  switch (ext.$type) {
184
224
  case 'camunda:Properties':
@@ -196,16 +236,21 @@ function getExtensions(element, context) {
196
236
  result.Service = Connector.bind(Connector, connectorId, io);
197
237
  break;
198
238
  }
239
+ case 'camunda:ExecutionListener':
240
+ listeners.add(ext, listener++);
241
+ break;
199
242
  }
200
243
  }
201
244
 
245
+ if (listeners.length) result.listeners = listeners;
246
+
202
247
  return result;
203
248
  }
204
249
 
205
250
  class FormatActivity {
206
251
  constructor(activity) {
207
252
  this.activity = activity;
208
- this.resultVariable = activity.behaviour.resultVariable || '_' + activity.id;
253
+ this.resultVariable = activity.behaviour.resultVariable;
209
254
 
210
255
  let timeCycles;
211
256
  if (activity.eventDefinitions) {
@@ -246,7 +291,7 @@ class FormatActivity {
246
291
  }
247
292
 
248
293
  return {
249
- resultVariable: this.resultVariable,
294
+ ...(this.resultVariable && {resultVariable: this.resultVariable}),
250
295
  ...(scheduledStart && activity.parent.type === 'bpmn:Process' && {scheduledStart}),
251
296
  ...(user?.length && {candidateUsers: user}),
252
297
  ...(groups?.length && {candidateGroups: groups}),
@@ -270,13 +315,11 @@ class FormatProcess {
270
315
  candidateStarterGroups,
271
316
  } = bp.behaviour;
272
317
 
273
-
274
318
  if (candidateStarterUsers) user = resolveAndSplit(elementApi, candidateStarterUsers);
275
319
  if (candidateStarterGroups) groups = resolveAndSplit(elementApi, candidateStarterGroups);
276
320
  if (documentation) description = documentation[0]?.text;
277
321
 
278
322
  return {
279
- resultVariable: this.resultVariable,
280
323
  ...(user?.length && {candidateStarterUsers: user}),
281
324
  ...(groups?.length && {candidateStarterGroups: groups}),
282
325
  ...(!elementApi.content.description && description && {description: elementApi.resolveExpression(description)}),
@@ -306,11 +349,20 @@ function extendFn(behaviour, context) {
306
349
 
307
350
  if (!Array.isArray(behaviour.extensionElements?.values)) return;
308
351
 
309
- const inputOutput = behaviour.extensionElements.values.find((el) => el.$type === 'camunda:InputOutput');
310
- const connector = behaviour.extensionElements.values.find((el) => el.$type === 'camunda:Connector');
311
-
312
- if (inputOutput) registerIOScripts(behaviour.id, context, inputOutput.$type, inputOutput);
313
- 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
+ }
314
366
  }
315
367
 
316
368
  function registerIOScripts(parentId, context, type, ioBehaviour) {
@@ -331,3 +383,16 @@ function registerIOScripts(parentId, context, type, ioBehaviour) {
331
383
  });
332
384
  }
333
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": "1.1.0",
3
+ "version": "3.0.0",
4
4
  "description": "Onify Flow extensions",
5
5
  "module": "index.js",
6
6
  "main": "dist/index.js",
@@ -35,26 +35,26 @@
35
35
  "license": "MIT",
36
36
  "devDependencies": {
37
37
  "@aircall/expression-parser": "^1.0.4",
38
- "@babel/cli": "^7.18.6",
39
- "@babel/core": "^7.18.6",
40
- "@babel/preset-env": "^7.18.6",
41
- "@babel/register": "^7.18.6",
42
- "bpmn-elements": "^8.1.0",
43
- "bpmn-engine": "^14.1.0",
44
- "bpmn-moddle": "^7.1.2",
45
- "c8": "^7.11.3",
46
- "camunda-bpmn-moddle": "^6.1.2",
47
- "chai": "^4.3.6",
38
+ "@babel/cli": "^7.19.3",
39
+ "@babel/core": "^7.20.2",
40
+ "@babel/preset-env": "^7.20.2",
41
+ "@babel/register": "^7.18.9",
42
+ "bpmn-elements-8-1": "npm:bpmn-elements@8.1",
43
+ "bpmn-engine": "^15.1.2",
44
+ "bpmn-engine-14": "npm:bpmn-engine@14",
45
+ "bpmn-moddle": "^7.1.3",
46
+ "c8": "^7.12.0",
47
+ "camunda-bpmn-moddle": "^7.0.1",
48
+ "chai": "^4.3.7",
48
49
  "chronokinesis": "^3.1.2",
49
50
  "debug": "^4.3.4",
50
- "eslint": "^7.32.0",
51
- "mocha": "^10.0.0",
51
+ "eslint": "^8.28.0",
52
+ "mocha": "^10.1.0",
52
53
  "mocha-cakes-2": "^3.3.0",
53
- "moddle-context-serializer": "^2.1.0",
54
- "nock": "^13.2.8"
54
+ "moddle-context-serializer": "^2.1.0"
55
55
  },
56
56
  "dependencies": {
57
- "cron-parser": "^4.5.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,