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
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.NOORMME = void 0;
7
+ const kysely_js_1 = require("./kysely.js");
4
8
  const schema_discovery_js_1 = require("./schema/schema-discovery.js");
5
9
  const type_generator_js_1 = require("./types/type-generator.js");
6
10
  const repository_factory_js_1 = require("./repository/repository-factory.js");
@@ -10,9 +14,12 @@ const logger_js_1 = require("./logging/logger.js");
10
14
  const NoormError_js_1 = require("./errors/NoormError.js");
11
15
  const dotenv_1 = require("dotenv");
12
16
  const schema_watcher_js_1 = require("./watch/schema-watcher.js");
13
- const query_analyzer_js_1 = require("./performance/query-analyzer.js");
17
+ const metrics_collector_js_1 = require("./performance/services/metrics-collector.js");
14
18
  const sqlite_auto_optimizer_js_1 = require("./dialect/sqlite/sqlite-auto-optimizer.js");
15
19
  const sqlite_auto_indexer_js_1 = require("./dialect/sqlite/sqlite-auto-indexer.js");
20
+ const sqlite_dialect_js_1 = require("./dialect/sqlite/sqlite-dialect.js");
21
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
22
+ const compiled_query_js_1 = require("./query-compiler/compiled-query.js");
16
23
  // Global initialization lock to prevent concurrent initialization
17
24
  const globalInitLock = new Map();
