@mikro-orm/core 7.0.0-dev.27 → 7.0.0-dev.271

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 (211) hide show
  1. package/EntityManager.d.ts +92 -60
  2. package/EntityManager.js +311 -256
  3. package/MikroORM.d.ts +44 -35
  4. package/MikroORM.js +109 -143
  5. package/README.md +2 -0
  6. package/cache/FileCacheAdapter.d.ts +1 -1
  7. package/cache/FileCacheAdapter.js +17 -8
  8. package/cache/GeneratedCacheAdapter.d.ts +0 -1
  9. package/cache/GeneratedCacheAdapter.js +0 -2
  10. package/cache/index.d.ts +0 -1
  11. package/cache/index.js +0 -1
  12. package/connections/Connection.d.ts +12 -5
  13. package/connections/Connection.js +21 -12
  14. package/drivers/DatabaseDriver.d.ts +25 -16
  15. package/drivers/DatabaseDriver.js +118 -35
  16. package/drivers/IDatabaseDriver.d.ts +75 -23
  17. package/entity/BaseEntity.d.ts +63 -4
  18. package/entity/BaseEntity.js +0 -3
  19. package/entity/Collection.d.ts +101 -29
  20. package/entity/Collection.js +436 -104
  21. package/entity/EntityAssigner.js +17 -17
  22. package/entity/EntityFactory.d.ts +7 -1
  23. package/entity/EntityFactory.js +87 -55
  24. package/entity/EntityHelper.d.ts +2 -2
  25. package/entity/EntityHelper.js +50 -17
  26. package/entity/EntityLoader.d.ts +11 -10
  27. package/entity/EntityLoader.js +221 -93
  28. package/entity/EntityRepository.d.ts +28 -8
  29. package/entity/EntityRepository.js +8 -2
  30. package/entity/PolymorphicRef.d.ts +12 -0
  31. package/entity/PolymorphicRef.js +18 -0
  32. package/entity/Reference.d.ts +1 -5
  33. package/entity/Reference.js +15 -11
  34. package/entity/WrappedEntity.d.ts +3 -8
  35. package/entity/WrappedEntity.js +2 -7
  36. package/entity/defineEntity.d.ts +512 -310
  37. package/entity/defineEntity.js +134 -287
  38. package/entity/index.d.ts +2 -2
  39. package/entity/index.js +2 -2
  40. package/entity/utils.d.ts +6 -1
  41. package/entity/utils.js +34 -1
  42. package/entity/validators.d.ts +11 -0
  43. package/entity/validators.js +65 -0
  44. package/enums.d.ts +8 -6
  45. package/enums.js +2 -1
  46. package/errors.d.ts +20 -10
  47. package/errors.js +55 -23
  48. package/events/EventManager.d.ts +2 -1
  49. package/events/EventManager.js +19 -11
  50. package/hydration/Hydrator.js +1 -2
  51. package/hydration/ObjectHydrator.d.ts +4 -4
  52. package/hydration/ObjectHydrator.js +87 -35
  53. package/index.d.ts +2 -2
  54. package/index.js +1 -2
  55. package/logging/DefaultLogger.d.ts +1 -1
  56. package/logging/DefaultLogger.js +1 -0
  57. package/logging/SimpleLogger.d.ts +1 -1
  58. package/logging/colors.d.ts +1 -1
  59. package/logging/colors.js +7 -6
  60. package/logging/index.d.ts +1 -0
  61. package/logging/index.js +1 -0
  62. package/logging/inspect.d.ts +2 -0
  63. package/logging/inspect.js +11 -0
  64. package/metadata/EntitySchema.d.ts +47 -23
  65. package/metadata/EntitySchema.js +92 -33
  66. package/metadata/MetadataDiscovery.d.ts +64 -9
  67. package/metadata/MetadataDiscovery.js +779 -326
  68. package/metadata/MetadataProvider.d.ts +11 -2
  69. package/metadata/MetadataProvider.js +46 -2
  70. package/metadata/MetadataStorage.d.ts +13 -11
  71. package/metadata/MetadataStorage.js +70 -37
  72. package/metadata/MetadataValidator.d.ts +32 -9
  73. package/metadata/MetadataValidator.js +196 -41
  74. package/metadata/discover-entities.d.ts +5 -0
  75. package/metadata/discover-entities.js +40 -0
  76. package/metadata/index.d.ts +1 -1
  77. package/metadata/index.js +1 -1
  78. package/metadata/types.d.ts +577 -0
  79. package/metadata/types.js +1 -0
  80. package/naming-strategy/AbstractNamingStrategy.d.ts +16 -4
  81. package/naming-strategy/AbstractNamingStrategy.js +20 -2
  82. package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
  83. package/naming-strategy/EntityCaseNamingStrategy.js +6 -5
  84. package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
  85. package/naming-strategy/MongoNamingStrategy.js +6 -6
  86. package/naming-strategy/NamingStrategy.d.ts +28 -4
  87. package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
  88. package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
  89. package/not-supported.d.ts +2 -0
  90. package/not-supported.js +4 -0
  91. package/package.json +22 -11
  92. package/platforms/ExceptionConverter.js +1 -1
  93. package/platforms/Platform.d.ts +10 -15
  94. package/platforms/Platform.js +21 -44
  95. package/serialization/EntitySerializer.d.ts +6 -3
  96. package/serialization/EntitySerializer.js +46 -26
  97. package/serialization/EntityTransformer.js +33 -21
  98. package/serialization/SerializationContext.d.ts +6 -6
  99. package/serialization/SerializationContext.js +3 -3
  100. package/types/ArrayType.d.ts +1 -1
  101. package/types/ArrayType.js +2 -3
  102. package/types/BigIntType.d.ts +8 -6
  103. package/types/BigIntType.js +1 -1
  104. package/types/BlobType.d.ts +0 -1
  105. package/types/BlobType.js +0 -3
  106. package/types/BooleanType.d.ts +1 -0
  107. package/types/BooleanType.js +3 -0
  108. package/types/DecimalType.d.ts +6 -4
  109. package/types/DecimalType.js +2 -2
  110. package/types/DoubleType.js +1 -1
  111. package/types/EnumArrayType.js +1 -2
  112. package/types/JsonType.d.ts +1 -1
  113. package/types/JsonType.js +7 -2
  114. package/types/TinyIntType.js +1 -1
  115. package/types/Type.d.ts +2 -4
  116. package/types/Type.js +3 -3
  117. package/types/Uint8ArrayType.d.ts +0 -1
  118. package/types/Uint8ArrayType.js +1 -4
  119. package/types/index.d.ts +1 -1
  120. package/typings.d.ts +446 -181
  121. package/typings.js +99 -44
  122. package/unit-of-work/ChangeSet.d.ts +4 -6
  123. package/unit-of-work/ChangeSet.js +4 -5
  124. package/unit-of-work/ChangeSetComputer.d.ts +3 -8
  125. package/unit-of-work/ChangeSetComputer.js +41 -20
  126. package/unit-of-work/ChangeSetPersister.d.ts +13 -12
  127. package/unit-of-work/ChangeSetPersister.js +94 -36
  128. package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
  129. package/unit-of-work/CommitOrderCalculator.js +13 -13
  130. package/unit-of-work/IdentityMap.d.ts +12 -0
  131. package/unit-of-work/IdentityMap.js +39 -1
  132. package/unit-of-work/UnitOfWork.d.ts +27 -3
  133. package/unit-of-work/UnitOfWork.js +248 -90
  134. package/utils/AbstractMigrator.d.ts +101 -0
  135. package/utils/AbstractMigrator.js +305 -0
  136. package/utils/AbstractSchemaGenerator.d.ts +5 -5
  137. package/utils/AbstractSchemaGenerator.js +28 -17
  138. package/utils/AsyncContext.d.ts +6 -0
  139. package/utils/AsyncContext.js +42 -0
  140. package/utils/Configuration.d.ts +797 -209
  141. package/utils/Configuration.js +150 -192
  142. package/utils/ConfigurationLoader.d.ts +1 -54
  143. package/utils/ConfigurationLoader.js +1 -352
  144. package/utils/Cursor.d.ts +0 -3
  145. package/utils/Cursor.js +24 -11
  146. package/utils/DataloaderUtils.d.ts +10 -5
  147. package/utils/DataloaderUtils.js +29 -12
  148. package/utils/EntityComparator.d.ts +16 -9
  149. package/utils/EntityComparator.js +157 -57
  150. package/utils/QueryHelper.d.ts +18 -6
  151. package/utils/QueryHelper.js +76 -23
  152. package/utils/RawQueryFragment.d.ts +28 -34
  153. package/utils/RawQueryFragment.js +35 -71
  154. package/utils/RequestContext.js +2 -2
  155. package/utils/TransactionContext.js +2 -2
  156. package/utils/TransactionManager.js +28 -4
  157. package/utils/Utils.d.ts +14 -127
  158. package/utils/Utils.js +80 -396
  159. package/utils/clone.js +8 -23
  160. package/utils/env-vars.d.ts +7 -0
  161. package/utils/env-vars.js +97 -0
  162. package/utils/fs-utils.d.ts +34 -0
  163. package/utils/fs-utils.js +196 -0
  164. package/utils/index.d.ts +1 -3
  165. package/utils/index.js +1 -3
  166. package/utils/upsert-utils.d.ts +9 -4
  167. package/utils/upsert-utils.js +46 -3
  168. package/decorators/Check.d.ts +0 -3
  169. package/decorators/Check.js +0 -13
  170. package/decorators/CreateRequestContext.d.ts +0 -3
  171. package/decorators/CreateRequestContext.js +0 -32
  172. package/decorators/Embeddable.d.ts +0 -8
  173. package/decorators/Embeddable.js +0 -11
  174. package/decorators/Embedded.d.ts +0 -12
  175. package/decorators/Embedded.js +0 -18
  176. package/decorators/Entity.d.ts +0 -33
  177. package/decorators/Entity.js +0 -12
  178. package/decorators/Enum.d.ts +0 -9
  179. package/decorators/Enum.js +0 -16
  180. package/decorators/Filter.d.ts +0 -2
  181. package/decorators/Filter.js +0 -8
  182. package/decorators/Formula.d.ts +0 -4
  183. package/decorators/Formula.js +0 -15
  184. package/decorators/Indexed.d.ts +0 -19
  185. package/decorators/Indexed.js +0 -20
  186. package/decorators/ManyToMany.d.ts +0 -42
  187. package/decorators/ManyToMany.js +0 -14
  188. package/decorators/ManyToOne.d.ts +0 -34
  189. package/decorators/ManyToOne.js +0 -14
  190. package/decorators/OneToMany.d.ts +0 -28
  191. package/decorators/OneToMany.js +0 -17
  192. package/decorators/OneToOne.d.ts +0 -28
  193. package/decorators/OneToOne.js +0 -7
  194. package/decorators/PrimaryKey.d.ts +0 -8
  195. package/decorators/PrimaryKey.js +0 -20
  196. package/decorators/Property.d.ts +0 -250
  197. package/decorators/Property.js +0 -32
  198. package/decorators/Transactional.d.ts +0 -14
  199. package/decorators/Transactional.js +0 -28
  200. package/decorators/hooks.d.ts +0 -16
  201. package/decorators/hooks.js +0 -47
  202. package/decorators/index.d.ts +0 -17
  203. package/decorators/index.js +0 -17
  204. package/entity/ArrayCollection.d.ts +0 -118
  205. package/entity/ArrayCollection.js +0 -407
  206. package/entity/EntityValidator.d.ts +0 -19
  207. package/entity/EntityValidator.js +0 -150
  208. package/metadata/ReflectMetadataProvider.d.ts +0 -8
  209. package/metadata/ReflectMetadataProvider.js +0 -44
  210. package/utils/resolveContextProvider.d.ts +0 -10
  211. package/utils/resolveContextProvider.js +0 -28
