@mikro-orm/core 7.0.0-dev.3 → 7.0.0-dev.300

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 (214) hide show
  1. package/EntityManager.d.ts +114 -63
  2. package/EntityManager.js +385 -310
  3. package/MikroORM.d.ts +44 -35
  4. package/MikroORM.js +109 -143
  5. package/README.md +3 -2
  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 +16 -7
  13. package/connections/Connection.js +23 -14
  14. package/drivers/DatabaseDriver.d.ts +25 -16
  15. package/drivers/DatabaseDriver.js +119 -36
  16. package/drivers/IDatabaseDriver.d.ts +125 -23
  17. package/entity/BaseEntity.d.ts +63 -4
  18. package/entity/BaseEntity.js +0 -3
  19. package/entity/Collection.d.ts +102 -31
  20. package/entity/Collection.js +446 -108
  21. package/entity/EntityAssigner.d.ts +1 -1
  22. package/entity/EntityAssigner.js +26 -18
  23. package/entity/EntityFactory.d.ts +13 -1
  24. package/entity/EntityFactory.js +106 -60
  25. package/entity/EntityHelper.d.ts +2 -2
  26. package/entity/EntityHelper.js +65 -20
  27. package/entity/EntityLoader.d.ts +13 -11
  28. package/entity/EntityLoader.js +257 -107
  29. package/entity/EntityRepository.d.ts +28 -8
  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 +9 -12
  34. package/entity/Reference.js +34 -9
  35. package/entity/WrappedEntity.d.ts +3 -8
  36. package/entity/WrappedEntity.js +3 -8
  37. package/entity/defineEntity.d.ts +753 -0
  38. package/entity/defineEntity.js +537 -0
  39. package/entity/index.d.ts +4 -2
  40. package/entity/index.js +4 -2
  41. package/entity/utils.d.ts +13 -1
  42. package/entity/utils.js +49 -4
  43. package/entity/validators.d.ts +11 -0
  44. package/entity/validators.js +65 -0
  45. package/enums.d.ts +23 -8
  46. package/enums.js +15 -1
  47. package/errors.d.ts +25 -9
  48. package/errors.js +67 -21
  49. package/events/EventManager.d.ts +2 -1
  50. package/events/EventManager.js +19 -11
  51. package/events/EventSubscriber.d.ts +3 -1
  52. package/hydration/Hydrator.js +1 -2
  53. package/hydration/ObjectHydrator.d.ts +4 -4
  54. package/hydration/ObjectHydrator.js +89 -36
  55. package/index.d.ts +2 -2
  56. package/index.js +1 -2
  57. package/logging/DefaultLogger.d.ts +1 -1
  58. package/logging/DefaultLogger.js +1 -0
  59. package/logging/SimpleLogger.d.ts +1 -1
  60. package/logging/colors.d.ts +1 -1
  61. package/logging/colors.js +7 -6
  62. package/logging/index.d.ts +1 -0
  63. package/logging/index.js +1 -0
  64. package/logging/inspect.d.ts +2 -0
  65. package/logging/inspect.js +11 -0
  66. package/metadata/EntitySchema.d.ts +53 -27
  67. package/metadata/EntitySchema.js +125 -52
  68. package/metadata/MetadataDiscovery.d.ts +64 -10
  69. package/metadata/MetadataDiscovery.js +823 -344
  70. package/metadata/MetadataProvider.d.ts +11 -2
  71. package/metadata/MetadataProvider.js +66 -2
  72. package/metadata/MetadataStorage.d.ts +13 -11
  73. package/metadata/MetadataStorage.js +71 -38
  74. package/metadata/MetadataValidator.d.ts +32 -9
  75. package/metadata/MetadataValidator.js +198 -42
  76. package/metadata/discover-entities.d.ts +5 -0
  77. package/metadata/discover-entities.js +40 -0
  78. package/metadata/index.d.ts +1 -1
  79. package/metadata/index.js +1 -1
  80. package/metadata/types.d.ts +577 -0
  81. package/metadata/types.js +1 -0
  82. package/naming-strategy/AbstractNamingStrategy.d.ts +16 -4
  83. package/naming-strategy/AbstractNamingStrategy.js +20 -2
  84. package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
  85. package/naming-strategy/EntityCaseNamingStrategy.js +6 -5
  86. package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
  87. package/naming-strategy/MongoNamingStrategy.js +6 -6
  88. package/naming-strategy/NamingStrategy.d.ts +28 -4
  89. package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
  90. package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
  91. package/not-supported.d.ts +2 -0
  92. package/not-supported.js +4 -0
  93. package/package.json +22 -11
  94. package/platforms/ExceptionConverter.js +1 -1
  95. package/platforms/Platform.d.ts +14 -16
  96. package/platforms/Platform.js +24 -44
  97. package/serialization/EntitySerializer.d.ts +8 -3
  98. package/serialization/EntitySerializer.js +47 -27
  99. package/serialization/EntityTransformer.js +33 -21
  100. package/serialization/SerializationContext.d.ts +6 -6
  101. package/serialization/SerializationContext.js +16 -13
  102. package/types/ArrayType.d.ts +1 -1
  103. package/types/ArrayType.js +2 -3
  104. package/types/BigIntType.d.ts +9 -6
  105. package/types/BigIntType.js +4 -1
  106. package/types/BlobType.d.ts +0 -1
  107. package/types/BlobType.js +0 -3
  108. package/types/BooleanType.d.ts +2 -1
  109. package/types/BooleanType.js +3 -0
  110. package/types/DecimalType.d.ts +6 -4
  111. package/types/DecimalType.js +3 -3
  112. package/types/DoubleType.js +2 -2
  113. package/types/EnumArrayType.js +1 -2
  114. package/types/JsonType.d.ts +1 -1
  115. package/types/JsonType.js +7 -2
  116. package/types/TinyIntType.js +1 -1
  117. package/types/Type.d.ts +2 -4
  118. package/types/Type.js +3 -3
  119. package/types/Uint8ArrayType.d.ts +0 -1
  120. package/types/Uint8ArrayType.js +1 -4
  121. package/types/index.d.ts +1 -1
  122. package/typings.d.ts +469 -175
  123. package/typings.js +120 -45
  124. package/unit-of-work/ChangeSet.d.ts +4 -6
  125. package/unit-of-work/ChangeSet.js +4 -5
  126. package/unit-of-work/ChangeSetComputer.d.ts +3 -8
  127. package/unit-of-work/ChangeSetComputer.js +44 -21
  128. package/unit-of-work/ChangeSetPersister.d.ts +15 -12
  129. package/unit-of-work/ChangeSetPersister.js +113 -45
  130. package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
  131. package/unit-of-work/CommitOrderCalculator.js +13 -13
  132. package/unit-of-work/IdentityMap.d.ts +12 -0
  133. package/unit-of-work/IdentityMap.js +39 -1
  134. package/unit-of-work/UnitOfWork.d.ts +28 -3
  135. package/unit-of-work/UnitOfWork.js +315 -110
  136. package/utils/AbstractMigrator.d.ts +101 -0
  137. package/utils/AbstractMigrator.js +305 -0
  138. package/utils/AbstractSchemaGenerator.d.ts +5 -5
  139. package/utils/AbstractSchemaGenerator.js +32 -18
  140. package/utils/AsyncContext.d.ts +6 -0
  141. package/utils/AsyncContext.js +42 -0
  142. package/utils/Configuration.d.ts +801 -207
  143. package/utils/Configuration.js +150 -191
  144. package/utils/ConfigurationLoader.d.ts +1 -54
  145. package/utils/ConfigurationLoader.js +1 -352
  146. package/utils/Cursor.d.ts +3 -6
  147. package/utils/Cursor.js +27 -11
  148. package/utils/DataloaderUtils.d.ts +15 -5
  149. package/utils/DataloaderUtils.js +65 -17
  150. package/utils/EntityComparator.d.ts +21 -10
  151. package/utils/EntityComparator.js +243 -106
  152. package/utils/QueryHelper.d.ts +24 -6
  153. package/utils/QueryHelper.js +122 -26
  154. package/utils/RawQueryFragment.d.ts +60 -32
  155. package/utils/RawQueryFragment.js +69 -66
  156. package/utils/RequestContext.js +2 -2
  157. package/utils/TransactionContext.js +2 -2
  158. package/utils/TransactionManager.d.ts +65 -0
  159. package/utils/TransactionManager.js +223 -0
  160. package/utils/Utils.d.ts +15 -122
  161. package/utils/Utils.js +108 -376
  162. package/utils/clone.js +8 -23
  163. package/utils/env-vars.d.ts +7 -0
  164. package/utils/env-vars.js +97 -0
  165. package/utils/fs-utils.d.ts +34 -0
  166. package/utils/fs-utils.js +196 -0
  167. package/utils/index.d.ts +2 -3
  168. package/utils/index.js +2 -3
  169. package/utils/upsert-utils.d.ts +9 -4
  170. package/utils/upsert-utils.js +55 -4
  171. package/decorators/Check.d.ts +0 -3
  172. package/decorators/Check.js +0 -13
  173. package/decorators/CreateRequestContext.d.ts +0 -3
  174. package/decorators/CreateRequestContext.js +0 -32
  175. package/decorators/Embeddable.d.ts +0 -8
  176. package/decorators/Embeddable.js +0 -11
  177. package/decorators/Embedded.d.ts +0 -18
  178. package/decorators/Embedded.js +0 -18
  179. package/decorators/Entity.d.ts +0 -18
  180. package/decorators/Entity.js +0 -13
  181. package/decorators/Enum.d.ts +0 -9
  182. package/decorators/Enum.js +0 -16
  183. package/decorators/Filter.d.ts +0 -2
  184. package/decorators/Filter.js +0 -8
  185. package/decorators/Formula.d.ts +0 -5
  186. package/decorators/Formula.js +0 -15
  187. package/decorators/Indexed.d.ts +0 -17
  188. package/decorators/Indexed.js +0 -20
  189. package/decorators/ManyToMany.d.ts +0 -40
  190. package/decorators/ManyToMany.js +0 -14
  191. package/decorators/ManyToOne.d.ts +0 -30
  192. package/decorators/ManyToOne.js +0 -14
  193. package/decorators/OneToMany.d.ts +0 -28
  194. package/decorators/OneToMany.js +0 -17
  195. package/decorators/OneToOne.d.ts +0 -24
  196. package/decorators/OneToOne.js +0 -7
  197. package/decorators/PrimaryKey.d.ts +0 -9
  198. package/decorators/PrimaryKey.js +0 -20
  199. package/decorators/Property.d.ts +0 -250
  200. package/decorators/Property.js +0 -32
  201. package/decorators/Transactional.d.ts +0 -13
  202. package/decorators/Transactional.js +0 -28
  203. package/decorators/hooks.d.ts +0 -16
  204. package/decorators/hooks.js +0 -47
  205. package/decorators/index.d.ts +0 -17
  206. package/decorators/index.js +0 -17
  207. package/entity/ArrayCollection.d.ts +0 -116
  208. package/entity/ArrayCollection.js +0 -395
  209. package/entity/EntityValidator.d.ts +0 -19
  210. package/entity/EntityValidator.js +0 -150
  211. package/metadata/ReflectMetadataProvider.d.ts +0 -8
  212. package/metadata/ReflectMetadataProvider.js +0 -44
  213. package/utils/resolveContextProvider.d.ts +0 -10
  214. 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,17 +32,16 @@ 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;
