Skip to content

Commit 6f7adda

Browse files
committed
feat: add getNodes utility for filtering and transforming TypeScript function declarations into compatible node functions
1 parent cad50a6 commit 6f7adda

1 file changed

Lines changed: 234 additions & 0 deletions

File tree

src/util/nodes.util.ts

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
import ts from "typescript";
2+
import {FunctionDefinition, NodeFunction} from "@code0-tech/sagittarius-graphql-types";
3+
import {isSubFlow} from "./schema.util";
4+
5+
/**
6+
* Filters and transforms function declarations into a collection of compatible node functions.
7+
*
8+
* This utility function analyzes TypeScript function declarations and matches them against
9+
* a target parameter type. It returns node functions for all functions whose return types
10+
* are assignable to the specified parameter type. Each node function is enriched with
11+
* metadata including function definitions and parameter information.
12+
*
13+
* @param {ts.TypeChecker} checker - The TypeScript type checker instance used to analyze
14+
* type information and verify type compatibility
15+
* @param {ts.FunctionDeclaration[]} functionDeclarations - Array of TypeScript function
16+
* declarations to be filtered and analyzed
17+
* @param {FunctionDefinition[]} functions - Array of function definitions containing
18+
* metadata about functions, including identifiers and parameter definitions
19+
* @param {ts.Type} paramType - The target parameter type used to filter compatible
20+
* functions. Only functions with return types assignable to this type are included
21+
*
22+
* @returns {NodeFunction[]} Array of node functions that are compatible with the
23+
* specified parameter type. Returns an empty array if the parameter type
24+
* is a sub-flow or if no compatible functions are found
25+
*
26+
* @example
27+
* const compatibleNodes = getNodes(checker, funcDecls, funcDefs, stringType);
28+
*/
29+
export const getNodes = (
30+
checker: ts.TypeChecker,
31+
functionDeclarations: ts.FunctionDeclaration[],
32+
functions: FunctionDefinition[],
33+
paramType: ts.Type
34+
): NodeFunction[] => {
35+
// Early exit: if the parameter type is a sub-flow, no node functions are applicable
36+
if (isSubFlow(paramType)) {
37+
return [];
38+
}
39+
40+
// Transform each function declaration into a node function if it matches the parameter type
41+
return functionDeclarations.flatMap((func) => {
42+
const nodeFunction = createNodeFunctionIfCompatible(checker, func, functions, paramType);
43+
return nodeFunction ? [nodeFunction] : [];
44+
});
45+
};
46+
47+
/**
48+
* Creates a node function from a function declaration if its return type is compatible
49+
* with the specified parameter type.
50+
*
51+
* This helper function handles the type checking logic and node function construction.
52+
* It extracts the function signature, resolves type parameters, verifies type compatibility,
53+
* and builds a complete node function object with parameter definitions.
54+
*
55+
* @param {ts.TypeChecker} checker - The TypeScript type checker for type analysis
56+
* @param {ts.FunctionDeclaration} func - The function declaration to process
57+
* @param {FunctionDefinition[]} functions - Array of function definitions for metadata lookup
58+
* @param {ts.Type} paramType - The target parameter type for compatibility check
59+
*
60+
* @returns {NodeFunction | null} A node function object if the function is compatible
61+
* with the parameter type, otherwise null
62+
*
63+
* @private
64+
*/
65+
const createNodeFunctionIfCompatible = (
66+
checker: ts.TypeChecker,
67+
func: ts.FunctionDeclaration,
68+
functions: FunctionDefinition[],
69+
paramType: ts.Type
70+
): NodeFunction | null => {
71+
// Extract the function signature and its return type
72+
const signature = checker.getSignatureFromDeclaration(func);
73+
const returnType = checker.getReturnTypeOfSignature(signature!);
74+
75+
// Simplify the return type by resolving type parameters
76+
const simplifiedReturnType = resolveReturnType(checker, returnType);
77+
78+
// Only proceed if the return type is assignable to the target parameter type
79+
if (!checker.isTypeAssignableTo(simplifiedReturnType, paramType)) {
80+
return null;
81+
}
82+
83+
// Extract and normalize the function name
84+
const functionName = normalizeFunctionName(func.name?.getText());
85+
const functionDefinition = functions.find((f) => f.identifier === functionName);
86+
87+
// Build and return the node function object
88+
return buildNodeFunction(functionDefinition);
89+
};
90+
91+
/**
92+
* Resolves a return type by handling type parameters with their base constraints.
93+
*
94+
* If the provided type is a type parameter, this function retrieves its base constraint.
95+
* If no base constraint exists, it falls back to the `any` type. Otherwise, it returns
96+
* the type as-is.
97+
*
98+
* @param {ts.TypeChecker} checker - The TypeScript type checker
99+
* @param {ts.Type} returnType - The return type to resolve
100+
*
101+
* @returns {ts.Type} The resolved return type with type parameters replaced by their
102+
* base constraints or the `any` type as a fallback
103+
*
104+
* @private
105+
*/
106+
const resolveReturnType = (checker: ts.TypeChecker, returnType: ts.Type): ts.Type => {
107+
if (returnType.isTypeParameter()) {
108+
return checker.getBaseConstraintOfType(returnType) || checker.getAnyType();
109+
}
110+
return returnType;
111+
};
112+
113+
/**
114+
* Normalizes a function name by removing prefixes and replacing underscores with
115+
* double colons (::).
116+
*
117+
* This function applies the following transformations:
118+
* 1. Removes the "fn_" prefix
119+
* 2. Replaces the first underscore with "::"
120+
* 3. Replaces the second underscore with "::"
121+
*
122+
* Example: "fn_module_submodule" becomes "module::submodule"
123+
*
124+
* @param {string | undefined} rawName - The raw function name from the declaration
125+
*
126+
* @returns {string} The normalized function name, or an empty string if the input
127+
* is undefined
128+
*
129+
* @private
130+
*/
131+
const normalizeFunctionName = (rawName: string | undefined): string => {
132+
if (!rawName) {
133+
return "";
134+
}
135+
return rawName
136+
.replace("fn_", "")
137+
.replace("_", "::")
138+
.replace("_", "::");
139+
};
140+
141+
/**
142+
* Builds a complete node function object with all required metadata and parameters.
143+
*
144+
* Constructs a GraphQL-compatible node function structure that includes:
145+
* - GraphQL type information (__typename and id)
146+
* - Function definition metadata (identifier and id)
147+
* - Parameter definitions with default values if applicable
148+
*
149+
* @param {FunctionDefinition | undefined} functionDefinition - The function definition
150+
* containing metadata and parameter information. If undefined, the node function
151+
* will still be created with null references for the definition.
152+
*
153+
* @returns {NodeFunction} A fully constructed node function object ready for use in
154+
* the GraphQL schema
155+
*
156+
* @private
157+
*/
158+
const buildNodeFunction = (
159+
functionDefinition: FunctionDefinition | undefined
160+
): NodeFunction => {
161+
const hasParameters =
162+
(functionDefinition?.parameterDefinitions?.nodes?.length ?? 0) > 0;
163+
164+
const baseNode: NodeFunction = {
165+
__typename: "NodeFunction",
166+
id: `gid://sagittarius/NodeFunction/1`,
167+
functionDefinition: {
168+
__typename: "FunctionDefinition",
169+
id: functionDefinition?.id,
170+
identifier: functionDefinition?.identifier,
171+
},
172+
};
173+
174+
if (hasParameters) {
175+
baseNode.parameters = buildParameterConnection(
176+
functionDefinition?.parameterDefinitions?.nodes || []
177+
) as any;
178+
}
179+
180+
return baseNode;
181+
};
182+
183+
/**
184+
* Builds a parameter connection object containing all parameter definitions.
185+
*
186+
* Transforms an array of parameter definitions into a GraphQL-compatible parameter
187+
* connection structure. Each parameter is enriched with its definition metadata and
188+
* default value if available.
189+
*
190+
* @param {any[]} parameterNodes - Array of parameter definition nodes to be transformed
191+
*
192+
* @returns {Object} A parameter connection object with __typename and an array of
193+
* node parameters, each containing parameter definition and default value
194+
*
195+
* @private
196+
*/
197+
const buildParameterConnection = (parameterNodes: any[]) => {
198+
return {
199+
__typename: "NodeParameterConnection",
200+
nodes: parameterNodes.map((p) => buildNodeParameter(p)),
201+
};
202+
};
203+
204+
/**
205+
* Builds a single node parameter object from a parameter definition.
206+
*
207+
* Constructs a GraphQL-compatible parameter node that includes:
208+
* - Parameter definition metadata (id and identifier)
209+
* - Default value if available, otherwise null
210+
*
211+
* @param {any} parameterDef - The parameter definition object containing id,
212+
* identifier, and optional defaultValue
213+
*
214+
* @returns {Object} A node parameter object with __typename, parameterDefinition,
215+
* and default value information
216+
*
217+
* @private
218+
*/
219+
const buildNodeParameter = (parameterDef: any) => {
220+
return {
221+
__typename: "NodeParameter",
222+
parameterDefinition: {
223+
__typename: "ParameterDefinition",
224+
id: parameterDef?.id,
225+
identifier: parameterDef?.identifier,
226+
},
227+
value: parameterDef?.defaultValue
228+
? {
229+
__typename: "LiteralValue",
230+
value: parameterDef.defaultValue.value,
231+
}
232+
: null,
233+
};
234+
};

0 commit comments

Comments
 (0)