try-error Documentation

Error Factory Patterns

Advanced patterns for creating reusable, composable error factory functions

Basic Factory Patterns

Error factories provide a consistent way to create errors with predefined structure and context. They encapsulate error creation logic and ensure consistency across your application.

Simple Error Factories

Basic Error Factory Functionstypescript
1import { createTryError, TryError } from 'try-error';
2
3// Basic validation error factory
4export function createValidationError(
5  field: string,
6  value: unknown,
7  message: string,
8  rule?: string
9): TryError {
10  return createTryError('ValidationError', message, {
11    field,
12    value,
13    rule,
14    timestamp: new Date().toISOString(),
15    severity: 'medium',
16    userFacing: true
17  });
18}
19
20// Network error factory with common patterns
21export function createNetworkError(
22  url: string,
23  method: string,
24  status?: number,
25  message?: string
26): TryError {
27  const defaultMessage = status 
28    ? `HTTP ${status}: Request failed`
29    : 'Network request failed';
30
31  return createTryError('NetworkError', message || defaultMessage, {
32    url,
33    method,
34    status,
35    timestamp: new Date().toISOString(),
36    retryable: status ? status >= 500 || status === 429 : true,
37    severity: status && status < 500 ? 'medium' : 'high'
38  });
39}
40
41// Database error factory
42export function createDatabaseError(
43  operation: string,
44  table?: string,
45  constraint?: string,
46  originalError?: Error
47): TryError {
48  return createTryError('DatabaseError', `Database operation failed: ${operation}`, {
49    operation,
50    table,
51    constraint,
52    originalError: originalError?.message,
53    timestamp: new Date().toISOString(),
54    retryable: !constraint, // Constraint violations are not retryable
55    severity: 'high'
56  });
57}
58
59// Usage examples
60const validationError = createValidationError(
61  'email',
62  'invalid-email',
63  'Email format is invalid',
64  'email_format'
65);
66
67const networkError = createNetworkError(
68  '/api/users/123',
69  'GET',
70  404,
71  'User not found'
72);
73
74const dbError = createDatabaseError(
75  'INSERT',
76  'users',
77  'unique_email',
78  new Error('Duplicate key violation')
79);

Parameterized Factory Functions

Configurable Error Factoriestypescript
1// Factory with configuration options
2interface ErrorFactoryOptions {
3  severity?: 'low' | 'medium' | 'high' | 'critical';
4  userFacing?: boolean;
5  retryable?: boolean;
6  code?: string;
7  metadata?: Record<string, unknown>;
8}
9
10export function createConfigurableError(
11  type: string,
12  message: string,
13  context: Record<string, unknown>,
14  options: ErrorFactoryOptions = {}
15): TryError {
16  return createTryError(type, message, {
17    ...context,
18    severity: options.severity || 'medium',
19    userFacing: options.userFacing || false,
20    retryable: options.retryable || false,
21    code: options.code,
22    metadata: options.metadata,
23    timestamp: new Date().toISOString()
24  });
25}
26
27// Specialized factories using the configurable base
28export function createAuthenticationError(
29  reason: 'invalid_credentials' | 'token_expired' | 'token_invalid',
30  userId?: string
31): TryError {
32  const messages = {
33    invalid_credentials: 'Invalid username or password',
34    token_expired: 'Authentication token has expired',
35    token_invalid: 'Invalid authentication token'
36  };
37
38  return createConfigurableError(
39    'AuthenticationError',
40    messages[reason],
41    { reason, userId },
42    {
43      severity: 'medium',
44      userFacing: true,
45      retryable: reason === 'token_expired',
46      code: `AUTH_${reason.toUpperCase()}`
47    }
48  );
49}
50
51export function createAuthorizationError(
52  resource: string,
53  action: string,
54  userId: string,
55  requiredRole?: string
56): TryError {
57  return createConfigurableError(
58    'AuthorizationError',
59    'Insufficient permissions to perform this action',
60    { resource, action, userId, requiredRole },
61    {
62      severity: 'medium',
63      userFacing: true,
64      retryable: false,
65      code: 'INSUFFICIENT_PERMISSIONS'
66    }
67  );
68}
69
70// Business logic error factory
71export function createBusinessRuleError(
72  rule: string,
73  entity: string,
74  entityId: string,
75  details?: Record<string, unknown>
76): TryError {
77  return createConfigurableError(
78    'BusinessRuleError',
79    `Business rule violation: ${rule}`,
80    { rule, entity, entityId, details },
81    {
82      severity: 'medium',
83      userFacing: true,
84      retryable: false,
85      code: `BUSINESS_RULE_${rule.toUpperCase()}`
86    }
87  );
88}
89
90// Usage examples
91const authError = createAuthenticationError('invalid_credentials', 'user123');
92
93const authzError = createAuthorizationError(
94  'user_profile',
95  'delete',
96  'user123',
97  'admin'
98);
99
100const businessError = createBusinessRuleError(
101  'minimum_age_requirement',
102  'user',
103  'user123',
104  { currentAge: 16, requiredAge: 18 }
105);

