1- import { Flow , type StepInput , type ExtractFlowContext } from '../../src/index.js' ;
1+ import {
2+ Flow ,
3+ type StepInput ,
4+ type ExtractFlowContext ,
5+ } from '../../src/index.js' ;
26import { describe , it , expectTypeOf } from 'vitest' ;
37
48describe ( '.array() method type constraints' , ( ) => {
@@ -9,7 +13,10 @@ describe('.array() method type constraints', () => {
913 . array ( { slug : 'objects' } , ( ) => [ { id : 1 } , { id : 2 } ] )
1014 . array ( { slug : 'strings' } , ( ) => [ 'a' , 'b' , 'c' ] )
1115 . array ( { slug : 'empty' } , ( ) => [ ] )
12- . array ( { slug : 'nested' } , ( ) => [ [ 1 , 2 ] , [ 3 , 4 ] ] ) ;
16+ . array ( { slug : 'nested' } , ( ) => [
17+ [ 1 , 2 ] ,
18+ [ 3 , 4 ] ,
19+ ] ) ;
1320 } ) ;
1421
1522 it ( 'should accept handlers that return Promise<Array>' , ( ) => {
@@ -42,12 +49,30 @@ describe('.array() method type constraints', () => {
4249 // @ts -expect-error - should reject Promise<null>
4350 . array ( { slug : 'invalid_async_null' } , async ( ) => null ) ;
4451 } ) ;
52+
53+ it ( 'should reject non-JSON array element shapes' , ( ) => {
54+ new Flow < Record < string , never > > ( { slug : 'test_json' } )
55+ // @ts -expect-error - undefined element is not Json
56+ . array ( { slug : 'invalid_undefined_element' } , ( ) => [ undefined ] )
57+ // @ts -expect-error - function element is not Json
58+ . array ( { slug : 'invalid_function_element' } , ( ) => [ ( ) => 'x' ] )
59+ // @ts -expect-error - symbol element is not Json
60+ . array ( { slug : 'invalid_symbol_element' } , ( ) => [ Symbol ( 'x' ) ] )
61+ // @ts -expect-error - object property with undefined is not Json
62+ . array ( { slug : 'invalid_object_property' } , ( ) => [
63+ { id : 1 , maybe : undefined as string | undefined } ,
64+ ] ) ;
65+ } ) ;
4566 } ) ;
4667
4768 describe ( 'type inference' , ( ) => {
4869 it ( 'should provide correct input types for dependent steps' , ( ) => {
4970 new Flow < { count : number } > ( { slug : 'test' } )
50- . array ( { slug : 'items' } , ( flowInput ) => Array ( flowInput . count ) . fill ( 0 ) . map ( ( _ , i ) => i ) )
71+ . array ( { slug : 'items' } , ( flowInput ) =>
72+ Array ( flowInput . count )
73+ . fill ( 0 )
74+ . map ( ( _ , i ) => i )
75+ )
5176 . step ( { slug : 'process' , dependsOn : [ 'items' ] } , ( deps ) => {
5277 expectTypeOf ( deps ) . toMatchTypeOf < {
5378 items : number [ ] ;
@@ -58,18 +83,32 @@ describe('.array() method type constraints', () => {
5883
5984 it ( 'should correctly infer element types from arrays' , ( ) => {
6085 new Flow < { userId : string } > ( { slug : 'test' } )
61- . array ( { slug : 'users' } , ( ) => [ { id : 1 , name : 'John' } , { id : 2 , name : 'Jane' } ] )
86+ . array ( { slug : 'users' } , ( ) => [
87+ { id : 1 , name : 'John' } ,
88+ { id : 2 , name : 'Jane' } ,
89+ ] )
6290 . step ( { slug : 'count_users' , dependsOn : [ 'users' ] } , ( deps ) => {
63- expectTypeOf ( deps . users ) . toEqualTypeOf < { id : number ; name : string } [ ] > ( ) ;
64- expectTypeOf ( deps . users [ 0 ] ) . toMatchTypeOf < { id : number ; name : string } > ( ) ;
91+ expectTypeOf ( deps . users ) . toEqualTypeOf <
92+ { id : number ; name : string } [ ]
93+ > ( ) ;
94+ expectTypeOf ( deps . users [ 0 ] ) . toMatchTypeOf < {
95+ id : number ;
96+ name : string ;
97+ } > ( ) ;
6598 return deps . users . length ;
6699 } ) ;
67100 } ) ;
68101
69102 it ( 'should handle complex nested array types' , ( ) => {
70103 new Flow < { depth : number } > ( { slug : 'test' } )
71104 . array ( { slug : 'matrix' } , ( flowInput ) =>
72- Array ( flowInput . depth ) . fill ( 0 ) . map ( ( ) => Array ( 3 ) . fill ( 0 ) . map ( ( ) => ( { value : Math . random ( ) } ) ) )
105+ Array ( flowInput . depth )
106+ . fill ( 0 )
107+ . map ( ( ) =>
108+ Array ( 3 )
109+ . fill ( 0 )
110+ . map ( ( ) => ( { value : Math . random ( ) } ) )
111+ )
73112 )
74113 . step ( { slug : 'flatten' , dependsOn : [ 'matrix' ] } , ( deps ) => {
75114 expectTypeOf ( deps . matrix ) . toEqualTypeOf < { value : number } [ ] [ ] > ( ) ;
@@ -83,12 +122,14 @@ describe('.array() method type constraints', () => {
83122 new Flow < { url : string } > ( { slug : 'test' } )
84123 . array ( { slug : 'data' } , async ( flowInput ) => {
85124 // Simulate async data fetching
86- await new Promise ( resolve => setTimeout ( resolve , 1 ) ) ;
125+ await new Promise ( ( resolve ) => setTimeout ( resolve , 1 ) ) ;
87126 return [ { url : flowInput . url , status : 200 } ] ;
88127 } )
89128 . step ( { slug : 'validate' , dependsOn : [ 'data' ] } , ( deps ) => {
90- expectTypeOf ( deps . data ) . toEqualTypeOf < { url : string ; status : number } [ ] > ( ) ;
91- return deps . data . every ( item => item . status === 200 ) ;
129+ expectTypeOf ( deps . data ) . toEqualTypeOf <
130+ { url : string ; status : number } [ ]
131+ > ( ) ;
132+ return deps . data . every ( ( item ) => item . status === 200 ) ;
92133 } ) ;
93134 } ) ;
94135 } ) ;
@@ -119,34 +160,49 @@ describe('.array() method type constraints', () => {
119160
120161 it ( 'should correctly type multi-dependency array steps' , ( ) => {
121162 new Flow < { base : number } > ( { slug : 'test' } )
122- . array ( { slug : 'numbers' } , ( flowInput ) => [ flowInput . base , flowInput . base + 1 ] )
163+ . array ( { slug : 'numbers' } , ( flowInput ) => [
164+ flowInput . base ,
165+ flowInput . base + 1 ,
166+ ] )
123167 . array ( { slug : 'letters' } , ( ) => [ 'a' , 'b' ] )
124- . array ( { slug : 'combined' , dependsOn : [ 'numbers' , 'letters' ] } , ( deps ) => {
125- expectTypeOf ( deps ) . toMatchTypeOf < {
126- numbers : number [ ] ;
127- letters : string [ ] ;
128- } > ( ) ;
168+ . array (
169+ { slug : 'combined' , dependsOn : [ 'numbers' , 'letters' ] } ,
170+ ( deps ) => {
171+ expectTypeOf ( deps ) . toMatchTypeOf < {
172+ numbers : number [ ] ;
173+ letters : string [ ] ;
174+ } > ( ) ;
129175
130- return deps . numbers . map ( ( num , i ) => ( {
131- number : num ,
132- letter : deps . letters [ i ] || 'z'
133- } ) ) ;
134- } ) ;
176+ return deps . numbers . map ( ( num , i ) => ( {
177+ number : num ,
178+ letter : deps . letters [ i ] || 'z' ,
179+ } ) ) ;
180+ }
181+ ) ;
135182 } ) ;
136183 } ) ;
137184
138185 describe ( 'context typing' , ( ) => {
139186 it ( 'should provide custom context via Flow type parameter' , ( ) => {
140187 // eslint-disable-next-line @typescript-eslint/no-explicit-any
141- const flow = new Flow < { id : number } , { api : { get : ( id : number ) => Promise < any > } } > ( { slug : 'test' } )
142- . array ( { slug : 'fetch_data' } , ( flowInput , context ) => {
188+ const flow = new Flow <
189+ { id : number } ,
190+ { api : { get : ( id : number ) => Promise < any > } }
191+ > ( { slug : 'test' } ) . array (
192+ { slug : 'fetch_data' } ,
193+ ( flowInput , context ) => {
143194 // No handler annotation needed! Type parameter provides context
144- expectTypeOf ( context . api ) . toEqualTypeOf < { get : ( id : number ) => Promise < any > } > ( ) ;
145- expectTypeOf ( context . env ) . toEqualTypeOf < Record < string , string | undefined > > ( ) ;
195+ expectTypeOf ( context . api ) . toEqualTypeOf < {
196+ get : ( id : number ) => Promise < any > ;
197+ } > ( ) ;
198+ expectTypeOf ( context . env ) . toEqualTypeOf <
199+ Record < string , string | undefined >
200+ > ( ) ;
146201 expectTypeOf ( context . shutdownSignal ) . toEqualTypeOf < AbortSignal > ( ) ;
147202
148203 return [ { id : flowInput . id , data : 'mock' } ] ;
149- } ) ;
204+ }
205+ ) ;
150206
151207 // ExtractFlowContext should include FlowContext & custom resources
152208 type FlowCtx = ExtractFlowContext < typeof flow > ;
@@ -160,10 +216,15 @@ describe('.array() method type constraints', () => {
160216 } ) ;
161217
162218 it ( 'should share custom context across array and regular steps' , ( ) => {
163- const flow = new Flow < { count : number } , { generator : ( ) => number ; processor : ( items : number [ ] ) => string } > ( { slug : 'test' } )
219+ const flow = new Flow <
220+ { count : number } ,
221+ { generator : ( ) => number ; processor : ( items : number [ ] ) => string }
222+ > ( { slug : 'test' } )
164223 . array ( { slug : 'items' } , ( flowInput , context ) => {
165224 // All steps get the same context automatically
166- return Array ( flowInput . count ) . fill ( 0 ) . map ( ( ) => context . generator ( ) ) ;
225+ return Array ( flowInput . count )
226+ . fill ( 0 )
227+ . map ( ( ) => context . generator ( ) ) ;
167228 } )
168229 . step ( { slug : 'process' } , ( flowInput , context ) => {
169230 return context . processor ( [ 1 , 2 , 3 ] ) ;
@@ -184,14 +245,23 @@ describe('.array() method type constraints', () => {
184245 describe ( 'handler signature validation' , ( ) => {
185246 it ( 'should correctly type array step handlers when using getStepDefinition' , ( ) => {
186247 const flow = new Flow < { size : number } > ( { slug : 'test' } )
187- . array ( { slug : 'data' } , ( flowInput , _context ) => Array ( flowInput . size ) . fill ( 0 ) . map ( ( _ , i ) => ( { index : i } ) ) )
188- . step ( { slug : 'dependent' , dependsOn : [ 'data' ] } , ( deps , _context ) => deps . data . length ) ;
248+ . array ( { slug : 'data' } , ( flowInput , _context ) =>
249+ Array ( flowInput . size )
250+ . fill ( 0 )
251+ . map ( ( _ , i ) => ( { index : i } ) )
252+ )
253+ . step (
254+ { slug : 'dependent' , dependsOn : [ 'data' ] } ,
255+ ( deps , _context ) => deps . data . length
256+ ) ;
189257
190258 const arrayStep = flow . getStepDefinition ( 'data' ) ;
191259
192260 // Test array step handler type - root steps receive flowInput directly (no run key)
193261 expectTypeOf ( arrayStep . handler ) . toBeFunction ( ) ;
194- expectTypeOf ( arrayStep . handler ) . parameter ( 0 ) . toMatchTypeOf < { size : number } > ( ) ;
262+ expectTypeOf ( arrayStep . handler )
263+ . parameter ( 0 )
264+ . toMatchTypeOf < { size : number } > ( ) ;
195265 expectTypeOf ( arrayStep . handler ) . returns . toMatchTypeOf <
196266 { index : number } [ ] | Promise < { index : number } [ ] >
197267 > ( ) ;
0 commit comments