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

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,51 @@
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');
17
+ const runtimeCfg = require('./config');
50
18
 
51
19
  /**
52
- * Error handling connector with retry, circuit breaker, and compensation
20
+ * Error handling connector for business services
21
+ * Wraps error-handler-core and integrates with conn-base-monitoring
53
22
  *
54
23
  * @class ErrorHandlerConnector
55
24
  *
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();
25
+ * @example <caption>Basic Usage in ServiceWrapper</caption>
26
+ * const { ServiceWrapper } = require('@onlineapps/service-wrapper');
27
+ *
28
+ * const wrapper = new ServiceWrapper({
29
+ * serviceName: 'my-service',
30
+ * monitoring: { enabled: true },
31
+ * errorHandling: {
32
+ * maxRetries: 3,
33
+ * retryDelay: 1000
34
+ * }
64
35
  * });
65
- *
66
- * @example <caption>With Circuit Breaker</caption>
36
+ *
37
+ * // Error handler is automatically available as wrapper.errorHandler
38
+ *
39
+ * @example <caption>Direct Usage</caption>
40
+ * const { ErrorHandlerConnector } = require('@onlineapps/conn-infra-error-handler');
41
+ *
67
42
  * const errorHandler = new ErrorHandlerConnector({
68
- * circuitBreakerEnabled: true,
69
- * errorThreshold: 50
70
- * });
71
- *
72
- * await errorHandler.executeWithCircuitBreaker('api-call', async () => {
73
- * return await apiClient.call();
43
+ * serviceName: 'my-service',
44
+ * monitoring: monitoringInstance, // conn-base-monitoring instance
45
+ * handling: {
46
+ * maxRetries: 3,
47
+ * retryDelay: 1000
48
+ * }
74
49
  * });
75
50
  */
76
51
  class ErrorHandlerConnector {
@@ -78,55 +53,66 @@ class ErrorHandlerConnector {
78
53
  * Creates a new ErrorHandlerConnector instance
79
54
  *
80
55
  * @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
56
+ * @param {Object} config - Configuration options
57
+ * @param {string} config.serviceName - Service name (required)
58
+ * @param {string} [config.serviceVersion] - Service version
59
+ * @param {string} [config.environment] - Environment
60
+ * @param {Object} config.monitoring - Monitoring instance (conn-base-monitoring, required)
61
+ * @param {Object} [config.handling] - Error handling configuration
62
+ * @param {number} [config.handling.maxRetries=3] - Maximum retry attempts
63
+ * @param {number} [config.handling.retryDelay=1000] - Initial retry delay in ms
64
+ * @param {number} [config.handling.retryMultiplier=2] - Backoff multiplier
65
+ * @param {number} [config.handling.maxRetryDelay=30000] - Maximum retry delay
66
+ * @param {boolean} [config.handling.circuitBreakerEnabled=true] - Enable circuit breaker
67
+ * @param {number} [config.handling.circuitTimeout=10000] - Circuit breaker timeout
68
+ * @param {number} [config.handling.errorThreshold=50] - Error threshold percentage
69
+ * @param {number} [config.handling.resetTimeout=30000] - Circuit reset timeout
70
+ * @param {boolean} [config.handling.dlqEnabled=true] - Enable DLQ routing
71
+ * @param {Object} [config.handling.mqClient] - MQ client for DLQ
72
+ * @param {boolean} [config.handling.compensationEnabled=true] - Enable compensation
92
73
  *
93
74
  * @example <caption>Full Configuration</caption>
94
75
  * 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
76
+ * serviceName: 'my-service',
77
+ * serviceVersion: '1.0.0',
78
+ * environment: 'production',
79
+ * monitoring: monitoringInstance,
80
+ * handling: {
81
+ * maxRetries: 5,
82
+ * retryDelay: 500,
83
+ * retryMultiplier: 1.5,
84
+ * maxRetryDelay: 20000,
85
+ * circuitBreakerEnabled: true,
86
+ * errorThreshold: 60,
87
+ * dlqEnabled: true,
88
+ * mqClient: mqClientInstance,
89
+ * compensationEnabled: true
90
+ * }
102
91
  * });
103
92
  */
104
93
  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
