@mikro-orm/core 7.0.17-dev.1 → 7.0.17-dev.10
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.js +15 -8
- package/entity/EntityFactory.js +5 -3
- package/entity/EntityLoader.js +43 -10
- package/metadata/MetadataDiscovery.js +4 -0
- package/package.json +1 -1
- package/utils/Utils.js +1 -1
package/EntityManager.js
CHANGED
|
@@ -319,13 +319,13 @@ export class EntityManager {
|
|
|
319
319
|
}
|
|
320
320
|
const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.get(cls));
|
|
321
321
|
const children = [];
|
|
322
|
-
const lookUpChildren = (ret,
|
|
323
|
-
const children = types.filter(meta2 => meta2.extends ===
|
|
324
|
-
children.forEach(m => lookUpChildren(ret, m
|
|
322
|
+
const lookUpChildren = (ret, parent) => {
|
|
323
|
+
const children = types.filter(meta2 => meta2.extends && this.metadata.find(meta2.extends) === parent);
|
|
324
|
+
children.forEach(m => lookUpChildren(ret, m));
|
|
325
325
|
ret.push(...children.filter(c => c.discriminatorValue));
|
|
326
326
|
return children;
|
|
327
327
|
};
|
|
328
|
-
lookUpChildren(children, meta
|
|
328
|
+
lookUpChildren(children, meta);
|
|
329
329
|
/* v8 ignore next */
|
|
330
330
|
where[meta.root.discriminatorColumn] =
|
|
331
331
|
children.length > 0
|
|
@@ -1743,9 +1743,13 @@ export class EntityManager {
|
|
|
1743
1743
|
return [];
|
|
1744
1744
|
}
|
|
1745
1745
|
const meta = this.metadata.find(entityName);
|
|
1746
|
-
// infer populate hints from `fields` when present; merge with explicit `populate` if both are set
|
|
1746
|
+
// infer populate hints from `fields` when present; merge with explicit `populate` if both are set.
|
|
1747
|
+
// `populateArray` is only kept when the entries are user-provided strings — when this method runs a
|
|
1748
|
+
// second time (e.g. from `lockAndPopulate`) the populate is already normalized and merging would be a no-op.
|
|
1747
1749
|
const fields = options.fields ? this.buildFields(options.fields) : undefined;
|
|
1748
|
-
const populateArray = Array.isArray(options.populate)
|
|
1750
|
+
const populateArray = Array.isArray(options.populate) && options.populate.every(p => typeof p === 'string')
|
|
1751
|
+
? options.populate
|
|
1752
|
+
: undefined;
|
|
1749
1753
|
const hasNestedFields = fields?.some(f => f.includes('.')) ?? false;
|
|
1750
1754
|
const shouldInfer = fields !== undefined && options.populate === undefined;
|
|
1751
1755
|
const shouldMerge = fields !== undefined && populateArray !== undefined && populateArray.length > 0 && hasNestedFields;
|
|
@@ -1789,8 +1793,11 @@ export class EntityManager {
|
|
|
1789
1793
|
}
|
|
1790
1794
|
else {
|
|
1791
1795
|
// with an explicit populate, only nested paths (e.g. `item.id`) need to be merged in — they
|
|
1792
|
-
// require a JOIN to access sub-fields, whereas bare names are already handled by the driver
|
|
1793
|
-
|
|
1796
|
+
// require a JOIN to access sub-fields, whereas bare names are already handled by the driver.
|
|
1797
|
+
// skip paths already in user's `populate` (regardless of `:ref` modifier) to avoid producing
|
|
1798
|
+
// both a `:ref` and a non-`:ref` entry for the same relation
|
|
1799
|
+
const existing = new Set(populateArray.map(p => p.split(':')[0]));
|
|
1800
|
+
const extras = fromFields.filter(f => f.includes('.') && !existing.has(f));
|
|
1794
1801
|
options.populate = [...populateArray, ...extras];
|
|
1795
1802
|
}
|
|
1796
1803
|
}
|
package/entity/EntityFactory.js
CHANGED
|
@@ -161,13 +161,15 @@ export class EntityFactory {
|
|
|
161
161
|
return diff2[key] === undefined;
|
|
162
162
|
})
|
|
163
163
|
.forEach(key => delete diff2[key]);
|
|
164
|
-
// but always add collection properties, formulas, generated columns,
|
|
165
|
-
// are part of the `data`, as these are excluded from
|
|
164
|
+
// but always add collection properties, formulas, generated columns, version properties, and the
|
|
165
|
+
// user-declared STI discriminator if they are part of the `data`, as these are excluded from
|
|
166
|
+
// `comparableProps` and won't appear in the diff
|
|
166
167
|
Utils.keys(data)
|
|
167
168
|
.filter(key => meta.properties[key]?.formula ||
|
|
168
169
|
meta.properties[key]?.version ||
|
|
169
170
|
(meta.properties[key]?.generated && !meta.properties[key]?.primary) ||
|
|
170
|
-
[ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(meta.properties[key]?.kind)
|
|
171
|
+
[ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(meta.properties[key]?.kind) ||
|
|
172
|
+
(key === meta.root.discriminatorColumn && meta.properties[key]?.userDefined !== false))
|
|
171
173
|
.forEach(key => (diff2[key] = data[key]));
|
|
172
174
|
// rehydrated with the new values, skip those changed by user
|
|
173
175
|
// use full hydration if the entity is already initialized, even if the caller used `initialized: false`
|
package/entity/EntityLoader.js
CHANGED
|
@@ -316,7 +316,7 @@ export class EntityLoader {
|
|
|
316
316
|
}
|
|
317
317
|
}
|
|
318
318
|
async findChildren(entities, prop, populate, options, ref) {
|
|
319
|
-
const children = Utils.unique(this.getChildReferences(entities, prop, options, ref));
|
|
319
|
+
const children = Utils.unique(this.getChildReferences(entities, prop, options, ref, populate));
|
|
320
320
|
const meta = prop.targetMeta;
|
|
321
321
|
// When targetKey is set, use it for FK lookup instead of the PK
|
|
322
322
|
let fk = prop.targetKey ?? Utils.getPrimaryKeyHash(meta.primaryKeys);
|
|
@@ -334,10 +334,10 @@ export class EntityLoader {
|
|
|
334
334
|
fk = ownerProp.name;
|
|
335
335
|
}
|
|
336
336
|
}
|
|
337
|
-
if (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && !ref) {
|
|
337
|
+
if (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && (!ref || (!prop.mapToPk && !populate.filter))) {
|
|
338
338
|
children.length = 0;
|
|
339
339
|
fk = meta.properties[prop.mappedBy].name;
|
|
340
|
-
children.push(...this.filterByReferences(entities, prop
|
|
340
|
+
children.push(...this.filterByReferences(entities, prop, options, ref));
|
|
341
341
|
}
|
|
342
342
|
if (children.length === 0) {
|
|
343
343
|
return { items: [], partial };
|
|
@@ -682,7 +682,7 @@ export class EntityLoader {
|
|
|
682
682
|
}
|
|
683
683
|
return ret;
|
|
684
684
|
}
|
|
685
|
-
getChildReferences(entities, prop, options, ref) {
|
|
685
|
+
getChildReferences(entities, prop, options, ref, populate) {
|
|
686
686
|
const filtered = this.filterCollections(entities, prop.name, options, ref);
|
|
687
687
|
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
|
688
688
|
return filtered.map(e => e[prop.name].owner);
|
|
@@ -698,7 +698,7 @@ export class EntityLoader {
|
|
|
698
698
|
return filtered;
|
|
699
699
|
}
|
|
700
700
|
// MANY_TO_ONE or ONE_TO_ONE
|
|
701
|
-
return this.filterReferences(entities, prop.name, options, ref);
|
|
701
|
+
return this.filterReferences(entities, prop.name, options, ref, populate);
|
|
702
702
|
}
|
|
703
703
|
filterCollections(entities, field, options, ref) {
|
|
704
704
|
if (options.refresh) {
|
|
@@ -724,7 +724,7 @@ export class EntityLoader {
|
|
|
724
724
|
}
|
|
725
725
|
return this.isPropertyLoaded(entity[f], r.join('.'));
|
|
726
726
|
}
|
|
727
|
-
filterReferences(entities, field, options, ref) {
|
|
727
|
+
filterReferences(entities, field, options, ref, populate) {
|
|
728
728
|
if (ref) {
|
|
729
729
|
return [];
|
|
730
730
|
}
|
|
@@ -733,13 +733,19 @@ export class EntityLoader {
|
|
|
733
733
|
return children.map(e => Reference.unwrapReference(e[field]));
|
|
734
734
|
}
|
|
735
735
|
if (options.fields) {
|
|
736
|
+
// `:ref` populate children are satisfied by FK/PK and will be loaded lazily — they don't force a parent reload
|
|
737
|
+
const refChildren = new Set((populate?.children ?? [])
|
|
738
|
+
.map(c => c.field.split(':'))
|
|
739
|
+
.filter(parts => parts[1] === 'ref')
|
|
740
|
+
.map(parts => parts[0]));
|
|
736
741
|
return children
|
|
737
742
|
.map(e => Reference.unwrapReference(e[field]))
|
|
738
743
|
.filter(target => {
|
|
739
744
|
const wrapped = helper(target);
|
|
740
745
|
const childFields = options.fields
|
|
741
746
|
.filter(f => f.startsWith(`${field}.`))
|
|
742
|
-
.map(f => f.substring(field.length + 1))
|
|
747
|
+
.map(f => f.substring(field.length + 1))
|
|
748
|
+
.filter(cf => !refChildren.has(cf.split('.')[0]));
|
|
743
749
|
return !wrapped.__initialized || !childFields.every(cf => this.isPropertyLoaded(target, cf));
|
|
744
750
|
});
|
|
745
751
|
}
|
|
@@ -747,12 +753,39 @@ export class EntityLoader {
|
|
|
747
753
|
.filter(e => !e[field].__helper.__initialized)
|
|
748
754
|
.map(e => Reference.unwrapReference(e[field]));
|
|
749
755
|
}
|
|
750
|
-
filterByReferences(entities,
|
|
756
|
+
filterByReferences(entities, prop, options, ref) {
|
|
751
757
|
/* v8 ignore next */
|
|
752
|
-
if (refresh) {
|
|
758
|
+
if (options.refresh) {
|
|
753
759
|
return entities;
|
|
754
760
|
}
|
|
755
|
-
|
|
761
|
+
const field = prop.name;
|
|
762
|
+
return entities.filter(e => {
|
|
763
|
+
const value = e[field];
|
|
764
|
+
if (value === null) {
|
|
765
|
+
return false;
|
|
766
|
+
}
|
|
767
|
+
if (value === undefined || !Utils.isEntity(value, true)) {
|
|
768
|
+
return true;
|
|
769
|
+
}
|
|
770
|
+
const target = Reference.unwrapReference(value);
|
|
771
|
+
const wrapped = helper(target);
|
|
772
|
+
if (!wrapped.__initialized) {
|
|
773
|
+
return true;
|
|
774
|
+
}
|
|
775
|
+
if (ref) {
|
|
776
|
+
return false;
|
|
777
|
+
}
|
|
778
|
+
if (options.fields) {
|
|
779
|
+
const childFields = options.fields
|
|
780
|
+
.filter(f => f.startsWith(`${prop.name}.`))
|
|
781
|
+
.map(f => f.substring(prop.name.length + 1));
|
|
782
|
+
return childFields.length > 0 && !childFields.every(f => this.isPropertyLoaded(target, f));
|
|
783
|
+
}
|
|
784
|
+
return prop.targetMeta.comparableProps.some(targetProp => {
|
|
785
|
+
const inlineEmbedded = targetProp.kind === ReferenceKind.EMBEDDED && !targetProp.object;
|
|
786
|
+
return !inlineEmbedded && !targetProp.lazy && !wrapped.__loadedProperties.has(targetProp.name);
|
|
787
|
+
});
|
|
788
|
+
});
|
|
756
789
|
}
|
|
757
790
|
lookupAllRelationships(entityName) {
|
|
758
791
|
const ret = [];
|
|
@@ -587,6 +587,10 @@ export class MetadataDiscovery {
|
|
|
587
587
|
prop.pivotEntity = pivotMeta.class;
|
|
588
588
|
if (prop.inversedBy) {
|
|
589
589
|
prop.targetMeta.properties[prop.inversedBy].pivotEntity = pivotMeta.class;
|
|
590
|
+
const targetRoot = prop.targetMeta.root;
|
|
591
|
+
if (targetRoot !== prop.targetMeta && targetRoot.properties[prop.inversedBy]) {
|
|
592
|
+
targetRoot.properties[prop.inversedBy].pivotEntity = pivotMeta.class;
|
|
593
|
+
}
|
|
590
594
|
}
|
|
591
595
|
return pivotMeta;
|
|
592
596
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/core",
|
|
3
|
-
"version": "7.0.17-dev.
|
|
3
|
+
"version": "7.0.17-dev.10",
|
|
4
4
|
"description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"data-mapper",
|
package/utils/Utils.js
CHANGED
|
@@ -141,7 +141,7 @@ export function parseJsonSafe(value) {
|
|
|
141
141
|
/** Collection of general-purpose utility methods used throughout the ORM. */
|
|
142
142
|
export class Utils {
|
|
143
143
|
static PK_SEPARATOR = '~~~';
|
|
144
|
-
static #ORM_VERSION = '7.0.17-dev.
|
|
144
|
+
static #ORM_VERSION = '7.0.17-dev.10';
|
|
145
145
|
/**
|
|
146
146
|
* Checks if the argument is instance of `Object`. Returns false for arrays.
|
|
147
147
|
*/
|