@mikro-orm/core 7.0.0-dev.33 → 7.0.0-dev.331

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 (218) hide show
  1. package/EntityManager.d.ts +70 -75
  2. package/EntityManager.js +487 -402
  3. package/MikroORM.d.ts +45 -38
  4. package/MikroORM.js +123 -156
  5. package/README.md +7 -4
  6. package/cache/FileCacheAdapter.d.ts +2 -7
  7. package/cache/FileCacheAdapter.js +35 -30
  8. package/cache/GeneratedCacheAdapter.d.ts +1 -2
  9. package/cache/GeneratedCacheAdapter.js +6 -8
  10. package/cache/MemoryCacheAdapter.d.ts +1 -2
  11. package/cache/MemoryCacheAdapter.js +8 -8
  12. package/cache/index.d.ts +1 -2
  13. package/cache/index.js +0 -2
  14. package/connections/Connection.d.ts +12 -5
  15. package/connections/Connection.js +37 -15
  16. package/drivers/DatabaseDriver.d.ts +25 -18
  17. package/drivers/DatabaseDriver.js +144 -45
  18. package/drivers/IDatabaseDriver.d.ts +118 -23
  19. package/entity/BaseEntity.d.ts +63 -4
  20. package/entity/BaseEntity.js +0 -3
  21. package/entity/Collection.d.ts +95 -31
  22. package/entity/Collection.js +487 -139
  23. package/entity/EntityAssigner.js +37 -25
  24. package/entity/EntityFactory.d.ts +8 -9
  25. package/entity/EntityFactory.js +152 -100
  26. package/entity/EntityHelper.d.ts +2 -2
  27. package/entity/EntityHelper.js +69 -27
  28. package/entity/EntityLoader.d.ts +12 -13
  29. package/entity/EntityLoader.js +286 -125
  30. package/entity/EntityRepository.d.ts +28 -8
  31. package/entity/EntityRepository.js +8 -2
  32. package/entity/PolymorphicRef.d.ts +12 -0
  33. package/entity/PolymorphicRef.js +18 -0
  34. package/entity/Reference.d.ts +3 -8
  35. package/entity/Reference.js +62 -29
  36. package/entity/WrappedEntity.d.ts +7 -10
  37. package/entity/WrappedEntity.js +6 -7
  38. package/entity/defineEntity.d.ts +472 -313
  39. package/entity/defineEntity.js +134 -290
  40. package/entity/index.d.ts +2 -2
  41. package/entity/index.js +2 -2
  42. package/entity/utils.d.ts +6 -1
  43. package/entity/utils.js +46 -11
  44. package/entity/validators.d.ts +11 -0
  45. package/entity/validators.js +66 -0
  46. package/enums.d.ts +8 -6
  47. package/enums.js +13 -17
  48. package/errors.d.ts +26 -16
  49. package/errors.js +63 -31
  50. package/events/EventManager.d.ts +3 -5
  51. package/events/EventManager.js +37 -26
  52. package/events/index.d.ts +1 -1
  53. package/events/index.js +0 -1
  54. package/exceptions.js +9 -2
  55. package/hydration/Hydrator.js +1 -2
  56. package/hydration/ObjectHydrator.d.ts +5 -6
  57. package/hydration/ObjectHydrator.js +109 -50
  58. package/index.d.ts +2 -2
  59. package/index.js +1 -2
  60. package/logging/DefaultLogger.d.ts +1 -1
  61. package/logging/DefaultLogger.js +3 -4
  62. package/logging/SimpleLogger.d.ts +1 -1
  63. package/logging/colors.d.ts +1 -1
  64. package/logging/colors.js +4 -6
  65. package/logging/index.d.ts +2 -1
  66. package/logging/index.js +1 -1
  67. package/logging/inspect.d.ts +2 -0
  68. package/logging/inspect.js +11 -0
  69. package/metadata/EntitySchema.d.ts +47 -23
  70. package/metadata/EntitySchema.js +103 -34
  71. package/metadata/MetadataDiscovery.d.ts +65 -18
  72. package/metadata/MetadataDiscovery.js +940 -424
  73. package/metadata/MetadataProvider.d.ts +11 -2
  74. package/metadata/MetadataProvider.js +71 -2
  75. package/metadata/MetadataStorage.d.ts +11 -13
  76. package/metadata/MetadataStorage.js +79 -48
  77. package/metadata/MetadataValidator.d.ts +32 -9
  78. package/metadata/MetadataValidator.js +214 -44
  79. package/metadata/discover-entities.d.ts +5 -0
  80. package/metadata/discover-entities.js +40 -0
  81. package/metadata/index.d.ts +1 -1
  82. package/metadata/index.js +0 -1
  83. package/metadata/types.d.ts +577 -0
  84. package/metadata/types.js +1 -0
  85. package/naming-strategy/AbstractNamingStrategy.d.ts +16 -4
  86. package/naming-strategy/AbstractNamingStrategy.js +26 -5
  87. package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
  88. package/naming-strategy/EntityCaseNamingStrategy.js +7 -6
  89. package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
  90. package/naming-strategy/MongoNamingStrategy.js +6 -6
  91. package/naming-strategy/NamingStrategy.d.ts +28 -4
  92. package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
  93. package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
  94. package/naming-strategy/index.d.ts +1 -1
  95. package/naming-strategy/index.js +0 -1
  96. package/not-supported.d.ts +2 -0
  97. package/not-supported.js +8 -0
  98. package/package.json +47 -36
  99. package/platforms/ExceptionConverter.js +1 -1
  100. package/platforms/Platform.d.ts +33 -15
  101. package/platforms/Platform.js +125 -69
  102. package/serialization/EntitySerializer.d.ts +6 -3
  103. package/serialization/EntitySerializer.js +54 -30
  104. package/serialization/EntityTransformer.js +37 -22
  105. package/serialization/SerializationContext.d.ts +10 -14
  106. package/serialization/SerializationContext.js +24 -19
  107. package/types/ArrayType.d.ts +1 -1
  108. package/types/ArrayType.js +2 -3
  109. package/types/BigIntType.js +1 -1
  110. package/types/BlobType.d.ts +0 -1
  111. package/types/BlobType.js +0 -3
  112. package/types/BooleanType.d.ts +1 -0
  113. package/types/BooleanType.js +3 -0
  114. package/types/DecimalType.js +2 -2
  115. package/types/DoubleType.js +1 -1
  116. package/types/EnumArrayType.js +1 -2
  117. package/types/JsonType.d.ts +1 -1
  118. package/types/JsonType.js +7 -2
  119. package/types/TinyIntType.js +1 -1
  120. package/types/Type.d.ts +2 -4
  121. package/types/Type.js +3 -3
  122. package/types/Uint8ArrayType.d.ts +0 -1
  123. package/types/Uint8ArrayType.js +1 -4
  124. package/types/UuidType.d.ts +2 -0
  125. package/types/UuidType.js +14 -2
  126. package/types/index.d.ts +3 -2
  127. package/typings.d.ts +427 -170
  128. package/typings.js +100 -45
  129. package/unit-of-work/ChangeSet.d.ts +4 -6
  130. package/unit-of-work/ChangeSet.js +8 -9
  131. package/unit-of-work/ChangeSetComputer.d.ts +2 -12
  132. package/unit-of-work/ChangeSetComputer.js +61 -38
  133. package/unit-of-work/ChangeSetPersister.d.ts +10 -17
  134. package/unit-of-work/ChangeSetPersister.js +136 -73
  135. package/unit-of-work/CommitOrderCalculator.d.ts +13 -14
  136. package/unit-of-work/CommitOrderCalculator.js +22 -20
  137. package/unit-of-work/IdentityMap.d.ts +12 -3
  138. package/unit-of-work/IdentityMap.js +51 -13
  139. package/unit-of-work/UnitOfWork.d.ts +39 -23
  140. package/unit-of-work/UnitOfWork.js +441 -246
  141. package/utils/AbstractMigrator.d.ts +101 -0
  142. package/utils/AbstractMigrator.js +303 -0
  143. package/utils/AbstractSchemaGenerator.d.ts +5 -5
  144. package/utils/AbstractSchemaGenerator.js +30 -18
  145. package/utils/AsyncContext.d.ts +6 -0
  146. package/utils/AsyncContext.js +42 -0
  147. package/utils/Configuration.d.ts +647 -185
  148. package/utils/Configuration.js +215 -252
  149. package/utils/ConfigurationLoader.d.ts +1 -52
  150. package/utils/ConfigurationLoader.js +1 -330
  151. package/utils/Cursor.d.ts +3 -6
  152. package/utils/Cursor.js +32 -17
  153. package/utils/DataloaderUtils.d.ts +10 -5
  154. package/utils/DataloaderUtils.js +42 -22
  155. package/utils/EntityComparator.d.ts +21 -21
  156. package/utils/EntityComparator.js +224 -118
  157. package/utils/QueryHelper.d.ts +34 -7
  158. package/utils/QueryHelper.js +183 -72
  159. package/utils/RawQueryFragment.d.ts +28 -34
  160. package/utils/RawQueryFragment.js +37 -72
  161. package/utils/RequestContext.js +2 -2
  162. package/utils/TransactionContext.js +2 -2
  163. package/utils/TransactionManager.js +11 -8
  164. package/utils/Utils.d.ts +16 -127
  165. package/utils/Utils.js +104 -402
  166. package/utils/clone.js +13 -23
  167. package/utils/env-vars.d.ts +7 -0
  168. package/utils/env-vars.js +98 -0
  169. package/utils/fs-utils.d.ts +20 -0
  170. package/utils/fs-utils.js +193 -0
  171. package/utils/index.d.ts +1 -3
  172. package/utils/index.js +1 -3
  173. package/utils/upsert-utils.d.ts +9 -4
  174. package/utils/upsert-utils.js +51 -5
  175. package/decorators/Check.d.ts +0 -3
  176. package/decorators/Check.js +0 -13
  177. package/decorators/CreateRequestContext.d.ts +0 -3
  178. package/decorators/CreateRequestContext.js +0 -32
  179. package/decorators/Embeddable.d.ts +0 -8
  180. package/decorators/Embeddable.js +0 -11
  181. package/decorators/Embedded.d.ts +0 -12
  182. package/decorators/Embedded.js +0 -18
  183. package/decorators/Entity.d.ts +0 -33
  184. package/decorators/Entity.js +0 -12
  185. package/decorators/Enum.d.ts +0 -9
  186. package/decorators/Enum.js +0 -16
  187. package/decorators/Filter.d.ts +0 -2
  188. package/decorators/Filter.js +0 -8
  189. package/decorators/Formula.d.ts +0 -4
  190. package/decorators/Formula.js +0 -15
  191. package/decorators/Indexed.d.ts +0 -19
  192. package/decorators/Indexed.js +0 -20
  193. package/decorators/ManyToMany.d.ts +0 -42
  194. package/decorators/ManyToMany.js +0 -14
  195. package/decorators/ManyToOne.d.ts +0 -34
  196. package/decorators/ManyToOne.js +0 -14
  197. package/decorators/OneToMany.d.ts +0 -28
  198. package/decorators/OneToMany.js +0 -17
  199. package/decorators/OneToOne.d.ts +0 -28
  200. package/decorators/OneToOne.js +0 -7
  201. package/decorators/PrimaryKey.d.ts +0 -8
  202. package/decorators/PrimaryKey.js +0 -20
  203. package/decorators/Property.d.ts +0 -250
  204. package/decorators/Property.js +0 -32
  205. package/decorators/Transactional.d.ts +0 -14
  206. package/decorators/Transactional.js +0 -28
  207. package/decorators/hooks.d.ts +0 -16
  208. package/decorators/hooks.js +0 -47
  209. package/decorators/index.d.ts +0 -17
  210. package/decorators/index.js +0 -17
  211. package/entity/ArrayCollection.d.ts +0 -118
  212. package/entity/ArrayCollection.js +0 -407
  213. package/entity/EntityValidator.d.ts +0 -19
  214. package/entity/EntityValidator.js +0 -150
  215. package/metadata/ReflectMetadataProvider.d.ts +0 -8
  216. package/metadata/ReflectMetadataProvider.js +0 -44
  217. package/utils/resolveContextProvider.d.ts +0 -10
  218. package/utils/resolveContextProvider.js +0 -28
