import { FORM_FIELDS_SCHEMA, getFormFieldDefaultValue, isFormFieldValueValid, type FormField, type FormFieldValues, } from "@automate.ax/catalog" import type { Encodable } from "@automate.ax/codec" import { getAutomationPlanningContext, runAutomationFunction } from "./runtime" import type { Synchronous } from "./signal-protocol" import type { AutomationDescriptor } from "./automation-descriptor" export type { AutomationDescriptor, AutomationSelector, } from "./automation-descriptor" /** Resolves one field declaration to its author-facing primitive type. */ export type AutomationParameterValues = FormFieldValues export interface ParameterizedAutomationContext< TFields extends readonly FormField[], > { parameters: AutomationParameterValues } export interface ParameterizedAutomationOptions< TFields extends readonly FormField[], > { parameters: TFields } /** * Defines a top-level automation without parameters. * * @param description - Human-readable summary of the automation. * @param fn - Automation body to run in the active runtime phase. */ export function automation unknown>( description: AutomationDescriptor["description"], fn: Synchronous extends never ? never : TFunction, ): AutomationDescriptor /** * Defines a top-level automation with project-specific parameters. * * @param description - Human-readable summary of the automation. * @param options - Parameter field declarations. * @param fn - Automation body receiving resolved parameter values. */ export function automation< const TFields extends readonly FormField[], TFunction extends ( context: ParameterizedAutomationContext, ) => unknown, >( description: AutomationDescriptor["description"], options: ParameterizedAutomationOptions, fn: Synchronous extends never ? never : TFunction, ): AutomationDescriptor export function automation( description: AutomationDescriptor["description"], optionsOrFunction: | ParameterizedAutomationOptions | (() => unknown), parameterizedFunction?: (context: { parameters: Record }) => unknown, ): AutomationDescriptor { const parameters = typeof optionsOrFunction === "function" ? [] : FORM_FIELDS_SCHEMA.parse(optionsOrFunction.parameters) return Object.assign( (assignedValues: Readonly> = {}) => { const { issues, values } = resolveAutomationParameters( parameters, assignedValues, ) const planning = getAutomationPlanningContext() if (planning) { planning.parameterIssues = issues planning.parameters = parameters if (issues.length > 0) return } else if (issues.length > 0) { throw new Error( `Automation parameters are missing or invalid: ${issues.join(", ")}.`, ) } if (typeof optionsOrFunction === "function") { return runAutomationFunction(optionsOrFunction) } return runAutomationFunction(() => parameterizedFunction!({ parameters: values }), ) }, { description, parameters }, ) } /** * Resolves assigned values and code defaults for one automation invocation. * * @param fields - Declared parameter fields. * @param assignedValues - Invocation-scoped stored assignments. */ function resolveAutomationParameters( fields: readonly FormField[], assignedValues: Readonly>, ) { const entries = fields.map((field) => { const assigned = Object.hasOwn(assignedValues, field.name) ? assignedValues[field.name] : undefined const value = assigned !== undefined ? assigned : getFormFieldDefaultValue(field) return isFormFieldValueValid(field, value) ? ([field.name, value] as const) : undefined }) return { issues: fields.flatMap((field, index) => entries[index] ? [] : [field.name], ), values: Object.fromEntries(entries.filter((entry) => entry !== undefined)), } }