Error Handling & Retries
By default, failed steps (flow.do, flow.request, flow.start, flow.dialog initiation) are retried a few times with a constant delay. You can customize this behavior globally for a workflow or per-step.
Retry Configuration (RetryStrategy):
interface RetryStrategy { limit?: number | false; // Max attempts (false = infinite, 0 = no retries). Default: 5 delay?: Duration; // Delay between retries (ms or string like '1 minute'). Default: '1 minute' backoff?: 'constant' | 'linear' | 'exponential'; // Backoff strategy. Default: 'constant'}Applying Retries:
-
Workflow Defaults: Set in the
defineWorkflowconfiguration under thedefaultskey. See Defining Workflows.defineWorkflow({// ... other configdefaults: { retries: { limit: 3, delay: '30 seconds', backoff: 'exponential' } },},async (flow) => {/* ... */},); -
Per-Step Options: Override defaults by passing
retriesin theoptionsargument of an activity.await flow.do('call-flaky-api',{ retries: { limit: 10, delay: '5 seconds' } },async ({ use }) => {/* ... */},);// Disable retries for a specific stepawait flow.do('critical-once-only-task',{ retries: false }, // Equivalent to { retries: { limit: 0 } }async ({ use }) => {/* ... */},);
Choose retry strategies appropriate for the type of failure expected (e.g., exponential backoff for overloaded services).
IdentityFlow provides structured error handling through custom error classes and utility functions exported from @identity-flow/sdk. This allows for more precise error management than relying solely on standard JavaScript Error objects.
Key Exports from @identity-flow/sdk (related to errors):
- Error Classes:
WorkflowError: Base class for workflow-related errors.NonRetryableError: For errors that should not trigger the retry mechanism.ValidationError: For issues related to data validation (often thrown automatically by schema checks, but can be used manually).TimeoutError: A specificNonRetryableErrorfor timeouts.AssertionError: Thrown byflow.assert()or can be used for custom assertions.LockedError: Indicates a resource is locked.
- Type Guard Functions:
isNonRetryableError(error: unknown): error is NonRetryableErrorisValidationError(error: unknown): error is ValidationErrorisLockedError(error: unknown): error is LockedErrorisAbortError(error: unknown): error is AbortError(for handlingAbortSignalrelated errors)
- Other Utilities:
throwIfAborted(value: unknown): Checks and throws if anAbortSignalhas been aborted.
Throwing Specific Errors:
When an activity function needs to signal a failure, throwing an instance of these specific error classes provides more context to the engine and to any catching logic.
import { NonRetryableError, ValidationError, defineWorkflow } from '@identity-flow/sdk';
// ... in an activity function ...if (criticalConditionFailed) { throw new NonRetryableError('Critical condition failed, will not retry.', { code: 'CRITICAL_FAILURE', });}if (userInputInvalid) { throw new ValidationError('Invalid user input provided.', { issues: [{ message: 'Email is required', path: ['email'] }], });}Catching and Checking Errors:
You can use standard try...catch blocks and the provided type guards to handle errors gracefully and specifically.
import { defineWorkflow, isNonRetryableError, isValidationError } from '@identity-flow/sdk';
// ... in execute function or an activity ...try { await flow.do('risky-operation', async () => { /* ... may throw custom errors ... */ });} catch (error) { if (isValidationError(error)) { flow.warn('Validation failed during risky operation:', error.message, error.issues); // Handle validation error, maybe inform user or return specific result return { status: 'VALIDATION_ERROR', issues: error.issues }; } else if (isNonRetryableError(error)) { flow.error('Non-retryable error in risky operation:', error.message, { code: error.code }); // Perform cleanup for non-retryable failure await flow.do('cleanup-non-retryable', async () => { /* ... */ }); return { status: 'FATAL_ERROR', reason: error.message }; } else { // Generic error, might be retried by the engine or bubble up flow.error('Risky operation failed:', error); throw error; // Re-throw if you want the engine to handle retries or fail the workflow }}- Unrecoverable Errors: If an error occurs outside of a configured retry mechanism (e.g., a
NonRetryableErroris thrown, or after all retries for a retryable error are exhausted), the workflow instance will typically transition to theFAILEDstate.
Using these specific error types and utilities enhances the robustness and debuggability of your workflows.
The flow.assert(condition, messageOrError) utility is a convenient way to perform simple precondition checks within your workflow logic. If the condition is false, it throws an AssertionError (which is a NonRetryableError) with the given message, or throws the provided error object.
Signature:
flow.assert(condition: any, messageOrError?: string | ErrorDetails | Error): asserts condition;Example:
async (flow) => { const { orderId, items } = flow.params;
flow.assert(orderId, 'Order ID is required.'); flow.assert(items && items.length > 0, { message: 'Order must contain items.', code: 'EMPTY_ORDER', });
// ... rest of the workflow logic ...};If an assertion fails, the workflow will typically terminate unless the AssertionError is caught and handled specifically.
Complete your understanding of workflow development with these topics: