@mikro-orm/core 7.0.0-dev.12 → 7.0.0-dev.120

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 (207) hide show
  1. package/EntityManager.d.ts +85 -56
  2. package/EntityManager.js +332 -293
  3. package/MikroORM.d.ts +41 -32
  4. package/MikroORM.js +100 -140
  5. package/README.md +3 -2
  6. package/cache/FileCacheAdapter.d.ts +1 -1
  7. package/cache/FileCacheAdapter.js +8 -7
  8. package/cache/GeneratedCacheAdapter.d.ts +0 -1
  9. package/cache/GeneratedCacheAdapter.js +0 -2
  10. package/cache/index.d.ts +0 -1
  11. package/cache/index.js +0 -1
  12. package/connections/Connection.d.ts +16 -7
  13. package/connections/Connection.js +23 -14
  14. package/drivers/DatabaseDriver.d.ts +25 -16
  15. package/drivers/DatabaseDriver.js +35 -19
  16. package/drivers/IDatabaseDriver.d.ts +38 -17
  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 +439 -99
  21. package/entity/EntityAssigner.d.ts +1 -1
  22. package/entity/EntityAssigner.js +26 -18
  23. package/entity/EntityFactory.d.ts +7 -0
  24. package/entity/EntityFactory.js +72 -53
  25. package/entity/EntityHelper.d.ts +2 -2
  26. package/entity/EntityHelper.js +30 -15
  27. package/entity/EntityLoader.d.ts +7 -6
  28. package/entity/EntityLoader.js +84 -72
  29. package/entity/EntityRepository.d.ts +1 -1
  30. package/entity/EntityRepository.js +2 -2
  31. package/entity/Reference.d.ts +6 -5
  32. package/entity/Reference.js +34 -9
  33. package/entity/WrappedEntity.d.ts +2 -7
  34. package/entity/WrappedEntity.js +3 -8
  35. package/entity/defineEntity.d.ts +568 -0
  36. package/entity/defineEntity.js +529 -0
  37. package/entity/index.d.ts +3 -2
  38. package/entity/index.js +3 -2
  39. package/entity/utils.d.ts +7 -0
  40. package/entity/utils.js +16 -4
  41. package/entity/validators.d.ts +11 -0
  42. package/entity/validators.js +65 -0
  43. package/enums.d.ts +21 -6
  44. package/enums.js +14 -1
  45. package/errors.d.ts +17 -9
  46. package/errors.js +41 -21
  47. package/events/EventManager.d.ts +2 -1
  48. package/events/EventManager.js +19 -11
  49. package/hydration/Hydrator.js +1 -2
  50. package/hydration/ObjectHydrator.d.ts +4 -4
  51. package/hydration/ObjectHydrator.js +50 -33
  52. package/index.d.ts +2 -2
  53. package/index.js +1 -2
  54. package/logging/DefaultLogger.d.ts +1 -1
  55. package/logging/DefaultLogger.js +1 -0
  56. package/logging/SimpleLogger.d.ts +1 -1
  57. package/logging/colors.d.ts +1 -1
  58. package/logging/colors.js +7 -6
  59. package/logging/index.d.ts +1 -0
  60. package/logging/index.js +1 -0
  61. package/logging/inspect.d.ts +2 -0
  62. package/logging/inspect.js +11 -0
  63. package/metadata/EntitySchema.d.ts +13 -17
  64. package/metadata/EntitySchema.js +67 -51
  65. package/metadata/MetadataDiscovery.d.ts +6 -10
  66. package/metadata/MetadataDiscovery.js +289 -298
  67. package/metadata/MetadataProvider.d.ts +11 -2
  68. package/metadata/MetadataProvider.js +46 -2
  69. package/metadata/MetadataStorage.d.ts +13 -11
  70. package/metadata/MetadataStorage.js +70 -37
  71. package/metadata/MetadataValidator.d.ts +2 -9
  72. package/metadata/MetadataValidator.js +22 -38
  73. package/metadata/discover-entities.d.ts +5 -0
  74. package/metadata/discover-entities.js +40 -0
  75. package/metadata/index.d.ts +1 -1
  76. package/metadata/index.js +1 -1
  77. package/metadata/types.d.ts +480 -0
  78. package/metadata/types.js +1 -0
  79. package/naming-strategy/AbstractNamingStrategy.d.ts +8 -4
  80. package/naming-strategy/AbstractNamingStrategy.js +8 -2
  81. package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
  82. package/naming-strategy/EntityCaseNamingStrategy.js +6 -5
  83. package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
  84. package/naming-strategy/MongoNamingStrategy.js +6 -6
  85. package/naming-strategy/NamingStrategy.d.ts +14 -4
  86. package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
  87. package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
  88. package/not-supported.d.ts +2 -0
  89. package/not-supported.js +4 -0
  90. package/package.json +19 -11
  91. package/platforms/ExceptionConverter.js +1 -1
  92. package/platforms/Platform.d.ts +6 -13
  93. package/platforms/Platform.js +17 -43
  94. package/serialization/EntitySerializer.d.ts +5 -0
  95. package/serialization/EntitySerializer.js +47 -27
  96. package/serialization/EntityTransformer.js +28 -18
  97. package/serialization/SerializationContext.d.ts +6 -6
  98. package/serialization/SerializationContext.js +16 -13
  99. package/types/ArrayType.d.ts +1 -1
  100. package/types/ArrayType.js +2 -3
  101. package/types/BigIntType.d.ts +8 -6
  102. package/types/BigIntType.js +1 -1
  103. package/types/BlobType.d.ts +0 -1
  104. package/types/BlobType.js +0 -3
  105. package/types/BooleanType.d.ts +2 -1
  106. package/types/BooleanType.js +3 -0
  107. package/types/DecimalType.d.ts +6 -4
  108. package/types/DecimalType.js +3 -3
  109. package/types/DoubleType.js +2 -2
  110. package/types/EnumArrayType.js +1 -2
  111. package/types/JsonType.d.ts +1 -1
  112. package/types/JsonType.js +7 -2
  113. package/types/TinyIntType.js +1 -1
  114. package/types/Type.d.ts +2 -4
  115. package/types/Type.js +3 -3
  116. package/types/Uint8ArrayType.d.ts +0 -1
  117. package/types/Uint8ArrayType.js +1 -4
  118. package/types/index.d.ts +1 -1
  119. package/typings.d.ts +124 -86
  120. package/typings.js +50 -42
  121. package/unit-of-work/ChangeSet.d.ts +2 -6
  122. package/unit-of-work/ChangeSet.js +4 -5
  123. package/unit-of-work/ChangeSetComputer.d.ts +1 -3
  124. package/unit-of-work/ChangeSetComputer.js +14 -12
  125. package/unit-of-work/ChangeSetPersister.d.ts +5 -4
  126. package/unit-of-work/ChangeSetPersister.js +65 -33
  127. package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
  128. package/unit-of-work/CommitOrderCalculator.js +13 -13
  129. package/unit-of-work/UnitOfWork.d.ts +10 -3
  130. package/unit-of-work/UnitOfWork.js +139 -96
  131. package/utils/AbstractSchemaGenerator.d.ts +5 -5
  132. package/utils/AbstractSchemaGenerator.js +18 -16
  133. package/utils/AsyncContext.d.ts +6 -0
  134. package/utils/AsyncContext.js +42 -0
  135. package/utils/Configuration.d.ts +753 -207
  136. package/utils/Configuration.js +145 -190
  137. package/utils/ConfigurationLoader.d.ts +1 -54
  138. package/utils/ConfigurationLoader.js +1 -352
  139. package/utils/Cursor.d.ts +0 -3
  140. package/utils/Cursor.js +9 -6
  141. package/utils/DataloaderUtils.d.ts +15 -5
  142. package/utils/DataloaderUtils.js +65 -17
  143. package/utils/EntityComparator.d.ts +13 -9
  144. package/utils/EntityComparator.js +85 -43
  145. package/utils/QueryHelper.d.ts +14 -6
  146. package/utils/QueryHelper.js +87 -25
  147. package/utils/RawQueryFragment.d.ts +48 -25
  148. package/utils/RawQueryFragment.js +66 -70
  149. package/utils/RequestContext.js +2 -2
  150. package/utils/TransactionContext.js +2 -2
  151. package/utils/TransactionManager.d.ts +65 -0
  152. package/utils/TransactionManager.js +223 -0
  153. package/utils/Utils.d.ts +12 -119
  154. package/utils/Utils.js +97 -373
  155. package/utils/clone.js +8 -23
  156. package/utils/env-vars.d.ts +7 -0
  157. package/utils/env-vars.js +97 -0
  158. package/utils/fs-utils.d.ts +32 -0
  159. package/utils/fs-utils.js +178 -0
  160. package/utils/index.d.ts +2 -1
  161. package/utils/index.js +2 -1
  162. package/utils/upsert-utils.d.ts +9 -4
  163. package/utils/upsert-utils.js +55 -4
  164. package/decorators/Check.d.ts +0 -3
  165. package/decorators/Check.js +0 -13
  166. package/decorators/CreateRequestContext.d.ts +0 -3
  167. package/decorators/CreateRequestContext.js +0 -32
  168. package/decorators/Embeddable.d.ts +0 -8
  169. package/decorators/Embeddable.js +0 -11
  170. package/decorators/Embedded.d.ts +0 -18
  171. package/decorators/Embedded.js +0 -18
  172. package/decorators/Entity.d.ts +0 -18
  173. package/decorators/Entity.js +0 -12
  174. package/decorators/Enum.d.ts +0 -9
  175. package/decorators/Enum.js +0 -16
  176. package/decorators/Filter.d.ts +0 -2
  177. package/decorators/Filter.js +0 -8
  178. package/decorators/Formula.d.ts +0 -4
  179. package/decorators/Formula.js +0 -15
  180. package/decorators/Indexed.d.ts +0 -19
  181. package/decorators/Indexed.js +0 -20
  182. package/decorators/ManyToMany.d.ts +0 -40
  183. package/decorators/ManyToMany.js +0 -14
  184. package/decorators/ManyToOne.d.ts +0 -30
  185. package/decorators/ManyToOne.js +0 -14
  186. package/decorators/OneToMany.d.ts +0 -28
  187. package/decorators/OneToMany.js +0 -17
  188. package/decorators/OneToOne.d.ts +0 -24
  189. package/decorators/OneToOne.js +0 -7
  190. package/decorators/PrimaryKey.d.ts +0 -8
  191. package/decorators/PrimaryKey.js +0 -20
  192. package/decorators/Property.d.ts +0 -250
  193. package/decorators/Property.js +0 -32
  194. package/decorators/Transactional.d.ts +0 -13
  195. package/decorators/Transactional.js +0 -28
  196. package/decorators/hooks.d.ts +0 -16
  197. package/decorators/hooks.js +0 -47
  198. package/decorators/index.d.ts +0 -17
  199. package/decorators/index.js +0 -17
  200. package/entity/ArrayCollection.d.ts +0 -116
  201. package/entity/ArrayCollection.js +0 -402
  202. package/entity/EntityValidator.d.ts +0 -19
  203. package/entity/EntityValidator.js +0 -150
  204. package/metadata/ReflectMetadataProvider.d.ts +0 -8
  205. package/metadata/ReflectMetadataProvider.js +0 -44
  206. package/utils/resolveContextProvider.d.ts +0 -10
  207. package/utils/resolveContextProvider.js +0 -28
