@mikro-orm/core 7.3.0-dev.18 → 7.3.0-dev.19

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.
@@ -29,6 +29,11 @@ export declare class MetadataDiscovery {
29
29
  private initFieldName;
30
30
  private initManyToOneFieldName;
31
31
  private initManyToManyFieldName;
32
+ /**
33
+ * Walks up the TPT hierarchy to the entity whose M:N declaration this property inherits. A child redeclaring
34
+ * the property with a different target, pivot table, join columns or fixed order keeps its own pivot table.
35
+ */
36
+ private getPivotDeclaringMeta;
32
37
  private initManyToManyFields;
33
38
  private isExplicitTableName;
34
39
  private initManyToOneFields;
@@ -485,8 +485,53 @@ export class MetadataDiscovery {
485
485
  const meta2 = prop.targetMeta;
486
486
  return meta2.primaryKeys.map(() => this.#namingStrategy.propertyToColumnName(name));
487
487
  }
488
+ /**
489
+ * Walks up the TPT hierarchy to the entity whose M:N declaration this property inherits. A child redeclaring
490
+ * the property with a different target, pivot table, join columns or fixed order keeps its own pivot table.
491
+ */
492
+ getPivotDeclaringMeta(meta, prop) {
493
+ const keys = [
494
+ 'type',
495
+ 'pivotTable',
496
+ 'pivotEntity',
497
+ 'joinColumns',
498
+ 'inverseJoinColumns',
499
+ 'fixedOrder',
500
+ 'fixedOrderColumn',
501
+ ];
502
+ // Rails-style polymorphic owners already share an explicit pivot table, each under its own discriminator value
503
+ if (prop.polymorphic && !QueryHelper.isUnionTargetPolymorphic(prop)) {
504
+ return meta;
505
+ }
506
+ while (meta.tptParent) {
507
+ const parentProp = meta.tptParent.properties[prop.name];
508
+ if (parentProp?.kind !== ReferenceKind.MANY_TO_MANY) {
509
+ break;
510
+ }
511
+ // resolve the parent first so explicit options are compared against its final mapping regardless of discovery order
512
+ this.initManyToManyFields(meta.tptParent, parentProp);
513
+ if (keys.some(key => prop[key] != null && !Utils.equals(prop[key], parentProp[key]))) {
514
+ break;
515
+ }
516
+ meta = meta.tptParent;
517
+ }
518
+ return meta;
519
+ }
488
520
  initManyToManyFields(meta, prop) {
489
521
  const meta2 = prop.targetMeta;
522
+ // inverse sides derive their mapping from the owner via `mappedBy`
523
+ const declaringMeta = prop.mappedBy ? meta : this.getPivotDeclaringMeta(meta, prop);
524
+ if (declaringMeta !== meta) {
525
+ const prop2 = declaringMeta.properties[prop.name];
526
+ prop.pivotTable = prop2.pivotTable;
527
+ prop.pivotEntity = prop2.pivotEntity;
528
+ prop.fixedOrder = prop2.fixedOrder;
529
+ prop.fixedOrderColumn = prop2.fixedOrderColumn;
530
+ prop.joinColumns = prop2.joinColumns;
531
+ prop.inverseJoinColumns = prop2.inverseJoinColumns;
532
+ prop.referencedColumnNames = prop2.referencedColumnNames;
533
+ return;
534
+ }
490
535
  Utils.defaultValue(prop, 'fixedOrder', !!prop.fixedOrderColumn);
491
536
  const pivotMeta = this.#metadata.find(prop.pivotEntity);
492
537
  const props = Object.values(pivotMeta?.properties ?? {});
@@ -528,12 +573,16 @@ export class MetadataDiscovery {
528
573
  prop.referencedColumnNames ??= Utils.flatten(meta.primaryKeys.map(primaryKey => meta.properties[primaryKey].fieldNames));
529
574
  // Union-target polymorphic M:N: owner side is fixed (real FK), target side uses discriminator-derived names.
530
575
  const isUnionTargetMN = QueryHelper.isUnionTargetPolymorphic(prop);
576
+ // both sides of a TPT hierarchy would collide on the root name, so name them after the entities themselves
577
+ const sameTPTRoot = meta.inheritanceType === 'tpt' && meta.root.class === meta2.root.class;
578
+ const ownerName = sameTPTRoot ? meta.className : meta.root.className;
579
+ const targetName = sameTPTRoot ? meta2.className : meta2.root.className;
531
580
  if (prop.polymorphic && prop.discriminator && !isUnionTargetMN) {
532
581
  // Rails-style: owner side is polymorphic, uses discriminator base name (e.g. taggable_id instead of post_id)
533
582
  prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.#namingStrategy.joinKeyColumnName(prop.discriminator, referencedColumnName, prop.referencedColumnNames.length > 1));
534
583
  }
535
584
  else {
536
- prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.#namingStrategy.joinKeyColumnName(meta.root.className, referencedColumnName, meta.compositePK));
585
+ prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.#namingStrategy.joinKeyColumnName(ownerName, referencedColumnName, meta.compositePK));
537
586
  }
538
587
  if (isUnionTargetMN) {
539
588
  // Target side uses discriminator base name (e.g. attachable_id — shared across Image/Video)
@@ -541,7 +590,7 @@ export class MetadataDiscovery {
541
590
  prop.inverseJoinColumns ??= targetPkCols.map(fieldName => this.#namingStrategy.joinKeyColumnName(prop.discriminator, fieldName, targetPkCols.length > 1));
542
591
  }
543
592
  else {
544
- prop.inverseJoinColumns ??= this.initManyToOneFieldName(prop, meta2.root.className);
593
+ prop.inverseJoinColumns ??= this.initManyToOneFieldName(prop, targetName);
545
594
  }
546
595
  }
547
596
  isExplicitTableName(meta) {
@@ -640,8 +689,13 @@ export class MetadataDiscovery {
640
689
  return Object.values(meta.properties)
641
690
  .filter(prop => prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && prop.pivotTable)
642
691
  .map(prop => {
643
- const pivotMeta = this.definePivotTableEntity(meta, prop);
692
+ // an inherited M:N lives in the pivot table of the declaring TPT entity, whose table the pivot FK references
693
+ const declaringMeta = this.getPivotDeclaringMeta(meta, prop);
694
+ const ownerProp = declaringMeta.properties[prop.name];
695
+ const pivotMeta = this.definePivotTableEntity(declaringMeta, ownerProp);
644
696
  prop.pivotEntity = pivotMeta.class;
697
+ prop.inversedBy ??= ownerProp.inversedBy;
698
+ prop.fixedOrderColumn = ownerProp.fixedOrderColumn;
645
699
  if (prop.inversedBy) {
646
700
  prop.targetMeta.properties[prop.inversedBy].pivotEntity = pivotMeta.class;
647
701
  const targetRoot = prop.targetMeta.root;
@@ -654,14 +708,22 @@ export class MetadataDiscovery {
654
708
  // - regular inverse (Tag.posts mappedBy Post.tags) — handled by inversedBy above
655
709
  // - union-target inverse (Image.posts mappedBy Post.attachments) — on each polymorph target
656
710
  // - merged inverse (Tag.owners mappedBy [Post,Video].tags) — union collection on the target
657
- const inverseCandidates = QueryHelper.isUnionTargetPolymorphic(prop)
658
- ? prop.polymorphTargets
659
- : [prop.targetMeta];
711
+ // inherited inverse copies on TPT descendants of the target need the pivot as well
712
+ const withTPTDescendants = (m) => [
713
+ m,
714
+ ...(m.tptChildren ?? []).flatMap(withTPTDescendants),
715
+ ];
716
+ const targets = QueryHelper.isUnionTargetPolymorphic(prop) ? prop.polymorphTargets : [prop.targetMeta];
717
+ const inverseCandidates = targets.flatMap(withTPTDescendants);
660
718
  for (const targetMeta of inverseCandidates) {
661
719
  for (const inverseProp of Object.values(targetMeta.properties)) {
662
720
  if (inverseProp.kind === ReferenceKind.MANY_TO_MANY &&
663
721
  inverseProp.mappedBy === prop.name &&
664
- !inverseProp.pivotEntity) {
722
+ !inverseProp.pivotEntity &&
723
+ // a descendant can declare its own inverse of another owner's same-named M:N
724
+ targets.some(target => target.properties[inverseProp.name]?.mappedBy === prop.name) &&
725
+ // the target itself can carry an inherited inverse of a parent M:N it redeclares
726
+ (inverseProp.targetMeta?.properties[prop.name]?.pivotTable ?? prop.pivotTable) === prop.pivotTable) {
665
727
  inverseProp.pivotEntity = pivotMeta.class;
666
728
  inverseProp.pivotTable = pivotMeta.tableName;
667
729
  }
@@ -832,15 +894,20 @@ export class MetadataDiscovery {
832
894
  prop2.inverseJoinColumns = prop.joinColumns;
833
895
  prop2.joinColumns = prop.inverseJoinColumns;
834
896
  }
835
- // propagate updated joinColumns to all entities of the hierarchy that inherit this property or its inverse (STI)
836
- for (const childMeta of this.#discovered.filter(m => m.root === baseMeta)) {
897
+ // propagate updated joinColumns to all entities that inherit this property or its inverse (STI, or TPT sharing the pivot)
898
+ const inherits = (m) => m.root === baseMeta || (m.inheritanceType === 'tpt' && m.root === meta.root);
899
+ // includes `meta` itself: an STI root carries the inverse copy of an M:N declared on a child
900
+ for (const childMeta of this.#discovered.filter(inherits)) {
837
901
  const childProp = childMeta.properties[prop.name];
838
- if (childProp) {
902
+ // a TPT subclass redeclaring its own pivot table keeps its own columns
903
+ const sharesPivot = childMeta.inheritanceType !== 'tpt' || childProp?.pivotTable === prop.pivotTable;
904
+ if (childProp && sharesPivot) {
839
905
  childProp.joinColumns = prop.joinColumns;
840
906
  childProp.inverseJoinColumns = prop.inverseJoinColumns;
841
907
  }
842
908
  const childProp2 = prop.inversedBy ? childMeta.properties[prop.inversedBy] : undefined;
843
- if (childProp2) {
909
+ // the inherited inverse can stay on this pivot even when the child redeclares the owner side
910
+ if (childProp2 && (childMeta.inheritanceType !== 'tpt' || childProp2.pivotTable === prop.pivotTable)) {
844
911
  childProp2.inverseJoinColumns = prop.joinColumns;
845
912
  childProp2.joinColumns = prop.inverseJoinColumns;
846
913
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
- "version": "7.3.0-dev.18",
3
+ "version": "7.3.0-dev.19",
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
@@ -153,7 +153,7 @@ export function parseJsonSafe(value) {
153
153
  /** Collection of general-purpose utility methods used throughout the ORM. */
154
154
  export class Utils {
155
155
  static PK_SEPARATOR = '~~~';
156
- static #ORM_VERSION = '7.3.0-dev.18';
156
+ static #ORM_VERSION = '7.3.0-dev.19';
157
157
  /** Default session variable name backing an RLS filter argument (`current_setting('mikro.<filter>.<arg>')`). */
158
158
  static getRlsSettingName(filterName, argName) {
159
159
  return `mikro.${filterName}.${argName}`;