@onify/flow-extensions 0.0.1
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/LICENSE +21 -0
- package/README.md +122 -0
- package/dist/index.js +455 -0
- package/dist/src/Connector.js +58 -0
- package/dist/src/FlowScripts.js +194 -0
- package/dist/src/IO.js +234 -0
- package/dist/src/IOForm.js +41 -0
- package/dist/src/IOProperties.js +30 -0
- package/dist/src/ServiceExpression.js +20 -0
- package/index.js +330 -0
- package/package.json +69 -0
- package/src/Connector.js +48 -0
- package/src/IO.js +181 -0
- package/src/IOForm.js +23 -0
- package/src/IOProperties.js +15 -0
- package/src/ServiceExpression.js +13 -0
package/index.js
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
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
|
+
class FormatError extends Error {
|
|
9
|
+
constructor(elementId, err) {
|
|
10
|
+
super(`<${elementId}> ${err.message}`);
|
|
11
|
+
this.code = 'EFLOW_FORMAT';
|
|
12
|
+
this.output = {statusCode: 500};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
extensions,
|
|
18
|
+
extendFn,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function extensions(element, context) {
|
|
22
|
+
if (element.type === 'bpmn:Process') return new OnifyProcessExtensions(element, context);
|
|
23
|
+
return new OnifyElementExtensions(element, context);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class OnifyProcessExtensions {
|
|
27
|
+
constructor(bp, context) {
|
|
28
|
+
this.process = bp;
|
|
29
|
+
this.context = context;
|
|
30
|
+
this.extensions = getExtensions(bp, context);
|
|
31
|
+
this._activate();
|
|
32
|
+
}
|
|
33
|
+
_activate() {
|
|
34
|
+
const bp = this.process;
|
|
35
|
+
bp.on('process.enter', (elementApi) => {
|
|
36
|
+
try {
|
|
37
|
+
const result = this._onEnter(elementApi);
|
|
38
|
+
const environment = elementApi.environment;
|
|
39
|
+
environment.assignVariables(result);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
elementApi.broker.publish('event', 'process.error', {
|
|
42
|
+
...elementApi.content,
|
|
43
|
+
error: new FormatError(bp.id, err)
|
|
44
|
+
}, {mandatory: true, type: 'error'});
|
|
45
|
+
}
|
|
46
|
+
}, {consumerTag: '_onify-extension-on-enter'});
|
|
47
|
+
}
|
|
48
|
+
_onEnter(elementApi) {
|
|
49
|
+
const {format, properties} = this.extensions;
|
|
50
|
+
const result = {};
|
|
51
|
+
|
|
52
|
+
Object.assign(result, format.resolve(elementApi));
|
|
53
|
+
Object.assign(elementApi.content, result);
|
|
54
|
+
|
|
55
|
+
if (properties) {
|
|
56
|
+
Object.assign(result, properties.resolve(elementApi));
|
|
57
|
+
Object.assign(elementApi.content, result);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
class OnifyElementExtensions {
|
|
65
|
+
constructor(activity, context) {
|
|
66
|
+
this.activity = activity;
|
|
67
|
+
this.context = context;
|
|
68
|
+
const {Service} = this.extensions = getExtensions(activity, context);
|
|
69
|
+
if (Service) {
|
|
70
|
+
activity.behaviour.Service = Service;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
activate() {
|
|
74
|
+
const activity = this.activity;
|
|
75
|
+
const broker = activity.broker;
|
|
76
|
+
const formatQ = activity.broker.getQueue('format-run-q');
|
|
77
|
+
|
|
78
|
+
activity.on('enter', async (elementApi) => {
|
|
79
|
+
formatQ.queueMessage({routingKey: 'run.enter.format'}, {endRoutingKey: 'run.enter.complete'}, {persistent: false});
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
var format = await this._onEnter(elementApi);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
return broker.publish('format', 'run.enter.error', {error: err}, {persistent: false});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
broker.publish('format', 'run.enter.complete', format, {persistent: false});
|
|
88
|
+
}, {consumerTag: '_onify-extension-on-enter'});
|
|
89
|
+
|
|
90
|
+
activity.on('activity.execution.completed', async (elementApi) => {
|
|
91
|
+
if (elementApi.fields.redelivered) return;
|
|
92
|
+
|
|
93
|
+
formatQ.queueMessage({routingKey: 'run.end.error'}, {endRoutingKey: 'run.end.complete'}, {persistent: false});
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
var format = await this._onExecuted(elementApi);
|
|
97
|
+
} catch (err) {
|
|
98
|
+
return broker.publish('format', 'run.enter.error', {error: err}, {persistent: false});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
broker.publish('format', 'run.end.complete', {...format}, {persistent: false});
|
|
102
|
+
}, {consumerTag: '_onify-extension-on-executed'});
|
|
103
|
+
}
|
|
104
|
+
deactivate() {
|
|
105
|
+
const broker = this.activity.broker;
|
|
106
|
+
broker.cancel('_onify-extension-on-enter');
|
|
107
|
+
broker.cancel('_onify-extension-on-executed');
|
|
108
|
+
}
|
|
109
|
+
async _onEnter(elementApi) {
|
|
110
|
+
const {format, properties, io, form} = this.extensions;
|
|
111
|
+
|
|
112
|
+
const result = {};
|
|
113
|
+
Object.assign(result, format.resolve(elementApi));
|
|
114
|
+
Object.assign(elementApi.content, result);
|
|
115
|
+
|
|
116
|
+
if (properties) {
|
|
117
|
+
Object.assign(result, {properties: properties.resolve(elementApi)});
|
|
118
|
+
Object.assign(elementApi.content, result);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (io) {
|
|
122
|
+
if (io.input?.length) {
|
|
123
|
+
const input = await io.getInput(this.activity, elementApi);
|
|
124
|
+
Object.assign(result, {input});
|
|
125
|
+
Object.assign(elementApi.content, result);
|
|
126
|
+
}
|
|
127
|
+
if (io.output?.length) {
|
|
128
|
+
result.assignToOutput = true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (form) {
|
|
133
|
+
Object.assign(result, {form: form.resolve(elementApi)});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
async _onExecuted(elementApi) {
|
|
139
|
+
const {properties, io} = this.extensions;
|
|
140
|
+
|
|
141
|
+
const result = {};
|
|
142
|
+
if (io?.output.length) {
|
|
143
|
+
const output = await io.getOutput(this.activity, elementApi);
|
|
144
|
+
Object.assign(result, {output});
|
|
145
|
+
Object.assign(elementApi.content, {output});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (properties) {
|
|
149
|
+
const props = properties.resolve(elementApi);
|
|
150
|
+
Object.assign(result, {properties: props});
|
|
151
|
+
Object.assign(elementApi.content, {properties: props});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const {output, assignToOutput, resultVariable} = elementApi.content;
|
|
155
|
+
|
|
156
|
+
if (output === undefined || output === null) return result;
|
|
157
|
+
|
|
158
|
+
if (assignToOutput && typeof output === 'object') {
|
|
159
|
+
Object.assign(this.activity.environment.output, output);
|
|
160
|
+
} else if (resultVariable) {
|
|
161
|
+
elementApi.environment.output[resultVariable] = output;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function getExtensions(element, context) {
|
|
169
|
+
const result = {
|
|
170
|
+
format: element.type === 'bpmn:Process' ? new FormatProcess(element) : new FormatActivity(element),
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const expression = element.behaviour.expression;
|
|
174
|
+
if (expression) result.Service = ServiceExpression;
|
|
175
|
+
|
|
176
|
+
const extensions = element.behaviour.extensionElements?.values;
|
|
177
|
+
if (!extensions) return result;
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
for (const ext of extensions) {
|
|
181
|
+
switch (ext.$type) {
|
|
182
|
+
case 'camunda:Properties':
|
|
183
|
+
if (ext.values?.length) result.properties = new IOProperties(element, ext);
|
|
184
|
+
break;
|
|
185
|
+
case 'camunda:InputOutput':
|
|
186
|
+
result.io = new InputOutput(element.id, ext, context);
|
|
187
|
+
break;
|
|
188
|
+
case 'camunda:FormData':
|
|
189
|
+
if (ext.fields?.length) result.form = new IOForm(element, ext);
|
|
190
|
+
break;
|
|
191
|
+
case 'camunda:Connector': {
|
|
192
|
+
const {connectorId, inputOutput} = ext;
|
|
193
|
+
const io = inputOutput && new InputOutput(`${element.id}/${ext.$type.toLowerCase()}`, inputOutput, context);
|
|
194
|
+
result.Service = Connector.bind(Connector, connectorId, io);
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
class FormatActivity {
|
|
204
|
+
constructor(activity) {
|
|
205
|
+
this.activity = activity;
|
|
206
|
+
this.resultVariable = activity.behaviour.resultVariable || '_' + activity.id;
|
|
207
|
+
|
|
208
|
+
let timeCycles;
|
|
209
|
+
if (activity.eventDefinitions) {
|
|
210
|
+
for (const ed of activity.eventDefinitions.filter((e) => e.type === 'bpmn:TimerEventDefinition')) {
|
|
211
|
+
if (!('timeCycle' in ed)) continue;
|
|
212
|
+
timeCycles = timeCycles || [];
|
|
213
|
+
timeCycles.push(ed.timeCycle);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
this.timeCycles = timeCycles;
|
|
217
|
+
}
|
|
218
|
+
resolve(elementApi) {
|
|
219
|
+
let user, groups, assigneeValue, description;
|
|
220
|
+
const activity = this.activity;
|
|
221
|
+
const {
|
|
222
|
+
documentation,
|
|
223
|
+
candidateUsers,
|
|
224
|
+
candidateGroups,
|
|
225
|
+
scheduledStart,
|
|
226
|
+
assignee,
|
|
227
|
+
} = activity.behaviour;
|
|
228
|
+
|
|
229
|
+
if (candidateUsers) user = resolveAndSplit(elementApi, candidateUsers);
|
|
230
|
+
if (candidateGroups) groups = resolveAndSplit(elementApi, candidateGroups);
|
|
231
|
+
if (assignee) assigneeValue = elementApi.resolveExpression(assignee);
|
|
232
|
+
if (documentation) description = documentation[0]?.text;
|
|
233
|
+
|
|
234
|
+
let expireAt;
|
|
235
|
+
let timeCycles = this.timeCycles;
|
|
236
|
+
if (timeCycles) {
|
|
237
|
+
for (const cycle of timeCycles) {
|
|
238
|
+
const cron = elementApi.resolveExpression(cycle);
|
|
239
|
+
if (!cron) continue;
|
|
240
|
+
const expireAtDt = cronParser.parseExpression(cron).next().toDate();
|
|
241
|
+
if (!expireAt || expireAtDt < expireAt) expireAt = expireAtDt;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
resultVariable: this.resultVariable,
|
|
247
|
+
...(scheduledStart && activity.parent.type === 'bpmn:Process' ? {scheduledStart} : undefined),
|
|
248
|
+
...(user?.length && {candidateUsers: user}),
|
|
249
|
+
...(groups?.length && {candidateGroups: groups}),
|
|
250
|
+
...(!elementApi.content.description && description && {description: elementApi.resolveExpression(description)}),
|
|
251
|
+
...(expireAt && {expireAt}),
|
|
252
|
+
...(assigneeValue && {assignee: assigneeValue}),
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
class FormatProcess {
|
|
258
|
+
constructor(bp) {
|
|
259
|
+
this.process = bp;
|
|
260
|
+
}
|
|
261
|
+
resolve(elementApi) {
|
|
262
|
+
let user, groups, description;
|
|
263
|
+
const bp = this.process;
|
|
264
|
+
const {
|
|
265
|
+
documentation,
|
|
266
|
+
candidateStarterUsers,
|
|
267
|
+
candidateStarterGroups,
|
|
268
|
+
} = bp.behaviour;
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
if (candidateStarterUsers) user = resolveAndSplit(elementApi, candidateStarterUsers);
|
|
272
|
+
if (candidateStarterGroups) groups = resolveAndSplit(elementApi, candidateStarterGroups);
|
|
273
|
+
if (documentation) description = documentation[0]?.text;
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
resultVariable: this.resultVariable,
|
|
277
|
+
...(user?.length && {candidateStarterUsers: user}),
|
|
278
|
+
...(groups?.length && {candidateStarterGroups: groups}),
|
|
279
|
+
...(!elementApi.content.description && description && {description: elementApi.resolveExpression(description)}),
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function resolveAndSplit(elementApi, str) {
|
|
285
|
+
if (Array.isArray(str)) return str.filter(Boolean);
|
|
286
|
+
if (typeof str !== 'string') return;
|
|
287
|
+
|
|
288
|
+
const resolved = elementApi.resolveExpression(str);
|
|
289
|
+
if (Array.isArray(resolved)) return resolved.filter(Boolean);
|
|
290
|
+
if (typeof resolved !== 'string') return;
|
|
291
|
+
|
|
292
|
+
return resolved
|
|
293
|
+
.split(',')
|
|
294
|
+
.map((g) => g.trim && g.trim().toLowerCase())
|
|
295
|
+
.filter(Boolean);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function extendFn(behaviour, context) {
|
|
299
|
+
if (behaviour.$type === 'bpmn:StartEvent' && behaviour.eventDefinitions) {
|
|
300
|
+
const timer = behaviour.eventDefinitions.find(({type, behaviour: edBehaviour}) => edBehaviour && type === 'bpmn:TimerEventDefinition');
|
|
301
|
+
if (timer && timer.behaviour.timeCycle) Object.assign(behaviour, {scheduledStart: timer.behaviour.timeCycle});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (!Array.isArray(behaviour.extensionElements?.values)) return;
|
|
305
|
+
|
|
306
|
+
const inputOutput = behaviour.extensionElements.values.find((el) => el.$type === 'camunda:InputOutput');
|
|
307
|
+
const connector = behaviour.extensionElements.values.find((el) => el.$type === 'camunda:Connector');
|
|
308
|
+
|
|
309
|
+
if (inputOutput) registerIOScripts(behaviour.id, context, inputOutput.$type, inputOutput);
|
|
310
|
+
if (connector) registerIOScripts(behaviour.id, context, connector.$type, connector.inputOutput);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function registerIOScripts(parentId, context, type, ioBehaviour) {
|
|
314
|
+
if (!ioBehaviour) return;
|
|
315
|
+
|
|
316
|
+
const {inputParameters = [], outputParameters = []} = ioBehaviour;
|
|
317
|
+
for (const {$type, name, definition} of inputParameters.concat(outputParameters)) {
|
|
318
|
+
if (!definition) continue;
|
|
319
|
+
if (definition.$type !== 'camunda:Script') continue;
|
|
320
|
+
|
|
321
|
+
const filename = `${parentId}/${type}/${$type}/${name}`;
|
|
322
|
+
|
|
323
|
+
context.addScript(filename, {
|
|
324
|
+
id: filename,
|
|
325
|
+
scriptFormat: definition.scriptFormat,
|
|
326
|
+
...(definition.value ? {body: definition.value}: undefined),
|
|
327
|
+
...(definition.resource ? {resource: definition.resource}: undefined),
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@onify/flow-extensions",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Onify Flow extensions",
|
|
5
|
+
"module": "index.js",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"directories": {
|
|
9
|
+
"test": "test"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "mocha",
|
|
13
|
+
"posttest": "npm run dist && eslint . --cache",
|
|
14
|
+
"prepare": "npm run dist",
|
|
15
|
+
"predist": "node -e \"fs.rmdirSync('./dist/src', {recursive: true});\"",
|
|
16
|
+
"dist": "babel index.js -d dist && babel src -d dist/src && babel test/helpers/FlowScripts.js -d dist/src",
|
|
17
|
+
"test:lcov": "c8 mocha -R dot && c8 report --reporter lcov && npm run posttest",
|
|
18
|
+
"cov:html": "c8 mocha -R dot && c8 report --reporter html"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"onify",
|
|
22
|
+
"flow",
|
|
23
|
+
"bpmn",
|
|
24
|
+
"bpmn-engine",
|
|
25
|
+
"extensions"
|
|
26
|
+
],
|
|
27
|
+
"author": {
|
|
28
|
+
"name": "Onify",
|
|
29
|
+
"url": "https://github.com/onify"
|
|
30
|
+
},
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@aircall/expression-parser": "^1.0.4",
|
|
34
|
+
"@babel/cli": "^7.17.6",
|
|
35
|
+
"@babel/core": "^7.17.5",
|
|
36
|
+
"@babel/preset-env": "^7.16.11",
|
|
37
|
+
"@babel/register": "^7.16.9",
|
|
38
|
+
"bpmn-elements": "^7.0.0",
|
|
39
|
+
"bpmn-engine": "^13.0.0",
|
|
40
|
+
"bpmn-moddle": "^7.1.2",
|
|
41
|
+
"c8": "^7.11.0",
|
|
42
|
+
"camunda-bpmn-moddle": "^6.1.2",
|
|
43
|
+
"chai": "^4.3.6",
|
|
44
|
+
"chronokinesis": "^3.1.2",
|
|
45
|
+
"debug": "^4.3.3",
|
|
46
|
+
"eslint": "^7.32.0",
|
|
47
|
+
"mocha": "^9.2.1",
|
|
48
|
+
"mocha-cakes-2": "^3.3.0",
|
|
49
|
+
"moddle-context-serializer": "^2.0.0",
|
|
50
|
+
"nock": "^13.2.4"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"cron-parser": "^4.2.1"
|
|
54
|
+
},
|
|
55
|
+
"files": [
|
|
56
|
+
"src",
|
|
57
|
+
"index.js",
|
|
58
|
+
"dist/src",
|
|
59
|
+
"dist/index.js"
|
|
60
|
+
],
|
|
61
|
+
"c8": {
|
|
62
|
+
"exclude": [
|
|
63
|
+
"dist",
|
|
64
|
+
"test",
|
|
65
|
+
".mocharc.js",
|
|
66
|
+
"babel.config.js"
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/Connector.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
class NotImplemented extends Error {
|
|
2
|
+
constructor(serviceId) {
|
|
3
|
+
super(`${serviceId} service function not found`);
|
|
4
|
+
this.code = 'EFLOW_NOT_IMPLEMENTED';
|
|
5
|
+
this.output = {statusCode: 501};
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function Connector(connectorId, io, activity, executionMessage) {
|
|
10
|
+
if (!(this instanceof Connector)) return new Connector(connectorId, io, activity, executionMessage);
|
|
11
|
+
this.connectorId = connectorId;
|
|
12
|
+
this.io = io;
|
|
13
|
+
this.activity = activity;
|
|
14
|
+
this.executionMessage = executionMessage;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
Connector.prototype.execute = async function execute(...args) {
|
|
18
|
+
const callback = args.pop();
|
|
19
|
+
args.push(formatCallback);
|
|
20
|
+
|
|
21
|
+
const activity = this.activity;
|
|
22
|
+
const io = this.io;
|
|
23
|
+
const environment = activity.environment;
|
|
24
|
+
const connectorId = this.connectorId;
|
|
25
|
+
const executionMessage = this.executionMessage;
|
|
26
|
+
|
|
27
|
+
const serviceFunction = environment.services[connectorId];
|
|
28
|
+
if (!serviceFunction) return callback(new NotImplemented(connectorId));
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const input = io && await io.getInput(activity, executionMessage);
|
|
32
|
+
await serviceFunction.call(activity, input, ...args);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
return callback(err);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function formatCallback(serviceErr, result) {
|
|
38
|
+
if (serviceErr) return callback(serviceErr);
|
|
39
|
+
if (!io?.output.length) return callback(null, result);
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const formattedResult = await io.getOutput(activity, {...executionMessage, ...result});
|
|
43
|
+
return callback(null, formattedResult);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
return callback(err);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
package/src/IO.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
export {
|
|
2
|
+
InputOutput,
|
|
3
|
+
IOBase,
|
|
4
|
+
IOMap,
|
|
5
|
+
IOList,
|
|
6
|
+
IOScript,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
class InputOutput {
|
|
10
|
+
constructor(parentId, behaviour, context) {
|
|
11
|
+
this.parentId = parentId;
|
|
12
|
+
this.context = context;
|
|
13
|
+
const {inputParameters, outputParameters} = behaviour;
|
|
14
|
+
this.input = this._map(parentId, inputParameters, 'input', context);
|
|
15
|
+
this.output = this._map(parentId, outputParameters, 'output', context);
|
|
16
|
+
}
|
|
17
|
+
async getInput(activity, executionMessage) {
|
|
18
|
+
const input = this.input;
|
|
19
|
+
const values = await Promise.all(input.map((parm) => parm.getValue(activity, executionMessage)));
|
|
20
|
+
return values.reduce((result, parm) => Object.assign(result, parm), {});
|
|
21
|
+
}
|
|
22
|
+
async getOutput(activity, executionMessage) {
|
|
23
|
+
const output = this.output;
|
|
24
|
+
const values = await Promise.all(output.map((parm) => parm.getValue(activity, executionMessage)));
|
|
25
|
+
return values.reduce((result, parm) => Object.assign(result, parm), {});
|
|
26
|
+
}
|
|
27
|
+
_map(parentId, list, ioType, context) {
|
|
28
|
+
const mapped = [];
|
|
29
|
+
if (!list) return mapped;
|
|
30
|
+
|
|
31
|
+
for (const parm of list) {
|
|
32
|
+
const definition = parm.definition;
|
|
33
|
+
const type = definition && definition.$type;
|
|
34
|
+
switch (type) {
|
|
35
|
+
case 'camunda:Map': {
|
|
36
|
+
mapped.push(new IOMap(parm));
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case 'camunda:List': {
|
|
40
|
+
mapped.push(new IOList(parm));
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
case 'camunda:Script': {
|
|
44
|
+
const id = `${parentId}/${ioType}/${type}/${parm.name}`;
|
|
45
|
+
const {scriptFormat, value, resource} = definition;
|
|
46
|
+
|
|
47
|
+
if (!value && !resource) break;
|
|
48
|
+
|
|
49
|
+
context.environment.scripts.register({
|
|
50
|
+
id,
|
|
51
|
+
type: parm.$type,
|
|
52
|
+
behaviour: {
|
|
53
|
+
scriptFormat,
|
|
54
|
+
...(value ? {script: value} : undefined),
|
|
55
|
+
...(resource ? {resource} : undefined),
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
mapped.push(new IOScript(parm, id));
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
default: {
|
|
63
|
+
mapped.push(new IOBase(parm));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return mapped;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
class IOBase {
|
|
73
|
+
constructor(parm) {
|
|
74
|
+
this.name = parm.name;
|
|
75
|
+
this.type = parm.definition && parm.definition.$type || 'string';
|
|
76
|
+
this.behaviour = parm;
|
|
77
|
+
}
|
|
78
|
+
getValue(activity, executionMessage) {
|
|
79
|
+
return {
|
|
80
|
+
[this.name]: activity.environment.resolveExpression(this.behaviour.value, executionMessage),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
class IOMap extends IOBase {
|
|
86
|
+
constructor(parm) {
|
|
87
|
+
super(parm);
|
|
88
|
+
}
|
|
89
|
+
getValue(activity, executionMessage) {
|
|
90
|
+
const name = this.name;
|
|
91
|
+
const entries = this.behaviour.definition.entries;
|
|
92
|
+
if (!Array.isArray(entries)) return {[name]: {}};
|
|
93
|
+
|
|
94
|
+
const environment = activity.environment;
|
|
95
|
+
|
|
96
|
+
const result = {};
|
|
97
|
+
for (const {key, value} of entries) {
|
|
98
|
+
if (!key) continue;
|
|
99
|
+
|
|
100
|
+
const val = environment.resolveExpression(value, executionMessage);
|
|
101
|
+
if (key in result) {
|
|
102
|
+
if (val === undefined) continue;
|
|
103
|
+
const current = result[key];
|
|
104
|
+
if (Array.isArray(current)) {
|
|
105
|
+
current.push(val);
|
|
106
|
+
} else {
|
|
107
|
+
const items = result[key] = [];
|
|
108
|
+
if (current !== undefined) items.push(current);
|
|
109
|
+
items.push(val);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
result[key] = val;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {[name]: result};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
class IOList extends IOBase {
|
|
121
|
+
constructor(parm) {
|
|
122
|
+
super(parm);
|
|
123
|
+
}
|
|
124
|
+
getValue(activity, executionMessage) {
|
|
125
|
+
const name = this.name;
|
|
126
|
+
const items = this.behaviour.definition?.items;
|
|
127
|
+
|
|
128
|
+
const result = [];
|
|
129
|
+
if (!Array.isArray(items)) return {[name]: result};
|
|
130
|
+
|
|
131
|
+
const environment = activity.environment;
|
|
132
|
+
for (const item of items) {
|
|
133
|
+
const val = environment.resolveExpression(item.value, executionMessage);
|
|
134
|
+
if (val !== undefined) result.push(val);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
[name]: result,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
class IOScript extends IOBase {
|
|
144
|
+
constructor(parm, scriptId) {
|
|
145
|
+
super(parm);
|
|
146
|
+
this.id = scriptId;
|
|
147
|
+
this.script = true;
|
|
148
|
+
}
|
|
149
|
+
getValue(activity, executionMessage) {
|
|
150
|
+
const definition = this.behaviour.definition;
|
|
151
|
+
const name = this.name;
|
|
152
|
+
const environment = activity.environment;
|
|
153
|
+
|
|
154
|
+
const {fields, content, properties, ...rest} = executionMessage;
|
|
155
|
+
const scope = {
|
|
156
|
+
id: this.scriptId,
|
|
157
|
+
type: this.type,
|
|
158
|
+
name,
|
|
159
|
+
fields,
|
|
160
|
+
content,
|
|
161
|
+
properties,
|
|
162
|
+
environment: activity.environment,
|
|
163
|
+
logger: activity.logger,
|
|
164
|
+
resolveExpression,
|
|
165
|
+
...rest,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const script = environment.scripts.getScript(definition.scriptFormat, {id: this.id});
|
|
169
|
+
|
|
170
|
+
return new Promise((resolve, reject) => {
|
|
171
|
+
return script.execute(scope, (err, result) => {
|
|
172
|
+
if (err) return reject(err);
|
|
173
|
+
resolve({[name]: result});
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
function resolveExpression(expression) {
|
|
178
|
+
return environment.resolveExpression(expression, scope);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
package/src/IOForm.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import IOProperties from './IOProperties.js';
|
|
2
|
+
|
|
3
|
+
export default class IOForm {
|
|
4
|
+
constructor(activity, behaviour) {
|
|
5
|
+
this.activity = activity;
|
|
6
|
+
this.behaviour = behaviour;
|
|
7
|
+
}
|
|
8
|
+
resolve(elementApi) {
|
|
9
|
+
const form = {};
|
|
10
|
+
for (const field of this.behaviour.fields) {
|
|
11
|
+
const f = form[field.id] = {
|
|
12
|
+
...field,
|
|
13
|
+
...(field.label && {defaultValue: elementApi.resolveExpression(field.label)}),
|
|
14
|
+
...(field.defaultValue && {defaultValue: elementApi.resolveExpression(field.defaultValue)}),
|
|
15
|
+
};
|
|
16
|
+
if (f.properties) {
|
|
17
|
+
f.properties = new IOProperties(this.activity, f.properties).resolve(elementApi);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return form;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default class IOProperties {
|
|
2
|
+
constructor(activity, behaviour) {
|
|
3
|
+
this.activity = activity;
|
|
4
|
+
this.behaviour = behaviour;
|
|
5
|
+
}
|
|
6
|
+
resolve(elementApi) {
|
|
7
|
+
const properties = {};
|
|
8
|
+
|
|
9
|
+
for (const {id, name, value} of this.behaviour.values) {
|
|
10
|
+
properties[id || name] = elementApi.resolveExpression(value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return properties;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export default function ServiceExpression(activity) {
|
|
2
|
+
if (!(this instanceof ServiceExpression)) return new ServiceExpression(activity);
|
|
3
|
+
this.activity = activity;
|
|
4
|
+
this.type = `${activity.type}:expression`;
|
|
5
|
+
this.expression = activity.behaviour.expression;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
ServiceExpression.prototype.execute = function execute(executionMessage, callback) {
|
|
9
|
+
const serviceFn = this.activity.environment.resolveExpression(this.expression, executionMessage);
|
|
10
|
+
serviceFn.call(this.activity, executionMessage, (err, result) => {
|
|
11
|
+
callback(err, result);
|
|
12
|
+
});
|
|
13
|
+
};
|