@reldens/storage 0.81.0 → 0.82.0
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/CLAUDE.md +125 -3
- package/bin/reldens-storage.js +63 -8
- package/lib/entities-generator.js +4 -3
- package/lib/generators/models-generation.js +161 -8
- package/lib/prisma/prisma-data-server.js +38 -4
- package/lib/prisma/prisma-driver.js +216 -368
- package/lib/prisma/prisma-filter-processor.js +146 -0
- package/lib/prisma/prisma-metadata-loader.js +7 -0
- package/lib/prisma/prisma-query-builder.js +42 -0
- package/lib/prisma/prisma-relation-resolver.js +168 -168
- package/lib/prisma/prisma-schema-generator.js +40 -33
- package/lib/prisma/prisma-type-caster.js +29 -0
- package/package.json +1 -1
package/CLAUDE.md
CHANGED
|
@@ -105,12 +105,18 @@ npx reldens-storage-prisma --host=<host> --database=<db> --user=<user> --passwor
|
|
|
105
105
|
- Automatic schema synchronization
|
|
106
106
|
|
|
107
107
|
**Prisma Driver** (`lib/prisma/`):
|
|
108
|
-
- `prisma-driver.js`: Driver implementation
|
|
108
|
+
- `prisma-driver.js`: Driver implementation with enhanced validation
|
|
109
109
|
- `prisma-data-server.js`: Data server using Prisma Client
|
|
110
110
|
- `prisma-schema-generator.js`: Schema generation and introspection
|
|
111
|
+
- `prisma-metadata-loader.js`: Loads field metadata including defaults
|
|
112
|
+
- `prisma-type-caster.js`: Type casting and normalization
|
|
113
|
+
- `prisma-relation-resolver.js`: Relation mapping and transformations
|
|
111
114
|
- Features:
|
|
112
|
-
- Schema-first approach
|
|
113
|
-
- Type-safe queries
|
|
115
|
+
- Schema-first approach with auto-introspection
|
|
116
|
+
- Type-safe queries with Prisma Client
|
|
117
|
+
- Custom `ensureRequiredFields()` validation for better error messages
|
|
118
|
+
- Database default value support (skips validation for fields with defaults)
|
|
119
|
+
- VARCHAR foreign key support using relation connect syntax
|
|
114
120
|
- Introspection via `prisma db pull`
|
|
115
121
|
- Binary targets configuration
|
|
116
122
|
- Data proxy support
|
|
@@ -237,6 +243,122 @@ All generated entity relations follow the `related_*` prefix pattern:
|
|
|
237
243
|
|
|
238
244
|
This pattern is consistent across all ORM drivers and is defined in `entities-config.js`.
|
|
239
245
|
|
|
246
|
+
## Prisma Driver Validation System
|
|
247
|
+
|
|
248
|
+
### ensureRequiredFields() Method
|
|
249
|
+
|
|
250
|
+
The Prisma driver includes custom validation that differs from ObjectionJS and provides better error messages:
|
|
251
|
+
|
|
252
|
+
**Location:** `lib/prisma/prisma-driver.js` lines 201-223
|
|
253
|
+
|
|
254
|
+
**Purpose:** Validates that all required fields are present before sending data to Prisma Client
|
|
255
|
+
|
|
256
|
+
**Key Behavior:**
|
|
257
|
+
```javascript
|
|
258
|
+
ensureRequiredFields(data) {
|
|
259
|
+
let missingFields = [];
|
|
260
|
+
for(let field of this.requiredFields){
|
|
261
|
+
if(sc.hasOwn(data, field)){
|
|
262
|
+
continue; // Field is present
|
|
263
|
+
}
|
|
264
|
+
if(sc.hasOwn(this.foreignKeyMappings, field)){
|
|
265
|
+
let relationName = this.foreignKeyMappings[field];
|
|
266
|
+
if(sc.hasOwn(data, relationName)){
|
|
267
|
+
continue; // FK mapped to relation (connect syntax)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if(sc.hasOwn(this.fieldDefaults, field)){
|
|
271
|
+
continue; // Field has database default - skip validation
|
|
272
|
+
}
|
|
273
|
+
missingFields.push(field);
|
|
274
|
+
}
|
|
275
|
+
if(0 < missingFields.length){
|
|
276
|
+
Logger.warning('Missing required fields for '+this.tableName()+': '+missingFields.join(', '));
|
|
277
|
+
}
|
|
278
|
+
return data;
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Important:** This validation runs BEFORE Prisma Client, allowing it to:
|
|
283
|
+
1. Provide clear error messages about missing fields
|
|
284
|
+
2. Allow database defaults to work (doesn't validate fields with defaults)
|
|
285
|
+
3. Support foreign key relation syntax (checks both FK field and relation)
|
|
286
|
+
|
|
287
|
+
### Database Default Values
|
|
288
|
+
|
|
289
|
+
**How It Works:**
|
|
290
|
+
- Metadata loader extracts default values from Prisma schema (`@default()` directive)
|
|
291
|
+
- Stored in `this.fieldDefaults` map during driver initialization
|
|
292
|
+
- Validation skips required fields that have defaults
|
|
293
|
+
- Prisma/database applies default when field is missing
|
|
294
|
+
|
|
295
|
+
**Example:**
|
|
296
|
+
```javascript
|
|
297
|
+
// Prisma schema
|
|
298
|
+
model scores_detail {
|
|
299
|
+
kill_time DateTime @default(now()) @db.DateTime(0)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Admin panel sends data without kill_time
|
|
303
|
+
{
|
|
304
|
+
player_id: 1001,
|
|
305
|
+
obtained_score: 150
|
|
306
|
+
// kill_time missing but has default
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Validation skips kill_time (has default)
|
|
310
|
+
// Prisma creates record, database applies DEFAULT CURRENT_TIMESTAMP
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Comparison with ObjectionJS
|
|
314
|
+
|
|
315
|
+
**ObjectionJS Driver:**
|
|
316
|
+
```javascript
|
|
317
|
+
create(params) {
|
|
318
|
+
return this.queryBuilder().insert(params);
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
- No validation
|
|
322
|
+
- Passes data directly to Knex
|
|
323
|
+
- Database handles missing fields and defaults
|
|
324
|
+
- Less informative error messages
|
|
325
|
+
|
|
326
|
+
**Prisma Driver:**
|
|
327
|
+
```javascript
|
|
328
|
+
async create(params) {
|
|
329
|
+
let preparedData = this.prepareDataWithRelations(params, true);
|
|
330
|
+
this.ensureRequiredFields(preparedData); // Custom validation
|
|
331
|
+
try {
|
|
332
|
+
return this.typeCaster.normalizeReturnData(await this.model.create({data: preparedData}));
|
|
333
|
+
```
|
|
334
|
+
- Custom validation before Prisma
|
|
335
|
+
- Better error messages
|
|
336
|
+
- Supports database defaults
|
|
337
|
+
- Type-safe queries
|
|
338
|
+
|
|
339
|
+
**Key Difference:** Prisma validates first, so it must be aware of database defaults to allow them to work.
|
|
340
|
+
|
|
341
|
+
### Foreign Key Handling
|
|
342
|
+
|
|
343
|
+
Both drivers handle foreign keys, but with different syntax:
|
|
344
|
+
|
|
345
|
+
**ObjectionJS:**
|
|
346
|
+
```javascript
|
|
347
|
+
// Direct FK field
|
|
348
|
+
{player_id: 1001}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**Prisma:**
|
|
352
|
+
```javascript
|
|
353
|
+
// Relation connect syntax
|
|
354
|
+
{players: {connect: {id: 1001}}}
|
|
355
|
+
|
|
356
|
+
// Also supports VARCHAR FKs
|
|
357
|
+
{related_table: {connect: {custom_id: "ABC123"}}}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
The `prepareDataWithRelations()` method (lines 156-199) automatically converts FK fields to relation syntax.
|
|
361
|
+
|
|
240
362
|
## Important Notes
|
|
241
363
|
|
|
242
364
|
### Entity Management
|
package/bin/reldens-storage.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const { EntitiesGenerator } = require('../lib/entities-generator');
|
|
10
|
+
const { FileHandler } = require('@reldens/server-utils');
|
|
10
11
|
const { Logger, sc } = require('@reldens/utils');
|
|
11
12
|
|
|
12
13
|
class StorageEntitiesGenerator
|
|
@@ -19,6 +20,7 @@ class StorageEntitiesGenerator
|
|
|
19
20
|
this.config = {};
|
|
20
21
|
this.projectPath = process.cwd();
|
|
21
22
|
this.isOverride = false;
|
|
23
|
+
this.prismaClientPath = '';
|
|
22
24
|
this.parseArguments();
|
|
23
25
|
}
|
|
24
26
|
|
|
@@ -38,11 +40,15 @@ class StorageEntitiesGenerator
|
|
|
38
40
|
continue;
|
|
39
41
|
}
|
|
40
42
|
let key = arg.substring(2, equalIndex);
|
|
41
|
-
let value = arg.substring(equalIndex
|
|
43
|
+
let value = arg.substring(equalIndex+1);
|
|
42
44
|
if('path' === key){
|
|
43
45
|
this.projectPath = value;
|
|
44
46
|
continue;
|
|
45
47
|
}
|
|
48
|
+
if('prismaClientPath' === key){
|
|
49
|
+
this.prismaClientPath = value;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
46
52
|
if('pass' === key){
|
|
47
53
|
this.config['password'] = value;
|
|
48
54
|
continue;
|
|
@@ -69,6 +75,9 @@ class StorageEntitiesGenerator
|
|
|
69
75
|
if('mikro-orm' === connectionData.driver && !this.config.client){
|
|
70
76
|
connectionData.client = 'mysql';
|
|
71
77
|
}
|
|
78
|
+
if('prisma' === connectionData.driver && !this.config.client){
|
|
79
|
+
connectionData.client = 'mysql';
|
|
80
|
+
}
|
|
72
81
|
return connectionData;
|
|
73
82
|
}
|
|
74
83
|
|
|
@@ -84,16 +93,54 @@ class StorageEntitiesGenerator
|
|
|
84
93
|
validateRequiredArgs(connectionData)
|
|
85
94
|
{
|
|
86
95
|
if(!connectionData.user || !connectionData.database){
|
|
87
|
-
Logger.error(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
Logger.error(
|
|
97
|
+
'Required parameters missing.',
|
|
98
|
+
'Usage: npx reldens-storage generateEntities --user=[db-username]'
|
|
99
|
+
+' --pass=[db-password]'
|
|
100
|
+
+' --host=[db-host]'
|
|
101
|
+
+' --database=[db-name]'
|
|
102
|
+
+' --driver=[driver-map-key]'
|
|
103
|
+
+' --client=[db-client]'
|
|
104
|
+
+' --prismaClientPath=[path-to-prisma-client]'
|
|
105
|
+
+' --path=[project-path] --override',
|
|
106
|
+
'Optional flags:',
|
|
107
|
+
' --override Regenerate all files even if they exist'
|
|
108
|
+
);
|
|
92
109
|
return false;
|
|
93
110
|
}
|
|
94
111
|
return true;
|
|
95
112
|
}
|
|
96
113
|
|
|
114
|
+
loadPrismaClient(connectionData)
|
|
115
|
+
{
|
|
116
|
+
let prismaClientPath = this.prismaClientPath;
|
|
117
|
+
if(!prismaClientPath){
|
|
118
|
+
prismaClientPath = FileHandler.joinPaths(this.projectPath, 'prisma', 'client');
|
|
119
|
+
}
|
|
120
|
+
if(!FileHandler.exists(prismaClientPath)){
|
|
121
|
+
Logger.critical('PrismaClient path does not exist: '+prismaClientPath);
|
|
122
|
+
Logger.info('Please run "npx prisma generate" first or provide --prismaClientPath argument.');
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
Logger.info('Loading PrismaClient from: '+prismaClientPath);
|
|
126
|
+
let prismaModule = require(prismaClientPath);
|
|
127
|
+
if(!prismaModule.PrismaClient){
|
|
128
|
+
Logger.critical('PrismaClient class not found at: '+prismaClientPath);
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
let connectionString = connectionData.client+'://'
|
|
132
|
+
+connectionData.user
|
|
133
|
+
+(connectionData.password ? ':'+connectionData.password : '')
|
|
134
|
+
+'@'+connectionData.host
|
|
135
|
+
+':'+connectionData.port
|
|
136
|
+
+'/'+connectionData.database;
|
|
137
|
+
Logger.info('Creating PrismaClient with connection to: '+connectionData.database);
|
|
138
|
+
return new prismaModule.PrismaClient({
|
|
139
|
+
datasources: {db: {url: connectionString}},
|
|
140
|
+
log: ['error']
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
97
144
|
async run()
|
|
98
145
|
{
|
|
99
146
|
if(!this.validateCommand()){
|
|
@@ -103,11 +150,19 @@ class StorageEntitiesGenerator
|
|
|
103
150
|
if(!this.validateRequiredArgs(connectionData)){
|
|
104
151
|
return false;
|
|
105
152
|
}
|
|
106
|
-
let
|
|
153
|
+
let generatorProps = {
|
|
107
154
|
connectionData,
|
|
108
155
|
projectPath: this.projectPath,
|
|
109
156
|
isOverride: this.isOverride
|
|
110
|
-
}
|
|
157
|
+
};
|
|
158
|
+
if('prisma' === connectionData.driver){
|
|
159
|
+
let prismaClient = this.loadPrismaClient(connectionData);
|
|
160
|
+
if(!prismaClient){
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
generatorProps.prismaClient = prismaClient;
|
|
164
|
+
}
|
|
165
|
+
let generator = new EntitiesGenerator(generatorProps);
|
|
111
166
|
let success = await generator.generate();
|
|
112
167
|
if(!success){
|
|
113
168
|
Logger.error('Entity generation failed.');
|
|
@@ -208,7 +208,7 @@ class EntitiesGenerator
|
|
|
208
208
|
}
|
|
209
209
|
this.processModelForRelations(model.name, model, models);
|
|
210
210
|
}
|
|
211
|
-
Logger.info('Extracted relations metadata for '
|
|
211
|
+
Logger.info('Extracted relations metadata for '+Object.keys(this.prismaRelationsMetadata).length+' models.');
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
processObjectModels(models)
|
|
@@ -225,7 +225,7 @@ class EntitiesGenerator
|
|
|
225
225
|
}
|
|
226
226
|
this.processModelForRelations(modelName, model, models);
|
|
227
227
|
}
|
|
228
|
-
Logger.info('Extracted relations metadata for '
|
|
228
|
+
Logger.info('Extracted relations metadata for '+Object.keys(this.prismaRelationsMetadata).length+' models.');
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
processModelForRelations(modelName, model, allModels)
|
|
@@ -470,7 +470,8 @@ class EntitiesGenerator
|
|
|
470
470
|
database: sc.get(this.connectionData, 'database', ''),
|
|
471
471
|
host: sc.get(this.connectionData, 'host', 'localhost'),
|
|
472
472
|
port: sc.get(this.connectionData, 'port', 3306)
|
|
473
|
-
}
|
|
473
|
+
},
|
|
474
|
+
projectRoot: this.projectPath
|
|
474
475
|
};
|
|
475
476
|
if('prisma' === driverKey && this.prismaClient){
|
|
476
477
|
serverConfig.prismaClient = this.prismaClient;
|
|
@@ -100,7 +100,7 @@ class ModelsGeneration extends BaseGenerator
|
|
|
100
100
|
generateModelRelations(tableName, tableData, relationMetadata, driverKey)
|
|
101
101
|
{
|
|
102
102
|
if('prisma' === driverKey){
|
|
103
|
-
return this.generatePrismaRelations(relationMetadata);
|
|
103
|
+
return this.generatePrismaRelations(tableName, tableData, relationMetadata);
|
|
104
104
|
}
|
|
105
105
|
if('objection-js' === driverKey){
|
|
106
106
|
return this.generateObjectionJsRelations(tableName, tableData);
|
|
@@ -108,20 +108,173 @@ class ModelsGeneration extends BaseGenerator
|
|
|
108
108
|
return '';
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
generatePrismaRelations(relationMetadata)
|
|
111
|
+
generatePrismaRelations(tableName, tableData, relationMetadata)
|
|
112
112
|
{
|
|
113
113
|
if(!relationMetadata || 0 === Object.keys(relationMetadata).length){
|
|
114
114
|
return '';
|
|
115
115
|
}
|
|
116
116
|
let relationTypesArray = [];
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
let relationMappingsArray = [];
|
|
118
|
+
let forwardMappings = this.buildPrismaForwardMappings(tableData, relationMetadata);
|
|
119
|
+
let reverseMappings = this.buildPrismaReverseMappings(tableName, relationMetadata);
|
|
120
|
+
let allMappings = Object.assign({}, forwardMappings, reverseMappings);
|
|
121
|
+
for(let prismaRelationName of Object.keys(relationMetadata)){
|
|
122
|
+
let relation = relationMetadata[prismaRelationName];
|
|
123
|
+
relationTypesArray.push(' '+prismaRelationName+': \''+relation.type+'\'');
|
|
124
|
+
}
|
|
125
|
+
for(let reldensKey of Object.keys(allMappings)){
|
|
126
|
+
let prismaName = allMappings[reldensKey];
|
|
127
|
+
relationMappingsArray.push(' \''+reldensKey+'\': \''+prismaName+'\'');
|
|
128
|
+
}
|
|
129
|
+
let result = '';
|
|
130
|
+
if(0 < relationTypesArray.length){
|
|
131
|
+
result += '\n\n static get relationTypes()\n {\n return {\n';
|
|
132
|
+
result += relationTypesArray.join(',\n');
|
|
133
|
+
result += '\n };\n }';
|
|
134
|
+
}
|
|
135
|
+
if(0 < relationMappingsArray.length){
|
|
136
|
+
result += '\n\n static get relationMappings()\n {\n return {\n';
|
|
137
|
+
result += relationMappingsArray.join(',\n');
|
|
138
|
+
result += '\n };\n }';
|
|
139
|
+
}
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
buildPrismaForwardMappings(tableData, relationMetadata)
|
|
144
|
+
{
|
|
145
|
+
let mappings = {};
|
|
146
|
+
let referenceCounts = this.countReferencesPerTable(tableData);
|
|
147
|
+
for(let columnName of Object.keys(tableData.columns)){
|
|
148
|
+
let column = tableData.columns[columnName];
|
|
149
|
+
if(!sc.hasOwn(column, 'referencedTable')){
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
let reldensKey = this.generateForwardRelationKey(
|
|
153
|
+
column.referencedTable,
|
|
154
|
+
columnName,
|
|
155
|
+
referenceCounts
|
|
156
|
+
);
|
|
157
|
+
let prismaName = this.findPrismaRelationForFk(columnName, column.referencedTable, relationMetadata);
|
|
158
|
+
if(!prismaName){
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
mappings[reldensKey] = prismaName;
|
|
120
162
|
}
|
|
121
|
-
|
|
122
|
-
|
|
163
|
+
return mappings;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
findPrismaRelationForFk(columnName, referencedTable, relationMetadata)
|
|
167
|
+
{
|
|
168
|
+
let lowerColumn = columnName.toLowerCase();
|
|
169
|
+
let lowerTable = referencedTable.toLowerCase();
|
|
170
|
+
for(let prismaName of Object.keys(relationMetadata)){
|
|
171
|
+
let relation = relationMetadata[prismaName];
|
|
172
|
+
if(relation.model.toLowerCase() !== lowerTable){
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if(relation.isList){
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
let lowerPrisma = prismaName.toLowerCase();
|
|
179
|
+
if(lowerPrisma.includes(lowerColumn.replace('_id', ''))){
|
|
180
|
+
return prismaName;
|
|
181
|
+
}
|
|
182
|
+
if(lowerPrisma.includes(lowerColumn)){
|
|
183
|
+
return prismaName;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
for(let prismaName of Object.keys(relationMetadata)){
|
|
187
|
+
let relation = relationMetadata[prismaName];
|
|
188
|
+
if(relation.model.toLowerCase() !== lowerTable){
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if(relation.isList){
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
return prismaName;
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
buildPrismaReverseMappings(tableName, relationMetadata)
|
|
200
|
+
{
|
|
201
|
+
let mappings = {};
|
|
202
|
+
let reverseRelations = {};
|
|
203
|
+
for(let prismaName of Object.keys(relationMetadata)){
|
|
204
|
+
let relation = relationMetadata[prismaName];
|
|
205
|
+
if(this.isForwardRelation(relation)){
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
let relatedTable = relation.model.toLowerCase();
|
|
209
|
+
if(!reverseRelations[relatedTable]){
|
|
210
|
+
reverseRelations[relatedTable] = [];
|
|
211
|
+
}
|
|
212
|
+
reverseRelations[relatedTable].push({prismaName, relation});
|
|
213
|
+
}
|
|
214
|
+
for(let relatedTable of Object.keys(reverseRelations)){
|
|
215
|
+
let relations = reverseRelations[relatedTable];
|
|
216
|
+
let referenceCount = relations.length;
|
|
217
|
+
for(let relInfo of relations){
|
|
218
|
+
let reldensKey = this.generatePrismaReverseRelationKey(
|
|
219
|
+
relatedTable,
|
|
220
|
+
relInfo.prismaName,
|
|
221
|
+
tableName,
|
|
222
|
+
referenceCount
|
|
223
|
+
);
|
|
224
|
+
mappings[reldensKey] = relInfo.prismaName;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return mappings;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
isForwardRelation(relation)
|
|
231
|
+
{
|
|
232
|
+
return sc.hasOwn(relation, 'foreignKeys')
|
|
233
|
+
&& sc.isArray(relation.foreignKeys)
|
|
234
|
+
&& 0 < relation.foreignKeys.length;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
generatePrismaReverseRelationKey(relatedTable, prismaName, tableName, referenceCount)
|
|
238
|
+
{
|
|
239
|
+
if(1 === referenceCount){
|
|
240
|
+
return 'related_'+relatedTable;
|
|
241
|
+
}
|
|
242
|
+
let columnHint = this.extractColumnHintFromPrismaName(prismaName, relatedTable, tableName);
|
|
243
|
+
if(columnHint){
|
|
244
|
+
return 'related_'+relatedTable+'_'+columnHint;
|
|
245
|
+
}
|
|
246
|
+
return 'related_'+relatedTable;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
extractColumnHintFromPrismaName(prismaName, relatedTable, tableName)
|
|
250
|
+
{
|
|
251
|
+
let lowerName = prismaName.toLowerCase();
|
|
252
|
+
let lowerTable = tableName.toLowerCase();
|
|
253
|
+
let lowerRelated = relatedTable.toLowerCase();
|
|
254
|
+
let toSuffix = 'to'+lowerTable;
|
|
255
|
+
if(!lowerName.endsWith(toSuffix)){
|
|
256
|
+
toSuffix = 'to'+lowerRelated;
|
|
257
|
+
if(!lowerName.endsWith(toSuffix)){
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
let withoutSuffix = prismaName.slice(0, -toSuffix.length);
|
|
262
|
+
let lowerWithout = withoutSuffix.toLowerCase();
|
|
263
|
+
let prefixes = [
|
|
264
|
+
lowerRelated+'_'+lowerRelated+'_',
|
|
265
|
+
lowerRelated+'_',
|
|
266
|
+
lowerTable+'_'
|
|
267
|
+
];
|
|
268
|
+
for(let prefix of prefixes){
|
|
269
|
+
if(lowerWithout.startsWith(prefix)){
|
|
270
|
+
withoutSuffix = withoutSuffix.slice(prefix.length);
|
|
271
|
+
lowerWithout = withoutSuffix.toLowerCase();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if(withoutSuffix.endsWith('_id')){
|
|
275
|
+
withoutSuffix = withoutSuffix.slice(0, -3);
|
|
123
276
|
}
|
|
124
|
-
return
|
|
277
|
+
return withoutSuffix.toLowerCase();
|
|
125
278
|
}
|
|
126
279
|
|
|
127
280
|
generateObjectionJsRelations(tableName, tableData)
|
|
@@ -8,6 +8,7 @@ const { BaseDataServer } = require('../base-data-server');
|
|
|
8
8
|
const { PrismaDriver } = require('./prisma-driver');
|
|
9
9
|
const { MySQLTablesProvider } = require('../mysql-tables-provider');
|
|
10
10
|
const { PrismaClient } = require('@prisma/client');
|
|
11
|
+
const { FileHandler } = require('@reldens/server-utils');
|
|
11
12
|
const { Logger, sc } = require('@reldens/utils');
|
|
12
13
|
|
|
13
14
|
class PrismaDataServer extends BaseDataServer
|
|
@@ -17,6 +18,8 @@ class PrismaDataServer extends BaseDataServer
|
|
|
17
18
|
{
|
|
18
19
|
super(props);
|
|
19
20
|
this.prisma = sc.get(props, 'prismaClient', false);
|
|
21
|
+
this.projectRoot = sc.get(props, 'projectRoot', false);
|
|
22
|
+
this.allRelationMappings = {};
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
async connect()
|
|
@@ -25,12 +28,16 @@ class PrismaDataServer extends BaseDataServer
|
|
|
25
28
|
return this.initialized;
|
|
26
29
|
}
|
|
27
30
|
try {
|
|
28
|
-
if(!this.prisma){
|
|
31
|
+
if(!this.prisma && this.defaultClientIsGenerated()){
|
|
29
32
|
this.prisma = new PrismaClient({
|
|
30
33
|
datasources: {db: {url: this.connectString}},
|
|
31
34
|
log: this.debug ? ['query', 'info', 'warn', 'error'] : ['error']
|
|
32
35
|
});
|
|
33
36
|
}
|
|
37
|
+
if(!this.prisma){
|
|
38
|
+
Logger.error('Prisma client could not be initialized.');
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
34
41
|
await this.prisma.$connect();
|
|
35
42
|
let dbTest = await this.prisma.$queryRaw`SELECT DATABASE() as current_db`;
|
|
36
43
|
Logger.info('Connected to database: ' + dbTest[0].current_db);
|
|
@@ -42,6 +49,20 @@ class PrismaDataServer extends BaseDataServer
|
|
|
42
49
|
return false;
|
|
43
50
|
}
|
|
44
51
|
|
|
52
|
+
defaultClientIsGenerated()
|
|
53
|
+
{
|
|
54
|
+
let existsFromProjectRoot = false;
|
|
55
|
+
if(this.projectRoot){
|
|
56
|
+
existsFromProjectRoot = FileHandler.exists(FileHandler.joinPaths(
|
|
57
|
+
this.projectRoot, 'node_modules', '@prisma', 'client', 'index.js'
|
|
58
|
+
));
|
|
59
|
+
}
|
|
60
|
+
let existsFromRelativePath = FileHandler.exists(FileHandler.joinPaths(
|
|
61
|
+
__dirname, '..', '..', '..', '@prisma', 'client', 'index.js'
|
|
62
|
+
));
|
|
63
|
+
return existsFromProjectRoot || existsFromRelativePath;
|
|
64
|
+
}
|
|
65
|
+
|
|
45
66
|
generateEntities()
|
|
46
67
|
{
|
|
47
68
|
if(!this.initialized){
|
|
@@ -52,6 +73,7 @@ class PrismaDataServer extends BaseDataServer
|
|
|
52
73
|
Logger.warning('Empty raw entities array, none entities generated.');
|
|
53
74
|
return {};
|
|
54
75
|
}
|
|
76
|
+
this.collectAllRelationMappings();
|
|
55
77
|
this.entities = {};
|
|
56
78
|
for(let i of Object.keys(this.rawEntities)){
|
|
57
79
|
let rawEntity = this.rawEntities[i];
|
|
@@ -68,13 +90,25 @@ class PrismaDataServer extends BaseDataServer
|
|
|
68
90
|
config: this.config,
|
|
69
91
|
prisma: this.prisma,
|
|
70
92
|
model: prismaModel,
|
|
71
|
-
server: this
|
|
93
|
+
server: this,
|
|
94
|
+
allRelationMappings: this.allRelationMappings
|
|
72
95
|
});
|
|
73
96
|
}
|
|
74
97
|
this.entityManager.setEntities(this.entities);
|
|
75
98
|
return this.entities;
|
|
76
99
|
}
|
|
77
100
|
|
|
101
|
+
collectAllRelationMappings()
|
|
102
|
+
{
|
|
103
|
+
for(let i of Object.keys(this.rawEntities)){
|
|
104
|
+
let rawEntity = this.rawEntities[i];
|
|
105
|
+
let tableName = rawEntity.tableName;
|
|
106
|
+
if(rawEntity.relationMappings){
|
|
107
|
+
this.allRelationMappings[tableName] = rawEntity.relationMappings;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
78
112
|
name()
|
|
79
113
|
{
|
|
80
114
|
return this.name || 'Prisma Data Server Driver';
|
|
@@ -103,7 +137,7 @@ class PrismaDataServer extends BaseDataServer
|
|
|
103
137
|
//Logger.error('Statement results length is zero.', results);
|
|
104
138
|
return false;
|
|
105
139
|
}
|
|
106
|
-
return 1 === results.length ? results
|
|
140
|
+
return 1 === results.length ? [...results].shift() : results;
|
|
107
141
|
} catch(error) {
|
|
108
142
|
Logger.error('Raw query failed: '+error.message);
|
|
109
143
|
return false;
|
|
@@ -126,7 +160,7 @@ class PrismaDataServer extends BaseDataServer
|
|
|
126
160
|
|| trimmedStatement.startsWith('ALTER')
|
|
127
161
|
|| trimmedStatement.startsWith('DROP')
|
|
128
162
|
){
|
|
129
|
-
return {
|
|
163
|
+
return {affectedRows: result};
|
|
130
164
|
}
|
|
131
165
|
return result;
|
|
132
166
|
}
|