package/EntityManager.js CHANGED
@@ -1,15 +1,12 @@
1
- import { inspect } from 'node:util';
2
- import DataLoader from 'dataloader';
3
- import { getOnConflictReturningFields } from './utils/upsert-utils.js';
1
+ import { getOnConflictReturningFields, getWhereCondition } from './utils/upsert-utils.js';
4
2
  import { Utils } from './utils/Utils.js';
5
3
  import { Cursor } from './utils/Cursor.js';
6
- import { DataloaderUtils } from './utils/DataloaderUtils.js';
7
4
  import { QueryHelper } from './utils/QueryHelper.js';
8
5
  import { TransactionContext } from './utils/TransactionContext.js';
9
- import { isRaw, RawQueryFragment } from './utils/RawQueryFragment.js';
6
+ import { isRaw, Raw } from './utils/RawQueryFragment.js';
10
7
  import { EntityFactory } from './entity/EntityFactory.js';
11
8
  import { EntityAssigner } from './entity/EntityAssigner.js';
12
- import { EntityValidator } from './entity/EntityValidator.js';
9
+ import { validateEmptyWhere, validateParams, validatePrimaryKey, validateProperty } from './entity/validators.js';
13
10
  import { EntityLoader } from './entity/EntityLoader.js';
14
11
  import { Reference } from './entity/Reference.js';
15
12
  import { helper } from './entity/wrap.js';
