@onlineapps/conn-infra-error-handler 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js CHANGED
@@ -1,76 +1,50 @@
1
1
  /**
2
2
  * @module @onlineapps/conn-infra-error-handler
3
- * @description Unified error handling connector providing retry strategies, circuit breaker pattern,
4
- * and compensation mechanisms for OA Drive microservices.
3
+ * @description Unified error handling connector - wrapper around error-handler-core for business services
4
+ *
5
+ * This connector wraps @onlineapps/error-handler-core and integrates with conn-base-monitoring
6
+ * for business services using ServiceWrapper.
5
7
  *
6
8
  * @see {@link https://github.com/onlineapps/oa-drive/tree/main/shared/connector/conn-infra-error-handler|GitHub Repository}
9
+ * @see {@link /docs/standards/UNIFIED_ERROR_HANDLING.md|Unified Error Handling Standard}
7
10
  * @author OA Drive Team
8
11
  * @license MIT
9
12
  * @since 1.0.0
10
13
  */
11
14
 
12
- const CircuitBreaker = require('opossum');
13
-
14
- // Error types enum
15
- const ErrorTypes = {
16
- TRANSIENT: 'TRANSIENT', // Retry with backoff
17
- BUSINESS: 'BUSINESS', // Don't retry, return error
18
- FATAL: 'FATAL', // Stop workflow
19
- VALIDATION: 'VALIDATION', // Input validation error
20
- TIMEOUT: 'TIMEOUT', // Operation timeout
21
- RATE_LIMIT: 'RATE_LIMIT', // Rate limiting
22
- UNKNOWN: 'UNKNOWN' // Unclassified error
23
- };
24
-
25
- // Standard error codes
26
- const ErrorCodes = {
27
- // Network errors
28
- ECONNREFUSED: ErrorTypes.TRANSIENT,
29
- ENOTFOUND: ErrorTypes.TRANSIENT,
30
- ETIMEDOUT: ErrorTypes.TRANSIENT,
31
- ECONNRESET: ErrorTypes.TRANSIENT,
32
- EPIPE: ErrorTypes.TRANSIENT,
33
-
34
- // HTTP status codes
35
- 408: ErrorTypes.TIMEOUT,
36
- 429: ErrorTypes.RATE_LIMIT,
37
- 500: ErrorTypes.TRANSIENT,
38
- 502: ErrorTypes.TRANSIENT,
39
- 503: ErrorTypes.TRANSIENT,
40
- 504: ErrorTypes.TIMEOUT,
41
-
42
- // Business errors
43
- 400: ErrorTypes.VALIDATION,
44
- 401: ErrorTypes.BUSINESS,
45
- 403: ErrorTypes.BUSINESS,
46
- 404: ErrorTypes.BUSINESS,
47
- 409: ErrorTypes.BUSINESS,
48
- 422: ErrorTypes.VALIDATION
49
- };
15
+ const { ErrorHandlerCore } = require('@onlineapps/error-handler-core');
16
+ const { ErrorTypes, ErrorCodes } = require('@onlineapps/error-handler-core');
50
17
 
51
18
  /**
52
- * Error handling connector with retry, circuit breaker, and compensation
19
+ * Error handling connector for business services
20
+ * Wraps error-handler-core and integrates with conn-base-monitoring
53
21
  *
54
22
  * @class ErrorHandlerConnector
55
23
  *
56
- * @example <caption>Basic Usage</caption>
57
- * const errorHandler = new ErrorHandlerConnector({
58
- * maxRetries: 3,
59
- * retryDelay: 1000
60
- * });
61
- *
62
- * const result = await errorHandler.executeWithRetry(async () => {
63
- * return await riskyOperation();
24
+ * @example <caption>Basic Usage in ServiceWrapper</caption>
25
+ * const { ServiceWrapper } = require('@onlineapps/service-wrapper');
26
+ *
27
+ * const wrapper = new ServiceWrapper({
28
+ * serviceName: 'my-service',
29
+ * monitoring: { enabled: true },
30
+ * errorHandling: {
31
+ * maxRetries: 3,
32
+ * retryDelay: 1000
33
+ * }
64
34
  * });
65
- *
66
- * @example <caption>With Circuit Breaker</caption>
35
+ *
36
+ * // Error handler is automatically available as wrapper.errorHandler
37
+ *
38
+ * @example <caption>Direct Usage</caption>
39
+ * const { ErrorHandlerConnector } = require('@onlineapps/conn-infra-error-handler');
40
+ *
67
41
  * const errorHandler = new ErrorHandlerConnector({
68
- * circuitBreakerEnabled: true,
69
- * errorThreshold: 50
70
- * });
71
- *
72
- * await errorHandler.executeWithCircuitBreaker('api-call', async () => {
73
- * return await apiClient.call();
42
+ * serviceName: 'my-service',
43
+ * monitoring: monitoringInstance, // conn-base-monitoring instance
44
+ * handling: {
45
+ * maxRetries: 3,
46
+ * retryDelay: 1000
47
+ * }
74
48
  * });
75
49
  */
