@mikro-orm/core 7.0.0-dev.32 → 7.0.0-dev.321

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 (216) hide show
  1. package/EntityManager.d.ts +71 -63
  2. package/EntityManager.js +365 -283
  3. package/MikroORM.d.ts +44 -35
  4. package/MikroORM.js +109 -142
  5. package/README.md +7 -4
  6. package/cache/FileCacheAdapter.d.ts +1 -2
  7. package/cache/FileCacheAdapter.js +19 -14
  8. package/cache/GeneratedCacheAdapter.d.ts +0 -1
  9. package/cache/GeneratedCacheAdapter.js +0 -2
  10. package/cache/index.d.ts +1 -2
  11. package/cache/index.js +0 -2
  12. package/connections/Connection.d.ts +12 -5
  13. package/connections/Connection.js +37 -15
  14. package/drivers/DatabaseDriver.d.ts +25 -18
  15. package/drivers/DatabaseDriver.js +144 -45
  16. package/drivers/IDatabaseDriver.d.ts +118 -23
  17. package/entity/BaseEntity.d.ts +63 -4
  18. package/entity/BaseEntity.js +0 -3
  19. package/entity/Collection.d.ts +101 -29
  20. package/entity/Collection.js +473 -115
  21. package/entity/EntityAssigner.js +37 -25
  22. package/entity/EntityFactory.d.ts +7 -1
  23. package/entity/EntityFactory.js +116 -64
  24. package/entity/EntityHelper.d.ts +2 -2
  25. package/entity/EntityHelper.js +69 -27
  26. package/entity/EntityLoader.d.ts +11 -10
  27. package/entity/EntityLoader.js +264 -102
  28. package/entity/EntityRepository.d.ts +28 -8
  29. package/entity/EntityRepository.js +8 -2
  30. package/entity/PolymorphicRef.d.ts +12 -0
  31. package/entity/PolymorphicRef.js +18 -0
  32. package/entity/Reference.d.ts +2 -6
  33. package/entity/Reference.js +52 -19
  34. package/entity/WrappedEntity.d.ts +3 -8
  35. package/entity/WrappedEntity.js +6 -7
  36. package/entity/defineEntity.d.ts +525 -311
  37. package/entity/defineEntity.js +134 -290
  38. package/entity/index.d.ts +2 -2
  39. package/entity/index.js +2 -2
  40. package/entity/utils.d.ts +6 -1
  41. package/entity/utils.js +46 -11
  42. package/entity/validators.d.ts +11 -0
  43. package/entity/validators.js +66 -0
  44. package/enums.d.ts +8 -6
  45. package/enums.js +13 -17
  46. package/errors.d.ts +20 -10
  47. package/errors.js +63 -31
  48. package/events/EventManager.d.ts +2 -1
  49. package/events/EventManager.js +24 -13
  50. package/events/index.d.ts +1 -1
  51. package/events/index.js +0 -1
  52. package/exceptions.js +9 -2
  53. package/hydration/Hydrator.js +1 -2
  54. package/hydration/ObjectHydrator.d.ts +4 -4
  55. package/hydration/ObjectHydrator.js +105 -46
  56. package/index.d.ts +2 -2
  57. package/index.js +1 -2
  58. package/logging/DefaultLogger.d.ts +1 -1
  59. package/logging/DefaultLogger.js +3 -4
  60. package/logging/SimpleLogger.d.ts +1 -1
  61. package/logging/colors.d.ts +1 -1
  62. package/logging/colors.js +5 -7
  63. package/logging/index.d.ts +2 -1
  64. package/logging/index.js +1 -1
  65. package/logging/inspect.d.ts +2 -0
  66. package/logging/inspect.js +11 -0
  67. package/metadata/EntitySchema.d.ts +47 -23
  68. package/metadata/EntitySchema.js +103 -34
  69. package/metadata/MetadataDiscovery.d.ts +64 -9
  70. package/metadata/MetadataDiscovery.js +867 -354
  71. package/metadata/MetadataProvider.d.ts +11 -2
  72. package/metadata/MetadataProvider.js +71 -2
  73. package/metadata/MetadataStorage.d.ts +13 -11
  74. package/metadata/MetadataStorage.js +72 -41
  75. package/metadata/MetadataValidator.d.ts +32 -9
  76. package/metadata/MetadataValidator.js +214 -44
  77. package/metadata/discover-entities.d.ts +5 -0
  78. package/metadata/discover-entities.js +40 -0
  79. package/metadata/index.d.ts +1 -1
  80. package/metadata/index.js +0 -1
  81. package/metadata/types.d.ts +577 -0
  82. package/metadata/types.js +1 -0
  83. package/naming-strategy/AbstractNamingStrategy.d.ts +16 -4
  84. package/naming-strategy/AbstractNamingStrategy.js +26 -5
  85. package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
  86. package/naming-strategy/EntityCaseNamingStrategy.js +6 -5
  87. package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
  88. package/naming-strategy/MongoNamingStrategy.js +6 -6
  89. package/naming-strategy/NamingStrategy.d.ts +28 -4
  90. package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
  91. package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
  92. package/naming-strategy/index.d.ts +1 -1
  93. package/naming-strategy/index.js +0 -1
  94. package/not-supported.d.ts +2 -0
  95. package/not-supported.js +8 -0
  96. package/package.json +47 -36
  97. package/platforms/ExceptionConverter.js +1 -1
  98. package/platforms/Platform.d.ts +33 -15
  99. package/platforms/Platform.js +125 -69
  100. package/serialization/EntitySerializer.d.ts +6 -3
  101. package/serialization/EntitySerializer.js +53 -29
  102. package/serialization/EntityTransformer.js +33 -21
  103. package/serialization/SerializationContext.d.ts +6 -6
  104. package/serialization/SerializationContext.js +4 -4
  105. package/types/ArrayType.d.ts +1 -1
  106. package/types/ArrayType.js +2 -3
  107. package/types/BigIntType.js +1 -1
  108. package/types/BlobType.d.ts +0 -1
  109. package/types/BlobType.js +0 -3
  110. package/types/BooleanType.d.ts +1 -0
  111. package/types/BooleanType.js +3 -0
  112. package/types/DecimalType.js +2 -2
  113. package/types/DoubleType.js +1 -1
  114. package/types/EnumArrayType.js +1 -2
  115. package/types/JsonType.d.ts +1 -1
  116. package/types/JsonType.js +7 -2
  117. package/types/TinyIntType.js +1 -1
  118. package/types/Type.d.ts +2 -4
  119. package/types/Type.js +3 -3
  120. package/types/Uint8ArrayType.d.ts +0 -1
  121. package/types/Uint8ArrayType.js +1 -4
  122. package/types/UuidType.d.ts +2 -0
  123. package/types/UuidType.js +14 -2
  124. package/types/index.d.ts +3 -2
  125. package/typings.d.ts +427 -170
  126. package/typings.js +100 -45
  127. package/unit-of-work/ChangeSet.d.ts +4 -6
  128. package/unit-of-work/ChangeSet.js +8 -9
  129. package/unit-of-work/ChangeSetComputer.d.ts +3 -8
  130. package/unit-of-work/ChangeSetComputer.js +49 -26
  131. package/unit-of-work/ChangeSetPersister.d.ts +13 -12
  132. package/unit-of-work/ChangeSetPersister.js +107 -44
  133. package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
  134. package/unit-of-work/CommitOrderCalculator.js +17 -15
  135. package/unit-of-work/IdentityMap.d.ts +12 -0
  136. package/unit-of-work/IdentityMap.js +39 -1
  137. package/unit-of-work/UnitOfWork.d.ts +34 -4
  138. package/unit-of-work/UnitOfWork.js +294 -107
  139. package/utils/AbstractMigrator.d.ts +101 -0
  140. package/utils/AbstractMigrator.js +303 -0
  141. package/utils/AbstractSchemaGenerator.d.ts +5 -5
  142. package/utils/AbstractSchemaGenerator.js +30 -18
  143. package/utils/AsyncContext.d.ts +6 -0
  144. package/utils/AsyncContext.js +42 -0
  145. package/utils/Configuration.d.ts +796 -211
  146. package/utils/Configuration.js +160 -197
  147. package/utils/ConfigurationLoader.d.ts +1 -52
  148. package/utils/ConfigurationLoader.js +1 -330
  149. package/utils/Cursor.d.ts +0 -3
  150. package/utils/Cursor.js +29 -14
  151. package/utils/DataloaderUtils.d.ts +10 -5
  152. package/utils/DataloaderUtils.js +42 -22
  153. package/utils/EntityComparator.d.ts +16 -9
  154. package/utils/EntityComparator.js +202 -96
  155. package/utils/QueryHelper.d.ts +34 -7
  156. package/utils/QueryHelper.js +183 -72
  157. package/utils/RawQueryFragment.d.ts +28 -34
  158. package/utils/RawQueryFragment.js +37 -72
  159. package/utils/RequestContext.js +2 -2
  160. package/utils/TransactionContext.js +2 -2
  161. package/utils/TransactionManager.js +11 -7
  162. package/utils/Utils.d.ts +16 -127
  163. package/utils/Utils.js +106 -401
  164. package/utils/clone.js +13 -23
  165. package/utils/env-vars.d.ts +7 -0
  166. package/utils/env-vars.js +98 -0
  167. package/utils/fs-utils.d.ts +34 -0
  168. package/utils/fs-utils.js +193 -0
  169. package/utils/index.d.ts +1 -3
  170. package/utils/index.js +1 -3
  171. package/utils/upsert-utils.d.ts +9 -4
  172. package/utils/upsert-utils.js +51 -5
  173. package/decorators/Check.d.ts +0 -3
  174. package/decorators/Check.js +0 -13
  175. package/decorators/CreateRequestContext.d.ts +0 -3
  176. package/decorators/CreateRequestContext.js +0 -32
  177. package/decorators/Embeddable.d.ts +0 -8
  178. package/decorators/Embeddable.js +0 -11
  179. package/decorators/Embedded.d.ts +0 -12
  180. package/decorators/Embedded.js +0 -18
  181. package/decorators/Entity.d.ts +0 -33
  182. package/decorators/Entity.js +0 -12
  183. package/decorators/Enum.d.ts +0 -9
  184. package/decorators/Enum.js +0 -16
  185. package/decorators/Filter.d.ts +0 -2
  186. package/decorators/Filter.js +0 -8
  187. package/decorators/Formula.d.ts +0 -4
  188. package/decorators/Formula.js +0 -15
  189. package/decorators/Indexed.d.ts +0 -19
  190. package/decorators/Indexed.js +0 -20
  191. package/decorators/ManyToMany.d.ts +0 -42
  192. package/decorators/ManyToMany.js +0 -14
  193. package/decorators/ManyToOne.d.ts +0 -34
  194. package/decorators/ManyToOne.js +0 -14
  195. package/decorators/OneToMany.d.ts +0 -28
  196. package/decorators/OneToMany.js +0 -17
  197. package/decorators/OneToOne.d.ts +0 -28
  198. package/decorators/OneToOne.js +0 -7
  199. package/decorators/PrimaryKey.d.ts +0 -8
  200. package/decorators/PrimaryKey.js +0 -20
  201. package/decorators/Property.d.ts +0 -250
  202. package/decorators/Property.js +0 -32
  203. package/decorators/Transactional.d.ts +0 -14
  204. package/decorators/Transactional.js +0 -28
  205. package/decorators/hooks.d.ts +0 -16
  206. package/decorators/hooks.js +0 -47
  207. package/decorators/index.d.ts +0 -17
  208. package/decorators/index.js +0 -17
  209. package/entity/ArrayCollection.d.ts +0 -118
  210. package/entity/ArrayCollection.js +0 -407
  211. package/entity/EntityValidator.d.ts +0 -19
  212. package/entity/EntityValidator.js +0 -150
  213. package/metadata/ReflectMetadataProvider.d.ts +0 -8
  214. package/metadata/ReflectMetadataProvider.js +0 -44
  215. package/utils/resolveContextProvider.d.ts +0 -10
  216. 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;
