anbaric 1.56.9 → 1.58.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/docs/api/state-machine.md +70 -11
- package/docs/api/web.md +11 -0
- package/docs/features/actions-and-actors.md +32 -2
- package/docs/features/ai-agents.md +7 -3
- package/docs/features/awaiting-input.md +2 -2
- package/docs/features/core-concepts.md +7 -0
- 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,20 +160,23 @@ actor : Actor
|
|
|
136
160
|
constructor(name : string, actor : Actor, description : string = "", id? : string)
|
|
137
161
|
|
|
138
162
|
// Replaceable function fields — assign your own:
|
|
139
|
-
predicate : (job : Job) => boolean
|
|
140
|
-
run : (job : Job) => Promise<Map<string, any>>
|
|
163
|
+
predicate : (job : Job) => boolean | Promise<boolean> // default: () => true
|
|
164
|
+
run : (job : Job) => Promise<Map<string, any>> // default: async () => new Map()
|
|
141
165
|
```
|
|
142
166
|
|
|
143
167
|
You configure an action by assigning `predicate` and `run`:
|
|
144
168
|
|
|
145
169
|
```ts
|
|
146
170
|
const sendWelcome = new Action("Send welcome email", new Code("welcome"));
|
|
171
|
+
sendWelcome.predicate = async (job) => ! await job.properties.has("welcomeSent");
|
|
147
172
|
sendWelcome.run = async (job) => new Map([["welcomeSent", true]]);
|
|
148
173
|
```
|
|
149
174
|
|
|
150
|
-
- **`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.
|
|
151
177
|
- **`run`** — return a `Map` of the properties to change. Only properties in the
|
|
152
|
-
machine's schema are applied; others are ignored with a warning.
|
|
178
|
+
machine's schema are applied; others are ignored with a warning. Only the
|
|
179
|
+
properties that actually changed are written back.
|
|
153
180
|
|
|
154
181
|
See [Actions and actors](../features/actions-and-actors.md).
|
|
155
182
|
|
|
@@ -170,12 +197,12 @@ name : string
|
|
|
170
197
|
description : string
|
|
171
198
|
waitingFor? : AwaitParty
|
|
172
199
|
fields : Array<string> = []
|
|
173
|
-
resolveUrl : string | ((job : Job) => string) = ""
|
|
174
|
-
metadata : (job : Job) => Map<string, any> // default: () => new Map()
|
|
200
|
+
resolveUrl : string | ((job : Job) => string | Promise<string>) = ""
|
|
201
|
+
metadata : (job : Job) => Map<string, any> | Promise<Map<string, any>> // default: () => new Map()
|
|
175
202
|
|
|
176
203
|
constructor(name : string, waitingFor? : AwaitParty, description : string = "", id? : string)
|
|
177
204
|
|
|
178
|
-
waitForInput(job : Job) : WaitForInput
|
|
205
|
+
waitForInput(job : Job) : Promise<WaitForInput>
|
|
179
206
|
```
|
|
180
207
|
|
|
181
208
|
- **`fields`** — the input you expect back (property names).
|
|
@@ -217,19 +244,20 @@ transition whose predicate holds moves the job to `to`.
|
|
|
217
244
|
|
|
218
245
|
```ts
|
|
219
246
|
to : string
|
|
220
|
-
predicate : (job : Job) => boolean
|
|
247
|
+
predicate : (job : Job) => boolean | Promise<boolean> // default: () => true
|
|
221
248
|
|
|
222
|
-
constructor(to : string, predicate? : (job : Job) => boolean)
|
|
249
|
+
constructor(to : string, predicate? : (job : Job) => boolean | Promise<boolean>)
|
|
223
250
|
```
|
|
224
251
|
|
|
225
252
|
```ts
|
|
226
|
-
new Transition("active", (job) => job.properties.get("welcomeSent") === true)
|
|
253
|
+
new Transition("active", async (job) => await job.properties.get("welcomeSent") === true)
|
|
227
254
|
new Transition("scoring") // unguarded: the actions run, then the job moves on
|
|
228
255
|
```
|
|
229
256
|
|
|
230
257
|
The predicate is optional. Omit it when a state's actions simply run and the job
|
|
231
258
|
should move on, rather than inventing a sentinel property for the transition to
|
|
232
|
-
read. Guard a transition only when the move is conditional.
|
|
259
|
+
read. Guard a transition only when the move is conditional. A guard may be
|
|
260
|
+
`async`, since reading a property is.
|
|
233
261
|
|
|
234
262
|
---
|
|
235
263
|
|
|
@@ -268,6 +296,37 @@ A job whose action threw is `FAILED`, with the reason in its audit trail. It
|
|
|
268
296
|
stays in its state rather than transitioning, and an update that moves it on
|
|
269
297
|
returns it to `ACTIVE` — so a failure is recoverable, not terminal.
|
|
270
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
|
+
|
|
317
|
+
### Reading a job
|
|
318
|
+
|
|
319
|
+
```ts
|
|
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
|
|
322
|
+
```
|
|
323
|
+
|
|
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
|
|
327
|
+
`GET /api/v2/jobs/<id>?keys=summary,score`, and the properties in a `PUT` body
|
|
328
|
+
are the ones written.
|
|
329
|
+
|
|
271
330
|
### Listing jobs
|
|
272
331
|
|
|
273
332
|
```ts
|
package/docs/api/web.md
CHANGED
|
@@ -116,6 +116,17 @@ In code the same listing is `JobPersistenceFactory.instance().list(actor,
|
|
|
116
116
|
pageSize, page, query)`; the CLI's `anbaric jobs list` and the MCP tool
|
|
117
117
|
`anbaric_jobs_list` take the same filters.
|
|
118
118
|
|
|
119
|
+
```
|
|
120
|
+
GET /api/v2/jobs/<id> every property
|
|
121
|
+
GET /api/v2/jobs/<id>?keys=summary,score only those
|
|
122
|
+
PUT /api/v2/jobs/<id> body: a serialised job
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
A job's properties are stored one by one. A `GET` with `keys` returns only
|
|
126
|
+
those; the properties in a `PUT` body are **upserted** — written if given,
|
|
127
|
+
left alone if not — so a caller that read a few keys can write a few keys
|
|
128
|
+
without disturbing the rest, and no `PUT` ever removes a property.
|
|
129
|
+
|
|
119
130
|
### Other resources
|
|
120
131
|
|
|
121
132
|
Also under `/api/v2`, reached through the cloud clients rather than raw HTTP:
|
|
@@ -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,6 +32,36 @@ 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
|
+
### Reading properties is asynchronous
|
|
36
|
+
|
|
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:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import {Reads} from "anbaric";
|
|
51
|
+
|
|
52
|
+
const scoring = new State("scoring", [score], [new Transition("grouped")]);
|
|
53
|
+
scoring.prewarm = Reads.only("summary");
|
|
54
|
+
```
|
|
55
|
+
|
|
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.
|
|
64
|
+
|
|
35
65
|
## Actors: who does the work
|
|
36
66
|
|
|
37
67
|
An actor is a small identity object — `type`, `id`, and `roles`. There are four
|
|
@@ -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,8 +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
|
-
|
|
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.
|
|
49
53
|
|
|
50
54
|
## The output schema
|
|
51
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,6 +43,13 @@ 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 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.
|
|
52
|
+
|
|
46
53
|
## Nothing is hard-wired
|
|
47
54
|
|
|
48
55
|
Persistence, queueing, document/secret/SQL stores — all of them come from
|
|
@@ -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.0",
|
|
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.0",
|
|
28
|
+
"anbaric-data-store": "^1.58.0",
|
|
29
|
+
"anbaric-state-machine": "^1.58.0",
|
|
30
|
+
"anbaric-tsapi": "^1.58.0"
|
|
31
31
|
}
|
|
32
32
|
}
|