88 isModule ,
99 isRecipe ,
1010 isAlias ,
11- isOpaqueRef ,
1211 isStreamAlias ,
1312 popFrame ,
1413 recipeFromFrame ,
@@ -28,14 +27,15 @@ import {
2827import { Action , schedule , addEventHandler } from "./scheduler.js" ;
2928import {
3029 extractDefaultValues ,
31- mapBindingsToCell ,
30+ unwrapOneLevelAndBindtoCell ,
3231 findAllAliasedCells ,
3332 followAliases ,
3433 mergeObjects ,
3534 sendValueToBinding ,
3635 staticDataToNestedCells ,
3736 deepCopy ,
3837 unsafe_noteParentOnRecipes ,
38+ containsOpaqueRef ,
3939} from "./utils.js" ;
4040import { getModuleByRef } from "./module.js" ;
4141import { type AddCancel , type Cancel , useCancelGroup } from "./cancel.js" ;
@@ -180,7 +180,9 @@ export function run<T, R = any>(
180180 } ) ;
181181
182182 // Send "query" to results to the result cell
183- resultCell . send ( mapBindingsToCell < R > ( recipe . result as R , processCell ) ) ;
183+ resultCell . send (
184+ unwrapOneLevelAndBindtoCell < R > ( recipe . result as R , processCell ) ,
185+ ) ;
184186
185187 // [unsafe closures:] For recipes from closures, add a materialize factory
186188 if ( recipe [ unsafe_originalRecipe ] )
@@ -308,7 +310,7 @@ function instantiateJavaScriptNode(
308310 addCancel : AddCancel ,
309311 recipe : Recipe ,
310312) {
311- const inputs = mapBindingsToCell (
313+ const inputs = unwrapOneLevelAndBindtoCell (
312314 inputBindings as { [ key : string ] : any } ,
313315 processCell ,
314316 ) ;
@@ -318,7 +320,7 @@ function instantiateJavaScriptNode(
318320 // written.
319321 const reads = findAllAliasedCells ( inputs , processCell ) ;
320322
321- const outputs = mapBindingsToCell ( outputBindings , processCell ) ;
323+ const outputs = unwrapOneLevelAndBindtoCell ( outputBindings , processCell ) ;
322324 const writes = findAllAliasedCells ( outputs , processCell ) ;
323325
324326 let fn = (
@@ -383,21 +385,14 @@ function instantiateJavaScriptNode(
383385 const result = fn ( inputsCell . getAsQueryResult ( [ ] ) ) ;
384386
385387 // If handler returns a graph created by builder, run it
386- // TODO: Handle case where the result is a structure with possibly
387- // multiple such nodes
388- if ( isOpaqueRef ( result ) ) {
389- const resultNode = result ;
390-
391- // Recipe that assigns the result of the returned node to "result"
388+ if ( containsOpaqueRef ( result ) ) {
392389 const resultRecipe = recipeFromFrame (
393390 "event handler result" ,
394391 undefined ,
395- ( ) => ( {
396- result : resultNode ,
397- } ) ,
392+ ( ) => result ,
398393 ) ;
399394
400- const resultCell = run ( resultRecipe , { } ) ;
395+ const resultCell = run ( resultRecipe ) ;
401396 addCancel ( cancels . get ( resultCell ) ) ;
402397 }
403398
@@ -411,6 +406,8 @@ function instantiateJavaScriptNode(
411406 const inputsCell = cell ( inputs ) ;
412407 inputsCell . freeze ( ) ; // Freezes the bindings, not aliased cells.
413408
409+ let resultCell : CellImpl < any > | undefined ;
410+
414411 const action : Action = ( log : ReactivityLog ) => {
415412 const inputsProxy = inputsCell . getAsQueryResult ( [ ] , log ) ;
416413
@@ -420,9 +417,28 @@ function instantiateJavaScriptNode(
420417 processCell . getAsQueryResult ( path , log ) ,
421418 } satisfies UnsafeBinding ) ;
422419 const result = fn ( inputsProxy ) ;
423- popFrame ( frame ) ;
424420
425- sendValueToBinding ( processCell , outputs , result , log ) ;
421+ if ( containsOpaqueRef ( result ) ) {
422+ const resultRecipe = recipeFromFrame (
423+ "action result" ,
424+ undefined ,
425+ ( ) => result ,
426+ ) ;
427+
428+ resultCell = run ( resultRecipe , undefined , resultCell ) ;
429+ addCancel ( cancels . get ( resultCell ) ) ;
430+
431+ sendValueToBinding (
432+ processCell ,
433+ outputs ,
434+ { cell : resultCell , path : [ ] } ,
435+ log ,
436+ ) ;
437+ } else {
438+ sendValueToBinding ( processCell , outputs , result , log ) ;
439+ }
440+
441+ popFrame ( frame ) ;
426442 } ;
427443
428444 addCancel ( schedule ( action , { reads, writes } satisfies ReactivityLog ) ) ;
@@ -445,8 +461,14 @@ function instantiateRawNode(
445461 // Built-ins can define their own scheduling logic, so they'll
446462 // implement parts of the above themselves.
447463
448- const mappedInputBindings = mapBindingsToCell ( inputBindings , processCell ) ;
449- const mappedOutputBindings = mapBindingsToCell ( outputBindings , processCell ) ;
464+ const mappedInputBindings = unwrapOneLevelAndBindtoCell (
465+ inputBindings ,
466+ processCell ,
467+ ) ;
468+ const mappedOutputBindings = unwrapOneLevelAndBindtoCell (
469+ outputBindings ,
470+ processCell ,
471+ ) ;
450472
451473 // For `map` and future other node types that take closures, we need to
452474 // note the parent recipe on the closure recipes.
@@ -474,11 +496,11 @@ function instantiatePassthroughNode(
474496 processCell : CellImpl < any > ,
475497 addCancel : AddCancel ,
476498) {
477- const inputs = mapBindingsToCell ( inputBindings , processCell ) ;
499+ const inputs = unwrapOneLevelAndBindtoCell ( inputBindings , processCell ) ;
478500 const inputsCell = cell ( inputs ) ;
479501 const reads = findAllAliasedCells ( inputs , processCell ) ;
480502
481- const outputs = mapBindingsToCell ( outputBindings , processCell ) ;
503+ const outputs = unwrapOneLevelAndBindtoCell ( outputBindings , processCell ) ;
482504 const writes = findAllAliasedCells ( outputs , processCell ) ;
483505
484506 const action : Action = ( log : ReactivityLog ) => {
@@ -496,12 +518,12 @@ function instantiateIsolatedNode(
496518 processCell : CellImpl < any > ,
497519 addCancel : AddCancel ,
498520) {
499- const inputs = mapBindingsToCell ( inputBindings , processCell ) ;
521+ const inputs = unwrapOneLevelAndBindtoCell ( inputBindings , processCell ) ;
500522 const reads = findAllAliasedCells ( inputs , processCell ) ;
501523 const inputsCell = cell ( inputs ) ;
502524 inputsCell . freeze ( ) ;
503525
504- const outputs = mapBindingsToCell ( outputBindings , processCell ) ;
526+ const outputs = unwrapOneLevelAndBindtoCell ( outputBindings , processCell ) ;
505527 const writes = findAllAliasedCells ( outputs , processCell ) ;
506528
507529 if ( ! isJavaScriptModuleDefinition ( module . implementation ) )
@@ -561,14 +583,18 @@ function instantiateRecipeNode(
561583 addCancel : AddCancel ,
562584) {
563585 if ( ! isRecipe ( module . implementation ) ) throw new Error ( `Invalid recipe` ) ;
564- const inputs = mapBindingsToCell ( inputBindings , processCell ) ;
586+ const recipe = unwrapOneLevelAndBindtoCell (
587+ module . implementation ,
588+ processCell ,
589+ ) ;
590+ const inputs = unwrapOneLevelAndBindtoCell ( inputBindings , processCell ) ;
565591 const resultCell = cell ( undefined , {
566592 recipe : module . implementation ,
567593 parent : processCell ,
568594 inputBindings,
569595 outputBindings,
570596 } ) ;
571- run ( module . implementation , inputs , resultCell ) ;
597+ run ( recipe , inputs , resultCell ) ;
572598 sendValueToBinding ( processCell , outputBindings , {
573599 cell : resultCell ,
574600 path : [ ] ,
0 commit comments