@mikro-orm/core 7.2.0-dev.15 → 7.2.0-dev.16
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/entity/defineEntity.d.ts +4 -0
- package/entity/defineEntity.js +4 -0
- package/errors.d.ts +3 -0
- package/errors.js +10 -0
- package/metadata/MetadataDiscovery.d.ts +2 -0
- package/metadata/MetadataDiscovery.js +57 -6
- package/metadata/types.d.ts +14 -2
- package/package.json +1 -1
- package/platforms/Platform.d.ts +6 -1
- package/platforms/Platform.js +8 -0
- package/typings.d.ts +11 -0
- package/utils/Utils.js +1 -1
package/entity/defineEntity.d.ts
CHANGED
|
@@ -148,6 +148,8 @@ export interface PropertyChain<in out Value, in out Options> {
|
|
|
148
148
|
orphanRemoval(orphanRemoval?: boolean): HasKind<Options, '1:m' | '1:1'> extends true ? PropertyChain<Value, Options> : never;
|
|
149
149
|
discriminator(discriminator: string): HasKind<Options, 'm:1' | '1:1' | 'm:n'> extends true ? PropertyChain<Value, Options> : never;
|
|
150
150
|
discriminatorMap(discriminatorMap: Dictionary<string>): HasKind<Options, 'm:1' | '1:1' | 'm:n'> extends true ? PropertyChain<Value, Options> : never;
|
|
151
|
+
/** Resolve this read-only to-one relation via a subquery on another entity (see {@doclink relationships#to-one-relations-through-another-entity | To-one relations through another entity}). */
|
|
152
|
+
through(through: () => EntityName): HasKind<Options, 'm:1' | '1:1'> extends true ? PropertyChain<Value, Options> : never;
|
|
151
153
|
pivotTable(pivotTable: string): HasKind<Options, 'm:n'> extends true ? PropertyChain<Value, Options> : never;
|
|
152
154
|
pivotEntity(pivotEntity: () => EntityName): HasKind<Options, 'm:n'> extends true ? PropertyChain<Value, Options> : never;
|
|
153
155
|
fixedOrder(fixedOrder?: boolean): HasKind<Options, 'm:n'> extends true ? PropertyChain<Value, Options> : never;
|
|
@@ -528,6 +530,8 @@ export declare class UniversalPropertyOptionsBuilder<in out Value, in out Option
|
|
|
528
530
|
fixedOrderColumn(fixedOrderColumn: string): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
|
|
529
531
|
/** Override default name for pivot table (see {@doclink naming-strategy | Naming Strategy}). */
|
|
530
532
|
pivotTable(pivotTable: string): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
|
|
533
|
+
/** Resolve this read-only to-one relation via a subquery on another entity (see {@doclink relationships#to-one-relations-through-another-entity | To-one relations through another entity}). */
|
|
534
|
+
through(through: () => EntityName): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
|
|
531
535
|
/** Set pivot entity for this relation (see {@doclink collections#custom-pivot-table-entity | Custom pivot table entity}). */
|
|
532
536
|
pivotEntity(pivotEntity: () => EntityName): Pick<UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys>, IncludeKeys>;
|
|
533
537
|
/** Override the default database column name on the owning side (see {@doclink naming-strategy | Naming Strategy}). This option is only for simple properties represented by a single column. */
|
package/entity/defineEntity.js
CHANGED
|
@@ -403,6 +403,10 @@ export class UniversalPropertyOptionsBuilder {
|
|
|
403
403
|
pivotTable(pivotTable) {
|
|
404
404
|
return this.assignOptions({ pivotTable });
|
|
405
405
|
}
|
|
406
|
+
/** Resolve this read-only to-one relation via a subquery on another entity (see {@doclink relationships#to-one-relations-through-another-entity | To-one relations through another entity}). */
|
|
407
|
+
through(through) {
|
|
408
|
+
return this.assignOptions({ through });
|
|
409
|
+
}
|
|
406
410
|
/** Set pivot entity for this relation (see {@doclink collections#custom-pivot-table-entity | Custom pivot table entity}). */
|
|
407
411
|
pivotEntity(pivotEntity) {
|
|
408
412
|
return this.assignOptions({ pivotEntity });
|
package/errors.d.ts
CHANGED
|
@@ -73,6 +73,9 @@ export declare class MetadataError<T extends AnyEntity = AnyEntity> extends Vali
|
|
|
73
73
|
static targetIsAbstract(meta: EntityMetadata, prop: EntityProperty): MetadataError;
|
|
74
74
|
static nonPersistentCompositeProp(meta: EntityMetadata, prop: EntityProperty): MetadataError;
|
|
75
75
|
static propertyTargetsEntityType(meta: EntityMetadata, prop: EntityProperty, target: EntityMetadata): MetadataError;
|
|
76
|
+
static throughRelationMissingProperty(meta: EntityMetadata, prop: EntityProperty, through: EntityMetadata, side: 'owner' | 'target'): MetadataError;
|
|
77
|
+
static throughRelationCompositeTarget(meta: EntityMetadata, prop: EntityProperty): MetadataError;
|
|
78
|
+
static throughRelationInvalidKind(meta: EntityMetadata, prop: EntityProperty): MetadataError;
|
|
76
79
|
static fromMissingOption(meta: EntityMetadata, prop: EntityProperty, option: string): MetadataError;
|
|
77
80
|
static targetKeyOnManyToMany(meta: EntityMetadata, prop: EntityProperty): MetadataError;
|
|
78
81
|
static targetKeyNotUnique(meta: EntityMetadata, prop: EntityProperty, target?: EntityMetadata): MetadataError;
|
package/errors.js
CHANGED
|
@@ -241,6 +241,16 @@ export class MetadataError extends ValidationError {
|
|
|
241
241
|
const suggestion = target.embeddable ? 'Embedded' : 'ManyToOne';
|
|
242
242
|
return this.fromMessage(meta, prop, `is defined as scalar @Property(), but its type is a discovered entity ${target.className}. Maybe you want to use @${suggestion}() decorator instead?`);
|
|
243
243
|
}
|
|
244
|
+
static throughRelationMissingProperty(meta, prop, through, side) {
|
|
245
|
+
const target = side === 'owner' ? meta.className : prop.targetMeta.className;
|
|
246
|
+
return this.fromMessage(meta, prop, `uses 'through' entity ${through.className} which has no ManyToOne property pointing to ${target}`);
|
|
247
|
+
}
|
|
248
|
+
static throughRelationCompositeTarget(meta, prop) {
|
|
249
|
+
return this.fromMessage(meta, prop, `uses 'through' option which is not supported for targets with composite primary key`);
|
|
250
|
+
}
|
|
251
|
+
static throughRelationInvalidKind(meta, prop) {
|
|
252
|
+
return this.fromMessage(meta, prop, `uses 'through' option which is only supported for ManyToOne and OneToOne relations`);
|
|
253
|
+
}
|
|
244
254
|
static fromMissingOption(meta, prop, option) {
|
|
245
255
|
return this.fromMessage(meta, prop, `is missing '${option}' option`);
|
|
246
256
|
}
|
|
@@ -126,6 +126,8 @@ export declare class MetadataDiscovery {
|
|
|
126
126
|
private initVersionProperty;
|
|
127
127
|
private initCustomType;
|
|
128
128
|
private initRelation;
|
|
129
|
+
/** Resolves the `through` option of a virtual to-one relation into a read-only formula property. */
|
|
130
|
+
private initThroughRelation;
|
|
129
131
|
private initColumnType;
|
|
130
132
|
private getMappedType;
|
|
131
133
|
private getPrefix;
|
|
@@ -168,10 +168,11 @@ export class MetadataDiscovery {
|
|
|
168
168
|
// filter names are load-bearing for RLS (policy and session variable names), backfill from the dictionary key
|
|
169
169
|
filtered.forEach(meta => Object.entries(meta.filters).forEach(([key, filter]) => (filter.name ??= key)));
|
|
170
170
|
filtered.forEach(meta => this.initPolicies(meta));
|
|
171
|
-
forEachProp((
|
|
171
|
+
forEachProp((m, p) => {
|
|
172
172
|
this.initDefaultValue(p);
|
|
173
173
|
this.inferTypeFromDefault(p);
|
|
174
174
|
this.initRelation(p);
|
|
175
|
+
this.initThroughRelation(m, p);
|
|
175
176
|
this.initColumnType(p);
|
|
176
177
|
});
|
|
177
178
|
forEachProp((m, p) => this.initIndexes(m, p));
|
|
@@ -223,11 +224,9 @@ export class MetadataDiscovery {
|
|
|
223
224
|
};
|
|
224
225
|
const missing = [];
|
|
225
226
|
this.#discovered.forEach(meta => Object.values(meta.properties).forEach(prop => {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const target = typeof
|
|
229
|
-
? pivotEntity()
|
|
230
|
-
: 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;
|
|
231
230
|
if (!this.#discovered.find(m => m.className === Utils.className(target)) || !discoveredByIdentity(target)) {
|
|
232
231
|
missing.push(target);
|
|
233
232
|
}
|
|
@@ -2078,6 +2077,58 @@ export class MetadataDiscovery {
|
|
|
2078
2077
|
}
|
|
2079
2078
|
}
|
|
2080
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
|
+
}
|
|
2081
2132
|
initColumnType(prop) {
|
|
2082
2133
|
this.initUnsigned(prop);
|
|
2083
2134
|
// Get the target properties for FK relations - use targetKey property if specified, otherwise PKs
|
package/metadata/types.d.ts
CHANGED
|
@@ -431,9 +431,15 @@ interface PolymorphicOptions {
|
|
|
431
431
|
*/
|
|
432
432
|
discriminatorMap?: Dictionary<string>;
|
|
433
433
|
}
|
|
434
|
-
export interface ManyToOneOptions<Owner, Target> extends ReferenceOptions<Owner, Target>, PolymorphicOptions {
|
|
434
|
+
export interface ManyToOneOptions<Owner, Target, Through = Target> extends ReferenceOptions<Owner, Target>, PolymorphicOptions {
|
|
435
435
|
/** Point to the inverse side property name. */
|
|
436
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>[];
|
|
437
443
|
/** Wrap the entity in {@apilink Reference} wrapper. */
|
|
438
444
|
ref?: boolean;
|
|
439
445
|
/** Use this relation as a primary key. */
|
|
@@ -485,9 +491,15 @@ export interface OneToManyOptions<Owner, Target> extends ReferenceOptions<Owner,
|
|
|
485
491
|
/** Point to the owning side property name. */
|
|
486
492
|
mappedBy: (string & keyof Target) | ((e: Target) => any);
|
|
487
493
|
}
|
|
488
|
-
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 {
|
|
489
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. */
|
|
490
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>[];
|
|
491
503
|
/** Point to the inverse side property name. */
|
|
492
504
|
inversedBy?: (string & keyof Target) | ((e: Target) => any);
|
|
493
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-dev.
|
|
3
|
+
"version": "7.2.0-dev.16",
|
|
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';
|
|
@@ -275,6 +275,11 @@ export declare abstract class Platform {
|
|
|
275
275
|
formatQuery(sql: string, params: readonly any[]): string;
|
|
276
276
|
/** Deep-clones embeddable data and tags it for JSON serialization. */
|
|
277
277
|
cloneEmbeddable<T>(data: T): T;
|
|
278
|
+
/**
|
|
279
|
+
* Builds the correlated subquery used as the formula of a virtual to-one relation defined via `through`.
|
|
280
|
+
* @internal
|
|
281
|
+
*/
|
|
282
|
+
getThroughRelationFormula(prop: EntityProperty, columns: FormulaColumns<any>): string;
|
|
278
283
|
/** Initializes the platform with the ORM configuration. */
|
|
279
284
|
setConfig(config: Configuration): void;
|
|
280
285
|
/** Returns the current ORM configuration. */
|
package/platforms/Platform.js
CHANGED
|
@@ -616,6 +616,14 @@ export class Platform {
|
|
|
616
616
|
Object.defineProperty(copy, JsonProperty, { enumerable: false, value: true });
|
|
617
617
|
return copy;
|
|
618
618
|
}
|
|
619
|
+
/**
|
|
620
|
+
* Builds the correlated subquery used as the formula of a virtual to-one relation defined via `through`.
|
|
621
|
+
* @internal
|
|
622
|
+
*/
|
|
623
|
+
/* v8 ignore next 3 */
|
|
624
|
+
getThroughRelationFormula(prop, columns) {
|
|
625
|
+
throw new Error(`${this.constructor.name} does not support the 'through' option of ${prop.name}`);
|
|
626
|
+
}
|
|
619
627
|
/** Initializes the platform with the ORM configuration. */
|
|
620
628
|
setConfig(config) {
|
|
621
629
|
this.config = config;
|
package/typings.d.ts
CHANGED
|
@@ -653,6 +653,16 @@ export type SerializeDTO<T, H extends string = never, E extends string = never,
|
|
|
653
653
|
};
|
|
654
654
|
type TargetKeys<T> = T extends EntityClass<infer P> ? keyof P : keyof T;
|
|
655
655
|
type PropertyName<T> = IsUnknown<T> extends false ? TargetKeys<T> : string;
|
|
656
|
+
/** Resolved `through` option of a virtual to-one relation, populated during discovery. */
|
|
657
|
+
export interface ThroughRelation {
|
|
658
|
+
entity: EntityClass;
|
|
659
|
+
where?: FilterQuery<any>;
|
|
660
|
+
orderBy?: QueryOrderMap<any>[];
|
|
661
|
+
/** M:1 property on the `through` entity pointing back to the owner. */
|
|
662
|
+
ownerProperty: string;
|
|
663
|
+
/** M:1 property on the `through` entity pointing to the target, undefined when the target is selected directly. */
|
|
664
|
+
targetProperty?: string;
|
|
665
|
+
}
|
|
656
666
|
/** Table reference object passed to formula callbacks, including alias and schema information. */
|
|
657
667
|
export type FormulaTable = {
|
|
658
668
|
alias: string;
|
|
@@ -1043,6 +1053,7 @@ export interface EntityProperty<Owner = any, Target = any> {
|
|
|
1043
1053
|
fixedOrderColumn?: string;
|
|
1044
1054
|
pivotTable: string;
|
|
1045
1055
|
pivotEntity: EntityClass<Target>;
|
|
1056
|
+
through?: ThroughRelation;
|
|
1046
1057
|
joinColumns: string[];
|
|
1047
1058
|
ownColumns: string[];
|
|
1048
1059
|
inverseJoinColumns: string[];
|
package/utils/Utils.js
CHANGED
|
@@ -153,7 +153,7 @@ export function parseJsonSafe(value) {
|
|
|
153
153
|
/** Collection of general-purpose utility methods used throughout the ORM. */
|
|
154
154
|
export class Utils {
|
|
155
155
|
static PK_SEPARATOR = '~~~';
|
|
156
|
-
static #ORM_VERSION = '7.2.0-dev.
|
|
156
|
+
static #ORM_VERSION = '7.2.0-dev.16';
|
|
157
157
|
/** Default session variable name backing an RLS filter argument (`current_setting('mikro.<filter>.<arg>')`). */
|
|
158
158
|
static getRlsSettingName(filterName, argName) {
|
|
159
159
|
return `mikro.${filterName}.${argName}`;
|