yaml-flow 1.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/README.md +380 -0
- package/dist/core/index.cjs +557 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +102 -0
- package/dist/core/index.d.ts +102 -0
- package/dist/core/index.js +549 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.cjs +742 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +731 -0
- package/dist/index.js.map +1 -0
- package/dist/stores/file.cjs +115 -0
- package/dist/stores/file.cjs.map +1 -0
- package/dist/stores/file.d.cts +36 -0
- package/dist/stores/file.d.ts +36 -0
- package/dist/stores/file.js +113 -0
- package/dist/stores/file.js.map +1 -0
- package/dist/stores/localStorage.cjs +77 -0
- package/dist/stores/localStorage.cjs.map +1 -0
- package/dist/stores/localStorage.d.cts +34 -0
- package/dist/stores/localStorage.d.ts +34 -0
- package/dist/stores/localStorage.js +75 -0
- package/dist/stores/localStorage.js.map +1 -0
- package/dist/stores/memory.cjs +48 -0
- package/dist/stores/memory.cjs.map +1 -0
- package/dist/stores/memory.d.cts +27 -0
- package/dist/stores/memory.d.ts +27 -0
- package/dist/stores/memory.js +46 -0
- package/dist/stores/memory.js.map +1 -0
- package/dist/types-BoWndaAJ.d.cts +237 -0
- package/dist/types-BoWndaAJ.d.ts +237 -0
- package/package.json +83 -0
- package/schema/flow.schema.json +159 -0
package/README.md
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
# yaml-flow
|
|
2
|
+
|
|
3
|
+
A lightweight, isomorphic workflow engine with declarative YAML flows and pluggable persistence.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/yaml-flow)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **Isomorphic** — Runs in both browser and Node.js
|
|
11
|
+
- **Declarative Flows** — Define workflows in YAML with JSON Schema validation
|
|
12
|
+
- **Pure Function Handlers** — Step handlers are simple `(input, context) => result` functions
|
|
13
|
+
- **Pluggable Storage** — Bring your own persistence (memory, localStorage, file, Redis, etc.)
|
|
14
|
+
- **Zero Core Dependencies** — Lightweight core, optional add-ons for YAML parsing
|
|
15
|
+
- **Resumable** — Pause and resume flows from persisted state
|
|
16
|
+
- **Circuit Breakers** — Prevent infinite loops with configurable limits
|
|
17
|
+
- **Retry Logic** — Built-in exponential backoff for failed steps
|
|
18
|
+
- **Event System** — Subscribe to flow events for UI updates
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install yaml-flow
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### 1. Define Your Flow (YAML)
|
|
29
|
+
|
|
30
|
+
```yaml
|
|
31
|
+
# my-flow.yaml
|
|
32
|
+
settings:
|
|
33
|
+
start_step: greet
|
|
34
|
+
max_total_steps: 10
|
|
35
|
+
|
|
36
|
+
steps:
|
|
37
|
+
greet:
|
|
38
|
+
produces_data:
|
|
39
|
+
- message
|
|
40
|
+
transitions:
|
|
41
|
+
success: done
|
|
42
|
+
failure: error
|
|
43
|
+
|
|
44
|
+
terminal_states:
|
|
45
|
+
done:
|
|
46
|
+
return_intent: success
|
|
47
|
+
return_artifacts: message
|
|
48
|
+
|
|
49
|
+
error:
|
|
50
|
+
return_intent: error
|
|
51
|
+
return_artifacts: false
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 2. Create Step Handlers
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { createEngine, loadFlow, MemoryStore } from 'yaml-flow';
|
|
58
|
+
|
|
59
|
+
const handlers = {
|
|
60
|
+
greet: async (input, ctx) => {
|
|
61
|
+
return {
|
|
62
|
+
result: 'success',
|
|
63
|
+
data: { message: `Hello, ${input.name}!` }
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Run the Flow
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const flow = await loadFlow('./my-flow.yaml');
|
|
73
|
+
const engine = createEngine(flow, handlers);
|
|
74
|
+
|
|
75
|
+
const result = await engine.run({ name: 'World' });
|
|
76
|
+
console.log(result.data.message); // "Hello, World!"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Flow Configuration
|
|
80
|
+
|
|
81
|
+
### Settings
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
settings:
|
|
85
|
+
start_step: my_step # Required: First step to execute
|
|
86
|
+
max_total_steps: 100 # Optional: Circuit breaker (default: 100)
|
|
87
|
+
timeout_ms: 60000 # Optional: Flow timeout in ms
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Steps
|
|
91
|
+
|
|
92
|
+
```yaml
|
|
93
|
+
steps:
|
|
94
|
+
my_step:
|
|
95
|
+
description: "What this step does"
|
|
96
|
+
expects_data: # Input data keys
|
|
97
|
+
- input_key
|
|
98
|
+
produces_data: # Output data keys
|
|
99
|
+
- output_key
|
|
100
|
+
transitions: # Result -> next step mapping
|
|
101
|
+
success: next_step
|
|
102
|
+
failure: error_step
|
|
103
|
+
retry: # Optional retry config
|
|
104
|
+
max_attempts: 3
|
|
105
|
+
delay_ms: 1000
|
|
106
|
+
backoff_multiplier: 2
|
|
107
|
+
circuit_breaker: # Optional loop protection
|
|
108
|
+
max_iterations: 5
|
|
109
|
+
on_open: fallback_step
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Terminal States
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
terminal_states:
|
|
116
|
+
success:
|
|
117
|
+
return_intent: "success"
|
|
118
|
+
return_artifacts:
|
|
119
|
+
- result_data
|
|
120
|
+
- metadata
|
|
121
|
+
|
|
122
|
+
error:
|
|
123
|
+
return_intent: "error"
|
|
124
|
+
return_artifacts: false
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Step Handlers
|
|
128
|
+
|
|
129
|
+
Step handlers are pure async functions:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
type StepHandler = (input: StepInput, context: StepContext) => Promise<StepResult>;
|
|
133
|
+
|
|
134
|
+
interface StepInput {
|
|
135
|
+
[key: string]: unknown; // Data from expects_data
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
interface StepContext {
|
|
139
|
+
runId: string; // Current run ID
|
|
140
|
+
stepName: string; // Current step name
|
|
141
|
+
components: object; // Injected dependencies
|
|
142
|
+
store: FlowStore; // Direct store access
|
|
143
|
+
signal?: AbortSignal; // Cancellation signal
|
|
144
|
+
emit: (event, data) => void; // Event emitter
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
interface StepResult {
|
|
148
|
+
result: string; // Transition key (e.g., 'success', 'failure')
|
|
149
|
+
data?: object; // Output data (matches produces_data)
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Example Handler
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const handlers = {
|
|
157
|
+
async processOrder(input, ctx) {
|
|
158
|
+
const { order } = input;
|
|
159
|
+
|
|
160
|
+
// Access injected database client
|
|
161
|
+
const db = ctx.components.db;
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
const savedOrder = await db.orders.save(order);
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
result: 'success',
|
|
168
|
+
data: {
|
|
169
|
+
order_id: savedOrder.id,
|
|
170
|
+
status: 'pending'
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
} catch (error) {
|
|
174
|
+
return {
|
|
175
|
+
result: 'failure',
|
|
176
|
+
data: { error: error.message }
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Storage Adapters
|
|
184
|
+
|
|
185
|
+
### Memory (Default)
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { MemoryStore } from 'yaml-flow';
|
|
189
|
+
|
|
190
|
+
const engine = createEngine(flow, handlers, {
|
|
191
|
+
store: new MemoryStore()
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### LocalStorage (Browser)
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import { LocalStorageStore } from 'yaml-flow/stores/localStorage';
|
|
199
|
+
|
|
200
|
+
const engine = createEngine(flow, handlers, {
|
|
201
|
+
store: new LocalStorageStore({ prefix: 'myapp' })
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### File System (Node.js)
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import { FileStore } from 'yaml-flow/stores/file';
|
|
209
|
+
|
|
210
|
+
const engine = createEngine(flow, handlers, {
|
|
211
|
+
store: new FileStore({ directory: './flow-data' })
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Custom Store
|
|
216
|
+
|
|
217
|
+
Implement the `FlowStore` interface:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
interface FlowStore {
|
|
221
|
+
saveRunState(runId: string, state: RunState): Promise<void>;
|
|
222
|
+
loadRunState(runId: string): Promise<RunState | null>;
|
|
223
|
+
deleteRunState(runId: string): Promise<void>;
|
|
224
|
+
setData(runId: string, key: string, value: unknown): Promise<void>;
|
|
225
|
+
getData(runId: string, key: string): Promise<unknown>;
|
|
226
|
+
getAllData(runId: string): Promise<Record<string, unknown>>;
|
|
227
|
+
clearData(runId: string): Promise<void>;
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Component Injection
|
|
232
|
+
|
|
233
|
+
Inject external dependencies (databases, API clients, etc.):
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
const engine = createEngine(flow, handlers, {
|
|
237
|
+
components: {
|
|
238
|
+
db: databaseClient,
|
|
239
|
+
api: httpClient,
|
|
240
|
+
cache: redisClient,
|
|
241
|
+
ai: openAIClient
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Access in handlers
|
|
246
|
+
const handlers = {
|
|
247
|
+
async fetchData(input, ctx) {
|
|
248
|
+
const result = await ctx.components.api.get('/data');
|
|
249
|
+
return { result: 'success', data: { fetched: result } };
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Events
|
|
255
|
+
|
|
256
|
+
Subscribe to flow events:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
const engine = createEngine(flow, handlers);
|
|
260
|
+
|
|
261
|
+
// Subscribe to events
|
|
262
|
+
const unsubscribe = engine.on('step:complete', (event) => {
|
|
263
|
+
console.log(`Step ${event.data.step} completed with ${event.data.result}`);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Available events:
|
|
267
|
+
// - flow:start, flow:complete, flow:error, flow:paused, flow:resumed
|
|
268
|
+
// - step:start, step:complete, step:error
|
|
269
|
+
// - transition
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Pause & Resume
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
// Start flow
|
|
276
|
+
const result = engine.run({ data: 'value' });
|
|
277
|
+
|
|
278
|
+
// Pause (from another context)
|
|
279
|
+
await engine.pause(runId);
|
|
280
|
+
|
|
281
|
+
// Later: resume
|
|
282
|
+
const resumed = await engine.resume(runId);
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Cancellation
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
const controller = new AbortController();
|
|
289
|
+
|
|
290
|
+
const engine = createEngine(flow, handlers, {
|
|
291
|
+
signal: controller.signal
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Start flow
|
|
295
|
+
const resultPromise = engine.run();
|
|
296
|
+
|
|
297
|
+
// Cancel
|
|
298
|
+
controller.abort();
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Browser Usage
|
|
302
|
+
|
|
303
|
+
### With Bundler (Vite, webpack, etc.)
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import { createEngine, MemoryStore } from 'yaml-flow';
|
|
307
|
+
|
|
308
|
+
// Flow as JSON (pre-parsed at build time)
|
|
309
|
+
const flow = {
|
|
310
|
+
settings: { start_step: 'start' },
|
|
311
|
+
steps: { start: { transitions: { success: 'done' } } },
|
|
312
|
+
terminal_states: { done: { return_intent: 'success' } }
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const engine = createEngine(flow, handlers);
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### From URL
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
import { loadFlowFromUrl, createEngine } from 'yaml-flow';
|
|
322
|
+
|
|
323
|
+
const flow = await loadFlowFromUrl('/flows/my-flow.json');
|
|
324
|
+
const engine = createEngine(flow, handlers);
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## JSON Schema
|
|
328
|
+
|
|
329
|
+
Use the included JSON Schema for IDE autocomplete:
|
|
330
|
+
|
|
331
|
+
```yaml
|
|
332
|
+
# yaml-language-server: $schema=node_modules/yaml-flow/schema/flow.schema.json
|
|
333
|
+
|
|
334
|
+
settings:
|
|
335
|
+
start_step: my_step
|
|
336
|
+
# ... IDE autocomplete works here
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## API Reference
|
|
340
|
+
|
|
341
|
+
### `createEngine(flow, handlers, options?)`
|
|
342
|
+
|
|
343
|
+
Create a new flow engine instance.
|
|
344
|
+
|
|
345
|
+
### `loadFlow(source)`
|
|
346
|
+
|
|
347
|
+
Load and validate a flow from file path, URL, or object.
|
|
348
|
+
|
|
349
|
+
### `FlowEngine.run(initialData?)`
|
|
350
|
+
|
|
351
|
+
Execute the flow from start.
|
|
352
|
+
|
|
353
|
+
### `FlowEngine.resume(runId)`
|
|
354
|
+
|
|
355
|
+
Resume a paused flow.
|
|
356
|
+
|
|
357
|
+
### `FlowEngine.pause(runId)`
|
|
358
|
+
|
|
359
|
+
Pause a running flow.
|
|
360
|
+
|
|
361
|
+
### `FlowEngine.on(event, listener)`
|
|
362
|
+
|
|
363
|
+
Subscribe to flow events.
|
|
364
|
+
|
|
365
|
+
### `FlowEngine.getStore()`
|
|
366
|
+
|
|
367
|
+
Get the store instance.
|
|
368
|
+
|
|
369
|
+
## Examples
|
|
370
|
+
|
|
371
|
+
See the [examples](./examples) directory:
|
|
372
|
+
|
|
373
|
+
- [Simple Greeting (Node.js)](./examples/node/simple-greeting.ts)
|
|
374
|
+
- [AI Conversation (Node.js)](./examples/node/ai-conversation.ts)
|
|
375
|
+
- [Browser Demo](./examples/browser/index.html)
|
|
376
|
+
- [Order Processing (Flow)](./examples/flows/order-processing.yaml)
|
|
377
|
+
|
|
378
|
+
## License
|
|
379
|
+
|
|
380
|
+
MIT
|