39
38
  options.convertCustomTypes ??= true;
40
39
  if (references.length > 0) {
41
- await this.populateScalar(meta, references, options);
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
  }
@@ -140,34 +139,102 @@ export class EntityLoader {
140
139
  const innerOrderBy = Utils.asArray(options.orderBy)
141
140
  .filter(orderBy => (Array.isArray(orderBy[prop.name]) && orderBy[prop.name].length > 0) || Utils.isObject(orderBy[prop.name]))
142
141
  .flatMap(orderBy => orderBy[prop.name]);
142
+ const where = await this.extractChildCondition(options, prop);
143
143
  if (prop.kind === ReferenceKind.MANY_TO_MANY && this.driver.getPlatform().usesPivotTable()) {
144
- return 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
+ return Utils.flatten(res);
145
147
  }
146
- const where = await this.extractChildCondition(options, prop);
147
- const data = await this.findChildren(entities, prop, populate, { ...options, where, orderBy: innerOrderBy }, !!(ref || prop.mapToPk));
148
- this.initializeCollections(filtered, prop, field, data, innerOrderBy.length > 0);
149
- return data;
148
+ if (prop.polymorphic && prop.polymorphTargets) {
149
+ return this.populatePolymorphic(entities, prop, options, !!ref);
150
+ }
151
+ const { items, partial } = await this.findChildren(options.filtered ?? entities, prop, populate, {
152
+ ...options,
153
+ where,
154
+ orderBy: innerOrderBy,
155
+ }, !!(ref || prop.mapToPk));
156
+ const customOrder = innerOrderBy.length > 0 || !!prop.orderBy || !!prop.targetMeta?.orderBy;
157
+ this.initializeCollections(filtered, prop, field, items, customOrder, partial);
158
+ return items;
150
159
  }
