@@ -438,3 +438,111 @@ describe('skippable deps type safety', () => {
438438 } ) ;
439439 } ) ;
440440} ) ;
441+
442+ /**
443+ * Compile-time error tests for skippable deps
444+ *
445+ * These tests use @ts-expect-error to verify that TypeScript correctly
446+ * rejects invalid patterns when accessing skippable dependencies.
447+ */
448+ describe ( 'skippable deps compile-time errors' , ( ) => {
449+ describe ( 'direct property access on optional deps' , ( ) => {
450+ it ( 'should reject direct property access on skippable dep without null check' , ( ) => {
451+ new Flow < { value : number } > ( { slug : 'test' } )
452+ . step ( { slug : 'maybeSkipped' , retriesExhausted : 'skip' } , ( ) => ( {
453+ data : 'result' ,
454+ } ) )
455+ . step ( { slug : 'consumer' , dependsOn : [ 'maybeSkipped' ] } , ( deps ) => {
456+ // @ts -expect-error - deps.maybeSkipped is optional, cannot access .data directly
457+ const result : string = deps . maybeSkipped . data ;
458+ return { result } ;
459+ } ) ;
460+ } ) ;
461+
462+ it ( 'should reject direct property access with else: skip' , ( ) => {
463+ new Flow < { value : number } > ( { slug : 'test' } )
464+ . step ( { slug : 'conditional' , if : { value : 42 } , else : 'skip' } , ( ) => ( {
465+ processed : true ,
466+ } ) )
467+ . step ( { slug : 'next' , dependsOn : [ 'conditional' ] } , ( deps ) => {
468+ // @ts -expect-error - deps.conditional is optional due to else: 'skip'
469+ const flag : boolean = deps . conditional . processed ;
470+ return { flag } ;
471+ } ) ;
472+ } ) ;
473+
474+ it ( 'should reject direct property access with else: skip-cascade' , ( ) => {
475+ new Flow < { value : number } > ( { slug : 'test' } )
476+ . step (
477+ { slug : 'cascading' , if : { value : 42 } , else : 'skip-cascade' } ,
478+ ( ) => ( { count : 10 } )
479+ )
480+ . step ( { slug : 'next' , dependsOn : [ 'cascading' ] } , ( deps ) => {
481+ // @ts -expect-error - deps.cascading is optional due to else: 'skip-cascade'
482+ const num : number = deps . cascading . count ;
483+ return { num } ;
484+ } ) ;
485+ } ) ;
486+
487+ it ( 'should reject direct property access with retriesExhausted: skip-cascade' , ( ) => {
488+ new Flow < { value : number } > ( { slug : 'test' } )
489+ . step ( { slug : 'risky' , retriesExhausted : 'skip-cascade' } , ( ) => ( {
490+ status : 'ok' ,
491+ } ) )
492+ . step ( { slug : 'next' , dependsOn : [ 'risky' ] } , ( deps ) => {
493+ // @ts -expect-error - deps.risky is optional due to retriesExhausted: skip-cascade
494+ const s : string = deps . risky . status ;
495+ return { s } ;
496+ } ) ;
497+ } ) ;
498+ } ) ;
499+
500+ describe ( 'mixed deps - optional and required' , ( ) => {
501+ it ( 'should allow direct access on required dep but reject on optional' , ( ) => {
502+ new Flow < { value : number } > ( { slug : 'test' } )
503+ . step ( { slug : 'required' } , ( ) => ( { reqData : 'always' } ) )
504+ . step ( { slug : 'optional' , retriesExhausted : 'skip' } , ( ) => ( {
505+ optData : 'maybe' ,
506+ } ) )
507+ . step (
508+ { slug : 'consumer' , dependsOn : [ 'required' , 'optional' ] } ,
509+ ( deps ) => {
510+ // This is fine - required dep is always present
511+ const req : string = deps . required . reqData ;
512+
513+ // @ts -expect-error - deps.optional is optional, cannot access .optData directly
514+ const opt : string = deps . optional . optData ;
515+
516+ return { req, opt } ;
517+ }
518+ ) ;
519+ } ) ;
520+ } ) ;
521+
522+ describe ( 'array and map steps with skip modes' , ( ) => {
523+ it ( 'should reject direct access on skippable array step output' , ( ) => {
524+ new Flow < { items : string [ ] } > ( { slug : 'test' } )
525+ . array ( { slug : 'processed' , retriesExhausted : 'skip' } , ( input ) =>
526+ input . items . map ( ( s ) => s . toUpperCase ( ) )
527+ )
528+ . step ( { slug : 'consumer' , dependsOn : [ 'processed' ] } , ( deps ) => {
529+ // @ts -expect-error - deps.processed is optional, cannot access .length directly
530+ const len : number = deps . processed . length ;
531+ return { len } ;
532+ } ) ;
533+ } ) ;
534+
535+ it ( 'should reject direct access on skippable map step output' , ( ) => {
536+ new Flow < string [ ] > ( { slug : 'test' } )
537+ . map (
538+ { slug : 'doubled' , retriesExhausted : 'skip' } ,
539+ ( item ) => item + item
540+ )
541+ . step ( { slug : 'consumer' , dependsOn : [ 'doubled' ] } , ( deps ) => {
542+ // @ts -expect-error - deps.doubled is optional, cannot access [0] directly
543+ const first : string = deps . doubled [ 0 ] ;
544+ return { first } ;
545+ } ) ;
546+ } ) ;
547+ } ) ;
548+ } ) ;
0 commit comments