@reldens/storage 0.116.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 (86) 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 -693
  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 +385 -121
  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 +72 -0
  52. package/tests/fixtures/expected-entities/entities-config.js +6 -4
  53. package/tests/fixtures/expected-entities/entities-translations.js +25 -14
  54. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/registered-models-mikro-orm.js +2 -0
  55. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-categories-model.js +13 -8
  56. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-product-details-model.js +80 -0
  57. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-products-model.js +20 -14
  58. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-reviews-model.js +13 -12
  59. package/tests/fixtures/expected-entities-objection-js/models/objection-js/registered-models-objection-js.js +2 -0
  60. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-product-details-model.js +33 -0
  61. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-products-model.js +9 -0
  62. package/tests/fixtures/expected-entities-prisma/models/prisma/registered-models-prisma.js +2 -0
  63. package/tests/fixtures/expected-entities-prisma/models/prisma/test-product-details-model.js +43 -0
  64. package/tests/fixtures/expected-entities-prisma/models/prisma/test-products-model.js +2 -0
  65. package/tests/fixtures/product-details-fixtures.js +22 -0
  66. package/tests/fixtures/reviews-fixtures.js +2 -1
  67. package/tests/fixtures/sql/test-schema.sql +23 -1
  68. package/tests/fixtures/table-columns-fixtures.js +97 -0
  69. package/tests/integration/test-cross-driver-equivalence.js +286 -0
  70. package/tests/integration/test-nested-filters.js +265 -408
  71. package/tests/integration/test-relations.js +199 -0
  72. package/tests/integration/test-reldens-sample-data.js +255 -0
  73. package/tests/integration/test-reldens-shape-relations.js +292 -0
  74. package/tests/run-tests.js +112 -60
  75. package/tests/unit/test-drivers.js +7 -1
  76. package/tests/unit/test-models-generation.js +174 -0
  77. package/tests/utils/column-value-assert.js +139 -0
  78. package/tests/utils/driver-registry.js +1 -1
  79. package/tests/utils/entity-projection.js +111 -0
  80. package/tests/utils/observed-value.js +41 -0
  81. package/tests/utils/projection-difference.js +151 -0
  82. package/tests/utils/relations-shape-support.js +104 -0
  83. package/tests/utils/reldens-schema-loader.js +274 -0
  84. package/tests/utils/test-helpers.js +152 -19
  85. package/tests/utils/test-runner.js +26 -5
  86. package/tests/utils/whole-result-assert.js +243 -0
@@ -6,7 +6,6 @@
6
6
 
7
7
  const { BaseDriver } = require('../base-driver');
8
8
  const { Logger, sc } = require('@reldens/utils');
9
- const { Collection } = require('@mikro-orm/core');
10
9
 
11
10
  class MikroOrmDriver extends BaseDriver
12
11
  {
@@ -26,6 +25,12 @@ class MikroOrmDriver extends BaseDriver
26
25
  Logger.critical('Missing raw entity on Mikro ORM driver.');
27
26
  return false;
28
27
  }
28
+ this.mikroOrmModules = sc.get(props, 'mikroOrmModules', false);
29
+ if(!this.mikroOrmModules){
30
+ Logger.critical('Missing Mikro ORM modules on Mikro ORM driver.');
31
+ return false;
32
+ }
33
+ this.Collection = this.mikroOrmModules.Collection;
29
34
  this.orm = props.orm;
30
35
  this.server = props.server;
31
36
  this.entitySchema = (sc.isObject(this.rawModel) && sc.hasOwn(this.rawModel, 'schema')) ? this.rawModel.schema : this.rawModel;
@@ -76,28 +81,109 @@ class MikroOrmDriver extends BaseDriver
76
81
  let relations = [];
77
82
  for(let propName of Object.keys(this.entitySchema.meta.properties)){
78
83
  let prop = this.entitySchema.meta.properties[propName];
79
- if(prop.kind === 'm:1' || prop.kind === '1:m' || prop.kind === 'm:n'){
84
+ if('m:1' === prop.kind || '1:1' === prop.kind || '1:m' === prop.kind || 'm:n' === prop.kind){
80
85
  relations.push(propName);
81
86
  }
82
87
  }
83
88
  return relations;
84
89
  }