151
160
  async populateScalar(meta, filtered, options) {
152
161
  const pk = Utils.getPrimaryKeyHash(meta.primaryKeys);
153
- const ids = Utils.unique(filtered.map(e => Utils.getPrimaryKeyValues(e, meta.primaryKeys, true)));
162
+ const ids = Utils.unique(filtered.map(e => Utils.getPrimaryKeyValues(e, meta, true)));
154
163
  const where = this.mergePrimaryCondition(ids, pk, options, meta, this.metadata, this.driver.getPlatform());
155
164
  const { filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, fields } = options;
156
- await this.em.find(meta.className, where, {
165
+ await this.em.find(meta.class, where, {
157
166
  filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging,
158
167
  fields: fields,
159
168
  populate: [],
160
169
  });
161
170
  }
162
- initializeCollections(filtered, prop, field, children, customOrder) {
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
+ }
229
+ initializeCollections(filtered, prop, field, children, customOrder, partial) {
163
230
  if (prop.kind === ReferenceKind.ONE_TO_MANY) {
164
- this.initializeOneToMany(filtered, children, prop, field);
231
+ this.initializeOneToMany(filtered, children, prop, field, partial);
165
232
  }
166
233
  if (prop.kind === ReferenceKind.MANY_TO_MANY && !this.driver.getPlatform().usesPivotTable()) {
167
- this.initializeManyToMany(filtered, children, prop, field, customOrder);
234
+ this.initializeManyToMany(filtered, children, prop, field, customOrder, partial);
168
235
  }
169
236
  }
170
- initializeOneToMany(filtered, children, prop, field) {
237
+ initializeOneToMany(filtered, children, prop, field, partial) {
171
238
  const mapToPk = prop.targetMeta.properties[prop.mappedBy].mapToPk;
172
239
  const map = {};
173
240
  for (const entity of filtered) {
@@ -177,20 +244,20 @@ export class EntityLoader {
177
244
  for (const child of children) {
178
245
  const pk = child.__helper.__data[prop.mappedBy] ?? child[prop.mappedBy];
179
246
  if (pk) {
180
- 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();
181
248
  map[key]?.push(child);
182
249
  }
183
250
  }
184
251
  for (const entity of filtered) {
185
252
  const key = helper(entity).getSerializedPrimaryKey();
186
- entity[field].hydrate(map[key]);
253
+ entity[field].hydrate(map[key], undefined, partial);
187
254
  }
188
255
  }
189
- initializeManyToMany(filtered, children, prop, field, customOrder) {
256
+ initializeManyToMany(filtered, children, prop, field, customOrder, partial) {
190
257
  if (prop.mappedBy) {
191
258
  for (const entity of filtered) {
192
259
  const items = children.filter(child => child[prop.mappedBy].contains(entity, false));
193
- entity[field].hydrate(items, true);
260
+ entity[field].hydrate(items, true, partial);
194
261
  }
195
262
  }
196
263
  else { // owning side of M:N without pivot table needs to be reordered
@@ -200,17 +267,28 @@ export class EntityLoader {
200
267
  if (!customOrder) {
201
268
  items.sort((a, b) => order.indexOf(a) - order.indexOf(b));
202
269
  }
203
- entity[field].hydrate(items, true);
270
+ entity[field].hydrate(items, true, partial);
204
271
  }
205
272
  }
206
273
  }
207
274
  async findChildren(entities, prop, populate, options, ref) {
208
- const children = this.getChildReferences(entities, prop, options, ref);
275
+ const children = Utils.unique(this.getChildReferences(entities, prop, options, ref));
209
276
  const meta = prop.targetMeta;
210
- 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);
211
279
  let schema = options.schema;
280
+ const partial = !Utils.isEmpty(prop.where) || !Utils.isEmpty(options.where);
281
+ let polymorphicOwnerProp;
212
282
  if (prop.kind === ReferenceKind.ONE_TO_MANY || (prop.kind === ReferenceKind.MANY_TO_MANY && !prop.owner)) {
213
- 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
+ }
214
292
  }
215
293
  if (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && !ref) {
216
294
  children.length = 0;
@@ -218,40 +296,43 @@ export class EntityLoader {
218
296
  children.push(...this.filterByReferences(entities, prop.name, options.refresh));
219
297
  }
220
298
  if (children.length === 0) {
221
- return [];
299
+ return { items: [], partial };
222
300
  }
223
301
  if (!schema && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
224
302
  schema = children.find(e => e.__helper.__schema)?.__helper.__schema;
225
303
  }
226
- const ids = Utils.unique(children.map(e => e.__helper.getPrimaryKey()));
227
- 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
+ }
228
322
  const fields = this.buildFields(options.fields, prop, ref);
229
323
  /* eslint-disable prefer-const */
230
- let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, } = options;
324
+ let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere = 'infer', connectionType, logging, } = options;
231
325
  /* eslint-enable prefer-const */
232
326
  if (typeof populateWhere === 'object') {
233
327
  populateWhere = await this.extractChildCondition({ where: populateWhere }, prop);
234
328
  }
235
- if (!Utils.isEmpty(prop.where)) {
329
+ if (!Utils.isEmpty(prop.where) || Raw.hasObjectFragments(prop.where)) {
236
330
  where = { $and: [where, prop.where] };
237
331
  }
238
- const propOrderBy = [];
239
- if (prop.orderBy) {
240
- for (const item of Utils.asArray(prop.orderBy)) {
241
- for (const field of Utils.keys(item)) {
242
- const rawField = RawQueryFragment.getKnownFragment(field, false);
243
- if (rawField) {
244
- const raw2 = raw(rawField.sql, rawField.params);
245
- propOrderBy.push({ [raw2.toString()]: item[field] });
246
- continue;
247
- }
248
- propOrderBy.push({ [field]: item[field] });
249
- }
250
- }
251
- }
252
- 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, {
253
334
  filters, convertCustomTypes, lockMode, populateWhere, logging,
254
- orderBy: [...Utils.asArray(options.orderBy), ...propOrderBy],
335
+ orderBy,
255
336
  populate: populate.children ?? populate.all ?? [],
256
337
  exclude: Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude,
257
338
  strategy, fields, schema, connectionType,
@@ -260,6 +341,49 @@ export class EntityLoader {
260
341
  // @ts-ignore not a public option, will be propagated to the populate call
261
342
  visited: options.visited,
262
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
+ }
263
387
  for (const item of items) {
264
388
  if (ref && !helper(item).__onLoadFired) {
265
389
  helper(item).__initialized = false;
@@ -267,10 +391,10 @@ export class EntityLoader {
267
391
  this.em.getUnitOfWork()['loadedEntities'].delete(item);
268
392
  }
269
393
  }
270
- return items;
394
+ return { items, partial };
271
395
  }
272
396
  mergePrimaryCondition(ids, pk, options, meta, metadata, platform) {
273
- 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 });
274
398
  const where = { ...options.where };
275
399
  Utils.dropUndefinedProperties(where);
276
400
  return where[pk]
@@ -283,6 +407,7 @@ export class EntityLoader {
283
407
  if (prop.kind === ReferenceKind.SCALAR && !prop.lazy) {
284
408
  return;
285
409
  }
410
+ options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
286
411
  const populated = await this.populateMany(entityName, entities, populate, options);
287
412
  if (!populate.children && !populate.all) {
288
413
  return;
@@ -310,38 +435,64 @@ export class EntityLoader {
310
435
  const innerOrderBy = Utils.asArray(options.orderBy)
311
436
  .filter(orderBy => Utils.isObject(orderBy[prop.name]))
312
437
  .map(orderBy => orderBy[prop.name]);
313
- const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType, logging } = options;
438
+ const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType, logging, schema } = options;
314
439
  const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
315
- const filtered = Utils.unique(children.filter(e => !options.visited.has(e)));
316
- await this.populate(prop.type, filtered, populate.children ?? populate.all, {
317
- where: await this.extractChildCondition(options, prop, false),
318
- orderBy: innerOrderBy,
319
- fields,
320
- exclude,
321
- validate: false,
322
- lookup: false,
323
- filters,
324
- ignoreLazyScalarProperties,
325
- populateWhere,
326
- connectionType,
327
- logging,
328
- // @ts-ignore not a public option, will be propagated to the populate call
329
- refresh: refresh && !filtered.every(item => options.visited.has(item)),
330
- // @ts-ignore not a public option, will be propagated to the populate call
331
- visited: options.visited,
332
- });
440
+ const visited = options.visited;
441
+ for (const entity of entities) {
442
+ visited.delete(entity);
443
+ }
444
+ const unique = Utils.unique(children);
445
+ const filtered = unique.filter(e => !visited.has(e));
446
+ for (const entity of entities) {
447
+ visited.add(entity);
448
+ }
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
+ }
333
485
  }
486
+ /** @internal */
334
487
  async findChildrenFromPivotTable(filtered, prop, options, orderBy, populate, pivotJoin) {
335
488
  const ids = filtered.map(e => e.__helper.__primaryKeys);
336
489
  const refresh = options.refresh;
337
490
  let where = await this.extractChildCondition(options, prop, true);
338
491
  const fields = this.buildFields(options.fields, prop);
339
492
  const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
340
- const options2 = { ...options };
341
- delete options2.limit;
342
- delete options2.offset;
343
- options2.fields = fields;
344
- 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]);
345
496
  options2.populate = (populate?.children ?? []);
346
497
  if (prop.customType) {
347
498
  ids.forEach((id, idx) => ids[idx] = QueryHelper.processCustomType(prop, id, this.driver.getPlatform()));
@@ -354,12 +505,12 @@ export class EntityLoader {
354
505
  for (const entity of filtered) {
355
506
  const items = map[entity.__helper.getSerializedPrimaryKey()].map(item => {
356
507
  if (pivotJoin) {
357
- return this.em.getReference(prop.type, item, {
508
+ return this.em.getReference(prop.targetMeta.class, item, {
358
509
  convertCustomTypes: true,
359
510
  schema: options.schema ?? this.em.config.get('schema'),
360
511
  });
361
512
  }
362
- const entity = this.em.getEntityFactory().create(prop.type, item, {
513
+ const entity = this.em.getEntityFactory().create(prop.targetMeta.class, item, {
363
514
  refresh,
364
515
  merge: true,
365
516
  convertCustomTypes: true,
@@ -368,23 +519,20 @@ export class EntityLoader {
368
519
  return this.em.getUnitOfWork().register(entity, item, { refresh, loaded: true });
369
520
  });
370
521
  entity[prop.name].hydrate(items, true);
371
- children.push(...items);
522
+ children.push(items);
372
523
  }
373
524
  return children;
374
525
  }
375
526
  async extractChildCondition(options, prop, filters = false) {
376
527
  const where = options.where;
377
528
  const subCond = Utils.isPlainObject(where[prop.name]) ? where[prop.name] : {};
378
- const meta2 = this.metadata.find(prop.type);
379
- if (!meta2) {
380
- return {};
381
- }
529
+ const meta2 = prop.targetMeta;
382
530
  const pk = Utils.getPrimaryKeyHash(meta2.primaryKeys);
383
531
  ['$and', '$or'].forEach(op => {
384
532
  if (where[op]) {
385
533
  const child = where[op]
386
534
  .map((cond) => cond[prop.name])
387
- .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))))
388
536
  .map((cond) => {
389
537
  if (Utils.isPrimaryKey(cond)) {
390
538
  return { [pk]: cond };
@@ -405,7 +553,7 @@ export class EntityLoader {
405
553
  });
406
554
  }
407
555
  if (filters) {
408
- return this.em.applyFilters(prop.type, subCond, options.filters, 'read', options);
556
+ return this.em.applyFilters(meta2.class, subCond, options.filters, 'read', options);
409
557
  }
410
558
  return subCond;
411
559
  }
@@ -423,44 +571,43 @@ export class EntityLoader {
423
571
  const parts = f.toString().split('.');
424
572
  const propName = parts.shift();
425
573
  const childPropName = parts.join('.');
426
- /* v8 ignore next 3 */
574
+ /* v8 ignore next */
427
575
  if (propName === prop.name) {
428
576
  ret.push(childPropName);
429
577
  }
430
578
  }
431
579
  return ret;
432
580
  }, []);
433
- if (ret.length === 0) {
434
- return undefined;
435
- }
436
581
  // we need to automatically select the FKs too, e.g. for 1:m relations to be able to wire them with the items
437
582
  if (prop.kind === ReferenceKind.ONE_TO_MANY || prop.kind === ReferenceKind.MANY_TO_MANY) {
438
583
  const owner = prop.targetMeta.properties[prop.mappedBy];
439
- 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)) {
440
587
  ret.push(owner.name);
441
588
  }
442
589
  }
590
+ if (ret.length === 0) {
591
+ return undefined;
592
+ }
443
593
  return ret;
444
594
  }
445
595
  getChildReferences(entities, prop, options, ref) {
446
596
  const filtered = this.filterCollections(entities, prop.name, options, ref);
447
- const children = [];
448
597
  if (prop.kind === ReferenceKind.ONE_TO_MANY) {
449
- children.push(...filtered.map(e => e[prop.name].owner));
598
+ return filtered.map(e => e[prop.name].owner);
450
599
  }
451
- else if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
452
- children.push(...filtered.reduce((a, b) => {
600
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
601
+ return filtered.reduce((a, b) => {
453
602
  a.push(...b[prop.name].getItems());
454
603
  return a;
455
- }, []));
604
+ }, []);
456
605
  }
457
- else if (prop.kind === ReferenceKind.MANY_TO_MANY) { // inverse side
458
- children.push(...filtered);
606
+ if (prop.kind === ReferenceKind.MANY_TO_MANY) { // inverse side
607
+ return filtered;
459
608
  }
460
- else { // MANY_TO_ONE or ONE_TO_ONE
461
- children.push(...this.filterReferences(entities, prop.name, options, ref));
462
- }
463
- return children;
609
+ // MANY_TO_ONE or ONE_TO_ONE
610
+ return this.filterReferences(entities, prop.name, options, ref);
464
611
  }
465
612
  filterCollections(entities, field, options, ref) {
466
613
  if (options.refresh) {
@@ -477,7 +624,7 @@ export class EntityLoader {
477
624
  return wrapped.__loadedProperties.has(field);
478
625
  }
479
626
  const [f, ...r] = field.split('.');
480
- /* v8 ignore next 3 */
627
+ /* v8 ignore next */
481
628
  if (!wrapped.__loadedProperties.has(f) || !wrapped.__meta.properties[f]?.targetMeta) {
482
629
  return false;
483
630
  }
@@ -510,11 +657,11 @@ export class EntityLoader {
510
657
  .map(e => Reference.unwrapReference(e[field]));
511
658
  }
512
659
  filterByReferences(entities, field, refresh) {
513
- /* v8 ignore next 3 */
660
+ /* v8 ignore next */
514
661
  if (refresh) {
515
662
  return entities;
516
663
  }
517
- return entities.filter(e => !e[field]?.__helper?.__initialized);
664
+ return entities.filter(e => e[field] !== null && !e[field]?.__helper?.__initialized);
518
665
  }
519
666
  lookupAllRelationships(entityName) {
520
667
  const ret = [];
@@ -536,33 +683,36 @@ export class EntityLoader {
536
683
  }
537
684
  return `${this.getRelationName(meta, meta.properties[prop.embedded[0]])}.${prop.embedded[1]}`;
538
685
  }
539
- lookupEagerLoadedRelationships(entityName, populate, strategy, prefix = '', visited = []) {
686
+ lookupEagerLoadedRelationships(entityName, populate, strategy, prefix = '', visited = [], exclude) {
540
687
  const meta = this.metadata.find(entityName);
541
688
  if (!meta && !prefix) {
542
689
  return populate;
543
690
  }
544
- if (visited.includes(entityName) || !meta) {
691
+ if (!meta || visited.includes(meta)) {
545
692
  return [];
546
693
  }
547
- visited.push(entityName);
694
+ visited.push(meta);
548
695
  const ret = prefix === '' ? [...populate] : [];
549
696
  meta.relations
550
697
  .filter(prop => {
698
+ const field = this.getRelationName(meta, prop);
699
+ const prefixed = prefix ? `${prefix}.${field}` : field;
700
+ const isExcluded = exclude?.includes(prefixed);
551
701
  const eager = prop.eager && !populate.some(p => p.field === `${prop.name}:ref`);
552
702
  const populated = populate.some(p => p.field === prop.name);
553
703
  const disabled = populate.some(p => p.field === prop.name && p.all === false);
554
- return !disabled && (eager || populated);
704
+ return !disabled && !isExcluded && (eager || populated);
555
705
  })
556
706
  .forEach(prop => {
557
707
  const field = this.getRelationName(meta, prop);
558
708
  const prefixed = prefix ? `${prefix}.${field}` : field;
559
709
  const nestedPopulate = populate.filter(p => p.field === prop.name).flatMap(p => p.children).filter(Boolean);
560
- 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);
561
711
  if (nested.length > 0) {
562
712
  ret.push(...nested);
563
713
  }
564
714
  else {
565
- 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;
566
716
  ret.push({
567
717
  field: prefixed,
568
718
  // enforce select-in strategy for self-referencing relations