Skip to content

TypeScript Integration

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 result
interface 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 values
total: number;
}
// Use generics with defineWorkflow to type params and the final return value
export default defineWorkflow<OrderParams, ProcessResult>(
{
name: 'process-order'
},
async (flow) => {
// flow.params is automatically typed as OrderParams
const { 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
}
);

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 present
type 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 workflow
try {
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 service
interface 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 interface
const 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 workflow
export 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;
},
);