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
@@ -2,19 +2,22 @@
2
2
  * Core type definitions for NOORMME
3
3
  */
4
4
  export interface NOORMConfig {
5
- dialect: 'sqlite';
5
+ dialect: 'sqlite' | 'postgresql' | 'mysql';
6
6
  connection: ConnectionConfig;
7
7
  introspection?: IntrospectionConfig;
8
8
  cache?: CacheConfig;
9
9
  logging?: LoggingConfig;
10
10
  performance?: PerformanceConfig;
11
+ automation?: AutomationConfig;
12
+ optimization?: OptimizationConfig;
13
+ sqlite?: SQLiteConfig;
11
14
  }
12
15
  export interface ConnectionConfig {
13
- host: string;
14
- port: number;
16
+ host?: string;
17
+ port?: number;
15
18
  database: string;
16
- username: string;
17
- password: string;
19
+ username?: string;
20
+ password?: string;
18
21
  ssl?: boolean | object;
19
22
  pool?: PoolConfig;
20
23
  }
@@ -42,6 +45,36 @@ export interface PerformanceConfig {
42
45
  enableQueryOptimization?: boolean;
43
46
  enableBatchLoading?: boolean;
44
47
  maxBatchSize?: number;
48
+ enableCaching?: boolean;
49
+ maxCacheSize?: number;
50
+ enableBatchOperations?: boolean;
51
+ slowQueryThreshold?: number;
52
+ }
53
+ export interface AutomationConfig {
54
+ enableAutoOptimization?: boolean;
55
+ enableIndexRecommendations?: boolean;
56
+ enableQueryAnalysis?: boolean;
57
+ enableMigrationGeneration?: boolean;
58
+ enablePerformanceMonitoring?: boolean;
59
+ enableSchemaWatcher?: boolean;
60
+ }
61
+ export interface OptimizationConfig {
62
+ enableWALMode?: boolean;
63
+ enableForeignKeys?: boolean;
64
+ cacheSize?: number;
65
+ synchronous?: 'OFF' | 'NORMAL' | 'FULL' | 'EXTRA';
66
+ tempStore?: 'DEFAULT' | 'FILE' | 'MEMORY';
67
+ autoVacuumMode?: 'NONE' | 'FULL' | 'INCREMENTAL';
68
+ journalMode?: 'DELETE' | 'TRUNCATE' | 'PERSIST' | 'MEMORY' | 'WAL' | 'OFF';
69
+ }
70
+ export interface SQLiteConfig {
71
+ enableWALMode?: boolean;
72
+ enableForeignKeys?: boolean;
73
+ cacheSize?: number;
74
+ synchronous?: 'OFF' | 'NORMAL' | 'FULL' | 'EXTRA';
75
+ tempStore?: 'DEFAULT' | 'FILE' | 'MEMORY';
76
+ autoVacuumMode?: 'NONE' | 'FULL' | 'INCREMENTAL';
77
+ journalMode?: 'DELETE' | 'TRUNCATE' | 'PERSIST' | 'MEMORY' | 'WAL' | 'OFF';
45
78
  }
