Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 68 additions & 7 deletions src/schema/getSignatureSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {getSchema, mergeSchemas, Schema} from "../util/schema.util"
* Includes the parameter's schema definition and any parameter dependencies that block it.
*/
export interface NodeSchema {
nodeId: NodeFunction["id"]
/**
* The schema definition for this node parameter. Produced by merging the
* function-declared parameter schema with the node's concrete value schema:
Expand All @@ -21,6 +20,20 @@ export interface NodeSchema {
blockedBy?: number[]
}

/**
* Represents the full schema information for a function signature.
* Wraps the per-parameter schemas together with the schema of the signature's
* return type.
*/
export interface SignatureSchema {
/** The analyzed node's ID, or undefined when the flow signature itself is analyzed */
nodeId: NodeFunction["id"]
/** Schema for each parameter of the signature */
parameters: NodeSchema[]
/** Schema describing the signature's return type */
return: Schema
}

/**
* Represents a parameter dependency relationship.
* Indicates which parameters depend on type parameters defined in other parameters.
Expand Down Expand Up @@ -57,7 +70,7 @@ export const getSignatureSchema = (
dataTypes: DataType[],
functions: FunctionDefinition[],
nodeId?: NodeFunction["id"],
): NodeSchema[] => {
): SignatureSchema => {
// Generate TypeScript source code from the flow definition
const sourceCode = generateFlowSourceCode(flow, functions, dataTypes)

Expand Down Expand Up @@ -108,8 +121,7 @@ export const getSignatureSchema = (
)

// Generate schema for each parameter
return generateNodeSchemas(
nodeId,
const parameters = generateNodeSchemas(
checker,
node!,
mergedParameterTypes,
Expand All @@ -119,6 +131,58 @@ export const getSignatureSchema = (
nodeId ? functions : [],
valueProvidedByIndex,
)

// Resolve the signature's return type and build its schema. The return type
// describes the value the function produces, so it carries no input
// suggestions.
const returnType = extractReturnType(checker, node, funktion)
const returnSchema: Schema = returnType
? getSchema(
checker,
node,
returnType,
Array.from(declaredFunctionsMap.values()),
functions,
false,
)
: {input: "generic"}

return {nodeId, parameters, return: returnSchema}
}

/**
* Extracts the return type of the signature being analyzed.
*
* The node's call expression is preferred because its resolved signature
* substitutes concrete type arguments (e.g. `REST_ADAPTER_INPUT<T>` becomes the
* instantiated type based on the supplied arguments). When no call expression is
* available, the function declaration's own signature is used as a fallback.
*
* @param checker - The TypeScript type checker
* @param node - The variable declaration containing the call expression
* @param funktion - The function declaration for the signature
* @returns The resolved return type, or undefined if it cannot be determined
*/
const extractReturnType = (
checker: ts.TypeChecker,
node: ts.VariableDeclaration | undefined,
funktion: ts.FunctionDeclaration | undefined,
): Type | undefined => {
if (node?.initializer && ts.isCallExpression(node.initializer)) {
const signature = checker.getResolvedSignature(node.initializer)
if (signature) {
return checker.getReturnTypeOfSignature(signature)
}
}

if (funktion) {
const signature = checker.getSignatureFromDeclaration(funktion)
if (signature) {
return checker.getReturnTypeOfSignature(signature)
}
}

return undefined
}

/**
Expand Down Expand Up @@ -301,7 +365,6 @@ const getParameterDependencies = (
* Generates node schemas for all parameters.
* Creates schema objects for each parameter with their dependencies.
*
* @param nodeId -
* @param checker - The TypeScript type checker
* @param node - The node's variable declaration
* @param nodeParameterTypes - Merged parameter types to use for schema generation
Expand All @@ -313,7 +376,6 @@ const getParameterDependencies = (
* @returns Array of NodeSchema objects
*/
const generateNodeSchemas = (
nodeId: NodeFunction["id"],
checker: ts.TypeChecker,
node: ts.VariableDeclaration,
nodeParameterTypes: Type[] | undefined,
Expand Down Expand Up @@ -359,7 +421,6 @@ const generateNodeSchemas = (
: undefined

return {
nodeId: nodeId,
schema: mergeSchemas(
functionSchema,
nodeSchema,
Expand Down
29 changes: 22 additions & 7 deletions src/util/schema.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ import {getSubFlows} from "./subflows.util";
export interface Input {
/** The type of input (string representation) */
input?: string;
/**
* The underlying TypeScript type rendered as a string
* (e.g. "NUMBER", "LIST<TEXT>", "string | undefined"), as produced by the
* type checker for the type this schema node was generated from.
*/
type?: string;
/** Array of suggested values (functions, references, or literals) */
suggestions?: (NodeFunction | ReferenceValue | LiteralValue | SubFlowValue)[];
}
Expand Down Expand Up @@ -148,6 +154,10 @@ export const getSchema = (
}
}

// The raw TypeScript type as a string, carried on every schema node so the
// consumer knows the concrete type each input was derived from.
const type = checker.typeToString(parameterType);

// Suggestions are filtered by what the surrounding function accepts, not by
// the narrower type a current value happens to narrow the node-side to.
// Example: `<T>(value: T)` with a current boolean literal must still surface
Expand Down Expand Up @@ -188,33 +198,33 @@ export const getSchema = (
)
if (nonNullish.length === 1) {
const baseSchema = getSchema(checker, node, nonNullish[0], functionDeclarations, functions, false, undefined, visited, recursionCache)
return {...baseSchema, ...combinedSuggestions}
return {...baseSchema, type, ...combinedSuggestions}
}
}

// Boolean is internally represented by TypeScript as the union `true | false`,
// so it must be detected before the primitive-literal-union check below; otherwise
// `boolean` (and `true | false`) would incorrectly surface as a select.
if (isBoolean(parameterType)) {
return {input: "boolean", ...combinedSuggestions};
return {input: "boolean", type, ...combinedSuggestions};
}

// Check primitive literal union first (e.g., "a" | "b" | "c") or a single
// string/number literal (e.g., "GET"). A bare literal has only one allowed
// value, so it should still surface as a select rather than a free-form text/number input.
if (isPrimitiveLiteralUnion(parameterType) || isStringOrNumberLiteral(parameterType)) {
return {input: "select", ...combinedSuggestions};
return {input: "select", type, ...combinedSuggestions};
}
if (isNumber(parameterType)) {
return {input: "number", ...combinedSuggestions};
return {input: "number", type, ...combinedSuggestions};
}
if (isString(parameterType)) {
return {input: "text", ...combinedSuggestions};
return {input: "text", type, ...combinedSuggestions};
}

// Check if type has call signatures (is callable/sub-flow)
if (isSubFlow(parameterType)) {
return {input: "sub-flow", ...combinedSuggestions};
return {input: "sub-flow", type, ...combinedSuggestions};
}

// Handle array and tuple types
Expand All @@ -233,6 +243,7 @@ export const getSchema = (

return {
input: "list",
type,
items: itemSchemas,
...combinedSuggestions,
};
Expand All @@ -252,7 +263,7 @@ export const getSchema = (
(visited.size >= MAX_SCHEMA_DEPTH &&
isRecursiveType(parameterType, checker, recursionCache))
) {
return {input: "data", ...combinedSuggestions};
return {input: "data", type, ...combinedSuggestions};
}
visited.add(parameterType);

Expand Down Expand Up @@ -304,6 +315,7 @@ export const getSchema = (

return {
input: "data",
type,
properties,
required,
...combinedSuggestions,
Expand All @@ -314,6 +326,7 @@ export const getSchema = (
// suggestions (e.g. references in scope) so the UI is never silently empty.
return {
input: "generic",
type,
...combinedSuggestions,
};
};
Expand Down Expand Up @@ -435,6 +448,7 @@ const liftGenericIfValued = (schema: Schema, valueProvided: boolean): Schema =>
if (!valueProvided || schema.input !== "generic") return schema;
return {
input: "data",
...(schema.type ? {type: schema.type} : {}),
properties: {},
required: [],
...(schema.suggestions ? {suggestions: schema.suggestions} : {}),
Expand Down Expand Up @@ -464,6 +478,7 @@ const demoteSelect = (schema: Schema): Schema => {
: "text";
return {
input: target,
...(schema.type ? {type: schema.type} : {}),
...(suggestions.length > 0 ? {suggestions} : {}),
};
};
Expand Down
Loading