@mikro-orm/core 7.0.0-dev.99 → 7.0.0-rc.1

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 (107) hide show
  1. package/EntityManager.d.ts +34 -17
  2. package/EntityManager.js +95 -103
  3. package/MikroORM.d.ts +5 -5
  4. package/MikroORM.js +25 -20
  5. package/cache/FileCacheAdapter.js +11 -3
  6. package/connections/Connection.d.ts +3 -2
  7. package/connections/Connection.js +4 -3
  8. package/drivers/DatabaseDriver.d.ts +11 -11
  9. package/drivers/DatabaseDriver.js +91 -25
  10. package/drivers/IDatabaseDriver.d.ts +50 -20
  11. package/entity/BaseEntity.d.ts +61 -1
  12. package/entity/Collection.d.ts +8 -1
  13. package/entity/Collection.js +12 -13
  14. package/entity/EntityAssigner.js +9 -9
  15. package/entity/EntityFactory.d.ts +6 -1
  16. package/entity/EntityFactory.js +40 -22
  17. package/entity/EntityHelper.d.ts +2 -2
  18. package/entity/EntityHelper.js +27 -4
  19. package/entity/EntityLoader.d.ts +5 -4
  20. package/entity/EntityLoader.js +193 -80
  21. package/entity/EntityRepository.d.ts +27 -7
  22. package/entity/EntityRepository.js +8 -2
  23. package/entity/PolymorphicRef.d.ts +12 -0
  24. package/entity/PolymorphicRef.js +18 -0
  25. package/entity/WrappedEntity.d.ts +2 -2
  26. package/entity/WrappedEntity.js +1 -1
  27. package/entity/defineEntity.d.ts +89 -50
  28. package/entity/defineEntity.js +12 -0
  29. package/entity/index.d.ts +1 -0
  30. package/entity/index.js +1 -0
  31. package/entity/utils.d.ts +6 -1
  32. package/entity/utils.js +33 -0
  33. package/entity/validators.js +2 -2
  34. package/enums.d.ts +2 -2
  35. package/enums.js +1 -0
  36. package/errors.d.ts +16 -8
  37. package/errors.js +40 -13
  38. package/hydration/ObjectHydrator.js +63 -21
  39. package/index.d.ts +1 -1
  40. package/logging/colors.d.ts +1 -1
  41. package/logging/colors.js +7 -6
  42. package/logging/inspect.js +1 -6
  43. package/metadata/EntitySchema.d.ts +43 -13
  44. package/metadata/EntitySchema.js +82 -27
  45. package/metadata/MetadataDiscovery.d.ts +60 -3
  46. package/metadata/MetadataDiscovery.js +665 -154
  47. package/metadata/MetadataProvider.js +3 -1
  48. package/metadata/MetadataStorage.d.ts +13 -6
  49. package/metadata/MetadataStorage.js +64 -19
  50. package/metadata/MetadataValidator.d.ts +32 -2
  51. package/metadata/MetadataValidator.js +196 -31
  52. package/metadata/discover-entities.js +5 -5
  53. package/metadata/types.d.ts +111 -14
  54. package/naming-strategy/AbstractNamingStrategy.d.ts +11 -3
  55. package/naming-strategy/AbstractNamingStrategy.js +12 -0
  56. package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
  57. package/naming-strategy/EntityCaseNamingStrategy.js +6 -5
  58. package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
  59. package/naming-strategy/MongoNamingStrategy.js +6 -6
  60. package/naming-strategy/NamingStrategy.d.ts +17 -3
  61. package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
  62. package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
  63. package/package.json +2 -2
  64. package/platforms/Platform.d.ts +4 -2
  65. package/platforms/Platform.js +5 -2
  66. package/serialization/EntitySerializer.d.ts +3 -0
  67. package/serialization/EntitySerializer.js +15 -13
  68. package/serialization/EntityTransformer.js +6 -6
  69. package/serialization/SerializationContext.d.ts +6 -6
  70. package/typings.d.ts +325 -110
  71. package/typings.js +84 -17
  72. package/unit-of-work/ChangeSet.d.ts +4 -3
  73. package/unit-of-work/ChangeSet.js +2 -3
  74. package/unit-of-work/ChangeSetComputer.d.ts +3 -6
  75. package/unit-of-work/ChangeSetComputer.js +34 -13
  76. package/unit-of-work/ChangeSetPersister.d.ts +12 -10
  77. package/unit-of-work/ChangeSetPersister.js +55 -25
  78. package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
  79. package/unit-of-work/CommitOrderCalculator.js +13 -13
  80. package/unit-of-work/IdentityMap.d.ts +12 -0
  81. package/unit-of-work/IdentityMap.js +39 -1
  82. package/unit-of-work/UnitOfWork.d.ts +21 -3
  83. package/unit-of-work/UnitOfWork.js +203 -56
  84. package/utils/AbstractSchemaGenerator.js +17 -8
  85. package/utils/AsyncContext.d.ts +6 -0
  86. package/utils/AsyncContext.js +42 -0
  87. package/utils/Configuration.d.ts +52 -11
  88. package/utils/Configuration.js +12 -8
  89. package/utils/Cursor.js +21 -8
  90. package/utils/DataloaderUtils.js +13 -11
  91. package/utils/EntityComparator.d.ts +14 -7
  92. package/utils/EntityComparator.js +132 -46
  93. package/utils/QueryHelper.d.ts +16 -6
  94. package/utils/QueryHelper.js +53 -18
  95. package/utils/RawQueryFragment.d.ts +28 -23
  96. package/utils/RawQueryFragment.js +34 -56
  97. package/utils/RequestContext.js +2 -2
  98. package/utils/TransactionContext.js +2 -2
  99. package/utils/TransactionManager.js +1 -1
  100. package/utils/Utils.d.ts +7 -26
  101. package/utils/Utils.js +25 -79
  102. package/utils/clone.js +7 -21
  103. package/utils/env-vars.d.ts +4 -0
  104. package/utils/env-vars.js +13 -3
  105. package/utils/fs-utils.d.ts +21 -0
  106. package/utils/fs-utils.js +106 -11
  107. package/utils/upsert-utils.d.ts +4 -4
