@mikro-orm/core 7.0.0-dev.9 → 7.0.0-dev.91

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 (191) hide show
  1. package/EntityManager.d.ts +77 -48
  2. package/EntityManager.js +288 -225
  3. package/MikroORM.d.ts +40 -31
  4. package/MikroORM.js +98 -137
  5. package/README.md +3 -2
  6. package/cache/FileCacheAdapter.d.ts +1 -1
  7. package/cache/FileCacheAdapter.js +6 -5
  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 +11 -7
  13. package/connections/Connection.js +16 -14
  14. package/drivers/DatabaseDriver.d.ts +11 -5
  15. package/drivers/DatabaseDriver.js +23 -11
  16. package/drivers/IDatabaseDriver.d.ts +25 -4
  17. package/entity/BaseEntity.d.ts +0 -1
  18. package/entity/BaseEntity.js +0 -3
  19. package/entity/Collection.d.ts +95 -30
  20. package/entity/Collection.js +432 -93
  21. package/entity/EntityAssigner.d.ts +1 -1
  22. package/entity/EntityAssigner.js +17 -9
  23. package/entity/EntityFactory.d.ts +7 -0
  24. package/entity/EntityFactory.js +63 -41
  25. package/entity/EntityHelper.js +26 -12
  26. package/entity/EntityLoader.d.ts +5 -4
  27. package/entity/EntityLoader.js +63 -38
  28. package/entity/EntityRepository.d.ts +1 -1
  29. package/entity/Reference.d.ts +6 -5
  30. package/entity/Reference.js +34 -9
  31. package/entity/WrappedEntity.d.ts +2 -7
  32. package/entity/WrappedEntity.js +2 -7
  33. package/entity/defineEntity.d.ts +568 -0
  34. package/entity/defineEntity.js +529 -0
  35. package/entity/index.d.ts +3 -2
  36. package/entity/index.js +3 -2
  37. package/entity/utils.d.ts +7 -0
  38. package/entity/utils.js +16 -4
  39. package/entity/validators.d.ts +11 -0
  40. package/entity/validators.js +65 -0
  41. package/enums.d.ts +21 -6
  42. package/enums.js +14 -1
  43. package/errors.d.ts +10 -2
  44. package/errors.js +29 -10
  45. package/events/EventManager.d.ts +2 -1
  46. package/events/EventManager.js +19 -11
  47. package/events/EventSubscriber.d.ts +3 -1
  48. package/hydration/Hydrator.js +1 -2
  49. package/hydration/ObjectHydrator.d.ts +4 -4
  50. package/hydration/ObjectHydrator.js +35 -25
  51. package/index.d.ts +2 -2
  52. package/index.js +1 -2
  53. package/logging/DefaultLogger.d.ts +1 -1
  54. package/logging/DefaultLogger.js +1 -0
  55. package/logging/SimpleLogger.d.ts +1 -1
  56. package/logging/index.d.ts +1 -0
  57. package/logging/index.js +1 -0
  58. package/logging/inspect.d.ts +2 -0
  59. package/logging/inspect.js +16 -0
  60. package/metadata/EntitySchema.d.ts +9 -13
  61. package/metadata/EntitySchema.js +44 -26
  62. package/metadata/MetadataDiscovery.d.ts +6 -9
  63. package/metadata/MetadataDiscovery.js +165 -205
  64. package/metadata/MetadataProvider.d.ts +11 -2
  65. package/metadata/MetadataProvider.js +44 -2
  66. package/metadata/MetadataStorage.d.ts +1 -6
  67. package/metadata/MetadataStorage.js +6 -18
  68. package/metadata/MetadataValidator.d.ts +0 -7
  69. package/metadata/MetadataValidator.js +0 -10
  70. package/metadata/discover-entities.d.ts +5 -0
  71. package/metadata/discover-entities.js +40 -0
  72. package/metadata/index.d.ts +1 -1
  73. package/metadata/index.js +1 -1
  74. package/metadata/types.d.ts +480 -0
  75. package/metadata/types.js +1 -0
  76. package/naming-strategy/AbstractNamingStrategy.d.ts +5 -1
  77. package/naming-strategy/AbstractNamingStrategy.js +8 -2
  78. package/naming-strategy/NamingStrategy.d.ts +11 -1
  79. package/not-supported.d.ts +2 -0
  80. package/not-supported.js +4 -0
  81. package/package.json +18 -10
  82. package/platforms/ExceptionConverter.js +1 -1
  83. package/platforms/Platform.d.ts +6 -13
  84. package/platforms/Platform.js +15 -41
  85. package/serialization/EntitySerializer.d.ts +2 -0
  86. package/serialization/EntitySerializer.js +32 -14
  87. package/serialization/EntityTransformer.js +22 -12
  88. package/serialization/SerializationContext.js +16 -13
  89. package/types/ArrayType.d.ts +1 -1
  90. package/types/ArrayType.js +2 -3
  91. package/types/BigIntType.d.ts +8 -6
  92. package/types/BigIntType.js +1 -1
  93. package/types/BlobType.d.ts +0 -1
  94. package/types/BlobType.js +0 -3
  95. package/types/BooleanType.d.ts +2 -1
  96. package/types/BooleanType.js +3 -0
  97. package/types/DecimalType.d.ts +6 -4
  98. package/types/DecimalType.js +3 -3
  99. package/types/DoubleType.js +2 -2
  100. package/types/EnumArrayType.js +1 -2
  101. package/types/JsonType.d.ts +1 -1
  102. package/types/JsonType.js +7 -2
  103. package/types/TinyIntType.js +1 -1
  104. package/types/Type.d.ts +2 -4
  105. package/types/Type.js +3 -3
  106. package/types/Uint8ArrayType.d.ts +0 -1
  107. package/types/Uint8ArrayType.js +1 -4
  108. package/types/index.d.ts +1 -1
  109. package/typings.d.ts +109 -73
  110. package/typings.js +38 -35
  111. package/unit-of-work/ChangeSet.d.ts +0 -3
  112. package/unit-of-work/ChangeSet.js +2 -2
  113. package/unit-of-work/ChangeSetComputer.d.ts +1 -3
  114. package/unit-of-work/ChangeSetComputer.js +11 -9
  115. package/unit-of-work/ChangeSetPersister.d.ts +5 -4
  116. package/unit-of-work/ChangeSetPersister.js +51 -19
  117. package/unit-of-work/UnitOfWork.d.ts +8 -1
  118. package/unit-of-work/UnitOfWork.js +91 -49
  119. package/utils/AbstractSchemaGenerator.d.ts +5 -5
  120. package/utils/AbstractSchemaGenerator.js +11 -9
  121. package/utils/Configuration.d.ts +757 -206
  122. package/utils/Configuration.js +140 -188
  123. package/utils/ConfigurationLoader.d.ts +1 -54
  124. package/utils/ConfigurationLoader.js +1 -352
  125. package/utils/Cursor.d.ts +0 -3
  126. package/utils/Cursor.js +6 -3
  127. package/utils/DataloaderUtils.d.ts +15 -5
  128. package/utils/DataloaderUtils.js +54 -8
  129. package/utils/EntityComparator.d.ts +8 -4
  130. package/utils/EntityComparator.js +52 -17
  131. package/utils/QueryHelper.d.ts +9 -1
  132. package/utils/QueryHelper.js +70 -9
  133. package/utils/RawQueryFragment.d.ts +36 -13
  134. package/utils/RawQueryFragment.js +36 -16
  135. package/utils/TransactionManager.d.ts +65 -0
  136. package/utils/TransactionManager.js +223 -0
  137. package/utils/Utils.d.ts +9 -97
  138. package/utils/Utils.js +83 -302
  139. package/utils/clone.js +2 -3
  140. package/utils/env-vars.d.ts +3 -0
  141. package/utils/env-vars.js +87 -0
  142. package/utils/fs-utils.d.ts +12 -0
  143. package/utils/fs-utils.js +97 -0
  144. package/utils/index.d.ts +2 -1
  145. package/utils/index.js +2 -1
  146. package/utils/upsert-utils.d.ts +7 -2
  147. package/utils/upsert-utils.js +55 -4
  148. package/decorators/Check.d.ts +0 -3
  149. package/decorators/Check.js +0 -13
  150. package/decorators/CreateRequestContext.d.ts +0 -3
  151. package/decorators/CreateRequestContext.js +0 -32
  152. package/decorators/Embeddable.d.ts +0 -8
  153. package/decorators/Embeddable.js +0 -11
  154. package/decorators/Embedded.d.ts +0 -18
  155. package/decorators/Embedded.js +0 -18
  156. package/decorators/Entity.d.ts +0 -18
  157. package/decorators/Entity.js +0 -12
  158. package/decorators/Enum.d.ts +0 -9
  159. package/decorators/Enum.js +0 -16
  160. package/decorators/Filter.d.ts +0 -2
  161. package/decorators/Filter.js +0 -8
  162. package/decorators/Formula.d.ts +0 -4
  163. package/decorators/Formula.js +0 -15
  164. package/decorators/Indexed.d.ts +0 -19
  165. package/decorators/Indexed.js +0 -20
  166. package/decorators/ManyToMany.d.ts +0 -40
  167. package/decorators/ManyToMany.js +0 -14
  168. package/decorators/ManyToOne.d.ts +0 -30
  169. package/decorators/ManyToOne.js +0 -14
  170. package/decorators/OneToMany.d.ts +0 -28
  171. package/decorators/OneToMany.js +0 -17
  172. package/decorators/OneToOne.d.ts +0 -24
  173. package/decorators/OneToOne.js +0 -7
  174. package/decorators/PrimaryKey.d.ts +0 -8
  175. package/decorators/PrimaryKey.js +0 -20
  176. package/decorators/Property.d.ts +0 -250
  177. package/decorators/Property.js +0 -32
  178. package/decorators/Transactional.d.ts +0 -13
  179. package/decorators/Transactional.js +0 -28
  180. package/decorators/hooks.d.ts +0 -16
  181. package/decorators/hooks.js +0 -47
  182. package/decorators/index.d.ts +0 -17
  183. package/decorators/index.js +0 -17
  184. package/entity/ArrayCollection.d.ts +0 -116
  185. package/entity/ArrayCollection.js +0 -402
  186. package/entity/EntityValidator.d.ts +0 -19
  187. package/entity/EntityValidator.js +0 -150
  188. package/metadata/ReflectMetadataProvider.d.ts +0 -8
  189. package/metadata/ReflectMetadataProvider.js +0 -44
  190. package/utils/resolveContextProvider.d.ts +0 -10
  191. package/utils/resolveContextProvider.js +0 -28
