@reldens/storage 0.116.0 → 0.117.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 (22) hide show
  1. package/lib/generators/models-generation.js +7 -20
  2. package/lib/mikro-orm/mikro-orm-driver.js +71 -16
  3. package/package.json +2 -2
  4. package/tests/fixtures/expected-entities/entities/test-product-details-entity.js +63 -0
  5. package/tests/fixtures/expected-entities/entities-config.js +6 -4
  6. package/tests/fixtures/expected-entities/entities-translations.js +23 -14
  7. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/registered-models-mikro-orm.js +2 -0
  8. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-product-details-model.js +60 -0
  9. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-products-model.js +5 -0
  10. package/tests/fixtures/expected-entities-objection-js/models/objection-js/registered-models-objection-js.js +2 -0
  11. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-product-details-model.js +33 -0
  12. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-products-model.js +9 -0
  13. package/tests/fixtures/expected-entities-prisma/models/prisma/registered-models-prisma.js +2 -0
  14. package/tests/fixtures/expected-entities-prisma/models/prisma/test-product-details-model.js +41 -0
  15. package/tests/fixtures/expected-entities-prisma/models/prisma/test-products-model.js +2 -0
  16. package/tests/fixtures/product-details-fixtures.js +19 -0
  17. package/tests/fixtures/sql/test-schema.sql +13 -0
  18. package/tests/integration/test-relations.js +64 -0
  19. package/tests/run-tests.js +6 -0
  20. package/tests/unit/test-models-generation.js +164 -0
  21. package/tests/utils/driver-registry.js +1 -1
  22. package/tests/utils/test-helpers.js +9 -6
@@ -346,7 +346,7 @@ class ModelsGeneration extends BaseGenerator
346
346
  continue;
347
347
  }
348
348
  let relationKey = this.generateForwardRelationKey(column.referencedTable, columnName, referenceCounts);
349
- let relationType = this.determineForwardRelationType(tableName, columnName, column);
349
+ let relationType = this.determineRelationType(column, true);
350
350
  relations[relationKey] = {
351
351
  fromColumn: columnName,
352
352
  toColumn: column.referencedColumn,
@@ -375,7 +375,7 @@ class ModelsGeneration extends BaseGenerator
375
375
  continue;
376
376
  }
377
377
  let relationKey = this.generateReverseRelationKey(otherTableName, columnName, referenceCounts);
378
- let relationType = this.determineReverseRelationType(otherTableName, columnName, column);
378
+ let relationType = this.determineRelationType(column, false);
379
379
  reverseRelations[relationKey] = {
380
380
  fromColumn: column.referencedColumn,
381
381
  toColumn: columnName,
@@ -442,25 +442,12 @@ class ModelsGeneration extends BaseGenerator
442
442
  return RELATION_PREFIX+referencingTable+'_'+columnSuffix;
443
443
  }
444
444
 
