@mikro-orm/core 7.0.0-dev.21 → 7.0.0-dev.211

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 (211) hide show
  1. package/EntityManager.d.ts +99 -57
  2. package/EntityManager.js +302 -276
  3. package/MikroORM.d.ts +44 -35
  4. package/MikroORM.js +103 -143
  5. package/README.md +3 -2
  6. package/cache/FileCacheAdapter.d.ts +1 -1
  7. package/cache/FileCacheAdapter.js +8 -7
  8. package/cache/GeneratedCacheAdapter.d.ts +0 -1
  9. package/cache/GeneratedCacheAdapter.js +0 -2
  10. package/cache/index.d.ts +0 -1
  11. package/cache/index.js +0 -1
  12. package/connections/Connection.d.ts +16 -7
  13. package/connections/Connection.js +23 -14
  14. package/drivers/DatabaseDriver.d.ts +25 -16
  15. package/drivers/DatabaseDriver.js +80 -35
  16. package/drivers/IDatabaseDriver.d.ts +44 -17
  17. package/entity/BaseEntity.d.ts +2 -2
  18. package/entity/BaseEntity.js +0 -3
  19. package/entity/Collection.d.ts +94 -29
  20. package/entity/Collection.js +434 -97
  21. package/entity/EntityAssigner.d.ts +1 -1
  22. package/entity/EntityAssigner.js +26 -18
  23. package/entity/EntityFactory.d.ts +13 -1
  24. package/entity/EntityFactory.js +84 -53
  25. package/entity/EntityHelper.d.ts +2 -2
  26. package/entity/EntityHelper.js +40 -15
  27. package/entity/EntityLoader.d.ts +6 -6
  28. package/entity/EntityLoader.js +119 -82
  29. package/entity/EntityRepository.d.ts +24 -4
  30. package/entity/EntityRepository.js +8 -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 +594 -0
  36. package/entity/defineEntity.js +533 -0
  37. package/entity/index.d.ts +3 -2
  38. package/entity/index.js +3 -2
  39. package/entity/utils.d.ts +7 -0
  40. package/entity/utils.js +16 -4
  41. package/entity/validators.d.ts +11 -0
  42. package/entity/validators.js +65 -0
  43. package/enums.d.ts +21 -5
  44. package/enums.js +15 -1
  45. package/errors.d.ts +23 -9
  46. package/errors.js +59 -21
  47. package/events/EventManager.d.ts +2 -1
  48. package/events/EventManager.js +19 -11
  49. package/hydration/Hydrator.js +1 -2
  50. package/hydration/ObjectHydrator.d.ts +4 -4
  51. package/hydration/ObjectHydrator.js +52 -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 +40 -23
  64. package/metadata/EntitySchema.js +81 -34
  65. package/metadata/MetadataDiscovery.d.ts +7 -10
  66. package/metadata/MetadataDiscovery.js +391 -331
  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 +17 -9
  72. package/metadata/MetadataValidator.js +97 -40
  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 +502 -0
  78. package/metadata/types.js +1 -0
  79. package/naming-strategy/AbstractNamingStrategy.d.ts +12 -4
  80. package/naming-strategy/AbstractNamingStrategy.js +14 -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 +24 -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 +18 -11
  91. package/platforms/ExceptionConverter.js +1 -1
  92. package/platforms/Platform.d.ts +7 -13
  93. package/platforms/Platform.js +20 -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 +290 -137
  120. package/typings.js +59 -44
  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 +26 -13
  125. package/unit-of-work/ChangeSetPersister.d.ts +5 -4
  126. package/unit-of-work/ChangeSetPersister.js +70 -34
  127. package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
  128. package/unit-of-work/CommitOrderCalculator.js +13 -13
  129. package/unit-of-work/IdentityMap.d.ts +12 -0
  130. package/unit-of-work/IdentityMap.js +39 -1
  131. package/unit-of-work/UnitOfWork.d.ts +23 -3
  132. package/unit-of-work/UnitOfWork.js +175 -98
  133. package/utils/AbstractSchemaGenerator.d.ts +5 -5
  134. package/utils/AbstractSchemaGenerator.js +18 -16
  135. package/utils/AsyncContext.d.ts +6 -0
  136. package/utils/AsyncContext.js +42 -0
  137. package/utils/Configuration.d.ts +785 -207
  138. package/utils/Configuration.js +147 -190
  139. package/utils/ConfigurationLoader.d.ts +1 -54
  140. package/utils/ConfigurationLoader.js +1 -352
  141. package/utils/Cursor.d.ts +0 -3
  142. package/utils/Cursor.js +27 -11
  143. package/utils/DataloaderUtils.d.ts +15 -5
  144. package/utils/DataloaderUtils.js +64 -30
  145. package/utils/EntityComparator.d.ts +13 -9
  146. package/utils/EntityComparator.js +101 -42
  147. package/utils/QueryHelper.d.ts +14 -6
  148. package/utils/QueryHelper.js +87 -25
  149. package/utils/RawQueryFragment.d.ts +48 -25
  150. package/utils/RawQueryFragment.js +66 -70
  151. package/utils/RequestContext.js +2 -2
  152. package/utils/TransactionContext.js +2 -2
  153. package/utils/TransactionManager.d.ts +65 -0
  154. package/utils/TransactionManager.js +223 -0
  155. package/utils/Utils.d.ts +13 -126
  156. package/utils/Utils.js +100 -391
  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 +32 -0
  161. package/utils/fs-utils.js +178 -0
  162. package/utils/index.d.ts +2 -1
  163. package/utils/index.js +2 -1
  164. package/utils/upsert-utils.d.ts +9 -4
  165. package/utils/upsert-utils.js +55 -4
  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 -18
  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 -40
  185. package/decorators/ManyToMany.js +0 -14
  186. package/decorators/ManyToOne.d.ts +0 -32
  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 -26
  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 -13
  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 -116
  203. package/entity/ArrayCollection.js +0 -402
  204. package/entity/EntityValidator.d.ts +0 -19
  205. package/entity/EntityValidator.js +0 -150
  206. package/exports.d.ts +0 -24
  207. package/exports.js +0 -23
  208. package/metadata/ReflectMetadataProvider.d.ts +0 -8
  209. package/metadata/ReflectMetadataProvider.js +0 -44
  210. package/utils/resolveContextProvider.d.ts +0 -10
  211. 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: false });
