@mikro-orm/core 7.0.0-dev.2 → 7.0.0-dev.200

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 (210) hide show
  1. package/EntityManager.d.ts +111 -61
  2. package/EntityManager.js +346 -300
  3. package/MikroORM.d.ts +44 -35
  4. package/MikroORM.js +103 -143
  5. package/README.md +3 -2
  6. package/cache/FileCacheAdapter.d.ts +1 -1
  7. package/cache/FileCacheAdapter.js +8 -7
  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 +80 -35
  16. package/drivers/IDatabaseDriver.d.ts +47 -17
  17. package/entity/BaseEntity.d.ts +2 -2
  18. package/entity/BaseEntity.js +0 -3
  19. package/entity/Collection.d.ts +95 -31
  20. package/entity/Collection.js +444 -102
  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 +88 -54
  25. package/entity/EntityHelper.d.ts +2 -2
  26. package/entity/EntityHelper.js +38 -15
  27. package/entity/EntityLoader.d.ts +8 -7
  28. package/entity/EntityLoader.js +134 -80
  29. package/entity/EntityRepository.d.ts +24 -4
  30. package/entity/EntityRepository.js +8 -2
  31. package/entity/Reference.d.ts +9 -12
  32. package/entity/Reference.js +34 -9
  33. package/entity/WrappedEntity.d.ts +2 -7
  34. package/entity/WrappedEntity.js +3 -8
  35. package/entity/defineEntity.d.ts +585 -0
  36. package/entity/defineEntity.js +533 -0
  37. package/entity/index.d.ts +3 -2
  38. package/entity/index.js +3 -2
  39. package/entity/utils.d.ts +7 -0
  40. package/entity/utils.js +16 -4
  41. package/entity/validators.d.ts +11 -0
  42. package/entity/validators.js +65 -0
  43. package/enums.d.ts +22 -6
  44. package/enums.js +15 -1
  45. package/errors.d.ts +23 -9
  46. package/errors.js +59 -21
  47. package/events/EventManager.d.ts +2 -1
  48. package/events/EventManager.js +19 -11
  49. package/events/EventSubscriber.d.ts +3 -1
  50. package/hydration/Hydrator.js +1 -2
  51. package/hydration/ObjectHydrator.d.ts +4 -4
  52. package/hydration/ObjectHydrator.js +53 -33
  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 +26 -26
  65. package/metadata/EntitySchema.js +82 -51
  66. package/metadata/MetadataDiscovery.d.ts +7 -10
  67. package/metadata/MetadataDiscovery.js +408 -335
  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 +17 -9
  73. package/metadata/MetadataValidator.js +100 -42
  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 +502 -0
  79. package/metadata/types.js +1 -0
  80. package/naming-strategy/AbstractNamingStrategy.d.ts +12 -4
  81. package/naming-strategy/AbstractNamingStrategy.js +14 -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 +24 -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 +19 -11
  92. package/platforms/ExceptionConverter.js +1 -1
  93. package/platforms/Platform.d.ts +7 -13
  94. package/platforms/Platform.js +20 -43
  95. package/serialization/EntitySerializer.d.ts +5 -0
  96. package/serialization/EntitySerializer.js +47 -27
  97. package/serialization/EntityTransformer.js +28 -18
  98. package/serialization/SerializationContext.d.ts +6 -6
  99. package/serialization/SerializationContext.js +16 -13
  100. package/types/ArrayType.d.ts +1 -1
  101. package/types/ArrayType.js +2 -3
  102. package/types/BigIntType.d.ts +9 -6
  103. package/types/BigIntType.js +4 -1
  104. package/types/BlobType.d.ts +0 -1
  105. package/types/BlobType.js +0 -3
  106. package/types/BooleanType.d.ts +2 -1
  107. package/types/BooleanType.js +3 -0
  108. package/types/DecimalType.d.ts +6 -4
  109. package/types/DecimalType.js +3 -3
  110. package/types/DoubleType.js +2 -2
  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 +300 -140
  121. package/typings.js +62 -44
  122. package/unit-of-work/ChangeSet.d.ts +2 -6
  123. package/unit-of-work/ChangeSet.js +4 -5
  124. package/unit-of-work/ChangeSetComputer.d.ts +1 -3
  125. package/unit-of-work/ChangeSetComputer.js +26 -13
  126. package/unit-of-work/ChangeSetPersister.d.ts +5 -4
  127. package/unit-of-work/ChangeSetPersister.js +77 -35
  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 +23 -3
  133. package/unit-of-work/UnitOfWork.js +199 -106
  134. package/utils/AbstractSchemaGenerator.d.ts +5 -5
  135. package/utils/AbstractSchemaGenerator.js +22 -17
  136. package/utils/AsyncContext.d.ts +6 -0
  137. package/utils/AsyncContext.js +42 -0
  138. package/utils/Configuration.d.ts +779 -207
  139. package/utils/Configuration.js +146 -190
  140. package/utils/ConfigurationLoader.d.ts +1 -54
  141. package/utils/ConfigurationLoader.js +1 -352
  142. package/utils/Cursor.d.ts +3 -6
  143. package/utils/Cursor.js +27 -11
  144. package/utils/DataloaderUtils.d.ts +15 -5
  145. package/utils/DataloaderUtils.js +65 -17
  146. package/utils/EntityComparator.d.ts +13 -9
  147. package/utils/EntityComparator.js +164 -89
  148. package/utils/QueryHelper.d.ts +14 -6
  149. package/utils/QueryHelper.js +88 -26
  150. package/utils/RawQueryFragment.d.ts +48 -25
  151. package/utils/RawQueryFragment.js +67 -66
  152. package/utils/RequestContext.js +2 -2
  153. package/utils/TransactionContext.js +2 -2
  154. package/utils/TransactionManager.d.ts +65 -0
  155. package/utils/TransactionManager.js +223 -0
  156. package/utils/Utils.d.ts +13 -120
  157. package/utils/Utils.js +104 -375
  158. package/utils/clone.js +8 -23
  159. package/utils/env-vars.d.ts +7 -0
  160. package/utils/env-vars.js +97 -0
  161. package/utils/fs-utils.d.ts +32 -0
  162. package/utils/fs-utils.js +178 -0
  163. package/utils/index.d.ts +2 -1
  164. package/utils/index.js +2 -1
  165. package/utils/upsert-utils.d.ts +9 -4
  166. package/utils/upsert-utils.js +55 -4
  167. package/decorators/Check.d.ts +0 -3
  168. package/decorators/Check.js +0 -13
  169. package/decorators/CreateRequestContext.d.ts +0 -3
  170. package/decorators/CreateRequestContext.js +0 -29
  171. package/decorators/Embeddable.d.ts +0 -8
  172. package/decorators/Embeddable.js +0 -11
  173. package/decorators/Embedded.d.ts +0 -18
  174. package/decorators/Embedded.js +0 -18
  175. package/decorators/Entity.d.ts +0 -18
  176. package/decorators/Entity.js +0 -13
  177. package/decorators/Enum.d.ts +0 -9
  178. package/decorators/Enum.js +0 -16
  179. package/decorators/Filter.d.ts +0 -2
  180. package/decorators/Filter.js +0 -8
  181. package/decorators/Formula.d.ts +0 -5
  182. package/decorators/Formula.js +0 -15
  183. package/decorators/Indexed.d.ts +0 -17
  184. package/decorators/Indexed.js +0 -20
  185. package/decorators/ManyToMany.d.ts +0 -40
  186. package/decorators/ManyToMany.js +0 -14
  187. package/decorators/ManyToOne.d.ts +0 -30
  188. package/decorators/ManyToOne.js +0 -14
  189. package/decorators/OneToMany.d.ts +0 -28
  190. package/decorators/OneToMany.js +0 -17
  191. package/decorators/OneToOne.d.ts +0 -24
  192. package/decorators/OneToOne.js +0 -7
  193. package/decorators/PrimaryKey.d.ts +0 -9
  194. package/decorators/PrimaryKey.js +0 -20
  195. package/decorators/Property.d.ts +0 -250
  196. package/decorators/Property.js +0 -32
  197. package/decorators/Transactional.d.ts +0 -13
  198. package/decorators/Transactional.js +0 -28
  199. package/decorators/hooks.d.ts +0 -16
  200. package/decorators/hooks.js +0 -47
  201. package/decorators/index.d.ts +0 -17
  202. package/decorators/index.js +0 -17
  203. package/entity/ArrayCollection.d.ts +0 -116
  204. package/entity/ArrayCollection.js +0 -395
  205. package/entity/EntityValidator.d.ts +0 -19
  206. package/entity/EntityValidator.js +0 -150
  207. package/metadata/ReflectMetadataProvider.d.ts +0 -8
  208. package/metadata/ReflectMetadataProvider.js +0 -44
  209. package/utils/resolveContextProvider.d.ts +0 -10
  210. package/utils/resolveContextProvider.js +0 -28
