anbaric 1.57.0 → 1.58.1
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/docs/api/state-machine.md +61 -46
- package/docs/features/actions-and-actors.md +24 -20
- package/docs/features/ai-agents.md +7 -7
- package/docs/features/awaiting-input.md +2 -2
- package/docs/features/core-concepts.md +6 -5
- package/docs/features/scheduled-runs.md +1 -1
- package/docs/features/state-machines.md +7 -7
- package/docs/patterns/first-app.md +4 -4
- package/docs/patterns/human-in-the-loop.md +9 -9
- package/docs/patterns/integrating-external-systems.md +4 -4
- package/docs/patterns/modelling-workflows.md +8 -8
- package/package.json +5 -5
|
@@ -84,6 +84,7 @@ readonly id : string
|
|
|
84
84
|
actions : Array<Action | Await>
|
|
85
85
|
transitions : Array<Transition>
|
|
86
86
|
readonly isTerminal : boolean
|
|
87
|
+
prewarm : Reads // default: Reads.everything
|
|
87
88
|
|
|
88
89
|
constructor(
|
|
89
90
|
id : string,
|
|
@@ -98,6 +99,29 @@ subscribe(action : Action | Await) : void // append an action after constructi
|
|
|
98
99
|
A state's `actions` may mix `Action`s and `Await`s; they are considered in order
|
|
99
100
|
when a job is processed.
|
|
100
101
|
|
|
102
|
+
### `prewarm` and `Reads`
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
type Reads = (definitions : Array<PropertyDefinition>) => Array<string>
|
|
106
|
+
|
|
107
|
+
Reads.everything // every property (the default)
|
|
108
|
+
Reads.nothing // none
|
|
109
|
+
Reads.only("summary", "articles") // a fixed few
|
|
110
|
+
Reads.where(definition => definition.id.startsWith("article")) // whatever matches
|
|
111
|
+
|
|
112
|
+
scoring.prewarm = Reads.only("summary");
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Before a job is processed in a state, the properties the state `prewarm`s are
|
|
116
|
+
loaded in one fetch. Anything a step reads that wasn't prewarmed still loads,
|
|
117
|
+
on demand, the moment it's asked for — so `prewarm` is never something a step
|
|
118
|
+
depends on for correctness, only what keeps a pass to one round trip. Given the
|
|
119
|
+
machine's definitions, so a mapping written once follows the schema. Narrow it
|
|
120
|
+
for a state that needs a little of a job that holds a lot: a workflow carrying
|
|
121
|
+
hundreds of article summaries stops moving all of them for a step that scores
|
|
122
|
+
one, and an [agentic action](../features/ai-agents.md) in that state sends the
|
|
123
|
+
model only what was loaded.
|
|
124
|
+
|
|
101
125
|
---
|
|
102
126
|
|
|
103
127
|
## `Terminal`
|
|
@@ -136,47 +160,24 @@ actor : Actor
|
|
|
136
160
|
constructor(name : string, actor : Actor, description : string = "", id? : string)
|
|
137
161
|
|
|
138
162
|
// Replaceable function fields — assign your own:
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
run : (job : Job) => Promise<Map<string, any>> // default: async () => new Map()
|
|
163
|
+
predicate : (job : Job) => boolean | Promise<boolean> // default: () => true
|
|
164
|
+
run : (job : Job) => Promise<Map<string, any>> // default: async () => new Map()
|
|
142
165
|
```
|
|
143
166
|
|
|
144
|
-
You configure an action by assigning `predicate` and `run
|
|
145
|
-
the job holds more than the action needs:
|
|
167
|
+
You configure an action by assigning `predicate` and `run`:
|
|
146
168
|
|
|
147
169
|
```ts
|
|
148
170
|
const sendWelcome = new Action("Send welcome email", new Code("welcome"));
|
|
149
|
-
sendWelcome.
|
|
171
|
+
sendWelcome.predicate = async (job) => ! await job.properties.has("welcomeSent");
|
|
150
172
|
sendWelcome.run = async (job) => new Map([["welcomeSent", true]]);
|
|
151
173
|
```
|
|
152
174
|
|
|
153
|
-
- **`
|
|
154
|
-
`
|
|
155
|
-
once and follow the schema. See [`Reads`](#reads).
|
|
156
|
-
- **`predicate`** — return `false` to skip this action for a given job.
|
|
175
|
+
- **`predicate`** — return `false` to skip this action for a given job. May be
|
|
176
|
+
`async`, since [reading a property](#jobproperties) is.
|
|
157
177
|
- **`run`** — return a `Map` of the properties to change. Only properties in the
|
|
158
178
|
machine's schema are applied; others are ignored with a warning. Only the
|
|
159
179
|
properties that actually changed are written back.
|
|
160
180
|
|
|
161
|
-
### `Reads`
|
|
162
|
-
|
|
163
|
-
```ts
|
|
164
|
-
type Reads = (definitions : Array<PropertyDefinition>) => Array<string>
|
|
165
|
-
|
|
166
|
-
Reads.everything // every property (the default)
|
|
167
|
-
Reads.nothing // none
|
|
168
|
-
Reads.only("summary", "articles") // a fixed few
|
|
169
|
-
Reads.where(definition => definition.id.startsWith("article")) // whatever matches
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
A job is loaded with the union of what the current state's actions, awaits and
|
|
173
|
-
transitions read, and nothing else: a property that wasn't declared is simply
|
|
174
|
-
absent from `job.properties`. A step that reads everything loads everything, so
|
|
175
|
-
nothing changes until you declare. Declaring matters for jobs that carry a lot
|
|
176
|
-
— a workflow holding hundreds of article summaries stops moving all of them
|
|
177
|
-
for a step that scores one — and for [agentic actions](../features/ai-agents.md),
|
|
178
|
-
whose prompt carries the loaded properties and nothing more.
|
|
179
|
-
|
|
180
181
|
See [Actions and actors](../features/actions-and-actors.md).
|
|
181
182
|
|
|
182
183
|
---
|
|
@@ -196,13 +197,12 @@ name : string
|
|
|
196
197
|
description : string
|
|
197
198
|
waitingFor? : AwaitParty
|
|
198
199
|
fields : Array<string> = []
|
|
199
|
-
resolveUrl : string | ((job : Job) => string) = ""
|
|
200
|
-
metadata : (job : Job) => Map<string, any> // default: () => new Map()
|
|
201
|
-
reads : Reads // what resolveUrl and metadata read; default: Reads.everything
|
|
200
|
+
resolveUrl : string | ((job : Job) => string | Promise<string>) = ""
|
|
201
|
+
metadata : (job : Job) => Map<string, any> | Promise<Map<string, any>> // default: () => new Map()
|
|
202
202
|
|
|
203
203
|
constructor(name : string, waitingFor? : AwaitParty, description : string = "", id? : string)
|
|
204
204
|
|
|
205
|
-
waitForInput(job : Job) : WaitForInput
|
|
205
|
+
waitForInput(job : Job) : Promise<WaitForInput>
|
|
206
206
|
```
|
|
207
207
|
|
|
208
208
|
- **`fields`** — the input you expect back (property names).
|
|
@@ -244,21 +244,20 @@ transition whose predicate holds moves the job to `to`.
|
|
|
244
244
|
|
|
245
245
|
```ts
|
|
246
246
|
to : string
|
|
247
|
-
predicate : (job : Job) => boolean
|
|
248
|
-
reads : Reads // default: everything if guarded, nothing if not
|
|
247
|
+
predicate : (job : Job) => boolean | Promise<boolean> // default: () => true
|
|
249
248
|
|
|
250
|
-
constructor(to : string, predicate? : (job : Job) => boolean
|
|
249
|
+
constructor(to : string, predicate? : (job : Job) => boolean | Promise<boolean>)
|
|
251
250
|
```
|
|
252
251
|
|
|
253
252
|
```ts
|
|
254
|
-
new Transition("active", (job) => job.properties.get("welcomeSent") === true
|
|
253
|
+
new Transition("active", async (job) => await job.properties.get("welcomeSent") === true)
|
|
255
254
|
new Transition("scoring") // unguarded: the actions run, then the job moves on
|
|
256
255
|
```
|
|
257
256
|
|
|
258
257
|
The predicate is optional. Omit it when a state's actions simply run and the job
|
|
259
258
|
should move on, rather than inventing a sentinel property for the transition to
|
|
260
|
-
read. Guard a transition only when the move is conditional
|
|
261
|
-
|
|
259
|
+
read. Guard a transition only when the move is conditional. A guard may be
|
|
260
|
+
`async`, since reading a property is.
|
|
262
261
|
|
|
263
262
|
---
|
|
264
263
|
|
|
@@ -297,18 +296,34 @@ A job whose action threw is `FAILED`, with the reason in its audit trail. It
|
|
|
297
296
|
stays in its state rather than transitioning, and an update that moves it on
|
|
298
297
|
returns it to `ACTIVE` — so a failure is recoverable, not terminal.
|
|
299
298
|
|
|
299
|
+
### `JobProperties`
|
|
300
|
+
|
|
301
|
+
`job.properties` is not a `Map`: it reads on demand, so a job may hold a great
|
|
302
|
+
deal while a step pays only for what it asks for.
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
await job.properties.get(key) : Promise<any>
|
|
306
|
+
await job.properties.has(key) : Promise<boolean>
|
|
307
|
+
await job.properties.getMany(keys) : Promise<Map<string, any>>
|
|
308
|
+
await job.properties.prewarm(keys?) : Promise<void> // fetch these (or everything) in one go
|
|
309
|
+
await job.properties.toMap() : Promise<Map<string, any>> // everything, loading what isn't held
|
|
310
|
+
job.properties.snapshot() : Map<string, any> // what is held right now, loading nothing
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
The first read of a property not yet held fetches it; the state's
|
|
314
|
+
[`prewarm`](#prewarm-and-reads) fetches a set up front. Reads are cached for
|
|
315
|
+
the pass. `snapshot()` is what serialisation and an agentic prompt use.
|
|
316
|
+
|
|
300
317
|
### Reading a job
|
|
301
318
|
|
|
302
319
|
```ts
|
|
303
|
-
persistence.retrieve(id, actor) : Promise<Job> // every property
|
|
304
|
-
persistence.retrieve(id, actor, ["summary", "score"]) : Promise<Job> //
|
|
320
|
+
persistence.retrieve(id, actor) : Promise<Job> // holding every property
|
|
321
|
+
persistence.retrieve(id, actor, ["summary", "score"]) : Promise<Job> // holding those; the rest on demand
|
|
305
322
|
```
|
|
306
323
|
|
|
307
|
-
Properties are stored one by one,
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
changed. A property absent from a partial read is never touched by a save of
|
|
311
|
-
that job, and no save ever removes a property. Over HTTP the same read is
|
|
324
|
+
Properties are stored one by one, and a save writes only the properties that
|
|
325
|
+
changed — never the ones a pass read, let alone the ones it never loaded — and
|
|
326
|
+
no save ever removes a property. Over HTTP the same read is
|
|
312
327
|
`GET /api/v2/jobs/<id>?keys=summary,score`, and the properties in a `PUT` body
|
|
313
328
|
are the ones written.
|
|
314
329
|
|
|
@@ -13,9 +13,9 @@ import {Action, Code} from "anbaric";
|
|
|
13
13
|
|
|
14
14
|
const chargeCard = new Action("Charge the card", new Code("billing"));
|
|
15
15
|
|
|
16
|
-
chargeCard.predicate = (job) => job.properties.get("paid") !== true; // skip if already paid
|
|
16
|
+
chargeCard.predicate = async (job) => await job.properties.get("paid") !== true; // skip if already paid
|
|
17
17
|
chargeCard.run = async (job) => {
|
|
18
|
-
const amount = job.properties.get("total");
|
|
18
|
+
const amount = await job.properties.get("total");
|
|
19
19
|
// ... call your payment provider ...
|
|
20
20
|
return new Map([["paid", true], ["chargedAmount", amount]]);
|
|
21
21
|
};
|
|
@@ -32,31 +32,35 @@ Actions in a state run **in order** each time a job is processed. An action that
|
|
|
32
32
|
returns an unchanged value doesn't churn the job — the machine only advances (or
|
|
33
33
|
schedules a re-check) when something actually changes.
|
|
34
34
|
|
|
35
|
-
###
|
|
35
|
+
### Reading properties is asynchronous
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
`job.properties` reads on demand — `await job.properties.get("total")` — so a
|
|
38
|
+
job can hold a lot (hundreds of article summaries, say) while an action that
|
|
39
|
+
needs one property pays for one. That's why `predicate` may be `async`, as in
|
|
40
|
+
the example above, and why a transition's guard may be too. Afterwards the
|
|
41
|
+
machine writes back only the properties that changed — never the ones the pass
|
|
42
|
+
read, let alone the ones it never loaded.
|
|
43
|
+
|
|
44
|
+
### Prewarm what a state needs
|
|
45
|
+
|
|
46
|
+
Each property read that wasn't already held is a fetch. A state can name what
|
|
47
|
+
to load up front, in one go:
|
|
39
48
|
|
|
40
49
|
```ts
|
|
41
50
|
import {Reads} from "anbaric";
|
|
42
51
|
|
|
43
|
-
|
|
52
|
+
const scoring = new State("scoring", [score], [new Transition("grouped")]);
|
|
53
|
+
scoring.prewarm = Reads.only("summary");
|
|
44
54
|
```
|
|
45
55
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
`Reads.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
state's actions, awaits and transitions read; a property that wasn't declared
|
|
55
|
-
is simply absent from `job.properties`, so declare honestly. Afterwards it
|
|
56
|
-
writes back only the properties that changed — never the ones it read, and
|
|
57
|
-
never the ones it didn't load. For an [agentic action](ai-agents.md) the
|
|
58
|
-
declaration also decides what reaches the model: the prompt carries the loaded
|
|
59
|
-
properties and nothing more.
|
|
56
|
+
By default a state prewarms everything, so nothing changes until you narrow it;
|
|
57
|
+
anything a step reads beyond the prewarm still loads on demand, so a narrow
|
|
58
|
+
prewarm can never break a step, only cost it a fetch. `Reads` is a function of
|
|
59
|
+
the machine's property definitions — `Reads.everything`, `Reads.nothing`,
|
|
60
|
+
`Reads.only(...keys)`, `Reads.where(definition => …)` — so a mapping written
|
|
61
|
+
once keeps up as the schema grows. For an [agentic action](ai-agents.md) the
|
|
62
|
+
prewarm also decides what reaches the model: the prompt carries the properties
|
|
63
|
+
the job holds when the action runs.
|
|
60
64
|
|
|
61
65
|
## Actors: who does the work
|
|
62
66
|
|
|
@@ -34,7 +34,7 @@ const triage = new RemoteLLMAgenticAction(
|
|
|
34
34
|
|
|
35
35
|
const support = new StateMachine("support", [
|
|
36
36
|
new State("open", [triage], [
|
|
37
|
-
new Transition("prioritised", (job) => job.properties.has("priority")),
|
|
37
|
+
new Transition("prioritised", async (job) => await job.properties.has("priority")),
|
|
38
38
|
]),
|
|
39
39
|
new State("prioritised"),
|
|
40
40
|
]);
|
|
@@ -44,12 +44,12 @@ When a job is processed in `open`, the agent is asked to produce a `priority`
|
|
|
44
44
|
constrained to the schema; the result is applied to the job, and the transition
|
|
45
45
|
advances it. The decision is recorded against `triager`.
|
|
46
46
|
|
|
47
|
-
The job's
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
history and everything else out of every call.
|
|
47
|
+
The job's properties are appended to the prompt automatically, so the model
|
|
48
|
+
sees the data it's reasoning about — the properties the job holds when the
|
|
49
|
+
action runs, which is all of them until the state says otherwise. On a job
|
|
50
|
+
that holds a lot, [prewarm](actions-and-actors.md#prewarm-what-a-state-needs)
|
|
51
|
+
just what the model needs: `open.prewarm = Reads.only("subject")` keeps a
|
|
52
|
+
ticket's attachments, history and everything else out of every call.
|
|
53
53
|
|
|
54
54
|
## The output schema
|
|
55
55
|
|
|
@@ -22,7 +22,7 @@ approve.resolveUrl = (job) => `/approve?job=${job.id}`; // where a person prov
|
|
|
22
22
|
|
|
23
23
|
const fulfilment = new StateMachine("fulfilment", [
|
|
24
24
|
new State("review", [approve], [
|
|
25
|
-
new Transition("approved", (job) => job.properties.get("approved") === true),
|
|
25
|
+
new Transition("approved", async (job) => await job.properties.get("approved") === true),
|
|
26
26
|
]),
|
|
27
27
|
new State("approved"),
|
|
28
28
|
]);
|
|
@@ -52,7 +52,7 @@ of the job** so you can build a per-job link — for example putting the job id
|
|
|
52
52
|
the query string so your form knows which job it's resolving:
|
|
53
53
|
|
|
54
54
|
```ts
|
|
55
|
-
approve.resolveUrl = (job) => `/approve?job=${job.id}&total=${job.properties.get("total")}`;
|
|
55
|
+
approve.resolveUrl = async (job) => `/approve?job=${job.id}&total=${await job.properties.get("total")}`;
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
Write it **app-relative** (an absolute path like `/approve`, as above) — it's a
|
|
@@ -43,11 +43,12 @@ against your schema before applying them. Every property a job holds must have a
|
|
|
43
43
|
`PropertyDefinition`, or the change is rejected. This is what keeps a job's data
|
|
44
44
|
trustworthy no matter who or what wrote it.
|
|
45
45
|
|
|
46
|
-
Properties are stored one by one
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
Properties are stored one by one and read on demand — `await
|
|
47
|
+
job.properties.get("name")` — so a job can carry a great deal of data without
|
|
48
|
+
every step paying for all of it. A state can
|
|
49
|
+
[prewarm](actions-and-actors.md#prewarm-what-a-state-needs) what it needs in
|
|
50
|
+
one fetch; a step that scores one summary loads one summary, and writes back
|
|
51
|
+
only what it changed.
|
|
51
52
|
|
|
52
53
|
## Nothing is hard-wired
|
|
53
54
|
|
|
@@ -66,7 +66,7 @@ Every scheduled job carries the run it belongs to:
|
|
|
66
66
|
|
|
67
67
|
```ts
|
|
68
68
|
processRun.run = async (job) => {
|
|
69
|
-
const scheduledFor = new Date(job.properties.get("scheduledFor"));
|
|
69
|
+
const scheduledFor = new Date(await job.properties.get("scheduledFor"));
|
|
70
70
|
// …reconcile everything up to scheduledFor
|
|
71
71
|
};
|
|
72
72
|
```
|
|
@@ -13,7 +13,7 @@ import {Action, Code, PropertyDefinition, State, StateMachine, Transition} from
|
|
|
13
13
|
|
|
14
14
|
const sendWelcome = new Action("Send welcome email", new Code("welcome"));
|
|
15
15
|
sendWelcome.run = async (job) => {
|
|
16
|
-
const email = job.properties.get("email");
|
|
16
|
+
const email = await job.properties.get("email");
|
|
17
17
|
console.log(`Sending welcome email to ${email}`);
|
|
18
18
|
return new Map([["welcomeSent", true]]); // properties to add to the job
|
|
19
19
|
};
|
|
@@ -25,7 +25,7 @@ const onboarding = new StateMachine(
|
|
|
25
25
|
"onboarding",
|
|
26
26
|
[
|
|
27
27
|
new State("new", [sendWelcome], [
|
|
28
|
-
new Transition("active", (job) => job.properties.get("welcomeSent") === true),
|
|
28
|
+
new Transition("active", async (job) => await job.properties.get("welcomeSent") === true),
|
|
29
29
|
]),
|
|
30
30
|
new State("active"),
|
|
31
31
|
],
|
|
@@ -67,7 +67,7 @@ once on entry. That is deliberate — it lets a predicate be time-based
|
|
|
67
67
|
work that must happen only once, guard it:
|
|
68
68
|
|
|
69
69
|
```ts
|
|
70
|
-
fetchReport.predicate = (job) => !job.properties.has("report");
|
|
70
|
+
fetchReport.predicate = async (job) => ! await job.properties.has("report");
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
## Failing a job
|
|
@@ -79,7 +79,7 @@ job cannot proceed":
|
|
|
79
79
|
|
|
80
80
|
```ts
|
|
81
81
|
chargeCard.run = async (job) => {
|
|
82
|
-
const outcome = await payments.charge(job.properties.get("amount"));
|
|
82
|
+
const outcome = await payments.charge(await job.properties.get("amount"));
|
|
83
83
|
if (!outcome.ok) throw new Error(`Card declined for job ${job.id}: ${outcome.reason}`);
|
|
84
84
|
return new Map([["charged", true]]);
|
|
85
85
|
};
|
|
@@ -97,7 +97,7 @@ Mark the end of a process with `Terminal`, which carries an outcome:
|
|
|
97
97
|
import {Terminal, Transition} from "anbaric";
|
|
98
98
|
|
|
99
99
|
new State("packing", [packItems], [
|
|
100
|
-
new Transition("shipped", (job) => job.properties.get("packed") === true),
|
|
100
|
+
new Transition("shipped", async (job) => await job.properties.get("packed") === true),
|
|
101
101
|
]),
|
|
102
102
|
new Terminal("shipped", Terminal.Outcome.SUCCESS),
|
|
103
103
|
new Terminal("cancelled", Terminal.Outcome.FAILURE),
|
|
@@ -127,8 +127,8 @@ specific conditions first.
|
|
|
127
127
|
|
|
128
128
|
```ts
|
|
129
129
|
new State("placed", [], [
|
|
130
|
-
new Transition("cancelled", (job) => job.properties.get("cancelled") === true),
|
|
131
|
-
new Transition("packing", (job) => job.properties.get("paid") === true),
|
|
130
|
+
new Transition("cancelled", async (job) => await job.properties.get("cancelled") === true),
|
|
131
|
+
new Transition("packing", async (job) => await job.properties.get("paid") === true),
|
|
132
132
|
]),
|
|
133
133
|
```
|
|
134
134
|
|
|
@@ -45,17 +45,17 @@ review.resolveUrl = (job) => `/review?job=${job.id}`;
|
|
|
45
45
|
// An automated payment step.
|
|
46
46
|
const payOut = new Action("Pay the expense", new Code("payments"));
|
|
47
47
|
payOut.run = async (job) => {
|
|
48
|
-
// ... call your payment provider with job.properties.get("amount") ...
|
|
48
|
+
// ... call your payment provider with await job.properties.get("amount") ...
|
|
49
49
|
return new Map([["paid", true]]);
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
const expenses = new StateMachine("expenses", [
|
|
53
53
|
new State("submitted", [review], [
|
|
54
|
-
new Transition("approved", (job) => job.properties.get("approved") === true),
|
|
55
|
-
new Transition("rejected", (job) => job.properties.get("approved") === false),
|
|
54
|
+
new Transition("approved", async (job) => await job.properties.get("approved") === true),
|
|
55
|
+
new Transition("rejected", async (job) => await job.properties.get("approved") === false),
|
|
56
56
|
]),
|
|
57
57
|
new State("approved", [payOut], [
|
|
58
|
-
new Transition("paid", (job) => job.properties.get("paid") === true),
|
|
58
|
+
new Transition("paid", async (job) => await job.properties.get("paid") === true),
|
|
59
59
|
]),
|
|
60
60
|
new Terminal("paid", Terminal.Outcome.SUCCESS),
|
|
61
61
|
new Terminal("rejected", Terminal.Outcome.FAILURE),
|
|
@@ -18,8 +18,8 @@ approve.fields = ["approved", "note"];
|
|
|
18
18
|
approve.resolveUrl = (job) => `/approve?job=${job.id}`;
|
|
19
19
|
|
|
20
20
|
new State("review", [approve], [
|
|
21
|
-
new Transition("approved", (job) => job.properties.get("approved") === true),
|
|
22
|
-
new Transition("rejected", (job) => job.properties.get("approved") === false),
|
|
21
|
+
new Transition("approved", async (job) => await job.properties.get("approved") === true),
|
|
22
|
+
new Transition("rejected", async (job) => await job.properties.get("approved") === false),
|
|
23
23
|
]),
|
|
24
24
|
```
|
|
25
25
|
|
|
@@ -29,8 +29,8 @@ Because `resolveUrl` is a function of the job, you can encode everything the pag
|
|
|
29
29
|
needs in the link — most importantly the **job id**:
|
|
30
30
|
|
|
31
31
|
```ts
|
|
32
|
-
approve.resolveUrl = (job) =>
|
|
33
|
-
`/approve?job=${job.id}&amount=${job.properties.get("amount")}`;
|
|
32
|
+
approve.resolveUrl = async (job) =>
|
|
33
|
+
`/approve?job=${job.id}&amount=${await job.properties.get("amount")}`;
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
The admin console lists every job **awaiting a human** and turns this
|
|
@@ -57,9 +57,9 @@ matching `approved` fires. The decision is recorded against the user in the
|
|
|
57
57
|
Use `metadata` to carry extra context with the wait (shown alongside the task):
|
|
58
58
|
|
|
59
59
|
```ts
|
|
60
|
-
approve.metadata = (job) => new Map([
|
|
61
|
-
["submittedBy", job.properties.get("submitter")],
|
|
62
|
-
["amount", job.properties.get("amount")],
|
|
60
|
+
approve.metadata = async (job) => new Map([
|
|
61
|
+
["submittedBy", await job.properties.get("submitter")],
|
|
62
|
+
["amount", await job.properties.get("amount")],
|
|
63
63
|
]);
|
|
64
64
|
```
|
|
65
65
|
|
|
@@ -72,10 +72,10 @@ Chain approvals by giving each its own state and `Await`. Because actions after
|
|
|
72
72
|
|
|
73
73
|
```ts
|
|
74
74
|
new State("manager-review", [managerApprove], [
|
|
75
|
-
new Transition("finance-review", (job) => job.properties.get("managerApproved") === true),
|
|
75
|
+
new Transition("finance-review", async (job) => await job.properties.get("managerApproved") === true),
|
|
76
76
|
]),
|
|
77
77
|
new State("finance-review", [financeApprove], [
|
|
78
|
-
new Transition("approved", (job) => job.properties.get("financeApproved") === true),
|
|
78
|
+
new Transition("approved", async (job) => await job.properties.get("financeApproved") === true),
|
|
79
79
|
]),
|
|
80
80
|
```
|
|
81
81
|
|
|
@@ -13,7 +13,7 @@ job park until the system reports back:
|
|
|
13
13
|
```ts
|
|
14
14
|
const startSigning = new Action("Send for signature", new Code("esign"));
|
|
15
15
|
startSigning.run = async (job) => {
|
|
16
|
-
const ref = await eSignProvider.createEnvelope(job.properties.get("documentUrl"));
|
|
16
|
+
const ref = await eSignProvider.createEnvelope(await job.properties.get("documentUrl"));
|
|
17
17
|
return new Map([["envelopeRef", ref]]);
|
|
18
18
|
};
|
|
19
19
|
|
|
@@ -21,7 +21,7 @@ const awaitSignature = new Await("Await signature", "EXTERNAL_SYSTEM");
|
|
|
21
21
|
awaitSignature.fields = ["signed"];
|
|
22
22
|
|
|
23
23
|
new State("signing", [startSigning, awaitSignature], [
|
|
24
|
-
new Transition("signed", (job) => job.properties.get("signed") === true),
|
|
24
|
+
new Transition("signed", async (job) => await job.properties.get("signed") === true),
|
|
25
25
|
]),
|
|
26
26
|
```
|
|
27
27
|
|
|
@@ -59,12 +59,12 @@ re-checks after a back-off until the status flips:
|
|
|
59
59
|
```ts
|
|
60
60
|
const checkStatus = new Action("Poll payment status", new Code("payments"));
|
|
61
61
|
checkStatus.run = async (job) => {
|
|
62
|
-
const status = await gateway.status(job.properties.get("paymentRef"));
|
|
62
|
+
const status = await gateway.status(await job.properties.get("paymentRef"));
|
|
63
63
|
return status === "settled" ? new Map([["settled", true]]) : new Map();
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
new State("charging", [checkStatus], [
|
|
67
|
-
new Transition("settled", (job) => job.properties.get("settled") === true),
|
|
67
|
+
new Transition("settled", async (job) => await job.properties.get("settled") === true),
|
|
68
68
|
]),
|
|
69
69
|
```
|
|
70
70
|
|
|
@@ -28,7 +28,7 @@ transition reacts. This keeps each piece testable and the flow visible.
|
|
|
28
28
|
|
|
29
29
|
```ts
|
|
30
30
|
new State("packing", [packItems], [
|
|
31
|
-
new Transition("shipped", (job) => job.properties.get("packed") === true),
|
|
31
|
+
new Transition("shipped", async (job) => await job.properties.get("packed") === true),
|
|
32
32
|
]),
|
|
33
33
|
```
|
|
34
34
|
|
|
@@ -39,8 +39,8 @@ transition wins. Put more specific transitions first:
|
|
|
39
39
|
|
|
40
40
|
```ts
|
|
41
41
|
new State("placed", [], [
|
|
42
|
-
new Transition("cancelled", (job) => job.properties.get("cancelled") === true),
|
|
43
|
-
new Transition("packing", (job) => job.properties.get("paid") === true),
|
|
42
|
+
new Transition("cancelled", async (job) => await job.properties.get("cancelled") === true),
|
|
43
|
+
new Transition("packing", async (job) => await job.properties.get("paid") === true),
|
|
44
44
|
]),
|
|
45
45
|
```
|
|
46
46
|
|
|
@@ -51,10 +51,10 @@ routes work without extra states:
|
|
|
51
51
|
|
|
52
52
|
```ts
|
|
53
53
|
const autoTriage = new Action("Auto triage", new Code("triage-bot"));
|
|
54
|
-
autoTriage.predicate = (job) => job.properties.get("priority") === "low";
|
|
54
|
+
autoTriage.predicate = async (job) => await job.properties.get("priority") === "low";
|
|
55
55
|
|
|
56
56
|
const escalate = new Action("Escalate", new Code("rules"));
|
|
57
|
-
escalate.predicate = (job) => job.properties.get("priority") === "high";
|
|
57
|
+
escalate.predicate = async (job) => await job.properties.get("priority") === "high";
|
|
58
58
|
|
|
59
59
|
new State("open", [autoTriage, escalate], [/* transitions */]);
|
|
60
60
|
```
|
|
@@ -67,7 +67,7 @@ parks cleanly and resumes when the input arrives:
|
|
|
67
67
|
|
|
68
68
|
```ts
|
|
69
69
|
new State("review", [new Await("Approve", "HUMAN")], [
|
|
70
|
-
new Transition("approved", (job) => job.properties.get("approved") === true),
|
|
70
|
+
new Transition("approved", async (job) => await job.properties.get("approved") === true),
|
|
71
71
|
]),
|
|
72
72
|
```
|
|
73
73
|
|
|
@@ -81,7 +81,7 @@ after a back-off — so a state can gently poll without a busy loop.
|
|
|
81
81
|
```ts
|
|
82
82
|
const poll = new Action("Check delivery status", new Code("carrier"));
|
|
83
83
|
poll.run = async (job) => {
|
|
84
|
-
const status = await fetchStatus(job.properties.get("tracking"));
|
|
84
|
+
const status = await fetchStatus(await job.properties.get("tracking"));
|
|
85
85
|
return status === "delivered" ? new Map([["delivered", true]]) : new Map();
|
|
86
86
|
};
|
|
87
87
|
```
|
|
@@ -109,7 +109,7 @@ const companies = new PropertyDefinition("companies");
|
|
|
109
109
|
companies.example = [{ slug: "acme", companyName: "Acme Corp" }];
|
|
110
110
|
|
|
111
111
|
// in an action
|
|
112
|
-
const companies = job.properties.get("companies") ?? [];
|
|
112
|
+
const companies = await job.properties.get("companies") ?? [];
|
|
113
113
|
```
|
|
114
114
|
|
|
115
115
|
## See also
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anbaric",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.58.1",
|
|
4
4
|
"description": "Everything needed to write an Anbaric app: state machines, jobs, document and secret stores, local in-memory implementations and the Anbaric Cloud clients",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"prepublishOnly": "npm run build"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"anbaric-impl-cloud": "^1.
|
|
28
|
-
"anbaric-data-store": "^1.
|
|
29
|
-
"anbaric-state-machine": "^1.
|
|
30
|
-
"anbaric-tsapi": "^1.
|
|
27
|
+
"anbaric-impl-cloud": "^1.58.1",
|
|
28
|
+
"anbaric-data-store": "^1.58.1",
|
|
29
|
+
"anbaric-state-machine": "^1.58.1",
|
|
30
|
+
"anbaric-tsapi": "^1.58.1"
|
|
31
31
|
}
|
|
32
32
|
}
|