@mikro-orm/core 7.0.0-dev.3 → 7.0.0-dev.31
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 +50 -7
- package/EntityManager.js +141 -97
- package/MikroORM.js +0 -1
- package/README.md +1 -2
- package/cache/FileCacheAdapter.d.ts +2 -1
- package/cache/FileCacheAdapter.js +6 -4
- package/connections/Connection.d.ts +4 -2
- package/connections/Connection.js +2 -2
- package/decorators/Check.d.ts +2 -2
- package/decorators/Embeddable.d.ts +5 -5
- package/decorators/Embeddable.js +1 -1
- package/decorators/Embedded.d.ts +6 -12
- package/decorators/Entity.d.ts +20 -5
- package/decorators/Entity.js +0 -1
- package/decorators/Enum.d.ts +1 -1
- package/decorators/Formula.d.ts +1 -2
- package/decorators/Indexed.d.ts +10 -8
- package/decorators/Indexed.js +1 -1
- package/decorators/ManyToMany.d.ts +4 -2
- package/decorators/ManyToOne.d.ts +6 -2
- package/decorators/OneToMany.d.ts +4 -4
- package/decorators/OneToOne.d.ts +5 -1
- package/decorators/PrimaryKey.d.ts +2 -3
- package/decorators/Property.d.ts +1 -1
- package/decorators/Transactional.d.ts +1 -0
- package/decorators/Transactional.js +3 -3
- package/drivers/IDatabaseDriver.d.ts +8 -1
- package/entity/ArrayCollection.d.ts +4 -2
- package/entity/ArrayCollection.js +18 -6
- package/entity/Collection.d.ts +1 -2
- package/entity/Collection.js +16 -10
- package/entity/EntityAssigner.d.ts +1 -1
- package/entity/EntityAssigner.js +9 -1
- package/entity/EntityFactory.d.ts +6 -0
- package/entity/EntityFactory.js +21 -7
- package/entity/EntityHelper.js +8 -1
- package/entity/EntityLoader.d.ts +3 -2
- package/entity/EntityLoader.js +54 -35
- package/entity/EntityRepository.d.ts +1 -1
- package/entity/EntityValidator.js +1 -1
- package/entity/Reference.d.ts +8 -7
- package/entity/Reference.js +22 -1
- package/entity/WrappedEntity.js +1 -1
- package/entity/defineEntity.d.ts +537 -0
- package/entity/defineEntity.js +693 -0
- package/entity/index.d.ts +2 -0
- package/entity/index.js +2 -0
- package/entity/utils.d.ts +7 -0
- package/entity/utils.js +15 -3
- package/enums.d.ts +16 -3
- package/enums.js +13 -0
- package/errors.d.ts +6 -0
- package/errors.js +14 -0
- package/events/EventSubscriber.d.ts +3 -1
- package/hydration/ObjectHydrator.js +10 -2
- package/index.d.ts +1 -1
- package/metadata/EntitySchema.d.ts +6 -4
- package/metadata/EntitySchema.js +33 -19
- package/metadata/MetadataDiscovery.d.ts +0 -1
- package/metadata/MetadataDiscovery.js +51 -29
- package/metadata/MetadataStorage.js +1 -1
- package/metadata/MetadataValidator.js +4 -3
- package/package.json +5 -5
- package/platforms/Platform.d.ts +3 -1
- package/serialization/EntitySerializer.d.ts +2 -0
- package/serialization/EntitySerializer.js +1 -1
- package/serialization/SerializationContext.js +13 -10
- package/types/BigIntType.d.ts +9 -6
- package/types/BigIntType.js +3 -0
- package/types/BooleanType.d.ts +1 -1
- package/types/DecimalType.d.ts +6 -4
- package/types/DecimalType.js +1 -1
- package/types/DoubleType.js +1 -1
- package/typings.d.ts +72 -35
- package/typings.js +24 -4
- package/unit-of-work/ChangeSetComputer.js +3 -1
- package/unit-of-work/ChangeSetPersister.d.ts +4 -2
- package/unit-of-work/ChangeSetPersister.js +21 -11
- package/unit-of-work/UnitOfWork.d.ts +2 -1
- package/unit-of-work/UnitOfWork.js +71 -24
- package/utils/AbstractSchemaGenerator.js +5 -2
- package/utils/Configuration.d.ts +15 -5
- package/utils/Configuration.js +7 -7
- package/utils/ConfigurationLoader.d.ts +0 -2
- package/utils/ConfigurationLoader.js +2 -24
- package/utils/Cursor.d.ts +3 -3
- package/utils/Cursor.js +3 -0
- package/utils/DataloaderUtils.d.ts +7 -2
- package/utils/DataloaderUtils.js +38 -7
- package/utils/EntityComparator.d.ts +6 -2
- package/utils/EntityComparator.js +98 -59
- package/utils/QueryHelper.d.ts +6 -0
- package/utils/QueryHelper.js +48 -5
- package/utils/RawQueryFragment.d.ts +34 -0
- package/utils/RawQueryFragment.js +40 -1
- package/utils/TransactionManager.d.ts +65 -0
- package/utils/TransactionManager.js +220 -0
- package/utils/Utils.d.ts +11 -7
- package/utils/Utils.js +67 -33
- package/utils/index.d.ts +1 -0
- package/utils/index.js +1 -0
- package/utils/upsert-utils.js +9 -1
package/entity/EntityFactory.js
CHANGED
|
@@ -45,6 +45,7 @@ export class EntityFactory {
|
|
|
45
45
|
let wrapped = exists && helper(exists);
|
|
46
46
|
if (wrapped && !options.refresh) {
|
|
47
47
|
wrapped.__processing = true;
|
|
48
|
+
Utils.dropUndefinedProperties(data);
|
|
48
49
|
this.mergeData(meta2, exists, data, options);
|
|
49
50
|
wrapped.__processing = false;
|
|
50
51
|
if (wrapped.isInitialized()) {
|
|
@@ -70,7 +71,7 @@ export class EntityFactory {
|
|
|
70
71
|
continue;
|
|
71
72
|
}
|
|
72
73
|
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils.isPlainObject(data[prop.name])) {
|
|
73
|
-
data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta
|
|
74
|
+
data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
|
|
74
75
|
}
|
|
75
76
|
data[prop.name] = prop.customType.convertToDatabaseValue(data[prop.name], this.platform, { key: prop.name, mode: 'hydration' });
|
|
76
77
|
}
|
|
@@ -86,7 +87,9 @@ export class EntityFactory {
|
|
|
86
87
|
}
|
|
87
88
|
if (options.merge && wrapped.hasPrimaryKey()) {
|
|
88
89
|
this.unitOfWork.register(entity, data, {
|
|
89
|
-
refresh
|
|
90
|
+
// Always refresh to ensure the payload is in correct shape for joined strategy. When loading nested relations,
|
|
91
|
+
// they will be created early without `Type.ensureComparable` being properly handled, resulting in extra updates.
|
|
92
|
+
refresh: options.initialized,
|
|
90
93
|
newEntity: options.newEntity,
|
|
91
94
|
loaded: options.initialized,
|
|
92
95
|
});
|
|
@@ -110,7 +113,7 @@ export class EntityFactory {
|
|
|
110
113
|
if (meta.versionProperty && data[meta.versionProperty] && data[meta.versionProperty] !== originalEntityData[meta.versionProperty]) {
|
|
111
114
|
diff[meta.versionProperty] = data[meta.versionProperty];
|
|
112
115
|
}
|
|
113
|
-
const diff2 = this.comparator.diffEntities(meta.className, existsData, data);
|
|
116
|
+
const diff2 = this.comparator.diffEntities(meta.className, existsData, data, { includeInverseSides: true });
|
|
114
117
|
// do not override values changed by user
|
|
115
118
|
Utils.keys(diff).forEach(key => delete diff2[key]);
|
|
116
119
|
Utils.keys(diff2).filter(key => {
|
|
@@ -168,8 +171,8 @@ export class EntityFactory {
|
|
|
168
171
|
if (Array.isArray(id)) {
|
|
169
172
|
id = Utils.getPrimaryKeyCondFromArray(id, meta);
|
|
170
173
|
}
|
|
171
|
-
const pks = Utils.getOrderedPrimaryKeys(id, meta, this.platform
|
|
172
|
-
const exists = this.unitOfWork.getById(entityName, pks, schema);
|
|
174
|
+
const pks = Utils.getOrderedPrimaryKeys(id, meta, this.platform);
|
|
175
|
+
const exists = this.unitOfWork.getById(entityName, pks, schema, options.convertCustomTypes);
|
|
173
176
|
if (exists) {
|
|
174
177
|
return exists;
|
|
175
178
|
}
|
|
@@ -228,6 +231,13 @@ export class EntityFactory {
|
|
|
228
231
|
}
|
|
229
232
|
return entity;
|
|
230
233
|
}
|
|
234
|
+
assignDefaultValues(entity, meta) {
|
|
235
|
+
for (const prop of meta.props) {
|
|
236
|
+
if (prop.onCreate) {
|
|
237
|
+
entity[prop.name] ??= prop.onCreate(entity, this.em);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
231
241
|
hydrate(entity, meta, data, options) {
|
|
232
242
|
if (options.initialized) {
|
|
233
243
|
this.hydrator.hydrate(entity, meta, data, this, 'full', options.newEntity, options.convertCustomTypes, options.schema, this.driver.getSchemaName(meta, options));
|
|
@@ -239,6 +249,10 @@ export class EntityFactory {
|
|
|
239
249
|
helper(entity)?.__loadedProperties.add(key);
|
|
240
250
|
helper(entity)?.__serializationContext.fields?.add(key);
|
|
241
251
|
});
|
|
252
|
+
const processOnCreateHooksEarly = options.processOnCreateHooksEarly ?? this.config.get('processOnCreateHooksEarly');
|
|
253
|
+
if (options.newEntity && processOnCreateHooksEarly) {
|
|
254
|
+
this.assignDefaultValues(entity, meta);
|
|
255
|
+
}
|
|
242
256
|
}
|
|
243
257
|
findEntity(data, meta, options) {
|
|
244
258
|
const schema = this.driver.getSchemaName(meta, options);
|
|
@@ -248,7 +262,7 @@ export class EntityFactory {
|
|
|
248
262
|
if (!Array.isArray(data) && meta.primaryKeys.some(pk => data[pk] == null)) {
|
|
249
263
|
return undefined;
|
|
250
264
|
}
|
|
251
|
-
const pks = Utils.getOrderedPrimaryKeys(data, meta, this.platform);
|
|
265
|
+
const pks = Utils.getOrderedPrimaryKeys(data, meta, this.platform, options.convertCustomTypes);
|
|
252
266
|
return this.unitOfWork.getById(meta.className, pks, schema);
|
|
253
267
|
}
|
|
254
268
|
processDiscriminatorColumn(meta, data) {
|
|
@@ -282,7 +296,7 @@ export class EntityFactory {
|
|
|
282
296
|
return meta.constructorParams.map(k => {
|
|
283
297
|
if (meta.properties[k] && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(meta.properties[k].kind) && data[k]) {
|
|
284
298
|
const pk = Reference.unwrapReference(data[k]);
|
|
285
|
-
const entity = this.unitOfWork.getById(meta.properties[k].type, pk, options.schema);
|
|
299
|
+
const entity = this.unitOfWork.getById(meta.properties[k].type, pk, options.schema, true);
|
|
286
300
|
if (entity) {
|
|
287
301
|
return entity;
|
|
288
302
|
}
|
package/entity/EntityHelper.js
CHANGED
|
@@ -146,10 +146,13 @@ export class EntityHelper {
|
|
|
146
146
|
set(val) {
|
|
147
147
|
const entity = Reference.unwrapReference(val ?? wrapped.__data[prop.name]);
|
|
148
148
|
const old = Reference.unwrapReference(wrapped.__data[prop.name]);
|
|
149
|
+
if (old && old !== entity && prop.kind === ReferenceKind.MANY_TO_ONE && prop.inversedBy && old[prop.inversedBy]) {
|
|
150
|
+
old[prop.inversedBy].removeWithoutPropagation(this);
|
|
151
|
+
}
|
|
149
152
|
wrapped.__data[prop.name] = Reference.wrapReference(val, prop);
|
|
150
153
|
// when propagation from inside hydration, we set the FK to the entity data immediately
|
|
151
154
|
if (val && hydrator.isRunning() && wrapped.__originalEntityData && prop.owner) {
|
|
152
|
-
wrapped.__originalEntityData[prop.name] = Utils.getPrimaryKeyValues(wrapped.__data[prop.name], prop.targetMeta
|
|
155
|
+
wrapped.__originalEntityData[prop.name] = Utils.getPrimaryKeyValues(wrapped.__data[prop.name], prop.targetMeta, true);
|
|
153
156
|
}
|
|
154
157
|
else {
|
|
155
158
|
wrapped.__touched = !hydrator.isRunning();
|
|
@@ -169,6 +172,9 @@ export class EntityHelper {
|
|
|
169
172
|
continue;
|
|
170
173
|
}
|
|
171
174
|
const inverse = value?.[prop2.name];
|
|
175
|
+
if (Utils.isCollection(inverse) && inverse.isPartial()) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
172
178
|
if (prop.kind === ReferenceKind.MANY_TO_ONE && Utils.isCollection(inverse) && inverse.isInitialized()) {
|
|
173
179
|
inverse.addWithoutPropagation(owner);
|
|
174
180
|
helper(owner).__em?.getUnitOfWork().cancelOrphanRemoval(owner);
|
|
@@ -207,6 +213,7 @@ export class EntityHelper {
|
|
|
207
213
|
}
|
|
208
214
|
if (old?.[prop2.name] != null) {
|
|
209
215
|
delete helper(old).__data[prop2.name];
|
|
216
|
+
old[prop2.name] = null;
|
|
210
217
|
}
|
|
211
218
|
}
|
|
212
219
|
static ensurePropagation(entity) {
|
package/entity/EntityLoader.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConnectionType, Dictionary, FilterQuery, PopulateOptions } from '../typings.js';
|
|
1
|
+
import type { AnyEntity, ConnectionType, Dictionary, EntityProperty, FilterQuery, PopulateOptions } from '../typings.js';
|
|
2
2
|
import type { EntityManager } from '../EntityManager.js';
|
|
3
3
|
import { LoadStrategy, type LockMode, type PopulateHint, PopulatePath, type QueryOrderMap } from '../enums.js';
|
|
4
4
|
import type { EntityField } from '../drivers/IDatabaseDriver.js';
|
|
@@ -49,7 +49,8 @@ export declare class EntityLoader {
|
|
|
49
49
|
private findChildren;
|
|
50
50
|
private mergePrimaryCondition;
|
|
51
51
|
private populateField;
|
|
52
|
-
|
|
52
|
+
/** @internal */
|
|
53
|
+
findChildrenFromPivotTable<Entity extends object>(filtered: Entity[], prop: EntityProperty<Entity>, options: Required<EntityLoaderOptions<Entity>>, orderBy?: QueryOrderMap<Entity>[], populate?: PopulateOptions<Entity>, pivotJoin?: boolean): Promise<AnyEntity[][]>;
|
|
53
54
|
private extractChildCondition;
|
|
54
55
|
private buildFields;
|
|
55
56
|
private getChildReferences;
|
package/entity/EntityLoader.js
CHANGED
|
@@ -38,7 +38,7 @@ export class EntityLoader {
|
|
|
38
38
|
options.refresh ??= false;
|
|
39
39
|
options.convertCustomTypes ??= true;
|
|
40
40
|
if (references.length > 0) {
|
|
41
|
-
await this.populateScalar(meta, references, options);
|
|
41
|
+
await this.populateScalar(meta, references, { ...options, populateWhere: undefined });
|
|
42
42
|
}
|
|
43
43
|
populate = this.normalizePopulate(entityName, populate, options.strategy, options.lookup);
|
|
44
44
|
const invalid = populate.find(({ field }) => !this.em.canPopulate(entityName, field));
|
|
@@ -140,17 +140,22 @@ export class EntityLoader {
|
|
|
140
140
|
const innerOrderBy = Utils.asArray(options.orderBy)
|
|
141
141
|
.filter(orderBy => (Array.isArray(orderBy[prop.name]) && orderBy[prop.name].length > 0) || Utils.isObject(orderBy[prop.name]))
|
|
142
142
|
.flatMap(orderBy => orderBy[prop.name]);
|
|
143
|
+
const where = await this.extractChildCondition(options, prop);
|
|
143
144
|
if (prop.kind === ReferenceKind.MANY_TO_MANY && this.driver.getPlatform().usesPivotTable()) {
|
|
144
|
-
|
|
145
|
+
const res = await this.findChildrenFromPivotTable(filtered, prop, options, innerOrderBy, populate, !!ref);
|
|
146
|
+
return Utils.flatten(res);
|
|
145
147
|
}
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
const { items, partial } = await this.findChildren(options.filtered ?? entities, prop, populate, {
|
|
149
|
+
...options,
|
|
150
|
+
where,
|
|
151
|
+
orderBy: innerOrderBy,
|
|
152
|
+
}, !!(ref || prop.mapToPk));
|
|
153
|
+
this.initializeCollections(filtered, prop, field, items, innerOrderBy.length > 0, partial);
|
|
154
|
+
return items;
|
|
150
155
|
}
|
|
151
156
|
async populateScalar(meta, filtered, options) {
|
|
152
157
|
const pk = Utils.getPrimaryKeyHash(meta.primaryKeys);
|
|
153
|
-
const ids = Utils.unique(filtered.map(e => Utils.getPrimaryKeyValues(e, meta
|
|
158
|
+
const ids = Utils.unique(filtered.map(e => Utils.getPrimaryKeyValues(e, meta, true)));
|
|
154
159
|
const where = this.mergePrimaryCondition(ids, pk, options, meta, this.metadata, this.driver.getPlatform());
|
|
155
160
|
const { filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, fields } = options;
|
|
156
161
|
await this.em.find(meta.className, where, {
|
|
@@ -159,15 +164,15 @@ export class EntityLoader {
|
|
|
159
164
|
populate: [],
|
|
160
165
|
});
|
|
161
166
|
}
|
|
162
|
-
initializeCollections(filtered, prop, field, children, customOrder) {
|
|
167
|
+
initializeCollections(filtered, prop, field, children, customOrder, partial) {
|
|
163
168
|
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
|
164
|
-
this.initializeOneToMany(filtered, children, prop, field);
|
|
169
|
+
this.initializeOneToMany(filtered, children, prop, field, partial);
|
|
165
170
|
}
|
|
166
171
|
if (prop.kind === ReferenceKind.MANY_TO_MANY && !this.driver.getPlatform().usesPivotTable()) {
|
|
167
|
-
this.initializeManyToMany(filtered, children, prop, field, customOrder);
|
|
172
|
+
this.initializeManyToMany(filtered, children, prop, field, customOrder, partial);
|
|
168
173
|
}
|
|
169
174
|
}
|
|
170
|
-
initializeOneToMany(filtered, children, prop, field) {
|
|
175
|
+
initializeOneToMany(filtered, children, prop, field, partial) {
|
|
171
176
|
const mapToPk = prop.targetMeta.properties[prop.mappedBy].mapToPk;
|
|
172
177
|
const map = {};
|
|
173
178
|
for (const entity of filtered) {
|
|
@@ -183,14 +188,14 @@ export class EntityLoader {
|
|
|
183
188
|
}
|
|
184
189
|
for (const entity of filtered) {
|
|
185
190
|
const key = helper(entity).getSerializedPrimaryKey();
|
|
186
|
-
entity[field].hydrate(map[key]);
|
|
191
|
+
entity[field].hydrate(map[key], undefined, partial);
|
|
187
192
|
}
|
|
188
193
|
}
|
|
189
|
-
initializeManyToMany(filtered, children, prop, field, customOrder) {
|
|
194
|
+
initializeManyToMany(filtered, children, prop, field, customOrder, partial) {
|
|
190
195
|
if (prop.mappedBy) {
|
|
191
196
|
for (const entity of filtered) {
|
|
192
197
|
const items = children.filter(child => child[prop.mappedBy].contains(entity, false));
|
|
193
|
-
entity[field].hydrate(items, true);
|
|
198
|
+
entity[field].hydrate(items, true, partial);
|
|
194
199
|
}
|
|
195
200
|
}
|
|
196
201
|
else { // owning side of M:N without pivot table needs to be reordered
|
|
@@ -200,7 +205,7 @@ export class EntityLoader {
|
|
|
200
205
|
if (!customOrder) {
|
|
201
206
|
items.sort((a, b) => order.indexOf(a) - order.indexOf(b));
|
|
202
207
|
}
|
|
203
|
-
entity[field].hydrate(items, true);
|
|
208
|
+
entity[field].hydrate(items, true, partial);
|
|
204
209
|
}
|
|
205
210
|
}
|
|
206
211
|
}
|
|
@@ -209,6 +214,7 @@ export class EntityLoader {
|
|
|
209
214
|
const meta = prop.targetMeta;
|
|
210
215
|
let fk = Utils.getPrimaryKeyHash(meta.primaryKeys);
|
|
211
216
|
let schema = options.schema;
|
|
217
|
+
const partial = !Utils.isEmpty(prop.where) || !Utils.isEmpty(options.where);
|
|
212
218
|
if (prop.kind === ReferenceKind.ONE_TO_MANY || (prop.kind === ReferenceKind.MANY_TO_MANY && !prop.owner)) {
|
|
213
219
|
fk = meta.properties[prop.mappedBy].name;
|
|
214
220
|
}
|
|
@@ -218,7 +224,7 @@ export class EntityLoader {
|
|
|
218
224
|
children.push(...this.filterByReferences(entities, prop.name, options.refresh));
|
|
219
225
|
}
|
|
220
226
|
if (children.length === 0) {
|
|
221
|
-
return [];
|
|
227
|
+
return { items: [], partial };
|
|
222
228
|
}
|
|
223
229
|
if (!schema && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
|
|
224
230
|
schema = children.find(e => e.__helper.__schema)?.__helper.__schema;
|
|
@@ -227,7 +233,7 @@ export class EntityLoader {
|
|
|
227
233
|
let where = this.mergePrimaryCondition(ids, fk, options, meta, this.metadata, this.driver.getPlatform());
|
|
228
234
|
const fields = this.buildFields(options.fields, prop, ref);
|
|
229
235
|
/* eslint-disable prefer-const */
|
|
230
|
-
let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, } = options;
|
|
236
|
+
let { refresh, filters, convertCustomTypes, lockMode, strategy, populateWhere = 'infer', connectionType, logging, } = options;
|
|
231
237
|
/* eslint-enable prefer-const */
|
|
232
238
|
if (typeof populateWhere === 'object') {
|
|
233
239
|
populateWhere = await this.extractChildCondition({ where: populateWhere }, prop);
|
|
@@ -249,9 +255,13 @@ export class EntityLoader {
|
|
|
249
255
|
}
|
|
250
256
|
}
|
|
251
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
|
+
});
|
|
252
262
|
const items = await this.em.find(prop.type, where, {
|
|
253
263
|
filters, convertCustomTypes, lockMode, populateWhere, logging,
|
|
254
|
-
orderBy
|
|
264
|
+
orderBy,
|
|
255
265
|
populate: populate.children ?? populate.all ?? [],
|
|
256
266
|
exclude: Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude,
|
|
257
267
|
strategy, fields, schema, connectionType,
|
|
@@ -267,7 +277,7 @@ export class EntityLoader {
|
|
|
267
277
|
this.em.getUnitOfWork()['loadedEntities'].delete(item);
|
|
268
278
|
}
|
|
269
279
|
}
|
|
270
|
-
return items;
|
|
280
|
+
return { items, partial };
|
|
271
281
|
}
|
|
272
282
|
mergePrimaryCondition(ids, pk, options, meta, metadata, platform) {
|
|
273
283
|
const cond1 = QueryHelper.processWhere({ where: { [pk]: { $in: ids } }, entityName: meta.className, metadata, platform, convertCustomTypes: !options.convertCustomTypes });
|
|
@@ -310,10 +320,18 @@ export class EntityLoader {
|
|
|
310
320
|
const innerOrderBy = Utils.asArray(options.orderBy)
|
|
311
321
|
.filter(orderBy => Utils.isObject(orderBy[prop.name]))
|
|
312
322
|
.map(orderBy => orderBy[prop.name]);
|
|
313
|
-
const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType, logging } = options;
|
|
323
|
+
const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType, logging, schema } = options;
|
|
314
324
|
const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
|
|
315
|
-
const
|
|
316
|
-
|
|
325
|
+
const visited = options.visited;
|
|
326
|
+
for (const entity of entities) {
|
|
327
|
+
visited.delete(entity);
|
|
328
|
+
}
|
|
329
|
+
const unique = Utils.unique(children);
|
|
330
|
+
const filtered = unique.filter(e => !visited.has(e));
|
|
331
|
+
for (const entity of entities) {
|
|
332
|
+
visited.add(entity);
|
|
333
|
+
}
|
|
334
|
+
await this.populate(prop.type, unique, populate.children ?? populate.all, {
|
|
317
335
|
where: await this.extractChildCondition(options, prop, false),
|
|
318
336
|
orderBy: innerOrderBy,
|
|
319
337
|
fields,
|
|
@@ -325,12 +343,16 @@ export class EntityLoader {
|
|
|
325
343
|
populateWhere,
|
|
326
344
|
connectionType,
|
|
327
345
|
logging,
|
|
346
|
+
schema,
|
|
328
347
|
// @ts-ignore not a public option, will be propagated to the populate call
|
|
329
348
|
refresh: refresh && !filtered.every(item => options.visited.has(item)),
|
|
330
349
|
// @ts-ignore not a public option, will be propagated to the populate call
|
|
331
350
|
visited: options.visited,
|
|
351
|
+
// @ts-ignore not a public option
|
|
352
|
+
filtered,
|
|
332
353
|
});
|
|
333
354
|
}
|
|
355
|
+
/** @internal */
|
|
334
356
|
async findChildrenFromPivotTable(filtered, prop, options, orderBy, populate, pivotJoin) {
|
|
335
357
|
const ids = filtered.map(e => e.__helper.__primaryKeys);
|
|
336
358
|
const refresh = options.refresh;
|
|
@@ -368,7 +390,7 @@ export class EntityLoader {
|
|
|
368
390
|
return this.em.getUnitOfWork().register(entity, item, { refresh, loaded: true });
|
|
369
391
|
});
|
|
370
392
|
entity[prop.name].hydrate(items, true);
|
|
371
|
-
children.push(
|
|
393
|
+
children.push(items);
|
|
372
394
|
}
|
|
373
395
|
return children;
|
|
374
396
|
}
|
|
@@ -444,23 +466,20 @@ export class EntityLoader {
|
|
|
444
466
|
}
|
|
445
467
|
getChildReferences(entities, prop, options, ref) {
|
|
446
468
|
const filtered = this.filterCollections(entities, prop.name, options, ref);
|
|
447
|
-
const children = [];
|
|
448
469
|
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
|
449
|
-
|
|
470
|
+
return filtered.map(e => e[prop.name].owner);
|
|
450
471
|
}
|
|
451
|
-
|
|
452
|
-
|
|
472
|
+
if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
|
|
473
|
+
return filtered.reduce((a, b) => {
|
|
453
474
|
a.push(...b[prop.name].getItems());
|
|
454
475
|
return a;
|
|
455
|
-
}, [])
|
|
476
|
+
}, []);
|
|
456
477
|
}
|
|
457
|
-
|
|
458
|
-
|
|
478
|
+
if (prop.kind === ReferenceKind.MANY_TO_MANY) { // inverse side
|
|
479
|
+
return filtered;
|
|
459
480
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
463
|
-
return children;
|
|
481
|
+
// MANY_TO_ONE or ONE_TO_ONE
|
|
482
|
+
return this.filterReferences(entities, prop.name, options, ref);
|
|
464
483
|
}
|
|
465
484
|
filterCollections(entities, field, options, ref) {
|
|
466
485
|
if (options.refresh) {
|
|
@@ -514,7 +533,7 @@ export class EntityLoader {
|
|
|
514
533
|
if (refresh) {
|
|
515
534
|
return entities;
|
|
516
535
|
}
|
|
517
|
-
return entities.filter(e => !e[field]?.__helper?.__initialized);
|
|
536
|
+
return entities.filter(e => e[field] !== null && !e[field]?.__helper?.__initialized);
|
|
518
537
|
}
|
|
519
538
|
lookupAllRelationships(entityName) {
|
|
520
539
|
const ret = [];
|
|
@@ -80,7 +80,7 @@ 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>(where: FilterQuery<Entity>, options: FindByCursorOptions<Entity, Hint, Fields, Excludes>): Promise<Cursor<Entity, Hint, Fields, Excludes>>;
|
|
83
|
+
findByCursor<Hint extends string = never, Fields extends string = '*', Excludes extends string = never, IncludeCount extends boolean = true>(where: FilterQuery<Entity>, 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
|
*/
|
|
@@ -11,7 +11,7 @@ export class EntityValidator {
|
|
|
11
11
|
}
|
|
12
12
|
validate(entity, payload, meta) {
|
|
13
13
|
meta.props.forEach(prop => {
|
|
14
|
-
if (prop.inherited) {
|
|
14
|
+
if (prop.inherited || (prop.persist === false && prop.userDefined !== false)) {
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
17
|
if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
|
package/entity/Reference.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { inspect } from 'node:util';
|
|
2
|
-
import type { AddEager, Dictionary, EntityClass, EntityKey, EntityProperty, Loaded, LoadedReference, Primary, Ref } from '../typings.js';
|
|
2
|
+
import type { AddEager, AddOptional, Dictionary, EntityClass, EntityKey, EntityProperty, Loaded, LoadedReference, Primary, Ref } from '../typings.js';
|
|
3
3
|
import type { FindOneOptions, FindOneOrFailOptions } from '../drivers/IDatabaseDriver.js';
|
|
4
4
|
export declare class Reference<T extends object> {
|
|
5
5
|
private entity;
|
|
@@ -56,6 +56,11 @@ export declare class ScalarReference<Value> {
|
|
|
56
56
|
* Returns either the whole entity, or the requested property.
|
|
57
57
|
*/
|
|
58
58
|
load(options?: Omit<LoadReferenceOptions<any, any>, 'populate' | 'fields' | 'exclude'>): Promise<Value | undefined>;
|
|
59
|
+
/**
|
|
60
|
+
* Ensures the underlying entity is loaded first (without reloading it if it already is loaded).
|
|
61
|
+
* Returns the entity or throws an error just like `em.findOneOrFail()` (and respects the same config options).
|
|
62
|
+
*/
|
|
63
|
+
loadOrFail(options?: Omit<LoadReferenceOrFailOptions<any, any>, 'populate' | 'fields' | 'exclude'>): Promise<Value>;
|
|
59
64
|
set(value: Value): void;
|
|
60
65
|
bind<Entity extends object>(entity: Entity, property: EntityKey<Entity>): void;
|
|
61
66
|
unwrap(): Value | undefined;
|
|
@@ -72,15 +77,11 @@ export interface LoadReferenceOrFailOptions<T extends object, P extends string =
|
|
|
72
77
|
/**
|
|
73
78
|
* shortcut for `wrap(entity).toReference()`
|
|
74
79
|
*/
|
|
75
|
-
export declare function ref<
|
|
80
|
+
export declare function ref<I extends unknown | Ref<unknown> | undefined | null, T extends I & {}>(entity: I): Ref<T> & LoadedReference<Loaded<T, AddEager<T>>> | AddOptional<typeof entity>;
|
|
76
81
|
/**
|
|
77
82
|
* shortcut for `Reference.createFromPK(entityType, pk)`
|
|
78
83
|
*/
|
|
79
|
-
export declare function ref<T, PKV extends Primary<T> = Primary<T>>(entityType: EntityClass<T>, pk
|
|
80
|
-
/**
|
|
81
|
-
* shortcut for `wrap(entity).toReference()`
|
|
82
|
-
*/
|
|
83
|
-
export declare function ref<T>(value: T | Ref<T>): Ref<T> & LoadedReference<Loaded<T, AddEager<T>>>;
|
|
84
|
+
export declare function ref<I extends unknown | undefined | null, T, PKV extends Primary<T> = Primary<T>>(entityType: EntityClass<T>, pk: I): Ref<T> | AddOptional<typeof pk>;
|
|
84
85
|
/**
|
|
85
86
|
* shortcut for `Reference.createNakedFromPK(entityType, pk)`
|
|
86
87
|
*/
|
package/entity/Reference.js
CHANGED
|
@@ -2,6 +2,7 @@ import { inspect } from 'node:util';
|
|
|
2
2
|
import { DataloaderType } from '../enums.js';
|
|
3
3
|
import { helper, wrap } from './wrap.js';
|
|
4
4
|
import { Utils } from '../utils/Utils.js';
|
|
5
|
+
import { NotFoundError } from '../errors.js';
|
|
5
6
|
export class Reference {
|
|
6
7
|
entity;
|
|
7
8
|
constructor(entity) {
|
|
@@ -33,10 +34,15 @@ export class Reference {
|
|
|
33
34
|
}
|
|
34
35
|
static createFromPK(entityType, pk, options) {
|
|
35
36
|
const ref = this.createNakedFromPK(entityType, pk, options);
|
|
36
|
-
return helper(ref)
|
|
37
|
+
return helper(ref)?.toReference() ?? ref;
|
|
37
38
|
}
|
|
38
39
|
static createNakedFromPK(entityType, pk, options) {
|
|
39
40
|
const factory = entityType.prototype.__factory;
|
|
41
|
+
if (!factory) {
|
|
42
|
+
// this can happen only if `ref()` is used as a property initializer, and the value is important only for the
|
|
43
|
+
// inference of defaults, so it's fine to return it directly without wrapping with `Reference` class
|
|
44
|
+
return pk;
|
|
45
|
+
}
|
|
40
46
|
const entity = factory.createReference(entityType, pk, {
|
|
41
47
|
merge: false,
|
|
42
48
|
convertCustomTypes: false,
|
|
@@ -171,6 +177,21 @@ export class ScalarReference {
|
|
|
171
177
|
}
|
|
172
178
|
return this.value;
|
|
173
179
|
}
|
|
180
|
+
/**
|
|
181
|
+
* Ensures the underlying entity is loaded first (without reloading it if it already is loaded).
|
|
182
|
+
* Returns the entity or throws an error just like `em.findOneOrFail()` (and respects the same config options).
|
|
183
|
+
*/
|
|
184
|
+
async loadOrFail(options = {}) {
|
|
185
|
+
const ret = await this.load(options);
|
|
186
|
+
if (!ret) {
|
|
187
|
+
const wrapped = helper(this.entity);
|
|
188
|
+
options.failHandler ??= wrapped.__em.config.get('findOneOrFailHandler');
|
|
189
|
+
const entityName = this.entity.constructor.name;
|
|
190
|
+
const where = wrapped.getPrimaryKey();
|
|
191
|
+
throw new NotFoundError(`${entityName} (${where}) failed to load property '${this.property}'`);
|
|
192
|
+
}
|
|
193
|
+
return ret;
|
|
194
|
+
}
|
|
174
195
|
set(value) {
|
|
175
196
|
this.value = value;
|
|
176
197
|
this.initialized = true;
|
package/entity/WrappedEntity.js
CHANGED
|
@@ -150,7 +150,7 @@ export class WrappedEntity {
|
|
|
150
150
|
return this.__em?.config ?? this.entity.__config;
|
|
151
151
|
}
|
|
152
152
|
get __primaryKeys() {
|
|
153
|
-
return Utils.getPrimaryKeyValues(this.entity, this.__meta
|
|
153
|
+
return Utils.getPrimaryKeyValues(this.entity, this.__meta);
|
|
154
154
|
}
|
|
155
155
|
/** @ignore */
|
|
156
156
|
[inspect.custom]() {
|