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