@mikro-orm/core 7.0.0-dev.3 → 7.0.0-dev.300
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 +114 -63
- package/EntityManager.js +385 -310
- package/MikroORM.d.ts +44 -35
- package/MikroORM.js +109 -143
- package/README.md +3 -2
- package/cache/FileCacheAdapter.d.ts +1 -1
- package/cache/FileCacheAdapter.js +17 -8
- package/cache/GeneratedCacheAdapter.d.ts +0 -1
- package/cache/GeneratedCacheAdapter.js +0 -2
- package/cache/index.d.ts +0 -1
- package/cache/index.js +0 -1
- package/connections/Connection.d.ts +16 -7
- package/connections/Connection.js +23 -14
- package/drivers/DatabaseDriver.d.ts +25 -16
- package/drivers/DatabaseDriver.js +119 -36
- package/drivers/IDatabaseDriver.d.ts +125 -23
- package/entity/BaseEntity.d.ts +63 -4
- package/entity/BaseEntity.js +0 -3
- package/entity/Collection.d.ts +102 -31
- package/entity/Collection.js +446 -108
- package/entity/EntityAssigner.d.ts +1 -1
- package/entity/EntityAssigner.js +26 -18
- package/entity/EntityFactory.d.ts +13 -1
- package/entity/EntityFactory.js +106 -60
- package/entity/EntityHelper.d.ts +2 -2
- package/entity/EntityHelper.js +65 -20
- package/entity/EntityLoader.d.ts +13 -11
- package/entity/EntityLoader.js +257 -107
- package/entity/EntityRepository.d.ts +28 -8
- package/entity/EntityRepository.js +8 -2
- package/entity/PolymorphicRef.d.ts +12 -0
- package/entity/PolymorphicRef.js +18 -0
- package/entity/Reference.d.ts +9 -12
- package/entity/Reference.js +34 -9
- package/entity/WrappedEntity.d.ts +3 -8
- package/entity/WrappedEntity.js +3 -8
- package/entity/defineEntity.d.ts +753 -0
- package/entity/defineEntity.js +537 -0
- package/entity/index.d.ts +4 -2
- package/entity/index.js +4 -2
- package/entity/utils.d.ts +13 -1
- package/entity/utils.js +49 -4
- package/entity/validators.d.ts +11 -0
- package/entity/validators.js +65 -0
- package/enums.d.ts +23 -8
- package/enums.js +15 -1
- package/errors.d.ts +25 -9
- package/errors.js +67 -21
- package/events/EventManager.d.ts +2 -1
- package/events/EventManager.js +19 -11
- package/events/EventSubscriber.d.ts +3 -1
- package/hydration/Hydrator.js +1 -2
- package/hydration/ObjectHydrator.d.ts +4 -4
- package/hydration/ObjectHydrator.js +89 -36
- package/index.d.ts +2 -2
- package/index.js +1 -2
- package/logging/DefaultLogger.d.ts +1 -1
- package/logging/DefaultLogger.js +1 -0
- package/logging/SimpleLogger.d.ts +1 -1
- package/logging/colors.d.ts +1 -1
- package/logging/colors.js +7 -6
- package/logging/index.d.ts +1 -0
- package/logging/index.js +1 -0
- package/logging/inspect.d.ts +2 -0
- package/logging/inspect.js +11 -0
- package/metadata/EntitySchema.d.ts +53 -27
- package/metadata/EntitySchema.js +125 -52
- package/metadata/MetadataDiscovery.d.ts +64 -10
- package/metadata/MetadataDiscovery.js +823 -344
- package/metadata/MetadataProvider.d.ts +11 -2
- package/metadata/MetadataProvider.js +66 -2
- package/metadata/MetadataStorage.d.ts +13 -11
- package/metadata/MetadataStorage.js +71 -38
- package/metadata/MetadataValidator.d.ts +32 -9
- package/metadata/MetadataValidator.js +198 -42
- package/metadata/discover-entities.d.ts +5 -0
- package/metadata/discover-entities.js +40 -0
- package/metadata/index.d.ts +1 -1
- package/metadata/index.js +1 -1
- package/metadata/types.d.ts +577 -0
- package/metadata/types.js +1 -0
- package/naming-strategy/AbstractNamingStrategy.d.ts +16 -4
- package/naming-strategy/AbstractNamingStrategy.js +20 -2
- package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
- package/naming-strategy/EntityCaseNamingStrategy.js +6 -5
- package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
- package/naming-strategy/MongoNamingStrategy.js +6 -6
- package/naming-strategy/NamingStrategy.d.ts +28 -4
- package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
- package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
- package/not-supported.d.ts +2 -0
- package/not-supported.js +4 -0
- package/package.json +22 -11
- package/platforms/ExceptionConverter.js +1 -1
- package/platforms/Platform.d.ts +14 -16
- package/platforms/Platform.js +24 -44
- package/serialization/EntitySerializer.d.ts +8 -3
- package/serialization/EntitySerializer.js +47 -27
- package/serialization/EntityTransformer.js +33 -21
- package/serialization/SerializationContext.d.ts +6 -6
- package/serialization/SerializationContext.js +16 -13
- package/types/ArrayType.d.ts +1 -1
- package/types/ArrayType.js +2 -3
- package/types/BigIntType.d.ts +9 -6
- package/types/BigIntType.js +4 -1
- package/types/BlobType.d.ts +0 -1
- package/types/BlobType.js +0 -3
- package/types/BooleanType.d.ts +2 -1
- package/types/BooleanType.js +3 -0
- package/types/DecimalType.d.ts +6 -4
- package/types/DecimalType.js +3 -3
- package/types/DoubleType.js +2 -2
- package/types/EnumArrayType.js +1 -2
- package/types/JsonType.d.ts +1 -1
- package/types/JsonType.js +7 -2
- package/types/TinyIntType.js +1 -1
- package/types/Type.d.ts +2 -4
- package/types/Type.js +3 -3
- package/types/Uint8ArrayType.d.ts +0 -1
- package/types/Uint8ArrayType.js +1 -4
- package/types/index.d.ts +1 -1
- package/typings.d.ts +469 -175
- package/typings.js +120 -45
- package/unit-of-work/ChangeSet.d.ts +4 -6
- package/unit-of-work/ChangeSet.js +4 -5
- package/unit-of-work/ChangeSetComputer.d.ts +3 -8
- package/unit-of-work/ChangeSetComputer.js +44 -21
- package/unit-of-work/ChangeSetPersister.d.ts +15 -12
- package/unit-of-work/ChangeSetPersister.js +113 -45
- package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
- package/unit-of-work/CommitOrderCalculator.js +13 -13
- package/unit-of-work/IdentityMap.d.ts +12 -0
- package/unit-of-work/IdentityMap.js +39 -1
- package/unit-of-work/UnitOfWork.d.ts +28 -3
- package/unit-of-work/UnitOfWork.js +315 -110
- package/utils/AbstractMigrator.d.ts +101 -0
- package/utils/AbstractMigrator.js +305 -0
- package/utils/AbstractSchemaGenerator.d.ts +5 -5
- package/utils/AbstractSchemaGenerator.js +32 -18
- package/utils/AsyncContext.d.ts +6 -0
- package/utils/AsyncContext.js +42 -0
- package/utils/Configuration.d.ts +801 -207
- package/utils/Configuration.js +150 -191
- package/utils/ConfigurationLoader.d.ts +1 -54
- package/utils/ConfigurationLoader.js +1 -352
- package/utils/Cursor.d.ts +3 -6
- package/utils/Cursor.js +27 -11
- package/utils/DataloaderUtils.d.ts +15 -5
- package/utils/DataloaderUtils.js +65 -17
- package/utils/EntityComparator.d.ts +21 -10
- package/utils/EntityComparator.js +243 -106
- package/utils/QueryHelper.d.ts +24 -6
- package/utils/QueryHelper.js +122 -26
- package/utils/RawQueryFragment.d.ts +60 -32
- package/utils/RawQueryFragment.js +69 -66
- package/utils/RequestContext.js +2 -2
- package/utils/TransactionContext.js +2 -2
- package/utils/TransactionManager.d.ts +65 -0
- package/utils/TransactionManager.js +223 -0
- package/utils/Utils.d.ts +15 -122
- package/utils/Utils.js +108 -376
- package/utils/clone.js +8 -23
- package/utils/env-vars.d.ts +7 -0
- package/utils/env-vars.js +97 -0
- package/utils/fs-utils.d.ts +34 -0
- package/utils/fs-utils.js +196 -0
- package/utils/index.d.ts +2 -3
- package/utils/index.js +2 -3
- package/utils/upsert-utils.d.ts +9 -4
- package/utils/upsert-utils.js +55 -4
- package/decorators/Check.d.ts +0 -3
- package/decorators/Check.js +0 -13
- package/decorators/CreateRequestContext.d.ts +0 -3
- package/decorators/CreateRequestContext.js +0 -32
- package/decorators/Embeddable.d.ts +0 -8
- package/decorators/Embeddable.js +0 -11
- package/decorators/Embedded.d.ts +0 -18
- package/decorators/Embedded.js +0 -18
- package/decorators/Entity.d.ts +0 -18
- package/decorators/Entity.js +0 -13
- package/decorators/Enum.d.ts +0 -9
- package/decorators/Enum.js +0 -16
- package/decorators/Filter.d.ts +0 -2
- package/decorators/Filter.js +0 -8
- package/decorators/Formula.d.ts +0 -5
- package/decorators/Formula.js +0 -15
- package/decorators/Indexed.d.ts +0 -17
- package/decorators/Indexed.js +0 -20
- package/decorators/ManyToMany.d.ts +0 -40
- package/decorators/ManyToMany.js +0 -14
- package/decorators/ManyToOne.d.ts +0 -30
- package/decorators/ManyToOne.js +0 -14
- package/decorators/OneToMany.d.ts +0 -28
- package/decorators/OneToMany.js +0 -17
- package/decorators/OneToOne.d.ts +0 -24
- package/decorators/OneToOne.js +0 -7
- package/decorators/PrimaryKey.d.ts +0 -9
- package/decorators/PrimaryKey.js +0 -20
- package/decorators/Property.d.ts +0 -250
- package/decorators/Property.js +0 -32
- package/decorators/Transactional.d.ts +0 -13
- package/decorators/Transactional.js +0 -28
- package/decorators/hooks.d.ts +0 -16
- package/decorators/hooks.js +0 -47
- package/decorators/index.d.ts +0 -17
- package/decorators/index.js +0 -17
- package/entity/ArrayCollection.d.ts +0 -116
- package/entity/ArrayCollection.js +0 -395
- package/entity/EntityValidator.d.ts +0 -19
- package/entity/EntityValidator.js +0 -150
- package/metadata/ReflectMetadataProvider.d.ts +0 -8
- package/metadata/ReflectMetadataProvider.js +0 -44
- package/utils/resolveContextProvider.d.ts +0 -10
- package/utils/resolveContextProvider.js +0 -28
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { basename, extname } from 'node:path';
|
|
2
|
-
import globby from 'globby';
|
|
3
1
|
import { EntityMetadata, } from '../typings.js';
|
|
4
|
-
import { Utils } from '../utils/Utils.js';
|
|
2
|
+
import { compareArrays, Utils } from '../utils/Utils.js';
|
|
3
|
+
import { QueryHelper } from '../utils/QueryHelper.js';
|
|
5
4
|
import { MetadataValidator } from './MetadataValidator.js';
|
|
5
|
+
import { MetadataProvider } from './MetadataProvider.js';
|
|
6
6
|
import { MetadataStorage } from './MetadataStorage.js';
|
|
7
7
|
import { EntitySchema } from './EntitySchema.js';
|
|
8
8
|
import { Cascade, ReferenceKind } from '../enums.js';
|
|
9
9
|
import { MetadataError } from '../errors.js';
|
|
10
|
-
import {
|
|
10
|
+
import { t, Type } from '../types/index.js';
|
|
11
11
|
import { colors } from '../logging/colors.js';
|
|
12
|
-
import { raw,
|
|
12
|
+
import { raw, Raw } from '../utils/RawQueryFragment.js';
|
|
13
|
+
import { BaseEntity } from '../entity/BaseEntity.js';
|
|
13
14
|
export class MetadataDiscovery {
|
|
14
15
|
metadata;
|
|
15
16
|
platform;
|
|
16
17
|
config;
|
|
17
18
|
namingStrategy;
|
|
18
19
|
metadataProvider;
|
|
19
|
-
cache;
|
|
20
20
|
logger;
|
|
21
21
|
schemaHelper;
|
|
22
22
|
validator = new MetadataValidator();
|
|
@@ -27,13 +27,14 @@ export class MetadataDiscovery {
|
|
|
27
27
|
this.config = config;
|
|
28
28
|
this.namingStrategy = this.config.getNamingStrategy();
|
|
29
29
|
this.metadataProvider = this.config.getMetadataProvider();
|
|
30
|
-
this.cache = this.config.getMetadataCacheAdapter();
|
|
31
30
|
this.logger = this.config.getLogger();
|
|
32
31
|
this.schemaHelper = this.platform.getSchemaHelper();
|
|
33
32
|
}
|
|
34
33
|
async discover(preferTs = true) {
|
|
34
|
+
this.discovered.length = 0;
|
|
35
35
|
const startTime = Date.now();
|
|
36
|
-
this.
|
|
36
|
+
const suffix = this.metadataProvider.constructor === MetadataProvider ? '' : `, using ${colors.cyan(this.metadataProvider.constructor.name)}`;
|
|
37
|
+
this.logger.log('discovery', `ORM entity discovery started${suffix}`);
|
|
37
38
|
await this.findEntities(preferTs);
|
|
38
39
|
for (const meta of this.discovered) {
|
|
39
40
|
/* v8 ignore next */
|
|
@@ -47,10 +48,13 @@ export class MetadataDiscovery {
|
|
|
47
48
|
await this.config.get('discovery').afterDiscovered?.(storage, this.platform);
|
|
48
49
|
return storage;
|
|
49
50
|
}
|
|
50
|
-
discoverSync(
|
|
51
|
+
discoverSync() {
|
|
52
|
+
this.discovered.length = 0;
|
|
51
53
|
const startTime = Date.now();
|
|
52
|
-
this.
|
|
53
|
-
this.
|
|
54
|
+
const suffix = this.metadataProvider.constructor === MetadataProvider ? '' : `, using ${colors.cyan(this.metadataProvider.constructor.name)}`;
|
|
55
|
+
this.logger.log('discovery', `ORM entity discovery started${suffix} in sync mode`);
|
|
56
|
+
const refs = this.config.get('entities');
|
|
57
|
+
this.discoverReferences(refs);
|
|
54
58
|
for (const meta of this.discovered) {
|
|
55
59
|
/* v8 ignore next */
|
|
56
60
|
void this.config.get('discovery').onMetadata?.(meta, this.platform);
|
|
@@ -70,82 +74,124 @@ export class MetadataDiscovery {
|
|
|
70
74
|
.sort((a, b) => b.root.name.localeCompare(a.root.name))
|
|
71
75
|
.forEach(meta => {
|
|
72
76
|
this.platform.validateMetadata(meta);
|
|
73
|
-
discovered.set(meta.
|
|
77
|
+
discovered.set(meta.class, meta);
|
|
74
78
|
});
|
|
79
|
+
for (const meta of discovered) {
|
|
80
|
+
meta.root = discovered.get(meta.root.class);
|
|
81
|
+
if (meta.inheritanceType === 'tpt') {
|
|
82
|
+
this.computeTPTOwnProps(meta);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
75
85
|
return discovered;
|
|
76
86
|
}
|
|
87
|
+
initAccessors(meta) {
|
|
88
|
+
for (const prop of Object.values(meta.properties)) {
|
|
89
|
+
if (!prop.accessor || meta.properties[prop.accessor]) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const desc = Object.getOwnPropertyDescriptor(meta.prototype, prop.name);
|
|
93
|
+
if (desc?.get || desc?.set) {
|
|
94
|
+
this.initRelation(prop);
|
|
95
|
+
this.initFieldName(prop);
|
|
96
|
+
const accessor = prop.name;
|
|
97
|
+
prop.name = typeof prop.accessor === 'string' ? prop.accessor : prop.name;
|
|
98
|
+
if (prop.accessor === true) {
|
|
99
|
+
prop.getter = prop.setter = true;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
prop.getter = prop.setter = false;
|
|
103
|
+
}
|
|
104
|
+
prop.accessor = accessor;
|
|
105
|
+
prop.serializedName ??= accessor;
|
|
106
|
+
Utils.renameKey(meta.properties, accessor, prop.name);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const name = prop.name;
|
|
110
|
+
if (prop.kind === ReferenceKind.SCALAR || prop.kind === ReferenceKind.EMBEDDED) {
|
|
111
|
+
prop.name = prop.accessor;
|
|
112
|
+
}
|
|
113
|
+
this.initRelation(prop);
|
|
114
|
+
this.initFieldName(prop);
|
|
115
|
+
prop.serializedName ??= prop.accessor;
|
|
116
|
+
prop.name = name;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
77
120
|
processDiscoveredEntities(discovered) {
|
|
78
121
|
for (const meta of discovered) {
|
|
79
122
|
let i = 1;
|
|
80
123
|
Object.values(meta.properties).forEach(prop => meta.propertyOrder.set(prop.name, i++));
|
|
81
124
|
Object.values(meta.properties).forEach(prop => this.initPolyEmbeddables(prop, discovered));
|
|
125
|
+
this.initAccessors(meta);
|
|
82
126
|
}
|
|
83
127
|
// ignore base entities (not annotated with @Entity)
|
|
84
128
|
const filtered = discovered.filter(meta => meta.root.name);
|
|
85
129
|
// sort so we discover entities first to get around issues with nested embeddables
|
|
86
130
|
filtered.sort((a, b) => !a.embeddable === !b.embeddable ? 0 : (a.embeddable ? 1 : -1));
|
|
87
131
|
filtered.forEach(meta => this.initSingleTableInheritance(meta, filtered));
|
|
132
|
+
filtered.forEach(meta => this.initTPTRelationships(meta, filtered));
|
|
88
133
|
filtered.forEach(meta => this.defineBaseEntityProperties(meta));
|
|
89
|
-
filtered.forEach(meta =>
|
|
134
|
+
filtered.forEach(meta => {
|
|
135
|
+
const newMeta = EntitySchema.fromMetadata(meta).init().meta;
|
|
136
|
+
return this.metadata.set(newMeta.class, newMeta);
|
|
137
|
+
});
|
|
90
138
|
filtered.forEach(meta => this.initAutoincrement(meta));
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
139
|
+
const forEachProp = (cb) => {
|
|
140
|
+
filtered.forEach(meta => Object.values(meta.properties).forEach(prop => cb(meta, prop)));
|
|
141
|
+
};
|
|
142
|
+
forEachProp((m, p) => this.initFactoryField(m, p));
|
|
143
|
+
forEachProp((m, p) => this.initPolymorphicRelation(m, p, filtered));
|
|
144
|
+
forEachProp((_m, p) => this.initRelation(p));
|
|
145
|
+
forEachProp((m, p) => this.initEmbeddables(m, p));
|
|
146
|
+
forEachProp((_m, p) => this.initFieldName(p));
|
|
147
|
+
filtered.forEach(meta => this.finalizeTPTInheritance(meta, filtered));
|
|
148
|
+
filtered.forEach(meta => this.computeTPTOwnProps(meta));
|
|
149
|
+
forEachProp((m, p) => this.initVersionProperty(m, p));
|
|
150
|
+
forEachProp((m, p) => this.initCustomType(m, p));
|
|
151
|
+
forEachProp((m, p) => this.initGeneratedColumn(m, p));
|
|
97
152
|
filtered.forEach(meta => this.initAutoincrement(meta)); // once again after we init custom types
|
|
98
153
|
filtered.forEach(meta => this.initCheckConstraints(meta));
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
prop.trackChanges = false;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
filtered.forEach(meta => Object.values(meta.properties).forEach(prop => this.initIndexes(meta, prop)));
|
|
154
|
+
forEachProp((_m, p) => {
|
|
155
|
+
this.initDefaultValue(p);
|
|
156
|
+
this.inferTypeFromDefault(p);
|
|
157
|
+
this.initRelation(p);
|
|
158
|
+
this.initColumnType(p);
|
|
159
|
+
});
|
|
160
|
+
forEachProp((m, p) => this.initIndexes(m, p));
|
|
111
161
|
filtered.forEach(meta => this.autoWireBidirectionalProperties(meta));
|
|
112
|
-
filtered.forEach(meta => this.findReferencingProperties(meta, filtered));
|
|
113
162
|
for (const meta of filtered) {
|
|
114
163
|
discovered.push(...this.processEntity(meta));
|
|
115
164
|
}
|
|
116
165
|
discovered.forEach(meta => meta.sync(true));
|
|
117
|
-
|
|
118
|
-
// override the path in the options, so we can log it from the CLI in `cache:generate` command
|
|
119
|
-
if (combinedCachePath) {
|
|
120
|
-
this.config.get('metadataCache').combined = combinedCachePath;
|
|
121
|
-
}
|
|
166
|
+
this.metadataProvider.combineCache();
|
|
122
167
|
return discovered.map(meta => {
|
|
123
|
-
meta = this.metadata.get(meta.
|
|
168
|
+
meta = this.metadata.get(meta.class);
|
|
124
169
|
meta.sync(true);
|
|
170
|
+
this.findReferencingProperties(meta, filtered);
|
|
171
|
+
if (meta.inheritanceType === 'tpt') {
|
|
172
|
+
this.computeTPTOwnProps(meta);
|
|
173
|
+
}
|
|
125
174
|
return meta;
|
|
126
175
|
});
|
|
127
176
|
}
|
|
128
|
-
findEntities(preferTs
|
|
129
|
-
this.
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
const paths =
|
|
133
|
-
const
|
|
177
|
+
async findEntities(preferTs) {
|
|
178
|
+
const { entities, entitiesTs, baseDir } = this.config.getAll();
|
|
179
|
+
const targets = (preferTs && entitiesTs.length > 0) ? entitiesTs : entities;
|
|
180
|
+
const processed = [];
|
|
181
|
+
const paths = [];
|
|
182
|
+
for (const entity of targets) {
|
|
183
|
+
if (typeof entity === 'string') {
|
|
184
|
+
paths.push(entity);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
processed.push(entity);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
134
190
|
if (paths.length > 0) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
return this.discoverDirectories(paths).then(() => {
|
|
139
|
-
this.discoverReferences(refs);
|
|
140
|
-
this.discoverMissingTargets();
|
|
141
|
-
this.validator.validateDiscovered(this.discovered, options);
|
|
142
|
-
return this.discovered;
|
|
143
|
-
});
|
|
191
|
+
const { discoverEntities } = await import('@mikro-orm/core/file-discovery');
|
|
192
|
+
processed.push(...await discoverEntities(paths, { baseDir }));
|
|
144
193
|
}
|
|
145
|
-
this.discoverReferences(
|
|
146
|
-
this.discoverMissingTargets();
|
|
147
|
-
this.validator.validateDiscovered(this.discovered, options);
|
|
148
|
-
return this.discovered;
|
|
194
|
+
return this.discoverReferences(processed);
|
|
149
195
|
}
|
|
150
196
|
discoverMissingTargets() {
|
|
151
197
|
const unwrap = (type) => type
|
|
@@ -154,17 +200,22 @@ export class MetadataDiscovery {
|
|
|
154
200
|
.replace(/\((.*)\)/, '$1'); // unwrap union types
|
|
155
201
|
const missing = [];
|
|
156
202
|
this.discovered.forEach(meta => Object.values(meta.properties).forEach(prop => {
|
|
157
|
-
if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.pivotEntity
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
203
|
+
if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.pivotEntity) {
|
|
204
|
+
const pivotEntity = prop.pivotEntity;
|
|
205
|
+
const target = typeof pivotEntity === 'function' && !pivotEntity.prototype
|
|
206
|
+
? pivotEntity()
|
|
207
|
+
: pivotEntity;
|
|
208
|
+
if (!this.discovered.find(m => m.className === Utils.className(target))) {
|
|
209
|
+
missing.push(target);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (prop.kind !== ReferenceKind.SCALAR) {
|
|
213
|
+
const target = typeof prop.entity === 'function' && !prop.entity.prototype
|
|
165
214
|
? prop.entity()
|
|
166
215
|
: prop.type;
|
|
167
|
-
|
|
216
|
+
if (!unwrap(prop.type).split(/ ?\| ?/).every(type => this.discovered.find(m => m.className === type))) {
|
|
217
|
+
missing.push(...Utils.asArray(target));
|
|
218
|
+
}
|
|
168
219
|
}
|
|
169
220
|
}));
|
|
170
221
|
if (missing.length > 0) {
|
|
@@ -173,122 +224,107 @@ export class MetadataDiscovery {
|
|
|
173
224
|
}
|
|
174
225
|
tryDiscoverTargets(targets) {
|
|
175
226
|
for (const target of targets) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const files = await globby(paths, { cwd: Utils.normalizePath(this.config.get('baseDir')) });
|
|
185
|
-
this.logger.log('discovery', `- processing ${colors.cyan('' + files.length)} files`);
|
|
186
|
-
const found = [];
|
|
187
|
-
for (const filepath of files) {
|
|
188
|
-
const filename = basename(filepath);
|
|
189
|
-
if (!filename.match(/\.[cm]?[jt]s$/) ||
|
|
190
|
-
filename.endsWith('.js.map') ||
|
|
191
|
-
filename.match(/\.d\.[cm]?ts/) ||
|
|
192
|
-
filename.startsWith('.') ||
|
|
193
|
-
filename.match(/index\.[cm]?[jt]s$/)) {
|
|
194
|
-
this.logger.log('discovery', `- ignoring file ${filename}`);
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
const name = this.namingStrategy.getClassName(filename);
|
|
198
|
-
const path = Utils.normalizePath(this.config.get('baseDir'), filepath);
|
|
199
|
-
const targets = await this.getEntityClassOrSchema(path, name);
|
|
200
|
-
for (const target of targets) {
|
|
201
|
-
if (!(target instanceof Function) && !(target instanceof EntitySchema)) {
|
|
202
|
-
this.logger.log('discovery', `- ignoring file ${filename}`);
|
|
203
|
-
continue;
|
|
227
|
+
const schema = target instanceof EntitySchema ? target : undefined;
|
|
228
|
+
const isDiscoverable = typeof target === 'function' || schema;
|
|
229
|
+
if (isDiscoverable && target.name) {
|
|
230
|
+
// Get the actual class for EntitySchema, or use target directly for classes
|
|
231
|
+
const targetClass = schema ? schema.meta.class : target;
|
|
232
|
+
if (!this.metadata.has(targetClass)) {
|
|
233
|
+
this.discoverReferences([target], false);
|
|
234
|
+
this.discoverMissingTargets();
|
|
204
235
|
}
|
|
205
|
-
const entity = this.prepare(target);
|
|
206
|
-
const schema = this.getSchema(entity, path);
|
|
207
|
-
const meta = schema.init().meta;
|
|
208
|
-
this.metadata.set(meta.className, meta);
|
|
209
|
-
found.push([schema, path]);
|
|
210
236
|
}
|
|
211
237
|
}
|
|
212
|
-
for (const [schema, path] of found) {
|
|
213
|
-
this.discoverEntity(schema, path);
|
|
214
|
-
}
|
|
215
238
|
}
|
|
216
|
-
discoverReferences(refs) {
|
|
239
|
+
discoverReferences(refs, validate = true) {
|
|
217
240
|
const found = [];
|
|
218
241
|
for (const entity of refs) {
|
|
219
|
-
|
|
242
|
+
if (typeof entity === 'string') {
|
|
243
|
+
throw new Error('Folder based discovery requires the async `MikroORM.init()` method.');
|
|
244
|
+
}
|
|
245
|
+
const schema = this.getSchema(entity);
|
|
220
246
|
const meta = schema.init().meta;
|
|
221
|
-
this.metadata.set(meta.
|
|
247
|
+
this.metadata.set(meta.class, meta);
|
|
222
248
|
found.push(schema);
|
|
223
249
|
}
|
|
224
250
|
// discover parents (base entities) automatically
|
|
225
251
|
for (const meta of this.metadata) {
|
|
226
252
|
let parent = meta.extends;
|
|
227
|
-
if (parent instanceof EntitySchema && !this.metadata.has(parent.meta.
|
|
228
|
-
this.discoverReferences([parent]);
|
|
253
|
+
if (parent instanceof EntitySchema && !this.metadata.has(parent.init().meta.class)) {
|
|
254
|
+
this.discoverReferences([parent], false);
|
|
255
|
+
}
|
|
256
|
+
if (typeof parent === 'function' && parent.name && !this.metadata.has(parent)) {
|
|
257
|
+
this.discoverReferences([parent], false);
|
|
229
258
|
}
|
|
230
|
-
/* v8 ignore next
|
|
259
|
+
/* v8 ignore next */
|
|
231
260
|
if (!meta.class) {
|
|
232
261
|
continue;
|
|
233
262
|
}
|
|
234
263
|
parent = Object.getPrototypeOf(meta.class);
|
|
235
|
-
if
|
|
236
|
-
|
|
264
|
+
// Skip if parent is the auto-generated base class for the same entity (from setClass usage)
|
|
265
|
+
if (parent.name !== '' && parent.name !== meta.className && !this.metadata.has(parent) && parent !== BaseEntity) {
|
|
266
|
+
this.discoverReferences([parent], false);
|
|
237
267
|
}
|
|
238
268
|
}
|
|
239
269
|
for (const schema of found) {
|
|
240
270
|
this.discoverEntity(schema);
|
|
241
271
|
}
|
|
272
|
+
this.discoverMissingTargets();
|
|
273
|
+
if (validate) {
|
|
274
|
+
this.validator.validateDiscovered(this.discovered, this.config.get('discovery'));
|
|
275
|
+
}
|
|
242
276
|
return this.discovered.filter(meta => found.find(m => m.name === meta.className));
|
|
243
277
|
}
|
|
244
|
-
reset(
|
|
245
|
-
const exists = this.discovered.findIndex(m => m.className === className);
|
|
278
|
+
reset(entityName) {
|
|
279
|
+
const exists = this.discovered.findIndex(m => m.class === entityName || m.className === Utils.className(entityName));
|
|
246
280
|
if (exists !== -1) {
|
|
247
|
-
this.metadata.reset(this.discovered[exists].
|
|
281
|
+
this.metadata.reset(this.discovered[exists].class);
|
|
248
282
|
this.discovered.splice(exists, 1);
|
|
249
283
|
}
|
|
250
284
|
}
|
|
251
|
-
|
|
252
|
-
/* v8 ignore next 3 */
|
|
253
|
-
if ('schema' in entity && entity.schema instanceof EntitySchema) {
|
|
254
|
-
return entity.schema;
|
|
255
|
-
}
|
|
285
|
+
getSchema(entity) {
|
|
256
286
|
if (EntitySchema.REGISTRY.has(entity)) {
|
|
257
|
-
|
|
287
|
+
entity = EntitySchema.REGISTRY.get(entity);
|
|
258
288
|
}
|
|
259
|
-
return entity;
|
|
260
|
-
}
|
|
261
|
-
getSchema(entity, filepath) {
|
|
262
289
|
if (entity instanceof EntitySchema) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
MetadataStorage.getMetadata(entity.meta.className, filepath);
|
|
266
|
-
}
|
|
267
|
-
return entity;
|
|
290
|
+
const meta = Utils.copy(entity.meta, false);
|
|
291
|
+
return EntitySchema.fromMetadata(meta);
|
|
268
292
|
}
|
|
269
293
|
const path = entity[MetadataStorage.PATH_SYMBOL];
|
|
270
294
|
if (path) {
|
|
271
295
|
const meta = Utils.copy(MetadataStorage.getMetadata(entity.name, path), false);
|
|
272
|
-
meta.path =
|
|
273
|
-
this.metadata.set(entity
|
|
296
|
+
meta.path = path;
|
|
297
|
+
this.metadata.set(entity, meta);
|
|
274
298
|
}
|
|
275
|
-
const exists = this.metadata.has(entity
|
|
276
|
-
const meta = this.metadata.get(entity
|
|
299
|
+
const exists = this.metadata.has(entity);
|
|
300
|
+
const meta = this.metadata.get(entity, true);
|
|
277
301
|
meta.abstract ??= !(exists && meta.name);
|
|
278
302
|
const schema = EntitySchema.fromMetadata(meta);
|
|
279
303
|
schema.setClass(entity);
|
|
280
304
|
return schema;
|
|
281
305
|
}
|
|
282
|
-
|
|
283
|
-
|
|
306
|
+
getRootEntity(meta) {
|
|
307
|
+
const base = meta.extends && this.metadata.find(meta.extends);
|
|
308
|
+
if (!base || base === meta) { // make sure we do not fall into infinite loop
|
|
309
|
+
return meta;
|
|
310
|
+
}
|
|
311
|
+
const root = this.getRootEntity(base);
|
|
312
|
+
// For STI or TPT, use the root entity.
|
|
313
|
+
// Check both `inheritanceType` (set during discovery) and raw `inheritance` option (set before discovery).
|
|
314
|
+
if (root.discriminatorColumn || root.inheritanceType || root.inheritance === 'tpt') {
|
|
315
|
+
return root;
|
|
316
|
+
}
|
|
317
|
+
return meta;
|
|
318
|
+
}
|
|
319
|
+
discoverEntity(schema) {
|
|
284
320
|
const meta = schema.meta;
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
const
|
|
321
|
+
const path = meta.path;
|
|
322
|
+
this.logger.log('discovery', `- processing entity ${colors.cyan(meta.className)}${colors.grey(path ? ` (${path})` : '')}`);
|
|
323
|
+
const root = this.getRootEntity(meta);
|
|
324
|
+
schema.meta.path = meta.path;
|
|
325
|
+
const cache = this.metadataProvider.getCachedMetadata(meta, root);
|
|
288
326
|
if (cache) {
|
|
289
327
|
this.logger.log('discovery', `- using cached metadata for entity ${colors.cyan(meta.className)}`);
|
|
290
|
-
this.metadataProvider.loadFromCache(meta, cache);
|
|
291
|
-
meta.root = root;
|
|
292
328
|
this.discovered.push(meta);
|
|
293
329
|
return;
|
|
294
330
|
}
|
|
@@ -297,50 +333,18 @@ export class MetadataDiscovery {
|
|
|
297
333
|
this.inferDefaultValue(meta, prop);
|
|
298
334
|
}
|
|
299
335
|
// if the definition is using EntitySchema we still want it to go through the metadata provider to validate no types are missing
|
|
300
|
-
this.metadataProvider.loadEntityMetadata(meta
|
|
301
|
-
if (!meta.
|
|
336
|
+
this.metadataProvider.loadEntityMetadata(meta);
|
|
337
|
+
if (!meta.tableName && meta.name) {
|
|
302
338
|
const entityName = root.discriminatorColumn ? root.name : meta.name;
|
|
303
|
-
meta.
|
|
339
|
+
meta.tableName = this.namingStrategy.classToTableName(entityName);
|
|
304
340
|
}
|
|
305
|
-
|
|
306
|
-
this.saveToCache(meta);
|
|
341
|
+
this.metadataProvider.saveToCache(meta);
|
|
307
342
|
meta.root = root;
|
|
308
343
|
this.discovered.push(meta);
|
|
309
344
|
}
|
|
310
|
-
saveToCache(meta) {
|
|
311
|
-
if (!this.metadataProvider.useCache()) {
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
const copy = Utils.copy(meta, false);
|
|
315
|
-
for (const prop of copy.props) {
|
|
316
|
-
if (Type.isMappedType(prop.type)) {
|
|
317
|
-
Reflect.deleteProperty(prop, 'type');
|
|
318
|
-
Reflect.deleteProperty(prop, 'customType');
|
|
319
|
-
}
|
|
320
|
-
if (prop.default) {
|
|
321
|
-
const raw = RawQueryFragment.getKnownFragment(prop.default);
|
|
322
|
-
if (raw) {
|
|
323
|
-
prop.defaultRaw ??= this.platform.formatQuery(raw.sql, raw.params);
|
|
324
|
-
Reflect.deleteProperty(prop, 'default');
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
Reflect.deleteProperty(prop, 'targetMeta');
|
|
328
|
-
}
|
|
329
|
-
[
|
|
330
|
-
'prototype', 'props', 'referencingProperties', 'propertyOrder', 'relations',
|
|
331
|
-
'concurrencyCheckKeys', 'checks',
|
|
332
|
-
].forEach(key => delete copy[key]);
|
|
333
|
-
// base entity without properties might not have path, but nothing to cache there
|
|
334
|
-
if (meta.path) {
|
|
335
|
-
this.cache.set(meta.className + extname(meta.path), copy, meta.path);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
345
|
initNullability(prop) {
|
|
339
|
-
if (prop.kind === ReferenceKind.MANY_TO_ONE) {
|
|
340
|
-
return Utils.defaultValue(prop, 'nullable', prop.optional || prop.cascade.includes(Cascade.REMOVE) || prop.cascade.includes(Cascade.ALL));
|
|
341
|
-
}
|
|
342
346
|
if (prop.kind === ReferenceKind.ONE_TO_ONE) {
|
|
343
|
-
return Utils.defaultValue(prop, 'nullable', prop.optional || !prop.owner
|
|
347
|
+
return Utils.defaultValue(prop, 'nullable', prop.optional || !prop.owner);
|
|
344
348
|
}
|
|
345
349
|
return Utils.defaultValue(prop, 'nullable', prop.optional);
|
|
346
350
|
}
|
|
@@ -364,6 +368,12 @@ export class MetadataDiscovery {
|
|
|
364
368
|
if (!prop.joinColumns || !prop.columnTypes || prop.ownColumns || ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
365
369
|
continue;
|
|
366
370
|
}
|
|
371
|
+
// For polymorphic relations, ownColumns should include all fieldNames
|
|
372
|
+
// (discriminator + ID columns) since they are all owned by this relation
|
|
373
|
+
if (prop.polymorphic) {
|
|
374
|
+
prop.ownColumns = prop.fieldNames;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
367
377
|
if (prop.joinColumns.length > 1) {
|
|
368
378
|
prop.ownColumns = prop.joinColumns.filter(col => {
|
|
369
379
|
return !meta.props.find(p => p.name !== prop.name && (!p.fieldNames || p.fieldNames.includes(col)));
|
|
@@ -375,7 +385,7 @@ export class MetadataDiscovery {
|
|
|
375
385
|
if (prop.joinColumns.length !== prop.columnTypes.length) {
|
|
376
386
|
prop.columnTypes = prop.joinColumns.flatMap(field => {
|
|
377
387
|
const matched = meta.props.find(p => p.fieldNames?.includes(field));
|
|
378
|
-
/* v8 ignore next
|
|
388
|
+
/* v8 ignore next */
|
|
379
389
|
if (!matched) {
|
|
380
390
|
throw MetadataError.fromWrongForeignKey(meta, prop, 'columnTypes');
|
|
381
391
|
}
|
|
@@ -394,30 +404,30 @@ export class MetadataDiscovery {
|
|
|
394
404
|
if (prop.kind === ReferenceKind.SCALAR || prop.kind === ReferenceKind.EMBEDDED) {
|
|
395
405
|
prop.fieldNames = [this.namingStrategy.propertyToColumnName(prop.name, object)];
|
|
396
406
|
}
|
|
397
|
-
else if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
407
|
+
else if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.polymorphic) {
|
|
398
408
|
prop.fieldNames = this.initManyToOneFieldName(prop, prop.name);
|
|
399
409
|
}
|
|
400
410
|
else if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
|
|
401
411
|
prop.fieldNames = this.initManyToManyFieldName(prop, prop.name);
|
|
402
412
|
}
|
|
403
413
|
}
|
|
404
|
-
initManyToOneFieldName(prop, name) {
|
|
405
|
-
const meta2 =
|
|
414
|
+
initManyToOneFieldName(prop, name, tableName) {
|
|
415
|
+
const meta2 = prop.targetMeta;
|
|
406
416
|
const ret = [];
|
|
407
417
|
for (const primaryKey of meta2.primaryKeys) {
|
|
408
418
|
this.initFieldName(meta2.properties[primaryKey]);
|
|
409
419
|
for (const fieldName of meta2.properties[primaryKey].fieldNames) {
|
|
410
|
-
ret.push(this.namingStrategy.joinKeyColumnName(name, fieldName, meta2.compositePK));
|
|
420
|
+
ret.push(this.namingStrategy.joinKeyColumnName(name, fieldName, meta2.compositePK, tableName));
|
|
411
421
|
}
|
|
412
422
|
}
|
|
413
423
|
return ret;
|
|
414
424
|
}
|
|
415
425
|
initManyToManyFieldName(prop, name) {
|
|
416
|
-
const meta2 =
|
|
426
|
+
const meta2 = prop.targetMeta;
|
|
417
427
|
return meta2.primaryKeys.map(() => this.namingStrategy.propertyToColumnName(name));
|
|
418
428
|
}
|
|
419
429
|
initManyToManyFields(meta, prop) {
|
|
420
|
-
const meta2 =
|
|
430
|
+
const meta2 = prop.targetMeta;
|
|
421
431
|
Utils.defaultValue(prop, 'fixedOrder', !!prop.fixedOrderColumn);
|
|
422
432
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
423
433
|
const props = Object.values(pivotMeta?.properties ?? {});
|
|
@@ -438,35 +448,77 @@ export class MetadataDiscovery {
|
|
|
438
448
|
prop.inverseJoinColumns ??= second.fieldNames;
|
|
439
449
|
}
|
|
440
450
|
if (!prop.pivotTable && prop.owner && this.platform.usesPivotTable()) {
|
|
441
|
-
prop.pivotTable = this.namingStrategy.joinTableName(meta.
|
|
451
|
+
prop.pivotTable = this.namingStrategy.joinTableName(meta.className, meta2.tableName, prop.name, meta.tableName);
|
|
442
452
|
}
|
|
443
453
|
if (prop.mappedBy) {
|
|
444
454
|
const prop2 = meta2.properties[prop.mappedBy];
|
|
445
455
|
this.initManyToManyFields(meta2, prop2);
|
|
446
456
|
prop.pivotTable = prop2.pivotTable;
|
|
447
|
-
prop.pivotEntity = prop2.pivotEntity
|
|
457
|
+
prop.pivotEntity = prop2.pivotEntity;
|
|
448
458
|
prop.fixedOrder = prop2.fixedOrder;
|
|
449
459
|
prop.fixedOrderColumn = prop2.fixedOrderColumn;
|
|
450
460
|
prop.joinColumns = prop2.inverseJoinColumns;
|
|
451
461
|
prop.inverseJoinColumns = prop2.joinColumns;
|
|
462
|
+
prop.polymorphic = prop2.polymorphic;
|
|
463
|
+
prop.discriminator = prop2.discriminator;
|
|
464
|
+
prop.discriminatorColumn = prop2.discriminatorColumn;
|
|
465
|
+
prop.discriminatorValue = prop2.discriminatorValue;
|
|
452
466
|
}
|
|
453
467
|
prop.referencedColumnNames ??= Utils.flatten(meta.primaryKeys.map(primaryKey => meta.properties[primaryKey].fieldNames));
|
|
454
|
-
|
|
455
|
-
prop.
|
|
468
|
+
// For polymorphic M:N, use discriminator base name for FK column (e.g., taggable_id instead of post_id)
|
|
469
|
+
if (prop.polymorphic && prop.discriminator) {
|
|
470
|
+
prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.namingStrategy.joinKeyColumnName(prop.discriminator, referencedColumnName, prop.referencedColumnNames.length > 1));
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
const ownerTableName = this.isExplicitTableName(meta.root) ? meta.root.tableName : undefined;
|
|
474
|
+
prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.namingStrategy.joinKeyColumnName(meta.root.className, referencedColumnName, meta.compositePK, ownerTableName));
|
|
475
|
+
}
|
|
476
|
+
const inverseTableName = this.isExplicitTableName(meta2.root) ? meta2.root.tableName : undefined;
|
|
477
|
+
prop.inverseJoinColumns ??= this.initManyToOneFieldName(prop, meta2.root.className, inverseTableName);
|
|
478
|
+
}
|
|
479
|
+
isExplicitTableName(meta) {
|
|
480
|
+
return meta.tableName !== this.namingStrategy.classToTableName(meta.className);
|
|
456
481
|
}
|
|
457
482
|
initManyToOneFields(prop) {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
483
|
+
if (prop.polymorphic && prop.polymorphTargets) {
|
|
484
|
+
const fieldNames1 = prop.targetMeta.getPrimaryProps().flatMap(pk => pk.fieldNames);
|
|
485
|
+
const idColumns = fieldNames1.map(fieldName => this.namingStrategy.joinKeyColumnName(prop.discriminator, fieldName, fieldNames1.length > 1));
|
|
486
|
+
prop.fieldNames ??= [prop.discriminatorColumn, ...idColumns];
|
|
487
|
+
prop.joinColumns ??= idColumns;
|
|
488
|
+
prop.referencedColumnNames ??= fieldNames1;
|
|
489
|
+
prop.referencedTableName ??= prop.targetMeta.tableName;
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
const meta2 = prop.targetMeta;
|
|
493
|
+
let fieldNames;
|
|
494
|
+
// If targetKey is specified, use that property's field names instead of PKs
|
|
495
|
+
if (prop.targetKey) {
|
|
496
|
+
const targetProp = meta2.properties[prop.targetKey];
|
|
497
|
+
fieldNames = targetProp.fieldNames;
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
fieldNames = Utils.flatten(meta2.primaryKeys.map(primaryKey => meta2.properties[primaryKey].fieldNames));
|
|
501
|
+
}
|
|
502
|
+
Utils.defaultValue(prop, 'referencedTableName', meta2.tableName);
|
|
461
503
|
if (!prop.joinColumns) {
|
|
462
504
|
prop.joinColumns = fieldNames.map(fieldName => this.namingStrategy.joinKeyColumnName(prop.name, fieldName, fieldNames.length > 1));
|
|
463
505
|
}
|
|
464
506
|
if (!prop.referencedColumnNames) {
|
|
465
507
|
prop.referencedColumnNames = fieldNames;
|
|
466
508
|
}
|
|
509
|
+
// Relations to composite PK targets need cascade update by default,
|
|
510
|
+
// since composite PKs are more likely to have mutable components
|
|
511
|
+
if (meta2.compositePK) {
|
|
512
|
+
prop.updateRule ??= 'cascade';
|
|
513
|
+
}
|
|
514
|
+
// Nullable relations default to 'set null' on delete - when the referenced
|
|
515
|
+
// entity is deleted, set the FK to null rather than failing
|
|
516
|
+
if (prop.nullable) {
|
|
517
|
+
prop.deleteRule ??= 'set null';
|
|
518
|
+
}
|
|
467
519
|
}
|
|
468
520
|
initOneToManyFields(prop) {
|
|
469
|
-
const meta2 =
|
|
521
|
+
const meta2 = prop.targetMeta;
|
|
470
522
|
if (!prop.joinColumns) {
|
|
471
523
|
prop.joinColumns = [this.namingStrategy.joinColumnName(prop.name)];
|
|
472
524
|
}
|
|
@@ -479,12 +531,17 @@ export class MetadataDiscovery {
|
|
|
479
531
|
const pks = Object.values(meta.properties).filter(prop => prop.primary);
|
|
480
532
|
meta.primaryKeys = pks.map(prop => prop.name);
|
|
481
533
|
meta.compositePK = pks.length > 1;
|
|
482
|
-
// FK used as PK, we need to cascade
|
|
483
|
-
|
|
484
|
-
|
|
534
|
+
// FK used as PK, we need to cascade - applies to both single FK-as-PK
|
|
535
|
+
// and composite PKs where all PKs are FKs (e.g., pivot-like entities)
|
|
536
|
+
const fkPks = pks.filter(pk => pk.kind !== ReferenceKind.SCALAR);
|
|
537
|
+
if (fkPks.length > 0 && fkPks.length === pks.length) {
|
|
538
|
+
for (const pk of fkPks) {
|
|
539
|
+
pk.deleteRule ??= 'cascade';
|
|
540
|
+
pk.updateRule ??= 'cascade';
|
|
541
|
+
}
|
|
485
542
|
}
|
|
486
543
|
meta.forceConstructor ??= this.shouldForceConstructorUsage(meta);
|
|
487
|
-
this.validator.validateEntityDefinition(this.metadata, meta.
|
|
544
|
+
this.validator.validateEntityDefinition(this.metadata, meta.class, this.config.get('discovery'));
|
|
488
545
|
for (const prop of Object.values(meta.properties)) {
|
|
489
546
|
this.initNullability(prop);
|
|
490
547
|
this.applyNamingStrategy(meta, prop);
|
|
@@ -497,15 +554,21 @@ export class MetadataDiscovery {
|
|
|
497
554
|
}
|
|
498
555
|
this.initOwnColumns(meta);
|
|
499
556
|
meta.simplePK = pks.length === 1 && pks[0].kind === ReferenceKind.SCALAR && !pks[0].customType && pks[0].runtimeType !== 'Date';
|
|
500
|
-
meta.serializedPrimaryKey
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
serializedPKProp.persist = false;
|
|
557
|
+
meta.serializedPrimaryKey ??= meta.props.find(prop => prop.serializedPrimaryKey)?.name;
|
|
558
|
+
if (meta.serializedPrimaryKey && meta.serializedPrimaryKey !== meta.primaryKeys[0]) {
|
|
559
|
+
meta.properties[meta.serializedPrimaryKey].persist ??= false;
|
|
504
560
|
}
|
|
505
561
|
if (this.platform.usesPivotTable()) {
|
|
506
562
|
return Object.values(meta.properties)
|
|
507
563
|
.filter(prop => prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && prop.pivotTable)
|
|
508
|
-
.map(prop =>
|
|
564
|
+
.map(prop => {
|
|
565
|
+
const pivotMeta = this.definePivotTableEntity(meta, prop);
|
|
566
|
+
prop.pivotEntity = pivotMeta.class;
|
|
567
|
+
if (prop.inversedBy) {
|
|
568
|
+
prop.targetMeta.properties[prop.inversedBy].pivotEntity = pivotMeta.class;
|
|
569
|
+
}
|
|
570
|
+
return pivotMeta;
|
|
571
|
+
});
|
|
509
572
|
}
|
|
510
573
|
return [];
|
|
511
574
|
}
|
|
@@ -522,8 +585,11 @@ export class MetadataDiscovery {
|
|
|
522
585
|
['mappedBy', 'inversedBy', 'pivotEntity'].forEach(type => {
|
|
523
586
|
const value = prop[type];
|
|
524
587
|
if (value instanceof Function) {
|
|
525
|
-
const meta2 = this.metadata.get(prop.
|
|
588
|
+
const meta2 = prop.targetMeta ?? this.metadata.get(prop.target);
|
|
526
589
|
prop[type] = value(meta2.properties)?.name;
|
|
590
|
+
if (type === 'pivotEntity' && value) {
|
|
591
|
+
prop[type] = value(meta2.properties);
|
|
592
|
+
}
|
|
527
593
|
if (prop[type] == null) {
|
|
528
594
|
throw MetadataError.fromWrongReference(meta, prop, type);
|
|
529
595
|
}
|
|
@@ -539,9 +605,9 @@ export class MetadataDiscovery {
|
|
|
539
605
|
}
|
|
540
606
|
else if (fks.length >= 2) {
|
|
541
607
|
[first, second] = fks;
|
|
542
|
-
/* v8 ignore next 3 */
|
|
543
608
|
}
|
|
544
609
|
else {
|
|
610
|
+
/* v8 ignore next */
|
|
545
611
|
return [];
|
|
546
612
|
}
|
|
547
613
|
// wrong FK order, first FK needs to point to the owning side
|
|
@@ -555,7 +621,9 @@ export class MetadataDiscovery {
|
|
|
555
621
|
return [first, second];
|
|
556
622
|
}
|
|
557
623
|
definePivotTableEntity(meta, prop) {
|
|
558
|
-
const pivotMeta =
|
|
624
|
+
const pivotMeta = prop.pivotEntity
|
|
625
|
+
? this.metadata.find(prop.pivotEntity)
|
|
626
|
+
: this.metadata.getByClassName(prop.pivotTable, false);
|
|
559
627
|
// ensure inverse side exists so we can join it when populating via pivot tables
|
|
560
628
|
if (!prop.inversedBy && prop.targetMeta) {
|
|
561
629
|
const inverseName = `${meta.className}_${prop.name}__inverse`;
|
|
@@ -564,6 +632,8 @@ export class MetadataDiscovery {
|
|
|
564
632
|
name: inverseName,
|
|
565
633
|
kind: ReferenceKind.MANY_TO_MANY,
|
|
566
634
|
type: meta.className,
|
|
635
|
+
target: meta.class,
|
|
636
|
+
targetMeta: meta,
|
|
567
637
|
mappedBy: prop.name,
|
|
568
638
|
pivotEntity: prop.pivotEntity,
|
|
569
639
|
pivotTable: prop.pivotTable,
|
|
@@ -572,55 +642,190 @@ export class MetadataDiscovery {
|
|
|
572
642
|
};
|
|
573
643
|
this.applyNamingStrategy(prop.targetMeta, inverseProp);
|
|
574
644
|
this.initCustomType(prop.targetMeta, inverseProp);
|
|
575
|
-
this.initRelation(inverseProp);
|
|
576
645
|
prop.targetMeta.properties[inverseName] = inverseProp;
|
|
577
646
|
}
|
|
578
647
|
if (pivotMeta) {
|
|
648
|
+
prop.pivotEntity = pivotMeta.class;
|
|
579
649
|
this.ensureCorrectFKOrderInPivotEntity(pivotMeta, prop);
|
|
650
|
+
if (prop.polymorphic && prop.discriminatorValue) {
|
|
651
|
+
pivotMeta.polymorphicDiscriminatorMap ??= {};
|
|
652
|
+
pivotMeta.polymorphicDiscriminatorMap[prop.discriminatorValue] = meta.class;
|
|
653
|
+
// For composite PK entities sharing a polymorphic pivot table,
|
|
654
|
+
// we need to add columns for each entity type's PKs
|
|
655
|
+
this.addPolymorphicPivotColumns(pivotMeta, meta, prop);
|
|
656
|
+
// Add virtual M:1 relation for this polymorphic owner (for join loading)
|
|
657
|
+
const ownerRelationName = `${prop.discriminator}_${meta.tableName}`;
|
|
658
|
+
if (!pivotMeta.properties[ownerRelationName]) {
|
|
659
|
+
pivotMeta.properties[ownerRelationName] = this.definePolymorphicOwnerRelation(prop, ownerRelationName, meta);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
580
662
|
return pivotMeta;
|
|
581
663
|
}
|
|
582
|
-
const exists = this.metadata.find(prop.pivotTable);
|
|
583
|
-
if (exists) {
|
|
584
|
-
prop.pivotEntity = exists.className;
|
|
585
|
-
return exists;
|
|
586
|
-
}
|
|
587
664
|
let tableName = prop.pivotTable;
|
|
588
665
|
let schemaName;
|
|
589
666
|
if (prop.pivotTable.includes('.')) {
|
|
590
667
|
[schemaName, tableName] = prop.pivotTable.split('.');
|
|
591
668
|
}
|
|
592
669
|
schemaName ??= meta.schema;
|
|
593
|
-
const
|
|
594
|
-
const
|
|
670
|
+
const targetMeta = prop.targetMeta;
|
|
671
|
+
const targetType = targetMeta.className;
|
|
672
|
+
const pivotMeta2 = new EntityMetadata({
|
|
595
673
|
name: prop.pivotTable,
|
|
596
674
|
className: prop.pivotTable,
|
|
597
675
|
collection: tableName,
|
|
598
676
|
schema: schemaName,
|
|
599
677
|
pivotTable: true,
|
|
600
678
|
});
|
|
601
|
-
prop.pivotEntity =
|
|
679
|
+
prop.pivotEntity = pivotMeta2.class;
|
|
602
680
|
if (prop.fixedOrder) {
|
|
603
|
-
const primaryProp = this.defineFixedOrderProperty(prop,
|
|
604
|
-
|
|
681
|
+
const primaryProp = this.defineFixedOrderProperty(prop, targetMeta);
|
|
682
|
+
pivotMeta2.properties[primaryProp.name] = primaryProp;
|
|
605
683
|
}
|
|
606
684
|
else {
|
|
607
|
-
|
|
685
|
+
pivotMeta2.compositePK = true;
|
|
608
686
|
}
|
|
609
687
|
// handle self-referenced m:n with same default field names
|
|
610
688
|
if (meta.className === targetType && prop.joinColumns.every((joinColumn, idx) => joinColumn === prop.inverseJoinColumns[idx])) {
|
|
611
|
-
|
|
612
|
-
|
|
689
|
+
// use tableName only when explicitly provided by user, otherwise use className for backwards compatibility
|
|
690
|
+
const baseName = this.isExplicitTableName(meta) ? meta.tableName : meta.className;
|
|
691
|
+
prop.joinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(baseName + '_1', name, meta.compositePK));
|
|
692
|
+
prop.inverseJoinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(baseName + '_2', name, meta.compositePK));
|
|
613
693
|
if (prop.inversedBy) {
|
|
614
|
-
const prop2 =
|
|
694
|
+
const prop2 = targetMeta.properties[prop.inversedBy];
|
|
615
695
|
prop2.inverseJoinColumns = prop.joinColumns;
|
|
616
696
|
prop2.joinColumns = prop.inverseJoinColumns;
|
|
617
697
|
}
|
|
698
|
+
// propagate updated joinColumns to all child entities that inherit this property (STI)
|
|
699
|
+
for (const childMeta of this.discovered.filter(m => m.root === meta && m !== meta)) {
|
|
700
|
+
const childProp = childMeta.properties[prop.name];
|
|
701
|
+
if (childProp) {
|
|
702
|
+
childProp.joinColumns = prop.joinColumns;
|
|
703
|
+
childProp.inverseJoinColumns = prop.inverseJoinColumns;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
// For polymorphic M:N, create discriminator column and polymorphic FK
|
|
708
|
+
if (prop.polymorphic && prop.discriminatorColumn) {
|
|
709
|
+
this.definePolymorphicPivotProperties(pivotMeta2, meta, prop, targetMeta);
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
pivotMeta2.properties[meta.name + '_owner'] = this.definePivotProperty(prop, meta.name + '_owner', meta.class, targetType + '_inverse', true, meta.className === targetType);
|
|
713
|
+
pivotMeta2.properties[targetType + '_inverse'] = this.definePivotProperty(prop, targetType + '_inverse', targetMeta.class, meta.name + '_owner', false, meta.className === targetType);
|
|
714
|
+
}
|
|
715
|
+
return this.metadata.set(pivotMeta2.class, EntitySchema.fromMetadata(pivotMeta2).init().meta);
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Create a scalar property for a pivot table column.
|
|
719
|
+
*/
|
|
720
|
+
createPivotScalarProperty(name, columnTypes, fieldNames = [name], options = {}) {
|
|
721
|
+
return {
|
|
722
|
+
name,
|
|
723
|
+
fieldNames,
|
|
724
|
+
columnTypes,
|
|
725
|
+
type: options.type ?? 'number',
|
|
726
|
+
kind: ReferenceKind.SCALAR,
|
|
727
|
+
primary: options.primary ?? false,
|
|
728
|
+
nullable: options.nullable ?? true,
|
|
729
|
+
...(options.persist !== undefined && { persist: options.persist }),
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Get column types for an entity's primary keys, initializing them if needed.
|
|
734
|
+
*/
|
|
735
|
+
getPrimaryKeyColumnTypes(meta) {
|
|
736
|
+
const columnTypes = [];
|
|
737
|
+
for (const pk of meta.primaryKeys) {
|
|
738
|
+
const pkProp = meta.properties[pk];
|
|
739
|
+
this.initCustomType(meta, pkProp);
|
|
740
|
+
this.initColumnType(pkProp);
|
|
741
|
+
columnTypes.push(...pkProp.columnTypes);
|
|
742
|
+
}
|
|
743
|
+
return columnTypes;
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Add missing FK columns for a polymorphic entity to an existing pivot table.
|
|
747
|
+
*/
|
|
748
|
+
addPolymorphicPivotColumns(pivotMeta, meta, prop) {
|
|
749
|
+
const existingFieldNames = new Set(Object.values(pivotMeta.properties).flatMap(p => p.fieldNames ?? []));
|
|
750
|
+
const columnTypes = this.getPrimaryKeyColumnTypes(meta);
|
|
751
|
+
for (let i = 0; i < prop.joinColumns.length; i++) {
|
|
752
|
+
const joinColumn = prop.joinColumns[i];
|
|
753
|
+
if (!existingFieldNames.has(joinColumn)) {
|
|
754
|
+
pivotMeta.properties[joinColumn] = this.createPivotScalarProperty(joinColumn, [columnTypes[i]]);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Define properties for a polymorphic pivot table.
|
|
760
|
+
*/
|
|
761
|
+
definePolymorphicPivotProperties(pivotMeta, meta, prop, targetMeta) {
|
|
762
|
+
const discriminatorColumn = prop.discriminatorColumn;
|
|
763
|
+
const isCompositePK = meta.compositePK;
|
|
764
|
+
// For composite PK polymorphic M:N, we need fixedOrder (auto-increment PK)
|
|
765
|
+
if (isCompositePK && !prop.fixedOrder) {
|
|
766
|
+
prop.fixedOrder = true;
|
|
767
|
+
const primaryProp = this.defineFixedOrderProperty(prop, targetMeta);
|
|
768
|
+
pivotMeta.properties[primaryProp.name] = primaryProp;
|
|
769
|
+
pivotMeta.compositePK = false;
|
|
770
|
+
}
|
|
771
|
+
const discriminatorProp = this.createPivotScalarProperty(discriminatorColumn, [this.platform.getVarcharTypeDeclarationSQL(prop)], [discriminatorColumn], { type: 'string', primary: !isCompositePK, nullable: false });
|
|
772
|
+
this.initFieldName(discriminatorProp);
|
|
773
|
+
pivotMeta.properties[discriminatorColumn] = discriminatorProp;
|
|
774
|
+
const columnTypes = this.getPrimaryKeyColumnTypes(meta);
|
|
775
|
+
if (isCompositePK) {
|
|
776
|
+
// Create separate properties for each PK column (nullable for other entity types)
|
|
777
|
+
for (let i = 0; i < prop.joinColumns.length; i++) {
|
|
778
|
+
pivotMeta.properties[prop.joinColumns[i]] = this.createPivotScalarProperty(prop.joinColumns[i], [columnTypes[i]]);
|
|
779
|
+
}
|
|
780
|
+
// Virtual property combining all columns (for compatibility)
|
|
781
|
+
pivotMeta.properties[prop.discriminator] = this.createPivotScalarProperty(prop.discriminator, columnTypes, [...prop.joinColumns], { type: meta.className, persist: false });
|
|
618
782
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
783
|
+
else {
|
|
784
|
+
pivotMeta.properties[prop.discriminator] = this.createPivotScalarProperty(prop.discriminator, columnTypes, [...prop.joinColumns], { type: meta.className, primary: true, nullable: false });
|
|
785
|
+
}
|
|
786
|
+
pivotMeta.properties[targetMeta.className + '_inverse'] = this.definePivotProperty(prop, targetMeta.className + '_inverse', targetMeta.class, prop.discriminator, false, false);
|
|
787
|
+
// Create virtual M:1 relation to the polymorphic owner for single-query join loading
|
|
788
|
+
const ownerRelationName = `${prop.discriminator}_${meta.tableName}`;
|
|
789
|
+
pivotMeta.properties[ownerRelationName] = this.definePolymorphicOwnerRelation(prop, ownerRelationName, meta);
|
|
790
|
+
pivotMeta.polymorphicDiscriminatorMap ??= {};
|
|
791
|
+
pivotMeta.polymorphicDiscriminatorMap[prop.discriminatorValue] = meta.class;
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Create a virtual M:1 relation from pivot to a polymorphic owner entity.
|
|
795
|
+
* This enables single-query join loading for inverse-side polymorphic M:N.
|
|
796
|
+
*/
|
|
797
|
+
definePolymorphicOwnerRelation(prop, name, targetMeta) {
|
|
798
|
+
const ret = {
|
|
799
|
+
name,
|
|
800
|
+
type: targetMeta.className,
|
|
801
|
+
target: targetMeta.class,
|
|
802
|
+
kind: ReferenceKind.MANY_TO_ONE,
|
|
803
|
+
nullable: true,
|
|
804
|
+
owner: true,
|
|
805
|
+
primary: false,
|
|
806
|
+
createForeignKeyConstraint: false,
|
|
807
|
+
persist: false,
|
|
808
|
+
index: false,
|
|
809
|
+
};
|
|
810
|
+
ret.targetMeta = targetMeta;
|
|
811
|
+
ret.fieldNames = ret.joinColumns = ret.ownColumns = [...prop.joinColumns];
|
|
812
|
+
ret.referencedColumnNames = [];
|
|
813
|
+
ret.inverseJoinColumns = [];
|
|
814
|
+
for (const primaryKey of targetMeta.primaryKeys) {
|
|
815
|
+
const pkProp = targetMeta.properties[primaryKey];
|
|
816
|
+
ret.referencedColumnNames.push(...pkProp.fieldNames);
|
|
817
|
+
ret.inverseJoinColumns.push(...pkProp.fieldNames);
|
|
818
|
+
ret.length = pkProp.length;
|
|
819
|
+
ret.precision = pkProp.precision;
|
|
820
|
+
ret.scale = pkProp.scale;
|
|
821
|
+
}
|
|
822
|
+
const schema = targetMeta.schema ?? this.config.get('schema') ?? this.platform.getDefaultSchemaName();
|
|
823
|
+
ret.referencedTableName = schema && schema !== '*' ? schema + '.' + targetMeta.tableName : targetMeta.tableName;
|
|
824
|
+
this.initColumnType(ret);
|
|
825
|
+
this.initRelation(ret);
|
|
826
|
+
return ret;
|
|
622
827
|
}
|
|
623
|
-
defineFixedOrderProperty(prop,
|
|
828
|
+
defineFixedOrderProperty(prop, targetMeta) {
|
|
624
829
|
const pk = prop.fixedOrderColumn || this.namingStrategy.referenceColumnName();
|
|
625
830
|
const primaryProp = {
|
|
626
831
|
name: pk,
|
|
@@ -634,7 +839,7 @@ export class MetadataDiscovery {
|
|
|
634
839
|
this.initColumnType(primaryProp);
|
|
635
840
|
prop.fixedOrderColumn = pk;
|
|
636
841
|
if (prop.inversedBy) {
|
|
637
|
-
const prop2 =
|
|
842
|
+
const prop2 = targetMeta.properties[prop.inversedBy];
|
|
638
843
|
prop2.fixedOrder = true;
|
|
639
844
|
prop2.fixedOrderColumn = pk;
|
|
640
845
|
}
|
|
@@ -643,7 +848,8 @@ export class MetadataDiscovery {
|
|
|
643
848
|
definePivotProperty(prop, name, type, inverse, owner, selfReferencing) {
|
|
644
849
|
const ret = {
|
|
645
850
|
name,
|
|
646
|
-
type,
|
|
851
|
+
type: Utils.className(type),
|
|
852
|
+
target: type,
|
|
647
853
|
kind: ReferenceKind.MANY_TO_ONE,
|
|
648
854
|
cascade: [Cascade.ALL],
|
|
649
855
|
fixedOrder: prop.fixedOrder,
|
|
@@ -653,11 +859,11 @@ export class MetadataDiscovery {
|
|
|
653
859
|
autoincrement: false,
|
|
654
860
|
updateRule: prop.updateRule,
|
|
655
861
|
deleteRule: prop.deleteRule,
|
|
862
|
+
createForeignKeyConstraint: prop.createForeignKeyConstraint,
|
|
656
863
|
};
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
}
|
|
864
|
+
const defaultRule = selfReferencing && !this.platform.supportsMultipleCascadePaths() ? 'no action' : 'cascade';
|
|
865
|
+
ret.updateRule ??= defaultRule;
|
|
866
|
+
ret.deleteRule ??= defaultRule;
|
|
661
867
|
const meta = this.metadata.get(type);
|
|
662
868
|
ret.targetMeta = meta;
|
|
663
869
|
ret.joinColumns = [];
|
|
@@ -700,7 +906,7 @@ export class MetadataDiscovery {
|
|
|
700
906
|
Object.values(meta.properties)
|
|
701
907
|
.filter(prop => prop.kind !== ReferenceKind.SCALAR && !prop.owner && prop.mappedBy)
|
|
702
908
|
.forEach(prop => {
|
|
703
|
-
const meta2 =
|
|
909
|
+
const meta2 = prop.targetMeta;
|
|
704
910
|
const prop2 = meta2.properties[prop.mappedBy];
|
|
705
911
|
if (prop2 && !prop2.inversedBy) {
|
|
706
912
|
prop2.inversedBy = prop.name;
|
|
@@ -708,7 +914,7 @@ export class MetadataDiscovery {
|
|
|
708
914
|
});
|
|
709
915
|
}
|
|
710
916
|
defineBaseEntityProperties(meta) {
|
|
711
|
-
const base = meta.extends && this.metadata.get(
|
|
917
|
+
const base = meta.extends && this.metadata.get(meta.extends);
|
|
712
918
|
if (!base || base === meta) { // make sure we do not fall into infinite loop
|
|
713
919
|
return 0;
|
|
714
920
|
}
|
|
@@ -739,12 +945,9 @@ export class MetadataDiscovery {
|
|
|
739
945
|
Utils.keys(base.hooks).forEach(type => {
|
|
740
946
|
meta.hooks[type] = Utils.unique([...base.hooks[type], ...(meta.hooks[type] || [])]);
|
|
741
947
|
});
|
|
742
|
-
if (meta.constructorParams
|
|
948
|
+
if ((meta.constructorParams?.length ?? 0) === 0 && (base.constructorParams?.length ?? 0) > 0) {
|
|
743
949
|
meta.constructorParams = [...base.constructorParams];
|
|
744
950
|
}
|
|
745
|
-
if (meta.toJsonParams.length === 0 && base.toJsonParams.length > 0) {
|
|
746
|
-
meta.toJsonParams = [...base.toJsonParams];
|
|
747
|
-
}
|
|
748
951
|
return order;
|
|
749
952
|
}
|
|
750
953
|
initPolyEmbeddables(embeddedProp, discovered, visited = new Set()) {
|
|
@@ -765,6 +968,7 @@ export class MetadataDiscovery {
|
|
|
765
968
|
delete prop.default;
|
|
766
969
|
if (properties[prop.name] && properties[prop.name].type !== prop.type) {
|
|
767
970
|
properties[prop.name].type = `${properties[prop.name].type} | ${prop.type}`;
|
|
971
|
+
properties[prop.name].runtimeType = 'any';
|
|
768
972
|
return properties[prop.name];
|
|
769
973
|
}
|
|
770
974
|
return properties[prop.name] = prop;
|
|
@@ -800,6 +1004,54 @@ export class MetadataDiscovery {
|
|
|
800
1004
|
polymorphs.forEach(meta => meta.root = embeddable);
|
|
801
1005
|
}
|
|
802
1006
|
}
|
|
1007
|
+
initPolymorphicRelation(meta, prop, discovered) {
|
|
1008
|
+
if (!prop.discriminator && !prop.discriminatorColumn && !prop.discriminatorMap && !Array.isArray(prop.target)) {
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
prop.polymorphic = true;
|
|
1012
|
+
prop.discriminator ??= prop.name;
|
|
1013
|
+
prop.discriminatorColumn ??= this.namingStrategy.discriminatorColumnName(prop.discriminator);
|
|
1014
|
+
prop.createForeignKeyConstraint = false;
|
|
1015
|
+
const isToOne = [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind);
|
|
1016
|
+
if (isToOne) {
|
|
1017
|
+
const types = prop.type.split(/ ?\| ?/);
|
|
1018
|
+
prop.polymorphTargets = discovered.filter(m => types.includes(m.className) && !m.embeddable);
|
|
1019
|
+
prop.targetMeta = prop.polymorphTargets[0];
|
|
1020
|
+
prop.referencedPKs = prop.targetMeta?.primaryKeys;
|
|
1021
|
+
}
|
|
1022
|
+
if (prop.discriminatorMap) {
|
|
1023
|
+
const normalizedMap = {};
|
|
1024
|
+
for (const [key, value] of Object.entries(prop.discriminatorMap)) {
|
|
1025
|
+
const targetMeta = this.metadata.getByClassName(value, false);
|
|
1026
|
+
if (!targetMeta) {
|
|
1027
|
+
throw MetadataError.fromUnknownEntity(value, `${meta.className}.${prop.name} discriminatorMap`);
|
|
1028
|
+
}
|
|
1029
|
+
normalizedMap[key] = targetMeta.class;
|
|
1030
|
+
if (targetMeta.class === meta.class) {
|
|
1031
|
+
prop.discriminatorValue = key;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
prop.discriminatorMap = normalizedMap;
|
|
1035
|
+
}
|
|
1036
|
+
else if (isToOne) {
|
|
1037
|
+
prop.discriminatorMap = {};
|
|
1038
|
+
const tableNameToTarget = new Map();
|
|
1039
|
+
for (const target of prop.polymorphTargets) {
|
|
1040
|
+
const existing = tableNameToTarget.get(target.tableName);
|
|
1041
|
+
if (existing) {
|
|
1042
|
+
throw MetadataError.incompatiblePolymorphicTargets(meta, prop, existing, target, `both use table '${target.tableName}'. Use separate properties instead of a single polymorphic relation.`);
|
|
1043
|
+
}
|
|
1044
|
+
tableNameToTarget.set(target.tableName, target);
|
|
1045
|
+
prop.discriminatorMap[target.tableName] = target.class;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
else {
|
|
1049
|
+
prop.discriminatorValue ??= meta.tableName;
|
|
1050
|
+
if (!prop.discriminatorMap) {
|
|
1051
|
+
prop.discriminatorMap = { [prop.discriminatorValue]: meta.class };
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
803
1055
|
initEmbeddables(meta, embeddedProp, visited = new Set()) {
|
|
804
1056
|
if (embeddedProp.kind !== ReferenceKind.EMBEDDED || visited.has(embeddedProp)) {
|
|
805
1057
|
return;
|
|
@@ -819,22 +1071,30 @@ export class MetadataDiscovery {
|
|
|
819
1071
|
}
|
|
820
1072
|
return prop.embedded ? isParentObject(meta.properties[prop.embedded[0]]) : false;
|
|
821
1073
|
};
|
|
1074
|
+
const isParentArray = (prop) => {
|
|
1075
|
+
if (prop.array) {
|
|
1076
|
+
return true;
|
|
1077
|
+
}
|
|
1078
|
+
return prop.embedded ? isParentArray(meta.properties[prop.embedded[0]]) : false;
|
|
1079
|
+
};
|
|
822
1080
|
const rootProperty = getRootProperty(embeddedProp);
|
|
823
1081
|
const parentProperty = meta.properties[embeddedProp.embedded?.[0] ?? ''];
|
|
824
1082
|
const object = isParentObject(embeddedProp);
|
|
1083
|
+
const array = isParentArray(embeddedProp);
|
|
825
1084
|
this.initFieldName(embeddedProp, rootProperty !== embeddedProp && object);
|
|
826
1085
|
// the prefix of the parent cannot be a boolean; it already passed here
|
|
827
1086
|
const prefix = this.getPrefix(embeddedProp, parentProperty);
|
|
828
1087
|
const glue = object ? '~' : '_';
|
|
829
1088
|
for (const prop of Object.values(embeddable.properties)) {
|
|
830
1089
|
const name = (embeddedProp.embeddedPath?.join(glue) ?? embeddedProp.fieldNames[0] + glue) + prop.name;
|
|
831
|
-
meta.properties[name] = Utils.copy(prop
|
|
1090
|
+
meta.properties[name] = Utils.copy(prop);
|
|
832
1091
|
meta.properties[name].name = name;
|
|
833
1092
|
meta.properties[name].embedded = [embeddedProp.name, prop.name];
|
|
834
1093
|
meta.propertyOrder.set(name, (order += 0.01));
|
|
835
1094
|
embeddedProp.embeddedProps[prop.name] = meta.properties[name];
|
|
836
1095
|
meta.properties[name].persist ??= embeddedProp.persist;
|
|
837
|
-
|
|
1096
|
+
const refInArray = array && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && prop.owner;
|
|
1097
|
+
if (embeddedProp.nullable || refInArray) {
|
|
838
1098
|
meta.properties[name].nullable = true;
|
|
839
1099
|
}
|
|
840
1100
|
if (meta.properties[name].fieldNames) {
|
|
@@ -864,14 +1124,17 @@ export class MetadataDiscovery {
|
|
|
864
1124
|
path = [embeddedProp.fieldNames[0]];
|
|
865
1125
|
}
|
|
866
1126
|
this.initFieldName(prop, true);
|
|
1127
|
+
this.initRelation(prop);
|
|
867
1128
|
path.push(prop.fieldNames[0]);
|
|
868
1129
|
meta.properties[name].fieldNames = prop.fieldNames;
|
|
869
1130
|
meta.properties[name].embeddedPath = path;
|
|
870
|
-
const
|
|
1131
|
+
const targetProp = prop.targetMeta?.getPrimaryProp() ?? prop;
|
|
1132
|
+
const fieldName = raw(this.platform.getSearchJsonPropertySQL(path.join('->'), targetProp.runtimeType ?? targetProp.type, true));
|
|
871
1133
|
meta.properties[name].fieldNameRaw = fieldName.sql; // for querying in SQL drivers
|
|
872
1134
|
meta.properties[name].persist = false; // only virtual as we store the whole object
|
|
873
1135
|
meta.properties[name].userDefined = false; // mark this as a generated/internal property, so we can distinguish from user-defined non-persist properties
|
|
874
1136
|
meta.properties[name].object = true;
|
|
1137
|
+
this.initCustomType(meta, meta.properties[name], false, true);
|
|
875
1138
|
}
|
|
876
1139
|
this.initEmbeddables(meta, meta.properties[name], visited);
|
|
877
1140
|
}
|
|
@@ -894,7 +1157,7 @@ export class MetadataDiscovery {
|
|
|
894
1157
|
}
|
|
895
1158
|
initSingleTableInheritance(meta, metadata) {
|
|
896
1159
|
if (meta.root !== meta && !meta.__processed) {
|
|
897
|
-
meta.root = metadata.find(m => m.
|
|
1160
|
+
meta.root = metadata.find(m => m.class === meta.root.class);
|
|
898
1161
|
meta.root.__processed = true;
|
|
899
1162
|
}
|
|
900
1163
|
else {
|
|
@@ -903,15 +1166,24 @@ export class MetadataDiscovery {
|
|
|
903
1166
|
if (!meta.root.discriminatorColumn) {
|
|
904
1167
|
return;
|
|
905
1168
|
}
|
|
906
|
-
|
|
1169
|
+
meta.root.inheritanceType = 'sti';
|
|
1170
|
+
if (meta.root.discriminatorMap) {
|
|
1171
|
+
const map = meta.root.discriminatorMap;
|
|
1172
|
+
Object.keys(map)
|
|
1173
|
+
.filter(key => typeof map[key] === 'string')
|
|
1174
|
+
.forEach(key => map[key] = this.metadata.getByClassName(map[key]).class);
|
|
1175
|
+
}
|
|
1176
|
+
else {
|
|
907
1177
|
meta.root.discriminatorMap = {};
|
|
908
|
-
const children = metadata
|
|
909
|
-
|
|
1178
|
+
const children = metadata
|
|
1179
|
+
.filter(m => m.root.class === meta.root.class && !m.abstract)
|
|
1180
|
+
.sort((a, b) => a.className.localeCompare(b.className));
|
|
1181
|
+
for (const m of children) {
|
|
910
1182
|
const name = m.discriminatorValue ?? this.namingStrategy.classToTableName(m.className);
|
|
911
|
-
meta.root.discriminatorMap[name] = m.
|
|
912
|
-
}
|
|
1183
|
+
meta.root.discriminatorMap[name] = m.class;
|
|
1184
|
+
}
|
|
913
1185
|
}
|
|
914
|
-
meta.discriminatorValue =
|
|
1186
|
+
meta.discriminatorValue = QueryHelper.findDiscriminatorValue(meta.root.discriminatorMap, meta.class);
|
|
915
1187
|
if (!meta.root.properties[meta.root.discriminatorColumn]) {
|
|
916
1188
|
this.createDiscriminatorProperty(meta.root);
|
|
917
1189
|
}
|
|
@@ -920,30 +1192,218 @@ export class MetadataDiscovery {
|
|
|
920
1192
|
if (meta.root === meta) {
|
|
921
1193
|
return;
|
|
922
1194
|
}
|
|
923
|
-
let i = 1;
|
|
924
1195
|
Object.values(meta.properties).forEach(prop => {
|
|
925
|
-
const newProp =
|
|
926
|
-
|
|
1196
|
+
const newProp = { ...prop };
|
|
1197
|
+
const rootProp = meta.root.properties[prop.name];
|
|
1198
|
+
if (rootProp && (rootProp.type !== prop.type || (rootProp.fieldNames && prop.fieldNames && !compareArrays(rootProp.fieldNames, prop.fieldNames)))) {
|
|
927
1199
|
const name = newProp.name;
|
|
928
1200
|
this.initFieldName(newProp, newProp.object);
|
|
929
|
-
newProp.
|
|
1201
|
+
newProp.renamedFrom = name;
|
|
1202
|
+
newProp.name = `${name}_${meta._id}`;
|
|
930
1203
|
meta.root.addProperty(newProp);
|
|
1204
|
+
this.initFieldName(prop, prop.object);
|
|
1205
|
+
// Track all field variants and map discriminator values to field names
|
|
1206
|
+
if (!rootProp.stiFieldNames) {
|
|
1207
|
+
this.initFieldName(rootProp, rootProp.object);
|
|
1208
|
+
rootProp.stiFieldNames = [...rootProp.fieldNames];
|
|
1209
|
+
rootProp.stiFieldNameMap = {};
|
|
1210
|
+
// Find which discriminator owns the original fieldNames
|
|
1211
|
+
for (const [discValue, childClass] of Object.entries(meta.root.discriminatorMap)) {
|
|
1212
|
+
const childMeta = this.metadata.find(childClass);
|
|
1213
|
+
if (childMeta?.properties[prop.name]?.fieldNames && compareArrays(childMeta.properties[prop.name].fieldNames, rootProp.fieldNames)) {
|
|
1214
|
+
rootProp.stiFieldNameMap[discValue] = rootProp.fieldNames[0];
|
|
1215
|
+
break;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
rootProp.stiFieldNameMap[meta.discriminatorValue] = prop.fieldNames[0];
|
|
1220
|
+
rootProp.stiFieldNames.push(...prop.fieldNames);
|
|
931
1221
|
newProp.nullable = true;
|
|
932
1222
|
newProp.name = name;
|
|
933
1223
|
newProp.hydrate = false;
|
|
934
1224
|
newProp.inherited = true;
|
|
935
1225
|
return;
|
|
936
1226
|
}
|
|
937
|
-
if (prop.enum && prop.items &&
|
|
938
|
-
newProp.items = Utils.unique([...
|
|
1227
|
+
if (prop.enum && prop.items && rootProp?.items) {
|
|
1228
|
+
newProp.items = Utils.unique([...rootProp.items, ...prop.items]);
|
|
939
1229
|
}
|
|
940
1230
|
newProp.nullable = true;
|
|
941
|
-
newProp.inherited =
|
|
1231
|
+
newProp.inherited = !rootProp;
|
|
942
1232
|
meta.root.addProperty(newProp);
|
|
943
1233
|
});
|
|
944
|
-
meta.
|
|
1234
|
+
meta.tableName = meta.root.tableName;
|
|
945
1235
|
meta.root.indexes = Utils.unique([...meta.root.indexes, ...meta.indexes]);
|
|
946
1236
|
meta.root.uniques = Utils.unique([...meta.root.uniques, ...meta.uniques]);
|
|
1237
|
+
meta.root.checks = Utils.unique([...meta.root.checks, ...meta.checks]);
|
|
1238
|
+
}
|
|
1239
|
+
/**
|
|
1240
|
+
* First pass of TPT initialization: sets up hierarchy relationships
|
|
1241
|
+
* (inheritanceType, tptParent, tptChildren) before properties have fieldNames.
|
|
1242
|
+
*/
|
|
1243
|
+
initTPTRelationships(meta, metadata) {
|
|
1244
|
+
if (meta.root !== meta) {
|
|
1245
|
+
meta.root = metadata.find(m => m.class === meta.root.class);
|
|
1246
|
+
}
|
|
1247
|
+
const inheritance = meta.inheritance;
|
|
1248
|
+
if (inheritance === 'tpt' && meta.root === meta) {
|
|
1249
|
+
meta.inheritanceType = 'tpt';
|
|
1250
|
+
meta.tptChildren = [];
|
|
1251
|
+
}
|
|
1252
|
+
if (meta.root.inheritanceType !== 'tpt') {
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
const parent = this.getTPTParent(meta, metadata);
|
|
1256
|
+
if (parent) {
|
|
1257
|
+
meta.tptParent = parent;
|
|
1258
|
+
meta.inheritanceType = 'tpt';
|
|
1259
|
+
parent.tptChildren ??= [];
|
|
1260
|
+
if (!parent.tptChildren.includes(meta)) {
|
|
1261
|
+
parent.tptChildren.push(meta);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* Second pass of TPT initialization: re-resolves metadata references after fieldNames
|
|
1267
|
+
* are set, syncs to registry metadata, and sets up discriminators.
|
|
1268
|
+
*/
|
|
1269
|
+
finalizeTPTInheritance(meta, metadata) {
|
|
1270
|
+
if (meta.inheritanceType !== 'tpt') {
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
if (meta.tptParent) {
|
|
1274
|
+
meta.tptParent = metadata.find(m => m.class === meta.tptParent.class) ?? meta.tptParent;
|
|
1275
|
+
}
|
|
1276
|
+
if (meta.tptChildren) {
|
|
1277
|
+
meta.tptChildren = meta.tptChildren.map(child => metadata.find(m => m.class === child.class) ?? child);
|
|
1278
|
+
}
|
|
1279
|
+
const registryMeta = this.metadata.get(meta.class);
|
|
1280
|
+
if (registryMeta && registryMeta !== meta) {
|
|
1281
|
+
registryMeta.inheritanceType = meta.inheritanceType;
|
|
1282
|
+
registryMeta.tptParent = meta.tptParent ? this.metadata.get(meta.tptParent.class) : undefined;
|
|
1283
|
+
registryMeta.tptChildren = meta.tptChildren?.map(child => this.metadata.get(child.class));
|
|
1284
|
+
}
|
|
1285
|
+
this.initTPTDiscriminator(meta, metadata);
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Initialize TPT discriminator map and virtual discriminator property.
|
|
1289
|
+
* Unlike STI where the discriminator is a persisted column, TPT discriminator is computed
|
|
1290
|
+
* at query time using CASE WHEN expressions based on which child table has data.
|
|
1291
|
+
*/
|
|
1292
|
+
initTPTDiscriminator(meta, metadata) {
|
|
1293
|
+
const allDescendants = this.collectAllTPTDescendants(meta, metadata);
|
|
1294
|
+
allDescendants.sort((a, b) => this.getTPTDepth(b) - this.getTPTDepth(a));
|
|
1295
|
+
meta.allTPTDescendants = allDescendants;
|
|
1296
|
+
if (meta.root !== meta) {
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
meta.root.discriminatorMap = {};
|
|
1300
|
+
for (const m of allDescendants) {
|
|
1301
|
+
const name = this.namingStrategy.classToTableName(m.className);
|
|
1302
|
+
meta.root.discriminatorMap[name] = m.class;
|
|
1303
|
+
m.discriminatorValue = name;
|
|
1304
|
+
}
|
|
1305
|
+
if (!meta.abstract) {
|
|
1306
|
+
const name = this.namingStrategy.classToTableName(meta.className);
|
|
1307
|
+
meta.root.discriminatorMap[name] = meta.class;
|
|
1308
|
+
meta.discriminatorValue = name;
|
|
1309
|
+
}
|
|
1310
|
+
// Virtual discriminator property - computed at query time via CASE WHEN, not persisted
|
|
1311
|
+
const discriminatorColumn = '__tpt_type';
|
|
1312
|
+
if (!meta.root.properties[discriminatorColumn]) {
|
|
1313
|
+
meta.root.addProperty({
|
|
1314
|
+
name: discriminatorColumn,
|
|
1315
|
+
type: 'string',
|
|
1316
|
+
kind: ReferenceKind.SCALAR,
|
|
1317
|
+
persist: false,
|
|
1318
|
+
userDefined: false,
|
|
1319
|
+
hidden: true,
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
meta.root.tptDiscriminatorColumn = discriminatorColumn;
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* Recursively collect all TPT descendants (children, grandchildren, etc.)
|
|
1326
|
+
*/
|
|
1327
|
+
collectAllTPTDescendants(meta, metadata) {
|
|
1328
|
+
const descendants = [];
|
|
1329
|
+
const collect = (parent) => {
|
|
1330
|
+
for (const child of parent.tptChildren ?? []) {
|
|
1331
|
+
const resolved = metadata.find(m => m.class === child.class) ?? child;
|
|
1332
|
+
if (!resolved.abstract) {
|
|
1333
|
+
descendants.push(resolved);
|
|
1334
|
+
}
|
|
1335
|
+
collect(resolved);
|
|
1336
|
+
}
|
|
1337
|
+
};
|
|
1338
|
+
collect(meta);
|
|
1339
|
+
return descendants;
|
|
1340
|
+
}
|
|
1341
|
+
/**
|
|
1342
|
+
* Computes ownProps for TPT entities - only properties defined in THIS entity,
|
|
1343
|
+
* not inherited from parent. Also creates synthetic join properties for parent/child relationships.
|
|
1344
|
+
*
|
|
1345
|
+
* Called multiple times during discovery as metadata is progressively built.
|
|
1346
|
+
* Each pass overwrites earlier results to reflect the final state of properties.
|
|
1347
|
+
*/
|
|
1348
|
+
computeTPTOwnProps(meta) {
|
|
1349
|
+
if (meta.inheritanceType !== 'tpt') {
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
const belongsToTable = (prop) => prop.persist !== false || prop.primary;
|
|
1353
|
+
// Use meta.properties (object) since meta.props (array) may not be populated yet
|
|
1354
|
+
const allProps = Object.values(meta.properties);
|
|
1355
|
+
if (!meta.tptParent) {
|
|
1356
|
+
meta.ownProps = allProps.filter(belongsToTable);
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
const parentPropNames = new Set(Object.values(meta.tptParent.properties).map(p => p.name));
|
|
1360
|
+
meta.ownProps = allProps.filter(prop => !parentPropNames.has(prop.name) && belongsToTable(prop));
|
|
1361
|
+
// Create synthetic join properties for the parent-child relationship
|
|
1362
|
+
const childFieldNames = meta.getPrimaryProps().flatMap(p => p.fieldNames);
|
|
1363
|
+
const parentFieldNames = meta.tptParent.getPrimaryProps().flatMap(p => p.fieldNames);
|
|
1364
|
+
meta.tptParentProp = {
|
|
1365
|
+
name: '[tpt:parent]',
|
|
1366
|
+
kind: ReferenceKind.MANY_TO_ONE,
|
|
1367
|
+
targetMeta: meta.tptParent,
|
|
1368
|
+
fieldNames: childFieldNames,
|
|
1369
|
+
referencedColumnNames: parentFieldNames,
|
|
1370
|
+
persist: false,
|
|
1371
|
+
};
|
|
1372
|
+
meta.tptInverseProp = {
|
|
1373
|
+
name: '[tpt:child]',
|
|
1374
|
+
kind: ReferenceKind.ONE_TO_ONE,
|
|
1375
|
+
owner: true,
|
|
1376
|
+
targetMeta: meta,
|
|
1377
|
+
fieldNames: parentFieldNames,
|
|
1378
|
+
joinColumns: parentFieldNames,
|
|
1379
|
+
referencedColumnNames: childFieldNames,
|
|
1380
|
+
persist: false,
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
/** Returns the depth of a TPT entity in its hierarchy (0 for root). */
|
|
1384
|
+
getTPTDepth(meta) {
|
|
1385
|
+
let depth = 0;
|
|
1386
|
+
let current = meta;
|
|
1387
|
+
while (current.tptParent) {
|
|
1388
|
+
depth++;
|
|
1389
|
+
current = current.tptParent;
|
|
1390
|
+
}
|
|
1391
|
+
return depth;
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Find the direct TPT parent entity for the given entity.
|
|
1395
|
+
*/
|
|
1396
|
+
getTPTParent(meta, metadata) {
|
|
1397
|
+
if (meta.root === meta) {
|
|
1398
|
+
return undefined;
|
|
1399
|
+
}
|
|
1400
|
+
return metadata.find(m => {
|
|
1401
|
+
const ext = meta.extends;
|
|
1402
|
+
if (ext instanceof EntitySchema) {
|
|
1403
|
+
return m.class === ext.meta.class || m.className === ext.meta.className;
|
|
1404
|
+
}
|
|
1405
|
+
return m.class === ext || m.className === Utils.className(ext);
|
|
1406
|
+
});
|
|
947
1407
|
}
|
|
948
1408
|
createDiscriminatorProperty(meta) {
|
|
949
1409
|
meta.addProperty({
|
|
@@ -961,18 +1421,28 @@ export class MetadataDiscovery {
|
|
|
961
1421
|
pks[0].autoincrement ??= true;
|
|
962
1422
|
}
|
|
963
1423
|
}
|
|
1424
|
+
createSchemaTable(meta) {
|
|
1425
|
+
const qualifiedName = meta.schema ? `${meta.schema}.${meta.tableName}` : meta.tableName;
|
|
1426
|
+
return {
|
|
1427
|
+
name: meta.tableName,
|
|
1428
|
+
schema: meta.schema,
|
|
1429
|
+
qualifiedName,
|
|
1430
|
+
toString: () => qualifiedName,
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
964
1433
|
initCheckConstraints(meta) {
|
|
965
|
-
const
|
|
1434
|
+
const columns = meta.createSchemaColumnMappingObject();
|
|
1435
|
+
const table = this.createSchemaTable(meta);
|
|
966
1436
|
for (const check of meta.checks) {
|
|
967
|
-
const
|
|
968
|
-
check.name ??= this.namingStrategy.indexName(meta.tableName,
|
|
1437
|
+
const fieldNames = check.property ? meta.properties[check.property].fieldNames : [];
|
|
1438
|
+
check.name ??= this.namingStrategy.indexName(meta.tableName, fieldNames, 'check');
|
|
969
1439
|
if (check.expression instanceof Function) {
|
|
970
|
-
check.expression = check.expression(
|
|
1440
|
+
check.expression = check.expression(columns, table);
|
|
971
1441
|
}
|
|
972
1442
|
}
|
|
973
1443
|
if (this.platform.usesEnumCheckConstraints() && !meta.embeddable) {
|
|
974
1444
|
for (const prop of meta.props) {
|
|
975
|
-
if (prop.enum && !prop.nativeEnumName && prop.items?.every(item =>
|
|
1445
|
+
if (prop.enum && prop.persist !== false && !prop.nativeEnumName && prop.items?.every(item => typeof item === 'string')) {
|
|
976
1446
|
this.initFieldName(prop);
|
|
977
1447
|
meta.checks.push({
|
|
978
1448
|
name: this.namingStrategy.indexName(meta.tableName, prop.fieldNames, 'check'),
|
|
@@ -997,38 +1467,28 @@ export class MetadataDiscovery {
|
|
|
997
1467
|
}
|
|
998
1468
|
return;
|
|
999
1469
|
}
|
|
1000
|
-
const map = this.createColumnMappingObject(meta);
|
|
1001
1470
|
if (prop.generated instanceof Function) {
|
|
1002
|
-
|
|
1471
|
+
const columns = meta.createSchemaColumnMappingObject();
|
|
1472
|
+
prop.generated = prop.generated(columns, this.createSchemaTable(meta));
|
|
1003
1473
|
}
|
|
1004
1474
|
}
|
|
1005
|
-
|
|
1006
|
-
return Object.values(meta.properties).reduce((o, prop) => {
|
|
1007
|
-
if (prop.fieldNames) {
|
|
1008
|
-
o[prop.name] = prop.fieldNames[0];
|
|
1009
|
-
}
|
|
1010
|
-
return o;
|
|
1011
|
-
}, {});
|
|
1012
|
-
}
|
|
1013
|
-
getDefaultVersionValue(prop) {
|
|
1475
|
+
getDefaultVersionValue(meta, prop) {
|
|
1014
1476
|
if (typeof prop.defaultRaw !== 'undefined') {
|
|
1015
1477
|
return prop.defaultRaw;
|
|
1016
1478
|
}
|
|
1017
|
-
/* v8 ignore next
|
|
1479
|
+
/* v8 ignore next */
|
|
1018
1480
|
if (prop.default != null) {
|
|
1019
|
-
return '' + this.platform.
|
|
1481
|
+
return '' + this.platform.convertVersionValue(prop.default, prop);
|
|
1020
1482
|
}
|
|
1021
|
-
|
|
1483
|
+
this.initCustomType(meta, prop, true);
|
|
1484
|
+
const type = prop.customType?.runtimeType ?? prop.runtimeType ?? prop.type;
|
|
1485
|
+
if (type === 'Date') {
|
|
1022
1486
|
prop.length ??= this.platform.getDefaultVersionLength();
|
|
1023
1487
|
return this.platform.getCurrentTimestampSQL(prop.length);
|
|
1024
1488
|
}
|
|
1025
1489
|
return '1';
|
|
1026
1490
|
}
|
|
1027
1491
|
inferDefaultValue(meta, prop) {
|
|
1028
|
-
/* v8 ignore next 3 */
|
|
1029
|
-
if (!meta.class) {
|
|
1030
|
-
return;
|
|
1031
|
-
}
|
|
1032
1492
|
try {
|
|
1033
1493
|
// try to create two entity instances to detect the value is stable
|
|
1034
1494
|
const now = Date.now();
|
|
@@ -1056,12 +1516,12 @@ export class MetadataDiscovery {
|
|
|
1056
1516
|
return;
|
|
1057
1517
|
}
|
|
1058
1518
|
let val = prop.default;
|
|
1059
|
-
const raw =
|
|
1519
|
+
const raw = Raw.getKnownFragment(val);
|
|
1060
1520
|
if (raw) {
|
|
1061
1521
|
prop.defaultRaw = this.platform.formatQuery(raw.sql, raw.params);
|
|
1062
1522
|
return;
|
|
1063
1523
|
}
|
|
1064
|
-
if (
|
|
1524
|
+
if (Array.isArray(prop.default) && prop.customType) {
|
|
1065
1525
|
val = prop.customType.convertToDatabaseValue(prop.default, this.platform);
|
|
1066
1526
|
}
|
|
1067
1527
|
prop.defaultRaw = typeof val === 'string' ? `'${val}'` : '' + val;
|
|
@@ -1089,13 +1549,13 @@ export class MetadataDiscovery {
|
|
|
1089
1549
|
if (prop.version) {
|
|
1090
1550
|
this.initDefaultValue(prop);
|
|
1091
1551
|
meta.versionProperty = prop.name;
|
|
1092
|
-
prop.defaultRaw = this.getDefaultVersionValue(prop);
|
|
1552
|
+
prop.defaultRaw = this.getDefaultVersionValue(meta, prop);
|
|
1093
1553
|
}
|
|
1094
1554
|
if (prop.concurrencyCheck && !prop.primary) {
|
|
1095
1555
|
meta.concurrencyCheckKeys.add(prop.name);
|
|
1096
1556
|
}
|
|
1097
1557
|
}
|
|
1098
|
-
initCustomType(meta, prop) {
|
|
1558
|
+
initCustomType(meta, prop, simple = false, objectEmbeddable = false) {
|
|
1099
1559
|
// `prop.type` might be actually instance of custom type class
|
|
1100
1560
|
if (Type.isMappedType(prop.type) && !prop.customType) {
|
|
1101
1561
|
prop.customType = prop.type;
|
|
@@ -1103,47 +1563,70 @@ export class MetadataDiscovery {
|
|
|
1103
1563
|
}
|
|
1104
1564
|
// `prop.type` might also be custom type class (not instance), so `typeof MyType` will give us `function`, not `object`
|
|
1105
1565
|
if (typeof prop.type === 'function' && Type.isMappedType(prop.type.prototype) && !prop.customType) {
|
|
1106
|
-
|
|
1107
|
-
|
|
1566
|
+
// if the type is an ORM defined mapped type without `ensureComparable: true`,
|
|
1567
|
+
// we use just the type name, to have more performant hydration code
|
|
1568
|
+
const type = Utils.keys(t).find(type => {
|
|
1569
|
+
return !Type.getType(t[type]).ensureComparable(meta, prop) && prop.type === t[type];
|
|
1570
|
+
});
|
|
1571
|
+
if (type) {
|
|
1572
|
+
prop.type = type === 'datetime' ? 'Date' : type;
|
|
1573
|
+
}
|
|
1574
|
+
else {
|
|
1575
|
+
prop.customType = new prop.type();
|
|
1576
|
+
prop.type = prop.customType.constructor.name;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
if (simple) {
|
|
1580
|
+
return;
|
|
1108
1581
|
}
|
|
1109
1582
|
if (!prop.customType && ['json', 'jsonb'].includes(prop.type?.toLowerCase())) {
|
|
1110
|
-
prop.customType = new
|
|
1583
|
+
prop.customType = new t.json();
|
|
1111
1584
|
}
|
|
1112
1585
|
if (prop.kind === ReferenceKind.SCALAR && !prop.customType && prop.columnTypes && ['json', 'jsonb'].includes(prop.columnTypes[0])) {
|
|
1113
|
-
prop.customType = new
|
|
1586
|
+
prop.customType = new t.json();
|
|
1587
|
+
}
|
|
1588
|
+
if (prop.kind === ReferenceKind.EMBEDDED && !prop.customType && (prop.object || prop.array)) {
|
|
1589
|
+
prop.customType = new t.json();
|
|
1114
1590
|
}
|
|
1115
1591
|
if (!prop.customType && prop.array && prop.items) {
|
|
1116
|
-
prop.customType = new
|
|
1592
|
+
prop.customType = new t.enumArray(`${meta.className}.${prop.name}`, prop.items);
|
|
1593
|
+
}
|
|
1594
|
+
const isArray = prop.type?.toLowerCase() === 'array' || prop.type?.toString().endsWith('[]');
|
|
1595
|
+
if (objectEmbeddable && !prop.customType && isArray) {
|
|
1596
|
+
prop.customType = new t.json();
|
|
1117
1597
|
}
|
|
1118
1598
|
// for number arrays we make sure to convert the items to numbers
|
|
1119
1599
|
if (!prop.customType && prop.type === 'number[]') {
|
|
1120
|
-
prop.customType = new
|
|
1600
|
+
prop.customType = new t.array(i => +i);
|
|
1121
1601
|
}
|
|
1122
1602
|
// `string[]` can be returned via ts-morph, while reflect metadata will give us just `array`
|
|
1123
|
-
if (!prop.customType &&
|
|
1124
|
-
prop.customType = new
|
|
1603
|
+
if (!prop.customType && isArray) {
|
|
1604
|
+
prop.customType = new t.array();
|
|
1125
1605
|
}
|
|
1126
1606
|
if (!prop.customType && prop.type?.toLowerCase() === 'buffer') {
|
|
1127
|
-
prop.customType = new
|
|
1607
|
+
prop.customType = new t.blob();
|
|
1128
1608
|
}
|
|
1129
1609
|
if (!prop.customType && prop.type?.toLowerCase() === 'uint8array') {
|
|
1130
|
-
prop.customType = new
|
|
1610
|
+
prop.customType = new t.uint8array();
|
|
1131
1611
|
}
|
|
1132
1612
|
const mappedType = this.getMappedType(prop);
|
|
1133
1613
|
if (prop.fieldNames?.length === 1 && !prop.customType) {
|
|
1134
|
-
[
|
|
1614
|
+
[t.bigint, t.double, t.decimal, t.interval, t.date]
|
|
1135
1615
|
.filter(type => mappedType instanceof type)
|
|
1136
|
-
.forEach(type => prop.customType = new type());
|
|
1616
|
+
.forEach((type) => prop.customType = new type());
|
|
1137
1617
|
}
|
|
1138
1618
|
if (prop.customType && !prop.columnTypes) {
|
|
1139
1619
|
const mappedType = this.getMappedType({ columnTypes: [prop.customType.getColumnType(prop, this.platform)] });
|
|
1140
|
-
if (prop.customType.compareAsType() === 'any' && ![
|
|
1620
|
+
if (prop.customType.compareAsType() === 'any' && ![t.json].some(t => prop.customType instanceof t)) {
|
|
1141
1621
|
prop.runtimeType ??= mappedType.runtimeType;
|
|
1142
1622
|
}
|
|
1143
1623
|
else {
|
|
1144
1624
|
prop.runtimeType ??= prop.customType.runtimeType;
|
|
1145
1625
|
}
|
|
1146
1626
|
}
|
|
1627
|
+
else if (prop.runtimeType === 'object') {
|
|
1628
|
+
prop.runtimeType = mappedType.runtimeType;
|
|
1629
|
+
}
|
|
1147
1630
|
else {
|
|
1148
1631
|
prop.runtimeType ??= mappedType.runtimeType;
|
|
1149
1632
|
}
|
|
@@ -1154,16 +1637,16 @@ export class MetadataDiscovery {
|
|
|
1154
1637
|
prop.columnTypes ??= [prop.customType.getColumnType(prop, this.platform)];
|
|
1155
1638
|
prop.hasConvertToJSValueSQL = !!prop.customType.convertToJSValueSQL && prop.customType.convertToJSValueSQL('', this.platform) !== '';
|
|
1156
1639
|
prop.hasConvertToDatabaseValueSQL = !!prop.customType.convertToDatabaseValueSQL && prop.customType.convertToDatabaseValueSQL('', this.platform) !== '';
|
|
1157
|
-
if (prop.customType instanceof
|
|
1640
|
+
if (prop.customType instanceof t.bigint && ['string', 'bigint', 'number'].includes(prop.runtimeType.toLowerCase())) {
|
|
1158
1641
|
prop.customType.mode = prop.runtimeType.toLowerCase();
|
|
1159
1642
|
}
|
|
1160
1643
|
}
|
|
1161
|
-
if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !
|
|
1644
|
+
if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !isArray) {
|
|
1162
1645
|
prop.type = prop.customType.name;
|
|
1163
1646
|
}
|
|
1164
|
-
if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) &&
|
|
1647
|
+
if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && !prop.polymorphic && prop.targetMeta.compositePK) {
|
|
1165
1648
|
prop.customTypes = [];
|
|
1166
|
-
for (const pk of
|
|
1649
|
+
for (const pk of prop.targetMeta.getPrimaryProps()) {
|
|
1167
1650
|
if (pk.customType) {
|
|
1168
1651
|
prop.customTypes.push(pk.customType);
|
|
1169
1652
|
prop.hasConvertToJSValueSQL ||= !!pk.customType.convertToJSValueSQL && pk.customType.convertToJSValueSQL('', this.platform) !== '';
|
|
@@ -1175,7 +1658,7 @@ export class MetadataDiscovery {
|
|
|
1175
1658
|
}
|
|
1176
1659
|
}
|
|
1177
1660
|
}
|
|
1178
|
-
if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof
|
|
1661
|
+
if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof t.unknown)) {
|
|
1179
1662
|
if (!prop.columnTypes && prop.nativeEnumName && meta.schema !== this.platform.getDefaultSchemaName() && meta.schema && !prop.nativeEnumName.includes('.')) {
|
|
1180
1663
|
prop.columnTypes = [`${meta.schema}.${prop.nativeEnumName}`];
|
|
1181
1664
|
}
|
|
@@ -1190,22 +1673,36 @@ export class MetadataDiscovery {
|
|
|
1190
1673
|
}
|
|
1191
1674
|
}
|
|
1192
1675
|
initRelation(prop) {
|
|
1193
|
-
if (prop.kind === ReferenceKind.SCALAR) {
|
|
1676
|
+
if (prop.kind === ReferenceKind.SCALAR || prop.polymorphTargets) {
|
|
1194
1677
|
return;
|
|
1195
1678
|
}
|
|
1196
|
-
|
|
1197
|
-
prop.
|
|
1679
|
+
// 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
|
|
1680
|
+
const meta2 = this.metadata.find(prop.target) ?? this.metadata.getByClassName(prop.type);
|
|
1681
|
+
// If targetKey is specified, use that property instead of PKs for referencedPKs
|
|
1682
|
+
prop.referencedPKs = prop.targetKey ? [prop.targetKey] : meta2.primaryKeys;
|
|
1198
1683
|
prop.targetMeta = meta2;
|
|
1684
|
+
if (meta2.view) {
|
|
1685
|
+
prop.createForeignKeyConstraint = false;
|
|
1686
|
+
}
|
|
1687
|
+
// Auto-generate formula for persist: false relations, but only for single-column FKs
|
|
1688
|
+
// Composite FK relations need standard JOIN conditions, not formula-based
|
|
1199
1689
|
if (!prop.formula && prop.persist === false && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.embedded) {
|
|
1200
|
-
|
|
1690
|
+
this.initFieldName(prop);
|
|
1691
|
+
if (prop.fieldNames?.length === 1) {
|
|
1692
|
+
prop.formula = table => `${table}.${this.platform.quoteIdentifier(prop.fieldNames[0])}`;
|
|
1693
|
+
}
|
|
1201
1694
|
}
|
|
1202
1695
|
}
|
|
1203
1696
|
initColumnType(prop) {
|
|
1204
1697
|
this.initUnsigned(prop);
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
prop.
|
|
1208
|
-
|
|
1698
|
+
// Get the target properties for FK relations - use targetKey property if specified, otherwise PKs
|
|
1699
|
+
const targetProps = prop.targetMeta
|
|
1700
|
+
? (prop.targetKey ? [prop.targetMeta.properties[prop.targetKey]] : prop.targetMeta.getPrimaryProps())
|
|
1701
|
+
: [];
|
|
1702
|
+
targetProps.map(targetProp => {
|
|
1703
|
+
prop.length ??= targetProp.length;
|
|
1704
|
+
prop.precision ??= targetProp.precision;
|
|
1705
|
+
prop.scale ??= targetProp.scale;
|
|
1209
1706
|
});
|
|
1210
1707
|
if (prop.kind === ReferenceKind.SCALAR && (prop.type == null || prop.type === 'object') && prop.columnTypes?.[0]) {
|
|
1211
1708
|
delete prop.type;
|
|
@@ -1218,8 +1715,7 @@ export class MetadataDiscovery {
|
|
|
1218
1715
|
if (prop.kind === ReferenceKind.SCALAR) {
|
|
1219
1716
|
const mappedType = this.getMappedType(prop);
|
|
1220
1717
|
const SCALAR_TYPES = ['string', 'number', 'boolean', 'bigint', 'Date', 'Buffer', 'RegExp', 'any', 'unknown'];
|
|
1221
|
-
if (mappedType instanceof
|
|
1222
|
-
&& !prop.columnTypes
|
|
1718
|
+
if (mappedType instanceof t.unknown
|
|
1223
1719
|
// it could be a runtime type from reflect-metadata
|
|
1224
1720
|
&& !SCALAR_TYPES.includes(prop.type)
|
|
1225
1721
|
// or it might be inferred via ts-morph to some generic type alias
|
|
@@ -1232,23 +1728,31 @@ export class MetadataDiscovery {
|
|
|
1232
1728
|
}
|
|
1233
1729
|
return;
|
|
1234
1730
|
}
|
|
1235
|
-
|
|
1731
|
+
/* v8 ignore next */
|
|
1732
|
+
if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
|
|
1236
1733
|
prop.columnTypes = [this.platform.getJsonDeclarationSQL()];
|
|
1237
1734
|
return;
|
|
1238
1735
|
}
|
|
1239
|
-
const targetMeta =
|
|
1736
|
+
const targetMeta = prop.targetMeta;
|
|
1240
1737
|
prop.columnTypes = [];
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1738
|
+
// Use targetKey property if specified, otherwise use primary key properties
|
|
1739
|
+
const referencedProps = prop.targetKey
|
|
1740
|
+
? [targetMeta.properties[prop.targetKey]]
|
|
1741
|
+
: targetMeta.getPrimaryProps();
|
|
1742
|
+
if (prop.polymorphic && prop.polymorphTargets) {
|
|
1743
|
+
prop.columnTypes.push(this.platform.getVarcharTypeDeclarationSQL(prop));
|
|
1744
|
+
}
|
|
1745
|
+
for (const referencedProp of referencedProps) {
|
|
1746
|
+
this.initCustomType(targetMeta, referencedProp);
|
|
1747
|
+
this.initColumnType(referencedProp);
|
|
1748
|
+
const mappedType = this.getMappedType(referencedProp);
|
|
1749
|
+
let columnTypes = referencedProp.columnTypes;
|
|
1750
|
+
if (referencedProp.autoincrement) {
|
|
1751
|
+
columnTypes = [mappedType.getColumnType({ ...referencedProp, autoincrement: false }, this.platform)];
|
|
1248
1752
|
}
|
|
1249
1753
|
prop.columnTypes.push(...columnTypes);
|
|
1250
|
-
if (!targetMeta.compositePK) {
|
|
1251
|
-
prop.customType =
|
|
1754
|
+
if (!targetMeta.compositePK || prop.targetKey) {
|
|
1755
|
+
prop.customType = referencedProp.customType;
|
|
1252
1756
|
}
|
|
1253
1757
|
}
|
|
1254
1758
|
}
|
|
@@ -1262,7 +1766,7 @@ export class MetadataDiscovery {
|
|
|
1262
1766
|
t = 'enum';
|
|
1263
1767
|
}
|
|
1264
1768
|
else if (prop.enum) {
|
|
1265
|
-
t = prop.items?.every(item =>
|
|
1769
|
+
t = prop.items?.every(item => typeof item === 'string') ? 'enum' : 'tinyint';
|
|
1266
1770
|
}
|
|
1267
1771
|
if (t === 'Date') {
|
|
1268
1772
|
t = 'datetime';
|
|
@@ -1286,8 +1790,7 @@ export class MetadataDiscovery {
|
|
|
1286
1790
|
return;
|
|
1287
1791
|
}
|
|
1288
1792
|
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
1289
|
-
|
|
1290
|
-
prop.unsigned = meta2.getPrimaryProps().some(pk => {
|
|
1793
|
+
prop.unsigned = prop.targetMeta.getPrimaryProps().some(pk => {
|
|
1291
1794
|
this.initUnsigned(pk);
|
|
1292
1795
|
return pk.unsigned;
|
|
1293
1796
|
});
|
|
@@ -1301,30 +1804,6 @@ export class MetadataDiscovery {
|
|
|
1301
1804
|
prop.index ??= true;
|
|
1302
1805
|
}
|
|
1303
1806
|
}
|
|
1304
|
-
async getEntityClassOrSchema(path, name) {
|
|
1305
|
-
const exports = await Utils.dynamicImport(path);
|
|
1306
|
-
const targets = Object.values(exports)
|
|
1307
|
-
.filter(item => item instanceof EntitySchema || (item instanceof Function && MetadataStorage.isKnownEntity(item.name)));
|
|
1308
|
-
// ignore class implementations that are linked from an EntitySchema
|
|
1309
|
-
for (const item of targets) {
|
|
1310
|
-
if (item instanceof EntitySchema) {
|
|
1311
|
-
targets.forEach((item2, idx) => {
|
|
1312
|
-
if (item.meta.class === item2) {
|
|
1313
|
-
targets.splice(idx, 1);
|
|
1314
|
-
}
|
|
1315
|
-
});
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
if (targets.length > 0) {
|
|
1319
|
-
return targets;
|
|
1320
|
-
}
|
|
1321
|
-
const target = exports.default ?? exports[name];
|
|
1322
|
-
/* v8 ignore next 3 */
|
|
1323
|
-
if (!target) {
|
|
1324
|
-
throw MetadataError.entityNotFound(name, path.replace(this.config.get('baseDir'), '.'));
|
|
1325
|
-
}
|
|
1326
|
-
return [target];
|
|
1327
|
-
}
|
|
1328
1807
|
shouldForceConstructorUsage(meta) {
|
|
1329
1808
|
const forceConstructor = this.config.get('forceEntityConstructor');
|
|
1330
1809
|
if (Array.isArray(forceConstructor)) {
|