feat: decode composite step inputs
This commit is contained in:
@@ -1,11 +1,33 @@
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { CapabilityNodeForm } from "./CapabilityNodeForm.js";
|
||||
import { CapabilityNodeForm, type CapabilityNodeFormValue } from "./CapabilityNodeForm.js";
|
||||
|
||||
afterEach(() => cleanup());
|
||||
|
||||
describe("CapabilityNodeForm", () => {
|
||||
it("accepts expression bindings in its callback value without authoring them", () => {
|
||||
const value = {
|
||||
stepId: "concat",
|
||||
capabilityName: "wf.std.concat",
|
||||
inputBindings: [
|
||||
{
|
||||
target: "request",
|
||||
expression: {
|
||||
kind: "array",
|
||||
items: [
|
||||
{ kind: "path", path: "state.foo" },
|
||||
{ kind: "literal", value: "wowcool" },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} satisfies CapabilityNodeFormValue;
|
||||
|
||||
expect(value.inputBindings).toHaveLength(1);
|
||||
expect(value.inputBindings[0]).toHaveProperty("expression.kind", "array");
|
||||
});
|
||||
|
||||
it("submits explicit node metadata and serialized schema bindings", async () => {
|
||||
const user = userEvent.setup();
|
||||
const submissions: unknown[] = [];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo, useRef, useState, type RefObject } from "react";
|
||||
import type { InputBinding } from "../domain/draft-workspace-models.js";
|
||||
import type { StepInputBinding } from "../domain/draft-workspace-models.js";
|
||||
import { SchemaForm } from "../schema-form/SchemaForm.js";
|
||||
import { normalizeSchema } from "../schema-form/schema-field.js";
|
||||
import {
|
||||
@@ -15,7 +15,7 @@ export type CapabilityNodeFormValue = {
|
||||
readonly description?: string | null;
|
||||
readonly retry?: number | null;
|
||||
readonly timeoutSeconds?: number | null;
|
||||
readonly inputBindings: ReadonlyArray<InputBinding> | null;
|
||||
readonly inputBindings: ReadonlyArray<StepInputBinding> | null;
|
||||
readonly inputMap?: Record<string, string> | null;
|
||||
readonly routes?: Record<string, string> | null;
|
||||
readonly bindOutputs?: Record<string, string>;
|
||||
|
||||
@@ -55,4 +55,25 @@ describe("canonical capability form projection", () => {
|
||||
|
||||
expect(projected?.initialInputValue).toEqual({ items: [] });
|
||||
});
|
||||
|
||||
it("preserves a valid expression binding while projecting the legacy form", () => {
|
||||
const expression = {
|
||||
kind: "object",
|
||||
fields: {
|
||||
value: { kind: "path", path: "state.foo" },
|
||||
label: { kind: "literal", value: "wowcool" },
|
||||
},
|
||||
} as const;
|
||||
const projected = canonicalCapabilityFormData(
|
||||
draft({
|
||||
use: "wf.std.concat",
|
||||
input: [{ target: "request", expression }],
|
||||
}),
|
||||
"render",
|
||||
);
|
||||
|
||||
expect(projected?.initialValue.inputBindings).toEqual([
|
||||
{ target: "request", expression },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import type {
|
||||
CapabilityNodeFormValue,
|
||||
} from "./CapabilityNodeForm.js";
|
||||
import type { InputBinding, InputPath, LocalInputPath } from "../domain/draft-workspace-models.js";
|
||||
import type {
|
||||
InputExpression,
|
||||
InputPath,
|
||||
LocalInputPath,
|
||||
StepInputBinding,
|
||||
} from "../domain/draft-workspace-models.js";
|
||||
import type { DraftWorkspace } from "../domain/draft-workspace-models.js";
|
||||
import type { FieldSources } from "../schema-form/schema-values.js";
|
||||
import { formatTOMLPath, parseTOMLPath } from "../schema-form/schema-paths.js";
|
||||
@@ -84,16 +89,48 @@ const inputPath = (value: unknown): value is InputPath =>
|
||||
value.parts.every((part): part is string => typeof part === "string")
|
||||
);
|
||||
|
||||
const inputBinding = (value: JsonRecord): InputBinding | null => {
|
||||
const jsonValue = (value: unknown): boolean => {
|
||||
if (
|
||||
value === null ||
|
||||
typeof value === "boolean" ||
|
||||
(typeof value === "number" && Number.isFinite(value)) ||
|
||||
typeof value === "string"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (Array.isArray(value)) return value.every(jsonValue);
|
||||
return isRecord(value) && Object.values(value).every(jsonValue);
|
||||
};
|
||||
|
||||
const inputExpression = (value: unknown): value is InputExpression => {
|
||||
if (!isRecord(value) || typeof value.kind !== "string") return false;
|
||||
switch (value.kind) {
|
||||
case "literal":
|
||||
return jsonValue(value.value);
|
||||
case "path":
|
||||
return inputPath(value.path);
|
||||
case "array":
|
||||
return Array.isArray(value.items) && value.items.every(inputExpression);
|
||||
case "object":
|
||||
return isRecord(value.fields) && Object.values(value.fields).every(inputExpression);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const inputBinding = (value: JsonRecord): StepInputBinding | null => {
|
||||
if (!localInputPath(value.target)) return null;
|
||||
if (inputPath(value.path)) return { target: value.target, path: value.path };
|
||||
if ("value" in value) return { target: value.target, value: value.value };
|
||||
if (inputExpression(value.expression)) {
|
||||
return { target: value.target, expression: value.expression };
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const bindingFormData = (
|
||||
capabilityName: string,
|
||||
inputBindings: ReadonlyArray<InputBinding>,
|
||||
inputBindings: ReadonlyArray<StepInputBinding>,
|
||||
): Omit<CanonicalCapabilityFormData, "initialValue"> => {
|
||||
const initialInputSources: Record<string, FieldSources[string]> = {};
|
||||
let initialInputValue: unknown = undefined;
|
||||
@@ -125,10 +162,14 @@ const formDataFromValue = (
|
||||
|
||||
const formDataFromBindings = (
|
||||
capabilityName: string,
|
||||
inputBindings: ReadonlyArray<InputBinding>,
|
||||
inputBindings: ReadonlyArray<StepInputBinding>,
|
||||
initialValue: Partial<CapabilityNodeFormValue>,
|
||||
): CanonicalCapabilityFormData => {
|
||||
return { ...bindingFormData(capabilityName, inputBindings), initialValue };
|
||||
const preservesExpressions = inputBindings.some((binding) => "expression" in binding);
|
||||
return {
|
||||
...bindingFormData(capabilityName, inputBindings),
|
||||
initialValue: preservesExpressions ? { ...initialValue, inputBindings } : initialValue,
|
||||
};
|
||||
};
|
||||
|
||||
export const capabilityFormDataFromValue = (
|
||||
@@ -156,7 +197,7 @@ export const canonicalCapabilityFormData = (
|
||||
? { timeoutSeconds }
|
||||
: {}),
|
||||
} satisfies Partial<CapabilityNodeFormValue>;
|
||||
const inputBindings: InputBinding[] = [];
|
||||
const inputBindings: StepInputBinding[] = [];
|
||||
const input = step.input;
|
||||
if (Array.isArray(input)) {
|
||||
for (const rawBinding of input) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
DraftWorkspace,
|
||||
InputBinding,
|
||||
OutputBinding,
|
||||
StepInputBinding,
|
||||
} from "../domain/draft-workspace-models.js";
|
||||
import type { DraftAuthoringClient } from "../domain/draft-authoring-client.js";
|
||||
import type { DraftWorkspaceClient } from "../domain/draft-workspace-client.js";
|
||||
@@ -18,9 +19,10 @@ import type { CapabilitySetupPatch } from "./selected-step-dataflow.js";
|
||||
import { useDraftAuthoring } from "./useDraftAuthoring.js";
|
||||
|
||||
vi.mock("../context.js", () => ({ useConsoleWorkspace: vi.fn() }));
|
||||
vi.mock("../domain/draft-authoring-client.js", () => ({
|
||||
createDraftAuthoringClient: vi.fn(),
|
||||
}));
|
||||
vi.mock("../domain/draft-authoring-client.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../domain/draft-authoring-client.js")>();
|
||||
return { ...actual, createDraftAuthoringClient: vi.fn() };
|
||||
});
|
||||
vi.mock("../domain/draft-workspace-client.js", () => ({
|
||||
createDraftWorkspaceClient: vi.fn(),
|
||||
}));
|
||||
@@ -613,6 +615,37 @@ describe("useDraftAuthoring", () => {
|
||||
expect(result.current.draft).toBe(canonical);
|
||||
});
|
||||
|
||||
it("submits a recursive expression through the selected-step controller", async () => {
|
||||
const initial = workspace({ revision: 7 });
|
||||
const canonical = workspace({ revision: 8 });
|
||||
const bindings = [
|
||||
{
|
||||
target: "request",
|
||||
expression: {
|
||||
kind: "object",
|
||||
fields: {
|
||||
value: { kind: "path", path: "state.foo" },
|
||||
},
|
||||
},
|
||||
},
|
||||
] satisfies ReadonlyArray<StepInputBinding>;
|
||||
setStepInputBindings.mockResolvedValue(canonical);
|
||||
const { result } = renderHook(() => useDraftAuthoring({
|
||||
draft: initial,
|
||||
initialSelection: { kind: "node", nodeId: "render" },
|
||||
}));
|
||||
|
||||
await act(async () => result.current.setStepInputs(bindings));
|
||||
|
||||
expect(setStepInputBindings).toHaveBeenCalledWith({
|
||||
workspaceId: "draft-report",
|
||||
revision: 7,
|
||||
stepId: "render",
|
||||
bindings,
|
||||
});
|
||||
expect(result.current.draft).toBe(canonical);
|
||||
});
|
||||
|
||||
it("submits ordered output bindings and commits the returned draft", async () => {
|
||||
const initial = workspace({ revision: 4 });
|
||||
const canonical = workspace({ revision: 5 });
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useConsoleWorkspace } from "../context.js";
|
||||
import {
|
||||
createDraftAuthoringClient,
|
||||
copyStepInputBinding,
|
||||
type DraftAuthoringClient,
|
||||
} from "../domain/draft-authoring-client.js";
|
||||
import {
|
||||
@@ -10,8 +11,8 @@ import {
|
||||
} from "../domain/draft-workspace-client.js";
|
||||
import type {
|
||||
DraftWorkspace,
|
||||
InputBinding,
|
||||
OutputBinding,
|
||||
StepInputBinding,
|
||||
} from "../domain/draft-workspace-models.js";
|
||||
import type { CapabilityNodeFormValue } from "./CapabilityNodeForm.js";
|
||||
import type { RouteFormValue } from "./RouteForm.js";
|
||||
@@ -35,7 +36,7 @@ export interface DraftAuthoringController {
|
||||
readonly preservedCapabilityForm: PreservedCapabilityForm;
|
||||
readonly addCapability: (input: CapabilityNodeFormValue) => Promise<void>;
|
||||
readonly updateCapability: (input: CapabilityNodeFormValue) => Promise<void>;
|
||||
readonly setStepInputs: (bindings: ReadonlyArray<InputBinding>) => Promise<void>;
|
||||
readonly setStepInputs: (bindings: ReadonlyArray<StepInputBinding>) => Promise<void>;
|
||||
readonly setStepOutputs: (bindings: ReadonlyArray<OutputBinding>) => Promise<void>;
|
||||
readonly updateSetup: (patch: CapabilitySetupPatch) => Promise<void>;
|
||||
readonly setRoute: (input: RouteFormValue) => Promise<void>;
|
||||
@@ -100,7 +101,7 @@ type LastSubmission =
|
||||
| {
|
||||
readonly kind: "inputs";
|
||||
readonly targetStepId: string;
|
||||
readonly bindings: ReadonlyArray<InputBinding>;
|
||||
readonly bindings: ReadonlyArray<StepInputBinding>;
|
||||
}
|
||||
| {
|
||||
readonly kind: "outputs";
|
||||
@@ -160,38 +161,6 @@ const mutationKey = (kind: string, input: unknown, revision: number): string =>
|
||||
return `${kind}:${revision}:${encoded ?? "undefined"}`;
|
||||
};
|
||||
|
||||
const copyJsonValue = (value: unknown): unknown => {
|
||||
if (Array.isArray(value)) return value.map(copyJsonValue);
|
||||
if (value !== null && typeof value === "object") {
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([key, nestedValue]) => [key, copyJsonValue(nestedValue)]),
|
||||
);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const copyInputBinding = (binding: InputBinding): InputBinding => {
|
||||
if ("path" in binding) {
|
||||
return {
|
||||
path:
|
||||
typeof binding.path === "string"
|
||||
? binding.path
|
||||
: { root: binding.path.root, parts: [...binding.path.parts] },
|
||||
target:
|
||||
typeof binding.target === "string"
|
||||
? binding.target
|
||||
: { root: binding.target.root, parts: [...binding.target.parts] },
|
||||
};
|
||||
}
|
||||
return {
|
||||
target:
|
||||
typeof binding.target === "string"
|
||||
? binding.target
|
||||
: { root: binding.target.root, parts: [...binding.target.parts] },
|
||||
value: copyJsonValue(binding.value),
|
||||
};
|
||||
};
|
||||
|
||||
const copyOutputBinding = (binding: OutputBinding): OutputBinding => ({
|
||||
source:
|
||||
typeof binding.source === "string"
|
||||
@@ -204,8 +173,8 @@ const copyOutputBinding = (binding: OutputBinding): OutputBinding => ({
|
||||
});
|
||||
|
||||
const copyInputBindings = (
|
||||
bindings: ReadonlyArray<InputBinding>,
|
||||
): ReadonlyArray<InputBinding> => bindings.map(copyInputBinding);
|
||||
bindings: ReadonlyArray<StepInputBinding>,
|
||||
): ReadonlyArray<StepInputBinding> => bindings.map(copyStepInputBinding);
|
||||
|
||||
const copyOutputBindings = (
|
||||
bindings: ReadonlyArray<OutputBinding>,
|
||||
@@ -567,7 +536,7 @@ export const useDraftAuthoring = ({
|
||||
const submitStepInputs = useCallback(
|
||||
(
|
||||
targetStepId: string,
|
||||
bindings: ReadonlyArray<InputBinding>,
|
||||
bindings: ReadonlyArray<StepInputBinding>,
|
||||
allowTargetSelectionChange = false,
|
||||
): Promise<void> => {
|
||||
const submittedBindings = copyInputBindings(bindings);
|
||||
@@ -629,7 +598,7 @@ export const useDraftAuthoring = ({
|
||||
);
|
||||
|
||||
const setStepInputs = useCallback(
|
||||
(bindings: ReadonlyArray<InputBinding>): Promise<void> => {
|
||||
(bindings: ReadonlyArray<StepInputBinding>): Promise<void> => {
|
||||
const targetStepId = selectedStepId();
|
||||
return targetStepId === null
|
||||
? missingSelectedStep()
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
type CreateEmptyDraftInput,
|
||||
type CreateFromCapabilityInput,
|
||||
type InputBinding,
|
||||
type StepInputBinding,
|
||||
type InputPathBinding,
|
||||
type InputValueBinding,
|
||||
type OutputBinding,
|
||||
@@ -476,4 +477,59 @@ describe("DraftAuthoringClient", () => {
|
||||
decodeDraftWorkspace,
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves recursive expression bindings when sending node-local inputs", async () => {
|
||||
const { executor: writeExecutor, run } = createExecutor();
|
||||
const client = createDraftAuthoringClient(writeExecutor);
|
||||
const bindings = [
|
||||
{
|
||||
target: "request",
|
||||
expression: {
|
||||
kind: "object",
|
||||
fields: {
|
||||
items: {
|
||||
kind: "array",
|
||||
items: [
|
||||
{ kind: "path", path: "state.foo" },
|
||||
{ kind: "literal", value: "wowcool" },
|
||||
],
|
||||
},
|
||||
separator: { kind: "literal", value: " " },
|
||||
},
|
||||
},
|
||||
},
|
||||
] satisfies ReadonlyArray<StepInputBinding>;
|
||||
|
||||
await client.setStepInputBindings({
|
||||
workspaceId: "report",
|
||||
revision: 7,
|
||||
stepId: "concat",
|
||||
bindings,
|
||||
});
|
||||
|
||||
expect(run).toHaveBeenCalledWith(
|
||||
"workflow.draft_workspaces.set_step_input_bindings",
|
||||
{
|
||||
workspace_id: "report",
|
||||
revision: 7,
|
||||
step_id: "concat",
|
||||
bindings,
|
||||
},
|
||||
decodeDraftWorkspace,
|
||||
);
|
||||
const sentParams = run.mock.calls[0]?.[1] as {
|
||||
readonly bindings: ReadonlyArray<StepInputBinding>;
|
||||
};
|
||||
const sourceBinding = bindings[0];
|
||||
const sentBinding = sentParams.bindings[0];
|
||||
if (sourceBinding === undefined || sentBinding === undefined) {
|
||||
throw new Error("expected the recursive binding to be sent");
|
||||
}
|
||||
expect(sentParams.bindings).not.toBe(bindings);
|
||||
expect(sentBinding).not.toBe(sourceBinding);
|
||||
if (!("expression" in sourceBinding) || !("expression" in sentBinding)) {
|
||||
throw new Error("expected an expression binding");
|
||||
}
|
||||
expect(sentBinding.expression).not.toBe(sourceBinding.expression);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,8 +5,12 @@ import {
|
||||
type CreateEmptyDraftInput,
|
||||
type CreateFromCapabilityInput,
|
||||
type DraftWorkspace,
|
||||
type InputBinding,
|
||||
type JsonValue,
|
||||
type InputExpression,
|
||||
type InputPath,
|
||||
type LocalInputPath,
|
||||
type OutputBinding,
|
||||
type StepInputBinding,
|
||||
type SetDraftRouteInput,
|
||||
type SetStepInputBindingsInput,
|
||||
type SetStepOutputBindingsInput,
|
||||
@@ -56,7 +60,9 @@ const ifDefined = <T>(
|
||||
if (value !== undefined) target[key] = value;
|
||||
};
|
||||
|
||||
const copyJsonValue = (value: unknown): unknown => {
|
||||
function copyJsonValue(value: JsonValue): JsonValue;
|
||||
function copyJsonValue(value: unknown): unknown;
|
||||
function copyJsonValue(value: unknown): unknown {
|
||||
if (Array.isArray(value)) return value.map(copyJsonValue);
|
||||
if (value !== null && typeof value === "object") {
|
||||
return Object.fromEntries(
|
||||
@@ -67,27 +73,57 @@ const copyJsonValue = (value: unknown): unknown => {
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
const copyInputPath = (path: InputPath): InputPath =>
|
||||
typeof path === "string" ? path : { root: path.root, parts: [...path.parts] };
|
||||
|
||||
const copyLocalInputPath = (path: LocalInputPath): LocalInputPath =>
|
||||
typeof path === "string" ? path : { root: path.root, parts: [...path.parts] };
|
||||
|
||||
// Keep this copy explicit so adding a canonical expression variant is a type error
|
||||
// instead of silently dropping data during a mutation or reapply.
|
||||
export const copyInputExpression = (
|
||||
expression: InputExpression,
|
||||
): InputExpression => {
|
||||
switch (expression.kind) {
|
||||
case "literal":
|
||||
return { kind: "literal", value: copyJsonValue(expression.value) };
|
||||
case "path":
|
||||
return { kind: "path", path: copyInputPath(expression.path) };
|
||||
case "array":
|
||||
return { kind: "array", items: expression.items.map(copyInputExpression) };
|
||||
case "object":
|
||||
return {
|
||||
kind: "object",
|
||||
fields: Object.fromEntries(
|
||||
Object.entries(expression.fields).map(([key, value]) => [
|
||||
key,
|
||||
copyInputExpression(value),
|
||||
]),
|
||||
),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const copyInputBinding = (binding: InputBinding): InputBinding => {
|
||||
export const copyStepInputBinding = (
|
||||
binding: StepInputBinding,
|
||||
): StepInputBinding => {
|
||||
if ("path" in binding) {
|
||||
return {
|
||||
path:
|
||||
typeof binding.path === "string"
|
||||
? binding.path
|
||||
: { root: binding.path.root, parts: [...binding.path.parts] },
|
||||
target:
|
||||
typeof binding.target === "string"
|
||||
? binding.target
|
||||
: { root: binding.target.root, parts: [...binding.target.parts] },
|
||||
path: copyInputPath(binding.path),
|
||||
target: copyLocalInputPath(binding.target),
|
||||
};
|
||||
}
|
||||
if ("value" in binding) {
|
||||
return {
|
||||
target: copyLocalInputPath(binding.target),
|
||||
value: copyJsonValue(binding.value),
|
||||
};
|
||||
}
|
||||
return {
|
||||
target:
|
||||
typeof binding.target === "string"
|
||||
? binding.target
|
||||
: { root: binding.target.root, parts: [...binding.target.parts] },
|
||||
value: copyJsonValue(binding.value),
|
||||
target: copyLocalInputPath(binding.target),
|
||||
expression: copyInputExpression(binding.expression),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -103,11 +139,11 @@ const copyOutputBinding = (binding: OutputBinding): OutputBinding => ({
|
||||
});
|
||||
|
||||
const copyInputBindings = (
|
||||
bindings: ReadonlyArray<InputBinding> | null | undefined,
|
||||
): InputBinding[] | null | undefined =>
|
||||
bindings: ReadonlyArray<StepInputBinding> | null | undefined,
|
||||
): StepInputBinding[] | null | undefined =>
|
||||
bindings === undefined || bindings === null
|
||||
? bindings
|
||||
: bindings.map(copyInputBinding);
|
||||
: bindings.map(copyStepInputBinding);
|
||||
|
||||
const copyOutputBindings = (
|
||||
bindings: ReadonlyArray<OutputBinding> | null | undefined,
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
type AddCapabilityStepInput,
|
||||
type CreateEmptyDraftInput,
|
||||
type InputBinding,
|
||||
type InputExpression,
|
||||
type StepInputBinding,
|
||||
} from "./draft-workspace-models.js";
|
||||
|
||||
const summary = {
|
||||
@@ -35,6 +37,28 @@ describe("draft workspace models", () => {
|
||||
expect(invalidInput).toBeDefined();
|
||||
});
|
||||
|
||||
it("models a recursive node-local expression without widening workflow outputs", () => {
|
||||
const expression = {
|
||||
kind: "object",
|
||||
fields: {
|
||||
items: {
|
||||
kind: "array",
|
||||
items: [
|
||||
{ kind: "path", path: "state.foo" },
|
||||
{ kind: "literal", value: "wowcool" },
|
||||
],
|
||||
},
|
||||
},
|
||||
} satisfies InputExpression;
|
||||
const binding = {
|
||||
target: "request",
|
||||
expression,
|
||||
} satisfies StepInputBinding;
|
||||
|
||||
expect(binding.expression.kind).toBe("object");
|
||||
expect(binding.expression.fields.items.kind).toBe("array");
|
||||
});
|
||||
|
||||
it("exposes camelCase inputs for draft authoring", () => {
|
||||
const emptyInput = {
|
||||
workspaceId: "draft-report",
|
||||
|
||||
@@ -2,6 +2,14 @@ import * as v from "valibot";
|
||||
|
||||
export type JsonObject = Record<string, unknown>;
|
||||
|
||||
export type JsonValue =
|
||||
| null
|
||||
| boolean
|
||||
| number
|
||||
| string
|
||||
| ReadonlyArray<JsonValue>
|
||||
| { readonly [key: string]: JsonValue };
|
||||
|
||||
export type InputPath =
|
||||
| string
|
||||
| {
|
||||
@@ -28,6 +36,39 @@ export type InputValueBinding = {
|
||||
|
||||
export type InputBinding = InputPathBinding | InputValueBinding;
|
||||
|
||||
export type LiteralExpression = {
|
||||
readonly kind: "literal";
|
||||
readonly value: JsonValue;
|
||||
};
|
||||
|
||||
export type PathExpression = {
|
||||
readonly kind: "path";
|
||||
readonly path: InputPath;
|
||||
};
|
||||
|
||||
export type ArrayExpression = {
|
||||
readonly kind: "array";
|
||||
readonly items: ReadonlyArray<InputExpression>;
|
||||
};
|
||||
|
||||
export type ObjectExpression = {
|
||||
readonly kind: "object";
|
||||
readonly fields: Readonly<Record<string, InputExpression>>;
|
||||
};
|
||||
|
||||
export type InputExpression =
|
||||
| LiteralExpression
|
||||
| PathExpression
|
||||
| ArrayExpression
|
||||
| ObjectExpression;
|
||||
|
||||
export type InputExpressionBinding = {
|
||||
readonly target: LocalInputPath;
|
||||
readonly expression: InputExpression;
|
||||
};
|
||||
|
||||
export type StepInputBinding = InputBinding | InputExpressionBinding;
|
||||
|
||||
export type StatePath =
|
||||
| string
|
||||
| { readonly root: "state"; readonly parts: string[] };
|
||||
@@ -41,7 +82,7 @@ export type SetStepInputBindingsInput = {
|
||||
readonly workspaceId: string;
|
||||
readonly revision: number;
|
||||
readonly stepId: string;
|
||||
readonly bindings: ReadonlyArray<InputBinding>;
|
||||
readonly bindings: ReadonlyArray<StepInputBinding>;
|
||||
};
|
||||
|
||||
export type SetStepOutputBindingsInput = {
|
||||
@@ -85,7 +126,7 @@ export type AddCapabilityStepInput = {
|
||||
readonly routeFromOutcome?: string;
|
||||
readonly routes?: Record<string, string> | null;
|
||||
readonly inputMap?: Record<string, string> | null;
|
||||
readonly inputBindings?: ReadonlyArray<InputBinding> | null;
|
||||
readonly inputBindings?: ReadonlyArray<StepInputBinding> | null;
|
||||
readonly bindOutputs?: Record<string, string>;
|
||||
readonly description?: string | null;
|
||||
readonly retry?: number | null;
|
||||
@@ -98,7 +139,7 @@ export type UpdateCapabilityStepInput = {
|
||||
readonly stepId: string;
|
||||
readonly update: {
|
||||
readonly description?: string | null;
|
||||
readonly input?: ReadonlyArray<InputBinding> | null;
|
||||
readonly input?: ReadonlyArray<StepInputBinding> | null;
|
||||
readonly retry?: number | null;
|
||||
readonly timeoutSeconds?: number | null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user