18
25
  /**
@@ -30,12 +37,13 @@ class NOORMME {
30
37
  cacheManager;
31
38
  logger;
32
39
  schemaWatcher = null;
33
- queryAnalyzer = null;
40
+ metricsCollector = null;
34
41
  sqliteAutoOptimizer = null;
35
42
  sqliteAutoIndexer = null;
36
43
  initialized = false;
37
44
  repositories = new Map();
38
45
  instanceId;
46
+ schemaChangeCallbacks = [];
39
47
  constructor(configOrConnectionString) {
40
48
  // Load .env if it exists
41
49
  (0, dotenv_1.config)({ path: '.env' });
@@ -65,7 +73,7 @@ class NOORMME {
65
73
  this.instanceId = `${this.config.dialect}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
66
74
  // Initialize Kysely with the provided dialect
67
75
  this.dialect = this.createDialect();
68
- this.db = new (require('./kysely.js').Kysely)({
76
+ this.db = new kysely_js_1.Kysely({
69
77
  dialect: this.dialect,
70
78
  log: this.config.logging?.enabled ? this.logger.createKyselyLogger() : undefined
71
79
  });
@@ -110,34 +118,80 @@ class NOORMME {
110
118
  this.logger.info('Initializing NOORMME...');
111
119
  // Test database connection using the dialect-specific introspector
112
120
  const introspector = this.dialect.createIntrospector(this.db);
113
- await introspector.getTables();
114
- this.logger.info('Database connection successful');
115
- // Discover schema
121
+ let tables = [];
122
+ try {
123
+ tables = await introspector.getTables();
124
+ this.logger.info('Database connection successful');
125
+ }
126
+ catch (error) {
127
+ this.logger.warn('Database connection test failed, but continuing with initialization:', error);
128
+ // Continue with empty schema if connection test fails
129
+ }
130
+ // Discover schema - handle empty databases gracefully
116
131
  this.logger.info('Discovering database schema...');
117
- const schemaInfo = await this.schemaDiscovery.discoverSchema();
118
- this.logger.info(`Discovered ${schemaInfo.tables.length} tables`);
119
- // Generate types
132
+ let schemaInfo;
133
+ try {
134
+ schemaInfo = await this.schemaDiscovery.discoverSchema();
135
+ this.logger.info(`Discovered ${schemaInfo.tables.length} tables`);
136
+ }
137
+ catch (error) {
138
+ // In test mode, throw the error instead of silently continuing
139
+ if (process.env.NODE_ENV === 'test') {
140
+ this.logger.error('Schema discovery failed in test mode:', error);
141
+ throw new Error(`Schema discovery failed: ${error instanceof Error ? error.message : String(error)}`);
142
+ }
143
+ this.logger.warn('Schema discovery failed, using empty schema:', error);
144
+ // Create empty schema info if discovery fails
145
+ schemaInfo = {
146
+ tables: [],
147
+ relationships: [],
148
+ views: []
149
+ };
150
+ }
151
+ // Generate types - handle empty schema gracefully
120
152
  this.logger.info('Generating TypeScript types...');
121
- const generatedTypes = this.typeGenerator.generateTypes(schemaInfo);
122
- this.logger.info(`Generated types for ${generatedTypes.entities.length} entities`);
123
- // Cache schema and types
124
- await this.cacheManager.set('schema', schemaInfo);
125
- await this.cacheManager.set('types', generatedTypes);
126
- // Initialize relationship engine
127
- this.relationshipEngine.initialize(schemaInfo.relationships);
128
- // Initialize query analyzer for development mode
129
- this.queryAnalyzer = new query_analyzer_js_1.QueryAnalyzer(this.db, this.logger, schemaInfo, {
130
- enabled: this.config.performance?.enableQueryOptimization ?? true,
153
+ let generatedTypes;
154
+ try {
155
+ generatedTypes = this.typeGenerator.generateTypes(schemaInfo);
156
+ this.logger.info(`Generated types for ${generatedTypes.entities.length} entities`);
157
+ }
158
+ catch (error) {
159
+ this.logger.warn('Type generation failed, using empty types:', error);
160
+ // Create empty type info if generation fails
161
+ generatedTypes = {
162
+ entities: [],
163
+ relationships: [],
164
+ types: {}
165
+ };
166
+ }
167
+ // Cache schema and types - handle caching errors gracefully
168
+ try {
169
+ await this.cacheManager.set('schema', schemaInfo);
170
+ await this.cacheManager.set('types', generatedTypes);
171
+ }
172
+ catch (error) {
173
+ this.logger.warn('Failed to cache schema/types, continuing without cache:', error);
174
+ }
175
+ // Initialize relationship engine - handle empty relationships
176
+ try {
177
+ this.relationshipEngine.initialize(schemaInfo.relationships);
178
+ }
179
+ catch (error) {
180
+ this.logger.warn('Failed to initialize relationship engine:', error);
181
+ }
182
+ // Initialize metrics collector for development mode
183
+ this.metricsCollector = new metrics_collector_js_1.MetricsCollector({
184
+ enabled: process.env.NODE_ENV === 'development',
131
185
  slowQueryThreshold: 1000,
132
- missingIndexDetection: true,
133
- largeResultSetThreshold: 1000
134
- });
186
+ nPlusOneDetection: true,
187
+ missingIndexDetection: true
188
+ }, this.logger);
135
189
  // Initialize SQLite-specific auto-optimization features
136
190
  if (this.config.dialect === 'sqlite') {
137
191
  this.sqliteAutoOptimizer = sqlite_auto_optimizer_js_1.SQLiteAutoOptimizer.getInstance(this.logger);
138
192
  this.sqliteAutoIndexer = sqlite_auto_indexer_js_1.SQLiteAutoIndexer.getInstance(this.logger);
139
193
  // Apply automatic optimizations if enabled (default: true)
140
- const enableAutoOptimization = this.config.performance?.enableAutoOptimization !== false;
194
+ const enableAutoOptimization = this.config.automation?.enableAutoOptimization !== false;
141
195
  if (enableAutoOptimization) {
142
196
  await this.applySQLiteAutoOptimizations();
143
197
  }
@@ -209,8 +263,8 @@ class NOORMME {
209
263
  if (this.config.dialect === 'sqlite' && this.sqliteAutoIndexer) {
210
264
  this.sqliteAutoIndexer.recordQuery(query, executionTime, table);
211
265
  }
212
- if (this.queryAnalyzer) {
213
- this.queryAnalyzer.recordQuery(query, executionTime, undefined, table);
266
+ if (this.metricsCollector) {
267
+ this.metricsCollector.recordQuery(query, executionTime, { table });
214
268
  }
215
269
  }
216
270
  /**
@@ -247,7 +301,8 @@ class NOORMME {
247
301
  }
248
302
  const table = schemaInfo.tables.find(t => t.name === tableName);
249
303
  if (!table) {
250
- throw new Error(`Table '${tableName}' not found in schema. Available tables: ${schemaInfo.tables.map(t => t.name).join(', ')}`);
304
+ const availableTables = schemaInfo.tables.map(t => t.name);
305
+ throw new NoormError_js_1.TableNotFoundError(tableName, availableTables);
251
306
  }
252
307
  const repository = this.repositoryFactory.createRepository(table, schemaInfo.relationships);
253
308
  this.repositories.set(tableName, repository);
@@ -301,28 +356,34 @@ class NOORMME {
301
356
  /**
302
357
  * Start monitoring schema changes in development mode
303
358
  */