@@ -4,16 +4,16 @@ 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
- em;
11
- metadata;
12
- driver;
10
+ #metadata;
11
+ #driver;
12
+ #em;
13
13
  constructor(em) {
14
- this.em = em;
15
- this.metadata = this.em.getMetadata();
16
- this.driver = this.em.getDriver();
14
+ this.#em = em;
15
+ this.#metadata = this.#em.getMetadata();
16
+ this.#driver = this.#em.getDriver();
17
17
  }
18
18
  /**
19
19
  * Loads specified relations in batch.
@@ -23,16 +23,15 @@ export class EntityLoader {
23
23
  if (entities.length === 0 || Utils.isEmpty(populate)) {
24
24
  return this.setSerializationContext(entities, populate, options);
25
25
  }
26
- const meta = this.metadata.find(entityName);
26
+ const meta = this.#metadata.find(entityName);
27
27
  if (entities.some(e => !e.__helper)) {
28
28
  const entity = entities.find(e => !Utils.isEntity(e));
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);
44
- const invalid = populate.find(({ field }) => !this.em.canPopulate(entityName, field));
45
- /* v8 ignore next 3 */
42
+ populate = this.normalizePopulate(entityName, populate, options.strategy, options.lookup, options.exclude);
43
+ const invalid = populate.find(({ field }) => !this.#em.canPopulate(entityName, field));
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) {
61
- const meta = this.metadata.find(entityName);
59
+ normalizePopulate(entityName, populate, strategy, lookup = true, exclude) {
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
  }
@@ -117,16 +118,17 @@ export class EntityLoader {
117
118
  */
118
119
  async populateMany(entityName, entities, populate, options) {
119
120
  const [field, ref] = populate.field.split(':', 2);
120
- const meta = this.metadata.find(entityName);
121
+ const meta = this.#metadata.find(entityName);
121
122
  const prop = meta.properties[field];
122
- if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && !this.driver.getPlatform().usesPivotTable()) {
123
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && !this.#driver.getPlatform().usesPivotTable()) {
123
124
  const filtered = entities.filter(e => !e[prop.name]?.isInitialized());
124
125
  if (filtered.length > 0) {
125
126
  await this.populateScalar(meta, filtered, { ...options, fields: [prop.name] });
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,37 +140,107 @@ 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
- if (prop.kind === ReferenceKind.MANY_TO_MANY && this.driver.getPlatform().usesPivotTable()) {
145
- const res = await this.findChildrenFromPivotTable(filtered, prop, options, innerOrderBy, populate, !!ref);
147
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && this.#driver.getPlatform().usesPivotTable()) {
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) {
157
165
  const pk = Utils.getPrimaryKeyHash(meta.primaryKeys);
158
166
  const ids = Utils.unique(filtered.map(e => Utils.getPrimaryKeyValues(e, meta, true)));
159
- const where = this.mergePrimaryCondition(ids, pk, options, meta, this.metadata, this.driver.getPlatform());
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);
170
242
  }
171
- if (prop.kind === ReferenceKind.MANY_TO_MANY && !this.driver.getPlatform().usesPivotTable()) {
243
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && !this.#driver.getPlatform().usesPivotTable()) {
172
244
  this.initializeManyToMany(filtered, children, prop, field, customOrder, partial);
173
245
  }
174
246
  }
@@ -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,61 +338,101 @@ 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;
276
- // eslint-disable-next-line dot-notation
277
- this.em.getUnitOfWork()['loadedEntities'].delete(item);
412
+ this.#em.getUnitOfWork().unmarkAsLoaded(item);
278
413
  }
279
414
  }
