@mikro-orm/core 7.0.0-dev.30 → 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 (209) hide show
  1. package/EntityManager.d.ts +68 -60
  2. package/EntityManager.js +290 -259
  3. package/MikroORM.d.ts +44 -35
  4. package/MikroORM.js +109 -142
  5. package/README.md +2 -0
  6. package/cache/FileCacheAdapter.d.ts +1 -2
  7. package/cache/FileCacheAdapter.js +18 -11
  8. package/cache/GeneratedCacheAdapter.d.ts +0 -1
  9. package/cache/GeneratedCacheAdapter.js +0 -2
  10. package/cache/index.d.ts +0 -1
  11. package/cache/index.js +0 -1
  12. package/connections/Connection.d.ts +12 -5
  13. package/connections/Connection.js +21 -12
  14. package/drivers/DatabaseDriver.d.ts +25 -16
  15. package/drivers/DatabaseDriver.js +119 -36
  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 +436 -104
  21. package/entity/EntityAssigner.js +17 -17
  22. package/entity/EntityFactory.d.ts +7 -1
  23. package/entity/EntityFactory.js +87 -55
  24. package/entity/EntityHelper.d.ts +2 -2
  25. package/entity/EntityHelper.js +57 -19
  26. package/entity/EntityLoader.d.ts +11 -10
  27. package/entity/EntityLoader.js +213 -82
  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 +1 -5
  33. package/entity/Reference.js +15 -11
  34. package/entity/WrappedEntity.d.ts +3 -8
  35. package/entity/WrappedEntity.js +2 -7
  36. package/entity/defineEntity.d.ts +526 -310
  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 +34 -1
  42. package/entity/validators.d.ts +11 -0
  43. package/entity/validators.js +65 -0
  44. package/enums.d.ts +8 -6
  45. package/enums.js +2 -1
  46. package/errors.d.ts +20 -10
  47. package/errors.js +55 -23
  48. package/events/EventManager.d.ts +2 -1
  49. package/events/EventManager.js +19 -11
  50. package/hydration/Hydrator.js +1 -2
  51. package/hydration/ObjectHydrator.d.ts +4 -4
  52. package/hydration/ObjectHydrator.js +79 -34
  53. package/index.d.ts +2 -2
  54. package/index.js +1 -2
  55. package/logging/DefaultLogger.d.ts +1 -1
  56. package/logging/DefaultLogger.js +1 -0
  57. package/logging/SimpleLogger.d.ts +1 -1
  58. package/logging/colors.d.ts +1 -1
  59. package/logging/colors.js +7 -6
  60. package/logging/index.d.ts +1 -0
  61. package/logging/index.js +1 -0
  62. package/logging/inspect.d.ts +2 -0
  63. package/logging/inspect.js +11 -0
  64. package/metadata/EntitySchema.d.ts +47 -23
  65. package/metadata/EntitySchema.js +92 -33
  66. package/metadata/MetadataDiscovery.d.ts +64 -9
  67. package/metadata/MetadataDiscovery.js +782 -325
  68. package/metadata/MetadataProvider.d.ts +11 -2
  69. package/metadata/MetadataProvider.js +66 -2
  70. package/metadata/MetadataStorage.d.ts +13 -11
  71. package/metadata/MetadataStorage.js +72 -39
  72. package/metadata/MetadataValidator.d.ts +32 -9
  73. package/metadata/MetadataValidator.js +196 -41
  74. package/metadata/discover-entities.d.ts +5 -0
  75. package/metadata/discover-entities.js +40 -0
  76. package/metadata/index.d.ts +1 -1
  77. package/metadata/index.js +1 -1
  78. package/metadata/types.d.ts +577 -0
  79. package/metadata/types.js +1 -0
  80. package/naming-strategy/AbstractNamingStrategy.d.ts +16 -4
  81. package/naming-strategy/AbstractNamingStrategy.js +20 -2
  82. package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
  83. package/naming-strategy/EntityCaseNamingStrategy.js +6 -5
  84. package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
  85. package/naming-strategy/MongoNamingStrategy.js +6 -6
  86. package/naming-strategy/NamingStrategy.d.ts +28 -4
  87. package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
  88. package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
  89. package/not-supported.d.ts +2 -0
  90. package/not-supported.js +4 -0
  91. package/package.json +22 -11
  92. package/platforms/ExceptionConverter.js +1 -1
  93. package/platforms/Platform.d.ts +11 -15
  94. package/platforms/Platform.js +24 -44
  95. package/serialization/EntitySerializer.d.ts +6 -3
  96. package/serialization/EntitySerializer.js +46 -26
  97. package/serialization/EntityTransformer.js +33 -21
  98. package/serialization/SerializationContext.d.ts +6 -6
  99. package/serialization/SerializationContext.js +3 -3
  100. package/types/ArrayType.d.ts +1 -1
  101. package/types/ArrayType.js +2 -3
  102. package/types/BigIntType.js +1 -1
  103. package/types/BlobType.d.ts +0 -1
  104. package/types/BlobType.js +0 -3
  105. package/types/BooleanType.d.ts +1 -0
  106. package/types/BooleanType.js +3 -0
  107. package/types/DecimalType.js +2 -2
  108. package/types/DoubleType.js +1 -1
  109. package/types/EnumArrayType.js +1 -2
  110. package/types/JsonType.d.ts +1 -1
  111. package/types/JsonType.js +7 -2
  112. package/types/TinyIntType.js +1 -1
  113. package/types/Type.d.ts +2 -4
  114. package/types/Type.js +3 -3
  115. package/types/Uint8ArrayType.d.ts +0 -1
  116. package/types/Uint8ArrayType.js +1 -4
  117. package/types/index.d.ts +1 -1
  118. package/typings.d.ts +412 -155
  119. package/typings.js +99 -44
  120. package/unit-of-work/ChangeSet.d.ts +4 -6
  121. package/unit-of-work/ChangeSet.js +4 -5
  122. package/unit-of-work/ChangeSetComputer.d.ts +3 -8
  123. package/unit-of-work/ChangeSetComputer.js +41 -20
  124. package/unit-of-work/ChangeSetPersister.d.ts +13 -12
  125. package/unit-of-work/ChangeSetPersister.js +94 -36
  126. package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
  127. package/unit-of-work/CommitOrderCalculator.js +13 -13
  128. package/unit-of-work/IdentityMap.d.ts +12 -0
  129. package/unit-of-work/IdentityMap.js +39 -1
  130. package/unit-of-work/UnitOfWork.d.ts +27 -3
  131. package/unit-of-work/UnitOfWork.js +248 -90
  132. package/utils/AbstractMigrator.d.ts +101 -0
  133. package/utils/AbstractMigrator.js +305 -0
  134. package/utils/AbstractSchemaGenerator.d.ts +5 -5
  135. package/utils/AbstractSchemaGenerator.js +28 -17
  136. package/utils/AsyncContext.d.ts +6 -0
  137. package/utils/AsyncContext.js +42 -0
  138. package/utils/Configuration.d.ts +795 -211
  139. package/utils/Configuration.js +153 -194
  140. package/utils/ConfigurationLoader.d.ts +1 -52
  141. package/utils/ConfigurationLoader.js +1 -330
  142. package/utils/Cursor.d.ts +0 -3
  143. package/utils/Cursor.js +24 -11
  144. package/utils/DataloaderUtils.d.ts +10 -5
  145. package/utils/DataloaderUtils.js +29 -12
  146. package/utils/EntityComparator.d.ts +16 -9
  147. package/utils/EntityComparator.js +154 -56
  148. package/utils/QueryHelper.d.ts +18 -6
  149. package/utils/QueryHelper.js +76 -23
  150. package/utils/RawQueryFragment.d.ts +28 -34
  151. package/utils/RawQueryFragment.js +35 -71
  152. package/utils/RequestContext.js +2 -2
  153. package/utils/TransactionContext.js +2 -2
  154. package/utils/TransactionManager.js +9 -6
  155. package/utils/Utils.d.ts +15 -126
  156. package/utils/Utils.js +80 -382
  157. package/utils/clone.js +8 -23
  158. package/utils/env-vars.d.ts +7 -0
  159. package/utils/env-vars.js +97 -0
  160. package/utils/fs-utils.d.ts +34 -0
  161. package/utils/fs-utils.js +196 -0
  162. package/utils/index.d.ts +1 -3
  163. package/utils/index.js +1 -3
  164. package/utils/upsert-utils.d.ts +9 -4
  165. package/utils/upsert-utils.js +46 -3
  166. package/decorators/Check.d.ts +0 -3
  167. package/decorators/Check.js +0 -13
  168. package/decorators/CreateRequestContext.d.ts +0 -3
  169. package/decorators/CreateRequestContext.js +0 -32
  170. package/decorators/Embeddable.d.ts +0 -8
  171. package/decorators/Embeddable.js +0 -11
  172. package/decorators/Embedded.d.ts +0 -12
  173. package/decorators/Embedded.js +0 -18
  174. package/decorators/Entity.d.ts +0 -33
  175. package/decorators/Entity.js +0 -12
  176. package/decorators/Enum.d.ts +0 -9
  177. package/decorators/Enum.js +0 -16
  178. package/decorators/Filter.d.ts +0 -2
  179. package/decorators/Filter.js +0 -8
  180. package/decorators/Formula.d.ts +0 -4
  181. package/decorators/Formula.js +0 -15
  182. package/decorators/Indexed.d.ts +0 -19
  183. package/decorators/Indexed.js +0 -20
  184. package/decorators/ManyToMany.d.ts +0 -42
  185. package/decorators/ManyToMany.js +0 -14
  186. package/decorators/ManyToOne.d.ts +0 -34
  187. package/decorators/ManyToOne.js +0 -14
  188. package/decorators/OneToMany.d.ts +0 -28
  189. package/decorators/OneToMany.js +0 -17
  190. package/decorators/OneToOne.d.ts +0 -28
  191. package/decorators/OneToOne.js +0 -7
  192. package/decorators/PrimaryKey.d.ts +0 -8
  193. package/decorators/PrimaryKey.js +0 -20
  194. package/decorators/Property.d.ts +0 -250
  195. package/decorators/Property.js +0 -32
  196. package/decorators/Transactional.d.ts +0 -14
  197. package/decorators/Transactional.js +0 -28
  198. package/decorators/hooks.d.ts +0 -16
  199. package/decorators/hooks.js +0 -47
  200. package/decorators/index.d.ts +0 -17
  201. package/decorators/index.js +0 -17
  202. package/entity/ArrayCollection.d.ts +0 -118
  203. package/entity/ArrayCollection.js +0 -407
  204. package/entity/EntityValidator.d.ts +0 -19
  205. package/entity/EntityValidator.js +0 -150
  206. package/metadata/ReflectMetadataProvider.d.ts +0 -8
  207. package/metadata/ReflectMetadataProvider.js +0 -44
  208. package/utils/resolveContextProvider.d.ts +0 -10
  209. package/utils/resolveContextProvider.js +0 -28
