@mikro-orm/core 7.0.0-dev.21 → 7.0.0-dev.211
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 +99 -57
- package/EntityManager.js +302 -276
- package/MikroORM.d.ts +44 -35
- package/MikroORM.js +103 -143
- package/README.md +3 -2
- package/cache/FileCacheAdapter.d.ts +1 -1
- package/cache/FileCacheAdapter.js +8 -7
- 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 +80 -35
- package/drivers/IDatabaseDriver.d.ts +44 -17
- package/entity/BaseEntity.d.ts +2 -2
- package/entity/BaseEntity.js +0 -3
- package/entity/Collection.d.ts +94 -29
- package/entity/Collection.js +434 -97
- package/entity/EntityAssigner.d.ts +1 -1
- package/entity/EntityAssigner.js +26 -18
- package/entity/EntityFactory.d.ts +13 -1
- package/entity/EntityFactory.js +84 -53
- package/entity/EntityHelper.d.ts +2 -2
- package/entity/EntityHelper.js +40 -15
- package/entity/EntityLoader.d.ts +6 -6
- package/entity/EntityLoader.js +119 -82
- package/entity/EntityRepository.d.ts +24 -4
- package/entity/EntityRepository.js +8 -2
- package/entity/Reference.d.ts +6 -5
- package/entity/Reference.js +34 -9
- package/entity/WrappedEntity.d.ts +2 -7
- package/entity/WrappedEntity.js +3 -8
- package/entity/defineEntity.d.ts +594 -0
- package/entity/defineEntity.js +533 -0
- package/entity/index.d.ts +3 -2
- package/entity/index.js +3 -2
- package/entity/utils.d.ts +7 -0
- package/entity/utils.js +16 -4
- package/entity/validators.d.ts +11 -0
- package/entity/validators.js +65 -0
- package/enums.d.ts +21 -5
- package/enums.js +15 -1
- package/errors.d.ts +23 -9
- package/errors.js +59 -21
- package/events/EventManager.d.ts +2 -1
- package/events/EventManager.js +19 -11
- package/hydration/Hydrator.js +1 -2
- package/hydration/ObjectHydrator.d.ts +4 -4
- package/hydration/ObjectHydrator.js +52 -33
- 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 +40 -23
- package/metadata/EntitySchema.js +81 -34
- package/metadata/MetadataDiscovery.d.ts +7 -10
- package/metadata/MetadataDiscovery.js +391 -331
- package/metadata/MetadataProvider.d.ts +11 -2
- package/metadata/MetadataProvider.js +46 -2
- package/metadata/MetadataStorage.d.ts +13 -11
- package/metadata/MetadataStorage.js +70 -37
- package/metadata/MetadataValidator.d.ts +17 -9
- package/metadata/MetadataValidator.js +97 -40
- 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 +502 -0
- package/metadata/types.js +1 -0
- package/naming-strategy/AbstractNamingStrategy.d.ts +12 -4
- package/naming-strategy/AbstractNamingStrategy.js +14 -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 +24 -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 +18 -11
- package/platforms/ExceptionConverter.js +1 -1
- package/platforms/Platform.d.ts +7 -13
- package/platforms/Platform.js +20 -43
- package/serialization/EntitySerializer.d.ts +5 -0
- package/serialization/EntitySerializer.js +47 -27
- package/serialization/EntityTransformer.js +28 -18
- 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 +8 -6
- package/types/BigIntType.js +1 -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 +290 -137
- package/typings.js +59 -44
- package/unit-of-work/ChangeSet.d.ts +2 -6
- package/unit-of-work/ChangeSet.js +4 -5
- package/unit-of-work/ChangeSetComputer.d.ts +1 -3
- package/unit-of-work/ChangeSetComputer.js +26 -13
- package/unit-of-work/ChangeSetPersister.d.ts +5 -4
- package/unit-of-work/ChangeSetPersister.js +70 -34
- 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 +23 -3
- package/unit-of-work/UnitOfWork.js +175 -98
- package/utils/AbstractSchemaGenerator.d.ts +5 -5
- package/utils/AbstractSchemaGenerator.js +18 -16
- package/utils/AsyncContext.d.ts +6 -0
- package/utils/AsyncContext.js +42 -0
- package/utils/Configuration.d.ts +785 -207
- package/utils/Configuration.js +147 -190
- package/utils/ConfigurationLoader.d.ts +1 -54
- package/utils/ConfigurationLoader.js +1 -352
- package/utils/Cursor.d.ts +0 -3
- package/utils/Cursor.js +27 -11
- package/utils/DataloaderUtils.d.ts +15 -5
- package/utils/DataloaderUtils.js +64 -30
- package/utils/EntityComparator.d.ts +13 -9
- package/utils/EntityComparator.js +101 -42
- package/utils/QueryHelper.d.ts +14 -6
- package/utils/QueryHelper.js +87 -25
- package/utils/RawQueryFragment.d.ts +48 -25
- package/utils/RawQueryFragment.js +66 -70
- 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 +13 -126
- package/utils/Utils.js +100 -391
- 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 +32 -0
- package/utils/fs-utils.js +178 -0
- package/utils/index.d.ts +2 -1
- package/utils/index.js +2 -1
- 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 -12
- package/decorators/Embedded.js +0 -18
- package/decorators/Entity.d.ts +0 -18
- package/decorators/Entity.js +0 -12
- 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 -4
- package/decorators/Formula.js +0 -15
- package/decorators/Indexed.d.ts +0 -19
- 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 -32
- 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 -26
- package/decorators/OneToOne.js +0 -7
- package/decorators/PrimaryKey.d.ts +0 -8
- 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 -402
- package/entity/EntityValidator.d.ts +0 -19
- package/entity/EntityValidator.js +0 -150
- package/exports.d.ts +0 -24
- package/exports.js +0 -23
- 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,21 @@
|
|
|
1
|
-
import { basename, extname } from 'node:path';
|
|
2
|
-
import { glob } from 'tinyglobby';
|
|
3
1
|
import { EntityMetadata, } from '../typings.js';
|
|
4
|
-
import { Utils } from '../utils/Utils.js';
|
|
2
|
+
import { compareArrays, Utils } from '../utils/Utils.js';
|
|
5
3
|
import { MetadataValidator } from './MetadataValidator.js';
|
|
4
|
+
import { MetadataProvider } from './MetadataProvider.js';
|
|
6
5
|
import { MetadataStorage } from './MetadataStorage.js';
|
|
7
6
|
import { EntitySchema } from './EntitySchema.js';
|
|
8
7
|
import { Cascade, ReferenceKind } from '../enums.js';
|
|
9
8
|
import { MetadataError } from '../errors.js';
|
|
10
|
-
import {
|
|
9
|
+
import { t, Type } from '../types/index.js';
|
|
11
10
|
import { colors } from '../logging/colors.js';
|
|
12
|
-
import { raw,
|
|
11
|
+
import { raw, Raw } from '../utils/RawQueryFragment.js';
|
|
12
|
+
import { BaseEntity } from '../entity/BaseEntity.js';
|
|
13
13
|
export class MetadataDiscovery {
|
|
14
14
|
metadata;
|
|
15
15
|
platform;
|
|
16
16
|
config;
|
|
17
17
|
namingStrategy;
|
|
18
18
|
metadataProvider;
|
|
19
|
-
cache;
|
|
20
19
|
logger;
|
|
21
20
|
schemaHelper;
|
|
22
21
|
validator = new MetadataValidator();
|
|
@@ -27,13 +26,14 @@ export class MetadataDiscovery {
|
|
|
27
26
|
this.config = config;
|
|
28
27
|
this.namingStrategy = this.config.getNamingStrategy();
|
|
29
28
|
this.metadataProvider = this.config.getMetadataProvider();
|
|
30
|
-
this.cache = this.config.getMetadataCacheAdapter();
|
|
31
29
|
this.logger = this.config.getLogger();
|
|
32
30
|
this.schemaHelper = this.platform.getSchemaHelper();
|
|
33
31
|
}
|
|
34
32
|
async discover(preferTs = true) {
|
|
33
|
+
this.discovered.length = 0;
|
|
35
34
|
const startTime = Date.now();
|
|
36
|
-
this.
|
|
35
|
+
const suffix = this.metadataProvider.constructor === MetadataProvider ? '' : `, using ${colors.cyan(this.metadataProvider.constructor.name)}`;
|
|
36
|
+
this.logger.log('discovery', `ORM entity discovery started${suffix}`);
|
|
37
37
|
await this.findEntities(preferTs);
|
|
38
38
|
for (const meta of this.discovered) {
|
|
39
39
|
/* v8 ignore next */
|
|
@@ -47,10 +47,13 @@ export class MetadataDiscovery {
|
|
|
47
47
|
await this.config.get('discovery').afterDiscovered?.(storage, this.platform);
|
|
48
48
|
return storage;
|
|
49
49
|
}
|
|
50
|
-
discoverSync(
|
|
50
|
+
discoverSync() {
|
|
51
|
+
this.discovered.length = 0;
|
|
51
52
|
const startTime = Date.now();
|
|
52
|
-
this.
|
|
53
|
-
this.
|
|
53
|
+
const suffix = this.metadataProvider.constructor === MetadataProvider ? '' : `, using ${colors.cyan(this.metadataProvider.constructor.name)}`;
|
|
54
|
+
this.logger.log('discovery', `ORM entity discovery started${suffix} in sync mode`);
|
|
55
|
+
const refs = this.config.get('entities');
|
|
56
|
+
this.discoverReferences(refs);
|
|
54
57
|
for (const meta of this.discovered) {
|
|
55
58
|
/* v8 ignore next */
|
|
56
59
|
void this.config.get('discovery').onMetadata?.(meta, this.platform);
|
|
@@ -70,15 +73,48 @@ export class MetadataDiscovery {
|
|
|
70
73
|
.sort((a, b) => b.root.name.localeCompare(a.root.name))
|
|
71
74
|
.forEach(meta => {
|
|
72
75
|
this.platform.validateMetadata(meta);
|
|
73
|
-
discovered.set(meta.
|
|
76
|
+
discovered.set(meta.class, meta);
|
|
74
77
|
});
|
|
78
|
+
for (const meta of discovered) {
|
|
79
|
+
meta.root = discovered.get(meta.root.class);
|
|
80
|
+
}
|
|
75
81
|
return discovered;
|
|
76
82
|
}
|
|
83
|
+
initAccessors(meta) {
|
|
84
|
+
for (const prop of Object.values(meta.properties)) {
|
|
85
|
+
if (!prop.accessor || meta.properties[prop.accessor]) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const desc = Object.getOwnPropertyDescriptor(meta.prototype, prop.name);
|
|
89
|
+
if (desc?.get || desc?.set) {
|
|
90
|
+
this.initFieldName(prop);
|
|
91
|
+
const accessor = prop.name;
|
|
92
|
+
prop.name = typeof prop.accessor === 'string' ? prop.accessor : prop.name;
|
|
93
|
+
if (prop.accessor === true) {
|
|
94
|
+
prop.getter = prop.setter = true;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
prop.getter = prop.setter = false;
|
|
98
|
+
}
|
|
99
|
+
prop.accessor = accessor;
|
|
100
|
+
prop.serializedName ??= accessor;
|
|
101
|
+
Utils.renameKey(meta.properties, accessor, prop.name);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const name = prop.name;
|
|
105
|
+
prop.name = prop.accessor;
|
|
106
|
+
this.initFieldName(prop);
|
|
107
|
+
prop.serializedName ??= prop.accessor;
|
|
108
|
+
prop.name = name;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
77
112
|
processDiscoveredEntities(discovered) {
|
|
78
113
|
for (const meta of discovered) {
|
|
79
114
|
let i = 1;
|
|
80
115
|
Object.values(meta.properties).forEach(prop => meta.propertyOrder.set(prop.name, i++));
|
|
81
116
|
Object.values(meta.properties).forEach(prop => this.initPolyEmbeddables(prop, discovered));
|
|
117
|
+
this.initAccessors(meta);
|
|
82
118
|
}
|
|
83
119
|
// ignore base entities (not annotated with @Entity)
|
|
84
120
|
const filtered = discovered.filter(meta => meta.root.name);
|
|
@@ -86,66 +122,61 @@ export class MetadataDiscovery {
|
|
|
86
122
|
filtered.sort((a, b) => !a.embeddable === !b.embeddable ? 0 : (a.embeddable ? 1 : -1));
|
|
87
123
|
filtered.forEach(meta => this.initSingleTableInheritance(meta, filtered));
|
|
88
124
|
filtered.forEach(meta => this.defineBaseEntityProperties(meta));
|
|
89
|
-
filtered.forEach(meta =>
|
|
125
|
+
filtered.forEach(meta => {
|
|
126
|
+
const newMeta = EntitySchema.fromMetadata(meta).init().meta;
|
|
127
|
+
return this.metadata.set(newMeta.class, newMeta);
|
|
128
|
+
});
|
|
90
129
|
filtered.forEach(meta => this.initAutoincrement(meta));
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
130
|
+
const forEachProp = (cb) => {
|
|
131
|
+
filtered.forEach(meta => Object.values(meta.properties).forEach(prop => cb(meta, prop)));
|
|
132
|
+
};
|
|
133
|
+
forEachProp((m, p) => this.initFactoryField(m, p));
|
|
134
|
+
forEachProp((_m, p) => this.initRelation(p));
|
|
135
|
+
forEachProp((m, p) => this.initEmbeddables(m, p));
|
|
136
|
+
forEachProp((_m, p) => this.initFieldName(p));
|
|
137
|
+
forEachProp((m, p) => this.initVersionProperty(m, p));
|
|
138
|
+
forEachProp((m, p) => this.initCustomType(m, p));
|
|
139
|
+
forEachProp((m, p) => this.initGeneratedColumn(m, p));
|
|
97
140
|
filtered.forEach(meta => this.initAutoincrement(meta)); // once again after we init custom types
|
|
98
141
|
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)));
|
|
142
|
+
forEachProp((_m, p) => {
|
|
143
|
+
this.initDefaultValue(p);
|
|
144
|
+
this.inferTypeFromDefault(p);
|
|
145
|
+
this.initRelation(p);
|
|
146
|
+
this.initColumnType(p);
|
|
147
|
+
});
|
|
148
|
+
forEachProp((m, p) => this.initIndexes(m, p));
|
|
111
149
|
filtered.forEach(meta => this.autoWireBidirectionalProperties(meta));
|
|
112
|
-
filtered.forEach(meta => this.findReferencingProperties(meta, filtered));
|
|
113
150
|
for (const meta of filtered) {
|
|
114
151
|
discovered.push(...this.processEntity(meta));
|
|
115
152
|
}
|
|
116
153
|
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
|
-
}
|
|
154
|
+
this.metadataProvider.combineCache();
|
|
122
155
|
return discovered.map(meta => {
|
|
123
|
-
meta = this.metadata.get(meta.
|
|
156
|
+
meta = this.metadata.get(meta.class);
|
|
124
157
|
meta.sync(true);
|
|
158
|
+
this.findReferencingProperties(meta, filtered);
|
|
125
159
|
return meta;
|
|
126
160
|
});
|
|
127
161
|
}
|
|
128
|
-
findEntities(preferTs
|
|
129
|
-
this.
|
|
130
|
-
const
|
|
131
|
-
const
|
|
132
|
-
const paths =
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
162
|
+
async findEntities(preferTs) {
|
|
163
|
+
const { entities, entitiesTs, baseDir } = this.config.getAll();
|
|
164
|
+
const targets = (preferTs && entitiesTs.length > 0) ? entitiesTs : entities;
|
|
165
|
+
const processed = [];
|
|
166
|
+
const paths = [];
|
|
167
|
+
for (const entity of targets) {
|
|
168
|
+
if (typeof entity === 'string') {
|
|
169
|
+
paths.push(entity);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
processed.push(entity);
|
|
137
173
|
}
|
|
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
|
-
});
|
|
144
174
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
175
|
+
if (paths.length > 0) {
|
|
176
|
+
const { discoverEntities } = await import('@mikro-orm/core/file-discovery');
|
|
177
|
+
processed.push(...await discoverEntities(paths, { baseDir }));
|
|
178
|
+
}
|
|
179
|
+
return this.discoverReferences(processed);
|
|
149
180
|
}
|
|
150
181
|
discoverMissingTargets() {
|
|
151
182
|
const unwrap = (type) => type
|
|
@@ -154,17 +185,22 @@ export class MetadataDiscovery {
|
|
|
154
185
|
.replace(/\((.*)\)/, '$1'); // unwrap union types
|
|
155
186
|
const missing = [];
|
|
156
187
|
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
|
-
|
|
188
|
+
if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.pivotEntity) {
|
|
189
|
+
const pivotEntity = prop.pivotEntity;
|
|
190
|
+
const target = typeof pivotEntity === 'function' && !pivotEntity.prototype
|
|
191
|
+
? pivotEntity()
|
|
192
|
+
: pivotEntity;
|
|
193
|
+
if (!this.discovered.find(m => m.className === Utils.className(target))) {
|
|
194
|
+
missing.push(target);
|
|
195
|
+
}
|
|
162
196
|
}
|
|
163
|
-
if (prop.kind !== ReferenceKind.SCALAR
|
|
164
|
-
const target = typeof prop.entity === 'function'
|
|
197
|
+
if (prop.kind !== ReferenceKind.SCALAR) {
|
|
198
|
+
const target = typeof prop.entity === 'function' && !prop.entity.prototype
|
|
165
199
|
? prop.entity()
|
|
166
200
|
: prop.type;
|
|
167
|
-
|
|
201
|
+
if (!unwrap(prop.type).split(/ ?\| ?/).every(type => this.discovered.find(m => m.className === type))) {
|
|
202
|
+
missing.push(...Utils.asArray(target));
|
|
203
|
+
}
|
|
168
204
|
}
|
|
169
205
|
}));
|
|
170
206
|
if (missing.length > 0) {
|
|
@@ -173,123 +209,105 @@ export class MetadataDiscovery {
|
|
|
173
209
|
}
|
|
174
210
|
tryDiscoverTargets(targets) {
|
|
175
211
|
for (const target of targets) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const files = await glob(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;
|
|
212
|
+
const schema = target instanceof EntitySchema ? target : undefined;
|
|
213
|
+
const isDiscoverable = typeof target === 'function' || schema;
|
|
214
|
+
if (isDiscoverable && target.name) {
|
|
215
|
+
// Get the actual class for EntitySchema, or use target directly for classes
|
|
216
|
+
const targetClass = schema ? schema.meta.class : target;
|
|
217
|
+
if (!this.metadata.has(targetClass)) {
|
|
218
|
+
this.discoverReferences([target], false);
|
|
219
|
+
this.discoverMissingTargets();
|
|
204
220
|
}
|
|
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
221
|
}
|
|
211
222
|
}
|
|
212
|
-
for (const [schema, path] of found) {
|
|
213
|
-
this.discoverEntity(schema, path);
|
|
214
|
-
}
|
|
215
223
|
}
|
|
216
|
-
discoverReferences(refs) {
|
|
224
|
+
discoverReferences(refs, validate = true) {
|
|
217
225
|
const found = [];
|
|
218
226
|
for (const entity of refs) {
|
|
219
|
-
|
|
227
|
+
if (typeof entity === 'string') {
|
|
228
|
+
throw new Error('Folder based discovery requires the async `MikroORM.init()` method.');
|
|
229
|
+
}
|
|
230
|
+
const schema = this.getSchema(entity);
|
|
220
231
|
const meta = schema.init().meta;
|
|
221
|
-
this.metadata.set(meta.
|
|
232
|
+
this.metadata.set(meta.class, meta);
|
|
222
233
|
found.push(schema);
|
|
223
234
|
}
|
|
224
235
|
// discover parents (base entities) automatically
|
|
225
236
|
for (const meta of this.metadata) {
|
|
226
237
|
let parent = meta.extends;
|
|
227
|
-
if (parent instanceof EntitySchema && !this.metadata.has(parent.meta.
|
|
228
|
-
this.discoverReferences([parent]);
|
|
238
|
+
if (parent instanceof EntitySchema && !this.metadata.has(parent.init().meta.class)) {
|
|
239
|
+
this.discoverReferences([parent], false);
|
|
240
|
+
}
|
|
241
|
+
if (typeof parent === 'function' && parent.name && !this.metadata.has(parent)) {
|
|
242
|
+
this.discoverReferences([parent], false);
|
|
229
243
|
}
|
|
230
|
-
/* v8 ignore next
|
|
244
|
+
/* v8 ignore next */
|
|
231
245
|
if (!meta.class) {
|
|
232
246
|
continue;
|
|
233
247
|
}
|
|
234
248
|
parent = Object.getPrototypeOf(meta.class);
|
|
235
|
-
if
|
|
236
|
-
|
|
249
|
+
// Skip if parent is the auto-generated base class for the same entity (from setClass usage)
|
|
250
|
+
if (parent.name !== '' && parent.name !== meta.className && !this.metadata.has(parent) && parent !== BaseEntity) {
|
|
251
|
+
this.discoverReferences([parent], false);
|
|
237
252
|
}
|
|
238
253
|
}
|
|
239
254
|
for (const schema of found) {
|
|
240
255
|
this.discoverEntity(schema);
|
|
241
256
|
}
|
|
257
|
+
this.discoverMissingTargets();
|
|
258
|
+
if (validate) {
|
|
259
|
+
this.validator.validateDiscovered(this.discovered, this.config.get('discovery'));
|
|
260
|
+
}
|
|
242
261
|
return this.discovered.filter(meta => found.find(m => m.name === meta.className));
|
|
243
262
|
}
|
|
244
|
-
reset(
|
|
245
|
-
const exists = this.discovered.findIndex(m => m.className === className);
|
|
263
|
+
reset(entityName) {
|
|
264
|
+
const exists = this.discovered.findIndex(m => m.class === entityName || m.className === Utils.className(entityName));
|
|
246
265
|
if (exists !== -1) {
|
|
247
|
-
this.metadata.reset(this.discovered[exists].
|
|
266
|
+
this.metadata.reset(this.discovered[exists].class);
|
|
248
267
|
this.discovered.splice(exists, 1);
|
|
249
268
|
}
|
|
250
269
|
}
|
|
251
|
-
|
|
252
|
-
/* v8 ignore next 3 */
|
|
253
|
-
if ('schema' in entity && entity.schema instanceof EntitySchema) {
|
|
254
|
-
return entity.schema;
|
|
255
|
-
}
|
|
270
|
+
getSchema(entity) {
|
|
256
271
|
if (EntitySchema.REGISTRY.has(entity)) {
|
|
257
|
-
|
|
272
|
+
entity = EntitySchema.REGISTRY.get(entity);
|
|
258
273
|
}
|
|
259
|
-
return entity;
|
|
260
|
-
}
|
|
261
|
-
getSchema(entity, filepath) {
|
|
262
274
|
if (entity instanceof EntitySchema) {
|
|
263
|
-
if (filepath) {
|
|
264
|
-
// initialize global metadata for given entity
|
|
265
|
-
MetadataStorage.getMetadata(entity.meta.className, filepath);
|
|
266
|
-
}
|
|
267
275
|
const meta = Utils.copy(entity.meta, false);
|
|
268
276
|
return EntitySchema.fromMetadata(meta);
|
|
269
277
|
}
|
|
270
278
|
const path = entity[MetadataStorage.PATH_SYMBOL];
|
|
271
279
|
if (path) {
|
|
272
280
|
const meta = Utils.copy(MetadataStorage.getMetadata(entity.name, path), false);
|
|
273
|
-
meta.path =
|
|
274
|
-
this.metadata.set(entity
|
|
281
|
+
meta.path = path;
|
|
282
|
+
this.metadata.set(entity, meta);
|
|
275
283
|
}
|
|
276
|
-
const exists = this.metadata.has(entity
|
|
277
|
-
const meta = this.metadata.get(entity
|
|
284
|
+
const exists = this.metadata.has(entity);
|
|
285
|
+
const meta = this.metadata.get(entity, true);
|
|
278
286
|
meta.abstract ??= !(exists && meta.name);
|
|
279
287
|
const schema = EntitySchema.fromMetadata(meta);
|
|
280
288
|
schema.setClass(entity);
|
|
281
289
|
return schema;
|
|
282
290
|
}
|
|
283
|
-
|
|
284
|
-
|
|
291
|
+
getRootEntity(meta) {
|
|
292
|
+
const base = meta.extends && this.metadata.find(meta.extends);
|
|
293
|
+
if (!base || base === meta) { // make sure we do not fall into infinite loop
|
|
294
|
+
return meta;
|
|
295
|
+
}
|
|
296
|
+
const root = this.getRootEntity(base);
|
|
297
|
+
if (root.discriminatorColumn) {
|
|
298
|
+
return root;
|
|
299
|
+
}
|
|
300
|
+
return meta;
|
|
301
|
+
}
|
|
302
|
+
discoverEntity(schema) {
|
|
285
303
|
const meta = schema.meta;
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
const
|
|
304
|
+
const path = meta.path;
|
|
305
|
+
this.logger.log('discovery', `- processing entity ${colors.cyan(meta.className)}${colors.grey(path ? ` (${path})` : '')}`);
|
|
306
|
+
const root = this.getRootEntity(meta);
|
|
307
|
+
schema.meta.path = meta.path;
|
|
308
|
+
const cache = this.metadataProvider.getCachedMetadata(meta, root);
|
|
289
309
|
if (cache) {
|
|
290
310
|
this.logger.log('discovery', `- using cached metadata for entity ${colors.cyan(meta.className)}`);
|
|
291
|
-
this.metadataProvider.loadFromCache(meta, cache);
|
|
292
|
-
meta.root = root;
|
|
293
311
|
this.discovered.push(meta);
|
|
294
312
|
return;
|
|
295
313
|
}
|
|
@@ -298,50 +316,18 @@ export class MetadataDiscovery {
|
|
|
298
316
|
this.inferDefaultValue(meta, prop);
|
|
299
317
|
}
|
|
300
318
|
// if the definition is using EntitySchema we still want it to go through the metadata provider to validate no types are missing
|
|
301
|
-
this.metadataProvider.loadEntityMetadata(meta
|
|
302
|
-
if (!meta.
|
|
319
|
+
this.metadataProvider.loadEntityMetadata(meta);
|
|
320
|
+
if (!meta.tableName && meta.name) {
|
|
303
321
|
const entityName = root.discriminatorColumn ? root.name : meta.name;
|
|
304
|
-
meta.
|
|
322
|
+
meta.tableName = this.namingStrategy.classToTableName(entityName);
|
|
305
323
|
}
|
|
306
|
-
|
|
307
|
-
this.saveToCache(meta);
|
|
324
|
+
this.metadataProvider.saveToCache(meta);
|
|
308
325
|
meta.root = root;
|
|
309
326
|
this.discovered.push(meta);
|
|
310
327
|
}
|
|
311
|
-
saveToCache(meta) {
|
|
312
|
-
if (!this.metadataProvider.useCache()) {
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
const copy = Utils.copy(meta, false);
|
|
316
|
-
for (const prop of copy.props) {
|
|
317
|
-
if (Type.isMappedType(prop.type)) {
|
|
318
|
-
Reflect.deleteProperty(prop, 'type');
|
|
319
|
-
Reflect.deleteProperty(prop, 'customType');
|
|
320
|
-
}
|
|
321
|
-
if (prop.default) {
|
|
322
|
-
const raw = RawQueryFragment.getKnownFragment(prop.default);
|
|
323
|
-
if (raw) {
|
|
324
|
-
prop.defaultRaw ??= this.platform.formatQuery(raw.sql, raw.params);
|
|
325
|
-
Reflect.deleteProperty(prop, 'default');
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
Reflect.deleteProperty(prop, 'targetMeta');
|
|
329
|
-
}
|
|
330
|
-
[
|
|
331
|
-
'prototype', 'props', 'referencingProperties', 'propertyOrder', 'relations',
|
|
332
|
-
'concurrencyCheckKeys', 'checks',
|
|
333
|
-
].forEach(key => delete copy[key]);
|
|
334
|
-
// base entity without properties might not have path, but nothing to cache there
|
|
335
|
-
if (meta.path) {
|
|
336
|
-
this.cache.set(meta.className + extname(meta.path), copy, meta.path);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
328
|
initNullability(prop) {
|
|
340
|
-
if (prop.kind === ReferenceKind.MANY_TO_ONE) {
|
|
341
|
-
return Utils.defaultValue(prop, 'nullable', prop.optional || prop.cascade.includes(Cascade.REMOVE) || prop.cascade.includes(Cascade.ALL));
|
|
342
|
-
}
|
|
343
329
|
if (prop.kind === ReferenceKind.ONE_TO_ONE) {
|
|
344
|
-
return Utils.defaultValue(prop, 'nullable', prop.optional || !prop.owner
|
|
330
|
+
return Utils.defaultValue(prop, 'nullable', prop.optional || !prop.owner);
|
|
345
331
|
}
|
|
346
332
|
return Utils.defaultValue(prop, 'nullable', prop.optional);
|
|
347
333
|
}
|
|
@@ -376,7 +362,7 @@ export class MetadataDiscovery {
|
|
|
376
362
|
if (prop.joinColumns.length !== prop.columnTypes.length) {
|
|
377
363
|
prop.columnTypes = prop.joinColumns.flatMap(field => {
|
|
378
364
|
const matched = meta.props.find(p => p.fieldNames?.includes(field));
|
|
379
|
-
/* v8 ignore next
|
|
365
|
+
/* v8 ignore next */
|
|
380
366
|
if (!matched) {
|
|
381
367
|
throw MetadataError.fromWrongForeignKey(meta, prop, 'columnTypes');
|
|
382
368
|
}
|
|
@@ -402,23 +388,23 @@ export class MetadataDiscovery {
|
|
|
402
388
|
prop.fieldNames = this.initManyToManyFieldName(prop, prop.name);
|
|
403
389
|
}
|
|
404
390
|
}
|
|
405
|
-
initManyToOneFieldName(prop, name) {
|
|
406
|
-
const meta2 =
|
|
391
|
+
initManyToOneFieldName(prop, name, tableName) {
|
|
392
|
+
const meta2 = prop.targetMeta;
|
|
407
393
|
const ret = [];
|
|
408
394
|
for (const primaryKey of meta2.primaryKeys) {
|
|
409
395
|
this.initFieldName(meta2.properties[primaryKey]);
|
|
410
396
|
for (const fieldName of meta2.properties[primaryKey].fieldNames) {
|
|
411
|
-
ret.push(this.namingStrategy.joinKeyColumnName(name, fieldName, meta2.compositePK));
|
|
397
|
+
ret.push(this.namingStrategy.joinKeyColumnName(name, fieldName, meta2.compositePK, tableName));
|
|
412
398
|
}
|
|
413
399
|
}
|
|
414
400
|
return ret;
|
|
415
401
|
}
|
|
416
402
|
initManyToManyFieldName(prop, name) {
|
|
417
|
-
const meta2 =
|
|
403
|
+
const meta2 = prop.targetMeta;
|
|
418
404
|
return meta2.primaryKeys.map(() => this.namingStrategy.propertyToColumnName(name));
|
|
419
405
|
}
|
|
420
406
|
initManyToManyFields(meta, prop) {
|
|
421
|
-
const meta2 =
|
|
407
|
+
const meta2 = prop.targetMeta;
|
|
422
408
|
Utils.defaultValue(prop, 'fixedOrder', !!prop.fixedOrderColumn);
|
|
423
409
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
424
410
|
const props = Object.values(pivotMeta?.properties ?? {});
|
|
@@ -439,35 +425,58 @@ export class MetadataDiscovery {
|
|
|
439
425
|
prop.inverseJoinColumns ??= second.fieldNames;
|
|
440
426
|
}
|
|
441
427
|
if (!prop.pivotTable && prop.owner && this.platform.usesPivotTable()) {
|
|
442
|
-
prop.pivotTable = this.namingStrategy.joinTableName(meta.
|
|
428
|
+
prop.pivotTable = this.namingStrategy.joinTableName(meta.className, meta2.tableName, prop.name, meta.tableName);
|
|
443
429
|
}
|
|
444
430
|
if (prop.mappedBy) {
|
|
445
431
|
const prop2 = meta2.properties[prop.mappedBy];
|
|
446
432
|
this.initManyToManyFields(meta2, prop2);
|
|
447
433
|
prop.pivotTable = prop2.pivotTable;
|
|
448
|
-
prop.pivotEntity = prop2.pivotEntity
|
|
434
|
+
prop.pivotEntity = prop2.pivotEntity;
|
|
449
435
|
prop.fixedOrder = prop2.fixedOrder;
|
|
450
436
|
prop.fixedOrderColumn = prop2.fixedOrderColumn;
|
|
451
437
|
prop.joinColumns = prop2.inverseJoinColumns;
|
|
452
438
|
prop.inverseJoinColumns = prop2.joinColumns;
|
|
453
439
|
}
|
|
454
440
|
prop.referencedColumnNames ??= Utils.flatten(meta.primaryKeys.map(primaryKey => meta.properties[primaryKey].fieldNames));
|
|
455
|
-
|
|
456
|
-
|
|
441
|
+
const ownerTableName = this.isExplicitTableName(meta.root) ? meta.root.tableName : undefined;
|
|
442
|
+
const inverseTableName = this.isExplicitTableName(meta2.root) ? meta2.root.tableName : undefined;
|
|
443
|
+
prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.namingStrategy.joinKeyColumnName(meta.root.className, referencedColumnName, meta.compositePK, ownerTableName));
|
|
444
|
+
prop.inverseJoinColumns ??= this.initManyToOneFieldName(prop, meta2.root.className, inverseTableName);
|
|
445
|
+
}
|
|
446
|
+
isExplicitTableName(meta) {
|
|
447
|
+
return meta.tableName !== this.namingStrategy.classToTableName(meta.className);
|
|
457
448
|
}
|
|
458
449
|
initManyToOneFields(prop) {
|
|
459
|
-
const meta2 =
|
|
460
|
-
|
|
461
|
-
|
|
450
|
+
const meta2 = prop.targetMeta;
|
|
451
|
+
let fieldNames;
|
|
452
|
+
// If targetKey is specified, use that property's field names instead of PKs
|
|
453
|
+
if (prop.targetKey) {
|
|
454
|
+
const targetProp = meta2.properties[prop.targetKey];
|
|
455
|
+
fieldNames = targetProp.fieldNames;
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
fieldNames = Utils.flatten(meta2.primaryKeys.map(primaryKey => meta2.properties[primaryKey].fieldNames));
|
|
459
|
+
}
|
|
460
|
+
Utils.defaultValue(prop, 'referencedTableName', meta2.tableName);
|
|
462
461
|
if (!prop.joinColumns) {
|
|
463
462
|
prop.joinColumns = fieldNames.map(fieldName => this.namingStrategy.joinKeyColumnName(prop.name, fieldName, fieldNames.length > 1));
|
|
464
463
|
}
|
|
465
464
|
if (!prop.referencedColumnNames) {
|
|
466
465
|
prop.referencedColumnNames = fieldNames;
|
|
467
466
|
}
|
|
467
|
+
// Relations to composite PK targets need cascade update by default,
|
|
468
|
+
// since composite PKs are more likely to have mutable components
|
|
469
|
+
if (meta2.compositePK) {
|
|
470
|
+
prop.updateRule ??= 'cascade';
|
|
471
|
+
}
|
|
472
|
+
// Nullable relations default to 'set null' on delete - when the referenced
|
|
473
|
+
// entity is deleted, set the FK to null rather than failing
|
|
474
|
+
if (prop.nullable) {
|
|
475
|
+
prop.deleteRule ??= 'set null';
|
|
476
|
+
}
|
|
468
477
|
}
|
|
469
478
|
initOneToManyFields(prop) {
|
|
470
|
-
const meta2 =
|
|
479
|
+
const meta2 = prop.targetMeta;
|
|
471
480
|
if (!prop.joinColumns) {
|
|
472
481
|
prop.joinColumns = [this.namingStrategy.joinColumnName(prop.name)];
|
|
473
482
|
}
|
|
@@ -480,12 +489,17 @@ export class MetadataDiscovery {
|
|
|
480
489
|
const pks = Object.values(meta.properties).filter(prop => prop.primary);
|
|
481
490
|
meta.primaryKeys = pks.map(prop => prop.name);
|
|
482
491
|
meta.compositePK = pks.length > 1;
|
|
483
|
-
// FK used as PK, we need to cascade
|
|
484
|
-
|
|
485
|
-
|
|
492
|
+
// FK used as PK, we need to cascade - applies to both single FK-as-PK
|
|
493
|
+
// and composite PKs where all PKs are FKs (e.g., pivot-like entities)
|
|
494
|
+
const fkPks = pks.filter(pk => pk.kind !== ReferenceKind.SCALAR);
|
|
495
|
+
if (fkPks.length > 0 && fkPks.length === pks.length) {
|
|
496
|
+
for (const pk of fkPks) {
|
|
497
|
+
pk.deleteRule ??= 'cascade';
|
|
498
|
+
pk.updateRule ??= 'cascade';
|
|
499
|
+
}
|
|
486
500
|
}
|
|
487
501
|
meta.forceConstructor ??= this.shouldForceConstructorUsage(meta);
|
|
488
|
-
this.validator.validateEntityDefinition(this.metadata, meta.
|
|
502
|
+
this.validator.validateEntityDefinition(this.metadata, meta.class, this.config.get('discovery'));
|
|
489
503
|
for (const prop of Object.values(meta.properties)) {
|
|
490
504
|
this.initNullability(prop);
|
|
491
505
|
this.applyNamingStrategy(meta, prop);
|
|
@@ -498,15 +512,21 @@ export class MetadataDiscovery {
|
|
|
498
512
|
}
|
|
499
513
|
this.initOwnColumns(meta);
|
|
500
514
|
meta.simplePK = pks.length === 1 && pks[0].kind === ReferenceKind.SCALAR && !pks[0].customType && pks[0].runtimeType !== 'Date';
|
|
501
|
-
meta.serializedPrimaryKey
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
serializedPKProp.persist = false;
|
|
515
|
+
meta.serializedPrimaryKey ??= meta.props.find(prop => prop.serializedPrimaryKey)?.name;
|
|
516
|
+
if (meta.serializedPrimaryKey && meta.serializedPrimaryKey !== meta.primaryKeys[0]) {
|
|
517
|
+
meta.properties[meta.serializedPrimaryKey].persist ??= false;
|
|
505
518
|
}
|
|
506
519
|
if (this.platform.usesPivotTable()) {
|
|
507
520
|
return Object.values(meta.properties)
|
|
508
521
|
.filter(prop => prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && prop.pivotTable)
|
|
509
|
-
.map(prop =>
|
|
522
|
+
.map(prop => {
|
|
523
|
+
const pivotMeta = this.definePivotTableEntity(meta, prop);
|
|
524
|
+
prop.pivotEntity = pivotMeta.class;
|
|
525
|
+
if (prop.inversedBy) {
|
|
526
|
+
prop.targetMeta.properties[prop.inversedBy].pivotEntity = pivotMeta.class;
|
|
527
|
+
}
|
|
528
|
+
return pivotMeta;
|
|
529
|
+
});
|
|
510
530
|
}
|
|
511
531
|
return [];
|
|
512
532
|
}
|
|
@@ -523,8 +543,11 @@ export class MetadataDiscovery {
|
|
|
523
543
|
['mappedBy', 'inversedBy', 'pivotEntity'].forEach(type => {
|
|
524
544
|
const value = prop[type];
|
|
525
545
|
if (value instanceof Function) {
|
|
526
|
-
const meta2 = this.metadata.get(prop.
|
|
546
|
+
const meta2 = prop.targetMeta ?? this.metadata.get(prop.target);
|
|
527
547
|
prop[type] = value(meta2.properties)?.name;
|
|
548
|
+
if (type === 'pivotEntity' && value) {
|
|
549
|
+
prop[type] = value(meta2.properties);
|
|
550
|
+
}
|
|
528
551
|
if (prop[type] == null) {
|
|
529
552
|
throw MetadataError.fromWrongReference(meta, prop, type);
|
|
530
553
|
}
|
|
@@ -540,9 +563,9 @@ export class MetadataDiscovery {
|
|
|
540
563
|
}
|
|
541
564
|
else if (fks.length >= 2) {
|
|
542
565
|
[first, second] = fks;
|
|
543
|
-
/* v8 ignore next 3 */
|
|
544
566
|
}
|
|
545
567
|
else {
|
|
568
|
+
/* v8 ignore next */
|
|
546
569
|
return [];
|
|
547
570
|
}
|
|
548
571
|
// wrong FK order, first FK needs to point to the owning side
|
|
@@ -556,7 +579,9 @@ export class MetadataDiscovery {
|
|
|
556
579
|
return [first, second];
|
|
557
580
|
}
|
|
558
581
|
definePivotTableEntity(meta, prop) {
|
|
559
|
-
const pivotMeta =
|
|
582
|
+
const pivotMeta = prop.pivotEntity
|
|
583
|
+
? this.metadata.find(prop.pivotEntity)
|
|
584
|
+
: this.metadata.getByClassName(prop.pivotTable, false);
|
|
560
585
|
// ensure inverse side exists so we can join it when populating via pivot tables
|
|
561
586
|
if (!prop.inversedBy && prop.targetMeta) {
|
|
562
587
|
const inverseName = `${meta.className}_${prop.name}__inverse`;
|
|
@@ -565,6 +590,8 @@ export class MetadataDiscovery {
|
|
|
565
590
|
name: inverseName,
|
|
566
591
|
kind: ReferenceKind.MANY_TO_MANY,
|
|
567
592
|
type: meta.className,
|
|
593
|
+
target: meta.class,
|
|
594
|
+
targetMeta: meta,
|
|
568
595
|
mappedBy: prop.name,
|
|
569
596
|
pivotEntity: prop.pivotEntity,
|
|
570
597
|
pivotTable: prop.pivotTable,
|
|
@@ -573,55 +600,61 @@ export class MetadataDiscovery {
|
|
|
573
600
|
};
|
|
574
601
|
this.applyNamingStrategy(prop.targetMeta, inverseProp);
|
|
575
602
|
this.initCustomType(prop.targetMeta, inverseProp);
|
|
576
|
-
this.initRelation(inverseProp);
|
|
577
603
|
prop.targetMeta.properties[inverseName] = inverseProp;
|
|
578
604
|
}
|
|
579
605
|
if (pivotMeta) {
|
|
606
|
+
prop.pivotEntity = pivotMeta.class;
|
|
580
607
|
this.ensureCorrectFKOrderInPivotEntity(pivotMeta, prop);
|
|
581
608
|
return pivotMeta;
|
|
582
609
|
}
|
|
583
|
-
const exists = this.metadata.find(prop.pivotTable);
|
|
584
|
-
if (exists) {
|
|
585
|
-
prop.pivotEntity = exists.className;
|
|
586
|
-
return exists;
|
|
587
|
-
}
|
|
588
610
|
let tableName = prop.pivotTable;
|
|
589
611
|
let schemaName;
|
|
590
612
|
if (prop.pivotTable.includes('.')) {
|
|
591
613
|
[schemaName, tableName] = prop.pivotTable.split('.');
|
|
592
614
|
}
|
|
593
615
|
schemaName ??= meta.schema;
|
|
594
|
-
const
|
|
595
|
-
const
|
|
616
|
+
const targetMeta = prop.targetMeta;
|
|
617
|
+
const targetType = targetMeta.className;
|
|
618
|
+
const pivotMeta2 = new EntityMetadata({
|
|
596
619
|
name: prop.pivotTable,
|
|
597
620
|
className: prop.pivotTable,
|
|
598
621
|
collection: tableName,
|
|
599
622
|
schema: schemaName,
|
|
600
623
|
pivotTable: true,
|
|
601
624
|
});
|
|
602
|
-
prop.pivotEntity =
|
|
625
|
+
prop.pivotEntity = pivotMeta2.class;
|
|
603
626
|
if (prop.fixedOrder) {
|
|
604
|
-
const primaryProp = this.defineFixedOrderProperty(prop,
|
|
605
|
-
|
|
627
|
+
const primaryProp = this.defineFixedOrderProperty(prop, targetMeta);
|
|
628
|
+
pivotMeta2.properties[primaryProp.name] = primaryProp;
|
|
606
629
|
}
|
|
607
630
|
else {
|
|
608
|
-
|
|
631
|
+
pivotMeta2.compositePK = true;
|
|
609
632
|
}
|
|
610
633
|
// handle self-referenced m:n with same default field names
|
|
611
634
|
if (meta.className === targetType && prop.joinColumns.every((joinColumn, idx) => joinColumn === prop.inverseJoinColumns[idx])) {
|
|
612
|
-
|
|
613
|
-
|
|
635
|
+
// use tableName only when explicitly provided by user, otherwise use className for backwards compatibility
|
|
636
|
+
const baseName = this.isExplicitTableName(meta) ? meta.tableName : meta.className;
|
|
637
|
+
prop.joinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(baseName + '_1', name, meta.compositePK));
|
|
638
|
+
prop.inverseJoinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(baseName + '_2', name, meta.compositePK));
|
|
614
639
|
if (prop.inversedBy) {
|
|
615
|
-
const prop2 =
|
|
640
|
+
const prop2 = targetMeta.properties[prop.inversedBy];
|
|
616
641
|
prop2.inverseJoinColumns = prop.joinColumns;
|
|
617
642
|
prop2.joinColumns = prop.inverseJoinColumns;
|
|
618
643
|
}
|
|
644
|
+
// propagate updated joinColumns to all child entities that inherit this property (STI)
|
|
645
|
+
for (const childMeta of this.discovered.filter(m => m.root === meta && m !== meta)) {
|
|
646
|
+
const childProp = childMeta.properties[prop.name];
|
|
647
|
+
if (childProp) {
|
|
648
|
+
childProp.joinColumns = prop.joinColumns;
|
|
649
|
+
childProp.inverseJoinColumns = prop.inverseJoinColumns;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
619
652
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
return this.metadata.set(
|
|
653
|
+
pivotMeta2.properties[meta.name + '_owner'] = this.definePivotProperty(prop, meta.name + '_owner', meta.class, targetType + '_inverse', true, meta.className === targetType);
|
|
654
|
+
pivotMeta2.properties[targetType + '_inverse'] = this.definePivotProperty(prop, targetType + '_inverse', targetMeta.class, meta.name + '_owner', false, meta.className === targetType);
|
|
655
|
+
return this.metadata.set(pivotMeta2.class, EntitySchema.fromMetadata(pivotMeta2).init().meta);
|
|
623
656
|
}
|
|
624
|
-
defineFixedOrderProperty(prop,
|
|
657
|
+
defineFixedOrderProperty(prop, targetMeta) {
|
|
625
658
|
const pk = prop.fixedOrderColumn || this.namingStrategy.referenceColumnName();
|
|
626
659
|
const primaryProp = {
|
|
627
660
|
name: pk,
|
|
@@ -635,7 +668,7 @@ export class MetadataDiscovery {
|
|
|
635
668
|
this.initColumnType(primaryProp);
|
|
636
669
|
prop.fixedOrderColumn = pk;
|
|
637
670
|
if (prop.inversedBy) {
|
|
638
|
-
const prop2 =
|
|
671
|
+
const prop2 = targetMeta.properties[prop.inversedBy];
|
|
639
672
|
prop2.fixedOrder = true;
|
|
640
673
|
prop2.fixedOrderColumn = pk;
|
|
641
674
|
}
|
|
@@ -644,7 +677,8 @@ export class MetadataDiscovery {
|
|
|
644
677
|
definePivotProperty(prop, name, type, inverse, owner, selfReferencing) {
|
|
645
678
|
const ret = {
|
|
646
679
|
name,
|
|
647
|
-
type,
|
|
680
|
+
type: Utils.className(type),
|
|
681
|
+
target: type,
|
|
648
682
|
kind: ReferenceKind.MANY_TO_ONE,
|
|
649
683
|
cascade: [Cascade.ALL],
|
|
650
684
|
fixedOrder: prop.fixedOrder,
|
|
@@ -654,11 +688,11 @@ export class MetadataDiscovery {
|
|
|
654
688
|
autoincrement: false,
|
|
655
689
|
updateRule: prop.updateRule,
|
|
656
690
|
deleteRule: prop.deleteRule,
|
|
691
|
+
createForeignKeyConstraint: prop.createForeignKeyConstraint,
|
|
657
692
|
};
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
}
|
|
693
|
+
const defaultRule = selfReferencing && !this.platform.supportsMultipleCascadePaths() ? 'no action' : 'cascade';
|
|
694
|
+
ret.updateRule ??= defaultRule;
|
|
695
|
+
ret.deleteRule ??= defaultRule;
|
|
662
696
|
const meta = this.metadata.get(type);
|
|
663
697
|
ret.targetMeta = meta;
|
|
664
698
|
ret.joinColumns = [];
|
|
@@ -701,7 +735,7 @@ export class MetadataDiscovery {
|
|
|
701
735
|
Object.values(meta.properties)
|
|
702
736
|
.filter(prop => prop.kind !== ReferenceKind.SCALAR && !prop.owner && prop.mappedBy)
|
|
703
737
|
.forEach(prop => {
|
|
704
|
-
const meta2 =
|
|
738
|
+
const meta2 = prop.targetMeta;
|
|
705
739
|
const prop2 = meta2.properties[prop.mappedBy];
|
|
706
740
|
if (prop2 && !prop2.inversedBy) {
|
|
707
741
|
prop2.inversedBy = prop.name;
|
|
@@ -709,7 +743,7 @@ export class MetadataDiscovery {
|
|
|
709
743
|
});
|
|
710
744
|
}
|
|
711
745
|
defineBaseEntityProperties(meta) {
|
|
712
|
-
const base = meta.extends && this.metadata.get(
|
|
746
|
+
const base = meta.extends && this.metadata.get(meta.extends);
|
|
713
747
|
if (!base || base === meta) { // make sure we do not fall into infinite loop
|
|
714
748
|
return 0;
|
|
715
749
|
}
|
|
@@ -740,12 +774,9 @@ export class MetadataDiscovery {
|
|
|
740
774
|
Utils.keys(base.hooks).forEach(type => {
|
|
741
775
|
meta.hooks[type] = Utils.unique([...base.hooks[type], ...(meta.hooks[type] || [])]);
|
|
742
776
|
});
|
|
743
|
-
if (meta.constructorParams
|
|
777
|
+
if ((meta.constructorParams?.length ?? 0) === 0 && (base.constructorParams?.length ?? 0) > 0) {
|
|
744
778
|
meta.constructorParams = [...base.constructorParams];
|
|
745
779
|
}
|
|
746
|
-
if (meta.toJsonParams.length === 0 && base.toJsonParams.length > 0) {
|
|
747
|
-
meta.toJsonParams = [...base.toJsonParams];
|
|
748
|
-
}
|
|
749
780
|
return order;
|
|
750
781
|
}
|
|
751
782
|
initPolyEmbeddables(embeddedProp, discovered, visited = new Set()) {
|
|
@@ -821,22 +852,30 @@ export class MetadataDiscovery {
|
|
|
821
852
|
}
|
|
822
853
|
return prop.embedded ? isParentObject(meta.properties[prop.embedded[0]]) : false;
|
|
823
854
|
};
|
|
855
|
+
const isParentArray = (prop) => {
|
|
856
|
+
if (prop.array) {
|
|
857
|
+
return true;
|
|
858
|
+
}
|
|
859
|
+
return prop.embedded ? isParentArray(meta.properties[prop.embedded[0]]) : false;
|
|
860
|
+
};
|
|
824
861
|
const rootProperty = getRootProperty(embeddedProp);
|
|
825
862
|
const parentProperty = meta.properties[embeddedProp.embedded?.[0] ?? ''];
|
|
826
863
|
const object = isParentObject(embeddedProp);
|
|
864
|
+
const array = isParentArray(embeddedProp);
|
|
827
865
|
this.initFieldName(embeddedProp, rootProperty !== embeddedProp && object);
|
|
828
866
|
// the prefix of the parent cannot be a boolean; it already passed here
|
|
829
867
|
const prefix = this.getPrefix(embeddedProp, parentProperty);
|
|
830
868
|
const glue = object ? '~' : '_';
|
|
831
869
|
for (const prop of Object.values(embeddable.properties)) {
|
|
832
870
|
const name = (embeddedProp.embeddedPath?.join(glue) ?? embeddedProp.fieldNames[0] + glue) + prop.name;
|
|
833
|
-
meta.properties[name] = Utils.copy(prop
|
|
871
|
+
meta.properties[name] = Utils.copy(prop);
|
|
834
872
|
meta.properties[name].name = name;
|
|
835
873
|
meta.properties[name].embedded = [embeddedProp.name, prop.name];
|
|
836
874
|
meta.propertyOrder.set(name, (order += 0.01));
|
|
837
875
|
embeddedProp.embeddedProps[prop.name] = meta.properties[name];
|
|
838
876
|
meta.properties[name].persist ??= embeddedProp.persist;
|
|
839
|
-
|
|
877
|
+
const refInArray = array && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && prop.owner;
|
|
878
|
+
if (embeddedProp.nullable || refInArray) {
|
|
840
879
|
meta.properties[name].nullable = true;
|
|
841
880
|
}
|
|
842
881
|
if (meta.properties[name].fieldNames) {
|
|
@@ -866,15 +905,17 @@ export class MetadataDiscovery {
|
|
|
866
905
|
path = [embeddedProp.fieldNames[0]];
|
|
867
906
|
}
|
|
868
907
|
this.initFieldName(prop, true);
|
|
908
|
+
this.initRelation(prop);
|
|
869
909
|
path.push(prop.fieldNames[0]);
|
|
870
910
|
meta.properties[name].fieldNames = prop.fieldNames;
|
|
871
911
|
meta.properties[name].embeddedPath = path;
|
|
872
|
-
const
|
|
912
|
+
const targetProp = prop.targetMeta?.getPrimaryProp() ?? prop;
|
|
913
|
+
const fieldName = raw(this.platform.getSearchJsonPropertySQL(path.join('->'), targetProp.runtimeType ?? targetProp.type, true));
|
|
873
914
|
meta.properties[name].fieldNameRaw = fieldName.sql; // for querying in SQL drivers
|
|
874
915
|
meta.properties[name].persist = false; // only virtual as we store the whole object
|
|
875
916
|
meta.properties[name].userDefined = false; // mark this as a generated/internal property, so we can distinguish from user-defined non-persist properties
|
|
876
917
|
meta.properties[name].object = true;
|
|
877
|
-
this.initCustomType(meta, meta.properties[name], true);
|
|
918
|
+
this.initCustomType(meta, meta.properties[name], false, true);
|
|
878
919
|
}
|
|
879
920
|
this.initEmbeddables(meta, meta.properties[name], visited);
|
|
880
921
|
}
|
|
@@ -897,7 +938,7 @@ export class MetadataDiscovery {
|
|
|
897
938
|
}
|
|
898
939
|
initSingleTableInheritance(meta, metadata) {
|
|
899
940
|
if (meta.root !== meta && !meta.__processed) {
|
|
900
|
-
meta.root = metadata.find(m => m.
|
|
941
|
+
meta.root = metadata.find(m => m.class === meta.root.class);
|
|
901
942
|
meta.root.__processed = true;
|
|
902
943
|
}
|
|
903
944
|
else {
|
|
@@ -906,17 +947,23 @@ export class MetadataDiscovery {
|
|
|
906
947
|
if (!meta.root.discriminatorColumn) {
|
|
907
948
|
return;
|
|
908
949
|
}
|
|
909
|
-
if (
|
|
950
|
+
if (meta.root.discriminatorMap) {
|
|
951
|
+
const map = meta.root.discriminatorMap;
|
|
952
|
+
Object.keys(map)
|
|
953
|
+
.filter(key => typeof map[key] === 'string')
|
|
954
|
+
.forEach(key => map[key] = this.metadata.getByClassName(map[key]).class);
|
|
955
|
+
}
|
|
956
|
+
else {
|
|
910
957
|
meta.root.discriminatorMap = {};
|
|
911
958
|
const children = metadata
|
|
912
|
-
.filter(m => m.root.
|
|
959
|
+
.filter(m => m.root.class === meta.root.class && !m.abstract)
|
|
913
960
|
.sort((a, b) => a.className.localeCompare(b.className));
|
|
914
961
|
for (const m of children) {
|
|
915
962
|
const name = m.discriminatorValue ?? this.namingStrategy.classToTableName(m.className);
|
|
916
|
-
meta.root.discriminatorMap[name] = m.
|
|
963
|
+
meta.root.discriminatorMap[name] = m.class;
|
|
917
964
|
}
|
|
918
965
|
}
|
|
919
|
-
meta.discriminatorValue = Object.entries(meta.root.discriminatorMap).find(([,
|
|
966
|
+
meta.discriminatorValue = Object.entries(meta.root.discriminatorMap).find(([, cls]) => cls === meta.class)?.[0];
|
|
920
967
|
if (!meta.root.properties[meta.root.discriminatorColumn]) {
|
|
921
968
|
this.createDiscriminatorProperty(meta.root);
|
|
922
969
|
}
|
|
@@ -925,28 +972,46 @@ export class MetadataDiscovery {
|
|
|
925
972
|
if (meta.root === meta) {
|
|
926
973
|
return;
|
|
927
974
|
}
|
|
928
|
-
let i = 1;
|
|
929
975
|
Object.values(meta.properties).forEach(prop => {
|
|
930
976
|
const newProp = { ...prop };
|
|
931
|
-
|
|
977
|
+
const rootProp = meta.root.properties[prop.name];
|
|
978
|
+
if (rootProp && (rootProp.type !== prop.type || (rootProp.fieldNames && prop.fieldNames && !compareArrays(rootProp.fieldNames, prop.fieldNames)))) {
|
|
932
979
|
const name = newProp.name;
|
|
933
980
|
this.initFieldName(newProp, newProp.object);
|
|
934
|
-
newProp.
|
|
981
|
+
newProp.renamedFrom = name;
|
|
982
|
+
newProp.name = `${name}_${meta._id}`;
|
|
935
983
|
meta.root.addProperty(newProp);
|
|
984
|
+
this.initFieldName(prop, prop.object);
|
|
985
|
+
// Track all field variants and map discriminator values to field names
|
|
986
|
+
if (!rootProp.stiFieldNames) {
|
|
987
|
+
this.initFieldName(rootProp, rootProp.object);
|
|
988
|
+
rootProp.stiFieldNames = [...rootProp.fieldNames];
|
|
989
|
+
rootProp.stiFieldNameMap = {};
|
|
990
|
+
// Find which discriminator owns the original fieldNames
|
|
991
|
+
for (const [discValue, childClass] of Object.entries(meta.root.discriminatorMap)) {
|
|
992
|
+
const childMeta = this.metadata.find(childClass);
|
|
993
|
+
if (childMeta?.properties[prop.name]?.fieldNames && compareArrays(childMeta.properties[prop.name].fieldNames, rootProp.fieldNames)) {
|
|
994
|
+
rootProp.stiFieldNameMap[discValue] = rootProp.fieldNames[0];
|
|
995
|
+
break;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
rootProp.stiFieldNameMap[meta.discriminatorValue] = prop.fieldNames[0];
|
|
1000
|
+
rootProp.stiFieldNames.push(...prop.fieldNames);
|
|
936
1001
|
newProp.nullable = true;
|
|
937
1002
|
newProp.name = name;
|
|
938
1003
|
newProp.hydrate = false;
|
|
939
1004
|
newProp.inherited = true;
|
|
940
1005
|
return;
|
|
941
1006
|
}
|
|
942
|
-
if (prop.enum && prop.items &&
|
|
943
|
-
newProp.items = Utils.unique([...
|
|
1007
|
+
if (prop.enum && prop.items && rootProp?.items) {
|
|
1008
|
+
newProp.items = Utils.unique([...rootProp.items, ...prop.items]);
|
|
944
1009
|
}
|
|
945
1010
|
newProp.nullable = true;
|
|
946
|
-
newProp.inherited =
|
|
1011
|
+
newProp.inherited = !rootProp;
|
|
947
1012
|
meta.root.addProperty(newProp);
|
|
948
1013
|
});
|
|
949
|
-
meta.
|
|
1014
|
+
meta.tableName = meta.root.tableName;
|
|
950
1015
|
meta.root.indexes = Utils.unique([...meta.root.indexes, ...meta.indexes]);
|
|
951
1016
|
meta.root.uniques = Utils.unique([...meta.root.uniques, ...meta.uniques]);
|
|
952
1017
|
meta.root.checks = Utils.unique([...meta.root.checks, ...meta.checks]);
|
|
@@ -968,7 +1033,7 @@ export class MetadataDiscovery {
|
|
|
968
1033
|
}
|
|
969
1034
|
}
|
|
970
1035
|
initCheckConstraints(meta) {
|
|
971
|
-
const map =
|
|
1036
|
+
const map = meta.createColumnMappingObject();
|
|
972
1037
|
for (const check of meta.checks) {
|
|
973
1038
|
const columns = check.property ? meta.properties[check.property].fieldNames : [];
|
|
974
1039
|
check.name ??= this.namingStrategy.indexName(meta.tableName, columns, 'check');
|
|
@@ -978,7 +1043,7 @@ export class MetadataDiscovery {
|
|
|
978
1043
|
}
|
|
979
1044
|
if (this.platform.usesEnumCheckConstraints() && !meta.embeddable) {
|
|
980
1045
|
for (const prop of meta.props) {
|
|
981
|
-
if (prop.enum && !prop.nativeEnumName && prop.items?.every(item =>
|
|
1046
|
+
if (prop.enum && !prop.nativeEnumName && prop.items?.every(item => typeof item === 'string')) {
|
|
982
1047
|
this.initFieldName(prop);
|
|
983
1048
|
meta.checks.push({
|
|
984
1049
|
name: this.namingStrategy.indexName(meta.tableName, prop.fieldNames, 'check'),
|
|
@@ -1003,38 +1068,28 @@ export class MetadataDiscovery {
|
|
|
1003
1068
|
}
|
|
1004
1069
|
return;
|
|
1005
1070
|
}
|
|
1006
|
-
const map =
|
|
1071
|
+
const map = meta.createColumnMappingObject();
|
|
1007
1072
|
if (prop.generated instanceof Function) {
|
|
1008
1073
|
prop.generated = prop.generated(map);
|
|
1009
1074
|
}
|
|
1010
1075
|
}
|
|
1011
|
-
|
|
1012
|
-
return Object.values(meta.properties).reduce((o, prop) => {
|
|
1013
|
-
if (prop.fieldNames) {
|
|
1014
|
-
o[prop.name] = prop.fieldNames[0];
|
|
1015
|
-
}
|
|
1016
|
-
return o;
|
|
1017
|
-
}, {});
|
|
1018
|
-
}
|
|
1019
|
-
getDefaultVersionValue(prop) {
|
|
1076
|
+
getDefaultVersionValue(meta, prop) {
|
|
1020
1077
|
if (typeof prop.defaultRaw !== 'undefined') {
|
|
1021
1078
|
return prop.defaultRaw;
|
|
1022
1079
|
}
|
|
1023
|
-
/* v8 ignore next
|
|
1080
|
+
/* v8 ignore next */
|
|
1024
1081
|
if (prop.default != null) {
|
|
1025
1082
|
return '' + this.platform.quoteVersionValue(prop.default, prop);
|
|
1026
1083
|
}
|
|
1027
|
-
|
|
1084
|
+
this.initCustomType(meta, prop, true);
|
|
1085
|
+
const type = prop.customType?.runtimeType ?? prop.runtimeType ?? prop.type;
|
|
1086
|
+
if (type === 'Date') {
|
|
1028
1087
|
prop.length ??= this.platform.getDefaultVersionLength();
|
|
1029
1088
|
return this.platform.getCurrentTimestampSQL(prop.length);
|
|
1030
1089
|
}
|
|
1031
1090
|
return '1';
|
|
1032
1091
|
}
|
|
1033
1092
|
inferDefaultValue(meta, prop) {
|
|
1034
|
-
/* v8 ignore next 3 */
|
|
1035
|
-
if (!meta.class) {
|
|
1036
|
-
return;
|
|
1037
|
-
}
|
|
1038
1093
|
try {
|
|
1039
1094
|
// try to create two entity instances to detect the value is stable
|
|
1040
1095
|
const now = Date.now();
|
|
@@ -1062,12 +1117,12 @@ export class MetadataDiscovery {
|
|
|
1062
1117
|
return;
|
|
1063
1118
|
}
|
|
1064
1119
|
let val = prop.default;
|
|
1065
|
-
const raw =
|
|
1120
|
+
const raw = Raw.getKnownFragment(val);
|
|
1066
1121
|
if (raw) {
|
|
1067
1122
|
prop.defaultRaw = this.platform.formatQuery(raw.sql, raw.params);
|
|
1068
1123
|
return;
|
|
1069
1124
|
}
|
|
1070
|
-
if (
|
|
1125
|
+
if (Array.isArray(prop.default) && prop.customType) {
|
|
1071
1126
|
val = prop.customType.convertToDatabaseValue(prop.default, this.platform);
|
|
1072
1127
|
}
|
|
1073
1128
|
prop.defaultRaw = typeof val === 'string' ? `'${val}'` : '' + val;
|
|
@@ -1095,13 +1150,13 @@ export class MetadataDiscovery {
|
|
|
1095
1150
|
if (prop.version) {
|
|
1096
1151
|
this.initDefaultValue(prop);
|
|
1097
1152
|
meta.versionProperty = prop.name;
|
|
1098
|
-
prop.defaultRaw = this.getDefaultVersionValue(prop);
|
|
1153
|
+
prop.defaultRaw = this.getDefaultVersionValue(meta, prop);
|
|
1099
1154
|
}
|
|
1100
1155
|
if (prop.concurrencyCheck && !prop.primary) {
|
|
1101
1156
|
meta.concurrencyCheckKeys.add(prop.name);
|
|
1102
1157
|
}
|
|
1103
1158
|
}
|
|
1104
|
-
initCustomType(meta, prop, objectEmbeddable = false) {
|
|
1159
|
+
initCustomType(meta, prop, simple = false, objectEmbeddable = false) {
|
|
1105
1160
|
// `prop.type` might be actually instance of custom type class
|
|
1106
1161
|
if (Type.isMappedType(prop.type) && !prop.customType) {
|
|
1107
1162
|
prop.customType = prop.type;
|
|
@@ -1109,45 +1164,61 @@ export class MetadataDiscovery {
|
|
|
1109
1164
|
}
|
|
1110
1165
|
// `prop.type` might also be custom type class (not instance), so `typeof MyType` will give us `function`, not `object`
|
|
1111
1166
|
if (typeof prop.type === 'function' && Type.isMappedType(prop.type.prototype) && !prop.customType) {
|
|
1112
|
-
|
|
1113
|
-
|
|
1167
|
+
// if the type is an ORM defined mapped type without `ensureComparable: true`,
|
|
1168
|
+
// we use just the type name, to have more performant hydration code
|
|
1169
|
+
const type = Utils.keys(t).find(type => {
|
|
1170
|
+
return !Type.getType(t[type]).ensureComparable(meta, prop) && prop.type === t[type];
|
|
1171
|
+
});
|
|
1172
|
+
if (type) {
|
|
1173
|
+
prop.type = type === 'datetime' ? 'Date' : type;
|
|
1174
|
+
}
|
|
1175
|
+
else {
|
|
1176
|
+
prop.customType = new prop.type();
|
|
1177
|
+
prop.type = prop.customType.constructor.name;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
if (simple) {
|
|
1181
|
+
return;
|
|
1114
1182
|
}
|
|
1115
1183
|
if (!prop.customType && ['json', 'jsonb'].includes(prop.type?.toLowerCase())) {
|
|
1116
|
-
prop.customType = new
|
|
1184
|
+
prop.customType = new t.json();
|
|
1117
1185
|
}
|
|
1118
1186
|
if (prop.kind === ReferenceKind.SCALAR && !prop.customType && prop.columnTypes && ['json', 'jsonb'].includes(prop.columnTypes[0])) {
|
|
1119
|
-
prop.customType = new
|
|
1187
|
+
prop.customType = new t.json();
|
|
1188
|
+
}
|
|
1189
|
+
if (prop.kind === ReferenceKind.EMBEDDED && !prop.customType && (prop.object || prop.array)) {
|
|
1190
|
+
prop.customType = new t.json();
|
|
1120
1191
|
}
|
|
1121
1192
|
if (!prop.customType && prop.array && prop.items) {
|
|
1122
|
-
prop.customType = new
|
|
1193
|
+
prop.customType = new t.enumArray(`${meta.className}.${prop.name}`, prop.items);
|
|
1123
1194
|
}
|
|
1124
1195
|
const isArray = prop.type?.toLowerCase() === 'array' || prop.type?.toString().endsWith('[]');
|
|
1125
1196
|
if (objectEmbeddable && !prop.customType && isArray) {
|
|
1126
|
-
prop.customType = new
|
|
1197
|
+
prop.customType = new t.json();
|
|
1127
1198
|
}
|
|
1128
1199
|
// for number arrays we make sure to convert the items to numbers
|
|
1129
1200
|
if (!prop.customType && prop.type === 'number[]') {
|
|
1130
|
-
prop.customType = new
|
|
1201
|
+
prop.customType = new t.array(i => +i);
|
|
1131
1202
|
}
|
|
1132
1203
|
// `string[]` can be returned via ts-morph, while reflect metadata will give us just `array`
|
|
1133
1204
|
if (!prop.customType && isArray) {
|
|
1134
|
-
prop.customType = new
|
|
1205
|
+
prop.customType = new t.array();
|
|
1135
1206
|
}
|
|
1136
1207
|
if (!prop.customType && prop.type?.toLowerCase() === 'buffer') {
|
|
1137
|
-
prop.customType = new
|
|
1208
|
+
prop.customType = new t.blob();
|
|
1138
1209
|
}
|
|
1139
1210
|
if (!prop.customType && prop.type?.toLowerCase() === 'uint8array') {
|
|
1140
|
-
prop.customType = new
|
|
1211
|
+
prop.customType = new t.uint8array();
|
|
1141
1212
|
}
|
|
1142
1213
|
const mappedType = this.getMappedType(prop);
|
|
1143
1214
|
if (prop.fieldNames?.length === 1 && !prop.customType) {
|
|
1144
|
-
[
|
|
1215
|
+
[t.bigint, t.double, t.decimal, t.interval, t.date]
|
|
1145
1216
|
.filter(type => mappedType instanceof type)
|
|
1146
|
-
.forEach(type => prop.customType = new type());
|
|
1217
|
+
.forEach((type) => prop.customType = new type());
|
|
1147
1218
|
}
|
|
1148
1219
|
if (prop.customType && !prop.columnTypes) {
|
|
1149
1220
|
const mappedType = this.getMappedType({ columnTypes: [prop.customType.getColumnType(prop, this.platform)] });
|
|
1150
|
-
if (prop.customType.compareAsType() === 'any' && ![
|
|
1221
|
+
if (prop.customType.compareAsType() === 'any' && ![t.json].some(t => prop.customType instanceof t)) {
|
|
1151
1222
|
prop.runtimeType ??= mappedType.runtimeType;
|
|
1152
1223
|
}
|
|
1153
1224
|
else {
|
|
@@ -1167,16 +1238,16 @@ export class MetadataDiscovery {
|
|
|
1167
1238
|
prop.columnTypes ??= [prop.customType.getColumnType(prop, this.platform)];
|
|
1168
1239
|
prop.hasConvertToJSValueSQL = !!prop.customType.convertToJSValueSQL && prop.customType.convertToJSValueSQL('', this.platform) !== '';
|
|
1169
1240
|
prop.hasConvertToDatabaseValueSQL = !!prop.customType.convertToDatabaseValueSQL && prop.customType.convertToDatabaseValueSQL('', this.platform) !== '';
|
|
1170
|
-
if (prop.customType instanceof
|
|
1241
|
+
if (prop.customType instanceof t.bigint && ['string', 'bigint', 'number'].includes(prop.runtimeType.toLowerCase())) {
|
|
1171
1242
|
prop.customType.mode = prop.runtimeType.toLowerCase();
|
|
1172
1243
|
}
|
|
1173
1244
|
}
|
|
1174
1245
|
if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !isArray) {
|
|
1175
1246
|
prop.type = prop.customType.name;
|
|
1176
1247
|
}
|
|
1177
|
-
if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) &&
|
|
1248
|
+
if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && prop.targetMeta.compositePK) {
|
|
1178
1249
|
prop.customTypes = [];
|
|
1179
|
-
for (const pk of
|
|
1250
|
+
for (const pk of prop.targetMeta.getPrimaryProps()) {
|
|
1180
1251
|
if (pk.customType) {
|
|
1181
1252
|
prop.customTypes.push(pk.customType);
|
|
1182
1253
|
prop.hasConvertToJSValueSQL ||= !!pk.customType.convertToJSValueSQL && pk.customType.convertToJSValueSQL('', this.platform) !== '';
|
|
@@ -1188,7 +1259,7 @@ export class MetadataDiscovery {
|
|
|
1188
1259
|
}
|
|
1189
1260
|
}
|
|
1190
1261
|
}
|
|
1191
|
-
if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof
|
|
1262
|
+
if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof t.unknown)) {
|
|
1192
1263
|
if (!prop.columnTypes && prop.nativeEnumName && meta.schema !== this.platform.getDefaultSchemaName() && meta.schema && !prop.nativeEnumName.includes('.')) {
|
|
1193
1264
|
prop.columnTypes = [`${meta.schema}.${prop.nativeEnumName}`];
|
|
1194
1265
|
}
|
|
@@ -1206,19 +1277,28 @@ export class MetadataDiscovery {
|
|
|
1206
1277
|
if (prop.kind === ReferenceKind.SCALAR) {
|
|
1207
1278
|
return;
|
|
1208
1279
|
}
|
|
1209
|
-
|
|
1210
|
-
prop.
|
|
1280
|
+
// 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
|
|
1281
|
+
const meta2 = this.metadata.find(prop.target) ?? this.metadata.getByClassName(prop.type);
|
|
1282
|
+
// If targetKey is specified, use that property instead of PKs for referencedPKs
|
|
1283
|
+
prop.referencedPKs = prop.targetKey ? [prop.targetKey] : meta2.primaryKeys;
|
|
1211
1284
|
prop.targetMeta = meta2;
|
|
1285
|
+
if (meta2.view) {
|
|
1286
|
+
prop.createForeignKeyConstraint = false;
|
|
1287
|
+
}
|
|
1212
1288
|
if (!prop.formula && prop.persist === false && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.embedded) {
|
|
1213
|
-
prop.formula =
|
|
1289
|
+
prop.formula = table => `${table}.${this.platform.quoteIdentifier(prop.fieldNames[0])}`;
|
|
1214
1290
|
}
|
|
1215
1291
|
}
|
|
1216
1292
|
initColumnType(prop) {
|
|
1217
1293
|
this.initUnsigned(prop);
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
prop.
|
|
1221
|
-
|
|
1294
|
+
// Get the target properties for FK relations - use targetKey property if specified, otherwise PKs
|
|
1295
|
+
const targetProps = prop.targetMeta
|
|
1296
|
+
? (prop.targetKey ? [prop.targetMeta.properties[prop.targetKey]] : prop.targetMeta.getPrimaryProps())
|
|
1297
|
+
: [];
|
|
1298
|
+
targetProps.map(targetProp => {
|
|
1299
|
+
prop.length ??= targetProp.length;
|
|
1300
|
+
prop.precision ??= targetProp.precision;
|
|
1301
|
+
prop.scale ??= targetProp.scale;
|
|
1222
1302
|
});
|
|
1223
1303
|
if (prop.kind === ReferenceKind.SCALAR && (prop.type == null || prop.type === 'object') && prop.columnTypes?.[0]) {
|
|
1224
1304
|
delete prop.type;
|
|
@@ -1231,8 +1311,7 @@ export class MetadataDiscovery {
|
|
|
1231
1311
|
if (prop.kind === ReferenceKind.SCALAR) {
|
|
1232
1312
|
const mappedType = this.getMappedType(prop);
|
|
1233
1313
|
const SCALAR_TYPES = ['string', 'number', 'boolean', 'bigint', 'Date', 'Buffer', 'RegExp', 'any', 'unknown'];
|
|
1234
|
-
if (mappedType instanceof
|
|
1235
|
-
&& !prop.columnTypes
|
|
1314
|
+
if (mappedType instanceof t.unknown
|
|
1236
1315
|
// it could be a runtime type from reflect-metadata
|
|
1237
1316
|
&& !SCALAR_TYPES.includes(prop.type)
|
|
1238
1317
|
// or it might be inferred via ts-morph to some generic type alias
|
|
@@ -1245,23 +1324,28 @@ export class MetadataDiscovery {
|
|
|
1245
1324
|
}
|
|
1246
1325
|
return;
|
|
1247
1326
|
}
|
|
1248
|
-
|
|
1327
|
+
/* v8 ignore next */
|
|
1328
|
+
if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
|
|
1249
1329
|
prop.columnTypes = [this.platform.getJsonDeclarationSQL()];
|
|
1250
1330
|
return;
|
|
1251
1331
|
}
|
|
1252
|
-
const targetMeta =
|
|
1332
|
+
const targetMeta = prop.targetMeta;
|
|
1253
1333
|
prop.columnTypes = [];
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1334
|
+
// Use targetKey property if specified, otherwise use primary key properties
|
|
1335
|
+
const referencedProps = prop.targetKey
|
|
1336
|
+
? [targetMeta.properties[prop.targetKey]]
|
|
1337
|
+
: targetMeta.getPrimaryProps();
|
|
1338
|
+
for (const referencedProp of referencedProps) {
|
|
1339
|
+
this.initCustomType(targetMeta, referencedProp);
|
|
1340
|
+
this.initColumnType(referencedProp);
|
|
1341
|
+
const mappedType = this.getMappedType(referencedProp);
|
|
1342
|
+
let columnTypes = referencedProp.columnTypes;
|
|
1343
|
+
if (referencedProp.autoincrement) {
|
|
1344
|
+
columnTypes = [mappedType.getColumnType({ ...referencedProp, autoincrement: false }, this.platform)];
|
|
1261
1345
|
}
|
|
1262
1346
|
prop.columnTypes.push(...columnTypes);
|
|
1263
|
-
if (!targetMeta.compositePK) {
|
|
1264
|
-
prop.customType =
|
|
1347
|
+
if (!targetMeta.compositePK || prop.targetKey) {
|
|
1348
|
+
prop.customType = referencedProp.customType;
|
|
1265
1349
|
}
|
|
1266
1350
|
}
|
|
1267
1351
|
}
|
|
@@ -1275,7 +1359,7 @@ export class MetadataDiscovery {
|
|
|
1275
1359
|
t = 'enum';
|
|
1276
1360
|
}
|
|
1277
1361
|
else if (prop.enum) {
|
|
1278
|
-
t = prop.items?.every(item =>
|
|
1362
|
+
t = prop.items?.every(item => typeof item === 'string') ? 'enum' : 'tinyint';
|
|
1279
1363
|
}
|
|
1280
1364
|
if (t === 'Date') {
|
|
1281
1365
|
t = 'datetime';
|
|
@@ -1299,7 +1383,7 @@ export class MetadataDiscovery {
|
|
|
1299
1383
|
return;
|
|
1300
1384
|
}
|
|
1301
1385
|
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
1302
|
-
const meta2 =
|
|
1386
|
+
const meta2 = prop.targetMeta;
|
|
1303
1387
|
prop.unsigned = meta2.getPrimaryProps().some(pk => {
|
|
1304
1388
|
this.initUnsigned(pk);
|
|
1305
1389
|
return pk.unsigned;
|
|
@@ -1314,30 +1398,6 @@ export class MetadataDiscovery {
|
|
|
1314
1398
|
prop.index ??= true;
|
|
1315
1399
|
}
|
|
1316
1400
|
}
|
|
1317
|
-
async getEntityClassOrSchema(path, name) {
|
|
1318
|
-
const exports = await Utils.dynamicImport(path);
|
|
1319
|
-
const targets = Object.values(exports)
|
|
1320
|
-
.filter(item => item instanceof EntitySchema || (item instanceof Function && MetadataStorage.isKnownEntity(item.name)));
|
|
1321
|
-
// ignore class implementations that are linked from an EntitySchema
|
|
1322
|
-
for (const item of targets) {
|
|
1323
|
-
if (item instanceof EntitySchema) {
|
|
1324
|
-
targets.forEach((item2, idx) => {
|
|
1325
|
-
if (item.meta.class === item2) {
|
|
1326
|
-
targets.splice(idx, 1);
|
|
1327
|
-
}
|
|
1328
|
-
});
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
if (targets.length > 0) {
|
|
1332
|
-
return targets;
|
|
1333
|
-
}
|
|
1334
|
-
const target = exports.default ?? exports[name];
|
|
1335
|
-
/* v8 ignore next 3 */
|
|
1336
|
-
if (!target) {
|
|
1337
|
-
throw MetadataError.entityNotFound(name, path.replace(this.config.get('baseDir'), '.'));
|
|
1338
|
-
}
|
|
1339
|
-
return [target];
|
|
1340
|
-
}
|
|
1341
1401
|
shouldForceConstructorUsage(meta) {
|
|
1342
1402
|
const forceConstructor = this.config.get('forceEntityConstructor');
|
|
1343
1403
|
if (Array.isArray(forceConstructor)) {
|