@mikro-orm/core 7.0.2 → 7.0.3-dev.0

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 (205) hide show
  1. package/EntityManager.d.ts +583 -883
  2. package/EntityManager.js +1869 -1897
  3. package/MikroORM.d.ts +74 -103
  4. package/MikroORM.js +179 -178
  5. package/cache/CacheAdapter.d.ts +36 -36
  6. package/cache/FileCacheAdapter.d.ts +24 -30
  7. package/cache/FileCacheAdapter.js +78 -80
  8. package/cache/GeneratedCacheAdapter.d.ts +20 -18
  9. package/cache/GeneratedCacheAdapter.js +30 -30
  10. package/cache/MemoryCacheAdapter.d.ts +20 -18
  11. package/cache/MemoryCacheAdapter.js +36 -35
  12. package/cache/NullCacheAdapter.d.ts +16 -16
  13. package/cache/NullCacheAdapter.js +24 -24
  14. package/connections/Connection.d.ts +84 -95
  15. package/connections/Connection.js +168 -165
  16. package/drivers/DatabaseDriver.d.ts +80 -186
  17. package/drivers/DatabaseDriver.js +443 -450
  18. package/drivers/IDatabaseDriver.d.ts +301 -440
  19. package/entity/BaseEntity.d.ts +83 -120
  20. package/entity/BaseEntity.js +43 -43
  21. package/entity/Collection.d.ts +179 -212
  22. package/entity/Collection.js +721 -727
  23. package/entity/EntityAssigner.d.ts +77 -88
  24. package/entity/EntityAssigner.js +230 -231
  25. package/entity/EntityFactory.d.ts +54 -66
  26. package/entity/EntityFactory.js +383 -425
  27. package/entity/EntityHelper.d.ts +22 -34
  28. package/entity/EntityHelper.js +267 -280
  29. package/entity/EntityIdentifier.d.ts +4 -4
  30. package/entity/EntityIdentifier.js +10 -10
  31. package/entity/EntityLoader.d.ts +73 -103
  32. package/entity/EntityLoader.js +723 -753
  33. package/entity/EntityRepository.d.ts +201 -316
  34. package/entity/EntityRepository.js +213 -213
  35. package/entity/PolymorphicRef.d.ts +5 -5
  36. package/entity/PolymorphicRef.js +10 -10
  37. package/entity/Reference.d.ts +82 -126
  38. package/entity/Reference.js +274 -278
  39. package/entity/WrappedEntity.d.ts +72 -115
  40. package/entity/WrappedEntity.js +166 -168
  41. package/entity/defineEntity.d.ts +614 -1280
  42. package/entity/defineEntity.js +511 -520
  43. package/entity/utils.d.ts +3 -13
  44. package/entity/utils.js +73 -71
  45. package/entity/validators.js +43 -43
  46. package/entity/wrap.js +8 -8
  47. package/enums.d.ts +253 -258
  48. package/enums.js +252 -251
  49. package/errors.d.ts +72 -114
  50. package/errors.js +253 -350
  51. package/events/EventManager.d.ts +14 -26
  52. package/events/EventManager.js +77 -79
  53. package/events/EventSubscriber.d.ts +29 -29
  54. package/events/TransactionEventBroadcaster.d.ts +8 -15
  55. package/events/TransactionEventBroadcaster.js +14 -14
  56. package/exceptions.d.ts +40 -23
  57. package/exceptions.js +52 -35
  58. package/hydration/Hydrator.d.ts +17 -42
  59. package/hydration/Hydrator.js +43 -43
  60. package/hydration/ObjectHydrator.d.ts +17 -50
  61. package/hydration/ObjectHydrator.js +416 -479
  62. package/index.d.ts +2 -116
  63. package/index.js +1 -10
  64. package/logging/DefaultLogger.d.ts +32 -34
  65. package/logging/DefaultLogger.js +86 -86
  66. package/logging/Logger.d.ts +41 -41
  67. package/logging/SimpleLogger.d.ts +11 -13
  68. package/logging/SimpleLogger.js +22 -22
  69. package/logging/colors.d.ts +6 -6
  70. package/logging/colors.js +10 -11
  71. package/logging/inspect.js +7 -7
  72. package/metadata/EntitySchema.d.ts +127 -211
  73. package/metadata/EntitySchema.js +398 -397
  74. package/metadata/MetadataDiscovery.d.ts +114 -114
  75. package/metadata/MetadataDiscovery.js +1863 -1947
  76. package/metadata/MetadataProvider.d.ts +21 -24
  77. package/metadata/MetadataProvider.js +84 -82
  78. package/metadata/MetadataStorage.d.ts +32 -38
  79. package/metadata/MetadataStorage.js +118 -118
  80. package/metadata/MetadataValidator.d.ts +39 -39
  81. package/metadata/MetadataValidator.js +338 -381
  82. package/metadata/discover-entities.d.ts +2 -5
  83. package/metadata/discover-entities.js +27 -27
  84. package/metadata/types.d.ts +531 -615
  85. package/naming-strategy/AbstractNamingStrategy.d.ts +39 -54
  86. package/naming-strategy/AbstractNamingStrategy.js +85 -90
  87. package/naming-strategy/EntityCaseNamingStrategy.d.ts +6 -6
  88. package/naming-strategy/EntityCaseNamingStrategy.js +22 -22
  89. package/naming-strategy/MongoNamingStrategy.d.ts +6 -6
  90. package/naming-strategy/MongoNamingStrategy.js +18 -18
  91. package/naming-strategy/NamingStrategy.d.ts +99 -109
  92. package/naming-strategy/UnderscoreNamingStrategy.d.ts +7 -7
  93. package/naming-strategy/UnderscoreNamingStrategy.js +21 -21
  94. package/not-supported.js +4 -7
  95. package/package.json +1 -1
  96. package/platforms/ExceptionConverter.d.ts +1 -1
  97. package/platforms/ExceptionConverter.js +4 -4
  98. package/platforms/Platform.d.ts +299 -308
  99. package/platforms/Platform.js +636 -659
  100. package/serialization/EntitySerializer.d.ts +26 -48
  101. package/serialization/EntitySerializer.js +218 -224
  102. package/serialization/EntityTransformer.d.ts +6 -10
  103. package/serialization/EntityTransformer.js +217 -219
  104. package/serialization/SerializationContext.d.ts +23 -27
  105. package/serialization/SerializationContext.js +105 -105
  106. package/types/ArrayType.d.ts +8 -8
  107. package/types/ArrayType.js +33 -33
  108. package/types/BigIntType.d.ts +10 -17
  109. package/types/BigIntType.js +37 -37
  110. package/types/BlobType.d.ts +3 -3
  111. package/types/BlobType.js +13 -13
  112. package/types/BooleanType.d.ts +4 -4
  113. package/types/BooleanType.js +12 -12
  114. package/types/CharacterType.d.ts +2 -2
  115. package/types/CharacterType.js +6 -6
  116. package/types/DateTimeType.d.ts +5 -5
  117. package/types/DateTimeType.js +15 -15
  118. package/types/DateType.d.ts +5 -5
  119. package/types/DateType.js +15 -15
  120. package/types/DecimalType.d.ts +7 -7
  121. package/types/DecimalType.js +26 -26
  122. package/types/DoubleType.d.ts +3 -3
  123. package/types/DoubleType.js +12 -12
  124. package/types/EnumArrayType.d.ts +5 -5
  125. package/types/EnumArrayType.js +24 -24
  126. package/types/EnumType.d.ts +3 -3
  127. package/types/EnumType.js +11 -11
  128. package/types/FloatType.d.ts +3 -3
  129. package/types/FloatType.js +9 -9
  130. package/types/IntegerType.d.ts +3 -3
  131. package/types/IntegerType.js +9 -9
  132. package/types/IntervalType.d.ts +4 -4
  133. package/types/IntervalType.js +12 -12
  134. package/types/JsonType.d.ts +8 -8
  135. package/types/JsonType.js +32 -32
  136. package/types/MediumIntType.d.ts +1 -1
  137. package/types/MediumIntType.js +3 -3
  138. package/types/SmallIntType.d.ts +3 -3
  139. package/types/SmallIntType.js +9 -9
  140. package/types/StringType.d.ts +4 -4
  141. package/types/StringType.js +12 -12
  142. package/types/TextType.d.ts +3 -3
  143. package/types/TextType.js +9 -9
  144. package/types/TimeType.d.ts +5 -5
  145. package/types/TimeType.js +17 -17
  146. package/types/TinyIntType.d.ts +3 -3
  147. package/types/TinyIntType.js +10 -10
  148. package/types/Type.d.ts +79 -83
  149. package/types/Type.js +82 -82
  150. package/types/Uint8ArrayType.d.ts +4 -4
  151. package/types/Uint8ArrayType.js +21 -21
  152. package/types/UnknownType.d.ts +4 -4
  153. package/types/UnknownType.js +12 -12
  154. package/types/UuidType.d.ts +5 -5
  155. package/types/UuidType.js +19 -19
  156. package/types/index.d.ts +49 -75
  157. package/types/index.js +26 -52
  158. package/typings.d.ts +729 -1211
  159. package/typings.js +231 -244
  160. package/unit-of-work/ChangeSet.d.ts +26 -26
  161. package/unit-of-work/ChangeSet.js +56 -56
  162. package/unit-of-work/ChangeSetComputer.d.ts +12 -12
  163. package/unit-of-work/ChangeSetComputer.js +170 -178
  164. package/unit-of-work/ChangeSetPersister.d.ts +44 -63
  165. package/unit-of-work/ChangeSetPersister.js +421 -442
  166. package/unit-of-work/CommitOrderCalculator.d.ts +40 -40
  167. package/unit-of-work/CommitOrderCalculator.js +88 -89
  168. package/unit-of-work/IdentityMap.d.ts +31 -31
  169. package/unit-of-work/IdentityMap.js +105 -105
  170. package/unit-of-work/UnitOfWork.d.ts +141 -181
  171. package/unit-of-work/UnitOfWork.js +1183 -1200
  172. package/utils/AbstractMigrator.d.ts +91 -111
  173. package/utils/AbstractMigrator.js +275 -275
  174. package/utils/AbstractSchemaGenerator.d.ts +34 -43
  175. package/utils/AbstractSchemaGenerator.js +122 -121
  176. package/utils/AsyncContext.d.ts +3 -3
  177. package/utils/AsyncContext.js +35 -34
  178. package/utils/Configuration.d.ts +808 -852
  179. package/utils/Configuration.js +344 -359
  180. package/utils/Cursor.d.ts +22 -40
  181. package/utils/Cursor.js +127 -135
  182. package/utils/DataloaderUtils.d.ts +43 -58
  183. package/utils/DataloaderUtils.js +198 -203
  184. package/utils/EntityComparator.d.ts +81 -98
  185. package/utils/EntityComparator.js +728 -824
  186. package/utils/NullHighlighter.d.ts +1 -1
  187. package/utils/NullHighlighter.js +3 -3
  188. package/utils/QueryHelper.d.ts +51 -79
  189. package/utils/QueryHelper.js +361 -372
  190. package/utils/RawQueryFragment.d.ts +34 -50
  191. package/utils/RawQueryFragment.js +105 -107
  192. package/utils/RequestContext.d.ts +32 -32
  193. package/utils/RequestContext.js +53 -52
  194. package/utils/TransactionContext.d.ts +16 -16
  195. package/utils/TransactionContext.js +27 -27
  196. package/utils/TransactionManager.d.ts +58 -58
  197. package/utils/TransactionManager.js +197 -199
  198. package/utils/Utils.d.ts +145 -204
  199. package/utils/Utils.js +813 -814
  200. package/utils/clone.js +113 -104
  201. package/utils/env-vars.js +88 -90
  202. package/utils/fs-utils.d.ts +15 -15
  203. package/utils/fs-utils.js +181 -180
  204. package/utils/upsert-utils.d.ts +5 -20
  205. 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
  }