@mikro-orm/core 7.0.4 → 7.0.5-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/EntityManager.d.ts +583 -884
  2. package/EntityManager.js +1895 -1922
  3. package/MikroORM.d.ts +74 -103
  4. package/MikroORM.js +179 -178
  5. package/README.md +1 -1
  6. package/cache/CacheAdapter.d.ts +36 -36
  7. package/cache/FileCacheAdapter.d.ts +24 -30
  8. package/cache/FileCacheAdapter.js +78 -80
  9. package/cache/GeneratedCacheAdapter.d.ts +20 -18
  10. package/cache/GeneratedCacheAdapter.js +30 -30
  11. package/cache/MemoryCacheAdapter.d.ts +20 -18
  12. package/cache/MemoryCacheAdapter.js +36 -35
  13. package/cache/NullCacheAdapter.d.ts +16 -16
  14. package/cache/NullCacheAdapter.js +24 -24
  15. package/connections/Connection.d.ts +84 -95
  16. package/connections/Connection.js +168 -165
  17. package/drivers/DatabaseDriver.d.ts +80 -186
  18. package/drivers/DatabaseDriver.js +443 -450
  19. package/drivers/IDatabaseDriver.d.ts +301 -440
  20. package/entity/BaseEntity.d.ts +83 -120
  21. package/entity/BaseEntity.js +43 -43
  22. package/entity/Collection.d.ts +179 -212
  23. package/entity/Collection.js +721 -727
  24. package/entity/EntityAssigner.d.ts +77 -88
  25. package/entity/EntityAssigner.js +230 -231
  26. package/entity/EntityFactory.d.ts +54 -66
  27. package/entity/EntityFactory.js +383 -425
  28. package/entity/EntityHelper.d.ts +22 -34
  29. package/entity/EntityHelper.js +267 -280
  30. package/entity/EntityIdentifier.d.ts +4 -4
  31. package/entity/EntityIdentifier.js +10 -10
  32. package/entity/EntityLoader.d.ts +72 -98
  33. package/entity/EntityLoader.js +723 -753
  34. package/entity/EntityRepository.d.ts +201 -316
  35. package/entity/EntityRepository.js +213 -213
  36. package/entity/PolymorphicRef.d.ts +5 -5
  37. package/entity/PolymorphicRef.js +10 -10
  38. package/entity/Reference.d.ts +82 -126
  39. package/entity/Reference.js +274 -278
  40. package/entity/WrappedEntity.d.ts +72 -115
  41. package/entity/WrappedEntity.js +166 -168
  42. package/entity/defineEntity.d.ts +636 -1315
  43. package/entity/defineEntity.js +518 -527
  44. package/entity/utils.d.ts +3 -13
  45. package/entity/utils.js +73 -71
  46. package/entity/validators.js +43 -43
  47. package/entity/wrap.js +8 -8
  48. package/enums.d.ts +253 -258
  49. package/enums.js +252 -251
  50. package/errors.d.ts +72 -114
  51. package/errors.js +253 -350
  52. package/events/EventManager.d.ts +14 -26
  53. package/events/EventManager.js +77 -79
  54. package/events/EventSubscriber.d.ts +29 -29
  55. package/events/TransactionEventBroadcaster.d.ts +8 -15
  56. package/events/TransactionEventBroadcaster.js +14 -14
  57. package/exceptions.d.ts +40 -23
  58. package/exceptions.js +52 -35
  59. package/hydration/Hydrator.d.ts +17 -42
  60. package/hydration/Hydrator.js +43 -43
  61. package/hydration/ObjectHydrator.d.ts +17 -50
  62. package/hydration/ObjectHydrator.js +416 -481
  63. package/index.d.ts +2 -116
  64. package/index.js +1 -10
  65. package/logging/DefaultLogger.d.ts +32 -34
  66. package/logging/DefaultLogger.js +86 -86
  67. package/logging/Logger.d.ts +41 -41
  68. package/logging/SimpleLogger.d.ts +11 -13
  69. package/logging/SimpleLogger.js +22 -22
  70. package/logging/colors.d.ts +6 -6
  71. package/logging/colors.js +10 -11
  72. package/logging/inspect.js +7 -7
  73. package/metadata/EntitySchema.d.ts +127 -211
  74. package/metadata/EntitySchema.js +398 -397
  75. package/metadata/MetadataDiscovery.d.ts +114 -114
  76. package/metadata/MetadataDiscovery.js +1870 -1951
  77. package/metadata/MetadataProvider.d.ts +21 -24
  78. package/metadata/MetadataProvider.js +84 -82
  79. package/metadata/MetadataStorage.d.ts +32 -38
  80. package/metadata/MetadataStorage.js +118 -118
  81. package/metadata/MetadataValidator.d.ts +39 -39
  82. package/metadata/MetadataValidator.js +338 -381
  83. package/metadata/discover-entities.d.ts +2 -5
  84. package/metadata/discover-entities.js +37 -35
  85. package/metadata/types.d.ts +531 -615
  86. package/naming-strategy/AbstractNamingStrategy.d.ts +39 -54
  87. package/naming-strategy/AbstractNamingStrategy.js +85 -90
  88. package/naming-strategy/EntityCaseNamingStrategy.d.ts +6 -6
  89. package/naming-strategy/EntityCaseNamingStrategy.js +22 -22
  90. package/naming-strategy/MongoNamingStrategy.d.ts +6 -6
  91. package/naming-strategy/MongoNamingStrategy.js +18 -18
  92. package/naming-strategy/NamingStrategy.d.ts +99 -109
  93. package/naming-strategy/UnderscoreNamingStrategy.d.ts +7 -7
  94. package/naming-strategy/UnderscoreNamingStrategy.js +21 -21
  95. package/not-supported.js +4 -7
  96. package/package.json +1 -1
  97. package/platforms/ExceptionConverter.d.ts +1 -1
  98. package/platforms/ExceptionConverter.js +4 -4
  99. package/platforms/Platform.d.ts +301 -310
  100. package/platforms/Platform.js +640 -663
  101. package/serialization/EntitySerializer.d.ts +26 -49
  102. package/serialization/EntitySerializer.js +218 -224
  103. package/serialization/EntityTransformer.d.ts +6 -10
  104. package/serialization/EntityTransformer.js +217 -219
  105. package/serialization/SerializationContext.d.ts +23 -27
  106. package/serialization/SerializationContext.js +105 -105
  107. package/types/ArrayType.d.ts +8 -8
  108. package/types/ArrayType.js +33 -33
  109. package/types/BigIntType.d.ts +10 -17
  110. package/types/BigIntType.js +37 -37
  111. package/types/BlobType.d.ts +3 -3
  112. package/types/BlobType.js +13 -13
  113. package/types/BooleanType.d.ts +4 -4
  114. package/types/BooleanType.js +12 -12
  115. package/types/CharacterType.d.ts +2 -2
  116. package/types/CharacterType.js +6 -6
  117. package/types/DateTimeType.d.ts +5 -5
  118. package/types/DateTimeType.js +15 -15
  119. package/types/DateType.d.ts +5 -5
  120. package/types/DateType.js +15 -15
  121. package/types/DecimalType.d.ts +7 -7
  122. package/types/DecimalType.js +26 -26
  123. package/types/DoubleType.d.ts +3 -3
  124. package/types/DoubleType.js +12 -12
  125. package/types/EnumArrayType.d.ts +5 -5
  126. package/types/EnumArrayType.js +24 -24
  127. package/types/EnumType.d.ts +3 -3
  128. package/types/EnumType.js +11 -11
  129. package/types/FloatType.d.ts +3 -3
  130. package/types/FloatType.js +9 -9
  131. package/types/IntegerType.d.ts +3 -3
  132. package/types/IntegerType.js +9 -9
  133. package/types/IntervalType.d.ts +4 -4
  134. package/types/IntervalType.js +12 -12
  135. package/types/JsonType.d.ts +8 -8
  136. package/types/JsonType.js +32 -32
  137. package/types/MediumIntType.d.ts +1 -1
  138. package/types/MediumIntType.js +3 -3
  139. package/types/SmallIntType.d.ts +3 -3
  140. package/types/SmallIntType.js +9 -9
  141. package/types/StringType.d.ts +4 -4
  142. package/types/StringType.js +12 -12
  143. package/types/TextType.d.ts +3 -3
  144. package/types/TextType.js +9 -9
  145. package/types/TimeType.d.ts +5 -5
  146. package/types/TimeType.js +17 -17
  147. package/types/TinyIntType.d.ts +3 -3
  148. package/types/TinyIntType.js +10 -10
  149. package/types/Type.d.ts +79 -83
  150. package/types/Type.js +82 -82
  151. package/types/Uint8ArrayType.d.ts +4 -4
  152. package/types/Uint8ArrayType.js +21 -21
  153. package/types/UnknownType.d.ts +4 -4
  154. package/types/UnknownType.js +12 -12
  155. package/types/UuidType.d.ts +5 -5
  156. package/types/UuidType.js +19 -19
  157. package/types/index.d.ts +49 -75
  158. package/types/index.js +26 -52
  159. package/typings.d.ts +737 -1250
  160. package/typings.js +231 -244
  161. package/unit-of-work/ChangeSet.d.ts +26 -26
  162. package/unit-of-work/ChangeSet.js +56 -56
  163. package/unit-of-work/ChangeSetComputer.d.ts +12 -12
  164. package/unit-of-work/ChangeSetComputer.js +170 -178
  165. package/unit-of-work/ChangeSetPersister.d.ts +44 -63
  166. package/unit-of-work/ChangeSetPersister.js +421 -442
  167. package/unit-of-work/CommitOrderCalculator.d.ts +40 -40
  168. package/unit-of-work/CommitOrderCalculator.js +88 -89
  169. package/unit-of-work/IdentityMap.d.ts +31 -31
  170. package/unit-of-work/IdentityMap.js +105 -105
  171. package/unit-of-work/UnitOfWork.d.ts +141 -181
  172. package/unit-of-work/UnitOfWork.js +1183 -1200
  173. package/utils/AbstractMigrator.d.ts +91 -111
  174. package/utils/AbstractMigrator.js +275 -275
  175. package/utils/AbstractSchemaGenerator.d.ts +34 -43
  176. package/utils/AbstractSchemaGenerator.js +122 -121
  177. package/utils/AsyncContext.d.ts +3 -3
  178. package/utils/AsyncContext.js +35 -34
  179. package/utils/Configuration.d.ts +808 -852
  180. package/utils/Configuration.js +344 -359
  181. package/utils/Cursor.d.ts +22 -40
  182. package/utils/Cursor.js +127 -135
  183. package/utils/DataloaderUtils.d.ts +43 -58
  184. package/utils/DataloaderUtils.js +198 -203
  185. package/utils/EntityComparator.d.ts +81 -98
  186. package/utils/EntityComparator.js +732 -828
  187. package/utils/NullHighlighter.d.ts +1 -1
  188. package/utils/NullHighlighter.js +3 -3
  189. package/utils/QueryHelper.d.ts +51 -79
  190. package/utils/QueryHelper.js +361 -372
  191. package/utils/RawQueryFragment.d.ts +34 -50
  192. package/utils/RawQueryFragment.js +105 -107
  193. package/utils/RequestContext.d.ts +32 -32
  194. package/utils/RequestContext.js +53 -52
  195. package/utils/TransactionContext.d.ts +16 -16
  196. package/utils/TransactionContext.js +27 -27
  197. package/utils/TransactionManager.d.ts +58 -58
  198. package/utils/TransactionManager.js +197 -199
  199. package/utils/Utils.d.ts +145 -204
  200. package/utils/Utils.js +812 -812
  201. package/utils/clone.js +113 -104
  202. package/utils/env-vars.js +88 -90
  203. package/utils/fs-utils.d.ts +15 -15
  204. package/utils/fs-utils.js +181 -180
  205. package/utils/upsert-utils.d.ts +5 -20
  206. package/utils/upsert-utils.js +116 -114