package/EntityManager.js CHANGED
@@ -1,15 +1,13 @@
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
4
  import { DataloaderUtils } from './utils/DataloaderUtils.js';
7
5
  import { QueryHelper } from './utils/QueryHelper.js';
8
6
  import { TransactionContext } from './utils/TransactionContext.js';
9
- import { isRaw, RawQueryFragment } from './utils/RawQueryFragment.js';
7
+ import { isRaw, Raw } from './utils/RawQueryFragment.js';
10
8
  import { EntityFactory } from './entity/EntityFactory.js';
11
9
  import { EntityAssigner } from './entity/EntityAssigner.js';
12
- import { EntityValidator } from './entity/EntityValidator.js';
10
+ import { validateEmptyWhere, validateParams, validatePrimaryKey, validateProperty } from './entity/validators.js';
13
11
  import { EntityLoader } from './entity/EntityLoader.js';
14
12
  import { Reference } from './entity/Reference.js';
15
13
  import { helper } from './entity/wrap.js';
@@ -19,6 +17,8 @@ import { EventType, FlushMode, LoadStrategy, LockMode, PopulateHint, PopulatePat
19
17
  import { EventManager } from './events/EventManager.js';
20
18
  import { TransactionEventBroadcaster } from './events/TransactionEventBroadcaster.js';
21
19
  import { OptimisticLockError, ValidationError } from './errors.js';
20
+ import { getLoadingStrategy } from './entity/utils.js';
21
+ import { TransactionManager } from './utils/TransactionManager.js';
22
22
  /**
23
23
  * The EntityManager is the central access point to ORM functionality. It is a facade to all different ORM subsystems
24
24
  * such as UnitOfWork, Query Language, and Repository API.
@@ -34,10 +34,8 @@ export class EntityManager {
34
34
  _id = EntityManager.counter++;
35
35
  global = false;
36
36
  name;
37
- refLoader = new DataLoader(DataloaderUtils.getRefBatchLoadFn(this));
38
- colLoader = new DataLoader(DataloaderUtils.getColBatchLoadFn(this));
39
- validator;
40
- repositoryMap = {};
37
+ loaders = {};
38
+ repositoryMap = new Map();
41
39
  entityLoader;
42
40
  comparator;
43
41
  entityFactory;
@@ -61,7 +59,6 @@ export class EntityManager {
61
59
  this.eventManager = eventManager;
62
60
  this.entityLoader = new EntityLoader(this);
63
61
  this.name = this.config.get('contextName');
64
- this.validator = new EntityValidator(this.config.get('strict'));
65
62
  this.comparator = this.config.getComparator(this.metadata);
66
63
  this.resultCache = this.config.getResultCacheAdapter();
67
64
  this.disableTransactions = this.config.get('disableTransactions');
@@ -91,13 +88,12 @@ export class EntityManager {
91
88
  * Gets repository for given entity. You can pass either string name or entity class reference.
92
89
  */
93
90
  getRepository(entityName) {
94
- entityName = Utils.className(entityName);
95
- if (!this.repositoryMap[entityName]) {
96
- const meta = this.metadata.get(entityName);
91
+ const meta = this.metadata.get(entityName);
92
+ if (!this.repositoryMap.has(meta)) {
97
93
  const RepositoryClass = this.config.getRepositoryClass(meta.repository);
98
- this.repositoryMap[entityName] = new RepositoryClass(this, entityName);
94
+ this.repositoryMap.set(meta, new RepositoryClass(this, entityName));
99
95
  }
100
- return this.repositoryMap[entityName];
96
+ return this.repositoryMap.get(meta);
101
97
  }
102
98
  /**
103
99
  * Shortcut for `em.getRepository()`.
@@ -105,12 +101,6 @@ export class EntityManager {
105
101
  repo(entityName) {
106
102
  return this.getRepository(entityName);
107
103
  }
108
- /**
109
- * Gets EntityValidator instance
110
- */
111
- getValidator() {
112
- return this.validator;
113
- }
114
104
  /**
115
105
  * Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
116
106
  */
@@ -125,9 +115,8 @@ export class EntityManager {
125
115
  const em = this.getContext();
126
116
  em.prepareOptions(options);
127
117
  await em.tryFlush(entityName, options);
128
- entityName = Utils.className(entityName);
129
118
  where = await em.processWhere(entityName, where, options, 'read');
130
- em.validator.validateParams(where);
119
+ validateParams(where);
131
120
  options.orderBy = options.orderBy || {};
132
121
  options.populate = await em.preparePopulate(entityName, options);
133
122
  const populate = options.populate;
@@ -137,7 +126,6 @@ export class EntityManager {
137
126
  await em.entityLoader.populate(entityName, cached.data, populate, {
138
127
  ...options,
139
128
  ...em.getPopulateWhere(where, options),
140
- convertCustomTypes: false,
141
129
  ignoreLazyScalarProperties: true,
142
130
  lookup: false,
143
131
  });
@@ -148,8 +136,8 @@ export class EntityManager {
148
136
  // save the original hint value so we know it was infer/all
149
137
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
150
138
  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 });
139
+ options.populateFilter = await this.getJoinedFilters(meta, options);
140
+ const results = await em.driver.find(entityName, where, { ctx: em.transactionContext, em, ...options });
153
141
  if (results.length === 0) {
154
142
  await em.storeCache(options.cache, cached, []);
155
143
  return [];
@@ -168,7 +156,6 @@ export class EntityManager {
168
156
  await em.entityLoader.populate(entityName, unique, populate, {
169
157
  ...options,
170
158
  ...em.getPopulateWhere(where, options),
171
- convertCustomTypes: false,
172
159
  ignoreLazyScalarProperties: true,
173
160
  lookup: false,
174
161
  });
@@ -181,6 +168,60 @@ export class EntityManager {
181
168
  }
182
169
  return unique;
183
170
  }
171
+ /**
172
+ * Finds all entities and returns an async iterable (async generator) that yields results one by one.
173
+ * The results are merged and mapped to entity instances, without adding them to the identity map.
174
+ * You can disable merging by passing the options `{ mergeResults: false }`.
175
+ * With `mergeResults` disabled, to-many collections will contain at most one item, and you will get duplicate
176
+ * root entities when there are multiple items in the populated collection.
177
+ * This is useful for processing large datasets without loading everything into memory at once.
178
+ *
179
+ * ```ts
180
+ * const stream = em.stream(Book, { populate: ['author'] });
181
+ *
182
+ * for await (const book of stream) {
183
+ * // book is an instance of Book entity
184
+ * console.log(book.title, book.author.name);
185
+ * }
186
+ * ```
187
+ */
188
+ async *stream(entityName, options = {}) {
189
+ const em = this.getContext();
190
+ em.prepareOptions(options);
191
+ options.strategy = 'joined';
192
+ await em.tryFlush(entityName, options);
193
+ const where = await em.processWhere(entityName, options.where ?? {}, options, 'read');
194
+ validateParams(where);
195
+ options.orderBy = options.orderBy || {};
196
+ options.populate = await em.preparePopulate(entityName, options);
197
+ const meta = this.metadata.get(entityName);
198
+ options = { ...options };
199
+ // save the original hint value so we know it was infer/all
200
+ options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
201
+ options.populateWhere = this.createPopulateWhere({ ...where }, options);
202
+ options.populateFilter = await this.getJoinedFilters(meta, options);
203
+ const stream = em.driver.stream(entityName, where, {
204
+ ctx: em.transactionContext,
205
+ mapResults: false,
206
+ ...options,
207
+ });
208
+ for await (const data of stream) {
209
+ const fork = em.fork();
210
+ const entity = fork.entityFactory.create(entityName, data, {
211
+ refresh: options.refresh,
212
+ schema: options.schema,
213
+ convertCustomTypes: true,
214
+ });
215
+ helper(entity).setSerializationContext({
216
+ populate: options.populate,
217
+ fields: options.fields,
218
+ exclude: options.exclude,
219
+ });
220
+ await fork.unitOfWork.dispatchOnLoadEvent();
221
+ fork.clear();
222
+ yield entity;
223
+ }
224
+ }
184
225
  /**
185
226
  * Finds all entities of given type, optionally matching the `where` condition provided in the `options` parameter.
186
227
  */
@@ -194,7 +235,7 @@ export class EntityManager {
194
235
  if (options.populateWhere === PopulateHint.ALL) {
195
236
  return { where: {}, populateWhere: options.populateWhere };
196
237
  }
197
- /* v8 ignore next 3 */
238
+ /* v8 ignore next */
198
239
  if (options.populateWhere === PopulateHint.INFER) {
199
240
  return { where, populateWhere: options.populateWhere };
200
241
  }
@@ -203,12 +244,12 @@ export class EntityManager {
203
244
  /**
204
245
  * Registers global filter to this entity manager. Global filters are enabled by default (unless disabled via last parameter).
205
246
  */
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));
247
+ addFilter(options) {
248
+ if (options.entity) {
249
+ options.entity = Utils.asArray(options.entity).map(n => Utils.className(n));
210
250
  }
211
- this.getContext(false).filters[name] = options;
251
+ options.default ??= true;
252
+ this.getContext(false).filters[options.name] = options;
212
253
  }
213
254
  /**
214
255
  * Sets filter parameter values globally inside context defined by this entity manager.
@@ -232,8 +273,8 @@ export class EntityManager {
232
273
  /**
233
274
  * Gets logger context for this entity manager.
234
275
  */
235
- getLoggerContext() {
236
- const em = this.getContext();
276
+ getLoggerContext(options) {
277
+ const em = options?.disableContextResolution ? this : this.getContext();
237
278
  em.loggerContext ??= {};
238
279
  return em.loggerContext;
239
280
  }
@@ -250,7 +291,7 @@ export class EntityManager {
250
291
  aliased: type === 'read',
251
292
  });
252
293
  where = (await this.applyFilters(entityName, where, options.filters ?? {}, type, options));
253
- where = await this.applyDiscriminatorCondition(entityName, where);
294
+ where = this.applyDiscriminatorCondition(entityName, where);
254
295
  return where;
255
296
  }
256
297
  // this method only handles the problem for mongo driver, SQL drivers have their implementation inside QueryBuilder
@@ -259,15 +300,15 @@ export class EntityManager {
259
300
  if (!meta?.discriminatorValue) {
260
301
  return where;
261
302
  }
262
- const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.find(cls));
303
+ const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.get(cls));
263
304
  const children = [];
264
305
  const lookUpChildren = (ret, type) => {
265
306
  const children = types.filter(meta2 => meta2.extends === type);
266
- children.forEach(m => lookUpChildren(ret, m.className));
307
+ children.forEach(m => lookUpChildren(ret, m.class));
267
308
  ret.push(...children.filter(c => c.discriminatorValue));
268
309
  return children;
269
310
  };
270
- lookUpChildren(children, meta.className);
311
+ lookUpChildren(children, meta.class);
271
312
  /* v8 ignore next */
272
313
  where[meta.root.discriminatorColumn] = children.length > 0 ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] } : meta.discriminatorValue;