575
+ em.config.getHydrator(this.metadata).hydrate(ref, helper(ref).__meta, data, em.entityFactory, 'full', false, false);
576
+ Utils.merge(helper(ref).__originalEntityData, this.comparator.prepareEntity(e));
577
+ found ||= ref === entity;
502
578
  }
503
- return reloaded ? entity : reloaded;
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));
583
+ }
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,13 +606,13 @@ 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);
@@ -541,7 +621,6 @@ export class EntityManager {
541
621
  await em.entityLoader.populate(entityName, [cached.data], options.populate, {
542
622
  ...options,
543
623
  ...em.getPopulateWhere(where, options),
544
- convertCustomTypes: false,
545
624
  ignoreLazyScalarProperties: true,
546
625
  lookup: false,
547
626
  });
@@ -552,7 +631,7 @@ export class EntityManager {
552
631
  // save the original hint value so we know it was infer/all
553
632
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
554
633
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
555
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
634
+ options.populateFilter = await this.getJoinedFilters(meta, options);
556
635
  const data = await em.driver.findOne(entityName, where, {
557
636
  ctx: em.transactionContext,
558
637
  em,
@@ -593,10 +672,10 @@ export class EntityManager {
593
672
  if (!entity || isStrictViolation) {
594
673
  const key = options.strict ? 'findExactlyOneOrFailHandler' : 'findOneOrFailHandler';
595
674
  options.failHandler ??= this.config.get(key);
596
- entityName = Utils.className(entityName);
675
+ const name = Utils.className(entityName);
597
676
  /* v8 ignore next */
598
677
  where = Utils.isEntity(where) ? helper(where).getPrimaryKey() : where;
599
- throw options.failHandler(entityName, where);
678
+ throw options.failHandler(name, where);
600
679
  }
601
680
  return entity;
602
681
  }
@@ -636,11 +715,11 @@ export class EntityManager {
636
715
  let where;
637
716
  let entity = null;
638
717
  if (data === undefined) {
639
- entityName = entityNameOrEntity.constructor.name;
718
+ entityName = entityNameOrEntity.constructor;
640
719
  data = entityNameOrEntity;
641
720
  }
642
721
  else {
643
- entityName = Utils.className(entityNameOrEntity);
722
+ entityName = entityNameOrEntity;
644
723
  }
645
724
  const meta = this.metadata.get(entityName);
646
725
  const convertCustomTypes = !Utils.isEntity(data);
@@ -663,26 +742,9 @@ export class EntityManager {
663
742
  }
664
743
  }
665
744
  }
666
- const unique = options.onConflictFields ?? meta.props.filter(p => p.unique).map(p => p.name);
667
- const propIndex = !isRaw(unique) && unique.findIndex(p => data[p] != null);
668
- if (options.onConflictFields || where == null) {
669
- if (propIndex !== false && propIndex >= 0) {
670
- where = { [unique[propIndex]]: data[unique[propIndex]] };
671
- }
672
- else if (meta.uniques.length > 0) {
673
- for (const u of meta.uniques) {
674
- if (Utils.asArray(u.properties).every(p => data[p] != null)) {
675
- where = Utils.asArray(u.properties).reduce((o, key) => {
676
- o[key] = data[key];
677
- return o;
678
- }, {});
679
- break;
680
- }
681
- }
682
- }
683
- }
745
+ where = getWhereCondition(meta, options.onConflictFields, data, where).where;
684
746
  data = QueryHelper.processObjectParams(data);
685
- em.validator.validateParams(data, 'insert data');
747
+ validateParams(data, 'insert data');
686
748
  if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
687
749
  await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity: data, em, meta }, meta);
688
750
  }
