@@ -20,7 +20,13 @@ import {
2020 unsafe_originalRecipe ,
2121} from "./types.ts" ;
2222import { getTopFrame } from "./recipe.ts" ;
23- import { type CellLink , isCell , isCellLink , isDoc } from "@commontools/runner" ;
23+ import {
24+ type CellLink ,
25+ ContextualFlowControl ,
26+ isCell ,
27+ isCellLink ,
28+ isDoc ,
29+ } from "@commontools/runner" ;
2430
2531/**
2632 * Traverse a value, _not_ entering cells
@@ -347,6 +353,8 @@ export function recipeToJSON(recipe: Recipe) {
347353}
348354
349355export function connectInputAndOutputs ( node : NodeRef ) {
356+ const cfc = new ContextualFlowControl ( ) ;
357+
350358 function connect ( value : any ) : any {
351359 if ( canBeOpaqueRef ( value ) ) value = makeOpaqueRef ( value ) ;
352360 if ( isOpaqueRef ( value ) ) {
@@ -361,4 +369,67 @@ export function connectInputAndOutputs(node: NodeRef) {
361369
362370 node . inputs = traverseValue ( node . inputs , connect ) ;
363371 node . outputs = traverseValue ( node . outputs , connect ) ;
372+
373+ // We will also apply ifc tags from inputs to outputs
374+ applyInputIfcToOutput ( node . inputs , node . outputs ) ;
375+ }
376+
377+ export function applyArgumentIfcToResult (
378+ argumentSchema ?: JSONSchema ,
379+ resultSchema ?: JSONSchema ,
380+ ) : JSONSchema | undefined {
381+ if ( argumentSchema !== undefined ) {
382+ const cfc = new ContextualFlowControl ( ) ;
383+ const joined = cfc . joinSchema ( new Set ( ) , argumentSchema ) ;
384+ return ( joined . size !== 0 )
385+ ? cfc . schemaWithLub ( resultSchema ?? { } , cfc . lub ( joined ) )
386+ : resultSchema ;
387+ }
388+ return resultSchema ;
389+ }
390+
391+ // If our inputs had any ifc tags, carry them through to our outputs
392+ export function applyInputIfcToOutput < T , R > (
393+ inputs : Opaque < T > ,
394+ outputs : Opaque < R > ,
395+ ) {
396+ const collectedClassifications = new Set < string > ( ) ;
397+ const cfc = new ContextualFlowControl ( ) ;
398+ traverseValue ( inputs , ( item ) => {
399+ if ( isOpaqueRef ( item ) ) {
400+ const { schema : inputSchema } = ( item as OpaqueRef < T > ) . export ( ) ;
401+ if ( inputSchema !== undefined ) {
402+ cfc . joinSchema ( collectedClassifications , inputSchema ) ;
403+ }
404+ }
405+ } ) ;
406+ if ( collectedClassifications . size !== 0 ) {
407+ attachCfcToOutputs ( outputs , cfc , cfc . lub ( collectedClassifications ) ) ;
408+ }
409+ }
410+
411+ // Attach ifc classification to OpaqueRef objects reachable
412+ // from the outputs without descending into OpaqueRef objects
413+ // TODO(@ubik2) Investigate: can we have cycles here?
414+ function attachCfcToOutputs < T , R > (
415+ outputs : Opaque < R > ,
416+ cfc : ContextualFlowControl ,
417+ lubClassification : string ,
418+ ) {
419+ if ( isOpaqueRef ( outputs ) ) {
420+ const exported = ( outputs as OpaqueRef < T > ) . export ( ) ;
421+ const outputSchema = exported . schema ?? { } ;
422+ // we may have fields in the output schema, so incorporate those
423+ const joined = cfc . joinSchema ( new Set ( [ lubClassification ] ) , outputSchema ) ;
424+ const ifc = ( outputSchema . ifc !== undefined ) ? { ...outputSchema . ifc } : { } ;
425+ ifc . classification = [ cfc . lub ( joined ) ] ;
426+ const cfcSchema : JSONSchema = { ...outputSchema , ifc } ;
427+ ( outputs as OpaqueRef < T > ) . setSchema ( cfcSchema ) ;
428+ return ;
429+ } else if ( typeof outputs === "object" && outputs !== null ) {
430+ // Descend into objects and arrays
431+ for ( const [ key , value ] of Object . entries ( outputs ) ) {
432+ attachCfcToOutputs ( value , cfc , lubClassification ) ;
433+ }
434+ }
364435}
0 commit comments