@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
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { errors, warnings } from '../enums/errors.js';
|
|
2
|
+
import { StepEvent, WorkflowEvent, StateEvent } from './events/index.js';
|
|
3
|
+
import {
|
|
4
|
+
base_types,
|
|
5
|
+
conditional_step_comparators,
|
|
6
|
+
state_event_names,
|
|
7
|
+
step_event_names,
|
|
8
|
+
step_statuses,
|
|
9
|
+
step_types,
|
|
10
|
+
sub_step_types,
|
|
11
|
+
workflow_event_names,
|
|
12
|
+
workflow_statuses,
|
|
13
|
+
} from '../enums/index.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parses a property path string into an array of keys, supporting both dot notation
|
|
17
|
+
* and bracket notation.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} path - The path to parse (e.g., "user.profile.name", "users[0].name", "data['key-name']").
|
|
20
|
+
* @returns {string[]} Array of property keys.
|
|
21
|
+
*/
|
|
22
|
+
function parsePath(path) {
|
|
23
|
+
const matches = path.match(/[^.[\]]+|(?<=\[)([^\]]+)(?=\])/g);
|
|
24
|
+
|
|
25
|
+
if (!matches) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return matches.map(part => part.replace(/^['"]|['"]$/g, ''));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Resolves a nested property path within an arbitrary object.
|
|
34
|
+
* @param {Object} target - The object to read from.
|
|
35
|
+
* @param {string} path - The path to the property.
|
|
36
|
+
* @returns {*} The value at the specified path, or undefined if not found.
|
|
37
|
+
*/
|
|
38
|
+
function getAtPath(target, path) {
|
|
39
|
+
const parts = parsePath(path);
|
|
40
|
+
let current = target;
|
|
41
|
+
|
|
42
|
+
for (const part of parts) {
|
|
43
|
+
if (current && Object.prototype.hasOwnProperty.call(current, part)) {
|
|
44
|
+
current = current[part];
|
|
45
|
+
} else {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return current;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Sets a nested property value within an arbitrary object based on a path.
|
|
55
|
+
* Creates intermediate objects/arrays as needed.
|
|
56
|
+
* @param {Object} target - The object to write to.
|
|
57
|
+
* @param {string} path - The path to the property.
|
|
58
|
+
* @param {*} value - The value to set at the specified path.
|
|
59
|
+
*/
|
|
60
|
+
function setAtPath(target, path, value) {
|
|
61
|
+
const parts = parsePath(path);
|
|
62
|
+
let current = target;
|
|
63
|
+
|
|
64
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
65
|
+
const part = parts[i];
|
|
66
|
+
const next_part = parts[i + 1];
|
|
67
|
+
|
|
68
|
+
if (!Object.prototype.hasOwnProperty.call(current, part) || typeof current[part] !== 'object') {
|
|
69
|
+
const is_next_part_numeric = /^\d+$/.test(next_part);
|
|
70
|
+
current[part] = is_next_part_numeric ? [] : {};
|
|
71
|
+
}
|
|
72
|
+
current = current[part];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
current[parts[parts.length - 1]] = value;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Deletes a property from an arbitrary object using a path.
|
|
80
|
+
* @param {Object} target - The object to delete from.
|
|
81
|
+
* @param {string} path - The path of the property to delete.
|
|
82
|
+
*/
|
|
83
|
+
function deleteAtPath(target, path) {
|
|
84
|
+
const parts = parsePath(path);
|
|
85
|
+
let current = target;
|
|
86
|
+
|
|
87
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
88
|
+
const part = parts[i];
|
|
89
|
+
|
|
90
|
+
if (!Object.prototype.hasOwnProperty.call(current, part) || typeof current[part] !== 'object') {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
current = current[part];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
delete current[parts[parts.length - 1]];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Converts a resolved state value to the requested output type.
|
|
102
|
+
* @param {*} value - The value to convert.
|
|
103
|
+
* @param {string|null} type - One of "string", "number", "boolean".
|
|
104
|
+
* @returns {*} The converted value, or the original value if conversion fails or type is unrecognized.
|
|
105
|
+
*/
|
|
106
|
+
function convertType(value, type) {
|
|
107
|
+
if (!type) {
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
switch (type) {
|
|
113
|
+
case 'string':
|
|
114
|
+
return String(value);
|
|
115
|
+
case 'number':
|
|
116
|
+
return Number(value);
|
|
117
|
+
case 'boolean':
|
|
118
|
+
return Boolean(value);
|
|
119
|
+
default:
|
|
120
|
+
return value;
|
|
121
|
+
}
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error('Error converting state value: ', error);
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Framework constants, built once here as the single canonical source. `Workflow` (see
|
|
130
|
+
* `workflow.js`) assigns these as its own static members - the public surface library
|
|
131
|
+
* consumers should reach them through - while step classes that need one directly may import
|
|
132
|
+
* the named export straight from this module instead of going through `Workflow` (avoiding an
|
|
133
|
+
* import cycle). The deprecated `State` singleton (`state.js`) also builds its `default_state`
|
|
134
|
+
* from these same values, so the `events.*` instances stay identical (by reference) regardless
|
|
135
|
+
* of whether a given `Workflow`/`Step` has opted into `use_state_singleton` - listeners
|
|
136
|
+
* registered via `State.get('events.workflow')` keep receiving events either way.
|
|
137
|
+
*/
|
|
138
|
+
export const messages = { errors, warnings };
|
|
139
|
+
export const statuses = { workflow: workflow_statuses, step: step_statuses };
|
|
140
|
+
export const event_names = { workflow: workflow_event_names, step: step_event_names, state: state_event_names };
|
|
141
|
+
export const events = { workflow: new WorkflowEvent(), step: new StepEvent(), state: new StateEvent() };
|
|
142
|
+
export const types = { base_types, step_types, sub_step_types };
|
|
143
|
+
export { conditional_step_comparators };
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Per-instance replacement for the deprecated `State` singleton. Each `Workflow` owns one of
|
|
147
|
+
* these (created in its constructor), and shares it with every `Step` added to it, so that
|
|
148
|
+
* `getState()`/`setState()`/`deleteState()` calls made anywhere in that workflow's tree read and
|
|
149
|
+
* write the same, workflow-scoped data instead of a single process-wide object. A `Workflow`
|
|
150
|
+
* registers itself under the `workflow` key of its own `InstanceState` (see
|
|
151
|
+
* `initializeWorkflowState()` in `workflow.js`), so `getState('workflow')` resolves to the live
|
|
152
|
+
* owning `Workflow` instance; any other path is arbitrary user data set via `setState()`.
|
|
153
|
+
*
|
|
154
|
+
* Supports the same dot-notation/bracket-notation path access as `State`.
|
|
155
|
+
*
|
|
156
|
+
* @class InstanceState
|
|
157
|
+
*/
|
|
158
|
+
export class InstanceState {
|
|
159
|
+
/**
|
|
160
|
+
* Creates a new InstanceState.
|
|
161
|
+
* @param {Object} [initial={}] - Initial data.
|
|
162
|
+
*/
|
|
163
|
+
constructor(initial = {}) {
|
|
164
|
+
this.data = initial;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Gets the value of a state property using dot-notation or bracket-notation path access.
|
|
169
|
+
* @param {string} path - The path of the state property to get. Falsy values, or "*", return the entire state.
|
|
170
|
+
* @param {*} [defaultValue=null] - Default value to return if the path doesn't exist.
|
|
171
|
+
* @param {string|null} [type=null] - The output type to convert the value to ("string", "number", "boolean").
|
|
172
|
+
* @returns {*} The value of the state property, or defaultValue if not found.
|
|
173
|
+
*/
|
|
174
|
+
get(path, defaultValue = null, type = null) {
|
|
175
|
+
if (!path || ['*', ''].includes(path)) {
|
|
176
|
+
return this.data ?? defaultValue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const gotten = getAtPath(this.data, path) ?? defaultValue;
|
|
180
|
+
|
|
181
|
+
return convertType(gotten, type) ?? defaultValue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Sets the value of a state property using dot-notation or bracket-notation path access.
|
|
186
|
+
* Creates intermediate objects if they don't exist.
|
|
187
|
+
* @param {string} path - The path of the state property to set.
|
|
188
|
+
* @param {*} value - The value to set for the state property.
|
|
189
|
+
* @throws {Error} Throws if path is empty or invalid.
|
|
190
|
+
*/
|
|
191
|
+
set(path, value) {
|
|
192
|
+
if (!path) {
|
|
193
|
+
throw new Error(errors.INVALID_STATE_PATH);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
setAtPath(this.data, path, value);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Resolves a nested property path within this instance's data. Low-level counterpart to
|
|
201
|
+
* `get()` - unlike `get()`, a falsy/`'*'` path is not special-cased to mean "entire state".
|
|
202
|
+
* @param {string} path - The path to the property.
|
|
203
|
+
* @returns {*} The value at the specified path, or undefined if not found.
|
|
204
|
+
*/
|
|
205
|
+
getStateFromPropertyPath(path) {
|
|
206
|
+
return getAtPath(this.data, path);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Parses a property path string into an array of keys, supporting both dot notation
|
|
211
|
+
* and bracket notation.
|
|
212
|
+
* @param {string} path - The path to parse.
|
|
213
|
+
* @returns {string[]} Array of property keys.
|
|
214
|
+
*/
|
|
215
|
+
parseStatePath(path) {
|
|
216
|
+
return parsePath(path);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Sets a nested property value within this instance's data based on a path. Low-level
|
|
221
|
+
* counterpart to `set()` - unlike `set()`, does not throw on an empty path.
|
|
222
|
+
* @param {string} path - The path to the property.
|
|
223
|
+
* @param {*} value - The value to set at the specified path.
|
|
224
|
+
*/
|
|
225
|
+
setStateToPropertyPath(path, value) {
|
|
226
|
+
setAtPath(this.data, path, value);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Deletes a state property using dot-notation or bracket-notation path access.
|
|
231
|
+
* @param {string} path - The path of the state property to delete.
|
|
232
|
+
* @throws {Error} Throws if path is empty or invalid.
|
|
233
|
+
*/
|
|
234
|
+
delete(path) {
|
|
235
|
+
if (!path) {
|
|
236
|
+
throw new Error(errors.INVALID_STATE_PATH);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
deleteAtPath(this.data, path);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Merges an object into the current instance state.
|
|
244
|
+
* @param {Object} newState - The object to merge in.
|
|
245
|
+
* @returns {Object} The updated state data.
|
|
246
|
+
*/
|
|
247
|
+
merge(newState) {
|
|
248
|
+
this.data = { ...this.data, ...newState };
|
|
249
|
+
return this.data;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Iterates over a collection (array or object) located at the specified path,
|
|
254
|
+
* executing a callback function for each item.
|
|
255
|
+
* @param {string} path - The path of the property to iterate over.
|
|
256
|
+
* @param {Function} callback - The function to execute for each item in the collection.
|
|
257
|
+
* @throws {Error} Throws if the value at the path is not an array or object.
|
|
258
|
+
*/
|
|
259
|
+
async each(path, callback) {
|
|
260
|
+
const collection = this.get(path);
|
|
261
|
+
|
|
262
|
+
if (Array.isArray(collection)) {
|
|
263
|
+
for (const [index, item] of collection.entries()) {
|
|
264
|
+
await callback(item, index);
|
|
265
|
+
}
|
|
266
|
+
} else if (
|
|
267
|
+
typeof collection === 'object' &&
|
|
268
|
+
Object.prototype.toString.call(collection) === '[object Object]'
|
|
269
|
+
) {
|
|
270
|
+
for (const key of Object.keys(collection)) {
|
|
271
|
+
await callback(collection[key], key);
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
throw new Error(errors.VALUE_NOT_ITERABLE);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
package/src/classes/state.js
CHANGED
|
@@ -1,321 +1,22 @@
|
|
|
1
|
-
import { errors
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const default_state = {
|
|
16
|
-
messages: {
|
|
17
|
-
errors,
|
|
18
|
-
warnings,
|
|
19
|
-
},
|
|
20
|
-
statuses: {
|
|
21
|
-
workflow: workflow_statuses,
|
|
22
|
-
step: step_statuses
|
|
23
|
-
},
|
|
24
|
-
event_names: {
|
|
25
|
-
workflow: workflow_event_names,
|
|
26
|
-
step: step_event_names,
|
|
27
|
-
state: state_event_names,
|
|
28
|
-
},
|
|
29
|
-
events: {
|
|
30
|
-
workflow: new WorkflowEvent(),
|
|
31
|
-
step: new StepEvent(),
|
|
32
|
-
state: new StateEvent(),
|
|
33
|
-
},
|
|
34
|
-
types: {
|
|
35
|
-
base_types,
|
|
36
|
-
step_types,
|
|
37
|
-
sub_step_types,
|
|
38
|
-
},
|
|
1
|
+
import { errors } from '../enums/errors.js';
|
|
2
|
+
import { messages, statuses, event_names, events, types, conditional_step_comparators } from './instance_state.js';
|
|
3
|
+
|
|
4
|
+
// Built from the same constants `Workflow` exposes as static members (see instance_state.js),
|
|
5
|
+
// so `events.*` stays the same instance regardless of whether a given Workflow/Step has opted
|
|
6
|
+
// into `use_state_singleton`. Only `workflows` is singleton-only - it was never meant to be
|
|
7
|
+
// copied into per-instance state.
|
|
8
|
+
export const default_state = {
|
|
9
|
+
messages,
|
|
10
|
+
statuses,
|
|
11
|
+
event_names,
|
|
12
|
+
events,
|
|
13
|
+
types,
|
|
39
14
|
workflows: {},
|
|
40
|
-
conditional_step_comparators
|
|
15
|
+
conditional_step_comparators,
|
|
41
16
|
};
|
|
42
17
|
|
|
43
18
|
let state = { ...default_state };
|
|
44
19
|
|
|
45
|
-
// Module-level shortcuts for events and event_names
|
|
46
|
-
const events = state.events;
|
|
47
|
-
const event_names = state.event_names;
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Parses a property path string into an array of keys, supporting both dot notation
|
|
51
|
-
* and bracket notation. Shared by both the deprecated singleton `State` and per-instance
|
|
52
|
-
* `InstanceState`.
|
|
53
|
-
*
|
|
54
|
-
* @param {string} path - The path to parse (e.g., "user.profile.name", "users[0].name", "data['key-name']").
|
|
55
|
-
* @returns {string[]} Array of property keys.
|
|
56
|
-
*/
|
|
57
|
-
function parsePath(path) {
|
|
58
|
-
const matches = path.match(/[^.[\]]+|(?<=\[)([^\]]+)(?=\])/g);
|
|
59
|
-
|
|
60
|
-
if (!matches) {
|
|
61
|
-
return [];
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return matches.map(part => part.replace(/^['"]|['"]$/g, ''));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Resolves a nested property path within an arbitrary object.
|
|
69
|
-
* @param {Object} target - The object to read from.
|
|
70
|
-
* @param {string} path - The path to the property.
|
|
71
|
-
* @returns {*} The value at the specified path, or undefined if not found.
|
|
72
|
-
*/
|
|
73
|
-
function getAtPath(target, path) {
|
|
74
|
-
const parts = parsePath(path);
|
|
75
|
-
let current = target;
|
|
76
|
-
|
|
77
|
-
for (const part of parts) {
|
|
78
|
-
if (current && Object.prototype.hasOwnProperty.call(current, part)) {
|
|
79
|
-
current = current[part];
|
|
80
|
-
} else {
|
|
81
|
-
return undefined;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return current;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Sets a nested property value within an arbitrary object based on a path.
|
|
90
|
-
* Creates intermediate objects/arrays as needed.
|
|
91
|
-
* @param {Object} target - The object to write to.
|
|
92
|
-
* @param {string} path - The path to the property.
|
|
93
|
-
* @param {*} value - The value to set at the specified path.
|
|
94
|
-
*/
|
|
95
|
-
function setAtPath(target, path, value) {
|
|
96
|
-
const parts = parsePath(path);
|
|
97
|
-
let current = target;
|
|
98
|
-
|
|
99
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
100
|
-
const part = parts[i];
|
|
101
|
-
const next_part = parts[i + 1];
|
|
102
|
-
|
|
103
|
-
if (!Object.prototype.hasOwnProperty.call(current, part) || typeof current[part] !== 'object') {
|
|
104
|
-
const is_next_part_numeric = /^\d+$/.test(next_part);
|
|
105
|
-
current[part] = is_next_part_numeric ? [] : {};
|
|
106
|
-
}
|
|
107
|
-
current = current[part];
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
current[parts[parts.length - 1]] = value;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Deletes a property from an arbitrary object using a path.
|
|
115
|
-
* @param {Object} target - The object to delete from.
|
|
116
|
-
* @param {string} path - The path of the property to delete.
|
|
117
|
-
*/
|
|
118
|
-
function deleteAtPath(target, path) {
|
|
119
|
-
const parts = parsePath(path);
|
|
120
|
-
let current = target;
|
|
121
|
-
|
|
122
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
123
|
-
const part = parts[i];
|
|
124
|
-
|
|
125
|
-
if (!Object.prototype.hasOwnProperty.call(current, part) || typeof current[part] !== 'object') {
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
current = current[part];
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
delete current[parts[parts.length - 1]];
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Converts a resolved state value to the requested output type.
|
|
137
|
-
* @param {*} value - The value to convert.
|
|
138
|
-
* @param {string|null} type - One of "string", "number", "boolean".
|
|
139
|
-
* @returns {*} The converted value, or the original value if conversion fails or type is unrecognized.
|
|
140
|
-
*/
|
|
141
|
-
function convertType(value, type) {
|
|
142
|
-
if (!type) {
|
|
143
|
-
return value;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
try {
|
|
147
|
-
switch (type) {
|
|
148
|
-
case 'string':
|
|
149
|
-
return String(value);
|
|
150
|
-
case 'number':
|
|
151
|
-
return Number(value);
|
|
152
|
-
case 'boolean':
|
|
153
|
-
return Boolean(value);
|
|
154
|
-
default:
|
|
155
|
-
return value;
|
|
156
|
-
}
|
|
157
|
-
} catch (error) {
|
|
158
|
-
console.error('Error converting state value: ', error);
|
|
159
|
-
return value;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Builds a fresh data object for a `Workflow`/`Step` instance's own state, seeded with the
|
|
165
|
-
* same framework constants (statuses, event enums, event emitters, comparators) the deprecated
|
|
166
|
-
* `State` singleton used to provide - but with its own independent, empty `workflows` registry
|
|
167
|
-
* rather than sharing the process-wide one.
|
|
168
|
-
*
|
|
169
|
-
* The event emitters (and other constant objects) are shared by reference with the singleton's
|
|
170
|
-
* defaults so that `on()`/`off()` listeners registered via `State.get('events.workflow')` keep
|
|
171
|
-
* receiving events regardless of whether a given `Workflow`/`Step` has opted back into
|
|
172
|
-
* `use_state_singleton`.
|
|
173
|
-
*
|
|
174
|
-
* @returns {Object} A fresh instance-state data object.
|
|
175
|
-
*/
|
|
176
|
-
export function createInstanceStateData() {
|
|
177
|
-
return {
|
|
178
|
-
messages: default_state.messages,
|
|
179
|
-
statuses: default_state.statuses,
|
|
180
|
-
event_names: default_state.event_names,
|
|
181
|
-
events: default_state.events,
|
|
182
|
-
types: default_state.types,
|
|
183
|
-
conditional_step_comparators: default_state.conditional_step_comparators,
|
|
184
|
-
workflows: {},
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Per-instance replacement for the deprecated `State` singleton. Each `Workflow` owns one of
|
|
190
|
-
* these (created in its constructor), and shares it with every `Step` added to it, so that
|
|
191
|
-
* `getState()`/`setState()`/`deleteState()` calls made anywhere in that workflow's tree read and
|
|
192
|
-
* write the same, workflow-scoped data instead of a single process-wide object.
|
|
193
|
-
*
|
|
194
|
-
* Supports the same dot-notation/bracket-notation path access as `State`.
|
|
195
|
-
*
|
|
196
|
-
* @class InstanceState
|
|
197
|
-
*/
|
|
198
|
-
export class InstanceState {
|
|
199
|
-
/**
|
|
200
|
-
* Creates a new InstanceState.
|
|
201
|
-
* @param {Object} [initial] - Initial data, defaults to a fresh `createInstanceStateData()` result.
|
|
202
|
-
*/
|
|
203
|
-
constructor(initial = createInstanceStateData()) {
|
|
204
|
-
this.data = initial;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Gets the value of a state property using dot-notation or bracket-notation path access.
|
|
209
|
-
* @param {string} path - The path of the state property to get. Falsy values, or "*", return the entire state.
|
|
210
|
-
* @param {*} [defaultValue=null] - Default value to return if the path doesn't exist.
|
|
211
|
-
* @param {string|null} [type=null] - The output type to convert the value to ("string", "number", "boolean").
|
|
212
|
-
* @returns {*} The value of the state property, or defaultValue if not found.
|
|
213
|
-
*/
|
|
214
|
-
get(path, defaultValue = null, type = null) {
|
|
215
|
-
if (!path || ['*', ''].includes(path)) {
|
|
216
|
-
return this.data ?? defaultValue;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const gotten = getAtPath(this.data, path) ?? defaultValue;
|
|
220
|
-
|
|
221
|
-
return convertType(gotten, type) ?? defaultValue;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Sets the value of a state property using dot-notation or bracket-notation path access.
|
|
226
|
-
* Creates intermediate objects if they don't exist.
|
|
227
|
-
* @param {string} path - The path of the state property to set.
|
|
228
|
-
* @param {*} value - The value to set for the state property.
|
|
229
|
-
* @throws {Error} Throws if path is empty or invalid.
|
|
230
|
-
*/
|
|
231
|
-
set(path, value) {
|
|
232
|
-
if (!path) {
|
|
233
|
-
throw new Error(errors.INVALID_STATE_PATH);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
setAtPath(this.data, path, value);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Resolves a nested property path within this instance's data. Low-level counterpart to
|
|
241
|
-
* `get()` - unlike `get()`, a falsy/`'*'` path is not special-cased to mean "entire state".
|
|
242
|
-
* @param {string} path - The path to the property.
|
|
243
|
-
* @returns {*} The value at the specified path, or undefined if not found.
|
|
244
|
-
*/
|
|
245
|
-
getStateFromPropertyPath(path) {
|
|
246
|
-
return getAtPath(this.data, path);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Parses a property path string into an array of keys, supporting both dot notation
|
|
251
|
-
* and bracket notation.
|
|
252
|
-
* @param {string} path - The path to parse.
|
|
253
|
-
* @returns {string[]} Array of property keys.
|
|
254
|
-
*/
|
|
255
|
-
parseStatePath(path) {
|
|
256
|
-
return parsePath(path);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Sets a nested property value within this instance's data based on a path. Low-level
|
|
261
|
-
* counterpart to `set()` - unlike `set()`, does not throw on an empty path.
|
|
262
|
-
* @param {string} path - The path to the property.
|
|
263
|
-
* @param {*} value - The value to set at the specified path.
|
|
264
|
-
*/
|
|
265
|
-
setStateToPropertyPath(path, value) {
|
|
266
|
-
setAtPath(this.data, path, value);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Deletes a state property using dot-notation or bracket-notation path access.
|
|
271
|
-
* @param {string} path - The path of the state property to delete.
|
|
272
|
-
* @throws {Error} Throws if path is empty or invalid.
|
|
273
|
-
*/
|
|
274
|
-
delete(path) {
|
|
275
|
-
if (!path) {
|
|
276
|
-
throw new Error(errors.INVALID_STATE_PATH);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
deleteAtPath(this.data, path);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Merges an object into the current instance state.
|
|
284
|
-
* @param {Object} newState - The object to merge in.
|
|
285
|
-
* @returns {Object} The updated state data.
|
|
286
|
-
*/
|
|
287
|
-
merge(newState) {
|
|
288
|
-
this.data = { ...this.data, ...newState };
|
|
289
|
-
return this.data;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Iterates over a collection (array or object) located at the specified path,
|
|
294
|
-
* executing a callback function for each item.
|
|
295
|
-
* @param {string} path - The path of the property to iterate over.
|
|
296
|
-
* @param {Function} callback - The function to execute for each item in the collection.
|
|
297
|
-
* @throws {Error} Throws if the value at the path is not an array or object.
|
|
298
|
-
*/
|
|
299
|
-
async each(path, callback) {
|
|
300
|
-
const collection = this.get(path);
|
|
301
|
-
|
|
302
|
-
if (Array.isArray(collection)) {
|
|
303
|
-
for (const [index, item] of collection.entries()) {
|
|
304
|
-
await callback(item, index);
|
|
305
|
-
}
|
|
306
|
-
} else if (
|
|
307
|
-
typeof collection === 'object' &&
|
|
308
|
-
Object.prototype.toString.call(collection) === '[object Object]'
|
|
309
|
-
) {
|
|
310
|
-
for (const key of Object.keys(collection)) {
|
|
311
|
-
await callback(collection[key], key);
|
|
312
|
-
}
|
|
313
|
-
} else {
|
|
314
|
-
throw new Error(errors.VALUE_NOT_ITERABLE);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
20
|
/**
|
|
320
21
|
* Singleton class representing the global state for workflows, steps, and processes.
|
|
321
22
|
* Provides methods for managing state with getter/setter functionality, nested path access,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Step from './step.js';
|
|
2
2
|
import LogicStep from './logic_step.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
|
* ConditionalStep class for branching logic based on conditions.
|
|
@@ -82,7 +83,7 @@ export default class ConditionalStep extends LogicStep {
|
|
|
82
83
|
|
|
83
84
|
if (this.checkCondition()) {
|
|
84
85
|
this.log(
|
|
85
|
-
|
|
86
|
+
event_names.step.CONDITIONAL_TRUE_BRANCH_EXECUTED,
|
|
86
87
|
`Condition met for step: ${this.name}, executing true branch`
|
|
87
88
|
);
|
|
88
89
|
|
|
@@ -96,7 +97,7 @@ export default class ConditionalStep extends LogicStep {
|
|
|
96
97
|
}
|
|
97
98
|
} else {
|
|
98
99
|
this.log(
|
|
99
|
-
|
|
100
|
+
event_names.step.CONDITIONAL_FALSE_BRANCH_EXECUTED,
|
|
100
101
|
`Condition not met for step: ${this.name}, executing false branch`
|
|
101
102
|
);
|
|
102
103
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Step from './step.js';
|
|
2
2
|
import { delay_types, step_types } from '../../enums/index.js';
|
|
3
|
+
import { event_names } from '../instance_state.js';
|
|
3
4
|
import schedule from 'node-schedule';
|
|
4
5
|
import { addMilliseconds } from 'date-fns';
|
|
5
6
|
|
|
@@ -47,7 +48,7 @@ export default class DelayStep extends Step {
|
|
|
47
48
|
|
|
48
49
|
if (this.absolute_timestamp.getTime() <= now.getTime()) {
|
|
49
50
|
this.log(
|
|
50
|
-
|
|
51
|
+
event_names.step.DELAY_STEP_ABSOLUTE_COMPLETE,
|
|
51
52
|
`No delay for step: ${this.name}. Continuing.`
|
|
52
53
|
);
|
|
53
54
|
return { delayed: false, delay_type: this.delay_type, timestamp: now.toISOString() };
|
|
@@ -63,17 +64,13 @@ export default class DelayStep extends Step {
|
|
|
63
64
|
async delay(delay_until) {
|
|
64
65
|
return new Promise((resolve) => {
|
|
65
66
|
this.log(
|
|
66
|
-
this.
|
|
67
|
-
`events.step.event_names.DELAY_STEP_${this.delay_type.toUpperCase()}_SCHEDULED`
|
|
68
|
-
),
|
|
67
|
+
event_names.step[`DELAY_STEP_${this.delay_type.toUpperCase()}_SCHEDULED`],
|
|
69
68
|
`Delay scheduled for step: ${this.name} until ${delay_until.toISOString()}`
|
|
70
69
|
);
|
|
71
70
|
|
|
72
71
|
const job = schedule.scheduleJob(delay_until, () => {
|
|
73
72
|
this.log(
|
|
74
|
-
this.
|
|
75
|
-
`events.step.event_names.DELAY_STEP_${this.delay_type.toUpperCase()}_COMPLETE`
|
|
76
|
-
),
|
|
73
|
+
event_names.step[`DELAY_STEP_${this.delay_type.toUpperCase()}_COMPLETE`],
|
|
77
74
|
`Delay complete for step: ${this.name}. Continuing.`
|
|
78
75
|
);
|
|
79
76
|
resolve({ delayed: true, delay_type: this.delay_type, timestamp: new Date().toISOString() });
|
|
@@ -90,7 +87,7 @@ export default class DelayStep extends Step {
|
|
|
90
87
|
async relative() {
|
|
91
88
|
if (this.relative_delay_ms <= 0) {
|
|
92
89
|
this.log(
|
|
93
|
-
|
|
90
|
+
event_names.step.DELAY_STEP_RELATIVE_COMPLETE,
|
|
94
91
|
`No delay for step: ${this.name}. Continuing.`
|
|
95
92
|
);
|
|
96
93
|
return { delayed: false, delay_type: this.delay_type, timestamp: new Date().toISOString() };
|