@@ -720,7 +782,7 @@ export class EntityManager {
720
782
  where[meta.primaryKeys[0]] = ret.insertId;
721
783
  }
722
784
  }
723
- const data2 = await this.driver.findOne(meta.className, where, {
785
+ const data2 = await this.driver.findOne(meta.class, where, {
724
786
  fields: returning,
725
787
  ctx: em.transactionContext,
726
788
  convertCustomTypes: true,
@@ -775,11 +837,11 @@ export class EntityManager {
775
837
  let entityName;
776
838
  let propIndex;
777
839
  if (data === undefined) {
778
- entityName = entityNameOrEntity[0].constructor.name;
840
+ entityName = entityNameOrEntity[0].constructor;
779
841
  data = entityNameOrEntity;
780
842
  }
781
843
  else {
782
- entityName = Utils.className(entityNameOrEntity);
844
+ entityName = entityNameOrEntity;
783
845
  }
784
846
  const batchSize = options.batchSize ?? this.config.get('batchSize');
785
847
  if (data.length > batchSize) {
@@ -823,32 +885,18 @@ export class EntityManager {
823
885
  }
824
886
  }
825
887
  }
826
- const unique = meta.props.filter(p => p.unique).map(p => p.name);
827
- propIndex = unique.findIndex(p => row[p] != null);
828
- if (options.onConflictFields || where == null) {
829
- if (propIndex >= 0) {
830
- where = { [unique[propIndex]]: row[unique[propIndex]] };
831
- }
832
- else if (meta.uniques.length > 0) {
833
- for (const u of meta.uniques) {
834
- if (Utils.asArray(u.properties).every(p => row[p] != null)) {
835
- where = Utils.asArray(u.properties).reduce((o, key) => {
836
- o[key] = row[key];
837
- return o;
838
- }, {});
839
- break;
840
- }
841
- }
842
- }
843
- }
844
- 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;
845
892
  where = QueryHelper.processWhere({
846
- where,
893
+ where: tmp.where,
847
894
  entityName,
848
895
  metadata: this.metadata,
849
896
  platform: this.getPlatform(),
850
897
  });
851
- em.validator.validateParams(row, 'insert data');
898
+ row = QueryHelper.processObjectParams(row);
899
+ validateParams(row, 'insert data');
852
900
  allData.push(row);
853
901
  allWhere.push(where);
854
902
  }
@@ -889,7 +937,7 @@ export class EntityManager {
889
937
  const reloadFields = returning.length > 0 && !(this.getPlatform().usesReturningStatement() && res.rows?.length);
890
938
  if (options.onConflictAction === 'ignore' || (!res.rows?.length && loadPK.size > 0) || reloadFields) {
891
939
  const unique = meta.hydrateProps.filter(p => !p.lazy).map(p => p.name);
892
- const add = new Set(propIndex >= 0 ? [unique[propIndex]] : []);
940
+ const add = new Set(propIndex !== false && propIndex >= 0 ? [unique[propIndex]] : []);
893
941
  for (const cond of loadPK.values()) {
894
942
  Utils.keys(cond).forEach(key => add.add(key));
895
943
  }
@@ -901,7 +949,7 @@ export class EntityManager {
901
949
  where.$or[idx][prop] = item[prop];
902
950
  });
903
951
  });
904
- const data2 = await this.driver.find(meta.className, where, {
952
+ const data2 = await this.driver.find(meta.class, where, {
905
953
  fields: returning.concat(...add).concat(...(Array.isArray(uniqueFields) ? uniqueFields : [])),
906
954
  ctx: em.transactionContext,
907
955
  convertCustomTypes: true,
@@ -918,7 +966,7 @@ export class EntityManager {
918
966
  });
919
967
  return this.comparator.matching(entityName, cond, tmp);
920
968
  });
921
- /* v8 ignore next 3 */
969
+ /* v8 ignore next */
922
970
  if (!row) {
923
971
  throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
924
972
  }
@@ -941,7 +989,7 @@ export class EntityManager {
941
989
  }, {});
942
990
  return this.comparator.matching(entityName, cond, pk);
943
991
  });
944
- /* v8 ignore next 3 */
992
+ /* v8 ignore next */
945
993
  if (!row) {
946
994
  throw new Error(`Cannot find matching entity for condition ${JSON.stringify(cond)}`);
947
995
  }
@@ -963,45 +1011,37 @@ export class EntityManager {
963
1011
  }
964
1012
  /**
965
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
+ * ```
966
1037
  */
967
1038
  async transactional(cb, options = {}) {
968
1039
  const em = this.getContext(false);
969
1040
  if (this.disableTransactions || em.disableTransactions) {
970
1041
  return cb(em);
971
1042
  }
972
- const fork = em.fork({
973
- clear: options.clear ?? false, // state will be merged once resolves
974
- flushMode: options.flushMode,
975
- cloneEventManager: true,
976
- disableTransactions: options.ignoreNestedTransactions,
977
- loggerContext: options.loggerContext,
978
- });
979
- options.ctx ??= em.transactionContext;
980
- const propagateToUpperContext = !em.global || this.config.get('allowGlobalContext');
981
- return TransactionContext.create(fork, async () => {
982
- return fork.getConnection().transactional(async (trx) => {
983
- fork.transactionContext = trx;
984
- if (propagateToUpperContext) {
985
- fork.eventManager.registerSubscriber({
986
- afterFlush(args) {
987
- args.uow.getChangeSets()
988
- .filter(cs => [ChangeSetType.DELETE, ChangeSetType.DELETE_EARLY].includes(cs.type))
989
- .forEach(cs => em.unitOfWork.unsetIdentity(cs.entity));
990
- },
991
- });
992
- }
993
- const ret = await cb(fork);
994
- await fork.flush();
995
- if (propagateToUpperContext) {
996
- // ensure all entities from inner context are merged to the upper one
997
- for (const entity of fork.unitOfWork.getIdentityMap()) {
998
- em.unitOfWork.register(entity);
999
- entity.__helper.__em = em;
1000
- }
1001
- }
1002
- return ret;
1003
- }, { ...options, eventBroadcaster: new TransactionEventBroadcaster(fork, { topLevelTransaction: !options.ctx }) });
1004
- });
1043
+ const manager = new TransactionManager(this);
1044
+ return manager.handle(cb, options);
1005
1045
  }
1006
1046
  /**
1007
1047
  * Starts new transaction bound to this EntityManager. Use `ctx` parameter to provide the parent when nesting transactions.
@@ -1062,11 +1102,11 @@ export class EntityManager {
1062
1102
  em.prepareOptions(options);
1063
1103
  let entityName;
1064
1104
  if (data === undefined) {
1065
- entityName = entityNameOrEntity.constructor.name;
1105
+ entityName = entityNameOrEntity.constructor;
1066
1106
  data = entityNameOrEntity;
1067
1107
  }
1068
1108
  else {
1069
- entityName = Utils.className(entityNameOrEntity);
1109
+ entityName = entityNameOrEntity;
1070
1110
  }
1071
1111
  if (Utils.isEntity(data)) {
1072
1112
  if (options.schema && helper(data).getSchema() == null) {
@@ -1085,7 +1125,7 @@ export class EntityManager {
1085
1125
  return cs.getPrimaryKey();
1086
1126
  }
1087
1127
  data = QueryHelper.processObjectParams(data);
1088
- em.validator.validateParams(data, 'insert data');
1128
+ validateParams(data, 'insert data');
1089
1129
  const res = await em.driver.nativeInsert(entityName, data, { ctx: em.transactionContext, ...options });
1090
1130
  return res.insertId;
1091
1131
  }
@@ -1097,11 +1137,11 @@ export class EntityManager {
1097
1137
  em.prepareOptions(options);
1098
1138
  let entityName;
1099
1139
  if (data === undefined) {
1100
- entityName = entityNameOrEntities[0].constructor.name;
1140
+ entityName = entityNameOrEntities[0].constructor;
1101
1141
  data = entityNameOrEntities;
1102
1142
  }
1103
1143
  else {
1104
- entityName = Utils.className(entityNameOrEntities);
1144
+ entityName = entityNameOrEntities;
1105
1145
  }
1106
1146
  if (data.length === 0) {
1107
1147
  return [];
@@ -1125,7 +1165,7 @@ export class EntityManager {
1125
1165
  return css.map(cs => cs.getPrimaryKey());
1126
1166
  }
1127
1167
  data = data.map(row => QueryHelper.processObjectParams(row));
1128
- data.forEach(row => em.validator.validateParams(row, 'insert data'));
1168
+ data.forEach(row => validateParams(row, 'insert data'));
1129
1169
  const res = await em.driver.nativeInsertMany(entityName, data, { ctx: em.transactionContext, ...options });
1130
1170
  if (res.insertedIds) {
1131
1171
  return res.insertedIds;
@@ -1138,11 +1178,10 @@ export class EntityManager {
1138
1178
  async nativeUpdate(entityName, where, data, options = {}) {
1139
1179
  const em = this.getContext(false);
1140
1180
  em.prepareOptions(options);
1141
- entityName = Utils.className(entityName);
1142
1181
  data = QueryHelper.processObjectParams(data);
1143
1182
  where = await em.processWhere(entityName, where, { ...options, convertCustomTypes: false }, 'update');
1144
- em.validator.validateParams(data, 'update data');
1145
- em.validator.validateParams(where, 'update condition');
1183
+ validateParams(data, 'update data');
1184
+ validateParams(where, 'update condition');
1146
1185
  const res = await em.driver.nativeUpdate(entityName, where, data, { ctx: em.transactionContext, ...options });
1147
1186
  return res.affectedRows;
1148
1187
  }
@@ -1152,9 +1191,8 @@ export class EntityManager {
1152
1191
  async nativeDelete(entityName, where, options = {}) {
1153
1192
  const em = this.getContext(false);
1154
1193
  em.prepareOptions(options);
1155
- entityName = Utils.className(entityName);
1156
1194
  where = await em.processWhere(entityName, where, options, 'delete');
1157
- em.validator.validateParams(where, 'delete condition');
1195
+ validateParams(where, 'delete condition');
1158
1196
  const res = await em.driver.nativeDelete(entityName, where, { ctx: em.transactionContext, ...options });
1159
1197
  return res.affectedRows;
1160
1198
  }
@@ -1162,18 +1200,19 @@ export class EntityManager {
1162
1200
  * Maps raw database result to an entity and merges it to this EntityManager.
1163
1201
  */
1164
1202
  map(entityName, result, options = {}) {
1165
- entityName = Utils.className(entityName);
1166
1203
  const meta = this.metadata.get(entityName);
1167
1204
  const data = this.driver.mapResult(result, meta);
1168
- Object.keys(data).forEach(k => {
1205
+ for (const k of Object.keys(data)) {
1169
1206
  const prop = meta.properties[k];
1170
- if (prop && prop.kind === ReferenceKind.SCALAR && SCALAR_TYPES.includes(prop.runtimeType) && !prop.customType && (prop.setter || !prop.getter)) {
1171
- 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);
1172
1209
  }
1173
- });
1210
+ }
1174
1211
  return this.merge(entityName, data, {
1175
1212
  convertCustomTypes: true,
1176
- refresh: true, ...options,
1213
+ refresh: true,
1214
+ validate: false,
1215
+ ...options,
1177
1216
  });
1178
1217
  }
1179
1218
  /**
@@ -1181,22 +1220,22 @@ export class EntityManager {
1181
1220
  * via second parameter. By default, it will return already loaded entities without modifying them.
1182
1221
  */
1183
1222
  merge(entityName, data, options = {}) {
1184
- const em = this.getContext();
1185
1223
  if (Utils.isEntity(entityName)) {
1186
- return em.merge(entityName.constructor.name, entityName, data);
1224
+ return this.merge(entityName.constructor, entityName, data);
1187
1225
  }
1226
+ const em = options.disableContextResolution ? this : this.getContext();
1188
1227
  options.schema ??= em._schema;
1189
- entityName = Utils.className(entityName);
1190
- em.validator.validatePrimaryKey(data, em.metadata.get(entityName));
1228
+ options.validate ??= true;
1229
+ options.cascade ??= true;
1230
+ validatePrimaryKey(data, em.metadata.get(entityName));
1191
1231
  let entity = em.unitOfWork.tryGetById(entityName, data, options.schema, false);
1192
1232
  if (entity && helper(entity).__managed && helper(entity).__initialized && !options.refresh) {
1193
1233
  return entity;
1194
1234
  }
1195
- const meta = em.metadata.find(entityName);
1196
- const childMeta = em.metadata.getByDiscriminatorColumn(meta, data);
1197
- entity = Utils.isEntity(data) ? data : em.entityFactory.create(entityName, data, { merge: true, ...options });
1198
- em.validator.validate(entity, data, childMeta ?? meta);
1199
- 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);
1200
1239
  return entity;
1201
1240
  }
1202
1241
  /**
@@ -1221,6 +1260,7 @@ export class EntityManager {
1221
1260
  ...options,
1222
1261
  newEntity: !options.managed,
1223
1262
  merge: options.managed,
1263
+ normalizeAccessors: true,
1224
1264
  });
1225
1265
  options.persist ??= em.config.get('persistOnCreate');
1226
1266
  if (options.persist && !this.getMetadata(entityName).embeddable) {
@@ -1240,7 +1280,7 @@ export class EntityManager {
1240
1280
  getReference(entityName, id, options = {}) {
1241
1281
  options.schema ??= this.schema;
1242
1282
  options.convertCustomTypes ??= false;
1243
- const meta = this.metadata.get(Utils.className(entityName));
1283
+ const meta = this.metadata.get(entityName);
1244
1284
  if (Utils.isPrimaryKey(id)) {
1245
1285
  if (meta.compositePK) {
1246
1286
  throw ValidationError.invalidCompositeIdentifier(meta);
@@ -1259,11 +1299,8 @@ export class EntityManager {
1259
1299
  async count(entityName, where = {}, options = {}) {
1260
1300
  const em = this.getContext(false);
1261
1301
  // Shallow copy options since the object will be modified when deleting orderBy
1262
- options = {
1263
- schema: em._schema,
1264
- ...options,
1265
- };
1266
- entityName = Utils.className(entityName);
1302
+ options = { ...options };
1303
+ em.prepareOptions(options);
1267
1304
  await em.tryFlush(entityName, options);
1268
1305
  where = await em.processWhere(entityName, where, options, 'read');
1269
1306
  options.populate = await em.preparePopulate(entityName, options);
@@ -1272,15 +1309,15 @@ export class EntityManager {
1272
1309
  const meta = em.metadata.find(entityName);
1273
1310
  options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
1274
1311
  options.populateWhere = this.createPopulateWhere({ ...where }, options);
1275
- options.populateFilter = await this.getJoinedFilters(meta, { ...where }, options);
1276
- em.validator.validateParams(where);
1312
+ options.populateFilter = await this.getJoinedFilters(meta, options);
1313
+ validateParams(where);
1277
1314
  delete options.orderBy;
1278
1315
  const cacheKey = em.cacheKey(entityName, options, 'em.count', where);
1279
1316
  const cached = await em.tryCache(entityName, options.cache, cacheKey);
1280
1317
  if (cached?.data !== undefined) {
1281
1318
  return cached.data;
1282
1319
  }
1283
- 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 });
1284
1321
  await em.storeCache(options.cache, cached, () => +count);
1285
1322
  return +count;
1286
1323
  }
@@ -1299,7 +1336,7 @@ export class EntityManager {
1299
1336
  for (const ent of entities) {
1300
1337
  if (!Utils.isEntity(ent, true)) {
1301
1338
  /* v8 ignore next */
1302
- const meta = typeof ent === 'object' ? em.metadata.find(ent.constructor.name) : undefined;
1339
+ const meta = typeof ent === 'object' ? em.metadata.find(ent.constructor) : undefined;
1303
1340
  throw ValidationError.notDiscoveredEntity(ent, meta);
1304
1341
  }
1305
1342
  // do not cascade just yet, cascading of entities in persist stack is done when flushing
@@ -1307,13 +1344,6 @@ export class EntityManager {
1307
1344
  }
1308
1345
  return this;
1309
1346
  }
1310
- /**
1311
- * Persists your entity immediately, flushing all not yet persisted changes to the database too.
1312
- * Equivalent to `em.persist(e).flush()`.
1313
- */
1314
- async persistAndFlush(entity) {
1315
- await this.persist(entity).flush();
1316
- }
1317
1347
  /**
1318
1348
  * Marks entity for removal.
1319
1349
  * A removed entity will be removed from the database at or before transaction commit or as a result of the flush operation.
@@ -1337,13 +1367,6 @@ export class EntityManager {
1337
1367
  }
1338
1368
  return em;
1339
1369
  }
1340
- /**
1341
- * Removes an entity instance immediately, flushing all not yet persisted changes to the database too.
1342
- * Equivalent to `em.remove(e).flush()`
1343
- */
1344
- async removeAndFlush(entity) {
1345
- await this.remove(entity).flush();
1346
- }
1347
1370
  /**
1348
1371
  * Flushes all changes to objects that have been queued up to now to the database.
1349
1372
  * This effectively synchronizes the in-memory state of managed objects with the database.
@@ -1357,7 +1380,6 @@ export class EntityManager {
1357
1380
  async tryFlush(entityName, options) {
1358
1381
  const em = this.getContext();
1359
1382
  const flushMode = options.flushMode ?? em.flushMode ?? em.config.get('flushMode');
1360
- entityName = Utils.className(entityName);
1361
1383
  const meta = em.metadata.get(entityName);
1362
1384
  if (flushMode === FlushMode.COMMIT) {
1363
1385
  return;
@@ -1376,7 +1398,6 @@ export class EntityManager {
1376
1398
  * Checks whether given property can be populated on the entity.
1377
1399
  */
