@mikro-orm/core 7.0.0-dev.99 → 7.0.0-rc.1

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 (107) hide show
  1. package/EntityManager.d.ts +34 -17
  2. package/EntityManager.js +95 -103
  3. package/MikroORM.d.ts +5 -5
  4. package/MikroORM.js +25 -20
  5. package/cache/FileCacheAdapter.js +11 -3
  6. package/connections/Connection.d.ts +3 -2
  7. package/connections/Connection.js +4 -3
  8. package/drivers/DatabaseDriver.d.ts +11 -11
  9. package/drivers/DatabaseDriver.js +91 -25
  10. package/drivers/IDatabaseDriver.d.ts +50 -20
  11. package/entity/BaseEntity.d.ts +61 -1
  12. package/entity/Collection.d.ts +8 -1
  13. package/entity/Collection.js +12 -13
  14. package/entity/EntityAssigner.js +9 -9
  15. package/entity/EntityFactory.d.ts +6 -1
  16. package/entity/EntityFactory.js +40 -22
  17. package/entity/EntityHelper.d.ts +2 -2
  18. package/entity/EntityHelper.js +27 -4
  19. package/entity/EntityLoader.d.ts +5 -4
  20. package/entity/EntityLoader.js +193 -80
  21. package/entity/EntityRepository.d.ts +27 -7
  22. package/entity/EntityRepository.js +8 -2
  23. package/entity/PolymorphicRef.d.ts +12 -0
  24. package/entity/PolymorphicRef.js +18 -0
  25. package/entity/WrappedEntity.d.ts +2 -2
  26. package/entity/WrappedEntity.js +1 -1
  27. package/entity/defineEntity.d.ts +89 -50
  28. package/entity/defineEntity.js +12 -0
  29. package/entity/index.d.ts +1 -0
  30. package/entity/index.js +1 -0
  31. package/entity/utils.d.ts +6 -1
  32. package/entity/utils.js +33 -0
  33. package/entity/validators.js +2 -2
  34. package/enums.d.ts +2 -2
  35. package/enums.js +1 -0
  36. package/errors.d.ts +16 -8
  37. package/errors.js +40 -13
  38. package/hydration/ObjectHydrator.js +63 -21
  39. package/index.d.ts +1 -1
  40. package/logging/colors.d.ts +1 -1
  41. package/logging/colors.js +7 -6
  42. package/logging/inspect.js +1 -6
  43. package/metadata/EntitySchema.d.ts +43 -13
  44. package/metadata/EntitySchema.js +82 -27
  45. package/metadata/MetadataDiscovery.d.ts +60 -3
  46. package/metadata/MetadataDiscovery.js +665 -154
  47. package/metadata/MetadataProvider.js +3 -1
  48. package/metadata/MetadataStorage.d.ts +13 -6
  49. package/metadata/MetadataStorage.js +64 -19
  50. package/metadata/MetadataValidator.d.ts +32 -2
  51. package/metadata/MetadataValidator.js +196 -31
  52. package/metadata/discover-entities.js +5 -5
  53. package/metadata/types.d.ts +111 -14
  54. package/naming-strategy/AbstractNamingStrategy.d.ts +11 -3
  55. package/naming-strategy/AbstractNamingStrategy.js +12 -0
  56. package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
  57. package/naming-strategy/EntityCaseNamingStrategy.js +6 -5
  58. package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
  59. package/naming-strategy/MongoNamingStrategy.js +6 -6
  60. package/naming-strategy/NamingStrategy.d.ts +17 -3
  61. package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
  62. package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
  63. package/package.json +2 -2
  64. package/platforms/Platform.d.ts +4 -2
  65. package/platforms/Platform.js +5 -2
  66. package/serialization/EntitySerializer.d.ts +3 -0
  67. package/serialization/EntitySerializer.js +15 -13
  68. package/serialization/EntityTransformer.js +6 -6
  69. package/serialization/SerializationContext.d.ts +6 -6
  70. package/typings.d.ts +325 -110
  71. package/typings.js +84 -17
  72. package/unit-of-work/ChangeSet.d.ts +4 -3
  73. package/unit-of-work/ChangeSet.js +2 -3
  74. package/unit-of-work/ChangeSetComputer.d.ts +3 -6
  75. package/unit-of-work/ChangeSetComputer.js +34 -13
  76. package/unit-of-work/ChangeSetPersister.d.ts +12 -10
  77. package/unit-of-work/ChangeSetPersister.js +55 -25
  78. package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
  79. package/unit-of-work/CommitOrderCalculator.js +13 -13
  80. package/unit-of-work/IdentityMap.d.ts +12 -0
  81. package/unit-of-work/IdentityMap.js +39 -1
  82. package/unit-of-work/UnitOfWork.d.ts +21 -3
  83. package/unit-of-work/UnitOfWork.js +203 -56
  84. package/utils/AbstractSchemaGenerator.js +17 -8
  85. package/utils/AsyncContext.d.ts +6 -0
  86. package/utils/AsyncContext.js +42 -0
  87. package/utils/Configuration.d.ts +52 -11
  88. package/utils/Configuration.js +12 -8
  89. package/utils/Cursor.js +21 -8
  90. package/utils/DataloaderUtils.js +13 -11
  91. package/utils/EntityComparator.d.ts +14 -7
  92. package/utils/EntityComparator.js +132 -46
  93. package/utils/QueryHelper.d.ts +16 -6
  94. package/utils/QueryHelper.js +53 -18
  95. package/utils/RawQueryFragment.d.ts +28 -23
  96. package/utils/RawQueryFragment.js +34 -56
  97. package/utils/RequestContext.js +2 -2
  98. package/utils/TransactionContext.js +2 -2
  99. package/utils/TransactionManager.js +1 -1
  100. package/utils/Utils.d.ts +7 -26
  101. package/utils/Utils.js +25 -79
  102. package/utils/clone.js +7 -21
  103. package/utils/env-vars.d.ts +4 -0
  104. package/utils/env-vars.js +13 -3
  105. package/utils/fs-utils.d.ts +21 -0
  106. package/utils/fs-utils.js +106 -11
  107. package/utils/upsert-utils.d.ts +4 -4
