@mikro-orm/core 7.0.0-dev.18 → 7.0.0-dev.180
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 +35 -15
- package/entity/EntityLoader.d.ts +6 -6
- package/entity/EntityLoader.js +117 -77
- 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 +583 -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 +20 -22
- package/metadata/EntitySchema.js +59 -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 +96 -39
- 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 +8 -4
- package/naming-strategy/AbstractNamingStrategy.js +8 -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 +14 -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 +189 -116
- 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 +764 -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 +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 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,123 +209,100 @@ export class MetadataDiscovery {
|
|
|
173
209
|
}
|
|
174
210
|
tryDiscoverTargets(targets) {
|
|
175
211
|
for (const target of targets) {
|
|
176
|
-
|
|
177
|
-
|
|
212
|
+
const isDiscoverable = typeof target === 'function' || target instanceof EntitySchema;
|
|
213
|
+
if (isDiscoverable && target.name && !this.metadata.has(target)) {
|
|
214
|
+
this.discoverReferences([target], false);
|
|
178
215
|
this.discoverMissingTargets();
|
|
179
216
|
}
|
|
180
217
|
}
|
|
181
218
|
}
|
|
182
|
-
|
|
183
|
-
paths = paths.map(path => Utils.normalizePath(path));
|
|
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;
|
|
204
|
-
}
|
|
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
|
-
}
|
|
211
|
-
}
|
|
212
|
-
for (const [schema, path] of found) {
|
|
213
|
-
this.discoverEntity(schema, path);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
discoverReferences(refs) {
|
|
219
|
+
discoverReferences(refs, validate = true) {
|
|
217
220
|
const found = [];
|
|
218
221
|
for (const entity of refs) {
|
|
219
|
-
|
|
222
|
+
if (typeof entity === 'string') {
|
|
223
|
+
throw new Error('Folder based discovery requires the async `MikroORM.init()` method.');
|
|
224
|
+
}
|
|
225
|
+
const schema = this.getSchema(entity);
|
|
220
226
|
const meta = schema.init().meta;
|
|
221
|
-
this.metadata.set(meta.
|
|
227
|
+
this.metadata.set(meta.class, meta);
|
|
222
228
|
found.push(schema);
|
|
223
229
|
}
|
|
224
230
|
// discover parents (base entities) automatically
|
|
225
231
|
for (const meta of this.metadata) {
|
|
226
232
|
let parent = meta.extends;
|
|
227
|
-
if (parent instanceof EntitySchema && !this.metadata.has(parent.meta.
|
|
228
|
-
this.discoverReferences([parent]);
|
|
233
|
+
if (parent instanceof EntitySchema && !this.metadata.has(parent.init().meta.class)) {
|
|
234
|
+
this.discoverReferences([parent], false);
|
|
229
235
|
}
|
|
230
|
-
|
|
236
|
+
if (typeof parent === 'function' && parent.name && !this.metadata.has(parent)) {
|
|
237
|
+
this.discoverReferences([parent], false);
|
|
238
|
+
}
|
|
239
|
+
/* v8 ignore next */
|
|
231
240
|
if (!meta.class) {
|
|
232
241
|
continue;
|
|
233
242
|
}
|
|
234
243
|
parent = Object.getPrototypeOf(meta.class);
|
|
235
|
-
if
|
|
236
|
-
|
|
244
|
+
// Skip if parent is the auto-generated base class for the same entity (from setClass usage)
|
|
245
|
+
if (parent.name !== '' && parent.name !== meta.className && !this.metadata.has(parent) && parent !== BaseEntity) {
|
|
246
|
+
this.discoverReferences([parent], false);
|
|
237
247
|
}
|
|
238
248
|
}
|
|
239
249
|
for (const schema of found) {
|
|
240
250
|
this.discoverEntity(schema);
|
|
241
251
|
}
|
|
252
|
+
this.discoverMissingTargets();
|
|
253
|
+
if (validate) {
|
|
254
|
+
this.validator.validateDiscovered(this.discovered, this.config.get('discovery'));
|
|
255
|
+
}
|
|
242
256
|
return this.discovered.filter(meta => found.find(m => m.name === meta.className));
|
|
243
257
|
}
|
|
244
|
-
reset(
|
|
245
|
-
const exists = this.discovered.findIndex(m => m.className === className);
|
|
258
|
+
reset(entityName) {
|
|
259
|
+
const exists = this.discovered.findIndex(m => m.class === entityName || m.className === Utils.className(entityName));
|
|
246
260
|
if (exists !== -1) {
|
|
247
|
-
this.metadata.reset(this.discovered[exists].
|
|
261
|
+
this.metadata.reset(this.discovered[exists].class);
|
|
248
262
|
this.discovered.splice(exists, 1);
|
|
249
263
|
}
|
|
250
264
|
}
|
|
251
|
-
|
|
252
|
-
/* v8 ignore next 3 */
|
|
253
|
-
if ('schema' in entity && entity.schema instanceof EntitySchema) {
|
|
254
|
-
return entity.schema;
|
|
255
|
-
}
|
|
265
|
+
getSchema(entity) {
|
|
256
266
|
if (EntitySchema.REGISTRY.has(entity)) {
|
|
257
|
-
|
|
267
|
+
entity = EntitySchema.REGISTRY.get(entity);
|
|
258
268
|
}
|
|
259
|
-
return entity;
|
|
260
|
-
}
|
|
261
|
-
getSchema(entity, filepath) {
|
|
262
269
|
if (entity instanceof EntitySchema) {
|
|
263
|
-
if (filepath) {
|
|
264
|
-
// initialize global metadata for given entity
|
|
265
|
-
MetadataStorage.getMetadata(entity.meta.className, filepath);
|
|
266
|
-
}
|
|
267
270
|
const meta = Utils.copy(entity.meta, false);
|
|
268
271
|
return EntitySchema.fromMetadata(meta);
|
|
269
272
|
}
|
|
270
273
|
const path = entity[MetadataStorage.PATH_SYMBOL];
|
|
271
274
|
if (path) {
|
|
272
275
|
const meta = Utils.copy(MetadataStorage.getMetadata(entity.name, path), false);
|
|
273
|
-
meta.path =
|
|
274
|
-
this.metadata.set(entity
|
|
276
|
+
meta.path = path;
|
|
277
|
+
this.metadata.set(entity, meta);
|
|
275
278
|
}
|
|
276
|
-
const exists = this.metadata.has(entity
|
|
277
|
-
const meta = this.metadata.get(entity
|
|
279
|
+
const exists = this.metadata.has(entity);
|
|
280
|
+
const meta = this.metadata.get(entity, true);
|
|
278
281
|
meta.abstract ??= !(exists && meta.name);
|
|
279
282
|
const schema = EntitySchema.fromMetadata(meta);
|
|
280
283
|
schema.setClass(entity);
|
|
281
284
|
return schema;
|
|
282
285
|
}
|
|
283
|
-
|
|
284
|
-
|
|
286
|
+
getRootEntity(meta) {
|
|
287
|
+
const base = meta.extends && this.metadata.find(meta.extends);
|
|
288
|
+
if (!base || base === meta) { // make sure we do not fall into infinite loop
|
|
289
|
+
return meta;
|
|
290
|
+
}
|
|
291
|
+
const root = this.getRootEntity(base);
|
|
292
|
+
if (root.discriminatorColumn) {
|
|
293
|
+
return root;
|
|
294
|
+
}
|
|
295
|
+
return meta;
|
|
296
|
+
}
|
|
297
|
+
discoverEntity(schema) {
|
|
285
298
|
const meta = schema.meta;
|
|
286
|
-
const
|
|
287
|
-
|
|
288
|
-
const
|
|
299
|
+
const path = meta.path;
|
|
300
|
+
this.logger.log('discovery', `- processing entity ${colors.cyan(meta.className)}${colors.grey(path ? ` (${path})` : '')}`);
|
|
301
|
+
const root = this.getRootEntity(meta);
|
|
302
|
+
schema.meta.path = meta.path;
|
|
303
|
+
const cache = this.metadataProvider.getCachedMetadata(meta, root);
|
|
289
304
|
if (cache) {
|
|
290
305
|
this.logger.log('discovery', `- using cached metadata for entity ${colors.cyan(meta.className)}`);
|
|
291
|
-
this.metadataProvider.loadFromCache(meta, cache);
|
|
292
|
-
meta.root = root;
|
|
293
306
|
this.discovered.push(meta);
|
|
294
307
|
return;
|
|
295
308
|
}
|
|
@@ -298,50 +311,18 @@ export class MetadataDiscovery {
|
|
|
298
311
|
this.inferDefaultValue(meta, prop);
|
|
299
312
|
}
|
|
300
313
|
// 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.
|
|
314
|
+
this.metadataProvider.loadEntityMetadata(meta);
|
|
315
|
+
if (!meta.tableName && meta.name) {
|
|
303
316
|
const entityName = root.discriminatorColumn ? root.name : meta.name;
|
|
304
|
-
meta.
|
|
317
|
+
meta.tableName = this.namingStrategy.classToTableName(entityName);
|
|
305
318
|
}
|
|
306
|
-
|
|
307
|
-
this.saveToCache(meta);
|
|
319
|
+
this.metadataProvider.saveToCache(meta);
|
|
308
320
|
meta.root = root;
|
|
309
321
|
this.discovered.push(meta);
|
|
310
322
|
}
|
|
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
323
|
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
324
|
if (prop.kind === ReferenceKind.ONE_TO_ONE) {
|
|
344
|
-
return Utils.defaultValue(prop, 'nullable', prop.optional || !prop.owner
|
|
325
|
+
return Utils.defaultValue(prop, 'nullable', prop.optional || !prop.owner);
|
|
345
326
|
}
|
|
346
327
|
return Utils.defaultValue(prop, 'nullable', prop.optional);
|
|
347
328
|
}
|
|
@@ -376,7 +357,7 @@ export class MetadataDiscovery {
|
|
|
376
357
|
if (prop.joinColumns.length !== prop.columnTypes.length) {
|
|
377
358
|
prop.columnTypes = prop.joinColumns.flatMap(field => {
|
|
378
359
|
const matched = meta.props.find(p => p.fieldNames?.includes(field));
|
|
379
|
-
/* v8 ignore next
|
|
360
|
+
/* v8 ignore next */
|
|
380
361
|
if (!matched) {
|
|
381
362
|
throw MetadataError.fromWrongForeignKey(meta, prop, 'columnTypes');
|
|
382
363
|
}
|
|
@@ -402,23 +383,23 @@ export class MetadataDiscovery {
|
|
|
402
383
|
prop.fieldNames = this.initManyToManyFieldName(prop, prop.name);
|
|
403
384
|
}
|
|
404
385
|
}
|
|
405
|
-
initManyToOneFieldName(prop, name) {
|
|
406
|
-
const meta2 =
|
|
386
|
+
initManyToOneFieldName(prop, name, tableName) {
|
|
387
|
+
const meta2 = prop.targetMeta;
|
|
407
388
|
const ret = [];
|
|
408
389
|
for (const primaryKey of meta2.primaryKeys) {
|
|
409
390
|
this.initFieldName(meta2.properties[primaryKey]);
|
|
410
391
|
for (const fieldName of meta2.properties[primaryKey].fieldNames) {
|
|
411
|
-
ret.push(this.namingStrategy.joinKeyColumnName(name, fieldName, meta2.compositePK));
|
|
392
|
+
ret.push(this.namingStrategy.joinKeyColumnName(name, fieldName, meta2.compositePK, tableName));
|
|
412
393
|
}
|
|
413
394
|
}
|
|
414
395
|
return ret;
|
|
415
396
|
}
|
|
416
397
|
initManyToManyFieldName(prop, name) {
|
|
417
|
-
const meta2 =
|
|
398
|
+
const meta2 = prop.targetMeta;
|
|
418
399
|
return meta2.primaryKeys.map(() => this.namingStrategy.propertyToColumnName(name));
|
|
419
400
|
}
|
|
420
401
|
initManyToManyFields(meta, prop) {
|
|
421
|
-
const meta2 =
|
|
402
|
+
const meta2 = prop.targetMeta;
|
|
422
403
|
Utils.defaultValue(prop, 'fixedOrder', !!prop.fixedOrderColumn);
|
|
423
404
|
const pivotMeta = this.metadata.find(prop.pivotEntity);
|
|
424
405
|
const props = Object.values(pivotMeta?.properties ?? {});
|
|
@@ -439,35 +420,58 @@ export class MetadataDiscovery {
|
|
|
439
420
|
prop.inverseJoinColumns ??= second.fieldNames;
|
|
440
421
|
}
|
|
441
422
|
if (!prop.pivotTable && prop.owner && this.platform.usesPivotTable()) {
|
|
442
|
-
prop.pivotTable = this.namingStrategy.joinTableName(meta.
|
|
423
|
+
prop.pivotTable = this.namingStrategy.joinTableName(meta.className, meta2.tableName, prop.name, meta.tableName);
|
|
443
424
|
}
|
|
444
425
|
if (prop.mappedBy) {
|
|
445
426
|
const prop2 = meta2.properties[prop.mappedBy];
|
|
446
427
|
this.initManyToManyFields(meta2, prop2);
|
|
447
428
|
prop.pivotTable = prop2.pivotTable;
|
|
448
|
-
prop.pivotEntity = prop2.pivotEntity
|
|
429
|
+
prop.pivotEntity = prop2.pivotEntity;
|
|
449
430
|
prop.fixedOrder = prop2.fixedOrder;
|
|
450
431
|
prop.fixedOrderColumn = prop2.fixedOrderColumn;
|
|
451
432
|
prop.joinColumns = prop2.inverseJoinColumns;
|
|
452
433
|
prop.inverseJoinColumns = prop2.joinColumns;
|
|
453
434
|
}
|
|
454
435
|
prop.referencedColumnNames ??= Utils.flatten(meta.primaryKeys.map(primaryKey => meta.properties[primaryKey].fieldNames));
|
|
455
|
-
|
|
456
|
-
|
|
436
|
+
const ownerTableName = this.isExplicitTableName(meta.root) ? meta.root.tableName : undefined;
|
|
437
|
+
const inverseTableName = this.isExplicitTableName(meta2.root) ? meta2.root.tableName : undefined;
|
|
438
|
+
prop.joinColumns ??= prop.referencedColumnNames.map(referencedColumnName => this.namingStrategy.joinKeyColumnName(meta.root.className, referencedColumnName, meta.compositePK, ownerTableName));
|
|
439
|
+
prop.inverseJoinColumns ??= this.initManyToOneFieldName(prop, meta2.root.className, inverseTableName);
|
|
440
|
+
}
|
|
441
|
+
isExplicitTableName(meta) {
|
|
442
|
+
return meta.tableName !== this.namingStrategy.classToTableName(meta.className);
|
|
457
443
|
}
|
|
458
444
|
initManyToOneFields(prop) {
|
|
459
|
-
const meta2 =
|
|
460
|
-
|
|
461
|
-
|
|
445
|
+
const meta2 = prop.targetMeta;
|
|
446
|
+
let fieldNames;
|
|
447
|
+
// If targetKey is specified, use that property's field names instead of PKs
|
|
448
|
+
if (prop.targetKey) {
|
|
449
|
+
const targetProp = meta2.properties[prop.targetKey];
|
|
450
|
+
fieldNames = targetProp.fieldNames;
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
fieldNames = Utils.flatten(meta2.primaryKeys.map(primaryKey => meta2.properties[primaryKey].fieldNames));
|
|
454
|
+
}
|
|
455
|
+
Utils.defaultValue(prop, 'referencedTableName', meta2.tableName);
|
|
462
456
|
if (!prop.joinColumns) {
|
|
463
457
|
prop.joinColumns = fieldNames.map(fieldName => this.namingStrategy.joinKeyColumnName(prop.name, fieldName, fieldNames.length > 1));
|
|
464
458
|
}
|
|
465
459
|
if (!prop.referencedColumnNames) {
|
|
466
460
|
prop.referencedColumnNames = fieldNames;
|
|
467
461
|
}
|
|
462
|
+
// Relations to composite PK targets need cascade update by default,
|
|
463
|
+
// since composite PKs are more likely to have mutable components
|
|
464
|
+
if (meta2.compositePK) {
|
|
465
|
+
prop.updateRule ??= 'cascade';
|
|
466
|
+
}
|
|
467
|
+
// Nullable relations default to 'set null' on delete - when the referenced
|
|
468
|
+
// entity is deleted, set the FK to null rather than failing
|
|
469
|
+
if (prop.nullable) {
|
|
470
|
+
prop.deleteRule ??= 'set null';
|
|
471
|
+
}
|
|
468
472
|
}
|
|
469
473
|
initOneToManyFields(prop) {
|
|
470
|
-
const meta2 =
|
|
474
|
+
const meta2 = prop.targetMeta;
|
|
471
475
|
if (!prop.joinColumns) {
|
|
472
476
|
prop.joinColumns = [this.namingStrategy.joinColumnName(prop.name)];
|
|
473
477
|
}
|
|
@@ -480,12 +484,17 @@ export class MetadataDiscovery {
|
|
|
480
484
|
const pks = Object.values(meta.properties).filter(prop => prop.primary);
|
|
481
485
|
meta.primaryKeys = pks.map(prop => prop.name);
|
|
482
486
|
meta.compositePK = pks.length > 1;
|
|
483
|
-
// FK used as PK, we need to cascade
|
|
484
|
-
|
|
485
|
-
|
|
487
|
+
// FK used as PK, we need to cascade - applies to both single FK-as-PK
|
|
488
|
+
// and composite PKs where all PKs are FKs (e.g., pivot-like entities)
|
|
489
|
+
const fkPks = pks.filter(pk => pk.kind !== ReferenceKind.SCALAR);
|
|
490
|
+
if (fkPks.length > 0 && fkPks.length === pks.length) {
|
|
491
|
+
for (const pk of fkPks) {
|
|
492
|
+
pk.deleteRule ??= 'cascade';
|
|
493
|
+
pk.updateRule ??= 'cascade';
|
|
494
|
+
}
|
|
486
495
|
}
|
|
487
496
|
meta.forceConstructor ??= this.shouldForceConstructorUsage(meta);
|
|
488
|
-
this.validator.validateEntityDefinition(this.metadata, meta.
|
|
497
|
+
this.validator.validateEntityDefinition(this.metadata, meta.class, this.config.get('discovery'));
|
|
489
498
|
for (const prop of Object.values(meta.properties)) {
|
|
490
499
|
this.initNullability(prop);
|
|
491
500
|
this.applyNamingStrategy(meta, prop);
|
|
@@ -498,15 +507,21 @@ export class MetadataDiscovery {
|
|
|
498
507
|
}
|
|
499
508
|
this.initOwnColumns(meta);
|
|
500
509
|
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;
|
|
510
|
+
meta.serializedPrimaryKey ??= meta.props.find(prop => prop.serializedPrimaryKey)?.name;
|
|
511
|
+
if (meta.serializedPrimaryKey && meta.serializedPrimaryKey !== meta.primaryKeys[0]) {
|
|
512
|
+
meta.properties[meta.serializedPrimaryKey].persist ??= false;
|
|
505
513
|
}
|
|
506
514
|
if (this.platform.usesPivotTable()) {
|
|
507
515
|
return Object.values(meta.properties)
|
|
508
516
|
.filter(prop => prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && prop.pivotTable)
|
|
509
|
-
.map(prop =>
|
|
517
|
+
.map(prop => {
|
|
518
|
+
const pivotMeta = this.definePivotTableEntity(meta, prop);
|
|
519
|
+
prop.pivotEntity = pivotMeta.class;
|
|
520
|
+
if (prop.inversedBy) {
|
|
521
|
+
prop.targetMeta.properties[prop.inversedBy].pivotEntity = pivotMeta.class;
|
|
522
|
+
}
|
|
523
|
+
return pivotMeta;
|
|
524
|
+
});
|
|
510
525
|
}
|
|
511
526
|
return [];
|
|
512
527
|
}
|
|
@@ -523,8 +538,11 @@ export class MetadataDiscovery {
|
|
|
523
538
|
['mappedBy', 'inversedBy', 'pivotEntity'].forEach(type => {
|
|
524
539
|
const value = prop[type];
|
|
525
540
|
if (value instanceof Function) {
|
|
526
|
-
const meta2 = this.metadata.get(prop.
|
|
541
|
+
const meta2 = prop.targetMeta ?? this.metadata.get(prop.target);
|
|
527
542
|
prop[type] = value(meta2.properties)?.name;
|
|
543
|
+
if (type === 'pivotEntity' && value) {
|
|
544
|
+
prop[type] = value(meta2.properties);
|
|
545
|
+
}
|
|
528
546
|
if (prop[type] == null) {
|
|
529
547
|
throw MetadataError.fromWrongReference(meta, prop, type);
|
|
530
548
|
}
|
|
@@ -540,9 +558,9 @@ export class MetadataDiscovery {
|
|
|
540
558
|
}
|
|
541
559
|
else if (fks.length >= 2) {
|
|
542
560
|
[first, second] = fks;
|
|
543
|
-
/* v8 ignore next 3 */
|
|
544
561
|
}
|
|
545
562
|
else {
|
|
563
|
+
/* v8 ignore next */
|
|
546
564
|
return [];
|
|
547
565
|
}
|
|
548
566
|
// wrong FK order, first FK needs to point to the owning side
|
|
@@ -556,7 +574,9 @@ export class MetadataDiscovery {
|
|
|
556
574
|
return [first, second];
|
|
557
575
|
}
|
|
558
576
|
definePivotTableEntity(meta, prop) {
|
|
559
|
-
const pivotMeta =
|
|
577
|
+
const pivotMeta = prop.pivotEntity
|
|
578
|
+
? this.metadata.find(prop.pivotEntity)
|
|
579
|
+
: this.metadata.getByClassName(prop.pivotTable, false);
|
|
560
580
|
// ensure inverse side exists so we can join it when populating via pivot tables
|
|
561
581
|
if (!prop.inversedBy && prop.targetMeta) {
|
|
562
582
|
const inverseName = `${meta.className}_${prop.name}__inverse`;
|
|
@@ -565,6 +585,8 @@ export class MetadataDiscovery {
|
|
|
565
585
|
name: inverseName,
|
|
566
586
|
kind: ReferenceKind.MANY_TO_MANY,
|
|
567
587
|
type: meta.className,
|
|
588
|
+
target: meta.class,
|
|
589
|
+
targetMeta: meta,
|
|
568
590
|
mappedBy: prop.name,
|
|
569
591
|
pivotEntity: prop.pivotEntity,
|
|
570
592
|
pivotTable: prop.pivotTable,
|
|
@@ -573,55 +595,61 @@ export class MetadataDiscovery {
|
|
|
573
595
|
};
|
|
574
596
|
this.applyNamingStrategy(prop.targetMeta, inverseProp);
|
|
575
597
|
this.initCustomType(prop.targetMeta, inverseProp);
|
|
576
|
-
this.initRelation(inverseProp);
|
|
577
598
|
prop.targetMeta.properties[inverseName] = inverseProp;
|
|
578
599
|
}
|
|
579
600
|
if (pivotMeta) {
|
|
601
|
+
prop.pivotEntity = pivotMeta.class;
|
|
580
602
|
this.ensureCorrectFKOrderInPivotEntity(pivotMeta, prop);
|
|
581
603
|
return pivotMeta;
|
|
582
604
|
}
|
|
583
|
-
const exists = this.metadata.find(prop.pivotTable);
|
|
584
|
-
if (exists) {
|
|
585
|
-
prop.pivotEntity = exists.className;
|
|
586
|
-
return exists;
|
|
587
|
-
}
|
|
588
605
|
let tableName = prop.pivotTable;
|
|
589
606
|
let schemaName;
|
|
590
607
|
if (prop.pivotTable.includes('.')) {
|
|
591
608
|
[schemaName, tableName] = prop.pivotTable.split('.');
|
|
592
609
|
}
|
|
593
610
|
schemaName ??= meta.schema;
|
|
594
|
-
const
|
|
595
|
-
const
|
|
611
|
+
const targetMeta = prop.targetMeta;
|
|
612
|
+
const targetType = targetMeta.className;
|
|
613
|
+
const pivotMeta2 = new EntityMetadata({
|
|
596
614
|
name: prop.pivotTable,
|
|
597
615
|
className: prop.pivotTable,
|
|
598
616
|
collection: tableName,
|
|
599
617
|
schema: schemaName,
|
|
600
618
|
pivotTable: true,
|
|
601
619
|
});
|
|
602
|
-
prop.pivotEntity =
|
|
620
|
+
prop.pivotEntity = pivotMeta2.class;
|
|
603
621
|
if (prop.fixedOrder) {
|
|
604
|
-
const primaryProp = this.defineFixedOrderProperty(prop,
|
|
605
|
-
|
|
622
|
+
const primaryProp = this.defineFixedOrderProperty(prop, targetMeta);
|
|
623
|
+
pivotMeta2.properties[primaryProp.name] = primaryProp;
|
|
606
624
|
}
|
|
607
625
|
else {
|
|
608
|
-
|
|
626
|
+
pivotMeta2.compositePK = true;
|
|
609
627
|
}
|
|
610
628
|
// handle self-referenced m:n with same default field names
|
|
611
629
|
if (meta.className === targetType && prop.joinColumns.every((joinColumn, idx) => joinColumn === prop.inverseJoinColumns[idx])) {
|
|
612
|
-
|
|
613
|
-
|
|
630
|
+
// use tableName only when explicitly provided by user, otherwise use className for backwards compatibility
|
|
631
|
+
const baseName = this.isExplicitTableName(meta) ? meta.tableName : meta.className;
|
|
632
|
+
prop.joinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(baseName + '_1', name, meta.compositePK));
|
|
633
|
+
prop.inverseJoinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(baseName + '_2', name, meta.compositePK));
|
|
614
634
|
if (prop.inversedBy) {
|
|
615
|
-
const prop2 =
|
|
635
|
+
const prop2 = targetMeta.properties[prop.inversedBy];
|
|
616
636
|
prop2.inverseJoinColumns = prop.joinColumns;
|
|
617
637
|
prop2.joinColumns = prop.inverseJoinColumns;
|
|
618
638
|
}
|
|
639
|
+
// propagate updated joinColumns to all child entities that inherit this property (STI)
|
|
640
|
+
for (const childMeta of this.discovered.filter(m => m.root === meta && m !== meta)) {
|
|
641
|
+
const childProp = childMeta.properties[prop.name];
|
|
642
|
+
if (childProp) {
|
|
643
|
+
childProp.joinColumns = prop.joinColumns;
|
|
644
|
+
childProp.inverseJoinColumns = prop.inverseJoinColumns;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
619
647
|
}
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
return this.metadata.set(
|
|
648
|
+
pivotMeta2.properties[meta.name + '_owner'] = this.definePivotProperty(prop, meta.name + '_owner', meta.class, targetType + '_inverse', true, meta.className === targetType);
|
|
649
|
+
pivotMeta2.properties[targetType + '_inverse'] = this.definePivotProperty(prop, targetType + '_inverse', targetMeta.class, meta.name + '_owner', false, meta.className === targetType);
|
|
650
|
+
return this.metadata.set(pivotMeta2.class, EntitySchema.fromMetadata(pivotMeta2).init().meta);
|
|
623
651
|
}
|
|
624
|
-
defineFixedOrderProperty(prop,
|
|
652
|
+
defineFixedOrderProperty(prop, targetMeta) {
|
|
625
653
|
const pk = prop.fixedOrderColumn || this.namingStrategy.referenceColumnName();
|
|
626
654
|
const primaryProp = {
|
|
627
655
|
name: pk,
|
|
@@ -635,7 +663,7 @@ export class MetadataDiscovery {
|
|
|
635
663
|
this.initColumnType(primaryProp);
|
|
636
664
|
prop.fixedOrderColumn = pk;
|
|
637
665
|
if (prop.inversedBy) {
|
|
638
|
-
const prop2 =
|
|
666
|
+
const prop2 = targetMeta.properties[prop.inversedBy];
|
|
639
667
|
prop2.fixedOrder = true;
|
|
640
668
|
prop2.fixedOrderColumn = pk;
|
|
641
669
|
}
|
|
@@ -644,7 +672,8 @@ export class MetadataDiscovery {
|
|
|
644
672
|
definePivotProperty(prop, name, type, inverse, owner, selfReferencing) {
|
|
645
673
|
const ret = {
|
|
646
674
|
name,
|
|
647
|
-
type,
|
|
675
|
+
type: Utils.className(type),
|
|
676
|
+
target: type,
|
|
648
677
|
kind: ReferenceKind.MANY_TO_ONE,
|
|
649
678
|
cascade: [Cascade.ALL],
|
|
650
679
|
fixedOrder: prop.fixedOrder,
|
|
@@ -654,11 +683,11 @@ export class MetadataDiscovery {
|
|
|
654
683
|
autoincrement: false,
|
|
655
684
|
updateRule: prop.updateRule,
|
|
656
685
|
deleteRule: prop.deleteRule,
|
|
686
|
+
createForeignKeyConstraint: prop.createForeignKeyConstraint,
|
|
657
687
|
};
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
}
|
|
688
|
+
const defaultRule = selfReferencing && !this.platform.supportsMultipleCascadePaths() ? 'no action' : 'cascade';
|
|
689
|
+
ret.updateRule ??= defaultRule;
|
|
690
|
+
ret.deleteRule ??= defaultRule;
|
|
662
691
|
const meta = this.metadata.get(type);
|
|
663
692
|
ret.targetMeta = meta;
|
|
664
693
|
ret.joinColumns = [];
|
|
@@ -701,7 +730,7 @@ export class MetadataDiscovery {
|
|
|
701
730
|
Object.values(meta.properties)
|
|
702
731
|
.filter(prop => prop.kind !== ReferenceKind.SCALAR && !prop.owner && prop.mappedBy)
|
|
703
732
|
.forEach(prop => {
|
|
704
|
-
const meta2 =
|
|
733
|
+
const meta2 = prop.targetMeta;
|
|
705
734
|
const prop2 = meta2.properties[prop.mappedBy];
|
|
706
735
|
if (prop2 && !prop2.inversedBy) {
|
|
707
736
|
prop2.inversedBy = prop.name;
|
|
@@ -709,7 +738,7 @@ export class MetadataDiscovery {
|
|
|
709
738
|
});
|
|
710
739
|
}
|
|
711
740
|
defineBaseEntityProperties(meta) {
|
|
712
|
-
const base = meta.extends && this.metadata.get(
|
|
741
|
+
const base = meta.extends && this.metadata.get(meta.extends);
|
|
713
742
|
if (!base || base === meta) { // make sure we do not fall into infinite loop
|
|
714
743
|
return 0;
|
|
715
744
|
}
|
|
@@ -740,12 +769,9 @@ export class MetadataDiscovery {
|
|
|
740
769
|
Utils.keys(base.hooks).forEach(type => {
|
|
741
770
|
meta.hooks[type] = Utils.unique([...base.hooks[type], ...(meta.hooks[type] || [])]);
|
|
742
771
|
});
|
|
743
|
-
if (meta.constructorParams
|
|
772
|
+
if ((meta.constructorParams?.length ?? 0) === 0 && (base.constructorParams?.length ?? 0) > 0) {
|
|
744
773
|
meta.constructorParams = [...base.constructorParams];
|
|
745
774
|
}
|
|
746
|
-
if (meta.toJsonParams.length === 0 && base.toJsonParams.length > 0) {
|
|
747
|
-
meta.toJsonParams = [...base.toJsonParams];
|
|
748
|
-
}
|
|
749
775
|
return order;
|
|
750
776
|
}
|
|
751
777
|
initPolyEmbeddables(embeddedProp, discovered, visited = new Set()) {
|
|
@@ -821,22 +847,30 @@ export class MetadataDiscovery {
|
|
|
821
847
|
}
|
|
822
848
|
return prop.embedded ? isParentObject(meta.properties[prop.embedded[0]]) : false;
|
|
823
849
|
};
|
|
850
|
+
const isParentArray = (prop) => {
|
|
851
|
+
if (prop.array) {
|
|
852
|
+
return true;
|
|
853
|
+
}
|
|
854
|
+
return prop.embedded ? isParentArray(meta.properties[prop.embedded[0]]) : false;
|
|
855
|
+
};
|
|
824
856
|
const rootProperty = getRootProperty(embeddedProp);
|
|
825
857
|
const parentProperty = meta.properties[embeddedProp.embedded?.[0] ?? ''];
|
|
826
858
|
const object = isParentObject(embeddedProp);
|
|
859
|
+
const array = isParentArray(embeddedProp);
|
|
827
860
|
this.initFieldName(embeddedProp, rootProperty !== embeddedProp && object);
|
|
828
861
|
// the prefix of the parent cannot be a boolean; it already passed here
|
|
829
862
|
const prefix = this.getPrefix(embeddedProp, parentProperty);
|
|
830
863
|
const glue = object ? '~' : '_';
|
|
831
864
|
for (const prop of Object.values(embeddable.properties)) {
|
|
832
865
|
const name = (embeddedProp.embeddedPath?.join(glue) ?? embeddedProp.fieldNames[0] + glue) + prop.name;
|
|
833
|
-
meta.properties[name] = Utils.copy(prop
|
|
866
|
+
meta.properties[name] = Utils.copy(prop);
|
|
834
867
|
meta.properties[name].name = name;
|
|
835
868
|
meta.properties[name].embedded = [embeddedProp.name, prop.name];
|
|
836
869
|
meta.propertyOrder.set(name, (order += 0.01));
|
|
837
870
|
embeddedProp.embeddedProps[prop.name] = meta.properties[name];
|
|
838
871
|
meta.properties[name].persist ??= embeddedProp.persist;
|
|
839
|
-
|
|
872
|
+
const refInArray = array && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && prop.owner;
|
|
873
|
+
if (embeddedProp.nullable || refInArray) {
|
|
840
874
|
meta.properties[name].nullable = true;
|
|
841
875
|
}
|
|
842
876
|
if (meta.properties[name].fieldNames) {
|
|
@@ -866,14 +900,17 @@ export class MetadataDiscovery {
|
|
|
866
900
|
path = [embeddedProp.fieldNames[0]];
|
|
867
901
|
}
|
|
868
902
|
this.initFieldName(prop, true);
|
|
903
|
+
this.initRelation(prop);
|
|
869
904
|
path.push(prop.fieldNames[0]);
|
|
870
905
|
meta.properties[name].fieldNames = prop.fieldNames;
|
|
871
906
|
meta.properties[name].embeddedPath = path;
|
|
872
|
-
const
|
|
907
|
+
const targetProp = prop.targetMeta?.getPrimaryProp() ?? prop;
|
|
908
|
+
const fieldName = raw(this.platform.getSearchJsonPropertySQL(path.join('->'), targetProp.runtimeType ?? targetProp.type, true));
|
|
873
909
|
meta.properties[name].fieldNameRaw = fieldName.sql; // for querying in SQL drivers
|
|
874
910
|
meta.properties[name].persist = false; // only virtual as we store the whole object
|
|
875
911
|
meta.properties[name].userDefined = false; // mark this as a generated/internal property, so we can distinguish from user-defined non-persist properties
|
|
876
912
|
meta.properties[name].object = true;
|
|
913
|
+
this.initCustomType(meta, meta.properties[name], false, true);
|
|
877
914
|
}
|
|
878
915
|
this.initEmbeddables(meta, meta.properties[name], visited);
|
|
879
916
|
}
|
|
@@ -896,7 +933,7 @@ export class MetadataDiscovery {
|
|
|
896
933
|
}
|
|
897
934
|
initSingleTableInheritance(meta, metadata) {
|
|
898
935
|
if (meta.root !== meta && !meta.__processed) {
|
|
899
|
-
meta.root = metadata.find(m => m.
|
|
936
|
+
meta.root = metadata.find(m => m.class === meta.root.class);
|
|
900
937
|
meta.root.__processed = true;
|
|
901
938
|
}
|
|
902
939
|
else {
|
|
@@ -905,17 +942,23 @@ export class MetadataDiscovery {
|
|
|
905
942
|
if (!meta.root.discriminatorColumn) {
|
|
906
943
|
return;
|
|
907
944
|
}
|
|
908
|
-
if (
|
|
945
|
+
if (meta.root.discriminatorMap) {
|
|
946
|
+
const map = meta.root.discriminatorMap;
|
|
947
|
+
Object.keys(map)
|
|
948
|
+
.filter(key => typeof map[key] === 'string')
|
|
949
|
+
.forEach(key => map[key] = this.metadata.getByClassName(map[key]).class);
|
|
950
|
+
}
|
|
951
|
+
else {
|
|
909
952
|
meta.root.discriminatorMap = {};
|
|
910
953
|
const children = metadata
|
|
911
|
-
.filter(m => m.root.
|
|
954
|
+
.filter(m => m.root.class === meta.root.class && !m.abstract)
|
|
912
955
|
.sort((a, b) => a.className.localeCompare(b.className));
|
|
913
956
|
for (const m of children) {
|
|
914
957
|
const name = m.discriminatorValue ?? this.namingStrategy.classToTableName(m.className);
|
|
915
|
-
meta.root.discriminatorMap[name] = m.
|
|
958
|
+
meta.root.discriminatorMap[name] = m.class;
|
|
916
959
|
}
|
|
917
960
|
}
|
|
918
|
-
meta.discriminatorValue = Object.entries(meta.root.discriminatorMap).find(([,
|
|
961
|
+
meta.discriminatorValue = Object.entries(meta.root.discriminatorMap).find(([, cls]) => cls === meta.class)?.[0];
|
|
919
962
|
if (!meta.root.properties[meta.root.discriminatorColumn]) {
|
|
920
963
|
this.createDiscriminatorProperty(meta.root);
|
|
921
964
|
}
|
|
@@ -924,28 +967,46 @@ export class MetadataDiscovery {
|
|
|
924
967
|
if (meta.root === meta) {
|
|
925
968
|
return;
|
|
926
969
|
}
|
|
927
|
-
let i = 1;
|
|
928
970
|
Object.values(meta.properties).forEach(prop => {
|
|
929
971
|
const newProp = { ...prop };
|
|
930
|
-
|
|
972
|
+
const rootProp = meta.root.properties[prop.name];
|
|
973
|
+
if (rootProp && (rootProp.type !== prop.type || (rootProp.fieldNames && prop.fieldNames && !compareArrays(rootProp.fieldNames, prop.fieldNames)))) {
|
|
931
974
|
const name = newProp.name;
|
|
932
975
|
this.initFieldName(newProp, newProp.object);
|
|
933
|
-
newProp.
|
|
976
|
+
newProp.renamedFrom = name;
|
|
977
|
+
newProp.name = `${name}_${meta._id}`;
|
|
934
978
|
meta.root.addProperty(newProp);
|
|
979
|
+
this.initFieldName(prop, prop.object);
|
|
980
|
+
// Track all field variants and map discriminator values to field names
|
|
981
|
+
if (!rootProp.stiFieldNames) {
|
|
982
|
+
this.initFieldName(rootProp, rootProp.object);
|
|
983
|
+
rootProp.stiFieldNames = [...rootProp.fieldNames];
|
|
984
|
+
rootProp.stiFieldNameMap = {};
|
|
985
|
+
// Find which discriminator owns the original fieldNames
|
|
986
|
+
for (const [discValue, childClass] of Object.entries(meta.root.discriminatorMap)) {
|
|
987
|
+
const childMeta = this.metadata.find(childClass);
|
|
988
|
+
if (childMeta?.properties[prop.name]?.fieldNames && compareArrays(childMeta.properties[prop.name].fieldNames, rootProp.fieldNames)) {
|
|
989
|
+
rootProp.stiFieldNameMap[discValue] = rootProp.fieldNames[0];
|
|
990
|
+
break;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
rootProp.stiFieldNameMap[meta.discriminatorValue] = prop.fieldNames[0];
|
|
995
|
+
rootProp.stiFieldNames.push(...prop.fieldNames);
|
|
935
996
|
newProp.nullable = true;
|
|
936
997
|
newProp.name = name;
|
|
937
998
|
newProp.hydrate = false;
|
|
938
999
|
newProp.inherited = true;
|
|
939
1000
|
return;
|
|
940
1001
|
}
|
|
941
|
-
if (prop.enum && prop.items &&
|
|
942
|
-
newProp.items = Utils.unique([...
|
|
1002
|
+
if (prop.enum && prop.items && rootProp?.items) {
|
|
1003
|
+
newProp.items = Utils.unique([...rootProp.items, ...prop.items]);
|
|
943
1004
|
}
|
|
944
1005
|
newProp.nullable = true;
|
|
945
|
-
newProp.inherited =
|
|
1006
|
+
newProp.inherited = !rootProp;
|
|
946
1007
|
meta.root.addProperty(newProp);
|
|
947
1008
|
});
|
|
948
|
-
meta.
|
|
1009
|
+
meta.tableName = meta.root.tableName;
|
|
949
1010
|
meta.root.indexes = Utils.unique([...meta.root.indexes, ...meta.indexes]);
|
|
950
1011
|
meta.root.uniques = Utils.unique([...meta.root.uniques, ...meta.uniques]);
|
|
951
1012
|
meta.root.checks = Utils.unique([...meta.root.checks, ...meta.checks]);
|
|
@@ -967,7 +1028,7 @@ export class MetadataDiscovery {
|
|
|
967
1028
|
}
|
|
968
1029
|
}
|
|
969
1030
|
initCheckConstraints(meta) {
|
|
970
|
-
const map =
|
|
1031
|
+
const map = meta.createColumnMappingObject();
|
|
971
1032
|
for (const check of meta.checks) {
|
|
972
1033
|
const columns = check.property ? meta.properties[check.property].fieldNames : [];
|
|
973
1034
|
check.name ??= this.namingStrategy.indexName(meta.tableName, columns, 'check');
|
|
@@ -977,7 +1038,7 @@ export class MetadataDiscovery {
|
|
|
977
1038
|
}
|
|
978
1039
|
if (this.platform.usesEnumCheckConstraints() && !meta.embeddable) {
|
|
979
1040
|
for (const prop of meta.props) {
|
|
980
|
-
if (prop.enum && !prop.nativeEnumName && prop.items?.every(item =>
|
|
1041
|
+
if (prop.enum && !prop.nativeEnumName && prop.items?.every(item => typeof item === 'string')) {
|
|
981
1042
|
this.initFieldName(prop);
|
|
982
1043
|
meta.checks.push({
|
|
983
1044
|
name: this.namingStrategy.indexName(meta.tableName, prop.fieldNames, 'check'),
|
|
@@ -1002,38 +1063,28 @@ export class MetadataDiscovery {
|
|
|
1002
1063
|
}
|
|
1003
1064
|
return;
|
|
1004
1065
|
}
|
|
1005
|
-
const map =
|
|
1066
|
+
const map = meta.createColumnMappingObject();
|
|
1006
1067
|
if (prop.generated instanceof Function) {
|
|
1007
1068
|
prop.generated = prop.generated(map);
|
|
1008
1069
|
}
|
|
1009
1070
|
}
|
|
1010
|
-
|
|
1011
|
-
return Object.values(meta.properties).reduce((o, prop) => {
|
|
1012
|
-
if (prop.fieldNames) {
|
|
1013
|
-
o[prop.name] = prop.fieldNames[0];
|
|
1014
|
-
}
|
|
1015
|
-
return o;
|
|
1016
|
-
}, {});
|
|
1017
|
-
}
|
|
1018
|
-
getDefaultVersionValue(prop) {
|
|
1071
|
+
getDefaultVersionValue(meta, prop) {
|
|
1019
1072
|
if (typeof prop.defaultRaw !== 'undefined') {
|
|
1020
1073
|
return prop.defaultRaw;
|
|
1021
1074
|
}
|
|
1022
|
-
/* v8 ignore next
|
|
1075
|
+
/* v8 ignore next */
|
|
1023
1076
|
if (prop.default != null) {
|
|
1024
1077
|
return '' + this.platform.quoteVersionValue(prop.default, prop);
|
|
1025
1078
|
}
|
|
1026
|
-
|
|
1079
|
+
this.initCustomType(meta, prop, true);
|
|
1080
|
+
const type = prop.customType?.runtimeType ?? prop.runtimeType ?? prop.type;
|
|
1081
|
+
if (type === 'Date') {
|
|
1027
1082
|
prop.length ??= this.platform.getDefaultVersionLength();
|
|
1028
1083
|
return this.platform.getCurrentTimestampSQL(prop.length);
|
|
1029
1084
|
}
|
|
1030
1085
|
return '1';
|
|
1031
1086
|
}
|
|
1032
1087
|
inferDefaultValue(meta, prop) {
|
|
1033
|
-
/* v8 ignore next 3 */
|
|
1034
|
-
if (!meta.class) {
|
|
1035
|
-
return;
|
|
1036
|
-
}
|
|
1037
1088
|
try {
|
|
1038
1089
|
// try to create two entity instances to detect the value is stable
|
|
1039
1090
|
const now = Date.now();
|
|
@@ -1061,12 +1112,12 @@ export class MetadataDiscovery {
|
|
|
1061
1112
|
return;
|
|
1062
1113
|
}
|
|
1063
1114
|
let val = prop.default;
|
|
1064
|
-
const raw =
|
|
1115
|
+
const raw = Raw.getKnownFragment(val);
|
|
1065
1116
|
if (raw) {
|
|
1066
1117
|
prop.defaultRaw = this.platform.formatQuery(raw.sql, raw.params);
|
|
1067
1118
|
return;
|
|
1068
1119
|
}
|
|
1069
|
-
if (
|
|
1120
|
+
if (Array.isArray(prop.default) && prop.customType) {
|
|
1070
1121
|
val = prop.customType.convertToDatabaseValue(prop.default, this.platform);
|
|
1071
1122
|
}
|
|
1072
1123
|
prop.defaultRaw = typeof val === 'string' ? `'${val}'` : '' + val;
|
|
@@ -1094,13 +1145,13 @@ export class MetadataDiscovery {
|
|
|
1094
1145
|
if (prop.version) {
|
|
1095
1146
|
this.initDefaultValue(prop);
|
|
1096
1147
|
meta.versionProperty = prop.name;
|
|
1097
|
-
prop.defaultRaw = this.getDefaultVersionValue(prop);
|
|
1148
|
+
prop.defaultRaw = this.getDefaultVersionValue(meta, prop);
|
|
1098
1149
|
}
|
|
1099
1150
|
if (prop.concurrencyCheck && !prop.primary) {
|
|
1100
1151
|
meta.concurrencyCheckKeys.add(prop.name);
|
|
1101
1152
|
}
|
|
1102
1153
|
}
|
|
1103
|
-
initCustomType(meta, prop) {
|
|
1154
|
+
initCustomType(meta, prop, simple = false, objectEmbeddable = false) {
|
|
1104
1155
|
// `prop.type` might be actually instance of custom type class
|
|
1105
1156
|
if (Type.isMappedType(prop.type) && !prop.customType) {
|
|
1106
1157
|
prop.customType = prop.type;
|
|
@@ -1108,41 +1159,61 @@ export class MetadataDiscovery {
|
|
|
1108
1159
|
}
|
|
1109
1160
|
// `prop.type` might also be custom type class (not instance), so `typeof MyType` will give us `function`, not `object`
|
|
1110
1161
|
if (typeof prop.type === 'function' && Type.isMappedType(prop.type.prototype) && !prop.customType) {
|
|
1111
|
-
|
|
1112
|
-
|
|
1162
|
+
// if the type is an ORM defined mapped type without `ensureComparable: true`,
|
|
1163
|
+
// we use just the type name, to have more performant hydration code
|
|
1164
|
+
const type = Utils.keys(t).find(type => {
|
|
1165
|
+
return !Type.getType(t[type]).ensureComparable(meta, prop) && prop.type === t[type];
|
|
1166
|
+
});
|
|
1167
|
+
if (type) {
|
|
1168
|
+
prop.type = type === 'datetime' ? 'Date' : type;
|
|
1169
|
+
}
|
|
1170
|
+
else {
|
|
1171
|
+
prop.customType = new prop.type();
|
|
1172
|
+
prop.type = prop.customType.constructor.name;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
if (simple) {
|
|
1176
|
+
return;
|
|
1113
1177
|
}
|
|
1114
1178
|
if (!prop.customType && ['json', 'jsonb'].includes(prop.type?.toLowerCase())) {
|
|
1115
|
-
prop.customType = new
|
|
1179
|
+
prop.customType = new t.json();
|
|
1116
1180
|
}
|
|
1117
1181
|
if (prop.kind === ReferenceKind.SCALAR && !prop.customType && prop.columnTypes && ['json', 'jsonb'].includes(prop.columnTypes[0])) {
|
|
1118
|
-
prop.customType = new
|
|
1182
|
+
prop.customType = new t.json();
|
|
1183
|
+
}
|
|
1184
|
+
if (prop.kind === ReferenceKind.EMBEDDED && !prop.customType && (prop.object || prop.array)) {
|
|
1185
|
+
prop.customType = new t.json();
|
|
1119
1186
|
}
|
|
1120
1187
|
if (!prop.customType && prop.array && prop.items) {
|
|
1121
|
-
prop.customType = new
|
|
1188
|
+
prop.customType = new t.enumArray(`${meta.className}.${prop.name}`, prop.items);
|
|
1189
|
+
}
|
|
1190
|
+
const isArray = prop.type?.toLowerCase() === 'array' || prop.type?.toString().endsWith('[]');
|
|
1191
|
+
if (objectEmbeddable && !prop.customType && isArray) {
|
|
1192
|
+
prop.customType = new t.json();
|
|
1122
1193
|
}
|
|
1123
1194
|
// for number arrays we make sure to convert the items to numbers
|
|
1124
1195
|
if (!prop.customType && prop.type === 'number[]') {
|
|
1125
|
-
prop.customType = new
|
|
1196
|
+
prop.customType = new t.array(i => +i);
|
|
1126
1197
|
}
|
|
1127
1198
|
// `string[]` can be returned via ts-morph, while reflect metadata will give us just `array`
|
|
1128
|
-
if (!prop.customType &&
|
|
1129
|
-
prop.customType = new
|
|
1199
|
+
if (!prop.customType && isArray) {
|
|
1200
|
+
prop.customType = new t.array();
|
|
1130
1201
|
}
|
|
1131
1202
|
if (!prop.customType && prop.type?.toLowerCase() === 'buffer') {
|
|
1132
|
-
prop.customType = new
|
|
1203
|
+
prop.customType = new t.blob();
|
|
1133
1204
|
}
|
|
1134
1205
|
if (!prop.customType && prop.type?.toLowerCase() === 'uint8array') {
|
|
1135
|
-
prop.customType = new
|
|
1206
|
+
prop.customType = new t.uint8array();
|
|
1136
1207
|
}
|
|
1137
1208
|
const mappedType = this.getMappedType(prop);
|
|
1138
1209
|
if (prop.fieldNames?.length === 1 && !prop.customType) {
|
|
1139
|
-
[
|
|
1210
|
+
[t.bigint, t.double, t.decimal, t.interval, t.date]
|
|
1140
1211
|
.filter(type => mappedType instanceof type)
|
|
1141
|
-
.forEach(type => prop.customType = new type());
|
|
1212
|
+
.forEach((type) => prop.customType = new type());
|
|
1142
1213
|
}
|
|
1143
1214
|
if (prop.customType && !prop.columnTypes) {
|
|
1144
1215
|
const mappedType = this.getMappedType({ columnTypes: [prop.customType.getColumnType(prop, this.platform)] });
|
|
1145
|
-
if (prop.customType.compareAsType() === 'any' && ![
|
|
1216
|
+
if (prop.customType.compareAsType() === 'any' && ![t.json].some(t => prop.customType instanceof t)) {
|
|
1146
1217
|
prop.runtimeType ??= mappedType.runtimeType;
|
|
1147
1218
|
}
|
|
1148
1219
|
else {
|
|
@@ -1162,16 +1233,16 @@ export class MetadataDiscovery {
|
|
|
1162
1233
|
prop.columnTypes ??= [prop.customType.getColumnType(prop, this.platform)];
|
|
1163
1234
|
prop.hasConvertToJSValueSQL = !!prop.customType.convertToJSValueSQL && prop.customType.convertToJSValueSQL('', this.platform) !== '';
|
|
1164
1235
|
prop.hasConvertToDatabaseValueSQL = !!prop.customType.convertToDatabaseValueSQL && prop.customType.convertToDatabaseValueSQL('', this.platform) !== '';
|
|
1165
|
-
if (prop.customType instanceof
|
|
1236
|
+
if (prop.customType instanceof t.bigint && ['string', 'bigint', 'number'].includes(prop.runtimeType.toLowerCase())) {
|
|
1166
1237
|
prop.customType.mode = prop.runtimeType.toLowerCase();
|
|
1167
1238
|
}
|
|
1168
1239
|
}
|
|
1169
|
-
if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !
|
|
1240
|
+
if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !isArray) {
|
|
1170
1241
|
prop.type = prop.customType.name;
|
|
1171
1242
|
}
|
|
1172
|
-
if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) &&
|
|
1243
|
+
if (!prop.customType && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && prop.targetMeta.compositePK) {
|
|
1173
1244
|
prop.customTypes = [];
|
|
1174
|
-
for (const pk of
|
|
1245
|
+
for (const pk of prop.targetMeta.getPrimaryProps()) {
|
|
1175
1246
|
if (pk.customType) {
|
|
1176
1247
|
prop.customTypes.push(pk.customType);
|
|
1177
1248
|
prop.hasConvertToJSValueSQL ||= !!pk.customType.convertToJSValueSQL && pk.customType.convertToJSValueSQL('', this.platform) !== '';
|
|
@@ -1183,7 +1254,7 @@ export class MetadataDiscovery {
|
|
|
1183
1254
|
}
|
|
1184
1255
|
}
|
|
1185
1256
|
}
|
|
1186
|
-
if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof
|
|
1257
|
+
if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof t.unknown)) {
|
|
1187
1258
|
if (!prop.columnTypes && prop.nativeEnumName && meta.schema !== this.platform.getDefaultSchemaName() && meta.schema && !prop.nativeEnumName.includes('.')) {
|
|
1188
1259
|
prop.columnTypes = [`${meta.schema}.${prop.nativeEnumName}`];
|
|
1189
1260
|
}
|
|
@@ -1201,19 +1272,28 @@ export class MetadataDiscovery {
|
|
|
1201
1272
|
if (prop.kind === ReferenceKind.SCALAR) {
|
|
1202
1273
|
return;
|
|
1203
1274
|
}
|
|
1204
|
-
|
|
1205
|
-
prop.
|
|
1275
|
+
// 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
|
|
1276
|
+
const meta2 = this.metadata.find(prop.target) ?? this.metadata.getByClassName(prop.type);
|
|
1277
|
+
// If targetKey is specified, use that property instead of PKs for referencedPKs
|
|
1278
|
+
prop.referencedPKs = prop.targetKey ? [prop.targetKey] : meta2.primaryKeys;
|
|
1206
1279
|
prop.targetMeta = meta2;
|
|
1280
|
+
if (meta2.view) {
|
|
1281
|
+
prop.createForeignKeyConstraint = false;
|
|
1282
|
+
}
|
|
1207
1283
|
if (!prop.formula && prop.persist === false && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && !prop.embedded) {
|
|
1208
|
-
prop.formula =
|
|
1284
|
+
prop.formula = table => `${table}.${this.platform.quoteIdentifier(prop.fieldNames[0])}`;
|
|
1209
1285
|
}
|
|
1210
1286
|
}
|
|
1211
1287
|
initColumnType(prop) {
|
|
1212
1288
|
this.initUnsigned(prop);
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
prop.
|
|
1216
|
-
|
|
1289
|
+
// Get the target properties for FK relations - use targetKey property if specified, otherwise PKs
|
|
1290
|
+
const targetProps = prop.targetMeta
|
|
1291
|
+
? (prop.targetKey ? [prop.targetMeta.properties[prop.targetKey]] : prop.targetMeta.getPrimaryProps())
|
|
1292
|
+
: [];
|
|
1293
|
+
targetProps.map(targetProp => {
|
|
1294
|
+
prop.length ??= targetProp.length;
|
|
1295
|
+
prop.precision ??= targetProp.precision;
|
|
1296
|
+
prop.scale ??= targetProp.scale;
|
|
1217
1297
|
});
|
|
1218
1298
|
if (prop.kind === ReferenceKind.SCALAR && (prop.type == null || prop.type === 'object') && prop.columnTypes?.[0]) {
|
|
1219
1299
|
delete prop.type;
|
|
@@ -1226,8 +1306,7 @@ export class MetadataDiscovery {
|
|
|
1226
1306
|
if (prop.kind === ReferenceKind.SCALAR) {
|
|
1227
1307
|
const mappedType = this.getMappedType(prop);
|
|
1228
1308
|
const SCALAR_TYPES = ['string', 'number', 'boolean', 'bigint', 'Date', 'Buffer', 'RegExp', 'any', 'unknown'];
|
|
1229
|
-
if (mappedType instanceof
|
|
1230
|
-
&& !prop.columnTypes
|
|
1309
|
+
if (mappedType instanceof t.unknown
|
|
1231
1310
|
// it could be a runtime type from reflect-metadata
|
|
1232
1311
|
&& !SCALAR_TYPES.includes(prop.type)
|
|
1233
1312
|
// or it might be inferred via ts-morph to some generic type alias
|
|
@@ -1240,23 +1319,28 @@ export class MetadataDiscovery {
|
|
|
1240
1319
|
}
|
|
1241
1320
|
return;
|
|
1242
1321
|
}
|
|
1243
|
-
|
|
1322
|
+
/* v8 ignore next */
|
|
1323
|
+
if (prop.kind === ReferenceKind.EMBEDDED && prop.object) {
|
|
1244
1324
|
prop.columnTypes = [this.platform.getJsonDeclarationSQL()];
|
|
1245
1325
|
return;
|
|
1246
1326
|
}
|
|
1247
|
-
const targetMeta =
|
|
1327
|
+
const targetMeta = prop.targetMeta;
|
|
1248
1328
|
prop.columnTypes = [];
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1329
|
+
// Use targetKey property if specified, otherwise use primary key properties
|
|
1330
|
+
const referencedProps = prop.targetKey
|
|
1331
|
+
? [targetMeta.properties[prop.targetKey]]
|
|
1332
|
+
: targetMeta.getPrimaryProps();
|
|
1333
|
+
for (const referencedProp of referencedProps) {
|
|
1334
|
+
this.initCustomType(targetMeta, referencedProp);
|
|
1335
|
+
this.initColumnType(referencedProp);
|
|
1336
|
+
const mappedType = this.getMappedType(referencedProp);
|
|
1337
|
+
let columnTypes = referencedProp.columnTypes;
|
|
1338
|
+
if (referencedProp.autoincrement) {
|
|
1339
|
+
columnTypes = [mappedType.getColumnType({ ...referencedProp, autoincrement: false }, this.platform)];
|
|
1256
1340
|
}
|
|
1257
1341
|
prop.columnTypes.push(...columnTypes);
|
|
1258
|
-
if (!targetMeta.compositePK) {
|
|
1259
|
-
prop.customType =
|
|
1342
|
+
if (!targetMeta.compositePK || prop.targetKey) {
|
|
1343
|
+
prop.customType = referencedProp.customType;
|
|
1260
1344
|
}
|
|
1261
1345
|
}
|
|
1262
1346
|
}
|
|
@@ -1270,7 +1354,7 @@ export class MetadataDiscovery {
|
|
|
1270
1354
|
t = 'enum';
|
|
1271
1355
|
}
|
|
1272
1356
|
else if (prop.enum) {
|
|
1273
|
-
t = prop.items?.every(item =>
|
|
1357
|
+
t = prop.items?.every(item => typeof item === 'string') ? 'enum' : 'tinyint';
|
|
1274
1358
|
}
|
|
1275
1359
|
if (t === 'Date') {
|
|
1276
1360
|
t = 'datetime';
|
|
@@ -1294,7 +1378,7 @@ export class MetadataDiscovery {
|
|
|
1294
1378
|
return;
|
|
1295
1379
|
}
|
|
1296
1380
|
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
1297
|
-
const meta2 =
|
|
1381
|
+
const meta2 = prop.targetMeta;
|
|
1298
1382
|
prop.unsigned = meta2.getPrimaryProps().some(pk => {
|
|
1299
1383
|
this.initUnsigned(pk);
|
|
1300
1384
|
return pk.unsigned;
|
|
@@ -1309,30 +1393,6 @@ export class MetadataDiscovery {
|
|
|
1309
1393
|
prop.index ??= true;
|
|
1310
1394
|
}
|
|
1311
1395
|
}
|
|
1312
|
-
async getEntityClassOrSchema(path, name) {
|
|
1313
|
-
const exports = await Utils.dynamicImport(path);
|
|
1314
|
-
const targets = Object.values(exports)
|
|
1315
|
-
.filter(item => item instanceof EntitySchema || (item instanceof Function && MetadataStorage.isKnownEntity(item.name)));
|
|
1316
|
-
// ignore class implementations that are linked from an EntitySchema
|
|
1317
|
-
for (const item of targets) {
|
|
1318
|
-
if (item instanceof EntitySchema) {
|
|
1319
|
-
targets.forEach((item2, idx) => {
|
|
1320
|
-
if (item.meta.class === item2) {
|
|
1321
|
-
targets.splice(idx, 1);
|
|
1322
|
-
}
|
|
1323
|
-
});
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
if (targets.length > 0) {
|
|
1327
|
-
return targets;
|
|
1328
|
-
}
|
|
1329
|
-
const target = exports.default ?? exports[name];
|
|
1330
|
-
/* v8 ignore next 3 */
|
|
1331
|
-
if (!target) {
|
|
1332
|
-
throw MetadataError.entityNotFound(name, path.replace(this.config.get('baseDir'), '.'));
|
|
1333
|
-
}
|
|
1334
|
-
return [target];
|
|
1335
|
-
}
|
|
1336
1396
|
shouldForceConstructorUsage(meta) {
|
|
1337
1397
|
const forceConstructor = this.config.get('forceEntityConstructor');
|
|
1338
1398
|
if (Array.isArray(forceConstructor)) {
|