@db-lyon/flowkit 0.12.0 → 0.13.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.
@@ -1,430 +1,458 @@
1
- # API reference
2
-
3
- Complete reference for all public exports from `@db-lyon/flowkit`.
4
-
5
- ## Config
6
-
7
- *Import from `@db-lyon/flowkit` or `@db-lyon/flowkit/config`*
8
-
9
- ### `loadConfig(options)`
10
-
11
- Load, layer, and validate YAML configuration files.
12
-
13
- ```typescript
14
- function loadConfig<T extends z.ZodType>(
15
- options: LoadConfigOptions<T>,
16
- ): LoadedConfig<z.infer<T>>
17
- ```
18
-
19
- **`LoadConfigOptions<T>`**
20
-
21
- | Field | Type | Required | Description |
22
- |-------|------|----------|-------------|
23
- | `filename` | `string` | yes | Primary config filename (e.g., `'app.yml'`) |
24
- | `schema` | `z.ZodType` | yes | Zod schema applied after merging all layers |
25
- | `defaults` | `unknown` | no | Built-in defaults merged under the project file |
26
- | `env` | `string` | no | Environment name — loads `{base}.{env}.{ext}` overlay |
27
- | `envVar` | `string` | no | Env var to read environment name from when `env` is not passed |
28
- | `configDir` | `string` | no | Directory to search (default: `process.cwd()`) |
29
-
30
- **`LoadedConfig<T>`**
31
-
32
- | Field | Type | Description |
33
- |-------|------|-------------|
34
- | `config` | `T` | The validated, merged configuration object |
35
- | `configDir` | `string` | The directory the config was loaded from |
36
-
37
- ---
38
-
39
- ### `findConfigFile(filename, startDir?)`
40
-
41
- Walk up parent directories looking for a file by name.
42
-
43
- ```typescript
44
- function findConfigFile(filename: string, startDir?: string): string
45
- ```
46
-
47
- Returns the absolute path. Throws if not found.
48
-
49
- ---
50
-
51
- ### `loadRawYaml(filePath)`
52
-
53
- Parse a YAML file and return the raw result (no schema validation).
54
-
55
- ```typescript
56
- function loadRawYaml(filePath: string): unknown
57
- ```
58
-
59
- ---
60
-
61
- ### `deepMerge(base, override)`
62
-
63
- Recursively merge two values. Objects merge key-by-key, arrays replace (unless `__merge: 'append'`), scalars override, `null` nullifies, `undefined` is a no-op.
64
-
65
- ```typescript
66
- function deepMerge(base: unknown, override: unknown): unknown
67
- ```
68
-
69
- ---
70
-
71
- ### Zod schemas
72
-
73
- | Schema | Validates |
74
- |--------|-----------|
75
- | `TaskOptionsSchema` | `Record<string, unknown>` |
76
- | `TaskDefinitionSchema` | Task definition object |
77
- | `FlowStepSchema` | Single flow step (task xor flow) |
78
- | `FlowDefinitionSchema` | Flow with description and steps |
79
- | `EngineConfigSchema` | Top-level config with `tasks` and `flows` |
80
-
81
- ---
82
-
83
- ### Config types
84
-
85
- ```typescript
86
- type TaskOptions = Record<string, unknown>;
87
-
88
- type TaskDefinition = {
89
- class_path: string;
90
- description?: string;
91
- group?: string;
92
- options: TaskOptions; // defaults to {}
93
- };
94
-
95
- type FlowStep = {
96
- task?: string;
97
- flow?: string;
98
- options?: TaskOptions;
99
- };
100
-
101
- type FlowDefinition = {
102
- description: string;
103
- steps: Record<string, FlowStep>;
104
- };
105
-
106
- type EngineConfig = {
107
- tasks: Record<string, TaskDefinition>;
108
- flows: Record<string, FlowDefinition>;
109
- };
110
- ```
111
-
112
- ---
113
-
114
- ## Task
115
-
116
- *Import from `@db-lyon/flowkit` or `@db-lyon/flowkit/task`*
117
-
118
- ### `BaseTask<TOpts>`
119
-
120
- Abstract base class for all tasks.
121
-
122
- ```typescript
123
- abstract class BaseTask<TOpts = Record<string, unknown>> {
124
- protected ctx: TaskContext;
125
- protected options: TOpts;
126
- protected logger: Logger;
127
-
128
- constructor(ctx: TaskContext, options: TOpts);
129
-
130
- abstract get taskName(): string;
131
- abstract execute(): Promise<TaskResult>;
132
- protected validate(): void;
133
- async run(): Promise<TaskResult>;
134
- }
135
- ```
136
-
137
- | Method | Description |
138
- |--------|-------------|
139
- | `taskName` | (getter) Human-readable name for logging |
140
- | `execute()` | Perform the task's work. Return a `TaskResult`. |
141
- | `validate()` | Optional. Called before `execute()`. Throw to abort. |
142
- | `run()` | Lifecycle wrapper: validate → execute → catch errors → add duration. Called by the flow runner. |
143
-
144
- ---
145
-
146
- ### `TaskContext`
147
-
148
- ```typescript
149
- interface TaskContext {
150
- logger?: Logger;
151
- [key: string]: unknown;
152
- }
153
- ```
154
-
155
- Shared context passed to every task. Add any properties you need (database connections, API clients, etc.).
156
-
157
- ---
158
-
159
- ### `TaskResult`
160
-
161
- ```typescript
162
- interface TaskResult {
163
- success: boolean;
164
- data?: Record<string, unknown>;
165
- error?: Error;
166
- duration?: number; // milliseconds, set by run()
167
- }
168
- ```
169
-
170
- ---
171
-
172
- ### `ShellTask`
173
-
174
- Built-in task that executes shell commands via `execSync`.
175
-
176
- ```typescript
177
- class ShellTask extends BaseTask<ShellTaskOptions> {
178
- get taskName(): string; // "shell:{command}"
179
- protected validate(): void; // requires command
180
- async execute(): Promise<TaskResult>;
181
- }
182
- ```
183
-
184
- **`ShellTaskOptions`**
185
-
186
- | Field | Type | Default | Description |
187
- |-------|------|---------|-------------|
188
- | `command` | `string` | (required) | Shell command to execute |
189
- | `cwd` | `string` | `undefined` | Working directory |
190
- | `timeout` | `number` | `300000` | Timeout in milliseconds (5 min) |
191
-
192
- **Success result:** `data.output` contains trimmed stdout.
193
-
194
- **Failure result:** `data.exitCode`, `data.stderr`, `data.stdout`.
195
-
196
- ---
197
-
198
- ### `TaskRegistry`
199
-
200
- Registry that maps names and class paths to task constructors.
201
-
202
- ```typescript
203
- class TaskRegistry {
204
- register(name: string, ctor: TaskConstructor): this;
205
- registerClassPath(classPath: string, ctor: TaskConstructor): this;
206
- registerAll(entries: Record<string, TaskConstructor>): this;
207
- registerClassPaths(entries: Record<string, TaskConstructor>): this;
208
- async resolve(classPathOrName: string): Promise<TaskConstructor>;
209
- async create(
210
- classPathOrName: string,
211
- ctx: TaskContext,
212
- options: Record<string, unknown>,
213
- ): Promise<BaseTask>;
214
- listRegistered(): string[];
215
- }
216
- ```
217
-
218
- | Method | Description |
219
- |--------|-------------|
220
- | `register(name, ctor)` | Register by short name |
221
- | `registerClassPath(path, ctor)` | Register by dotted class path |
222
- | `registerAll(entries)` | Bulk register by short name |
223
- | `registerClassPaths(entries)` | Bulk register by class path |
224
- | `resolve(nameOrPath)` | Look up a constructor. Falls back to dynamic filesystem import. |
225
- | `create(nameOrPath, ctx, opts)` | Resolve + instantiate in one call |
226
- | `listRegistered()` | Return all registered names and class paths |
227
-
228
- **`TaskConstructor`**
229
-
230
- ```typescript
231
- type TaskConstructor = new (
232
- ctx: TaskContext,
233
- options: Record<string, unknown>,
234
- ) => BaseTask;
235
- ```
236
-
237
- ---
238
-
239
- ## Flow
240
-
241
- *Import from `@db-lyon/flowkit` or `@db-lyon/flowkit/flow`*
242
-
243
- ### `FlowRunner`
244
-
245
- Orchestration engine that executes flows.
246
-
247
- ```typescript
248
- class FlowRunner {
249
- constructor(config: FlowRunnerConfig);
250
- async run(options: FlowRunOptions): Promise<FlowRunResult>;
251
- resolveExecutionPlan(
252
- flow: FlowDefinition,
253
- skipSet: Set<string>,
254
- ): PlanStep[];
255
- }
256
- ```
257
-
258
- ---
259
-
260
- ### `FlowRunnerConfig`
261
-
262
- ```typescript
263
- interface FlowRunnerConfig {
264
- tasks: Record<string, TaskDefinition>;
265
- flows: Record<string, FlowDefinition>;
266
- registry: TaskRegistry;
267
- context: TaskContext;
268
- hooks?: FlowRunnerHooks;
269
- logger?: Logger;
270
- }
271
- ```
272
-
273
- ---
274
-
275
- ### `FlowRunOptions`
276
-
277
- ```typescript
278
- interface FlowRunOptions {
279
- flowName: string; // name of the flow to execute
280
- skip?: string[]; // task names or step numbers to skip
281
- plan?: boolean; // return plan without executing
282
- }
283
- ```
284
-
285
- ---
286
-
287
- ### `FlowRunResult`
288
-
289
- ```typescript
290
- interface FlowRunResult {
291
- success: boolean;
292
- steps: FlowStepResult[];
293
- duration: number; // total milliseconds
294
- error?: Error; // first error that caused failure
295
- }
296
- ```
297
-
298
- ---
299
-
300
- ### `FlowStepResult`
301
-
302
- ```typescript
303
- interface FlowStepResult {
304
- stepNumber: number;
305
- type: 'task' | 'flow';
306
- name: string;
307
- result?: TaskResult;
308
- skipped: boolean;
309
- duration: number; // milliseconds
310
- }
311
- ```
312
-
313
- ---
314
-
315
- ### `PlanStep`
316
-
317
- Represents a step in the execution plan (returned by plan mode or passed to hooks).
318
-
319
- ```typescript
320
- interface PlanStep {
321
- stepNumber: number;
322
- type: 'task' | 'flow';
323
- name: string;
324
- skipped: boolean;
325
- options?: Record<string, unknown>;
326
- }
327
- ```
328
-
329
- ---
330
-
331
- ### `FlowRunnerHooks`
332
-
333
- ```typescript
334
- interface FlowRunnerHooks {
335
- beforeRun?(flowName: string, plan: PlanStep[]): Promise<void>;
336
- afterRun?(result: FlowRunResult): Promise<void>;
337
- beforeStep?(step: PlanStep): Promise<void>;
338
- afterStep?(step: PlanStep, result: FlowStepResult): Promise<void>;
339
- onStepError?(
340
- step: PlanStep,
341
- error: Error,
342
- completed: FlowStepResult[],
343
- ): Promise<void>;
344
- }
345
- ```
346
-
347
- | Hook | Fires | Scope |
348
- |------|-------|-------|
349
- | `beforeRun` | Once before execution starts | Top-level flow only |
350
- | `afterRun` | Once after execution completes | Top-level flow only |
351
- | `beforeStep` | Before each step executes | All steps (including nested) |
352
- | `afterStep` | After each step completes | All steps (including nested) |
353
- | `onStepError` | When a step fails | All steps (including nested) |
354
-
355
- ---
356
-
357
- ## DAG
358
-
359
- *Import from `@db-lyon/flowkit` or `@db-lyon/flowkit/dag`*
360
-
361
- ### `topologicalSort(nodes)`
362
-
363
- Sort a directed acyclic graph in dependency order (dependencies first).
364
-
365
- ```typescript
366
- function topologicalSort<T>(nodes: DagNode<T>[]): DagNode<T>[]
367
- ```
368
-
369
- Throws `CircularDependencyError` if the graph has cycles. Throws `MissingDependencyError` if a node references a dependency that doesn't exist.
370
-
371
- ---
372
-
373
- ### `DagNode<T>`
374
-
375
- ```typescript
376
- interface DagNode<T = unknown> {
377
- id: string;
378
- dependencies: string[];
379
- data: T;
380
- }
381
- ```
382
-
383
- ---
384
-
385
- ### `CircularDependencyError`
386
-
387
- ```typescript
388
- class CircularDependencyError extends Error {
389
- cycle: string[]; // e.g., ['a', 'b', 'c', 'a']
390
- }
391
- ```
392
-
393
- ---
394
-
395
- ### `MissingDependencyError`
396
-
397
- ```typescript
398
- class MissingDependencyError extends Error {
399
- nodeId: string; // the node that has the bad dependency
400
- missingDep: string; // the dependency that doesn't exist
401
- }
402
- ```
403
-
404
- ---
405
-
406
- ## Logger
407
-
408
- *Import from `@db-lyon/flowkit`*
409
-
410
- ### `Logger` interface
411
-
412
- ```typescript
413
- interface Logger {
414
- debug(...args: unknown[]): void;
415
- info(...args: unknown[]): void;
416
- warn(...args: unknown[]): void;
417
- error(...args: unknown[]): void;
418
- child(bindings: Record<string, unknown>): Logger;
419
- }
420
- ```
421
-
422
- Compatible with pino, winston, and similar structured loggers.
423
-
424
- ### `noopLogger`
425
-
426
- A silent logger that discards all output. Used as the default when no logger is provided.
427
-
428
- ```typescript
429
- const noopLogger: Logger;
430
- ```
1
+ # API reference
2
+
3
+ Complete reference for all public exports from `@db-lyon/flowkit`.
4
+
5
+ ## Config
6
+
7
+ *Import from `@db-lyon/flowkit` or `@db-lyon/flowkit/config`*
8
+
9
+ ### `loadConfig(options)`
10
+
11
+ Load, layer, and validate YAML configuration files.
12
+
13
+ ```typescript
14
+ function loadConfig<T extends z.ZodType>(
15
+ options: LoadConfigOptions<T>,
16
+ ): LoadedConfig<z.infer<T>>
17
+ ```
18
+
19
+ **`LoadConfigOptions<T>`**
20
+
21
+ | Field | Type | Required | Description |
22
+ |-------|------|----------|-------------|
23
+ | `filename` | `string` | yes | Primary config filename (e.g., `'app.yml'`) |
24
+ | `schema` | `z.ZodType` | yes | Zod schema applied after merging all layers |
25
+ | `defaults` | `unknown` | no | Built-in defaults merged under the project file |
26
+ | `env` | `string` | no | Environment name — loads `{base}.{env}.{ext}` overlay |
27
+ | `envVar` | `string` | no | Env var to read environment name from when `env` is not passed |
28
+ | `configDir` | `string` | no | Directory to search (default: `process.cwd()`) |
29
+
30
+ **`LoadedConfig<T>`**
31
+
32
+ | Field | Type | Description |
33
+ |-------|------|-------------|
34
+ | `config` | `T` | The validated, merged configuration object |
35
+ | `configDir` | `string` | The directory the config was loaded from |
36
+
37
+ ---
38
+
39
+ ### `findConfigFile(filename, startDir?)`
40
+
41
+ Walk up parent directories looking for a file by name.
42
+
43
+ ```typescript
44
+ function findConfigFile(filename: string, startDir?: string): string
45
+ ```
46
+
47
+ Returns the absolute path. Throws if not found.
48
+
49
+ ---
50
+
51
+ ### `loadRawYaml(filePath)`
52
+
53
+ Parse a YAML file and return the raw result (no schema validation).
54
+
55
+ ```typescript
56
+ function loadRawYaml(filePath: string): unknown
57
+ ```
58
+
59
+ ---
60
+
61
+ ### `deepMerge(base, override)`
62
+
63
+ Recursively merge two values. Objects merge key-by-key, arrays replace (unless `__merge: 'append'`), scalars override, `null` nullifies, `undefined` is a no-op.
64
+
65
+ ```typescript
66
+ function deepMerge(base: unknown, override: unknown): unknown
67
+ ```
68
+
69
+ ---
70
+
71
+ ### Zod schemas
72
+
73
+ | Schema | Validates |
74
+ |--------|-----------|
75
+ | `TaskOptionsSchema` | `Record<string, unknown>` |
76
+ | `TaskDefinitionSchema` | Task definition object |
77
+ | `FlowStepSchema` | Single flow step (task xor flow) |
78
+ | `FlowDefinitionSchema` | Flow with description and steps |
79
+ | `EngineConfigSchema` | Top-level config with `tasks` and `flows` |
80
+
81
+ ---
82
+
83
+ ### Config types
84
+
85
+ ```typescript
86
+ type TaskOptions = Record<string, unknown>;
87
+
88
+ type TaskDefinition = {
89
+ class_path: string;
90
+ description?: string;
91
+ group?: string;
92
+ options: TaskOptions; // defaults to {}
93
+ };
94
+
95
+ type FlowStep = {
96
+ task?: string;
97
+ flow?: string;
98
+ options?: TaskOptions;
99
+ };
100
+
101
+ type FlowDefinition = {
102
+ description: string;
103
+ steps: Record<string, FlowStep>;
104
+ };
105
+
106
+ type EngineConfig = {
107
+ tasks: Record<string, TaskDefinition>;
108
+ flows: Record<string, FlowDefinition>;
109
+ };
110
+ ```
111
+
112
+ ---
113
+
114
+ ## Task
115
+
116
+ *Import from `@db-lyon/flowkit` or `@db-lyon/flowkit/task`*
117
+
118
+ ### `BaseTask<TOpts>`
119
+
120
+ Abstract base class for all tasks.
121
+
122
+ ```typescript
123
+ abstract class BaseTask<TOpts = Record<string, unknown>> {
124
+ protected ctx: TaskContext;
125
+ protected options: TOpts;
126
+ protected logger: Logger;
127
+
128
+ constructor(ctx: TaskContext, options: TOpts);
129
+
130
+ abstract get taskName(): string;
131
+ abstract execute(): Promise<TaskResult>;
132
+ protected validate(): void;
133
+ async run(): Promise<TaskResult>;
134
+ }
135
+ ```
136
+
137
+ | Method | Description |
138
+ |--------|-------------|
139
+ | `taskName` | (getter) Human-readable name for logging |
140
+ | `execute()` | Perform the task's work. Return a `TaskResult`. |
141
+ | `validate()` | Optional. Called before `execute()`. Throw to abort. |
142
+ | `run()` | Lifecycle wrapper: validate → execute → catch errors → add duration. Called by the flow runner. |
143
+
144
+ ---
145
+
146
+ ### `TaskContext`
147
+
148
+ ```typescript
149
+ interface TaskContext {
150
+ logger?: Logger;
151
+ [key: string]: unknown;
152
+ }
153
+ ```
154
+
155
+ Shared context passed to every task. Add any properties you need (database connections, API clients, etc.).
156
+
157
+ ---
158
+
159
+ ### `TaskResult`
160
+
161
+ ```typescript
162
+ interface TaskResult {
163
+ success: boolean;
164
+ data?: Record<string, unknown>;
165
+ error?: Error;
166
+ duration?: number; // milliseconds, set by run()
167
+ }
168
+ ```
169
+
170
+ ---
171
+
172
+ ### `ShellTask`
173
+
174
+ Built-in task that executes shell commands through the platform shell with
175
+ streamed stdout and stderr.
176
+
177
+ ```typescript
178
+ class ShellTask extends BaseTask<ShellTaskOptions> {
179
+ get taskName(): string; // "shell:{command}"
180
+ protected validate(): void; // requires command
181
+ async execute(): Promise<TaskResult>;
182
+ }
183
+ ```
184
+
185
+ **`ShellTaskOptions`**
186
+
187
+ | Field | Type | Default | Description |
188
+ |-------|------|---------|-------------|
189
+ | `command` | `string` | (required) | Shell command to execute |
190
+ | `cwd` | `string` | `undefined` | Working directory |
191
+ | `timeout` | `number` | `300000` | Timeout in milliseconds (5 min) |
192
+ | `signal` | `AbortSignal` | `undefined` | Cancels this individual programmatic invocation; not representable in YAML |
193
+
194
+ **Success result:** `data.output` contains trimmed stdout.
195
+
196
+ **Failure result:** `data.exitCode`, `data.stderr`, `data.stdout`.
197
+
198
+ When `signal` is already aborted, no shell process is launched and the task
199
+ returns the normal failed result with `Shell command cancelled`. An abort during
200
+ execution requests termination of the spawned shell process. On Windows,
201
+ Flowkit makes a best-effort `taskkill /T /F` request for that invocation's
202
+ shell PID and falls back to direct shell termination only if that request
203
+ fails or the three-second terminal deadline expires. On POSIX,
204
+ signal-bearing invocations run in a dedicated process group. Flowkit requests
205
+ `SIGTERM`, allows 250ms for cooperative cleanup, and then escalates the group
206
+ to `SIGKILL`. Neither approach guarantees termination of descendants that have
207
+ escaped the managed process tree or group; callers must not treat the returned
208
+ result as proof of complete descendant termination.
209
+
210
+ For signal-bearing invocations, Flowkit waits for the spawned shell to close
211
+ after requesting termination. If the operating system does not report closure
212
+ within one second on POSIX or three seconds on Windows, it returns the
213
+ cancellation or timeout result with the output captured so far; that bounded
214
+ fallback requests force termination and releases Flowkit's Node handles for
215
+ the root shell and any Windows `taskkill` helper, but is not confirmation that
216
+ either process, or any escaped descendant, has
217
+ exited. POSIX
218
+ signal-bearing invocations are in a separate process group, so terminal Ctrl+C
219
+ does not reach them automatically; cancel through the supplied `AbortSignal`.
220
+
221
+ Trailing stdout and stderr fragments are captured once. This corrects the
222
+ duplicate final-partial-line output in earlier releases.
223
+
224
+ ---
225
+
226
+ ### `TaskRegistry`
227
+
228
+ Registry that maps names and class paths to task constructors.
229
+
230
+ ```typescript
231
+ class TaskRegistry {
232
+ register(name: string, ctor: TaskConstructor): this;
233
+ registerClassPath(classPath: string, ctor: TaskConstructor): this;
234
+ registerAll(entries: Record<string, TaskConstructor>): this;
235
+ registerClassPaths(entries: Record<string, TaskConstructor>): this;
236
+ async resolve(classPathOrName: string): Promise<TaskConstructor>;
237
+ async create(
238
+ classPathOrName: string,
239
+ ctx: TaskContext,
240
+ options: Record<string, unknown>,
241
+ ): Promise<BaseTask>;
242
+ listRegistered(): string[];
243
+ }
244
+ ```
245
+
246
+ | Method | Description |
247
+ |--------|-------------|
248
+ | `register(name, ctor)` | Register by short name |
249
+ | `registerClassPath(path, ctor)` | Register by dotted class path |
250
+ | `registerAll(entries)` | Bulk register by short name |
251
+ | `registerClassPaths(entries)` | Bulk register by class path |
252
+ | `resolve(nameOrPath)` | Look up a constructor. Falls back to dynamic filesystem import. |
253
+ | `create(nameOrPath, ctx, opts)` | Resolve + instantiate in one call |
254
+ | `listRegistered()` | Return all registered names and class paths |
255
+
256
+ **`TaskConstructor`**
257
+
258
+ ```typescript
259
+ type TaskConstructor = new (
260
+ ctx: TaskContext,
261
+ options: Record<string, unknown>,
262
+ ) => BaseTask;
263
+ ```
264
+
265
+ ---
266
+
267
+ ## Flow
268
+
269
+ *Import from `@db-lyon/flowkit` or `@db-lyon/flowkit/flow`*
270
+
271
+ ### `FlowRunner`
272
+
273
+ Orchestration engine that executes flows.
274
+
275
+ ```typescript
276
+ class FlowRunner {
277
+ constructor(config: FlowRunnerConfig);
278
+ async run(options: FlowRunOptions): Promise<FlowRunResult>;
279
+ resolveExecutionPlan(
280
+ flow: FlowDefinition,
281
+ skipSet: Set<string>,
282
+ ): PlanStep[];
283
+ }
284
+ ```
285
+
286
+ ---
287
+
288
+ ### `FlowRunnerConfig`
289
+
290
+ ```typescript
291
+ interface FlowRunnerConfig {
292
+ tasks: Record<string, TaskDefinition>;
293
+ flows: Record<string, FlowDefinition>;
294
+ registry: TaskRegistry;
295
+ context: TaskContext;
296
+ hooks?: FlowRunnerHooks;
297
+ logger?: Logger;
298
+ }
299
+ ```
300
+
301
+ ---
302
+
303
+ ### `FlowRunOptions`
304
+
305
+ ```typescript
306
+ interface FlowRunOptions {
307
+ flowName: string; // name of the flow to execute
308
+ skip?: string[]; // task names or step numbers to skip
309
+ plan?: boolean; // return plan without executing
310
+ }
311
+ ```
312
+
313
+ ---
314
+
315
+ ### `FlowRunResult`
316
+
317
+ ```typescript
318
+ interface FlowRunResult {
319
+ success: boolean;
320
+ steps: FlowStepResult[];
321
+ duration: number; // total milliseconds
322
+ error?: Error; // first error that caused failure
323
+ }
324
+ ```
325
+
326
+ ---
327
+
328
+ ### `FlowStepResult`
329
+
330
+ ```typescript
331
+ interface FlowStepResult {
332
+ stepNumber: number;
333
+ type: 'task' | 'flow';
334
+ name: string;
335
+ result?: TaskResult;
336
+ skipped: boolean;
337
+ duration: number; // milliseconds
338
+ }
339
+ ```
340
+
341
+ ---
342
+
343
+ ### `PlanStep`
344
+
345
+ Represents a step in the execution plan (returned by plan mode or passed to hooks).
346
+
347
+ ```typescript
348
+ interface PlanStep {
349
+ stepNumber: number;
350
+ type: 'task' | 'flow';
351
+ name: string;
352
+ skipped: boolean;
353
+ options?: Record<string, unknown>;
354
+ }
355
+ ```
356
+
357
+ ---
358
+
359
+ ### `FlowRunnerHooks`
360
+
361
+ ```typescript
362
+ interface FlowRunnerHooks {
363
+ beforeRun?(flowName: string, plan: PlanStep[]): Promise<void>;
364
+ afterRun?(result: FlowRunResult): Promise<void>;
365
+ beforeStep?(step: PlanStep): Promise<void>;
366
+ afterStep?(step: PlanStep, result: FlowStepResult): Promise<void>;
367
+ onStepError?(
368
+ step: PlanStep,
369
+ error: Error,
370
+ completed: FlowStepResult[],
371
+ ): Promise<void>;
372
+ }
373
+ ```
374
+
375
+ | Hook | Fires | Scope |
376
+ |------|-------|-------|
377
+ | `beforeRun` | Once before execution starts | Top-level flow only |
378
+ | `afterRun` | Once after execution completes | Top-level flow only |
379
+ | `beforeStep` | Before each step executes | All steps (including nested) |
380
+ | `afterStep` | After each step completes | All steps (including nested) |
381
+ | `onStepError` | When a step fails | All steps (including nested) |
382
+
383
+ ---
384
+
385
+ ## DAG
386
+
387
+ *Import from `@db-lyon/flowkit` or `@db-lyon/flowkit/dag`*
388
+
389
+ ### `topologicalSort(nodes)`
390
+
391
+ Sort a directed acyclic graph in dependency order (dependencies first).
392
+
393
+ ```typescript
394
+ function topologicalSort<T>(nodes: DagNode<T>[]): DagNode<T>[]
395
+ ```
396
+
397
+ Throws `CircularDependencyError` if the graph has cycles. Throws `MissingDependencyError` if a node references a dependency that doesn't exist.
398
+
399
+ ---
400
+
401
+ ### `DagNode<T>`
402
+
403
+ ```typescript
404
+ interface DagNode<T = unknown> {
405
+ id: string;
406
+ dependencies: string[];
407
+ data: T;
408
+ }
409
+ ```
410
+
411
+ ---
412
+
413
+ ### `CircularDependencyError`
414
+
415
+ ```typescript
416
+ class CircularDependencyError extends Error {
417
+ cycle: string[]; // e.g., ['a', 'b', 'c', 'a']
418
+ }
419
+ ```
420
+
421
+ ---
422
+
423
+ ### `MissingDependencyError`
424
+
425
+ ```typescript
426
+ class MissingDependencyError extends Error {
427
+ nodeId: string; // the node that has the bad dependency
428
+ missingDep: string; // the dependency that doesn't exist
429
+ }
430
+ ```
431
+
432
+ ---
433
+
434
+ ## Logger
435
+
436
+ *Import from `@db-lyon/flowkit`*
437
+
438
+ ### `Logger` interface
439
+
440
+ ```typescript
441
+ interface Logger {
442
+ debug(...args: unknown[]): void;
443
+ info(...args: unknown[]): void;
444
+ warn(...args: unknown[]): void;
445
+ error(...args: unknown[]): void;
446
+ child(bindings: Record<string, unknown>): Logger;
447
+ }
448
+ ```
449
+
450
+ Compatible with pino, winston, and similar structured loggers.
451
+
452
+ ### `noopLogger`
453
+
454
+ A silent logger that discards all output. Used as the default when no logger is provided.
455
+
456
+ ```typescript
457
+ const noopLogger: Logger;
458
+ ```