@pogodisco/zephyr 1.4.1 → 1.5.0
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 +0 -2
- package/dist/index.js +0 -2
- package/dist/observer.js +8 -7
- package/dist/types.d.ts +6 -0
- package/dist/utils.d.ts +1 -10
- package/dist/utils.js +5 -34
- package/dist/workflow-composer.d.ts +60 -27
- package/dist/workflow-composer.js +21 -451
- package/dist/workflow-executor.d.ts +10 -4
- package/dist/workflow-executor.js +36 -248
- package/dist/workflow-module.d.ts +23 -18
- package/dist/workflow-module.js +222 -87
- package/package.json +5 -2
|
@@ -1,234 +1,3 @@
|
|
|
1
|
-
// import { composeObserver } from "./observer.js";
|
|
2
|
-
// import { ActionRegistry, ExecutionFrame, WorkflowObserver } from "./types.js";
|
|
3
|
-
// import {
|
|
4
|
-
// StepDef,
|
|
5
|
-
// WorkflowDef,
|
|
6
|
-
// ResolvedStepInput,
|
|
7
|
-
// } from "./workflow-composer.js";
|
|
8
|
-
//
|
|
9
|
-
// export function createCallHelpers() {
|
|
10
|
-
// return {
|
|
11
|
-
// args: (...args: any[]) => ({ kind: "positional", args }),
|
|
12
|
-
// obj: (arg: any) => ({ kind: "object", args: arg }),
|
|
13
|
-
// none: () => ({ kind: "none" }),
|
|
14
|
-
// loop: (items: any[]) => ({ kind: "loop", items }),
|
|
15
|
-
// };
|
|
16
|
-
// }
|
|
17
|
-
//
|
|
18
|
-
// // Helper to wrap action execution with timeout
|
|
19
|
-
// async function withTimeout<T>(promise: Promise<T>, ms?: number): Promise<T> {
|
|
20
|
-
// if (!ms) return promise;
|
|
21
|
-
// return Promise.race([
|
|
22
|
-
// promise,
|
|
23
|
-
// new Promise<T>((_, reject) =>
|
|
24
|
-
// setTimeout(() => reject(new Error("Timeout")), ms),
|
|
25
|
-
// ),
|
|
26
|
-
// ]);
|
|
27
|
-
// }
|
|
28
|
-
//
|
|
29
|
-
// // Helper to run action with retry support
|
|
30
|
-
// async function runWithRetry(
|
|
31
|
-
// actionFn: () => Promise<any>,
|
|
32
|
-
// stepOptions?: {
|
|
33
|
-
// retry?: number;
|
|
34
|
-
// retryDelay?: number | ((attempt: number) => number);
|
|
35
|
-
// },
|
|
36
|
-
// ): Promise<any> {
|
|
37
|
-
// const maxRetries = stepOptions?.retry ?? 0;
|
|
38
|
-
// let lastError: any;
|
|
39
|
-
//
|
|
40
|
-
// for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
41
|
-
// try {
|
|
42
|
-
// return await actionFn();
|
|
43
|
-
// } catch (err) {
|
|
44
|
-
// lastError = err;
|
|
45
|
-
//
|
|
46
|
-
// if (attempt === maxRetries) break;
|
|
47
|
-
//
|
|
48
|
-
// const delay = stepOptions?.retryDelay;
|
|
49
|
-
// if (typeof delay === "number") {
|
|
50
|
-
// await new Promise((r) => setTimeout(r, delay));
|
|
51
|
-
// } else if (typeof delay === "function") {
|
|
52
|
-
// await new Promise((r) => setTimeout(r, delay(attempt)));
|
|
53
|
-
// }
|
|
54
|
-
// }
|
|
55
|
-
// }
|
|
56
|
-
//
|
|
57
|
-
// throw lastError;
|
|
58
|
-
// }
|
|
59
|
-
//
|
|
60
|
-
// export async function executeWorkflow<
|
|
61
|
-
// Reg extends ActionRegistry,
|
|
62
|
-
// I,
|
|
63
|
-
// R,
|
|
64
|
-
// O = R,
|
|
65
|
-
// Steps extends StepDef<Reg, any, any>[] = StepDef<Reg, any, any>[],
|
|
66
|
-
// >(
|
|
67
|
-
// workflow: WorkflowDef<Reg, I, R, Steps, O>,
|
|
68
|
-
// registry: Reg,
|
|
69
|
-
// input: I,
|
|
70
|
-
// context: any,
|
|
71
|
-
// observers: WorkflowObserver<Reg>[] = [],
|
|
72
|
-
// ): Promise<{ results: R; output: O; extras: Record<string, any> }> {
|
|
73
|
-
// const results: Record<string, any> = {};
|
|
74
|
-
// const extras: Record<string, any> = {};
|
|
75
|
-
// extras.frames = {} as Record<string, ExecutionFrame>;
|
|
76
|
-
//
|
|
77
|
-
// // -----------------------------
|
|
78
|
-
// // Fully typed step map
|
|
79
|
-
// // -----------------------------
|
|
80
|
-
// const stepById: Map<string, StepDef<Reg, any, any>> = new Map(
|
|
81
|
-
// workflow.steps.map((s: StepDef<Reg, any, any>) => [s.id, s]),
|
|
82
|
-
// );
|
|
83
|
-
//
|
|
84
|
-
// const remainingDeps = new Map<string, number>();
|
|
85
|
-
// const dependents = new Map<string, string[]>();
|
|
86
|
-
// const ready: string[] = [];
|
|
87
|
-
//
|
|
88
|
-
// for (const step of workflow.steps) {
|
|
89
|
-
// remainingDeps.set(step.id, step.dependsOn.length);
|
|
90
|
-
// if (step.dependsOn.length === 0) ready.push(step.id);
|
|
91
|
-
//
|
|
92
|
-
// for (const dep of step.dependsOn) {
|
|
93
|
-
// if (!dependents.has(dep)) dependents.set(dep, []);
|
|
94
|
-
// dependents.get(dep)!.push(step.id);
|
|
95
|
-
// }
|
|
96
|
-
// }
|
|
97
|
-
//
|
|
98
|
-
// let completed = 0;
|
|
99
|
-
//
|
|
100
|
-
// // -----------------------------
|
|
101
|
-
// // Normalized runner
|
|
102
|
-
// // -----------------------------
|
|
103
|
-
// const runAction = async (
|
|
104
|
-
// input: ResolvedStepInput | undefined,
|
|
105
|
-
// action: any,
|
|
106
|
-
// ): Promise<any> => {
|
|
107
|
-
// if (!input) return await action();
|
|
108
|
-
//
|
|
109
|
-
// switch (input.kind) {
|
|
110
|
-
// case "none":
|
|
111
|
-
// return await action();
|
|
112
|
-
// case "positional":
|
|
113
|
-
// return await action(...input.args);
|
|
114
|
-
// case "object":
|
|
115
|
-
// return await action(input.args);
|
|
116
|
-
// case "loop":
|
|
117
|
-
// return await Promise.all(
|
|
118
|
-
// input.items.map((item) => runAction(item, action)),
|
|
119
|
-
// );
|
|
120
|
-
// default:
|
|
121
|
-
// throw new Error(
|
|
122
|
-
// `Unknown ResolvedStepInput kind: ${(input as any).kind}`,
|
|
123
|
-
// );
|
|
124
|
-
// }
|
|
125
|
-
// };
|
|
126
|
-
//
|
|
127
|
-
// // -----------------------------
|
|
128
|
-
// // Execution loop
|
|
129
|
-
// // -----------------------------
|
|
130
|
-
// while (ready.length > 0) {
|
|
131
|
-
// const batch = ready.splice(0);
|
|
132
|
-
//
|
|
133
|
-
// await Promise.all(
|
|
134
|
-
// batch.map(async (stepId) => {
|
|
135
|
-
// const step = stepById.get(stepId)!;
|
|
136
|
-
//
|
|
137
|
-
// const frame: ExecutionFrame = {
|
|
138
|
-
// stepId,
|
|
139
|
-
// attempts: 0,
|
|
140
|
-
// start: Date.now(),
|
|
141
|
-
// };
|
|
142
|
-
// extras.frames[stepId] = frame;
|
|
143
|
-
//
|
|
144
|
-
// const ctx = {
|
|
145
|
-
// stepId,
|
|
146
|
-
// input,
|
|
147
|
-
// results,
|
|
148
|
-
// context,
|
|
149
|
-
// registry,
|
|
150
|
-
// extras,
|
|
151
|
-
// frame,
|
|
152
|
-
// };
|
|
153
|
-
//
|
|
154
|
-
// const core = async () => {
|
|
155
|
-
// frame.attempts++;
|
|
156
|
-
// const stepCtx = { input, results, context, ...createCallHelpers() };
|
|
157
|
-
//
|
|
158
|
-
// // Conditional skip
|
|
159
|
-
// if (step.when && !step.when(stepCtx)) {
|
|
160
|
-
// frame.output = undefined;
|
|
161
|
-
// frame.end = Date.now();
|
|
162
|
-
// frame.skipped = true;
|
|
163
|
-
// results[step.id] = undefined;
|
|
164
|
-
// return undefined;
|
|
165
|
-
// }
|
|
166
|
-
//
|
|
167
|
-
// const resolvedArgs = step.resolve?.(stepCtx);
|
|
168
|
-
// frame.input = resolvedArgs;
|
|
169
|
-
//
|
|
170
|
-
// const actionFn = async () => {
|
|
171
|
-
// const action = registry[step.action];
|
|
172
|
-
// return await runAction(resolvedArgs, action);
|
|
173
|
-
// };
|
|
174
|
-
//
|
|
175
|
-
// try {
|
|
176
|
-
// const result = await withTimeout(
|
|
177
|
-
// runWithRetry(actionFn, step.options),
|
|
178
|
-
// step.options?.timeout,
|
|
179
|
-
// );
|
|
180
|
-
//
|
|
181
|
-
// frame.output = result;
|
|
182
|
-
// frame.end = Date.now();
|
|
183
|
-
// results[step.id] = result;
|
|
184
|
-
// return result;
|
|
185
|
-
// } catch (err) {
|
|
186
|
-
// frame.error = err;
|
|
187
|
-
// frame.end = Date.now();
|
|
188
|
-
//
|
|
189
|
-
// if (step.options?.onError) {
|
|
190
|
-
// const fallback = step.options.onError(err, ctx);
|
|
191
|
-
// results[step.id] = fallback;
|
|
192
|
-
// return fallback;
|
|
193
|
-
// }
|
|
194
|
-
//
|
|
195
|
-
// if (step.options?.continueOnError) {
|
|
196
|
-
// results[step.id] = undefined;
|
|
197
|
-
// return undefined;
|
|
198
|
-
// }
|
|
199
|
-
//
|
|
200
|
-
// throw err;
|
|
201
|
-
// }
|
|
202
|
-
// };
|
|
203
|
-
//
|
|
204
|
-
// const composed = composeObserver(observers, ctx, core);
|
|
205
|
-
// await composed();
|
|
206
|
-
//
|
|
207
|
-
// for (const childId of dependents.get(stepId) ?? []) {
|
|
208
|
-
// const remaining = remainingDeps.get(childId)! - 1;
|
|
209
|
-
// remainingDeps.set(childId, remaining);
|
|
210
|
-
// if (remaining === 0) ready.push(childId);
|
|
211
|
-
// }
|
|
212
|
-
//
|
|
213
|
-
// completed++;
|
|
214
|
-
// }),
|
|
215
|
-
// );
|
|
216
|
-
// }
|
|
217
|
-
//
|
|
218
|
-
// if (completed !== workflow.steps.length) {
|
|
219
|
-
// throw new Error("Workflow execution failed (cycle or missing dependency)");
|
|
220
|
-
// }
|
|
221
|
-
//
|
|
222
|
-
// const output: O = workflow.outputResolver
|
|
223
|
-
// ? workflow.outputResolver({
|
|
224
|
-
// input,
|
|
225
|
-
// results: results as R,
|
|
226
|
-
// context: (workflow as any).__context,
|
|
227
|
-
// })
|
|
228
|
-
// : (results as unknown as O);
|
|
229
|
-
//
|
|
230
|
-
// return { results: results as R, output, extras };
|
|
231
|
-
// }
|
|
232
1
|
import { composeObserver } from "./observer.js";
|
|
233
2
|
export function createCallHelpers() {
|
|
234
3
|
return {
|
|
@@ -270,11 +39,7 @@ async function runWithRetry(actionFn, stepOptions) {
|
|
|
270
39
|
}
|
|
271
40
|
throw lastError;
|
|
272
41
|
}
|
|
273
|
-
export async function executeWorkflow(
|
|
274
|
-
const workflow = workflowRegistry[workflowId];
|
|
275
|
-
if (!workflow) {
|
|
276
|
-
throw new Error(`Workflow not found: ${String(workflowId)}`);
|
|
277
|
-
}
|
|
42
|
+
export async function executeWorkflow({ workflow, actionRegistry, services, input, depsExecutors, observers = [], }) {
|
|
278
43
|
const results = {};
|
|
279
44
|
const extras = {};
|
|
280
45
|
extras.frames = {};
|
|
@@ -335,40 +100,64 @@ export async function executeWorkflow(workflowId, workflowRegistry, actionRegist
|
|
|
335
100
|
stepId,
|
|
336
101
|
input,
|
|
337
102
|
results,
|
|
338
|
-
context,
|
|
339
103
|
actionRegistry,
|
|
340
104
|
extras,
|
|
341
105
|
frame,
|
|
342
106
|
};
|
|
107
|
+
const stepCtx = {
|
|
108
|
+
input,
|
|
109
|
+
results,
|
|
110
|
+
...createCallHelpers(),
|
|
111
|
+
};
|
|
112
|
+
const resolvedArgs = step.resolve?.(stepCtx);
|
|
113
|
+
frame.input = resolvedArgs;
|
|
343
114
|
const core = async () => {
|
|
344
115
|
frame.attempts++;
|
|
345
|
-
|
|
346
|
-
//
|
|
116
|
+
// -----------------------------
|
|
117
|
+
// ✅ SINGLE when evaluation
|
|
118
|
+
// -----------------------------
|
|
347
119
|
if (step.when && !step.when(stepCtx)) {
|
|
120
|
+
frame.input = undefined; // 👈 important for observer consistency
|
|
348
121
|
frame.output = undefined;
|
|
349
122
|
frame.end = Date.now();
|
|
350
123
|
frame.skipped = true;
|
|
351
124
|
results[step.id] = undefined;
|
|
352
125
|
return undefined;
|
|
353
126
|
}
|
|
354
|
-
const resolvedArgs = step.resolve?.(stepCtx);
|
|
355
|
-
frame.input = resolvedArgs;
|
|
356
127
|
// -----------------------------
|
|
357
128
|
// Subflow handling
|
|
358
129
|
// -----------------------------
|
|
359
130
|
if (step.__subflowId) {
|
|
360
|
-
const
|
|
361
|
-
const
|
|
362
|
-
const subExecution = await
|
|
363
|
-
//
|
|
131
|
+
const [modId, subWfId] = step.__subflowId.split(".");
|
|
132
|
+
const exec = depsExecutors[modId];
|
|
133
|
+
const subExecution = await exec.run(subWfId, resolvedArgs, services, observers);
|
|
134
|
+
// const subExecution = await executeWorkflow(
|
|
135
|
+
// step.__subflowId,
|
|
136
|
+
// workflowRegistry,
|
|
137
|
+
// actionRegistry,
|
|
138
|
+
// resolvedArgs as any,
|
|
139
|
+
// context,
|
|
140
|
+
// observers,
|
|
141
|
+
// );
|
|
364
142
|
frame.output = subExecution.output;
|
|
143
|
+
frame.end = Date.now();
|
|
365
144
|
results[step.id] = subExecution.output;
|
|
366
|
-
// Merge extras
|
|
367
145
|
Object.assign(extras, subExecution.extras);
|
|
368
146
|
return subExecution.output;
|
|
369
147
|
}
|
|
148
|
+
// -----------------------------
|
|
149
|
+
// Normal action
|
|
150
|
+
// -----------------------------
|
|
370
151
|
const actionFn = async () => {
|
|
371
|
-
|
|
152
|
+
let action = null;
|
|
153
|
+
if (step.action === "__service__") {
|
|
154
|
+
const { service, method } = step.serviceCall;
|
|
155
|
+
action = services[service][method];
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
action = actionRegistry[step.action];
|
|
159
|
+
}
|
|
160
|
+
console.log("action", action);
|
|
372
161
|
return await runAction(resolvedArgs, action);
|
|
373
162
|
};
|
|
374
163
|
try {
|
|
@@ -411,7 +200,6 @@ export async function executeWorkflow(workflowId, workflowRegistry, actionRegist
|
|
|
411
200
|
? workflow.outputResolver({
|
|
412
201
|
input,
|
|
413
202
|
results,
|
|
414
|
-
context: workflow.__context,
|
|
415
203
|
})
|
|
416
204
|
: results;
|
|
417
205
|
return {
|
|
@@ -1,42 +1,47 @@
|
|
|
1
|
-
import { ActionRegistry, WorkflowObserver } from "./types.js";
|
|
1
|
+
import { ActionRegistry, ServiceRegistry, Executor, Simplify, WorkflowObserver } from "./types.js";
|
|
2
2
|
import { createWorkflow, WorkflowDef } from "./workflow-composer.js";
|
|
3
3
|
type UnionToIntersection<U> = (U extends any ? (x: U) => any : never) extends (x: infer I) => any ? I : never;
|
|
4
|
-
type
|
|
5
|
-
|
|
4
|
+
type EnsureWorkflowShape<T> = {
|
|
5
|
+
[K in keyof T]: T[K] extends WorkflowDef<any, any, any, any, any> ? T[K] : never;
|
|
6
|
+
};
|
|
7
|
+
type DepWorkflows<Deps extends ModuleMap> = keyof Deps extends never ? {} : Simplify<EnsureWorkflowShape<UnionToIntersection<{
|
|
6
8
|
[D in keyof Deps & string]: {
|
|
7
9
|
[K in keyof Deps[D]["workflows"] & string as `${D}.${K}`]: Deps[D]["workflows"][K];
|
|
8
10
|
};
|
|
9
|
-
}[keyof Deps & string]
|
|
11
|
+
}[keyof Deps & string]>>>;
|
|
10
12
|
type WorkflowRegistry<Own extends ModuleShape, Deps extends ModuleMap> = Own & DepWorkflows<Deps>;
|
|
11
13
|
type AnyWorkflow = WorkflowDef<any, any, any, any, any>;
|
|
12
14
|
type ModuleShape = Record<string, AnyWorkflow>;
|
|
13
15
|
type ModuleMap = Record<string, Module<any, any, any, any>>;
|
|
14
|
-
type
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
type FinalServices<S extends ServiceRegistry, Deps extends ModuleMap> = S & ServicesFromDepsRecursive<Deps>;
|
|
17
|
+
type ServicesFromDepsRecursive<Deps extends ModuleMap> = [keyof Deps] extends [
|
|
18
|
+
never
|
|
19
|
+
] ? {} : UnionToIntersection<{
|
|
20
|
+
[K in keyof Deps]: Deps[K] extends Module<any, infer S, any, infer SubDeps> ? S & ServicesFromDepsRecursive<SubDeps> : never;
|
|
21
|
+
}[keyof Deps]>;
|
|
18
22
|
export type WorkflowInput<W> = W extends WorkflowDef<any, infer I, any, any, any> ? I : never;
|
|
19
23
|
export type WorkflowResults<W> = W extends WorkflowDef<any, any, infer R, any, any> ? R : never;
|
|
20
24
|
export type WorkflowOutput<W> = W extends WorkflowDef<any, any, any, any, infer O> ? O : never;
|
|
21
|
-
type Module<Reg extends ActionRegistry,
|
|
25
|
+
type Module<Reg extends ActionRegistry, S extends ServiceRegistry, Own extends ModuleShape, Deps extends ModuleMap> = {
|
|
22
26
|
workflows: Own;
|
|
27
|
+
__getExecutor: () => Executor;
|
|
23
28
|
createRuntime: (config: {
|
|
24
|
-
|
|
25
|
-
context: FinalContext<Reg, Context, Deps>;
|
|
29
|
+
services: FinalServices<S, Deps>;
|
|
26
30
|
}) => {
|
|
27
31
|
run: <K extends keyof WorkflowRegistry<Own, Deps>>(workflow: K, input: WorkflowInput<WorkflowRegistry<Own, Deps>[K]>, observers?: WorkflowObserver<Reg>[]) => Promise<{
|
|
28
32
|
output: WorkflowOutput<WorkflowRegistry<Own, Deps>[K]>;
|
|
29
33
|
extras: Record<string, any>;
|
|
30
34
|
}>;
|
|
31
|
-
|
|
35
|
+
getServices: () => FinalServices<S, Deps>;
|
|
32
36
|
};
|
|
33
37
|
};
|
|
34
|
-
|
|
35
|
-
wf: ReturnType<typeof createWorkflow<Reg, WFReg,
|
|
36
|
-
|
|
38
|
+
type ModuleContext<Reg extends ActionRegistry, WFReg extends Record<string, WorkflowDef<any, any, any, any, any>>, S extends ServiceRegistry> = {
|
|
39
|
+
wf: ReturnType<typeof createWorkflow<Reg, WFReg, S>>;
|
|
40
|
+
services: S;
|
|
37
41
|
};
|
|
38
|
-
export declare function createModuleFactory<Reg extends ActionRegistry
|
|
42
|
+
export declare function createModuleFactory<S extends ServiceRegistry>(): <Reg extends ActionRegistry = Record<string, any>, Use extends ModuleMap = {}, Own extends ModuleShape = {}>(config: {
|
|
43
|
+
actionRegistry: Reg;
|
|
39
44
|
use?: Use;
|
|
40
|
-
define: (ctx: ModuleContext<
|
|
41
|
-
}) => Module<Reg,
|
|
45
|
+
define: (ctx: ModuleContext<typeof config.actionRegistry, DepWorkflows<Use>, S>) => Own;
|
|
46
|
+
}) => Module<Reg, S, Own, Use>;
|
|
42
47
|
export {};
|