@@ -95,7 +95,12 @@ export function compileBridge(
9595 ( i ) : i is ToolDef => i . kind === "tool" ,
9696 ) ;
9797
98- const ctx = new CodegenContext ( bridge , constDefs , toolDefs , options . requestedFields ) ;
98+ const ctx = new CodegenContext (
99+ bridge ,
100+ constDefs ,
101+ toolDefs ,
102+ options . requestedFields ,
103+ ) ;
99104 return ctx . compile ( ) ;
100105}
101106
@@ -251,7 +256,9 @@ class CodegenContext {
251256 this . constDefs = constDefs ;
252257 this . toolDefs = toolDefs ;
253258 this . selfTrunkKey = `${ SELF_MODULE } :${ bridge . type } :${ bridge . field } ` ;
254- this . requestedFields = requestedFields ?. length ? requestedFields : undefined ;
259+ this . requestedFields = requestedFields ?. length
260+ ? requestedFields
261+ : undefined ;
255262
256263 for ( const h of bridge . handles ) {
257264 switch ( h . kind ) {
@@ -1329,8 +1336,36 @@ class CodegenContext {
13291336 // Only check control flow on direct element wires, not sub-array element wires
13301337 const directElemWires = elemWires . filter ( ( w ) => w . to . path . length === 1 ) ;
13311338 const cf = detectControlFlow ( directElemWires ) ;
1332- if ( cf === "continue" ) {
1333- // Use flatMap — skip elements that trigger continue
1339+ // Check if any element wire generates `await` (element-scoped tools or catch fallbacks)
1340+ const needsAsync = elemWires . some ( ( w ) => this . wireNeedsAwait ( w ) ) ;
1341+
1342+ if ( needsAsync ) {
1343+ // ALL async processing must use for...of loop
1344+ const preambleLines : string [ ] = [ ] ;
1345+ this . elementLocalVars . clear ( ) ;
1346+ this . collectElementPreamble ( elemWires , "_el0" , preambleLines ) ;
1347+
1348+ const body = cf
1349+ ? this . buildElementBodyWithControlFlow (
1350+ elemWires ,
1351+ arrayIterators ,
1352+ 0 ,
1353+ 4 ,
1354+ cf === "continue" ? "for-continue" : "break" ,
1355+ )
1356+ : ` _result.push(${ this . buildElementBody ( elemWires , arrayIterators , 0 , 4 ) } );` ;
1357+
1358+ lines . push ( ` const _result = [];` ) ;
1359+ lines . push ( ` for (const _el0 of (${ arrayExpr } ?? [])) {` ) ;
1360+ for ( const pl of preambleLines ) {
1361+ lines . push ( ` ${ pl } ` ) ;
1362+ }
1363+ lines . push ( body ) ;
1364+ lines . push ( ` }` ) ;
1365+ lines . push ( ` return _result;` ) ;
1366+ this . elementLocalVars . clear ( ) ;
1367+ } else if ( cf === "continue" ) {
1368+ // Use flatMap — skip elements that trigger continue (sync only)
13341369 const body = this . buildElementBodyWithControlFlow (
13351370 elemWires ,
13361371 arrayIterators ,
@@ -1342,7 +1377,7 @@ class CodegenContext {
13421377 lines . push ( body ) ;
13431378 lines . push ( ` });` ) ;
13441379 } else if ( cf === "break" ) {
1345- // Use a loop with early break
1380+ // Use a loop with early break (sync)
13461381 const body = this . buildElementBodyWithControlFlow (
13471382 elemWires ,
13481383 arrayIterators ,
@@ -1357,44 +1392,7 @@ class CodegenContext {
13571392 lines . push ( ` return _result;` ) ;
13581393 } else {
13591394 const body = this . buildElementBody ( elemWires , arrayIterators , 0 , 4 ) ;
1360- // Check if any element wire references an element-scoped non-internal tool (requires await)
1361- const needsAsync = elemWires . some ( ( w ) => {
1362- if ( "from" in w && ! w . from . element ) {
1363- const srcKey = refTrunkKey ( w . from ) ;
1364- if (
1365- this . elementScopedTools . has ( srcKey ) &&
1366- ! this . internalToolKeys . has ( srcKey )
1367- )
1368- return true ;
1369- // Check transitive: if the source is a define container that depends on an async element-scoped tool
1370- if (
1371- this . elementScopedTools . has ( srcKey ) &&
1372- this . defineContainers . has ( srcKey )
1373- ) {
1374- return this . hasAsyncElementDeps ( srcKey ) ;
1375- }
1376- }
1377- return false ;
1378- } ) ;
1379- if ( needsAsync ) {
1380- // Collect element-scoped real tool calls and define containers that need
1381- // per-element computation. Emit them as loop-local variables.
1382- const preambleLines : string [ ] = [ ] ;
1383- this . elementLocalVars . clear ( ) ;
1384- this . collectElementPreamble ( elemWires , "_el0" , preambleLines ) ;
1385- const body = this . buildElementBody ( elemWires , arrayIterators , 0 , 4 ) ;
1386- lines . push ( ` const _result = [];` ) ;
1387- lines . push ( ` for (const _el0 of (${ arrayExpr } ?? [])) {` ) ;
1388- for ( const pl of preambleLines ) {
1389- lines . push ( ` ${ pl } ` ) ;
1390- }
1391- lines . push ( ` _result.push(${ body } );` ) ;
1392- lines . push ( ` }` ) ;
1393- lines . push ( ` return _result;` ) ;
1394- this . elementLocalVars . clear ( ) ;
1395- } else {
1396- lines . push ( ` return (${ arrayExpr } ?? []).map((_el0) => (${ body } ));` ) ;
1397- }
1395+ lines . push ( ` return (${ arrayExpr } ?? []).map((_el0) => (${ body } ));` ) ;
13981396 }
13991397 return ;
14001398 }
@@ -1478,8 +1476,29 @@ class CodegenContext {
14781476 // Only check control flow on direct element wires (not sub-array element wires)
14791477 const directShifted = shifted . filter ( ( w ) => w . to . path . length === 1 ) ;
14801478 const cf = detectControlFlow ( directShifted ) ;
1479+ // Check if any element wire generates `await` (element-scoped tools or catch fallbacks)
1480+ const needsAsync = shifted . some ( ( w ) => this . wireNeedsAwait ( w ) ) ;
14811481 let mapExpr : string ;
1482- if ( cf === "continue" ) {
1482+ if ( needsAsync ) {
1483+ // ALL async processing must use for...of inside an async IIFE
1484+ const preambleLines : string [ ] = [ ] ;
1485+ this . elementLocalVars . clear ( ) ;
1486+ this . collectElementPreamble ( shifted , "_el0" , preambleLines ) ;
1487+
1488+ const asyncBody = cf
1489+ ? this . buildElementBodyWithControlFlow (
1490+ shifted ,
1491+ arrayIterators ,
1492+ 0 ,
1493+ 8 ,
1494+ cf === "continue" ? "for-continue" : "break" ,
1495+ )
1496+ : ` _result.push(${ this . buildElementBody ( shifted , arrayIterators , 0 , 8 ) } );` ;
1497+
1498+ const preamble = preambleLines . map ( ( l ) => ` ${ l } ` ) . join ( "\n" ) ;
1499+ mapExpr = `await (async () => { const _src = ${ arrayExpr } ; if (_src == null) return null; const _result = []; for (const _el0 of _src) {\n${ preamble } \n${ asyncBody } \n } return _result; })()` ;
1500+ this . elementLocalVars . clear ( ) ;
1501+ } else if ( cf === "continue" ) {
14831502 const cfBody = this . buildElementBodyWithControlFlow (
14841503 shifted ,
14851504 arrayIterators ,
@@ -1499,40 +1518,7 @@ class CodegenContext {
14991518 mapExpr = `(() => { const _src = ${ arrayExpr } ; if (_src == null) return null; const _result = []; for (const _el0 of _src) {\n${ cfBody } \n } return _result; })()` ;
15001519 } else {
15011520 const body = this . buildElementBody ( shifted , arrayIterators , 0 , 6 ) ;
1502- // Check if any element wire references an element-scoped non-internal tool (requires await)
1503- const needsAsync = shifted . some ( ( w ) => {
1504- if ( "from" in w && ! w . from . element ) {
1505- const srcKey = refTrunkKey ( w . from ) ;
1506- if (
1507- this . elementScopedTools . has ( srcKey ) &&
1508- ! this . internalToolKeys . has ( srcKey )
1509- )
1510- return true ;
1511- if (
1512- this . elementScopedTools . has ( srcKey ) &&
1513- this . defineContainers . has ( srcKey )
1514- ) {
1515- return this . hasAsyncElementDeps ( srcKey ) ;
1516- }
1517- }
1518- return false ;
1519- } ) ;
1520- if ( needsAsync ) {
1521- const preambleLines : string [ ] = [ ] ;
1522- this . elementLocalVars . clear ( ) ;
1523- this . collectElementPreamble ( shifted , "_el0" , preambleLines ) ;
1524- const asyncBody = this . buildElementBody (
1525- shifted ,
1526- arrayIterators ,
1527- 0 ,
1528- 8 ,
1529- ) ;
1530- const preamble = preambleLines . map ( ( l ) => ` ${ l } ` ) . join ( "\n" ) ;
1531- mapExpr = `await (async () => { const _src = ${ arrayExpr } ; if (_src == null) return null; const _r = []; for (const _el0 of _src) {\n${ preamble } \n _r.push(${ asyncBody } );\n } return _r; })()` ;
1532- this . elementLocalVars . clear ( ) ;
1533- } else {
1534- mapExpr = `(${ arrayExpr } )?.map((_el0) => (${ body } )) ?? null` ;
1535- }
1521+ mapExpr = `(${ arrayExpr } )?.map((_el0) => (${ body } )) ?? null` ;
15361522 }
15371523
15381524 if ( ! tree . children . has ( arrayField ) ) {
@@ -1647,8 +1633,22 @@ class CodegenContext {
16471633 const srcExpr = this . elementWireToExpr ( sourceW , elVar ) ;
16481634 const innerElVar = `_el${ depth + 1 } ` ;
16491635 const innerCf = detectControlFlow ( shifted ) ;
1636+ // Check if inner loop needs async (element-scoped tools or catch fallbacks)
1637+ const innerNeedsAsync = shifted . some ( ( w ) => this . wireNeedsAwait ( w ) ) ;
16501638 let mapExpr : string ;
1651- if ( innerCf === "continue" ) {
1639+ if ( innerNeedsAsync ) {
1640+ // Inner async loop must use for...of inside an async IIFE
1641+ const innerBody = innerCf
1642+ ? this . buildElementBodyWithControlFlow (
1643+ shifted ,
1644+ arrayIterators ,
1645+ depth + 1 ,
1646+ indent + 4 ,
1647+ innerCf === "continue" ? "for-continue" : "break" ,
1648+ )
1649+ : `${ " " . repeat ( indent + 4 ) } _result.push(${ this . buildElementBody ( shifted , arrayIterators , depth + 1 , indent + 4 ) } );` ;
1650+ mapExpr = `await (async () => { const _src = ${ srcExpr } ; if (_src == null) return null; const _result = []; for (const ${ innerElVar } of _src) {\n${ innerBody } \n${ " " . repeat ( indent + 2 ) } } return _result; })()` ;
1651+ } else if ( innerCf === "continue" ) {
16521652 const cfBody = this . buildElementBodyWithControlFlow (
16531653 shifted ,
16541654 arrayIterators ,
@@ -1696,7 +1696,7 @@ class CodegenContext {
16961696 arrayIterators : Record < string , string > ,
16971697 depth : number ,
16981698 indent : number ,
1699- mode : "break" | "continue" ,
1699+ mode : "break" | "continue" | "for-continue" ,
17001700 ) : string {
17011701 const elVar = `_el${ depth } ` ;
17021702 const pad = " " . repeat ( indent ) ;
@@ -1740,6 +1740,14 @@ class CodegenContext {
17401740 return `${ pad } if (!${ checkExpr } ) return [];\n${ pad } return [${ this . buildElementBody ( elemWires , arrayIterators , depth , indent ) } ];` ;
17411741 }
17421742
1743+ // mode === "for-continue" — same as break but uses native 'continue' keyword
1744+ if ( mode === "for-continue" ) {
1745+ if ( isNullish ) {
1746+ return `${ pad } if (${ checkExpr } == null) continue;\n${ pad } _result.push(${ this . buildElementBody ( elemWires , arrayIterators , depth , indent ) } );` ;
1747+ }
1748+ return `${ pad } if (!${ checkExpr } ) continue;\n${ pad } _result.push(${ this . buildElementBody ( elemWires , arrayIterators , depth , indent ) } );` ;
1749+ }
1750+
17431751 // mode === "break"
17441752 if ( isNullish ) {
17451753 return `${ pad } if (${ checkExpr } == null) break;\n${ pad } _result.push(${ this . buildElementBody ( elemWires , arrayIterators , depth , indent ) } );` ;
@@ -2018,6 +2026,35 @@ class CodegenContext {
20182026 return `await __call(tools[${ JSON . stringify ( fnName ) } ], ${ inputObj } , ${ JSON . stringify ( fnName ) } )` ;
20192027 }
20202028
2029+ /**
2030+ * Check if a wire's generated expression would contain `await`.
2031+ * Used to determine whether array loops must be async (for...of) instead of .map()/.flatMap().
2032+ */
2033+ private wireNeedsAwait ( w : Wire ) : boolean {
2034+ // Element-scoped non-internal tool reference generates await __call()
2035+ if ( "from" in w && ! w . from . element ) {
2036+ const srcKey = refTrunkKey ( w . from ) ;
2037+ if (
2038+ this . elementScopedTools . has ( srcKey ) &&
2039+ ! this . internalToolKeys . has ( srcKey )
2040+ )
2041+ return true ;
2042+ if (
2043+ this . elementScopedTools . has ( srcKey ) &&
2044+ this . defineContainers . has ( srcKey )
2045+ ) {
2046+ return this . hasAsyncElementDeps ( srcKey ) ;
2047+ }
2048+ }
2049+ // Catch fallback/control without errFlag → applyFallbacks generates await (async () => ...)()
2050+ if (
2051+ ( hasCatchFallback ( w ) || hasCatchControl ( w ) ) &&
2052+ ! this . getSourceErrorFlag ( w )
2053+ )
2054+ return true ;
2055+ return false ;
2056+ }
2057+
20212058 /** Check if an element-scoped tool has transitive async dependencies. */
20222059 private hasAsyncElementDeps ( trunkKey : string ) : boolean {
20232060 const wires = this . bridge . wires . filter (
0 commit comments