@@ -19,7 +16,7 @@ import { EventType, FlushMode, LoadStrategy, LockMode, PopulateHint, PopulatePat
19
16
  import { EventManager } from './events/EventManager.js';
20
17
  import { TransactionEventBroadcaster } from './events/TransactionEventBroadcaster.js';
21
18
  import { OptimisticLockError, ValidationError } from './errors.js';
22
- import { getLoadingStrategy } from './entity/utils.js';
19
+ import { applyPopulateHints, getLoadingStrategy } from './entity/utils.js';
23
20
  import { TransactionManager } from './utils/TransactionManager.js';
24
21
  /**
25
22
  * The EntityManager is the central access point to ORM functionality. It is a facade to all different ORM subsystems
@@ -36,11 +33,8 @@ export class EntityManager {
36
33
  _id = EntityManager.counter++;
37
34
  global = false;
38
35
  name;
39
- refLoader = new DataLoader(DataloaderUtils.getRefBatchLoadFn(this));
40
- colLoader = new DataLoader(DataloaderUtils.getColBatchLoadFn(this));
41
- colLoaderMtoN = new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(this));
42
- validator;
43
- repositoryMap = {};
36
+ loaders = {};
37
+ repositoryMap = new Map();
44
38
  entityLoader;
45
39
  comparator;
46
40
  entityFactory;
@@ -64,7 +58,6 @@ export class EntityManager {
64
58
  this.eventManager = eventManager;
65
59
  this.entityLoader = new EntityLoader(this);
66
60
  this.name = this.config.get('contextName');
67
- this.validator = new EntityValidator(this.config.get('strict'));
68
61
  this.comparator = this.config.getComparator(this.metadata);
69
62
  this.resultCache = this.config.getResultCacheAdapter();
70
63
  this.disableTransactions = this.config.get('disableTransactions');
@@ -94,13 +87,12 @@ export class EntityManager {
94
87
  * Gets repository for given entity. You can pass either string name or entity class reference.
95
88
  */
96
89
  getRepository(entityName) {
97
- entityName = Utils.className(entityName);
98
- if (!this.repositoryMap[entityName]) {
99
- const meta = this.metadata.get(entityName);
90
+ const meta = this.metadata.get(entityName);
91
+ if (!this.repositoryMap.has(meta)) {
100
92
  const RepositoryClass = this.config.getRepositoryClass(meta.repository);
101
- this.repositoryMap[entityName] = new RepositoryClass(this, entityName);
93
+ this.repositoryMap.set(meta, new RepositoryClass(this, entityName));
102
94
  }
103
- return this.repositoryMap[entityName];
95
+ return this.repositoryMap.get(meta);
104
96
  }
105
97
  /**
106
98
  * Shortcut for `em.getRepository()`.
@@ -108,12 +100,6 @@ export class EntityManager {
108
100
  repo(entityName) {
109
101
  return this.getRepository(entityName);
110
102
  }
111
- /**
112
- * Gets EntityValidator instance
113
- */
114
- getValidator() {
115
- return this.validator;
116
- }
117
103
  /**
118
104
  * Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
119
105
  */
@@ -128,10 +114,15 @@ export class EntityManager {
128
114
  const em = this.getContext();
129
115
  em.prepareOptions(options);
130
116
  await em.tryFlush(entityName, options);
131
- entityName = Utils.className(entityName);
132
117
  where = await em.processWhere(entityName, where, options, 'read');
133
- em.validator.validateParams(where);
134
- options.orderBy = options.orderBy || {};
118
+ validateParams(where);
119
+ const meta = this.metadata.get(entityName);
120
+ if (meta.orderBy) {
121
+ options.orderBy = QueryHelper.mergeOrderBy(options.orderBy, meta.orderBy);
122
+ }
123
+ else {
124
+ options.orderBy ??= {};
125
+ }
135
126
  options.populate = await em.preparePopulate(entityName, options);
136
127
  const populate = options.populate;
137
128
  const cacheKey = em.cacheKey(entityName, options, 'em.find', where);
@@ -145,12 +136,11 @@ export class EntityManager {
145
136
  });
146
137
  return cached.data;
147
138
  }
148
- const meta = this.metadata.get(entityName);
149
139
  options = { ...options };
150
140
  // save the original hint value so we know it was infer/all
151
141
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
152
142
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
153
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
143
+ options.populateFilter = await this.getJoinedFilters(meta, options);
154
144
  const results = await em.driver.find(entityName, where, { ctx: em.transactionContext, em, ...options });
155
145
  if (results.length === 0) {
156
146
  await em.storeCache(options.cache, cached, []);
@@ -182,6 +172,60 @@ export class EntityManager {
182
172
  }
183
173
  return unique;
184
174
  }
175
+ /**
176
+ * Finds all entities and returns an async iterable (async generator) that yields results one by one.
177
+ * The results are merged and mapped to entity instances, without adding them to the identity map.
178
+ * You can disable merging by passing the options `{ mergeResults: false }`.
179
+ * With `mergeResults` disabled, to-many collections will contain at most one item, and you will get duplicate
180
+ * root entities when there are multiple items in the populated collection.
181
+ * This is useful for processing large datasets without loading everything into memory at once.
182
+ *
183
+ * ```ts
184
+ * const stream = em.stream(Book, { populate: ['author'] });
185
+ *
186
+ * for await (const book of stream) {
187
+ * // book is an instance of Book entity
188
+ * console.log(book.title, book.author.name);
189
+ * }
190
+ * ```
191
+ */
192
+ async *stream(entityName, options = {}) {
193
+ const em = this.getContext();
194
+ em.prepareOptions(options);
195
+ options.strategy = 'joined';
196
+ await em.tryFlush(entityName, options);
197
+ const where = await em.processWhere(entityName, options.where ?? {}, options, 'read');
198
+ validateParams(where);
199
+ options.orderBy = options.orderBy || {};
200
+ options.populate = await em.preparePopulate(entityName, options);
201
+ const meta = this.metadata.get(entityName);
202
+ options = { ...options };
203
+ // save the original hint value so we know it was infer/all
204
+ options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
205
+ options.populateWhere = this.createPopulateWhere({ ...where }, options);
206
+ options.populateFilter = await this.getJoinedFilters(meta, options);
207
+ const stream = em.driver.stream(entityName, where, {
208
+ ctx: em.transactionContext,
209
+ mapResults: false,
210
+ ...options,
211
+ });
212
+ for await (const data of stream) {
213
+ const fork = em.fork();
214
+ const entity = fork.entityFactory.create(entityName, data, {
215
+ refresh: options.refresh,
216
+ schema: options.schema,
217
+ convertCustomTypes: true,
218
+ });
219
+ helper(entity).setSerializationContext({
220
+ populate: options.populate,
221
+ fields: options.fields,
222
+ exclude: options.exclude,
223
+ });
224
+ await fork.unitOfWork.dispatchOnLoadEvent();
225
+ fork.clear();
226
+ yield entity;
227
+ }
228
+ }
185
229
  /**
186
230
  * Finds all entities of given type, optionally matching the `where` condition provided in the `options` parameter.
187
231
  */
@@ -195,7 +239,7 @@ export class EntityManager {
195
239
  if (options.populateWhere === PopulateHint.ALL) {
196
240
  return { where: {}, populateWhere: options.populateWhere };
197
241
  }
198
- /* v8 ignore next 3 */
242
+ /* v8 ignore next */
199
243
  if (options.populateWhere === PopulateHint.INFER) {
200
244
  return { where, populateWhere: options.populateWhere };
201
245
  }
@@ -204,12 +248,12 @@ export class EntityManager {
204
248
  /**
205
249
  * Registers global filter to this entity manager. Global filters are enabled by default (unless disabled via last parameter).
206
250
  */
207
- addFilter(name, cond, entityName, enabled = true) {
208
- const options = { name, cond, default: enabled };
209
- if (entityName) {
210
- options.entity = Utils.asArray(entityName).map(n => Utils.className(n));
251
+ addFilter(options) {
252
+ if (options.entity) {
253
+ options.entity = Utils.asArray(options.entity).map(n => Utils.className(n));
211
254
  }
212
- this.getContext(false).filters[name] = options;
255
+ options.default ??= true;
256
+ this.getContext(false).filters[options.name] = options;
213
257
  }
214
258
  /**
215
259
  * Sets filter parameter values globally inside context defined by this entity manager.
@@ -257,18 +301,18 @@ export class EntityManager {
257
301
  // this method only handles the problem for mongo driver, SQL drivers have their implementation inside QueryBuilder
258
302
  applyDiscriminatorCondition(entityName, where) {
259
303
  const meta = this.metadata.find(entityName);
260
- if (!meta?.discriminatorValue) {
304
+ if (meta?.root.inheritanceType !== 'sti' || !meta?.discriminatorValue) {
261
305
  return where;
262
306
  }
263
- const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.find(cls));
307
+ const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.get(cls));
264
308
  const children = [];
265
309
  const lookUpChildren = (ret, type) => {
266
310
  const children = types.filter(meta2 => meta2.extends === type);
267
- children.forEach(m => lookUpChildren(ret, m.className));
311
+ children.forEach(m => lookUpChildren(ret, m.class));
268
312
  ret.push(...children.filter(c => c.discriminatorValue));
269
313
  return children;
270
314
  };
271
- lookUpChildren(children, meta.className);
315
+ lookUpChildren(children, meta.class);
272
316
  /* v8 ignore next */
273
317
  where[meta.root.discriminatorColumn] = children.length > 0 ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] } : meta.discriminatorValue;
274
318
  return where;
@@ -284,60 +328,77 @@ export class EntityManager {
284
328
  }
285
329
  return ret;
286
330
  }
287
- async getJoinedFilters(meta, cond, options) {
331
+ async getJoinedFilters(meta, options) {
332
+ // If user provided populateFilter, merge it with computed filters
333
+ const userFilter = options.populateFilter;
334
+ if (!this.config.get('filtersOnRelations') || !options.populate) {
335
+ return userFilter;
336
+ }
288
337
  const ret = {};
289
- if (options.populate) {
290
- for (const hint of options.populate) {
291
- const field = hint.field.split(':')[0];
292
- const prop = meta.properties[field];
293
- const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
294
- const joined = strategy === LoadStrategy.JOINED && prop.kind !== ReferenceKind.SCALAR;
295
- if (!joined && !hint.filter) {
296
- continue;
297
- }
298
- const where = await this.applyFilters(prop.type, {}, options.filters ?? {}, 'read', { ...options, populate: hint.children });
299
- const where2 = await this.getJoinedFilters(prop.targetMeta, {}, { ...options, populate: hint.children, populateWhere: PopulateHint.ALL });
300
- if (Utils.hasObjectKeys(where)) {
301
- ret[field] = ret[field] ? { $and: [where, ret[field]] } : where;
338
+ for (const hint of options.populate) {
339
+ const field = hint.field.split(':')[0];
340
+ const prop = meta.properties[field];
341
+ const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
342
+ const joined = strategy === LoadStrategy.JOINED && prop.kind !== ReferenceKind.SCALAR;
343
+ if (!joined && !hint.filter) {
344
+ continue;
345
+ }
346
+ const filters = QueryHelper.mergePropertyFilters(prop.filters, options.filters);
347
+ const where = await this.applyFilters(prop.targetMeta.class, {}, filters, 'read', {
348
+ ...options,
349
+ populate: hint.children,
350
+ });
351
+ const where2 = await this.getJoinedFilters(prop.targetMeta, {
352
+ ...options,
353
+ filters,
354
+ populate: hint.children,
355
+ populateWhere: PopulateHint.ALL,
356
+ });
357
+ if (Utils.hasObjectKeys(where)) {
358
+ ret[field] = ret[field] ? { $and: [where, ret[field]] } : where;
359
+ }
360
+ if (where2 && Utils.hasObjectKeys(where2)) {
361
+ if (ret[field]) {
362
+ Utils.merge(ret[field], where2);
302
363
  }
303
- if (Utils.hasObjectKeys(where2)) {
304
- if (ret[field]) {
305
- Utils.merge(ret[field], where2);
306
- }
307
- else {
308
- ret[field] = where2;
309
- }
364
+ else {
365
+ ret[field] = where2;
310
366
  }
311
367
  }
312
368
  }
313
- return ret;
369
+ // Merge user-provided populateFilter with computed filters
370
+ if (userFilter) {
371
+ Utils.merge(ret, userFilter);
372
+ }
373
+ return Utils.hasObjectKeys(ret) ? ret : undefined;
314
374
  }
315
375
  /**
316
376
  * When filters are active on M:1 or 1:1 relations, we need to ref join them eagerly as they might affect the FK value.
317
377
  */
318
- async autoJoinRefsForFilters(meta, options) {
319
- if (!meta || !this.config.get('autoJoinRefsForFilters')) {
378
+ async autoJoinRefsForFilters(meta, options, parent) {
379
+ if (!meta || !this.config.get('autoJoinRefsForFilters') || options.filters === false) {
320
380
  return;
321
381
  }
322
- const props = meta.relations.filter(prop => {
323
- return !prop.object && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)
324
- && ((options.fields?.length ?? 0) === 0 || options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)));
325
- });
326
382
  const ret = options.populate;
327
- for (const prop of props) {
328
- const cond = await this.applyFilters(prop.type, {}, options.filters ?? {}, 'read', options);
383
+ 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)) {
388
+ continue;
389
+ }
390
+ options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
391
+ const cond = await this.applyFilters(prop.targetMeta.class, {}, options.filters, 'read', options);
329
392
  if (!Utils.isEmpty(cond)) {
330
393
  const populated = options.populate.filter(({ field }) => field.split(':')[0] === prop.name);
331
394
  let found = false;
332
- if (populated.length > 0) {
333
- for (const hint of populated) {
334
- if (!hint.all) {
335
- hint.filter = true;
336
- found = true;
337
- }
338
- else if (hint.field === `${prop.name}:ref`) {
339
- found = true;
340
- }
395
+ for (const hint of populated) {
396
+ if (!hint.all) {
397
+ hint.filter = true;
398
+ }
399
+ const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
400
+ if (hint.field === `${prop.name}:ref` || (hint.filter && strategy === LoadStrategy.JOINED)) {
401
+ found = true;
341
402
  }
342
403
  }
343
404
  if (!found) {
@@ -345,21 +406,26 @@ export class EntityManager {
345
406
  }
346
407
  }
347
408
  }
409
+ for (const hint of ret) {
410
+ const [field, ref] = hint.field.split(':');
411
+ const prop = meta?.properties[field];
412
+ if (prop && !ref) {
413
+ hint.children ??= [];
414
+ await this.autoJoinRefsForFilters(prop.targetMeta, { ...options, populate: hint.children }, { class: meta.root.class, propName: prop.name });
415
+ }
416
+ }
348
417
  }
349
418
  /**
350
419
  * @internal
351
420
  */
352
421
  async applyFilters(entityName, where, options, type, findOptions) {
353
- const meta = this.metadata.find(entityName);
422
+ const meta = this.metadata.get(entityName);
354
423
  const filters = [];
355
424
  const ret = [];
356
- if (!meta) {
357
- return where;
358
- }
359
425
  const active = new Set();
360
426
  const push = (source) => {
361
427
  const activeFilters = QueryHelper
362
- .getActiveFilters(entityName, options, source)
428
+ .getActiveFilters(meta, options, source)
363
429
  .filter(f => !active.has(f.name));
364
430
  filters.push(...activeFilters);
365
431
  activeFilters.forEach(f => active.add(f.name));
@@ -374,24 +440,28 @@ export class EntityManager {
374
440
  let cond;
375
441
  if (filter.cond instanceof Function) {
376
442
  // @ts-ignore
377
- const args = Utils.isPlainObject(options[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
443
+ const args = Utils.isPlainObject(options?.[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
378
444
  if (!args && filter.cond.length > 0 && filter.args !== false) {
379
445
  throw new Error(`No arguments provided for filter '${filter.name}'`);
380
446
  }
381
- cond = await filter.cond(args, type, this, findOptions, entityName);
447
+ cond = await filter.cond(args, type, this, findOptions, Utils.className(entityName));
382
448
  }
383
449
  else {
384
450
  cond = filter.cond;
385
451
  }
386
- ret.push(QueryHelper.processWhere({
452
+ cond = QueryHelper.processWhere({
387
453
  where: cond,
388
454
  entityName,
389
455
  metadata: this.metadata,
390
456
  platform: this.driver.getPlatform(),
391
457
  aliased: type === 'read',
392
- }));
458
+ });
459
+ if (filter.strict) {
460
+ Object.defineProperty(cond, '__strict', { value: filter.strict, enumerable: false });
461
+ }
462
+ ret.push(cond);
393
463
  }
394
- const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c));
464
+ const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c) || Raw.hasObjectFragments(c));
395
465
  return conds.length > 1 ? { $and: conds } : conds[0];
396
466
  }
397
467
  /**
@@ -402,12 +472,10 @@ export class EntityManager {
402
472
  const em = this.getContext(false);
403
473
  await em.tryFlush(entityName, options);
404
474
  options.flushMode = 'commit'; // do not try to auto flush again
405
- return RawQueryFragment.run(async () => {
406
- return Promise.all([
407
- em.find(entityName, where, options),
408
- em.count(entityName, where, options),
409
- ]);
410
- });
475
+ return Promise.all([
476
+ em.find(entityName, where, options),
477
+ em.count(entityName, where, options),
478
+ ]);
411
479
  }
412
480
  /**
413
481
  * Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as {@apilink Cursor} object.
@@ -423,21 +491,21 @@ export class EntityManager {
423
491
  * - POJO/entity instance
424
492
  *
425
493
  * ```ts
426
- * const currentCursor = await em.findByCursor(User, {}, {
494
+ * const currentCursor = await em.findByCursor(User, {
427
495
  * first: 10,
428
496
  * after: previousCursor, // cursor instance
429
497
  * orderBy: { id: 'desc' },
430
498
  * });
431
499
  *
432
500
  * // to fetch next page
433
- * const nextCursor = await em.findByCursor(User, {}, {
501
+ * const nextCursor = await em.findByCursor(User, {
434
502
  * first: 10,
435
503
  * after: currentCursor.endCursor, // opaque string
436
504
  * orderBy: { id: 'desc' },
437
505
  * });
438
506
  *
439
507
  * // to fetch next page
440
- * const nextCursor2 = await em.findByCursor(User, {}, {
508
+ * const nextCursor2 = await em.findByCursor(User, {
441
509
  * first: 10,
442
510
  * after: { id: lastSeenId }, // entity-like POJO
443
511
  * orderBy: { id: 'desc' },
@@ -465,16 +533,16 @@ export class EntityManager {
465
533
  * }
466
534
  * ```
467
535
  */
468
- async findByCursor(entityName, where, options) {
536
+ async findByCursor(entityName, options) {
469
537
  const em = this.getContext(false);
470
- entityName = Utils.className(entityName);
471
538
  options.overfetch ??= true;
472
- if (Utils.isEmpty(options.orderBy)) {
539
+ options.where ??= {};
540
+ if (Utils.isEmpty(options.orderBy) && !Raw.hasObjectFragments(options.orderBy)) {
473
541
  throw new Error('Explicit `orderBy` option required');
474
542
  }
475
543
  const [entities, count] = options.includeCount !== false
476
- ? await em.findAndCount(entityName, where, options)
477
- : [await em.find(entityName, where, options)];
544
+ ? await em.findAndCount(entityName, options.where, options)
545
+ : [await em.find(entityName, options.where, options)];
478
546
  return new Cursor(entities, count, options, this.metadata.get(entityName));
479
547
  }
480
548
  /**
@@ -486,9 +554,9 @@ export class EntityManager {
486
554
  const ret = await this.refresh(entity, options);
487
555
  if (!ret) {
488
556
  options.failHandler ??= this.config.get('findOneOrFailHandler');
489
- const entityName = entity.constructor.name;
490
- const where = helper(entity).getPrimaryKey();
491
- throw options.failHandler(entityName, where);
557
+ const wrapped = helper(entity);
558
+ const where = wrapped.getPrimaryKey();
559
+ throw options.failHandler(wrapped.__meta.className, where);
492
560
  }
493
561
  return ret;
494
562
  }
@@ -499,25 +567,31 @@ export class EntityManager {
499
567
  */
500
568
  async refresh(entity, options = {}) {
501
569
  const fork = this.fork({ keepTransactionContext: true });
502
- const entityName = entity.constructor.name;
503
- const reloaded = await fork.findOne(entityName, entity, {
504
- schema: helper(entity).__schema,
570
+ const wrapped = helper(entity);
571
+ const reloaded = await fork.findOne(wrapped.__meta.class, entity, {
572
+ schema: wrapped.__schema,
505
573
  ...options,
506
574
  flushMode: FlushMode.COMMIT,
507
575
  });
508
576
  const em = this.getContext();
509
- if (reloaded) {
510
- for (const e of fork.unitOfWork.getIdentityMap()) {
511
- const ref = em.getReference(e.constructor.name, helper(e).getPrimaryKey());
512
- const data = this.comparator.prepareEntity(e);
513
- em.config.getHydrator(this.metadata).hydrate(ref, helper(ref).__meta, helper(e).serialize({ ignoreSerializers: true, includeHidden: true }), em.entityFactory, 'full', false, true);
514
- helper(ref).__originalEntityData = data;
515
- }
516
- }
517
- else {
577
+ if (!reloaded) {
518
578
  em.unitOfWork.unsetIdentity(entity);
579
+ return null;
519
580
  }
520
- return reloaded ? entity : reloaded;
581
+ let found = false;
582
+ for (const e of fork.unitOfWork.getIdentityMap()) {
583
+ const ref = em.getReference(e.constructor, helper(e).getPrimaryKey());
584
+ 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);
586
+ Utils.merge(helper(ref).__originalEntityData, this.comparator.prepareEntity(e));
587
+ found ||= ref === entity;
588
+ }
589
+ if (!found) {
590
+ const data = helper(reloaded).serialize({ ignoreSerializers: true, includeHidden: true, convertCustomTypes: true });
591
+ em.config.getHydrator(this.metadata).hydrate(entity, wrapped.__meta, data, em.entityFactory, 'full', false, true);
592
+ Utils.merge(wrapped.__originalEntityData, this.comparator.prepareEntity(reloaded));
593
+ }
594
+ return entity;
521
595
  }
522
596
  /**
523
597
  * Finds first entity matching your `where` query.
@@ -531,7 +605,6 @@ export class EntityManager {
531
605
  return ret;
532
606
  }
533
607
  const em = this.getContext();
534
- entityName = Utils.className(entityName);
535
608
  em.prepareOptions(options);
536
609
  let entity = em.unitOfWork.tryGetById(entityName, where, options.schema);
537
610
  // query for a not managed entity which is already in the identity map as it
@@ -543,13 +616,13 @@ export class EntityManager {
543
616
  await em.tryFlush(entityName, options);
544
617
  const meta = em.metadata.get(entityName);
545
618
  where = await em.processWhere(entityName, where, options, 'read');
546
- em.validator.validateEmptyWhere(where);
619
+ validateEmptyWhere(where);
547
620
  em.checkLockRequirements(options.lockMode, meta);
548
- const isOptimisticLocking = !Utils.isDefined(options.lockMode) || options.lockMode === LockMode.OPTIMISTIC;
621
+ const isOptimisticLocking = options.lockMode == null || options.lockMode === LockMode.OPTIMISTIC;
549
622
  if (entity && !em.shouldRefresh(meta, entity, options) && isOptimisticLocking) {
550
623
  return em.lockAndPopulate(meta, entity, where, options);
551
624
  }
552
- em.validator.validateParams(where);
625
+ validateParams(where);
553
626
  options.populate = await em.preparePopulate(entityName, options);
554
627
  const cacheKey = em.cacheKey(entityName, options, 'em.findOne', where);
555
628
  const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true);
@@ -568,7 +641,7 @@ export class EntityManager {
568
641
  // save the original hint value so we know it was infer/all
569
642
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
570
643
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
571
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
644
+ options.populateFilter = await this.getJoinedFilters(meta, options);
572
645
  const data = await em.driver.findOne(entityName, where, {
573
646
  ctx: em.transactionContext,
574
647
  em,
@@ -609,10 +682,10 @@ export class EntityManager {
609
682
  if (!entity || isStrictViolation) {
610
683
  const key = options.strict ? 'findExactlyOneOrFailHandler' : 'findOneOrFailHandler';
611
684
  options.failHandler ??= this.config.get(key);
612
- entityName = Utils.className(entityName);
685
+ const name = Utils.className(entityName);
613
686
  /* v8 ignore next */
614
687
  where = Utils.isEntity(where) ? helper(where).getPrimaryKey() : where;
615
- throw options.failHandler(entityName, where);
688
+ throw options.failHandler(name, where);
616
689
  }
617
690
  return entity;
618
691
  }
@@ -652,11 +725,11 @@ export class EntityManager {
652
725
  let where;
653
726
  let entity = null;
654
727
  if (data === undefined) {
655
- entityName = entityNameOrEntity.constructor.name;
728
+ entityName = entityNameOrEntity.constructor;
656
729
  data = entityNameOrEntity;
657
730
  }
658
731
  else {
659
- entityName = Utils.className(entityNameOrEntity);
732
+ entityName = entityNameOrEntity;
660
733
  }
661
734
  const meta = this.metadata.get(entityName);
662
735
  const convertCustomTypes = !Utils.isEntity(data);
@@ -679,26 +752,9 @@ export class EntityManager {
679
752
  }
680
753
  }
681
754
  }
682
- const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
683
- const propIndex = !isRaw(unique) && unique.findIndex(p => data[p] != null);
684
- if (options.onConflictFields || where == null) {
685
- if (propIndex !== false && propIndex >= 0) {
686
- where = { [unique[propIndex]]: data[unique[propIndex]] };
687
- }
688
- else if (meta.uniques.length > 0) {
689
- for (const u of meta.uniques) {
690
- if (Utils.asArray(u.properties).every(p => data[p] != null)) {
691
- where = Utils.asArray(u.properties).reduce((o, key) => {
692
- o[key] = data[key];
693
- return o;
694
- }, {});
695
- break;
696
- }
697
- }
698
- }
699
- }
755
+ where = getWhereCondition(meta, options.onConflictFields, data, where).where;
700
756
  data = QueryHelper.processObjectParams(data);
701
- em.validator.validateParams(data, 'insert data');
757
+ validateParams(data, 'insert data');
702
758
  if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
703
759
  await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity: data, em, meta }, meta);
704
760
  }
@@ -736,7 +792,7 @@ export class EntityManager {
736
792
  where[meta.primaryKeys[0]] = ret.insertId;
737
793
  }
738
794
  }
739
- const data2 = await this.driver.findOne(meta.className, where, {
795
+ const data2 = await this.driver.findOne(meta.class, where, {
740
796
  fields: returning,
741
797
  ctx: em.transactionContext,
742
798
  convertCustomTypes: true,
@@ -791,11 +847,11 @@ export class EntityManager {
791
847
  let entityName;
792
848
  let propIndex;
793
849
  if (data === undefined) {
794
- entityName = entityNameOrEntity[0].constructor.name;
850
+ entityName = entityNameOrEntity[0].constructor;
795
851
  data = entityNameOrEntity;
796
852
  }
797
853
  else {
798
- entityName = Utils.className(entityNameOrEntity);
854
+ entityName = entityNameOrEntity;
799
855
  }
800
856
  const batchSize = options.batchSize ?? this.config.get('batchSize');
801
857
  if (data.length > batchSize) {
@@ -839,32 +895,18 @@ export class EntityManager {
839
895
  }
840
896
  }
841
897
  }
842
- const unique = meta.props.filter(p => p.unique).map(p => p.name);
843
- propIndex = unique.findIndex(p => row[p] != null);
844
- if (options.onConflictFields || where == null) {
845
- if (propIndex >= 0) {
846
- where = { [unique[propIndex]]: row[unique[propIndex]] };
847
- }
848
- else if (meta.uniques.length > 0) {
849
- for (const u of meta.uniques) {
850
- if (Utils.asArray(u.properties).every(p => row[p] != null)) {
851
- where = Utils.asArray(u.properties).reduce((o, key) => {
852
- o[key] = row[key];
853
- return o;
854
- }, {});
855
- break;
856
- }
857
- }
858
- }
859
- }
860
- row = QueryHelper.processObjectParams(row);
898
+ 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);
900
+ const tmp = getWhereCondition(meta, options.onConflictFields, row, where);
901
+ propIndex = tmp.propIndex;
861
902
  where = QueryHelper.processWhere({
862
- where,
903
+ where: tmp.where,
863
904
  entityName,
864
905
  metadata: this.metadata,
865
906
  platform: this.getPlatform(),
866
907
  });
867
- em.validator.validateParams(row, 'insert data');
908
+ row = QueryHelper.processObjectParams(row);
909
+ validateParams(row, 'insert data');
868
910
  allData.push(row);
869
911
  allWhere.push(where);
870
912
  }
@@ -905,7 +947,7 @@ export class EntityManager {
905
947
  const reloadFields = returning.length > 0 && !(this.getPlatform().usesReturningStatement() && res.rows?.length);
906
948
  if (options.onConflictAction === 'ignore' || (!res.rows?.length && loadPK.size > 0) || reloadFields) {
907
949
  const unique = meta.hydrateProps.filter(p => !p.lazy).map(p => p.name);
908
- const add = new Set(propIndex >= 0 ? [unique[propIndex]] : []);
950
+ const add = new Set(propIndex !== false && propIndex >= 0 ? [unique[propIndex]] : []);
909
951
  for (const cond of loadPK.values()) {
910
952
  Utils.keys(cond).forEach(key => add.add(key));
911
953
  }
@@ -917,7 +959,7 @@ export class EntityManager {
917
959
  where.$or[idx][prop] = item[prop];
918
960
  });
919
961
  });
920
- const data2 = await this.driver.find(meta.className, where, {
962
+ const data2 = await this.driver.find(meta.class, where, {
921
963
  fields: returning.concat(...add).concat(...(Array.isArray(uniqueFields) ? uniqueFields : [])),
922
964
  ctx: em.transactionContext,
923
965
  convertCustomTypes: true,
@@ -934,7 +976,7 @@ export class EntityManager {
934
976
  });
935
977
  return this.comparator.matching(entityName, cond, tmp);
936
978
  });
937
- /* v8 ignore next 3 */
979
+ /* v8 ignore next */
938
980
  if (!row) {
939
981
  throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
940
982
  }
@@ -957,7 +999,7 @@ export class EntityManager {
957
999
  }, {});
958
1000
  return this.comparator.matching(entityName, cond, pk);
959
1001
  });
960
- /* v8 ignore next 3 */
1002
+ /* v8 ignore next */
961
1003
  if (!row) {
962
1004
  throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
963
1005
  }
@@ -979,6 +1021,29 @@ export class EntityManager {
979
1021
  }
980
1022
  /**
981
1023
  * Runs your callback wrapped inside a database transaction.
1024
+ *
1025
+ * If a transaction is already active, a new savepoint (nested transaction) will be created by default. This behavior
1026
+ * can be controlled via the `propagation` option. Use the provided EntityManager instance for all operations that
1027
+ * should be part of the transaction. You can safely use a global EntityManager instance from a DI container, as this
1028
+ * method automatically creates an async context for the transaction.
1029
+ *
1030
+ * **Concurrency note:** When running multiple transactions concurrently (e.g. in parallel requests or jobs), use the
1031
+ * `clear: true` option. This ensures the callback runs in a clear fork of the EntityManager, providing full isolation
1032
+ * between concurrent transactional handlers. Using `clear: true` is an alternative to forking explicitly and calling
1033
+ * the method on the new fork – it already provides the necessary isolation for safe concurrent usage.
1034
+ *
1035
+ * **Propagation note:** Changes made within a transaction (whether top-level or nested) are always propagated to the
1036
+ * parent context, unless the parent context is a global one. If you want to avoid that, fork the EntityManager first
1037
+ * and then call this method on the fork.
1038
+ *
1039
+ * **Example:**
1040
+ * ```ts
1041
+ * await em.transactional(async (em) => {
1042
+ * const author = new Author('Jon');
1043
+ * em.persist(author);
1044
+ * // flush is called automatically at the end of the callback
1045
+ * });
1046
+ * ```
982
1047
  */
983
1048
  async transactional(cb, options = {}) {
984
1049
  const em = this.getContext(false);
@@ -1047,11 +1112,11 @@ export class EntityManager {
1047
1112
  em.prepareOptions(options);
1048
1113
  let entityName;
1049
1114
  if (data === undefined) {
1050
- entityName = entityNameOrEntity.constructor.name;
1115
+ entityName = entityNameOrEntity.constructor;
1051
1116
  data = entityNameOrEntity;
1052
1117
  }
1053
1118
  else {
1054
- entityName = Utils.className(entityNameOrEntity);
1119
+ entityName = entityNameOrEntity;
1055
1120
  }
1056
1121
  if (Utils.isEntity(data)) {
1057
1122
  if (options.schema && helper(data).getSchema() == null) {
@@ -1070,7 +1135,7 @@ export class EntityManager {
1070
1135
  return cs.getPrimaryKey();
1071
1136
  }
1072
1137
  data = QueryHelper.processObjectParams(data);
1073
- em.validator.validateParams(data, 'insert data');
1138
+ validateParams(data, 'insert data');
1074
1139
  const res = await em.driver.nativeInsert(entityName, data, { ctx: em.transactionContext, ...options });
1075
1140
  return res.insertId;
1076
1141
  }
@@ -1082,11 +1147,11 @@ export class EntityManager {
1082
1147
  em.prepareOptions(options);
1083
1148
  let entityName;
1084
1149
  if (data === undefined) {
1085
- entityName = entityNameOrEntities[0].constructor.name;
1150
+ entityName = entityNameOrEntities[0].constructor;
1086
1151
  data = entityNameOrEntities;
1087
1152
  }
1088
1153
  else {
1089
- entityName = Utils.className(entityNameOrEntities);
1154
+ entityName = entityNameOrEntities;
1090
1155
  }
1091
1156
  if (data.length === 0) {
1092
1157
  return [];
@@ -1110,7 +1175,7 @@ export class EntityManager {
1110
1175
  return css.map(cs => cs.getPrimaryKey());
1111
1176
  }
1112
1177
  data = data.map(row => QueryHelper.processObjectParams(row));
1113
- data.forEach(row => em.validator.validateParams(row, 'insert data'));
1178
+ data.forEach(row => validateParams(row, 'insert data'));
1114
1179
  const res = await em.driver.nativeInsertMany(entityName, data, { ctx: em.transactionContext, ...options });
1115
1180
  if (res.insertedIds) {
1116
1181
  return res.insertedIds;
@@ -1123,11 +1188,10 @@ export class EntityManager {
1123
1188
  async nativeUpdate(entityName, where, data, options = {}) {
1124
1189
  const em = this.getContext(false);
1125
1190
  em.prepareOptions(options);
1126
- entityName = Utils.className(entityName);
1127
1191
  data = QueryHelper.processObjectParams(data);
1128
1192
  where = await em.processWhere(entityName, where, { ...options, convertCustomTypes: false }, 'update');
1129
- em.validator.validateParams(data, 'update data');
1130
- em.validator.validateParams(where, 'update condition');
1193
+ validateParams(data, 'update data');
1194
+ validateParams(where, 'update condition');
1131
1195
  const res = await em.driver.nativeUpdate(entityName, where, data, { ctx: em.transactionContext, ...options });
1132
1196
  return res.affectedRows;
1133
1197
  }
@@ -1137,9 +1201,8 @@ export class EntityManager {
1137
1201
  async nativeDelete(entityName, where, options = {}) {
1138
1202
  const em = this.getContext(false);
1139
1203
  em.prepareOptions(options);
1140
- entityName = Utils.className(entityName);
1141
1204
  where = await em.processWhere(entityName, where, options, 'delete');
1142
- em.validator.validateParams(where, 'delete condition');
1205
+ validateParams(where, 'delete condition');
1143
1206
  const res = await em.driver.nativeDelete(entityName, where, { ctx: em.transactionContext, ...options });
1144
1207
  return res.affectedRows;
1145
1208
  }
@@ -1147,18 +1210,19 @@ export class EntityManager {
1147
1210
  * Maps raw database result to an entity and merges it to this EntityManager.
1148
1211
  */
1149
1212
  map(entityName, result, options = {}) {
1150
- entityName = Utils.className(entityName);
1151
1213
  const meta = this.metadata.get(entityName);
1152
1214
  const data = this.driver.mapResult(result, meta);
1153
- Object.keys(data).forEach(k => {
1215
+ for (const k of Object.keys(data)) {
1154
1216
  const prop = meta.properties[k];
1155
- if (prop && prop.kind === ReferenceKind.SCALAR && SCALAR_TYPES.includes(prop.runtimeType) && !prop.customType && (prop.setter || !prop.getter)) {
1156
- data[k] = this.validator.validateProperty(prop, data[k], data);
1217
+ if (prop?.kind === ReferenceKind.SCALAR && SCALAR_TYPES.has(prop.runtimeType) && !prop.customType && (prop.setter || !prop.getter)) {
1218
+ validateProperty(prop, data[k], data);
1157
1219
  }
1158
- });
1220
+ }
1159
1221
  return this.merge(entityName, data, {
1160
1222
  convertCustomTypes: true,
1161
- refresh: true, ...options,
1223
+ refresh: true,
1224
+ validate: false,
1225
+ ...options,
1162
1226
  });
1163
1227
  }
1164
1228
  /**
@@ -1167,26 +1231,21 @@ export class EntityManager {
1167
1231
  */
1168
1232
  merge(entityName, data, options = {}) {
1169
1233
  if (Utils.isEntity(entityName)) {
1170
- return this.merge(entityName.constructor.name, entityName, data);
1234
+ return this.merge(entityName.constructor, entityName, data);
1171
1235
  }
1172
1236
  const em = options.disableContextResolution ? this : this.getContext();
1173
1237
  options.schema ??= em._schema;
1174
- entityName = Utils.className(entityName);
1175
- em.validator.validatePrimaryKey(data, em.metadata.get(entityName));
1238
+ options.validate ??= true;
1239
+ options.cascade ??= true;
1240
+ validatePrimaryKey(data, em.metadata.get(entityName));
1176
1241
  let entity = em.unitOfWork.tryGetById(entityName, data, options.schema, false);
1177
1242
  if (entity && helper(entity).__managed && helper(entity).__initialized && !options.refresh) {
1178
1243
  return entity;
1179
1244
  }
1180
- const meta = em.metadata.find(entityName);
1181
- const childMeta = em.metadata.getByDiscriminatorColumn(meta, data);
1182
1245
  const dataIsEntity = Utils.isEntity(data);
1183
- if (options.keepIdentity && entity && dataIsEntity && entity !== data) {
1184
- em.entityFactory.mergeData(meta, entity, helper(data).__originalEntityData, { initialized: true, merge: true, ...options });
1185
- return entity;
1186
- }
1187
1246
  entity = dataIsEntity ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
1188
- em.validator.validate(entity, data, childMeta ?? meta);
1189
- em.unitOfWork.merge(entity);
1247
+ const visited = options.cascade ? undefined : new Set([entity]);
1248
+ em.unitOfWork.merge(entity, visited);
1190
1249
  return entity;
1191
1250
  }
1192
1251
  /**
@@ -1211,6 +1270,7 @@ export class EntityManager {
1211
1270
  ...options,
1212
1271
  newEntity: !options.managed,
1213
1272
  merge: options.managed,
1273
+ normalizeAccessors: true,
1214
1274
  });
1215
1275
  options.persist ??= em.config.get('persistOnCreate');
1216
1276
  if (options.persist && !this.getMetadata(entityName).embeddable) {
@@ -1230,7 +1290,7 @@ export class EntityManager {
1230
1290
  getReference(entityName, id, options = {}) {
1231
1291
  options.schema ??= this.schema;
1232
1292
  options.convertCustomTypes ??= false;
1233
- const meta = this.metadata.get(Utils.className(entityName));
1293
+ const meta = this.metadata.get(entityName);
1234
1294
  if (Utils.isPrimaryKey(id)) {
1235
1295
  if (meta.compositePK) {
1236
1296
  throw ValidationError.invalidCompositeIdentifier(meta);
@@ -1251,7 +1311,6 @@ export class EntityManager {
1251
1311
  // Shallow copy options since the object will be modified when deleting orderBy
1252
1312
  options = { ...options };
1253
1313
  em.prepareOptions(options);
1254
- entityName = Utils.className(entityName);
1255
1314
  await em.tryFlush(entityName, options);
1256
1315
  where = await em.processWhere(entityName, where, options, 'read');
1257
1316
  options.populate = await em.preparePopulate(entityName, options);
@@ -1260,8 +1319,8 @@ export class EntityManager {
1260
1319
  const meta = em.metadata.find(entityName);
1261
1320
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
1262
1321
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
1263
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
1264
- em.validator.validateParams(where);
1322
+ options.populateFilter = await this.getJoinedFilters(meta, options);
1323
+ validateParams(where);
1265
1324
  delete options.orderBy;
1266
1325
  const cacheKey = em.cacheKey(entityName, options, 'em.count', where);
1267
1326
  const cached = await em.tryCache(entityName, options.cache, cacheKey);
@@ -1287,7 +1346,7 @@ export class EntityManager {
1287
1346
  for (const ent of entities) {
1288
1347
  if (!Utils.isEntity(ent, true)) {
1289
1348
  /* v8 ignore next */
1290
- const meta = typeof ent === 'object' ? em.metadata.find(ent.constructor.name) : undefined;
1349
+ const meta = typeof ent === 'object' ? em.metadata.find(ent.constructor) : undefined;
1291
1350
  throw ValidationError.notDiscoveredEntity(ent, meta);
1292
1351
  }
1293
1352
  // do not cascade just yet, cascading of entities in persist stack is done when flushing
@@ -1295,13 +1354,6 @@ export class EntityManager {
1295
1354
  }
1296
1355
  return this;
1297
1356
  }
1298
- /**
1299
- * Persists your entity immediately, flushing all not yet persisted changes to the database too.
1300
- * Equivalent to `em.persist(e).flush()`.
1301
- */
1302
- async persistAndFlush(entity) {
1303
- await this.persist(entity).flush();
1304
- }
1305
1357
  /**
1306
1358
  * Marks entity for removal.
1307
1359
  * A removed entity will be removed from the database at or before transaction commit or as a result of the flush operation.
@@ -1325,13 +1377,6 @@ export class EntityManager {
1325
1377
  }
1326
1378
  return em;
1327
1379
  }
1328
- /**
1329
- * Removes an entity instance immediately, flushing all not yet persisted changes to the database too.
1330
- * Equivalent to `em.remove(e).flush()`
1331
- */
1332
- async removeAndFlush(entity) {
1333
- await this.remove(entity).flush();
1334
- }
1335
1380
  /**
1336
1381
  * Flushes all changes to objects that have been queued up to now to the database.
1337
1382
  * This effectively synchronizes the in-memory state of managed objects with the database.
@@ -1345,7 +1390,6 @@ export class EntityManager {
1345
1390
  async tryFlush(entityName, options) {
1346
1391
  const em = this.getContext();
1347
1392
  const flushMode = options.flushMode ?? em.flushMode ?? em.config.get('flushMode');
1348
- entityName = Utils.className(entityName);
1349
1393
  const meta = em.metadata.get(entityName);
1350
1394
  if (flushMode === FlushMode.COMMIT) {
1351
1395
  return;
@@ -1364,7 +1408,6 @@ export class EntityManager {
1364
1408
  * Checks whether given property can be populated on the entity.
1365
1409
  */
1366
1410
  canPopulate(entityName, property) {
1367
- entityName = Utils.className(entityName);
1368
1411
  // eslint-disable-next-line prefer-const
1369
1412
  let [p, ...parts] = property.split('.');
1370
1413
  const meta = this.metadata.find(entityName);
@@ -1374,12 +1417,11 @@ export class EntityManager {
1374
1417
  if (p.includes(':')) {
1375
1418
  p = p.split(':', 2)[0];
1376
1419
  }
1377
- const ret = p in meta.root.properties;
1378
- if (!ret) {
1379
- return !!this.metadata.find(property)?.pivotTable;
1380
- }
1420
+ // For TPT inheritance, check the entity's own properties, not just the root's
1421
+ // For STI, meta.properties includes all properties anyway
1422
+ const ret = p in meta.properties;
1381
1423
  if (parts.length > 0) {
1382
- return this.canPopulate((meta.root.properties)[p].type, parts.join('.'));
1424
+ return this.canPopulate(meta.properties[p].targetMeta.class, parts.join('.'));
1383
1425
  }
1384
1426
  return ret;
1385
1427
  }
@@ -1393,8 +1435,8 @@ export class EntityManager {
1393
1435
  }
1394
1436
  const em = this.getContext();
1395
1437
  em.prepareOptions(options);
1396
- const entityName = arr[0].constructor.name;
1397
- const preparedPopulate = await em.preparePopulate(entityName, { populate: populate }, options.validate);
1438
+ const entityName = arr[0].constructor;
1439
+ const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters }, options.validate);
1398
1440
  await em.entityLoader.populate(entityName, arr, preparedPopulate, options);
1399
1441
  return entities;
1400
1442
  }
@@ -1522,7 +1564,6 @@ export class EntityManager {
1522
1564
  */
1523
1565
  getMetadata(entityName) {
1524
1566
  if (entityName) {
1525
- entityName = Utils.className(entityName);
1526
1567
  return this.metadata.get(entityName);
1527
1568
  }
1528
1569
  return this.metadata;
@@ -1551,8 +1592,8 @@ export class EntityManager {
1551
1592
  lockTableAliases: options.lockTableAliases,
1552
1593
  });
1553
1594
  }
1554
- const preparedPopulate = await this.preparePopulate(meta.className, options);
1555
- await this.entityLoader.populate(meta.className, [entity], preparedPopulate, {
1595
+ const preparedPopulate = await this.preparePopulate(meta.class, options);
1596
+ await this.entityLoader.populate(meta.class, [entity], preparedPopulate, {
1556
1597
  ...options,
1557
1598
  ...this.getPopulateWhere(where, options),
1558
1599
  orderBy: options.populateOrderBy ?? options.orderBy,
@@ -1572,6 +1613,7 @@ export class EntityManager {
1572
1613
  return ret;
1573
1614
  }, []);
1574
1615
  }
1616
+ /** @internal */
1575
1617
  async preparePopulate(entityName, options, validate = true) {
1576
1618
  if (options.populate === false) {
1577
1619
  return [];
@@ -1612,13 +1654,13 @@ export class EntityManager {
1612
1654
  options.populate = pruneToOneRelations(meta, this.buildFields(options.fields));
1613
1655
  }
1614
1656
  if (!options.populate) {
1615
- const populate = this.entityLoader.normalizePopulate(entityName, [], options.strategy);
1657
+ const populate = this.entityLoader.normalizePopulate(entityName, [], options.strategy, true, options.exclude);
1616
1658
  await this.autoJoinRefsForFilters(meta, { ...options, populate });
1617
1659
  return populate;
1618
1660
  }
1619
1661
  if (typeof options.populate !== 'boolean') {
1620
1662
  options.populate = Utils.asArray(options.populate).map(field => {
1621
- /* v8 ignore next 3 */
1663
+ /* v8 ignore next */
1622
1664
  if (typeof field === 'boolean' || field === PopulatePath.ALL) {
1623
1665
  return [{ field: meta.primaryKeys[0], strategy: options.strategy, all: !!field }]; //
1624
1666
  }
@@ -1628,24 +1670,27 @@ export class EntityManager {
1628
1670
  options.flags.push(QueryFlag.INFER_POPULATE);
1629
1671
  return [];
1630
1672
  }
1631
- if (Utils.isString(field)) {
1673
+ if (typeof field === 'string') {
1632
1674
  return [{ field, strategy: options.strategy }];
1633
1675
  }
1634
1676
  return [field];
1635
1677
  }).flat();
1636
1678
  }
1637
- const populate = this.entityLoader.normalizePopulate(entityName, options.populate, options.strategy);
1679
+ const populate = this.entityLoader.normalizePopulate(entityName, options.populate, options.strategy, true, options.exclude);
1638
1680
  const invalid = populate.find(({ field }) => !this.canPopulate(entityName, field));
1639
1681
  if (validate && invalid) {
1640
1682
  throw ValidationError.invalidPropertyName(entityName, invalid.field);
1641
1683
  }
1642
1684
  await this.autoJoinRefsForFilters(meta, { ...options, populate });
1643
- return populate.map(field => {
1685
+ for (const field of populate) {
1644
1686
  // force select-in strategy when populating all relations as otherwise we could cause infinite loops when self-referencing
1645
1687
  const all = field.all ?? (Array.isArray(options.populate) && options.populate.includes('*'));
1646
1688
  field.strategy = all ? LoadStrategy.SELECT_IN : (options.strategy ?? field.strategy);
1647
- return field;
1648
- });
1689
+ }
1690
+ if (options.populateHints) {
1691
+ applyPopulateHints(populate, options.populateHints);
1692
+ }
1693
+ return populate;
1649
1694
  }
1650
1695
  /**
1651
1696
  * when the entity is found in identity map, we check if it was partially loaded or we are trying to populate
@@ -1665,7 +1710,7 @@ export class EntityManager {
1665
1710
  return !inlineEmbedded && !prop.lazy && !helper(entity).__loadedProperties.has(prop.name);
1666
1711
  });
1667
1712
  }
1668
- if (autoRefresh) {
1713
+ if (autoRefresh || options.filters) {
1669
1714
  return true;
1670
1715
  }
1671
1716
  if (Array.isArray(options.populate)) {
@@ -1689,7 +1734,7 @@ export class EntityManager {
1689
1734
  for (const k of ['ctx', 'strategy', 'flushMode', 'logging', 'loggerContext']) {
1690
1735
  delete opts[k];
1691
1736
  }
1692
- return [entityName, method, opts, where];
1737
+ return [Utils.className(entityName), method, opts, where];
1693
1738
  }
1694
1739
  /**
1695
1740
  * @internal
@@ -1706,21 +1751,17 @@ export class EntityManager {
1706
1751
  return { key: cacheKey, data: cached };
1707
1752
  }
1708
1753
  let data;
1754
+ const createOptions = {
1755
+ merge: true,
1756
+ convertCustomTypes: false,
1757
+ refresh,
1758
+ recomputeSnapshot: true,
1759
+ };
1709
1760
  if (Array.isArray(cached) && merge) {
1710
- data = cached.map(item => em.entityFactory.create(entityName, item, {
1711
- merge: true,
1712
- convertCustomTypes: true,
1713
- refresh,
1714
- recomputeSnapshot: true,
1715
- }));
1761
+ data = cached.map(item => em.entityFactory.create(entityName, item, createOptions));
1716
1762
  }
1717
1763
  else if (Utils.isObject(cached) && merge) {
1718
- data = em.entityFactory.create(entityName, cached, {
1719
- merge: true,
1720
- convertCustomTypes: true,
1721
- refresh,
1722
- recomputeSnapshot: true,
1723
- });
1764
+ data = em.entityFactory.create(entityName, cached, createOptions);
1724
1765
  }
1725
1766
  else {
1726
1767
  data = cached;
@@ -1735,7 +1776,7 @@ export class EntityManager {
1735
1776
  config ??= this.config.get('resultCache').global;
1736
1777
  if (config) {
1737
1778
  const em = this.getContext();
1738
- const expiration = Array.isArray(config) ? config[1] : (Utils.isNumber(config) ? config : undefined);
1779
+ const expiration = Array.isArray(config) ? config[1] : (typeof config === 'number' ? config : undefined);
1739
1780
  await em.resultCache.set(key.key, data instanceof Function ? data() : data, '', expiration);
1740
1781
  }
1741
1782
  }
@@ -1768,6 +1809,20 @@ export class EntityManager {
1768
1809
  set schema(schema) {
1769
1810
  this.getContext(false)._schema = schema ?? undefined;
1770
1811
  }
1812
+ /** @internal */
1813
+ async getDataLoader(type) {
1814
+ const em = this.getContext();
1815
+ if (em.loaders[type]) {
1816
+ return em.loaders[type];
1817
+ }
1818
+ const { DataloaderUtils } = await import('@mikro-orm/core/dataloader');
1819
+ const DataLoader = await DataloaderUtils.getDataLoader();
1820
+ 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)));
1824
+ }
1825
+ }
1771
1826
  /**
1772
1827
  * Returns the ID of this EntityManager. Respects the context, so global EM will give you the contextual ID
1773
1828
  * if executed inside request context handler.
@@ -1776,7 +1831,7 @@ export class EntityManager {
1776
1831
  return this.getContext(false)._id;
1777
1832
  }
1778
1833
  /** @ignore */
1779
- [inspect.custom]() {
1834
+ [Symbol.for('nodejs.util.inspect.custom')]() {
1780
1835
  return `[EntityManager<${this.id}>]`;
1781
1836
  }
1782
1837
  }