@@ -2,10 +2,13 @@ import { clone } from './clone.js';
2
2
  import { ReferenceKind } from '../enums.js';
3
3
  import { compareArrays, compareBooleans, compareBuffers, compareObjects, equals, parseJsonSafe, Utils } from './Utils.js';
4
4
  import { JsonType } from '../types/JsonType.js';
5
- import { RawQueryFragment } from './RawQueryFragment.js';
5
+ import { Raw } from './RawQueryFragment.js';
6
+ import { EntityIdentifier } from '../entity/EntityIdentifier.js';
7
+ import { PolymorphicRef } from '../entity/PolymorphicRef.js';
6
8
  export class EntityComparator {
7
9
  metadata;
8
10
  platform;
11
+ config;
9
12
  comparators = new Map();
10
13
  mappers = new Map();
11
14
  snapshotGenerators = new Map();
@@ -13,9 +16,10 @@ export class EntityComparator {
13
16
  pkGettersConverted = new Map();
14
17
  pkSerializers = new Map();
15
18
  tmpIndex = 0;
16
- constructor(metadata, platform) {
19
+ constructor(metadata, platform, config) {
17
20
  this.metadata = metadata;
18
21
  this.platform = platform;
22
+ this.config = config;
19
23
  }
20
24
  /**
21
25
  * Computes difference between two entities.
@@ -33,21 +37,21 @@ export class EntityComparator {
33
37
  * References will be mapped to primary keys, collections to arrays of primary keys.
34
38
  */
35
39
  prepareEntity(entity) {
36
- const generator = this.getSnapshotGenerator(entity.constructor.name);
40
+ const generator = this.getSnapshotGenerator(entity.constructor);
37
41
  return Utils.callCompiledFunction(generator, entity);
38
42
  }
39
43
  /**
40
44
  * Maps database columns to properties.
41
45
  */
42
- mapResult(entityName, result) {
43
- const mapper = this.getResultMapper(entityName);
46
+ mapResult(meta, result) {
47
+ const mapper = this.getResultMapper(meta);
44
48
  return Utils.callCompiledFunction(mapper, result);
45
49
  }
46
50
  /**
47
51
  * @internal Highly performance-sensitive method.
48
52
  */
49
53
  getPkGetter(meta) {
50
- const exists = this.pkGetters.get(meta.className);
54
+ const exists = this.pkGetters.get(meta);
51
55
  /* v8 ignore next */
52
56
  if (exists) {
53
57
  return exists;
@@ -87,17 +91,18 @@ export class EntityComparator {
87
91
  }
88
92
  lines.push(` return entity${this.wrap(pk)};`);
89
93
  }
90
- const code = `// compiled pk serializer for entity ${meta.className}\n`
94
+ const code = `// compiled pk getter for entity ${meta.className}\n`
91
95
  + `return function(entity) {\n${lines.join('\n')}\n}`;
92
- const pkSerializer = Utils.createFunction(context, code);
93
- this.pkGetters.set(meta.className, pkSerializer);
96
+ const fnKey = `pkGetter-${meta.uniqueName}`;
97
+ const pkSerializer = Utils.createFunction(context, code, this.config?.get('compiledFunctions'), fnKey);
98
+ this.pkGetters.set(meta, pkSerializer);
94
99
  return pkSerializer;
95
100
  }
96
101
  /**
97
102
  * @internal Highly performance-sensitive method.
98
103
  */
99
104
  getPkGetterConverted(meta) {
100
- const exists = this.pkGettersConverted.get(meta.className);
105
+ const exists = this.pkGettersConverted.get(meta);
101
106
  /* v8 ignore next */
102
107
  if (exists) {
103
108
  return exists;
@@ -139,15 +144,16 @@ export class EntityComparator {
139
144
  }
140
145
  const code = `// compiled pk getter (with converted custom types) for entity ${meta.className}\n`
141
146
  + `return function(entity) {\n${lines.join('\n')}\n}`;
142
- const pkSerializer = Utils.createFunction(context, code);
143
- this.pkGettersConverted.set(meta.className, pkSerializer);
147
+ const fnKey = `pkGetterConverted-${meta.uniqueName}`;
148
+ const pkSerializer = Utils.createFunction(context, code, this.config?.get('compiledFunctions'), fnKey);
149
+ this.pkGettersConverted.set(meta, pkSerializer);
144
150
  return pkSerializer;
145
151
  }
146
152
  /**
147
153
  * @internal Highly performance-sensitive method.
148
154
  */
149
155
  getPkSerializer(meta) {
150
- const exists = this.pkSerializers.get(meta.className);
156
+ const exists = this.pkSerializers.get(meta);
151
157
  /* v8 ignore next */
152
158
  if (exists) {
153
159
  return exists;
@@ -191,25 +197,25 @@ export class EntityComparator {
191
197
  }
192
198
  const code = `// compiled pk serializer for entity ${meta.className}\n`
193
199
  + `return function(entity) {\n${lines.join('\n')}\n}`;
194
- const pkSerializer = Utils.createFunction(context, code);
195
- this.pkSerializers.set(meta.className, pkSerializer);
200
+ const fnKey = `pkSerializer-${meta.uniqueName}`;
201
+ const pkSerializer = Utils.createFunction(context, code, this.config?.get('compiledFunctions'), fnKey);
202
+ this.pkSerializers.set(meta, pkSerializer);
196
203
  return pkSerializer;
197
204
  }
198
205
  /**
199
206
  * @internal Highly performance-sensitive method.
200
207
  */
201
208
  getSnapshotGenerator(entityName) {
202
- entityName = Utils.className(entityName);
203
- const exists = this.snapshotGenerators.get(entityName);
209
+ const meta = this.metadata.find(entityName);
210
+ const exists = this.snapshotGenerators.get(meta);
204
211
  if (exists) {
205
212
  return exists;
206
213
  }
207
- const meta = this.metadata.find(entityName);
208
214
  const lines = [];
209
215
  const context = new Map();
210
216
  context.set('clone', clone);
211
217
  context.set('cloneEmbeddable', (o) => this.platform.cloneEmbeddable(o)); // do not clone prototypes
212
- if (meta.discriminatorValue) {
218
+ if (meta.root.inheritanceType === 'sti' && meta.discriminatorValue) {
213
219
  lines.push(` ret${this.wrap(meta.root.discriminatorColumn)} = '${meta.discriminatorValue}'`);
214
220
  }
215
221
  const getRootProperty = (prop) => prop.embedded ? getRootProperty(meta.properties[prop.embedded[0]]) : prop;
@@ -221,8 +227,9 @@ export class EntityComparator {
221
227
  })
222
228
  .forEach(prop => lines.push(this.getPropertySnapshot(meta, prop, context, this.wrap(prop.name), this.wrap(prop.name), [prop.name])));
223
229
  const code = `return function(entity) {\n const ret = {};\n${lines.join('\n')}\n return ret;\n}`;
224
- const snapshotGenerator = Utils.createFunction(context, code);
225
- this.snapshotGenerators.set(entityName, snapshotGenerator);
230
+ const fnKey = `snapshotGenerator-${meta.uniqueName}`;
231
+ const snapshotGenerator = Utils.createFunction(context, code, this.config?.get('compiledFunctions'), fnKey);
232
+ this.snapshotGenerators.set(meta, snapshotGenerator);
226
233
  return snapshotGenerator;
227
234
  }
228
235
  /**
@@ -272,14 +279,14 @@ export class EntityComparator {
272
279
  /**
273
280
  * @internal Highly performance-sensitive method.
274
281
  */
275
- getResultMapper(entityName) {
276
- const exists = this.mappers.get(entityName);
282
+ getResultMapper(meta) {
283
+ const exists = this.mappers.get(meta);
277
284
  if (exists) {
278
285
  return exists;
279
286
  }
280
- const meta = this.metadata.get(entityName);
281
287
  const lines = [];
282
288
  const context = new Map();
289
+ context.set('PolymorphicRef', PolymorphicRef);
283
290
  const tz = this.platform.getTimezone();
284
291
  const parseDate = (key, value, padding = '') => {
285
292
  lines.push(`${padding} if (${value} == null || ${value} instanceof Date) {`);
@@ -306,12 +313,28 @@ export class EntityComparator {
306
313
  if (!prop.fieldNames) {
307
314
  continue;
308
315
  }
316
+ if (prop.polymorphic && prop.fieldNames.length >= 2) {
317
+ const discriminatorField = prop.fieldNames[0];
318
+ const idFields = prop.fieldNames.slice(1);
319
+ lines.push(`${padding} if (${prop.fieldNames.map(field => `typeof ${this.propName(field)} === 'undefined'`).join(' && ')}) {`);
320
+ lines.push(`${padding} } else if (${prop.fieldNames.map(field => `${this.propName(field)} != null`).join(' && ')}) {`);
321
+ if (idFields.length === 1) {
322
+ lines.push(`${padding} ret${this.wrap(prop.name)} = new PolymorphicRef(${this.propName(discriminatorField)}, ${this.propName(idFields[0])});`);
323
+ }
324
+ else {
325
+ lines.push(`${padding} ret${this.wrap(prop.name)} = new PolymorphicRef(${this.propName(discriminatorField)}, [${idFields.map(f => this.propName(f)).join(', ')}]);`);
326
+ }
327
+ lines.push(...prop.fieldNames.map(field => `${padding} ${this.propName(field, 'mapped')} = true;`));
328
+ lines.push(`${padding} } else if (${prop.fieldNames.map(field => `${this.propName(field)} == null`).join(' && ')}) {\n${padding} ret${this.wrap(prop.name)} = null;`);
329
+ lines.push(...prop.fieldNames.map(field => `${padding} ${this.propName(field, 'mapped')} = true;`), ' }');
330
+ continue;
331
+ }
309
332
  if (prop.targetMeta && prop.fieldNames.length > 1) {
310
333
  lines.push(`${padding} if (${prop.fieldNames.map(field => `typeof ${this.propName(field)} === 'undefined'`).join(' && ')}) {`);
311
334
  lines.push(`${padding} } else if (${prop.fieldNames.map(field => `${this.propName(field)} != null`).join(' && ')}) {`);
312
335
  lines.push(`${padding} ret${this.wrap(prop.name)} = ${this.createCompositeKeyArray(prop)};`);
313
336
  lines.push(...prop.fieldNames.map(field => `${padding} ${this.propName(field, 'mapped')} = true;`));
314
- lines.push(`${padding} } else if (${prop.fieldNames.map(field => `${this.propName(field)} == null`).join(' && ')}) {\n ret${this.wrap(prop.name)} = null;`);
337
+ lines.push(`${padding} } else if (${prop.fieldNames.map(field => `${this.propName(field)} == null`).join(' && ')}) {\n${padding} ret${this.wrap(prop.name)} = null;`);
315
338
  lines.push(...prop.fieldNames.map(field => `${padding} ${this.propName(field, 'mapped')} = true;`), ' }');
316
339
  continue;
317
340
  }
@@ -336,9 +359,9 @@ export class EntityComparator {
336
359
  context.set(`mapEmbeddedResult_${idx}`, (data) => {
337
360
  const item = parseJsonSafe(data);
338
361
  if (Array.isArray(item)) {
339
- return item.map(row => row == null ? row : this.getResultMapper(prop.type)(row));
362
+ return item.map(row => row == null ? row : this.getResultMapper(prop.targetMeta)(row));
340
363
  }
341
- return item == null ? item : this.getResultMapper(prop.type)(item);
364
+ return item == null ? item : this.getResultMapper(prop.targetMeta)(item);
342
365
  });
343
366
  lines.push(`${padding} if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
344
367
  lines.push(`${padding} ret${this.wrap(prop.name)} = ${this.propName(prop.fieldNames[0])} == null ? ${this.propName(prop.fieldNames[0])} : mapEmbeddedResult_${idx}(${this.propName(prop.fieldNames[0])});`);
@@ -370,8 +393,9 @@ export class EntityComparator {
370
393
  lines.push(` for (let k in result) { if (Object.hasOwn(result, k) && !mapped[k] && ret[k] === undefined) ret[k] = result[k]; }`);
371
394
  const code = `// compiled mapper for entity ${meta.className}\n`
372
395
  + `return function(result) {\n const ret = {};\n${lines.join('\n')}\n return ret;\n}`;
373
- const resultMapper = Utils.createFunction(context, code);
374
- this.mappers.set(entityName, resultMapper);
396
+ const fnKey = `resultMapper-${meta.uniqueName}`;
397
+ const resultMapper = Utils.createFunction(context, code, this.config?.get('compiledFunctions'), fnKey);
398
+ this.mappers.set(meta, resultMapper);
375
399
  return resultMapper;
376
400
  }
377
401
  getPropertyCondition(path) {
@@ -482,7 +506,7 @@ export class EntityComparator {
482
506
  const convertorKey = this.safeKey(prop.name);
483
507
  context.set(`convertToDatabaseValue_${convertorKey}`, (val) => {
484
508
  /* v8 ignore next */
485
- if (RawQueryFragment.isKnownFragment(val)) {
509
+ if (Raw.isKnownFragment(val)) {
486
510
  return val;
487
511
  }
488
512
  return prop.customType.convertToDatabaseValue(val, this.platform, { mode: 'serialization' });
@@ -511,16 +535,51 @@ export class EntityComparator {
511
535
  ret += ` ret${dataKey} = entity${entityKey};\n`;
512
536
  }
513
537
  }
538
+ else if (prop.polymorphic) {
539
+ const discriminatorMapKey = `discriminatorMapReverse_${prop.name}`;
540
+ const reverseMap = new Map();
541
+ for (const [key, value] of Object.entries(prop.discriminatorMap)) {
542
+ reverseMap.set(value, key);
543
+ }
544
+ context.set(discriminatorMapKey, reverseMap);
545
+ this.setToArrayHelper(context);
546
+ context.set('EntityIdentifier', EntityIdentifier);
547
+ context.set('PolymorphicRef', PolymorphicRef);
548
+ ret += ` if (entity${entityKey} === null) {\n`;
549
+ ret += ` ret${dataKey} = null;\n`;
550
+ ret += ` } else if (typeof entity${entityKey} !== 'undefined') {\n`;
551
+ ret += ` const val${level} = entity${entityKey}${unwrap};\n`;
552
+ ret += ` const discriminator = ${discriminatorMapKey}.get(val${level}?.constructor);\n`;
553
+ ret += ` const pk = val${level}?.__helper?.__identifier && !val${level}?.__helper?.hasPrimaryKey()\n`;
554
+ ret += ` ? val${level}.__helper.__identifier\n`;
555
+ ret += ` : toArray(val${level}?.__helper?.getPrimaryKey(true));\n`;
556
+ ret += ` ret${dataKey} = new PolymorphicRef(discriminator, pk);\n`;
557
+ ret += ` }\n`;
558
+ }
559
+ else if (prop.targetKey) {
560
+ // When targetKey is set, extract that property value instead of the PK
561
+ const targetProp = prop.targetMeta?.properties[prop.targetKey];
562
+ ret += ` if (entity${entityKey} === null) {\n`;
563
+ ret += ` ret${dataKey} = null;\n`;
564
+ ret += ` } else if (typeof entity${entityKey} !== 'undefined') {\n`;
565
+ ret += ` const val${level} = entity${entityKey}${unwrap};\n`;
566
+ if (targetProp?.customType) {
567
+ // If targetKey property has a custom type, convert to database value
568
+ const convertorKey = this.registerCustomType(targetProp, context);
569
+ ret += ` ret${dataKey} = convertToDatabaseValue_${convertorKey}(val${level}?.${prop.targetKey});\n`;
570
+ }
571
+ else {
572
+ ret += ` ret${dataKey} = val${level}?.${prop.targetKey};\n`;
573
+ }
574
+ ret += ` }\n`;
575
+ }
514
576
  else {
515
- const toArray = (val) => {
516
- if (Utils.isPlainObject(val)) {
517
- return Object.values(val).map(v => toArray(v));
518
- }
519
- return val;
520
- };
521
- context.set('toArray', toArray);
577
+ this.setToArrayHelper(context);
578
+ context.set('EntityIdentifier', EntityIdentifier);
522
579
  ret += ` if (entity${entityKey} === null) {\n`;
523
580
  ret += ` ret${dataKey} = null;\n`;
581
+ ret += ` } else if (entity${entityKey}?.__helper.__identifier && !entity${entityKey}.__helper.hasPrimaryKey()) {\n`;
582
+ ret += ` ret${dataKey} = entity${entityKey}?.__helper.__identifier;\n`;
524
583
  ret += ` } else if (typeof entity${entityKey} !== 'undefined') {\n`;
525
584
  ret += ` ret${dataKey} = toArray(entity${entityKey}.__helper.getPrimaryKey(true));\n`;
526
585
  ret += ` }\n`;
@@ -544,11 +603,11 @@ export class EntityComparator {
544
603
  * @internal Highly performance-sensitive method.
545
604
  */
546
605
  getEntityComparator(entityName) {
547
- const exists = this.comparators.get(entityName);
606
+ const meta = this.metadata.find(entityName);
607
+ const exists = this.comparators.get(meta);
548
608
  if (exists) {
549
609
  return exists;
550
610
  }
551
- const meta = this.metadata.find(entityName);
552
611
  const lines = [];
553
612
  const context = new Map();
554
613
  context.set('compareArrays', compareArrays);
@@ -569,8 +628,9 @@ export class EntityComparator {
569
628
  lines.push(`}`);
570
629
  const code = `// compiled comparator for entity ${meta.className}\n`
571
630
  + `return function(last, current, options) {\n const diff = {};\n${lines.join('\n')}\n return diff;\n}`;
572
- const comparator = Utils.createFunction(context, code);
573
- this.comparators.set(entityName, comparator);
631
+ const fnKey = `comparator-${meta.uniqueName}`;
632
+ const comparator = Utils.createFunction(context, code, this.config?.get('compiledFunctions'), fnKey);
633
+ this.comparators.set(meta, comparator);
574
634
  return comparator;
575
635
  }
576
636
  getGenericComparator(prop, cond) {
@@ -586,18 +646,28 @@ export class EntityComparator {
586
646
  getPropertyComparator(prop, context) {
587
647
  let type = prop.type.toLowerCase();
588
648
  if (prop.kind !== ReferenceKind.SCALAR && prop.kind !== ReferenceKind.EMBEDDED) {
589
- const meta2 = this.metadata.find(prop.type);
590
- if (meta2.primaryKeys.length > 1) {
591
- type = 'array';
649
+ if (prop.polymorphic) {
650
+ type = 'object';
592
651
  }
593
652
  else {
594
- type = meta2.properties[meta2.primaryKeys[0]].type.toLowerCase();
653
+ const meta2 = prop.targetMeta;
654
+ if (meta2.primaryKeys.length > 1) {
655
+ type = 'array';
656
+ }
657
+ else {
658
+ type = meta2.getPrimaryProp().type.toLowerCase();
659
+ }
595
660
  }
596
661
  }
597
662
  if (prop.customType) {
598
663
  if (prop.customType.compareValues) {
599
664
  const idx = this.tmpIndex++;
600
- context.set(`compareValues_${idx}`, (a, b) => prop.customType.compareValues(a, b));
665
+ context.set(`compareValues_${idx}`, (a, b) => {
666
+ if (Raw.isKnownFragment(a) || Raw.isKnownFragment(b)) {
667
+ return Raw.getKnownFragment(a) === Raw.getKnownFragment(b);
668
+ }
669
+ return prop.customType.compareValues(a, b);
670
+ });
601
671
  return this.getGenericComparator(this.wrap(prop.name), `!compareValues_${idx}(last${this.wrap(prop.name)}, current${this.wrap(prop.name)})`);
602
672
  }
603
673
  type = prop.customType.compareAsType().toLowerCase();
@@ -638,6 +708,22 @@ export class EntityComparator {
638
708
  safeKey(key) {
639
709
  return key.replace(/\W/g, '_');
640
710
  }
711
+ /**
712
+ * Sets the toArray helper in the context if not already set.
713
+ * Used for converting composite PKs to arrays.
714
+ */
715
+ setToArrayHelper(context) {
716
+ if (context.has('toArray')) {
717
+ return;
718
+ }
719
+ const toArray = (val) => {
720
+ if (Utils.isPlainObject(val)) {
721
+ return Object.values(val).map(v => toArray(v));
722
+ }
723
+ return val;
724
+ };
725
+ context.set('toArray', toArray);
726
+ }
641
727
  /**
642
728
  * perf: used to generate list of comparable properties during discovery, so we speed up the runtime comparison
643
729
  */
@@ -1,12 +1,17 @@
1
- import type { Dictionary, EntityMetadata, EntityProperty, FilterDef, FilterQuery } from '../typings.js';
1
+ import type { Dictionary, EntityMetadata, EntityName, EntityProperty, FilterDef, FilterQuery } from '../typings.js';
2
+ import { type QueryOrderMap } from '../enums.js';
2
3
  import type { Platform } from '../platforms/Platform.js';
3
4
  import type { MetadataStorage } from '../metadata/MetadataStorage.js';
4
5
  import type { FilterOptions } from '../drivers/IDatabaseDriver.js';
5
6
  /** @internal */
6
7
  export declare class QueryHelper {
7
8
  static readonly SUPPORTED_OPERATORS: string[];
9
+ /**
10
+ * Finds the discriminator value (key) for a given entity class in a discriminator map.
11
+ */
12
+ static findDiscriminatorValue<T>(discriminatorMap: Dictionary<T>, targetClass: T): string | undefined;
8
13
  static processParams(params: unknown): any;
9
- static processObjectParams<T extends object>(params?: T): T;
14
+ static processObjectParams<T extends Dictionary>(params?: T): T;
10
15
  /**
11
16
  * converts `{ account: { $or: [ [Object], [Object] ] } }`
12
17
  * to `{ $or: [ { account: [Object] }, { account: [Object] } ] }`
@@ -14,22 +19,27 @@ export declare class QueryHelper {
14
19
  static liftGroupOperators<T extends object>(where: Dictionary, meta: EntityMetadata<T>, metadata: MetadataStorage, key?: string): string | undefined;
15
20
  static inlinePrimaryKeyObjects<T extends object>(where: Dictionary, meta: EntityMetadata<T>, metadata: MetadataStorage, key?: string): boolean;
16
21
  static processWhere<T extends object>(options: ProcessWhereOptions<T>): FilterQuery<T>;
17
- static getActiveFilters(entityName: string, options: FilterOptions | undefined, filters: Dictionary<FilterDef>): FilterDef[];
22
+ static getActiveFilters<T>(meta: EntityMetadata<T>, options: FilterOptions | undefined, filters: Dictionary<FilterDef>): FilterDef[];
18
23
  static mergePropertyFilters(propFilters: FilterOptions | undefined, options: FilterOptions | undefined): FilterOptions | undefined;
19
- static isFilterActive(entityName: string, filterName: string, filter: FilterDef, options: Dictionary<boolean | Dictionary>): boolean;
24
+ static isFilterActive<T>(meta: EntityMetadata<T>, filterName: string, filter: FilterDef, options: Dictionary<boolean | Dictionary>): boolean;
20
25
  static processCustomType<T extends object>(prop: EntityProperty<T>, cond: FilterQuery<T>, platform: Platform, key?: string, fromQuery?: boolean): FilterQuery<T>;
21
26
  private static isSupportedOperator;
22
27
  private static processJsonCondition;
23
28
  private static getValueType;
24
29
  static findProperty<T>(fieldName: string, options: ProcessWhereOptions<T>): EntityProperty<T> | undefined;
30
+ /**
31
+ * Merges multiple orderBy sources with key-level deduplication (first-seen key wins).
32
+ * RawQueryFragment symbol keys are never deduped (each is unique).
33
+ */
34
+ static mergeOrderBy<T>(...sources: (QueryOrderMap<T> | QueryOrderMap<T>[] | undefined)[]): QueryOrderMap<T>[];
25
35
  }
26
36
  interface ProcessWhereOptions<T> {
27
37
  where: FilterQuery<T>;
28
- entityName: string;
38
+ entityName: EntityName<T>;
29
39
  metadata: MetadataStorage;
30
40
  platform: Platform;
31
41
  aliased?: boolean;
32
- aliasMap?: Dictionary<string>;
42
+ aliasMap?: Dictionary<EntityName>;
33
43
  convertCustomTypes?: boolean;
34
44
  root?: boolean;
35
45
  type?: 'where' | 'orderBy';
@@ -3,10 +3,16 @@ import { Utils } from './Utils.js';
3
3
  import { ARRAY_OPERATORS, GroupOperator, JSON_KEY_OPERATORS, ReferenceKind } from '../enums.js';
4
4
  import { JsonType } from '../types/JsonType.js';
5
5
  import { helper } from '../entity/wrap.js';
6
- import { isRaw, RawQueryFragment } from './RawQueryFragment.js';
6
+ import { isRaw, Raw } from './RawQueryFragment.js';
7
7
  /** @internal */
8
8
  export class QueryHelper {
9
9
  static SUPPORTED_OPERATORS = ['>', '<', '<=', '>=', '!', '!='];
10
+ /**
11
+ * Finds the discriminator value (key) for a given entity class in a discriminator map.
12
+ */
13
+ static findDiscriminatorValue(discriminatorMap, targetClass) {
14
+ return Object.entries(discriminatorMap).find(([, cls]) => cls === targetClass)?.[0];
15
+ }
10
16
  static processParams(params) {
11
17
  if (Reference.isReference(params)) {
12
18
  params = params.unwrap();
@@ -29,7 +35,7 @@ export class QueryHelper {
29
35
  return params;
30
36
  }
31
37
  static processObjectParams(params = {}) {
32
- Utils.keys(params).forEach(k => {
38
+ Utils.getObjectQueryKeys(params).forEach(k => {
33
39
  params[k] = QueryHelper.processParams(params[k]);
34
40
  });
35
41
  return params;
@@ -62,7 +68,10 @@ export class QueryHelper {
62
68
  for (const k of keys) {
63
69
  const value = where[k];
64
70
  const prop = meta.properties[k];
65
- if (!prop || ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
71
+ // Polymorphic relations use multiple columns (discriminator + FK), so they cannot
72
+ // participate in the standard single-column FK expansion. Query by discriminator
73
+ // column directly instead, e.g. { likeableType: 'post', likeableId: 1 }.
74
+ if (!prop || ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) || prop.polymorphic) {
66
75
  continue;
67
76
  }
68
77
  const op = this.liftGroupOperators(value, prop.targetMeta, metadata, k);
@@ -98,7 +107,7 @@ export class QueryHelper {
98
107
  }));
99
108
  }
100
109
  Object.keys(where).forEach(k => {
101
- const meta2 = metadata.find(meta.properties[k]?.type) || meta;
110
+ const meta2 = metadata.find(meta.properties[k]?.targetMeta?.class) || meta;
102
111
  if (this.inlinePrimaryKeyObjects(where[k], meta2, metadata, k)) {
103
112
  where[k] = Utils.getPrimaryKeyValues(where[k], meta2, true);
104
113
  }
@@ -126,7 +135,7 @@ export class QueryHelper {
126
135
  where = { [Utils.getPrimaryKeyHash(meta.primaryKeys)]: where };
127
136
  }
128
137
  if (Array.isArray(where) && root) {
129
- const rootPrimaryKey = meta ? Utils.getPrimaryKeyHash(meta.primaryKeys) : entityName;
138
+ const rootPrimaryKey = meta ? Utils.getPrimaryKeyHash(meta.primaryKeys) : Utils.className(entityName);
130
139
  let cond = { [rootPrimaryKey]: { $in: where } };
131
140
  // @ts-ignore
132
141
  // detect tuple comparison, use `$or` in case the number of constituents don't match
@@ -138,12 +147,10 @@ export class QueryHelper {
138
147
  if (!Utils.isPlainObject(where)) {
139
148
  return where;
140
149
  }
141
- return Object.keys(where).reduce((o, key) => {
150
+ return Utils.getObjectQueryKeys(where).reduce((o, key) => {
142
151
  let value = where[key];
143
- const prop = this.findProperty(key, options);
144
- const keys = prop?.joinColumns?.length ?? 0;
145
- const composite = keys > 1;
146
- if (Array.isArray(value) && value.length === 0 && RawQueryFragment.isKnownFragment(key)) {
152
+ const customExpression = Raw.isKnownFragmentSymbol(key);
153
+ if (Array.isArray(value) && value.length === 0 && customExpression) {
147
154
  o[key] = value;
148
155
  return o;
149
156
  }
@@ -157,6 +164,9 @@ export class QueryHelper {
157
164
  o[rootPrimaryKey] = { [key]: QueryHelper.processWhere({ ...options, where: value, root: false }) };
158
165
  return o;
159
166
  }
167
+ const prop = customExpression ? null : this.findProperty(key, options);
168
+ const keys = prop?.joinColumns?.length ?? 0;
169
+ const composite = keys > 1;
160
170
  if (prop?.customType && convertCustomTypes && !isRaw(value)) {
161
171
  value = QueryHelper.processCustomType(prop, value, platform, undefined, true);
162
172
  }
@@ -164,7 +174,7 @@ export class QueryHelper {
164
174
  if (isJsonProperty && prop?.kind !== ReferenceKind.EMBEDDED) {
165
175
  return this.processJsonCondition(o, value, [prop.fieldNames[0]], platform, aliased);
166
176
  }
167
- if (Array.isArray(value) && !Utils.isOperator(key) && !QueryHelper.isSupportedOperator(key) && !key.includes('?') && options.type !== 'orderBy') {
177
+ if (Array.isArray(value) && !Utils.isOperator(key) && !QueryHelper.isSupportedOperator(key) && !(customExpression && Raw.getKnownFragment(key).params.length > 0) && options.type !== 'orderBy') {
168
178
  // comparing single composite key - use $eq instead of $in
169
179
  const op = composite && !value.every(v => Array.isArray(v)) ? '$eq' : '$in';
170
180
  o[key] = { [op]: value };
@@ -174,7 +184,7 @@ export class QueryHelper {
174
184
  o[key] = QueryHelper.processWhere({
175
185
  ...options,
176
186
  where: value,
177
- entityName: prop?.type ?? entityName,
187
+ entityName: prop?.targetMeta?.class ?? entityName,
178
188
  root: false,
179
189
  });
180
190
  }
@@ -184,7 +194,7 @@ export class QueryHelper {
184
194
  return o;
185
195
  }, {});
186
196
  }
187
- static getActiveFilters(entityName, options, filters) {
197
+ static getActiveFilters(meta, options, filters) {
188
198
  if (options === false) {
189
199
  return [];
190
200
  }
@@ -196,7 +206,7 @@ export class QueryHelper {
196
206
  Object.keys(options).forEach(filter => opts[filter] = options[filter]);
197
207
  }
198
208
  return Object.keys(filters)
199
- .filter(f => QueryHelper.isFilterActive(entityName, f, filters[f], opts))
209
+ .filter(f => QueryHelper.isFilterActive(meta, f, filters[f], opts))
200
210
  .map(f => {
201
211
  filters[f].name = f;
202
212
  return filters[f];
@@ -220,8 +230,8 @@ export class QueryHelper {
220
230
  }
221
231
  return Utils.mergeConfig({}, propFilters, options);
222
232
  }
223
- static isFilterActive(entityName, filterName, filter, options) {
224
- if (filter.entity && !filter.entity.includes(entityName)) {
233
+ static isFilterActive(meta, filterName, filter, options) {
234
+ if (filter.entity && !filter.entity.includes(meta.className)) {
225
235
  return false;
226
236
  }
227
237
  if (options[filterName] === false) {
@@ -231,8 +241,8 @@ export class QueryHelper {
231
241
  }
232
242
  static processCustomType(prop, cond, platform, key, fromQuery) {
233
243
  if (Utils.isPlainObject(cond)) {
234
- return Utils.keys(cond).reduce((o, k) => {
235
- if (Utils.isOperator(k, true) || prop.referencedPKs?.includes(k)) {
244
+ return Utils.getObjectQueryKeys(cond).reduce((o, k) => {
245
+ if (!Raw.isKnownFragmentSymbol(k) && (Utils.isOperator(k, true) || prop.referencedPKs?.includes(k))) {
236
246
  o[k] = QueryHelper.processCustomType(prop, cond[k], platform, k, fromQuery);
237
247
  }
238
248
  else {
@@ -290,4 +300,29 @@ export class QueryHelper {
290
300
  const meta = entityName ? options.metadata.find(entityName) : undefined;
291
301
  return meta?.properties[propName];
292
302
  }
303
+ /**
304
+ * Merges multiple orderBy sources with key-level deduplication (first-seen key wins).
305
+ * RawQueryFragment symbol keys are never deduped (each is unique).
306
+ */
307
+ static mergeOrderBy(...sources) {
308
+ const result = [];
309
+ const seenKeys = new Set();
310
+ for (const source of sources) {
311
+ if (source == null) {
312
+ continue;
313
+ }
314
+ for (const item of Utils.asArray(source)) {
315
+ for (const key of Utils.getObjectQueryKeys(item)) {
316
+ if (typeof key === 'symbol') {
317
+ result.push({ [key]: item[key] });
318
+ }
319
+ else if (!seenKeys.has(key)) {
320
+ seenKeys.add(key);
321
+ result.push({ [key]: item[key] });
322
+ }
323
+ }
324
+ }
325
+ }
326
+ return result;
327
+ }
293
328
  }