46
79
  export interface SchemaInfo {
47
80
  tables: TableInfo[];
@@ -60,7 +93,7 @@ export interface ColumnInfo {
60
93
  name: string;
61
94
  type: string;
62
95
  nullable: boolean;
63
- defaultValue?: any;
96
+ defaultValue?: unknown;
64
97
  isPrimaryKey: boolean;
65
98
  isAutoIncrement: boolean;
66
99
  maxLength?: number;
@@ -111,15 +144,16 @@ export interface EntityType {
111
144
  selectType: string;
112
145
  }
113
146
  export interface Repository<T> {
114
- findById(id: any): Promise<T | null>;
147
+ objects: any;
148
+ findById(id: string | number): Promise<T | null>;
115
149
  findAll(): Promise<T[]>;
116
150
  create(data: Partial<T>): Promise<T>;
117
151
  update(entity: T): Promise<T>;
118
- delete(id: any): Promise<boolean>;
119
- findWithRelations(id: any, relations: string[]): Promise<T | null>;
152
+ delete(id: string | number): Promise<boolean>;
153
+ findWithRelations(id: string | number, relations: string[]): Promise<T | null>;
120
154
  loadRelationships(entities: T[], relations: string[]): Promise<void>;
121
155
  count(): Promise<number>;
122
- exists(id: any): Promise<boolean>;
156
+ exists(id: string | number): Promise<boolean>;
123
157
  paginate(options: {
124
158
  page: number;
125
159
  limit: number;
@@ -139,17 +173,48 @@ export interface Repository<T> {
139
173
  hasPrev: boolean;
140
174
  };
141
175
  }>;
142
- withCount(id: any, relationships: string[]): Promise<T & Record<string, number>>;
143
- [key: string]: any;
176
+ withCount(id: string | number, relationships: string[]): Promise<T & Record<string, number>>;
177
+ [key: string]: unknown;
144
178
  }
145
179
  export interface SchemaChange {
146
180
  type: 'table_added' | 'table_removed' | 'column_added' | 'column_removed' | 'column_modified';
147
181
  table: string;
148
182
  column?: string;
149
- details?: any;
183
+ details?: unknown;
150
184
  }
151
185
  export interface RefreshResult {
152
186
  schemaInfo: SchemaInfo;
153
187
  changes: SchemaChange[];
154
188
  typesRegenerated: boolean;
155
189
  }
190
+ export interface ConnectionPoolConfig {
191
+ min?: number;
192
+ max?: number;
193
+ idleTimeout?: number;
194
+ acquireTimeout?: number;
195
+ }
196
+ export interface QueryCacheConfig {
197
+ enabled: boolean;
198
+ ttl: number;
199
+ maxSize: number;
200
+ }
201
+ export interface BatchConfig {
202
+ maxBatchSize: number;
203
+ batchTimeout: number;
204
+ }
205
+ export interface OptimizationRecommendation {
206
+ type: 'index' | 'query' | 'schema' | 'performance';
207
+ priority: 'low' | 'medium' | 'high';
208
+ description: string;
209
+ suggestion: string;
210
+ estimatedImpact: string;
211
+ }
212
+ export interface BaseRepository<T> {
213
+ findAll(): Promise<T[]>;
214
+ findById(id: string | number): Promise<T | null>;
215
+ create(data: Partial<T>): Promise<T>;
216
+ update(id: string | number, data: Partial<T>): Promise<T>;
217
+ delete(id: string | number): Promise<boolean>;
218
+ count(): Promise<number>;
219
+ }
220
+ export declare function validateNOORMConfig(config: NOORMConfig): void;
@@ -2,4 +2,25 @@
2
2
  /**
3
3
  * Core type definitions for NOORMME
4
4
  */
5
- export {};
5
+ // Configuration validation function type
6
+ export function validateNOORMConfig(config) {
7
+ if (!config.dialect) {
8
+ throw new Error('Dialect is required');
9
+ }
10
+ if (!config.connection?.database) {
11
+ throw new Error('Database path is required');
12
+ }
13
+ // Validate dialect-specific requirements
14
+ if (config.dialect === 'sqlite') {
15
+ if (!config.connection.database.endsWith('.db') && !config.connection.database.endsWith('.sqlite')) {
16
+ console.warn('SQLite database path should typically end with .db or .sqlite');
17
+ }
18
+ }
19
+ // Validate performance settings
20
+ if (config.performance?.maxBatchSize && config.performance.maxBatchSize <= 0) {
21
+ throw new Error('maxBatchSize must be greater than 0');
22
+ }
23
+ if (config.performance?.maxCacheSize && config.performance.maxCacheSize <= 0) {
24
+ throw new Error('maxCacheSize must be greater than 0');
25
+ }
26
+ }
@@ -41,6 +41,14 @@ export declare class TypeGenerator {
41
41
  * Get relationships for a specific table
42
42
  */
43
43
  private getRelationshipsForTable;
44
+ /**
45
+ * Generate relationship interfaces
46
+ */
47
+ private generateRelationshipInterfaces;
48
+ /**
49
+ * Convert string to camelCase
50
+ */
51
+ private toCamelCase;
44
52
  /**
45
53
  * Get relationship type name
46
54
  */
@@ -16,7 +16,7 @@ export class TypeGenerator {
16
16
  let types = '';
17
17
  // Generate entity types for each table
18
18
  for (const table of schemaInfo.tables) {
19
- const entity = this.generateEntityType(table);
19
+ const entity = this.generateEntityType(table, schemaInfo.relationships);
20
20
  entities.push(entity);
21
21
  interfaces += entity.interface + '\n\n';
22
22
  types += entity.insertType + '\n\n';
@@ -34,11 +34,11 @@ export class TypeGenerator {
34
34
  /**
35
35
  * Generate entity type for a table
36
36
  */
37
- generateEntityType(table) {
37
+ generateEntityType(table, relationships) {
38
38
  const entityName = this.toPascalCase(table.name);
39
39
  const tableName = table.name;
40
40
  // Generate main entity interface
41
- const interfaceCode = this.generateEntityInterface(table, entityName);
41
+ const interfaceCode = this.generateEntityInterface(table, entityName, relationships);
42
42
  // Generate insert type (all columns except auto-generated ones)
43
43
  const insertType = this.generateInsertType(table, entityName);
44
44
  // Generate update type (all columns optional except primary key)
@@ -57,7 +57,7 @@ export class TypeGenerator {
57
57
  /**
58
58
  * Generate main entity interface
59
59
  */
60
- generateEntityInterface(table, entityName) {
60
+ generateEntityInterface(table, entityName, relationships) {
61
61
  let interfaceCode = `export interface ${entityName} {\n`;
62
62
  // Add primary key columns first
63
63
  for (const column of table.columns) {
@@ -76,8 +76,8 @@ export class TypeGenerator {
76
76
  }
77
77
  }
78
78
  // Add relationship properties
79
- const relationships = this.getRelationshipsForTable(table.name);
80
- for (const rel of relationships) {
79
+ const tableRelationships = this.getRelationshipsForTable(table.name, relationships);
80
+ for (const rel of tableRelationships) {
81
81
  const relType = this.getRelationshipType(rel);
82
82
  interfaceCode += ` ${rel.name}?: ${relType}\n`;
83
83
  }
@@ -187,8 +187,8 @@ export class TypeGenerator {
187
187
  'timestamp': 'Date',
188
188
  'timestamptz': 'Date',
189
189
  'time': 'Date',
190
- 'json': 'any',
191
- 'jsonb': 'any',
190
+ 'json': 'Record<string, unknown>',
191
+ 'jsonb': 'Record<string, unknown>',
192
192
  'uuid': 'string',
193
193
  // MySQL specific types
194
194
  'longtext': 'string',
@@ -200,8 +200,11 @@ export class TypeGenerator {
200
200
  'double': 'number',
201
201
  'bool': 'boolean',
202
202
  'datetime': 'Date',
203
- // SQLite specific types
203
+ // SQLite specific types (enhanced)
204
204
  'blob': 'Buffer',
205
+ 'int2': 'number',
206
+ 'int8': 'number',
207
+ 'clob': 'string',
205
208
  // MSSQL specific types
206
209
  'nvarchar': 'string',
207
210
  'nchar': 'string',
@@ -212,23 +215,89 @@ export class TypeGenerator {
212
215
  };
213
216
  // Try exact match first
214
217
  if (typeMapping[column.type.toLowerCase()]) {
215
- return typeMapping[column.type.toLowerCase()];
218
+ let mappedType = typeMapping[column.type.toLowerCase()];
219
+ // Handle nullable columns
220
+ if (column.nullable && mappedType !== 'unknown') {
221
+ mappedType = `${mappedType} | null`;
222
+ }
223
+ return mappedType;
216
224
  }
217
225
  // Handle parameterized types (e.g., varchar(255), decimal(10,2))
218
226
  const baseType = column.type.toLowerCase().split('(')[0];
219
227
  if (typeMapping[baseType]) {
220
- return typeMapping[baseType];
228
+ let mappedType = typeMapping[baseType];
229
+ // Handle nullable columns
230
+ if (column.nullable && mappedType !== 'unknown') {
231
+ mappedType = `${mappedType} | null`;
232
+ }
233
+ return mappedType;
221
234
  }
222
- // Default to any for unknown types
223
- return 'any';
235
+ // Default to unknown for unknown types
236
+ return column.nullable ? 'unknown | null' : 'unknown';
224
237
  }
225
238
  /**
226
239
  * Get relationships for a specific table
227
240
  */
228
- getRelationshipsForTable(tableName) {
229
- // This would be passed from the schema discovery
230
- // For now, return empty array
231
- return [];
241
+ getRelationshipsForTable(tableName, relationships) {
242
+ return relationships.filter(rel => rel.fromTable === tableName || rel.toTable === tableName);
243
+ }
244
+ /**
245
+ * Generate relationship interfaces
246
+ */
247
+ generateRelationshipInterfaces(relationships) {
248
+ if (relationships.length === 0) {
249
+ return '';
250
+ }
251
+ let relationshipInterfaces = '// Relationship Types\n\n';
252
+ // Group relationships by type
253
+ const groupedRelationships = relationships.reduce((acc, rel) => {
254
+ const key = `${rel.fromTable}_${rel.toTable}_${rel.type}`;
255
+ if (!acc[key]) {
256
+ acc[key] = [];
257
+ }
258
+ acc[key].push(rel);
259
+ return acc;
260
+ }, {});
261
+ for (const [key, rels] of Object.entries(groupedRelationships)) {
262
+ const rel = rels[0]; // Use first relationship as template
263
+ const fromEntity = this.toPascalCase(rel.fromTable);
264
+ const toEntity = this.toPascalCase(rel.toTable);
265
+ switch (rel.type) {
266
+ case 'one-to-many':
267
+ relationshipInterfaces += `export interface ${fromEntity}With${toEntity}s extends ${fromEntity} {\n`;
268
+ relationshipInterfaces += ` ${this.toCamelCase(rel.toTable)}s: ${toEntity}[]\n`;
269
+ relationshipInterfaces += `}\n\n`;
270
+ relationshipInterfaces += `export interface ${toEntity}With${fromEntity} extends ${toEntity} {\n`;
271
+ relationshipInterfaces += ` ${this.toCamelCase(rel.fromTable)}: ${fromEntity}\n`;
272
+ relationshipInterfaces += `}\n\n`;
273
+ break;
274
+ case 'many-to-one':
275
+ relationshipInterfaces += `export interface ${fromEntity}With${toEntity} extends ${fromEntity} {\n`;
276
+ relationshipInterfaces += ` ${this.toCamelCase(rel.toTable)}: ${toEntity}\n`;
277
+ relationshipInterfaces += `}\n\n`;
278
+ relationshipInterfaces += `export interface ${toEntity}With${fromEntity}s extends ${toEntity} {\n`;
279
+ relationshipInterfaces += ` ${this.toCamelCase(rel.fromTable)}s: ${fromEntity}[]\n`;
280
+ relationshipInterfaces += `}\n\n`;
281
+ break;
282
+ case 'many-to-many':
283
+ relationshipInterfaces += `export interface ${fromEntity}With${toEntity}s extends ${fromEntity} {\n`;
284
+ relationshipInterfaces += ` ${this.toCamelCase(rel.toTable)}s: ${toEntity}[]\n`;
285
+ relationshipInterfaces += `}\n\n`;
286
+ relationshipInterfaces += `export interface ${toEntity}With${fromEntity}s extends ${toEntity} {\n`;
287
+ relationshipInterfaces += ` ${this.toCamelCase(rel.fromTable)}s: ${fromEntity}[]\n`;
288
+ relationshipInterfaces += `}\n\n`;
289
+ break;
290
+ }
291
+ }
292
+ return relationshipInterfaces;
293
+ }
294
+ /**
295
+ * Convert string to camelCase
296
+ */
297
+ toCamelCase(str) {
298
+ return str.replace(/([-_][a-z])/gi, ($1) => {
299
+ return $1.toUpperCase().replace('-', '').replace('_', '');
300
+ });
232
301
  }
233
302
  /**
234
303
  * Get relationship type name
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Safe SQL helper utilities
3
+ * Provides secure alternatives to dangerous sql.raw() and sql.lit() methods
4
+ */
5
+ import { RawBuilder } from '../raw-builder/raw-builder.js';
6
+ /**
7
+ * Safely creates a SQL ORDER BY direction clause
8
+ * Only allows 'ASC' or 'DESC'
9
+ */
10
+ export declare function safeOrderDirection(direction: string): RawBuilder<unknown>;
11
+ /**
12
+ * Safely creates a SQL LIMIT clause
13
+ * Validates that the limit is a positive integer
14
+ */
15
+ export declare function safeLimit(limit: number | string): RawBuilder<unknown>;
16
+ /**
17
+ * Safely creates a SQL OFFSET clause
18
+ * Validates that the offset is a non-negative integer
19
+ */
20
+ export declare function safeOffset(offset: number | string): RawBuilder<unknown>;
21
+ /**
22
+ * Safely creates SQL keywords from a whitelist
23
+ * Useful for database-specific syntax that must be hardcoded
24
+ */
25
+ export declare function safeKeyword(keyword: string, allowedKeywords: string[]): RawBuilder<unknown>;
26
+ /**
27
+ * Type-safe enum for common SQL keywords
28
+ */
29
+ export declare const SafeSQLKeywords: {
30
+ /**
31
+ * Lock modes for SELECT statements
32
+ */
33
+ readonly LockMode: {
34
+ readonly FOR_UPDATE: "FOR UPDATE";
35
+ readonly FOR_SHARE: "FOR SHARE";
36
+ readonly FOR_NO_KEY_UPDATE: "FOR NO KEY UPDATE";
37
+ readonly FOR_KEY_SHARE: "FOR KEY SHARE";
38
+ };
39
+ /**
40
+ * Join types
41
+ */
42
+ readonly JoinType: {
43
+ readonly INNER: "INNER";
44
+ readonly LEFT: "LEFT";
45
+ readonly RIGHT: "RIGHT";
46
+ readonly FULL: "FULL";
47
+ readonly CROSS: "CROSS";
48
+ };
49
+ /**
50
+ * Set operations
51
+ */
52
+ readonly SetOperation: {
53
+ readonly UNION: "UNION";
54
+ readonly UNION_ALL: "UNION ALL";
55
+ readonly INTERSECT: "INTERSECT";
56
+ readonly EXCEPT: "EXCEPT";
57
+ };
58
+ /**
59
+ * SQLite-specific pragmas (use with extreme caution)
60
+ */
61
+ readonly SafePragma: {
62
+ readonly OPTIMIZE: "OPTIMIZE";
63
+ readonly WAL_CHECKPOINT: "WAL_CHECKPOINT";
64
+ };
65
+ };
66
+ /**
67
+ * Safely creates a lock mode clause for SELECT statements
68
+ */
69
+ export declare function safeLockMode(mode: keyof typeof SafeSQLKeywords.LockMode): RawBuilder<unknown>;
70
+ /**
71
+ * Type-safe ORDER BY builder
72
+ * Validates column names and direction
73
+ */
74
+ export interface SafeOrderBy {
75
+ column: string;
76
+ direction?: 'ASC' | 'DESC';
77
+ }
78
+ /**
79
+ * Creates a safe ORDER BY clause from validated columns
80
+ */
81
+ export declare function safeOrderBy(orderBy: SafeOrderBy[], allowedColumns: string[]): RawBuilder<unknown>;
82
+ /**
83
+ * Validates and creates a safe boolean value for SQL
84
+ */
85
+ export declare function safeBoolean(value: boolean | string | number): RawBuilder<boolean>;
86
+ /**
87
+ * Creates a safe CASE statement with validated conditions
88
+ */
89
+ export interface SafeCaseWhen<T> {
90
+ condition: RawBuilder<boolean>;
91
+ result: T;
92
+ }
93
+ export declare function safeCaseStatement<T>(cases: SafeCaseWhen<T>[], elseResult: T): RawBuilder<T>;
94
+ /**
95
+ * Example usage and best practices
96
+ */
97
+ export declare const SafeSQLExamples: {
98
+ /**
99
+ * Example: Safe pagination with user input
100
+ */
101
+ safePagination: () => {
102
+ limit: RawBuilder<unknown>;
103
+ offset: RawBuilder<unknown>;
104
+ };
105
+ /**
106
+ * Example: Safe sorting with user input
107
+ */
108
+ safeSorting: (userSortColumn: string, userDirection: string) => {
109
+ column: RawBuilder<unknown>;
110
+ direction: RawBuilder<unknown>;
111
+ };
112
+ /**
113
+ * Example: Safe lock mode for transactions
114
+ */
115
+ safeTransactionLock: () => RawBuilder<unknown>;
116
+ };
117
+ /**
118
+ * Helper to validate that a value is from a specific enum
119
+ */
120
+ export declare function validateEnum<T extends string>(value: string, enumValues: readonly T[], fieldName?: string): T;
121
+ /**
122
+ * Helper to validate numeric ranges
123
+ */
124
+ export declare function validateNumericRange(value: number | string, min: number, max: number, fieldName?: string): number;
@@ -0,0 +1,209 @@
1
+ /// <reference types="./safe-sql-helpers.d.ts" />
2
+ /**
3
+ * Safe SQL helper utilities
4
+ * Provides secure alternatives to dangerous sql.raw() and sql.lit() methods
5
+ */
6
+ import { sql } from '../raw-builder/sql.js';
7
+ /**
8
+ * Safely creates a SQL ORDER BY direction clause
9
+ * Only allows 'ASC' or 'DESC'
10
+ */
11
+ export function safeOrderDirection(direction) {
12
+ const normalized = direction.toUpperCase().trim();
13
+ if (normalized !== 'ASC' && normalized !== 'DESC') {
14
+ throw new Error(`Invalid order direction: "${direction}". Only "ASC" and "DESC" are allowed.`);
15
+ }
16
+ return sql.raw(normalized);
17
+ }
18
+ /**
19
+ * Safely creates a SQL LIMIT clause
20
+ * Validates that the limit is a positive integer
21
+ */
22
+ export function safeLimit(limit) {
23
+ const numLimit = typeof limit === 'string' ? parseInt(limit, 10) : limit;
24
+ if (!Number.isInteger(numLimit) || numLimit <= 0 || numLimit > 10000) {
25
+ throw new Error(`Invalid limit: "${limit}". Must be a positive integer between 1 and 10000.`);
26
+ }
27
+ return sql.raw(numLimit.toString());
28
+ }
29
+ /**
30
+ * Safely creates a SQL OFFSET clause
31
+ * Validates that the offset is a non-negative integer
32
+ */
33
+ export function safeOffset(offset) {
34
+ const numOffset = typeof offset === 'string' ? parseInt(offset, 10) : offset;
35
+ if (!Number.isInteger(numOffset) || numOffset < 0 || numOffset > 1000000) {
36
+ throw new Error(`Invalid offset: "${offset}". Must be a non-negative integer between 0 and 1000000.`);
37
+ }
38
+ return sql.raw(numOffset.toString());
39
+ }
40
+ /**
41
+ * Safely creates SQL keywords from a whitelist
42
+ * Useful for database-specific syntax that must be hardcoded
43
+ */
44
+ export function safeKeyword(keyword, allowedKeywords) {
45
+ const normalized = keyword.toUpperCase().trim();
46
+ const allowedNormalized = allowedKeywords.map(k => k.toUpperCase().trim());
47
+ if (!allowedNormalized.includes(normalized)) {
48
+ throw new Error(`Invalid keyword: "${keyword}". Allowed keywords: ${allowedKeywords.join(', ')}`);
49
+ }
50
+ return sql.raw(normalized);
51
+ }
52
+ /**
53
+ * Type-safe enum for common SQL keywords
54
+ */
55
+ export const SafeSQLKeywords = {
56
+ /**
57
+ * Lock modes for SELECT statements
58
+ */
59
+ LockMode: {
60
+ FOR_UPDATE: 'FOR UPDATE',
61
+ FOR_SHARE: 'FOR SHARE',
62
+ FOR_NO_KEY_UPDATE: 'FOR NO KEY UPDATE',
63
+ FOR_KEY_SHARE: 'FOR KEY SHARE',
64
+ },
65
+ /**
66
+ * Join types
67
+ */
68
+ JoinType: {
69
+ INNER: 'INNER',
70
+ LEFT: 'LEFT',
71
+ RIGHT: 'RIGHT',
72
+ FULL: 'FULL',
73
+ CROSS: 'CROSS',
74
+ },
75
+ /**
76
+ * Set operations
77
+ */
78
+ SetOperation: {
79
+ UNION: 'UNION',
80
+ UNION_ALL: 'UNION ALL',
81
+ INTERSECT: 'INTERSECT',
82
+ EXCEPT: 'EXCEPT',
83
+ },
84
+ /**
85
+ * SQLite-specific pragmas (use with extreme caution)
86
+ */
87
+ SafePragma: {
88
+ OPTIMIZE: 'OPTIMIZE',
89
+ WAL_CHECKPOINT: 'WAL_CHECKPOINT',
90
+ },
91
+ };
92
+ /**
93
+ * Safely creates a lock mode clause for SELECT statements
94
+ */
95
+ export function safeLockMode(mode) {
96
+ return sql.raw(SafeSQLKeywords.LockMode[mode]);
97
+ }
98
+ /**
99
+ * Creates a safe ORDER BY clause from validated columns
100
+ */
101
+ export function safeOrderBy(orderBy, allowedColumns) {
102
+ if (orderBy.length === 0) {
103
+ throw new Error('At least one order by clause is required');
104
+ }
105
+ const clauses = orderBy.map(({ column, direction = 'ASC' }) => {
106
+ if (!allowedColumns.includes(column)) {
107
+ throw new Error(`Invalid order by column: "${column}". Allowed columns: ${allowedColumns.join(', ')}`);
108
+ }
109
+ const dir = direction.toUpperCase();
110
+ if (dir !== 'ASC' && dir !== 'DESC') {
111
+ throw new Error(`Invalid direction: "${direction}". Must be ASC or DESC`);
112
+ }
113
+ return `${column} ${dir}`;
114
+ });
115
+ return sql.raw(clauses.join(', '));
116
+ }
117
+ /**
118
+ * Validates and creates a safe boolean value for SQL
119
+ */
120
+ export function safeBoolean(value) {
121
+ let boolValue;
122
+ if (typeof value === 'boolean') {
123
+ boolValue = value;
124
+ }
125
+ else if (value === 'true' || value === '1' || value === 1) {
126
+ boolValue = true;
127
+ }
128
+ else if (value === 'false' || value === '0' || value === 0) {
129
+ boolValue = false;
130
+ }
131
+ else {
132
+ throw new Error(`Invalid boolean value: "${value}". Must be true, false, 1, 0, "true", or "false"`);
133
+ }
134
+ // Use parameterized value instead of raw SQL
135
+ return sql `${boolValue}`;
136
+ }
137
+ export function safeCaseStatement(cases, elseResult) {
138
+ if (cases.length === 0) {
139
+ throw new Error('At least one WHEN clause is required');
140
+ }
141
+ // Note: This function needs to be refactored to not use sql.raw()
142
+ // For now, we construct the CASE statement carefully
143
+ const whenClauses = cases
144
+ .map((c, i) => {
145
+ // Conditions must be RawBuilder instances to ensure they're safe
146
+ const conditionSql = c.condition.toOperationNode();
147
+ return `WHEN ${conditionSql} THEN ${c.result}`;
148
+ })
149
+ .join(' ');
150
+ return sql.raw(`CASE ${whenClauses} ELSE ${elseResult} END`);
151
+ }
152
+ /**
153
+ * Example usage and best practices
154
+ */
155
+ export const SafeSQLExamples = {
156
+ /**
157
+ * Example: Safe pagination with user input
158
+ */
159
+ safePagination: () => {
160
+ const userPage = '2'; // From request
161
+ const userLimit = '10'; // From request
162
+ const offset = (parseInt(userPage) - 1) * parseInt(userLimit);
163
+ return {
164
+ limit: safeLimit(userLimit),
165
+ offset: safeOffset(offset),
166
+ };
167
+ },
168
+ /**
169
+ * Example: Safe sorting with user input
170
+ */
171
+ safeSorting: (userSortColumn, userDirection) => {
172
+ const allowedColumns = ['name', 'email', 'created_at', 'updated_at'];
173
+ if (!allowedColumns.includes(userSortColumn)) {
174
+ throw new Error('Invalid sort column');
175
+ }
176
+ return {
177
+ column: sql.ref(userSortColumn),
178
+ direction: safeOrderDirection(userDirection),
179
+ };
180
+ },
181
+ /**
182
+ * Example: Safe lock mode for transactions
183
+ */
184
+ safeTransactionLock: () => {
185
+ return safeLockMode('FOR_UPDATE');
186
+ },
187
+ };
188
+ /**
189
+ * Helper to validate that a value is from a specific enum
190
+ */
191
+ export function validateEnum(value, enumValues, fieldName = 'value') {
192
+ if (!enumValues.includes(value)) {
193
+ throw new Error(`Invalid ${fieldName}: "${value}". Allowed values: ${enumValues.join(', ')}`);
194
+ }
195
+ return value;
196
+ }
197
+ /**
198
+ * Helper to validate numeric ranges
199
+ */
200
+ export function validateNumericRange(value, min, max, fieldName = 'value') {
201
+ const num = typeof value === 'string' ? parseFloat(value) : value;
202
+ if (isNaN(num)) {
203
+ throw new Error(`Invalid ${fieldName}: "${value}". Must be a number`);
204
+ }
205
+ if (num < min || num > max) {
206
+ throw new Error(`Invalid ${fieldName}: ${num}. Must be between ${min} and ${max}`);
207
+ }
208
+ return num;
209
+ }