@ronaldroe/micro-flow 1.0.0 → 1.2.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/README.md +63 -153
- package/dist/index.js +2 -0
- package/dist/index.js.map +7 -0
- package/dist/src/classes/base.js +4 -0
- package/dist/src/classes/base.js.map +7 -0
- package/dist/src/classes/events/broadcast.js +2 -0
- package/dist/src/classes/events/broadcast.js.map +7 -0
- package/dist/src/classes/events/event.js +2 -0
- package/dist/src/classes/events/event.js.map +7 -0
- package/dist/src/classes/events/index.js +2 -0
- package/dist/src/classes/events/index.js.map +7 -0
- package/dist/src/classes/events/state_event.js +2 -0
- package/dist/src/classes/events/state_event.js.map +7 -0
- package/dist/src/classes/events/step_event.js +2 -0
- package/dist/src/classes/events/step_event.js.map +7 -0
- package/dist/src/classes/events/workflow_event.js +2 -0
- package/dist/src/classes/events/workflow_event.js.map +7 -0
- package/dist/src/classes/index.js +2 -0
- package/dist/src/classes/index.js.map +7 -0
- package/dist/src/classes/state.js +2 -0
- package/dist/src/classes/state.js.map +7 -0
- package/dist/src/classes/steps/case.js +2 -0
- package/dist/src/classes/steps/case.js.map +7 -0
- package/dist/src/classes/steps/conditional_step.js +2 -0
- package/dist/src/classes/steps/conditional_step.js.map +7 -0
- package/dist/src/classes/steps/delay_step.js +2 -0
- package/dist/src/classes/steps/delay_step.js.map +7 -0
- package/dist/src/classes/steps/flow_control_step.js +2 -0
- package/dist/src/classes/steps/flow_control_step.js.map +7 -0
- package/dist/src/classes/steps/index.js +2 -0
- package/dist/src/classes/steps/index.js.map +7 -0
- package/dist/src/classes/steps/logic_step.js +2 -0
- package/dist/src/classes/steps/logic_step.js.map +7 -0
- package/dist/src/classes/steps/loop_step.js +2 -0
- package/dist/src/classes/steps/loop_step.js.map +7 -0
- package/dist/src/classes/steps/step.js +2 -0
- package/dist/src/classes/steps/step.js.map +7 -0
- package/dist/src/classes/steps/switch_step.js +2 -0
- package/dist/src/classes/steps/switch_step.js.map +7 -0
- package/dist/src/classes/workflow.js +2 -0
- package/dist/src/classes/workflow.js.map +7 -0
- package/dist/src/classes/workflow.test.js +2 -0
- package/dist/src/classes/workflow.test.js.map +7 -0
- package/dist/src/enums/base_types.js +2 -0
- package/dist/src/enums/base_types.js.map +7 -0
- package/dist/src/enums/conditional_step_comparators.js +2 -0
- package/dist/src/enums/conditional_step_comparators.js.map +7 -0
- package/dist/src/enums/delay_types.js +2 -0
- package/dist/src/enums/delay_types.js.map +7 -0
- package/dist/src/enums/errors.js +6 -0
- package/dist/src/enums/errors.js.map +7 -0
- package/dist/src/enums/flow_control_types.js +2 -0
- package/dist/src/enums/flow_control_types.js.map +7 -0
- package/dist/src/enums/index.js +2 -0
- package/dist/src/enums/index.js.map +7 -0
- package/dist/src/enums/logic_step_types.js +2 -0
- package/dist/src/enums/logic_step_types.js.map +7 -0
- package/dist/src/enums/loop_types.js +2 -0
- package/dist/src/enums/loop_types.js.map +7 -0
- package/dist/src/enums/state_event_names.js +2 -0
- package/dist/src/enums/state_event_names.js.map +7 -0
- package/dist/src/enums/step_event_names.js +2 -0
- package/dist/src/enums/step_event_names.js.map +7 -0
- package/dist/src/enums/step_statuses.js +2 -0
- package/dist/src/enums/step_statuses.js.map +7 -0
- package/dist/src/enums/step_types.js +2 -0
- package/dist/src/enums/step_types.js.map +7 -0
- package/dist/src/enums/sub_step_types.js +2 -0
- package/dist/src/enums/sub_step_types.js.map +7 -0
- package/dist/src/enums/workflow_event_names.js +2 -0
- package/dist/src/enums/workflow_event_names.js.map +7 -0
- package/dist/src/enums/workflow_statuses.js +2 -0
- package/dist/src/enums/workflow_statuses.js.map +7 -0
- package/package.json +10 -6
- package/src/classes/base.js +4 -5
- package/src/classes/events/event.js +16 -9
- package/src/classes/events/index.js +1 -1
- package/src/classes/events/state_event.js +28 -0
- package/src/classes/state.js +161 -54
- package/src/classes/steps/case.js +70 -0
- package/src/classes/steps/conditional_step.js +20 -7
- package/src/classes/steps/delay_step.js +103 -0
- package/src/classes/steps/flow_control_step.js +8 -7
- package/src/classes/steps/index.js +4 -0
- package/src/classes/steps/logic_step.js +89 -29
- package/src/classes/steps/loop_step.js +148 -0
- package/src/classes/steps/step.js +59 -18
- package/src/classes/steps/switch_step.js +77 -0
- package/src/classes/workflow.js +74 -26
- package/src/enums/conditional_step_comparators.js +38 -8
- package/src/enums/delay_types.js +0 -7
- package/src/enums/errors.js +2 -0
- package/src/enums/index.js +1 -0
- package/src/enums/loop_types.js +3 -1
- package/src/enums/state_event_names.js +17 -0
- package/src/enums/step_event_names.js +3 -0
- package/src/enums/step_types.js +2 -1
- package/src/enums/sub_step_types.js +15 -125
- package/src/enums/workflow_event_names.js +1 -0
- package/index.js +0 -1
- package/src/classes/events/broadcast.js +0 -57
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/enums/workflow_event_names.js"],
|
|
4
|
+
"sourcesContent": ["/**\n * Enumeration of workflow lifecycle event names.\n * Workflows emit these events during their creation and execution lifecycle.\n * \n * @enum {string}\n * @readonly\n */\nconst workflow_event_names = {\n WORKFLOW_BREAK_EXECUTED: 'workflow_break_executed',\n WORKFLOW_CANCELLED: 'workflow_cancelled',\n WORKFLOW_COMPLETE: 'workflow_complete',\n WORKFLOW_CREATED: 'workflow_created',\n WORKFLOW_ERRORED: 'workflow_errored',\n WORKFLOW_FAILED: 'workflow_failed',\n WORKFLOW_PAUSED: 'workflow_paused',\n WORKFLOW_RESUMED: 'workflow_resumed',\n WORKFLOW_RUNNING: 'workflow_running',\n WORKFLOW_STEP_ADDED: 'workflow_step_added',\n WORKFLOW_STEP_MOVED: 'workflow_step_moved',\n WORKFLOW_STEP_REMOVED: 'workflow_step_removed',\n WORKFLOW_STEP_SHIFTED: 'workflow_step_shifted',\n WORKFLOW_STEP_SKIPPED: 'workflow_step_skipped',\n WORKFLOW_STEPS_ADDED: 'workflow_steps_added',\n WORKFLOW_STEPS_CLEARED: 'workflow_steps_cleared',\n}\n\nexport default workflow_event_names;\n"],
|
|
5
|
+
"mappings": "AAOA,MAAMA,EAAuB,CAC3B,wBAAyB,0BACzB,mBAAoB,qBACpB,kBAAmB,oBACnB,iBAAkB,mBAClB,iBAAkB,mBAClB,gBAAiB,kBACjB,gBAAiB,kBACjB,iBAAkB,mBAClB,iBAAkB,mBAClB,oBAAqB,sBACrB,oBAAqB,sBACrB,sBAAuB,wBACvB,sBAAuB,wBACvB,sBAAuB,wBACvB,qBAAsB,uBACtB,uBAAwB,wBAC1B,EAEA,IAAOC,EAAQD",
|
|
6
|
+
"names": ["workflow_event_names", "workflow_event_names_default"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/enums/workflow_statuses.js"],
|
|
4
|
+
"sourcesContent": ["/**\n * Enumeration of possible workflow execution statuses.\n * Workflows transition through these statuses during their lifecycle.\n * \n * @enum {string}\n * @readonly\n */\nconst workflow_statuses = {\n CANCELLED: 'cancelled',\n COMPLETE: 'complete',\n CREATED: 'created',\n ERRORED: 'errored',\n FROZEN: 'frozen',\n FAILED: 'failed',\n PAUSED: 'paused',\n PENDING: 'pending',\n RUNNING: 'running',\n SKIPPED: 'skipped'\n};\n\nexport default workflow_statuses;\n"],
|
|
5
|
+
"mappings": "AAOA,MAAMA,EAAoB,CACxB,UAAW,YACX,SAAU,WACV,QAAS,UACT,QAAS,UACT,OAAQ,SACR,OAAQ,SACR,OAAQ,SACR,QAAS,UACT,QAAS,UACT,QAAS,SACX,EAEA,IAAOC,EAAQD",
|
|
6
|
+
"names": ["workflow_statuses", "workflow_statuses_default"]
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ronaldroe/micro-flow",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A lightweight, flexible workflow orchestration library for Node.js and browser environments. Build complex, sequential processes with ease using an intuitive API that supports conditional logic, flow control, event handling, and state management.",
|
|
5
|
-
"main": "index.js",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./dist/index.js"
|
|
8
|
+
},
|
|
6
9
|
"scripts": {
|
|
7
10
|
"build": "node build.js",
|
|
8
|
-
"test": "
|
|
11
|
+
"test": "vitest run",
|
|
12
|
+
"test:watch": "vitest",
|
|
13
|
+
"test:coverage": "vitest run --coverage",
|
|
9
14
|
"prepublishOnly": "npm run build"
|
|
10
15
|
},
|
|
11
16
|
"keywords": [
|
|
@@ -32,7 +37,7 @@
|
|
|
32
37
|
},
|
|
33
38
|
"homepage": "https://github.com/starkeysoft/micro-flow#readme",
|
|
34
39
|
"files": [
|
|
35
|
-
"
|
|
40
|
+
"dist/",
|
|
36
41
|
"src/",
|
|
37
42
|
"README.md",
|
|
38
43
|
"LICENSE"
|
|
@@ -42,8 +47,7 @@
|
|
|
42
47
|
},
|
|
43
48
|
"dependencies": {
|
|
44
49
|
"date-fns": "^4.1.0",
|
|
45
|
-
"node-schedule": "^2.1.1"
|
|
46
|
-
"uuid": "^13.0.0"
|
|
50
|
+
"node-schedule": "^2.1.1"
|
|
47
51
|
},
|
|
48
52
|
"devDependencies": {
|
|
49
53
|
"@vitest/coverage-v8": "^4.0.14",
|
package/src/classes/base.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import crypto from 'crypto';
|
|
2
2
|
import { base_types } from '../enums/index.js';
|
|
3
3
|
import State from './state.js';
|
|
4
4
|
|
|
@@ -15,14 +15,13 @@ export default class Base {
|
|
|
15
15
|
* @param {string} [options.base_type=base_types.STEP] - Type of the base instance.
|
|
16
16
|
*/
|
|
17
17
|
constructor({ name, base_type = base_types.STEP }) {
|
|
18
|
-
this.id =
|
|
18
|
+
this.id = crypto.randomUUID();
|
|
19
19
|
this.name = name ?? `${base_type}-${this.id}`;
|
|
20
20
|
|
|
21
21
|
this.base_type = base_type;
|
|
22
22
|
this.timing = {
|
|
23
23
|
cancel_time: null,
|
|
24
24
|
complete_time: null,
|
|
25
|
-
end_time: null,
|
|
26
25
|
execution_time_ms: null,
|
|
27
26
|
start_time: null,
|
|
28
27
|
}
|
|
@@ -54,7 +53,7 @@ export default class Base {
|
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
const logMessage = message ? `\n[${this.base_type.toUpperCase()} - ${this.name}] ${message}` : `\n[${this.base_type.toUpperCase()} - ${this.name}] Event: ${event_name}`;
|
|
57
|
-
const logType = event_name.endsWith('
|
|
56
|
+
const logType = event_name.endsWith('_failed') ? 'error' : 'log';
|
|
58
57
|
|
|
59
58
|
console[logType](logMessage);
|
|
60
59
|
}
|
|
@@ -105,7 +104,7 @@ export default class Base {
|
|
|
105
104
|
* Marks the instance as running and sets the start time.
|
|
106
105
|
*/
|
|
107
106
|
markAsRunning() {
|
|
108
|
-
this.timing.start_time = new Date();
|
|
107
|
+
this.timing.start_time = this.timing.start_time ?? new Date();
|
|
109
108
|
this.status = State.get('statuses')[this.base_type].RUNNING;
|
|
110
109
|
|
|
111
110
|
this.log(
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { errors, warnings } from '../../enums/index.js';
|
|
2
|
-
import Broadcast from './broadcast.js';
|
|
3
2
|
|
|
4
3
|
/**
|
|
5
4
|
* Event class for micro-flow
|
|
6
5
|
* Provides a simple event emitter implementation for workflow steps and state changes.
|
|
7
6
|
*
|
|
8
7
|
* This class is used for emitting and listening to events within workflows and steps.
|
|
9
|
-
* For broadcasting events across multiple workflows or listeners,
|
|
8
|
+
* For broadcasting events across multiple workflows or listeners, it uses BroadcastChannel.
|
|
10
9
|
*/
|
|
11
10
|
class Event extends EventTarget {
|
|
12
11
|
/**
|
|
@@ -50,9 +49,9 @@ class Event extends EventTarget {
|
|
|
50
49
|
const result = this.dispatchEvent(custom_event);
|
|
51
50
|
|
|
52
51
|
try {
|
|
53
|
-
const channel = new
|
|
54
|
-
channel.
|
|
55
|
-
channel.
|
|
52
|
+
const channel = new BroadcastChannel(event_name);
|
|
53
|
+
channel.postMessage(workingData);
|
|
54
|
+
channel.close();
|
|
56
55
|
} catch (e) {
|
|
57
56
|
console.warn(warnings.BROADCAST_FAILED, e);
|
|
58
57
|
}
|
|
@@ -63,11 +62,19 @@ class Event extends EventTarget {
|
|
|
63
62
|
* Listen for broadcasts on a given event name (channel).
|
|
64
63
|
* @param {string} event_name - The event name/channel to listen for.
|
|
65
64
|
* @param {Function} listener - Callback for broadcasted data.
|
|
66
|
-
* @returns {
|
|
65
|
+
* @returns {BroadcastChannel} Returns the channel with send() and destroy() aliases.
|
|
67
66
|
*/
|
|
68
67
|
onBroadcast(event_name, listener) {
|
|
69
|
-
const channel = new
|
|
70
|
-
channel.
|
|
68
|
+
const channel = new BroadcastChannel(event_name);
|
|
69
|
+
channel.onmessage = (event) => {
|
|
70
|
+
listener(event.data);
|
|
71
|
+
};
|
|
72
|
+
channel.send = (data) => {
|
|
73
|
+
channel.postMessage(data);
|
|
74
|
+
};
|
|
75
|
+
channel.destroy = () => {
|
|
76
|
+
channel.close();
|
|
77
|
+
};
|
|
71
78
|
return channel;
|
|
72
79
|
}
|
|
73
80
|
|
|
@@ -75,7 +82,7 @@ class Event extends EventTarget {
|
|
|
75
82
|
* Listen for both local and broadcast events.
|
|
76
83
|
* @param {string} event_name - The event name/channel to listen for.
|
|
77
84
|
* @param {Function} listener - Callback for event data.
|
|
78
|
-
|
|
85
|
+
* @returns {Object} Returns { event: this, broadcast: BroadcastChannel }
|
|
79
86
|
*/
|
|
80
87
|
onAny(event_name, listener) {
|
|
81
88
|
this.on(event_name, listener);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { default as Broadcast } from './broadcast.js';
|
|
2
1
|
export { default as Event } from './event.js';
|
|
2
|
+
export { default as StateEvent } from './state_event.js';
|
|
3
3
|
export { default as StepEvent } from './step_event.js';
|
|
4
4
|
export { default as WorkflowEvent } from './workflow_event.js';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Event } from './index.js';
|
|
2
|
+
import { state_event_names } from '../../enums/index.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Manages state-specific events by extending the base Event class.
|
|
6
|
+
* @class StateEvent
|
|
7
|
+
* @extends Event
|
|
8
|
+
*/
|
|
9
|
+
export default class StateEvent extends Event {
|
|
10
|
+
event_names = state_event_names;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates a new StateEvent instance and registers all state events.
|
|
14
|
+
* @constructor
|
|
15
|
+
*/
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
this.registerStateEvents();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Registers all state event names defined in the state_event_names enum.
|
|
23
|
+
* @returns {void}
|
|
24
|
+
*/
|
|
25
|
+
registerStateEvents() {
|
|
26
|
+
this.registerEvents(this.event_names);
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/classes/state.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { errors, warnings } from '../enums/errors.js';
|
|
2
|
-
import { StepEvent, WorkflowEvent } from './events/index.js';
|
|
2
|
+
import { StepEvent, WorkflowEvent, StateEvent } from './events/index.js';
|
|
3
3
|
import {
|
|
4
4
|
base_types,
|
|
5
5
|
conditional_step_comparators,
|
|
6
|
+
state_event_names,
|
|
6
7
|
step_event_names,
|
|
7
8
|
step_statuses,
|
|
8
9
|
step_types,
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
workflow_statuses,
|
|
12
13
|
} from '../enums/index.js';
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
const defaultState = {
|
|
15
16
|
messages: {
|
|
16
17
|
errors,
|
|
17
18
|
warnings,
|
|
@@ -22,11 +23,13 @@ let state = {
|
|
|
22
23
|
},
|
|
23
24
|
event_names: {
|
|
24
25
|
workflow: workflow_event_names,
|
|
25
|
-
step: step_event_names
|
|
26
|
+
step: step_event_names,
|
|
27
|
+
state: state_event_names,
|
|
26
28
|
},
|
|
27
29
|
events: {
|
|
28
30
|
workflow: new WorkflowEvent(),
|
|
29
|
-
step: new StepEvent()
|
|
31
|
+
step: new StepEvent(),
|
|
32
|
+
state: new StateEvent(),
|
|
30
33
|
},
|
|
31
34
|
types: {
|
|
32
35
|
base_types,
|
|
@@ -35,7 +38,13 @@ let state = {
|
|
|
35
38
|
},
|
|
36
39
|
workflows: {},
|
|
37
40
|
conditional_step_comparators
|
|
38
|
-
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
let state = { ...defaultState };
|
|
44
|
+
|
|
45
|
+
// Module-level shortcuts for events and event_names
|
|
46
|
+
const events = state.events;
|
|
47
|
+
const event_names = state.event_names;
|
|
39
48
|
|
|
40
49
|
/**
|
|
41
50
|
* Singleton class representing the global state for workflows, steps, and processes.
|
|
@@ -56,21 +65,62 @@ class State {
|
|
|
56
65
|
if (!path) {
|
|
57
66
|
throw new Error(errors.INVALID_STATE_PATH);
|
|
58
67
|
}
|
|
59
|
-
|
|
60
|
-
const parts =
|
|
68
|
+
|
|
69
|
+
const parts = State.parsePath(path);
|
|
61
70
|
let current = state;
|
|
62
|
-
|
|
71
|
+
|
|
63
72
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
64
73
|
const part = parts[i];
|
|
65
|
-
|
|
74
|
+
|
|
66
75
|
if (!Object.prototype.hasOwnProperty.call(current, part) || typeof current[part] !== 'object') {
|
|
67
76
|
return;
|
|
68
77
|
}
|
|
69
|
-
|
|
78
|
+
|
|
70
79
|
current = current[part];
|
|
71
80
|
}
|
|
72
|
-
|
|
81
|
+
|
|
73
82
|
delete current[parts[parts.length - 1]];
|
|
83
|
+
|
|
84
|
+
events.state.emit(event_names.state.DELETED, { state });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Iterates over a collection (array or object) located at the specified state path,
|
|
89
|
+
* executing a callback function for each item.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} path - The path of the state property to iterate over.
|
|
92
|
+
* @param {Function} callback - The function to execute for each item in the collection.
|
|
93
|
+
* @throws {Error} Throws if the state property at the path is not an array or object.
|
|
94
|
+
*/
|
|
95
|
+
static async each(path, callback) {
|
|
96
|
+
const collection = State.get(path);
|
|
97
|
+
|
|
98
|
+
if (Array.isArray(collection)) {
|
|
99
|
+
for (const [index, item] of collection.entries()) {
|
|
100
|
+
events.state.emit(event_names.state.EACH, { state });
|
|
101
|
+
await callback(item, index);
|
|
102
|
+
}
|
|
103
|
+
} else if (
|
|
104
|
+
typeof collection === 'object' &&
|
|
105
|
+
Object.prototype.toString.call(collection) === '[object Object]'
|
|
106
|
+
) {
|
|
107
|
+
for (const key of Object.keys(collection)) {
|
|
108
|
+
events.state.emit(event_names.state.EACH, { state });
|
|
109
|
+
await callback(collection[key], key);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
throw new Error(errors.VALUE_NOT_ITERABLE);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Freezes the entire state object, making it immutable.
|
|
118
|
+
* @returns {void}
|
|
119
|
+
*/
|
|
120
|
+
static freeze() {
|
|
121
|
+
const frozenState = Object.freeze(state);
|
|
122
|
+
events.state.emit(event_names.state.FROZEN, { state });
|
|
123
|
+
return frozenState;
|
|
74
124
|
}
|
|
75
125
|
|
|
76
126
|
/**
|
|
@@ -79,53 +129,94 @@ class State {
|
|
|
79
129
|
* @param {string} path - The path of the state property to get. Supports both dot notation
|
|
80
130
|
* (e.g., "user.profile.name") and bracket notation (e.g., "users[0].name" or "data['key-name']").
|
|
81
131
|
* Special values:
|
|
82
|
-
* - Falsy values (null, undefined, false): Returns entire state object
|
|
83
|
-
* - "*"
|
|
132
|
+
* - Falsy values (null, undefined, false, ""): Returns entire state object
|
|
133
|
+
* - "*": Returns entire state object
|
|
84
134
|
* @param {*} [defaultValue=null] - Default value to return if the path doesn't exist.
|
|
135
|
+
* @param {string} [type='string'] - The output type to convert the value to.
|
|
136
|
+
* Supported types: "string", "number", "boolean".
|
|
85
137
|
* @returns {*} The value of the state property, or defaultValue if not found. null if not found
|
|
86
138
|
* and no defaultValue provided.
|
|
139
|
+
* @throws {Error} Throws if the value cannot be converted to the specified type.
|
|
87
140
|
*/
|
|
88
|
-
static get(path, defaultValue = null) {
|
|
141
|
+
static get(path, defaultValue = null, type = null) {
|
|
142
|
+
let gotten = state;
|
|
89
143
|
if (!path || ['*', ''].includes(path)) {
|
|
90
|
-
|
|
144
|
+
events.state.emit(event_names.state.GET, { state: gotten ?? defaultValue });
|
|
145
|
+
return gotten;
|
|
91
146
|
}
|
|
92
147
|
|
|
93
|
-
|
|
94
|
-
}
|
|
148
|
+
gotten = State.getFromPropertyPath(path, false) ?? defaultValue;
|
|
95
149
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
150
|
+
if (type) {
|
|
151
|
+
try {
|
|
152
|
+
switch (type) {
|
|
153
|
+
case 'string':
|
|
154
|
+
gotten = String(gotten);
|
|
155
|
+
break;
|
|
156
|
+
case 'number':
|
|
157
|
+
gotten = Number(gotten);
|
|
158
|
+
break;
|
|
159
|
+
case 'boolean':
|
|
160
|
+
gotten = Boolean(gotten);
|
|
161
|
+
break;
|
|
162
|
+
default:
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error("Error converting state value: ", error);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
events.state.emit(event_names.state.GET, { state: gotten });
|
|
171
|
+
|
|
172
|
+
return gotten ?? defaultValue;
|
|
102
173
|
}
|
|
103
174
|
|
|
104
175
|
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
176
|
+
* Resolves a nested property path within the state object.
|
|
177
|
+
* Supports both dot notation and bracket notation.
|
|
107
178
|
*
|
|
108
|
-
* @param {string} path - The path
|
|
109
|
-
*
|
|
110
|
-
* @
|
|
111
|
-
* @returns {void}
|
|
112
|
-
* @throws {Error} Throws if path is empty or invalid.
|
|
179
|
+
* @param {string} path - The path to the property (e.g., "user.profile.name", "users[0].name", "data['key-name']").
|
|
180
|
+
* @param {boolean} [emit=true] - Whether to emit the GET_FROM_PROPERTY_PATH event.
|
|
181
|
+
* @returns {*} The value at the specified path, or undefined if not found.
|
|
113
182
|
*/
|
|
114
|
-
static
|
|
115
|
-
|
|
116
|
-
|
|
183
|
+
static getFromPropertyPath(path, emit = true) {
|
|
184
|
+
const parts = State.parsePath(path);
|
|
185
|
+
let current = state;
|
|
186
|
+
|
|
187
|
+
for (const part of parts) {
|
|
188
|
+
if (current && Object.prototype.hasOwnProperty.call(current, part)) {
|
|
189
|
+
current = current[part];
|
|
190
|
+
} else {
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (emit) {
|
|
196
|
+
events.state.emit(event_names.state.GET_FROM_PROPERTY_PATH, { state });
|
|
117
197
|
}
|
|
118
198
|
|
|
119
|
-
|
|
199
|
+
return current;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Gets the entire state object.
|
|
204
|
+
* @returns {Object} The entire state object.
|
|
205
|
+
*/
|
|
206
|
+
static getState() {
|
|
207
|
+
events.state.emit(event_names.state.GET_STATE, { state });
|
|
208
|
+
return state;
|
|
120
209
|
}
|
|
121
210
|
|
|
122
211
|
/**
|
|
123
212
|
* Merges an object into the current State.
|
|
124
213
|
* @param {Object} newState - The object to merge into the current State.
|
|
125
|
-
* @returns {
|
|
214
|
+
* @returns {object} The updated state object.
|
|
126
215
|
*/
|
|
127
216
|
static merge(newState) {
|
|
128
217
|
state = { ...state, ...newState };
|
|
218
|
+
events.state.emit(event_names.state.MERGE, { state });
|
|
219
|
+
return state;
|
|
129
220
|
}
|
|
130
221
|
|
|
131
222
|
/**
|
|
@@ -146,25 +237,36 @@ class State {
|
|
|
146
237
|
}
|
|
147
238
|
|
|
148
239
|
/**
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* @param {string} path - The path to the property (e.g., "user.profile.name", "users[0].name", "data['key-name']").
|
|
153
|
-
* @returns {*} The value at the specified path, or undefined if not found.
|
|
240
|
+
* Resets the state to its default values.
|
|
241
|
+
* @returns {object} The reset state object.
|
|
154
242
|
*/
|
|
155
|
-
static
|
|
156
|
-
|
|
157
|
-
|
|
243
|
+
static reset() {
|
|
244
|
+
state = {
|
|
245
|
+
...defaultState,
|
|
246
|
+
workflows: {}, // Always create fresh to avoid shared reference mutation
|
|
247
|
+
};
|
|
248
|
+
events.state.emit(event_names.state.RESET, { state });
|
|
249
|
+
return state;
|
|
250
|
+
}
|
|
158
251
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
252
|
+
/**
|
|
253
|
+
* Sets the value of a state property using dot-notation or bracket-notation path access.
|
|
254
|
+
* Creates intermediate objects if they don't exist.
|
|
255
|
+
*
|
|
256
|
+
* @param {string} path - The path of the state property to set. Supports both dot notation
|
|
257
|
+
* (e.g., "user.profile.name") and bracket notation (e.g., "users[0].name" or "data['key-name']").
|
|
258
|
+
* @param {*} value - The value to set for the state property.
|
|
259
|
+
* @returns {void}
|
|
260
|
+
* @throws {Error} Throws if path is empty or invalid.
|
|
261
|
+
*/
|
|
262
|
+
static set(path, value) {
|
|
263
|
+
if (!path) {
|
|
264
|
+
throw new Error(errors.INVALID_STATE_PATH);
|
|
165
265
|
}
|
|
166
266
|
|
|
167
|
-
|
|
267
|
+
events.state.emit(event_names.state.SET, { state });
|
|
268
|
+
|
|
269
|
+
State.setToPropertyPath(path, value, false);
|
|
168
270
|
}
|
|
169
271
|
|
|
170
272
|
/**
|
|
@@ -173,15 +275,16 @@ class State {
|
|
|
173
275
|
*
|
|
174
276
|
* @param {string} path - The path to the property (e.g., "user.profile.name", "users[0].name", "data['key-name']").
|
|
175
277
|
* @param {*} value - The value to set at the specified path.
|
|
278
|
+
* @param {boolean} [emit=true] - Whether to emit the SET_TO_PROPERTY_PATH event.
|
|
176
279
|
*/
|
|
177
|
-
static setToPropertyPath(path, value) {
|
|
178
|
-
const parts =
|
|
280
|
+
static setToPropertyPath(path, value, emit = true) {
|
|
281
|
+
const parts = State.parsePath(path);
|
|
179
282
|
let current = state;
|
|
180
|
-
|
|
283
|
+
|
|
181
284
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
182
285
|
const part = parts[i];
|
|
183
286
|
const nextPart = parts[i + 1];
|
|
184
|
-
|
|
287
|
+
|
|
185
288
|
if (!Object.prototype.hasOwnProperty.call(current, part) || typeof current[part] !== 'object') {
|
|
186
289
|
// Determine if next part is an array index (numeric)
|
|
187
290
|
const isNextPartNumeric = /^\d+$/.test(nextPart);
|
|
@@ -189,6 +292,10 @@ class State {
|
|
|
189
292
|
}
|
|
190
293
|
current = current[part];
|
|
191
294
|
}
|
|
295
|
+
|
|
296
|
+
if (emit) {
|
|
297
|
+
events.state.emit(event_names.state.SET_TO_PROPERTY_PATH, { state });
|
|
298
|
+
}
|
|
192
299
|
|
|
193
300
|
current[parts[parts.length - 1]] = value;
|
|
194
301
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import LogicStep from './logic_step.js';
|
|
2
|
+
import { conditional_step_comparators } from '../../enums/index.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Case class representing a single case in a switch statement.
|
|
6
|
+
* Used in conjunction with SwitchStep to create switch/case logic.
|
|
7
|
+
* @class Case
|
|
8
|
+
* @extends LogicStep
|
|
9
|
+
*/
|
|
10
|
+
export default class Case extends LogicStep {
|
|
11
|
+
static step_name = 'case';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new Case instance.
|
|
15
|
+
* Note: Plain LogicStep instances can be used in place of Case, but they MUST have conditional.subject set.
|
|
16
|
+
* @param {Object} options - Configuration options.
|
|
17
|
+
* @param {string} [options.name] - Name of the case.
|
|
18
|
+
* @param {Object} [options.conditional] - Conditional configuration.
|
|
19
|
+
* @param {*|Function} [options.conditional.subject=null] - Subject to evaluate (typically set by SwitchStep). Can be a function.
|
|
20
|
+
* @param {conditional_step_comparators|string} [options.conditional.operator=null] - Comparison operator.
|
|
21
|
+
* @param {*|Function} [options.conditional.value=null] - Value to compare against. Can be a function that returns the value.
|
|
22
|
+
* @param {Function|Step|Workflow} [options.callable=async () => {}] - Function, Step, or Workflow to execute when case matches.
|
|
23
|
+
* @param {boolean} [options.force_subject_override=false] - Force override of subject even if already set.
|
|
24
|
+
*/
|
|
25
|
+
constructor({
|
|
26
|
+
name,
|
|
27
|
+
conditional = {
|
|
28
|
+
subject: null,
|
|
29
|
+
operator: null,
|
|
30
|
+
value: null,
|
|
31
|
+
},
|
|
32
|
+
callable = async () => {},
|
|
33
|
+
force_subject_override = false,
|
|
34
|
+
}) {
|
|
35
|
+
super({
|
|
36
|
+
name,
|
|
37
|
+
step_type: Case.step_name,
|
|
38
|
+
callable,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
this.conditional_config = conditional;
|
|
42
|
+
this.force_subject_override = force_subject_override;
|
|
43
|
+
|
|
44
|
+
this.is_matched = false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Sets the switch subject from the parent SwitchStep.
|
|
49
|
+
* Automatically sets the conditional subject if not already set or if force_subject_override is true.
|
|
50
|
+
* @param {*} subject - The subject value from the SwitchStep.
|
|
51
|
+
* @throws {Error} If no subject is provided and conditional.subject is not set.
|
|
52
|
+
* @throws {Error} If the resulting conditional configuration is invalid.
|
|
53
|
+
*/
|
|
54
|
+
set switch_subject(subject) {
|
|
55
|
+
const subjectProvided = subject !== null && subject !== undefined;
|
|
56
|
+
const hasExistingSubject = this.conditional_config.subject !== null && this.conditional_config.subject !== undefined;
|
|
57
|
+
|
|
58
|
+
if (!subjectProvided && !hasExistingSubject) {
|
|
59
|
+
throw new Error(`No subject set for case step: ${this.name}, using default equality check`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (subjectProvided && (!hasExistingSubject || this.force_subject_override)) {
|
|
63
|
+
this.conditional_config.subject = subject;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!this.conditionalIsValid()) {
|
|
67
|
+
throw new Error(`Invalid conditional configuration for case step: ${this.name}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import LogicStep from './logic_step.js';
|
|
2
|
+
import { conditional_step_comparators } from '../../enums/index.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* ConditionalStep class for branching logic based on conditions.
|
|
@@ -13,9 +14,9 @@ export default class ConditionalStep extends LogicStep {
|
|
|
13
14
|
* @param {Object} options - Configuration options.
|
|
14
15
|
* @param {string} [options.name] - Name of the step.
|
|
15
16
|
* @param {Object} [options.conditional] - Conditional configuration.
|
|
16
|
-
* @param {
|
|
17
|
-
* @param {string} [options.conditional.operator] - Comparison operator.
|
|
18
|
-
* @param {
|
|
17
|
+
* @param {*|Function} [options.conditional.subject] - Subject to evaluate. Can be a function that returns the value.
|
|
18
|
+
* @param {conditional_step_comparators|string} [options.conditional.operator] - Comparison operator.
|
|
19
|
+
* @param {*|Function} [options.conditional.value] - Value to compare against. Can be a function that returns the value.
|
|
19
20
|
* @param {Function|Step|Workflow} [options.true_callable=async () => {}] - Callable to execute if condition is true.
|
|
20
21
|
* @param {Function|Step|Workflow} [options.false_callable=async () => {}] - Callable to execute if condition is false.
|
|
21
22
|
*/
|
|
@@ -34,8 +35,18 @@ export default class ConditionalStep extends LogicStep {
|
|
|
34
35
|
conditional
|
|
35
36
|
});
|
|
36
37
|
|
|
37
|
-
this
|
|
38
|
-
|
|
38
|
+
// Bind function callables to this step instance for state access
|
|
39
|
+
if (typeof true_callable === 'function') {
|
|
40
|
+
this.true_callable = true_callable.bind(this);
|
|
41
|
+
} else {
|
|
42
|
+
this.true_callable = true_callable;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (typeof false_callable === 'function') {
|
|
46
|
+
this.false_callable = false_callable.bind(this);
|
|
47
|
+
} else {
|
|
48
|
+
this.false_callable = false_callable;
|
|
49
|
+
}
|
|
39
50
|
|
|
40
51
|
this.callable = this.conditional.bind(this);
|
|
41
52
|
}
|
|
@@ -60,6 +71,7 @@ export default class ConditionalStep extends LogicStep {
|
|
|
60
71
|
if (typeof true_callable === 'function') {
|
|
61
72
|
result = await true_callable();
|
|
62
73
|
} else {
|
|
74
|
+
true_callable.parentWorkflowId = this.parentWorkflowId;
|
|
63
75
|
result = await true_callable.execute();
|
|
64
76
|
}
|
|
65
77
|
} else {
|
|
@@ -71,10 +83,11 @@ export default class ConditionalStep extends LogicStep {
|
|
|
71
83
|
if (typeof false_callable === 'function') {
|
|
72
84
|
result = await false_callable();
|
|
73
85
|
} else {
|
|
86
|
+
false_callable.parentWorkflowId = this.parentWorkflowId;
|
|
74
87
|
result = await false_callable.execute();
|
|
75
88
|
}
|
|
76
89
|
}
|
|
77
90
|
|
|
78
|
-
return result;
|
|
91
|
+
return { message: `Conditional step ${this.name} completed`, result };
|
|
79
92
|
}
|
|
80
93
|
}
|