@mikro-orm/core 7.0.0-dev.8 → 7.0.0-dev.80

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 (181) hide show
  1. package/EntityManager.d.ts +85 -48
  2. package/EntityManager.js +300 -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 +17 -8
  16. package/drivers/IDatabaseDriver.d.ts +27 -5
  17. package/entity/BaseEntity.d.ts +0 -1
  18. package/entity/BaseEntity.js +0 -3
  19. package/entity/Collection.d.ts +98 -30
  20. package/entity/Collection.js +432 -93
  21. package/entity/EntityAssigner.d.ts +1 -1
  22. package/entity/EntityAssigner.js +15 -7
  23. package/entity/EntityFactory.d.ts +7 -0
  24. package/entity/EntityFactory.js +64 -41
  25. package/entity/EntityHelper.js +26 -9
  26. package/entity/EntityLoader.d.ts +5 -4
  27. package/entity/EntityLoader.js +73 -40
  28. package/entity/EntityRepository.d.ts +1 -1
  29. package/entity/Reference.d.ts +9 -7
  30. package/entity/Reference.js +33 -6
  31. package/entity/WrappedEntity.d.ts +2 -4
  32. package/entity/WrappedEntity.js +1 -5
  33. package/entity/defineEntity.d.ts +549 -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 +6 -2
  44. package/errors.js +14 -9
  45. package/events/EventSubscriber.d.ts +3 -1
  46. package/hydration/Hydrator.js +1 -2
  47. package/hydration/ObjectHydrator.d.ts +4 -4
  48. package/hydration/ObjectHydrator.js +36 -25
  49. package/index.d.ts +2 -2
  50. package/index.js +1 -2
  51. package/logging/DefaultLogger.d.ts +1 -1
  52. package/logging/SimpleLogger.d.ts +1 -1
  53. package/metadata/EntitySchema.d.ts +9 -13
  54. package/metadata/EntitySchema.js +44 -26
  55. package/metadata/MetadataDiscovery.d.ts +6 -9
  56. package/metadata/MetadataDiscovery.js +167 -206
  57. package/metadata/MetadataProvider.d.ts +11 -2
  58. package/metadata/MetadataProvider.js +44 -2
  59. package/metadata/MetadataStorage.d.ts +1 -6
  60. package/metadata/MetadataStorage.js +6 -18
  61. package/metadata/MetadataValidator.d.ts +0 -7
  62. package/metadata/MetadataValidator.js +4 -13
  63. package/metadata/discover-entities.d.ts +5 -0
  64. package/metadata/discover-entities.js +40 -0
  65. package/metadata/index.d.ts +1 -1
  66. package/metadata/index.js +1 -1
  67. package/metadata/types.d.ts +480 -0
  68. package/metadata/types.js +1 -0
  69. package/naming-strategy/AbstractNamingStrategy.d.ts +5 -1
  70. package/naming-strategy/AbstractNamingStrategy.js +8 -2
  71. package/naming-strategy/NamingStrategy.d.ts +11 -1
  72. package/not-supported.d.ts +2 -0
  73. package/not-supported.js +4 -0
  74. package/package.json +18 -10
  75. package/platforms/ExceptionConverter.js +1 -1
  76. package/platforms/Platform.d.ts +6 -10
  77. package/platforms/Platform.js +14 -39
  78. package/serialization/EntitySerializer.d.ts +2 -0
  79. package/serialization/EntitySerializer.js +32 -14
  80. package/serialization/EntityTransformer.js +22 -12
  81. package/serialization/SerializationContext.js +16 -13
  82. package/types/ArrayType.d.ts +1 -1
  83. package/types/ArrayType.js +2 -3
  84. package/types/BigIntType.d.ts +8 -6
  85. package/types/BigIntType.js +1 -1
  86. package/types/BlobType.d.ts +0 -1
  87. package/types/BlobType.js +0 -3
  88. package/types/BooleanType.d.ts +2 -1
  89. package/types/BooleanType.js +3 -0
  90. package/types/DecimalType.d.ts +6 -4
  91. package/types/DecimalType.js +3 -3
  92. package/types/DoubleType.js +2 -2
  93. package/types/JsonType.d.ts +1 -1
  94. package/types/JsonType.js +7 -2
  95. package/types/TinyIntType.js +1 -1
  96. package/types/Type.d.ts +2 -1
  97. package/types/Type.js +1 -1
  98. package/types/Uint8ArrayType.d.ts +0 -1
  99. package/types/Uint8ArrayType.js +1 -4
  100. package/types/index.d.ts +1 -1
  101. package/typings.d.ts +113 -77
  102. package/typings.js +41 -35
  103. package/unit-of-work/ChangeSetComputer.d.ts +1 -3
  104. package/unit-of-work/ChangeSetComputer.js +11 -9
  105. package/unit-of-work/ChangeSetPersister.d.ts +5 -4
  106. package/unit-of-work/ChangeSetPersister.js +58 -20
  107. package/unit-of-work/UnitOfWork.d.ts +8 -1
  108. package/unit-of-work/UnitOfWork.js +115 -57
  109. package/utils/AbstractSchemaGenerator.d.ts +5 -5
  110. package/utils/AbstractSchemaGenerator.js +11 -9
  111. package/utils/Configuration.d.ts +757 -206
  112. package/utils/Configuration.js +139 -187
  113. package/utils/ConfigurationLoader.d.ts +1 -54
  114. package/utils/ConfigurationLoader.js +1 -352
  115. package/utils/Cursor.d.ts +3 -3
  116. package/utils/Cursor.js +4 -1
  117. package/utils/DataloaderUtils.d.ts +15 -5
  118. package/utils/DataloaderUtils.js +54 -8
  119. package/utils/EntityComparator.d.ts +8 -4
  120. package/utils/EntityComparator.js +111 -64
  121. package/utils/QueryHelper.d.ts +9 -1
  122. package/utils/QueryHelper.js +70 -9
  123. package/utils/RawQueryFragment.d.ts +36 -4
  124. package/utils/RawQueryFragment.js +35 -14
  125. package/utils/TransactionManager.d.ts +65 -0
  126. package/utils/TransactionManager.js +223 -0
  127. package/utils/Utils.d.ts +8 -97
  128. package/utils/Utils.js +88 -303
  129. package/utils/clone.js +2 -3
  130. package/utils/env-vars.d.ts +3 -0
  131. package/utils/env-vars.js +87 -0
  132. package/utils/fs-utils.d.ts +12 -0
  133. package/utils/fs-utils.js +96 -0
  134. package/utils/index.d.ts +2 -1
  135. package/utils/index.js +2 -1
  136. package/utils/upsert-utils.d.ts +7 -2
  137. package/utils/upsert-utils.js +55 -4
  138. package/decorators/Check.d.ts +0 -3
  139. package/decorators/Check.js +0 -13
  140. package/decorators/CreateRequestContext.d.ts +0 -3
  141. package/decorators/CreateRequestContext.js +0 -32
  142. package/decorators/Embeddable.d.ts +0 -8
  143. package/decorators/Embeddable.js +0 -11
  144. package/decorators/Embedded.d.ts +0 -18
  145. package/decorators/Embedded.js +0 -18
  146. package/decorators/Entity.d.ts +0 -18
  147. package/decorators/Entity.js +0 -12
  148. package/decorators/Enum.d.ts +0 -9
  149. package/decorators/Enum.js +0 -16
  150. package/decorators/Filter.d.ts +0 -2
  151. package/decorators/Filter.js +0 -8
  152. package/decorators/Formula.d.ts +0 -4
  153. package/decorators/Formula.js +0 -15
  154. package/decorators/Indexed.d.ts +0 -19
  155. package/decorators/Indexed.js +0 -20
  156. package/decorators/ManyToMany.d.ts +0 -40
  157. package/decorators/ManyToMany.js +0 -14
  158. package/decorators/ManyToOne.d.ts +0 -30
  159. package/decorators/ManyToOne.js +0 -14
  160. package/decorators/OneToMany.d.ts +0 -28
  161. package/decorators/OneToMany.js +0 -17
  162. package/decorators/OneToOne.d.ts +0 -24
  163. package/decorators/OneToOne.js +0 -7
  164. package/decorators/PrimaryKey.d.ts +0 -8
  165. package/decorators/PrimaryKey.js +0 -20
  166. package/decorators/Property.d.ts +0 -250
  167. package/decorators/Property.js +0 -32
  168. package/decorators/Transactional.d.ts +0 -13
  169. package/decorators/Transactional.js +0 -28
  170. package/decorators/hooks.d.ts +0 -16
  171. package/decorators/hooks.js +0 -47
  172. package/decorators/index.d.ts +0 -17
  173. package/decorators/index.js +0 -17
  174. package/entity/ArrayCollection.d.ts +0 -116
  175. package/entity/ArrayCollection.js +0 -402
  176. package/entity/EntityValidator.d.ts +0 -19
  177. package/entity/EntityValidator.js +0 -150
  178. package/metadata/ReflectMetadataProvider.d.ts +0 -8
  179. package/metadata/ReflectMetadataProvider.js +0 -44
  180. package/utils/resolveContextProvider.d.ts +0 -10
  181. package/utils/resolveContextProvider.js +0 -28
