try-error Documentation

Custom Error Types

Creating domain-specific error types and hierarchies for better error handling

Creating Custom Error Types

Custom error types provide better semantic meaning and enable more precise error handling in your application. They extend the basic TryError structure with domain-specific context.

Basic Custom Error

Simple Custom Error Typestypescript
1import { createTryError, TryError } from 'try-error';
2
3// Define custom error interfaces
4interface ValidationErrorContext {
5  field: string;
6  value: unknown;
7  rule: string;
8  expected?: string;
9}
10
11interface NetworkErrorContext {
12  url: string;
13  method: string;
14  status?: number;
15  statusText?: string;
16  timeout?: number;
17}
18
19interface BusinessErrorContext {
20  operation: string;
21  entityId: string;
22  entityType: string;
23  reason: string;
24}
25
26// Custom error creation functions
27export function createValidationError(
28  message: string,
29  context: ValidationErrorContext
30): TryError<ValidationErrorContext> {
31  return createTryError('ValidationError', message, context);
32}
33
34export function createNetworkError(
35  message: string,
36  context: NetworkErrorContext
37): TryError<NetworkErrorContext> {
38  return createTryError('NetworkError', message, context);
39}
40
41export function createBusinessError(
42  message: string,
43  context: BusinessErrorContext
44): TryError<BusinessErrorContext> {
45  return createTryError('BusinessError', message, context);
46}
47
48// Usage examples
49const validationError = createValidationError(
50  'Email format is invalid',
51  {
52    field: 'email',
53    value: 'invalid-email',
54    rule: 'email_format',
55    expected: 'valid email address'
56  }
57);
58
59const networkError = createNetworkError(
60  'Request timeout',
61  {
62    url: '/api/users/123',
63    method: 'GET',
64    timeout: 5000
65  }
66);
67
68const businessError = createBusinessError(
69  'Cannot delete user with active orders',
70  {
71    operation: 'delete_user',
72    entityId: '123',
73    entityType: 'User',
74    reason: 'has_active_orders'
75  }
76);

Type-Safe Error Handling

Type-Safe Custom Error Handlingtypescript
1import { isTryError, hasErrorType } from 'try-error';
2
3// Type guards for custom errors
4function isValidationError(error: TryError): error is TryError<ValidationErrorContext> {
5  return hasErrorType(error, 'ValidationError');
6}
7
8function isNetworkError(error: TryError): error is TryError<NetworkErrorContext> {
9  return hasErrorType(error, 'NetworkError');
10}
11
12function isBusinessError(error: TryError): error is TryError<BusinessErrorContext> {
13  return hasErrorType(error, 'BusinessError');
14}
15
16// Usage with type safety
17async function handleUserOperation(userId: string) {
18  const result = await tryAsync(() => performUserOperation(userId));
19  
20  if (isTryError(result)) {
21    if (isValidationError(result)) {
22      // TypeScript knows result.context is ValidationErrorContext
23      return {
24        status: 'validation_failed',
25        field: result.context.field,
26        message: result.message,
27        expected: result.context.expected
28      };
29    }
30    
31    if (isNetworkError(result)) {
32      // TypeScript knows result.context is NetworkErrorContext
33      return {
34        status: 'network_error',
35        url: result.context.url,
36        method: result.context.method,
37        retryable: result.context.status !== 404
38      };
39    }
40    
41    if (isBusinessError(result)) {
42      // TypeScript knows result.context is BusinessErrorContext
43      return {
44        status: 'business_error',
45        operation: result.context.operation,
46        entityType: result.context.entityType,
47        reason: result.context.reason
48      };
49    }
50    
51    // Generic error handling
52    return {
53      status: 'unknown_error',
54      message: result.message
55    };
56  }
57  
58  return {
59    status: 'success',
60    data: result
61  };
62}

Error Hierarchies

Create hierarchical error structures that allow for both specific and general error handling.

Hierarchical Error Types

