@pogodisco/zephyr 1.3.9 → 1.3.11

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/dist/types.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createWorkflow } from "./workflow-composer.js";
2
2
  export type Action<F extends (...args: any[]) => any = (...args: any[]) => any> = F;
3
3
  export type ActionRegistry = Record<string, (...args: any[]) => any>;
4
- export type MergeActionRegistries<A extends ActionRegistry, B extends ActionRegistry> = Omit<A, keyof B> & B;
4
+ export type MergeActionRegistries<A extends ActionRegistry, B extends ActionRegistry> = Simplify<Omit<A, keyof B> & B>;
5
5
  export type ExecutionFrame = {
6
6
  stepId: string;
7
7
  attempts: number;
@@ -12,6 +12,9 @@ export type ExecutionFrame = {
12
12
  error?: any;
13
13
  skipped?: boolean;
14
14
  };
15
+ export type Simplify<T> = {
16
+ [K in keyof T]: T[K];
17
+ } & {};
15
18
  export type ActionParams<Reg, K extends keyof Reg> = Reg[K] extends (...args: infer P) => any ? P : never;
16
19
  export type ActionReturn<Reg, K extends keyof Reg> = Reg[K] extends (...args: any[]) => infer R ? Awaited<R> : never;
17
20
  export type WorkflowObserver<Reg extends ActionRegistry = any> = {
package/dist/utils.d.ts CHANGED
@@ -14,4 +14,15 @@ export declare function createRunner<Reg extends ActionRegistry>(registry: Reg):
14
14
  results: W extends WorkflowDef<Reg, any, infer R, any, any> ? R : never;
15
15
  extras: Record<string, any>;
16
16
  }>;
17
+ /**
18
+ * Create a typed runner for a specific module
19
+ * @example
20
+ * const run = createModuleRunner(modWithLoop, regOne);
21
+ * const result = await run("loopWorkflow", { items: ["APPLE"] });
22
+ */
23
+ export declare function createModuleRunner<M extends Record<string, WorkflowDef<any, any, any, any, any>>, Reg extends ActionRegistry>(module: M, registry: Reg): <K extends keyof M>(key: K, input: M[K] extends WorkflowDef<Reg, infer I, any, any, any> ? I : never, observers?: any[]) => Promise<{
24
+ output: M[K] extends WorkflowDef<Reg, any, any, any, infer O> ? O : never;
25
+ results: M[K] extends WorkflowDef<Reg, any, infer R, any, any> ? R : never;
26
+ extras: Record<string, any>;
27
+ }>;
17
28
  export {};
package/dist/utils.js CHANGED
@@ -21,3 +21,15 @@ export function createRunner(registry) {
21
21
  return executeWorkflow(workflow, registry, input, observers ?? []);
22
22
  };
23
23
  }
24
+ /**
25
+ * Create a typed runner for a specific module
26
+ * @example
27
+ * const run = createModuleRunner(modWithLoop, regOne);
28
+ * const result = await run("loopWorkflow", { items: ["APPLE"] });
29
+ */
30
+ export function createModuleRunner(module, registry) {
31
+ return async (key, input, observers) => {
32
+ const workflow = module[key];
33
+ return executeWorkflow(workflow, registry, input, observers ?? []);
34
+ };
35
+ }
@@ -48,6 +48,7 @@ export type StepDef<Reg extends ActionRegistry, ID extends string = string, Acti
48
48
  dependsOn: string[];
49
49
  resolve: (ctx: any) => ResolvedStepInput;
50
50
  when?: (ctx: any) => boolean;
51
+ options?: StepOptions<any, any, any>;
51
52
  };
52
53
  export type WorkflowDef<Reg extends ActionRegistry, Input, Results, Steps extends StepDef<Reg, any, any>[] = StepDef<Reg, any, any>[], Output = undefined> = {
53
54
  name: string;
@@ -59,6 +60,20 @@ export type WorkflowDef<Reg extends ActionRegistry, Input, Results, Steps extend
59
60
  outputResolver?: (ctx: any) => Output;
60
61
  __context?: any;
61
62
  };
