@0dep/bpmn-extensions 0.0.4 → 0.0.6
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 +74 -5
- package/dist/index.js +157 -46
- package/package.json +6 -4
- package/src/TimerEventDefinition.js +46 -0
- package/src/extensions/formatters.js +49 -5
- package/src/getExtensions.js +29 -2
- package/src/index.js +26 -6
- package/types/index.d.ts +42 -9
- package/types/index.d.ts.map +4 -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.
|
|
@@ -12,10 +12,11 @@ which covers the older `camunda:*` extension elements.
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
|
-
npm install @0dep/bpmn-extensions bpmn-elements
|
|
15
|
+
npm install @0dep/bpmn-extensions bpmn-elements croner
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
`bpmn-elements` (>= 18)
|
|
18
|
+
`bpmn-elements` (>= 18) and [`croner`](https://www.npmjs.com/package/croner) (cron timer cycles) are
|
|
19
|
+
peer dependencies. Requires Node.js >= 22.
|
|
19
20
|
|
|
20
21
|
## What it does
|
|
21
22
|
|
|
@@ -38,6 +39,9 @@ npm install @0dep/bpmn-extensions bpmn-elements
|
|
|
38
39
|
process's input becomes variables local to its children.
|
|
39
40
|
- **`zeebe:taskHeaders`** / **`zeebe:properties`** — exposed on the element content.
|
|
40
41
|
- **`zeebe:assignmentDefinition`** — assignee / candidate users / groups for user tasks.
|
|
42
|
+
- **`zeebe:priorityDefinition`** / **`zeebe:taskSchedule`** — a user task's `priority` and
|
|
43
|
+
`dueDate` / `followUpDate` resolved onto the element content for a task list to order and
|
|
44
|
+
remind by; FEEL date results are exposed as ISO 8601 strings.
|
|
41
45
|
- **`zeebe:formDefinition`** — resolves a user task's form (`formId` / `formKey` / `externalReference`,
|
|
42
46
|
`bindingType`) onto the element content for a task list to render.
|
|
43
47
|
- **`zeebe:executionListeners`** — blocking start/end job workers around an element, called as
|
|
@@ -46,6 +50,10 @@ npm install @0dep/bpmn-extensions bpmn-elements
|
|
|
46
50
|
- **`zeebe:calledElement`** — call activity target process id.
|
|
47
51
|
- **`zeebe:loopCharacteristics`** — collection-based multi-instance (`inputCollection` / `inputElement`),
|
|
48
52
|
sequential or parallel, with `outputElement` aggregated into the `outputCollection` array in input order.
|
|
53
|
+
- **Timer start events with cron** — a `TimerEventDefinition` that also parses a **cron** `timeCycle`
|
|
54
|
+
(`0 0 * * *`, as Camunda 8 schedules timer start events) besides ISO 8601 intervals. A top-level
|
|
55
|
+
timer start event's cycle is lifted onto its behaviour as `scheduledStart` by `extendFn` and exposed
|
|
56
|
+
on the start event content, so a scheduler can find the flows to start without running them.
|
|
49
57
|
- **`zeebe:subscription`** — the referenced message's `correlationKey` is resolved when the catching
|
|
50
58
|
element (receive task, message event) starts waiting, and exposed on its content as
|
|
51
59
|
`subscription: { message, correlationKey }` — route an incoming message by matching it against the
|
|
@@ -54,8 +62,14 @@ npm install @0dep/bpmn-extensions bpmn-elements
|
|
|
54
62
|
## API
|
|
55
63
|
|
|
56
64
|
- `extensions(element, context)` — the flow extensions factory; pass it as an environment extension.
|
|
65
|
+
Elements that carry no zeebe extension data (and nothing else to format) get no extension attached
|
|
66
|
+
at all and run untouched; call activities and processes are always attached, since they propagate
|
|
67
|
+
io across process boundaries.
|
|
57
68
|
- `extendFn(behaviour)` — moddle-context-serializer behaviour extender (lifts the extension data
|
|
58
|
-
bpmn-elements expects on the behaviour: call activity process id
|
|
69
|
+
bpmn-elements expects on the behaviour: call activity process id, multi-instance collection, and a
|
|
70
|
+
timer start event's cycle as `scheduledStart`).
|
|
71
|
+
- `TimerEventDefinition` — a bpmn-elements timer event definition that accepts cron `timeCycle`s;
|
|
72
|
+
install it through the type resolver: `TypeResolver({ ...elements, TimerEventDefinition })`.
|
|
59
73
|
- `FeelExpressions()` — a bpmn-elements `IExpressions` implementation backed by FEEL.
|
|
60
74
|
- `FeelScripts()` — a bpmn-elements `scripts` implementation that runs `zeebe:script` FEEL expressions.
|
|
61
75
|
- FEEL helpers: `isFeelExpression`, `stripFeel`, `evaluateFeel`, `evaluateFeelUnaryTest`, `resolveValue`,
|
|
@@ -69,7 +83,7 @@ Wire it into a bpmn-elements definition (see `test/helpers/testHelpers.js` for t
|
|
|
69
83
|
```javascript
|
|
70
84
|
import { createRequire } from 'node:module';
|
|
71
85
|
import { strict as assert } from 'node:assert';
|
|
72
|
-
import BpmnModdle from 'bpmn-moddle';
|
|
86
|
+
import { BpmnModdle } from 'bpmn-moddle';
|
|
73
87
|
import * as elements from 'bpmn-elements';
|
|
74
88
|
import { Serializer, TypeResolver } from 'moddle-context-serializer';
|
|
75
89
|
import { extensions, extendFn, FeelExpressions, FeelScripts } from '@0dep/bpmn-extensions';
|
|
@@ -124,6 +138,60 @@ A service task's `zeebe:taskDefinition type="charge-card"` is dispatched to the
|
|
|
124
138
|
service named `charge-card`. Its callback result is the job's variables, which `zeebe:ioMapping`
|
|
125
139
|
output parameters map back into the process.
|
|
126
140
|
|
|
141
|
+
## Extract timers
|
|
142
|
+
|
|
143
|
+
List the timers of a definition without running it — e.g. to schedule flows by their timer start
|
|
144
|
+
event. The serializer collects every timer event definition; `extendFn` marks a timer start event's
|
|
145
|
+
cycle as `scheduledStart`, and the cron-capable `TimerEventDefinition` parses each one to its next
|
|
146
|
+
expiry:
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
import { createRequire } from 'node:module';
|
|
150
|
+
import { strict as assert } from 'node:assert';
|
|
151
|
+
import { BpmnModdle } from 'bpmn-moddle';
|
|
152
|
+
import * as elements from 'bpmn-elements';
|
|
153
|
+
import { Serializer, TypeResolver } from 'moddle-context-serializer';
|
|
154
|
+
import { extendFn, TimerEventDefinition } from '@0dep/bpmn-extensions';
|
|
155
|
+
|
|
156
|
+
const require = createRequire(import.meta.url);
|
|
157
|
+
const moddle = new BpmnModdle({ zeebe: require('zeebe-bpmn-moddle/resources/zeebe.json') });
|
|
158
|
+
|
|
159
|
+
const source = `<?xml version="1.0" encoding="UTF-8"?>
|
|
160
|
+
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="def">
|
|
161
|
+
<process id="nightly" isExecutable="true">
|
|
162
|
+
<startEvent id="start">
|
|
163
|
+
<timerEventDefinition>
|
|
164
|
+
<timeCycle xsi:type="tFormalExpression">0 1 * * *</timeCycle>
|
|
165
|
+
</timerEventDefinition>
|
|
166
|
+
</startEvent>
|
|
167
|
+
<sequenceFlow id="to-task" sourceRef="start" targetRef="task" />
|
|
168
|
+
<userTask id="task" />
|
|
169
|
+
<boundaryEvent id="reminder" cancelActivity="false" attachedToRef="task">
|
|
170
|
+
<timerEventDefinition>
|
|
171
|
+
<timeDuration xsi:type="tFormalExpression">R3/PT1M</timeDuration>
|
|
172
|
+
</timerEventDefinition>
|
|
173
|
+
</boundaryEvent>
|
|
174
|
+
</process>
|
|
175
|
+
</definitions>`;
|
|
176
|
+
|
|
177
|
+
const moddleContext = await moddle.fromXML(source);
|
|
178
|
+
const serializer = Serializer(moddleContext, TypeResolver({ ...elements, TimerEventDefinition }), extendFn);
|
|
179
|
+
|
|
180
|
+
const dummyActivity = { id: 'dummy', broker: {}, environment: { Logger() {} } };
|
|
181
|
+
const timers = serializer.getTimers().map((t) => ({
|
|
182
|
+
...t,
|
|
183
|
+
parsed: new TimerEventDefinition(dummyActivity, { type: t.timer.type, behaviour: t.timer }).parse(t.timer.timerType, t.timer.value),
|
|
184
|
+
}));
|
|
185
|
+
|
|
186
|
+
console.log(timers.map((t) => [t.parent.id, t.timer.timerType, t.timer.value, t.parsed.expireAt]));
|
|
187
|
+
assert.equal(timers.length, 2);
|
|
188
|
+
assert.equal(timers[0].parent.id, 'start');
|
|
189
|
+
assert.ok(timers[0].parsed.expireAt instanceof Date);
|
|
190
|
+
|
|
191
|
+
// A timer start event is a schedule: the cycle is lifted onto the behaviour
|
|
192
|
+
assert.equal(serializer.getActivityById('start').behaviour.scheduledStart, '0 1 * * *');
|
|
193
|
+
```
|
|
194
|
+
|
|
127
195
|
## Development
|
|
128
196
|
|
|
129
197
|
```sh
|
|
@@ -141,6 +209,7 @@ the engine's `Logger` to the [`debug`](https://www.npmjs.com/package/debug) pack
|
|
|
141
209
|
|
|
142
210
|
# Ecosystem
|
|
143
211
|
|
|
212
|
+
- [0dep.se/run](https://0dep.se/run) — Run a BPMN diagram in the browser: `bpmn-elements` wired with `@0dep/bpmn-extensions`, drawn with bpmn-js; step through, signal waiting tasks, and drop DMN files for business rule tasks.
|
|
144
213
|
- [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
214
|
- [bpmn-middleware](https://github.com/zerodep/bpmn-middleware) — Express middleware exposing the engine over HTTP, with pluggable state storage.
|
|
146
215
|
- [@onify/flow-extensions](https://github.com/onify/flow-extensions) — Onify Flow extensions for `bpmn-elements`.
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var croner = require('croner');
|
|
4
|
+
var bpmnElements = require('bpmn-elements');
|
|
5
|
+
|
|
3
6
|
// these aren't really private, but nor are they really useful to document
|
|
4
7
|
|
|
5
8
|
/**
|
|
@@ -11609,9 +11612,9 @@ function getSpecializer(spec) {
|
|
|
11609
11612
|
*
|
|
11610
11613
|
* @return {T[]}
|
|
11611
11614
|
*/
|
|
11612
|
-
const nativeHasOwnProperty
|
|
11615
|
+
const nativeHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
11613
11616
|
|
|
11614
|
-
function isNil$
|
|
11617
|
+
function isNil$1(obj) {
|
|
11615
11618
|
return obj == null;
|
|
11616
11619
|
}
|
|
11617
11620
|
|
|
@@ -11623,8 +11626,8 @@ function isNil$2(obj) {
|
|
|
11623
11626
|
*
|
|
11624
11627
|
* @return {Boolean}
|
|
11625
11628
|
*/
|
|
11626
|
-
function has
|
|
11627
|
-
return !isNil$
|
|
11629
|
+
function has(target, key) {
|
|
11630
|
+
return !isNil$1(target) && nativeHasOwnProperty.call(target, key);
|
|
11628
11631
|
}
|
|
11629
11632
|
|
|
11630
11633
|
let nextTagID = 0;
|
|
@@ -13107,7 +13110,7 @@ class VariableContext {
|
|
|
13107
13110
|
continue;
|
|
13108
13111
|
}
|
|
13109
13112
|
|
|
13110
|
-
if (has
|
|
13113
|
+
if (has(merged, key)) {
|
|
13111
13114
|
|
|
13112
13115
|
// deep merge nested contexts
|
|
13113
13116
|
merged[key] = this.__merge(merged[key], value);
|
|
@@ -13241,7 +13244,7 @@ class Variables {
|
|
|
13241
13244
|
return null;
|
|
13242
13245
|
}
|
|
13243
13246
|
|
|
13244
|
-
if (!isNil$
|
|
13247
|
+
if (!isNil$1(scope.value)) {
|
|
13245
13248
|
return scope.value;
|
|
13246
13249
|
}
|
|
13247
13250
|
}
|
|
@@ -13871,33 +13874,6 @@ const parser = LRParser.deserialize({
|
|
|
13871
13874
|
tokenPrec: 2660
|
|
13872
13875
|
});
|
|
13873
13876
|
|
|
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
13877
|
function isNil(e) {
|
|
13902
13878
|
return e === null || e === undefined;
|
|
13903
13879
|
}
|
|
@@ -16238,17 +16214,22 @@ function getFeelScope(environment, localVariables) {
|
|
|
16238
16214
|
}
|
|
16239
16215
|
|
|
16240
16216
|
/**
|
|
16241
|
-
* Format an activity on enter from its behaviour and extensions: documentation
|
|
16242
|
-
* for user tasks, the `zeebe:assignmentDefinition` (assignee, candidate users/groups)
|
|
16217
|
+
* Format an activity on enter from its behaviour and extensions: documentation, a top-level
|
|
16218
|
+
* timer start event's `scheduledStart` (lifted by `extendFn`) and, for user tasks, the `zeebe:assignmentDefinition` (assignee, candidate users/groups),
|
|
16219
|
+
* `zeebe:priorityDefinition` (priority), and `zeebe:taskSchedule` (due/follow-up date).
|
|
16243
16220
|
*/
|
|
16244
16221
|
class FormatActivity {
|
|
16245
16222
|
/**
|
|
16246
16223
|
* @param {import('bpmn-elements').Activity} activity
|
|
16247
16224
|
* @param {any} assignmentDefinition
|
|
16225
|
+
* @param {any} [priorityDefinition]
|
|
16226
|
+
* @param {any} [taskSchedule]
|
|
16248
16227
|
*/
|
|
16249
|
-
constructor(activity, assignmentDefinition) {
|
|
16228
|
+
constructor(activity, assignmentDefinition, priorityDefinition, taskSchedule) {
|
|
16250
16229
|
this.activity = activity;
|
|
16251
16230
|
this.assignmentDefinition = assignmentDefinition;
|
|
16231
|
+
this.priorityDefinition = priorityDefinition;
|
|
16232
|
+
this.taskSchedule = taskSchedule;
|
|
16252
16233
|
}
|
|
16253
16234
|
/**
|
|
16254
16235
|
* @param {import('bpmn-elements').IApi<import('bpmn-elements').Activity>} elementApi
|
|
@@ -16260,9 +16241,14 @@ class FormatActivity {
|
|
|
16260
16241
|
const documentation = this.activity.behaviour.documentation;
|
|
16261
16242
|
if (documentation && !elementApi.content.description) {
|
|
16262
16243
|
const text = documentation[0]?.text;
|
|
16263
|
-
if (text) result.description = resolveValue(text, scope);
|
|
16244
|
+
if (text) result.description = /** @type {string} */ (resolveValue(text, scope));
|
|
16264
16245
|
}
|
|
16265
16246
|
|
|
16247
|
+
// A timer start event's cycle is only a schedule when it starts the process itself — a
|
|
16248
|
+
// sub-process start event runs whenever its parent does.
|
|
16249
|
+
const scheduledStart = this.activity.behaviour.scheduledStart;
|
|
16250
|
+
if (scheduledStart && this.activity.parent?.type === 'bpmn:Process') result.scheduledStart = scheduledStart;
|
|
16251
|
+
|
|
16266
16252
|
const assignment = this.assignmentDefinition;
|
|
16267
16253
|
if (assignment) {
|
|
16268
16254
|
const { assignee, candidateUsers, candidateGroups } = assignment;
|
|
@@ -16273,6 +16259,22 @@ class FormatActivity {
|
|
|
16273
16259
|
if (groups) result.candidateGroups = groups;
|
|
16274
16260
|
}
|
|
16275
16261
|
|
|
16262
|
+
const priority = this.priorityDefinition?.priority;
|
|
16263
|
+
if (priority !== undefined) {
|
|
16264
|
+
const resolved = resolveValue(priority, scope);
|
|
16265
|
+
// A static attribute value is a string — expose a numeric one as a number (like retries).
|
|
16266
|
+
const num = Number(resolved);
|
|
16267
|
+
/** @type {Number | string | undefined} */
|
|
16268
|
+
result.priority = typeof resolved === 'string' && resolved.trim() !== '' && Number.isFinite(num) ? num : resolved;
|
|
16269
|
+
}
|
|
16270
|
+
|
|
16271
|
+
const schedule = this.taskSchedule;
|
|
16272
|
+
if (schedule) {
|
|
16273
|
+
const { dueDate, followUpDate } = schedule;
|
|
16274
|
+
if (dueDate !== undefined) result.dueDate = resolveDate(dueDate, scope);
|
|
16275
|
+
if (followUpDate !== undefined) result.followUpDate = resolveDate(followUpDate, scope);
|
|
16276
|
+
}
|
|
16277
|
+
|
|
16276
16278
|
return result;
|
|
16277
16279
|
}
|
|
16278
16280
|
}
|
|
@@ -16295,15 +16297,33 @@ class FormatProcess {
|
|
|
16295
16297
|
const documentation = this.process.behaviour.documentation;
|
|
16296
16298
|
if (documentation && !elementApi.content.description) {
|
|
16297
16299
|
const text = documentation[0]?.text;
|
|
16298
|
-
if (text) result.description = resolveValue(text, getFeelScope(elementApi.environment));
|
|
16300
|
+
if (text) result.description = /** @type {string} */ (resolveValue(text, getFeelScope(elementApi.environment)));
|
|
16299
16301
|
}
|
|
16300
16302
|
return result;
|
|
16301
16303
|
}
|
|
16302
16304
|
}
|
|
16303
16305
|
|
|
16306
|
+
/**
|
|
16307
|
+
* Resolve a due/follow-up date to an ISO 8601 string. A FEEL temporal result (a luxon
|
|
16308
|
+
* DateTime) or a Date is ISO-stringified — content is JSON-serialized on `getState()`, so a
|
|
16309
|
+
* string is the shape that survives a recover round trip. Anything else passes through as-is.
|
|
16310
|
+
* @param {any} value
|
|
16311
|
+
* @param {any} scope
|
|
16312
|
+
* @returns {string | undefined} Date as iso string
|
|
16313
|
+
*/
|
|
16314
|
+
function resolveDate(value, scope) {
|
|
16315
|
+
const resolved = resolveValue(value, scope);
|
|
16316
|
+
if (typeof resolved?.toISO === 'function') return resolved.toISO();
|
|
16317
|
+
if (resolved instanceof Date) return resolved.toISOString();
|
|
16318
|
+
return resolved;
|
|
16319
|
+
}
|
|
16320
|
+
|
|
16304
16321
|
/**
|
|
16305
16322
|
* Resolve a candidate users/groups value to a string array. A FEEL expression may yield an
|
|
16306
16323
|
* array directly, otherwise a comma-separated string is split.
|
|
16324
|
+
* @param {any} value
|
|
16325
|
+
* @param {any} scope
|
|
16326
|
+
* @returns {string[] | undefined}
|
|
16307
16327
|
*/
|
|
16308
16328
|
function resolveList(value, scope) {
|
|
16309
16329
|
if (!value) return undefined;
|
|
@@ -16652,6 +16672,7 @@ class Subscription {
|
|
|
16652
16672
|
* @property {any} [calledDecision]
|
|
16653
16673
|
* @property {Function} [Service]
|
|
16654
16674
|
* @property {Subscription} [subscription]
|
|
16675
|
+
* @property {boolean} [isEmpty] no zeebe extension data found and nothing to format — the element needs no extension
|
|
16655
16676
|
*/
|
|
16656
16677
|
|
|
16657
16678
|
/**
|
|
@@ -16664,10 +16685,15 @@ function getExtensions(element, context) {
|
|
|
16664
16685
|
const result = {};
|
|
16665
16686
|
const isProcess = element.type === 'bpmn:Process';
|
|
16666
16687
|
|
|
16667
|
-
let assignmentDefinition;
|
|
16688
|
+
let assignmentDefinition, priorityDefinition, taskSchedule;
|
|
16689
|
+
let foundZeebe = false;
|
|
16668
16690
|
const values = element.behaviour.extensionElements?.values;
|
|
16669
16691
|
if (values) {
|
|
16670
16692
|
for (const ext of values) {
|
|
16693
|
+
// Any zeebe-namespaced extension element counts as found — including ones with no
|
|
16694
|
+
// handler (e.g. the bare `zeebe:userTask` marker), which keep the element extension
|
|
16695
|
+
// loaded so e.g. a native user task's completion payload is merged.
|
|
16696
|
+
if (ext.$type.startsWith('zeebe:')) foundZeebe = true;
|
|
16671
16697
|
switch (ext.$type) {
|
|
16672
16698
|
case 'zeebe:IoMapping':
|
|
16673
16699
|
result.io = new IoMapping(element, ext);
|
|
@@ -16681,6 +16707,12 @@ function getExtensions(element, context) {
|
|
|
16681
16707
|
case 'zeebe:AssignmentDefinition':
|
|
16682
16708
|
assignmentDefinition = ext;
|
|
16683
16709
|
break;
|
|
16710
|
+
case 'zeebe:PriorityDefinition':
|
|
16711
|
+
priorityDefinition = ext;
|
|
16712
|
+
break;
|
|
16713
|
+
case 'zeebe:TaskSchedule':
|
|
16714
|
+
taskSchedule = ext;
|
|
16715
|
+
break;
|
|
16684
16716
|
case 'zeebe:ExecutionListeners':
|
|
16685
16717
|
if (ext.listeners?.length) result.listeners = new ExecutionListeners(element, ext);
|
|
16686
16718
|
break;
|
|
@@ -16722,7 +16754,22 @@ function getExtensions(element, context) {
|
|
|
16722
16754
|
);
|
|
16723
16755
|
if (loopExtension?.outputCollection) result.loop = new LoopCharacteristics(loopExtension);
|
|
16724
16756
|
|
|
16725
|
-
|
|
16757
|
+
// Nothing zeebe-flavoured and nothing to format (documentation, a process-level timer start
|
|
16758
|
+
// event's `scheduledStart`) — the element needs no extension. A call
|
|
16759
|
+
// activity is never inert: its output unwrap/merge is type-driven (the standard BPMN
|
|
16760
|
+
// `calledElement` attribute needs no zeebe elements). Ignored for processes — a called
|
|
16761
|
+
// process always needs its inbound input promoted, whatever it carries itself.
|
|
16762
|
+
result.isEmpty =
|
|
16763
|
+
!foundZeebe &&
|
|
16764
|
+
!result.subscription &&
|
|
16765
|
+
!loopExtension &&
|
|
16766
|
+
!element.behaviour.documentation &&
|
|
16767
|
+
!(element.behaviour.scheduledStart && element.parent?.type === 'bpmn:Process') &&
|
|
16768
|
+
element.type !== 'bpmn:CallActivity';
|
|
16769
|
+
|
|
16770
|
+
result.format = isProcess
|
|
16771
|
+
? new FormatProcess(element)
|
|
16772
|
+
: new FormatActivity(element, assignmentDefinition, priorityDefinition, taskSchedule);
|
|
16726
16773
|
|
|
16727
16774
|
return result;
|
|
16728
16775
|
}
|
|
@@ -17090,6 +17137,50 @@ function getScriptExpression(activity) {
|
|
|
17090
17137
|
return script?.expression;
|
|
17091
17138
|
}
|
|
17092
17139
|
|
|
17140
|
+
/**
|
|
17141
|
+
* bpmn-elements timer event definition that also accepts a **cron** `timeCycle` (Camunda 8
|
|
17142
|
+
* timer start events schedule with cron, e.g. `0 0 * * *`). An ISO 8601 interval is tried
|
|
17143
|
+
* first; on failure the value is parsed as cron and the next run is the expiry.
|
|
17144
|
+
*
|
|
17145
|
+
* Install it via the type resolver so it replaces the bpmn-elements default:
|
|
17146
|
+
* `TypeResolver({ ...elements, TimerEventDefinition })`.
|
|
17147
|
+
*/
|
|
17148
|
+
class TimerEventDefinition extends bpmnElements.TimerEventDefinition {
|
|
17149
|
+
/**
|
|
17150
|
+
* Supported timeCycle formats
|
|
17151
|
+
* @returns {string[]}
|
|
17152
|
+
*/
|
|
17153
|
+
get supports() {
|
|
17154
|
+
return ['cron', 'iso8601'];
|
|
17155
|
+
}
|
|
17156
|
+
/**
|
|
17157
|
+
* @param {import('bpmn-elements').TimerType} timerType
|
|
17158
|
+
* @param {string} value
|
|
17159
|
+
* @returns {import('bpmn-elements').parsedTimer}
|
|
17160
|
+
*/
|
|
17161
|
+
parse(timerType, value) {
|
|
17162
|
+
if (timerType !== 'timeCycle') return super.parse(timerType, value);
|
|
17163
|
+
|
|
17164
|
+
/** @type {any} */
|
|
17165
|
+
let isoError;
|
|
17166
|
+
try {
|
|
17167
|
+
return super.parse(timerType, value);
|
|
17168
|
+
} catch (err) {
|
|
17169
|
+
isoError = err;
|
|
17170
|
+
}
|
|
17171
|
+
|
|
17172
|
+
try {
|
|
17173
|
+
const expireAt = new croner.Cron(value).nextRun();
|
|
17174
|
+
if (!expireAt) throw new RangeError('cron has no next run');
|
|
17175
|
+
return { expireAt, delay: expireAt.getTime() - Date.now() };
|
|
17176
|
+
} catch (/** @type {any} */ err) {
|
|
17177
|
+
this.logger.error(`<${this.activity?.id}> failed to parse timeCycle: ${isoError.message}`);
|
|
17178
|
+
this.logger.error(`<${this.activity?.id}> failed to parse timeCycle as cron: ${err.message}`);
|
|
17179
|
+
throw new RangeError(`Failed to parse timeCycle <${value?.substring(0, 255)}> as ISO 8601 interval or cron`, { cause: err });
|
|
17180
|
+
}
|
|
17181
|
+
}
|
|
17182
|
+
}
|
|
17183
|
+
|
|
17093
17184
|
/**
|
|
17094
17185
|
* A flow extension activated by bpmn-elements around an element's run.
|
|
17095
17186
|
* @typedef {object} FlowExtension
|
|
@@ -17099,20 +17190,32 @@ function getScriptExpression(activity) {
|
|
|
17099
17190
|
|
|
17100
17191
|
/**
|
|
17101
17192
|
* Flow extensions factory. Pass it to the engine via the environment `extensions` option.
|
|
17193
|
+
*
|
|
17194
|
+
* Returns undefined for an element that carries no zeebe extension data and nothing else to
|
|
17195
|
+
* format (no documentation, not a call activity) — bpmn-elements skips falsy extensions, so
|
|
17196
|
+
* plain elements run untouched, with no broker subscriptions or format-queue traffic.
|
|
17102
17197
|
* @param {import('bpmn-elements').Activity | import('bpmn-elements').Process} element
|
|
17103
17198
|
* @param {import('bpmn-elements').ContextInstance} context
|
|
17104
|
-
* @returns {FlowExtension}
|
|
17199
|
+
* @returns {FlowExtension | undefined}
|
|
17105
17200
|
*/
|
|
17106
17201
|
function extensions(element, context) {
|
|
17107
17202
|
switch (element.type) {
|
|
17108
17203
|
case 'bpmn:Process':
|
|
17204
|
+
// A process is always attached: a called process needs its inbound input promoted
|
|
17205
|
+
// even when it carries no zeebe extension data of its own.
|
|
17109
17206
|
return new ProcessExtensions(element, context);
|
|
17110
17207
|
case 'bpmn:SubProcess':
|
|
17111
17208
|
case 'bpmn:AdHocSubProcess':
|
|
17112
|
-
case 'bpmn:Transaction':
|
|
17113
|
-
|
|
17114
|
-
|
|
17115
|
-
return
|
|
17209
|
+
case 'bpmn:Transaction': {
|
|
17210
|
+
const subProcessExtensions = new SubProcessExtensions(element, context);
|
|
17211
|
+
if (subProcessExtensions.extensions.isEmpty) return undefined;
|
|
17212
|
+
return subProcessExtensions;
|
|
17213
|
+
}
|
|
17214
|
+
default: {
|
|
17215
|
+
const elementExtensions = new ElementExtensions(element, context);
|
|
17216
|
+
if (elementExtensions.extensions.isEmpty) return undefined;
|
|
17217
|
+
return elementExtensions;
|
|
17218
|
+
}
|
|
17116
17219
|
}
|
|
17117
17220
|
}
|
|
17118
17221
|
|
|
@@ -17121,10 +17224,17 @@ function extensions(element, context) {
|
|
|
17121
17224
|
*
|
|
17122
17225
|
* Lifts extension data onto the places bpmn-elements expects to find it on the element
|
|
17123
17226
|
* behaviour: the call activity's called process id (`zeebe:calledElement`) and the multi-instance
|
|
17124
|
-
* input collection/element (`zeebe:loopCharacteristics`).
|
|
17227
|
+
* input collection/element (`zeebe:loopCharacteristics`). A timer start event's `timeCycle`
|
|
17228
|
+
* (ISO 8601 or cron) is lifted as `scheduledStart`, so a scheduler can find it without running
|
|
17229
|
+
* the flow.
|
|
17125
17230
|
* @param {any} behaviour
|
|
17126
17231
|
*/
|
|
17127
17232
|
function extendFn(behaviour) {
|
|
17233
|
+
if (behaviour.$type === 'bpmn:StartEvent' && Array.isArray(behaviour.eventDefinitions)) {
|
|
17234
|
+
const timer = behaviour.eventDefinitions.find((ed) => ed?.type === 'bpmn:TimerEventDefinition' && ed.behaviour?.timeCycle);
|
|
17235
|
+
if (timer) behaviour.scheduledStart = timer.behaviour.timeCycle;
|
|
17236
|
+
}
|
|
17237
|
+
|
|
17128
17238
|
const values = behaviour.extensionElements?.values;
|
|
17129
17239
|
if (Array.isArray(values)) {
|
|
17130
17240
|
for (const ext of values) {
|
|
@@ -17152,6 +17262,7 @@ exports.JobService = JobService;
|
|
|
17152
17262
|
exports.ProcessExtensions = ProcessExtensions;
|
|
17153
17263
|
exports.ServiceError = ServiceError;
|
|
17154
17264
|
exports.SubProcessExtensions = SubProcessExtensions;
|
|
17265
|
+
exports.TimerEventDefinition = TimerEventDefinition;
|
|
17155
17266
|
exports.evaluateFeel = evaluateFeel;
|
|
17156
17267
|
exports.evaluateFeelUnaryTest = evaluateFeelUnaryTest;
|
|
17157
17268
|
exports.extendFn = extendFn;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0dep/bpmn-extensions",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
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,7 +51,8 @@
|
|
|
51
51
|
"access": "public"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"bpmn-elements": ">=18.0.12"
|
|
54
|
+
"bpmn-elements": ">=18.0.12",
|
|
55
|
+
"croner": ">=9"
|
|
55
56
|
},
|
|
56
57
|
"dependencies": {
|
|
57
58
|
"feelin": "^7.0.1"
|
|
@@ -69,9 +70,10 @@
|
|
|
69
70
|
"@types/mocha": "^10.0.10",
|
|
70
71
|
"@types/node": "^26.1.1",
|
|
71
72
|
"bpmn-elements": "^18.0.12",
|
|
72
|
-
"bpmn-moddle": "^
|
|
73
|
+
"bpmn-moddle": "^10.1.0",
|
|
73
74
|
"c8": "^12.0.0",
|
|
74
75
|
"chai": "^6.2.2",
|
|
76
|
+
"croner": "^10.0.1",
|
|
75
77
|
"debug": "^4.4.3",
|
|
76
78
|
"dts-buddy": "^0.8.2",
|
|
77
79
|
"eslint": "^10.6.0",
|
|
@@ -83,7 +85,7 @@
|
|
|
83
85
|
"rollup": "^4.62.2",
|
|
84
86
|
"texample": "^1.0.2",
|
|
85
87
|
"typescript": "^6.0.3",
|
|
86
|
-
"zeebe-bpmn-moddle": "^
|
|
88
|
+
"zeebe-bpmn-moddle": "^2.0.0"
|
|
87
89
|
},
|
|
88
90
|
"files": [
|
|
89
91
|
"src",
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Cron } from 'croner';
|
|
2
|
+
import { TimerEventDefinition as BaseTimerEventDefinition } from 'bpmn-elements';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* bpmn-elements timer event definition that also accepts a **cron** `timeCycle` (Camunda 8
|
|
6
|
+
* timer start events schedule with cron, e.g. `0 0 * * *`). An ISO 8601 interval is tried
|
|
7
|
+
* first; on failure the value is parsed as cron and the next run is the expiry.
|
|
8
|
+
*
|
|
9
|
+
* Install it via the type resolver so it replaces the bpmn-elements default:
|
|
10
|
+
* `TypeResolver({ ...elements, TimerEventDefinition })`.
|
|
11
|
+
*/
|
|
12
|
+
export class TimerEventDefinition extends BaseTimerEventDefinition {
|
|
13
|
+
/**
|
|
14
|
+
* Supported timeCycle formats
|
|
15
|
+
* @returns {string[]}
|
|
16
|
+
*/
|
|
17
|
+
get supports() {
|
|
18
|
+
return ['cron', 'iso8601'];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* @param {import('bpmn-elements').TimerType} timerType
|
|
22
|
+
* @param {string} value
|
|
23
|
+
* @returns {import('bpmn-elements').parsedTimer}
|
|
24
|
+
*/
|
|
25
|
+
parse(timerType, value) {
|
|
26
|
+
if (timerType !== 'timeCycle') return super.parse(timerType, value);
|
|
27
|
+
|
|
28
|
+
/** @type {any} */
|
|
29
|
+
let isoError;
|
|
30
|
+
try {
|
|
31
|
+
return super.parse(timerType, value);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
isoError = err;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const expireAt = new Cron(value).nextRun();
|
|
38
|
+
if (!expireAt) throw new RangeError('cron has no next run');
|
|
39
|
+
return { expireAt, delay: expireAt.getTime() - Date.now() };
|
|
40
|
+
} catch (/** @type {any} */ err) {
|
|
41
|
+
this.logger.error(`<${this.activity?.id}> failed to parse timeCycle: ${isoError.message}`);
|
|
42
|
+
this.logger.error(`<${this.activity?.id}> failed to parse timeCycle as cron: ${err.message}`);
|
|
43
|
+
throw new RangeError(`Failed to parse timeCycle <${value?.substring(0, 255)}> as ISO 8601 interval or cron`, { cause: err });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import { resolveValue, getFeelScope } from '../feel.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Format an activity on enter from its behaviour and extensions: documentation
|
|
5
|
-
* for user tasks, the `zeebe:assignmentDefinition` (assignee, candidate users/groups)
|
|
4
|
+
* Format an activity on enter from its behaviour and extensions: documentation, a top-level
|
|
5
|
+
* timer start event's `scheduledStart` (lifted by `extendFn`) and, 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
|
|
@@ -23,9 +28,14 @@ export class FormatActivity {
|
|
|
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
|
|
|
34
|
+
// A timer start event's cycle is only a schedule when it starts the process itself — a
|
|
35
|
+
// sub-process start event runs whenever its parent does.
|
|
36
|
+
const scheduledStart = this.activity.behaviour.scheduledStart;
|
|
37
|
+
if (scheduledStart && this.activity.parent?.type === 'bpmn:Process') result.scheduledStart = scheduledStart;
|
|
38
|
+
|
|
29
39
|
const assignment = this.assignmentDefinition;
|
|
30
40
|
if (assignment) {
|
|
31
41
|
const { assignee, candidateUsers, candidateGroups } = assignment;
|
|
@@ -36,6 +46,22 @@ export class FormatActivity {
|
|
|
36
46
|
if (groups) result.candidateGroups = groups;
|
|
37
47
|
}
|
|
38
48
|
|
|
49
|
+
const priority = this.priorityDefinition?.priority;
|
|
50
|
+
if (priority !== undefined) {
|
|
51
|
+
const resolved = resolveValue(priority, scope);
|
|
52
|
+
// A static attribute value is a string — expose a numeric one as a number (like retries).
|
|
53
|
+
const num = Number(resolved);
|
|
54
|
+
/** @type {Number | string | undefined} */
|
|
55
|
+
result.priority = typeof resolved === 'string' && resolved.trim() !== '' && Number.isFinite(num) ? num : resolved;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const schedule = this.taskSchedule;
|
|
59
|
+
if (schedule) {
|
|
60
|
+
const { dueDate, followUpDate } = schedule;
|
|
61
|
+
if (dueDate !== undefined) result.dueDate = resolveDate(dueDate, scope);
|
|
62
|
+
if (followUpDate !== undefined) result.followUpDate = resolveDate(followUpDate, scope);
|
|
63
|
+
}
|
|
64
|
+
|
|
39
65
|
return result;
|
|
40
66
|
}
|
|
41
67
|
}
|
|
@@ -58,15 +84,33 @@ export class FormatProcess {
|
|
|
58
84
|
const documentation = this.process.behaviour.documentation;
|
|
59
85
|
if (documentation && !elementApi.content.description) {
|
|
60
86
|
const text = documentation[0]?.text;
|
|
61
|
-
if (text) result.description = resolveValue(text, getFeelScope(elementApi.environment));
|
|
87
|
+
if (text) result.description = /** @type {string} */ (resolveValue(text, getFeelScope(elementApi.environment)));
|
|
62
88
|
}
|
|
63
89
|
return result;
|
|
64
90
|
}
|
|
65
91
|
}
|
|
66
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Resolve a due/follow-up date to an ISO 8601 string. A FEEL temporal result (a luxon
|
|
95
|
+
* DateTime) or a Date is ISO-stringified — content is JSON-serialized on `getState()`, so a
|
|
96
|
+
* string is the shape that survives a recover round trip. Anything else passes through as-is.
|
|
97
|
+
* @param {any} value
|
|
98
|
+
* @param {any} scope
|
|
99
|
+
* @returns {string | undefined} Date as iso string
|
|
100
|
+
*/
|
|
101
|
+
function resolveDate(value, scope) {
|
|
102
|
+
const resolved = resolveValue(value, scope);
|
|
103
|
+
if (typeof resolved?.toISO === 'function') return resolved.toISO();
|
|
104
|
+
if (resolved instanceof Date) return resolved.toISOString();
|
|
105
|
+
return resolved;
|
|
106
|
+
}
|
|
107
|
+
|
|
67
108
|
/**
|
|
68
109
|
* Resolve a candidate users/groups value to a string array. A FEEL expression may yield an
|
|
69
110
|
* array directly, otherwise a comma-separated string is split.
|
|
111
|
+
* @param {any} value
|
|
112
|
+
* @param {any} scope
|
|
113
|
+
* @returns {string[] | undefined}
|
|
70
114
|
*/
|
|
71
115
|
function resolveList(value, scope) {
|
|
72
116
|
if (!value) return undefined;
|
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,22 @@ 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 (documentation, a process-level timer start
|
|
108
|
+
// event's `scheduledStart`) — the element needs no extension. A call
|
|
109
|
+
// activity is never inert: its output unwrap/merge is type-driven (the standard BPMN
|
|
110
|
+
// `calledElement` attribute needs no zeebe elements). Ignored for processes — a called
|
|
111
|
+
// process always needs its inbound input promoted, whatever it carries itself.
|
|
112
|
+
result.isEmpty =
|
|
113
|
+
!foundZeebe &&
|
|
114
|
+
!result.subscription &&
|
|
115
|
+
!loopExtension &&
|
|
116
|
+
!element.behaviour.documentation &&
|
|
117
|
+
!(element.behaviour.scheduledStart && element.parent?.type === 'bpmn:Process') &&
|
|
118
|
+
element.type !== 'bpmn:CallActivity';
|
|
119
|
+
|
|
120
|
+
result.format = isProcess
|
|
121
|
+
? new FormatProcess(element)
|
|
122
|
+
: new FormatActivity(element, assignmentDefinition, priorityDefinition, taskSchedule);
|
|
96
123
|
|
|
97
124
|
return result;
|
|
98
125
|
}
|
package/src/index.js
CHANGED
|
@@ -5,6 +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 { TimerEventDefinition } from './TimerEventDefinition.js';
|
|
8
9
|
export { isFeelExpression, stripFeel, evaluateFeel, evaluateFeelUnaryTest, resolveValue, getFeelScope } from './feel.js';
|
|
9
10
|
export { JobService } from './extensions/TaskDefinition.js';
|
|
10
11
|
export { ServiceError, FormatError } from './Errors.js';
|
|
@@ -18,20 +19,32 @@ export { ServiceError, FormatError } from './Errors.js';
|
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Flow extensions factory. Pass it to the engine via the environment `extensions` option.
|
|
22
|
+
*
|
|
23
|
+
* Returns undefined for an element that carries no zeebe extension data and nothing else to
|
|
24
|
+
* format (no documentation, not a call activity) — bpmn-elements skips falsy extensions, so
|
|
25
|
+
* plain elements run untouched, with no broker subscriptions or format-queue traffic.
|
|
21
26
|
* @param {import('bpmn-elements').Activity | import('bpmn-elements').Process} element
|
|
22
27
|
* @param {import('bpmn-elements').ContextInstance} context
|
|
23
|
-
* @returns {FlowExtension}
|
|
28
|
+
* @returns {FlowExtension | undefined}
|
|
24
29
|
*/
|
|
25
30
|
export function extensions(element, context) {
|
|
26
31
|
switch (element.type) {
|
|
27
32
|
case 'bpmn:Process':
|
|
33
|
+
// A process is always attached: a called process needs its inbound input promoted
|
|
34
|
+
// even when it carries no zeebe extension data of its own.
|
|
28
35
|
return new ProcessExtensions(element, context);
|
|
29
36
|
case 'bpmn:SubProcess':
|
|
30
37
|
case 'bpmn:AdHocSubProcess':
|
|
31
|
-
case 'bpmn:Transaction':
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return
|
|
38
|
+
case 'bpmn:Transaction': {
|
|
39
|
+
const subProcessExtensions = new SubProcessExtensions(element, context);
|
|
40
|
+
if (subProcessExtensions.extensions.isEmpty) return undefined;
|
|
41
|
+
return subProcessExtensions;
|
|
42
|
+
}
|
|
43
|
+
default: {
|
|
44
|
+
const elementExtensions = new ElementExtensions(element, context);
|
|
45
|
+
if (elementExtensions.extensions.isEmpty) return undefined;
|
|
46
|
+
return elementExtensions;
|
|
47
|
+
}
|
|
35
48
|
}
|
|
36
49
|
}
|
|
37
50
|
|
|
@@ -40,10 +53,17 @@ export function extensions(element, context) {
|
|
|
40
53
|
*
|
|
41
54
|
* Lifts extension data onto the places bpmn-elements expects to find it on the element
|
|
42
55
|
* behaviour: the call activity's called process id (`zeebe:calledElement`) and the multi-instance
|
|
43
|
-
* input collection/element (`zeebe:loopCharacteristics`).
|
|
56
|
+
* input collection/element (`zeebe:loopCharacteristics`). A timer start event's `timeCycle`
|
|
57
|
+
* (ISO 8601 or cron) is lifted as `scheduledStart`, so a scheduler can find it without running
|
|
58
|
+
* the flow.
|
|
44
59
|
* @param {any} behaviour
|
|
45
60
|
*/
|
|
46
61
|
export function extendFn(behaviour) {
|
|
62
|
+
if (behaviour.$type === 'bpmn:StartEvent' && Array.isArray(behaviour.eventDefinitions)) {
|
|
63
|
+
const timer = behaviour.eventDefinitions.find((ed) => ed?.type === 'bpmn:TimerEventDefinition' && ed.behaviour?.timeCycle);
|
|
64
|
+
if (timer) behaviour.scheduledStart = timer.behaviour.timeCycle;
|
|
65
|
+
}
|
|
66
|
+
|
|
47
67
|
const values = behaviour.extensionElements?.values;
|
|
48
68
|
if (Array.isArray(values)) {
|
|
49
69
|
for (const ext of values) {
|
package/types/index.d.ts
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
declare module '@0dep/bpmn-extensions' {
|
|
2
|
+
import type { TimerEventDefinition as BaseTimerEventDefinition } from 'bpmn-elements';
|
|
2
3
|
/**
|
|
3
4
|
* A flow extension activated by bpmn-elements around an element's run.
|
|
4
5
|
* */
|
|
5
6
|
/**
|
|
6
7
|
* Flow extensions factory. Pass it to the engine via the environment `extensions` option.
|
|
8
|
+
*
|
|
9
|
+
* Returns undefined for an element that carries no zeebe extension data and nothing else to
|
|
10
|
+
* format (no documentation, not a call activity) — bpmn-elements skips falsy extensions, so
|
|
11
|
+
* plain elements run untouched, with no broker subscriptions or format-queue traffic.
|
|
7
12
|
* */
|
|
8
|
-
export function extensions(element: import("bpmn-elements").Activity | import("bpmn-elements").Process, context: import("bpmn-elements").ContextInstance): FlowExtension;
|
|
13
|
+
export function extensions(element: import("bpmn-elements").Activity | import("bpmn-elements").Process, context: import("bpmn-elements").ContextInstance): FlowExtension | undefined;
|
|
9
14
|
/**
|
|
10
15
|
* Behaviour extend function for moddle-context-serializer.
|
|
11
16
|
*
|
|
12
17
|
* Lifts extension data onto the places bpmn-elements expects to find it on the element
|
|
13
18
|
* behaviour: the call activity's called process id (`zeebe:calledElement`) and the multi-instance
|
|
14
|
-
* input collection/element (`zeebe:loopCharacteristics`).
|
|
19
|
+
* input collection/element (`zeebe:loopCharacteristics`). A timer start event's `timeCycle`
|
|
20
|
+
* (ISO 8601 or cron) is lifted as `scheduledStart`, so a scheduler can find it without running
|
|
21
|
+
* the flow.
|
|
15
22
|
* */
|
|
16
23
|
export function extendFn(behaviour: any): void;
|
|
17
24
|
/**
|
|
@@ -44,6 +51,20 @@ declare module '@0dep/bpmn-extensions' {
|
|
|
44
51
|
*
|
|
45
52
|
* */
|
|
46
53
|
export function FeelScripts(): import("bpmn-elements").IScripts;
|
|
54
|
+
/**
|
|
55
|
+
* bpmn-elements timer event definition that also accepts a **cron** `timeCycle` (Camunda 8
|
|
56
|
+
* timer start events schedule with cron, e.g. `0 0 * * *`). An ISO 8601 interval is tried
|
|
57
|
+
* first; on failure the value is parsed as cron and the next run is the expiry.
|
|
58
|
+
*
|
|
59
|
+
* Install it via the type resolver so it replaces the bpmn-elements default:
|
|
60
|
+
* `TypeResolver({ ...elements, TimerEventDefinition })`.
|
|
61
|
+
*/
|
|
62
|
+
export class TimerEventDefinition extends BaseTimerEventDefinition {
|
|
63
|
+
/**
|
|
64
|
+
* Supported timeCycle formats
|
|
65
|
+
* */
|
|
66
|
+
get supports(): string[];
|
|
67
|
+
}
|
|
47
68
|
/**
|
|
48
69
|
* `zeebe:taskDefinition`.
|
|
49
70
|
*
|
|
@@ -210,22 +231,34 @@ declare module '@0dep/bpmn-extensions' {
|
|
|
210
231
|
calledDecision?: any;
|
|
211
232
|
Service?: Function | undefined;
|
|
212
233
|
subscription?: Subscription | undefined;
|
|
234
|
+
/**
|
|
235
|
+
* no zeebe extension data found and nothing to format — the element needs no extension
|
|
236
|
+
*/
|
|
237
|
+
isEmpty?: boolean | undefined;
|
|
213
238
|
};
|
|
214
239
|
/**
|
|
215
|
-
* Format an activity on enter from its behaviour and extensions: documentation
|
|
216
|
-
* for user tasks, the `zeebe:assignmentDefinition` (assignee, candidate users/groups)
|
|
240
|
+
* Format an activity on enter from its behaviour and extensions: documentation, a top-level
|
|
241
|
+
* timer start event's `scheduledStart` (lifted by `extendFn`) and, for user tasks, the `zeebe:assignmentDefinition` (assignee, candidate users/groups),
|
|
242
|
+
* `zeebe:priorityDefinition` (priority), and `zeebe:taskSchedule` (due/follow-up date).
|
|
217
243
|
*/
|
|
218
244
|
class FormatActivity {
|
|
219
245
|
|
|
220
|
-
constructor(activity: import("bpmn-elements").Activity, assignmentDefinition: any);
|
|
246
|
+
constructor(activity: import("bpmn-elements").Activity, assignmentDefinition: any, priorityDefinition?: any, taskSchedule?: any);
|
|
221
247
|
activity: import("bpmn-elements").Activity;
|
|
222
248
|
assignmentDefinition: any;
|
|
249
|
+
priorityDefinition: any;
|
|
250
|
+
taskSchedule: any;
|
|
223
251
|
|
|
224
252
|
resolve(elementApi: import("bpmn-elements").IApi<import("bpmn-elements").Activity>): {
|
|
225
|
-
description:
|
|
253
|
+
description: string;
|
|
254
|
+
scheduledStart: any;
|
|
226
255
|
assignee: any;
|
|
227
|
-
candidateUsers:
|
|
228
|
-
candidateGroups:
|
|
256
|
+
candidateUsers: string[];
|
|
257
|
+
candidateGroups: string[];
|
|
258
|
+
|
|
259
|
+
priority: number | string | undefined;
|
|
260
|
+
dueDate: string | undefined;
|
|
261
|
+
followUpDate: string | undefined;
|
|
229
262
|
};
|
|
230
263
|
}
|
|
231
264
|
/**
|
|
@@ -237,7 +270,7 @@ declare module '@0dep/bpmn-extensions' {
|
|
|
237
270
|
process: import("bpmn-elements").Process;
|
|
238
271
|
|
|
239
272
|
resolve(elementApi: import("bpmn-elements").IApi<import("bpmn-elements").Process>): {
|
|
240
|
-
description:
|
|
273
|
+
description: string;
|
|
241
274
|
};
|
|
242
275
|
}
|
|
243
276
|
/**
|
package/types/index.d.ts.map
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"extendFn",
|
|
7
7
|
"FeelExpressions",
|
|
8
8
|
"FeelScripts",
|
|
9
|
+
"TimerEventDefinition",
|
|
9
10
|
"JobService",
|
|
10
11
|
"ElementExtensions",
|
|
11
12
|
"ProcessExtensions",
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
"../src/index.js",
|
|
33
34
|
"../src/Expressions.js",
|
|
34
35
|
"../src/extensions/FeelScripts.js",
|
|
36
|
+
"../src/TimerEventDefinition.js",
|
|
35
37
|
"../src/extensions/TaskDefinition.js",
|
|
36
38
|
"../src/ElementExtensions.js",
|
|
37
39
|
"../src/ProcessExtensions.js",
|
|
@@ -64,8 +66,9 @@
|
|
|
64
66
|
null,
|
|
65
67
|
null,
|
|
66
68
|
null,
|
|
69
|
+
null,
|
|
67
70
|
null
|
|
68
71
|
],
|
|
69
|
-
"mappings": "
|
|
72
|
+
"mappings": ";;;;;;;;;;;;iBA6BgBA,UAAUA;;;;;;;;;;iBA+BVC,QAAQA;;;;;;;;;;;;;;;;iBCjDRC,eAAeA;;;;;;;;;;;;;;iBCEfC,WAAWA;;;;;;;;;cCFdC,oBAAoBA;;;;;;;;;;;;;;;;;;;;iBCMjBC,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;;;;;;;;;;;;;;;;;;;;;;;OAgEdC,aAAaA;;;;;;;;;;;;;;;;;;;;OC1DbC,SAASA;;;;;;;;;;;;;;;;;;;;;;;OCPTC,WAAWA;;;;;;;;;;;OCEXC,UAAUA;;;;;;;;;;;;;;;OCIVC,kBAAkBA;;;;;;;;;;;;;;;;;;;OCFlBC,IAAIA;;;;;;;;;;;;;;;;;;;;;;OCGJC,mBAAmBA;;;;;;;;;;;;;;;;;;;OCHnBC,YAAYA",
|
|
70
73
|
"ignoreList": []
|
|
71
74
|
}
|