Error Type Hierarchytypescript
1// Base error categories
2type ErrorCategory = 'UserError' | 'SystemError' | 'ExternalError';
3
4// Specific error types within categories
5type UserErrorType = 
6  | 'ValidationError'
7  | 'AuthenticationError'
8  | 'AuthorizationError'
9  | 'InputError';
10
11type SystemErrorType = 
12  | 'DatabaseError'
13  | 'FileSystemError'
14  | 'ConfigurationError'
15  | 'InternalError';
16
17type ExternalErrorType = 
18  | 'NetworkError'
19  | 'APIError'
20  | 'ServiceUnavailableError'
21  | 'ThirdPartyError';
22
23// Combined error type
24type AppErrorType = UserErrorType | SystemErrorType | ExternalErrorType;
25
26// Enhanced error context with hierarchy
27interface HierarchicalErrorContext {
28  category: ErrorCategory;
29  severity: 'low' | 'medium' | 'high' | 'critical';
30  retryable: boolean;
31  userFacing: boolean;
32  code?: string;
33  details?: Record<string, unknown>;
34}
35
36// Error creation with hierarchy
37function createHierarchicalError<T extends Record<string, unknown> = {}>(
38  type: AppErrorType,
39  message: string,
40  context: HierarchicalErrorContext & T
41): TryError<HierarchicalErrorContext & T> {
42  return createTryError(type, message, context);
43}
44
45// Category-specific error creators
46export function createUserError<T extends Record<string, unknown> = {}>(
47  type: UserErrorType,
48  message: string,
49  context: Omit<HierarchicalErrorContext, 'category'> & T
50): TryError<HierarchicalErrorContext & T> {
51  return createHierarchicalError(type, message, {
52    ...context,
53    category: 'UserError'
54  });
55}
56
57export function createSystemError<T extends Record<string, unknown> = {}>(
58  type: SystemErrorType,
59  message: string,
60  context: Omit<HierarchicalErrorContext, 'category'> & T
61): TryError<HierarchicalErrorContext & T> {
62  return createHierarchicalError(type, message, {
63    ...context,
64    category: 'SystemError'
65  });
66}
67
68export function createExternalError<T extends Record<string, unknown> = {}>(
69  type: ExternalErrorType,
70  message: string,
71  context: Omit<HierarchicalErrorContext, 'category'> & T
72): TryError<HierarchicalErrorContext & T> {
73  return createHierarchicalError(type, message, {
74    ...context,
75    category: 'ExternalError'
76  });
77}
78
79// Usage examples
80const validationError = createUserError('ValidationError', 'Invalid email format', {
81  severity: 'medium',
82  retryable: false,
83  userFacing: true,
84  code: 'INVALID_EMAIL',
85  field: 'email',
86  value: 'invalid@'
87});
88
89const databaseError = createSystemError('DatabaseError', 'Connection timeout', {
90  severity: 'high',
91  retryable: true,
92  userFacing: false,
93  code: 'DB_TIMEOUT',
94  connectionString: 'postgresql://...',
95  timeout: 5000
96});
97
98const apiError = createExternalError('APIError', 'Third-party service unavailable', {
99  severity: 'medium',
100  retryable: true,
101  userFacing: false,
102  code: 'EXTERNAL_API_DOWN',
103  service: 'payment-processor',
104  endpoint: '/api/payments'
105});

Hierarchical Error Handling

