Skip to content

Commit 562e880

Browse files
Added convert layer for outputSchema
1 parent dabb587 commit 562e880

6 files changed

Lines changed: 92 additions & 12 deletions

File tree

packages/spectral/src/generators/cniComponentManifest/types.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,10 @@ export interface InputNode {
5252
onPremiseControlled: boolean;
5353
}
5454

55-
export type FormattedAction = Pick<
56-
Action,
57-
"key" | "display" | "inputs" | "examplePayload" | "outputSchema"
58-
>;
55+
export type FormattedAction = Pick<Action, "key" | "display" | "inputs" | "examplePayload"> & {
56+
/** Emitted verbatim into the generated manifest; shape mirrors `examplePayload`. */
57+
outputSchema?: unknown;
58+
};
5959
export type FormattedTrigger<
6060
TInputs extends Inputs,
6161
TActionInputs extends Inputs,

packages/spectral/src/generators/componentManifest/createActions.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import path from "path";
22
import type { ConfigVarResultCollection, Inputs, TriggerPayload, TriggerResult } from "../../types";
3-
import type { OutputSchema } from "../../types/OutputSchema";
43
import type { ComponentForManifest } from "../cniComponentManifest/types";
54
import { createImport } from "../utils/createImport";
65
import { createTemplate } from "../utils/createTemplate";
@@ -150,7 +149,7 @@ interface RenderActionProps {
150149
description: string;
151150
inputs: Input[];
152151
examplePayload?: unknown;
153-
outputSchema?: OutputSchema;
152+
outputSchema?: unknown;
154153
componentKey: string;
155154
};
156155
dryRun: boolean;

packages/spectral/src/serverTypes/convertComponent.test.ts

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import { describe, expect, it, vi } from "vitest";
2-
import { connection, dynamicObjectInput, input, structuredObjectInput, trigger } from "..";
2+
import {
3+
action,
4+
component,
5+
connection,
6+
dynamicObjectInput,
7+
input,
8+
structuredObjectInput,
9+
trigger,
10+
} from "..";
311
import {
412
cleanerFor,
513
convertConnection,
@@ -554,3 +562,52 @@ describe("cleanerFor", () => {
554562
});
555563
});
556564
});
565+
566+
describe("convertComponent outputSchema", () => {
567+
// `component()` is `convertComponent()`, i.e. the real publish path.
568+
const convertedAction = (outputSchema?: unknown) =>
569+
(
570+
component({
571+
key: "acme",
572+
public: false,
573+
display: { label: "Acme", description: "Acme", iconPath: "icon.png" },
574+
actions: {
575+
doThing: action({
576+
display: { label: "Do Thing", description: "Does the thing" },
577+
inputs: {},
578+
...(outputSchema ? { outputSchema } : {}),
579+
perform: async () => ({ data: {} }),
580+
} as any),
581+
},
582+
} as any).actions as any
583+
).doThing;
584+
585+
it("serializes an actionOutput schema to a JSON string", () => {
586+
const schema = { type: "object", properties: { id: { type: "string" } } };
587+
const { outputSchema } = convertedAction({ type: "actionOutput", schema });
588+
589+
expect(outputSchema).toEqual({ type: "actionOutput", schema: JSON.stringify(schema) });
590+
expect(typeof outputSchema.schema).toBe("string");
591+
});
592+
593+
it("flattens branchingOutput branchSchemas to a list of stringified name/schema pairs", () => {
594+
const found = { type: "object", properties: { id: { type: "string" } } };
595+
const notFound = { type: "object" };
596+
const { outputSchema } = convertedAction({
597+
type: "branchingOutput",
598+
branchSchemas: { found, notFound },
599+
});
600+
601+
expect(outputSchema).toEqual({
602+
type: "branchingOutput",
603+
branchSchemas: [
604+
{ name: "found", schema: JSON.stringify(found) },
605+
{ name: "notFound", schema: JSON.stringify(notFound) },
606+
],
607+
});
608+
});
609+
610+
it("omits outputSchema when the action declares none", () => {
611+
expect(convertedAction()).not.toHaveProperty("outputSchema");
612+
});
613+
});