1378
1400
  canPopulate(entityName, property) {
1379
- entityName = Utils.className(entityName);
1380
1401
  // eslint-disable-next-line prefer-const
1381
1402
  let [p, ...parts] = property.split('.');
1382
1403
  const meta = this.metadata.find(entityName);
@@ -1387,11 +1408,8 @@ export class EntityManager {
1387
1408
  p = p.split(':', 2)[0];
1388
1409
  }
1389
1410
  const ret = p in meta.root.properties;
1390
- if (!ret) {
1391
- return !!this.metadata.find(property)?.pivotTable;
1392
- }
1393
1411
  if (parts.length > 0) {
1394
- return this.canPopulate((meta.root.properties)[p].type, parts.join('.'));
1412
+ return this.canPopulate(meta.root.properties[p].targetMeta.class, parts.join('.'));
1395
1413
  }
1396
1414
  return ret;
1397
1415
  }
@@ -1405,8 +1423,8 @@ export class EntityManager {
1405
1423
  }
1406
1424
  const em = this.getContext();
1407
1425
  em.prepareOptions(options);
1408
- const entityName = arr[0].constructor.name;
1409
- 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);
1410
1428
  await em.entityLoader.populate(entityName, arr, preparedPopulate, options);
1411
1429
  return entities;
1412
1430
  }
@@ -1534,7 +1552,6 @@ export class EntityManager {
1534
1552
  */
1535
1553
  getMetadata(entityName) {
1536
1554
  if (entityName) {
1537
- entityName = Utils.className(entityName);
1538
1555
  return this.metadata.get(entityName);
1539
1556
  }
1540
1557
  return this.metadata;
@@ -1563,12 +1580,11 @@ export class EntityManager {
1563
1580
  lockTableAliases: options.lockTableAliases,
1564
1581
  });
1565
1582
  }
1566
- const preparedPopulate = await this.preparePopulate(meta.className, options);
1567
- 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, {
1568
1585
  ...options,
1569
1586
  ...this.getPopulateWhere(where, options),
1570
1587
  orderBy: options.populateOrderBy ?? options.orderBy,
1571
- convertCustomTypes: false,
1572
1588
  ignoreLazyScalarProperties: true,
1573
1589
  lookup: false,
1574
1590
  });
@@ -1585,6 +1601,7 @@ export class EntityManager {
1585
1601
  return ret;
1586
1602
  }, []);
1587
1603
  }