94
+ if (!config.serviceName) {
95
+ throw new Error('serviceName is required');
96
+ }
97
+
98
+ if (!config.monitoring) {
99
+ throw new Error('monitoring instance is required (conn-base-monitoring)');
100
+ }
101
+
102
+ // Initialize error-handler-core with monitoring instance
103
+ // monitoring instance from conn-base-monitoring wraps monitoring-core
104
+ this.core = new ErrorHandlerCore({
105
+ serviceName: config.serviceName,
106
+ serviceVersion: config.serviceVersion,
107
+ environment: config.environment,
108
+ monitoring: config.monitoring, // conn-base-monitoring instance
109
+ handling: config.handling || {}
110
+ });
111
+
112
+ // Store config for compatibility
113
+ this.config = config;
114
+
115
+ // Statistics (for backward compatibility)
130
116
  this.stats = {
131
117
  errors: 0,
132
118
  retries: 0,
@@ -134,285 +120,171 @@ class ErrorHandlerConnector {
134
120
  circuitBreaks: 0,
135
121
  byType: {}
136
122
  };
137
-
123
+
138
124
  // Initialize error type stats
139
125
  Object.values(ErrorTypes).forEach(type => {
140
126
  this.stats.byType[type] = 0;
141
127
  });
142
128
  }
143
-
129
+
144
130
  /**
145
131
  * Classify error into type for appropriate handling
132
+ * Delegates to error-handler-core
146
133
  *
147
134
  * @method classifyError
148
135
  * @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
136
  * @returns {string} Error type from ErrorTypes enum
153
137
  *
154
- * @example <caption>Network Error</caption>
138
+ * @example
155
139
  * const type = errorHandler.classifyError(new Error('ECONNREFUSED'));
156
140
  * // 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
141
  */
168
142
  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;
143
+ return this.core.classifier.classify(error);
198
144
  }
199
-
145
+
200
146
  /**
201
147
  * Determine if error should be retried
148
+ * Delegates to error-handler-core
202
149
  *
203
150
  * @method shouldRetry
204
151
  * @param {Error} error - Error to check
205
152
  * @param {number} [attempts=0] - Current attempt count
206
153
  * @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
154
  */
214
155
  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);
156
+ return this.core.classifier.shouldRetry(error, attempts, this.core.retryHandler.maxRetries);
227
157
  }
228
-
158
+
229
159
  /**
230
160
  * Calculate exponential backoff delay
161
+ * Delegates to error-handler-core
231
162
  *
232
163
  * @method calculateBackoff
233
164
  * @param {number} attempts - Current attempt number (1-based)
234
165
  * @returns {number} Delay in milliseconds
235
- *
236
- * @example
237
- * const delay = errorHandler.calculateBackoff(3);
238
- * // With default config: 1000 * 2^2 = 4000ms
239
166
  */
240
167
  calculateBackoff(attempts) {
241
- const delay = this.retryDelay * Math.pow(this.retryMultiplier, attempts - 1);
242
- return Math.min(delay, this.maxRetryDelay);
168
+ return this.core.retryHandler.calculateBackoff(attempts);
243
169
  }
244
-
170
+
245
171
  /**
246
172
  * Execute function with automatic retry on failure
173
+ * Delegates to error-handler-core
247
174
  *
248
175
  * @async
249
176
  * @method executeWithRetry
250
177
  * @param {Function} fn - Async function to execute
251
178
  * @param {Object} [options={}] - Retry options
252
179
  * @param {number} [options.maxRetries] - Override max retries
253
- * @param {Array<string>} [options.retryOn=[]] - Additional error codes to retry
254
180
  * @param {Function} [options.onRetry] - Callback on retry (error, attempt, delay)
255
181
  * @returns {Promise<*>} Function result
256
182
  *
257
183
  * @throws {Error} Last error if all retries fail
258
184
  *
259
- * @example <caption>Simple Retry</caption>
185
+ * @example
260
186
  * const result = await errorHandler.executeWithRetry(async () => {
261
187
  * return await apiClient.fetchData();
262
188
  * });
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
189
  */
286
190
  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);
191
+ this.stats.errors++;
192
+
193
+ try {
194
+ return await this.core.retryHandler.executeWithRetry(fn, options);
195
+ } catch (error) {
196
+ const errorType = this.classifyError(error);
197
+ this.stats.byType[errorType] = (this.stats.byType[errorType] || 0) + 1;
198
+ throw error;
353
199
  }
354
-
355
- return this.circuitBreakers.get(name);
356
200
  }
357
-
201
+
358
202
  /**
359
203
  * Execute function with circuit breaker protection
204
+ * Delegates to error-handler-core
360
205
  *
361
206
  * @async
362
207
  * @method executeWithCircuitBreaker
363
208
  * @param {string} name - Operation name for circuit breaker
364
209
  * @param {Function} fn - Async function to execute
365
210
  * @param {Object} [options={}] - Circuit breaker options
366
- * @param {number} [options.timeout] - Operation timeout
367
- * @param {number} [options.errorThresholdPercentage] - Error threshold
368
211
  * @returns {Promise<*>} Function result
369
212
  *
370
213
  * @throws {Error} If circuit is open or operation fails
371
214
  *
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
- * }
215
+ * @example
216
+ * const data = await errorHandler.executeWithCircuitBreaker(
217
+ * 'user-api',
218
+ * async () => await userAPI.getUser(id)
395
219
  * );
396
220
  */
