@ronaldroe/micro-flow 2.0.0 → 3.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.
- package/dist/src/classes/base.js +2 -2
- package/dist/src/classes/base.js.map +2 -2
- package/dist/src/classes/index.js +1 -1
- package/dist/src/classes/index.js.map +2 -2
- package/dist/src/classes/instance_state.js +2 -0
- package/dist/src/classes/instance_state.js.map +7 -0
- package/dist/src/classes/state.js +1 -1
- package/dist/src/classes/state.js.map +3 -3
- package/dist/src/classes/steps/conditional_step.js +1 -1
- package/dist/src/classes/steps/conditional_step.js.map +3 -3
- package/dist/src/classes/steps/delay_step.js +1 -1
- package/dist/src/classes/steps/delay_step.js.map +3 -3
- package/dist/src/classes/steps/flow_control_step.js +1 -1
- package/dist/src/classes/steps/flow_control_step.js.map +3 -3
- package/dist/src/classes/steps/logic_step.js +1 -1
- package/dist/src/classes/steps/logic_step.js.map +3 -3
- package/dist/src/classes/steps/step.js +1 -1
- package/dist/src/classes/steps/step.js.map +2 -2
- package/dist/src/classes/steps/switch_step.js +1 -1
- package/dist/src/classes/steps/switch_step.js.map +3 -3
- package/dist/src/classes/workflow.js +1 -1
- package/dist/src/classes/workflow.js.map +3 -3
- package/package.json +1 -1
- package/src/classes/base.js +2 -1
- package/src/classes/index.js +2 -1
- package/src/classes/instance_state.js +277 -0
- package/src/classes/state.js +14 -313
- package/src/classes/steps/conditional_step.js +3 -2
- package/src/classes/steps/delay_step.js +5 -8
- package/src/classes/steps/flow_control_step.js +3 -2
- package/src/classes/steps/logic_step.js +38 -38
- package/src/classes/steps/step.js +12 -6
- package/src/classes/steps/switch_step.js +2 -1
- package/src/classes/workflow.js +52 -31
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import LogicStep from './logic_step.js';
|
|
2
2
|
import flow_control_types from '../../enums/flow_control_types.js';
|
|
3
3
|
import { conditional_step_comparators } from '../../enums/index.js';
|
|
4
|
+
import { event_names } from '../instance_state.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* FlowControlStep class for controlling workflow execution flow (break or skip).
|
|
@@ -51,7 +52,7 @@ export default class FlowControlStep extends LogicStep {
|
|
|
51
52
|
async shouldFlowControl() {
|
|
52
53
|
if (this.checkCondition()) {
|
|
53
54
|
this.log(
|
|
54
|
-
|
|
55
|
+
event_names.step.CONDITIONAL_TRUE_BRANCH_EXECUTED,
|
|
55
56
|
`Break condition met for step: ${this.name}`
|
|
56
57
|
);
|
|
57
58
|
this.setParentWorkflowValue(this.parent_workflow_id, `should_${this.flow_control_type}`, true);
|
|
@@ -59,7 +60,7 @@ export default class FlowControlStep extends LogicStep {
|
|
|
59
60
|
return true;
|
|
60
61
|
} else {
|
|
61
62
|
this.log(
|
|
62
|
-
|
|
63
|
+
event_names.step.CONDITIONAL_FALSE_BRANCH_EXECUTED,
|
|
63
64
|
`Break condition not met for step: ${this.name}`
|
|
64
65
|
);
|
|
65
66
|
this.setParentWorkflowValue(this.parent_workflow_id, `should_${this.flow_control_type}`, false);
|
|
@@ -55,77 +55,77 @@ export default class LogicStep extends Step {
|
|
|
55
55
|
const subject = typeof raw_subject === 'function' ? raw_subject() : raw_subject;
|
|
56
56
|
|
|
57
57
|
// Don't resolve value for CUSTOM_FUNCTION - the value IS the function to call
|
|
58
|
-
const is_custom_function = operator ===
|
|
58
|
+
const is_custom_function = operator === conditional_step_comparators.CUSTOM_FUNCTION;
|
|
59
59
|
const value = (!is_custom_function && typeof raw_value === 'function') ? raw_value() : raw_value;
|
|
60
60
|
|
|
61
61
|
switch (operator) {
|
|
62
|
-
case
|
|
63
|
-
case
|
|
62
|
+
case conditional_step_comparators.STRICT_EQUALS:
|
|
63
|
+
case conditional_step_comparators.SIGN_STRICT_EQUALS:
|
|
64
64
|
return subject === value;
|
|
65
|
-
case
|
|
66
|
-
case
|
|
65
|
+
case conditional_step_comparators.SIGN_EQUALS:
|
|
66
|
+
case conditional_step_comparators.EQUALS:
|
|
67
67
|
return subject == value;
|
|
68
|
-
case
|
|
69
|
-
case
|
|
68
|
+
case conditional_step_comparators.NOT_EQUALS:
|
|
69
|
+
case conditional_step_comparators.SIGN_NOT_EQUALS:
|
|
70
70
|
return subject != value;
|
|
71
|
-
case
|
|
72
|
-
case
|
|
71
|
+
case conditional_step_comparators.STRICT_NOT_EQUALS:
|
|
72
|
+
case conditional_step_comparators.SIGN_STRICT_NOT_EQUALS:
|
|
73
73
|
return subject !== value;
|
|
74
|
-
case
|
|
75
|
-
case
|
|
74
|
+
case conditional_step_comparators.GREATER_THAN:
|
|
75
|
+
case conditional_step_comparators.SIGN_GREATER_THAN:
|
|
76
76
|
return subject > value;
|
|
77
|
-
case
|
|
78
|
-
case
|
|
77
|
+
case conditional_step_comparators.LESS_THAN:
|
|
78
|
+
case conditional_step_comparators.SIGN_LESS_THAN:
|
|
79
79
|
return subject < value;
|
|
80
|
-
case
|
|
81
|
-
case
|
|
80
|
+
case conditional_step_comparators.GREATER_THAN_OR_EQUAL:
|
|
81
|
+
case conditional_step_comparators.SIGN_GREATER_THAN_OR_EQUAL:
|
|
82
82
|
return subject >= value;
|
|
83
|
-
case
|
|
84
|
-
case
|
|
83
|
+
case conditional_step_comparators.LESS_THAN_OR_EQUAL:
|
|
84
|
+
case conditional_step_comparators.SIGN_LESS_THAN_OR_EQUAL:
|
|
85
85
|
return subject <= value;
|
|
86
|
-
case
|
|
87
|
-
case
|
|
88
|
-
case
|
|
89
|
-
case
|
|
86
|
+
case conditional_step_comparators.STRING_CONTAINS:
|
|
87
|
+
case conditional_step_comparators.STRING_INCLUDES:
|
|
88
|
+
case conditional_step_comparators.ARRAY_CONTAINS:
|
|
89
|
+
case conditional_step_comparators.ARRAY_INCLUDES:
|
|
90
90
|
return (Array.isArray(subject) || typeof subject === 'string') && subject.includes(value);
|
|
91
|
-
case
|
|
91
|
+
case conditional_step_comparators.IN:
|
|
92
92
|
return (Array.isArray(value) || typeof value === 'string') && value.includes(subject);
|
|
93
|
-
case
|
|
94
|
-
case
|
|
95
|
-
case
|
|
96
|
-
case
|
|
93
|
+
case conditional_step_comparators.STRING_NOT_CONTAINS:
|
|
94
|
+
case conditional_step_comparators.STRING_NOT_INCLUDES:
|
|
95
|
+
case conditional_step_comparators.ARRAY_NOT_CONTAINS:
|
|
96
|
+
case conditional_step_comparators.ARRAY_NOT_INCLUDES:
|
|
97
97
|
return (Array.isArray(subject) || typeof subject === 'string') && !subject.includes(value);
|
|
98
|
-
case
|
|
98
|
+
case conditional_step_comparators.NOT_IN:
|
|
99
99
|
return (Array.isArray(value) || typeof value === 'string') && !value.includes(subject);
|
|
100
|
-
case
|
|
100
|
+
case conditional_step_comparators.EMPTY:
|
|
101
101
|
return subject === '' || subject === null || subject === undefined || subject.length === 0;
|
|
102
|
-
case
|
|
102
|
+
case conditional_step_comparators.NOT_EMPTY:
|
|
103
103
|
return subject !== '' && subject !== null && subject !== undefined && subject.length > 0;
|
|
104
|
-
case
|
|
104
|
+
case conditional_step_comparators.REGEX_MATCH:
|
|
105
105
|
if (typeof value !== 'string') {
|
|
106
106
|
throw new Error(`Regex input must be a string.`);
|
|
107
107
|
}
|
|
108
108
|
const regex = new RegExp(value);
|
|
109
109
|
return regex.test(subject);
|
|
110
|
-
case
|
|
110
|
+
case conditional_step_comparators.REGEX_NOT_MATCH:
|
|
111
111
|
if (typeof value !== 'string') {
|
|
112
112
|
throw new Error(`Regex input must be a string.`);
|
|
113
113
|
}
|
|
114
114
|
const not_match_regex = new RegExp(value);
|
|
115
115
|
return !not_match_regex.test(subject);
|
|
116
|
-
case
|
|
116
|
+
case conditional_step_comparators.STRING_STARTS_WITH:
|
|
117
117
|
return typeof subject === 'string' && typeof value === 'string' && subject.startsWith(value);
|
|
118
|
-
case
|
|
118
|
+
case conditional_step_comparators.STRING_ENDS_WITH:
|
|
119
119
|
return typeof subject === 'string' && typeof value === 'string' && subject.endsWith(value);
|
|
120
|
-
case
|
|
120
|
+
case conditional_step_comparators.NULLISH:
|
|
121
121
|
return subject === null || subject === undefined;
|
|
122
|
-
case
|
|
122
|
+
case conditional_step_comparators.NOT_NULLISH:
|
|
123
123
|
return subject !== null && subject !== undefined;
|
|
124
|
-
case
|
|
124
|
+
case conditional_step_comparators.IS_TYPE:
|
|
125
125
|
return typeof subject === value;
|
|
126
|
-
case
|
|
126
|
+
case conditional_step_comparators.IS_NOT_TYPE:
|
|
127
127
|
return typeof subject !== value;
|
|
128
|
-
case
|
|
128
|
+
case conditional_step_comparators.CUSTOM_FUNCTION:
|
|
129
129
|
if (typeof value !== 'function') {
|
|
130
130
|
throw new Error(`Invalid custom function: ${value}`);
|
|
131
131
|
}
|
|
@@ -101,7 +101,7 @@ export default class Step extends Base {
|
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
const { FAILED, COMPLETE } =
|
|
104
|
+
const { FAILED, COMPLETE } = Workflow.statuses[this.base_type];
|
|
105
105
|
|
|
106
106
|
if (![FAILED, COMPLETE].includes(this.status)) {
|
|
107
107
|
this.markAsComplete();
|
|
@@ -264,16 +264,22 @@ export default class Step extends Base {
|
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
/**
|
|
267
|
-
* Sets a value
|
|
267
|
+
* Sets a value on the parent workflow instance itself. Normally this is the live object
|
|
268
|
+
* shared via this step's own state under the `workflow` key (see `initializeWorkflowState()`
|
|
269
|
+
* in `workflow.js`); when `use_state_singleton` is `true`, several unrelated workflows can
|
|
270
|
+
* share the same process-wide state, so it's looked up by id in the singleton's `workflows`
|
|
271
|
+
* registry instead.
|
|
268
272
|
* @param {string} workflow_id - ID of the parent workflow.
|
|
269
|
-
* @param {string} path -
|
|
273
|
+
* @param {string} path - Property name to set on the workflow instance.
|
|
270
274
|
* @param {*} value - Value to set at the specified path.
|
|
271
|
-
* @throws {Error} Throws if parent workflow is not found.
|
|
275
|
+
* @throws {Error} Throws if the parent workflow is not found.
|
|
272
276
|
*/
|
|
273
277
|
setParentWorkflowValue(workflow_id, path, value) {
|
|
274
|
-
const parent_workflow = this.
|
|
278
|
+
const parent_workflow = this.use_state_singleton
|
|
279
|
+
? this.getState('workflows')[workflow_id]
|
|
280
|
+
: this.getState('workflow');
|
|
275
281
|
|
|
276
|
-
if (!parent_workflow) {
|
|
282
|
+
if (!parent_workflow || parent_workflow.id !== workflow_id) {
|
|
277
283
|
throw new Error(`Parent workflow with ID ${workflow_id} not found.`);
|
|
278
284
|
}
|
|
279
285
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Step from './step.js';
|
|
2
|
+
import { event_names } from '../instance_state.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* SwitchStep class for implementing switch/case logic in workflows.
|
|
@@ -67,7 +68,7 @@ export default class SwitchStep extends Step {
|
|
|
67
68
|
|
|
68
69
|
if (is_matched) {
|
|
69
70
|
this.log(
|
|
70
|
-
|
|
71
|
+
event_names.step.SWITCH_CASE_MATCHED,
|
|
71
72
|
`Case matched for step: ${this.name}, executing case callable`
|
|
72
73
|
);
|
|
73
74
|
|
package/src/classes/workflow.js
CHANGED
|
@@ -3,6 +3,7 @@ import Base from './base.js';
|
|
|
3
3
|
import CallableRegistry from './callable_registry.js';
|
|
4
4
|
import Step from './steps/step.js';
|
|
5
5
|
import State from './state.js';
|
|
6
|
+
import { statuses, event_names, events, types, conditional_step_comparators, messages } from './instance_state.js';
|
|
6
7
|
import { base_types } from '../enums/index.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -76,7 +77,7 @@ export default class Workflow extends Base {
|
|
|
76
77
|
return this;
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
const is_resuming = this.status ===
|
|
80
|
+
const is_resuming = this.status === statuses.workflow.PAUSED;
|
|
80
81
|
const paused_at_index = this._steps.findIndex(step => step.id === this.current_step);
|
|
81
82
|
const start_index = is_resuming ? paused_at_index + 1 : 0;
|
|
82
83
|
|
|
@@ -84,13 +85,13 @@ export default class Workflow extends Base {
|
|
|
84
85
|
|
|
85
86
|
for (let i = start_index; i < this._steps.length; i++) {
|
|
86
87
|
if (this.should_break) {
|
|
87
|
-
this.log(
|
|
88
|
+
this.log(event_names.workflow.WORKFLOW_BREAK_EXECUTED, `Workflow "${this.name}" execution broken at step ${this._steps[i].name} - ${this._steps[i].id}.`);
|
|
88
89
|
break;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
if (this.should_skip) {
|
|
92
93
|
this.log(
|
|
93
|
-
|
|
94
|
+
event_names.workflow.WORKFLOW_STEP_SKIPPED,
|
|
94
95
|
`Workflow "${this.name}" skipping step ${this._steps[i].name} - ${this._steps[i].id}.`
|
|
95
96
|
);
|
|
96
97
|
this.should_skip = false;
|
|
@@ -131,8 +132,8 @@ export default class Workflow extends Base {
|
|
|
131
132
|
this.should_pause = false;
|
|
132
133
|
this.timing.resume_time = new Date();
|
|
133
134
|
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
events.workflow.emit(
|
|
136
|
+
event_names.workflow.WORKFLOW_RESUMED,
|
|
136
137
|
this.getState()
|
|
137
138
|
);
|
|
138
139
|
return this.execute();
|
|
@@ -148,7 +149,7 @@ export default class Workflow extends Base {
|
|
|
148
149
|
|
|
149
150
|
const result = await step.execute();
|
|
150
151
|
|
|
151
|
-
if (step.status ===
|
|
152
|
+
if (step.status === statuses.step.FAILED) {
|
|
152
153
|
throw step.errors[step.errors.length - 1] ?? new Error(`Step "${step.name}" failed`);
|
|
153
154
|
}
|
|
154
155
|
|
|
@@ -292,7 +293,7 @@ export default class Workflow extends Base {
|
|
|
292
293
|
this.should_continue = this.should_continue ?? false;
|
|
293
294
|
this.should_pause = this.should_pause ?? false;
|
|
294
295
|
this.should_skip = this.should_skip ?? false;
|
|
295
|
-
this.status = this.status ??
|
|
296
|
+
this.status = this.status ?? statuses.workflow.CREATED;
|
|
296
297
|
this.timing = {
|
|
297
298
|
...this.timing,
|
|
298
299
|
create_time: this.timing?.create_time ?? new Date(),
|
|
@@ -300,12 +301,19 @@ export default class Workflow extends Base {
|
|
|
300
301
|
resume_time: this.timing?.resume_time ?? null,
|
|
301
302
|
}
|
|
302
303
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
304
|
+
if (this.use_state_singleton) {
|
|
305
|
+
// The deprecated `State` singleton is shared by every workflow that opts into it, so it
|
|
306
|
+
// still needs an id-keyed registry (unlike per-instance state, which only ever has one
|
|
307
|
+
// workflow to represent and can just reference it directly - see the else branch).
|
|
308
|
+
const workflows = this.getState('workflows');
|
|
309
|
+
workflows[this.id] = this;
|
|
310
|
+
this.setState('workflows', workflows);
|
|
311
|
+
} else {
|
|
312
|
+
this.setState('workflow', this);
|
|
313
|
+
}
|
|
306
314
|
|
|
307
315
|
this.log(
|
|
308
|
-
|
|
316
|
+
event_names.workflow.WORKFLOW_CREATED,
|
|
309
317
|
`Workflow "${this.name}" initialized.`
|
|
310
318
|
);
|
|
311
319
|
}
|
|
@@ -334,11 +342,11 @@ export default class Workflow extends Base {
|
|
|
334
342
|
this.timing.create_time = new Date();
|
|
335
343
|
|
|
336
344
|
this.log(
|
|
337
|
-
|
|
345
|
+
event_names.workflow.WORKFLOW_CREATED,
|
|
338
346
|
`Workflow "${this.name}" created.`
|
|
339
347
|
);
|
|
340
348
|
|
|
341
|
-
return
|
|
349
|
+
return statuses.workflow.CREATED;
|
|
342
350
|
}
|
|
343
351
|
|
|
344
352
|
/**
|
|
@@ -354,23 +362,23 @@ export default class Workflow extends Base {
|
|
|
354
362
|
*/
|
|
355
363
|
markAsPaused() {
|
|
356
364
|
this.timing.pause_time = new Date();
|
|
357
|
-
this.status =
|
|
365
|
+
this.status = statuses.workflow.PAUSED;
|
|
358
366
|
|
|
359
|
-
|
|
360
|
-
|
|
367
|
+
events.workflow.emit(
|
|
368
|
+
event_names.workflow.WORKFLOW_PAUSED,
|
|
361
369
|
this.getState()
|
|
362
370
|
);
|
|
363
371
|
}
|
|
364
|
-
|
|
372
|
+
|
|
365
373
|
/**
|
|
366
374
|
* Marks the workflow as resumed.
|
|
367
375
|
*/
|
|
368
376
|
markAsResumed() {
|
|
369
377
|
this.timing.resume_time = new Date();
|
|
370
|
-
this.status =
|
|
378
|
+
this.status = statuses.workflow.RUNNING;
|
|
371
379
|
|
|
372
|
-
|
|
373
|
-
|
|
380
|
+
events.workflow.emit(
|
|
381
|
+
event_names.workflow.WORKFLOW_RESUMED,
|
|
374
382
|
this.getState()
|
|
375
383
|
);
|
|
376
384
|
}
|
|
@@ -384,8 +392,8 @@ export default class Workflow extends Base {
|
|
|
384
392
|
const [step] = this._steps.splice(fromIndex, 1);
|
|
385
393
|
this._steps.splice(toIndex, 0, step);
|
|
386
394
|
|
|
387
|
-
|
|
388
|
-
|
|
395
|
+
events.workflow.emit(
|
|
396
|
+
event_names.workflow.WORKFLOW_STEP_MOVED,
|
|
389
397
|
this.getState()
|
|
390
398
|
);
|
|
391
399
|
}
|
|
@@ -407,8 +415,8 @@ export default class Workflow extends Base {
|
|
|
407
415
|
this.should_pause = true;
|
|
408
416
|
this.timing.pause_time = new Date();
|
|
409
417
|
|
|
410
|
-
|
|
411
|
-
|
|
418
|
+
events.workflow.emit(
|
|
419
|
+
event_names.workflow.WORKFLOW_PAUSED,
|
|
412
420
|
this.getState()
|
|
413
421
|
);
|
|
414
422
|
}
|
|
@@ -597,16 +605,20 @@ export default class Workflow extends Base {
|
|
|
597
605
|
});
|
|
598
606
|
|
|
599
607
|
// The constructor above (via Base) always generates a fresh id, and addStep() has
|
|
600
|
-
// already stamped that fresh id onto each step's parent_workflow_id and
|
|
601
|
-
// the workflow under it in
|
|
602
|
-
// below would otherwise leave both
|
|
608
|
+
// already stamped that fresh id onto each step's parent_workflow_id and (in singleton mode)
|
|
609
|
+
// registered the workflow under it in the singleton's `workflows` registry. Restoring the
|
|
610
|
+
// real id below would otherwise leave both referencing a discarded id, so fix them up here
|
|
611
|
+
// too. Per-instance state needs no such fixup for its `workflow` key - it's the same live
|
|
612
|
+
// object, so the id mutation below is reflected automatically.
|
|
603
613
|
const stale_id = hydrated_workflow.id;
|
|
604
614
|
hydrated_workflow.id = parsed_workflow.id;
|
|
605
615
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
616
|
+
if (hydrated_workflow.use_state_singleton) {
|
|
617
|
+
const workflows = hydrated_workflow.getState('workflows');
|
|
618
|
+
delete workflows[stale_id];
|
|
619
|
+
workflows[hydrated_workflow.id] = hydrated_workflow;
|
|
620
|
+
hydrated_workflow.setState('workflows', workflows);
|
|
621
|
+
}
|
|
610
622
|
|
|
611
623
|
hydrated_workflow.steps.forEach(step => {
|
|
612
624
|
step.parent_workflow_id = hydrated_workflow.id;
|
|
@@ -622,3 +634,12 @@ export default class Workflow extends Base {
|
|
|
622
634
|
return hydrated_workflow;
|
|
623
635
|
}
|
|
624
636
|
}
|
|
637
|
+
|
|
638
|
+
// Framework constants, exposed as static members rather than duplicated into every
|
|
639
|
+
// Workflow's own state (see instance_state.js, the canonical source for these values).
|
|
640
|
+
Workflow.statuses = statuses;
|
|
641
|
+
Workflow.event_names = event_names;
|
|
642
|
+
Workflow.events = events;
|
|
643
|
+
Workflow.types = types;
|
|
644
|
+
Workflow.conditional_step_comparators = conditional_step_comparators;
|
|
645
|
+
Workflow.messages = messages;
|