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