Factory Classes and Builders

For more complex scenarios, factory classes provide better organization and allow for stateful error creation with shared configuration.

Error Factory Classes

Class-Based Error Factoriestypescript
1// Base error factory class
2abstract class BaseErrorFactory {
3  protected defaultSeverity: 'low' | 'medium' | 'high' | 'critical' = 'medium';
4  protected defaultUserFacing: boolean = false;
5  protected serviceContext: Record<string, unknown> = {};
6
7  constructor(serviceContext: Record<string, unknown> = {}) {
8    this.serviceContext = serviceContext;
9  }
10
11  protected createError(
12    type: string,
13    message: string,
14    context: Record<string, unknown> = {},
15    overrides: Partial<ErrorFactoryOptions> = {}
16  ): TryError {
17    return createTryError(type, message, {
18      ...this.serviceContext,
19      ...context,
20      severity: overrides.severity || this.defaultSeverity,
21      userFacing: overrides.userFacing ?? this.defaultUserFacing,
22      retryable: overrides.retryable || false,
23      timestamp: new Date().toISOString(),
24      ...overrides.metadata
25    });
26  }
27}
28
29// User service error factory
30class UserErrorFactory extends BaseErrorFactory {
31  constructor(serviceContext: Record<string, unknown> = {}) {
32    super({ service: 'user-service', ...serviceContext });
33    this.defaultUserFacing = true;
34  }
35
36  userNotFound(userId: string): TryError {
37    return this.createError(
38      'UserNotFoundError',
39      'User not found',
40      { userId },
41      { code: 'USER_NOT_FOUND' }
42    );
43  }
44
45  emailAlreadyExists(email: string): TryError {
46    return this.createError(
47      'EmailConflictError',
48      'Email address is already registered',
49      { email },
50      { code: 'EMAIL_CONFLICT' }
51    );
52  }
53
54  invalidUserData(field: string, value: unknown, reason: string): TryError {
55    return this.createError(
56      'InvalidUserDataError',
57      `Invalid user data: ${reason}`,
58      { field, value, reason },
59      { code: 'INVALID_USER_DATA' }
60    );
61  }
62
63  userDeactivated(userId: string, reason: string): TryError {
64    return this.createError(
65      'UserDeactivatedError',
66      'User account is deactivated',
67      { userId, reason },
68      { code: 'USER_DEACTIVATED' }
69    );
70  }
71}
72
73// Payment service error factory
74class PaymentErrorFactory extends BaseErrorFactory {
75  constructor(serviceContext: Record<string, unknown> = {}) {
76    super({ service: 'payment-service', ...serviceContext });
77    this.defaultSeverity = 'high';
78  }
79
80  paymentDeclined(
81    transactionId: string,
82    reason: string,
83    gatewayResponse?: string
84  ): TryError {
85    return this.createError(
86      'PaymentDeclinedError',
87      'Payment was declined',
88      { transactionId, reason, gatewayResponse },
89      { 
90        code: 'PAYMENT_DECLINED',
91        userFacing: true,
92        retryable: reason === 'insufficient_funds' ? false : true
93      }
94    );
95  }
96
97  paymentTimeout(transactionId: string, timeoutMs: number): TryError {
98    return this.createError(
99      'PaymentTimeoutError',
100      'Payment processing timed out',
101      { transactionId, timeoutMs },
102      { 
103        code: 'PAYMENT_TIMEOUT',
104        retryable: true,
105        severity: 'critical'
106      }
107    );
108  }
109
110  invalidPaymentMethod(paymentMethodId: string, reason: string): TryError {
111    return this.createError(
112      'InvalidPaymentMethodError',
113      'Payment method is invalid',
114      { paymentMethodId, reason },
115      { 
116        code: 'INVALID_PAYMENT_METHOD',
117        userFacing: true
118      }
119    );
120  }
121}
122
123// Usage with dependency injection
124class UserService {
125  private errorFactory: UserErrorFactory;
126
127  constructor(requestContext: Record<string, unknown> = {}) {
128    this.errorFactory = new UserErrorFactory({
129      requestId: requestContext.requestId,
130      userId: requestContext.userId,
131      userAgent: requestContext.userAgent
132    });
133  }
134
135  async getUser(userId: string): Promise<TryResult<User, TryError>> {
136    return tryAsync(async () => {
137      const user = await this.repository.findById(userId);
138      
139      if (!user) {
140        throw this.errorFactory.userNotFound(userId);
141      }
142
143      if (user.status === 'deactivated') {
144        throw this.errorFactory.userDeactivated(userId, user.deactivationReason);
145      }
146
147      return user;
148    });
149  }
150
151  async createUser(userData: CreateUserData): Promise<TryResult<User, TryError>> {
152    return tryAsync(async () => {
153      // Check if email exists
154      const existingUser = await this.repository.findByEmail(userData.email);
155      if (existingUser) {
156        throw this.errorFactory.emailAlreadyExists(userData.email);
157      }
158
159      // Validate user data
160      if (!userData.email || !this.isValidEmail(userData.email)) {
161        throw this.errorFactory.invalidUserData(
162          'email',
163          userData.email,
164          'Invalid email format'
165        );
166      }
167
168      return this.repository.create(userData);
169    });
170  }
171}