@@ -1,787 +1,757 @@
1
1
  import { QueryHelper } from '../utils/QueryHelper.js';
2
2
  import { Utils } from '../utils/Utils.js';
3
3
  import { ValidationError } from '../errors.js';
4
- import { LoadStrategy, PopulatePath, ReferenceKind } from '../enums.js';
4
+ import { LoadStrategy, PopulatePath, ReferenceKind, } from '../enums.js';
5
5
  import { Reference } from './Reference.js';
6
6
  import { helper } from './wrap.js';
7
7
  import { expandDotPaths } from './utils.js';
8
8
  import { Raw } from '../utils/RawQueryFragment.js';
9
9
  /** Responsible for batch-loading entity relations using either select-in or joined loading strategies. */
10
10
  export class EntityLoader {
11
- #metadata;
12
- #driver;
13
- #em;
14
- constructor(em) {
15
- this.#em = em;
16
- this.#metadata = this.#em.getMetadata();
17
- this.#driver = this.#em.getDriver();
18
- }
19
- /**
20
- * Loads specified relations in batch.
21
- * This will execute one query for each relation, that will populate it on all the specified entities.
22
- */
23
- async populate(entityName, entities, populate, options) {
24
- if (entities.length === 0 || Utils.isEmpty(populate)) {
25
- return this.setSerializationContext(entities, populate, options);
26
- }
27
- const meta = this.#metadata.find(entityName);
28
- if (entities.some(e => !e.__helper)) {
29
- const entity = entities.find(e => !Utils.isEntity(e));
30
- throw ValidationError.notDiscoveredEntity(entity, meta, 'populate');
31
- }
32
- const references = entities.filter(e => !helper(e).isInitialized());
33
- const visited = (options.visited ??= new Set());
34
- options.where ??= {};
35
- options.orderBy ??= {};
36
- options.lookup ??= true;
37
- options.validate ??= true;
38
- options.refresh ??= false;
39
- options.convertCustomTypes ??= true;
40
- if (references.length > 0) {
41
- await this.populateScalar(meta, references, { ...options, populateWhere: undefined });
42
- }
43
- populate = this.normalizePopulate(entityName, populate, options.strategy, options.lookup, options.exclude);
44
- const invalid = populate.find(({ field }) => !this.#em.canPopulate(entityName, field));
45
- /* v8 ignore next */
46
- if (options.validate && invalid) {
47
- throw ValidationError.invalidPropertyName(entityName, invalid.field);
48
- }
49
- this.setSerializationContext(entities, populate, options);
50
- for (const entity of entities) {
51
- visited.add(entity);
52
- }
53
- for (const pop of populate) {
54
- await this.populateField(entityName, entities, pop, options);
55
- }
56
- for (const entity of entities) {
57
- visited.delete(entity);
58
- }
59
- }
60
- /** Normalizes populate hints into a structured array of PopulateOptions, expanding dot paths and eager relations. */
61
- normalizePopulate(entityName, populate, strategy, lookup = true, exclude) {
62
- const meta = this.#metadata.find(entityName);
63
- let normalized = Utils.asArray(populate).map(field => {
64
- // oxfmt-ignore
65
- return typeof field === 'boolean' || field.field === PopulatePath.ALL ? { all: !!field, field: meta.primaryKeys[0] } : field;
66
- });
67
- if (normalized.some(p => p.all)) {
68
- normalized = this.lookupAllRelationships(entityName);
69
- }
70
- // convert nested `field` with dot syntax to PopulateOptions with `children` array
71
- expandDotPaths(meta, normalized, true);
72
- if (lookup && populate !== false) {
73
- normalized = this.lookupEagerLoadedRelationships(entityName, normalized, strategy, '', [], exclude);
74
- // convert nested `field` with dot syntax produced by eager relations
75
- expandDotPaths(meta, normalized, true);
76
- }
77
- // merge same fields
78
- return this.mergeNestedPopulate(normalized);
79
- }
80
- setSerializationContext(entities, populate, options) {
81
- for (const entity of entities) {
82
- helper(entity).setSerializationContext({
83
- populate,
84
- fields: options.fields,
85
- exclude: options.exclude,
86
- });
87
- }
88
- }
89
- /**
90
- * Merge multiple populates for the same entity with different children. Also skips `*` fields, those can come from
91
- * partial loading hints (`fields`) that are used to infer the `populate` hint if missing.
92
- */
93
- mergeNestedPopulate(populate) {
94
- const tmp = populate.reduce((ret, item) => {
95
- /* v8 ignore next */
96
- if (item.field === PopulatePath.ALL) {
97
- return ret;
98
- }
99
- if (!ret[item.field]) {
100
- ret[item.field] = item;
101
- return ret;
102
- }
103
- if (!ret[item.field].children && item.children) {
104
- ret[item.field].children = item.children;
105
- } else if (ret[item.field].children && item.children) {
106
- ret[item.field].children.push(...item.children);
107
- }
108
- return ret;
109
- }, {});
110
- return Object.values(tmp).map(item => {
111
- if (item.children) {
112
- item.children = this.mergeNestedPopulate(item.children);
113
- }
114
- return item;
115
- });
116
- }
117
- /**
118
- * preload everything in one call (this will update already existing references in IM)
119
- */
120
- async populateMany(entityName, entities, populate, options) {
121
- const [field, ref] = populate.field.split(':', 2);
122
- const meta = this.#metadata.find(entityName);
123
- const prop = meta.properties[field];
124
- if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && !this.#driver.getPlatform().usesPivotTable()) {
125
- const filtered = entities.filter(e => !e[prop.name]?.isInitialized());
126
- if (filtered.length > 0) {
127
- await this.populateScalar(meta, filtered, { ...options, fields: [prop.name] });
128
- }
129
- }
130
- if (prop.kind === ReferenceKind.SCALAR && prop.lazy) {
131
- const filtered = entities.filter(
132
- e => options.refresh || (prop.ref ? !e[prop.name]?.isInitialized() : e[prop.name] === undefined),
133
- );
134
- if (options.ignoreLazyScalarProperties || filtered.length === 0) {
135
- return entities;
136
- }
137
- await this.populateScalar(meta, filtered, { ...options, fields: [prop.name] });
138
- return entities;
139
- }
140
- if (prop.kind === ReferenceKind.EMBEDDED) {
141
- return [];
142
- }
143
- const filtered = this.filterCollections(entities, field, options, ref);
144
- const innerOrderBy = Utils.asArray(options.orderBy)
145
- .filter(
146
- orderBy =>
147
- (Array.isArray(orderBy[prop.name]) && orderBy[prop.name].length > 0) || Utils.isObject(orderBy[prop.name]),
148
- )
149
- .flatMap(orderBy => orderBy[prop.name]);
150
- const where = await this.extractChildCondition(options, prop);
151
- if (prop.kind === ReferenceKind.MANY_TO_MANY && this.#driver.getPlatform().usesPivotTable()) {
152
- const pivotOrderBy = QueryHelper.mergeOrderBy(innerOrderBy, prop.orderBy, prop.targetMeta?.orderBy);
153
- const res = await this.findChildrenFromPivotTable(filtered, prop, options, pivotOrderBy, populate, !!ref);
154
- return Utils.flatten(res);
155
- }
156
- if (prop.polymorphic && prop.polymorphTargets) {
157
- return this.populatePolymorphic(entities, prop, options, !!ref);
158
- }
159
- const { items, partial } = await this.findChildren(
160
- options.filtered ?? entities,
161
- prop,
162
- populate,
163
- {
164
- ...options,
165
- where,
166
- orderBy: innerOrderBy,
167
- },
168
- !!(ref || prop.mapToPk),
169
- );
170
- const customOrder = innerOrderBy.length > 0 || !!prop.orderBy || !!prop.targetMeta?.orderBy;
171
- this.initializeCollections(filtered, prop, field, items, customOrder, partial);
172
- return items;
173
- }
174
- async populateScalar(meta, filtered, options) {
175
- const pk = Utils.getPrimaryKeyHash(meta.primaryKeys);
176
- const ids = Utils.unique(filtered.map(e => Utils.getPrimaryKeyValues(e, meta, true)));
177
- const where = this.mergePrimaryCondition(ids, pk, options, meta, this.#metadata, this.#driver.getPlatform());
178
- const { filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, fields } = options;
179
- await this.#em.find(meta.class, where, {
180
- filters,
181
- convertCustomTypes,
182
- lockMode,
183
- strategy,
184
- populateWhere,
185
- connectionType,
186
- logging,
187
- fields: fields,
188
- populate: [],
189
- });
190
- }
191
- async populatePolymorphic(entities, prop, options, ref) {
192
- const ownerMeta = this.#metadata.get(entities[0].constructor);
193
- // Separate entities: those with loaded refs vs those needing FK load
194
- const toPopulate = [];
195
- const needsFkLoad = [];
196
- for (const entity of entities) {
197
- const refValue = entity[prop.name];
198
- if (refValue && helper(refValue).hasPrimaryKey()) {
199
- if (
200
- (ref && !options.refresh) || // :ref hint - already have reference
201
- (!ref && helper(refValue).__initialized && !options.refresh) // already loaded
202
- ) {
203
- continue;
204
- }
205
- toPopulate.push(entity);
206
- } else if (refValue == null && !helper(entity).__loadedProperties.has(prop.name)) {
207
- // FK columns weren't loaded (partial loading) — need to re-fetch them.
208
- // If the property IS in __loadedProperties, the FK was loaded and is genuinely null.
209
- needsFkLoad.push(entity);
210
- }
211
- }
212
- // Load FK columns using populateScalar pattern
213
- if (needsFkLoad.length > 0) {
214
- await this.populateScalar(ownerMeta, needsFkLoad, {
215
- ...options,
216
- fields: [...ownerMeta.primaryKeys, prop.name],
217
- });
218
- // After loading FKs, add to toPopulate if not using :ref hint
219
- if (!ref) {
220
- for (const entity of needsFkLoad) {
221
- const refValue = entity[prop.name];
222
- if (refValue && helper(refValue).hasPrimaryKey()) {
223
- toPopulate.push(entity);
224
- }
225
- }
226
- }
227
- }
228
- if (toPopulate.length === 0) {
229
- return [];
230
- }
231
- // Group references by target class for batch loading
232
- const groups = new Map();
233
- for (const entity of toPopulate) {
234
- const refValue = Reference.unwrapReference(entity[prop.name]);
235
- const discriminator = QueryHelper.findDiscriminatorValue(prop.discriminatorMap, helper(refValue).__meta.class);
236
- const group = groups.get(discriminator) ?? [];
237
- group.push(refValue);
238
- groups.set(discriminator, group);
239
- }
240
- // Load each group concurrently - identity map handles merging with existing references
241
- const allItems = [];
242
- await Promise.all(
243
- [...groups].map(async ([discriminator, children]) => {
244
- const targetMeta = this.#metadata.find(prop.discriminatorMap[discriminator]);
245
- await this.populateScalar(targetMeta, children, options);
246
- allItems.push(...children);
247
- }),
248
- );
249
- return allItems;
250
- }
251
- initializeCollections(filtered, prop, field, children, customOrder, partial) {
252
- if (prop.kind === ReferenceKind.ONE_TO_MANY) {
253
- this.initializeOneToMany(filtered, children, prop, field, partial);
254
- }
255
- if (prop.kind === ReferenceKind.MANY_TO_MANY && !this.#driver.getPlatform().usesPivotTable()) {
256
- this.initializeManyToMany(filtered, children, prop, field, customOrder, partial);
257
- }
258
- }
259
- initializeOneToMany(filtered, children, prop, field, partial) {
260
- const mapToPk = prop.targetMeta.properties[prop.mappedBy].mapToPk;
261
- const map = {};
262
- for (const entity of filtered) {
263
- const key = helper(entity).getSerializedPrimaryKey();
264
- map[key] = [];
265
- }
266
- for (const child of children) {
267
- const pk = child.__helper.__data[prop.mappedBy] ?? child[prop.mappedBy];
268
- if (pk) {
269
- const key = helper(mapToPk ? this.#em.getReference(prop.targetMeta.class, pk) : pk).getSerializedPrimaryKey();
270
- map[key]?.push(child);
271
- }
272
- }
273
- for (const entity of filtered) {
274
- const key = helper(entity).getSerializedPrimaryKey();
275
- entity[field].hydrate(map[key], undefined, partial);
276
- }
277
- }
278
- initializeManyToMany(filtered, children, prop, field, customOrder, partial) {
279
- if (prop.mappedBy) {
280
- for (const entity of filtered) {
281
- const items = children.filter(child => child[prop.mappedBy].contains(entity, false));
282
- entity[field].hydrate(items, true, partial);
283
- }
284
- } else {
285
- // owning side of M:N without pivot table needs to be reordered
286
- for (const entity of filtered) {
287
- const order = !customOrder ? [...entity[prop.name].getItems(false)] : []; // copy order of references
288
- const items = children.filter(child => entity[prop.name].contains(child, false));
289
- if (!customOrder) {
290
- items.sort((a, b) => order.indexOf(a) - order.indexOf(b));
291
- }
292
- entity[field].hydrate(items, true, partial);
293
- }
294
- }
295
- }
296
- async findChildren(entities, prop, populate, options, ref) {
297
- const children = Utils.unique(this.getChildReferences(entities, prop, options, ref));
298
- const meta = prop.targetMeta;
299
- // When targetKey is set, use it for FK lookup instead of the PK
300
- let fk = prop.targetKey ?? Utils.getPrimaryKeyHash(meta.primaryKeys);
301
- let schema = options.schema;
302
- const partial = !Utils.isEmpty(prop.where) || !Utils.isEmpty(options.where);
303
- let polymorphicOwnerProp;
304
- if (prop.kind === ReferenceKind.ONE_TO_MANY || (prop.kind === ReferenceKind.MANY_TO_MANY && !prop.owner)) {
305
- const ownerProp = meta.properties[prop.mappedBy];
306
- if (ownerProp.polymorphic && ownerProp.fieldNames.length >= 2) {
307
- const idColumns = ownerProp.fieldNames.slice(1);
308
- fk = idColumns.length === 1 ? idColumns[0] : idColumns;
309
- polymorphicOwnerProp = ownerProp;
310
- } else {
311
- fk = ownerProp.name;
312
- }
313
- }
314
- if (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && !ref) {
315
- children.length = 0;
316
- fk = meta.properties[prop.mappedBy].name;
317
- children.push(...this.filterByReferences(entities, prop.name, options.refresh));
318
- }
319
- if (children.length === 0) {
320
- return { items: [], partial };
321
- }
322
- if (!schema && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
323
- schema = children.find(e => e.__helper.__schema)?.__helper.__schema;
324
- }
325
- const ids = Utils.unique(children.map(e => (prop.targetKey ? e[prop.targetKey] : e.__helper.getPrimaryKey())));
326
- let where;
327
- if (polymorphicOwnerProp && Array.isArray(fk)) {
328
- const conditions = ids.map(id => {
329
- const pkValues = Object.values(id);
330
- return Object.fromEntries(fk.map((col, idx) => [col, pkValues[idx]]));
331
- });
332
- where = conditions.length === 1 ? conditions[0] : { $or: conditions };
333
- } else {
334
- where = this.mergePrimaryCondition(ids, fk, options, meta, this.#metadata, this.#driver.getPlatform());
335
- }
336
- if (polymorphicOwnerProp) {
337
- const parentMeta = this.#metadata.find(entities[0].constructor);
338
- const discriminatorValue =
339
- QueryHelper.findDiscriminatorValue(polymorphicOwnerProp.discriminatorMap, parentMeta.class) ??
340
- parentMeta.tableName;
341
- const discriminatorColumn = polymorphicOwnerProp.fieldNames[0];
342
- where = { $and: [where, { [discriminatorColumn]: discriminatorValue }] };
343
- }
344
- const fields = this.buildFields(options.fields, prop, ref);
345
- /* eslint-disable prefer-const */
346
- let {
347
- refresh,
348
- filters,
349
- convertCustomTypes,
350
- lockMode,
351
- strategy,
352
- populateWhere = 'infer',
353
- connectionType,
354
- logging,
355
- } = options;
356
- /* eslint-enable prefer-const */
357
- if (typeof populateWhere === 'object') {
358
- populateWhere = await this.extractChildCondition({ where: populateWhere }, prop);
359
- }
360
- if (!Utils.isEmpty(prop.where) || Raw.hasObjectFragments(prop.where)) {
361
- where = { $and: [where, prop.where] };
362
- }
363
- const orderBy = QueryHelper.mergeOrderBy(options.orderBy, prop.orderBy);
364
- const items = await this.#em.find(meta.class, where, {
365
- filters,
366
- convertCustomTypes,
367
- lockMode,
368
- populateWhere,
369
- logging,
370
- orderBy,
371
- populate: populate.children ?? populate.all ?? [],
372
- exclude: Array.isArray(options.exclude)
373
- ? Utils.extractChildElements(options.exclude, prop.name)
374
- : options.exclude,
375
- strategy,
376
- fields,
377
- schema,
378
- connectionType,
379
- // @ts-ignore not a public option, will be propagated to the populate call
380
- refresh: refresh && !children.every(item => options.visited.has(item)),
381
- // @ts-ignore not a public option, will be propagated to the populate call
382
- visited: options.visited,
383
- });
384
- // For targetKey relations, wire up loaded entities to parent references
385
- // This is needed because the references were created under alternate key,
386
- // but loaded entities are stored under PK, so they don't automatically merge
387
- if (prop.targetKey && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
388
- const itemsByKey = new Map();
389
- for (const item of items) {
390
- itemsByKey.set('' + item[prop.targetKey], item);
391
- }
392
- for (const entity of entities) {
393
- const ref = entity[prop.name];
394
- /* v8 ignore next */
395
- if (!ref) {
396
- continue;
11
+ #metadata;
12
+ #driver;
13
+ #em;
14
+ constructor(em) {
15
+ this.#em = em;
16
+ this.#metadata = this.#em.getMetadata();
17
+ this.#driver = this.#em.getDriver();
18
+ }
19
+ /**
20
+ * Loads specified relations in batch.
21
+ * This will execute one query for each relation, that will populate it on all the specified entities.
22
+ */
23
+ async populate(entityName, entities, populate, options) {
24
+ if (entities.length === 0 || Utils.isEmpty(populate)) {
25
+ return this.setSerializationContext(entities, populate, options);
397
26
  }
398
- // oxfmt-ignore
399
- const keyValue = '' + (Reference.isReference(ref) ? ref.unwrap()[prop.targetKey] : ref[prop.targetKey]);
400
- const loadedItem = itemsByKey.get(keyValue);
401
- if (loadedItem) {
402
- entity[prop.name] = Reference.isReference(ref) ? Reference.create(loadedItem) : loadedItem;
27
+ const meta = this.#metadata.find(entityName);
28
+ if (entities.some(e => !e.__helper)) {
29
+ const entity = entities.find(e => !Utils.isEntity(e));
30
+ throw ValidationError.notDiscoveredEntity(entity, meta, 'populate');
403
31
  }
404
- }
405
- }
406
- if ([ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && items.length !== children.length) {
407
- const nullVal = this.#em.config.get('forceUndefined') ? undefined : null;
408
- const itemsMap = new Set();
409
- const childrenMap = new Set();
410
- // Use targetKey value if set, otherwise use serialized PK
411
- const getKey = e => (prop.targetKey ? '' + e[prop.targetKey] : helper(e).getSerializedPrimaryKey());
412
- for (const item of items) {
32
+ const references = entities.filter(e => !helper(e).isInitialized());
33
+ const visited = (options.visited ??= new Set());
34
+ options.where ??= {};
35
+ options.orderBy ??= {};
36
+ options.lookup ??= true;
37
+ options.validate ??= true;
38
+ options.refresh ??= false;
39
+ options.convertCustomTypes ??= true;
40
+ if (references.length > 0) {
41
+ await this.populateScalar(meta, references, { ...options, populateWhere: undefined });
42
+ }
43
+ populate = this.normalizePopulate(entityName, populate, options.strategy, options.lookup, options.exclude);
44
+ const invalid = populate.find(({ field }) => !this.#em.canPopulate(entityName, field));
413
45
  /* v8 ignore next */
414
- itemsMap.add(getKey(item));
415
- }
416
- for (const child of children) {
417
- childrenMap.add(getKey(child));
418
- }
419
- for (const entity of entities) {
420
- const ref = entity[prop.name] ?? {};
421
- const key = helper(ref) ? getKey(ref) : undefined;
422
- if (key && childrenMap.has(key) && !itemsMap.has(key)) {
423
- entity[prop.name] = nullVal;
424
- helper(entity).__originalEntityData[prop.name] = null;
425
- }
426
- }
427
- }
428
- for (const item of items) {
429
- if (ref && !helper(item).__onLoadFired) {
430
- helper(item).__initialized = false;
431
- this.#em.getUnitOfWork().unmarkAsLoaded(item);
432
- }
433
- }
434
- return { items, partial };
435
- }
436
- mergePrimaryCondition(ids, pk, options, meta, metadata, platform) {
437
- const cond1 = QueryHelper.processWhere({
438
- where: { [pk]: { $in: ids } },
439
- entityName: meta.class,
440
- metadata,
441
- platform,
442
- convertCustomTypes: !options.convertCustomTypes,
443
- });
444
- const where = { ...options.where };
445
- Utils.dropUndefinedProperties(where);
446
- return where[pk] ? { $and: [cond1, where] } : { ...cond1, ...where };
447
- }
448
- async populateField(entityName, entities, populate, options) {
449
- const field = populate.field.split(':')[0];
450
- const prop = this.#metadata.find(entityName).properties[field];
451
- if (prop.kind === ReferenceKind.SCALAR && !prop.lazy) {
452
- return;
453
- }
454
- options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
455
- const populated = await this.populateMany(entityName, entities, populate, options);
456
- if (!populate.children && !populate.all) {
457
- return;
46
+ if (options.validate && invalid) {
47
+ throw ValidationError.invalidPropertyName(entityName, invalid.field);
48
+ }
49
+ this.setSerializationContext(entities, populate, options);
50
+ for (const entity of entities) {
51
+ visited.add(entity);
52
+ }
53
+ for (const pop of populate) {
54
+ await this.populateField(entityName, entities, pop, options);
55
+ }
56
+ for (const entity of entities) {
57
+ visited.delete(entity);
58
+ }
458
59
  }
459
- const children = [];
460
- for (const entity of entities) {
461
- const ref = entity[field];
462
- if (Utils.isEntity(ref)) {
463
- children.push(ref);
464
- } else if (Reference.isReference(ref)) {
465
- children.push(ref.unwrap());
466
- } else if (Utils.isCollection(ref)) {
467
- children.push(...ref.getItems());
468
- } else if (ref && prop.kind === ReferenceKind.EMBEDDED) {
469
- children.push(...Utils.asArray(ref));
470
- }
60
+ /** Normalizes populate hints into a structured array of PopulateOptions, expanding dot paths and eager relations. */
61
+ normalizePopulate(entityName, populate, strategy, lookup = true, exclude) {
62
+ const meta = this.#metadata.find(entityName);
63
+ let normalized = Utils.asArray(populate).map(field => {
64
+ // oxfmt-ignore
65
+ return typeof field === 'boolean' || field.field === PopulatePath.ALL ? { all: !!field, field: meta.primaryKeys[0] } : field;
66
+ });
67
+ if (normalized.some(p => p.all)) {
68
+ normalized = this.lookupAllRelationships(entityName);
69
+ }
70
+ // convert nested `field` with dot syntax to PopulateOptions with `children` array
71
+ expandDotPaths(meta, normalized, true);
72
+ if (lookup && populate !== false) {
73
+ normalized = this.lookupEagerLoadedRelationships(entityName, normalized, strategy, '', [], exclude);
74
+ // convert nested `field` with dot syntax produced by eager relations
75
+ expandDotPaths(meta, normalized, true);
76
+ }
77
+ // merge same fields
78
+ return this.mergeNestedPopulate(normalized);
79
+ }
80
+ setSerializationContext(entities, populate, options) {
81
+ for (const entity of entities) {
82
+ helper(entity).setSerializationContext({
83
+ populate,
84
+ fields: options.fields,
85
+ exclude: options.exclude,
86
+ });
87
+ }
471
88
  }
472
- if (populated.length === 0 && !populate.children) {
473
- return;
89
+ /**
90
+ * Merge multiple populates for the same entity with different children. Also skips `*` fields, those can come from
91
+ * partial loading hints (`fields`) that are used to infer the `populate` hint if missing.
92
+ */
93
+ mergeNestedPopulate(populate) {
94
+ const tmp = populate.reduce((ret, item) => {
95
+ /* v8 ignore next */
96
+ if (item.field === PopulatePath.ALL) {
97
+ return ret;
98
+ }
99
+ if (!ret[item.field]) {
100
+ ret[item.field] = item;
101
+ return ret;
102
+ }
103
+ if (!ret[item.field].children && item.children) {
104
+ ret[item.field].children = item.children;
105
+ }
106
+ else if (ret[item.field].children && item.children) {
107
+ ret[item.field].children.push(...item.children);
108
+ }
109
+ return ret;
110
+ }, {});
111
+ return Object.values(tmp).map(item => {
112
+ if (item.children) {
113
+ item.children = this.mergeNestedPopulate(item.children);
114
+ }
115
+ return item;
116
+ });
474
117
  }
475
- const fields = this.buildFields(options.fields, prop);
476
- const innerOrderBy = Utils.asArray(options.orderBy)
477
- .filter(orderBy => Utils.isObject(orderBy[prop.name]))
478
- .map(orderBy => orderBy[prop.name]);
479
- const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType, logging, schema } = options;
480
- // oxfmt-ignore
481
- const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
482
- const visited = options.visited;
483
- for (const entity of entities) {
484
- visited.delete(entity);
118
+ /**
119
+ * preload everything in one call (this will update already existing references in IM)
120
+ */
121
+ async populateMany(entityName, entities, populate, options) {
122
+ const [field, ref] = populate.field.split(':', 2);
123
+ const meta = this.#metadata.find(entityName);
124
+ const prop = meta.properties[field];
125
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && !this.#driver.getPlatform().usesPivotTable()) {
126
+ const filtered = entities.filter(e => !e[prop.name]?.isInitialized());
127
+ if (filtered.length > 0) {
128
+ await this.populateScalar(meta, filtered, { ...options, fields: [prop.name] });
129
+ }
130
+ }
131
+ if (prop.kind === ReferenceKind.SCALAR && prop.lazy) {
132
+ const filtered = entities.filter(e => options.refresh ||
133
+ (prop.ref ? !e[prop.name]?.isInitialized() : e[prop.name] === undefined));
134
+ if (options.ignoreLazyScalarProperties || filtered.length === 0) {
135
+ return entities;
136
+ }
137
+ await this.populateScalar(meta, filtered, { ...options, fields: [prop.name] });
138
+ return entities;
139
+ }
140
+ if (prop.kind === ReferenceKind.EMBEDDED) {
141
+ return [];
142
+ }
143
+ const filtered = this.filterCollections(entities, field, options, ref);
144
+ const innerOrderBy = Utils.asArray(options.orderBy)
145
+ .filter(orderBy => (Array.isArray(orderBy[prop.name]) && orderBy[prop.name].length > 0) ||
146
+ Utils.isObject(orderBy[prop.name]))
147
+ .flatMap(orderBy => orderBy[prop.name]);
148
+ const where = await this.extractChildCondition(options, prop);
149
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && this.#driver.getPlatform().usesPivotTable()) {
150
+ const pivotOrderBy = QueryHelper.mergeOrderBy(innerOrderBy, prop.orderBy, prop.targetMeta?.orderBy);
151
+ const res = await this.findChildrenFromPivotTable(filtered, prop, options, pivotOrderBy, populate, !!ref);
152
+ return Utils.flatten(res);
153
+ }
154
+ if (prop.polymorphic && prop.polymorphTargets) {
155
+ return this.populatePolymorphic(entities, prop, options, !!ref);
156
+ }
157
+ const { items, partial } = await this.findChildren(options.filtered ?? entities, prop, populate, {
158
+ ...options,
159
+ where,
160
+ orderBy: innerOrderBy,
161
+ }, !!(ref || prop.mapToPk));
162
+ const customOrder = innerOrderBy.length > 0 || !!prop.orderBy || !!prop.targetMeta?.orderBy;
163
+ this.initializeCollections(filtered, prop, field, items, customOrder, partial);
164
+ return items;
165
+ }
166
+ async populateScalar(meta, filtered, options) {
167
+ const pk = Utils.getPrimaryKeyHash(meta.primaryKeys);
168
+ const ids = Utils.unique(filtered.map(e => Utils.getPrimaryKeyValues(e, meta, true)));
169
+ const where = this.mergePrimaryCondition(ids, pk, options, meta, this.#metadata, this.#driver.getPlatform());
170
+ const { filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, fields } = options;
171
+ await this.#em.find(meta.class, where, {
172
+ filters,
173
+ convertCustomTypes,
174
+ lockMode,
175
+ strategy,
176
+ populateWhere,
177
+ connectionType,
178
+ logging,
179
+ fields: fields,
180
+ populate: [],
181
+ });
485
182
  }
486
- const unique = Utils.unique(children);
487
- const filtered = unique.filter(e => !visited.has(e));
488
- for (const entity of entities) {
489
- visited.add(entity);
183
+ async populatePolymorphic(entities, prop, options, ref) {
184
+ const ownerMeta = this.#metadata.get(entities[0].constructor);
185
+ // Separate entities: those with loaded refs vs those needing FK load
186
+ const toPopulate = [];
187
+ const needsFkLoad = [];
188
+ for (const entity of entities) {
189
+ const refValue = entity[prop.name];
190
+ if (refValue && helper(refValue).hasPrimaryKey()) {
191
+ if ((ref && !options.refresh) || // :ref hint - already have reference
192
+ (!ref && helper(refValue).__initialized && !options.refresh) // already loaded
193
+ ) {
194
+ continue;
195
+ }
196
+ toPopulate.push(entity);
197
+ }
198
+ else if (refValue == null && !helper(entity).__loadedProperties.has(prop.name)) {
199
+ // FK columns weren't loaded (partial loading) — need to re-fetch them.
200
+ // If the property IS in __loadedProperties, the FK was loaded and is genuinely null.
201
+ needsFkLoad.push(entity);
202
+ }
203
+ }
204
+ // Load FK columns using populateScalar pattern
205
+ if (needsFkLoad.length > 0) {
206
+ await this.populateScalar(ownerMeta, needsFkLoad, {
207
+ ...options,
208
+ fields: [...ownerMeta.primaryKeys, prop.name],
209
+ });
210
+ // After loading FKs, add to toPopulate if not using :ref hint
211
+ if (!ref) {
212
+ for (const entity of needsFkLoad) {
213
+ const refValue = entity[prop.name];
214
+ if (refValue && helper(refValue).hasPrimaryKey()) {
215
+ toPopulate.push(entity);
216
+ }
217
+ }
218
+ }
219
+ }
220
+ if (toPopulate.length === 0) {
221
+ return [];
222
+ }
223
+ // Group references by target class for batch loading
224
+ const groups = new Map();
225
+ for (const entity of toPopulate) {
226
+ const refValue = Reference.unwrapReference(entity[prop.name]);
227
+ const discriminator = QueryHelper.findDiscriminatorValue(prop.discriminatorMap, helper(refValue).__meta.class);
228
+ const group = groups.get(discriminator) ?? [];
229
+ group.push(refValue);
230
+ groups.set(discriminator, group);
231
+ }
232
+ // Load each group concurrently - identity map handles merging with existing references
233
+ const allItems = [];
234
+ await Promise.all([...groups].map(async ([discriminator, children]) => {
235
+ const targetMeta = this.#metadata.find(prop.discriminatorMap[discriminator]);
236
+ await this.populateScalar(targetMeta, children, options);
237
+ allItems.push(...children);
238
+ }));
239
+ return allItems;
240
+ }
241
+ initializeCollections(filtered, prop, field, children, customOrder, partial) {
242
+ if (prop.kind === ReferenceKind.ONE_TO_MANY) {
243
+ this.initializeOneToMany(filtered, children, prop, field, partial);
244
+ }
245
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && !this.#driver.getPlatform().usesPivotTable()) {
246
+ this.initializeManyToMany(filtered, children, prop, field, customOrder, partial);
247
+ }
490
248
  }
491
- if (!prop.targetMeta) {
492
- return;
249
+ initializeOneToMany(filtered, children, prop, field, partial) {
250
+ const mapToPk = prop.targetMeta.properties[prop.mappedBy].mapToPk;
251
+ const map = {};
252
+ for (const entity of filtered) {
253
+ const key = helper(entity).getSerializedPrimaryKey();
254
+ map[key] = [];
255
+ }
256
+ for (const child of children) {
257
+ const pk = child.__helper.__data[prop.mappedBy] ?? child[prop.mappedBy];
258
+ if (pk) {
259
+ const key = helper(mapToPk ? this.#em.getReference(prop.targetMeta.class, pk) : pk).getSerializedPrimaryKey();
260
+ map[key]?.push(child);
261
+ }
262
+ }
263
+ for (const entity of filtered) {
264
+ const key = helper(entity).getSerializedPrimaryKey();
265
+ entity[field].hydrate(map[key], undefined, partial);
266
+ }
493
267
  }
494
- const populateChildren = async (targetMeta, items) => {
495
- await this.populate(targetMeta.class, items, populate.children ?? populate.all, {
496
- where: await this.extractChildCondition(options, prop, false),
497
- orderBy: innerOrderBy,
498
- fields,
499
- exclude,
500
- validate: false,
501
- lookup: false,
502
- filters,
503
- ignoreLazyScalarProperties,
504
- populateWhere,
505
- connectionType,
506
- logging,
507
- schema,
508
- // @ts-ignore not a public option, will be propagated to the populate call
509
- refresh: refresh && !filtered.every(item => options.visited.has(item)),
510
- // @ts-ignore not a public option, will be propagated to the populate call
511
- visited: options.visited,
512
- // @ts-ignore not a public option
513
- filtered,
514
- });
515
- };
516
- if (prop.polymorphic && prop.polymorphTargets) {
517
- await Promise.all(
518
- prop.polymorphTargets.map(async targetMeta => {
519
- const targetChildren = unique.filter(child => helper(child).__meta.className === targetMeta.className);
520
- if (targetChildren.length > 0) {
521
- await populateChildren(targetMeta, targetChildren);
522
- }
523
- }),
524
- );
525
- } else {
526
- await populateChildren(prop.targetMeta, unique);
268
+ initializeManyToMany(filtered, children, prop, field, customOrder, partial) {
269
+ if (prop.mappedBy) {
270
+ for (const entity of filtered) {
271
+ const items = children.filter(child => child[prop.mappedBy].contains(entity, false));
272
+ entity[field].hydrate(items, true, partial);
273
+ }
274
+ }
275
+ else {
276
+ // owning side of M:N without pivot table needs to be reordered
277
+ for (const entity of filtered) {
278
+ const order = !customOrder ? [...entity[prop.name].getItems(false)] : []; // copy order of references
279
+ const items = children.filter(child => entity[prop.name].contains(child, false));
280
+ if (!customOrder) {
281
+ items.sort((a, b) => order.indexOf(a) - order.indexOf(b));
282
+ }
283
+ entity[field].hydrate(items, true, partial);
284
+ }
285
+ }
527
286
  }
528
- }
529
- /** @internal */
530
- async findChildrenFromPivotTable(filtered, prop, options, orderBy, populate, pivotJoin) {
531
- const ids = filtered.map(e => e.__helper.__primaryKeys);
532
- const refresh = options.refresh;
533
- let where = await this.extractChildCondition(options, prop, true);
534
- const fields = this.buildFields(options.fields, prop);
535
- // oxfmt-ignore
536
- const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
537
- const populateFilter = options.populateFilter?.[prop.name];
538
- const options2 = { ...options, fields, exclude, populateFilter };
539
- ['limit', 'offset', 'first', 'last', 'before', 'after', 'overfetch'].forEach(prop => delete options2[prop]);
540
- options2.populate = populate?.children ?? [];
541
- if (!Utils.isEmpty(prop.where)) {
542
- where = { $and: [where, prop.where] };
287
+ async findChildren(entities, prop, populate, options, ref) {
288
+ const children = Utils.unique(this.getChildReferences(entities, prop, options, ref));
289
+ const meta = prop.targetMeta;
290
+ // When targetKey is set, use it for FK lookup instead of the PK
291
+ let fk = prop.targetKey ?? Utils.getPrimaryKeyHash(meta.primaryKeys);
292
+ let schema = options.schema;
293
+ const partial = !Utils.isEmpty(prop.where) || !Utils.isEmpty(options.where);
294
+ let polymorphicOwnerProp;
295
+ if (prop.kind === ReferenceKind.ONE_TO_MANY || (prop.kind === ReferenceKind.MANY_TO_MANY && !prop.owner)) {
296
+ const ownerProp = meta.properties[prop.mappedBy];
297
+ if (ownerProp.polymorphic && ownerProp.fieldNames.length >= 2) {
298
+ const idColumns = ownerProp.fieldNames.slice(1);
299
+ fk = idColumns.length === 1 ? idColumns[0] : idColumns;
300
+ polymorphicOwnerProp = ownerProp;
301
+ }
302
+ else {
303
+ fk = ownerProp.name;
304
+ }
305
+ }
306
+ if (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && !ref) {
307
+ children.length = 0;
308
+ fk = meta.properties[prop.mappedBy].name;
309
+ children.push(...this.filterByReferences(entities, prop.name, options.refresh));
310
+ }
311
+ if (children.length === 0) {
312
+ return { items: [], partial };
313
+ }
314
+ if (!schema && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
315
+ schema = children.find(e => e.__helper.__schema)?.__helper.__schema;
316
+ }
317
+ const ids = Utils.unique(children.map(e => (prop.targetKey ? e[prop.targetKey] : e.__helper.getPrimaryKey())));
318
+ let where;
319
+ if (polymorphicOwnerProp && Array.isArray(fk)) {
320
+ const conditions = ids.map(id => {
321
+ const pkValues = Object.values(id);
322
+ return Object.fromEntries(fk.map((col, idx) => [col, pkValues[idx]]));
323
+ });
324
+ where = (conditions.length === 1 ? conditions[0] : { $or: conditions });
325
+ }
326
+ else {
327
+ where = this.mergePrimaryCondition(ids, fk, options, meta, this.#metadata, this.#driver.getPlatform());
328
+ }
329
+ if (polymorphicOwnerProp) {
330
+ const parentMeta = this.#metadata.find(entities[0].constructor);
331
+ const discriminatorValue = QueryHelper.findDiscriminatorValue(polymorphicOwnerProp.discriminatorMap, parentMeta.class) ??
332
+ parentMeta.tableName;
333
+ const discriminatorColumn = polymorphicOwnerProp.fieldNames[0];
334
+ where = { $and: [where, { [discriminatorColumn]: discriminatorValue }] };
335
+ }
336
+ const fields = this.buildFields(options.fields, prop, ref);
337
+ /* eslint-disable prefer-const */
338
+ let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere = 'infer', connectionType, logging, } = options;
339
+ /* eslint-enable prefer-const */
340
+ if (typeof populateWhere === 'object') {
341
+ populateWhere = await this.extractChildCondition({ where: populateWhere }, prop);
342
+ }
343
+ if (!Utils.isEmpty(prop.where) || Raw.hasObjectFragments(prop.where)) {
344
+ where = { $and: [where, prop.where] };
345
+ }
346
+ const orderBy = QueryHelper.mergeOrderBy(options.orderBy, prop.orderBy);
347
+ const items = await this.#em.find(meta.class, where, {
348
+ filters,
349
+ convertCustomTypes,
350
+ lockMode,
351
+ populateWhere,
352
+ logging,
353
+ orderBy,
354
+ populate: populate.children ?? populate.all ?? [],
355
+ exclude: Array.isArray(options.exclude)
356
+ ? Utils.extractChildElements(options.exclude, prop.name)
357
+ : options.exclude,
358
+ strategy,
359
+ fields,
360
+ schema,
361
+ connectionType,
362
+ // @ts-ignore not a public option, will be propagated to the populate call
363
+ refresh: refresh && !children.every(item => options.visited.has(item)),
364
+ // @ts-ignore not a public option, will be propagated to the populate call
365
+ visited: options.visited,
366
+ });
367
+ // For targetKey relations, wire up loaded entities to parent references
368
+ // This is needed because the references were created under alternate key,
369
+ // but loaded entities are stored under PK, so they don't automatically merge
370
+ if (prop.targetKey && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
371
+ const itemsByKey = new Map();
372
+ for (const item of items) {
373
+ itemsByKey.set('' + item[prop.targetKey], item);
374
+ }
375
+ for (const entity of entities) {
376
+ const ref = entity[prop.name];
377
+ /* v8 ignore next */
378
+ if (!ref) {
379
+ continue;
380
+ }
381
+ // oxfmt-ignore
382
+ const keyValue = '' + (Reference.isReference(ref) ? ref.unwrap()[prop.targetKey] : ref[prop.targetKey]);
383
+ const loadedItem = itemsByKey.get(keyValue);
384
+ if (loadedItem) {
385
+ entity[prop.name] = (Reference.isReference(ref) ? Reference.create(loadedItem) : loadedItem);
386
+ }
387
+ }
388
+ }
389
+ if ([ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && items.length !== children.length) {
390
+ const nullVal = this.#em.config.get('forceUndefined') ? undefined : null;
391
+ const itemsMap = new Set();
392
+ const childrenMap = new Set();
393
+ // Use targetKey value if set, otherwise use serialized PK
394
+ const getKey = (e) => (prop.targetKey ? '' + e[prop.targetKey] : helper(e).getSerializedPrimaryKey());
395
+ for (const item of items) {
396
+ /* v8 ignore next */
397
+ itemsMap.add(getKey(item));
398
+ }
399
+ for (const child of children) {
400
+ childrenMap.add(getKey(child));
401
+ }
402
+ for (const entity of entities) {
403
+ const ref = entity[prop.name] ?? {};
404
+ const key = helper(ref) ? getKey(ref) : undefined;
405
+ if (key && childrenMap.has(key) && !itemsMap.has(key)) {
406
+ entity[prop.name] = nullVal;
407
+ helper(entity).__originalEntityData[prop.name] = null;
408
+ }
409
+ }
410
+ }
411
+ for (const item of items) {
412
+ if (ref && !helper(item).__onLoadFired) {
413
+ helper(item).__initialized = false;
414
+ this.#em.getUnitOfWork().unmarkAsLoaded(item);
415
+ }
416
+ }
417
+ return { items, partial };
418
+ }
419
+ mergePrimaryCondition(ids, pk, options, meta, metadata, platform) {
420
+ const cond1 = QueryHelper.processWhere({
421
+ where: { [pk]: { $in: ids } },
422
+ entityName: meta.class,
423
+ metadata,
424
+ platform,
425
+ convertCustomTypes: !options.convertCustomTypes,
426
+ });
427
+ const where = { ...options.where };
428
+ Utils.dropUndefinedProperties(where);
429
+ return where[pk] ? { $and: [cond1, where] } : { ...cond1, ...where };
430
+ }
431
+ async populateField(entityName, entities, populate, options) {
432
+ const field = populate.field.split(':')[0];
433
+ const prop = this.#metadata.find(entityName).properties[field];
434
+ if (prop.kind === ReferenceKind.SCALAR && !prop.lazy) {
435
+ return;
436
+ }
437
+ options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
438
+ const populated = await this.populateMany(entityName, entities, populate, options);
439
+ if (!populate.children && !populate.all) {
440
+ return;
441
+ }
442
+ const children = [];
443
+ for (const entity of entities) {
444
+ const ref = entity[field];
445
+ if (Utils.isEntity(ref)) {
446
+ children.push(ref);
447
+ }
448
+ else if (Reference.isReference(ref)) {
449
+ children.push(ref.unwrap());
450
+ }
451
+ else if (Utils.isCollection(ref)) {
452
+ children.push(...ref.getItems());
453
+ }
454
+ else if (ref && prop.kind === ReferenceKind.EMBEDDED) {
455
+ children.push(...Utils.asArray(ref));
456
+ }
457
+ }
458
+ if (populated.length === 0 && !populate.children) {
459
+ return;
460
+ }
461
+ const fields = this.buildFields(options.fields, prop);
462
+ const innerOrderBy = Utils.asArray(options.orderBy)
463
+ .filter(orderBy => Utils.isObject(orderBy[prop.name]))
464
+ .map(orderBy => orderBy[prop.name]);
465
+ const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType, logging, schema } = options;
466
+ // oxfmt-ignore
467
+ const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
468
+ const visited = options.visited;
469
+ for (const entity of entities) {
470
+ visited.delete(entity);
471
+ }
472
+ const unique = Utils.unique(children);
473
+ const filtered = unique.filter(e => !visited.has(e));
474
+ for (const entity of entities) {
475
+ visited.add(entity);
476
+ }
477
+ if (!prop.targetMeta) {
478
+ return;
479
+ }
480
+ const populateChildren = async (targetMeta, items) => {
481
+ await this.populate(targetMeta.class, items, populate.children ?? populate.all, {
482
+ where: (await this.extractChildCondition(options, prop, false)),
483
+ orderBy: innerOrderBy,
484
+ fields,
485
+ exclude,
486
+ validate: false,
487
+ lookup: false,
488
+ filters,
489
+ ignoreLazyScalarProperties,
490
+ populateWhere,
491
+ connectionType,
492
+ logging,
493
+ schema,
494
+ // @ts-ignore not a public option, will be propagated to the populate call
495
+ refresh: refresh && !filtered.every(item => options.visited.has(item)),
496
+ // @ts-ignore not a public option, will be propagated to the populate call
497
+ visited: options.visited,
498
+ // @ts-ignore not a public option
499
+ filtered,
500
+ });
501
+ };
502
+ if (prop.polymorphic && prop.polymorphTargets) {
503
+ await Promise.all(prop.polymorphTargets.map(async (targetMeta) => {
504
+ const targetChildren = unique.filter(child => helper(child).__meta.className === targetMeta.className);
505
+ if (targetChildren.length > 0) {
506
+ await populateChildren(targetMeta, targetChildren);
507
+ }
508
+ }));
509
+ }
510
+ else {
511
+ await populateChildren(prop.targetMeta, unique);
512
+ }
543
513
  }
544
- const map = await this.#driver.loadFromPivotTable(
545
- prop,
546
- ids,
547
- where,
548
- orderBy,
549
- this.#em.getTransactionContext(),
550
- options2,
551
- pivotJoin,
552
- );
553
- const children = [];
554
- for (let i = 0; i < filtered.length; i++) {
555
- const entity = filtered[i];
556
- const items = map[Utils.getPrimaryKeyHash(ids[i])].map(item => {
557
- if (pivotJoin) {
558
- return this.#em.getReference(prop.targetMeta.class, item, {
559
- convertCustomTypes: true,
560
- schema: options.schema ?? this.#em.config.get('schema'),
561
- });
562
- }
563
- const entity = this.#em.getEntityFactory().create(prop.targetMeta.class, item, {
564
- refresh,
565
- merge: true,
566
- convertCustomTypes: true,
567
- schema: options.schema ?? this.#em.config.get('schema'),
514
+ /** @internal */
515
+ async findChildrenFromPivotTable(filtered, prop, options, orderBy, populate, pivotJoin) {
516
+ const ids = filtered.map(e => e.__helper.__primaryKeys);
517
+ const refresh = options.refresh;
518
+ let where = await this.extractChildCondition(options, prop, true);
519
+ const fields = this.buildFields(options.fields, prop);
520
+ // oxfmt-ignore
521
+ const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
522
+ const populateFilter = options.populateFilter?.[prop.name];
523
+ const options2 = { ...options, fields, exclude, populateFilter };
524
+ ['limit', 'offset', 'first', 'last', 'before', 'after', 'overfetch'].forEach(prop => delete options2[prop]);
525
+ options2.populate = populate?.children ?? [];
526
+ if (!Utils.isEmpty(prop.where)) {
527
+ where = { $and: [where, prop.where] };
528
+ }
529
+ const map = await this.#driver.loadFromPivotTable(prop, ids, where, orderBy, this.#em.getTransactionContext(), options2, pivotJoin);
530
+ const children = [];
531
+ for (let i = 0; i < filtered.length; i++) {
532
+ const entity = filtered[i];
533
+ const items = map[Utils.getPrimaryKeyHash(ids[i])].map(item => {
534
+ if (pivotJoin) {
535
+ return this.#em.getReference(prop.targetMeta.class, item, {
536
+ convertCustomTypes: true,
537
+ schema: options.schema ?? this.#em.config.get('schema'),
538
+ });
539
+ }
540
+ const entity = this.#em.getEntityFactory().create(prop.targetMeta.class, item, {
541
+ refresh,
542
+ merge: true,
543
+ convertCustomTypes: true,
544
+ schema: options.schema ?? this.#em.config.get('schema'),
545
+ });
546
+ return this.#em.getUnitOfWork().register(entity, item, { refresh, loaded: true });
547
+ });
548
+ entity[prop.name].hydrate(items, true);
549
+ children.push(items);
550
+ }
551
+ return children;
552
+ }
553
+ async extractChildCondition(options, prop, filters = false) {
554
+ const where = options.where;
555
+ const subCond = Utils.isPlainObject(where[prop.name]) ? where[prop.name] : {};
556
+ const meta2 = prop.targetMeta;
557
+ const pk = Utils.getPrimaryKeyHash(meta2.primaryKeys);
558
+ ['$and', '$or'].forEach(op => {
559
+ if (where[op]) {
560
+ const child = where[op]
561
+ .map((cond) => cond[prop.name])
562
+ .filter((sub) => sub != null &&
563
+ !(Utils.isPlainObject(sub) && Utils.getObjectQueryKeys(sub).every(key => Utils.isOperator(key, false))))
564
+ .map((cond) => {
565
+ if (Utils.isPrimaryKey(cond)) {
566
+ return { [pk]: cond };
567
+ }
568
+ return cond;
569
+ });
570
+ if (child.length > 0) {
571
+ subCond[op] = child;
572
+ }
573
+ }
568
574
  });
569
- return this.#em.getUnitOfWork().register(entity, item, { refresh, loaded: true });
570
- });
571
- entity[prop.name].hydrate(items, true);
572
- children.push(items);
575
+ const operators = Object.keys(subCond).filter(key => Utils.isOperator(key, false));
576
+ if (operators.length > 0) {
577
+ operators.forEach(op => {
578
+ subCond[pk] ??= {};
579
+ subCond[pk][op] = subCond[op];
580
+ delete subCond[op];
581
+ });
582
+ }
583
+ if (filters) {
584
+ return this.#em.applyFilters(meta2.class, subCond, options.filters, 'read', options);
585
+ }
586
+ return subCond;
573
587
  }
574
- return children;
575
- }
576
- async extractChildCondition(options, prop, filters = false) {
577
- const where = options.where;
578
- const subCond = Utils.isPlainObject(where[prop.name]) ? where[prop.name] : {};
579
- const meta2 = prop.targetMeta;
580
- const pk = Utils.getPrimaryKeyHash(meta2.primaryKeys);
581
- ['$and', '$or'].forEach(op => {
582
- if (where[op]) {
583
- const child = where[op]
584
- .map(cond => cond[prop.name])
585
- .filter(
586
- sub =>
587
- sub != null &&
588
- !(Utils.isPlainObject(sub) && Utils.getObjectQueryKeys(sub).every(key => Utils.isOperator(key, false))),
589
- )
590
- .map(cond => {
591
- if (Utils.isPrimaryKey(cond)) {
592
- return { [pk]: cond };
588
+ buildFields(fields = [], prop, ref) {
589
+ if (ref) {
590
+ fields = prop.targetMeta.primaryKeys.map(targetPkName => `${prop.name}.${targetPkName}`);
591
+ }
592
+ const ret = fields.reduce((ret, f) => {
593
+ if (Utils.isPlainObject(f)) {
594
+ Utils.keys(f)
595
+ .filter(ff => ff === prop.name)
596
+ .forEach(ff => ret.push(...f[ff]));
597
+ }
598
+ else if (f.toString().includes('.')) {
599
+ const parts = f.toString().split('.');
600
+ const propName = parts.shift();
601
+ const childPropName = parts.join('.');
602
+ /* v8 ignore next */
603
+ if (propName === prop.name) {
604
+ ret.push(childPropName);
605
+ }
593
606
  }
594
- return cond;
595
- });
596
- if (child.length > 0) {
597
- subCond[op] = child;
598
- }
599
- }
600
- });
601
- const operators = Object.keys(subCond).filter(key => Utils.isOperator(key, false));
602
- if (operators.length > 0) {
603
- operators.forEach(op => {
604
- subCond[pk] ??= {};
605
- subCond[pk][op] = subCond[op];
606
- delete subCond[op];
607
- });
607
+ return ret;
608
+ }, []);
609
+ // we need to automatically select the FKs too, e.g. for 1:m relations to be able to wire them with the items
610
+ if (prop.kind === ReferenceKind.ONE_TO_MANY || prop.kind === ReferenceKind.MANY_TO_MANY) {
611
+ const owner = prop.targetMeta.properties[prop.mappedBy];
612
+ // when the owning FK is lazy, we need to explicitly select it even without user-provided fields,
613
+ // otherwise the driver will exclude it and we won't be able to map children to their parent collections
614
+ if (owner && !ret.includes(owner.name) && (ret.length > 0 || owner.lazy)) {
615
+ ret.push(owner.name);
616
+ }
617
+ }
618
+ if (ret.length === 0) {
619
+ return undefined;
620
+ }
621
+ return ret;
608
622
  }
609
- if (filters) {
610
- return this.#em.applyFilters(meta2.class, subCond, options.filters, 'read', options);
623
+ getChildReferences(entities, prop, options, ref) {
624
+ const filtered = this.filterCollections(entities, prop.name, options, ref);
625
+ if (prop.kind === ReferenceKind.ONE_TO_MANY) {
626
+ return filtered.map(e => e[prop.name].owner);
627
+ }
628
+ if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
629
+ return filtered.reduce((a, b) => {
630
+ a.push(...b[prop.name].getItems());
631
+ return a;
632
+ }, []);
633
+ }
634
+ if (prop.kind === ReferenceKind.MANY_TO_MANY) {
635
+ // inverse side
636
+ return filtered;
637
+ }
638
+ // MANY_TO_ONE or ONE_TO_ONE
639
+ return this.filterReferences(entities, prop.name, options, ref);
611
640
  }
612
- return subCond;
613
- }
614
- buildFields(fields = [], prop, ref) {
615
- if (ref) {
616
- fields = prop.targetMeta.primaryKeys.map(targetPkName => `${prop.name}.${targetPkName}`);
641
+ filterCollections(entities, field, options, ref) {
642
+ if (options.refresh) {
643
+ return entities.filter(e => e[field]);
644
+ }
645
+ return entities.filter(e => Utils.isCollection(e[field]) && !e[field].isInitialized(!ref));
617
646
  }
618
- const ret = fields.reduce((ret, f) => {
619
- if (Utils.isPlainObject(f)) {
620
- Utils.keys(f)
621
- .filter(ff => ff === prop.name)
622
- .forEach(ff => ret.push(...f[ff]));
623
- } else if (f.toString().includes('.')) {
624
- const parts = f.toString().split('.');
625
- const propName = parts.shift();
626
- const childPropName = parts.join('.');
647
+ isPropertyLoaded(entity, field) {
648
+ if (!entity || field === '*') {
649
+ return true;
650
+ }
651
+ const wrapped = helper(entity);
652
+ if (!field.includes('.')) {
653
+ return wrapped.__loadedProperties.has(field);
654
+ }
655
+ const [f, ...r] = field.split('.');
627
656
  /* v8 ignore next */
628
- if (propName === prop.name) {
629
- ret.push(childPropName);
630
- }
631
- }
632
- return ret;
633
- }, []);
634
- // we need to automatically select the FKs too, e.g. for 1:m relations to be able to wire them with the items
635
- if (prop.kind === ReferenceKind.ONE_TO_MANY || prop.kind === ReferenceKind.MANY_TO_MANY) {
636
- const owner = prop.targetMeta.properties[prop.mappedBy];
637
- // when the owning FK is lazy, we need to explicitly select it even without user-provided fields,
638
- // otherwise the driver will exclude it and we won't be able to map children to their parent collections
639
- if (owner && !ret.includes(owner.name) && (ret.length > 0 || owner.lazy)) {
640
- ret.push(owner.name);
641
- }
642
- }
643
- if (ret.length === 0) {
644
- return undefined;
645
- }
646
- return ret;
647
- }
648
- getChildReferences(entities, prop, options, ref) {
649
- const filtered = this.filterCollections(entities, prop.name, options, ref);
650
- if (prop.kind === ReferenceKind.ONE_TO_MANY) {
651
- return filtered.map(e => e[prop.name].owner);
652
- }
653
- if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
654
- return filtered.reduce((a, b) => {
655
- a.push(...b[prop.name].getItems());
656
- return a;
657
- }, []);
658
- }
659
- if (prop.kind === ReferenceKind.MANY_TO_MANY) {
660
- // inverse side
661
- return filtered;
662
- }
663
- // MANY_TO_ONE or ONE_TO_ONE
664
- return this.filterReferences(entities, prop.name, options, ref);
665
- }
666
- filterCollections(entities, field, options, ref) {
667
- if (options.refresh) {
668
- return entities.filter(e => e[field]);
669
- }
670
- return entities.filter(e => Utils.isCollection(e[field]) && !e[field].isInitialized(!ref));
671
- }
672
- isPropertyLoaded(entity, field) {
673
- if (!entity || field === '*') {
674
- return true;
675
- }
676
- const wrapped = helper(entity);
677
- if (!field.includes('.')) {
678
- return wrapped.__loadedProperties.has(field);
679
- }
680
- const [f, ...r] = field.split('.');
681
- /* v8 ignore next */
682
- if (!wrapped.__loadedProperties.has(f) || !wrapped.__meta.properties[f]?.targetMeta) {
683
- return false;
684
- }
685
- if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(wrapped.__meta.properties[f].kind)) {
686
- return entity[f].getItems(false).every(item => this.isPropertyLoaded(item, r.join('.')));
687
- }
688
- return this.isPropertyLoaded(entity[f], r.join('.'));
689
- }
690
- filterReferences(entities, field, options, ref) {
691
- if (ref) {
692
- return [];
657
+ if (!wrapped.__loadedProperties.has(f) || !wrapped.__meta.properties[f]?.targetMeta) {
658
+ return false;
659
+ }
660
+ if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(wrapped.__meta.properties[f].kind)) {
661
+ return entity[f].getItems(false).every((item) => this.isPropertyLoaded(item, r.join('.')));
662
+ }
663
+ return this.isPropertyLoaded(entity[f], r.join('.'));
693
664
  }
694
- const children = entities.filter(e => Utils.isEntity(e[field], true));
695
- if (options.refresh) {
696
- return children.map(e => Reference.unwrapReference(e[field]));
665
+ filterReferences(entities, field, options, ref) {
666
+ if (ref) {
667
+ return [];
668
+ }
669
+ const children = entities.filter(e => Utils.isEntity(e[field], true));
670
+ if (options.refresh) {
671
+ return children.map(e => Reference.unwrapReference(e[field]));
672
+ }
673
+ if (options.fields) {
674
+ return children
675
+ .map(e => Reference.unwrapReference(e[field]))
676
+ .filter(target => {
677
+ const wrapped = helper(target);
678
+ const childFields = options.fields
679
+ .filter(f => f.startsWith(`${field}.`))
680
+ .map(f => f.substring(field.length + 1));
681
+ return !wrapped.__initialized || !childFields.every(cf => this.isPropertyLoaded(target, cf));
682
+ });
683
+ }
684
+ return children
685
+ .filter(e => !e[field].__helper.__initialized)
686
+ .map(e => Reference.unwrapReference(e[field]));
697
687
  }
698
- if (options.fields) {
699
- return children
700
- .map(e => Reference.unwrapReference(e[field]))
701
- .filter(target => {
702
- const wrapped = helper(target);
703
- const childFields = options.fields
704
- .filter(f => f.startsWith(`${field}.`))
705
- .map(f => f.substring(field.length + 1));
706
- return !wrapped.__initialized || !childFields.every(cf => this.isPropertyLoaded(target, cf));
688
+ filterByReferences(entities, field, refresh) {
689
+ /* v8 ignore next */
690
+ if (refresh) {
691
+ return entities;
692
+ }
693
+ return entities.filter(e => e[field] !== null && !e[field]?.__helper?.__initialized);
694
+ }
695
+ lookupAllRelationships(entityName) {
696
+ const ret = [];
697
+ const meta = this.#metadata.find(entityName);
698
+ meta.relations.forEach(prop => {
699
+ ret.push({
700
+ field: this.getRelationName(meta, prop),
701
+ // force select-in strategy when populating all relations as otherwise we could cause infinite loops when self-referencing
702
+ strategy: LoadStrategy.SELECT_IN,
703
+ // no need to look up populate children recursively as we just pass `all: true` here
704
+ all: true,
705
+ });
707
706
  });
707
+ return ret;
708
708
  }
709
- return children.filter(e => !e[field].__helper.__initialized).map(e => Reference.unwrapReference(e[field]));
710
- }
711
- filterByReferences(entities, field, refresh) {
712
- /* v8 ignore next */
713
- if (refresh) {
714
- return entities;
715
- }
716
- return entities.filter(e => e[field] !== null && !e[field]?.__helper?.__initialized);
717
- }
718
- lookupAllRelationships(entityName) {
719
- const ret = [];
720
- const meta = this.#metadata.find(entityName);
721
- meta.relations.forEach(prop => {
722
- ret.push({
723
- field: this.getRelationName(meta, prop),
724
- // force select-in strategy when populating all relations as otherwise we could cause infinite loops when self-referencing
725
- strategy: LoadStrategy.SELECT_IN,
726
- // no need to look up populate children recursively as we just pass `all: true` here
727
- all: true,
728
- });
729
- });
730
- return ret;
731
- }
732
- getRelationName(meta, prop) {
733
- if (!prop.embedded) {
734
- return prop.name;
735
- }
736
- return `${this.getRelationName(meta, meta.properties[prop.embedded[0]])}.${prop.embedded[1]}`;
737
- }
738
- lookupEagerLoadedRelationships(entityName, populate, strategy, prefix = '', visited = [], exclude) {
739
- const meta = this.#metadata.find(entityName);
740
- if (!meta && !prefix) {
741
- return populate;
709
+ getRelationName(meta, prop) {
710
+ if (!prop.embedded) {
711
+ return prop.name;
712
+ }
713
+ return `${this.getRelationName(meta, meta.properties[prop.embedded[0]])}.${prop.embedded[1]}`;
742
714
  }
743
- if (!meta || visited.includes(meta)) {
744
- return [];
715
+ lookupEagerLoadedRelationships(entityName, populate, strategy, prefix = '', visited = [], exclude) {
716
+ const meta = this.#metadata.find(entityName);
717
+ if (!meta && !prefix) {
718
+ return populate;
719
+ }
720
+ if (!meta || visited.includes(meta)) {
721
+ return [];
722
+ }
723
+ visited.push(meta);
724
+ const ret = prefix === '' ? [...populate] : [];
725
+ meta.relations
726
+ .filter(prop => {
727
+ const field = this.getRelationName(meta, prop);
728
+ const prefixed = prefix ? `${prefix}.${field}` : field;
729
+ const isExcluded = exclude?.includes(prefixed);
730
+ const eager = prop.eager && !populate.some(p => p.field === `${prop.name}:ref`);
731
+ const populated = populate.some(p => p.field === prop.name);
732
+ const disabled = populate.some(p => p.field === prop.name && p.all === false);
733
+ return !disabled && !isExcluded && (eager || populated);
734
+ })
735
+ .forEach(prop => {
736
+ const field = this.getRelationName(meta, prop);
737
+ const prefixed = prefix ? `${prefix}.${field}` : field;
738
+ const nestedPopulate = populate
739
+ .filter(p => p.field === prop.name)
740
+ .flatMap(p => p.children)
741
+ .filter(Boolean);
742
+ const nested = this.lookupEagerLoadedRelationships(prop.targetMeta.class, nestedPopulate, strategy, prefixed, visited.slice(), exclude);
743
+ if (nested.length > 0) {
744
+ ret.push(...nested);
745
+ }
746
+ else {
747
+ const selfReferencing = [meta.tableName, ...visited.map(m => m.tableName)].includes(prop.targetMeta.tableName) && prop.eager;
748
+ ret.push({
749
+ field: prefixed,
750
+ // enforce select-in strategy for self-referencing relations
751
+ strategy: selfReferencing ? LoadStrategy.SELECT_IN : (strategy ?? prop.strategy),
752
+ });
753
+ }
754
+ });
755
+ return ret;
745
756
  }
746
- visited.push(meta);
747
- const ret = prefix === '' ? [...populate] : [];
748
- meta.relations
749
- .filter(prop => {
750
- const field = this.getRelationName(meta, prop);
751
- const prefixed = prefix ? `${prefix}.${field}` : field;
752
- const isExcluded = exclude?.includes(prefixed);
753
- const eager = prop.eager && !populate.some(p => p.field === `${prop.name}:ref`);
754
- const populated = populate.some(p => p.field === prop.name);
755
- const disabled = populate.some(p => p.field === prop.name && p.all === false);
756
- return !disabled && !isExcluded && (eager || populated);
757
- })
758
- .forEach(prop => {
759
- const field = this.getRelationName(meta, prop);
760
- const prefixed = prefix ? `${prefix}.${field}` : field;
761
- const nestedPopulate = populate
762
- .filter(p => p.field === prop.name)
763
- .flatMap(p => p.children)
764
- .filter(Boolean);
765
- const nested = this.lookupEagerLoadedRelationships(
766
- prop.targetMeta.class,
767
- nestedPopulate,
768
- strategy,
769
- prefixed,
770
- visited.slice(),
771
- exclude,
772
- );
773
- if (nested.length > 0) {
774
- ret.push(...nested);
775
- } else {
776
- const selfReferencing =
777
- [meta.tableName, ...visited.map(m => m.tableName)].includes(prop.targetMeta.tableName) && prop.eager;
778
- ret.push({
779
- field: prefixed,
780
- // enforce select-in strategy for self-referencing relations
781
- strategy: selfReferencing ? LoadStrategy.SELECT_IN : (strategy ?? prop.strategy),
782
- });
783
- }
784
- });
785
- return ret;
786
- }
787
757
  }