package/EntityManager.js CHANGED
@@ -1,6 +1,4 @@
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';
@@ -9,7 +7,7 @@ import { TransactionContext } from './utils/TransactionContext.js';
9
7
  import { isRaw, RawQueryFragment } 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,9 +34,7 @@ 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;
37
+ loaders = {};
40
38
  repositoryMap = {};
41
39
  entityLoader;
42
40
  comparator;
@@ -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');
@@ -105,12 +102,6 @@ export class EntityManager {
105
102
  repo(entityName) {
106
103
  return this.getRepository(entityName);
107
104
  }
108
- /**
109
- * Gets EntityValidator instance
110
- */
111
- getValidator() {
112
- return this.validator;
113
- }
114
105
  /**
115
106
  * Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
116
107
  */
@@ -127,7 +118,7 @@ export class EntityManager {
127
118
  await em.tryFlush(entityName, options);
128
119
  entityName = Utils.className(entityName);
129
120
  where = await em.processWhere(entityName, where, options, 'read');
130
- em.validator.validateParams(where);
121
+ validateParams(where);
131
122
  options.orderBy = options.orderBy || {};
132
123
  options.populate = await em.preparePopulate(entityName, options);
133
124
  const populate = options.populate;
@@ -137,7 +128,6 @@ export class EntityManager {
137
128
  await em.entityLoader.populate(entityName, cached.data, populate, {
138
129
  ...options,
139
130
  ...em.getPopulateWhere(where, options),
140
- convertCustomTypes: false,
141
131
  ignoreLazyScalarProperties: true,
142
132
  lookup: false,
143
133
  });
@@ -148,7 +138,7 @@ export class EntityManager {
148
138
  // save the original hint value so we know it was infer/all
149
139
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
150
140
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
151
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
141
+ options.populateFilter = await this.getJoinedFilters(meta, options);
152
142
  const results = await em.driver.find(entityName, where, { ctx: em.transactionContext, em, ...options });
153
143
  if (results.length === 0) {
154
144
  await em.storeCache(options.cache, cached, []);
@@ -168,7 +158,6 @@ export class EntityManager {
168
158
  await em.entityLoader.populate(entityName, unique, populate, {
169
159
  ...options,
170
160
  ...em.getPopulateWhere(where, options),
171
- convertCustomTypes: false,
172
161
  ignoreLazyScalarProperties: true,
173
162
  lookup: false,
174
163
  });
@@ -181,6 +170,61 @@ export class EntityManager {
181
170
  }
182
171
  return unique;
183
172
  }
173
+ /**
174
+ * Finds all entities and returns an async iterable (async generator) that yields results one by one.
175
+ * The results are merged and mapped to entity instances, without adding them to the identity map.
176
+ * You can disable merging by passing the options `{ mergeResults: false }`.
177
+ * With `mergeResults` disabled, to-many collections will contain at most one item, and you will get duplicate
178
+ * root entities when there are multiple items in the populated collection.
179
+ * This is useful for processing large datasets without loading everything into memory at once.
180
+ *
181
+ * ```ts
182
+ * const stream = em.stream(Book, { populate: ['author'] });
183
+ *
184
+ * for await (const book of stream) {
185
+ * // book is an instance of Book entity
186
+ * console.log(book.title, book.author.name);
187
+ * }
188
+ * ```
189
+ */
190
+ async *stream(entityName, options = {}) {
191
+ const em = this.getContext();
192
+ em.prepareOptions(options);
193
+ options.strategy = 'joined';
194
+ await em.tryFlush(entityName, options);
195
+ entityName = Utils.className(entityName);
196
+ const where = await em.processWhere(entityName, options.where ?? {}, options, 'read');
197
+ validateParams(where);
198
+ options.orderBy = options.orderBy || {};
199
+ options.populate = await em.preparePopulate(entityName, options);
200
+ const meta = this.metadata.get(entityName);
201
+ options = { ...options };
202
+ // save the original hint value so we know it was infer/all
203
+ options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
204
+ options.populateWhere = this.createPopulateWhere({ ...where }, options);
205
+ options.populateFilter = await this.getJoinedFilters(meta, options);
206
+ const stream = em.driver.stream(entityName, where, {
207
+ ctx: em.transactionContext,
208
+ mapResults: false,
209
+ ...options,
210
+ });
211
+ for await (const data of stream) {
212
+ const fork = em.fork();
213
+ const entity = fork.entityFactory.create(entityName, data, {
214
+ refresh: options.refresh,
215
+ schema: options.schema,
216
+ convertCustomTypes: true,
217
+ });
218
+ helper(entity).setSerializationContext({
219
+ populate: options.populate,
220
+ fields: options.fields,
221
+ exclude: options.exclude,
222
+ });
223
+ await fork.unitOfWork.dispatchOnLoadEvent();
224
+ fork.clear();
225
+ yield entity;
226
+ }
227
+ }
184
228
  /**
185
229
  * Finds all entities of given type, optionally matching the `where` condition provided in the `options` parameter.
186
230
  */
@@ -194,7 +238,7 @@ export class EntityManager {
194
238
  if (options.populateWhere === PopulateHint.ALL) {
195
239
  return { where: {}, populateWhere: options.populateWhere };
196
240
  }
197
- /* v8 ignore next 3 */
241
+ /* v8 ignore next */
198
242
  if (options.populateWhere === PopulateHint.INFER) {
199
243
  return { where, populateWhere: options.populateWhere };
200
244
  }
@@ -203,12 +247,12 @@ export class EntityManager {
203
247
  /**
204
248
  * Registers global filter to this entity manager. Global filters are enabled by default (unless disabled via last parameter).
205
249
  */
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));
250
+ addFilter(options) {
251
+ if (options.entity) {
252
+ options.entity = Utils.asArray(options.entity).map(n => Utils.className(n));
210
253
  }
211
- this.getContext(false).filters[name] = options;
254
+ options.default ??= true;
255
+ this.getContext(false).filters[options.name] = options;
212
256
  }
213
257
  /**
214
258
  * Sets filter parameter values globally inside context defined by this entity manager.
@@ -232,8 +276,8 @@ export class EntityManager {
232
276
  /**
233
277
  * Gets logger context for this entity manager.
234
278
  */
235
- getLoggerContext() {
236
- const em = this.getContext();
279
+ getLoggerContext(options) {
280
+ const em = options?.disableContextResolution ? this : this.getContext();
237
281
  em.loggerContext ??= {};
238
282
  return em.loggerContext;
239
283
  }
@@ -283,28 +327,39 @@ export class EntityManager {
283
327
  }
284
328
  return ret;
285
329
  }
286
- async getJoinedFilters(meta, cond, options) {
330
+ async getJoinedFilters(meta, options) {
331
+ if (!this.config.get('filtersOnRelations') || !options.populate) {
332
+ return undefined;
333
+ }
287
334
  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;
335
+ for (const hint of options.populate) {
336
+ const field = hint.field.split(':')[0];
337
+ const prop = meta.properties[field];
338
+ const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
339
+ const joined = strategy === LoadStrategy.JOINED && prop.kind !== ReferenceKind.SCALAR;
340
+ if (!joined && !hint.filter) {
341
+ continue;
342
+ }
343
+ const filters = QueryHelper.mergePropertyFilters(prop.filters, options.filters);
344
+ const where = await this.applyFilters(prop.type, {}, filters, 'read', {
345
+ ...options,
346
+ populate: hint.children,
347
+ });
348
+ const where2 = await this.getJoinedFilters(prop.targetMeta, {
349
+ ...options,
350
+ filters,
351
+ populate: hint.children,
352
+ populateWhere: PopulateHint.ALL,
353
+ });
354
+ if (Utils.hasObjectKeys(where)) {
355
+ ret[field] = ret[field] ? { $and: [where, ret[field]] } : where;
356
+ }
357
+ if (where2 && Utils.hasObjectKeys(where2)) {
358
+ if (ret[field]) {
359
+ Utils.merge(ret[field], where2);
300
360
  }
301
- if (Utils.hasObjectKeys(where2)) {
302
- if (ret[field]) {
303
- Utils.merge(ret[field], where2);
304
- }
305
- else {
306
- ret[field] = where2;
307
- }
361
+ else {
362
+ ret[field] = where2;
308
363
  }
309
364
  }
310
365
  }
@@ -313,27 +368,45 @@ export class EntityManager {
313
368
  /**
314
369
  * 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
370
  */
316
- async autoJoinRefsForFilters(meta, options) {
317
- if (!meta || !this.config.get('autoJoinRefsForFilters')) {
371
+ async autoJoinRefsForFilters(meta, options, parent) {
372
+ if (!meta || !this.config.get('autoJoinRefsForFilters') || options.filters === false) {
318
373
  return;
319
374
  }
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
375
  const ret = options.populate;
325
- for (const prop of props) {
326
- const cond = await this.applyFilters(prop.type, {}, options.filters ?? {}, 'read', options);
376
+ for (const prop of meta.relations) {
377
+ if (prop.object
378
+ || ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)
379
+ || !((options.fields?.length ?? 0) === 0 || options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
380
+ || (parent?.className === prop.targetMeta.root.className && parent.propName === prop.inversedBy)) {
381
+ continue;
382
+ }
383
+ options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
384
+ const cond = await this.applyFilters(prop.type, {}, options.filters, 'read', options);
327
385
  if (!Utils.isEmpty(cond)) {
328
386
  const populated = options.populate.filter(({ field }) => field.split(':')[0] === prop.name);
329
- if (populated.length > 0) {
330
- populated.forEach(hint => hint.filter = true);
387
+ let found = false;
388
+ for (const hint of populated) {
389
+ if (!hint.all) {
390
+ hint.filter = true;
391
+ }
392
+ const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
393
+ if (hint.field === `${prop.name}:ref` || (hint.filter && strategy === LoadStrategy.JOINED)) {
394
+ found = true;
395
+ }
331
396
  }
332
- else {
397
+ if (!found) {
333
398
  ret.push({ field: `${prop.name}:ref`, strategy: LoadStrategy.JOINED, filter: true });
334
399
  }
335
400
  }
336
401
  }
402
+ for (const hint of ret) {
403
+ const [field, ref] = hint.field.split(':');
404
+ const prop = meta?.properties[field];
405
+ if (prop && !ref) {
406
+ hint.children ??= [];
407
+ await this.autoJoinRefsForFilters(prop.targetMeta, { ...options, populate: hint.children }, { className: meta.root.className, propName: prop.name });
408
+ }
409
+ }
337
410
  }
338
411
  /**
339
412
  * @internal
@@ -363,7 +436,7 @@ export class EntityManager {
363
436
  let cond;
364
437
  if (filter.cond instanceof Function) {
365
438
  // @ts-ignore
366
- const args = Utils.isPlainObject(options[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
439
+ const args = Utils.isPlainObject(options?.[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
367
440
  if (!args && filter.cond.length > 0 && filter.args !== false) {
368
441
  throw new Error(`No arguments provided for filter '${filter.name}'`);
369
442
  }
@@ -372,13 +445,17 @@ export class EntityManager {
372
445
  else {
373
446
  cond = filter.cond;
374
447
  }
375
- ret.push(QueryHelper.processWhere({
448
+ cond = QueryHelper.processWhere({
376
449
  where: cond,
377
450
  entityName,
378
451
  metadata: this.metadata,
379
452
  platform: this.driver.getPlatform(),
380
453
  aliased: type === 'read',
381
- }));
454
+ });
455
+ if (filter.strict) {
456
+ Object.defineProperty(cond, '__strict', { value: filter.strict, enumerable: false });
457
+ }
458
+ ret.push(cond);
382
459
  }
383
460
  const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c));
384
461
  return conds.length > 1 ? { $and: conds } : conds[0];
@@ -489,18 +566,31 @@ export class EntityManager {
489
566
  async refresh(entity, options = {}) {
490
567
  const fork = this.fork({ keepTransactionContext: true });
491
568
  const entityName = entity.constructor.name;
569
+ const wrapped = helper(entity);
492
570
  const reloaded = await fork.findOne(entityName, entity, {
493
- schema: helper(entity).__schema,
571
+ schema: wrapped.__schema,
494
572
  ...options,
495
573
  flushMode: FlushMode.COMMIT,
496
574
  });
497
- if (reloaded) {
498
- this.config.getHydrator(this.metadata).hydrate(entity, helper(entity).__meta, helper(reloaded).toPOJO(), this.getEntityFactory(), 'full');
575
+ const em = this.getContext();
576
+ if (!reloaded) {
577
+ em.unitOfWork.unsetIdentity(entity);
578
+ return null;
499
579
  }
500
- else {
501
- this.getUnitOfWork().unsetIdentity(entity);
580
+ let found = false;
581
+ for (const e of fork.unitOfWork.getIdentityMap()) {
582
+ const ref = em.getReference(e.constructor.name, helper(e).getPrimaryKey());
583
+ const data = helper(e).serialize({ ignoreSerializers: true, includeHidden: true });
584
+ em.config.getHydrator(this.metadata).hydrate(ref, helper(ref).__meta, data, em.entityFactory, 'full', false, true);
585
+ Utils.merge(helper(ref).__originalEntityData, this.comparator.prepareEntity(e));
586
+ found ||= ref === entity;
502
587
  }
503
- return reloaded ? entity : reloaded;
588
+ if (!found) {
589
+ const data = helper(reloaded).serialize({ ignoreSerializers: true, includeHidden: true });
590
+ em.config.getHydrator(this.metadata).hydrate(entity, wrapped.__meta, data, em.entityFactory, 'full', false, true);
591
+ Utils.merge(wrapped.__originalEntityData, this.comparator.prepareEntity(reloaded));
592
+ }
593
+ return entity;
504
594
  }
505
595
  /**
506
596
  * Finds first entity matching your `where` query.
@@ -526,31 +616,32 @@ export class EntityManager {
526
616
  await em.tryFlush(entityName, options);
527
617
  const meta = em.metadata.get(entityName);
528
618
  where = await em.processWhere(entityName, where, options, 'read');
529
- em.validator.validateEmptyWhere(where);
619
+ validateEmptyWhere(where);
530
620
  em.checkLockRequirements(options.lockMode, meta);
531
- const isOptimisticLocking = !Utils.isDefined(options.lockMode) || options.lockMode === LockMode.OPTIMISTIC;
621
+ const isOptimisticLocking = options.lockMode == null || options.lockMode === LockMode.OPTIMISTIC;
532
622
  if (entity && !em.shouldRefresh(meta, entity, options) && isOptimisticLocking) {
533
623
  return em.lockAndPopulate(meta, entity, where, options);
534
624
  }
535
- em.validator.validateParams(where);
625
+ validateParams(where);
536
626
  options.populate = await em.preparePopulate(entityName, options);
537
627
  const cacheKey = em.cacheKey(entityName, options, 'em.findOne', where);
538
628
  const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true);
539
- if (cached?.data) {
540
- await em.entityLoader.populate(entityName, [cached.data], options.populate, {
541
- ...options,
542
- ...em.getPopulateWhere(where, options),
543
- convertCustomTypes: false,
544
- ignoreLazyScalarProperties: true,
545
- lookup: false,
546
- });
629
+ if (cached?.data !== undefined) {
630
+ if (cached.data) {
631
+ await em.entityLoader.populate(entityName, [cached.data], options.populate, {
632
+ ...options,
633
+ ...em.getPopulateWhere(where, options),
634
+ ignoreLazyScalarProperties: true,
635
+ lookup: false,
636
+ });
637
+ }
547
638
  return cached.data;
548
639
  }
549
640
  options = { ...options };
550
641
  // save the original hint value so we know it was infer/all
551
642
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
552
643
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
553
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
644
+ options.populateFilter = await this.getJoinedFilters(meta, options);
554
645
  const data = await em.driver.findOne(entityName, where, {
555
646
  ctx: em.transactionContext,
556
647
  em,
@@ -661,26 +752,9 @@ export class EntityManager {
661
752
  }
662
753
  }
663
754
  }
664
- const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
665
- const propIndex = !isRaw(unique) && unique.findIndex(p => data[p] != null);
666
- if (options.onConflictFields || where == null) {
667
- if (propIndex !== false && propIndex >= 0) {
668
- where = { [unique[propIndex]]: data[unique[propIndex]] };
669
- }
670
- else if (meta.uniques.length > 0) {
671
- for (const u of meta.uniques) {
672
- if (Utils.asArray(u.properties).every(p => data[p] != null)) {
673
- where = Utils.asArray(u.properties).reduce((o, key) => {
674
- o[key] = data[key];
675
- return o;
676
- }, {});
677
- break;
678
- }
679
- }
680
- }
681
- }
755
+ where = getWhereCondition(meta, options.onConflictFields, data, where).where;
682
756
  data = QueryHelper.processObjectParams(data);
683
- em.validator.validateParams(data, 'insert data');
757
+ validateParams(data, 'insert data');
684
758
  if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
685
759
  await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity: data, em, meta }, meta);
686
760
  }
@@ -723,6 +797,7 @@ export class EntityManager {
723
797
  ctx: em.transactionContext,
724
798
  convertCustomTypes: true,
725
799
  connectionType: 'write',
800
+ schema: options.schema,
726
801
  });
727
802
  em.getHydrator().hydrate(entity, meta, data2, em.entityFactory, 'full', false, true);
728
803
  }
@@ -820,32 +895,18 @@ export class EntityManager {
820
895
  }
821
896
  }
822
897
  }
823
- const unique = meta.props.filter(p => p.unique).map(p => p.name);
824
- propIndex = unique.findIndex(p => row[p] != null);
825
- if (options.onConflictFields || where == null) {
826
- if (propIndex >= 0) {
827
- where = { [unique[propIndex]]: row[unique[propIndex]] };
828
- }
829
- else if (meta.uniques.length > 0) {
830
- for (const u of meta.uniques) {
831
- if (Utils.asArray(u.properties).every(p => row[p] != null)) {
832
- where = Utils.asArray(u.properties).reduce((o, key) => {
833
- o[key] = row[key];
834
- return o;
835
- }, {});
836
- break;
837
- }
838
- }
839
- }
840
- }
841
- row = QueryHelper.processObjectParams(row);
898
+ const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
899
+ propIndex = !isRaw(unique) && unique.findIndex(p => data[p] ?? data[p.substring(0, p.indexOf('.'))] != null);
900
+ const tmp = getWhereCondition(meta, options.onConflictFields, row, where);
901
+ propIndex = tmp.propIndex;
842
902
  where = QueryHelper.processWhere({
843
- where,
903
+ where: tmp.where,
844
904
  entityName,
845
905
  metadata: this.metadata,
846
906
  platform: this.getPlatform(),
847
907
  });
848
- em.validator.validateParams(row, 'insert data');
908
+ row = QueryHelper.processObjectParams(row);
909
+ validateParams(row, 'insert data');
849
910
  allData.push(row);
850
911
  allWhere.push(where);
851
912
  }
@@ -886,7 +947,7 @@ export class EntityManager {
886
947
  const reloadFields = returning.length > 0 && !(this.getPlatform().usesReturningStatement() && res.rows?.length);
887
948
  if (options.onConflictAction === 'ignore' || (!res.rows?.length && loadPK.size > 0) || reloadFields) {
888
949
  const unique = meta.hydrateProps.filter(p => !p.lazy).map(p => p.name);
889
- const add = new Set(propIndex >= 0 ? [unique[propIndex]] : []);
950
+ const add = new Set(propIndex !== false && propIndex >= 0 ? [unique[propIndex]] : []);
890
951
  for (const cond of loadPK.values()) {
891
952
  Utils.keys(cond).forEach(key => add.add(key));
892
953
  }
@@ -903,6 +964,7 @@ export class EntityManager {
903
964
  ctx: em.transactionContext,
904
965
  convertCustomTypes: true,
905
966
  connectionType: 'write',
967
+ schema: options.schema,
906
968
  });
907
969
  for (const [entity, cond] of loadPK.entries()) {
908
970
  const row = data2.find(row => {
@@ -914,7 +976,7 @@ export class EntityManager {
914
976
  });
915
977
  return this.comparator.matching(entityName, cond, tmp);
916
978
  });
917
- /* v8 ignore next 3 */
979
+ /* v8 ignore next */
918
980
  if (!row) {
919
981
  throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
920
982
  }
@@ -937,7 +999,7 @@ export class EntityManager {
937
999
  }, {});
938
1000
  return this.comparator.matching(entityName, cond, pk);
939
1001
  });
940
- /* v8 ignore next 3 */
1002
+ /* v8 ignore next */
941
1003
  if (!row) {
942
1004
  throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
943
1005
  }
@@ -959,45 +1021,37 @@ export class EntityManager {
959
1021
  }
960
1022
  /**
961
1023
  * Runs your callback wrapped inside a database transaction.
1024
+ *
1025
+ * If a transaction is already active, a new savepoint (nested transaction) will be created by default. This behavior
1026
+ * can be controlled via the `propagation` option. Use the provided EntityManager instance for all operations that
1027
+ * should be part of the transaction. You can safely use a global EntityManager instance from a DI container, as this
1028
+ * method automatically creates an async context for the transaction.
1029
+ *
1030
+ * **Concurrency note:** When running multiple transactions concurrently (e.g. in parallel requests or jobs), use the
1031
+ * `clear: true` option. This ensures the callback runs in a clear fork of the EntityManager, providing full isolation
1032
+ * between concurrent transactional handlers. Using `clear: true` is an alternative to forking explicitly and calling
1033
+ * the method on the new fork – it already provides the necessary isolation for safe concurrent usage.
1034
+ *
1035
+ * **Propagation note:** Changes made within a transaction (whether top-level or nested) are always propagated to the
1036
+ * parent context, unless the parent context is a global one. If you want to avoid that, fork the EntityManager first
1037
+ * and then call this method on the fork.
1038
+ *
1039
+ * **Example:**
1040
+ * ```ts
1041
+ * await em.transactional(async (em) => {
1042
+ * const author = new Author('Jon');
1043
+ * em.persist(author);
1044
+ * // flush is called automatically at the end of the callback
1045
+ * });
1046
+ * ```
962
1047
  */
963
1048
  async transactional(cb, options = {}) {
964
1049
  const em = this.getContext(false);
965
1050
  if (this.disableTransactions || em.disableTransactions) {
966
1051
  return cb(em);
967
1052
  }
968
- const fork = em.fork({
969
- clear: options.clear ?? false, // state will be merged once resolves
970
- flushMode: options.flushMode,
971
- cloneEventManager: true,
972
- disableTransactions: options.ignoreNestedTransactions,
973
- loggerContext: options.loggerContext,
974
- });
975
- options.ctx ??= em.transactionContext;
976
- const propagateToUpperContext = !em.global || this.config.get('allowGlobalContext');
977
- return TransactionContext.create(fork, async () => {
978
- return fork.getConnection().transactional(async (trx) => {
979
- fork.transactionContext = trx;
980
- if (propagateToUpperContext) {
981
- fork.eventManager.registerSubscriber({
982
- afterFlush(args) {
983
- args.uow.getChangeSets()
984
- .filter(cs => [ChangeSetType.DELETE, ChangeSetType.DELETE_EARLY].includes(cs.type))
985
- .forEach(cs => em.unitOfWork.unsetIdentity(cs.entity));
986
- },
987
- });
988
- }
989
- const ret = await cb(fork);
990
- await fork.flush();
991
- if (propagateToUpperContext) {
992
- // ensure all entities from inner context are merged to the upper one
993
- for (const entity of fork.unitOfWork.getIdentityMap()) {
994
- em.unitOfWork.register(entity);
995
- entity.__helper.__em = em;
996
- }
997
- }
998
- return ret;
999
- }, { ...options, eventBroadcaster: new TransactionEventBroadcaster(fork, { topLevelTransaction: !options.ctx }) });
1000
- });
1053
+ const manager = new TransactionManager(this);
1054
+ return manager.handle(cb, options);
1001
1055
  }
1002
1056
  /**
1003
1057
  * Starts new transaction bound to this EntityManager. Use `ctx` parameter to provide the parent when nesting transactions.
@@ -1081,7 +1135,7 @@ export class EntityManager {
1081
1135
  return cs.getPrimaryKey();
1082
1136
  }
1083
1137
  data = QueryHelper.processObjectParams(data);
1084
- em.validator.validateParams(data, 'insert data');
1138
+ validateParams(data, 'insert data');
1085
1139
  const res = await em.driver.nativeInsert(entityName, data, { ctx: em.transactionContext, ...options });
1086
1140
  return res.insertId;
1087
1141
  }
@@ -1121,7 +1175,7 @@ export class EntityManager {
1121
1175
  return css.map(cs => cs.getPrimaryKey());
1122
1176
  }
1123
1177
  data = data.map(row => QueryHelper.processObjectParams(row));
1124
- data.forEach(row => em.validator.validateParams(row, 'insert data'));
1178
+ data.forEach(row => validateParams(row, 'insert data'));
1125
1179
  const res = await em.driver.nativeInsertMany(entityName, data, { ctx: em.transactionContext, ...options });
1126
1180
  if (res.insertedIds) {
1127
1181
  return res.insertedIds;
@@ -1137,8 +1191,8 @@ export class EntityManager {
1137
1191
  entityName = Utils.className(entityName);
1138
1192
  data = QueryHelper.processObjectParams(data);
1139
1193
  where = await em.processWhere(entityName, where, { ...options, convertCustomTypes: false }, 'update');
1140
- em.validator.validateParams(data, 'update data');
1141
- em.validator.validateParams(where, 'update condition');
1194
+ validateParams(data, 'update data');
1195
+ validateParams(where, 'update condition');
1142
1196
  const res = await em.driver.nativeUpdate(entityName, where, data, { ctx: em.transactionContext, ...options });
1143
1197
  return res.affectedRows;
1144
1198
  }
@@ -1150,7 +1204,7 @@ export class EntityManager {
1150
1204
  em.prepareOptions(options);
1151
1205
  entityName = Utils.className(entityName);
1152
1206
  where = await em.processWhere(entityName, where, options, 'delete');
1153
- em.validator.validateParams(where, 'delete condition');
1207
+ validateParams(where, 'delete condition');
1154
1208
  const res = await em.driver.nativeDelete(entityName, where, { ctx: em.transactionContext, ...options });
1155
1209
  return res.affectedRows;
1156
1210
  }
@@ -1161,15 +1215,17 @@ export class EntityManager {
1161
1215
  entityName = Utils.className(entityName);
1162
1216
  const meta = this.metadata.get(entityName);
1163
1217
  const data = this.driver.mapResult(result, meta);
1164
- Object.keys(data).forEach(k => {
1218
+ for (const k of Object.keys(data)) {
1165
1219
  const prop = meta.properties[k];
1166
- if (prop && prop.kind === ReferenceKind.SCALAR && SCALAR_TYPES.includes(prop.runtimeType) && !prop.customType && (prop.setter || !prop.getter)) {
1167
- data[k] = this.validator.validateProperty(prop, data[k], data);
1220
+ if (prop?.kind === ReferenceKind.SCALAR && SCALAR_TYPES.has(prop.runtimeType) && !prop.customType && (prop.setter || !prop.getter)) {
1221
+ validateProperty(prop, data[k], data);
1168
1222
  }
1169
- });
1223
+ }
1170
1224
  return this.merge(entityName, data, {
1171
1225
  convertCustomTypes: true,
1172
- refresh: true, ...options,
1226
+ refresh: true,
1227
+ validate: false,
1228
+ ...options,
1173
1229
  });
1174
1230
  }
1175
1231
  /**
@@ -1177,22 +1233,23 @@ export class EntityManager {
1177
1233
  * via second parameter. By default, it will return already loaded entities without modifying them.
1178
1234
  */
1179
1235
  merge(entityName, data, options = {}) {
1180
- const em = this.getContext();
1181
1236
  if (Utils.isEntity(entityName)) {
1182
- return em.merge(entityName.constructor.name, entityName, data);
1237
+ return this.merge(entityName.constructor.name, entityName, data);
1183
1238
  }
1239
+ const em = options.disableContextResolution ? this : this.getContext();
1184
1240
  options.schema ??= em._schema;
1241
+ options.validate ??= true;
1242
+ options.cascade ??= true;
1185
1243
  entityName = Utils.className(entityName);
1186
- em.validator.validatePrimaryKey(data, em.metadata.get(entityName));
1244
+ validatePrimaryKey(data, em.metadata.get(entityName));
1187
1245
  let entity = em.unitOfWork.tryGetById(entityName, data, options.schema, false);
1188
1246
  if (entity && helper(entity).__managed && helper(entity).__initialized && !options.refresh) {
1189
1247
  return entity;
1190
1248
  }
1191
- const meta = em.metadata.find(entityName);
1192
- const childMeta = em.metadata.getByDiscriminatorColumn(meta, data);
1193
- entity = Utils.isEntity(data) ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
1194
- em.validator.validate(entity, data, childMeta ?? meta);
1195
- em.unitOfWork.merge(entity);
1249
+ const dataIsEntity = Utils.isEntity(data);
1250
+ entity = dataIsEntity ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
1251
+ const visited = options.cascade ? undefined : new Set([entity]);
1252
+ em.unitOfWork.merge(entity, visited);
1196
1253
  return entity;
1197
1254
  }
1198
1255
  /**
@@ -1217,6 +1274,7 @@ export class EntityManager {
1217
1274
  ...options,
1218
1275
  newEntity: !options.managed,
1219
1276
  merge: options.managed,
1277
+ normalizeAccessors: true,
1220
1278
  });
1221
1279
  options.persist ??= em.config.get('persistOnCreate');
1222
1280
  if (options.persist && !this.getMetadata(entityName).embeddable) {
@@ -1255,10 +1313,8 @@ export class EntityManager {
1255
1313
  async count(entityName, where = {}, options = {}) {
1256
1314
  const em = this.getContext(false);
1257
1315
  // Shallow copy options since the object will be modified when deleting orderBy
1258
- options = {
1259
- schema: em._schema,
1260
- ...options,
1261
- };
1316
+ options = { ...options };
1317
+ em.prepareOptions(options);
1262
1318
  entityName = Utils.className(entityName);
1263
1319
  await em.tryFlush(entityName, options);
1264
1320
  where = await em.processWhere(entityName, where, options, 'read');
@@ -1268,15 +1324,15 @@ export class EntityManager {
1268
1324
  const meta = em.metadata.find(entityName);
1269
1325
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
1270
1326
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
1271
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
1272
- em.validator.validateParams(where);
1327
+ options.populateFilter = await this.getJoinedFilters(meta, options);
1328
+ validateParams(where);
1273
1329
  delete options.orderBy;
1274
1330
  const cacheKey = em.cacheKey(entityName, options, 'em.count', where);
1275
1331
  const cached = await em.tryCache(entityName, options.cache, cacheKey);
1276
- if (cached?.data) {
1332
+ if (cached?.data !== undefined) {
1277
1333
  return cached.data;
1278
1334
  }
1279
- const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, ...options });
1335
+ const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, em, ...options });
1280
1336
  await em.storeCache(options.cache, cached, () => +count);
1281
1337
  return +count;
1282
1338
  }
@@ -1303,13 +1359,6 @@ export class EntityManager {
1303
1359
  }
1304
1360
  return this;
1305
1361
  }
1306
- /**
1307
- * Persists your entity immediately, flushing all not yet persisted changes to the database too.
1308
- * Equivalent to `em.persist(e).flush()`.
1309
- */
1310
- async persistAndFlush(entity) {
1311
- await this.persist(entity).flush();
1312
- }
1313
1362
  /**
1314
1363
  * Marks entity for removal.
1315
1364
  * A removed entity will be removed from the database at or before transaction commit or as a result of the flush operation.
@@ -1333,13 +1382,6 @@ export class EntityManager {
1333
1382
  }
1334
1383
  return em;
1335
1384
  }
1336
- /**
1337
- * Removes an entity instance immediately, flushing all not yet persisted changes to the database too.
1338
- * Equivalent to `em.remove(e).flush()`
1339
- */
1340
- async removeAndFlush(entity) {
1341
- await this.remove(entity).flush();
1342
- }
1343
1385
  /**
1344
1386
  * Flushes all changes to objects that have been queued up to now to the database.
1345
1387
  * This effectively synchronizes the in-memory state of managed objects with the database.
@@ -1402,7 +1444,7 @@ export class EntityManager {
1402
1444
  const em = this.getContext();
1403
1445
  em.prepareOptions(options);
1404
1446
  const entityName = arr[0].constructor.name;
1405
- const preparedPopulate = await em.preparePopulate(entityName, { populate: populate }, options.validate);
1447
+ const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters }, options.validate);
1406
1448
  await em.entityLoader.populate(entityName, arr, preparedPopulate, options);
1407
1449
  return entities;
1408
1450
  }
@@ -1438,6 +1480,9 @@ export class EntityManager {
1438
1480
  for (const entity of em.unitOfWork.getIdentityMap()) {
1439
1481
  fork.unitOfWork.register(entity);
1440
1482
  }
1483
+ for (const entity of em.unitOfWork.getPersistStack()) {
1484
+ fork.unitOfWork.persist(entity);
1485
+ }
1441
1486
  for (const entity of em.unitOfWork.getOrphanRemoveStack()) {
1442
1487
  fork.unitOfWork.getOrphanRemoveStack().add(entity);
1443
1488
  }
@@ -1459,6 +1504,12 @@ export class EntityManager {
1459
1504
  getEntityFactory() {
1460
1505
  return this.getContext().entityFactory;
1461
1506
  }
1507
+ /**
1508
+ * @internal use `em.populate()` as the user facing API, this is exposed only for internal usage
1509
+ */
1510
+ getEntityLoader() {
1511
+ return this.getContext().entityLoader;
1512
+ }
1462
1513
  /**
1463
1514
  * Gets the Hydrator used by the EntityManager.
1464
1515
  */
@@ -1555,7 +1606,6 @@ export class EntityManager {
1555
1606
  ...options,
1556
1607
  ...this.getPopulateWhere(where, options),
1557
1608
  orderBy: options.populateOrderBy ?? options.orderBy,
1558
- convertCustomTypes: false,
1559
1609
  ignoreLazyScalarProperties: true,
1560
1610
  lookup: false,
1561
1611
  });
@@ -1618,7 +1668,7 @@ export class EntityManager {
1618
1668
  }
1619
1669
  if (typeof options.populate !== 'boolean') {
1620
1670
  options.populate = Utils.asArray(options.populate).map(field => {
1621
- /* v8 ignore next 3 */
1671
+ /* v8 ignore next */
1622
1672
  if (typeof field === 'boolean' || field === PopulatePath.ALL) {
1623
1673
  return [{ field: meta.primaryKeys[0], strategy: options.strategy, all: !!field }]; //
1624
1674
  }
@@ -1628,7 +1678,7 @@ export class EntityManager {
1628
1678
  options.flags.push(QueryFlag.INFER_POPULATE);
1629
1679
  return [];
1630
1680
  }
1631
- if (Utils.isString(field)) {
1681
+ if (typeof field === 'string') {
1632
1682
  return [{ field, strategy: options.strategy }];
1633
1683
  }
1634
1684
  return [field];
@@ -1678,7 +1728,7 @@ export class EntityManager {
1678
1728
  throw new ValidationError(`Cannot combine 'fields' and 'exclude' option.`);
1679
1729
  }
1680
1730
  options.schema ??= this._schema;
1681
- options.logging = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
1731
+ options.logging = options.loggerContext = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
1682
1732
  }
1683
1733
  /**
1684
1734
  * @internal
@@ -1702,31 +1752,31 @@ export class EntityManager {
1702
1752
  const em = this.getContext();
1703
1753
  const cacheKey = Array.isArray(config) ? config[0] : JSON.stringify(key);
1704
1754
  const cached = await em.resultCache.get(cacheKey);
1705
- if (cached) {
1706
- let data;
1707
- if (Array.isArray(cached) && merge) {
1708
- data = cached.map(item => em.entityFactory.create(entityName, item, {
1709
- merge: true,
1710
- convertCustomTypes: true,
1711
- refresh,
1712
- recomputeSnapshot: true,
1713
- }));
1714
- }
1715
- else if (Utils.isObject(cached) && merge) {
1716
- data = em.entityFactory.create(entityName, cached, {
1717
- merge: true,
1718
- convertCustomTypes: true,
1719
- refresh,
1720
- recomputeSnapshot: true,
1721
- });
1722
- }
1723
- else {
1724
- data = cached;
1725
- }
1726
- await em.unitOfWork.dispatchOnLoadEvent();
1727
- return { key: cacheKey, data };
1755
+ if (!cached) {
1756
+ return { key: cacheKey, data: cached };
1728
1757
  }
1729
- return { key: cacheKey };
1758
+ let data;
1759
+ if (Array.isArray(cached) && merge) {
1760
+ data = cached.map(item => em.entityFactory.create(entityName, item, {
1761
+ merge: true,
1762
+ convertCustomTypes: true,
1763
+ refresh,
1764
+ recomputeSnapshot: true,
1765
+ }));
1766
+ }
1767
+ else if (Utils.isObject(cached) && merge) {
1768
+ data = em.entityFactory.create(entityName, cached, {
1769
+ merge: true,
1770
+ convertCustomTypes: true,
1771
+ refresh,
1772
+ recomputeSnapshot: true,
1773
+ });
1774
+ }
1775
+ else {
1776
+ data = cached;
1777
+ }
1778
+ await em.unitOfWork.dispatchOnLoadEvent();
1779
+ return { key: cacheKey, data };
1730
1780
  }
1731
1781
  /**
1732
1782
  * @internal
@@ -1735,7 +1785,7 @@ export class EntityManager {
1735
1785
  config ??= this.config.get('resultCache').global;
1736
1786
  if (config) {
1737
1787
  const em = this.getContext();
1738
- const expiration = Array.isArray(config) ? config[1] : (Utils.isNumber(config) ? config : undefined);
1788
+ const expiration = Array.isArray(config) ? config[1] : (typeof config === 'number' ? config : undefined);
1739
1789
  await em.resultCache.set(key.key, data instanceof Function ? data() : data, '', expiration);
1740
1790
  }
1741
1791
  }
@@ -1768,6 +1818,19 @@ export class EntityManager {
1768
1818
  set schema(schema) {
1769
1819
  this.getContext(false)._schema = schema ?? undefined;
1770
1820
  }
1821
+ /** @internal */
1822
+ async getDataLoader(type) {
1823
+ const em = this.getContext();
1824
+ if (em.loaders[type]) {
1825
+ return em.loaders[type];
1826
+ }
1827
+ const DataLoader = await DataloaderUtils.getDataLoader();
1828
+ switch (type) {
1829
+ case 'ref': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getRefBatchLoadFn(em)));
1830
+ case '1:m': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getColBatchLoadFn(em)));
1831
+ case 'm:n': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(em)));
1832
+ }
1833
+ }
1771
1834
  /**
1772
1835
  * Returns the ID of this EntityManager. Respects the context, so global EM will give you the contextual ID
1773
1836
  * if executed inside request context handler.
@@ -1776,7 +1839,7 @@ export class EntityManager {
1776
1839
  return this.getContext(false)._id;
1777
1840
  }
1778
1841
  /** @ignore */
1779
- [inspect.custom]() {
1842
+ [Symbol.for('nodejs.util.inspect.custom')]() {
1780
1843
  return `[EntityManager<${this.id}>]`;
1781
1844
  }
1782
1845
  }