@pogodisco/zephyr 1.5.4 → 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/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types.d.ts +5 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.js +15 -0
- package/dist/workflow-composer.d.ts +10 -8
- package/dist/workflow-composer.js +35 -16
- package/dist/workflow-executor.js +512 -94
- package/dist/workflow-module.d.ts +9 -6
- package/dist/workflow-module.js +305 -5
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./registry.js";
|
|
|
3
3
|
export * from "./workflow-executor.js";
|
|
4
4
|
export * from "./workflow-composer.js";
|
|
5
5
|
export * from "./workflow-module.js";
|
|
6
|
+
export { exposeAll, exposeAllAs } from "./utils.js";
|
|
6
7
|
export * from "./types.js";
|
|
7
8
|
export * from "./session.js";
|
|
8
9
|
export { useMetrics, useLog } from "./observer.js";
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./registry.js";
|
|
|
3
3
|
export * from "./workflow-executor.js";
|
|
4
4
|
export * from "./workflow-composer.js";
|
|
5
5
|
export * from "./workflow-module.js";
|
|
6
|
+
export { exposeAll, exposeAllAs } from "./utils.js";
|
|
6
7
|
export * from "./types.js";
|
|
7
8
|
export * from "./session.js";
|
|
8
9
|
export { useMetrics, useLog } from "./observer.js";
|
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>) => {
|
package/dist/utils.d.ts
CHANGED
|
@@ -1 +1,4 @@
|
|
|
1
|
+
import { Module } from "./workflow-module.js";
|
|
1
2
|
export declare function generateWorkflowId(name: string): string;
|
|
3
|
+
export declare function exposeAll<Name extends string, M extends Module<any, any, any, any>>(name: Name, mod: M): Record<`${Name}.${keyof M["workflows"] & string}`, `${Name}.${keyof M["workflows"] & string}`>;
|
|
4
|
+
export declare function exposeAllAs<Name extends string, M extends Module<any, any, any, any>>(name: Name, mod: M): Record<keyof M["workflows"] & string, `${Name}.${keyof M["workflows"] & string}`>;
|
package/dist/utils.js
CHANGED
|
@@ -3,3 +3,18 @@ export function generateWorkflowId(name) {
|
|
|
3
3
|
const time = Date.now().toString(36);
|
|
4
4
|
return `${name}-${time}-${random}`;
|
|
5
5
|
}
|
|
6
|
+
export function exposeAll(name, mod) {
|
|
7
|
+
const result = {};
|
|
8
|
+
for (const key in mod.workflows) {
|
|
9
|
+
const full = `${name}.${key}`;
|
|
10
|
+
result[full] = full;
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
export function exposeAllAs(name, mod) {
|
|
15
|
+
const result = {};
|
|
16
|
+
for (const key in mod.workflows) {
|
|
17
|
+
result[key] = `${name}.${key}`;
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
@@ -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
|
-
}) => 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,75 +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
|
-
: `${pipeStep.service}.${pipeStep.method}`}`);
|
|
168
|
-
}
|
|
169
|
-
current = await runAction(resolved, action);
|
|
170
|
-
}
|
|
171
|
-
return current;
|
|
172
|
-
}));
|
|
173
|
-
results[step.id] = pipeResults;
|
|
174
|
-
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;
|
|
175
603
|
frame.end = Date.now();
|
|
176
|
-
return
|
|
604
|
+
return outputs;
|
|
177
605
|
}
|
|
606
|
+
// ---------------- Subflow ----------------
|
|
178
607
|
if (step.__subflowId) {
|
|
179
|
-
// -----------------------------
|
|
180
|
-
// Subflow handling
|
|
181
|
-
// -----------------------------
|
|
182
608
|
const [modId, subWfId] = step.__subflowId.split(".");
|
|
183
609
|
const exec = depsExecutors[modId];
|
|
184
610
|
const subExecution = await exec.run(subWfId, resolvedArgs, services, observers);
|
|
@@ -188,11 +614,9 @@ export async function executeWorkflow({ workflow, actionRegistry, services, inpu
|
|
|
188
614
|
Object.assign(extras, subExecution.extras);
|
|
189
615
|
return subExecution.output;
|
|
190
616
|
}
|
|
191
|
-
//
|
|
192
|
-
// Normal action
|
|
193
|
-
// -----------------------------
|
|
617
|
+
// ---------------- Normal action ----------------
|
|
194
618
|
const actionFn = async () => {
|
|
195
|
-
let action
|
|
619
|
+
let action;
|
|
196
620
|
if (step.action === "__service__") {
|
|
197
621
|
const { service, method } = step.serviceCall;
|
|
198
622
|
action = services[service][method];
|
|
@@ -200,8 +624,7 @@ export async function executeWorkflow({ workflow, actionRegistry, services, inpu
|
|
|
200
624
|
else {
|
|
201
625
|
action = actionRegistry[step.action];
|
|
202
626
|
}
|
|
203
|
-
|
|
204
|
-
return await runAction(resolvedArgs, action);
|
|
627
|
+
return runAction(resolvedArgs, action);
|
|
205
628
|
};
|
|
206
629
|
try {
|
|
207
630
|
const result = await withTimeout(runWithRetry(actionFn, step.options), step.options?.timeout);
|
|
@@ -236,16 +659,11 @@ export async function executeWorkflow({ workflow, actionRegistry, services, inpu
|
|
|
236
659
|
completed++;
|
|
237
660
|
}));
|
|
238
661
|
}
|
|
239
|
-
if (completed !== workflow.steps.length)
|
|
662
|
+
if (completed !== workflow.steps.length)
|
|
240
663
|
throw new Error("Workflow execution failed (cycle or missing dependency)");
|
|
241
|
-
}
|
|
242
664
|
const outputCtx = createStepCtx(input, results);
|
|
243
665
|
const output = workflow.outputResolver
|
|
244
666
|
? workflow.outputResolver(outputCtx)
|
|
245
667
|
: results;
|
|
246
|
-
return {
|
|
247
|
-
// results: results as WorkflowResults<WR[K]>,
|
|
248
|
-
output: output,
|
|
249
|
-
extras,
|
|
250
|
-
};
|
|
668
|
+
return { output: output, extras };
|
|
251
669
|
}
|
|
@@ -9,7 +9,6 @@ type DepWorkflows<Deps extends ModuleMap> = keyof Deps extends never ? {} : Simp
|
|
|
9
9
|
[K in keyof Deps[D]["workflows"] & string as `${D}.${K}`]: Deps[D]["workflows"][K];
|
|
10
10
|
};
|
|
11
11
|
}[keyof Deps & string]>>>;
|
|
12
|
-
type WorkflowRegistry<Own extends ModuleShape, Deps extends ModuleMap> = Own & DepWorkflows<Deps>;
|
|
13
12
|
type AnyWorkflow = WorkflowDef<any, any, any, any, any>;
|
|
14
13
|
type ModuleShape = Record<string, AnyWorkflow>;
|
|
15
14
|
type ModuleMap = Record<string, Module<any, any, any, any>>;
|
|
@@ -22,14 +21,14 @@ type ServicesFromDepsRecursive<Deps extends ModuleMap> = [keyof Deps] extends [
|
|
|
22
21
|
export type WorkflowInput<W> = W extends WorkflowDef<any, infer I, any, any, any> ? I : never;
|
|
23
22
|
export type WorkflowResults<W> = W extends WorkflowDef<any, any, infer R, any, any> ? R : never;
|
|
24
23
|
export type WorkflowOutput<W> = W extends WorkflowDef<any, any, any, any, infer O> ? O : never;
|
|
25
|
-
type Module<Reg extends ActionRegistry, S extends ServiceRegistry, Own extends ModuleShape, Deps extends ModuleMap> = {
|
|
24
|
+
export type Module<Reg extends ActionRegistry, S extends ServiceRegistry, Own extends ModuleShape, Deps extends ModuleMap, Public extends ModuleShape = Own> = {
|
|
26
25
|
workflows: Own;
|
|
27
26
|
__getExecutor: () => Executor;
|
|
28
27
|
createRuntime: (config: {
|
|
29
28
|
services: FinalServices<S, Deps>;
|
|
30
29
|
}) => {
|
|
31
|
-
run: <K extends keyof
|
|
32
|
-
output: WorkflowOutput<
|
|
30
|
+
run: <K extends keyof Public>(workflow: K, input: WorkflowInput<Public[K]>, observers?: WorkflowObserver<Reg>[]) => Promise<{
|
|
31
|
+
output: WorkflowOutput<Public[K]>;
|
|
33
32
|
extras: Record<string, any>;
|
|
34
33
|
}>;
|
|
35
34
|
getServices: () => FinalServices<S, Deps>;
|
|
@@ -39,9 +38,13 @@ type ModuleContext<Reg extends ActionRegistry, WFReg extends Record<string, Work
|
|
|
39
38
|
wf: ReturnType<typeof createWorkflow<Reg, WFReg, S>>;
|
|
40
39
|
services: S;
|
|
41
40
|
};
|
|
42
|
-
|
|
41
|
+
type ExposedWorkflows<Own extends ModuleShape, Use extends ModuleMap, Expose extends Record<string, keyof DepWorkflows<Use>> | undefined> = Own & (Expose extends Record<string, keyof DepWorkflows<Use>> ? {
|
|
42
|
+
[K in keyof Expose]: DepWorkflows<Use>[Expose[K]];
|
|
43
|
+
} : {});
|
|
44
|
+
export declare function createModuleFactory<S extends ServiceRegistry>(): <Reg extends ActionRegistry = Record<string, any>, Use extends ModuleMap = {}, Own extends ModuleShape = {}, Expose extends Record<string, keyof DepWorkflows<Use>> | undefined = undefined>(config: {
|
|
43
45
|
actionRegistry: Reg;
|
|
44
46
|
use?: Use;
|
|
47
|
+
expose?: Expose;
|
|
45
48
|
define: (ctx: ModuleContext<typeof config.actionRegistry, DepWorkflows<Use>, S>) => Own;
|
|
46
|
-
}) => Module<Reg, S, Own, Use
|
|
49
|
+
}) => Module<Reg, S, Own, Use, ExposedWorkflows<Own, Use, Expose>>;
|
|
47
50
|
export {};
|
package/dist/workflow-module.js
CHANGED
|
@@ -1,21 +1,316 @@
|
|
|
1
|
-
import {
|
|
1
|
+
// import {
|
|
2
|
+
// ActionRegistry,
|
|
3
|
+
// ServiceRegistry,
|
|
4
|
+
// Executor,
|
|
5
|
+
// Simplify,
|
|
6
|
+
// WorkflowObserver,
|
|
7
|
+
// } from "./types.js";
|
|
8
|
+
// import { createWorkflow, WorkflowDef } from "./workflow-composer.js";
|
|
9
|
+
// import { executeWorkflow } from "./workflow-executor.js";
|
|
10
|
+
// type UnionToIntersection<U> = (U extends any ? (x: U) => any : never) extends (
|
|
11
|
+
// x: infer I,
|
|
12
|
+
// ) => any
|
|
13
|
+
// ? I
|
|
14
|
+
// : never;
|
|
15
|
+
// /* ------------------------------------------------ */
|
|
16
|
+
// /* WORKFLOW REGISTRY TYPES */
|
|
17
|
+
// /* ------------------------------------------------ */
|
|
18
|
+
// type EnsureWorkflowRecord<T> =
|
|
19
|
+
// T extends Record<string, WorkflowDef<any, any, any, any, any>>
|
|
20
|
+
// ? T
|
|
21
|
+
// : Record<string, WorkflowDef<any, any, any, any, any>>;
|
|
22
|
+
//
|
|
23
|
+
// type EnsureWorkflowShape<T> = {
|
|
24
|
+
// [K in keyof T]: T[K] extends WorkflowDef<any, any, any, any, any>
|
|
25
|
+
// ? T[K]
|
|
26
|
+
// : never;
|
|
27
|
+
// };
|
|
28
|
+
//
|
|
29
|
+
// type DepWorkflows<Deps extends ModuleMap> = keyof Deps extends never
|
|
30
|
+
// ? {}
|
|
31
|
+
// : Simplify<
|
|
32
|
+
// EnsureWorkflowShape<
|
|
33
|
+
// UnionToIntersection<
|
|
34
|
+
// {
|
|
35
|
+
// [D in keyof Deps & string]: {
|
|
36
|
+
// [K in keyof Deps[D]["workflows"] &
|
|
37
|
+
// string as `${D}.${K}`]: Deps[D]["workflows"][K];
|
|
38
|
+
// };
|
|
39
|
+
// }[keyof Deps & string]
|
|
40
|
+
// >
|
|
41
|
+
// >
|
|
42
|
+
// >;
|
|
43
|
+
//
|
|
44
|
+
// type WorkflowRegistry<Own extends ModuleShape, Deps extends ModuleMap> = Own &
|
|
45
|
+
// DepWorkflows<Deps>;
|
|
46
|
+
//
|
|
47
|
+
// /* ------------------------------------------------ */
|
|
48
|
+
// /* MODULE TYPES */
|
|
49
|
+
// /* ------------------------------------------------ */
|
|
50
|
+
//
|
|
51
|
+
// type AnyWorkflow = WorkflowDef<any, any, any, any, any>;
|
|
52
|
+
// type ModuleShape = Record<string, AnyWorkflow>;
|
|
53
|
+
// type ModuleMap = Record<string, Module<any, any, any, any>>;
|
|
54
|
+
//
|
|
55
|
+
// // type ContextFromDeps<Deps> = [keyof Deps] extends [never]
|
|
56
|
+
// // ? {}
|
|
57
|
+
// // : {
|
|
58
|
+
// // [K in keyof Deps]: Deps[K] extends Module<any, infer Ctx, any, any>
|
|
59
|
+
// // ? Ctx
|
|
60
|
+
// // : never;
|
|
61
|
+
// // }[keyof Deps];
|
|
62
|
+
//
|
|
63
|
+
// type FinalServices<S extends ServiceRegistry, Deps extends ModuleMap> = S &
|
|
64
|
+
// ServicesFromDepsRecursive<Deps>;
|
|
65
|
+
//
|
|
66
|
+
// // type ContextFromDeps<Deps> = [keyof Deps] extends [never]
|
|
67
|
+
// // ? {}
|
|
68
|
+
// // : UnionToIntersection<
|
|
69
|
+
// // {
|
|
70
|
+
// // [K in keyof Deps]: Deps[K] extends Module<any, infer Ctx, any, any>
|
|
71
|
+
// // ? Ctx
|
|
72
|
+
// // : never;
|
|
73
|
+
// // }[keyof Deps]
|
|
74
|
+
// // >;
|
|
75
|
+
//
|
|
76
|
+
// type ServicesFromDepsRecursive<Deps extends ModuleMap> = [keyof Deps] extends [
|
|
77
|
+
// never,
|
|
78
|
+
// ]
|
|
79
|
+
// ? {} // no deps
|
|
80
|
+
// : UnionToIntersection<
|
|
81
|
+
// {
|
|
82
|
+
// [K in keyof Deps]: Deps[K] extends Module<
|
|
83
|
+
// any,
|
|
84
|
+
// infer S,
|
|
85
|
+
// any,
|
|
86
|
+
// infer SubDeps
|
|
87
|
+
// >
|
|
88
|
+
// ? S & ServicesFromDepsRecursive<SubDeps>
|
|
89
|
+
// : never;
|
|
90
|
+
// }[keyof Deps]
|
|
91
|
+
// >;
|
|
92
|
+
//
|
|
93
|
+
// /* ------------------------------------------------ */
|
|
94
|
+
// /* WORKFLOW IO TYPES */
|
|
95
|
+
// /* ------------------------------------------------ */
|
|
96
|
+
//
|
|
97
|
+
// export type WorkflowInput<W> =
|
|
98
|
+
// W extends WorkflowDef<any, infer I, any, any, any> ? I : never;
|
|
99
|
+
//
|
|
100
|
+
// export type WorkflowResults<W> =
|
|
101
|
+
// W extends WorkflowDef<any, any, infer R, any, any> ? R : never;
|
|
102
|
+
//
|
|
103
|
+
// export type WorkflowOutput<W> =
|
|
104
|
+
// W extends WorkflowDef<any, any, any, any, infer O> ? O : never;
|
|
105
|
+
//
|
|
106
|
+
// /* ------------------------------------------------ */
|
|
107
|
+
// /* MODULE RUNTIME */
|
|
108
|
+
// /* ------------------------------------------------ */
|
|
109
|
+
//
|
|
110
|
+
// type Module<
|
|
111
|
+
// Reg extends ActionRegistry,
|
|
112
|
+
// S extends ServiceRegistry,
|
|
113
|
+
// Own extends ModuleShape,
|
|
114
|
+
// Deps extends ModuleMap,
|
|
115
|
+
// > = {
|
|
116
|
+
// workflows: Own;
|
|
117
|
+
// __getExecutor: () => Executor;
|
|
118
|
+
//
|
|
119
|
+
// createRuntime: (config: { services: FinalServices<S, Deps> }) => {
|
|
120
|
+
// run: <K extends keyof WorkflowRegistry<Own, Deps>>(
|
|
121
|
+
// workflow: K,
|
|
122
|
+
// input: WorkflowInput<WorkflowRegistry<Own, Deps>[K]>,
|
|
123
|
+
// observers?: WorkflowObserver<Reg>[],
|
|
124
|
+
// ) => Promise<{
|
|
125
|
+
// // results: WorkflowResults<WorkflowRegistry<Own, Deps>[K]>;
|
|
126
|
+
// output: WorkflowOutput<WorkflowRegistry<Own, Deps>[K]>;
|
|
127
|
+
// extras: Record<string, any>;
|
|
128
|
+
// }>;
|
|
129
|
+
//
|
|
130
|
+
// getServices: () => FinalServices<S, Deps>;
|
|
131
|
+
// };
|
|
132
|
+
// };
|
|
133
|
+
//
|
|
134
|
+
// /* ------------------------------------------------ */
|
|
135
|
+
// /* MODULE CONTEXT (FIXED) */
|
|
136
|
+
// /* ------------------------------------------------ */
|
|
137
|
+
//
|
|
138
|
+
// type ModuleContext<
|
|
139
|
+
// Reg extends ActionRegistry,
|
|
140
|
+
// WFReg extends Record<string, WorkflowDef<any, any, any, any, any>>,
|
|
141
|
+
// S extends ServiceRegistry,
|
|
142
|
+
// > = {
|
|
143
|
+
// wf: ReturnType<typeof createWorkflow<Reg, WFReg, S>>;
|
|
144
|
+
// services: S;
|
|
145
|
+
// };
|
|
146
|
+
//
|
|
147
|
+
// function createModule<
|
|
148
|
+
// Reg extends ActionRegistry,
|
|
149
|
+
// S extends ServiceRegistry,
|
|
150
|
+
// Use extends ModuleMap,
|
|
151
|
+
// Own extends ModuleShape,
|
|
152
|
+
// >(config: {
|
|
153
|
+
// actionRegistry: Reg;
|
|
154
|
+
// use?: Use;
|
|
155
|
+
// define: (ctx: ModuleContext<Reg, DepWorkflows<Use>, S>) => Own;
|
|
156
|
+
// }): Module<Reg, S, Own, Use> {
|
|
157
|
+
// const deps = (config.use ?? {}) as Use;
|
|
158
|
+
//
|
|
159
|
+
// const wf = createWorkflow<Reg, DepWorkflows<Use>, S>();
|
|
160
|
+
//
|
|
161
|
+
// const own = config.define({
|
|
162
|
+
// wf,
|
|
163
|
+
// services: {} as S,
|
|
164
|
+
// });
|
|
165
|
+
//
|
|
166
|
+
// function buildWorkflowMap(): WorkflowRegistry<Own, Use> {
|
|
167
|
+
// const depWFs = Object.fromEntries(
|
|
168
|
+
// Object.entries(deps).flatMap(([name, mod]) =>
|
|
169
|
+
// Object.entries(mod.workflows).map(([k, wf]) => [`${name}.${k}`, wf]),
|
|
170
|
+
// ),
|
|
171
|
+
// );
|
|
172
|
+
//
|
|
173
|
+
// return { ...own, ...depWFs } as WorkflowRegistry<Own, Use>;
|
|
174
|
+
// }
|
|
175
|
+
//
|
|
176
|
+
// const workflowMap = buildWorkflowMap();
|
|
177
|
+
//
|
|
178
|
+
// const depsExecutors = Object.fromEntries(
|
|
179
|
+
// Object.entries(deps).map(([name, mod]) => [name, mod.__getExecutor()]),
|
|
180
|
+
// );
|
|
181
|
+
//
|
|
182
|
+
// const executor: Executor = {
|
|
183
|
+
// run(wfId, input, services, observers = []) {
|
|
184
|
+
// const workflow = workflowMap[wfId];
|
|
185
|
+
//
|
|
186
|
+
// if (!workflow) {
|
|
187
|
+
// throw new Error(`Workflow not found: ${String(wfId)}`);
|
|
188
|
+
// }
|
|
189
|
+
//
|
|
190
|
+
// return executeWorkflow({
|
|
191
|
+
// workflow,
|
|
192
|
+
// actionRegistry: config.actionRegistry,
|
|
193
|
+
// depsExecutors,
|
|
194
|
+
// input,
|
|
195
|
+
// services,
|
|
196
|
+
// observers,
|
|
197
|
+
// });
|
|
198
|
+
// },
|
|
199
|
+
// };
|
|
200
|
+
//
|
|
201
|
+
// return {
|
|
202
|
+
// workflows: own,
|
|
203
|
+
// __getExecutor: () => executor,
|
|
204
|
+
//
|
|
205
|
+
// createRuntime({ services }) {
|
|
206
|
+
// let runtimeActions = config.actionRegistry;
|
|
207
|
+
//
|
|
208
|
+
// // const runtimeService = createServiceRegisty(services)
|
|
209
|
+
// return {
|
|
210
|
+
// run: async <K extends keyof WorkflowRegistry<Own, Use>>(
|
|
211
|
+
// workflowId: K,
|
|
212
|
+
// input: WorkflowInput<WorkflowRegistry<Own, Use>[K]>,
|
|
213
|
+
// observers: WorkflowObserver<Reg>[] = [],
|
|
214
|
+
// ) => {
|
|
215
|
+
// return executor.run(workflowId as string, input, services, observers);
|
|
216
|
+
// },
|
|
217
|
+
// // make it same, practically nothing changes but naming, and what context holds
|
|
218
|
+
// getServices: () => ({ ...services }) as FinalServices<S, Use>,
|
|
219
|
+
//
|
|
220
|
+
// setActionRegistry(reg: Reg) {
|
|
221
|
+
// runtimeActions = reg;
|
|
222
|
+
// // ⚠️ optional: if you REALLY want override, you'd need:
|
|
223
|
+
// // executor.actions = reg
|
|
224
|
+
// // but better keep actions immutable
|
|
225
|
+
// },
|
|
226
|
+
// };
|
|
227
|
+
// },
|
|
228
|
+
// };
|
|
229
|
+
// }
|
|
230
|
+
//
|
|
231
|
+
// /* ------------------------------------------------ */
|
|
232
|
+
// /* FACTORY (FIXED) */
|
|
233
|
+
// /* ------------------------------------------------ */
|
|
234
|
+
//
|
|
235
|
+
// export function createModuleFactory<
|
|
236
|
+
// // Reg extends ActionRegistry,
|
|
237
|
+
// S extends ServiceRegistry,
|
|
238
|
+
// >() {
|
|
239
|
+
// return function <
|
|
240
|
+
// Reg extends ActionRegistry = Record<string, any>,
|
|
241
|
+
// Use extends ModuleMap = {},
|
|
242
|
+
// Own extends ModuleShape = {},
|
|
243
|
+
// >(config: {
|
|
244
|
+
// actionRegistry: Reg;
|
|
245
|
+
// use?: Use;
|
|
246
|
+
// define: (
|
|
247
|
+
// ctx: ModuleContext<typeof config.actionRegistry, DepWorkflows<Use>, S>,
|
|
248
|
+
// ) => Own;
|
|
249
|
+
// }): Module<Reg, S, Own, Use> {
|
|
250
|
+
// return createModule<Reg, S, Use, Own>(config);
|
|
251
|
+
// };
|
|
252
|
+
// }
|
|
253
|
+
import { createWorkflow, } from "./workflow-composer.js";
|
|
2
254
|
import { executeWorkflow } from "./workflow-executor.js";
|
|
3
255
|
function createModule(config) {
|
|
4
256
|
const deps = (config.use ?? {});
|
|
5
257
|
const wf = createWorkflow();
|
|
258
|
+
// const macro = createMacro<Reg, S, DepWorkflows<Use>>();
|
|
6
259
|
const own = config.define({
|
|
7
260
|
wf,
|
|
8
261
|
services: {},
|
|
9
262
|
});
|
|
263
|
+
// function buildWorkflowMap(): WorkflowRegistry<Own, Use> {
|
|
264
|
+
// const depWFs = Object.fromEntries(
|
|
265
|
+
// Object.entries(deps).flatMap(([name, mod]) =>
|
|
266
|
+
// Object.entries(mod.workflows).map(([k, wf]) => [`${name}.${k}`, wf]),
|
|
267
|
+
// ),
|
|
268
|
+
// );
|
|
269
|
+
//
|
|
270
|
+
// const internal = { ...own, ...depWFs } as WorkflowRegistry<Own, Use>;
|
|
271
|
+
//
|
|
272
|
+
// const publicMap = own;
|
|
273
|
+
// return { internal, publicMap };
|
|
274
|
+
// }
|
|
10
275
|
function buildWorkflowMap() {
|
|
11
276
|
const depWFs = Object.fromEntries(Object.entries(deps).flatMap(([name, mod]) => Object.entries(mod.workflows).map(([k, wf]) => [`${name}.${k}`, wf])));
|
|
12
|
-
|
|
277
|
+
// const internal = { ...own, ...depWFs } as WorkflowRegistry<Own, Use>;
|
|
278
|
+
// const exposed = Object.fromEntries(
|
|
279
|
+
// Object.entries(config.expose ?? {}).map(([alias, key]) => [
|
|
280
|
+
// alias,
|
|
281
|
+
// internal[key], // reuse already resolved workflow
|
|
282
|
+
// ]),
|
|
283
|
+
// );
|
|
284
|
+
//
|
|
285
|
+
const internalBase = { ...own, ...depWFs };
|
|
286
|
+
const exposed = {};
|
|
287
|
+
if (config.expose) {
|
|
288
|
+
for (const alias in config.expose) {
|
|
289
|
+
const key = config.expose[alias];
|
|
290
|
+
exposed[alias] = internalBase[key];
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const internal = {
|
|
294
|
+
...internalBase,
|
|
295
|
+
...exposed,
|
|
296
|
+
};
|
|
297
|
+
// if (config.expose) {
|
|
298
|
+
// for (const alias in config.expose) {
|
|
299
|
+
// const key = config.expose[alias];
|
|
300
|
+
// exposed[alias] = internal[key];
|
|
301
|
+
// }
|
|
302
|
+
// }
|
|
303
|
+
const publicMap = { ...own, ...exposed };
|
|
304
|
+
return { internal, publicMap };
|
|
13
305
|
}
|
|
14
|
-
const
|
|
306
|
+
const { internal, publicMap } = buildWorkflowMap();
|
|
15
307
|
const depsExecutors = Object.fromEntries(Object.entries(deps).map(([name, mod]) => [name, mod.__getExecutor()]));
|
|
16
308
|
const executor = {
|
|
17
309
|
run(wfId, input, services, observers = []) {
|
|
18
|
-
|
|
310
|
+
if (!(wfId in publicMap)) {
|
|
311
|
+
throw new Error(`Workflow not in public: ${wfId}`);
|
|
312
|
+
}
|
|
313
|
+
const workflow = internal[wfId];
|
|
19
314
|
if (!workflow) {
|
|
20
315
|
throw new Error(`Workflow not found: ${String(wfId)}`);
|
|
21
316
|
}
|
|
@@ -36,7 +331,12 @@ function createModule(config) {
|
|
|
36
331
|
let runtimeActions = config.actionRegistry;
|
|
37
332
|
// const runtimeService = createServiceRegisty(services)
|
|
38
333
|
return {
|
|
39
|
-
run: async (
|
|
334
|
+
run: async (
|
|
335
|
+
// run: async <K extends keyof WorkflowRegistry<Own, Use>>(
|
|
336
|
+
workflowId, input,
|
|
337
|
+
// input: WorkflowInput<WorkflowRegistry<Own, Use>[K]>,
|
|
338
|
+
// input: WorkflowInput<WorkflowRegistry<Own, Use>[K]>,
|
|
339
|
+
observers = []) => {
|
|
40
340
|
return executor.run(workflowId, input, services, observers);
|
|
41
341
|
},
|
|
42
342
|
// make it same, practically nothing changes but naming, and what context holds
|