@mikro-orm/core 7.0.0-rc.2 → 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 +2 -1
- package/EntityManager.js +106 -42
- 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 +43 -0
- package/entity/Collection.js +43 -17
- package/entity/EntityAssigner.js +23 -11
- package/entity/EntityFactory.js +32 -12
- package/entity/EntityHelper.js +25 -16
- package/entity/EntityLoader.js +55 -22
- package/entity/Reference.d.ts +1 -1
- package/entity/Reference.js +37 -8
- package/entity/WrappedEntity.js +5 -1
- package/entity/defineEntity.d.ts +24 -12
- 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 +1 -1
- 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 +106 -47
- 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 +38 -38
- package/platforms/Platform.d.ts +1 -0
- package/platforms/Platform.js +49 -23
- package/serialization/EntitySerializer.js +7 -3
- package/serialization/SerializationContext.js +1 -1
- package/typings.d.ts +23 -23
- package/typings.js +9 -9
- package/unit-of-work/ChangeSet.js +4 -4
- package/unit-of-work/ChangeSetComputer.js +8 -6
- package/unit-of-work/ChangeSetPersister.js +13 -8
- 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 +1 -1
- package/utils/AbstractMigrator.js +3 -5
- package/utils/AbstractSchemaGenerator.js +2 -1
- package/utils/AsyncContext.js +1 -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.js +2 -5
- package/utils/upsert-utils.js +6 -3
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,23 +115,28 @@ 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`
|
|
133
142
|
// (e.g. from createReference), otherwise scalar properties in diff2 won't be applied
|
|
@@ -137,10 +146,15 @@ export class EntityFactory {
|
|
|
137
146
|
const nullVal = this.config.get('forceUndefined') ? undefined : null;
|
|
138
147
|
Utils.keys(diff2).forEach(key => {
|
|
139
148
|
const prop = meta.properties[key];
|
|
140
|
-
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
|
|
141
152
|
diff2[key] = entity[prop.name] ? helper(entity[prop.name]).getPrimaryKey(options.convertCustomTypes) : null;
|
|
142
153
|
}
|
|
143
|
-
if (!options.convertCustomTypes &&
|
|
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) {
|
|
144
158
|
const converted = prop.customType.convertToJSValue(diff2[key], this.platform, { force: true });
|
|
145
159
|
diff2[key] = prop.customType.convertToDatabaseValue(converted, this.platform, { fromQuery: true });
|
|
146
160
|
}
|
|
@@ -149,7 +163,8 @@ export class EntityFactory {
|
|
|
149
163
|
});
|
|
150
164
|
// in case of joined loading strategy, we need to cascade the merging to possibly loaded relations manually
|
|
151
165
|
meta.relations.forEach(prop => {
|
|
152
|
-
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])) {
|
|
153
168
|
// instead of trying to match the collection items (which could easily fail if the collection was loaded with different ordering),
|
|
154
169
|
// we just create the entity from scratch, which will automatically pick the right one from the identity map and call `mergeData` on it
|
|
155
170
|
data[prop.name]
|
|
@@ -157,7 +172,10 @@ export class EntityFactory {
|
|
|
157
172
|
.forEach(child => this.create(prop.targetMeta.class, child, options)); // we can ignore the value, we just care about the `mergeData` call
|
|
158
173
|
return;
|
|
159
174
|
}
|
|
160
|
-
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) {
|
|
161
179
|
this.create(prop.targetMeta.class, data[prop.name], options); // we can ignore the value, we just care about the `mergeData` call
|
|
162
180
|
}
|
|
163
181
|
});
|
|
@@ -356,7 +374,9 @@ export class EntityFactory {
|
|
|
356
374
|
if (!options.convertCustomTypes || !prop.customType || tmp[prop.name] == null) {
|
|
357
375
|
continue;
|
|
358
376
|
}
|
|
359
|
-
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)) {
|
|
360
380
|
tmp[prop.name] = Reference.wrapReference(this.create(prop.targetMeta.class, tmp[prop.name], options), prop);
|
|
361
381
|
}
|
|
362
382
|
else if (prop.kind === ReferenceKind.SCALAR) {
|
package/entity/EntityHelper.js
CHANGED
|
@@ -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, {
|
|
@@ -131,9 +137,7 @@ export class EntityHelper {
|
|
|
131
137
|
}
|
|
132
138
|
// ensure we dont have internal symbols in the POJO
|
|
133
139
|
[OptionalProps, EntityRepositoryType, PrimaryKeyProp, EagerProps, HiddenProps, EntityName].forEach(sym => delete object[sym]);
|
|
134
|
-
meta.props
|
|
135
|
-
.filter(prop => object[prop.name] === undefined)
|
|
136
|
-
.forEach(prop => delete object[prop.name]);
|
|
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.js
CHANGED
|
@@ -29,7 +29,7 @@ export class EntityLoader {
|
|
|
29
29
|
throw ValidationError.notDiscoveredEntity(entity, meta, 'populate');
|
|
30
30
|
}
|
|
31
31
|
const references = entities.filter(e => !helper(e).isInitialized());
|
|
32
|
-
const visited = options.visited ??= new Set();
|
|
32
|
+
const visited = (options.visited ??= new Set());
|
|
33
33
|
options.where ??= {};
|
|
34
34
|
options.orderBy ??= {};
|
|
35
35
|
options.lookup ??= true;
|
|
@@ -59,6 +59,7 @@ export class EntityLoader {
|
|
|
59
59
|
normalizePopulate(entityName, populate, strategy, lookup = true, exclude) {
|
|
60
60
|
const meta = this.metadata.find(entityName);
|
|
61
61
|
let normalized = Utils.asArray(populate).map(field => {
|
|
62
|
+
// oxfmt-ignore
|
|
62
63
|
return typeof field === 'boolean' || field.field === PopulatePath.ALL ? { all: !!field, field: meta.primaryKeys[0] } : field;
|
|
63
64
|
});
|
|
64
65
|
if (normalized.some(p => p.all)) {
|
|
@@ -89,6 +90,7 @@ export class EntityLoader {
|
|
|
89
90
|
*/
|
|
90
91
|
mergeNestedPopulate(populate) {
|
|
91
92
|
const tmp = populate.reduce((ret, item) => {
|
|
93
|
+
/* v8 ignore next */
|
|
92
94
|
if (item.field === PopulatePath.ALL) {
|
|
93
95
|
return ret;
|
|
94
96
|
}
|
|
@@ -125,7 +127,8 @@ export class EntityLoader {
|
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
129
|
if (prop.kind === ReferenceKind.SCALAR && prop.lazy) {
|
|
128
|
-
const filtered = entities.filter(e => options.refresh ||
|
|
130
|
+
const filtered = entities.filter(e => options.refresh ||
|
|
131
|
+
(prop.ref ? !e[prop.name]?.isInitialized() : e[prop.name] === undefined));
|
|
129
132
|
if (options.ignoreLazyScalarProperties || filtered.length === 0) {
|
|
130
133
|
return entities;
|
|
131
134
|
}
|
|
@@ -137,7 +140,8 @@ export class EntityLoader {
|
|
|
137
140
|
}
|
|
138
141
|
const filtered = this.filterCollections(entities, field, options, ref);
|
|
139
142
|
const innerOrderBy = Utils.asArray(options.orderBy)
|
|
140
|
-
.filter(orderBy => (Array.isArray(orderBy[prop.name]) && orderBy[prop.name].length > 0) ||
|
|
143
|
+
.filter(orderBy => (Array.isArray(orderBy[prop.name]) && orderBy[prop.name].length > 0) ||
|
|
144
|
+
Utils.isObject(orderBy[prop.name]))
|
|
141
145
|
.flatMap(orderBy => orderBy[prop.name]);
|
|
142
146
|
const where = await this.extractChildCondition(options, prop);
|
|
143
147
|
if (prop.kind === ReferenceKind.MANY_TO_MANY && this.driver.getPlatform().usesPivotTable()) {
|
|
@@ -163,7 +167,13 @@ export class EntityLoader {
|
|
|
163
167
|
const where = this.mergePrimaryCondition(ids, pk, options, meta, this.metadata, this.driver.getPlatform());
|
|
164
168
|
const { filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, fields } = options;
|
|
165
169
|
await this.em.find(meta.class, where, {
|
|
166
|
-
filters,
|
|
170
|
+
filters,
|
|
171
|
+
convertCustomTypes,
|
|
172
|
+
lockMode,
|
|
173
|
+
strategy,
|
|
174
|
+
populateWhere,
|
|
175
|
+
connectionType,
|
|
176
|
+
logging,
|
|
167
177
|
fields: fields,
|
|
168
178
|
populate: [],
|
|
169
179
|
});
|
|
@@ -260,7 +270,8 @@ export class EntityLoader {
|
|
|
260
270
|
entity[field].hydrate(items, true, partial);
|
|
261
271
|
}
|
|
262
272
|
}
|
|
263
|
-
else {
|
|
273
|
+
else {
|
|
274
|
+
// owning side of M:N without pivot table needs to be reordered
|
|
264
275
|
for (const entity of filtered) {
|
|
265
276
|
const order = !customOrder ? [...entity[prop.name].getItems(false)] : []; // copy order of references
|
|
266
277
|
const items = children.filter(child => entity[prop.name].contains(child, false));
|
|
@@ -301,7 +312,7 @@ export class EntityLoader {
|
|
|
301
312
|
if (!schema && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
|
|
302
313
|
schema = children.find(e => e.__helper.__schema)?.__helper.__schema;
|
|
303
314
|
}
|
|
304
|
-
const ids = Utils.unique(children.map(e => prop.targetKey ? e[prop.targetKey] : e.__helper.getPrimaryKey()));
|
|
315
|
+
const ids = Utils.unique(children.map(e => (prop.targetKey ? e[prop.targetKey] : e.__helper.getPrimaryKey())));
|
|
305
316
|
let where;
|
|
306
317
|
if (polymorphicOwnerProp && Array.isArray(fk)) {
|
|
307
318
|
const conditions = ids.map(id => {
|
|
@@ -315,7 +326,8 @@ export class EntityLoader {
|
|
|
315
326
|
}
|
|
316
327
|
if (polymorphicOwnerProp) {
|
|
317
328
|
const parentMeta = this.metadata.find(entities[0].constructor);
|
|
318
|
-
const discriminatorValue = QueryHelper.findDiscriminatorValue(polymorphicOwnerProp.discriminatorMap, parentMeta.class) ??
|
|
329
|
+
const discriminatorValue = QueryHelper.findDiscriminatorValue(polymorphicOwnerProp.discriminatorMap, parentMeta.class) ??
|
|
330
|
+
parentMeta.tableName;
|
|
319
331
|
const discriminatorColumn = polymorphicOwnerProp.fieldNames[0];
|
|
320
332
|
where = { $and: [where, { [discriminatorColumn]: discriminatorValue }] };
|
|
321
333
|
}
|
|
@@ -331,11 +343,20 @@ export class EntityLoader {
|
|
|
331
343
|
}
|
|
332
344
|
const orderBy = QueryHelper.mergeOrderBy(options.orderBy, prop.orderBy);
|
|
333
345
|
const items = await this.em.find(meta.class, where, {
|
|
334
|
-
filters,
|
|
346
|
+
filters,
|
|
347
|
+
convertCustomTypes,
|
|
348
|
+
lockMode,
|
|
349
|
+
populateWhere,
|
|
350
|
+
logging,
|
|
335
351
|
orderBy,
|
|
336
352
|
populate: populate.children ?? populate.all ?? [],
|
|
337
|
-
exclude: Array.isArray(options.exclude)
|
|
338
|
-
|
|
353
|
+
exclude: Array.isArray(options.exclude)
|
|
354
|
+
? Utils.extractChildElements(options.exclude, prop.name)
|
|
355
|
+
: options.exclude,
|
|
356
|
+
strategy,
|
|
357
|
+
fields,
|
|
358
|
+
schema,
|
|
359
|
+
connectionType,
|
|
339
360
|
// @ts-ignore not a public option, will be propagated to the populate call
|
|
340
361
|
refresh: refresh && !children.every(item => options.visited.has(item)),
|
|
341
362
|
// @ts-ignore not a public option, will be propagated to the populate call
|
|
@@ -355,6 +376,7 @@ export class EntityLoader {
|
|
|
355
376
|
if (!ref) {
|
|
356
377
|
continue;
|
|
357
378
|
}
|
|
379
|
+
// oxfmt-ignore
|
|
358
380
|
const keyValue = '' + (Reference.isReference(ref) ? ref.unwrap()[prop.targetKey] : ref[prop.targetKey]);
|
|
359
381
|
const loadedItem = itemsByKey.get(keyValue);
|
|
360
382
|
if (loadedItem) {
|
|
@@ -367,7 +389,7 @@ export class EntityLoader {
|
|
|
367
389
|
const itemsMap = new Set();
|
|
368
390
|
const childrenMap = new Set();
|
|
369
391
|
// Use targetKey value if set, otherwise use serialized PK
|
|
370
|
-
const getKey = (e) => prop.targetKey ? '' + e[prop.targetKey] : helper(e).getSerializedPrimaryKey();
|
|
392
|
+
const getKey = (e) => (prop.targetKey ? '' + e[prop.targetKey] : helper(e).getSerializedPrimaryKey());
|
|
371
393
|
for (const item of items) {
|
|
372
394
|
/* v8 ignore next */
|
|
373
395
|
itemsMap.add(getKey(item));
|
|
@@ -394,12 +416,16 @@ export class EntityLoader {
|
|
|
394
416
|
return { items, partial };
|
|
395
417
|
}
|
|
396
418
|
mergePrimaryCondition(ids, pk, options, meta, metadata, platform) {
|
|
397
|
-
const cond1 = QueryHelper.processWhere({
|
|
419
|
+
const cond1 = QueryHelper.processWhere({
|
|
420
|
+
where: { [pk]: { $in: ids } },
|
|
421
|
+
entityName: meta.class,
|
|
422
|
+
metadata,
|
|
423
|
+
platform,
|
|
424
|
+
convertCustomTypes: !options.convertCustomTypes,
|
|
425
|
+
});
|
|
398
426
|
const where = { ...options.where };
|
|
399
427
|
Utils.dropUndefinedProperties(where);
|
|
400
|
-
return where[pk]
|
|
401
|
-
? { $and: [cond1, where] }
|
|
402
|
-
: { ...cond1, ...where };
|
|
428
|
+
return where[pk] ? { $and: [cond1, where] } : { ...cond1, ...where };
|
|
403
429
|
}
|
|
404
430
|
async populateField(entityName, entities, populate, options) {
|
|
405
431
|
const field = populate.field.split(':')[0];
|
|
@@ -436,6 +462,7 @@ export class EntityLoader {
|
|
|
436
462
|
.filter(orderBy => Utils.isObject(orderBy[prop.name]))
|
|
437
463
|
.map(orderBy => orderBy[prop.name]);
|
|
438
464
|
const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType, logging, schema } = options;
|
|
465
|
+
// oxfmt-ignore
|
|
439
466
|
const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
|
|
440
467
|
const visited = options.visited;
|
|
441
468
|
for (const entity of entities) {
|
|
@@ -451,7 +478,7 @@ export class EntityLoader {
|
|
|
451
478
|
}
|
|
452
479
|
const populateChildren = async (targetMeta, items) => {
|
|
453
480
|
await this.populate(targetMeta.class, items, populate.children ?? populate.all, {
|
|
454
|
-
where: await this.extractChildCondition(options, prop, false),
|
|
481
|
+
where: (await this.extractChildCondition(options, prop, false)),
|
|
455
482
|
orderBy: innerOrderBy,
|
|
456
483
|
fields,
|
|
457
484
|
exclude,
|
|
@@ -489,13 +516,14 @@ export class EntityLoader {
|
|
|
489
516
|
const refresh = options.refresh;
|
|
490
517
|
let where = await this.extractChildCondition(options, prop, true);
|
|
491
518
|
const fields = this.buildFields(options.fields, prop);
|
|
519
|
+
// oxfmt-ignore
|
|
492
520
|
const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
|
|
493
521
|
const populateFilter = options.populateFilter?.[prop.name];
|
|
494
522
|
const options2 = { ...options, fields, exclude, populateFilter };
|
|
495
523
|
['limit', 'offset', 'first', 'last', 'before', 'after', 'overfetch'].forEach(prop => delete options2[prop]);
|
|
496
|
-
options2.populate =
|
|
524
|
+
options2.populate = populate?.children ?? [];
|
|
497
525
|
if (prop.customType) {
|
|
498
|
-
ids.forEach((id, idx) => ids[idx] = QueryHelper.processCustomType(prop, id, this.driver.getPlatform()));
|
|
526
|
+
ids.forEach((id, idx) => (ids[idx] = QueryHelper.processCustomType(prop, id, this.driver.getPlatform())));
|
|
499
527
|
}
|
|
500
528
|
if (!Utils.isEmpty(prop.where)) {
|
|
501
529
|
where = { $and: [where, prop.where] };
|
|
@@ -532,7 +560,8 @@ export class EntityLoader {
|
|
|
532
560
|
if (where[op]) {
|
|
533
561
|
const child = where[op]
|
|
534
562
|
.map((cond) => cond[prop.name])
|
|
535
|
-
.filter((sub) => sub != null &&
|
|
563
|
+
.filter((sub) => sub != null &&
|
|
564
|
+
!(Utils.isPlainObject(sub) && Utils.getObjectQueryKeys(sub).every(key => Utils.isOperator(key, false))))
|
|
536
565
|
.map((cond) => {
|
|
537
566
|
if (Utils.isPrimaryKey(cond)) {
|
|
538
567
|
return { [pk]: cond };
|
|
@@ -603,7 +632,8 @@ export class EntityLoader {
|
|
|
603
632
|
return a;
|
|
604
633
|
}, []);
|
|
605
634
|
}
|
|
606
|
-
if (prop.kind === ReferenceKind.MANY_TO_MANY) {
|
|
635
|
+
if (prop.kind === ReferenceKind.MANY_TO_MANY) {
|
|
636
|
+
// inverse side
|
|
607
637
|
return filtered;
|
|
608
638
|
}
|
|
609
639
|
// MANY_TO_ONE or ONE_TO_ONE
|
|
@@ -706,7 +736,10 @@ export class EntityLoader {
|
|
|
706
736
|
.forEach(prop => {
|
|
707
737
|
const field = this.getRelationName(meta, prop);
|
|
708
738
|
const prefixed = prefix ? `${prefix}.${field}` : field;
|
|
709
|
-
const nestedPopulate = populate
|
|
739
|
+
const nestedPopulate = populate
|
|
740
|
+
.filter(p => p.field === prop.name)
|
|
741
|
+
.flatMap(p => p.children)
|
|
742
|
+
.filter(Boolean);
|
|
710
743
|
const nested = this.lookupEagerLoadedRelationships(prop.targetMeta.class, nestedPopulate, strategy, prefixed, visited.slice(), exclude);
|
|
711
744
|
if (nested.length > 0) {
|
|
712
745
|
ret.push(...nested);
|
|
@@ -716,7 +749,7 @@ export class EntityLoader {
|
|
|
716
749
|
ret.push({
|
|
717
750
|
field: prefixed,
|
|
718
751
|
// enforce select-in strategy for self-referencing relations
|
|
719
|
-
strategy: selfReferencing ? LoadStrategy.SELECT_IN : strategy ?? prop.strategy,
|
|
752
|
+
strategy: selfReferencing ? LoadStrategy.SELECT_IN : (strategy ?? prop.strategy),
|
|
720
753
|
});
|
|
721
754
|
}
|
|
722
755
|
});
|
package/entity/Reference.d.ts
CHANGED
|
@@ -73,7 +73,7 @@ export interface LoadReferenceOrFailOptions<T extends object, P extends string =
|
|
|
73
73
|
/**
|
|
74
74
|
* shortcut for `wrap(entity).toReference()`
|
|
75
75
|
*/
|
|
76
|
-
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
|
+
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>;
|
|
77
77
|
/**
|
|
78
78
|
* shortcut for `Reference.createFromPK(entityType, pk)`
|
|
79
79
|
*/
|