1- import { Flow , BaseContext , Context , ExtractFlowContext } from '../../src/index.js' ;
1+ import { Flow , FlowContext , ExtractFlowContext } from '../../src/index.js' ;
22import { describe , it , expectTypeOf } from 'vitest' ;
3+ import type { Json } from '../../src/index.js' ;
34
45// Mock types for testing
56interface TestSql {
@@ -11,150 +12,84 @@ interface TestRedis {
1112 set : ( key : string , value : string ) => Promise < void > ;
1213}
1314
14- interface TestSupabase {
15- from : ( table : string ) => any ;
16- }
17-
1815describe ( 'Context Type Inference Tests' , ( ) => {
19- it ( 'should have minimal context by default' , ( ) => {
16+ it ( 'should have FlowContext by default (no custom resources) ' , ( ) => {
2017 const flow = new Flow ( { slug : 'minimal_flow' } )
2118 . step ( { slug : 'process' } , ( input , context ) => {
22- expectTypeOf ( context ) . toMatchTypeOf < BaseContext > ( ) ;
19+ // Handler automatically gets FlowContext (no annotation needed!)
20+ expectTypeOf ( context ) . toMatchTypeOf < FlowContext > ( ) ;
2321 expectTypeOf ( context . env ) . toEqualTypeOf < Record < string , string | undefined > > ( ) ;
2422 expectTypeOf ( context . shutdownSignal ) . toEqualTypeOf < AbortSignal > ( ) ;
25-
26- // Should not have sql by default
27- expectTypeOf ( context ) . not . toHaveProperty ( 'sql' ) ;
28-
23+ expectTypeOf ( context . stepTask . run_id ) . toEqualTypeOf < string > ( ) ;
24+ expectTypeOf ( context . rawMessage . msg_id ) . toEqualTypeOf < number > ( ) ;
25+
2926 return { processed : true } ;
3027 } ) ;
3128
32- // ExtractFlowContext should return BaseContext for minimal flow
33- type FlowContext = ExtractFlowContext < typeof flow > ;
34- expectTypeOf < FlowContext > ( ) . toEqualTypeOf < BaseContext > ( ) ;
29+ // ExtractFlowContext returns just FlowContext (no custom resources)
30+ type FlowCtx = ExtractFlowContext < typeof flow > ;
31+ expectTypeOf < FlowCtx > ( ) . toEqualTypeOf < FlowContext > ( ) ;
3532 } ) ;
3633
37- it ( 'should infer context from single handler type annotation' , ( ) => {
38- const flow = new Flow ( { slug : 'single_inferred' } )
39- . step ( { slug : 'query' } , ( input , context : { sql : TestSql } ) => {
34+ it ( 'should provide custom context via Flow type parameter' , ( ) => {
35+ const flow = new Flow < Json , { sql : TestSql } > ( { slug : 'custom_context' } )
36+ . step ( { slug : 'query' } , ( input , context ) => {
37+ // No handler annotation needed! Type parameter provides context
4038 expectTypeOf ( context . sql ) . toEqualTypeOf < TestSql > ( ) ;
41- // Base Context properties are STILL available even without typing
4239 expectTypeOf ( context . env ) . toEqualTypeOf < Record < string , string | undefined > > ( ) ;
4340 expectTypeOf ( context . shutdownSignal ) . toEqualTypeOf < AbortSignal > ( ) ;
44-
41+
4542 return { result : 'data' } ;
4643 } ) ;
4744
48- // ExtractFlowContext should return BaseContext & { sql: TestSql }
49- type FlowContext = ExtractFlowContext < typeof flow > ;
50- expectTypeOf < FlowContext > ( ) . toEqualTypeOf < BaseContext & { sql : TestSql } > ( ) ;
45+ // ExtractFlowContext returns FlowContext & custom resources
46+ type FlowCtx = ExtractFlowContext < typeof flow > ;
47+ expectTypeOf < FlowCtx > ( ) . toEqualTypeOf < FlowContext & { sql : TestSql } > ( ) ;
5148 } ) ;
5249
53- it ( 'should accumulate context from multiple handlers' , ( ) => {
54- const flow = new Flow ( { slug : 'multi_inferred' } )
55- . step ( { slug : 'query' } , ( input , context : Context < { sql : TestSql } > ) => {
50+ it ( 'should share custom context across all steps' , ( ) => {
51+ const flow = new Flow < Json , { sql : TestSql ; redis : TestRedis } > ( { slug : 'shared_context' } )
52+ . step ( { slug : 'query' } , ( input , context ) => {
53+ // All steps get the same context automatically
5654 expectTypeOf ( context . sql ) . toEqualTypeOf < TestSql > ( ) ;
55+ expectTypeOf ( context . redis ) . toEqualTypeOf < TestRedis > ( ) ;
5756 return { users : [ ] } ;
5857 } )
59- . step ( { slug : 'cache' } , ( input , context : Context < { redis : TestRedis } > ) => {
58+ . step ( { slug : 'cache' } , ( input , context ) => {
59+ // Second step also has access to all resources
60+ expectTypeOf ( context . sql ) . toEqualTypeOf < TestSql > ( ) ;
6061 expectTypeOf ( context . redis ) . toEqualTypeOf < TestRedis > ( ) ;
6162 return { cached : true } ;
62- } )
63- . step ( { slug : 'notify' } , ( input , context : Context < { sql : TestSql , supabase : TestSupabase } > ) => {
64- expectTypeOf ( context . sql ) . toEqualTypeOf < TestSql > ( ) ;
65- expectTypeOf ( context . supabase ) . toEqualTypeOf < TestSupabase > ( ) ;
66- return { notified : true } ;
6763 } ) ;
6864
69- // ExtractFlowContext should have BaseContext plus all accumulated resources
70- type FlowContext = ExtractFlowContext < typeof flow > ;
71- expectTypeOf < FlowContext > ( ) . toEqualTypeOf < BaseContext & {
65+ // ExtractFlowContext returns FlowContext & all custom resources
66+ type FlowCtx = ExtractFlowContext < typeof flow > ;
67+ expectTypeOf < FlowCtx > ( ) . toEqualTypeOf < FlowContext & {
7268 sql : TestSql ;
7369 redis : TestRedis ;
74- supabase : TestSupabase ;
7570 } > ( ) ;
7671 } ) ;
7772
78- it ( 'should support explicit context type parameter' , ( ) => {
79- interface ExplicitContext {
80- sql : TestSql ;
81- cache : TestRedis ;
82- pubsub : { publish : ( event : string ) => void } ;
83- }
84-
85- const flow = new Flow < { userId : string } , ExplicitContext > ( { slug : 'explicit_flow' } )
86- . step ( { slug : 'get_user' } , ( input , context ) => {
87- // All properties from ExplicitContext should be available
88- expectTypeOf ( context ) . toMatchTypeOf < BaseContext & ExplicitContext > ( ) ;
89- expectTypeOf ( context . sql ) . toEqualTypeOf < TestSql > ( ) ;
90- expectTypeOf ( context . cache ) . toEqualTypeOf < TestRedis > ( ) ;
91- expectTypeOf ( context . pubsub ) . toEqualTypeOf < { publish : ( event : string ) => void } > ( ) ;
92-
93- return { id : 1 , name : 'Test' } ;
94- } ) ;
95-
96- // ExtractFlowContext should return BaseContext merged with explicit type
97- type FlowContext = ExtractFlowContext < typeof flow > ;
98- expectTypeOf < FlowContext > ( ) . toEqualTypeOf < BaseContext & ExplicitContext > ( ) ;
99- } ) ;
100-
101- it ( 'should support mixed explicit and inferred context' , ( ) => {
102- const flow = new Flow < { id : string } , { sql : TestSql } > ( { slug : 'mixed_flow' } )
103- . step ( { slug : 'query' } , ( input , context ) => {
104- // Has sql from explicit type
105- expectTypeOf ( context . sql ) . toEqualTypeOf < TestSql > ( ) ;
106- return { data : 'result' } ;
107- } )
108- . step ( { slug : 'enhance' } , ( input , context : Context < { sql : TestSql , ai : { generate : ( ) => string } } > ) => {
109- // Should have both sql (from explicit) and ai (from inference)
110- expectTypeOf ( context . sql ) . toEqualTypeOf < TestSql > ( ) ;
111- expectTypeOf ( context . ai ) . toEqualTypeOf < { generate : ( ) => string } > ( ) ;
112- return { enhanced : true } ;
113- } ) ;
114-
115- // ExtractFlowContext should have BaseContext plus both explicit and inferred
116- type FlowContext = ExtractFlowContext < typeof flow > ;
117- expectTypeOf < FlowContext > ( ) . toEqualTypeOf < BaseContext & {
118- sql : TestSql ;
119- ai : { generate : ( ) => string } ;
120- } > ( ) ;
121- } ) ;
122-
123- it ( 'should allow handlers to specify only custom context but still get base Context' , ( ) => {
124- const flow = new Flow ( { slug : 'custom_only' } )
125- . step ( { slug : 'process' } , ( input , context : { customField : string } ) => {
126- expectTypeOf ( context . customField ) . toEqualTypeOf < string > ( ) ;
127- // Base Context properties are ALWAYS available now
128- expectTypeOf ( context . env ) . toEqualTypeOf < Record < string , string | undefined > > ( ) ;
129- expectTypeOf ( context . shutdownSignal ) . toEqualTypeOf < AbortSignal > ( ) ;
130-
131- return { processed : context . customField } ;
132- } ) ;
133-
134- // ExtractFlowContext should have BaseContext plus the custom field
135- type FlowContext = ExtractFlowContext < typeof flow > ;
136- expectTypeOf < FlowContext > ( ) . toEqualTypeOf < BaseContext & { customField : string } > ( ) ;
137- } ) ;
138-
13973 it ( 'should preserve existing step type inference while adding context' , ( ) => {
140- const flow = new Flow < { initial : number } > ( { slug : 'step_chain' } )
141- . step ( { slug : 'double' } , ( input , context : Context < { multiplier : number } > ) => {
74+ const flow = new Flow < { initial : number } , { multiplier : number } > ( { slug : 'step_chain' } )
75+ . step ( { slug : 'double' } , ( input , context ) => {
76+ // Input inference still works
14277 expectTypeOf ( input . run . initial ) . toEqualTypeOf < number > ( ) ;
78+ // Custom context available
14379 expectTypeOf ( context . multiplier ) . toEqualTypeOf < number > ( ) ;
14480 return { doubled : input . run . initial * 2 } ;
14581 } )
146- . step ( { slug : 'format' , dependsOn : [ 'double' ] } , ( input , context : Context < { formatter : ( n : number ) => string } > ) => {
82+ . step ( { slug : 'format' , dependsOn : [ 'double' ] } , ( input , context ) => {
83+ // Dependent step has access to previous step output
14784 expectTypeOf ( input . run . initial ) . toEqualTypeOf < number > ( ) ;
14885 expectTypeOf ( input . double . doubled ) . toEqualTypeOf < number > ( ) ;
149- expectTypeOf ( context . formatter ) . toEqualTypeOf < ( n : number ) => string > ( ) ;
150- return { formatted : context . formatter ( input . double . doubled ) } ;
86+ // And still has custom context
87+ expectTypeOf ( context . multiplier ) . toEqualTypeOf < number > ( ) ;
88+ return { formatted : String ( input . double . doubled ) } ;
15189 } ) ;
15290
153- // Context should have base plus accumulated requirements
154- type FlowContext = ExtractFlowContext < typeof flow > ;
155- expectTypeOf < FlowContext > ( ) . toEqualTypeOf < BaseContext & {
156- multiplier : number ;
157- formatter : ( n : number ) => string ;
158- } > ( ) ;
91+ // Context includes custom resources
92+ type FlowCtx = ExtractFlowContext < typeof flow > ;
93+ expectTypeOf < FlowCtx > ( ) . toEqualTypeOf < FlowContext & { multiplier : number } > ( ) ;
15994 } ) ;
160- } ) ;
95+ } ) ;
0 commit comments