@aetherframework/database 1.1.1 → 1.1.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/examples/mysql-test-pressure.js +1530 -0
- package/examples/test-direct.js +116 -0
- package/examples/transaction_example.js +127 -0
- package/package.json +3 -1
- package/src/DatabaseManager.js +565 -0
- package/src/core/ConnectionManager.js +351 -0
- package/src/core/DatabaseFactory.js +188 -0
- package/src/core/MongoQueryBuilder.js +576 -0
- package/src/core/PluginManager.js +968 -0
- package/src/core/QueryBuilder.js +4394 -0
- package/src/core/TransactionManager.js +40 -0
- package/src/drivers/clickhouse-driver.js +272 -0
- package/src/drivers/index.js +273 -0
- package/src/drivers/mongodb-driver.js +87 -0
- package/src/drivers/mssql-driver.js +117 -0
- package/src/drivers/mysql-driver.js +169 -0
- package/src/drivers/oracle-driver.js +101 -0
- package/src/drivers/postgres-driver.js +234 -0
- package/src/drivers/redis-driver.js +52 -0
- package/src/drivers/sqlite-driver.js +67 -0
- package/src/middleware/connection-pool.js +455 -0
- package/src/middleware/performance-monitor.js +652 -0
- package/src/middleware/query-cache.js +500 -0
- package/src/middleware/query-logger.js +262 -0
- package/src/plugins/AuditPlugin.js +447 -0
- package/src/plugins/BasePlugin.js +418 -0
- package/src/plugins/BatchOperationPlugin.js +165 -0
- package/src/plugins/CachePlugin.js +407 -0
- package/src/plugins/CtePlugin.js +523 -0
- package/src/plugins/DistributedPlugin.js +543 -0
- package/src/plugins/EncryptionPlugin.js +211 -0
- package/src/plugins/FullTextSearchPlugin.js +164 -0
- package/src/plugins/GeospatialPlugin.js +219 -0
- package/src/plugins/GraphQLPlugin.js +162 -0
- package/src/plugins/HookPlugin.js +211 -0
- package/src/plugins/JsonPlugin.js +366 -0
- package/src/plugins/OptimisticLockPlugin.js +374 -0
- package/src/plugins/PerformancePlugin.js +175 -0
- package/src/plugins/ResiliencePlugin.js +114 -0
- package/src/plugins/ShardingPlugin.js +227 -0
- package/src/plugins/SoftDeletePlugin.js +258 -0
- package/src/plugins/SyncPlugin.js +373 -0
- package/src/plugins/VersioningPlugin.js +314 -0
- package/src/plugins/WindowFunctionPlugin.js +343 -0
- package/src/utils/config-loader.js +632 -0
- package/src/utils/error-handler.js +724 -0
- package/src/utils/migration-runner.js +1066 -0
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license MIT
|
|
3
|
+
* Copyright (c) 2026-present AetherFramework Contributors.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
* @module @aetherframework/src/utils/error-handler
|
|
6
|
+
*/
|
|
7
|
+
import { EventEmitter } from 'events';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Error Handler - Centralized error handling for database operations
|
|
11
|
+
*/
|
|
12
|
+
class ErrorHandler extends EventEmitter {
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
super();
|
|
15
|
+
this.options = {
|
|
16
|
+
logErrors: options.logErrors !== false,
|
|
17
|
+
throwErrors: options.throwErrors !== false,
|
|
18
|
+
retryOnError: options.retryOnError || false,
|
|
19
|
+
maxRetries: options.maxRetries || 3,
|
|
20
|
+
retryDelay: options.retryDelay || 1000,
|
|
21
|
+
errorCodes: {
|
|
22
|
+
connection: ['ECONNREFUSED', 'ETIMEDOUT', 'EHOSTUNREACH', 'ENOTFOUND'],
|
|
23
|
+
timeout: ['ETIMEDOUT', 'ESOCKETTIMEDOUT'],
|
|
24
|
+
deadlock: ['ER_LOCK_DEADLOCK', '40P01', '40001'],
|
|
25
|
+
duplicate: ['ER_DUP_ENTRY', '23505', '23000'],
|
|
26
|
+
constraint: ['ER_NO_REFERENCED_ROW', 'ER_ROW_IS_REFERENCED', '23503', '23504'],
|
|
27
|
+
syntax: ['ER_PARSE_ERROR', '42601', '42000'],
|
|
28
|
+
...options.errorCodes
|
|
29
|
+
},
|
|
30
|
+
...options
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
this.errorStats = {
|
|
34
|
+
totalErrors: 0,
|
|
35
|
+
byType: {},
|
|
36
|
+
byConnection: {},
|
|
37
|
+
byDriver: {},
|
|
38
|
+
recentErrors: []
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Handle database error
|
|
44
|
+
* @param {Error} error - Error object
|
|
45
|
+
* @param {Object} context - Error context
|
|
46
|
+
* @returns {Promise<Error>} Handled error
|
|
47
|
+
*/
|
|
48
|
+
async handle(error, context = {}) {
|
|
49
|
+
this.errorStats.totalErrors++;
|
|
50
|
+
|
|
51
|
+
// Classify error
|
|
52
|
+
const errorType = this.classifyError(error);
|
|
53
|
+
const errorCode = error.code || error.errno || 'UNKNOWN';
|
|
54
|
+
const errorMessage = error.message || 'Unknown error';
|
|
55
|
+
|
|
56
|
+
// Update error statistics
|
|
57
|
+
this.updateErrorStats(errorType, errorCode, context);
|
|
58
|
+
|
|
59
|
+
// Create enhanced error object
|
|
60
|
+
const enhancedError = this.enhanceError(error, errorType, errorCode, context);
|
|
61
|
+
|
|
62
|
+
// Log error if enabled
|
|
63
|
+
if (this.options.logErrors) {
|
|
64
|
+
this.logError(enhancedError, context);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Emit error event
|
|
68
|
+
this.emit('error', enhancedError);
|
|
69
|
+
|
|
70
|
+
// Check if error is retryable
|
|
71
|
+
if (this.options.retryOnError && this.isRetryableError(errorType, errorCode)) {
|
|
72
|
+
return this.handleRetryableError(enhancedError, context);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Throw error if configured
|
|
76
|
+
if (this.options.throwErrors) {
|
|
77
|
+
throw enhancedError;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return enhancedError;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Classify error type
|
|
85
|
+
* @param {Error} error - Error object
|
|
86
|
+
* @returns {string} Error type
|
|
87
|
+
*/
|
|
88
|
+
classifyError(error) {
|
|
89
|
+
const errorCode = error.code || error.errno || '';
|
|
90
|
+
const errorMessage = error.message || '';
|
|
91
|
+
|
|
92
|
+
// Check connection errors
|
|
93
|
+
if (this.options.errorCodes.connection.includes(errorCode) ||
|
|
94
|
+
errorMessage.includes('connection') ||
|
|
95
|
+
errorMessage.includes('connect')) {
|
|
96
|
+
return 'connection';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check timeout errors
|
|
100
|
+
if (this.options.errorCodes.timeout.includes(errorCode) ||
|
|
101
|
+
errorMessage.includes('timeout') ||
|
|
102
|
+
errorMessage.includes('timed out')) {
|
|
103
|
+
return 'timeout';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check deadlock errors
|
|
107
|
+
if (this.options.errorCodes.deadlock.includes(errorCode) ||
|
|
108
|
+
errorMessage.includes('deadlock') ||
|
|
109
|
+
errorMessage.includes('lock')) {
|
|
110
|
+
return 'deadlock';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check duplicate errors
|
|
114
|
+
if (this.options.errorCodes.duplicate.includes(errorCode) ||
|
|
115
|
+
errorMessage.includes('duplicate') ||
|
|
116
|
+
errorMessage.includes('unique constraint')) {
|
|
117
|
+
return 'duplicate';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Check constraint errors
|
|
121
|
+
if (this.options.errorCodes.constraint.includes(errorCode) ||
|
|
122
|
+
errorMessage.includes('constraint') ||
|
|
123
|
+
errorMessage.includes('foreign key')) {
|
|
124
|
+
return 'constraint';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check syntax errors
|
|
128
|
+
if (this.options.errorCodes.syntax.includes(errorCode) ||
|
|
129
|
+
errorMessage.includes('syntax') ||
|
|
130
|
+
errorMessage.includes('parse')) {
|
|
131
|
+
return 'syntax';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Check authentication errors
|
|
135
|
+
if (errorMessage.includes('authentication') ||
|
|
136
|
+
errorMessage.includes('password') ||
|
|
137
|
+
errorMessage.includes('access denied')) {
|
|
138
|
+
return 'authentication';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Check permission errors
|
|
142
|
+
if (errorMessage.includes('permission') ||
|
|
143
|
+
errorMessage.includes('access denied') ||
|
|
144
|
+
errorMessage.includes('privilege')) {
|
|
145
|
+
return 'permission';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Check resource errors
|
|
149
|
+
if (errorMessage.includes('resource') ||
|
|
150
|
+
errorMessage.includes('memory') ||
|
|
151
|
+
errorMessage.includes('disk')) {
|
|
152
|
+
return 'resource';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return 'unknown';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Update error statistics
|
|
160
|
+
* @param {string} errorType - Error type
|
|
161
|
+
* @param {string} errorCode - Error code
|
|
162
|
+
* @param {Object} context - Error context
|
|
163
|
+
*/
|
|
164
|
+
updateErrorStats(errorType, errorCode, context) {
|
|
165
|
+
// Update by type
|
|
166
|
+
if (!this.errorStats.byType[errorType]) {
|
|
167
|
+
this.errorStats.byType[errorType] = 0;
|
|
168
|
+
}
|
|
169
|
+
this.errorStats.byType[errorType]++;
|
|
170
|
+
|
|
171
|
+
// Update by connection
|
|
172
|
+
const connection = context.connection || 'unknown';
|
|
173
|
+
if (!this.errorStats.byConnection[connection]) {
|
|
174
|
+
this.errorStats.byConnection[connection] = 0;
|
|
175
|
+
}
|
|
176
|
+
this.errorStats.byConnection[connection]++;
|
|
177
|
+
|
|
178
|
+
// Update by driver
|
|
179
|
+
const driver = context.driver || 'unknown';
|
|
180
|
+
if (!this.errorStats.byDriver[driver]) {
|
|
181
|
+
this.errorStats.byDriver[driver] = 0;
|
|
182
|
+
}
|
|
183
|
+
this.errorStats.byDriver[driver]++;
|
|
184
|
+
|
|
185
|
+
// Add to recent errors
|
|
186
|
+
const errorRecord = {
|
|
187
|
+
timestamp: new Date().toISOString(),
|
|
188
|
+
type: errorType,
|
|
189
|
+
code: errorCode,
|
|
190
|
+
message: context.message || 'Unknown error',
|
|
191
|
+
connection,
|
|
192
|
+
driver,
|
|
193
|
+
sql: context.sql,
|
|
194
|
+
params: context.params,
|
|
195
|
+
stack: context.stack
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
this.errorStats.recentErrors.push(errorRecord);
|
|
199
|
+
if (this.errorStats.recentErrors.length > 100) {
|
|
200
|
+
this.errorStats.recentErrors.shift();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Enhance error with additional information
|
|
206
|
+
* @param {Error} error - Original error
|
|
207
|
+
* @param {string} errorType - Error type
|
|
208
|
+
* @param {string} errorCode - Error code
|
|
209
|
+
* @param {Object} context - Error context
|
|
210
|
+
* @returns {Error} Enhanced error
|
|
211
|
+
*/
|
|
212
|
+
enhanceError(error, errorType, errorCode, context) {
|
|
213
|
+
const enhancedError = new Error(error.message);
|
|
214
|
+
enhancedError.name = error.name || 'DatabaseError';
|
|
215
|
+
enhancedError.code = errorCode;
|
|
216
|
+
enhancedError.type = errorType;
|
|
217
|
+
enhancedError.timestamp = new Date().toISOString();
|
|
218
|
+
enhancedError.context = context;
|
|
219
|
+
enhancedError.stack = error.stack;
|
|
220
|
+
enhancedError.originalError = error;
|
|
221
|
+
|
|
222
|
+
// Add suggestions based on error type
|
|
223
|
+
enhancedError.suggestions = this.getErrorSuggestions(errorType, errorCode, context);
|
|
224
|
+
|
|
225
|
+
// Add recovery strategies
|
|
226
|
+
enhancedError.recoveryStrategies = this.getRecoveryStrategies(errorType, errorCode);
|
|
227
|
+
|
|
228
|
+
return enhancedError;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get error suggestions
|
|
233
|
+
* @param {string} errorType - Error type
|
|
234
|
+
* @param {string} errorCode - Error code
|
|
235
|
+
* @param {Object} context - Error context
|
|
236
|
+
* @returns {Array} Error suggestions
|
|
237
|
+
*/
|
|
238
|
+
getErrorSuggestions(errorType, errorCode, context) {
|
|
239
|
+
const suggestions = [];
|
|
240
|
+
|
|
241
|
+
switch (errorType) {
|
|
242
|
+
case 'connection':
|
|
243
|
+
suggestions.push('Check database server status');
|
|
244
|
+
suggestions.push('Verify connection parameters (host, port, username, password)');
|
|
245
|
+
suggestions.push('Check network connectivity');
|
|
246
|
+
suggestions.push('Verify firewall settings');
|
|
247
|
+
break;
|
|
248
|
+
case 'timeout':
|
|
249
|
+
suggestions.push('Increase query timeout settings');
|
|
250
|
+
suggestions.push('Optimize slow queries');
|
|
251
|
+
suggestions.push('Check database server load');
|
|
252
|
+
suggestions.push('Consider adding database indexes');
|
|
253
|
+
break;
|
|
254
|
+
case 'deadlock':
|
|
255
|
+
suggestions.push('Retry the transaction');
|
|
256
|
+
suggestions.push('Review transaction isolation levels');
|
|
257
|
+
suggestions.push('Optimize transaction order');
|
|
258
|
+
suggestions.push('Consider using row-level locking');
|
|
259
|
+
break;
|
|
260
|
+
case 'duplicate':
|
|
261
|
+
suggestions.push('Check for existing records before insert');
|
|
262
|
+
suggestions.push('Use UPSERT operations');
|
|
263
|
+
suggestions.push('Handle duplicate key gracefully');
|
|
264
|
+
suggestions.push('Review unique constraints');
|
|
265
|
+
break;
|
|
266
|
+
case 'constraint':
|
|
267
|
+
suggestions.push('Check foreign key relationships');
|
|
268
|
+
suggestions.push('Verify data integrity');
|
|
269
|
+
suggestions.push('Review constraint definitions');
|
|
270
|
+
suggestions.push('Check referenced records exist');
|
|
271
|
+
break;
|
|
272
|
+
case 'syntax':
|
|
273
|
+
suggestions.push('Review SQL syntax');
|
|
274
|
+
suggestions.push('Check for missing or extra parentheses');
|
|
275
|
+
suggestions.push('Verify table and column names');
|
|
276
|
+
suggestions.push('Check for reserved keyword usage');
|
|
277
|
+
break;
|
|
278
|
+
case 'authentication':
|
|
279
|
+
suggestions.push('Verify username and password');
|
|
280
|
+
suggestions.push('Check user permissions');
|
|
281
|
+
suggestions.push('Verify authentication method');
|
|
282
|
+
suggestions.push('Check password expiration');
|
|
283
|
+
break;
|
|
284
|
+
case 'permission':
|
|
285
|
+
suggestions.push('Check user permissions');
|
|
286
|
+
suggestions.push('Verify database privileges');
|
|
287
|
+
suggestions.push('Review access control lists');
|
|
288
|
+
suggestions.push('Contact database administrator');
|
|
289
|
+
break;
|
|
290
|
+
case 'resource':
|
|
291
|
+
suggestions.push('Increase database memory limits');
|
|
292
|
+
suggestions.push('Optimize query performance');
|
|
293
|
+
suggestions.push('Add database indexes');
|
|
294
|
+
suggestions.push('Consider database scaling');
|
|
295
|
+
break;
|
|
296
|
+
default:
|
|
297
|
+
suggestions.push('Review error details');
|
|
298
|
+
suggestions.push('Check database logs');
|
|
299
|
+
suggestions.push('Contact database administrator');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Add context-specific suggestions
|
|
303
|
+
if (context.sql) {
|
|
304
|
+
suggestions.push('Review the executed SQL query');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (context.params && context.params.length > 0) {
|
|
308
|
+
suggestions.push('Check query parameter values');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return suggestions;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Get recovery strategies
|
|
316
|
+
* @param {string} errorType - Error type
|
|
317
|
+
* @param {string} errorCode - Error code
|
|
318
|
+
* @returns {Array} Recovery strategies
|
|
319
|
+
*/
|
|
320
|
+
getRecoveryStrategies(errorType, errorCode) {
|
|
321
|
+
const strategies = [];
|
|
322
|
+
|
|
323
|
+
switch (errorType) {
|
|
324
|
+
case 'connection':
|
|
325
|
+
strategies.push('Retry connection with exponential backoff');
|
|
326
|
+
strategies.push('Fallback to backup server if available');
|
|
327
|
+
strategies.push('Use cached data if applicable');
|
|
328
|
+
break;
|
|
329
|
+
case 'timeout':
|
|
330
|
+
strategies.push('Retry with increased timeout');
|
|
331
|
+
strategies.push('Break query into smaller batches');
|
|
332
|
+
strategies.push('Use asynchronous processing');
|
|
333
|
+
break;
|
|
334
|
+
case 'deadlock':
|
|
335
|
+
strategies.push('Automatic retry with random delay');
|
|
336
|
+
strategies.push('Implement deadlock detection and resolution');
|
|
337
|
+
strategies.push('Use optimistic locking');
|
|
338
|
+
break;
|
|
339
|
+
case 'duplicate':
|
|
340
|
+
strategies.push('Update existing record instead of insert');
|
|
341
|
+
strategies.push('Ignore duplicate and continue');
|
|
342
|
+
strategies.push('Return existing record');
|
|
343
|
+
break;
|
|
344
|
+
case 'constraint':
|
|
345
|
+
strategies.push('Validate data before operation');
|
|
346
|
+
strategies.push('Use transactions with proper rollback');
|
|
347
|
+
strategies.push('Implement data validation at application level');
|
|
348
|
+
break;
|
|
349
|
+
case 'syntax':
|
|
350
|
+
strategies.push('Validate SQL syntax before execution');
|
|
351
|
+
strategies.push('Use parameterized queries');
|
|
352
|
+
strategies.push('Implement SQL linting');
|
|
353
|
+
break;
|
|
354
|
+
default:
|
|
355
|
+
strategies.push('Log error for investigation');
|
|
356
|
+
strategies.push('Provide user-friendly error message');
|
|
357
|
+
strategies.push('Implement circuit breaker pattern');
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return strategies;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Log error
|
|
365
|
+
* @param {Error} error - Error object
|
|
366
|
+
* @param {Object} context - Error context
|
|
367
|
+
*/
|
|
368
|
+
logError(error, context) {
|
|
369
|
+
const logEntry = {
|
|
370
|
+
timestamp: error.timestamp,
|
|
371
|
+
type: error.type,
|
|
372
|
+
code: error.code,
|
|
373
|
+
message: error.message,
|
|
374
|
+
name: error.name,
|
|
375
|
+
connection: context.connection || 'unknown',
|
|
376
|
+
driver: context.driver || 'unknown',
|
|
377
|
+
sql: context.sql ? context.sql.substring(0, 200) + (context.sql.length > 200 ? '...' : '') : null,
|
|
378
|
+
params: context.params,
|
|
379
|
+
stack: error.stack,
|
|
380
|
+
suggestions: error.suggestions,
|
|
381
|
+
recoveryStrategies: error.recoveryStrategies
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
console.error('Database Error:', logEntry);
|
|
385
|
+
|
|
386
|
+
// Emit log event
|
|
387
|
+
this.emit('error-logged', logEntry);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Check if error is retryable
|
|
392
|
+
* @param {string} errorType - Error type
|
|
393
|
+
* @param {string} errorCode - Error code
|
|
394
|
+
* @returns {boolean} True if error is retryable
|
|
395
|
+
*/
|
|
396
|
+
isRetryableError(errorType, errorCode) {
|
|
397
|
+
const retryableTypes = ['connection', 'timeout', 'deadlock'];
|
|
398
|
+
const retryableCodes = ['ETIMEDOUT', 'ECONNREFUSED', 'ER_LOCK_DEADLOCK', '40P01', '40001'];
|
|
399
|
+
|
|
400
|
+
return retryableTypes.includes(errorType) || retryableCodes.includes(errorCode);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Handle retryable error
|
|
405
|
+
* @param {Error} error - Error object
|
|
406
|
+
* @param {Object} context - Error context
|
|
407
|
+
* @returns {Promise<Error>} Handled error
|
|
408
|
+
*/
|
|
409
|
+
async handleRetryableError(error, context) {
|
|
410
|
+
const maxRetries = this.options.maxRetries;
|
|
411
|
+
const retryDelay = this.options.retryDelay;
|
|
412
|
+
|
|
413
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
414
|
+
try {
|
|
415
|
+
this.emit('retry-attempt', { attempt, maxRetries, error, context });
|
|
416
|
+
|
|
417
|
+
// Exponential backoff
|
|
418
|
+
const delay = retryDelay * Math.pow(2, attempt - 1);
|
|
419
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
420
|
+
|
|
421
|
+
// Retry the operation
|
|
422
|
+
if (context.retryCallback) {
|
|
423
|
+
return await context.retryCallback();
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// If no retry callback, throw original error
|
|
427
|
+
break;
|
|
428
|
+
} catch (retryError) {
|
|
429
|
+
if (attempt === maxRetries) {
|
|
430
|
+
this.emit('retry-failed', { attempt, maxRetries, error: retryError, context });
|
|
431
|
+
throw this.enhanceError(retryError, error.type, error.code, {
|
|
432
|
+
...context,
|
|
433
|
+
retryAttempts: attempt,
|
|
434
|
+
originalError: error
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return error;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Get error statistics
|
|
445
|
+
* @returns {Object} Error statistics
|
|
446
|
+
*/
|
|
447
|
+
getStats() {
|
|
448
|
+
const now = new Date();
|
|
449
|
+
const oneHourAgo = new Date(now.getTime() - 3600000);
|
|
450
|
+
const oneDayAgo = new Date(now.getTime() - 86400000);
|
|
451
|
+
|
|
452
|
+
const recentErrorsLastHour = this.errorStats.recentErrors.filter(
|
|
453
|
+
error => new Date(error.timestamp) > oneHourAgo
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
const recentErrorsLastDay = this.errorStats.recentErrors.filter(
|
|
457
|
+
error => new Date(error.timestamp) > oneDayAgo
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
// Calculate error rates
|
|
461
|
+
const totalErrors = this.errorStats.totalErrors;
|
|
462
|
+
const errorRateLastHour = recentErrorsLastHour.length;
|
|
463
|
+
const errorRateLastDay = recentErrorsLastDay.length;
|
|
464
|
+
|
|
465
|
+
// Find most common error types
|
|
466
|
+
const errorTypes = Object.entries(this.errorStats.byType)
|
|
467
|
+
.sort((a, b) => b[1] - a[1])
|
|
468
|
+
.slice(0, 5);
|
|
469
|
+
|
|
470
|
+
// Find most problematic connections
|
|
471
|
+
const problematicConnections = Object.entries(this.errorStats.byConnection)
|
|
472
|
+
.sort((a, b) => b[1] - a[1])
|
|
473
|
+
.slice(0, 5);
|
|
474
|
+
|
|
475
|
+
// Find most problematic drivers
|
|
476
|
+
const problematicDrivers = Object.entries(this.errorStats.byDriver)
|
|
477
|
+
.sort((a, b) => b[1] - a[1])
|
|
478
|
+
.slice(0, 5);
|
|
479
|
+
|
|
480
|
+
return {
|
|
481
|
+
summary: {
|
|
482
|
+
totalErrors,
|
|
483
|
+
errorRateLastHour,
|
|
484
|
+
errorRateLastDay,
|
|
485
|
+
timestamp: now.toISOString()
|
|
486
|
+
},
|
|
487
|
+
byType: errorTypes.reduce((acc, [type, count]) => {
|
|
488
|
+
acc[type] = count;
|
|
489
|
+
return acc;
|
|
490
|
+
}, {}),
|
|
491
|
+
byConnection: problematicConnections.reduce((acc, [connection, count]) => {
|
|
492
|
+
acc[connection] = count;
|
|
493
|
+
return acc;
|
|
494
|
+
}, {}),
|
|
495
|
+
byDriver: problematicDrivers.reduce((acc, [driver, count]) => {
|
|
496
|
+
acc[driver] = count;
|
|
497
|
+
return acc;
|
|
498
|
+
}, {}),
|
|
499
|
+
recentErrors: this.errorStats.recentErrors.slice(-10).reverse(),
|
|
500
|
+
settings: {
|
|
501
|
+
logErrors: this.options.logErrors,
|
|
502
|
+
throwErrors: this.options.throwErrors,
|
|
503
|
+
retryOnError: this.options.retryOnError,
|
|
504
|
+
maxRetries: this.options.maxRetries,
|
|
505
|
+
retryDelay: this.options.retryDelay
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Get error report
|
|
512
|
+
* @returns {Object} Error report
|
|
513
|
+
*/
|
|
514
|
+
getReport() {
|
|
515
|
+
const stats = this.getStats();
|
|
516
|
+
const report = {
|
|
517
|
+
timestamp: new Date().toISOString(),
|
|
518
|
+
summary: stats.summary,
|
|
519
|
+
analysis: this.analyzeErrors(),
|
|
520
|
+
recommendations: this.generateRecommendations(),
|
|
521
|
+
topErrors: stats.recentErrors,
|
|
522
|
+
errorDistribution: {
|
|
523
|
+
byType: stats.byType,
|
|
524
|
+
byConnection: stats.byConnection,
|
|
525
|
+
byDriver: stats.byDriver
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
return report;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Analyze errors
|
|
534
|
+
* @returns {Object} Error analysis
|
|
535
|
+
*/
|
|
536
|
+
analyzeErrors() {
|
|
537
|
+
const stats = this.getStats();
|
|
538
|
+
const analysis = {
|
|
539
|
+
issues: [],
|
|
540
|
+
warnings: [],
|
|
541
|
+
suggestions: []
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// Check for high error rates
|
|
545
|
+
if (stats.summary.errorRateLastHour > 10) {
|
|
546
|
+
analysis.issues.push({
|
|
547
|
+
type: 'high-error-rate',
|
|
548
|
+
message: `High error rate detected: ${stats.summary.errorRateLastHour} errors in the last hour`,
|
|
549
|
+
severity: 'high'
|
|
550
|
+
});
|
|
551
|
+
analysis.suggestions.push('Investigate connection pool settings');
|
|
552
|
+
analysis.suggestions.push('Check database server health');
|
|
553
|
+
analysis.suggestions.push('Review application error handling');
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Check for specific error patterns
|
|
557
|
+
const connectionErrors = stats.byType.connection || 0;
|
|
558
|
+
if (connectionErrors > 5) {
|
|
559
|
+
analysis.warnings.push({
|
|
560
|
+
type: 'connection-errors',
|
|
561
|
+
message: `Multiple connection errors detected: ${connectionErrors}`,
|
|
562
|
+
severity: 'medium'
|
|
563
|
+
});
|
|
564
|
+
analysis.suggestions.push('Verify database connection settings');
|
|
565
|
+
analysis.suggestions.push('Check network connectivity');
|
|
566
|
+
analysis.suggestions.push('Review connection pool configuration');
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const timeoutErrors = stats.byType.timeout || 0;
|
|
570
|
+
if (timeoutErrors > 3) {
|
|
571
|
+
analysis.warnings.push({
|
|
572
|
+
type: 'timeout-errors',
|
|
573
|
+
message: `Multiple timeout errors detected: ${timeoutErrors}`,
|
|
574
|
+
severity: 'medium'
|
|
575
|
+
});
|
|
576
|
+
analysis.suggestions.push('Increase query timeout settings');
|
|
577
|
+
analysis.suggestions.push('Optimize slow queries');
|
|
578
|
+
analysis.suggestions.push('Consider database indexing');
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Check for problematic connections
|
|
582
|
+
const topConnection = Object.entries(stats.byConnection)[0];
|
|
583
|
+
if (topConnection && topConnection[1] > 10) {
|
|
584
|
+
analysis.warnings.push({
|
|
585
|
+
type: 'problematic-connection',
|
|
586
|
+
message: `Connection "${topConnection[0]}" has ${topConnection[1]} errors`,
|
|
587
|
+
severity: 'medium'
|
|
588
|
+
});
|
|
589
|
+
analysis.suggestions.push(`Review connection "${topConnection[0]}" configuration`);
|
|
590
|
+
analysis.suggestions.push('Check connection pool settings for this connection');
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return analysis;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Generate recommendations
|
|
598
|
+
* @returns {Array} Recommendations
|
|
599
|
+
*/
|
|
600
|
+
generateRecommendations() {
|
|
601
|
+
const stats = this.getStats();
|
|
602
|
+
const recommendations = [];
|
|
603
|
+
|
|
604
|
+
// General recommendations
|
|
605
|
+
if (stats.summary.totalErrors > 0) {
|
|
606
|
+
recommendations.push('Implement comprehensive error logging and monitoring');
|
|
607
|
+
recommendations.push('Set up alerts for critical error types');
|
|
608
|
+
recommendations.push('Regularly review error reports and statistics');
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// Specific recommendations based on error types
|
|
612
|
+
if (stats.byType.connection > 0) {
|
|
613
|
+
recommendations.push('Implement connection retry logic with exponential backoff');
|
|
614
|
+
recommendations.push('Consider using connection pooling');
|
|
615
|
+
recommendations.push('Set up database health checks');
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (stats.byType.timeout > 0) {
|
|
619
|
+
recommendations.push('Optimize slow-running queries');
|
|
620
|
+
recommendations.push('Add appropriate database indexes');
|
|
621
|
+
recommendations.push('Consider query result caching');
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (stats.byType.deadlock > 0) {
|
|
625
|
+
recommendations.push('Review transaction isolation levels');
|
|
626
|
+
recommendations.push('Implement deadlock detection and retry logic');
|
|
627
|
+
recommendations.push('Consider using optimistic locking');
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (stats.byType.duplicate > 0) {
|
|
631
|
+
recommendations.push('Implement UPSERT operations');
|
|
632
|
+
recommendations.push('Add duplicate key handling in application logic');
|
|
633
|
+
recommendations.push('Consider using unique constraints with proper error handling');
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return recommendations;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Clear error statistics
|
|
641
|
+
*/
|
|
642
|
+
clearStats() {
|
|
643
|
+
this.errorStats = {
|
|
644
|
+
totalErrors: 0,
|
|
645
|
+
byType: {},
|
|
646
|
+
byConnection: {},
|
|
647
|
+
byDriver: {},
|
|
648
|
+
recentErrors: []
|
|
649
|
+
};
|
|
650
|
+
this.emit('stats-cleared');
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Export error data
|
|
655
|
+
* @param {string} format - Export format (json, csv)
|
|
656
|
+
* @returns {string} Exported data
|
|
657
|
+
*/
|
|
658
|
+
export(format = 'json') {
|
|
659
|
+
const data = {
|
|
660
|
+
stats: this.getStats(),
|
|
661
|
+
recentErrors: this.errorStats.recentErrors,
|
|
662
|
+
timestamp: new Date().toISOString()
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
switch (format.toLowerCase()) {
|
|
666
|
+
case 'csv':
|
|
667
|
+
return this.exportToCSV(data);
|
|
668
|
+
case 'json':
|
|
669
|
+
default:
|
|
670
|
+
return JSON.stringify(data, null, 2);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Export to CSV
|
|
676
|
+
* @param {Object} data - Data to export
|
|
677
|
+
* @returns {string} CSV data
|
|
678
|
+
*/
|
|
679
|
+
exportToCSV(data) {
|
|
680
|
+
const csvLines = [];
|
|
681
|
+
|
|
682
|
+
// Export error statistics
|
|
683
|
+
csvLines.push('=== ERROR STATISTICS ===');
|
|
684
|
+
csvLines.push('Metric,Value');
|
|
685
|
+
csvLines.push(`Total Errors,${data.stats.summary.totalErrors}`);
|
|
686
|
+
csvLines.push(`Errors Last Hour,${data.stats.summary.errorRateLastHour}`);
|
|
687
|
+
csvLines.push(`Errors Last Day,${data.stats.summary.errorRateLastDay}`);
|
|
688
|
+
csvLines.push(`Timestamp,${data.stats.summary.timestamp}`);
|
|
689
|
+
|
|
690
|
+
// Export error types
|
|
691
|
+
csvLines.push('\n=== ERROR TYPES ===');
|
|
692
|
+
csvLines.push('Type,Count');
|
|
693
|
+
Object.entries(data.stats.byType).forEach(([type, count]) => {
|
|
694
|
+
csvLines.push(`${type},${count}`);
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
// Export recent errors
|
|
698
|
+
if (data.recentErrors.length > 0) {
|
|
699
|
+
csvLines.push('\n=== RECENT ERRORS ===');
|
|
700
|
+
const headers = Object.keys(data.recentErrors[0]).join(',');
|
|
701
|
+
csvLines.push(headers);
|
|
702
|
+
data.recentErrors.forEach(error => {
|
|
703
|
+
const values = Object.values(error).map(v =>
|
|
704
|
+
typeof v === 'string' ? `"${v.replace(/"/g, '""')}"` : v
|
|
705
|
+
).join(',');
|
|
706
|
+
csvLines.push(values);
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
return csvLines.join('\n');
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Create error handler middleware
|
|
715
|
+
* @returns {Function} Error handler middleware
|
|
716
|
+
*/
|
|
717
|
+
createMiddleware() {
|
|
718
|
+
return async (error, context) => {
|
|
719
|
+
return await this.handle(error, context);
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
export default ErrorHandler;
|