@mikro-orm/core 7.0.0-rc.2 → 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 (65) hide show
  1. package/EntityManager.d.ts +2 -1
  2. package/EntityManager.js +106 -42
  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 +43 -0
  8. package/entity/Collection.js +43 -17
  9. package/entity/EntityAssigner.js +23 -11
  10. package/entity/EntityFactory.js +32 -12
  11. package/entity/EntityHelper.js +25 -16
  12. package/entity/EntityLoader.js +55 -22
  13. package/entity/Reference.d.ts +1 -1
  14. package/entity/Reference.js +37 -8
  15. package/entity/WrappedEntity.js +5 -1
  16. package/entity/defineEntity.d.ts +24 -12
  17. package/entity/utils.js +28 -26
  18. package/entity/validators.js +2 -1
  19. package/enums.js +12 -17
  20. package/errors.js +18 -8
  21. package/events/EventManager.js +1 -1
  22. package/exceptions.js +7 -2
  23. package/hydration/ObjectHydrator.js +27 -13
  24. package/index.d.ts +1 -1
  25. package/index.js +1 -1
  26. package/logging/DefaultLogger.js +3 -5
  27. package/logging/colors.js +3 -6
  28. package/metadata/EntitySchema.d.ts +2 -2
  29. package/metadata/EntitySchema.js +12 -2
  30. package/metadata/MetadataDiscovery.js +106 -47
  31. package/metadata/MetadataProvider.js +26 -1
  32. package/metadata/MetadataStorage.js +2 -4
  33. package/metadata/MetadataValidator.js +20 -5
  34. package/metadata/types.d.ts +2 -2
  35. package/naming-strategy/AbstractNamingStrategy.js +5 -2
  36. package/not-supported.js +5 -1
  37. package/package.json +38 -38
  38. package/platforms/Platform.d.ts +1 -0
  39. package/platforms/Platform.js +49 -23
  40. package/serialization/EntitySerializer.js +7 -3
  41. package/serialization/SerializationContext.js +1 -1
  42. package/typings.d.ts +23 -23
  43. package/typings.js +9 -9
  44. package/unit-of-work/ChangeSet.js +4 -4
  45. package/unit-of-work/ChangeSetComputer.js +8 -6
  46. package/unit-of-work/ChangeSetPersister.js +13 -8
  47. package/unit-of-work/CommitOrderCalculator.js +4 -2
  48. package/unit-of-work/UnitOfWork.d.ts +7 -1
  49. package/unit-of-work/UnitOfWork.js +51 -22
  50. package/utils/AbstractMigrator.d.ts +1 -1
  51. package/utils/AbstractMigrator.js +3 -5
  52. package/utils/AbstractSchemaGenerator.js +2 -1
  53. package/utils/AsyncContext.js +1 -1
  54. package/utils/Configuration.js +8 -4
  55. package/utils/Cursor.js +4 -2
  56. package/utils/DataloaderUtils.js +15 -12
  57. package/utils/EntityComparator.js +51 -43
  58. package/utils/QueryHelper.js +38 -26
  59. package/utils/RawQueryFragment.js +3 -2
  60. package/utils/TransactionManager.js +2 -1
  61. package/utils/Utils.d.ts +1 -1
  62. package/utils/Utils.js +36 -30
  63. package/utils/env-vars.js +6 -5
  64. package/utils/fs-utils.js +2 -5
  65. 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>;
@@ -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
@@ -123,7 +123,7 @@ export class EntityManager {
123
123
  else {
124
124
  options.orderBy ??= {};
125
125
  }
126
- options.populate = await em.preparePopulate(entityName, options);
126
+ options.populate = (await em.preparePopulate(entityName, options));
127
127
  const populate = options.populate;
128
128
  const cacheKey = em.cacheKey(entityName, options, 'em.find', where);
129
129
  const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true);
