@mikro-orm/core 7.0.0-dev.3 → 7.0.0-dev.300

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 (214) hide show
  1. package/EntityManager.d.ts +114 -63
  2. package/EntityManager.js +385 -310
  3. package/MikroORM.d.ts +44 -35
  4. package/MikroORM.js +109 -143
  5. package/README.md +3 -2
  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 +16 -7
  13. package/connections/Connection.js +23 -14
  14. package/drivers/DatabaseDriver.d.ts +25 -16
  15. package/drivers/DatabaseDriver.js +119 -36
  16. package/drivers/IDatabaseDriver.d.ts +125 -23
  17. package/entity/BaseEntity.d.ts +63 -4
  18. package/entity/BaseEntity.js +0 -3
  19. package/entity/Collection.d.ts +102 -31
  20. package/entity/Collection.js +446 -108
  21. package/entity/EntityAssigner.d.ts +1 -1
  22. package/entity/EntityAssigner.js +26 -18
  23. package/entity/EntityFactory.d.ts +13 -1
  24. package/entity/EntityFactory.js +106 -60
  25. package/entity/EntityHelper.d.ts +2 -2
  26. package/entity/EntityHelper.js +65 -20
  27. package/entity/EntityLoader.d.ts +13 -11
  28. package/entity/EntityLoader.js +257 -107
  29. package/entity/EntityRepository.d.ts +28 -8
  30. package/entity/EntityRepository.js +8 -2
  31. package/entity/PolymorphicRef.d.ts +12 -0
  32. package/entity/PolymorphicRef.js +18 -0
  33. package/entity/Reference.d.ts +9 -12
  34. package/entity/Reference.js +34 -9
  35. package/entity/WrappedEntity.d.ts +3 -8
  36. package/entity/WrappedEntity.js +3 -8
  37. package/entity/defineEntity.d.ts +753 -0
  38. package/entity/defineEntity.js +537 -0
  39. package/entity/index.d.ts +4 -2
  40. package/entity/index.js +4 -2
  41. package/entity/utils.d.ts +13 -1
  42. package/entity/utils.js +49 -4
  43. package/entity/validators.d.ts +11 -0
  44. package/entity/validators.js +65 -0
  45. package/enums.d.ts +23 -8
  46. package/enums.js +15 -1
  47. package/errors.d.ts +25 -9
  48. package/errors.js +67 -21
  49. package/events/EventManager.d.ts +2 -1
  50. package/events/EventManager.js +19 -11
  51. package/events/EventSubscriber.d.ts +3 -1
  52. package/hydration/Hydrator.js +1 -2
  53. package/hydration/ObjectHydrator.d.ts +4 -4
  54. package/hydration/ObjectHydrator.js +89 -36
  55. package/index.d.ts +2 -2
  56. package/index.js +1 -2
  57. package/logging/DefaultLogger.d.ts +1 -1
  58. package/logging/DefaultLogger.js +1 -0
  59. package/logging/SimpleLogger.d.ts +1 -1
  60. package/logging/colors.d.ts +1 -1
  61. package/logging/colors.js +7 -6
  62. package/logging/index.d.ts +1 -0
  63. package/logging/index.js +1 -0
  64. package/logging/inspect.d.ts +2 -0
  65. package/logging/inspect.js +11 -0
  66. package/metadata/EntitySchema.d.ts +53 -27
  67. package/metadata/EntitySchema.js +125 -52
  68. package/metadata/MetadataDiscovery.d.ts +64 -10
  69. package/metadata/MetadataDiscovery.js +823 -344
  70. package/metadata/MetadataProvider.d.ts +11 -2
  71. package/metadata/MetadataProvider.js +66 -2
  72. package/metadata/MetadataStorage.d.ts +13 -11
  73. package/metadata/MetadataStorage.js +71 -38
  74. package/metadata/MetadataValidator.d.ts +32 -9
  75. package/metadata/MetadataValidator.js +198 -42
  76. package/metadata/discover-entities.d.ts +5 -0
  77. package/metadata/discover-entities.js +40 -0
  78. package/metadata/index.d.ts +1 -1
  79. package/metadata/index.js +1 -1
  80. package/metadata/types.d.ts +577 -0
  81. package/metadata/types.js +1 -0
  82. package/naming-strategy/AbstractNamingStrategy.d.ts +16 -4
  83. package/naming-strategy/AbstractNamingStrategy.js +20 -2
  84. package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
  85. package/naming-strategy/EntityCaseNamingStrategy.js +6 -5
  86. package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
  87. package/naming-strategy/MongoNamingStrategy.js +6 -6
  88. package/naming-strategy/NamingStrategy.d.ts +28 -4
  89. package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
  90. package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
  91. package/not-supported.d.ts +2 -0
  92. package/not-supported.js +4 -0
  93. package/package.json +22 -11
  94. package/platforms/ExceptionConverter.js +1 -1
  95. package/platforms/Platform.d.ts +14 -16
  96. package/platforms/Platform.js +24 -44
  97. package/serialization/EntitySerializer.d.ts +8 -3
  98. package/serialization/EntitySerializer.js +47 -27
  99. package/serialization/EntityTransformer.js +33 -21
  100. package/serialization/SerializationContext.d.ts +6 -6
  101. package/serialization/SerializationContext.js +16 -13
  102. package/types/ArrayType.d.ts +1 -1
  103. package/types/ArrayType.js +2 -3
  104. package/types/BigIntType.d.ts +9 -6
  105. package/types/BigIntType.js +4 -1
  106. package/types/BlobType.d.ts +0 -1
  107. package/types/BlobType.js +0 -3
  108. package/types/BooleanType.d.ts +2 -1
  109. package/types/BooleanType.js +3 -0
  110. package/types/DecimalType.d.ts +6 -4
  111. package/types/DecimalType.js +3 -3
  112. package/types/DoubleType.js +2 -2
  113. package/types/EnumArrayType.js +1 -2
  114. package/types/JsonType.d.ts +1 -1
  115. package/types/JsonType.js +7 -2
  116. package/types/TinyIntType.js +1 -1
  117. package/types/Type.d.ts +2 -4
  118. package/types/Type.js +3 -3
  119. package/types/Uint8ArrayType.d.ts +0 -1
  120. package/types/Uint8ArrayType.js +1 -4
  121. package/types/index.d.ts +1 -1
  122. package/typings.d.ts +469 -175
  123. package/typings.js +120 -45
  124. package/unit-of-work/ChangeSet.d.ts +4 -6
  125. package/unit-of-work/ChangeSet.js +4 -5
  126. package/unit-of-work/ChangeSetComputer.d.ts +3 -8
  127. package/unit-of-work/ChangeSetComputer.js +44 -21
  128. package/unit-of-work/ChangeSetPersister.d.ts +15 -12
  129. package/unit-of-work/ChangeSetPersister.js +113 -45
  130. package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
  131. package/unit-of-work/CommitOrderCalculator.js +13 -13
  132. package/unit-of-work/IdentityMap.d.ts +12 -0
  133. package/unit-of-work/IdentityMap.js +39 -1
  134. package/unit-of-work/UnitOfWork.d.ts +28 -3
  135. package/unit-of-work/UnitOfWork.js +315 -110
  136. package/utils/AbstractMigrator.d.ts +101 -0
  137. package/utils/AbstractMigrator.js +305 -0
  138. package/utils/AbstractSchemaGenerator.d.ts +5 -5
  139. package/utils/AbstractSchemaGenerator.js +32 -18
  140. package/utils/AsyncContext.d.ts +6 -0
  141. package/utils/AsyncContext.js +42 -0
  142. package/utils/Configuration.d.ts +801 -207
  143. package/utils/Configuration.js +150 -191
  144. package/utils/ConfigurationLoader.d.ts +1 -54
  145. package/utils/ConfigurationLoader.js +1 -352
  146. package/utils/Cursor.d.ts +3 -6
  147. package/utils/Cursor.js +27 -11
  148. package/utils/DataloaderUtils.d.ts +15 -5
  149. package/utils/DataloaderUtils.js +65 -17
  150. package/utils/EntityComparator.d.ts +21 -10
  151. package/utils/EntityComparator.js +243 -106
  152. package/utils/QueryHelper.d.ts +24 -6
  153. package/utils/QueryHelper.js +122 -26
  154. package/utils/RawQueryFragment.d.ts +60 -32
  155. package/utils/RawQueryFragment.js +69 -66
  156. package/utils/RequestContext.js +2 -2
  157. package/utils/TransactionContext.js +2 -2
  158. package/utils/TransactionManager.d.ts +65 -0
  159. package/utils/TransactionManager.js +223 -0
  160. package/utils/Utils.d.ts +15 -122
  161. package/utils/Utils.js +108 -376
  162. package/utils/clone.js +8 -23
  163. package/utils/env-vars.d.ts +7 -0
  164. package/utils/env-vars.js +97 -0
  165. package/utils/fs-utils.d.ts +34 -0
  166. package/utils/fs-utils.js +196 -0
  167. package/utils/index.d.ts +2 -3
  168. package/utils/index.js +2 -3
  169. package/utils/upsert-utils.d.ts +9 -4
  170. package/utils/upsert-utils.js +55 -4
  171. package/decorators/Check.d.ts +0 -3
  172. package/decorators/Check.js +0 -13
  173. package/decorators/CreateRequestContext.d.ts +0 -3
  174. package/decorators/CreateRequestContext.js +0 -32
  175. package/decorators/Embeddable.d.ts +0 -8
  176. package/decorators/Embeddable.js +0 -11
  177. package/decorators/Embedded.d.ts +0 -18
  178. package/decorators/Embedded.js +0 -18
  179. package/decorators/Entity.d.ts +0 -18
  180. package/decorators/Entity.js +0 -13
  181. package/decorators/Enum.d.ts +0 -9
  182. package/decorators/Enum.js +0 -16
  183. package/decorators/Filter.d.ts +0 -2
  184. package/decorators/Filter.js +0 -8
  185. package/decorators/Formula.d.ts +0 -5
  186. package/decorators/Formula.js +0 -15
  187. package/decorators/Indexed.d.ts +0 -17
  188. package/decorators/Indexed.js +0 -20
  189. package/decorators/ManyToMany.d.ts +0 -40
  190. package/decorators/ManyToMany.js +0 -14
  191. package/decorators/ManyToOne.d.ts +0 -30
  192. package/decorators/ManyToOne.js +0 -14
  193. package/decorators/OneToMany.d.ts +0 -28
  194. package/decorators/OneToMany.js +0 -17
  195. package/decorators/OneToOne.d.ts +0 -24
  196. package/decorators/OneToOne.js +0 -7
  197. package/decorators/PrimaryKey.d.ts +0 -9
  198. package/decorators/PrimaryKey.js +0 -20
  199. package/decorators/Property.d.ts +0 -250
  200. package/decorators/Property.js +0 -32
  201. package/decorators/Transactional.d.ts +0 -13
  202. package/decorators/Transactional.js +0 -28
  203. package/decorators/hooks.d.ts +0 -16
  204. package/decorators/hooks.js +0 -47
  205. package/decorators/index.d.ts +0 -17
  206. package/decorators/index.js +0 -17
  207. package/entity/ArrayCollection.d.ts +0 -116
  208. package/entity/ArrayCollection.js +0 -395
  209. package/entity/EntityValidator.d.ts +0 -19
  210. package/entity/EntityValidator.js +0 -150
  211. package/metadata/ReflectMetadataProvider.d.ts +0 -8
  212. package/metadata/ReflectMetadataProvider.js +0 -44
  213. package/utils/resolveContextProvider.d.ts +0 -10
  214. 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,6 +16,8 @@ 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';
19
+ import { applyPopulateHints, getLoadingStrategy } from './entity/utils.js';
20
+ import { TransactionManager } from './utils/TransactionManager.js';
22
21
  /**
23
22
  * The EntityManager is the central access point to ORM functionality. It is a facade to all different ORM subsystems
24
23
  * such as UnitOfWork, Query Language, and Repository API.
@@ -34,10 +33,8 @@ export class EntityManager {
34
33
  _id = EntityManager.counter++;
35
34
  global = false;
36
35
  name;
37
- refLoader = new DataLoader(DataloaderUtils.getRefBatchLoadFn(this));
38
- colLoader = new DataLoader(DataloaderUtils.getColBatchLoadFn(this));
39
- validator;
40
- repositoryMap = {};
36
+ loaders = {};
37
+ repositoryMap = new Map();
41
38
  entityLoader;
42
39
  comparator;
43
40
  entityFactory;
@@ -61,7 +58,6 @@ export class EntityManager {
61
58
  this.eventManager = eventManager;
62
59
  this.entityLoader = new EntityLoader(this);
63
60
  this.name = this.config.get('contextName');
64
- this.validator = new EntityValidator(this.config.get('strict'));
65
61
  this.comparator = this.config.getComparator(this.metadata);
66
62
  this.resultCache = this.config.getResultCacheAdapter();
67
63
  this.disableTransactions = this.config.get('disableTransactions');
@@ -91,13 +87,12 @@ export class EntityManager {
91
87
  * Gets repository for given entity. You can pass either string name or entity class reference.
92
88
  */
