From 469286e0da9ac1547dc5f46bc87b0cfcdfba3e59 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 6 Jul 2026 00:40:00 +0200 Subject: [PATCH 1/2] feat: add test for ReferenceValue suggestion in rest::control::respond payload --- test/schema/schema.test.ts | 69 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/test/schema/schema.test.ts b/test/schema/schema.test.ts index 0a2a0e6..efadaf6 100644 --- a/test/schema/schema.test.ts +++ b/test/schema/schema.test.ts @@ -619,4 +619,73 @@ describe("Schema", () => { expect(first.schema.input).toBe("text"); }); + it('offers a saved object as a ReferenceValue suggestion in rest::control::respond payload', () => { + // Node 1: std::control::value(value: T): T saves an object literal, so its + // return type is an OBJECT. + // Node 2: rest::control::respond + // (http_status_code, headers: OBJECT<{}>, http_schema: S, + // payload: HTTP_PAYLOAD): void + // With http_schema = "application/json", HTTP_PAYLOAD resolves to OBJECT<{}>. + // + // Node 1 returns an object, which is assignable to OBJECT<{}>, so node 1 must be + // offered as a ReferenceValue suggestion for the payload parameter. + const flow: Flow = { + id: "gid://sagittarius/Flow/1", + startingNodeId: "gid://sagittarius/NodeFunction/1", + signature: "(): void", + nodes: { + nodes: [ + { + id: "gid://sagittarius/NodeFunction/1", + functionDefinition: {identifier: "std::control::value"}, + nextNodeId: "gid://sagittarius/NodeFunction/2", + parameters: { + nodes: [ + {value: {__typename: "LiteralValue", value: {}}}, + ], + }, + }, + { + id: "gid://sagittarius/NodeFunction/2", + functionDefinition: {identifier: "rest::control::respond"}, + parameters: { + nodes: [ + {value: {__typename: "LiteralValue", value: 200}}, + {value: {__typename: "LiteralValue", value: {}}}, + {value: {__typename: "LiteralValue", value: "application/json"}}, + {value: {__typename: "LiteralValue", value: {}}}, + ], + }, + }, + ], + }, + }; + + const result = getSignatureSchema( + flow, + DATA_TYPES, + FUNCTION_SIGNATURES, + "gid://sagittarius/NodeFunction/2", + ); + + // payload is the 4th parameter of rest::control::respond. + const payloadSchema = result[3]; + + // application/json → HTTP_PAYLOAD = OBJECT<{}> → open object input. + expect(payloadSchema.schema.input).toBe("data"); + + // Node 1 (std::control::value) returns an object assignable to OBJECT<{}>. + // Its return value is a direct reference (no property path). + const suggestions = (payloadSchema.schema.suggestions ?? []) as any[]; + expect(suggestions.length).toBeGreaterThan(0); + expect( + suggestions.some( + (s) => + s.__typename === "ReferenceValue" && + s.nodeFunctionId === "gid://sagittarius/NodeFunction/1" && + !s.referencePath, + ), + ).toBe(true); + }); + }) \ No newline at end of file From ab0fabb3f57510b7b4e27c7c1ecd7cfc3641a6f0 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 6 Jul 2026 00:40:06 +0200 Subject: [PATCH 2/2] feat: widen type resolution for conditional types in getSignatureSchema --- src/schema/getSignatureSchema.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/schema/getSignatureSchema.ts b/src/schema/getSignatureSchema.ts index c5963ef..46df755 100644 --- a/src/schema/getSignatureSchema.ts +++ b/src/schema/getSignatureSchema.ts @@ -393,6 +393,17 @@ const widenForSuggestions = (checker: ts.TypeChecker, type: ts.Type, node: ts.Va return checker.getAnyType() } + // A conditional type that still depends on a free type parameter cannot be + // resolved to a single branch for suggestion scoping (e.g. + // HTTP_PAYLOAD = S extends 'application/json' ? OBJECT<{}> : … stays + // unresolved while S is free, and isTypeAssignableTo against it is always + // false). Its base constraint is the union of every branch + // (string | OBJECT<{}> | undefined) — exactly the set of values the function + // could accept here — so widen to that. + if ((type.flags & ts.TypeFlags.Conditional) !== 0) { + return checker.getBaseConstraintOfType(type) ?? checker.getAnyType() + } + if ((type.flags & ts.TypeFlags.Object) !== 0 && hasFreeTypeParam(type, checker, new Set())) { const aliasName: string | undefined = (type as any).aliasSymbol?.getName() if (aliasName) {