@mikro-orm/core 7.0.17-dev.1 → 7.0.17-dev.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.js CHANGED
@@ -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) ? options.populate : undefined;
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
- const extras = fromFields.filter(f => f.includes('.'));
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
  }
@@ -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
  }
@@ -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.1",
3
+ "version": "7.0.17-dev.3",
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.1';
144
+ static #ORM_VERSION = '7.0.17-dev.3';
145
145
  /**
146
146
  * Checks if the argument is instance of `Object`. Returns false for arrays.
147
147
  */