anbaric-state-machine 1.10.0 → 1.12.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/package.json +3 -3
- package/src/StateMachine.ts +44 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anbaric-state-machine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"description": "This is the state machine library that one can use to add state management to an application. It will, by default, be locally runnable but can deployed to the Anbaric Cloud via the CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "chris@anbaric.ai",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"typescript": "^7.0.2"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"anbaric-impl-cloud": "^1.
|
|
19
|
-
"anbaric-tsapi": "^1.
|
|
18
|
+
"anbaric-impl-cloud": "^1.12.0",
|
|
19
|
+
"anbaric-tsapi": "^1.12.0"
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"src"
|
package/src/StateMachine.ts
CHANGED
|
@@ -15,16 +15,18 @@ import {Code} from "./actors/Code";
|
|
|
15
15
|
|
|
16
16
|
class StateMachine {
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
readonly workflowId: string;
|
|
19
|
+
readonly states: Map<string, State>;
|
|
20
|
+
readonly startState: string;
|
|
21
|
+
readonly dataSchema: Map<string, PropertyDefinition>;
|
|
22
|
+
|
|
22
23
|
private persistence: JobPersistence;
|
|
23
24
|
private queue: Queue;
|
|
24
25
|
private consumer: Consumer;
|
|
25
26
|
private machineActor: Code;
|
|
27
|
+
private sameStateDelayMs: number;
|
|
26
28
|
|
|
27
|
-
constructor(workflowId : string, states : Array<State>, startState : string, dataSchema : Array<PropertyDefinition>, persistence : JobPersistence = JobPersistenceFactory.instance(), queue : Queue = QueueFactory.instance()) {
|
|
29
|
+
constructor(workflowId : string, states : Array<State>, startState : string, dataSchema : Array<PropertyDefinition>, persistence : JobPersistence = JobPersistenceFactory.instance(), queue : Queue = QueueFactory.instance(), sameStateDelayMs : number = 5 * 60_000) {
|
|
28
30
|
|
|
29
31
|
this.workflowId = workflowId;
|
|
30
32
|
this.states = new Map(states.map(state => [state.id, state]));
|
|
@@ -33,6 +35,7 @@ class StateMachine {
|
|
|
33
35
|
this.persistence = persistence;
|
|
34
36
|
this.queue = queue;
|
|
35
37
|
this.machineActor = new Code(workflowId, "state-machine");
|
|
38
|
+
this.sameStateDelayMs = sameStateDelayMs;
|
|
36
39
|
|
|
37
40
|
this.consumer = ConsumerFactory.instance(queue)
|
|
38
41
|
this.consumer.subscribe(workflowId, jobId => this.progressJob(jobId));
|
|
@@ -87,8 +90,7 @@ class StateMachine {
|
|
|
87
90
|
|
|
88
91
|
private validateProperties(properties : Map<string, any>, isNew : boolean) : boolean {
|
|
89
92
|
for (const [key, value] of properties) {
|
|
90
|
-
|
|
91
|
-
if (! definition || ! definition.validation(value)) return false;
|
|
93
|
+
if (this.propertyProblem(key, value)) return false;
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
if (! isNew) return true;
|
|
@@ -100,12 +102,21 @@ class StateMachine {
|
|
|
100
102
|
return true;
|
|
101
103
|
}
|
|
102
104
|
|
|
105
|
+
private propertyProblem(key : string, value : any) : string | undefined {
|
|
106
|
+
const definition = this.dataSchema.get(key);
|
|
107
|
+
if (! definition) return "is not declared in the data schema";
|
|
108
|
+
if (! definition.validation(value)) return "failed its validation";
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
103
112
|
private async progressJob(jobId : string) : Promise<void> {
|
|
104
113
|
let pristine = true;
|
|
105
114
|
|
|
106
115
|
const job = await this.persistence.retrieve(jobId, this.machineActor);
|
|
107
116
|
const currentState = this.states.get(job.state);
|
|
108
117
|
|
|
118
|
+
if (currentState?.isTerminal) return;
|
|
119
|
+
|
|
109
120
|
const involvedActors : Actor[] = [];
|
|
110
121
|
|
|
111
122
|
for (const action of currentState?.actions ?? []) {
|
|
@@ -114,14 +125,20 @@ class StateMachine {
|
|
|
114
125
|
|
|
115
126
|
const newProperties = await action.run(job);
|
|
116
127
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
128
|
+
let applied = false;
|
|
129
|
+
for (const [key, value] of newProperties) {
|
|
130
|
+
const problem = this.propertyProblem(key, value);
|
|
131
|
+
if (problem) {
|
|
132
|
+
console.warn(`[${this.workflowId}] action "${action.name}" set "${key}", which ${problem}, on job ${job.id} — skipping that property.`);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (job.properties.has(key) && this.sameValue(job.properties.get(key), value)) continue;
|
|
123
136
|
job.properties.set(key, value);
|
|
124
|
-
|
|
137
|
+
pristine = false;
|
|
138
|
+
applied = true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (applied) involvedActors.push(action.actor);
|
|
125
142
|
}
|
|
126
143
|
|
|
127
144
|
let newState : string | undefined = undefined;
|
|
@@ -134,13 +151,23 @@ class StateMachine {
|
|
|
134
151
|
}
|
|
135
152
|
}
|
|
136
153
|
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
|
|
154
|
+
if (pristine) return;
|
|
155
|
+
|
|
156
|
+
// TODO: persistence.save should have the ability to accept multiple actors for this case.
|
|
157
|
+
await this.persistence.save(involvedActors[0] ?? this.machineActor, `Job ${job.id} progressed automatically`, job, job.properties, newState);
|
|
158
|
+
|
|
159
|
+
if (this.states.get(newState ?? job.state)?.isTerminal) return;
|
|
160
|
+
if (newState !== undefined) {
|
|
140
161
|
await this.queue.enqueue(job.id, this.workflowId);
|
|
162
|
+
} else {
|
|
163
|
+
await this.queue.schedule(job.id, this.workflowId, new Date(Date.now() + this.sameStateDelayMs));
|
|
141
164
|
}
|
|
142
165
|
}
|
|
143
166
|
|
|
167
|
+
private sameValue(a : any, b : any) : boolean {
|
|
168
|
+
return a === b || JSON.stringify(a) === JSON.stringify(b);
|
|
169
|
+
}
|
|
170
|
+
|
|
144
171
|
async cleanUp() : Promise<void> {
|
|
145
172
|
await this.consumer.cleanUp();
|
|
146
173
|
}
|