@mikro-orm/core 7.0.0-dev.225 → 7.0.0-dev.227
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 +8 -2
- package/cache/FileCacheAdapter.js +9 -1
- package/drivers/DatabaseDriver.js +38 -0
- package/entity/EntityHelper.js +13 -1
- package/entity/EntityLoader.d.ts +1 -0
- package/entity/EntityLoader.js +123 -25
- package/entity/PolymorphicRef.d.ts +12 -0
- package/entity/PolymorphicRef.js +18 -0
- package/entity/defineEntity.d.ts +8 -4
- package/entity/defineEntity.js +8 -0
- package/entity/index.d.ts +1 -0
- package/entity/index.js +1 -0
- package/errors.d.ts +3 -2
- package/errors.js +9 -4
- package/hydration/ObjectHydrator.js +40 -8
- package/metadata/EntitySchema.d.ts +1 -1
- package/metadata/MetadataDiscovery.d.ts +22 -0
- package/metadata/MetadataDiscovery.js +222 -11
- package/metadata/MetadataValidator.d.ts +6 -0
- package/metadata/MetadataValidator.js +76 -3
- package/metadata/types.d.ts +21 -5
- package/naming-strategy/AbstractNamingStrategy.d.ts +4 -0
- package/naming-strategy/AbstractNamingStrategy.js +6 -0
- package/naming-strategy/NamingStrategy.d.ts +4 -0
- package/package.json +1 -1
- package/typings.d.ts +20 -6
- package/typings.js +5 -1
- package/unit-of-work/ChangeSetComputer.js +14 -4
- package/unit-of-work/ChangeSetPersister.js +7 -0
- package/unit-of-work/UnitOfWork.js +13 -2
- package/utils/EntityComparator.d.ts +5 -0
- package/utils/EntityComparator.js +66 -12
- package/utils/QueryHelper.d.ts +4 -0
- package/utils/QueryHelper.js +10 -1
- package/utils/Utils.d.ts +1 -1
- package/utils/Utils.js +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { EntityMetadata, } from '../typings.js';
|
|
2
2
|
import { compareArrays, Utils } from '../utils/Utils.js';
|
|
3
|
+
import { QueryHelper } from '../utils/QueryHelper.js';
|
|
3
4
|
import { MetadataValidator } from './MetadataValidator.js';
|
|
4
5
|
import { MetadataProvider } from './MetadataProvider.js';
|
|
5
6
|
import { MetadataStorage } from './MetadataStorage.js';
|
|
@@ -131,6 +132,7 @@ export class MetadataDiscovery {
|
|
|
131
132
|
filtered.forEach(meta => Object.values(meta.properties).forEach(prop => cb(meta, prop)));
|
|
132
133
|
};
|
|
133
134
|
forEachProp((m, p) => this.initFactoryField(m, p));
|
|
135
|
+
forEachProp((m, p) => this.initPolymorphicRelation(m, p, filtered));
|
|
134
136
|
forEachProp((_m, p) => this.initRelation(p));
|
|
135
137
|
forEachProp((m, p) => this.initEmbeddables(m, p));
|
|
136
138
|
forEachProp((_m, p) => this.initFieldName(p));
|
|
@@ -351,6 +353,12 @@ export class MetadataDiscovery {
|
|
|
351
353
|
if (!prop.joinColumns || !prop.columnTypes || prop.ownColumns || ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
352
354
|
continue;
|
|
353
355
|
}
|
|
356
|
+
// For polymorphic relations, ownColumns should include all fieldNames
|
|
357
|
+
// (discriminator + ID columns) since they are all owned by this relation
|
|
358
|
+
if (prop.polymorphic) {
|
|
359
|
+
prop.ownColumns = prop.fieldNames;
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
354
362
|
if (prop.joinColumns.length > 1) {
|
|
355
363
|
prop.ownColumns = prop.joinColumns.filter(col => {
|
|
356
364
|
return !meta.props.find(p => p.name !== prop.name && (!p.fieldNames || p.fieldNames.includes(col)));
|
|
@@ -381,7 +389,7 @@ export class MetadataDiscovery {
|
|
|
381
389
|
if (prop.kind === ReferenceKind.SCALAR || prop.kind === ReferenceKind.EMBEDDED) {
|
|
382
390
|
prop.fieldNames = [this.namingStrategy.propertyToColumnName(prop.name, object)];
|
|
383
391
|
}
|
|
384
|
-
else if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
392
|
+
else if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.polymorphic) {
|
|
385
393
|
prop.fieldNames = this.initManyToOneFieldName(prop, prop.name);
|
|
386
394
|
}
|
|
387
395
|
else if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
|
|
@@ -436,17 +444,36 @@ export class MetadataDiscovery {
|
|
|
436
444
|
prop.fixedOrderColumn = prop2.fixedOrderColumn;
|
|
437
445
|
prop.joinColumns = prop2.inverseJoinColumns;
|
|
438
446
|
prop.inverseJoinColumns = prop2.joinColumns;
|
|
447
|
+
prop.polymorphic = prop2.polymorphic;
|
|
448
|
+
prop.discriminator = prop2.discriminator;
|
|
449
|
+
prop.discriminatorColumn = prop2.discriminatorColumn;
|
|
450
|
+
prop.discriminatorValue = prop2.discriminatorValue;
|
|
439
451
|
}
|
|
440
452
|
prop.referencedColumnNames ??= Utils.flatten(meta.primaryKeys.map(primaryKey => meta.properties[primaryKey].fieldNames));
|
|
441
|
-
|
|
453
|
+
// For polymorphic M:N, use discriminator base name for FK column (e.g., taggable_id instead of post_id)
|
|
454
|
+
if (prop.polymorphic && prop.discriminator) {
|
|
455
|
+
prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.namingStrategy.joinKeyColumnName(prop.discriminator, referencedColumnName, prop.referencedColumnNames.length > 1));
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
const ownerTableName = this.isExplicitTableName(meta.root) ? meta.root.tableName : undefined;
|
|
459
|
+
prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.namingStrategy.joinKeyColumnName(meta.root.className, referencedColumnName, meta.compositePK, ownerTableName));
|
|
460
|
+
}
|
|
442
461
|
const inverseTableName = this.isExplicitTableName(meta2.root) ? meta2.root.tableName : undefined;
|
|
443
|
-
prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.namingStrategy.joinKeyColumnName(meta.root.className, referencedColumnName, meta.compositePK, ownerTableName));
|
|
444
462
|
prop.inverseJoinColumns ??= this.initManyToOneFieldName(prop, meta2.root.className, inverseTableName);
|
|
445
463
|
}
|
|
446
464
|
isExplicitTableName(meta) {
|
|
447
465
|
return meta.tableName !== this.namingStrategy.classToTableName(meta.className);
|
|
448
466
|
}
|
|
449
467
|
initManyToOneFields(prop) {
|
|
468
|
+
if (prop.polymorphic && prop.polymorphTargets) {
|
|
469
|
+
const fieldNames1 = prop.targetMeta.getPrimaryProps().flatMap(pk => pk.fieldNames);
|
|
470
|
+
const idColumns = fieldNames1.map(fieldName => this.namingStrategy.joinKeyColumnName(prop.discriminator, fieldName, fieldNames1.length > 1));
|
|
471
|
+
prop.fieldNames ??= [prop.discriminatorColumn, ...idColumns];
|
|
472
|
+
prop.joinColumns ??= idColumns;
|
|
473
|
+
prop.referencedColumnNames ??= fieldNames1;
|
|
474
|
+
prop.referencedTableName ??= prop.targetMeta.tableName;
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
450
477
|
const meta2 = prop.targetMeta;
|
|
451
478
|
let fieldNames;
|
|
452
479
|
// If targetKey is specified, use that property's field names instead of PKs
|
|
@@ -605,6 +632,18 @@ export class MetadataDiscovery {
|
|
|
605
632
|
if (pivotMeta) {
|
|
606
633
|
prop.pivotEntity = pivotMeta.class;
|
|
607
634
|
this.ensureCorrectFKOrderInPivotEntity(pivotMeta, prop);
|
|
635
|
+
if (prop.polymorphic && prop.discriminatorValue) {
|
|
636
|
+
pivotMeta.polymorphicDiscriminatorMap ??= {};
|
|
637
|
+
pivotMeta.polymorphicDiscriminatorMap[prop.discriminatorValue] = meta.class;
|
|
638
|
+
// For composite PK entities sharing a polymorphic pivot table,
|
|
639
|
+
// we need to add columns for each entity type's PKs
|
|
640
|
+
this.addPolymorphicPivotColumns(pivotMeta, meta, prop);
|
|
641
|
+
// Add virtual M:1 relation for this polymorphic owner (for join loading)
|
|
642
|
+
const ownerRelationName = `${prop.discriminator}_${meta.tableName}`;
|
|
643
|
+
if (!pivotMeta.properties[ownerRelationName]) {
|
|
644
|
+
pivotMeta.properties[ownerRelationName] = this.definePolymorphicOwnerRelation(prop, ownerRelationName, meta);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
608
647
|
return pivotMeta;
|
|
609
648
|
}
|
|
610
649
|
let tableName = prop.pivotTable;
|
|
@@ -650,10 +689,127 @@ export class MetadataDiscovery {
|
|
|
650
689
|
}
|
|
651
690
|
}
|
|
652
691
|
}
|
|
653
|
-
|
|
654
|
-
|
|
692
|
+
// For polymorphic M:N, create discriminator column and polymorphic FK
|
|
693
|
+
if (prop.polymorphic && prop.discriminatorColumn) {
|
|
694
|
+
this.definePolymorphicPivotProperties(pivotMeta2, meta, prop, targetMeta);
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
pivotMeta2.properties[meta.name + '_owner'] = this.definePivotProperty(prop, meta.name + '_owner', meta.class, targetType + '_inverse', true, meta.className === targetType);
|
|
698
|
+
pivotMeta2.properties[targetType + '_inverse'] = this.definePivotProperty(prop, targetType + '_inverse', targetMeta.class, meta.name + '_owner', false, meta.className === targetType);
|
|
699
|
+
}
|
|
655
700
|
return this.metadata.set(pivotMeta2.class, EntitySchema.fromMetadata(pivotMeta2).init().meta);
|
|
656
701
|
}
|
|
702
|
+
/**
|
|
703
|
+
* Create a scalar property for a pivot table column.
|
|
704
|
+
*/
|
|
705
|
+
createPivotScalarProperty(name, columnTypes, fieldNames = [name], options = {}) {
|
|
706
|
+
return {
|
|
707
|
+
name,
|
|
708
|
+
fieldNames,
|
|
709
|
+
columnTypes,
|
|
710
|
+
type: options.type ?? 'number',
|
|
711
|
+
kind: ReferenceKind.SCALAR,
|
|
712
|
+
primary: options.primary ?? false,
|
|
713
|
+
nullable: options.nullable ?? true,
|
|
714
|
+
...(options.persist !== undefined && { persist: options.persist }),
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Get column types for an entity's primary keys, initializing them if needed.
|
|
719
|
+
*/
|
|
720
|
+
getPrimaryKeyColumnTypes(meta) {
|
|
721
|
+
const columnTypes = [];
|
|
722
|
+
for (const pk of meta.primaryKeys) {
|
|
723
|
+
const pkProp = meta.properties[pk];
|
|
724
|
+
this.initCustomType(meta, pkProp);
|
|
725
|
+
this.initColumnType(pkProp);
|
|
726
|
+
columnTypes.push(...pkProp.columnTypes);
|
|
727
|
+
}
|
|
728
|
+
return columnTypes;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Add missing FK columns for a polymorphic entity to an existing pivot table.
|
|
732
|
+
*/
|
|
733
|
+
addPolymorphicPivotColumns(pivotMeta, meta, prop) {
|
|
734
|
+
const existingFieldNames = new Set(Object.values(pivotMeta.properties).flatMap(p => p.fieldNames ?? []));
|
|
735
|
+
const columnTypes = this.getPrimaryKeyColumnTypes(meta);
|
|
736
|
+
for (let i = 0; i < prop.joinColumns.length; i++) {
|
|
737
|
+
const joinColumn = prop.joinColumns[i];
|
|
738
|
+
if (!existingFieldNames.has(joinColumn)) {
|
|
739
|
+
pivotMeta.properties[joinColumn] = this.createPivotScalarProperty(joinColumn, [columnTypes[i]]);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Define properties for a polymorphic pivot table.
|
|
745
|
+
*/
|
|
746
|
+
definePolymorphicPivotProperties(pivotMeta, meta, prop, targetMeta) {
|
|
747
|
+
const discriminatorColumn = prop.discriminatorColumn;
|
|
748
|
+
const isCompositePK = meta.compositePK;
|
|
749
|
+
// For composite PK polymorphic M:N, we need fixedOrder (auto-increment PK)
|
|
750
|
+
if (isCompositePK && !prop.fixedOrder) {
|
|
751
|
+
prop.fixedOrder = true;
|
|
752
|
+
const primaryProp = this.defineFixedOrderProperty(prop, targetMeta);
|
|
753
|
+
pivotMeta.properties[primaryProp.name] = primaryProp;
|
|
754
|
+
pivotMeta.compositePK = false;
|
|
755
|
+
}
|
|
756
|
+
const discriminatorProp = this.createPivotScalarProperty(discriminatorColumn, [this.platform.getVarcharTypeDeclarationSQL(prop)], [discriminatorColumn], { type: 'string', primary: !isCompositePK, nullable: false });
|
|
757
|
+
this.initFieldName(discriminatorProp);
|
|
758
|
+
pivotMeta.properties[discriminatorColumn] = discriminatorProp;
|
|
759
|
+
const columnTypes = this.getPrimaryKeyColumnTypes(meta);
|
|
760
|
+
if (isCompositePK) {
|
|
761
|
+
// Create separate properties for each PK column (nullable for other entity types)
|
|
762
|
+
for (let i = 0; i < prop.joinColumns.length; i++) {
|
|
763
|
+
pivotMeta.properties[prop.joinColumns[i]] = this.createPivotScalarProperty(prop.joinColumns[i], [columnTypes[i]]);
|
|
764
|
+
}
|
|
765
|
+
// Virtual property combining all columns (for compatibility)
|
|
766
|
+
pivotMeta.properties[prop.discriminator] = this.createPivotScalarProperty(prop.discriminator, columnTypes, [...prop.joinColumns], { type: meta.className, persist: false });
|
|
767
|
+
}
|
|
768
|
+
else {
|
|
769
|
+
pivotMeta.properties[prop.discriminator] = this.createPivotScalarProperty(prop.discriminator, columnTypes, [...prop.joinColumns], { type: meta.className, primary: true, nullable: false });
|
|
770
|
+
}
|
|
771
|
+
pivotMeta.properties[targetMeta.className + '_inverse'] = this.definePivotProperty(prop, targetMeta.className + '_inverse', targetMeta.class, prop.discriminator, false, false);
|
|
772
|
+
// Create virtual M:1 relation to the polymorphic owner for single-query join loading
|
|
773
|
+
const ownerRelationName = `${prop.discriminator}_${meta.tableName}`;
|
|
774
|
+
pivotMeta.properties[ownerRelationName] = this.definePolymorphicOwnerRelation(prop, ownerRelationName, meta);
|
|
775
|
+
pivotMeta.polymorphicDiscriminatorMap ??= {};
|
|
776
|
+
pivotMeta.polymorphicDiscriminatorMap[prop.discriminatorValue] = meta.class;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Create a virtual M:1 relation from pivot to a polymorphic owner entity.
|
|
780
|
+
* This enables single-query join loading for inverse-side polymorphic M:N.
|
|
781
|
+
*/
|
|
782
|
+
definePolymorphicOwnerRelation(prop, name, targetMeta) {
|
|
783
|
+
const ret = {
|
|
784
|
+
name,
|
|
785
|
+
type: targetMeta.className,
|
|
786
|
+
target: targetMeta.class,
|
|
787
|
+
kind: ReferenceKind.MANY_TO_ONE,
|
|
788
|
+
nullable: true,
|
|
789
|
+
owner: true,
|
|
790
|
+
primary: false,
|
|
791
|
+
createForeignKeyConstraint: false,
|
|
792
|
+
persist: false,
|
|
793
|
+
index: false,
|
|
794
|
+
};
|
|
795
|
+
ret.targetMeta = targetMeta;
|
|
796
|
+
ret.fieldNames = ret.joinColumns = ret.ownColumns = [...prop.joinColumns];
|
|
797
|
+
ret.referencedColumnNames = [];
|
|
798
|
+
ret.inverseJoinColumns = [];
|
|
799
|
+
for (const primaryKey of targetMeta.primaryKeys) {
|
|
800
|
+
const pkProp = targetMeta.properties[primaryKey];
|
|
801
|
+
ret.referencedColumnNames.push(...pkProp.fieldNames);
|
|
802
|
+
ret.inverseJoinColumns.push(...pkProp.fieldNames);
|
|
803
|
+
ret.length = pkProp.length;
|
|
804
|
+
ret.precision = pkProp.precision;
|
|
805
|
+
ret.scale = pkProp.scale;
|
|
806
|
+
}
|
|
807
|
+
const schema = targetMeta.schema ?? this.config.get('schema') ?? this.platform.getDefaultSchemaName();
|
|
808
|
+
ret.referencedTableName = schema && schema !== '*' ? schema + '.' + targetMeta.tableName : targetMeta.tableName;
|
|
809
|
+
this.initColumnType(ret);
|
|
810
|
+
this.initRelation(ret);
|
|
811
|
+
return ret;
|
|
812
|
+
}
|
|
657
813
|
defineFixedOrderProperty(prop, targetMeta) {
|
|
658
814
|
const pk = prop.fixedOrderColumn || this.namingStrategy.referenceColumnName();
|
|
659
815
|
const primaryProp = {
|
|
@@ -833,6 +989,54 @@ export class MetadataDiscovery {
|
|
|
833
989
|
polymorphs.forEach(meta => meta.root = embeddable);
|
|
834
990
|
}
|
|
835
991
|
}
|
|
992
|
+
initPolymorphicRelation(meta, prop, discovered) {
|
|
993
|
+
if (!prop.discriminator && !prop.discriminatorColumn && !prop.discriminatorMap && !Array.isArray(prop.target)) {
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
prop.polymorphic = true;
|
|
997
|
+
prop.discriminator ??= prop.name;
|
|
998
|
+
prop.discriminatorColumn ??= this.namingStrategy.discriminatorColumnName(prop.discriminator);
|
|
999
|
+
prop.createForeignKeyConstraint = false;
|
|
1000
|
+
const isToOne = [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind);
|
|
1001
|
+
if (isToOne) {
|
|
1002
|
+
const types = prop.type.split(/ ?\| ?/);
|
|
1003
|
+
prop.polymorphTargets = discovered.filter(m => types.includes(m.className) && !m.embeddable);
|
|
1004
|
+
prop.targetMeta = prop.polymorphTargets[0];
|
|
1005
|
+
prop.referencedPKs = prop.targetMeta?.primaryKeys;
|
|
1006
|
+
}
|
|
1007
|
+
if (prop.discriminatorMap) {
|
|
1008
|
+
const normalizedMap = {};
|
|
1009
|
+
for (const [key, value] of Object.entries(prop.discriminatorMap)) {
|
|
1010
|
+
const targetMeta = this.metadata.getByClassName(value, false);
|
|
1011
|
+
if (!targetMeta) {
|
|
1012
|
+
throw MetadataError.fromUnknownEntity(value, `${meta.className}.${prop.name} discriminatorMap`);
|
|
1013
|
+
}
|
|
1014
|
+
normalizedMap[key] = targetMeta.class;
|
|
1015
|
+
if (targetMeta.class === meta.class) {
|
|
1016
|
+
prop.discriminatorValue = key;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
prop.discriminatorMap = normalizedMap;
|
|
1020
|
+
}
|
|
1021
|
+
else if (isToOne) {
|
|
1022
|
+
prop.discriminatorMap = {};
|
|
1023
|
+
const tableNameToTarget = new Map();
|
|
1024
|
+
for (const target of prop.polymorphTargets) {
|
|
1025
|
+
const existing = tableNameToTarget.get(target.tableName);
|
|
1026
|
+
if (existing) {
|
|
1027
|
+
throw MetadataError.incompatiblePolymorphicTargets(meta, prop, existing, target, `both use table '${target.tableName}'. Use separate properties instead of a single polymorphic relation.`);
|
|
1028
|
+
}
|
|
1029
|
+
tableNameToTarget.set(target.tableName, target);
|
|
1030
|
+
prop.discriminatorMap[target.tableName] = target.class;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
else {
|
|
1034
|
+
prop.discriminatorValue ??= meta.tableName;
|
|
1035
|
+
if (!prop.discriminatorMap) {
|
|
1036
|
+
prop.discriminatorMap = { [prop.discriminatorValue]: meta.class };
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
836
1040
|
initEmbeddables(meta, embeddedProp, visited = new Set()) {
|
|
837
1041
|
if (embeddedProp.kind !== ReferenceKind.EMBEDDED || visited.has(embeddedProp)) {
|
|
838
1042
|
return;
|
|
@@ -963,7 +1167,7 @@ export class MetadataDiscovery {
|
|
|
963
1167
|
meta.root.discriminatorMap[name] = m.class;
|
|
964
1168
|
}
|
|
965
1169
|
}
|
|
966
|
-
meta.discriminatorValue =
|
|
1170
|
+
meta.discriminatorValue = QueryHelper.findDiscriminatorValue(meta.root.discriminatorMap, meta.class);
|
|
967
1171
|
if (!meta.root.properties[meta.root.discriminatorColumn]) {
|
|
968
1172
|
this.createDiscriminatorProperty(meta.root);
|
|
969
1173
|
}
|
|
@@ -1245,7 +1449,7 @@ export class MetadataDiscovery {
|
|
|
1245
1449
|
if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !isArray) {
|
|
1246
1450
|
prop.type = prop.customType.name;
|
|
1247
1451
|
}
|
|
1248
|
-
if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && prop.targetMeta.compositePK) {
|
|
1452
|
+
if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && !prop.polymorphic && prop.targetMeta.compositePK) {
|
|
1249
1453
|
prop.customTypes = [];
|
|
1250
1454
|
for (const pk of prop.targetMeta.getPrimaryProps()) {
|
|
1251
1455
|
if (pk.customType) {
|
|
@@ -1274,7 +1478,7 @@ export class MetadataDiscovery {
|
|
|
1274
1478
|
}
|
|
1275
1479
|
}
|
|
1276
1480
|
initRelation(prop) {
|
|
1277
|
-
if (prop.kind === ReferenceKind.SCALAR) {
|
|
1481
|
+
if (prop.kind === ReferenceKind.SCALAR || prop.polymorphTargets) {
|
|
1278
1482
|
return;
|
|
1279
1483
|
}
|
|
1280
1484
|
// when the target is a polymorphic embedded entity, `prop.target` is an array of classes, we need to get the metadata by the type name instead
|
|
@@ -1285,8 +1489,13 @@ export class MetadataDiscovery {
|
|
|
1285
1489
|
if (meta2.view) {
|
|
1286
1490
|
prop.createForeignKeyConstraint = false;
|
|
1287
1491
|
}
|
|
1492
|
+
// Auto-generate formula for persist: false relations, but only for single-column FKs
|
|
1493
|
+
// Composite FK relations need standard JOIN conditions, not formula-based
|
|
1288
1494
|
if (!prop.formula && prop.persist === false && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.embedded) {
|
|
1289
|
-
|
|
1495
|
+
this.initFieldName(prop);
|
|
1496
|
+
if (prop.fieldNames?.length === 1) {
|
|
1497
|
+
prop.formula = table => `${table}.${this.platform.quoteIdentifier(prop.fieldNames[0])}`;
|
|
1498
|
+
}
|
|
1290
1499
|
}
|
|
1291
1500
|
}
|
|
1292
1501
|
initColumnType(prop) {
|
|
@@ -1335,6 +1544,9 @@ export class MetadataDiscovery {
|
|
|
1335
1544
|
const referencedProps = prop.targetKey
|
|
1336
1545
|
? [targetMeta.properties[prop.targetKey]]
|
|
1337
1546
|
: targetMeta.getPrimaryProps();
|
|
1547
|
+
if (prop.polymorphic && prop.polymorphTargets) {
|
|
1548
|
+
prop.columnTypes.push(this.platform.getVarcharTypeDeclarationSQL(prop));
|
|
1549
|
+
}
|
|
1338
1550
|
for (const referencedProp of referencedProps) {
|
|
1339
1551
|
this.initCustomType(targetMeta, referencedProp);
|
|
1340
1552
|
this.initColumnType(referencedProp);
|
|
@@ -1383,8 +1595,7 @@ export class MetadataDiscovery {
|
|
|
1383
1595
|
return;
|
|
1384
1596
|
}
|
|
1385
1597
|
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
1386
|
-
|
|
1387
|
-
prop.unsigned = meta2.getPrimaryProps().some(pk => {
|
|
1598
|
+
prop.unsigned = prop.targetMeta.getPrimaryProps().some(pk => {
|
|
1388
1599
|
this.initUnsigned(pk);
|
|
1389
1600
|
return pk.unsigned;
|
|
1390
1601
|
});
|
|
@@ -9,6 +9,12 @@ export declare class MetadataValidator {
|
|
|
9
9
|
validateDiscovered(discovered: EntityMetadata[], options: MetadataDiscoveryOptions): void;
|
|
10
10
|
private validateReference;
|
|
11
11
|
private validateTargetKey;
|
|
12
|
+
/**
|
|
13
|
+
* Checks if a property has a unique constraint (either via `unique: true` or single-property `@Unique` decorator).
|
|
14
|
+
* Composite unique constraints are not sufficient for targetKey.
|
|
15
|
+
*/
|
|
16
|
+
private isPropertyUnique;
|
|
17
|
+
private validatePolymorphicTargets;
|
|
12
18
|
private validateBidirectional;
|
|
13
19
|
private validateOwningSide;
|
|
14
20
|
private validateInverseSide;
|
|
@@ -107,6 +107,11 @@ export class MetadataValidator {
|
|
|
107
107
|
if (!prop.type) {
|
|
108
108
|
throw MetadataError.fromWrongTypeDefinition(meta, prop);
|
|
109
109
|
}
|
|
110
|
+
// Polymorphic relations have multiple targets, validate PK compatibility
|
|
111
|
+
if (prop.polymorphic && prop.polymorphTargets) {
|
|
112
|
+
this.validatePolymorphicTargets(meta, prop);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
110
115
|
const targetMeta = prop.targetMeta;
|
|
111
116
|
// references do have type of known entity
|
|
112
117
|
if (!targetMeta) {
|
|
@@ -133,11 +138,56 @@ export class MetadataValidator {
|
|
|
133
138
|
if (!targetProp) {
|
|
134
139
|
throw MetadataError.targetKeyNotFound(meta, prop);
|
|
135
140
|
}
|
|
136
|
-
// targetKey must point to a unique property
|
|
137
|
-
if (!targetProp
|
|
141
|
+
// targetKey must point to a unique property (composite unique is not sufficient)
|
|
142
|
+
if (!this.isPropertyUnique(targetProp, targetMeta)) {
|
|
138
143
|
throw MetadataError.targetKeyNotUnique(meta, prop);
|
|
139
144
|
}
|
|
140
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Checks if a property has a unique constraint (either via `unique: true` or single-property `@Unique` decorator).
|
|
148
|
+
* Composite unique constraints are not sufficient for targetKey.
|
|
149
|
+
*/
|
|
150
|
+
isPropertyUnique(prop, meta) {
|
|
151
|
+
if (prop.unique) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
// Check for single-property unique constraint via @Unique decorator
|
|
155
|
+
return !!meta.uniques?.some(u => {
|
|
156
|
+
const props = Utils.asArray(u.properties);
|
|
157
|
+
return props.length === 1 && props[0] === prop.name && !u.options;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
validatePolymorphicTargets(meta, prop) {
|
|
161
|
+
const targets = prop.polymorphTargets;
|
|
162
|
+
// Validate targetKey exists and is compatible across all targets
|
|
163
|
+
if (prop.targetKey) {
|
|
164
|
+
for (const target of targets) {
|
|
165
|
+
const targetProp = target.properties[prop.targetKey];
|
|
166
|
+
if (!targetProp) {
|
|
167
|
+
throw MetadataError.targetKeyNotFound(meta, prop, target);
|
|
168
|
+
}
|
|
169
|
+
// targetKey must point to a unique property (composite unique is not sufficient)
|
|
170
|
+
if (!this.isPropertyUnique(targetProp, target)) {
|
|
171
|
+
throw MetadataError.targetKeyNotUnique(meta, prop, target);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
const firstPKs = targets[0].getPrimaryProps();
|
|
176
|
+
for (let i = 1; i < targets.length; i++) {
|
|
177
|
+
const target = targets[i];
|
|
178
|
+
const targetPKs = target.getPrimaryProps();
|
|
179
|
+
if (targetPKs.length !== firstPKs.length) {
|
|
180
|
+
throw MetadataError.incompatiblePolymorphicTargets(meta, prop, targets[0], target, 'different number of primary keys');
|
|
181
|
+
}
|
|
182
|
+
for (let j = 0; j < firstPKs.length; j++) {
|
|
183
|
+
const firstPK = firstPKs[j];
|
|
184
|
+
const targetPK = targetPKs[j];
|
|
185
|
+
if (firstPK.runtimeType !== targetPK.runtimeType) {
|
|
186
|
+
throw MetadataError.incompatiblePolymorphicTargets(meta, prop, targets[0], target, `incompatible primary key types: ${firstPK.name} (${firstPK.runtimeType}) vs ${targetPK.name} (${targetPK.runtimeType})`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
141
191
|
validateBidirectional(meta, prop) {
|
|
142
192
|
if (prop.inversedBy) {
|
|
143
193
|
this.validateOwningSide(meta, prop);
|
|
@@ -151,6 +201,27 @@ export class MetadataValidator {
|
|
|
151
201
|
}
|
|
152
202
|
}
|
|
153
203
|
validateOwningSide(meta, prop) {
|
|
204
|
+
// For polymorphic relations, inversedBy may point to multiple entity types
|
|
205
|
+
if (prop.polymorphic && prop.polymorphTargets?.length) {
|
|
206
|
+
// For polymorphic relations, validate inversedBy against each target
|
|
207
|
+
// The inverse property should exist on the target entities and reference back to this property
|
|
208
|
+
for (const targetMeta of prop.polymorphTargets) {
|
|
209
|
+
const inverse = targetMeta.properties[prop.inversedBy];
|
|
210
|
+
// The inverse property is optional - some targets may not have it
|
|
211
|
+
if (!inverse) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
// Validate the inverse property
|
|
215
|
+
if (inverse.targetMeta?.root.class !== meta.root.class) {
|
|
216
|
+
throw MetadataError.fromWrongReference(meta, prop, 'inversedBy', inverse);
|
|
217
|
+
}
|
|
218
|
+
// inverse side is not defined as owner
|
|
219
|
+
if (inverse.inversedBy || inverse.owner) {
|
|
220
|
+
throw MetadataError.fromWrongOwnership(meta, prop, 'inversedBy');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
154
225
|
const inverse = prop.targetMeta.properties[prop.inversedBy];
|
|
155
226
|
// has correct `inversedBy` on owning side
|
|
156
227
|
if (!inverse) {
|
|
@@ -173,7 +244,9 @@ export class MetadataValidator {
|
|
|
173
244
|
throw MetadataError.fromWrongReference(meta, prop, 'mappedBy');
|
|
174
245
|
}
|
|
175
246
|
// has correct `mappedBy` reference type
|
|
176
|
-
if
|
|
247
|
+
// For polymorphic relations, check if this entity is one of the polymorphic targets
|
|
248
|
+
const isValidPolymorphicInverse = owner.polymorphic && owner.polymorphTargets?.some(target => target.class === meta.root.class);
|
|
249
|
+
if (!isValidPolymorphicInverse && owner.type !== meta.className && owner.targetMeta?.root.class !== meta.root.class) {
|
|
177
250
|
throw MetadataError.fromWrongReference(meta, prop, 'mappedBy', owner);
|
|
178
251
|
}
|
|
179
252
|
// owning side is not defined as inverse
|
package/metadata/types.d.ts
CHANGED
|
@@ -321,8 +321,8 @@ export interface PropertyOptions<Owner> {
|
|
|
321
321
|
ignoreSchemaChanges?: ('type' | 'extra' | 'default')[];
|
|
322
322
|
}
|
|
323
323
|
export interface ReferenceOptions<Owner, Target> extends PropertyOptions<Owner> {
|
|
324
|
-
/** Set target entity type. */
|
|
325
|
-
entity?: () => EntityName<Target
|
|
324
|
+
/** Set target entity type. For polymorphic relations, pass an array of entity types. */
|
|
325
|
+
entity?: () => EntityName<Target> | EntityName<Target>[];
|
|
326
326
|
/** Set what actions on owning entity should be cascaded to the relationship. Defaults to [Cascade.PERSIST, Cascade.MERGE] (see {@doclink cascading}). */
|
|
327
327
|
cascade?: Cascade[];
|
|
328
328
|
/** Always load the relationship. Discouraged for use with to-many relations for performance reasons. */
|
|
@@ -337,7 +337,20 @@ export interface ReferenceOptions<Owner, Target> extends PropertyOptions<Owner>
|
|
|
337
337
|
* @ignore
|
|
338
338
|
*/
|
|
339
339
|
export type ColumnType = 'int' | 'int4' | 'integer' | 'bigint' | 'int8' | 'int2' | 'tinyint' | 'smallint' | 'mediumint' | 'double' | 'double precision' | 'real' | 'float8' | 'decimal' | 'numeric' | 'float' | 'float4' | 'datetime' | 'time' | 'time with time zone' | 'timestamp' | 'timestamp with time zone' | 'timetz' | 'timestamptz' | 'date' | 'interval' | 'character varying' | 'varchar' | 'char' | 'character' | 'uuid' | 'text' | 'tinytext' | 'mediumtext' | 'longtext' | 'boolean' | 'bool' | 'bit' | 'enum' | 'blob' | 'tinyblob' | 'mediumblob' | 'longblob' | 'bytea' | 'point' | 'line' | 'lseg' | 'box' | 'circle' | 'path' | 'polygon' | 'geometry' | 'tsvector' | 'tsquery' | 'json' | 'jsonb';
|
|
340
|
-
|
|
340
|
+
interface PolymorphicOptions {
|
|
341
|
+
/**
|
|
342
|
+
* For polymorphic relations. Specifies the property name that stores the entity type discriminator.
|
|
343
|
+
* Defaults to the property name. Only used when `entity` returns an array of types.
|
|
344
|
+
* For M:N relations, this is the column name in the pivot table.
|
|
345
|
+
*/
|
|
346
|
+
discriminator?: string;
|
|
347
|
+
/**
|
|
348
|
+
* For polymorphic relations. Custom mapping of discriminator values to entity class names.
|
|
349
|
+
* If not provided, table names are used as discriminator values.
|
|
350
|
+
*/
|
|
351
|
+
discriminatorMap?: Dictionary<string>;
|
|
352
|
+
}
|
|
353
|
+
export interface ManyToOneOptions<Owner, Target> extends ReferenceOptions<Owner, Target>, PolymorphicOptions {
|
|
341
354
|
/** Point to the inverse side property name. */
|
|
342
355
|
inversedBy?: (string & keyof Target) | ((e: Target) => any);
|
|
343
356
|
/** Wrap the entity in {@apilink Reference} wrapper. */
|
|
@@ -391,7 +404,7 @@ export interface OneToManyOptions<Owner, Target> extends ReferenceOptions<Owner,
|
|
|
391
404
|
/** Point to the owning side property name. */
|
|
392
405
|
mappedBy: (string & keyof Target) | ((e: Target) => any);
|
|
393
406
|
}
|
|
394
|
-
export interface OneToOneOptions<Owner, Target> extends Partial<Omit<OneToManyOptions<Owner, Target>, 'orderBy'
|
|
407
|
+
export interface OneToOneOptions<Owner, Target> extends Partial<Omit<OneToManyOptions<Owner, Target>, 'orderBy'>>, PolymorphicOptions {
|
|
395
408
|
/** Set this side as owning. Owning side is where the foreign key is defined. This option is not required if you use `inversedBy` or `mappedBy` to distinguish owning and inverse side. */
|
|
396
409
|
owner?: boolean;
|
|
397
410
|
/** Point to the inverse side property name. */
|
|
@@ -417,7 +430,7 @@ export interface OneToOneOptions<Owner, Target> extends Partial<Omit<OneToManyOp
|
|
|
417
430
|
/** Enable/disable foreign key constraint creation on this relation */
|
|
418
431
|
createForeignKeyConstraint?: boolean;
|
|
419
432
|
}
|
|
420
|
-
export interface ManyToManyOptions<Owner, Target> extends ReferenceOptions<Owner, Target
|
|
433
|
+
export interface ManyToManyOptions<Owner, Target> extends ReferenceOptions<Owner, Target>, PolymorphicOptions {
|
|
421
434
|
/** Set this side as owning. Owning side is where the foreign key is defined. This option is not required if you use `inversedBy` or `mappedBy` to distinguish owning and inverse side. */
|
|
422
435
|
owner?: boolean;
|
|
423
436
|
/** Point to the inverse side property name. */
|
|
@@ -465,6 +478,9 @@ export interface EmbeddedOptions<Owner, Target> extends PropertyOptions<Owner> {
|
|
|
465
478
|
export interface EmbeddableOptions<Owner> {
|
|
466
479
|
/** Specify constructor parameters to be used in `em.create` or when `forceConstructor` is enabled. Those should be names of declared entity properties in the same order as your constructor uses them. The ORM tries to infer those automatically, use this option in case the inference fails. */
|
|
467
480
|
constructorParams?: (Owner extends EntityClass<infer P> ? keyof P : string)[];
|
|
481
|
+
/** For polymorphic embeddables. Specify the property name that stores the discriminator value. Alias for `discriminatorColumn`. */
|
|
482
|
+
discriminator?: (Owner extends EntityClass<infer P> ? keyof P : string) | AnyString;
|
|
483
|
+
/** For polymorphic embeddables. @deprecated Use `discriminator` instead. */
|
|
468
484
|
discriminatorColumn?: (Owner extends EntityClass<infer P> ? keyof P : string) | AnyString;
|
|
469
485
|
discriminatorMap?: Dictionary<string>;
|
|
470
486
|
discriminatorValue?: number | string;
|
|
@@ -30,6 +30,10 @@ export declare abstract class AbstractNamingStrategy implements NamingStrategy {
|
|
|
30
30
|
* @inheritDoc
|
|
31
31
|
*/
|
|
32
32
|
manyToManyPropertyName(ownerEntityName: string, targetEntityName: string, pivotTableName: string, ownerTableName: string, schemaName?: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* @inheritDoc
|
|
35
|
+
*/
|
|
36
|
+
discriminatorColumnName(baseName: string): string;
|
|
33
37
|
abstract classToTableName(entityName: string, tableName?: string): string;
|
|
34
38
|
abstract joinColumnName(propertyName: string): string;
|
|
35
39
|
abstract joinKeyColumnName(entityName: string, referencedColumnName?: string, composite?: boolean, tableName?: string): string;
|
|
@@ -85,4 +85,10 @@ export class AbstractNamingStrategy {
|
|
|
85
85
|
manyToManyPropertyName(ownerEntityName, targetEntityName, pivotTableName, ownerTableName, schemaName) {
|
|
86
86
|
return this.columnNameToProperty(pivotTableName.replace(new RegExp('^' + ownerTableName + '_'), ''));
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* @inheritDoc
|
|
90
|
+
*/
|
|
91
|
+
discriminatorColumnName(baseName) {
|
|
92
|
+
return this.propertyToColumnName(baseName + 'Type');
|
|
93
|
+
}
|
|
88
94
|
}
|
|
@@ -95,4 +95,8 @@ export interface NamingStrategy {
|
|
|
95
95
|
* @param schemaName - The schema name (if any)
|
|
96
96
|
*/
|
|
97
97
|
manyToManyPropertyName(ownerEntityName: string, targetEntityName: string, pivotTableName: string, ownerTableName: string, schemaName?: string): string;
|
|
98
|
+
/**
|
|
99
|
+
* Returns the discriminator column name for polymorphic relations.
|
|
100
|
+
*/
|
|
101
|
+
discriminatorColumnName(baseName: string): string;
|
|
98
102
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "7.0.0-dev.
|
|
4
|
+
"version": "7.0.0-dev.227",
|
|
5
5
|
"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.",
|
|
6
6
|
"exports": {
|
|
7
7
|
"./package.json": "./package.json",
|
package/typings.d.ts
CHANGED
|
@@ -295,7 +295,14 @@ export type EntityName<T = any> = EntityClass<T> | EntityCtor<T> | EntitySchema<
|
|
|
295
295
|
export type GetRepository<Entity extends {
|
|
296
296
|
[k: PropertyKey]: any;
|
|
297
297
|
}, Fallback> = Entity[typeof EntityRepositoryType] extends EntityRepository<any> | undefined ? NonNullable<Entity[typeof EntityRepositoryType]> : Fallback;
|
|
298
|
-
|
|
298
|
+
type PolymorphicPrimaryInner<T> = T extends object ? Primary<T> extends readonly [infer First, infer Second, ...infer Rest] ? readonly [string, First, Second, ...Rest] | [string, First, Second, ...Rest] : readonly [string, Primary<T>] | [string, Primary<T>] : never;
|
|
299
|
+
/**
|
|
300
|
+
* Tuple format for polymorphic FK values: [discriminator, ...pkValues]
|
|
301
|
+
* Distributes over unions, so `Post | Comment` becomes `['post', number] | ['comment', number]`
|
|
302
|
+
* For composite keys like [tenantId, orgId], becomes ['discriminator', tenantId, orgId]
|
|
303
|
+
*/
|
|
304
|
+
export type PolymorphicPrimary<T> = true extends IsUnion<T> ? PolymorphicPrimaryInner<T> : never;
|
|
305
|
+
export type EntityDataPropValue<T> = T | Primary<T> | PolymorphicPrimary<T>;
|
|
299
306
|
type ExpandEntityProp<T, C extends boolean = false> = T extends Record<string, any> ? {
|
|
300
307
|
[K in keyof T as CleanKeys<T, K>]?: EntityDataProp<ExpandProperty<T[K]>, C> | EntityDataPropValue<ExpandProperty<T[K]>> | null;
|
|
301
308
|
} | EntityDataPropValue<ExpandProperty<T>> : T;
|
|
@@ -339,9 +346,9 @@ export type EntityData<T, C extends boolean = false> = {
|
|
|
339
346
|
[K in EntityKey<T>]?: EntityDataItem<T[K] & {}, C>;
|
|
340
347
|
};
|
|
341
348
|
export type RequiredEntityData<T, I = never, C extends boolean = false> = {
|
|
342
|
-
[K in keyof T as RequiredKeys<T, K, I>]: T[K] | RequiredEntityDataProp<T[K], T, C> | Primary<T[K]> | Raw;
|
|
349
|
+
[K in keyof T as RequiredKeys<T, K, I>]: T[K] | RequiredEntityDataProp<T[K], T, C> | Primary<T[K]> | PolymorphicPrimary<T[K]> | Raw;
|
|
343
350
|
} & {
|
|
344
|
-
[K in keyof T as OptionalKeys<T, K, I>]?: T[K] | RequiredEntityDataProp<T[K], T, C> | Primary<T[K]> | Raw | null;
|
|
351
|
+
[K in keyof T as OptionalKeys<T, K, I>]?: T[K] | RequiredEntityDataProp<T[K], T, C> | Primary<T[K]> | PolymorphicPrimary<T[K]> | Raw | null;
|
|
345
352
|
};
|
|
346
353
|
export type EntityDictionary<T> = EntityData<T> & Record<any, any>;
|
|
347
354
|
type ExtractEagerProps<T> = T extends {
|
|
@@ -453,6 +460,11 @@ export interface EntityProperty<Owner = any, Target = any> {
|
|
|
453
460
|
embeddable: EntityClass<Owner>;
|
|
454
461
|
embeddedProps: Dictionary<EntityProperty>;
|
|
455
462
|
discriminatorColumn?: string;
|
|
463
|
+
discriminator?: string;
|
|
464
|
+
polymorphic?: boolean;
|
|
465
|
+
polymorphTargets?: EntityMetadata[];
|
|
466
|
+
discriminatorMap?: Dictionary<EntityClass<Target>>;
|
|
467
|
+
discriminatorValue?: string;
|
|
456
468
|
object?: boolean;
|
|
457
469
|
index?: boolean | string;
|
|
458
470
|
unique?: boolean | string;
|
|
@@ -614,6 +626,8 @@ export interface EntityMetadata<Entity = any, Class extends EntityCtor<Entity> =
|
|
|
614
626
|
polymorphs?: EntityMetadata[];
|
|
615
627
|
root: EntityMetadata<Entity>;
|
|
616
628
|
definedProperties: Dictionary;
|
|
629
|
+
/** For polymorphic M:N pivot tables, maps discriminator values to entity classes */
|
|
630
|
+
polymorphicDiscriminatorMap?: Dictionary<EntityClass>;
|
|
617
631
|
hasTriggers?: boolean;
|
|
618
632
|
/** @internal can be used for computed numeric cache keys */
|
|
619
633
|
readonly _id: number;
|
|
@@ -853,10 +867,10 @@ type ExtractStringKeys<T> = {
|
|
|
853
867
|
type StringKeys<T, E extends string = never> = T extends object ? ExtractStringKeys<ExtractType<T>> | E : never;
|
|
854
868
|
type GetStringKey<T, K extends StringKeys<T, string>, E extends string> = K extends keyof T ? ExtractType<T[K]> : (K extends E ? keyof T : never);
|
|
855
869
|
type Prev = [never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
856
|
-
type
|
|
857
|
-
[K in keyof T]-?: T
|
|
870
|
+
type RelationKeys<T> = T extends object ? {
|
|
871
|
+
[K in keyof T]-?: CleanKeys<T, K, true>;
|
|
858
872
|
}[keyof T] & {} : never;
|
|
859
|
-
export type AutoPath<O, P extends string | boolean, E extends string = never, D extends Prev[number] = 9> = P extends boolean ? P : [D] extends [never] ? never : P extends any ? P extends string ? P extends `${infer A}.${infer B}` ? A extends StringKeys<O, E> ? `${A}.${AutoPath<NonNullable<GetStringKey<O, A, E>>, B, E, Prev[D]>}` : never : P extends StringKeys<O, E> ? (NonNullable<GetStringKey<O, P & StringKeys<O, E>, E>> extends unknown ? Exclude<P, `${string}.`> : never) | (StringKeys<NonNullable<GetStringKey<O, P & StringKeys<O, E>, E>>, E> extends never ? never : `${P & string}.`) : StringKeys<O, E> | `${
|
|
873
|
+
export type AutoPath<O, P extends string | boolean, E extends string = never, D extends Prev[number] = 9> = P extends boolean ? P : [D] extends [never] ? never : P extends any ? P extends string ? P extends `${infer A}.${infer B}` ? A extends StringKeys<O, E> ? `${A}.${AutoPath<NonNullable<GetStringKey<O, A, E>>, B, E, Prev[D]>}` : never : P extends StringKeys<O, E> ? (NonNullable<GetStringKey<O, P & StringKeys<O, E>, E>> extends unknown ? Exclude<P, `${string}.`> : never) | (StringKeys<NonNullable<GetStringKey<O, P & StringKeys<O, E>, E>>, E> extends never ? never : `${P & string}.`) : StringKeys<O, E> | `${RelationKeys<O>}:ref` : never : never;
|
|
860
874
|
export type UnboxArray<T> = T extends any[] ? ArrayElement<T> : T;
|
|
861
875
|
export type ArrayElement<ArrayType extends unknown[]> = ArrayType extends (infer ElementType)[] ? ElementType : never;
|
|
862
876
|
export type ExpandProperty<T> = T extends ReferenceShape<infer U> ? NonNullable<U> : T extends CollectionShape<infer U> ? NonNullable<U> : T extends (infer U)[] ? NonNullable<U> : NonNullable<T>;
|