@multiplier-labs/stepflow 0.3.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/.github/workflows/publish.yml +30 -0
- package/README.md +408 -0
- package/dist/chunk-FQCT3I2A.js +1512 -0
- package/dist/chunk-FQCT3I2A.js.map +1 -0
- package/dist/chunk-GXY66V7Q.js +111 -0
- package/dist/chunk-GXY66V7Q.js.map +1 -0
- package/dist/chunk-U5QGN3RT.js +715 -0
- package/dist/chunk-U5QGN3RT.js.map +1 -0
- package/dist/chunk-XCF4PLN7.js +575 -0
- package/dist/chunk-XCF4PLN7.js.map +1 -0
- package/dist/events/index.d.ts +346 -0
- package/dist/events/index.js +11 -0
- package/dist/events/index.js.map +1 -0
- package/dist/index-CPfGsmrp.d.ts +567 -0
- package/dist/index.d.ts +748 -0
- package/dist/index.js +1707 -0
- package/dist/index.js.map +1 -0
- package/dist/scheduler/index.d.ts +6 -0
- package/dist/scheduler/index.js +12 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/storage/index.d.ts +402 -0
- package/dist/storage/index.js +13 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/types-BVShq7zT.d.ts +150 -0
- package/dist/types-BlwFEGzj.d.ts +276 -0
- package/dist/types-DzkEqnQJ.d.ts +75 -0
- package/docs/github-packages-setup.md +97 -0
- package/docs/stepflow-api-reference.md +1662 -0
- package/package.json +85 -0
- package/src/core/engine.test.ts +1421 -0
- package/src/core/engine.ts +717 -0
- package/src/core/index.ts +7 -0
- package/src/core/orchestrator.ts +590 -0
- package/src/core/types.ts +242 -0
- package/src/events/index.ts +8 -0
- package/src/events/memory.test.ts +109 -0
- package/src/events/memory.ts +95 -0
- package/src/events/socketio.test.ts +428 -0
- package/src/events/socketio.ts +269 -0
- package/src/events/types.ts +121 -0
- package/src/events/webhook.test.ts +695 -0
- package/src/events/webhook.ts +591 -0
- package/src/index.ts +270 -0
- package/src/planning/index.ts +52 -0
- package/src/planning/planner.ts +601 -0
- package/src/planning/planning.test.ts +790 -0
- package/src/planning/registry.test.ts +445 -0
- package/src/planning/registry.ts +369 -0
- package/src/planning/types.ts +570 -0
- package/src/scheduler/cron.test.ts +480 -0
- package/src/scheduler/cron.ts +449 -0
- package/src/scheduler/index.ts +18 -0
- package/src/scheduler/postgres-persistence.test.ts +906 -0
- package/src/scheduler/postgres-persistence.ts +546 -0
- package/src/scheduler/sqlite-persistence.test.ts +235 -0
- package/src/scheduler/sqlite-persistence.ts +268 -0
- package/src/scheduler/types.ts +78 -0
- package/src/storage/index.ts +27 -0
- package/src/storage/memory.test.ts +399 -0
- package/src/storage/memory.ts +248 -0
- package/src/storage/postgres.ts +1550 -0
- package/src/storage/sqlite.test.ts +593 -0
- package/src/storage/sqlite.ts +662 -0
- package/src/storage/types.ts +368 -0
- package/src/utils/errors.test.ts +152 -0
- package/src/utils/errors.ts +191 -0
- package/src/utils/id.ts +21 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/logger.test.ts +127 -0
- package/src/utils/logger.ts +96 -0
- package/src/utils/postgres-deps.ts +63 -0
- package/src/utils/retry.test.ts +197 -0
- package/src/utils/retry.ts +121 -0
- package/tsconfig.json +21 -0
- package/tsup.config.ts +14 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [created]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version: '20'
|
|
18
|
+
registry-url: 'https://registry.npmjs.org'
|
|
19
|
+
scope: '@multiplier-labs'
|
|
20
|
+
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: npm ci
|
|
23
|
+
|
|
24
|
+
- name: Build
|
|
25
|
+
run: npm run build
|
|
26
|
+
|
|
27
|
+
- name: Publish
|
|
28
|
+
run: npm publish --access public
|
|
29
|
+
env:
|
|
30
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/README.md
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# Stepflow
|
|
2
|
+
|
|
3
|
+
A durable, type-safe workflow orchestration engine for Node.js with SQLite or PostgreSQL persistence, scheduling, and real-time events.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Durable Execution**: Steps are persisted to SQLite or PostgreSQL, allowing workflows to resume after crashes
|
|
8
|
+
- **Type-Safe**: Full TypeScript support with inferred types for inputs, outputs, and step results
|
|
9
|
+
- **Step Orchestration**: Sequential step execution with automatic retry and timeout support
|
|
10
|
+
- **Scheduling**: Cron-based scheduling and workflow completion triggers
|
|
11
|
+
- **Real-Time Events**: Socket.IO and webhook adapters for event streaming
|
|
12
|
+
- **Concurrency Control**: Limit concurrent workflow runs with priority queues
|
|
13
|
+
- **Multiple Storage Backends**: Choose SQLite for simplicity or PostgreSQL for distributed deployments
|
|
14
|
+
- **Planning System**: Rule-based recipe selection and dynamic plan generation
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
This package is published to GitHub Packages. See [docs/github-packages-setup.md](docs/github-packages-setup.md) for authentication setup.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @multiplier-labs/stepflow
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import Database from 'better-sqlite3';
|
|
28
|
+
import { WorkflowEngine, SQLiteStorageAdapter } from '@multiplier-labs/stepflow';
|
|
29
|
+
|
|
30
|
+
// Create engine with SQLite storage
|
|
31
|
+
const db = new Database('./workflows.db');
|
|
32
|
+
const engine = new WorkflowEngine({
|
|
33
|
+
storage: new SQLiteStorageAdapter({ db }),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Register a workflow
|
|
37
|
+
engine.registerWorkflow({
|
|
38
|
+
kind: 'order.process',
|
|
39
|
+
name: 'Process Order',
|
|
40
|
+
steps: [
|
|
41
|
+
{
|
|
42
|
+
key: 'validateOrder',
|
|
43
|
+
name: 'Validate Order',
|
|
44
|
+
handler: async (ctx) => {
|
|
45
|
+
return { valid: true, total: 99.99 };
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
key: 'processPayment',
|
|
50
|
+
name: 'Process Payment',
|
|
51
|
+
handler: async (ctx) => {
|
|
52
|
+
const { total } = ctx.results.validateOrder;
|
|
53
|
+
return { transactionId: 'txn-123', amount: total };
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
key: 'sendConfirmation',
|
|
58
|
+
name: 'Send Confirmation',
|
|
59
|
+
handler: async (ctx) => {
|
|
60
|
+
return { emailSent: true };
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Start a workflow run
|
|
67
|
+
const runId = await engine.startRun({
|
|
68
|
+
kind: 'order.process',
|
|
69
|
+
input: { orderId: 'order-123', items: ['item-1', 'item-2'] },
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Wait for completion
|
|
73
|
+
const result = await engine.waitForRun(runId);
|
|
74
|
+
console.log(result.status); // 'succeeded'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Core Concepts
|
|
78
|
+
|
|
79
|
+
### Workflows
|
|
80
|
+
|
|
81
|
+
A workflow is a series of steps that execute sequentially. Each step's result is persisted, allowing the workflow to resume from where it left off if interrupted.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
engine.registerWorkflow({
|
|
85
|
+
kind: 'my.workflow',
|
|
86
|
+
name: 'My Workflow',
|
|
87
|
+
steps: [
|
|
88
|
+
{
|
|
89
|
+
key: 'step1',
|
|
90
|
+
name: 'Step 1',
|
|
91
|
+
handler: async (ctx) => {
|
|
92
|
+
return { data: 'step1 result' };
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
key: 'step2',
|
|
97
|
+
name: 'Step 2',
|
|
98
|
+
handler: async (ctx) => {
|
|
99
|
+
// Access previous step results
|
|
100
|
+
const step1Result = ctx.results.step1;
|
|
101
|
+
return { combined: step1Result.data + ' plus step2' };
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Step Options
|
|
109
|
+
|
|
110
|
+
Steps support retry logic and timeouts:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
engine.registerWorkflow({
|
|
114
|
+
kind: 'resilient.workflow',
|
|
115
|
+
name: 'Resilient Workflow',
|
|
116
|
+
steps: [
|
|
117
|
+
{
|
|
118
|
+
key: 'fetchData',
|
|
119
|
+
name: 'Fetch Data',
|
|
120
|
+
handler: async (ctx) => {
|
|
121
|
+
const response = await fetch(ctx.input.url);
|
|
122
|
+
return response.json();
|
|
123
|
+
},
|
|
124
|
+
onError: 'retry',
|
|
125
|
+
maxRetries: 3,
|
|
126
|
+
retryDelay: 1000,
|
|
127
|
+
timeout: 30000,
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Workflow Timeouts
|
|
134
|
+
|
|
135
|
+
Set a maximum duration for entire workflow runs via engine settings:
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
const engine = new WorkflowEngine({
|
|
139
|
+
storage,
|
|
140
|
+
settings: {
|
|
141
|
+
defaultTimeout: 60000, // 1 minute default timeout for all runs
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Concurrency Control
|
|
147
|
+
|
|
148
|
+
Limit concurrent workflow executions with priority queues:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
const engine = new WorkflowEngine({
|
|
152
|
+
storage,
|
|
153
|
+
settings: {
|
|
154
|
+
maxConcurrency: 5, // Max 5 concurrent runs
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Higher priority runs execute first
|
|
159
|
+
await engine.startRun({
|
|
160
|
+
kind: 'urgent.workflow',
|
|
161
|
+
input: { data: 'urgent' },
|
|
162
|
+
priority: 10,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
await engine.startRun({
|
|
166
|
+
kind: 'normal.workflow',
|
|
167
|
+
input: { data: 'normal' },
|
|
168
|
+
priority: 1,
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Lifecycle
|
|
173
|
+
|
|
174
|
+
The engine supports explicit initialization and shutdown:
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
const engine = new WorkflowEngine({
|
|
178
|
+
storage: new PostgresStorageAdapter({
|
|
179
|
+
connectionString: 'postgresql://localhost:5432/myapp',
|
|
180
|
+
}),
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Initialize storage (required for PostgreSQL, creates tables)
|
|
184
|
+
await engine.initialize();
|
|
185
|
+
|
|
186
|
+
// ... use the engine ...
|
|
187
|
+
|
|
188
|
+
// Graceful shutdown (cancels active runs, closes connections)
|
|
189
|
+
await engine.shutdown();
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Storage
|
|
193
|
+
|
|
194
|
+
### In-Memory Storage
|
|
195
|
+
|
|
196
|
+
For testing and development:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import { MemoryStorageAdapter } from '@multiplier-labs/stepflow';
|
|
200
|
+
|
|
201
|
+
const storage = new MemoryStorageAdapter();
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### SQLite Storage
|
|
205
|
+
|
|
206
|
+
For single-process deployments:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import Database from 'better-sqlite3';
|
|
210
|
+
import { SQLiteStorageAdapter } from '@multiplier-labs/stepflow';
|
|
211
|
+
|
|
212
|
+
const db = new Database('./workflows.db'); // File path or ':memory:'
|
|
213
|
+
const storage = new SQLiteStorageAdapter({ db });
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### PostgreSQL Storage
|
|
217
|
+
|
|
218
|
+
For production use with distributed workers:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import { PostgresStorageAdapter } from '@multiplier-labs/stepflow';
|
|
222
|
+
|
|
223
|
+
// Option 1: Connection string
|
|
224
|
+
const storage = new PostgresStorageAdapter({
|
|
225
|
+
connectionString: 'postgresql://user:pass@localhost:5432/myapp',
|
|
226
|
+
schema: 'myapp', // Optional, defaults to 'public'
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Option 2: Share existing connection pool
|
|
230
|
+
import pg from 'pg';
|
|
231
|
+
|
|
232
|
+
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
|
|
233
|
+
const storage = new PostgresStorageAdapter({ pool });
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Note:** `pg` and `kysely` are optional peer dependencies. They are loaded dynamically at runtime only when `PostgresStorageAdapter` is initialized, so users of other storage backends are not affected.
|
|
237
|
+
|
|
238
|
+
PostgreSQL storage provides:
|
|
239
|
+
- **Atomic dequeue**: Safe concurrent processing with `FOR UPDATE SKIP LOCKED`
|
|
240
|
+
- **Connection pooling**: Share database connections with your application
|
|
241
|
+
- **Schema isolation**: Custom schema names for multi-tenant deployments
|
|
242
|
+
- **Stale workflow cleanup**: Automatic timeout handling
|
|
243
|
+
|
|
244
|
+
## Event Transports
|
|
245
|
+
|
|
246
|
+
### Memory Events
|
|
247
|
+
|
|
248
|
+
Simple in-memory event emitter:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import { MemoryEventTransport } from '@multiplier-labs/stepflow';
|
|
252
|
+
|
|
253
|
+
const events = new MemoryEventTransport();
|
|
254
|
+
const engine = new WorkflowEngine({ storage, events });
|
|
255
|
+
|
|
256
|
+
// Subscribe to events
|
|
257
|
+
events.subscribeAll((event) => {
|
|
258
|
+
console.log(`Event: ${event.eventType} for run ${event.runId}`);
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Socket.IO Events
|
|
263
|
+
|
|
264
|
+
Real-time events via Socket.IO:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
import { Server } from 'socket.io';
|
|
268
|
+
import { SocketIOEventTransport } from '@multiplier-labs/stepflow';
|
|
269
|
+
|
|
270
|
+
const io = new Server(httpServer);
|
|
271
|
+
const events = new SocketIOEventTransport({ io });
|
|
272
|
+
|
|
273
|
+
const engine = new WorkflowEngine({ storage, events });
|
|
274
|
+
|
|
275
|
+
// Set up client handlers with authorization
|
|
276
|
+
io.on('connection', (socket) => {
|
|
277
|
+
events.setupClientHandlers(socket, async (runId, socket) => {
|
|
278
|
+
return /* authorization check */;
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Client-side:
|
|
283
|
+
// socket.emit('workflow:subscribe', runId);
|
|
284
|
+
// socket.on('workflow:event', (event) => console.log(event));
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Webhook Events
|
|
288
|
+
|
|
289
|
+
POST events to HTTP endpoints:
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
import { WebhookEventTransport } from '@multiplier-labs/stepflow';
|
|
293
|
+
|
|
294
|
+
const events = new WebhookEventTransport({
|
|
295
|
+
endpoints: [
|
|
296
|
+
{
|
|
297
|
+
id: 'slack',
|
|
298
|
+
url: 'https://hooks.slack.com/...',
|
|
299
|
+
eventTypes: ['run.completed', 'run.failed'],
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
id: 'analytics',
|
|
303
|
+
url: 'https://api.analytics.com/events',
|
|
304
|
+
secret: 'webhook-secret', // HMAC-SHA256 signing
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const engine = new WorkflowEngine({ storage, events });
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Scheduling
|
|
313
|
+
|
|
314
|
+
### Cron Scheduler
|
|
315
|
+
|
|
316
|
+
Schedule workflows using cron expressions:
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
import Database from 'better-sqlite3';
|
|
320
|
+
import { CronScheduler, SQLiteSchedulePersistence, SQLiteStorageAdapter } from '@multiplier-labs/stepflow';
|
|
321
|
+
|
|
322
|
+
const db = new Database('./workflows.db');
|
|
323
|
+
const storage = new SQLiteStorageAdapter({ db });
|
|
324
|
+
const engine = new WorkflowEngine({ storage });
|
|
325
|
+
|
|
326
|
+
const schedulePersistence = new SQLiteSchedulePersistence({ db });
|
|
327
|
+
const scheduler = new CronScheduler({
|
|
328
|
+
engine,
|
|
329
|
+
persistence: schedulePersistence,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Schedule a workflow to run every hour
|
|
333
|
+
await scheduler.addSchedule({
|
|
334
|
+
id: 'hourly-cleanup',
|
|
335
|
+
workflowKind: 'cleanup.workflow',
|
|
336
|
+
triggerType: 'cron',
|
|
337
|
+
cronExpression: '0 * * * *', // Every hour
|
|
338
|
+
timezone: 'America/New_York',
|
|
339
|
+
input: { dryRun: false },
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Start the scheduler
|
|
343
|
+
await scheduler.start();
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Workflow Completion Triggers
|
|
347
|
+
|
|
348
|
+
Trigger workflows when other workflows complete:
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
await scheduler.addSchedule({
|
|
352
|
+
id: 'post-order-notification',
|
|
353
|
+
workflowKind: 'notification.send',
|
|
354
|
+
triggerType: 'workflow_completed',
|
|
355
|
+
triggerOnWorkflowKind: 'order.process',
|
|
356
|
+
triggerOnStatus: ['succeeded', 'failed'],
|
|
357
|
+
input: { template: 'order-status' },
|
|
358
|
+
});
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## Event Types
|
|
362
|
+
|
|
363
|
+
The engine emits the following event types:
|
|
364
|
+
|
|
365
|
+
- `run.created` - Workflow run created
|
|
366
|
+
- `run.queued` - Run queued (when at max concurrency)
|
|
367
|
+
- `run.dequeued` - Run dequeued and starting
|
|
368
|
+
- `run.started` - Run execution started
|
|
369
|
+
- `run.resumed` - Run resumed after interruption
|
|
370
|
+
- `run.completed` - Run completed successfully
|
|
371
|
+
- `run.failed` - Run failed with error
|
|
372
|
+
- `run.canceled` - Run was canceled
|
|
373
|
+
- `run.timeout` - Run exceeded timeout
|
|
374
|
+
- `step.started` - Step execution started
|
|
375
|
+
- `step.completed` - Step completed successfully
|
|
376
|
+
- `step.failed` - Step failed (may retry)
|
|
377
|
+
- `step.skipped` - Step skipped (already completed)
|
|
378
|
+
- `step.retry` - Step retrying after failure
|
|
379
|
+
|
|
380
|
+
## Planning System
|
|
381
|
+
|
|
382
|
+
Stepflow includes a rule-based planning system for dynamic workflow generation:
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
import {
|
|
386
|
+
RuleBasedPlanner,
|
|
387
|
+
MemoryRecipeRegistry,
|
|
388
|
+
MemoryStepHandlerRegistry,
|
|
389
|
+
createRegistry,
|
|
390
|
+
} from '@multiplier-labs/stepflow';
|
|
391
|
+
|
|
392
|
+
// Create registries for recipes and step handlers
|
|
393
|
+
const { recipes, handlers } = createRegistry();
|
|
394
|
+
|
|
395
|
+
// Register recipes and handlers, then create plans
|
|
396
|
+
const planner = new RuleBasedPlanner({ recipeRegistry: recipes, handlerRegistry: handlers });
|
|
397
|
+
const plan = await planner.plan('process-order', { orderId: '123' });
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
See the [API Reference](docs/stepflow-api-reference.md#planning-system) for full documentation on recipes, step handlers, and planner configuration.
|
|
401
|
+
|
|
402
|
+
## API Reference
|
|
403
|
+
|
|
404
|
+
For the complete API reference including all classes, interfaces, and configuration options, see [docs/stepflow-api-reference.md](docs/stepflow-api-reference.md).
|
|
405
|
+
|
|
406
|
+
## License
|
|
407
|
+
|
|
408
|
+
MIT
|