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
4 changes: 4 additions & 0 deletions src/util/nodes.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ const createNodeFunctionIfCompatible = (
functions: FunctionDefinition[],
paramType: ts.Type
): NodeFunction | null => {

if (func.parameters.length > 0)
return null;

// Extract the function signature and its return type
const signature = checker.getSignatureFromDeclaration(func);
const returnType = checker.getReturnTypeOfSignature(signature!);
Expand Down
17 changes: 15 additions & 2 deletions src/util/schema.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import ts, {FunctionDeclaration} from "typescript";
import {getValues} from "./values.util";
import {getReferences} from "./references.util";
import {getNodes} from "./nodes.util";
import {FunctionDefinition, LiteralValue, NodeFunction, ReferenceValue,} from "@code0-tech/sagittarius-graphql-types";
import {
FunctionDefinition,
LiteralValue,
NodeFunction,
ReferenceValue,
SubFlowValue,
} from "@code0-tech/sagittarius-graphql-types";
import {getSubFlows} from "./subflows.util";


/**
Expand All @@ -13,7 +20,7 @@ export interface Input {
/** The type of input (string representation) */
input?: string;
/** Array of suggested values (functions, references, or literals) */
suggestions?: (NodeFunction | ReferenceValue | LiteralValue)[];
suggestions?: (NodeFunction | ReferenceValue | LiteralValue | SubFlowValue)[];
}

/**
Expand Down Expand Up @@ -135,6 +142,12 @@ export const getSchema = (
functions,
parameterType
),
...getSubFlows(
checker,
functionDeclarations,
functions,
parameterType
),
],
} : {};

Expand Down
142 changes: 142 additions & 0 deletions src/util/subflows.util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import ts from "typescript";
import {FunctionDefinition, SubFlowValue, SubFlowValueSetting} from "@code0-tech/sagittarius-graphql-types";
import {isSubFlow} from "./schema.util";

/**
* Extracts and creates a list of SubFlowValue objects from function declarations
* that are compatible with the given parameter type.
*
* This function filters function declarations based on type compatibility and
* converts matching functions into SubFlowValue objects that can be used as
* sub-flows in a larger workflow.
*
* @param {ts.TypeChecker} checker - The TypeScript type checker for type analysis
* @param {ts.FunctionDeclaration[]} functionDeclarations - Array of function declarations to analyze
* @param {FunctionDefinition[]} functions - Array of function definitions for reference lookup
* @param {ts.Type} paramType - The target parameter type that functions must be compatible with
*
* @returns {SubFlowValue[]} An array of SubFlowValue objects for compatible functions,
* or an empty array if the paramType is not a sub-flow type
*/
export const getSubFlows = (
checker: ts.TypeChecker,
functionDeclarations: ts.FunctionDeclaration[],
functions: FunctionDefinition[],
paramType: ts.Type
): SubFlowValue[] => {

if (!isSubFlow(paramType)) {
return [];
}

return functionDeclarations.flatMap((func) => {
const subFlow = createSubFlowIfCompatible(checker, func, functions, paramType);
return subFlow ? [subFlow] : [];
});

}

/**
* Verifies that a function declaration is compatible with a given parameter type
* and creates a SubFlowValue if the types match.
*
* Type compatibility is checked by comparing the function type against the expected
* parameter type (e.g., a function signature like (number: number) => void).
* If compatible, the function is converted into a SubFlowValue object with its
* associated parameter settings.
*
* @param {ts.TypeChecker} checker - The TypeScript type checker for type analysis
* @param {ts.FunctionDeclaration} func - The function declaration to check
* @param {FunctionDefinition[]} functions - Array of function definitions for lookup
* @param {ts.Type} paramType - The expected parameter type to check compatibility against
*
* @returns {SubFlowValue | null} A SubFlowValue object if the function is compatible
* with the paramType and a matching function definition exists, or null otherwise
*/
const createSubFlowIfCompatible = (
checker: ts.TypeChecker,
func: ts.FunctionDeclaration,
functions: FunctionDefinition[],
paramType: ts.Type
): SubFlowValue | null => {

// Get the full function type from the declaration
const functionType = checker.getTypeAtLocation(func);

// Check if the function type is assignable to the parameter type
// This ensures the function signature matches the expected interface
// e.g., paramType might be (number: number) => void, and functionType should be compatible
if (!checker.isTypeAssignableTo(functionType, paramType)) {
return null;
}

// If type-compatible, find the function definition and create the SubFlowValue
const functionName = normalizeFunctionName(func.name?.getText());
const functionDefinition = functions.find((f) => f.identifier === functionName);

if (!functionDefinition) {
return null;
}

return buildSubFlowValue(functionDefinition);

}

/**
* Normalizes a function name by removing prefixes and replacing underscores with
* double colons (::).
*
* This function applies the following transformations:
* 1. Removes the "fn_" prefix
* 2. Replaces the first underscore with "::"
* 3. Replaces the second underscore with "::"
*
* Example: "fn_module_submodule" becomes "module::submodule"
*
* @param {string | undefined} rawName - The raw function name from the declaration
*
* @returns {string} The normalized function name, or an empty string if the input
* is undefined
*
* @private
*/
const normalizeFunctionName = (rawName: string | undefined): string => {
if (!rawName) {
return "";
}
return rawName
.replace("fn_", "")
.replace("_", "::")
.replace("_", "::");
}

/**
* Constructs a SubFlowValue object from a function definition.
*
* This function transforms a FunctionDefinition into a SubFlowValue with all
* its parameter settings properly configured. Each parameter from the function
* definition is converted into a SubFlowValueSetting object that includes
* metadata such as identifier, default values, hidden status, and optional status.
*
* @param {FunctionDefinition} functionDefinition - The function definition to convert
*
* @returns {SubFlowValue} A SubFlowValue object with the function definition
* and all parameter settings configured
*/
const buildSubFlowValue = (functionDefinition: FunctionDefinition): SubFlowValue => {
return {
__typename: "SubFlowValue",
functionDefinition: functionDefinition,
signature: functionDefinition.signature,
settings: functionDefinition.parameterDefinitions?.nodes?.map(param => {
const setting: SubFlowValueSetting = {
__typename: "SubFlowValueSetting",
identifier: param?.identifier,
defaultValue: param?.defaultValue ?? null,
hidden: param?.hidden ?? false,
optional: param?.optional ?? false,
}
return setting
})
}
}
8 changes: 4 additions & 4 deletions test/schema/schema.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {describe, expect, it} from "vitest";
import {describe, it} from "vitest";
import {Flow} from "@code0-tech/sagittarius-graphql-types";
import {getFlowValidation, getSignatureSchema, getTypeSchema} from "../../src";
import {getSignatureSchema, getTypeSchema} from "../../src";
import {DATA_TYPES, FUNCTION_SIGNATURES} from "../data";

describe("Schema", () => {
Expand Down Expand Up @@ -36,8 +36,8 @@ describe("Schema", () => {
},
{
value: {
__typename: "NodeFunctionIdWrapper",
id: "gid://sagittarius/NodeFunction/3"
__typename: "SubFlowValue",
startingNodeId: "gid://sagittarius/NodeFunction/3"
}
}
]
Expand Down