noormme 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.
Files changed (137) hide show
  1. package/LICENSE +89 -21
  2. package/README.md +195 -578
  3. package/dist/cjs/cli/commands/analyze.js +4 -1
  4. package/dist/cjs/cli/commands/generate.js +48 -8
  5. package/dist/cjs/cli/commands/init.js +54 -14
  6. package/dist/cjs/cli/commands/inspect.js +10 -3
  7. package/dist/cjs/cli/commands/migrate.js +38 -2
  8. package/dist/cjs/cli/commands/optimize.js +4 -1
  9. package/dist/cjs/cli/commands/status.js +41 -5
  10. package/dist/cjs/cli/commands/watch.js +4 -1
  11. package/dist/cjs/cli/index.js +4 -1
  12. package/dist/cjs/dialect/database-introspector.js +16 -21
  13. package/dist/cjs/dialect/sqlite/sqlite-introspector.js +13 -24
  14. package/dist/cjs/dynamic/dynamic.d.ts +20 -0
  15. package/dist/cjs/dynamic/dynamic.js +25 -0
  16. package/dist/cjs/edge-runtime/edge-config.d.ts +125 -0
  17. package/dist/cjs/edge-runtime/edge-config.js +323 -0
  18. package/dist/cjs/errors/NoormError.d.ts +27 -4
  19. package/dist/cjs/errors/NoormError.js +142 -21
  20. package/dist/cjs/logging/logger.d.ts +7 -2
  21. package/dist/cjs/logging/logger.js +21 -7
  22. package/dist/cjs/noormme.d.ts +12 -8
  23. package/dist/cjs/noormme.js +133 -61
  24. package/dist/cjs/operation-node/column-node.js +4 -0
  25. package/dist/cjs/operation-node/identifier-node.js +4 -0
  26. package/dist/cjs/operation-node/table-node.js +7 -0
  27. package/dist/cjs/parser/reference-parser.js +5 -0
  28. package/dist/cjs/parser/table-parser.js +2 -0
  29. package/dist/cjs/performance/index.d.ts +44 -0
  30. package/dist/cjs/performance/index.js +64 -0
  31. package/dist/cjs/performance/query-optimizer.d.ts +134 -0
  32. package/dist/cjs/performance/query-optimizer.js +391 -0
  33. package/dist/cjs/performance/services/cache-service.d.ts +177 -0
  34. package/dist/cjs/performance/services/cache-service.js +415 -0
  35. package/dist/cjs/performance/services/connection-factory.d.ts +198 -0
  36. package/dist/cjs/performance/services/connection-factory.js +498 -0
  37. package/dist/cjs/performance/services/metrics-collector.d.ts +162 -0
  38. package/dist/cjs/performance/services/metrics-collector.js +406 -0
  39. package/dist/cjs/performance/utils/query-parser.d.ts +123 -0
  40. package/dist/cjs/performance/utils/query-parser.js +295 -0
  41. package/dist/cjs/raw-builder/sql.d.ts +73 -26
  42. package/dist/cjs/raw-builder/sql.js +9 -0
  43. package/dist/cjs/repository/repository-factory.d.ts +10 -42
  44. package/dist/cjs/repository/repository-factory.js +276 -394
  45. package/dist/cjs/schema/core/coordinators/schema-discovery.coordinator.js +6 -4
  46. package/dist/cjs/schema/core/factories/discovery-factory.js +5 -5
  47. package/dist/cjs/schema/core/utils/name-generator.js +34 -2
  48. package/dist/cjs/schema/core/utils/type-mapper.d.ts +19 -14
  49. package/dist/cjs/schema/core/utils/type-mapper.js +4 -7
  50. package/dist/cjs/schema/dialects/sqlite/discovery/sqlite-constraint-discovery.js +3 -2
  51. package/dist/cjs/schema/dialects/sqlite/sqlite-discovery.coordinator.d.ts +2 -0
  52. package/dist/cjs/schema/dialects/sqlite/sqlite-discovery.coordinator.js +19 -5
  53. package/dist/cjs/schema/test/dialect-capabilities.test.js +6 -0
  54. package/dist/cjs/schema/test/error-handling.test.js +52 -33
  55. package/dist/cjs/schema/test/integration.test.js +51 -5
  56. package/dist/cjs/schema/test/sqlite-discovery-coordinator.test.js +8 -4
  57. package/dist/cjs/sqlite-migration/index.js +35 -2
  58. package/dist/cjs/testing/test-utils.d.ts +5 -0
  59. package/dist/cjs/testing/test-utils.js +66 -6
  60. package/dist/cjs/types/index.d.ts +78 -13
  61. package/dist/cjs/types/index.js +23 -0
  62. package/dist/cjs/types/type-generator.d.ts +8 -0
  63. package/dist/cjs/types/type-generator.js +86 -17
  64. package/dist/cjs/util/safe-sql-helpers.d.ts +124 -0
  65. package/dist/cjs/util/safe-sql-helpers.js +221 -0
  66. package/dist/cjs/util/security-validator.d.ts +44 -0
  67. package/dist/cjs/util/security-validator.js +256 -0
  68. package/dist/cjs/util/security.d.ts +60 -0
  69. package/dist/cjs/util/security.js +137 -0
  70. package/dist/cjs/watch/schema-watcher.js +26 -7
  71. package/dist/esm/cli/commands/generate.js +10 -6
  72. package/dist/esm/cli/commands/init.js +15 -11
  73. package/dist/esm/cli/commands/inspect.js +6 -2
  74. package/dist/esm/cli/commands/status.js +3 -3
  75. package/dist/esm/dialect/database-introspector.js +16 -21
  76. package/dist/esm/dialect/sqlite/sqlite-introspector.js +13 -24
  77. package/dist/esm/dynamic/dynamic.d.ts +20 -0
  78. package/dist/esm/dynamic/dynamic.js +25 -0
  79. package/dist/esm/edge-runtime/edge-config.d.ts +125 -0
  80. package/dist/esm/edge-runtime/edge-config.js +281 -0
  81. package/dist/esm/errors/NoormError.d.ts +27 -4
  82. package/dist/esm/errors/NoormError.js +134 -18
  83. package/dist/esm/logging/logger.d.ts +7 -2
  84. package/dist/esm/logging/logger.js +21 -7
  85. package/dist/esm/noormme.d.ts +12 -8
  86. package/dist/esm/noormme.js +130 -61
  87. package/dist/esm/operation-node/column-node.js +4 -0
  88. package/dist/esm/operation-node/identifier-node.js +4 -0
  89. package/dist/esm/operation-node/table-node.js +7 -0
  90. package/dist/esm/parser/reference-parser.js +5 -0
  91. package/dist/esm/parser/table-parser.js +2 -0
  92. package/dist/esm/performance/index.d.ts +44 -0
  93. package/dist/esm/performance/index.js +48 -0
  94. package/dist/esm/performance/query-optimizer.d.ts +134 -0
  95. package/dist/esm/performance/query-optimizer.js +387 -0
  96. package/dist/esm/performance/services/cache-service.d.ts +177 -0
  97. package/dist/esm/performance/services/cache-service.js +410 -0
  98. package/dist/esm/performance/services/connection-factory.d.ts +198 -0
  99. package/dist/esm/performance/services/connection-factory.js +493 -0
  100. package/dist/esm/performance/services/metrics-collector.d.ts +162 -0
  101. package/dist/esm/performance/services/metrics-collector.js +402 -0
  102. package/dist/esm/performance/utils/query-parser.d.ts +123 -0
  103. package/dist/esm/performance/utils/query-parser.js +292 -0
  104. package/dist/esm/raw-builder/sql.d.ts +73 -26
  105. package/dist/esm/raw-builder/sql.js +9 -0
  106. package/dist/esm/repository/repository-factory.d.ts +10 -42
  107. package/dist/esm/repository/repository-factory.js +277 -395
  108. package/dist/esm/schema/core/coordinators/schema-discovery.coordinator.js +6 -4
  109. package/dist/esm/schema/core/factories/discovery-factory.js +5 -5
  110. package/dist/esm/schema/core/utils/name-generator.js +34 -2
  111. package/dist/esm/schema/core/utils/type-mapper.d.ts +19 -14
  112. package/dist/esm/schema/core/utils/type-mapper.js +4 -7
  113. package/dist/esm/schema/dialects/sqlite/discovery/sqlite-constraint-discovery.js +3 -2
  114. package/dist/esm/schema/dialects/sqlite/sqlite-discovery.coordinator.d.ts +2 -0
  115. package/dist/esm/schema/dialects/sqlite/sqlite-discovery.coordinator.js +19 -5
  116. package/dist/esm/schema/test/dialect-capabilities.test.js +6 -0
  117. package/dist/esm/schema/test/error-handling.test.js +52 -33
  118. package/dist/esm/schema/test/integration.test.js +18 -5
  119. package/dist/esm/schema/test/sqlite-discovery-coordinator.test.js +8 -4
  120. package/dist/esm/testing/test-utils.d.ts +5 -0
  121. package/dist/esm/testing/test-utils.js +66 -6
  122. package/dist/esm/types/index.d.ts +78 -13
  123. package/dist/esm/types/index.js +22 -1
  124. package/dist/esm/types/type-generator.d.ts +8 -0
  125. package/dist/esm/types/type-generator.js +86 -17
  126. package/dist/esm/util/safe-sql-helpers.d.ts +124 -0
  127. package/dist/esm/util/safe-sql-helpers.js +209 -0
  128. package/dist/esm/util/security-validator.d.ts +44 -0
  129. package/dist/esm/util/security-validator.js +246 -0
  130. package/dist/esm/util/security.d.ts +60 -0
  131. package/dist/esm/util/security.js +114 -0
  132. package/dist/esm/watch/schema-watcher.js +26 -7
  133. package/package.json +1 -1
  134. package/dist/cjs/performance/query-analyzer.d.ts +0 -89
  135. package/dist/cjs/performance/query-analyzer.js +0 -263
  136. package/dist/esm/performance/query-analyzer.d.ts +0 -89
  137. package/dist/esm/performance/query-analyzer.js +0 -260
