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 ADDED
@@ -0,0 +1,380 @@
1
+ # yaml-flow
2
+
3
+ A lightweight, isomorphic workflow engine with declarative YAML flows and pluggable persistence.
4
+
5
+ [![npm version](https://badge.fury.io/js/yaml-flow.svg)](https://www.npmjs.com/package/yaml-flow)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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