304
- startSchemaWatching(options) {
359
+ async startSchemaWatching(options) {
305
360
  if (!this.initialized) {
306
361
  throw new NoormError_js_1.NoormError('NOORMME must be initialized before starting schema watching');
307
362
  }
308
- if (!this.schemaWatcher) {
309
- this.schemaWatcher = new schema_watcher_js_1.SchemaWatcher(this.db, this.schemaDiscovery, this.logger, options);
310
- // Auto-refresh schema when changes detected
311
- this.schemaWatcher.onSchemaChange(async (changes) => {
312
- this.logger.info(`Schema changes detected: ${changes.length} changes`);
313
- changes.forEach(change => {
314
- this.logger.info(` - ${change.type}: ${change.table}`);
315
- });
316
- try {
317
- await this.refreshSchema();
318
- this.logger.info('Schema refreshed successfully');
319
- }
320
- catch (error) {
321
- this.logger.error('Failed to refresh schema:', error);
322
- }
323
- });
363
+ // If watcher already exists (e.g., from onSchemaChange), recreate it with new options
364
+ if (this.schemaWatcher) {
365
+ this.schemaWatcher.stopWatching();
366
+ }
367
+ this.schemaWatcher = new schema_watcher_js_1.SchemaWatcher(this.db, this.schemaDiscovery, this.logger, options);
368
+ // Register all previously registered callbacks
369
+ for (const callback of this.schemaChangeCallbacks) {
370
+ this.schemaWatcher.onSchemaChange(callback);
324
371
  }
325
- this.schemaWatcher.startWatching();
372
+ // Auto-refresh schema when changes detected
373
+ this.schemaWatcher.onSchemaChange(async (changes) => {
374
+ this.logger.info(`Schema changes detected: ${changes.length} changes`);
375
+ changes.forEach(change => {
376
+ this.logger.info(` - ${change.type}: ${change.table}`);
377
+ });
378
+ try {
379
+ await this.refreshSchema();
380
+ this.logger.info('Schema refreshed successfully');
381
+ }
382
+ catch (error) {
383
+ this.logger.error('Failed to refresh schema:', error);
384
+ }
385
+ });
386
+ await this.schemaWatcher.startWatching();
326
387
  }
327
388
  /**
328
389
  * Stop monitoring schema changes
@@ -330,16 +391,19 @@ class NOORMME {
330
391
  stopSchemaWatching() {
331
392
  if (this.schemaWatcher) {
332
393
  this.schemaWatcher.stopWatching();
394
+ // Don't set to null - keep watcher instance for potential restart
333
395
  }
334
396
  }
335
397
  /**
336
398
  * Register callback for schema changes
337
399
  */
338
400
  onSchemaChange(callback) {
339
- if (!this.schemaWatcher) {
340
- this.schemaWatcher = new schema_watcher_js_1.SchemaWatcher(this.db, this.schemaDiscovery, this.logger);
401
+ // Store callback so it can be re-registered if watcher is recreated
402
+ this.schemaChangeCallbacks.push(callback);
403
+ // If watcher already exists, register callback immediately
404
+ if (this.schemaWatcher) {
405
+ this.schemaWatcher.onSchemaChange(callback);
341
406
  }
342
- this.schemaWatcher.onSchemaChange(callback);
343
407
  }
344
408
  /**
345
409
  * Get performance metrics
@@ -351,10 +415,10 @@ class NOORMME {
351
415
  cacheHitRate: this.cacheManager.getHitRate(),
352
416
  repositoryCount: this.repositories.size
353
417
  };
354
- if (this.queryAnalyzer) {
418
+ if (this.metricsCollector) {
355
419
  return {
356
420
  ...baseMetrics,
357
- ...this.queryAnalyzer.getPerformanceStats()
421
+ ...this.metricsCollector.getPerformanceStats()
358
422
  };
359
423
  }
360
424
  return baseMetrics;
@@ -370,16 +434,21 @@ class NOORMME {
370
434
  if (!schemaInfo) {
371
435
  throw new NoormError_js_1.NoormError('Schema not found. Please reinitialize NOORMME.');
372
436
  }
373
- this.queryAnalyzer = new query_analyzer_js_1.QueryAnalyzer(this.db, this.logger, schemaInfo, options);
437
+ this.metricsCollector = new metrics_collector_js_1.MetricsCollector({
438
+ enabled: true,
439
+ slowQueryThreshold: options?.slowQueryThreshold || 1000,
440
+ nPlusOneDetection: true,
441
+ missingIndexDetection: true
442
+ }, this.logger);
374
443
  this.logger.info('Query performance monitoring enabled');
375
444
  }
376
445
  /**
377
446
  * Disable query performance monitoring
378
447
  */
379
448
  disablePerformanceMonitoring() {
380
- if (this.queryAnalyzer) {
381
- this.queryAnalyzer.clearHistory();
382
- this.queryAnalyzer = null;
449
+ if (this.metricsCollector) {
450
+ this.metricsCollector.clear();
451
+ this.metricsCollector = null;
383
452
  this.logger.info('Query performance monitoring disabled');
384
453
  }
385
454
  }
@@ -396,6 +465,12 @@ class NOORMME {
396
465
  this.repositories.clear();
397
466
  this.logger.info('NOORMME closed');
398
467
  }
468
+ /**
469
+ * Alias for close() - for backward compatibility
470
+ */
471
+ async destroy() {
472
+ return this.close();
473
+ }
399
474
  /**
400
475
  * Get the underlying Kysely instance for custom queries
401
476
  */
@@ -411,11 +486,9 @@ class NOORMME {
411
486
  /**
412
487
  * Execute raw SQL
413
488
  */
414
- async execute(sql, parameters) {
415
- return await this.db.executeQuery({
416
- sql,
417
- parameters: parameters || []
418
- });
489
+ async execute(sqlString, parameters) {
490
+ const compiledQuery = compiled_query_js_1.CompiledQuery.raw(sqlString, parameters || []);
491
+ return await this.db.executeQuery(compiledQuery);
419
492
  }
420
493
  mergeConfig(config) {
421
494
  return {
@@ -507,9 +580,8 @@ class NOORMME {
507
580
  const { dialect, connection } = this.config;
508
581
  switch (dialect) {
509
582
  case 'sqlite':
510
- const Database = require('better-sqlite3');
511
- return new (require('./dialect/sqlite/sqlite-dialect').SqliteDialect)({
512
- database: new Database(connection.database)
583
+ return new sqlite_dialect_js_1.SqliteDialect({
584
+ database: new better_sqlite3_1.default(connection.database)
513
585
  });
514
586
  default:
515
587
  throw new Error(`Unsupported dialect: ${dialect}`);
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ColumnNode = void 0;
4
4
  const object_utils_js_1 = require("../util/object-utils.js");
5
5
  const identifier_node_js_1 = require("./identifier-node.js");
6
+ const security_validator_js_1 = require("../util/security-validator.js");
6
7
  /**
7
8
  * @internal
8
9
  */
@@ -11,6 +12,9 @@ exports.ColumnNode = (0, object_utils_js_1.freeze)({
11
12
  return node.kind === 'ColumnNode';
12
13
  },
13
14
  create(column) {
15
+ // SECURITY: Validate column name to prevent SQL injection
16
+ // Even though IdentifierNode will validate, we validate here too for defense in depth
17
+ (0, security_validator_js_1.validateIdentifier)(column, 'column name');
14
18
  return (0, object_utils_js_1.freeze)({
15
19
  kind: 'ColumnNode',
16
20
  column: identifier_node_js_1.IdentifierNode.create(column),
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.IdentifierNode = void 0;
4
4
  const object_utils_js_1 = require("../util/object-utils.js");
5
+ const security_validator_js_1 = require("../util/security-validator.js");
5
6
  /**
6
7
  * @internal
7
8
  */
@@ -10,6 +11,9 @@ exports.IdentifierNode = (0, object_utils_js_1.freeze)({
10
11
  return node.kind === 'IdentifierNode';
11
12
  },
12
13
  create(name) {
14
+ // SECURITY: Validate identifier to prevent SQL injection at the lowest level
15
+ // This ensures ALL identifiers are validated, even when parsers are called directly
16
+ (0, security_validator_js_1.validateIdentifier)(name, 'identifier');
13
17
  return (0, object_utils_js_1.freeze)({
14
18
  kind: 'IdentifierNode',
15
19
  name,
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TableNode = void 0;
4
4
  const object_utils_js_1 = require("../util/object-utils.js");
5
5
  const schemable_identifier_node_js_1 = require("./schemable-identifier-node.js");
6
+ const security_validator_js_1 = require("../util/security-validator.js");
6
7
  /**
7
8
  * @internal
8
9
  */
@@ -11,12 +12,18 @@ exports.TableNode = (0, object_utils_js_1.freeze)({
11
12
  return node.kind === 'TableNode';
12
13
  },
13
14
  create(table) {
15
+ // SECURITY: Validate table name to prevent SQL injection
16
+ // Even though SchemableIdentifierNode will validate, we validate here too for defense in depth
17
+ (0, security_validator_js_1.validateIdentifier)(table, 'table name');
14
18
  return (0, object_utils_js_1.freeze)({
15
19
  kind: 'TableNode',
16
20
  table: schemable_identifier_node_js_1.SchemableIdentifierNode.create(table),
17
21
  });
18
22
  },
19
23
  createWithSchema(schema, table) {
24
+ // SECURITY: Validate both schema and table names
25
+ (0, security_validator_js_1.validateIdentifier)(schema, 'schema name');
26
+ (0, security_validator_js_1.validateIdentifier)(table, 'table name');
20
27
  return (0, object_utils_js_1.freeze)({
21
28
  kind: 'TableNode',
22
29
  table: schemable_identifier_node_js_1.SchemableIdentifierNode.createWithSchema(schema, table),
@@ -54,13 +54,16 @@ function parseJSONReference(ref, op) {
54
54
  function parseStringReference(ref) {
55
55
  const COLUMN_SEPARATOR = '.';
56
56
  if (!ref.includes(COLUMN_SEPARATOR)) {
57
+ // SECURITY: ColumnNode.create will validate the column name
57
58
  return reference_node_js_1.ReferenceNode.create(column_node_js_1.ColumnNode.create(ref));
58
59
  }
59
60
  const parts = ref.split(COLUMN_SEPARATOR).map(trim);
60
61
  if (parts.length === 3) {
62
+ // SECURITY: parseStringReferenceWithTableAndSchema uses ColumnNode and TableNode which validate
61
63
  return parseStringReferenceWithTableAndSchema(parts);
62
64
  }
63
65
  if (parts.length === 2) {
66
+ // SECURITY: parseStringReferenceWithTable uses ColumnNode and TableNode which validate
64
67
  return parseStringReferenceWithTable(parts);
65
68
  }
66
69
  throw new Error(`invalid column reference ${ref}`);
@@ -93,10 +96,12 @@ function parseOrderedColumnName(column) {
93
96
  }
94
97
  function parseStringReferenceWithTableAndSchema(parts) {
95
98
  const [schema, table, column] = parts;
99
+ // SECURITY: Both ColumnNode.create and TableNode.createWithSchema validate their inputs
96
100
  return reference_node_js_1.ReferenceNode.create(column_node_js_1.ColumnNode.create(column), table_node_js_1.TableNode.createWithSchema(schema, table));
97
101
  }
98
102
  function parseStringReferenceWithTable(parts) {
99
103
  const [table, column] = parts;
104
+ // SECURITY: Both ColumnNode.create and TableNode.create validate their inputs
100
105
  return reference_node_js_1.ReferenceNode.create(column_node_js_1.ColumnNode.create(column), table_node_js_1.TableNode.create(table));
101
106
  }
102
107
  function trim(str) {
@@ -43,9 +43,11 @@ function parseTable(from) {
43
43
  const SCHEMA_SEPARATOR = '.';
44
44
  if (from.includes(SCHEMA_SEPARATOR)) {
45
45
  const [schema, table] = from.split(SCHEMA_SEPARATOR).map(trim);
46
+ // SECURITY: TableNode.createWithSchema will validate both parts
46
47
  return table_node_js_1.TableNode.createWithSchema(schema, table);
47
48
  }
48
49
  else {
50
+ // SECURITY: TableNode.create will validate the table name
49
51
  return table_node_js_1.TableNode.create(from);
50
52
  }
51
53
  }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Performance module exports
3
+ * Clean, focused services, utilities, and classes
4
+ */
5
+ export * from './utils/query-parser';
6
+ export * from './services/cache-service';
7
+ export * from './services/metrics-collector';
8
+ export * from './services/connection-factory';
9
+ export * from './query-optimizer';
10
+ /**
11
+ * Default performance configuration
12
+ */
13
+ export declare const defaultPerformanceConfig: {
14
+ cache: {
15
+ maxSize: number;
16
+ defaultTtl: number;
17
+ cleanupInterval: number;
18
+ enableMetrics: boolean;
19
+ };
20
+ metrics: {
21
+ enabled: boolean;
22
+ slowQueryThreshold: number;
23
+ nPlusOneDetection: boolean;
24
+ missingIndexDetection: boolean;
25
+ largeResultSetThreshold: number;
26
+ maxHistorySize: number;
27
+ warningRetentionDays: number;
28
+ };
29
+ connectionPool: {
30
+ minConnections: number;
31
+ maxConnections: number;
32
+ acquireTimeout: number;
33
+ idleTimeout: number;
34
+ validationInterval: number;
35
+ };
36
+ optimizer: {
37
+ enableQueryCache: boolean;
38
+ enableIndexRecommendations: boolean;
39
+ enableQueryRewriting: boolean;
40
+ slowQueryThreshold: number;
41
+ cacheSize: number;
42
+ maxCacheAge: number;
43
+ };
44
+ };
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ /**
3
+ * Performance module exports
4
+ * Clean, focused services, utilities, and classes
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
18
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
19
+ };
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.defaultPerformanceConfig = void 0;
22
+ // Utilities
23
+ __exportStar(require("./utils/query-parser"), exports);
24
+ // Services
25
+ __exportStar(require("./services/cache-service"), exports);
26
+ __exportStar(require("./services/metrics-collector"), exports);
27
+ __exportStar(require("./services/connection-factory"), exports);
28
+ // Main optimizer
29
+ __exportStar(require("./query-optimizer"), exports);
30
+ /**
31
+ * Default performance configuration
32
+ */
33
+ exports.defaultPerformanceConfig = {
34
+ cache: {
35
+ maxSize: 1000,
36
+ defaultTtl: 300000, // 5 minutes
37
+ cleanupInterval: 60000, // 1 minute
38
+ enableMetrics: true
39
+ },
40
+ metrics: {
41
+ enabled: true,
42
+ slowQueryThreshold: 1000,
43
+ nPlusOneDetection: true,
44
+ missingIndexDetection: true,
45
+ largeResultSetThreshold: 1000,
46
+ maxHistorySize: 10000,
47
+ warningRetentionDays: 7
48
+ },
49
+ connectionPool: {
50
+ minConnections: 2,
51
+ maxConnections: 10,
52
+ acquireTimeout: 30000,
53
+ idleTimeout: 300000,
54
+ validationInterval: 60000
55
+ },
56
+ optimizer: {
57
+ enableQueryCache: true,
58
+ enableIndexRecommendations: true,
59
+ enableQueryRewriting: true,
60
+ slowQueryThreshold: 1000,
61
+ cacheSize: 1000,
62
+ maxCacheAge: 300000
63
+ }
64
+ };
@@ -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;