From e334c5fde7f1733103d831b4383f65ad88e3718f Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 15 Oct 2026 13:17:31 +0200 Subject: [PATCH] feat: enhance flow validation and schema generation with flow types --- entrypoint/flow-schema-extraction.ts | 2 +- entrypoint/mapper.ts | 22 +++++++-- entrypoint/single-validation.ts | 2 +- src/extraction/getFlowSchemas.ts | 6 ++- src/schema/getSignatureSchema.ts | 7 ++- src/utils.ts | 14 +++++- src/validation/getFlowValidation.ts | 11 +++-- test/data.ts | 15 +++++- test/flowCast.test.ts | 14 +++--- test/flowSchemas.test.ts | 27 ++++++----- test/flowValidation.test.ts | 32 +++++-------- test/schema/circular.test.ts | 1 - test/schema/listElementProducer.test.ts | 1 - test/schema/schema.test.ts | 63 ++++++++++--------------- test/schema/suggestionCertainty.test.ts | 3 -- 15 files changed, 125 insertions(+), 95 deletions(-) diff --git a/entrypoint/flow-schema-extraction.ts b/entrypoint/flow-schema-extraction.ts index 8050078..f51af90 100644 --- a/entrypoint/flow-schema-extraction.ts +++ b/entrypoint/flow-schema-extraction.ts @@ -7,7 +7,7 @@ import type {Scalars} from "@code0-tech/sagittarius-graphql-types"; const data = await readSingleValidation(console); const validationInput = mapToFlowValidation(data); -const result = getFlowSchemas(validationInput.flow, validationInput.functions, validationInput.dataTypes) +const result = getFlowSchemas(validationInput.flow, validationInput.functions, validationInput.dataTypes, validationInput.flowTypes) if (result) { type MappedResult = { diff --git a/entrypoint/mapper.ts b/entrypoint/mapper.ts index 4024079..d51e640 100644 --- a/entrypoint/mapper.ts +++ b/entrypoint/mapper.ts @@ -1,7 +1,7 @@ import {SingleValidationInputData} from "./readSingle"; import { DataType, - Flow, FlowSetting, + Flow, FlowSetting, FlowType, FunctionDefinition, NodeFunction, NodeParameter, @@ -27,14 +27,28 @@ import {toAllowedValue} from "@code0-tech/tucana/helpers"; export type TriangulumFlowValidationInput = { flow?: Flow, functions?: FunctionDefinition[], - dataTypes?: DataType[] + dataTypes?: DataType[], + flowTypes?: FlowType[] } export function mapToFlowValidation(data: SingleValidationInputData): TriangulumFlowValidationInput { return { flow: mapFlow(data.flow!), functions: data.functions.map(mapFunctionDefinition), - dataTypes: data.dataTypes.map(mapDataType) + dataTypes: data.dataTypes.map(mapDataType), + flowTypes: [mapFlowType(data.flow!)] + } +} + +// The validation input carries the flow's signature inline rather than a flow type +// catalogue, so the flow type the flow points at is reconstructed from it. +const FLOW_TYPE_ID = gid('FlowType', 1) as FlowType['id']; + +function mapFlowType(flow: ValidationFlow): FlowType { + return { + __typename: "FlowType", + id: FLOW_TYPE_ID, + signature: flow.signature } } @@ -46,7 +60,7 @@ function mapFlow(flow: ValidationFlow): Flow { return { __typename: "Flow", id: gid('Flow', flow.flowId) as Flow['id'], - signature: flow.signature, + type: {__typename: "FlowType", id: FLOW_TYPE_ID}, startingNodeId: gid('NodeFunction', flow.startingNodeId) as NodeFunction['id'], settings: { nodes: flow.settings.map(mapFlowSetting) diff --git a/entrypoint/single-validation.ts b/entrypoint/single-validation.ts index c474b31..e043ac1 100644 --- a/entrypoint/single-validation.ts +++ b/entrypoint/single-validation.ts @@ -5,6 +5,6 @@ import {getFlowValidation} from "@code0-tech/triangulum"; const data = await readSingleValidation(console); const validationInput = mapToFlowValidation(data); -const result = getFlowValidation(validationInput.flow, validationInput.functions, validationInput.dataTypes) +const result = getFlowValidation(validationInput.flow, validationInput.functions, validationInput.dataTypes, validationInput.flowTypes) console.info(JSON.stringify(result)); diff --git a/src/extraction/getFlowSchemas.ts b/src/extraction/getFlowSchemas.ts index b42e4a6..2bd65bc 100644 --- a/src/extraction/getFlowSchemas.ts +++ b/src/extraction/getFlowSchemas.ts @@ -2,6 +2,7 @@ import ts from "typescript" import type { DataType, Flow, + FlowType, FunctionDefinition, NodeFunction, NodeParameter, @@ -66,6 +67,8 @@ export type SchematizedFlow = Flow & SignatureSchemas * @param flow - The flow to enrich; returned unchanged when undefined * @param functions - Available function definitions (drive the generated code) * @param dataTypes - Available data type definitions (drive the type aliases) + * @param flowTypes - Available flow types; the one the flow points at supplies + * the flow's own signature, and with it its input/output schemas * @returns The same flow, enriched with input/output schemas, or `undefined` * when no flow was supplied */ @@ -73,11 +76,12 @@ export const getFlowSchemas = ( flow?: Flow, functions?: FunctionDefinition[], dataTypes?: DataType[], + flowTypes?: FlowType[], ): SchematizedFlow | undefined => { if (!flow) return undefined const fileName = "index.ts" - const sourceCode = generateFlowSourceCode(flow, functions, dataTypes) + const sourceCode = generateFlowSourceCode(flow, functions, dataTypes, false, true, flowTypes) const host = createCompilerHost(fileName, sourceCode) const sourceFile = host.getSourceFile(fileName)! const checker = host.languageService.getProgram()!.getTypeChecker() diff --git a/src/schema/getSignatureSchema.ts b/src/schema/getSignatureSchema.ts index 5a081f9..a4ab4d8 100644 --- a/src/schema/getSignatureSchema.ts +++ b/src/schema/getSignatureSchema.ts @@ -1,4 +1,4 @@ -import {DataType, Flow, FunctionDefinition, NodeFunction} from "@code0-tech/sagittarius-graphql-types" +import {DataType, Flow, FlowType, FunctionDefinition, NodeFunction} from "@code0-tech/sagittarius-graphql-types" import {createCompilerHost, generateFlowSourceCode, sanitizeId} from "../utils" import ts, {Type} from "typescript" import { @@ -76,6 +76,8 @@ interface ParameterDependency { * @param dataTypes - Array of available data type definitions * @param functions - Array of available function definitions * @param nodeId - Optional specific node ID to analyze; if provided, only that node's schema is processed + * @param flowTypes - Array of available flow types; the one the flow points at supplies + * the flow's own signature, which drives the flow-level schemas * * @returns Array of NodeSchema objects, each containing a schema and optional blocked dependencies * @@ -90,9 +92,10 @@ export const getSignatureSchema = ( dataTypes: DataType[], functions: FunctionDefinition[], nodeId?: NodeFunction["id"], + flowTypes?: FlowType[], ): SignatureSchema => { // Generate TypeScript source code from the flow definition - const sourceCode = generateFlowSourceCode(flow, functions, dataTypes) + const sourceCode = generateFlowSourceCode(flow, functions, dataTypes, false, true, flowTypes) // Set up the TypeScript compiler environment const fileName = "index.ts" diff --git a/src/utils.ts b/src/utils.ts index b224b1b..2266e4b 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,6 +2,7 @@ import { DataType, Flow, + FlowType, FunctionDefinition, NodeFunction, SubFlowValue, @@ -324,6 +325,14 @@ const emitLiteralNode = (value: unknown, refExpr: Map): string = return stringify(value) ?? "undefined"; }; +/** + * Resolves the signature a flow is callable by. A flow carries only the ID of its + * flow type, so the signature is looked up in the supplied flow types; a flow + * whose type is unknown falls back to the parameterless `(): void`. + */ +export const resolveFlowSignature = (flow?: Flow, flowTypes?: FlowType[]): string => + flowTypes?.find(t => t?.id === flow?.type?.id)?.signature ?? "(): void"; + /** * Generates TypeScript source code for a flow, suitable for validation and type inference. */ @@ -332,7 +341,8 @@ export function generateFlowSourceCode( functions?: FunctionDefinition[], dataTypes?: DataType[], isForInference: boolean = false, - assertNonNullReferences: boolean = true + assertNonNullReferences: boolean = true, + flowTypes?: FlowType[] ): string { const nodes = flow?.nodes?.nodes || []; const funcMap = new Map(functions?.map(f => [f.identifier, f])); @@ -498,7 +508,7 @@ export function generateFlowSourceCode( }; const typeDefs = getSharedTypeDeclarations(dataTypes); - const flowTypeDeclaration = `declare function flow${flow?.signature ?? "(): void"}` + const flowTypeDeclaration = `declare function flow${resolveFlowSignature(flow, flowTypes)}` const funcDeclarations = functions?.map(f => `declare function fn_${f.identifier?.replace(/::/g, '_')}${f.signature}`).join('\n'); const nextNodeIds = new Set(nodes.map(n => n?.nextNodeId).filter(id => !!id)); diff --git a/src/validation/getFlowValidation.ts b/src/validation/getFlowValidation.ts index f3635c9..f49f10a 100644 --- a/src/validation/getFlowValidation.ts +++ b/src/validation/getFlowValidation.ts @@ -1,5 +1,5 @@ import ts, {flattenDiagnosticMessageText} from "typescript"; -import {DataType, Flow, FunctionDefinition, NodeFunction} from "@code0-tech/sagittarius-graphql-types"; +import {DataType, Flow, FlowType, FunctionDefinition, NodeFunction} from "@code0-tech/sagittarius-graphql-types"; import {createCompilerHost, generateFlowSourceCode, ValidationResult} from "../utils"; // TypeScript diagnostic codes we may soften into warnings for union-branch references. @@ -83,11 +83,16 @@ const isSoftReferenceMismatch = ( /** * Validates a flow by generating virtual TypeScript code and running it through the TS compiler. + * + * The flow's own signature comes from its flow type: the flow carries only the + * type's ID, so `flowTypes` has to contain the type it points at for the flow's + * settings and its return to be validated against the real signature. */ export const getFlowValidation = ( flow?: Flow, functions?: FunctionDefinition[], - dataTypes?: DataType[] + dataTypes?: DataType[], + flowTypes?: FlowType[] ): ValidationResult => { if (!flow?.startingNodeId) { @@ -140,7 +145,7 @@ export const getFlowValidation = ( // Validation keeps reference nullability (assertNonNullReferences = false) so that a // possibly-null reference into a non-null parameter surfaces as a diagnostic, which is // then downgraded to a warning below instead of being silently suppressed by a `!`. - const sourceCode = generateFlowSourceCode(flow, functions, dataTypes, false, false); + const sourceCode = generateFlowSourceCode(flow, functions, dataTypes, false, false, flowTypes); // 3. Virtual TypeScript Compilation const fileName = "index.ts"; diff --git a/test/data.ts b/test/data.ts index da96f0f..43ac77f 100644 --- a/test/data.ts +++ b/test/data.ts @@ -1,8 +1,21 @@ -import {DataType, FunctionDefinition} from "@code0-tech/sagittarius-graphql-types"; +import {DataType, Flow, FlowType, FunctionDefinition} from "@code0-tech/sagittarius-graphql-types"; // The variants are declared as a `const enum` in a types-only package, so there // is no runtime value to reference — the string literals are cast instead. import type {DataTypeRulesVariant} from "@code0-tech/sagittarius-graphql-types"; +/** + * A flow is callable by the signature of its flow type, and carries only that + * type's ID — so a fixture flow that needs a signature points its `type` at + * {@link FLOW_TYPE} and the call under test gets {@link flowTypesFor} with the + * signature itself. A flow whose type is not in the catalogue is `(): void`. + */ +export const FLOW_TYPE_ID = "gid://sagittarius/FlowType/1" as FlowType["id"]; + +export const FLOW_TYPE: Flow["type"] = {__typename: "FlowType", id: FLOW_TYPE_ID}; + +export const flowTypesFor = (signature: string): FlowType[] => + [{__typename: "FlowType", id: FLOW_TYPE_ID, signature}]; + /** * Available data types and their TypeScript mappings. */ diff --git a/test/flowCast.test.ts b/test/flowCast.test.ts index 519fa16..335831c 100644 --- a/test/flowCast.test.ts +++ b/test/flowCast.test.ts @@ -4,7 +4,7 @@ import {generateFlowSourceCode} from "../src/utils"; import {getFlowValidation} from "../src/validation/getFlowValidation"; import {getSignatureSchema} from "../src/schema/getSignatureSchema"; // @ts-ignore -import {DATA_TYPES, FUNCTION_SIGNATURES} from "./data"; +import {DATA_TYPES, FLOW_TYPE, FUNCTION_SIGNATURES, flowTypesFor} from "./data"; /** * A parameter or flow setting may carry a `cast` — the type its value is meant to @@ -178,11 +178,13 @@ describe("cast handling in the generated flow source", () => { // The REST trigger is `(input_schema: T, ...)`: whatever // the `input_schema` setting resolves to is what T — and with it the // trigger's payload — binds to. + const REST_TRIGGER_TYPES = flowTypesFor( + "(input_schema: T, httpURL: HTTP_URL, httpMethod: HTTP_METHOD): REST_ADAPTER_INPUT", + ); const restTrigger = (cast?: string): Flow => ({ id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: - "(input_schema: T, httpURL: HTTP_URL, httpMethod: HTTP_METHOD): REST_ADAPTER_INPUT", + type: FLOW_TYPE, settings: { nodes: [ {value: {name: "text"}, cast}, @@ -194,19 +196,19 @@ describe("cast handling in the generated flow source", () => { } as Flow); const cast = "{ name: TEXT, age: NUMBER }"; - expect(generateFlowSourceCode(restTrigger(cast), FUNCTION_SIGNATURES, DATA_TYPES)) + expect(generateFlowSourceCode(restTrigger(cast), FUNCTION_SIGNATURES, DATA_TYPES, false, true, REST_TRIGGER_TYPES)) .toContain(`as unknown as ${cast}`); // Uncast, T is the shape of the setting value itself. The payload is a // return, so it renders that shape as data — never as the `T extends // TYPE` picker the parameter side uses. - const uncastPayload = (getSignatureSchema(restTrigger(), DATA_TYPES, FUNCTION_SIGNATURES) + const uncastPayload = (getSignatureSchema(restTrigger(), DATA_TYPES, FUNCTION_SIGNATURES, undefined, REST_TRIGGER_TYPES) .return as { properties: Record }).properties.payload; expect(uncastPayload.input).toBe("data"); expect(uncastPayload.type).toBe("{ name: string; }"); // Cast, T is the cast type — the payload gains the declared `age`. - const castPayload = (getSignatureSchema(restTrigger(cast), DATA_TYPES, FUNCTION_SIGNATURES) + const castPayload = (getSignatureSchema(restTrigger(cast), DATA_TYPES, FUNCTION_SIGNATURES, undefined, REST_TRIGGER_TYPES) .return as { properties: Record }).properties.payload; expect(castPayload.input).toBe("data"); expect(castPayload.properties.age.input).toBe("number"); diff --git a/test/flowSchemas.test.ts b/test/flowSchemas.test.ts index 7796bb9..f750257 100644 --- a/test/flowSchemas.test.ts +++ b/test/flowSchemas.test.ts @@ -2,7 +2,7 @@ import {describe, expect, it} from "vitest"; import type {Flow, FunctionDefinition, NodeFunction, NodeParameterValue} from "@code0-tech/sagittarius-graphql-types"; import {getFlowSchemas} from "../src/server"; import type {JsonSchema, SchematizedSubFlowValue} from "../src/server"; -import {DATA_TYPES, FUNCTION_SIGNATURES} from "./data"; +import {DATA_TYPES, FLOW_TYPE, FUNCTION_SIGNATURES, flowTypesFor} from "./data"; type NodeId = NonNullable; @@ -52,11 +52,13 @@ const node = ( parameters: {nodes: parameterValues.map((value) => ({value}))}, }); -const flowWithNodes = (nodes: NodeFunction[], signature = "(): void"): Flow => ({ +// The flow points at the fixture flow type; a test whose schemas depend on the +// flow's own signature passes that type's signature via `flowTypesFor`. +const flowWithNodes = (nodes: NodeFunction[]): Flow => ({ id: FLOW_ID, startingNodeId: nodes[0]?.id, nodes: {nodes}, - signature, + type: FLOW_TYPE, }); /** @@ -82,9 +84,10 @@ describe("getFlowSchemas", () => { }); it("adds input/output schemas derived from the flow signature", () => { - const flow = flowWithNodes([], "(method: HTTP_METHOD): NUMBER"); + const flow = flowWithNodes([]); - const result = getFlowSchemas(flow, FUNCTION_SIGNATURES, DATA_TYPES); + const result = getFlowSchemas(flow, FUNCTION_SIGNATURES, DATA_TYPES, + flowTypesFor("(method: HTTP_METHOD): NUMBER")); // inputSchema is an object keyed by parameter name. expect(result?.inputSchema.type).toBe("object"); @@ -104,12 +107,11 @@ describe("getFlowSchemas", () => { // Taken from the REST trigger scenario in schema.test.ts: input_schema is // the non-generic TYPE data type, echoed by the payload of // REST_ADAPTER_INPUT. - const flow = flowWithNodes( - [], - "(input_schema: T, httpURL: HTTP_URL, httpMethod: HTTP_METHOD): REST_ADAPTER_INPUT", - ); + const flow = flowWithNodes([]); - const result = getFlowSchemas(flow, FUNCTION_SIGNATURES, DATA_TYPES); + const result = getFlowSchemas(flow, FUNCTION_SIGNATURES, DATA_TYPES, flowTypesFor( + "(input_schema: T, httpURL: HTTP_URL, httpMethod: HTTP_METHOD): REST_ADAPTER_INPUT", + )); // TYPE is `any` underneath, so it maps to the open object schema; the // concrete parameters resolve fully. @@ -131,9 +133,10 @@ describe("getFlowSchemas", () => { }); it("maps the DATE data type to its underlying number type", () => { - const flow = flowWithNodes([], "(created: DATE): void"); + const flow = flowWithNodes([]); - const result = getFlowSchemas(flow, FUNCTION_SIGNATURES, DATA_TYPES); + const result = getFlowSchemas(flow, FUNCTION_SIGNATURES, DATA_TYPES, + flowTypesFor("(created: DATE): void")); expect(result?.inputSchema.properties?.created).toEqual({type: "number"}); }); diff --git a/test/flowValidation.test.ts b/test/flowValidation.test.ts index 22b1e65..0e93a79 100644 --- a/test/flowValidation.test.ts +++ b/test/flowValidation.test.ts @@ -2,7 +2,7 @@ import {describe, expect, it} from 'vitest'; import {getFlowValidation} from '../src/validation/getFlowValidation'; import {DataType, Flow, FunctionDefinition, InlineReferenceValue} from "@code0-tech/sagittarius-graphql-types"; // Pfad ggf. anpassen // @ts-ignore -import {DATA_TYPES, FUNCTION_SIGNATURES} from "./data"; +import {DATA_TYPES, FUNCTION_SIGNATURES, flowTypesFor} from "./data"; describe('getFlowValidation - Integrationstest', () => { it('1', () => { @@ -261,7 +261,6 @@ describe('getFlowValidation - Integrationstest', () => { "id": "gid://sagittarius/Flow/1", "createdAt": "2026-03-17T14:02:31Z", "name": "Test", - "signature": "(httpURL: HTTP_URL, httpMethod: HTTP_METHOD): REST_ADAPTER_INPUT<{}>", "nodes": { "__typename": "NodeFunctionConnection", "nodes": [ @@ -359,7 +358,7 @@ describe('getFlowValidation - Integrationstest', () => { } }; - const result = getFlowValidation(flow, FUNCTION_SIGNATURES, DATA_TYPES); + const result = getFlowValidation(flow, FUNCTION_SIGNATURES, DATA_TYPES, flowTypesFor("(httpURL: HTTP_URL, httpMethod: HTTP_METHOD): REST_ADAPTER_INPUT<{}>")); expect(result.isValid).toBe(false); result.diagnostics.forEach((error) => { @@ -374,7 +373,6 @@ describe('getFlowValidation - Integrationstest', () => { "id": "gid://sagittarius/Flow/1", "createdAt": "2026-03-17T14:02:31Z", "name": "Test", - "signature": "(httpURL: HTTP_URL, httpMethod: HTTP_METHOD): REST_ADAPTER_INPUT<{}>", "nodes": { "__typename": "NodeFunctionConnection", "nodes": [ @@ -532,7 +530,7 @@ describe('getFlowValidation - Integrationstest', () => { }, }; - const result = getFlowValidation(flow, FUNCTION_SIGNATURES, DATA_TYPES); + const result = getFlowValidation(flow, FUNCTION_SIGNATURES, DATA_TYPES, flowTypesFor("(httpURL: HTTP_URL, httpMethod: HTTP_METHOD): REST_ADAPTER_INPUT<{}>")); expect(result.isValid).toBe(false); result.diagnostics.forEach((error) => { @@ -547,7 +545,6 @@ describe('getFlowValidation - Integrationstest', () => { "id": "gid://sagittarius/Flow/1", "createdAt": "2026-04-12T13:46:13Z", "name": "Test", - "signature": "(httpURL: HTTP_URL, httpMethod: HTTP_METHOD): { body: { test: TEXT } }", "nodes": { "__typename": "NodeFunctionConnection", "nodes": [ @@ -674,7 +671,7 @@ describe('getFlowValidation - Integrationstest', () => { } }; - const result = getFlowValidation(flow, FUNCTION_SIGNATURES, DATA_TYPES); + const result = getFlowValidation(flow, FUNCTION_SIGNATURES, DATA_TYPES, flowTypesFor("(httpURL: HTTP_URL, httpMethod: HTTP_METHOD): { body: { test: TEXT } }")); expect(result.isValid).toBe(true); result.diagnostics.forEach((error) => { @@ -688,7 +685,6 @@ describe('getFlowValidation - Integrationstest', () => { "__typename": "Flow", "id": "gid://sagittarius/Flow/1", "createdAt": "2026-04-13T19:45:44Z", - "signature": "(httpURL: HTTP_URL, httpMethod: HTTP_METHOD): { payload: { test: BOOLEAN }, headers: { bla: TEXT } }", "disabledReason": null, "name": "Test", "project": { @@ -932,7 +928,13 @@ describe('getFlowValidation - Integrationstest', () => { } }; - const result = getFlowValidation(flow, FUNCTION_SIGNATURES, DATA_TYPES); + // This flow points at FlowType/2, so only a catalogue entry with that ID + // supplies its signature. + const result = getFlowValidation(flow, FUNCTION_SIGNATURES, DATA_TYPES, [{ + __typename: "FlowType", + id: "gid://sagittarius/FlowType/2", + signature: "(httpURL: HTTP_URL, httpMethod: HTTP_METHOD): { payload: { test: BOOLEAN }, headers: { bla: TEXT } }", + }]); expect(result.isValid).toBe(false); expect(result.diagnostics).toEqual(expect.arrayContaining([ @@ -956,7 +958,6 @@ describe('getFlowValidation - Integrationstest', () => { "id": "gid://sagittarius/Flow/1", "createdAt": "2026-04-12T13:46:13Z", "name": "Test", - "signature": "(httpURL: HTTP_URL, httpMethod: HTTP_METHOD): { body: { test: TEXT } }", "nodes": { "__typename": "NodeFunctionConnection", "nodes": [ @@ -1083,7 +1084,7 @@ describe('getFlowValidation - Integrationstest', () => { } }; - const result = getFlowValidation(flow, FUNCTION_SIGNATURES, DATA_TYPES); + const result = getFlowValidation(flow, FUNCTION_SIGNATURES, DATA_TYPES, flowTypesFor("(httpURL: HTTP_URL, httpMethod: HTTP_METHOD): { body: { test: TEXT } }")); expect(result.isValid).toBe(false); expect(result.diagnostics[0].nodeId).toBe(null) @@ -1097,7 +1098,6 @@ describe('getFlowValidation - Integrationstest', () => { "id": "gid://sagittarius/Flow/1", "createdAt": "2026-04-12T13:46:13Z", "name": "Test", - "signature": "(httpURL: HTTP_URL, httpMethod: HTTP_METHOD): { body: { test: TEXT } }", "nodes": { "__typename": "NodeFunctionConnection", "nodes": [ @@ -1216,7 +1216,7 @@ describe('getFlowValidation - Integrationstest', () => { } }; - const result = getFlowValidation(flow, FUNCTION_SIGNATURES, DATA_TYPES); + const result = getFlowValidation(flow, FUNCTION_SIGNATURES, DATA_TYPES, flowTypesFor("(httpURL: HTTP_URL, httpMethod: HTTP_METHOD): { body: { test: TEXT } }")); expect(result.isValid).toBe(false); expect(result.diagnostics[0].nodeId).toBe(null) @@ -1364,7 +1364,6 @@ describe('getFlowValidation - Integrationstest', () => { const buildFlow = (firstIdentifier: string, valueRef: any): Flow => ({ id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -1490,7 +1489,6 @@ describe('getFlowValidation - Integrationstest', () => { const buildFlow = (valueRef: any): Flow => ({ id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -1638,7 +1636,6 @@ describe('getFlowValidation - Integrationstest', () => { // that carries the given inline references. const buildFlow = (sink: string, literal: unknown, references: InlineReferenceValue[]): Flow => ({ startingNodeId: NODE1, - signature: "(): void", nodes: { nodes: [ { @@ -1796,7 +1793,6 @@ describe('getFlowValidation - Integrationstest', () => { // name but live in separate scopes, so the LIST<() => NUMBER> is valid. const flow: Flow = { startingNodeId: NODE1, - signature: "(): void", nodes: { nodes: [ { @@ -1926,7 +1922,6 @@ describe('getFlowValidation - Integrationstest', () => { const buildFlow = (firstIdentifier: string, valueRef: any): Flow => ({ id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -2074,7 +2069,6 @@ describe('getFlowValidation - Integrationstest', () => { // that carries the inline reference. const buildFlow = (sink: string, literal: unknown): Flow => ({ startingNodeId: NODE1, - signature: "(): void", nodes: { nodes: [ { diff --git a/test/schema/circular.test.ts b/test/schema/circular.test.ts index cafcf46..b4afbd6 100644 --- a/test/schema/circular.test.ts +++ b/test/schema/circular.test.ts @@ -15,7 +15,6 @@ const probeTwoParam = ( const flow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { diff --git a/test/schema/listElementProducer.test.ts b/test/schema/listElementProducer.test.ts index 8a7382e..8a02a7e 100644 --- a/test/schema/listElementProducer.test.ts +++ b/test/schema/listElementProducer.test.ts @@ -30,7 +30,6 @@ describe("list element producers", () => { const flow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [{ id: "gid://sagittarius/NodeFunction/1", diff --git a/test/schema/schema.test.ts b/test/schema/schema.test.ts index 7c0e0a9..66cce58 100644 --- a/test/schema/schema.test.ts +++ b/test/schema/schema.test.ts @@ -1,7 +1,7 @@ import {describe, expect, it} from "vitest"; import {DataType, Flow, FunctionDefinition} from "@code0-tech/sagittarius-graphql-types"; import {getSignatureSchema, getTypeSchema, ListSubFlowInput} from "../../src"; -import {DATA_TYPES, FUNCTION_SIGNATURES} from "../data"; +import {DATA_TYPES, FLOW_TYPE, FUNCTION_SIGNATURES, flowTypesFor} from "../data"; describe("Schema", () => { @@ -65,10 +65,11 @@ describe("Schema", () => { } ] }, - signature: "(test: HTTP_METHOD): void" + type: FLOW_TYPE }; - const result = getSignatureSchema(flow, DATA_TYPES, FUNCTION_SIGNATURES, "gid://sagittarius/NodeFunction/2"); + const result = getSignatureSchema(flow, DATA_TYPES, FUNCTION_SIGNATURES, "gid://sagittarius/NodeFunction/2", + flowTypesFor("(test: HTTP_METHOD): void")); //console.dir(result, {depth: null}) }); @@ -282,7 +283,6 @@ describe("Schema", () => { const flow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -330,7 +330,6 @@ describe("Schema", () => { const buildFlow = (returnValue: any): Flow => ({ id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -454,7 +453,6 @@ describe("Schema", () => { const buildFlow = (returnValue: any): Flow => ({ id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -536,7 +534,6 @@ describe("Schema", () => { const flow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -593,7 +590,6 @@ describe("Schema", () => { const flow: Flow = { id: "gid://sagittarius/Flow/2", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -632,7 +628,6 @@ describe("Schema", () => { const flow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -706,7 +701,6 @@ describe("Schema", () => { const flow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -774,7 +768,6 @@ describe("Schema", () => { const flow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -846,7 +839,6 @@ describe("Schema", () => { const flow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -915,7 +907,6 @@ describe("Schema", () => { const flow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -1002,7 +993,6 @@ describe("Schema", () => { const flow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -1067,7 +1057,6 @@ describe("Schema", () => { const buildPushFlow = (listValue: any): Flow => ({ id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -1117,7 +1106,6 @@ describe("Schema", () => { const buildPushFlow = (listValue: any): Flow => ({ id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -1158,7 +1146,6 @@ describe("Schema", () => { const flow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -1203,7 +1190,6 @@ describe("Schema", () => { const singleNode = (identifier: string, params: any[]): Flow => ({ id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -1339,11 +1325,13 @@ describe("Schema", () => { // REST trigger, whose type parameter is bounded by the (non-generic) // TYPE data type and whose payload echoes the bound argument: // (input_schema: T, ...): REST_ADAPTER_INPUT. + const REST_TRIGGER_TYPES = flowTypesFor( + "(input_schema: T, httpURL: HTTP_URL, httpMethod: HTTP_METHOD): REST_ADAPTER_INPUT", + ); const restTrigger = (inputSchema: any): Flow => ({ id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: - "(input_schema: T, httpURL: HTTP_URL, httpMethod: HTTP_METHOD): REST_ADAPTER_INPUT", + type: FLOW_TYPE, settings: { nodes: [ {value: inputSchema}, @@ -1359,6 +1347,8 @@ describe("Schema", () => { restTrigger({name: "text"}), DATA_TYPES, FUNCTION_SIGNATURES, + undefined, + REST_TRIGGER_TYPES, ); // Flow-level probe → no target node. @@ -1413,6 +1403,8 @@ describe("Schema", () => { restTrigger(42), DATA_TYPES, FUNCTION_SIGNATURES, + undefined, + REST_TRIGGER_TYPES, ); expect(result.nodeId).toBeUndefined(); @@ -1526,10 +1518,10 @@ describe("Schema", () => { const DIRECT_SIG = "(input_schema: TYPE, httpURL: HTTP_URL, httpMethod: HTTP_METHOD): REST_ADAPTER_INPUT"; - const flowFor = (signature: string, firstValue: any): Flow => ({ + const flowFor = (firstValue: any): Flow => ({ id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature, + type: FLOW_TYPE, settings: { nodes: [ {value: firstValue}, @@ -1543,9 +1535,11 @@ describe("Schema", () => { // Schema of the first parameter (`input_schema`) at the flow level. const paramSchema = (firstValue: any, signature = GENERIC_SIG) => (getSignatureSchema( - flowFor(signature, firstValue), + flowFor(firstValue), DATA_TYPES, FUNCTION_SIGNATURES, + undefined, + flowTypesFor(signature), ).parameters as any)[0].schema; // Clone DATA_TYPES, replacing the TYPE entry's `type` (and optionally @@ -1689,12 +1683,14 @@ describe("Schema", () => { { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature, + type: FLOW_TYPE, settings: {nodes: [{value}]}, nodes: {nodes: []}, } as Flow, [REAL_TYPE], [], + undefined, + flowTypesFor(signature), ).parameters as any)[0].schema; for (const signature of [ @@ -1875,19 +1871,21 @@ describe("Schema", () => { // A COLOR value is structurally an object, so the value-driven object path // could expand it back into a `data` input and lose the custom input — the // invariant is that a supplied value never downgrades a color parameter. - const colorFlow = (signature: string, values: any[]): Flow => ({ + const colorFlow = (values: any[]): Flow => ({ id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature, + type: FLOW_TYPE, settings: {nodes: values.map((value) => ({value}))}, nodes: {nodes: []}, } as Flow); const colorParams = (signature: string, values: any[]) => (getSignatureSchema( - colorFlow(signature, values), + colorFlow(values), DATA_TYPES, FUNCTION_SIGNATURES, + undefined, + flowTypesFor(signature), ).parameters as any[]).map((p) => p.schema); it("resolves a COLOR parameter to a color input with and without a value", () => { @@ -2040,7 +2038,6 @@ describe("Schema", () => { const flow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -2102,7 +2099,6 @@ describe("Schema", () => { const flow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -2264,7 +2260,6 @@ describe("Schema", () => { const flow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -2313,7 +2308,6 @@ describe("Schema", () => { const flow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -2535,7 +2529,6 @@ describe("Schema", () => { const buildFlow = (): Flow => ({ id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -2624,7 +2617,6 @@ describe("Schema", () => { const objectGetFlow = (objectValue: any): Flow => ({ id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -2646,7 +2638,6 @@ describe("Schema", () => { const listFilterFlow = (listValue: any): Flow => ({ id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -2805,7 +2796,6 @@ describe("Schema", () => { const flow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -2866,7 +2856,6 @@ describe("Schema", () => { const flow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -2936,7 +2925,6 @@ describe("Schema", () => { const flow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -3044,7 +3032,6 @@ describe("Schema", () => { const flow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { diff --git a/test/schema/suggestionCertainty.test.ts b/test/schema/suggestionCertainty.test.ts index a642da5..dcc1560 100644 --- a/test/schema/suggestionCertainty.test.ts +++ b/test/schema/suggestionCertainty.test.ts @@ -36,7 +36,6 @@ describe("Suggestion certainty", () => { const respondFlow = (): Flow => ({ id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -146,7 +145,6 @@ describe("Suggestion certainty", () => { const containsKeyFlow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ { @@ -241,7 +239,6 @@ describe("Suggestion certainty", () => { const flow: Flow = { id: "gid://sagittarius/Flow/1", startingNodeId: "gid://sagittarius/NodeFunction/1", - signature: "(): void", nodes: { nodes: [ {