273
314
  return where;
@@ -283,28 +324,39 @@ export class EntityManager {
283
324
  }
284
325
  return ret;
285
326
  }
286
- async getJoinedFilters(meta, cond, options) {
327
+ async getJoinedFilters(meta, options) {
328
+ if (!this.config.get('filtersOnRelations') || !options.populate) {
329
+ return undefined;
330
+ }
287
331
  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;
332
+ for (const hint of options.populate) {
333
+ const field = hint.field.split(':')[0];
334
+ const prop = meta.properties[field];
335
+ const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
336
+ const joined = strategy === LoadStrategy.JOINED && prop.kind !== ReferenceKind.SCALAR;
337
+ if (!joined && !hint.filter) {
338
+ continue;
339
+ }
340
+ const filters = QueryHelper.mergePropertyFilters(prop.filters, options.filters);
341
+ const where = await this.applyFilters(prop.targetMeta.class, {}, filters, 'read', {
342
+ ...options,
343
+ populate: hint.children,
344
+ });
345
+ const where2 = await this.getJoinedFilters(prop.targetMeta, {
346
+ ...options,
347
+ filters,
348
+ populate: hint.children,
349
+ populateWhere: PopulateHint.ALL,
350
+ });
351
+ if (Utils.hasObjectKeys(where)) {
352
+ ret[field] = ret[field] ? { $and: [where, ret[field]] } : where;
353
+ }
354
+ if (where2 && Utils.hasObjectKeys(where2)) {
355
+ if (ret[field]) {
356
+ Utils.merge(ret[field], where2);
300
357
  }
301
- if (Utils.hasObjectKeys(where2)) {
302
- if (ret[field]) {
303
- Utils.merge(ret[field], where2);
304
- }
305
- else {
306
- ret[field] = where2;
307
- }
358
+ else {
359
+ ret[field] = where2;
308
360
  }
309
361
  }
310
362
  }
@@ -313,42 +365,57 @@ export class EntityManager {
313
365
  /**
314
366
  * 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
367
  */
316
- async autoJoinRefsForFilters(meta, options) {
317
- if (!meta || !this.config.get('autoJoinRefsForFilters')) {
368
+ async autoJoinRefsForFilters(meta, options, parent) {
369
+ if (!meta || !this.config.get('autoJoinRefsForFilters') || options.filters === false) {
318
370
  return;
319
371
  }
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
372
  const ret = options.populate;
325
- for (const prop of props) {
326
- const cond = await this.applyFilters(prop.type, {}, options.filters ?? {}, 'read', options);
373
+ for (const prop of meta.relations) {
374
+ if (prop.object
375
+ || ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)
376
+ || !((options.fields?.length ?? 0) === 0 || options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
377
+ || (parent?.class === prop.targetMeta.root.class && parent.propName === prop.inversedBy)) {
378
+ continue;
379
+ }
380
+ options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
381
+ const cond = await this.applyFilters(prop.targetMeta.class, {}, options.filters, 'read', options);
327
382
  if (!Utils.isEmpty(cond)) {
328
383
  const populated = options.populate.filter(({ field }) => field.split(':')[0] === prop.name);
329
- if (populated.length > 0) {
330
- populated.forEach(hint => hint.filter = true);
384
+ let found = false;
385
+ for (const hint of populated) {
386
+ if (!hint.all) {
387
+ hint.filter = true;
388
+ }
389
+ const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
390
+ if (hint.field === `${prop.name}:ref` || (hint.filter && strategy === LoadStrategy.JOINED)) {
391
+ found = true;
392
+ }
331
393
  }
332
- else {
394
+ if (!found) {
333
395
  ret.push({ field: `${prop.name}:ref`, strategy: LoadStrategy.JOINED, filter: true });
334
396
  }
335
397
  }
336
398
  }
399
+ for (const hint of ret) {
400
+ const [field, ref] = hint.field.split(':');
401
+ const prop = meta?.properties[field];
402
+ if (prop && !ref) {
403
+ hint.children ??= [];
404
+ await this.autoJoinRefsForFilters(prop.targetMeta, { ...options, populate: hint.children }, { class: meta.root.class, propName: prop.name });
405
+ }
406
+ }
337
407
  }
338
408
  /**
339
409
  * @internal
340
410
  */
341
411
  async applyFilters(entityName, where, options, type, findOptions) {
342
- const meta = this.metadata.find(entityName);
412
+ const meta = this.metadata.get(entityName);
343
413
  const filters = [];
344
414
  const ret = [];
345
- if (!meta) {
346
- return where;
347
- }
348
415
  const active = new Set();
349
416
  const push = (source) => {
350
417
  const activeFilters = QueryHelper
351
- .getActiveFilters(entityName, options, source)
418
+ .getActiveFilters(meta, options, source)
352
419
  .filter(f => !active.has(f.name));
353
420
  filters.push(...activeFilters);
354
421
  activeFilters.forEach(f => active.add(f.name));
@@ -363,24 +430,28 @@ export class EntityManager {
363
430
  let cond;
364
431
  if (filter.cond instanceof Function) {
365
432
  // @ts-ignore
366
- const args = Utils.isPlainObject(options[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
433
+ const args = Utils.isPlainObject(options?.[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
367
434
  if (!args && filter.cond.length > 0 && filter.args !== false) {
368
435
  throw new Error(`No arguments provided for filter '${filter.name}'`);
369
436
  }
370
- cond = await filter.cond(args, type, this, findOptions);
437
+ cond = await filter.cond(args, type, this, findOptions, Utils.className(entityName));
371
438
  }
372
439
  else {
373
440
  cond = filter.cond;
374
441
  }
375
- ret.push(QueryHelper.processWhere({
442
+ cond = QueryHelper.processWhere({
376
443
  where: cond,
377
444
  entityName,
378
445
  metadata: this.metadata,
379
446
  platform: this.driver.getPlatform(),
380
447
  aliased: type === 'read',
381
- }));
448
+ });
449
+ if (filter.strict) {
450
+ Object.defineProperty(cond, '__strict', { value: filter.strict, enumerable: false });
451
+ }
452
+ ret.push(cond);
382
453
  }
383
- const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c));
454
+ const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c) || Raw.hasObjectFragments(c));
384
455
  return conds.length > 1 ? { $and: conds } : conds[0];
385
456
  }
386
457
  /**
@@ -391,12 +462,10 @@ export class EntityManager {
391
462
  const em = this.getContext(false);
392
463
  await em.tryFlush(entityName, options);
393
464
  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
- });
465
+ return Promise.all([
466
+ em.find(entityName, where, options),
467
+ em.count(entityName, where, options),
468
+ ]);
400
469
  }
401
470
  /**
402
471
  * Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as {@apilink Cursor} object.
@@ -412,27 +481,31 @@ export class EntityManager {
412
481
  * - POJO/entity instance
413
482
  *
414
483
  * ```ts
415
- * const currentCursor = await em.findByCursor(User, {}, {
484
+ * const currentCursor = await em.findByCursor(User, {
416
485
  * first: 10,
417
486
  * after: previousCursor, // cursor instance
418
487
  * orderBy: { id: 'desc' },
419
488
  * });
420
489
  *
421
490
  * // to fetch next page
422
- * const nextCursor = await em.findByCursor(User, {}, {
491
+ * const nextCursor = await em.findByCursor(User, {
423
492
  * first: 10,
424
493
  * after: currentCursor.endCursor, // opaque string
425
494
  * orderBy: { id: 'desc' },
426
495
  * });
427
496
  *
428
497
  * // to fetch next page
429
- * const nextCursor2 = await em.findByCursor(User, {}, {
498
+ * const nextCursor2 = await em.findByCursor(User, {
430
499
  * first: 10,
431
500
  * after: { id: lastSeenId }, // entity-like POJO
432
501
  * orderBy: { id: 'desc' },
433
502
  * });
434
503
  * ```
435
504
  *
505
+ * The options also support an `includeCount` (true by default) option. If set to false, the `totalCount` is not
506
+ * returned as part of the cursor. This is useful for performance reason, when you don't care about the total number
507
+ * of pages.
508
+ *
436
509
  * The `Cursor` object provides the following interface:
437
510
  *
438
511
  * ```ts
@@ -442,7 +515,7 @@ export class EntityManager {
442
515
  * User { ... },
443
516
  * User { ... },
444
517
  * ],
445
- * totalCount: 50,
518
+ * totalCount: 50, // not included if `includeCount: false`
446
519
  * startCursor: 'WzRd',
447
520
  * endCursor: 'WzZd',
448
521
  * hasPrevPage: true,
@@ -450,14 +523,16 @@ export class EntityManager {
450
523
  * }
451
524
  * ```
452
525
  */
453
- async findByCursor(entityName, where, options) {
526
+ async findByCursor(entityName, options) {
454
527
  const em = this.getContext(false);
455
- entityName = Utils.className(entityName);
456
528
  options.overfetch ??= true;
457
- if (Utils.isEmpty(options.orderBy)) {
529
+ options.where ??= {};
530
+ if (Utils.isEmpty(options.orderBy) && !Raw.hasObjectFragments(options.orderBy)) {
458
531
  throw new Error('Explicit `orderBy` option required');
459
532
  }
460
- const [entities, count] = await em.findAndCount(entityName, where, options);
533
+ const [entities, count] = options.includeCount !== false
534
+ ? await em.findAndCount(entityName, options.where, options)
535
+ : [await em.find(entityName, options.where, options)];
461
536
  return new Cursor(entities, count, options, this.metadata.get(entityName));
462
537
  }
463
538
  /**
@@ -469,9 +544,9 @@ export class EntityManager {
469
544
  const ret = await this.refresh(entity, options);
470
545
  if (!ret) {
471
546
  options.failHandler ??= this.config.get('findOneOrFailHandler');
472
- const entityName = entity.constructor.name;
473
- const where = helper(entity).getPrimaryKey();
474
- throw options.failHandler(entityName, where);
547
+ const wrapped = helper(entity);
548
+ const where = wrapped.getPrimaryKey();
549
+ throw options.failHandler(wrapped.__meta.className, where);
475
550
  }
476
551
  return ret;
477
552
  }
@@ -482,19 +557,31 @@ export class EntityManager {
482
557
  */
483
558
  async refresh(entity, options = {}) {
484
559
  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,
560
+ const wrapped = helper(entity);
561
+ const reloaded = await fork.findOne(wrapped.__meta.class, entity, {
562
+ schema: wrapped.__schema,
488
563
  ...options,
489
564
  flushMode: FlushMode.COMMIT,
490
565
  });
491
- if (reloaded) {
492
- this.config.getHydrator(this.metadata).hydrate(entity, helper(entity).__meta, helper(reloaded).toPOJO(), this.getEntityFactory(), 'full');
566
+ const em = this.getContext();
567
+ if (!reloaded) {
568
+ em.unitOfWork.unsetIdentity(entity);
569
+ return null;
493
570
  }
494
- else {
495
- this.getUnitOfWork().unsetIdentity(entity);
571
+ let found = false;
572
+ for (const e of fork.unitOfWork.getIdentityMap()) {
573
+ const ref = em.getReference(e.constructor, helper(e).getPrimaryKey());
574
+ const data = helper(e).serialize({ ignoreSerializers: true, includeHidden: true, convertCustomTypes: false });
575
+ em.config.getHydrator(this.metadata).hydrate(ref, helper(ref).__meta, data, em.entityFactory, 'full', false, false);
576
+ Utils.merge(helper(ref).__originalEntityData, this.comparator.prepareEntity(e));
577
+ found ||= ref === entity;
578
+ }
579
+ if (!found) {
580
+ const data = helper(reloaded).serialize({ ignoreSerializers: true, includeHidden: true, convertCustomTypes: true });
581
+ em.config.getHydrator(this.metadata).hydrate(entity, wrapped.__meta, data, em.entityFactory, 'full', false, true);
582
+ Utils.merge(wrapped.__originalEntityData, this.comparator.prepareEntity(reloaded));
496
583
  }
497
- return reloaded ? entity : reloaded;
584
+ return entity;
498
585
  }
499
586
  /**
500
587
  * Finds first entity matching your `where` query.
@@ -508,7 +595,6 @@ export class EntityManager {
508
595
  return ret;
509
596
  }
510
597
  const em = this.getContext();
511
- entityName = Utils.className(entityName);
512
598
  em.prepareOptions(options);
513
599
  let entity = em.unitOfWork.tryGetById(entityName, where, options.schema);
514
600
  // query for a not managed entity which is already in the identity map as it
@@ -520,33 +606,35 @@ export class EntityManager {
520
606
  await em.tryFlush(entityName, options);
521
607
  const meta = em.metadata.get(entityName);
522
608
  where = await em.processWhere(entityName, where, options, 'read');
523
- em.validator.validateEmptyWhere(where);
609
+ validateEmptyWhere(where);
524
610
  em.checkLockRequirements(options.lockMode, meta);
525
- const isOptimisticLocking = !Utils.isDefined(options.lockMode) || options.lockMode === LockMode.OPTIMISTIC;
611
+ const isOptimisticLocking = options.lockMode == null || options.lockMode === LockMode.OPTIMISTIC;
526
612
  if (entity && !em.shouldRefresh(meta, entity, options) && isOptimisticLocking) {
527
613
  return em.lockAndPopulate(meta, entity, where, options);
528
614
  }
529
- em.validator.validateParams(where);
615
+ validateParams(where);
530
616
  options.populate = await em.preparePopulate(entityName, options);
531
617
  const cacheKey = em.cacheKey(entityName, options, 'em.findOne', where);
532
618
  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
- });
619
+ if (cached?.data !== undefined) {
620
+ if (cached.data) {
621
+ await em.entityLoader.populate(entityName, [cached.data], options.populate, {
622
+ ...options,
623
+ ...em.getPopulateWhere(where, options),
624
+ ignoreLazyScalarProperties: true,
625
+ lookup: false,
626
+ });
627
+ }
541
628
  return cached.data;
542
629
  }
543
630
  options = { ...options };
544
631
  // save the original hint value so we know it was infer/all
545
632
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
546
633
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
547
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
634
+ options.populateFilter = await this.getJoinedFilters(meta, options);
548
635
  const data = await em.driver.findOne(entityName, where, {
549
636
  ctx: em.transactionContext,
637
+ em,
550
638
  ...options,
551
639
  });
552
640
  if (!data) {
@@ -584,10 +672,10 @@ export class EntityManager {
584
672
  if (!entity || isStrictViolation) {
585
673
  const key = options.strict ? 'findExactlyOneOrFailHandler' : 'findOneOrFailHandler';
586
674
  options.failHandler ??= this.config.get(key);
587
- entityName = Utils.className(entityName);
675
+ const name = Utils.className(entityName);
588
676
  /* v8 ignore next */
589
677
  where = Utils.isEntity(where) ? helper(where).getPrimaryKey() : where;
590
- throw options.failHandler(entityName, where);
678
+ throw options.failHandler(name, where);
591
679
  }
592
680
  return entity;
593
681
  }
@@ -627,11 +715,11 @@ export class EntityManager {
627
715
  let where;
628
716
  let entity = null;
629
717
  if (data === undefined) {
630
- entityName = entityNameOrEntity.constructor.name;
718
+ entityName = entityNameOrEntity.constructor;
631
719
  data = entityNameOrEntity;
632
720
  }
633
721
  else {
634
- entityName = Utils.className(entityNameOrEntity);
722
+ entityName = entityNameOrEntity;
635
723
  }
636
724
  const meta = this.metadata.get(entityName);
637
725
  const convertCustomTypes = !Utils.isEntity(data);
@@ -654,26 +742,9 @@ export class EntityManager {
654
742
  }
655
743
  }
656
744
  }
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
- }
745
+ where = getWhereCondition(meta, options.onConflictFields, data, where).where;
675
746
  data = QueryHelper.processObjectParams(data);
676
- em.validator.validateParams(data, 'insert data');
747
+ validateParams(data, 'insert data');
677
748
  if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
678
749
  await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity: data, em, meta }, meta);
679
750
  }
@@ -711,13 +782,14 @@ export class EntityManager {
711
782
  where[meta.primaryKeys[0]] = ret.insertId;
712
783
  }
713
784
  }
714
- const data2 = await this.driver.findOne(meta.className, where, {
785
+ const data2 = await this.driver.findOne(meta.class, where, {
715
786
  fields: returning,
716
787
  ctx: em.transactionContext,
717
788
  convertCustomTypes: true,
718
789
  connectionType: 'write',
790
+ schema: options.schema,
719
791
  });
720
- em.getHydrator().hydrate(entity, meta, data2, em.entityFactory, 'full');
792
+ em.getHydrator().hydrate(entity, meta, data2, em.entityFactory, 'full', false, true);
721
793
  }
722
794
  // recompute the data as there might be some values missing (e.g. those with db column defaults)
723
795
  const snapshot = this.comparator.prepareEntity(entity);
@@ -765,11 +837,11 @@ export class EntityManager {
765
837
  let entityName;
766
838
  let propIndex;
767
839
  if (data === undefined) {
768
- entityName = entityNameOrEntity[0].constructor.name;
840
+ entityName = entityNameOrEntity[0].constructor;
769
841
  data = entityNameOrEntity;
770
842
  }
771
843
  else {
772
- entityName = Utils.className(entityNameOrEntity);
844
+ entityName = entityNameOrEntity;
773
845
  }
774
846
  const batchSize = options.batchSize ?? this.config.get('batchSize');
775
847
  if (data.length > batchSize) {
@@ -813,32 +885,18 @@ export class EntityManager {
813
885
  }
814
886
  }
815
887
  }
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);
888
+ const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
889
+ propIndex = !isRaw(unique) && unique.findIndex(p => data[p] ?? data[p.substring(0, p.indexOf('.'))] != null);
890
+ const tmp = getWhereCondition(meta, options.onConflictFields, row, where);
891
+ propIndex = tmp.propIndex;
835
892
  where = QueryHelper.processWhere({
836
- where,
893
+ where: tmp.where,
837
894
  entityName,
838
895
  metadata: this.metadata,
839
896
  platform: this.getPlatform(),
840
897
  });
841
- em.validator.validateParams(row, 'insert data');
898
+ row = QueryHelper.processObjectParams(row);
899
+ validateParams(row, 'insert data');
842
900
  allData.push(row);
843
901
  allWhere.push(where);
844
902
  }
@@ -879,7 +937,7 @@ export class EntityManager {
879
937
  const reloadFields = returning.length > 0 && !(this.getPlatform().usesReturningStatement() && res.rows?.length);
880
938
  if (options.onConflictAction === 'ignore' || (!res.rows?.length && loadPK.size > 0) || reloadFields) {
881
939
  const unique = meta.hydrateProps.filter(p => !p.lazy).map(p => p.name);
882
- const add = new Set(propIndex >= 0 ? [unique[propIndex]] : []);
940
+ const add = new Set(propIndex !== false && propIndex >= 0 ? [unique[propIndex]] : []);
883
941
  for (const cond of loadPK.values()) {
884
942
  Utils.keys(cond).forEach(key => add.add(key));
885
943
  }
@@ -891,11 +949,12 @@ export class EntityManager {
891
949
  where.$or[idx][prop] = item[prop];
892
950
  });
893
951
  });
894
- const data2 = await this.driver.find(meta.className, where, {
952
+ const data2 = await this.driver.find(meta.class, where, {
895
953
  fields: returning.concat(...add).concat(...(Array.isArray(uniqueFields) ? uniqueFields : [])),
896
954
  ctx: em.transactionContext,
897
955
  convertCustomTypes: true,
898
956
  connectionType: 'write',
957
+ schema: options.schema,
899
958
  });
900
959
  for (const [entity, cond] of loadPK.entries()) {
901
960
  const row = data2.find(row => {
@@ -907,11 +966,11 @@ export class EntityManager {
907
966
  });
908
967
  return this.comparator.matching(entityName, cond, tmp);
909
968
  });
910
- /* v8 ignore next 3 */
969
+ /* v8 ignore next */
911
970
  if (!row) {
912
971
  throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
913
972
  }
914
- em.getHydrator().hydrate(entity, meta, row, em.entityFactory, 'full');
973
+ em.getHydrator().hydrate(entity, meta, row, em.entityFactory, 'full', false, true);
915
974
  }
916
975
  if (loadPK.size !== data2.length && Array.isArray(uniqueFields)) {
917
976
  for (let i = 0; i < allData.length; i++) {
@@ -930,7 +989,7 @@ export class EntityManager {
930
989
  }, {});
931
990
  return this.comparator.matching(entityName, cond, pk);
932
991
  });
933
- /* v8 ignore next 3 */
992
+ /* v8 ignore next */
934
993
  if (!row) {
935
994
  throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
936
995
  }
@@ -952,45 +1011,37 @@ export class EntityManager {
952
1011
  }
953
1012
  /**
954
1013
  * Runs your callback wrapped inside a database transaction.
1014
+ *
1015
+ * If a transaction is already active, a new savepoint (nested transaction) will be created by default. This behavior
1016
+ * can be controlled via the `propagation` option. Use the provided EntityManager instance for all operations that
1017
+ * should be part of the transaction. You can safely use a global EntityManager instance from a DI container, as this
1018
+ * method automatically creates an async context for the transaction.
1019
+ *
1020
+ * **Concurrency note:** When running multiple transactions concurrently (e.g. in parallel requests or jobs), use the
1021
+ * `clear: true` option. This ensures the callback runs in a clear fork of the EntityManager, providing full isolation
1022
+ * between concurrent transactional handlers. Using `clear: true` is an alternative to forking explicitly and calling
1023
+ * the method on the new fork – it already provides the necessary isolation for safe concurrent usage.
1024
+ *
1025
+ * **Propagation note:** Changes made within a transaction (whether top-level or nested) are always propagated to the
1026
+ * parent context, unless the parent context is a global one. If you want to avoid that, fork the EntityManager first
1027
+ * and then call this method on the fork.
1028
+ *
1029
+ * **Example:**
1030
+ * ```ts
1031
+ * await em.transactional(async (em) => {
1032
+ * const author = new Author('Jon');
1033
+ * em.persist(author);
1034
+ * // flush is called automatically at the end of the callback
1035
+ * });
1036
+ * ```
955
1037
  */
956
1038
  async transactional(cb, options = {}) {
957
1039
  const em = this.getContext(false);
958
1040
  if (this.disableTransactions || em.disableTransactions) {
959
1041
  return cb(em);
960
1042
  }
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
- });
1043
+ const manager = new TransactionManager(this);
1044
+ return manager.handle(cb, options);
994
1045
  }
995
1046
  /**
996
1047
  * Starts new transaction bound to this EntityManager. Use `ctx` parameter to provide the parent when nesting transactions.
@@ -1051,11 +1102,11 @@ export class EntityManager {
1051
1102
  em.prepareOptions(options);
1052
1103
  let entityName;
1053
1104
  if (data === undefined) {
1054
- entityName = entityNameOrEntity.constructor.name;
1105
+ entityName = entityNameOrEntity.constructor;
1055
1106
  data = entityNameOrEntity;
1056
1107
  }
1057
1108
  else {
1058
- entityName = Utils.className(entityNameOrEntity);
1109
+ entityName = entityNameOrEntity;
1059
1110
  }
1060
1111
  if (Utils.isEntity(data)) {
1061
1112
  if (options.schema && helper(data).getSchema() == null) {
@@ -1074,7 +1125,7 @@ export class EntityManager {
1074
1125
  return cs.getPrimaryKey();
1075
1126
  }
1076
1127
  data = QueryHelper.processObjectParams(data);
1077
- em.validator.validateParams(data, 'insert data');
1128
+ validateParams(data, 'insert data');
1078
1129
  const res = await em.driver.nativeInsert(entityName, data, { ctx: em.transactionContext, ...options });
1079
1130
  return res.insertId;
1080
1131
  }
@@ -1086,11 +1137,11 @@ export class EntityManager {
1086
1137
  em.prepareOptions(options);
1087
1138
  let entityName;
1088
1139
  if (data === undefined) {
1089
- entityName = entityNameOrEntities[0].constructor.name;
1140
+ entityName = entityNameOrEntities[0].constructor;
1090
1141
  data = entityNameOrEntities;
1091
1142
  }
1092
1143
  else {
1093
- entityName = Utils.className(entityNameOrEntities);
1144
+ entityName = entityNameOrEntities;
1094
1145
  }
1095
1146
  if (data.length === 0) {
1096
1147
  return [];
@@ -1114,7 +1165,7 @@ export class EntityManager {
1114
1165
  return css.map(cs => cs.getPrimaryKey());
1115
1166
  }
1116
1167
  data = data.map(row => QueryHelper.processObjectParams(row));
1117
- data.forEach(row => em.validator.validateParams(row, 'insert data'));
1168
+ data.forEach(row => validateParams(row, 'insert data'));
1118
1169
  const res = await em.driver.nativeInsertMany(entityName, data, { ctx: em.transactionContext, ...options });
1119
1170
  if (res.insertedIds) {
1120
1171
  return res.insertedIds;
@@ -1127,11 +1178,10 @@ export class EntityManager {
1127
1178
  async nativeUpdate(entityName, where, data, options = {}) {
1128
1179
  const em = this.getContext(false);
1129
1180
  em.prepareOptions(options);
1130
- entityName = Utils.className(entityName);
1131
1181
  data = QueryHelper.processObjectParams(data);
1132
1182
  where = await em.processWhere(entityName, where, { ...options, convertCustomTypes: false }, 'update');
1133
- em.validator.validateParams(data, 'update data');
1134
- em.validator.validateParams(where, 'update condition');
1183
+ validateParams(data, 'update data');
1184
+ validateParams(where, 'update condition');
1135
1185
  const res = await em.driver.nativeUpdate(entityName, where, data, { ctx: em.transactionContext, ...options });
1136
1186
  return res.affectedRows;
1137
1187
  }
@@ -1141,9 +1191,8 @@ export class EntityManager {
1141
1191
  async nativeDelete(entityName, where, options = {}) {
1142
1192
  const em = this.getContext(false);
1143
1193
  em.prepareOptions(options);
1144
- entityName = Utils.className(entityName);
1145
1194
  where = await em.processWhere(entityName, where, options, 'delete');
1146
- em.validator.validateParams(where, 'delete condition');
1195
+ validateParams(where, 'delete condition');
1147
1196
  const res = await em.driver.nativeDelete(entityName, where, { ctx: em.transactionContext, ...options });
1148
1197
  return res.affectedRows;
1149
1198
  }
@@ -1151,18 +1200,19 @@ export class EntityManager {
1151
1200
  * Maps raw database result to an entity and merges it to this EntityManager.
1152
1201
  */
1153
1202
  map(entityName, result, options = {}) {
1154
- entityName = Utils.className(entityName);
1155
1203
  const meta = this.metadata.get(entityName);
1156
1204
  const data = this.driver.mapResult(result, meta);
1157
- Object.keys(data).forEach(k => {
1205
+ for (const k of Object.keys(data)) {
1158
1206
  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);
1207
+ if (prop?.kind === ReferenceKind.SCALAR && SCALAR_TYPES.has(prop.runtimeType) && !prop.customType && (prop.setter || !prop.getter)) {
1208
+ validateProperty(prop, data[k], data);
1161
1209
  }
1162
- });
1210
+ }
1163
1211
  return this.merge(entityName, data, {
1164
1212
  convertCustomTypes: true,
1165
- refresh: true, ...options,
1213
+ refresh: true,
1214
+ validate: false,
1215
+ ...options,
1166
1216
  });
1167
1217
  }
1168
1218
  /**
@@ -1170,22 +1220,22 @@ export class EntityManager {
1170
1220
  * via second parameter. By default, it will return already loaded entities without modifying them.
1171
1221
  */
1172
1222
  merge(entityName, data, options = {}) {
1173
- const em = this.getContext();
1174
1223
  if (Utils.isEntity(entityName)) {
1175
- return em.merge(entityName.constructor.name, entityName, data);
1224
+ return this.merge(entityName.constructor, entityName, data);
1176
1225
  }
1226
+ const em = options.disableContextResolution ? this : this.getContext();
1177
1227
  options.schema ??= em._schema;
1178
- entityName = Utils.className(entityName);
1179
- em.validator.validatePrimaryKey(data, em.metadata.get(entityName));
1228
+ options.validate ??= true;
1229
+ options.cascade ??= true;
1230
+ validatePrimaryKey(data, em.metadata.get(entityName));
1180
1231
  let entity = em.unitOfWork.tryGetById(entityName, data, options.schema, false);
1181
1232
  if (entity && helper(entity).__managed && helper(entity).__initialized && !options.refresh) {
1182
1233
  return entity;
1183
1234
  }
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);
1235
+ const dataIsEntity = Utils.isEntity(data);
1236
+ entity = dataIsEntity ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
1237
+ const visited = options.cascade ? undefined : new Set([entity]);
1238
+ em.unitOfWork.merge(entity, visited);
1189
1239
  return entity;
1190
1240
  }