packages/spectral/src/serverTypes/convertComponent.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
type InputFieldDefinition,
1313
type Inputs,
1414
type OnPremConnectionInput,
15+
type OutputSchema,
1516
type TriggerDefinition,
1617
type TriggerOptionChoice,
1718
type TriggerPayload,
@@ -28,6 +29,7 @@ import type {
2829
Connection as ServerConnection,
2930
DataSource as ServerDataSource,
3031
Input as ServerInput,
32+
ServerOutputSchema,
3133
Trigger as ServerTrigger,
3234
} from ".";
3335
import {
@@ -279,9 +281,22 @@ export const convertTemplateInput = (
279281
};
280282
};
281283

284+
const convertOutputSchema = (outputSchema: OutputSchema): ServerOutputSchema => {
285+
if (outputSchema.type === "actionOutput") {
286+
return { type: "actionOutput", schema: JSON.stringify(outputSchema.schema) };
287+
}
288+
return {
289+
type: "branchingOutput",
290+
branchSchemas: Object.entries(outputSchema.branchSchemas).map(([name, schema]) => ({
291+
name,
292+
schema: JSON.stringify(schema),
293+
})),
294+
};
295+
};
296+
282297
const convertAction = (
283298
actionKey: string,
284-
{ inputs = {}, perform, ...action }: ActionDefinition<Inputs, any, boolean, any>,
299+
{ inputs = {}, perform, outputSchema, ...action }: ActionDefinition<Inputs, any, boolean, any>,
285300
hooks?: ComponentHooks,
286301
): ServerAction => {
287302
const convertedInputs = Object.entries(inputs).map(([key, value]) => convertInput(key, value));
@@ -298,6 +313,7 @@ const convertAction = (
298313
inputCleaners,
299314
errorHandler: hooks?.error,
300315
}),
316+
...(outputSchema ? { outputSchema: convertOutputSchema(outputSchema) } : {}),
301317
};
302318
};
303319

packages/spectral/src/serverTypes/index.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import type {
2121
TriggerResult as TriggerPerformResult,
2222
UserAttributes,
2323
} from "../types";
24-
import type { OutputSchema } from "../types/OutputSchema";
2524
import type { CNIPollingPerformFunction, ComponentRefTriggerPerformFunction } from "./triggerTypes";
2625

2726
interface DisplayDefinition {
@@ -82,10 +81,20 @@ export interface Action {
8281
dynamicBranchInput?: string;
8382
perform: ActionPerformFunction;
8483
examplePayload?: unknown;
85-
/** Declares the shape of this action's output `data` as a JSON Schema (discriminated union: actionOutput | branchingOutput). */
86-
outputSchema?: OutputSchema;
84+
/**
85+
* The on-the-wire form of an action's `outputSchema`, as accepted by the
86+
* `PublishComponent` mutation. JSON Schemas are serialized to strings and the
87+
* branching variant's per-branch map is flattened to a `{ name, schema }`
88+
* list (GraphQL input has no map type). Produced by `convertOutputSchema`
89+
* from the author-facing `OutputSchema`.
90+
*/
91+
outputSchema?: ServerOutputSchema;
8792
}
8893

94+
export type ServerOutputSchema =
95+
| { type: "actionOutput"; schema: string }
96+
| { type: "branchingOutput"; branchSchemas: Array<{ name: string; schema: string }> };
97+
8998
export type ActionLoggerFunction = (...args: unknown[]) => void;
9099

91100
export interface ActionLogger {

packages/spectral/src/types/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ export * from "./IntegrationDefinition";
3131
export * from "./OutputSchema";
3232
export * from "./PollingTriggerDefinition";
3333
export * from "./ScopedConfigVars";
34-
export * from "./ScopedConfigVars";
3534
export * from "./TriggerDefinition";
3635
export * from "./TriggerEventFunction";
3736
export * from "./TriggerPayload";

0 commit comments

Comments
 (0)