280
415
  return { items, partial };
281
416
  }
282
417
  mergePrimaryCondition(ids, pk, options, meta, metadata, platform) {
283
- const cond1 = QueryHelper.processWhere({ where: { [pk]: { $in: ids } }, entityName: meta.className, metadata, platform, convertCustomTypes: !options.convertCustomTypes });
418
+ const cond1 = QueryHelper.processWhere({
419
+ where: { [pk]: { $in: ids } },
420
+ entityName: meta.class,
421
+ metadata,
422
+ platform,
423
+ convertCustomTypes: !options.convertCustomTypes,
424
+ });
284
425
  const where = { ...options.where };
285
426
  Utils.dropUndefinedProperties(where);
286
- return where[pk]
287
- ? { $and: [cond1, where] }
288
- : { ...cond1, ...where };
427
+ return where[pk] ? { $and: [cond1, where] } : { ...cond1, ...where };
289
428
  }
290
429
  async populateField(entityName, entities, populate, options) {
291
430
  const field = populate.field.split(':')[0];
292
- const prop = this.metadata.find(entityName).properties[field];
431
+ const prop = this.#metadata.find(entityName).properties[field];
293
432
  if (prop.kind === ReferenceKind.SCALAR && !prop.lazy) {
294
433
  return;
295
434
  }
435
+ options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
296
436
  const populated = await this.populateMany(entityName, entities, populate, options);
297
437
  if (!populate.children && !populate.all) {
298
438
  return;
@@ -321,6 +461,7 @@ export class EntityLoader {
321
461
  .filter(orderBy => Utils.isObject(orderBy[prop.name]))
322
462
  .map(orderBy => orderBy[prop.name]);
323
463
  const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType, logging, schema } = options;
464
+ // oxfmt-ignore
324
465
  const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
325
466
  const visited = options.visited;
326
467
  for (const entity of entities) {
@@ -331,26 +472,42 @@ export class EntityLoader {
331
472
  for (const entity of entities) {
332
473
  visited.add(entity);
333
474
  }
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
- });
475
+ if (!prop.targetMeta) {
476
+ return;
477
+ }
478
+ const populateChildren = async (targetMeta, items) => {
479
+ await this.populate(targetMeta.class, items, populate.children ?? populate.all, {
480
+ where: (await this.extractChildCondition(options, prop, false)),
481
+ orderBy: innerOrderBy,
482
+ fields,
483
+ exclude,
484
+ validate: false,
485
+ lookup: false,
486
+ filters,
487
+ ignoreLazyScalarProperties,
488
+ populateWhere,
489
+ connectionType,
490
+ logging,
491
+ schema,
492
+ // @ts-ignore not a public option, will be propagated to the populate call
493
+ refresh: refresh && !filtered.every(item => options.visited.has(item)),
494
+ // @ts-ignore not a public option, will be propagated to the populate call
495
+ visited: options.visited,
496
+ // @ts-ignore not a public option
497
+ filtered,
498
+ });
499
+ };
500
+ if (prop.polymorphic && prop.polymorphTargets) {
501
+ await Promise.all(prop.polymorphTargets.map(async (targetMeta) => {
502
+ const targetChildren = unique.filter(child => helper(child).__meta.className === targetMeta.className);
503
+ if (targetChildren.length > 0) {
504
+ await populateChildren(targetMeta, targetChildren);
505
+ }
506
+ }));
507
+ }
508
+ else {
509
+ await populateChildren(prop.targetMeta, unique);
510
+ }
354
511
  }
355
512
  /** @internal */
356
513
  async findChildrenFromPivotTable(filtered, prop, options, orderBy, populate, pivotJoin) {
@@ -358,36 +515,33 @@ export class EntityLoader {
358
515
  const refresh = options.refresh;
359
516
  let where = await this.extractChildCondition(options, prop, true);
360
517
  const fields = this.buildFields(options.fields, prop);
518
+ // oxfmt-ignore
361
519
  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
- }
520
+ const populateFilter = options.populateFilter?.[prop.name];
521
+ const options2 = { ...options, fields, exclude, populateFilter };
522
+ ['limit', 'offset', 'first', 'last', 'before', 'after', 'overfetch'].forEach(prop => delete options2[prop]);
523
+ options2.populate = populate?.children ?? [];
371
524
  if (!Utils.isEmpty(prop.where)) {
372
525
  where = { $and: [where, prop.where] };
373
526
  }
374
- const map = await this.driver.loadFromPivotTable(prop, ids, where, orderBy, this.em.getTransactionContext(), options2, pivotJoin);
527
+ const map = await this.#driver.loadFromPivotTable(prop, ids, where, orderBy, this.#em.getTransactionContext(), options2, pivotJoin);
375
528
  const children = [];
376
- for (const entity of filtered) {
377
- const items = map[entity.__helper.getSerializedPrimaryKey()].map(item => {
529
+ for (let i = 0; i < filtered.length; i++) {
530
+ const entity = filtered[i];
531
+ const items = map[Utils.getPrimaryKeyHash(ids[i])].map(item => {
378
532
  if (pivotJoin) {
379
- return this.em.getReference(prop.type, item, {
533
+ return this.#em.getReference(prop.targetMeta.class, item, {
380
534
  convertCustomTypes: true,
381
- schema: options.schema ?? this.em.config.get('schema'),
535
+ schema: options.schema ?? this.#em.config.get('schema'),
382
536
  });
383
537
  }
384
- const entity = this.em.getEntityFactory().create(prop.type, item, {
538
+ const entity = this.#em.getEntityFactory().create(prop.targetMeta.class, item, {
385
539
  refresh,
386
540
  merge: true,
387
541
  convertCustomTypes: true,
388
- schema: options.schema ?? this.em.config.get('schema'),
542
+ schema: options.schema ?? this.#em.config.get('schema'),
389
543
  });
390
- return this.em.getUnitOfWork().register(entity, item, { refresh, loaded: true });
544
+ return this.#em.getUnitOfWork().register(entity, item, { refresh, loaded: true });
391
545
  });
392
546
  entity[prop.name].hydrate(items, true);
393
547
  children.push(items);
@@ -397,16 +551,14 @@ export class EntityLoader {
397
551
  async extractChildCondition(options, prop, filters = false) {
398
552
  const where = options.where;
399
553
  const subCond = Utils.isPlainObject(where[prop.name]) ? where[prop.name] : {};
400
- const meta2 = this.metadata.find(prop.type);
401
- if (!meta2) {
402
- return {};
403
- }
554
+ const meta2 = prop.targetMeta;
404
555
  const pk = Utils.getPrimaryKeyHash(meta2.primaryKeys);
405
556
  ['$and', '$or'].forEach(op => {
406
557
  if (where[op]) {
407
558
  const child = where[op]
408
559
  .map((cond) => cond[prop.name])
409
- .filter((sub) => sub != null && !(Utils.isPlainObject(sub) && Object.keys(sub).every(key => Utils.isOperator(key, false))))
560
+ .filter((sub) => sub != null &&
561
+ !(Utils.isPlainObject(sub) && Utils.getObjectQueryKeys(sub).every(key => Utils.isOperator(key, false))))
410
562
  .map((cond) => {
411
563
  if (Utils.isPrimaryKey(cond)) {
412
564
  return { [pk]: cond };
@@ -427,7 +579,7 @@ export class EntityLoader {
427
579
  });
428
580
  }
429
581
  if (filters) {
430
- return this.em.applyFilters(prop.type, subCond, options.filters, 'read', options);
582
+ return this.#em.applyFilters(meta2.class, subCond, options.filters, 'read', options);
431
583
  }
432
584
  return subCond;
433
585
  }
@@ -445,23 +597,25 @@ export class EntityLoader {
445
597
  const parts = f.toString().split('.');
446
598
  const propName = parts.shift();
447
599
  const childPropName = parts.join('.');
448
- /* v8 ignore next 3 */
600
+ /* v8 ignore next */
449
601
  if (propName === prop.name) {
450
602
  ret.push(childPropName);
451
603
  }
452
604
  }
453
605
  return ret;
454
606
  }, []);
455
- if (ret.length === 0) {
456
- return undefined;
457
- }
458
607
  // we need to automatically select the FKs too, e.g. for 1:m relations to be able to wire them with the items
459
608
  if (prop.kind === ReferenceKind.ONE_TO_MANY || prop.kind === ReferenceKind.MANY_TO_MANY) {
460
609
  const owner = prop.targetMeta.properties[prop.mappedBy];
461
- if (owner && !ret.includes(owner.name)) {
610
+ // when the owning FK is lazy, we need to explicitly select it even without user-provided fields,
611
+ // otherwise the driver will exclude it and we won't be able to map children to their parent collections
612
+ if (owner && !ret.includes(owner.name) && (ret.length > 0 || owner.lazy)) {
462
613
  ret.push(owner.name);
463
614
  }
464
615
  }
616
+ if (ret.length === 0) {
617
+ return undefined;
618
+ }
465
619
  return ret;
466
620
  }
467
621
  getChildReferences(entities, prop, options, ref) {
@@ -475,7 +629,8 @@ export class EntityLoader {
475
629
  return a;
476
630
  }, []);
477
631
  }
478
- if (prop.kind === ReferenceKind.MANY_TO_MANY) { // inverse side
632
+ if (prop.kind === ReferenceKind.MANY_TO_MANY) {
633
+ // inverse side
479
634
  return filtered;
480
635
  }
481
636
  // MANY_TO_ONE or ONE_TO_ONE
@@ -496,7 +651,7 @@ export class EntityLoader {
496
651
  return wrapped.__loadedProperties.has(field);
497
652
  }
498
653
  const [f, ...r] = field.split('.');
499
- /* v8 ignore next 3 */
654
+ /* v8 ignore next */
500
655
  if (!wrapped.__loadedProperties.has(f) || !wrapped.__meta.properties[f]?.targetMeta) {
501
656
  return false;
502
657
  }
@@ -529,7 +684,7 @@ export class EntityLoader {
529
684
  .map(e => Reference.unwrapReference(e[field]));
530
685
  }
531
686
  filterByReferences(entities, field, refresh) {
532
- /* v8 ignore next 3 */
687
+ /* v8 ignore next */
533
688
  if (refresh) {
534
689
  return entities;
535
690
  }
@@ -537,7 +692,7 @@ export class EntityLoader {
537
692
  }
538
693
  lookupAllRelationships(entityName) {
539
694
  const ret = [];
540
- const meta = this.metadata.find(entityName);
695
+ const meta = this.#metadata.find(entityName);
541
696
  meta.relations.forEach(prop => {
542
697
  ret.push({
543
698
  field: this.getRelationName(meta, prop),
@@ -555,37 +710,43 @@ export class EntityLoader {
555
710
  }
556
711
  return `${this.getRelationName(meta, meta.properties[prop.embedded[0]])}.${prop.embedded[1]}`;
557
712
  }
558
- lookupEagerLoadedRelationships(entityName, populate, strategy, prefix = '', visited = []) {
559
- const meta = this.metadata.find(entityName);
713
+ lookupEagerLoadedRelationships(entityName, populate, strategy, prefix = '', visited = [], exclude) {
714
+ const meta = this.#metadata.find(entityName);
560
715
  if (!meta && !prefix) {
561
716
  return populate;
562
717
  }
563
- if (visited.includes(entityName) || !meta) {
718
+ if (!meta || visited.includes(meta)) {
564
719
  return [];
565
720
  }
566
- visited.push(entityName);
721
+ visited.push(meta);
567
722
  const ret = prefix === '' ? [...populate] : [];
568
723
  meta.relations
569
724
  .filter(prop => {
725
+ const field = this.getRelationName(meta, prop);
726
+ const prefixed = prefix ? `${prefix}.${field}` : field;
727
+ const isExcluded = exclude?.includes(prefixed);
570
728
  const eager = prop.eager && !populate.some(p => p.field === `${prop.name}:ref`);
571
729
  const populated = populate.some(p => p.field === prop.name);
572
730
  const disabled = populate.some(p => p.field === prop.name && p.all === false);
573
- return !disabled && (eager || populated);
731
+ return !disabled && !isExcluded && (eager || populated);
574
732
  })
575
733
  .forEach(prop => {
576
734
  const field = this.getRelationName(meta, prop);
577
735
  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());
736
+ const nestedPopulate = populate
737
+ .filter(p => p.field === prop.name)
738
+ .flatMap(p => p.children)
739
+ .filter(Boolean);
740
+ const nested = this.lookupEagerLoadedRelationships(prop.targetMeta.class, nestedPopulate, strategy, prefixed, visited.slice(), exclude);
580
741
  if (nested.length > 0) {
581
742
  ret.push(...nested);
582
743
  }
583
744
  else {
584
- const selfReferencing = [meta.className, meta.root.className, ...visited].includes(prop.type) && prop.eager;
745
+ const selfReferencing = [meta.tableName, ...visited.map(m => m.tableName)].includes(prop.targetMeta.tableName) && prop.eager;
585
746
  ret.push({
586
747
  field: prefixed,
587
748
  // enforce select-in strategy for self-referencing relations
588
- strategy: selfReferencing ? LoadStrategy.SELECT_IN : strategy ?? prop.strategy,
749
+ strategy: selfReferencing ? LoadStrategy.SELECT_IN : (strategy ?? prop.strategy),
589
750
  });
590
751
  }
591
752
  });