1191
1241
  /**
@@ -1210,6 +1260,7 @@ export class EntityManager {
1210
1260
  ...options,
1211
1261
  newEntity: !options.managed,
1212
1262
  merge: options.managed,
1263
+ normalizeAccessors: true,
1213
1264
  });
1214
1265
  options.persist ??= em.config.get('persistOnCreate');
1215
1266
  if (options.persist && !this.getMetadata(entityName).embeddable) {
@@ -1229,7 +1280,7 @@ export class EntityManager {
1229
1280
  getReference(entityName, id, options = {}) {
1230
1281
  options.schema ??= this.schema;
1231
1282
  options.convertCustomTypes ??= false;
1232
- const meta = this.metadata.get(Utils.className(entityName));
1283
+ const meta = this.metadata.get(entityName);
1233
1284
  if (Utils.isPrimaryKey(id)) {
1234
1285
  if (meta.compositePK) {
1235
1286
  throw ValidationError.invalidCompositeIdentifier(meta);
@@ -1248,11 +1299,8 @@ export class EntityManager {
1248
1299
  async count(entityName, where = {}, options = {}) {
1249
1300
  const em = this.getContext(false);
1250
1301
  // 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);
1302
+ options = { ...options };
1303
+ em.prepareOptions(options);
1256
1304
  await em.tryFlush(entityName, options);
1257
1305
  where = await em.processWhere(entityName, where, options, 'read');
1258
1306
  options.populate = await em.preparePopulate(entityName, options);
@@ -1261,15 +1309,15 @@ export class EntityManager {
1261
1309
  const meta = em.metadata.find(entityName);
1262
1310
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
1263
1311
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
1264
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
1265
- em.validator.validateParams(where);
1312
+ options.populateFilter = await this.getJoinedFilters(meta, options);
1313
+ validateParams(where);
1266
1314
  delete options.orderBy;
1267
1315
  const cacheKey = em.cacheKey(entityName, options, 'em.count', where);
1268
1316
  const cached = await em.tryCache(entityName, options.cache, cacheKey);
1269
- if (cached?.data) {
1317
+ if (cached?.data !== undefined) {
1270
1318
  return cached.data;
1271
1319
  }
1272
- const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, ...options });
1320
+ const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, em, ...options });
1273
1321
  await em.storeCache(options.cache, cached, () => +count);
1274
1322
  return +count;
1275
1323
  }
@@ -1288,7 +1336,7 @@ export class EntityManager {
1288
1336
  for (const ent of entities) {
1289
1337
  if (!Utils.isEntity(ent, true)) {
1290
1338
  /* v8 ignore next */
