@mikro-orm/core 7.0.0-dev.20 → 7.0.0-dev.201
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 +99 -57
- package/EntityManager.js +302 -276
- package/MikroORM.d.ts +44 -35
- package/MikroORM.js +103 -143
- package/README.md +3 -2
- package/cache/FileCacheAdapter.d.ts +1 -1
- package/cache/FileCacheAdapter.js +8 -7
- package/cache/GeneratedCacheAdapter.d.ts +0 -1
- package/cache/GeneratedCacheAdapter.js +0 -2
- package/cache/index.d.ts +0 -1
- package/cache/index.js +0 -1
- package/connections/Connection.d.ts +16 -7
- package/connections/Connection.js +23 -14
- package/drivers/DatabaseDriver.d.ts +25 -16
- package/drivers/DatabaseDriver.js +80 -35
- package/drivers/IDatabaseDriver.d.ts +44 -17
- package/entity/BaseEntity.d.ts +2 -2
- package/entity/BaseEntity.js +0 -3
- package/entity/Collection.d.ts +94 -29
- package/entity/Collection.js +434 -97
- package/entity/EntityAssigner.d.ts +1 -1
- package/entity/EntityAssigner.js +26 -18
- package/entity/EntityFactory.d.ts +13 -1
- package/entity/EntityFactory.js +84 -53
- package/entity/EntityHelper.d.ts +2 -2
- package/entity/EntityHelper.js +35 -15
- package/entity/EntityLoader.d.ts +6 -6
- package/entity/EntityLoader.js +117 -77
- package/entity/EntityRepository.d.ts +24 -4
- package/entity/EntityRepository.js +8 -2
- package/entity/Reference.d.ts +6 -5
- package/entity/Reference.js +34 -9
- package/entity/WrappedEntity.d.ts +2 -7
- package/entity/WrappedEntity.js +3 -8
- package/entity/defineEntity.d.ts +594 -0
- package/entity/defineEntity.js +533 -0
- package/entity/index.d.ts +3 -2
- package/entity/index.js +3 -2
- package/entity/utils.d.ts +7 -0
- package/entity/utils.js +16 -4
- package/entity/validators.d.ts +11 -0
- package/entity/validators.js +65 -0
- package/enums.d.ts +21 -5
- package/enums.js +15 -1
- package/errors.d.ts +23 -9
- package/errors.js +59 -21
- package/events/EventManager.d.ts +2 -1
- package/events/EventManager.js +19 -11
- package/hydration/Hydrator.js +1 -2
- package/hydration/ObjectHydrator.d.ts +4 -4
- package/hydration/ObjectHydrator.js +52 -33
- package/index.d.ts +2 -2
- package/index.js +1 -2
- package/logging/DefaultLogger.d.ts +1 -1
- package/logging/DefaultLogger.js +1 -0
- package/logging/SimpleLogger.d.ts +1 -1
- package/logging/colors.d.ts +1 -1
- package/logging/colors.js +7 -6
- package/logging/index.d.ts +1 -0
- package/logging/index.js +1 -0
- package/logging/inspect.d.ts +2 -0
- package/logging/inspect.js +11 -0
- package/metadata/EntitySchema.d.ts +40 -23
- package/metadata/EntitySchema.js +81 -34
- package/metadata/MetadataDiscovery.d.ts +7 -10
- package/metadata/MetadataDiscovery.js +391 -331
- package/metadata/MetadataProvider.d.ts +11 -2
- package/metadata/MetadataProvider.js +46 -2
- package/metadata/MetadataStorage.d.ts +13 -11
- package/metadata/MetadataStorage.js +70 -37
- package/metadata/MetadataValidator.d.ts +17 -9
- package/metadata/MetadataValidator.js +97 -40
- 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 +1 -1
- package/metadata/types.d.ts +502 -0
- package/metadata/types.js +1 -0
- package/naming-strategy/AbstractNamingStrategy.d.ts +12 -4
- package/naming-strategy/AbstractNamingStrategy.js +14 -2
- package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
- package/naming-strategy/EntityCaseNamingStrategy.js +6 -5
- package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
- package/naming-strategy/MongoNamingStrategy.js +6 -6
- package/naming-strategy/NamingStrategy.d.ts +24 -4
- package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
- package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
- package/not-supported.d.ts +2 -0
- package/not-supported.js +4 -0
- package/package.json +18 -11
- package/platforms/ExceptionConverter.js +1 -1
- package/platforms/Platform.d.ts +7 -13
- package/platforms/Platform.js +20 -43
- package/serialization/EntitySerializer.d.ts +5 -0
- package/serialization/EntitySerializer.js +47 -27
- package/serialization/EntityTransformer.js +28 -18
- package/serialization/SerializationContext.d.ts +6 -6
- package/serialization/SerializationContext.js +16 -13
- package/types/ArrayType.d.ts +1 -1
- package/types/ArrayType.js +2 -3
- package/types/BigIntType.d.ts +8 -6
- package/types/BigIntType.js +1 -1
- package/types/BlobType.d.ts +0 -1
- package/types/BlobType.js +0 -3
- package/types/BooleanType.d.ts +2 -1
- package/types/BooleanType.js +3 -0
- package/types/DecimalType.d.ts +6 -4
- package/types/DecimalType.js +3 -3
- package/types/DoubleType.js +2 -2
- 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/index.d.ts +1 -1
- package/typings.d.ts +290 -137
- package/typings.js +59 -44
- package/unit-of-work/ChangeSet.d.ts +2 -6
- package/unit-of-work/ChangeSet.js +4 -5
- package/unit-of-work/ChangeSetComputer.d.ts +1 -3
- package/unit-of-work/ChangeSetComputer.js +26 -13
- package/unit-of-work/ChangeSetPersister.d.ts +5 -4
- package/unit-of-work/ChangeSetPersister.js +70 -34
- package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
- package/unit-of-work/CommitOrderCalculator.js +13 -13
- package/unit-of-work/IdentityMap.d.ts +12 -0
- package/unit-of-work/IdentityMap.js +39 -1
- package/unit-of-work/UnitOfWork.d.ts +23 -3
- package/unit-of-work/UnitOfWork.js +175 -98
- package/utils/AbstractSchemaGenerator.d.ts +5 -5
- package/utils/AbstractSchemaGenerator.js +18 -16
- package/utils/AsyncContext.d.ts +6 -0
- package/utils/AsyncContext.js +42 -0
- package/utils/Configuration.d.ts +779 -207
- package/utils/Configuration.js +146 -190
- package/utils/ConfigurationLoader.d.ts +1 -54
- package/utils/ConfigurationLoader.js +1 -352
- package/utils/Cursor.d.ts +0 -3
- package/utils/Cursor.js +27 -11
- package/utils/DataloaderUtils.d.ts +15 -5
- package/utils/DataloaderUtils.js +64 -30
- package/utils/EntityComparator.d.ts +13 -9
- package/utils/EntityComparator.js +101 -42
- package/utils/QueryHelper.d.ts +14 -6
- package/utils/QueryHelper.js +87 -25
- package/utils/RawQueryFragment.d.ts +48 -25
- package/utils/RawQueryFragment.js +66 -70
- package/utils/RequestContext.js +2 -2
- package/utils/TransactionContext.js +2 -2
- package/utils/TransactionManager.d.ts +65 -0
- package/utils/TransactionManager.js +223 -0
- package/utils/Utils.d.ts +13 -126
- package/utils/Utils.js +100 -391
- package/utils/clone.js +8 -23
- package/utils/env-vars.d.ts +7 -0
- package/utils/env-vars.js +97 -0
- package/utils/fs-utils.d.ts +32 -0
- package/utils/fs-utils.js +178 -0
- package/utils/index.d.ts +2 -1
- package/utils/index.js +2 -1
- package/utils/upsert-utils.d.ts +9 -4
- package/utils/upsert-utils.js +55 -4
- 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 -18
- 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 -40
- package/decorators/ManyToMany.js +0 -14
- package/decorators/ManyToOne.d.ts +0 -32
- 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 -26
- 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 -13
- 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 -116
- package/entity/ArrayCollection.js +0 -402
- package/entity/EntityValidator.d.ts +0 -19
- package/entity/EntityValidator.js +0 -150
- package/exports.d.ts +0 -24
- package/exports.js +0 -23
- 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,8 +4,8 @@ import { ValidationError } from '../errors.js';
|
|
|
4
4
|
import { LoadStrategy, PopulatePath, ReferenceKind, } from '../enums.js';
|
|
5
5
|
import { Reference } from './Reference.js';
|
|
6
6
|
import { helper } from './wrap.js';
|
|
7
|
-
import { raw, RawQueryFragment } from '../utils/RawQueryFragment.js';
|
|
8
7
|
import { expandDotPaths } from './utils.js';
|
|
8
|
+
import { Raw } from '../utils/RawQueryFragment.js';
|
|
9
9
|
export class EntityLoader {
|
|
10
10
|
em;
|
|
11
11
|
metadata;
|
|
@@ -32,17 +32,16 @@ export class EntityLoader {
|
|
|
32
32
|
const visited = options.visited ??= new Set();
|
|
33
33
|
options.where ??= {};
|
|
34
34
|
options.orderBy ??= {};
|
|
35
|
-
options.filters ??= {};
|
|
36
35
|
options.lookup ??= true;
|
|
37
36
|
options.validate ??= true;
|
|
38
37
|
options.refresh ??= false;
|
|
39
38
|
options.convertCustomTypes ??= true;
|
|
40
39
|
if (references.length > 0) {
|
|
41
|
-
await this.populateScalar(meta, references, options);
|
|
40
|
+
await this.populateScalar(meta, references, { ...options, populateWhere: undefined });
|
|
42
41
|
}
|
|
43
|
-
populate = this.normalizePopulate(entityName, populate, options.strategy, options.lookup);
|
|
42
|
+
populate = this.normalizePopulate(entityName, populate, options.strategy, options.lookup, options.exclude);
|
|
44
43
|
const invalid = populate.find(({ field }) => !this.em.canPopulate(entityName, field));
|
|
45
|
-
/* v8 ignore next
|
|
44
|
+
/* v8 ignore next */
|
|
46
45
|
if (options.validate && invalid) {
|
|
47
46
|
throw ValidationError.invalidPropertyName(entityName, invalid.field);
|
|
48
47
|
}
|
|
@@ -57,7 +56,7 @@ export class EntityLoader {
|
|
|
57
56
|
visited.delete(entity);
|
|
58
57
|
}
|
|
59
58
|
}
|
|
60
|
-
normalizePopulate(entityName, populate, strategy, lookup = true) {
|
|
59
|
+
normalizePopulate(entityName, populate, strategy, lookup = true, exclude) {
|
|
61
60
|
const meta = this.metadata.find(entityName);
|
|
62
61
|
let normalized = Utils.asArray(populate).map(field => {
|
|
63
62
|
return typeof field === 'boolean' || field.field === PopulatePath.ALL ? { all: !!field, field: meta.primaryKeys[0] } : field;
|
|
@@ -68,7 +67,7 @@ export class EntityLoader {
|
|
|
68
67
|
// convert nested `field` with dot syntax to PopulateOptions with `children` array
|
|
69
68
|
expandDotPaths(meta, normalized, true);
|
|
70
69
|
if (lookup && populate !== false) {
|
|
71
|
-
normalized = this.lookupEagerLoadedRelationships(entityName, normalized, strategy);
|
|
70
|
+
normalized = this.lookupEagerLoadedRelationships(entityName, normalized, strategy, '', [], exclude);
|
|
72
71
|
// convert nested `field` with dot syntax produced by eager relations
|
|
73
72
|
expandDotPaths(meta, normalized, true);
|
|
74
73
|
}
|
|
@@ -140,35 +139,39 @@ export class EntityLoader {
|
|
|
140
139
|
const innerOrderBy = Utils.asArray(options.orderBy)
|
|
141
140
|
.filter(orderBy => (Array.isArray(orderBy[prop.name]) && orderBy[prop.name].length > 0) || Utils.isObject(orderBy[prop.name]))
|
|
142
141
|
.flatMap(orderBy => orderBy[prop.name]);
|
|
142
|
+
const where = await this.extractChildCondition(options, prop);
|
|
143
143
|
if (prop.kind === ReferenceKind.MANY_TO_MANY && this.driver.getPlatform().usesPivotTable()) {
|
|
144
144
|
const res = await this.findChildrenFromPivotTable(filtered, prop, options, innerOrderBy, populate, !!ref);
|
|
145
145
|
return Utils.flatten(res);
|
|
146
146
|
}
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
147
|
+
const { items, partial } = await this.findChildren(options.filtered ?? entities, prop, populate, {
|
|
148
|
+
...options,
|
|
149
|
+
where,
|
|
150
|
+
orderBy: innerOrderBy,
|
|
151
|
+
}, !!(ref || prop.mapToPk));
|
|
152
|
+
this.initializeCollections(filtered, prop, field, items, innerOrderBy.length > 0, partial);
|
|
153
|
+
return items;
|
|
151
154
|
}
|
|
152
155
|
async populateScalar(meta, filtered, options) {
|
|
153
156
|
const pk = Utils.getPrimaryKeyHash(meta.primaryKeys);
|
|
154
|
-
const ids = Utils.unique(filtered.map(e => Utils.getPrimaryKeyValues(e, meta
|
|
157
|
+
const ids = Utils.unique(filtered.map(e => Utils.getPrimaryKeyValues(e, meta, true)));
|
|
155
158
|
const where = this.mergePrimaryCondition(ids, pk, options, meta, this.metadata, this.driver.getPlatform());
|
|
156
159
|
const { filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, fields } = options;
|
|
157
|
-
await this.em.find(meta.
|
|
160
|
+
await this.em.find(meta.class, where, {
|
|
158
161
|
filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging,
|
|
159
162
|
fields: fields,
|
|
160
163
|
populate: [],
|
|
161
164
|
});
|
|
162
165
|
}
|
|
163
|
-
initializeCollections(filtered, prop, field, children, customOrder) {
|
|
166
|
+
initializeCollections(filtered, prop, field, children, customOrder, partial) {
|
|
164
167
|
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
|
165
|
-
this.initializeOneToMany(filtered, children, prop, field);
|
|
168
|
+
this.initializeOneToMany(filtered, children, prop, field, partial);
|
|
166
169
|
}
|
|
167
170
|
if (prop.kind === ReferenceKind.MANY_TO_MANY && !this.driver.getPlatform().usesPivotTable()) {
|
|
168
|
-
this.initializeManyToMany(filtered, children, prop, field, customOrder);
|
|
171
|
+
this.initializeManyToMany(filtered, children, prop, field, customOrder, partial);
|
|
169
172
|
}
|
|
170
173
|
}
|
|
171
|
-
initializeOneToMany(filtered, children, prop, field) {
|
|
174
|
+
initializeOneToMany(filtered, children, prop, field, partial) {
|
|
172
175
|
const mapToPk = prop.targetMeta.properties[prop.mappedBy].mapToPk;
|
|
173
176
|
const map = {};
|
|
174
177
|
for (const entity of filtered) {
|
|
@@ -178,20 +181,20 @@ export class EntityLoader {
|
|
|
178
181
|
for (const child of children) {
|
|
179
182
|
const pk = child.__helper.__data[prop.mappedBy] ?? child[prop.mappedBy];
|
|
180
183
|
if (pk) {
|
|
181
|
-
const key = helper(mapToPk ? this.em.getReference(prop.
|
|
184
|
+
const key = helper(mapToPk ? this.em.getReference(prop.targetMeta.class, pk) : pk).getSerializedPrimaryKey();
|
|
182
185
|
map[key]?.push(child);
|
|
183
186
|
}
|
|
184
187
|
}
|
|
185
188
|
for (const entity of filtered) {
|
|
186
189
|
const key = helper(entity).getSerializedPrimaryKey();
|
|
187
|
-
entity[field].hydrate(map[key]);
|
|
190
|
+
entity[field].hydrate(map[key], undefined, partial);
|
|
188
191
|
}
|
|
189
192
|
}
|
|
190
|
-
initializeManyToMany(filtered, children, prop, field, customOrder) {
|
|
193
|
+
initializeManyToMany(filtered, children, prop, field, customOrder, partial) {
|
|
191
194
|
if (prop.mappedBy) {
|
|
192
195
|
for (const entity of filtered) {
|
|
193
196
|
const items = children.filter(child => child[prop.mappedBy].contains(entity, false));
|
|
194
|
-
entity[field].hydrate(items, true);
|
|
197
|
+
entity[field].hydrate(items, true, partial);
|
|
195
198
|
}
|
|
196
199
|
}
|
|
197
200
|
else { // owning side of M:N without pivot table needs to be reordered
|
|
@@ -201,15 +204,17 @@ export class EntityLoader {
|
|
|
201
204
|
if (!customOrder) {
|
|
202
205
|
items.sort((a, b) => order.indexOf(a) - order.indexOf(b));
|
|
203
206
|
}
|
|
204
|
-
entity[field].hydrate(items, true);
|
|
207
|
+
entity[field].hydrate(items, true, partial);
|
|
205
208
|
}
|
|
206
209
|
}
|
|
207
210
|
}
|
|
208
211
|
async findChildren(entities, prop, populate, options, ref) {
|
|
209
|
-
const children = this.getChildReferences(entities, prop, options, ref);
|
|
212
|
+
const children = Utils.unique(this.getChildReferences(entities, prop, options, ref));
|
|
210
213
|
const meta = prop.targetMeta;
|
|
211
|
-
|
|
214
|
+
// When targetKey is set, use it for FK lookup instead of the PK
|
|
215
|
+
let fk = prop.targetKey ?? Utils.getPrimaryKeyHash(meta.primaryKeys);
|
|
212
216
|
let schema = options.schema;
|
|
217
|
+
const partial = !Utils.isEmpty(prop.where) || !Utils.isEmpty(options.where);
|
|
213
218
|
if (prop.kind === ReferenceKind.ONE_TO_MANY || (prop.kind === ReferenceKind.MANY_TO_MANY && !prop.owner)) {
|
|
214
219
|
fk = meta.properties[prop.mappedBy].name;
|
|
215
220
|
}
|
|
@@ -219,42 +224,29 @@ export class EntityLoader {
|
|
|
219
224
|
children.push(...this.filterByReferences(entities, prop.name, options.refresh));
|
|
220
225
|
}
|
|
221
226
|
if (children.length === 0) {
|
|
222
|
-
return [];
|
|
227
|
+
return { items: [], partial };
|
|
223
228
|
}
|
|
224
229
|
if (!schema && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
|
|
225
230
|
schema = children.find(e => e.__helper.__schema)?.__helper.__schema;
|
|
226
231
|
}
|
|
227
|
-
|
|
232
|
+
// When targetKey is set, get the targetKey value instead of PK
|
|
233
|
+
const ids = Utils.unique(children.map(e => prop.targetKey ? e[prop.targetKey] : e.__helper.getPrimaryKey()));
|
|
228
234
|
let where = this.mergePrimaryCondition(ids, fk, options, meta, this.metadata, this.driver.getPlatform());
|
|
229
235
|
const fields = this.buildFields(options.fields, prop, ref);
|
|
230
236
|
/* eslint-disable prefer-const */
|
|
231
|
-
let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, } = options;
|
|
237
|
+
let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere = 'infer', connectionType, logging, } = options;
|
|
232
238
|
/* eslint-enable prefer-const */
|
|
233
239
|
if (typeof populateWhere === 'object') {
|
|
234
240
|
populateWhere = await this.extractChildCondition({ where: populateWhere }, prop);
|
|
235
241
|
}
|
|
236
|
-
if (!Utils.isEmpty(prop.where)) {
|
|
242
|
+
if (!Utils.isEmpty(prop.where) || Raw.hasObjectFragments(prop.where)) {
|
|
237
243
|
where = { $and: [where, prop.where] };
|
|
238
244
|
}
|
|
239
|
-
const
|
|
240
|
-
if (prop.orderBy) {
|
|
241
|
-
for (const item of Utils.asArray(prop.orderBy)) {
|
|
242
|
-
for (const field of Utils.keys(item)) {
|
|
243
|
-
const rawField = RawQueryFragment.getKnownFragment(field, false);
|
|
244
|
-
if (rawField) {
|
|
245
|
-
const raw2 = raw(rawField.sql, rawField.params);
|
|
246
|
-
propOrderBy.push({ [raw2.toString()]: item[field] });
|
|
247
|
-
continue;
|
|
248
|
-
}
|
|
249
|
-
propOrderBy.push({ [field]: item[field] });
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
const orderBy = [...Utils.asArray(options.orderBy), ...propOrderBy].filter((order, idx, array) => {
|
|
245
|
+
const orderBy = [...Utils.asArray(options.orderBy), ...Utils.asArray(prop.orderBy)].filter((order, idx, array) => {
|
|
254
246
|
// skip consecutive ordering with the same key to get around mongo issues
|
|
255
|
-
return idx === 0 || !Utils.equals(
|
|
247
|
+
return idx === 0 || !Utils.equals(Utils.getObjectQueryKeys(array[idx - 1]), Utils.getObjectQueryKeys(order));
|
|
256
248
|
});
|
|
257
|
-
const items = await this.em.find(
|
|
249
|
+
const items = await this.em.find(meta.class, where, {
|
|
258
250
|
filters, convertCustomTypes, lockMode, populateWhere, logging,
|
|
259
251
|
orderBy,
|
|
260
252
|
populate: populate.children ?? populate.all ?? [],
|
|
@@ -265,6 +257,49 @@ export class EntityLoader {
|
|
|
265
257
|
// @ts-ignore not a public option, will be propagated to the populate call
|
|
266
258
|
visited: options.visited,
|
|
267
259
|
});
|
|
260
|
+
// For targetKey relations, wire up loaded entities to parent references
|
|
261
|
+
// This is needed because the references were created under alternate key,
|
|
262
|
+
// but loaded entities are stored under PK, so they don't automatically merge
|
|
263
|
+
if (prop.targetKey && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
|
|
264
|
+
const itemsByKey = new Map();
|
|
265
|
+
for (const item of items) {
|
|
266
|
+
itemsByKey.set('' + item[prop.targetKey], item);
|
|
267
|
+
}
|
|
268
|
+
for (const entity of entities) {
|
|
269
|
+
const ref = entity[prop.name];
|
|
270
|
+
/* v8 ignore next */
|
|
271
|
+
if (!ref) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const keyValue = '' + (Reference.isReference(ref) ? ref.unwrap()[prop.targetKey] : ref[prop.targetKey]);
|
|
275
|
+
const loadedItem = itemsByKey.get(keyValue);
|
|
276
|
+
if (loadedItem) {
|
|
277
|
+
entity[prop.name] = (Reference.isReference(ref) ? Reference.create(loadedItem) : loadedItem);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if ([ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && items.length !== children.length) {
|
|
282
|
+
const nullVal = this.em.config.get('forceUndefined') ? undefined : null;
|
|
283
|
+
const itemsMap = new Set();
|
|
284
|
+
const childrenMap = new Set();
|
|
285
|
+
// Use targetKey value if set, otherwise use serialized PK
|
|
286
|
+
const getKey = (e) => prop.targetKey ? '' + e[prop.targetKey] : helper(e).getSerializedPrimaryKey();
|
|
287
|
+
for (const item of items) {
|
|
288
|
+
/* v8 ignore next */
|
|
289
|
+
itemsMap.add(getKey(item));
|
|
290
|
+
}
|
|
291
|
+
for (const child of children) {
|
|
292
|
+
childrenMap.add(getKey(child));
|
|
293
|
+
}
|
|
294
|
+
for (const entity of entities) {
|
|
295
|
+
const ref = entity[prop.name] ?? {};
|
|
296
|
+
const key = helper(ref) ? getKey(ref) : undefined;
|
|
297
|
+
if (key && childrenMap.has(key) && !itemsMap.has(key)) {
|
|
298
|
+
entity[prop.name] = nullVal;
|
|
299
|
+
helper(entity).__originalEntityData[prop.name] = null;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
268
303
|
for (const item of items) {
|
|
269
304
|
if (ref && !helper(item).__onLoadFired) {
|
|
270
305
|
helper(item).__initialized = false;
|
|
@@ -272,10 +307,10 @@ export class EntityLoader {
|
|
|
272
307
|
this.em.getUnitOfWork()['loadedEntities'].delete(item);
|
|
273
308
|
}
|
|
274
309
|
}
|
|
275
|
-
return items;
|
|
310
|
+
return { items, partial };
|
|
276
311
|
}
|
|
277
312
|
mergePrimaryCondition(ids, pk, options, meta, metadata, platform) {
|
|
278
|
-
const cond1 = QueryHelper.processWhere({ where: { [pk]: { $in: ids } }, entityName: meta.
|
|
313
|
+
const cond1 = QueryHelper.processWhere({ where: { [pk]: { $in: ids } }, entityName: meta.class, metadata, platform, convertCustomTypes: !options.convertCustomTypes });
|
|
279
314
|
const where = { ...options.where };
|
|
280
315
|
Utils.dropUndefinedProperties(where);
|
|
281
316
|
return where[pk]
|
|
@@ -288,6 +323,7 @@ export class EntityLoader {
|
|
|
288
323
|
if (prop.kind === ReferenceKind.SCALAR && !prop.lazy) {
|
|
289
324
|
return;
|
|
290
325
|
}
|
|
326
|
+
options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
|
|
291
327
|
const populated = await this.populateMany(entityName, entities, populate, options);
|
|
292
328
|
if (!populate.children && !populate.all) {
|
|
293
329
|
return;
|
|
@@ -321,11 +357,16 @@ export class EntityLoader {
|
|
|
321
357
|
for (const entity of entities) {
|
|
322
358
|
visited.delete(entity);
|
|
323
359
|
}
|
|
324
|
-
const
|
|
360
|
+
const unique = Utils.unique(children);
|
|
361
|
+
const filtered = unique.filter(e => !visited.has(e));
|
|
325
362
|
for (const entity of entities) {
|
|
326
363
|
visited.add(entity);
|
|
327
364
|
}
|
|
328
|
-
|
|
365
|
+
// skip lazy scalar properties
|
|
366
|
+
if (!prop.targetMeta) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
await this.populate(prop.targetMeta.class, unique, populate.children ?? populate.all, {
|
|
329
370
|
where: await this.extractChildCondition(options, prop, false),
|
|
330
371
|
orderBy: innerOrderBy,
|
|
331
372
|
fields,
|
|
@@ -342,6 +383,8 @@ export class EntityLoader {
|
|
|
342
383
|
refresh: refresh && !filtered.every(item => options.visited.has(item)),
|
|
343
384
|
// @ts-ignore not a public option, will be propagated to the populate call
|
|
344
385
|
visited: options.visited,
|
|
386
|
+
// @ts-ignore not a public option
|
|
387
|
+
filtered,
|
|
345
388
|
});
|
|
346
389
|
}
|
|
347
390
|
/** @internal */
|
|
@@ -368,12 +411,12 @@ export class EntityLoader {
|
|
|
368
411
|
for (const entity of filtered) {
|
|
369
412
|
const items = map[entity.__helper.getSerializedPrimaryKey()].map(item => {
|
|
370
413
|
if (pivotJoin) {
|
|
371
|
-
return this.em.getReference(prop.
|
|
414
|
+
return this.em.getReference(prop.targetMeta.class, item, {
|
|
372
415
|
convertCustomTypes: true,
|
|
373
416
|
schema: options.schema ?? this.em.config.get('schema'),
|
|
374
417
|
});
|
|
375
418
|
}
|
|
376
|
-
const entity = this.em.getEntityFactory().create(prop.
|
|
419
|
+
const entity = this.em.getEntityFactory().create(prop.targetMeta.class, item, {
|
|
377
420
|
refresh,
|
|
378
421
|
merge: true,
|
|
379
422
|
convertCustomTypes: true,
|
|
@@ -389,16 +432,13 @@ export class EntityLoader {
|
|
|
389
432
|
async extractChildCondition(options, prop, filters = false) {
|
|
390
433
|
const where = options.where;
|
|
391
434
|
const subCond = Utils.isPlainObject(where[prop.name]) ? where[prop.name] : {};
|
|
392
|
-
const meta2 =
|
|
393
|
-
if (!meta2) {
|
|
394
|
-
return {};
|
|
395
|
-
}
|
|
435
|
+
const meta2 = prop.targetMeta;
|
|
396
436
|
const pk = Utils.getPrimaryKeyHash(meta2.primaryKeys);
|
|
397
437
|
['$and', '$or'].forEach(op => {
|
|
398
438
|
if (where[op]) {
|
|
399
439
|
const child = where[op]
|
|
400
440
|
.map((cond) => cond[prop.name])
|
|
401
|
-
.filter((sub) => sub != null && !(Utils.isPlainObject(sub) &&
|
|
441
|
+
.filter((sub) => sub != null && !(Utils.isPlainObject(sub) && Utils.getObjectQueryKeys(sub).every(key => Utils.isOperator(key, false))))
|
|
402
442
|
.map((cond) => {
|
|
403
443
|
if (Utils.isPrimaryKey(cond)) {
|
|
404
444
|
return { [pk]: cond };
|
|
@@ -419,7 +459,7 @@ export class EntityLoader {
|
|
|
419
459
|
});
|
|
420
460
|
}
|
|
421
461
|
if (filters) {
|
|
422
|
-
return this.em.applyFilters(
|
|
462
|
+
return this.em.applyFilters(meta2.class, subCond, options.filters, 'read', options);
|
|
423
463
|
}
|
|
424
464
|
return subCond;
|
|
425
465
|
}
|
|
@@ -437,7 +477,7 @@ export class EntityLoader {
|
|
|
437
477
|
const parts = f.toString().split('.');
|
|
438
478
|
const propName = parts.shift();
|
|
439
479
|
const childPropName = parts.join('.');
|
|
440
|
-
/* v8 ignore next
|
|
480
|
+
/* v8 ignore next */
|
|
441
481
|
if (propName === prop.name) {
|
|
442
482
|
ret.push(childPropName);
|
|
443
483
|
}
|
|
@@ -458,23 +498,20 @@ export class EntityLoader {
|
|
|
458
498
|
}
|
|
459
499
|
getChildReferences(entities, prop, options, ref) {
|
|
460
500
|
const filtered = this.filterCollections(entities, prop.name, options, ref);
|
|
461
|
-
const children = [];
|
|
462
501
|
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
|
463
|
-
|
|
502
|
+
return filtered.map(e => e[prop.name].owner);
|
|
464
503
|
}
|
|
465
|
-
|
|
466
|
-
|
|
504
|
+
if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
|
|
505
|
+
return filtered.reduce((a, b) => {
|
|
467
506
|
a.push(...b[prop.name].getItems());
|
|
468
507
|
return a;
|
|
469
|
-
}, [])
|
|
470
|
-
}
|
|
471
|
-
else if (prop.kind === ReferenceKind.MANY_TO_MANY) { // inverse side
|
|
472
|
-
children.push(...filtered);
|
|
508
|
+
}, []);
|
|
473
509
|
}
|
|
474
|
-
|
|
475
|
-
|
|
510
|
+
if (prop.kind === ReferenceKind.MANY_TO_MANY) { // inverse side
|
|
511
|
+
return filtered;
|
|
476
512
|
}
|
|
477
|
-
|
|
513
|
+
// MANY_TO_ONE or ONE_TO_ONE
|
|
514
|
+
return this.filterReferences(entities, prop.name, options, ref);
|
|
478
515
|
}
|
|
479
516
|
filterCollections(entities, field, options, ref) {
|
|
480
517
|
if (options.refresh) {
|
|
@@ -491,7 +528,7 @@ export class EntityLoader {
|
|
|
491
528
|
return wrapped.__loadedProperties.has(field);
|
|
492
529
|
}
|
|
493
530
|
const [f, ...r] = field.split('.');
|
|
494
|
-
/* v8 ignore next
|
|
531
|
+
/* v8 ignore next */
|
|
495
532
|
if (!wrapped.__loadedProperties.has(f) || !wrapped.__meta.properties[f]?.targetMeta) {
|
|
496
533
|
return false;
|
|
497
534
|
}
|
|
@@ -524,7 +561,7 @@ export class EntityLoader {
|
|
|
524
561
|
.map(e => Reference.unwrapReference(e[field]));
|
|
525
562
|
}
|
|
526
563
|
filterByReferences(entities, field, refresh) {
|
|
527
|
-
/* v8 ignore next
|
|
564
|
+
/* v8 ignore next */
|
|
528
565
|
if (refresh) {
|
|
529
566
|
return entities;
|
|
530
567
|
}
|
|
@@ -550,33 +587,36 @@ export class EntityLoader {
|
|
|
550
587
|
}
|
|
551
588
|
return `${this.getRelationName(meta, meta.properties[prop.embedded[0]])}.${prop.embedded[1]}`;
|
|
552
589
|
}
|
|
553
|
-
lookupEagerLoadedRelationships(entityName, populate, strategy, prefix = '', visited = []) {
|
|
590
|
+
lookupEagerLoadedRelationships(entityName, populate, strategy, prefix = '', visited = [], exclude) {
|
|
554
591
|
const meta = this.metadata.find(entityName);
|
|
555
592
|
if (!meta && !prefix) {
|
|
556
593
|
return populate;
|
|
557
594
|
}
|
|
558
|
-
if (visited.includes(
|
|
595
|
+
if (!meta || visited.includes(meta)) {
|
|
559
596
|
return [];
|
|
560
597
|
}
|
|
561
|
-
visited.push(
|
|
598
|
+
visited.push(meta);
|
|
562
599
|
const ret = prefix === '' ? [...populate] : [];
|
|
563
600
|
meta.relations
|
|
564
601
|
.filter(prop => {
|
|
602
|
+
const field = this.getRelationName(meta, prop);
|
|
603
|
+
const prefixed = prefix ? `${prefix}.${field}` : field;
|
|
604
|
+
const isExcluded = exclude?.includes(prefixed);
|
|
565
605
|
const eager = prop.eager && !populate.some(p => p.field === `${prop.name}:ref`);
|
|
566
606
|
const populated = populate.some(p => p.field === prop.name);
|
|
567
607
|
const disabled = populate.some(p => p.field === prop.name && p.all === false);
|
|
568
|
-
return !disabled && (eager || populated);
|
|
608
|
+
return !disabled && !isExcluded && (eager || populated);
|
|
569
609
|
})
|
|
570
610
|
.forEach(prop => {
|
|
571
611
|
const field = this.getRelationName(meta, prop);
|
|
572
612
|
const prefixed = prefix ? `${prefix}.${field}` : field;
|
|
573
613
|
const nestedPopulate = populate.filter(p => p.field === prop.name).flatMap(p => p.children).filter(Boolean);
|
|
574
|
-
const nested = this.lookupEagerLoadedRelationships(prop.
|
|
614
|
+
const nested = this.lookupEagerLoadedRelationships(prop.targetMeta.class, nestedPopulate, strategy, prefixed, visited.slice(), exclude);
|
|
575
615
|
if (nested.length > 0) {
|
|
576
616
|
ret.push(...nested);
|
|
577
617
|
}
|
|
578
618
|
else {
|
|
579
|
-
const selfReferencing = [meta.
|
|
619
|
+
const selfReferencing = [meta.tableName, ...visited.map(m => m.tableName)].includes(prop.targetMeta.tableName) && prop.eager;
|
|
580
620
|
ret.push({
|
|
581
621
|
field: prefixed,
|
|
582
622
|
// enforce select-in strategy for self-referencing relations
|
|
@@ -2,7 +2,7 @@ import type { PopulatePath } from '../enums.js';
|
|
|
2
2
|
import type { CreateOptions, EntityManager, MergeOptions } from '../EntityManager.js';
|
|
3
3
|
import type { AssignOptions } from './EntityAssigner.js';
|
|
4
4
|
import type { EntityData, EntityName, Primary, Loaded, FilterQuery, EntityDictionary, AutoPath, RequiredEntityData, Ref, EntityType, EntityDTO, MergeSelected, FromEntityType, IsSubset, MergeLoaded, ArrayElement } from '../typings.js';
|
|
5
|
-
import type { CountOptions, DeleteOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, NativeInsertUpdateOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from '../drivers/IDatabaseDriver.js';
|
|
5
|
+
import type { CountOptions, DeleteOptions, FindAllOptions, FindByCursorOptions, FindOneOptions, FindOneOrFailOptions, FindOptions, GetReferenceOptions, NativeInsertUpdateOptions, StreamOptions, UpdateOptions, UpsertManyOptions, UpsertOptions } from '../drivers/IDatabaseDriver.js';
|
|
6
6
|
import type { EntityLoaderOptions } from './EntityLoader.js';
|
|
7
7
|
import type { Cursor } from '../utils/Cursor.js';
|
|
8
8
|
export declare class EntityRepository<Entity extends object> {
|
|
@@ -80,11 +80,15 @@ export declare class EntityRepository<Entity extends object> {
|
|
|
80
80
|
/**
|
|
81
81
|
* @inheritDoc EntityManager.findByCursor
|
|
82
82
|
*/
|
|
83
|
-
findByCursor<Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(
|
|
83
|
+
findByCursor<Hint extends string = never, Fields extends string = '*', Excludes extends string = never, IncludeCount extends boolean = true>(options: FindByCursorOptions<Entity, Hint, Fields, Excludes, IncludeCount>): Promise<Cursor<Entity, Hint, Fields, Excludes, IncludeCount>>;
|
|
84
84
|
/**
|
|
85
85
|
* Finds all entities of given type. You can pass additional options via the `options` parameter.
|
|
86
86
|
*/
|
|
87
87
|
findAll<Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(options?: FindAllOptions<Entity, Hint, Fields, Excludes>): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
|
|
88
|
+
/**
|
|
89
|
+
* @inheritDoc EntityManager.stream
|
|
90
|
+
*/
|
|
91
|
+
stream<Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(options?: StreamOptions<Entity, Hint, Fields, Excludes>): AsyncIterableIterator<Loaded<Entity, Hint, Fields, Excludes>>;
|
|
88
92
|
/**
|
|
89
93
|
* @inheritDoc EntityManager.insert
|
|
90
94
|
*/
|
|
@@ -107,10 +111,26 @@ export declare class EntityRepository<Entity extends object> {
|
|
|
107
111
|
map(result: EntityDictionary<Entity>, options?: {
|
|
108
112
|
schema?: string;
|
|
109
113
|
}): Entity;
|
|
114
|
+
/**
|
|
115
|
+
* Gets a reference to the entity identified by the given type and alternate key property without actually loading it.
|
|
116
|
+
* The key option specifies which property to use for identity map lookup instead of the primary key.
|
|
117
|
+
*/
|
|
118
|
+
getReference<K extends string & keyof Entity>(id: Entity[K], options: Omit<GetReferenceOptions, 'key' | 'wrapped'> & {
|
|
119
|
+
key: K;
|
|
120
|
+
wrapped: true;
|
|
121
|
+
}): Ref<Entity>;
|
|
122
|
+
/**
|
|
123
|
+
* Gets a reference to the entity identified by the given type and alternate key property without actually loading it.
|
|
124
|
+
* The key option specifies which property to use for identity map lookup instead of the primary key.
|
|
125
|
+
*/
|
|
126
|
+
getReference<K extends string & keyof Entity>(id: Entity[K], options: Omit<GetReferenceOptions, 'key'> & {
|
|
127
|
+
key: K;
|
|
128
|
+
wrapped?: false;
|
|
129
|
+
}): Entity;
|
|
110
130
|
/**
|
|
111
131
|
* Gets a reference to the entity identified by the given type and identifier without actually loading it, if the entity is not yet loaded
|
|
112
132
|
*/
|
|
113
|
-
getReference(id: Primary<Entity>, options: Omit<GetReferenceOptions, 'wrapped'> & {
|
|
133
|
+
getReference(id: Primary<Entity>, options: Omit<GetReferenceOptions, 'wrapped' | 'key'> & {
|
|
114
134
|
wrapped: true;
|
|
115
135
|
}): Ref<Entity>;
|
|
116
136
|
/**
|
|
@@ -120,7 +140,7 @@ export declare class EntityRepository<Entity extends object> {
|
|
|
120
140
|
/**
|
|
121
141
|
* Gets a reference to the entity identified by the given type and identifier without actually loading it, if the entity is not yet loaded
|
|
122
142
|
*/
|
|
123
|
-
getReference(id: Primary<Entity>, options: Omit<GetReferenceOptions, 'wrapped'> & {
|
|
143
|
+
getReference(id: Primary<Entity>, options: Omit<GetReferenceOptions, 'wrapped' | 'key'> & {
|
|
124
144
|
wrapped: false;
|
|
125
145
|
}): Entity;
|
|
126
146
|
/**
|
|
@@ -90,8 +90,8 @@ export class EntityRepository {
|
|
|
90
90
|
/**
|
|
91
91
|
* @inheritDoc EntityManager.findByCursor
|
|
92
92
|
*/
|
|
93
|
-
async findByCursor(
|
|
94
|
-
return this.getEntityManager().findByCursor(this.entityName,
|
|
93
|
+
async findByCursor(options) {
|
|
94
|
+
return this.getEntityManager().findByCursor(this.entityName, options);
|
|
95
95
|
}
|
|
96
96
|
/**
|
|
97
97
|
* Finds all entities of given type. You can pass additional options via the `options` parameter.
|
|
@@ -99,6 +99,12 @@ export class EntityRepository {
|
|
|
99
99
|
async findAll(options) {
|
|
100
100
|
return this.getEntityManager().findAll(this.entityName, options);
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* @inheritDoc EntityManager.stream
|
|
104
|
+
*/
|
|
105
|
+
async *stream(options) {
|
|
106
|
+
yield* this.getEntityManager().stream(this.entityName, options);
|
|
107
|
+
}
|
|
102
108
|
/**
|
|
103
109
|
* @inheritDoc EntityManager.insert
|
|
104
110
|
*/
|
package/entity/Reference.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { inspect } from 'node:util';
|
|
2
1
|
import type { AddEager, AddOptional, Dictionary, EntityClass, EntityKey, EntityProperty, Loaded, LoadedReference, Primary, Ref } from '../typings.js';
|
|
3
2
|
import type { FindOneOptions, FindOneOrFailOptions } from '../drivers/IDatabaseDriver.js';
|
|
4
3
|
export declare class Reference<T extends object> {
|
|
5
4
|
private entity;
|
|
5
|
+
private property?;
|
|
6
6
|
constructor(entity: T);
|
|
7
7
|
static create<T extends object>(entity: T | Ref<T>): Ref<T>;
|
|
8
8
|
static createFromPK<T extends object>(entityType: EntityClass<T>, pk: Primary<T>, options?: {
|
|
@@ -42,8 +42,6 @@ export declare class Reference<T extends object> {
|
|
|
42
42
|
isInitialized(): boolean;
|
|
43
43
|
populated(populated?: boolean): void;
|
|
44
44
|
toJSON(...args: any[]): Dictionary;
|
|
45
|
-
/** @ignore */
|
|
46
|
-
[inspect.custom](depth?: number): string;
|
|
47
45
|
}
|
|
48
46
|
export declare class ScalarReference<Value> {
|
|
49
47
|
private value?;
|
|
@@ -56,12 +54,15 @@ export declare class ScalarReference<Value> {
|
|
|
56
54
|
* Returns either the whole entity, or the requested property.
|
|
57
55
|
*/
|
|
58
56
|
load(options?: Omit<LoadReferenceOptions<any, any>, 'populate' | 'fields' | 'exclude'>): Promise<Value | undefined>;
|
|
57
|
+
/**
|
|
58
|
+
* Ensures the underlying entity is loaded first (without reloading it if it already is loaded).
|
|
59
|
+
* Returns the entity or throws an error just like `em.findOneOrFail()` (and respects the same config options).
|
|
60
|
+
*/
|
|
61
|
+
loadOrFail(options?: Omit<LoadReferenceOrFailOptions<any, any>, 'populate' | 'fields' | 'exclude'>): Promise<Value>;
|
|
59
62
|
set(value: Value): void;
|
|
60
63
|
bind<Entity extends object>(entity: Entity, property: EntityKey<Entity>): void;
|
|
61
64
|
unwrap(): Value | undefined;
|
|
62
65
|
isInitialized(): boolean;
|
|
63
|
-
/** @ignore */
|
|
64
|
-
[inspect.custom](): string;
|
|
65
66
|
}
|
|
66
67
|
export interface LoadReferenceOptions<T extends object, P extends string = never, F extends string = '*', E extends string = never> extends FindOneOptions<T, P, F, E> {
|
|
67
68
|
dataloader?: boolean;
|
package/entity/Reference.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { inspect } from 'node:util';
|
|
2
1
|
import { DataloaderType } from '../enums.js';
|
|
3
2
|
import { helper, wrap } from './wrap.js';
|
|
4
3
|
import { Utils } from '../utils/Utils.js';
|
|
4
|
+
import { QueryHelper } from '../utils/QueryHelper.js';
|
|
5
|
+
import { NotFoundError } from '../errors.js';
|
|
6
|
+
import { inspect } from '../logging/inspect.js';
|
|
5
7
|
export class Reference {
|
|
6
8
|
entity;
|
|
9
|
+
property;
|
|
7
10
|
constructor(entity) {
|
|
8
11
|
this.entity = entity;
|
|
9
12
|
this.set(entity);
|
|
@@ -33,10 +36,15 @@ export class Reference {
|
|
|
33
36
|
}
|
|
34
37
|
static createFromPK(entityType, pk, options) {
|
|
35
38
|
const ref = this.createNakedFromPK(entityType, pk, options);
|
|
36
|
-
return helper(ref)
|
|
39
|
+
return helper(ref)?.toReference() ?? ref;
|
|
37
40
|
}
|
|
38
41
|
static createNakedFromPK(entityType, pk, options) {
|
|
39
42
|
const factory = entityType.prototype.__factory;
|
|
43
|
+
if (!factory) {
|
|
44
|
+
// this can happen only if `ref()` is used as a property initializer, and the value is important only for the
|
|
45
|
+
// inference of defaults, so it's fine to return it directly without wrapping with `Reference` class
|
|
46
|
+
return pk;
|
|
47
|
+
}
|
|
40
48
|
const entity = factory.createReference(entityType, pk, {
|
|
41
49
|
merge: false,
|
|
42
50
|
convertCustomTypes: false,
|
|
@@ -58,7 +66,9 @@ export class Reference {
|
|
|
58
66
|
*/
|
|
59
67
|
static wrapReference(entity, prop) {
|
|
60
68
|
if (entity && prop.ref && !Reference.isReference(entity)) {
|
|
61
|
-
|
|
69
|
+
const ref = Reference.create(entity);
|
|
70
|
+
ref.property = prop;
|
|
71
|
+
return ref;
|
|
62
72
|
}
|
|
63
73
|
return entity;
|
|
64
74
|
}
|
|
@@ -78,13 +88,14 @@ export class Reference {
|
|
|
78
88
|
if (!wrapped.__em) {
|
|
79
89
|
return this.entity;
|
|
80
90
|
}
|
|
91
|
+
options = { ...options, filters: QueryHelper.mergePropertyFilters(this.property?.filters, options.filters) };
|
|
81
92
|
if (this.isInitialized() && !options.refresh && options.populate) {
|
|
82
93
|
await wrapped.__em.populate(this.entity, options.populate, options);
|
|
83
94
|
}
|
|
84
95
|
if (!this.isInitialized() || options.refresh) {
|
|
85
96
|
if (options.dataloader ?? [DataloaderType.ALL, DataloaderType.REFERENCE].includes(wrapped.__em.config.getDataloaderType())) {
|
|
86
|
-
|
|
87
|
-
return
|
|
97
|
+
const dataLoader = await wrapped.__em.getDataLoader('ref');
|
|
98
|
+
return dataLoader.load([this, options]);
|
|
88
99
|
}
|
|
89
100
|
return wrapped.init(options);
|
|
90
101
|
}
|
|
@@ -135,9 +146,9 @@ export class Reference {
|
|
|
135
146
|
return wrap(this.entity).toJSON(...args);
|
|
136
147
|
}
|
|
137
148
|
/** @ignore */
|
|
138
|
-
[inspect.custom](depth = 2) {
|
|
149
|
+
[Symbol.for('nodejs.util.inspect.custom')](depth = 2) {
|
|
139
150
|
const object = { ...this };
|
|
140
|
-
const hidden = ['meta'];
|
|
151
|
+
const hidden = ['meta', 'property'];
|
|
141
152
|
hidden.forEach(k => delete object[k]);
|
|
142
153
|
const ret = inspect(object, { depth });
|
|
143
154
|
const wrapped = helper(this.entity);
|
|
@@ -171,6 +182,20 @@ export class ScalarReference {
|
|
|
171
182
|
}
|
|
172
183
|
return this.value;
|
|
173
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Ensures the underlying entity is loaded first (without reloading it if it already is loaded).
|
|
187
|
+
* Returns the entity or throws an error just like `em.findOneOrFail()` (and respects the same config options).
|
|
188
|
+
*/
|
|
189
|
+
async loadOrFail(options = {}) {
|
|
190
|
+
const ret = await this.load(options);
|
|
191
|
+
if (ret == null) {
|
|
192
|
+
const wrapped = helper(this.entity);
|
|
193
|
+
options.failHandler ??= wrapped.__em.config.get('findOneOrFailHandler');
|
|
194
|
+
const entityName = this.entity.constructor.name;
|
|
195
|
+
throw NotFoundError.failedToLoadProperty(entityName, this.property, wrapped.getPrimaryKey());
|
|
196
|
+
}
|
|
197
|
+
return ret;
|
|
198
|
+
}
|
|
174
199
|
set(value) {
|
|
175
200
|
this.value = value;
|
|
176
201
|
this.initialized = true;
|
|
@@ -186,9 +211,9 @@ export class ScalarReference {
|
|
|
186
211
|
isInitialized() {
|
|
187
212
|
return this.initialized;
|
|
188
213
|
}
|
|
189
|
-
/* v8 ignore next 4 */
|
|
190
214
|
/** @ignore */
|
|
191
|
-
|
|
215
|
+
/* v8 ignore next */
|
|
216
|
+
[Symbol.for('nodejs.util.inspect.custom')]() {
|
|
192
217
|
return this.initialized ? `Ref<${inspect(this.value)}>` : `Ref<?>`;
|
|
193
218
|
}
|
|
194
219
|
}
|