@mikro-orm/core 7.0.0-dev.99 → 7.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/EntityManager.d.ts +34 -17
- package/EntityManager.js +95 -103
- package/MikroORM.d.ts +5 -5
- package/MikroORM.js +25 -20
- package/cache/FileCacheAdapter.js +11 -3
- package/connections/Connection.d.ts +3 -2
- package/connections/Connection.js +4 -3
- package/drivers/DatabaseDriver.d.ts +11 -11
- package/drivers/DatabaseDriver.js +91 -25
- package/drivers/IDatabaseDriver.d.ts +50 -20
- package/entity/BaseEntity.d.ts +61 -1
- package/entity/Collection.d.ts +8 -1
- package/entity/Collection.js +12 -13
- package/entity/EntityAssigner.js +9 -9
- package/entity/EntityFactory.d.ts +6 -1
- package/entity/EntityFactory.js +40 -22
- package/entity/EntityHelper.d.ts +2 -2
- package/entity/EntityHelper.js +27 -4
- package/entity/EntityLoader.d.ts +5 -4
- package/entity/EntityLoader.js +193 -80
- package/entity/EntityRepository.d.ts +27 -7
- package/entity/EntityRepository.js +8 -2
- package/entity/PolymorphicRef.d.ts +12 -0
- package/entity/PolymorphicRef.js +18 -0
- package/entity/WrappedEntity.d.ts +2 -2
- package/entity/WrappedEntity.js +1 -1
- package/entity/defineEntity.d.ts +89 -50
- package/entity/defineEntity.js +12 -0
- package/entity/index.d.ts +1 -0
- package/entity/index.js +1 -0
- package/entity/utils.d.ts +6 -1
- package/entity/utils.js +33 -0
- package/entity/validators.js +2 -2
- package/enums.d.ts +2 -2
- package/enums.js +1 -0
- package/errors.d.ts +16 -8
- package/errors.js +40 -13
- package/hydration/ObjectHydrator.js +63 -21
- package/index.d.ts +1 -1
- package/logging/colors.d.ts +1 -1
- package/logging/colors.js +7 -6
- package/logging/inspect.js +1 -6
- package/metadata/EntitySchema.d.ts +43 -13
- package/metadata/EntitySchema.js +82 -27
- package/metadata/MetadataDiscovery.d.ts +60 -3
- package/metadata/MetadataDiscovery.js +665 -154
- package/metadata/MetadataProvider.js +3 -1
- package/metadata/MetadataStorage.d.ts +13 -6
- package/metadata/MetadataStorage.js +64 -19
- package/metadata/MetadataValidator.d.ts +32 -2
- package/metadata/MetadataValidator.js +196 -31
- package/metadata/discover-entities.js +5 -5
- package/metadata/types.d.ts +111 -14
- package/naming-strategy/AbstractNamingStrategy.d.ts +11 -3
- package/naming-strategy/AbstractNamingStrategy.js +12 -0
- 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 +17 -3
- package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
- package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
- package/package.json +2 -2
- package/platforms/Platform.d.ts +4 -2
- package/platforms/Platform.js +5 -2
- package/serialization/EntitySerializer.d.ts +3 -0
- package/serialization/EntitySerializer.js +15 -13
- package/serialization/EntityTransformer.js +6 -6
- package/serialization/SerializationContext.d.ts +6 -6
- package/typings.d.ts +325 -110
- package/typings.js +84 -17
- package/unit-of-work/ChangeSet.d.ts +4 -3
- package/unit-of-work/ChangeSet.js +2 -3
- package/unit-of-work/ChangeSetComputer.d.ts +3 -6
- package/unit-of-work/ChangeSetComputer.js +34 -13
- package/unit-of-work/ChangeSetPersister.d.ts +12 -10
- package/unit-of-work/ChangeSetPersister.js +55 -25
- 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 +21 -3
- package/unit-of-work/UnitOfWork.js +203 -56
- package/utils/AbstractSchemaGenerator.js +17 -8
- package/utils/AsyncContext.d.ts +6 -0
- package/utils/AsyncContext.js +42 -0
- package/utils/Configuration.d.ts +52 -11
- package/utils/Configuration.js +12 -8
- package/utils/Cursor.js +21 -8
- package/utils/DataloaderUtils.js +13 -11
- package/utils/EntityComparator.d.ts +14 -7
- package/utils/EntityComparator.js +132 -46
- package/utils/QueryHelper.d.ts +16 -6
- package/utils/QueryHelper.js +53 -18
- package/utils/RawQueryFragment.d.ts +28 -23
- package/utils/RawQueryFragment.js +34 -56
- package/utils/RequestContext.js +2 -2
- package/utils/TransactionContext.js +2 -2
- package/utils/TransactionManager.js +1 -1
- package/utils/Utils.d.ts +7 -26
- package/utils/Utils.js +25 -79
- package/utils/clone.js +7 -21
- package/utils/env-vars.d.ts +4 -0
- package/utils/env-vars.js +13 -3
- package/utils/fs-utils.d.ts +21 -0
- package/utils/fs-utils.js +106 -11
- package/utils/upsert-utils.d.ts +4 -4
package/entity/Collection.d.ts
CHANGED
|
@@ -62,7 +62,6 @@ export declare class Collection<T extends object, O extends object = object> {
|
|
|
62
62
|
init<TT extends T, P extends string = never>(options?: InitCollectionOptions<TT, P>): Promise<LoadedCollection<Loaded<TT, P>>>;
|
|
63
63
|
private getEntityManager;
|
|
64
64
|
private createCondition;
|
|
65
|
-
private createOrderBy;
|
|
66
65
|
private createManyToManyCondition;
|
|
67
66
|
private createLoadCountCondition;
|
|
68
67
|
private checkInitialized;
|
|
@@ -104,10 +103,18 @@ export declare class Collection<T extends object, O extends object = object> {
|
|
|
104
103
|
* Tests for the existence of an element that satisfies the given predicate.
|
|
105
104
|
*/
|
|
106
105
|
exists(cb: (item: T) => boolean): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Returns the first element of this collection that satisfies the predicate.
|
|
108
|
+
*/
|
|
109
|
+
find<S extends T>(cb: (item: T, index: number) => item is S): S | undefined;
|
|
107
110
|
/**
|
|
108
111
|
* Returns the first element of this collection that satisfies the predicate.
|
|
109
112
|
*/
|
|
110
113
|
find(cb: (item: T, index: number) => boolean): T | undefined;
|
|
114
|
+
/**
|
|
115
|
+
* Extracts a subset of the collection items.
|
|
116
|
+
*/
|
|
117
|
+
filter<S extends T>(cb: (item: T, index: number) => item is S): S[];
|
|
111
118
|
/**
|
|
112
119
|
* Extracts a subset of the collection items.
|
|
113
120
|
*/
|
package/entity/Collection.js
CHANGED
|
@@ -83,7 +83,7 @@ export class Collection {
|
|
|
83
83
|
return this._count = this.length;
|
|
84
84
|
}
|
|
85
85
|
const cond = this.createLoadCountCondition(where ?? {});
|
|
86
|
-
const count = await em.count(this.property.
|
|
86
|
+
const count = await em.count(this.property.targetMeta.class, cond, countOptions);
|
|
87
87
|
if (!where) {
|
|
88
88
|
this._count = count;
|
|
89
89
|
}
|
|
@@ -92,15 +92,20 @@ export class Collection {
|
|
|
92
92
|
async matching(options) {
|
|
93
93
|
const em = this.getEntityManager();
|
|
94
94
|
const { where, ctx, ...opts } = options;
|
|
95
|
-
opts.orderBy = this.createOrderBy(opts.orderBy);
|
|
96
95
|
let items;
|
|
97
96
|
if (this.property.kind === ReferenceKind.MANY_TO_MANY && em.getPlatform().usesPivotTable()) {
|
|
98
|
-
|
|
97
|
+
// M:N via pivot table bypasses em.find(), so merge all 3 levels here
|
|
98
|
+
opts.orderBy = QueryHelper.mergeOrderBy(opts.orderBy, this.property.orderBy, this.property.targetMeta?.orderBy);
|
|
99
|
+
options.populate = await em.preparePopulate(this.property.targetMeta.class, options);
|
|
100
|
+
const cond = await em.applyFilters(this.property.targetMeta.class, where, options.filters ?? {}, 'read');
|
|
99
101
|
const map = await em.getDriver().loadFromPivotTable(this.property, [helper(this.owner).__primaryKeys], cond, opts.orderBy, ctx, options);
|
|
100
|
-
items = map[helper(this.owner).getSerializedPrimaryKey()].map((item) => em.merge(this.property.
|
|
102
|
+
items = map[helper(this.owner).getSerializedPrimaryKey()].map((item) => em.merge(this.property.targetMeta.class, item, { convertCustomTypes: true }));
|
|
103
|
+
await em.populate(items, options.populate, options);
|
|
101
104
|
}
|
|
102
105
|
else {
|
|
103
|
-
|
|
106
|
+
// em.find() merges entity-level orderBy, so only merge runtime + relation here
|
|
107
|
+
opts.orderBy = QueryHelper.mergeOrderBy(opts.orderBy, this.property.orderBy);
|
|
108
|
+
items = await em.find(this.property.targetMeta.class, this.createCondition(where), opts);
|
|
104
109
|
}
|
|
105
110
|
if (options.store) {
|
|
106
111
|
this.hydrate(items, true);
|
|
@@ -233,7 +238,7 @@ export class Collection {
|
|
|
233
238
|
options = { ...options, filters: QueryHelper.mergePropertyFilters(this.property.filters, options.filters) };
|
|
234
239
|
if (options.dataloader ?? [DataloaderType.ALL, DataloaderType.COLLECTION].includes(em.config.getDataloaderType())) {
|
|
235
240
|
const order = [...this.items]; // copy order of references
|
|
236
|
-
const orderBy =
|
|
241
|
+
const orderBy = QueryHelper.mergeOrderBy(options.orderBy, this.property.orderBy, this.property.targetMeta?.orderBy);
|
|
237
242
|
const customOrder = orderBy.length > 0;
|
|
238
243
|
const pivotTable = this.property.kind === ReferenceKind.MANY_TO_MANY && em.getPlatform().usesPivotTable();
|
|
239
244
|
const loader = await em.getDataLoader(pivotTable ? 'm:n' : '1:m');
|
|
@@ -297,12 +302,6 @@ export class Collection {
|
|
|
297
302
|
}
|
|
298
303
|
return cond;
|
|
299
304
|
}
|
|
300
|
-
createOrderBy(orderBy = []) {
|
|
301
|
-
if (Utils.isEmpty(orderBy) && this.property.orderBy) {
|
|
302
|
-
orderBy = this.property.orderBy;
|
|
303
|
-
}
|
|
304
|
-
return Utils.asArray(orderBy);
|
|
305
|
-
}
|
|
306
305
|
createManyToManyCondition(cond) {
|
|
307
306
|
const dict = cond;
|
|
308
307
|
if (this.property.owner || this.property.pivotTable) {
|
|
@@ -330,7 +329,7 @@ export class Collection {
|
|
|
330
329
|
}
|
|
331
330
|
checkInitialized() {
|
|
332
331
|
if (!this.isInitialized()) {
|
|
333
|
-
throw new Error(`Collection<${this.property.type}> of entity ${this.owner.
|
|
332
|
+
throw new Error(`Collection<${this.property.type}> of entity ${helper(this.owner).__meta.name}[${helper(this.owner).getSerializedPrimaryKey()}] not initialized`);
|
|
334
333
|
}
|
|
335
334
|
}
|
|
336
335
|
/**
|
package/entity/EntityAssigner.js
CHANGED
|
@@ -77,7 +77,7 @@ export class EntityAssigner {
|
|
|
77
77
|
if (options.updateByPrimaryKey) {
|
|
78
78
|
const pk = Utils.extractPK(value, prop.targetMeta);
|
|
79
79
|
if (pk) {
|
|
80
|
-
const ref = options.em.getReference(prop.
|
|
80
|
+
const ref = options.em.getReference(prop.targetMeta.class, pk, options);
|
|
81
81
|
// if the PK differs, we want to change the target entity, not update it
|
|
82
82
|
const wrappedChild = helper(ref);
|
|
83
83
|
const sameTarget = wrappedChild.getSerializedPrimaryKey() === wrapped.getSerializedPrimaryKey();
|
|
@@ -141,13 +141,13 @@ export class EntityAssigner {
|
|
|
141
141
|
entity[prop.name] = Reference.wrapReference(value, prop);
|
|
142
142
|
}
|
|
143
143
|
else if (Utils.isPrimaryKey(value, true) && EntityAssigner.validateEM(em)) {
|
|
144
|
-
entity[prop.name] = prop.mapToPk ? value : Reference.wrapReference(em.getReference(prop.
|
|
144
|
+
entity[prop.name] = prop.mapToPk ? value : Reference.wrapReference(em.getReference(prop.targetMeta.class, value, options), prop);
|
|
145
145
|
}
|
|
146
146
|
else if (Utils.isPlainObject(value) && options.merge && EntityAssigner.validateEM(em)) {
|
|
147
|
-
entity[prop.name] = Reference.wrapReference(em.merge(prop.
|
|
147
|
+
entity[prop.name] = Reference.wrapReference(em.merge(prop.targetMeta.class, value, options), prop);
|
|
148
148
|
}
|
|
149
149
|
else if (Utils.isPlainObject(value) && EntityAssigner.validateEM(em)) {
|
|
150
|
-
entity[prop.name] = Reference.wrapReference(em.create(prop.
|
|
150
|
+
entity[prop.name] = Reference.wrapReference(em.create(prop.targetMeta.class, value, options), prop);
|
|
151
151
|
}
|
|
152
152
|
else {
|
|
153
153
|
const name = entity.constructor.name;
|
|
@@ -166,7 +166,7 @@ export class EntityAssigner {
|
|
|
166
166
|
if (options.updateNestedEntities && options.updateByPrimaryKey && Utils.isPlainObject(item)) {
|
|
167
167
|
const pk = Utils.extractPK(item, prop.targetMeta);
|
|
168
168
|
if (pk && EntityAssigner.validateEM(em)) {
|
|
169
|
-
const ref = em.getUnitOfWork().getById(prop.
|
|
169
|
+
const ref = em.getUnitOfWork().getById(prop.targetMeta.class, pk, options.schema);
|
|
170
170
|
if (ref) {
|
|
171
171
|
return EntityAssigner.assign(ref, item, options);
|
|
172
172
|
}
|
|
@@ -207,7 +207,7 @@ export class EntityAssigner {
|
|
|
207
207
|
entity[propName].push(...Object.values(tmp));
|
|
208
208
|
});
|
|
209
209
|
}
|
|
210
|
-
const create = () => EntityAssigner.validateEM(em) && em.getEntityFactory().createEmbeddable(prop.
|
|
210
|
+
const create = () => EntityAssigner.validateEM(em) && em.getEntityFactory().createEmbeddable(prop.targetMeta.class, value, {
|
|
211
211
|
convertCustomTypes: options.convertCustomTypes,
|
|
212
212
|
newEntity: options.mergeEmbeddedProperties ? !('propName' in entity) : true,
|
|
213
213
|
});
|
|
@@ -221,13 +221,13 @@ export class EntityAssigner {
|
|
|
221
221
|
return item;
|
|
222
222
|
}
|
|
223
223
|
if (Utils.isPrimaryKey(item) && EntityAssigner.validateEM(em)) {
|
|
224
|
-
return em.getReference(prop.
|
|
224
|
+
return em.getReference(prop.targetMeta.class, item, options);
|
|
225
225
|
}
|
|
226
226
|
if (Utils.isPlainObject(item) && options.merge && EntityAssigner.validateEM(em)) {
|
|
227
|
-
return em.merge(prop.
|
|
227
|
+
return em.merge(prop.targetMeta.class, item, options);
|
|
228
228
|
}
|
|
229
229
|
if (Utils.isPlainObject(item) && EntityAssigner.validateEM(em)) {
|
|
230
|
-
return em.create(prop.
|
|
230
|
+
return em.create(prop.targetMeta.class, item, options);
|
|
231
231
|
}
|
|
232
232
|
invalid.push(item);
|
|
233
233
|
return item;
|
|
@@ -16,6 +16,11 @@ export interface FactoryOptions {
|
|
|
16
16
|
schema?: string;
|
|
17
17
|
parentSchema?: string;
|
|
18
18
|
normalizeAccessors?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Property name to use for identity map lookup instead of the primary key.
|
|
21
|
+
* This is useful for creating references by unique non-PK properties.
|
|
22
|
+
*/
|
|
23
|
+
key?: string;
|
|
19
24
|
}
|
|
20
25
|
export declare class EntityFactory {
|
|
21
26
|
private readonly em;
|
|
@@ -29,7 +34,7 @@ export declare class EntityFactory {
|
|
|
29
34
|
constructor(em: EntityManager);
|
|
30
35
|
create<T extends object, P extends string = string>(entityName: EntityName<T>, data: EntityData<T>, options?: FactoryOptions): New<T, P>;
|
|
31
36
|
mergeData<T extends object>(meta: EntityMetadata<T>, entity: T, data: EntityData<T>, options?: FactoryOptions): void;
|
|
32
|
-
createReference<T extends object>(entityName: EntityName<T>, id: Primary<T> | Primary<T>[] | Record<string, Primary<T>>, options?: Pick<FactoryOptions, 'merge' | 'convertCustomTypes' | 'schema'>): T;
|
|
37
|
+
createReference<T extends object>(entityName: EntityName<T>, id: Primary<T> | Primary<T>[] | Record<string, Primary<T>>, options?: Pick<FactoryOptions, 'merge' | 'convertCustomTypes' | 'schema' | 'key'>): T;
|
|
33
38
|
createEmbeddable<T extends object>(entityName: EntityName<T>, data: EntityData<T>, options?: Pick<FactoryOptions, 'newEntity' | 'convertCustomTypes'>): T;
|
|
34
39
|
getComparator(): EntityComparator;
|
|
35
40
|
private createEntity;
|
package/entity/EntityFactory.js
CHANGED
|
@@ -30,7 +30,6 @@ export class EntityFactory {
|
|
|
30
30
|
if (data.__entity) {
|
|
31
31
|
return data;
|
|
32
32
|
}
|
|
33
|
-
entityName = Utils.className(entityName);
|
|
34
33
|
const meta = this.metadata.get(entityName);
|
|
35
34
|
if (meta.virtual) {
|
|
36
35
|
data = { ...data };
|
|
@@ -84,7 +83,7 @@ export class EntityFactory {
|
|
|
84
83
|
else {
|
|
85
84
|
this.hydrate(entity, meta2, data, options);
|
|
86
85
|
}
|
|
87
|
-
if (exists && meta.
|
|
86
|
+
if (exists && meta.root.inheritanceType && !(entity instanceof meta2.class)) {
|
|
88
87
|
Object.setPrototypeOf(entity, meta2.prototype);
|
|
89
88
|
}
|
|
90
89
|
if (options.merge && wrapped.hasPrimaryKey()) {
|
|
@@ -110,12 +109,12 @@ export class EntityFactory {
|
|
|
110
109
|
data = QueryHelper.processParams(data);
|
|
111
110
|
const existsData = this.comparator.prepareEntity(entity);
|
|
112
111
|
const originalEntityData = helper(entity).__originalEntityData ?? {};
|
|
113
|
-
const diff = this.comparator.diffEntities(meta.
|
|
112
|
+
const diff = this.comparator.diffEntities(meta.class, originalEntityData, existsData);
|
|
114
113
|
// version properties are not part of entity snapshots
|
|
115
114
|
if (meta.versionProperty && data[meta.versionProperty] && data[meta.versionProperty] !== originalEntityData[meta.versionProperty]) {
|
|
116
115
|
diff[meta.versionProperty] = data[meta.versionProperty];
|
|
117
116
|
}
|
|
118
|
-
const diff2 = this.comparator.diffEntities(meta.
|
|
117
|
+
const diff2 = this.comparator.diffEntities(meta.class, existsData, data, { includeInverseSides: true });
|
|
119
118
|
// do not override values changed by user
|
|
120
119
|
Utils.keys(diff).forEach(key => delete diff2[key]);
|
|
121
120
|
Utils.keys(diff2).filter(key => {
|
|
@@ -152,20 +151,31 @@ export class EntityFactory {
|
|
|
152
151
|
// we just create the entity from scratch, which will automatically pick the right one from the identity map and call `mergeData` on it
|
|
153
152
|
data[prop.name]
|
|
154
153
|
.filter(child => Utils.isPlainObject(child)) // objects with prototype can be PKs (e.g. `ObjectId`)
|
|
155
|
-
.forEach(child => this.create(prop.
|
|
154
|
+
.forEach(child => this.create(prop.targetMeta.class, child, options)); // we can ignore the value, we just care about the `mergeData` call
|
|
156
155
|
return;
|
|
157
156
|
}
|
|
158
157
|
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils.isPlainObject(data[prop.name]) && entity[prop.name] && helper(entity[prop.name]).__initialized) {
|
|
159
|
-
this.create(prop.
|
|
158
|
+
this.create(prop.targetMeta.class, data[prop.name], options); // we can ignore the value, we just care about the `mergeData` call
|
|
160
159
|
}
|
|
161
160
|
});
|
|
162
161
|
this.unitOfWork.normalizeEntityData(meta, originalEntityData);
|
|
163
162
|
}
|
|
164
163
|
createReference(entityName, id, options = {}) {
|
|
165
164
|
options.convertCustomTypes ??= true;
|
|
166
|
-
entityName = Utils.className(entityName);
|
|
167
165
|
const meta = this.metadata.get(entityName);
|
|
168
166
|
const schema = this.driver.getSchemaName(meta, options);
|
|
167
|
+
// Handle alternate key lookup
|
|
168
|
+
if (options.key) {
|
|
169
|
+
const value = '' + (Array.isArray(id) ? id[0] : Utils.isPlainObject(id) ? id[options.key] : id);
|
|
170
|
+
const exists = this.unitOfWork.getByKey(entityName, options.key, value, schema, options.convertCustomTypes);
|
|
171
|
+
if (exists) {
|
|
172
|
+
return exists;
|
|
173
|
+
}
|
|
174
|
+
// Create entity stub - storeByKey will set the alternate key property and store in identity map
|
|
175
|
+
const entity = this.create(entityName, {}, { ...options, initialized: false });
|
|
176
|
+
this.unitOfWork.storeByKey(entity, options.key, value, schema, options.convertCustomTypes);
|
|
177
|
+
return entity;
|
|
178
|
+
}
|
|
169
179
|
if (meta.simplePK) {
|
|
170
180
|
const exists = this.unitOfWork.getById(entityName, id, schema);
|
|
171
181
|
if (exists) {
|
|
@@ -188,7 +198,6 @@ export class EntityFactory {
|
|
|
188
198
|
return this.create(entityName, id, { ...options, initialized: false });
|
|
189
199
|
}
|
|
190
200
|
createEmbeddable(entityName, data, options = {}) {
|
|
191
|
-
entityName = Utils.className(entityName);
|
|
192
201
|
data = { ...data };
|
|
193
202
|
const meta = this.metadata.get(entityName);
|
|
194
203
|
const meta2 = this.processDiscriminatorColumn(meta, data);
|
|
@@ -200,7 +209,7 @@ export class EntityFactory {
|
|
|
200
209
|
createEntity(data, meta, options) {
|
|
201
210
|
const schema = this.driver.getSchemaName(meta, options);
|
|
202
211
|
if (options.newEntity || meta.forceConstructor || meta.virtual) {
|
|
203
|
-
if (
|
|
212
|
+
if (meta.polymorphs) {
|
|
204
213
|
throw new Error(`Cannot create entity ${meta.className}, class prototype is unknown`);
|
|
205
214
|
}
|
|
206
215
|
const params = this.extractConstructorParams(meta, data, options);
|
|
@@ -263,22 +272,31 @@ export class EntityFactory {
|
|
|
263
272
|
findEntity(data, meta, options) {
|
|
264
273
|
const schema = this.driver.getSchemaName(meta, options);
|
|
265
274
|
if (meta.simplePK) {
|
|
266
|
-
return this.unitOfWork.getById(meta.
|
|
275
|
+
return this.unitOfWork.getById(meta.class, data[meta.primaryKeys[0]], schema);
|
|
267
276
|
}
|
|
268
277
|
if (!Array.isArray(data) && meta.primaryKeys.some(pk => data[pk] == null)) {
|
|
269
278
|
return undefined;
|
|
270
279
|
}
|
|
271
280
|
const pks = Utils.getOrderedPrimaryKeys(data, meta, this.platform, options.convertCustomTypes);
|
|
272
|
-
return this.unitOfWork.getById(meta.
|
|
281
|
+
return this.unitOfWork.getById(meta.class, pks, schema);
|
|
273
282
|
}
|
|
274
283
|
processDiscriminatorColumn(meta, data) {
|
|
275
|
-
|
|
284
|
+
// Handle STI discriminator (persisted column)
|
|
285
|
+
if (meta.root.inheritanceType === 'sti') {
|
|
286
|
+
const prop = meta.properties[meta.root.discriminatorColumn];
|
|
287
|
+
const value = data[prop.name];
|
|
288
|
+
const type = meta.root.discriminatorMap[value];
|
|
289
|
+
meta = type ? this.metadata.get(type) : meta;
|
|
276
290
|
return meta;
|
|
277
291
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
292
|
+
// Handle TPT discriminator (computed at query time)
|
|
293
|
+
if (meta.root.inheritanceType === 'tpt' && meta.root.discriminatorMap) {
|
|
294
|
+
const value = data[meta.root.tptDiscriminatorColumn];
|
|
295
|
+
if (value) {
|
|
296
|
+
const type = meta.root.discriminatorMap[value];
|
|
297
|
+
meta = type ? this.metadata.get(type) : meta;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
282
300
|
return meta;
|
|
283
301
|
}
|
|
284
302
|
/**
|
|
@@ -307,7 +325,7 @@ export class EntityFactory {
|
|
|
307
325
|
const value = data[k];
|
|
308
326
|
if (prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && value) {
|
|
309
327
|
const pk = Reference.unwrapReference(value);
|
|
310
|
-
const entity = this.unitOfWork.getById(prop.
|
|
328
|
+
const entity = this.unitOfWork.getById(prop.targetMeta.class, pk, options.schema, true);
|
|
311
329
|
if (entity) {
|
|
312
330
|
return entity;
|
|
313
331
|
}
|
|
@@ -316,10 +334,10 @@ export class EntityFactory {
|
|
|
316
334
|
}
|
|
317
335
|
const nakedPk = Utils.extractPK(value, prop.targetMeta, true);
|
|
318
336
|
if (Utils.isObject(value) && !nakedPk) {
|
|
319
|
-
return this.create(prop.
|
|
337
|
+
return this.create(prop.targetMeta.class, value, options);
|
|
320
338
|
}
|
|
321
339
|
const { newEntity, initialized, ...rest } = options;
|
|
322
|
-
const target = this.createReference(prop.
|
|
340
|
+
const target = this.createReference(prop.targetMeta.class, nakedPk, rest);
|
|
323
341
|
return Reference.wrapReference(target, prop);
|
|
324
342
|
}
|
|
325
343
|
if (prop?.kind === ReferenceKind.EMBEDDED && value) {
|
|
@@ -327,7 +345,7 @@ export class EntityFactory {
|
|
|
327
345
|
if (Utils.isEntity(value)) {
|
|
328
346
|
return value;
|
|
329
347
|
}
|
|
330
|
-
return this.createEmbeddable(prop.
|
|
348
|
+
return this.createEmbeddable(prop.targetMeta.class, value, options);
|
|
331
349
|
}
|
|
332
350
|
if (!prop) {
|
|
333
351
|
const tmp = { ...data };
|
|
@@ -335,8 +353,8 @@ export class EntityFactory {
|
|
|
335
353
|
if (!options.convertCustomTypes || !prop.customType || tmp[prop.name] == null) {
|
|
336
354
|
continue;
|
|
337
355
|
}
|
|
338
|
-
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils.isPlainObject(tmp[prop.name]) && !Utils.extractPK(tmp[prop.name],
|
|
339
|
-
tmp[prop.name] = Reference.wrapReference(this.create(
|
|
356
|
+
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && Utils.isPlainObject(tmp[prop.name]) && !Utils.extractPK(tmp[prop.name], prop.targetMeta, true)) {
|
|
357
|
+
tmp[prop.name] = Reference.wrapReference(this.create(prop.targetMeta.class, tmp[prop.name], options), prop);
|
|
340
358
|
}
|
|
341
359
|
else if (prop.kind === ReferenceKind.SCALAR) {
|
|
342
360
|
tmp[prop.name] = prop.customType.convertToJSValue(tmp[prop.name], this.platform);
|
package/entity/EntityHelper.d.ts
CHANGED
|
@@ -6,9 +6,9 @@ import { type EntityMetadata, type EntityProperty, type IHydrator } from '../typ
|
|
|
6
6
|
export declare class EntityHelper {
|
|
7
7
|
static decorate<T extends object>(meta: EntityMetadata<T>, em: EntityManager): void;
|
|
8
8
|
/**
|
|
9
|
-
* As a performance optimization, we create entity state methods
|
|
9
|
+
* As a performance optimization, we create entity state methods lazily. We first add
|
|
10
10
|
* the `null` value to the prototype to reserve space in memory. Then we define a setter on the
|
|
11
|
-
* prototype
|
|
11
|
+
* prototype that will be executed exactly once per entity instance. There we redefine the given
|
|
12
12
|
* property on the entity instance, so shadowing the prototype setter.
|
|
13
13
|
*/
|
|
14
14
|
private static defineBaseProperties;
|
package/entity/EntityHelper.js
CHANGED
|
@@ -6,6 +6,7 @@ import { WrappedEntity } from './WrappedEntity.js';
|
|
|
6
6
|
import { ReferenceKind } from '../enums.js';
|
|
7
7
|
import { helper } from './wrap.js';
|
|
8
8
|
import { inspect } from '../logging/inspect.js';
|
|
9
|
+
import { getEnv } from '../utils/env-vars.js';
|
|
9
10
|
/**
|
|
10
11
|
* @internal
|
|
11
12
|
*/
|
|
@@ -32,14 +33,19 @@ export class EntityHelper {
|
|
|
32
33
|
const prototype = meta.prototype;
|
|
33
34
|
if (!prototype.toJSON) { // toJSON can be overridden
|
|
34
35
|
prototype.toJSON = function (...args) {
|
|
36
|
+
// Guard against being called on the prototype itself (e.g. by serializers
|
|
37
|
+
// walking the object graph and calling toJSON on prototype objects)
|
|
38
|
+
if (this === prototype) {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
35
41
|
return EntityTransformer.toObject(this, ...args);
|
|
36
42
|
};
|
|
37
43
|
}
|
|
38
44
|
}
|
|
39
45
|
/**
|
|
40
|
-
* As a performance optimization, we create entity state methods
|
|
46
|
+
* As a performance optimization, we create entity state methods lazily. We first add
|
|
41
47
|
* the `null` value to the prototype to reserve space in memory. Then we define a setter on the
|
|
42
|
-
* prototype
|
|
48
|
+
* prototype that will be executed exactly once per entity instance. There we redefine the given
|
|
43
49
|
* property on the entity instance, so shadowing the prototype setter.
|
|
44
50
|
*/
|
|
45
51
|
static defineBaseProperties(meta, prototype, em) {
|
|
@@ -130,7 +136,7 @@ export class EntityHelper {
|
|
|
130
136
|
.forEach(prop => delete object[prop.name]);
|
|
131
137
|
const ret = inspect(object, { depth });
|
|
132
138
|
let name = this.constructor.name;
|
|
133
|
-
const showEM = ['true', 't', '1'].includes(
|
|
139
|
+
const showEM = ['true', 't', '1'].includes(getEnv('MIKRO_ORM_LOG_EM_ID')?.toLowerCase() ?? '');
|
|
134
140
|
if (showEM) {
|
|
135
141
|
if (helper(this).__em) {
|
|
136
142
|
name += ` [managed by ${helper(this).__em.id}]`;
|
|
@@ -170,7 +176,19 @@ export class EntityHelper {
|
|
|
170
176
|
});
|
|
171
177
|
}
|
|
172
178
|
static propagate(meta, entity, owner, prop, value, old) {
|
|
173
|
-
|
|
179
|
+
// For polymorphic relations, get bidirectional relations from the actual entity's metadata
|
|
180
|
+
let bidirectionalRelations;
|
|
181
|
+
if (prop.polymorphic && prop.polymorphTargets?.length) {
|
|
182
|
+
// For polymorphic relations, we need to get the bidirectional relations from the actual value's metadata
|
|
183
|
+
if (!value) {
|
|
184
|
+
return; // No value means no propagation needed
|
|
185
|
+
}
|
|
186
|
+
bidirectionalRelations = helper(value).__meta.bidirectionalRelations;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
bidirectionalRelations = prop.targetMeta.bidirectionalRelations;
|
|
190
|
+
}
|
|
191
|
+
for (const prop2 of bidirectionalRelations) {
|
|
174
192
|
if ((prop2.inversedBy || prop2.mappedBy) !== prop.name) {
|
|
175
193
|
continue;
|
|
176
194
|
}
|
|
@@ -212,6 +230,11 @@ export class EntityHelper {
|
|
|
212
230
|
helper(other).__em?.getUnitOfWork().scheduleOrphanRemoval(other);
|
|
213
231
|
}
|
|
214
232
|
}
|
|
233
|
+
// Skip setting the inverse side to null if it's a primary key - the entity will be removed via orphan removal
|
|
234
|
+
// Setting a primary key to null would corrupt the entity and cause validation errors
|
|
235
|
+
if (value == null && prop.orphanRemoval && prop2.primary) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
215
238
|
if (value == null) {
|
|
216
239
|
entity[prop2.name] = value;
|
|
217
240
|
}
|
package/entity/EntityLoader.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnyEntity, ConnectionType, EntityProperty, FilterQuery, PopulateOptions } from '../typings.js';
|
|
1
|
+
import type { AnyEntity, ConnectionType, EntityName, 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, FilterOptions } from '../drivers/IDatabaseDriver.js';
|
|
@@ -15,7 +15,7 @@ export type EntityLoaderOptions<Entity, Fields extends string = PopulatePath.ALL
|
|
|
15
15
|
convertCustomTypes?: boolean;
|
|
16
16
|
ignoreLazyScalarProperties?: boolean;
|
|
17
17
|
filters?: FilterOptions;
|
|
18
|
-
strategy?: LoadStrategy
|
|
18
|
+
strategy?: LoadStrategy | `${LoadStrategy}`;
|
|
19
19
|
lockMode?: Exclude<LockMode, LockMode.OPTIMISTIC>;
|
|
20
20
|
schema?: string;
|
|
21
21
|
connectionType?: ConnectionType;
|
|
@@ -30,8 +30,8 @@ export declare class EntityLoader {
|
|
|
30
30
|
* Loads specified relations in batch.
|
|
31
31
|
* This will execute one query for each relation, that will populate it on all the specified entities.
|
|
32
32
|
*/
|
|
33
|
-
populate<Entity extends object, Fields extends string = PopulatePath.ALL>(entityName:
|
|
34
|
-
normalizePopulate<Entity>(entityName:
|
|
33
|
+
populate<Entity extends object, Fields extends string = PopulatePath.ALL>(entityName: EntityName<Entity>, entities: Entity[], populate: PopulateOptions<Entity>[] | boolean, options: EntityLoaderOptions<Entity, Fields>): Promise<void>;
|
|
34
|
+
normalizePopulate<Entity>(entityName: EntityName<Entity>, populate: (PopulateOptions<Entity> | boolean)[] | PopulateOptions<Entity> | boolean, strategy?: LoadStrategy, lookup?: boolean, exclude?: string[]): PopulateOptions<Entity>[];
|
|
35
35
|
private setSerializationContext;
|
|
36
36
|
/**
|
|
37
37
|
* Merge multiple populates for the same entity with different children. Also skips `*` fields, those can come from
|
|
@@ -43,6 +43,7 @@ export declare class EntityLoader {
|
|
|
43
43
|
*/
|
|
44
44
|
private populateMany;
|
|
45
45
|
private populateScalar;
|
|
46
|
+
private populatePolymorphic;
|
|
46
47
|
private initializeCollections;
|
|
47
48
|
private initializeOneToMany;
|
|
48
49
|
private initializeManyToMany;
|