@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 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
- See the `camunda8-bpmn-schemas` skill (`.claude/skills/`) for a reference of the `zeebe:*`
132
- extension elements and FEEL.
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), { ...(context || {}), '?': input });
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.variables;
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.variables);
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((v) => v !== null && v !== undefined);
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.variables;
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 = { ...elementApi.environment.variables, ...jobResult };
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.variables;
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.variables;
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 process variables in scope for `outputElement`
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.variables);
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
- * @returns {{
16582
- * format: FormatActivity | FormatProcess,
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
- constructor(activity) {
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
- this.extensions = getExtensions(activity);
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: '0dep-bpmn-extensions:on-enter' });
16769
+ activity.on('start', (api) => this._onEnter(api), { consumerTag: ON_ENTER_TAG });
16673
16770
  } else {
16674
- activity.on('enter', (api) => this._onEnter(api), { consumerTag: '0dep-bpmn-extensions:on-enter' });
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: '0dep-bpmn-extensions:on-executed',
16775
+ consumerTag: ON_EXECUTED_TAG,
16679
16776
  });
16680
16777
 
16681
16778
  if (listeners?.onStart) {
16682
- activity.on('start', (api) => this._onListener('start', api), { consumerTag: '0dep-bpmn-extensions:on-start' });
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: '0dep-bpmn-extensions:on-end' });
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('0dep-bpmn-extensions:on-enter');
16691
- broker.cancel('0dep-bpmn-extensions:on-executed');
16692
- broker.cancel('0dep-bpmn-extensions:on-start');
16693
- broker.cancel('0dep-bpmn-extensions:on-end');
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
- /** @internal Shared with SubProcessExtensions. */
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
- /** @internal Shared with SubProcessExtensions. */
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
- /** @internal Shared with SubProcessExtensions. */
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.variables) };
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), '0dep-bpmn-extensions:on-enter');
16853
- this.#subscribe('activity.execution.completed', (api) => this._onExecuted(api), '0dep-bpmn-extensions:on-executed');
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), '0dep-bpmn-extensions:on-start');
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), '0dep-bpmn-extensions:on-end');
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 against the environment variables (overlaid with any element-local variables).
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, getScope(context));
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, { ...scope.environment.variables }) : 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: any) => void} activate
16976
- * @property {(message?: any) => void} deactivate
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.2",
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.4"
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
- "bpmn-elements": "^18.0.4",
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": "^11.0.0",
73
+ "c8": "^12.0.0",
66
74
  "chai": "^6.2.2",
67
75
  "debug": "^4.4.3",
68
76
  "dts-buddy": "^0.8.2",