CrystalFlow provides a comprehensive error handling system with typed exceptions and detailed error context.
Error Types
CrystalFlow includes several specialized error types:
ExecutionError Base class for all execution errors
NodeExecutionError Errors during node execution
ValidationError Workflow validation failures
TimeoutError Execution timeout exceeded
CancellationError Execution was cancelled
UserCancelledError User-initiated cancellation
Basic Error Handling
import { Executor , ExecutionError } from '@crystalflow/core' ;
try {
const result = await executor . execute ( workflow );
console . log ( 'Success:' , result );
} catch ( error ) {
if ( error instanceof ExecutionError ) {
console . error ( 'Execution failed:' , error . message );
console . error ( 'Execution ID:' , error . executionId );
console . error ( 'Workflow ID:' , error . workflowId );
} else {
console . error ( 'Unexpected error:' , error );
}
}
Node Execution Errors
When a node fails, a NodeExecutionError is thrown with detailed context:
import { NodeExecutionError } from '@crystalflow/core' ;
try {
const result = await executor . execute ( workflow );
} catch ( error ) {
if ( error instanceof NodeExecutionError ) {
console . error ( 'Node failed:' , {
nodeId: error . nodeId ,
nodeType: error . nodeType ,
executionId: error . executionId ,
workflowId: error . workflowId ,
cause: error . cause // Original error
});
}
}
Error Properties
ID of the node that failed
Type of the node (e.g., 'math.add')
Unique execution identifier
The original error that caused the failure
Validation Errors
Thrown when workflow validation fails:
import { ValidationError } from '@crystalflow/core' ;
try {
const result = await executor . execute ( workflow );
} catch ( error ) {
if ( error instanceof ValidationError ) {
console . error ( 'Workflow validation failed:' , error . message );
// Check specific validation issues
if ( error . message . includes ( 'cycle' )) {
console . error ( 'Workflow contains a circular dependency' );
}
}
}
Timeout Errors
Thrown when execution exceeds the timeout limit:
import { TimeoutError } from '@crystalflow/core' ;
try {
const result = await executor . execute ( workflow , {
timeout: 30000 // 30 seconds
});
} catch ( error ) {
if ( error instanceof TimeoutError ) {
console . error ( `Execution timed out after ${ error . timeout } ms` );
console . error ( `Execution ID: ${ error . executionId } ` );
}
}
Cancellation Errors
Thrown when execution is cancelled:
import { UserCancelledError , CancellationError } from '@crystalflow/core' ;
try {
const result = await executor . execute ( workflow );
} catch ( error ) {
if ( error instanceof UserCancelledError ) {
console . log ( 'User cancelled execution' );
console . log ( 'Reason:' , error . reason );
} else if ( error instanceof CancellationError ) {
console . log ( 'Execution was cancelled' );
console . log ( 'Reason:' , error . reason );
}
}
Error Events
Listen to error events during execution:
executor . on ( 'onNodeError' , ( nodeId , error ) => {
console . error ( `Node ${ nodeId } failed:` , error . message );
// Send to error tracking service
sendToSentry ({
nodeId ,
error ,
timestamp: new Date ()
});
});
executor . on ( 'onError' , ( error ) => {
console . error ( 'Workflow execution error:' , error );
// Notify user
notifyUser ({
type: 'error' ,
message: 'Workflow execution failed' ,
details: error . message
});
});
Node-Level Error Handling
Handle errors within individual nodes:
@ defineNode ({
type: 'http.request' ,
label: 'HTTP Request' ,
category: 'Network'
})
class HttpRequestNode extends Node {
@ Property ({ type: 'string' , label: 'URL' , required: true })
url : string = '' ;
@ Output ({ type: 'any' , label: 'Response' })
response : any ;
@ Output ({ type: 'string' , label: 'Error' , required: false })
error ?: string ;
async execute () {
try {
const res = await fetch ( this . url );
if ( ! res . ok ) {
throw new Error ( `HTTP ${ res . status } : ${ res . statusText } ` );
}
this . response = await res . json ();
} catch ( error ) {
// Log the error
console . error ( `Failed to fetch ${ this . url } :` , error );
// Set error output
this . error = error . message ;
// Re-throw to stop workflow execution
throw new Error ( `HTTP request failed: ${ error . message } ` );
}
}
}
Validation in Nodes
Validate inputs before execution:
execute () {
// Validate required inputs
if ( ! this . url ) {
throw new Error ( 'URL is required' );
}
// Validate format
try {
new URL ( this . url );
} catch {
throw new Error ( 'Invalid URL format' );
}
// Validate value ranges
if ( this . timeout < 1000 || this . timeout > 300000 ) {
throw new Error ( 'Timeout must be between 1 and 300 seconds' );
}
// Validate data types
if ( ! Array . isArray ( this . items )) {
throw new Error ( 'Items must be an array' );
}
// Proceed with execution
this . result = this . processData ();
}
Graceful Degradation
Continue execution with fallback values:
async execute () {
try {
this . data = await this . fetchFromPrimarySource ();
} catch ( primaryError ) {
console . warn ( 'Primary source failed, trying fallback' );
try {
this . data = await this . fetchFromFallbackSource ();
} catch ( fallbackError ) {
// Both failed - use default
console . error ( 'All sources failed, using default data' );
this . data = this . getDefaultData ();
}
}
}
Error Recovery
Implement retry logic with exponential backoff:
async execute () {
const maxRetries = 3 ;
let lastError : Error ;
for ( let attempt = 1 ; attempt <= maxRetries ; attempt ++ ) {
try {
this . result = await this . fetchData ();
return ; // Success!
} catch ( error ) {
lastError = error ;
console . warn ( `Attempt ${ attempt } failed:` , error . message );
if ( attempt < maxRetries ) {
// Exponential backoff: 1s, 2s, 4s
const delay = 1000 * Math . pow ( 2 , attempt - 1 );
console . log ( `Retrying in ${ delay } ms...` );
await this . sleep ( delay );
}
}
}
// All retries failed
throw new Error ( `Failed after ${ maxRetries } attempts: ${ lastError . message } ` );
}
private sleep ( ms : number ): Promise < void > {
return new Promise ( resolve => setTimeout ( resolve , ms ));
}
Complete Error Handling Example
import {
Executor ,
ExecutionError ,
NodeExecutionError ,
ValidationError ,
TimeoutError ,
UserCancelledError
} from '@crystalflow/core' ;
async function executeWorkflowWithErrorHandling ( workflow ) {
const executor = new Executor ();
// Set up error event listeners
executor . on ( 'onNodeError' , ( nodeId , error ) => {
console . error ( `❌ Node ${ nodeId } failed:` , error . message );
// Log to error tracking service
logToErrorTracking ({
level: 'error' ,
nodeId ,
error: error . message ,
stack: error . stack
});
});
executor . on ( 'onError' , ( error ) => {
console . error ( '❌ Workflow execution failed:' , error . message );
// Notify administrators
sendAdminNotification ({
subject: 'Workflow Execution Failed' ,
body: error . message
});
});
// Execute with comprehensive error handling
try {
const result = await executor . execute ( workflow , {
timeout: 60000 ,
variables: { apiKey: process . env . API_KEY }
});
if ( result . status === 'success' ) {
console . log ( '✅ Workflow executed successfully' );
return result ;
}
} catch ( error ) {
// Handle specific error types
if ( error instanceof NodeExecutionError ) {
console . error ( 'Node execution failed:' , {
node: error . nodeId ,
type: error . nodeType ,
message: error . message
});
// Attempt recovery or fallback
return handleNodeFailure ( error );
} else if ( error instanceof ValidationError ) {
console . error ( 'Workflow validation failed:' , error . message );
// Show validation errors to user
showValidationErrors ( error );
} else if ( error instanceof TimeoutError ) {
console . error ( `Execution timed out after ${ error . timeout } ms` );
// Offer to retry with longer timeout
return promptRetryWithTimeout ( workflow );
} else if ( error instanceof UserCancelledError ) {
console . log ( 'User cancelled the execution' );
// Clean up and notify
cleanupCancelledExecution ( error . executionId );
} else if ( error instanceof ExecutionError ) {
console . error ( 'General execution error:' , error . message );
} else {
console . error ( 'Unexpected error:' , error );
throw error ; // Re-throw unknown errors
}
}
}
UI Error Display
Show errors to users in React:
import React , { useState } from 'react' ;
import { Executor , NodeExecutionError } from '@crystalflow/core' ;
function WorkflowExecutor ({ workflow }) {
const [ error , setError ] = useState ( null );
const [ nodeErrors , setNodeErrors ] = useState ({});
const execute = async () => {
setError ( null );
setNodeErrors ({});
const executor = new Executor ();
executor . on ( 'onNodeError' , ( nodeId , error ) => {
setNodeErrors ( prev => ({
... prev ,
[nodeId]: error . message
}));
});
try {
await executor . execute ( workflow );
} catch ( error ) {
if ( error instanceof NodeExecutionError ) {
setError ( `Node ${ error . nodeId } failed: ${ error . message } ` );
} else {
setError ( error . message );
}
}
};
return (
< div >
< button onClick = { execute } > Execute </ button >
{ error && (
< div className = "error-banner" >
< strong > Error: </ strong > { error }
</ div >
) }
{ Object . entries ( nodeErrors ). map (([ nodeId , errorMsg ]) => (
< div key = { nodeId } className = "node-error" >
< strong > { nodeId } : </ strong > { errorMsg }
</ div >
)) }
</ div >
);
}
Best Practices
Wrap executor calls in try-catch blocks to handle failures gracefully.
Validate inputs at the beginning of execute() to fail fast.
Include helpful error messages with context about what failed and why.
Log errors with enough detail for debugging but not sensitive data.
Check for specific error types to handle different scenarios appropriately.
Always clean up resources (connections, timers) in finally blocks.
Next Steps