Fluent Error Builders

Fluent Builder Patterntypescript
1// Fluent error builder
2class ErrorBuilder {
3  private errorData: {
4    type: string;
5    message: string;
6    context: Record<string, unknown>;
7    severity: 'low' | 'medium' | 'high' | 'critical';
8    userFacing: boolean;
9    retryable: boolean;
10    code?: string;
11    cause?: TryError;
12  };
13
14  constructor(type: string, message: string) {
15    this.errorData = {
16      type,
17      message,
18      context: {},
19      severity: 'medium',
20      userFacing: false,
21      retryable: false
22    };
23  }
24
25  static create(type: string, message: string): ErrorBuilder {
26    return new ErrorBuilder(type, message);
27  }
28
29  // Context methods
30  withContext(key: string, value: unknown): ErrorBuilder {
31    this.errorData.context[key] = value;
32    return this;
33  }
34
35  withContextObject(context: Record<string, unknown>): ErrorBuilder {
36    this.errorData.context = { ...this.errorData.context, ...context };
37    return this;
38  }
39
40  // Configuration methods
41  severity(level: 'low' | 'medium' | 'high' | 'critical'): ErrorBuilder {
42    this.errorData.severity = level;
43    return this;
44  }
45
46  userFacing(isUserFacing: boolean = true): ErrorBuilder {
47    this.errorData.userFacing = isUserFacing;
48    return this;
49  }
50
51  retryable(canRetry: boolean = true): ErrorBuilder {
52    this.errorData.retryable = canRetry;
53    return this;
54  }
55
56  code(errorCode: string): ErrorBuilder {
57    this.errorData.code = errorCode;
58    return this;
59  }
60
61  causedBy(cause: TryError): ErrorBuilder {
62    this.errorData.cause = cause;
63    return this;
64  }
65
66  // Conditional methods
67  when(condition: boolean, configureFn: (builder: ErrorBuilder) => ErrorBuilder): ErrorBuilder {
68    if (condition) {
69      return configureFn(this);
70    }
71    return this;
72  }
73
74  // Specialized context methods
75  forUser(userId: string): ErrorBuilder {
76    return this.withContext('userId', userId);
77  }
78
79  forResource(resourceType: string, resourceId: string): ErrorBuilder {
80    return this.withContextObject({ resourceType, resourceId });
81  }
82
83  forOperation(operation: string): ErrorBuilder {
84    return this.withContext('operation', operation);
85  }
86
87  withTimestamp(): ErrorBuilder {
88    return this.withContext('timestamp', new Date().toISOString());
89  }
90
91  // Build method
92  build(): TryError {
93    return createTryError(this.errorData.type, this.errorData.message, {
94      ...this.errorData.context,
95      severity: this.errorData.severity,
96      userFacing: this.errorData.userFacing,
97      retryable: this.errorData.retryable,
98      ...(this.errorData.code && { code: this.errorData.code }),
99      ...(this.errorData.cause && { cause: this.errorData.cause })
100    });
101  }
102}
103
104// Specialized builders for common patterns
105class ValidationErrorBuilder extends ErrorBuilder {
106  constructor(field: string, value: unknown, message: string) {
107    super('ValidationError', message);
108    this.withContextObject({ field, value })
109        .userFacing(true)
110        .severity('medium');
111  }
112
113  static field(field: string, value: unknown, message: string): ValidationErrorBuilder {
114    return new ValidationErrorBuilder(field, value, message);
115  }
116
117  rule(ruleName: string): ValidationErrorBuilder {
118    return this.withContext('rule', ruleName) as ValidationErrorBuilder;
119  }
120
121  expected(expectedValue: unknown): ValidationErrorBuilder {
122    return this.withContext('expected', expectedValue) as ValidationErrorBuilder;
123  }
124}
125
126class NetworkErrorBuilder extends ErrorBuilder {
127  constructor(url: string, method: string, message: string) {
128    super('NetworkError', message);
129    this.withContextObject({ url, method })
130        .severity('high')
131        .retryable(true);
132  }
133
134  static request(url: string, method: string, message: string): NetworkErrorBuilder {
135    return new NetworkErrorBuilder(url, method, message);
136  }
137
138  status(statusCode: number): NetworkErrorBuilder {
139    this.withContext('status', statusCode);
140    // Adjust retryability based on status code
141    if (statusCode >= 400 && statusCode < 500) {
142      this.retryable(false);
143    }
144    return this as NetworkErrorBuilder;
145  }
146
147  timeout(timeoutMs: number): NetworkErrorBuilder {
148    return this.withContext('timeout', timeoutMs) as NetworkErrorBuilder;
149  }
150}
151
152// Usage examples
153const validationError = ValidationErrorBuilder
154  .field('email', 'invalid@', 'Invalid email format')
155  .rule('email_format')
156  .expected('valid email address')
157  .code('INVALID_EMAIL')
158  .build();
159
160const networkError = NetworkErrorBuilder
161  .request('/api/users/123', 'GET', 'Request failed')
162  .status(404)
163  .timeout(5000)
164  .code('USER_NOT_FOUND')
165  .build();
166
167const complexError = ErrorBuilder
168  .create('OrderProcessingError', 'Failed to process order')
169  .forUser('user123')
170  .forResource('order', 'order456')
171  .forOperation('payment_processing')
172  .severity('high')
173  .userFacing(true)
174  .retryable(true)
175  .code('ORDER_PAYMENT_FAILED')
176  .withTimestamp()
177  .when(process.env.NODE_ENV === 'development', builder =>
178    builder.withContext('debug', { stackTrace: true, verbose: true })
179  )
180  .build();

