@onify/flow-extensions 2.0.0 → 4.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,19 +1,5 @@
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';
7
-
8
- const iso8601cycle = /^\s*(R\d+\/)?P\w+/i;
9
-
10
- class FormatError extends Error {
11
- constructor(elementId, err) {
12
- super(`<${elementId}> ${err.message}`);
13
- this.code = 'EFLOW_FORMAT';
14
- this.output = {statusCode: 500};
15
- }
16
- }
1
+ import {OnifyProcessExtensions} from './src/OnifyProcessExtensions.js';
2
+ import {OnifyElementExtensions} from './src/OnifyElementExtensions.js';
17
3
 
18
4
  export {
19
5
  extensions,
@@ -25,297 +11,28 @@ function extensions(element, context) {
25
11
  return new OnifyElementExtensions(element, context);
26
12
  }
27
13
 
28
- class OnifyProcessExtensions {
29
- constructor(bp, context) {
30
- this.process = bp;
31
- this.context = context;
32
- this.extensions = getExtensions(bp, context);
33
- this._activate();
34
- }
35
- _activate() {
36
- const bp = this.process;
37
- bp.on('process.enter', (elementApi) => {
38
- try {
39
- const result = this._onEnter(elementApi);
40
- const environment = elementApi.environment;
41
- environment.assignVariables(result);
42
- } catch (err) {
43
- elementApi.broker.publish('event', 'process.error', {
44
- ...elementApi.content,
45
- error: new FormatError(bp.id, err)
46
- }, {mandatory: true, type: 'error'});
47
- }
48
- }, {consumerTag: '_onify-extension-on-enter'});
49
- }
50
- _onEnter(elementApi) {
51
- const {format, properties} = this.extensions;
52
- const result = {};
53
-
54
- Object.assign(result, format.resolve(elementApi));
55
- Object.assign(elementApi.content, result);
56
-
57
- if (properties) {
58
- Object.assign(result, properties.resolve(elementApi));
59
- Object.assign(elementApi.content, result);
60
- }
61
-
62
- return result;
63
- }
64
- }
65
-
66
- class OnifyElementExtensions {
67
- constructor(activity, context) {
68
- this.activity = activity;
69
- this.context = context;
70
- const {Service} = this.extensions = getExtensions(activity, context);
71
- if (Service) {
72
- activity.behaviour.Service = Service;
73
- }
74
- }
75
- activate(message) {
76
- const activity = this.activity;
77
- const broker = activity.broker;
78
- const formatQ = activity.broker.getQueue('format-run-q');
79
-
80
- if (message.fields.redelivered && message.fields.routingKey === 'run.start') {
81
- activity.on('start', (elementApi) => {
82
- this._formatOnEnter(broker, formatQ, elementApi);
83
- }, {consumerTag: '_onify-extension-on-enter'});
84
- } else {
85
- activity.on('enter', (elementApi) => {
86
- this._formatOnEnter(broker, formatQ, elementApi);
87
- }, {consumerTag: '_onify-extension-on-enter'});
88
- }
89
-
90
- activity.on('activity.execution.completed', async (elementApi) => {
91
- formatQ.queueMessage({routingKey: 'run.end.format'}, {endRoutingKey: 'run.end.complete'}, {persistent: false});
92
-
93
- try {
94
- var format = await this._onExecuted(elementApi);
95
- } catch (err) {
96
- return broker.publish('format', 'run.end.error', {error: err}, {persistent: false});
97
- }
98
-
99
- broker.publish('format', 'run.end.complete', {...format}, {persistent: false});
100
- }, {consumerTag: '_onify-extension-on-executed'});
101
- }
102
- deactivate() {
103
- const broker = this.activity.broker;
104
- broker.cancel('_onify-extension-on-enter');
105
- broker.cancel('_onify-extension-on-executed');
106
- }
107
- async _formatOnEnter(broker, formatQ, elementApi) {
108
- formatQ.queueMessage({routingKey: 'run.enter.format'}, {endRoutingKey: 'run.enter.complete'}, {persistent: false});
109
-
110
- try {
111
- var format = await this._onEnter(elementApi);
112
- } catch (err) {
113
- return broker.publish('format', 'run.enter.error', {error: err}, {persistent: false});
114
- }
115
-
116
- broker.publish('format', 'run.enter.complete', format, {persistent: false});
117
- }
118
- async _onEnter(elementApi) {
119
- const {format, properties, io, form} = this.extensions;
120
-
121
- const result = {};
122
- Object.assign(result, format.resolve(elementApi));
123
- Object.assign(elementApi.content, result);
124
-
125
- if (properties) {
126
- Object.assign(result, {properties: properties.resolve(elementApi)});
127
- Object.assign(elementApi.content, result);
128
- }
129
-
130
- if (io) {
131
- if (io.input?.length) {
132
- const input = await io.getInput(this.activity, elementApi);
133
- Object.assign(result, {input});
134
- Object.assign(elementApi.content, result);
135
- }
136
- if (io.output?.length) {
137
- result.assignToOutput = true;
138
- }
139
- }
140
-
141
- if (form) {
142
- Object.assign(result, {form: form.resolve(elementApi)});
143
- }
144
-
145
- return result;
146
- }
147
- async _onExecuted(elementApi) {
148
- const {properties, io} = this.extensions;
149
-
150
- const result = {};
151
- if (io?.output.length) {
152
- const output = await io.getOutput(this.activity, elementApi);
153
- Object.assign(result, {output});
154
- Object.assign(elementApi.content, {output});
155
- }
156
-
157
- if (properties) {
158
- const props = properties.resolve(elementApi);
159
- Object.assign(result, {properties: props});
160
- Object.assign(elementApi.content, {properties: props});
161
- }
162
-
163
- const {output, assignToOutput, resultVariable} = elementApi.content;
164
-
165
- if (output === undefined || output === null) return result;
166
-
167
- if (assignToOutput && typeof output === 'object') {
168
- Object.assign(this.activity.environment.output, output);
169
- } else if (resultVariable) {
170
- elementApi.environment.output[resultVariable] = output;
171
- }
172
-
173
- return result;
14
+ function extendFn(behaviour, context) {
15
+ if (behaviour.$type === 'bpmn:StartEvent' && behaviour.eventDefinitions) {
16
+ const timer = behaviour.eventDefinitions.find(({type, behaviour: edBehaviour}) => edBehaviour && type === 'bpmn:TimerEventDefinition');
17
+ if (timer && timer.behaviour.timeCycle) Object.assign(behaviour, {scheduledStart: timer.behaviour.timeCycle});
174
18
  }
175
- }
176
-
177
- function getExtensions(element, context) {
178
- const result = {
179
- format: element.type === 'bpmn:Process' ? new FormatProcess(element) : new FormatActivity(element),
180
- };
181
19
 
182
- const expression = element.behaviour.expression;
183
- if (expression) result.Service = ServiceExpression;
184
-
185
- const extensions = element.behaviour.extensionElements?.values;
186
- if (!extensions) return result;
20
+ if (!Array.isArray(behaviour.extensionElements?.values)) return;
187
21
 
188
- for (const ext of extensions) {
189
- switch (ext.$type) {
190
- case 'camunda:Properties':
191
- if (ext.values?.length) result.properties = new IOProperties(element, ext);
192
- break;
22
+ let listener = 0;
23
+ for (const extension of behaviour.extensionElements.values) {
24
+ switch (extension.$type) {
193
25
  case 'camunda:InputOutput':
194
- result.io = new InputOutput(element.id, ext, context);
26
+ registerIOScripts(behaviour.id, context, extension.$type, extension);
195
27
  break;
196
- case 'camunda:FormData':
197
- if (ext.fields?.length) result.form = new IOForm(element, ext);
28
+ case 'camunda:Connector':
29
+ registerIOScripts(behaviour.id, context, extension.$type, extension.inputOutput);
198
30
  break;
199
- case 'camunda:Connector': {
200
- const {connectorId, inputOutput} = ext;
201
- const io = inputOutput && new InputOutput(`${element.id}/${ext.$type.toLowerCase()}`, inputOutput, context);
202
- result.Service = Connector.bind(Connector, connectorId, io);
31
+ case 'camunda:ExecutionListener':
32
+ registerListenerScript(behaviour.id, context, extension.$type, extension, listener++);
203
33
  break;
204
- }
205
- }
206
- }
207
-
208
- return result;
209
- }
210
-
211
- class FormatActivity {
212
- constructor(activity) {
213
- this.activity = activity;
214
- this.resultVariable = activity.behaviour.resultVariable || '_' + activity.id;
215
-
216
- let timeCycles;
217
- if (activity.eventDefinitions) {
218
- for (const ed of activity.eventDefinitions.filter((e) => e.type === 'bpmn:TimerEventDefinition')) {
219
- if (!('timeCycle' in ed)) continue;
220
- timeCycles = timeCycles || [];
221
- timeCycles.push(ed.timeCycle);
222
- }
223
34
  }
224
- this.timeCycles = timeCycles;
225
- }
226
- resolve(elementApi) {
227
- let user, groups, assigneeValue, description;
228
- const activity = this.activity;
229
- const {
230
- documentation,
231
- candidateUsers,
232
- candidateGroups,
233
- scheduledStart,
234
- assignee,
235
- } = activity.behaviour;
236
-
237
- if (candidateUsers) user = resolveAndSplit(elementApi, candidateUsers);
238
- if (candidateGroups) groups = resolveAndSplit(elementApi, candidateGroups);
239
- if (assignee) assigneeValue = elementApi.resolveExpression(assignee);
240
- if (documentation) description = documentation[0]?.text;
241
-
242
- let expireAt;
243
- let timeCycles = this.timeCycles;
244
- if (timeCycles) {
245
- for (const cycle of timeCycles) {
246
- const cron = elementApi.resolveExpression(cycle);
247
- if (!cron || iso8601cycle.test(cron)) continue;
248
-
249
- const expireAtDt = cronParser.parseExpression(cron).next().toDate();
250
- if (!expireAt || expireAtDt < expireAt) expireAt = expireAtDt;
251
- }
252
- }
253
-
254
- return {
255
- resultVariable: this.resultVariable,
256
- ...(scheduledStart && activity.parent.type === 'bpmn:Process' && {scheduledStart}),
257
- ...(user?.length && {candidateUsers: user}),
258
- ...(groups?.length && {candidateGroups: groups}),
259
- ...(!elementApi.content.description && description && {description: elementApi.resolveExpression(description)}),
260
- ...(expireAt && {expireAt}),
261
- ...(assigneeValue && {assignee: assigneeValue}),
262
- };
263
- }
264
- }
265
-
266
- class FormatProcess {
267
- constructor(bp) {
268
- this.process = bp;
269
- }
270
- resolve(elementApi) {
271
- let user, groups, description;
272
- const bp = this.process;
273
- const {
274
- documentation,
275
- candidateStarterUsers,
276
- candidateStarterGroups,
277
- } = bp.behaviour;
278
-
279
- if (candidateStarterUsers) user = resolveAndSplit(elementApi, candidateStarterUsers);
280
- if (candidateStarterGroups) groups = resolveAndSplit(elementApi, candidateStarterGroups);
281
- if (documentation) description = documentation[0]?.text;
282
-
283
- return {
284
- resultVariable: this.resultVariable,
285
- ...(user?.length && {candidateStarterUsers: user}),
286
- ...(groups?.length && {candidateStarterGroups: groups}),
287
- ...(!elementApi.content.description && description && {description: elementApi.resolveExpression(description)}),
288
- };
289
- }
290
- }
291
-
292
- function resolveAndSplit(elementApi, str) {
293
- if (Array.isArray(str)) return str.filter(Boolean);
294
- if (typeof str !== 'string') return;
295
-
296
- const resolved = elementApi.resolveExpression(str);
297
- if (Array.isArray(resolved)) return resolved.filter(Boolean);
298
- if (typeof resolved !== 'string') return;
299
-
300
- return resolved
301
- .split(',')
302
- .map((g) => g.trim && g.trim().toLowerCase())
303
- .filter(Boolean);
304
- }
305
-
306
- function extendFn(behaviour, context) {
307
- if (behaviour.$type === 'bpmn:StartEvent' && behaviour.eventDefinitions) {
308
- const timer = behaviour.eventDefinitions.find(({type, behaviour: edBehaviour}) => edBehaviour && type === 'bpmn:TimerEventDefinition');
309
- if (timer && timer.behaviour.timeCycle) Object.assign(behaviour, {scheduledStart: timer.behaviour.timeCycle});
310
35
  }
311
-
312
- if (!Array.isArray(behaviour.extensionElements?.values)) return;
313
-
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);
319
36
  }
320
37
 
321
38
  function registerIOScripts(parentId, context, type, ioBehaviour) {
@@ -326,13 +43,29 @@ function registerIOScripts(parentId, context, type, ioBehaviour) {
326
43
  if (!definition) continue;
327
44
  if (definition.$type !== 'camunda:Script') continue;
328
45
 
329
- const filename = `${parentId}/${type}/${$type}/${name}`;
46
+ const ioType = `${type}/${$type}`;
47
+ const filename = `${parentId}/${ioType}/${name}`;
330
48
 
331
49
  context.addScript(filename, {
332
50
  id: filename,
333
51
  scriptFormat: definition.scriptFormat,
52
+ type: ioType,
334
53
  ...(definition.value && {body: definition.value}),
335
54
  ...(definition.resource && {resource: definition.resource}),
336
55
  });
337
56
  }
338
57
  }
58
+
59
+ function registerListenerScript(parentId, context, type, listener, pos) {
60
+ const {event, script} = listener;
61
+ if (!script) return;
62
+
63
+ const id = `${parentId}/${type}/${event}/${pos}`;
64
+ context.addScript(id, {
65
+ id,
66
+ scriptFormat: script.scriptFormat,
67
+ type,
68
+ ...(script.value && {body: script.value}),
69
+ ...(script.resource && {resource: script.resource}),
70
+ });
71
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onify/flow-extensions",
3
- "version": "2.0.0",
3
+ "version": "4.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.19.3",
39
- "@babel/core": "^7.20.2",
38
+ "@babel/cli": "^7.20.7",
39
+ "@babel/core": "^7.20.12",
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",
47
47
  "camunda-bpmn-moddle": "^7.0.1",
48
48
  "chai": "^4.3.7",
49
- "chronokinesis": "^3.1.2",
49
+ "chronokinesis": "^4.0.1",
50
50
  "debug": "^4.3.4",
51
- "eslint": "^8.27.0",
52
- "mocha": "^10.1.0",
51
+ "eslint": "^8.32.0",
52
+ "mocha": "^10.2.0",
53
53
  "mocha-cakes-2": "^3.3.0",
54
- "moddle-context-serializer": "^2.1.0"
54
+ "moddle-context-serializer": "^3.1.1"
55
55
  },
56
56
  "dependencies": {
57
- "cron-parser": "^4.6.0"
57
+ "cron-parser": "^4.7.1"
58
58
  },
59
59
  "files": [
60
60
  "src",
package/src/Connector.js CHANGED
@@ -1,4 +1,4 @@
1
- import {NotImplemented} from './Errors';
1
+ import {NotImplemented} from './Errors.js';
2
2
 
3
3
  export default function Connector(connectorId, io, activity, executionMessage) {
4
4
  if (!(this instanceof Connector)) return new Connector(connectorId, io, activity, executionMessage);
package/src/Errors.js CHANGED
@@ -1,4 +1,4 @@
1
- class NotImplemented extends Error {
1
+ export class NotImplemented extends Error {
2
2
  constructor(serviceId) {
3
3
  super(`${serviceId} service function not found`);
4
4
  this.code = 'EFLOW_NOT_IMPLEMENTED';
@@ -6,6 +6,10 @@ class NotImplemented extends Error {
6
6
  }
7
7
  }
8
8
 
9
- export {
10
- NotImplemented,
11
- };
9
+ export class FormatError extends Error {
10
+ constructor(elementId, err) {
11
+ super(`<${elementId}> ${err.message}`);
12
+ this.code = 'EFLOW_FORMAT';
13
+ this.output = {statusCode: 500};
14
+ }
15
+ }
@@ -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
+ }