anbaric-state-machine 1.7.0 → 2.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/package.json +3 -3
- package/src/StateMachine.ts +44 -55
- package/src/actions/RemoteLLMAgenticAction.ts +27 -0
- package/src/agents/OpenAIAgent.ts +83 -0
- package/src/auditing/ConsoleAuditor.ts +4 -4
- package/src/index.ts +2 -2
- package/src/persistence/InMemoryJobPersistence.ts +10 -14
- package/src/persistence/JobPersistenceFactory.ts +4 -2
- package/src/actors/Agent.ts +0 -16
- package/src/auditing/AuditorTransaction.ts +0 -31
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anbaric-state-machine",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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": "^
|
|
19
|
-
"anbaric-tsapi": "^
|
|
18
|
+
"anbaric-impl-cloud": "^2.0.0",
|
|
19
|
+
"anbaric-tsapi": "^2.0.0"
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"src"
|
package/src/StateMachine.ts
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Action,
|
|
3
|
+
Actor,
|
|
4
|
+
AuditInteraction,
|
|
5
|
+
Consumer,
|
|
6
|
+
Job,
|
|
7
|
+
JobPersistence,
|
|
8
|
+
PropertyDefinition,
|
|
9
|
+
Queue,
|
|
10
|
+
State
|
|
11
|
+
} from "anbaric-tsapi";
|
|
2
12
|
import {JobPersistenceFactory} from "./persistence/JobPersistenceFactory";
|
|
3
13
|
import {QueueFactory} from "./scheduling/QueueFactory";
|
|
4
14
|
import {ConsumerFactory} from "./scheduling/ConsumerFactory";
|
|
5
15
|
import {Code} from "./actors/Code";
|
|
6
|
-
import {AuditorFactory} from "./auditing/AuditorFactory";
|
|
7
|
-
import {AuditorTransaction} from "./auditing/AuditorTransaction";
|
|
8
16
|
|
|
9
17
|
class StateMachine {
|
|
10
18
|
|
|
@@ -15,10 +23,9 @@ class StateMachine {
|
|
|
15
23
|
private persistence: JobPersistence;
|
|
16
24
|
private queue: Queue;
|
|
17
25
|
private consumer: Consumer;
|
|
18
|
-
private auditor: Auditor;
|
|
19
26
|
private machineActor: Code;
|
|
20
27
|
|
|
21
|
-
constructor(workflowId : string, states : Array<State>, startState : string, dataSchema : Array<PropertyDefinition>, persistence : JobPersistence = JobPersistenceFactory.instance(), queue : Queue = QueueFactory.instance()
|
|
28
|
+
constructor(workflowId : string, states : Array<State>, startState : string, dataSchema : Array<PropertyDefinition>, persistence : JobPersistence = JobPersistenceFactory.instance(), queue : Queue = QueueFactory.instance()) {
|
|
22
29
|
|
|
23
30
|
this.workflowId = workflowId;
|
|
24
31
|
this.states = new Map(states.map(state => [state.id, state]));
|
|
@@ -26,29 +33,21 @@ class StateMachine {
|
|
|
26
33
|
this.dataSchema = new Map(dataSchema.map(property => [property.id, property]));
|
|
27
34
|
this.persistence = persistence;
|
|
28
35
|
this.queue = queue;
|
|
29
|
-
this.auditor = auditor;
|
|
30
36
|
this.machineActor = new Code(workflowId, "state-machine");
|
|
31
37
|
|
|
32
38
|
this.consumer = ConsumerFactory.instance(queue)
|
|
33
39
|
this.consumer.subscribe(workflowId, jobId => this.progressJob(jobId));
|
|
34
40
|
}
|
|
35
41
|
|
|
36
|
-
async startJob(properties?: Map<string, any>, actor
|
|
42
|
+
async startJob(properties?: Map<string, any>, actor : Actor = this.machineActor): Promise<Job> {
|
|
37
43
|
|
|
38
44
|
const job = new Job(crypto.randomUUID(), properties, this.startState, this.workflowId,
|
|
39
45
|
actor?.id ?? this.workflowId);
|
|
40
46
|
|
|
41
47
|
if (! this.validateProperties(properties ?? new Map(), true)) throw new Error("Invalid properties");
|
|
48
|
+
if (! this.authorizeActor(actor, job)) throw new Error("Unauthorized");
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
await this.auditor.audit(job.id, actor ?? this.machineActor, "CREATE", "Unauthorized start", null);
|
|
45
|
-
throw new Error("Unauthorized");
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
await this.persistence.save(job);
|
|
49
|
-
await this.auditor.audit(job.id, actor ?? this.machineActor, "CREATE", "Job started",
|
|
50
|
-
Object.fromEntries(job.properties));
|
|
51
|
-
|
|
50
|
+
await this.persistence.create(actor, job);
|
|
52
51
|
await this.queue.enqueue(job.id, this.workflowId);
|
|
53
52
|
|
|
54
53
|
return job;
|
|
@@ -56,40 +55,35 @@ class StateMachine {
|
|
|
56
55
|
|
|
57
56
|
async updateJob(jobId : string, properties : Map<string, any>, actor : Actor) : Promise<void> {
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
await this.auditor.audit(jobId, actor, "UPDATE_PROPERTIES", "Unauthorized update", null);
|
|
61
|
-
throw new Error("Unauthorized");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (! this.validateProperties(properties, false)) throw new Error("Invalid properties");
|
|
58
|
+
const job = await this.persistence.retrieve(jobId, actor);
|
|
65
59
|
|
|
66
|
-
|
|
67
|
-
await this.auditor.audit(jobId, actor, "UPDATE_PROPERTIES", "Properties updated", Object.fromEntries(properties));
|
|
60
|
+
if (!this.authorizeActor(actor, job)) throw new Error("Unauthorized");
|
|
68
61
|
|
|
69
|
-
await this.
|
|
62
|
+
await this.updateJobInternal(actor, "Properties Updated", job, properties);
|
|
70
63
|
}
|
|
71
64
|
|
|
72
65
|
async executeAction(jobId : string, action : Action) : Promise<void> {
|
|
73
|
-
const job = await this.persistence.retrieve(jobId);
|
|
74
66
|
|
|
75
|
-
|
|
67
|
+
const job = await this.persistence.retrieve(jobId, action.actor);
|
|
76
68
|
|
|
77
|
-
if (!
|
|
78
|
-
|
|
79
|
-
throw new Error("Actor not authorized to execute action");
|
|
80
|
-
}
|
|
69
|
+
if (! action.predicate(job)) throw new Error("Action predicate unmet");
|
|
70
|
+
if (! this.authorizeActor(action.actor, job)) throw new Error("Actor not authorized to execute action");
|
|
81
71
|
|
|
82
72
|
const newProperties = await action.run(job);
|
|
83
73
|
|
|
84
|
-
|
|
85
|
-
await this.
|
|
86
|
-
|
|
74
|
+
try {
|
|
75
|
+
await this.updateJobInternal(action.actor, `Action ${action.name} executed on job ${job.id}: ${action.description}`, job, newProperties);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
throw new Error('The action generated invalid properties');
|
|
87
78
|
}
|
|
79
|
+
}
|
|
88
80
|
|
|
89
|
-
|
|
90
|
-
await this.auditor.audit(jobId, action.actor, "UPDATE_PROPERTIES", "Properties updated", Object.fromEntries(newProperties));
|
|
81
|
+
private async updateJobInternal(actor: Actor, message : string, job: Job, newProperties?: Map<string, any>, newState? : string) {
|
|
91
82
|
|
|
92
|
-
|
|
83
|
+
if (newProperties && !this.validateProperties(newProperties, false)) throw new Error("Invalid properties");
|
|
84
|
+
|
|
85
|
+
await this.persistence.save(actor, "Properties Updated", job, newProperties, newState)
|
|
86
|
+
await this.queue.enqueue(job.id, this.workflowId);
|
|
93
87
|
}
|
|
94
88
|
|
|
95
89
|
private validateProperties(properties : Map<string, any>, isNew : boolean) : boolean {
|
|
@@ -108,49 +102,44 @@ class StateMachine {
|
|
|
108
102
|
}
|
|
109
103
|
|
|
110
104
|
private async progressJob(jobId : string) : Promise<void> {
|
|
111
|
-
const auditTransaction = new AuditorTransaction(this.auditor);
|
|
112
105
|
let pristine = true;
|
|
113
106
|
|
|
114
|
-
const job = await this.persistence.retrieve(jobId);
|
|
115
|
-
const currentState = this.states.get(job.
|
|
107
|
+
const job = await this.persistence.retrieve(jobId, this.machineActor);
|
|
108
|
+
const currentState = this.states.get(job.state);
|
|
109
|
+
|
|
110
|
+
const involvedActors : Actor[] = [];
|
|
116
111
|
|
|
117
112
|
for (const action of currentState?.actions ?? []) {
|
|
118
113
|
if (! action.predicate(job)) continue;
|
|
119
|
-
|
|
120
|
-
if (! this.authorizeActor(action.actor, job)) {
|
|
121
|
-
auditTransaction.audit(jobId, action.actor, "UPDATE_PROPERTIES", "Unauthorized update", null);
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
114
|
+
if (! this.authorizeActor(action.actor, job)) continue;
|
|
124
115
|
|
|
125
116
|
const newProperties = await action.run(job);
|
|
126
117
|
|
|
127
|
-
if (! this.validateProperties(newProperties, false))
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
118
|
+
if (! this.validateProperties(newProperties, false)) continue;
|
|
119
|
+
|
|
120
|
+
involvedActors.push(action.actor);
|
|
131
121
|
|
|
132
|
-
auditTransaction.audit(jobId, action.actor, "UPDATE_PROPERTIES", "Properties updated", Object.fromEntries(newProperties));
|
|
133
122
|
newProperties.forEach((value, key) => {
|
|
134
123
|
pristine = false;
|
|
135
124
|
job.properties.set(key, value);
|
|
136
125
|
});
|
|
137
126
|
}
|
|
138
127
|
|
|
128
|
+
let newState : string | undefined = undefined;
|
|
139
129
|
for (const transition of currentState?.transitions ?? []) {
|
|
140
130
|
if (! this.states.has(transition.to)) continue;
|
|
141
|
-
if (
|
|
142
|
-
|
|
131
|
+
if (transition.predicate(job)) {
|
|
132
|
+
newState = transition.to;
|
|
143
133
|
pristine = false;
|
|
144
134
|
break;
|
|
145
135
|
}
|
|
146
136
|
}
|
|
147
137
|
|
|
148
138
|
if (! pristine) {
|
|
149
|
-
|
|
139
|
+
// TODO: persistence.save should have the ability to accept multiple actors for this case.
|
|
140
|
+
await this.persistence.save(involvedActors[0] ?? this.machineActor, `Job ${job.id} progressed automatically`, job, job.properties, newState);
|
|
150
141
|
await this.queue.enqueue(job.id, this.workflowId);
|
|
151
142
|
}
|
|
152
|
-
|
|
153
|
-
await auditTransaction.flush();
|
|
154
143
|
}
|
|
155
144
|
|
|
156
145
|
async cleanUp() : Promise<void> {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {Agent, AgenticAction, AgentMessage, AgentRequest, Job} from "anbaric-tsapi";
|
|
2
|
+
|
|
3
|
+
/* An OpenAI-API-compatible agentic action: it carries a prompt as an array of
|
|
4
|
+
chat messages and a JSON Schema, appends the job's properties as the final
|
|
5
|
+
user message, and lets its agent's client generate the properties to write. */
|
|
6
|
+
class RemoteLLMAgenticAction extends AgenticAction {
|
|
7
|
+
|
|
8
|
+
private messages : Array<AgentMessage>;
|
|
9
|
+
private outputSchema : object;
|
|
10
|
+
|
|
11
|
+
constructor(name : string, agent : Agent, messages : Array<AgentMessage>, outputSchema : object,
|
|
12
|
+
description : string = "", id? : string) {
|
|
13
|
+
super(name, agent, description, id);
|
|
14
|
+
this.messages = messages;
|
|
15
|
+
this.outputSchema = outputSchema;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
protected requestFor(job : Job) : AgentRequest {
|
|
19
|
+
return {
|
|
20
|
+
messages: [...this.messages, { role: "user", content: JSON.stringify(Object.fromEntries(job.properties)) }],
|
|
21
|
+
outputSchema: this.outputSchema,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { RemoteLLMAgenticAction }
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {Agent, AgentRequest} from "anbaric-tsapi";
|
|
2
|
+
|
|
3
|
+
type FetchFn = (url : string, init : RequestInit) => Promise<Response>;
|
|
4
|
+
|
|
5
|
+
type OpenAIConnection = {
|
|
6
|
+
apiKey : string,
|
|
7
|
+
model : string,
|
|
8
|
+
baseUrl? : string,
|
|
9
|
+
organization? : string,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const strictSchema = (schema : any) : any => {
|
|
13
|
+
if (schema?.type === "object" && schema.properties) {
|
|
14
|
+
return {
|
|
15
|
+
...schema,
|
|
16
|
+
required: Object.keys(schema.properties),
|
|
17
|
+
additionalProperties: false,
|
|
18
|
+
properties: Object.fromEntries(
|
|
19
|
+
Object.entries(schema.properties).map(([key, property]) => [key, strictSchema(property)])),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (schema?.type === "array" && schema.items) {
|
|
23
|
+
return { ...schema, items: strictSchema(schema.items) };
|
|
24
|
+
}
|
|
25
|
+
return schema;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/* The functional half of an OpenAI agent: asks an OpenAI-compatible chat
|
|
29
|
+
completions endpoint for JSON conforming to the request's schema via
|
|
30
|
+
structured outputs. */
|
|
31
|
+
class OpenAIClient extends Agent.Client {
|
|
32
|
+
|
|
33
|
+
private baseUrl : string;
|
|
34
|
+
|
|
35
|
+
constructor(private connection : OpenAIConnection,
|
|
36
|
+
private fetchFn : FetchFn = (url, init) => fetch(url, init)) {
|
|
37
|
+
super();
|
|
38
|
+
this.baseUrl = connection.baseUrl ?? "https://api.openai.com/v1";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async generate(request : AgentRequest) : Promise<Record<string, any>> {
|
|
42
|
+
const response = await this.fetchFn(`${this.baseUrl}/chat/completions`, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers: {
|
|
45
|
+
"content-type": "application/json",
|
|
46
|
+
"authorization": `Bearer ${this.connection.apiKey}`,
|
|
47
|
+
...(this.connection.organization ? { "openai-organization": this.connection.organization } : {}),
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify({
|
|
50
|
+
model: this.connection.model,
|
|
51
|
+
messages: request.messages,
|
|
52
|
+
response_format: {
|
|
53
|
+
type: "json_schema",
|
|
54
|
+
json_schema: { name: "properties", strict: true, schema: strictSchema(request.outputSchema) },
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
const problem = await response.json().catch(() => ({}));
|
|
61
|
+
throw new Error(`The OpenAI request failed with status ${response.status}${problem.error?.message ? `: ${problem.error.message}` : ""}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const body = await response.json();
|
|
65
|
+
const content = body.choices?.[0]?.message?.content;
|
|
66
|
+
if (typeof content !== "string") throw new Error("The OpenAI response carried no structured content");
|
|
67
|
+
return JSON.parse(content);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* A light agent backed by an OpenAI-compatible chat completions endpoint. */
|
|
73
|
+
class OpenAIAgent extends Agent {
|
|
74
|
+
|
|
75
|
+
constructor(id : string, role : string, connection : OpenAIConnection,
|
|
76
|
+
fetchFn : FetchFn = (url, init) => fetch(url, init)) {
|
|
77
|
+
super(id, role, new OpenAIClient(connection, fetchFn));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export { OpenAIAgent, OpenAIClient }
|
|
83
|
+
export type { OpenAIConnection }
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {Actor,
|
|
1
|
+
import {Actor, AuditInteraction, Auditor} from "anbaric-tsapi";
|
|
2
2
|
|
|
3
3
|
class ConsoleAuditor implements Auditor {
|
|
4
4
|
|
|
5
|
-
async audit(
|
|
6
|
-
|
|
7
|
-
console.log(`[${
|
|
5
|
+
async audit(resourceType : string, resourceId : string, actor : Actor, interaction : AuditInteraction[],
|
|
6
|
+
description : string, details : any) : Promise<void> {
|
|
7
|
+
console.log(`[${resourceType} ${resourceId}] ${actor.id} ${interaction.join(",")} ${description} ${JSON.stringify(details ?? null)}`);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export * from "./StateMachine";
|
|
2
|
-
export * from "./
|
|
2
|
+
export * from "./actions/RemoteLLMAgenticAction";
|
|
3
|
+
export * from "./agents/OpenAIAgent";
|
|
3
4
|
export * from "./actors/Code";
|
|
4
5
|
export * from "./actors/Human";
|
|
5
6
|
export * from "./auditing/AuditorFactory";
|
|
6
|
-
export * from "./auditing/AuditorTransaction";
|
|
7
7
|
export * from "./auditing/ConsoleAuditor";
|
|
8
8
|
export * from "./persistence/InMemoryJobPersistence";
|
|
9
9
|
export * from "./persistence/JobPersistenceFactory";
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import {Job, JobPersistence} from "anbaric-tsapi";
|
|
1
|
+
import {Auditor, Job, JobPersistence, NoOpAuditor} from "anbaric-tsapi";
|
|
2
2
|
|
|
3
|
-
class InMemoryJobPersistence
|
|
3
|
+
class InMemoryJobPersistence extends JobPersistence {
|
|
4
4
|
|
|
5
5
|
private jobs = new Map<string, Job>();
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
constructor(auditor : Auditor = new NoOpAuditor()) {
|
|
8
|
+
super(auditor);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
protected async saveInternal(job : Job) : Promise<void> {
|
|
8
12
|
this.jobs.set(job.id, job);
|
|
9
13
|
}
|
|
10
14
|
|
|
11
|
-
async
|
|
15
|
+
protected async retrieveInternal(id : string) : Promise<Job> {
|
|
12
16
|
const job = this.jobs.get(id);
|
|
13
17
|
if (!job) {
|
|
14
18
|
throw new Error(`No job found with id "${id}"`);
|
|
@@ -16,22 +20,14 @@ class InMemoryJobPersistence implements JobPersistence {
|
|
|
16
20
|
return job;
|
|
17
21
|
}
|
|
18
22
|
|
|
19
|
-
async
|
|
23
|
+
protected async deleteInternal(id : string) : Promise<void> {
|
|
20
24
|
this.jobs.delete(id);
|
|
21
25
|
}
|
|
22
26
|
|
|
23
|
-
async
|
|
27
|
+
protected async listInternal(pageSize : number = 100, page : number = 0) : Promise<Array<Job>> {
|
|
24
28
|
return Array.from(this.jobs.values()).slice(page * pageSize, (page + 1) * pageSize);
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
async updateProperties(id : string, properties : Map<string, any>) : Promise<void> {
|
|
28
|
-
const job = await this.retrieve(id);
|
|
29
|
-
for (const [key, value] of properties) {
|
|
30
|
-
job.properties.set(key, value);
|
|
31
|
-
}
|
|
32
|
-
job.lastUpdated = new Date();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
31
|
}
|
|
36
32
|
|
|
37
33
|
export { InMemoryJobPersistence }
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import {JobPersistence} from "anbaric-tsapi";
|
|
2
2
|
import {CloudJobPersistence} from "anbaric-impl-cloud";
|
|
3
3
|
import {InMemoryJobPersistence} from "./InMemoryJobPersistence";
|
|
4
|
+
import {AuditorFactory} from "../auditing/AuditorFactory";
|
|
4
5
|
|
|
5
6
|
const JobPersistenceFactory = {
|
|
6
7
|
instance() : JobPersistence {
|
|
8
|
+
const auditor = AuditorFactory.instance();
|
|
7
9
|
switch (process.env.ANBARIC_JOB_PERSISTENCE_TYPE) {
|
|
8
10
|
case "cloud":
|
|
9
|
-
return new CloudJobPersistence();
|
|
11
|
+
return new CloudJobPersistence(undefined, auditor);
|
|
10
12
|
case "memory":
|
|
11
13
|
default:
|
|
12
|
-
return new InMemoryJobPersistence();
|
|
14
|
+
return new InMemoryJobPersistence(auditor);
|
|
13
15
|
}
|
|
14
16
|
}
|
|
15
17
|
}
|
package/src/actors/Agent.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import {Actor} from "anbaric-tsapi";
|
|
2
|
-
|
|
3
|
-
class Agent implements Actor {
|
|
4
|
-
|
|
5
|
-
readonly type = "AGENT" as const;
|
|
6
|
-
readonly id : string;
|
|
7
|
-
readonly role : string;
|
|
8
|
-
|
|
9
|
-
constructor(id : string, role : string) {
|
|
10
|
-
this.id = id;
|
|
11
|
-
this.role = role;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export { Agent }
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import {Actor, AuditChange, Auditor} from "anbaric-tsapi";
|
|
2
|
-
|
|
3
|
-
type BufferedEntry = {
|
|
4
|
-
jobId : string,
|
|
5
|
-
actor : Actor,
|
|
6
|
-
change : AuditChange,
|
|
7
|
-
changeDescription : string,
|
|
8
|
-
details : any,
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
class AuditorTransaction {
|
|
12
|
-
|
|
13
|
-
private entries = new Array<BufferedEntry>();
|
|
14
|
-
|
|
15
|
-
constructor(private auditor : Auditor) {}
|
|
16
|
-
|
|
17
|
-
audit(jobId : string, actor : Actor, change : AuditChange, changeDescription : string, details : any) : void {
|
|
18
|
-
this.entries.push({ jobId, actor, change, changeDescription, details });
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async flush() : Promise<void> {
|
|
22
|
-
const flushing = this.entries;
|
|
23
|
-
this.entries = [];
|
|
24
|
-
for (const entry of flushing) {
|
|
25
|
-
await this.auditor.audit(entry.jobId, entry.actor, entry.change, entry.changeDescription, entry.details);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export { AuditorTransaction }
|