63
+ type StepRuntimeCtx<I, R, C> = {
64
+ input: I;
65
+ results: R;
66
+ context: C;
67
+ };
68
+ type StepOptions<Input, Results, Context> = {
69
+ retry?: number;
70
+ retryDelay?: number | ((attempt: number) => number);
71
+ timeout?: number;
72
+ continueOnError?: boolean;
73
+ onError?: (err: unknown, ctx: StepRuntimeCtx<Input, Results, Context>) => any;
74
+ label?: string;
75
+ meta?: Record<string, any>;
76
+ };
62
77
  type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
63
78
  export declare class WorkflowBuilder<Reg extends ActionRegistry, Input = unknown, Context extends Record<string, any> = {}, Steps extends StepDef<Reg, any, any>[] = [], Results = {}, Output = undefined> {
64
79
  private name;
@@ -66,13 +81,15 @@ export declare class WorkflowBuilder<Reg extends ActionRegistry, Input = unknown
66
81
  private context;
67
82
  private steps;
68
83
  private frontier;
84
+ private pendingWhen?;
69
85
  private outputResolver?;
86
+ private clearPendingWhen;
70
87
  constructor(name: string, registry: Reg, context: Context);
71
88
  step<ID extends string, ActionName extends keyof Reg & string, ResolveOut extends ResolvedStepInput = ResolvedStepInput>(id: ID, action: ActionName, resolve?: (ctx: {
72
89
  input: Input;
73
90
  results: Results;
74
91
  context: Context;
75
- } & CallHelpers<Reg, ActionName>) => ResolveOut, dependsOn?: string[], options?: {}): WorkflowBuilder<Reg, Input, Context, [
92
+ } & CallHelpers<Reg, ActionName>) => ResolveOut, dependsOn?: string[], options?: StepOptions<Input, Results, Context>): WorkflowBuilder<Reg, Input, Context, [
76
93
  ...Steps,
77
94
  StepDef<Reg, ID, ActionName>
78
95
  ], Results & {
@@ -82,7 +99,7 @@ export declare class WorkflowBuilder<Reg extends ActionRegistry, Input = unknown
82
99
  input: Input;
83
100
  results: Results;
84
101
  context: Context;
85
- } & CallHelpers<Reg, ActionName>) => ResolveOut, options?: {}): WorkflowBuilder<Reg, Input, Context, [...Steps, StepDef<Reg, ID, ActionName>], Results & { [K in ID]: StepResultFromResolve<Reg, ActionName, ResolveOut>; }, undefined>;
102
+ } & CallHelpers<Reg, ActionName>) => ResolveOut, options?: StepOptions<Input, Results, Context>): WorkflowBuilder<Reg, Input, Context, [...Steps, StepDef<Reg, ID, ActionName>], Results & { [K in ID]: StepResultFromResolve<Reg, ActionName, ResolveOut>; }, undefined>;
86
103
  as<NewType>(): WorkflowBuilder<Reg, Input, Context, Steps, Steps extends [...infer Rest, infer Last] ? Last extends StepDef<Reg, infer ID, any> ? Omit<Results, ID> & {
87
104
  [K in ID]: NewType;
88
105
  } : Results : Results, Output>;