85
90
 
86
- async create(params)
91
+ isOwningRelation(prop)
87
92
  {
88
- let transformed = this.transformFkToRelations(params);
89
- let newInstance = await this.orm.em.create(this.entitySchema, transformed);
90
- await this.orm.em.upsert(newInstance);
91
- await this.orm.em.flush();
93
+ if('m:1' === prop.kind){
94
+ return true;
95
+ }
96
+ return '1:1' === prop.kind && Boolean(prop.owner);
97
+ }
98
+
99
+ isInverseRelation(prop)
100
+ {
101
+ if('1:m' === prop.kind){
102
+ return true;
103
+ }
104
+ return '1:1' === prop.kind && !prop.owner;
105
+ }
106
+
107
+ inverseRelationOwnerJoinColumn(prop, relationDriver)
108
+ {
109
+ if(!this.isInverseRelation(prop)){
110
+ return false;
111
+ }
112
+ if(!prop.mappedBy){
113
+ return false;
114
+ }
115
+ if(!relationDriver.entitySchema){
116
+ return false;
117
+ }
118
+ if(!relationDriver.entitySchema.meta){
119
+ return false;
120
+ }
121
+ let mappedProp = relationDriver.entitySchema.meta.properties[prop.mappedBy];
122
+ if(!mappedProp){
123
+ return false;
124
+ }
125
+ if(!mappedProp.joinColumns){
126
+ return false;
127
+ }
128
+ return mappedProp.joinColumns[0] || false;
129
+ }
130
+
131
+ nestedFkColumn(prop, relationDriver)
132
+ {
133
+ if(!this.isOwningRelation(prop)){
134
+ return this.inverseRelationOwnerJoinColumn(prop, relationDriver);
135
+ }
136
+ if(!prop.joinColumns){
137
+ return false;
138
+ }
139
+ return prop.joinColumns[0] || false;
140
+ }
141
+
142
+ removeRelationsFromParams(params)
143
+ {
144
+ if(!params || 'object' !== typeof params){
145
+ return params;
146
+ }
147
+ let columnsOnly = {...params};
148
+ for(let relationName of this.getAllRelations()){
149
+ delete columnsOnly[relationName];
150
+ }
151
+ return columnsOnly;
152
+ }
153
+
154
+ async createEntity(params)
155
+ {
156
+ let writeManager = this.orm.em.fork();
157
+ let newInstance = await writeManager.create(this.entitySchema, this.removeRelationsFromParams(params));
158
+ await writeManager.upsert(newInstance);
159
+ await writeManager.flush();
92
160
  return newInstance;
93
161
  }
94
162
 
95
- async createWithRelations(params, relations)
163
+ async create(params)
96
164
  {
97
- if(!sc.isArray(relations) || 0 === relations.length){
98
- relations = this.getAllRelations();
165
+ return this.withoutUninitializedCollections(await this.createEntity(params));
166
+ }
167
+
168
+ withoutUninitializedCollections(entity)
169
+ {
170
+ if(!entity || !this.Collection){
171
+ return entity;
99
172
  }
100
- let newInstance = await this.create(params);
173
+ let result = {};
174
+ for(let key of Object.keys(entity)){
175
+ if(entity[key] instanceof this.Collection && !entity[key].isInitialized()){
176
+ continue;
177
+ }
178
+ result[key] = entity[key];
179
+ }
180
+ return result;
181
+ }
182
+
183
+ async createWithRelations(params, relations)
184
+ {
185
+ relations = this.normalizeRelations(relations);
186
+ let newInstance = await this.createEntity(params);
101
187
  await this.createNested(newInstance, params, relations);
102
188
  return await this.appendRelationsToCollection(newInstance, relations);
103
189
  }
@@ -108,9 +194,9 @@ class MikroOrmDriver extends BaseDriver
108
194
  if(0 === entities.length){
109
195
  return false;
110
196
  }
111
- let transformed = this.transformFkToRelations(updatePatch);
197
+ let columnsOnly = this.removeRelationsFromParams(updatePatch);
112
198
  for(let entity of entities){
113
- Object.assign(entity, transformed);
199
+ Object.assign(entity, columnsOnly);
114
200
  await this.orm.em.upsert(entity);
115
201
  await this.orm.em.flush();
116
202
  }
@@ -139,7 +225,7 @@ class MikroOrmDriver extends BaseDriver
139
225
  if(1 < entities.length){
140
226
  Logger.warning('Multiple entities updated by ID: '+entities.map(e => e.id).join(', '));
141
227
  }
142
- return entities.shift();
228
+ return this.withoutUninitializedCollections(entities.shift());
143
229
  }