397
221
  async executeWithCircuitBreaker(name, fn, options = {}) {
398
- const breaker = this.getCircuitBreaker(name, fn, options);
399
-
400
222
  try {
401
- return await breaker.fire();
223
+ return await this.core.circuitBreaker.executeWithCircuitBreaker(name, fn, options);
402
224
  } catch (error) {
403
225
  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`);
226
+ const errorType = this.classifyError(error);
227
+ this.stats.byType[errorType] = (this.stats.byType[errorType] || 0) + 1;
228
+
229
+ if (error.message && error.message.includes('circuit is open')) {
230
+ this.stats.circuitBreaks++;
408
231
  }
409
-
232
+
410
233
  throw error;
411
234
  }
412
235
  }
413
-
236
+
237
+ /**
238
+ * Log error using unified schema
239
+ * Delegates to error-handler-core
240
+ *
241
+ * @async
242
+ * @method logError
243
+ * @param {Object} errorData - Error data
244
+ * @param {string} errorData.moduleName - Module name
245
+ * @param {string} errorData.operation - Operation name
246
+ * @param {Error} errorData.error - Error object
247
+ * @param {Object} [errorData.context] - Additional context
248
+ * @returns {Promise<Object>} Unified error log entry
249
+ */
250
+ async logError(errorData) {
251
+ return await this.core.logError(errorData);
252
+ }
253
+
254
+ /**
255
+ * Handle error: classify + log + decide action
256
+ * Delegates to error-handler-core
257
+ *
258
+ * @async
259
+ * @method handleError
260
+ * @param {Object} errorData - Error data
261
+ * @param {string} errorData.moduleName - Module name
262
+ * @param {string} errorData.operation - Operation name
263
+ * @param {Error} errorData.error - Error object
264
+ * @param {Object} [errorData.context] - Additional context
265
+ * @returns {Promise<Object>} Handling result
266
+ */
267
+ async handleError(errorData) {
268
+ this.stats.errors++;
269
+ const result = await this.core.handleError(errorData);
270
+
271
+ const errorType = result.errorType;
272
+ this.stats.byType[errorType] = (this.stats.byType[errorType] || 0) + 1;
273
+
274
+ if (result.action === 'retry') {
275
+ this.stats.retries++;
276
+ }
277
+
278
+ if (result.compensated) {
279
+ this.stats.compensations++;
280
+ }
281
+
282
+ return result;
283
+ }
284
+
414
285
  /**
415
286
  * Register compensation handler for an operation
287
+ * Delegates to error-handler-core
416
288
  *
417
289
  * @method registerCompensation
418
290
  * @param {string} operation - Operation name
@@ -421,21 +293,16 @@ class ErrorHandlerConnector {
421
293
  *
422
294
  * @example
423
295
  * errorHandler.registerCompensation('create-order', async (context) => {
424
- * // Rollback order creation
425
296
  * await orderService.cancel(context.orderId);
426
297
  * });
427
- *
428
- * errorHandler.registerCompensation('charge-payment', async (context) => {
429
- * // Refund payment
430
- * await paymentService.refund(context.chargeId);
431
- * });
432
298
  */
433
299
  registerCompensation(operation, handler) {
434
- this.compensationHandlers.set(operation, handler);
300
+ this.core.registerCompensation(operation, handler);
435
301
  }
436
-
302
+
437
303
  /**
438
304
  * Execute compensation for failed operation
305
+ * Delegates to error-handler-core
439
306
  *
440
307
  * @async
441
308
  * @method executeCompensation
@@ -444,82 +311,37 @@ class ErrorHandlerConnector {
444
311
  * @returns {Promise<*>} Compensation result or null if no handler
445
312
  *
446
313
  * @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
314
  */
458
315
  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
316
  try {
471
- this.stats.compensations++;
472
- const result = await handler(context);
473
- console.info(`Compensation executed for: ${operation}`);
317
+ const result = await this.core.compensation.execute(operation, context);
318
+ if (result !== null) {
319
+ this.stats.compensations++;
320
+ }
474
321
  return result;
475
322
  } catch (error) {
476
- console.error(`Compensation failed for ${operation}:`, error);
477
323
  throw error;
478
324
  }
479
325
  }
480
-
326
+
481
327
  /**
482
328
  * Route failed message to dead letter queue
329
+ * Delegates to error-handler-core
483
330
  *
484
331
  * @async
485
332
  * @method routeToDLQ
486
- * @param {Object} mqClient - Message queue client
333
+ * @param {Object} mqClient - Message queue client (optional, can be in config)
487
334
  * @param {Object} message - Original message that failed
488
335
  * @param {Error} error - Error that occurred
489
336
  * @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
337
  */
500
338
  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
339
+ const errorType = this.classifyError(error);
340
+ await this.core.dlqRouter.routeToDLQ(message, error, errorType, {
341
+ queue: message.queue || runtimeCfg.get('unknownQueueName')
518
342
  });
519
-
520
- console.info(`Message routed to DLQ: ${dlqName}`);
521
343
  }
522
-
344
+
523
345
  /**
524
346
  * Create standardized error response
525
347
  *
@@ -527,22 +349,15 @@ class ErrorHandlerConnector {
527
349
  * @param {Error} error - Original error
528
350
  * @param {Object} [context={}] - Additional context
529
351
  * @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
352
  */
538
353
  createErrorResponse(error, context = {}) {
539
354
  const errorType = this.classifyError(error);
540
-
355
+
541
356
  return {
542
357
  success: false,
543
358
  error: {
544
359
  message: error.message,
545
- code: error.code || 'UNKNOWN_ERROR',
360
+ code: error.code || runtimeCfg.get('unknownErrorCode'),
546
361
  type: errorType,
547
362
  retryable: this.shouldRetry(error),
548
363
  timestamp: new Date().toISOString(),
@@ -550,161 +365,27 @@ class ErrorHandlerConnector {
550
365
  }
551
366
  };
552
367
  }
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
-
368
+
671
369
  /**
672
370
  * Get error handler statistics
673
371
  *
674
372
  * @method getStats
675
373
  * @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
374
  */
683
375
  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
-
376
+ const circuitBreakerStats = this.core.getAllCircuitBreakerStates();
377
+
693
378
  return {
694
379
  ...this.stats,
695
380
  circuitBreakers: circuitBreakerStats
696
381
  };
697
382
  }
698
-
383
+
699
384
  /**
700
385
  * Reset all statistics
701
386
  *
702
387
  * @method resetStats
703
388
  * @returns {void}
704
- *
705
- * @example
706
- * errorHandler.resetStats();
707
- * // All counters reset to 0
708
389
  */
709
390
  resetStats() {
710
391
  this.stats = {
@@ -714,51 +395,34 @@ class ErrorHandlerConnector {
714
395
  circuitBreaks: 0,
715
396
  byType: {}
716
397
  };
717
-
398
+
718
399
  Object.values(ErrorTypes).forEach(type => {
719
400
  this.stats.byType[type] = 0;
720
401
  });
721
402
  }
722
-
403
+
404
+ /**
405
+ * Get circuit breaker state
406
+ *
407
+ * @method getCircuitBreakerState
408
+ * @param {string} name - Circuit breaker name
409
+ * @returns {string} State: 'open' | 'half-open' | 'closed'
410
+ */
411
+ getCircuitBreakerState(name) {
412
+ return this.core.getCircuitBreakerState(name);
413
+ }
414
+
723
415
  /**
724
- * Sleep utility
725
- * @private
416
+ * Get all circuit breaker states
417
+ *
418
+ * @method getAllCircuitBreakerStates
419
+ * @returns {Object} Map of circuit breaker states
726
420
  */
727
- async sleep(ms) {
728
- return new Promise(resolve => setTimeout(resolve, ms));
421
+ getAllCircuitBreakerStates() {
422
+ return this.core.getAllCircuitBreakerStates();
729
423
  }
730
424
  }
731
425
 
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
426
  // Export main class as default
763
427
  module.exports = ErrorHandlerConnector;
764
428
 
@@ -780,12 +444,6 @@ module.exports.ErrorCodes = ErrorCodes;
780
444
  * @function create
781
445
  * @param {Object} config - Configuration object
782
446
  * @returns {ErrorHandlerConnector} New error handler instance
783
- *
784
- * @example
785
- * const errorHandler = ErrorHandlerConnector.create({
786
- * maxRetries: 5,
787
- * circuitBreakerEnabled: true
788
- * });
789
447
  */
790
448
  module.exports.create = (config) => new ErrorHandlerConnector(config);
791
449
 
@@ -793,30 +451,4 @@ module.exports.create = (config) => new ErrorHandlerConnector(config);
793
451
  * Current version
794
452
  * @constant {string}
795
453
  */
796
- 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
- };
454
+ module.exports.VERSION = runtimeCfg.get('version');