76
50
  class ErrorHandlerConnector {
@@ -78,55 +52,66 @@ class ErrorHandlerConnector {
78
52
  * Creates a new ErrorHandlerConnector instance
79
53
  *
80
54
  * @constructor
81
- * @param {Object} [config={}] - Configuration options
82
- * @param {number} [config.maxRetries=3] - Maximum retry attempts
83
- * @param {number} [config.retryDelay=1000] - Initial retry delay in ms
84
- * @param {number} [config.retryMultiplier=2] - Backoff multiplier
85
- * @param {number} [config.maxRetryDelay=30000] - Maximum retry delay
86
- * @param {boolean} [config.circuitBreakerEnabled=true] - Enable circuit breaker
87
- * @param {number} [config.circuitTimeout=10000] - Circuit breaker timeout
88
- * @param {number} [config.errorThreshold=50] - Error threshold percentage
89
- * @param {number} [config.resetTimeout=30000] - Circuit reset timeout
90
- * @param {boolean} [config.compensationEnabled=true] - Enable compensation
91
- * @param {Object} [config.circuitBreakerOptions] - Additional circuit breaker options
55
+ * @param {Object} config - Configuration options
56
+ * @param {string} config.serviceName - Service name (required)
57
+ * @param {string} [config.serviceVersion] - Service version
58
+ * @param {string} [config.environment] - Environment
59
+ * @param {Object} config.monitoring - Monitoring instance (conn-base-monitoring, required)
60
+ * @param {Object} [config.handling] - Error handling configuration
61
+ * @param {number} [config.handling.maxRetries=3] - Maximum retry attempts
62
+ * @param {number} [config.handling.retryDelay=1000] - Initial retry delay in ms
63
+ * @param {number} [config.handling.retryMultiplier=2] - Backoff multiplier
64
+ * @param {number} [config.handling.maxRetryDelay=30000] - Maximum retry delay
65
+ * @param {boolean} [config.handling.circuitBreakerEnabled=true] - Enable circuit breaker
66
+ * @param {number} [config.handling.circuitTimeout=10000] - Circuit breaker timeout
67
+ * @param {number} [config.handling.errorThreshold=50] - Error threshold percentage
68
+ * @param {number} [config.handling.resetTimeout=30000] - Circuit reset timeout
69
+ * @param {boolean} [config.handling.dlqEnabled=true] - Enable DLQ routing
70
+ * @param {Object} [config.handling.mqClient] - MQ client for DLQ
71
+ * @param {boolean} [config.handling.compensationEnabled=true] - Enable compensation
92
72
  *
93
73
  * @example <caption>Full Configuration</caption>
94
74
  * const errorHandler = new ErrorHandlerConnector({
95
- * maxRetries: 5,
96
- * retryDelay: 500,
97
- * retryMultiplier: 1.5,
98
- * maxRetryDelay: 20000,
99
- * circuitBreakerEnabled: true,
100
- * errorThreshold: 60,
101
- * compensationEnabled: true
75
+ * serviceName: 'my-service',
76
+ * serviceVersion: '1.0.0',
77
+ * environment: 'production',
78
+ * monitoring: monitoringInstance,
79
+ * handling: {
80
+ * maxRetries: 5,
81
+ * retryDelay: 500,
82
+ * retryMultiplier: 1.5,
83
+ * maxRetryDelay: 20000,
84
+ * circuitBreakerEnabled: true,
85
+ * errorThreshold: 60,
86
+ * dlqEnabled: true,
87
+ * mqClient: mqClientInstance,
88
+ * compensationEnabled: true
89
+ * }
102
90
  * });
103
91
  */
104
92
  constructor(config = {}) {
105
- // Retry configuration
106
- this.maxRetries = config.maxRetries || 3;
107
- this.retryDelay = config.retryDelay || 1000;
108
- this.retryMultiplier = config.retryMultiplier || 2;
109
- this.maxRetryDelay = config.maxRetryDelay || 30000;
110
-
111
- // Circuit breaker configuration
112
- this.circuitBreakerEnabled = config.circuitBreakerEnabled !== false;
113
- this.circuitBreakerOptions = {
114
- timeout: config.circuitTimeout || 10000,
115
- errorThresholdPercentage: config.errorThreshold || 50,
116
- resetTimeout: config.resetTimeout || 30000,
117
- rollingCountTimeout: config.rollingCountTimeout || 10000,
118
- rollingCountBuckets: config.rollingCountBuckets || 10,
119
- ...config.circuitBreakerOptions
120
- };
121
-
122
- // Compensation configuration
123
- this.compensationEnabled = config.compensationEnabled !== false;
124
- this.compensationHandlers = new Map();
125
-
126
- // Circuit breakers registry
127
- this.circuitBreakers = new Map();
128
-
129
- // Error statistics
93
+ if (!config.serviceName) {
94
+ throw new Error('serviceName is required');
95
+ }
96
+
97
+ if (!config.monitoring) {
98
+ throw new Error('monitoring instance is required (conn-base-monitoring)');
99
+ }
100
+
101
+ // Initialize error-handler-core with monitoring instance
102
+ // monitoring instance from conn-base-monitoring wraps monitoring-core
103
+ this.core = new ErrorHandlerCore({
104
+ serviceName: config.serviceName,
105
+ serviceVersion: config.serviceVersion,
106
+ environment: config.environment,
107
+ monitoring: config.monitoring, // conn-base-monitoring instance
108
+ handling: config.handling || {}
109
+ });
110
+
111
+ // Store config for compatibility
112
+ this.config = config;
113
+
114
+ // Statistics (for backward compatibility)
130
115
  this.stats = {
131
116
  errors: 0,
132
117
  retries: 0,
@@ -134,285 +119,171 @@ class ErrorHandlerConnector {
134
119
  circuitBreaks: 0,
135
120
  byType: {}
136
121
  };
137
-
122
+
138
123
  // Initialize error type stats
139
124
  Object.values(ErrorTypes).forEach(type => {
140
125
  this.stats.byType[type] = 0;
141
126
  });
142
127
  }
143
-
128
+
144
129
  /**
145
130
  * Classify error into type for appropriate handling
131
+ * Delegates to error-handler-core
146
132
  *
147
133
  * @method classifyError
148
134
  * @param {Error} error - Error to classify
149
- * @param {string} [error.code] - Error code (e.g., ECONNREFUSED)
150
- * @param {number} [error.statusCode] - HTTP status code
151
- * @param {string} [error.type] - Explicit error type
152
135
  * @returns {string} Error type from ErrorTypes enum
153
136
  *
154
- * @example <caption>Network Error</caption>
137
+ * @example
155
138
  * const type = errorHandler.classifyError(new Error('ECONNREFUSED'));
156
139
  * // Returns: 'TRANSIENT'
157
- *
158
- * @example <caption>HTTP Error</caption>
159
- * const error = new Error('Not Found');
160
- * error.statusCode = 404;
161
- * const type = errorHandler.classifyError(error);
162
- * // Returns: 'BUSINESS'
163
- *
164
- * @example <caption>Timeout Error</caption>
165
- * const type = errorHandler.classifyError(new Error('Operation timeout'));
166
- * // Returns: 'TIMEOUT'
167
140
  */
168
141
  classifyError(error) {
169
- // Check by error code
170
- if (error.code && ErrorCodes[error.code]) {
171
- return ErrorCodes[error.code];
172
- }
173
-
174
- // Check by HTTP status
175
- if (error.statusCode && ErrorCodes[error.statusCode]) {
176
- return ErrorCodes[error.statusCode];
177
- }
178
-
179
- // Check by error message patterns
180
- const message = error.message?.toLowerCase() || '';
181
-
182
- if (message.includes('timeout')) return ErrorTypes.TIMEOUT;
183
- if (message.includes('rate limit')) return ErrorTypes.RATE_LIMIT;
184
- if (message.includes('validation')) return ErrorTypes.VALIDATION;
185
- if (message.includes('unauthorized') || message.includes('forbidden')) {
186
- return ErrorTypes.BUSINESS;
187
- }
188
- if (message.includes('connection') || message.includes('network')) {
189
- return ErrorTypes.TRANSIENT;
190
- }
191
-
192
- // Check if error has explicit type
193
- if (error.type && ErrorTypes[error.type]) {
194
- return error.type;
195
- }
196
-
197
- return ErrorTypes.UNKNOWN;
142
+ return this.core.classifier.classify(error);
198
143
  }
199
-
144
+
200
145
  /**
201
146
  * Determine if error should be retried
147
+ * Delegates to error-handler-core
202
148
  *
203
149
  * @method shouldRetry
204
150
  * @param {Error} error - Error to check
205
151
  * @param {number} [attempts=0] - Current attempt count
206
152
  * @returns {boolean} True if should retry
207
- *
208
- * @example
209
- * const error = new Error('Connection refused');
210
- * if (errorHandler.shouldRetry(error, 1)) {
211
- * // Retry the operation
212
- * }
213
153
  */
214
154
  shouldRetry(error, attempts = 0) {
215
- if (attempts >= this.maxRetries) {
216
- return false;
217
- }
218
-
219
- const errorType = this.classifyError(error);
220
-
221
- // Only retry transient and timeout errors
222
- return [
223
- ErrorTypes.TRANSIENT,
224
- ErrorTypes.TIMEOUT,
225
- ErrorTypes.RATE_LIMIT
226
- ].includes(errorType);
155
+ return this.core.classifier.shouldRetry(error, attempts, this.core.retryHandler.maxRetries);
227
156
  }
228
-
157
+
229
158
  /**
230
159
  * Calculate exponential backoff delay
160
+ * Delegates to error-handler-core
231
161
  *
232
162
  * @method calculateBackoff
233
163
  * @param {number} attempts - Current attempt number (1-based)
234
164
  * @returns {number} Delay in milliseconds
235
- *
236
- * @example
237
- * const delay = errorHandler.calculateBackoff(3);
238
- * // With default config: 1000 * 2^2 = 4000ms
239
165
  */
240
166
  calculateBackoff(attempts) {
241
- const delay = this.retryDelay * Math.pow(this.retryMultiplier, attempts - 1);
242
- return Math.min(delay, this.maxRetryDelay);
167
+ return this.core.retryHandler.calculateBackoff(attempts);
243
168
  }
244
-
169
+
245
170
  /**
246
171
  * Execute function with automatic retry on failure
172
+ * Delegates to error-handler-core
247
173
  *
248
174
  * @async
249
175
  * @method executeWithRetry
250
176
  * @param {Function} fn - Async function to execute
251
177
  * @param {Object} [options={}] - Retry options
252
178
  * @param {number} [options.maxRetries] - Override max retries
253
- * @param {Array<string>} [options.retryOn=[]] - Additional error codes to retry
254
179
  * @param {Function} [options.onRetry] - Callback on retry (error, attempt, delay)
255
180
  * @returns {Promise<*>} Function result
256
181
  *
257
182
  * @throws {Error} Last error if all retries fail
258
183
  *
259
- * @example <caption>Simple Retry</caption>
184
+ * @example
260
185
  * const result = await errorHandler.executeWithRetry(async () => {
261
186
  * return await apiClient.fetchData();
262
187
  * });
263
- *
264
- * @example <caption>With Options</caption>
265
- * const result = await errorHandler.executeWithRetry(
266
- * async () => await riskyOperation(),
267
- * {
268
- * maxRetries: 5,
269
- * retryOn: ['CUSTOM_ERROR'],
270
- * onRetry: (error, attempt, delay) => {
271
- * console.log(`Retry ${attempt} after ${delay}ms`);
272
- * }
273
- * }
274
- * );
275
- *
276
- * @example <caption>Database Operation</caption>
277
- * const data = await errorHandler.executeWithRetry(async () => {
278
- * const conn = await db.connect();
279
- * try {
280
- * return await conn.query('SELECT * FROM users');
281
- * } finally {
282
- * conn.close();
283
- * }
284
- * });
285
188
  */
286
189
  async executeWithRetry(fn, options = {}) {
287
- const maxAttempts = options.maxRetries || this.maxRetries;
288
- const retryOn = options.retryOn || [];
289
-
290
- let lastError;
291
-
292
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
293
- try {
294
- return await fn();
295
- } catch (error) {
296
- lastError = error;
297
- this.stats.errors++;
298
- this.stats.byType[this.classifyError(error)]++;
299
-
300
- // Check if should retry
301
- const shouldRetry = this.shouldRetry(error, attempt) ||
302
- retryOn.includes(error.code);
303
-
304
- if (!shouldRetry || attempt === maxAttempts) {
305
- throw error;
306
- }
307
-
308
- // Calculate and wait backoff
309
- const delay = this.calculateBackoff(attempt);
310
- this.stats.retries++;
311
-
312
- if (options.onRetry) {
313
- options.onRetry(error, attempt, delay);
314
- }
315
-
316
- await this.sleep(delay);
317
- }
318
- }
319
-
320
- throw lastError;
321
- }
322
-
323
- /**
324
- * Get or create circuit breaker for operation
325
- * @param {string} name - Operation name
326
- * @param {Function} fn - Function to protect
327
- * @param {object} options - Circuit breaker options
328
- * @returns {CircuitBreaker} Circuit breaker instance
329
- */
330
- getCircuitBreaker(name, fn, options = {}) {
331
- if (!this.circuitBreakerEnabled) {
332
- // Return pass-through if disabled
333
- return { fire: () => fn() };
334
- }
335
-
336
- if (!this.circuitBreakers.has(name)) {
337
- const breaker = new CircuitBreaker(fn, {
338
- ...this.circuitBreakerOptions,
339
- ...options
340
- });
341
-
342
- // Track circuit breaker events
343
- breaker.on('open', () => {
344
- this.stats.circuitBreaks++;
345
- console.warn(`Circuit breaker opened: ${name}`);
346
- });
347
-
348
- breaker.on('halfOpen', () => {
349
- console.info(`Circuit breaker half-open: ${name}`);
350
- });
351
-
352
- this.circuitBreakers.set(name, breaker);
190
+ this.stats.errors++;
191
+
192
+ try {
193
+ return await this.core.retryHandler.executeWithRetry(fn, options);
194
+ } catch (error) {
195
+ const errorType = this.classifyError(error);
196
+ this.stats.byType[errorType] = (this.stats.byType[errorType] || 0) + 1;
197
+ throw error;
353
198
  }
354
-
355
- return this.circuitBreakers.get(name);
356
199
  }
357
-
200
+
358
201
  /**
359
202
  * Execute function with circuit breaker protection
203
+ * Delegates to error-handler-core
360
204
  *
361
205
  * @async
362
206
  * @method executeWithCircuitBreaker
363
207
  * @param {string} name - Operation name for circuit breaker
364
208
  * @param {Function} fn - Async function to execute
365
209
  * @param {Object} [options={}] - Circuit breaker options
366
- * @param {number} [options.timeout] - Operation timeout
367
- * @param {number} [options.errorThresholdPercentage] - Error threshold
368
210
  * @returns {Promise<*>} Function result
369
211
  *
370
212
  * @throws {Error} If circuit is open or operation fails
371
213
  *
372
- * @fires CircuitBreaker#open - When circuit opens
373
- * @fires CircuitBreaker#halfOpen - When circuit enters half-open state
374
- *
375
- * @example <caption>API Call Protection</caption>
376
- * try {
377
- * const data = await errorHandler.executeWithCircuitBreaker(
378
- * 'user-api',
379
- * async () => await userAPI.getUser(id)
380
- * );
381
- * } catch (error) {
382
- * if (error.message.includes('circuit is open')) {
383
- * // Service is down, use fallback
384
- * }
385
- * }
386
- *
387
- * @example <caption>With Custom Options</caption>
388
- * const result = await errorHandler.executeWithCircuitBreaker(
389
- * 'payment-gateway',
390
- * async () => await processPayment(order),
391
- * {
392
- * timeout: 5000,
393
- * errorThresholdPercentage: 30
394
- * }
214
+ * @example
215
+ * const data = await errorHandler.executeWithCircuitBreaker(
216
+ * 'user-api',
217
+ * async () => await userAPI.getUser(id)
395
218
  * );
396
219
  */
397
220
  async executeWithCircuitBreaker(name, fn, options = {}) {
398
- const breaker = this.getCircuitBreaker(name, fn, options);
399
-
400
221
  try {
401
- return await breaker.fire();
222
+ return await this.core.circuitBreaker.executeWithCircuitBreaker(name, fn, options);
402
223
  } catch (error) {
403
224
  this.stats.errors++;
404
- this.stats.byType[this.classifyError(error)]++;
405
-
406
- if (error.code === 'EOPENBREAKER') {
407
- throw new Error(`Service unavailable: ${name} circuit is open`);
225
+ const errorType = this.classifyError(error);
226
+ this.stats.byType[errorType] = (this.stats.byType[errorType] || 0) + 1;
227
+
228
+ if (error.message && error.message.includes('circuit is open')) {
229
+ this.stats.circuitBreaks++;
408
230
  }
409
-
231
+
410
232
  throw error;
411
233
  }
412
234
  }
413
-
235
+
236
+ /**
237
+ * Log error using unified schema
238
+ * Delegates to error-handler-core
239
+ *
240
+ * @async
241
+ * @method logError
242
+ * @param {Object} errorData - Error data
243
+ * @param {string} errorData.moduleName - Module name
244
+ * @param {string} errorData.operation - Operation name
245
+ * @param {Error} errorData.error - Error object
246
+ * @param {Object} [errorData.context] - Additional context
247
+ * @returns {Promise<Object>} Unified error log entry
248
+ */
249
+ async logError(errorData) {
250
+ return await this.core.logError(errorData);
251
+ }
252
+
253
+ /**
254
+ * Handle error: classify + log + decide action
255
+ * Delegates to error-handler-core
256
+ *
257
+ * @async
258
+ * @method handleError
259
+ * @param {Object} errorData - Error data
260
+ * @param {string} errorData.moduleName - Module name
261
+ * @param {string} errorData.operation - Operation name
262
+ * @param {Error} errorData.error - Error object
263
+ * @param {Object} [errorData.context] - Additional context
264
+ * @returns {Promise<Object>} Handling result
265
+ */
266
+ async handleError(errorData) {
267
+ this.stats.errors++;
268
+ const result = await this.core.handleError(errorData);
269
+
270
+ const errorType = result.errorType;
271
+ this.stats.byType[errorType] = (this.stats.byType[errorType] || 0) + 1;
272
+
273
+ if (result.action === 'retry') {
274
+ this.stats.retries++;
275
+ }
276
+
277
+ if (result.compensated) {
278
+ this.stats.compensations++;
279
+ }
280
+
281
+ return result;
282
+ }
283
+
414
284
  /**
415
285
  * Register compensation handler for an operation
286
+ * Delegates to error-handler-core
416
287
  *
417
288
  * @method registerCompensation
418
289
  * @param {string} operation - Operation name
@@ -421,21 +292,16 @@ class ErrorHandlerConnector {
421
292
  *
422
293
  * @example
423
294
  * errorHandler.registerCompensation('create-order', async (context) => {
424
- * // Rollback order creation
425
295
  * await orderService.cancel(context.orderId);
426
296
  * });
427
- *
428
- * errorHandler.registerCompensation('charge-payment', async (context) => {
429
- * // Refund payment
430
- * await paymentService.refund(context.chargeId);
431
- * });
432
297
  */
433
298
  registerCompensation(operation, handler) {
434
- this.compensationHandlers.set(operation, handler);
299
+ this.core.registerCompensation(operation, handler);
435
300
  }
436
-
301
+
437
302
  /**
438
303
  * Execute compensation for failed operation
304
+ * Delegates to error-handler-core
439
305
  *
440
306
  * @async
441
307
  * @method executeCompensation
@@ -444,82 +310,37 @@ class ErrorHandlerConnector {
444
310
  * @returns {Promise<*>} Compensation result or null if no handler
445
311
  *
446
312
  * @throws {Error} If compensation fails
447
- *
448
- * @example
449
- * try {
450
- * await createOrder(data);
451
- * } catch (error) {
452
- * await errorHandler.executeCompensation('create-order', {
453
- * orderId: data.orderId,
454
- * userId: data.userId
455
- * });
456
- * }
457
313
  */
458
314
  async executeCompensation(operation, context) {
459
- if (!this.compensationEnabled) {
460
- return null;
461
- }
462
-
463
- const handler = this.compensationHandlers.get(operation);
464
-
465
- if (!handler) {
466
- console.warn(`No compensation handler for: ${operation}`);
467
- return null;
468
- }
469
-
470
315
  try {
471
- this.stats.compensations++;
472
- const result = await handler(context);
473
- console.info(`Compensation executed for: ${operation}`);
316
+ const result = await this.core.compensation.execute(operation, context);
317
+ if (result !== null) {
318
+ this.stats.compensations++;
319
+ }
474
320
  return result;
475
321
  } catch (error) {
476
- console.error(`Compensation failed for ${operation}:`, error);
477
322
  throw error;
478
323
  }
479
324
  }
480
-
325
+
481
326
  /**
482
327
  * Route failed message to dead letter queue
328
+ * Delegates to error-handler-core
483
329
  *
484
330
  * @async
485
331
  * @method routeToDLQ
486
- * @param {Object} mqClient - Message queue client
332
+ * @param {Object} mqClient - Message queue client (optional, can be in config)
487
333
  * @param {Object} message - Original message that failed
488
334
  * @param {Error} error - Error that occurred
489
335
  * @returns {Promise<void>}
490
- *
491
- * @example
492
- * try {
493
- * await processMessage(message);
494
- * } catch (error) {
495
- * if (!errorHandler.shouldRetry(error)) {
496
- * await errorHandler.routeToDLQ(mqClient, message, error);
497
- * }
498
- * }
499
336
  */
500
337
  async routeToDLQ(mqClient, message, error) {
501
- const dlqMessage = {
502
- ...message,
503
- error: {
504
- message: error.message,
505
- stack: error.stack,
506
- code: error.code,
507
- type: this.classifyError(error),
508
- timestamp: new Date().toISOString()
509
- },
510
- originalQueue: message.queue || 'unknown',
511
- retryCount: message.retryCount || 0
512
- };
513
-
514
- const dlqName = `${message.queue || 'unknown'}.dlq`;
515
-
516
- await mqClient.publish(dlqName, dlqMessage, {
517
- persistent: true
338
+ const errorType = this.classifyError(error);
339
+ await this.core.dlqRouter.routeToDLQ(message, error, errorType, {
340
+ queue: message.queue || 'unknown'
518
341
  });
519
-
520
- console.info(`Message routed to DLQ: ${dlqName}`);
521
342
  }
522
-
343
+
523
344
  /**
524
345
  * Create standardized error response
525
346
  *
@@ -527,17 +348,10 @@ class ErrorHandlerConnector {
527
348
  * @param {Error} error - Original error
528
349
  * @param {Object} [context={}] - Additional context
529
350
  * @returns {ErrorResponse} Formatted error response
530
- *
531
- * @example
532
- * const response = errorHandler.createErrorResponse(
533
- * new Error('Database connection failed'),
534
- * { operation: 'user-fetch', userId: 123 }
535
- * );
536
- * // Returns standardized error object
537
351
  */
538
352
  createErrorResponse(error, context = {}) {
539
353
  const errorType = this.classifyError(error);
540
-
354
+
541
355
  return {
542
356
  success: false,
543
357
  error: {
@@ -550,161 +364,27 @@ class ErrorHandlerConnector {
550
364
  }
551
365
  };
552
366
  }
553
-
554
- /**
555
- * Wrap function with error handling capabilities
556
- *
557
- * @method wrap
558
- * @param {Function} fn - Function to wrap
559
- * @param {Object} [options={}] - Wrapping options
560
- * @param {string} [options.name] - Operation name
561
- * @param {boolean} [options.circuitBreaker=true] - Use circuit breaker
562
- * @param {boolean} [options.retry=true] - Use retry logic
563
- * @param {number} [options.maxRetries] - Max retry attempts
564
- * @returns {Function} Wrapped function
565
- *
566
- * @example <caption>Wrap API Function</caption>
567
- * const safeApiCall = errorHandler.wrap(
568
- * async (id) => await api.getUser(id),
569
- * { name: 'get-user', maxRetries: 3 }
570
- * );
571
- *
572
- * const user = await safeApiCall(123);
573
- *
574
- * @example <caption>Wrap Database Function</caption>
575
- * const safeQuery = errorHandler.wrap(
576
- * async (sql, params) => await db.query(sql, params),
577
- * { circuitBreaker: false, retry: true }
578
- * );
579
- */
580
- wrap(fn, options = {}) {
581
- const name = options.name || fn.name || 'wrapped';
582
- const useCircuitBreaker = options.circuitBreaker !== false;
583
- const useRetry = options.retry !== false;
584
-
585
- return async (...args) => {
586
- const execute = async () => {
587
- if (useRetry) {
588
- return await this.executeWithRetry(() => fn(...args), options);
589
- }
590
- return await fn(...args);
591
- };
592
-
593
- if (useCircuitBreaker) {
594
- return await this.executeWithCircuitBreaker(name, execute, options);
595
- }
596
-
597
- return await execute();
598
- };
599
- }
600
-
601
- /**
602
- * Handle workflow error with compensation support
603
- *
604
- * @async
605
- * @method handleWorkflowError
606
- * @param {Object} workflow - Workflow context
607
- * @param {Error} error - Error that occurred
608
- * @param {string} failedStep - Step that failed
609
- * @returns {Promise<WorkflowErrorResult>} Error handling result
610
- *
611
- * @example
612
- * const result = await errorHandler.handleWorkflowError(
613
- * workflowContext,
614
- * error,
615
- * 'payment-processing'
616
- * );
617
- *
618
- * if (result.shouldRetry) {
619
- * // Retry the step
620
- * } else if (result.compensated) {
621
- * // Compensation was executed
622
- * }
623
- */
624
- async handleWorkflowError(workflow, error, failedStep) {
625
- const errorType = this.classifyError(error);
626
-
627
- const result = {
628
- handled: false,
629
- compensated: false,
630
- shouldContinue: false,
631
- shouldRetry: false,
632
- error: this.createErrorResponse(error, { step: failedStep })
633
- };
634
-
635
- // Determine action based on error type
636
- switch (errorType) {
637
- case ErrorTypes.TRANSIENT:
638
- case ErrorTypes.TIMEOUT:
639
- result.shouldRetry = true;
640
- result.handled = true;
641
- break;
642
-
643
- case ErrorTypes.BUSINESS:
644
- case ErrorTypes.VALIDATION:
645
- // Try compensation
646
- if (this.compensationEnabled && workflow.compensationSteps) {
647
- try {
648
- await this.executeCompensation(failedStep, workflow);
649
- result.compensated = true;
650
- result.handled = true;
651
- } catch (compError) {
652
- console.error('Compensation failed:', compError);
653
- }
654
- }
655
- break;
656
-
657
- case ErrorTypes.FATAL:
658
- // Stop workflow immediately
659
- result.shouldContinue = false;
660
- result.handled = true;
661
- break;
662
-
663
- default:
664
- // Unknown error, let it propagate
665
- break;
666
- }
667
-
668
- return result;
669
- }
670
-
367
+
671
368
  /**
672
369
  * Get error handler statistics
673
370
  *
674
371
  * @method getStats
675
372
  * @returns {ErrorStats} Current statistics
676
- *
677
- * @example
678
- * const stats = errorHandler.getStats();
679
- * console.log(`Total errors: ${stats.errors}`);
680
- * console.log(`Retry success rate: ${stats.retries / stats.errors * 100}%`);
681
- * console.log(`Circuit breakers:`, stats.circuitBreakers);
682
373
  */
683
374
  getStats() {
684
- const circuitBreakerStats = {};
685
-
686
- this.circuitBreakers.forEach((breaker, name) => {
687
- circuitBreakerStats[name] = {
688
- state: breaker.opened ? 'open' : breaker.halfOpen ? 'half-open' : 'closed',
689
- stats: breaker.stats
690
- };
691
- });
692
-
375
+ const circuitBreakerStats = this.core.getAllCircuitBreakerStates();
376
+
693
377
  return {
694
378
  ...this.stats,
695
379
  circuitBreakers: circuitBreakerStats
696
380
  };
697
381
  }
698
-
382
+
699
383
  /**
700
384
  * Reset all statistics
701
385
  *
702
386
  * @method resetStats
703
387
  * @returns {void}
704
- *
705
- * @example
706
- * errorHandler.resetStats();
707
- * // All counters reset to 0
708
388
  */
709
389
  resetStats() {
710
390
  this.stats = {
@@ -714,51 +394,34 @@ class ErrorHandlerConnector {
714
394
  circuitBreaks: 0,
715
395
  byType: {}
716
396
  };
717
-
397
+
718
398
  Object.values(ErrorTypes).forEach(type => {
719
399
  this.stats.byType[type] = 0;
720
400
  });
721
401
  }
722
-
402
+
403
+ /**
404
+ * Get circuit breaker state
405
+ *
406
+ * @method getCircuitBreakerState
407
+ * @param {string} name - Circuit breaker name
408
+ * @returns {string} State: 'open' | 'half-open' | 'closed'
409
+ */
410
+ getCircuitBreakerState(name) {
411
+ return this.core.getCircuitBreakerState(name);
412
+ }
413
+
723
414
  /**
724
- * Sleep utility
725
- * @private
415
+ * Get all circuit breaker states
416
+ *
417
+ * @method getAllCircuitBreakerStates
418
+ * @returns {Object} Map of circuit breaker states
726
419
  */
727
- async sleep(ms) {
728
- return new Promise(resolve => setTimeout(resolve, ms));
420
+ getAllCircuitBreakerStates() {
421
+ return this.core.getAllCircuitBreakerStates();
729
422
  }
730
423
  }
731
424
 
732
- /**
733
- * @typedef {Object} ErrorResponse
734
- * @property {boolean} success - Always false for errors
735
- * @property {Object} error - Error details
736
- * @property {string} error.message - Error message
737
- * @property {string} error.code - Error code
738
- * @property {string} error.type - Error type from ErrorTypes
739
- * @property {boolean} error.retryable - Whether error is retryable
740
- * @property {string} error.timestamp - ISO timestamp
741
- */
742
-
743
- /**
744
- * @typedef {Object} WorkflowErrorResult
745
- * @property {boolean} handled - Whether error was handled
746
- * @property {boolean} compensated - Whether compensation was executed
747
- * @property {boolean} shouldContinue - Whether workflow should continue
748
- * @property {boolean} shouldRetry - Whether step should be retried
749
- * @property {ErrorResponse} error - Error response
750
- */
751
-
752
- /**
753
- * @typedef {Object} ErrorStats
754
- * @property {number} errors - Total errors
755
- * @property {number} retries - Total retries
756
- * @property {number} compensations - Total compensations
757
- * @property {number} circuitBreaks - Circuit breaker trips
758
- * @property {Object} byType - Errors by type
759
- * @property {Object} circuitBreakers - Circuit breaker states
760
- */
761
-
762
425
  // Export main class as default
763
426
  module.exports = ErrorHandlerConnector;
764
427
 
@@ -780,12 +443,6 @@ module.exports.ErrorCodes = ErrorCodes;
780
443
  * @function create
781
444
  * @param {Object} config - Configuration object
782
445
  * @returns {ErrorHandlerConnector} New error handler instance
783
- *
784
- * @example
785
- * const errorHandler = ErrorHandlerConnector.create({
786
- * maxRetries: 5,
787
- * circuitBreakerEnabled: true
788
- * });
789
446
  */
790
447
  module.exports.create = (config) => new ErrorHandlerConnector(config);
791
448
 
@@ -794,29 +451,3 @@ module.exports.create = (config) => new ErrorHandlerConnector(config);
794
451
  * @constant {string}
795
452
  */
796
453
  module.exports.VERSION = '1.0.0';
797
-
798
- // Export mock for testing
799
- module.exports.MockErrorHandler = class MockErrorHandler {
800
- constructor() {
801
- this.stats = { errors: 0, retries: 0, compensations: 0 };
802
- }
803
-
804
- classifyError(error) {
805
- return error.type || ErrorTypes.UNKNOWN;
806
- }
807
-
808
- shouldRetry(error) {
809
- return error.retryable !== false;
810
- }
811
-
812
- async executeWithRetry(fn) {
813
- try {
814
- return await fn();
815
- } catch (error) {
816
- this.stats.errors++;
817
- throw error;
818
- }
819
- }
820
-
821
- getStats() { return this.stats; }
822
- };