1291
- const meta = typeof ent === 'object' ? em.metadata.find(ent.constructor.name) : undefined;
1339
+ const meta = typeof ent === 'object' ? em.metadata.find(ent.constructor) : undefined;
1292
1340
  throw ValidationError.notDiscoveredEntity(ent, meta);
1293
1341
  }
1294
1342
  // do not cascade just yet, cascading of entities in persist stack is done when flushing
@@ -1296,13 +1344,6 @@ export class EntityManager {
1296
1344
  }
1297
1345
  return this;
1298
1346
  }
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
1347
  /**
1307
1348
  * Marks entity for removal.
1308
1349
  * A removed entity will be removed from the database at or before transaction commit or as a result of the flush operation.
@@ -1326,13 +1367,6 @@ export class EntityManager {
1326
1367
  }
1327
1368
  return em;
1328
1369
  }
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
1370
  /**
1337
1371
  * Flushes all changes to objects that have been queued up to now to the database.
1338
1372
  * This effectively synchronizes the in-memory state of managed objects with the database.
@@ -1346,7 +1380,6 @@ export class EntityManager {
1346
1380
  async tryFlush(entityName, options) {
1347
1381
  const em = this.getContext();
1348
1382
  const flushMode = options.flushMode ?? em.flushMode ?? em.config.get('flushMode');
1349
- entityName = Utils.className(entityName);
1350
1383
  const meta = em.metadata.get(entityName);
1351
1384
  if (flushMode === FlushMode.COMMIT) {
1352
1385
  return;
@@ -1365,7 +1398,6 @@ export class EntityManager {
1365
1398
  * Checks whether given property can be populated on the entity.
1366
1399
  */