Category-Based Error Handlingtypescript
1// Type guards for error categories
2function isUserError(error: TryError): boolean {
3  return error.context?.category === 'UserError';
4}
5
6function isSystemError(error: TryError): boolean {
7  return error.context?.category === 'SystemError';
8}
9
10function isExternalError(error: TryError): boolean {
11  return error.context?.category === 'ExternalError';
12}
13
14// Severity-based handling
15function isCriticalError(error: TryError): boolean {
16  return error.context?.severity === 'critical';
17}
18
19function isRetryableError(error: TryError): boolean {
20  return error.context?.retryable === true;
21}
22
23function isUserFacingError(error: TryError): boolean {
24  return error.context?.userFacing === true;
25}
26
27// Comprehensive error handler
28class ErrorHandler {
29  async handleError(error: TryError): Promise<ErrorResponse> {
30    // Log error based on severity
31    this.logError(error);
32    
33    // Alert on critical errors
34    if (isCriticalError(error)) {
35      await this.alertOncall(error);
36    }
37    
38    // Handle by category
39    if (isUserError(error)) {
40      return this.handleUserError(error);
41    }
42    
43    if (isSystemError(error)) {
44      return this.handleSystemError(error);
45    }
46    
47    if (isExternalError(error)) {
48      return this.handleExternalError(error);
49    }
50    
51    return this.handleUnknownError(error);
52  }
53  
54  private handleUserError(error: TryError): ErrorResponse {
55    return {
56      status: 400,
57      code: error.context?.code || 'USER_ERROR',
58      message: isUserFacingError(error) ? error.message : 'Invalid request',
59      retryable: false,
60      details: isUserFacingError(error) ? error.context?.details : undefined
61    };
62  }
63  
64  private handleSystemError(error: TryError): ErrorResponse {
65    return {
66      status: 500,
67      code: error.context?.code || 'SYSTEM_ERROR',
68      message: 'Internal server error',
69      retryable: isRetryableError(error),
70      details: undefined // Never expose system details
71    };
72  }
73  
74  private handleExternalError(error: TryError): ErrorResponse {
75    return {
76      status: 502,
77      code: error.context?.code || 'EXTERNAL_ERROR',
78      message: 'Service temporarily unavailable',
79      retryable: isRetryableError(error),
80      details: undefined
81    };
82  }
83  
84  private logError(error: TryError): void {
85    const logLevel = this.getLogLevel(error);
86    const logData = {
87      type: error.type,
88      message: error.message,
89      category: error.context?.category,
90      severity: error.context?.severity,
91      code: error.context?.code,
92      context: error.context,
93      timestamp: new Date().toISOString()
94    };
95    
96    switch (logLevel) {
97      case 'error':
98        console.error('Error occurred:', logData);
99        break;
100      case 'warn':
101        console.warn('Warning:', logData);
102        break;
103      case 'info':
104        console.info('Info:', logData);
105        break;
106    }
107  }
108  
109  private getLogLevel(error: TryError): 'error' | 'warn' | 'info' {
110    switch (error.context?.severity) {
111      case 'critical':
112      case 'high':
113        return 'error';
114      case 'medium':
115        return 'warn';
116      case 'low':
117      default:
118        return 'info';
119    }
120  }
121}
122
123interface ErrorResponse {
124  status: number;
125  code: string;
126  message: string;
127  retryable: boolean;
128  details?: Record<string, unknown>;
129}

Domain-Specific Error Systems

Create comprehensive error systems tailored to specific business domains.

E-commerce Error System

