@@ -29,7 +29,10 @@ export function toJSONWithLegacyAliases(
2929 ignoreSelfAliases : boolean = false ,
3030 path : PropertyKey [ ] = [ ] ,
3131) : JSONValue | undefined {
32- // Convert regular cells to opaque refs
32+ // Turn strongly typed builder values into legacy JSON structures while
33+ // preserving alias metadata for consumers that still rely on it.
34+
35+ // Convert regular cells and results from Cell.get() to opaque refs
3336 if ( canBeOpaqueRef ( value ) ) value = makeOpaqueRef ( value ) ;
3437
3538 // Verify that opaque refs are not in a parent frame
@@ -45,12 +48,13 @@ export function toJSONWithLegacyAliases(
4548 if ( external ) return external as JSONValue ;
4649 }
4750
51+ // Otherwise it's an internal reference. Extract the schema and output a link.
4852 if ( isOpaqueRef ( value ) || isShadowRef ( value ) ) {
4953 const pathToCell = paths . get ( value ) ;
5054 if ( pathToCell ) {
5155 if ( ignoreSelfAliases && deepEqual ( path , pathToCell ) ) return undefined ;
5256
53- // Get schema from exported value if available
57+ // Add schema from exported value if available
5458 const exported = isOpaqueRef ( value ) ? value . export ( ) : undefined ;
5559 return {
5660 $alias : {
@@ -67,8 +71,13 @@ export function toJSONWithLegacyAliases(
6771 } else throw new Error ( `Cell not found in paths` ) ;
6872 }
6973
74+ // If we encounter a link, it's from a nested recipe.
7075 if ( isLegacyAlias ( value ) ) {
7176 const alias = ( value as LegacyAlias ) . $alias ;
77+ // If this was a shadow ref, i.e. a closed over reference, see whether
78+ // we're now at the level that it should be resolved to the actual cell.
79+ // (i.e. we're generating the recipe from which the closed over reference
80+ // was captured)
7281 if ( isShadowRef ( alias . cell ) ) {
7382 const cell = alias . cell . shadowOf ;
7483 if ( cell . export ( ) . frame !== getTopFrame ( ) ) {
@@ -81,18 +90,30 @@ export function toJSONWithLegacyAliases(
8190 `Shadow ref alias with parent cell not found in current frame` ,
8291 ) ;
8392 }
93+ // If we're not at the top level, just emit it again. This will be
94+ // converted once the higher level recipe is being processed.
8495 return value as JSONValue ;
8596 }
8697 if ( ! paths . has ( cell ) ) throw new Error ( `Cell not found in paths` ) ;
98+ // If in top frame, it's an alias to another cell on the process cell. So
99+ // we emit the alias without the cell reference (it will be filled in
100+ // later with the process cell) and concatenate the path.
101+ const { cell : _ , ...aliasWithoutCell } = alias ;
87102 return {
88103 $alias : {
104+ // Keep any extra metadata (schema, rootSchema, etc.) that might have
105+ // been attached to the legacy alias originally.
106+ ...aliasWithoutCell ,
89107 path : [ ...paths . get ( cell ) ! , ...alias . path ] as ( string | number ) [ ] ,
90108 } ,
91109 } satisfies LegacyAlias ;
92110 } else if ( ! ( "cell" in alias ) || typeof alias . cell === "number" ) {
111+ // If we encounter an existing alias and it isn't an absolute reference
112+ // with a cell id, then increase the nesting level.
93113 return {
94114 $alias : {
95- cell : ( ( alias . cell as number ) ?? 0 ) + 1 ,
115+ ...alias , // Preserve existing metadata.
116+ cell : ( ( alias . cell as number ) ?? 0 ) + 1 , // Increase nesting level.
96117 path : alias . path as ( string | number ) [ ] ,
97118 } ,
98119 } satisfies LegacyAlias ;
@@ -101,20 +122,23 @@ export function toJSONWithLegacyAliases(
101122 }
102123 }
103124
125+ // If this is an array, process each element recursively.
104126 if ( Array . isArray ( value ) ) {
105127 return ( value as Opaque < any > ) . map ( ( v : Opaque < any > , i : number ) =>
106128 toJSONWithLegacyAliases ( v , paths , ignoreSelfAliases , [ ...path , i ] )
107129 ) ;
108130 }
109131
132+ // If this is an object or a recipe, process each key recursively.
110133 if ( isRecord ( value ) || isRecipe ( value ) ) {
134+ // If this is a recipe, call its toJSON method to get the properly
135+ // serialized version.
111136 const valueToProcess = ( isRecipe ( value ) &&
112137 typeof ( value as unknown as toJSON ) . toJSON === "function" )
113138 ? ( value as unknown as toJSON ) . toJSON ( ) as Record < string , any >
114139 : ( value as Record < string , any > ) ;
115140
116141 const result : any = { } ;
117- let hasValue = false ;
118142 for ( const key in valueToProcess as any ) {
119143 const jsonValue = toJSONWithLegacyAliases (
120144 valueToProcess [ key ] ,
@@ -124,13 +148,13 @@ export function toJSONWithLegacyAliases(
124148 ) ;
125149 if ( jsonValue !== undefined ) {
126150 result [ key ] = jsonValue ;
127- hasValue = true ;
128151 }
129152 }
130153
154+ // Retain the original recipe reference for downstream processing.
131155 if ( isRecipe ( value ) ) result [ unsafe_originalRecipe ] = value ;
132156
133- return hasValue || Object . keys ( result ) . length === 0 ? result : undefined ;
157+ return result ;
134158 }
135159
136160 return value ;
0 commit comments