@paulduvall/claude-dev-toolkit 0.0.1-alpha.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/README.md +254 -0
- package/bin/claude-commands +132 -0
- package/lib/claude-code-compatibility.js +545 -0
- package/lib/command-selector.js +245 -0
- package/lib/config.js +182 -0
- package/lib/context-utils.js +80 -0
- package/lib/dependency-validator.js +354 -0
- package/lib/error-factory.js +394 -0
- package/lib/error-handler-utils.js +432 -0
- package/lib/error-recovery-system.js +563 -0
- package/lib/failure-recovery-installer.js +370 -0
- package/lib/hook-installer-core.js +330 -0
- package/lib/hook-installer.js +187 -0
- package/lib/hook-metadata-service.js +352 -0
- package/lib/hook-validator.js +358 -0
- package/lib/installation-configuration.js +380 -0
- package/lib/installation-instruction-generator.js +564 -0
- package/lib/installer.js +68 -0
- package/lib/package-manager-service.js +270 -0
- package/lib/permission-error-handler.js +543 -0
- package/lib/platform-utils.js +491 -0
- package/lib/setup-wizard-ui.js +245 -0
- package/lib/setup-wizard.js +355 -0
- package/lib/system-requirements-checker.js +558 -0
- package/lib/utils.js +15 -0
- package/lib/validation-utils.js +320 -0
- package/lib/version-validator-service.js +326 -0
- package/package.json +73 -0
- package/scripts/postinstall.js +182 -0
- package/scripts/validate.js +94 -0
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Recovery System
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive error recovery and retry mechanism for resilient operation handling.
|
|
5
|
+
* Provides automated error recovery strategies and context-aware retry logic.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Intelligent retry strategies with backoff
|
|
9
|
+
* - Context-aware recovery actions
|
|
10
|
+
* - Fallback mechanism support
|
|
11
|
+
* - Recovery metrics and monitoring
|
|
12
|
+
* - Integration with error handling workflow
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const ErrorHandlerUtils = require('./error-handler-utils');
|
|
16
|
+
const ErrorFactory = require('./error-factory');
|
|
17
|
+
|
|
18
|
+
class ErrorRecoverySystem {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.errorHandler = new ErrorHandlerUtils();
|
|
21
|
+
this.errorFactory = new ErrorFactory();
|
|
22
|
+
|
|
23
|
+
this.config = {
|
|
24
|
+
defaultRetryConfig: {
|
|
25
|
+
maxAttempts: 3,
|
|
26
|
+
baseDelay: 1000,
|
|
27
|
+
maxDelay: 30000,
|
|
28
|
+
backoffMultiplier: 2,
|
|
29
|
+
jitter: true
|
|
30
|
+
},
|
|
31
|
+
recoveryStrategies: this._initializeRecoveryStrategies(),
|
|
32
|
+
recoverableErrorCodes: new Set([
|
|
33
|
+
'EACCES', 'EPERM', 'ENOENT', 'TIMEOUT', 'ECONNREFUSED',
|
|
34
|
+
'NOT_FOUND', 'VERSION_MISMATCH', 'VALIDATION_ERROR'
|
|
35
|
+
])
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
this.metrics = {
|
|
39
|
+
attempts: new Map(),
|
|
40
|
+
successes: new Map(),
|
|
41
|
+
failures: new Map(),
|
|
42
|
+
recoveries: new Map()
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Initialize recovery strategies
|
|
48
|
+
* @returns {Object} Recovery strategies by error category
|
|
49
|
+
* @private
|
|
50
|
+
*/
|
|
51
|
+
_initializeRecoveryStrategies() {
|
|
52
|
+
return {
|
|
53
|
+
permission: {
|
|
54
|
+
strategies: [
|
|
55
|
+
'check_permissions',
|
|
56
|
+
'attempt_elevation',
|
|
57
|
+
'try_alternative_location',
|
|
58
|
+
'request_manual_intervention'
|
|
59
|
+
],
|
|
60
|
+
autoRecoverable: true,
|
|
61
|
+
estimatedTime: 30000, // 30 seconds
|
|
62
|
+
priority: 'high'
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
dependency: {
|
|
66
|
+
strategies: [
|
|
67
|
+
'check_package_manager',
|
|
68
|
+
'update_package_lists',
|
|
69
|
+
'install_missing_dependency',
|
|
70
|
+
'try_alternative_package_manager',
|
|
71
|
+
'manual_installation_guide'
|
|
72
|
+
],
|
|
73
|
+
autoRecoverable: true,
|
|
74
|
+
estimatedTime: 120000, // 2 minutes
|
|
75
|
+
priority: 'high'
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
network: {
|
|
79
|
+
strategies: [
|
|
80
|
+
'check_connectivity',
|
|
81
|
+
'retry_with_backoff',
|
|
82
|
+
'try_alternative_endpoint',
|
|
83
|
+
'use_cached_data',
|
|
84
|
+
'offline_mode'
|
|
85
|
+
],
|
|
86
|
+
autoRecoverable: true,
|
|
87
|
+
estimatedTime: 60000, // 1 minute
|
|
88
|
+
priority: 'medium'
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
validation: {
|
|
92
|
+
strategies: [
|
|
93
|
+
'analyze_validation_failure',
|
|
94
|
+
'apply_automatic_fixes',
|
|
95
|
+
'use_default_values',
|
|
96
|
+
'request_user_correction',
|
|
97
|
+
'skip_validation'
|
|
98
|
+
],
|
|
99
|
+
autoRecoverable: true,
|
|
100
|
+
estimatedTime: 15000, // 15 seconds
|
|
101
|
+
priority: 'medium'
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
system: {
|
|
105
|
+
strategies: [
|
|
106
|
+
'check_system_resources',
|
|
107
|
+
'cleanup_temporary_files',
|
|
108
|
+
'restart_services',
|
|
109
|
+
'escalate_to_administrator'
|
|
110
|
+
],
|
|
111
|
+
autoRecoverable: false,
|
|
112
|
+
estimatedTime: 300000, // 5 minutes
|
|
113
|
+
priority: 'critical'
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Wrap function with comprehensive error recovery
|
|
120
|
+
* @param {Function} fn - Function to wrap
|
|
121
|
+
* @param {Object} options - Recovery options
|
|
122
|
+
* @returns {Function} Wrapped function with recovery
|
|
123
|
+
*/
|
|
124
|
+
wrapWithRecovery(fn, options = {}) {
|
|
125
|
+
const {
|
|
126
|
+
retryConfig = this.config.defaultRetryConfig,
|
|
127
|
+
context = {},
|
|
128
|
+
fallbacks = [],
|
|
129
|
+
enableAutoRecovery = true,
|
|
130
|
+
recordMetrics = true
|
|
131
|
+
} = options;
|
|
132
|
+
|
|
133
|
+
return async (...args) => {
|
|
134
|
+
const operationId = this._generateOperationId();
|
|
135
|
+
const startTime = Date.now();
|
|
136
|
+
|
|
137
|
+
if (recordMetrics) {
|
|
138
|
+
this._recordAttempt(operationId, context);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let lastError = null;
|
|
142
|
+
let recoveryAttempted = false;
|
|
143
|
+
|
|
144
|
+
for (let attempt = 1; attempt <= retryConfig.maxAttempts; attempt++) {
|
|
145
|
+
try {
|
|
146
|
+
const result = await fn(...args);
|
|
147
|
+
|
|
148
|
+
if (recordMetrics) {
|
|
149
|
+
this._recordSuccess(operationId, attempt, Date.now() - startTime);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return result;
|
|
153
|
+
|
|
154
|
+
} catch (error) {
|
|
155
|
+
lastError = error;
|
|
156
|
+
const enhancedError = this.errorHandler.createEnhancedError(error, {
|
|
157
|
+
...context,
|
|
158
|
+
attempt,
|
|
159
|
+
operationId
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Log the error
|
|
163
|
+
this.errorHandler.logError(enhancedError, {
|
|
164
|
+
attempt,
|
|
165
|
+
maxAttempts: retryConfig.maxAttempts,
|
|
166
|
+
operationId
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Check if error is recoverable
|
|
170
|
+
if (!this._isRecoverable(enhancedError)) {
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Attempt automatic recovery if enabled and not already attempted
|
|
175
|
+
if (enableAutoRecovery && !recoveryAttempted && this._shouldAttemptRecovery(enhancedError)) {
|
|
176
|
+
const recoveryResult = await this.attemptRecovery(enhancedError, context);
|
|
177
|
+
recoveryAttempted = true;
|
|
178
|
+
|
|
179
|
+
if (recoveryResult.success) {
|
|
180
|
+
// Retry immediately after successful recovery
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Don't retry if this is the last attempt
|
|
186
|
+
if (attempt === retryConfig.maxAttempts) {
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Calculate delay with backoff and jitter
|
|
191
|
+
const delay = this._calculateDelay(attempt, retryConfig);
|
|
192
|
+
await this._sleep(delay);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Try fallback functions
|
|
197
|
+
for (const fallback of fallbacks) {
|
|
198
|
+
try {
|
|
199
|
+
const fallbackResult = await fallback(lastError, ...args);
|
|
200
|
+
|
|
201
|
+
if (recordMetrics) {
|
|
202
|
+
this._recordSuccess(operationId, 'fallback', Date.now() - startTime);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return fallbackResult;
|
|
206
|
+
|
|
207
|
+
} catch (fallbackError) {
|
|
208
|
+
this.errorHandler.logError(
|
|
209
|
+
this.errorHandler.createEnhancedError(fallbackError, {
|
|
210
|
+
...context,
|
|
211
|
+
fallback: true,
|
|
212
|
+
operationId
|
|
213
|
+
})
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Record final failure
|
|
219
|
+
if (recordMetrics) {
|
|
220
|
+
this._recordFailure(operationId, lastError, Date.now() - startTime);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
throw lastError;
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Attempt automatic error recovery
|
|
229
|
+
* @param {Object} enhancedError - Enhanced error object
|
|
230
|
+
* @param {Object} context - Recovery context
|
|
231
|
+
* @returns {Promise<Object>} Recovery result
|
|
232
|
+
*/
|
|
233
|
+
async attemptRecovery(enhancedError, context = {}) {
|
|
234
|
+
const strategy = this._getRecoveryStrategy(enhancedError);
|
|
235
|
+
if (!strategy) {
|
|
236
|
+
return { success: false, reason: 'No recovery strategy available' };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const recoveryId = this._generateRecoveryId();
|
|
240
|
+
const startTime = Date.now();
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const result = await this._executeRecoveryStrategy(enhancedError, strategy, {
|
|
244
|
+
...context,
|
|
245
|
+
recoveryId
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
this._recordRecovery(recoveryId, enhancedError, result, Date.now() - startTime);
|
|
249
|
+
|
|
250
|
+
return result;
|
|
251
|
+
|
|
252
|
+
} catch (recoveryError) {
|
|
253
|
+
const failureResult = {
|
|
254
|
+
success: false,
|
|
255
|
+
error: recoveryError,
|
|
256
|
+
reason: 'Recovery strategy failed'
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
this._recordRecovery(recoveryId, enhancedError, failureResult, Date.now() - startTime);
|
|
260
|
+
|
|
261
|
+
return failureResult;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Execute specific recovery strategy
|
|
267
|
+
* @param {Object} enhancedError - Enhanced error object
|
|
268
|
+
* @param {Object} strategy - Recovery strategy
|
|
269
|
+
* @param {Object} context - Recovery context
|
|
270
|
+
* @returns {Promise<Object>} Strategy execution result
|
|
271
|
+
* @private
|
|
272
|
+
*/
|
|
273
|
+
async _executeRecoveryStrategy(enhancedError, strategy, context) {
|
|
274
|
+
const result = {
|
|
275
|
+
success: false,
|
|
276
|
+
actions: [],
|
|
277
|
+
duration: 0,
|
|
278
|
+
strategy: strategy
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const startTime = Date.now();
|
|
282
|
+
|
|
283
|
+
for (const strategyStep of strategy.strategies) {
|
|
284
|
+
const stepResult = await this._executeRecoveryStep(strategyStep, enhancedError, context);
|
|
285
|
+
|
|
286
|
+
result.actions.push({
|
|
287
|
+
step: strategyStep,
|
|
288
|
+
result: stepResult,
|
|
289
|
+
timestamp: new Date().toISOString()
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// If step succeeds, we can potentially stop
|
|
293
|
+
if (stepResult.success && stepResult.recoveryComplete) {
|
|
294
|
+
result.success = true;
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// If step indicates fatal failure, stop recovery
|
|
299
|
+
if (stepResult.fatal) {
|
|
300
|
+
result.reason = stepResult.reason || 'Fatal error during recovery';
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
result.duration = Date.now() - startTime;
|
|
306
|
+
|
|
307
|
+
return result;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Execute individual recovery step
|
|
312
|
+
* @param {string} step - Recovery step name
|
|
313
|
+
* @param {Object} enhancedError - Enhanced error object
|
|
314
|
+
* @param {Object} context - Recovery context
|
|
315
|
+
* @returns {Promise<Object>} Step execution result
|
|
316
|
+
* @private
|
|
317
|
+
*/
|
|
318
|
+
async _executeRecoveryStep(step, enhancedError, context) {
|
|
319
|
+
const stepResult = {
|
|
320
|
+
success: false,
|
|
321
|
+
recoveryComplete: false,
|
|
322
|
+
fatal: false,
|
|
323
|
+
details: {},
|
|
324
|
+
reason: null
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
try {
|
|
328
|
+
switch (step) {
|
|
329
|
+
case 'check_permissions':
|
|
330
|
+
stepResult.success = await this._checkPermissions(enhancedError.path);
|
|
331
|
+
stepResult.details.permissions = 'checked';
|
|
332
|
+
break;
|
|
333
|
+
|
|
334
|
+
case 'attempt_elevation':
|
|
335
|
+
stepResult.success = await this._attemptElevation(context);
|
|
336
|
+
stepResult.details.elevation = 'attempted';
|
|
337
|
+
break;
|
|
338
|
+
|
|
339
|
+
case 'check_connectivity':
|
|
340
|
+
stepResult.success = await this._checkNetworkConnectivity();
|
|
341
|
+
stepResult.details.connectivity = 'checked';
|
|
342
|
+
break;
|
|
343
|
+
|
|
344
|
+
case 'retry_with_backoff':
|
|
345
|
+
// This is handled by the main retry logic
|
|
346
|
+
stepResult.success = true;
|
|
347
|
+
stepResult.details.backoff = 'applied';
|
|
348
|
+
break;
|
|
349
|
+
|
|
350
|
+
case 'check_package_manager':
|
|
351
|
+
stepResult.success = await this._checkPackageManager();
|
|
352
|
+
stepResult.details.packageManager = 'checked';
|
|
353
|
+
break;
|
|
354
|
+
|
|
355
|
+
case 'analyze_validation_failure':
|
|
356
|
+
stepResult.success = await this._analyzeValidationFailure(enhancedError);
|
|
357
|
+
stepResult.details.analysis = 'completed';
|
|
358
|
+
break;
|
|
359
|
+
|
|
360
|
+
case 'use_default_values':
|
|
361
|
+
stepResult.success = true;
|
|
362
|
+
stepResult.recoveryComplete = true;
|
|
363
|
+
stepResult.details.defaults = 'applied';
|
|
364
|
+
break;
|
|
365
|
+
|
|
366
|
+
default:
|
|
367
|
+
stepResult.success = false;
|
|
368
|
+
stepResult.reason = `Unknown recovery step: ${step}`;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
} catch (stepError) {
|
|
372
|
+
stepResult.success = false;
|
|
373
|
+
stepResult.fatal = this._isStepFatal(stepError);
|
|
374
|
+
stepResult.reason = stepError.message;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return stepResult;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Check if error is recoverable
|
|
382
|
+
* @param {Object} enhancedError - Enhanced error object
|
|
383
|
+
* @returns {boolean} True if recoverable
|
|
384
|
+
* @private
|
|
385
|
+
*/
|
|
386
|
+
_isRecoverable(enhancedError) {
|
|
387
|
+
return this.config.recoverableErrorCodes.has(enhancedError.code) ||
|
|
388
|
+
enhancedError.recoverable === true;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Check if automatic recovery should be attempted
|
|
393
|
+
* @param {Object} enhancedError - Enhanced error object
|
|
394
|
+
* @returns {boolean} True if recovery should be attempted
|
|
395
|
+
* @private
|
|
396
|
+
*/
|
|
397
|
+
_shouldAttemptRecovery(enhancedError) {
|
|
398
|
+
const strategy = this._getRecoveryStrategy(enhancedError);
|
|
399
|
+
return strategy && strategy.autoRecoverable;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Get recovery strategy for error
|
|
404
|
+
* @param {Object} enhancedError - Enhanced error object
|
|
405
|
+
* @returns {Object|null} Recovery strategy or null
|
|
406
|
+
* @private
|
|
407
|
+
*/
|
|
408
|
+
_getRecoveryStrategy(enhancedError) {
|
|
409
|
+
return this.config.recoveryStrategies[enhancedError.category] || null;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Calculate retry delay with backoff and jitter
|
|
414
|
+
* @param {number} attempt - Current attempt number
|
|
415
|
+
* @param {Object} retryConfig - Retry configuration
|
|
416
|
+
* @returns {number} Delay in milliseconds
|
|
417
|
+
* @private
|
|
418
|
+
*/
|
|
419
|
+
_calculateDelay(attempt, retryConfig) {
|
|
420
|
+
let delay = retryConfig.baseDelay * Math.pow(retryConfig.backoffMultiplier, attempt - 1);
|
|
421
|
+
|
|
422
|
+
// Apply maximum delay limit
|
|
423
|
+
delay = Math.min(delay, retryConfig.maxDelay);
|
|
424
|
+
|
|
425
|
+
// Add jitter if enabled
|
|
426
|
+
if (retryConfig.jitter) {
|
|
427
|
+
delay = delay * (0.5 + Math.random() * 0.5);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return Math.floor(delay);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Sleep for specified duration
|
|
435
|
+
* @param {number} ms - Milliseconds to sleep
|
|
436
|
+
* @returns {Promise} Sleep promise
|
|
437
|
+
* @private
|
|
438
|
+
*/
|
|
439
|
+
_sleep(ms) {
|
|
440
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Simple recovery step implementations (would be more sophisticated in practice)
|
|
445
|
+
*/
|
|
446
|
+
async _checkPermissions(path) {
|
|
447
|
+
// Simplified permission check
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async _attemptElevation(context) {
|
|
452
|
+
// Simplified elevation attempt
|
|
453
|
+
return false; // Cannot actually elevate in this context
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async _checkNetworkConnectivity() {
|
|
457
|
+
// Simplified network check
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
async _checkPackageManager() {
|
|
462
|
+
// Simplified package manager check
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
async _analyzeValidationFailure(enhancedError) {
|
|
467
|
+
// Simplified validation analysis
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Check if step error is fatal
|
|
473
|
+
* @param {Error} error - Step error
|
|
474
|
+
* @returns {boolean} True if fatal
|
|
475
|
+
* @private
|
|
476
|
+
*/
|
|
477
|
+
_isStepFatal(error) {
|
|
478
|
+
const fatalCodes = ['SYSTEM_FAILURE', 'CORRUPTION'];
|
|
479
|
+
return fatalCodes.includes(error.code);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Metrics and monitoring methods
|
|
484
|
+
*/
|
|
485
|
+
_generateOperationId() {
|
|
486
|
+
return `op_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
_generateRecoveryId() {
|
|
490
|
+
return `rec_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
_recordAttempt(operationId, context) {
|
|
494
|
+
this.metrics.attempts.set(operationId, {
|
|
495
|
+
timestamp: new Date().toISOString(),
|
|
496
|
+
context
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
_recordSuccess(operationId, attempt, duration) {
|
|
501
|
+
this.metrics.successes.set(operationId, {
|
|
502
|
+
attempt,
|
|
503
|
+
duration,
|
|
504
|
+
timestamp: new Date().toISOString()
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
_recordFailure(operationId, error, duration) {
|
|
509
|
+
this.metrics.failures.set(operationId, {
|
|
510
|
+
error: error.code || error.message,
|
|
511
|
+
duration,
|
|
512
|
+
timestamp: new Date().toISOString()
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
_recordRecovery(recoveryId, error, result, duration) {
|
|
517
|
+
this.metrics.recoveries.set(recoveryId, {
|
|
518
|
+
errorCode: error.code,
|
|
519
|
+
success: result.success,
|
|
520
|
+
duration,
|
|
521
|
+
timestamp: new Date().toISOString()
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Get recovery system metrics
|
|
527
|
+
* @returns {Object} System metrics
|
|
528
|
+
*/
|
|
529
|
+
getMetrics() {
|
|
530
|
+
return {
|
|
531
|
+
attempts: this.metrics.attempts.size,
|
|
532
|
+
successes: this.metrics.successes.size,
|
|
533
|
+
failures: this.metrics.failures.size,
|
|
534
|
+
recoveries: this.metrics.recoveries.size,
|
|
535
|
+
successRate: this.metrics.successes.size / Math.max(this.metrics.attempts.size, 1),
|
|
536
|
+
recoveryRate: Array.from(this.metrics.recoveries.values())
|
|
537
|
+
.filter(r => r.success).length / Math.max(this.metrics.recoveries.size, 1)
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Clear metrics (useful for testing)
|
|
543
|
+
*/
|
|
544
|
+
clearMetrics() {
|
|
545
|
+
this.metrics.attempts.clear();
|
|
546
|
+
this.metrics.successes.clear();
|
|
547
|
+
this.metrics.failures.clear();
|
|
548
|
+
this.metrics.recoveries.clear();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Get singleton instance
|
|
553
|
+
* @returns {ErrorRecoverySystem} Recovery system instance
|
|
554
|
+
*/
|
|
555
|
+
static getInstance() {
|
|
556
|
+
if (!ErrorRecoverySystem._instance) {
|
|
557
|
+
ErrorRecoverySystem._instance = new ErrorRecoverySystem();
|
|
558
|
+
}
|
|
559
|
+
return ErrorRecoverySystem._instance;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
module.exports = ErrorRecoverySystem;
|