@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 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?: PipeDef;
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[], PB extends PipeBuilder<any, Reg, Services, Results, any>>(id: ID, input: (ctx: {
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: (p: PipeBuilder<Arr[number], Reg, Services, Results>) => PB, options?: StepOptions<Input, Results>): WorkflowBuilder<Reg, Services, WFReg, Input, [
95
- ...Steps,
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 pb = new PipeBuilder();
59
- const built = builder(pb);
60
- const steps = built.getSteps();
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: "__pipe__",
64
- pipe: {
65
- input,
66
- steps,
67
- },
80
+ action: "__pipe_map__",
68
81
  dependsOn: deps,
69
82
  when: this.pendingWhen,
70
- resolve: () => ({ kind: "none" }),
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
- extras.frames = {};
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; // 👈 important for observer consistency
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
- // Pipe handling
145
- // -----------------------------
146
- if (step.pipe && step.pipe.steps) {
147
- const items = step.pipe.input(stepCtx);
148
- const pipeResults = await Promise.all(items.map(async (item) => {
149
- let current = item;
150
- for (const pipeStep of step.pipe.steps) {
151
- // return new Proxy(
152
- // {
153
- // input,
154
- // results,
155
- // ...helpers,
156
- // },
157
- // {
158
- // get(target, prop) {
159
- // // 1. explicit keys first
160
- // if (prop in target) return target[prop as keyof typeof target];
161
- //
162
- // // 2. fallback to results
163
- // if (prop in results) return results[prop as keyof typeof target];
164
- //
165
- // return undefined;
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 pipeResults;
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 = null;
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
- console.log("action", action);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pogodisco/zephyr",
3
- "version": "1.5.5",
3
+ "version": "1.5.6",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },