@mikro-orm/core 7.0.0-rc.1 → 7.0.0-rc.3
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 +3 -2
- package/EntityManager.js +107 -43
- package/MikroORM.js +4 -4
- package/cache/FileCacheAdapter.js +1 -3
- package/connections/Connection.js +16 -3
- package/drivers/DatabaseDriver.js +26 -8
- package/drivers/IDatabaseDriver.d.ts +49 -1
- package/entity/BaseEntity.d.ts +3 -3
- package/entity/Collection.js +43 -17
- package/entity/EntityAssigner.js +23 -11
- package/entity/EntityFactory.js +36 -13
- package/entity/EntityHelper.js +27 -18
- package/entity/EntityLoader.d.ts +6 -6
- package/entity/EntityLoader.js +55 -22
- package/entity/EntityRepository.d.ts +1 -1
- package/entity/Reference.d.ts +1 -1
- package/entity/Reference.js +37 -8
- package/entity/WrappedEntity.d.ts +3 -3
- package/entity/WrappedEntity.js +5 -1
- package/entity/defineEntity.d.ts +202 -58
- package/entity/defineEntity.js +20 -24
- package/entity/utils.js +28 -26
- package/entity/validators.js +2 -1
- package/enums.js +12 -17
- package/errors.js +18 -8
- package/events/EventManager.js +1 -1
- package/exceptions.js +7 -2
- package/hydration/ObjectHydrator.js +27 -13
- package/index.d.ts +2 -2
- package/index.js +1 -1
- package/logging/DefaultLogger.js +3 -5
- package/logging/colors.js +3 -6
- package/metadata/EntitySchema.d.ts +2 -2
- package/metadata/EntitySchema.js +12 -2
- package/metadata/MetadataDiscovery.js +107 -48
- package/metadata/MetadataProvider.js +26 -1
- package/metadata/MetadataStorage.js +2 -4
- package/metadata/MetadataValidator.js +20 -5
- package/metadata/types.d.ts +2 -2
- package/naming-strategy/AbstractNamingStrategy.js +5 -2
- package/not-supported.js +5 -1
- package/package.json +40 -37
- package/platforms/Platform.d.ts +4 -1
- package/platforms/Platform.js +50 -24
- package/serialization/EntitySerializer.d.ts +3 -3
- package/serialization/EntitySerializer.js +7 -3
- package/serialization/EntityTransformer.js +6 -4
- package/serialization/SerializationContext.js +1 -1
- package/typings.d.ts +73 -33
- package/typings.js +11 -9
- package/unit-of-work/ChangeSet.js +4 -4
- package/unit-of-work/ChangeSetComputer.js +8 -6
- package/unit-of-work/ChangeSetPersister.js +15 -10
- package/unit-of-work/CommitOrderCalculator.js +4 -2
- package/unit-of-work/UnitOfWork.d.ts +7 -1
- package/unit-of-work/UnitOfWork.js +51 -22
- package/utils/AbstractMigrator.d.ts +101 -0
- package/utils/AbstractMigrator.js +303 -0
- package/utils/AbstractSchemaGenerator.js +2 -1
- package/utils/AsyncContext.js +1 -1
- package/utils/Configuration.d.ts +3 -1
- package/utils/Configuration.js +8 -4
- package/utils/Cursor.js +4 -2
- package/utils/DataloaderUtils.js +15 -12
- package/utils/EntityComparator.js +51 -43
- package/utils/QueryHelper.js +38 -26
- package/utils/RawQueryFragment.js +3 -2
- package/utils/TransactionManager.js +2 -1
- package/utils/Utils.d.ts +1 -1
- package/utils/Utils.js +36 -30
- package/utils/env-vars.js +6 -5
- package/utils/fs-utils.d.ts +1 -0
- package/utils/fs-utils.js +6 -5
- package/utils/index.d.ts +0 -2
- package/utils/index.js +0 -2
- package/utils/upsert-utils.js +6 -3
package/entity/BaseEntity.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Ref } from './Reference.js';
|
|
2
|
-
import type { AutoPath, EntityData, EntityDTO, Loaded, LoadedReference, AddEager, EntityKey, FromEntityType, IsSubset, MergeSelected } from '../typings.js';
|
|
2
|
+
import type { AutoPath, EntityData, EntityDTO, Loaded, LoadedReference, AddEager, EntityKey, FromEntityType, IsSubset, MergeSelected, SerializeDTO } from '../typings.js';
|
|
3
3
|
import { type AssignOptions } from './EntityAssigner.js';
|
|
4
4
|
import type { EntityLoaderOptions } from './EntityLoader.js';
|
|
5
5
|
import { type SerializeOptions } from '../serialization/EntitySerializer.js';
|
|
@@ -8,7 +8,7 @@ import type { PopulatePath } from '../enums.js';
|
|
|
8
8
|
export declare abstract class BaseEntity {
|
|
9
9
|
isInitialized(): boolean;
|
|
10
10
|
populated(populated?: boolean): void;
|
|
11
|
-
populate<Entity extends this = this, Hint extends string = never>(populate: AutoPath<Entity, Hint, PopulatePath.ALL>[] | false, options?: EntityLoaderOptions<Entity>): Promise<Loaded<Entity, Hint>>;
|
|
11
|
+
populate<Entity extends this = this, Hint extends string = never, Fields extends string = never>(populate: AutoPath<Entity, Hint, PopulatePath.ALL>[] | false, options?: EntityLoaderOptions<Entity, Fields>): Promise<Loaded<Entity, Hint>>;
|
|
12
12
|
toReference<Entity extends this = this>(): Ref<Entity> & LoadedReference<Loaded<Entity, AddEager<Entity>>>;
|
|
13
13
|
/**
|
|
14
14
|
* Converts the entity to a plain object representation.
|
|
@@ -73,7 +73,7 @@ export declare abstract class BaseEntity {
|
|
|
73
73
|
*/
|
|
74
74
|
toObject<Entity extends this = this, Ignored extends EntityKey<Entity> = never>(ignoreFields: Ignored[]): Omit<EntityDTO<Entity>, Ignored>;
|
|
75
75
|
toPOJO<Entity extends this = this>(): EntityDTO<Entity>;
|
|
76
|
-
serialize<Entity extends this = this, Naked extends FromEntityType<Entity> = FromEntityType<Entity>, Hint extends string = never, Exclude extends string = never>(options?: SerializeOptions<Naked, Hint, Exclude>):
|
|
76
|
+
serialize<Entity extends this = this, Naked extends FromEntityType<Entity> = FromEntityType<Entity>, Hint extends string = never, Exclude extends string = never>(options?: SerializeOptions<Naked, Hint, Exclude>): SerializeDTO<Naked, Hint, Exclude>;
|
|
77
77
|
assign<Entity extends this, Naked extends FromEntityType<Entity> = FromEntityType<Entity>, Convert extends boolean = false, Data extends EntityData<Naked, Convert> | Partial<EntityDTO<Naked>> = EntityData<Naked, Convert> | Partial<EntityDTO<Naked>>>(data: Data & IsSubset<EntityData<Naked>, Data>, options?: AssignOptions<Convert>): MergeSelected<Entity, Naked, keyof Data & string>;
|
|
78
78
|
init<Entity extends this = this, Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(options?: FindOneOptions<Entity, Hint, Fields, Excludes>): Promise<Loaded<Entity, Hint, Fields, Excludes> | null>;
|
|
79
79
|
getSchema(): string | undefined;
|
package/entity/Collection.js
CHANGED
|
@@ -22,7 +22,7 @@ export class Collection {
|
|
|
22
22
|
if (items) {
|
|
23
23
|
let i = 0;
|
|
24
24
|
this.items = new Set(items);
|
|
25
|
-
this.items.forEach(item => this[i++] = item);
|
|
25
|
+
this.items.forEach(item => (this[i++] = item));
|
|
26
26
|
}
|
|
27
27
|
this.initialized = !!items || initialized;
|
|
28
28
|
}
|
|
@@ -58,7 +58,7 @@ export class Collection {
|
|
|
58
58
|
helper(this.owner).setSerializationContext({
|
|
59
59
|
populate: Array.isArray(options.populate)
|
|
60
60
|
? options.populate.map(hint => `${this.property.name}.${hint}`)
|
|
61
|
-
: options.populate ?? [this.property.name],
|
|
61
|
+
: (options.populate ?? [this.property.name]),
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
@@ -79,8 +79,10 @@ export class Collection {
|
|
|
79
79
|
return this._count;
|
|
80
80
|
}
|
|
81
81
|
const em = this.getEntityManager();
|
|
82
|
-
if (!em.getPlatform().usesPivotTable() &&
|
|
83
|
-
|
|
82
|
+
if (!em.getPlatform().usesPivotTable() &&
|
|
83
|
+
this.property.kind === ReferenceKind.MANY_TO_MANY &&
|
|
84
|
+
this.property.owner) {
|
|
85
|
+
return (this._count = this.length);
|
|
84
86
|
}
|
|
85
87
|
const cond = this.createLoadCountCondition(where ?? {});
|
|
86
88
|
const count = await em.count(this.property.targetMeta.class, cond, countOptions);
|
|
@@ -96,16 +98,18 @@ export class Collection {
|
|
|
96
98
|
if (this.property.kind === ReferenceKind.MANY_TO_MANY && em.getPlatform().usesPivotTable()) {
|
|
97
99
|
// M:N via pivot table bypasses em.find(), so merge all 3 levels here
|
|
98
100
|
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');
|
|
101
|
-
const map = await em
|
|
101
|
+
options.populate = (await em.preparePopulate(this.property.targetMeta.class, options));
|
|
102
|
+
const cond = (await em.applyFilters(this.property.targetMeta.class, where, options.filters ?? {}, 'read'));
|
|
103
|
+
const map = await em
|
|
104
|
+
.getDriver()
|
|
105
|
+
.loadFromPivotTable(this.property, [helper(this.owner).__primaryKeys], cond, opts.orderBy, ctx, options);
|
|
102
106
|
items = map[helper(this.owner).getSerializedPrimaryKey()].map((item) => em.merge(this.property.targetMeta.class, item, { convertCustomTypes: true }));
|
|
103
107
|
await em.populate(items, options.populate, options);
|
|
104
108
|
}
|
|
105
109
|
else {
|
|
106
110
|
// em.find() merges entity-level orderBy, so only merge runtime + relation here
|
|
107
111
|
opts.orderBy = QueryHelper.mergeOrderBy(opts.orderBy, this.property.orderBy);
|
|
108
|
-
items = await em.find(this.property.targetMeta.class, this.createCondition(where), opts);
|
|
112
|
+
items = (await em.find(this.property.targetMeta.class, this.createCondition(where), opts));
|
|
109
113
|
}
|
|
110
114
|
if (options.store) {
|
|
111
115
|
this.hydrate(items, true);
|
|
@@ -262,11 +266,9 @@ export class Collection {
|
|
|
262
266
|
return this;
|
|
263
267
|
}
|
|
264
268
|
const populate = Array.isArray(options.populate)
|
|
265
|
-
? options.populate.map(f => f === '*' ? f : `${this.property.name}.${f}`)
|
|
269
|
+
? options.populate.map(f => (f === '*' ? f : `${this.property.name}.${f}`))
|
|
266
270
|
: [`${this.property.name}${options.ref ? ':ref' : ''}`];
|
|
267
|
-
const schema = this.property.targetMeta.schema === '*'
|
|
268
|
-
? helper(this.owner).__schema
|
|
269
|
-
: undefined;
|
|
271
|
+
const schema = this.property.targetMeta.schema === '*' ? helper(this.owner).__schema : undefined;
|
|
270
272
|
await em.populate(this.owner, populate, {
|
|
271
273
|
refresh: true,
|
|
272
274
|
...options,
|
|
@@ -297,7 +299,8 @@ export class Collection {
|
|
|
297
299
|
if (this.property.kind === ReferenceKind.ONE_TO_MANY) {
|
|
298
300
|
cond[this.property.mappedBy] = helper(this.owner).getPrimaryKey();
|
|
299
301
|
}
|
|
300
|
-
else {
|
|
302
|
+
else {
|
|
303
|
+
// MANY_TO_MANY
|
|
301
304
|
this.createManyToManyCondition(cond);
|
|
302
305
|
}
|
|
303
306
|
return cond;
|
|
@@ -386,7 +389,9 @@ export class Collection {
|
|
|
386
389
|
if (items.length === 0) {
|
|
387
390
|
return [];
|
|
388
391
|
}
|
|
389
|
-
field ??= targetMeta.compositePK
|
|
392
|
+
field ??= targetMeta.compositePK
|
|
393
|
+
? targetMeta.primaryKeys
|
|
394
|
+
: (targetMeta.serializedPrimaryKey ?? targetMeta.primaryKeys[0]);
|
|
390
395
|
const cb = (i, f) => {
|
|
391
396
|
if (Utils.isEntity(i[f], true)) {
|
|
392
397
|
return wrap(i[f], true).getPrimaryKey();
|
|
@@ -616,6 +621,7 @@ export class Collection {
|
|
|
616
621
|
* @internal
|
|
617
622
|
*/
|
|
618
623
|
get property() {
|
|
624
|
+
// cannot be typed to `EntityProperty<O, T>` as it causes issues in assignability of `Loaded` type
|
|
619
625
|
if (!this._property) {
|
|
620
626
|
const meta = wrap(this.owner, true).__meta;
|
|
621
627
|
/* v8 ignore next */
|
|
@@ -630,6 +636,7 @@ export class Collection {
|
|
|
630
636
|
* @internal
|
|
631
637
|
*/
|
|
632
638
|
set property(prop) {
|
|
639
|
+
// cannot be typed to `EntityProperty<O, T>` as it causes issues in assignability of `Loaded` type
|
|
633
640
|
this._property = prop;
|
|
634
641
|
}
|
|
635
642
|
propagate(item, method) {
|
|
@@ -696,7 +703,18 @@ export class Collection {
|
|
|
696
703
|
/** @ignore */
|
|
697
704
|
[Symbol.for('nodejs.util.inspect.custom')](depth = 2) {
|
|
698
705
|
const object = { ...this };
|
|
699
|
-
const hidden = [
|
|
706
|
+
const hidden = [
|
|
707
|
+
'items',
|
|
708
|
+
'owner',
|
|
709
|
+
'_property',
|
|
710
|
+
'_count',
|
|
711
|
+
'snapshot',
|
|
712
|
+
'_populated',
|
|
713
|
+
'_lazyInitialized',
|
|
714
|
+
'_em',
|
|
715
|
+
'readonly',
|
|
716
|
+
'partial',
|
|
717
|
+
];
|
|
700
718
|
hidden.forEach(k => delete object[k]);
|
|
701
719
|
const ret = inspect(object, { depth });
|
|
702
720
|
const name = `${this.constructor.name}<${this.property?.type ?? 'unknown'}>`;
|
|
@@ -704,7 +722,15 @@ export class Collection {
|
|
|
704
722
|
}
|
|
705
723
|
}
|
|
706
724
|
Object.defineProperties(Collection.prototype, {
|
|
707
|
-
$: {
|
|
708
|
-
|
|
725
|
+
$: {
|
|
726
|
+
get() {
|
|
727
|
+
return this;
|
|
728
|
+
},
|
|
729
|
+
},
|
|
730
|
+
get: {
|
|
731
|
+
get() {
|
|
732
|
+
return () => this;
|
|
733
|
+
},
|
|
734
|
+
},
|
|
709
735
|
__collection: { value: true, enumerable: false, writable: false },
|
|
710
736
|
});
|
package/entity/EntityAssigner.js
CHANGED
|
@@ -71,7 +71,10 @@ export class EntityAssigner {
|
|
|
71
71
|
value = customType.convertToJSValue(value, options.platform);
|
|
72
72
|
}
|
|
73
73
|
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop?.kind) && value != null) {
|
|
74
|
-
if (options.updateNestedEntities &&
|
|
74
|
+
if (options.updateNestedEntities &&
|
|
75
|
+
Object.hasOwn(entity, propName) &&
|
|
76
|
+
Utils.isEntity(entity[propName], true) &&
|
|
77
|
+
Utils.isPlainObject(value)) {
|
|
75
78
|
const unwrappedEntity = Reference.unwrapReference(entity[propName]);
|
|
76
79
|
const wrapped = helper(unwrappedEntity);
|
|
77
80
|
if (options.updateByPrimaryKey) {
|
|
@@ -95,12 +98,14 @@ export class EntityAssigner {
|
|
|
95
98
|
}
|
|
96
99
|
if (prop.kind === ReferenceKind.SCALAR && SCALAR_TYPES.has(prop.runtimeType) && (prop.setter || !prop.getter)) {
|
|
97
100
|
validateProperty(prop, value, entity);
|
|
98
|
-
return entity[prop.name] = value;
|
|
101
|
+
return (entity[prop.name] = value);
|
|
99
102
|
}
|
|
100
103
|
if (prop.kind === ReferenceKind.EMBEDDED && EntityAssigner.validateEM(options.em)) {
|
|
101
104
|
return EntityAssigner.assignEmbeddable(entity, value, prop, options.em, options);
|
|
102
105
|
}
|
|
103
|
-
if (options.mergeObjectProperties &&
|
|
106
|
+
if (options.mergeObjectProperties &&
|
|
107
|
+
Utils.isPlainObject(entity[propName]) &&
|
|
108
|
+
Utils.isPlainObject(value)) {
|
|
104
109
|
entity[propName] ??= {};
|
|
105
110
|
entity[propName] = Utils.merge({}, entity[propName], value);
|
|
106
111
|
}
|
|
@@ -141,7 +146,9 @@ export class EntityAssigner {
|
|
|
141
146
|
entity[prop.name] = Reference.wrapReference(value, prop);
|
|
142
147
|
}
|
|
143
148
|
else if (Utils.isPrimaryKey(value, true) && EntityAssigner.validateEM(em)) {
|
|
144
|
-
entity[prop.name] = prop.mapToPk
|
|
149
|
+
entity[prop.name] = prop.mapToPk
|
|
150
|
+
? value
|
|
151
|
+
: Reference.wrapReference(em.getReference(prop.targetMeta.class, value, options), prop);
|
|
145
152
|
}
|
|
146
153
|
else if (Utils.isPlainObject(value) && options.merge && EntityAssigner.validateEM(em)) {
|
|
147
154
|
entity[prop.name] = Reference.wrapReference(em.merge(prop.targetMeta.class, value, options), prop);
|
|
@@ -174,7 +181,10 @@ export class EntityAssigner {
|
|
|
174
181
|
return this.createCollectionItem(item, em, prop, invalid, options);
|
|
175
182
|
}
|
|
176
183
|
/* v8 ignore next */
|
|
177
|
-
if (options.updateNestedEntities &&
|
|
184
|
+
if (options.updateNestedEntities &&
|
|
185
|
+
!options.updateByPrimaryKey &&
|
|
186
|
+
collection[idx] &&
|
|
187
|
+
helper(collection[idx])?.isInitialized()) {
|
|
178
188
|
return EntityAssigner.assign(collection[idx], item, options);
|
|
179
189
|
}
|
|
180
190
|
return this.createCollectionItem(item, em, prop, invalid, options);
|
|
@@ -186,7 +196,8 @@ export class EntityAssigner {
|
|
|
186
196
|
if (Array.isArray(value)) {
|
|
187
197
|
collection.set(items);
|
|
188
198
|
}
|
|
189
|
-
else {
|
|
199
|
+
else {
|
|
200
|
+
// append to the collection in case of assigning a single value instead of array
|
|
190
201
|
collection.add(items);
|
|
191
202
|
}
|
|
192
203
|
}
|
|
@@ -207,11 +218,12 @@ export class EntityAssigner {
|
|
|
207
218
|
entity[propName].push(...Object.values(tmp));
|
|
208
219
|
});
|
|
209
220
|
}
|
|
210
|
-
const create = () => EntityAssigner.validateEM(em) &&
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
221
|
+
const create = () => EntityAssigner.validateEM(em) &&
|
|
222
|
+
em.getEntityFactory().createEmbeddable(prop.targetMeta.class, value, {
|
|
223
|
+
convertCustomTypes: options.convertCustomTypes,
|
|
224
|
+
newEntity: options.mergeEmbeddedProperties ? !('propName' in entity) : true,
|
|
225
|
+
});
|
|
226
|
+
entity[propName] = (options.mergeEmbeddedProperties ? entity[propName] || create() : create());
|
|
215
227
|
Object.keys(value).forEach(key => {
|
|
216
228
|
EntityAssigner.assignProperty(entity[propName], key, prop.embeddedProps, value, options);
|
|
217
229
|
});
|
package/entity/EntityFactory.js
CHANGED
|
@@ -70,11 +70,15 @@ export class EntityFactory {
|
|
|
70
70
|
if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
|
|
71
71
|
continue;
|
|
72
72
|
}
|
|
73
|
-
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
|
|
73
|
+
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
|
|
74
|
+
Utils.isPlainObject(data[prop.name])) {
|
|
74
75
|
data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
|
|
75
76
|
}
|
|
76
77
|
if (prop.customType instanceof JsonType && this.platform.convertsJsonAutomatically()) {
|
|
77
|
-
data[prop.name] = prop.customType.convertToDatabaseValue(data[prop.name], this.platform, {
|
|
78
|
+
data[prop.name] = prop.customType.convertToDatabaseValue(data[prop.name], this.platform, {
|
|
79
|
+
key: prop.name,
|
|
80
|
+
mode: 'hydration',
|
|
81
|
+
});
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
84
|
}
|
|
@@ -111,33 +115,46 @@ export class EntityFactory {
|
|
|
111
115
|
const originalEntityData = helper(entity).__originalEntityData ?? {};
|
|
112
116
|
const diff = this.comparator.diffEntities(meta.class, originalEntityData, existsData);
|
|
113
117
|
// version properties are not part of entity snapshots
|
|
114
|
-
if (meta.versionProperty &&
|
|
118
|
+
if (meta.versionProperty &&
|
|
119
|
+
data[meta.versionProperty] &&
|
|
120
|
+
data[meta.versionProperty] !== originalEntityData[meta.versionProperty]) {
|
|
115
121
|
diff[meta.versionProperty] = data[meta.versionProperty];
|
|
116
122
|
}
|
|
117
123
|
const diff2 = this.comparator.diffEntities(meta.class, existsData, data, { includeInverseSides: true });
|
|
118
124
|
// do not override values changed by user
|
|
119
125
|
Utils.keys(diff).forEach(key => delete diff2[key]);
|
|
120
|
-
Utils.keys(diff2)
|
|
126
|
+
Utils.keys(diff2)
|
|
127
|
+
.filter(key => {
|
|
121
128
|
// ignore null values if there is already present non-null value
|
|
122
129
|
if (existsData[key] != null) {
|
|
123
130
|
return diff2[key] == null;
|
|
124
131
|
}
|
|
125
132
|
return diff2[key] === undefined;
|
|
126
|
-
})
|
|
133
|
+
})
|
|
134
|
+
.forEach(key => delete diff2[key]);
|
|
127
135
|
// but always add collection properties and formulas if they are part of the `data`
|
|
128
136
|
Utils.keys(data)
|
|
129
|
-
.filter(key => meta.properties[key]?.formula ||
|
|
130
|
-
.
|
|
137
|
+
.filter(key => meta.properties[key]?.formula ||
|
|
138
|
+
[ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(meta.properties[key]?.kind))
|
|
139
|
+
.forEach(key => (diff2[key] = data[key]));
|
|
131
140
|
// rehydrated with the new values, skip those changed by user
|
|
132
|
-
|
|
141
|
+
// use full hydration if the entity is already initialized, even if the caller used `initialized: false`
|
|
142
|
+
// (e.g. from createReference), otherwise scalar properties in diff2 won't be applied
|
|
143
|
+
const initialized = options.initialized || helper(entity).__initialized;
|
|
144
|
+
this.hydrate(entity, meta, diff2, initialized ? { ...options, initialized } : options);
|
|
133
145
|
// we need to update the entity data only with keys that were not present before
|
|
134
146
|
const nullVal = this.config.get('forceUndefined') ? undefined : null;
|
|
135
147
|
Utils.keys(diff2).forEach(key => {
|
|
136
148
|
const prop = meta.properties[key];
|
|
137
|
-
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
|
|
149
|
+
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
|
|
150
|
+
Utils.isPlainObject(data[prop.name])) {
|
|
151
|
+
// oxfmt-ignore
|
|
138
152
|
diff2[key] = entity[prop.name] ? helper(entity[prop.name]).getPrimaryKey(options.convertCustomTypes) : null;
|
|
139
153
|
}
|
|
140
|
-
if (
|
|
154
|
+
if (!options.convertCustomTypes &&
|
|
155
|
+
[ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE, ReferenceKind.SCALAR].includes(prop.kind) &&
|
|
156
|
+
prop.customType?.ensureComparable(meta, prop) &&
|
|
157
|
+
diff2[key] != null) {
|
|
141
158
|
const converted = prop.customType.convertToJSValue(diff2[key], this.platform, { force: true });
|
|
142
159
|
diff2[key] = prop.customType.convertToDatabaseValue(converted, this.platform, { fromQuery: true });
|
|
143
160
|
}
|
|
@@ -146,7 +163,8 @@ export class EntityFactory {
|
|
|
146
163
|
});
|
|
147
164
|
// in case of joined loading strategy, we need to cascade the merging to possibly loaded relations manually
|
|
148
165
|
meta.relations.forEach(prop => {
|
|
149
|
-
if ([ReferenceKind.MANY_TO_MANY, ReferenceKind.ONE_TO_MANY].includes(prop.kind) &&
|
|
166
|
+
if ([ReferenceKind.MANY_TO_MANY, ReferenceKind.ONE_TO_MANY].includes(prop.kind) &&
|
|
167
|
+
Array.isArray(data[prop.name])) {
|
|
150
168
|
// instead of trying to match the collection items (which could easily fail if the collection was loaded with different ordering),
|
|
151
169
|
// we just create the entity from scratch, which will automatically pick the right one from the identity map and call `mergeData` on it
|
|
152
170
|
data[prop.name]
|
|
@@ -154,7 +172,10 @@ export class EntityFactory {
|
|
|
154
172
|
.forEach(child => this.create(prop.targetMeta.class, child, options)); // we can ignore the value, we just care about the `mergeData` call
|
|
155
173
|
return;
|
|
156
174
|
}
|
|
157
|
-
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
|
|
175
|
+
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
|
|
176
|
+
Utils.isPlainObject(data[prop.name]) &&
|
|
177
|
+
entity[prop.name] &&
|
|
178
|
+
helper(entity[prop.name]).__initialized) {
|
|
158
179
|
this.create(prop.targetMeta.class, data[prop.name], options); // we can ignore the value, we just care about the `mergeData` call
|
|
159
180
|
}
|
|
160
181
|
});
|
|
@@ -353,7 +374,9 @@ export class EntityFactory {
|
|
|
353
374
|
if (!options.convertCustomTypes || !prop.customType || tmp[prop.name] == null) {
|
|
354
375
|
continue;
|
|
355
376
|
}
|
|
356
|
-
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
|
|
377
|
+
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
|
|
378
|
+
Utils.isPlainObject(tmp[prop.name]) &&
|
|
379
|
+
!Utils.extractPK(tmp[prop.name], prop.targetMeta, true)) {
|
|
357
380
|
tmp[prop.name] = Reference.wrapReference(this.create(prop.targetMeta.class, tmp[prop.name], options), prop);
|
|
358
381
|
}
|
|
359
382
|
else if (prop.kind === ReferenceKind.SCALAR) {
|
package/entity/EntityHelper.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EagerProps, EntityRepositoryType, HiddenProps, OptionalProps, PrimaryKeyProp, } from '../typings.js';
|
|
1
|
+
import { EagerProps, EntityName, EntityRepositoryType, HiddenProps, OptionalProps, PrimaryKeyProp, } from '../typings.js';
|
|
2
2
|
import { EntityTransformer } from '../serialization/EntityTransformer.js';
|
|
3
3
|
import { Reference } from './Reference.js';
|
|
4
4
|
import { Utils } from '../utils/Utils.js';
|
|
@@ -31,15 +31,21 @@ export class EntityHelper {
|
|
|
31
31
|
EntityHelper.defineProperties(meta, fork);
|
|
32
32
|
}
|
|
33
33
|
const prototype = meta.prototype;
|
|
34
|
-
if (!prototype.toJSON) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
if (!prototype.toJSON) {
|
|
35
|
+
// toJSON can be overridden
|
|
36
|
+
Object.defineProperty(prototype, 'toJSON', {
|
|
37
|
+
value: function (...args) {
|
|
38
|
+
// Guard against being called on the prototype itself (e.g. by serializers
|
|
39
|
+
// walking the object graph and calling toJSON on prototype objects)
|
|
40
|
+
if (this === prototype) {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
return EntityTransformer.toObject(this, ...args);
|
|
44
|
+
},
|
|
45
|
+
writable: true,
|
|
46
|
+
configurable: true,
|
|
47
|
+
enumerable: false,
|
|
48
|
+
});
|
|
43
49
|
}
|
|
44
50
|
}
|
|
45
51
|
/**
|
|
@@ -49,6 +55,7 @@ export class EntityHelper {
|
|
|
49
55
|
* property on the entity instance, so shadowing the prototype setter.
|
|
50
56
|
*/
|
|
51
57
|
static defineBaseProperties(meta, prototype, em) {
|
|
58
|
+
// oxfmt-ignore
|
|
52
59
|
const helperParams = meta.embeddable || meta.virtual ? [] : [em.getComparator().getPkGetter(meta), em.getComparator().getPkSerializer(meta), em.getComparator().getPkGetterConverted(meta)];
|
|
53
60
|
Object.defineProperties(prototype, {
|
|
54
61
|
__entity: { value: !meta.embeddable, configurable: true },
|
|
@@ -78,10 +85,9 @@ export class EntityHelper {
|
|
|
78
85
|
* than on its prototype. Thanks to this we still have those properties enumerable (e.g. part of `Object.keys(entity)`).
|
|
79
86
|
*/
|
|
80
87
|
static defineProperties(meta, em) {
|
|
81
|
-
Object
|
|
82
|
-
.values(meta.properties)
|
|
83
|
-
.forEach(prop => {
|
|
88
|
+
Object.values(meta.properties).forEach(prop => {
|
|
84
89
|
const isCollection = [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind);
|
|
90
|
+
// oxfmt-ignore
|
|
85
91
|
const isReference = [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && (prop.inversedBy || prop.mappedBy) && !prop.mapToPk;
|
|
86
92
|
if (isReference) {
|
|
87
93
|
Object.defineProperty(meta.prototype, prop.name, {
|
|
@@ -130,10 +136,8 @@ export class EntityHelper {
|
|
|
130
136
|
}
|
|
131
137
|
}
|
|
132
138
|
// ensure we dont have internal symbols in the POJO
|
|
133
|
-
[OptionalProps, EntityRepositoryType, PrimaryKeyProp, EagerProps, HiddenProps].forEach(sym => delete object[sym]);
|
|
134
|
-
meta.props
|
|
135
|
-
.filter(prop => object[prop.name] === undefined)
|
|
136
|
-
.forEach(prop => delete object[prop.name]);
|
|
139
|
+
[OptionalProps, EntityRepositoryType, PrimaryKeyProp, EagerProps, HiddenProps, EntityName].forEach(sym => delete object[sym]);
|
|
140
|
+
meta.props.filter(prop => object[prop.name] === undefined).forEach(prop => delete object[prop.name]);
|
|
137
141
|
const ret = inspect(object, { depth });
|
|
138
142
|
let name = this.constructor.name;
|
|
139
143
|
const showEM = ['true', 't', '1'].includes(getEnv('MIKRO_ORM_LOG_EM_ID')?.toLowerCase() ?? '');
|
|
@@ -161,6 +165,7 @@ export class EntityHelper {
|
|
|
161
165
|
set(val) {
|
|
162
166
|
const entity = Reference.unwrapReference(val ?? wrapped.__data[prop.name]);
|
|
163
167
|
const old = Reference.unwrapReference(wrapped.__data[prop.name]);
|
|
168
|
+
// oxfmt-ignore
|
|
164
169
|
if (old && old !== entity && prop.kind === ReferenceKind.MANY_TO_ONE && prop.inversedBy && old[prop.inversedBy]) {
|
|
165
170
|
old[prop.inversedBy].removeWithoutPropagation(this);
|
|
166
171
|
}
|
|
@@ -192,6 +197,7 @@ export class EntityHelper {
|
|
|
192
197
|
if ((prop2.inversedBy || prop2.mappedBy) !== prop.name) {
|
|
193
198
|
continue;
|
|
194
199
|
}
|
|
200
|
+
// oxfmt-ignore
|
|
195
201
|
if (prop2.targetMeta.abstract ? prop2.targetMeta.root.class !== meta.root.class : prop2.targetMeta.class !== meta.class) {
|
|
196
202
|
continue;
|
|
197
203
|
}
|
|
@@ -223,7 +229,10 @@ export class EntityHelper {
|
|
|
223
229
|
static propagateOneToOne(entity, owner, prop, prop2, value, old) {
|
|
224
230
|
helper(entity).__pk = helper(entity).getPrimaryKey();
|
|
225
231
|
// the inverse side will be changed on the `value` too, so we need to clean-up and schedule orphan removal there too
|
|
226
|
-
if (!prop.primary &&
|
|
232
|
+
if (!prop.primary &&
|
|
233
|
+
!prop2.mapToPk &&
|
|
234
|
+
value?.[prop2.name] != null &&
|
|
235
|
+
Reference.unwrapReference(value[prop2.name]) !== entity) {
|
|
227
236
|
const other = Reference.unwrapReference(value[prop2.name]);
|
|
228
237
|
delete helper(other).__data[prop.name];
|
|
229
238
|
if (prop2.orphanRemoval) {
|
package/entity/EntityLoader.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type { AnyEntity, ConnectionType, EntityName, EntityProperty, FilterQuery, PopulateOptions } from '../typings.js';
|
|
1
|
+
import type { AnyEntity, AutoPath, 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
|
-
import type {
|
|
4
|
+
import type { FilterOptions } from '../drivers/IDatabaseDriver.js';
|
|
5
5
|
import type { LoggingOptions } from '../logging/Logger.js';
|
|
6
|
-
export
|
|
6
|
+
export interface EntityLoaderOptions<Entity, Fields extends string = PopulatePath.ALL, Excludes extends string = never> {
|
|
7
|
+
fields?: readonly AutoPath<Entity, Fields, `${PopulatePath.ALL}`>[];
|
|
8
|
+
exclude?: readonly AutoPath<Entity, Excludes>[];
|
|
7
9
|
where?: FilterQuery<Entity>;
|
|
8
10
|
populateWhere?: PopulateHint | `${PopulateHint}`;
|
|
9
|
-
fields?: readonly EntityField<Entity, Fields>[];
|
|
10
|
-
exclude?: readonly EntityField<Entity, Excludes>[];
|
|
11
11
|
orderBy?: QueryOrderMap<Entity> | QueryOrderMap<Entity>[];
|
|
12
12
|
refresh?: boolean;
|
|
13
13
|
validate?: boolean;
|
|
@@ -20,7 +20,7 @@ export type EntityLoaderOptions<Entity, Fields extends string = PopulatePath.ALL
|
|
|
20
20
|
schema?: string;
|
|
21
21
|
connectionType?: ConnectionType;
|
|
22
22
|
logging?: LoggingOptions;
|
|
23
|
-
}
|
|
23
|
+
}
|
|
24
24
|
export declare class EntityLoader {
|
|
25
25
|
private readonly em;
|
|
26
26
|
private readonly metadata;
|