@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/README.md
CHANGED
|
@@ -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).
|
|
@@ -40,6 +46,10 @@ npm install @0dep/bpmn-extensions bpmn-elements
|
|
|
40
46
|
- **`zeebe:calledElement`** — call activity target process id.
|
|
41
47
|
- **`zeebe:loopCharacteristics`** — collection-based multi-instance (`inputCollection` / `inputElement`),
|
|
42
48
|
sequential or parallel, with `outputElement` aggregated into the `outputCollection` array in input order.
|
|
49
|
+
- **`zeebe:subscription`** — the referenced message's `correlationKey` is resolved when the catching
|
|
50
|
+
element (receive task, message event) starts waiting, and exposed on its content as
|
|
51
|
+
`subscription: { message, correlationKey }` — route an incoming message by matching it against the
|
|
52
|
+
waiting elements (`getPostponed()`) and signalling the right one.
|
|
43
53
|
|
|
44
54
|
## API
|
|
45
55
|
|
|
@@ -48,7 +58,8 @@ npm install @0dep/bpmn-extensions bpmn-elements
|
|
|
48
58
|
bpmn-elements expects on the behaviour: call activity process id and multi-instance collection).
|
|
49
59
|
- `FeelExpressions()` — a bpmn-elements `IExpressions` implementation backed by FEEL.
|
|
50
60
|
- `FeelScripts()` — a bpmn-elements `scripts` implementation that runs `zeebe:script` FEEL expressions.
|
|
51
|
-
- FEEL helpers: `isFeelExpression`, `stripFeel`, `evaluateFeel`, `evaluateFeelUnaryTest`, `resolveValue
|
|
61
|
+
- FEEL helpers: `isFeelExpression`, `stripFeel`, `evaluateFeel`, `evaluateFeelUnaryTest`, `resolveValue`,
|
|
62
|
+
`getFeelScope` (the scope every expression is evaluated in: `services` + environment variables + local overlay).
|
|
52
63
|
- `JobService`, `ServiceError`, `FormatError`.
|
|
53
64
|
|
|
54
65
|
## Usage
|
|
@@ -128,8 +139,12 @@ in `test/helpers/factory.js`, then run on a real bpmn-elements `Definition`. The
|
|
|
128
139
|
the engine's `Logger` to the [`debug`](https://www.npmjs.com/package/debug) package, so
|
|
129
140
|
`DEBUG=bpmn-extensions:* npm test` traces the engine and extensions (`:error:*` for errors only).
|
|
130
141
|
|
|
131
|
-
|
|
132
|
-
|
|
142
|
+
# Ecosystem
|
|
143
|
+
|
|
144
|
+
- [bpmn-engine](https://github.com/paed01/bpmn-engine) — BPMN 2.0 execution engine wrapping `bpmn-elements`; the batteries-included way to run, stop, resume, and recover flows.
|
|
145
|
+
- [bpmn-middleware](https://github.com/zerodep/bpmn-middleware) — Express middleware exposing the engine over HTTP, with pluggable state storage.
|
|
146
|
+
- [@onify/flow-extensions](https://github.com/onify/flow-extensions) — Onify Flow extensions for `bpmn-elements`.
|
|
147
|
+
- [dmn-elements](https://github.com/zerodep/dmn-elements) — Executable DMN 1.3 decision elements; back a Business Rule Task with it (see [Conformance](/docs/Conformance.md#business-rule-task-and-dmn)).
|
|
133
148
|
|
|
134
149
|
## License
|
|
135
150
|
|
package/dist/index.js
CHANGED
|
@@ -16203,11 +16203,11 @@ function evaluateFeel(expression, context) {
|
|
|
16203
16203
|
* @param {string} expression FEEL unary test
|
|
16204
16204
|
* @param {any} input The value being tested, available as `?` in the test
|
|
16205
16205
|
* @param {Record<string, any>} [context] Additional variables in scope
|
|
16206
|
-
* @returns {boolean}
|
|
16206
|
+
* @returns {boolean} true only when the test is satisfied — an undecidable (null) test is false
|
|
16207
16207
|
*/
|
|
16208
16208
|
function evaluateFeelUnaryTest(expression, input, context) {
|
|
16209
|
-
const { value } = unaryTest(stripFeel(expression), { ...
|
|
16210
|
-
return value;
|
|
16209
|
+
const { value } = unaryTest(stripFeel(expression), { ...context, '?': input });
|
|
16210
|
+
return value === true;
|
|
16211
16211
|
}
|
|
16212
16212
|
|
|
16213
16213
|
/**
|
|
@@ -16222,17 +16222,39 @@ function resolveValue(value, context) {
|
|
|
16222
16222
|
return value;
|
|
16223
16223
|
}
|
|
16224
16224
|
|
|
16225
|
+
/**
|
|
16226
|
+
* Build the FEEL evaluation scope for an environment. The environment services are exposed
|
|
16227
|
+
* under `services`, callable as FEEL functions (e.g. `services.isEligible(order)`) — resume-safe,
|
|
16228
|
+
* since the host re-supplies services at recover time, unlike functions smuggled through
|
|
16229
|
+
* variables, which JSON state serialization drops. The environment variables form the base
|
|
16230
|
+
* scope, overlaid with any local variables (a job result, element-local variables such as a
|
|
16231
|
+
* multi-instance item); a variable named `services` shadows the overlay.
|
|
16232
|
+
* @param {import('bpmn-elements').Environment} [environment]
|
|
16233
|
+
* @param {Record<string, any>} [localVariables] scope-local overlay
|
|
16234
|
+
* @returns {Record<string, any>}
|
|
16235
|
+
*/
|
|
16236
|
+
function getFeelScope(environment, localVariables) {
|
|
16237
|
+
return { services: environment?.services, ...environment?.variables, ...localVariables };
|
|
16238
|
+
}
|
|
16239
|
+
|
|
16225
16240
|
/**
|
|
16226
16241
|
* Format an activity on enter from its behaviour and extensions: documentation and,
|
|
16227
16242
|
* for user tasks, the `zeebe:assignmentDefinition` (assignee, candidate users/groups).
|
|
16228
16243
|
*/
|
|
16229
16244
|
class FormatActivity {
|
|
16245
|
+
/**
|
|
16246
|
+
* @param {import('bpmn-elements').Activity} activity
|
|
16247
|
+
* @param {any} assignmentDefinition
|
|
16248
|
+
*/
|
|
16230
16249
|
constructor(activity, assignmentDefinition) {
|
|
16231
16250
|
this.activity = activity;
|
|
16232
16251
|
this.assignmentDefinition = assignmentDefinition;
|
|
16233
16252
|
}
|
|
16253
|
+
/**
|
|
16254
|
+
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Activity>} elementApi
|
|
16255
|
+
*/
|
|
16234
16256
|
resolve(elementApi) {
|
|
16235
|
-
const scope = elementApi.environment
|
|
16257
|
+
const scope = getFeelScope(elementApi.environment);
|
|
16236
16258
|
const result = {};
|
|
16237
16259
|
|
|
16238
16260
|
const documentation = this.activity.behaviour.documentation;
|
|
@@ -16259,15 +16281,21 @@ class FormatActivity {
|
|
|
16259
16281
|
* Format a process on enter: documentation.
|
|
16260
16282
|
*/
|
|
16261
16283
|
class FormatProcess {
|
|
16284
|
+
/**
|
|
16285
|
+
* @param {import('bpmn-elements').Process} bp
|
|
16286
|
+
*/
|
|
16262
16287
|
constructor(bp) {
|
|
16263
16288
|
this.process = bp;
|
|
16264
16289
|
}
|
|
16290
|
+
/**
|
|
16291
|
+
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Process>} elementApi
|
|
16292
|
+
*/
|
|
16265
16293
|
resolve(elementApi) {
|
|
16266
16294
|
const result = {};
|
|
16267
16295
|
const documentation = this.process.behaviour.documentation;
|
|
16268
16296
|
if (documentation && !elementApi.content.description) {
|
|
16269
16297
|
const text = documentation[0]?.text;
|
|
16270
|
-
if (text) result.description = resolveValue(text, elementApi.environment
|
|
16298
|
+
if (text) result.description = resolveValue(text, getFeelScope(elementApi.environment));
|
|
16271
16299
|
}
|
|
16272
16300
|
return result;
|
|
16273
16301
|
}
|
|
@@ -16280,7 +16308,7 @@ class FormatProcess {
|
|
|
16280
16308
|
function resolveList(value, scope) {
|
|
16281
16309
|
if (!value) return undefined;
|
|
16282
16310
|
const resolved = resolveValue(value, scope);
|
|
16283
|
-
if (Array.isArray(resolved)) return resolved.filter(
|
|
16311
|
+
if (Array.isArray(resolved)) return resolved.filter(Boolean);
|
|
16284
16312
|
if (typeof resolved !== 'string') return undefined;
|
|
16285
16313
|
return resolved
|
|
16286
16314
|
.split(',')
|
|
@@ -16316,7 +16344,7 @@ class IoMapping {
|
|
|
16316
16344
|
* @param {import('bpmn-elements').IApi<any>} elementApi
|
|
16317
16345
|
*/
|
|
16318
16346
|
getInput(elementApi) {
|
|
16319
|
-
const scope = elementApi.environment
|
|
16347
|
+
const scope = getFeelScope(elementApi.environment);
|
|
16320
16348
|
const result = {};
|
|
16321
16349
|
for (const { source, target } of this.inputParameters) {
|
|
16322
16350
|
if (!target) continue;
|
|
@@ -16330,7 +16358,7 @@ class IoMapping {
|
|
|
16330
16358
|
* @param {Record<string, any>} [jobResult] Variables produced by the job
|
|
16331
16359
|
*/
|
|
16332
16360
|
getOutput(elementApi, jobResult) {
|
|
16333
|
-
const scope =
|
|
16361
|
+
const scope = getFeelScope(elementApi.environment, jobResult);
|
|
16334
16362
|
const result = {};
|
|
16335
16363
|
for (const { source, target } of this.outputParameters) {
|
|
16336
16364
|
if (!target) continue;
|
|
@@ -16386,7 +16414,7 @@ class Properties {
|
|
|
16386
16414
|
this.properties = properties.properties || [];
|
|
16387
16415
|
}
|
|
16388
16416
|
resolve(elementApi) {
|
|
16389
|
-
const scope = elementApi.environment
|
|
16417
|
+
const scope = getFeelScope(elementApi.environment);
|
|
16390
16418
|
const result = {};
|
|
16391
16419
|
for (const { name, value } of this.properties) {
|
|
16392
16420
|
if (name === undefined) continue;
|
|
@@ -16482,7 +16510,7 @@ class Form {
|
|
|
16482
16510
|
this.formDefinition = formDefinition;
|
|
16483
16511
|
}
|
|
16484
16512
|
resolve(elementApi) {
|
|
16485
|
-
const scope = elementApi.environment
|
|
16513
|
+
const scope = getFeelScope(elementApi.environment);
|
|
16486
16514
|
const { formId, formKey, externalReference, bindingType, versionTag } = this.formDefinition;
|
|
16487
16515
|
return {
|
|
16488
16516
|
...(formId !== undefined && { formId: resolveValue(formId, scope) }),
|
|
@@ -16515,7 +16543,7 @@ class LoopCharacteristics {
|
|
|
16515
16543
|
}
|
|
16516
16544
|
/**
|
|
16517
16545
|
* @param {Record<string, any>|undefined} indexedOutput bpmn-elements index-keyed instance outputs
|
|
16518
|
-
* @param {Record<string, any>} baseScope
|
|
16546
|
+
* @param {Record<string, any>} baseScope base scope for `outputElement` (services + process variables)
|
|
16519
16547
|
* @returns {any[]}
|
|
16520
16548
|
*/
|
|
16521
16549
|
aggregate(indexedOutput, baseScope) {
|
|
@@ -16558,13 +16586,19 @@ function JobService(taskType, activity) {
|
|
|
16558
16586
|
this.activity = activity;
|
|
16559
16587
|
}
|
|
16560
16588
|
|
|
16589
|
+
/**
|
|
16590
|
+
* Execute
|
|
16591
|
+
* @param {import('bpmn-elements').ElementBrokerMessage} executionMessage
|
|
16592
|
+
* @param {CallableFunction} callback
|
|
16593
|
+
* @returns {any}
|
|
16594
|
+
*/
|
|
16561
16595
|
JobService.prototype.execute = function execute(executionMessage, callback) {
|
|
16562
16596
|
const activity = this.activity;
|
|
16563
16597
|
const environment = activity.environment;
|
|
16564
16598
|
|
|
16565
16599
|
let jobType;
|
|
16566
16600
|
try {
|
|
16567
|
-
jobType = resolveValue(this.taskType, environment
|
|
16601
|
+
jobType = resolveValue(this.taskType, getFeelScope(environment));
|
|
16568
16602
|
} catch (err) {
|
|
16569
16603
|
return callback(err);
|
|
16570
16604
|
}
|
|
@@ -16575,23 +16609,58 @@ JobService.prototype.execute = function execute(executionMessage, callback) {
|
|
|
16575
16609
|
return serviceFn.call(activity, executionMessage, (err, result) => callback(err, result));
|
|
16576
16610
|
};
|
|
16577
16611
|
|
|
16612
|
+
/**
|
|
16613
|
+
* `zeebe:subscription` — rides on the referenced `bpmn:Message`, not on the catching element.
|
|
16614
|
+
*
|
|
16615
|
+
* Resolves the correlation key in the activity's variable scope on enter and exposes it on the
|
|
16616
|
+
* element content as `subscription`. Correlation itself is the embedding application's job:
|
|
16617
|
+
* read the resolved key off the waiting activity (`getPostponed()` → `content.subscription`)
|
|
16618
|
+
* and signal the matching one (`api.signal(...)`).
|
|
16619
|
+
*/
|
|
16620
|
+
class Subscription {
|
|
16621
|
+
/**
|
|
16622
|
+
* @param {{ id: string, name?: string }} messageRef The serialized message reference
|
|
16623
|
+
* @param {{ correlationKey: string }} subscription The `zeebe:Subscription` behaviour
|
|
16624
|
+
*/
|
|
16625
|
+
constructor(messageRef, subscription) {
|
|
16626
|
+
this.message = { id: messageRef.id, name: messageRef.name };
|
|
16627
|
+
this.correlationKey = subscription.correlationKey;
|
|
16628
|
+
}
|
|
16629
|
+
/**
|
|
16630
|
+
* @param {import('bpmn-elements').IApi<any>} elementApi
|
|
16631
|
+
* @returns {{ message: { id: string, name?: string }, correlationKey: any }}
|
|
16632
|
+
*/
|
|
16633
|
+
resolve(elementApi) {
|
|
16634
|
+
return {
|
|
16635
|
+
message: this.message,
|
|
16636
|
+
correlationKey: resolveValue(this.correlationKey, getFeelScope(elementApi.environment)),
|
|
16637
|
+
};
|
|
16638
|
+
}
|
|
16639
|
+
}
|
|
16640
|
+
|
|
16641
|
+
/**
|
|
16642
|
+
* The extension handlers assembled for one element.
|
|
16643
|
+
* @typedef {object} ExtensionHandlers
|
|
16644
|
+
* @property {FormatActivity | FormatProcess} format
|
|
16645
|
+
* @property {IoMapping} [io]
|
|
16646
|
+
* @property {TaskHeaders} [headers]
|
|
16647
|
+
* @property {Properties} [properties]
|
|
16648
|
+
* @property {ExecutionListeners} [listeners]
|
|
16649
|
+
* @property {Form} [form]
|
|
16650
|
+
* @property {LoopCharacteristics} [loop]
|
|
16651
|
+
* @property {any} [script]
|
|
16652
|
+
* @property {any} [calledDecision]
|
|
16653
|
+
* @property {Function} [Service]
|
|
16654
|
+
* @property {Subscription} [subscription]
|
|
16655
|
+
*/
|
|
16656
|
+
|
|
16578
16657
|
/**
|
|
16579
16658
|
* Inspect an element's extension elements and assemble the extension handlers it needs.
|
|
16580
16659
|
* @param {import('bpmn-elements').Activity | import('bpmn-elements').Process} element
|
|
16581
|
-
* @
|
|
16582
|
-
*
|
|
16583
|
-
* io?: IoMapping,
|
|
16584
|
-
* headers?: TaskHeaders,
|
|
16585
|
-
* properties?: Properties,
|
|
16586
|
-
* listeners?: ExecutionListeners,
|
|
16587
|
-
* form?: Form,
|
|
16588
|
-
* loop?: LoopCharacteristics,
|
|
16589
|
-
* script?: any,
|
|
16590
|
-
* calledDecision?: any,
|
|
16591
|
-
* Service?: Function,
|
|
16592
|
-
* }}
|
|
16660
|
+
* @param {import('bpmn-elements').ContextInstance} [context]
|
|
16661
|
+
* @returns {ExtensionHandlers}
|
|
16593
16662
|
*/
|
|
16594
|
-
function getExtensions(element) {
|
|
16663
|
+
function getExtensions(element, context) {
|
|
16595
16664
|
const result = {};
|
|
16596
16665
|
const isProcess = element.type === 'bpmn:Process';
|
|
16597
16666
|
|
|
@@ -16635,6 +16704,18 @@ function getExtensions(element) {
|
|
|
16635
16704
|
}
|
|
16636
16705
|
}
|
|
16637
16706
|
|
|
16707
|
+
// A message catch (receive task / message event) may reference a bpmn:Message carrying a
|
|
16708
|
+
// zeebe:subscription with the correlation key. The subscription lives on the message element,
|
|
16709
|
+
// which the instantiated bpmn-elements Message strips — read it off the serialized context.
|
|
16710
|
+
const messageRef =
|
|
16711
|
+
element.behaviour.messageRef ?? element.behaviour.eventDefinitions?.find((ed) => ed.behaviour?.messageRef)?.behaviour.messageRef;
|
|
16712
|
+
if (messageRef?.id && context) {
|
|
16713
|
+
const subscription = context.definitionContext
|
|
16714
|
+
.getActivityById(messageRef.id)
|
|
16715
|
+
?.behaviour.extensionElements?.values?.find((v) => v.$type === 'zeebe:Subscription');
|
|
16716
|
+
if (subscription?.correlationKey) result.subscription = new Subscription(messageRef, subscription);
|
|
16717
|
+
}
|
|
16718
|
+
|
|
16638
16719
|
// Output side of a multi-instance `zeebe:loopCharacteristics` (the input side is lifted by extendFn).
|
|
16639
16720
|
const loopExtension = element.behaviour.loopCharacteristics?.behaviour?.extensionElements?.values?.find(
|
|
16640
16721
|
(v) => v.$type === 'zeebe:LoopCharacteristics'
|
|
@@ -16648,6 +16729,13 @@ function getExtensions(element) {
|
|
|
16648
16729
|
|
|
16649
16730
|
const SUBPROCESS_TYPES = new Set(['bpmn:SubProcess', 'bpmn:AdHocSubProcess', 'bpmn:Transaction']);
|
|
16650
16731
|
|
|
16732
|
+
// Consumer tags shared between activate (here and in the SubProcessExtensions override) and
|
|
16733
|
+
// deactivate — deactivate cancels by tag, so both must subscribe with these exact tags.
|
|
16734
|
+
const ON_ENTER_TAG = '0dep-bpmn-extensions:on-enter';
|
|
16735
|
+
const ON_EXECUTED_TAG = '0dep-bpmn-extensions:on-executed';
|
|
16736
|
+
const ON_START_TAG = '0dep-bpmn-extensions:on-start';
|
|
16737
|
+
const ON_END_TAG = '0dep-bpmn-extensions:on-end';
|
|
16738
|
+
|
|
16651
16739
|
/**
|
|
16652
16740
|
* Activity-level extensions.
|
|
16653
16741
|
*
|
|
@@ -16656,43 +16744,55 @@ const SUBPROCESS_TYPES = new Set(['bpmn:SubProcess', 'bpmn:AdHocSubProcess', 'bp
|
|
|
16656
16744
|
* proceeding — the same mechanism bpmn-elements uses for extension formatting.
|
|
16657
16745
|
*/
|
|
16658
16746
|
class ElementExtensions {
|
|
16659
|
-
|
|
16747
|
+
/**
|
|
16748
|
+
* @param {import('bpmn-elements').Activity} activity
|
|
16749
|
+
* @param {import('bpmn-elements').ContextInstance} context
|
|
16750
|
+
*/
|
|
16751
|
+
constructor(activity, context) {
|
|
16660
16752
|
this.activity = activity;
|
|
16661
16753
|
this.formatQ = activity.broker.getQueue('format-run-q');
|
|
16662
16754
|
|
|
16663
|
-
|
|
16755
|
+
/** @type {import('./getExtensions.js').ExtensionHandlers} */
|
|
16756
|
+
this.extensions = getExtensions(activity, context);
|
|
16664
16757
|
const Service = this.extensions.Service;
|
|
16665
16758
|
if (Service) activity.behaviour.Service = Service;
|
|
16666
16759
|
}
|
|
16760
|
+
/**
|
|
16761
|
+
* Activate extensions
|
|
16762
|
+
* @param {import('bpmn-elements').ElementBrokerMessage} message
|
|
16763
|
+
*/
|
|
16667
16764
|
activate(message) {
|
|
16668
16765
|
const activity = this.activity;
|
|
16669
16766
|
const listeners = this.extensions.listeners;
|
|
16670
16767
|
|
|
16671
16768
|
if (message.fields.redelivered && message.fields.routingKey === 'run.start') {
|
|
16672
|
-
activity.on('start', (api) => this._onEnter(api), { consumerTag:
|
|
16769
|
+
activity.on('start', (api) => this._onEnter(api), { consumerTag: ON_ENTER_TAG });
|
|
16673
16770
|
} else {
|
|
16674
|
-
activity.on('enter', (api) => this._onEnter(api), { consumerTag:
|
|
16771
|
+
activity.on('enter', (api) => this._onEnter(api), { consumerTag: ON_ENTER_TAG });
|
|
16675
16772
|
}
|
|
16676
16773
|
|
|
16677
16774
|
activity.on('activity.execution.completed', (api) => this._onExecuted(api), {
|
|
16678
|
-
consumerTag:
|
|
16775
|
+
consumerTag: ON_EXECUTED_TAG,
|
|
16679
16776
|
});
|
|
16680
16777
|
|
|
16681
16778
|
if (listeners?.onStart) {
|
|
16682
|
-
activity.on('start', (api) => this._onListener('start', api), { consumerTag:
|
|
16779
|
+
activity.on('start', (api) => this._onListener('start', api), { consumerTag: ON_START_TAG });
|
|
16683
16780
|
}
|
|
16684
16781
|
if (listeners?.onEnd) {
|
|
16685
|
-
activity.on('end', (api) => this._onListener('end', api), { consumerTag:
|
|
16782
|
+
activity.on('end', (api) => this._onListener('end', api), { consumerTag: ON_END_TAG });
|
|
16686
16783
|
}
|
|
16687
16784
|
}
|
|
16688
16785
|
deactivate() {
|
|
16689
16786
|
const broker = this.activity.broker;
|
|
16690
|
-
broker.cancel(
|
|
16691
|
-
broker.cancel(
|
|
16692
|
-
broker.cancel(
|
|
16693
|
-
broker.cancel(
|
|
16787
|
+
broker.cancel(ON_ENTER_TAG);
|
|
16788
|
+
broker.cancel(ON_EXECUTED_TAG);
|
|
16789
|
+
broker.cancel(ON_START_TAG);
|
|
16790
|
+
broker.cancel(ON_END_TAG);
|
|
16694
16791
|
}
|
|
16695
|
-
/**
|
|
16792
|
+
/**
|
|
16793
|
+
* @internal Shared with SubProcessExtensions.
|
|
16794
|
+
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Activity>} elementApi
|
|
16795
|
+
*/
|
|
16696
16796
|
async _onEnter(elementApi) {
|
|
16697
16797
|
this.formatQ.queueMessage({ routingKey: 'run.enter.format' }, { endRoutingKey: 'run.enter.complete' }, { persistent: false });
|
|
16698
16798
|
try {
|
|
@@ -16702,7 +16802,10 @@ class ElementExtensions {
|
|
|
16702
16802
|
elementApi.broker.publish('format', 'run.enter.error', { error: err }, { persistent: false });
|
|
16703
16803
|
}
|
|
16704
16804
|
}
|
|
16705
|
-
/**
|
|
16805
|
+
/**
|
|
16806
|
+
* @internal Shared with SubProcessExtensions.
|
|
16807
|
+
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Activity>} elementApi
|
|
16808
|
+
*/
|
|
16706
16809
|
async _onExecuted(elementApi) {
|
|
16707
16810
|
this.formatQ.queueMessage({ routingKey: 'run.end.format' }, { endRoutingKey: 'run.end.complete' }, { persistent: false });
|
|
16708
16811
|
try {
|
|
@@ -16712,7 +16815,10 @@ class ElementExtensions {
|
|
|
16712
16815
|
elementApi.broker.publish('format', 'run.end.error', { error: err }, { persistent: false });
|
|
16713
16816
|
}
|
|
16714
16817
|
}
|
|
16715
|
-
/**
|
|
16818
|
+
/**
|
|
16819
|
+
* @internal Shared with SubProcessExtensions.
|
|
16820
|
+
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Activity>} elementApi
|
|
16821
|
+
*/
|
|
16716
16822
|
async _onListener(eventType, elementApi) {
|
|
16717
16823
|
const routingKey = `run.listener.${eventType}`;
|
|
16718
16824
|
this.formatQ.queueMessage({ routingKey }, { endRoutingKey: `${routingKey}.complete` }, { persistent: false });
|
|
@@ -16724,13 +16830,17 @@ class ElementExtensions {
|
|
|
16724
16830
|
elementApi.broker.publish('format', `${routingKey}.error`, { error: err }, { persistent: false });
|
|
16725
16831
|
}
|
|
16726
16832
|
}
|
|
16833
|
+
/**
|
|
16834
|
+
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Activity>} elementApi
|
|
16835
|
+
*/
|
|
16727
16836
|
#formatOnEnter(elementApi) {
|
|
16728
|
-
const { format, io, headers, properties, form } = this.extensions;
|
|
16837
|
+
const { format, io, headers, properties, form, subscription } = this.extensions;
|
|
16729
16838
|
const result = { ...format.resolve(elementApi) };
|
|
16730
16839
|
|
|
16731
16840
|
if (headers) result.headers = headers.resolve();
|
|
16732
16841
|
if (properties) result.properties = properties.resolve(elementApi);
|
|
16733
16842
|
if (form) result.form = form.resolve(elementApi);
|
|
16843
|
+
if (subscription) result.subscription = subscription.resolve(elementApi);
|
|
16734
16844
|
if (io?.hasInput) {
|
|
16735
16845
|
result.input = io.getInput(elementApi);
|
|
16736
16846
|
// A (sub) process input mapping creates local variables visible to its children; a job
|
|
@@ -16742,13 +16852,16 @@ class ElementExtensions {
|
|
|
16742
16852
|
Object.assign(elementApi.content, result);
|
|
16743
16853
|
return result;
|
|
16744
16854
|
}
|
|
16855
|
+
/**
|
|
16856
|
+
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Activity>} elementApi
|
|
16857
|
+
*/
|
|
16745
16858
|
#formatOnExecuted(elementApi) {
|
|
16746
16859
|
const { io, script, calledDecision, loop } = this.extensions;
|
|
16747
16860
|
let jobResult = elementApi.content.output;
|
|
16748
16861
|
|
|
16749
16862
|
// Multi-instance: aggregate the per-instance outputs into the `outputCollection` array.
|
|
16750
16863
|
if (loop?.hasOutputCollection) {
|
|
16751
|
-
const output = { [loop.outputCollection]: loop.aggregate(jobResult, elementApi.environment
|
|
16864
|
+
const output = { [loop.outputCollection]: loop.aggregate(jobResult, getFeelScope(elementApi.environment)) };
|
|
16752
16865
|
this.#assignOutput(elementApi, output);
|
|
16753
16866
|
return { output };
|
|
16754
16867
|
}
|
|
@@ -16778,6 +16891,10 @@ class ElementExtensions {
|
|
|
16778
16891
|
}
|
|
16779
16892
|
return {};
|
|
16780
16893
|
}
|
|
16894
|
+
/**
|
|
16895
|
+
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Activity>} elementApi
|
|
16896
|
+
* @param {any} output
|
|
16897
|
+
*/
|
|
16781
16898
|
#assignOutput(elementApi, output) {
|
|
16782
16899
|
const environment = this.activity.environment;
|
|
16783
16900
|
// Visible to downstream FEEL within the process, and surfaced as process output
|
|
@@ -16793,8 +16910,12 @@ class ElementExtensions {
|
|
|
16793
16910
|
* the result to the process variables.
|
|
16794
16911
|
*/
|
|
16795
16912
|
class ProcessExtensions {
|
|
16913
|
+
/**
|
|
16914
|
+
* @param {import('bpmn-elements').Process} bp
|
|
16915
|
+
*/
|
|
16796
16916
|
constructor(bp) {
|
|
16797
16917
|
this.process = bp;
|
|
16918
|
+
/** @type {import('./getExtensions.js').ExtensionHandlers} */
|
|
16798
16919
|
this.extensions = getExtensions(bp);
|
|
16799
16920
|
}
|
|
16800
16921
|
activate() {
|
|
@@ -16808,6 +16929,9 @@ class ProcessExtensions {
|
|
|
16808
16929
|
deactivate() {
|
|
16809
16930
|
this.process.broker.cancel('0dep-bpmn-extensions:on-enter');
|
|
16810
16931
|
}
|
|
16932
|
+
/**
|
|
16933
|
+
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Process>} elementApi
|
|
16934
|
+
*/
|
|
16811
16935
|
#onEnter(elementApi) {
|
|
16812
16936
|
try {
|
|
16813
16937
|
// bpmn-elements (>=18.0.4) forwards a call activity's formatted input — its io mapping plus,
|
|
@@ -16849,14 +16973,14 @@ class SubProcessExtensions extends ElementExtensions {
|
|
|
16849
16973
|
const listeners = this.extensions.listeners;
|
|
16850
16974
|
const enterKey = message.fields.redelivered && message.fields.routingKey === 'run.start' ? 'activity.start' : 'activity.enter';
|
|
16851
16975
|
|
|
16852
|
-
this.#subscribe(enterKey, (api) => this._onEnter(api),
|
|
16853
|
-
this.#subscribe('activity.execution.completed', (api) => this._onExecuted(api),
|
|
16976
|
+
this.#subscribe(enterKey, (api) => this._onEnter(api), ON_ENTER_TAG);
|
|
16977
|
+
this.#subscribe('activity.execution.completed', (api) => this._onExecuted(api), ON_EXECUTED_TAG);
|
|
16854
16978
|
|
|
16855
16979
|
if (listeners?.onStart) {
|
|
16856
|
-
this.#subscribe('activity.start', (api) => this._onListener('start', api),
|
|
16980
|
+
this.#subscribe('activity.start', (api) => this._onListener('start', api), ON_START_TAG);
|
|
16857
16981
|
}
|
|
16858
16982
|
if (listeners?.onEnd) {
|
|
16859
|
-
this.#subscribe('activity.end', (api) => this._onListener('end', api),
|
|
16983
|
+
this.#subscribe('activity.end', (api) => this._onListener('end', api), ON_END_TAG);
|
|
16860
16984
|
}
|
|
16861
16985
|
}
|
|
16862
16986
|
#subscribe(routingKey, callback, consumerTag) {
|
|
@@ -16880,7 +17004,7 @@ class SubProcessExtensions extends ElementExtensions {
|
|
|
16880
17004
|
* definition resolves FEEL expressions (`= ...`) instead of the default
|
|
16881
17005
|
* `${...}` template expressions.
|
|
16882
17006
|
*
|
|
16883
|
-
* @returns {import('bpmn-elements').IExpressions}
|
|
17007
|
+
* @returns {import('bpmn-elements').IExpressions & {isExpression: (text:string) => boolean, hasExpression: (text:string) => boolean}}
|
|
16884
17008
|
*/
|
|
16885
17009
|
function FeelExpressions() {
|
|
16886
17010
|
return {
|
|
@@ -16900,25 +17024,14 @@ function hasExpression(text) {
|
|
|
16900
17024
|
|
|
16901
17025
|
/**
|
|
16902
17026
|
* Resolve a templated string. Static literals are returned as-is; FEEL expressions are
|
|
16903
|
-
* evaluated
|
|
17027
|
+
* evaluated in the environment scope (see `getFeelScope`), overlaid with any element-local
|
|
17028
|
+
* variables carried on the message content (e.g. a multi-instance item).
|
|
16904
17029
|
* @param {string} templatedString
|
|
16905
17030
|
* @param {{ environment?: import('bpmn-elements').Environment, content?: any }} [context]
|
|
16906
17031
|
*/
|
|
16907
17032
|
function resolveExpression(templatedString, context) {
|
|
16908
17033
|
if (!isFeelExpression(templatedString)) return templatedString;
|
|
16909
|
-
return evaluateFeel(templatedString,
|
|
16910
|
-
}
|
|
16911
|
-
|
|
16912
|
-
/**
|
|
16913
|
-
* Build the FEEL variable scope from a resolution context. Process/definition variables form
|
|
16914
|
-
* the base scope, overlaid with any local variables carried on the element message content
|
|
16915
|
-
* (e.g. a multi-instance item).
|
|
16916
|
-
*/
|
|
16917
|
-
function getScope(context) {
|
|
16918
|
-
const environment = context && context.environment;
|
|
16919
|
-
const variables = environment ? environment.variables : {};
|
|
16920
|
-
const local = context && context.content && context.content.variables;
|
|
16921
|
-
return { ...variables, ...local };
|
|
17034
|
+
return evaluateFeel(templatedString, getFeelScope(context?.environment, context?.content?.variables));
|
|
16922
17035
|
}
|
|
16923
17036
|
|
|
16924
17037
|
/**
|
|
@@ -16937,7 +17050,15 @@ function FeelScripts() {
|
|
|
16937
17050
|
}
|
|
16938
17051
|
|
|
16939
17052
|
class FeelScriptRegistry {
|
|
16940
|
-
register() {
|
|
17053
|
+
register() {
|
|
17054
|
+
return undefined;
|
|
17055
|
+
}
|
|
17056
|
+
/**
|
|
17057
|
+
* @param {string} _scriptFormat
|
|
17058
|
+
* @param {{ id: string, [x: string]: any }} activity
|
|
17059
|
+
* @returns {import('bpmn-elements').Script | undefined} the FEEL script, or undefined when
|
|
17060
|
+
* the activity has no `zeebe:script`
|
|
17061
|
+
*/
|
|
16941
17062
|
getScript(_scriptFormat, activity) {
|
|
16942
17063
|
const expression = getScriptExpression(activity);
|
|
16943
17064
|
if (expression === undefined) return undefined;
|
|
@@ -16951,7 +17072,7 @@ class FeelScript {
|
|
|
16951
17072
|
}
|
|
16952
17073
|
execute(scope, callback) {
|
|
16953
17074
|
try {
|
|
16954
|
-
const value = isFeelExpression(this.expression) ? evaluateFeel(this.expression,
|
|
17075
|
+
const value = isFeelExpression(this.expression) ? evaluateFeel(this.expression, getFeelScope(scope.environment)) : this.expression;
|
|
16955
17076
|
callback(null, value);
|
|
16956
17077
|
} catch (err) {
|
|
16957
17078
|
callback(err);
|
|
@@ -16972,8 +17093,8 @@ function getScriptExpression(activity) {
|
|
|
16972
17093
|
/**
|
|
16973
17094
|
* A flow extension activated by bpmn-elements around an element's run.
|
|
16974
17095
|
* @typedef {object} FlowExtension
|
|
16975
|
-
* @property {(message:
|
|
16976
|
-
* @property {(message?:
|
|
17096
|
+
* @property {(message: import('bpmn-elements').ElementBrokerMessage) => void} activate
|
|
17097
|
+
* @property {(message?: import('bpmn-elements').ElementBrokerMessage) => void} deactivate
|
|
16977
17098
|
*/
|
|
16978
17099
|
|
|
16979
17100
|
/**
|
|
@@ -17035,6 +17156,7 @@ exports.evaluateFeel = evaluateFeel;
|
|
|
17035
17156
|
exports.evaluateFeelUnaryTest = evaluateFeelUnaryTest;
|
|
17036
17157
|
exports.extendFn = extendFn;
|
|
17037
17158
|
exports.extensions = extensions;
|
|
17159
|
+
exports.getFeelScope = getFeelScope;
|
|
17038
17160
|
exports.isFeelExpression = isFeelExpression;
|
|
17039
17161
|
exports.resolveValue = resolveValue;
|
|
17040
17162
|
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.4",
|
|
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",
|
|
@@ -51,18 +51,26 @@
|
|
|
51
51
|
"access": "public"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"bpmn-elements": ">=18.0.
|
|
54
|
+
"bpmn-elements": ">=18.0.12"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"feelin": "^7.0.1"
|
|
58
58
|
},
|
|
59
|
+
"overrides": {
|
|
60
|
+
"mocha": {
|
|
61
|
+
"yargs": "^18.1.0"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
59
64
|
"devDependencies": {
|
|
60
65
|
"@eslint/js": "^10.0.1",
|
|
61
66
|
"@rollup/plugin-commonjs": "^29.0.3",
|
|
62
67
|
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
63
|
-
"
|
|
68
|
+
"@types/chai": "^5.2.3",
|
|
69
|
+
"@types/mocha": "^10.0.10",
|
|
70
|
+
"@types/node": "^26.1.1",
|
|
71
|
+
"bpmn-elements": "^18.0.12",
|
|
64
72
|
"bpmn-moddle": "^9.0.1",
|
|
65
|
-
"c8": "^
|
|
73
|
+
"c8": "^12.0.0",
|
|
66
74
|
"chai": "^6.2.2",
|
|
67
75
|
"debug": "^4.4.3",
|
|
68
76
|
"dts-buddy": "^0.8.2",
|