@@ -29,10 +29,9 @@ export class EntityLoader {
29
29
  throw ValidationError.notDiscoveredEntity(entity, meta, 'populate');
30
30
  }
31
31
  const references = entities.filter(e => !helper(e).isInitialized());
32
- const visited = options.visited ??= new Set();
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,9 +56,10 @@ 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 => {
62
+ // oxfmt-ignore
63
63
  return typeof field === 'boolean' || field.field === PopulatePath.ALL ? { all: !!field, field: meta.primaryKeys[0] } : field;
64
64
  });
65
65
  if (normalized.some(p => p.all)) {
@@ -68,7 +68,7 @@ export class EntityLoader {
68
68
  // convert nested `field` with dot syntax to PopulateOptions with `children` array
69
69
  expandDotPaths(meta, normalized, true);
70
70
  if (lookup && populate !== false) {
71
- normalized = this.lookupEagerLoadedRelationships(entityName, normalized, strategy);
71
+ normalized = this.lookupEagerLoadedRelationships(entityName, normalized, strategy, '', [], exclude);
72
72
  // convert nested `field` with dot syntax produced by eager relations
73
73
  expandDotPaths(meta, normalized, true);
74
74
  }
@@ -90,6 +90,7 @@ export class EntityLoader {
90
90
  */
91
91
  mergeNestedPopulate(populate) {
92
92
  const tmp = populate.reduce((ret, item) => {
93
+ /* v8 ignore next */
93
94
  if (item.field === PopulatePath.ALL) {
94
95
  return ret;
95
96
  }
@@ -126,7 +127,8 @@ export class EntityLoader {
126
127
  }
127
128
  }
128
129
  if (prop.kind === ReferenceKind.SCALAR && prop.lazy) {
129
- const filtered = entities.filter(e => options.refresh || (prop.ref ? !e[prop.name]?.isInitialized() : e[prop.name] === undefined));
130
+ const filtered = entities.filter(e => options.refresh ||
131
+ (prop.ref ? !e[prop.name]?.isInitialized() : e[prop.name] === undefined));
130
132
  if (options.ignoreLazyScalarProperties || filtered.length === 0) {
131
133
  return entities;
132
134
  }
@@ -138,19 +140,25 @@ export class EntityLoader {
138
140
  }
139
141
  const filtered = this.filterCollections(entities, field, options, ref);
140
142
  const innerOrderBy = Utils.asArray(options.orderBy)
141
- .filter(orderBy => (Array.isArray(orderBy[prop.name]) && orderBy[prop.name].length > 0) || Utils.isObject(orderBy[prop.name]))
143
+ .filter(orderBy => (Array.isArray(orderBy[prop.name]) && orderBy[prop.name].length > 0) ||
144
+ Utils.isObject(orderBy[prop.name]))
142
145
  .flatMap(orderBy => orderBy[prop.name]);
143
146
  const where = await this.extractChildCondition(options, prop);
144
147
  if (prop.kind === ReferenceKind.MANY_TO_MANY && this.driver.getPlatform().usesPivotTable()) {
145
- const res = await this.findChildrenFromPivotTable(filtered, prop, options, innerOrderBy, populate, !!ref);
148
+ const pivotOrderBy = QueryHelper.mergeOrderBy(innerOrderBy, prop.orderBy, prop.targetMeta?.orderBy);
149
+ const res = await this.findChildrenFromPivotTable(filtered, prop, options, pivotOrderBy, populate, !!ref);
146
150
  return Utils.flatten(res);
147
151
  }
152
+ if (prop.polymorphic && prop.polymorphTargets) {
153
+ return this.populatePolymorphic(entities, prop, options, !!ref);
154
+ }
148
155
  const { items, partial } = await this.findChildren(options.filtered ?? entities, prop, populate, {
149
156
  ...options,
150
157
  where,
151
158
  orderBy: innerOrderBy,
152
159
  }, !!(ref || prop.mapToPk));
153
- this.initializeCollections(filtered, prop, field, items, innerOrderBy.length > 0, partial);
160
+ const customOrder = innerOrderBy.length > 0 || !!prop.orderBy || !!prop.targetMeta?.orderBy;
161
+ this.initializeCollections(filtered, prop, field, items, customOrder, partial);
154
162
  return items;
155
163
  }
156
164
  async populateScalar(meta, filtered, options) {
@@ -158,12 +166,76 @@ export class EntityLoader {
158
166
  const ids = Utils.unique(filtered.map(e => Utils.getPrimaryKeyValues(e, meta, true)));
159
167
  const where = this.mergePrimaryCondition(ids, pk, options, meta, this.metadata, this.driver.getPlatform());
160
168
  const { filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, fields } = options;
161
- await this.em.find(meta.className, where, {
162
- filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging,
169
+ await this.em.find(meta.class, where, {
170
+ filters,
171
+ convertCustomTypes,
172
+ lockMode,
173
+ strategy,
174
+ populateWhere,
175
+ connectionType,
176
+ logging,
163
177
  fields: fields,
164
178
  populate: [],
165
179
  });
166
180
  }
181
+ async populatePolymorphic(entities, prop, options, ref) {
182
+ const ownerMeta = this.metadata.get(entities[0].constructor);
183
+ // Separate entities: those with loaded refs vs those needing FK load
184
+ const toPopulate = [];
185
+ const needsFkLoad = [];
186
+ for (const entity of entities) {
187
+ const refValue = entity[prop.name];
188
+ if (refValue && helper(refValue).hasPrimaryKey()) {
189
+ if ((ref && !options.refresh) || // :ref hint - already have reference
190
+ (!ref && helper(refValue).__initialized && !options.refresh) // already loaded
191
+ ) {
192
+ continue;
193
+ }
194
+ toPopulate.push(entity);
195
+ }
196
+ else if (refValue == null && !helper(entity).__loadedProperties.has(prop.name)) {
197
+ // FK columns weren't loaded (partial loading) — need to re-fetch them.
198
+ // If the property IS in __loadedProperties, the FK was loaded and is genuinely null.
199
+ needsFkLoad.push(entity);
200
+ }
201
+ }
202
+ // Load FK columns using populateScalar pattern
203
+ if (needsFkLoad.length > 0) {
204
+ await this.populateScalar(ownerMeta, needsFkLoad, {
205
+ ...options,
206
+ fields: [...ownerMeta.primaryKeys, prop.name],
207
+ });
208
+ // After loading FKs, add to toPopulate if not using :ref hint
209
+ if (!ref) {
210
+ for (const entity of needsFkLoad) {
211
+ const refValue = entity[prop.name];
212
+ if (refValue && helper(refValue).hasPrimaryKey()) {
213
+ toPopulate.push(entity);
214
+ }
215
+ }
216
+ }
217
+ }
218
+ if (toPopulate.length === 0) {
219
+ return [];
220
+ }
221
+ // Group references by target class for batch loading
222
+ const groups = new Map();
223
+ for (const entity of toPopulate) {
224
+ const refValue = Reference.unwrapReference(entity[prop.name]);
225
+ const discriminator = QueryHelper.findDiscriminatorValue(prop.discriminatorMap, helper(refValue).__meta.class);
226
+ const group = groups.get(discriminator) ?? [];
227
+ group.push(refValue);
228
+ groups.set(discriminator, group);
229
+ }
230
+ // Load each group concurrently - identity map handles merging with existing references
231
+ const allItems = [];
232
+ await Promise.all([...groups].map(async ([discriminator, children]) => {
233
+ const targetMeta = this.metadata.find(prop.discriminatorMap[discriminator]);
234
+ await this.populateScalar(targetMeta, children, options);
235
+ allItems.push(...children);
236
+ }));
237
+ return allItems;
238
+ }
167
239
  initializeCollections(filtered, prop, field, children, customOrder, partial) {
168
240
  if (prop.kind === ReferenceKind.ONE_TO_MANY) {
169
241
  this.initializeOneToMany(filtered, children, prop, field, partial);
@@ -182,7 +254,7 @@ export class EntityLoader {
182
254
  for (const child of children) {
183
255
  const pk = child.__helper.__data[prop.mappedBy] ?? child[prop.mappedBy];
184
256
  if (pk) {
185
- const key = helper(mapToPk ? this.em.getReference(prop.type, pk) : pk).getSerializedPrimaryKey();
257
+ const key = helper(mapToPk ? this.em.getReference(prop.targetMeta.class, pk) : pk).getSerializedPrimaryKey();
186
258
  map[key]?.push(child);
187
259
  }
188
260
  }
@@ -198,7 +270,8 @@ export class EntityLoader {
198
270
  entity[field].hydrate(items, true, partial);
199
271
  }
200
272
  }
201
- else { // owning side of M:N without pivot table needs to be reordered
273
+ else {
274
+ // owning side of M:N without pivot table needs to be reordered
202
275
  for (const entity of filtered) {
203
276
  const order = !customOrder ? [...entity[prop.name].getItems(false)] : []; // copy order of references
204
277
  const items = children.filter(child => entity[prop.name].contains(child, false));
@@ -210,13 +283,23 @@ export class EntityLoader {
210
283
  }
211
284
  }
212
285
  async findChildren(entities, prop, populate, options, ref) {
213
- const children = this.getChildReferences(entities, prop, options, ref);
286
+ const children = Utils.unique(this.getChildReferences(entities, prop, options, ref));
214
287
  const meta = prop.targetMeta;
215
- let fk = Utils.getPrimaryKeyHash(meta.primaryKeys);
288
+ // When targetKey is set, use it for FK lookup instead of the PK
289
+ let fk = prop.targetKey ?? Utils.getPrimaryKeyHash(meta.primaryKeys);
216
290
  let schema = options.schema;
217
291
  const partial = !Utils.isEmpty(prop.where) || !Utils.isEmpty(options.where);
292
+ let polymorphicOwnerProp;
218
293
  if (prop.kind === ReferenceKind.ONE_TO_MANY || (prop.kind === ReferenceKind.MANY_TO_MANY && !prop.owner)) {
219
- fk = meta.properties[prop.mappedBy].name;
294
+ const ownerProp = meta.properties[prop.mappedBy];
295
+ if (ownerProp.polymorphic && ownerProp.fieldNames.length >= 2) {
296
+ const idColumns = ownerProp.fieldNames.slice(1);
297
+ fk = idColumns.length === 1 ? idColumns[0] : idColumns;
298
+ polymorphicOwnerProp = ownerProp;
299
+ }
300
+ else {
301
+ fk = ownerProp.name;
302
+ }
220
303
  }
221
304
  if (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && !ref) {
222
305
  children.length = 0;
@@ -229,8 +312,25 @@ export class EntityLoader {
229
312
  if (!schema && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
230
313
  schema = children.find(e => e.__helper.__schema)?.__helper.__schema;
231
314
  }
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());
315
+ const ids = Utils.unique(children.map(e => (prop.targetKey ? e[prop.targetKey] : e.__helper.getPrimaryKey())));
316
+ let where;
317
+ if (polymorphicOwnerProp && Array.isArray(fk)) {
318
+ const conditions = ids.map(id => {
319
+ const pkValues = Object.values(id);
320
+ return Object.fromEntries(fk.map((col, idx) => [col, pkValues[idx]]));
321
+ });
322
+ where = (conditions.length === 1 ? conditions[0] : { $or: conditions });
323
+ }
324
+ else {
325
+ where = this.mergePrimaryCondition(ids, fk, options, meta, this.metadata, this.driver.getPlatform());
326
+ }
327
+ if (polymorphicOwnerProp) {
328
+ const parentMeta = this.metadata.find(entities[0].constructor);
329
+ const discriminatorValue = QueryHelper.findDiscriminatorValue(polymorphicOwnerProp.discriminatorMap, parentMeta.class) ??
330
+ parentMeta.tableName;
331
+ const discriminatorColumn = polymorphicOwnerProp.fieldNames[0];
332
+ where = { $and: [where, { [discriminatorColumn]: discriminatorValue }] };
333
+ }
234
334
  const fields = this.buildFields(options.fields, prop, ref);
235
335
  /* eslint-disable prefer-const */
236
336
  let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere = 'infer', connectionType, logging, } = options;
@@ -238,38 +338,74 @@ export class EntityLoader {
238
338
  if (typeof populateWhere === 'object') {
239
339
  populateWhere = await this.extractChildCondition({ where: populateWhere }, prop);
240
340
  }
241
- if (!Utils.isEmpty(prop.where)) {
341
+ if (!Utils.isEmpty(prop.where) || Raw.hasObjectFragments(prop.where)) {
242
342
  where = { $and: [where, prop.where] };
243
343
  }
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, {
263
- filters, convertCustomTypes, lockMode, populateWhere, logging,
344
+ const orderBy = QueryHelper.mergeOrderBy(options.orderBy, prop.orderBy);
345
+ const items = await this.em.find(meta.class, where, {
346
+ filters,
347
+ convertCustomTypes,
348
+ lockMode,
349
+ populateWhere,
350
+ logging,
264
351
  orderBy,
265
352
  populate: populate.children ?? populate.all ?? [],
266
- exclude: Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude,
267
- strategy, fields, schema, connectionType,
353
+ exclude: Array.isArray(options.exclude)
354
+ ? Utils.extractChildElements(options.exclude, prop.name)
355
+ : options.exclude,
356
+ strategy,
357
+ fields,
358
+ schema,
359
+ connectionType,
268
360
  // @ts-ignore not a public option, will be propagated to the populate call
269
361
  refresh: refresh && !children.every(item => options.visited.has(item)),
270
362
  // @ts-ignore not a public option, will be propagated to the populate call
271
363
  visited: options.visited,
272
364
  });
365
+ // For targetKey relations, wire up loaded entities to parent references
366
+ // This is needed because the references were created under alternate key,
367
+ // but loaded entities are stored under PK, so they don't automatically merge
368
+ if (prop.targetKey && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
369
+ const itemsByKey = new Map();
370
+ for (const item of items) {
371
+ itemsByKey.set('' + item[prop.targetKey], item);
372
+ }
373
+ for (const entity of entities) {
374
+ const ref = entity[prop.name];
375
+ /* v8 ignore next */
376
+ if (!ref) {
377
+ continue;
378
+ }
379
+ // oxfmt-ignore
380
+ const keyValue = '' + (Reference.isReference(ref) ? ref.unwrap()[prop.targetKey] : ref[prop.targetKey]);
381
+ const loadedItem = itemsByKey.get(keyValue);
382
+ if (loadedItem) {
383
+ entity[prop.name] = (Reference.isReference(ref) ? Reference.create(loadedItem) : loadedItem);
384
+ }
385
+ }
386
+ }
387
+ if ([ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && items.length !== children.length) {
388
+ const nullVal = this.em.config.get('forceUndefined') ? undefined : null;
389
+ const itemsMap = new Set();
390
+ const childrenMap = new Set();
391
+ // Use targetKey value if set, otherwise use serialized PK
392
+ const getKey = (e) => (prop.targetKey ? '' + e[prop.targetKey] : helper(e).getSerializedPrimaryKey());
393
+ for (const item of items) {
394
+ /* v8 ignore next */
395
+ itemsMap.add(getKey(item));
396
+ }
397
+ for (const child of children) {
398
+ childrenMap.add(getKey(child));
399
+ }
400
+ for (const entity of entities) {
401
+ const ref = entity[prop.name] ?? {};
402
+ const key = helper(ref) ? getKey(ref) : undefined;
403
+ if (key && childrenMap.has(key) && !itemsMap.has(key)) {
404
+ entity[prop.name] = nullVal;
405
+ helper(entity).__originalEntityData[prop.name] = null;
406
+ }
407
+ }
408
+ }
273
409
  for (const item of items) {
274
410
  if (ref && !helper(item).__onLoadFired) {
275
411
  helper(item).__initialized = false;
@@ -280,12 +416,16 @@ export class EntityLoader {
280
416
  return { items, partial };
281
417
  }
282
418
  mergePrimaryCondition(ids, pk, options, meta, metadata, platform) {
283
- const cond1 = QueryHelper.processWhere({ where: { [pk]: { $in: ids } }, entityName: meta.className, metadata, platform, convertCustomTypes: !options.convertCustomTypes });
419
+ const cond1 = QueryHelper.processWhere({
420
+ where: { [pk]: { $in: ids } },
421
+ entityName: meta.class,
422
+ metadata,
423
+ platform,
424
+ convertCustomTypes: !options.convertCustomTypes,
425
+ });
284
426
  const where = { ...options.where };
285
427
  Utils.dropUndefinedProperties(where);
286
- return where[pk]
287
- ? { $and: [cond1, where] }
288
- : { ...cond1, ...where };
428
+ return where[pk] ? { $and: [cond1, where] } : { ...cond1, ...where };
289
429
  }
290
430
  async populateField(entityName, entities, populate, options) {
291
431
  const field = populate.field.split(':')[0];
@@ -293,6 +433,7 @@ export class EntityLoader {
293
433
  if (prop.kind === ReferenceKind.SCALAR && !prop.lazy) {
294
434
  return;
295
435
  }
436
+ options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
296
437
  const populated = await this.populateMany(entityName, entities, populate, options);
297
438
  if (!populate.children && !populate.all) {
298
439
  return;
@@ -321,6 +462,7 @@ export class EntityLoader {
321
462
  .filter(orderBy => Utils.isObject(orderBy[prop.name]))
322
463
  .map(orderBy => orderBy[prop.name]);
323
464
  const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType, logging, schema } = options;
465
+ // oxfmt-ignore
324
466
  const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
325
467
  const visited = options.visited;
326
468
  for (const entity of entities) {
@@ -331,26 +473,42 @@ export class EntityLoader {
331
473
  for (const entity of entities) {
332
474
  visited.add(entity);
333
475
  }
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
- });
476
+ if (!prop.targetMeta) {
477
+ return;
478
+ }
479
+ const populateChildren = async (targetMeta, items) => {
480
+ await this.populate(targetMeta.class, items, populate.children ?? populate.all, {
481
+ where: (await this.extractChildCondition(options, prop, false)),
482
+ orderBy: innerOrderBy,
483
+ fields,
484
+ exclude,
485
+ validate: false,
486
+ lookup: false,
487
+ filters,
488
+ ignoreLazyScalarProperties,
489
+ populateWhere,
490
+ connectionType,
491
+ logging,
492
+ schema,
493
+ // @ts-ignore not a public option, will be propagated to the populate call
494
+ refresh: refresh && !filtered.every(item => options.visited.has(item)),
495
+ // @ts-ignore not a public option, will be propagated to the populate call
496
+ visited: options.visited,
497
+ // @ts-ignore not a public option
498
+ filtered,
499
+ });
500
+ };
501
+ if (prop.polymorphic && prop.polymorphTargets) {
502
+ await Promise.all(prop.polymorphTargets.map(async (targetMeta) => {
503
+ const targetChildren = unique.filter(child => helper(child).__meta.className === targetMeta.className);
504
+ if (targetChildren.length > 0) {
505
+ await populateChildren(targetMeta, targetChildren);
506
+ }
507
+ }));
508
+ }
509
+ else {
510
+ await populateChildren(prop.targetMeta, unique);
511
+ }
354
512
  }
355
513
  /** @internal */
356
514
  async findChildrenFromPivotTable(filtered, prop, options, orderBy, populate, pivotJoin) {
@@ -358,30 +516,27 @@ export class EntityLoader {
358
516
  const refresh = options.refresh;
359
517
  let where = await this.extractChildCondition(options, prop, true);
360
518
  const fields = this.buildFields(options.fields, prop);
519
+ // oxfmt-ignore
361
520
  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;
367
- options2.populate = (populate?.children ?? []);
368
- if (prop.customType) {
369
- ids.forEach((id, idx) => ids[idx] = QueryHelper.processCustomType(prop, id, this.driver.getPlatform()));
370
- }
521
+ const populateFilter = options.populateFilter?.[prop.name];
522
+ const options2 = { ...options, fields, exclude, populateFilter };
523
+ ['limit', 'offset', 'first', 'last', 'before', 'after', 'overfetch'].forEach(prop => delete options2[prop]);
524
+ options2.populate = populate?.children ?? [];
371
525
  if (!Utils.isEmpty(prop.where)) {
372
526
  where = { $and: [where, prop.where] };
373
527
  }
374
528
  const map = await this.driver.loadFromPivotTable(prop, ids, where, orderBy, this.em.getTransactionContext(), options2, pivotJoin);
375
529
  const children = [];
376
- for (const entity of filtered) {
377
- const items = map[entity.__helper.getSerializedPrimaryKey()].map(item => {
530
+ for (let i = 0; i < filtered.length; i++) {
531
+ const entity = filtered[i];
532
+ const items = map[Utils.getPrimaryKeyHash(ids[i])].map(item => {
378
533
  if (pivotJoin) {
379
- return this.em.getReference(prop.type, item, {
534
+ return this.em.getReference(prop.targetMeta.class, item, {
380
535
  convertCustomTypes: true,
381
536
  schema: options.schema ?? this.em.config.get('schema'),
382
537
  });
383
538
  }
384
- const entity = this.em.getEntityFactory().create(prop.type, item, {
539
+ const entity = this.em.getEntityFactory().create(prop.targetMeta.class, item, {
385
540
  refresh,
386
541
  merge: true,
387
542
  convertCustomTypes: true,
@@ -397,16 +552,14 @@ export class EntityLoader {
397
552
  async extractChildCondition(options, prop, filters = false) {
398
553
  const where = options.where;
399
554
  const subCond = Utils.isPlainObject(where[prop.name]) ? where[prop.name] : {};
400
- const meta2 = this.metadata.find(prop.type);
401
- if (!meta2) {
402
- return {};
403
- }
555
+ const meta2 = prop.targetMeta;
404
556
  const pk = Utils.getPrimaryKeyHash(meta2.primaryKeys);
405
557
  ['$and', '$or'].forEach(op => {
406
558
  if (where[op]) {
407
559
  const child = where[op]
408
560
  .map((cond) => cond[prop.name])
409
- .filter((sub) => sub != null && !(Utils.isPlainObject(sub) && Object.keys(sub).every(key => Utils.isOperator(key, false))))
561
+ .filter((sub) => sub != null &&
562
+ !(Utils.isPlainObject(sub) && Utils.getObjectQueryKeys(sub).every(key => Utils.isOperator(key, false))))
410
563
  .map((cond) => {
411
564
  if (Utils.isPrimaryKey(cond)) {
412
565
  return { [pk]: cond };
@@ -427,7 +580,7 @@ export class EntityLoader {
427
580
  });
428
581
  }
429
582
  if (filters) {
430
- return this.em.applyFilters(prop.type, subCond, options.filters, 'read', options);
583
+ return this.em.applyFilters(meta2.class, subCond, options.filters, 'read', options);
431
584
  }
432
585
  return subCond;
433
586
  }
@@ -445,23 +598,25 @@ export class EntityLoader {
445
598
  const parts = f.toString().split('.');
446
599
  const propName = parts.shift();
447
600
  const childPropName = parts.join('.');
448
- /* v8 ignore next 3 */
601
+ /* v8 ignore next */
449
602
  if (propName === prop.name) {
450
603
  ret.push(childPropName);
451
604
  }
452
605
  }
453
606
  return ret;
454
607
  }, []);
455
- if (ret.length === 0) {
456
- return undefined;
457
- }
458
608
  // we need to automatically select the FKs too, e.g. for 1:m relations to be able to wire them with the items
459
609
  if (prop.kind === ReferenceKind.ONE_TO_MANY || prop.kind === ReferenceKind.MANY_TO_MANY) {
460
610
  const owner = prop.targetMeta.properties[prop.mappedBy];
461
- if (owner && !ret.includes(owner.name)) {
611
+ // when the owning FK is lazy, we need to explicitly select it even without user-provided fields,
612
+ // otherwise the driver will exclude it and we won't be able to map children to their parent collections
613
+ if (owner && !ret.includes(owner.name) && (ret.length > 0 || owner.lazy)) {
462
614
  ret.push(owner.name);
463
615
  }
464
616
  }
617
+ if (ret.length === 0) {
618
+ return undefined;
619
+ }
465
620
  return ret;
466
621
  }
467
622
  getChildReferences(entities, prop, options, ref) {
@@ -475,7 +630,8 @@ export class EntityLoader {
475
630
  return a;
476
631
  }, []);
477
632
  }
478
- if (prop.kind === ReferenceKind.MANY_TO_MANY) { // inverse side
633
+ if (prop.kind === ReferenceKind.MANY_TO_MANY) {
634
+ // inverse side
479
635
  return filtered;
480
636
  }
481
637
  // MANY_TO_ONE or ONE_TO_ONE
@@ -496,7 +652,7 @@ export class EntityLoader {
496
652
  return wrapped.__loadedProperties.has(field);
497
653
  }
498
654
  const [f, ...r] = field.split('.');
499
- /* v8 ignore next 3 */
655
+ /* v8 ignore next */
500
656
  if (!wrapped.__loadedProperties.has(f) || !wrapped.__meta.properties[f]?.targetMeta) {
501
657
  return false;
502
658
  }
@@ -529,7 +685,7 @@ export class EntityLoader {
529
685
  .map(e => Reference.unwrapReference(e[field]));
530
686
  }
531
687
  filterByReferences(entities, field, refresh) {
532
- /* v8 ignore next 3 */
688
+ /* v8 ignore next */
533
689
  if (refresh) {
534
690
  return entities;
535
691
  }
@@ -555,37 +711,43 @@ export class EntityLoader {
555
711
  }
556
712
  return `${this.getRelationName(meta, meta.properties[prop.embedded[0]])}.${prop.embedded[1]}`;
557
713
  }
558
- lookupEagerLoadedRelationships(entityName, populate, strategy, prefix = '', visited = []) {
714
+ lookupEagerLoadedRelationships(entityName, populate, strategy, prefix = '', visited = [], exclude) {
559
715
  const meta = this.metadata.find(entityName);
560
716
  if (!meta && !prefix) {
561
717
  return populate;
562
718
  }
563
- if (visited.includes(entityName) || !meta) {
719
+ if (!meta || visited.includes(meta)) {
564
720
  return [];
565
721
  }
566
- visited.push(entityName);
722
+ visited.push(meta);
567
723
  const ret = prefix === '' ? [...populate] : [];
568
724
  meta.relations
569
725
  .filter(prop => {
726
+ const field = this.getRelationName(meta, prop);
727
+ const prefixed = prefix ? `${prefix}.${field}` : field;
728
+ const isExcluded = exclude?.includes(prefixed);
570
729
  const eager = prop.eager && !populate.some(p => p.field === `${prop.name}:ref`);
571
730
  const populated = populate.some(p => p.field === prop.name);
572
731
  const disabled = populate.some(p => p.field === prop.name && p.all === false);
573
- return !disabled && (eager || populated);
732
+ return !disabled && !isExcluded && (eager || populated);
574
733
  })
575
734
  .forEach(prop => {
576
735
  const field = this.getRelationName(meta, prop);
577
736
  const prefixed = prefix ? `${prefix}.${field}` : field;
578
- const nestedPopulate = populate.filter(p => p.field === prop.name).flatMap(p => p.children).filter(Boolean);
579
- const nested = this.lookupEagerLoadedRelationships(prop.type, nestedPopulate, strategy, prefixed, visited.slice());
737
+ const nestedPopulate = populate
738
+ .filter(p => p.field === prop.name)
739
+ .flatMap(p => p.children)
740
+ .filter(Boolean);
741
+ const nested = this.lookupEagerLoadedRelationships(prop.targetMeta.class, nestedPopulate, strategy, prefixed, visited.slice(), exclude);
580
742
  if (nested.length > 0) {
581
743
  ret.push(...nested);
582
744
  }
583
745
  else {
584
- const selfReferencing = [meta.className, meta.root.className, ...visited].includes(prop.type) && prop.eager;
746
+ const selfReferencing = [meta.tableName, ...visited.map(m => m.tableName)].includes(prop.targetMeta.tableName) && prop.eager;
585
747
  ret.push({
586
748
  field: prefixed,
587
749
  // enforce select-in strategy for self-referencing relations
588
- strategy: selfReferencing ? LoadStrategy.SELECT_IN : strategy ?? prop.strategy,
750
+ strategy: selfReferencing ? LoadStrategy.SELECT_IN : (strategy ?? prop.strategy),
589
751
  });
590
752
  }
591
753
  });