package/EntityManager.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import { inspect } from 'node:util';
2
- import DataLoader from 'dataloader';
3
- import { getOnConflictReturningFields } from './utils/upsert-utils.js';
2
+ import { getOnConflictReturningFields, getWhereCondition } from './utils/upsert-utils.js';
4
3
  import { Utils } from './utils/Utils.js';
5
4
  import { Cursor } from './utils/Cursor.js';
6
5
  import { DataloaderUtils } from './utils/DataloaderUtils.js';
@@ -9,7 +8,7 @@ import { TransactionContext } from './utils/TransactionContext.js';
9
8
  import { isRaw, RawQueryFragment } from './utils/RawQueryFragment.js';
10
9
  import { EntityFactory } from './entity/EntityFactory.js';
11
10
  import { EntityAssigner } from './entity/EntityAssigner.js';
12
- import { EntityValidator } from './entity/EntityValidator.js';
11
+ import { validateEmptyWhere, validateParams, validatePrimaryKey, validateProperty } from './entity/validators.js';
13
12
  import { EntityLoader } from './entity/EntityLoader.js';
14
13
  import { Reference } from './entity/Reference.js';
15
14
  import { helper } from './entity/wrap.js';
@@ -19,6 +18,8 @@ import { EventType, FlushMode, LoadStrategy, LockMode, PopulateHint, PopulatePat
19
18
  import { EventManager } from './events/EventManager.js';
20
19
  import { TransactionEventBroadcaster } from './events/TransactionEventBroadcaster.js';
21
20
  import { OptimisticLockError, ValidationError } from './errors.js';
21
+ import { getLoadingStrategy } from './entity/utils.js';
22
+ import { TransactionManager } from './utils/TransactionManager.js';
22
23
  /**
23
24
  * The EntityManager is the central access point to ORM functionality. It is a facade to all different ORM subsystems
24
25
  * such as UnitOfWork, Query Language, and Repository API.
@@ -34,9 +35,7 @@ export class EntityManager {
34
35
  _id = EntityManager.counter++;
35
36
  global = false;
36
37
  name;
37
- refLoader = new DataLoader(DataloaderUtils.getRefBatchLoadFn(this));
38
- colLoader = new DataLoader(DataloaderUtils.getColBatchLoadFn(this));
39
- validator;
38
+ loaders = {};
40
39
  repositoryMap = {};
41
40
  entityLoader;
42
41
  comparator;
@@ -61,7 +60,6 @@ export class EntityManager {
61
60
  this.eventManager = eventManager;
62
61
  this.entityLoader = new EntityLoader(this);
63
62
  this.name = this.config.get('contextName');
64
- this.validator = new EntityValidator(this.config.get('strict'));
65
63
  this.comparator = this.config.getComparator(this.metadata);
66
64
  this.resultCache = this.config.getResultCacheAdapter();
67
65
  this.disableTransactions = this.config.get('disableTransactions');
@@ -105,12 +103,6 @@ export class EntityManager {
105
103
  repo(entityName) {
106
104
  return this.getRepository(entityName);
107
105
  }
108
- /**
109
- * Gets EntityValidator instance
110
- */
111
- getValidator() {
112
- return this.validator;
113
- }
114
106
  /**
115
107
  * Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
116
108
  */
@@ -127,7 +119,7 @@ export class EntityManager {
127
119
  await em.tryFlush(entityName, options);
128
120
  entityName = Utils.className(entityName);
129
121
  where = await em.processWhere(entityName, where, options, 'read');
130
- em.validator.validateParams(where);
122
+ validateParams(where);
131
123
  options.orderBy = options.orderBy || {};
132
124
  options.populate = await em.preparePopulate(entityName, options);
133
125
  const populate = options.populate;
@@ -137,7 +129,6 @@ export class EntityManager {
137
129
  await em.entityLoader.populate(entityName, cached.data, populate, {
138
130
  ...options,
139
131
  ...em.getPopulateWhere(where, options),
140
- convertCustomTypes: false,
141
132
  ignoreLazyScalarProperties: true,
142
133
  lookup: false,
143
134
  });
@@ -148,7 +139,7 @@ export class EntityManager {
148
139
  // save the original hint value so we know it was infer/all
149
140
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
150
141
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
151
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
142
+ options.populateFilter = await this.getJoinedFilters(meta, options);
152
143
  const results = await em.driver.find(entityName, where, { ctx: em.transactionContext, em, ...options });
153
144
  if (results.length === 0) {
154
145
  await em.storeCache(options.cache, cached, []);
@@ -168,7 +159,6 @@ export class EntityManager {
168
159
  await em.entityLoader.populate(entityName, unique, populate, {
169
160
  ...options,
170
161
  ...em.getPopulateWhere(where, options),
171
- convertCustomTypes: false,
172
162
  ignoreLazyScalarProperties: true,
173
163
  lookup: false,
174
164
  });
@@ -181,6 +171,61 @@ export class EntityManager {
181
171
  }
182
172
  return unique;
183
173
  }
174
+ /**
175
+ * Finds all entities and returns an async iterable (async generator) that yields results one by one.
176
+ * The results are merged and mapped to entity instances, without adding them to the identity map.
177
+ * You can disable merging by passing the options `{ mergeResults: false }`.
178
+ * With `mergeResults` disabled, to-many collections will contain at most one item, and you will get duplicate
179
+ * root entities when there are multiple items in the populated collection.
180
+ * This is useful for processing large datasets without loading everything into memory at once.
181
+ *
182
+ * ```ts
183
+ * const stream = em.stream(Book, { populate: ['author'] });
184
+ *
185
+ * for await (const book of stream) {
186
+ * // book is an instance of Book entity
187
+ * console.log(book.title, book.author.name);
188
+ * }
189
+ * ```
190
+ */
191
+ async *stream(entityName, options = {}) {
192
+ const em = this.getContext();
193
+ em.prepareOptions(options);
194
+ options.strategy = 'joined';
195
+ await em.tryFlush(entityName, options);
196
+ entityName = Utils.className(entityName);
197
+ const where = await em.processWhere(entityName, options.where ?? {}, options, 'read');
198
+ validateParams(where);
199
+ options.orderBy = options.orderBy || {};
200
+ options.populate = await em.preparePopulate(entityName, options);
201
+ const meta = this.metadata.get(entityName);
202
+ options = { ...options };
203
+ // save the original hint value so we know it was infer/all
204
+ options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
205
+ options.populateWhere = this.createPopulateWhere({ ...where }, options);
206
+ options.populateFilter = await this.getJoinedFilters(meta, options);
207
+ const stream = em.driver.stream(entityName, where, {
208
+ ctx: em.transactionContext,
209
+ mapResults: false,
210
+ ...options,
211
+ });
212
+ for await (const data of stream) {
213
+ const fork = em.fork();
214
+ const entity = fork.entityFactory.create(entityName, data, {
215
+ refresh: options.refresh,
216
+ schema: options.schema,
217
+ convertCustomTypes: true,
218
+ });
219
+ helper(entity).setSerializationContext({
220
+ populate: options.populate,
221
+ fields: options.fields,
222
+ exclude: options.exclude,
223
+ });
224
+ await fork.unitOfWork.dispatchOnLoadEvent();
225
+ fork.clear();
226
+ yield entity;
227
+ }
228
+ }
184
229
  /**
185
230
  * Finds all entities of given type, optionally matching the `where` condition provided in the `options` parameter.
186
231
  */
@@ -194,7 +239,7 @@ export class EntityManager {
194
239
  if (options.populateWhere === PopulateHint.ALL) {
195
240
  return { where: {}, populateWhere: options.populateWhere };
196
241
  }
197
- /* v8 ignore next 3 */
242
+ /* v8 ignore next */
198
243
  if (options.populateWhere === PopulateHint.INFER) {
199
244
  return { where, populateWhere: options.populateWhere };
200
245
  }
@@ -203,12 +248,12 @@ export class EntityManager {
203
248
  /**
204
249
  * Registers global filter to this entity manager. Global filters are enabled by default (unless disabled via last parameter).
205
250
  */
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));
251
+ addFilter(options) {
252
+ if (options.entity) {
253
+ options.entity = Utils.asArray(options.entity).map(n => Utils.className(n));
210
254
  }
211
- this.getContext(false).filters[name] = options;
255
+ options.default ??= true;
256
+ this.getContext(false).filters[options.name] = options;
212
257
  }
213
258
  /**
214
259
  * Sets filter parameter values globally inside context defined by this entity manager.
@@ -232,8 +277,8 @@ export class EntityManager {
232
277
  /**
233
278
  * Gets logger context for this entity manager.
234
279
  */
235
- getLoggerContext() {
236
- const em = this.getContext();
280
+ getLoggerContext(options) {
281
+ const em = options?.disableContextResolution ? this : this.getContext();
237
282
  em.loggerContext ??= {};
238
283
  return em.loggerContext;
239
284
  }
@@ -283,28 +328,39 @@ export class EntityManager {
283
328
  }
284
329
  return ret;
285
330
  }
286
- async getJoinedFilters(meta, cond, options) {
331
+ async getJoinedFilters(meta, options) {
332
+ if (!this.config.get('filtersOnRelations') || !options.populate) {
333
+ return undefined;
334
+ }
287
335
  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;
336
+ for (const hint of options.populate) {
337
+ const field = hint.field.split(':')[0];
338
+ const prop = meta.properties[field];
339
+ const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
340
+ const joined = strategy === LoadStrategy.JOINED && prop.kind !== ReferenceKind.SCALAR;
341
+ if (!joined && !hint.filter) {
342
+ continue;
343
+ }
344
+ const filters = QueryHelper.mergePropertyFilters(prop.filters, options.filters);
345
+ const where = await this.applyFilters(prop.type, {}, filters, 'read', {
346
+ ...options,
347
+ populate: hint.children,
348
+ });
349
+ const where2 = await this.getJoinedFilters(prop.targetMeta, {
350
+ ...options,
351
+ filters,
352
+ populate: hint.children,
353
+ populateWhere: PopulateHint.ALL,
354
+ });
355
+ if (Utils.hasObjectKeys(where)) {
356
+ ret[field] = ret[field] ? { $and: [where, ret[field]] } : where;
357
+ }
358
+ if (where2 && Utils.hasObjectKeys(where2)) {
359
+ if (ret[field]) {
360
+ Utils.merge(ret[field], where2);
300
361
  }
301
- if (Utils.hasObjectKeys(where2)) {
302
- if (ret[field]) {
303
- Utils.merge(ret[field], where2);
304
- }
305
- else {
306
- ret[field] = where2;
307
- }
362
+ else {
363
+ ret[field] = where2;
308
364
  }
309
365
  }
310
366
  }
@@ -313,27 +369,45 @@ export class EntityManager {
313
369
  /**
314
370
  * 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
371
  */
316
- async autoJoinRefsForFilters(meta, options) {
317
- if (!meta || !this.config.get('autoJoinRefsForFilters')) {
372
+ async autoJoinRefsForFilters(meta, options, parent) {
373
+ if (!meta || !this.config.get('autoJoinRefsForFilters') || options.filters === false) {
318
374
  return;
319
375
  }
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
376
  const ret = options.populate;
325
- for (const prop of props) {
326
- const cond = await this.applyFilters(prop.type, {}, options.filters ?? {}, 'read', options);
377
+ for (const prop of meta.relations) {
378
+ if (prop.object
379
+ || ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)
380
+ || !((options.fields?.length ?? 0) === 0 || options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
381
+ || (parent?.className === prop.targetMeta.root.className && parent.propName === prop.inversedBy)) {
382
+ continue;
383
+ }
384
+ options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
385
+ const cond = await this.applyFilters(prop.type, {}, options.filters, 'read', options);
327
386
  if (!Utils.isEmpty(cond)) {
328
387
  const populated = options.populate.filter(({ field }) => field.split(':')[0] === prop.name);
329
- if (populated.length > 0) {
330
- populated.forEach(hint => hint.filter = true);
388
+ let found = false;
389
+ for (const hint of populated) {
390
+ if (!hint.all) {
391
+ hint.filter = true;
392
+ }
393
+ const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
394
+ if (hint.field === `${prop.name}:ref` || (hint.filter && strategy === LoadStrategy.JOINED)) {
395
+ found = true;
396
+ }
331
397
  }
332
- else {
398
+ if (!found) {
333
399
  ret.push({ field: `${prop.name}:ref`, strategy: LoadStrategy.JOINED, filter: true });
334
400
  }
335
401
  }
336
402
  }
403
+ for (const hint of ret) {
404
+ const [field, ref] = hint.field.split(':');
405
+ const prop = meta?.properties[field];
406
+ if (prop && !ref) {
407
+ hint.children ??= [];
408
+ await this.autoJoinRefsForFilters(prop.targetMeta, { ...options, populate: hint.children }, { className: meta.root.className, propName: prop.name });
409
+ }
410
+ }
337
411
  }
338
412
  /**
339
413
  * @internal
@@ -363,7 +437,7 @@ export class EntityManager {
363
437
  let cond;
364
438
  if (filter.cond instanceof Function) {
365
439
  // @ts-ignore
366
- const args = Utils.isPlainObject(options[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
440
+ const args = Utils.isPlainObject(options?.[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
367
441
  if (!args && filter.cond.length > 0 && filter.args !== false) {
368
442
  throw new Error(`No arguments provided for filter '${filter.name}'`);
369
443
  }
@@ -372,13 +446,17 @@ export class EntityManager {
372
446
  else {
373
447
  cond = filter.cond;
374
448
  }
375
- ret.push(QueryHelper.processWhere({
449
+ cond = QueryHelper.processWhere({
376
450
  where: cond,
377
451
  entityName,
378
452
  metadata: this.metadata,
379
453
  platform: this.driver.getPlatform(),
380
454
  aliased: type === 'read',
381
- }));
455
+ });
456
+ if (filter.strict) {
457
+ Object.defineProperty(cond, '__strict', { value: filter.strict, enumerable: false });
458
+ }
459
+ ret.push(cond);
382
460
  }
383
461
  const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c));
384
462
  return conds.length > 1 ? { $and: conds } : conds[0];
@@ -433,6 +511,10 @@ export class EntityManager {
433
511
  * });
434
512
  * ```
435
513
  *
514
+ * The options also support an `includeCount` (true by default) option. If set to false, the `totalCount` is not
515
+ * returned as part of the cursor. This is useful for performance reason, when you don't care about the total number
516
+ * of pages.
517
+ *
436
518
  * The `Cursor` object provides the following interface:
437
519
  *
438
520
  * ```ts
@@ -442,7 +524,7 @@ export class EntityManager {
442
524
  * User { ... },
443
525
  * User { ... },
444
526
  * ],
445
- * totalCount: 50,
527
+ * totalCount: 50, // not included if `includeCount: false`
446
528
  * startCursor: 'WzRd',
447
529
  * endCursor: 'WzZd',
448
530
  * hasPrevPage: true,
@@ -457,7 +539,9 @@ export class EntityManager {
457
539
  if (Utils.isEmpty(options.orderBy)) {
458
540
  throw new Error('Explicit `orderBy` option required');
459
541
  }
460
- const [entities, count] = await em.findAndCount(entityName, where, options);
542
+ const [entities, count] = options.includeCount !== false
543
+ ? await em.findAndCount(entityName, where, options)
544
+ : [await em.find(entityName, where, options)];
461
545
  return new Cursor(entities, count, options, this.metadata.get(entityName));
462
546
  }
463
547
  /**
@@ -483,18 +567,31 @@ export class EntityManager {
483
567
  async refresh(entity, options = {}) {
484
568
  const fork = this.fork({ keepTransactionContext: true });
485
569
  const entityName = entity.constructor.name;
570
+ const wrapped = helper(entity);
486
571
  const reloaded = await fork.findOne(entityName, entity, {
487
- schema: helper(entity).__schema,
572
+ schema: wrapped.__schema,
488
573
  ...options,
489
574
  flushMode: FlushMode.COMMIT,
490
575
  });
491
- if (reloaded) {
492
- this.config.getHydrator(this.metadata).hydrate(entity, helper(entity).__meta, helper(reloaded).toPOJO(), this.getEntityFactory(), 'full');
576
+ const em = this.getContext();
577
+ if (!reloaded) {
578
+ em.unitOfWork.unsetIdentity(entity);
579
+ return null;
493
580
  }
494
- else {
495
- this.getUnitOfWork().unsetIdentity(entity);
581
+ let found = false;
582
+ for (const e of fork.unitOfWork.getIdentityMap()) {
583
+ const ref = em.getReference(e.constructor.name, helper(e).getPrimaryKey());
584
+ const data = helper(e).serialize({ ignoreSerializers: true, includeHidden: true });
585
+ em.config.getHydrator(this.metadata).hydrate(ref, helper(ref).__meta, data, em.entityFactory, 'full', false, true);
586
+ Utils.merge(helper(ref).__originalEntityData, this.comparator.prepareEntity(e));
587
+ found ||= ref === entity;
496
588
  }
497
- return reloaded ? entity : reloaded;
589
+ if (!found) {
590
+ const data = helper(reloaded).serialize({ ignoreSerializers: true, includeHidden: true });
591
+ em.config.getHydrator(this.metadata).hydrate(entity, wrapped.__meta, data, em.entityFactory, 'full', false, true);
592
+ Utils.merge(wrapped.__originalEntityData, this.comparator.prepareEntity(reloaded));
593
+ }
594
+ return entity;
498
595
  }
499
596
  /**
500
597
  * Finds first entity matching your `where` query.
@@ -520,31 +617,32 @@ export class EntityManager {
520
617
  await em.tryFlush(entityName, options);
521
618
  const meta = em.metadata.get(entityName);
522
619
  where = await em.processWhere(entityName, where, options, 'read');
523
- em.validator.validateEmptyWhere(where);
620
+ validateEmptyWhere(where);
524
621
  em.checkLockRequirements(options.lockMode, meta);
525
- const isOptimisticLocking = !Utils.isDefined(options.lockMode) || options.lockMode === LockMode.OPTIMISTIC;
622
+ const isOptimisticLocking = options.lockMode == null || options.lockMode === LockMode.OPTIMISTIC;
526
623
  if (entity && !em.shouldRefresh(meta, entity, options) && isOptimisticLocking) {
527
624
  return em.lockAndPopulate(meta, entity, where, options);
528
625
  }
529
- em.validator.validateParams(where);
626
+ validateParams(where);
530
627
  options.populate = await em.preparePopulate(entityName, options);
531
628
  const cacheKey = em.cacheKey(entityName, options, 'em.findOne', where);
532
629
  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
- });
630
+ if (cached?.data !== undefined) {
631
+ if (cached.data) {
632
+ await em.entityLoader.populate(entityName, [cached.data], options.populate, {
633
+ ...options,
634
+ ...em.getPopulateWhere(where, options),
635
+ ignoreLazyScalarProperties: true,
636
+ lookup: false,
637
+ });
638
+ }
541
639
  return cached.data;
542
640
  }
543
641
  options = { ...options };
544
642
  // save the original hint value so we know it was infer/all
545
643
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
546
644
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
547
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
645
+ options.populateFilter = await this.getJoinedFilters(meta, options);
548
646
  const data = await em.driver.findOne(entityName, where, {
549
647
  ctx: em.transactionContext,
550
648
  em,
@@ -655,26 +753,9 @@ export class EntityManager {
655
753
  }
656
754
  }
657
755
  }
658
- const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
659
- const propIndex = !isRaw(unique) && unique.findIndex(p => data[p] != null);
660
- if (options.onConflictFields || where == null) {
661
- if (propIndex !== false && propIndex >= 0) {
662
- where = { [unique[propIndex]]: data[unique[propIndex]] };
663
- }
664
- else if (meta.uniques.length > 0) {
665
- for (const u of meta.uniques) {
666
- if (Utils.asArray(u.properties).every(p => data[p] != null)) {
667
- where = Utils.asArray(u.properties).reduce((o, key) => {
668
- o[key] = data[key];
669
- return o;
670
- }, {});
671
- break;
672
- }
673
- }
674
- }
675
- }
756
+ where = getWhereCondition(meta, options.onConflictFields, data, where).where;
676
757
  data = QueryHelper.processObjectParams(data);
677
- em.validator.validateParams(data, 'insert data');
758
+ validateParams(data, 'insert data');
678
759
  if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
679
760
  await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity: data, em, meta }, meta);
680
761
  }
@@ -717,6 +798,7 @@ export class EntityManager {
717
798
  ctx: em.transactionContext,
718
799
  convertCustomTypes: true,
719
800
  connectionType: 'write',
801
+ schema: options.schema,
720
802
  });
721
803
  em.getHydrator().hydrate(entity, meta, data2, em.entityFactory, 'full', false, true);
722
804
  }
@@ -814,32 +896,18 @@ export class EntityManager {
814
896
  }
815
897
  }
816
898
  }
817
- const unique = meta.props.filter(p => p.unique).map(p => p.name);
818
- propIndex = unique.findIndex(p => row[p] != null);
819
- if (options.onConflictFields || where == null) {
820
- if (propIndex >= 0) {
821
- where = { [unique[propIndex]]: row[unique[propIndex]] };
822
- }
823
- else if (meta.uniques.length > 0) {
824
- for (const u of meta.uniques) {
825
- if (Utils.asArray(u.properties).every(p => row[p] != null)) {
826
- where = Utils.asArray(u.properties).reduce((o, key) => {
827
- o[key] = row[key];
828
- return o;
829
- }, {});
830
- break;
831
- }
832
- }
833
- }
834
- }
835
- row = QueryHelper.processObjectParams(row);
899
+ const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
900
+ propIndex = !isRaw(unique) && unique.findIndex(p => data[p] ?? data[p.substring(0, p.indexOf('.'))] != null);
901
+ const tmp = getWhereCondition(meta, options.onConflictFields, row, where);
902
+ propIndex = tmp.propIndex;
836
903
  where = QueryHelper.processWhere({
837
- where,
904
+ where: tmp.where,
838
905
  entityName,
839
906
  metadata: this.metadata,
840
907
  platform: this.getPlatform(),
841
908
  });
842
- em.validator.validateParams(row, 'insert data');
909
+ row = QueryHelper.processObjectParams(row);
910
+ validateParams(row, 'insert data');
843
911
  allData.push(row);
844
912
  allWhere.push(where);
845
913
  }
@@ -880,7 +948,7 @@ export class EntityManager {
880
948
  const reloadFields = returning.length > 0 && !(this.getPlatform().usesReturningStatement() && res.rows?.length);
881
949
  if (options.onConflictAction === 'ignore' || (!res.rows?.length && loadPK.size > 0) || reloadFields) {
882
950
  const unique = meta.hydrateProps.filter(p => !p.lazy).map(p => p.name);
883
- const add = new Set(propIndex >= 0 ? [unique[propIndex]] : []);
951
+ const add = new Set(propIndex !== false && propIndex >= 0 ? [unique[propIndex]] : []);
884
952
  for (const cond of loadPK.values()) {
885
953
  Utils.keys(cond).forEach(key => add.add(key));
886
954
  }
@@ -897,6 +965,7 @@ export class EntityManager {
897
965
  ctx: em.transactionContext,
898
966
  convertCustomTypes: true,
899
967
  connectionType: 'write',
968
+ schema: options.schema,
900
969
  });
901
970
  for (const [entity, cond] of loadPK.entries()) {
902
971
  const row = data2.find(row => {
@@ -908,7 +977,7 @@ export class EntityManager {
908
977
  });
909
978
  return this.comparator.matching(entityName, cond, tmp);
910
979
  });
911
- /* v8 ignore next 3 */
980
+ /* v8 ignore next */
912
981
  if (!row) {
913
982
  throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
914
983
  }
@@ -931,7 +1000,7 @@ export class EntityManager {
931
1000
  }, {});
932
1001
  return this.comparator.matching(entityName, cond, pk);
933
1002
  });
934
- /* v8 ignore next 3 */
1003
+ /* v8 ignore next */
935
1004
  if (!row) {
936
1005
  throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
937
1006
  }
@@ -953,45 +1022,37 @@ export class EntityManager {
953
1022
  }
954
1023
  /**
955
1024
  * Runs your callback wrapped inside a database transaction.
1025
+ *
1026
+ * If a transaction is already active, a new savepoint (nested transaction) will be created by default. This behavior
1027
+ * can be controlled via the `propagation` option. Use the provided EntityManager instance for all operations that
1028
+ * should be part of the transaction. You can safely use a global EntityManager instance from a DI container, as this
1029
+ * method automatically creates an async context for the transaction.
1030
+ *
1031
+ * **Concurrency note:** When running multiple transactions concurrently (e.g. in parallel requests or jobs), use the
1032
+ * `clear: true` option. This ensures the callback runs in a clear fork of the EntityManager, providing full isolation
1033
+ * between concurrent transactional handlers. Using `clear: true` is an alternative to forking explicitly and calling
1034
+ * the method on the new fork – it already provides the necessary isolation for safe concurrent usage.
1035
+ *
1036
+ * **Propagation note:** Changes made within a transaction (whether top-level or nested) are always propagated to the
1037
+ * parent context, unless the parent context is a global one. If you want to avoid that, fork the EntityManager first
1038
+ * and then call this method on the fork.
1039
+ *
1040
+ * **Example:**
1041
+ * ```ts
1042
+ * await em.transactional(async (em) => {
1043
+ * const author = new Author('Jon');
1044
+ * em.persist(author);
1045
+ * // flush is called automatically at the end of the callback
1046
+ * });
1047
+ * ```
956
1048
  */
957
1049
  async transactional(cb, options = {}) {
958
1050
  const em = this.getContext(false);
959
1051
  if (this.disableTransactions || em.disableTransactions) {
960
1052
  return cb(em);
961
1053
  }
962
- const fork = em.fork({
963
- clear: options.clear ?? false, // state will be merged once resolves
964
- flushMode: options.flushMode,
965
- cloneEventManager: true,
966
- disableTransactions: options.ignoreNestedTransactions,
967
- loggerContext: options.loggerContext,
968
- });
969
- options.ctx ??= em.transactionContext;
970
- const propagateToUpperContext = !em.global || this.config.get('allowGlobalContext');
971
- return TransactionContext.create(fork, async () => {
972
- return fork.getConnection().transactional(async (trx) => {
973
- fork.transactionContext = trx;
974
- if (propagateToUpperContext) {
975
- fork.eventManager.registerSubscriber({
976
- afterFlush(args) {
977
- args.uow.getChangeSets()
978
- .filter(cs => [ChangeSetType.DELETE, ChangeSetType.DELETE_EARLY].includes(cs.type))
979
- .forEach(cs => em.unitOfWork.unsetIdentity(cs.entity));
980
- },
981
- });
982
- }
983
- const ret = await cb(fork);
984
- await fork.flush();
985
- if (propagateToUpperContext) {
986
- // ensure all entities from inner context are merged to the upper one
987
- for (const entity of fork.unitOfWork.getIdentityMap()) {
988
- em.unitOfWork.register(entity);
989
- entity.__helper.__em = em;
990
- }
991
- }
992
- return ret;
993
- }, { ...options, eventBroadcaster: new TransactionEventBroadcaster(fork, { topLevelTransaction: !options.ctx }) });
994
- });
1054
+ const manager = new TransactionManager(this);
1055
+ return manager.handle(cb, options);
995
1056
  }
996
1057
  /**
997
1058
  * Starts new transaction bound to this EntityManager. Use `ctx` parameter to provide the parent when nesting transactions.
@@ -1075,7 +1136,7 @@ export class EntityManager {
1075
1136
  return cs.getPrimaryKey();
1076
1137
  }
1077
1138
  data = QueryHelper.processObjectParams(data);
1078
- em.validator.validateParams(data, 'insert data');
1139
+ validateParams(data, 'insert data');
1079
1140
  const res = await em.driver.nativeInsert(entityName, data, { ctx: em.transactionContext, ...options });
1080
1141
  return res.insertId;
1081
1142
  }
@@ -1115,7 +1176,7 @@ export class EntityManager {
1115
1176
  return css.map(cs => cs.getPrimaryKey());
1116
1177
  }
1117
1178
  data = data.map(row => QueryHelper.processObjectParams(row));
1118
- data.forEach(row => em.validator.validateParams(row, 'insert data'));
1179
+ data.forEach(row => validateParams(row, 'insert data'));
1119
1180
  const res = await em.driver.nativeInsertMany(entityName, data, { ctx: em.transactionContext, ...options });
1120
1181
  if (res.insertedIds) {
1121
1182
  return res.insertedIds;
@@ -1131,8 +1192,8 @@ export class EntityManager {
1131
1192
  entityName = Utils.className(entityName);
1132
1193
  data = QueryHelper.processObjectParams(data);
1133
1194
  where = await em.processWhere(entityName, where, { ...options, convertCustomTypes: false }, 'update');
1134
- em.validator.validateParams(data, 'update data');
1135
- em.validator.validateParams(where, 'update condition');
1195
+ validateParams(data, 'update data');
1196
+ validateParams(where, 'update condition');
1136
1197
  const res = await em.driver.nativeUpdate(entityName, where, data, { ctx: em.transactionContext, ...options });
1137
1198
  return res.affectedRows;
1138
1199
  }
@@ -1144,7 +1205,7 @@ export class EntityManager {
1144
1205
  em.prepareOptions(options);
1145
1206
  entityName = Utils.className(entityName);
1146
1207
  where = await em.processWhere(entityName, where, options, 'delete');
1147
- em.validator.validateParams(where, 'delete condition');
1208
+ validateParams(where, 'delete condition');
1148
1209
  const res = await em.driver.nativeDelete(entityName, where, { ctx: em.transactionContext, ...options });
1149
1210
  return res.affectedRows;
1150
1211
  }
@@ -1155,15 +1216,17 @@ export class EntityManager {
1155
1216
  entityName = Utils.className(entityName);
1156
1217
  const meta = this.metadata.get(entityName);
1157
1218
  const data = this.driver.mapResult(result, meta);
1158
- Object.keys(data).forEach(k => {
1219
+ for (const k of Object.keys(data)) {
1159
1220
  const prop = meta.properties[k];
1160
- if (prop && prop.kind === ReferenceKind.SCALAR && SCALAR_TYPES.includes(prop.runtimeType) && !prop.customType && (prop.setter || !prop.getter)) {
1161
- data[k] = this.validator.validateProperty(prop, data[k], data);
1221
+ if (prop?.kind === ReferenceKind.SCALAR && SCALAR_TYPES.has(prop.runtimeType) && !prop.customType && (prop.setter || !prop.getter)) {
1222
+ validateProperty(prop, data[k], data);
1162
1223
  }
1163
- });
1224
+ }
1164
1225
  return this.merge(entityName, data, {
1165
1226
  convertCustomTypes: true,
1166
- refresh: true, ...options,
1227
+ refresh: true,
1228
+ validate: false,
1229
+ ...options,
1167
1230
  });
1168
1231
  }
1169
1232
  /**
@@ -1171,22 +1234,28 @@ export class EntityManager {
1171
1234
  * via second parameter. By default, it will return already loaded entities without modifying them.
1172
1235
  */
1173
1236
  merge(entityName, data, options = {}) {
1174
- const em = this.getContext();
1175
1237
  if (Utils.isEntity(entityName)) {
1176
- return em.merge(entityName.constructor.name, entityName, data);
1238
+ return this.merge(entityName.constructor.name, entityName, data);
1177
1239
  }
1240
+ const em = options.disableContextResolution ? this : this.getContext();
1178
1241
  options.schema ??= em._schema;
1242
+ options.validate ??= true;
1243
+ options.cascade ??= true;
1179
1244
  entityName = Utils.className(entityName);
1180
- em.validator.validatePrimaryKey(data, em.metadata.get(entityName));
1245
+ validatePrimaryKey(data, em.metadata.get(entityName));
1181
1246
  let entity = em.unitOfWork.tryGetById(entityName, data, options.schema, false);
1182
1247
  if (entity && helper(entity).__managed && helper(entity).__initialized && !options.refresh) {
1183
1248
  return entity;
1184
1249
  }
1185
- const meta = em.metadata.find(entityName);
1186
- const childMeta = em.metadata.getByDiscriminatorColumn(meta, data);
1187
- entity = Utils.isEntity(data) ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
1188
- em.validator.validate(entity, data, childMeta ?? meta);
1189
- em.unitOfWork.merge(entity);
1250
+ const dataIsEntity = Utils.isEntity(data);
1251
+ if (options.keepIdentity && entity && dataIsEntity && entity !== data) {
1252
+ helper(entity).__data = helper(data).__data;
1253
+ helper(entity).__originalEntityData = helper(data).__originalEntityData;
1254
+ return entity;
1255
+ }
1256
+ entity = dataIsEntity ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
1257
+ const visited = options.cascade ? undefined : new Set([entity]);
1258
+ em.unitOfWork.merge(entity, visited);
1190
1259
  return entity;
1191
1260
  }
1192
1261
  /**
@@ -1211,6 +1280,7 @@ export class EntityManager {
1211
1280
  ...options,
1212
1281
  newEntity: !options.managed,
1213
1282
  merge: options.managed,
1283
+ normalizeAccessors: true,
1214
1284
  });
1215
1285
  options.persist ??= em.config.get('persistOnCreate');
1216
1286
  if (options.persist && !this.getMetadata(entityName).embeddable) {
@@ -1249,10 +1319,8 @@ export class EntityManager {
1249
1319
  async count(entityName, where = {}, options = {}) {
1250
1320
  const em = this.getContext(false);
1251
1321
  // Shallow copy options since the object will be modified when deleting orderBy
1252
- options = {
1253
- schema: em._schema,
1254
- ...options,
1255
- };
1322
+ options = { ...options };
1323
+ em.prepareOptions(options);
1256
1324
  entityName = Utils.className(entityName);
1257
1325
  await em.tryFlush(entityName, options);
1258
1326
  where = await em.processWhere(entityName, where, options, 'read');
@@ -1262,15 +1330,15 @@ export class EntityManager {
1262
1330
  const meta = em.metadata.find(entityName);
1263
1331
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
1264
1332
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
1265
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
1266
- em.validator.validateParams(where);
1333
+ options.populateFilter = await this.getJoinedFilters(meta, options);
1334
+ validateParams(where);
1267
1335
  delete options.orderBy;
1268
1336
  const cacheKey = em.cacheKey(entityName, options, 'em.count', where);
1269
1337
  const cached = await em.tryCache(entityName, options.cache, cacheKey);
1270
- if (cached?.data) {
1338
+ if (cached?.data !== undefined) {
1271
1339
  return cached.data;
1272
1340
  }
1273
- const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, ...options });
1341
+ const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, em, ...options });
1274
1342
  await em.storeCache(options.cache, cached, () => +count);
1275
1343
  return +count;
1276
1344
  }
@@ -1297,13 +1365,6 @@ export class EntityManager {
1297
1365
  }
1298
1366
  return this;
1299
1367
  }
1300
- /**
1301
- * Persists your entity immediately, flushing all not yet persisted changes to the database too.
1302
- * Equivalent to `em.persist(e).flush()`.
1303
- */
1304
- async persistAndFlush(entity) {
1305
- await this.persist(entity).flush();
1306
- }
1307
1368
  /**
1308
1369
  * Marks entity for removal.
1309
1370
  * A removed entity will be removed from the database at or before transaction commit or as a result of the flush operation.
@@ -1327,13 +1388,6 @@ export class EntityManager {
1327
1388
  }
1328
1389
  return em;
1329
1390
  }
1330
- /**
1331
- * Removes an entity instance immediately, flushing all not yet persisted changes to the database too.
1332
- * Equivalent to `em.remove(e).flush()`
1333
- */
1334
- async removeAndFlush(entity) {
1335
- await this.remove(entity).flush();
1336
- }
1337
1391
  /**
1338
1392
  * Flushes all changes to objects that have been queued up to now to the database.
1339
1393
  * This effectively synchronizes the in-memory state of managed objects with the database.
@@ -1396,7 +1450,7 @@ export class EntityManager {
1396
1450
  const em = this.getContext();
1397
1451
  em.prepareOptions(options);
1398
1452
  const entityName = arr[0].constructor.name;
1399
- const preparedPopulate = await em.preparePopulate(entityName, { populate: populate }, options.validate);
1453
+ const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters }, options.validate);
1400
1454
  await em.entityLoader.populate(entityName, arr, preparedPopulate, options);
1401
1455
  return entities;
1402
1456
  }
@@ -1432,6 +1486,9 @@ export class EntityManager {
1432
1486
  for (const entity of em.unitOfWork.getIdentityMap()) {
1433
1487
  fork.unitOfWork.register(entity);
1434
1488
  }
1489
+ for (const entity of em.unitOfWork.getPersistStack()) {
1490
+ fork.unitOfWork.persist(entity);
1491
+ }
1435
1492
  for (const entity of em.unitOfWork.getOrphanRemoveStack()) {
1436
1493
  fork.unitOfWork.getOrphanRemoveStack().add(entity);
1437
1494
  }
@@ -1453,6 +1510,12 @@ export class EntityManager {
1453
1510
  getEntityFactory() {
1454
1511
  return this.getContext().entityFactory;
1455
1512
  }
1513
+ /**
1514
+ * @internal use `em.populate()` as the user facing API, this is exposed only for internal usage
1515
+ */
1516
+ getEntityLoader() {
1517
+ return this.getContext().entityLoader;
1518
+ }
1456
1519
  /**
1457
1520
  * Gets the Hydrator used by the EntityManager.
1458
1521
  */
@@ -1549,7 +1612,6 @@ export class EntityManager {
1549
1612
  ...options,
1550
1613
  ...this.getPopulateWhere(where, options),
1551
1614
  orderBy: options.populateOrderBy ?? options.orderBy,
1552
- convertCustomTypes: false,
1553
1615
  ignoreLazyScalarProperties: true,
1554
1616
  lookup: false,
1555
1617
  });
@@ -1612,7 +1674,7 @@ export class EntityManager {
1612
1674
  }
1613
1675
  if (typeof options.populate !== 'boolean') {
1614
1676
  options.populate = Utils.asArray(options.populate).map(field => {
1615
- /* v8 ignore next 3 */
1677
+ /* v8 ignore next */
1616
1678
  if (typeof field === 'boolean' || field === PopulatePath.ALL) {
1617
1679
  return [{ field: meta.primaryKeys[0], strategy: options.strategy, all: !!field }]; //
1618
1680
  }
@@ -1622,7 +1684,7 @@ export class EntityManager {
1622
1684
  options.flags.push(QueryFlag.INFER_POPULATE);
1623
1685
  return [];
1624
1686
  }
1625
- if (Utils.isString(field)) {
1687
+ if (typeof field === 'string') {
1626
1688
  return [{ field, strategy: options.strategy }];
1627
1689
  }
1628
1690
  return [field];
@@ -1672,7 +1734,7 @@ export class EntityManager {
1672
1734
  throw new ValidationError(`Cannot combine 'fields' and 'exclude' option.`);
1673
1735
  }
1674
1736
  options.schema ??= this._schema;
1675
- options.logging = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
1737
+ options.logging = options.loggerContext = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
1676
1738
  }
1677
1739
  /**
1678
1740
  * @internal
@@ -1696,31 +1758,31 @@ export class EntityManager {
1696
1758
  const em = this.getContext();
1697
1759
  const cacheKey = Array.isArray(config) ? config[0] : JSON.stringify(key);
1698
1760
  const cached = await em.resultCache.get(cacheKey);
1699
- if (cached) {
1700
- let data;
1701
- if (Array.isArray(cached) && merge) {
1702
- data = cached.map(item => em.entityFactory.create(entityName, item, {
1703
- merge: true,
1704
- convertCustomTypes: true,
1705
- refresh,
1706
- recomputeSnapshot: true,
1707
- }));
1708
- }
1709
- else if (Utils.isObject(cached) && merge) {
1710
- data = em.entityFactory.create(entityName, cached, {
1711
- merge: true,
1712
- convertCustomTypes: true,
1713
- refresh,
1714
- recomputeSnapshot: true,
1715
- });
1716
- }
1717
- else {
1718
- data = cached;
1719
- }
1720
- await em.unitOfWork.dispatchOnLoadEvent();
1721
- return { key: cacheKey, data };
1761
+ if (!cached) {
1762
+ return { key: cacheKey, data: cached };
1763
+ }
1764
+ let data;
1765
+ if (Array.isArray(cached) && merge) {
1766
+ data = cached.map(item => em.entityFactory.create(entityName, item, {
1767
+ merge: true,
1768
+ convertCustomTypes: true,
1769
+ refresh,
1770
+ recomputeSnapshot: true,
1771
+ }));
1772
+ }
1773
+ else if (Utils.isObject(cached) && merge) {
1774
+ data = em.entityFactory.create(entityName, cached, {
1775
+ merge: true,
1776
+ convertCustomTypes: true,
1777
+ refresh,
1778
+ recomputeSnapshot: true,
1779
+ });
1722
1780
  }
1723
- return { key: cacheKey };
1781
+ else {
1782
+ data = cached;
1783
+ }
1784
+ await em.unitOfWork.dispatchOnLoadEvent();
1785
+ return { key: cacheKey, data };
1724
1786
  }
1725
1787
  /**
1726
1788
  * @internal
@@ -1729,7 +1791,7 @@ export class EntityManager {
1729
1791
  config ??= this.config.get('resultCache').global;
1730
1792
  if (config) {
1731
1793
  const em = this.getContext();
1732
- const expiration = Array.isArray(config) ? config[1] : (Utils.isNumber(config) ? config : undefined);
1794
+ const expiration = Array.isArray(config) ? config[1] : (typeof config === 'number' ? config : undefined);
1733
1795
  await em.resultCache.set(key.key, data instanceof Function ? data() : data, '', expiration);
1734
1796
  }
1735
1797
  }
@@ -1762,6 +1824,19 @@ export class EntityManager {
1762
1824
  set schema(schema) {
1763
1825
  this.getContext(false)._schema = schema ?? undefined;
1764
1826
  }
1827
+ /** @internal */
1828
+ async getDataLoader(type) {
1829
+ const em = this.getContext();
1830
+ if (em.loaders[type]) {
1831
+ return em.loaders[type];
1832
+ }
1833
+ const DataLoader = await DataloaderUtils.getDataLoader();
1834
+ switch (type) {
1835
+ case 'ref': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getRefBatchLoadFn(em)));
1836
+ case '1:m': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getColBatchLoadFn(em)));
1837
+ case 'm:n': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(em)));
1838
+ }
1839
+ }
1765
1840
  /**
1766
1841
  * Returns the ID of this EntityManager. Respects the context, so global EM will give you the contextual ID
1767
1842
  * if executed inside request context handler.