144
230
 
145
231
  async upsert(params, filters)
@@ -155,8 +241,7 @@ class MikroOrmDriver extends BaseDriver
155
241
  if(filters){
156
242
  let existent = await this.loadOne(filters);
157
243
  if(existent){
158
- let transformed = this.transformFkToRelations(params);
159
- return this.updateById(existent.id, transformed);
244
+ return this.updateById(existent.id, params);
160
245
  }
161
246
  }
162
247
  return this.create(params);
@@ -173,18 +258,18 @@ class MikroOrmDriver extends BaseDriver
173
258
  await this.orm.em.remove(entry);
174
259
  }
175
260
  await this.orm.em.flush();
176
- return true;
261
+ return entries.length;
177
262
  }
178
263
 
179
264
  async deleteById(id)
180
265
  {
181
- let entity = await this.loadById(id);
266
+ let entity = await this.repository.findOne(this.processFilters({id}));
182
267
  if(!entity){
183
268
  return false;
184
269
  }
185
270
  await this.orm.em.remove(entity);
186
271
  await this.orm.em.flush();
187
- return true;
272
+ return 1;
188
273
  }
189
274
 
190
275
  async count(filters)
@@ -195,9 +280,7 @@ class MikroOrmDriver extends BaseDriver
195
280
 
196
281
  async countWithRelations(filters, relations)