Domain-Specific Factory Patterns

Create specialized factory patterns tailored to specific business domains and use cases.

E-commerce Factory System

E-commerce Error Factorytypescript
1// E-commerce domain factory
2class EcommerceErrorFactory {
3  private static instance: EcommerceErrorFactory;
4  private requestContext: Record<string, unknown> = {};
5
6  private constructor() {}
7
8  static getInstance(): EcommerceErrorFactory {
9    if (!EcommerceErrorFactory.instance) {
10      EcommerceErrorFactory.instance = new EcommerceErrorFactory();
11    }
12    return EcommerceErrorFactory.instance;
13  }
14
15  withContext(context: Record<string, unknown>): EcommerceErrorFactory {
16    this.requestContext = { ...this.requestContext, ...context };
17    return this;
18  }
19
20  // Product-related errors
21  product = {
22    notFound: (productId: string, sku?: string): TryError => 
23      ErrorBuilder
24        .create('ProductNotFoundError', 'Product not found')
25        .withContextObject({ productId, sku, ...this.requestContext })
26        .userFacing(true)
27        .code('PRODUCT_NOT_FOUND')
28        .build(),
29
30    outOfStock: (productId: string, requested: number, available: number): TryError =>
31      ErrorBuilder
32        .create('OutOfStockError', 'Product is out of stock')
33        .withContextObject({ 
34          productId, 
35          requested, 
36          available,
37          ...this.requestContext 
38        })
39        .userFacing(true)
40        .code('OUT_OF_STOCK')
41        .build(),
42
43    priceChanged: (productId: string, oldPrice: number, newPrice: number): TryError =>
44      ErrorBuilder
45        .create('PriceChangedError', 'Product price has changed')
46        .withContextObject({ 
47          productId, 
48          oldPrice, 
49          newPrice,
50          ...this.requestContext 
51        })
52        .userFacing(true)
53        .code('PRICE_CHANGED')
54        .build(),
55
56    discontinued: (productId: string, discontinuedDate: string): TryError =>
57      ErrorBuilder
58        .create('ProductDiscontinuedError', 'Product has been discontinued')
59        .withContextObject({ 
60          productId, 
61          discontinuedDate,
62          ...this.requestContext 
63        })
64        .userFacing(true)
65        .code('PRODUCT_DISCONTINUED')
66        .build()
67  };
68
69  // Cart-related errors
70  cart = {
71    empty: (userId: string): TryError =>
72      ErrorBuilder
73        .create('EmptyCartError', 'Cart is empty')
74        .withContextObject({ userId, ...this.requestContext })
75        .userFacing(true)
76        .code('CART_EMPTY')
77        .build(),
78
79    itemNotFound: (userId: string, productId: string): TryError =>
80      ErrorBuilder
81        .create('CartItemNotFoundError', 'Item not found in cart')
82        .withContextObject({ userId, productId, ...this.requestContext })
83        .userFacing(true)
84        .code('CART_ITEM_NOT_FOUND')
85        .build(),
86
87    quantityExceeded: (productId: string, maxQuantity: number): TryError =>
88      ErrorBuilder
89        .create('QuantityExceededError', 'Quantity exceeds maximum allowed')
90        .withContextObject({ 
91          productId, 
92          maxQuantity,
93          ...this.requestContext 
94        })
95        .userFacing(true)
96        .code('QUANTITY_EXCEEDED')
97        .build()
98  };
99
100  // Order-related errors
101  order = {
102    notFound: (orderId: string, userId?: string): TryError =>
103      ErrorBuilder
104        .create('OrderNotFoundError', 'Order not found')
105        .withContextObject({ orderId, userId, ...this.requestContext })
106        .userFacing(true)
107        .code('ORDER_NOT_FOUND')
108        .build(),
109
110    cannotCancel: (orderId: string, status: string, reason: string): TryError =>
111      ErrorBuilder
112        .create('OrderCancellationError', 'Order cannot be cancelled')
113        .withContextObject({ 
114          orderId, 
115          status, 
116          reason,
117          ...this.requestContext 
118        })
119        .userFacing(true)
120        .code('ORDER_CANNOT_CANCEL')
121        .build(),
122
123    paymentFailed: (orderId: string, paymentId: string, reason: string): TryError =>
124      ErrorBuilder
125        .create('OrderPaymentError', 'Order payment failed')
126        .withContextObject({ 
127          orderId, 
128          paymentId, 
129          reason,
130          ...this.requestContext 
131        })
132        .severity('high')
133        .userFacing(true)
134        .retryable(true)
135        .code('ORDER_PAYMENT_FAILED')
136        .build(),
137
138    shippingUnavailable: (orderId: string, address: string, reason: string): TryError =>
139      ErrorBuilder
140        .create('ShippingUnavailableError', 'Shipping not available to this address')
141        .withContextObject({ 
142          orderId, 
143          address, 
144          reason,
145          ...this.requestContext 
146        })
147        .userFacing(true)
148        .code('SHIPPING_UNAVAILABLE')
149        .build()
150  };
151
152  // Promotion-related errors
153  promotion = {
154    expired: (promoCode: string, expiredDate: string): TryError =>
155      ErrorBuilder
156        .create('PromotionExpiredError', 'Promotion code has expired')
157        .withContextObject({ 
158          promoCode, 
159          expiredDate,
160          ...this.requestContext 
161        })
162        .userFacing(true)
163        .code('PROMOTION_EXPIRED')
164        .build(),
165
166    notApplicable: (promoCode: string, reason: string): TryError =>
167      ErrorBuilder
168        .create('PromotionNotApplicableError', 'Promotion code is not applicable')
169        .withContextObject({ 
170          promoCode, 
171          reason,
172          ...this.requestContext 
173        })
174        .userFacing(true)
175        .code('PROMOTION_NOT_APPLICABLE')
176        .build(),
177
178    usageLimitReached: (promoCode: string, limit: number): TryError =>
179      ErrorBuilder
180        .create('PromotionUsageLimitError', 'Promotion usage limit reached')
181        .withContextObject({ 
182          promoCode, 
183          limit,
184          ...this.requestContext 
185        })
186        .userFacing(true)
187        .code('PROMOTION_USAGE_LIMIT')
188        .build()
189  };
190}
191
192// Usage in services
193class ProductService {
194  private errorFactory = EcommerceErrorFactory.getInstance();
195
196  async getProduct(productId: string, requestContext: Record<string, unknown>): Promise<TryResult<Product, TryError>> {
197    this.errorFactory.withContext(requestContext);
198
199    return tryAsync(async () => {
200      const product = await this.repository.findById(productId);
201      
202      if (!product) {
203        throw this.errorFactory.product.notFound(productId, product?.sku);
204      }
205
206      if (product.status === 'discontinued') {
207        throw this.errorFactory.product.discontinued(productId, product.discontinuedDate);
208      }
209
210      return product;
211    });
212  }
213
214  async checkAvailability(productId: string, quantity: number): Promise<TryResult<void, TryError>> {
215    return tryAsync(async () => {
216      const product = await this.repository.findById(productId);
217      
218      if (!product) {
219        throw this.errorFactory.product.notFound(productId);
220      }
221
222      if (product.inventory < quantity) {
223        throw this.errorFactory.product.outOfStock(
224          productId,
225          quantity,
226          product.inventory
227        );
228      }
229    });
230  }
231}

