@onify/flow-extensions 9.1.0 → 10.0.0

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.
@@ -1,34 +1,45 @@
1
1
  import { getExtensions } from './getExtensions.js';
2
2
 
3
3
  export class OnifyElementExtensions {
4
+ /**
5
+ * @param {import('bpmn-elements').Activity} activity
6
+ * @param {import('bpmn-elements').ContextInstance} context
7
+ */
4
8
  constructor(activity, context) {
5
9
  this.activity = activity;
6
10
  this.context = context;
7
11
  this.formatQ = activity.broker.getQueue('format-run-q');
8
12
  this._asyncFormatOnEnter = this._asyncFormatOnEnter.bind(this);
9
13
 
10
- const { Service } = (this.extensions = getExtensions(activity, context));
14
+ const { Service, format, properties, io, form } = (this.extensions = getExtensions(activity, context));
11
15
  if (Service) {
12
16
  activity.behaviour.Service = Service;
13
17
  }
18
+
19
+ this._formatOnEnter = Boolean(format?.hasFormatting || properties || form || io?.input.length || io?.output.length);
20
+ this._formatOnEnd = Boolean(properties || io?.output.length || format?.resultVariable);
14
21
  }
15
22
  activate(message) {
16
23
  const activity = this.activity;
17
24
  const executionListeners = this.extensions.listeners;
18
25
 
19
- if (message.fields.redelivered && message.fields.routingKey === 'run.start') {
20
- activity.on('start', this._asyncFormatOnEnter, { consumerTag: '_onify-extension-on-enter' });
21
- } else {
22
- activity.on('enter', this._asyncFormatOnEnter, { consumerTag: '_onify-extension-on-enter' });
26
+ if (this._formatOnEnter) {
27
+ if (message.fields.redelivered && message.fields.routingKey === 'run.start') {
28
+ activity.on('start', this._asyncFormatOnEnter, { consumerTag: '_onify-extension-on-enter' });
29
+ } else {
30
+ activity.on('enter', this._asyncFormatOnEnter, { consumerTag: '_onify-extension-on-enter' });
31
+ }
23
32
  }
24
33
 
25
- activity.on(
26
- 'activity.execution.completed',
27
- (elementApi) => {
28
- return this._onExecutionCompleted(elementApi);
29
- },
30
- { consumerTag: '_onify-extension-on-executed' },
31
- );
34
+ if (this._formatOnEnd) {
35
+ activity.on(
36
+ 'activity.execution.completed',
37
+ (elementApi) => {
38
+ return this._onExecutionCompleted(elementApi);
39
+ },
40
+ { consumerTag: '_onify-extension-on-executed' }
41
+ );
42
+ }
32
43
 
33
44
  if (executionListeners?.onStart) {
34
45
  activity.on(
@@ -36,7 +47,7 @@ export class OnifyElementExtensions {
36
47
  (elementApi) => {
37
48
  this._executeExecutionListener('start', elementApi);
38
49
  },
39
- { consumerTag: '_onify-extension-on-listenerstart' },
50
+ { consumerTag: '_onify-extension-on-listenerstart' }
40
51
  );
41
52
  }
42
53
 
@@ -46,7 +57,7 @@ export class OnifyElementExtensions {
46
57
  (elementApi) => {
47
58
  this._executeExecutionListener('end', elementApi);
48
59
  },
49
- { consumerTag: '_onify-extension-on-listenerend' },
60
+ { consumerTag: '_onify-extension-on-listenerend' }
50
61
  );
51
62
  }
52
63
  }
@@ -2,6 +2,10 @@ import { FormatError } from './Errors.js';
2
2
  import { getExtensions } from './getExtensions.js';
3
3
 
4
4
  export class OnifyProcessExtensions {
5
+ /**
6
+ * @param {import('bpmn-elements').Process} bp
7
+ * @param {import('bpmn-elements').ContextInstance} context
8
+ */
5
9
  constructor(bp, context) {
6
10
  this.process = bp;
7
11
  this.context = context;
@@ -25,11 +29,11 @@ export class OnifyProcessExtensions {
25
29
  ...elementApi.content,
26
30
  error: new FormatError(bp.id, err),
27
31
  },
28
- { mandatory: true, type: 'error' },
32
+ { mandatory: true, type: 'error' }
29
33
  );
30
34
  }
31
35
  },
32
- { consumerTag: '_onify-extension-on-enter' },
36
+ { consumerTag: '_onify-extension-on-enter' }
33
37
  );
