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