E-commerce Domain Errorstypescript
1// E-commerce specific error types
2type EcommerceErrorType = 
3  | 'ProductNotFoundError'
4  | 'InsufficientInventoryError'
5  | 'InvalidPriceError'
6  | 'PaymentFailedError'
7  | 'ShippingUnavailableError'
8  | 'CouponInvalidError'
9  | 'OrderNotFoundError'
10  | 'CartEmptyError';
11
12// Domain-specific contexts
13interface ProductErrorContext {
14  productId: string;
15  sku?: string;
16  category?: string;
17  availability?: number;
18}
19
20interface PaymentErrorContext {
21  paymentMethod: string;
22  amount: number;
23  currency: string;
24  transactionId?: string;
25  gatewayResponse?: string;
26}
27
28interface OrderErrorContext {
29  orderId: string;
30  userId: string;
31  status: string;
32  items: Array<{ productId: string; quantity: number }>;
33}
34
35// E-commerce error factory
36class EcommerceErrorFactory {
37  static productNotFound(productId: string, sku?: string): TryError<ProductErrorContext> {
38    return createTryError('ProductNotFoundError', 'Product not found', {
39      productId,
40      sku,
41      category: 'product',
42      severity: 'medium',
43      userFacing: true,
44      retryable: false
45    });
46  }
47  
48  static insufficientInventory(
49    productId: string, 
50    requested: number, 
51    available: number
52  ): TryError<ProductErrorContext> {
53    return createTryError('InsufficientInventoryError', 'Not enough items in stock', {
54      productId,
55      availability: available,
56      requested,
57      category: 'inventory',
58      severity: 'medium',
59      userFacing: true,
60      retryable: false
61    });
62  }
63  
64  static paymentFailed(
65    paymentMethod: string,
66    amount: number,
67    currency: string,
68    gatewayResponse?: string
69  ): TryError<PaymentErrorContext> {
70    return createTryError('PaymentFailedError', 'Payment processing failed', {
71      paymentMethod,
72      amount,
73      currency,
74      gatewayResponse,
75      category: 'payment',
76      severity: 'high',
77      userFacing: true,
78      retryable: true
79    });
80  }
81  
82  static orderNotFound(orderId: string, userId: string): TryError<OrderErrorContext> {
83    return createTryError('OrderNotFoundError', 'Order not found', {
84      orderId,
85      userId,
86      status: 'not_found',
87      items: [],
88      category: 'order',
89      severity: 'medium',
90      userFacing: true,
91      retryable: false
92    });
93  }
94  
95  static invalidCoupon(
96    couponCode: string,
97    reason: 'expired' | 'invalid' | 'used' | 'minimum_not_met'
98  ): TryError {
99    const messages = {
100      expired: 'Coupon has expired',
101      invalid: 'Invalid coupon code',
102      used: 'Coupon has already been used',
103      minimum_not_met: 'Order does not meet minimum requirements for this coupon'
104    };
105    
106    return createTryError('CouponInvalidError', messages[reason], {
107      couponCode,
108      reason,
109      category: 'promotion',
110      severity: 'low',
111      userFacing: true,
112      retryable: false
113    });
114  }
115}
116
117// Usage in e-commerce service
118class ProductService {
119  async getProduct(productId: string): Promise<TryResult<Product, TryError>> {
120    return tryAsync(async () => {
121      const product = await this.repository.findById(productId);
122      
123      if (!product) {
124        throw EcommerceErrorFactory.productNotFound(productId, product?.sku);
125      }
126      
127      return product;
128    });
129  }
130  
131  async reserveInventory(
132    productId: string, 
133    quantity: number
134  ): Promise<TryResult<void, TryError>> {
135    return tryAsync(async () => {
136      const product = await this.repository.findById(productId);
137      
138      if (!product) {
139        throw EcommerceErrorFactory.productNotFound(productId);
140      }
141      
142      if (product.inventory < quantity) {
143        throw EcommerceErrorFactory.insufficientInventory(
144          productId,
145          quantity,
146          product.inventory
147        );
148      }
149      
150      await this.repository.updateInventory(productId, -quantity);
151    });
152  }
153}
154
155// Error handling in controllers
156class OrderController {
157  async createOrder(req: Request, res: Response) {
158    const result = await this.orderService.createOrder(req.body);
159    
160    if (isTryError(result)) {
161      const response = this.mapErrorToResponse(result);
162      return res.status(response.status).json(response);
163    }
164    
165    return res.status(201).json({ order: result });
166  }
167  
168  private mapErrorToResponse(error: TryError): ErrorResponse {
169    switch (error.type) {
170      case 'ProductNotFoundError':
171        return {
172          status: 404,
173          code: 'PRODUCT_NOT_FOUND',
174          message: error.message,
175          details: { productId: error.context?.productId }
176        };
177        
178      case 'InsufficientInventoryError':
179        return {
180          status: 409,
181          code: 'INSUFFICIENT_INVENTORY',
182          message: error.message,
183          details: {
184            productId: error.context?.productId,
185            available: error.context?.availability,
186            requested: error.context?.requested
187          }
188        };
189        
190      case 'PaymentFailedError':
191        return {
192          status: 402,
193          code: 'PAYMENT_FAILED',
194          message: error.message,
195          retryable: true
196        };
197        
198      default:
199        return {
200          status: 500,
201          code: 'INTERNAL_ERROR',
202          message: 'An unexpected error occurred'
203        };
204    }
205  }
206}

Error Composition and Chaining

Compose complex errors from simpler ones and maintain error chains for better debugging.

Error Composition

