@mikro-orm/core 7.0.0-dev.2 → 7.0.0-dev.200
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 +111 -61
- package/EntityManager.js +346 -300
- 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 +47 -17
- package/entity/BaseEntity.d.ts +2 -2
- package/entity/BaseEntity.js +0 -3
- package/entity/Collection.d.ts +95 -31
- package/entity/Collection.js +444 -102
- package/entity/EntityAssigner.d.ts +1 -1
- package/entity/EntityAssigner.js +26 -18
- package/entity/EntityFactory.d.ts +13 -1
- package/entity/EntityFactory.js +88 -54
- package/entity/EntityHelper.d.ts +2 -2
- package/entity/EntityHelper.js +38 -15
- package/entity/EntityLoader.d.ts +8 -7
- package/entity/EntityLoader.js +134 -80
- package/entity/EntityRepository.d.ts +24 -4
- package/entity/EntityRepository.js +8 -2
- package/entity/Reference.d.ts +9 -12
- package/entity/Reference.js +34 -9
- package/entity/WrappedEntity.d.ts +2 -7
- package/entity/WrappedEntity.js +3 -8
- package/entity/defineEntity.d.ts +585 -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 +22 -6
- 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/events/EventSubscriber.d.ts +3 -1
- package/hydration/Hydrator.js +1 -2
- package/hydration/ObjectHydrator.d.ts +4 -4
- package/hydration/ObjectHydrator.js +53 -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 +26 -26
- package/metadata/EntitySchema.js +82 -51
- package/metadata/MetadataDiscovery.d.ts +7 -10
- package/metadata/MetadataDiscovery.js +408 -335
- 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 +100 -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 +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 +19 -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 +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 +300 -140
- package/typings.js +62 -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 +77 -35
- 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 +199 -106
- package/utils/AbstractSchemaGenerator.d.ts +5 -5
- package/utils/AbstractSchemaGenerator.js +22 -17
- package/utils/AsyncContext.d.ts +6 -0
- package/utils/AsyncContext.js +42 -0
- package/utils/Configuration.d.ts +779 -207
- package/utils/Configuration.js +146 -190
- 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 +13 -9
- package/utils/EntityComparator.js +164 -89
- package/utils/QueryHelper.d.ts +14 -6
- package/utils/QueryHelper.js +88 -26
- package/utils/RawQueryFragment.d.ts +48 -25
- package/utils/RawQueryFragment.js +67 -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 +13 -120
- package/utils/Utils.js +104 -375
- 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 -29
- 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,21 @@
|
|
|
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';
|
|
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,122 +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 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;
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
MetadataStorage.getMetadata(entity.meta.className, filepath);
|
|
266
|
-
}
|
|
267
|
-
return entity;
|
|
275
|
+
const meta = Utils.copy(entity.meta, false);
|
|
276
|
+
return EntitySchema.fromMetadata(meta);
|
|
268
277
|
}
|
|
269
278
|
const path = entity[MetadataStorage.PATH_SYMBOL];
|
|
270
279
|
if (path) {
|
|
271
280
|
const meta = Utils.copy(MetadataStorage.getMetadata(entity.name, path), false);
|
|
272
|
-
meta.path =
|
|
273
|
-
this.metadata.set(entity
|
|
281
|
+
meta.path = path;
|
|
282
|
+
this.metadata.set(entity, meta);
|
|
274
283
|
}
|
|
275
|
-
const exists = this.metadata.has(entity
|
|
276
|
-
const meta = this.metadata.get(entity
|
|
284
|
+
const exists = this.metadata.has(entity);
|
|
285
|
+
const meta = this.metadata.get(entity, true);
|
|
277
286
|
meta.abstract ??= !(exists && meta.name);
|
|
278
287
|
const schema = EntitySchema.fromMetadata(meta);
|
|
279
288
|
schema.setClass(entity);
|
|
280
289
|
return schema;
|
|
281
290
|
}
|
|
282
|
-
|
|
283
|
-
|
|
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) {
|
|
284
303
|
const meta = schema.meta;
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
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);
|
|
288
309
|
if (cache) {
|
|
289
310
|
this.logger.log('discovery', `- using cached metadata for entity ${colors.cyan(meta.className)}`);
|
|
290
|
-
this.metadataProvider.loadFromCache(meta, cache);
|
|
291
|
-
meta.root = root;
|
|
292
311
|
this.discovered.push(meta);
|
|
293
312
|
return;
|
|
294
313
|
}
|
|
@@ -297,50 +316,18 @@ export class MetadataDiscovery {
|
|
|
297
316
|
this.inferDefaultValue(meta, prop);
|
|
298
317
|
}
|
|
299
318
|
// 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.
|
|
319
|
+
this.metadataProvider.loadEntityMetadata(meta);
|
|
320
|
+
if (!meta.tableName && meta.name) {
|
|
302
321
|
const entityName = root.discriminatorColumn ? root.name : meta.name;
|
|
303
|
-
meta.
|
|
322
|
+
meta.tableName = this.namingStrategy.classToTableName(entityName);
|
|
304
323
|
}
|
|
305
|
-
|
|
306
|
-
this.saveToCache(meta);
|
|
324
|
+
this.metadataProvider.saveToCache(meta);
|
|
307
325
|
meta.root = root;
|
|
308
326
|
this.discovered.push(meta);
|
|
309
327
|
}
|
|
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
328
|
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
329
|
if (prop.kind === ReferenceKind.ONE_TO_ONE) {
|
|
343
|
-
return Utils.defaultValue(prop, 'nullable', prop.optional || !prop.owner
|
|
330
|
+
return Utils.defaultValue(prop, 'nullable', prop.optional || !prop.owner);
|
|
344
331
|
}
|
|
345
332
|
return Utils.defaultValue(prop, 'nullable', prop.optional);
|
|
346
333
|
}
|
|
@@ -375,7 +362,7 @@ export class MetadataDiscovery {
|
|
|
375
362
|
if (prop.joinColumns.length !== prop.columnTypes.length) {
|
|
376
363
|
prop.columnTypes = prop.joinColumns.flatMap(field => {
|
|
377
364
|
const matched = meta.props.find(p => p.fieldNames?.includes(field));
|
|
378
|
-
/* v8 ignore next
|
|
365
|
+
/* v8 ignore next */
|
|
379
366
|
if (!matched) {
|
|
380
367
|
throw MetadataError.fromWrongForeignKey(meta, prop, 'columnTypes');
|
|
381
368
|
}
|
|
@@ -401,23 +388,23 @@ export class MetadataDiscovery {
|
|
|
401
388
|
prop.fieldNames = this.initManyToManyFieldName(prop, prop.name);
|
|
402
389
|
}
|
|
403
390
|
}
|
|
404
|
-
initManyToOneFieldName(prop, name) {
|
|
405
|
-
const meta2 =
|
|
391
|
+
initManyToOneFieldName(prop, name, tableName) {
|
|
392
|
+
const meta2 = prop.targetMeta;
|
|
406
393
|
const ret = [];
|
|
407
394
|
for (const primaryKey of meta2.primaryKeys) {
|
|
408
395
|
this.initFieldName(meta2.properties[primaryKey]);
|
|
409
396
|
for (const fieldName of meta2.properties[primaryKey].fieldNames) {
|
|
410
|
-
ret.push(this.namingStrategy.joinKeyColumnName(name, fieldName, meta2.compositePK));
|
|
397
|
+
ret.push(this.namingStrategy.joinKeyColumnName(name, fieldName, meta2.compositePK, tableName));
|
|
411
398
|
}
|
|
412
399
|
}
|
|
413
400
|
return ret;
|
|
414
401
|
}
|
|
415
402
|
initManyToManyFieldName(prop, name) {
|
|
416
|
-
const meta2 =
|
|
403
|
+
const meta2 = prop.targetMeta;
|
|
417
404
|
return meta2.primaryKeys.map(() => this.namingStrategy.propertyToColumnName(name));
|
|
418
405
|
}
|
|
419
406
|
initManyToManyFields(meta, prop) {
|
|
420
|
-
const meta2 =
|
|
407
|
+
const meta2 = prop.targetMeta;
|
|
421
408
|
Utils.defaultValue(prop, 'fixedOrder', !!prop.fixedOrderColumn);
|
|
422
409
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
423
410
|
const props = Object.values(pivotMeta?.properties ?? {});
|
|
@@ -438,35 +425,58 @@ export class MetadataDiscovery {
|
|
|
438
425
|
prop.inverseJoinColumns ??= second.fieldNames;
|
|
439
426
|
}
|
|
440
427
|
if (!prop.pivotTable && prop.owner && this.platform.usesPivotTable()) {
|
|
441
|
-
prop.pivotTable = this.namingStrategy.joinTableName(meta.
|
|
428
|
+
prop.pivotTable = this.namingStrategy.joinTableName(meta.className, meta2.tableName, prop.name, meta.tableName);
|
|
442
429
|
}
|
|
443
430
|
if (prop.mappedBy) {
|
|
444
431
|
const prop2 = meta2.properties[prop.mappedBy];
|
|
445
432
|
this.initManyToManyFields(meta2, prop2);
|
|
446
433
|
prop.pivotTable = prop2.pivotTable;
|
|
447
|
-
prop.pivotEntity = prop2.pivotEntity
|
|
434
|
+
prop.pivotEntity = prop2.pivotEntity;
|
|
448
435
|
prop.fixedOrder = prop2.fixedOrder;
|
|
449
436
|
prop.fixedOrderColumn = prop2.fixedOrderColumn;
|
|
450
437
|
prop.joinColumns = prop2.inverseJoinColumns;
|
|
451
438
|
prop.inverseJoinColumns = prop2.joinColumns;
|
|
452
439
|
}
|
|
453
440
|
prop.referencedColumnNames ??= Utils.flatten(meta.primaryKeys.map(primaryKey => meta.properties[primaryKey].fieldNames));
|
|
454
|
-
|
|
455
|
-
|
|
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);
|
|
456
448
|
}
|
|
457
449
|
initManyToOneFields(prop) {
|
|
458
|
-
const meta2 =
|
|
459
|
-
|
|
460
|
-
|
|
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);
|
|
461
461
|
if (!prop.joinColumns) {
|
|
462
462
|
prop.joinColumns = fieldNames.map(fieldName => this.namingStrategy.joinKeyColumnName(prop.name, fieldName, fieldNames.length > 1));
|
|
463
463
|
}
|
|
464
464
|
if (!prop.referencedColumnNames) {
|
|
465
465
|
prop.referencedColumnNames = fieldNames;
|
|
466
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
|
+
}
|
|
467
477
|
}
|
|
468
478
|
initOneToManyFields(prop) {
|
|
469
|
-
const meta2 =
|
|
479
|
+
const meta2 = prop.targetMeta;
|
|
470
480
|
if (!prop.joinColumns) {
|
|
471
481
|
prop.joinColumns = [this.namingStrategy.joinColumnName(prop.name)];
|
|
472
482
|
}
|
|
@@ -479,12 +489,17 @@ export class MetadataDiscovery {
|
|
|
479
489
|
const pks = Object.values(meta.properties).filter(prop => prop.primary);
|
|
480
490
|
meta.primaryKeys = pks.map(prop => prop.name);
|
|
481
491
|
meta.compositePK = pks.length > 1;
|
|
482
|
-
// FK used as PK, we need to cascade
|
|
483
|
-
|
|
484
|
-
|
|
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
|
+
}
|
|
485
500
|
}
|
|
486
501
|
meta.forceConstructor ??= this.shouldForceConstructorUsage(meta);
|
|
487
|
-
this.validator.validateEntityDefinition(this.metadata, meta.
|
|
502
|
+
this.validator.validateEntityDefinition(this.metadata, meta.class, this.config.get('discovery'));
|
|
488
503
|
for (const prop of Object.values(meta.properties)) {
|
|
489
504
|
this.initNullability(prop);
|
|
490
505
|
this.applyNamingStrategy(meta, prop);
|
|
@@ -497,15 +512,21 @@ export class MetadataDiscovery {
|
|
|
497
512
|
}
|
|
498
513
|
this.initOwnColumns(meta);
|
|
499
514
|
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;
|
|
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;
|
|
504
518
|
}
|
|
505
519
|
if (this.platform.usesPivotTable()) {
|
|
506
520
|
return Object.values(meta.properties)
|
|
507
521
|
.filter(prop => prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && prop.pivotTable)
|
|
508
|
-
.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
|
+
});
|
|
509
530
|
}
|
|
510
531
|
return [];
|
|
511
532
|
}
|
|
@@ -522,8 +543,11 @@ export class MetadataDiscovery {
|
|
|
522
543
|
['mappedBy', 'inversedBy', 'pivotEntity'].forEach(type => {
|
|
523
544
|
const value = prop[type];
|
|
524
545
|
if (value instanceof Function) {
|
|
525
|
-
const meta2 = this.metadata.get(prop.
|
|
546
|
+
const meta2 = prop.targetMeta ?? this.metadata.get(prop.target);
|
|
526
547
|
prop[type] = value(meta2.properties)?.name;
|
|
548
|
+
if (type === 'pivotEntity' && value) {
|
|
549
|
+
prop[type] = value(meta2.properties);
|
|
550
|
+
}
|
|
527
551
|
if (prop[type] == null) {
|
|
528
552
|
throw MetadataError.fromWrongReference(meta, prop, type);
|
|
529
553
|
}
|
|
@@ -539,9 +563,9 @@ export class MetadataDiscovery {
|
|
|
539
563
|
}
|
|
540
564
|
else if (fks.length >= 2) {
|
|
541
565
|
[first, second] = fks;
|
|
542
|
-
/* v8 ignore next 3 */
|
|
543
566
|
}
|
|
544
567
|
else {
|
|
568
|
+
/* v8 ignore next */
|
|
545
569
|
return [];
|
|
546
570
|
}
|
|
547
571
|
// wrong FK order, first FK needs to point to the owning side
|
|
@@ -555,7 +579,9 @@ export class MetadataDiscovery {
|
|
|
555
579
|
return [first, second];
|
|
556
580
|
}
|
|
557
581
|
definePivotTableEntity(meta, prop) {
|
|
558
|
-
const pivotMeta =
|
|
582
|
+
const pivotMeta = prop.pivotEntity
|
|
583
|
+
? this.metadata.find(prop.pivotEntity)
|
|
584
|
+
: this.metadata.getByClassName(prop.pivotTable, false);
|
|
559
585
|
// ensure inverse side exists so we can join it when populating via pivot tables
|
|
560
586
|
if (!prop.inversedBy && prop.targetMeta) {
|
|
561
587
|
const inverseName = `${meta.className}_${prop.name}__inverse`;
|
|
@@ -564,6 +590,8 @@ export class MetadataDiscovery {
|
|
|
564
590
|
name: inverseName,
|
|
565
591
|
kind: ReferenceKind.MANY_TO_MANY,
|
|
566
592
|
type: meta.className,
|
|
593
|
+
target: meta.class,
|
|
594
|
+
targetMeta: meta,
|
|
567
595
|
mappedBy: prop.name,
|
|
568
596
|
pivotEntity: prop.pivotEntity,
|
|
569
597
|
pivotTable: prop.pivotTable,
|
|
@@ -572,55 +600,61 @@ export class MetadataDiscovery {
|
|
|
572
600
|
};
|
|
573
601
|
this.applyNamingStrategy(prop.targetMeta, inverseProp);
|
|
574
602
|
this.initCustomType(prop.targetMeta, inverseProp);
|
|
575
|
-
this.initRelation(inverseProp);
|
|
576
603
|
prop.targetMeta.properties[inverseName] = inverseProp;
|
|
577
604
|
}
|
|
578
605
|
if (pivotMeta) {
|
|
606
|
+
prop.pivotEntity = pivotMeta.class;
|
|
579
607
|
this.ensureCorrectFKOrderInPivotEntity(pivotMeta, prop);
|
|
580
608
|
return pivotMeta;
|
|
581
609
|
}
|
|
582
|
-
const exists = this.metadata.find(prop.pivotTable);
|
|
583
|
-
if (exists) {
|
|
584
|
-
prop.pivotEntity = exists.className;
|
|
585
|
-
return exists;
|
|
586
|
-
}
|
|
587
610
|
let tableName = prop.pivotTable;
|
|
588
611
|
let schemaName;
|
|
589
612
|
if (prop.pivotTable.includes('.')) {
|
|
590
613
|
[schemaName, tableName] = prop.pivotTable.split('.');
|
|
591
614
|
}
|
|
592
615
|
schemaName ??= meta.schema;
|
|
593
|
-
const
|
|
594
|
-
const
|
|
616
|
+
const targetMeta = prop.targetMeta;
|
|
617
|
+
const targetType = targetMeta.className;
|
|
618
|
+
const pivotMeta2 = new EntityMetadata({
|
|
595
619
|
name: prop.pivotTable,
|
|
596
620
|
className: prop.pivotTable,
|
|
597
621
|
collection: tableName,
|
|
598
622
|
schema: schemaName,
|
|
599
623
|
pivotTable: true,
|
|
600
624
|
});
|
|
601
|
-
prop.pivotEntity =
|
|
625
|
+
prop.pivotEntity = pivotMeta2.class;
|
|
602
626
|
if (prop.fixedOrder) {
|
|
603
|
-
const primaryProp = this.defineFixedOrderProperty(prop,
|
|
604
|
-
|
|
627
|
+
const primaryProp = this.defineFixedOrderProperty(prop, targetMeta);
|
|
628
|
+
pivotMeta2.properties[primaryProp.name] = primaryProp;
|
|
605
629
|
}
|
|
606
630
|
else {
|
|
607
|
-
|
|
631
|
+
pivotMeta2.compositePK = true;
|
|
608
632
|
}
|
|
609
633
|
// handle self-referenced m:n with same default field names
|
|
610
634
|
if (meta.className === targetType && prop.joinColumns.every((joinColumn, idx) => joinColumn === prop.inverseJoinColumns[idx])) {
|
|
611
|
-
|
|
612
|
-
|
|
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));
|
|
613
639
|
if (prop.inversedBy) {
|
|
614
|
-
const prop2 =
|
|
640
|
+
const prop2 = targetMeta.properties[prop.inversedBy];
|
|
615
641
|
prop2.inverseJoinColumns = prop.joinColumns;
|
|
616
642
|
prop2.joinColumns = prop.inverseJoinColumns;
|
|
617
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
|
+
}
|
|
618
652
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
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);
|
|
622
656
|
}
|
|
623
|
-
defineFixedOrderProperty(prop,
|
|
657
|
+
defineFixedOrderProperty(prop, targetMeta) {
|
|
624
658
|
const pk = prop.fixedOrderColumn || this.namingStrategy.referenceColumnName();
|
|
625
659
|
const primaryProp = {
|
|
626
660
|
name: pk,
|
|
@@ -634,7 +668,7 @@ export class MetadataDiscovery {
|
|
|
634
668
|
this.initColumnType(primaryProp);
|
|
635
669
|
prop.fixedOrderColumn = pk;
|
|
636
670
|
if (prop.inversedBy) {
|
|
637
|
-
const prop2 =
|
|
671
|
+
const prop2 = targetMeta.properties[prop.inversedBy];
|
|
638
672
|
prop2.fixedOrder = true;
|
|
639
673
|
prop2.fixedOrderColumn = pk;
|
|
640
674
|
}
|
|
@@ -643,7 +677,8 @@ export class MetadataDiscovery {
|
|
|
643
677
|
definePivotProperty(prop, name, type, inverse, owner, selfReferencing) {
|
|
644
678
|
const ret = {
|
|
645
679
|
name,
|
|
646
|
-
type,
|
|
680
|
+
type: Utils.className(type),
|
|
681
|
+
target: type,
|
|
647
682
|
kind: ReferenceKind.MANY_TO_ONE,
|
|
648
683
|
cascade: [Cascade.ALL],
|
|
649
684
|
fixedOrder: prop.fixedOrder,
|
|
@@ -653,11 +688,11 @@ export class MetadataDiscovery {
|
|
|
653
688
|
autoincrement: false,
|
|
654
689
|
updateRule: prop.updateRule,
|
|
655
690
|
deleteRule: prop.deleteRule,
|
|
691
|
+
createForeignKeyConstraint: prop.createForeignKeyConstraint,
|
|
656
692
|
};
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
}
|
|
693
|
+
const defaultRule = selfReferencing && !this.platform.supportsMultipleCascadePaths() ? 'no action' : 'cascade';
|
|
694
|
+
ret.updateRule ??= defaultRule;
|
|
695
|
+
ret.deleteRule ??= defaultRule;
|
|
661
696
|
const meta = this.metadata.get(type);
|
|
662
697
|
ret.targetMeta = meta;
|
|
663
698
|
ret.joinColumns = [];
|
|
@@ -700,7 +735,7 @@ export class MetadataDiscovery {
|
|
|
700
735
|
Object.values(meta.properties)
|
|
701
736
|
.filter(prop => prop.kind !== ReferenceKind.SCALAR && !prop.owner && prop.mappedBy)
|
|
702
737
|
.forEach(prop => {
|
|
703
|
-
const meta2 =
|
|
738
|
+
const meta2 = prop.targetMeta;
|
|
704
739
|
const prop2 = meta2.properties[prop.mappedBy];
|
|
705
740
|
if (prop2 && !prop2.inversedBy) {
|
|
706
741
|
prop2.inversedBy = prop.name;
|
|
@@ -708,7 +743,7 @@ export class MetadataDiscovery {
|
|
|
708
743
|
});
|
|
709
744
|
}
|
|
710
745
|
defineBaseEntityProperties(meta) {
|
|
711
|
-
const base = meta.extends && this.metadata.get(
|
|
746
|
+
const base = meta.extends && this.metadata.get(meta.extends);
|
|
712
747
|
if (!base || base === meta) { // make sure we do not fall into infinite loop
|
|
713
748
|
return 0;
|
|
714
749
|
}
|
|
@@ -739,12 +774,9 @@ export class MetadataDiscovery {
|
|
|
739
774
|
Utils.keys(base.hooks).forEach(type => {
|
|
740
775
|
meta.hooks[type] = Utils.unique([...base.hooks[type], ...(meta.hooks[type] || [])]);
|
|
741
776
|
});
|
|
742
|
-
if (meta.constructorParams
|
|
777
|
+
if ((meta.constructorParams?.length ?? 0) === 0 && (base.constructorParams?.length ?? 0) > 0) {
|
|
743
778
|
meta.constructorParams = [...base.constructorParams];
|
|
744
779
|
}
|
|
745
|
-
if (meta.toJsonParams.length === 0 && base.toJsonParams.length > 0) {
|
|
746
|
-
meta.toJsonParams = [...base.toJsonParams];
|
|
747
|
-
}
|
|
748
780
|
return order;
|
|
749
781
|
}
|
|
750
782
|
initPolyEmbeddables(embeddedProp, discovered, visited = new Set()) {
|
|
@@ -765,6 +797,7 @@ export class MetadataDiscovery {
|
|
|
765
797
|
delete prop.default;
|
|
766
798
|
if (properties[prop.name] && properties[prop.name].type !== prop.type) {
|
|
767
799
|
properties[prop.name].type = `${properties[prop.name].type} | ${prop.type}`;
|
|
800
|
+
properties[prop.name].runtimeType = 'any';
|
|
768
801
|
return properties[prop.name];
|
|
769
802
|
}
|
|
770
803
|
return properties[prop.name] = prop;
|
|
@@ -819,22 +852,30 @@ export class MetadataDiscovery {
|
|
|
819
852
|
}
|
|
820
853
|
return prop.embedded ? isParentObject(meta.properties[prop.embedded[0]]) : false;
|
|
821
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
|
+
};
|
|
822
861
|
const rootProperty = getRootProperty(embeddedProp);
|
|
823
862
|
const parentProperty = meta.properties[embeddedProp.embedded?.[0] ?? ''];
|
|
824
863
|
const object = isParentObject(embeddedProp);
|
|
864
|
+
const array = isParentArray(embeddedProp);
|
|
825
865
|
this.initFieldName(embeddedProp, rootProperty !== embeddedProp && object);
|
|
826
866
|
// the prefix of the parent cannot be a boolean; it already passed here
|
|
827
867
|
const prefix = this.getPrefix(embeddedProp, parentProperty);
|
|
828
868
|
const glue = object ? '~' : '_';
|
|
829
869
|
for (const prop of Object.values(embeddable.properties)) {
|
|
830
870
|
const name = (embeddedProp.embeddedPath?.join(glue) ?? embeddedProp.fieldNames[0] + glue) + prop.name;
|
|
831
|
-
meta.properties[name] = Utils.copy(prop
|
|
871
|
+
meta.properties[name] = Utils.copy(prop);
|
|
832
872
|
meta.properties[name].name = name;
|
|
833
873
|
meta.properties[name].embedded = [embeddedProp.name, prop.name];
|
|
834
874
|
meta.propertyOrder.set(name, (order += 0.01));
|
|
835
875
|
embeddedProp.embeddedProps[prop.name] = meta.properties[name];
|
|
836
876
|
meta.properties[name].persist ??= embeddedProp.persist;
|
|
837
|
-
|
|
877
|
+
const refInArray = array && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && prop.owner;
|
|
878
|
+
if (embeddedProp.nullable || refInArray) {
|
|
838
879
|
meta.properties[name].nullable = true;
|
|
839
880
|
}
|
|
840
881
|
if (meta.properties[name].fieldNames) {
|
|
@@ -864,14 +905,17 @@ export class MetadataDiscovery {
|
|
|
864
905
|
path = [embeddedProp.fieldNames[0]];
|
|
865
906
|
}
|
|
866
907
|
this.initFieldName(prop, true);
|
|
908
|
+
this.initRelation(prop);
|
|
867
909
|
path.push(prop.fieldNames[0]);
|
|
868
910
|
meta.properties[name].fieldNames = prop.fieldNames;
|
|
869
911
|
meta.properties[name].embeddedPath = path;
|
|
870
|
-
const
|
|
912
|
+
const targetProp = prop.targetMeta?.getPrimaryProp() ?? prop;
|
|
913
|
+
const fieldName = raw(this.platform.getSearchJsonPropertySQL(path.join('->'), targetProp.runtimeType ?? targetProp.type, true));
|
|
871
914
|
meta.properties[name].fieldNameRaw = fieldName.sql; // for querying in SQL drivers
|
|
872
915
|
meta.properties[name].persist = false; // only virtual as we store the whole object
|
|
873
916
|
meta.properties[name].userDefined = false; // mark this as a generated/internal property, so we can distinguish from user-defined non-persist properties
|
|
874
917
|
meta.properties[name].object = true;
|
|
918
|
+
this.initCustomType(meta, meta.properties[name], false, true);
|
|
875
919
|
}
|
|
876
920
|
this.initEmbeddables(meta, meta.properties[name], visited);
|
|
877
921
|
}
|
|
@@ -894,7 +938,7 @@ export class MetadataDiscovery {
|
|
|
894
938
|
}
|
|
895
939
|
initSingleTableInheritance(meta, metadata) {
|
|
896
940
|
if (meta.root !== meta && !meta.__processed) {
|
|
897
|
-
meta.root = metadata.find(m => m.
|
|
941
|
+
meta.root = metadata.find(m => m.class === meta.root.class);
|
|
898
942
|
meta.root.__processed = true;
|
|
899
943
|
}
|
|
900
944
|
else {
|
|
@@ -903,15 +947,23 @@ export class MetadataDiscovery {
|
|
|
903
947
|
if (!meta.root.discriminatorColumn) {
|
|
904
948
|
return;
|
|
905
949
|
}
|
|
906
|
-
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 {
|
|
907
957
|
meta.root.discriminatorMap = {};
|
|
908
|
-
const children = metadata
|
|
909
|
-
|
|
958
|
+
const children = metadata
|
|
959
|
+
.filter(m => m.root.class === meta.root.class && !m.abstract)
|
|
960
|
+
.sort((a, b) => a.className.localeCompare(b.className));
|
|
961
|
+
for (const m of children) {
|
|
910
962
|
const name = m.discriminatorValue ?? this.namingStrategy.classToTableName(m.className);
|
|
911
|
-
meta.root.discriminatorMap[name] = m.
|
|
912
|
-
}
|
|
963
|
+
meta.root.discriminatorMap[name] = m.class;
|
|
964
|
+
}
|
|
913
965
|
}
|
|
914
|
-
meta.discriminatorValue = Object.entries(meta.root.discriminatorMap).find(([,
|
|
966
|
+
meta.discriminatorValue = Object.entries(meta.root.discriminatorMap).find(([, cls]) => cls === meta.class)?.[0];
|
|
915
967
|
if (!meta.root.properties[meta.root.discriminatorColumn]) {
|
|
916
968
|
this.createDiscriminatorProperty(meta.root);
|
|
917
969
|
}
|
|
@@ -920,30 +972,49 @@ export class MetadataDiscovery {
|
|
|
920
972
|
if (meta.root === meta) {
|
|
921
973
|
return;
|
|
922
974
|
}
|
|
923
|
-
let i = 1;
|
|
924
975
|
Object.values(meta.properties).forEach(prop => {
|
|
925
|
-
const newProp =
|
|
926
|
-
|
|
976
|
+
const newProp = { ...prop };
|
|
977
|
+
const rootProp = meta.root.properties[prop.name];
|
|
978
|
+
if (rootProp && (rootProp.type !== prop.type || (rootProp.fieldNames && prop.fieldNames && !compareArrays(rootProp.fieldNames, prop.fieldNames)))) {
|
|
927
979
|
const name = newProp.name;
|
|
928
980
|
this.initFieldName(newProp, newProp.object);
|
|
929
|
-
newProp.
|
|
981
|
+
newProp.renamedFrom = name;
|
|
982
|
+
newProp.name = `${name}_${meta._id}`;
|
|
930
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);
|
|
931
1001
|
newProp.nullable = true;
|
|
932
1002
|
newProp.name = name;
|
|
933
1003
|
newProp.hydrate = false;
|
|
934
1004
|
newProp.inherited = true;
|
|
935
1005
|
return;
|
|
936
1006
|
}
|
|
937
|
-
if (prop.enum && prop.items &&
|
|
938
|
-
newProp.items = Utils.unique([...
|
|
1007
|
+
if (prop.enum && prop.items && rootProp?.items) {
|
|
1008
|
+
newProp.items = Utils.unique([...rootProp.items, ...prop.items]);
|
|
939
1009
|
}
|
|
940
1010
|
newProp.nullable = true;
|
|
941
|
-
newProp.inherited =
|
|
1011
|
+
newProp.inherited = !rootProp;
|
|
942
1012
|
meta.root.addProperty(newProp);
|
|
943
1013
|
});
|
|
944
|
-
meta.
|
|
1014
|
+
meta.tableName = meta.root.tableName;
|
|
945
1015
|
meta.root.indexes = Utils.unique([...meta.root.indexes, ...meta.indexes]);
|
|
946
1016
|
meta.root.uniques = Utils.unique([...meta.root.uniques, ...meta.uniques]);
|
|
1017
|
+
meta.root.checks = Utils.unique([...meta.root.checks, ...meta.checks]);
|
|
947
1018
|
}
|
|
948
1019
|
createDiscriminatorProperty(meta) {
|
|
949
1020
|
meta.addProperty({
|
|
@@ -962,7 +1033,7 @@ export class MetadataDiscovery {
|
|
|
962
1033
|
}
|
|
963
1034
|
}
|
|
964
1035
|
initCheckConstraints(meta) {
|
|
965
|
-
const map =
|
|
1036
|
+
const map = meta.createColumnMappingObject();
|
|
966
1037
|
for (const check of meta.checks) {
|
|
967
1038
|
const columns = check.property ? meta.properties[check.property].fieldNames : [];
|
|
968
1039
|
check.name ??= this.namingStrategy.indexName(meta.tableName, columns, 'check');
|
|
@@ -972,7 +1043,7 @@ export class MetadataDiscovery {
|
|
|
972
1043
|
}
|
|
973
1044
|
if (this.platform.usesEnumCheckConstraints() && !meta.embeddable) {
|
|
974
1045
|
for (const prop of meta.props) {
|
|
975
|
-
if (prop.enum && !prop.nativeEnumName && prop.items?.every(item =>
|
|
1046
|
+
if (prop.enum && !prop.nativeEnumName && prop.items?.every(item => typeof item === 'string')) {
|
|
976
1047
|
this.initFieldName(prop);
|
|
977
1048
|
meta.checks.push({
|
|
978
1049
|
name: this.namingStrategy.indexName(meta.tableName, prop.fieldNames, 'check'),
|
|
@@ -997,38 +1068,28 @@ export class MetadataDiscovery {
|
|
|
997
1068
|
}
|
|
998
1069
|
return;
|
|
999
1070
|
}
|
|
1000
|
-
const map =
|
|
1071
|
+
const map = meta.createColumnMappingObject();
|
|
1001
1072
|
if (prop.generated instanceof Function) {
|
|
1002
1073
|
prop.generated = prop.generated(map);
|
|
1003
1074
|
}
|
|
1004
1075
|
}
|
|
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) {
|
|
1076
|
+
getDefaultVersionValue(meta, prop) {
|
|
1014
1077
|
if (typeof prop.defaultRaw !== 'undefined') {
|
|
1015
1078
|
return prop.defaultRaw;
|
|
1016
1079
|
}
|
|
1017
|
-
/* v8 ignore next
|
|
1080
|
+
/* v8 ignore next */
|
|
1018
1081
|
if (prop.default != null) {
|
|
1019
1082
|
return '' + this.platform.quoteVersionValue(prop.default, prop);
|
|
1020
1083
|
}
|
|
1021
|
-
|
|
1084
|
+
this.initCustomType(meta, prop, true);
|
|
1085
|
+
const type = prop.customType?.runtimeType ?? prop.runtimeType ?? prop.type;
|
|
1086
|
+
if (type === 'Date') {
|
|
1022
1087
|
prop.length ??= this.platform.getDefaultVersionLength();
|
|
1023
1088
|
return this.platform.getCurrentTimestampSQL(prop.length);
|
|
1024
1089
|
}
|
|
1025
1090
|
return '1';
|
|
1026
1091
|
}
|
|
1027
1092
|
inferDefaultValue(meta, prop) {
|
|
1028
|
-
/* v8 ignore next 3 */
|
|
1029
|
-
if (!meta.class) {
|
|
1030
|
-
return;
|
|
1031
|
-
}
|
|
1032
1093
|
try {
|
|
1033
1094
|
// try to create two entity instances to detect the value is stable
|
|
1034
1095
|
const now = Date.now();
|
|
@@ -1056,12 +1117,12 @@ export class MetadataDiscovery {
|
|
|
1056
1117
|
return;
|
|
1057
1118
|
}
|
|
1058
1119
|
let val = prop.default;
|
|
1059
|
-
const raw =
|
|
1120
|
+
const raw = Raw.getKnownFragment(val);
|
|
1060
1121
|
if (raw) {
|
|
1061
1122
|
prop.defaultRaw = this.platform.formatQuery(raw.sql, raw.params);
|
|
1062
1123
|
return;
|
|
1063
1124
|
}
|
|
1064
|
-
if (
|
|
1125
|
+
if (Array.isArray(prop.default) && prop.customType) {
|
|
1065
1126
|
val = prop.customType.convertToDatabaseValue(prop.default, this.platform);
|
|
1066
1127
|
}
|
|
1067
1128
|
prop.defaultRaw = typeof val === 'string' ? `'${val}'` : '' + val;
|
|
@@ -1089,13 +1150,13 @@ export class MetadataDiscovery {
|
|
|
1089
1150
|
if (prop.version) {
|
|
1090
1151
|
this.initDefaultValue(prop);
|
|
1091
1152
|
meta.versionProperty = prop.name;
|
|
1092
|
-
prop.defaultRaw = this.getDefaultVersionValue(prop);
|
|
1153
|
+
prop.defaultRaw = this.getDefaultVersionValue(meta, prop);
|
|
1093
1154
|
}
|
|
1094
1155
|
if (prop.concurrencyCheck && !prop.primary) {
|
|
1095
1156
|
meta.concurrencyCheckKeys.add(prop.name);
|
|
1096
1157
|
}
|
|
1097
1158
|
}
|
|
1098
|
-
initCustomType(meta, prop) {
|
|
1159
|
+
initCustomType(meta, prop, simple = false, objectEmbeddable = false) {
|
|
1099
1160
|
// `prop.type` might be actually instance of custom type class
|
|
1100
1161
|
if (Type.isMappedType(prop.type) && !prop.customType) {
|
|
1101
1162
|
prop.customType = prop.type;
|
|
@@ -1103,47 +1164,70 @@ export class MetadataDiscovery {
|
|
|
1103
1164
|
}
|
|
1104
1165
|
// `prop.type` might also be custom type class (not instance), so `typeof MyType` will give us `function`, not `object`
|
|
1105
1166
|
if (typeof prop.type === 'function' && Type.isMappedType(prop.type.prototype) && !prop.customType) {
|
|
1106
|
-
|
|
1107
|
-
|
|
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;
|
|
1108
1182
|
}
|
|
1109
1183
|
if (!prop.customType && ['json', 'jsonb'].includes(prop.type?.toLowerCase())) {
|
|
1110
|
-
prop.customType = new
|
|
1184
|
+
prop.customType = new t.json();
|
|
1111
1185
|
}
|
|
1112
1186
|
if (prop.kind === ReferenceKind.SCALAR && !prop.customType && prop.columnTypes && ['json', 'jsonb'].includes(prop.columnTypes[0])) {
|
|
1113
|
-
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();
|
|
1114
1191
|
}
|
|
1115
1192
|
if (!prop.customType && prop.array && prop.items) {
|
|
1116
|
-
prop.customType = new
|
|
1193
|
+
prop.customType = new t.enumArray(`${meta.className}.${prop.name}`, prop.items);
|
|
1194
|
+
}
|
|
1195
|
+
const isArray = prop.type?.toLowerCase() === 'array' || prop.type?.toString().endsWith('[]');
|
|
1196
|
+
if (objectEmbeddable && !prop.customType && isArray) {
|
|
1197
|
+
prop.customType = new t.json();
|
|
1117
1198
|
}
|
|
1118
1199
|
// for number arrays we make sure to convert the items to numbers
|
|
1119
1200
|
if (!prop.customType && prop.type === 'number[]') {
|
|
1120
|
-
prop.customType = new
|
|
1201
|
+
prop.customType = new t.array(i => +i);
|
|
1121
1202
|
}
|
|
1122
1203
|
// `string[]` can be returned via ts-morph, while reflect metadata will give us just `array`
|
|
1123
|
-
if (!prop.customType &&
|
|
1124
|
-
prop.customType = new
|
|
1204
|
+
if (!prop.customType && isArray) {
|
|
1205
|
+
prop.customType = new t.array();
|
|
1125
1206
|
}
|
|
1126
1207
|
if (!prop.customType && prop.type?.toLowerCase() === 'buffer') {
|
|
1127
|
-
prop.customType = new
|
|
1208
|
+
prop.customType = new t.blob();
|
|
1128
1209
|
}
|
|
1129
1210
|
if (!prop.customType && prop.type?.toLowerCase() === 'uint8array') {
|
|
1130
|
-
prop.customType = new
|
|
1211
|
+
prop.customType = new t.uint8array();
|
|
1131
1212
|
}
|
|
1132
1213
|
const mappedType = this.getMappedType(prop);
|
|
1133
1214
|
if (prop.fieldNames?.length === 1 && !prop.customType) {
|
|
1134
|
-
[
|
|
1215
|
+
[t.bigint, t.double, t.decimal, t.interval, t.date]
|
|
1135
1216
|
.filter(type => mappedType instanceof type)
|
|
1136
|
-
.forEach(type => prop.customType = new type());
|
|
1217
|
+
.forEach((type) => prop.customType = new type());
|
|
1137
1218
|
}
|
|
1138
1219
|
if (prop.customType && !prop.columnTypes) {
|
|
1139
1220
|
const mappedType = this.getMappedType({ columnTypes: [prop.customType.getColumnType(prop, this.platform)] });
|
|
1140
|
-
if (prop.customType.compareAsType() === 'any' && ![
|
|
1221
|
+
if (prop.customType.compareAsType() === 'any' && ![t.json].some(t => prop.customType instanceof t)) {
|
|
1141
1222
|
prop.runtimeType ??= mappedType.runtimeType;
|
|
1142
1223
|
}
|
|
1143
1224
|
else {
|
|
1144
1225
|
prop.runtimeType ??= prop.customType.runtimeType;
|
|
1145
1226
|
}
|
|
1146
1227
|
}
|
|
1228
|
+
else if (prop.runtimeType === 'object') {
|
|
1229
|
+
prop.runtimeType = mappedType.runtimeType;
|
|
1230
|
+
}
|
|
1147
1231
|
else {
|
|
1148
1232
|
prop.runtimeType ??= mappedType.runtimeType;
|
|
1149
1233
|
}
|
|
@@ -1154,16 +1238,16 @@ export class MetadataDiscovery {
|
|
|
1154
1238
|
prop.columnTypes ??= [prop.customType.getColumnType(prop, this.platform)];
|
|
1155
1239
|
prop.hasConvertToJSValueSQL = !!prop.customType.convertToJSValueSQL && prop.customType.convertToJSValueSQL('', this.platform) !== '';
|
|
1156
1240
|
prop.hasConvertToDatabaseValueSQL = !!prop.customType.convertToDatabaseValueSQL && prop.customType.convertToDatabaseValueSQL('', this.platform) !== '';
|
|
1157
|
-
if (prop.customType instanceof
|
|
1241
|
+
if (prop.customType instanceof t.bigint && ['string', 'bigint', 'number'].includes(prop.runtimeType.toLowerCase())) {
|
|
1158
1242
|
prop.customType.mode = prop.runtimeType.toLowerCase();
|
|
1159
1243
|
}
|
|
1160
1244
|
}
|
|
1161
|
-
if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !
|
|
1245
|
+
if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !isArray) {
|
|
1162
1246
|
prop.type = prop.customType.name;
|
|
1163
1247
|
}
|
|
1164
|
-
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) {
|
|
1165
1249
|
prop.customTypes = [];
|
|
1166
|
-
for (const pk of
|
|
1250
|
+
for (const pk of prop.targetMeta.getPrimaryProps()) {
|
|
1167
1251
|
if (pk.customType) {
|
|
1168
1252
|
prop.customTypes.push(pk.customType);
|
|
1169
1253
|
prop.hasConvertToJSValueSQL ||= !!pk.customType.convertToJSValueSQL && pk.customType.convertToJSValueSQL('', this.platform) !== '';
|
|
@@ -1175,7 +1259,7 @@ export class MetadataDiscovery {
|
|
|
1175
1259
|
}
|
|
1176
1260
|
}
|
|
1177
1261
|
}
|
|
1178
|
-
if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof
|
|
1262
|
+
if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof t.unknown)) {
|
|
1179
1263
|
if (!prop.columnTypes && prop.nativeEnumName && meta.schema !== this.platform.getDefaultSchemaName() && meta.schema && !prop.nativeEnumName.includes('.')) {
|
|
1180
1264
|
prop.columnTypes = [`${meta.schema}.${prop.nativeEnumName}`];
|
|
1181
1265
|
}
|
|
@@ -1193,19 +1277,28 @@ export class MetadataDiscovery {
|
|
|
1193
1277
|
if (prop.kind === ReferenceKind.SCALAR) {
|
|
1194
1278
|
return;
|
|
1195
1279
|
}
|
|
1196
|
-
|
|
1197
|
-
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;
|
|
1198
1284
|
prop.targetMeta = meta2;
|
|
1285
|
+
if (meta2.view) {
|
|
1286
|
+
prop.createForeignKeyConstraint = false;
|
|
1287
|
+
}
|
|
1199
1288
|
if (!prop.formula && prop.persist === false && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.embedded) {
|
|
1200
|
-
prop.formula =
|
|
1289
|
+
prop.formula = table => `${table}.${this.platform.quoteIdentifier(prop.fieldNames[0])}`;
|
|
1201
1290
|
}
|
|
1202
1291
|
}
|
|
1203
1292
|
initColumnType(prop) {
|
|
1204
1293
|
this.initUnsigned(prop);
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
prop.
|
|
1208
|
-
|
|
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;
|
|
1209
1302
|
});
|
|
1210
1303
|
if (prop.kind === ReferenceKind.SCALAR && (prop.type == null || prop.type === 'object') && prop.columnTypes?.[0]) {
|
|
1211
1304
|
delete prop.type;
|
|
@@ -1218,8 +1311,7 @@ export class MetadataDiscovery {
|
|
|
1218
1311
|
if (prop.kind === ReferenceKind.SCALAR) {
|
|
1219
1312
|
const mappedType = this.getMappedType(prop);
|
|
1220
1313
|
const SCALAR_TYPES = ['string', 'number', 'boolean', 'bigint', 'Date', 'Buffer', 'RegExp', 'any', 'unknown'];
|
|
1221
|
-
if (mappedType instanceof
|
|
1222
|
-
&& !prop.columnTypes
|
|
1314
|
+
if (mappedType instanceof t.unknown
|
|
1223
1315
|
// it could be a runtime type from reflect-metadata
|
|
1224
1316
|
&& !SCALAR_TYPES.includes(prop.type)
|
|
1225
1317
|
// or it might be inferred via ts-morph to some generic type alias
|
|
@@ -1232,23 +1324,28 @@ export class MetadataDiscovery {
|
|
|
1232
1324
|
}
|
|
1233
1325
|
return;
|
|
1234
1326
|
}
|
|
1235
|
-
|
|
1327
|
+
/* v8 ignore next */
|
|
1328
|
+
if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
|
|
1236
1329
|
prop.columnTypes = [this.platform.getJsonDeclarationSQL()];
|
|
1237
1330
|
return;
|
|
1238
1331
|
}
|
|
1239
|
-
const targetMeta =
|
|
1332
|
+
const targetMeta = prop.targetMeta;
|
|
1240
1333
|
prop.columnTypes = [];
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
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)];
|
|
1248
1345
|
}
|
|
1249
1346
|
prop.columnTypes.push(...columnTypes);
|
|
1250
|
-
if (!targetMeta.compositePK) {
|
|
1251
|
-
prop.customType =
|
|
1347
|
+
if (!targetMeta.compositePK || prop.targetKey) {
|
|
1348
|
+
prop.customType = referencedProp.customType;
|
|
1252
1349
|
}
|
|
1253
1350
|
}
|
|
1254
1351
|
}
|
|
@@ -1262,7 +1359,7 @@ export class MetadataDiscovery {
|
|
|
1262
1359
|
t = 'enum';
|
|
1263
1360
|
}
|
|
1264
1361
|
else if (prop.enum) {
|
|
1265
|
-
t = prop.items?.every(item =>
|
|
1362
|
+
t = prop.items?.every(item => typeof item === 'string') ? 'enum' : 'tinyint';
|
|
1266
1363
|
}
|
|
1267
1364
|
if (t === 'Date') {
|
|
1268
1365
|
t = 'datetime';
|
|
@@ -1286,7 +1383,7 @@ export class MetadataDiscovery {
|
|
|
1286
1383
|
return;
|
|
1287
1384
|
}
|
|
1288
1385
|
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
1289
|
-
const meta2 =
|
|
1386
|
+
const meta2 = prop.targetMeta;
|
|
1290
1387
|
prop.unsigned = meta2.getPrimaryProps().some(pk => {
|
|
1291
1388
|
this.initUnsigned(pk);
|
|
1292
1389
|
return pk.unsigned;
|
|
@@ -1301,30 +1398,6 @@ export class MetadataDiscovery {
|
|
|
1301
1398
|
prop.index ??= true;
|
|
1302
1399
|
}
|
|
1303
1400
|
}
|
|
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
1401
|
shouldForceConstructorUsage(meta) {
|
|
1329
1402
|
const forceConstructor = this.config.get('forceEntityConstructor');
|
|
1330
1403
|
if (Array.isArray(forceConstructor)) {
|