Factory Registry Pattern

Centralized Factory Registrytypescript
1// Factory registry for managing multiple error factories
2class ErrorFactoryRegistry {
3  private static instance: ErrorFactoryRegistry;
4  private factories: Map<string, any> = new Map();
5  private globalContext: Record<string, unknown> = {};
6
7  private constructor() {}
8
9  static getInstance(): ErrorFactoryRegistry {
10    if (!ErrorFactoryRegistry.instance) {
11      ErrorFactoryRegistry.instance = new ErrorFactoryRegistry();
12    }
13    return ErrorFactoryRegistry.instance;
14  }
15
16  // Register a factory
17  register<T>(name: string, factory: T): void {
18    this.factories.set(name, factory);
19  }
20
21  // Get a factory
22  get<T>(name: string): T {
23    const factory = this.factories.get(name);
24    if (!factory) {
25      throw new Error(`Factory '${name}' not found`);
26    }
27    return factory;
28  }
29
30  // Set global context that all factories can use
31  setGlobalContext(context: Record<string, unknown>): void {
32    this.globalContext = { ...this.globalContext, ...context };
33  }
34
35  getGlobalContext(): Record<string, unknown> {
36    return { ...this.globalContext };
37  }
38
39  // Create a factory with global context
40  createWithContext<T extends { withContext: (ctx: any) => T }>(name: string): T {
41    const factory = this.get<T>(name);
42    return factory.withContext(this.globalContext);
43  }
44}
45
46// Factory initialization
47function initializeErrorFactories(): void {
48  const registry = ErrorFactoryRegistry.getInstance();
49
50  // Register domain-specific factories
51  registry.register('ecommerce', EcommerceErrorFactory.getInstance());
52  registry.register('user', new UserErrorFactory());
53  registry.register('payment', new PaymentErrorFactory());
54
55  // Set global context
56  registry.setGlobalContext({
57    service: 'api-server',
58    version: '1.0.0',
59    environment: process.env.NODE_ENV
60  });
61}
62
63// Factory provider for dependency injection
64class ErrorFactoryProvider {
65  private registry = ErrorFactoryRegistry.getInstance();
66
67  constructor(private requestContext: Record<string, unknown> = {}) {}
68
69  ecommerce(): EcommerceErrorFactory {
70    return this.registry
71      .createWithContext<EcommerceErrorFactory>('ecommerce')
72      .withContext(this.requestContext);
73  }
74
75  user(): UserErrorFactory {
76    return this.registry
77      .createWithContext<UserErrorFactory>('user')
78      .withContext(this.requestContext);
79  }
80
81  payment(): PaymentErrorFactory {
82    return this.registry
83      .createWithContext<PaymentErrorFactory>('payment')
84      .withContext(this.requestContext);
85  }
86}
87
88// Usage in Express middleware
89function errorFactoryMiddleware(req: Request, res: Response, next: NextFunction): void {
90  const requestContext = {
91    requestId: req.headers['x-request-id'] || generateRequestId(),
92    userId: req.user?.id,
93    userAgent: req.headers['user-agent'],
94    ip: req.ip,
95    method: req.method,
96    url: req.url,
97    timestamp: new Date().toISOString()
98  };
99
100  // Attach factory provider to request
101  req.errorFactory = new ErrorFactoryProvider(requestContext);
102  next();
103}
104
105// Usage in controllers
106class OrderController {
107  async createOrder(req: Request, res: Response): Promise<void> {
108    const { errorFactory } = req;
109    
110    try {
111      const orderData = req.body;
112      
113      // Validate order data
114      if (!orderData.items || orderData.items.length === 0) {
115        const error = errorFactory.ecommerce().cart.empty(req.user.id);
116        return res.status(400).json({ error: this.serializeError(error) });
117      }
118
119      // Process order
120      const result = await this.orderService.createOrder(orderData);
121      
122      if (isTryError(result)) {
123        const statusCode = this.getStatusCodeForError(result);
124        return res.status(statusCode).json({ error: this.serializeError(result) });
125      }
126
127      res.status(201).json({ order: result });
128    } catch (error) {
129      const internalError = errorFactory.ecommerce().order.paymentFailed(
130        'unknown',
131        'unknown',
132        'Unexpected error during order creation'
133      );
134      res.status(500).json({ error: this.serializeError(internalError) });
135    }
136  }
137
138  private serializeError(error: TryError): Record<string, unknown> {
139    return {
140      type: error.type,
141      message: error.message,
142      code: error.context?.code,
143      ...(error.context?.userFacing && { details: error.context })
144    };
145  }
146
147  private getStatusCodeForError(error: TryError): number {
148    const statusMap: Record<string, number> = {
149      'ProductNotFoundError': 404,
150      'OutOfStockError': 409,
151      'CartEmptyError': 400,
152      'OrderPaymentError': 402,
153      'PromotionExpiredError': 400,
154      'ShippingUnavailableError': 400
155    };
156
157    return statusMap[error.type] || 500;
158  }
159}
160
161// Type augmentation for Express
162declare global {
163  namespace Express {
164    interface Request {
165      errorFactory: ErrorFactoryProvider;
166    }
167  }
168}