197
282
  {
198
- if(!sc.isArray(relations) || 0 === relations.length){
199
- relations = this.getAllRelations();
200
- }
283
+ relations = this.normalizeRelations(relations);
201
284
  let processedFilters = this.processFilters(filters);
202
285
  if(0 === relations.length || !this.entitySchema.meta || !this.entitySchema.meta.properties){
203
286
  return this.repository.count(processedFilters);
@@ -207,22 +290,20 @@ class MikroOrmDriver extends BaseDriver
207
290
  let aliasCounter = 0;
208
291
  for(let relationName of relations){
209
292
  let prop = properties[relationName];
210
- if(!prop || !prop.kind || (prop.kind !== 'm:1' && prop.kind !== '1:m')){
293
+ if(!prop || !prop.kind || (!this.isOwningRelation(prop) && !this.isInverseRelation(prop))){
211
294
  continue;
212
295
  }
213
- let relationDriver = this.server.entityManager.get(prop.entity);
296
+ let relationDriver = this.relationDriverFor(prop);
214
297
  if(!relationDriver || !relationDriver.entitySchema){
215
298
  continue;
216
299
  }
217
300
  let alias = 'rel_'+aliasCounter;
218
- if('m:1' === prop.kind && prop.joinColumns && prop.joinColumns[0]){
301
+ if(this.isOwningRelation(prop) && prop.joinColumns && prop.joinColumns[0]){
219
302
  queryBuilder.leftJoin(relationName, alias, 'e.'+prop.joinColumns[0]+' = '+alias+'.id');
220
303
  }
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
- }
304
+ let ownerJoinColumn = this.inverseRelationOwnerJoinColumn(prop, relationDriver);
305
+ if(ownerJoinColumn){
306
+ queryBuilder.leftJoin(relationName, alias, 'e.id = '+alias+'.'+ownerJoinColumn);
226
307
  }
227
308
  aliasCounter++;
228
309
  }
@@ -232,106 +313,180 @@ class MikroOrmDriver extends BaseDriver
232
313
  return queryBuilder.getCount();
233
314
  }
234
315
 
235
- loadAll()
316
+ async loadAll()
236
317
  {
237
- return this.repository.findAll();
318
+ return this.toPlainResult(await this.repository.findAll());
238
319
  }
239
320
 
240
- async loadAllWithRelations(relations)
321
+ normalizeRelations(relations)
241
322
  {
323
+ if(sc.isString(relations)){
324
+ relations = this.parseRelationsString(relations);
325
+ }
242
326
  if(!sc.isArray(relations) || 0 === relations.length){
243
- relations = this.getAllRelations();
327
+ return this.getAllRelations();
244
328
  }
245
- let entities = await this.loadAll();
246
- return await this.appendRelationsToCollection(entities, relations);
329
+ return relations;
247
330
  }
248
331
 
249
- load(filters)
332
+ toPlainResult(result, populatedPaths, ancestors)
250
333
  {
251
- let processedFilters = this.processFilters(filters);
252
- return this.repository.find(processedFilters, this.queryBuilder(true, true, true));
334
+ if(!result || 'object' !== typeof result || result instanceof Date){
335
+ return result;
336
+ }
337
+ if(!ancestors){
338
+ ancestors = new Set();
339
+ }
340
+ if(!populatedPaths){
341
+ populatedPaths = {};
342
+ }
343
+ if(result instanceof this.Collection){
344
+ if(!result.isInitialized()){
345
+ return [];
346
+ }
347
+ return this.toPlainList(result.getItems(), populatedPaths, ancestors);
348
+ }
349
+ if(sc.isArray(result)){
350
+ return this.toPlainList(result, populatedPaths, ancestors);
351
+ }
352
+ return this.toPlainEntity(result, populatedPaths, ancestors);
253
353
  }
254
354
 
255
- async loadWithRelations(filters, relations)
355
+ toPlainList(result, populatedPaths, ancestors)
256
356
  {
257
- if(!sc.isArray(relations) || 0 === relations.length){
258
- relations = this.getAllRelations();
357
+ ancestors.add(result);
358
+ let plainList = [];
359
+ for(let item of result){
360
+ if(ancestors.has(item)){
361
+ continue;
362
+ }
363
+ plainList.push(this.toPlainResult(item, populatedPaths, ancestors));
259
364
  }
260
- let processedFilters = this.processFilters(filters);
261
- let entitiesCollection = await this.repository.find(processedFilters, this.queryBuilder(true, true, true));
262
- return await this.appendRelationsToCollection(entitiesCollection, relations);
365
+ ancestors.delete(result);
366
+ return plainList;
263
367
  }
264
368
 
265
- loadBy(field, fieldValue, operator = null)
369
+ toPlainEntity(result, populatedPaths, ancestors)
266
370
  {
267
- let filter = this.createSingleFilter(field, fieldValue, operator);
268
- let processedFilters = this.processFilters(filter);
269
- return this.repository.find(processedFilters, this.queryBuilder(true, true, true));
371
+ ancestors.add(result);
372
+ let relationKeys = this.getAllRelations();
373
+ let plainEntity = {};
374
+ for(let key of Object.keys(result)){
375
+ if(ancestors.has(result[key])){
376
+ continue;
377
+ }
378
+ if(-1 === relationKeys.indexOf(key)){
379
+ plainEntity[key] = result[key];
380
+ continue;
381
+ }
382
+ if(!populatedPaths[key]){
383
+ continue;
384
+ }
385
+ plainEntity[key] = this.toPlainRelation(key, result[key], populatedPaths[key], ancestors);
386
+ }
387
+ ancestors.delete(result);
388
+ return plainEntity;
270
389
  }
271
390
 
272
- async loadByWithRelations(field, fieldValue, relations, operator = null)
391
+ toPlainRelation(propName, relationValue, populatedPaths, ancestors)
273
392
  {
274
- if(!sc.isArray(relations) || 0 === relations.length){
275
- relations = this.getAllRelations();
393
+ let relationDriver = this.relationDriverFor(this.entitySchema.meta.properties[propName]);
394
+ if(!relationDriver){
395
+ return this.toPlainResult(relationValue, populatedPaths, ancestors);
276
396
  }
397
+ return relationDriver.toPlainResult(relationValue, populatedPaths, ancestors);
398
+ }
399
+
400
+ async plainRelationsResult(entitiesCollection, relations)
401
+ {
402
+ return this.toPlainResult(
403
+ await this.appendRelationsToCollection(entitiesCollection, relations),
404
+ this.populatedPathsTree(relations)
405
+ );
406
+ }
407
+
408
+ findEntities(filters)
409
+ {
410
+ return this.repository.find(this.processFilters(filters), this.queryBuilder(true, true, true));
411
+ }
412
+
413
+ findOneEntity(filters)
414
+ {
415
+ return this.repository.findOne(this.processFilters(filters), this.queryBuilder(false, true, true));
416
+ }
417
+
418
+ async loadAllWithRelations(relations)
419
+ {
420
+ relations = this.normalizeRelations(relations);
421
+ let entities = await this.repository.findAll();
422
+ return this.plainRelationsResult(entities, relations);
423
+ }
424
+
425
+ async load(filters)
426
+ {
427
+ return this.toPlainResult(await this.findEntities(filters));
428
+ }
429
+
430
+ async loadWithRelations(filters, relations)
431
+ {
432
+ relations = this.normalizeRelations(relations);
433
+ let entitiesCollection = await this.findEntities(filters);
434
+ return this.plainRelationsResult(entitiesCollection, relations);
435
+ }
436
+
437
+ async loadBy(field, fieldValue, operator = null)
438
+ {
439
+ return this.toPlainResult(await this.findEntities(this.createSingleFilter(field, fieldValue, operator)));
440
+ }
441
+
442
+ async loadByWithRelations(field, fieldValue, relations, operator = null)
443
+ {
444
+ relations = this.normalizeRelations(relations);
277
445
  let filter = this.createSingleFilter(field, fieldValue, operator);
278
- let processedFilters = this.processFilters(filter);
279
- let entitiesCollection = await this.repository.find(processedFilters, this.queryBuilder(true, true, true));
280
- return await this.appendRelationsToCollection(entitiesCollection, relations);
446
+ let entitiesCollection = await this.findEntities(filter);
447
+ return this.plainRelationsResult(entitiesCollection, relations);
281
448
  }
282
449
 
283
- loadById(id)
450
+ async loadById(id)
284
451
  {
285
- return this.loadOneBy('id', id);
452
+ return this.toPlainResult(await this.findOneEntity({id}));
286
453
  }
287
454
 
288
455
  async loadByIdWithRelations(id, relations)
289
456
  {
290
- if(!sc.isArray(relations) || 0 === relations.length){
291
- relations = this.getAllRelations();
292
- }
293
- let entity = await this.loadOneBy('id', id);
294
- return await this.appendRelationsToCollection(entity, relations);
457
+ relations = this.normalizeRelations(relations);
458
+ let entity = await this.findOneEntity({id});
459
+ return this.plainRelationsResult(entity, relations);
295
460
  }
296
461
 
297
- loadByIds(ids)
462
+ async loadByIds(ids)
298
463
  {
299
- let processedFilters = this.processFilters({id: {$in: ids}});
300
- return this.repository.find(processedFilters);
464
+ return this.toPlainResult(await this.repository.find(this.processFilters({id: {$in: ids}})));
301
465
  }
302
466
 
303
- loadOne(filters)
467
+ async loadOne(filters)
304
468
  {
305
- let processedFilters = this.processFilters(filters);
306
- return this.repository.findOne(processedFilters, this.queryBuilder(false, true, true));
469
+ return this.toPlainResult(await this.findOneEntity(filters));
307
470
  }
308
471
 
309
472
  async loadOneWithRelations(filters, relations)
310
473
  {
311
- if(!sc.isArray(relations) || 0 === relations.length){
312
- relations = this.getAllRelations();
313
- }
314
- let processedFilters = this.processFilters(filters);
315
- let entitiesCollection = await this.repository.findOne(processedFilters, this.queryBuilder(false, true, true));
316
- return this.appendRelationsToCollection(entitiesCollection, relations);
474
+ relations = this.normalizeRelations(relations);
475
+ let entitiesCollection = await this.findOneEntity(filters);
476
+ return this.plainRelationsResult(entitiesCollection, relations);
317
477
  }
318
478
 
319
- loadOneBy(field, fieldValue, operator = null)
479
+ async loadOneBy(field, fieldValue, operator = null)
320
480
  {
321
- let filter = this.createSingleFilter(field, fieldValue, operator);
322
- let processedFilters = this.processFilters(filter);
323
- return this.repository.findOne(processedFilters, this.queryBuilder(false, true, true));
481
+ return this.toPlainResult(await this.findOneEntity(this.createSingleFilter(field, fieldValue, operator)));
324
482
  }
325
483
 
326
484
  async loadOneByWithRelations(field, fieldValue, relations, operator = null)
327
485
  {
328
- if(!sc.isArray(relations) || 0 === relations.length){
329
- relations = this.getAllRelations();
330
- }
486
+ relations = this.normalizeRelations(relations);
331
487
  let filter = this.createSingleFilter(field, fieldValue, operator);
332
- let processedFilters = this.processFilters(filter);
333
- let entitiesCollection = await this.repository.findOne(processedFilters, this.queryBuilder(false, true, true));
334
- return this.appendRelationsToCollection(entitiesCollection, relations);
488
+ let entitiesCollection = await this.findOneEntity(filter);
489
+ return this.plainRelationsResult(entitiesCollection, relations);
335
490
  }
336
491
 
337
492
  processFilters(filters)
@@ -342,6 +497,9 @@ class MikroOrmDriver extends BaseDriver
342
497
  let processedFilters = {};
343
498
  for(let key of Object.keys(filters)){
344
499
  let value = filters[key];
500
+ if(('AND' === key || 'OR' === key) && sc.isArray(value) && 0 === value.length){
501
+ continue;
502
+ }
345
503
  if('AND' === key && sc.isArray(value)){
346
504
  processedFilters.$and = value.map(condition => this.processFilters(condition));
347
505
  continue;
@@ -418,15 +576,39 @@ class MikroOrmDriver extends BaseDriver
418
576
  return filter;
419
577
  }
420
578
 
421
- convertCollectionsToArrays(entity)
579
+ populatedPathsTree(relations)
580
+ {
581
+ let tree = {};
582
+ if(!sc.isArray(relations)){
583
+ return tree;
584
+ }
585
+ for(let relationPath of relations){
586
+ let node = tree;
587
+ for(let pathPart of String(relationPath).split('.')){
588
+ if(!node[pathPart]){
589
+ node[pathPart] = {};
590
+ }
591
+ node = node[pathPart];
592
+ }
593
+ }
594
+ return tree;
595
+ }
596
+
597
+ convertCollectionsToArrays(entity, populatedPaths)
422
598
  {
423
599
  if(!entity){
424
600
  return entity;
425
601
  }
426
- for(let key of Object.keys(entity)){
427
- if(entity[key] && entity[key] instanceof Collection){
428
- entity[key] = entity[key].getItems();
602
+ for(let key of Object.keys(populatedPaths)){
603
+ if(!entity[key] || !(entity[key] instanceof this.Collection)){
604
+ continue;
429
605
  }
606
+ if(!entity[key].isInitialized()){
607
+ Logger.warning('Relation "'+key+'" was not populated on '+this.tableName()+', it will be empty.');
608
+ entity[key] = [];
609
+ continue;
610
+ }
611
+ entity[key] = entity[key].getItems();
430
612
  }
431
613
  return entity;
432
614
  }
@@ -462,13 +644,104 @@ class MikroOrmDriver extends BaseDriver
462
644
  return entitiesCollection;
463
645
  }
464
646
  await this.orm.em.populate(entitiesCollection, relations);
647
+ let populatedPaths = this.populatedPathsTree(relations);
465
648
  if(sc.isArray(entitiesCollection)){
466
649
  for(let entity of entitiesCollection){
467
- this.convertCollectionsToArrays(entity);
650
+ this.restoreRelatedForeignKeys(entity, new Set(), populatedPaths);
651
+ await this.resolveKeyMappedRelations(entity, populatedPaths);
468
652
  }
469
653
  return entitiesCollection;
470
654
  }
471
- return this.convertCollectionsToArrays(entitiesCollection);
655
+ this.restoreRelatedForeignKeys(entitiesCollection, new Set(), populatedPaths);
656
+ await this.resolveKeyMappedRelations(entitiesCollection, populatedPaths);
657
+ return entitiesCollection;
658
+ }
659
+
660
+ async resolveKeyMappedRelations(entity, populatedPaths)
661
+ {
662
+ if(!entity || !this.entitySchema.meta || !this.entitySchema.meta.properties){
663
+ return entity;
664
+ }
665
+ for(let propName of Object.keys(populatedPaths)){
666
+ let prop = this.entitySchema.meta.properties[propName];
667
+ if(!prop || !prop.mapToPk || !prop.targetKey || sc.isObject(entity[propName])){
668
+ continue;
669
+ }
670
+ if(null === entity[propName] || 'undefined' === typeof entity[propName]){
671
+ continue;
672
+ }
673
+ let relationDriver = this.relationDriverFor(prop);
674
+ if(!relationDriver){
675
+ continue;
676
+ }
677
+ entity[propName] = await relationDriver.loadOneBy(prop.targetKey, entity[propName]);
678
+ }
679
+ return entity;
680
+ }
681
+
682
+ restoreRelatedForeignKeys(entity, visited, populatedPaths)
683
+ {
684
+ if(!entity || visited.has(entity)){
685
+ return;
686
+ }
687
+ visited.add(entity);
688
+ this.convertCollectionsToArrays(entity, populatedPaths);
689
+ this.addFkFieldsFromRelations(entity);
690
+ if(!this.entitySchema.meta || !this.entitySchema.meta.properties){
691
+ return;
692
+ }
693
+ for(let propName of Object.keys(populatedPaths)){
694
+ let relationDriver = this.relationDriverFor(this.entitySchema.meta.properties[propName]);
695
+ if(!relationDriver){
696
+ continue;
697
+ }
698
+ relationDriver.restoreRelatedEntities(entity[propName], visited, populatedPaths[propName]);
699
+ }
700
+ }
701
+
702
+ restoreRelatedEntities(relatedValue, visited, populatedPaths)
703
+ {
704
+ if(!relatedValue || relatedValue instanceof this.Collection){
705
+ return;
706
+ }
707
+ if(sc.isArray(relatedValue)){
708
+ for(let relatedEntity of relatedValue){
709
+ this.restoreRelatedForeignKeys(relatedEntity, visited, populatedPaths);
710
+ }
711
+ return;
712
+ }
713
+ this.restoreRelatedForeignKeys(relatedValue, visited, populatedPaths);
714
+ }
715
+
716
+ relationTargetClass(prop)
717
+ {
718
+ if(!sc.isFunction(prop?.entity)){
719
+ return false;
720
+ }
721
+ return prop.entity();
722
+ }
723
+
724
+ relationDriverFor(prop)
725
+ {
726
+ let targetClass = this.relationTargetClass(prop);
727
+ let targetClassName = (prop && prop.targetMeta) ? prop.targetMeta.className : false;
728
+ if(!targetClass && !targetClassName){
729
+ return false;
730
+ }
731
+ let entities = this.server.entityManager.entities;
732
+ for(let entityKey of Object.keys(entities)){
733
+ let candidate = entities[entityKey];
734
+ if(!candidate || !candidate.entitySchema || !candidate.entitySchema.meta){
735
+ continue;
736
+ }
737
+ if(targetClass && candidate.entitySchema.meta.class === targetClass){
738
+ return candidate;
739
+ }
740
+ if(targetClassName && candidate.entitySchema.meta.className === targetClassName){
741
+ return candidate;
742
+ }
743
+ }
744
+ return false;
472
745
  }
473
746
 
474
747
  async createNested(newInstance, params, relations)
@@ -479,36 +752,42 @@ class MikroOrmDriver extends BaseDriver
479
752
  let properties = this.entitySchema.meta.properties;
480
753
  for(let relationName of Object.keys(properties)){
481
754
  let prop = properties[relationName];
482
- if(!prop.kind || (prop.kind !== 'm:1' && prop.kind !== '1:m')){
755
+ if(!prop.kind || (!this.isOwningRelation(prop) && !this.isInverseRelation(prop))){
483
756
  continue;
484
757
  }
485
758
  if(sc.isArray(relations) && 0 < relations.length && -1 === relations.indexOf(relationName)){
486
759
  continue;
487
760
  }
488
- let relationDriver = this.server.entityManager.get(prop.entity);
761
+ if(!sc.hasOwn(params, relationName)){
762
+ continue;
763
+ }
764
+ let relationDriver = this.relationDriverFor(prop);
489
765
  if(!relationDriver){
490
- Logger.warning('Entity not found for relation:', prop.entity);
766
+ Logger.warning('Entity not found for relation: '+relationName);
491
767
  continue;
492
768
  }
493
- let isOneToMany = '1:m' === prop.kind;
494
- let isManyToOne = 'm:1' === prop.kind;
495
- if(isManyToOne && sc.hasOwn(params, relationName)){
769
+ if(this.isOwningRelation(prop)){
496
770
  await this.createOne(params, relationName, relationDriver, newInstance, prop);
497
771
  continue;
498
772
  }
499
- if(isOneToMany && sc.hasOwn(params, relationName) && sc.isArray(params[relationName])){
773
+ if(sc.isArray(params[relationName])){
500
774
  await this.createMany(params, relationName, relationDriver, newInstance, prop);
775
+ continue;
776
+ }
777
+ if('1:1' === prop.kind && sc.isObject(params[relationName])){
778
+ await this.createOne(params, relationName, relationDriver, newInstance, prop);
501
779
  }
502
780
  }
503
781
  }
504
782
 
505
783
  async createOne(params, relationName, relationDriver, newInstance, prop)
506
784
  {
507
- let nestedData = params[relationName];
508
- if(prop.joinColumns && prop.joinColumns[0]){
509
- nestedData[prop.joinColumns[0]] = newInstance.id;
785
+ let nestedData = Object.assign({}, params[relationName]);
786
+ let fkColumn = this.nestedFkColumn(prop, relationDriver);
787
+ if(fkColumn){
788
+ nestedData[fkColumn] = newInstance.id;
510
789
  }
511
- let nestedObject = await relationDriver.create(nestedData);
790
+ let nestedObject = await relationDriver.createEntity(nestedData);
512
791
  await this.orm.em.flush();
513
792
  newInstance[relationName] = nestedObject;
514
793
  }
@@ -516,34 +795,19 @@ class MikroOrmDriver extends BaseDriver
516
795
  async createMany(params, relationName, relationDriver, newInstance, prop)
517
796
  {
518
797
  let nestedArray = [];
798
+ let fkColumn = this.nestedFkColumn(prop, relationDriver);
519
799
  for(let objectData of params[relationName]){
520
- if(prop.joinColumns && prop.joinColumns[0]){
521
- objectData[prop.joinColumns[0]] = newInstance.id;
800
+ let nestedData = Object.assign({}, objectData);
801
+ if(fkColumn){
802
+ nestedData[fkColumn] = newInstance.id;
522
803
  }
523
- let nestedObject = await relationDriver.create(objectData);
804
+ let nestedObject = await relationDriver.createEntity(nestedData);
524
805
  await this.orm.em.flush();
525
806
  nestedArray.push(nestedObject);
526
807
  }
527
808
  newInstance[relationName] = nestedArray;
528
809
  }
529
810
 
530
- transformFkToRelations(params)
531
- {
532
- if(!params || 'object' !== typeof params){
533
- return params;
534
- }
535
- let transformed = {...params};
536
- for(let fkColumn of Object.keys(this.fkMappings)){
537
- if(!sc.hasOwn(params, fkColumn) || null === params[fkColumn]){
538
- continue;
539
- }
540
- let mapping = this.fkMappings[fkColumn];
541
- transformed[mapping.relationKey] = this.orm.em.getReference(mapping.entityName, params[fkColumn]);
542
- delete transformed[fkColumn];
543
- }
544
- return transformed;
545
- }
546
-
547
811
  }
548
812
 
549
813
  module.exports.MikroOrmDriver = MikroOrmDriver;