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,134 @@
1
+ import { Kysely } from '../kysely';
2
+ import { Logger } from '../logging/logger.js';
3
+ import { SchemaInfo } from '../types';
4
+ export interface QueryOptimizationOptions {
5
+ enableQueryCache: boolean;
6
+ enableIndexRecommendations: boolean;
7
+ enableQueryRewriting: boolean;
8
+ slowQueryThreshold: number;
9
+ cacheSize: number;
10
+ maxCacheAge: number;
11
+ }
12
+ export interface QueryOptimizationResult {
13
+ originalQuery: string;
14
+ optimizedQuery: string;
15
+ optimizationType: 'index_hint' | 'query_rewrite' | 'cache_hit' | 'no_optimization';
16
+ executionTime: number;
17
+ improvement: number;
18
+ suggestions: string[];
19
+ result?: any;
20
+ }
21
+ export interface IndexRecommendation {
22
+ table: string;
23
+ columns: string[];
24
+ type: 'btree' | 'hash' | 'unique';
25
+ reason: string;
26
+ estimatedImprovement: number;
27
+ priority: 'low' | 'medium' | 'high';
28
+ }
29
+ /**
30
+ * Refactored query optimizer using focused services
31
+ */
32
+ export declare class QueryOptimizer {
33
+ private db;
34
+ private cacheService;
35
+ private metricsCollector;
36
+ private options;
37
+ private logger;
38
+ private schemaInfo;
39
+ constructor(db: Kysely<any>, schemaInfo: SchemaInfo, options?: Partial<QueryOptimizationOptions>, logger?: Logger);
40
+ /**
41
+ * Optimize and execute a query
42
+ */
43
+ optimizeQuery<T>(query: string, params?: any[], context?: {
44
+ table?: string;
45
+ operation?: string;
46
+ }): Promise<QueryOptimizationResult & {
47
+ result: T;
48
+ }>;
49
+ /**
50
+ * Get index recommendations
51
+ */
52
+ getIndexRecommendations(): Promise<IndexRecommendation[]>;
53
+ /**
54
+ * Get query performance statistics
55
+ */
56
+ getQueryStats(): {
57
+ cache: import("./services/cache-service").CacheStats;
58
+ indexRecommendations: number;
59
+ totalQueries: number;
60
+ averageExecutionTime: number;
61
+ slowQueries: number;
62
+ errorRate: number;
63
+ warningCount: {
64
+ [key: string]: number;
65
+ };
66
+ topSlowQueries: Array<{
67
+ query: string;
68
+ averageTime: number;
69
+ count: number;
70
+ }>;
71
+ queryPatterns: Array<{
72
+ pattern: string;
73
+ frequency: number;
74
+ averageTime: number;
75
+ }>;
76
+ };
77
+ /**
78
+ * Clear query cache
79
+ */
80
+ clearCache(): void;
81
+ /**
82
+ * Clear metrics
83
+ */
84
+ clearMetrics(): void;
85
+ /**
86
+ * Get cache statistics
87
+ */
88
+ getCacheStats(): import("./services/cache-service").CacheStats;
89
+ /**
90
+ * Get performance warnings
91
+ */
92
+ getPerformanceWarnings(): import("./services/metrics-collector").PerformanceWarning[];
93
+ /**
94
+ * Shutdown optimizer
95
+ */
96
+ shutdown(): void;
97
+ /**
98
+ * Analyze query for optimization opportunities
99
+ */
100
+ private analyzeQuery;
101
+ /**
102
+ * Optimize SELECT queries
103
+ */
104
+ private optimizeSelectQuery;
105
+ /**
106
+ * Optimize complex queries (joins, aggregates)
107
+ */
108
+ private optimizeComplexQuery;
109
+ /**
110
+ * Analyze query for index recommendations
111
+ */
112
+ private analyzeQueryForIndexes;
113
+ /**
114
+ * Deduplicate index recommendations
115
+ */
116
+ private deduplicateRecommendations;
117
+ /**
118
+ * Generate optimization suggestions
119
+ */
120
+ private generateSuggestions;
121
+ /**
122
+ * Execute a query with error handling
123
+ * Note: This creates a proper SQL query with parameters
124
+ */
125
+ private executeQuery;
126
+ /**
127
+ * Check if column has an index
128
+ */
129
+ private hasIndexOnColumn;
130
+ }
131
+ /**
132
+ * Factory function to create query optimizer
133
+ */
134
+ export declare function createQueryOptimizer(db: Kysely<any>, schemaInfo: SchemaInfo, options?: Partial<QueryOptimizationOptions>, logger?: Logger): QueryOptimizer;
@@ -0,0 +1,387 @@
1
+ /// <reference types="./query-optimizer.d.ts" />
2
+ import { Logger } from '../logging/logger.js';
3
+ import { QueryParser } from './utils/query-parser';
4
+ import { QueryCacheService } from './services/cache-service';
5
+ import { MetricsCollector } from './services/metrics-collector';
6
+ import { sql } from '../raw-builder/sql';
7
+ /**
8
+ * Refactored query optimizer using focused services
9
+ */
10
+ export class QueryOptimizer {
11
+ db;
12
+ cacheService;
13
+ metricsCollector;
14
+ options;
15
+ logger;
16
+ schemaInfo;
17
+ constructor(db, schemaInfo, options = {}, logger) {
18
+ this.db = db;
19
+ this.schemaInfo = schemaInfo;
20
+ this.logger = logger || new Logger('QueryOptimizer');
21
+ this.options = {
22
+ enableQueryCache: true,
23
+ enableIndexRecommendations: true,
24
+ enableQueryRewriting: true,
25
+ slowQueryThreshold: 1000,
26
+ cacheSize: 1000,
27
+ maxCacheAge: 300000,
28
+ ...options
29
+ };
30
+ // Initialize services
31
+ this.cacheService = new QueryCacheService({
32
+ maxSize: this.options.cacheSize,
33
+ defaultTtl: this.options.maxCacheAge,
34
+ enableMetrics: true
35
+ }, this.logger);
36
+ this.metricsCollector = new MetricsCollector({
37
+ enabled: true,
38
+ slowQueryThreshold: this.options.slowQueryThreshold,
39
+ nPlusOneDetection: true,
40
+ missingIndexDetection: this.options.enableIndexRecommendations,
41
+ largeResultSetThreshold: 1000
42
+ }, this.logger);
43
+ }
44
+ /**
45
+ * Optimize and execute a query
46
+ */
47
+ async optimizeQuery(query, params = [], context) {
48
+ const startTime = performance.now();
49
+ try {
50
+ // Check cache first
51
+ if (this.options.enableQueryCache) {
52
+ const cachedResult = this.cacheService.getCachedQuery(query, params);
53
+ if (cachedResult) {
54
+ const executionTime = performance.now() - startTime;
55
+ return {
56
+ originalQuery: query,
57
+ optimizedQuery: query,
58
+ optimizationType: 'cache_hit',
59
+ executionTime,
60
+ improvement: 100,
61
+ suggestions: ['Query served from cache'],
62
+ result: cachedResult
63
+ };
64
+ }
65
+ }
66
+ // Parse and analyze query
67
+ const parsedQuery = QueryParser.parseQuery(query);
68
+ const optimization = await this.analyzeQuery(parsedQuery, query, params);
69
+ const optimizedQuery = optimization.optimizedQuery || query;
70
+ // Execute the query
71
+ const result = await this.executeQuery(optimizedQuery, params);
72
+ const executionTime = performance.now() - startTime;
73
+ // Cache the result if appropriate
74
+ if (this.options.enableQueryCache &&
75
+ QueryParser.shouldCache(query, executionTime)) {
76
+ this.cacheService.cacheQuery(query, params, result);
77
+ }
78
+ // Record metrics
79
+ this.metricsCollector.recordQuery(query, executionTime, {
80
+ table: context?.table,
81
+ operation: context?.operation,
82
+ resultCount: Array.isArray(result) ? result.length : 1
83
+ });
84
+ // Generate suggestions
85
+ const suggestions = this.generateSuggestions(parsedQuery, executionTime, context);
86
+ return {
87
+ originalQuery: query,
88
+ optimizedQuery,
89
+ optimizationType: optimization.type,
90
+ executionTime,
91
+ improvement: optimization.improvement,
92
+ suggestions,
93
+ result
94
+ };
95
+ }
96
+ catch (error) {
97
+ const executionTime = performance.now() - startTime;
98
+ // Record error metrics
99
+ const errorMessage = error instanceof Error ? error.message : String(error);
100
+ this.metricsCollector.recordQuery(query, executionTime, {
101
+ table: context?.table,
102
+ operation: context?.operation,
103
+ error: errorMessage
104
+ });
105
+ this.logger.error(`Query optimization failed: ${errorMessage}`, {
106
+ query,
107
+ executionTime
108
+ });
109
+ throw error;
110
+ }
111
+ }
112
+ /**
113
+ * Get index recommendations
114
+ */
115
+ async getIndexRecommendations() {
116
+ if (!this.options.enableIndexRecommendations) {
117
+ return [];
118
+ }
119
+ const slowQueries = this.metricsCollector.getSlowQueries();
120
+ const recommendations = [];
121
+ for (const queryInfo of slowQueries) {
122
+ const parsedQuery = QueryParser.parseQuery(queryInfo.query);
123
+ const queryRecommendations = this.analyzeQueryForIndexes(parsedQuery, queryInfo.query);
124
+ recommendations.push(...queryRecommendations);
125
+ }
126
+ return this.deduplicateRecommendations(recommendations);
127
+ }
128
+ /**
129
+ * Get query performance statistics
130
+ */
131
+ getQueryStats() {
132
+ const metricsStats = this.metricsCollector.getPerformanceStats();
133
+ const cacheStats = this.cacheService.getStats();
134
+ return {
135
+ ...metricsStats,
136
+ cache: cacheStats,
137
+ indexRecommendations: 0 // Would be populated by recommendations
138
+ };
139
+ }
140
+ /**
141
+ * Clear query cache
142
+ */
143
+ clearCache() {
144
+ this.cacheService.clear();
145
+ }
146
+ /**
147
+ * Clear metrics
148
+ */
149
+ clearMetrics() {
150
+ this.metricsCollector.clear();
151
+ }
152
+ /**
153
+ * Get cache statistics
154
+ */
155
+ getCacheStats() {
156
+ return this.cacheService.getStats();
157
+ }
158
+ /**
159
+ * Get performance warnings
160
+ */
161
+ getPerformanceWarnings() {
162
+ return this.metricsCollector.getRecentWarnings();
163
+ }
164
+ /**
165
+ * Shutdown optimizer
166
+ */
167
+ shutdown() {
168
+ this.cacheService.shutdown();
169
+ this.metricsCollector.shutdown();
170
+ }
171
+ /**
172
+ * Analyze query for optimization opportunities
173
+ */
174
+ async analyzeQuery(parsedQuery, originalQuery, params) {
175
+ let optimizedQuery = originalQuery;
176
+ let improvement = 0;
177
+ let type = 'no_optimization';
178
+ // Apply query-specific optimizations
179
+ if (parsedQuery.type === 'SELECT') {
180
+ const selectOptimization = this.optimizeSelectQuery(parsedQuery, originalQuery);
181
+ if (selectOptimization.improvement > 0) {
182
+ optimizedQuery = selectOptimization.optimizedQuery;
183
+ improvement = selectOptimization.improvement;
184
+ type = 'query_rewrite';
185
+ }
186
+ }
187
+ if (parsedQuery.isComplex) {
188
+ const complexOptimization = this.optimizeComplexQuery(parsedQuery, originalQuery);
189
+ if (complexOptimization.improvement > improvement) {
190
+ optimizedQuery = complexOptimization.optimizedQuery;
191
+ improvement = complexOptimization.improvement;
192
+ type = 'query_rewrite';
193
+ }
194
+ }
195
+ return {
196
+ optimizedQuery,
197
+ type,
198
+ improvement
199
+ };
200
+ }
201
+ /**
202
+ * Optimize SELECT queries
203
+ */
204
+ optimizeSelectQuery(parsedQuery, originalQuery) {
205
+ let optimizedQuery = originalQuery;
206
+ let improvement = 0;
207
+ // Add LIMIT if missing and query could return many rows
208
+ if (!parsedQuery.hasLimit && QueryParser.couldReturnManyRows(originalQuery)) {
209
+ optimizedQuery = `${originalQuery} LIMIT 1000`;
210
+ improvement = 10;
211
+ }
212
+ // Optimize ORDER BY with LIMIT
213
+ if (parsedQuery.hasLimit && parsedQuery.orderByColumns.length > 0) {
214
+ const orderByColumn = parsedQuery.orderByColumns[0];
215
+ const table = parsedQuery.tables[0];
216
+ if (table && this.hasIndexOnColumn(table, orderByColumn)) {
217
+ improvement = Math.max(improvement, 20);
218
+ }
219
+ }
220
+ return {
221
+ optimizedQuery,
222
+ improvement
223
+ };
224
+ }
225
+ /**
226
+ * Optimize complex queries (joins, aggregates)
227
+ */
228
+ optimizeComplexQuery(parsedQuery, originalQuery) {
229
+ let optimizedQuery = originalQuery;
230
+ let improvement = 0;
231
+ // Check for missing indexes on join columns
232
+ for (const { table, column } of parsedQuery.joinColumns) {
233
+ if (!this.hasIndexOnColumn(table, column)) {
234
+ improvement = Math.max(improvement, 15);
235
+ }
236
+ }
237
+ // Check for missing indexes on WHERE columns
238
+ for (const column of parsedQuery.whereColumns) {
239
+ const table = parsedQuery.tables[0];
240
+ if (table && !this.hasIndexOnColumn(table, column)) {
241
+ improvement = Math.max(improvement, 20);
242
+ }
243
+ }
244
+ // Check for missing indexes on GROUP BY columns
245
+ if (parsedQuery.hasAggregates && parsedQuery.groupByColumns.length > 0) {
246
+ const table = parsedQuery.tables[0];
247
+ if (table && parsedQuery.groupByColumns.every(col => this.hasIndexOnColumn(table, col))) {
248
+ improvement = Math.max(improvement, 25);
249
+ }
250
+ }
251
+ return {
252
+ optimizedQuery,
253
+ improvement
254
+ };
255
+ }
256
+ /**
257
+ * Analyze query for index recommendations
258
+ */
259
+ analyzeQueryForIndexes(parsedQuery, originalQuery) {
260
+ const recommendations = [];
261
+ const table = parsedQuery.tables[0];
262
+ if (!table) {
263
+ return recommendations;
264
+ }
265
+ // Analyze WHERE clauses
266
+ for (const column of parsedQuery.whereColumns) {
267
+ if (!this.hasIndexOnColumn(table, column)) {
268
+ recommendations.push({
269
+ table,
270
+ columns: [column],
271
+ type: 'btree',
272
+ reason: `WHERE clause on ${column}`,
273
+ estimatedImprovement: 20,
274
+ priority: 'medium'
275
+ });
276
+ }
277
+ }
278
+ // Analyze JOIN columns
279
+ for (const { table: joinTable, column } of parsedQuery.joinColumns) {
280
+ if (!this.hasIndexOnColumn(joinTable, column)) {
281
+ recommendations.push({
282
+ table: joinTable,
283
+ columns: [column],
284
+ type: 'btree',
285
+ reason: `JOIN condition on ${column}`,
286
+ estimatedImprovement: 30,
287
+ priority: 'high'
288
+ });
289
+ }
290
+ }
291
+ // Analyze ORDER BY columns
292
+ for (const column of parsedQuery.orderByColumns) {
293
+ if (!this.hasIndexOnColumn(table, column)) {
294
+ recommendations.push({
295
+ table,
296
+ columns: [column],
297
+ type: 'btree',
298
+ reason: `ORDER BY clause on ${column}`,
299
+ estimatedImprovement: 15,
300
+ priority: 'medium'
301
+ });
302
+ }
303
+ }
304
+ return recommendations;
305
+ }
306
+ /**
307
+ * Deduplicate index recommendations
308
+ */
309
+ deduplicateRecommendations(recommendations) {
310
+ const unique = new Map();
311
+ for (const rec of recommendations) {
312
+ const key = `${rec.table}:${rec.columns.join(',')}`;
313
+ if (!unique.has(key) || unique.get(key).priority === 'low') {
314
+ unique.set(key, rec);
315
+ }
316
+ }
317
+ return Array.from(unique.values()).sort((a, b) => {
318
+ const priorityOrder = { high: 3, medium: 2, low: 1 };
319
+ return priorityOrder[b.priority] - priorityOrder[a.priority];
320
+ });
321
+ }
322
+ /**
323
+ * Generate optimization suggestions
324
+ */
325
+ generateSuggestions(parsedQuery, executionTime, context) {
326
+ const suggestions = [];
327
+ if (executionTime > this.options.slowQueryThreshold) {
328
+ suggestions.push(`Query took ${executionTime.toFixed(2)}ms - consider adding indexes`);
329
+ }
330
+ if (parsedQuery.type === 'SELECT' && !parsedQuery.hasLimit) {
331
+ suggestions.push('Consider adding LIMIT clause to prevent large result sets');
332
+ }
333
+ if (parsedQuery.joinColumns.length > 0) {
334
+ for (const { table, column } of parsedQuery.joinColumns) {
335
+ if (!this.hasIndexOnColumn(table, column)) {
336
+ suggestions.push(`Consider adding index on ${table}.${column} for join optimization`);
337
+ }
338
+ }
339
+ }
340
+ if (parsedQuery.hasAggregates && parsedQuery.groupByColumns.length > 0) {
341
+ suggestions.push('Consider adding composite indexes for GROUP BY columns');
342
+ }
343
+ return suggestions;
344
+ }
345
+ /**
346
+ * Execute a query with error handling
347
+ * Note: This creates a proper SQL query with parameters
348
+ */
349
+ async executeQuery(query, params) {
350
+ try {
351
+ // Split query by '?' placeholders and create template string array
352
+ const parts = query.split('?');
353
+ const templateStrings = Object.assign([...parts], { raw: [...parts] });
354
+ // Create SQL query using template tag syntax
355
+ const sqlQuery = sql(templateStrings, ...params);
356
+ // Compile the query first to match the expected interface
357
+ const compiledQuery = sqlQuery.compile(this.db);
358
+ const result = await this.db.executeQuery(compiledQuery);
359
+ return result.rows;
360
+ }
361
+ catch (error) {
362
+ const errorMessage = error instanceof Error ? error.message : String(error);
363
+ this.logger.error(`Query execution failed: ${errorMessage}`, { query, params });
364
+ throw error;
365
+ }
366
+ }
367
+ /**
368
+ * Check if column has an index
369
+ */
370
+ hasIndexOnColumn(table, column) {
371
+ const tableInfo = this.schemaInfo.tables.find(t => t.name === table);
372
+ if (!tableInfo)
373
+ return false;
374
+ // Check if column is primary key
375
+ const columnInfo = tableInfo.columns.find(col => col.name === column);
376
+ if (columnInfo?.isPrimaryKey)
377
+ return true;
378
+ // Check if column has an index
379
+ return tableInfo.indexes.some(idx => idx.columns.includes(column));
380
+ }
381
+ }
382
+ /**
383
+ * Factory function to create query optimizer
384
+ */
385
+ export function createQueryOptimizer(db, schemaInfo, options, logger) {
386
+ return new QueryOptimizer(db, schemaInfo, options, logger);
387
+ }
@@ -0,0 +1,177 @@
1
+ import { Logger } from '../../logging/logger.js';
2
+ export interface CacheEntry<T = any> {
3
+ key: string;
4
+ value: T;
5
+ timestamp: number;
6
+ ttl: number;
7
+ hitCount: number;
8
+ size: number;
9
+ }
10
+ export interface CacheConfig {
11
+ maxSize: number;
12
+ defaultTtl: number;
13
+ cleanupInterval: number;
14
+ enableCompression: boolean;
15
+ enableMetrics: boolean;
16
+ }
17
+ export interface CacheStats {
18
+ size: number;
19
+ maxSize: number;
20
+ hitCount: number;
21
+ missCount: number;
22
+ hitRate: number;
23
+ totalSize: number;
24
+ evictions: number;
25
+ lastCleanup: Date;
26
+ }
27
+ export interface CacheMetrics {
28
+ operations: {
29
+ get: number;
30
+ set: number;
31
+ delete: number;
32
+ clear: number;
33
+ };
34
+ performance: {
35
+ averageGetTime: number;
36
+ averageSetTime: number;
37
+ slowestOperation: number;
38
+ };
39
+ memory: {
40
+ totalSize: number;
41
+ averageEntrySize: number;
42
+ largestEntry: number;
43
+ };
44
+ }
45
+ /**
46
+ * Generic cache service with TTL, size limits, and metrics
47
+ */
48
+ export declare class CacheService<T = any> {
49
+ private cache;
50
+ private config;
51
+ private stats;
52
+ private metrics;
53
+ private cleanupTimer?;
54
+ protected logger: Logger;
55
+ constructor(config?: Partial<CacheConfig>, logger?: Logger);
56
+ /**
57
+ * Get a value from cache
58
+ */
59
+ get(key: string): T | null;
60
+ /**
61
+ * Set a value in cache
62
+ */
63
+ set(key: string, value: T, ttl?: number): boolean;
64
+ /**
65
+ * Delete a value from cache
66
+ */
67
+ delete(key: string): boolean;
68
+ /**
69
+ * Check if key exists in cache
70
+ */
71
+ has(key: string): boolean;
72
+ /**
73
+ * Clear all entries from cache
74
+ */
75
+ clear(): void;
76
+ /**
77
+ * Get cache statistics
78
+ */
79
+ getStats(): CacheStats;
80
+ /**
81
+ * Get detailed metrics
82
+ */
83
+ getMetrics(): CacheMetrics;
84
+ /**
85
+ * Get cache size
86
+ */
87
+ size(): number;
88
+ /**
89
+ * Get all cache keys
90
+ */
91
+ keys(): string[];
92
+ /**
93
+ * Get cache entries (for debugging)
94
+ */
95
+ entries(): Array<{
96
+ key: string;
97
+ entry: CacheEntry<T>;
98
+ }>;
99
+ /**
100
+ * Warm cache with multiple entries
101
+ */
102
+ warm(entries: Array<{
103
+ key: string;
104
+ value: T;
105
+ ttl?: number;
106
+ }>): number;
107
+ /**
108
+ * Get cache health status
109
+ */
110
+ getHealth(): {
111
+ status: 'healthy' | 'warning' | 'critical';
112
+ issues: string[];
113
+ recommendations: string[];
114
+ };
115
+ /**
116
+ * Shutdown cache service
117
+ */
118
+ shutdown(): void;
119
+ /**
120
+ * Check if cache entry is expired
121
+ */
122
+ private isExpired;
123
+ /**
124
+ * Calculate size of a value (rough estimation)
125
+ */
126
+ private calculateSize;
127
+ /**
128
+ * Evict entries when cache is full
129
+ */
130
+ private evictEntries;
131
+ /**
132
+ * Update cache statistics
133
+ */
134
+ private updateStats;
135
+ /**
136
+ * Update hit rate
137
+ */
138
+ private updateHitRate;
139
+ /**
140
+ * Update performance metrics
141
+ */
142
+ private updatePerformanceMetrics;
143
+ /**
144
+ * Start cleanup timer
145
+ */
146
+ private startCleanupTimer;
147
+ /**
148
+ * Cleanup expired entries
149
+ */
150
+ private cleanup;
151
+ }
152
+ /**
153
+ * Factory function to create cache service
154
+ */
155
+ export declare function createCacheService<T = any>(config?: Partial<CacheConfig>, logger?: Logger): CacheService<T>;
156
+ /**
157
+ * Specialized query cache service
158
+ */
159
+ export declare class QueryCacheService extends CacheService<any> {
160
+ constructor(config?: Partial<CacheConfig>, logger?: Logger);
161
+ /**
162
+ * Cache query result with automatic key generation
163
+ */
164
+ cacheQuery(query: string, params: any[], result: any, ttl?: number): boolean;
165
+ /**
166
+ * Get cached query result
167
+ */
168
+ getCachedQuery(query: string, params: any[]): any | null;
169
+ /**
170
+ * Generate cache key for query
171
+ */
172
+ private generateQueryKey;
173
+ /**
174
+ * Invalidate cache entries for a specific table
175
+ */
176
+ invalidateTable(table: string): number;
177
+ }