@reldens/storage 0.117.0 → 0.118.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.
Files changed (78) hide show
  1. package/.claude/test-architecture.md +27 -17
  2. package/CLAUDE.md +67 -11
  3. package/README.md +204 -22
  4. package/bin/reldens-storage.js +43 -10
  5. package/index.js +53 -12
  6. package/lib/drizzle/drizzle-data-server.js +123 -0
  7. package/lib/drizzle/drizzle-driver.js +228 -0
  8. package/lib/drizzle/drizzle-models-generation.js +49 -0
  9. package/lib/drizzle/drizzle-modules-loader.js +32 -0
  10. package/lib/drizzle/drizzle-modules-validator.js +74 -0
  11. package/lib/entities-generator.js +36 -7
  12. package/lib/entity-templates/drizzle-model.template +28 -0
  13. package/lib/entity-templates/query-builder-model.template +17 -0
  14. package/lib/generators/base-generator.js +13 -0
  15. package/lib/generators/entities-generation.js +0 -13
  16. package/lib/generators/models-generation.js +179 -680
  17. package/lib/generators/relations-detection.js +171 -0
  18. package/lib/knex/knex-data-server.js +128 -0
  19. package/lib/knex/knex-driver.js +157 -0
  20. package/lib/knex/knex-modules-loader.js +28 -0
  21. package/lib/knex/knex-modules-validator.js +45 -0
  22. package/lib/kysely/kysely-data-server.js +128 -0
  23. package/lib/kysely/kysely-driver.js +176 -0
  24. package/lib/kysely/kysely-modules-loader.js +28 -0
  25. package/lib/kysely/kysely-modules-validator.js +53 -0
  26. package/lib/mikro-orm/mikro-orm-data-server.js +48 -29
  27. package/lib/mikro-orm/mikro-orm-driver.js +317 -108
  28. package/lib/mikro-orm/mikro-orm-models-generation.js +178 -0
  29. package/lib/mikro-orm/mikro-orm-modules-loader.js +50 -0
  30. package/lib/mikro-orm/mikro-orm-modules-validator.js +46 -0
  31. package/lib/mysql2-connection-config.js +38 -0
  32. package/lib/objection-js/objection-js-data-server.js +71 -125
  33. package/lib/objection-js/objection-js-driver.js +4 -1
  34. package/lib/objection-js/objection-js-models-generation.js +98 -0
  35. package/lib/objection-js/objection-modules-loader.js +28 -0
  36. package/lib/objection-js/objection-modules-validator.js +31 -0
  37. package/lib/package-resolver.js +45 -0
  38. package/lib/prisma/prisma-client-loader.js +11 -1
  39. package/lib/prisma/prisma-driver.js +16 -2
  40. package/lib/prisma/prisma-filter-processor.js +5 -6
  41. package/lib/prisma/prisma-models-generation.js +236 -0
  42. package/lib/prisma/prisma-schema-generator.js +5 -1
  43. package/lib/prisma/prisma-type-caster.js +14 -0
  44. package/lib/query-builder-driver.js +210 -0
  45. package/lib/query-builder-models-generation.js +80 -0
  46. package/lib/relations-loader.js +192 -0
  47. package/lib/type-mapper.js +38 -0
  48. package/package.json +7 -7
  49. package/tests/.env.test.example +6 -0
  50. package/tests/fixtures/categories-fixtures.js +8 -0
  51. package/tests/fixtures/expected-entities/entities/test-product-details-entity.js +10 -1
  52. package/tests/fixtures/expected-entities/entities-translations.js +2 -0
  53. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-categories-model.js +13 -8
  54. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-product-details-model.js +30 -10
  55. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-products-model.js +15 -14
  56. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-reviews-model.js +13 -12
  57. package/tests/fixtures/expected-entities-prisma/models/prisma/test-product-details-model.js +3 -1
  58. package/tests/fixtures/product-details-fixtures.js +4 -1
  59. package/tests/fixtures/reviews-fixtures.js +2 -1
  60. package/tests/fixtures/sql/test-schema.sql +11 -2
  61. package/tests/fixtures/table-columns-fixtures.js +97 -0
  62. package/tests/integration/test-cross-driver-equivalence.js +286 -0
  63. package/tests/integration/test-nested-filters.js +265 -408
  64. package/tests/integration/test-relations.js +135 -0
  65. package/tests/integration/test-reldens-sample-data.js +255 -0
  66. package/tests/integration/test-reldens-shape-relations.js +292 -0
  67. package/tests/run-tests.js +111 -65
  68. package/tests/unit/test-drivers.js +7 -1
  69. package/tests/unit/test-models-generation.js +28 -18
  70. package/tests/utils/column-value-assert.js +139 -0
  71. package/tests/utils/entity-projection.js +111 -0
  72. package/tests/utils/observed-value.js +41 -0
  73. package/tests/utils/projection-difference.js +151 -0
  74. package/tests/utils/relations-shape-support.js +104 -0
  75. package/tests/utils/reldens-schema-loader.js +274 -0
  76. package/tests/utils/test-helpers.js +143 -13
  77. package/tests/utils/test-runner.js +26 -5
  78. package/tests/utils/whole-result-assert.js +243 -0
