@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/API.md +0 -0
- package/README.md +225 -0
- package/onlineapps-conn-infra-error-handler-1.0.0.tgz +0 -0
- package/package.json +6 -4
- package/src/config.js +33 -0
- package/src/defaults.js +17 -0
- package/src/index.js +211 -579
- package/{test → tests}/component/error-handling-flow.test.js +56 -34
- package/{test → tests}/unit/ErrorHandlerConnector.test.js +106 -100
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
|
|
4
|
-
*
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
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
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
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}
|
|
82
|
-
* @param {
|
|
83
|
-
* @param {
|
|
84
|
-
* @param {
|
|
85
|
-
* @param {
|
|
86
|
-
* @param {
|
|
87
|
-
* @param {number} [config.
|
|
88
|
-
* @param {number} [config.
|
|
89
|
-
* @param {number} [config.
|
|
90
|
-
* @param {
|
|
91
|
-
* @param {
|
|
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
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
this.
|
|
125
|
-
|
|
126
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
* @
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
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
|
|
223
|
+
return await this.core.circuitBreaker.executeWithCircuitBreaker(name, fn, options);
|
|
402
224
|
} catch (error) {
|
|
403
225
|
this.stats.errors++;
|
|
404
|
-
this.
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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.
|
|
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.
|
|
472
|
-
|
|
473
|
-
|
|
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
|
|
502
|
-
|
|
503
|
-
|
|
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 || '
|
|
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
|
-
*
|
|
725
|
-
*
|
|
416
|
+
* Get all circuit breaker states
|
|
417
|
+
*
|
|
418
|
+
* @method getAllCircuitBreakerStates
|
|
419
|
+
* @returns {Object} Map of circuit breaker states
|
|
726
420
|
*/
|
|
727
|
-
|
|
728
|
-
return
|
|
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 =
|
|
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');
|