@pogodisco/zephyr 1.3.9 → 1.3.10
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/utils.d.ts +11 -0
- package/dist/utils.js +12 -0
- package/dist/workflow-composer.d.ts +26 -6
- package/dist/workflow-composer.js +55 -369
- package/dist/workflow-executor.js +46 -2
- package/package.json +1 -1
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?:
|
|
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?:
|
|
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 &
|
|
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?:
|
|
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?:
|
|
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):
|
|
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
|
-
|
|
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
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
-
|
|
119
|
+
const actionFn = async () => {
|
|
88
120
|
const action = registry[step.action];
|
|
89
|
-
|
|
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
|
};
|