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.
- package/LICENSE +89 -21
- package/README.md +195 -578
- package/dist/cjs/cli/commands/analyze.js +4 -1
- package/dist/cjs/cli/commands/generate.js +48 -8
- package/dist/cjs/cli/commands/init.js +54 -14
- package/dist/cjs/cli/commands/inspect.js +10 -3
- package/dist/cjs/cli/commands/migrate.js +38 -2
- package/dist/cjs/cli/commands/optimize.js +4 -1
- package/dist/cjs/cli/commands/status.js +41 -5
- package/dist/cjs/cli/commands/watch.js +4 -1
- package/dist/cjs/cli/index.js +4 -1
- package/dist/cjs/dialect/database-introspector.js +16 -21
- package/dist/cjs/dialect/sqlite/sqlite-introspector.js +13 -24
- package/dist/cjs/dynamic/dynamic.d.ts +20 -0
- package/dist/cjs/dynamic/dynamic.js +25 -0
- package/dist/cjs/edge-runtime/edge-config.d.ts +125 -0
- package/dist/cjs/edge-runtime/edge-config.js +323 -0
- package/dist/cjs/errors/NoormError.d.ts +27 -4
- package/dist/cjs/errors/NoormError.js +142 -21
- package/dist/cjs/logging/logger.d.ts +7 -2
- package/dist/cjs/logging/logger.js +21 -7
- package/dist/cjs/noormme.d.ts +12 -8
- package/dist/cjs/noormme.js +133 -61
- package/dist/cjs/operation-node/column-node.js +4 -0
- package/dist/cjs/operation-node/identifier-node.js +4 -0
- package/dist/cjs/operation-node/table-node.js +7 -0
- package/dist/cjs/parser/reference-parser.js +5 -0
- package/dist/cjs/parser/table-parser.js +2 -0
- package/dist/cjs/performance/index.d.ts +44 -0
- package/dist/cjs/performance/index.js +64 -0
- package/dist/cjs/performance/query-optimizer.d.ts +134 -0
- package/dist/cjs/performance/query-optimizer.js +391 -0
- package/dist/cjs/performance/services/cache-service.d.ts +177 -0
- package/dist/cjs/performance/services/cache-service.js +415 -0
- package/dist/cjs/performance/services/connection-factory.d.ts +198 -0
- package/dist/cjs/performance/services/connection-factory.js +498 -0
- package/dist/cjs/performance/services/metrics-collector.d.ts +162 -0
- package/dist/cjs/performance/services/metrics-collector.js +406 -0
- package/dist/cjs/performance/utils/query-parser.d.ts +123 -0
- package/dist/cjs/performance/utils/query-parser.js +295 -0
- package/dist/cjs/raw-builder/sql.d.ts +73 -26
- package/dist/cjs/raw-builder/sql.js +9 -0
- package/dist/cjs/repository/repository-factory.d.ts +10 -42
- package/dist/cjs/repository/repository-factory.js +276 -394
- package/dist/cjs/schema/core/coordinators/schema-discovery.coordinator.js +6 -4
- package/dist/cjs/schema/core/factories/discovery-factory.js +5 -5
- package/dist/cjs/schema/core/utils/name-generator.js +34 -2
- package/dist/cjs/schema/core/utils/type-mapper.d.ts +19 -14
- package/dist/cjs/schema/core/utils/type-mapper.js +4 -7
- package/dist/cjs/schema/dialects/sqlite/discovery/sqlite-constraint-discovery.js +3 -2
- package/dist/cjs/schema/dialects/sqlite/sqlite-discovery.coordinator.d.ts +2 -0
- package/dist/cjs/schema/dialects/sqlite/sqlite-discovery.coordinator.js +19 -5
- package/dist/cjs/schema/test/dialect-capabilities.test.js +6 -0
- package/dist/cjs/schema/test/error-handling.test.js +52 -33
- package/dist/cjs/schema/test/integration.test.js +51 -5
- package/dist/cjs/schema/test/sqlite-discovery-coordinator.test.js +8 -4
- package/dist/cjs/sqlite-migration/index.js +35 -2
- package/dist/cjs/testing/test-utils.d.ts +5 -0
- package/dist/cjs/testing/test-utils.js +66 -6
- package/dist/cjs/types/index.d.ts +78 -13
- package/dist/cjs/types/index.js +23 -0
- package/dist/cjs/types/type-generator.d.ts +8 -0
- package/dist/cjs/types/type-generator.js +86 -17
- package/dist/cjs/util/safe-sql-helpers.d.ts +124 -0
- package/dist/cjs/util/safe-sql-helpers.js +221 -0
- package/dist/cjs/util/security-validator.d.ts +44 -0
- package/dist/cjs/util/security-validator.js +256 -0
- package/dist/cjs/util/security.d.ts +60 -0
- package/dist/cjs/util/security.js +137 -0
- package/dist/cjs/watch/schema-watcher.js +26 -7
- package/dist/esm/cli/commands/generate.js +10 -6
- package/dist/esm/cli/commands/init.js +15 -11
- package/dist/esm/cli/commands/inspect.js +6 -2
- package/dist/esm/cli/commands/status.js +3 -3
- package/dist/esm/dialect/database-introspector.js +16 -21
- package/dist/esm/dialect/sqlite/sqlite-introspector.js +13 -24
- package/dist/esm/dynamic/dynamic.d.ts +20 -0
- package/dist/esm/dynamic/dynamic.js +25 -0
- package/dist/esm/edge-runtime/edge-config.d.ts +125 -0
- package/dist/esm/edge-runtime/edge-config.js +281 -0
- package/dist/esm/errors/NoormError.d.ts +27 -4
- package/dist/esm/errors/NoormError.js +134 -18
- package/dist/esm/logging/logger.d.ts +7 -2
- package/dist/esm/logging/logger.js +21 -7
- package/dist/esm/noormme.d.ts +12 -8
- package/dist/esm/noormme.js +130 -61
- package/dist/esm/operation-node/column-node.js +4 -0
- package/dist/esm/operation-node/identifier-node.js +4 -0
- package/dist/esm/operation-node/table-node.js +7 -0
- package/dist/esm/parser/reference-parser.js +5 -0
- package/dist/esm/parser/table-parser.js +2 -0
- package/dist/esm/performance/index.d.ts +44 -0
- package/dist/esm/performance/index.js +48 -0
- package/dist/esm/performance/query-optimizer.d.ts +134 -0
- package/dist/esm/performance/query-optimizer.js +387 -0
- package/dist/esm/performance/services/cache-service.d.ts +177 -0
- package/dist/esm/performance/services/cache-service.js +410 -0
- package/dist/esm/performance/services/connection-factory.d.ts +198 -0
- package/dist/esm/performance/services/connection-factory.js +493 -0
- package/dist/esm/performance/services/metrics-collector.d.ts +162 -0
- package/dist/esm/performance/services/metrics-collector.js +402 -0
- package/dist/esm/performance/utils/query-parser.d.ts +123 -0
- package/dist/esm/performance/utils/query-parser.js +292 -0
- package/dist/esm/raw-builder/sql.d.ts +73 -26
- package/dist/esm/raw-builder/sql.js +9 -0
- package/dist/esm/repository/repository-factory.d.ts +10 -42
- package/dist/esm/repository/repository-factory.js +277 -395
- package/dist/esm/schema/core/coordinators/schema-discovery.coordinator.js +6 -4
- package/dist/esm/schema/core/factories/discovery-factory.js +5 -5
- package/dist/esm/schema/core/utils/name-generator.js +34 -2
- package/dist/esm/schema/core/utils/type-mapper.d.ts +19 -14
- package/dist/esm/schema/core/utils/type-mapper.js +4 -7
- package/dist/esm/schema/dialects/sqlite/discovery/sqlite-constraint-discovery.js +3 -2
- package/dist/esm/schema/dialects/sqlite/sqlite-discovery.coordinator.d.ts +2 -0
- package/dist/esm/schema/dialects/sqlite/sqlite-discovery.coordinator.js +19 -5
- package/dist/esm/schema/test/dialect-capabilities.test.js +6 -0
- package/dist/esm/schema/test/error-handling.test.js +52 -33
- package/dist/esm/schema/test/integration.test.js +18 -5
- package/dist/esm/schema/test/sqlite-discovery-coordinator.test.js +8 -4
- package/dist/esm/testing/test-utils.d.ts +5 -0
- package/dist/esm/testing/test-utils.js +66 -6
- package/dist/esm/types/index.d.ts +78 -13
- package/dist/esm/types/index.js +22 -1
- package/dist/esm/types/type-generator.d.ts +8 -0
- package/dist/esm/types/type-generator.js +86 -17
- package/dist/esm/util/safe-sql-helpers.d.ts +124 -0
- package/dist/esm/util/safe-sql-helpers.js +209 -0
- package/dist/esm/util/security-validator.d.ts +44 -0
- package/dist/esm/util/security-validator.js +246 -0
- package/dist/esm/util/security.d.ts +60 -0
- package/dist/esm/util/security.js +114 -0
- package/dist/esm/watch/schema-watcher.js +26 -7
- package/package.json +1 -1
- package/dist/cjs/performance/query-analyzer.d.ts +0 -89
- package/dist/cjs/performance/query-analyzer.js +0 -263
- package/dist/esm/performance/query-analyzer.d.ts +0 -89
- package/dist/esm/performance/query-analyzer.js +0 -260
|
@@ -23,7 +23,7 @@ async function createTestDatabase(config = {}) {
|
|
|
23
23
|
password: ''
|
|
24
24
|
},
|
|
25
25
|
logging: {
|
|
26
|
-
enabled:
|
|
26
|
+
enabled: process.env.TEST_DEBUG === 'true' // Enable with TEST_DEBUG=true
|
|
27
27
|
}
|
|
28
28
|
};
|
|
29
29
|
break;
|
|
@@ -95,14 +95,62 @@ async function setupTestSchema(db) {
|
|
|
95
95
|
.column('user_id')
|
|
96
96
|
.execute();
|
|
97
97
|
// Initialize NOORMME to discover the schema
|
|
98
|
-
|
|
98
|
+
// Temporarily enable warnings to see discovery errors
|
|
99
|
+
const originalWarn = console.warn;
|
|
100
|
+
let discoveryError = null;
|
|
101
|
+
console.warn = (...args) => {
|
|
102
|
+
if (args[0]?.includes?.('Schema discovery failed')) {
|
|
103
|
+
discoveryError = args;
|
|
104
|
+
console.error('[DISCOVERY ERROR]', ...args);
|
|
105
|
+
}
|
|
106
|
+
originalWarn(...args);
|
|
107
|
+
};
|
|
108
|
+
try {
|
|
109
|
+
await db.initialize();
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
console.error('NOORMME initialization error:', error);
|
|
113
|
+
throw new Error(`Test setup failed during initialization: ${error}`);
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
console.warn = originalWarn;
|
|
117
|
+
}
|
|
118
|
+
// Verify schema was discovered
|
|
119
|
+
const schemaInfo = await db.getSchemaInfo();
|
|
120
|
+
if (schemaInfo.tables.length === 0) {
|
|
121
|
+
// Try to manually check if tables exist
|
|
122
|
+
let actualTables = [];
|
|
123
|
+
try {
|
|
124
|
+
actualTables = await kysely.selectFrom('sqlite_master')
|
|
125
|
+
.select(['name', 'type'])
|
|
126
|
+
.where('type', '=', 'table')
|
|
127
|
+
.where('name', 'not like', 'sqlite_%')
|
|
128
|
+
.execute();
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
console.error('[DEBUG] Failed to query sqlite_master:', e);
|
|
132
|
+
}
|
|
133
|
+
throw new Error(`Test setup failed: Schema discovery returned no tables but ${actualTables.length} tables exist in database (${actualTables.map((t) => t.name).join(', ')}). ` +
|
|
134
|
+
`Discovery error: ${discoveryError ? discoveryError.join(' ') : 'Unknown'}`);
|
|
135
|
+
}
|
|
136
|
+
// Verify expected tables exist
|
|
137
|
+
const tableNames = schemaInfo.tables.map(t => t.name);
|
|
138
|
+
const expectedTables = ['users', 'posts', 'comments'];
|
|
139
|
+
const missingTables = expectedTables.filter(t => !tableNames.includes(t));
|
|
140
|
+
if (missingTables.length > 0) {
|
|
141
|
+
throw new Error(`Test setup failed: Missing tables: ${missingTables.join(', ')}. ` +
|
|
142
|
+
`Found tables: ${tableNames.join(', ')}. ` +
|
|
143
|
+
`Expected: ${expectedTables.join(', ')}`);
|
|
144
|
+
}
|
|
99
145
|
}
|
|
100
146
|
/**
|
|
101
147
|
* Clean up test database
|
|
102
148
|
*/
|
|
103
149
|
async function cleanupTestDatabase(db) {
|
|
104
|
-
|
|
150
|
+
if (!db)
|
|
151
|
+
return;
|
|
105
152
|
try {
|
|
153
|
+
const kysely = db.getKysely();
|
|
106
154
|
// Drop tables in reverse order to handle foreign keys
|
|
107
155
|
await kysely.schema.dropTable('comments').ifExists().execute();
|
|
108
156
|
await kysely.schema.dropTable('posts').ifExists().execute();
|
|
@@ -111,16 +159,29 @@ async function cleanupTestDatabase(db) {
|
|
|
111
159
|
catch (error) {
|
|
112
160
|
// Ignore errors during cleanup
|
|
113
161
|
}
|
|
114
|
-
|
|
162
|
+
try {
|
|
163
|
+
await db.close();
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
// Ignore close errors
|
|
167
|
+
}
|
|
115
168
|
}
|
|
116
169
|
/**
|
|
117
170
|
* Test data factory
|
|
118
171
|
*/
|
|
119
172
|
class TestDataFactory {
|
|
120
173
|
db;
|
|
174
|
+
static emailCounter = 0;
|
|
121
175
|
constructor(db) {
|
|
122
176
|
this.db = db;
|
|
123
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Generate a unique email address
|
|
180
|
+
*/
|
|
181
|
+
generateUniqueEmail() {
|
|
182
|
+
TestDataFactory.emailCounter++;
|
|
183
|
+
return `test${Date.now()}_${TestDataFactory.emailCounter}_${Math.random().toString(36).substring(7)}@example.com`;
|
|
184
|
+
}
|
|
124
185
|
/**
|
|
125
186
|
* Create a test user
|
|
126
187
|
*/
|
|
@@ -128,7 +189,7 @@ class TestDataFactory {
|
|
|
128
189
|
const userRepo = this.db.getRepository('users');
|
|
129
190
|
const userData = {
|
|
130
191
|
name: 'Test User',
|
|
131
|
-
email:
|
|
192
|
+
email: this.generateUniqueEmail(),
|
|
132
193
|
age: 25,
|
|
133
194
|
active: true,
|
|
134
195
|
...overrides
|
|
@@ -143,7 +204,6 @@ class TestDataFactory {
|
|
|
143
204
|
for (let i = 0; i < count; i++) {
|
|
144
205
|
const user = await this.createUser({
|
|
145
206
|
name: `Test User ${i + 1}`,
|
|
146
|
-
email: `test${Date.now()}_${i}@example.com`,
|
|
147
207
|
...overrides
|
|
148
208
|
});
|
|
149
209
|
users.push(user);
|
|
@@ -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
|
|
14
|
-
port
|
|
16
|
+
host?: string;
|
|
17
|
+
port?: number;
|
|
15
18
|
database: string;
|
|
16
|
-
username
|
|
17
|
-
password
|
|
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?:
|
|
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
|
-
|
|
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:
|
|
119
|
-
findWithRelations(id:
|
|
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:
|
|
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:
|
|
143
|
-
[key: string]:
|
|
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?:
|
|
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;
|
package/dist/cjs/types/index.js
CHANGED
|
@@ -3,3 +3,26 @@
|
|
|
3
3
|
* Core type definitions for NOORMME
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.validateNOORMConfig = validateNOORMConfig;
|
|
7
|
+
// Configuration validation function type
|
|
8
|
+
function validateNOORMConfig(config) {
|
|
9
|
+
if (!config.dialect) {
|
|
10
|
+
throw new Error('Dialect is required');
|
|
11
|
+
}
|
|
12
|
+
if (!config.connection?.database) {
|
|
13
|
+
throw new Error('Database path is required');
|
|
14
|
+
}
|
|
15
|
+
// Validate dialect-specific requirements
|
|
16
|
+
if (config.dialect === 'sqlite') {
|
|
17
|
+
if (!config.connection.database.endsWith('.db') && !config.connection.database.endsWith('.sqlite')) {
|
|
18
|
+
console.warn('SQLite database path should typically end with .db or .sqlite');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
// Validate performance settings
|
|
22
|
+
if (config.performance?.maxBatchSize && config.performance.maxBatchSize <= 0) {
|
|
23
|
+
throw new Error('maxBatchSize must be greater than 0');
|
|
24
|
+
}
|
|
25
|
+
if (config.performance?.maxCacheSize && config.performance.maxCacheSize <= 0) {
|
|
26
|
+
throw new Error('maxCacheSize must be greater than 0');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -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
|
*/
|
|
@@ -18,7 +18,7 @@ class TypeGenerator {
|
|
|
18
18
|
let types = '';
|
|
19
19
|
// Generate entity types for each table
|
|
20
20
|
for (const table of schemaInfo.tables) {
|
|
21
|
-
const entity = this.generateEntityType(table);
|
|
21
|
+
const entity = this.generateEntityType(table, schemaInfo.relationships);
|
|
22
22
|
entities.push(entity);
|
|
23
23
|
interfaces += entity.interface + '\n\n';
|
|
24
24
|
types += entity.insertType + '\n\n';
|
|
@@ -36,11 +36,11 @@ class TypeGenerator {
|
|
|
36
36
|
/**
|
|
37
37
|
* Generate entity type for a table
|
|
38
38
|
*/
|
|
39
|
-
generateEntityType(table) {
|
|
39
|
+
generateEntityType(table, relationships) {
|
|
40
40
|
const entityName = this.toPascalCase(table.name);
|
|
41
41
|
const tableName = table.name;
|
|
42
42
|
// Generate main entity interface
|
|
43
|
-
const interfaceCode = this.generateEntityInterface(table, entityName);
|
|
43
|
+
const interfaceCode = this.generateEntityInterface(table, entityName, relationships);
|
|
44
44
|
// Generate insert type (all columns except auto-generated ones)
|
|
45
45
|
const insertType = this.generateInsertType(table, entityName);
|
|
46
46
|
// Generate update type (all columns optional except primary key)
|
|
@@ -59,7 +59,7 @@ class TypeGenerator {
|
|
|
59
59
|
/**
|
|
60
60
|
* Generate main entity interface
|
|
61
61
|
*/
|
|
62
|
-
generateEntityInterface(table, entityName) {
|
|
62
|
+
generateEntityInterface(table, entityName, relationships) {
|
|
63
63
|
let interfaceCode = `export interface ${entityName} {\n`;
|
|
64
64
|
// Add primary key columns first
|
|
65
65
|
for (const column of table.columns) {
|
|
@@ -78,8 +78,8 @@ class TypeGenerator {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
// Add relationship properties
|
|
81
|
-
const
|
|
82
|
-
for (const rel of
|
|
81
|
+
const tableRelationships = this.getRelationshipsForTable(table.name, relationships);
|
|
82
|
+
for (const rel of tableRelationships) {
|
|
83
83
|
const relType = this.getRelationshipType(rel);
|
|
84
84
|
interfaceCode += ` ${rel.name}?: ${relType}\n`;
|
|
85
85
|
}
|
|
@@ -189,8 +189,8 @@ class TypeGenerator {
|
|
|
189
189
|
'timestamp': 'Date',
|
|
190
190
|
'timestamptz': 'Date',
|
|
191
191
|
'time': 'Date',
|
|
192
|
-
'json': '
|
|
193
|
-
'jsonb': '
|
|
192
|
+
'json': 'Record<string, unknown>',
|
|
193
|
+
'jsonb': 'Record<string, unknown>',
|
|
194
194
|
'uuid': 'string',
|
|
195
195
|
// MySQL specific types
|
|
196
196
|
'longtext': 'string',
|
|
@@ -202,8 +202,11 @@ class TypeGenerator {
|
|
|
202
202
|
'double': 'number',
|
|
203
203
|
'bool': 'boolean',
|
|
204
204
|
'datetime': 'Date',
|
|
205
|
-
// SQLite specific types
|
|
205
|
+
// SQLite specific types (enhanced)
|
|
206
206
|
'blob': 'Buffer',
|
|
207
|
+
'int2': 'number',
|
|
208
|
+
'int8': 'number',
|
|
209
|
+
'clob': 'string',
|
|
207
210
|
// MSSQL specific types
|
|
208
211
|
'nvarchar': 'string',
|
|
209
212
|
'nchar': 'string',
|
|
@@ -214,23 +217,89 @@ class TypeGenerator {
|
|
|
214
217
|
};
|
|
215
218
|
// Try exact match first
|
|
216
219
|
if (typeMapping[column.type.toLowerCase()]) {
|
|
217
|
-
|
|
220
|
+
let mappedType = typeMapping[column.type.toLowerCase()];
|
|
221
|
+
// Handle nullable columns
|
|
222
|
+
if (column.nullable && mappedType !== 'unknown') {
|
|
223
|
+
mappedType = `${mappedType} | null`;
|
|
224
|
+
}
|
|
225
|
+
return mappedType;
|
|
218
226
|
}
|
|
219
227
|
// Handle parameterized types (e.g., varchar(255), decimal(10,2))
|
|
220
228
|
const baseType = column.type.toLowerCase().split('(')[0];
|
|
221
229
|
if (typeMapping[baseType]) {
|
|
222
|
-
|
|
230
|
+
let mappedType = typeMapping[baseType];
|
|
231
|
+
// Handle nullable columns
|
|
232
|
+
if (column.nullable && mappedType !== 'unknown') {
|
|
233
|
+
mappedType = `${mappedType} | null`;
|
|
234
|
+
}
|
|
235
|
+
return mappedType;
|
|
223
236
|
}
|
|
224
|
-
// Default to
|
|
225
|
-
return '
|
|
237
|
+
// Default to unknown for unknown types
|
|
238
|
+
return column.nullable ? 'unknown | null' : 'unknown';
|
|
226
239
|
}
|
|
227
240
|
/**
|
|
228
241
|
* Get relationships for a specific table
|
|
229
242
|
*/
|
|
230
|
-
getRelationshipsForTable(tableName) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
243
|
+
getRelationshipsForTable(tableName, relationships) {
|
|
244
|
+
return relationships.filter(rel => rel.fromTable === tableName || rel.toTable === tableName);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Generate relationship interfaces
|
|
248
|
+
*/
|
|
249
|
+
generateRelationshipInterfaces(relationships) {
|
|
250
|
+
if (relationships.length === 0) {
|
|
251
|
+
return '';
|
|
252
|
+
}
|
|
253
|
+
let relationshipInterfaces = '// Relationship Types\n\n';
|
|
254
|
+
// Group relationships by type
|
|
255
|
+
const groupedRelationships = relationships.reduce((acc, rel) => {
|
|
256
|
+
const key = `${rel.fromTable}_${rel.toTable}_${rel.type}`;
|
|
257
|
+
if (!acc[key]) {
|
|
258
|
+
acc[key] = [];
|
|
259
|
+
}
|
|
260
|
+
acc[key].push(rel);
|
|
261
|
+
return acc;
|
|
262
|
+
}, {});
|
|
263
|
+
for (const [key, rels] of Object.entries(groupedRelationships)) {
|
|
264
|
+
const rel = rels[0]; // Use first relationship as template
|
|
265
|
+
const fromEntity = this.toPascalCase(rel.fromTable);
|
|
266
|
+
const toEntity = this.toPascalCase(rel.toTable);
|
|
267
|
+
switch (rel.type) {
|
|
268
|
+
case 'one-to-many':
|
|
269
|
+
relationshipInterfaces += `export interface ${fromEntity}With${toEntity}s extends ${fromEntity} {\n`;
|
|
270
|
+
relationshipInterfaces += ` ${this.toCamelCase(rel.toTable)}s: ${toEntity}[]\n`;
|
|
271
|
+
relationshipInterfaces += `}\n\n`;
|
|
272
|
+
relationshipInterfaces += `export interface ${toEntity}With${fromEntity} extends ${toEntity} {\n`;
|
|
273
|
+
relationshipInterfaces += ` ${this.toCamelCase(rel.fromTable)}: ${fromEntity}\n`;
|
|
274
|
+
relationshipInterfaces += `}\n\n`;
|
|
275
|
+
break;
|
|
276
|
+
case 'many-to-one':
|
|
277
|
+
relationshipInterfaces += `export interface ${fromEntity}With${toEntity} extends ${fromEntity} {\n`;
|
|
278
|
+
relationshipInterfaces += ` ${this.toCamelCase(rel.toTable)}: ${toEntity}\n`;
|
|
279
|
+
relationshipInterfaces += `}\n\n`;
|
|
280
|
+
relationshipInterfaces += `export interface ${toEntity}With${fromEntity}s extends ${toEntity} {\n`;
|
|
281
|
+
relationshipInterfaces += ` ${this.toCamelCase(rel.fromTable)}s: ${fromEntity}[]\n`;
|
|
282
|
+
relationshipInterfaces += `}\n\n`;
|
|
283
|
+
break;
|
|
284
|
+
case 'many-to-many':
|
|
285
|
+
relationshipInterfaces += `export interface ${fromEntity}With${toEntity}s extends ${fromEntity} {\n`;
|
|
286
|
+
relationshipInterfaces += ` ${this.toCamelCase(rel.toTable)}s: ${toEntity}[]\n`;
|
|
287
|
+
relationshipInterfaces += `}\n\n`;
|
|
288
|
+
relationshipInterfaces += `export interface ${toEntity}With${fromEntity}s extends ${toEntity} {\n`;
|
|
289
|
+
relationshipInterfaces += ` ${this.toCamelCase(rel.fromTable)}s: ${fromEntity}[]\n`;
|
|
290
|
+
relationshipInterfaces += `}\n\n`;
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return relationshipInterfaces;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Convert string to camelCase
|
|
298
|
+
*/
|
|
299
|
+
toCamelCase(str) {
|
|
300
|
+
return str.replace(/([-_][a-z])/gi, ($1) => {
|
|
301
|
+
return $1.toUpperCase().replace('-', '').replace('_', '');
|
|
302
|
+
});
|
|
234
303
|
}
|
|
235
304
|
/**
|
|
236
305
|
* 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;
|