@@ -0,0 +1,406 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MetricsCollector = void 0;
4
+ exports.createMetricsCollector = createMetricsCollector;
5
+ const logger_js_1 = require("../../logging/logger.js");
6
+ /**
7
+ * Service for collecting and analyzing performance metrics
8
+ */
9
+ class MetricsCollector {
10
+ queryHistory = [];
11
+ warnings = [];
12
+ recentQueries = new Map();
13
+ config;
14
+ logger;
15
+ cleanupTimer;
16
+ constructor(config = {}, logger) {
17
+ this.logger = logger || new logger_js_1.Logger('MetricsCollector');
18
+ this.config = {
19
+ enabled: process.env.NODE_ENV === 'development',
20
+ slowQueryThreshold: 1000, // 1 second
21
+ nPlusOneDetection: true,
22
+ missingIndexDetection: true,
23
+ largeResultSetThreshold: 1000,
24
+ maxHistorySize: 10000,
25
+ warningRetentionDays: 7,
26
+ ...config
27
+ };
28
+ this.startCleanupTimer();
29
+ }
30
+ /**
31
+ * Record a query execution
32
+ */
33
+ recordQuery(query, executionTime, options = {}) {
34
+ if (!this.config.enabled)
35
+ return;
36
+ const metrics = {
37
+ query: this.normalizeQuery(query),
38
+ executionTime,
39
+ timestamp: Date.now(),
40
+ table: options.table,
41
+ operation: options.operation,
42
+ resultCount: options.resultCount,
43
+ error: options.error
44
+ };
45
+ this.queryHistory.push(metrics);
46
+ // Keep history size under control
47
+ if (this.queryHistory.length > this.config.maxHistorySize) {
48
+ this.queryHistory = this.queryHistory.slice(-this.config.maxHistorySize);
49
+ }
50
+ // Track recent queries for pattern detection
51
+ this.trackRecentQuery(metrics);
52
+ // Analyze for warnings
53
+ this.analyzeQuery(metrics);
54
+ }
55
+ /**
56
+ * Get performance statistics
57
+ */
58
+ getPerformanceStats() {
59
+ const totalQueries = this.queryHistory.length;
60
+ const slowQueries = this.queryHistory.filter(q => q.executionTime > this.config.slowQueryThreshold).length;
61
+ const averageExecutionTime = totalQueries > 0
62
+ ? this.queryHistory.reduce((sum, q) => sum + q.executionTime, 0) / totalQueries
63
+ : 0;
64
+ const errorRate = totalQueries > 0
65
+ ? this.queryHistory.filter(q => q.error).length / totalQueries
66
+ : 0;
67
+ const warningCount = {};
68
+ for (const warning of this.warnings) {
69
+ warningCount[warning.type] = (warningCount[warning.type] || 0) + 1;
70
+ }
71
+ const topSlowQueries = this.getTopSlowQueries();
72
+ const queryPatterns = this.getQueryPatterns();
73
+ return {
74
+ totalQueries,
75
+ averageExecutionTime,
76
+ slowQueries,
77
+ errorRate,
78
+ warningCount,
79
+ topSlowQueries,
80
+ queryPatterns
81
+ };
82
+ }
83
+ /**
84
+ * Get recent warnings
85
+ */
86
+ getRecentWarnings(limit = 50) {
87
+ return this.warnings
88
+ .sort((a, b) => b.timestamp - a.timestamp)
89
+ .slice(0, limit);
90
+ }
91
+ /**
92
+ * Get warnings by type
93
+ */
94
+ getWarningsByType(type) {
95
+ return this.warnings.filter(w => w.type === type);
96
+ }
97
+ /**
98
+ * Get query history
99
+ */
100
+ getQueryHistory(limit) {
101
+ const history = this.queryHistory
102
+ .sort((a, b) => b.timestamp - a.timestamp);
103
+ return limit ? history.slice(0, limit) : history;
104
+ }
105
+ /**
106
+ * Get slow queries
107
+ */
108
+ getSlowQueries(threshold) {
109
+ const actualThreshold = threshold || this.config.slowQueryThreshold;
110
+ return this.queryHistory
111
+ .filter(q => q.executionTime > actualThreshold)
112
+ .sort((a, b) => b.executionTime - a.executionTime);
113
+ }
114
+ /**
115
+ * Get N+1 query patterns
116
+ */
117
+ getNPlusOnePatterns() {
118
+ const patterns = new Map();
119
+ // Group queries by normalized pattern
120
+ for (const query of this.queryHistory) {
121
+ if (!patterns.has(query.query)) {
122
+ patterns.set(query.query, []);
123
+ }
124
+ patterns.get(query.query).push(query);
125
+ }
126
+ const nPlusOnePatterns = [];
127
+ // Analyze each pattern for N+1 behavior
128
+ for (const [pattern, queries] of patterns.entries()) {
129
+ if (queries.length < 5)
130
+ continue;
131
+ const timeWindows = [5000, 10000, 30000]; // 5s, 10s, 30s
132
+ for (const windowMs of timeWindows) {
133
+ let maxCountInWindow = 0;
134
+ for (const query of queries) {
135
+ const windowStart = query.timestamp - windowMs;
136
+ const countInWindow = queries.filter(q => q.timestamp >= windowStart && q.timestamp <= query.timestamp).length;
137
+ maxCountInWindow = Math.max(maxCountInWindow, countInWindow);
138
+ }
139
+ if (maxCountInWindow >= 5) {
140
+ let severity = 'low';
141
+ if (maxCountInWindow >= 20)
142
+ severity = 'high';
143
+ else if (maxCountInWindow >= 10)
144
+ severity = 'medium';
145
+ nPlusOnePatterns.push({
146
+ pattern,
147
+ count: maxCountInWindow,
148
+ timeWindow: windowMs,
149
+ severity
150
+ });
151
+ break;
152
+ }
153
+ }
154
+ }
155
+ return nPlusOnePatterns.sort((a, b) => b.count - a.count);
156
+ }
157
+ /**
158
+ * Clear all metrics
159
+ */
160
+ clear() {
161
+ this.queryHistory = [];
162
+ this.warnings = [];
163
+ this.recentQueries.clear();
164
+ this.logger.info('Metrics cleared');
165
+ }
166
+ /**
167
+ * Export metrics data
168
+ */
169
+ exportMetrics() {
170
+ return {
171
+ config: this.config,
172
+ stats: this.getPerformanceStats(),
173
+ warnings: this.warnings,
174
+ queries: this.queryHistory
175
+ };
176
+ }
177
+ /**
178
+ * Shutdown metrics collector
179
+ */
180
+ shutdown() {
181
+ if (this.cleanupTimer) {
182
+ clearInterval(this.cleanupTimer);
183
+ }
184
+ this.logger.info('Metrics collector shutdown');
185
+ }
186
+ /**
187
+ * Track recent query for pattern detection
188
+ */
189
+ trackRecentQuery(metrics) {
190
+ const normalizedQuery = metrics.query;
191
+ if (!this.recentQueries.has(normalizedQuery)) {
192
+ this.recentQueries.set(normalizedQuery, []);
193
+ }
194
+ this.recentQueries.get(normalizedQuery).push(metrics);
195
+ // Clean up old recent queries (keep last 10 seconds)
196
+ const cutoff = Date.now() - 10000;
197
+ for (const [query, queries] of this.recentQueries.entries()) {
198
+ const recent = queries.filter(q => q.timestamp >= cutoff);
199
+ if (recent.length === 0) {
200
+ this.recentQueries.delete(query);
201
+ }
202
+ else {
203
+ this.recentQueries.set(query, recent);
204
+ }
205
+ }
206
+ }
207
+ /**
208
+ * Analyze query for performance issues
209
+ */
210
+ analyzeQuery(metrics) {
211
+ const warnings = [];
212
+ // Check for slow queries
213
+ if (metrics.executionTime > this.config.slowQueryThreshold) {
214
+ warnings.push({
215
+ type: 'slow_query',
216
+ message: `Slow query detected: ${metrics.executionTime}ms`,
217
+ suggestion: 'Consider adding indexes or optimizing the query',
218
+ query: metrics.query,
219
+ table: metrics.table,
220
+ severity: metrics.executionTime > this.config.slowQueryThreshold * 3 ? 'high' : 'medium',
221
+ timestamp: Date.now()
222
+ });
223
+ }
224
+ // Check for N+1 queries
225
+ if (this.config.nPlusOneDetection) {
226
+ const nPlusOneWarning = this.detectNPlusOne(metrics);
227
+ if (nPlusOneWarning) {
228
+ warnings.push(nPlusOneWarning);
229
+ }
230
+ }
231
+ // Check for large result sets
232
+ if (metrics.resultCount && metrics.resultCount > this.config.largeResultSetThreshold) {
233
+ warnings.push({
234
+ type: 'large_result_set',
235
+ message: `Large result set: ${metrics.resultCount} rows returned`,
236
+ suggestion: 'Consider using pagination or filtering to reduce result size',
237
+ query: metrics.query,
238
+ table: metrics.table,
239
+ severity: metrics.resultCount > this.config.largeResultSetThreshold * 5 ? 'high' : 'medium',
240
+ timestamp: Date.now()
241
+ });
242
+ }
243
+ // Log warnings
244
+ for (const warning of warnings) {
245
+ this.addWarning(warning);
246
+ }
247
+ }
248
+ /**
249
+ * Detect N+1 query patterns
250
+ */
251
+ detectNPlusOne(metrics) {
252
+ const recentQueries = this.recentQueries.get(metrics.query) || [];
253
+ // Look for the same query executed multiple times in quick succession
254
+ if (recentQueries.length >= 5) {
255
+ const timeWindow = 5000; // 5 seconds
256
+ const recentInWindow = recentQueries.filter(q => Date.now() - q.timestamp < timeWindow);
257
+ if (recentInWindow.length >= 5) {
258
+ return {
259
+ type: 'n_plus_one',
260
+ message: `Potential N+1 query detected: same query executed ${recentInWindow.length} times`,
261
+ suggestion: 'Consider using joins or batch loading to reduce query count',
262
+ query: metrics.query,
263
+ table: metrics.table,
264
+ severity: 'high',
265
+ timestamp: Date.now()
266
+ };
267
+ }
268
+ }
269
+ return null;
270
+ }
271
+ /**
272
+ * Add warning to collection
273
+ */
274
+ addWarning(warning) {
275
+ this.warnings.push(warning);
276
+ this.logWarning(warning);
277
+ }
278
+ /**
279
+ * Log performance warning
280
+ */
281
+ logWarning(warning) {
282
+ const emoji = this.getWarningEmoji(warning.type);
283
+ const message = `${emoji} Performance Warning [${warning.severity.toUpperCase()}]: ${warning.message}`;
284
+ switch (warning.severity) {
285
+ case 'high':
286
+ this.logger.warn(message);
287
+ break;
288
+ case 'medium':
289
+ this.logger.info(message);
290
+ break;
291
+ case 'low':
292
+ this.logger.debug(message);
293
+ break;
294
+ }
295
+ this.logger.debug(` Suggestion: ${warning.suggestion}`);
296
+ if (warning.query) {
297
+ this.logger.debug(` Query: ${warning.query}`);
298
+ }
299
+ }
300
+ /**
301
+ * Get emoji for warning type
302
+ */
303
+ getWarningEmoji(type) {
304
+ const emojis = {
305
+ 'n_plus_one': '🔄',
306
+ 'missing_index': '📇',
307
+ 'slow_query': '🐌',
308
+ 'full_table_scan': '🔍',
309
+ 'large_result_set': '📊'
310
+ };
311
+ return emojis[type] || '⚠️';
312
+ }
313
+ /**
314
+ * Normalize query for comparison
315
+ */
316
+ normalizeQuery(query) {
317
+ return query
318
+ .replace(/\$\d+/g, '?') // Replace PostgreSQL parameters
319
+ .replace(/'[^']*'/g, '?') // Replace string literals
320
+ .replace(/\b\d+\b/g, '?') // Replace numbers
321
+ .replace(/\s+/g, ' ') // Normalize whitespace
322
+ .trim();
323
+ }
324
+ /**
325
+ * Get top slow queries
326
+ */
327
+ getTopSlowQueries() {
328
+ const queryMap = new Map();
329
+ for (const metrics of this.queryHistory) {
330
+ if (metrics.executionTime > this.config.slowQueryThreshold) {
331
+ if (!queryMap.has(metrics.query)) {
332
+ queryMap.set(metrics.query, { totalTime: 0, count: 0 });
333
+ }
334
+ const entry = queryMap.get(metrics.query);
335
+ entry.totalTime += metrics.executionTime;
336
+ entry.count++;
337
+ }
338
+ }
339
+ return Array.from(queryMap.entries())
340
+ .map(([query, { totalTime, count }]) => ({
341
+ query,
342
+ averageTime: totalTime / count,
343
+ count
344
+ }))
345
+ .sort((a, b) => b.averageTime - a.averageTime)
346
+ .slice(0, 10);
347
+ }
348
+ /**
349
+ * Get query patterns
350
+ */
351
+ getQueryPatterns() {
352
+ const patternMap = new Map();
353
+ for (const metrics of this.queryHistory) {
354
+ if (!patternMap.has(metrics.query)) {
355
+ patternMap.set(metrics.query, { totalTime: 0, count: 0 });
356
+ }
357
+ const entry = patternMap.get(metrics.query);
358
+ entry.totalTime += metrics.executionTime;
359
+ entry.count++;
360
+ }
361
+ return Array.from(patternMap.entries())
362
+ .map(([pattern, { totalTime, count }]) => ({
363
+ pattern,
364
+ frequency: count,
365
+ averageTime: totalTime / count
366
+ }))
367
+ .sort((a, b) => b.frequency - a.frequency)
368
+ .slice(0, 20);
369
+ }
370
+ /**
371
+ * Start cleanup timer
372
+ */
373
+ startCleanupTimer() {
374
+ this.cleanupTimer = setInterval(() => {
375
+ this.cleanup();
376
+ }, 60000); // Cleanup every minute
377
+ }
378
+ /**
379
+ * Cleanup old data
380
+ */
381
+ cleanup() {
382
+ const cutoffTime = Date.now() - (this.config.warningRetentionDays * 24 * 60 * 60 * 1000);
383
+ // Remove old warnings
384
+ this.warnings = this.warnings.filter(w => w.timestamp > cutoffTime);
385
+ // Remove old queries (keep last 24 hours)
386
+ const queryCutoff = Date.now() - (24 * 60 * 60 * 1000);
387
+ this.queryHistory = this.queryHistory.filter(q => q.timestamp > queryCutoff);
388
+ // Clean up recent queries
389
+ for (const [query, queries] of this.recentQueries.entries()) {
390
+ const recent = queries.filter(q => q.timestamp > queryCutoff);
391
+ if (recent.length === 0) {
392
+ this.recentQueries.delete(query);
393
+ }
394
+ else {
395
+ this.recentQueries.set(query, recent);
396
+ }
397
+ }
398
+ }
399
+ }
400
+ exports.MetricsCollector = MetricsCollector;
401
+ /**
402
+ * Factory function to create metrics collector
403
+ */
404
+ function createMetricsCollector(config, logger) {
405
+ return new MetricsCollector(config, logger);
406
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Query parsing utilities for performance analysis and optimization
3
+ */
4
+ export interface ParsedQuery {
5
+ type: 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE' | 'OTHER';
6
+ tables: string[];
7
+ whereColumns: string[];
8
+ joinColumns: Array<{
9
+ table: string;
10
+ column: string;
11
+ }>;
12
+ orderByColumns: string[];
13
+ groupByColumns: string[];
14
+ hasLimit: boolean;
15
+ hasAggregates: boolean;
16
+ isComplex: boolean;
17
+ }
18
+ export interface QueryPattern {
19
+ normalized: string;
20
+ original: string;
21
+ frequency: number;
22
+ averageTime: number;
23
+ tables: string[];
24
+ }
25
+ /**
26
+ * Utility class for parsing and analyzing SQL queries
27
+ */
28
+ export declare class QueryParser {
29
+ /**
30
+ * Parse a SQL query into structured components
31
+ */
32
+ static parseQuery(query: string): ParsedQuery;
33
+ /**
34
+ * Normalize query for comparison (remove dynamic values)
35
+ */
36
+ static normalizeQuery(query: string): string;
37
+ /**
38
+ * Extract query type from SQL statement
39
+ */
40
+ static extractQueryType(query: string): ParsedQuery['type'];
41
+ /**
42
+ * Extract table names from query
43
+ */
44
+ static extractTables(query: string): string[];
45
+ /**
46
+ * Extract columns used in WHERE clauses
47
+ */
48
+ static extractWhereColumns(query: string): string[];
49
+ /**
50
+ * Extract columns used in JOIN conditions
51
+ */
52
+ static extractJoinColumns(query: string): Array<{
53
+ table: string;
54
+ column: string;
55
+ }>;
56
+ /**
57
+ * Extract columns used in ORDER BY clauses
58
+ */
59
+ static extractOrderByColumns(query: string): string[];
60
+ /**
61
+ * Extract columns used in GROUP BY clauses
62
+ */
63
+ static extractGroupByColumns(query: string): string[];
64
+ /**
65
+ * Check if query contains aggregate functions
66
+ */
67
+ static hasAggregates(query: string): boolean;
68
+ /**
69
+ * Check if query is complex (joins, subqueries, etc.)
70
+ */
71
+ static isComplexQuery(query: string): boolean;
72
+ /**
73
+ * Check if query could return many rows
74
+ */
75
+ static couldReturnManyRows(query: string): boolean;
76
+ /**
77
+ * Extract the primary table from a query
78
+ */
79
+ static extractPrimaryTable(query: string): string | null;
80
+ /**
81
+ * Check if query is a SELECT statement
82
+ */
83
+ static isSelectQuery(query: string): boolean;
84
+ /**
85
+ * Check if query is a JOIN statement
86
+ */
87
+ static isJoinQuery(query: string): boolean;
88
+ /**
89
+ * Check if query is an aggregate statement
90
+ */
91
+ static isAggregateQuery(query: string): boolean;
92
+ /**
93
+ * Generate a cache key for a query
94
+ */
95
+ static generateCacheKey(query: string, params?: any[]): string;
96
+ /**
97
+ * Check if query should be cached
98
+ */
99
+ static shouldCache(query: string, executionTime: number): boolean;
100
+ /**
101
+ * Extract operation type from query
102
+ */
103
+ static extractOperation(query: string): string;
104
+ /**
105
+ * Analyze query patterns from a list of queries
106
+ */
107
+ static analyzePatterns(queries: Array<{
108
+ query: string;
109
+ executionTime: number;
110
+ }>): QueryPattern[];
111
+ /**
112
+ * Detect potential N+1 query patterns
113
+ */
114
+ static detectNPlusOnePatterns(queries: Array<{
115
+ query: string;
116
+ timestamp: number;
117
+ }>): Array<{
118
+ pattern: string;
119
+ count: number;
120
+ timeWindow: number;
121
+ severity: 'low' | 'medium' | 'high';
122
+ }>;
123
+ }