93
89
  getRepository(entityName) {
94
- entityName = Utils.className(entityName);
95
- if (!this.repositoryMap[entityName]) {
96
- const meta = this.metadata.get(entityName);
90
+ const meta = this.metadata.get(entityName);
91
+ if (!this.repositoryMap.has(meta)) {
97
92
  const RepositoryClass = this.config.getRepositoryClass(meta.repository);
98
- this.repositoryMap[entityName] = new RepositoryClass(this, entityName);
93
+ this.repositoryMap.set(meta, new RepositoryClass(this, entityName));
99
94
  }
100
- return this.repositoryMap[entityName];
95
+ return this.repositoryMap.get(meta);
101
96
  }
102
97
  /**
103
98
  * Shortcut for `em.getRepository()`.
@@ -105,12 +100,6 @@ export class EntityManager {
105
100
  repo(entityName) {
106
101
  return this.getRepository(entityName);
107
102
  }
108
- /**
109
- * Gets EntityValidator instance
110
- */
111
- getValidator() {
112
- return this.validator;
113
- }
114
103
  /**
115
104
  * Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
116
105
  */
@@ -125,10 +114,15 @@ export class EntityManager {
125
114
  const em = this.getContext();
126
115
  em.prepareOptions(options);
127
116
  await em.tryFlush(entityName, options);
128
- entityName = Utils.className(entityName);
129
117
  where = await em.processWhere(entityName, where, options, 'read');
130
- em.validator.validateParams(where);
131
- 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
+ }
132
126
  options.populate = await em.preparePopulate(entityName, options);
133
127
  const populate = options.populate;
134
128
  const cacheKey = em.cacheKey(entityName, options, 'em.find', where);
@@ -137,19 +131,18 @@ export class EntityManager {
137
131
  await em.entityLoader.populate(entityName, cached.data, populate, {
138
132
  ...options,
139
133
  ...em.getPopulateWhere(where, options),
140
- convertCustomTypes: false,
141
134
  ignoreLazyScalarProperties: true,
142
135
  lookup: false,
143
136
  });
144
137
  return cached.data;
145
138
  }
146
- const meta = this.metadata.get(entityName);
147
139
  options = { ...options };
148
140
  // save the original hint value so we know it was infer/all
149
141
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
150
142
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
151
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
152
- const results = await em.driver.find(entityName, where, { ctx: em.transactionContext, ...options });
143
+ options.populateFilter = await this.getJoinedFilters(meta, options);
144
+ await em.processUnionWhere(entityName, options, 'read');
145
+ const results = await em.driver.find(entityName, where, { ctx: em.transactionContext, em, ...options });
153
146
  if (results.length === 0) {
154
147
  await em.storeCache(options.cache, cached, []);
155
148
  return [];
@@ -168,7 +161,6 @@ export class EntityManager {
168
161
  await em.entityLoader.populate(entityName, unique, populate, {
169
162
  ...options,
170
163
  ...em.getPopulateWhere(where, options),
171
- convertCustomTypes: false,
172
164
  ignoreLazyScalarProperties: true,
173
165
  lookup: false,
174
166
  });
@@ -181,6 +173,60 @@ export class EntityManager {
181
173
  }
182
174
  return unique;
183
175
  }
176
+ /**
177
+ * Finds all entities and returns an async iterable (async generator) that yields results one by one.
178
+ * The results are merged and mapped to entity instances, without adding them to the identity map.
179
+ * You can disable merging by passing the options `{ mergeResults: false }`.
180
+ * With `mergeResults` disabled, to-many collections will contain at most one item, and you will get duplicate
181
+ * root entities when there are multiple items in the populated collection.
182
+ * This is useful for processing large datasets without loading everything into memory at once.
183
+ *
184
+ * ```ts
185
+ * const stream = em.stream(Book, { populate: ['author'] });
186
+ *
187
+ * for await (const book of stream) {
188
+ * // book is an instance of Book entity
189
+ * console.log(book.title, book.author.name);
190
+ * }
191
+ * ```
192
+ */
193
+ async *stream(entityName, options = {}) {
194
+ const em = this.getContext();
195
+ em.prepareOptions(options);
196
+ options.strategy = 'joined';
197
+ await em.tryFlush(entityName, options);
198
+ const where = await em.processWhere(entityName, options.where ?? {}, options, 'read');
199
+ validateParams(where);
200
+ options.orderBy = options.orderBy || {};
201
+ options.populate = await em.preparePopulate(entityName, options);
202
+ const meta = this.metadata.get(entityName);
203
+ options = { ...options };
204
+ // save the original hint value so we know it was infer/all
205
+ options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
206
+ options.populateWhere = this.createPopulateWhere({ ...where }, options);
207
+ options.populateFilter = await this.getJoinedFilters(meta, options);
208
+ const stream = em.driver.stream(entityName, where, {
209
+ ctx: em.transactionContext,
210
+ mapResults: false,
211
+ ...options,
212
+ });
213
+ for await (const data of stream) {
214
+ const fork = em.fork();
215
+ const entity = fork.entityFactory.create(entityName, data, {
216
+ refresh: options.refresh,
217
+ schema: options.schema,
218
+ convertCustomTypes: true,
219
+ });
220
+ helper(entity).setSerializationContext({
221
+ populate: options.populate,
222
+ fields: options.fields,
223
+ exclude: options.exclude,
224
+ });
225
+ await fork.unitOfWork.dispatchOnLoadEvent();
226
+ fork.clear();
227
+ yield entity;
228
+ }
229
+ }
184
230
  /**
185
231
  * Finds all entities of given type, optionally matching the `where` condition provided in the `options` parameter.
186
232
  */
@@ -194,7 +240,7 @@ export class EntityManager {
194
240
  if (options.populateWhere === PopulateHint.ALL) {
195
241
  return { where: {}, populateWhere: options.populateWhere };
196
242
  }
197
- /* v8 ignore next 3 */
243
+ /* v8 ignore next */
198
244
  if (options.populateWhere === PopulateHint.INFER) {
199
245
  return { where, populateWhere: options.populateWhere };
200
246
  }
@@ -203,12 +249,12 @@ export class EntityManager {
203
249
  /**
204
250
  * Registers global filter to this entity manager. Global filters are enabled by default (unless disabled via last parameter).
205
251
  */
206
- addFilter(name, cond, entityName, enabled = true) {
207
- const options = { name, cond, default: enabled };
208
- if (entityName) {
209
- options.entity = Utils.asArray(entityName).map(n => Utils.className(n));
252
+ addFilter(options) {
253
+ if (options.entity) {
254
+ options.entity = Utils.asArray(options.entity).map(n => Utils.className(n));
210
255
  }
211
- this.getContext(false).filters[name] = options;
256
+ options.default ??= true;
257
+ this.getContext(false).filters[options.name] = options;
212
258
  }
213
259
  /**
214
260
  * Sets filter parameter values globally inside context defined by this entity manager.
@@ -232,8 +278,8 @@ export class EntityManager {
232
278
  /**
233
279
  * Gets logger context for this entity manager.
234
280
  */
235
- getLoggerContext() {
236
- const em = this.getContext();
281
+ getLoggerContext(options) {
282
+ const em = options?.disableContextResolution ? this : this.getContext();
237
283
  em.loggerContext ??= {};
238
284
  return em.loggerContext;
239
285
  }
@@ -253,21 +299,29 @@ export class EntityManager {
253
299
  where = this.applyDiscriminatorCondition(entityName, where);
254
300
  return where;
255
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
+ }
256
310
  // this method only handles the problem for mongo driver, SQL drivers have their implementation inside QueryBuilder
257
311
  applyDiscriminatorCondition(entityName, where) {
258
312
  const meta = this.metadata.find(entityName);
259
- if (!meta?.discriminatorValue) {
313
+ if (meta?.root.inheritanceType !== 'sti' || !meta?.discriminatorValue) {
260
314
  return where;
261
315
  }
262
- const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.find(cls));
316
+ const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.get(cls));
263
317
  const children = [];
264
318
  const lookUpChildren = (ret, type) => {
265
319
  const children = types.filter(meta2 => meta2.extends === type);
266
- children.forEach(m => lookUpChildren(ret, m.className));
320
+ children.forEach(m => lookUpChildren(ret, m.class));
267
321
  ret.push(...children.filter(c => c.discriminatorValue));
268
322
  return children;
269
323
  };
270
- lookUpChildren(children, meta.className);
324
+ lookUpChildren(children, meta.class);
271
325
  /* v8 ignore next */
272
326
  where[meta.root.discriminatorColumn] = children.length > 0 ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] } : meta.discriminatorValue;
273
327
  return where;
@@ -283,72 +337,104 @@ export class EntityManager {
283
337
  }
284
338
  return ret;
285
339
  }
286
- async getJoinedFilters(meta, cond, options) {
340
+ async getJoinedFilters(meta, options) {
341
+ // If user provided populateFilter, merge it with computed filters
342
+ const userFilter = options.populateFilter;
343
+ if (!this.config.get('filtersOnRelations') || !options.populate) {
344
+ return userFilter;
345
+ }
287
346
  const ret = {};
288
- if (options.populate) {
289
- for (const hint of options.populate) {
290
- const field = hint.field.split(':')[0];
291
- const prop = meta.properties[field];
292
- const joined = (prop.strategy || options.strategy || hint.strategy || this.config.get('loadStrategy')) === LoadStrategy.JOINED && prop.kind !== ReferenceKind.SCALAR;
293
- if (!joined && !hint.filter) {
294
- continue;
295
- }
296
- const where = await this.applyFilters(prop.type, {}, options.filters ?? {}, 'read', { ...options, populate: hint.children });
297
- const where2 = await this.getJoinedFilters(prop.targetMeta, {}, { ...options, populate: hint.children, populateWhere: PopulateHint.ALL });
298
- if (Utils.hasObjectKeys(where)) {
299
- ret[field] = ret[field] ? { $and: [where, ret[field]] } : where;
347
+ for (const hint of options.populate) {
348
+ const field = hint.field.split(':')[0];
349
+ const prop = meta.properties[field];
350
+ const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
351
+ const joined = strategy === LoadStrategy.JOINED && prop.kind !== ReferenceKind.SCALAR;
352
+ if (!joined && !hint.filter) {
353
+ continue;
354
+ }
355
+ const filters = QueryHelper.mergePropertyFilters(prop.filters, options.filters);
356
+ const where = await this.applyFilters(prop.targetMeta.class, {}, filters, 'read', {
357
+ ...options,
358
+ populate: hint.children,
359
+ });
360
+ const where2 = await this.getJoinedFilters(prop.targetMeta, {
361
+ ...options,
362
+ filters,
363
+ populate: hint.children,
364
+ populateWhere: PopulateHint.ALL,
365
+ });
366
+ if (Utils.hasObjectKeys(where)) {
367
+ ret[field] = ret[field] ? { $and: [where, ret[field]] } : where;
368
+ }
369
+ if (where2 && Utils.hasObjectKeys(where2)) {
370
+ if (ret[field]) {
371
+ Utils.merge(ret[field], where2);
300
372
  }
301
- if (Utils.hasObjectKeys(where2)) {
302
- if (ret[field]) {
303
- Utils.merge(ret[field], where2);
304
- }
305
- else {
306
- ret[field] = where2;
307
- }
373
+ else {
374
+ ret[field] = where2;
308
375
  }
309
376
  }
310
377
  }
311
- return ret;
378
+ // Merge user-provided populateFilter with computed filters
379
+ if (userFilter) {
380
+ Utils.merge(ret, userFilter);
381
+ }
382
+ return Utils.hasObjectKeys(ret) ? ret : undefined;
312
383
  }
313
384
  /**
314
385
  * 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.
315
386
  */
316
- async autoJoinRefsForFilters(meta, options) {
317
- if (!meta || !this.config.get('autoJoinRefsForFilters')) {
387
+ async autoJoinRefsForFilters(meta, options, parent) {
388
+ if (!meta || !this.config.get('autoJoinRefsForFilters') || options.filters === false) {
318
389
  return;
319
390
  }
320
- const props = meta.relations.filter(prop => {
321
- return !prop.object && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)
322
- && ((options.fields?.length ?? 0) === 0 || options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)));
323
- });
324
391
  const ret = options.populate;
325
- for (const prop of props) {
326
- const cond = await this.applyFilters(prop.type, {}, options.filters ?? {}, 'read', options);
392
+ for (const prop of meta.relations) {
393
+ if (prop.object
394
+ || ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)
395
+ || !((options.fields?.length ?? 0) === 0 || options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
396
+ || (parent?.class === prop.targetMeta.root.class && parent.propName === prop.inversedBy)) {
397
+ continue;
398
+ }
399
+ options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
400
+ const cond = await this.applyFilters(prop.targetMeta.class, {}, options.filters, 'read', options);
327
401
  if (!Utils.isEmpty(cond)) {
328
402
  const populated = options.populate.filter(({ field }) => field.split(':')[0] === prop.name);
329
- if (populated.length > 0) {
330
- populated.forEach(hint => hint.filter = true);
403
+ let found = false;
404
+ for (const hint of populated) {
405
+ if (!hint.all) {
406
+ hint.filter = true;
407
+ }
408
+ const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
409
+ if (hint.field === `${prop.name}:ref` || (hint.filter && strategy === LoadStrategy.JOINED)) {
410
+ found = true;
411
+ }
331
412
  }
332
- else {
413
+ if (!found) {
333
414
  ret.push({ field: `${prop.name}:ref`, strategy: LoadStrategy.JOINED, filter: true });
334
415
  }
335
416
  }
336
417
  }
418
+ for (const hint of ret) {
419
+ const [field, ref] = hint.field.split(':');
420
+ const prop = meta?.properties[field];
421
+ if (prop && !ref) {
422
+ hint.children ??= [];
423
+ await this.autoJoinRefsForFilters(prop.targetMeta, { ...options, populate: hint.children }, { class: meta.root.class, propName: prop.name });
424
+ }
425
+ }
337
426
  }
338
427
  /**
339
428
  * @internal
340
429
  */
341
430
  async applyFilters(entityName, where, options, type, findOptions) {
342
- const meta = this.metadata.find(entityName);
431
+ const meta = this.metadata.get(entityName);
343
432
  const filters = [];
344
433
  const ret = [];
345
- if (!meta) {
346
- return where;
347
- }
348
434
  const active = new Set();
349
435
  const push = (source) => {
350
436
  const activeFilters = QueryHelper
351
- .getActiveFilters(entityName, options, source)
437
+ .getActiveFilters(meta, options, source)
352
438
  .filter(f => !active.has(f.name));
353
439
  filters.push(...activeFilters);
354
440
  activeFilters.forEach(f => active.add(f.name));
@@ -363,24 +449,28 @@ export class EntityManager {
363
449
  let cond;
364
450
  if (filter.cond instanceof Function) {
365
451
  // @ts-ignore
366
- const args = Utils.isPlainObject(options[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
452
+ const args = Utils.isPlainObject(options?.[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
367
453
  if (!args && filter.cond.length > 0 && filter.args !== false) {
368
454
  throw new Error(`No arguments provided for filter '${filter.name}'`);
369
455
  }
370
- cond = await filter.cond(args, type, this, findOptions);
456
+ cond = await filter.cond(args, type, this, findOptions, Utils.className(entityName));
371
457
  }
372
458
  else {
373
459
  cond = filter.cond;
374
460
  }
375
- ret.push(QueryHelper.processWhere({
461
+ cond = QueryHelper.processWhere({
376
462
  where: cond,
377
463
  entityName,
378
464
  metadata: this.metadata,
379
465
  platform: this.driver.getPlatform(),
380
466
  aliased: type === 'read',
381
- }));
467
+ });
468
+ if (filter.strict) {
469
+ Object.defineProperty(cond, '__strict', { value: filter.strict, enumerable: false });
470
+ }
471
+ ret.push(cond);
382
472
  }
383
- const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c));
473
+ const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c) || Raw.hasObjectFragments(c));
384
474
  return conds.length > 1 ? { $and: conds } : conds[0];
385
475
  }
386
476
  /**
@@ -391,12 +481,10 @@ export class EntityManager {
391
481
  const em = this.getContext(false);
392
482
  await em.tryFlush(entityName, options);
393
483
  options.flushMode = 'commit'; // do not try to auto flush again
394
- return RawQueryFragment.run(async () => {
395
- return Promise.all([
396
- em.find(entityName, where, options),
397
- em.count(entityName, where, options),
398
- ]);
399
- });
484
+ return Promise.all([
485
+ em.find(entityName, where, options),
486
+ em.count(entityName, where, options),
487
+ ]);
400
488
  }
401
489
  /**
402
490
  * Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as {@apilink Cursor} object.
@@ -412,27 +500,31 @@ export class EntityManager {
412
500
  * - POJO/entity instance
413
501
  *
414
502
  * ```ts
415
- * const currentCursor = await em.findByCursor(User, {}, {
503
+ * const currentCursor = await em.findByCursor(User, {
416
504
  * first: 10,
417
505
  * after: previousCursor, // cursor instance
418
506
  * orderBy: { id: 'desc' },
419
507
  * });
420
508
  *
421
509
  * // to fetch next page
422
- * const nextCursor = await em.findByCursor(User, {}, {
510
+ * const nextCursor = await em.findByCursor(User, {
423
511
  * first: 10,
424
512
  * after: currentCursor.endCursor, // opaque string
425
513
  * orderBy: { id: 'desc' },
426
514
  * });
427
515
  *
428
516
  * // to fetch next page
429
- * const nextCursor2 = await em.findByCursor(User, {}, {
517
+ * const nextCursor2 = await em.findByCursor(User, {
430
518
  * first: 10,
431
519
  * after: { id: lastSeenId }, // entity-like POJO
432
520
  * orderBy: { id: 'desc' },
433
521
  * });
434
522
  * ```
435
523
  *
524
+ * The options also support an `includeCount` (true by default) option. If set to false, the `totalCount` is not
525
+ * returned as part of the cursor. This is useful for performance reason, when you don't care about the total number
526
+ * of pages.
527
+ *
436
528
  * The `Cursor` object provides the following interface:
437
529
  *
438
530
  * ```ts
@@ -442,7 +534,7 @@ export class EntityManager {
442
534
  * User { ... },
443
535
  * User { ... },
444
536
  * ],
445
- * totalCount: 50,
537
+ * totalCount: 50, // not included if `includeCount: false`
446
538
  * startCursor: 'WzRd',
447
539
  * endCursor: 'WzZd',
448
540
  * hasPrevPage: true,
@@ -450,14 +542,16 @@ export class EntityManager {
450
542
  * }
451
543
  * ```
452
544
  */
453
- async findByCursor(entityName, where, options) {
545
+ async findByCursor(entityName, options) {
454
546
  const em = this.getContext(false);
455
- entityName = Utils.className(entityName);
456
547
  options.overfetch ??= true;
457
- if (Utils.isEmpty(options.orderBy)) {
548
+ options.where ??= {};
549
+ if (Utils.isEmpty(options.orderBy) && !Raw.hasObjectFragments(options.orderBy)) {
458
550
  throw new Error('Explicit `orderBy` option required');
459
551
  }
460
- const [entities, count] = await em.findAndCount(entityName, where, options);
552
+ const [entities, count] = options.includeCount !== false
553
+ ? await em.findAndCount(entityName, options.where, options)
554
+ : [await em.find(entityName, options.where, options)];
461
555
  return new Cursor(entities, count, options, this.metadata.get(entityName));
462
556
  }
463
557
  /**
@@ -469,9 +563,9 @@ export class EntityManager {
469
563
  const ret = await this.refresh(entity, options);
470
564
  if (!ret) {
471
565
  options.failHandler ??= this.config.get('findOneOrFailHandler');
472
- const entityName = entity.constructor.name;
473
- const where = helper(entity).getPrimaryKey();
474
- throw options.failHandler(entityName, where);
566
+ const wrapped = helper(entity);
567
+ const where = wrapped.getPrimaryKey();
568
+ throw options.failHandler(wrapped.__meta.className, where);
475
569
  }
476
570
  return ret;
477
571
  }
@@ -482,19 +576,31 @@ export class EntityManager {
482
576
  */
483
577
  async refresh(entity, options = {}) {
484
578
  const fork = this.fork({ keepTransactionContext: true });
485
- const entityName = entity.constructor.name;
486
- const reloaded = await fork.findOne(entityName, entity, {
487
- schema: helper(entity).__schema,
579
+ const wrapped = helper(entity);
580
+ const reloaded = await fork.findOne(wrapped.__meta.class, entity, {
581
+ schema: wrapped.__schema,
488
582
  ...options,
489
583
  flushMode: FlushMode.COMMIT,
490
584
  });
491
- if (reloaded) {
492
- this.config.getHydrator(this.metadata).hydrate(entity, helper(entity).__meta, helper(reloaded).toPOJO(), this.getEntityFactory(), 'full');
585
+ const em = this.getContext();
586
+ if (!reloaded) {
587
+ em.unitOfWork.unsetIdentity(entity);
588
+ return null;
493
589
  }
494
- else {
495
- this.getUnitOfWork().unsetIdentity(entity);
590
+ let found = false;
591
+ for (const e of fork.unitOfWork.getIdentityMap()) {
592
+ const ref = em.getReference(e.constructor, helper(e).getPrimaryKey());
593
+ const data = helper(e).serialize({ ignoreSerializers: true, includeHidden: true, convertCustomTypes: false });
594
+ em.config.getHydrator(this.metadata).hydrate(ref, helper(ref).__meta, data, em.entityFactory, 'full', false, false);
595
+ Utils.merge(helper(ref).__originalEntityData, this.comparator.prepareEntity(e));
596
+ found ||= ref === entity;
597
+ }
598
+ if (!found) {
599
+ const data = helper(reloaded).serialize({ ignoreSerializers: true, includeHidden: true, convertCustomTypes: true });
600
+ em.config.getHydrator(this.metadata).hydrate(entity, wrapped.__meta, data, em.entityFactory, 'full', false, true);
601
+ Utils.merge(wrapped.__originalEntityData, this.comparator.prepareEntity(reloaded));
496
602
  }
497
- return reloaded ? entity : reloaded;
603
+ return entity;
498
604
  }
499
605
  /**
500
606
  * Finds first entity matching your `where` query.
@@ -508,7 +614,6 @@ export class EntityManager {
508
614
  return ret;
509
615
  }
510
616
  const em = this.getContext();
511
- entityName = Utils.className(entityName);
512
617
  em.prepareOptions(options);
513
618
  let entity = em.unitOfWork.tryGetById(entityName, where, options.schema);
514
619
  // query for a not managed entity which is already in the identity map as it
@@ -520,33 +625,36 @@ export class EntityManager {
520
625
  await em.tryFlush(entityName, options);
521
626
  const meta = em.metadata.get(entityName);
522
627
  where = await em.processWhere(entityName, where, options, 'read');
523
- em.validator.validateEmptyWhere(where);
628
+ validateEmptyWhere(where);
524
629
  em.checkLockRequirements(options.lockMode, meta);
525
- const isOptimisticLocking = !Utils.isDefined(options.lockMode) || options.lockMode === LockMode.OPTIMISTIC;
630
+ const isOptimisticLocking = options.lockMode == null || options.lockMode === LockMode.OPTIMISTIC;
526
631
  if (entity && !em.shouldRefresh(meta, entity, options) && isOptimisticLocking) {
527
632
  return em.lockAndPopulate(meta, entity, where, options);
528
633
  }
529
- em.validator.validateParams(where);
634
+ validateParams(where);
530
635
  options.populate = await em.preparePopulate(entityName, options);
531
636
  const cacheKey = em.cacheKey(entityName, options, 'em.findOne', where);
532
637
  const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true);
533
- if (cached?.data) {
534
- await em.entityLoader.populate(entityName, [cached.data], options.populate, {
535
- ...options,
536
- ...em.getPopulateWhere(where, options),
537
- convertCustomTypes: false,
538
- ignoreLazyScalarProperties: true,
539
- lookup: false,
540
- });
638
+ if (cached?.data !== undefined) {
639
+ if (cached.data) {
640
+ await em.entityLoader.populate(entityName, [cached.data], options.populate, {
641
+ ...options,
642
+ ...em.getPopulateWhere(where, options),
643
+ ignoreLazyScalarProperties: true,
644
+ lookup: false,
645
+ });
646
+ }
541
647
  return cached.data;
542
648
  }
543
649
  options = { ...options };
544
650
  // save the original hint value so we know it was infer/all
545
651
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
546
652
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
547
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
653
+ options.populateFilter = await this.getJoinedFilters(meta, options);
654
+ await em.processUnionWhere(entityName, options, 'read');
548
655
  const data = await em.driver.findOne(entityName, where, {
549
656
  ctx: em.transactionContext,
657
+ em,
550
658
  ...options,
551
659
  });
552
660
  if (!data) {
@@ -584,10 +692,10 @@ export class EntityManager {
584
692
  if (!entity || isStrictViolation) {
585
693
  const key = options.strict ? 'findExactlyOneOrFailHandler' : 'findOneOrFailHandler';
586
694
  options.failHandler ??= this.config.get(key);
587
- entityName = Utils.className(entityName);
695
+ const name = Utils.className(entityName);
588
696
  /* v8 ignore next */
589
697
  where = Utils.isEntity(where) ? helper(where).getPrimaryKey() : where;
590
- throw options.failHandler(entityName, where);
698
+ throw options.failHandler(name, where);
591
699
  }
592
700
  return entity;
593
701
  }
@@ -627,11 +735,11 @@ export class EntityManager {
627
735
  let where;
628
736
  let entity = null;
629
737
  if (data === undefined) {
630
- entityName = entityNameOrEntity.constructor.name;
738
+ entityName = entityNameOrEntity.constructor;
631
739
  data = entityNameOrEntity;
632
740
  }
633
741
  else {
634
- entityName = Utils.className(entityNameOrEntity);
742
+ entityName = entityNameOrEntity;
635
743
  }
636
744
  const meta = this.metadata.get(entityName);
637
745
  const convertCustomTypes = !Utils.isEntity(data);
@@ -654,26 +762,9 @@ export class EntityManager {
654
762
  }
655
763
  }
656
764
  }
657
- const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
658
- const propIndex = !isRaw(unique) && unique.findIndex(p => data[p] != null);
659
- if (options.onConflictFields || where == null) {
660
- if (propIndex !== false && propIndex >= 0) {
661
- where = { [unique[propIndex]]: data[unique[propIndex]] };
662
- }
663
- else if (meta.uniques.length > 0) {
664
- for (const u of meta.uniques) {
665
- if (Utils.asArray(u.properties).every(p => data[p] != null)) {
666
- where = Utils.asArray(u.properties).reduce((o, key) => {
667
- o[key] = data[key];
668
- return o;
669
- }, {});
670
- break;
671
- }
672
- }
673
- }
674
- }
765
+ where = getWhereCondition(meta, options.onConflictFields, data, where).where;
675
766
  data = QueryHelper.processObjectParams(data);
676
- em.validator.validateParams(data, 'insert data');
767
+ validateParams(data, 'insert data');
677
768
  if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
678
769
  await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity: data, em, meta }, meta);
679
770
  }
@@ -711,13 +802,14 @@ export class EntityManager {
711
802
  where[meta.primaryKeys[0]] = ret.insertId;
712
803
  }
713
804
  }
714
- const data2 = await this.driver.findOne(meta.className, where, {
805
+ const data2 = await this.driver.findOne(meta.class, where, {
715
806
  fields: returning,
716
807
  ctx: em.transactionContext,
717
808
  convertCustomTypes: true,
718
809
  connectionType: 'write',
810
+ schema: options.schema,
719
811
  });
720
- em.getHydrator().hydrate(entity, meta, data2, em.entityFactory, 'full');
812
+ em.getHydrator().hydrate(entity, meta, data2, em.entityFactory, 'full', false, true);
721
813
  }
722
814
  // recompute the data as there might be some values missing (e.g. those with db column defaults)
723
815
  const snapshot = this.comparator.prepareEntity(entity);
@@ -765,11 +857,11 @@ export class EntityManager {
765
857
  let entityName;
766
858
  let propIndex;
767
859
  if (data === undefined) {
768
- entityName = entityNameOrEntity[0].constructor.name;
860
+ entityName = entityNameOrEntity[0].constructor;
769
861
  data = entityNameOrEntity;
770
862
  }
771
863
  else {
772
- entityName = Utils.className(entityNameOrEntity);
864
+ entityName = entityNameOrEntity;
773
865
  }
774
866
  const batchSize = options.batchSize ?? this.config.get('batchSize');
775
867
  if (data.length > batchSize) {
@@ -813,32 +905,18 @@ export class EntityManager {
813
905
  }
814
906
  }
815
907
  }
816
- const unique = meta.props.filter(p => p.unique).map(p => p.name);
817
- propIndex = unique.findIndex(p => row[p] != null);
818
- if (options.onConflictFields || where == null) {
819
- if (propIndex >= 0) {
820
- where = { [unique[propIndex]]: row[unique[propIndex]] };
821
- }
822
- else if (meta.uniques.length > 0) {
823
- for (const u of meta.uniques) {
824
- if (Utils.asArray(u.properties).every(p => row[p] != null)) {
825
- where = Utils.asArray(u.properties).reduce((o, key) => {
826
- o[key] = row[key];
827
- return o;
828
- }, {});
829
- break;
830
- }
831
- }
832
- }
833
- }
834
- row = QueryHelper.processObjectParams(row);
908
+ const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
909
+ propIndex = !isRaw(unique) && unique.findIndex(p => data[p] ?? data[p.substring(0, p.indexOf('.'))] != null);
910
+ const tmp = getWhereCondition(meta, options.onConflictFields, row, where);
911
+ propIndex = tmp.propIndex;
835
912
  where = QueryHelper.processWhere({
836
- where,
913
+ where: tmp.where,
837
914
  entityName,
838
915
  metadata: this.metadata,
839
916
  platform: this.getPlatform(),
840
917
  });
841
- em.validator.validateParams(row, 'insert data');
918
+ row = QueryHelper.processObjectParams(row);
919
+ validateParams(row, 'insert data');
842
920
  allData.push(row);
843
921
  allWhere.push(where);
844
922
  }
@@ -879,7 +957,7 @@ export class EntityManager {
879
957
  const reloadFields = returning.length > 0 && !(this.getPlatform().usesReturningStatement() && res.rows?.length);
880
958
  if (options.onConflictAction === 'ignore' || (!res.rows?.length && loadPK.size > 0) || reloadFields) {
881
959
  const unique = meta.hydrateProps.filter(p => !p.lazy).map(p => p.name);
882
- const add = new Set(propIndex >= 0 ? [unique[propIndex]] : []);
960
+ const add = new Set(propIndex !== false && propIndex >= 0 ? [unique[propIndex]] : []);
883
961
  for (const cond of loadPK.values()) {
884
962
  Utils.keys(cond).forEach(key => add.add(key));
885
963
  }
@@ -891,11 +969,12 @@ export class EntityManager {
891
969
  where.$or[idx][prop] = item[prop];
892
970
  });
893
971
  });
894
- const data2 = await this.driver.find(meta.className, where, {
972
+ const data2 = await this.driver.find(meta.class, where, {
895
973
  fields: returning.concat(...add).concat(...(Array.isArray(uniqueFields) ? uniqueFields : [])),
896
974
  ctx: em.transactionContext,
897
975
  convertCustomTypes: true,
898
976
  connectionType: 'write',
977
+ schema: options.schema,
899
978
  });
900
979
  for (const [entity, cond] of loadPK.entries()) {
901
980
  const row = data2.find(row => {
@@ -907,11 +986,11 @@ export class EntityManager {
907
986
  });
908
987
  return this.comparator.matching(entityName, cond, tmp);
909
988
  });
910
- /* v8 ignore next 3 */
989
+ /* v8 ignore next */
911
990
  if (!row) {
912
991
  throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
913
992
  }
914
- em.getHydrator().hydrate(entity, meta, row, em.entityFactory, 'full');
993
+ em.getHydrator().hydrate(entity, meta, row, em.entityFactory, 'full', false, true);
915
994
  }
916
995
  if (loadPK.size !== data2.length && Array.isArray(uniqueFields)) {
917
996
  for (let i = 0; i < allData.length; i++) {
@@ -930,7 +1009,7 @@ export class EntityManager {
930
1009
  }, {});
931
1010
  return this.comparator.matching(entityName, cond, pk);
932
1011
  });
933
- /* v8 ignore next 3 */
1012
+ /* v8 ignore next */
934
1013
  if (!row) {
935
1014
  throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
936
1015
  }
@@ -952,45 +1031,37 @@ export class EntityManager {
952
1031
  }
953
1032
  /**
954
1033
  * Runs your callback wrapped inside a database transaction.
1034
+ *
1035
+ * If a transaction is already active, a new savepoint (nested transaction) will be created by default. This behavior
1036
+ * can be controlled via the `propagation` option. Use the provided EntityManager instance for all operations that
1037
+ * should be part of the transaction. You can safely use a global EntityManager instance from a DI container, as this
1038
+ * method automatically creates an async context for the transaction.
1039
+ *
1040
+ * **Concurrency note:** When running multiple transactions concurrently (e.g. in parallel requests or jobs), use the
1041
+ * `clear: true` option. This ensures the callback runs in a clear fork of the EntityManager, providing full isolation
1042
+ * between concurrent transactional handlers. Using `clear: true` is an alternative to forking explicitly and calling
1043
+ * the method on the new fork – it already provides the necessary isolation for safe concurrent usage.
1044
+ *
1045
+ * **Propagation note:** Changes made within a transaction (whether top-level or nested) are always propagated to the
1046
+ * parent context, unless the parent context is a global one. If you want to avoid that, fork the EntityManager first
1047
+ * and then call this method on the fork.
1048
+ *
1049
+ * **Example:**
1050
+ * ```ts
1051
+ * await em.transactional(async (em) => {
1052
+ * const author = new Author('Jon');
1053
+ * em.persist(author);
1054
+ * // flush is called automatically at the end of the callback
1055
+ * });
1056
+ * ```
955
1057
  */
956
1058
  async transactional(cb, options = {}) {
957
1059
  const em = this.getContext(false);
958
1060
  if (this.disableTransactions || em.disableTransactions) {
959
1061
  return cb(em);
960
1062
  }
961
- const fork = em.fork({
962
- clear: options.clear ?? false, // state will be merged once resolves
963
- flushMode: options.flushMode,
964
- cloneEventManager: true,
965
- disableTransactions: options.ignoreNestedTransactions,
966
- loggerContext: options.loggerContext,
967
- });
968
- options.ctx ??= em.transactionContext;
969
- const propagateToUpperContext = !em.global || this.config.get('allowGlobalContext');
970
- return TransactionContext.create(fork, async () => {
971
- return fork.getConnection().transactional(async (trx) => {
972
- fork.transactionContext = trx;
973
- if (propagateToUpperContext) {
974
- fork.eventManager.registerSubscriber({
975
- afterFlush(args) {
976
- args.uow.getChangeSets()
977
- .filter(cs => [ChangeSetType.DELETE, ChangeSetType.DELETE_EARLY].includes(cs.type))
978
- .forEach(cs => em.unitOfWork.unsetIdentity(cs.entity));
979
- },
980
- });
981
- }
982
- const ret = await cb(fork);
983
- await fork.flush();
984
- if (propagateToUpperContext) {
985
- // ensure all entities from inner context are merged to the upper one
986
- for (const entity of fork.unitOfWork.getIdentityMap()) {
987
- em.unitOfWork.register(entity);
988
- entity.__helper.__em = em;
989
- }
990
- }
991
- return ret;
992
- }, { ...options, eventBroadcaster: new TransactionEventBroadcaster(fork, { topLevelTransaction: !options.ctx }) });
993
- });
1063
+ const manager = new TransactionManager(this);
1064
+ return manager.handle(cb, options);
994
1065
  }
995
1066
  /**
996
1067
  * Starts new transaction bound to this EntityManager. Use `ctx` parameter to provide the parent when nesting transactions.
@@ -1051,11 +1122,11 @@ export class EntityManager {
1051
1122
  em.prepareOptions(options);
1052
1123
  let entityName;
1053
1124
  if (data === undefined) {
1054
- entityName = entityNameOrEntity.constructor.name;
1125
+ entityName = entityNameOrEntity.constructor;
1055
1126
  data = entityNameOrEntity;
1056
1127
  }
1057
1128
  else {
1058
- entityName = Utils.className(entityNameOrEntity);
1129
+ entityName = entityNameOrEntity;
1059
1130
  }
1060
1131
  if (Utils.isEntity(data)) {
1061
1132
  if (options.schema && helper(data).getSchema() == null) {
@@ -1074,7 +1145,7 @@ export class EntityManager {
1074
1145
  return cs.getPrimaryKey();
1075
1146
  }
1076
1147
  data = QueryHelper.processObjectParams(data);
1077
- em.validator.validateParams(data, 'insert data');
1148
+ validateParams(data, 'insert data');
1078
1149
  const res = await em.driver.nativeInsert(entityName, data, { ctx: em.transactionContext, ...options });
1079
1150
  return res.insertId;
1080
1151
  }
@@ -1086,11 +1157,11 @@ export class EntityManager {
1086
1157
  em.prepareOptions(options);
1087
1158
  let entityName;
1088
1159
  if (data === undefined) {
1089
- entityName = entityNameOrEntities[0].constructor.name;
1160
+ entityName = entityNameOrEntities[0].constructor;
1090
1161
  data = entityNameOrEntities;
1091
1162
  }
1092
1163
  else {
1093
- entityName = Utils.className(entityNameOrEntities);
1164
+ entityName = entityNameOrEntities;
1094
1165
  }
1095
1166
  if (data.length === 0) {
1096
1167
  return [];
@@ -1114,7 +1185,7 @@ export class EntityManager {
1114
1185
  return css.map(cs => cs.getPrimaryKey());
1115
1186
  }
1116
1187
  data = data.map(row => QueryHelper.processObjectParams(row));
1117
- data.forEach(row => em.validator.validateParams(row, 'insert data'));
1188
+ data.forEach(row => validateParams(row, 'insert data'));
1118
1189
  const res = await em.driver.nativeInsertMany(entityName, data, { ctx: em.transactionContext, ...options });
1119
1190
  if (res.insertedIds) {
1120
1191
  return res.insertedIds;
@@ -1127,12 +1198,12 @@ export class EntityManager {
1127
1198
  async nativeUpdate(entityName, where, data, options = {}) {
1128
1199
  const em = this.getContext(false);
1129
1200
  em.prepareOptions(options);
1130
- entityName = Utils.className(entityName);
1201
+ await em.processUnionWhere(entityName, options, 'update');
1131
1202
  data = QueryHelper.processObjectParams(data);
1132
1203
  where = await em.processWhere(entityName, where, { ...options, convertCustomTypes: false }, 'update');
1133
- em.validator.validateParams(data, 'update data');
1134
- em.validator.validateParams(where, 'update condition');
1135
- const res = await em.driver.nativeUpdate(entityName, where, data, { ctx: em.transactionContext, ...options });
1204
+ validateParams(data, 'update data');
1205
+ validateParams(where, 'update condition');
1206
+ const res = await em.driver.nativeUpdate(entityName, where, data, { ctx: em.transactionContext, em, ...options });
1136
1207
  return res.affectedRows;
1137
1208
  }
1138
1209
  /**
@@ -1141,28 +1212,29 @@ export class EntityManager {
1141
1212
  async nativeDelete(entityName, where, options = {}) {
1142
1213
  const em = this.getContext(false);
1143
1214
  em.prepareOptions(options);
1144
- entityName = Utils.className(entityName);
1215
+ await em.processUnionWhere(entityName, options, 'delete');
1145
1216
  where = await em.processWhere(entityName, where, options, 'delete');
1146
- em.validator.validateParams(where, 'delete condition');
1147
- const res = await em.driver.nativeDelete(entityName, where, { ctx: em.transactionContext, ...options });
1217
+ validateParams(where, 'delete condition');
1218
+ const res = await em.driver.nativeDelete(entityName, where, { ctx: em.transactionContext, em, ...options });
1148
1219
  return res.affectedRows;
1149
1220
  }
1150
1221
  /**
1151
1222
  * Maps raw database result to an entity and merges it to this EntityManager.
1152
1223
  */
1153
1224
  map(entityName, result, options = {}) {
1154
- entityName = Utils.className(entityName);
1155
1225
  const meta = this.metadata.get(entityName);
1156
1226
  const data = this.driver.mapResult(result, meta);
1157
- Object.keys(data).forEach(k => {
1227
+ for (const k of Object.keys(data)) {
1158
1228
  const prop = meta.properties[k];
1159
- if (prop && prop.kind === ReferenceKind.SCALAR && SCALAR_TYPES.includes(prop.runtimeType) && !prop.customType && (prop.setter || !prop.getter)) {
1160
- data[k] = this.validator.validateProperty(prop, data[k], data);
1229
+ if (prop?.kind === ReferenceKind.SCALAR && SCALAR_TYPES.has(prop.runtimeType) && !prop.customType && (prop.setter || !prop.getter)) {
1230
+ validateProperty(prop, data[k], data);
1161
1231
  }
1162
- });
1232
+ }
1163
1233
  return this.merge(entityName, data, {
1164
1234
  convertCustomTypes: true,
1165
- refresh: true, ...options,
1235
+ refresh: true,
1236
+ validate: false,
1237
+ ...options,
1166
1238
  });
1167
1239
  }
1168
1240
  /**
@@ -1170,22 +1242,22 @@ export class EntityManager {
1170
1242
  * via second parameter. By default, it will return already loaded entities without modifying them.
1171
1243
  */
1172
1244
  merge(entityName, data, options = {}) {
1173
- const em = this.getContext();
1174
1245
  if (Utils.isEntity(entityName)) {
1175
- return em.merge(entityName.constructor.name, entityName, data);
1246
+ return this.merge(entityName.constructor, entityName, data);
1176
1247
  }
1248
+ const em = options.disableContextResolution ? this : this.getContext();
1177
1249
  options.schema ??= em._schema;
1178
- entityName = Utils.className(entityName);
1179
- em.validator.validatePrimaryKey(data, em.metadata.get(entityName));
1250
+ options.validate ??= true;
1251
+ options.cascade ??= true;
1252
+ validatePrimaryKey(data, em.metadata.get(entityName));
1180
1253
  let entity = em.unitOfWork.tryGetById(entityName, data, options.schema, false);
1181
1254
  if (entity && helper(entity).__managed && helper(entity).__initialized && !options.refresh) {
1182
1255
  return entity;
1183
1256
  }
1184
- const meta = em.metadata.find(entityName);
1185
- const childMeta = em.metadata.getByDiscriminatorColumn(meta, data);
1186
- entity = Utils.isEntity(data) ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
1187
- em.validator.validate(entity, data, childMeta ?? meta);
1188
- em.unitOfWork.merge(entity);
1257
+ const dataIsEntity = Utils.isEntity(data);
1258
+ entity = dataIsEntity ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
1259
+ const visited = options.cascade ? undefined : new Set([entity]);
1260
+ em.unitOfWork.merge(entity, visited);
1189
1261
  return entity;
1190
1262
  }
1191
1263
  /**
@@ -1210,6 +1282,7 @@ export class EntityManager {
1210
1282
  ...options,
1211
1283
  newEntity: !options.managed,
1212
1284
  merge: options.managed,
1285
+ normalizeAccessors: true,
1213
1286
  });
1214
1287
  options.persist ??= em.config.get('persistOnCreate');
1215
1288
  if (options.persist && !this.getMetadata(entityName).embeddable) {
@@ -1229,7 +1302,7 @@ export class EntityManager {
1229
1302
  getReference(entityName, id, options = {}) {
1230
1303
  options.schema ??= this.schema;
1231
1304
  options.convertCustomTypes ??= false;
1232
- const meta = this.metadata.get(Utils.className(entityName));
1305
+ const meta = this.metadata.get(entityName);
1233
1306
  if (Utils.isPrimaryKey(id)) {
1234
1307
  if (meta.compositePK) {
1235
1308
  throw ValidationError.invalidCompositeIdentifier(meta);
@@ -1248,11 +1321,8 @@ export class EntityManager {
1248
1321
  async count(entityName, where = {}, options = {}) {
1249
1322
  const em = this.getContext(false);
1250
1323
  // Shallow copy options since the object will be modified when deleting orderBy
1251
- options = {
1252
- schema: em._schema,
1253
- ...options,
1254
- };
1255
- entityName = Utils.className(entityName);
1324
+ options = { ...options };
1325
+ em.prepareOptions(options);
1256
1326
  await em.tryFlush(entityName, options);
1257
1327
  where = await em.processWhere(entityName, where, options, 'read');
1258
1328
  options.populate = await em.preparePopulate(entityName, options);
@@ -1261,15 +1331,16 @@ export class EntityManager {
1261
1331
  const meta = em.metadata.find(entityName);
1262
1332
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
1263
1333
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
1264
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
1265
- em.validator.validateParams(where);
1334
+ options.populateFilter = await this.getJoinedFilters(meta, options);
1335
+ validateParams(where);
1266
1336
  delete options.orderBy;
1337
+ await em.processUnionWhere(entityName, options, 'read');
1267
1338
  const cacheKey = em.cacheKey(entityName, options, 'em.count', where);
1268
1339
  const cached = await em.tryCache(entityName, options.cache, cacheKey);
1269
- if (cached?.data) {
1340
+ if (cached?.data !== undefined) {
1270
1341
  return cached.data;
1271
1342
  }
1272
- const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, ...options });
1343
+ const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, em, ...options });
1273
1344
  await em.storeCache(options.cache, cached, () => +count);
1274
1345
  return +count;
1275
1346
  }
@@ -1288,7 +1359,7 @@ export class EntityManager {
1288
1359
  for (const ent of entities) {
1289
1360
  if (!Utils.isEntity(ent, true)) {
1290
1361
  /* v8 ignore next */
1291
- const meta = typeof ent === 'object' ? em.metadata.find(ent.constructor.name) : undefined;
1362
+ const meta = typeof ent === 'object' ? em.metadata.find(ent.constructor) : undefined;
1292
1363
  throw ValidationError.notDiscoveredEntity(ent, meta);
1293
1364
  }
1294
1365
  // do not cascade just yet, cascading of entities in persist stack is done when flushing
@@ -1296,13 +1367,6 @@ export class EntityManager {
1296
1367
  }
1297
1368
  return this;
1298
1369
  }
1299
- /**
1300
- * Persists your entity immediately, flushing all not yet persisted changes to the database too.
1301
- * Equivalent to `em.persist(e).flush()`.
1302
- */
1303
- async persistAndFlush(entity) {
1304
- await this.persist(entity).flush();
1305
- }
1306
1370
  /**
1307
1371
  * Marks entity for removal.
1308
1372
  * A removed entity will be removed from the database at or before transaction commit or as a result of the flush operation.
@@ -1326,13 +1390,6 @@ export class EntityManager {
1326
1390
  }
1327
1391
  return em;
1328
1392
  }
1329
- /**
1330
- * Removes an entity instance immediately, flushing all not yet persisted changes to the database too.
1331
- * Equivalent to `em.remove(e).flush()`
1332
- */
1333
- async removeAndFlush(entity) {
1334
- await this.remove(entity).flush();
1335
- }
1336
1393
  /**
1337
1394
  * Flushes all changes to objects that have been queued up to now to the database.
1338
1395
  * This effectively synchronizes the in-memory state of managed objects with the database.
@@ -1346,7 +1403,6 @@ export class EntityManager {
1346
1403
  async tryFlush(entityName, options) {
1347
1404
  const em = this.getContext();
1348
1405
  const flushMode = options.flushMode ?? em.flushMode ?? em.config.get('flushMode');
1349
- entityName = Utils.className(entityName);
1350
1406
  const meta = em.metadata.get(entityName);
1351
1407
  if (flushMode === FlushMode.COMMIT) {
1352
1408
  return;
@@ -1365,7 +1421,6 @@ export class EntityManager {
1365
1421
  * Checks whether given property can be populated on the entity.
1366
1422
  */
1367
1423
  canPopulate(entityName, property) {
1368
- entityName = Utils.className(entityName);
1369
1424
  // eslint-disable-next-line prefer-const
1370
1425
  let [p, ...parts] = property.split('.');
1371
1426
  const meta = this.metadata.find(entityName);
@@ -1375,12 +1430,11 @@ export class EntityManager {
1375
1430
  if (p.includes(':')) {
1376
1431
  p = p.split(':', 2)[0];
1377
1432
  }
1378
- const ret = p in meta.root.properties;
1379
- if (!ret) {
1380
- return !!this.metadata.find(property)?.pivotTable;
1381
- }
1433
+ // For TPT inheritance, check the entity's own properties, not just the root's
1434
+ // For STI, meta.properties includes all properties anyway
1435
+ const ret = p in meta.properties;
1382
1436
  if (parts.length > 0) {
1383
- return this.canPopulate((meta.root.properties)[p].type, parts.join('.'));
1437
+ return this.canPopulate(meta.properties[p].targetMeta.class, parts.join('.'));
1384
1438
  }
1385
1439
  return ret;
1386
1440
  }
@@ -1394,8 +1448,8 @@ export class EntityManager {
1394
1448
  }
1395
1449
  const em = this.getContext();
1396
1450
  em.prepareOptions(options);
1397
- const entityName = arr[0].constructor.name;
1398
- const preparedPopulate = await em.preparePopulate(entityName, { populate: populate }, options.validate);
1451
+ const entityName = arr[0].constructor;
1452
+ const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters }, options.validate);
1399
1453
  await em.entityLoader.populate(entityName, arr, preparedPopulate, options);
1400
1454
  return entities;
1401
1455
  }
@@ -1431,6 +1485,9 @@ export class EntityManager {
1431
1485
  for (const entity of em.unitOfWork.getIdentityMap()) {
1432
1486
  fork.unitOfWork.register(entity);
1433
1487
  }
1488
+ for (const entity of em.unitOfWork.getPersistStack()) {
1489
+ fork.unitOfWork.persist(entity);
1490
+ }
1434
1491
  for (const entity of em.unitOfWork.getOrphanRemoveStack()) {
1435
1492
  fork.unitOfWork.getOrphanRemoveStack().add(entity);
1436
1493
  }
@@ -1452,6 +1509,12 @@ export class EntityManager {
1452
1509
  getEntityFactory() {
1453
1510
  return this.getContext().entityFactory;
1454
1511
  }
1512
+ /**
1513
+ * @internal use `em.populate()` as the user facing API, this is exposed only for internal usage
1514
+ */
1515
+ getEntityLoader() {
1516
+ return this.getContext().entityLoader;
1517
+ }
1455
1518
  /**
1456
1519
  * Gets the Hydrator used by the EntityManager.
1457
1520
  */
@@ -1514,7 +1577,6 @@ export class EntityManager {
1514
1577
  */
1515
1578
  getMetadata(entityName) {
1516
1579
  if (entityName) {
1517
- entityName = Utils.className(entityName);
1518
1580
  return this.metadata.get(entityName);
1519
1581
  }
1520
1582
  return this.metadata;
@@ -1543,12 +1605,11 @@ export class EntityManager {
1543
1605
  lockTableAliases: options.lockTableAliases,
1544
1606
  });
1545
1607
  }
1546
- const preparedPopulate = await this.preparePopulate(meta.className, options);
1547
- await this.entityLoader.populate(meta.className, [entity], preparedPopulate, {
1608
+ const preparedPopulate = await this.preparePopulate(meta.class, options);
1609
+ await this.entityLoader.populate(meta.class, [entity], preparedPopulate, {
1548
1610
  ...options,
1549
1611
  ...this.getPopulateWhere(where, options),
1550
1612
  orderBy: options.populateOrderBy ?? options.orderBy,
1551
- convertCustomTypes: false,
1552
1613
  ignoreLazyScalarProperties: true,
1553
1614
  lookup: false,
1554
1615
  });
@@ -1565,6 +1626,7 @@ export class EntityManager {
1565
1626
  return ret;
1566
1627
  }, []);
1567
1628
  }
1629
+ /** @internal */
1568
1630
  async preparePopulate(entityName, options, validate = true) {
1569
1631
  if (options.populate === false) {
1570
1632
  return [];
@@ -1605,13 +1667,13 @@ export class EntityManager {
1605
1667
  options.populate = pruneToOneRelations(meta, this.buildFields(options.fields));
1606
1668
  }
1607
1669
  if (!options.populate) {
1608
- const populate = this.entityLoader.normalizePopulate(entityName, [], options.strategy);
1670
+ const populate = this.entityLoader.normalizePopulate(entityName, [], options.strategy, true, options.exclude);
1609
1671
  await this.autoJoinRefsForFilters(meta, { ...options, populate });
1610
1672
  return populate;
1611
1673
  }
1612
1674
  if (typeof options.populate !== 'boolean') {
1613
1675
  options.populate = Utils.asArray(options.populate).map(field => {
1614
- /* v8 ignore next 3 */
1676
+ /* v8 ignore next */
1615
1677
  if (typeof field === 'boolean' || field === PopulatePath.ALL) {
1616
1678
  return [{ field: meta.primaryKeys[0], strategy: options.strategy, all: !!field }]; //
1617
1679
  }
@@ -1621,24 +1683,27 @@ export class EntityManager {
1621
1683
  options.flags.push(QueryFlag.INFER_POPULATE);
1622
1684
  return [];
1623
1685
  }
1624
- if (Utils.isString(field)) {
1686
+ if (typeof field === 'string') {
1625
1687
  return [{ field, strategy: options.strategy }];
1626
1688
  }
1627
1689
  return [field];
1628
1690
  }).flat();
1629
1691
  }
1630
- const populate = this.entityLoader.normalizePopulate(entityName, options.populate, options.strategy);
1692
+ const populate = this.entityLoader.normalizePopulate(entityName, options.populate, options.strategy, true, options.exclude);
1631
1693
  const invalid = populate.find(({ field }) => !this.canPopulate(entityName, field));
1632
1694
  if (validate && invalid) {
1633
1695
  throw ValidationError.invalidPropertyName(entityName, invalid.field);
1634
1696
  }
1635
1697
  await this.autoJoinRefsForFilters(meta, { ...options, populate });
1636
- return populate.map(field => {
1698
+ for (const field of populate) {
1637
1699
  // force select-in strategy when populating all relations as otherwise we could cause infinite loops when self-referencing
1638
1700
  const all = field.all ?? (Array.isArray(options.populate) && options.populate.includes('*'));
1639
1701
  field.strategy = all ? LoadStrategy.SELECT_IN : (options.strategy ?? field.strategy);
1640
- return field;
1641
- });
1702
+ }
1703
+ if (options.populateHints) {
1704
+ applyPopulateHints(populate, options.populateHints);
1705
+ }
1706
+ return populate;
1642
1707
  }
1643
1708
  /**
1644
1709
  * when the entity is found in identity map, we check if it was partially loaded or we are trying to populate
@@ -1658,7 +1723,7 @@ export class EntityManager {
1658
1723
  return !inlineEmbedded && !prop.lazy && !helper(entity).__loadedProperties.has(prop.name);
1659
1724
  });
1660
1725
  }
1661
- if (autoRefresh) {
1726
+ if (autoRefresh || options.filters) {
1662
1727
  return true;
1663
1728
  }
1664
1729
  if (Array.isArray(options.populate)) {
@@ -1671,7 +1736,7 @@ export class EntityManager {
1671
1736
  throw new ValidationError(`Cannot combine 'fields' and 'exclude' option.`);
1672
1737
  }
1673
1738
  options.schema ??= this._schema;
1674
- options.logging = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
1739
+ options.logging = options.loggerContext = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
1675
1740
  }
1676
1741
  /**
1677
1742
  * @internal
@@ -1682,7 +1747,7 @@ export class EntityManager {
1682
1747
  for (const k of ['ctx', 'strategy', 'flushMode', 'logging', 'loggerContext']) {
1683
1748
  delete opts[k];
1684
1749
  }
1685
- return [entityName, method, opts, where];
1750
+ return [Utils.className(entityName), method, opts, where];
1686
1751
  }
1687
1752
  /**
1688
1753
  * @internal
@@ -1695,31 +1760,27 @@ export class EntityManager {
1695
1760
  const em = this.getContext();
1696
1761
  const cacheKey = Array.isArray(config) ? config[0] : JSON.stringify(key);
1697
1762
  const cached = await em.resultCache.get(cacheKey);
1698
- if (cached) {
1699
- let data;
1700
- if (Array.isArray(cached) && merge) {
1701
- data = cached.map(item => em.entityFactory.create(entityName, item, {
1702
- merge: true,
1703
- convertCustomTypes: true,
1704
- refresh,
1705
- recomputeSnapshot: true,
1706
- }));
1707
- }
1708
- else if (Utils.isObject(cached) && merge) {
1709
- data = em.entityFactory.create(entityName, cached, {
1710
- merge: true,
1711
- convertCustomTypes: true,
1712
- refresh,
1713
- recomputeSnapshot: true,
1714
- });
1715
- }
1716
- else {
1717
- data = cached;
1718
- }
1719
- await em.unitOfWork.dispatchOnLoadEvent();
1720
- return { key: cacheKey, data };
1763
+ if (!cached) {
1764
+ return { key: cacheKey, data: cached };
1765
+ }
1766
+ let data;
1767
+ const createOptions = {
1768
+ merge: true,
1769
+ convertCustomTypes: false,
1770
+ refresh,
1771
+ recomputeSnapshot: true,
1772
+ };
1773
+ if (Array.isArray(cached) && merge) {
1774
+ data = cached.map(item => em.entityFactory.create(entityName, item, createOptions));
1775
+ }
1776
+ else if (Utils.isObject(cached) && merge) {
1777
+ data = em.entityFactory.create(entityName, cached, createOptions);
1778
+ }
1779
+ else {
1780
+ data = cached;
1721
1781
  }
1722
- return { key: cacheKey };
1782
+ await em.unitOfWork.dispatchOnLoadEvent();
1783
+ return { key: cacheKey, data };
1723
1784
  }
1724
1785
  /**
1725
1786
  * @internal
@@ -1728,7 +1789,7 @@ export class EntityManager {
1728
1789
  config ??= this.config.get('resultCache').global;
1729
1790
  if (config) {
1730
1791
  const em = this.getContext();
1731
- const expiration = Array.isArray(config) ? config[1] : (Utils.isNumber(config) ? config : undefined);
1792
+ const expiration = Array.isArray(config) ? config[1] : (typeof config === 'number' ? config : undefined);
1732
1793
  await em.resultCache.set(key.key, data instanceof Function ? data() : data, '', expiration);
1733
1794
  }
1734
1795
  }
@@ -1761,6 +1822,20 @@ export class EntityManager {
1761
1822
  set schema(schema) {
1762
1823
  this.getContext(false)._schema = schema ?? undefined;
1763
1824
  }
1825
+ /** @internal */
1826
+ async getDataLoader(type) {
1827
+ const em = this.getContext();
1828
+ if (em.loaders[type]) {
1829
+ return em.loaders[type];
1830
+ }
1831
+ const { DataloaderUtils } = await import('@mikro-orm/core/dataloader');
1832
+ const DataLoader = await DataloaderUtils.getDataLoader();
1833
+ switch (type) {
1834
+ case 'ref': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getRefBatchLoadFn(em)));
1835
+ case '1:m': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getColBatchLoadFn(em)));
1836
+ case 'm:n': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(em)));
1837
+ }
1838
+ }
1764
1839
  /**
1765
1840
  * Returns the ID of this EntityManager. Respects the context, so global EM will give you the contextual ID
1766
1841
  * if executed inside request context handler.
@@ -1769,7 +1844,7 @@ export class EntityManager {
1769
1844
  return this.getContext(false)._id;
1770
1845
  }
1771
1846
  /** @ignore */
1772
- [inspect.custom]() {
1847
+ [Symbol.for('nodejs.util.inspect.custom')]() {
1773
1848
  return `[EntityManager<${this.id}>]`;
1774
1849
  }
1775
1850
  }