@mikro-orm/core 7.0.0-dev.23 → 7.0.0-dev.230

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 (209) hide show
  1. package/EntityManager.d.ts +91 -59
  2. package/EntityManager.js +303 -251
  3. package/MikroORM.d.ts +44 -35
  4. package/MikroORM.js +109 -143
  5. package/README.md +2 -0
  6. package/cache/FileCacheAdapter.d.ts +1 -1
  7. package/cache/FileCacheAdapter.js +17 -8
  8. package/cache/GeneratedCacheAdapter.d.ts +0 -1
  9. package/cache/GeneratedCacheAdapter.js +0 -2
  10. package/cache/index.d.ts +0 -1
  11. package/cache/index.js +0 -1
  12. package/connections/Connection.d.ts +12 -5
  13. package/connections/Connection.js +21 -12
  14. package/drivers/DatabaseDriver.d.ts +25 -16
  15. package/drivers/DatabaseDriver.js +118 -35
  16. package/drivers/IDatabaseDriver.d.ts +42 -19
  17. package/entity/BaseEntity.d.ts +61 -2
  18. package/entity/BaseEntity.js +0 -3
  19. package/entity/Collection.d.ts +101 -29
  20. package/entity/Collection.js +436 -104
  21. package/entity/EntityAssigner.d.ts +1 -1
  22. package/entity/EntityAssigner.js +26 -18
  23. package/entity/EntityFactory.d.ts +7 -1
  24. package/entity/EntityFactory.js +83 -54
  25. package/entity/EntityHelper.d.ts +2 -2
  26. package/entity/EntityHelper.js +48 -15
  27. package/entity/EntityLoader.d.ts +7 -6
  28. package/entity/EntityLoader.js +215 -89
  29. package/entity/EntityRepository.d.ts +27 -7
  30. package/entity/EntityRepository.js +8 -2
  31. package/entity/PolymorphicRef.d.ts +12 -0
  32. package/entity/PolymorphicRef.js +18 -0
  33. package/entity/Reference.d.ts +1 -5
  34. package/entity/Reference.js +21 -12
  35. package/entity/WrappedEntity.d.ts +0 -5
  36. package/entity/WrappedEntity.js +2 -7
  37. package/entity/defineEntity.d.ts +380 -310
  38. package/entity/defineEntity.js +124 -273
  39. package/entity/index.d.ts +2 -2
  40. package/entity/index.js +2 -2
  41. package/entity/utils.js +1 -1
  42. package/entity/validators.d.ts +11 -0
  43. package/entity/validators.js +65 -0
  44. package/enums.d.ts +8 -6
  45. package/enums.js +2 -1
  46. package/errors.d.ts +20 -10
  47. package/errors.js +55 -23
  48. package/events/EventManager.d.ts +2 -1
  49. package/events/EventManager.js +19 -11
  50. package/hydration/Hydrator.js +1 -2
  51. package/hydration/ObjectHydrator.d.ts +4 -4
  52. package/hydration/ObjectHydrator.js +87 -35
  53. package/index.d.ts +2 -2
  54. package/index.js +1 -2
  55. package/logging/DefaultLogger.d.ts +1 -1
  56. package/logging/DefaultLogger.js +1 -0
  57. package/logging/SimpleLogger.d.ts +1 -1
  58. package/logging/colors.d.ts +1 -1
  59. package/logging/colors.js +7 -6
  60. package/logging/index.d.ts +1 -0
  61. package/logging/index.js +1 -0
  62. package/logging/inspect.d.ts +2 -0
  63. package/logging/inspect.js +11 -0
  64. package/metadata/EntitySchema.d.ts +47 -23
  65. package/metadata/EntitySchema.js +92 -33
  66. package/metadata/MetadataDiscovery.d.ts +64 -9
  67. package/metadata/MetadataDiscovery.js +778 -325
  68. package/metadata/MetadataProvider.d.ts +11 -2
  69. package/metadata/MetadataProvider.js +46 -2
  70. package/metadata/MetadataStorage.d.ts +13 -11
  71. package/metadata/MetadataStorage.js +70 -37
  72. package/metadata/MetadataValidator.d.ts +32 -9
  73. package/metadata/MetadataValidator.js +196 -41
  74. package/metadata/discover-entities.d.ts +5 -0
  75. package/metadata/discover-entities.js +40 -0
  76. package/metadata/index.d.ts +1 -1
  77. package/metadata/index.js +1 -1
  78. package/metadata/types.d.ts +526 -0
  79. package/metadata/types.js +1 -0
  80. package/naming-strategy/AbstractNamingStrategy.d.ts +16 -4
  81. package/naming-strategy/AbstractNamingStrategy.js +20 -2
  82. package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
  83. package/naming-strategy/EntityCaseNamingStrategy.js +6 -5
  84. package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
  85. package/naming-strategy/MongoNamingStrategy.js +6 -6
  86. package/naming-strategy/NamingStrategy.d.ts +28 -4
  87. package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
  88. package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
  89. package/not-supported.d.ts +2 -0
  90. package/not-supported.js +4 -0
  91. package/package.json +19 -11
  92. package/platforms/ExceptionConverter.js +1 -1
  93. package/platforms/Platform.d.ts +7 -14
  94. package/platforms/Platform.js +20 -43
  95. package/serialization/EntitySerializer.d.ts +5 -0
  96. package/serialization/EntitySerializer.js +47 -27
  97. package/serialization/EntityTransformer.js +28 -18
  98. package/serialization/SerializationContext.d.ts +6 -6
  99. package/serialization/SerializationContext.js +3 -3
  100. package/types/ArrayType.d.ts +1 -1
  101. package/types/ArrayType.js +2 -3
  102. package/types/BigIntType.d.ts +8 -6
  103. package/types/BigIntType.js +1 -1
  104. package/types/BlobType.d.ts +0 -1
  105. package/types/BlobType.js +0 -3
  106. package/types/BooleanType.d.ts +1 -0
  107. package/types/BooleanType.js +3 -0
  108. package/types/DecimalType.d.ts +6 -4
  109. package/types/DecimalType.js +2 -2
  110. package/types/DoubleType.js +1 -1
  111. package/types/EnumArrayType.js +1 -2
  112. package/types/JsonType.d.ts +1 -1
  113. package/types/JsonType.js +7 -2
  114. package/types/TinyIntType.js +1 -1
  115. package/types/Type.d.ts +2 -4
  116. package/types/Type.js +3 -3
  117. package/types/Uint8ArrayType.d.ts +0 -1
  118. package/types/Uint8ArrayType.js +1 -4
  119. package/types/index.d.ts +1 -1
  120. package/typings.d.ts +381 -171
  121. package/typings.js +97 -44
  122. package/unit-of-work/ChangeSet.d.ts +4 -6
  123. package/unit-of-work/ChangeSet.js +4 -5
  124. package/unit-of-work/ChangeSetComputer.d.ts +1 -3
  125. package/unit-of-work/ChangeSetComputer.js +35 -14
  126. package/unit-of-work/ChangeSetPersister.d.ts +7 -3
  127. package/unit-of-work/ChangeSetPersister.js +83 -25
  128. package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
  129. package/unit-of-work/CommitOrderCalculator.js +13 -13
  130. package/unit-of-work/IdentityMap.d.ts +12 -0
  131. package/unit-of-work/IdentityMap.js +39 -1
  132. package/unit-of-work/UnitOfWork.d.ts +27 -3
  133. package/unit-of-work/UnitOfWork.js +258 -92
  134. package/utils/AbstractSchemaGenerator.d.ts +5 -5
  135. package/utils/AbstractSchemaGenerator.js +28 -17
  136. package/utils/AsyncContext.d.ts +6 -0
  137. package/utils/AsyncContext.js +42 -0
  138. package/utils/Configuration.d.ts +795 -209
  139. package/utils/Configuration.js +150 -192
  140. package/utils/ConfigurationLoader.d.ts +1 -54
  141. package/utils/ConfigurationLoader.js +1 -352
  142. package/utils/Cursor.d.ts +0 -3
  143. package/utils/Cursor.js +24 -11
  144. package/utils/DataloaderUtils.d.ts +10 -5
  145. package/utils/DataloaderUtils.js +29 -12
  146. package/utils/EntityComparator.d.ts +16 -9
  147. package/utils/EntityComparator.js +158 -58
  148. package/utils/QueryHelper.d.ts +18 -6
  149. package/utils/QueryHelper.js +76 -23
  150. package/utils/RawQueryFragment.d.ts +28 -34
  151. package/utils/RawQueryFragment.js +35 -71
  152. package/utils/RequestContext.js +2 -2
  153. package/utils/TransactionContext.js +2 -2
  154. package/utils/TransactionManager.js +28 -4
  155. package/utils/Utils.d.ts +14 -127
  156. package/utils/Utils.js +85 -397
  157. package/utils/clone.js +8 -23
  158. package/utils/env-vars.d.ts +7 -0
  159. package/utils/env-vars.js +97 -0
  160. package/utils/fs-utils.d.ts +33 -0
  161. package/utils/fs-utils.js +192 -0
  162. package/utils/index.d.ts +1 -1
  163. package/utils/index.js +1 -1
  164. package/utils/upsert-utils.d.ts +9 -4
  165. package/utils/upsert-utils.js +46 -3
  166. package/decorators/Check.d.ts +0 -3
  167. package/decorators/Check.js +0 -13
  168. package/decorators/CreateRequestContext.d.ts +0 -3
  169. package/decorators/CreateRequestContext.js +0 -32
  170. package/decorators/Embeddable.d.ts +0 -8
  171. package/decorators/Embeddable.js +0 -11
  172. package/decorators/Embedded.d.ts +0 -12
  173. package/decorators/Embedded.js +0 -18
  174. package/decorators/Entity.d.ts +0 -33
  175. package/decorators/Entity.js +0 -12
  176. package/decorators/Enum.d.ts +0 -9
  177. package/decorators/Enum.js +0 -16
  178. package/decorators/Filter.d.ts +0 -2
  179. package/decorators/Filter.js +0 -8
  180. package/decorators/Formula.d.ts +0 -4
  181. package/decorators/Formula.js +0 -15
  182. package/decorators/Indexed.d.ts +0 -19
  183. package/decorators/Indexed.js +0 -20
  184. package/decorators/ManyToMany.d.ts +0 -42
  185. package/decorators/ManyToMany.js +0 -14
  186. package/decorators/ManyToOne.d.ts +0 -34
  187. package/decorators/ManyToOne.js +0 -14
  188. package/decorators/OneToMany.d.ts +0 -28
  189. package/decorators/OneToMany.js +0 -17
  190. package/decorators/OneToOne.d.ts +0 -28
  191. package/decorators/OneToOne.js +0 -7
  192. package/decorators/PrimaryKey.d.ts +0 -8
  193. package/decorators/PrimaryKey.js +0 -20
  194. package/decorators/Property.d.ts +0 -250
  195. package/decorators/Property.js +0 -32
  196. package/decorators/Transactional.d.ts +0 -14
  197. package/decorators/Transactional.js +0 -28
  198. package/decorators/hooks.d.ts +0 -16
  199. package/decorators/hooks.js +0 -47
  200. package/decorators/index.d.ts +0 -17
  201. package/decorators/index.js +0 -17
  202. package/entity/ArrayCollection.d.ts +0 -118
  203. package/entity/ArrayCollection.js +0 -407
  204. package/entity/EntityValidator.d.ts +0 -19
  205. package/entity/EntityValidator.js +0 -150
  206. package/metadata/ReflectMetadataProvider.d.ts +0 -8
  207. package/metadata/ReflectMetadataProvider.js +0 -44
  208. package/utils/resolveContextProvider.d.ts +0 -10
  209. 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';