34
38
  }
35
39
  _onEnter(elementApi) {
@@ -2,30 +2,38 @@ import { SequenceFlow } from 'bpmn-elements';
2
2
  import { getExtensions } from './getExtensions.js';
3
3
 
4
4
  export class OnifySequenceFlow extends SequenceFlow {
5
+ /**
6
+ * @param {import('bpmn-elements').SequenceFlowDefinition} flowDef
7
+ * @param {import('bpmn-elements').ContextInstance} context
8
+ */
5
9
  constructor(flowDef, context) {
6
10
  super(flowDef, context);
7
11
  this.extensions = getExtensions(this, context);
8
- this._activate();
12
+ this.#activate();
9
13
  }
10
- _activate() {
14
+ #activate() {
11
15
  if (!this.extensions.listeners?.onTake) return;
12
16
 
13
17
  this.broker.subscribeTmp(
14
18
  'event',
15
19
  'flow.take',
16
20
  (_, msg) => {
17
- this._executeListeners(msg);
21
+ this.#executeListeners(msg);
18
22
  },
19
- { noAck: true, consumerTag: '_onify-execution-listener' },
23
+ { noAck: true, consumerTag: '_onify-execution-listener' }
20
24
  );
21
25
  }
22
- async _executeListeners(message) {
26
+ async #executeListeners(message) {
23
27
  try {
24
28
  await this.extensions.listeners.execute('take', message);
25
29
  } catch (err) {
26
30
  this.logger.error(`<${this.id}> execution listener error: ${err}`);
27
31
  }
28
32
  }
33
+ /**
34
+ * @param {import('bpmn-elements').ElementBrokerMessage} fromMessage
35
+ * @param {(err: Error | null, result?: boolean | unknown) => void} callback
36
+ */
29
37
  evaluate(fromMessage, callback) {
30
38
  const properties = this.extensions.properties;
31
39
  if (!properties) return super.evaluate(fromMessage, callback);
@@ -1,43 +1,44 @@
1
1
  import { OnifyElementExtensions } from './OnifyElementExtensions.js';
2
2
 
3
3
  export class OnifySubProcessExtensions extends OnifyElementExtensions {
4
- constructor(activity, context) {
5
- super(activity, context);
6
- }
7
4
  activate(runMessage) {
8
5
  const activity = this.activity;
9
6
  const broker = activity.broker;
10
7
  const executionListeners = this.extensions.listeners;
11
8
 
12
- if (runMessage.fields.redelivered && runMessage.fields.routingKey === 'run.start') {
13
- this._setupListener(
14
- broker,
15
- 'activity.start',
16
- (elementApi) => {
17
- return this._asyncFormatOnEnter(elementApi);
18
- },
19
- '_onify-extension-on-enter',
20
- );
21
- } else {
9
+ if (this._formatOnEnter) {
10
+ if (runMessage.fields.redelivered && runMessage.fields.routingKey === 'run.start') {
11
+ this._setupListener(
12
+ broker,
13
+ 'activity.start',
14
+ (elementApi) => {
15
+ return this._asyncFormatOnEnter(elementApi);
16
+ },
17
+ '_onify-extension-on-enter'
18
+ );
19
+ } else {
20
+ this._setupListener(
21
+ broker,
22
+ 'activity.enter',
23
+ (elementApi) => {
24
+ return this._asyncFormatOnEnter(elementApi);
25
+ },
26
+ '_onify-extension-on-enter'
27
+ );
28
+ }
29
+ }
30
+
31
+ if (this._formatOnEnd) {
22
32
  this._setupListener(
23
33
  broker,
24
- 'activity.enter',
34
+ 'activity.execution.completed',
25
35
  (elementApi) => {
26
- return this._asyncFormatOnEnter(elementApi);
36
+ return this._onExecutionCompleted(elementApi);
27
37
  },
28
- '_onify-extension-on-enter',
38
+ '_onify-extension-on-executed'
29
39
  );
30
40
  }
31
41
 
32
- this._setupListener(
33
- broker,
34
- 'activity.execution.completed',
35
- (elementApi) => {
36
- return this._onExecutionCompleted(elementApi);
37
- },
38
- '_onify-extension-on-executed',
39
- );
40
-
41
42
  if (executionListeners?.onStart) {
42
43
  this._setupListener(
43
44
  broker,
@@ -45,7 +46,7 @@ export class OnifySubProcessExtensions extends OnifyElementExtensions {
45
46
  (elementApi) => {
46
47
  this._executeExecutionListener('start', elementApi);
47
48
  },
48
- '_onify-extension-on-listenerstart',
49
+ '_onify-extension-on-listenerstart'
49
50
  );
50
51
  }
51
52
 
@@ -56,7 +57,7 @@ export class OnifySubProcessExtensions extends OnifyElementExtensions {
56
57
  (elementApi) => {
57
58
  this._executeExecutionListener('end', elementApi);
58
59
  },
59
- '_onify-extension-on-listenerend',
60
+ '_onify-extension-on-listenerend'
60
61
  );
61
62
  }
62
63
  }
@@ -70,7 +71,7 @@ export class OnifySubProcessExtensions extends OnifyElementExtensions {
70
71
 
71
72
  return callback(activity.getApi(message));
72
73
  },
73
- { noAck: true, consumerTag },
74
+ { noAck: true, consumerTag }
74
75
  );
75
76
  }
76
77
  }
@@ -2,12 +2,18 @@ import { Cron } from 'croner';
2
2
  import { TimerEventDefinition } from 'bpmn-elements';
3
3
 
4
4
  export class OnifyTimerEventDefinition extends TimerEventDefinition {
5
- constructor(activity, def) {
6
- super(activity, def);
7
- Object.defineProperty(this, 'supports', {
8
- value: ['cron', 'iso8601'],
9
- });
5
+ /**
6
+ * Supported timeCycle formats
7
+ * @returns {string[]}
8
+ */
9
+ get supports() {
10
+ return ['cron', 'iso8601'];
10
11
  }
12
+ /**
13
+ * @param {import('bpmn-elements').TimerType} timerType
14
+ * @param {string} value
15
+ * @returns {import('bpmn-elements').parsedTimer}
16
+ */
11
17
  parse(timerType, value) {
12
18
  if (timerType === 'timeCycle') {
13
19
  try {
@@ -27,7 +33,7 @@ export class OnifyTimerEventDefinition extends TimerEventDefinition {
27
33
  this.logger.error(`<${this.activity?.id}> failed to parse timeCycle: ${rangeError.message}`);
28
34
  this.logger.error(`<${this.activity?.id}> failed to parse timeCycle as cron: ${err.message}`);
29
35
 
30
- throw new RangeError(`Failed to parse timeCycle <${value?.substring(0, 255)}> as ISO 8601 interval or cron`);
36
+ throw new RangeError(`Failed to parse timeCycle <${value?.substring(0, 255)}> as ISO 8601 interval or cron`, { cause: err });
31
37
  }
32
38
  }
33
39
 
@@ -1,6 +1,6 @@
1
1
  import { NotImplemented } from './Errors.js';
2
2
 
3
- export default function ServiceExpression(activity) {
3
+ export function ServiceExpression(activity) {
4
4
  if (!(this instanceof ServiceExpression)) return new ServiceExpression(activity);
5
5
  this.activity = activity;
6
6
  this.type = `${activity.type}:expression`;
package/src/formatters.js CHANGED
@@ -1,4 +1,7 @@
1
1
  export class FormatActivity {
2
+ /**
3
+ * @param {import('bpmn-elements').Activity} activity
4
+ */
2
5
  constructor(activity) {
3
6
  this.activity = activity;
4
7
  this.resultVariable = activity.behaviour.resultVariable;
@@ -13,7 +16,20 @@ export class FormatActivity {
13
16
  }
14
17
  }
15
18
  this.timeCycles = timeCycles;
19
+
20
+ const { documentation, candidateUsers, candidateGroups, scheduledStart, assignee } = activity.behaviour;
21
+ this.hasFormatting = Boolean(
22
+ this.resultVariable ||
23
+ candidateUsers ||
24
+ candidateGroups ||
25
+ assignee ||
26
+ documentation?.[0]?.text ||
27
+ (scheduledStart && activity.parent?.type === 'bpmn:Process')
28
+ );
16
29
  }
30
+ /**
31
+ * @param {import('bpmn-elements').IApi<import('bpmn-elements').Activity>} elementApi
32
+ */
17
33
  resolve(elementApi) {
18
34
  let user, groups, assigneeValue, description;
19
35
  const activity = this.activity;
@@ -36,6 +52,9 @@ export class FormatActivity {
36
52
  }
37
53
 
38
54
  export class FormatProcess {
55
+ /**
56
+ * @param {import('bpmn-elements').Process} bp
57
+ */
39
58
  constructor(bp) {
40
59
  this.process = bp;
41
60
  this._historyTTL = undefined;
@@ -43,6 +62,9 @@ export class FormatProcess {
43
62
  this._historyTTL = bp.context.definitionContext.getTimersByElementId(bp.id).find((t) => t.timer.type === 'historyTimeToLive');
44
63
  }
45
64
  }
65
+ /**
66
+ * @param {import('bpmn-elements').IApi<import('bpmn-elements').Process>} elementApi
67
+ */
46
68
  resolve(elementApi) {
47
69
  let user, groups, description;
48
70
  const bp = this.process;
@@ -61,6 +83,11 @@ export class FormatProcess {
61
83
  }
62
84
  }
63
85
 
86
+ /**
87
+ * @param {import('bpmn-elements').IApi<import('bpmn-elements').Activity>} elementApi
88
+ * @param {string} str
89
+ * @returns {string[] | undefined}
90
+ */
64
91
  function resolveAndSplit(elementApi, str) {
65
92
  if (Array.isArray(str)) return str.filter(Boolean);
66
93
  if (typeof str !== 'string') return;
@@ -1,12 +1,29 @@
1
1
  import { FormatActivity, FormatProcess } from './formatters.js';
2
2
  import { InputOutput } from './IO.js';
3
- import Connector from './Connector.js';
4
- import ExecutionListeners from './ExecutionListeners.js';
5
- import IOForm from './IOForm.js';
6
- import IOProperties from './IOProperties.js';
7
- import ServiceExpression from './ServiceExpression.js';
3
+ import { Connector } from './Connector.js';
4
+ import { ExecutionListeners } from './ExecutionListeners.js';
5
+ import { IOForm } from './IOForm.js';
6
+ import { IOProperties } from './IOProperties.js';
7
+ import { ServiceExpression } from './ServiceExpression.js';
8
8
 
9
+ /**
10
+ * @typedef {object} OnifyExtensions assembled element extensions
11
+ * @property {FormatActivity | FormatProcess} [format] enter/end formatting
12
+ * @property {import('bpmn-elements').IActivityBehaviour} [Service] service factory
13
+ * @property {InputOutput} [io] camunda:InputOutput
14
+ * @property {IOProperties} [properties] camunda:Properties
15
+ * @property {IOForm} [form] camunda:FormData
16
+ * @property {ExecutionListeners} [listeners] camunda:ExecutionListener
17
+ */
18
+
19
+ /**
20
+ * Assemble extensions for an element
21
+ * @param {import('bpmn-elements').Activity | import('bpmn-elements').Process} element
22
+ * @param {import('bpmn-elements').ContextInstance} context
23
+ * @returns {OnifyExtensions}
24
+ */
9
25
  export function getExtensions(element, context) {
26
+ /** @type {OnifyExtensions} */
10
27
  const result = {};
11
28
 
12
29
  switch (element.type) {
@@ -26,8 +43,9 @@ export function getExtensions(element, context) {
26
43
  if (expression) result.Service = ServiceExpression;
27
44
 
28
45
  const extensions = element.behaviour.extensionElements?.values;
29
- if (extensions) {
46
+ if (Array.isArray(extensions)) {
30
47
  for (const ext of extensions) {
48
+ if (!ext) continue;
31
49
  switch (ext.$type) {
32
50
  case 'camunda:Properties':
33
51
  if (ext.values?.length) result.properties = new IOProperties(element, ext);
package/src/index.js CHANGED
@@ -6,6 +6,12 @@ import { OnifySubProcessExtensions } from './OnifySubProcessExtensions.js';
6
6
  export { OnifySequenceFlow } from './OnifySequenceFlow.js';
7
7
  export { OnifyTimerEventDefinition } from './OnifyTimerEventDefinition.js';
8
8
 
9
+ /**
10
+ * Onify flow extensions factory, pass to the engine as `extensions: { onify: extensions }`
11
+ * @param {import('bpmn-elements').ElementBase} element
12
+ * @param {import('bpmn-elements').ContextInstance} context
13
+ * @returns {import('bpmn-elements').IExtension}
14
+ */
9
15
  export function extensions(element, context) {
10
16
  switch (element.type) {
11
17
  case 'bpmn:Process':
@@ -21,14 +27,17 @@ export function extensions(element, context) {
21
27
  }
22
28
  }
23
29
 
30
+ /**
31
+ * Extend function for moddle-context-serializer, registers scripts and timers at serialize time
32
+ * @param {import('bpmn-moddle').BaseElement & Record<string, any>} behaviour
33
+ * @param {import('moddle-context-serializer').ExtendContext} context
34
+ */
24
35
  export function extendFn(behaviour, context) {
25
36
  switch (behaviour.$type) {
26
37
  case 'bpmn:StartEvent': {
27
- if (!behaviour.eventDefinitions) break;
38
+ if (!Array.isArray(behaviour.eventDefinitions)) break;
28
39
 
29
- const timer = behaviour.eventDefinitions.find(
30
- ({ type, behaviour: edBehaviour }) => edBehaviour && type === 'bpmn:TimerEventDefinition',
31
- );
40
+ const timer = behaviour.eventDefinitions.find((ed) => ed?.behaviour && ed.type === 'bpmn:TimerEventDefinition');
32
41
  if (timer && timer.behaviour.timeCycle) Object.assign(behaviour, { scheduledStart: timer.behaviour.timeCycle });
33
42
 
34
43
  break;
@@ -50,6 +59,7 @@ export function extendFn(behaviour, context) {
50
59
 
51
60
  let listener = 0;
52
61
  for (const extension of behaviour.extensionElements.values) {
62
+ if (!extension) continue;
53
63
  switch (extension.$type) {
54
64
  case 'camunda:InputOutput':
55
65
  registerIOScripts(behaviour.id, context, extension.$type, extension);
@@ -64,16 +74,27 @@ export function extendFn(behaviour, context) {
64
74
  }
65
75
  }
66
76
 
77
+ /**
78
+ * @param {string} parentId
79
+ * @param {import('bpmn-elements').ContextInstance} context
80
+ * @param {string} type
81
+ * @param {{ inputParameters?: any[], outputParameters?: any[], [x:string]: any }} ioBehaviour
82
+ */
67
83
  function registerIOScripts(parentId, context, type, ioBehaviour) {
68
84
  if (!ioBehaviour) return;
69
85
 
70
- const { inputParameters = [], outputParameters = [] } = ioBehaviour;
71
- for (const { $type, name, definition } of inputParameters.concat(outputParameters)) {
86
+ const { inputParameters, outputParameters } = ioBehaviour;
87
+ const parameters = [
88
+ ...(Array.isArray(inputParameters) ? inputParameters : []),
89
+ ...(Array.isArray(outputParameters) ? outputParameters : []),
90
+ ];
91
+ for (const parm of parameters) {
92
+ const definition = parm?.definition;
72
93
  if (!definition) continue;
73
94
  if (definition.$type !== 'camunda:Script') continue;
74
95
 
75
- const ioType = `${type}/${$type}`;
76
- const filename = `${parentId}/${ioType}/${name}`;
96
+ const ioType = `${type}/${parm.$type}`;
97
+ const filename = `${parentId}/${ioType}/${parm.name}`;
77
98
 
78
99
  context.addScript(filename, {
79
100
  id: filename,
@@ -85,6 +106,13 @@ function registerIOScripts(parentId, context, type, ioBehaviour) {
85
106
  }
86
107
  }
87
108
 
109
+ /**
110
+ * @param {string} parentId script parent id
111
+ * @param {import('bpmn-elements').ContextInstance} context
112
+ * @param {string} type
113
+ * @param {{event:string, script?: {scriptFormat:string, value?:string, resource?:string, [x:string]: any}, [x:string]: any}} listener
114
+ * @param {Number} pos
115
+ */
88
116
  function registerListenerScript(parentId, context, type, listener, pos) {
89
117
  const { event, script } = listener;
90
118
  if (!script) return;
@@ -99,6 +127,9 @@ function registerListenerScript(parentId, context, type, listener, pos) {
99
127
  });
100
128
  }
101
129
 
130
+ /**
131
+ * @param {{id:string, $type:string, historyTimeToLive?:string, [x:string]: any}} behaviour
132
+ */
102
133
  function getHistoryTimeToLiveTimer(behaviour) {
103
134
  const { id, $type: type, historyTimeToLive } = behaviour;
104
135