package/EntityManager.js CHANGED
@@ -1,15 +1,13 @@
1
- import { inspect } from 'node:util';
2
- import DataLoader from 'dataloader';
3
- import { getOnConflictReturningFields } from './utils/upsert-utils.js';
1
+ import { getOnConflictReturningFields, getWhereCondition } from './utils/upsert-utils.js';
4
2
  import { Utils } from './utils/Utils.js';
5
3
  import { Cursor } from './utils/Cursor.js';
6
4
  import { DataloaderUtils } from './utils/DataloaderUtils.js';
7
5
  import { QueryHelper } from './utils/QueryHelper.js';
8
6
  import { TransactionContext } from './utils/TransactionContext.js';
9
- import { isRaw, RawQueryFragment } from './utils/RawQueryFragment.js';
7
+ import { isRaw, Raw } from './utils/RawQueryFragment.js';
10
8
  import { EntityFactory } from './entity/EntityFactory.js';
11
9
  import { EntityAssigner } from './entity/EntityAssigner.js';
12
- import { EntityValidator } from './entity/EntityValidator.js';
10
+ import { validateEmptyWhere, validateParams, validatePrimaryKey, validateProperty } from './entity/validators.js';
13
11
  import { EntityLoader } from './entity/EntityLoader.js';
14
12
  import { Reference } from './entity/Reference.js';
15
13
  import { helper } from './entity/wrap.js';