@@ -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;
@@ -39,7 +39,7 @@ export class EntityLoader {
39
39
  if (references.length > 0) {
40
40
  await this.populateScalar(meta, references, { ...options, populateWhere: undefined });
41
41
  }
42
- populate = this.normalizePopulate(entityName, populate, options.strategy, options.lookup);
42
+ populate = this.normalizePopulate(entityName, populate, options.strategy, options.lookup, options.exclude);
43
43
  const invalid = populate.find(({ field }) => !this.em.canPopulate(entityName, field));
44
44
  /* v8 ignore next */
45
45
  if (options.validate && invalid) {
@@ -56,7 +56,7 @@ export class EntityLoader {
56
56
  visited.delete(entity);
57
57
  }
58
58
  }
59
- normalizePopulate(entityName, populate, strategy, lookup = true) {
59
+ normalizePopulate(entityName, populate, strategy, lookup = true, exclude) {
60
60
  const meta = this.metadata.find(entityName);
61
61
  let normalized = Utils.asArray(populate).map(field => {
62
62
  return typeof field === 'boolean' || field.field === PopulatePath.ALL ? { all: !!field, field: meta.primaryKeys[0] } : field;
@@ -67,7 +67,7 @@ export class EntityLoader {
67
67
  // convert nested `field` with dot syntax to PopulateOptions with `children` array
68
68
  expandDotPaths(meta, normalized, true);
69
69
  if (lookup && populate !== false) {
70
- normalized = this.lookupEagerLoadedRelationships(entityName, normalized, strategy);
70
+ normalized = this.lookupEagerLoadedRelationships(entityName, normalized, strategy, '', [], exclude);
71
71
  // convert nested `field` with dot syntax produced by eager relations
72
72
  expandDotPaths(meta, normalized, true);
73
73
  }
@@ -141,15 +141,20 @@ export class EntityLoader {
141
141
  .flatMap(orderBy => orderBy[prop.name]);
142
142
  const where = await this.extractChildCondition(options, prop);
143
143
  if (prop.kind === ReferenceKind.MANY_TO_MANY && this.driver.getPlatform().usesPivotTable()) {
144
- 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);
145
146
  return Utils.flatten(res);
146
147
  }
148
+ if (prop.polymorphic && prop.polymorphTargets) {
149
+ return this.populatePolymorphic(entities, prop, options, !!ref);
150
+ }
147
151
  const { items, partial } = await this.findChildren(options.filtered ?? entities, prop, populate, {
148
152
  ...options,
149
153
  where,
150
154
  orderBy: innerOrderBy,
151
155
  }, !!(ref || prop.mapToPk));
152
- 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);
153
158
  return items;
154
159
  }
155
160
  async populateScalar(meta, filtered, options) {
@@ -157,12 +162,70 @@ export class EntityLoader {
157
162
  const ids = Utils.unique(filtered.map(e => Utils.getPrimaryKeyValues(e, meta, true)));
158
163
  const where = this.mergePrimaryCondition(ids, pk, options, meta, this.metadata, this.driver.getPlatform());
159
164
  const { filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, fields } = options;
160
- await this.em.find(meta.className, where, {
165
+ await this.em.find(meta.class, where, {
161
166
  filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging,
162
167
  fields: fields,
163
168
  populate: [],
164
169
  });
165
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
+ }
166
229
  initializeCollections(filtered, prop, field, children, customOrder, partial) {
167
230
  if (prop.kind === ReferenceKind.ONE_TO_MANY) {
168
231
  this.initializeOneToMany(filtered, children, prop, field, partial);
@@ -181,7 +244,7 @@ export class EntityLoader {
181
244
  for (const child of children) {
182
245
  const pk = child.__helper.__data[prop.mappedBy] ?? child[prop.mappedBy];
183
246
  if (pk) {
184
- 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();
185
248
  map[key]?.push(child);
186
249
  }
187
250
  }
@@ -211,11 +274,21 @@ export class EntityLoader {
211
274
  async findChildren(entities, prop, populate, options, ref) {
212
275
  const children = Utils.unique(this.getChildReferences(entities, prop, options, ref));
213
276
  const meta = prop.targetMeta;
214
- 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);
215
279
  let schema = options.schema;
216
280
  const partial = !Utils.isEmpty(prop.where) || !Utils.isEmpty(options.where);
281
+ let polymorphicOwnerProp;
217
282
  if (prop.kind === ReferenceKind.ONE_TO_MANY || (prop.kind === ReferenceKind.MANY_TO_MANY && !prop.owner)) {
218
- 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
+ }
219
292
  }
220
293
  if (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && !ref) {
221
294
  children.length = 0;
@@ -228,8 +301,24 @@ export class EntityLoader {
228
301
  if (!schema && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
229
302
  schema = children.find(e => e.__helper.__schema)?.__helper.__schema;
230
303
  }
231
- const ids = Utils.unique(children.map(e => e.__helper.getPrimaryKey()));
232
- 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
+ }
233
322
  const fields = this.buildFields(options.fields, prop, ref);
234
323
  /* eslint-disable prefer-const */
235
324
  let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere = 'infer', connectionType, logging, } = options;
@@ -237,28 +326,11 @@ export class EntityLoader {
237
326
  if (typeof populateWhere === 'object') {
238
327
  populateWhere = await this.extractChildCondition({ where: populateWhere }, prop);
239
328
  }
240
- if (!Utils.isEmpty(prop.where)) {
329
+ if (!Utils.isEmpty(prop.where) || Raw.hasObjectFragments(prop.where)) {
241
330
  where = { $and: [where, prop.where] };
242
331
  }
243
- const propOrderBy = [];
244
- if (prop.orderBy) {
245
- for (const item of Utils.asArray(prop.orderBy)) {
246
- for (const field of Utils.keys(item)) {
247
- const rawField = RawQueryFragment.getKnownFragment(field, false);
248
- if (rawField) {
249
- const raw2 = raw(rawField.sql, rawField.params);
250
- propOrderBy.push({ [raw2.toString()]: item[field] });
251
- continue;
252
- }
253
- propOrderBy.push({ [field]: item[field] });
254
- }
255
- }
256
- }
257
- const orderBy = [...Utils.asArray(options.orderBy), ...propOrderBy].filter((order, idx, array) => {
258
- // skip consecutive ordering with the same key to get around mongo issues
259
- return idx === 0 || !Utils.equals(Object.keys(array[idx - 1]), Object.keys(order));
260
- });
261
- 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, {
262
334
  filters, convertCustomTypes, lockMode, populateWhere, logging,
263
335
  orderBy,
264
336
  populate: populate.children ?? populate.all ?? [],
@@ -269,19 +341,44 @@ export class EntityLoader {
269
341
  // @ts-ignore not a public option, will be propagated to the populate call
270
342
  visited: options.visited,
271
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
+ }
272
365
  if ([ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && items.length !== children.length) {
273
366
  const nullVal = this.em.config.get('forceUndefined') ? undefined : null;
274
367
  const itemsMap = new Set();
275
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();
276
371
  for (const item of items) {
277
- itemsMap.add(helper(item).getSerializedPrimaryKey());
372
+ /* v8 ignore next */
373
+ itemsMap.add(getKey(item));
278
374
  }
279
375
  for (const child of children) {
280
- childrenMap.add(helper(child).getSerializedPrimaryKey());
376
+ childrenMap.add(getKey(child));
281
377
  }
282
378
  for (const entity of entities) {
283
- const key = helper(entity[prop.name] ?? {})?.getSerializedPrimaryKey();
284
- if (childrenMap.has(key) && !itemsMap.has(key)) {
379
+ const ref = entity[prop.name] ?? {};
380
+ const key = helper(ref) ? getKey(ref) : undefined;
381
+ if (key && childrenMap.has(key) && !itemsMap.has(key)) {
285
382
  entity[prop.name] = nullVal;
286
383
  helper(entity).__originalEntityData[prop.name] = null;
287
384
  }
@@ -297,7 +394,7 @@ export class EntityLoader {
297
394
  return { items, partial };
298
395
  }
299
396
  mergePrimaryCondition(ids, pk, options, meta, metadata, platform) {
300
- 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 });
301
398
  const where = { ...options.where };
302
399
  Utils.dropUndefinedProperties(where);
303
400
  return where[pk]
@@ -349,26 +446,42 @@ export class EntityLoader {
349
446
  for (const entity of entities) {
350
447
  visited.add(entity);
351
448
  }
352
- await this.populate(prop.type, unique, populate.children ?? populate.all, {
353
- where: await this.extractChildCondition(options, prop, false),
354
- orderBy: innerOrderBy,
355
- fields,
356
- exclude,
357
- validate: false,
358
- lookup: false,
359
- filters,
360
- ignoreLazyScalarProperties,
361
- populateWhere,
362
- connectionType,
363
- logging,
364
- schema,
365
- // @ts-ignore not a public option, will be propagated to the populate call
366
- refresh: refresh && !filtered.every(item => options.visited.has(item)),
367
- // @ts-ignore not a public option, will be propagated to the populate call
368
- visited: options.visited,
369
- // @ts-ignore not a public option
370
- filtered,
371
- });
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
+ }
372
485
  }
373
486
  /** @internal */
374
487
  async findChildrenFromPivotTable(filtered, prop, options, orderBy, populate, pivotJoin) {
@@ -377,11 +490,9 @@ export class EntityLoader {
377
490
  let where = await this.extractChildCondition(options, prop, true);
378
491
  const fields = this.buildFields(options.fields, prop);
379
492
  const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
380
- const options2 = { ...options };
381
- delete options2.limit;
382
- delete options2.offset;
383
- options2.fields = fields;
384
- 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]);
385
496
  options2.populate = (populate?.children ?? []);
386
497
  if (prop.customType) {
387
498
  ids.forEach((id, idx) => ids[idx] = QueryHelper.processCustomType(prop, id, this.driver.getPlatform()));
@@ -394,12 +505,12 @@ export class EntityLoader {
394
505
  for (const entity of filtered) {
395
506
  const items = map[entity.__helper.getSerializedPrimaryKey()].map(item => {
396
507
  if (pivotJoin) {
397
- return this.em.getReference(prop.type, item, {
508
+ return this.em.getReference(prop.targetMeta.class, item, {
398
509
  convertCustomTypes: true,
399
510
  schema: options.schema ?? this.em.config.get('schema'),
400
511
  });
401
512
  }
402
- const entity = this.em.getEntityFactory().create(prop.type, item, {
513
+ const entity = this.em.getEntityFactory().create(prop.targetMeta.class, item, {
403
514
  refresh,
404
515
  merge: true,
405
516
  convertCustomTypes: true,
@@ -415,16 +526,13 @@ export class EntityLoader {
415
526
  async extractChildCondition(options, prop, filters = false) {
416
527
  const where = options.where;
417
528
  const subCond = Utils.isPlainObject(where[prop.name]) ? where[prop.name] : {};
418
- const meta2 = this.metadata.find(prop.type);
419
- if (!meta2) {
420
- return {};
421
- }
529
+ const meta2 = prop.targetMeta;
422
530
  const pk = Utils.getPrimaryKeyHash(meta2.primaryKeys);
423
531
  ['$and', '$or'].forEach(op => {
424
532
  if (where[op]) {
425
533
  const child = where[op]
426
534
  .map((cond) => cond[prop.name])
427
- .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))))
428
536
  .map((cond) => {
429
537
  if (Utils.isPrimaryKey(cond)) {
430
538
  return { [pk]: cond };
@@ -445,7 +553,7 @@ export class EntityLoader {
445
553
  });
446
554
  }
447
555
  if (filters) {
448
- return this.em.applyFilters(prop.type, subCond, options.filters, 'read', options);
556
+ return this.em.applyFilters(meta2.class, subCond, options.filters, 'read', options);
449
557
  }
450
558
  return subCond;
451
559
  }
@@ -470,16 +578,18 @@ export class EntityLoader {
470
578
  }
471
579
  return ret;
472
580
  }, []);
473
- if (ret.length === 0) {
474
- return undefined;
475
- }
476
581
  // we need to automatically select the FKs too, e.g. for 1:m relations to be able to wire them with the items
477
582
  if (prop.kind === ReferenceKind.ONE_TO_MANY || prop.kind === ReferenceKind.MANY_TO_MANY) {
478
583
  const owner = prop.targetMeta.properties[prop.mappedBy];
479
- 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)) {
480
587
  ret.push(owner.name);
481
588
  }
482
589
  }
590
+ if (ret.length === 0) {
591
+ return undefined;
592
+ }
483
593
  return ret;
484
594
  }
485
595
  getChildReferences(entities, prop, options, ref) {
@@ -573,33 +683,36 @@ export class EntityLoader {
573
683
  }
574
684
  return `${this.getRelationName(meta, meta.properties[prop.embedded[0]])}.${prop.embedded[1]}`;
575
685
  }
576
- lookupEagerLoadedRelationships(entityName, populate, strategy, prefix = '', visited = []) {
686
+ lookupEagerLoadedRelationships(entityName, populate, strategy, prefix = '', visited = [], exclude) {
577
687
  const meta = this.metadata.find(entityName);
578
688
  if (!meta && !prefix) {
579
689
  return populate;
580
690
  }
581
- if (visited.includes(entityName) || !meta) {
691
+ if (!meta || visited.includes(meta)) {
582
692
  return [];
583
693
  }
584
- visited.push(entityName);
694
+ visited.push(meta);
585
695
  const ret = prefix === '' ? [...populate] : [];
586
696
  meta.relations
587
697
  .filter(prop => {
698
+ const field = this.getRelationName(meta, prop);
699
+ const prefixed = prefix ? `${prefix}.${field}` : field;
700
+ const isExcluded = exclude?.includes(prefixed);
588
701
  const eager = prop.eager && !populate.some(p => p.field === `${prop.name}:ref`);
589
702
  const populated = populate.some(p => p.field === prop.name);
590
703
  const disabled = populate.some(p => p.field === prop.name && p.all === false);
591
- return !disabled && (eager || populated);
704
+ return !disabled && !isExcluded && (eager || populated);
592
705
  })
593
706
  .forEach(prop => {
594
707
  const field = this.getRelationName(meta, prop);
595
708
  const prefixed = prefix ? `${prefix}.${field}` : field;
596
709
  const nestedPopulate = populate.filter(p => p.field === prop.name).flatMap(p => p.children).filter(Boolean);
597
- 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);
598
711
  if (nested.length > 0) {
599
712
  ret.push(...nested);
600
713
  }
601
714
  else {
602
- 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;
603
716
  ret.push({
604
717
  field: prefixed,
605
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, IncludeCount extends boolean = true>(where: FilterQuery<Entity>, options: FindByCursorOptions<Entity, Hint, Fields, Excludes, IncludeCount>): Promise<Cursor<Entity, Hint, Fields, Excludes, IncludeCount>>;
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
  */
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Wrapper class for polymorphic relation reference data.
3
+ * Holds the discriminator value (type identifier) and the primary key value(s).
4
+ * Used internally to track polymorphic FK values before hydration.
5
+ */
6
+ export declare class PolymorphicRef {
7
+ readonly discriminator: string;
8
+ id: unknown;
9
+ constructor(discriminator: string, id: unknown);
10
+ /** Returns `[discriminator, ...idValues]` tuple suitable for column-level expansion. */
11
+ toTuple(): unknown[];
12
+ }
@@ -0,0 +1,18 @@
1
+ import { Utils } from '../utils/Utils.js';
2
+ /**
3
+ * Wrapper class for polymorphic relation reference data.
4
+ * Holds the discriminator value (type identifier) and the primary key value(s).
5
+ * Used internally to track polymorphic FK values before hydration.
6
+ */
7
+ export class PolymorphicRef {
8
+ discriminator;
9
+ id;
10
+ constructor(discriminator, id) {
11
+ this.discriminator = discriminator;
12
+ this.id = id;
13
+ }
14
+ /** Returns `[discriminator, ...idValues]` tuple suitable for column-level expansion. */
15
+ toTuple() {
16
+ return [this.discriminator, ...Utils.asArray(this.id)];
17
+ }
18
+ }
@@ -58,7 +58,7 @@ export declare class WrappedEntity<Entity extends object> {
58
58
  setPrimaryKey(id: Primary<Entity> | null): void;
59
59
  getSerializedPrimaryKey(): string;
60
60
  get __meta(): EntityMetadata<Entity>;
61
- get __platform(): import("@mikro-orm/sql").Platform;
62
- get __config(): import("@mikro-orm/sql").Configuration<import("../drivers/IDatabaseDriver.js").IDatabaseDriver<import("@mikro-orm/sql").Connection>, EntityManager<import("../drivers/IDatabaseDriver.js").IDatabaseDriver<import("@mikro-orm/sql").Connection>>>;
61
+ get __platform(): import("../index.js").Platform;
62
+ get __config(): import("../index.js").Configuration<import("../drivers/IDatabaseDriver.js").IDatabaseDriver<import("../index.js").Connection>, EntityManager<import("../drivers/IDatabaseDriver.js").IDatabaseDriver<import("../index.js").Connection>>>;
63
63
  get __primaryKeys(): Primary<Entity>[];
64
64
  }
@@ -71,7 +71,7 @@ export class WrappedEntity {
71
71
  if (!this.__em) {
72
72
  throw ValidationError.entityNotManaged(this.entity);
73
73
  }
74
- return this.__em.findOne(this.entity.constructor.name, this.entity, { ...options, refresh: true, schema: this.__schema });
74
+ return this.__em.findOne(this.entity.constructor, this.entity, { ...options, refresh: true, schema: this.__schema });
75
75
  }
76
76
  async populate(populate, options = {}) {
77
77
  if (!this.__em) {