Skip to content

Custom Step Types

For common operations or complex logic that you want to reuse across multiple workflows or steps, you can define custom step functions. These functions can encapsulate specific behaviors, include their own validation, and contribute to cleaner, more modular workflow definitions.

Example: Generic Validation Step Function

This example creates a higher-order function validateStep that takes a schema (e.g., a Valibot schema) and returns an asynchronous function suitable for use as a flow.do() task. This task will parse and validate input data according to the provided schema. See the Data Validation page for more on integrating validation schemas.

import { type Schema } from '@identity-flow/sdk/valibot';
// Assuming Valibot or similar schema type
// TODO: use ValidationError from @identity-flow/sdk
// Define a custom error for validation failures if needed
class ValidationError extends Error {
constructor(public details: any) {
super('Validation failed');
this.name = 'ValidationError';
}
}
const validateStep =
<T>(schema: Schema<T>) =>
async (data: unknown): Promise<T> => {
// Replace with your chosen validation library's parsing logic
// For example, using Valibot:
// import { parse } from 'valibot';
// const result = parse(schema, data);
// return result;
// Placeholder for generic schema validation logic:
const result = await schema.parse(data); // Example with a parse method on schema
if (!result.success) {
// Adjust based on your schema library's result structure
throw new ValidationError(result.error);
}
return result.data;
};
// Example usage in a workflow:
export default defineWorkflow('validated-process', async (flow) => {
// Define an input schema (e.g., using Valibot)
// const InputSchema = v.object({ id: v.string(), quantity: v.number() });
try {
// Use the custom validation step
// const data = await flow.do('validate input', validateStep(InputSchema))(flow.params);
// For demonstration, assuming flow.params is directly passed and InputSchema is defined elsewhere
const validatedData = await flow.do(
'validate input',
validateStep(flow.params.InputSchema),
)(flow.params.dataToValidate);
// Continue with validatedData
flow.log('Input validated successfully', validatedData);
// ... further processing
} catch (error) {
if (error instanceof ValidationError) {
flow.error('Input validation failed', { details: error.details });
} else {
flow.error('An unexpected error occurred during validation', { message: error.message });
}
throw error; // Fail the workflow
}
});

Benefits:

  • Reusability: Write complex logic once and use it in many places.
  • Abstraction: Hide implementation details of common tasks.
  • Testability: Custom step functions can often be unit-tested in isolation.
  • Clarity: Workflows become easier to read by composing them from well-defined custom steps.

Consider Dynamic Workflows for adapting behavior at runtime.