@@ -19,6 +17,8 @@ import { EventType, FlushMode, LoadStrategy, LockMode, PopulateHint, PopulatePat
19
17
  import { EventManager } from './events/EventManager.js';
20
18
  import { TransactionEventBroadcaster } from './events/TransactionEventBroadcaster.js';
21
19
  import { OptimisticLockError, ValidationError } from './errors.js';
20
+ import { getLoadingStrategy } from './entity/utils.js';
21
+ import { TransactionManager } from './utils/TransactionManager.js';
22
22
  /**
23
23
  * The EntityManager is the central access point to ORM functionality. It is a facade to all different ORM subsystems
24
24
  * such as UnitOfWork, Query Language, and Repository API.
@@ -34,10 +34,8 @@ export class EntityManager {
34
34
  _id = EntityManager.counter++;
35
35
  global = false;
36
36
  name;
37
- refLoader = new DataLoader(DataloaderUtils.getRefBatchLoadFn(this));
38
- colLoader = new DataLoader(DataloaderUtils.getColBatchLoadFn(this));
39
- validator;
40
- repositoryMap = {};
37
+ loaders = {};
38
+ repositoryMap = new Map();
41
39
  entityLoader;
42
40
  comparator;
43
41
  entityFactory;
@@ -61,7 +59,6 @@ export class EntityManager {
61
59
  this.eventManager = eventManager;
62
60
  this.entityLoader = new EntityLoader(this);
63
61
  this.name = this.config.get('contextName');
64
- this.validator = new EntityValidator(this.config.get('strict'));
65
62
  this.comparator = this.config.getComparator(this.metadata);
66
63
  this.resultCache = this.config.getResultCacheAdapter();
67
64
  this.disableTransactions = this.config.get('disableTransactions');
@@ -91,13 +88,12 @@ export class EntityManager {
91
88
  * Gets repository for given entity. You can pass either string name or entity class reference.
92
89
  */
93
90
  getRepository(entityName) {
94
- entityName = Utils.className(entityName);
95
- if (!this.repositoryMap[entityName]) {
96
- const meta = this.metadata.get(entityName);
91
+ const meta = this.metadata.get(entityName);
92
+ if (!this.repositoryMap.has(meta)) {
97
93
  const RepositoryClass = this.config.getRepositoryClass(meta.repository);
98
- this.repositoryMap[entityName] = new RepositoryClass(this, entityName);
94
+ this.repositoryMap.set(meta, new RepositoryClass(this, entityName));
99
95
  }
100
- return this.repositoryMap[entityName];
96
+ return this.repositoryMap.get(meta);
101
97
  }
102
98
  /**
103
99
  * Shortcut for `em.getRepository()`.
@@ -105,12 +101,6 @@ export class EntityManager {
105
101
  repo(entityName) {
106
102
  return this.getRepository(entityName);
107
103
  }
108
- /**
109
- * Gets EntityValidator instance
110
- */
111
- getValidator() {
112
- return this.validator;
113
- }
114
104
  /**
115
105
  * Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
116
106
  */
@@ -125,9 +115,8 @@ export class EntityManager {
125
115
  const em = this.getContext();
126
116
  em.prepareOptions(options);
127
117
  await em.tryFlush(entityName, options);
128
- entityName = Utils.className(entityName);
129
118
  where = await em.processWhere(entityName, where, options, 'read');
130
- em.validator.validateParams(where);
119
+ validateParams(where);
131
120
  options.orderBy = options.orderBy || {};
132
121
  options.populate = await em.preparePopulate(entityName, options);
133
122
  const populate = options.populate;
@@ -137,7 +126,6 @@ export class EntityManager {
137
126
  await em.entityLoader.populate(entityName, cached.data, populate, {
138
127
  ...options,
139
128
  ...em.getPopulateWhere(where, options),
140
- convertCustomTypes: false,
141
129
  ignoreLazyScalarProperties: true,
142
130
  lookup: false,
143
131
  });
@@ -148,7 +136,7 @@ export class EntityManager {
148
136
  // save the original hint value so we know it was infer/all
149
137
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
150
138
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
151
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
139
+ options.populateFilter = await this.getJoinedFilters(meta, options);
152
140
  const results = await em.driver.find(entityName, where, { ctx: em.transactionContext, em, ...options });
153
141
  if (results.length === 0) {
154
142
  await em.storeCache(options.cache, cached, []);
@@ -168,7 +156,6 @@ export class EntityManager {
168
156
  await em.entityLoader.populate(entityName, unique, populate, {
169
157
  ...options,
170
158
  ...em.getPopulateWhere(where, options),
171
- convertCustomTypes: false,
172
159
  ignoreLazyScalarProperties: true,
173
160
  lookup: false,
174
161
  });
@@ -181,6 +168,60 @@ export class EntityManager {
181
168
  }
182
169
  return unique;
183
170
  }
171
+ /**
172
+ * Finds all entities and returns an async iterable (async generator) that yields results one by one.
173
+ * The results are merged and mapped to entity instances, without adding them to the identity map.
174
+ * You can disable merging by passing the options `{ mergeResults: false }`.
175
+ * With `mergeResults` disabled, to-many collections will contain at most one item, and you will get duplicate
176
+ * root entities when there are multiple items in the populated collection.
177
+ * This is useful for processing large datasets without loading everything into memory at once.
178
+ *
179
+ * ```ts
180
+ * const stream = em.stream(Book, { populate: ['author'] });
181
+ *
182
+ * for await (const book of stream) {
183
+ * // book is an instance of Book entity
184
+ * console.log(book.title, book.author.name);
185
+ * }
186
+ * ```
187
+ */
188
+ async *stream(entityName, options = {}) {
189
+ const em = this.getContext();
190
+ em.prepareOptions(options);
191
+ options.strategy = 'joined';
192
+ await em.tryFlush(entityName, options);
193
+ const where = await em.processWhere(entityName, options.where ?? {}, options, 'read');
194
+ validateParams(where);
195
+ options.orderBy = options.orderBy || {};
196
+ options.populate = await em.preparePopulate(entityName, options);
197
+ const meta = this.metadata.get(entityName);
198
+ options = { ...options };
199
+ // save the original hint value so we know it was infer/all
200
+ options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
201
+ options.populateWhere = this.createPopulateWhere({ ...where }, options);
202
+ options.populateFilter = await this.getJoinedFilters(meta, options);
203
+ const stream = em.driver.stream(entityName, where, {
204
+ ctx: em.transactionContext,
205
+ mapResults: false,
206
+ ...options,
207
+ });
208
+ for await (const data of stream) {
209
+ const fork = em.fork();
210
+ const entity = fork.entityFactory.create(entityName, data, {
211
+ refresh: options.refresh,
212
+ schema: options.schema,
213
+ convertCustomTypes: true,
214
+ });
215
+ helper(entity).setSerializationContext({
216
+ populate: options.populate,
217
+ fields: options.fields,
218
+ exclude: options.exclude,
219
+ });
220
+ await fork.unitOfWork.dispatchOnLoadEvent();
221
+ fork.clear();
222
+ yield entity;
223
+ }
224
+ }
184
225
  /**
185
226
  * Finds all entities of given type, optionally matching the `where` condition provided in the `options` parameter.
186
227
  */
@@ -194,7 +235,7 @@ export class EntityManager {
194
235
  if (options.populateWhere === PopulateHint.ALL) {
195
236
  return { where: {}, populateWhere: options.populateWhere };
196
237
  }
197
- /* v8 ignore next 3 */
238
+ /* v8 ignore next */
198
239
  if (options.populateWhere === PopulateHint.INFER) {
199
240
  return { where, populateWhere: options.populateWhere };
200
241
  }
@@ -203,12 +244,12 @@ export class EntityManager {
203
244
  /**
204
245
  * Registers global filter to this entity manager. Global filters are enabled by default (unless disabled via last parameter).
205
246
  */
206
- addFilter(name, cond, entityName, enabled = true) {
207
- const options = { name, cond, default: enabled };
208
- if (entityName) {
209
- options.entity = Utils.asArray(entityName).map(n => Utils.className(n));
247
+ addFilter(options) {
248
+ if (options.entity) {
249
+ options.entity = Utils.asArray(options.entity).map(n => Utils.className(n));
210
250
  }
211
- this.getContext(false).filters[name] = options;
251
+ options.default ??= true;
252
+ this.getContext(false).filters[options.name] = options;
212
253
  }
213
254
  /**
214
255
  * Sets filter parameter values globally inside context defined by this entity manager.
@@ -232,8 +273,8 @@ export class EntityManager {
232
273
  /**
233
274
  * Gets logger context for this entity manager.
234
275
  */
235
- getLoggerContext() {
236
- const em = this.getContext();
276
+ getLoggerContext(options) {
277
+ const em = options?.disableContextResolution ? this : this.getContext();
237
278
  em.loggerContext ??= {};
238
279
  return em.loggerContext;
239
280
  }
@@ -259,15 +300,15 @@ export class EntityManager {
259
300
  if (!meta?.discriminatorValue) {
260
301
  return where;
261
302
  }
262
- const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.find(cls));
303
+ const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.get(cls));
263
304
  const children = [];
264
305
  const lookUpChildren = (ret, type) => {
265
306
  const children = types.filter(meta2 => meta2.extends === type);
266
- children.forEach(m => lookUpChildren(ret, m.className));
307
+ children.forEach(m => lookUpChildren(ret, m.class));
267
308
  ret.push(...children.filter(c => c.discriminatorValue));
268
309
  return children;
269
310
  };
270
- lookUpChildren(children, meta.className);
311
+ lookUpChildren(children, meta.class);
271
312
  /* v8 ignore next */
272
313
  where[meta.root.discriminatorColumn] = children.length > 0 ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] } : meta.discriminatorValue;
273
314
  return where;
@@ -283,28 +324,39 @@ export class EntityManager {
283
324
  }
284
325
  return ret;
285
326
  }
286
- async getJoinedFilters(meta, cond, options) {
327
+ async getJoinedFilters(meta, options) {
328
+ if (!this.config.get('filtersOnRelations') || !options.populate) {
329
+ return undefined;
330
+ }
287
331
  const ret = {};
288
- if (options.populate) {
289
- for (const hint of options.populate) {
290
- const field = hint.field.split(':')[0];
291
- const prop = meta.properties[field];
292
- const joined = (prop.strategy || options.strategy || hint.strategy || this.config.get('loadStrategy')) === LoadStrategy.JOINED && prop.kind !== ReferenceKind.SCALAR;
293
- if (!joined && !hint.filter) {
294
- continue;
295
- }
296
- const where = await this.applyFilters(prop.type, {}, options.filters ?? {}, 'read', { ...options, populate: hint.children });
297
- const where2 = await this.getJoinedFilters(prop.targetMeta, {}, { ...options, populate: hint.children, populateWhere: PopulateHint.ALL });
298
- if (Utils.hasObjectKeys(where)) {
299
- ret[field] = ret[field] ? { $and: [where, ret[field]] } : where;
332
+ for (const hint of options.populate) {
333
+ const field = hint.field.split(':')[0];
334
+ const prop = meta.properties[field];
335
+ const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
336
+ const joined = strategy === LoadStrategy.JOINED && prop.kind !== ReferenceKind.SCALAR;
337
+ if (!joined && !hint.filter) {
338
+ continue;
339
+ }
340
+ const filters = QueryHelper.mergePropertyFilters(prop.filters, options.filters);
341
+ const where = await this.applyFilters(prop.targetMeta.class, {}, filters, 'read', {
342
+ ...options,
343
+ populate: hint.children,
344
+ });
345
+ const where2 = await this.getJoinedFilters(prop.targetMeta, {
346
+ ...options,
347
+ filters,
348
+ populate: hint.children,
349
+ populateWhere: PopulateHint.ALL,
350
+ });
351
+ if (Utils.hasObjectKeys(where)) {
352
+ ret[field] = ret[field] ? { $and: [where, ret[field]] } : where;
353
+ }
354
+ if (where2 && Utils.hasObjectKeys(where2)) {
355
+ if (ret[field]) {
356
+ Utils.merge(ret[field], where2);
300
357
  }
301
- if (Utils.hasObjectKeys(where2)) {
302
- if (ret[field]) {
303
- Utils.merge(ret[field], where2);
304
- }
305
- else {
306
- ret[field] = where2;
307
- }
358
+ else {
359
+ ret[field] = where2;
308
360
  }
309
361
  }
310
362
  }
@@ -313,42 +365,57 @@ export class EntityManager {
313
365
  /**
314
366
  * When filters are active on M:1 or 1:1 relations, we need to ref join them eagerly as they might affect the FK value.
315
367
  */
316
- async autoJoinRefsForFilters(meta, options) {
317
- if (!meta || !this.config.get('autoJoinRefsForFilters')) {
368
+ async autoJoinRefsForFilters(meta, options, parent) {
369
+ if (!meta || !this.config.get('autoJoinRefsForFilters') || options.filters === false) {
318
370
  return;
319
371
  }
320
- const props = meta.relations.filter(prop => {
321
- return !prop.object && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)
322
- && ((options.fields?.length ?? 0) === 0 || options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)));
323
- });
324
372
  const ret = options.populate;
325
- for (const prop of props) {
326
- const cond = await this.applyFilters(prop.type, {}, options.filters ?? {}, 'read', options);
373
+ for (const prop of meta.relations) {
374
+ if (prop.object
375
+ || ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)
376
+ || !((options.fields?.length ?? 0) === 0 || options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
377
+ || (parent?.class === prop.targetMeta.root.class && parent.propName === prop.inversedBy)) {
378
+ continue;
379
+ }
380
+ options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
381
+ const cond = await this.applyFilters(prop.targetMeta.class, {}, options.filters, 'read', options);
327
382
  if (!Utils.isEmpty(cond)) {
328
383
  const populated = options.populate.filter(({ field }) => field.split(':')[0] === prop.name);
329
- if (populated.length > 0) {
330
- populated.forEach(hint => hint.filter = true);
384
+ let found = false;
385
+ for (const hint of populated) {
386
+ if (!hint.all) {
387
+ hint.filter = true;
388
+ }
389
+ const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
390
+ if (hint.field === `${prop.name}:ref` || (hint.filter && strategy === LoadStrategy.JOINED)) {
391
+ found = true;
392
+ }
331
393
  }
332
- else {
394
+ if (!found) {
333
395
  ret.push({ field: `${prop.name}:ref`, strategy: LoadStrategy.JOINED, filter: true });
334
396
  }
335
397
  }
336
398
  }
399
+ for (const hint of ret) {
400
+ const [field, ref] = hint.field.split(':');
401
+ const prop = meta?.properties[field];
402
+ if (prop && !ref) {
403
+ hint.children ??= [];
404
+ await this.autoJoinRefsForFilters(prop.targetMeta, { ...options, populate: hint.children }, { class: meta.root.class, propName: prop.name });
405
+ }
406
+ }
337
407
  }
338
408
  /**
339
409
  * @internal
340
410
  */
341
411
  async applyFilters(entityName, where, options, type, findOptions) {
342
- const meta = this.metadata.find(entityName);
412
+ const meta = this.metadata.get(entityName);
343
413
  const filters = [];
344
414
  const ret = [];
345
- if (!meta) {
346
- return where;
347
- }
348
415
  const active = new Set();
349
416
  const push = (source) => {
350
417
  const activeFilters = QueryHelper
351
- .getActiveFilters(entityName, options, source)
418
+ .getActiveFilters(meta, options, source)
352
419
  .filter(f => !active.has(f.name));
353
420
  filters.push(...activeFilters);
354
421
  activeFilters.forEach(f => active.add(f.name));
@@ -363,24 +430,28 @@ export class EntityManager {
363
430
  let cond;
364
431
  if (filter.cond instanceof Function) {
365
432
  // @ts-ignore
366
- const args = Utils.isPlainObject(options[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
433
+ const args = Utils.isPlainObject(options?.[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
367
434
  if (!args && filter.cond.length > 0 && filter.args !== false) {
368
435
  throw new Error(`No arguments provided for filter '${filter.name}'`);
369
436
  }
370
- cond = await filter.cond(args, type, this, findOptions, entityName);
437
+ cond = await filter.cond(args, type, this, findOptions, Utils.className(entityName));
371
438
  }
372
439
  else {
373
440
  cond = filter.cond;
374
441
  }
375
- ret.push(QueryHelper.processWhere({
442
+ cond = QueryHelper.processWhere({
376
443
  where: cond,
377
444
  entityName,
378
445
  metadata: this.metadata,
379
446
  platform: this.driver.getPlatform(),
380
447
  aliased: type === 'read',
381
- }));
448
+ });
449
+ if (filter.strict) {
450
+ Object.defineProperty(cond, '__strict', { value: filter.strict, enumerable: false });
451
+ }
452
+ ret.push(cond);
382
453
  }
383
- const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c));
454
+ const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c) || Raw.hasObjectFragments(c));
384
455
  return conds.length > 1 ? { $and: conds } : conds[0];
385
456
  }
386
457
  /**
@@ -391,12 +462,10 @@ export class EntityManager {
391
462
  const em = this.getContext(false);
392
463
  await em.tryFlush(entityName, options);
393
464
  options.flushMode = 'commit'; // do not try to auto flush again
394
- return RawQueryFragment.run(async () => {
395
- return Promise.all([
396
- em.find(entityName, where, options),
397
- em.count(entityName, where, options),
398
- ]);
399
- });
465
+ return Promise.all([
466
+ em.find(entityName, where, options),
467
+ em.count(entityName, where, options),
468
+ ]);
400
469
  }
401
470
  /**
402
471
  * Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as {@apilink Cursor} object.
@@ -412,21 +481,21 @@ export class EntityManager {
412
481
  * - POJO/entity instance
413
482
  *
414
483
  * ```ts
415
- * const currentCursor = await em.findByCursor(User, {}, {
484
+ * const currentCursor = await em.findByCursor(User, {
416
485
  * first: 10,
417
486
  * after: previousCursor, // cursor instance
418
487
  * orderBy: { id: 'desc' },
419
488
  * });
420
489
  *
421
490
  * // to fetch next page
422
- * const nextCursor = await em.findByCursor(User, {}, {
491
+ * const nextCursor = await em.findByCursor(User, {
423
492
  * first: 10,
424
493
  * after: currentCursor.endCursor, // opaque string
425
494
  * orderBy: { id: 'desc' },
426
495
  * });
427
496
  *
428
497
  * // to fetch next page
429
- * const nextCursor2 = await em.findByCursor(User, {}, {
498
+ * const nextCursor2 = await em.findByCursor(User, {
430
499
  * first: 10,
431
500
  * after: { id: lastSeenId }, // entity-like POJO
432
501
  * orderBy: { id: 'desc' },
@@ -454,16 +523,16 @@ export class EntityManager {
454
523
  * }
455
524
  * ```
456
525
  */
457
- async findByCursor(entityName, where, options) {
526
+ async findByCursor(entityName, options) {
458
527
  const em = this.getContext(false);
459
- entityName = Utils.className(entityName);
460
528
  options.overfetch ??= true;
461
- if (Utils.isEmpty(options.orderBy)) {
529
+ options.where ??= {};
530
+ if (Utils.isEmpty(options.orderBy) && !Raw.hasObjectFragments(options.orderBy)) {
462
531
  throw new Error('Explicit `orderBy` option required');
463
532
  }
464
533
  const [entities, count] = options.includeCount !== false
465
- ? await em.findAndCount(entityName, where, options)
466
- : [await em.find(entityName, where, options)];
534
+ ? await em.findAndCount(entityName, options.where, options)
535
+ : [await em.find(entityName, options.where, options)];
467
536
  return new Cursor(entities, count, options, this.metadata.get(entityName));
468
537
  }
469
538
  /**
@@ -475,9 +544,9 @@ export class EntityManager {
475
544
  const ret = await this.refresh(entity, options);
476
545
  if (!ret) {
477
546
  options.failHandler ??= this.config.get('findOneOrFailHandler');
478
- const entityName = entity.constructor.name;
479
- const where = helper(entity).getPrimaryKey();
480
- throw options.failHandler(entityName, where);
547
+ const wrapped = helper(entity);
548
+ const where = wrapped.getPrimaryKey();
549
+ throw options.failHandler(wrapped.__meta.className, where);
481
550
  }
482
551
  return ret;
483
552
  }
@@ -488,19 +557,31 @@ export class EntityManager {
488
557
  */
489
558
  async refresh(entity, options = {}) {
490
559
  const fork = this.fork({ keepTransactionContext: true });
491
- const entityName = entity.constructor.name;
492
- const reloaded = await fork.findOne(entityName, entity, {
493
- schema: helper(entity).__schema,
560
+ const wrapped = helper(entity);
561
+ const reloaded = await fork.findOne(wrapped.__meta.class, entity, {
562
+ schema: wrapped.__schema,
494
563
  ...options,
495
564
  flushMode: FlushMode.COMMIT,
496
565
  });
497
- if (reloaded) {
498
- this.config.getHydrator(this.metadata).hydrate(entity, helper(entity).__meta, helper(reloaded).toPOJO(), this.getEntityFactory(), 'full');
566
+ const em = this.getContext();
567
+ if (!reloaded) {
568
+ em.unitOfWork.unsetIdentity(entity);
569
+ return null;
499
570
  }
500
- else {
501
- this.getUnitOfWork().unsetIdentity(entity);
571
+ let found = false;
572
+ for (const e of fork.unitOfWork.getIdentityMap()) {
573
+ const ref = em.getReference(e.constructor, helper(e).getPrimaryKey());
574
+ const data = helper(e).serialize({ ignoreSerializers: true, includeHidden: true, convertCustomTypes: true });
575
+ em.config.getHydrator(this.metadata).hydrate(ref, helper(ref).__meta, data, em.entityFactory, 'full', false, true);
576
+ Utils.merge(helper(ref).__originalEntityData, this.comparator.prepareEntity(e));
577
+ found ||= ref === entity;
578
+ }
579
+ if (!found) {
580
+ const data = helper(reloaded).serialize({ ignoreSerializers: true, includeHidden: true, convertCustomTypes: true });
581
+ em.config.getHydrator(this.metadata).hydrate(entity, wrapped.__meta, data, em.entityFactory, 'full', false, true);
582
+ Utils.merge(wrapped.__originalEntityData, this.comparator.prepareEntity(reloaded));
502
583
  }
503
- return reloaded ? entity : reloaded;
584
+ return entity;
504
585
  }
505
586
  /**
506
587
  * Finds first entity matching your `where` query.
@@ -514,7 +595,6 @@ export class EntityManager {
514
595
  return ret;
515
596
  }
516
597
  const em = this.getContext();
517
- entityName = Utils.className(entityName);
518
598
  em.prepareOptions(options);
519
599
  let entity = em.unitOfWork.tryGetById(entityName, where, options.schema);
520
600
  // query for a not managed entity which is already in the identity map as it
@@ -526,31 +606,32 @@ export class EntityManager {
526
606
  await em.tryFlush(entityName, options);
527
607
  const meta = em.metadata.get(entityName);
528
608
  where = await em.processWhere(entityName, where, options, 'read');
529
- em.validator.validateEmptyWhere(where);
609
+ validateEmptyWhere(where);
530
610
  em.checkLockRequirements(options.lockMode, meta);
531
- const isOptimisticLocking = !Utils.isDefined(options.lockMode) || options.lockMode === LockMode.OPTIMISTIC;
611
+ const isOptimisticLocking = options.lockMode == null || options.lockMode === LockMode.OPTIMISTIC;
532
612
  if (entity && !em.shouldRefresh(meta, entity, options) && isOptimisticLocking) {
533
613
  return em.lockAndPopulate(meta, entity, where, options);
534
614
  }
535
- em.validator.validateParams(where);
615
+ validateParams(where);
536
616
  options.populate = await em.preparePopulate(entityName, options);
537
617
  const cacheKey = em.cacheKey(entityName, options, 'em.findOne', where);
538
618
  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
- });
619
+ if (cached?.data !== undefined) {
620
+ if (cached.data) {
621
+ await em.entityLoader.populate(entityName, [cached.data], options.populate, {
622
+ ...options,
623
+ ...em.getPopulateWhere(where, options),
624
+ ignoreLazyScalarProperties: true,
625
+ lookup: false,
626
+ });
627
+ }
547
628
  return cached.data;
548
629
  }
549
630
  options = { ...options };
550
631
  // save the original hint value so we know it was infer/all
551
632
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
552
633
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
553
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
634
+ options.populateFilter = await this.getJoinedFilters(meta, options);
554
635
  const data = await em.driver.findOne(entityName, where, {
555
636
  ctx: em.transactionContext,
556
637
  em,
@@ -591,10 +672,10 @@ export class EntityManager {
591
672
  if (!entity || isStrictViolation) {
592
673
  const key = options.strict ? 'findExactlyOneOrFailHandler' : 'findOneOrFailHandler';
593
674
  options.failHandler ??= this.config.get(key);
594
- entityName = Utils.className(entityName);
675
+ const name = Utils.className(entityName);
595
676
  /* v8 ignore next */
596
677
  where = Utils.isEntity(where) ? helper(where).getPrimaryKey() : where;
597
- throw options.failHandler(entityName, where);
678
+ throw options.failHandler(name, where);
598
679
  }
599
680
  return entity;
600
681
  }
@@ -634,11 +715,11 @@ export class EntityManager {
634
715
  let where;
635
716
  let entity = null;
636
717
  if (data === undefined) {
637
- entityName = entityNameOrEntity.constructor.name;
718
+ entityName = entityNameOrEntity.constructor;
638
719
  data = entityNameOrEntity;
639
720
  }
640
721
  else {
641
- entityName = Utils.className(entityNameOrEntity);
722
+ entityName = entityNameOrEntity;
642
723
  }
643
724
  const meta = this.metadata.get(entityName);
644
725
  const convertCustomTypes = !Utils.isEntity(data);
@@ -661,26 +742,9 @@ export class EntityManager {
661
742
  }
662
743
  }
663
744
  }
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
- }
745
+ where = getWhereCondition(meta, options.onConflictFields, data, where).where;
682
746
  data = QueryHelper.processObjectParams(data);
683
- em.validator.validateParams(data, 'insert data');
747
+ validateParams(data, 'insert data');
684
748
  if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
685
749
  await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity: data, em, meta }, meta);
686
750
  }
@@ -718,11 +782,12 @@ export class EntityManager {
718
782
  where[meta.primaryKeys[0]] = ret.insertId;
719
783
  }
720
784
  }
721
- const data2 = await this.driver.findOne(meta.className, where, {
785
+ const data2 = await this.driver.findOne(meta.class, where, {
722
786
  fields: returning,
723
787
  ctx: em.transactionContext,
724
788
  convertCustomTypes: true,
725
789
  connectionType: 'write',
790
+ schema: options.schema,
726
791
  });
727
792
  em.getHydrator().hydrate(entity, meta, data2, em.entityFactory, 'full', false, true);
728
793
  }
@@ -772,11 +837,11 @@ export class EntityManager {
772
837
  let entityName;
773
838
  let propIndex;
774
839
  if (data === undefined) {
775
- entityName = entityNameOrEntity[0].constructor.name;
840
+ entityName = entityNameOrEntity[0].constructor;
776
841
  data = entityNameOrEntity;
777
842
  }
778
843
  else {
779
- entityName = Utils.className(entityNameOrEntity);
844
+ entityName = entityNameOrEntity;
780
845
  }
781
846
  const batchSize = options.batchSize ?? this.config.get('batchSize');
782
847
  if (data.length > batchSize) {
@@ -820,32 +885,18 @@ export class EntityManager {
820
885
  }
821
886
  }
822
887
  }
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);
888
+ const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
889
+ propIndex = !isRaw(unique) && unique.findIndex(p => data[p] ?? data[p.substring(0, p.indexOf('.'))] != null);
890
+ const tmp = getWhereCondition(meta, options.onConflictFields, row, where);
891
+ propIndex = tmp.propIndex;
842
892
  where = QueryHelper.processWhere({
843
- where,
893
+ where: tmp.where,
844
894
  entityName,
845
895
  metadata: this.metadata,
846
896
  platform: this.getPlatform(),
847
897
  });
848
- em.validator.validateParams(row, 'insert data');
898
+ row = QueryHelper.processObjectParams(row);
899
+ validateParams(row, 'insert data');
849
900
  allData.push(row);
850
901
  allWhere.push(where);
851
902
  }
@@ -886,7 +937,7 @@ export class EntityManager {
886
937
  const reloadFields = returning.length > 0 && !(this.getPlatform().usesReturningStatement() && res.rows?.length);
887
938
  if (options.onConflictAction === 'ignore' || (!res.rows?.length && loadPK.size > 0) || reloadFields) {
888
939
  const unique = meta.hydrateProps.filter(p => !p.lazy).map(p => p.name);
889
- const add = new Set(propIndex >= 0 ? [unique[propIndex]] : []);
940
+ const add = new Set(propIndex !== false && propIndex >= 0 ? [unique[propIndex]] : []);
890
941
  for (const cond of loadPK.values()) {
891
942
  Utils.keys(cond).forEach(key => add.add(key));
892
943
  }
@@ -898,11 +949,12 @@ export class EntityManager {
898
949
  where.$or[idx][prop] = item[prop];
899
950
  });
900
951
  });
901
- const data2 = await this.driver.find(meta.className, where, {
952
+ const data2 = await this.driver.find(meta.class, where, {
902
953
  fields: returning.concat(...add).concat(...(Array.isArray(uniqueFields) ? uniqueFields : [])),
903
954
  ctx: em.transactionContext,
904
955
  convertCustomTypes: true,
905
956
  connectionType: 'write',
957
+ schema: options.schema,
906
958
  });
907
959
  for (const [entity, cond] of loadPK.entries()) {
908
960
  const row = data2.find(row => {
@@ -914,7 +966,7 @@ export class EntityManager {
914
966
  });
915
967
  return this.comparator.matching(entityName, cond, tmp);
916
968
  });
917
- /* v8 ignore next 3 */
969
+ /* v8 ignore next */
918
970
  if (!row) {
919
971
  throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
920
972
  }
@@ -937,7 +989,7 @@ export class EntityManager {
937
989
  }, {});
938
990
  return this.comparator.matching(entityName, cond, pk);
939
991
  });
940
- /* v8 ignore next 3 */
992
+ /* v8 ignore next */
941
993
  if (!row) {
942
994
  throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
943
995
  }
@@ -959,45 +1011,37 @@ export class EntityManager {
959
1011
  }
960
1012
  /**
961
1013
  * Runs your callback wrapped inside a database transaction.
1014
+ *
1015
+ * If a transaction is already active, a new savepoint (nested transaction) will be created by default. This behavior
1016
+ * can be controlled via the `propagation` option. Use the provided EntityManager instance for all operations that
1017
+ * should be part of the transaction. You can safely use a global EntityManager instance from a DI container, as this
1018
+ * method automatically creates an async context for the transaction.
1019
+ *
1020
+ * **Concurrency note:** When running multiple transactions concurrently (e.g. in parallel requests or jobs), use the
1021
+ * `clear: true` option. This ensures the callback runs in a clear fork of the EntityManager, providing full isolation
1022
+ * between concurrent transactional handlers. Using `clear: true` is an alternative to forking explicitly and calling
1023
+ * the method on the new fork – it already provides the necessary isolation for safe concurrent usage.
1024
+ *
1025
+ * **Propagation note:** Changes made within a transaction (whether top-level or nested) are always propagated to the
1026
+ * parent context, unless the parent context is a global one. If you want to avoid that, fork the EntityManager first
1027
+ * and then call this method on the fork.
1028
+ *
1029
+ * **Example:**
1030
+ * ```ts
1031
+ * await em.transactional(async (em) => {
1032
+ * const author = new Author('Jon');
1033
+ * em.persist(author);
1034
+ * // flush is called automatically at the end of the callback
1035
+ * });
1036
+ * ```
962
1037
  */
963
1038
  async transactional(cb, options = {}) {
964
1039
  const em = this.getContext(false);
965
1040
  if (this.disableTransactions || em.disableTransactions) {
966
1041
  return cb(em);
967
1042
  }
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
- });
1043
+ const manager = new TransactionManager(this);
1044
+ return manager.handle(cb, options);
1001
1045
  }
1002
1046
  /**
1003
1047
  * Starts new transaction bound to this EntityManager. Use `ctx` parameter to provide the parent when nesting transactions.
@@ -1058,11 +1102,11 @@ export class EntityManager {
1058
1102
  em.prepareOptions(options);
1059
1103
  let entityName;
1060
1104
  if (data === undefined) {
1061
- entityName = entityNameOrEntity.constructor.name;
1105
+ entityName = entityNameOrEntity.constructor;
1062
1106
  data = entityNameOrEntity;
1063
1107
  }
1064
1108
  else {
1065
- entityName = Utils.className(entityNameOrEntity);
1109
+ entityName = entityNameOrEntity;
1066
1110
  }
1067
1111
  if (Utils.isEntity(data)) {
1068
1112
  if (options.schema && helper(data).getSchema() == null) {
@@ -1081,7 +1125,7 @@ export class EntityManager {
1081
1125
  return cs.getPrimaryKey();
1082
1126
  }
1083
1127
  data = QueryHelper.processObjectParams(data);
1084
- em.validator.validateParams(data, 'insert data');
1128
+ validateParams(data, 'insert data');
1085
1129
  const res = await em.driver.nativeInsert(entityName, data, { ctx: em.transactionContext, ...options });
1086
1130
  return res.insertId;
1087
1131
  }
@@ -1093,11 +1137,11 @@ export class EntityManager {
1093
1137
  em.prepareOptions(options);
1094
1138
  let entityName;
1095
1139
  if (data === undefined) {
1096
- entityName = entityNameOrEntities[0].constructor.name;
1140
+ entityName = entityNameOrEntities[0].constructor;
1097
1141
  data = entityNameOrEntities;
1098
1142
  }
1099
1143
  else {
1100
- entityName = Utils.className(entityNameOrEntities);
1144
+ entityName = entityNameOrEntities;
1101
1145
  }
1102
1146
  if (data.length === 0) {
1103
1147
  return [];
@@ -1121,7 +1165,7 @@ export class EntityManager {
1121
1165
  return css.map(cs => cs.getPrimaryKey());
1122
1166
  }
1123
1167
  data = data.map(row => QueryHelper.processObjectParams(row));
1124
- data.forEach(row => em.validator.validateParams(row, 'insert data'));
1168
+ data.forEach(row => validateParams(row, 'insert data'));
1125
1169
  const res = await em.driver.nativeInsertMany(entityName, data, { ctx: em.transactionContext, ...options });
1126
1170
  if (res.insertedIds) {
1127
1171
  return res.insertedIds;
@@ -1134,11 +1178,10 @@ export class EntityManager {
1134
1178
  async nativeUpdate(entityName, where, data, options = {}) {
1135
1179
  const em = this.getContext(false);
1136
1180
  em.prepareOptions(options);
1137
- entityName = Utils.className(entityName);
1138
1181
  data = QueryHelper.processObjectParams(data);
1139
1182
  where = await em.processWhere(entityName, where, { ...options, convertCustomTypes: false }, 'update');
1140
- em.validator.validateParams(data, 'update data');
1141
- em.validator.validateParams(where, 'update condition');
1183
+ validateParams(data, 'update data');
1184
+ validateParams(where, 'update condition');
1142
1185
  const res = await em.driver.nativeUpdate(entityName, where, data, { ctx: em.transactionContext, ...options });
1143
1186
  return res.affectedRows;
1144
1187
  }
@@ -1148,9 +1191,8 @@ export class EntityManager {
1148
1191
  async nativeDelete(entityName, where, options = {}) {
1149
1192
  const em = this.getContext(false);
1150
1193
  em.prepareOptions(options);
1151
- entityName = Utils.className(entityName);
1152
1194
  where = await em.processWhere(entityName, where, options, 'delete');
1153
- em.validator.validateParams(where, 'delete condition');
1195
+ validateParams(where, 'delete condition');
1154
1196
  const res = await em.driver.nativeDelete(entityName, where, { ctx: em.transactionContext, ...options });
1155
1197
  return res.affectedRows;
1156
1198
  }
@@ -1158,18 +1200,19 @@ export class EntityManager {
1158
1200
  * Maps raw database result to an entity and merges it to this EntityManager.
1159
1201
  */
1160
1202
  map(entityName, result, options = {}) {
1161
- entityName = Utils.className(entityName);
1162
1203
  const meta = this.metadata.get(entityName);
1163
1204
  const data = this.driver.mapResult(result, meta);
1164
- Object.keys(data).forEach(k => {
1205
+ for (const k of Object.keys(data)) {
1165
1206
  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);
1207
+ if (prop?.kind === ReferenceKind.SCALAR && SCALAR_TYPES.has(prop.runtimeType) && !prop.customType && (prop.setter || !prop.getter)) {
1208
+ validateProperty(prop, data[k], data);
1168
1209
  }
1169
- });
1210
+ }
1170
1211
  return this.merge(entityName, data, {
1171
1212
  convertCustomTypes: true,
1172
- refresh: true, ...options,
1213
+ refresh: true,
1214
+ validate: false,
1215
+ ...options,
1173
1216
  });
1174
1217
  }
1175
1218
  /**
@@ -1177,22 +1220,22 @@ export class EntityManager {
1177
1220
  * via second parameter. By default, it will return already loaded entities without modifying them.
1178
1221
  */
1179
1222
  merge(entityName, data, options = {}) {
1180
- const em = this.getContext();
1181
1223
  if (Utils.isEntity(entityName)) {
1182
- return em.merge(entityName.constructor.name, entityName, data);
1224
+ return this.merge(entityName.constructor, entityName, data);
1183
1225
  }
1226
+ const em = options.disableContextResolution ? this : this.getContext();
1184
1227
  options.schema ??= em._schema;
1185
- entityName = Utils.className(entityName);
1186
- em.validator.validatePrimaryKey(data, em.metadata.get(entityName));
1228
+ options.validate ??= true;
1229
+ options.cascade ??= true;
1230
+ validatePrimaryKey(data, em.metadata.get(entityName));
1187
1231
  let entity = em.unitOfWork.tryGetById(entityName, data, options.schema, false);
1188
1232
  if (entity && helper(entity).__managed && helper(entity).__initialized && !options.refresh) {
1189
1233
  return entity;
1190
1234
  }
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);
1235
+ const dataIsEntity = Utils.isEntity(data);
1236
+ entity = dataIsEntity ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
1237
+ const visited = options.cascade ? undefined : new Set([entity]);
1238
+ em.unitOfWork.merge(entity, visited);
1196
1239
  return entity;
1197
1240
  }
1198
1241
  /**
@@ -1217,6 +1260,7 @@ export class EntityManager {
1217
1260
  ...options,
1218
1261
  newEntity: !options.managed,
1219
1262
  merge: options.managed,
1263
+ normalizeAccessors: true,
1220
1264
  });
1221
1265
  options.persist ??= em.config.get('persistOnCreate');
1222
1266
  if (options.persist && !this.getMetadata(entityName).embeddable) {
@@ -1236,7 +1280,7 @@ export class EntityManager {
1236
1280
  getReference(entityName, id, options = {}) {
1237
1281
  options.schema ??= this.schema;
1238
1282
  options.convertCustomTypes ??= false;
1239
- const meta = this.metadata.get(Utils.className(entityName));
1283
+ const meta = this.metadata.get(entityName);
1240
1284
  if (Utils.isPrimaryKey(id)) {
1241
1285
  if (meta.compositePK) {
1242
1286
  throw ValidationError.invalidCompositeIdentifier(meta);
@@ -1255,11 +1299,8 @@ export class EntityManager {
1255
1299
  async count(entityName, where = {}, options = {}) {
1256
1300
  const em = this.getContext(false);
1257
1301
  // Shallow copy options since the object will be modified when deleting orderBy
1258
- options = {
1259
- schema: em._schema,
1260
- ...options,
1261
- };
1262
- entityName = Utils.className(entityName);
1302
+ options = { ...options };
1303
+ em.prepareOptions(options);
1263
1304
  await em.tryFlush(entityName, options);
1264
1305
  where = await em.processWhere(entityName, where, options, 'read');
1265
1306
  options.populate = await em.preparePopulate(entityName, options);
@@ -1268,15 +1309,15 @@ export class EntityManager {
1268
1309
  const meta = em.metadata.find(entityName);
1269
1310
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
1270
1311
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
1271
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
1272
- em.validator.validateParams(where);
1312
+ options.populateFilter = await this.getJoinedFilters(meta, options);
1313
+ validateParams(where);
1273
1314
  delete options.orderBy;
1274
1315
  const cacheKey = em.cacheKey(entityName, options, 'em.count', where);
1275
1316
  const cached = await em.tryCache(entityName, options.cache, cacheKey);
1276
- if (cached?.data) {
1317
+ if (cached?.data !== undefined) {
1277
1318
  return cached.data;
1278
1319
  }
1279
- const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, ...options });
1320
+ const count = await em.driver.count(entityName, where, { ctx: em.transactionContext, em, ...options });
1280
1321
  await em.storeCache(options.cache, cached, () => +count);
1281
1322
  return +count;
1282
1323
  }
@@ -1295,7 +1336,7 @@ export class EntityManager {
1295
1336
  for (const ent of entities) {
1296
1337
  if (!Utils.isEntity(ent, true)) {
1297
1338
  /* v8 ignore next */
1298
- const meta = typeof ent === 'object' ? em.metadata.find(ent.constructor.name) : undefined;
1339
+ const meta = typeof ent === 'object' ? em.metadata.find(ent.constructor) : undefined;
1299
1340
  throw ValidationError.notDiscoveredEntity(ent, meta);
1300
1341
  }
1301
1342
  // do not cascade just yet, cascading of entities in persist stack is done when flushing
@@ -1303,13 +1344,6 @@ export class EntityManager {
1303
1344
  }
1304
1345
  return this;
1305
1346
  }
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
1347
  /**
1314
1348
  * Marks entity for removal.
1315
1349
  * A removed entity will be removed from the database at or before transaction commit or as a result of the flush operation.
@@ -1333,13 +1367,6 @@ export class EntityManager {
1333
1367
  }
1334
1368
  return em;
1335
1369
  }
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
1370
  /**
1344
1371
  * Flushes all changes to objects that have been queued up to now to the database.
1345
1372
  * This effectively synchronizes the in-memory state of managed objects with the database.
@@ -1353,7 +1380,6 @@ export class EntityManager {
1353
1380
  async tryFlush(entityName, options) {
1354
1381
  const em = this.getContext();
1355
1382
  const flushMode = options.flushMode ?? em.flushMode ?? em.config.get('flushMode');
1356
- entityName = Utils.className(entityName);
1357
1383
  const meta = em.metadata.get(entityName);
1358
1384
  if (flushMode === FlushMode.COMMIT) {
1359
1385
  return;
@@ -1372,7 +1398,6 @@ export class EntityManager {
1372
1398
  * Checks whether given property can be populated on the entity.
1373
1399
  */
1374
1400
  canPopulate(entityName, property) {
1375
- entityName = Utils.className(entityName);
1376
1401
  // eslint-disable-next-line prefer-const
1377
1402
  let [p, ...parts] = property.split('.');
1378
1403
  const meta = this.metadata.find(entityName);
@@ -1383,11 +1408,8 @@ export class EntityManager {
1383
1408
  p = p.split(':', 2)[0];
1384
1409
  }
1385
1410
  const ret = p in meta.root.properties;
1386
- if (!ret) {
1387
- return !!this.metadata.find(property)?.pivotTable;
1388
- }
1389
1411
  if (parts.length > 0) {
1390
- return this.canPopulate((meta.root.properties)[p].type, parts.join('.'));
1412
+ return this.canPopulate(meta.root.properties[p].targetMeta.class, parts.join('.'));
1391
1413
  }
1392
1414
  return ret;
1393
1415
  }
@@ -1401,8 +1423,8 @@ export class EntityManager {
1401
1423
  }
1402
1424
  const em = this.getContext();
1403
1425
  em.prepareOptions(options);
1404
- const entityName = arr[0].constructor.name;
1405
- const preparedPopulate = await em.preparePopulate(entityName, { populate: populate }, options.validate);
1426
+ const entityName = arr[0].constructor;
1427
+ const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters }, options.validate);
1406
1428
  await em.entityLoader.populate(entityName, arr, preparedPopulate, options);
1407
1429
  return entities;
1408
1430
  }
@@ -1462,6 +1484,12 @@ export class EntityManager {
1462
1484
  getEntityFactory() {
1463
1485
  return this.getContext().entityFactory;
1464
1486
  }
1487
+ /**
1488
+ * @internal use `em.populate()` as the user facing API, this is exposed only for internal usage
1489
+ */
1490
+ getEntityLoader() {
1491
+ return this.getContext().entityLoader;
1492
+ }
1465
1493
  /**
1466
1494
  * Gets the Hydrator used by the EntityManager.
1467
1495
  */
@@ -1524,7 +1552,6 @@ export class EntityManager {
1524
1552
  */
1525
1553
  getMetadata(entityName) {
1526
1554
  if (entityName) {
1527
- entityName = Utils.className(entityName);
1528
1555
  return this.metadata.get(entityName);
1529
1556
  }
1530
1557
  return this.metadata;
@@ -1553,12 +1580,11 @@ export class EntityManager {
1553
1580
  lockTableAliases: options.lockTableAliases,
1554
1581
  });
1555
1582
  }
1556
- const preparedPopulate = await this.preparePopulate(meta.className, options);
1557
- await this.entityLoader.populate(meta.className, [entity], preparedPopulate, {
1583
+ const preparedPopulate = await this.preparePopulate(meta.class, options);
1584
+ await this.entityLoader.populate(meta.class, [entity], preparedPopulate, {
1558
1585
  ...options,
1559
1586
  ...this.getPopulateWhere(where, options),
1560
1587
  orderBy: options.populateOrderBy ?? options.orderBy,
1561
- convertCustomTypes: false,
1562
1588
  ignoreLazyScalarProperties: true,
1563
1589
  lookup: false,
1564
1590
  });
@@ -1621,7 +1647,7 @@ export class EntityManager {
1621
1647
  }
1622
1648
  if (typeof options.populate !== 'boolean') {
1623
1649
  options.populate = Utils.asArray(options.populate).map(field => {
1624
- /* v8 ignore next 3 */
1650
+ /* v8 ignore next */
1625
1651
  if (typeof field === 'boolean' || field === PopulatePath.ALL) {
1626
1652
  return [{ field: meta.primaryKeys[0], strategy: options.strategy, all: !!field }]; //
1627
1653
  }
@@ -1631,7 +1657,7 @@ export class EntityManager {
1631
1657
  options.flags.push(QueryFlag.INFER_POPULATE);
1632
1658
  return [];
1633
1659
  }
1634
- if (Utils.isString(field)) {
1660
+ if (typeof field === 'string') {
1635
1661
  return [{ field, strategy: options.strategy }];
1636
1662
  }
1637
1663
  return [field];
@@ -1681,7 +1707,7 @@ export class EntityManager {
1681
1707
  throw new ValidationError(`Cannot combine 'fields' and 'exclude' option.`);
1682
1708
  }
1683
1709
  options.schema ??= this._schema;
1684
- options.logging = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
1710
+ options.logging = options.loggerContext = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
1685
1711
  }
1686
1712
  /**
1687
1713
  * @internal
@@ -1692,7 +1718,7 @@ export class EntityManager {
1692
1718
  for (const k of ['ctx', 'strategy', 'flushMode', 'logging', 'loggerContext']) {
1693
1719
  delete opts[k];
1694
1720
  }
1695
- return [entityName, method, opts, where];
1721
+ return [Utils.className(entityName), method, opts, where];
1696
1722
  }
1697
1723
  /**
1698
1724
  * @internal
@@ -1705,31 +1731,31 @@ export class EntityManager {
1705
1731
  const em = this.getContext();
1706
1732
  const cacheKey = Array.isArray(config) ? config[0] : JSON.stringify(key);
1707
1733
  const cached = await em.resultCache.get(cacheKey);
1708
- if (cached) {
1709
- let data;
1710
- if (Array.isArray(cached) && merge) {
1711
- data = cached.map(item => em.entityFactory.create(entityName, item, {
1712
- merge: true,
1713
- convertCustomTypes: true,
1714
- refresh,
1715
- recomputeSnapshot: true,
1716
- }));
1717
- }
1718
- else if (Utils.isObject(cached) && merge) {
1719
- data = em.entityFactory.create(entityName, cached, {
1720
- merge: true,
1721
- convertCustomTypes: true,
1722
- refresh,
1723
- recomputeSnapshot: true,
1724
- });
1725
- }
1726
- else {
1727
- data = cached;
1728
- }
1729
- await em.unitOfWork.dispatchOnLoadEvent();
1730
- return { key: cacheKey, data };
1734
+ if (!cached) {
1735
+ return { key: cacheKey, data: cached };
1736
+ }
1737
+ let data;
1738
+ if (Array.isArray(cached) && merge) {
1739
+ data = cached.map(item => em.entityFactory.create(entityName, item, {
1740
+ merge: true,
1741
+ convertCustomTypes: true,
1742
+ refresh,
1743
+ recomputeSnapshot: true,
1744
+ }));
1745
+ }
1746
+ else if (Utils.isObject(cached) && merge) {
1747
+ data = em.entityFactory.create(entityName, cached, {
1748
+ merge: true,
1749
+ convertCustomTypes: true,
1750
+ refresh,
1751
+ recomputeSnapshot: true,
1752
+ });
1731
1753
  }
1732
- return { key: cacheKey };
1754
+ else {
1755
+ data = cached;
1756
+ }
1757
+ await em.unitOfWork.dispatchOnLoadEvent();
1758
+ return { key: cacheKey, data };
1733
1759
  }
1734
1760
  /**
1735
1761
  * @internal
@@ -1738,7 +1764,7 @@ export class EntityManager {
1738
1764
  config ??= this.config.get('resultCache').global;
1739
1765
  if (config) {
1740
1766
  const em = this.getContext();
1741
- const expiration = Array.isArray(config) ? config[1] : (Utils.isNumber(config) ? config : undefined);
1767
+ const expiration = Array.isArray(config) ? config[1] : (typeof config === 'number' ? config : undefined);
1742
1768
  await em.resultCache.set(key.key, data instanceof Function ? data() : data, '', expiration);
1743
1769
  }
1744
1770
  }
@@ -1771,6 +1797,19 @@ export class EntityManager {
1771
1797
  set schema(schema) {
1772
1798
  this.getContext(false)._schema = schema ?? undefined;
1773
1799
  }
1800
+ /** @internal */
1801
+ async getDataLoader(type) {
1802
+ const em = this.getContext();
1803
+ if (em.loaders[type]) {
1804
+ return em.loaders[type];
1805
+ }
1806
+ const DataLoader = await DataloaderUtils.getDataLoader();
1807
+ switch (type) {
1808
+ case 'ref': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getRefBatchLoadFn(em)));
1809
+ case '1:m': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getColBatchLoadFn(em)));
1810
+ case 'm:n': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(em)));
1811
+ }
1812
+ }
1774
1813
  /**
1775
1814
  * Returns the ID of this EntityManager. Respects the context, so global EM will give you the contextual ID
1776
1815
  * if executed inside request context handler.
@@ -1779,7 +1818,7 @@ export class EntityManager {
1779
1818
  return this.getContext(false)._id;
1780
1819
  }
1781
1820
  /** @ignore */
1782
- [inspect.custom]() {
1821
+ [Symbol.for('nodejs.util.inspect.custom')]() {
1783
1822
  return `[EntityManager<${this.id}>]`;
1784
1823
  }
1785
1824
  }