@pogodisco/zephyr 1.5.5 → 1.5.6
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 +5 -0
- package/dist/workflow-composer.d.ts +10 -8
- package/dist/workflow-composer.js +35 -16
- package/dist/workflow-executor.js +512 -126
- package/package.json +1 -1
package/dist/types.d.ts
CHANGED
|
@@ -40,6 +40,11 @@ export type NormalizedCall = {
|
|
|
40
40
|
} | {
|
|
41
41
|
kind: "object";
|
|
42
42
|
args: any;
|
|
43
|
+
} | {
|
|
44
|
+
kind: "pipe_source";
|
|
45
|
+
args: any;
|
|
46
|
+
} | {
|
|
47
|
+
kind: "pipe_collect";
|
|
43
48
|
};
|
|
44
49
|
export type CallHelpers<Reg extends ActionRegistry, ActionName extends keyof Reg> = {
|
|
45
50
|
args: (...args: ActionParams<Reg, ActionName>) => {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ActionRegistry, ActionReturn, CallHelpers, NormalizedCall, ServiceParams, ServiceRegistry, ServiceReturn, Simplify } from "./types.js";
|
|
2
|
-
import { PipeBuilder, PipeDef, PipeFinalType, StepsOfPipeBuilder } from "./workflow-composer-pipe.js";
|
|
3
2
|
type WorkflowInput<T> = T extends WorkflowDef<any, infer I, any, any, any> ? I : never;
|
|
4
3
|
type WorkflowOutput<T> = T extends WorkflowDef<any, any, any, any, infer O> ? unknown extends O ? undefined : O : undefined;
|
|
5
4
|
export type StepDef<Reg extends ActionRegistry, ID extends string = string, ActionName extends keyof Reg = any> = {
|
|
@@ -11,7 +10,9 @@ export type StepDef<Reg extends ActionRegistry, ID extends string = string, Acti
|
|
|
11
10
|
options?: StepOptions<any, any>;
|
|
12
11
|
__subflowId?: string;
|
|
13
12
|
serviceCall?: ServiceCall;
|
|
14
|
-
pipe?:
|
|
13
|
+
pipe?: {
|
|
14
|
+
workflow: WorkflowDef<any, any, any>;
|
|
15
|
+
};
|
|
15
16
|
};
|
|
16
17
|
export type WorkflowDef<Reg extends ActionRegistry, Input, Results, Steps extends StepDef<Reg, any, any>[] = StepDef<Reg, any, any>[], Output = undefined> = {
|
|
17
18
|
name: string;
|
|
@@ -33,6 +34,9 @@ type StepOptions<Input, Results> = {
|
|
|
33
34
|
timeout?: number;
|
|
34
35
|
continueOnError?: boolean;
|
|
35
36
|
onError?: (err: unknown, ctx: StepRuntimeCtx<Input, Results>) => any;
|
|
37
|
+
pipe?: {
|
|
38
|
+
parallel?: boolean;
|
|
39
|
+
};
|
|
36
40
|
label?: string;
|
|
37
41
|
meta?: Record<string, any>;
|
|
38
42
|
};
|
|
@@ -45,6 +49,7 @@ type MergeBranchSteps<Branches extends readonly any[], Acc extends any[]> = Bran
|
|
|
45
49
|
...Acc,
|
|
46
50
|
...(Head extends WorkflowBuilder<any, any, any, any, infer Steps, any> ? Steps : [])
|
|
47
51
|
]> : Acc;
|
|
52
|
+
type LastStepOfBranch<B> = B extends WorkflowBuilder<any, any, any, any, any, infer Results> ? Results extends Record<string, infer Last> ? Last : never : never;
|
|
48
53
|
export declare class WorkflowBuilder<Reg extends ActionRegistry, Services extends ServiceRegistry, WFReg extends Record<string, WorkflowDef<any, any, any, any, any>>, //
|
|
49
54
|
Input = unknown, Steps extends StepDef<Reg, any, any>[] = [], Results = {}, Output = undefined> {
|
|
50
55
|
private name;
|
|
@@ -88,14 +93,11 @@ Input = unknown, Steps extends StepDef<Reg, any, any>[] = [], Results = {}, Outp
|
|
|
88
93
|
], Simplify<Results & {
|
|
89
94
|
[K in ID]: ServiceReturn<Services, SK, MK>;
|
|
90
95
|
}>>;
|
|
91
|
-
pipe<ID extends string, Arr extends any[],
|
|
96
|
+
pipe<ID extends string, Arr extends any[], Branch extends WorkflowBuilder<Reg, Services, WFReg, Arr[number], any, any>>(id: ID, input: (ctx: {
|
|
92
97
|
input: Input;
|
|
93
98
|
results: Results;
|
|
94
|
-
} & Results) => Arr, builder: (
|
|
95
|
-
|
|
96
|
-
StepDef<Reg, ID, any>
|
|
97
|
-
], Simplify<Results & {
|
|
98
|
-
[K in ID]: PipeFinalType<StepsOfPipeBuilder<PB>, Reg, Services>[];
|
|
99
|
+
} & Results) => Arr, builder: (b: WorkflowBuilder<Reg, Services, WFReg, Arr[number], [], Results>) => Branch, options?: StepOptions<Input, Results>): WorkflowBuilder<Reg, Services, WFReg, Input, Steps, Simplify<Results & {
|
|
100
|
+
[K in ID]: LastStepOfBranch<Branch>[];
|
|
99
101
|
}>>;
|
|
100
102
|
as<NewType>(): WorkflowBuilder<Reg, Services, WFReg, Input, Steps, Steps extends [...infer Rest, infer Last] ? Last extends StepDef<Reg, infer ID, any> ? Simplify<Omit<Results, ID> & {
|
|
101
103
|
[K in ID]: NewType;
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import { generateWorkflowId } from "./utils.js";
|
|
2
|
-
import { PipeBuilder, } from "./workflow-composer-pipe.js";
|
|
3
|
-
/* ------------------------------------------------ */
|
|
4
|
-
/* FLUENT WORKFLOW BUILDER */
|
|
5
|
-
/* ------------------------------------------------ */
|
|
6
2
|
export class WorkflowBuilder {
|
|
7
3
|
clearPendingWhen() {
|
|
8
4
|
this.pendingWhen = undefined;
|
|
@@ -50,24 +46,47 @@ export class WorkflowBuilder {
|
|
|
50
46
|
this.frontier = [id];
|
|
51
47
|
return this;
|
|
52
48
|
}
|
|
53
|
-
/* ----------------------- */
|
|
54
|
-
/* Pipe */
|
|
55
|
-
/* ----------------------- */
|
|
56
49
|
pipe(id, input, builder, options) {
|
|
57
50
|
const deps = [...this.frontier];
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
51
|
+
const branchBuilder = new WorkflowBuilder(this.name);
|
|
52
|
+
branchBuilder.frontier = [];
|
|
53
|
+
branchBuilder.pendingWhen = this.pendingWhen;
|
|
54
|
+
const built = builder(branchBuilder);
|
|
55
|
+
const wfId = generateWorkflowId(id);
|
|
56
|
+
const entrySteps = built.steps.filter((s) => s.dependsOn.length === 0);
|
|
57
|
+
const hasDependents = new Set();
|
|
58
|
+
for (const step of built.steps) {
|
|
59
|
+
for (const dep of step.dependsOn)
|
|
60
|
+
hasDependents.add(dep);
|
|
61
|
+
}
|
|
62
|
+
const endSteps = built.steps.filter((s) => !hasDependents.has(s.id));
|
|
63
|
+
const subWf = {
|
|
64
|
+
_id: wfId,
|
|
65
|
+
input: {},
|
|
66
|
+
results: {},
|
|
67
|
+
name: `${id}_pipe`,
|
|
68
|
+
steps: built.steps,
|
|
69
|
+
// steps: built.steps,
|
|
70
|
+
entrySteps,
|
|
71
|
+
endSteps,
|
|
72
|
+
outputResolver: (ctx) => {
|
|
73
|
+
return built.frontier.length === 1
|
|
74
|
+
? ctx[built.frontier[0]]
|
|
75
|
+
: built.frontier.map((f) => ctx[f]);
|
|
76
|
+
},
|
|
77
|
+
};
|
|
61
78
|
this.steps.push({
|
|
62
79
|
id,
|
|
63
|
-
action: "
|
|
64
|
-
pipe: {
|
|
65
|
-
input,
|
|
66
|
-
steps,
|
|
67
|
-
},
|
|
80
|
+
action: "__pipe_map__",
|
|
68
81
|
dependsOn: deps,
|
|
69
82
|
when: this.pendingWhen,
|
|
70
|
-
resolve: () => ({
|
|
83
|
+
resolve: (ctx) => ({
|
|
84
|
+
kind: "pipe_source",
|
|
85
|
+
args: input(ctx),
|
|
86
|
+
}),
|
|
87
|
+
pipe: {
|
|
88
|
+
workflow: subWf,
|
|
89
|
+
},
|
|
71
90
|
options,
|
|
72
91
|
});
|
|
73
92
|
this.frontier = [id];
|
|
@@ -1,3 +1,474 @@
|
|
|
1
|
+
// import { composeObserver } from "./observer.js";
|
|
2
|
+
// import {
|
|
3
|
+
// ActionRegistry,
|
|
4
|
+
// ExecutionFrame,
|
|
5
|
+
// Executor,
|
|
6
|
+
// NormalizedCall,
|
|
7
|
+
// ServiceRegistry,
|
|
8
|
+
// WorkflowObserver,
|
|
9
|
+
// } from "./types.js";
|
|
10
|
+
// import { StepDef, WorkflowDef } from "./workflow-composer.js";
|
|
11
|
+
//
|
|
12
|
+
// export function createCallHelpers() {
|
|
13
|
+
// return {
|
|
14
|
+
// args: (...args: any[]) => ({ kind: "positional", args }),
|
|
15
|
+
// obj: (arg: any) => ({ kind: "object", args: arg }),
|
|
16
|
+
// none: () => ({ kind: "none" }),
|
|
17
|
+
// };
|
|
18
|
+
// }
|
|
19
|
+
//
|
|
20
|
+
// async function withTimeout<T>(promise: Promise<T>, ms?: number): Promise<T> {
|
|
21
|
+
// if (!ms) return promise;
|
|
22
|
+
// return Promise.race([
|
|
23
|
+
// promise,
|
|
24
|
+
// new Promise<T>((_, reject) =>
|
|
25
|
+
// setTimeout(() => reject(new Error("Timeout")), ms),
|
|
26
|
+
// ),
|
|
27
|
+
// ]);
|
|
28
|
+
// }
|
|
29
|
+
//
|
|
30
|
+
// function createStepCtx(input: any, results: any) {
|
|
31
|
+
// const helpers = createCallHelpers();
|
|
32
|
+
//
|
|
33
|
+
// return new Proxy(
|
|
34
|
+
// {
|
|
35
|
+
// input,
|
|
36
|
+
// results,
|
|
37
|
+
// ...helpers,
|
|
38
|
+
// },
|
|
39
|
+
// {
|
|
40
|
+
// get(target, prop) {
|
|
41
|
+
// // 1. explicit keys first
|
|
42
|
+
// if (prop in target) return target[prop as keyof typeof target];
|
|
43
|
+
//
|
|
44
|
+
// // 2. fallback to results
|
|
45
|
+
// if (prop in results) return results[prop as keyof typeof target];
|
|
46
|
+
//
|
|
47
|
+
// return undefined;
|
|
48
|
+
// },
|
|
49
|
+
// },
|
|
50
|
+
// );
|
|
51
|
+
// }
|
|
52
|
+
//
|
|
53
|
+
// // Helper to run action with retry support
|
|
54
|
+
// async function runWithRetry(
|
|
55
|
+
// actionFn: () => Promise<any>,
|
|
56
|
+
// stepOptions?: {
|
|
57
|
+
// retry?: number;
|
|
58
|
+
// retryDelay?: number | ((attempt: number) => number);
|
|
59
|
+
// },
|
|
60
|
+
// ): Promise<any> {
|
|
61
|
+
// const maxRetries = stepOptions?.retry ?? 0;
|
|
62
|
+
// let lastError: any;
|
|
63
|
+
//
|
|
64
|
+
// for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
65
|
+
// try {
|
|
66
|
+
// return await actionFn();
|
|
67
|
+
// } catch (err) {
|
|
68
|
+
// lastError = err;
|
|
69
|
+
//
|
|
70
|
+
// if (attempt === maxRetries) break;
|
|
71
|
+
//
|
|
72
|
+
// const delay = stepOptions?.retryDelay;
|
|
73
|
+
// if (typeof delay === "number") {
|
|
74
|
+
// await new Promise((r) => setTimeout(r, delay));
|
|
75
|
+
// } else if (typeof delay === "function") {
|
|
76
|
+
// await new Promise((r) => setTimeout(r, delay(attempt)));
|
|
77
|
+
// }
|
|
78
|
+
// }
|
|
79
|
+
// }
|
|
80
|
+
//
|
|
81
|
+
// throw lastError;
|
|
82
|
+
// }
|
|
83
|
+
//
|
|
84
|
+
// export async function executeWorkflow<Reg extends ActionRegistry, I, R, O = R>({
|
|
85
|
+
// workflow,
|
|
86
|
+
// actionRegistry,
|
|
87
|
+
// services,
|
|
88
|
+
// input,
|
|
89
|
+
//
|
|
90
|
+
// depsExecutors,
|
|
91
|
+
// observers = [],
|
|
92
|
+
// }: {
|
|
93
|
+
// workflow: WorkflowDef<Reg, I, any, any, O>;
|
|
94
|
+
//
|
|
95
|
+
// actionRegistry: Reg;
|
|
96
|
+
// services: ServiceRegistry;
|
|
97
|
+
// input: I;
|
|
98
|
+
//
|
|
99
|
+
// depsExecutors: Record<string, Executor>;
|
|
100
|
+
// observers: WorkflowObserver<Reg>[];
|
|
101
|
+
// }): Promise<{
|
|
102
|
+
// // results: WorkflowResults<WR[K]>;
|
|
103
|
+
// output: O;
|
|
104
|
+
// extras: Record<string, any>;
|
|
105
|
+
// }> {
|
|
106
|
+
// const results: Record<string, any> = {};
|
|
107
|
+
// const extras: Record<string, any> = {};
|
|
108
|
+
// extras.frames = {} as Record<string, ExecutionFrame>;
|
|
109
|
+
//
|
|
110
|
+
// // -----------------------------
|
|
111
|
+
// // Fully typed step map
|
|
112
|
+
// // -----------------------------
|
|
113
|
+
// // const stepById: Map<string, StepDef<Reg, any, any>> = new Map(
|
|
114
|
+
// // workflow.steps.map((s: StepDef<Reg, any, any>) => [s.id, s]),
|
|
115
|
+
// // );
|
|
116
|
+
// const stepById = new Map(
|
|
117
|
+
// workflow.steps.map((s: StepDef<Reg, any, any>) => [s.id, s]),
|
|
118
|
+
// );
|
|
119
|
+
// console.log("SERVICES", services);
|
|
120
|
+
// console.log("WF STEPS", workflow.steps);
|
|
121
|
+
// // return;
|
|
122
|
+
//
|
|
123
|
+
// const remainingDeps = new Map<string, number>();
|
|
124
|
+
// const dependents = new Map<string, string[]>();
|
|
125
|
+
// const ready: string[] = [];
|
|
126
|
+
//
|
|
127
|
+
// for (const step of workflow.steps) {
|
|
128
|
+
// remainingDeps.set(step.id, step.dependsOn.length);
|
|
129
|
+
// if (step.dependsOn.length === 0) ready.push(step.id);
|
|
130
|
+
//
|
|
131
|
+
// for (const dep of step.dependsOn) {
|
|
132
|
+
// if (!dependents.has(dep)) dependents.set(dep, []);
|
|
133
|
+
// dependents.get(dep)!.push(step.id);
|
|
134
|
+
// }
|
|
135
|
+
// }
|
|
136
|
+
//
|
|
137
|
+
// let completed = 0;
|
|
138
|
+
//
|
|
139
|
+
// // -----------------------------
|
|
140
|
+
// // Normalized runner
|
|
141
|
+
// // -----------------------------
|
|
142
|
+
// const runAction = async (
|
|
143
|
+
// input: NormalizedCall | undefined,
|
|
144
|
+
// action: any,
|
|
145
|
+
// ): Promise<any> => {
|
|
146
|
+
// if (!input) return await action();
|
|
147
|
+
//
|
|
148
|
+
// switch (input.kind) {
|
|
149
|
+
// case "none":
|
|
150
|
+
// return await action();
|
|
151
|
+
// case "positional":
|
|
152
|
+
// return await action(...input.args);
|
|
153
|
+
// case "object":
|
|
154
|
+
// return await action(input.args);
|
|
155
|
+
// default:
|
|
156
|
+
// throw new Error(
|
|
157
|
+
// `Unknown ResolvedStepInput kind: ${(input as any).kind}`,
|
|
158
|
+
// );
|
|
159
|
+
// }
|
|
160
|
+
// };
|
|
161
|
+
//
|
|
162
|
+
// // -----------------------------
|
|
163
|
+
// // Execution loop
|
|
164
|
+
// // -----------------------------
|
|
165
|
+
//
|
|
166
|
+
// while (ready.length > 0) {
|
|
167
|
+
// const batch = ready.splice(0);
|
|
168
|
+
//
|
|
169
|
+
// await Promise.all(
|
|
170
|
+
// batch.map(async (stepId) => {
|
|
171
|
+
// const step = stepById.get(stepId)! as StepDef<Reg, any, any>;
|
|
172
|
+
//
|
|
173
|
+
// const frame: ExecutionFrame = {
|
|
174
|
+
// stepId,
|
|
175
|
+
// attempts: 0,
|
|
176
|
+
// start: Date.now(),
|
|
177
|
+
// };
|
|
178
|
+
// extras.frames[stepId] = frame;
|
|
179
|
+
//
|
|
180
|
+
// const ctx = {
|
|
181
|
+
// stepId,
|
|
182
|
+
// input,
|
|
183
|
+
// results,
|
|
184
|
+
//
|
|
185
|
+
// actionRegistry,
|
|
186
|
+
// extras,
|
|
187
|
+
// frame,
|
|
188
|
+
// };
|
|
189
|
+
//
|
|
190
|
+
// const stepCtx = createStepCtx(input, results);
|
|
191
|
+
// // const stepCtx = {
|
|
192
|
+
// // input,
|
|
193
|
+
// // results,
|
|
194
|
+
// //
|
|
195
|
+
// // ...createCallHelpers(),
|
|
196
|
+
// // };
|
|
197
|
+
//
|
|
198
|
+
// const resolvedArgs = step.resolve?.(stepCtx);
|
|
199
|
+
//
|
|
200
|
+
// frame.input = resolvedArgs;
|
|
201
|
+
//
|
|
202
|
+
// const core = async () => {
|
|
203
|
+
// frame.attempts++;
|
|
204
|
+
//
|
|
205
|
+
// // -----------------------------
|
|
206
|
+
// // ✅ SINGLE when evaluation
|
|
207
|
+
// // -----------------------------
|
|
208
|
+
// if (step.when && !step.when(stepCtx)) {
|
|
209
|
+
// frame.input = undefined; // 👈 important for observer consistency
|
|
210
|
+
// frame.output = undefined;
|
|
211
|
+
// frame.end = Date.now();
|
|
212
|
+
// frame.skipped = true;
|
|
213
|
+
// results[step.id] = undefined;
|
|
214
|
+
// return undefined;
|
|
215
|
+
// }
|
|
216
|
+
//
|
|
217
|
+
// // -----------------------------
|
|
218
|
+
// // Pipe V2 handling
|
|
219
|
+
// // -----------------------------
|
|
220
|
+
//
|
|
221
|
+
// if (step.action === "__pipe_source__") {
|
|
222
|
+
// // The source step produces the array
|
|
223
|
+
// const args = step.resolve!(stepCtx);
|
|
224
|
+
// results[step.id] = args.args;
|
|
225
|
+
// frame.output = args;
|
|
226
|
+
// frame.end = Date.now();
|
|
227
|
+
// return args.args;
|
|
228
|
+
// }
|
|
229
|
+
//
|
|
230
|
+
// if (step.action === "__pipe_collect__") {
|
|
231
|
+
// // Collect outputs of branch steps
|
|
232
|
+
// const pipeSourceId = (step as any).__pipeSource;
|
|
233
|
+
// const sourceItems: any[] = results[pipeSourceId] ?? [];
|
|
234
|
+
//
|
|
235
|
+
// const branchStepIds = step.dependsOn; // prefixed branch step IDs
|
|
236
|
+
//
|
|
237
|
+
// const collected = sourceItems.map((_, index) => {
|
|
238
|
+
// // Last step for each item in the branch
|
|
239
|
+
// const lastStepId =
|
|
240
|
+
// branchStepIds[
|
|
241
|
+
// branchStepIds.length - sourceItems.length + index
|
|
242
|
+
// ];
|
|
243
|
+
// return results[lastStepId];
|
|
244
|
+
// });
|
|
245
|
+
//
|
|
246
|
+
// results[step.id] = collected;
|
|
247
|
+
// frame.output = collected;
|
|
248
|
+
// frame.end = Date.now();
|
|
249
|
+
// return collected;
|
|
250
|
+
// }
|
|
251
|
+
//
|
|
252
|
+
// // Branch steps inside pipeV2
|
|
253
|
+
// //
|
|
254
|
+
// //
|
|
255
|
+
// // Branch steps inside pipeV2
|
|
256
|
+
// if (step.__pipeSource) {
|
|
257
|
+
// const pipeSourceId = step.__pipeSource;
|
|
258
|
+
// const sourceItems: any[] = results[pipeSourceId] ?? [];
|
|
259
|
+
//
|
|
260
|
+
// // Determine the branch index
|
|
261
|
+
// const branchSteps = workflow.steps.filter(
|
|
262
|
+
// (s) => s.__pipeSource === pipeSourceId,
|
|
263
|
+
// );
|
|
264
|
+
//
|
|
265
|
+
// // For each source item, run sequential branch steps
|
|
266
|
+
// for (let i = 0; i < sourceItems.length; i++) {
|
|
267
|
+
// const item = sourceItems[i];
|
|
268
|
+
// let current = item;
|
|
269
|
+
//
|
|
270
|
+
// for (const branchStep of branchSteps) {
|
|
271
|
+
// const branchCtx = createStepCtx(current, results);
|
|
272
|
+
//
|
|
273
|
+
// // check step.when
|
|
274
|
+
// if (branchStep.when && !branchStep.when(branchCtx)) {
|
|
275
|
+
// results[branchStep.id] = undefined;
|
|
276
|
+
// continue;
|
|
277
|
+
// }
|
|
278
|
+
//
|
|
279
|
+
// let actionFn;
|
|
280
|
+
// if (branchStep.__subflowId) {
|
|
281
|
+
// const [modId, subWfId] = branchStep.__subflowId.split(".");
|
|
282
|
+
// actionFn = async () =>
|
|
283
|
+
// depsExecutors[modId]
|
|
284
|
+
// .run(subWfId, branchCtx, services, observers)
|
|
285
|
+
// .then((r) => r.output);
|
|
286
|
+
// } else if (branchStep.action === "__service__") {
|
|
287
|
+
// const { service, method } = branchStep.serviceCall!;
|
|
288
|
+
// actionFn = async () =>
|
|
289
|
+
// services[service][method](
|
|
290
|
+
// ...branchStep.resolve!(branchCtx).args,
|
|
291
|
+
// );
|
|
292
|
+
// } else {
|
|
293
|
+
// actionFn = async () =>
|
|
294
|
+
// actionRegistry[branchStep.action](
|
|
295
|
+
// ...branchStep.resolve!(branchCtx).args,
|
|
296
|
+
// );
|
|
297
|
+
// }
|
|
298
|
+
//
|
|
299
|
+
// current = await runWithRetry(actionFn, branchStep.options);
|
|
300
|
+
// results[branchStep.id] = current;
|
|
301
|
+
// }
|
|
302
|
+
// }
|
|
303
|
+
//
|
|
304
|
+
// // collector step just gathers last step of each item
|
|
305
|
+
// return (results[step.id] = branchSteps.length
|
|
306
|
+
// ? branchSteps[branchSteps.length - 1].map((s) => results[s.id])
|
|
307
|
+
// : []);
|
|
308
|
+
// }
|
|
309
|
+
//
|
|
310
|
+
// // -----------------------------
|
|
311
|
+
// // Pipe handling
|
|
312
|
+
// // -----------------------------
|
|
313
|
+
//
|
|
314
|
+
// if (step.pipe && step.pipe.steps) {
|
|
315
|
+
// const items = step.pipe.input(stepCtx);
|
|
316
|
+
//
|
|
317
|
+
// const pipeResults = await Promise.all(
|
|
318
|
+
// items.map(async (item) => {
|
|
319
|
+
// let current = item;
|
|
320
|
+
//
|
|
321
|
+
// for (const pipeStep of step.pipe!.steps) {
|
|
322
|
+
// const pipeCtx = new Proxy(
|
|
323
|
+
// {
|
|
324
|
+
// current,
|
|
325
|
+
// results,
|
|
326
|
+
// ...createCallHelpers(),
|
|
327
|
+
// },
|
|
328
|
+
// {
|
|
329
|
+
// get(target, prop) {
|
|
330
|
+
// if (prop in target)
|
|
331
|
+
// return target[prop as keyof typeof target];
|
|
332
|
+
//
|
|
333
|
+
// if (prop in results)
|
|
334
|
+
// return results[prop as keyof typeof target];
|
|
335
|
+
//
|
|
336
|
+
// return undefined;
|
|
337
|
+
// },
|
|
338
|
+
// },
|
|
339
|
+
// );
|
|
340
|
+
//
|
|
341
|
+
// const resolved = pipeStep.resolve(pipeCtx);
|
|
342
|
+
// let action;
|
|
343
|
+
//
|
|
344
|
+
// if (pipeStep.type === "action") {
|
|
345
|
+
// action = actionRegistry[pipeStep.action!];
|
|
346
|
+
// } else {
|
|
347
|
+
// action = services[pipeStep.service!][pipeStep.method!];
|
|
348
|
+
// }
|
|
349
|
+
//
|
|
350
|
+
// if (!action) {
|
|
351
|
+
// throw new Error(
|
|
352
|
+
// `Unknown ${pipeStep.type} in pipe step: ${
|
|
353
|
+
// pipeStep.type === "action"
|
|
354
|
+
// ? pipeStep.action
|
|
355
|
+
// : `${pipeStep.service}.${pipeStep.method}`
|
|
356
|
+
// }`,
|
|
357
|
+
// );
|
|
358
|
+
// }
|
|
359
|
+
//
|
|
360
|
+
// current = await runAction(resolved, action);
|
|
361
|
+
// }
|
|
362
|
+
//
|
|
363
|
+
// return current;
|
|
364
|
+
// }),
|
|
365
|
+
// );
|
|
366
|
+
//
|
|
367
|
+
// results[step.id] = pipeResults;
|
|
368
|
+
// frame.output = pipeResults;
|
|
369
|
+
// frame.end = Date.now();
|
|
370
|
+
// return pipeResults;
|
|
371
|
+
// }
|
|
372
|
+
// if (step.__subflowId) {
|
|
373
|
+
// // -----------------------------
|
|
374
|
+
// // Subflow handling
|
|
375
|
+
// // -----------------------------
|
|
376
|
+
// const [modId, subWfId] = step.__subflowId.split(".");
|
|
377
|
+
//
|
|
378
|
+
// const exec = depsExecutors[modId];
|
|
379
|
+
//
|
|
380
|
+
// const subExecution = await exec.run(
|
|
381
|
+
// subWfId,
|
|
382
|
+
// resolvedArgs,
|
|
383
|
+
// services,
|
|
384
|
+
// observers,
|
|
385
|
+
// );
|
|
386
|
+
//
|
|
387
|
+
// frame.output = subExecution.output;
|
|
388
|
+
// frame.end = Date.now();
|
|
389
|
+
// results[step.id] = subExecution.output;
|
|
390
|
+
//
|
|
391
|
+
// Object.assign(extras, subExecution.extras);
|
|
392
|
+
//
|
|
393
|
+
// return subExecution.output;
|
|
394
|
+
// }
|
|
395
|
+
//
|
|
396
|
+
// // -----------------------------
|
|
397
|
+
// // Normal action
|
|
398
|
+
// // -----------------------------
|
|
399
|
+
// const actionFn = async () => {
|
|
400
|
+
// let action = null;
|
|
401
|
+
// if (step.action === "__service__") {
|
|
402
|
+
// const { service, method } = step.serviceCall!;
|
|
403
|
+
//
|
|
404
|
+
// action = services[service][method];
|
|
405
|
+
// } else {
|
|
406
|
+
// action = actionRegistry[step.action];
|
|
407
|
+
// }
|
|
408
|
+
//
|
|
409
|
+
// console.log("action", action);
|
|
410
|
+
// return await runAction(resolvedArgs, action);
|
|
411
|
+
// };
|
|
412
|
+
//
|
|
413
|
+
// try {
|
|
414
|
+
// const result = await withTimeout(
|
|
415
|
+
// runWithRetry(actionFn, step.options),
|
|
416
|
+
// step.options?.timeout,
|
|
417
|
+
// );
|
|
418
|
+
//
|
|
419
|
+
// frame.output = result;
|
|
420
|
+
// frame.end = Date.now();
|
|
421
|
+
// results[step.id] = result;
|
|
422
|
+
// return result;
|
|
423
|
+
// } catch (err) {
|
|
424
|
+
// frame.error = err;
|
|
425
|
+
// frame.end = Date.now();
|
|
426
|
+
//
|
|
427
|
+
// if (step.options?.onError) {
|
|
428
|
+
// const fallback = step.options.onError(err, ctx);
|
|
429
|
+
// results[step.id] = fallback;
|
|
430
|
+
// return fallback;
|
|
431
|
+
// }
|
|
432
|
+
//
|
|
433
|
+
// if (step.options?.continueOnError) {
|
|
434
|
+
// results[step.id] = undefined;
|
|
435
|
+
// return undefined;
|
|
436
|
+
// }
|
|
437
|
+
//
|
|
438
|
+
// throw err;
|
|
439
|
+
// }
|
|
440
|
+
// };
|
|
441
|
+
//
|
|
442
|
+
// const composed = composeObserver(observers, ctx, core);
|
|
443
|
+
// await composed();
|
|
444
|
+
//
|
|
445
|
+
// for (const childId of dependents.get(stepId) ?? []) {
|
|
446
|
+
// const remaining = remainingDeps.get(childId)! - 1;
|
|
447
|
+
// remainingDeps.set(childId, remaining);
|
|
448
|
+
// if (remaining === 0) ready.push(childId);
|
|
449
|
+
// }
|
|
450
|
+
//
|
|
451
|
+
// completed++;
|
|
452
|
+
// }),
|
|
453
|
+
// );
|
|
454
|
+
// }
|
|
455
|
+
//
|
|
456
|
+
// if (completed !== workflow.steps.length) {
|
|
457
|
+
// throw new Error("Workflow execution failed (cycle or missing dependency)");
|
|
458
|
+
// }
|
|
459
|
+
//
|
|
460
|
+
// const outputCtx = createStepCtx(input, results);
|
|
461
|
+
// const output = workflow.outputResolver
|
|
462
|
+
// ? workflow.outputResolver(outputCtx)
|
|
463
|
+
// : results;
|
|
464
|
+
//
|
|
465
|
+
// return {
|
|
466
|
+
// // results: results as WorkflowResults<WR[K]>,
|
|
467
|
+
// output: output as O,
|
|
468
|
+
// extras,
|
|
469
|
+
// };
|
|
470
|
+
// }
|
|
471
|
+
//
|
|
1
472
|
import { composeObserver } from "./observer.js";
|
|
2
473
|
export function createCallHelpers() {
|
|
3
474
|
return {
|
|
@@ -16,23 +487,16 @@ async function withTimeout(promise, ms) {
|
|
|
16
487
|
}
|
|
17
488
|
function createStepCtx(input, results) {
|
|
18
489
|
const helpers = createCallHelpers();
|
|
19
|
-
return new Proxy({
|
|
20
|
-
input,
|
|
21
|
-
results,
|
|
22
|
-
...helpers,
|
|
23
|
-
}, {
|
|
490
|
+
return new Proxy({ input, results, ...helpers }, {
|
|
24
491
|
get(target, prop) {
|
|
25
|
-
// 1. explicit keys first
|
|
26
492
|
if (prop in target)
|
|
27
493
|
return target[prop];
|
|
28
|
-
// 2. fallback to results
|
|
29
494
|
if (prop in results)
|
|
30
495
|
return results[prop];
|
|
31
496
|
return undefined;
|
|
32
497
|
},
|
|
33
498
|
});
|
|
34
499
|
}
|
|
35
|
-
// Helper to run action with retry support
|
|
36
500
|
async function runWithRetry(actionFn, stepOptions) {
|
|
37
501
|
const maxRetries = stepOptions?.retry ?? 0;
|
|
38
502
|
let lastError;
|
|
@@ -45,26 +509,19 @@ async function runWithRetry(actionFn, stepOptions) {
|
|
|
45
509
|
if (attempt === maxRetries)
|
|
46
510
|
break;
|
|
47
511
|
const delay = stepOptions?.retryDelay;
|
|
48
|
-
if (typeof delay === "number")
|
|
512
|
+
if (typeof delay === "number")
|
|
49
513
|
await new Promise((r) => setTimeout(r, delay));
|
|
50
|
-
|
|
51
|
-
else if (typeof delay === "function") {
|
|
514
|
+
else if (typeof delay === "function")
|
|
52
515
|
await new Promise((r) => setTimeout(r, delay(attempt)));
|
|
53
|
-
}
|
|
54
516
|
}
|
|
55
517
|
}
|
|
56
518
|
throw lastError;
|
|
57
519
|
}
|
|
58
520
|
export async function executeWorkflow({ workflow, actionRegistry, services, input, depsExecutors, observers = [], }) {
|
|
59
521
|
const results = {};
|
|
60
|
-
const extras = {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
// Fully typed step map
|
|
64
|
-
// -----------------------------
|
|
65
|
-
// const stepById: Map<string, StepDef<Reg, any, any>> = new Map(
|
|
66
|
-
// workflow.steps.map((s: StepDef<Reg, any, any>) => [s.id, s]),
|
|
67
|
-
// );
|
|
522
|
+
const extras = {
|
|
523
|
+
frames: {},
|
|
524
|
+
};
|
|
68
525
|
const stepById = new Map(workflow.steps.map((s) => [s.id, s]));
|
|
69
526
|
const remainingDeps = new Map();
|
|
70
527
|
const dependents = new Map();
|
|
@@ -79,10 +536,6 @@ export async function executeWorkflow({ workflow, actionRegistry, services, inpu
|
|
|
79
536
|
dependents.get(dep).push(step.id);
|
|
80
537
|
}
|
|
81
538
|
}
|
|
82
|
-
let completed = 0;
|
|
83
|
-
// -----------------------------
|
|
84
|
-
// Normalized runner
|
|
85
|
-
// -----------------------------
|
|
86
539
|
const runAction = async (input, action) => {
|
|
87
540
|
if (!input)
|
|
88
541
|
return await action();
|
|
@@ -97,9 +550,7 @@ export async function executeWorkflow({ workflow, actionRegistry, services, inpu
|
|
|
97
550
|
throw new Error(`Unknown ResolvedStepInput kind: ${input.kind}`);
|
|
98
551
|
}
|
|
99
552
|
};
|
|
100
|
-
|
|
101
|
-
// Execution loop
|
|
102
|
-
// -----------------------------
|
|
553
|
+
let completed = 0;
|
|
103
554
|
while (ready.length > 0) {
|
|
104
555
|
const batch = ready.splice(0);
|
|
105
556
|
await Promise.all(batch.map(async (stepId) => {
|
|
@@ -110,107 +561,50 @@ export async function executeWorkflow({ workflow, actionRegistry, services, inpu
|
|
|
110
561
|
start: Date.now(),
|
|
111
562
|
};
|
|
112
563
|
extras.frames[stepId] = frame;
|
|
113
|
-
const ctx = {
|
|
114
|
-
stepId,
|
|
115
|
-
input,
|
|
116
|
-
results,
|
|
117
|
-
actionRegistry,
|
|
118
|
-
extras,
|
|
119
|
-
frame,
|
|
120
|
-
};
|
|
564
|
+
const ctx = { stepId, input, results, actionRegistry, extras, frame };
|
|
121
565
|
const stepCtx = createStepCtx(input, results);
|
|
122
|
-
// const stepCtx = {
|
|
123
|
-
// input,
|
|
124
|
-
// results,
|
|
125
|
-
//
|
|
126
|
-
// ...createCallHelpers(),
|
|
127
|
-
// };
|
|
128
566
|
const resolvedArgs = step.resolve?.(stepCtx);
|
|
129
567
|
frame.input = resolvedArgs;
|
|
130
568
|
const core = async () => {
|
|
131
569
|
frame.attempts++;
|
|
132
|
-
//
|
|
133
|
-
// ✅ SINGLE when evaluation
|
|
134
|
-
// -----------------------------
|
|
570
|
+
// Skip if .when is false
|
|
135
571
|
if (step.when && !step.when(stepCtx)) {
|
|
136
|
-
frame.input = undefined;
|
|
572
|
+
frame.input = undefined;
|
|
137
573
|
frame.output = undefined;
|
|
138
574
|
frame.end = Date.now();
|
|
139
575
|
frame.skipped = true;
|
|
140
576
|
results[step.id] = undefined;
|
|
141
577
|
return undefined;
|
|
142
578
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
// },
|
|
168
|
-
// );
|
|
169
|
-
// }
|
|
170
|
-
// const pipeCtx = {
|
|
171
|
-
// current,
|
|
172
|
-
// results,
|
|
173
|
-
// ...createCallHelpers(),
|
|
174
|
-
// };
|
|
175
|
-
const pipeCtx = new Proxy({
|
|
176
|
-
current,
|
|
177
|
-
results,
|
|
178
|
-
...createCallHelpers(),
|
|
179
|
-
}, {
|
|
180
|
-
get(target, prop) {
|
|
181
|
-
if (prop in target)
|
|
182
|
-
return target[prop];
|
|
183
|
-
if (prop in results)
|
|
184
|
-
return results[prop];
|
|
185
|
-
return undefined;
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
const resolved = pipeStep.resolve(pipeCtx);
|
|
189
|
-
let action;
|
|
190
|
-
if (pipeStep.type === "action") {
|
|
191
|
-
action = actionRegistry[pipeStep.action];
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
action = services[pipeStep.service][pipeStep.method];
|
|
195
|
-
}
|
|
196
|
-
if (!action) {
|
|
197
|
-
throw new Error(`Unknown ${pipeStep.type} in pipe step: ${pipeStep.type === "action"
|
|
198
|
-
? pipeStep.action
|
|
199
|
-
: `${pipeStep.service}.${pipeStep.method}`}`);
|
|
200
|
-
}
|
|
201
|
-
current = await runAction(resolved, action);
|
|
202
|
-
}
|
|
203
|
-
return current;
|
|
204
|
-
}));
|
|
205
|
-
results[step.id] = pipeResults;
|
|
206
|
-
frame.output = pipeResults;
|
|
579
|
+
if (step.action === "__pipe_map__") {
|
|
580
|
+
const isParallel = step?.options?.pipe?.parallel;
|
|
581
|
+
const items = step.resolve(stepCtx).args ?? [];
|
|
582
|
+
const { workflow } = step.pipe;
|
|
583
|
+
const executions = items.map((item, index) => executeWorkflow({
|
|
584
|
+
workflow,
|
|
585
|
+
input: item,
|
|
586
|
+
services,
|
|
587
|
+
actionRegistry,
|
|
588
|
+
depsExecutors,
|
|
589
|
+
observers,
|
|
590
|
+
}).then((res) => ({
|
|
591
|
+
index,
|
|
592
|
+
output: res.output,
|
|
593
|
+
extras: res.extras,
|
|
594
|
+
})));
|
|
595
|
+
const settled = await Promise.all(executions);
|
|
596
|
+
console.log("SETTLED", settled);
|
|
597
|
+
// ✅ preserve order (IMPORTANT)
|
|
598
|
+
const outputs = settled
|
|
599
|
+
.sort((a, b) => a.index - b.index)
|
|
600
|
+
.map((r) => r.output);
|
|
601
|
+
results[step.id] = outputs;
|
|
602
|
+
frame.output = outputs;
|
|
207
603
|
frame.end = Date.now();
|
|
208
|
-
return
|
|
604
|
+
return outputs;
|
|
209
605
|
}
|
|
606
|
+
// ---------------- Subflow ----------------
|
|
210
607
|
if (step.__subflowId) {
|
|
211
|
-
// -----------------------------
|
|
212
|
-
// Subflow handling
|
|
213
|
-
// -----------------------------
|
|
214
608
|
const [modId, subWfId] = step.__subflowId.split(".");
|
|
215
609
|
const exec = depsExecutors[modId];
|
|
216
610
|
const subExecution = await exec.run(subWfId, resolvedArgs, services, observers);
|
|
@@ -220,11 +614,9 @@ export async function executeWorkflow({ workflow, actionRegistry, services, inpu
|
|
|
220
614
|
Object.assign(extras, subExecution.extras);
|
|
221
615
|
return subExecution.output;
|
|
222
616
|
}
|
|
223
|
-
//
|
|
224
|
-
// Normal action
|
|
225
|
-
// -----------------------------
|
|
617
|
+
// ---------------- Normal action ----------------
|
|
226
618
|
const actionFn = async () => {
|
|
227
|
-
let action
|
|
619
|
+
let action;
|
|
228
620
|
if (step.action === "__service__") {
|
|
229
621
|
const { service, method } = step.serviceCall;
|
|
230
622
|
action = services[service][method];
|
|
@@ -232,8 +624,7 @@ export async function executeWorkflow({ workflow, actionRegistry, services, inpu
|
|
|
232
624
|
else {
|
|
233
625
|
action = actionRegistry[step.action];
|
|
234
626
|
}
|
|
235
|
-
|
|
236
|
-
return await runAction(resolvedArgs, action);
|
|
627
|
+
return runAction(resolvedArgs, action);
|
|
237
628
|
};
|
|
238
629
|
try {
|
|
239
630
|
const result = await withTimeout(runWithRetry(actionFn, step.options), step.options?.timeout);
|
|
@@ -268,16 +659,11 @@ export async function executeWorkflow({ workflow, actionRegistry, services, inpu
|
|
|
268
659
|
completed++;
|
|
269
660
|
}));
|
|
270
661
|
}
|
|
271
|
-
if (completed !== workflow.steps.length)
|
|
662
|
+
if (completed !== workflow.steps.length)
|
|
272
663
|
throw new Error("Workflow execution failed (cycle or missing dependency)");
|
|
273
|
-
}
|
|
274
664
|
const outputCtx = createStepCtx(input, results);
|
|
275
665
|
const output = workflow.outputResolver
|
|
276
666
|
? workflow.outputResolver(outputCtx)
|
|
277
667
|
: results;
|
|
278
|
-
return {
|
|
279
|
-
// results: results as WorkflowResults<WR[K]>,
|
|
280
|
-
output: output,
|
|
281
|
-
extras,
|
|
282
|
-
};
|
|
668
|
+
return { output: output, extras };
|
|
283
669
|
}
|