@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/src/IO.js CHANGED
@@ -1,75 +1,4 @@
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}),
55
- ...(resource && {resource}),
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 {
1
+ export class IOBase {
73
2
  constructor(parm) {
74
3
  this.name = parm.name;
75
4
  this.type = parm.definition && parm.definition.$type || 'string';
@@ -117,7 +46,7 @@ class IOMap extends IOBase {
117
46
  }
118
47
  }
119
48
 
120
- class IOList extends IOBase {
49
+ export class IOList extends IOBase {
121
50
  constructor(parm) {
122
51
  super(parm);
123
52
  }
@@ -140,7 +69,7 @@ class IOList extends IOBase {
140
69
  }
141
70
  }
142
71
 
143
- class IOScript extends IOBase {
72
+ export class IOScript extends IOBase {
144
73
  constructor(parm, scriptId) {
145
74
  super(parm);
146
75
  this.id = scriptId;
@@ -153,7 +82,7 @@ class IOScript extends IOBase {
153
82
 
154
83
  const {fields, content, properties, ...rest} = executionMessage;
155
84
  const scope = {
156
- id: this.scriptId,
85
+ id: this.id,
157
86
  type: this.type,
158
87
  name,
159
88
  fields,
@@ -179,3 +108,66 @@ class IOScript extends IOBase {
179
108
  }
180
109
  }
181
110
  }
111
+
112
+ export class InputOutput {
113
+ constructor(parentId, behaviour, context) {
114
+ this.parentId = parentId;
115
+ this.context = context;
116
+ const {inputParameters, outputParameters} = behaviour;
117
+ this.input = this._map(parentId, inputParameters, 'input', context);
118
+ this.output = this._map(parentId, outputParameters, 'output', context);
119
+ }
120
+ async getInput(activity, executionMessage) {
121
+ const input = this.input;
122
+ const values = await Promise.all(input.map((parm) => parm.getValue(activity, executionMessage)));
123
+ return values.reduce((result, parm) => Object.assign(result, parm), {});
124
+ }
125
+ async getOutput(activity, executionMessage) {
126
+ const output = this.output;
127
+ const values = await Promise.all(output.map((parm) => parm.getValue(activity, executionMessage)));
128
+ return values.reduce((result, parm) => Object.assign(result, parm), {});
129
+ }
130
+ _map(parentId, list, ioType, context) {
131
+ const mapped = [];
132
+ if (!list) return mapped;
133
+
134
+ for (const parm of list) {
135
+ const definition = parm.definition;
136
+ const type = definition && definition.$type;
137
+ switch (type) {
138
+ case 'camunda:Map': {
139
+ mapped.push(new IOMap(parm));
140
+ break;
141
+ }
142
+ case 'camunda:List': {
143
+ mapped.push(new IOList(parm));
144
+ break;
145
+ }
146
+ case 'camunda:Script': {
147
+ const id = `${parentId}/${ioType}/${type}/${parm.name}`;
148
+ const {scriptFormat, value, resource} = definition;
149
+
150
+ if (!value && !resource) break;
151
+
152
+ context.environment.scripts.register({
153
+ id,
154
+ type: parm.$type,
155
+ behaviour: {
156
+ scriptFormat,
157
+ ...(value && {script: value}),
158
+ ...(resource && {resource}),
159
+ },
160
+ });
161
+
162
+ mapped.push(new IOScript(parm, id));
163
+ break;
164
+ }
165
+ default: {
166
+ mapped.push(new IOBase(parm));
167
+ }
168
+ }
169
+ }
170
+
171
+ return mapped;
172
+ }
173
+ }
@@ -0,0 +1,150 @@
1
+ import {getExtensions} from './getExtensions.js';
2
+
3
+ export class OnifyElementExtensions {
4
+ constructor(activity, context) {
5
+ this.activity = activity;
6
+ this.context = context;
7
+ const {Service} = this.extensions = getExtensions(activity, context);
8
+ if (Service) {
9
+ activity.behaviour.Service = Service;
10
+ }
11
+ }
12
+ activate(message) {
13
+ const activity = this.activity;
14
+ const broker = activity.broker;
15
+ const formatQ = activity.broker.getQueue('format-run-q');
16
+
17
+ if (message.fields.redelivered && message.fields.routingKey === 'run.start') {
18
+ activity.on('start', (elementApi) => {
19
+ this._formatOnEnter(broker, formatQ, elementApi);
20
+ }, {consumerTag: '_onify-extension-on-enter'});
21
+ } else {
22
+ activity.on('enter', (elementApi) => {
23
+ this._formatOnEnter(broker, formatQ, elementApi);
24
+ }, {consumerTag: '_onify-extension-on-enter'});
25
+ }
26
+
27
+ activity.on('activity.execution.completed', async (elementApi) => {
28
+ if (activity.isSubProcess && activity.id !== elementApi.id) return;
29
+
30
+ formatQ.queueMessage({routingKey: 'run.end.format'}, {endRoutingKey: 'run.end.complete'}, {persistent: false});
31
+
32
+ try {
33
+ var format = await this._onExecuted(elementApi);
34
+ } catch (err) {
35
+ return broker.publish('format', 'run.end.error', {error: err}, {persistent: false});
36
+ }
37
+
38
+ broker.publish('format', 'run.end.complete', {...format}, {persistent: false});
39
+ }, {consumerTag: '_onify-extension-on-executed'});
40
+
41
+ const executionListeners = this.extensions.listeners;
42
+ if (executionListeners?.onStart) {
43
+ activity.on('start', async (elementApi) => {
44
+ if (activity.isSubProcess && activity.id !== elementApi.id) return;
45
+
46
+ formatQ.queueMessage({routingKey: 'run.listener.start'}, {endRoutingKey: 'run.listener.start.complete'}, {persistent: false});
47
+
48
+ try {
49
+ var format = await executionListeners.execute('start', elementApi);
50
+ } catch (err) {
51
+ return broker.publish('format', 'run.listener.start.error', {error: err}, {persistent: false});
52
+ }
53
+
54
+ broker.publish('format', 'run.listener.start.complete', {...format}, {persistent: false});
55
+ }, {consumerTag: '_onify-extension-on-listenerstart'});
56
+ }
57
+ if (executionListeners?.onEnd) {
58
+ activity.on('end', async (elementApi) => {
59
+ if (activity.isSubProcess && activity.id !== elementApi.id) return;
60
+
61
+ formatQ.queueMessage({routingKey: 'run.listener.end'}, {endRoutingKey: 'run.listener.end.complete'}, {persistent: false});
62
+
63
+ try {
64
+ var format = await executionListeners.execute('end', elementApi);
65
+ } catch (err) {
66
+ return broker.publish('format', 'run.listener.end.error', {error: err}, {persistent: false});
67
+ }
68
+
69
+ broker.publish('format', 'run.listener.end.complete', {...format}, {persistent: false});
70
+ }, {consumerTag: '_onify-extension-on-listenerend'});
71
+ }
72
+ }
73
+ deactivate() {
74
+ const broker = this.activity.broker;
75
+ broker.cancel('_onify-extension-on-enter');
76
+ broker.cancel('_onify-extension-on-executed');
77
+ broker.cancel('_onify-extension-on-listenerstart');
78
+ broker.cancel('_onify-extension-on-listenerend');
79
+ }
80
+ async _formatOnEnter(broker, formatQ, elementApi) {
81
+ if (this.activity.isSubProcess && this.activity.id !== elementApi.id) return;
82
+
83
+ formatQ.queueMessage({routingKey: 'run.enter.format'}, {endRoutingKey: 'run.enter.complete'}, {persistent: false});
84
+
85
+ try {
86
+ var format = await this._onEnter(elementApi);
87
+ } catch (err) {
88
+ return broker.publish('format', 'run.enter.error', {error: err}, {persistent: false});
89
+ }
90
+
91
+ broker.publish('format', 'run.enter.complete', format, {persistent: false});
92
+ }
93
+ async _onEnter(elementApi) {
94
+ const {format, properties, io, form} = this.extensions;
95
+
96
+ const result = {};
97
+ Object.assign(result, format.resolve(elementApi));
98
+ Object.assign(elementApi.content, result);
99
+
100
+ if (properties) {
101
+ Object.assign(result, {properties: properties.resolve(elementApi)});
102
+ Object.assign(elementApi.content, result);
103
+ }
104
+
105
+ if (io) {
106
+ if (io.input?.length) {
107
+ const input = await io.getInput(this.activity, elementApi);
108
+ Object.assign(result, {input});
109
+ Object.assign(elementApi.content, result);
110
+ }
111
+ if (io.output?.length) {
112
+ result.assignToOutput = true;
113
+ }
114
+ }
115
+
116
+ if (form) {
117
+ Object.assign(result, {form: form.resolve(elementApi)});
118
+ }
119
+
120
+ return result;
121
+ }
122
+ async _onExecuted(elementApi) {
123
+ const {properties, io} = this.extensions;
124
+
125
+ const result = {};
126
+ if (io?.output.length) {
127
+ const output = await io.getOutput(this.activity, elementApi);
128
+ Object.assign(result, {output});
129
+ Object.assign(elementApi.content, {output});
130
+ }
131
+
132
+ if (properties) {
133
+ const props = properties.resolve(elementApi);
134
+ Object.assign(result, {properties: props});
135
+ Object.assign(elementApi.content, {properties: props});
136
+ }
137
+
138
+ const {output, assignToOutput, resultVariable} = elementApi.content;
139
+
140
+ if (output === undefined || output === null) return result;
141
+
142
+ if (assignToOutput && typeof output === 'object') {
143
+ Object.assign(this.activity.environment.output, output);
144
+ } else if (resultVariable) {
145
+ elementApi.environment.output[resultVariable] = output;
146
+ }
147
+
148
+ return result;
149
+ }
150
+ }
@@ -0,0 +1,40 @@
1
+ import {FormatError} from './Errors.js';
2
+ import {getExtensions} from './getExtensions.js';
3
+
4
+ export class OnifyProcessExtensions {
5
+ constructor(bp, context) {
6
+ this.process = bp;
7
+ this.context = context;
8
+ this.extensions = getExtensions(bp, context);
9
+ this._activate();
10
+ }
11
+ _activate() {
12
+ const bp = this.process;
13
+ bp.on('process.enter', (elementApi) => {
14
+ try {
15
+ const result = this._onEnter(elementApi);
16
+ const environment = elementApi.environment;
17
+ environment.assignVariables(result);
18
+ } catch (err) {
19
+ elementApi.broker.publish('event', 'process.error', {
20
+ ...elementApi.content,
21
+ error: new FormatError(bp.id, err),
22
+ }, {mandatory: true, type: 'error'});
23
+ }
24
+ }, {consumerTag: '_onify-extension-on-enter'});
25
+ }
26
+ _onEnter(elementApi) {
27
+ const {format, properties} = this.extensions;
28
+ const result = {};
29
+
30
+ Object.assign(result, format.resolve(elementApi));
31
+ Object.assign(elementApi.content, result);
32
+
33
+ if (properties) {
34
+ Object.assign(result, properties.resolve(elementApi));
35
+ Object.assign(elementApi.content, result);
36
+ }
37
+
38
+ return result;
39
+ }
40
+ }
@@ -1,4 +1,4 @@
1
- import {NotImplemented} from './Errors';
1
+ import {NotImplemented} from './Errors.js';
2
2
 
3
3
  export default function ServiceExpression(activity) {
4
4
  if (!(this instanceof ServiceExpression)) return new ServiceExpression(activity);
@@ -0,0 +1,97 @@
1
+ import cronParser from 'cron-parser';
2
+
3
+ const iso8601cycle = /^\s*(R\d+\/)?P\w+/i;
4
+
5
+ export class FormatActivity {
6
+ constructor(activity) {
7
+ this.activity = activity;
8
+ this.resultVariable = activity.behaviour.resultVariable;
9
+
10
+ let timeCycles;
11
+ if (activity.eventDefinitions) {
12
+ for (const ed of activity.eventDefinitions.filter((e) => e.type === 'bpmn:TimerEventDefinition')) {
13
+ if (!('timeCycle' in ed)) continue;
14
+ timeCycles = timeCycles || [];
15
+ timeCycles.push(ed.timeCycle);
16
+ }
17
+ }
18
+ this.timeCycles = timeCycles;
19
+ }
20
+ resolve(elementApi) {
21
+ let user, groups, assigneeValue, description;
22
+ const activity = this.activity;
23
+ const {
24
+ documentation,
25
+ candidateUsers,
26
+ candidateGroups,
27
+ scheduledStart,
28
+ assignee,
29
+ } = activity.behaviour;
30
+
31
+ if (candidateUsers) user = resolveAndSplit(elementApi, candidateUsers);
32
+ if (candidateGroups) groups = resolveAndSplit(elementApi, candidateGroups);
33
+ if (assignee) assigneeValue = elementApi.resolveExpression(assignee);
34
+ if (documentation) description = documentation[0]?.text;
35
+
36
+ let expireAt;
37
+ const timeCycles = this.timeCycles;
38
+ if (timeCycles) {
39
+ for (const cycle of timeCycles) {
40
+ const cron = elementApi.resolveExpression(cycle);
41
+ if (!cron || iso8601cycle.test(cron)) continue;
42
+
43
+ const expireAtDt = cronParser.parseExpression(cron).next().toDate();
44
+ if (!expireAt || expireAtDt < expireAt) expireAt = expireAtDt;
45
+ }
46
+ }
47
+
48
+ return {
49
+ ...(this.resultVariable && {resultVariable: this.resultVariable}),
50
+ ...(scheduledStart && activity.parent.type === 'bpmn:Process' && {scheduledStart}),
51
+ ...(user?.length && {candidateUsers: user}),
52
+ ...(groups?.length && {candidateGroups: groups}),
53
+ ...(!elementApi.content.description && description && {description: elementApi.resolveExpression(description)}),
54
+ ...(expireAt && {expireAt}),
55
+ ...(assigneeValue && {assignee: assigneeValue}),
56
+ };
57
+ }
58
+ }
59
+
60
+ export class FormatProcess {
61
+ constructor(bp) {
62
+ this.process = bp;
63
+ }
64
+ resolve(elementApi) {
65
+ let user, groups, description;
66
+ const bp = this.process;
67
+ const {
68
+ documentation,
69
+ candidateStarterUsers,
70
+ candidateStarterGroups,
71
+ } = bp.behaviour;
72
+
73
+ if (candidateStarterUsers) user = resolveAndSplit(elementApi, candidateStarterUsers);
74
+ if (candidateStarterGroups) groups = resolveAndSplit(elementApi, candidateStarterGroups);
75
+ if (documentation) description = documentation[0]?.text;
76
+
77
+ return {
78
+ ...(user?.length && {candidateStarterUsers: user}),
79
+ ...(groups?.length && {candidateStarterGroups: groups}),
80
+ ...(!elementApi.content.description && description && {description: elementApi.resolveExpression(description)}),
81
+ };
82
+ }
83
+ }
84
+
85
+ function resolveAndSplit(elementApi, str) {
86
+ if (Array.isArray(str)) return str.filter(Boolean);
87
+ if (typeof str !== 'string') return;
88
+
89
+ const resolved = elementApi.resolveExpression(str);
90
+ if (Array.isArray(resolved)) return resolved.filter(Boolean);
91
+ if (typeof resolved !== 'string') return;
92
+
93
+ return resolved
94
+ .split(',')
95
+ .map((g) => g.trim && g.trim().toLowerCase())
96
+ .filter(Boolean);
97
+ }
@@ -0,0 +1,49 @@
1
+ import {FormatActivity, FormatProcess} from './formatters.js';
2
+ import {InputOutput} from './IO.js';
3
+ import Connector from './Connector.js';
4
+ import ExecutionListeners from './ExecutionListeners.js';
5
+ import IOForm from './IOForm.js';
6
+ import IOProperties from './IOProperties.js';
7
+ import ServiceExpression from './ServiceExpression.js';
8
+
9
+ export function getExtensions(element, context) {
10
+ const result = {
11
+ format: element.type === 'bpmn:Process' ? new FormatProcess(element) : new FormatActivity(element),
12
+ };
13
+
14
+ const expression = element.behaviour.expression;
15
+ if (expression) result.Service = ServiceExpression;
16
+
17
+ const extensions = element.behaviour.extensionElements?.values;
18
+ if (!extensions) return result;
19
+
20
+ const listeners = new ExecutionListeners(element, context);
21
+
22
+ let listener = 0;
23
+ for (const ext of extensions) {
24
+ switch (ext.$type) {
25
+ case 'camunda:Properties':
26
+ if (ext.values?.length) result.properties = new IOProperties(element, ext);
27
+ break;
28
+ case 'camunda:InputOutput':
29
+ result.io = new InputOutput(element.id, ext, context);
30
+ break;
31
+ case 'camunda:FormData':
32
+ if (ext.fields?.length) result.form = new IOForm(element, ext);
33
+ break;
34
+ case 'camunda:Connector': {
35
+ const {connectorId, inputOutput} = ext;
36
+ const io = inputOutput && new InputOutput(`${element.id}/${ext.$type.toLowerCase()}`, inputOutput, context);
37
+ result.Service = Connector.bind(Connector, connectorId, io);
38
+ break;
39
+ }
40
+ case 'camunda:ExecutionListener':
41
+ listeners.add(ext, listener++);
42
+ break;
43
+ }
44
+ }
45
+
46
+ if (listeners.length) result.listeners = listeners;
47
+
48
+ return result;
49
+ }