@db-lyon/flowkit 0.17.3 → 0.18.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.
Files changed (59) hide show
  1. package/README.md +53 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/config/index.d.ts +4 -2
  4. package/dist/config/index.d.ts.map +1 -1
  5. package/dist/config/index.js +2 -1
  6. package/dist/config/index.js.map +1 -1
  7. package/dist/config/loader.d.ts +11 -0
  8. package/dist/config/loader.d.ts.map +1 -1
  9. package/dist/config/loader.js +4 -0
  10. package/dist/config/loader.js.map +1 -1
  11. package/dist/config/schema.d.ts +1486 -79
  12. package/dist/config/schema.d.ts.map +1 -1
  13. package/dist/config/schema.js +125 -8
  14. package/dist/config/schema.js.map +1 -1
  15. package/dist/config/strict.d.ts +40 -0
  16. package/dist/config/strict.d.ts.map +1 -0
  17. package/dist/config/strict.js +146 -0
  18. package/dist/config/strict.js.map +1 -0
  19. package/dist/flow/index.d.ts +2 -2
  20. package/dist/flow/index.d.ts.map +1 -1
  21. package/dist/flow/index.js +1 -1
  22. package/dist/flow/index.js.map +1 -1
  23. package/dist/flow/runner.d.ts +279 -6
  24. package/dist/flow/runner.d.ts.map +1 -1
  25. package/dist/flow/runner.js +845 -40
  26. package/dist/flow/runner.js.map +1 -1
  27. package/dist/index.d.ts +13 -5
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +6 -2
  30. package/dist/index.js.map +1 -1
  31. package/dist/task/base-task.d.ts +50 -1
  32. package/dist/task/base-task.d.ts.map +1 -1
  33. package/dist/task/base-task.js +23 -0
  34. package/dist/task/base-task.js.map +1 -1
  35. package/dist/task/composite.d.ts +66 -0
  36. package/dist/task/composite.d.ts.map +1 -0
  37. package/dist/task/composite.js +21 -0
  38. package/dist/task/composite.js.map +1 -0
  39. package/dist/task/index.d.ts +7 -1
  40. package/dist/task/index.d.ts.map +1 -1
  41. package/dist/task/index.js +3 -0
  42. package/dist/task/index.js.map +1 -1
  43. package/dist/task/options-schema.d.ts +51 -0
  44. package/dist/task/options-schema.d.ts.map +1 -0
  45. package/dist/task/options-schema.js +102 -0
  46. package/dist/task/options-schema.js.map +1 -0
  47. package/dist/task/registry.d.ts +38 -0
  48. package/dist/task/registry.d.ts.map +1 -1
  49. package/dist/task/registry.js +42 -0
  50. package/dist/task/registry.js.map +1 -1
  51. package/dist/task/warnings.d.ts +25 -0
  52. package/dist/task/warnings.d.ts.map +1 -0
  53. package/dist/task/warnings.js +30 -0
  54. package/dist/task/warnings.js.map +1 -0
  55. package/docs/api-reference.md +273 -0
  56. package/docs/configuration.md +262 -1
  57. package/docs/custom-tasks.md +155 -0
  58. package/docs/releases.md +47 -0
  59. package/package.json +1 -1
