@mikro-orm/core 7.2.0-dev.8 → 7.2.0
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 +41 -7
- package/EntityManager.js +225 -45
- package/MikroORM.d.ts +4 -0
- package/MikroORM.js +9 -0
- package/README.md +1 -0
- package/connections/Connection.d.ts +3 -1
- package/drivers/DatabaseDriver.d.ts +14 -5
- package/drivers/DatabaseDriver.js +151 -52
- package/drivers/IDatabaseDriver.d.ts +1 -0
- package/entity/Collection.js +4 -2
- package/entity/EntityFactory.js +6 -0
- package/entity/EntityLoader.d.ts +7 -1
- package/entity/EntityLoader.js +46 -11
- package/entity/EntityRepository.d.ts +4 -5
- package/entity/EntityRepository.js +7 -2
- package/entity/defineEntity.d.ts +48 -14
- package/entity/defineEntity.js +32 -1
- package/enums.d.ts +5 -1
- package/enums.js +2 -0
- package/errors.d.ts +36 -0
- package/errors.js +90 -0
- package/events/EventManager.js +6 -3
- package/exceptions.d.ts +5 -0
- package/exceptions.js +5 -0
- package/hydration/ObjectHydrator.d.ts +2 -0
- package/hydration/ObjectHydrator.js +12 -8
- package/index.d.ts +1 -1
- package/metadata/MetadataDiscovery.d.ts +3 -0
- package/metadata/MetadataDiscovery.js +133 -18
- package/metadata/MetadataStorage.js +17 -1
- package/metadata/types.d.ts +19 -3
- package/package.json +1 -1
- package/platforms/Platform.d.ts +22 -3
- package/platforms/Platform.js +59 -1
- package/types/BigIntType.d.ts +1 -0
- package/types/BigIntType.js +23 -0
- package/types/DateTimeType.d.ts +1 -0
- package/types/DateTimeType.js +8 -0
- package/types/StringType.d.ts +14 -3
- package/types/StringType.js +34 -4
- package/types/TextType.d.ts +2 -4
- package/types/TextType.js +2 -8
- package/types/Type.d.ts +11 -0
- package/types/Type.js +4 -4
- package/types/index.d.ts +2 -2
- package/typings.d.ts +56 -2
- package/typings.js +24 -1
- package/unit-of-work/ChangeSetPersister.js +17 -13
- package/unit-of-work/UnitOfWork.js +11 -4
- package/utils/Configuration.d.ts +15 -1
- package/utils/Configuration.js +11 -1
- package/utils/Cursor.d.ts +2 -0
- package/utils/Cursor.js +43 -33
- package/utils/DataloaderUtils.js +2 -1
- package/utils/EntityComparator.d.ts +2 -0
- package/utils/EntityComparator.js +7 -3
- package/utils/QueryHelper.d.ts +12 -0
- package/utils/QueryHelper.js +75 -4
- package/utils/RawQueryFragment.d.ts +6 -0
- package/utils/RawQueryFragment.js +15 -6
- package/utils/TransactionManager.js +1 -1
- package/utils/Utils.d.ts +14 -2
- package/utils/Utils.js +31 -4
- package/utils/env-vars.js +1 -0
- package/utils/index.d.ts +1 -0
- package/utils/index.js +1 -0
- package/utils/rls-utils.d.ts +35 -0
- package/utils/rls-utils.js +97 -0
- package/utils/upsert-utils.d.ts +9 -1
- package/utils/upsert-utils.js +26 -3
|
@@ -117,6 +117,7 @@ export declare class MetadataDiscovery {
|
|
|
117
117
|
private createSchemaTable;
|
|
118
118
|
private initCheckConstraints;
|
|
119
119
|
private initTriggers;
|
|
120
|
+
private initPolicies;
|
|
120
121
|
private initGeneratedColumn;
|
|
121
122
|
private getDefaultVersionValue;
|
|
122
123
|
private inferDefaultValue;
|
|
@@ -125,6 +126,8 @@ export declare class MetadataDiscovery {
|
|
|
125
126
|
private initVersionProperty;
|
|
126
127
|
private initCustomType;
|
|
127
128
|
private initRelation;
|
|
129
|
+
/** Resolves the `through` option of a virtual to-one relation into a read-only formula property. */
|
|
130
|
+
private initThroughRelation;
|
|
128
131
|
private initColumnType;
|
|
129
132
|
private getMappedType;
|
|
130
133
|
private getPrefix;
|
|
@@ -165,10 +165,14 @@ export class MetadataDiscovery {
|
|
|
165
165
|
filtered.forEach(meta => this.initAutoincrement(meta)); // once again after we init custom types
|
|
166
166
|
filtered.forEach(meta => this.initCheckConstraints(meta));
|
|
167
167
|
filtered.forEach(meta => this.initTriggers(meta));
|
|
168
|
-
|
|
168
|
+
// filter names are load-bearing for RLS (policy and session variable names), backfill from the dictionary key
|
|
169
|
+
filtered.forEach(meta => Object.entries(meta.filters).forEach(([key, filter]) => (filter.name ??= key)));
|
|
170
|
+
filtered.forEach(meta => this.initPolicies(meta));
|
|
171
|
+
forEachProp((m, p) => {
|
|
169
172
|
this.initDefaultValue(p);
|
|
170
173
|
this.inferTypeFromDefault(p);
|
|
171
174
|
this.initRelation(p);
|
|
175
|
+
this.initThroughRelation(m, p);
|
|
172
176
|
this.initColumnType(p);
|
|
173
177
|
});
|
|
174
178
|
forEachProp((m, p) => this.initIndexes(m, p));
|
|
@@ -220,11 +224,9 @@ export class MetadataDiscovery {
|
|
|
220
224
|
};
|
|
221
225
|
const missing = [];
|
|
222
226
|
this.#discovered.forEach(meta => Object.values(meta.properties).forEach(prop => {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const target = typeof
|
|
226
|
-
? pivotEntity()
|
|
227
|
-
: pivotEntity;
|
|
227
|
+
const indirect = (prop.kind === ReferenceKind.MANY_TO_MANY ? prop.pivotEntity : prop.through);
|
|
228
|
+
if (indirect) {
|
|
229
|
+
const target = typeof indirect === 'function' && !indirect.prototype ? indirect() : indirect;
|
|
228
230
|
if (!this.#discovered.find(m => m.className === Utils.className(target)) || !discoveredByIdentity(target)) {
|
|
229
231
|
missing.push(target);
|
|
230
232
|
}
|
|
@@ -441,7 +443,18 @@ export class MetadataDiscovery {
|
|
|
441
443
|
if (prop.kind === ReferenceKind.SCALAR || prop.kind === ReferenceKind.EMBEDDED) {
|
|
442
444
|
prop.fieldNames = [this.#namingStrategy.propertyToColumnName(prop.name, object)];
|
|
443
445
|
}
|
|
444
|
-
else if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
|
|
446
|
+
else if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && prop.polymorphic) {
|
|
447
|
+
if (prop.targetMeta) {
|
|
448
|
+
// same layout as `initManyToOneFields` builds later: `[discriminatorColumn, ...fkIdColumns]`
|
|
449
|
+
const pkFields = prop.targetMeta.getPrimaryProps().flatMap(pk => {
|
|
450
|
+
this.initFieldName(pk);
|
|
451
|
+
return pk.fieldNames;
|
|
452
|
+
});
|
|
453
|
+
const idColumns = pkFields.map(fieldName => this.#namingStrategy.joinKeyColumnName(prop.discriminator, fieldName, pkFields.length > 1));
|
|
454
|
+
prop.fieldNames = [prop.discriminatorColumn, ...idColumns];
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
else if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
445
458
|
prop.fieldNames = this.initManyToOneFieldName(prop, prop.name);
|
|
446
459
|
}
|
|
447
460
|
else if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
|
|
@@ -451,10 +464,13 @@ export class MetadataDiscovery {
|
|
|
451
464
|
initManyToOneFieldName(prop, name) {
|
|
452
465
|
const meta2 = prop.targetMeta;
|
|
453
466
|
const ret = [];
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
467
|
+
// with `targetKey` on a composite PK target, derive the FK field name from that property
|
|
468
|
+
// instead of the PKs (simple PK targets keep the PK based naming for backwards compatibility)
|
|
469
|
+
const referencedKeys = prop.targetKey && meta2.compositePK ? [prop.targetKey] : meta2.primaryKeys;
|
|
470
|
+
for (const referencedKey of referencedKeys) {
|
|
471
|
+
this.initFieldName(meta2.properties[referencedKey]);
|
|
472
|
+
for (const fieldName of meta2.properties[referencedKey].fieldNames) {
|
|
473
|
+
ret.push(this.#namingStrategy.joinKeyColumnName(name, fieldName, !prop.targetKey && meta2.compositePK));
|
|
458
474
|
}
|
|
459
475
|
}
|
|
460
476
|
return ret;
|
|
@@ -956,6 +972,11 @@ export class MetadataDiscovery {
|
|
|
956
972
|
return primaryProp;
|
|
957
973
|
}
|
|
958
974
|
definePivotProperty(prop, name, type, inverse, owner, selfReferencing) {
|
|
975
|
+
let index = prop.index ?? this.#platform.indexForeignKeys();
|
|
976
|
+
if (owner && prop.index) {
|
|
977
|
+
// owner join columns are the leading prefix of the composite PK, so an explicit `index` only applies to them with `fixedOrder`; a custom index name always belongs to the inverse side
|
|
978
|
+
index = prop.fixedOrder ? true : this.#platform.indexForeignKeys();
|
|
979
|
+
}
|
|
959
980
|
const ret = {
|
|
960
981
|
name,
|
|
961
982
|
type: Utils.className(type),
|
|
@@ -964,7 +985,7 @@ export class MetadataDiscovery {
|
|
|
964
985
|
cascade: [Cascade.ALL],
|
|
965
986
|
fixedOrder: prop.fixedOrder,
|
|
966
987
|
fixedOrderColumn: prop.fixedOrderColumn,
|
|
967
|
-
index
|
|
988
|
+
index,
|
|
968
989
|
primary: !prop.fixedOrder,
|
|
969
990
|
autoincrement: false,
|
|
970
991
|
updateRule: prop.updateRule,
|
|
@@ -1050,11 +1071,18 @@ export class MetadataDiscovery {
|
|
|
1050
1071
|
}
|
|
1051
1072
|
// TPT children have their own tables that don't contain the parent's columns,
|
|
1052
1073
|
// so propagating parent indexes/uniques/checks/triggers would target missing columns.
|
|
1074
|
+
// deep equality, as the subclass items might be copies of the base class ones (e.g. with TC39 decorators)
|
|
1053
1075
|
if (meta.inheritanceType !== 'tpt' || !meta.tptParent) {
|
|
1054
|
-
meta.indexes = Utils.unique([...base.indexes, ...meta.indexes]);
|
|
1055
|
-
meta.uniques = Utils.unique([...base.uniques, ...meta.uniques]);
|
|
1056
|
-
meta.checks = Utils.unique([...base.checks, ...meta.checks]);
|
|
1057
|
-
meta.triggers = Utils.unique([...base.triggers, ...meta.triggers]);
|
|
1076
|
+
meta.indexes = Utils.unique([...base.indexes, ...meta.indexes], Utils.equals);
|
|
1077
|
+
meta.uniques = Utils.unique([...base.uniques, ...meta.uniques], Utils.equals);
|
|
1078
|
+
meta.checks = Utils.unique([...base.checks, ...meta.checks], Utils.equals);
|
|
1079
|
+
meta.triggers = Utils.unique([...base.triggers, ...meta.triggers], Utils.equals);
|
|
1080
|
+
}
|
|
1081
|
+
// Policies pass down only from inlined abstract bases; STI children share the root table
|
|
1082
|
+
// and TPT children own their tables, so both declare policies directly on the root/child.
|
|
1083
|
+
if (base.abstract && base.inheritanceType !== 'sti' && (meta.inheritanceType !== 'tpt' || !meta.tptParent)) {
|
|
1084
|
+
meta.policies = Utils.unique([...base.policies, ...meta.policies]);
|
|
1085
|
+
meta.rowLevelSecurity ??= base.rowLevelSecurity;
|
|
1058
1086
|
}
|
|
1059
1087
|
const pks = Object.values(meta.properties)
|
|
1060
1088
|
.filter(p => p.primary)
|
|
@@ -1242,8 +1270,17 @@ export class MetadataDiscovery {
|
|
|
1242
1270
|
if (embeddedProp.nullable || refInArray) {
|
|
1243
1271
|
meta.properties[name].nullable = true;
|
|
1244
1272
|
}
|
|
1273
|
+
// polymorphic relations derive their column names from the discriminator, so prefix it too
|
|
1274
|
+
if (meta.properties[name].polymorphic && !object) {
|
|
1275
|
+
meta.properties[name].discriminator = prefix + meta.properties[name].discriminator;
|
|
1276
|
+
meta.properties[name].discriminatorColumn = prefix + meta.properties[name].discriminatorColumn;
|
|
1277
|
+
}
|
|
1245
1278
|
if (meta.properties[name].fieldNames) {
|
|
1246
|
-
|
|
1279
|
+
const { fieldNames, polymorphic } = meta.properties[name];
|
|
1280
|
+
// polymorphic `fieldNames` hold `[discriminatorColumn, ...fkIdColumns]`, so prefix all of them
|
|
1281
|
+
for (let i = 0; i < (polymorphic ? fieldNames.length : 1); i++) {
|
|
1282
|
+
fieldNames[i] = prefix + fieldNames[i];
|
|
1283
|
+
}
|
|
1247
1284
|
}
|
|
1248
1285
|
else {
|
|
1249
1286
|
const name2 = meta.properties[name].name;
|
|
@@ -1672,6 +1709,9 @@ export class MetadataDiscovery {
|
|
|
1672
1709
|
if (prop.persist === false || prop.nativeEnumName || !prop.items?.every(item => typeof item === 'string')) {
|
|
1673
1710
|
continue;
|
|
1674
1711
|
}
|
|
1712
|
+
if (prop.customType instanceof t.json || ['json', 'jsonb'].includes(prop.columnTypes?.[0])) {
|
|
1713
|
+
continue;
|
|
1714
|
+
}
|
|
1675
1715
|
this.initFieldName(prop);
|
|
1676
1716
|
let expression = null;
|
|
1677
1717
|
if (prop.enum) {
|
|
@@ -1711,6 +1751,28 @@ export class MetadataDiscovery {
|
|
|
1711
1751
|
}
|
|
1712
1752
|
meta.hasTriggers = true;
|
|
1713
1753
|
}
|
|
1754
|
+
initPolicies(meta) {
|
|
1755
|
+
if (meta.policies.length === 0) {
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1758
|
+
const columns = meta.createSchemaColumnMappingObject();
|
|
1759
|
+
const table = this.createSchemaTable(meta);
|
|
1760
|
+
// resolve callbacks into a copy — the defs can be shared with siblings via an inlined abstract base,
|
|
1761
|
+
// and resolving in place would bake the first child's table into every other child's policy
|
|
1762
|
+
meta.policies = meta.policies.map(policy => {
|
|
1763
|
+
if (!(policy.using instanceof Function) && !(policy.check instanceof Function)) {
|
|
1764
|
+
return policy;
|
|
1765
|
+
}
|
|
1766
|
+
const resolved = { ...policy };
|
|
1767
|
+
if (resolved.using instanceof Function) {
|
|
1768
|
+
resolved.using = resolved.using(columns, table);
|
|
1769
|
+
}
|
|
1770
|
+
if (resolved.check instanceof Function) {
|
|
1771
|
+
resolved.check = resolved.check(columns, table);
|
|
1772
|
+
}
|
|
1773
|
+
return resolved;
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1714
1776
|
initGeneratedColumn(meta, prop) {
|
|
1715
1777
|
if (!prop.generated && prop.columnTypes) {
|
|
1716
1778
|
const match = /(.*) generated always as (.*)/i.exec(prop.columnTypes[0]);
|
|
@@ -2015,6 +2077,58 @@ export class MetadataDiscovery {
|
|
|
2015
2077
|
}
|
|
2016
2078
|
}
|
|
2017
2079
|
}
|
|
2080
|
+
/** Resolves the `through` option of a virtual to-one relation into a read-only formula property. */
|
|
2081
|
+
initThroughRelation(meta, prop) {
|
|
2082
|
+
// already resolved, or not a through relation at all
|
|
2083
|
+
if (prop.through?.ownerProperty || !prop.through) {
|
|
2084
|
+
return;
|
|
2085
|
+
}
|
|
2086
|
+
if (![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
2087
|
+
throw MetadataError.throughRelationInvalidKind(meta, prop);
|
|
2088
|
+
}
|
|
2089
|
+
const targetMeta = prop.targetMeta;
|
|
2090
|
+
// the subquery selects a single column
|
|
2091
|
+
if (targetMeta.compositePK) {
|
|
2092
|
+
throw MetadataError.throughRelationCompositeTarget(meta, prop);
|
|
2093
|
+
}
|
|
2094
|
+
const through = prop.through;
|
|
2095
|
+
const throughMeta = this.#metadata.get(!through.prototype ? through() : through);
|
|
2096
|
+
// a property is considered to point at an entity when it targets it or one of its parents
|
|
2097
|
+
const pointsTo = (p, m) => {
|
|
2098
|
+
const candidate = this.#metadata.find(p.target);
|
|
2099
|
+
/* v8 ignore next 3 */
|
|
2100
|
+
if (!candidate) {
|
|
2101
|
+
return false;
|
|
2102
|
+
}
|
|
2103
|
+
return candidate.class === m.class || m.class.prototype instanceof candidate.class;
|
|
2104
|
+
};
|
|
2105
|
+
const fks = Object.values(throughMeta.properties).filter(p => p.kind === ReferenceKind.MANY_TO_ONE);
|
|
2106
|
+
const ownerProp = fks.find(p => pointsTo(p, meta));
|
|
2107
|
+
if (!ownerProp) {
|
|
2108
|
+
throw MetadataError.throughRelationMissingProperty(meta, prop, throughMeta, 'owner');
|
|
2109
|
+
}
|
|
2110
|
+
let targetProperty;
|
|
2111
|
+
const selectsTarget = throughMeta.class === targetMeta.class || throughMeta.class.prototype instanceof targetMeta.class;
|
|
2112
|
+
if (!selectsTarget) {
|
|
2113
|
+
const targetProp = fks.find(p => p !== ownerProp && pointsTo(p, targetMeta));
|
|
2114
|
+
if (!targetProp) {
|
|
2115
|
+
throw MetadataError.throughRelationMissingProperty(meta, prop, throughMeta, 'target');
|
|
2116
|
+
}
|
|
2117
|
+
targetProperty = targetProp.name;
|
|
2118
|
+
}
|
|
2119
|
+
prop.through = {
|
|
2120
|
+
entity: throughMeta.class,
|
|
2121
|
+
where: prop.where,
|
|
2122
|
+
orderBy: prop.orderBy ? Utils.asArray(prop.orderBy) : undefined,
|
|
2123
|
+
ownerProperty: ownerProp.name,
|
|
2124
|
+
targetProperty,
|
|
2125
|
+
};
|
|
2126
|
+
// the condition and ordering apply to the `through` entity, not to the target, so they must not leak into the target joins
|
|
2127
|
+
delete prop.where;
|
|
2128
|
+
delete prop.orderBy;
|
|
2129
|
+
prop.persist = false;
|
|
2130
|
+
prop.formula = columns => this.#platform.getThroughRelationFormula(prop, columns);
|
|
2131
|
+
}
|
|
2018
2132
|
initColumnType(prop) {
|
|
2019
2133
|
this.initUnsigned(prop);
|
|
2020
2134
|
// Get the target properties for FK relations - use targetKey property if specified, otherwise PKs
|
|
@@ -2075,6 +2189,7 @@ export class MetadataDiscovery {
|
|
|
2075
2189
|
prop.columnTypes.push(...columnTypes);
|
|
2076
2190
|
if (!targetMeta.compositePK || prop.targetKey) {
|
|
2077
2191
|
prop.customType = referencedProp.customType;
|
|
2192
|
+
prop.collation ??= referencedProp.collation;
|
|
2078
2193
|
}
|
|
2079
2194
|
}
|
|
2080
2195
|
}
|
|
@@ -2130,7 +2245,7 @@ export class MetadataDiscovery {
|
|
|
2130
2245
|
shouldForceConstructorUsage(meta) {
|
|
2131
2246
|
const forceConstructor = this.#config.get('forceEntityConstructor');
|
|
2132
2247
|
if (Array.isArray(forceConstructor)) {
|
|
2133
|
-
return forceConstructor.some(cls => Utils.
|
|
2248
|
+
return forceConstructor.some(cls => Utils.matchesEntity(cls, meta));
|
|
2134
2249
|
}
|
|
2135
2250
|
return forceConstructor;
|
|
2136
2251
|
}
|
|
@@ -17,6 +17,7 @@ export class MetadataStorage {
|
|
|
17
17
|
#idMap;
|
|
18
18
|
#classNameMap;
|
|
19
19
|
#uniqueNameMap;
|
|
20
|
+
#ambiguousNames = new Set();
|
|
20
21
|
constructor(metadata = {}) {
|
|
21
22
|
this.#idMap = {};
|
|
22
23
|
this.#uniqueNameMap = {};
|
|
@@ -64,6 +65,10 @@ export class MetadataStorage {
|
|
|
64
65
|
}
|
|
65
66
|
/** Returns metadata for the given entity, optionally initializing it if not found. */
|
|
66
67
|
get(entityName, init = false) {
|
|
68
|
+
// string lookups cannot be resolved when several classes were minified to the same name
|
|
69
|
+
if (typeof entityName === 'string' && this.#ambiguousNames.has(entityName)) {
|
|
70
|
+
throw MetadataError.ambiguousEntityName(entityName);
|
|
71
|
+
}
|
|
67
72
|
const exists = this.find(entityName);
|
|
68
73
|
if (exists) {
|
|
69
74
|
return exists;
|
|
@@ -99,7 +104,13 @@ export class MetadataStorage {
|
|
|
99
104
|
this.#metadataMap.set(entityName, meta);
|
|
100
105
|
this.#idMap[meta._id] = meta;
|
|
101
106
|
this.#uniqueNameMap[meta.uniqueName] = meta;
|
|
102
|
-
|
|
107
|
+
const className = Utils.className(entityName);
|
|
108
|
+
const existing = this.#classNameMap[className];
|
|
109
|
+
// track name collisions caused by minifiers mangling two classes to the same name
|
|
110
|
+
if (existing && existing !== meta && existing.class !== meta.class) {
|
|
111
|
+
this.#ambiguousNames.add(className);
|
|
112
|
+
}
|
|
113
|
+
this.#classNameMap[className] = meta;
|
|
103
114
|
return meta;
|
|
104
115
|
}
|
|
105
116
|
/** Removes metadata for the given entity from all internal maps. */
|
|
@@ -110,6 +121,11 @@ export class MetadataStorage {
|
|
|
110
121
|
delete this.#idMap[meta._id];
|
|
111
122
|
delete this.#uniqueNameMap[meta.uniqueName];
|
|
112
123
|
delete this.#classNameMap[meta.className];
|
|
124
|
+
// the name may still be ambiguous among the remaining metas
|
|
125
|
+
const remaining = new Set([...this.#metadataMap.values()].filter(m => m.className === meta.className).map(m => m.class));
|
|
126
|
+
if (remaining.size <= 1) {
|
|
127
|
+
this.#ambiguousNames.delete(meta.className);
|
|
128
|
+
}
|
|
113
129
|
}
|
|
114
130
|
}
|
|
115
131
|
/** Decorates all entity prototypes with helper methods (e.g. init, toJSON). */
|
package/metadata/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnyEntity, Constructor, EntityName, AnyString, CheckCallback, GeneratedColumnCallback, FormulaCallback, FilterQuery, Dictionary, AutoPath, EntityClass, IndexCallback, ObjectQuery, Raw, SchemaColumns, TriggerDef } from '../typings.js';
|
|
1
|
+
import type { AnyEntity, Constructor, EntityName, AnyString, CheckCallback, GeneratedColumnCallback, FormulaCallback, FilterQuery, Dictionary, AutoPath, EntityClass, IndexCallback, ObjectQuery, Raw, SchemaColumns, TriggerDef, PolicyDef } from '../typings.js';
|
|
2
2
|
import type { Cascade, LoadStrategy, DeferMode, QueryOrderMap, EmbeddedPrefixMode } from '../enums.js';
|
|
3
3
|
import type { Type, types } from '../types/index.js';
|
|
4
4
|
import type { EntityManager } from '../EntityManager.js';
|
|
@@ -104,6 +104,10 @@ export type EntityOptions<T, E = T extends EntityClass<infer P> ? P : T> = {
|
|
|
104
104
|
hasTriggers?: boolean;
|
|
105
105
|
/** Database triggers to create for this entity's table. (SQL drivers only) */
|
|
106
106
|
triggers?: TriggerDef<E>[];
|
|
107
|
+
/** PostgreSQL row level security policies for this entity's table. Declaring policies implicitly enables RLS. */
|
|
108
|
+
policies?: PolicyDef<E>[];
|
|
109
|
+
/** Enables PostgreSQL row level security on this entity's table. `'force'` also enforces it for the table owner. Set to `false` to keep declared policies staged while leaving RLS disabled. */
|
|
110
|
+
rowLevelSecurity?: boolean | 'force';
|
|
107
111
|
/**
|
|
108
112
|
* PostgreSQL partitioning definition for this table.
|
|
109
113
|
*
|
|
@@ -427,9 +431,15 @@ interface PolymorphicOptions {
|
|
|
427
431
|
*/
|
|
428
432
|
discriminatorMap?: Dictionary<string>;
|
|
429
433
|
}
|
|
430
|
-
export interface ManyToOneOptions<Owner, Target> extends ReferenceOptions<Owner, Target>, PolymorphicOptions {
|
|
434
|
+
export interface ManyToOneOptions<Owner, Target, Through = Target> extends ReferenceOptions<Owner, Target>, PolymorphicOptions {
|
|
431
435
|
/** Point to the inverse side property name. */
|
|
432
436
|
inversedBy?: (string & keyof Target) | ((e: Target) => any);
|
|
437
|
+
/** Resolve this read-only relation via a subquery on another entity: a pivot entity with FKs to both sides, or the target itself to pick a single item out of a to-many relation (see {@doclink relationships#to-one-relations-through-another-entity | To-one relations through another entity}). */
|
|
438
|
+
through?: () => EntityName<Through>;
|
|
439
|
+
/** Condition applied on the `through` entity. */
|
|
440
|
+
where?: FilterQuery<Through>;
|
|
441
|
+
/** Ordering applied on the `through` entity, the first matching row is used. */
|
|
442
|
+
orderBy?: QueryOrderMap<Through> | QueryOrderMap<Through>[];
|
|
433
443
|
/** Wrap the entity in {@apilink Reference} wrapper. */
|
|
434
444
|
ref?: boolean;
|
|
435
445
|
/** Use this relation as a primary key. */
|
|
@@ -481,9 +491,15 @@ export interface OneToManyOptions<Owner, Target> extends ReferenceOptions<Owner,
|
|
|
481
491
|
/** Point to the owning side property name. */
|
|
482
492
|
mappedBy: (string & keyof Target) | ((e: Target) => any);
|
|
483
493
|
}
|
|
484
|
-
export interface OneToOneOptions<Owner, Target> extends Partial<Omit<OneToManyOptions<Owner, Target>, 'orderBy'>>, PolymorphicOptions {
|
|
494
|
+
export interface OneToOneOptions<Owner, Target, Through = Target> extends Partial<Omit<OneToManyOptions<Owner, Target>, 'orderBy' | 'where'>>, PolymorphicOptions {
|
|
485
495
|
/** Set this side as owning. Owning side is where the foreign key is defined. This option is not required if you use `inversedBy` or `mappedBy` to distinguish owning and inverse side. */
|
|
486
496
|
owner?: boolean;
|
|
497
|
+
/** Resolve this read-only relation via a subquery on another entity: a pivot entity with FKs to both sides, or the target itself to pick a single item out of a to-many relation (see {@doclink relationships#to-one-relations-through-another-entity | To-one relations through another entity}). */
|
|
498
|
+
through?: () => EntityName<Through>;
|
|
499
|
+
/** Condition for {@doclink collections#declarative-partial-loading | Declarative partial loading}, or the condition applied on the `through` entity. */
|
|
500
|
+
where?: FilterQuery<Through>;
|
|
501
|
+
/** Ordering applied on the `through` entity, the first matching row is used. */
|
|
502
|
+
orderBy?: QueryOrderMap<Through> | QueryOrderMap<Through>[];
|
|
487
503
|
/** Point to the inverse side property name. */
|
|
488
504
|
inversedBy?: (string & keyof Target) | ((e: Target) => any);
|
|
489
505
|
/** Wrap the entity in {@apilink Reference} wrapper. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/core",
|
|
3
|
-
"version": "7.2.0
|
|
3
|
+
"version": "7.2.0",
|
|
4
4
|
"description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"data-mapper",
|
package/platforms/Platform.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EntityRepository } from '../entity/EntityRepository.js';
|
|
2
2
|
import { type NamingStrategy } from '../naming-strategy/NamingStrategy.js';
|
|
3
|
-
import type { Constructor, EntityMetadata, EntityProperty, IPrimaryKey, ISchemaGenerator, PopulateOptions, Primary, SimpleColumnMeta, FilterQuery, EntityValue, EntityKey } from '../typings.js';
|
|
3
|
+
import type { Constructor, EntityMetadata, EntityProperty, IPrimaryKey, ISchemaGenerator, PopulateOptions, Primary, SimpleColumnMeta, FilterQuery, EntityValue, EntityKey, FormulaColumns } from '../typings.js';
|
|
4
4
|
import { ExceptionConverter } from './ExceptionConverter.js';
|
|
5
5
|
import type { EntityManager } from '../EntityManager.js';
|
|
6
6
|
import type { Configuration } from '../utils/Configuration.js';
|
|
@@ -211,8 +211,9 @@ export declare abstract class Platform {
|
|
|
211
211
|
getBlobDeclarationSQL(): string;
|
|
212
212
|
getJsonDeclarationSQL(): string;
|
|
213
213
|
getSearchJsonPropertySQL(path: string, type: string, aliased: boolean): string | Raw;
|
|
214
|
-
|
|
215
|
-
|
|
214
|
+
/** When `aliased` is a string, it holds an explicit alias the key was prefixed with. */
|
|
215
|
+
getSearchJsonPropertyKey(path: string[], type: string, aliased: boolean | string, value?: unknown): string | Raw;
|
|
216
|
+
processJsonCondition<T extends object>(o: FilterQuery<T>, value: EntityValue<T>, path: EntityKey<T>[], alias: boolean | string): FilterQuery<T>;
|
|
216
217
|
protected getJsonValueType(value: unknown): string;
|
|
217
218
|
getJsonIndexDefinition(index: {
|
|
218
219
|
columnNames: string[];
|
|
@@ -229,6 +230,10 @@ export declare abstract class Platform {
|
|
|
229
230
|
convertsJsonAutomatically(): boolean;
|
|
230
231
|
/** Whether date values inside JSON documents keep their native type (e.g. BSON dates), instead of being serialized to ISO strings. */
|
|
231
232
|
preservesDatesInsideJson(): boolean;
|
|
233
|
+
/** Whether `nulls first`/`nulls last` can be requested in an `orderBy`. */
|
|
234
|
+
supportsNullsOrdering(): boolean;
|
|
235
|
+
/** Where nulls land when an `orderBy` requests no explicit placement: lowest (`asc` puts them first) or highest. */
|
|
236
|
+
sortsNullsLowest(): boolean;
|
|
232
237
|
/** Converts a JS value to its JSON database representation (typically JSON.stringify). */
|
|
233
238
|
convertJsonToDatabaseValue(value: unknown, context?: TransformContext): unknown;
|
|
234
239
|
/** Converts a database JSON value to its JS representation. */
|
|
@@ -274,6 +279,11 @@ export declare abstract class Platform {
|
|
|
274
279
|
formatQuery(sql: string, params: readonly any[]): string;
|
|
275
280
|
/** Deep-clones embeddable data and tags it for JSON serialization. */
|
|
276
281
|
cloneEmbeddable<T>(data: T): T;
|
|
282
|
+
/**
|
|
283
|
+
* Builds the correlated subquery used as the formula of a virtual to-one relation defined via `through`.
|
|
284
|
+
* @internal
|
|
285
|
+
*/
|
|
286
|
+
getThroughRelationFormula(prop: EntityProperty, columns: FormulaColumns<any>): string;
|
|
277
287
|
/** Initializes the platform with the ORM configuration. */
|
|
278
288
|
setConfig(config: Configuration): void;
|
|
279
289
|
/** Returns the current ORM configuration. */
|
|
@@ -315,6 +325,15 @@ export declare abstract class Platform {
|
|
|
315
325
|
supportsDownMigrations(): boolean;
|
|
316
326
|
/** Whether the platform supports deferred unique constraints. */
|
|
317
327
|
supportsDeferredUniqueConstraints(): boolean;
|
|
328
|
+
/** Whether the platform supports row level security (PostgreSQL). */
|
|
329
|
+
supportsRowLevelSecurity(): boolean;
|
|
330
|
+
/** Whether the driver can apply the session context on every pooled connection acquire (`sessionContext: 'connection'`). */
|
|
331
|
+
supportsConnectionSessionContext(): boolean;
|
|
332
|
+
/**
|
|
333
|
+
* SQL cast suffix (e.g. `'::uuid'`, or `''` when none is needed) applied when an RLS filter reads a session
|
|
334
|
+
* variable via `current_setting()` as the given column type, or `null` if the type has no automatic cast.
|
|
335
|
+
*/
|
|
336
|
+
getCurrentSettingCast(mappedType: Type<unknown>): string | null;
|
|
318
337
|
/** Platform-specific validation of entity metadata. */
|
|
319
338
|
validateMetadata(meta: EntityMetadata): void;
|
|
320
339
|
/**
|
package/platforms/Platform.js
CHANGED
|
@@ -413,6 +413,7 @@ export class Platform {
|
|
|
413
413
|
getSearchJsonPropertySQL(path, type, aliased) {
|
|
414
414
|
return path;
|
|
415
415
|
}
|
|
416
|
+
/** When `aliased` is a string, it holds an explicit alias the key was prefixed with. */
|
|
416
417
|
getSearchJsonPropertyKey(path, type, aliased, value) {
|
|
417
418
|
return path.join('.');
|
|
418
419
|
}
|
|
@@ -424,7 +425,8 @@ export class Platform {
|
|
|
424
425
|
return o;
|
|
425
426
|
}
|
|
426
427
|
if (path.length === 1) {
|
|
427
|
-
|
|
428
|
+
const key = typeof alias === 'string' ? `${alias}.${path[0]}` : path[0];
|
|
429
|
+
o[key] = value;
|
|
428
430
|
return o;
|
|
429
431
|
}
|
|
430
432
|
const type = this.getJsonValueType(value);
|
|
@@ -469,6 +471,14 @@ export class Platform {
|
|
|
469
471
|
preservesDatesInsideJson() {
|
|
470
472
|
return false;
|
|
471
473
|
}
|
|
474
|
+
/** Whether `nulls first`/`nulls last` can be requested in an `orderBy`. */
|
|
475
|
+
supportsNullsOrdering() {
|
|
476
|
+
return true;
|
|
477
|
+
}
|
|
478
|
+
/** Where nulls land when an `orderBy` requests no explicit placement: lowest (`asc` puts them first) or highest. */
|
|
479
|
+
sortsNullsLowest() {
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
472
482
|
/** Converts a JS value to its JSON database representation (typically JSON.stringify). */
|
|
473
483
|
convertJsonToDatabaseValue(value, context) {
|
|
474
484
|
return JSON.stringify(value);
|
|
@@ -614,6 +624,14 @@ export class Platform {
|
|
|
614
624
|
Object.defineProperty(copy, JsonProperty, { enumerable: false, value: true });
|
|
615
625
|
return copy;
|
|
616
626
|
}
|
|
627
|
+
/**
|
|
628
|
+
* Builds the correlated subquery used as the formula of a virtual to-one relation defined via `through`.
|
|
629
|
+
* @internal
|
|
630
|
+
*/
|
|
631
|
+
/* v8 ignore next 3 */
|
|
632
|
+
getThroughRelationFormula(prop, columns) {
|
|
633
|
+
throw new Error(`${this.constructor.name} does not support the 'through' option of ${prop.name}`);
|
|
634
|
+
}
|
|
617
635
|
/** Initializes the platform with the ORM configuration. */
|
|
618
636
|
setConfig(config) {
|
|
619
637
|
this.config = config;
|
|
@@ -720,11 +738,51 @@ export class Platform {
|
|
|
720
738
|
supportsDeferredUniqueConstraints() {
|
|
721
739
|
return true;
|
|
722
740
|
}
|
|
741
|
+
/** Whether the platform supports row level security (PostgreSQL). */
|
|
742
|
+
supportsRowLevelSecurity() {
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
/** Whether the driver can apply the session context on every pooled connection acquire (`sessionContext: 'connection'`). */
|
|
746
|
+
supportsConnectionSessionContext() {
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* SQL cast suffix (e.g. `'::uuid'`, or `''` when none is needed) applied when an RLS filter reads a session
|
|
751
|
+
* variable via `current_setting()` as the given column type, or `null` if the type has no automatic cast.
|
|
752
|
+
*/
|
|
753
|
+
getCurrentSettingCast(mappedType) {
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
723
756
|
/** Platform-specific validation of entity metadata. */
|
|
724
757
|
validateMetadata(meta) {
|
|
725
758
|
if (meta.partitionBy && !this.supportsPartitionedTables()) {
|
|
726
759
|
throw new MetadataError(`Entity ${meta.className} uses partitionBy, but ${this.constructor.name} does not support partitioned tables`);
|
|
727
760
|
}
|
|
761
|
+
const declaresRls = meta.policies.length > 0 || !!meta.rowLevelSecurity;
|
|
762
|
+
if (declaresRls && !this.supportsRowLevelSecurity()) {
|
|
763
|
+
throw MetadataError.rowLevelSecurityNotSupportedByDriver(meta);
|
|
764
|
+
}
|
|
765
|
+
// STI hierarchies share a single table, so only the root may declare policies; `root` is optional-chained
|
|
766
|
+
// as `validateMetadata` is public API and tolerates partially populated metadata
|
|
767
|
+
if (declaresRls && meta.root?.inheritanceType === 'sti' && meta.root !== meta) {
|
|
768
|
+
throw MetadataError.rowLevelSecurityOnNonRootStiEntity(meta);
|
|
769
|
+
}
|
|
770
|
+
if (meta.root?.inheritanceType === 'sti' && meta.root !== meta) {
|
|
771
|
+
for (const filter of Object.values(meta.filters)) {
|
|
772
|
+
// inherited root filters share the def object; only defs declared on the child itself are a problem,
|
|
773
|
+
// as non-root STI metas never reach the schema generator and the policy would silently not exist
|
|
774
|
+
if (filter.rls && meta.root.filters[filter.name] !== filter) {
|
|
775
|
+
throw MetadataError.rlsFilterOnNonRootStiEntity(meta, filter.name);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (!this.supportsRowLevelSecurity()) {
|
|
780
|
+
for (const filter of Object.values(meta.filters)) {
|
|
781
|
+
if (filter.rls) {
|
|
782
|
+
throw MetadataError.rlsFilterNotSupportedByDriver(meta, filter.name);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
728
786
|
}
|
|
729
787
|
/**
|
|
730
788
|
* Generates a custom order by statement given a set of in order values, eg.
|
package/types/BigIntType.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare class BigIntType<Mode extends 'bigint' | 'number' | 'string' = 'b
|
|
|
11
11
|
convertToDatabaseValue(value: JSTypeByMode<Mode> | null | undefined): string | null | undefined;
|
|
12
12
|
convertToJSValue(value: string | bigint | null | undefined): JSTypeByMode<Mode> | null | undefined;
|
|
13
13
|
toJSON(value: JSTypeByMode<Mode> | null | undefined): JSTypeByMode<Mode> | null | undefined;
|
|
14
|
+
fromJSON(value: unknown): JSTypeByMode<Mode> | null | undefined;
|
|
14
15
|
getColumnType(prop: EntityProperty, platform: Platform): string;
|
|
15
16
|
compareAsType(): string;
|
|
16
17
|
compareValues(a: string, b: string): boolean;
|
package/types/BigIntType.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Type } from './Type.js';
|
|
2
|
+
import { ValidationError } from '../errors.js';
|
|
2
3
|
/**
|
|
3
4
|
* This type will automatically convert string values returned from the database to native JS bigints (default)
|
|
4
5
|
* or numbers (safe only for values up to `Number.MAX_SAFE_INTEGER`), or strings, depending on the `mode`.
|
|
@@ -36,6 +37,28 @@ export class BigIntType extends Type {
|
|
|
36
37
|
}
|
|
37
38
|
return this.convertToDatabaseValue(value);
|
|
38
39
|
}
|
|
40
|
+
fromJSON(value) {
|
|
41
|
+
// the serialized form is a decimal string, or a plain number in `number` mode
|
|
42
|
+
const valid = (typeof value === 'string' && /^-?\d+$/.test(value)) || (typeof value === 'number' && Number.isInteger(value));
|
|
43
|
+
if (!valid) {
|
|
44
|
+
throw ValidationError.invalidType(BigIntType, value, 'JSON');
|
|
45
|
+
}
|
|
46
|
+
switch (this.mode) {
|
|
47
|
+
case 'number': {
|
|
48
|
+
// `Number` silently rounds past `MAX_SAFE_INTEGER`, tampered cursors must fail loudly
|
|
49
|
+
const num = Number(value);
|
|
50
|
+
if (!Number.isSafeInteger(num)) {
|
|
51
|
+
throw ValidationError.invalidType(BigIntType, value, 'JSON');
|
|
52
|
+
}
|
|
53
|
+
return num;
|
|
54
|
+
}
|
|
55
|
+
case 'string':
|
|
56
|
+
return String(value);
|
|
57
|
+
case 'bigint':
|
|
58
|
+
default:
|
|
59
|
+
return BigInt(value);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
39
62
|
getColumnType(prop, platform) {
|
|
40
63
|
return platform.getBigIntTypeDeclarationSQL(prop);
|
|
41
64
|
}
|
package/types/DateTimeType.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { EntityProperty } from '../typings.js';
|
|
|
5
5
|
export declare class DateTimeType extends Type<Date, string> {
|
|
6
6
|
getColumnType(prop: EntityProperty, platform: Platform): string;
|
|
7
7
|
compareAsType(): string;
|
|
8
|
+
fromJSON(value: unknown): Date;
|
|
8
9
|
get runtimeType(): string;
|
|
9
10
|
ensureComparable(): boolean;
|
|
10
11
|
getDefaultLength(platform: Platform): number;
|
package/types/DateTimeType.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Type } from './Type.js';
|
|
2
|
+
import { ValidationError } from '../errors.js';
|
|
2
3
|
/** Maps a database DATETIME/TIMESTAMP column to a JS `Date` object. */
|
|
3
4
|
export class DateTimeType extends Type {
|
|
4
5
|
getColumnType(prop, platform) {
|
|
@@ -7,6 +8,13 @@ export class DateTimeType extends Type {
|
|
|
7
8
|
compareAsType() {
|
|
8
9
|
return 'Date';
|
|
9
10
|
}
|
|
11
|
+
fromJSON(value) {
|
|
12
|
+
const date = new Date(value);
|
|
13
|
+
if (typeof value !== 'string' || Number.isNaN(date.getTime())) {
|
|
14
|
+
throw ValidationError.invalidType(DateTimeType, value, 'JSON');
|
|
15
|
+
}
|
|
16
|
+
return date;
|
|
17
|
+
}
|
|
10
18
|
get runtimeType() {
|
|
11
19
|
return 'Date';
|
|
12
20
|
}
|
package/types/StringType.d.ts
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import { Type } from './Type.js';
|
|
2
2
|
import type { Platform } from '../platforms/Platform.js';
|
|
3
3
|
import type { EntityProperty } from '../typings.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
export interface StringTypeOptions {
|
|
5
|
+
trim?: boolean;
|
|
6
|
+
case?: 'upper' | 'lower';
|
|
7
|
+
}
|
|
8
|
+
/** @internal */
|
|
9
|
+
export declare abstract class BaseStringType extends Type<string | null | undefined, string | null | undefined> {
|
|
10
|
+
readonly options: StringTypeOptions;
|
|
11
|
+
constructor(options?: StringTypeOptions);
|
|
12
|
+
convertToDatabaseValue(value: string | null | undefined): string | null | undefined;
|
|
7
13
|
compareAsType(): string;
|
|
8
14
|
ensureComparable(): boolean;
|
|
15
|
+
private normalize;
|
|
16
|
+
}
|
|
17
|
+
/** Maps a database VARCHAR column to a JS `string`. */
|
|
18
|
+
export declare class StringType extends BaseStringType {
|
|
19
|
+
getColumnType(prop: EntityProperty, platform: Platform): string;
|
|
9
20
|
getDefaultLength(platform: Platform): number;
|
|
10
21
|
}
|