@@ -36,11 +34,8 @@ export class EntityManager {
36
34
  _id = EntityManager.counter++;
37
35
  global = false;
38
36
  name;
39
- refLoader = new DataLoader(DataloaderUtils.getRefBatchLoadFn(this));
40
- colLoader = new DataLoader(DataloaderUtils.getColBatchLoadFn(this));
41
- colLoaderMtoN = new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(this));
42
- validator;
43
- repositoryMap = {};
37
+ loaders = {};
38
+ repositoryMap = new Map();
44
39
  entityLoader;
45
40
  comparator;
46
41
  entityFactory;
@@ -64,7 +59,6 @@ export class EntityManager {
64
59
  this.eventManager = eventManager;
65
60
  this.entityLoader = new EntityLoader(this);
66
61
  this.name = this.config.get('contextName');
67
- this.validator = new EntityValidator(this.config.get('strict'));
68
62
  this.comparator = this.config.getComparator(this.metadata);
69
63
  this.resultCache = this.config.getResultCacheAdapter();
70
64
  this.disableTransactions = this.config.get('disableTransactions');
@@ -94,13 +88,12 @@ export class EntityManager {
94
88
  * Gets repository for given entity. You can pass either string name or entity class reference.
95
89
  */
96
90
  getRepository(entityName) {
97
- entityName = Utils.className(entityName);
98
- if (!this.repositoryMap[entityName]) {
99
- const meta = this.metadata.get(entityName);
91
+ const meta = this.metadata.get(entityName);
92
+ if (!this.repositoryMap.has(meta)) {
100
93
  const RepositoryClass = this.config.getRepositoryClass(meta.repository);
101
- this.repositoryMap[entityName] = new RepositoryClass(this, entityName);
94
+ this.repositoryMap.set(meta, new RepositoryClass(this, entityName));
102
95
  }
103
- return this.repositoryMap[entityName];
96
+ return this.repositoryMap.get(meta);
104
97
  }
105
98
  /**
106
99
  * Shortcut for `em.getRepository()`.
@@ -108,12 +101,6 @@ export class EntityManager {
108
101
  repo(entityName) {
109
102
  return this.getRepository(entityName);
110
103
  }
111
- /**
112
- * Gets EntityValidator instance
113
- */
114
- getValidator() {
115
- return this.validator;
116
- }
117
104
  /**
118
105
  * Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
119
106
  */
@@ -128,10 +115,15 @@ export class EntityManager {
128
115
  const em = this.getContext();
129
116
  em.prepareOptions(options);
130
117
  await em.tryFlush(entityName, options);
131
- entityName = Utils.className(entityName);
132
118
  where = await em.processWhere(entityName, where, options, 'read');
133
- em.validator.validateParams(where);
134
- options.orderBy = options.orderBy || {};
119
+ validateParams(where);
120
+ const meta = this.metadata.get(entityName);
121
+ if (meta.orderBy) {
122
+ options.orderBy = QueryHelper.mergeOrderBy(options.orderBy, meta.orderBy);
123
+ }
124
+ else {
125
+ options.orderBy ??= {};
126
+ }
135
127
  options.populate = await em.preparePopulate(entityName, options);
136
128
  const populate = options.populate;
137
129
  const cacheKey = em.cacheKey(entityName, options, 'em.find', where);
@@ -145,12 +137,11 @@ export class EntityManager {
145
137
  });
146
138
  return cached.data;
147
139
  }
148
- const meta = this.metadata.get(entityName);
149
140
  options = { ...options };
150
141
  // save the original hint value so we know it was infer/all
151
142
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
152
143
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
153
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
144
+ options.populateFilter = await this.getJoinedFilters(meta, options);
154
145
  const results = await em.driver.find(entityName, where, { ctx: em.transactionContext, em, ...options });
155
146
  if (results.length === 0) {
156
147
  await em.storeCache(options.cache, cached, []);
@@ -182,6 +173,60 @@ export class EntityManager {
182
173
  }
183
174
  return unique;
184
175
  }
176
+ /**
177
+ * Finds all entities and returns an async iterable (async generator) that yields results one by one.
178
+ * The results are merged and mapped to entity instances, without adding them to the identity map.
179
+ * You can disable merging by passing the options `{ mergeResults: false }`.
180
+ * With `mergeResults` disabled, to-many collections will contain at most one item, and you will get duplicate
181
+ * root entities when there are multiple items in the populated collection.
182
+ * This is useful for processing large datasets without loading everything into memory at once.
183
+ *
184
+ * ```ts
185
+ * const stream = em.stream(Book, { populate: ['author'] });
186
+ *
187
+ * for await (const book of stream) {
188
+ * // book is an instance of Book entity
189
+ * console.log(book.title, book.author.name);
190
+ * }
191
+ * ```
192
+ */
193
+ async *stream(entityName, options = {}) {
194
+ const em = this.getContext();
195
+ em.prepareOptions(options);
196
+ options.strategy = 'joined';
197
+ await em.tryFlush(entityName, options);
198
+ const where = await em.processWhere(entityName, options.where ?? {}, options, 'read');
199
+ validateParams(where);
200
+ options.orderBy = options.orderBy || {};
201
+ options.populate = await em.preparePopulate(entityName, options);
202
+ const meta = this.metadata.get(entityName);
203
+ options = { ...options };
204
+ // save the original hint value so we know it was infer/all
205
+ options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
206
+ options.populateWhere = this.createPopulateWhere({ ...where }, options);
207
+ options.populateFilter = await this.getJoinedFilters(meta, options);
208
+ const stream = em.driver.stream(entityName, where, {
209
+ ctx: em.transactionContext,
210
+ mapResults: false,
211
+ ...options,
212
+ });
213
+ for await (const data of stream) {
214
+ const fork = em.fork();
215
+ const entity = fork.entityFactory.create(entityName, data, {
216
+ refresh: options.refresh,
217
+ schema: options.schema,
218
+ convertCustomTypes: true,
219
+ });
220
+ helper(entity).setSerializationContext({
221
+ populate: options.populate,
222
+ fields: options.fields,
223
+ exclude: options.exclude,
224
+ });
225
+ await fork.unitOfWork.dispatchOnLoadEvent();
226
+ fork.clear();
227
+ yield entity;
228
+ }
229
+ }
185
230
  /**
186
231
  * Finds all entities of given type, optionally matching the `where` condition provided in the `options` parameter.
187
232
  */
@@ -195,7 +240,7 @@ export class EntityManager {
195
240
  if (options.populateWhere === PopulateHint.ALL) {
196
241
  return { where: {}, populateWhere: options.populateWhere };
197
242
  }
198
- /* v8 ignore next 3 */
243
+ /* v8 ignore next */
199
244
  if (options.populateWhere === PopulateHint.INFER) {
200
245
  return { where, populateWhere: options.populateWhere };
201
246
  }
@@ -204,12 +249,12 @@ export class EntityManager {
204
249
  /**
205
250
  * Registers global filter to this entity manager. Global filters are enabled by default (unless disabled via last parameter).
206
251
  */
207
- addFilter(name, cond, entityName, enabled = true) {
208
- const options = { name, cond, default: enabled };
209
- if (entityName) {
210
- options.entity = Utils.asArray(entityName).map(n => Utils.className(n));
252
+ addFilter(options) {
253
+ if (options.entity) {
254
+ options.entity = Utils.asArray(options.entity).map(n => Utils.className(n));
211
255
  }
212
- this.getContext(false).filters[name] = options;
256
+ options.default ??= true;
257
+ this.getContext(false).filters[options.name] = options;
213
258
  }
214
259
  /**
215
260
  * Sets filter parameter values globally inside context defined by this entity manager.
@@ -257,18 +302,18 @@ export class EntityManager {
257
302
  // this method only handles the problem for mongo driver, SQL drivers have their implementation inside QueryBuilder
258
303
  applyDiscriminatorCondition(entityName, where) {
259
304
  const meta = this.metadata.find(entityName);
260
- if (!meta?.discriminatorValue) {
305
+ if (meta?.root.inheritanceType !== 'sti' || !meta?.discriminatorValue) {
261
306
  return where;
262
307
  }
263
- const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.find(cls));
308
+ const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.get(cls));
264
309
  const children = [];
265
310
  const lookUpChildren = (ret, type) => {
266
311
  const children = types.filter(meta2 => meta2.extends === type);
267
- children.forEach(m => lookUpChildren(ret, m.className));
312
+ children.forEach(m => lookUpChildren(ret, m.class));
268
313
  ret.push(...children.filter(c => c.discriminatorValue));
269
314
  return children;
270
315
  };
271
- lookUpChildren(children, meta.className);
316
+ lookUpChildren(children, meta.class);
272
317
  /* v8 ignore next */
273
318
  where[meta.root.discriminatorColumn] = children.length > 0 ? { $in: [meta.discriminatorValue, ...children.map(c => c.discriminatorValue)] } : meta.discriminatorValue;
274
319
  return where;
@@ -284,60 +329,77 @@ export class EntityManager {
284
329
  }
285
330
  return ret;
286
331
  }
287
- async getJoinedFilters(meta, cond, options) {
332
+ async getJoinedFilters(meta, options) {
333
+ // If user provided populateFilter, merge it with computed filters
334
+ const userFilter = options.populateFilter;
335
+ if (!this.config.get('filtersOnRelations') || !options.populate) {
336
+ return userFilter;
337
+ }
288
338
  const ret = {};
289
- if (options.populate) {
290
- for (const hint of options.populate) {
291
- const field = hint.field.split(':')[0];
292
- const prop = meta.properties[field];
293
- const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
294
- const joined = strategy === LoadStrategy.JOINED && prop.kind !== ReferenceKind.SCALAR;
295
- if (!joined && !hint.filter) {
296
- continue;
297
- }
298
- const where = await this.applyFilters(prop.type, {}, options.filters ?? {}, 'read', { ...options, populate: hint.children });
299
- const where2 = await this.getJoinedFilters(prop.targetMeta, {}, { ...options, populate: hint.children, populateWhere: PopulateHint.ALL });
300
- if (Utils.hasObjectKeys(where)) {
301
- ret[field] = ret[field] ? { $and: [where, ret[field]] } : where;
339
+ for (const hint of options.populate) {
340
+ const field = hint.field.split(':')[0];
341
+ const prop = meta.properties[field];
342
+ const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
343
+ const joined = strategy === LoadStrategy.JOINED && prop.kind !== ReferenceKind.SCALAR;
344
+ if (!joined && !hint.filter) {
345
+ continue;
346
+ }
347
+ const filters = QueryHelper.mergePropertyFilters(prop.filters, options.filters);
348
+ const where = await this.applyFilters(prop.targetMeta.class, {}, filters, 'read', {
349
+ ...options,
350
+ populate: hint.children,
351
+ });
352
+ const where2 = await this.getJoinedFilters(prop.targetMeta, {
353
+ ...options,
354
+ filters,
355
+ populate: hint.children,
356
+ populateWhere: PopulateHint.ALL,
357
+ });
358
+ if (Utils.hasObjectKeys(where)) {
359
+ ret[field] = ret[field] ? { $and: [where, ret[field]] } : where;
360
+ }
361
+ if (where2 && Utils.hasObjectKeys(where2)) {
362
+ if (ret[field]) {
363
+ Utils.merge(ret[field], where2);
302
364
  }
303
- if (Utils.hasObjectKeys(where2)) {
304
- if (ret[field]) {
305
- Utils.merge(ret[field], where2);
306
- }
307
- else {
308
- ret[field] = where2;
309
- }
365
+ else {
366
+ ret[field] = where2;
310
367
  }
311
368
  }
312
369
  }
313
- return ret;
370
+ // Merge user-provided populateFilter with computed filters
371
+ if (userFilter) {
372
+ Utils.merge(ret, userFilter);
373
+ }
374
+ return Utils.hasObjectKeys(ret) ? ret : undefined;
314
375
  }
315
376
  /**
316
377
  * 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.
317
378
  */
318
- async autoJoinRefsForFilters(meta, options) {
319
- if (!meta || !this.config.get('autoJoinRefsForFilters')) {
379
+ async autoJoinRefsForFilters(meta, options, parent) {
380
+ if (!meta || !this.config.get('autoJoinRefsForFilters') || options.filters === false) {
320
381
  return;
321
382
  }
322
- const props = meta.relations.filter(prop => {
323
- return !prop.object && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)
324
- && ((options.fields?.length ?? 0) === 0 || options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)));
325
- });
326
383
  const ret = options.populate;
327
- for (const prop of props) {
328
- const cond = await this.applyFilters(prop.type, {}, options.filters ?? {}, 'read', options);
384
+ for (const prop of meta.relations) {
385
+ if (prop.object
386
+ || ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)
387
+ || !((options.fields?.length ?? 0) === 0 || options.fields?.some(f => prop.name === f || prop.name.startsWith(`${String(f)}.`)))
388
+ || (parent?.class === prop.targetMeta.root.class && parent.propName === prop.inversedBy)) {
389
+ continue;
390
+ }
391
+ options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
392
+ const cond = await this.applyFilters(prop.targetMeta.class, {}, options.filters, 'read', options);
329
393
  if (!Utils.isEmpty(cond)) {
330
394
  const populated = options.populate.filter(({ field }) => field.split(':')[0] === prop.name);
331
395
  let found = false;
332
- if (populated.length > 0) {
333
- for (const hint of populated) {
334
- if (!hint.all) {
335
- hint.filter = true;
336
- found = true;
337
- }
338
- else if (hint.field === `${prop.name}:ref`) {
339
- found = true;
340
- }
396
+ for (const hint of populated) {
397
+ if (!hint.all) {
398
+ hint.filter = true;
399
+ }
400
+ const strategy = getLoadingStrategy(prop.strategy || hint.strategy || options.strategy || this.config.get('loadStrategy'), prop.kind);
401
+ if (hint.field === `${prop.name}:ref` || (hint.filter && strategy === LoadStrategy.JOINED)) {
402
+ found = true;
341
403
  }
342
404
  }
343
405
  if (!found) {
@@ -345,21 +407,26 @@ export class EntityManager {
345
407
  }
346
408
  }
347
409
  }
410
+ for (const hint of ret) {
411
+ const [field, ref] = hint.field.split(':');
412
+ const prop = meta?.properties[field];
413
+ if (prop && !ref) {
414
+ hint.children ??= [];
415
+ await this.autoJoinRefsForFilters(prop.targetMeta, { ...options, populate: hint.children }, { class: meta.root.class, propName: prop.name });
416
+ }
417
+ }
348
418
  }
349
419
  /**
350
420
  * @internal
351
421
  */
352
422
  async applyFilters(entityName, where, options, type, findOptions) {
353
- const meta = this.metadata.find(entityName);
423
+ const meta = this.metadata.get(entityName);
354
424
  const filters = [];
355
425
  const ret = [];
356
- if (!meta) {
357
- return where;
358
- }
359
426
  const active = new Set();
360
427
  const push = (source) => {
361
428
  const activeFilters = QueryHelper
362
- .getActiveFilters(entityName, options, source)
429
+ .getActiveFilters(meta, options, source)
363
430
  .filter(f => !active.has(f.name));
364
431
  filters.push(...activeFilters);
365
432
  activeFilters.forEach(f => active.add(f.name));
@@ -374,24 +441,28 @@ export class EntityManager {
374
441
  let cond;
375
442
  if (filter.cond instanceof Function) {
376
443
  // @ts-ignore
377
- const args = Utils.isPlainObject(options[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
444
+ const args = Utils.isPlainObject(options?.[filter.name]) ? options[filter.name] : this.getContext().filterParams[filter.name];
378
445
  if (!args && filter.cond.length > 0 && filter.args !== false) {
379
446
  throw new Error(`No arguments provided for filter '${filter.name}'`);
380
447
  }
381
- cond = await filter.cond(args, type, this, findOptions, entityName);
448
+ cond = await filter.cond(args, type, this, findOptions, Utils.className(entityName));
382
449
  }
383
450
  else {
384
451
  cond = filter.cond;
385
452
  }
386
- ret.push(QueryHelper.processWhere({
453
+ cond = QueryHelper.processWhere({
387
454
  where: cond,
388
455
  entityName,
389
456
  metadata: this.metadata,
390
457
  platform: this.driver.getPlatform(),
391
458
  aliased: type === 'read',
392
- }));
459
+ });
460
+ if (filter.strict) {
461
+ Object.defineProperty(cond, '__strict', { value: filter.strict, enumerable: false });
462
+ }
463
+ ret.push(cond);
393
464
  }
394
- const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c));
465
+ const conds = [...ret, where].filter(c => Utils.hasObjectKeys(c) || Raw.hasObjectFragments(c));
395
466
  return conds.length > 1 ? { $and: conds } : conds[0];
396
467
  }
397
468
  /**
@@ -402,12 +473,10 @@ export class EntityManager {
402
473
  const em = this.getContext(false);
403
474
  await em.tryFlush(entityName, options);
404
475
  options.flushMode = 'commit'; // do not try to auto flush again
405
- return RawQueryFragment.run(async () => {
406
- return Promise.all([
407
- em.find(entityName, where, options),
408
- em.count(entityName, where, options),
409
- ]);
410
- });
476
+ return Promise.all([
477
+ em.find(entityName, where, options),
478
+ em.count(entityName, where, options),
479
+ ]);
411
480
  }
412
481
  /**
413
482
  * Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as {@apilink Cursor} object.
@@ -423,21 +492,21 @@ export class EntityManager {
423
492
  * - POJO/entity instance
424
493
  *
425
494
  * ```ts
426
- * const currentCursor = await em.findByCursor(User, {}, {
495
+ * const currentCursor = await em.findByCursor(User, {
427
496
  * first: 10,
428
497
  * after: previousCursor, // cursor instance
429
498
  * orderBy: { id: 'desc' },
430
499
  * });
431
500
  *
432
501
  * // to fetch next page
433
- * const nextCursor = await em.findByCursor(User, {}, {
502
+ * const nextCursor = await em.findByCursor(User, {
434
503
  * first: 10,
435
504
  * after: currentCursor.endCursor, // opaque string
436
505
  * orderBy: { id: 'desc' },
437
506
  * });
438
507
  *
439
508
  * // to fetch next page
440
- * const nextCursor2 = await em.findByCursor(User, {}, {
509
+ * const nextCursor2 = await em.findByCursor(User, {
441
510
  * first: 10,
442
511
  * after: { id: lastSeenId }, // entity-like POJO
443
512
  * orderBy: { id: 'desc' },
@@ -465,16 +534,16 @@ export class EntityManager {
465
534
  * }
466
535
  * ```
467
536
  */
468
- async findByCursor(entityName, where, options) {
537
+ async findByCursor(entityName, options) {
469
538
  const em = this.getContext(false);
470
- entityName = Utils.className(entityName);
471
539
  options.overfetch ??= true;
472
- if (Utils.isEmpty(options.orderBy)) {
540
+ options.where ??= {};
541
+ if (Utils.isEmpty(options.orderBy) && !Raw.hasObjectFragments(options.orderBy)) {
473
542
  throw new Error('Explicit `orderBy` option required');
474
543
  }
475
544
  const [entities, count] = options.includeCount !== false
476
- ? await em.findAndCount(entityName, where, options)
477
- : [await em.find(entityName, where, options)];
545
+ ? await em.findAndCount(entityName, options.where, options)
546
+ : [await em.find(entityName, options.where, options)];
478
547
  return new Cursor(entities, count, options, this.metadata.get(entityName));
479
548
  }
480
549
  /**
@@ -486,9 +555,9 @@ export class EntityManager {
486
555
  const ret = await this.refresh(entity, options);
487
556
  if (!ret) {
488
557
  options.failHandler ??= this.config.get('findOneOrFailHandler');
489
- const entityName = entity.constructor.name;
490
- const where = helper(entity).getPrimaryKey();
491
- throw options.failHandler(entityName, where);
558
+ const wrapped = helper(entity);
559
+ const where = wrapped.getPrimaryKey();
560
+ throw options.failHandler(wrapped.__meta.className, where);
492
561
  }
493
562
  return ret;
494
563
  }
@@ -499,25 +568,31 @@ export class EntityManager {
499
568
  */
500
569
  async refresh(entity, options = {}) {
501
570
  const fork = this.fork({ keepTransactionContext: true });
502
- const entityName = entity.constructor.name;
503
- const reloaded = await fork.findOne(entityName, entity, {
504
- schema: helper(entity).__schema,
571
+ const wrapped = helper(entity);
572
+ const reloaded = await fork.findOne(wrapped.__meta.class, entity, {
573
+ schema: wrapped.__schema,
505
574
  ...options,
506
575
  flushMode: FlushMode.COMMIT,
507
576
  });
508
577
  const em = this.getContext();
509
- if (reloaded) {
510
- for (const e of fork.unitOfWork.getIdentityMap()) {
511
- const ref = em.getReference(e.constructor.name, helper(e).getPrimaryKey());
512
- const data = this.comparator.prepareEntity(e);
513
- em.config.getHydrator(this.metadata).hydrate(ref, helper(ref).__meta, helper(e).serialize({ ignoreSerializers: true }), em.entityFactory, 'full', false, true);
514
- helper(ref).__originalEntityData = data;
515
- }
516
- }
517
- else {
578
+ if (!reloaded) {
518
579
  em.unitOfWork.unsetIdentity(entity);
580
+ return null;
581
+ }
582
+ let found = false;
583
+ for (const e of fork.unitOfWork.getIdentityMap()) {
584
+ const ref = em.getReference(e.constructor, helper(e).getPrimaryKey());
585
+ const data = helper(e).serialize({ ignoreSerializers: true, includeHidden: true, convertCustomTypes: false });
586
+ em.config.getHydrator(this.metadata).hydrate(ref, helper(ref).__meta, data, em.entityFactory, 'full', false, false);
587
+ Utils.merge(helper(ref).__originalEntityData, this.comparator.prepareEntity(e));
588
+ found ||= ref === entity;
589
+ }
590
+ if (!found) {
591
+ const data = helper(reloaded).serialize({ ignoreSerializers: true, includeHidden: true, convertCustomTypes: true });
592
+ em.config.getHydrator(this.metadata).hydrate(entity, wrapped.__meta, data, em.entityFactory, 'full', false, true);
593
+ Utils.merge(wrapped.__originalEntityData, this.comparator.prepareEntity(reloaded));
519
594
  }
520
- return reloaded ? entity : reloaded;
595
+ return entity;
521
596
  }
522
597
  /**
523
598
  * Finds first entity matching your `where` query.
@@ -531,7 +606,6 @@ export class EntityManager {
531
606
  return ret;
532
607
  }
533
608
  const em = this.getContext();
534
- entityName = Utils.className(entityName);
535
609
  em.prepareOptions(options);
536
610
  let entity = em.unitOfWork.tryGetById(entityName, where, options.schema);
537
611
  // query for a not managed entity which is already in the identity map as it
@@ -543,13 +617,13 @@ export class EntityManager {
543
617
  await em.tryFlush(entityName, options);
544
618
  const meta = em.metadata.get(entityName);
545
619
  where = await em.processWhere(entityName, where, options, 'read');
546
- em.validator.validateEmptyWhere(where);
620
+ validateEmptyWhere(where);
547
621
  em.checkLockRequirements(options.lockMode, meta);
548
- const isOptimisticLocking = !Utils.isDefined(options.lockMode) || options.lockMode === LockMode.OPTIMISTIC;
622
+ const isOptimisticLocking = options.lockMode == null || options.lockMode === LockMode.OPTIMISTIC;
549
623
  if (entity && !em.shouldRefresh(meta, entity, options) && isOptimisticLocking) {
550
624
  return em.lockAndPopulate(meta, entity, where, options);
551
625
  }
552
- em.validator.validateParams(where);
626
+ validateParams(where);
553
627
  options.populate = await em.preparePopulate(entityName, options);
554
628
  const cacheKey = em.cacheKey(entityName, options, 'em.findOne', where);
555
629
  const cached = await em.tryCache(entityName, options.cache, cacheKey, options.refresh, true);
@@ -568,7 +642,7 @@ export class EntityManager {
568
642
  // save the original hint value so we know it was infer/all
569
643
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
570
644
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
571
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
645
+ options.populateFilter = await this.getJoinedFilters(meta, options);
572
646
  const data = await em.driver.findOne(entityName, where, {
573
647
  ctx: em.transactionContext,
574
648
  em,
@@ -609,10 +683,10 @@ export class EntityManager {
609
683
  if (!entity || isStrictViolation) {
610
684
  const key = options.strict ? 'findExactlyOneOrFailHandler' : 'findOneOrFailHandler';
611
685
  options.failHandler ??= this.config.get(key);
612
- entityName = Utils.className(entityName);
686
+ const name = Utils.className(entityName);
613
687
  /* v8 ignore next */
614
688
  where = Utils.isEntity(where) ? helper(where).getPrimaryKey() : where;
615
- throw options.failHandler(entityName, where);
689
+ throw options.failHandler(name, where);
616
690
  }
617
691
  return entity;
618
692
  }
@@ -652,11 +726,11 @@ export class EntityManager {
652
726
  let where;
653
727
  let entity = null;
654
728
  if (data === undefined) {
655
- entityName = entityNameOrEntity.constructor.name;
729
+ entityName = entityNameOrEntity.constructor;
656
730
  data = entityNameOrEntity;
657
731
  }
658
732
  else {
659
- entityName = Utils.className(entityNameOrEntity);
733
+ entityName = entityNameOrEntity;
660
734
  }
661
735
  const meta = this.metadata.get(entityName);
662
736
  const convertCustomTypes = !Utils.isEntity(data);
@@ -679,26 +753,9 @@ export class EntityManager {
679
753
  }
680
754
  }
681
755
  }
682
- const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
683
- const propIndex = !isRaw(unique) && unique.findIndex(p => data[p] != null);
684
- if (options.onConflictFields || where == null) {
685
- if (propIndex !== false && propIndex >= 0) {
686
- where = { [unique[propIndex]]: data[unique[propIndex]] };
687
- }
688
- else if (meta.uniques.length > 0) {
689
- for (const u of meta.uniques) {
690
- if (Utils.asArray(u.properties).every(p => data[p] != null)) {
691
- where = Utils.asArray(u.properties).reduce((o, key) => {
692
- o[key] = data[key];
693
- return o;
694
- }, {});
695
- break;
696
- }
697
- }
698
- }
699
- }
756
+ where = getWhereCondition(meta, options.onConflictFields, data, where).where;
700
757
  data = QueryHelper.processObjectParams(data);
701
- em.validator.validateParams(data, 'insert data');
758
+ validateParams(data, 'insert data');
702
759
  if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
703
760
  await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity: data, em, meta }, meta);
704
761
  }
@@ -736,7 +793,7 @@ export class EntityManager {
736
793
  where[meta.primaryKeys[0]] = ret.insertId;
737
794
  }
738
795
  }
739
- const data2 = await this.driver.findOne(meta.className, where, {
796
+ const data2 = await this.driver.findOne(meta.class, where, {
740
797
  fields: returning,
741
798
  ctx: em.transactionContext,
742
799
  convertCustomTypes: true,
@@ -791,11 +848,11 @@ export class EntityManager {
791
848
  let entityName;
792
849
  let propIndex;
793
850
  if (data === undefined) {
794
- entityName = entityNameOrEntity[0].constructor.name;
851
+ entityName = entityNameOrEntity[0].constructor;
795
852
  data = entityNameOrEntity;
796
853
  }
797
854
  else {
798
- entityName = Utils.className(entityNameOrEntity);
855
+ entityName = entityNameOrEntity;
799
856
  }
800
857
  const batchSize = options.batchSize ?? this.config.get('batchSize');
801
858
  if (data.length > batchSize) {
@@ -839,32 +896,18 @@ export class EntityManager {
839
896
  }
840
897
  }
841
898
  }
842
- const unique = meta.props.filter(p => p.unique).map(p => p.name);
843
- propIndex = unique.findIndex(p => row[p] != null);
844
- if (options.onConflictFields || where == null) {
845
- if (propIndex >= 0) {
846
- where = { [unique[propIndex]]: row[unique[propIndex]] };
847
- }
848
- else if (meta.uniques.length > 0) {
849
- for (const u of meta.uniques) {
850
- if (Utils.asArray(u.properties).every(p => row[p] != null)) {
851
- where = Utils.asArray(u.properties).reduce((o, key) => {
852
- o[key] = row[key];
853
- return o;
854
- }, {});
855
- break;
856
- }
857
- }
858
- }
859
- }
860
- 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;
861
903
  where = QueryHelper.processWhere({
862
- where,
904
+ where: tmp.where,
863
905
  entityName,
864
906
  metadata: this.metadata,
865
907
  platform: this.getPlatform(),
866
908
  });
867
- em.validator.validateParams(row, 'insert data');
909
+ row = QueryHelper.processObjectParams(row);
910
+ validateParams(row, 'insert data');
868
911
  allData.push(row);
869
912
  allWhere.push(where);
870
913
  }
@@ -905,7 +948,7 @@ export class EntityManager {
905
948
  const reloadFields = returning.length > 0 && !(this.getPlatform().usesReturningStatement() && res.rows?.length);
906
949
  if (options.onConflictAction === 'ignore' || (!res.rows?.length && loadPK.size > 0) || reloadFields) {
907
950
  const unique = meta.hydrateProps.filter(p => !p.lazy).map(p => p.name);
908
- const add = new Set(propIndex >= 0 ? [unique[propIndex]] : []);
951
+ const add = new Set(propIndex !== false && propIndex >= 0 ? [unique[propIndex]] : []);
909
952
  for (const cond of loadPK.values()) {
910
953
  Utils.keys(cond).forEach(key => add.add(key));
911
954
  }
@@ -917,7 +960,7 @@ export class EntityManager {
917
960
  where.$or[idx][prop] = item[prop];
918
961
  });
919
962
  });
920
- const data2 = await this.driver.find(meta.className, where, {
963
+ const data2 = await this.driver.find(meta.class, where, {
921
964
  fields: returning.concat(...add).concat(...(Array.isArray(uniqueFields) ? uniqueFields : [])),
922
965
  ctx: em.transactionContext,
923
966
  convertCustomTypes: true,
@@ -934,7 +977,7 @@ export class EntityManager {
934
977
  });
935
978
  return this.comparator.matching(entityName, cond, tmp);
936
979
  });
937
- /* v8 ignore next 3 */
980
+ /* v8 ignore next */
938
981
  if (!row) {
939
982
  throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
940
983
  }
@@ -957,7 +1000,7 @@ export class EntityManager {
957
1000
  }, {});
958
1001
  return this.comparator.matching(entityName, cond, pk);
959
1002
  });
960
- /* v8 ignore next 3 */
1003
+ /* v8 ignore next */
961
1004
  if (!row) {
962
1005
  throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
963
1006
  }
@@ -979,6 +1022,29 @@ export class EntityManager {
979
1022
  }
980
1023
  /**
981
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
+ * ```
982
1048
  */
983
1049
  async transactional(cb, options = {}) {
984
1050
  const em = this.getContext(false);
@@ -1047,11 +1113,11 @@ export class EntityManager {
1047
1113
  em.prepareOptions(options);
1048
1114
  let entityName;
1049
1115
  if (data === undefined) {
1050
- entityName = entityNameOrEntity.constructor.name;
1116
+ entityName = entityNameOrEntity.constructor;
1051
1117
  data = entityNameOrEntity;
1052
1118
  }
1053
1119
  else {
1054
- entityName = Utils.className(entityNameOrEntity);
1120
+ entityName = entityNameOrEntity;
1055
1121
  }
1056
1122
  if (Utils.isEntity(data)) {
1057
1123
  if (options.schema && helper(data).getSchema() == null) {
@@ -1070,7 +1136,7 @@ export class EntityManager {
1070
1136
  return cs.getPrimaryKey();
1071
1137
  }
1072
1138
  data = QueryHelper.processObjectParams(data);
1073
- em.validator.validateParams(data, 'insert data');
1139
+ validateParams(data, 'insert data');
1074
1140
  const res = await em.driver.nativeInsert(entityName, data, { ctx: em.transactionContext, ...options });
1075
1141
  return res.insertId;
1076
1142
  }
@@ -1082,11 +1148,11 @@ export class EntityManager {
1082
1148
  em.prepareOptions(options);
1083
1149
  let entityName;
1084
1150
  if (data === undefined) {
1085
- entityName = entityNameOrEntities[0].constructor.name;
1151
+ entityName = entityNameOrEntities[0].constructor;
1086
1152
  data = entityNameOrEntities;
1087
1153
  }
1088
1154
  else {
1089
- entityName = Utils.className(entityNameOrEntities);
1155
+ entityName = entityNameOrEntities;
1090
1156
  }
1091
1157
  if (data.length === 0) {
1092
1158
  return [];
@@ -1110,7 +1176,7 @@ export class EntityManager {
1110
1176
  return css.map(cs => cs.getPrimaryKey());
1111
1177
  }
1112
1178
  data = data.map(row => QueryHelper.processObjectParams(row));
1113
- data.forEach(row => em.validator.validateParams(row, 'insert data'));
1179
+ data.forEach(row => validateParams(row, 'insert data'));
1114
1180
  const res = await em.driver.nativeInsertMany(entityName, data, { ctx: em.transactionContext, ...options });
1115
1181
  if (res.insertedIds) {
1116
1182
  return res.insertedIds;
@@ -1123,11 +1189,10 @@ export class EntityManager {
1123
1189
  async nativeUpdate(entityName, where, data, options = {}) {
1124
1190
  const em = this.getContext(false);
1125
1191
  em.prepareOptions(options);
1126
- entityName = Utils.className(entityName);
1127
1192
  data = QueryHelper.processObjectParams(data);
1128
1193
  where = await em.processWhere(entityName, where, { ...options, convertCustomTypes: false }, 'update');
1129
- em.validator.validateParams(data, 'update data');
1130
- em.validator.validateParams(where, 'update condition');
1194
+ validateParams(data, 'update data');
1195
+ validateParams(where, 'update condition');
1131
1196
  const res = await em.driver.nativeUpdate(entityName, where, data, { ctx: em.transactionContext, ...options });
1132
1197
  return res.affectedRows;
1133
1198
  }
@@ -1137,9 +1202,8 @@ export class EntityManager {
1137
1202
  async nativeDelete(entityName, where, options = {}) {
1138
1203
  const em = this.getContext(false);
1139
1204
  em.prepareOptions(options);
1140
- entityName = Utils.className(entityName);
1141
1205
  where = await em.processWhere(entityName, where, options, 'delete');
1142
- em.validator.validateParams(where, 'delete condition');
1206
+ validateParams(where, 'delete condition');
1143
1207
  const res = await em.driver.nativeDelete(entityName, where, { ctx: em.transactionContext, ...options });
1144
1208
  return res.affectedRows;
1145
1209
  }
@@ -1147,18 +1211,19 @@ export class EntityManager {
1147
1211
  * Maps raw database result to an entity and merges it to this EntityManager.
1148
1212
  */
1149
1213
  map(entityName, result, options = {}) {
1150
- entityName = Utils.className(entityName);
1151
1214
  const meta = this.metadata.get(entityName);
1152
1215
  const data = this.driver.mapResult(result, meta);
1153
- Object.keys(data).forEach(k => {
1216
+ for (const k of Object.keys(data)) {
1154
1217
  const prop = meta.properties[k];
1155
- if (prop && prop.kind === ReferenceKind.SCALAR && SCALAR_TYPES.includes(prop.runtimeType) && !prop.customType && (prop.setter || !prop.getter)) {
1156
- data[k] = this.validator.validateProperty(prop, data[k], data);
1218
+ if (prop?.kind === ReferenceKind.SCALAR && SCALAR_TYPES.has(prop.runtimeType) && !prop.customType && (prop.setter || !prop.getter)) {
1219
+ validateProperty(prop, data[k], data);
1157
1220
  }
1158
- });
1221
+ }
1159
1222
  return this.merge(entityName, data, {
1160
1223
  convertCustomTypes: true,
1161
- refresh: true, ...options,
1224
+ refresh: true,
1225
+ validate: false,
1226
+ ...options,
1162
1227
  });
1163
1228
  }
1164
1229
  /**
@@ -1167,26 +1232,21 @@ export class EntityManager {
1167
1232
  */
1168
1233
  merge(entityName, data, options = {}) {
1169
1234
  if (Utils.isEntity(entityName)) {
1170
- return this.merge(entityName.constructor.name, entityName, data);
1235
+ return this.merge(entityName.constructor, entityName, data);
1171
1236
  }
1172
1237
  const em = options.disableContextResolution ? this : this.getContext();
1173
1238
  options.schema ??= em._schema;
1174
- entityName = Utils.className(entityName);
1175
- em.validator.validatePrimaryKey(data, em.metadata.get(entityName));
1239
+ options.validate ??= true;
1240
+ options.cascade ??= true;
1241
+ validatePrimaryKey(data, em.metadata.get(entityName));
1176
1242
  let entity = em.unitOfWork.tryGetById(entityName, data, options.schema, false);
1177
1243
  if (entity && helper(entity).__managed && helper(entity).__initialized && !options.refresh) {
1178
1244
  return entity;
1179
1245
  }
1180
- const meta = em.metadata.find(entityName);
1181
- const childMeta = em.metadata.getByDiscriminatorColumn(meta, data);
1182
1246
  const dataIsEntity = Utils.isEntity(data);
1183
- if (options.keepIdentity && entity && dataIsEntity && entity !== data) {
1184
- em.entityFactory.mergeData(meta, entity, helper(data).__originalEntityData, { initialized: true, merge: true, ...options });
1185
- return entity;
1186
- }
1187
1247
  entity = dataIsEntity ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
1188
- em.validator.validate(entity, data, childMeta ?? meta);
1189
- em.unitOfWork.merge(entity);
1248
+ const visited = options.cascade ? undefined : new Set([entity]);
1249
+ em.unitOfWork.merge(entity, visited);
1190
1250
  return entity;
1191
1251
  }
1192
1252
  /**
@@ -1211,6 +1271,7 @@ export class EntityManager {
1211
1271
  ...options,
1212
1272
  newEntity: !options.managed,
1213
1273
  merge: options.managed,
1274
+ normalizeAccessors: true,
1214
1275
  });
1215
1276
  options.persist ??= em.config.get('persistOnCreate');
1216
1277
  if (options.persist && !this.getMetadata(entityName).embeddable) {
@@ -1230,7 +1291,7 @@ export class EntityManager {
1230
1291
  getReference(entityName, id, options = {}) {
1231
1292
  options.schema ??= this.schema;
1232
1293
  options.convertCustomTypes ??= false;
1233
- const meta = this.metadata.get(Utils.className(entityName));
1294
+ const meta = this.metadata.get(entityName);
1234
1295
  if (Utils.isPrimaryKey(id)) {
1235
1296
  if (meta.compositePK) {
1236
1297
  throw ValidationError.invalidCompositeIdentifier(meta);
@@ -1251,7 +1312,6 @@ export class EntityManager {
1251
1312
  // Shallow copy options since the object will be modified when deleting orderBy
1252
1313
  options = { ...options };
1253
1314
  em.prepareOptions(options);
1254
- entityName = Utils.className(entityName);
1255
1315
  await em.tryFlush(entityName, options);
1256
1316
  where = await em.processWhere(entityName, where, options, 'read');
1257
1317
  options.populate = await em.preparePopulate(entityName, options);
@@ -1260,8 +1320,8 @@ export class EntityManager {
1260
1320
  const meta = em.metadata.find(entityName);
1261
1321
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
1262
1322
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
1263
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
1264
- em.validator.validateParams(where);
1323
+ options.populateFilter = await this.getJoinedFilters(meta, options);
1324
+ validateParams(where);
1265
1325
  delete options.orderBy;
1266
1326
  const cacheKey = em.cacheKey(entityName, options, 'em.count', where);
1267
1327
  const cached = await em.tryCache(entityName, options.cache, cacheKey);
@@ -1287,7 +1347,7 @@ export class EntityManager {
1287
1347
  for (const ent of entities) {
1288
1348
  if (!Utils.isEntity(ent, true)) {
1289
1349
  /* v8 ignore next */
1290
- const meta = typeof ent === 'object' ? em.metadata.find(ent.constructor.name) : undefined;
1350
+ const meta = typeof ent === 'object' ? em.metadata.find(ent.constructor) : undefined;
1291
1351
  throw ValidationError.notDiscoveredEntity(ent, meta);
1292
1352
  }
1293
1353
  // do not cascade just yet, cascading of entities in persist stack is done when flushing
@@ -1295,13 +1355,6 @@ export class EntityManager {
1295
1355
  }
1296
1356
  return this;
1297
1357
  }
1298
- /**
1299
- * Persists your entity immediately, flushing all not yet persisted changes to the database too.
1300
- * Equivalent to `em.persist(e).flush()`.
1301
- */
1302
- async persistAndFlush(entity) {
1303
- await this.persist(entity).flush();
1304
- }
1305
1358
  /**
1306
1359
  * Marks entity for removal.
1307
1360
  * A removed entity will be removed from the database at or before transaction commit or as a result of the flush operation.
@@ -1325,13 +1378,6 @@ export class EntityManager {
1325
1378
  }
1326
1379
  return em;
1327
1380
  }
1328
- /**
1329
- * Removes an entity instance immediately, flushing all not yet persisted changes to the database too.
1330
- * Equivalent to `em.remove(e).flush()`
1331
- */
1332
- async removeAndFlush(entity) {
1333
- await this.remove(entity).flush();
1334
- }
1335
1381
  /**
1336
1382
  * Flushes all changes to objects that have been queued up to now to the database.
1337
1383
  * This effectively synchronizes the in-memory state of managed objects with the database.
@@ -1345,7 +1391,6 @@ export class EntityManager {
1345
1391
  async tryFlush(entityName, options) {
1346
1392
  const em = this.getContext();
1347
1393
  const flushMode = options.flushMode ?? em.flushMode ?? em.config.get('flushMode');
1348
- entityName = Utils.className(entityName);
1349
1394
  const meta = em.metadata.get(entityName);
1350
1395
  if (flushMode === FlushMode.COMMIT) {
1351
1396
  return;
@@ -1364,7 +1409,6 @@ export class EntityManager {
1364
1409
  * Checks whether given property can be populated on the entity.
1365
1410
  */
1366
1411
  canPopulate(entityName, property) {
1367
- entityName = Utils.className(entityName);
1368
1412
  // eslint-disable-next-line prefer-const
1369
1413
  let [p, ...parts] = property.split('.');
1370
1414
  const meta = this.metadata.find(entityName);
@@ -1374,12 +1418,11 @@ export class EntityManager {
1374
1418
  if (p.includes(':')) {
1375
1419
  p = p.split(':', 2)[0];
1376
1420
  }
1377
- const ret = p in meta.root.properties;
1378
- if (!ret) {
1379
- return !!this.metadata.find(property)?.pivotTable;
1380
- }
1421
+ // For TPT inheritance, check the entity's own properties, not just the root's
1422
+ // For STI, meta.properties includes all properties anyway
1423
+ const ret = p in meta.properties;
1381
1424
  if (parts.length > 0) {
1382
- return this.canPopulate((meta.root.properties)[p].type, parts.join('.'));
1425
+ return this.canPopulate(meta.properties[p].targetMeta.class, parts.join('.'));
1383
1426
  }
1384
1427
  return ret;
1385
1428
  }
@@ -1393,8 +1436,8 @@ export class EntityManager {
1393
1436
  }
1394
1437
  const em = this.getContext();
1395
1438
  em.prepareOptions(options);
1396
- const entityName = arr[0].constructor.name;
1397
- const preparedPopulate = await em.preparePopulate(entityName, { populate: populate }, options.validate);
1439
+ const entityName = arr[0].constructor;
1440
+ const preparedPopulate = await em.preparePopulate(entityName, { populate: populate, filters: options.filters }, options.validate);
1398
1441
  await em.entityLoader.populate(entityName, arr, preparedPopulate, options);
1399
1442
  return entities;
1400
1443
  }
@@ -1522,7 +1565,6 @@ export class EntityManager {
1522
1565
  */
1523
1566
  getMetadata(entityName) {
1524
1567
  if (entityName) {
1525
- entityName = Utils.className(entityName);
1526
1568
  return this.metadata.get(entityName);
1527
1569
  }
1528
1570
  return this.metadata;
@@ -1551,8 +1593,8 @@ export class EntityManager {
1551
1593
  lockTableAliases: options.lockTableAliases,
1552
1594
  });
1553
1595
  }
1554
- const preparedPopulate = await this.preparePopulate(meta.className, options);
1555
- await this.entityLoader.populate(meta.className, [entity], preparedPopulate, {
1596
+ const preparedPopulate = await this.preparePopulate(meta.class, options);
1597
+ await this.entityLoader.populate(meta.class, [entity], preparedPopulate, {
1556
1598
  ...options,
1557
1599
  ...this.getPopulateWhere(where, options),
1558
1600
  orderBy: options.populateOrderBy ?? options.orderBy,
@@ -1572,6 +1614,7 @@ export class EntityManager {
1572
1614
  return ret;
1573
1615
  }, []);
1574
1616
  }
1617
+ /** @internal */
1575
1618
  async preparePopulate(entityName, options, validate = true) {
1576
1619
  if (options.populate === false) {
1577
1620
  return [];
@@ -1612,13 +1655,13 @@ export class EntityManager {
1612
1655
  options.populate = pruneToOneRelations(meta, this.buildFields(options.fields));
1613
1656
  }
1614
1657
  if (!options.populate) {
1615
- const populate = this.entityLoader.normalizePopulate(entityName, [], options.strategy);
1658
+ const populate = this.entityLoader.normalizePopulate(entityName, [], options.strategy, true, options.exclude);
1616
1659
  await this.autoJoinRefsForFilters(meta, { ...options, populate });
1617
1660
  return populate;
1618
1661
  }
1619
1662
  if (typeof options.populate !== 'boolean') {
1620
1663
  options.populate = Utils.asArray(options.populate).map(field => {
1621
- /* v8 ignore next 3 */
1664
+ /* v8 ignore next */
1622
1665
  if (typeof field === 'boolean' || field === PopulatePath.ALL) {
1623
1666
  return [{ field: meta.primaryKeys[0], strategy: options.strategy, all: !!field }]; //
1624
1667
  }
@@ -1628,13 +1671,13 @@ export class EntityManager {
1628
1671
  options.flags.push(QueryFlag.INFER_POPULATE);
1629
1672
  return [];
1630
1673
  }
1631
- if (Utils.isString(field)) {
1674
+ if (typeof field === 'string') {
1632
1675
  return [{ field, strategy: options.strategy }];
1633
1676
  }
1634
1677
  return [field];
1635
1678
  }).flat();
1636
1679
  }
1637
- const populate = this.entityLoader.normalizePopulate(entityName, options.populate, options.strategy);
1680
+ const populate = this.entityLoader.normalizePopulate(entityName, options.populate, options.strategy, true, options.exclude);
1638
1681
  const invalid = populate.find(({ field }) => !this.canPopulate(entityName, field));
1639
1682
  if (validate && invalid) {
1640
1683
  throw ValidationError.invalidPropertyName(entityName, invalid.field);
@@ -1665,7 +1708,7 @@ export class EntityManager {
1665
1708
  return !inlineEmbedded && !prop.lazy && !helper(entity).__loadedProperties.has(prop.name);
1666
1709
  });
1667
1710
  }
1668
- if (autoRefresh) {
1711
+ if (autoRefresh || options.filters) {
1669
1712
  return true;
1670
1713
  }
1671
1714
  if (Array.isArray(options.populate)) {
@@ -1689,7 +1732,7 @@ export class EntityManager {
1689
1732
  for (const k of ['ctx', 'strategy', 'flushMode', 'logging', 'loggerContext']) {
1690
1733
  delete opts[k];
1691
1734
  }
1692
- return [entityName, method, opts, where];
1735
+ return [Utils.className(entityName), method, opts, where];
1693
1736
  }
1694
1737
  /**
1695
1738
  * @internal
@@ -1706,21 +1749,17 @@ export class EntityManager {
1706
1749
  return { key: cacheKey, data: cached };
1707
1750
  }
1708
1751
  let data;
1752
+ const createOptions = {
1753
+ merge: true,
1754
+ convertCustomTypes: false,
1755
+ refresh,
1756
+ recomputeSnapshot: true,
1757
+ };
1709
1758
  if (Array.isArray(cached) && merge) {
1710
- data = cached.map(item => em.entityFactory.create(entityName, item, {
1711
- merge: true,
1712
- convertCustomTypes: true,
1713
- refresh,
1714
- recomputeSnapshot: true,
1715
- }));
1759
+ data = cached.map(item => em.entityFactory.create(entityName, item, createOptions));
1716
1760
  }
1717
1761
  else if (Utils.isObject(cached) && merge) {
1718
- data = em.entityFactory.create(entityName, cached, {
1719
- merge: true,
1720
- convertCustomTypes: true,
1721
- refresh,
1722
- recomputeSnapshot: true,
1723
- });
1762
+ data = em.entityFactory.create(entityName, cached, createOptions);
1724
1763
  }
1725
1764
  else {
1726
1765
  data = cached;
@@ -1735,7 +1774,7 @@ export class EntityManager {
1735
1774
  config ??= this.config.get('resultCache').global;
1736
1775
  if (config) {
1737
1776
  const em = this.getContext();
1738
- const expiration = Array.isArray(config) ? config[1] : (Utils.isNumber(config) ? config : undefined);
1777
+ const expiration = Array.isArray(config) ? config[1] : (typeof config === 'number' ? config : undefined);
1739
1778
  await em.resultCache.set(key.key, data instanceof Function ? data() : data, '', expiration);
1740
1779
  }
1741
1780
  }
@@ -1768,6 +1807,19 @@ export class EntityManager {
1768
1807
  set schema(schema) {
1769
1808
  this.getContext(false)._schema = schema ?? undefined;
1770
1809
  }
1810
+ /** @internal */
1811
+ async getDataLoader(type) {
1812
+ const em = this.getContext();
1813
+ if (em.loaders[type]) {
1814
+ return em.loaders[type];
1815
+ }
1816
+ const DataLoader = await DataloaderUtils.getDataLoader();
1817
+ switch (type) {
1818
+ case 'ref': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getRefBatchLoadFn(em)));
1819
+ case '1:m': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getColBatchLoadFn(em)));
1820
+ case 'm:n': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(em)));
1821
+ }
1822
+ }
1771
1823
  /**
1772
1824
  * Returns the ID of this EntityManager. Respects the context, so global EM will give you the contextual ID
1773
1825
  * if executed inside request context handler.
@@ -1776,7 +1828,7 @@ export class EntityManager {
1776
1828
  return this.getContext(false)._id;
1777
1829
  }
1778
1830
  /** @ignore */
1779
- [inspect.custom]() {
1831
+ [Symbol.for('nodejs.util.inspect.custom')]() {
1780
1832
  return `[EntityManager<${this.id}>]`;
1781
1833
  }
1782
1834
  }