1367
1400
  canPopulate(entityName, property) {
1368
- entityName = Utils.className(entityName);
1369
1401
  // eslint-disable-next-line prefer-const
1370
1402
  let [p, ...parts] = property.split('.');
1371
1403
  const meta = this.metadata.find(entityName);
@@ -1376,11 +1408,8 @@ export class EntityManager {
1376
1408
  p = p.split(':', 2)[0];
1377
1409
  }
1378
1410
  const ret = p in meta.root.properties;
1379
- if (!ret) {
1380
- return !!this.metadata.find(property)?.pivotTable;
1381
- }
1382
1411
  if (parts.length > 0) {
1383
- return this.canPopulate((meta.root.properties)[p].type, parts.join('.'));
1412
+ return this.canPopulate(meta.root.properties[p].targetMeta.class, parts.join('.'));
1384
1413
  }
1385
1414
  return ret;
1386
1415
  }
@@ -1394,8 +1423,8 @@ export class EntityManager {
1394
1423
  }
1395
1424
  const em = this.getContext();
1396
1425
  em.prepareOptions(options);
1397
- const entityName = arr[0].constructor.name;
1398
- const preparedPopulate = await em.preparePopulate(entityName, { populate: populate }, options.validate);
1426
+ const entityName = arr[0].constructor;
1427
+ const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters }, options.validate);
1399
1428
  await em.entityLoader.populate(entityName, arr, preparedPopulate, options);
