fyflow-orchestration 0.7.0 → 0.8.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/AGENTS.md +1800 -200
- package/README.md +88 -104
- package/dist/browser/chunks/{chunk-YVOP3DIN.js → chunk-EUU44PHD.js} +2104 -283
- package/dist/browser/index.js +19 -9
- package/dist/browser/scheduler/index.js +101 -58
- package/dist/node/chunks/{chunk-YVOP3DIN.js → chunk-EUU44PHD.js} +2104 -283
- package/dist/node/index.js +19 -9
- package/dist/node/scheduler/index.js +101 -58
- package/dist/types/src/definition/assist.d.ts +29 -5
- package/dist/types/src/definition/condition.d.ts +49 -0
- package/dist/types/src/definition/regions.d.ts +12 -1
- package/dist/types/src/definition/registry.d.ts +52 -10
- package/dist/types/src/definition/schema.d.ts +20 -8
- package/dist/types/src/definition/types.d.ts +152 -23
- package/dist/types/src/definition/validate.d.ts +68 -20
- package/dist/types/src/definition/wiring.d.ts +58 -10
- package/dist/types/src/definition/witness.d.ts +12 -4
- package/dist/types/src/driver/driver.d.ts +772 -40
- package/dist/types/src/driver/listener.d.ts +7 -1
- package/dist/types/src/driver/log.d.ts +20 -5
- package/dist/types/src/driver/routing.d.ts +10 -5
- package/dist/types/src/fold/policies.d.ts +4 -5
- package/dist/types/src/fold/run-state.d.ts +245 -14
- package/dist/types/src/scheduler/bootstrap.d.ts +18 -26
- package/dist/types/src/scheduler/envelope-worker.d.ts +6 -5
- package/dist/types/src/scheduler/index.d.ts +2 -2
- package/dist/types/src/scheduler/runtime.d.ts +105 -32
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -14,12 +14,10 @@ worker returned, routing can **fan out** into parallel branches, and a **join**
|
|
|
14
14
|
+--> [review] (low confidence routes away entirely)
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
The scheduler runs tasks
|
|
18
|
-
|
|
19
|
-
built here rather than wrapped from below. See [doc/concept.md](doc/concept.md) for the reasoning.
|
|
17
|
+
The scheduler runs tasks; this library decides **which task runs next**. `fyflow-scheduler` has no
|
|
18
|
+
dependency graph of its own, so sequence, decision, fan-out and join live here.
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
The manifests read `0.7.0`.
|
|
20
|
+
Published on npm as `fyflow-orchestration` and on JSR as `@fyflow/orchestration`. The manifests read `0.8.0`.
|
|
23
21
|
|
|
24
22
|
## Install
|
|
25
23
|
|
|
@@ -28,33 +26,27 @@ npm install fyflow-orchestration fyflow-scheduler # Node, and a browser bundl
|
|
|
28
26
|
deno add jsr:@fyflow/orchestration # Deno
|
|
29
27
|
```
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
from JSR at the same version. **Node 24 or a current Deno.** The library's own suites also run in the
|
|
38
|
-
browser, but a flow does not: a worker there has to be a bundled `.js` asset, because nothing in a
|
|
39
|
-
browser executes TypeScript.
|
|
29
|
+
- **Runtimes:** Node 24 or a current Deno. A flow does not run in a browser: a worker there would have
|
|
30
|
+
to be a bundled `.js` asset.
|
|
31
|
+
- **The scheduler is a peer dependency**, pinned to the exact version in this package's
|
|
32
|
+
`peerDependencies`. Deno takes it from JSR at the same version.
|
|
33
|
+
- **What ships:** npm has bundles for Node and browser bundlers plus type declarations, and JSR has
|
|
34
|
+
the TypeScript source. Both include [AGENTS.md](AGENTS.md), the complete reference.
|
|
40
35
|
|
|
41
36
|
## Getting started
|
|
42
37
|
|
|
43
|
-
**Two JSON documents, and one file of your own.** A **registry** says what exists
|
|
44
|
-
and
|
|
45
|
-
edges between them. `createRuntime` builds
|
|
46
|
-
|
|
47
|
-
(
|
|
48
|
-
worker — [examples/getting-started/worker.ts](examples/getting-started/worker.ts), a class with a
|
|
49
|
-
`run` method, and nothing else.
|
|
38
|
+
**Two JSON documents, and one file of your own.** A **registry** says what exists: worker modules,
|
|
39
|
+
and the node types that run on them. A **flow** says what happens: nodes naming those types, and the
|
|
40
|
+
edges between them. `createRuntime` builds what the registry names, and `start` runs a flow on it.
|
|
41
|
+
The only code you write is the worker,
|
|
42
|
+
[examples/getting-started/worker.ts](examples/getting-started/worker.ts): a class with a `run` method.
|
|
50
43
|
|
|
51
44
|
```ts
|
|
52
45
|
import { createRuntime } from 'fyflow-orchestration/scheduler';
|
|
53
46
|
import { type FlowDefinition, type RegistryDocument } from 'fyflow-orchestration';
|
|
54
47
|
|
|
55
48
|
// THE REGISTRY — what exists. One worker module, and one node type that runs on it. This is data:
|
|
56
|
-
// in your program it is a JSON file you import. The worker url is relative
|
|
57
|
-
// to what; the file itself is `examples/getting-started/worker.ts` — a class with a `run` method.
|
|
49
|
+
// in your program it is a JSON file you import. The worker url is relative to `base` below.
|
|
58
50
|
const registry: RegistryDocument = {
|
|
59
51
|
workers: {
|
|
60
52
|
greeter: {
|
|
@@ -66,9 +58,7 @@ const registry: RegistryDocument = {
|
|
|
66
58
|
};
|
|
67
59
|
|
|
68
60
|
// THE FLOW — what happens. Two nodes of that type, each with its own `settings`, then an end. A flow
|
|
69
|
-
// names registry
|
|
70
|
-
// runtimes that agree on the keys. The end node's `outcome` is a LABEL of yours that the driver does
|
|
71
|
-
// not interpret; the run's own outcome is the fold's — `state.outcome` below.
|
|
61
|
+
// names registry keys only (no files, urls or classes), so it is portable between runtimes.
|
|
72
62
|
const flow: FlowDefinition = {
|
|
73
63
|
flowId: 'hello', version: 1, entry: 'greet',
|
|
74
64
|
nodes: [
|
|
@@ -78,29 +68,22 @@ const flow: FlowDefinition = {
|
|
|
78
68
|
],
|
|
79
69
|
};
|
|
80
70
|
|
|
81
|
-
// ONE call builds the
|
|
82
|
-
//
|
|
83
|
-
// flow arrives, at `start`, which is what builds the `Driver` — so a `settings` value the worker's
|
|
84
|
-
// schema rejects names its node from the call three lines below, not from this one. `base` is what
|
|
85
|
-
// relative worker urls resolve against — `import.meta.url` when the registry sits beside this file.
|
|
71
|
+
// ONE call builds the pools and the scheduler, and refuses a defective registry here, by name. A
|
|
72
|
+
// defect in the FLOW is refused by `start`, below.
|
|
86
73
|
const runtime = createRuntime(registry, {}, { base: import.meta.url });
|
|
87
74
|
|
|
88
|
-
// `start`
|
|
89
|
-
//
|
|
90
|
-
// and `stopped` are terminal OUTCOMES, so a run that goes wrong arrives as `state.outcome` and not
|
|
91
|
-
// as a `catch`. This snippet wants only the end, so it names only `done`. *A run with a person in
|
|
92
|
-
// it does not end by itself — section 7 is where that is taught, and section 8 is what `done` waits
|
|
93
|
-
// for.*
|
|
75
|
+
// `start` runs the flow. `done` resolves with the run's final state and never rejects: a run that
|
|
76
|
+
// goes wrong arrives as `state.outcome` ('failed', 'stuck', 'halted'), not as a `catch`.
|
|
94
77
|
const { done } = runtime.start(flow, { text: 'hello' });
|
|
95
78
|
const state = await done;
|
|
96
79
|
await runtime.shutdown();
|
|
97
80
|
|
|
98
|
-
//
|
|
99
|
-
// handed THAT as its input — the previous node's result is the next node's `input`.
|
|
81
|
+
// The previous node's result is the next node's input: `greet` made `hello world`, `shout` added `!`.
|
|
100
82
|
const shout = Object.values(state.nodes).find((n) => n.nodeId === 'shout');
|
|
101
83
|
console.log(state.outcome); // 'completed'
|
|
102
84
|
console.log(shout?.result); // { text: 'hello world !' }
|
|
103
85
|
|
|
86
|
+
// Verified by the docs gate:
|
|
104
87
|
if (state.outcome !== 'completed') throw new Error(`expected completed, got ${state.outcome}`);
|
|
105
88
|
if (JSON.stringify(shout?.result) !== '{"text":"hello world !"}') throw new Error(`shout returned ${JSON.stringify(shout?.result)}`);
|
|
106
89
|
```
|
|
@@ -108,37 +91,32 @@ if (JSON.stringify(shout?.result) !== '{"text":"hello world !"}') throw new Erro
|
|
|
108
91
|
|
|
109
92
|
## A flow that decides
|
|
110
93
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
[examples/getting-started/tag.ts](examples/getting-started/tag.ts), five lines that tag what they see.
|
|
94
|
+
A **decision** is an edge with a `when` naming a predicate: code you supply, under a name the
|
|
95
|
+
registry declares. A **fan-out** opens branches that run concurrently; a **join** waits for them and
|
|
96
|
+
hands the next node every branch's result, keyed by branch path. Below, two runs at high and low
|
|
97
|
+
confidence take different roads. The worker is
|
|
98
|
+
[examples/getting-started/tag.ts](examples/getting-started/tag.ts).
|
|
117
99
|
|
|
118
100
|
```ts
|
|
119
101
|
import { createRuntime } from 'fyflow-orchestration/scheduler';
|
|
120
102
|
import { type FlowDefinition, type Predicate, type RegistryDocument, type RunState } from 'fyflow-orchestration';
|
|
121
103
|
|
|
122
|
-
// THE REGISTRY — one worker, one type, and the NAME of a predicate the flow may use.
|
|
123
|
-
//
|
|
124
|
-
// is refused at construction, not at the decision. The worker tags its input with the node's label.
|
|
104
|
+
// THE REGISTRY — one worker, one type, and the NAME of a predicate the flow may use. The predicate
|
|
105
|
+
// itself is code, passed to `createRuntime` under that name. The worker tags its input with a label.
|
|
125
106
|
const registry: RegistryDocument = {
|
|
126
107
|
workers: { tag: { url: '../../examples/getting-started/tag.ts' } }, // in your program: './tag.ts'
|
|
127
108
|
nodeTypes: { Tag: { worker: 'tag', inline: true } },
|
|
128
109
|
predicates: ['isLowConfidence'],
|
|
129
110
|
};
|
|
130
|
-
//
|
|
131
|
-
// failed (`OR-R9`) — so it is guarded, as section 4's is. Unreachable here, because `ingest` is
|
|
132
|
-
// not optional; guarded anyway, because this is the form a consumer copies.
|
|
111
|
+
// `result` is `null` when an optional task failed, so a predicate guards it.
|
|
133
112
|
const isLowConfidence: Predicate = (ctx) => {
|
|
134
113
|
const r = ctx.outcome.result as { confidence?: number } | null;
|
|
135
114
|
return typeof r?.confidence === 'number' && r.confidence < 0.5;
|
|
136
115
|
};
|
|
137
116
|
|
|
138
|
-
// THE FLOW — a DECISION, a FAN-OUT, a JOIN. `ingest`
|
|
139
|
-
// otherwise
|
|
140
|
-
//
|
|
141
|
-
// the road the run takes when confidence is low — it never reaches the join, and the run completes.
|
|
117
|
+
// THE FLOW — a DECISION, a FAN-OUT, a JOIN. `ingest` goes to `review` when the predicate holds,
|
|
118
|
+
// otherwise to `split`. `split` runs two branches concurrently; `collect` waits for both and hands
|
|
119
|
+
// `publish` every branch's result, keyed by branch path.
|
|
142
120
|
const flow: FlowDefinition = {
|
|
143
121
|
flowId: 'decide', version: 1, entry: 'ingest',
|
|
144
122
|
nodes: [
|
|
@@ -154,44 +132,42 @@ const flow: FlowDefinition = {
|
|
|
154
132
|
],
|
|
155
133
|
};
|
|
156
134
|
|
|
157
|
-
// ONE runtime, TWO runs. Pools belong to the runtime and are shared
|
|
158
|
-
// `Driver` with its own fold, and the join policy defaults to `live-token` (ADR 0003).
|
|
135
|
+
// ONE runtime, TWO runs. Pools belong to the runtime and are shared; each run has its own state.
|
|
159
136
|
const runtime = createRuntime(registry, { predicates: { isLowConfidence } }, { base: import.meta.url });
|
|
160
|
-
// Each run has its OWN `done`, so two runs are two promises — `start` is called twice and the whole
|
|
161
|
-
// of the wait is `await`. Nothing here folds anything: the runtime keeps the fold (ADR 0058).
|
|
162
137
|
const run = (payload: unknown) => runtime.start(flow, payload).done;
|
|
163
138
|
const high = await run({ confidence: 0.9 });
|
|
164
139
|
const low = await run({ confidence: 0.2 });
|
|
165
140
|
await runtime.shutdown();
|
|
166
141
|
|
|
167
|
-
//
|
|
142
|
+
// Which nodes ran: the decision took a different edge each time.
|
|
168
143
|
const ran = (s: RunState) => Object.values(s.nodes).map((n) => n.nodeId).sort();
|
|
169
144
|
const resultOf = (s: RunState, id: string) => Object.values(s.nodes).find((n) => n.nodeId === id)?.result;
|
|
170
145
|
console.log(ran(high)); // [ 'classify', 'enrich', 'ingest', 'publish' ]
|
|
171
146
|
console.log(ran(low)); // [ 'ingest', 'review' ]
|
|
172
147
|
console.log(resultOf(high, 'publish')); // { '0': { confidence: 0.9, ingest: true, enrich: true }, '1': { …, classify: true }, publish: true }
|
|
173
148
|
|
|
149
|
+
// Verified by the docs gate:
|
|
174
150
|
if (high.outcome !== 'completed' || low.outcome !== 'completed') throw new Error(`outcomes ${high.outcome} / ${low.outcome}`);
|
|
175
151
|
if (ran(high).join() !== 'classify,enrich,ingest,publish') throw new Error(`high ran ${ran(high).join()}`);
|
|
176
152
|
if (ran(low).join() !== 'ingest,review') throw new Error(`low ran ${ran(low).join()}`);
|
|
177
|
-
// `publish` received BOTH branches' results, keyed by branch path (JP-R12), and each branch's value
|
|
178
|
-
// carries the whole road it came by. What this string pins is the branch-to-KEY MAPPING: the ASCENDING
|
|
179
|
-
// order that rule also fixes is not observable at depth 1, because "0" and "1" are integer-like keys
|
|
180
|
-
// and the language orders those ascending whatever the driver did. A nested region would show it.
|
|
181
153
|
const want = '{"0":{"confidence":0.9,"ingest":true,"enrich":true},"1":{"confidence":0.9,"ingest":true,"classify":true},"publish":true}';
|
|
182
154
|
if (JSON.stringify(resultOf(high, 'publish')) !== want) throw new Error(`publish received ${JSON.stringify(resultOf(high, 'publish'))}`);
|
|
183
155
|
```
|
|
184
156
|
|
|
185
157
|
## Checking a flow before it runs
|
|
186
158
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
159
|
+
Every worker declares JSON schemas for what it expects and what it returns, so a flow can be checked
|
|
160
|
+
before anything runs:
|
|
161
|
+
|
|
162
|
+
- `checkWiring` gives every arc of a flow a verdict from those declarations.
|
|
163
|
+
- `pathsOf` lists what a producer's output offers.
|
|
164
|
+
- `tryTransform` runs a candidate JSONata expression on the producer's declared example and checks
|
|
165
|
+
the result against the consumer's schema.
|
|
166
|
+
- `signatureOf` generates the TypeScript signature a worker is written against.
|
|
167
|
+
|
|
168
|
+
Below, a flow with a miswire is repaired in two tries, then run. The two workers are
|
|
193
169
|
[examples/getting-started/reader.ts](examples/getting-started/reader.ts) and
|
|
194
|
-
[summarise.ts](examples/getting-started/summarise.ts), whose schemas
|
|
170
|
+
[summarise.ts](examples/getting-started/summarise.ts), whose schemas deliberately do not match.
|
|
195
171
|
|
|
196
172
|
```ts
|
|
197
173
|
import { checkWiring, describePaths, pathsOf, signatureOf, tryTransform, type FlowDefinition } from 'fyflow-orchestration';
|
|
@@ -203,8 +179,7 @@ import { SESSION_BASE, SESSION_REGISTRY, SESSION_WORKERS } from '../../examples/
|
|
|
203
179
|
// and the languages a transform may be written in. `createRuntime` takes the whole document below.
|
|
204
180
|
const registry = { workers: SESSION_WORKERS, nodeTypes: SESSION_REGISTRY.nodeTypes, languages: { jsonata } };
|
|
205
181
|
|
|
206
|
-
// THE FLOW, as an author first writes it: read a batch, summarise it.
|
|
207
|
-
// `validateDefinition` has nothing to say about it, because nothing here is about the grammar.
|
|
182
|
+
// THE FLOW, as an author first writes it: read a batch, summarise it. Its grammar is valid.
|
|
208
183
|
const flow: FlowDefinition = {
|
|
209
184
|
flowId: 'session', version: 1, entry: 'read',
|
|
210
185
|
nodes: [
|
|
@@ -229,40 +204,39 @@ console.log(describePaths(pathsOf(SESSION_WORKERS.reader.outputSchema)));
|
|
|
229
204
|
// ? $.lines[] string
|
|
230
205
|
// $.source string
|
|
231
206
|
|
|
232
|
-
// STEP 3 — a first candidate
|
|
233
|
-
//
|
|
207
|
+
// STEP 3 — a first candidate, run on the producer's declared example and checked against the
|
|
208
|
+
// consumer's input schema.
|
|
234
209
|
const attempt = await tryTransform(registry, flow, 'sum', { lang: 'jsonata', expr: '{ "text": lines }' });
|
|
235
210
|
console.log(`${attempt[0]!.kind}: ${attempt[0]!.why}`);
|
|
236
211
|
// transform-fails: the transform's result on the example fails the consumer's inputSchema — $.text expected string, got array
|
|
237
212
|
if (attempt[0]!.kind !== 'transform-fails') throw new Error('an array is not a string, and the loop says which');
|
|
238
213
|
|
|
239
|
-
// STEP 4 —
|
|
240
|
-
//
|
|
241
|
-
// here; where a schema declares none, a generated WITNESS, and the verdict says so).
|
|
214
|
+
// STEP 4 — one edit, and the same loop. `transform-passes` is about the sample it names, which is
|
|
215
|
+
// the declared example here, or a generated one where a schema declares none.
|
|
242
216
|
const good = { lang: 'jsonata', expr: '{ "text": $join(lines, " ") }' };
|
|
243
217
|
const passed = await tryTransform(registry, flow, 'sum', good);
|
|
244
218
|
console.log(`${passed[0]!.kind} on the ${passed[0]!.sample!.source}: ${JSON.stringify(passed[0]!.sample!.result)}`);
|
|
245
219
|
if (passed[0]!.kind !== 'transform-passes') throw new Error(`expected the sample to pass, got ${passed[0]!.kind}`);
|
|
246
220
|
|
|
247
|
-
// STEP 5 — the signature the worker is written against, generated from the same
|
|
248
|
-
// `examples/getting-started/session-types.ts` is this text,
|
|
249
|
-
// it. A type holds the SHAPE: `words` is `number` here and its schema says `minimum: 0`.
|
|
221
|
+
// STEP 5 — the signature the worker is written against, generated from the same schemas.
|
|
222
|
+
// `examples/getting-started/session-types.ts` is this text, and `summarise.ts` imports it.
|
|
250
223
|
const signature = signatureOf('Summarise', SESSION_WORKERS.summarise);
|
|
251
224
|
console.log(signature.split('\n').filter((l) => l.startsWith('export')).join('\n'));
|
|
252
225
|
if (!signature.includes('"text": string;')) throw new Error('the required key is required in the type too');
|
|
253
226
|
|
|
254
|
-
// STEP 6 —
|
|
255
|
-
//
|
|
256
|
-
// naming the type. Check, then build.
|
|
227
|
+
// STEP 6 — only now the runtime. Check the wired flow against it, then start it; `start` refuses
|
|
228
|
+
// from that same check.
|
|
257
229
|
const wired: FlowDefinition = { ...flow, nodes: flow.nodes.map((n) => (n.id === 'sum' ? { ...n, transform: good } : n)) };
|
|
258
|
-
const runtime = createRuntime(SESSION_REGISTRY, { languages: { jsonata } }, { base: SESSION_BASE
|
|
230
|
+
const runtime = createRuntime(SESSION_REGISTRY, { languages: { jsonata } }, { base: SESSION_BASE });
|
|
231
|
+
const refused = runtime.check(wired);
|
|
232
|
+
if (refused.length !== 0) throw new Error(`the runtime refuses the wired flow: ${JSON.stringify(refused)}`);
|
|
233
|
+
const unknownLang: FlowDefinition = { ...flow, nodes: flow.nodes.map((n) => (n.id === 'sum' ? { ...n, transform: { lang: 'nope', expr: '$' } } : n)) };
|
|
234
|
+
if (!runtime.check(unknownLang).some((f) => f.rule === 'FD-V35' && f.at === 'sum')) throw new Error('a language the registry does not name is FD-V35, at check');
|
|
259
235
|
const state = await runtime.start(wired, { source: 'inbox' }).done;
|
|
260
236
|
await runtime.shutdown();
|
|
261
237
|
console.log(state.outcome, Object.values(state.nodes).find((n) => n.nodeId === 'sum')?.result); // completed { words: 3 }
|
|
262
238
|
|
|
263
|
-
//
|
|
264
|
-
// output example, and the producer's declared line count. A registry whose examples stop describing
|
|
265
|
-
// its workers is red here and in `test/definition/assist.test.ts`, which runs the same chain.
|
|
239
|
+
// Verified by the docs gate:
|
|
266
240
|
if (state.outcome !== 'completed') throw new Error(`expected completed, got ${state.outcome}`);
|
|
267
241
|
const got = Object.values(state.nodes).find((n) => n.nodeId === 'sum')?.result;
|
|
268
242
|
if (JSON.stringify(got) !== JSON.stringify(SESSION_WORKERS.summarise.outputSchema.examples[0])) {
|
|
@@ -277,21 +251,34 @@ if (after.counts.no !== 0) throw new Error('and the flow that ran has no unwired
|
|
|
277
251
|
|
|
278
252
|
## More examples
|
|
279
253
|
|
|
280
|
-
|
|
281
|
-
snippet is extracted and executed on every `npm run gate`, so none of it can drift from the code:
|
|
254
|
+
[AGENTS.md](AGENTS.md) is the full reference, and every snippet in it is tested:
|
|
282
255
|
|
|
283
256
|
| | |
|
|
284
257
|
|---|---|
|
|
285
|
-
|
|
|
258
|
+
| A task **cancelled** by its id — the run ends `cancelled` and refuses a resume | [AGENTS.md](AGENTS.md) section 6 |
|
|
259
|
+
| **Cancelling a run** — `driver.cancel`, the run `cancelling` until its last task has stopped | section 6 |
|
|
260
|
+
| **Pausing** a run and unpausing it — every dispatch held, then handed on in order | section 8 |
|
|
261
|
+
| A node that **waits for a person** by design — an `ask`, which keeps what reached it and holds the answer to a schema | section 7 |
|
|
262
|
+
| A task's **messages and progress**, each with its run, node and attempt as fields — `onTaskMessage`, `onTaskProgress` | section 8 |
|
|
263
|
+
| A **worker's own messages**, outside any task — `worker.message` on its pool, reached through `createScheduler` | section 8 |
|
|
264
|
+
| Reaching what reached the **run** from any node or edge — `needs: { invoice: "$payload" }` | section 3 |
|
|
265
|
+
| Reaching the item that opened a **map instance** from any node in it — `needs: { invoice: "$item" }` | section 3 |
|
|
266
|
+
| **Stopping** a run to resume it on another runtime — `handle.stop` | section 18 |
|
|
267
|
+
| Writing a run's **header first**, before its first event — `onHeader` | section 18 |
|
|
268
|
+
| A resumed task **keeps its retry budget** — the attempts it already made, handed on as `attemptsSpent` | section 18 |
|
|
269
|
+
| **Checking** a flow against a running runtime before starting it — `runtime.check` | section 12 |
|
|
270
|
+
| **Trying** a transform on a value you give, the entry node included | section 15 |
|
|
271
|
+
| Holding a **result** to the schema its worker declares | section 16 |
|
|
286
272
|
| Fanning out **per item** — a region inside a `map` region | section 17 |
|
|
287
273
|
| **Saving** a run and **resuming** it from the stored events | section 18 |
|
|
288
274
|
| What a node may **remember** across branches — a `gate` | section 19 |
|
|
275
|
+
| Routing with **no code** — the built-in `if` and `switch` gates | section 19 |
|
|
289
276
|
| Wiring a gate **before it runs** | section 20 |
|
|
290
277
|
|
|
291
|
-
Runnable programs
|
|
292
|
-
[examples/
|
|
293
|
-
[examples/
|
|
294
|
-
a named pool
|
|
278
|
+
Runnable programs are in [examples/](examples/). For a larger one, see
|
|
279
|
+
[examples/run-flow.ts](examples/run-flow.ts) running
|
|
280
|
+
[examples/flows/triage.flow.json](examples/flows/triage.flow.json): a custom resource group, a
|
|
281
|
+
node-type class, a named pool, a decision, a fan-out, a join and a transform.
|
|
295
282
|
|
|
296
283
|
## Running the tests
|
|
297
284
|
|
|
@@ -299,22 +286,19 @@ a named pool instance, a decision, a fan-out, a join and a declared transform on
|
|
|
299
286
|
npm install
|
|
300
287
|
npm test # the unit and routing suites
|
|
301
288
|
npm run test:l6 # real runs on a real scheduler
|
|
302
|
-
npm run gate #
|
|
289
|
+
npm run gate # lint, typecheck, checks, all suites, Node and Deno runs
|
|
303
290
|
```
|
|
304
291
|
|
|
305
|
-
`npm run gate` is the full ladder — lint, typecheck, the static checks, the suites in three runtimes,
|
|
306
|
-
and the real-scheduler runs in Node and Deno.
|
|
307
|
-
|
|
308
292
|
## Documentation
|
|
309
293
|
|
|
310
294
|
| | |
|
|
311
295
|
|---|---|
|
|
312
|
-
| [AGENTS.md](AGENTS.md) | the complete reference
|
|
296
|
+
| [AGENTS.md](AGENTS.md) | the complete API reference, with tested snippets |
|
|
313
297
|
| [doc/concept.md](doc/concept.md) | what this is and why it is shaped this way |
|
|
314
|
-
| [spec/](spec/) | the normative rules
|
|
315
|
-
| [doc/README.md](doc/README.md) |
|
|
316
|
-
| [CLAUDE.md](CLAUDE.md) |
|
|
298
|
+
| [spec/](spec/) | the normative rules: flow grammar, run state, join policies, task ids |
|
|
299
|
+
| [doc/README.md](doc/README.md) | for contributors: plans, work items, decisions, open questions |
|
|
300
|
+
| [CLAUDE.md](CLAUDE.md) | for an agent working on this library |
|
|
317
301
|
|
|
318
302
|
## Licence
|
|
319
303
|
|
|
320
|
-
MIT
|
|
304
|
+
MIT. See `LICENSE`.
|