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
@@ -3,19 +3,33 @@
3
3
  * Logger for NOORMME
4
4
  */
5
5
  export class Logger {
6
- config;
7
6
  queryLogs = [];
8
7
  queryCount = 0;
9
8
  totalQueryTime = 0;
10
- constructor(config = {}) {
11
- this.config = config;
9
+ config;
10
+ namespace;
11
+ constructor(configOrNamespace) {
12
+ if (typeof configOrNamespace === 'string') {
13
+ this.namespace = configOrNamespace;
14
+ this.config = { enabled: true, level: 'info' };
15
+ }
16
+ else {
17
+ this.config = configOrNamespace || {};
18
+ }
19
+ }
20
+ /**
21
+ * Format namespace prefix
22
+ */
23
+ getPrefix(level) {
24
+ const prefix = this.namespace ? `[${this.namespace}]` : '';
25
+ return `[NOORMME ${level.toUpperCase()}]${prefix}`;
12
26
  }
13
27
  /**
14
28
  * Log debug message
15
29
  */
16
30
  debug(message, ...args) {
17
31
  if (this.shouldLog('debug')) {
18
- console.debug(`[NOORMME DEBUG] ${message}`, ...args);
32
+ console.debug(`${this.getPrefix('debug')} ${message}`, ...args);
19
33
  }
20
34
  }
21
35
  /**
@@ -23,7 +37,7 @@ export class Logger {
23
37
  */
24
38
  info(message, ...args) {
25
39
  if (this.shouldLog('info')) {
26
- console.info(`[NOORMME INFO] ${message}`, ...args);
40
+ console.info(`${this.getPrefix('info')} ${message}`, ...args);
27
41
  }
28
42
  }
29
43
  /**
@@ -31,7 +45,7 @@ export class Logger {
31
45
  */
32
46
  warn(message, ...args) {
33
47
  if (this.shouldLog('warn')) {
34
- console.warn(`[NOORMME WARN] ${message}`, ...args);
48
+ console.warn(`${this.getPrefix('warn')} ${message}`, ...args);
35
49
  }
36
50
  }
37
51
  /**
@@ -39,7 +53,7 @@ export class Logger {
39
53
  */
40
54
  error(message, ...args) {
41
55
  if (this.shouldLog('error')) {
42
- console.error(`[NOORMME ERROR] ${message}`, ...args);
56
+ console.error(`${this.getPrefix('error')} ${message}`, ...args);
43
57
  }
44
58
  }
45
59
  /**
@@ -1,7 +1,6 @@
1
- import type { Kysely } from './kysely.js';
2
- import { NOORMConfig, SchemaInfo, Repository } from './types/index.js';
1
+ import { Kysely } from './kysely.js';
2
+ import { NOORMConfig, SchemaInfo, Repository, SchemaChange } from './types/index.js';
3
3
  import { WatchOptions } from './watch/schema-watcher.js';
4
- import { QueryAnalyzerOptions } from './performance/query-analyzer.js';
5
4
  /**
6
5
  * NOORMME - No ORM, just magic!
7
6
  * Zero-configuration pseudo-ORM that works with any existing database
@@ -17,12 +16,13 @@ export declare class NOORMME {
17
16
  private cacheManager;
18
17
  private logger;
19
18
  private schemaWatcher;
20
- private queryAnalyzer;
19
+ private metricsCollector;
21
20
  private sqliteAutoOptimizer;
22
21
  private sqliteAutoIndexer;
23
22
  private initialized;
24
23
  private repositories;
25
24
  private instanceId;
25
+ private schemaChangeCallbacks;
26
26
  constructor(configOrConnectionString?: NOORMConfig | string);
27
27
  /**
28
28
  * Initialize NOORMME - discovers schema and generates types
@@ -81,7 +81,7 @@ export declare class NOORMME {
81
81
  /**
82
82
  * Start monitoring schema changes in development mode
83
83
  */
84
- startSchemaWatching(options?: WatchOptions): void;
84
+ startSchemaWatching(options?: WatchOptions): Promise<void>;
85
85
  /**
86
86
  * Stop monitoring schema changes
87
87
  */
@@ -89,7 +89,7 @@ export declare class NOORMME {
89
89
  /**
90
90
  * Register callback for schema changes
91
91
  */
92
- onSchemaChange(callback: (changes: any[]) => void): void;
92
+ onSchemaChange(callback: (changes: SchemaChange[]) => void): void;
93
93
  /**
94
94
  * Get performance metrics
95
95
  */
@@ -102,7 +102,7 @@ export declare class NOORMME {
102
102
  /**
103
103
  * Enable query performance monitoring
104
104
  */
105
- enablePerformanceMonitoring(options?: QueryAnalyzerOptions): void;
105
+ enablePerformanceMonitoring(options?: any): void;
106
106
  /**
107
107
  * Disable query performance monitoring
108
108
  */
@@ -111,6 +111,10 @@ export declare class NOORMME {
111
111
  * Close database connections
112
112
  */
113
113
  close(): Promise<void>;
114
+ /**
115
+ * Alias for close() - for backward compatibility
116
+ */
117
+ destroy(): Promise<void>;
114
118
  /**
115
119
  * Get the underlying Kysely instance for custom queries
116
120
  */
@@ -122,7 +126,7 @@ export declare class NOORMME {
122
126
  /**
123
127
  * Execute raw SQL
124
128
  */
125
- execute(sql: string, parameters?: any[]): Promise<any>;
129
+ execute(sqlString: string, parameters?: unknown[]): Promise<unknown>;
126
130
  private mergeConfig;
127
131
  /**
128
132
  * Parse connection string into NOORMConfig
@@ -1,16 +1,20 @@
1
1
  /// <reference types="./noormme.d.ts" />
2
+ import { Kysely } from './kysely.js';
2
3
  import { SchemaDiscovery } from './schema/schema-discovery.js';
3
4
  import { TypeGenerator } from './types/type-generator.js';
4
5
  import { RepositoryFactory } from './repository/repository-factory.js';
5
6
  import { RelationshipEngine } from './relationships/relationship-engine.js';
6
7
  import { CacheManager } from './cache/cache-manager.js';
7
8
  import { Logger } from './logging/logger.js';
8
- import { NoormError } from './errors/NoormError.js';
9
+ import { NoormError, TableNotFoundError } from './errors/NoormError.js';
9
10
  import { config as loadDotenv } from 'dotenv';
10
11
  import { SchemaWatcher } from './watch/schema-watcher.js';
11
- import { QueryAnalyzer } from './performance/query-analyzer.js';
12
+ import { MetricsCollector } from './performance/services/metrics-collector.js';
12
13
  import { SQLiteAutoOptimizer } from './dialect/sqlite/sqlite-auto-optimizer.js';
13
14
  import { SQLiteAutoIndexer } from './dialect/sqlite/sqlite-auto-indexer.js';
15
+ import { SqliteDialect } from './dialect/sqlite/sqlite-dialect.js';
16
+ import Database from 'better-sqlite3';
17
+ import { CompiledQuery } from './query-compiler/compiled-query.js';
14
18
  // Global initialization lock to prevent concurrent initialization
15
19
  const globalInitLock = new Map();
16
20
  /**
@@ -28,12 +32,13 @@ export class NOORMME {
28
32
  cacheManager;
29
33
  logger;
30
34
  schemaWatcher = null;
31
- queryAnalyzer = null;
35
+ metricsCollector = null;
32
36
  sqliteAutoOptimizer = null;
33
37
  sqliteAutoIndexer = null;
34
38
  initialized = false;
35
39
  repositories = new Map();
36
40
  instanceId;
41
+ schemaChangeCallbacks = [];
37
42
  constructor(configOrConnectionString) {
38
43
  // Load .env if it exists
39
44
  loadDotenv({ path: '.env' });
@@ -63,7 +68,7 @@ export class NOORMME {
63
68
  this.instanceId = `${this.config.dialect}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
64
69
  // Initialize Kysely with the provided dialect
65
70
  this.dialect = this.createDialect();
66
- this.db = new (require('./kysely.js').Kysely)({
71
+ this.db = new Kysely({
67
72
  dialect: this.dialect,
68
73
  log: this.config.logging?.enabled ? this.logger.createKyselyLogger() : undefined
69
74
  });
@@ -108,34 +113,80 @@ export class NOORMME {
108
113
  this.logger.info('Initializing NOORMME...');
109
114
  // Test database connection using the dialect-specific introspector
110
115
  const introspector = this.dialect.createIntrospector(this.db);
111
- await introspector.getTables();
112
- this.logger.info('Database connection successful');
113
- // Discover schema
116
+ let tables = [];
117
+ try {
118
+ tables = await introspector.getTables();
119
+ this.logger.info('Database connection successful');
120
+ }
121
+ catch (error) {
122
+ this.logger.warn('Database connection test failed, but continuing with initialization:', error);
123
+ // Continue with empty schema if connection test fails
124
+ }
125
+ // Discover schema - handle empty databases gracefully
114
126
  this.logger.info('Discovering database schema...');
115
- const schemaInfo = await this.schemaDiscovery.discoverSchema();
116
- this.logger.info(`Discovered ${schemaInfo.tables.length} tables`);
117
- // Generate types
127
+ let schemaInfo;
128
+ try {
129
+ schemaInfo = await this.schemaDiscovery.discoverSchema();
130
+ this.logger.info(`Discovered ${schemaInfo.tables.length} tables`);
131
+ }
132
+ catch (error) {
133
+ // In test mode, throw the error instead of silently continuing
134
+ if (process.env.NODE_ENV === 'test') {
135
+ this.logger.error('Schema discovery failed in test mode:', error);
136
+ throw new Error(`Schema discovery failed: ${error instanceof Error ? error.message : String(error)}`);
137
+ }
138
+ this.logger.warn('Schema discovery failed, using empty schema:', error);
139
+ // Create empty schema info if discovery fails
140
+ schemaInfo = {
141
+ tables: [],
142
+ relationships: [],
143
+ views: []
144
+ };
145
+ }
146
+ // Generate types - handle empty schema gracefully
118
147
  this.logger.info('Generating TypeScript types...');
119
- const generatedTypes = this.typeGenerator.generateTypes(schemaInfo);
120
- this.logger.info(`Generated types for ${generatedTypes.entities.length} entities`);
121
- // Cache schema and types
122
- await this.cacheManager.set('schema', schemaInfo);
123
- await this.cacheManager.set('types', generatedTypes);
124
- // Initialize relationship engine
125
- this.relationshipEngine.initialize(schemaInfo.relationships);
126
- // Initialize query analyzer for development mode
127
- this.queryAnalyzer = new QueryAnalyzer(this.db, this.logger, schemaInfo, {
128
- enabled: this.config.performance?.enableQueryOptimization ?? true,
148
+ let generatedTypes;
149
+ try {
150
+ generatedTypes = this.typeGenerator.generateTypes(schemaInfo);
151
+ this.logger.info(`Generated types for ${generatedTypes.entities.length} entities`);
152
+ }
153
+ catch (error) {
154
+ this.logger.warn('Type generation failed, using empty types:', error);
155
+ // Create empty type info if generation fails
156
+ generatedTypes = {
157
+ entities: [],
158
+ relationships: [],
159
+ types: {}
160
+ };
161
+ }
162
+ // Cache schema and types - handle caching errors gracefully
163
+ try {
164
+ await this.cacheManager.set('schema', schemaInfo);
165
+ await this.cacheManager.set('types', generatedTypes);
166
+ }
167
+ catch (error) {
168
+ this.logger.warn('Failed to cache schema/types, continuing without cache:', error);
169
+ }
170
+ // Initialize relationship engine - handle empty relationships
171
+ try {
172
+ this.relationshipEngine.initialize(schemaInfo.relationships);
173
+ }
174
+ catch (error) {
175
+ this.logger.warn('Failed to initialize relationship engine:', error);
176
+ }
177
+ // Initialize metrics collector for development mode
178
+ this.metricsCollector = new MetricsCollector({
179
+ enabled: process.env.NODE_ENV === 'development',
129
180
  slowQueryThreshold: 1000,
130
- missingIndexDetection: true,
131
- largeResultSetThreshold: 1000
132
- });
181
+ nPlusOneDetection: true,
182
+ missingIndexDetection: true
183
+ }, this.logger);
133
184
  // Initialize SQLite-specific auto-optimization features
134
185
  if (this.config.dialect === 'sqlite') {
135
186
  this.sqliteAutoOptimizer = SQLiteAutoOptimizer.getInstance(this.logger);
136
187
  this.sqliteAutoIndexer = SQLiteAutoIndexer.getInstance(this.logger);
137
188
  // Apply automatic optimizations if enabled (default: true)
138
- const enableAutoOptimization = this.config.performance?.enableAutoOptimization !== false;
189
+ const enableAutoOptimization = this.config.automation?.enableAutoOptimization !== false;
139
190
  if (enableAutoOptimization) {
140
191
  await this.applySQLiteAutoOptimizations();
141
192
  }
@@ -207,8 +258,8 @@ export class NOORMME {
207
258
  if (this.config.dialect === 'sqlite' && this.sqliteAutoIndexer) {
208
259
  this.sqliteAutoIndexer.recordQuery(query, executionTime, table);
209
260
  }
210
- if (this.queryAnalyzer) {
211
- this.queryAnalyzer.recordQuery(query, executionTime, undefined, table);
261
+ if (this.metricsCollector) {
262
+ this.metricsCollector.recordQuery(query, executionTime, { table });
212
263
  }
213
264
  }
214
265
  /**
@@ -245,7 +296,8 @@ export class NOORMME {
245
296
  }
246
297
  const table = schemaInfo.tables.find(t => t.name === tableName);
247
298
  if (!table) {
248
- throw new Error(`Table '${tableName}' not found in schema. Available tables: ${schemaInfo.tables.map(t => t.name).join(', ')}`);
299
+ const availableTables = schemaInfo.tables.map(t => t.name);
300
+ throw new TableNotFoundError(tableName, availableTables);
249
301
  }
250
302
  const repository = this.repositoryFactory.createRepository(table, schemaInfo.relationships);
251
303
  this.repositories.set(tableName, repository);
@@ -299,28 +351,34 @@ export class NOORMME {
299
351
  /**
300
352
  * Start monitoring schema changes in development mode
301
353
  */
302
- startSchemaWatching(options) {
354
+ async startSchemaWatching(options) {
303
355
  if (!this.initialized) {
304
356
  throw new NoormError('NOORMME must be initialized before starting schema watching');
305
357
  }
306
- if (!this.schemaWatcher) {
307
- this.schemaWatcher = new SchemaWatcher(this.db, this.schemaDiscovery, this.logger, options);
308
- // Auto-refresh schema when changes detected
309
- this.schemaWatcher.onSchemaChange(async (changes) => {
310
- this.logger.info(`Schema changes detected: ${changes.length} changes`);
311
- changes.forEach(change => {
312
- this.logger.info(` - ${change.type}: ${change.table}`);
313
- });
314
- try {
315
- await this.refreshSchema();
316
- this.logger.info('Schema refreshed successfully');
317
- }
318
- catch (error) {
319
- this.logger.error('Failed to refresh schema:', error);
320
- }
321
- });
358
+ // If watcher already exists (e.g., from onSchemaChange), recreate it with new options
359
+ if (this.schemaWatcher) {
360
+ this.schemaWatcher.stopWatching();
361
+ }
362
+ this.schemaWatcher = new SchemaWatcher(this.db, this.schemaDiscovery, this.logger, options);
363
+ // Register all previously registered callbacks
364
+ for (const callback of this.schemaChangeCallbacks) {
365
+ this.schemaWatcher.onSchemaChange(callback);
322
366
  }
323
- this.schemaWatcher.startWatching();
367
+ // Auto-refresh schema when changes detected
368
+ this.schemaWatcher.onSchemaChange(async (changes) => {
369
+ this.logger.info(`Schema changes detected: ${changes.length} changes`);
370
+ changes.forEach(change => {
371
+ this.logger.info(` - ${change.type}: ${change.table}`);
372
+ });
373
+ try {
374
+ await this.refreshSchema();
375
+ this.logger.info('Schema refreshed successfully');
376
+ }
377
+ catch (error) {
378
+ this.logger.error('Failed to refresh schema:', error);
379
+ }
380
+ });
381
+ await this.schemaWatcher.startWatching();
324
382
  }
325
383
  /**
326
384
  * Stop monitoring schema changes
@@ -328,16 +386,19 @@ export class NOORMME {
328
386
  stopSchemaWatching() {
329
387
  if (this.schemaWatcher) {
330
388
  this.schemaWatcher.stopWatching();
389
+ // Don't set to null - keep watcher instance for potential restart
331
390
  }
332
391
  }
333
392
  /**
334
393
  * Register callback for schema changes
335
394
  */
336
395
  onSchemaChange(callback) {
337
- if (!this.schemaWatcher) {
338
- this.schemaWatcher = new SchemaWatcher(this.db, this.schemaDiscovery, this.logger);
396
+ // Store callback so it can be re-registered if watcher is recreated
397
+ this.schemaChangeCallbacks.push(callback);
398
+ // If watcher already exists, register callback immediately
399
+ if (this.schemaWatcher) {
400
+ this.schemaWatcher.onSchemaChange(callback);
339
401
  }
340
- this.schemaWatcher.onSchemaChange(callback);
341
402
  }
342
403
  /**
343
404
  * Get performance metrics
@@ -349,10 +410,10 @@ export class NOORMME {
349
410
  cacheHitRate: this.cacheManager.getHitRate(),
350
411
  repositoryCount: this.repositories.size
351
412
  };
352
- if (this.queryAnalyzer) {
413
+ if (this.metricsCollector) {
353
414
  return {
354
415
  ...baseMetrics,
355
- ...this.queryAnalyzer.getPerformanceStats()
416
+ ...this.metricsCollector.getPerformanceStats()
356
417
  };
357
418
  }
358
419
  return baseMetrics;
@@ -368,16 +429,21 @@ export class NOORMME {
368
429
  if (!schemaInfo) {
369
430
  throw new NoormError('Schema not found. Please reinitialize NOORMME.');
370
431
  }
371
- this.queryAnalyzer = new QueryAnalyzer(this.db, this.logger, schemaInfo, options);
432
+ this.metricsCollector = new MetricsCollector({
433
+ enabled: true,
434
+ slowQueryThreshold: options?.slowQueryThreshold || 1000,
435
+ nPlusOneDetection: true,
436
+ missingIndexDetection: true
437
+ }, this.logger);
372
438
  this.logger.info('Query performance monitoring enabled');
373
439
  }
374
440
  /**
375
441
  * Disable query performance monitoring
376
442
  */
377
443
  disablePerformanceMonitoring() {
378
- if (this.queryAnalyzer) {
379
- this.queryAnalyzer.clearHistory();
380
- this.queryAnalyzer = null;
444
+ if (this.metricsCollector) {
445
+ this.metricsCollector.clear();
446
+ this.metricsCollector = null;
381
447
  this.logger.info('Query performance monitoring disabled');
382
448
  }
383
449
  }
@@ -394,6 +460,12 @@ export class NOORMME {
394
460
  this.repositories.clear();
395
461
  this.logger.info('NOORMME closed');
396
462
  }
463
+ /**
464
+ * Alias for close() - for backward compatibility
465
+ */
466
+ async destroy() {
467
+ return this.close();
468
+ }
397
469
  /**
398
470
  * Get the underlying Kysely instance for custom queries
399
471
  */
@@ -409,11 +481,9 @@ export class NOORMME {
409
481
  /**
410
482
  * Execute raw SQL
411
483
  */
412
- async execute(sql, parameters) {
413
- return await this.db.executeQuery({
414
- sql,
415
- parameters: parameters || []
416
- });
484
+ async execute(sqlString, parameters) {
485
+ const compiledQuery = CompiledQuery.raw(sqlString, parameters || []);
486
+ return await this.db.executeQuery(compiledQuery);
417
487
  }
418
488
  mergeConfig(config) {
419
489
  return {
@@ -505,8 +575,7 @@ export class NOORMME {
505
575
  const { dialect, connection } = this.config;
506
576
  switch (dialect) {
507
577
  case 'sqlite':
508
- const Database = require('better-sqlite3');
509
- return new (require('./dialect/sqlite/sqlite-dialect').SqliteDialect)({
578
+ return new SqliteDialect({
510
579
  database: new Database(connection.database)
511
580
  });
512
581
  default:
@@ -1,6 +1,7 @@
1
1
  /// <reference types="./column-node.d.ts" />
2
2
  import { freeze } from '../util/object-utils.js';
3
3
  import { IdentifierNode } from './identifier-node.js';
4
+ import { validateIdentifier } from '../util/security-validator.js';
4
5
  /**
5
6
  * @internal
6
7
  */
@@ -9,6 +10,9 @@ export const ColumnNode = freeze({
9
10
  return node.kind === 'ColumnNode';
10
11
  },
11
12
  create(column) {
13
+ // SECURITY: Validate column name to prevent SQL injection
14
+ // Even though IdentifierNode will validate, we validate here too for defense in depth
15
+ validateIdentifier(column, 'column name');
12
16
  return freeze({
13
17
  kind: 'ColumnNode',
14
18
  column: IdentifierNode.create(column),
@@ -1,5 +1,6 @@
1
1
  /// <reference types="./identifier-node.d.ts" />
2
2
  import { freeze } from '../util/object-utils.js';
3
+ import { validateIdentifier } from '../util/security-validator.js';
3
4
  /**
4
5
  * @internal
5
6
  */
@@ -8,6 +9,9 @@ export const IdentifierNode = freeze({
8
9
  return node.kind === 'IdentifierNode';
9
10
  },
10
11
  create(name) {
12
+ // SECURITY: Validate identifier to prevent SQL injection at the lowest level
13
+ // This ensures ALL identifiers are validated, even when parsers are called directly
14
+ validateIdentifier(name, 'identifier');
11
15
  return freeze({
12
16
  kind: 'IdentifierNode',
13
17
  name,
@@ -1,6 +1,7 @@
1
1
  /// <reference types="./table-node.d.ts" />
2
2
  import { freeze } from '../util/object-utils.js';
3
3
  import { SchemableIdentifierNode } from './schemable-identifier-node.js';
4
+ import { validateIdentifier } from '../util/security-validator.js';
4
5
  /**
5
6
  * @internal
6
7
  */
@@ -9,12 +10,18 @@ export const TableNode = freeze({
9
10
  return node.kind === 'TableNode';
10
11
  },
11
12
  create(table) {
13
+ // SECURITY: Validate table name to prevent SQL injection
14
+ // Even though SchemableIdentifierNode will validate, we validate here too for defense in depth
15
+ validateIdentifier(table, 'table name');
12
16
  return freeze({
13
17
  kind: 'TableNode',
14
18
  table: SchemableIdentifierNode.create(table),
15
19
  });
16
20
  },
17
21
  createWithSchema(schema, table) {
22
+ // SECURITY: Validate both schema and table names
23
+ validateIdentifier(schema, 'schema name');
24
+ validateIdentifier(table, 'table name');
18
25
  return freeze({
19
26
  kind: 'TableNode',
20
27
  table: SchemableIdentifierNode.createWithSchema(schema, table),
@@ -45,13 +45,16 @@ export function parseJSONReference(ref, op) {
45
45
  export function parseStringReference(ref) {
46
46
  const COLUMN_SEPARATOR = '.';
47
47
  if (!ref.includes(COLUMN_SEPARATOR)) {
48
+ // SECURITY: ColumnNode.create will validate the column name
48
49
  return ReferenceNode.create(ColumnNode.create(ref));
49
50
  }
50
51
  const parts = ref.split(COLUMN_SEPARATOR).map(trim);
51
52
  if (parts.length === 3) {
53
+ // SECURITY: parseStringReferenceWithTableAndSchema uses ColumnNode and TableNode which validate
52
54
  return parseStringReferenceWithTableAndSchema(parts);
53
55
  }
54
56
  if (parts.length === 2) {
57
+ // SECURITY: parseStringReferenceWithTable uses ColumnNode and TableNode which validate
55
58
  return parseStringReferenceWithTable(parts);
56
59
  }
57
60
  throw new Error(`invalid column reference ${ref}`);
@@ -84,10 +87,12 @@ export function parseOrderedColumnName(column) {
84
87
  }
85
88
  function parseStringReferenceWithTableAndSchema(parts) {
86
89
  const [schema, table, column] = parts;
90
+ // SECURITY: Both ColumnNode.create and TableNode.createWithSchema validate their inputs
87
91
  return ReferenceNode.create(ColumnNode.create(column), TableNode.createWithSchema(schema, table));
88
92
  }
89
93
  function parseStringReferenceWithTable(parts) {
90
94
  const [table, column] = parts;
95
+ // SECURITY: Both ColumnNode.create and TableNode.create validate their inputs
91
96
  return ReferenceNode.create(ColumnNode.create(column), TableNode.create(table));
92
97
  }
93
98
  function trim(str) {
@@ -38,9 +38,11 @@ export function parseTable(from) {
38
38
  const SCHEMA_SEPARATOR = '.';
39
39
  if (from.includes(SCHEMA_SEPARATOR)) {
40
40
  const [schema, table] = from.split(SCHEMA_SEPARATOR).map(trim);
41
+ // SECURITY: TableNode.createWithSchema will validate both parts
41
42
  return TableNode.createWithSchema(schema, table);
42
43
  }
43
44
  else {
45
+ // SECURITY: TableNode.create will validate the table name
44
46
  return TableNode.create(from);
45
47
  }
46
48
  }
@@ -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,48 @@
1
+ /// <reference types="./index.d.ts" />
2
+ /**
3
+ * Performance module exports
4
+ * Clean, focused services, utilities, and classes
5
+ */
6
+ // Utilities
7
+ export * from './utils/query-parser';
8
+ // Services
9
+ export * from './services/cache-service';
10
+ export * from './services/metrics-collector';
11
+ export * from './services/connection-factory';
12
+ // Main optimizer
13
+ export * from './query-optimizer';
14
+ /**
15
+ * Default performance configuration
16
+ */
17
+ export const defaultPerformanceConfig = {
18
+ cache: {
19
+ maxSize: 1000,
20
+ defaultTtl: 300000, // 5 minutes
21
+ cleanupInterval: 60000, // 1 minute
22
+ enableMetrics: true
23
+ },
24
+ metrics: {
25
+ enabled: true,
26
+ slowQueryThreshold: 1000,
27
+ nPlusOneDetection: true,
28
+ missingIndexDetection: true,
29
+ largeResultSetThreshold: 1000,
30
+ maxHistorySize: 10000,
31
+ warningRetentionDays: 7
32
+ },
33
+ connectionPool: {
34
+ minConnections: 2,
35
+ maxConnections: 10,
36
+ acquireTimeout: 30000,
37
+ idleTimeout: 300000,
38
+ validationInterval: 60000
39
+ },
40
+ optimizer: {
41
+ enableQueryCache: true,
42
+ enableIndexRecommendations: true,
43
+ enableQueryRewriting: true,
44
+ slowQueryThreshold: 1000,
45
+ cacheSize: 1000,
46
+ maxCacheAge: 300000
47
+ }
48
+ };