@aetherframework/database 1.1.0 → 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.
Files changed (47) hide show
  1. package/examples/mysql-test-pressure.js +1530 -0
  2. package/examples/test-direct.js +116 -0
  3. package/examples/transaction_example.js +127 -0
  4. package/package.json +3 -1
  5. package/src/DatabaseManager.js +565 -0
  6. package/src/core/ConnectionManager.js +351 -0
  7. package/src/core/DatabaseFactory.js +188 -0
  8. package/src/core/MongoQueryBuilder.js +576 -0
  9. package/src/core/PluginManager.js +968 -0
  10. package/src/core/QueryBuilder.js +4394 -0
  11. package/src/core/TransactionManager.js +40 -0
  12. package/src/drivers/clickhouse-driver.js +272 -0
  13. package/src/drivers/index.js +273 -0
  14. package/src/drivers/mongodb-driver.js +87 -0
  15. package/src/drivers/mssql-driver.js +117 -0
  16. package/src/drivers/mysql-driver.js +169 -0
  17. package/src/drivers/oracle-driver.js +101 -0
  18. package/src/drivers/postgres-driver.js +234 -0
  19. package/src/drivers/redis-driver.js +52 -0
  20. package/src/drivers/sqlite-driver.js +67 -0
  21. package/src/middleware/connection-pool.js +455 -0
  22. package/src/middleware/performance-monitor.js +652 -0
  23. package/src/middleware/query-cache.js +500 -0
  24. package/src/middleware/query-logger.js +262 -0
  25. package/src/plugins/AuditPlugin.js +447 -0
  26. package/src/plugins/BasePlugin.js +418 -0
  27. package/src/plugins/BatchOperationPlugin.js +165 -0
  28. package/src/plugins/CachePlugin.js +407 -0
  29. package/src/plugins/CtePlugin.js +523 -0
  30. package/src/plugins/DistributedPlugin.js +543 -0
  31. package/src/plugins/EncryptionPlugin.js +211 -0
  32. package/src/plugins/FullTextSearchPlugin.js +164 -0
  33. package/src/plugins/GeospatialPlugin.js +219 -0
  34. package/src/plugins/GraphQLPlugin.js +162 -0
  35. package/src/plugins/HookPlugin.js +211 -0
  36. package/src/plugins/JsonPlugin.js +366 -0
  37. package/src/plugins/OptimisticLockPlugin.js +374 -0
  38. package/src/plugins/PerformancePlugin.js +175 -0
  39. package/src/plugins/ResiliencePlugin.js +114 -0
  40. package/src/plugins/ShardingPlugin.js +227 -0
  41. package/src/plugins/SoftDeletePlugin.js +258 -0
  42. package/src/plugins/SyncPlugin.js +373 -0
  43. package/src/plugins/VersioningPlugin.js +314 -0
  44. package/src/plugins/WindowFunctionPlugin.js +343 -0
  45. package/src/utils/config-loader.js +632 -0
  46. package/src/utils/error-handler.js +724 -0
  47. 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;