Advanced Factory Patterns

Sophisticated patterns for complex error handling scenarios, including composable factories and dynamic error generation.

Composable Factory Pattern

Composable Error Factoriestypescript
1// Composable factory traits
2interface ErrorFactoryTrait {
3  name: string;
4  apply(builder: ErrorBuilder): ErrorBuilder;
5}
6
7// Common traits
8class TimestampTrait implements ErrorFactoryTrait {
9  name = 'timestamp';
10  
11  apply(builder: ErrorBuilder): ErrorBuilder {
12    return builder.withContext('timestamp', new Date().toISOString());
13  }
14}
15
16class RequestContextTrait implements ErrorFactoryTrait {
17  name = 'requestContext';
18  
19  constructor(private context: Record<string, unknown>) {}
20  
21  apply(builder: ErrorBuilder): ErrorBuilder {
22    return builder.withContextObject(this.context);
23  }
24}
25
26class SeverityTrait implements ErrorFactoryTrait {
27  name = 'severity';
28  
29  constructor(private severity: 'low' | 'medium' | 'high' | 'critical') {}
30  
31  apply(builder: ErrorBuilder): ErrorBuilder {
32    return builder.severity(this.severity);
33  }
34}
35
36class UserFacingTrait implements ErrorFactoryTrait {
37  name = 'userFacing';
38  
39  constructor(private isUserFacing: boolean = true) {}
40  
41  apply(builder: ErrorBuilder): ErrorBuilder {
42    return builder.userFacing(this.isUserFacing);
43  }
44}
45
46class RetryableTrait implements ErrorFactoryTrait {
47  name = 'retryable';
48  
49  constructor(private canRetry: boolean = true) {}
50  
51  apply(builder: ErrorBuilder): ErrorBuilder {
52    return builder.retryable(this.canRetry);
53  }
54}
55
56// Composable factory
57class ComposableErrorFactory {
58  private traits: ErrorFactoryTrait[] = [];
59
60  withTrait(trait: ErrorFactoryTrait): ComposableErrorFactory {
61    this.traits.push(trait);
62    return this;
63  }
64
65  withTraits(...traits: ErrorFactoryTrait[]): ComposableErrorFactory {
66    this.traits.push(...traits);
67    return this;
68  }
69
70  create(type: string, message: string, context: Record<string, unknown> = {}): TryError {
71    let builder = ErrorBuilder.create(type, message).withContextObject(context);
72
73    // Apply all traits
74    for (const trait of this.traits) {
75      builder = trait.apply(builder);
76    }
77
78    return builder.build();
79  }
80
81  // Convenience methods for common patterns
82  createUserError(type: string, message: string, context: Record<string, unknown> = {}): TryError {
83    return this.withTraits(
84      new UserFacingTrait(true),
85      new SeverityTrait('medium'),
86      new TimestampTrait()
87    ).create(type, message, context);
88  }
89
90  createSystemError(type: string, message: string, context: Record<string, unknown> = {}): TryError {
91    return this.withTraits(
92      new UserFacingTrait(false),
93      new SeverityTrait('high'),
94      new RetryableTrait(true),
95      new TimestampTrait()
96    ).create(type, message, context);
97  }
98
99  createCriticalError(type: string, message: string, context: Record<string, unknown> = {}): TryError {
100    return this.withTraits(
101      new SeverityTrait('critical'),
102      new UserFacingTrait(false),
103      new RetryableTrait(false),
104      new TimestampTrait()
105    ).create(type, message, context);
106  }
107}
108
109// Factory composition for specific domains
110class APIErrorFactory extends ComposableErrorFactory {
111  constructor(requestContext: Record<string, unknown>) {
112    super();
113    this.withTraits(
114      new RequestContextTrait(requestContext),
115      new TimestampTrait()
116    );
117  }
118
119  validationError(field: string, value: unknown, rule: string): TryError {
120    return this.createUserError('ValidationError', `Validation failed for field: ${field}`, {
121      field,
122      value,
123      rule
124    });
125  }
126
127  authenticationError(reason: string): TryError {
128    return this.createUserError('AuthenticationError', 'Authentication failed', {
129      reason
130    });
131  }
132
133  rateLimitError(limit: number, window: string): TryError {
134    return this.withTrait(new RetryableTrait(true))
135      .createUserError('RateLimitError', 'Rate limit exceeded', {
136        limit,
137        window,
138        retryAfter: this.calculateRetryAfter(window)
139      });
140  }
141
142  internalError(operation: string, originalError?: Error): TryError {
143    return this.createCriticalError('InternalError', 'Internal server error', {
144      operation,
145      originalError: originalError?.message
146    });
147  }
148
149  private calculateRetryAfter(window: string): number {
150    // Simple calculation - in real implementation, this would be more sophisticated
151    return window === '1m' ? 60 : window === '1h' ? 3600 : 300;
152  }
153}
154
155// Usage
156const requestContext = {
157  requestId: 'req_123',
158  userId: 'user_456',
159  endpoint: '/api/users',
160  method: 'POST'
161};
162
163const apiFactory = new APIErrorFactory(requestContext);
164
165const validationError = apiFactory.validationError('email', 'invalid@', 'email_format');
166const authError = apiFactory.authenticationError('invalid_token');
167const rateLimitError = apiFactory.rateLimitError(100, '1h');
168const internalError = apiFactory.internalError('user_creation', new Error('Database connection failed'));

Best Practices

✅ Do

  • • Create domain-specific factories for better organization
  • • Use builder patterns for complex error construction
  • • Implement factory registries for centralized management
  • • Include request context in error factories
  • • Use traits/mixins for reusable error characteristics
  • • Provide convenience methods for common error patterns
  • • Document factory APIs and usage patterns

❌ Don't

  • • Create overly complex factory hierarchies
  • • Include sensitive data in factory default contexts
  • • Make factories stateful without proper isolation
  • • Create factories that are tightly coupled to specific implementations
  • • Ignore error factory performance in high-throughput scenarios
  • • Mix factory patterns inconsistently across the codebase
  • • Create factories without clear ownership and responsibility

💡 Tips

  • • Start with simple factories and evolve to complex patterns
  • • Use TypeScript for better factory API type safety
  • • Consider factory caching for performance-critical paths
  • • Implement factory testing strategies and mock patterns
  • • Use factory composition over inheritance when possible
  • • Create factory documentation with usage examples

Related Pages

Custom Errors

Learn about creating custom error types and hierarchies

View Custom Errors →

Error Creation API

Core functions for creating and working with TryError objects

View Error API →