@@ -2,60 +2,100 @@ import type { Span } from "@opentelemetry/api";
22import { defaultStepConfig } from "../internal/utils" ;
33import { matchStep } from "./helpers" ;
44import { throwIfAborted } from "./signal" ;
5- import type { WorkflowStepLoop , WorkflowStepLoopConfig } from "./types" ;
5+ import type { WorkflowStepLoop , WorkflowStepLoopConfig , WorkflowStepLoopSteps } from "./types" ;
66
77type LoopType = "dowhile" | "dountil" ;
8+ type LoopStepMetadata = { id : string ; name ?: string ; purpose ?: string ; retries ?: number } ;
9+
10+ function splitLoopConfig < INPUT , DATA , RESULT > ( config : WorkflowStepLoopConfig < INPUT , DATA , RESULT > ) {
11+ if ( "step" in config ) {
12+ const { step : _step , condition, ...stepConfig } = config ;
13+ return { condition, stepConfig : stepConfig as LoopStepMetadata } ;
14+ }
15+
16+ const { steps : _steps , condition, ...stepConfig } = config ;
17+ return { condition, stepConfig : stepConfig as LoopStepMetadata } ;
18+ }
19+
20+ function getLoopSteps < INPUT , DATA , RESULT > (
21+ config : WorkflowStepLoopConfig < INPUT , DATA , RESULT > ,
22+ ) : WorkflowStepLoopSteps < INPUT , DATA , RESULT > {
23+ if ( "steps" in config && config . steps ) {
24+ if ( config . steps . length === 0 ) {
25+ throw new Error ( "andDoWhile/andDoUntil requires at least one step" ) ;
26+ }
27+ return config . steps ;
28+ }
29+
30+ return [ config . step ] ;
31+ }
832
933const createLoopStep = < INPUT , DATA , RESULT > (
1034 loopType : LoopType ,
11- { step , condition , ... config } : WorkflowStepLoopConfig < INPUT , DATA , RESULT > ,
35+ config : WorkflowStepLoopConfig < INPUT , DATA , RESULT > ,
1236) => {
13- const finalStep = matchStep ( step ) ;
37+ const { condition, stepConfig } = splitLoopConfig ( config ) ;
38+ const loopSteps = getLoopSteps ( config ) ;
39+ const resolvedSteps = loopSteps . map ( ( loopStep ) => matchStep ( loopStep ) ) ;
40+ const firstStep = loopSteps [ 0 ] ;
41+
42+ if ( ! firstStep ) {
43+ throw new Error ( "andDoWhile/andDoUntil requires at least one step" ) ;
44+ }
1445
1546 return {
16- ...defaultStepConfig ( config ) ,
47+ ...defaultStepConfig ( stepConfig ) ,
1748 type : "loop" ,
1849 loopType,
19- step,
50+ step : firstStep ,
51+ steps : loopSteps ,
2052 condition,
2153 execute : async ( context ) => {
2254 const { state } = context ;
2355 const traceContext = state . workflowContext ?. traceContext ;
2456 let currentData = context . data as DATA | RESULT ;
2557 let iteration = 0 ;
2658
27- while ( true ) {
28- throwIfAborted ( state . signal ) ;
59+ const runResolvedStep = async ( stepIndex : number ) => {
60+ const resolvedStep = resolvedSteps [ stepIndex ] ;
61+ if ( ! resolvedStep ) {
62+ return ;
63+ }
2964
3065 let childSpan : Span | undefined ;
3166 if ( traceContext ) {
67+ const isSingleLoopStep = resolvedSteps . length === 1 ;
3268 childSpan = traceContext . createStepSpan (
33- iteration ,
34- finalStep . type ,
35- finalStep . name || finalStep . id || `Loop ${ iteration + 1 } ` ,
69+ iteration * resolvedSteps . length + stepIndex ,
70+ resolvedStep . type ,
71+ resolvedStep . name ||
72+ resolvedStep . id ||
73+ ( isSingleLoopStep
74+ ? `Loop ${ iteration + 1 } `
75+ : `Loop ${ iteration + 1 } .${ stepIndex + 1 } ` ) ,
3676 {
37- parentStepId : config . id ,
38- parallelIndex : iteration ,
77+ parentStepId : stepConfig . id ,
78+ parallelIndex : isSingleLoopStep ? iteration : stepIndex ,
3979 input : currentData ,
4080 attributes : {
4181 "workflow.step.loop" : true ,
4282 "workflow.step.parent_type" : "loop" ,
4383 "workflow.step.loop_type" : loopType ,
84+ "workflow.step.loop_iteration" : iteration ,
85+ "workflow.step.loop_step_index" : stepIndex ,
4486 } ,
4587 } ,
4688 ) ;
4789 }
4890
49- const subState = {
50- ...state ,
51- workflowContext : undefined ,
52- } ;
53-
5491 const executeStep = ( ) =>
55- finalStep . execute ( {
92+ resolvedStep . execute ( {
5693 ...context ,
57- data : currentData as DATA ,
58- state : subState ,
94+ data : currentData as never ,
95+ state : {
96+ ...state ,
97+ workflowContext : undefined ,
98+ } ,
5999 } ) ;
60100
61101 try {
@@ -73,6 +113,15 @@ const createLoopStep = <INPUT, DATA, RESULT>(
73113 }
74114 throw error ;
75115 }
116+ } ;
117+
118+ while ( true ) {
119+ throwIfAborted ( state . signal ) ;
120+
121+ for ( let stepIndex = 0 ; stepIndex < resolvedSteps . length ; stepIndex += 1 ) {
122+ throwIfAborted ( state . signal ) ;
123+ await runResolvedStep ( stepIndex ) ;
124+ }
76125
77126 iteration += 1 ;
78127 throwIfAborted ( state . signal ) ;
0 commit comments