445
- determineForwardRelationType(tableName, columnName, column)
445
+ determineMikroOrmForwardKind(column)
446
446
  {
447
- return 'BelongsToOneRelation';
448
- }
449
-
450
- determineReverseRelationType(referencingTableName, referencingColumnName, referencingColumn)
451
- {
452
- if(!this.allTablesData[referencingTableName]){
453
- return 'HasManyRelation';
454
- }
455
- let referencingTableData = this.allTablesData[referencingTableName];
456
- let referencingColumnData = referencingTableData.columns[referencingColumnName];
457
- if(!referencingColumnData){
458
- return 'HasManyRelation';
447
+ if('HasOneRelation' === this.determineRelationType(column, false)){
448
+ return this.mikroOrmRelationKindMap.HasOneRelation;
459
449
  }
460
- if('UNI' === referencingColumnData.key || 'PRI' === referencingColumnData.key){
461
- return 'HasOneRelation';
462
- }
463
- return 'HasManyRelation';
450
+ return this.mikroOrmRelationKindMap.BelongsToOneRelation;
464
451
  }
465
452
 
466
453
  determineRelationType(column, isForwardRelation)
@@ -529,7 +516,7 @@ class ModelsGeneration extends BaseGenerator
529
516
  let relation = relations[relationKey];
530
517
  let relatedModelClass = sc.capitalizedCamelCase(relation.referencedTable)+'Model';
531
518
  let relatedModelFile = sc.kebabCase(relation.referencedTable)+'-model';
532
- let kind = sc.get(this.mikroOrmRelationKindMap, relation.relationType, 'm:1');
519
+ let kind = this.determineMikroOrmForwardKind(columns[relation.fromColumn]);
533
520
  let propDef = relationKey+': {\n';
534
521
  propDef += ' kind: \''+kind+'\',\n';
535
522
  propDef += ' entity: () => require(\'./'+relatedModelFile+'\').'+relatedModelClass;
@@ -76,13 +76,64 @@ class MikroOrmDriver extends BaseDriver
76
76
  let relations = [];
77
77
  for(let propName of Object.keys(this.entitySchema.meta.properties)){
78
78
  let prop = this.entitySchema.meta.properties[propName];
79
- if(prop.kind === 'm:1' || prop.kind === '1:m' || prop.kind === 'm:n'){
79
+ if('m:1' === prop.kind || '1:1' === prop.kind || '1:m' === prop.kind || 'm:n' === prop.kind){
80
80
  relations.push(propName);
81
81
  }
82
82
  }
83
83
  return relations;
84
84
  }
85
85
 
86
+ isOwningRelation(prop)
87
+ {
88
+ if('m:1' === prop.kind){
89
+ return true;
90
+ }
91
+ return '1:1' === prop.kind && Boolean(prop.owner);
92
+ }
93
+
94
+ isInverseRelation(prop)
95
+ {
96
+ if('1:m' === prop.kind){
97
+ return true;
98
+ }
99
+ return '1:1' === prop.kind && !prop.owner;
100
+ }
101
+
102
+ inverseRelationOwnerJoinColumn(prop, relationDriver)
103
+ {
104
+ if(!this.isInverseRelation(prop)){
105
+ return false;
106
+ }
107
+ if(!prop.mappedBy){
108
+ return false;
109
+ }
110
+ if(!relationDriver.entitySchema){
111
+ return false;
112
+ }
113
+ if(!relationDriver.entitySchema.meta){
114
+ return false;
115
+ }
116
+ let mappedProp = relationDriver.entitySchema.meta.properties[prop.mappedBy];
117
+ if(!mappedProp){
118
+ return false;
119
+ }
120
+ if(!mappedProp.joinColumns){
121
+ return false;
122
+ }
123
+ return mappedProp.joinColumns[0] || false;
124
+ }
125
+
126
+ nestedFkColumn(prop, relationDriver)
127
+ {
128
+ if(!this.isOwningRelation(prop)){
129
+ return this.inverseRelationOwnerJoinColumn(prop, relationDriver);
130
+ }
131
+ if(!prop.joinColumns){
132
+ return false;
133
+ }
134
+ return prop.joinColumns[0] || false;
135
+ }
136
+
86
137
  async create(params)
87
138
  {
88
139
  let transformed = this.transformFkToRelations(params);
@@ -207,7 +258,7 @@ class MikroOrmDriver extends BaseDriver
207
258
  let aliasCounter = 0;
208
259
  for(let relationName of relations){
209
260
  let prop = properties[relationName];
210
- if(!prop || !prop.kind || (prop.kind !== 'm:1' && prop.kind !== '1:m')){
261
+ if(!prop || !prop.kind || (!this.isOwningRelation(prop) && !this.isInverseRelation(prop))){
211
262
  continue;
212
263
  }
213
264
  let relationDriver = this.server.entityManager.get(prop.entity);
@@ -215,14 +266,12 @@ class MikroOrmDriver extends BaseDriver
215
266
  continue;
216
267
  }
217
268
  let alias = 'rel_'+aliasCounter;
218
- if('m:1' === prop.kind && prop.joinColumns && prop.joinColumns[0]){
269
+ if(this.isOwningRelation(prop) && prop.joinColumns && prop.joinColumns[0]){
219
270
  queryBuilder.leftJoin(relationName, alias, 'e.'+prop.joinColumns[0]+' = '+alias+'.id');
220
271
  }
221
- if('1:m' === prop.kind && prop.mappedBy){
222
- let mappedProp = relationDriver.entitySchema.meta.properties[prop.mappedBy];
223
- if(mappedProp && mappedProp.joinColumns && mappedProp.joinColumns[0]){
224
- queryBuilder.leftJoin(relationName, alias, 'e.id = '+alias+'.'+mappedProp.joinColumns[0]);
225
- }
272
+ let ownerJoinColumn = this.inverseRelationOwnerJoinColumn(prop, relationDriver);
273
+ if(ownerJoinColumn){
274
+ queryBuilder.leftJoin(relationName, alias, 'e.id = '+alias+'.'+ownerJoinColumn);
226
275
  }
227
276
  aliasCounter++;
228
277
  }
@@ -424,7 +473,7 @@ class MikroOrmDriver extends BaseDriver
424
473
  return entity;
425
474
  }
426
475
  for(let key of Object.keys(entity)){
427
- if(entity[key] && entity[key] instanceof Collection){
476
+ if(entity[key] && entity[key] instanceof Collection && entity[key].isInitialized()){
428
477
  entity[key] = entity[key].getItems();
429
478
  }
430
479
  }
@@ -479,7 +528,7 @@ class MikroOrmDriver extends BaseDriver
479
528
  let properties = this.entitySchema.meta.properties;
480
529
  for(let relationName of Object.keys(properties)){
481
530
  let prop = properties[relationName];
482
- if(!prop.kind || (prop.kind !== 'm:1' && prop.kind !== '1:m')){
531
+ if(!prop.kind || (!this.isOwningRelation(prop) && !this.isInverseRelation(prop))){
483
532
  continue;
484
533
  }
485
534
  if(sc.isArray(relations) && 0 < relations.length && -1 === relations.indexOf(relationName)){
@@ -490,14 +539,19 @@ class MikroOrmDriver extends BaseDriver
490
539
  Logger.warning('Entity not found for relation:', prop.entity);
491
540
  continue;
492
541
  }
493
- let isOneToMany = '1:m' === prop.kind;
494
- let isManyToOne = 'm:1' === prop.kind;
495
- if(isManyToOne && sc.hasOwn(params, relationName)){
542
+ if(!sc.hasOwn(params, relationName)){
543
+ continue;
544
+ }
545
+ if(this.isOwningRelation(prop)){
496
546
  await this.createOne(params, relationName, relationDriver, newInstance, prop);
497
547
  continue;
498
548
  }
499
- if(isOneToMany && sc.hasOwn(params, relationName) && sc.isArray(params[relationName])){
549
+ if(sc.isArray(params[relationName])){
500
550
  await this.createMany(params, relationName, relationDriver, newInstance, prop);
551
+ continue;
552
+ }
553
+ if('1:1' === prop.kind && sc.isObject(params[relationName])){
554
+ await this.createOne(params, relationName, relationDriver, newInstance, prop);
501
555
  }
502
556
  }
503
557
  }
@@ -505,8 +559,9 @@ class MikroOrmDriver extends BaseDriver
505
559
  async createOne(params, relationName, relationDriver, newInstance, prop)
506
560
  {
507
561
  let nestedData = params[relationName];
508
- if(prop.joinColumns && prop.joinColumns[0]){
509
- nestedData[prop.joinColumns[0]] = newInstance.id;
562
+ let fkColumn = this.nestedFkColumn(prop, relationDriver);
563
+ if(fkColumn){
564
+ nestedData[fkColumn] = newInstance.id;
510
565
  }
511
566
  let nestedObject = await relationDriver.create(nestedData);
512
567
  await this.orm.em.flush();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reldens/storage",
3
3
  "scope": "@reldens",
4
- "version": "0.116.0",
4
+ "version": "0.117.0",
5
5
  "description": "Reldens - Storage",
6
6
  "author": "Damian A. Pastorini",
7
7
  "license": "MIT",
@@ -58,7 +58,7 @@
58
58
  "@reldens/utils": "^0.58.0",
59
59
  "knex": "3.3.0",
60
60
  "mysql": "2.18.1",
61
- "mysql2": "3.24.3",
61
+ "mysql2": "3.24.4",
62
62
  "objection": "3.1.5"
63
63
  }
64
64
  }
@@ -0,0 +1,63 @@
1
+ /**
2
+ *
3
+ * Reldens - TestProductDetailsEntity
4
+ *
5
+ */
6
+
7
+ const { EntityProperties } = require('../../index');
8
+ const { sc } = require('@reldens/utils');
9
+
10
+ class TestProductDetailsEntity extends EntityProperties
11
+ {
12
+
13
+ static propertiesConfig(extraProps)
14
+ {
15
+ let properties = {
16
+ id: {
17
+ isId: true,
18
+ type: 'number',
19
+ isRequired: true,
20
+ dbType: 'int'
21
+ },
22
+ product_id: {
23
+ type: 'reference',
24
+ reference: 'test_products',
25
+ alias: 'related_test_products',
26
+ onDelete: 'cascade',
27
+ isRequired: true,
28
+ isUnique: true,
29
+ dbType: 'int'
30
+ },
31
+ weight: {
32
+ type: 'number',
33
+ dbType: 'decimal'
34
+ },
35
+ dimensions: {
36
+ dbType: 'varchar'
37
+ },
38
+ created_at: {
39
+ type: 'datetime',
40
+ dbType: 'timestamp'
41
+ },
42
+ updated_at: {
43
+ type: 'datetime',
44
+ dbType: 'timestamp'
45
+ }
46
+ };
47
+ let propertiesKeys = Object.keys(properties);
48
+ let showProperties = propertiesKeys;
49
+ let editProperties = sc.removeFromArray([...propertiesKeys], ['id', 'created_at', 'updated_at']);
50
+ let listProperties = propertiesKeys;
51
+ return {
52
+ showProperties,
53
+ editProperties,
54
+ listProperties,
55
+ filterProperties: listProperties,
56
+ properties,
57
+ ...extraProps
58
+ };
59
+ }
60
+
61
+ }
62
+
63
+ module.exports.TestProductDetailsEntity = TestProductDetailsEntity;
@@ -4,13 +4,15 @@
4
4
  *
5
5
  */
6
6
 
7
- const { TestCategoriesEntity } = require('./entities/test-categories-entity');
8
- const { TestProductsEntity } = require('./entities/test-products-entity');
7
+ const { TestCategoriesEntity } = require('./entities/test-categories-entity');
8
+ const { TestProductDetailsEntity } = require('./entities/test-product-details-entity');
9
+ const { TestProductsEntity } = require('./entities/test-products-entity');
9
10
  const { TestReviewsEntity } = require('./entities/test-reviews-entity');
10
11
 
11
12
  let entitiesConfig = {
12
- testCategories: TestCategoriesEntity.propertiesConfig(),
13
- testProducts: TestProductsEntity.propertiesConfig(),
13
+ testCategories: TestCategoriesEntity.propertiesConfig(),
14
+ testProductDetails: TestProductDetailsEntity.propertiesConfig(),
15
+ testProducts: TestProductsEntity.propertiesConfig(),
14
16
  testReviews: TestReviewsEntity.propertiesConfig()
15
17
  };
16
18
 
@@ -1,16 +1,17 @@
1
- /**
2
- *
3
- * Reldens - Entities Translations
4
- *
5
- */
6
-
7
- module.exports.entitiesTranslations = {
8
- labels: {
1
+ /**
2
+ *
3
+ * Reldens - Entities Translations
4
+ *
5
+ */
6
+
7
+ module.exports.entitiesTranslations = {
8
+ labels: {
9
9
  'test_categories': 'Test Categories',
10
+ 'test_product_details': 'Test Product Details',
10
11
  'test_products': 'Test Products',
11
- 'test_reviews': 'Test Reviews'
12
- },
13
- fields: {
12
+ 'test_reviews': 'Test Reviews'
13
+ },
14
+ fields: {
14
15
  'test_categories': {
15
16
  'id': 'ID',
16
17
  'name': 'Name',
@@ -21,6 +22,14 @@ module.exports.entitiesTranslations = {
21
22
  'created_at': 'Created At',
22
23
  'updated_at': 'Updated At'
23
24
  },
25
+ 'test_product_details': {
26
+ 'id': 'ID',
27
+ 'product_id': 'Product ID',
28
+ 'weight': 'Weight',
29
+ 'dimensions': 'Dimensions',
30
+ 'created_at': 'Created At',
31
+ 'updated_at': 'Updated At'
32
+ },
24
33
  'test_products': {
25
34
  'id': 'ID',
26
35
  'category_id': 'Category ID',
@@ -48,6 +57,6 @@ module.exports.entitiesTranslations = {
48
57
  'helpful_count': 'Helpful Count',
49
58
  'created_at': 'Created At',
50
59
  'updated_at': 'Updated At'
51
- }
52
- }
53
- };
60
+ }
61
+ }
62
+ };
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  const testCategoriesModel = require('./test-categories-model');
8
+ const testProductDetailsModel = require('./test-product-details-model');
8
9
  const testProductsModel = require('./test-products-model');
9
10
  const testReviewsModel = require('./test-reviews-model');
10
11
  const { entitiesConfig } = require('../../entities-config');
@@ -12,6 +13,7 @@ const { entitiesTranslations } = require('../../entities-translations');
12
13
 
13
14
  let rawRegisteredEntities = {
14
15
  testCategories: testCategoriesModel,
16
+ testProductDetails: testProductDetailsModel,
15
17
  testProducts: testProductsModel,
16
18
  testReviews: testReviewsModel
17
19
  };
@@ -0,0 +1,60 @@
1
+ /**
2
+ *
3
+ * Reldens - TestProductDetailsModel
4
+ *
5
+ */
6
+
7
+ const { MikroOrmCore } = require('../../../index');
8
+ const { EntitySchema } = MikroOrmCore;
9
+
10
+ class TestProductDetailsModel
11
+ {
12
+
13
+ constructor(id, product_id, weight, dimensions, created_at, updated_at)
14
+ {
15
+ this.id = id;
16
+ this.product_id = product_id;
17
+ this.weight = weight;
18
+ this.dimensions = dimensions;
19
+ this.created_at = created_at;
20
+ this.updated_at = updated_at;
21
+ }
22
+
23
+ static createByProps(props)
24
+ {
25
+ const {id, product_id, weight, dimensions, created_at, updated_at} = props;
26
+ return new this(id, product_id, weight, dimensions, created_at, updated_at);
27
+ }
28
+
29
+ }
30
+
31
+ const schema = new EntitySchema({
32
+ class: TestProductDetailsModel,
33
+ tableName: 'test_product_details',
34
+ properties: {
35
+ id: { type: 'number', primary: true },
36
+ product_id: { type: 'number', persist: false },
37
+ weight: { type: 'number', nullable: true },
38
+ dimensions: { type: 'string', nullable: true },
39
+ created_at: { type: 'Date', nullable: true },
40
+ updated_at: { type: 'Date', nullable: true },
41
+ related_test_products: {
42
+ kind: '1:1',
43
+ entity: () => require('./test-products-model').TestProductsModel,
44
+ joinColumns: ['product_id']
45
+ }
46
+ },
47
+ });
48
+ schema._fkMappings = {
49
+ "product_id": {
50
+ "relationKey": "related_test_products",
51
+ "entityName": "TestProductsModel",
52
+ "referencedColumn": "id",
53
+ "nullable": false
54
+ }
55
+ };
56
+ module.exports = {
57
+ TestProductDetailsModel,
58
+ entity: TestProductDetailsModel,
59
+ schema: schema
60
+ };
@@ -57,6 +57,11 @@ const schema = new EntitySchema({
57
57
  entity: () => require('./test-categories-model').TestCategoriesModel,
58
58
  joinColumns: ['category_id']
59
59
  },
60
+ related_test_product_details: {
61
+ kind: '1:1',
62
+ entity: () => require('./test-product-details-model').TestProductDetailsModel,
63
+ mappedBy: 'related_test_products'
64
+ },
60
65
  related_test_reviews: {
61
66
  kind: '1:m',
62
67
  entity: () => require('./test-reviews-model').TestReviewsModel,
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  const { TestCategoriesModel } = require('./test-categories-model');
8
+ const { TestProductDetailsModel } = require('./test-product-details-model');
8
9
  const { TestProductsModel } = require('./test-products-model');
9
10
  const { TestReviewsModel } = require('./test-reviews-model');
10
11
  const { entitiesConfig } = require('../../entities-config');
@@ -12,6 +13,7 @@ const { entitiesTranslations } = require('../../entities-translations');
12
13
 
13
14
  let rawRegisteredEntities = {
14
15
  testCategories: TestCategoriesModel,
16
+ testProductDetails: TestProductDetailsModel,
15
17
  testProducts: TestProductsModel,
16
18
  testReviews: TestReviewsModel
17
19
  };
@@ -0,0 +1,33 @@
1
+ /**
2
+ *
3
+ * Reldens - TestProductDetailsModel
4
+ *
5
+ */
6
+
7
+ const { ObjectionJsRawModel } = require('../../../index');
8
+
9
+ class TestProductDetailsModel extends ObjectionJsRawModel
10
+ {
11
+
12
+ static get tableName()
13
+ {
14
+ return 'test_product_details';
15
+ }
16
+
17
+ static get relationMappings()
18
+ {
19
+ const { TestProductsModel } = require('./test-products-model');
20
+ return {
21
+ related_test_products: {
22
+ relation: this.BelongsToOneRelation,
23
+ modelClass: TestProductsModel,
24
+ join: {
25
+ from: this.tableName+'.product_id',
26
+ to: TestProductsModel.tableName+'.id'
27
+ }
28
+ }
29
+ };
30
+ }
31
+ }
32
+
33
+ module.exports.TestProductDetailsModel = TestProductDetailsModel;
@@ -17,6 +17,7 @@ class TestProductsModel extends ObjectionJsRawModel
17
17
  static get relationMappings()
18
18
  {
19
19
  const { TestCategoriesModel } = require('./test-categories-model');
20
+ const { TestProductDetailsModel } = require('./test-product-details-model');
20
21
  const { TestReviewsModel } = require('./test-reviews-model');
21
22
  return {
22
23
  related_test_categories: {
@@ -27,6 +28,14 @@ class TestProductsModel extends ObjectionJsRawModel
27
28
  to: TestCategoriesModel.tableName+'.id'
28
29
  }
29
30
  },
31
+ related_test_product_details: {
32
+ relation: this.HasOneRelation,
33
+ modelClass: TestProductDetailsModel,
34
+ join: {
35
+ from: this.tableName+'.id',
36
+ to: TestProductDetailsModel.tableName+'.product_id'
37
+ }
38
+ },
30
39
  related_test_reviews: {
31
40
  relation: this.HasManyRelation,
32
41
  modelClass: TestReviewsModel,
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  const { TestCategoriesModel } = require('./test-categories-model');
8
+ const { TestProductDetailsModel } = require('./test-product-details-model');
8
9
  const { TestProductsModel } = require('./test-products-model');
9
10
  const { TestReviewsModel } = require('./test-reviews-model');
10
11
  const { entitiesConfig } = require('../../entities-config');
@@ -12,6 +13,7 @@ const { entitiesTranslations } = require('../../entities-translations');
12
13
 
13
14
  let rawRegisteredEntities = {
14
15
  testCategories: TestCategoriesModel,
16
+ testProductDetails: TestProductDetailsModel,
15
17
  testProducts: TestProductsModel,
16
18
  testReviews: TestReviewsModel
17
19
  };
@@ -0,0 +1,41 @@
1
+ /**
2
+ *
3
+ * Reldens - TestProductDetailsModel
4
+ *
5
+ */
6
+
7
+ class TestProductDetailsModel
8
+ {
9
+
10
+ constructor(id, product_id, weight, dimensions, created_at, updated_at)
11
+ {
12
+ this.id = id;
13
+ this.product_id = product_id;
14
+ this.weight = weight;
15
+ this.dimensions = dimensions;
16
+ this.created_at = created_at;
17
+ this.updated_at = updated_at;
18
+ }
19
+
20
+ static get tableName()
21
+ {
22
+ return 'test_product_details';
23
+ }
24
+
25
+
26
+ static get relationTypes()
27
+ {
28
+ return {
29
+ test_products: 'one'
30
+ };
31
+ }
32
+
33
+ static get relationMappings()
34
+ {
35
+ return {
36
+ 'related_test_products': 'test_products'
37
+ };
38
+ }
39
+ }
40
+
41
+ module.exports.TestProductDetailsModel = TestProductDetailsModel;
@@ -34,6 +34,7 @@ class TestProductsModel
34
34
  {
35
35
  return {
36
36
  test_categories: 'one',
37
+ test_product_details: 'one',
37
38
  test_reviews: 'many'
38
39
  };
39
40
  }
@@ -42,6 +43,7 @@ class TestProductsModel
42
43
  {
43
44
  return {
44
45
  'related_test_categories': 'test_categories',
46
+ 'related_test_product_details': 'test_product_details',
45
47
  'related_test_reviews': 'test_reviews'
46
48
  };
47
49
  }
@@ -0,0 +1,19 @@
1
+ /**
2
+ *
3
+ * Reldens - Test Product Details Fixtures
4
+ * Universal fixtures for all drivers (no driver-specific nesting)
5
+ *
6
+ */
7
+
8
+ module.exports.ProductDetailsFixtures = {
9
+ product_details_relations_1: {
10
+ id: 4600,
11
+ product_id: 2600,
12
+ weight: 1.25,
13
+ dimensions: '10x20x30'
14
+ },
15
+ product_details_create_nested: {
16
+ weight: 2.5,
17
+ dimensions: '5x5x5'
18
+ }
19
+ };
@@ -1,4 +1,5 @@
1
1
  DROP TABLE IF EXISTS `test_reviews`;
2
+ DROP TABLE IF EXISTS `test_product_details`;
2
3
  DROP TABLE IF EXISTS `test_products`;
3
4
  DROP TABLE IF EXISTS `test_categories`;
4
5
 
@@ -39,6 +40,18 @@ CREATE TABLE `test_products` (
39
40
  CONSTRAINT `fk_products_category` FOREIGN KEY (`category_id`) REFERENCES `test_categories` (`id`) ON DELETE CASCADE
40
41
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
41
42
 
43
+ CREATE TABLE `test_product_details` (
44
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
45
+ `product_id` INT UNSIGNED NOT NULL,
46
+ `weight` DECIMAL(10,2) NULL,
47
+ `dimensions` VARCHAR(100) NULL,
48
+ `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
49
+ `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
50
+ PRIMARY KEY (`id`),
51
+ UNIQUE KEY `product_id` (`product_id`),
52
+ CONSTRAINT `fk_product_details_product` FOREIGN KEY (`product_id`) REFERENCES `test_products` (`id`) ON DELETE CASCADE
53
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
54
+
42
55
  CREATE TABLE `test_reviews` (
43
56
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
44
57
  `product_id` INT UNSIGNED NOT NULL,
@@ -10,6 +10,7 @@ const { TestHelpers } = require('../utils/test-helpers');
10
10
  const { CategoriesFixtures } = require('../fixtures/categories-fixtures');
11
11
  const { ProductsFixtures } = require('../fixtures/products-fixtures');
12
12
  const { ReviewsFixtures } = require('../fixtures/reviews-fixtures');
13
+ const { ProductDetailsFixtures } = require('../fixtures/product-details-fixtures');
13
14
 
14
15
  class RelationsTest
15
16
  {
@@ -19,6 +20,7 @@ class RelationsTest
19
20
  this.dataServer = dataServer;
20
21
  this.categoriesRepo = repos.testCategories;
21
22
  this.productsRepo = repos.testProducts;
23
+ this.productDetailsRepo = repos.testProductDetails;
22
24
  this.driverName = driverName;
23
25
  this.runner = new TestRunner();
24
26
  }
@@ -36,9 +38,71 @@ class RelationsTest
36
38
  await this.testCreateWithRelations();
37
39
  await this.testNestedRelations();
38
40
  await this.testRelationStringParsing();
41
+ await this.testOneToOneRelations();
39
42
  return this.runner.getResults();
40
43
  }
41
44
 
45
+ async testOneToOneRelations()
46
+ {
47
+ this.runner.group('One To One Relations');
48
+ await TestHelpers.cleanDatabase(this.dataServer);
49
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_categories', [
50
+ CategoriesFixtures.category_relations_1
51
+ ]);
52
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_products', [
53
+ ProductsFixtures.product_relations_1,
54
+ {...ProductsFixtures.product_relations_2, category_id: 1600}
55
+ ]);
56
+ await TestHelpers.insertFixturesViaRawSQL(this.dataServer, 'test_product_details', [
57
+ ProductDetailsFixtures.product_details_relations_1
58
+ ]);
59
+ await this.runner.test('should load the one to one relation from the referenced side as a single object', async () => {
60
+ let results = await this.productsRepo.loadWithRelations({id: 2600}, ['related_test_product_details']);
61
+ assert.strictEqual(results.length, 1);
62
+ let details = results[0].related_test_product_details;
63
+ assert.ok(details);
64
+ assert.ok(!Array.isArray(details));
65
+ assert.strictEqual(details.id, 4600);
66
+ });
67
+ await this.runner.test('should load the one to one relation from the owning side as a single object', async () => {
68
+ let results = await this.productDetailsRepo.loadWithRelations({id: 4600}, ['related_test_products']);
69
+ assert.strictEqual(results.length, 1);
70
+ let product = results[0].related_test_products;
71
+ assert.ok(product);
72
+ assert.ok(!Array.isArray(product));
73
+ assert.strictEqual(product.id, 2600);
74
+ });
75
+ await this.runner.test('should return no related record on the one to one relation when none exists', async () => {
76
+ let result = await this.productsRepo.loadByIdWithRelations(2601, ['related_test_product_details']);
77
+ assert.ok(result);
78
+ assert.ok(!result.related_test_product_details);
79
+ });
80
+ await this.runner.test('should count records joining the one to one relation from the referenced side', async () => {
81
+ let count = await this.productsRepo.countWithRelations({}, ['related_test_product_details']);
82
+ assert.strictEqual(count, 2);
83
+ });
84
+ await this.runner.test('should count records joining the one to one relation from the owning side', async () => {
85
+ let count = await this.productDetailsRepo.countWithRelations({}, ['related_test_products']);
86
+ assert.strictEqual(count, 1);
87
+ });
88
+ await this.runner.test('should create a record with a nested one to one relation', async () => {
89
+ let productWithDetails = {
90
+ ...ProductsFixtures.product_create_nested,
91
+ category_id: 1600,
92
+ related_test_product_details: {...ProductDetailsFixtures.product_details_create_nested}
93
+ };
94
+ let created = await this.productsRepo.createWithRelations(
95
+ productWithDetails,
96
+ ['related_test_product_details']
97
+ );
98
+ assert.ok(created);
99
+ assert.ok(created.id);
100
+ let details = await this.productDetailsRepo.loadBy('product_id', created.id);
101
+ assert.strictEqual(details.length, 1);
102
+ assert.strictEqual(details[0].dimensions, ProductDetailsFixtures.product_details_create_nested.dimensions);
103
+ });
104
+ }
105
+
42
106
  async testLoadWithRelations()
43
107
  {
44
108
  this.runner.group('loadWithRelations');
@@ -16,6 +16,7 @@ const EntityManagerTest = require('./unit/test-entity-manager');
16
16
  const TypeMapperTest = require('./unit/test-type-mapper');
17
17
  const DriversUnitTest = require('./unit/test-drivers');
18
18
  const EntitiesGenerationTest = require('./unit/test-entities-generation');
19
+ const ModelsGenerationTest = require('./unit/test-models-generation');
19
20
 
20
21
  if(!process.env.RELDENS_TEST_DB_HOST){
21
22
  let envPath = FileHandler.joinPaths(__dirname, '.env.test');
@@ -193,6 +194,11 @@ class RunTests
193
194
  this.allCounts.total += entitiesGenerationResult.total;
194
195
  this.allCounts.passed += entitiesGenerationResult.passed;
195
196
  this.allCounts.failed += entitiesGenerationResult.failed;
197
+ let modelsGenerationTest = new ModelsGenerationTest();
198
+ let modelsGenerationResult = await modelsGenerationTest.run();
199
+ this.allCounts.total += modelsGenerationResult.total;
200
+ this.allCounts.passed += modelsGenerationResult.passed;
201
+ this.allCounts.failed += modelsGenerationResult.failed;
196
202
  }
197
203
 
198
204
  async runPreFlightChecks(config)
@@ -0,0 +1,164 @@
1
+ /**
2
+ *
3
+ * Reldens - ModelsGeneration Test
4
+ *
5
+ */
6
+
7
+ const { TestRunner, assert } = require('../utils/test-runner');
8
+ const { ModelsGeneration } = require('../../lib/generators/models-generation');
9
+
10
+ class ModelsGenerationTest
11
+ {
12
+
13
+ constructor()
14
+ {
15
+ this.runner = new TestRunner();
16
+ }
17
+
18
+ createColumn(props)
19
+ {
20
+ return Object.assign(
21
+ {
22
+ name: 'test_column',
23
+ type: 'int',
24
+ columnType: 'int(10) unsigned',
25
+ length: null,
26
+ nullable: false,
27
+ key: '',
28
+ extra: '',
29
+ default: null
30
+ },
31
+ props
32
+ );
33
+ }
34
+
35
+ createForeignKeyColumn(name, key)
36
+ {
37
+ return this.createColumn({
38
+ name,
39
+ key,
40
+ referencedTable: 'test_products',
41
+ referencedColumn: 'id',
42
+ referencedDeleteRule: 'CASCADE'
43
+ });
44
+ }
45
+
46
+ createTables()
47
+ {
48
+ return {
49
+ test_products: {
50
+ name: 'test_products',
51
+ columns: {
52
+ id: this.createColumn({name: 'id', key: 'PRI', extra: 'auto_increment'}),
53
+ name: this.createColumn({name: 'name', type: 'varchar', columnType: 'varchar(200)', length: '200'})
54
+ }
55
+ },
56
+ test_product_details: {
57
+ name: 'test_product_details',
58
+ columns: {
59
+ id: this.createColumn({name: 'id', key: 'PRI', extra: 'auto_increment'}),
60
+ product_id: this.createForeignKeyColumn('product_id', 'UNI')
61
+ }
62
+ },
63
+ test_reviews: {
64
+ name: 'test_reviews',
65
+ columns: {
66
+ id: this.createColumn({name: 'id', key: 'PRI', extra: 'auto_increment'}),
67
+ product_id: this.createForeignKeyColumn('product_id', 'MUL')
68
+ }
69
+ }
70
+ };
71
+ }
72
+
73
+ createGeneration()
74
+ {
75
+ let generation = new ModelsGeneration({});
76
+ generation.setAllTablesData(this.createTables());
77
+ return generation;
78
+ }
79
+
80
+ mikroOrmDefinition(generation, tableName)
81
+ {
82
+ let tableData = generation.allTablesData[tableName];
83
+ return generation.getEntityPropertiesDefinition(tableData.columns, 'mikro-orm', tableName, tableData);
84
+ }
85
+
86
+ async run()
87
+ {
88
+ this.runner.suite('ModelsGeneration');
89
+ await this.testDetermineMikroOrmForwardKind();
90
+ await this.testMikroOrmOneToOneDefinitions();
91
+ await this.testObjectionJsRelationTypes();
92
+ return this.runner.getResults();
93
+ }
94
+
95
+ async testDetermineMikroOrmForwardKind()
96
+ {
97
+ this.runner.group('determineMikroOrmForwardKind');
98
+ let generation = this.createGeneration();
99
+ await this.runner.test('should return 1:1 for a unique foreign key column', async () => {
100
+ let kind = generation.determineMikroOrmForwardKind(this.createColumn({key: 'UNI'}));
101
+ assert.strictEqual(kind, '1:1');
102
+ });
103
+ await this.runner.test('should return 1:1 for a primary foreign key column', async () => {
104
+ let kind = generation.determineMikroOrmForwardKind(this.createColumn({key: 'PRI'}));
105
+ assert.strictEqual(kind, '1:1');
106
+ });
107
+ await this.runner.test('should return m:1 for an indexed foreign key column', async () => {
108
+ let kind = generation.determineMikroOrmForwardKind(this.createColumn({key: 'MUL'}));
109
+ assert.strictEqual(kind, 'm:1');
110
+ });
111
+ await this.runner.test('should return m:1 for a non indexed foreign key column', async () => {
112
+ let kind = generation.determineMikroOrmForwardKind(this.createColumn({key: ''}));
113
+ assert.strictEqual(kind, 'm:1');
114
+ });
115
+ }
116
+
117
+ async testMikroOrmOneToOneDefinitions()
118
+ {
119
+ this.runner.group('getEntityPropertiesDefinition - mikro-orm one to one');
120
+ let generation = this.createGeneration();
121
+ await this.runner.test('should emit a 1:1 owning side with joinColumns for a unique foreign key', async () => {
122
+ let definition = this.mikroOrmDefinition(generation, 'test_product_details');
123
+ assert.ok(definition.includes('related_test_products: {\n kind: \'1:1\','));
124
+ assert.ok(definition.includes('joinColumns: [\'product_id\']'));
125
+ assert.ok(!definition.includes('mappedBy'));
126
+ });
127
+ await this.runner.test('should emit a 1:1 inverse side with mappedBy for a unique foreign key', async () => {
128
+ let definition = this.mikroOrmDefinition(generation, 'test_products');
129
+ assert.ok(definition.includes('related_test_product_details: {\n kind: \'1:1\','));
130
+ assert.ok(definition.includes('mappedBy: \'related_test_products\''));
131
+ });
132
+ await this.runner.test('should emit a 1:m inverse side for a non unique foreign key', async () => {
133
+ let definition = this.mikroOrmDefinition(generation, 'test_products');
134
+ assert.ok(definition.includes('related_test_reviews: {\n kind: \'1:m\','));
135
+ });
136
+ await this.runner.test('should emit a m:1 owning side for a non unique foreign key', async () => {
137
+ let definition = this.mikroOrmDefinition(generation, 'test_reviews');
138
+ assert.ok(definition.includes('related_test_products: {\n kind: \'m:1\','));
139
+ assert.ok(definition.includes('joinColumns: [\'product_id\']'));
140
+ });
141
+ }
142
+
143
+ async testObjectionJsRelationTypes()
144
+ {
145
+ this.runner.group('ObjectionJS relation types for unique foreign keys');
146
+ let generation = this.createGeneration();
147
+ let tables = generation.allTablesData;
148
+ await this.runner.test('should keep BelongsToOneRelation on the owning side', async () => {
149
+ let relations = generation.detectObjectionJsRelations('test_product_details', tables.test_product_details);
150
+ assert.strictEqual(relations.related_test_products.relationType, 'BelongsToOneRelation');
151
+ });
152
+ await this.runner.test('should use HasOneRelation on the referenced side of a unique foreign key', async () => {
153
+ let reverseRelations = generation.detectReverseObjectionJsRelations('test_products');
154
+ assert.strictEqual(reverseRelations.related_test_product_details.relationType, 'HasOneRelation');
155
+ });
156
+ await this.runner.test('should use HasManyRelation on the referenced side of a non unique foreign key', async () => {
157
+ let reverseRelations = generation.detectReverseObjectionJsRelations('test_products');
158
+ assert.strictEqual(reverseRelations.related_test_reviews.relationType, 'HasManyRelation');
159
+ });
160
+ }
161
+
162
+ }
163
+
164
+ module.exports = ModelsGenerationTest;
@@ -22,7 +22,7 @@ class DriverRegistry
22
22
  instanceId: Math.random()
23
23
  };
24
24
  this.schemaPath = FileHandler.joinPaths(__dirname, '..', 'fixtures', 'sql', 'test-schema.sql');
25
- this.repoNames = ['testCategories', 'testProducts', 'testReviews'];
25
+ this.repoNames = ['testCategories', 'testProductDetails', 'testProducts', 'testReviews'];
26
26
  this.driverNames = TestHelpers.activeDriverNames();
27
27
  this.skipGeneration = false;
28
28
  }
@@ -156,6 +156,7 @@ class TestHelpers
156
156
  try {
157
157
  await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=0;');
158
158
  await dataServer.rawQuery('DELETE FROM test_reviews;');
159
+ await dataServer.rawQuery('DELETE FROM test_product_details;');
159
160
  await dataServer.rawQuery('DELETE FROM test_products;');
160
161
  await dataServer.rawQuery('DELETE FROM test_categories;');
161
162
  await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=1;');
@@ -210,6 +211,7 @@ class TestHelpers
210
211
  try {
211
212
  await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=0;');
212
213
  await dataServer.rawQuery('DROP TABLE IF EXISTS test_reviews;');
214
+ await dataServer.rawQuery('DROP TABLE IF EXISTS test_product_details;');
213
215
  await dataServer.rawQuery('DROP TABLE IF EXISTS test_products;');
214
216
  await dataServer.rawQuery('DROP TABLE IF EXISTS test_categories;');
215
217
  await dataServer.rawQuery('SET FOREIGN_KEY_CHECKS=1;');
@@ -719,9 +721,7 @@ class TestHelpers
719
721
  for(let filename of expectedFiles){
720
722
  let expectedContent = FileHandler.readFile(FileHandler.joinPaths(expectedPath, filename));
721
723
  let generatedContent = FileHandler.readFile(FileHandler.joinPaths(generatedPath, filename));
722
- let normalizedExpected = expectedContent.replace(/\r\n/g, '\n');
723
- let normalizedGenerated = generatedContent.replace(/\r\n/g, '\n');
724
- if(normalizedExpected !== normalizedGenerated){
724
+ if(this.normalizeGeneratedContent(expectedContent) !== this.normalizeGeneratedContent(generatedContent)){
725
725
  Logger.warning('File content mismatch: '+filename);
726
726
  Logger.warning('Expected: '+FileHandler.joinPaths(expectedPath, filename));
727
727
  Logger.warning('Generated: '+FileHandler.joinPaths(generatedPath, filename));
@@ -735,9 +735,7 @@ class TestHelpers
735
735
  if(!isExpectedDir && !isGeneratedDir){
736
736
  let expectedContent = FileHandler.readFile(expectedPath);
737
737
  let generatedContent = FileHandler.readFile(generatedPath);
738
- let normalizedExpected = expectedContent.replace(/\r\n/g, '\n');
739
- let normalizedGenerated = generatedContent.replace(/\r\n/g, '\n');
740
- if(normalizedExpected !== normalizedGenerated){
738
+ if(this.normalizeGeneratedContent(expectedContent) !== this.normalizeGeneratedContent(generatedContent)){
741
739
  Logger.warning('File content mismatch: '+relativePath);
742
740
  }
743
741
  Logger.info('File '+relativePath+' matches expected output');
@@ -746,6 +744,11 @@ class TestHelpers
746
744
  throw new Error('Path type mismatch for: '+relativePath);
747
745
  }
748
746
 
747
+ static normalizeGeneratedContent(content)
748
+ {
749
+ return content.replace(/\r\n/g, '\n').replace(/[ \t]+\n/g, '\n');
750
+ }
751
+
749
752
  static async loadGeneratedEntities(dataServer, driverName)
750
753
  {
751
754
  let modelsPath = FileHandler.joinPaths(