@0dep/bpmn-extensions 0.0.3 → 0.0.5
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 +16 -3
- package/dist/index.js +121 -68
- package/package.json +2 -2
- package/src/ElementExtensions.js +2 -1
- package/src/Expressions.js +4 -12
- package/src/extensions/FeelScripts.js +5 -5
- 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 +2 -2
- package/src/extensions/TaskDefinition.js +2 -2
- package/src/extensions/formatters.js +45 -6
- package/src/feel.js +15 -0
- package/src/getExtensions.js +23 -2
- package/src/index.js +18 -6
- package/types/index.d.ts +33 -8
- package/types/index.d.ts.map +2 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @0dep/bpmn-extensions
|
|
2
2
|
|
|
3
|
-
[](https://github.com/zerodep/bpmn-extensions/actions/workflows/build-latest.yaml)[](https://coveralls.io/github/zerodep/bpmn-extensions?branch=main)
|
|
3
|
+
[](https://github.com/zerodep/bpmn-extensions/actions/workflows/build-latest.yaml)[](https://coveralls.io/github/zerodep/bpmn-extensions?branch=main)
|
|
4
4
|
|
|
5
5
|
Flow extensions for [bpmn-elements](https://github.com/paed01/bpmn-elements) with
|
|
6
6
|
[FEEL](https://www.omg.org/dmn/) expression support.
|
|
@@ -21,6 +21,12 @@ npm install @0dep/bpmn-extensions bpmn-elements
|
|
|
21
21
|
|
|
22
22
|
- **FEEL expressions** — a `FeelExpressions()` adapter resolves FEEL (`= order.total > 100`)
|
|
23
23
|
everywhere bpmn-elements resolves an expression, including sequence flow conditions.
|
|
24
|
+
- **Environment services in FEEL scope** — every FEEL expression can call the environment services
|
|
25
|
+
as functions, namespaced under `services` (e.g. a condition `= services.isEligible(order)`).
|
|
26
|
+
This strays from strict Camunda 8 conformance on purpose: unlike functions smuggled through
|
|
27
|
+
variables, services survive `getState()`/`recover()` — state is JSON-serialized (functions are
|
|
28
|
+
dropped), while services are re-supplied by the host at recover time — so service-backed
|
|
29
|
+
conditions are resume-safe. A variable named `services` shadows the overlay.
|
|
24
30
|
- **`zeebe:script`** — a `FeelScripts()` adapter runs a script task's FEEL `expression` and assigns
|
|
25
31
|
the result to its `resultVariable`.
|
|
26
32
|
- **`zeebe:taskDefinition`** — maps a service task's job `type` to an environment service (job worker).
|
|
@@ -32,6 +38,9 @@ npm install @0dep/bpmn-extensions bpmn-elements
|
|
|
32
38
|
process's input becomes variables local to its children.
|
|
33
39
|
- **`zeebe:taskHeaders`** / **`zeebe:properties`** — exposed on the element content.
|
|
34
40
|
- **`zeebe:assignmentDefinition`** — assignee / candidate users / groups for user tasks.
|
|
41
|
+
- **`zeebe:priorityDefinition`** / **`zeebe:taskSchedule`** — a user task's `priority` and
|
|
42
|
+
`dueDate` / `followUpDate` resolved onto the element content for a task list to order and
|
|
43
|
+
remind by; FEEL date results are exposed as ISO 8601 strings.
|
|
35
44
|
- **`zeebe:formDefinition`** — resolves a user task's form (`formId` / `formKey` / `externalReference`,
|
|
36
45
|
`bindingType`) onto the element content for a task list to render.
|
|
37
46
|
- **`zeebe:executionListeners`** — blocking start/end job workers around an element, called as
|
|
@@ -48,11 +57,15 @@ npm install @0dep/bpmn-extensions bpmn-elements
|
|
|
48
57
|
## API
|
|
49
58
|
|
|
50
59
|
- `extensions(element, context)` — the flow extensions factory; pass it as an environment extension.
|
|
60
|
+
Elements that carry no zeebe extension data (and nothing else to format) get no extension attached
|
|
61
|
+
at all and run untouched; call activities and processes are always attached, since they propagate
|
|
62
|
+
io across process boundaries.
|
|
51
63
|
- `extendFn(behaviour)` — moddle-context-serializer behaviour extender (lifts the extension data
|
|
52
64
|
bpmn-elements expects on the behaviour: call activity process id and multi-instance collection).
|
|
53
65
|
- `FeelExpressions()` — a bpmn-elements `IExpressions` implementation backed by FEEL.
|
|
54
66
|
- `FeelScripts()` — a bpmn-elements `scripts` implementation that runs `zeebe:script` FEEL expressions.
|
|
55
|
-
- FEEL helpers: `isFeelExpression`, `stripFeel`, `evaluateFeel`, `evaluateFeelUnaryTest`, `resolveValue
|
|
67
|
+
- FEEL helpers: `isFeelExpression`, `stripFeel`, `evaluateFeel`, `evaluateFeelUnaryTest`, `resolveValue`,
|
|
68
|
+
`getFeelScope` (the scope every expression is evaluated in: `services` + environment variables + local overlay).
|
|
56
69
|
- `JobService`, `ServiceError`, `FormatError`.
|
|
57
70
|
|
|
58
71
|
## Usage
|
|
@@ -62,7 +75,7 @@ Wire it into a bpmn-elements definition (see `test/helpers/testHelpers.js` for t
|
|
|
62
75
|
```javascript
|
|
63
76
|
import { createRequire } from 'node:module';
|
|
64
77
|
import { strict as assert } from 'node:assert';
|
|
65
|
-
import BpmnModdle from 'bpmn-moddle';
|
|
78
|
+
import { BpmnModdle } from 'bpmn-moddle';
|
|
66
79
|
import * as elements from 'bpmn-elements';
|
|
67
80
|
import { Serializer, TypeResolver } from 'moddle-context-serializer';
|
|
68
81
|
import { extensions, extendFn, FeelExpressions, FeelScripts } from '@0dep/bpmn-extensions';
|
package/dist/index.js
CHANGED
|
@@ -11609,9 +11609,9 @@ function getSpecializer(spec) {
|
|
|
11609
11609
|
*
|
|
11610
11610
|
* @return {T[]}
|
|
11611
11611
|
*/
|
|
11612
|
-
const nativeHasOwnProperty
|
|
11612
|
+
const nativeHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
11613
11613
|
|
|
11614
|
-
function isNil$
|
|
11614
|
+
function isNil$1(obj) {
|
|
11615
11615
|
return obj == null;
|
|
11616
11616
|
}
|
|
11617
11617
|
|
|
@@ -11623,8 +11623,8 @@ function isNil$2(obj) {
|
|
|
11623
11623
|
*
|
|
11624
11624
|
* @return {Boolean}
|
|
11625
11625
|
*/
|
|
11626
|
-
function has
|
|
11627
|
-
return !isNil$
|
|
11626
|
+
function has(target, key) {
|
|
11627
|
+
return !isNil$1(target) && nativeHasOwnProperty.call(target, key);
|
|
11628
11628
|
}
|
|
11629
11629
|
|
|
11630
11630
|
let nextTagID = 0;
|
|
@@ -13107,7 +13107,7 @@ class VariableContext {
|
|
|
13107
13107
|
continue;
|
|
13108
13108
|
}
|
|
13109
13109
|
|
|
13110
|
-
if (has
|
|
13110
|
+
if (has(merged, key)) {
|
|
13111
13111
|
|
|
13112
13112
|
// deep merge nested contexts
|
|
13113
13113
|
merged[key] = this.__merge(merged[key], value);
|
|
@@ -13241,7 +13241,7 @@ class Variables {
|
|
|
13241
13241
|
return null;
|
|
13242
13242
|
}
|
|
13243
13243
|
|
|
13244
|
-
if (!isNil$
|
|
13244
|
+
if (!isNil$1(scope.value)) {
|
|
13245
13245
|
return scope.value;
|
|
13246
13246
|
}
|
|
13247
13247
|
}
|
|
@@ -13871,33 +13871,6 @@ const parser = LRParser.deserialize({
|
|
|
13871
13871
|
tokenPrec: 2660
|
|
13872
13872
|
});
|
|
13873
13873
|
|
|
13874
|
-
/**
|
|
13875
|
-
* Flatten array, one level deep.
|
|
13876
|
-
*
|
|
13877
|
-
* @template T
|
|
13878
|
-
*
|
|
13879
|
-
* @param {T[][] | T[] | null} [arr]
|
|
13880
|
-
*
|
|
13881
|
-
* @return {T[]}
|
|
13882
|
-
*/
|
|
13883
|
-
const nativeHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
13884
|
-
|
|
13885
|
-
function isNil$1(obj) {
|
|
13886
|
-
return obj == null;
|
|
13887
|
-
}
|
|
13888
|
-
|
|
13889
|
-
/**
|
|
13890
|
-
* Return true, if target owns a property with the given key.
|
|
13891
|
-
*
|
|
13892
|
-
* @param {Object} target
|
|
13893
|
-
* @param {String} key
|
|
13894
|
-
*
|
|
13895
|
-
* @return {Boolean}
|
|
13896
|
-
*/
|
|
13897
|
-
function has(target, key) {
|
|
13898
|
-
return !isNil$1(target) && nativeHasOwnProperty.call(target, key);
|
|
13899
|
-
}
|
|
13900
|
-
|
|
13901
13874
|
function isNil(e) {
|
|
13902
13875
|
return e === null || e === undefined;
|
|
13903
13876
|
}
|
|
@@ -16222,30 +16195,50 @@ function resolveValue(value, context) {
|
|
|
16222
16195
|
return value;
|
|
16223
16196
|
}
|
|
16224
16197
|
|
|
16198
|
+
/**
|
|
16199
|
+
* Build the FEEL evaluation scope for an environment. The environment services are exposed
|
|
16200
|
+
* under `services`, callable as FEEL functions (e.g. `services.isEligible(order)`) — resume-safe,
|
|
16201
|
+
* since the host re-supplies services at recover time, unlike functions smuggled through
|
|
16202
|
+
* variables, which JSON state serialization drops. The environment variables form the base
|
|
16203
|
+
* scope, overlaid with any local variables (a job result, element-local variables such as a
|
|
16204
|
+
* multi-instance item); a variable named `services` shadows the overlay.
|
|
16205
|
+
* @param {import('bpmn-elements').Environment} [environment]
|
|
16206
|
+
* @param {Record<string, any>} [localVariables] scope-local overlay
|
|
16207
|
+
* @returns {Record<string, any>}
|
|
16208
|
+
*/
|
|
16209
|
+
function getFeelScope(environment, localVariables) {
|
|
16210
|
+
return { services: environment?.services, ...environment?.variables, ...localVariables };
|
|
16211
|
+
}
|
|
16212
|
+
|
|
16225
16213
|
/**
|
|
16226
16214
|
* Format an activity on enter from its behaviour and extensions: documentation and,
|
|
16227
|
-
* for user tasks, the `zeebe:assignmentDefinition` (assignee, candidate users/groups)
|
|
16215
|
+
* for user tasks, the `zeebe:assignmentDefinition` (assignee, candidate users/groups),
|
|
16216
|
+
* `zeebe:priorityDefinition` (priority), and `zeebe:taskSchedule` (due/follow-up date).
|
|
16228
16217
|
*/
|
|
16229
16218
|
class FormatActivity {
|
|
16230
16219
|
/**
|
|
16231
16220
|
* @param {import('bpmn-elements').Activity} activity
|
|
16232
16221
|
* @param {any} assignmentDefinition
|
|
16222
|
+
* @param {any} [priorityDefinition]
|
|
16223
|
+
* @param {any} [taskSchedule]
|
|
16233
16224
|
*/
|
|
16234
|
-
constructor(activity, assignmentDefinition) {
|
|
16225
|
+
constructor(activity, assignmentDefinition, priorityDefinition, taskSchedule) {
|
|
16235
16226
|
this.activity = activity;
|
|
16236
16227
|
this.assignmentDefinition = assignmentDefinition;
|
|
16228
|
+
this.priorityDefinition = priorityDefinition;
|
|
16229
|
+
this.taskSchedule = taskSchedule;
|
|
16237
16230
|
}
|
|
16238
16231
|
/**
|
|
16239
16232
|
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Activity>} elementApi
|
|
16240
16233
|
*/
|
|
16241
16234
|
resolve(elementApi) {
|
|
16242
|
-
const scope = elementApi.environment
|
|
16235
|
+
const scope = getFeelScope(elementApi.environment);
|
|
16243
16236
|
const result = {};
|
|
16244
16237
|
|
|
16245
16238
|
const documentation = this.activity.behaviour.documentation;
|
|
16246
16239
|
if (documentation && !elementApi.content.description) {
|
|
16247
16240
|
const text = documentation[0]?.text;
|
|
16248
|
-
if (text) result.description = resolveValue(text, scope);
|
|
16241
|
+
if (text) result.description = /** @type {string} */ (resolveValue(text, scope));
|
|
16249
16242
|
}
|
|
16250
16243
|
|
|
16251
16244
|
const assignment = this.assignmentDefinition;
|
|
@@ -16258,6 +16251,22 @@ class FormatActivity {
|
|
|
16258
16251
|
if (groups) result.candidateGroups = groups;
|
|
16259
16252
|
}
|
|
16260
16253
|
|
|
16254
|
+
const priority = this.priorityDefinition?.priority;
|
|
16255
|
+
if (priority !== undefined) {
|
|
16256
|
+
const resolved = resolveValue(priority, scope);
|
|
16257
|
+
// A static attribute value is a string — expose a numeric one as a number (like retries).
|
|
16258
|
+
const num = Number(resolved);
|
|
16259
|
+
/** @type {Number | string | undefined} */
|
|
16260
|
+
result.priority = typeof resolved === 'string' && resolved.trim() !== '' && Number.isFinite(num) ? num : resolved;
|
|
16261
|
+
}
|
|
16262
|
+
|
|
16263
|
+
const schedule = this.taskSchedule;
|
|
16264
|
+
if (schedule) {
|
|
16265
|
+
const { dueDate, followUpDate } = schedule;
|
|
16266
|
+
if (dueDate !== undefined) result.dueDate = resolveDate(dueDate, scope);
|
|
16267
|
+
if (followUpDate !== undefined) result.followUpDate = resolveDate(followUpDate, scope);
|
|
16268
|
+
}
|
|
16269
|
+
|
|
16261
16270
|
return result;
|
|
16262
16271
|
}
|
|
16263
16272
|
}
|
|
@@ -16280,15 +16289,33 @@ class FormatProcess {
|
|
|
16280
16289
|
const documentation = this.process.behaviour.documentation;
|
|
16281
16290
|
if (documentation && !elementApi.content.description) {
|
|
16282
16291
|
const text = documentation[0]?.text;
|
|
16283
|
-
if (text) result.description = resolveValue(text, elementApi.environment
|
|
16292
|
+
if (text) result.description = /** @type {string} */ (resolveValue(text, getFeelScope(elementApi.environment)));
|
|
16284
16293
|
}
|
|
16285
16294
|
return result;
|
|
16286
16295
|
}
|
|
16287
16296
|
}
|
|
16288
16297
|
|
|
16298
|
+
/**
|
|
16299
|
+
* Resolve a due/follow-up date to an ISO 8601 string. A FEEL temporal result (a luxon
|
|
16300
|
+
* DateTime) or a Date is ISO-stringified — content is JSON-serialized on `getState()`, so a
|
|
16301
|
+
* string is the shape that survives a recover round trip. Anything else passes through as-is.
|
|
16302
|
+
* @param {any} value
|
|
16303
|
+
* @param {any} scope
|
|
16304
|
+
* @returns {string | undefined} Date as iso string
|
|
16305
|
+
*/
|
|
16306
|
+
function resolveDate(value, scope) {
|
|
16307
|
+
const resolved = resolveValue(value, scope);
|
|
16308
|
+
if (typeof resolved?.toISO === 'function') return resolved.toISO();
|
|
16309
|
+
if (resolved instanceof Date) return resolved.toISOString();
|
|
16310
|
+
return resolved;
|
|
16311
|
+
}
|
|
16312
|
+
|
|
16289
16313
|
/**
|
|
16290
16314
|
* Resolve a candidate users/groups value to a string array. A FEEL expression may yield an
|
|
16291
16315
|
* array directly, otherwise a comma-separated string is split.
|
|
16316
|
+
* @param {any} value
|
|
16317
|
+
* @param {any} scope
|
|
16318
|
+
* @returns {string[] | undefined}
|
|
16292
16319
|
*/
|
|
16293
16320
|
function resolveList(value, scope) {
|
|
16294
16321
|
if (!value) return undefined;
|
|
@@ -16329,7 +16356,7 @@ class IoMapping {
|
|
|
16329
16356
|
* @param {import('bpmn-elements').IApi<any>} elementApi
|
|
16330
16357
|
*/
|
|
16331
16358
|
getInput(elementApi) {
|
|
16332
|
-
const scope = elementApi.environment
|
|
16359
|
+
const scope = getFeelScope(elementApi.environment);
|
|
16333
16360
|
const result = {};
|
|
16334
16361
|
for (const { source, target } of this.inputParameters) {
|
|
16335
16362
|
if (!target) continue;
|
|
@@ -16343,7 +16370,7 @@ class IoMapping {
|
|
|
16343
16370
|
* @param {Record<string, any>} [jobResult] Variables produced by the job
|
|
16344
16371
|
*/
|
|
16345
16372
|
getOutput(elementApi, jobResult) {
|
|
16346
|
-
const scope =
|
|
16373
|
+
const scope = getFeelScope(elementApi.environment, jobResult);
|
|
16347
16374
|
const result = {};
|
|
16348
16375
|
for (const { source, target } of this.outputParameters) {
|
|
16349
16376
|
if (!target) continue;
|
|
@@ -16399,7 +16426,7 @@ class Properties {
|
|
|
16399
16426
|
this.properties = properties.properties || [];
|
|
16400
16427
|
}
|
|
16401
16428
|
resolve(elementApi) {
|
|
16402
|
-
const scope = elementApi.environment
|
|
16429
|
+
const scope = getFeelScope(elementApi.environment);
|
|
16403
16430
|
const result = {};
|
|
16404
16431
|
for (const { name, value } of this.properties) {
|
|
16405
16432
|
if (name === undefined) continue;
|
|
@@ -16495,7 +16522,7 @@ class Form {
|
|
|
16495
16522
|
this.formDefinition = formDefinition;
|
|
16496
16523
|
}
|
|
16497
16524
|
resolve(elementApi) {
|
|
16498
|
-
const scope = elementApi.environment
|
|
16525
|
+
const scope = getFeelScope(elementApi.environment);
|
|
16499
16526
|
const { formId, formKey, externalReference, bindingType, versionTag } = this.formDefinition;
|
|
16500
16527
|
return {
|
|
16501
16528
|
...(formId !== undefined && { formId: resolveValue(formId, scope) }),
|
|
@@ -16528,7 +16555,7 @@ class LoopCharacteristics {
|
|
|
16528
16555
|
}
|
|
16529
16556
|
/**
|
|
16530
16557
|
* @param {Record<string, any>|undefined} indexedOutput bpmn-elements index-keyed instance outputs
|
|
16531
|
-
* @param {Record<string, any>} baseScope
|
|
16558
|
+
* @param {Record<string, any>} baseScope base scope for `outputElement` (services + process variables)
|
|
16532
16559
|
* @returns {any[]}
|
|
16533
16560
|
*/
|
|
16534
16561
|
aggregate(indexedOutput, baseScope) {
|
|
@@ -16583,7 +16610,7 @@ JobService.prototype.execute = function execute(executionMessage, callback) {
|
|
|
16583
16610
|
|
|
16584
16611
|
let jobType;
|
|
16585
16612
|
try {
|
|
16586
|
-
jobType = resolveValue(this.taskType, environment
|
|
16613
|
+
jobType = resolveValue(this.taskType, getFeelScope(environment));
|
|
16587
16614
|
} catch (err) {
|
|
16588
16615
|
return callback(err);
|
|
16589
16616
|
}
|
|
@@ -16618,7 +16645,7 @@ class Subscription {
|
|
|
16618
16645
|
resolve(elementApi) {
|
|
16619
16646
|
return {
|
|
16620
16647
|
message: this.message,
|
|
16621
|
-
correlationKey: resolveValue(this.correlationKey, elementApi.environment
|
|
16648
|
+
correlationKey: resolveValue(this.correlationKey, getFeelScope(elementApi.environment)),
|
|
16622
16649
|
};
|
|
16623
16650
|
}
|
|
16624
16651
|
}
|
|
@@ -16637,6 +16664,7 @@ class Subscription {
|
|
|
16637
16664
|
* @property {any} [calledDecision]
|
|
16638
16665
|
* @property {Function} [Service]
|
|
16639
16666
|
* @property {Subscription} [subscription]
|
|
16667
|
+
* @property {boolean} [isEmpty] no zeebe extension data found and nothing to format — the element needs no extension
|
|
16640
16668
|
*/
|
|
16641
16669
|
|
|
16642
16670
|
/**
|
|
@@ -16649,10 +16677,15 @@ function getExtensions(element, context) {
|
|
|
16649
16677
|
const result = {};
|
|
16650
16678
|
const isProcess = element.type === 'bpmn:Process';
|
|
16651
16679
|
|
|
16652
|
-
let assignmentDefinition;
|
|
16680
|
+
let assignmentDefinition, priorityDefinition, taskSchedule;
|
|
16681
|
+
let foundZeebe = false;
|
|
16653
16682
|
const values = element.behaviour.extensionElements?.values;
|
|
16654
16683
|
if (values) {
|
|
16655
16684
|
for (const ext of values) {
|
|
16685
|
+
// Any zeebe-namespaced extension element counts as found — including ones with no
|
|
16686
|
+
// handler (e.g. the bare `zeebe:userTask` marker), which keep the element extension
|
|
16687
|
+
// loaded so e.g. a native user task's completion payload is merged.
|
|
16688
|
+
if (ext.$type.startsWith('zeebe:')) foundZeebe = true;
|
|
16656
16689
|
switch (ext.$type) {
|
|
16657
16690
|
case 'zeebe:IoMapping':
|
|
16658
16691
|
result.io = new IoMapping(element, ext);
|
|
@@ -16666,6 +16699,12 @@ function getExtensions(element, context) {
|
|
|
16666
16699
|
case 'zeebe:AssignmentDefinition':
|
|
16667
16700
|
assignmentDefinition = ext;
|
|
16668
16701
|
break;
|
|
16702
|
+
case 'zeebe:PriorityDefinition':
|
|
16703
|
+
priorityDefinition = ext;
|
|
16704
|
+
break;
|
|
16705
|
+
case 'zeebe:TaskSchedule':
|
|
16706
|
+
taskSchedule = ext;
|
|
16707
|
+
break;
|
|
16669
16708
|
case 'zeebe:ExecutionListeners':
|
|
16670
16709
|
if (ext.listeners?.length) result.listeners = new ExecutionListeners(element, ext);
|
|
16671
16710
|
break;
|
|
@@ -16707,7 +16746,16 @@ function getExtensions(element, context) {
|
|
|
16707
16746
|
);
|
|
16708
16747
|
if (loopExtension?.outputCollection) result.loop = new LoopCharacteristics(loopExtension);
|
|
16709
16748
|
|
|
16710
|
-
|
|
16749
|
+
// Nothing zeebe-flavoured and nothing to format — the element needs no extension. A call
|
|
16750
|
+
// activity is never inert: its output unwrap/merge is type-driven (the standard BPMN
|
|
16751
|
+
// `calledElement` attribute needs no zeebe elements). Ignored for processes — a called
|
|
16752
|
+
// process always needs its inbound input promoted, whatever it carries itself.
|
|
16753
|
+
result.isEmpty =
|
|
16754
|
+
!foundZeebe && !result.subscription && !loopExtension && !element.behaviour.documentation && element.type !== 'bpmn:CallActivity';
|
|
16755
|
+
|
|
16756
|
+
result.format = isProcess
|
|
16757
|
+
? new FormatProcess(element)
|
|
16758
|
+
: new FormatActivity(element, assignmentDefinition, priorityDefinition, taskSchedule);
|
|
16711
16759
|
|
|
16712
16760
|
return result;
|
|
16713
16761
|
}
|
|
@@ -16846,7 +16894,7 @@ class ElementExtensions {
|
|
|
16846
16894
|
|
|
16847
16895
|
// Multi-instance: aggregate the per-instance outputs into the `outputCollection` array.
|
|
16848
16896
|
if (loop?.hasOutputCollection) {
|
|
16849
|
-
const output = { [loop.outputCollection]: loop.aggregate(jobResult, elementApi.environment
|
|
16897
|
+
const output = { [loop.outputCollection]: loop.aggregate(jobResult, getFeelScope(elementApi.environment)) };
|
|
16850
16898
|
this.#assignOutput(elementApi, output);
|
|
16851
16899
|
return { output };
|
|
16852
16900
|
}
|
|
@@ -17009,22 +17057,14 @@ function hasExpression(text) {
|
|
|
17009
17057
|
|
|
17010
17058
|
/**
|
|
17011
17059
|
* Resolve a templated string. Static literals are returned as-is; FEEL expressions are
|
|
17012
|
-
* evaluated
|
|
17060
|
+
* evaluated in the environment scope (see `getFeelScope`), overlaid with any element-local
|
|
17061
|
+
* variables carried on the message content (e.g. a multi-instance item).
|
|
17013
17062
|
* @param {string} templatedString
|
|
17014
17063
|
* @param {{ environment?: import('bpmn-elements').Environment, content?: any }} [context]
|
|
17015
17064
|
*/
|
|
17016
17065
|
function resolveExpression(templatedString, context) {
|
|
17017
17066
|
if (!isFeelExpression(templatedString)) return templatedString;
|
|
17018
|
-
return evaluateFeel(templatedString,
|
|
17019
|
-
}
|
|
17020
|
-
|
|
17021
|
-
/**
|
|
17022
|
-
* Build the FEEL variable scope from a resolution context. Process/definition variables form
|
|
17023
|
-
* the base scope, overlaid with any local variables carried on the element message content
|
|
17024
|
-
* (e.g. a multi-instance item).
|
|
17025
|
-
*/
|
|
17026
|
-
function getScope(context) {
|
|
17027
|
-
return { ...context?.environment?.variables, ...context?.content?.variables };
|
|
17067
|
+
return evaluateFeel(templatedString, getFeelScope(context?.environment, context?.content?.variables));
|
|
17028
17068
|
}
|
|
17029
17069
|
|
|
17030
17070
|
/**
|
|
@@ -17049,12 +17089,12 @@ class FeelScriptRegistry {
|
|
|
17049
17089
|
/**
|
|
17050
17090
|
* @param {string} _scriptFormat
|
|
17051
17091
|
* @param {{ id: string, [x: string]: any }} activity
|
|
17052
|
-
* @returns {import('bpmn-elements').Script} the FEEL script, or undefined when
|
|
17053
|
-
* has no `zeebe:script`
|
|
17092
|
+
* @returns {import('bpmn-elements').Script | undefined} the FEEL script, or undefined when
|
|
17093
|
+
* the activity has no `zeebe:script`
|
|
17054
17094
|
*/
|
|
17055
17095
|
getScript(_scriptFormat, activity) {
|
|
17056
17096
|
const expression = getScriptExpression(activity);
|
|
17057
|
-
if (expression === undefined) return
|
|
17097
|
+
if (expression === undefined) return undefined;
|
|
17058
17098
|
return new FeelScript(expression);
|
|
17059
17099
|
}
|
|
17060
17100
|
}
|
|
@@ -17065,7 +17105,7 @@ class FeelScript {
|
|
|
17065
17105
|
}
|
|
17066
17106
|
execute(scope, callback) {
|
|
17067
17107
|
try {
|
|
17068
|
-
const value = isFeelExpression(this.expression) ? evaluateFeel(this.expression,
|
|
17108
|
+
const value = isFeelExpression(this.expression) ? evaluateFeel(this.expression, getFeelScope(scope.environment)) : this.expression;
|
|
17069
17109
|
callback(null, value);
|
|
17070
17110
|
} catch (err) {
|
|
17071
17111
|
callback(err);
|
|
@@ -17092,20 +17132,32 @@ function getScriptExpression(activity) {
|
|
|
17092
17132
|
|
|
17093
17133
|
/**
|
|
17094
17134
|
* Flow extensions factory. Pass it to the engine via the environment `extensions` option.
|
|
17135
|
+
*
|
|
17136
|
+
* Returns undefined for an element that carries no zeebe extension data and nothing else to
|
|
17137
|
+
* format (no documentation, not a call activity) — bpmn-elements skips falsy extensions, so
|
|
17138
|
+
* plain elements run untouched, with no broker subscriptions or format-queue traffic.
|
|
17095
17139
|
* @param {import('bpmn-elements').Activity | import('bpmn-elements').Process} element
|
|
17096
17140
|
* @param {import('bpmn-elements').ContextInstance} context
|
|
17097
|
-
* @returns {FlowExtension}
|
|
17141
|
+
* @returns {FlowExtension | undefined}
|
|
17098
17142
|
*/
|
|
17099
17143
|
function extensions(element, context) {
|
|
17100
17144
|
switch (element.type) {
|
|
17101
17145
|
case 'bpmn:Process':
|
|
17146
|
+
// A process is always attached: a called process needs its inbound input promoted
|
|
17147
|
+
// even when it carries no zeebe extension data of its own.
|
|
17102
17148
|
return new ProcessExtensions(element, context);
|
|
17103
17149
|
case 'bpmn:SubProcess':
|
|
17104
17150
|
case 'bpmn:AdHocSubProcess':
|
|
17105
|
-
case 'bpmn:Transaction':
|
|
17106
|
-
|
|
17107
|
-
|
|
17108
|
-
return
|
|
17151
|
+
case 'bpmn:Transaction': {
|
|
17152
|
+
const subProcessExtensions = new SubProcessExtensions(element, context);
|
|
17153
|
+
if (subProcessExtensions.extensions.isEmpty) return undefined;
|
|
17154
|
+
return subProcessExtensions;
|
|
17155
|
+
}
|
|
17156
|
+
default: {
|
|
17157
|
+
const elementExtensions = new ElementExtensions(element, context);
|
|
17158
|
+
if (elementExtensions.extensions.isEmpty) return undefined;
|
|
17159
|
+
return elementExtensions;
|
|
17160
|
+
}
|
|
17109
17161
|
}
|
|
17110
17162
|
}
|
|
17111
17163
|
|
|
@@ -17149,6 +17201,7 @@ exports.evaluateFeel = evaluateFeel;
|
|
|
17149
17201
|
exports.evaluateFeelUnaryTest = evaluateFeelUnaryTest;
|
|
17150
17202
|
exports.extendFn = extendFn;
|
|
17151
17203
|
exports.extensions = extensions;
|
|
17204
|
+
exports.getFeelScope = getFeelScope;
|
|
17152
17205
|
exports.isFeelExpression = isFeelExpression;
|
|
17153
17206
|
exports.resolveValue = resolveValue;
|
|
17154
17207
|
exports.stripFeel = stripFeel;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0dep/bpmn-extensions",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Flow extensions for bpmn-elements: FEEL expressions and the zeebe-namespace BPMN extension elements",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"@types/mocha": "^10.0.10",
|
|
70
70
|
"@types/node": "^26.1.1",
|
|
71
71
|
"bpmn-elements": "^18.0.12",
|
|
72
|
-
"bpmn-moddle": "^
|
|
72
|
+
"bpmn-moddle": "^10.1.0",
|
|
73
73
|
"c8": "^12.0.0",
|
|
74
74
|
"chai": "^6.2.2",
|
|
75
75
|
"debug": "^4.4.3",
|
package/src/ElementExtensions.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
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
|
|
|
@@ -134,7 +135,7 @@ export class ElementExtensions {
|
|
|
134
135
|
|
|
135
136
|
// Multi-instance: aggregate the per-instance outputs into the `outputCollection` array.
|
|
136
137
|
if (loop?.hasOutputCollection) {
|
|
137
|
-
const output = { [loop.outputCollection]: loop.aggregate(jobResult, elementApi.environment
|
|
138
|
+
const output = { [loop.outputCollection]: loop.aggregate(jobResult, getFeelScope(elementApi.environment)) };
|
|
138
139
|
this.#assignOutput(elementApi, output);
|
|
139
140
|
return { output };
|
|
140
141
|
}
|
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.
|
|
@@ -27,20 +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
|
-
return { ...context?.environment?.variables, ...context?.content?.variables };
|
|
37
|
+
return evaluateFeel(templatedString, getFeelScope(context?.environment, context?.content?.variables));
|
|
46
38
|
}
|
|
@@ -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.
|
|
@@ -22,12 +22,12 @@ class FeelScriptRegistry {
|
|
|
22
22
|
/**
|
|
23
23
|
* @param {string} _scriptFormat
|
|
24
24
|
* @param {{ id: string, [x: string]: any }} activity
|
|
25
|
-
* @returns {import('bpmn-elements').Script} the FEEL script, or undefined when
|
|
26
|
-
* has no `zeebe:script`
|
|
25
|
+
* @returns {import('bpmn-elements').Script | undefined} the FEEL script, or undefined when
|
|
26
|
+
* the activity has no `zeebe:script`
|
|
27
27
|
*/
|
|
28
28
|
getScript(_scriptFormat, activity) {
|
|
29
29
|
const expression = getScriptExpression(activity);
|
|
30
|
-
if (expression === undefined) return
|
|
30
|
+
if (expression === undefined) return undefined;
|
|
31
31
|
return new FeelScript(expression);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -38,7 +38,7 @@ class FeelScript {
|
|
|
38
38
|
}
|
|
39
39
|
execute(scope, callback) {
|
|
40
40
|
try {
|
|
41
|
-
const value = isFeelExpression(this.expression) ? evaluateFeel(this.expression,
|
|
41
|
+
const value = isFeelExpression(this.expression) ? evaluateFeel(this.expression, getFeelScope(scope.environment)) : this.expression;
|
|
42
42
|
callback(null, value);
|
|
43
43
|
} catch (err) {
|
|
44
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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveValue } from '../feel.js';
|
|
1
|
+
import { resolveValue, getFeelScope } from '../feel.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* `zeebe:subscription` — rides on the referenced `bpmn:Message`, not on the catching element.
|
|
@@ -24,7 +24,7 @@ export class Subscription {
|
|
|
24
24
|
resolve(elementApi) {
|
|
25
25
|
return {
|
|
26
26
|
message: this.message,
|
|
27
|
-
correlationKey: resolveValue(this.correlationKey, elementApi.environment
|
|
27
|
+
correlationKey: resolveValue(this.correlationKey, getFeelScope(elementApi.environment)),
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
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`.
|
|
@@ -34,7 +34,7 @@ JobService.prototype.execute = function execute(executionMessage, callback) {
|
|
|
34
34
|
|
|
35
35
|
let jobType;
|
|
36
36
|
try {
|
|
37
|
-
jobType = resolveValue(this.taskType, environment
|
|
37
|
+
jobType = resolveValue(this.taskType, getFeelScope(environment));
|
|
38
38
|
} catch (err) {
|
|
39
39
|
return callback(err);
|
|
40
40
|
}
|
|
@@ -1,29 +1,34 @@
|
|
|
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
|
-
* for user tasks, the `zeebe:assignmentDefinition` (assignee, candidate users/groups)
|
|
5
|
+
* for user tasks, the `zeebe:assignmentDefinition` (assignee, candidate users/groups),
|
|
6
|
+
* `zeebe:priorityDefinition` (priority), and `zeebe:taskSchedule` (due/follow-up date).
|
|
6
7
|
*/
|
|
7
8
|
export class FormatActivity {
|
|
8
9
|
/**
|
|
9
10
|
* @param {import('bpmn-elements').Activity} activity
|
|
10
11
|
* @param {any} assignmentDefinition
|
|
12
|
+
* @param {any} [priorityDefinition]
|
|
13
|
+
* @param {any} [taskSchedule]
|
|
11
14
|
*/
|
|
12
|
-
constructor(activity, assignmentDefinition) {
|
|
15
|
+
constructor(activity, assignmentDefinition, priorityDefinition, taskSchedule) {
|
|
13
16
|
this.activity = activity;
|
|
14
17
|
this.assignmentDefinition = assignmentDefinition;
|
|
18
|
+
this.priorityDefinition = priorityDefinition;
|
|
19
|
+
this.taskSchedule = taskSchedule;
|
|
15
20
|
}
|
|
16
21
|
/**
|
|
17
22
|
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Activity>} elementApi
|
|
18
23
|
*/
|
|
19
24
|
resolve(elementApi) {
|
|
20
|
-
const scope = elementApi.environment
|
|
25
|
+
const scope = getFeelScope(elementApi.environment);
|
|
21
26
|
const result = {};
|
|
22
27
|
|
|
23
28
|
const documentation = this.activity.behaviour.documentation;
|
|
24
29
|
if (documentation && !elementApi.content.description) {
|
|
25
30
|
const text = documentation[0]?.text;
|
|
26
|
-
if (text) result.description = resolveValue(text, scope);
|
|
31
|
+
if (text) result.description = /** @type {string} */ (resolveValue(text, scope));
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
const assignment = this.assignmentDefinition;
|
|
@@ -36,6 +41,22 @@ export class FormatActivity {
|
|
|
36
41
|
if (groups) result.candidateGroups = groups;
|
|
37
42
|
}
|
|
38
43
|
|
|
44
|
+
const priority = this.priorityDefinition?.priority;
|
|
45
|
+
if (priority !== undefined) {
|
|
46
|
+
const resolved = resolveValue(priority, scope);
|
|
47
|
+
// A static attribute value is a string — expose a numeric one as a number (like retries).
|
|
48
|
+
const num = Number(resolved);
|
|
49
|
+
/** @type {Number | string | undefined} */
|
|
50
|
+
result.priority = typeof resolved === 'string' && resolved.trim() !== '' && Number.isFinite(num) ? num : resolved;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const schedule = this.taskSchedule;
|
|
54
|
+
if (schedule) {
|
|
55
|
+
const { dueDate, followUpDate } = schedule;
|
|
56
|
+
if (dueDate !== undefined) result.dueDate = resolveDate(dueDate, scope);
|
|
57
|
+
if (followUpDate !== undefined) result.followUpDate = resolveDate(followUpDate, scope);
|
|
58
|
+
}
|
|
59
|
+
|
|
39
60
|
return result;
|
|
40
61
|
}
|
|
41
62
|
}
|
|
@@ -58,15 +79,33 @@ export class FormatProcess {
|
|
|
58
79
|
const documentation = this.process.behaviour.documentation;
|
|
59
80
|
if (documentation && !elementApi.content.description) {
|
|
60
81
|
const text = documentation[0]?.text;
|
|
61
|
-
if (text) result.description = resolveValue(text, elementApi.environment
|
|
82
|
+
if (text) result.description = /** @type {string} */ (resolveValue(text, getFeelScope(elementApi.environment)));
|
|
62
83
|
}
|
|
63
84
|
return result;
|
|
64
85
|
}
|
|
65
86
|
}
|
|
66
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Resolve a due/follow-up date to an ISO 8601 string. A FEEL temporal result (a luxon
|
|
90
|
+
* DateTime) or a Date is ISO-stringified — content is JSON-serialized on `getState()`, so a
|
|
91
|
+
* string is the shape that survives a recover round trip. Anything else passes through as-is.
|
|
92
|
+
* @param {any} value
|
|
93
|
+
* @param {any} scope
|
|
94
|
+
* @returns {string | undefined} Date as iso string
|
|
95
|
+
*/
|
|
96
|
+
function resolveDate(value, scope) {
|
|
97
|
+
const resolved = resolveValue(value, scope);
|
|
98
|
+
if (typeof resolved?.toISO === 'function') return resolved.toISO();
|
|
99
|
+
if (resolved instanceof Date) return resolved.toISOString();
|
|
100
|
+
return resolved;
|
|
101
|
+
}
|
|
102
|
+
|
|
67
103
|
/**
|
|
68
104
|
* Resolve a candidate users/groups value to a string array. A FEEL expression may yield an
|
|
69
105
|
* array directly, otherwise a comma-separated string is split.
|
|
106
|
+
* @param {any} value
|
|
107
|
+
* @param {any} scope
|
|
108
|
+
* @returns {string[] | undefined}
|
|
70
109
|
*/
|
|
71
110
|
function resolveList(value, scope) {
|
|
72
111
|
if (!value) return undefined;
|
package/src/feel.js
CHANGED
|
@@ -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
|
@@ -22,6 +22,7 @@ import { Subscription } from './extensions/Subscription.js';
|
|
|
22
22
|
* @property {any} [calledDecision]
|
|
23
23
|
* @property {Function} [Service]
|
|
24
24
|
* @property {Subscription} [subscription]
|
|
25
|
+
* @property {boolean} [isEmpty] no zeebe extension data found and nothing to format — the element needs no extension
|
|
25
26
|
*/
|
|
26
27
|
|
|
27
28
|
/**
|
|
@@ -34,10 +35,15 @@ export function getExtensions(element, context) {
|
|
|
34
35
|
const result = {};
|
|
35
36
|
const isProcess = element.type === 'bpmn:Process';
|
|
36
37
|
|
|
37
|
-
let assignmentDefinition;
|
|
38
|
+
let assignmentDefinition, priorityDefinition, taskSchedule;
|
|
39
|
+
let foundZeebe = false;
|
|
38
40
|
const values = element.behaviour.extensionElements?.values;
|
|
39
41
|
if (values) {
|
|
40
42
|
for (const ext of values) {
|
|
43
|
+
// Any zeebe-namespaced extension element counts as found — including ones with no
|
|
44
|
+
// handler (e.g. the bare `zeebe:userTask` marker), which keep the element extension
|
|
45
|
+
// loaded so e.g. a native user task's completion payload is merged.
|
|
46
|
+
if (ext.$type.startsWith('zeebe:')) foundZeebe = true;
|
|
41
47
|
switch (ext.$type) {
|
|
42
48
|
case 'zeebe:IoMapping':
|
|
43
49
|
result.io = new IoMapping(element, ext);
|
|
@@ -51,6 +57,12 @@ export function getExtensions(element, context) {
|
|
|
51
57
|
case 'zeebe:AssignmentDefinition':
|
|
52
58
|
assignmentDefinition = ext;
|
|
53
59
|
break;
|
|
60
|
+
case 'zeebe:PriorityDefinition':
|
|
61
|
+
priorityDefinition = ext;
|
|
62
|
+
break;
|
|
63
|
+
case 'zeebe:TaskSchedule':
|
|
64
|
+
taskSchedule = ext;
|
|
65
|
+
break;
|
|
54
66
|
case 'zeebe:ExecutionListeners':
|
|
55
67
|
if (ext.listeners?.length) result.listeners = new ExecutionListeners(element, ext);
|
|
56
68
|
break;
|
|
@@ -92,7 +104,16 @@ export function getExtensions(element, context) {
|
|
|
92
104
|
);
|
|
93
105
|
if (loopExtension?.outputCollection) result.loop = new LoopCharacteristics(loopExtension);
|
|
94
106
|
|
|
95
|
-
|
|
107
|
+
// Nothing zeebe-flavoured and nothing to format — the element needs no extension. A call
|
|
108
|
+
// activity is never inert: its output unwrap/merge is type-driven (the standard BPMN
|
|
109
|
+
// `calledElement` attribute needs no zeebe elements). Ignored for processes — a called
|
|
110
|
+
// process always needs its inbound input promoted, whatever it carries itself.
|
|
111
|
+
result.isEmpty =
|
|
112
|
+
!foundZeebe && !result.subscription && !loopExtension && !element.behaviour.documentation && element.type !== 'bpmn:CallActivity';
|
|
113
|
+
|
|
114
|
+
result.format = isProcess
|
|
115
|
+
? new FormatProcess(element)
|
|
116
|
+
: new FormatActivity(element, assignmentDefinition, priorityDefinition, taskSchedule);
|
|
96
117
|
|
|
97
118
|
return result;
|
|
98
119
|
}
|
package/src/index.js
CHANGED
|
@@ -5,7 +5,7 @@ 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
|
|
|
@@ -18,20 +18,32 @@ export { ServiceError, FormatError } from './Errors.js';
|
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Flow extensions factory. Pass it to the engine via the environment `extensions` option.
|
|
21
|
+
*
|
|
22
|
+
* Returns undefined for an element that carries no zeebe extension data and nothing else to
|
|
23
|
+
* format (no documentation, not a call activity) — bpmn-elements skips falsy extensions, so
|
|
24
|
+
* plain elements run untouched, with no broker subscriptions or format-queue traffic.
|
|
21
25
|
* @param {import('bpmn-elements').Activity | import('bpmn-elements').Process} element
|
|
22
26
|
* @param {import('bpmn-elements').ContextInstance} context
|
|
23
|
-
* @returns {FlowExtension}
|
|
27
|
+
* @returns {FlowExtension | undefined}
|
|
24
28
|
*/
|
|
25
29
|
export function extensions(element, context) {
|
|
26
30
|
switch (element.type) {
|
|
27
31
|
case 'bpmn:Process':
|
|
32
|
+
// A process is always attached: a called process needs its inbound input promoted
|
|
33
|
+
// even when it carries no zeebe extension data of its own.
|
|
28
34
|
return new ProcessExtensions(element, context);
|
|
29
35
|
case 'bpmn:SubProcess':
|
|
30
36
|
case 'bpmn:AdHocSubProcess':
|
|
31
|
-
case 'bpmn:Transaction':
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return
|
|
37
|
+
case 'bpmn:Transaction': {
|
|
38
|
+
const subProcessExtensions = new SubProcessExtensions(element, context);
|
|
39
|
+
if (subProcessExtensions.extensions.isEmpty) return undefined;
|
|
40
|
+
return subProcessExtensions;
|
|
41
|
+
}
|
|
42
|
+
default: {
|
|
43
|
+
const elementExtensions = new ElementExtensions(element, context);
|
|
44
|
+
if (elementExtensions.extensions.isEmpty) return undefined;
|
|
45
|
+
return elementExtensions;
|
|
46
|
+
}
|
|
35
47
|
}
|
|
36
48
|
}
|
|
37
49
|
|
package/types/index.d.ts
CHANGED
|
@@ -4,8 +4,12 @@ declare module '@0dep/bpmn-extensions' {
|
|
|
4
4
|
* */
|
|
5
5
|
/**
|
|
6
6
|
* Flow extensions factory. Pass it to the engine via the environment `extensions` option.
|
|
7
|
+
*
|
|
8
|
+
* Returns undefined for an element that carries no zeebe extension data and nothing else to
|
|
9
|
+
* format (no documentation, not a call activity) — bpmn-elements skips falsy extensions, so
|
|
10
|
+
* plain elements run untouched, with no broker subscriptions or format-queue traffic.
|
|
7
11
|
* */
|
|
8
|
-
export function extensions(element: import("bpmn-elements").Activity | import("bpmn-elements").Process, context: import("bpmn-elements").ContextInstance): FlowExtension;
|
|
12
|
+
export function extensions(element: import("bpmn-elements").Activity | import("bpmn-elements").Process, context: import("bpmn-elements").ContextInstance): FlowExtension | undefined;
|
|
9
13
|
/**
|
|
10
14
|
* Behaviour extend function for moddle-context-serializer.
|
|
11
15
|
*
|
|
@@ -171,6 +175,16 @@ declare module '@0dep/bpmn-extensions' {
|
|
|
171
175
|
* @param context Variables in scope for FEEL evaluation
|
|
172
176
|
* */
|
|
173
177
|
export function resolveValue(value: any, context?: Record<string, any>): any;
|
|
178
|
+
/**
|
|
179
|
+
* Build the FEEL evaluation scope for an environment. The environment services are exposed
|
|
180
|
+
* under `services`, callable as FEEL functions (e.g. `services.isEligible(order)`) — resume-safe,
|
|
181
|
+
* since the host re-supplies services at recover time, unlike functions smuggled through
|
|
182
|
+
* variables, which JSON state serialization drops. The environment variables form the base
|
|
183
|
+
* scope, overlaid with any local variables (a job result, element-local variables such as a
|
|
184
|
+
* multi-instance item); a variable named `services` shadows the overlay.
|
|
185
|
+
* @param localVariables scope-local overlay
|
|
186
|
+
* */
|
|
187
|
+
export function getFeelScope(environment?: import("bpmn-elements").Environment, localVariables?: Record<string, any>): Record<string, any>;
|
|
174
188
|
export class ServiceError extends Error {
|
|
175
189
|
constructor(jobType: any);
|
|
176
190
|
code: string;
|
|
@@ -200,22 +214,33 @@ declare module '@0dep/bpmn-extensions' {
|
|
|
200
214
|
calledDecision?: any;
|
|
201
215
|
Service?: Function | undefined;
|
|
202
216
|
subscription?: Subscription | undefined;
|
|
217
|
+
/**
|
|
218
|
+
* no zeebe extension data found and nothing to format — the element needs no extension
|
|
219
|
+
*/
|
|
220
|
+
isEmpty?: boolean | undefined;
|
|
203
221
|
};
|
|
204
222
|
/**
|
|
205
223
|
* Format an activity on enter from its behaviour and extensions: documentation and,
|
|
206
|
-
* for user tasks, the `zeebe:assignmentDefinition` (assignee, candidate users/groups)
|
|
224
|
+
* for user tasks, the `zeebe:assignmentDefinition` (assignee, candidate users/groups),
|
|
225
|
+
* `zeebe:priorityDefinition` (priority), and `zeebe:taskSchedule` (due/follow-up date).
|
|
207
226
|
*/
|
|
208
227
|
class FormatActivity {
|
|
209
228
|
|
|
210
|
-
constructor(activity: import("bpmn-elements").Activity, assignmentDefinition: any);
|
|
229
|
+
constructor(activity: import("bpmn-elements").Activity, assignmentDefinition: any, priorityDefinition?: any, taskSchedule?: any);
|
|
211
230
|
activity: import("bpmn-elements").Activity;
|
|
212
231
|
assignmentDefinition: any;
|
|
232
|
+
priorityDefinition: any;
|
|
233
|
+
taskSchedule: any;
|
|
213
234
|
|
|
214
235
|
resolve(elementApi: import("bpmn-elements").IApi<import("bpmn-elements").Activity>): {
|
|
215
|
-
description:
|
|
236
|
+
description: string;
|
|
216
237
|
assignee: any;
|
|
217
|
-
candidateUsers:
|
|
218
|
-
candidateGroups:
|
|
238
|
+
candidateUsers: string[];
|
|
239
|
+
candidateGroups: string[];
|
|
240
|
+
|
|
241
|
+
priority: number | string | undefined;
|
|
242
|
+
dueDate: string | undefined;
|
|
243
|
+
followUpDate: string | undefined;
|
|
219
244
|
};
|
|
220
245
|
}
|
|
221
246
|
/**
|
|
@@ -227,7 +252,7 @@ declare module '@0dep/bpmn-extensions' {
|
|
|
227
252
|
process: import("bpmn-elements").Process;
|
|
228
253
|
|
|
229
254
|
resolve(elementApi: import("bpmn-elements").IApi<import("bpmn-elements").Process>): {
|
|
230
|
-
description:
|
|
255
|
+
description: string;
|
|
231
256
|
};
|
|
232
257
|
}
|
|
233
258
|
/**
|
|
@@ -338,7 +363,7 @@ declare module '@0dep/bpmn-extensions' {
|
|
|
338
363
|
get hasOutputCollection(): boolean;
|
|
339
364
|
/**
|
|
340
365
|
* @param indexedOutput bpmn-elements index-keyed instance outputs
|
|
341
|
-
* @param baseScope
|
|
366
|
+
* @param baseScope base scope for `outputElement` (services + process variables)
|
|
342
367
|
* */
|
|
343
368
|
aggregate(indexedOutput: Record<string, any> | undefined, baseScope: Record<string, any>): any[];
|
|
344
369
|
}
|
package/types/index.d.ts.map
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"evaluateFeel",
|
|
16
16
|
"evaluateFeelUnaryTest",
|
|
17
17
|
"resolveValue",
|
|
18
|
+
"getFeelScope",
|
|
18
19
|
"ServiceError",
|
|
19
20
|
"FormatError",
|
|
20
21
|
"FormatActivity",
|
|
@@ -65,6 +66,6 @@
|
|
|
65
66
|
null,
|
|
66
67
|
null
|
|
67
68
|
],
|
|
68
|
-
"mappings": "
|
|
69
|
+
"mappings": ";;;;;;;;;;;iBA4BgBA,UAAUA;;;;;;;;iBA6BVC,QAAQA;;;;;;;;;;;;;;;;iBC9CRC,eAAeA;;;;;;;;;;;;;;iBCEfC,WAAWA;;;;;;;;;;;;;;;iBCIXC,UAAUA;cAAVA,UAAUA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cCEbC,iBAAiBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cCZjBC,iBAAiBA;;;;;;;;;;;;;;;;;;;cCIjBC,oBAAoBA;;;;;;;iBCEjBC,gBAAgBA;;;;iBAShBC,SAASA;;;;;;iBAUTC,YAAYA;;;;;;;;iBAYZC,qBAAqBA;;;;;;iBAYrBC,YAAYA;;;;;;;;;;iBAgBZC,YAAYA;cCxEfC,YAAYA;;;;;;;cAQZC,WAAWA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OCDXC,cAAcA;;;;;;;;;;;;;;;;;;;;;;OA2DdC,aAAaA;;;;;;;;;;;;;;;;;;;;OCrDbC,SAASA;;;;;;;;;;;;;;;;;;;;;;;OCPTC,WAAWA;;;;;;;;;;;OCEXC,UAAUA;;;;;;;;;;;;;;;OCIVC,kBAAkBA;;;;;;;;;;;;;;;;;;;OCFlBC,IAAIA;;;;;;;;;;;;;;;;;;;;;;OCGJC,mBAAmBA;;;;;;;;;;;;;;;;;;;OCHnBC,YAAYA",
|
|
69
70
|
"ignoreList": []
|
|
70
71
|
}
|