@mikro-orm/core 7.0.0-rc.1 → 7.0.0-rc.3

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 (76) hide show
  1. package/EntityManager.d.ts +3 -2
  2. package/EntityManager.js +107 -43
  3. package/MikroORM.js +4 -4
  4. package/cache/FileCacheAdapter.js +1 -3
  5. package/connections/Connection.js +16 -3
  6. package/drivers/DatabaseDriver.js +26 -8
  7. package/drivers/IDatabaseDriver.d.ts +49 -1
  8. package/entity/BaseEntity.d.ts +3 -3
  9. package/entity/Collection.js +43 -17
  10. package/entity/EntityAssigner.js +23 -11
  11. package/entity/EntityFactory.js +36 -13
  12. package/entity/EntityHelper.js +27 -18
  13. package/entity/EntityLoader.d.ts +6 -6
  14. package/entity/EntityLoader.js +55 -22
  15. package/entity/EntityRepository.d.ts +1 -1
  16. package/entity/Reference.d.ts +1 -1
  17. package/entity/Reference.js +37 -8
  18. package/entity/WrappedEntity.d.ts +3 -3
  19. package/entity/WrappedEntity.js +5 -1
  20. package/entity/defineEntity.d.ts +202 -58
  21. package/entity/defineEntity.js +20 -24
  22. package/entity/utils.js +28 -26
  23. package/entity/validators.js +2 -1
  24. package/enums.js +12 -17
  25. package/errors.js +18 -8
  26. package/events/EventManager.js +1 -1
  27. package/exceptions.js +7 -2
  28. package/hydration/ObjectHydrator.js +27 -13
  29. package/index.d.ts +2 -2
  30. package/index.js +1 -1
  31. package/logging/DefaultLogger.js +3 -5
  32. package/logging/colors.js +3 -6
  33. package/metadata/EntitySchema.d.ts +2 -2
  34. package/metadata/EntitySchema.js +12 -2
  35. package/metadata/MetadataDiscovery.js +107 -48
  36. package/metadata/MetadataProvider.js +26 -1
  37. package/metadata/MetadataStorage.js +2 -4
  38. package/metadata/MetadataValidator.js +20 -5
  39. package/metadata/types.d.ts +2 -2
  40. package/naming-strategy/AbstractNamingStrategy.js +5 -2
  41. package/not-supported.js +5 -1
  42. package/package.json +40 -37
  43. package/platforms/Platform.d.ts +4 -1
  44. package/platforms/Platform.js +50 -24
  45. package/serialization/EntitySerializer.d.ts +3 -3
  46. package/serialization/EntitySerializer.js +7 -3
  47. package/serialization/EntityTransformer.js +6 -4
  48. package/serialization/SerializationContext.js +1 -1
  49. package/typings.d.ts +73 -33
  50. package/typings.js +11 -9
  51. package/unit-of-work/ChangeSet.js +4 -4
  52. package/unit-of-work/ChangeSetComputer.js +8 -6
  53. package/unit-of-work/ChangeSetPersister.js +15 -10
  54. package/unit-of-work/CommitOrderCalculator.js +4 -2
  55. package/unit-of-work/UnitOfWork.d.ts +7 -1
  56. package/unit-of-work/UnitOfWork.js +51 -22
  57. package/utils/AbstractMigrator.d.ts +101 -0
  58. package/utils/AbstractMigrator.js +303 -0
  59. package/utils/AbstractSchemaGenerator.js +2 -1
  60. package/utils/AsyncContext.js +1 -1
  61. package/utils/Configuration.d.ts +3 -1
  62. package/utils/Configuration.js +8 -4
  63. package/utils/Cursor.js +4 -2
  64. package/utils/DataloaderUtils.js +15 -12
  65. package/utils/EntityComparator.js +51 -43
  66. package/utils/QueryHelper.js +38 -26
  67. package/utils/RawQueryFragment.js +3 -2
  68. package/utils/TransactionManager.js +2 -1
  69. package/utils/Utils.d.ts +1 -1
  70. package/utils/Utils.js +36 -30
  71. package/utils/env-vars.js +6 -5
  72. package/utils/fs-utils.d.ts +1 -0
  73. package/utils/fs-utils.js +6 -5
  74. package/utils/index.d.ts +0 -2
  75. package/utils/index.js +0 -2
  76. package/utils/upsert-utils.js +6 -3
@@ -121,6 +121,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
121
121
  }): T;
122
122
  setFlushMode(flushMode?: FlushMode | `${FlushMode}`): void;
123
123
  protected processWhere<Entity extends object, Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(entityName: EntityName<Entity>, where: FilterQuery<Entity>, options: FindOptions<Entity, Hint, Fields, Excludes> | FindOneOptions<Entity, Hint, Fields, Excludes>, type: 'read' | 'update' | 'delete'): Promise<FilterQuery<Entity>>;
124
+ protected processUnionWhere<Entity extends object, Hint extends string = never>(entityName: EntityName<Entity>, options: FindOptions<Entity, Hint, any, any> | CountOptions<Entity, Hint> | UpdateOptions<Entity> | DeleteOptions<Entity>, type: 'read' | 'update' | 'delete'): Promise<void>;
124
125
  protected applyDiscriminatorCondition<Entity extends object>(entityName: EntityName<Entity>, where: FilterQuery<Entity>): FilterQuery<Entity>;
