@mikro-orm/core 7.0.17 → 7.0.18-dev.1
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.d.ts +106 -12
- package/EntityManager.js +228 -19
- package/MikroORM.d.ts +3 -3
- package/README.md +2 -1
- package/connections/Connection.d.ts +67 -1
- package/connections/Connection.js +64 -0
- package/drivers/DatabaseDriver.d.ts +1 -0
- package/drivers/DatabaseDriver.js +3 -0
- package/drivers/IDatabaseDriver.d.ts +50 -7
- package/entity/BaseEntity.d.ts +70 -3
- package/entity/BaseEntity.js +18 -0
- package/entity/Collection.d.ts +6 -3
- package/entity/Collection.js +15 -4
- package/entity/EntityAssigner.js +8 -0
- package/entity/EntityFactory.js +20 -1
- package/entity/EntityLoader.d.ts +8 -1
- package/entity/EntityLoader.js +41 -16
- package/entity/EntityRepository.d.ts +27 -9
- package/entity/EntityRepository.js +12 -0
- package/entity/Reference.d.ts +42 -1
- package/entity/Reference.js +9 -0
- package/entity/WrappedEntity.d.ts +2 -2
- package/entity/defineEntity.d.ts +92 -17
- package/entity/defineEntity.js +17 -6
- package/entity/utils.js +4 -5
- package/enums.d.ts +8 -1
- package/errors.d.ts +2 -0
- package/errors.js +4 -0
- package/index.d.ts +2 -2
- package/index.js +1 -1
- package/metadata/EntitySchema.js +3 -0
- package/metadata/MetadataDiscovery.d.ts +11 -0
- package/metadata/MetadataDiscovery.js +135 -10
- package/metadata/MetadataValidator.d.ts +26 -0
- package/metadata/MetadataValidator.js +236 -0
- package/metadata/Routine.d.ts +74 -0
- package/metadata/Routine.js +140 -0
- package/metadata/index.d.ts +1 -0
- package/metadata/index.js +1 -0
- package/metadata/types.d.ts +77 -4
- package/naming-strategy/AbstractNamingStrategy.d.ts +1 -1
- package/naming-strategy/NamingStrategy.d.ts +1 -1
- package/package.json +1 -1
- package/platforms/Platform.d.ts +7 -0
- package/platforms/Platform.js +15 -1
- package/serialization/EntitySerializer.d.ts +18 -6
- package/serialization/EntitySerializer.js +31 -4
- package/typings.d.ts +389 -18
- package/typings.js +21 -4
- package/unit-of-work/UnitOfWork.js +11 -3
- package/utils/AbstractMigrator.d.ts +26 -3
- package/utils/AbstractMigrator.js +264 -15
- package/utils/AbstractSchemaGenerator.d.ts +1 -1
- package/utils/AbstractSchemaGenerator.js +4 -1
- package/utils/Configuration.d.ts +40 -3
- package/utils/Configuration.js +46 -0
- package/utils/DataloaderUtils.d.ts +10 -1
- package/utils/DataloaderUtils.js +78 -0
- package/utils/QueryHelper.d.ts +16 -0
- package/utils/QueryHelper.js +15 -0
- package/utils/TransactionManager.js +2 -0
- package/utils/Utils.d.ts +1 -1
- package/utils/Utils.js +1 -1
- package/utils/fs-utils.d.ts +2 -0
- package/utils/fs-utils.js +7 -1
- package/utils/index.d.ts +1 -0
- package/utils/index.js +1 -0
- package/utils/partition-utils.d.ts +17 -0
- package/utils/partition-utils.js +79 -0
package/entity/utils.js
CHANGED
|
@@ -94,11 +94,10 @@ export function applyPopulateHints(populate, hints) {
|
|
|
94
94
|
if (!entry) {
|
|
95
95
|
continue;
|
|
96
96
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
entry.joinType = hint.joinType;
|
|
97
|
+
for (const key of Object.keys(hint)) {
|
|
98
|
+
if (hint[key] != null) {
|
|
99
|
+
entry[key] = hint[key];
|
|
100
|
+
}
|
|
102
101
|
}
|
|
103
102
|
}
|
|
104
103
|
}
|
package/enums.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EntityKey, ExpandProperty } from './typings.js';
|
|
2
|
-
import type { Transaction } from './connections/Connection.js';
|
|
2
|
+
import type { InflightQueryAbortStrategy, Transaction } from './connections/Connection.js';
|
|
3
3
|
import type { LogContext } from './logging/Logger.js';
|
|
4
4
|
/** Controls when the `EntityManager` flushes pending changes to the database. */
|
|
5
5
|
export declare enum FlushMode {
|
|
@@ -307,6 +307,13 @@ export interface TransactionOptions {
|
|
|
307
307
|
flushMode?: FlushMode | `${FlushMode}`;
|
|
308
308
|
ignoreNestedTransactions?: boolean;
|
|
309
309
|
loggerContext?: LogContext;
|
|
310
|
+
/**
|
|
311
|
+
* `AbortSignal` cancelling every query within the transaction (including the implicit flush).
|
|
312
|
+
* Cancelling mid-transaction triggers a rollback once the in-flight query settles.
|
|
313
|
+
*/
|
|
314
|
+
signal?: AbortSignal;
|
|
315
|
+
/** Cancellation strategy paired with {@link signal}. */
|
|
316
|
+
inflightQueryAbortStrategy?: InflightQueryAbortStrategy;
|
|
310
317
|
}
|
|
311
318
|
export declare abstract class PlainObject {
|
|
312
319
|
}
|
package/errors.d.ts
CHANGED
|
@@ -74,6 +74,8 @@ export declare class MetadataError<T extends AnyEntity = AnyEntity> extends Vali
|
|
|
74
74
|
static viewEntityWithoutExpression(meta: EntityMetadata): MetadataError;
|
|
75
75
|
static mixedInheritanceStrategies(root: EntityMetadata, child: EntityMetadata): MetadataError;
|
|
76
76
|
static tptNotSupportedByDriver(meta: EntityMetadata): MetadataError;
|
|
77
|
+
/** Thrown when database triggers are defined on an entity using a driver that does not support them. */
|
|
78
|
+
static triggersNotSupportedByDriver(meta: EntityMetadata): MetadataError;
|
|
77
79
|
private static fromMessage;
|
|
78
80
|
}
|
|
79
81
|
/** Error thrown when an entity lookup fails to find the expected result. */
|
package/errors.js
CHANGED
|
@@ -243,6 +243,10 @@ export class MetadataError extends ValidationError {
|
|
|
243
243
|
static tptNotSupportedByDriver(meta) {
|
|
244
244
|
return new MetadataError(`Entity ${meta.className} uses TPT (Table-Per-Type) inheritance which is not supported by the current driver. TPT requires SQL JOINs and is only available with SQL drivers.`);
|
|
245
245
|
}
|
|
246
|
+
/** Thrown when database triggers are defined on an entity using a driver that does not support them. */
|
|
247
|
+
static triggersNotSupportedByDriver(meta) {
|
|
248
|
+
return new MetadataError(`Entity ${meta.className} defines database triggers which are not supported by the current driver. Triggers are only available with SQL drivers.`);
|
|
249
|
+
}
|
|
246
250
|
static fromMessage(meta, prop, message) {
|
|
247
251
|
return new MetadataError(`${meta.className}.${prop.name} ${message}`);
|
|
248
252
|
}
|
package/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* @packageDocumentation
|
|
3
3
|
* @module core
|
|
4
4
|
*/
|
|
5
|
-
export { EntityMetadata, PrimaryKeyProp, EntityRepositoryType, OptionalProps, EagerProps, HiddenProps, Config, EntityName, } from './typings.js';
|
|
6
|
-
export type { CompiledFunctions, Constructor, ConnectionType, Dictionary, Primary, IPrimaryKey, ObjectQuery, FilterQuery, IWrappedEntity, InferEntityName, EntityData, Highlighter, MaybePromise, AnyEntity, EntityClass, EntityProperty, PopulateOptions, Populate, Loaded, New, LoadedReference, LoadedCollection, IMigrator, IMigrationGenerator, MigratorEvent, GetRepository, MigrationObject, DeepPartial, PrimaryProperty, Cast, IsUnknown, EntityDictionary, EntityDTO, EntityDTOFlat, EntityDTOProp, SerializeDTO, MigrationDiff, GenerateOptions, FilterObject, IMigrationRunner, IEntityGenerator, ISeedManager, SeederObject, IMigratorStorage, RequiredEntityData, CheckCallback, IndexCallback, FormulaCallback, FormulaColumns, FormulaTable, SchemaTable, SchemaColumns, SchemaColumnRef, EntityDataPropValue, SimpleColumnMeta, Rel, Ref, ScalarRef, EntityRef, ISchemaGenerator, MigrationInfo, MigrateOptions, MigrationResult, MigrationRow, EntityKey, EntityValue, EntityDataValue, FilterKey, EntityType, FromEntityType, Selected, IsSubset, EntityProps, ExpandProperty, ExpandScalar, FilterItemValue, ExpandQuery, Scalar, ExpandHint, FilterValue, MergeLoaded, MergeSelected, TypeConfig, AnyString, ClearDatabaseOptions, CreateSchemaOptions, EnsureDatabaseOptions, UpdateSchemaOptions, DropSchemaOptions, RefreshDatabaseOptions, AutoPath, UnboxArray, MetadataProcessor, ImportsResolver, RequiredNullable, DefineConfig, Opt, Hidden, EntitySchemaWithMeta, InferEntity, CheckConstraint, GeneratedColumnCallback, FilterDef, EntityCtor, Subquery, PopulateHintOptions, Prefixes, } from './typings.js';
|
|
5
|
+
export { EntityMetadata, PrimaryKeyProp, EntityRepositoryType, OptionalProps, EagerProps, HiddenProps, Config, EntityName, IndexHints, } from './typings.js';
|
|
6
|
+
export type { CompiledFunctions, Constructor, ConnectionType, Dictionary, Primary, IPrimaryKey, ObjectQuery, FilterQuery, IWrappedEntity, InferEntityName, EntityData, Highlighter, MaybePromise, AnyEntity, EntityClass, EntityProperty, PopulateOptions, Populate, Loaded, New, LoadedReference, LoadedCollection, IMigrator, IMigrationGenerator, MigratorEvent, GetRepository, MigrationObject, DeepPartial, PrimaryProperty, Cast, IsUnknown, EntityDictionary, EntityDTO, EntityDTOFlat, EntityDTOProp, SerializeDTO, MigrationDiff, GenerateOptions, FilterObject, IndexFilterQuery, ExtractIndexHints, IndexName, IndexColumns, WithUsingOptions, IMigrationRunner, IEntityGenerator, ISeedManager, SeederObject, IMigratorStorage, RequiredEntityData, CheckCallback, TriggerCallback, IndexCallback, FormulaCallback, FormulaColumns, FormulaTable, SchemaTable, SchemaColumns, SchemaColumnRef, EntityDataPropValue, SimpleColumnMeta, Rel, Ref, LazyRef, ScalarRef, EntityRef, ISchemaGenerator, MigrationInfo, MigrateOptions, MigrationResult, MigrationRow, EntityKey, EntityValue, EntityDataValue, FilterKey, EntityType, FromEntityType, Selected, IsSubset, EntityProps, ExpandProperty, ExpandScalar, FilterItemValue, ExpandQuery, Scalar, ExpandHint, FilterValue, MergeLoaded, MergeSelected, TypeConfig, AnyString, ClearDatabaseOptions, CreateSchemaOptions, EnsureDatabaseOptions, UpdateSchemaOptions, DropSchemaOptions, RefreshDatabaseOptions, AutoPath, UnboxArray, MetadataProcessor, ImportsResolver, RequiredNullable, DefineConfig, Opt, Hidden, EntitySchemaWithMeta, InferEntity, CheckConstraint, TriggerDef, RoutineReturns, RoutineBodyCallback, RoutineJsBody, RoutineIgnoreField, RoutineParamConfig, RoutineConfig, RoutineRuntimeType, RoutineArgs, RoutineReturn, GeneratedColumnCallback, FilterDef, EntityCtor, Subquery, PopulateHintOptions, Prefixes, } from './typings.js';
|
|
7
7
|
export * from './enums.js';
|
|
8
8
|
export * from './errors.js';
|
|
9
9
|
export * from './exceptions.js';
|
package/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @packageDocumentation
|
|
3
3
|
* @module core
|
|
4
4
|
*/
|
|
5
|
-
export { EntityMetadata, PrimaryKeyProp, EntityRepositoryType, OptionalProps, EagerProps, HiddenProps, Config, EntityName, } from './typings.js';
|
|
5
|
+
export { EntityMetadata, PrimaryKeyProp, EntityRepositoryType, OptionalProps, EagerProps, HiddenProps, Config, EntityName, IndexHints, } from './typings.js';
|
|
6
6
|
export * from './enums.js';
|
|
7
7
|
export * from './errors.js';
|
|
8
8
|
export * from './exceptions.js';
|
package/metadata/EntitySchema.js
CHANGED
|
@@ -85,6 +85,9 @@ export class EntitySchema {
|
|
|
85
85
|
if (options.items instanceof Function) {
|
|
86
86
|
options.items = Utils.extractEnumValues(options.items());
|
|
87
87
|
}
|
|
88
|
+
else if (options.items && !Array.isArray(options.items) && typeof options.items === 'object') {
|
|
89
|
+
options.items = Utils.extractEnumValues(options.items);
|
|
90
|
+
}
|
|
88
91
|
// enum arrays are simple numeric/string arrays, the constraint is enforced in the custom type only
|
|
89
92
|
if (options.array && !options.type) {
|
|
90
93
|
options.type = new EnumArrayType(`${this._meta.className}.${name}`, options.items);
|
|
@@ -54,6 +54,16 @@ export declare class MetadataDiscovery {
|
|
|
54
54
|
* Define properties for a polymorphic pivot table.
|
|
55
55
|
*/
|
|
56
56
|
private definePolymorphicPivotProperties;
|
|
57
|
+
/**
|
|
58
|
+
* Mirror of definePolymorphicPivotProperties for union-target M:N
|
|
59
|
+
* (e.g. Post.attachments -> Image | Video via shared pivot with a target-side discriminator).
|
|
60
|
+
*
|
|
61
|
+
* Pivot shape:
|
|
62
|
+
* (owner_fk..., discriminator_column, target_fk...)
|
|
63
|
+
* - owner side is a normal M:1 to the single owner entity
|
|
64
|
+
* - target side is a discriminator column + per-target-type virtual M:1 relations
|
|
65
|
+
*/
|
|
66
|
+
private defineUnionTargetPolymorphicPivotProperties;
|
|
57
67
|
/**
|
|
58
68
|
* Create a virtual M:1 relation from pivot to a polymorphic owner entity.
|
|
59
69
|
* This enables single-query join loading for inverse-side polymorphic M:N.
|
|
@@ -106,6 +116,7 @@ export declare class MetadataDiscovery {
|
|
|
106
116
|
private initAutoincrement;
|
|
107
117
|
private createSchemaTable;
|
|
108
118
|
private initCheckConstraints;
|
|
119
|
+
private initTriggers;
|
|
109
120
|
private initGeneratedColumn;
|
|
110
121
|
private getDefaultVersionValue;
|
|
111
122
|
private inferDefaultValue;
|
|
@@ -164,6 +164,7 @@ export class MetadataDiscovery {
|
|
|
164
164
|
forEachProp((m, p) => this.initGeneratedColumn(m, p));
|
|
165
165
|
filtered.forEach(meta => this.initAutoincrement(meta)); // once again after we init custom types
|
|
166
166
|
filtered.forEach(meta => this.initCheckConstraints(meta));
|
|
167
|
+
filtered.forEach(meta => this.initTriggers(meta));
|
|
167
168
|
forEachProp((_m, p) => {
|
|
168
169
|
this.initDefaultValue(p);
|
|
169
170
|
this.inferTypeFromDefault(p);
|
|
@@ -484,17 +485,28 @@ export class MetadataDiscovery {
|
|
|
484
485
|
prop.polymorphic = prop2.polymorphic;
|
|
485
486
|
prop.discriminator = prop2.discriminator;
|
|
486
487
|
prop.discriminatorColumn = prop2.discriminatorColumn;
|
|
487
|
-
|
|
488
|
+
// For a union-target pivot each inverse side sits on one specific target class, so its
|
|
489
|
+
// discriminator value is that class's tableName. For Rails-style, prop2 has a single fixed value.
|
|
490
|
+
prop.discriminatorValue = QueryHelper.isUnionTargetPolymorphic(prop2) ? meta.tableName : prop2.discriminatorValue;
|
|
488
491
|
}
|
|
489
492
|
prop.referencedColumnNames ??= Utils.flatten(meta.primaryKeys.map(primaryKey => meta.properties[primaryKey].fieldNames));
|
|
490
|
-
//
|
|
491
|
-
|
|
493
|
+
// Union-target polymorphic M:N: owner side is fixed (real FK), target side uses discriminator-derived names.
|
|
494
|
+
const isUnionTargetMN = QueryHelper.isUnionTargetPolymorphic(prop);
|
|
495
|
+
if (prop.polymorphic && prop.discriminator && !isUnionTargetMN) {
|
|
496
|
+
// Rails-style: owner side is polymorphic, uses discriminator base name (e.g. taggable_id instead of post_id)
|
|
492
497
|
prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.#namingStrategy.joinKeyColumnName(prop.discriminator, referencedColumnName, prop.referencedColumnNames.length > 1));
|
|
493
498
|
}
|
|
494
499
|
else {
|
|
495
500
|
prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.#namingStrategy.joinKeyColumnName(meta.root.className, referencedColumnName, meta.compositePK));
|
|
496
501
|
}
|
|
497
|
-
|
|
502
|
+
if (isUnionTargetMN) {
|
|
503
|
+
// Target side uses discriminator base name (e.g. attachable_id — shared across Image/Video)
|
|
504
|
+
const targetPkCols = Utils.flatten(meta2.primaryKeys.map(pk => meta2.properties[pk].fieldNames));
|
|
505
|
+
prop.inverseJoinColumns ??= targetPkCols.map(fieldName => this.#namingStrategy.joinKeyColumnName(prop.discriminator, fieldName, targetPkCols.length > 1));
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
prop.inverseJoinColumns ??= this.initManyToOneFieldName(prop, meta2.root.className);
|
|
509
|
+
}
|
|
498
510
|
}
|
|
499
511
|
isExplicitTableName(meta) {
|
|
500
512
|
return meta.tableName !== this.#namingStrategy.classToTableName(meta.className);
|
|
@@ -561,6 +573,12 @@ export class MetadataDiscovery {
|
|
|
561
573
|
}
|
|
562
574
|
}
|
|
563
575
|
meta.forceConstructor ??= this.shouldForceConstructorUsage(meta);
|
|
576
|
+
// Fail fast when the platform rejects the metadata, so users see the platform-specific
|
|
577
|
+
// error (e.g. "SqlitePlatform does not support partitioned tables") instead of a
|
|
578
|
+
// downstream core validator message that assumes partitioning is supported.
|
|
579
|
+
if (meta.partitionBy && !this.#platform.supportsPartitionedTables()) {
|
|
580
|
+
this.#platform.validateMetadata(meta);
|
|
581
|
+
}
|
|
564
582
|
this.#validator.validateEntityDefinition(this.#metadata, meta.class, this.#config.get('discovery'));
|
|
565
583
|
for (const prop of Object.values(meta.properties)) {
|
|
566
584
|
this.initNullability(prop);
|
|
@@ -592,6 +610,24 @@ export class MetadataDiscovery {
|
|
|
592
610
|
targetRoot.properties[prop.inversedBy].pivotEntity = pivotMeta.class;
|
|
593
611
|
}
|
|
594
612
|
}
|
|
613
|
+
// Propagate pivotEntity to ALL inverse collections using mappedBy pointing at this
|
|
614
|
+
// owner prop. Covers three cases:
|
|
615
|
+
// - regular inverse (Tag.posts mappedBy Post.tags) — handled by inversedBy above
|
|
616
|
+
// - union-target inverse (Image.posts mappedBy Post.attachments) — on each polymorph target
|
|
617
|
+
// - merged inverse (Tag.owners mappedBy [Post,Video].tags) — union collection on the target
|
|
618
|
+
const inverseCandidates = QueryHelper.isUnionTargetPolymorphic(prop)
|
|
619
|
+
? prop.polymorphTargets
|
|
620
|
+
: [prop.targetMeta];
|
|
621
|
+
for (const targetMeta of inverseCandidates) {
|
|
622
|
+
for (const inverseProp of Object.values(targetMeta.properties)) {
|
|
623
|
+
if (inverseProp.kind === ReferenceKind.MANY_TO_MANY &&
|
|
624
|
+
inverseProp.mappedBy === prop.name &&
|
|
625
|
+
!inverseProp.pivotEntity) {
|
|
626
|
+
inverseProp.pivotEntity = pivotMeta.class;
|
|
627
|
+
inverseProp.pivotTable = pivotMeta.tableName;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
595
631
|
return pivotMeta;
|
|
596
632
|
});
|
|
597
633
|
}
|
|
@@ -730,8 +766,12 @@ export class MetadataDiscovery {
|
|
|
730
766
|
}
|
|
731
767
|
}
|
|
732
768
|
}
|
|
733
|
-
//
|
|
734
|
-
if (prop.
|
|
769
|
+
// Union-target polymorphic M:N: discriminator + target FK share the pivot across multiple target types
|
|
770
|
+
if (prop.discriminatorColumn && QueryHelper.isUnionTargetPolymorphic(prop)) {
|
|
771
|
+
this.defineUnionTargetPolymorphicPivotProperties(pivotMeta2, meta, prop);
|
|
772
|
+
}
|
|
773
|
+
else if (prop.polymorphic && prop.discriminatorColumn) {
|
|
774
|
+
// Rails-style polymorphic M:N: multiple owners share the pivot, single target type
|
|
735
775
|
this.definePolymorphicPivotProperties(pivotMeta2, meta, prop, targetMeta);
|
|
736
776
|
}
|
|
737
777
|
else {
|
|
@@ -818,6 +858,33 @@ export class MetadataDiscovery {
|
|
|
818
858
|
pivotMeta.polymorphicDiscriminatorMap ??= {};
|
|
819
859
|
pivotMeta.polymorphicDiscriminatorMap[prop.discriminatorValue] = meta.class;
|
|
820
860
|
}
|
|
861
|
+
/**
|
|
862
|
+
* Mirror of definePolymorphicPivotProperties for union-target M:N
|
|
863
|
+
* (e.g. Post.attachments -> Image | Video via shared pivot with a target-side discriminator).
|
|
864
|
+
*
|
|
865
|
+
* Pivot shape:
|
|
866
|
+
* (owner_fk..., discriminator_column, target_fk...)
|
|
867
|
+
* - owner side is a normal M:1 to the single owner entity
|
|
868
|
+
* - target side is a discriminator column + per-target-type virtual M:1 relations
|
|
869
|
+
*/
|
|
870
|
+
defineUnionTargetPolymorphicPivotProperties(pivotMeta, meta, prop) {
|
|
871
|
+
const discriminatorColumn = prop.discriminatorColumn;
|
|
872
|
+
const targets = prop.polymorphTargets;
|
|
873
|
+
pivotMeta.properties[meta.name + '_owner'] = this.definePivotProperty(prop, meta.name + '_owner', meta.class, prop.discriminator, true, false);
|
|
874
|
+
const discriminatorProp = this.createPivotScalarProperty(discriminatorColumn, [this.#platform.getVarcharTypeDeclarationSQL(prop)], [discriminatorColumn], { type: 'string', primary: true, nullable: false });
|
|
875
|
+
this.initFieldName(discriminatorProp);
|
|
876
|
+
pivotMeta.properties[discriminatorColumn] = discriminatorProp;
|
|
877
|
+
const firstTargetColumnTypes = this.getPrimaryKeyColumnTypes(targets[0]);
|
|
878
|
+
pivotMeta.properties[prop.discriminator] = this.createPivotScalarProperty(prop.discriminator, firstTargetColumnTypes, [...prop.inverseJoinColumns], { type: targets[0].className, primary: true, nullable: false });
|
|
879
|
+
pivotMeta.polymorphicDiscriminatorMap ??= {};
|
|
880
|
+
for (const targetMeta of targets) {
|
|
881
|
+
const relationName = `${prop.discriminator}_${targetMeta.tableName}`;
|
|
882
|
+
const relation = this.definePolymorphicOwnerRelation(prop, relationName, targetMeta);
|
|
883
|
+
relation.joinColumns = relation.fieldNames = relation.ownColumns = [...prop.inverseJoinColumns];
|
|
884
|
+
pivotMeta.properties[relationName] = relation;
|
|
885
|
+
pivotMeta.polymorphicDiscriminatorMap[targetMeta.tableName] = targetMeta.class;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
821
888
|
/**
|
|
822
889
|
* Create a virtual M:1 relation from pivot to a polymorphic owner entity.
|
|
823
890
|
* This enables single-query join loading for inverse-side polymorphic M:N.
|
|
@@ -968,11 +1035,12 @@ export class MetadataDiscovery {
|
|
|
968
1035
|
});
|
|
969
1036
|
}
|
|
970
1037
|
// TPT children have their own tables that don't contain the parent's columns,
|
|
971
|
-
// so propagating parent indexes/uniques/checks would target missing columns.
|
|
1038
|
+
// so propagating parent indexes/uniques/checks/triggers would target missing columns.
|
|
972
1039
|
if (meta.inheritanceType !== 'tpt' || !meta.tptParent) {
|
|
973
1040
|
meta.indexes = Utils.unique([...base.indexes, ...meta.indexes]);
|
|
974
1041
|
meta.uniques = Utils.unique([...base.uniques, ...meta.uniques]);
|
|
975
1042
|
meta.checks = Utils.unique([...base.checks, ...meta.checks]);
|
|
1043
|
+
meta.triggers = Utils.unique([...base.triggers, ...meta.triggers]);
|
|
976
1044
|
}
|
|
977
1045
|
const pks = Object.values(meta.properties)
|
|
978
1046
|
.filter(p => p.primary)
|
|
@@ -1058,11 +1126,15 @@ export class MetadataDiscovery {
|
|
|
1058
1126
|
prop.discriminatorColumn ??= this.#namingStrategy.discriminatorColumnName(prop.discriminator);
|
|
1059
1127
|
prop.createForeignKeyConstraint = false;
|
|
1060
1128
|
const isToOne = [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind);
|
|
1061
|
-
|
|
1129
|
+
const isUnionTargetMN = prop.kind === ReferenceKind.MANY_TO_MANY && Array.isArray(prop.target);
|
|
1130
|
+
if (isToOne || isUnionTargetMN) {
|
|
1062
1131
|
const types = prop.type.split(/ ?\| ?/);
|
|
1063
1132
|
prop.polymorphTargets = discovered.filter(m => types.includes(m.className) && !m.embeddable);
|
|
1064
1133
|
prop.targetMeta = prop.polymorphTargets[0];
|
|
1065
1134
|
prop.referencedPKs = prop.targetMeta?.primaryKeys;
|
|
1135
|
+
if (isUnionTargetMN && prop.polymorphTargets.length < 2) {
|
|
1136
|
+
throw new MetadataError(`${meta.className}.${prop.name} union-target polymorphic M:N requires at least two target entity types; use a regular M:N relation for a single target.`);
|
|
1137
|
+
}
|
|
1066
1138
|
}
|
|
1067
1139
|
if (prop.discriminatorMap) {
|
|
1068
1140
|
const normalizedMap = {};
|
|
@@ -1078,7 +1150,7 @@ export class MetadataDiscovery {
|
|
|
1078
1150
|
}
|
|
1079
1151
|
prop.discriminatorMap = normalizedMap;
|
|
1080
1152
|
}
|
|
1081
|
-
else if (isToOne) {
|
|
1153
|
+
else if (isToOne || isUnionTargetMN) {
|
|
1082
1154
|
prop.discriminatorMap = {};
|
|
1083
1155
|
const tableNameToTarget = new Map();
|
|
1084
1156
|
for (const target of prop.polymorphTargets) {
|
|
@@ -1572,6 +1644,27 @@ export class MetadataDiscovery {
|
|
|
1572
1644
|
}
|
|
1573
1645
|
}
|
|
1574
1646
|
}
|
|
1647
|
+
initTriggers(meta) {
|
|
1648
|
+
if (meta.triggers.length === 0) {
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
const columns = meta.createSchemaColumnMappingObject();
|
|
1652
|
+
const table = this.createSchemaTable(meta);
|
|
1653
|
+
for (const trigger of meta.triggers) {
|
|
1654
|
+
if (trigger.body && trigger.expression) {
|
|
1655
|
+
throw new MetadataError(`Trigger "${trigger.name ?? '(unnamed)'}" on entity ${meta.className} defines both 'body' and 'expression'. Use one or the other.`);
|
|
1656
|
+
}
|
|
1657
|
+
if (!trigger.body && !trigger.expression) {
|
|
1658
|
+
throw new MetadataError(`Trigger "${trigger.name ?? '(unnamed)'}" on entity ${meta.className} must define either 'body' or 'expression'.`);
|
|
1659
|
+
}
|
|
1660
|
+
trigger.name ??= this.#namingStrategy.indexName(meta.tableName, trigger.events, 'trigger');
|
|
1661
|
+
trigger.forEach ??= 'row';
|
|
1662
|
+
if (trigger.body instanceof Function) {
|
|
1663
|
+
trigger.body = trigger.body(columns, table);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
meta.hasTriggers = true;
|
|
1667
|
+
}
|
|
1575
1668
|
initGeneratedColumn(meta, prop) {
|
|
1576
1669
|
if (!prop.generated && prop.columnTypes) {
|
|
1577
1670
|
const match = /(.*) generated always as (.*)/i.exec(prop.columnTypes[0]);
|
|
@@ -1698,7 +1791,14 @@ export class MetadataDiscovery {
|
|
|
1698
1791
|
return (!Type.getType(t[type]).ensureComparable(meta, prop) && (prop.type === t[type] || brand === type));
|
|
1699
1792
|
});
|
|
1700
1793
|
if (type) {
|
|
1701
|
-
prop.
|
|
1794
|
+
if (prop.array) {
|
|
1795
|
+
// built-in type + array: true — force-create instance for ArrayType wrapping
|
|
1796
|
+
prop.customType = new prop.type();
|
|
1797
|
+
prop.type = prop.customType.constructor.name;
|
|
1798
|
+
}
|
|
1799
|
+
else {
|
|
1800
|
+
prop.type = type === 'datetime' ? 'Date' : type;
|
|
1801
|
+
}
|
|
1702
1802
|
}
|
|
1703
1803
|
else {
|
|
1704
1804
|
prop.customType = new prop.type();
|
|
@@ -1708,6 +1808,31 @@ export class MetadataDiscovery {
|
|
|
1708
1808
|
if (simple) {
|
|
1709
1809
|
return;
|
|
1710
1810
|
}
|
|
1811
|
+
if (prop.array &&
|
|
1812
|
+
prop.customType &&
|
|
1813
|
+
!(prop.customType instanceof t.array) &&
|
|
1814
|
+
!(prop.customType instanceof t.enumArray) &&
|
|
1815
|
+
!(prop.customType instanceof t.json) &&
|
|
1816
|
+
prop.kind === ReferenceKind.SCALAR) {
|
|
1817
|
+
const innerType = prop.customType;
|
|
1818
|
+
innerType.platform = this.#platform;
|
|
1819
|
+
innerType.meta = meta;
|
|
1820
|
+
innerType.prop = prop;
|
|
1821
|
+
if (!prop.columnTypes) {
|
|
1822
|
+
const arrayDecl = this.#platform.getArrayDeclarationSQL();
|
|
1823
|
+
if (arrayDecl.endsWith('[]')) {
|
|
1824
|
+
// native array support (e.g. postgres) — use inner type's column type with [] suffix
|
|
1825
|
+
prop.columnTypes = [innerType.getColumnType(prop, this.#platform) + '[]'];
|
|
1826
|
+
}
|
|
1827
|
+
else {
|
|
1828
|
+
// non-native (e.g. mysql, sqlite) — store as text/clob
|
|
1829
|
+
prop.columnTypes = [arrayDecl];
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
const platform = this.#platform;
|
|
1833
|
+
prop.customType = new t.array(v => innerType.convertToJSValue(v, platform), v => innerType.convertToDatabaseValue(v, platform));
|
|
1834
|
+
prop.type = prop.customType.constructor.name;
|
|
1835
|
+
}
|
|
1711
1836
|
if (!prop.customType && ['json', 'jsonb'].includes(prop.type?.toLowerCase())) {
|
|
1712
1837
|
prop.customType = new t.json();
|
|
1713
1838
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EntityMetadata, EntityName } from '../typings.js';
|
|
2
|
+
import type { Routine } from './Routine.js';
|
|
2
3
|
import { type MetadataDiscoveryOptions } from '../utils/Configuration.js';
|
|
3
4
|
import type { MetadataStorage } from './MetadataStorage.js';
|
|
4
5
|
/**
|
|
@@ -6,7 +7,32 @@ import type { MetadataStorage } from './MetadataStorage.js';
|
|
|
6
7
|
*/
|
|
7
8
|
export declare class MetadataValidator {
|
|
8
9
|
validateEntityDefinition<T>(metadata: MetadataStorage, name: EntityName<T>, options: MetadataDiscoveryOptions): void;
|
|
10
|
+
validateRoutineDefinition(routine: Routine): void;
|
|
9
11
|
validateDiscovered(discovered: EntityMetadata[], options: MetadataDiscoveryOptions): void;
|
|
12
|
+
private validatePartitioning;
|
|
13
|
+
/**
|
|
14
|
+
* Find the first partition name whose normalized form (case-folded for unquoted segments,
|
|
15
|
+
* quoted segments preserved) has already been seen. Returns the offending name in its
|
|
16
|
+
* original form for the error message.
|
|
17
|
+
*/
|
|
18
|
+
private findDuplicatePartitionName;
|
|
19
|
+
/**
|
|
20
|
+
* Partition names may be bare (`child`), schema-qualified (`schema.child`), or use quoted
|
|
21
|
+
* identifiers (`"my.schema"."child"`). Reject anything with more than one unquoted `.`.
|
|
22
|
+
*/
|
|
23
|
+
private hasValidPartitionName;
|
|
24
|
+
private hasPartitionExpression;
|
|
25
|
+
private validatePartitionKeyConstraints;
|
|
26
|
+
/**
|
|
27
|
+
* Returns the list of physical field names that a partition expression references, or
|
|
28
|
+
* `undefined` when the expression is opaque (callback, or raw SQL like `date_trunc('day', x)`
|
|
29
|
+
* that we cannot statically parse). Opaque expressions intentionally bypass the primary-key /
|
|
30
|
+
* unique-constraint coverage checks — users are trusted to ensure the referenced columns are
|
|
31
|
+
* part of the partition key, since PostgreSQL will surface the violation at DDL execution.
|
|
32
|
+
*/
|
|
33
|
+
private getPartitionKeyFields;
|
|
34
|
+
private resolvePartitionKeyField;
|
|
35
|
+
private getConstraintFields;
|
|
10
36
|
private validateReference;
|
|
11
37
|
private validateTargetKey;
|
|
12
38
|
/**
|