1400
1429
  return entities;
1401
1430
  }
@@ -1431,6 +1460,9 @@ export class EntityManager {
1431
1460
  for (const entity of em.unitOfWork.getIdentityMap()) {
1432
1461
  fork.unitOfWork.register(entity);
1433
1462
  }
1463
+ for (const entity of em.unitOfWork.getPersistStack()) {
1464
+ fork.unitOfWork.persist(entity);
1465
+ }
1434
1466
  for (const entity of em.unitOfWork.getOrphanRemoveStack()) {
1435
1467
  fork.unitOfWork.getOrphanRemoveStack().add(entity);
1436
1468
  }
@@ -1452,6 +1484,12 @@ export class EntityManager {
1452
1484
  getEntityFactory() {
1453
1485
  return this.getContext().entityFactory;
1454
1486
  }
1487
+ /**
1488
+ * @internal use `em.populate()` as the user facing API, this is exposed only for internal usage
1489
+ */
1490
+ getEntityLoader() {
1491
+ return this.getContext().entityLoader;
1492
+ }
1455
1493
  /**
1456
1494
  * Gets the Hydrator used by the EntityManager.
1457
1495
  */
@@ -1514,7 +1552,6 @@ export class EntityManager {
1514
1552
  */
1515
1553
  getMetadata(entityName) {
1516
1554
  if (entityName) {
1517
- entityName = Utils.className(entityName);
1518
1555
  return this.metadata.get(entityName);
1519
1556
  }
1520
1557
  return this.metadata;
@@ -1543,12 +1580,11 @@ export class EntityManager {
1543
1580
  lockTableAliases: options.lockTableAliases,
1544
1581
  });
1545
1582
  }
1546
- const preparedPopulate = await this.preparePopulate(meta.className, options);
1547
- await this.entityLoader.populate(meta.className, [entity], preparedPopulate, {
1583
+ const preparedPopulate = await this.preparePopulate(meta.class, options);
1584
+ await this.entityLoader.populate(meta.class, [entity], preparedPopulate, {
1548
1585
  ...options,
1549
1586
  ...this.getPopulateWhere(where, options),
1550
1587
  orderBy: options.populateOrderBy ?? options.orderBy,
1551
- convertCustomTypes: false,
1552
1588
  ignoreLazyScalarProperties: true,
1553
1589
  lookup: false,
1554
1590
  });
@@ -1565,6 +1601,7 @@ export class EntityManager {
1565
1601
  return ret;
1566
1602
  }, []);
1567
1603
  }
1604
+ /** @internal */
1568
1605
  async preparePopulate(entityName, options, validate = true) {
1569
1606
  if (options.populate === false) {
1570
1607
  return [];
@@ -1605,13 +1642,13 @@ export class EntityManager {
1605
1642
  options.populate = pruneToOneRelations(meta, this.buildFields(options.fields));
1606
1643
  }
1607
1644
  if (!options.populate) {
1608
- const populate = this.entityLoader.normalizePopulate(entityName, [], options.strategy);
1645
+ const populate = this.entityLoader.normalizePopulate(entityName, [], options.strategy, true, options.exclude);
1609
1646
  await this.autoJoinRefsForFilters(meta, { ...options, populate });
1610
1647
  return populate;
1611
1648
  }
1612
1649
  if (typeof options.populate !== 'boolean') {
1613
1650
  options.populate = Utils.asArray(options.populate).map(field => {
1614
- /* v8 ignore next 3 */
1651
+ /* v8 ignore next */
1615
1652
  if (typeof field === 'boolean' || field === PopulatePath.ALL) {
1616
1653
  return [{ field: meta.primaryKeys[0], strategy: options.strategy, all: !!field }]; //
1617
1654
  }
@@ -1621,13 +1658,13 @@ export class EntityManager {
1621
1658
  options.flags.push(QueryFlag.INFER_POPULATE);
1622
1659
  return [];
1623
1660
  }
1624
- if (Utils.isString(field)) {
1661
+ if (typeof field === 'string') {
1625
1662
  return [{ field, strategy: options.strategy }];
1626
1663
  }
1627
1664
  return [field];
1628
1665
  }).flat();
1629
1666
  }
1630
- const populate = this.entityLoader.normalizePopulate(entityName, options.populate, options.strategy);
1667
+ const populate = this.entityLoader.normalizePopulate(entityName, options.populate, options.strategy, true, options.exclude);
1631
1668
  const invalid = populate.find(({ field }) => !this.canPopulate(entityName, field));
1632
1669
  if (validate && invalid) {
1633
1670
  throw ValidationError.invalidPropertyName(entityName, invalid.field);
@@ -1658,7 +1695,7 @@ export class EntityManager {
1658
1695
  return !inlineEmbedded && !prop.lazy && !helper(entity).__loadedProperties.has(prop.name);
1659
1696
  });
1660
1697
  }
1661
- if (autoRefresh) {
1698
+ if (autoRefresh || options.filters) {
1662
1699
  return true;
1663
1700
  }
1664
1701
  if (Array.isArray(options.populate)) {
@@ -1671,7 +1708,7 @@ export class EntityManager {
1671
1708
  throw new ValidationError(`Cannot combine 'fields' and 'exclude' option.`);
1672
1709
  }
1673
1710
  options.schema ??= this._schema;
1674
- options.logging = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
1711
+ options.logging = options.loggerContext = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
1675
1712
  }
1676
1713
  /**
1677
1714
  * @internal
@@ -1682,7 +1719,7 @@ export class EntityManager {
1682
1719
  for (const k of ['ctx', 'strategy', 'flushMode', 'logging', 'loggerContext']) {
1683
1720
  delete opts[k];
1684
1721
  }
1685
- return [entityName, method, opts, where];
1722
+ return [Utils.className(entityName), method, opts, where];
1686
1723
  }
1687
1724
  /**
1688
1725
  * @internal
@@ -1695,31 +1732,27 @@ export class EntityManager {
1695
1732
  const em = this.getContext();
1696
1733
  const cacheKey = Array.isArray(config) ? config[0] : JSON.stringify(key);
1697
1734
  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 };
1735
+ if (!cached) {
1736
+ return { key: cacheKey, data: cached };
1737
+ }
1738
+ let data;
1739
+ const createOptions = {
1740
+ merge: true,
1741
+ convertCustomTypes: false,
1742
+ refresh,
1743
+ recomputeSnapshot: true,
1744
+ };
1745
+ if (Array.isArray(cached) && merge) {
1746
+ data = cached.map(item => em.entityFactory.create(entityName, item, createOptions));
1747
+ }
1748
+ else if (Utils.isObject(cached) && merge) {
1749
+ data = em.entityFactory.create(entityName, cached, createOptions);
1721
1750
  }
1722
- return { key: cacheKey };
1751
+ else {
1752
+ data = cached;
1753
+ }
1754
+ await em.unitOfWork.dispatchOnLoadEvent();
1755
+ return { key: cacheKey, data };
1723
1756
  }
1724
1757
  /**
1725
1758
  * @internal
@@ -1728,7 +1761,7 @@ export class EntityManager {
1728
1761
  config ??= this.config.get('resultCache').global;
1729
1762
  if (config) {
1730
1763
  const em = this.getContext();
1731
- const expiration = Array.isArray(config) ? config[1] : (Utils.isNumber(config) ? config : undefined);
1764
+ const expiration = Array.isArray(config) ? config[1] : (typeof config === 'number' ? config : undefined);
1732
1765
  await em.resultCache.set(key.key, data instanceof Function ? data() : data, '', expiration);
1733
1766
  }
1734
1767
  }
@@ -1761,6 +1794,19 @@ export class EntityManager {
1761
1794
  set schema(schema) {
1762
1795
  this.getContext(false)._schema = schema ?? undefined;
1763
1796
  }
1797
+ /** @internal */
1798
+ async getDataLoader(type) {
1799
+ const em = this.getContext();
1800
+ if (em.loaders[type]) {
1801
+ return em.loaders[type];
1802
+ }
1803
+ const DataLoader = await DataloaderUtils.getDataLoader();
1804
+ switch (type) {
1805
+ case 'ref': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getRefBatchLoadFn(em)));
1806
+ case '1:m': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getColBatchLoadFn(em)));
1807
+ case 'm:n': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(em)));
1808
+ }
1809
+ }
1764
1810
  /**
1765
1811
  * Returns the ID of this EntityManager. Respects the context, so global EM will give you the contextual ID
1766
1812
  * if executed inside request context handler.
@@ -1769,7 +1815,7 @@ export class EntityManager {
1769
1815
  return this.getContext(false)._id;
1770
1816
  }
1771
1817
  /** @ignore */
1772
- [inspect.custom]() {
1818
+ [Symbol.for('nodejs.util.inspect.custom')]() {
1773
1819
  return `[EntityManager<${this.id}>]`;
1774
1820
  }
1775
1821
  }