@mikro-orm/core 7.0.17-dev.0 → 7.0.17-dev.2
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 +28 -5
- package/entity/EntityLoader.js +11 -5
- package/package.json +1 -1
- package/utils/Utils.js +1 -1
package/EntityManager.js
CHANGED
|
@@ -452,7 +452,23 @@ export class EntityManager {
|
|
|
452
452
|
const prop = meta?.properties[field];
|
|
453
453
|
if (prop && !ref) {
|
|
454
454
|
hint.children ??= [];
|
|
455
|
-
|
|
455
|
+
const targets = prop.polymorphic && prop.polymorphTargets?.length ? prop.polymorphTargets : [prop.targetMeta];
|
|
456
|
+
for (const targetMeta of targets) {
|
|
457
|
+
const before = hint.children.length;
|
|
458
|
+
await this.autoJoinRefsForFilters(targetMeta, { ...options, populate: hint.children }, { class: meta.root.class, propName: prop.name });
|
|
459
|
+
// For polymorphic relations, `hint.children` is applied to every polymorph target during
|
|
460
|
+
// population, so drop any auto-added hint whose field does not exist on every target —
|
|
461
|
+
// otherwise the shared `:ref` would crash when applied to a target lacking it (GH #7722).
|
|
462
|
+
if (targets.length > 1 && hint.children.length > before) {
|
|
463
|
+
const added = hint.children.splice(before);
|
|
464
|
+
for (const h of added) {
|
|
465
|
+
const f = h.field.split(':')[0];
|
|
466
|
+
if (targets.every(t => f in t.properties)) {
|
|
467
|
+
hint.children.push(h);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
456
472
|
}
|
|
457
473
|
}
|
|
458
474
|
}
|
|
@@ -1727,9 +1743,13 @@ export class EntityManager {
|
|
|
1727
1743
|
return [];
|
|
1728
1744
|
}
|
|
1729
1745
|
const meta = this.metadata.find(entityName);
|
|
1730
|
-
// 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.
|
|
1731
1749
|
const fields = options.fields ? this.buildFields(options.fields) : undefined;
|
|
1732
|
-
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;
|
|
1733
1753
|
const hasNestedFields = fields?.some(f => f.includes('.')) ?? false;
|
|
1734
1754
|
const shouldInfer = fields !== undefined && options.populate === undefined;
|
|
1735
1755
|
const shouldMerge = fields !== undefined && populateArray !== undefined && populateArray.length > 0 && hasNestedFields;
|
|
@@ -1773,8 +1793,11 @@ export class EntityManager {
|
|
|
1773
1793
|
}
|
|
1774
1794
|
else {
|
|
1775
1795
|
// with an explicit populate, only nested paths (e.g. `item.id`) need to be merged in — they
|
|
1776
|
-
// require a JOIN to access sub-fields, whereas bare names are already handled by the driver
|
|
1777
|
-
|
|
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));
|
|
1778
1801
|
options.populate = [...populateArray, ...extras];
|
|
1779
1802
|
}
|
|
1780
1803
|
}
|
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);
|
|
@@ -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
|
}
|
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.2",
|
|
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.2';
|
|
145
145
|
/**
|
|
146
146
|
* Checks if the argument is instance of `Object`. Returns false for arrays.
|
|
147
147
|
*/
|