@pogodisco/zephyr 1.3.8 → 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 +18 -0
- package/dist/utils.js +18 -0
- package/dist/workflow-composer.d.ts +26 -6
- package/dist/workflow-composer.js +55 -410
- package/dist/workflow-executor.js +46 -2
- package/package.json +1 -1
package/dist/utils.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ActionRegistry } from "./types.js";
|
|
2
|
+
import { WorkflowDef } from "./workflow-composer.js";
|
|
1
3
|
type AnyFn = (...args: any[]) => any;
|
|
2
4
|
/**
|
|
3
5
|
* For generic actions - preserves the generic parameter
|
|
@@ -7,4 +9,20 @@ export declare function genericAction<F extends AnyFn>(fn: F): <T>() => ((...arg
|
|
|
7
9
|
* For fixed actions
|
|
8
10
|
*/
|
|
9
11
|
export declare function fixedAction<F extends AnyFn>(fn: F): () => ((...args: Parameters<F>) => ReturnType<F>);
|
|
12
|
+
export declare function createRunner<Reg extends ActionRegistry>(registry: Reg): <W extends WorkflowDef<Reg, any, any, any, any>>(workflow: W, input: W extends WorkflowDef<Reg, infer I, any, any, any> ? I : never, observers?: any[]) => Promise<{
|
|
13
|
+
output: W extends WorkflowDef<Reg, any, any, any, infer O> ? O : never;
|
|
14
|
+
results: W extends WorkflowDef<Reg, any, infer R, any, any> ? R : never;
|
|
15
|
+
extras: Record<string, any>;
|
|
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
|
+
}>;
|
|
10
28
|
export {};
|
package/dist/utils.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { executeWorkflow } from "./workflow-executor.js";
|
|
1
2
|
/**
|
|
2
3
|
* For generic actions - preserves the generic parameter
|
|
3
4
|
*/
|
|
@@ -15,3 +16,20 @@ export function fixedAction(fn) {
|
|
|
15
16
|
return fn;
|
|
16
17
|
};
|
|
17
18
|
}
|
|
19
|
+
export function createRunner(registry) {
|
|
20
|
+
return async (workflow, input, observers) => {
|
|
21
|
+
return executeWorkflow(workflow, registry, input, observers ?? []);
|
|
22
|
+
};
|
|
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;
|
|
@@ -373,47 +12,6 @@ export class WorkflowBuilder {
|
|
|
373
12
|
this.steps = [];
|
|
374
13
|
this.frontier = [];
|
|
375
14
|
}
|
|
376
|
-
/* ------------------------------------------------ */
|
|
377
|
-
/* Base Step */
|
|
378
|
-
/* ------------------------------------------------ */
|
|
379
|
-
// step<
|
|
380
|
-
// ID extends string,
|
|
381
|
-
// ActionName extends keyof Reg & string,
|
|
382
|
-
// ResolveOut extends ResolvedStepInput = ResolvedStepInput,
|
|
383
|
-
// >(
|
|
384
|
-
// id: ID,
|
|
385
|
-
// action: ActionName,
|
|
386
|
-
// resolve?: (
|
|
387
|
-
// ctx: {
|
|
388
|
-
// input: Input;
|
|
389
|
-
// results: Results;
|
|
390
|
-
// context: Context;
|
|
391
|
-
// } & CallHelpers<Reg, ActionName>,
|
|
392
|
-
// ) => ResolveOut,
|
|
393
|
-
// dependsOn?: string[],
|
|
394
|
-
// options?: {},
|
|
395
|
-
// ): WorkflowBuilder<
|
|
396
|
-
// Reg,
|
|
397
|
-
// Input,
|
|
398
|
-
// Context,
|
|
399
|
-
// [...Steps, StepDef<Reg, ID, ActionName>],
|
|
400
|
-
// Results & {
|
|
401
|
-
// [K in ID]: StepResultFromResolve<Reg, ActionName, ResolveOut>;
|
|
402
|
-
// }
|
|
403
|
-
// > {
|
|
404
|
-
// const deps = dependsOn ?? [...this.frontier];
|
|
405
|
-
//
|
|
406
|
-
// this.steps.push({
|
|
407
|
-
// id,
|
|
408
|
-
// action,
|
|
409
|
-
// resolve: resolve ?? (() => ({ kind: "none" })),
|
|
410
|
-
// dependsOn: deps,
|
|
411
|
-
// });
|
|
412
|
-
//
|
|
413
|
-
// this.frontier = [id];
|
|
414
|
-
//
|
|
415
|
-
// return this as any;
|
|
416
|
-
// }
|
|
417
15
|
step(id, action, resolve, dependsOn, options) {
|
|
418
16
|
const deps = dependsOn ?? [...this.frontier];
|
|
419
17
|
this.steps.push({
|
|
@@ -421,6 +19,8 @@ export class WorkflowBuilder {
|
|
|
421
19
|
action,
|
|
422
20
|
resolve: resolve ?? (() => ({ kind: "none" })),
|
|
423
21
|
dependsOn: deps,
|
|
22
|
+
when: this.pendingWhen,
|
|
23
|
+
options,
|
|
424
24
|
});
|
|
425
25
|
this.frontier = [id];
|
|
426
26
|
return this;
|
|
@@ -440,6 +40,47 @@ export class WorkflowBuilder {
|
|
|
440
40
|
/* ------------------------------------------------ */
|
|
441
41
|
/* Parallel branches */
|
|
442
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
|
+
// }
|
|
443
84
|
parallel(...branches) {
|
|
444
85
|
const parentFrontier = [...this.frontier];
|
|
445
86
|
const branchEnds = [];
|
|
@@ -451,13 +92,16 @@ export class WorkflowBuilder {
|
|
|
451
92
|
this.steps.push(...b.steps);
|
|
452
93
|
});
|
|
453
94
|
this.frontier = branchEnds;
|
|
95
|
+
this.clearPendingWhen();
|
|
454
96
|
return this;
|
|
455
97
|
}
|
|
456
98
|
/* ------------------------------------------------ */
|
|
457
99
|
/* Join helper */
|
|
458
100
|
/* ------------------------------------------------ */
|
|
459
101
|
join(id, action, resolve, options) {
|
|
460
|
-
|
|
102
|
+
const result = this.step(id, action, resolve, [...this.frontier], options);
|
|
103
|
+
this.clearPendingWhen();
|
|
104
|
+
return result;
|
|
461
105
|
}
|
|
462
106
|
/* ------------------------------------------------ */
|
|
463
107
|
/* Subflow */
|
|
@@ -493,10 +137,11 @@ export class WorkflowBuilder {
|
|
|
493
137
|
/* Conditional */
|
|
494
138
|
/* ------------------------------------------------ */
|
|
495
139
|
when(predicate) {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
140
|
+
this.pendingWhen = predicate;
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
endWhen() {
|
|
144
|
+
this.clearPendingWhen();
|
|
500
145
|
return this;
|
|
501
146
|
}
|
|
502
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
|
};
|