@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.
@@ -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(workflowId, workflowRegistry, actionRegistry, input, context, observers = []) {
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
- const stepCtx = { input, results, context, ...createCallHelpers() };
346
- // Conditional skip
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 subWorkflow = step.__subflow;
361
- const subInput = resolvedArgs;
362
- const subExecution = await executeWorkflow(step.__subflowId, workflowRegistry, actionRegistry, resolvedArgs, context, observers);
363
- // Assign output to this step
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
- const action = actionRegistry[step.action];
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 EnsureWorkflowRecord<T> = T extends Record<string, WorkflowDef<any, any, any, any, any>> ? T : Record<string, WorkflowDef<any, any, any, any, any>>;
5
- type DepWorkflows<Deps extends ModuleMap> = keyof Deps extends never ? {} : EnsureWorkflowRecord<UnionToIntersection<{
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 ContextFromDeps<Deps> = [keyof Deps] extends [never] ? {} : {
15
- [K in keyof Deps]: Deps[K] extends Module<any, infer Ctx, any, any> ? Ctx : never;
16
- }[keyof Deps];
17
- type FinalContext<Reg extends ActionRegistry, Context extends Record<string, any>, Deps extends ModuleMap> = Context & ContextFromDeps<Deps>;
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, Context extends Record<string, any>, Own extends ModuleShape, Deps extends ModuleMap> = {
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
- registry: Reg;
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
- getContext: () => FinalContext<Reg, Context, Deps>;
35
+ getServices: () => FinalServices<S, Deps>;
32
36
  };
33
37
  };
34
- export type ModuleContext<Reg extends ActionRegistry, WFReg extends Record<string, WorkflowDef<any, any, any, any, any>>, Context extends Record<string, any>> = {
35
- wf: ReturnType<typeof createWorkflow<Reg, WFReg, Context>>;
36
- context: Context;
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, Context extends Record<string, any>>(): <Use extends ModuleMap = {}, Own extends ModuleShape = {}>(config: {
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<Reg, DepWorkflows<Use>, Context>) => Own;
41
- }) => Module<Reg, Context, Own, Use>;
45
+ define: (ctx: ModuleContext<typeof config.actionRegistry, DepWorkflows<Use>, S>) => Own;
46
+ }) => Module<Reg, S, Own, Use>;
42
47
  export {};