125
126
  protected createPopulateWhere<Entity extends object>(cond: ObjectQuery<Entity>, options: FindOptions<Entity, any, any, any> | FindOneOptions<Entity, any, any, any> | CountOptions<Entity, any>): ObjectQuery<Entity>;
126
127
  protected getJoinedFilters<Entity extends object>(meta: EntityMetadata<Entity>, options: FindOptions<Entity, any, any, any> | FindOneOptions<Entity, any, any, any>): Promise<ObjectQuery<Entity> | undefined>;
@@ -456,7 +457,7 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
456
457
  /**
457
458
  * Loads specified relations in batch. This will execute one query for each relation, that will populate it on all the specified entities.
458
459
  */
459
- populate<Entity extends object, Naked extends FromEntityType<UnboxArray<Entity>> = FromEntityType<UnboxArray<Entity>>, Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(entities: Entity, populate: readonly AutoPath<Naked, Hint, PopulatePath.ALL>[] | false, options?: EntityLoaderOptions<Naked, Fields, Excludes>): Promise<Entity extends object[] ? MergeLoaded<ArrayElement<Entity>, Naked, Hint, Fields, Excludes>[] : MergeLoaded<Entity, Naked, Hint, Fields, Excludes>>;
460
+ populate<Entity extends object, Naked extends FromEntityType<UnboxArray<Entity>> = FromEntityType<UnboxArray<Entity>>, Hint extends string = never, Fields extends string = never, Excludes extends string = never>(entities: Entity, populate: readonly AutoPath<Naked, Hint, PopulatePath.ALL>[] | false, options?: EntityLoaderOptions<Naked, Fields, Excludes>): Promise<Entity extends object[] ? MergeLoaded<ArrayElement<Entity>, Naked, Hint, Fields, Excludes>[] : MergeLoaded<Entity, Naked, Hint, Fields, Excludes>>;
460
461
  /**
461
462
  * Returns new EntityManager instance with its own identity map
462
463
  */
@@ -593,7 +594,7 @@ export interface MergeOptions {
593
594
  schema?: string;
594
595
  disableContextResolution?: boolean;
595
596
  validate?: boolean;
596
- cascade?: boolean; /** @default true */
597
+ cascade?: boolean /** @default true */;
597
598
  }
598
599
  export interface ForkOptions {
599
600
  /** do we want a clear identity map? defaults to true */
package/EntityManager.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import { getOnConflictReturningFields, getWhereCondition } from './utils/upsert-utils.js';
2
2
  import { Utils } from './utils/Utils.js';
3
3
  import { Cursor } from './utils/Cursor.js';
4
- import { DataloaderUtils } from './utils/DataloaderUtils.js';
5
4
  import { QueryHelper } from './utils/QueryHelper.js';
6
5
  import { TransactionContext } from './utils/TransactionContext.js';
7
6
  import { isRaw, Raw } from './utils/RawQueryFragment.js';
@@ -124,7 +123,7 @@ export class EntityManager {
124
123
  else {
125
124
  options.orderBy ??= {};
126
125
  }
127
- options.populate = await em.preparePopulate(entityName, options);
126
+ options.populate = (await em.preparePopulate(entityName, options));
128
127
  const populate = options.populate;
129
128
  const cacheKey = em.cacheKey(entityName, options, 'em.find', where);
130
129
  const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true);
@@ -142,6 +141,7 @@ export class EntityManager {
142
141
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
143
142
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
144
143
  options.populateFilter = await this.getJoinedFilters(meta, options);
144
+ await em.processUnionWhere(entityName, options, 'read');
145
145
  const results = await em.driver.find(entityName, where, { ctx: em.transactionContext, em, ...options });
146
146
  if (results.length === 0) {
147
147
  await em.storeCache(options.cache, cached, []);
@@ -195,10 +195,10 @@ export class EntityManager {
195
195
  em.prepareOptions(options);
196
196
  options.strategy = 'joined';
197
197
  await em.tryFlush(entityName, options);
198
- const where = await em.processWhere(entityName, options.where ?? {}, options, 'read');
198
+ const where = (await em.processWhere(entityName, options.where ?? {}, options, 'read'));
199
199
  validateParams(where);
200
200
  options.orderBy = options.orderBy || {};
201
- options.populate = await em.preparePopulate(entityName, options);
201
+ options.populate = (await em.preparePopulate(entityName, options));
202
202
  const meta = this.metadata.get(entityName);
203
203
  options = { ...options };
204
204
  // save the original hint value so we know it was infer/all
@@ -299,6 +299,14 @@ export class EntityManager {
299
299
  where = this.applyDiscriminatorCondition(entityName, where);
300
300
  return where;
301
301
  }
302
+ async processUnionWhere(entityName, options, type) {
303
+ if (options.unionWhere?.length) {
304
+ if (!this.driver.getPlatform().supportsUnionWhere()) {
305
+ throw new Error(`unionWhere is only supported on SQL drivers`);
306
+ }
307
+ options.unionWhere = (await Promise.all(options.unionWhere.map(branch => this.processWhere(entityName, branch, options, type))));
308
+ }
309
+ }
302
310
  // this method only handles the problem for mongo driver, SQL drivers have their implementation inside QueryBuilder
303
311
  applyDiscriminatorCondition(entityName, where) {
304
312
  const meta = this.metadata.find(entityName);
@@ -315,7 +323,10 @@ export class EntityManager {
315
323
  };
316
324
  lookUpChildren(children, meta.class);
317
325
  /* v8 ignore next */
318
- where[meta.root.discriminatorColumn] = children.length > 0 ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] } : meta.discriminatorValue;
326
+ where[meta.root.discriminatorColumn] =
327
+ children.length > 0
328
+ ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] }
329
+ : meta.discriminatorValue;
319
330
  return where;
320
331
  }
