@0dep/bpmn-extensions 0.0.2 → 0.0.4
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/README.md +18 -3
- package/dist/index.js +186 -64
- package/package.json +12 -4
- package/src/ElementExtensions.js +53 -16
- package/src/Expressions.js +5 -16
- package/src/ProcessExtensions.js +7 -0
- package/src/SubProcessExtensions.js +5 -5
- package/src/extensions/FeelScripts.js +11 -3
- package/src/extensions/Form.js +2 -2
- package/src/extensions/IoMapping.js +3 -3
- package/src/extensions/LoopCharacteristics.js +1 -1
- package/src/extensions/Properties.js +2 -2
- package/src/extensions/Subscription.js +30 -0
- package/src/extensions/TaskDefinition.js +8 -2
- package/src/extensions/formatters.js +17 -4
- package/src/feel.js +18 -3
- package/src/getExtensions.js +32 -13
- package/src/index.js +3 -3
- package/types/index.d.ts +110 -48
- package/types/index.d.ts.map +7 -3
package/src/ElementExtensions.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { getExtensions } from './getExtensions.js';
|
|
2
|
+
import { getFeelScope } from './feel.js';
|
|
2
3
|
|
|
3
4
|
const SUBPROCESS_TYPES = new Set(['bpmn:SubProcess', 'bpmn:AdHocSubProcess', 'bpmn:Transaction']);
|
|
4
5
|
|
|
6
|
+
// Consumer tags shared between activate (here and in the SubProcessExtensions override) and
|
|
7
|
+
// deactivate — deactivate cancels by tag, so both must subscribe with these exact tags.
|
|
8
|
+
export const ON_ENTER_TAG = '0dep-bpmn-extensions:on-enter';
|
|
9
|
+
export const ON_EXECUTED_TAG = '0dep-bpmn-extensions:on-executed';
|
|
10
|
+
export const ON_START_TAG = '0dep-bpmn-extensions:on-start';
|
|
11
|
+
export const ON_END_TAG = '0dep-bpmn-extensions:on-end';
|
|
12
|
+
|
|
5
13
|
/**
|
|
6
14
|
* Activity-level extensions.
|
|
7
15
|
*
|
|
@@ -10,43 +18,55 @@ const SUBPROCESS_TYPES = new Set(['bpmn:SubProcess', 'bpmn:AdHocSubProcess', 'bp
|
|
|
10
18
|
* proceeding — the same mechanism bpmn-elements uses for extension formatting.
|
|
11
19
|
*/
|
|
12
20
|
export class ElementExtensions {
|
|
13
|
-
|
|
21
|
+
/**
|
|
22
|
+
* @param {import('bpmn-elements').Activity} activity
|
|
23
|
+
* @param {import('bpmn-elements').ContextInstance} context
|
|
24
|
+
*/
|
|
25
|
+
constructor(activity, context) {
|
|
14
26
|
this.activity = activity;
|
|
15
27
|
this.formatQ = activity.broker.getQueue('format-run-q');
|
|
16
28
|
|
|
17
|
-
|
|
29
|
+
/** @type {import('./getExtensions.js').ExtensionHandlers} */
|
|
30
|
+
this.extensions = getExtensions(activity, context);
|
|
18
31
|
const Service = this.extensions.Service;
|
|
19
32
|
if (Service) activity.behaviour.Service = Service;
|
|
20
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Activate extensions
|
|
36
|
+
* @param {import('bpmn-elements').ElementBrokerMessage} message
|
|
37
|
+
*/
|
|
21
38
|
activate(message) {
|
|
22
39
|
const activity = this.activity;
|
|
23
40
|
const listeners = this.extensions.listeners;
|
|
24
41
|
|
|
25
42
|
if (message.fields.redelivered && message.fields.routingKey === 'run.start') {
|
|
26
|
-
activity.on('start', (api) => this._onEnter(api), { consumerTag:
|
|
43
|
+
activity.on('start', (api) => this._onEnter(api), { consumerTag: ON_ENTER_TAG });
|
|
27
44
|
} else {
|
|
28
|
-
activity.on('enter', (api) => this._onEnter(api), { consumerTag:
|
|
45
|
+
activity.on('enter', (api) => this._onEnter(api), { consumerTag: ON_ENTER_TAG });
|
|
29
46
|
}
|
|
30
47
|
|
|
31
48
|
activity.on('activity.execution.completed', (api) => this._onExecuted(api), {
|
|
32
|
-
consumerTag:
|
|
49
|
+
consumerTag: ON_EXECUTED_TAG,
|
|
33
50
|
});
|
|
34
51
|
|
|
35
52
|
if (listeners?.onStart) {
|
|
36
|
-
activity.on('start', (api) => this._onListener('start', api), { consumerTag:
|
|
53
|
+
activity.on('start', (api) => this._onListener('start', api), { consumerTag: ON_START_TAG });
|
|
37
54
|
}
|
|
38
55
|
if (listeners?.onEnd) {
|
|
39
|
-
activity.on('end', (api) => this._onListener('end', api), { consumerTag:
|
|
56
|
+
activity.on('end', (api) => this._onListener('end', api), { consumerTag: ON_END_TAG });
|
|
40
57
|
}
|
|
41
58
|
}
|
|
42
59
|
deactivate() {
|
|
43
60
|
const broker = this.activity.broker;
|
|
44
|
-
broker.cancel(
|
|
45
|
-
broker.cancel(
|
|
46
|
-
broker.cancel(
|
|
47
|
-
broker.cancel(
|
|
61
|
+
broker.cancel(ON_ENTER_TAG);
|
|
62
|
+
broker.cancel(ON_EXECUTED_TAG);
|
|
63
|
+
broker.cancel(ON_START_TAG);
|
|
64
|
+
broker.cancel(ON_END_TAG);
|
|
48
65
|
}
|
|
49
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* @internal Shared with SubProcessExtensions.
|
|
68
|
+
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Activity>} elementApi
|
|
69
|
+
*/
|
|
50
70
|
async _onEnter(elementApi) {
|
|
51
71
|
this.formatQ.queueMessage({ routingKey: 'run.enter.format' }, { endRoutingKey: 'run.enter.complete' }, { persistent: false });
|
|
52
72
|
try {
|
|
@@ -56,7 +76,10 @@ export class ElementExtensions {
|
|
|
56
76
|
elementApi.broker.publish('format', 'run.enter.error', { error: err }, { persistent: false });
|
|
57
77
|
}
|
|
58
78
|
}
|
|
59
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* @internal Shared with SubProcessExtensions.
|
|
81
|
+
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Activity>} elementApi
|
|
82
|
+
*/
|
|
60
83
|
async _onExecuted(elementApi) {
|
|
61
84
|
this.formatQ.queueMessage({ routingKey: 'run.end.format' }, { endRoutingKey: 'run.end.complete' }, { persistent: false });
|
|
62
85
|
try {
|
|
@@ -66,7 +89,10 @@ export class ElementExtensions {
|
|
|
66
89
|
elementApi.broker.publish('format', 'run.end.error', { error: err }, { persistent: false });
|
|
67
90
|
}
|
|
68
91
|
}
|
|
69
|
-
/**
|
|
92
|
+
/**
|
|
93
|
+
* @internal Shared with SubProcessExtensions.
|
|
94
|
+
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Activity>} elementApi
|
|
95
|
+
*/
|
|
70
96
|
async _onListener(eventType, elementApi) {
|
|
71
97
|
const routingKey = `run.listener.${eventType}`;
|
|
72
98
|
this.formatQ.queueMessage({ routingKey }, { endRoutingKey: `${routingKey}.complete` }, { persistent: false });
|
|
@@ -78,13 +104,17 @@ export class ElementExtensions {
|
|
|
78
104
|
elementApi.broker.publish('format', `${routingKey}.error`, { error: err }, { persistent: false });
|
|
79
105
|
}
|
|
80
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Activity>} elementApi
|
|
109
|
+
*/
|
|
81
110
|
#formatOnEnter(elementApi) {
|
|
82
|
-
const { format, io, headers, properties, form } = this.extensions;
|
|
111
|
+
const { format, io, headers, properties, form, subscription } = this.extensions;
|
|
83
112
|
const result = { ...format.resolve(elementApi) };
|
|
84
113
|
|
|
85
114
|
if (headers) result.headers = headers.resolve();
|
|
86
115
|
if (properties) result.properties = properties.resolve(elementApi);
|
|
87
116
|
if (form) result.form = form.resolve(elementApi);
|
|
117
|
+
if (subscription) result.subscription = subscription.resolve(elementApi);
|
|
88
118
|
if (io?.hasInput) {
|
|
89
119
|
result.input = io.getInput(elementApi);
|
|
90
120
|
// A (sub) process input mapping creates local variables visible to its children; a job
|
|
@@ -96,13 +126,16 @@ export class ElementExtensions {
|
|
|
96
126
|
Object.assign(elementApi.content, result);
|
|
97
127
|
return result;
|
|
98
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Activity>} elementApi
|
|
131
|
+
*/
|
|
99
132
|
#formatOnExecuted(elementApi) {
|
|
100
133
|
const { io, script, calledDecision, loop } = this.extensions;
|
|
101
134
|
let jobResult = elementApi.content.output;
|
|
102
135
|
|
|
103
136
|
// Multi-instance: aggregate the per-instance outputs into the `outputCollection` array.
|
|
104
137
|
if (loop?.hasOutputCollection) {
|
|
105
|
-
const output = { [loop.outputCollection]: loop.aggregate(jobResult, elementApi.environment
|
|
138
|
+
const output = { [loop.outputCollection]: loop.aggregate(jobResult, getFeelScope(elementApi.environment)) };
|
|
106
139
|
this.#assignOutput(elementApi, output);
|
|
107
140
|
return { output };
|
|
108
141
|
}
|
|
@@ -132,6 +165,10 @@ export class ElementExtensions {
|
|
|
132
165
|
}
|
|
133
166
|
return {};
|
|
134
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Activity>} elementApi
|
|
170
|
+
* @param {any} output
|
|
171
|
+
*/
|
|
135
172
|
#assignOutput(elementApi, output) {
|
|
136
173
|
const environment = this.activity.environment;
|
|
137
174
|
// Visible to downstream FEEL within the process, and surfaced as process output
|
package/src/Expressions.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isFeelExpression, evaluateFeel } from './feel.js';
|
|
1
|
+
import { isFeelExpression, evaluateFeel, getFeelScope } from './feel.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* FEEL-aware expressions implementation for bpmn-elements.
|
|
@@ -7,7 +7,7 @@ import { isFeelExpression, evaluateFeel } from './feel.js';
|
|
|
7
7
|
* definition resolves FEEL expressions (`= ...`) instead of the default
|
|
8
8
|
* `${...}` template expressions.
|
|
9
9
|
*
|
|
10
|
-
* @returns {import('bpmn-elements').IExpressions}
|
|
10
|
+
* @returns {import('bpmn-elements').IExpressions & {isExpression: (text:string) => boolean, hasExpression: (text:string) => boolean}}
|
|
11
11
|
*/
|
|
12
12
|
export function FeelExpressions() {
|
|
13
13
|
return {
|
|
@@ -27,23 +27,12 @@ function hasExpression(text) {
|
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Resolve a templated string. Static literals are returned as-is; FEEL expressions are
|
|
30
|
-
* evaluated
|
|
30
|
+
* evaluated in the environment scope (see `getFeelScope`), overlaid with any element-local
|
|
31
|
+
* variables carried on the message content (e.g. a multi-instance item).
|
|
31
32
|
* @param {string} templatedString
|
|
32
33
|
* @param {{ environment?: import('bpmn-elements').Environment, content?: any }} [context]
|
|
33
34
|
*/
|
|
34
35
|
function resolveExpression(templatedString, context) {
|
|
35
36
|
if (!isFeelExpression(templatedString)) return templatedString;
|
|
36
|
-
return evaluateFeel(templatedString,
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Build the FEEL variable scope from a resolution context. Process/definition variables form
|
|
41
|
-
* the base scope, overlaid with any local variables carried on the element message content
|
|
42
|
-
* (e.g. a multi-instance item).
|
|
43
|
-
*/
|
|
44
|
-
function getScope(context) {
|
|
45
|
-
const environment = context && context.environment;
|
|
46
|
-
const variables = environment ? environment.variables : {};
|
|
47
|
-
const local = context && context.content && context.content.variables;
|
|
48
|
-
return { ...variables, ...local };
|
|
37
|
+
return evaluateFeel(templatedString, getFeelScope(context?.environment, context?.content?.variables));
|
|
49
38
|
}
|
package/src/ProcessExtensions.js
CHANGED
|
@@ -6,8 +6,12 @@ import { getExtensions } from './getExtensions.js';
|
|
|
6
6
|
* the result to the process variables.
|
|
7
7
|
*/
|
|
8
8
|
export class ProcessExtensions {
|
|
9
|
+
/**
|
|
10
|
+
* @param {import('bpmn-elements').Process} bp
|
|
11
|
+
*/
|
|
9
12
|
constructor(bp) {
|
|
10
13
|
this.process = bp;
|
|
14
|
+
/** @type {import('./getExtensions.js').ExtensionHandlers} */
|
|
11
15
|
this.extensions = getExtensions(bp);
|
|
12
16
|
}
|
|
13
17
|
activate() {
|
|
@@ -21,6 +25,9 @@ export class ProcessExtensions {
|
|
|
21
25
|
deactivate() {
|
|
22
26
|
this.process.broker.cancel('0dep-bpmn-extensions:on-enter');
|
|
23
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Process>} elementApi
|
|
30
|
+
*/
|
|
24
31
|
#onEnter(elementApi) {
|
|
25
32
|
try {
|
|
26
33
|
// bpmn-elements (>=18.0.4) forwards a call activity's formatted input — its io mapping plus,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ElementExtensions } from './ElementExtensions.js';
|
|
1
|
+
import { ElementExtensions, ON_ENTER_TAG, ON_EXECUTED_TAG, ON_START_TAG, ON_END_TAG } from './ElementExtensions.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Sub-process extensions.
|
|
@@ -14,14 +14,14 @@ export class SubProcessExtensions extends ElementExtensions {
|
|
|
14
14
|
const listeners = this.extensions.listeners;
|
|
15
15
|
const enterKey = message.fields.redelivered && message.fields.routingKey === 'run.start' ? 'activity.start' : 'activity.enter';
|
|
16
16
|
|
|
17
|
-
this.#subscribe(enterKey, (api) => this._onEnter(api),
|
|
18
|
-
this.#subscribe('activity.execution.completed', (api) => this._onExecuted(api),
|
|
17
|
+
this.#subscribe(enterKey, (api) => this._onEnter(api), ON_ENTER_TAG);
|
|
18
|
+
this.#subscribe('activity.execution.completed', (api) => this._onExecuted(api), ON_EXECUTED_TAG);
|
|
19
19
|
|
|
20
20
|
if (listeners?.onStart) {
|
|
21
|
-
this.#subscribe('activity.start', (api) => this._onListener('start', api),
|
|
21
|
+
this.#subscribe('activity.start', (api) => this._onListener('start', api), ON_START_TAG);
|
|
22
22
|
}
|
|
23
23
|
if (listeners?.onEnd) {
|
|
24
|
-
this.#subscribe('activity.end', (api) => this._onListener('end', api),
|
|
24
|
+
this.#subscribe('activity.end', (api) => this._onListener('end', api), ON_END_TAG);
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
#subscribe(routingKey, callback, consumerTag) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isFeelExpression, evaluateFeel } from '../feel.js';
|
|
1
|
+
import { isFeelExpression, evaluateFeel, getFeelScope } from '../feel.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* A bpmn-elements `scripts` implementation for script tasks.
|
|
@@ -16,7 +16,15 @@ export function FeelScripts() {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
class FeelScriptRegistry {
|
|
19
|
-
register() {
|
|
19
|
+
register() {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} _scriptFormat
|
|
24
|
+
* @param {{ id: string, [x: string]: any }} activity
|
|
25
|
+
* @returns {import('bpmn-elements').Script | undefined} the FEEL script, or undefined when
|
|
26
|
+
* the activity has no `zeebe:script`
|
|
27
|
+
*/
|
|
20
28
|
getScript(_scriptFormat, activity) {
|
|
21
29
|
const expression = getScriptExpression(activity);
|
|
22
30
|
if (expression === undefined) return undefined;
|
|
@@ -30,7 +38,7 @@ class FeelScript {
|
|
|
30
38
|
}
|
|
31
39
|
execute(scope, callback) {
|
|
32
40
|
try {
|
|
33
|
-
const value = isFeelExpression(this.expression) ? evaluateFeel(this.expression,
|
|
41
|
+
const value = isFeelExpression(this.expression) ? evaluateFeel(this.expression, getFeelScope(scope.environment)) : this.expression;
|
|
34
42
|
callback(null, value);
|
|
35
43
|
} catch (err) {
|
|
36
44
|
callback(err);
|
package/src/extensions/Form.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveValue } from '../feel.js';
|
|
1
|
+
import { resolveValue, getFeelScope } from '../feel.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* `zeebe:formDefinition`.
|
|
@@ -13,7 +13,7 @@ export class Form {
|
|
|
13
13
|
this.formDefinition = formDefinition;
|
|
14
14
|
}
|
|
15
15
|
resolve(elementApi) {
|
|
16
|
-
const scope = elementApi.environment
|
|
16
|
+
const scope = getFeelScope(elementApi.environment);
|
|
17
17
|
const { formId, formKey, externalReference, bindingType, versionTag } = this.formDefinition;
|
|
18
18
|
return {
|
|
19
19
|
...(formId !== undefined && { formId: resolveValue(formId, scope) }),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveValue } from '../feel.js';
|
|
1
|
+
import { resolveValue, getFeelScope } from '../feel.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* `zeebe:ioMapping`.
|
|
@@ -28,7 +28,7 @@ export class IoMapping {
|
|
|
28
28
|
* @param {import('bpmn-elements').IApi<any>} elementApi
|
|
29
29
|
*/
|
|
30
30
|
getInput(elementApi) {
|
|
31
|
-
const scope = elementApi.environment
|
|
31
|
+
const scope = getFeelScope(elementApi.environment);
|
|
32
32
|
const result = {};
|
|
33
33
|
for (const { source, target } of this.inputParameters) {
|
|
34
34
|
if (!target) continue;
|
|
@@ -42,7 +42,7 @@ export class IoMapping {
|
|
|
42
42
|
* @param {Record<string, any>} [jobResult] Variables produced by the job
|
|
43
43
|
*/
|
|
44
44
|
getOutput(elementApi, jobResult) {
|
|
45
|
-
const scope =
|
|
45
|
+
const scope = getFeelScope(elementApi.environment, jobResult);
|
|
46
46
|
const result = {};
|
|
47
47
|
for (const { source, target } of this.outputParameters) {
|
|
48
48
|
if (!target) continue;
|
|
@@ -21,7 +21,7 @@ export class LoopCharacteristics {
|
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* @param {Record<string, any>|undefined} indexedOutput bpmn-elements index-keyed instance outputs
|
|
24
|
-
* @param {Record<string, any>} baseScope
|
|
24
|
+
* @param {Record<string, any>} baseScope base scope for `outputElement` (services + process variables)
|
|
25
25
|
* @returns {any[]}
|
|
26
26
|
*/
|
|
27
27
|
aggregate(indexedOutput, baseScope) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveValue } from '../feel.js';
|
|
1
|
+
import { resolveValue, getFeelScope } from '../feel.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* `zeebe:properties`.
|
|
@@ -11,7 +11,7 @@ export class Properties {
|
|
|
11
11
|
this.properties = properties.properties || [];
|
|
12
12
|
}
|
|
13
13
|
resolve(elementApi) {
|
|
14
|
-
const scope = elementApi.environment
|
|
14
|
+
const scope = getFeelScope(elementApi.environment);
|
|
15
15
|
const result = {};
|
|
16
16
|
for (const { name, value } of this.properties) {
|
|
17
17
|
if (name === undefined) continue;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { resolveValue, getFeelScope } from '../feel.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `zeebe:subscription` — rides on the referenced `bpmn:Message`, not on the catching element.
|
|
5
|
+
*
|
|
6
|
+
* Resolves the correlation key in the activity's variable scope on enter and exposes it on the
|
|
7
|
+
* element content as `subscription`. Correlation itself is the embedding application's job:
|
|
8
|
+
* read the resolved key off the waiting activity (`getPostponed()` → `content.subscription`)
|
|
9
|
+
* and signal the matching one (`api.signal(...)`).
|
|
10
|
+
*/
|
|
11
|
+
export class Subscription {
|
|
12
|
+
/**
|
|
13
|
+
* @param {{ id: string, name?: string }} messageRef The serialized message reference
|
|
14
|
+
* @param {{ correlationKey: string }} subscription The `zeebe:Subscription` behaviour
|
|
15
|
+
*/
|
|
16
|
+
constructor(messageRef, subscription) {
|
|
17
|
+
this.message = { id: messageRef.id, name: messageRef.name };
|
|
18
|
+
this.correlationKey = subscription.correlationKey;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* @param {import('bpmn-elements').IApi<any>} elementApi
|
|
22
|
+
* @returns {{ message: { id: string, name?: string }, correlationKey: any }}
|
|
23
|
+
*/
|
|
24
|
+
resolve(elementApi) {
|
|
25
|
+
return {
|
|
26
|
+
message: this.message,
|
|
27
|
+
correlationKey: resolveValue(this.correlationKey, getFeelScope(elementApi.environment)),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ServiceError } from '../Errors.js';
|
|
2
|
-
import { resolveValue } from '../feel.js';
|
|
2
|
+
import { resolveValue, getFeelScope } from '../feel.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* `zeebe:taskDefinition`.
|
|
@@ -22,13 +22,19 @@ export function JobService(taskType, activity) {
|
|
|
22
22
|
this.activity = activity;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Execute
|
|
27
|
+
* @param {import('bpmn-elements').ElementBrokerMessage} executionMessage
|
|
28
|
+
* @param {CallableFunction} callback
|
|
29
|
+
* @returns {any}
|
|
30
|
+
*/
|
|
25
31
|
JobService.prototype.execute = function execute(executionMessage, callback) {
|
|
26
32
|
const activity = this.activity;
|
|
27
33
|
const environment = activity.environment;
|
|
28
34
|
|
|
29
35
|
let jobType;
|
|
30
36
|
try {
|
|
31
|
-
jobType = resolveValue(this.taskType, environment
|
|
37
|
+
jobType = resolveValue(this.taskType, getFeelScope(environment));
|
|
32
38
|
} catch (err) {
|
|
33
39
|
return callback(err);
|
|
34
40
|
}
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
import { resolveValue } from '../feel.js';
|
|
1
|
+
import { resolveValue, getFeelScope } from '../feel.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Format an activity on enter from its behaviour and extensions: documentation and,
|
|
5
5
|
* for user tasks, the `zeebe:assignmentDefinition` (assignee, candidate users/groups).
|
|
6
6
|
*/
|
|
7
7
|
export class FormatActivity {
|
|
8
|
+
/**
|
|
9
|
+
* @param {import('bpmn-elements').Activity} activity
|
|
10
|
+
* @param {any} assignmentDefinition
|
|
11
|
+
*/
|
|
8
12
|
constructor(activity, assignmentDefinition) {
|
|
9
13
|
this.activity = activity;
|
|
10
14
|
this.assignmentDefinition = assignmentDefinition;
|
|
11
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Activity>} elementApi
|
|
18
|
+
*/
|
|
12
19
|
resolve(elementApi) {
|
|
13
|
-
const scope = elementApi.environment
|
|
20
|
+
const scope = getFeelScope(elementApi.environment);
|
|
14
21
|
const result = {};
|
|
15
22
|
|
|
16
23
|
const documentation = this.activity.behaviour.documentation;
|
|
@@ -37,15 +44,21 @@ export class FormatActivity {
|
|
|
37
44
|
* Format a process on enter: documentation.
|
|
38
45
|
*/
|
|
39
46
|
export class FormatProcess {
|
|
47
|
+
/**
|
|
48
|
+
* @param {import('bpmn-elements').Process} bp
|
|
49
|
+
*/
|
|
40
50
|
constructor(bp) {
|
|
41
51
|
this.process = bp;
|
|
42
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Process>} elementApi
|
|
55
|
+
*/
|
|
43
56
|
resolve(elementApi) {
|
|
44
57
|
const result = {};
|
|
45
58
|
const documentation = this.process.behaviour.documentation;
|
|
46
59
|
if (documentation && !elementApi.content.description) {
|
|
47
60
|
const text = documentation[0]?.text;
|
|
48
|
-
if (text) result.description = resolveValue(text, elementApi.environment
|
|
61
|
+
if (text) result.description = resolveValue(text, getFeelScope(elementApi.environment));
|
|
49
62
|
}
|
|
50
63
|
return result;
|
|
51
64
|
}
|
|
@@ -58,7 +71,7 @@ export class FormatProcess {
|
|
|
58
71
|
function resolveList(value, scope) {
|
|
59
72
|
if (!value) return undefined;
|
|
60
73
|
const resolved = resolveValue(value, scope);
|
|
61
|
-
if (Array.isArray(resolved)) return resolved.filter(
|
|
74
|
+
if (Array.isArray(resolved)) return resolved.filter(Boolean);
|
|
62
75
|
if (typeof resolved !== 'string') return undefined;
|
|
63
76
|
return resolved
|
|
64
77
|
.split(',')
|
package/src/feel.js
CHANGED
|
@@ -40,11 +40,11 @@ export function evaluateFeel(expression, context) {
|
|
|
40
40
|
* @param {string} expression FEEL unary test
|
|
41
41
|
* @param {any} input The value being tested, available as `?` in the test
|
|
42
42
|
* @param {Record<string, any>} [context] Additional variables in scope
|
|
43
|
-
* @returns {boolean}
|
|
43
|
+
* @returns {boolean} true only when the test is satisfied — an undecidable (null) test is false
|
|
44
44
|
*/
|
|
45
45
|
export function evaluateFeelUnaryTest(expression, input, context) {
|
|
46
|
-
const { value } = unaryTest(stripFeel(expression), { ...
|
|
47
|
-
return value;
|
|
46
|
+
const { value } = unaryTest(stripFeel(expression), { ...context, '?': input });
|
|
47
|
+
return value === true;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
@@ -58,3 +58,18 @@ export function resolveValue(value, context) {
|
|
|
58
58
|
if (isFeelExpression(value)) return evaluateFeel(value, context);
|
|
59
59
|
return value;
|
|
60
60
|
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Build the FEEL evaluation scope for an environment. The environment services are exposed
|
|
64
|
+
* under `services`, callable as FEEL functions (e.g. `services.isEligible(order)`) — resume-safe,
|
|
65
|
+
* since the host re-supplies services at recover time, unlike functions smuggled through
|
|
66
|
+
* variables, which JSON state serialization drops. The environment variables form the base
|
|
67
|
+
* scope, overlaid with any local variables (a job result, element-local variables such as a
|
|
68
|
+
* multi-instance item); a variable named `services` shadows the overlay.
|
|
69
|
+
* @param {import('bpmn-elements').Environment} [environment]
|
|
70
|
+
* @param {Record<string, any>} [localVariables] scope-local overlay
|
|
71
|
+
* @returns {Record<string, any>}
|
|
72
|
+
*/
|
|
73
|
+
export function getFeelScope(environment, localVariables) {
|
|
74
|
+
return { services: environment?.services, ...environment?.variables, ...localVariables };
|
|
75
|
+
}
|
package/src/getExtensions.js
CHANGED
|
@@ -6,24 +6,31 @@ import { ExecutionListeners } from './extensions/ExecutionListeners.js';
|
|
|
6
6
|
import { Form } from './extensions/Form.js';
|
|
7
7
|
import { LoopCharacteristics } from './extensions/LoopCharacteristics.js';
|
|
8
8
|
import { JobService } from './extensions/TaskDefinition.js';
|
|
9
|
+
import { Subscription } from './extensions/Subscription.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The extension handlers assembled for one element.
|
|
13
|
+
* @typedef {object} ExtensionHandlers
|
|
14
|
+
* @property {FormatActivity | FormatProcess} format
|
|
15
|
+
* @property {IoMapping} [io]
|
|
16
|
+
* @property {TaskHeaders} [headers]
|
|
17
|
+
* @property {Properties} [properties]
|
|
18
|
+
* @property {ExecutionListeners} [listeners]
|
|
19
|
+
* @property {Form} [form]
|
|
20
|
+
* @property {LoopCharacteristics} [loop]
|
|
21
|
+
* @property {any} [script]
|
|
22
|
+
* @property {any} [calledDecision]
|
|
23
|
+
* @property {Function} [Service]
|
|
24
|
+
* @property {Subscription} [subscription]
|
|
25
|
+
*/
|
|
9
26
|
|
|
10
27
|
/**
|
|
11
28
|
* Inspect an element's extension elements and assemble the extension handlers it needs.
|
|
12
29
|
* @param {import('bpmn-elements').Activity | import('bpmn-elements').Process} element
|
|
13
|
-
* @
|
|
14
|
-
*
|
|
15
|
-
* io?: IoMapping,
|
|
16
|
-
* headers?: TaskHeaders,
|
|
17
|
-
* properties?: Properties,
|
|
18
|
-
* listeners?: ExecutionListeners,
|
|
19
|
-
* form?: Form,
|
|
20
|
-
* loop?: LoopCharacteristics,
|
|
21
|
-
* script?: any,
|
|
22
|
-
* calledDecision?: any,
|
|
23
|
-
* Service?: Function,
|
|
24
|
-
* }}
|
|
30
|
+
* @param {import('bpmn-elements').ContextInstance} [context]
|
|
31
|
+
* @returns {ExtensionHandlers}
|
|
25
32
|
*/
|
|
26
|
-
export function getExtensions(element) {
|
|
33
|
+
export function getExtensions(element, context) {
|
|
27
34
|
const result = {};
|
|
28
35
|
const isProcess = element.type === 'bpmn:Process';
|
|
29
36
|
|
|
@@ -67,6 +74,18 @@ export function getExtensions(element) {
|
|
|
67
74
|
}
|
|
68
75
|
}
|
|
69
76
|
|
|
77
|
+
// A message catch (receive task / message event) may reference a bpmn:Message carrying a
|
|
78
|
+
// zeebe:subscription with the correlation key. The subscription lives on the message element,
|
|
79
|
+
// which the instantiated bpmn-elements Message strips — read it off the serialized context.
|
|
80
|
+
const messageRef =
|
|
81
|
+
element.behaviour.messageRef ?? element.behaviour.eventDefinitions?.find((ed) => ed.behaviour?.messageRef)?.behaviour.messageRef;
|
|
82
|
+
if (messageRef?.id && context) {
|
|
83
|
+
const subscription = context.definitionContext
|
|
84
|
+
.getActivityById(messageRef.id)
|
|
85
|
+
?.behaviour.extensionElements?.values?.find((v) => v.$type === 'zeebe:Subscription');
|
|
86
|
+
if (subscription?.correlationKey) result.subscription = new Subscription(messageRef, subscription);
|
|
87
|
+
}
|
|
88
|
+
|
|
70
89
|
// Output side of a multi-instance `zeebe:loopCharacteristics` (the input side is lifted by extendFn).
|
|
71
90
|
const loopExtension = element.behaviour.loopCharacteristics?.behaviour?.extensionElements?.values?.find(
|
|
72
91
|
(v) => v.$type === 'zeebe:LoopCharacteristics'
|
package/src/index.js
CHANGED
|
@@ -5,15 +5,15 @@ import { SubProcessExtensions } from './SubProcessExtensions.js';
|
|
|
5
5
|
export { ElementExtensions, ProcessExtensions, SubProcessExtensions };
|
|
6
6
|
export { FeelExpressions } from './Expressions.js';
|
|
7
7
|
export { FeelScripts } from './extensions/FeelScripts.js';
|
|
8
|
-
export { isFeelExpression, stripFeel, evaluateFeel, evaluateFeelUnaryTest, resolveValue } from './feel.js';
|
|
8
|
+
export { isFeelExpression, stripFeel, evaluateFeel, evaluateFeelUnaryTest, resolveValue, getFeelScope } from './feel.js';
|
|
9
9
|
export { JobService } from './extensions/TaskDefinition.js';
|
|
10
10
|
export { ServiceError, FormatError } from './Errors.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* A flow extension activated by bpmn-elements around an element's run.
|
|
14
14
|
* @typedef {object} FlowExtension
|
|
15
|
-
* @property {(message:
|
|
16
|
-
* @property {(message?:
|
|
15
|
+
* @property {(message: import('bpmn-elements').ElementBrokerMessage) => void} activate
|
|
16
|
+
* @property {(message?: import('bpmn-elements').ElementBrokerMessage) => void} deactivate
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
/**
|