11import { DataType , Flow , FunctionDefinition , NodeFunction } from "@code0-tech/sagittarius-graphql-types"
22import { createCompilerHost , generateFlowSourceCode , sanitizeId } from "../utils"
33import ts , { Type } from "typescript"
4- import { getSchema , Schema } from "../util/schema.util"
4+ import { getSchema , mergeSchemas , Schema } from "../util/schema.util"
55
66/**
77 * Represents the schema information for a node parameter.
88 * Includes the parameter's schema definition and any parameter dependencies that block it.
99 */
1010export interface NodeSchema {
1111 nodeId : NodeFunction [ "id" ]
12- /** The schema definition for this node parameter */
12+ /**
13+ * The schema definition for this node parameter. Produced by merging the
14+ * function-declared parameter schema with the node's concrete value schema:
15+ * the function schema drives the structural shape, the node schema contributes
16+ * additional suggestions, and a generic function parameter falls back to the
17+ * node's concrete shape (never as a select).
18+ */
1319 schema : Schema
14- /** The schema definition for the function parameter */
15- functionSchema : Schema
1620 /** Array of parameter indices that must be resolved before this parameter */
1721 blockedBy ?: number [ ]
1822}
@@ -95,6 +99,14 @@ export const getSignatureSchema = (
9599 // Identify parameter dependencies based on type parameters
96100 const funktionDependencies = getParameterDependencies ( funktion ! , nodeParameterTypes )
97101
102+ // Track which parameter slots actually carry a user-supplied value. The merge
103+ // uses this as a last-resort signal: if the function- and node-side schemas
104+ // both came out generic but the user did set something, the lift falls back
105+ // to `data` so the UI has an open object to render against.
106+ const valueProvidedByIndex = ( targetNode ?. parameters ?. nodes ?? [ ] ) . map (
107+ ( p ) => p ?. value != null
108+ )
109+
98110 // Generate schema for each parameter
99111 return generateNodeSchemas (
100112 nodeId ,
@@ -105,6 +117,7 @@ export const getSignatureSchema = (
105117 funktionDependencies ,
106118 nodeId ? declaredFunctionsMap : new Map ( ) ,
107119 nodeId ? functions : [ ] ,
120+ valueProvidedByIndex ,
108121 )
109122}
110123
@@ -292,9 +305,11 @@ const getParameterDependencies = (
292305 * @param checker - The TypeScript type checker
293306 * @param node - The node's variable declaration
294307 * @param nodeParameterTypes - Merged parameter types to use for schema generation
308+ * @param functionParameterTypes
295309 * @param funktionDependencies - Parameter dependencies to link with each parameter
296310 * @param declaredFunctionsMap - Map of available functions for schema context
297311 * @param functions - Array of function definitions
312+ * @param valueProvidedByIndex
298313 * @returns Array of NodeSchema objects
299314 */
300315const generateNodeSchemas = (
@@ -306,31 +321,67 @@ const generateNodeSchemas = (
306321 funktionDependencies : ParameterDependency [ ] ,
307322 declaredFunctionsMap : Map < string , ts . FunctionDeclaration > ,
308323 functions : FunctionDefinition [ ] ,
324+ valueProvidedByIndex : boolean [ ] ,
309325) : NodeSchema [ ] => {
310326 if ( ! nodeParameterTypes ) {
311327 return [ ]
312328 }
313329
314- return nodeParameterTypes . map ( ( parameterType , index ) => ( {
315- nodeId : nodeId ,
316- schema : getSchema (
330+ return nodeParameterTypes . map ( ( parameterType , index ) => {
331+ const functionParameterType = functionParameterTypes ?. [ index ]
332+ // Suggestions are scoped by what the *function* parameter accepts (e.g.
333+ // `T` widens to `any`, so anything in scope is a valid candidate), even
334+ // when the node value has narrowed the actual parameter type — otherwise
335+ // setting a boolean literal in a generic slot would silently hide all
336+ // other suggestions.
337+ const suggestionType = functionParameterType
338+ ? widenForSuggestions ( checker , functionParameterType )
339+ : undefined
340+
341+ const nodeSchema = getSchema (
317342 checker ,
318343 node ,
319344 parameterType ,
320345 Array . from ( declaredFunctionsMap . values ( ) ) ,
321- functions
322- ) ,
323- functionSchema : getSchema (
324- checker ,
325- node ,
326- functionParameterTypes ?. [ index ] ! ,
327- Array . from ( declaredFunctionsMap . values ( ) ) ,
328346 functions ,
329- false
330- ) ,
331- blockedBy : funktionDependencies
332- . filter ( ( dep ) => dep . parameterIndex === index )
333- . map ( ( dep ) => dep . dependsOnIndex ) ,
334- } ) )
347+ true ,
348+ suggestionType ,
349+ )
350+ const functionSchema = functionParameterType
351+ ? getSchema (
352+ checker ,
353+ node ,
354+ functionParameterType ,
355+ Array . from ( declaredFunctionsMap . values ( ) ) ,
356+ functions ,
357+ false
358+ )
359+ : undefined
360+
361+ return {
362+ nodeId : nodeId ,
363+ schema : mergeSchemas (
364+ functionSchema ,
365+ nodeSchema ,
366+ valueProvidedByIndex [ index ] ?? false ,
367+ ) ,
368+ blockedBy : funktionDependencies
369+ . filter ( ( dep ) => dep . parameterIndex === index )
370+ . map ( ( dep ) => dep . dependsOnIndex ) ,
371+ }
372+ } )
373+ }
374+
375+ // Widen a function parameter type so that suggestion collection asks "what could
376+ // the function accept here", not "what does the current value narrow this to".
377+ // An unconstrained type parameter accepts anything → `any`. A constrained type
378+ // parameter is replaced by its constraint. Everything else is used as-is.
379+ const widenForSuggestions = ( checker : ts . TypeChecker , type : ts . Type ) : ts . Type => {
380+ if ( ( type . flags & ts . TypeFlags . TypeParameter ) === 0 ) return type
381+ const decl = type . symbol ?. declarations ?. [ 0 ]
382+ if ( decl && ts . isTypeParameterDeclaration ( decl ) && decl . constraint ) {
383+ return checker . getTypeFromTypeNode ( decl . constraint )
384+ }
385+ return checker . getAnyType ( )
335386}
336387
0 commit comments