1604
+ /** @internal */
1588
1605
  async preparePopulate(entityName, options, validate = true) {
1589
1606
  if (options.populate === false) {
1590
1607
  return [];
@@ -1625,13 +1642,13 @@ export class EntityManager {
1625
1642
  options.populate = pruneToOneRelations(meta, this.buildFields(options.fields));
1626
1643
  }
1627
1644
  if (!options.populate) {
1628
- const populate = this.entityLoader.normalizePopulate(entityName, [], options.strategy);
1645
+ const populate = this.entityLoader.normalizePopulate(entityName, [], options.strategy, true, options.exclude);
1629
1646
  await this.autoJoinRefsForFilters(meta, { ...options, populate });
1630
1647
  return populate;
1631
1648
  }
1632
1649
  if (typeof options.populate !== 'boolean') {
1633
1650
  options.populate = Utils.asArray(options.populate).map(field => {
1634
- /* v8 ignore next 3 */
1651
+ /* v8 ignore next */
1635
1652
  if (typeof field === 'boolean' || field === PopulatePath.ALL) {
1636
1653
  return [{ field: meta.primaryKeys[0], strategy: options.strategy, all: !!field }]; //
1637
1654
  }
@@ -1641,13 +1658,13 @@ export class EntityManager {
1641
1658
  options.flags.push(QueryFlag.INFER_POPULATE);
1642
1659
  return [];
1643
1660
  }
1644
- if (Utils.isString(field)) {
1661
+ if (typeof field === 'string') {
1645
1662
  return [{ field, strategy: options.strategy }];
1646
1663
  }
1647
1664
  return [field];
1648
1665
  }).flat();
1649
1666
  }
1650
- const populate = this.entityLoader.normalizePopulate(entityName, options.populate, options.strategy);
1667
+ const populate = this.entityLoader.normalizePopulate(entityName, options.populate, options.strategy, true, options.exclude);
1651
1668
  const invalid = populate.find(({ field }) => !this.canPopulate(entityName, field));
1652
1669
  if (validate && invalid) {
1653
1670
  throw ValidationError.invalidPropertyName(entityName, invalid.field);
@@ -1678,7 +1695,7 @@ export class EntityManager {
1678
1695
  return !inlineEmbedded && !prop.lazy && !helper(entity).__loadedProperties.has(prop.name);
1679
1696
  });
1680
1697
  }
1681
- if (autoRefresh) {
1698
+ if (autoRefresh || options.filters) {
1682
1699
  return true;
1683
1700
  }
1684
1701
  if (Array.isArray(options.populate)) {
@@ -1691,7 +1708,7 @@ export class EntityManager {
1691
1708
  throw new ValidationError(`Cannot combine 'fields' and 'exclude' option.`);
1692
1709
  }
1693
1710
  options.schema ??= this._schema;
1694
- options.logging = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
1711
+ options.logging = options.loggerContext = Utils.merge({ id: this.id }, this.loggerContext, options.loggerContext, options.logging);
1695
1712
  }
1696
1713
  /**
1697
1714
  * @internal
@@ -1702,7 +1719,7 @@ export class EntityManager {
1702
1719
  for (const k of ['ctx', 'strategy', 'flushMode', 'logging', 'loggerContext']) {
1703
1720
  delete opts[k];
1704
1721
  }
1705
- return [entityName, method, opts, where];
1722
+ return [Utils.className(entityName), method, opts, where];
1706
1723
  }
1707
1724
  /**
1708
1725
  * @internal
@@ -1719,21 +1736,17 @@ export class EntityManager {
1719
1736
  return { key: cacheKey, data: cached };
1720
1737
  }
1721
1738
  let data;
1739
+ const createOptions = {
1740
+ merge: true,
1741
+ convertCustomTypes: false,
1742
+ refresh,
1743
+ recomputeSnapshot: true,
1744
+ };
1722
1745
  if (Array.isArray(cached) && merge) {
1723
- data = cached.map(item => em.entityFactory.create(entityName, item, {
1724
- merge: true,
1725
- convertCustomTypes: true,
1726
- refresh,
1727
- recomputeSnapshot: true,
1728
- }));
1746
+ data = cached.map(item => em.entityFactory.create(entityName, item, createOptions));
1729
1747
  }
1730
1748
  else if (Utils.isObject(cached) && merge) {
1731
- data = em.entityFactory.create(entityName, cached, {
1732
- merge: true,
1733
- convertCustomTypes: true,
1734
- refresh,
1735
- recomputeSnapshot: true,
1736
- });
1749
+ data = em.entityFactory.create(entityName, cached, createOptions);
1737
1750
  }
1738
1751
  else {
1739
1752
  data = cached;
@@ -1748,7 +1761,7 @@ export class EntityManager {
1748
1761
  config ??= this.config.get('resultCache').global;
1749
1762
  if (config) {
1750
1763
  const em = this.getContext();
1751
- const expiration = Array.isArray(config) ? config[1] : (Utils.isNumber(config) ? config : undefined);
1764
+ const expiration = Array.isArray(config) ? config[1] : (typeof config === 'number' ? config : undefined);
1752
1765
  await em.resultCache.set(key.key, data instanceof Function ? data() : data, '', expiration);
1753
1766
  }
1754
1767
  }
@@ -1781,6 +1794,19 @@ export class EntityManager {
1781
1794
  set schema(schema) {
1782
1795
  this.getContext(false)._schema = schema ?? undefined;
1783
1796
  }
1797
+ /** @internal */
1798
+ async getDataLoader(type) {
1799
+ const em = this.getContext();
1800
+ if (em.loaders[type]) {
1801
+ return em.loaders[type];
1802
+ }
1803
+ const DataLoader = await DataloaderUtils.getDataLoader();
1804
+ switch (type) {
1805
+ case 'ref': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getRefBatchLoadFn(em)));
1806
+ case '1:m': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getColBatchLoadFn(em)));
1807
+ case 'm:n': return (em.loaders[type] ??= new DataLoader(DataloaderUtils.getManyToManyColBatchLoadFn(em)));
1808
+ }
1809
+ }
1784
1810
  /**
1785
1811
  * Returns the ID of this EntityManager. Respects the context, so global EM will give you the contextual ID
1786
1812
  * if executed inside request context handler.
@@ -1789,7 +1815,7 @@ export class EntityManager {
1789
1815
  return this.getContext(false)._id;
1790
1816
  }
1791
1817
  /** @ignore */
1792
- [inspect.custom]() {
1818
+ [Symbol.for('nodejs.util.inspect.custom')]() {
1793
1819
  return `[EntityManager<${this.id}>]`;
1794
1820
  }
1795
1821
  }