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