@@ -26,6 +26,7 @@ function loadConfig<T extends z.ZodType>(
26
26
  | `env` | `string` | no | Environment name — loads `{base}.{env}.{ext}` overlay |
27
27
  | `envVar` | `string` | no | Env var to read environment name from when `env` is not passed |
28
28
  | `configDir` | `string` | no | Directory to search (default: `process.cwd()`) |
29
+ | `strict` | `boolean \| { passthroughKeys?: string[] }` | no | Reject keys the schema does not declare, with an `UnknownConfigKeyError`. Off by default. See [Strict validation](configuration.md#strict-validation) |
29
30
 
30
31
  **`LoadedConfig<T>`**
31
32
 
@@ -36,6 +37,31 @@ function loadConfig<T extends z.ZodType>(
36
37
 
37
38
  ---
38
39
 
40
+ ### `findUnknownKeys(schema, value, options?)` / `assertKnownKeys(schema, value, options?)`
41
+
42
+ The check behind `loadConfig({ strict })`, for config that does not come
43
+ through the loader.
44
+
45
+ ```typescript
46
+ interface UnknownConfigKey {
47
+ path: string; // e.g. 'flows.ci.steps.2.retires', 'tasks["asset.list"].x'
48
+ key: string;
49
+ suggestion?: string; // nearest declared key, when close enough to be a typo
50
+ }
51
+ interface FindUnknownKeysOptions {
52
+ passthroughKeys?: readonly string[]; // top-level keys left unchecked
53
+ }
54
+
55
+ function findUnknownKeys(schema: z.ZodTypeAny, value: unknown, options?: FindUnknownKeysOptions): UnknownConfigKey[];
56
+ function assertKnownKeys(schema: z.ZodTypeAny, value: unknown, options?: FindUnknownKeysOptions): void; // throws UnknownConfigKeyError
57
+
58
+ class UnknownConfigKeyError extends Error {
59
+ readonly keys: UnknownConfigKey[];
60
+ }
61
+ ```
62
+
63
+ ---
64
+
39
65
  ### `findConfigFile(filename, startDir?)`
40
66
 
41
67
  Walk up parent directories looking for a file by name.
@@ -75,9 +101,23 @@ function deepMerge(base: unknown, override: unknown): unknown
75
101
  | `TaskOptionsSchema` | `Record<string, unknown>` |
76
102
  | `TaskDefinitionSchema` | Task definition object |
77
103
  | `FlowStepSchema` | Single flow step (task xor flow) |
104
+ | `FlowStepObjectSchema` | The same step fields as an unrefined `z.object`, for hosts to `.extend()` |
105
+ | `FlowStepsSchema` | A `steps:` map keyed by step number |
78
106
  | `FlowDefinitionSchema` | Flow with description and steps |
79
107
  | `EngineConfigSchema` | Top-level config with `tasks` and `flows` |
80
108
 
109
+ `refineFlowStep(schema)` applies the step target rule (exactly one of `task`
110
+ or `flow`, or a `None` skip) to any step object schema, so a host manifest can
111
+ reuse every step field the runner understands and add its own:
112
+
113
+ ```typescript
114
+ import { FlowStepObjectSchema, refineFlowStep } from '@db-lyon/flowkit';
115
+
116
+ const ManifestStepSchema = refineFlowStep(
117
+ FlowStepObjectSchema.extend({ label: z.string().optional() }),
118
+ );
119
+ ```
120
+
81
121
  ---
82
122
 
83
123
  ### Config types
@@ -131,9 +171,46 @@ abstract class BaseTask<TOpts = Record<string, unknown>> {
131
171
  abstract execute(): Promise<TaskResult>;
132
172
  protected validate(): void;
133
173
  async run(): Promise<TaskResult>;
174
+ protected resolve<T extends BaseTask>(taskName: string, options?: Record<string, unknown>): Promise<T>;
175
+ protected call(taskName: string, options?: Record<string, unknown>): Promise<TaskResult>;
176
+ protected step(
177
+ target: ChildStepTarget,
178
+ options?: Record<string, unknown>,
179
+ spec?: ChildStepSpec,
180
+ ): Promise<TaskResult>;
181
+
182
+ // Optional class-level declarations
183
+ static optionsSchema?: OptionSpecs;
184
+ static outputs?: OutputSpecs;
185
+ static description?: string;
186
+ static deprecated?: boolean | string;
187
+ static replacedBy?: string;
188
+ static expand?: ExpandFunction;
134
189
  }
190
+
191
+ type ChildStepTarget = string | { task: string } | { flow: string };
192
+ interface ChildStepSpec { retries?: number; retryDelay?: number; retryOn?: string }
193
+ type ChildPlanEntry =
194
+ | { task: string; options?: Record<string, unknown> }
195
+ | { flow: string; options?: Record<string, unknown> };
196
+ interface ExpandContext {
197
+ taskName: string;
198
+ taskDefinitions: Record<string, TaskDefinition>;
199
+ flows: Record<string, FlowDefinition>;
200
+ references?: Record<string, unknown>;
201
+ }
202
+ type ExpandFunction = (
203
+ options: Record<string, unknown>,
204
+ ctx: ExpandContext,
205
+ ) => ChildPlanEntry[] | null | Promise<ChildPlanEntry[] | null>;
206
+
207
+ // Every rollback record in a result tree, children first; invoke in reverse.
208
+ function collectRollbackRecords(result: TaskResult): RollbackRecord[];
135
209
  ```
136
210
 
211
+ `step()` runs a composite child through the runner (`ctx.step`); see
212
+ [Composite tasks](custom-tasks.md#composite-tasks).
213
+
137
214
  | Method | Description |
138
215
  |--------|-------------|
139
216
  | `taskName` | (getter) Human-readable name for logging |
@@ -159,6 +236,8 @@ interface TaskContext {
159
236
  logger?: Logger;
160
237
  /** Cancels LLM work and retry backoff owned by this task invocation. */
161
238
  readonly signal?: AbortSignal;
239
+ /** Run one composite child through the runner. Supplied by FlowRunner. */
240
+ step?: (target: ChildStepTarget, options?: Record<string, unknown>, spec?: ChildStepSpec) => Promise<TaskResult>;
162
241
  [key: string]: unknown;
163
242
  }
164
243
 
@@ -204,9 +283,25 @@ interface TaskResult {
204
283
  data?: Record<string, unknown>;
205
284
  error?: Error;
206
285
  duration?: number; // milliseconds, set by run()
286
+ rollback?: RollbackRecord;
287
+ warnings?: RunWarning[]; // non-fatal notices; the runner appends deprecation warnings
288
+ children?: FlowStepResult[]; // composite child steps run through ctx.step, set by the runner
289
+ }
290
+
291
+ interface RunWarning {
292
+ code: 'deprecated' | 'check';
293
+ message: string;
294
+ name: string; // the task or flow it is about
295
+ kind?: 'task' | 'flow';
296
+ replacedBy?: string; // for 'deprecated'
297
+ stepNumber?: number; // for 'check'
207
298
  }
208
299
  ```
209
300
 
301
+ `deprecationWarning(kind, name, deprecated, replacedBy)` builds the
302
+ `deprecated` warning (or returns `undefined`), and `mergeWarnings(...lists)`
303
+ concatenates lists without repeats, for a host that assembles its own.
304
+
210
305
  ---
211
306
 
212
307
  ### `ShellTask`
@@ -280,6 +375,10 @@ class TaskRegistry {
280
375
  options: Record<string, unknown>,
281
376
  ): Promise<BaseTask>;
282
377
  listRegistered(): string[];
378
+ async describe(
379
+ name: string,
380
+ taskDefinitions?: Record<string, TaskDefinition>,
381
+ ): Promise<TaskDescription>;
283
382
  }
284
383
  ```
285
384
 
@@ -292,6 +391,56 @@ class TaskRegistry {
292
391
  | `resolve(nameOrPath)` | Look up a constructor. Falls back to dynamic filesystem import. |
293
392
  | `create(nameOrPath, ctx, opts)` | Resolve + instantiate in one call; omitted phase defaults to `task` |
294
393
  | `listRegistered()` | Return all registered names and class paths |
394
+ | `describe(name, defs?)` | Class metadata folded with the configured definition. See below |
395
+
396
+ **`TaskDescription`**
397
+
398
+ ```typescript
399
+ interface TaskDescription {
400
+ name: string;
401
+ class_path: string;
402
+ description?: string; // definition, else the class's static description
403
+ group?: string;
404
+ options: Record<string, unknown>; // schema defaults, then definition options (uninterpolated)
405
+ options_schema?: OptionSpecs; // class optionsSchema refined by definition options_schema
406
+ outputs?: OutputSpecs;
407
+ idempotent?: boolean;
408
+ reversible?: boolean;
409
+ }
410
+ ```
411
+
412
+ **Option schemas**
413
+
414
+ ```typescript
415
+ type OptionSpecs = Record<string, OptionSpec>;
416
+ interface OptionSpec {
417
+ type?: OptionType | OptionType[]; // 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null'
418
+ description?: string;
419
+ required?: boolean;
420
+ default?: unknown;
421
+ enum?: unknown[];
422
+ const?: unknown;
423
+ minimum?: number; maximum?: number; exclusiveMinimum?: number; exclusiveMaximum?: number;
424
+ minLength?: number; maxLength?: number; pattern?: string;
425
+ minItems?: number; maxItems?: number; items?: Record<string, unknown>;
426
+ properties?: Record<string, unknown>; additionalProperties?: boolean | Record<string, unknown>;
427
+ nullable?: boolean;
428
+ }
429
+ type OutputSpecs = Record<string, { type?: OptionType | OptionType[]; description?: string; items?: object; properties?: object }>;
430
+
431
+ class TaskOptionsError extends Error {
432
+ readonly taskName: string;
433
+ readonly issues: { option: string; message: string }[];
434
+ }
435
+
436
+ function validateTaskOptions(specs: OptionSpecs | undefined, options: Record<string, unknown>): TaskOptionIssue[];
437
+ function assertTaskOptions(taskName: string, specs: OptionSpecs | undefined, options: Record<string, unknown>): Record<string, unknown>; // defaults applied; throws TaskOptionsError
438
+ function applyOptionDefaults(specs: OptionSpecs | undefined, options: Record<string, unknown>): Record<string, unknown>;
439
+ function mergeOptionSpecs(base?: OptionSpecs, override?: OptionSpecs): OptionSpecs | undefined;
440
+ function taskClassMetadata(ctor: unknown): TaskClassMetadata; // { description?, optionsSchema?, outputs? }
441
+ ```
442
+
443
+ Zod: `OptionSpecSchema`, `OptionSpecsSchema`, `OutputSpecSchema`, `OutputSpecsSchema`.
295
444
 
296
445
  **`TaskConstructor`**
297
446
 
@@ -321,6 +470,16 @@ Orchestration engine that executes flows.
321
470
  class FlowRunner {
322
471
  constructor(config: FlowRunnerConfig);
323
472
  async run(options: FlowRunOptions): Promise<FlowRunResult>;
473
+ async runTask(taskName: string, options?: Record<string, unknown>): Promise<TaskResult>;
474
+ async describeTask(taskName: string): Promise<TaskDescription>;
475
+ describeFlow(flowName: string): FlowDescription;
476
+ checkStepReferences(flowName: string): StepReferenceIssue[];
477
+ async expandTask(taskName: string, options?: Record<string, unknown>): Promise<ChildPlanEntry[] | null>;
478
+ async preflight(
479
+ flowName: string,
480
+ params?: Record<string, unknown>,
481
+ options?: { skip?: string[] },
482
+ ): Promise<PreflightResult>;
324
483
  resolveExecutionPlan(
325
484
  flow: FlowDefinition,
326
485
  skipSet: Set<string>,
@@ -330,6 +489,92 @@ class FlowRunner {
330
489
 
331
490
  ---
332
491
 
492
+ **`StepReferenceIssue`**
493
+
494
+ ```typescript
495
+ interface StepReferenceIssue {
496
+ flowName: string;
497
+ stepNumber: number;
498
+ phase?: HookPhase;
499
+ reference: string; // e.g. '${steps.deploy.url}'
500
+ kind: 'ambiguous' | 'unknown' | 'forward';
501
+ message: string;
502
+ }
503
+ ```
504
+
505
+ **Checks and preflight**
506
+
507
+ ```typescript
508
+ type StepCheck = { when: string | boolean; action: 'error' | 'warn' | 'skip'; message?: string };
509
+
510
+ interface CheckOutcome {
511
+ scope: 'flow' | 'step';
512
+ flowName: string;
513
+ stepNumber?: number;
514
+ name?: string;
515
+ path?: string;
516
+ when: string | boolean;
517
+ action: 'error' | 'warn' | 'skip';
518
+ message: string; // declared, or one naming the condition
519
+ triggered: boolean; // the condition was truthy
520
+ error?: Error; // the evaluator threw
521
+ }
522
+
523
+ class CheckFailedError extends Error {
524
+ readonly outcome: CheckOutcome;
525
+ }
526
+
527
+ interface PreflightResult {
528
+ flowName: string;
529
+ ok: boolean; // no `error` check fired
530
+ checks: CheckOutcome[]; // the flow's own
531
+ steps: PreflightStep[];
532
+ warnings?: RunWarning[]; // fired `warn` checks, deprecations
533
+ }
534
+
535
+ interface PreflightStep {
536
+ stepNumber: number;
537
+ type: 'task' | 'flow';
538
+ name: string;
539
+ path: string; // '2/1', or '<phase>/<n>' for hooks
540
+ depth: number;
541
+ phase?: HookPhase;
542
+ status: 'run' | 'skip' | 'error' | 'unknown';
543
+ skipReason?: 'static' | 'check';
544
+ checks: CheckOutcome[];
545
+ deprecated?: boolean | string;
546
+ replaced_by?: string;
547
+ }
548
+
549
+ interface ConditionContext {
550
+ steps: FlowStepResult[];
551
+ params?: Record<string, unknown>;
552
+ context: TaskContext;
553
+ error?: { message: string; name: string; stack?: string; step?: string };
554
+ references?: Record<string, unknown>; // FlowRunnerConfig.references
555
+ step?: PlanStep; // the step being gated
556
+ check?: StepCheck; // set when evaluating a check
557
+ flowName?: string;
558
+ }
559
+ ```
560
+
561
+ **`FlowDescription`**
562
+
563
+ ```typescript
564
+ interface FlowDescription {
565
+ name: string;
566
+ description?: string;
567
+ deprecated?: boolean | string;
568
+ replaced_by?: string;
569
+ rollback_on_failure?: boolean;
570
+ options_scope?: 'flat' | 'step';
571
+ checks?: StepCheck[];
572
+ steps: PlanStep[]; // main steps in run order
573
+ }
574
+ ```
575
+
576
+ ---
577
+
333
578
  ### `FlowRunnerConfig`
334
579
 
335
580
  ```typescript
@@ -344,6 +589,8 @@ interface FlowRunnerConfig {
344
589
  references?: Record<string, unknown>;
345
590
  agents?: Record<string, AgentDefinition>;
346
591
  nestedAgentTaskFactory?: NestedAgentTaskFactory;
592
+ optionsScope?: 'flat' | 'step'; // default for how `params` reach steps; default 'flat'
593
+ strictStepReferences?: boolean; // refuse ambiguous/unknown/forward ${steps.x}; default false
347
594
  }
348
595
  ```
349
596
 
@@ -380,6 +627,11 @@ interface FlowRunOptions {
380
627
  flowName: string; // name of the flow to execute
381
628
  skip?: string[]; // task names or step numbers to skip
382
629
  plan?: boolean; // return plan without executing
630
+ params?: Record<string, unknown>; // runtime options, highest precedence
631
+ optionsScope?: 'flat' | 'step'; // how `params` are addressed; see configuration.md
632
+ rollback_on_failure?: boolean;
633
+ expandNestedFlows?: boolean; // plan mode: expand nested flows into child rows
634
+ expandComposites?: boolean; // plan mode: list composite children from static expand()
383
635
  }
384
636
  ```
385
637
 
@@ -393,6 +645,10 @@ interface FlowRunResult {
393
645
  steps: FlowStepResult[];
394
646
  duration: number; // total milliseconds
395
647
  error?: Error; // first error that caused failure
648
+ hookErrors?: HookError[];
649
+ rollback?: RollbackResult;
650
+ warnings?: RunWarning[]; // deprecations and fired `warn` checks, without repeats
651
+ checks?: CheckOutcome[]; // every check that fired, nested flows included
396
652
  }
397
653
  ```
398
654
 
@@ -408,7 +664,12 @@ interface FlowStepResult {
408
664
  result?: TaskResult;
409
665
  skipped: boolean;
410
666
  duration: number; // milliseconds
667
+ attempts?: number;
668
+ skipReason?: 'static' | 'when' | 'check';
669
+ ignoredFailure?: boolean;
670
+ checks?: CheckOutcome[]; // checks that fired for this step (and inside its flow)
411
671
  nestedSteps?: FlowStepResult[]; // for a `flow` step: the child's own steps
672
+ path?: string; // for a composite child step: e.g. '2/1'
412
673
  }
413
674
  ```
414
675
 
@@ -425,6 +686,18 @@ interface PlanStep {
425
686
  name: string;
426
687
  skipped: boolean;
427
688
  options?: Record<string, unknown>;
689
+ retries?: number;
690
+ retryDelay?: number;
691
+ retryOn?: string;
692
+ when?: string | boolean;
693
+ ignore_failure?: boolean;
694
+ checks?: StepCheck[]; // declared, unevaluated
695
+ phase?: HookPhase; // hook steps only
696
+ path?: string; // expanded plans: hierarchical id, e.g. '2/1'
697
+ depth?: number;
698
+ deprecated?: boolean | string; // plan mode: the target is deprecated
699
+ replaced_by?: string;
700
+ composite?: 'expanded' | 'opaque'; // plan mode with expandComposites
428
701
  }
429
702
  ```
430
703