@@ -30,10 +30,35 @@ import type {
3030 NodeRef ,
3131 ToolDef ,
3232} from "@stackables/bridge-core" ;
33- import { matchesRequestedFields } from "@stackables/bridge-core" ;
3433
3534const SELF_MODULE = "_" ;
3635
36+ function matchesRequestedFields (
37+ fieldPath : string ,
38+ requestedFields : string [ ] | undefined ,
39+ ) : boolean {
40+ if ( ! requestedFields || requestedFields . length === 0 ) return true ;
41+
42+ for ( const pattern of requestedFields ) {
43+ if ( pattern === fieldPath ) return true ;
44+
45+ if ( fieldPath . startsWith ( pattern + "." ) ) return true ;
46+
47+ if ( pattern . startsWith ( fieldPath + "." ) ) return true ;
48+
49+ if ( pattern . endsWith ( ".*" ) ) {
50+ const prefix = pattern . slice ( 0 , - 2 ) ;
51+ if ( fieldPath . startsWith ( prefix + "." ) ) {
52+ const rest = fieldPath . slice ( prefix . length + 1 ) ;
53+ if ( ! rest . includes ( "." ) ) return true ;
54+ }
55+ if ( fieldPath === prefix ) return true ;
56+ }
57+ }
58+
59+ return false ;
60+ }
61+
3762// ── Public API ──────────────────────────────────────────────────────────────
3863
3964export interface CompileOptions {
@@ -1432,6 +1457,7 @@ class CodegenContext {
14321457 // Build a nested tree from scalar wires using their full output path
14331458 interface TreeNode {
14341459 expr ?: string ;
1460+ terminal ?: boolean ;
14351461 children : Map < string , TreeNode > ;
14361462 }
14371463 const tree : TreeNode = { children : new Map ( ) } ;
@@ -1451,12 +1477,7 @@ class CodegenContext {
14511477 current . children . set ( lastSeg , { children : new Map ( ) } ) ;
14521478 }
14531479 const node = current . children . get ( lastSeg ) ! ;
1454- if ( node . expr != null ) {
1455- // Overdefinition: combine with ?? — first non-null wins
1456- node . expr = `(${ node . expr } ?? ${ this . wireToExpr ( w ) } )` ;
1457- } else {
1458- node . expr = this . wireToExpr ( w ) ;
1459- }
1480+ this . mergeOverdefinedExpr ( node , w ) ;
14601481 }
14611482
14621483 // Emit array-mapped fields into the tree as well
@@ -1794,9 +1815,10 @@ class CodegenContext {
17941815 const { leftRef, rightRef, rightValue } = w . condAnd ;
17951816 const left = this . refToExpr ( leftRef ) ;
17961817 let expr : string ;
1797- if ( rightRef ) expr = `(${ left } && ${ this . refToExpr ( rightRef ) } )` ;
1818+ if ( rightRef )
1819+ expr = `(Boolean(${ left } ) && Boolean(${ this . refToExpr ( rightRef ) } ))` ;
17981820 else if ( rightValue !== undefined )
1799- expr = `(${ left } && ${ emitCoerced ( rightValue ) } )` ;
1821+ expr = `(Boolean( ${ left } ) && Boolean( ${ emitCoerced ( rightValue ) } ) )` ;
18001822 else expr = `Boolean(${ left } )` ;
18011823 expr = this . applyFallbacks ( w , expr ) ;
18021824 return expr ;
@@ -1807,9 +1829,10 @@ class CodegenContext {
18071829 const { leftRef, rightRef, rightValue } = w . condOr ;
18081830 const left = this . refToExpr ( leftRef ) ;
18091831 let expr : string ;
1810- if ( rightRef ) expr = `(${ left } || ${ this . refToExpr ( rightRef ) } )` ;
1832+ if ( rightRef )
1833+ expr = `(Boolean(${ left } ) || Boolean(${ this . refToExpr ( rightRef ) } ))` ;
18111834 else if ( rightValue !== undefined )
1812- expr = `(${ left } || ${ emitCoerced ( rightValue ) } )` ;
1835+ expr = `(Boolean( ${ left } ) || Boolean( ${ emitCoerced ( rightValue ) } ) )` ;
18131836 else expr = `Boolean(${ left } )` ;
18141837 expr = this . applyFallbacks ( w , expr ) ;
18151838 return expr ;
@@ -2230,9 +2253,9 @@ class CodegenContext {
22302253
22312254 // Nullish coalescing (??)
22322255 if ( "nullishFallbackRef" in w && w . nullishFallbackRef ) {
2233- expr = `(${ expr } ?? ${ this . refToExpr ( w . nullishFallbackRef ) } )` ; // lgtm [js/code-injection]
2256+ expr = `((__v) => (__v == null ? undefined : __v))(( ${ expr } ?? ${ this . refToExpr ( w . nullishFallbackRef ) } ) )` ; // lgtm [js/code-injection]
22342257 } else if ( "nullishFallback" in w && w . nullishFallback != null ) {
2235- expr = `(${ expr } ?? ${ emitCoerced ( w . nullishFallback ) } )` ; // lgtm [js/code-injection]
2258+ expr = `((__v) => (__v == null ? undefined : __v))(( ${ expr } ?? ${ emitCoerced ( w . nullishFallback ) } ) )` ; // lgtm [js/code-injection]
22362259 }
22372260 // Nullish control flow (throw/panic on ?? gate)
22382261 if ( "nullishControl" in w && w . nullishControl ) {
@@ -2526,6 +2549,30 @@ class CodegenContext {
25262549
25272550 // ── Nested object literal builder ─────────────────────────────────────────
25282551
2552+ private mergeOverdefinedExpr (
2553+ node : { expr ?: string ; terminal ?: boolean } ,
2554+ wire : Wire ,
2555+ ) : void {
2556+ const nextExpr = this . wireToExpr ( wire ) ;
2557+ const nextIsConstant = "value" in wire ;
2558+
2559+ if ( node . expr == null ) {
2560+ node . expr = nextExpr ;
2561+ node . terminal = nextIsConstant ;
2562+ return ;
2563+ }
2564+
2565+ if ( node . terminal ) return ;
2566+
2567+ if ( nextIsConstant ) {
2568+ node . expr = `((__v) => (__v != null ? __v : ${ nextExpr } ))(${ node . expr } )` ;
2569+ node . terminal = true ;
2570+ return ;
2571+ }
2572+
2573+ node . expr = `(${ node . expr } ?? ${ nextExpr } )` ;
2574+ }
2575+
25292576 /**
25302577 * Build a JavaScript object literal from a set of wires.
25312578 * Handles nested paths by creating nested object literals.
@@ -2540,6 +2587,7 @@ class CodegenContext {
25402587 // Build tree
25412588 interface TreeNode {
25422589 expr ?: string ;
2590+ terminal ?: boolean ;
25432591 children : Map < string , TreeNode > ;
25442592 }
25452593 const root : TreeNode = { children : new Map ( ) } ;
@@ -2560,11 +2608,7 @@ class CodegenContext {
25602608 current . children . set ( lastSeg , { children : new Map ( ) } ) ;
25612609 }
25622610 const node = current . children . get ( lastSeg ) ! ;
2563- if ( node . expr != null ) {
2564- node . expr = `(${ node . expr } ?? ${ this . wireToExpr ( w ) } )` ;
2565- } else {
2566- node . expr = this . wireToExpr ( w ) ;
2567- }
2611+ this . mergeOverdefinedExpr ( node , w ) ;
25682612 }
25692613
25702614 return this . serializeTreeNode ( root , indent ) ;
0 commit comments