@mikro-orm/core 7.0.0-rc.2 → 7.0.0-rc.3
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 +2 -1
- package/EntityManager.js +106 -42
- package/MikroORM.js +4 -4
- package/cache/FileCacheAdapter.js +1 -3
- package/connections/Connection.js +16 -3
- package/drivers/DatabaseDriver.js +26 -8
- package/drivers/IDatabaseDriver.d.ts +43 -0
- package/entity/Collection.js +43 -17
- package/entity/EntityAssigner.js +23 -11
- package/entity/EntityFactory.js +32 -12
- package/entity/EntityHelper.js +25 -16
- package/entity/EntityLoader.js +55 -22
- package/entity/Reference.d.ts +1 -1
- package/entity/Reference.js +37 -8
- package/entity/WrappedEntity.js +5 -1
- package/entity/defineEntity.d.ts +24 -12
- package/entity/utils.js +28 -26
- package/entity/validators.js +2 -1
- package/enums.js +12 -17
- package/errors.js +18 -8
- package/events/EventManager.js +1 -1
- package/exceptions.js +7 -2
- package/hydration/ObjectHydrator.js +27 -13
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/logging/DefaultLogger.js +3 -5
- package/logging/colors.js +3 -6
- package/metadata/EntitySchema.d.ts +2 -2
- package/metadata/EntitySchema.js +12 -2
- package/metadata/MetadataDiscovery.js +106 -47
- package/metadata/MetadataProvider.js +26 -1
- package/metadata/MetadataStorage.js +2 -4
- package/metadata/MetadataValidator.js +20 -5
- package/metadata/types.d.ts +2 -2
- package/naming-strategy/AbstractNamingStrategy.js +5 -2
- package/not-supported.js +5 -1
- package/package.json +38 -38
- package/platforms/Platform.d.ts +1 -0
- package/platforms/Platform.js +49 -23
- package/serialization/EntitySerializer.js +7 -3
- package/serialization/SerializationContext.js +1 -1
- package/typings.d.ts +23 -23
- package/typings.js +9 -9
- package/unit-of-work/ChangeSet.js +4 -4
- package/unit-of-work/ChangeSetComputer.js +8 -6
- package/unit-of-work/ChangeSetPersister.js +13 -8
- package/unit-of-work/CommitOrderCalculator.js +4 -2
- package/unit-of-work/UnitOfWork.d.ts +7 -1
- package/unit-of-work/UnitOfWork.js +51 -22
- package/utils/AbstractMigrator.d.ts +1 -1
- package/utils/AbstractMigrator.js +3 -5
- package/utils/AbstractSchemaGenerator.js +2 -1
- package/utils/AsyncContext.js +1 -1
- package/utils/Configuration.js +8 -4
- package/utils/Cursor.js +4 -2
- package/utils/DataloaderUtils.js +15 -12
- package/utils/EntityComparator.js +51 -43
- package/utils/QueryHelper.js +38 -26
- package/utils/RawQueryFragment.js +3 -2
- package/utils/TransactionManager.js +2 -1
- package/utils/Utils.d.ts +1 -1
- package/utils/Utils.js +36 -30
- package/utils/env-vars.js +6 -5
- package/utils/fs-utils.js +2 -5
- package/utils/upsert-utils.js +6 -3
|
@@ -33,7 +33,9 @@ export class MetadataDiscovery {
|
|
|
33
33
|
async discover(preferTs = true) {
|
|
34
34
|
this.discovered.length = 0;
|
|
35
35
|
const startTime = Date.now();
|
|
36
|
-
const suffix = this.metadataProvider.constructor === MetadataProvider
|
|
36
|
+
const suffix = this.metadataProvider.constructor === MetadataProvider
|
|
37
|
+
? ''
|
|
38
|
+
: `, using ${colors.cyan(this.metadataProvider.constructor.name)}`;
|
|
37
39
|
this.logger.log('discovery', `ORM entity discovery started${suffix}`);
|
|
38
40
|
await this.findEntities(preferTs);
|
|
39
41
|
for (const meta of this.discovered) {
|
|
@@ -51,7 +53,9 @@ export class MetadataDiscovery {
|
|
|
51
53
|
discoverSync() {
|
|
52
54
|
this.discovered.length = 0;
|
|
53
55
|
const startTime = Date.now();
|
|
54
|
-
const suffix = this.metadataProvider.constructor === MetadataProvider
|
|
56
|
+
const suffix = this.metadataProvider.constructor === MetadataProvider
|
|
57
|
+
? ''
|
|
58
|
+
: `, using ${colors.cyan(this.metadataProvider.constructor.name)}`;
|
|
55
59
|
this.logger.log('discovery', `ORM entity discovery started${suffix} in sync mode`);
|
|
56
60
|
const refs = this.config.get('entities');
|
|
57
61
|
this.discoverReferences(refs);
|
|
@@ -91,6 +95,7 @@ export class MetadataDiscovery {
|
|
|
91
95
|
}
|
|
92
96
|
const desc = Object.getOwnPropertyDescriptor(meta.prototype, prop.name);
|
|
93
97
|
if (desc?.get || desc?.set) {
|
|
98
|
+
this.initRelation(prop);
|
|
94
99
|
this.initFieldName(prop);
|
|
95
100
|
const accessor = prop.name;
|
|
96
101
|
prop.name = typeof prop.accessor === 'string' ? prop.accessor : prop.name;
|
|
@@ -106,7 +111,10 @@ export class MetadataDiscovery {
|
|
|
106
111
|
}
|
|
107
112
|
else {
|
|
108
113
|
const name = prop.name;
|
|
109
|
-
prop.
|
|
114
|
+
if (prop.kind === ReferenceKind.SCALAR || prop.kind === ReferenceKind.EMBEDDED) {
|
|
115
|
+
prop.name = prop.accessor;
|
|
116
|
+
}
|
|
117
|
+
this.initRelation(prop);
|
|
110
118
|
this.initFieldName(prop);
|
|
111
119
|
prop.serializedName ??= prop.accessor;
|
|
112
120
|
prop.name = name;
|
|
@@ -123,7 +131,7 @@ export class MetadataDiscovery {
|
|
|
123
131
|
// ignore base entities (not annotated with @Entity)
|
|
124
132
|
const filtered = discovered.filter(meta => meta.root.name);
|
|
125
133
|
// sort so we discover entities first to get around issues with nested embeddables
|
|
126
|
-
filtered.sort((a, b) => !a.embeddable === !b.embeddable ? 0 :
|
|
134
|
+
filtered.sort((a, b) => (!a.embeddable === !b.embeddable ? 0 : a.embeddable ? 1 : -1));
|
|
127
135
|
filtered.forEach(meta => this.initSingleTableInheritance(meta, filtered));
|
|
128
136
|
filtered.forEach(meta => this.initTPTRelationships(meta, filtered));
|
|
129
137
|
filtered.forEach(meta => this.defineBaseEntityProperties(meta));
|
|
@@ -172,7 +180,7 @@ export class MetadataDiscovery {
|
|
|
172
180
|
}
|
|
173
181
|
async findEntities(preferTs) {
|
|
174
182
|
const { entities, entitiesTs, baseDir } = this.config.getAll();
|
|
175
|
-
const targets =
|
|
183
|
+
const targets = preferTs && entitiesTs.length > 0 ? entitiesTs : entities;
|
|
176
184
|
const processed = [];
|
|
177
185
|
const paths = [];
|
|
178
186
|
for (const entity of targets) {
|
|
@@ -185,7 +193,7 @@ export class MetadataDiscovery {
|
|
|
185
193
|
}
|
|
186
194
|
if (paths.length > 0) {
|
|
187
195
|
const { discoverEntities } = await import('@mikro-orm/core/file-discovery');
|
|
188
|
-
processed.push(...await discoverEntities(paths, { baseDir }));
|
|
196
|
+
processed.push(...(await discoverEntities(paths, { baseDir })));
|
|
189
197
|
}
|
|
190
198
|
return this.discoverReferences(processed);
|
|
191
199
|
}
|
|
@@ -206,10 +214,10 @@ export class MetadataDiscovery {
|
|
|
206
214
|
}
|
|
207
215
|
}
|
|
208
216
|
if (prop.kind !== ReferenceKind.SCALAR) {
|
|
209
|
-
const target = typeof prop.entity === 'function' && !prop.entity.prototype
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
217
|
+
const target = typeof prop.entity === 'function' && !prop.entity.prototype ? prop.entity() : prop.type;
|
|
218
|
+
if (!unwrap(prop.type)
|
|
219
|
+
.split(/ ?\| ?/)
|
|
220
|
+
.every(type => this.discovered.find(m => m.className === type))) {
|
|
213
221
|
missing.push(...Utils.asArray(target));
|
|
214
222
|
}
|
|
215
223
|
}
|
|
@@ -301,7 +309,8 @@ export class MetadataDiscovery {
|
|
|
301
309
|
}
|
|
302
310
|
getRootEntity(meta) {
|
|
303
311
|
const base = meta.extends && this.metadata.find(meta.extends);
|
|
304
|
-
if (!base || base === meta) {
|
|
312
|
+
if (!base || base === meta) {
|
|
313
|
+
// make sure we do not fall into infinite loop
|
|
305
314
|
return meta;
|
|
306
315
|
}
|
|
307
316
|
const root = this.getRootEntity(base);
|
|
@@ -361,7 +370,10 @@ export class MetadataDiscovery {
|
|
|
361
370
|
initOwnColumns(meta) {
|
|
362
371
|
meta.sync();
|
|
363
372
|
for (const prop of meta.props) {
|
|
364
|
-
if (!prop.joinColumns ||
|
|
373
|
+
if (!prop.joinColumns ||
|
|
374
|
+
!prop.columnTypes ||
|
|
375
|
+
prop.ownColumns ||
|
|
376
|
+
![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
365
377
|
continue;
|
|
366
378
|
}
|
|
367
379
|
// For polymorphic relations, ownColumns should include all fieldNames
|
|
@@ -549,7 +561,8 @@ export class MetadataDiscovery {
|
|
|
549
561
|
this.initRelation(prop);
|
|
550
562
|
}
|
|
551
563
|
this.initOwnColumns(meta);
|
|
552
|
-
meta.simplePK =
|
|
564
|
+
meta.simplePK =
|
|
565
|
+
pks.length === 1 && pks[0].kind === ReferenceKind.SCALAR && !pks[0].customType && pks[0].runtimeType !== 'Date';
|
|
553
566
|
meta.serializedPrimaryKey ??= meta.props.find(prop => prop.serializedPrimaryKey)?.name;
|
|
554
567
|
if (meta.serializedPrimaryKey && meta.serializedPrimaryKey !== meta.primaryKeys[0]) {
|
|
555
568
|
meta.properties[meta.serializedPrimaryKey].persist ??= false;
|
|
@@ -681,7 +694,8 @@ export class MetadataDiscovery {
|
|
|
681
694
|
pivotMeta2.compositePK = true;
|
|
682
695
|
}
|
|
683
696
|
// handle self-referenced m:n with same default field names
|
|
684
|
-
if (meta.className === targetType &&
|
|
697
|
+
if (meta.className === targetType &&
|
|
698
|
+
prop.joinColumns.every((joinColumn, idx) => joinColumn === prop.inverseJoinColumns[idx])) {
|
|
685
699
|
// use tableName only when explicitly provided by user, otherwise use className for backwards compatibility
|
|
686
700
|
const baseName = this.isExplicitTableName(meta) ? meta.tableName : meta.className;
|
|
687
701
|
prop.joinColumns = prop.referencedColumnNames.map(name => this.namingStrategy.joinKeyColumnName(baseName + '_1', name, meta.compositePK));
|
|
@@ -771,7 +785,9 @@ export class MetadataDiscovery {
|
|
|
771
785
|
if (isCompositePK) {
|
|
772
786
|
// Create separate properties for each PK column (nullable for other entity types)
|
|
773
787
|
for (let i = 0; i < prop.joinColumns.length; i++) {
|
|
774
|
-
pivotMeta.properties[prop.joinColumns[i]] = this.createPivotScalarProperty(prop.joinColumns[i], [
|
|
788
|
+
pivotMeta.properties[prop.joinColumns[i]] = this.createPivotScalarProperty(prop.joinColumns[i], [
|
|
789
|
+
columnTypes[i],
|
|
790
|
+
]);
|
|
775
791
|
}
|
|
776
792
|
// Virtual property combining all columns (for compatibility)
|
|
777
793
|
pivotMeta.properties[prop.discriminator] = this.createPivotScalarProperty(prop.discriminator, columnTypes, [...prop.joinColumns], { type: meta.className, persist: false });
|
|
@@ -911,7 +927,8 @@ export class MetadataDiscovery {
|
|
|
911
927
|
}
|
|
912
928
|
defineBaseEntityProperties(meta) {
|
|
913
929
|
const base = meta.extends && this.metadata.get(meta.extends);
|
|
914
|
-
if (!base || base === meta) {
|
|
930
|
+
if (!base || base === meta) {
|
|
931
|
+
// make sure we do not fall into infinite loop
|
|
915
932
|
return 0;
|
|
916
933
|
}
|
|
917
934
|
let order = this.defineBaseEntityProperties(base);
|
|
@@ -923,10 +940,12 @@ export class MetadataDiscovery {
|
|
|
923
940
|
meta.properties[prop.name] = prop;
|
|
924
941
|
}
|
|
925
942
|
});
|
|
926
|
-
ownProps.forEach(prop => meta.properties[prop.name] = prop);
|
|
943
|
+
ownProps.forEach(prop => (meta.properties[prop.name] = prop));
|
|
927
944
|
meta.filters = { ...base.filters, ...meta.filters };
|
|
928
945
|
if (!meta.discriminatorValue) {
|
|
929
|
-
Object.values(base.properties)
|
|
946
|
+
Object.values(base.properties)
|
|
947
|
+
.filter(prop => !old.includes(prop.name))
|
|
948
|
+
.forEach(prop => {
|
|
930
949
|
meta.properties[prop.name] = { ...prop };
|
|
931
950
|
meta.propertyOrder.set(prop.name, (order += 0.01));
|
|
932
951
|
});
|
|
@@ -934,7 +953,9 @@ export class MetadataDiscovery {
|
|
|
934
953
|
meta.indexes = Utils.unique([...base.indexes, ...meta.indexes]);
|
|
935
954
|
meta.uniques = Utils.unique([...base.uniques, ...meta.uniques]);
|
|
936
955
|
meta.checks = Utils.unique([...base.checks, ...meta.checks]);
|
|
937
|
-
const pks = Object.values(meta.properties)
|
|
956
|
+
const pks = Object.values(meta.properties)
|
|
957
|
+
.filter(p => p.primary)
|
|
958
|
+
.map(p => p.name);
|
|
938
959
|
if (pks.length > 0 && meta.primaryKeys.length === 0) {
|
|
939
960
|
meta.primaryKeys = pks;
|
|
940
961
|
}
|
|
@@ -967,7 +988,7 @@ export class MetadataDiscovery {
|
|
|
967
988
|
properties[prop.name].runtimeType = 'any';
|
|
968
989
|
return properties[prop.name];
|
|
969
990
|
}
|
|
970
|
-
return properties[prop.name] = prop;
|
|
991
|
+
return (properties[prop.name] = prop);
|
|
971
992
|
});
|
|
972
993
|
};
|
|
973
994
|
const processExtensions = (meta) => {
|
|
@@ -985,7 +1006,10 @@ export class MetadataDiscovery {
|
|
|
985
1006
|
inlineProperties(meta);
|
|
986
1007
|
processExtensions(meta);
|
|
987
1008
|
});
|
|
988
|
-
const name = polymorphs
|
|
1009
|
+
const name = polymorphs
|
|
1010
|
+
.map(t => t.className)
|
|
1011
|
+
.sort()
|
|
1012
|
+
.join(' | ');
|
|
989
1013
|
embeddable = new EntityMetadata({
|
|
990
1014
|
name,
|
|
991
1015
|
className: name,
|
|
@@ -997,7 +1021,7 @@ export class MetadataDiscovery {
|
|
|
997
1021
|
});
|
|
998
1022
|
embeddable.sync();
|
|
999
1023
|
discovered.push(embeddable);
|
|
1000
|
-
polymorphs.forEach(meta => meta.root = embeddable);
|
|
1024
|
+
polymorphs.forEach(meta => (meta.root = embeddable));
|
|
1001
1025
|
}
|
|
1002
1026
|
}
|
|
1003
1027
|
initPolymorphicRelation(meta, prop, discovered) {
|
|
@@ -1167,7 +1191,7 @@ export class MetadataDiscovery {
|
|
|
1167
1191
|
const map = meta.root.discriminatorMap;
|
|
1168
1192
|
Object.keys(map)
|
|
1169
1193
|
.filter(key => typeof map[key] === 'string')
|
|
1170
|
-
.forEach(key => map[key] = this.metadata.getByClassName(map[key]).class);
|
|
1194
|
+
.forEach(key => (map[key] = this.metadata.getByClassName(map[key]).class));
|
|
1171
1195
|
}
|
|
1172
1196
|
else {
|
|
1173
1197
|
meta.root.discriminatorMap = {};
|
|
@@ -1191,7 +1215,9 @@ export class MetadataDiscovery {
|
|
|
1191
1215
|
Object.values(meta.properties).forEach(prop => {
|
|
1192
1216
|
const newProp = { ...prop };
|
|
1193
1217
|
const rootProp = meta.root.properties[prop.name];
|
|
1194
|
-
if (rootProp &&
|
|
1218
|
+
if (rootProp &&
|
|
1219
|
+
(rootProp.type !== prop.type ||
|
|
1220
|
+
(rootProp.fieldNames && prop.fieldNames && !compareArrays(rootProp.fieldNames, prop.fieldNames)))) {
|
|
1195
1221
|
const name = newProp.name;
|
|
1196
1222
|
this.initFieldName(newProp, newProp.object);
|
|
1197
1223
|
newProp.renamedFrom = name;
|
|
@@ -1206,7 +1232,8 @@ export class MetadataDiscovery {
|
|
|
1206
1232
|
// Find which discriminator owns the original fieldNames
|
|
1207
1233
|
for (const [discValue, childClass] of Object.entries(meta.root.discriminatorMap)) {
|
|
1208
1234
|
const childMeta = this.metadata.find(childClass);
|
|
1209
|
-
if (childMeta?.properties[prop.name]?.fieldNames &&
|
|
1235
|
+
if (childMeta?.properties[prop.name]?.fieldNames &&
|
|
1236
|
+
compareArrays(childMeta.properties[prop.name].fieldNames, rootProp.fieldNames)) {
|
|
1210
1237
|
rootProp.stiFieldNameMap[discValue] = rootProp.fieldNames[0];
|
|
1211
1238
|
break;
|
|
1212
1239
|
}
|
|
@@ -1438,7 +1465,10 @@ export class MetadataDiscovery {
|
|
|
1438
1465
|
}
|
|
1439
1466
|
if (this.platform.usesEnumCheckConstraints() && !meta.embeddable) {
|
|
1440
1467
|
for (const prop of meta.props) {
|
|
1441
|
-
if (prop.enum &&
|
|
1468
|
+
if (prop.enum &&
|
|
1469
|
+
prop.persist !== false &&
|
|
1470
|
+
!prop.nativeEnumName &&
|
|
1471
|
+
prop.items?.every(item => typeof item === 'string')) {
|
|
1442
1472
|
this.initFieldName(prop);
|
|
1443
1473
|
meta.checks.push({
|
|
1444
1474
|
name: this.namingStrategy.indexName(meta.tableName, prop.fieldNames, 'check'),
|
|
@@ -1491,7 +1521,11 @@ export class MetadataDiscovery {
|
|
|
1491
1521
|
const entity1 = new meta.class();
|
|
1492
1522
|
const entity2 = new meta.class();
|
|
1493
1523
|
// we compare the two values by reference, this will discard things like `new Date()` or `Date.now()`
|
|
1494
|
-
if (this.config.get('discovery').inferDefaultValues &&
|
|
1524
|
+
if (this.config.get('discovery').inferDefaultValues &&
|
|
1525
|
+
prop.default === undefined &&
|
|
1526
|
+
entity1[prop.name] != null &&
|
|
1527
|
+
entity1[prop.name] === entity2[prop.name] &&
|
|
1528
|
+
entity1[prop.name] !== now) {
|
|
1495
1529
|
prop.default ??= entity1[prop.name];
|
|
1496
1530
|
}
|
|
1497
1531
|
// if the default value is null, infer nullability
|
|
@@ -1558,7 +1592,9 @@ export class MetadataDiscovery {
|
|
|
1558
1592
|
prop.type = prop.customType.constructor.name;
|
|
1559
1593
|
}
|
|
1560
1594
|
// `prop.type` might also be custom type class (not instance), so `typeof MyType` will give us `function`, not `object`
|
|
1561
|
-
if (typeof prop.type === 'function' &&
|
|
1595
|
+
if (typeof prop.type === 'function' &&
|
|
1596
|
+
Type.isMappedType(prop.type.prototype) &&
|
|
1597
|
+
!prop.customType) {
|
|
1562
1598
|
// if the type is an ORM defined mapped type without `ensureComparable: true`,
|
|
1563
1599
|
// we use just the type name, to have more performant hydration code
|
|
1564
1600
|
const type = Utils.keys(t).find(type => {
|
|
@@ -1578,7 +1614,10 @@ export class MetadataDiscovery {
|
|
|
1578
1614
|
if (!prop.customType && ['json', 'jsonb'].includes(prop.type?.toLowerCase())) {
|
|
1579
1615
|
prop.customType = new t.json();
|
|
1580
1616
|
}
|
|
1581
|
-
if (prop.kind === ReferenceKind.SCALAR &&
|
|
1617
|
+
if (prop.kind === ReferenceKind.SCALAR &&
|
|
1618
|
+
!prop.customType &&
|
|
1619
|
+
prop.columnTypes &&
|
|
1620
|
+
['json', 'jsonb'].includes(prop.columnTypes[0])) {
|
|
1582
1621
|
prop.customType = new t.json();
|
|
1583
1622
|
}
|
|
1584
1623
|
if (prop.kind === ReferenceKind.EMBEDDED && !prop.customType && (prop.object || prop.array)) {
|
|
@@ -1609,10 +1648,12 @@ export class MetadataDiscovery {
|
|
|
1609
1648
|
if (prop.fieldNames?.length === 1 && !prop.customType) {
|
|
1610
1649
|
[t.bigint, t.double, t.decimal, t.interval, t.date]
|
|
1611
1650
|
.filter(type => mappedType instanceof type)
|
|
1612
|
-
.forEach((type) => prop.customType = new type());
|
|
1651
|
+
.forEach((type) => (prop.customType = new type()));
|
|
1613
1652
|
}
|
|
1614
1653
|
if (prop.customType && !prop.columnTypes) {
|
|
1615
|
-
const mappedType = this.getMappedType({
|
|
1654
|
+
const mappedType = this.getMappedType({
|
|
1655
|
+
columnTypes: [prop.customType.getColumnType(prop, this.platform)],
|
|
1656
|
+
});
|
|
1616
1657
|
if (prop.customType.compareAsType() === 'any' && ![t.json].some(t => prop.customType instanceof t)) {
|
|
1617
1658
|
prop.runtimeType ??= mappedType.runtimeType;
|
|
1618
1659
|
}
|
|
@@ -1631,23 +1672,33 @@ export class MetadataDiscovery {
|
|
|
1631
1672
|
prop.customType.meta = meta;
|
|
1632
1673
|
prop.customType.prop = prop;
|
|
1633
1674
|
prop.columnTypes ??= [prop.customType.getColumnType(prop, this.platform)];
|
|
1634
|
-
prop.hasConvertToJSValueSQL =
|
|
1635
|
-
|
|
1636
|
-
|
|
1675
|
+
prop.hasConvertToJSValueSQL =
|
|
1676
|
+
!!prop.customType.convertToJSValueSQL && prop.customType.convertToJSValueSQL('', this.platform) !== '';
|
|
1677
|
+
prop.hasConvertToDatabaseValueSQL =
|
|
1678
|
+
!!prop.customType.convertToDatabaseValueSQL &&
|
|
1679
|
+
prop.customType.convertToDatabaseValueSQL('', this.platform) !== '';
|
|
1680
|
+
if (prop.customType instanceof t.bigint &&
|
|
1681
|
+
['string', 'bigint', 'number'].includes(prop.runtimeType.toLowerCase())) {
|
|
1637
1682
|
prop.customType.mode = prop.runtimeType.toLowerCase();
|
|
1638
1683
|
}
|
|
1639
1684
|
}
|
|
1640
1685
|
if (Type.isMappedType(prop.customType) && prop.kind === ReferenceKind.SCALAR && !isArray) {
|
|
1641
1686
|
prop.type = prop.customType.name;
|
|
1642
1687
|
}
|
|
1643
|
-
if (!prop.customType &&
|
|
1688
|
+
if (!prop.customType &&
|
|
1689
|
+
[ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) &&
|
|
1690
|
+
!prop.polymorphic &&
|
|
1691
|
+
prop.targetMeta.compositePK) {
|
|
1644
1692
|
prop.customTypes = [];
|
|
1645
1693
|
for (const pk of prop.targetMeta.getPrimaryProps()) {
|
|
1646
1694
|
if (pk.customType) {
|
|
1647
1695
|
prop.customTypes.push(pk.customType);
|
|
1648
|
-
prop.hasConvertToJSValueSQL ||=
|
|
1696
|
+
prop.hasConvertToJSValueSQL ||=
|
|
1697
|
+
!!pk.customType.convertToJSValueSQL && pk.customType.convertToJSValueSQL('', this.platform) !== '';
|
|
1649
1698
|
/* v8 ignore next */
|
|
1650
|
-
prop.hasConvertToDatabaseValueSQL ||=
|
|
1699
|
+
prop.hasConvertToDatabaseValueSQL ||=
|
|
1700
|
+
!!pk.customType.convertToDatabaseValueSQL &&
|
|
1701
|
+
pk.customType.convertToDatabaseValueSQL('', this.platform) !== '';
|
|
1651
1702
|
}
|
|
1652
1703
|
else {
|
|
1653
1704
|
prop.customTypes.push(undefined);
|
|
@@ -1655,7 +1706,11 @@ export class MetadataDiscovery {
|
|
|
1655
1706
|
}
|
|
1656
1707
|
}
|
|
1657
1708
|
if (prop.kind === ReferenceKind.SCALAR && !(mappedType instanceof t.unknown)) {
|
|
1658
|
-
if (!prop.columnTypes &&
|
|
1709
|
+
if (!prop.columnTypes &&
|
|
1710
|
+
prop.nativeEnumName &&
|
|
1711
|
+
meta.schema !== this.platform.getDefaultSchemaName() &&
|
|
1712
|
+
meta.schema &&
|
|
1713
|
+
!prop.nativeEnumName.includes('.')) {
|
|
1659
1714
|
prop.columnTypes = [`${meta.schema}.${prop.nativeEnumName}`];
|
|
1660
1715
|
}
|
|
1661
1716
|
else {
|
|
@@ -1682,7 +1737,10 @@ export class MetadataDiscovery {
|
|
|
1682
1737
|
}
|
|
1683
1738
|
// Auto-generate formula for persist: false relations, but only for single-column FKs
|
|
1684
1739
|
// Composite FK relations need standard JOIN conditions, not formula-based
|
|
1685
|
-
if (!prop.formula &&
|
|
1740
|
+
if (!prop.formula &&
|
|
1741
|
+
prop.persist === false &&
|
|
1742
|
+
[ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
|
|
1743
|
+
!prop.embedded) {
|
|
1686
1744
|
this.initFieldName(prop);
|
|
1687
1745
|
if (prop.fieldNames?.length === 1) {
|
|
1688
1746
|
prop.formula = table => `${table}.${this.platform.quoteIdentifier(prop.fieldNames[0])}`;
|
|
@@ -1693,7 +1751,9 @@ export class MetadataDiscovery {
|
|
|
1693
1751
|
this.initUnsigned(prop);
|
|
1694
1752
|
// Get the target properties for FK relations - use targetKey property if specified, otherwise PKs
|
|
1695
1753
|
const targetProps = prop.targetMeta
|
|
1696
|
-
?
|
|
1754
|
+
? prop.targetKey
|
|
1755
|
+
? [prop.targetMeta.properties[prop.targetKey]]
|
|
1756
|
+
: prop.targetMeta.getPrimaryProps()
|
|
1697
1757
|
: [];
|
|
1698
1758
|
targetProps.map(targetProp => {
|
|
1699
1759
|
prop.length ??= targetProp.length;
|
|
@@ -1711,11 +1771,11 @@ export class MetadataDiscovery {
|
|
|
1711
1771
|
if (prop.kind === ReferenceKind.SCALAR) {
|
|
1712
1772
|
const mappedType = this.getMappedType(prop);
|
|
1713
1773
|
const SCALAR_TYPES = ['string', 'number', 'boolean', 'bigint', 'Date', 'Buffer', 'RegExp', 'any', 'unknown'];
|
|
1714
|
-
if (mappedType instanceof t.unknown
|
|
1774
|
+
if (mappedType instanceof t.unknown &&
|
|
1715
1775
|
// it could be a runtime type from reflect-metadata
|
|
1716
|
-
|
|
1776
|
+
!SCALAR_TYPES.includes(prop.type) &&
|
|
1717
1777
|
// or it might be inferred via ts-morph to some generic type alias
|
|
1718
|
-
|
|
1778
|
+
!prop.type.match(/[<>:"';{}]/)) {
|
|
1719
1779
|
const type = prop.length != null && !prop.type.endsWith(`(${prop.length})`) ? `${prop.type}(${prop.length})` : prop.type;
|
|
1720
1780
|
prop.columnTypes = [type];
|
|
1721
1781
|
}
|
|
@@ -1732,9 +1792,7 @@ export class MetadataDiscovery {
|
|
|
1732
1792
|
const targetMeta = prop.targetMeta;
|
|
1733
1793
|
prop.columnTypes = [];
|
|
1734
1794
|
// Use targetKey property if specified, otherwise use primary key properties
|
|
1735
|
-
const referencedProps = prop.targetKey
|
|
1736
|
-
? [targetMeta.properties[prop.targetKey]]
|
|
1737
|
-
: targetMeta.getPrimaryProps();
|
|
1795
|
+
const referencedProps = prop.targetKey ? [targetMeta.properties[prop.targetKey]] : targetMeta.getPrimaryProps();
|
|
1738
1796
|
if (prop.polymorphic && prop.polymorphTargets) {
|
|
1739
1797
|
prop.columnTypes.push(this.platform.getVarcharTypeDeclarationSQL(prop));
|
|
1740
1798
|
}
|
|
@@ -1792,7 +1850,8 @@ export class MetadataDiscovery {
|
|
|
1792
1850
|
});
|
|
1793
1851
|
return;
|
|
1794
1852
|
}
|
|
1795
|
-
prop.unsigned ??=
|
|
1853
|
+
prop.unsigned ??=
|
|
1854
|
+
(prop.primary || prop.unsigned) && this.platform.isNumericProperty(prop) && this.platform.supportsUnsigned();
|
|
1796
1855
|
}
|
|
1797
1856
|
initIndexes(meta, prop) {
|
|
1798
1857
|
const hasIndex = meta.indexes.some(idx => idx.properties?.length === 1 && idx.properties[0] === prop.name);
|
|
@@ -13,7 +13,12 @@ export class MetadataProvider {
|
|
|
13
13
|
}
|
|
14
14
|
else if (prop.entity) {
|
|
15
15
|
const tmp = prop.entity();
|
|
16
|
-
prop.type = Array.isArray(tmp)
|
|
16
|
+
prop.type = Array.isArray(tmp)
|
|
17
|
+
? tmp
|
|
18
|
+
.map(t => Utils.className(t))
|
|
19
|
+
.sort()
|
|
20
|
+
.join(' | ')
|
|
21
|
+
: Utils.className(tmp);
|
|
17
22
|
prop.target = tmp instanceof EntitySchema ? tmp.meta.class : tmp;
|
|
18
23
|
}
|
|
19
24
|
else if (!prop.type && !((prop.enum || prop.array) && (prop.items?.length ?? 0) > 0)) {
|
|
@@ -29,7 +34,27 @@ export class MetadataProvider {
|
|
|
29
34
|
delete prop.items;
|
|
30
35
|
}
|
|
31
36
|
});
|
|
37
|
+
// Preserve function expressions from indexes/uniques — they can't survive JSON cache serialization
|
|
38
|
+
const expressionMap = new Map();
|
|
39
|
+
for (const arr of [meta.indexes, meta.uniques]) {
|
|
40
|
+
for (const idx of arr ?? []) {
|
|
41
|
+
if (typeof idx.expression === 'function' && idx.name) {
|
|
42
|
+
expressionMap.set(idx.name, idx.expression);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
32
46
|
Utils.mergeConfig(meta, cache);
|
|
47
|
+
// Restore function expressions that were lost during JSON serialization
|
|
48
|
+
if (expressionMap.size > 0) {
|
|
49
|
+
for (const arr of [meta.indexes, meta.uniques]) {
|
|
50
|
+
for (const idx of arr ?? []) {
|
|
51
|
+
const fn = idx.name && expressionMap.get(idx.name);
|
|
52
|
+
if (fn && typeof idx.expression !== 'function') {
|
|
53
|
+
idx.expression = fn;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
33
58
|
}
|
|
34
59
|
static useCache() {
|
|
35
60
|
return false;
|
|
@@ -9,7 +9,7 @@ function getGlobalStorage(namespace) {
|
|
|
9
9
|
return globalThis[key];
|
|
10
10
|
}
|
|
11
11
|
export class MetadataStorage {
|
|
12
|
-
static PATH_SYMBOL = Symbol('MetadataStorage.PATH_SYMBOL');
|
|
12
|
+
static PATH_SYMBOL = Symbol.for('@mikro-orm/core/MetadataStorage.PATH_SYMBOL');
|
|
13
13
|
static metadata = getGlobalStorage('metadata');
|
|
14
14
|
metadata = new Map();
|
|
15
15
|
idMap;
|
|
@@ -90,9 +90,7 @@ export class MetadataStorage {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
decorate(em) {
|
|
93
|
-
[...this.metadata.values()]
|
|
94
|
-
.filter(meta => meta.prototype)
|
|
95
|
-
.forEach(meta => EntityHelper.decorate(meta, em));
|
|
93
|
+
[...this.metadata.values()].filter(meta => meta.prototype).forEach(meta => EntityHelper.decorate(meta, em));
|
|
96
94
|
}
|
|
97
95
|
*[Symbol.iterator]() {
|
|
98
96
|
for (const meta of this.metadata.values()) {
|
|
@@ -63,7 +63,11 @@ export class MetadataValidator {
|
|
|
63
63
|
}
|
|
64
64
|
// Validate no mixing of STI and TPT in the same hierarchy
|
|
65
65
|
this.validateInheritanceStrategies(discovered);
|
|
66
|
-
const tableNames = discovered.filter(meta => !meta.abstract &&
|
|
66
|
+
const tableNames = discovered.filter(meta => !meta.abstract &&
|
|
67
|
+
!meta.embeddable &&
|
|
68
|
+
meta === meta.root &&
|
|
69
|
+
(meta.tableName || meta.collection) &&
|
|
70
|
+
meta.schema !== '*');
|
|
67
71
|
const duplicateTableNames = Utils.findDuplicates(tableNames.map(meta => {
|
|
68
72
|
const tableName = meta.tableName || meta.collection;
|
|
69
73
|
return (meta.schema ? '.' + meta.schema : '') + tableName;
|
|
@@ -88,7 +92,10 @@ export class MetadataValidator {
|
|
|
88
92
|
const pivotProps = new Map();
|
|
89
93
|
// check for not discovered entities
|
|
90
94
|
discovered.forEach(meta => Object.values(meta.properties).forEach(prop => {
|
|
91
|
-
if (prop.kind !== ReferenceKind.SCALAR &&
|
|
95
|
+
if (prop.kind !== ReferenceKind.SCALAR &&
|
|
96
|
+
!unwrap(prop.type)
|
|
97
|
+
.split(/ ?\| ?/)
|
|
98
|
+
.every(type => discovered.find(m => m.className === type))) {
|
|
92
99
|
throw MetadataError.fromUnknownEntity(prop.type, `${meta.className}.${prop.name}`);
|
|
93
100
|
}
|
|
94
101
|
if (prop.pivotEntity) {
|
|
@@ -122,7 +129,10 @@ export class MetadataValidator {
|
|
|
122
129
|
if (targetMeta.abstract && !targetMeta.root?.inheritanceType && !targetMeta.embeddable) {
|
|
123
130
|
throw MetadataError.targetIsAbstract(meta, prop);
|
|
124
131
|
}
|
|
125
|
-
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
|
|
132
|
+
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
|
|
133
|
+
prop.persist === false &&
|
|
134
|
+
targetMeta.compositePK &&
|
|
135
|
+
options.checkNonPersistentCompositeProps) {
|
|
126
136
|
throw MetadataError.nonPersistentCompositeProp(meta, prop);
|
|
127
137
|
}
|
|
128
138
|
this.validateTargetKey(meta, prop, targetMeta);
|
|
@@ -248,7 +258,9 @@ export class MetadataValidator {
|
|
|
248
258
|
// has correct `mappedBy` reference type
|
|
249
259
|
// For polymorphic relations, check if this entity is one of the polymorphic targets
|
|
250
260
|
const isValidPolymorphicInverse = owner.polymorphic && owner.polymorphTargets?.some(target => target.class === meta.root.class);
|
|
251
|
-
if (!isValidPolymorphicInverse &&
|
|
261
|
+
if (!isValidPolymorphicInverse &&
|
|
262
|
+
owner.type !== meta.className &&
|
|
263
|
+
owner.targetMeta?.root.class !== meta.root.class) {
|
|
252
264
|
throw MetadataError.fromWrongReference(meta, prop, 'mappedBy', owner);
|
|
253
265
|
}
|
|
254
266
|
// owning side is not defined as inverse
|
|
@@ -280,7 +292,10 @@ export class MetadataValidator {
|
|
|
280
292
|
}
|
|
281
293
|
validateDuplicateFieldNames(meta, options) {
|
|
282
294
|
const candidates = Object.values(meta.properties)
|
|
283
|
-
.filter(prop => prop.persist !== false &&
|
|
295
|
+
.filter(prop => prop.persist !== false &&
|
|
296
|
+
!prop.inherited &&
|
|
297
|
+
prop.fieldNames?.length === 1 &&
|
|
298
|
+
(prop.kind !== ReferenceKind.EMBEDDED || prop.object))
|
|
284
299
|
.map(prop => prop.fieldNames[0]);
|
|
285
300
|
const duplicates = Utils.findDuplicates(candidates);
|
|
286
301
|
if (duplicates.length > 0 && options.checkDuplicateFieldNames) {
|
package/metadata/types.d.ts
CHANGED
|
@@ -526,7 +526,7 @@ export interface IndexColumnOptions {
|
|
|
526
526
|
}
|
|
527
527
|
interface BaseOptions<T, H extends string> {
|
|
528
528
|
name?: string;
|
|
529
|
-
properties?:
|
|
529
|
+
properties?: T extends EntityClass<infer P> ? Properties<P, H> : Properties<T, H>;
|
|
530
530
|
options?: Dictionary;
|
|
531
531
|
expression?: string | (T extends EntityClass<infer P> ? IndexCallback<P> : IndexCallback<T>);
|
|
532
532
|
/**
|
|
@@ -540,7 +540,7 @@ interface BaseOptions<T, H extends string> {
|
|
|
540
540
|
* Columns to include in the index but not as part of the key (PostgreSQL, MSSQL).
|
|
541
541
|
* These columns are stored in the leaf level of the index but not used for searching.
|
|
542
542
|
*/
|
|
543
|
-
include?:
|
|
543
|
+
include?: T extends EntityClass<infer P> ? Properties<P, H> : Properties<T, H>;
|
|
544
544
|
/** Fill factor for the index as a percentage 0-100 (PostgreSQL, MSSQL). */
|
|
545
545
|
fillFactor?: number;
|
|
546
546
|
}
|
|
@@ -35,10 +35,13 @@ export class AbstractNamingStrategy {
|
|
|
35
35
|
*/
|
|
36
36
|
getEntityName(tableName, schemaName) {
|
|
37
37
|
const name = tableName.match(/^[^$_\p{ID_Start}]/u) ? `E_${tableName}` : tableName;
|
|
38
|
-
return this.getClassName(name.replaceAll(/[^\u200C\u200D\p{ID_Continue}]+/
|
|
38
|
+
return this.getClassName(name.replaceAll(/[^\u200C\u200D\p{ID_Continue}]+/gu, r => r
|
|
39
|
+
.split('')
|
|
40
|
+
.map(c => `$${c.codePointAt(0)}`)
|
|
41
|
+
.join('')), '_');
|
|
39
42
|
}
|
|
40
43
|
columnNameToProperty(columnName) {
|
|
41
|
-
const propName = columnName.replace(/[_\- ]+(\w)/
|
|
44
|
+
const propName = columnName.replace(/[_\- ]+(\w)/gu, (_, p1) => p1.toUpperCase());
|
|
42
45
|
if (populatePathMembers.includes(propName.replace(/^\${2,}/u, '$$').replace(/^\$\*$/u, '*'))) {
|
|
43
46
|
return `$${propName}`;
|
|
44
47
|
}
|
package/not-supported.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export function discoverEntities() {
|
|
2
2
|
throw new Error('Folder-based discovery is not supported in this environment.');
|
|
3
3
|
}
|
|
4
|
-
export const fs = new Proxy({}, {
|
|
4
|
+
export const fs = new Proxy({}, {
|
|
5
|
+
get: () => {
|
|
6
|
+
throw new Error('File system is not supported in this environment.');
|
|
7
|
+
},
|
|
8
|
+
});
|