Composable Error Systemtypescript
1// Composable error builder
2class ErrorBuilder {
3  private errorData: {
4    type: string;
5    message: string;
6    context: Record<string, unknown>;
7    cause?: TryError;
8    chain: TryError[];
9  };
10  
11  constructor(type: string, message: string) {
12    this.errorData = {
13      type,
14      message,
15      context: {},
16      chain: []
17    };
18  }
19  
20  static create(type: string, message: string): ErrorBuilder {
21    return new ErrorBuilder(type, message);
22  }
23  
24  withContext<T extends Record<string, unknown>>(context: T): ErrorBuilder {
25    this.errorData.context = { ...this.errorData.context, ...context };
26    return this;
27  }
28  
29  withCause(cause: TryError): ErrorBuilder {
30    this.errorData.cause = cause;
31    return this;
32  }
33  
34  withChain(errors: TryError[]): ErrorBuilder {
35    this.errorData.chain = [...this.errorData.chain, ...errors];
36    return this;
37  }
38  
39  severity(level: 'low' | 'medium' | 'high' | 'critical'): ErrorBuilder {
40    return this.withContext({ severity: level });
41  }
42  
43  retryable(canRetry: boolean = true): ErrorBuilder {
44    return this.withContext({ retryable: canRetry });
45  }
46  
47  userFacing(isUserFacing: boolean = true): ErrorBuilder {
48    return this.withContext({ userFacing: isUserFacing });
49  }
50  
51  code(errorCode: string): ErrorBuilder {
52    return this.withContext({ code: errorCode });
53  }
54  
55  build(): TryError {
56    return createTryError(this.errorData.type, this.errorData.message, {
57      ...this.errorData.context,
58      ...(this.errorData.cause && { cause: this.errorData.cause }),
59      ...(this.errorData.chain.length > 0 && { chain: this.errorData.chain })
60    });
61  }
62}
63
64// Usage examples
65const composedError = ErrorBuilder
66  .create('OrderProcessingError', 'Failed to process order')
67  .withContext({
68    orderId: '12345',
69    userId: 'user123',
70    step: 'payment_processing'
71  })
72  .severity('high')
73  .retryable(true)
74  .code('ORDER_PROC_001')
75  .withCause(paymentError)
76  .build();
77
78// Error aggregation for batch operations
79class ErrorAggregator {
80  private errors: TryError[] = [];
81  
82  add(error: TryError): void {
83    this.errors.push(error);
84  }
85  
86  addAll(errors: TryError[]): void {
87    this.errors.push(...errors);
88  }
89  
90  hasErrors(): boolean {
91    return this.errors.length > 0;
92  }
93  
94  getErrors(): TryError[] {
95    return [...this.errors];
96  }
97  
98  createAggregateError(type: string, message: string): TryError {
99    return ErrorBuilder
100      .create(type, message)
101      .withContext({
102        errorCount: this.errors.length,
103        errors: this.errors.map(e => ({
104          type: e.type,
105          message: e.message,
106          context: e.context
107        }))
108      })
109      .severity(this.getHighestSeverity())
110      .build();
111  }
112  
113  private getHighestSeverity(): 'low' | 'medium' | 'high' | 'critical' {
114    const severities = this.errors
115      .map(e => e.context?.severity)
116      .filter(Boolean) as string[];
117    
118    if (severities.includes('critical')) return 'critical';
119    if (severities.includes('high')) return 'high';
120    if (severities.includes('medium')) return 'medium';
121    return 'low';
122  }
123}
124
125// Batch processing with error aggregation
126async function processBatchWithAggregation<T>(
127  items: T[],
128  processor: (item: T) => Promise<TryResult<void, TryError>>
129): Promise<TryResult<void, TryError>> {
130  const aggregator = new ErrorAggregator();
131  
132  for (const item of items) {
133    const result = await processor(item);
134    if (isTryError(result)) {
135      aggregator.add(result);
136    }
137  }
138  
139  if (aggregator.hasErrors()) {
140    return aggregator.createAggregateError(
141      'BatchProcessingError',
142      `Failed to process ${aggregator.getErrors().length} out of ${items.length} items`
143    );
144  }
145  
146  return undefined as any; // Success
147}

Best Practices

✅ Do

  • • Create domain-specific error types for better semantics
  • • Use hierarchical error structures for flexible handling
  • • Include rich context information in custom errors
  • • Implement type guards for type-safe error handling
  • • Use error builders for complex error composition
  • • Maintain error chains for debugging complex flows
  • • Categorize errors by severity and user-facing nature

❌ Don't

  • • Create too many granular error types without clear purpose
  • • Include sensitive information in user-facing errors
  • • Make error hierarchies too deep or complex
  • • Ignore error context when handling errors
  • • Use generic error types for domain-specific problems
  • • Create circular references in error chains
  • • Expose internal system details in error messages

💡 Tips

  • • Start with basic custom errors and evolve to hierarchies
  • • Use error codes for programmatic error handling
  • • Document your error types and their contexts
  • • Consider internationalization for user-facing messages
  • • Use error aggregation for batch operations
  • • Implement error recovery strategies based on error types

Related Pages

Error Creation API

Learn about the core error creation functions and utilities

View Error API →

Error Factories

Advanced patterns for creating reusable error factories

View Factories →