Type-Safe Development
Define workflows with strong typing for parameters, variables, and results, enhancing reliability and maintainability.
Type-Safe Development
Define workflows with strong typing for parameters, variables, and results, enhancing reliability and maintainability.
SDK Type Exports
Utilize the comprehensive types exported by @identity-flow/sdk for building robust integrations.
IdentityFlow is built with TypeScript, providing strong typing throughout the SDK to help you catch errors early and build more reliable workflows.
import { defineWorkflow } from '@identity-flow/sdk';
// Define interfaces for your specific workflow's parameters and expected resultinterface OrderParams {id: string;items: Array<{productId: string;quantity: number;}>;shipping: {address: string;method: 'standard' | 'express';};}
interface ProcessResult {orderId: string;status: 'COMPLETED' | 'PENDING' | 'FAILED'; // Consistent status valuestotal: number;}
// Use generics with defineWorkflow to type params and the final return valueexport default defineWorkflow<OrderParams, ProcessResult>({name: 'process-order'},async (flow) => {// flow.params is automatically typed as OrderParamsconst { id, items, shipping } = flow.params;flow.log(`Processing order ${id} with ${items.length} items.`);
// The final value returned must match ProcessResult const result: ProcessResult = await flow.do('process order step', async () => { const externalResult = await processOrder({ orderId: id, items, shippingMethod: shipping.method });
// Mapping external result to the defined ProcessResult type return { orderId: id, status: externalResult.status === 'success' ? 'COMPLETED' : 'FAILED', total: externalResult.total }; });
return result; // Type checked against ProcessResult }
);import { defineWorkflow } from '@identity-flow/sdk';import * as v from '@identity-flow/sdk/valibot'; // Using built-in Valibot
// Define a schema using a Standard Schema compatible library (like Valibot)const OrderItemSchema = v.object({ productId: v.string(), quantity: v.number([v.positive()])});
const OrderParamsSchema = v.object({ id: v.string([v.uuid()]), items: v.array(OrderItemSchema), customerEmail: v.optional(v.string([v.email()]))});
// Define workflow, providing the schema for input parametersexport default defineWorkflow({ name: 'fulfill-order', version: '1.1.0', schema: OrderParamsSchema // SDK uses this schema to validate and infer types}, async (flow) => {
// flow.params is automatically inferred as the output type of OrderParamsSchema // You get type safety and autocompletion without explicit generics here! const { id, items, customerEmail } = flow.params; flow.log(`Fulfilling order ${id}`);
if (customerEmail) { flow.log(`Notifying customer: ${customerEmail}`) }
// You can also use schemas to type activity results const shippingInfoSchema = v.object({ trackingId: v.string(), carrier: v.string() });
const shippingResult = await flow.do( 'arrange-shipping', { schema: shippingInfoSchema }, // Validate and type the step result async () => { // ... logic to arrange shipping ... return { trackingId: 'ABC123XYZ', carrier: 'FlowEx' }; // Must match schema } );
// shippingResult is typed based on shippingInfoSchema flow.log(`Shipment arranged: ${shippingResult.carrier} / ${shippingResult.trackingId}`);
return { status: 'Fulfilled', tracking: shippingResult.trackingId };});TypeScript’s advanced features can be useful within workflow logic.
Conditional types can help create flexible return types based on inputs or intermediate results within your workflow steps.
// Example: Define a type that varies based on whether data is presenttype StepCompletion<TData> = TData extends undefined ? { status: 'COMPLETED' } : { status: 'COMPLETED'; data: TData };
async function executeOptionalStep<TData>( task: () => Promise<TData | undefined>,): Promise<StepCompletion<TData>> { const result = await task(); return result === undefined ? { status: 'COMPLETED' } : { status: 'COMPLETED', data: result }; // Type safety ensures data is present}
// Usage in workflow:const optionalDataResult = await flow.do('optional-step', async () => { return await executeOptionalStep(async () => { // ... logic that might or might not return data ... return Math.random() > 0.5 ? { value: 123 } : undefined; });});
if ('data' in optionalDataResult) { // TS knows optionalDataResult has a data property here flow.log('Optional step produced data:', optionalDataResult.data.value);}Use the type guard functions exported by the SDK (isNonRetryableError, isValidationError, etc.) within try...catch blocks to safely narrow down error types and handle different failure scenarios appropriately. See Error Handling for more strategies.
import { NonRetryableError, ValidationError, isNonRetryableError, isValidationError,} from '@identity-flow/sdk';
async function handleWorkflowError(error: unknown) { if (isValidationError(error)) { // TypeScript knows `error` is `ValidationError` here console.warn('Workflow encountered validation issues:', error.issues); // Specific handling for validation problems } else if (isNonRetryableError(error)) { // TypeScript knows `error` is `NonRetryableError` here console.error('Workflow encountered a non-retryable error:', error.message, { code: error.code, }); // Specific handling for permanent failures } else { // Handle other unknown or potentially transient errors console.error('An unexpected error occurred during workflow:', error); // Potentially re-throw to let the engine handle retries/failure throw error; }}
// Example usage in workflowtry { await flow.do('critical-step', async () => { /* ... */ });} catch (err) { await handleWorkflowError(err);}Use Generics or Schemas
Define input/output types for workflows using <Params, Result> generics or the schema option for clarity and safety.
Enable Strict Mode
Use strict TypeScript compiler options (tsconfig.json) for maximum compile-time safety.
Define clear TypeScript interfaces for any external services your workflows interact with. Use these interfaces when creating your binding functions for flow.use to ensure type safety during integration.
// Define a clear interface for your external serviceinterface PaymentService { processPayment( amount: number, currency: string, orderId: string, ): Promise<{ transactionId: string; status: 'success' | 'failed' }>;}
// Create a typed binding function that returns an instance implementing the interfaceconst PaymentClientBinding = (): PaymentService => { // In a real scenario, initialize and return your actual payment client instance here console.log('Initializing Payment Client Binding...'); return { processPayment: async (amount, currency, orderId) => { console.log(`Mock charging ${amount} ${currency} for order ${orderId}`); // Simulate API call await new Promise((resolve) => setTimeout(resolve, 50)); return { transactionId: `txn_${Date.now()}`, status: 'success' }; }, };};
// Use the typed binding in your workflowexport default defineWorkflow<{ amount: number; currency: string; orderId: string }>( { name: 'charge-customer' }, async (flow) => { // paymentService is correctly typed as PaymentService const paymentService = flow.use(PaymentClientBinding);
const paymentResult = await flow.do('process-payment-via-service', async () => { // Type checking ensures correct arguments are passed return paymentService.processPayment( flow.params.amount, flow.params.currency, flow.params.orderId, ); });
// paymentResult is correctly typed based on PaymentService interface flow.log(`Payment status: ${paymentResult.status}, ID: ${paymentResult.transactionId}`); return paymentResult; },);Developing Workflows
Explore the main guide on Developing Workflows for detailed API usage.
Data Validation
Learn more about using schemas for validation.
Error Handling
See examples of using type guards in error handling.