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

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 +221 -93
  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
@@ -4,8 +4,8 @@ import { ValidationError } from '../errors.js';
4
4
  import { LoadStrategy, PopulatePath, ReferenceKind, } from '../enums.js';
5
5
  import { Reference } from './Reference.js';
6
6
  import { helper } from './wrap.js';
7
- import { raw, RawQueryFragment } from '../utils/RawQueryFragment.js';
8
7
  import { expandDotPaths } from './utils.js';
8
+ import { Raw } from '../utils/RawQueryFragment.js';
9
9
  export class EntityLoader {
10
10
  em;
11
11
  metadata;
@@ -32,7 +32,6 @@ export class EntityLoader {
32
32
  const visited = options.visited ??= new Set();
33
33
  options.where ??= {};
34
34
  options.orderBy ??= {};
35
- options.filters ??= {};
36
35
  options.lookup ??= true;
37
36
  options.validate ??= true;
38
37
  options.refresh ??= false;
@@ -40,9 +39,9 @@ export class EntityLoader {
40
39
  if (references.length > 0) {
41
40
  await this.populateScalar(meta, references, { ...options, populateWhere: undefined });
42
41
  }
43
- populate = this.normalizePopulate(entityName, populate, options.strategy, options.lookup);
42
+ populate = this.normalizePopulate(entityName, populate, options.strategy, options.lookup, options.exclude);
44
43
  const invalid = populate.find(({ field }) => !this.em.canPopulate(entityName, field));
45
- /* v8 ignore next 3 */
44
+ /* v8 ignore next */
46
45
  if (options.validate && invalid) {
47
46
  throw ValidationError.invalidPropertyName(entityName, invalid.field);
48
47
  }
@@ -57,7 +56,7 @@ export class EntityLoader {
57
56
  visited.delete(entity);
58
57
  }
59
58
  }
60
- normalizePopulate(entityName, populate, strategy, lookup = true) {
59
+ normalizePopulate(entityName, populate, strategy, lookup = true, exclude) {
61
60
  const meta = this.metadata.find(entityName);
62
61
  let normalized = Utils.asArray(populate).map(field => {
63
62
  return typeof field === 'boolean' || field.field === PopulatePath.ALL ? { all: !!field, field: meta.primaryKeys[0] } : field;
@@ -68,7 +67,7 @@ export class EntityLoader {
68
67
  // convert nested `field` with dot syntax to PopulateOptions with `children` array
69
68
  expandDotPaths(meta, normalized, true);
70
69
  if (lookup && populate !== false) {
71
- normalized = this.lookupEagerLoadedRelationships(entityName, normalized, strategy);
70
+ normalized = this.lookupEagerLoadedRelationships(entityName, normalized, strategy, '', [], exclude);
72
71
  // convert nested `field` with dot syntax produced by eager relations
73
72
  expandDotPaths(meta, normalized, true);
74
73
  }
@@ -142,15 +141,20 @@ export class EntityLoader {
142
141
  .flatMap(orderBy => orderBy[prop.name]);
143
142
  const where = await this.extractChildCondition(options, prop);
144
143
  if (prop.kind === ReferenceKind.MANY_TO_MANY && this.driver.getPlatform().usesPivotTable()) {
145
- const res = await this.findChildrenFromPivotTable(filtered, prop, options, innerOrderBy, populate, !!ref);
144
+ const pivotOrderBy = QueryHelper.mergeOrderBy(innerOrderBy, prop.orderBy, prop.targetMeta?.orderBy);
145
+ const res = await this.findChildrenFromPivotTable(filtered, prop, options, pivotOrderBy, populate, !!ref);
146
146
  return Utils.flatten(res);
147
147
  }
148
+ if (prop.polymorphic && prop.polymorphTargets) {
149
+ return this.populatePolymorphic(entities, prop, options, !!ref);
150
+ }
148
151
  const { items, partial } = await this.findChildren(options.filtered ?? entities, prop, populate, {
149
152
  ...options,
150
153
  where,
151
154
  orderBy: innerOrderBy,
152
155
  }, !!(ref || prop.mapToPk));
153
- this.initializeCollections(filtered, prop, field, items, innerOrderBy.length > 0, partial);
156
+ const customOrder = innerOrderBy.length > 0 || !!prop.orderBy || !!prop.targetMeta?.orderBy;
157
+ this.initializeCollections(filtered, prop, field, items, customOrder, partial);
154
158
  return items;
155
159
  }
156
160
  async populateScalar(meta, filtered, options) {
@@ -158,12 +162,70 @@ export class EntityLoader {
158
162
  const ids = Utils.unique(filtered.map(e => Utils.getPrimaryKeyValues(e, meta, true)));
159
163
  const where = this.mergePrimaryCondition(ids, pk, options, meta, this.metadata, this.driver.getPlatform());
160
164
  const { filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, fields } = options;
161
- await this.em.find(meta.className, where, {
165
+ await this.em.find(meta.class, where, {
162
166
  filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging,
163
167
  fields: fields,
164
168
  populate: [],
165
169
  });
166
170
  }
171
+ async populatePolymorphic(entities, prop, options, ref) {
172
+ const ownerMeta = this.metadata.get(entities[0].constructor);
173
+ // Separate entities: those with loaded refs vs those needing FK load
174
+ const toPopulate = [];
175
+ const needsFkLoad = [];
176
+ for (const entity of entities) {
177
+ const refValue = entity[prop.name];
178
+ if (refValue && helper(refValue).hasPrimaryKey()) {
179
+ if ((ref && !options.refresh) || // :ref hint - already have reference
180
+ (!ref && helper(refValue).__initialized && !options.refresh) // already loaded
181
+ ) {
182
+ continue;
183
+ }
184
+ toPopulate.push(entity);
185
+ }
186
+ else if (refValue == null && !helper(entity).__loadedProperties.has(prop.name)) {
187
+ // FK columns weren't loaded (partial loading) — need to re-fetch them.
188
+ // If the property IS in __loadedProperties, the FK was loaded and is genuinely null.
189
+ needsFkLoad.push(entity);
190
+ }
191
+ }
192
+ // Load FK columns using populateScalar pattern
193
+ if (needsFkLoad.length > 0) {
194
+ await this.populateScalar(ownerMeta, needsFkLoad, {
195
+ ...options,
196
+ fields: [...ownerMeta.primaryKeys, prop.name],
197
+ });
198
+ // After loading FKs, add to toPopulate if not using :ref hint
199
+ if (!ref) {
200
+ for (const entity of needsFkLoad) {
201
+ const refValue = entity[prop.name];
202
+ if (refValue && helper(refValue).hasPrimaryKey()) {
203
+ toPopulate.push(entity);
204
+ }
205
+ }
206
+ }
207
+ }
208
+ if (toPopulate.length === 0) {
209
+ return [];
210
+ }
211
+ // Group references by target class for batch loading
212
+ const groups = new Map();
213
+ for (const entity of toPopulate) {
214
+ const refValue = Reference.unwrapReference(entity[prop.name]);
215
+ const discriminator = QueryHelper.findDiscriminatorValue(prop.discriminatorMap, helper(refValue).__meta.class);
216
+ const group = groups.get(discriminator) ?? [];
217
+ group.push(refValue);
218
+ groups.set(discriminator, group);
219
+ }
220
+ // Load each group concurrently - identity map handles merging with existing references
221
+ const allItems = [];
222
+ await Promise.all([...groups].map(async ([discriminator, children]) => {
223
+ const targetMeta = this.metadata.find(prop.discriminatorMap[discriminator]);
224
+ await this.populateScalar(targetMeta, children, options);
225
+ allItems.push(...children);
226
+ }));
227
+ return allItems;
228
+ }
167
229
  initializeCollections(filtered, prop, field, children, customOrder, partial) {
168
230
  if (prop.kind === ReferenceKind.ONE_TO_MANY) {
169
231
  this.initializeOneToMany(filtered, children, prop, field, partial);
@@ -182,7 +244,7 @@ export class EntityLoader {
182
244
  for (const child of children) {
183
245
  const pk = child.__helper.__data[prop.mappedBy] ?? child[prop.mappedBy];
184
246
  if (pk) {
185
- const key = helper(mapToPk ? this.em.getReference(prop.type, pk) : pk).getSerializedPrimaryKey();
247
+ const key = helper(mapToPk ? this.em.getReference(prop.targetMeta.class, pk) : pk).getSerializedPrimaryKey();
186
248
  map[key]?.push(child);
187
249
  }
188
250
  }
@@ -210,13 +272,23 @@ export class EntityLoader {
210
272
  }
211
273
  }
212
274
  async findChildren(entities, prop, populate, options, ref) {
213
- const children = this.getChildReferences(entities, prop, options, ref);
275
+ const children = Utils.unique(this.getChildReferences(entities, prop, options, ref));
214
276
  const meta = prop.targetMeta;
215
- let fk = Utils.getPrimaryKeyHash(meta.primaryKeys);
277
+ // When targetKey is set, use it for FK lookup instead of the PK
278
+ let fk = prop.targetKey ?? Utils.getPrimaryKeyHash(meta.primaryKeys);
216
279
  let schema = options.schema;
217
280
  const partial = !Utils.isEmpty(prop.where) || !Utils.isEmpty(options.where);
281
+ let polymorphicOwnerProp;
218
282
  if (prop.kind === ReferenceKind.ONE_TO_MANY || (prop.kind === ReferenceKind.MANY_TO_MANY && !prop.owner)) {
219
- fk = meta.properties[prop.mappedBy].name;
283
+ const ownerProp = meta.properties[prop.mappedBy];
284
+ if (ownerProp.polymorphic && ownerProp.fieldNames.length >= 2) {
285
+ const idColumns = ownerProp.fieldNames.slice(1);
286
+ fk = idColumns.length === 1 ? idColumns[0] : idColumns;
287
+ polymorphicOwnerProp = ownerProp;
288
+ }
289
+ else {
290
+ fk = ownerProp.name;
291
+ }
220
292
  }
221
293
  if (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && !ref) {
222
294
  children.length = 0;
@@ -229,8 +301,24 @@ export class EntityLoader {
229
301
  if (!schema && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
230
302
  schema = children.find(e => e.__helper.__schema)?.__helper.__schema;
231
303
  }
232
- const ids = Utils.unique(children.map(e => e.__helper.getPrimaryKey()));
233
- let where = this.mergePrimaryCondition(ids, fk, options, meta, this.metadata, this.driver.getPlatform());
304
+ const ids = Utils.unique(children.map(e => prop.targetKey ? e[prop.targetKey] : e.__helper.getPrimaryKey()));
305
+ let where;
306
+ if (polymorphicOwnerProp && Array.isArray(fk)) {
307
+ const conditions = ids.map(id => {
308
+ const pkValues = Object.values(id);
309
+ return Object.fromEntries(fk.map((col, idx) => [col, pkValues[idx]]));
310
+ });
311
+ where = (conditions.length === 1 ? conditions[0] : { $or: conditions });
312
+ }
313
+ else {
314
+ where = this.mergePrimaryCondition(ids, fk, options, meta, this.metadata, this.driver.getPlatform());
315
+ }
316
+ if (polymorphicOwnerProp) {
317
+ const parentMeta = this.metadata.find(entities[0].constructor);
318
+ const discriminatorValue = QueryHelper.findDiscriminatorValue(polymorphicOwnerProp.discriminatorMap, parentMeta.class) ?? parentMeta.tableName;
319
+ const discriminatorColumn = polymorphicOwnerProp.fieldNames[0];
320
+ where = { $and: [where, { [discriminatorColumn]: discriminatorValue }] };
321
+ }
234
322
  const fields = this.buildFields(options.fields, prop, ref);
235
323
  /* eslint-disable prefer-const */
236
324
  let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere = 'infer', connectionType, logging, } = options;
@@ -238,28 +326,11 @@ export class EntityLoader {
238
326
  if (typeof populateWhere === 'object') {
239
327
  populateWhere = await this.extractChildCondition({ where: populateWhere }, prop);
240
328
  }
241
- if (!Utils.isEmpty(prop.where)) {
329
+ if (!Utils.isEmpty(prop.where) || Raw.hasObjectFragments(prop.where)) {
242
330
  where = { $and: [where, prop.where] };
243
331
  }
244
- const propOrderBy = [];
245
- if (prop.orderBy) {
246
- for (const item of Utils.asArray(prop.orderBy)) {
247
- for (const field of Utils.keys(item)) {
248
- const rawField = RawQueryFragment.getKnownFragment(field, false);
249
- if (rawField) {
250
- const raw2 = raw(rawField.sql, rawField.params);
251
- propOrderBy.push({ [raw2.toString()]: item[field] });
252
- continue;
253
- }
254
- propOrderBy.push({ [field]: item[field] });
255
- }
256
- }
257
- }
258
- const orderBy = [...Utils.asArray(options.orderBy), ...propOrderBy].filter((order, idx, array) => {
259
- // skip consecutive ordering with the same key to get around mongo issues
260
- return idx === 0 || !Utils.equals(Object.keys(array[idx - 1]), Object.keys(order));
261
- });
262
- const items = await this.em.find(prop.type, where, {
332
+ const orderBy = QueryHelper.mergeOrderBy(options.orderBy, prop.orderBy);
333
+ const items = await this.em.find(meta.class, where, {
263
334
  filters, convertCustomTypes, lockMode, populateWhere, logging,
264
335
  orderBy,
265
336
  populate: populate.children ?? populate.all ?? [],
@@ -270,6 +341,49 @@ export class EntityLoader {
270
341
  // @ts-ignore not a public option, will be propagated to the populate call
271
342
  visited: options.visited,
272
343
  });
344
+ // For targetKey relations, wire up loaded entities to parent references
345
+ // This is needed because the references were created under alternate key,
346
+ // but loaded entities are stored under PK, so they don't automatically merge
347
+ if (prop.targetKey && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
348
+ const itemsByKey = new Map();
349
+ for (const item of items) {
350
+ itemsByKey.set('' + item[prop.targetKey], item);
351
+ }
352
+ for (const entity of entities) {
353
+ const ref = entity[prop.name];
354
+ /* v8 ignore next */
355
+ if (!ref) {
356
+ continue;
357
+ }
358
+ const keyValue = '' + (Reference.isReference(ref) ? ref.unwrap()[prop.targetKey] : ref[prop.targetKey]);
359
+ const loadedItem = itemsByKey.get(keyValue);
360
+ if (loadedItem) {
361
+ entity[prop.name] = (Reference.isReference(ref) ? Reference.create(loadedItem) : loadedItem);
362
+ }
363
+ }
364
+ }
365
+ if ([ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && items.length !== children.length) {
366
+ const nullVal = this.em.config.get('forceUndefined') ? undefined : null;
367
+ const itemsMap = new Set();
368
+ const childrenMap = new Set();
369
+ // Use targetKey value if set, otherwise use serialized PK
370
+ const getKey = (e) => prop.targetKey ? '' + e[prop.targetKey] : helper(e).getSerializedPrimaryKey();
371
+ for (const item of items) {
372
+ /* v8 ignore next */
373
+ itemsMap.add(getKey(item));
374
+ }
375
+ for (const child of children) {
376
+ childrenMap.add(getKey(child));
377
+ }
378
+ for (const entity of entities) {
379
+ const ref = entity[prop.name] ?? {};
380
+ const key = helper(ref) ? getKey(ref) : undefined;
381
+ if (key && childrenMap.has(key) && !itemsMap.has(key)) {
382
+ entity[prop.name] = nullVal;
383
+ helper(entity).__originalEntityData[prop.name] = null;
384
+ }
385
+ }
386
+ }
273
387
  for (const item of items) {
274
388
  if (ref && !helper(item).__onLoadFired) {
275
389
  helper(item).__initialized = false;
@@ -280,7 +394,7 @@ export class EntityLoader {
280
394
  return { items, partial };
281
395
  }
282
396
  mergePrimaryCondition(ids, pk, options, meta, metadata, platform) {
283
- const cond1 = QueryHelper.processWhere({ where: { [pk]: { $in: ids } }, entityName: meta.className, metadata, platform, convertCustomTypes: !options.convertCustomTypes });
397
+ const cond1 = QueryHelper.processWhere({ where: { [pk]: { $in: ids } }, entityName: meta.class, metadata, platform, convertCustomTypes: !options.convertCustomTypes });
284
398
  const where = { ...options.where };
285
399
  Utils.dropUndefinedProperties(where);
286
400
  return where[pk]
@@ -293,6 +407,7 @@ export class EntityLoader {
293
407
  if (prop.kind === ReferenceKind.SCALAR && !prop.lazy) {
294
408
  return;
295
409
  }
410
+ options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
296
411
  const populated = await this.populateMany(entityName, entities, populate, options);
297
412
  if (!populate.children && !populate.all) {
298
413
  return;
@@ -331,26 +446,42 @@ export class EntityLoader {
331
446
  for (const entity of entities) {
332
447
  visited.add(entity);
333
448
  }
334
- await this.populate(prop.type, unique, populate.children ?? populate.all, {
335
- where: await this.extractChildCondition(options, prop, false),
336
- orderBy: innerOrderBy,
337
- fields,
338
- exclude,
339
- validate: false,
340
- lookup: false,
341
- filters,
342
- ignoreLazyScalarProperties,
343
- populateWhere,
344
- connectionType,
345
- logging,
346
- schema,
347
- // @ts-ignore not a public option, will be propagated to the populate call
348
- refresh: refresh && !filtered.every(item => options.visited.has(item)),
349
- // @ts-ignore not a public option, will be propagated to the populate call
350
- visited: options.visited,
351
- // @ts-ignore not a public option
352
- filtered,
353
- });
449
+ if (!prop.targetMeta) {
450
+ return;
451
+ }
452
+ const populateChildren = async (targetMeta, items) => {
453
+ await this.populate(targetMeta.class, items, populate.children ?? populate.all, {
454
+ where: await this.extractChildCondition(options, prop, false),
455
+ orderBy: innerOrderBy,
456
+ fields,
457
+ exclude,
458
+ validate: false,
459
+ lookup: false,
460
+ filters,
461
+ ignoreLazyScalarProperties,
462
+ populateWhere,
463
+ connectionType,
464
+ logging,
465
+ schema,
466
+ // @ts-ignore not a public option, will be propagated to the populate call
467
+ refresh: refresh && !filtered.every(item => options.visited.has(item)),
468
+ // @ts-ignore not a public option, will be propagated to the populate call
469
+ visited: options.visited,
470
+ // @ts-ignore not a public option
471
+ filtered,
472
+ });
473
+ };
474
+ if (prop.polymorphic && prop.polymorphTargets) {
475
+ await Promise.all(prop.polymorphTargets.map(async (targetMeta) => {
476
+ const targetChildren = unique.filter(child => helper(child).__meta.className === targetMeta.className);
477
+ if (targetChildren.length > 0) {
478
+ await populateChildren(targetMeta, targetChildren);
479
+ }
480
+ }));
481
+ }
482
+ else {
483
+ await populateChildren(prop.targetMeta, unique);
484
+ }
354
485
  }
355
486
  /** @internal */
356
487
  async findChildrenFromPivotTable(filtered, prop, options, orderBy, populate, pivotJoin) {
@@ -359,11 +490,9 @@ export class EntityLoader {
359
490
  let where = await this.extractChildCondition(options, prop, true);
360
491
  const fields = this.buildFields(options.fields, prop);
361
492
  const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
362
- const options2 = { ...options };
363
- delete options2.limit;
364
- delete options2.offset;
365
- options2.fields = fields;
366
- options2.exclude = exclude;
493
+ const populateFilter = options.populateFilter?.[prop.name];
494
+ const options2 = { ...options, fields, exclude, populateFilter };
495
+ ['limit', 'offset', 'first', 'last', 'before', 'after', 'overfetch'].forEach(prop => delete options2[prop]);
367
496
  options2.populate = (populate?.children ?? []);
368
497
  if (prop.customType) {
369
498
  ids.forEach((id, idx) => ids[idx] = QueryHelper.processCustomType(prop, id, this.driver.getPlatform()));
@@ -376,12 +505,12 @@ export class EntityLoader {
376
505
  for (const entity of filtered) {
377
506
  const items = map[entity.__helper.getSerializedPrimaryKey()].map(item => {
378
507
  if (pivotJoin) {
379
- return this.em.getReference(prop.type, item, {
508
+ return this.em.getReference(prop.targetMeta.class, item, {
380
509
  convertCustomTypes: true,
381
510
  schema: options.schema ?? this.em.config.get('schema'),
382
511
  });
383
512
  }
384
- const entity = this.em.getEntityFactory().create(prop.type, item, {
513
+ const entity = this.em.getEntityFactory().create(prop.targetMeta.class, item, {
385
514
  refresh,
386
515
  merge: true,
387
516
  convertCustomTypes: true,
@@ -397,16 +526,13 @@ export class EntityLoader {
397
526
  async extractChildCondition(options, prop, filters = false) {
398
527
  const where = options.where;
399
528
  const subCond = Utils.isPlainObject(where[prop.name]) ? where[prop.name] : {};
400
- const meta2 = this.metadata.find(prop.type);
401
- if (!meta2) {
402
- return {};
403
- }
529
+ const meta2 = prop.targetMeta;
404
530
  const pk = Utils.getPrimaryKeyHash(meta2.primaryKeys);
405
531
  ['$and', '$or'].forEach(op => {
406
532
  if (where[op]) {
407
533
  const child = where[op]
408
534
  .map((cond) => cond[prop.name])
409
- .filter((sub) => sub != null && !(Utils.isPlainObject(sub) && Object.keys(sub).every(key => Utils.isOperator(key, false))))
535
+ .filter((sub) => sub != null && !(Utils.isPlainObject(sub) && Utils.getObjectQueryKeys(sub).every(key => Utils.isOperator(key, false))))
410
536
  .map((cond) => {
411
537
  if (Utils.isPrimaryKey(cond)) {
412
538
  return { [pk]: cond };
@@ -427,7 +553,7 @@ export class EntityLoader {
427
553
  });
428
554
  }
429
555
  if (filters) {
430
- return this.em.applyFilters(prop.type, subCond, options.filters, 'read', options);
556
+ return this.em.applyFilters(meta2.class, subCond, options.filters, 'read', options);
431
557
  }
432
558
  return subCond;
433
559
  }
@@ -445,44 +571,43 @@ export class EntityLoader {
445
571
  const parts = f.toString().split('.');
446
572
  const propName = parts.shift();
447
573
  const childPropName = parts.join('.');
448
- /* v8 ignore next 3 */
574
+ /* v8 ignore next */
449
575
  if (propName === prop.name) {
450
576
  ret.push(childPropName);
451
577
  }
452
578
  }
453
579
  return ret;
454
580
  }, []);
455
- if (ret.length === 0) {
456
- return undefined;
457
- }
458
581
  // we need to automatically select the FKs too, e.g. for 1:m relations to be able to wire them with the items
459
582
  if (prop.kind === ReferenceKind.ONE_TO_MANY || prop.kind === ReferenceKind.MANY_TO_MANY) {
460
583
  const owner = prop.targetMeta.properties[prop.mappedBy];
461
- if (owner && !ret.includes(owner.name)) {
584
+ // when the owning FK is lazy, we need to explicitly select it even without user-provided fields,
585
+ // otherwise the driver will exclude it and we won't be able to map children to their parent collections
586
+ if (owner && !ret.includes(owner.name) && (ret.length > 0 || owner.lazy)) {
462
587
  ret.push(owner.name);
463
588
  }
464
589
  }
590
+ if (ret.length === 0) {
591
+ return undefined;
592
+ }
465
593
  return ret;
466
594
  }
467
595
  getChildReferences(entities, prop, options, ref) {
468
596
  const filtered = this.filterCollections(entities, prop.name, options, ref);
469
- const children = [];
470
597
  if (prop.kind === ReferenceKind.ONE_TO_MANY) {
471
- children.push(...filtered.map(e => e[prop.name].owner));
598
+ return filtered.map(e => e[prop.name].owner);
472
599
  }
473
- else if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
474
- children.push(...filtered.reduce((a, b) => {
600
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
601
+ return filtered.reduce((a, b) => {
475
602
  a.push(...b[prop.name].getItems());
476
603
  return a;
477
- }, []));
478
- }
479
- else if (prop.kind === ReferenceKind.MANY_TO_MANY) { // inverse side
480
- children.push(...filtered);
604
+ }, []);
481
605
  }
482
- else { // MANY_TO_ONE or ONE_TO_ONE
483
- children.push(...this.filterReferences(entities, prop.name, options, ref));
606
+ if (prop.kind === ReferenceKind.MANY_TO_MANY) { // inverse side
607
+ return filtered;
484
608
  }
485
- return children;
609
+ // MANY_TO_ONE or ONE_TO_ONE
610
+ return this.filterReferences(entities, prop.name, options, ref);
486
611
  }
487
612
  filterCollections(entities, field, options, ref) {
488
613
  if (options.refresh) {
@@ -499,7 +624,7 @@ export class EntityLoader {
499
624
  return wrapped.__loadedProperties.has(field);
500
625
  }
501
626
  const [f, ...r] = field.split('.');
502
- /* v8 ignore next 3 */
627
+ /* v8 ignore next */
503
628
  if (!wrapped.__loadedProperties.has(f) || !wrapped.__meta.properties[f]?.targetMeta) {
504
629
  return false;
505
630
  }
@@ -532,7 +657,7 @@ export class EntityLoader {
532
657
  .map(e => Reference.unwrapReference(e[field]));
533
658
  }
534
659
  filterByReferences(entities, field, refresh) {
535
- /* v8 ignore next 3 */
660
+ /* v8 ignore next */
536
661
  if (refresh) {
537
662
  return entities;
538
663
  }
@@ -558,33 +683,36 @@ export class EntityLoader {
558
683
  }
559
684
  return `${this.getRelationName(meta, meta.properties[prop.embedded[0]])}.${prop.embedded[1]}`;
560
685
  }
561
- lookupEagerLoadedRelationships(entityName, populate, strategy, prefix = '', visited = []) {
686
+ lookupEagerLoadedRelationships(entityName, populate, strategy, prefix = '', visited = [], exclude) {
562
687
  const meta = this.metadata.find(entityName);
563
688
  if (!meta && !prefix) {
564
689
  return populate;
565
690
  }
566
- if (visited.includes(entityName) || !meta) {
691
+ if (!meta || visited.includes(meta)) {
567
692
  return [];
568
693
  }
569
- visited.push(entityName);
694
+ visited.push(meta);
570
695
  const ret = prefix === '' ? [...populate] : [];
571
696
  meta.relations
572
697
  .filter(prop => {
698
+ const field = this.getRelationName(meta, prop);
699
+ const prefixed = prefix ? `${prefix}.${field}` : field;
700
+ const isExcluded = exclude?.includes(prefixed);
573
701
  const eager = prop.eager && !populate.some(p => p.field === `${prop.name}:ref`);
574
702
  const populated = populate.some(p => p.field === prop.name);
575
703
  const disabled = populate.some(p => p.field === prop.name && p.all === false);
576
- return !disabled && (eager || populated);
704
+ return !disabled && !isExcluded && (eager || populated);
577
705
  })
578
706
  .forEach(prop => {
579
707
  const field = this.getRelationName(meta, prop);
580
708
  const prefixed = prefix ? `${prefix}.${field}` : field;
581
709
  const nestedPopulate = populate.filter(p => p.field === prop.name).flatMap(p => p.children).filter(Boolean);
582
- const nested = this.lookupEagerLoadedRelationships(prop.type, nestedPopulate, strategy, prefixed, visited.slice());
710
+ const nested = this.lookupEagerLoadedRelationships(prop.targetMeta.class, nestedPopulate, strategy, prefixed, visited.slice(), exclude);
583
711
  if (nested.length > 0) {
584
712
  ret.push(...nested);
585
713
  }
586
714
  else {
587
- const selfReferencing = [meta.className, meta.root.className, ...visited].includes(prop.type) && prop.eager;
715
+ const selfReferencing = [meta.tableName, ...visited.map(m => m.tableName)].includes(prop.targetMeta.tableName) && prop.eager;
588
716
  ret.push({
589
717
  field: prefixed,
590
718
  // enforce select-in strategy for self-referencing relations
@@ -2,7 +2,7 @@ import type { PopulatePath } from '../enums.js';
2
2
  import type { CreateOptions, EntityManager, MergeOptions } from '../EntityManager.js';
3
3
  import type { AssignOptions } from './EntityAssigner.js';
4
4
  import type { EntityData, EntityName, Primary, Loaded, FilterQuery, EntityDictionary, AutoPath, RequiredEntityData, Ref, EntityType, EntityDTO, MergeSelected, FromEntityType, IsSubset, MergeLoaded, ArrayElement } from '../typings.js';
5
- import type { CountOptions, DeleteOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, NativeInsertUpdateOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from '../drivers/IDatabaseDriver.js';
5
+ import type { CountOptions, DeleteOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, NativeInsertUpdateOptions, StreamOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from '../drivers/IDatabaseDriver.js';
6
6
  import type { EntityLoaderOptions } from './EntityLoader.js';
7
7
  import type { Cursor } from '../utils/Cursor.js';
8
8
  export declare class EntityRepository<Entity extends object> {
@@ -80,11 +80,15 @@ export declare class EntityRepository<Entity extends object> {
80
80
  /**
81
81
  * @inheritDoc EntityManager.findByCursor
82
82
  */
83
- findByCursor<Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(where: FilterQuery<Entity>, options: FindByCursorOptions<Entity, Hint, Fields, Excludes>): Promise<Cursor<Entity, Hint, Fields, Excludes>>;
83
+ findByCursor<Hint extends string = never, Fields extends string = '*', Excludes extends string = never, IncludeCount extends boolean = true>(options: FindByCursorOptions<Entity, Hint, Fields, Excludes, IncludeCount>): Promise<Cursor<Entity, Hint, Fields, Excludes, IncludeCount>>;
84
84
  /**
85
85
  * Finds all entities of given type. You can pass additional options via the `options` parameter.
86
86
  */
87
87
  findAll<Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(options?: FindAllOptions<Entity, Hint, Fields, Excludes>): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
88
+ /**
89
+ * @inheritDoc EntityManager.stream
90
+ */
91
+ stream<Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(options?: StreamOptions<Entity, Hint, Fields, Excludes>): AsyncIterableIterator<Loaded<Entity, Hint, Fields, Excludes>>;
88
92
  /**
89
93
  * @inheritDoc EntityManager.insert
90
94
  */
@@ -107,10 +111,26 @@ export declare class EntityRepository<Entity extends object> {
107
111
  map(result: EntityDictionary<Entity>, options?: {
108
112
  schema?: string;
109
113
  }): Entity;
114
+ /**
115
+ * Gets a reference to the entity identified by the given type and alternate key property without actually loading it.
116
+ * The key option specifies which property to use for identity map lookup instead of the primary key.
117
+ */
118
+ getReference<K extends string & keyof Entity>(id: Entity[K], options: Omit<GetReferenceOptions, 'key' | 'wrapped'> & {
119
+ key: K;
120
+ wrapped: true;
121
+ }): Ref<Entity>;
122
+ /**
123
+ * Gets a reference to the entity identified by the given type and alternate key property without actually loading it.
124
+ * The key option specifies which property to use for identity map lookup instead of the primary key.
125
+ */
126
+ getReference<K extends string & keyof Entity>(id: Entity[K], options: Omit<GetReferenceOptions, 'key'> & {
127
+ key: K;
128
+ wrapped?: false;
129
+ }): Entity;
110
130
  /**
111
131
  * Gets a reference to the entity identified by the given type and identifier without actually loading it, if the entity is not yet loaded
112
132
  */
113
- getReference(id: Primary<Entity>, options: Omit<GetReferenceOptions, 'wrapped'> & {
133
+ getReference(id: Primary<Entity>, options: Omit<GetReferenceOptions, 'wrapped' | 'key'> & {
114
134
  wrapped: true;
115
135
  }): Ref<Entity>;
116
136
  /**
@@ -120,7 +140,7 @@ export declare class EntityRepository<Entity extends object> {
120
140
  /**
121
141
  * Gets a reference to the entity identified by the given type and identifier without actually loading it, if the entity is not yet loaded
122
142
  */
123
- getReference(id: Primary<Entity>, options: Omit<GetReferenceOptions, 'wrapped'> & {
143
+ getReference(id: Primary<Entity>, options: Omit<GetReferenceOptions, 'wrapped' | 'key'> & {
124
144
  wrapped: false;
125
145
  }): Entity;
126
146
  /**
@@ -146,13 +166,13 @@ export declare class EntityRepository<Entity extends object> {
146
166
  * The newly created entity will be automatically marked for persistence via `em.persist` unless you disable this
147
167
  * behavior, either locally via `persist: false` option, or globally via `persistOnCreate` ORM config option.
148
168
  */
149
- create<Convert extends boolean = false>(data: RequiredEntityData<Entity, never, Convert>, options?: CreateOptions<Convert>): Entity;
169
+ create<Convert extends boolean = false, Data extends RequiredEntityData<Entity, never, Convert> = RequiredEntityData<Entity, never, Convert>>(data: Data & IsSubset<RequiredEntityData<Entity, never, Convert>, Data>, options?: CreateOptions<Convert>): Entity;
150
170
  /**
151
171
  * Creates new instance of given entity and populates it with given data.
152
172
  * The entity constructor will be used unless you provide `{ managed: true }` in the `options` parameter.
153
173
  * The constructor will be given parameters based on the defined constructor of the entity. If the constructor
154
174
  * parameter matches a property name, its value will be extracted from `data`. If no matching property exists,
155
- * the whole `data` parameter will be passed. This means we can also define `constructor(data: Partial<T>)` and
175
+ * the whole `data` parameter will be pass. This means we can also define `constructor(data: Partial<T>)` and
156
176
  * `em.create()` will pass the data into it (unless we have a property named `data` too).
157
177
  *
158
178
  * The parameters are strictly checked, you need to provide all required properties. You can use `OptionalProps`
@@ -162,7 +182,7 @@ export declare class EntityRepository<Entity extends object> {
162
182
  * The newly created entity will be automatically marked for persistence via `em.persist` unless you disable this
163
183
  * behavior, either locally via `persist: false` option, or globally via `persistOnCreate` ORM config option.
164
184
  */
165
- create<Convert extends boolean = false>(data: EntityData<Entity, Convert>, options: CreateOptions<Convert> & {
185
+ create<Convert extends boolean = false, Data extends EntityData<Entity, Convert> = EntityData<Entity, Convert>>(data: Data & IsSubset<EntityData<Entity, Convert>, Data>, options: CreateOptions<Convert> & {
166
186
  partial: true;
167
187
  }): Entity;
168
188
  /**
@@ -90,8 +90,8 @@ export class EntityRepository {
90
90
  /**
91
91
  * @inheritDoc EntityManager.findByCursor
92
92
  */
93
- async findByCursor(where, options) {
94
- return this.getEntityManager().findByCursor(this.entityName, where, options);
93
+ async findByCursor(options) {
94
+ return this.getEntityManager().findByCursor(this.entityName, options);
95
95
  }
96
96
  /**
97
97
  * Finds all entities of given type. You can pass additional options via the `options` parameter.
@@ -99,6 +99,12 @@ export class EntityRepository {
99
99
  async findAll(options) {
100
100
  return this.getEntityManager().findAll(this.entityName, options);
101
101
  }
102
+ /**
103
+ * @inheritDoc EntityManager.stream
104
+ */
105
+ async *stream(options) {
106
+ yield* this.getEntityManager().stream(this.entityName, options);
107
+ }
102
108
  /**
103
109
  * @inheritDoc EntityManager.insert
104
110
  */