321
332
  createPopulateWhere(cond, options) {
@@ -382,10 +393,11 @@ export class EntityManager {
382
393
  }
383
394
  const ret = options.populate;
384
395
  for (const prop of meta.relations) {
385
- if (prop.object
386
- || ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)
387
- || !((options.fields?.length ?? 0) === 0 || options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
388
- || (parent?.class === prop.targetMeta.root.class && parent.propName === prop.inversedBy)) {
396
+ if (prop.object ||
397
+ ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) ||
398
+ !((options.fields?.length ?? 0) === 0 ||
399
+ options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`))) ||
400
+ (parent?.class === prop.targetMeta.root.class && parent.propName === prop.inversedBy)) {
389
401
  continue;
390
402
  }
391
403
  options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
@@ -425,9 +437,7 @@ export class EntityManager {
425
437
  const ret = [];
426
438
  const active = new Set();
427
439
  const push = (source) => {
428
- const activeFilters = QueryHelper
429
- .getActiveFilters(meta, options, source)
430
- .filter(f => !active.has(f.name));
440
+ const activeFilters = QueryHelper.getActiveFilters(meta, options, source).filter(f => !active.has(f.name));
431
441
  filters.push(...activeFilters);
432
442
  activeFilters.forEach(f => active.add(f.name));
433
443
  };
@@ -441,6 +451,7 @@ export class EntityManager {
441
451
  let cond;
442
452
  if (filter.cond instanceof Function) {
443
453
  // @ts-ignore
454
+ // oxfmt-ignore
444
455
  const args = Utils.isPlainObject(options?.[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
445
456
  if (!args && filter.cond.length > 0 && filter.args !== false) {
446
457
  throw new Error(`No arguments provided for filter '${filter.name}'`);
@@ -583,12 +594,18 @@ export class EntityManager {
583
594
  for (const e of fork.unitOfWork.getIdentityMap()) {
584
595
  const ref = em.getReference(e.constructor, helper(e).getPrimaryKey());
585
596
  const data = helper(e).serialize({ ignoreSerializers: true, includeHidden: true, convertCustomTypes: false });
586
- em.config.getHydrator(this.metadata).hydrate(ref, helper(ref).__meta, data, em.entityFactory, 'full', false, false);
597
+ em.config
598
+ .getHydrator(this.metadata)
599
+ .hydrate(ref, helper(ref).__meta, data, em.entityFactory, 'full', false, false);
587
600
  Utils.merge(helper(ref).__originalEntityData, this.comparator.prepareEntity(e));
588
601
  found ||= ref === entity;
589
602
  }
590
603
  if (!found) {
591
- const data = helper(reloaded).serialize({ ignoreSerializers: true, includeHidden: true, convertCustomTypes: true });
604
+ const data = helper(reloaded).serialize({
605
+ ignoreSerializers: true,
606
+ includeHidden: true,
607
+ convertCustomTypes: true,
608
+ });
592
609
  em.config.getHydrator(this.metadata).hydrate(entity, wrapped.__meta, data, em.entityFactory, 'full', false, true);
593
610
  Utils.merge(wrapped.__originalEntityData, this.comparator.prepareEntity(reloaded));
594
611
  }
@@ -624,7 +641,7 @@ export class EntityManager {
624
641
  return em.lockAndPopulate(meta, entity, where, options);
625
642
  }
626
643
  validateParams(where);
627
- options.populate = await em.preparePopulate(entityName, options);
644
+ options.populate = (await em.preparePopulate(entityName, options));
628
645
  const cacheKey = em.cacheKey(entityName, options, 'em.findOne', where);
629
646
  const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true);
630
647
  if (cached?.data !== undefined) {
@@ -643,6 +660,7 @@ export class EntityManager {
643
660
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
644
661
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
645
662
  options.populateFilter = await this.getJoinedFilters(meta, options);
663
+ await em.processUnionWhere(entityName, options, 'read');
646
664
  const data = await em.driver.findOne(entityName, where, {
647
665
  ctx: em.transactionContext,
648
666
  em,
@@ -771,9 +789,12 @@ export class EntityManager {
771
789
  initialized: true,
772
790
  schema: options.schema,
773
791
  });
774
- const uniqueFields = options.onConflictFields ?? (Utils.isPlainObject(where) ? Object.keys(where) : meta.primaryKeys);
792
+ const uniqueFields = options.onConflictFields ??
793
+ (Utils.isPlainObject(where) ? Object.keys(where) : meta.primaryKeys);
775
794
  const returning = getOnConflictReturningFields(meta, data, uniqueFields, options);
776
- if (options.onConflictAction === 'ignore' || !helper(entity).hasPrimaryKey() || (returning.length > 0 && !(this.getPlatform().usesReturningStatement() && ret.row))) {
795
+ if (options.onConflictAction === 'ignore' ||
796
+ !helper(entity).hasPrimaryKey() ||
797
+ (returning.length > 0 && !(this.getPlatform().usesReturningStatement() && ret.row))) {
777
798
  const where = {};
778
799
  if (Array.isArray(uniqueFields)) {
779
800
  for (const prop of uniqueFields) {
@@ -897,7 +918,9 @@ export class EntityManager {
897
918
  }
898
919
  }
899
920
  const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
900
- propIndex = !isRaw(unique) && unique.findIndex(p => data[p] ?? data[p.substring(0, p.indexOf('.'))] != null);
921
+ propIndex =
922
+ !isRaw(unique) &&
923
+ unique.findIndex(p => data[p] ?? data[p.substring(0, p.indexOf('.'))] != null);
901
924
  const tmp = getWhereCondition(meta, options.onConflictFields, row, where);
902
925
  propIndex = tmp.propIndex;
903
926
  where = QueryHelper.processWhere({
@@ -930,12 +953,16 @@ export class EntityManager {
930
953
  entitiesByData.clear();
931
954
  const loadPK = new Map();
932
955
  allData.forEach((row, i) => {
933
- em.unitOfWork.getChangeSetPersister().mapReturnedValues(Utils.isEntity(data[i]) ? data[i] : null, Utils.isEntity(data[i]) ? {} : data[i], res.rows?.[i], meta, true);
934
- const entity = Utils.isEntity(data[i]) ? data[i] : em.entityFactory.create(entityName, row, {
935
- refresh: true,
936
- initialized: true,
937
- schema: options.schema,
938
- });
956
+ em.unitOfWork
957
+ .getChangeSetPersister()
958
+ .mapReturnedValues(Utils.isEntity(data[i]) ? data[i] : null, Utils.isEntity(data[i]) ? {} : data[i], res.rows?.[i], meta, true);
959
+ const entity = Utils.isEntity(data[i])
960
+ ? data[i]
961
+ : em.entityFactory.create(entityName, row, {
962
+ refresh: true,
963
+ initialized: true,
964
+ schema: options.schema,
965
+ });
939
966
  if (!helper(entity).hasPrimaryKey()) {
940
967
  loadPK.set(entity, allWhere[i]);
941
968
  }
@@ -943,12 +970,15 @@ export class EntityManager {
943
970
  entitiesByData.set(row, entity);
944
971
  });
945
972
  // skip if we got the PKs via returning statement (`rows`)
973
+ // oxfmt-ignore
946
974
  const uniqueFields = options.onConflictFields ?? (Utils.isPlainObject(allWhere[0]) ? Object.keys(allWhere[0]).flatMap(key => Utils.splitPrimaryKeys(key)) : meta.primaryKeys);
947
975
  const returning = getOnConflictReturningFields(meta, data[0], uniqueFields, options);
948
976
  const reloadFields = returning.length > 0 && !(this.getPlatform().usesReturningStatement() && res.rows?.length);
949
977
  if (options.onConflictAction === 'ignore' || (!res.rows?.length && loadPK.size > 0) || reloadFields) {
950
978
  const unique = meta.hydrateProps.filter(p => !p.lazy).map(p => p.name);
951
- const add = new Set(propIndex !== false && propIndex >= 0 ? [unique[propIndex]] : []);
979
+ const add = new Set(propIndex !== false && propIndex >= 0
980
+ ? [unique[propIndex]]
981
+ : []);
952
982
  for (const cond of loadPK.values()) {
953
983
  Utils.keys(cond).forEach(key => add.add(key));
954
984
  }
@@ -961,7 +991,9 @@ export class EntityManager {
961
991
  });
962
992
  });
963
993
  const data2 = await this.driver.find(meta.class, where, {
964
- fields: returning.concat(...add).concat(...(Array.isArray(uniqueFields) ? uniqueFields : [])),
994
+ fields: returning
995
+ .concat(...add)
996
+ .concat(...(Array.isArray(uniqueFields) ? uniqueFields : [])),
965
997
  ctx: em.transactionContext,
966
998
  convertCustomTypes: true,
967
999
  connectionType: 'write',
@@ -1137,7 +1169,10 @@ export class EntityManager {
1137
1169
  }
1138
1170
  data = QueryHelper.processObjectParams(data);
1139
1171
  validateParams(data, 'insert data');
1140
- const res = await em.driver.nativeInsert(entityName, data, { ctx: em.transactionContext, ...options });
1172
+ const res = await em.driver.nativeInsert(entityName, data, {
1173
+ ctx: em.transactionContext,
1174
+ ...options,
1175
+ });
1141
1176
  return res.insertId;
1142
1177
  }
1143
1178
  /**
@@ -1177,7 +1212,10 @@ export class EntityManager {
1177
1212
  }
1178
1213
  data = data.map(row => QueryHelper.processObjectParams(row));
1179
1214
  data.forEach(row => validateParams(row, 'insert data'));
1180
- const res = await em.driver.nativeInsertMany(entityName, data, { ctx: em.transactionContext, ...options });
1215
+ const res = await em.driver.nativeInsertMany(entityName, data, {
1216
+ ctx: em.transactionContext,
1217
+ ...options,
1218
+ });
1181
1219
  if (res.insertedIds) {
1182
1220
  return res.insertedIds;
1183
1221
  }
@@ -1189,11 +1227,16 @@ export class EntityManager {
1189
1227
  async nativeUpdate(entityName, where, data, options = {}) {
1190
1228
  const em = this.getContext(false);
1191
1229
  em.prepareOptions(options);
1230
+ await em.processUnionWhere(entityName, options, 'update');
1192
1231
  data = QueryHelper.processObjectParams(data);
1193
1232
  where = await em.processWhere(entityName, where, { ...options, convertCustomTypes: false }, 'update');
1194
1233
  validateParams(data, 'update data');
1195
1234
  validateParams(where, 'update condition');
1196
- const res = await em.driver.nativeUpdate(entityName, where, data, { ctx: em.transactionContext, ...options });
1235
+ const res = await em.driver.nativeUpdate(entityName, where, data, {
1236
+ ctx: em.transactionContext,
1237
+ em,
1238
+ ...options,
1239
+ });
1197
1240
  return res.affectedRows;
1198
1241
  }
1199
1242
  /**
@@ -1202,9 +1245,14 @@ export class EntityManager {
1202
1245
  async nativeDelete(entityName, where, options = {}) {
1203
1246
  const em = this.getContext(false);
1204
1247
  em.prepareOptions(options);
1205
- where = await em.processWhere(entityName, where, options, 'delete');
1248
+ await em.processUnionWhere(entityName, options, 'delete');
1249
+ where = (await em.processWhere(entityName, where, options, 'delete'));
1206
1250
  validateParams(where, 'delete condition');
1207
- const res = await em.driver.nativeDelete(entityName, where, { ctx: em.transactionContext, ...options });
1251
+ const res = await em.driver.nativeDelete(entityName, where, {
1252
+ ctx: em.transactionContext,
1253
+ em,
1254
+ ...options,
1255
+ });
1208
1256
  return res.affectedRows;
1209
1257
  }
1210
1258
  /**
@@ -1215,7 +1263,10 @@ export class EntityManager {
1215
1263
  const data = this.driver.mapResult(result, meta);
1216
1264
  for (const k of Object.keys(data)) {
1217
1265
  const prop = meta.properties[k];
1218
- if (prop?.kind === ReferenceKind.SCALAR && SCALAR_TYPES.has(prop.runtimeType) && !prop.customType && (prop.setter || !prop.getter)) {
1266
+ if (prop?.kind === ReferenceKind.SCALAR &&
1267
+ SCALAR_TYPES.has(prop.runtimeType) &&
1268
+ !prop.customType &&
1269
+ (prop.setter || !prop.getter)) {
1219
1270
  validateProperty(prop, data[k], data);
1220
1271
  }
1221
1272
  }
@@ -1244,7 +1295,9 @@ export class EntityManager {
1244
1295
  return entity;
1245
1296
  }
1246
1297
  const dataIsEntity = Utils.isEntity(data);
1247
- entity = dataIsEntity ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
1298
+ entity = dataIsEntity
1299
+ ? data
1300
+ : em.entityFactory.create(entityName, data, { merge: true, ...options });
1248
1301
  const visited = options.cascade ? undefined : new Set([entity]);
1249
1302
  em.unitOfWork.merge(entity, visited);
1250
1303
  return entity;
@@ -1314,7 +1367,7 @@ export class EntityManager {
1314
1367
  em.prepareOptions(options);
1315
1368
  await em.tryFlush(entityName, options);
1316
1369
  where = await em.processWhere(entityName, where, options, 'read');
1317
- options.populate = await em.preparePopulate(entityName, options);
1370
+ options.populate = (await em.preparePopulate(entityName, options));
1318
1371
  options = { ...options };
1319
1372
  // save the original hint value so we know it was infer/all
1320
1373
  const meta = em.metadata.find(entityName);
@@ -1323,6 +1376,7 @@ export class EntityManager {
1323
1376
  options.populateFilter = await this.getJoinedFilters(meta, options);
1324
1377
  validateParams(where);
1325
1378
  delete options.orderBy;
1379
+ await em.processUnionWhere(entityName, options, 'read');
1326
1380
  const cacheKey = em.cacheKey(entityName, options, 'em.count', where);
1327
1381
  const cached = await em.tryCache(entityName, options.cache, cacheKey);
1328
1382
  if (cached?.data !== undefined) {
@@ -1460,7 +1514,8 @@ export class EntityManager {
1460
1514
  em.config.set('allowGlobalContext', true);
1461
1515
  const fork = new em.constructor(em.config, em.driver, em.metadata, options.useContext, eventManager);
1462
1516
  fork.setFlushMode(options.flushMode ?? em.flushMode);
1463
- fork.disableTransactions = options.disableTransactions ?? this.disableTransactions ?? this.config.get('disableTransactions');
1517
+ fork.disableTransactions =
1518
+ options.disableTransactions ?? this.disableTransactions ?? this.config.get('disableTransactions');
1464
1519
  em.config.set('allowGlobalContext', allowGlobalContext);
1465
1520
  if (options.keepTransactionContext) {
1466
1521
  fork.transactionContext = em.transactionContext;
@@ -1627,11 +1682,14 @@ export class EntityManager {
1627
1682
  const ret = [];
1628
1683
  for (let field of fields) {
1629
1684
  if (field === PopulatePath.ALL || field.startsWith(`${PopulatePath.ALL}.`)) {
1630
- ret.push(...meta.props.filter(prop => prop.lazy || [ReferenceKind.SCALAR, ReferenceKind.EMBEDDED].includes(prop.kind)).map(prop => prop.name));
1685
+ ret.push(...meta.props
1686
+ .filter(prop => prop.lazy || [ReferenceKind.SCALAR, ReferenceKind.EMBEDDED].includes(prop.kind))
1687
+ .map(prop => prop.name));
1631
1688
  continue;
1632
1689
  }
1633
1690
  field = field.split(':')[0];
1634
- if (!field.includes('.') && ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(meta.properties[field].kind)) {
1691
+ if (!field.includes('.') &&
1692
+ ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(meta.properties[field].kind)) {
1635
1693
  ret.push(field);
1636
1694
  continue;
1637
1695
  }
@@ -1660,7 +1718,8 @@ export class EntityManager {
1660
1718
  return populate;
1661
1719
  }
1662
1720
  if (typeof options.populate !== 'boolean') {
1663
- options.populate = Utils.asArray(options.populate).map(field => {
1721
+ options.populate = Utils.asArray(options.populate)
1722
+ .map(field => {
1664
1723
  /* v8 ignore next */
1665
1724
  if (typeof field === 'boolean' || field === PopulatePath.ALL) {
1666
1725
  return [{ field: meta.primaryKeys[0], strategy: options.strategy, all: !!field }]; //
@@ -1675,7 +1734,8 @@ export class EntityManager {
1675
1734
  return [{ field, strategy: options.strategy }];
1676
1735
  }
1677
1736
  return [field];
1678
- }).flat();
1737
+ })
1738
+ .flat();
1679
1739
  }
1680
1740
  const populate = this.entityLoader.normalizePopulate(entityName, options.populate, options.strategy, true, options.exclude);
1681
1741
  const invalid = populate.find(({ field }) => !this.canPopulate(entityName, field));
@@ -1777,7 +1837,7 @@ export class EntityManager {
1777
1837
  config ??= this.config.get('resultCache').global;
1778
1838
  if (config) {
1779
1839
  const em = this.getContext();
1780
- const expiration = Array.isArray(config) ? config[1] : (typeof config === 'number' ? config : undefined);
1840
+ const expiration = Array.isArray(config) ? config[1] : typeof config === 'number' ? config : undefined;
1781
1841
  await em.resultCache.set(key.key, data instanceof Function ? data() : data, '', expiration);
1782
1842
  }
1783
1843
  }
@@ -1816,11 +1876,15 @@ export class EntityManager {
1816
1876
  if (em.loaders[type]) {
1817
1877
  return em.loaders[type];
1818
1878
  }
1879
+ const { DataloaderUtils } = await import('@mikro-orm/core/dataloader');
1819
1880
  const DataLoader = await DataloaderUtils.getDataLoader();
1820
1881
  switch (type) {
1821
- case 'ref': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getRefBatchLoadFn(em)));
1822
- case '1:m': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getColBatchLoadFn(em)));
1823
- case 'm:n': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(em)));
1882
+ case 'ref':
1883
+ return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getRefBatchLoadFn(em)));
1884
+ case '1:m':
1885
+ return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getColBatchLoadFn(em)));
1886
+ case 'm:n':
1887
+ return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(em)));
1824
1888
  }
1825
1889
  }
1826
1890
  /**
package/MikroORM.js CHANGED
@@ -104,9 +104,7 @@ export class MikroORM {
104
104
  */
105
105
  constructor(options) {
106
106
  const env = loadEnvironmentVars();
107
- options = options.preferEnvVars
108
- ? Utils.merge(options, env)
109
- : Utils.merge(env, options);
107
+ options = options.preferEnvVars ? Utils.merge(options, env) : Utils.merge(env, options);
110
108
  this.config = new Configuration(options);
111
109
  const discovery = this.config.get('discovery');
112
110
  this.driver = this.config.getDriver();
@@ -213,6 +211,8 @@ export class MikroORM {
213
211
  * Gets the EntityGenerator.
214
212
  */
215
213
  get entityGenerator() {
216
- return this.driver.getPlatform().getExtension('EntityGenerator', '@mikro-orm/entity-generator', '@mikro-orm/entity-generator', this.em);
214
+ return this.driver
215
+ .getPlatform()
216
+ .getExtension('EntityGenerator', '@mikro-orm/entity-generator', '@mikro-orm/entity-generator', this.em);
217
217
  }
218
218
  }
@@ -68,9 +68,7 @@ export class FileCacheAdapter {
68
68
  if (!this.options.combined) {
69
69
  return;
70
70
  }
71
- let path = typeof this.options.combined === 'string'
72
- ? this.options.combined
73
- : './metadata.json';
71
+ let path = typeof this.options.combined === 'string' ? this.options.combined : './metadata.json';
74
72
  path = fs.normalizePath(this.options.cacheDir, path);
75
73
  this.options.combined = path; // override in the options, so we can log it from the CLI in `cache:generate` command
76
74
  writeFileSync(path, JSON.stringify(this.cache, null, this.pretty ? 2 : undefined));
@@ -16,7 +16,18 @@ export class Connection {
16
16
  this.options = Utils.copy(options);
17
17
  }
18
18
  else {
19
- const props = ['dbName', 'clientUrl', 'host', 'port', 'user', 'password', 'multipleStatements', 'pool', 'schema', 'driverOptions'];
19
+ const props = [
20
+ 'dbName',
21
+ 'clientUrl',
22
+ 'host',
23
+ 'port',
24
+ 'user',
25
+ 'password',
26
+ 'multipleStatements',
27
+ 'pool',
28
+ 'schema',
29
+ 'driverOptions',
30
+ ];
20
31
  this.options = props.reduce((o, i) => {
21
32
  o[i] = this.config.get(i);
22
33
  return o;
@@ -89,8 +100,10 @@ export class Connection {
89
100
  this.options.host = ret.host = this.options.host ?? this.config.get('host', decodeURIComponent(url.hostname));
90
101
  this.options.port = ret.port = this.options.port ?? this.config.get('port', +url.port);
91
102
  this.options.user = ret.user = this.options.user ?? this.config.get('user', decodeURIComponent(url.username));
92
- this.options.password = ret.password = this.options.password ?? this.config.get('password', decodeURIComponent(url.password));
93
- this.options.dbName = ret.database = this.options.dbName ?? this.config.get('dbName', decodeURIComponent(url.pathname).replace(/^\//, ''));
103
+ this.options.password = ret.password =
104
+ this.options.password ?? this.config.get('password', decodeURIComponent(url.password));
105
+ this.options.dbName = ret.database =
106
+ this.options.dbName ?? this.config.get('dbName', decodeURIComponent(url.pathname).replace(/^\//, ''));
94
107
  }
95
108
  return ret;
96
109
  }
@@ -157,11 +157,11 @@ export class DatabaseDriver {
157
157
  Object.assign(o, createOrderBy(key, direction[key]));
158
158
  return o;
159
159
  }, {});
160
- return ({ [prop]: value });
160
+ return { [prop]: value };
161
161
  }
162
162
  const desc = direction === QueryOrderNumeric.DESC || direction.toString().toLowerCase() === 'desc';
163
163
  const dir = Utils.xor(desc, isLast) ? 'desc' : 'asc';
164
- return ({ [prop]: dir });
164
+ return { [prop]: dir };
165
165
  };
166
166
  return {
167
167
  orderBy: definition.map(([prop, direction]) => createOrderBy(prop, direction)),
@@ -256,7 +256,7 @@ export class DatabaseDriver {
256
256
  else {
257
257
  data[prop.fieldNames[0]] = this.mapDataToFieldNames(copy, stringifyJsonArrays, prop.embeddedProps, convertCustomTypes, true);
258
258
  }
259
- if (stringifyJsonArrays && prop.array) {
259
+ if (stringifyJsonArrays && prop.array && !object) {
260
260
  data[prop.fieldNames[0]] = this.platform.convertJsonToDatabaseValue(data[prop.fieldNames[0]]);
261
261
  }
262
262
  return;
@@ -301,16 +301,23 @@ export class DatabaseDriver {
301
301
  if (prop.joinColumns && Array.isArray(data[k])) {
302
302
  const copy = Utils.flatten(data[k]);
303
303
  delete data[k];
304
- prop.joinColumns.forEach((joinColumn, idx) => data[joinColumn] = copy[idx]);
304
+ prop.joinColumns.forEach((joinColumn, idx) => (data[joinColumn] = copy[idx]));
305
305
  return;
306
306
  }
307
307
  if (prop.joinColumns?.length > 1 && data[k] == null) {
308
308
  delete data[k];
309
- prop.ownColumns.forEach(joinColumn => data[joinColumn] = null);
309
+ prop.ownColumns.forEach(joinColumn => (data[joinColumn] = null));
310
310
  return;
311
311
  }
312
- if (prop.customType && convertCustomTypes && !(prop.customType instanceof JsonType && object) && !isRaw(data[k])) {
313
- data[k] = prop.customType.convertToDatabaseValue(data[k], this.platform, { fromQuery: true, key: k, mode: 'query-data' });
312
+ if (prop.customType &&
313
+ convertCustomTypes &&
314
+ !(prop.customType instanceof JsonType && object) &&
315
+ !isRaw(data[k])) {
316
+ data[k] = prop.customType.convertToDatabaseValue(data[k], this.platform, {
317
+ fromQuery: true,
318
+ key: k,
319
+ mode: 'query-data',
320
+ });
314
321
  }
315
322
  if (prop.hasConvertToDatabaseValueSQL && !prop.object && !isRaw(data[k])) {
316
323
  const quoted = this.platform.quoteValue(data[k]);
@@ -390,7 +397,18 @@ export class DatabaseDriver {
390
397
  createReplicas(cb) {
391
398
  const replicas = this.config.get('replicas', []);
392
399
  const ret = [];
393
- const props = ['dbName', 'clientUrl', 'host', 'port', 'user', 'password', 'multipleStatements', 'pool', 'name', 'driverOptions'];
400
+ const props = [
401
+ 'dbName',
402
+ 'clientUrl',
403
+ 'host',
404
+ 'port',
405
+ 'user',
406
+ 'password',
407
+ 'multipleStatements',
408
+ 'pool',
409
+ 'name',
410
+ 'driverOptions',
411
+ ];
394
412
  for (const conf of replicas) {
395
413
  const replicaConfig = Utils.copy(conf);
396
414
  for (const prop of props) {
@@ -1,4 +1,4 @@
1
- import type { ConnectionType, EntityData, EntityMetadata, EntityProperty, FilterQuery, Primary, Dictionary, IPrimaryKey, PopulateOptions, EntityDictionary, AutoPath, ObjectQuery, FilterObject, Populate, EntityName, PopulateHintOptions, Prefixes } from '../typings.js';
1
+ import type { ConnectionType, Constructor, EntityData, EntityMetadata, EntityProperty, FilterQuery, Primary, Dictionary, IPrimaryKey, PopulateOptions, EntityDictionary, AutoPath, ObjectQuery, FilterObject, Populate, EntityName, PopulateHintOptions, Prefixes } from '../typings.js';
2
2
  import type { Connection, QueryResult, Transaction } from '../connections/Connection.js';
3
3
  import type { FlushMode, LockMode, QueryOrderMap, QueryFlag, LoadStrategy, PopulateHint, PopulatePath } from '../enums.js';
4
4
  import type { Platform } from '../platforms/Platform.js';
@@ -7,6 +7,7 @@ import type { Collection } from '../entity/Collection.js';
7
7
  import type { EntityManager } from '../EntityManager.js';
8
8
  import type { DriverException } from '../exceptions.js';
9
9
  import type { Configuration } from '../utils/Configuration.js';
10
+ import type { MikroORM } from '../MikroORM.js';
10
11
  import type { LoggingOptions, LogContext } from '../logging/Logger.js';
11
12
  import type { Raw } from '../utils/RawQueryFragment.js';
12
13
  export declare const EntityManagerType: unique symbol;
@@ -65,6 +66,10 @@ export interface IDatabaseDriver<C extends Connection = Connection> {
65
66
  schema?: string;
66
67
  parentSchema?: string;
67
68
  }): string | undefined;
69
+ /**
70
+ * @internal
71
+ */
72
+ getORMClass(): Constructor<MikroORM>;
68
73
  }
69
74
  export type EntityField<T, P extends string = PopulatePath.ALL> = keyof T | PopulatePath.ALL | AutoPath<T, P, `${PopulatePath.ALL}`>;
70
75
  export type OrderDefinition<T> = (QueryOrderMap<T> & {
@@ -105,6 +110,22 @@ export interface FindOptions<Entity, Hint extends string = never, Fields extends
105
110
  * when nesting the condition. This is used for implementation of joined filters.
106
111
  */
107
112
  populateFilter?: ObjectQuery<Entity>;
113
+ /**
114
+ * Index-friendly alternative to `$or` for conditions that span joined relations.
115
+ * Each array element becomes an independent branch combined via `UNION ALL` subquery:
116
+ * `WHERE pk IN (branch_1 UNION ALL branch_2 ... branch_N)`.
117
+ * The database plans each branch independently, enabling per-table index usage
118
+ * (e.g. GIN trigram indexes for fuzzy search across related entities).
119
+ * sql only
120
+ */
121
+ unionWhere?: ObjectQuery<Entity>[];
122
+ /**
123
+ * Strategy for combining `unionWhere` branches.
124
+ * - `'union-all'` (default) — skips deduplication, faster for most use cases.
125
+ * - `'union'` — deduplicates rows between branches; useful when branch overlap is very high.
126
+ * sql only
127
+ */
128
+ unionWhereStrategy?: 'union-all' | 'union';
108
129
  /** Used for ordering of the populate queries. If not specified, the value of `options.orderBy` is used. */
109
130
  populateOrderBy?: OrderDefinition<Entity>;
110
131
  /** Per-relation overrides for populate loading behavior. Keys are populate paths (same as used in `populate`). */
@@ -194,6 +215,13 @@ export interface NativeInsertUpdateOptions<T> {
194
215
  /** `nativeUpdate()` only option */
195
216
  upsert?: boolean;
196
217
  loggerContext?: LogContext;
218
+ /** sql only */
219
+ unionWhere?: ObjectQuery<T>[];
220
+ /** sql only */
221
+ unionWhereStrategy?: 'union-all' | 'union';
222
+ filters?: FilterOptions;
223
+ /** @internal */
224
+ em?: EntityManager;
197
225
  }
198
226
  export interface NativeInsertUpdateManyOptions<T> extends NativeInsertUpdateOptions<T> {
199
227
  processCollections?: boolean;
@@ -218,6 +246,10 @@ export interface CountOptions<T extends object, P extends string = never> {
218
246
  populate?: Populate<T, P>;
219
247
  populateWhere?: ObjectQuery<T> | PopulateHint | `${PopulateHint}`;
220
248
  populateFilter?: ObjectQuery<T>;
249
+ /** @see FindOptions.unionWhere */
250
+ unionWhere?: ObjectQuery<T>[];
251
+ /** @see FindOptions.unionWhereStrategy */
252
+ unionWhereStrategy?: 'union-all' | 'union';
221
253
  ctx?: Transaction;
222
254
  connectionType?: ConnectionType;
223
255
  flushMode?: FlushMode | `${FlushMode}`;
@@ -240,12 +272,28 @@ export interface UpdateOptions<T> {
240
272
  filters?: FilterOptions;
241
273
  schema?: string;
242
274
  ctx?: Transaction;
275
+ /** sql only */
276
+ unionWhere?: ObjectQuery<T>[];
277
+ /** sql only */
278
+ unionWhereStrategy?: 'union-all' | 'union';
243
279
  }
244
280
  export interface DeleteOptions<T> extends DriverMethodOptions {
245
281
  filters?: FilterOptions;
282
+ /** sql only */
283
+ unionWhere?: ObjectQuery<T>[];
284
+ /** sql only */
285
+ unionWhereStrategy?: 'union-all' | 'union';
286
+ /** @internal */
287
+ em?: EntityManager;
246
288
  }
247
289
  export interface NativeDeleteOptions<T> extends DriverMethodOptions {
248
290
  filters?: FilterOptions;
291
+ /** sql only */
292
+ unionWhere?: ObjectQuery<T>[];
293
+ /** sql only */
294
+ unionWhereStrategy?: 'union-all' | 'union';
295
+ /** @internal */
296
+ em?: EntityManager;
249
297
  }
250
298
  export interface LockOptions extends DriverMethodOptions {
251
299
  lockMode?: LockMode;