@@ -141,6 +141,7 @@ export class EntityManager {
141
141
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
142
142
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
143
143
  options.populateFilter = await this.getJoinedFilters(meta, options);
144
+ await em.processUnionWhere(entityName, options, 'read');
144
145
  const results = await em.driver.find(entityName, where, { ctx: em.transactionContext, em, ...options });
145
146
  if (results.length === 0) {
146
147
  await em.storeCache(options.cache, cached, []);
@@ -194,10 +195,10 @@ export class EntityManager {
194
195
  em.prepareOptions(options);
195
196
  options.strategy = 'joined';
196
197
  await em.tryFlush(entityName, options);
197
- const where = await em.processWhere(entityName, options.where ?? {}, options, 'read');
198
+ const where = (await em.processWhere(entityName, options.where ?? {}, options, 'read'));
198
199
  validateParams(where);
199
200
  options.orderBy = options.orderBy || {};
200
- options.populate = await em.preparePopulate(entityName, options);
201
+ options.populate = (await em.preparePopulate(entityName, options));
201
202
  const meta = this.metadata.get(entityName);
202
203
  options = { ...options };
203
204
  // save the original hint value so we know it was infer/all
@@ -298,6 +299,14 @@ export class EntityManager {
298
299
  where = this.applyDiscriminatorCondition(entityName, where);
299
300
  return where;
300
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
+ }
301
310
  // this method only handles the problem for mongo driver, SQL drivers have their implementation inside QueryBuilder
302
311
  applyDiscriminatorCondition(entityName, where) {
303
312
  const meta = this.metadata.find(entityName);
@@ -314,7 +323,10 @@ export class EntityManager {
314
323
  };
315
324
  lookUpChildren(children, meta.class);
316
325
  /* v8 ignore next */
317
- 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;
318
330
  return where;
319
331
  }
320
332
  createPopulateWhere(cond, options) {
@@ -381,10 +393,11 @@ export class EntityManager {
381
393
  }
382
394
  const ret = options.populate;
383
395
  for (const prop of meta.relations) {
384
- if (prop.object
385
- || ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)
386
- || !((options.fields?.length ?? 0) === 0 || options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
387
- || (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)) {
388
401
  continue;
389
402
  }
390
403
  options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
@@ -424,9 +437,7 @@ export class EntityManager {
424
437
  const ret = [];
425
438
  const active = new Set();
426
439
  const push = (source) => {
427
- const activeFilters = QueryHelper
428
- .getActiveFilters(meta, options, source)
429
- .filter(f => !active.has(f.name));
440
+ const activeFilters = QueryHelper.getActiveFilters(meta, options, source).filter(f => !active.has(f.name));
430
441
  filters.push(...activeFilters);
431
442
  activeFilters.forEach(f => active.add(f.name));
432
443
  };
@@ -440,6 +451,7 @@ export class EntityManager {
440
451
  let cond;
441
452
  if (filter.cond instanceof Function) {
442
453
  // @ts-ignore
454
+ // oxfmt-ignore
443
455
  const args = Utils.isPlainObject(options?.[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
444
456
  if (!args && filter.cond.length > 0 && filter.args !== false) {
445
457
  throw new Error(`No arguments provided for filter '${filter.name}'`);
@@ -582,12 +594,18 @@ export class EntityManager {
582
594
  for (const e of fork.unitOfWork.getIdentityMap()) {
583
595
  const ref = em.getReference(e.constructor, helper(e).getPrimaryKey());
584
596
  const data = helper(e).serialize({ ignoreSerializers: true, includeHidden: true, convertCustomTypes: false });
585
- 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);
586
600
  Utils.merge(helper(ref).__originalEntityData, this.comparator.prepareEntity(e));
587
601
  found ||= ref === entity;
588
602
  }
589
603
  if (!found) {
590
- 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
+ });
591
609
  em.config.getHydrator(this.metadata).hydrate(entity, wrapped.__meta, data, em.entityFactory, 'full', false, true);
592
610
  Utils.merge(wrapped.__originalEntityData, this.comparator.prepareEntity(reloaded));
593
611
  }
@@ -623,7 +641,7 @@ export class EntityManager {
623
641
  return em.lockAndPopulate(meta, entity, where, options);
624
642
  }
625
643
  validateParams(where);
626
- options.populate = await em.preparePopulate(entityName, options);
644
+ options.populate = (await em.preparePopulate(entityName, options));
627
645
  const cacheKey = em.cacheKey(entityName, options, 'em.findOne', where);
628
646
  const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true);
629
647
  if (cached?.data !== undefined) {
@@ -642,6 +660,7 @@ export class EntityManager {
642
660
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
643
661
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
644
662
  options.populateFilter = await this.getJoinedFilters(meta, options);
663
+ await em.processUnionWhere(entityName, options, 'read');
645
664
  const data = await em.driver.findOne(entityName, where, {
646
665
  ctx: em.transactionContext,
647
666
  em,
@@ -770,9 +789,12 @@ export class EntityManager {
770
789
  initialized: true,
771
790
  schema: options.schema,
772
791
  });
773
- 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);
774
794
  const returning = getOnConflictReturningFields(meta, data, uniqueFields, options);
775
- 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))) {
776
798
  const where = {};
777
799
  if (Array.isArray(uniqueFields)) {
778
800
  for (const prop of uniqueFields) {
@@ -896,7 +918,9 @@ export class EntityManager {
896
918
  }
897
919
  }
898
920
  const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
899
- 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);
900
924
  const tmp = getWhereCondition(meta, options.onConflictFields, row, where);
901
925
  propIndex = tmp.propIndex;
902
926
  where = QueryHelper.processWhere({
@@ -929,12 +953,16 @@ export class EntityManager {
929
953
  entitiesByData.clear();
930
954
  const loadPK = new Map();
931
955
  allData.forEach((row, i) => {
932
- em.unitOfWork.getChangeSetPersister().mapReturnedValues(Utils.isEntity(data[i]) ? data[i] : null, Utils.isEntity(data[i]) ? {} : data[i], res.rows?.[i], meta, true);
933
- const entity = Utils.isEntity(data[i]) ? data[i] : em.entityFactory.create(entityName, row, {
934
- refresh: true,
935
- initialized: true,
936
- schema: options.schema,
937
- });
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
+ });
938
966
  if (!helper(entity).hasPrimaryKey()) {
939
967
  loadPK.set(entity, allWhere[i]);
940
968
  }
@@ -942,12 +970,15 @@ export class EntityManager {
942
970
  entitiesByData.set(row, entity);
943
971
  });
944
972
  // skip if we got the PKs via returning statement (`rows`)
973
+ // oxfmt-ignore
945
974
  const uniqueFields = options.onConflictFields ?? (Utils.isPlainObject(allWhere[0]) ? Object.keys(allWhere[0]).flatMap(key => Utils.splitPrimaryKeys(key)) : meta.primaryKeys);
946
975
  const returning = getOnConflictReturningFields(meta, data[0], uniqueFields, options);
947
976
  const reloadFields = returning.length > 0 && !(this.getPlatform().usesReturningStatement() && res.rows?.length);
948
977
  if (options.onConflictAction === 'ignore' || (!res.rows?.length && loadPK.size > 0) || reloadFields) {
949
978
  const unique = meta.hydrateProps.filter(p => !p.lazy).map(p => p.name);
950
- const add = new Set(propIndex !== false && propIndex >= 0 ? [unique[propIndex]] : []);
979
+ const add = new Set(propIndex !== false && propIndex >= 0
980
+ ? [unique[propIndex]]
981
+ : []);
951
982
  for (const cond of loadPK.values()) {
952
983
  Utils.keys(cond).forEach(key => add.add(key));
953
984
  }
@@ -960,7 +991,9 @@ export class EntityManager {
960
991
  });
961
992
  });
962
993
  const data2 = await this.driver.find(meta.class, where, {
963
- fields: returning.concat(...add).concat(...(Array.isArray(uniqueFields) ? uniqueFields : [])),
994
+ fields: returning
995
+ .concat(...add)
996
+ .concat(...(Array.isArray(uniqueFields) ? uniqueFields : [])),
964
997
  ctx: em.transactionContext,
965
998
  convertCustomTypes: true,
966
999
  connectionType: 'write',
@@ -1136,7 +1169,10 @@ export class EntityManager {
1136
1169
  }
1137
1170
  data = QueryHelper.processObjectParams(data);
1138
1171
  validateParams(data, 'insert data');
1139
- 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
+ });
1140
1176
  return res.insertId;
1141
1177
  }
1142
1178
  /**
@@ -1176,7 +1212,10 @@ export class EntityManager {
1176
1212
  }
1177
1213
  data = data.map(row => QueryHelper.processObjectParams(row));
1178
1214
  data.forEach(row => validateParams(row, 'insert data'));
1179
- 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
+ });
1180
1219
  if (res.insertedIds) {
1181
1220
  return res.insertedIds;
1182
1221
  }
@@ -1188,11 +1227,16 @@ export class EntityManager {
1188
1227
  async nativeUpdate(entityName, where, data, options = {}) {
1189
1228
  const em = this.getContext(false);
1190
1229
  em.prepareOptions(options);
1230
+ await em.processUnionWhere(entityName, options, 'update');
1191
1231
  data = QueryHelper.processObjectParams(data);
1192
1232
  where = await em.processWhere(entityName, where, { ...options, convertCustomTypes: false }, 'update');
1193
1233
  validateParams(data, 'update data');
1194
1234
  validateParams(where, 'update condition');
1195
- 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
+ });
1196
1240
  return res.affectedRows;
1197
1241
  }
1198
1242
  /**
@@ -1201,9 +1245,14 @@ export class EntityManager {
1201
1245
  async nativeDelete(entityName, where, options = {}) {
1202
1246
  const em = this.getContext(false);
1203
1247
  em.prepareOptions(options);
1204
- where = await em.processWhere(entityName, where, options, 'delete');
1248
+ await em.processUnionWhere(entityName, options, 'delete');
1249
+ where = (await em.processWhere(entityName, where, options, 'delete'));
1205
1250
  validateParams(where, 'delete condition');
1206
- 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
+ });
1207
1256
  return res.affectedRows;
1208
1257
  }
1209
1258
  /**
@@ -1214,7 +1263,10 @@ export class EntityManager {
1214
1263
  const data = this.driver.mapResult(result, meta);
1215
1264
  for (const k of Object.keys(data)) {
1216
1265
  const prop = meta.properties[k];
1217
- 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)) {
1218
1270
  validateProperty(prop, data[k], data);
1219
1271
  }
1220
1272
  }
@@ -1243,7 +1295,9 @@ export class EntityManager {
1243
1295
  return entity;
1244
1296
  }
1245
1297
  const dataIsEntity = Utils.isEntity(data);
1246
- 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 });
1247
1301
  const visited = options.cascade ? undefined : new Set([entity]);
1248
1302
  em.unitOfWork.merge(entity, visited);
1249
1303
  return entity;
@@ -1313,7 +1367,7 @@ export class EntityManager {
1313
1367
  em.prepareOptions(options);
1314
1368
  await em.tryFlush(entityName, options);
1315
1369
  where = await em.processWhere(entityName, where, options, 'read');
1316
- options.populate = await em.preparePopulate(entityName, options);
1370
+ options.populate = (await em.preparePopulate(entityName, options));
1317
1371
  options = { ...options };
1318
1372
  // save the original hint value so we know it was infer/all
1319
1373
  const meta = em.metadata.find(entityName);
@@ -1322,6 +1376,7 @@ export class EntityManager {
1322
1376
  options.populateFilter = await this.getJoinedFilters(meta, options);
1323
1377
  validateParams(where);
1324
1378
  delete options.orderBy;
1379
+ await em.processUnionWhere(entityName, options, 'read');
1325
1380
  const cacheKey = em.cacheKey(entityName, options, 'em.count', where);
1326
1381
  const cached = await em.tryCache(entityName, options.cache, cacheKey);
1327
1382
  if (cached?.data !== undefined) {
@@ -1459,7 +1514,8 @@ export class EntityManager {
1459
1514
  em.config.set('allowGlobalContext', true);
1460
1515
  const fork = new em.constructor(em.config, em.driver, em.metadata, options.useContext, eventManager);
1461
1516
  fork.setFlushMode(options.flushMode ?? em.flushMode);
1462
- fork.disableTransactions = options.disableTransactions ?? this.disableTransactions ?? this.config.get('disableTransactions');
1517
+ fork.disableTransactions =
1518
+ options.disableTransactions ?? this.disableTransactions ?? this.config.get('disableTransactions');
1463
1519
  em.config.set('allowGlobalContext', allowGlobalContext);
1464
1520
  if (options.keepTransactionContext) {
1465
1521
  fork.transactionContext = em.transactionContext;
@@ -1626,11 +1682,14 @@ export class EntityManager {
1626
1682
  const ret = [];
1627
1683
  for (let field of fields) {
1628
1684
  if (field === PopulatePath.ALL || field.startsWith(`${PopulatePath.ALL}.`)) {
1629
- 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));
1630
1688
  continue;
1631
1689
  }
1632
1690
  field = field.split(':')[0];
1633
- 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)) {
1634
1693
  ret.push(field);
1635
1694
  continue;
1636
1695
  }
@@ -1659,7 +1718,8 @@ export class EntityManager {
1659
1718
  return populate;
1660
1719
  }
1661
1720
  if (typeof options.populate !== 'boolean') {
1662
- options.populate = Utils.asArray(options.populate).map(field => {
1721
+ options.populate = Utils.asArray(options.populate)
1722
+ .map(field => {
1663
1723
  /* v8 ignore next */
1664
1724
  if (typeof field === 'boolean' || field === PopulatePath.ALL) {
1665
1725
  return [{ field: meta.primaryKeys[0], strategy: options.strategy, all: !!field }]; //
@@ -1674,7 +1734,8 @@ export class EntityManager {
1674
1734
  return [{ field, strategy: options.strategy }];
1675
1735
  }
1676
1736
  return [field];
1677
- }).flat();
1737
+ })
1738
+ .flat();
1678
1739
  }
1679
1740
  const populate = this.entityLoader.normalizePopulate(entityName, options.populate, options.strategy, true, options.exclude);
1680
1741
  const invalid = populate.find(({ field }) => !this.canPopulate(entityName, field));
@@ -1776,7 +1837,7 @@ export class EntityManager {
1776
1837
  config ??= this.config.get('resultCache').global;
1777
1838
  if (config) {
1778
1839
  const em = this.getContext();
1779
- 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;
1780
1841
  await em.resultCache.set(key.key, data instanceof Function ? data() : data, '', expiration);
1781
1842
  }
1782
1843
  }
@@ -1818,9 +1879,12 @@ export class EntityManager {
1818
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) {
@@ -110,6 +110,22 @@ export interface FindOptions<Entity, Hint extends string = never, Fields extends
110
110
  * when nesting the condition. This is used for implementation of joined filters.
111
111
  */
112
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';
113
129
  /** Used for ordering of the populate queries. If not specified, the value of `options.orderBy` is used. */
114
130
  populateOrderBy?: OrderDefinition<Entity>;
115
131
  /** Per-relation overrides for populate loading behavior. Keys are populate paths (same as used in `populate`). */
@@ -199,6 +215,13 @@ export interface NativeInsertUpdateOptions<T> {
199
215
  /** `nativeUpdate()` only option */
200
216
  upsert?: boolean;
201
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;
202
225
  }
203
226
  export interface NativeInsertUpdateManyOptions<T> extends NativeInsertUpdateOptions<T> {
204
227
  processCollections?: boolean;
@@ -223,6 +246,10 @@ export interface CountOptions<T extends object, P extends string = never> {
223
246
  populate?: Populate<T, P>;
224
247
  populateWhere?: ObjectQuery<T> | PopulateHint | `${PopulateHint}`;
225
248
  populateFilter?: ObjectQuery<T>;
249
+ /** @see FindOptions.unionWhere */
250
+ unionWhere?: ObjectQuery<T>[];
251
+ /** @see FindOptions.unionWhereStrategy */
252
+ unionWhereStrategy?: 'union-all' | 'union';
226
253
  ctx?: Transaction;
227
254
  connectionType?: ConnectionType;
228
255
  flushMode?: FlushMode | `${FlushMode}`;
@@ -245,12 +272,28 @@ export interface UpdateOptions<T> {
245
272
  filters?: FilterOptions;
246
273
  schema?: string;
247
274
  ctx?: Transaction;
275
+ /** sql only */
276
+ unionWhere?: ObjectQuery<T>[];
277
+ /** sql only */
278
+ unionWhereStrategy?: 'union-all' | 'union';
248
279
  }
249
280
  export interface DeleteOptions<T> extends DriverMethodOptions {
250
281
  filters?: FilterOptions;
282
+ /** sql only */
283
+ unionWhere?: ObjectQuery<T>[];
284
+ /** sql only */
285
+ unionWhereStrategy?: 'union-all' | 'union';
286
+ /** @internal */
287
+ em?: EntityManager;
251
288
  }
252
289
  export interface NativeDeleteOptions<T> extends DriverMethodOptions {
253
290
  filters?: FilterOptions;
291
+ /** sql only */
292
+ unionWhere?: ObjectQuery<T>[];
293
+ /** sql only */
294
+ unionWhereStrategy?: 'union-all' | 'union';
295
+ /** @internal */
296
+ em?: EntityManager;
254
297
  }
255
298
  export interface LockOptions extends DriverMethodOptions {
256
299
  lockMode?: LockMode;