@@ -1,680 +1,179 @@
1
- /**
2
- *
3
- * Reldens - ModelsGeneration
4
- *
5
- */
6
-
7
- const { FileHandler } = require('@reldens/server-utils');
8
- const { Logger, sc } = require('@reldens/utils');
9
- const { TypeMapper } = require('../type-mapper');
10
- const { BaseGenerator } = require('./base-generator');
11
- const { RELATION_PREFIX } = require('../relation-key');
12
-
13
- class ModelsGeneration extends BaseGenerator
14
- {
15
-
16
- constructor(props)
17
- {
18
- super();
19
- this.modelsFolder = sc.get(props, 'modelsFolder', '');
20
- this.templates = sc.get(props, 'templates', {});
21
- this.generatedModels = {};
22
- this.allTablesData = {};
23
- this.removeIdFromMultipleRelations = sc.get(props, 'removeIdFromMultipleRelations', true);
24
- this.mikroOrmRelationKindMap = {
25
- 'BelongsToOneRelation': 'm:1',
26
- 'HasOneRelation': '1:1',
27
- 'HasManyRelation': '1:m',
28
- 'ManyToManyRelation': 'm:n'
29
- };
30
- }
31
-
32
- async generateModelFile(tableName, tableData, driverKey, entityInfo, relationMetadata = {})
33
- {
34
- let modelTemplatePath = this.templates[driverKey];
35
- if(!FileHandler.exists(modelTemplatePath)){
36
- Logger.critical('Model template file "'+modelTemplatePath+'" not found for driver "'+driverKey+'".');
37
- return false;
38
- }
39
- let modelTemplateContent = FileHandler.fetchFileContents(modelTemplatePath);
40
- if(!modelTemplateContent){
41
- Logger.critical('Failed to read model template file: '+modelTemplatePath);
42
- return false;
43
- }
44
- let modelClassName = sc.capitalizedCamelCase(tableName)+'Model';
45
- let modelPropertiesList = Object.keys(tableData.columns).join(', ');
46
- let modelPropertiesConstructor = Object.keys(tableData.columns)
47
- .map(columnName => 'this.'+columnName+' = '+columnName+';')
48
- .join('\n ');
49
- let modelRelations = this.generateModelRelations(tableName, tableData, relationMetadata, driverKey);
50
- let entityPropertiesDefinition = this.getEntityPropertiesDefinition(tableData.columns, driverKey, tableName, tableData);
51
- let fkMappingsJson = this.generateFkMappings(tableName, tableData, driverKey);
52
- let fkMappingsAttachment = '';
53
- if('mikro-orm' === driverKey && fkMappingsJson){
54
- fkMappingsAttachment = 'schema._fkMappings = '+fkMappingsJson+';';
55
- }
56
- let idColumn = this.generateIdColumn(tableData.columns, driverKey);
57
- let replacements = {
58
- modelClassName,
59
- tableName: 'prisma' === driverKey ? tableName.toLowerCase() : tableName,
60
- modelPropertiesList,
61
- modelPropertiesConstructor,
62
- modelRelations,
63
- entityPropertiesDefinition,
64
- fkMappingsAttachment,
65
- idColumn
66
- };
67
- let modelContent = this.applyReplacements(modelTemplateContent, replacements);
68
- let fileName = sc.kebabCase(tableName)+'-model.js';
69
- let driverFolder = FileHandler.joinPaths(this.modelsFolder, driverKey);
70
- FileHandler.createFolder(driverFolder);
71
- let filePath = FileHandler.joinPaths(driverFolder, fileName);
72
- if(!FileHandler.writeFile(filePath, modelContent)){
73
- Logger.critical('Failed to write model file: '+fileName);
74
- return false;
75
- }
76
- Logger.info('Generated model file: '+fileName);
77
- this.generatedModels[tableName] = {
78
- modelClassName,
79
- modelFileName: fileName,
80
- driverKey
81
- };
82
- return true;
83
- }
84
-
85
- generateIdColumn(columns, driverKey)
86
- {
87
- if('objection-js' !== driverKey && 'prisma' !== driverKey){
88
- return '';
89
- }
90
- let primaryKeyColumn = this.detectPrimaryKeyColumn(columns);
91
- if(!primaryKeyColumn || 'id' === primaryKeyColumn){
92
- return '';
93
- }
94
- return '\n static get idColumn()\n {\n return \''+primaryKeyColumn+'\';\n }\n';
95
- }
96
-
97
- detectPrimaryKeyColumn(columns)
98
- {
99
- for(let columnName of Object.keys(columns)){
100
- let column = columns[columnName];
101
- if('PRI' === column.key){
102
- return columnName;
103
- }
104
- }
105
- return null;
106
- }
107
-
108
- setAllTablesData(allTablesData)
109
- {
110
- this.allTablesData = allTablesData;
111
- }
112
-
113
- generateModelRelations(tableName, tableData, relationMetadata, driverKey)
114
- {
115
- if('prisma' === driverKey){
116
- return this.generatePrismaRelations(tableName, tableData, relationMetadata);
117
- }
118
- if('objection-js' === driverKey){
119
- return this.generateObjectionJsRelations(tableName, tableData);
120
- }
121
- return '';
122
- }
123
-
124
- generatePrismaRelations(tableName, tableData, relationMetadata)
125
- {
126
- if(!relationMetadata || 0 === Object.keys(relationMetadata).length){
127
- return '';
128
- }
129
- let relationTypesArray = [];
130
- let relationMappingsArray = [];
131
- let forwardMappings = this.buildPrismaForwardMappings(tableData, relationMetadata);
132
- let reverseMappings = this.buildPrismaReverseMappings(tableName, relationMetadata);
133
- let allMappings = Object.assign({}, forwardMappings, reverseMappings);
134
- for(let prismaRelationName of Object.keys(relationMetadata)){
135
- let relation = relationMetadata[prismaRelationName];
136
- relationTypesArray.push(' '+prismaRelationName+': \''+relation.type+'\'');
137
- }
138
- for(let reldensKey of Object.keys(allMappings)){
139
- let prismaName = allMappings[reldensKey];
140
- relationMappingsArray.push(' \''+reldensKey+'\': \''+prismaName+'\'');
141
- }
142
- let result = '';
143
- if(0 < relationTypesArray.length){
144
- result += '\n\n static get relationTypes()\n {\n return {\n';
145
- result += relationTypesArray.join(',\n');
146
- result += '\n };\n }';
147
- }
148
- if(0 < relationMappingsArray.length){
149
- result += '\n\n static get relationMappings()\n {\n return {\n';
150
- result += relationMappingsArray.join(',\n');
151
- result += '\n };\n }';
152
- }
153
- return result;
154
- }
155
-
156
- buildPrismaForwardMappings(tableData, relationMetadata)
157
- {
158
- let mappings = {};
159
- let referenceCounts = this.countReferencesPerTable(tableData);
160
- for(let columnName of Object.keys(tableData.columns)){
161
- let column = tableData.columns[columnName];
162
- if(!sc.hasOwn(column, 'referencedTable')){
163
- continue;
164
- }
165
- let reldensKey = this.generateForwardRelationKey(
166
- column.referencedTable,
167
- columnName,
168
- referenceCounts
169
- );
170
- let prismaName = this.findPrismaRelationForFk(columnName, column.referencedTable, relationMetadata);
171
- if(!prismaName){
172
- continue;
173
- }
174
- mappings[reldensKey] = prismaName;
175
- }
176
- return mappings;
177
- }
178
-
179
- findPrismaRelationForFk(columnName, referencedTable, relationMetadata)
180
- {
181
- let lowerColumn = columnName.toLowerCase();
182
- let lowerTable = referencedTable.toLowerCase();
183
- for(let prismaName of Object.keys(relationMetadata)){
184
- let relation = relationMetadata[prismaName];
185
- if(relation.model.toLowerCase() !== lowerTable){
186
- continue;
187
- }
188
- if(relation.isList){
189
- continue;
190
- }
191
- let lowerPrisma = prismaName.toLowerCase();
192
- if(lowerPrisma.includes(lowerColumn.replace('_id', ''))){
193
- return prismaName;
194
- }
195
- if(lowerPrisma.includes(lowerColumn)){
196
- return prismaName;
197
- }
198
- }
199
- for(let prismaName of Object.keys(relationMetadata)){
200
- let relation = relationMetadata[prismaName];
201
- if(relation.model.toLowerCase() !== lowerTable){
202
- continue;
203
- }
204
- if(relation.isList){
205
- continue;
206
- }
207
- return prismaName;
208
- }
209
- return null;
210
- }
211
-
212
- buildPrismaReverseMappings(tableName, relationMetadata)
213
- {
214
- let mappings = {};
215
- let reverseRelations = {};
216
- for(let prismaName of Object.keys(relationMetadata)){
217
- let relation = relationMetadata[prismaName];
218
- if(this.isForwardRelation(relation)){
219
- continue;
220
- }
221
- let relatedTable = relation.model.toLowerCase();
222
- if(!reverseRelations[relatedTable]){
223
- reverseRelations[relatedTable] = [];
224
- }
225
- reverseRelations[relatedTable].push({prismaName, relation});
226
- }
227
- for(let relatedTable of Object.keys(reverseRelations)){
228
- let relations = reverseRelations[relatedTable];
229
- let referenceCount = relations.length;
230
- for(let relInfo of relations){
231
- let reldensKey = this.generatePrismaReverseRelationKey(
232
- relatedTable,
233
- relInfo.prismaName,
234
- tableName,
235
- referenceCount
236
- );
237
- mappings[reldensKey] = relInfo.prismaName;
238
- }
239
- }
240
- return mappings;
241
- }
242
-
243
- isForwardRelation(relation)
244
- {
245
- return sc.hasOwn(relation, 'foreignKeys')
246
- && sc.isArray(relation.foreignKeys)
247
- && 0 < relation.foreignKeys.length;
248
- }
249
-
250
- generatePrismaReverseRelationKey(relatedTable, prismaName, tableName, referenceCount)
251
- {
252
- if(1 === referenceCount){
253
- return RELATION_PREFIX+relatedTable;
254
- }
255
- let columnHint = this.extractColumnHintFromPrismaName(prismaName, relatedTable, tableName);
256
- if(columnHint){
257
- return RELATION_PREFIX+relatedTable+'_'+columnHint;
258
- }
259
- return RELATION_PREFIX+relatedTable;
260
- }
261
-
262
- extractColumnHintFromPrismaName(prismaName, relatedTable, tableName)
263
- {
264
- let lowerName = prismaName.toLowerCase();
265
- let lowerTable = tableName.toLowerCase();
266
- let lowerRelated = relatedTable.toLowerCase();
267
- let toSuffix = 'to'+lowerTable;
268
- if(!lowerName.endsWith(toSuffix)){
269
- toSuffix = 'to'+lowerRelated;
270
- if(!lowerName.endsWith(toSuffix)){
271
- return null;
272
- }
273
- }
274
- let withoutSuffix = prismaName.slice(0, -toSuffix.length);
275
- let lowerWithout = withoutSuffix.toLowerCase();
276
- let prefixes = [
277
- lowerRelated+'_'+lowerRelated+'_',
278
- lowerRelated+'_',
279
- lowerTable+'_'
280
- ];
281
- for(let prefix of prefixes){
282
- if(lowerWithout.startsWith(prefix)){
283
- withoutSuffix = withoutSuffix.slice(prefix.length);
284
- lowerWithout = withoutSuffix.toLowerCase();
285
- }
286
- }
287
- if(withoutSuffix.endsWith('_id')){
288
- withoutSuffix = withoutSuffix.slice(0, -3);
289
- }
290
- return withoutSuffix.toLowerCase();
291
- }
292
-
293
- generateObjectionJsRelations(tableName, tableData)
294
- {
295
- let relations = this.detectObjectionJsRelations(tableName, tableData);
296
- let reverseRelations = this.detectReverseObjectionJsRelations(tableName);
297
- let allRelations = Object.assign({}, relations, reverseRelations);
298
- if(0 === Object.keys(allRelations).length){
299
- return '';
300
- }
301
- let relationMappings = [];
302
- let requiredModels = [];
303
- for(let relationKey of Object.keys(allRelations)){
304
- let relation = allRelations[relationKey];
305
- let tableReference = relation.referencedTable || relation.referencingTable;
306
- let relatedModelClass = sc.capitalizedCamelCase(tableReference)+'Model';
307
- if(!requiredModels.find(m => m.modelClass === relatedModelClass)){
308
- requiredModels.push({
309
- modelClass: relatedModelClass,
310
- fileName: sc.kebabCase(tableReference)+'-model'
311
- });
312
- }
313
- let relationMapping = ' '+relationKey+': {\n';
314
- relationMapping += ' relation: this.'+relation.relationType+',\n';
315
- relationMapping += ' modelClass: '+relatedModelClass+',\n';
316
- relationMapping += ' join: {\n';
317
- relationMapping += ' from: this.tableName+\'.'+relation.fromColumn+'\',\n';
318
- relationMapping += ' to: '+relatedModelClass+'.tableName+\'.'+relation.toColumn+'\'\n';
319
- relationMapping += ' }\n';
320
- relationMapping += ' }';
321
- relationMappings.push(relationMapping);
322
- }
323
- if(0 === relationMappings.length){
324
- return '';
325
- }
326
- let requireStatements = [];
327
- for(let modelInfo of requiredModels){
328
- requireStatements.push(' const { '+modelInfo.modelClass+' } = require(\'./'+modelInfo.fileName+'\');');
329
- }
330
- let relationMappingsMethod = '\n static get relationMappings()\n {\n';
331
- relationMappingsMethod += requireStatements.join('\n')+'\n';
332
- relationMappingsMethod += ' return {\n';
333
- relationMappingsMethod += relationMappings.join(',\n');
334
- relationMappingsMethod += '\n };\n';
335
- relationMappingsMethod += ' }';
336
- return relationMappingsMethod;
337
- }
338
-
339
- detectObjectionJsRelations(tableName, tableData)
340
- {
341
- let relations = {};
342
- let referenceCounts = this.countReferencesPerTable(tableData);
343
- for(let columnName of Object.keys(tableData.columns)){
344
- let column = tableData.columns[columnName];
345
- if(!sc.hasOwn(column, 'referencedTable')){
346
- continue;
347
- }
348
- let relationKey = this.generateForwardRelationKey(column.referencedTable, columnName, referenceCounts);
349
- let relationType = this.determineRelationType(column, true);
350
- relations[relationKey] = {
351
- fromColumn: columnName,
352
- toColumn: column.referencedColumn,
353
- referencedTable: column.referencedTable,
354
- relationType: relationType
355
- };
356
- }
357
- return relations;
358
- }
359
-
360
- detectReverseObjectionJsRelations(tableName)
361
- {
362
- let reverseRelations = {};
363
- for(let otherTableName of Object.keys(this.allTablesData)){
364
- if(otherTableName === tableName){
365
- continue;
366
- }
367
- let otherTableData = this.allTablesData[otherTableName];
368
- let referenceCounts = this.countReferencesToTable(tableName, otherTableData);
369
- for(let columnName of Object.keys(otherTableData.columns)){
370
- let column = otherTableData.columns[columnName];
371
- if(!sc.hasOwn(column, 'referencedTable')){
372
- continue;
373
- }
374
- if(column.referencedTable !== tableName){
375
- continue;
376
- }
377
- let relationKey = this.generateReverseRelationKey(otherTableName, columnName, referenceCounts);
378
- let relationType = this.determineRelationType(column, false);
379
- reverseRelations[relationKey] = {
380
- fromColumn: column.referencedColumn,
381
- toColumn: columnName,
382
- referencingTable: otherTableName,
383
- relationType: relationType
384
- };
385
- }
386
- }
387
- return reverseRelations;
388
- }
389
-
390
- countReferencesPerTable(tableData)
391
- {
392
- let counts = {};
393
- for(let columnName of Object.keys(tableData.columns)){
394
- let column = tableData.columns[columnName];
395
- if(!sc.hasOwn(column, 'referencedTable')){
396
- continue;
397
- }
398
- if(!counts[column.referencedTable]){
399
- counts[column.referencedTable] = 0;
400
- }
401
- counts[column.referencedTable]++;
402
- }
403
- return counts;
404
- }
405
-
406
- countReferencesToTable(targetTable, sourceTableData)
407
- {
408
- let count = 0;
409
- for(let columnName of Object.keys(sourceTableData.columns)){
410
- let column = sourceTableData.columns[columnName];
411
- if(!sc.hasOwn(column, 'referencedTable')){
412
- continue;
413
- }
414
- if(column.referencedTable === targetTable){
415
- count++;
416
- }
417
- }
418
- return count;
419
- }
420
-
421
- generateForwardRelationKey(referencedTable, columnName, referenceCounts)
422
- {
423
- if(1 === referenceCounts[referencedTable]){
424
- return RELATION_PREFIX+referencedTable;
425
- }
426
- let columnSuffix = columnName;
427
- if(this.removeIdFromMultipleRelations){
428
- columnSuffix = columnName.replace(/_id$/i, '');
429
- }
430
- return RELATION_PREFIX+referencedTable+'_'+columnSuffix;
431
- }
432
-
433
- generateReverseRelationKey(referencingTable, columnName, referenceCount)
434
- {
435
- if(1 === referenceCount){
436
- return RELATION_PREFIX+referencingTable;
437
- }
438
- let columnSuffix = columnName;
439
- if(this.removeIdFromMultipleRelations){
440
- columnSuffix = columnName.replace(/_id$/i, '');
441
- }
442
- return RELATION_PREFIX+referencingTable+'_'+columnSuffix;
443
- }
444
-
445
- determineMikroOrmForwardKind(column)
446
- {
447
- if('HasOneRelation' === this.determineRelationType(column, false)){
448
- return this.mikroOrmRelationKindMap.HasOneRelation;
449
- }
450
- return this.mikroOrmRelationKindMap.BelongsToOneRelation;
451
- }
452
-
453
- determineRelationType(column, isForwardRelation)
454
- {
455
- if(isForwardRelation){
456
- return 'BelongsToOneRelation';
457
- }
458
- if('PRI' === column.key || 'UNI' === column.key){
459
- return 'HasOneRelation';
460
- }
461
- return 'HasManyRelation';
462
- }
463
-
464
- getEntityPropertiesDefinition(columns, driverKey, tableName = null, tableData = null)
465
- {
466
- if('mikro-orm' === driverKey){
467
- let entityProps = [];
468
- if(!tableName || !tableData){
469
- for(let columnName of Object.keys(columns)){
470
- let column = columns[columnName];
471
- let isPrimary = 'PRI' === column.key;
472
- let type = TypeMapper.mapDbTypeToJsType(column.type);
473
- let hasDefault = null !== column.default && undefined !== column.default;
474
- let propDef = columnName+': { type: \''+type+'\'';
475
- if(isPrimary){
476
- propDef += ', primary: true';
477
- }
478
- if(column.nullable || hasDefault){
479
- propDef += ', nullable: true';
480
- }
481
- propDef += ' }';
482
- entityProps.push(propDef);
483
- }
484
- return entityProps.join(',\n ');
485
- }
486
- let relations = this.detectObjectionJsRelations(tableName, tableData);
487
- let fkColumnsUsedByRelations = {};
488
- for(let relationKey of Object.keys(relations)){
489
- let relation = relations[relationKey];
490
- if(relation.fromColumn){
491
- fkColumnsUsedByRelations[relation.fromColumn] = true;
492
- }
493
- }
494
- for(let columnName of Object.keys(columns)){
495
- let column = columns[columnName];
496
- if(fkColumnsUsedByRelations[columnName]){
497
- let type = TypeMapper.mapDbTypeToJsType(column.type);
498
- let propDef = columnName+': { type: \''+type+'\', persist: false }';
499
- entityProps.push(propDef);
500
- continue;
501
- }
502
- let isPrimary = 'PRI' === column.key;
503
- let type = TypeMapper.mapDbTypeToJsType(column.type);
504
- let hasDefault = null !== column.default && undefined !== column.default;
505
- let propDef = columnName+': { type: \''+type+'\'';
506
- if(isPrimary){
507
- propDef += ', primary: true';
508
- }
509
- if(column.nullable || hasDefault){
510
- propDef += ', nullable: true';
511
- }
512
- propDef += ' }';
513
- entityProps.push(propDef);
514
- }
515
- for(let relationKey of Object.keys(relations)){
516
- let relation = relations[relationKey];
517
- let relatedModelClass = sc.capitalizedCamelCase(relation.referencedTable)+'Model';
518
- let relatedModelFile = sc.kebabCase(relation.referencedTable)+'-model';
519
- let kind = this.determineMikroOrmForwardKind(columns[relation.fromColumn]);
520
- let propDef = relationKey+': {\n';
521
- propDef += ' kind: \''+kind+'\',\n';
522
- propDef += ' entity: () => require(\'./'+relatedModelFile+'\').'+relatedModelClass;
523
- if(relation.fromColumn){
524
- propDef += ',\n joinColumns: [\''+relation.fromColumn+'\']';
525
- }
526
- propDef += '\n }';
527
- entityProps.push(propDef);
528
- }
529
- let reverseRelations = this.detectReverseObjectionJsRelations(tableName);
530
- for(let relationKey of Object.keys(reverseRelations)){
531
- let relation = reverseRelations[relationKey];
532
- let relatedModelClass = sc.capitalizedCamelCase(relation.referencingTable)+'Model';
533
- let relatedModelFile = sc.kebabCase(relation.referencingTable)+'-model';
534
- let kind = sc.get(this.mikroOrmRelationKindMap, relation.relationType, '1:m');
535
- let mappedBy = this.findMappedByKey(tableName, relation.referencingTable, relation);
536
- let propDef = relationKey+': {\n';
537
- propDef += ' kind: \''+kind+'\',\n';
538
- propDef += ' entity: () => require(\'./'+relatedModelFile+'\').'+relatedModelClass;
539
- if(mappedBy){
540
- propDef += ',\n mappedBy: \''+mappedBy+'\'';
541
- }
542
- propDef += '\n }';
543
- entityProps.push(propDef);
544
- }
545
- return entityProps.join(',\n ');
546
- }
547
- if('prisma' === driverKey){
548
- let prismaProps = [];
549
- for(let columnName of Object.keys(columns)){
550
- let column = columns[columnName];
551
- let prismaType = TypeMapper.mapDbTypeToPrismaType(column.type);
552
- let isPrimary = 'PRI' === column.key;
553
- let propDef = columnName+': {\n type: \''+prismaType+'\'';
554
- if(isPrimary){
555
- propDef += ',\n id: true';
556
- }
557
- if(column.nullable){
558
- propDef += ',\n optional: true';
559
- }
560
- propDef += '\n }';
561
- prismaProps.push(propDef);
562
- }
563
- return prismaProps.join(',\n ');
564
- }
565
- return '';
566
- }
567
-
568
- findMappedByKey(currentTable, referencingTable, reverseRelation)
569
- {
570
- if(!this.allTablesData[referencingTable]){
571
- return RELATION_PREFIX+currentTable;
572
- }
573
- let referencingTableData = this.allTablesData[referencingTable];
574
- let forwardRelations = this.detectObjectionJsRelations(referencingTable, referencingTableData);
575
- for(let forwardKey of Object.keys(forwardRelations)){
576
- let forward = forwardRelations[forwardKey];
577
- if(forward.referencedTable === currentTable && forward.toColumn === reverseRelation.fromColumn){
578
- return forwardKey;
579
- }
580
- }
581
- return RELATION_PREFIX+currentTable;
582
- }
583
-
584
- generateFkMappings(tableName, tableData, driverKey)
585
- {
586
- if('mikro-orm' !== driverKey){
587
- return '';
588
- }
589
- if(!tableName || !tableData){
590
- return '';
591
- }
592
- let fkMappings = {};
593
- let referenceCounts = this.countReferencesPerTable(tableData);
594
- for(let columnName of Object.keys(tableData.columns)){
595
- let column = tableData.columns[columnName];
596
- if(!sc.hasOwn(column, 'referencedTable')){
597
- continue;
598
- }
599
- let relationKey = this.generateForwardRelationKey(column.referencedTable, columnName, referenceCounts);
600
- let relatedModelClass = sc.capitalizedCamelCase(column.referencedTable)+'Model';
601
- fkMappings[columnName] = {
602
- relationKey: relationKey,
603
- entityName: relatedModelClass,
604
- referencedColumn: column.referencedColumn,
605
- nullable: column.nullable || false
606
- };
607
- }
608
- if(0 === Object.keys(fkMappings).length){
609
- return '';
610
- }
611
- return JSON.stringify(fkMappings, null, 4);
612
- }
613
-
614
- generateRegisteredModelsFile(entitiesInfo, existingModels, driverKey, templatePath)
615
- {
616
- if(!FileHandler.exists(templatePath)){
617
- Logger.critical('Registered models template file not found: '+templatePath);
618
- return false;
619
- }
620
- let registeredTemplateContent = FileHandler.fetchFileContents(templatePath);
621
- if(!registeredTemplateContent){
622
- Logger.critical('Failed to read registered models template file: '+templatePath);
623
- return false;
624
- }
625
- let allEntities = Object.assign({}, entitiesInfo);
626
- let replacements = {
627
- registeredModels: this.getRegisteredModels(allEntities, existingModels, driverKey),
628
- registeredEntitiesObject: this.getRegisteredEntitiesObject(allEntities, existingModels, driverKey)
629
- };
630
- let registeredContent = this.applyReplacements(registeredTemplateContent, replacements);
631
- let driverFolder = FileHandler.joinPaths(this.modelsFolder, driverKey);
632
- let filePath = FileHandler.joinPaths(driverFolder, 'registered-models-'+driverKey+'.js');
633
- if(!FileHandler.writeFile(filePath, registeredContent)){
634
- Logger.critical('Failed to write registered models file: '+filePath);
635
- return false;
636
- }
637
- Logger.info('Generated registered models file: '+filePath);
638
- return true;
639
- }
640
-
641
- getRegisteredModels(allEntities, existingModels, driverKey)
642
- {
643
- let registeredModels = [];
644
- for(let tableName of Object.keys(allEntities)){
645
- let entity = allEntities[tableName];
646
- if(entity.driverKey && driverKey !== entity.driverKey){
647
- if(!existingModels[tableName] || !existingModels[tableName][driverKey]){
648
- continue;
649
- }
650
- }
651
- let modelClassName = entity.modelClassName || sc.capitalizedCamelCase(tableName)+'Model';
652
- let modelFileName = entity.modelFileName || sc.kebabCase(tableName)+'-model.js';
653
- let variableName = 'mikro-orm' !== driverKey ? '{ '+modelClassName+' }' : sc.camelCase(tableName)+'Model';
654
- registeredModels.push(
655
- 'const '+variableName+' = require(\'./'+modelFileName.replace('.js', '')+'\');'
656
- );
657
- }
658
- return registeredModels.join('\n');
659
- }
660
-
661
- getRegisteredEntitiesObject(allEntities, existingModels, driverKey)
662
- {
663
- let registeredEntitiesObject = [];
664
- for(let tableName of Object.keys(allEntities)){
665
- let entity = allEntities[tableName];
666
- if(entity.driverKey && driverKey !== entity.driverKey){
667
- if(!existingModels[tableName] || !existingModels[tableName][driverKey]){
668
- continue;
669
- }
670
- }
671
- let modelClassName = entity.modelClassName || sc.capitalizedCamelCase(tableName)+'Model';
672
- let variableName = 'mikro-orm' === driverKey ? sc.camelCase(tableName)+'Model' : modelClassName;
673
- registeredEntitiesObject.push(sc.camelCase(tableName)+': '+variableName);
674
- }
675
- return registeredEntitiesObject.join(',\n ');
676
- }
677
-
678
- }
679
-
680
- module.exports.ModelsGeneration = ModelsGeneration;
1
+ /**
2
+ *
3
+ * Reldens - ModelsGeneration
4
+ *
5
+ */
6
+
7
+ const { FileHandler } = require('@reldens/server-utils');
8
+ const { Logger, sc } = require('@reldens/utils');
9
+ const { BaseGenerator } = require('./base-generator');
10
+ const { RelationsDetection } = require('./relations-detection');
11
+ const { ObjectionJsModelsGeneration } = require('../objection-js/objection-js-models-generation');
12
+ const { MikroOrmModelsGeneration } = require('../mikro-orm/mikro-orm-models-generation');
13
+ const { PrismaModelsGeneration } = require('../prisma/prisma-models-generation');
14
+ const { QueryBuilderModelsGeneration } = require('../query-builder-models-generation');
15
+ const { DrizzleModelsGeneration } = require('../drizzle/drizzle-models-generation');
16
+
17
+ class ModelsGeneration extends BaseGenerator
18
+ {
19
+
20
+ constructor(props)
21
+ {
22
+ super();
23
+ this.modelsFolder = sc.get(props, 'modelsFolder', '');
24
+ this.templates = sc.get(props, 'templates', {});
25
+ this.generatedModels = {};
26
+ this.relationsDetection = new RelationsDetection(props);
27
+ this.driversGeneration = {
28
+ 'objection-js': new ObjectionJsModelsGeneration(this.relationsDetection),
29
+ 'mikro-orm': new MikroOrmModelsGeneration(this.relationsDetection),
30
+ 'prisma': new PrismaModelsGeneration(this.relationsDetection),
31
+ 'knex': new QueryBuilderModelsGeneration(this.relationsDetection),
32
+ 'kysely': new QueryBuilderModelsGeneration(this.relationsDetection),
33
+ 'drizzle': new DrizzleModelsGeneration(this.relationsDetection)
34
+ };
35
+ }
36
+
37
+ driverGeneration(driverKey)
38
+ {
39
+ if(!sc.hasOwn(this.driversGeneration, driverKey)){
40
+ Logger.critical('Models generation not available for driver "'+driverKey+'".');
41
+ return false;
42
+ }
43
+ return this.driversGeneration[driverKey];
44
+ }
45
+
46
+ async generateModelFile(tableName, tableData, driverKey, entityInfo, relationMetadata = {})
47
+ {
48
+ let driverGeneration = this.driverGeneration(driverKey);
49
+ if(!driverGeneration){
50
+ return false;
51
+ }
52
+ let modelTemplatePath = this.templates[driverKey];
53
+ if(!FileHandler.exists(modelTemplatePath)){
54
+ Logger.critical('Model template file "'+modelTemplatePath+'" not found for driver "'+driverKey+'".');
55
+ return false;
56
+ }
57
+ let modelTemplateContent = FileHandler.fetchFileContents(modelTemplatePath);
58
+ if(!modelTemplateContent){
59
+ Logger.critical('Failed to read model template file: '+modelTemplatePath);
60
+ return false;
61
+ }
62
+ let modelContent = this.applyReplacements(
63
+ modelTemplateContent,
64
+ this.modelReplacements(tableName, tableData, driverGeneration, relationMetadata)
65
+ );
66
+ return this.writeModelFile(tableName, driverKey, modelContent);
67
+ }
68
+
69
+ modelReplacements(tableName, tableData, driverGeneration, relationMetadata)
70
+ {
71
+ let fkMappingsJson = driverGeneration.generateFkMappings(tableName, tableData);
72
+ let fkMappingsAttachment = fkMappingsJson ? 'schema._fkMappings = '+fkMappingsJson+';' : '';
73
+ return {
74
+ modelClassName: sc.capitalizedCamelCase(tableName)+'Model',
75
+ tableName: driverGeneration.modelTableName(tableName),
76
+ modelPropertiesList: Object.keys(tableData.columns).join(', '),
77
+ modelPropertiesConstructor: Object.keys(tableData.columns)
78
+ .map(columnName => 'this.'+columnName+' = '+columnName+';')
79
+ .join('\n '),
80
+ modelRelations: driverGeneration.generateRelations(tableName, tableData, relationMetadata),
81
+ entityPropertiesDefinition: driverGeneration.generateEntityProperties(
82
+ tableData.columns,
83
+ tableName,
84
+ tableData
85
+ ),
86
+ fkMappingsAttachment,
87
+ idColumn: driverGeneration.generateIdColumn(tableData.columns)
88
+ };
89
+ }
90
+
91
+ writeModelFile(tableName, driverKey, modelContent)
92
+ {
93
+ let fileName = sc.kebabCase(tableName)+'-model.js';
94
+ let driverFolder = FileHandler.joinPaths(this.modelsFolder, driverKey);
95
+ FileHandler.createFolder(driverFolder);
96
+ if(!FileHandler.writeFile(FileHandler.joinPaths(driverFolder, fileName), modelContent)){
97
+ Logger.critical('Failed to write model file: '+fileName);
98
+ return false;
99
+ }
100
+ Logger.info('Generated model file: '+fileName);
101
+ this.generatedModels[tableName] = {
102
+ modelClassName: sc.capitalizedCamelCase(tableName)+'Model',
103
+ modelFileName: fileName,
104
+ driverKey
105
+ };
106
+ return true;
107
+ }
108
+
109
+ generateRegisteredModelsFile(entitiesInfo, existingModels, driverKey, templatePath)
110
+ {
111
+ if(!FileHandler.exists(templatePath)){
112
+ Logger.critical('Registered models template file not found: '+templatePath);
113
+ return false;
114
+ }
115
+ let registeredTemplateContent = FileHandler.fetchFileContents(templatePath);
116
+ if(!registeredTemplateContent){
117
+ Logger.critical('Failed to read registered models template file: '+templatePath);
118
+ return false;
119
+ }
120
+ let allEntities = Object.assign({}, entitiesInfo);
121
+ let replacements = {
122
+ registeredModels: this.getRegisteredModels(allEntities, existingModels, driverKey),
123
+ registeredEntitiesObject: this.getRegisteredEntitiesObject(allEntities, existingModels, driverKey)
124
+ };
125
+ let registeredContent = this.applyReplacements(registeredTemplateContent, replacements);
126
+ let driverFolder = FileHandler.joinPaths(this.modelsFolder, driverKey);
127
+ let filePath = FileHandler.joinPaths(driverFolder, 'registered-models-'+driverKey+'.js');
128
+ if(!FileHandler.writeFile(filePath, registeredContent)){
129
+ Logger.critical('Failed to write registered models file: '+filePath);
130
+ return false;
131
+ }
132
+ Logger.info('Generated registered models file: '+filePath);
133
+ return true;
134
+ }
135
+
136
+ isRegisteredEntity(entity, existingModels, tableName, driverKey)
137
+ {
138
+ if(!entity.driverKey || driverKey === entity.driverKey){
139
+ return true;
140
+ }
141
+ return Boolean(existingModels[tableName] && existingModels[tableName][driverKey]);
142
+ }
143
+
144
+ getRegisteredModels(allEntities, existingModels, driverKey)
145
+ {
146
+ let registeredModels = [];
147
+ for(let tableName of Object.keys(allEntities)){
148
+ let entity = allEntities[tableName];
149
+ if(!this.isRegisteredEntity(entity, existingModels, tableName, driverKey)){
150
+ continue;
151
+ }
152
+ let modelClassName = sc.get(entity, 'modelClassName', sc.capitalizedCamelCase(tableName)+'Model');
153
+ let modelFileName = sc.get(entity, 'modelFileName', sc.kebabCase(tableName)+'-model.js');
154
+ let variableName = 'mikro-orm' !== driverKey ? '{ '+modelClassName+' }' : sc.camelCase(tableName)+'Model';
155
+ registeredModels.push(
156
+ 'const '+variableName+' = require(\'./'+modelFileName.replace('.js', '')+'\');'
157
+ );
158
+ }
159
+ return registeredModels.join('\n');
160
+ }
161
+
162
+ getRegisteredEntitiesObject(allEntities, existingModels, driverKey)
163
+ {
164
+ let registeredEntitiesObject = [];
165
+ for(let tableName of Object.keys(allEntities)){
166
+ let entity = allEntities[tableName];
167
+ if(!this.isRegisteredEntity(entity, existingModels, tableName, driverKey)){
168
+ continue;
169
+ }
170
+ let modelClassName = sc.get(entity, 'modelClassName', sc.capitalizedCamelCase(tableName)+'Model');
171
+ let variableName = 'mikro-orm' === driverKey ? sc.camelCase(tableName)+'Model' : modelClassName;
172
+ registeredEntitiesObject.push(sc.camelCase(tableName)+': '+variableName);
173
+ }
174
+ return registeredEntitiesObject.join(',\n ');
175
+ }
176
+
177
+ }
178
+
179
+ module.exports.ModelsGeneration = ModelsGeneration;