@@ -1,26 +1,26 @@
1
- import type { AnyEntity, ConnectionType, Dictionary, EntityProperty, FilterQuery, PopulateOptions } from '../typings.js';
1
+ import type { AnyEntity, AutoPath, ConnectionType, EntityName, EntityProperty, FilterQuery, PopulateOptions } from '../typings.js';
2
2
  import type { EntityManager } from '../EntityManager.js';
3
3
  import { LoadStrategy, type LockMode, type PopulateHint, PopulatePath, type QueryOrderMap } from '../enums.js';
4
- import type { EntityField } from '../drivers/IDatabaseDriver.js';
4
+ import type { FilterOptions } from '../drivers/IDatabaseDriver.js';
5
5
  import type { LoggingOptions } from '../logging/Logger.js';
6
- export type EntityLoaderOptions<Entity, Fields extends string = PopulatePath.ALL, Excludes extends string = never> = {
6
+ export interface EntityLoaderOptions<Entity, Fields extends string = PopulatePath.ALL, Excludes extends string = never> {
7
+ fields?: readonly AutoPath<Entity, Fields, `${PopulatePath.ALL}`>[];
8
+ exclude?: readonly AutoPath<Entity, Excludes>[];
7
9
  where?: FilterQuery<Entity>;
8
10
  populateWhere?: PopulateHint | `${PopulateHint}`;
9
- fields?: readonly EntityField<Entity, Fields>[];
10
- exclude?: readonly EntityField<Entity, Excludes>[];
11
11
  orderBy?: QueryOrderMap<Entity> | QueryOrderMap<Entity>[];
12
12
  refresh?: boolean;
13
13
  validate?: boolean;
14
14
  lookup?: boolean;
15
15
  convertCustomTypes?: boolean;
16
16
  ignoreLazyScalarProperties?: boolean;
17
- filters?: Dictionary<boolean | Dictionary> | string[] | boolean;
18
- strategy?: LoadStrategy;
17
+ filters?: FilterOptions;
18
+ strategy?: LoadStrategy | `${LoadStrategy}`;
19
19
  lockMode?: Exclude<LockMode, LockMode.OPTIMISTIC>;
20
20
  schema?: string;
21
21
  connectionType?: ConnectionType;
22
22
  logging?: LoggingOptions;
23
- };
23
+ }
24
24
  export declare class EntityLoader {
25
25
  private readonly em;
26
26
  private readonly metadata;
@@ -30,8 +30,8 @@ export declare class EntityLoader {
30
30
  * Loads specified relations in batch.
31
31
  * This will execute one query for each relation, that will populate it on all the specified entities.
32
32
  */
33
- populate<Entity extends object, Fields extends string = PopulatePath.ALL>(entityName: string, entities: Entity[], populate: PopulateOptions<Entity>[] | boolean, options: EntityLoaderOptions<Entity, Fields>): Promise<void>;
34
- normalizePopulate<Entity>(entityName: string, populate: (PopulateOptions<Entity> | boolean)[] | PopulateOptions<Entity> | boolean, strategy?: LoadStrategy, lookup?: boolean): PopulateOptions<Entity>[];
33
+ populate<Entity extends object, Fields extends string = PopulatePath.ALL>(entityName: EntityName<Entity>, entities: Entity[], populate: PopulateOptions<Entity>[] | boolean, options: EntityLoaderOptions<Entity, Fields>): Promise<void>;
34
+ normalizePopulate<Entity>(entityName: EntityName<Entity>, populate: (PopulateOptions<Entity> | boolean)[] | PopulateOptions<Entity> | boolean, strategy?: LoadStrategy, lookup?: boolean, exclude?: string[]): PopulateOptions<Entity>[];
35
35
  private setSerializationContext;
36
36
  /**
37
37
  * Merge multiple populates for the same entity with different children. Also skips `*` fields, those can come from
@@ -43,6 +43,7 @@ export declare class EntityLoader {
43
43
  */
44
44
  private populateMany;
45
45
  private populateScalar;
46
+ private populatePolymorphic;
46
47
  private initializeCollections;
47
48
  private initializeOneToMany;
48
49
  private initializeManyToMany;
@@ -4,8 +4,8 @@ import { ValidationError } from '../errors.js';
4
4
  import { LoadStrategy, PopulatePath, ReferenceKind, } from '../enums.js';
5
5
  import { Reference } from './Reference.js';
6
6
  import { helper } from './wrap.js';
7
- import { raw, RawQueryFragment } from '../utils/RawQueryFragment.js';
8
7
  import { expandDotPaths } from './utils.js';
8
+ import { Raw } from '../utils/RawQueryFragment.js';
9
9
  export class EntityLoader {
10
10
  em;
11
11
  metadata;
@@ -32,7 +32,6 @@ export class EntityLoader {
32
32
  const visited = options.visited ??= new Set();
33
33
  options.where ??= {};
34
34
  options.orderBy ??= {};
35
- options.filters ??= {};
36
35
  options.lookup ??= true;
37
36
  options.validate ??= true;
38
37
  options.refresh ??= false;
@@ -40,9 +39,9 @@ export class EntityLoader {
40
39
  if (references.length > 0) {
41
40
  await this.populateScalar(meta, references, { ...options, populateWhere: undefined });
42
41
  }
43
- populate = this.normalizePopulate(entityName, populate, options.strategy, options.lookup);
42
+ populate = this.normalizePopulate(entityName, populate, options.strategy, options.lookup, options.exclude);
44
43
  const invalid = populate.find(({ field }) => !this.em.canPopulate(entityName, field));
45
- /* v8 ignore next 3 */
44
+ /* v8 ignore next */
46
45
  if (options.validate && invalid) {
47
46
  throw ValidationError.invalidPropertyName(entityName, invalid.field);
48
47
  }
@@ -57,7 +56,7 @@ export class EntityLoader {
57
56
  visited.delete(entity);
58
57
  }
59
58
  }
60
- normalizePopulate(entityName, populate, strategy, lookup = true) {
59
+ normalizePopulate(entityName, populate, strategy, lookup = true, exclude) {
61
60
  const meta = this.metadata.find(entityName);
62
61
  let normalized = Utils.asArray(populate).map(field => {
63
62
  return typeof field === 'boolean' || field.field === PopulatePath.ALL ? { all: !!field, field: meta.primaryKeys[0] } : field;
@@ -68,7 +67,7 @@ export class EntityLoader {
68
67
  // convert nested `field` with dot syntax to PopulateOptions with `children` array
69
68
  expandDotPaths(meta, normalized, true);
70
69
  if (lookup && populate !== false) {
71
- normalized = this.lookupEagerLoadedRelationships(entityName, normalized, strategy);
70
+ normalized = this.lookupEagerLoadedRelationships(entityName, normalized, strategy, '', [], exclude);
72
71
  // convert nested `field` with dot syntax produced by eager relations
73
72
  expandDotPaths(meta, normalized, true);
74
73
  }
@@ -142,15 +141,20 @@ export class EntityLoader {
142
141
  .flatMap(orderBy => orderBy[prop.name]);
143
142
  const where = await this.extractChildCondition(options, prop);
144
143
  if (prop.kind === ReferenceKind.MANY_TO_MANY && this.driver.getPlatform().usesPivotTable()) {
145
- const res = await this.findChildrenFromPivotTable(filtered, prop, options, innerOrderBy, populate, !!ref);
144
+ const pivotOrderBy = QueryHelper.mergeOrderBy(innerOrderBy, prop.orderBy, prop.targetMeta?.orderBy);
145
+ const res = await this.findChildrenFromPivotTable(filtered, prop, options, pivotOrderBy, populate, !!ref);
146
146
  return Utils.flatten(res);
147
147
  }
148
+ if (prop.polymorphic && prop.polymorphTargets) {
149
+ return this.populatePolymorphic(entities, prop, options, !!ref);
150
+ }
148
151
  const { items, partial } = await this.findChildren(options.filtered ?? entities, prop, populate, {
149
152
  ...options,
150
153
  where,
151
154
  orderBy: innerOrderBy,
152
155
  }, !!(ref || prop.mapToPk));
153
- this.initializeCollections(filtered, prop, field, items, innerOrderBy.length > 0, partial);
156
+ const customOrder = innerOrderBy.length > 0 || !!prop.orderBy || !!prop.targetMeta?.orderBy;
157
+ this.initializeCollections(filtered, prop, field, items, customOrder, partial);
154
158
  return items;
155
159
  }
156
160
  async populateScalar(meta, filtered, options) {
@@ -158,12 +162,70 @@ export class EntityLoader {
158
162
  const ids = Utils.unique(filtered.map(e => Utils.getPrimaryKeyValues(e, meta, true)));
159
163
  const where = this.mergePrimaryCondition(ids, pk, options, meta, this.metadata, this.driver.getPlatform());
160
164
  const { filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, fields } = options;
161
- await this.em.find(meta.className, where, {
165
+ await this.em.find(meta.class, where, {
162
166
  filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging,
163
167
  fields: fields,
164
168
  populate: [],
165
169
  });
166
170
  }
171
+ async populatePolymorphic(entities, prop, options, ref) {
172
+ const ownerMeta = this.metadata.get(entities[0].constructor);
173
+ // Separate entities: those with loaded refs vs those needing FK load
174
+ const toPopulate = [];
175
+ const needsFkLoad = [];
176
+ for (const entity of entities) {
177
+ const refValue = entity[prop.name];
178
+ if (refValue && helper(refValue).hasPrimaryKey()) {
179
+ if ((ref && !options.refresh) || // :ref hint - already have reference
180
+ (!ref && helper(refValue).__initialized && !options.refresh) // already loaded
181
+ ) {
182
+ continue;
183
+ }
184
+ toPopulate.push(entity);
185
+ }
186
+ else if (refValue == null && !helper(entity).__loadedProperties.has(prop.name)) {
187
+ // FK columns weren't loaded (partial loading) — need to re-fetch them.
188
+ // If the property IS in __loadedProperties, the FK was loaded and is genuinely null.
189
+ needsFkLoad.push(entity);
190
+ }
191
+ }
192
+ // Load FK columns using populateScalar pattern
193
+ if (needsFkLoad.length > 0) {
194
+ await this.populateScalar(ownerMeta, needsFkLoad, {
195
+ ...options,
196
+ fields: [...ownerMeta.primaryKeys, prop.name],
197
+ });
198
+ // After loading FKs, add to toPopulate if not using :ref hint
199
+ if (!ref) {
200
+ for (const entity of needsFkLoad) {
201
+ const refValue = entity[prop.name];
202
+ if (refValue && helper(refValue).hasPrimaryKey()) {
203
+ toPopulate.push(entity);
204
+ }
205
+ }
206
+ }
207
+ }
208
+ if (toPopulate.length === 0) {
209
+ return [];
210
+ }
211
+ // Group references by target class for batch loading
212
+ const groups = new Map();
213
+ for (const entity of toPopulate) {
214
+ const refValue = Reference.unwrapReference(entity[prop.name]);
215
+ const discriminator = QueryHelper.findDiscriminatorValue(prop.discriminatorMap, helper(refValue).__meta.class);
216
+ const group = groups.get(discriminator) ?? [];
217
+ group.push(refValue);
218
+ groups.set(discriminator, group);
219
+ }
220
+ // Load each group concurrently - identity map handles merging with existing references
221
+ const allItems = [];
222
+ await Promise.all([...groups].map(async ([discriminator, children]) => {
223
+ const targetMeta = this.metadata.find(prop.discriminatorMap[discriminator]);
224
+ await this.populateScalar(targetMeta, children, options);
225
+ allItems.push(...children);
226
+ }));
227
+ return allItems;
228
+ }
167
229
  initializeCollections(filtered, prop, field, children, customOrder, partial) {
168
230
  if (prop.kind === ReferenceKind.ONE_TO_MANY) {
169
231
  this.initializeOneToMany(filtered, children, prop, field, partial);
@@ -182,7 +244,7 @@ export class EntityLoader {
182
244
  for (const child of children) {
183
245
  const pk = child.__helper.__data[prop.mappedBy] ?? child[prop.mappedBy];
184
246
  if (pk) {
185
- const key = helper(mapToPk ? this.em.getReference(prop.type, pk) : pk).getSerializedPrimaryKey();
247
+ const key = helper(mapToPk ? this.em.getReference(prop.targetMeta.class, pk) : pk).getSerializedPrimaryKey();
186
248
  map[key]?.push(child);
187
249
  }
188
250
  }
@@ -210,13 +272,23 @@ export class EntityLoader {
210
272
  }
211
273
  }
212
274
  async findChildren(entities, prop, populate, options, ref) {
213
- const children = this.getChildReferences(entities, prop, options, ref);
275
+ const children = Utils.unique(this.getChildReferences(entities, prop, options, ref));
214
276
  const meta = prop.targetMeta;
215
- let fk = Utils.getPrimaryKeyHash(meta.primaryKeys);
277
+ // When targetKey is set, use it for FK lookup instead of the PK
278
+ let fk = prop.targetKey ?? Utils.getPrimaryKeyHash(meta.primaryKeys);
216
279
  let schema = options.schema;
217
280
  const partial = !Utils.isEmpty(prop.where) || !Utils.isEmpty(options.where);
281
+ let polymorphicOwnerProp;
218
282
  if (prop.kind === ReferenceKind.ONE_TO_MANY || (prop.kind === ReferenceKind.MANY_TO_MANY && !prop.owner)) {
219
- fk = meta.properties[prop.mappedBy].name;
283
+ const ownerProp = meta.properties[prop.mappedBy];
284
+ if (ownerProp.polymorphic && ownerProp.fieldNames.length >= 2) {
285
+ const idColumns = ownerProp.fieldNames.slice(1);
286
+ fk = idColumns.length === 1 ? idColumns[0] : idColumns;
287
+ polymorphicOwnerProp = ownerProp;
288
+ }
289
+ else {
290
+ fk = ownerProp.name;
291
+ }
220
292
  }
221
293
  if (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && !ref) {
222
294
  children.length = 0;
@@ -229,8 +301,24 @@ export class EntityLoader {
229
301
  if (!schema && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
230
302
  schema = children.find(e => e.__helper.__schema)?.__helper.__schema;
231
303
  }
232
- const ids = Utils.unique(children.map(e => e.__helper.getPrimaryKey()));
233
- let where = this.mergePrimaryCondition(ids, fk, options, meta, this.metadata, this.driver.getPlatform());
304
+ const ids = Utils.unique(children.map(e => prop.targetKey ? e[prop.targetKey] : e.__helper.getPrimaryKey()));
305
+ let where;
306
+ if (polymorphicOwnerProp && Array.isArray(fk)) {
307
+ const conditions = ids.map(id => {
308
+ const pkValues = Object.values(id);
309
+ return Object.fromEntries(fk.map((col, idx) => [col, pkValues[idx]]));
310
+ });
311
+ where = (conditions.length === 1 ? conditions[0] : { $or: conditions });
312
+ }
313
+ else {
314
+ where = this.mergePrimaryCondition(ids, fk, options, meta, this.metadata, this.driver.getPlatform());
315
+ }
316
+ if (polymorphicOwnerProp) {
317
+ const parentMeta = this.metadata.find(entities[0].constructor);
318
+ const discriminatorValue = QueryHelper.findDiscriminatorValue(polymorphicOwnerProp.discriminatorMap, parentMeta.class) ?? parentMeta.tableName;
319
+ const discriminatorColumn = polymorphicOwnerProp.fieldNames[0];
320
+ where = { $and: [where, { [discriminatorColumn]: discriminatorValue }] };
321
+ }
234
322
  const fields = this.buildFields(options.fields, prop, ref);
235
323
  /* eslint-disable prefer-const */
236
324
  let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere = 'infer', connectionType, logging, } = options;
@@ -238,28 +326,11 @@ export class EntityLoader {
238
326
  if (typeof populateWhere === 'object') {
239
327
  populateWhere = await this.extractChildCondition({ where: populateWhere }, prop);
240
328
  }
241
- if (!Utils.isEmpty(prop.where)) {
329
+ if (!Utils.isEmpty(prop.where) || Raw.hasObjectFragments(prop.where)) {
242
330
  where = { $and: [where, prop.where] };
243
331
  }
244
- const propOrderBy = [];
245
- if (prop.orderBy) {
246
- for (const item of Utils.asArray(prop.orderBy)) {
247
- for (const field of Utils.keys(item)) {
248
- const rawField = RawQueryFragment.getKnownFragment(field, false);
249
- if (rawField) {
250
- const raw2 = raw(rawField.sql, rawField.params);
251
- propOrderBy.push({ [raw2.toString()]: item[field] });
252
- continue;
253
- }
254
- propOrderBy.push({ [field]: item[field] });
255
- }
256
- }
257
- }
258
- const orderBy = [...Utils.asArray(options.orderBy), ...propOrderBy].filter((order, idx, array) => {
259
- // skip consecutive ordering with the same key to get around mongo issues
260
- return idx === 0 || !Utils.equals(Object.keys(array[idx - 1]), Object.keys(order));
261
- });
262
- const items = await this.em.find(prop.type, where, {
332
+ const orderBy = QueryHelper.mergeOrderBy(options.orderBy, prop.orderBy);
333
+ const items = await this.em.find(meta.class, where, {
263
334
  filters, convertCustomTypes, lockMode, populateWhere, logging,
264
335
  orderBy,
265
336
  populate: populate.children ?? populate.all ?? [],
@@ -270,6 +341,49 @@ export class EntityLoader {
270
341
  // @ts-ignore not a public option, will be propagated to the populate call
271
342
  visited: options.visited,
272
343
  });
344
+ // For targetKey relations, wire up loaded entities to parent references
345
+ // This is needed because the references were created under alternate key,
346
+ // but loaded entities are stored under PK, so they don't automatically merge
347
+ if (prop.targetKey && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
348
+ const itemsByKey = new Map();
349
+ for (const item of items) {
350
+ itemsByKey.set('' + item[prop.targetKey], item);
351
+ }
352
+ for (const entity of entities) {
353
+ const ref = entity[prop.name];
354
+ /* v8 ignore next */
355
+ if (!ref) {
356
+ continue;
357
+ }
358
+ const keyValue = '' + (Reference.isReference(ref) ? ref.unwrap()[prop.targetKey] : ref[prop.targetKey]);
359
+ const loadedItem = itemsByKey.get(keyValue);
360
+ if (loadedItem) {
361
+ entity[prop.name] = (Reference.isReference(ref) ? Reference.create(loadedItem) : loadedItem);
362
+ }
363
+ }
364
+ }
365
+ if ([ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && items.length !== children.length) {
366
+ const nullVal = this.em.config.get('forceUndefined') ? undefined : null;
367
+ const itemsMap = new Set();
368
+ const childrenMap = new Set();
369
+ // Use targetKey value if set, otherwise use serialized PK
370
+ const getKey = (e) => prop.targetKey ? '' + e[prop.targetKey] : helper(e).getSerializedPrimaryKey();
371
+ for (const item of items) {
372
+ /* v8 ignore next */
373
+ itemsMap.add(getKey(item));
374
+ }
375
+ for (const child of children) {
376
+ childrenMap.add(getKey(child));
377
+ }
378
+ for (const entity of entities) {
379
+ const ref = entity[prop.name] ?? {};
380
+ const key = helper(ref) ? getKey(ref) : undefined;
381
+ if (key && childrenMap.has(key) && !itemsMap.has(key)) {
382
+ entity[prop.name] = nullVal;
383
+ helper(entity).__originalEntityData[prop.name] = null;
384
+ }
385
+ }
386
+ }
273
387
  for (const item of items) {
274
388
  if (ref && !helper(item).__onLoadFired) {
275
389
  helper(item).__initialized = false;
@@ -280,7 +394,7 @@ export class EntityLoader {
280
394
  return { items, partial };
281
395
  }
282
396
  mergePrimaryCondition(ids, pk, options, meta, metadata, platform) {
283
- const cond1 = QueryHelper.processWhere({ where: { [pk]: { $in: ids } }, entityName: meta.className, metadata, platform, convertCustomTypes: !options.convertCustomTypes });
397
+ const cond1 = QueryHelper.processWhere({ where: { [pk]: { $in: ids } }, entityName: meta.class, metadata, platform, convertCustomTypes: !options.convertCustomTypes });
284
398
  const where = { ...options.where };
285
399
  Utils.dropUndefinedProperties(where);
286
400
  return where[pk]
@@ -293,6 +407,7 @@ export class EntityLoader {
293
407
  if (prop.kind === ReferenceKind.SCALAR && !prop.lazy) {
294
408
  return;
295
409
  }
410
+ options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
296
411
  const populated = await this.populateMany(entityName, entities, populate, options);
297
412
  if (!populate.children && !populate.all) {
298
413
  return;
@@ -331,26 +446,42 @@ export class EntityLoader {
331
446
  for (const entity of entities) {
332
447
  visited.add(entity);
333
448
  }
334
- await this.populate(prop.type, unique, populate.children ?? populate.all, {
335
- where: await this.extractChildCondition(options, prop, false),
336
- orderBy: innerOrderBy,
337
- fields,
338
- exclude,
339
- validate: false,
340
- lookup: false,
341
- filters,
342
- ignoreLazyScalarProperties,
343
- populateWhere,
344
- connectionType,
345
- logging,
346
- schema,
347
- // @ts-ignore not a public option, will be propagated to the populate call
348
- refresh: refresh && !filtered.every(item => options.visited.has(item)),
349
- // @ts-ignore not a public option, will be propagated to the populate call
350
- visited: options.visited,
351
- // @ts-ignore not a public option
352
- filtered,
353
- });
449
+ if (!prop.targetMeta) {
450
+ return;
451
+ }
452
+ const populateChildren = async (targetMeta, items) => {
453
+ await this.populate(targetMeta.class, items, populate.children ?? populate.all, {
454
+ where: await this.extractChildCondition(options, prop, false),
455
+ orderBy: innerOrderBy,
456
+ fields,
457
+ exclude,
458
+ validate: false,
459
+ lookup: false,
460
+ filters,
461
+ ignoreLazyScalarProperties,
462
+ populateWhere,
463
+ connectionType,
464
+ logging,
465
+ schema,
466
+ // @ts-ignore not a public option, will be propagated to the populate call
467
+ refresh: refresh && !filtered.every(item => options.visited.has(item)),
468
+ // @ts-ignore not a public option, will be propagated to the populate call
469
+ visited: options.visited,
470
+ // @ts-ignore not a public option
471
+ filtered,
472
+ });
473
+ };
474
+ if (prop.polymorphic && prop.polymorphTargets) {
475
+ await Promise.all(prop.polymorphTargets.map(async (targetMeta) => {
476
+ const targetChildren = unique.filter(child => helper(child).__meta.className === targetMeta.className);
477
+ if (targetChildren.length > 0) {
478
+ await populateChildren(targetMeta, targetChildren);
479
+ }
480
+ }));
481
+ }
482
+ else {
483
+ await populateChildren(prop.targetMeta, unique);
484
+ }
354
485
  }
355
486
  /** @internal */
356
487
  async findChildrenFromPivotTable(filtered, prop, options, orderBy, populate, pivotJoin) {
@@ -359,11 +490,9 @@ export class EntityLoader {
359
490
  let where = await this.extractChildCondition(options, prop, true);
360
491
  const fields = this.buildFields(options.fields, prop);
361
492
  const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
362
- const options2 = { ...options };
363
- delete options2.limit;
364
- delete options2.offset;
365
- options2.fields = fields;
366
- options2.exclude = exclude;
493
+ const populateFilter = options.populateFilter?.[prop.name];
494
+ const options2 = { ...options, fields, exclude, populateFilter };
495
+ ['limit', 'offset', 'first', 'last', 'before', 'after', 'overfetch'].forEach(prop => delete options2[prop]);
367
496
  options2.populate = (populate?.children ?? []);
368
497
  if (prop.customType) {
369
498
  ids.forEach((id, idx) => ids[idx] = QueryHelper.processCustomType(prop, id, this.driver.getPlatform()));
@@ -376,12 +505,12 @@ export class EntityLoader {
376
505
  for (const entity of filtered) {
377
506
  const items = map[entity.__helper.getSerializedPrimaryKey()].map(item => {
378
507
  if (pivotJoin) {
379
- return this.em.getReference(prop.type, item, {
508
+ return this.em.getReference(prop.targetMeta.class, item, {
380
509
  convertCustomTypes: true,
381
510
  schema: options.schema ?? this.em.config.get('schema'),
382
511
  });
383
512
  }
384
- const entity = this.em.getEntityFactory().create(prop.type, item, {
513
+ const entity = this.em.getEntityFactory().create(prop.targetMeta.class, item, {
385
514
  refresh,
386
515
  merge: true,
387
516
  convertCustomTypes: true,
@@ -397,16 +526,13 @@ export class EntityLoader {
397
526
  async extractChildCondition(options, prop, filters = false) {
398
527
  const where = options.where;
399
528
  const subCond = Utils.isPlainObject(where[prop.name]) ? where[prop.name] : {};
400
- const meta2 = this.metadata.find(prop.type);
401
- if (!meta2) {
402
- return {};
403
- }
529
+ const meta2 = prop.targetMeta;
404
530
  const pk = Utils.getPrimaryKeyHash(meta2.primaryKeys);
405
531
  ['$and', '$or'].forEach(op => {
406
532
  if (where[op]) {
407
533
  const child = where[op]
408
534
  .map((cond) => cond[prop.name])
409
- .filter((sub) => sub != null && !(Utils.isPlainObject(sub) && Object.keys(sub).every(key => Utils.isOperator(key, false))))
535
+ .filter((sub) => sub != null && !(Utils.isPlainObject(sub) && Utils.getObjectQueryKeys(sub).every(key => Utils.isOperator(key, false))))
410
536
  .map((cond) => {
411
537
  if (Utils.isPrimaryKey(cond)) {
412
538
  return { [pk]: cond };
@@ -427,7 +553,7 @@ export class EntityLoader {
427
553
  });
428
554
  }
429
555
  if (filters) {
430
- return this.em.applyFilters(prop.type, subCond, options.filters, 'read', options);
556
+ return this.em.applyFilters(meta2.class, subCond, options.filters, 'read', options);
431
557
  }
432
558
  return subCond;
433
559
  }
@@ -445,23 +571,25 @@ export class EntityLoader {
445
571
  const parts = f.toString().split('.');
446
572
  const propName = parts.shift();
447
573
  const childPropName = parts.join('.');
448
- /* v8 ignore next 3 */
574
+ /* v8 ignore next */
449
575
  if (propName === prop.name) {
450
576
  ret.push(childPropName);
451
577
  }
452
578
  }
453
579
  return ret;
454
580
  }, []);
455
- if (ret.length === 0) {
456
- return undefined;
457
- }
458
581
  // we need to automatically select the FKs too, e.g. for 1:m relations to be able to wire them with the items
459
582
  if (prop.kind === ReferenceKind.ONE_TO_MANY || prop.kind === ReferenceKind.MANY_TO_MANY) {
460
583
  const owner = prop.targetMeta.properties[prop.mappedBy];
461
- if (owner && !ret.includes(owner.name)) {
584
+ // when the owning FK is lazy, we need to explicitly select it even without user-provided fields,
585
+ // otherwise the driver will exclude it and we won't be able to map children to their parent collections
586
+ if (owner && !ret.includes(owner.name) && (ret.length > 0 || owner.lazy)) {
462
587
  ret.push(owner.name);
463
588
  }
464
589
  }
590
+ if (ret.length === 0) {
591
+ return undefined;
592
+ }
465
593
  return ret;
466
594
  }
467
595
  getChildReferences(entities, prop, options, ref) {
@@ -496,7 +624,7 @@ export class EntityLoader {
496
624
  return wrapped.__loadedProperties.has(field);
497
625
  }
498
626
  const [f, ...r] = field.split('.');
499
- /* v8 ignore next 3 */
627
+ /* v8 ignore next */
500
628
  if (!wrapped.__loadedProperties.has(f) || !wrapped.__meta.properties[f]?.targetMeta) {
501
629
  return false;
502
630
  }
@@ -529,7 +657,7 @@ export class EntityLoader {
529
657
  .map(e => Reference.unwrapReference(e[field]));
530
658
  }
531
659
  filterByReferences(entities, field, refresh) {
532
- /* v8 ignore next 3 */
660
+ /* v8 ignore next */
533
661
  if (refresh) {
534
662
  return entities;
535
663
  }
@@ -555,33 +683,36 @@ export class EntityLoader {
555
683
  }
556
684
  return `${this.getRelationName(meta, meta.properties[prop.embedded[0]])}.${prop.embedded[1]}`;
557
685
  }
558
- lookupEagerLoadedRelationships(entityName, populate, strategy, prefix = '', visited = []) {
686
+ lookupEagerLoadedRelationships(entityName, populate, strategy, prefix = '', visited = [], exclude) {
559
687
  const meta = this.metadata.find(entityName);
560
688
  if (!meta && !prefix) {
561
689
  return populate;
562
690
  }
563
- if (visited.includes(entityName) || !meta) {
691
+ if (!meta || visited.includes(meta)) {
564
692
  return [];
565
693
  }
566
- visited.push(entityName);
694
+ visited.push(meta);
567
695
  const ret = prefix === '' ? [...populate] : [];
568
696
  meta.relations
569
697
  .filter(prop => {
698
+ const field = this.getRelationName(meta, prop);
699
+ const prefixed = prefix ? `${prefix}.${field}` : field;
700
+ const isExcluded = exclude?.includes(prefixed);
570
701
  const eager = prop.eager && !populate.some(p => p.field === `${prop.name}:ref`);
571
702
  const populated = populate.some(p => p.field === prop.name);
572
703
  const disabled = populate.some(p => p.field === prop.name && p.all === false);
573
- return !disabled && (eager || populated);
704
+ return !disabled && !isExcluded && (eager || populated);
574
705
  })
575
706
  .forEach(prop => {
576
707
  const field = this.getRelationName(meta, prop);
577
708
  const prefixed = prefix ? `${prefix}.${field}` : field;
578
709
  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());
710
+ const nested = this.lookupEagerLoadedRelationships(prop.targetMeta.class, nestedPopulate, strategy, prefixed, visited.slice(), exclude);
580
711
  if (nested.length > 0) {
581
712
  ret.push(...nested);
582
713
  }
583
714
  else {
584
- 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;
585
716
  ret.push({
586
717
  field: prefixed,
587
718
  // enforce select-in strategy for self-referencing relations