Skip to content

Commit 496b768

Browse files
authored
Merge pull request #139 from code0-tech/feat/#117
Adding return schema to getSignatureSchema
2 parents 04b3b0d + 865f47f commit 496b768

3 files changed

Lines changed: 247 additions & 26 deletions

File tree

src/schema/getSignatureSchema.ts

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {getSchema, mergeSchemas, Schema} from "../util/schema.util"
88
* Includes the parameter's schema definition and any parameter dependencies that block it.
99
*/
1010
export interface NodeSchema {
11-
nodeId: NodeFunction["id"]
1211
/**
1312
* The schema definition for this node parameter. Produced by merging the
1413
* function-declared parameter schema with the node's concrete value schema:
@@ -21,6 +20,20 @@ export interface NodeSchema {
2120
blockedBy?: number[]
2221
}
2322

23+
/**
24+
* Represents the full schema information for a function signature.
25+
* Wraps the per-parameter schemas together with the schema of the signature's
26+
* return type.
27+
*/
28+
export interface SignatureSchema {
29+
/** The analyzed node's ID, or undefined when the flow signature itself is analyzed */
30+
nodeId: NodeFunction["id"]
31+
/** Schema for each parameter of the signature */
32+
parameters: NodeSchema[]
33+
/** Schema describing the signature's return type */
34+
return: Schema
35+
}
36+
2437
/**
2538
* Represents a parameter dependency relationship.
2639
* Indicates which parameters depend on type parameters defined in other parameters.
@@ -57,7 +70,7 @@ export const getSignatureSchema = (
5770
dataTypes: DataType[],
5871
functions: FunctionDefinition[],
5972
nodeId?: NodeFunction["id"],
60-
): NodeSchema[] => {
73+
): SignatureSchema => {
6174
// Generate TypeScript source code from the flow definition
6275
const sourceCode = generateFlowSourceCode(flow, functions, dataTypes)
6376

@@ -108,8 +121,7 @@ export const getSignatureSchema = (
108121
)
109122

110123
// Generate schema for each parameter
111-
return generateNodeSchemas(
112-
nodeId,
124+
const parameters = generateNodeSchemas(
113125
checker,
114126
node!,
115127
mergedParameterTypes,
@@ -119,6 +131,58 @@ export const getSignatureSchema = (
119131
nodeId ? functions : [],
120132
valueProvidedByIndex,
121133
)
134+
135+
// Resolve the signature's return type and build its schema. The return type
136+
// describes the value the function produces, so it carries no input
137+
// suggestions.
138+
const returnType = extractReturnType(checker, node, funktion)
139+
const returnSchema: Schema = returnType
140+
? getSchema(
141+
checker,
142+
node,
143+
returnType,
144+
Array.from(declaredFunctionsMap.values()),
145+
functions,
146+
false,
147+
)
148+
: {input: "generic"}
149+
150+
return {nodeId, parameters, return: returnSchema}
151+
}
152+
153+
/**
154+
* Extracts the return type of the signature being analyzed.
155+
*
156+
* The node's call expression is preferred because its resolved signature
157+
* substitutes concrete type arguments (e.g. `REST_ADAPTER_INPUT<T>` becomes the
158+
* instantiated type based on the supplied arguments). When no call expression is
159+
* available, the function declaration's own signature is used as a fallback.
160+
*
161+
* @param checker - The TypeScript type checker
162+
* @param node - The variable declaration containing the call expression
163+
* @param funktion - The function declaration for the signature
164+
* @returns The resolved return type, or undefined if it cannot be determined
165+
*/
166+
const extractReturnType = (
167+
checker: ts.TypeChecker,
168+
node: ts.VariableDeclaration | undefined,
169+
funktion: ts.FunctionDeclaration | undefined,
170+
): Type | undefined => {
171+
if (node?.initializer && ts.isCallExpression(node.initializer)) {
172+
const signature = checker.getResolvedSignature(node.initializer)
173+
if (signature) {
174+
return checker.getReturnTypeOfSignature(signature)
175+
}
176+
}
177+
178+
if (funktion) {
179+
const signature = checker.getSignatureFromDeclaration(funktion)
180+
if (signature) {
181+
return checker.getReturnTypeOfSignature(signature)
182+
}
183+
}
184+
185+
return undefined
122186
}
123187

124188
/**
@@ -301,7 +365,6 @@ const getParameterDependencies = (
301365
* Generates node schemas for all parameters.
302366
* Creates schema objects for each parameter with their dependencies.
303367
*
304-
* @param nodeId -
305368
* @param checker - The TypeScript type checker
306369
* @param node - The node's variable declaration
307370
* @param nodeParameterTypes - Merged parameter types to use for schema generation
@@ -313,7 +376,6 @@ const getParameterDependencies = (
313376
* @returns Array of NodeSchema objects
314377
*/
315378
const generateNodeSchemas = (
316-
nodeId: NodeFunction["id"],
317379
checker: ts.TypeChecker,
318380
node: ts.VariableDeclaration,
319381
nodeParameterTypes: Type[] | undefined,
@@ -359,7 +421,6 @@ const generateNodeSchemas = (
359421
: undefined
360422

361423
return {
362-
nodeId: nodeId,
363424
schema: mergeSchemas(
364425
functionSchema,
365426
nodeSchema,

src/util/schema.util.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ import {getSubFlows} from "./subflows.util";
1919
export interface Input {
2020
/** The type of input (string representation) */
2121
input?: string;
22+
/**
23+
* The underlying TypeScript type rendered as a string
24+
* (e.g. "NUMBER", "LIST<TEXT>", "string | undefined"), as produced by the
25+
* type checker for the type this schema node was generated from.
26+
*/
27+
type?: string;
2228
/** Array of suggested values (functions, references, or literals) */
2329
suggestions?: (NodeFunction | ReferenceValue | LiteralValue | SubFlowValue)[];
2430
}
@@ -148,6 +154,10 @@ export const getSchema = (
148154
}
149155
}
150156

157+
// The raw TypeScript type as a string, carried on every schema node so the
158+
// consumer knows the concrete type each input was derived from.
159+
const type = checker.typeToString(parameterType);
160+
151161
// Suggestions are filtered by what the surrounding function accepts, not by
152162
// the narrower type a current value happens to narrow the node-side to.
153163
// Example: `<T>(value: T)` with a current boolean literal must still surface
@@ -188,33 +198,33 @@ export const getSchema = (
188198
)
189199
if (nonNullish.length === 1) {
190200
const baseSchema = getSchema(checker, node, nonNullish[0], functionDeclarations, functions, false, undefined, visited, recursionCache)
191-
return {...baseSchema, ...combinedSuggestions}
201+
return {...baseSchema, type, ...combinedSuggestions}
192202
}
193203
}
194204

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

202212
// Check primitive literal union first (e.g., "a" | "b" | "c") or a single
203213
// string/number literal (e.g., "GET"). A bare literal has only one allowed
204214
// value, so it should still surface as a select rather than a free-form text/number input.
205215
if (isPrimitiveLiteralUnion(parameterType) || isStringOrNumberLiteral(parameterType)) {
206-
return {input: "select", ...combinedSuggestions};
216+
return {input: "select", type, ...combinedSuggestions};
207217
}
208218
if (isNumber(parameterType)) {
209-
return {input: "number", ...combinedSuggestions};
219+
return {input: "number", type, ...combinedSuggestions};
210220
}
211221
if (isString(parameterType)) {
212-
return {input: "text", ...combinedSuggestions};
222+
return {input: "text", type, ...combinedSuggestions};
213223
}
214224

215225
// Check if type has call signatures (is callable/sub-flow)
216226
if (isSubFlow(parameterType)) {
217-
return {input: "sub-flow", ...combinedSuggestions};
227+
return {input: "sub-flow", type, ...combinedSuggestions};
218228
}
219229

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

234244
return {
235245
input: "list",
246+
type,
236247
items: itemSchemas,
237248
...combinedSuggestions,
238249
};
@@ -252,7 +263,7 @@ export const getSchema = (
252263
(visited.size >= MAX_SCHEMA_DEPTH &&
253264
isRecursiveType(parameterType, checker, recursionCache))
254265
) {
255-
return {input: "data", ...combinedSuggestions};
266+
return {input: "data", type, ...combinedSuggestions};
256267
}
257268
visited.add(parameterType);
258269

@@ -304,6 +315,7 @@ export const getSchema = (
304315

305316
return {
306317
input: "data",
318+
type,
307319
properties,
308320
required,
309321
...combinedSuggestions,
@@ -314,6 +326,7 @@ export const getSchema = (
314326
// suggestions (e.g. references in scope) so the UI is never silently empty.
315327
return {
316328
input: "generic",
329+
type,
317330
...combinedSuggestions,
318331
};
319332
};
@@ -435,6 +448,7 @@ const liftGenericIfValued = (schema: Schema, valueProvided: boolean): Schema =>
435448
if (!valueProvided || schema.input !== "generic") return schema;
436449
return {
437450
input: "data",
451+
...(schema.type ? {type: schema.type} : {}),
438452
properties: {},
439453
required: [],
440454
...(schema.suggestions ? {suggestions: schema.suggestions} : {}),
@@ -464,6 +478,7 @@ const demoteSelect = (schema: Schema): Schema => {
464478
: "text";
465479
return {
466480
input: target,
481+
...(schema.type ? {type: schema.type} : {}),
467482
...(suggestions.length > 0 ? {suggestions} : {}),
468483
};
469484
};

0 commit comments

Comments
 (0)