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.
Files changed (2) hide show
  1. package/package.json +3 -3
  2. 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.10.0",
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.10.0",
19
- "anbaric-tsapi": "^1.10.0"
18
+ "anbaric-impl-cloud": "^1.12.0",
19
+ "anbaric-tsapi": "^1.12.0"
20
20
  },
21
21
  "files": [
22
22
  "src"
@@ -15,16 +15,18 @@ import {Code} from "./actors/Code";
15
15
 
16
16
  class StateMachine {
17
17
 
18
- private workflowId: string;
19
- private states: Map<string, State>;
20
- private startState: string;
21
- private dataSchema: Map<string, PropertyDefinition>;
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
- const definition = this.dataSchema.get(key);
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
- if (! this.validateProperties(newProperties, false)) continue;
118
-
119
- involvedActors.push(action.actor);
120
-
121
- newProperties.forEach((value, key) => {
122
- pristine = false;
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 (! pristine) {
138
- // TODO: persistence.save should have the ability to accept multiple actors for this case.
139
- await this.persistence.save(involvedActors[0] ?? this.machineActor, `Job ${job.id} progressed automatically`, job, job.properties, newState);
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
  }