@@ -91,12 +108,14 @@ export declare class WorkflowBuilder<Reg extends ActionRegistry, Input = unknown
91
108
  }): WorkflowBuilder<Reg, Input, Context, [
92
109
  ...Steps,
93
110
  ...(Branches[number] extends WorkflowBuilder<Reg, any, any, infer S, any> ? S : never)
94
- ], Results & (Branches[number] extends WorkflowBuilder<Reg, any, any, any, infer R> ? UnionToIntersection<R> : {})>;
111
+ ], Results & UnionToIntersection<{
112
+ [K in keyof Branches]: Branches[K] extends WorkflowBuilder<Reg, any, any, any, infer R> ? R : never;
113
+ }[number]>>;
95
114
  join<ID extends string = string, ActionName extends keyof Reg & string = any, ResolveOut extends ResolvedStepInput = ResolvedStepInput>(id: ID, action: ActionName, resolve?: (ctx: {
96
115
  input: Input;
97
116
  results: Results;
98
117
  context: Context;
99
- } & CallHelpers<Reg, ActionName>) => ResolveOut, options?: {}): WorkflowBuilder<Reg, Input, Context, [...Steps, StepDef<Reg, ID, ActionName>], Results & { [K in ID]: StepResultFromResolve<Reg, ActionName, ResolveOut>; }, undefined>;
118
+ } & CallHelpers<Reg, ActionName>) => ResolveOut, options?: StepOptions<Input, Results, Context>): WorkflowBuilder<Reg, Input, Context, [...Steps, StepDef<Reg, ID, ActionName>], Results & { [K in ID]: StepResultFromResolve<Reg, ActionName, ResolveOut>; }, undefined>;
100
119
  subflow<Prefix extends string, SubSteps extends StepDef<Reg, any, any>[], WF extends WorkflowDef<Reg, any, any, SubSteps, any>>(prefix: Prefix, workflow: WF, resolveInput: WorkflowInput<WF> extends undefined ? ((ctx: {
101
120
  input: Input;
102
121
  results: Results;
@@ -105,7 +124,7 @@ export declare class WorkflowBuilder<Reg extends ActionRegistry, Input = unknown
105
124
  input: Input;
106
125
  results: Results;
107
126
  context: Context;
108
- }) => WorkflowInput<WF>, options?: {}): WorkflowBuilder<Reg, Input, Context, [
127
+ }) => WorkflowInput<WF>, options?: StepOptions<Input, Results, Context>): WorkflowBuilder<Reg, Input, Context, [
109
128
  ...Steps,
110
129
  ...WF["steps"]
111
130
  ], Results & {
@@ -115,7 +134,8 @@ export declare class WorkflowBuilder<Reg extends ActionRegistry, Input = unknown
115
134
  input: Input;
116
135
  results: Results;
117
136
  context: Context;
118
- }) => boolean): this;
137
+ }) => boolean): WorkflowBuilder<Reg, Input, Context, Steps, Results, Output>;
138
+ endWhen(): this;
119
139
  output<Output>(fn: (ctx: {
120
140
  input: Input;
121
141
  results: Results;
@@ -1,371 +1,10 @@
1
- // import { ActionRegistry, ActionReturn } from "./types.js";
2
- //
3
- // /* ------------------------------------------------ */
4
- // /* STEP INPUT NORMALIZATION TYPES */
5
- // /* ------------------------------------------------ */
6
- //
7
- // export type NormalizedCall =
8
- // | { kind: "none" }
9
- // | { kind: "positional"; args: any[] }
10
- // | { kind: "object"; args: any };
11
- //
12
- // export type ResolvedStepInput =
13
- // | NormalizedCall
14
- // | { kind: "loop"; items: NormalizedCall[] };
15
- //
16
- // /* ------------------------------------------------ */
17
- // /* STEP RESULT TYPES */
18
- // /* ------------------------------------------------ */
19
- //
20
- // type SubflowResult<SubResults, SubOutput> = SubOutput extends undefined
21
- // ? SubResults
22
- // : SubOutput;
23
- //
24
- // export type StepResult<
25
- // Reg extends ActionRegistry,
26
- // ActionName extends keyof Reg,
27
- // Loop extends boolean | undefined = undefined,
28
- // > = Loop extends true
29
- // ? ActionReturn<Reg, ActionName>[]
30
- // : ActionReturn<Reg, ActionName>;
31
- //
32
- // /* ------------------------------------------------ */
33
- // /* STEP DEFINITION */
34
- // /* ------------------------------------------------ */
35
- //
36
- // export type StepDef<
37
- // Reg extends ActionRegistry,
38
- // ID extends string = string,
39
- // ActionName extends keyof Reg = any,
40
- // > = {
41
- // id: ID;
42
- // action: ActionName;
43
- // dependsOn: string[];
44
- // resolve: (ctx: any) => ResolvedStepInput;
45
- // when?: (ctx: any) => boolean;
46
- // loop?: boolean;
47
- // };
48
- //
49
- // /* ------------------------------------------------ */
50
- // /* WORKFLOW DEFINITION */
51
- // /* ------------------------------------------------ */
52
- //
53
- // export type WorkflowDef<
54
- // Reg extends ActionRegistry,
55
- // Input,
56
- // Results,
57
- // Steps extends StepDef<Reg, any, any>[] = StepDef<Reg, any, any>[],
58
- // Output = undefined,
59
- // > = {
60
- // name: string;
61
- // steps: Steps;
62
- // entrySteps: StepDef<Reg>[];
63
- // endSteps: StepDef<Reg>[];
64
- // input: Input;
65
- // results: Results;
66
- // outputResolver?: (ctx: any) => Output;
67
- // __context?: any;
68
- // };
69
- //
70
- // /* ------------------------------------------------ */
71
- // /* HELPER TYPES */
72
- // /* ------------------------------------------------ */
73
- //
74
- // type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
75
- // k: infer I,
76
- // ) => void
77
- // ? I
78
- // : never;
79
- //
80
- // /* ------------------------------------------------ */
81
- // /* FLUENT WORKFLOW BUILDER */
82
- // /* ------------------------------------------------ */
83
- //
84
- // export class WorkflowBuilder<
85
- // Reg extends ActionRegistry,
86
- // Input = unknown,
87
- // Context extends Record<string, any> = {},
88
- // Steps extends StepDef<Reg, any, any>[] = [],
89
- // Results = {},
90
- // Output = undefined,
91
- // > {
92
- // private steps: StepDef<Reg, any, any>[] = [];
93
- // private frontier: string[] = [];
94
- // private outputResolver?: (ctx: any) => any;
95
- //
96
- // constructor(
97
- // private name: string,
98
- // private registry: Reg,
99
- // private context: Context,
100
- // ) {}
101
- //
102
- // /* ------------------------------------------------ */
103
- // /* Base Step */
104
- // /* ------------------------------------------------ */
105
- // step<
106
- // ID extends string,
107
- // ActionName extends keyof Reg & string,
108
- // Loop extends boolean | undefined = undefined,
109
- // >(
110
- // id: ID,
111
- // action: ActionName,
112
- // resolve?: (ctx: {
113
- // input: Input;
114
- // results: Results;
115
- // context: Context;
116
- // }) => ResolvedStepInput,
117
- // dependsOn?: string[],
118
- // options?: { loop?: Loop },
119
- // ): WorkflowBuilder<
120
- // Reg,
121
- // Input,
122
- // Context,
123
- // [...Steps, StepDef<Reg, ID, ActionName>],
124
- // Results & { [K in ID]: StepResult<Reg, ActionName, Loop> }
125
- // > {
126
- // const deps = dependsOn ?? [...this.frontier];
127
- //
128
- // this.steps.push({
129
- // id,
130
- // action,
131
- // resolve: resolve ?? (() => ({ kind: "none" })),
132
- // dependsOn: deps,
133
- // loop: options?.loop ?? false,
134
- // });
135
- //
136
- // this.frontier = [id];
137
- //
138
- // return this as any;
139
- // }
140
- //
141
- // /* ------------------------------------------------ */
142
- // /* Sequential shortcut */
143
- // /* ------------------------------------------------ */
144
- // seq<ID extends string, ActionName extends keyof Reg & string>(
145
- // id: ID,
146
- // action: ActionName,
147
- // resolve?: (ctx: {
148
- // input: Input;
149
- // results: Results;
150
- // context: Context;
151
- // }) => ResolvedStepInput,
152
- // options?: { loop?: boolean },
153
- // ) {
154
- // return this.step(id, action, resolve, undefined, options);
155
- // }
156
- //
157
- // /* ------------------------------------------------ */
158
- // /* Parallel branches */
159
- // /* ------------------------------------------------ */
160
- // parallel<Branches extends WorkflowBuilder<Reg, Input, Context, any, any>[]>(
161
- // ...branches: {
162
- // [K in keyof Branches]: (
163
- // builder: WorkflowBuilder<Reg, Input, Context, [], Results>,
164
- // ) => Branches[K];
165
- // }
166
- // ): WorkflowBuilder<
167
- // Reg,
168
- // Input,
169
- // Context,
170
- // [
171
- // ...Steps,
172
- // ...(Branches[number] extends WorkflowBuilder<Reg, any, any, infer S, any>
173
- // ? S
174
- // : never),
175
- // ],
176
- // Results &
177
- // (Branches[number] extends WorkflowBuilder<Reg, any, any, any, infer R>
178
- // ? UnionToIntersection<R>
179
- // : {})
180
- // > {
181
- // const parentFrontier = [...this.frontier];
182
- // const branchEnds: string[] = [];
183
- //
184
- // branches.forEach((branch) => {
185
- // const b = new WorkflowBuilder<Reg, Input, Context, [], Results>(
186
- // this.name,
187
- // this.registry,
188
- // this.context,
189
- // );
190
- //
191
- // b.frontier = parentFrontier;
192
- // branch(b);
193
- // branchEnds.push(...b.frontier);
194
- // this.steps.push(...(b as any).steps);
195
- // });
196
- //
197
- // this.frontier = branchEnds;
198
- // return this as any;
199
- // }
200
- //
201
- // /* ------------------------------------------------ */
202
- // /* Join helper */
203
- // /* ------------------------------------------------ */
204
- // join<ID extends string, ActionName extends keyof Reg & string>(
205
- // id: ID,
206
- // action: ActionName,
207
- // resolve?: (ctx: {
208
- // input: Input;
209
- // results: Results;
210
- // context: Context;
211
- // }) => ResolvedStepInput,
212
- // options?: { loop?: boolean },
213
- // ) {
214
- // return this.step(id, action, resolve, [...this.frontier], options);
215
- // }
216
- //
217
- // /* ------------------------------------------------ */
218
- // /* Subflow */
219
- // /* ------------------------------------------------ */
220
- // subflow<
221
- // Prefix extends string,
222
- // SubInput,
223
- // SubResults,
224
- // SubSteps extends StepDef<Reg, any, any>[],
225
- // SubOutput,
226
- // >(
227
- // prefix: Prefix,
228
- // workflow: WorkflowDef<Reg, SubInput, SubResults, SubSteps, SubOutput>,
229
- // resolveInput?: (ctx: {
230
- // input: Input;
231
- // results: Results;
232
- // context: Context;
233
- // }) => SubInput,
234
- // options?: { loop?: boolean },
235
- // ): WorkflowBuilder<
236
- // Reg,
237
- // Input,
238
- // Context,
239
- // [...Steps, ...SubSteps],
240
- // Results & { [K in Prefix]: SubflowResult<SubResults, SubOutput> }
241
- // > {
242
- // const idMap = new Map<string, string>();
243
- //
244
- // workflow.steps.forEach((step) => {
245
- // idMap.set(step.id, `${prefix}.${step.id}`);
246
- // });
247
- //
248
- // workflow.steps.forEach((step) => {
249
- // const newStep: StepDef<Reg, any, any> = {
250
- // ...step,
251
- // id: idMap.get(step.id)!,
252
- // dependsOn: step.dependsOn.map((d) => idMap.get(d)!),
253
- // resolve: (ctx: any) => {
254
- // const subInput = resolveInput ? resolveInput(ctx) : undefined;
255
- // return step.resolve({
256
- // input: subInput,
257
- // results: ctx.results,
258
- // context: ctx.context,
259
- // });
260
- // },
261
- // ...(options ? { loop: options.loop ?? step.loop } : {}),
262
- // };
263
- //
264
- // if (workflow.entrySteps.find((e) => e.id === step.id)) {
265
- // newStep.dependsOn = [...this.frontier];
266
- // }
267
- //
268
- // this.steps.push(newStep);
269
- // });
270
- //
271
- // this.frontier = workflow.endSteps.map((e) => idMap.get(e.id)!);
272
- // return this as any;
273
- // }
274
- //
275
- // /* ------------------------------------------------ */
276
- // /* Conditional */
277
- // /* ------------------------------------------------ */
278
- // when(
279
- // predicate: (ctx: {
280
- // input: Input;
281
- // results: Results;
282
- // context: Context;
283
- // }) => boolean,
284
- // ): this {
285
- // const lastStep = this.steps[this.steps.length - 1];
286
- // if (!lastStep) throw new Error("when() must follow a step");
287
- // lastStep.when = predicate;
288
- // return this;
289
- // }
290
- //
291
- // /* ------------------------------------------------ */
292
- // /* Workflow output */
293
- // /* ------------------------------------------------ */
294
- // output<Output>(
295
- // fn: (ctx: { input: Input; results: Results; context: Context }) => Output,
296
- // ): WorkflowDef<Reg, Input, Results, Steps, Output> {
297
- // this.outputResolver = fn;
298
- // return this.build() as WorkflowDef<Reg, Input, Results, Steps, Output>;
299
- // }
300
- //
301
- // build(): WorkflowDef<Reg, Input, Results, Steps> {
302
- // this.validateDependencies();
303
- //
304
- // return {
305
- // name: this.name,
306
- // steps: this.steps as Steps,
307
- // entrySteps: this.steps.filter((s) => s.dependsOn.length === 0),
308
- // endSteps: this.getEndSteps(),
309
- // input: {} as Input,
310
- // results: {} as Results,
311
- // outputResolver: this.outputResolver,
312
- // __context: this.context,
313
- // };
314
- // }
315
- //
316
- // private validateDependencies() {
317
- // const stepIds = new Set(this.steps.map((s) => s.id));
318
- // for (const step of this.steps) {
319
- // for (const dep of step.dependsOn) {
320
- // if (!stepIds.has(dep))
321
- // throw new Error(`Step ${step.id} depends on unknown step ${dep}`);
322
- // }
323
- // }
324
- // }
325
- //
326
- // private getEndSteps() {
327
- // const hasDependents = new Set<string>();
328
- // for (const step of this.steps) {
329
- // for (const dep of step.dependsOn) hasDependents.add(dep);
330
- // }
331
- // return this.steps.filter((s) => !hasDependents.has(s.id));
332
- // }
333
- //
334
- // /* ------------------------------------------------ */
335
- // /* STATIC HELPERS FOR NORMALIZATION */
336
- // /* ------------------------------------------------ */
337
- // static args = (...args: any[]): NormalizedCall => ({
338
- // kind: "positional",
339
- // args,
340
- // });
341
- // static obj = (args: any): NormalizedCall => ({ kind: "object", args });
342
- // static none = (): NormalizedCall => ({ kind: "none" });
343
- // static loop = (items: NormalizedCall[]): ResolvedStepInput => ({
344
- // kind: "loop",
345
- // items,
346
- // });
347
- // }
348
- //
349
- // /* ------------------------------------------------ */
350
- // /* WORKFLOW CREATOR */
351
- // /* ------------------------------------------------ */
352
- // export function createWorkflow<
353
- // Reg extends ActionRegistry,
354
- // Context extends Record<string, any> = {},
355
- // >(registry: Reg, context?: Context) {
356
- // return function workflow<Input = unknown>(name: string) {
357
- // return new WorkflowBuilder<Reg, Input, Context>(
358
- // name,
359
- // registry,
360
- // context || ({} as Context),
361
- // );
362
- // };
363
- // }
364
- //
365
1
  /* ------------------------------------------------ */
366
2
  /* FLUENT WORKFLOW BUILDER */
367
3
  /* ------------------------------------------------ */
368
4
  export class WorkflowBuilder {
5
+ clearPendingWhen() {
6
+ this.pendingWhen = undefined;
7
+ }
369
8
  constructor(name, registry, context) {
370
9
  this.name = name;
371
10
  this.registry = registry;
@@ -380,6 +19,8 @@ export class WorkflowBuilder {
380
19
  action,
381
20
  resolve: resolve ?? (() => ({ kind: "none" })),
382
21
  dependsOn: deps,
22
+ when: this.pendingWhen,
23
+ options,
383
24
  });
384
25
  this.frontier = [id];
385
26
  return this;
@@ -399,6 +40,47 @@ export class WorkflowBuilder {
399
40
  /* ------------------------------------------------ */
400
41
  /* Parallel branches */
401
42
  /* ------------------------------------------------ */
43
+ // parallel<Branches extends WorkflowBuilder<Reg, Input, Context, any, any>[]>(
44
+ // ...branches: {
45
+ // [K in keyof Branches]: (
46
+ // builder: WorkflowBuilder<Reg, Input, Context, [], Results>,
47
+ // ) => Branches[K];
48
+ // }
49
+ // ): WorkflowBuilder<
50
+ // Reg,
51
+ // Input,
52
+ // Context,
53
+ // [
54
+ // ...Steps,
55
+ // ...(Branches[number] extends WorkflowBuilder<Reg, any, any, infer S, any>
56
+ // ? S
57
+ // : never),
58
+ // ],
59
+ // Results &
60
+ // (Branches[number] extends WorkflowBuilder<Reg, any, any, any, infer R>
61
+ // ? UnionToIntersection<R>
62
+ // : {})
63
+ // > {
64
+ // const parentFrontier = [...this.frontier];
65
+ // const branchEnds: string[] = [];
66
+ //
67
+ // branches.forEach((branch) => {
68
+ // const b = new WorkflowBuilder<Reg, Input, Context, [], Results>(
69
+ // this.name,
70
+ // this.registry,
71
+ // this.context,
72
+ // );
73
+ //
74
+ // b.frontier = parentFrontier;
75
+ // branch(b);
76
+ // branchEnds.push(...b.frontier);
77
+ // this.steps.push(...(b as any).steps);
78
+ // });
79
+ //
80
+ // this.frontier = branchEnds;
81
+ // this.clearPendingWhen();
82
+ // return this as any;
83
+ // }
402
84
  parallel(...branches) {
403
85
  const parentFrontier = [...this.frontier];
404
86
  const branchEnds = [];
@@ -410,13 +92,16 @@ export class WorkflowBuilder {
410
92
  this.steps.push(...b.steps);
411
93
  });
412
94
  this.frontier = branchEnds;
95
+ this.clearPendingWhen();
413
96
  return this;
414
97
  }
415
98
  /* ------------------------------------------------ */
416
99
  /* Join helper */
417
100
  /* ------------------------------------------------ */
418
101
  join(id, action, resolve, options) {
419
- return this.step(id, action, resolve, [...this.frontier], options);
102
+ const result = this.step(id, action, resolve, [...this.frontier], options);
103
+ this.clearPendingWhen();
104
+ return result;
420
105
  }
421
106
  /* ------------------------------------------------ */
422
107
  /* Subflow */
@@ -452,10 +137,11 @@ export class WorkflowBuilder {
452
137
  /* Conditional */
453
138
  /* ------------------------------------------------ */
454
139
  when(predicate) {
455
- const lastStep = this.steps[this.steps.length - 1];
456
- if (!lastStep)
457
- throw new Error("when() must follow a step");
458
- lastStep.when = predicate;
140
+ this.pendingWhen = predicate;
141
+ return this;
142
+ }
143
+ endWhen() {
144
+ this.clearPendingWhen();
459
145
  return this;
460
146
  }
461
147
  /* ------------------------------------------------ */
@@ -7,6 +7,38 @@ function createCallHelpers() {
7
7
  loop: (items) => ({ kind: "loop", items }),
8
8
  };
9
9
  }
10
+ // Helper to wrap action execution with timeout
11
+ async function withTimeout(promise, ms) {
12
+ if (!ms)
13
+ return promise;
14
+ return Promise.race([
15
+ promise,
16
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), ms)),
17
+ ]);
18
+ }
19
+ // Helper to run action with retry support
20
+ async function runWithRetry(actionFn, stepOptions) {
21
+ const maxRetries = stepOptions?.retry ?? 0;
22
+ let lastError;
23
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
24
+ try {
25
+ return await actionFn();
26
+ }
27
+ catch (err) {
28
+ lastError = err;
29
+ if (attempt === maxRetries)
30
+ break;
31
+ const delay = stepOptions?.retryDelay;
32
+ if (typeof delay === "number") {
33
+ await new Promise((r) => setTimeout(r, delay));
34
+ }
35
+ else if (typeof delay === "function") {
36
+ await new Promise((r) => setTimeout(r, delay(attempt)));
37
+ }
38
+ }
39
+ }
40
+ throw lastError;
41
+ }
10
42
  export async function executeWorkflow(workflow, registry, input, observers = []) {
11
43
  const results = {};
12
44
  const extras = {};
@@ -84,9 +116,12 @@ export async function executeWorkflow(workflow, registry, input, observers = [])
84
116
  }
85
117
  const resolvedArgs = step.resolve?.(stepCtx);
86
118
  frame.input = resolvedArgs;
87
- try {
119
+ const actionFn = async () => {
88
120
  const action = registry[step.action];
89
- const result = await runAction(resolvedArgs, action);
121
+ return await runAction(resolvedArgs, action);
122
+ };
123
+ try {
124
+ const result = await withTimeout(runWithRetry(actionFn, step.options), step.options?.timeout);
90
125
  frame.output = result;
91
126
  frame.end = Date.now();
92
127
  results[step.id] = result;
@@ -95,6 +130,15 @@ export async function executeWorkflow(workflow, registry, input, observers = [])
95
130
  catch (err) {
96
131
  frame.error = err;
97
132
  frame.end = Date.now();
133
+ if (step.options?.onError) {
134
+ const fallback = step.options.onError(err, ctx);
135
+ results[step.id] = fallback;
136
+ return fallback;
137
+ }
138
+ if (step.options?.continueOnError) {
139
+ results[step.id] = undefined;
140
+ return undefined;
141
+ }
98
142
  throw err;
99
143
  }
100
144
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pogodisco/zephyr",
3
- "version": "1.3.9",
3
+ "version": "1.3.11",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },