We use zod heavily to ensure runtime validations align with our compile-time TS types. I created this helper that leverages zod to validate workflow input
import { z, ZodTypeAny } from 'zod'
export const validatedWorkflowInputHelper = <
TInputSchema extends ZodTypeAny,
TWorkflowReturn
>(
inputSchema: TInputSchema,
workflowFunction: (input: z.infer<TInputSchema>) => Promise<TWorkflowReturn>
): (input: z.infer<TInputSchema>) => Promise<TWorkflowReturn> =>
async input => workflowFunction(inputSchema.parse(input))
Then, a workflow can be created like this
const myWorkflowInputSchema = z.object({ name: z.string })
export const myWorkflow = validatedWorkflowInputHelper(
myWorkflowInputSchema,
({ name }): Promise<string> => `Hello ${name}`
)
It’s not a huge difference, but I believe this is slightly better than the following
const myWorkflowInputSchema = z.object({ name: z.string })
type Input = z.infer<myWorkflowInputSchema>
export const myWorkflow = (input: Input): Promise<string> => {
const validatedInput = myWorkflowInputSchema.parse(input)
return `Hello ${name}`
}
When you’re authoring this function, it can be easy to accidentally use input instead of validatedInput. The validatedWorkflowInputHelper seems like a slightly better DX because the unvalidated input is out of scope. We could also type input as any
, which would technically be more accurate, but then you would lose type safety when starting the workflow from another TS function.
The problem with using validatedWorkflowInputHelper is that the workflow function becomes an anonymous function, so passing the function directly into executeChild()
, for example, results in an error: BadStartChildExecutionAttributes: Required field WorkflowType is not set on command
. We can still pass the string "myWorkflow"
, but then we lose the type safety of options.args.
For now, I’ll move forward with the approach without the helper, but I wonder if anyone has a better approach for typing and validating workflow inputs