@mikro-orm/core 7.0.0-dev.229 → 7.0.0-dev.230
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.js +7 -2
- package/entity/Collection.d.ts +0 -1
- package/entity/Collection.js +5 -9
- package/entity/EntityLoader.js +5 -6
- package/entity/defineEntity.d.ts +13 -6
- package/metadata/types.d.ts +11 -0
- package/package.json +1 -1
- package/typings.d.ts +5 -0
- package/utils/QueryHelper.d.ts +6 -0
- package/utils/QueryHelper.js +25 -0
- package/utils/Utils.js +1 -1
package/EntityManager.js
CHANGED
|
@@ -117,7 +117,13 @@ export class EntityManager {
|
|
|
117
117
|
await em.tryFlush(entityName, options);
|
|
118
118
|
where = await em.processWhere(entityName, where, options, 'read');
|
|
119
119
|
validateParams(where);
|
|
120
|
-
|
|
120
|
+
const meta = this.metadata.get(entityName);
|
|
121
|
+
if (meta.orderBy) {
|
|
122
|
+
options.orderBy = QueryHelper.mergeOrderBy(options.orderBy, meta.orderBy);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
options.orderBy ??= {};
|
|
126
|
+
}
|
|
121
127
|
options.populate = await em.preparePopulate(entityName, options);
|
|
122
128
|
const populate = options.populate;
|
|
123
129
|
const cacheKey = em.cacheKey(entityName, options, 'em.find', where);
|
|
@@ -131,7 +137,6 @@ export class EntityManager {
|
|
|
131
137
|
});
|
|
132
138
|
return cached.data;
|
|
133
139
|
}
|
|
134
|
-
const meta = this.metadata.get(entityName);
|
|
135
140
|
options = { ...options };
|
|
136
141
|
// save the original hint value so we know it was infer/all
|
|
137
142
|
options._populateWhere = options.populateWhere ?? this.config.get('populateWhere');
|
package/entity/Collection.d.ts
CHANGED
|
@@ -62,7 +62,6 @@ export declare class Collection<T extends object, O extends object = object> {
|
|
|
62
62
|
init<TT extends T, P extends string = never>(options?: InitCollectionOptions<TT, P>): Promise<LoadedCollection<Loaded<TT, P>>>;
|
|
63
63
|
private getEntityManager;
|
|
64
64
|
private createCondition;
|
|
65
|
-
private createOrderBy;
|
|
66
65
|
private createManyToManyCondition;
|
|
67
66
|
private createLoadCountCondition;
|
|
68
67
|
private checkInitialized;
|
package/entity/Collection.js
CHANGED
|
@@ -4,7 +4,6 @@ import { DataloaderType, ReferenceKind } from '../enums.js';
|
|
|
4
4
|
import { Reference } from './Reference.js';
|
|
5
5
|
import { helper, wrap } from './wrap.js';
|
|
6
6
|
import { QueryHelper } from '../utils/QueryHelper.js';
|
|
7
|
-
import { Raw } from '../utils/RawQueryFragment.js';
|
|
8
7
|
import { inspect } from '../logging/inspect.js';
|
|
9
8
|
export class Collection {
|
|
10
9
|
owner;
|
|
@@ -93,9 +92,10 @@ export class Collection {
|
|
|
93
92
|
async matching(options) {
|
|
94
93
|
const em = this.getEntityManager();
|
|
95
94
|
const { where, ctx, ...opts } = options;
|
|
96
|
-
opts.orderBy = this.createOrderBy(opts.orderBy);
|
|
97
95
|
let items;
|
|
98
96
|
if (this.property.kind === ReferenceKind.MANY_TO_MANY && em.getPlatform().usesPivotTable()) {
|
|
97
|
+
// M:N via pivot table bypasses em.find(), so merge all 3 levels here
|
|
98
|
+
opts.orderBy = QueryHelper.mergeOrderBy(opts.orderBy, this.property.orderBy, this.property.targetMeta?.orderBy);
|
|
99
99
|
options.populate = await em.preparePopulate(this.property.targetMeta.class, options);
|
|
100
100
|
const cond = await em.applyFilters(this.property.targetMeta.class, where, options.filters ?? {}, 'read');
|
|
101
101
|
const map = await em.getDriver().loadFromPivotTable(this.property, [helper(this.owner).__primaryKeys], cond, opts.orderBy, ctx, options);
|
|
@@ -103,6 +103,8 @@ export class Collection {
|
|
|
103
103
|
await em.populate(items, options.populate, options);
|
|
104
104
|
}
|
|
105
105
|
else {
|
|
106
|
+
// em.find() merges entity-level orderBy, so only merge runtime + relation here
|
|
107
|
+
opts.orderBy = QueryHelper.mergeOrderBy(opts.orderBy, this.property.orderBy);
|
|
106
108
|
items = await em.find(this.property.targetMeta.class, this.createCondition(where), opts);
|
|
107
109
|
}
|
|
108
110
|
if (options.store) {
|
|
@@ -236,7 +238,7 @@ export class Collection {
|
|
|
236
238
|
options = { ...options, filters: QueryHelper.mergePropertyFilters(this.property.filters, options.filters) };
|
|
237
239
|
if (options.dataloader ?? [DataloaderType.ALL, DataloaderType.COLLECTION].includes(em.config.getDataloaderType())) {
|
|
238
240
|
const order = [...this.items]; // copy order of references
|
|
239
|
-
const orderBy =
|
|
241
|
+
const orderBy = QueryHelper.mergeOrderBy(options.orderBy, this.property.orderBy, this.property.targetMeta?.orderBy);
|
|
240
242
|
const customOrder = orderBy.length > 0;
|
|
241
243
|
const pivotTable = this.property.kind === ReferenceKind.MANY_TO_MANY && em.getPlatform().usesPivotTable();
|
|
242
244
|
const loader = await em.getDataLoader(pivotTable ? 'm:n' : '1:m');
|
|
@@ -300,12 +302,6 @@ export class Collection {
|
|
|
300
302
|
}
|
|
301
303
|
return cond;
|
|
302
304
|
}
|
|
303
|
-
createOrderBy(orderBy = []) {
|
|
304
|
-
if (Utils.isEmpty(orderBy) && !Raw.hasObjectFragments(orderBy) && this.property.orderBy) {
|
|
305
|
-
orderBy = this.property.orderBy;
|
|
306
|
-
}
|
|
307
|
-
return Utils.asArray(orderBy);
|
|
308
|
-
}
|
|
309
305
|
createManyToManyCondition(cond) {
|
|
310
306
|
const dict = cond;
|
|
311
307
|
if (this.property.owner || this.property.pivotTable) {
|
package/entity/EntityLoader.js
CHANGED
|
@@ -141,7 +141,8 @@ export class EntityLoader {
|
|
|
141
141
|
.flatMap(orderBy => orderBy[prop.name]);
|
|
142
142
|
const where = await this.extractChildCondition(options, prop);
|
|
143
143
|
if (prop.kind === ReferenceKind.MANY_TO_MANY && this.driver.getPlatform().usesPivotTable()) {
|
|
144
|
-
const
|
|
144
|
+
const pivotOrderBy = QueryHelper.mergeOrderBy(innerOrderBy, prop.orderBy, prop.targetMeta?.orderBy);
|
|
145
|
+
const res = await this.findChildrenFromPivotTable(filtered, prop, options, pivotOrderBy, populate, !!ref);
|
|
145
146
|
return Utils.flatten(res);
|
|
146
147
|
}
|
|
147
148
|
if (prop.polymorphic && prop.polymorphTargets) {
|
|
@@ -152,7 +153,8 @@ export class EntityLoader {
|
|
|
152
153
|
where,
|
|
153
154
|
orderBy: innerOrderBy,
|
|
154
155
|
}, !!(ref || prop.mapToPk));
|
|
155
|
-
|
|
156
|
+
const customOrder = innerOrderBy.length > 0 || !!prop.orderBy || !!prop.targetMeta?.orderBy;
|
|
157
|
+
this.initializeCollections(filtered, prop, field, items, customOrder, partial);
|
|
156
158
|
return items;
|
|
157
159
|
}
|
|
158
160
|
async populateScalar(meta, filtered, options) {
|
|
@@ -327,10 +329,7 @@ export class EntityLoader {
|
|
|
327
329
|
if (!Utils.isEmpty(prop.where) || Raw.hasObjectFragments(prop.where)) {
|
|
328
330
|
where = { $and: [where, prop.where] };
|
|
329
331
|
}
|
|
330
|
-
const orderBy =
|
|
331
|
-
// skip consecutive ordering with the same key to get around mongo issues
|
|
332
|
-
return idx === 0 || !Utils.equals(Utils.getObjectQueryKeys(array[idx - 1]), Utils.getObjectQueryKeys(order));
|
|
333
|
-
});
|
|
332
|
+
const orderBy = QueryHelper.mergeOrderBy(options.orderBy, prop.orderBy);
|
|
334
333
|
const items = await this.em.find(meta.class, where, {
|
|
335
334
|
filters, convertCustomTypes, lockMode, populateWhere, logging,
|
|
336
335
|
orderBy,
|
package/entity/defineEntity.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { AnyString, GeneratedColumnCallback, Constructor, CheckCallback, Fi
|
|
|
4
4
|
import type { Raw } from '../utils/RawQueryFragment.js';
|
|
5
5
|
import type { ScalarReference } from './Reference.js';
|
|
6
6
|
import type { SerializeOptions } from '../serialization/EntitySerializer.js';
|
|
7
|
-
import type { Cascade, DeferMode, EmbeddedPrefixMode, LoadStrategy, QueryOrderMap } from '../enums.js';
|
|
7
|
+
import type { Cascade, DeferMode, EmbeddedPrefixMode, LoadStrategy, QueryOrderKeysFlat, QueryOrderMap } from '../enums.js';
|
|
8
8
|
import type { EventSubscriber } from '../events/EventSubscriber.js';
|
|
9
9
|
import type { IType, Type } from '../types/Type.js';
|
|
10
10
|
import { types } from '../types/index.js';
|
|
@@ -419,7 +419,9 @@ declare const propertyBuilders: {
|
|
|
419
419
|
interval: () => UniversalPropertyOptionsBuilder<string, EmptyOptions, IncludeKeysForProperty>;
|
|
420
420
|
unknown: () => UniversalPropertyOptionsBuilder<{}, EmptyOptions, IncludeKeysForProperty>;
|
|
421
421
|
};
|
|
422
|
-
|
|
422
|
+
/** Own keys + base entity keys (when TBase is not `never`). Guards against `keyof never = string | number | symbol`. */
|
|
423
|
+
type AllKeys<TProperties, TBase> = keyof TProperties | (IsNever<TBase> extends true ? never : keyof TBase);
|
|
424
|
+
export interface EntityMetadataWithProperties<TName extends string, TTableName extends string, TProperties extends Record<string, any>, TPK extends (keyof TProperties)[] | undefined = undefined, TBase = never, TRepository = never> extends Omit<Partial<EntityMetadata<InferEntityFromProperties<TProperties, TPK, TBase, TRepository>>>, 'properties' | 'extends' | 'primaryKeys' | 'hooks' | 'discriminatorColumn' | 'versionProperty' | 'concurrencyCheckKeys' | 'serializedPrimaryKey' | 'indexes' | 'uniques' | 'repository' | 'orderBy'> {
|
|
423
425
|
name: TName;
|
|
424
426
|
tableName?: TTableName;
|
|
425
427
|
extends?: {
|
|
@@ -430,10 +432,15 @@ export interface EntityMetadataWithProperties<TName extends string, TTableName e
|
|
|
430
432
|
hooks?: DefineEntityHooks;
|
|
431
433
|
repository?: () => TRepository;
|
|
432
434
|
inheritance?: 'tpt';
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
435
|
+
orderBy?: {
|
|
436
|
+
[K in Extract<AllKeys<TProperties, TBase>, string>]?: QueryOrderKeysFlat;
|
|
437
|
+
} | {
|
|
438
|
+
[K in Extract<AllKeys<TProperties, TBase>, string>]?: QueryOrderKeysFlat;
|
|
439
|
+
}[];
|
|
440
|
+
discriminatorColumn?: string;
|
|
441
|
+
versionProperty?: AllKeys<TProperties, TBase>;
|
|
442
|
+
concurrencyCheckKeys?: Set<AllKeys<TProperties, TBase>>;
|
|
443
|
+
serializedPrimaryKey?: AllKeys<TProperties, TBase>;
|
|
437
444
|
indexes?: {
|
|
438
445
|
properties?: keyof TProperties | (keyof TProperties)[];
|
|
439
446
|
name?: string;
|
package/metadata/types.d.ts
CHANGED
|
@@ -11,6 +11,17 @@ export type EntityOptions<T, E = T extends EntityClass<infer P> ? P : T> = {
|
|
|
11
11
|
schema?: string;
|
|
12
12
|
/** Override default collection/table name. Alias for `tableName`. */
|
|
13
13
|
collection?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Set default ordering for this entity. This ordering is applied when:
|
|
16
|
+
* - Querying the entity directly via `em.find()`, `em.findAll()`, etc.
|
|
17
|
+
* - Populating the entity as a relation
|
|
18
|
+
*
|
|
19
|
+
* All orderings are combined together. Precedence (highest to lowest):
|
|
20
|
+
* 1. Runtime `FindOptions.orderBy`
|
|
21
|
+
* 2. Relation-level `@OneToMany({ orderBy })` / `@ManyToMany({ orderBy })`
|
|
22
|
+
* 3. Entity-level `@Entity({ orderBy })`
|
|
23
|
+
*/
|
|
24
|
+
orderBy?: QueryOrderMap<E> | QueryOrderMap<E>[];
|
|
14
25
|
/** For {@doclink inheritance-mapping#single-table-inheritance | Single Table Inheritance}. */
|
|
15
26
|
discriminatorColumn?: (T extends EntityClass<infer P> ? keyof P : string) | AnyString;
|
|
16
27
|
/** For {@doclink inheritance-mapping#single-table-inheritance | Single Table Inheritance}. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/core",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "7.0.0-dev.
|
|
4
|
+
"version": "7.0.0-dev.230",
|
|
5
5
|
"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.",
|
|
6
6
|
"exports": {
|
|
7
7
|
"./package.json": "./package.json",
|
package/typings.d.ts
CHANGED
|
@@ -681,6 +681,11 @@ export interface EntityMetadata<Entity = any, Class extends EntityCtor<Entity> =
|
|
|
681
681
|
/** For TPT: properties defined only in THIS entity (not inherited from parent). */
|
|
682
682
|
ownProps?: EntityProperty<Entity>[];
|
|
683
683
|
hasTriggers?: boolean;
|
|
684
|
+
/**
|
|
685
|
+
* Default ordering for this entity. Applied when querying this entity directly
|
|
686
|
+
* or when it's populated as a relation. Combined with other orderings based on precedence.
|
|
687
|
+
*/
|
|
688
|
+
orderBy?: QueryOrderMap<Entity> | QueryOrderMap<Entity>[];
|
|
684
689
|
/** @internal can be used for computed numeric cache keys */
|
|
685
690
|
readonly _id: number;
|
|
686
691
|
}
|
package/utils/QueryHelper.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Dictionary, EntityMetadata, EntityName, EntityProperty, FilterDef, FilterQuery } from '../typings.js';
|
|
2
|
+
import { type QueryOrderMap } from '../enums.js';
|
|
2
3
|
import type { Platform } from '../platforms/Platform.js';
|
|
3
4
|
import type { MetadataStorage } from '../metadata/MetadataStorage.js';
|
|
4
5
|
import type { FilterOptions } from '../drivers/IDatabaseDriver.js';
|
|
@@ -26,6 +27,11 @@ export declare class QueryHelper {
|
|
|
26
27
|
private static processJsonCondition;
|
|
27
28
|
private static getValueType;
|
|
28
29
|
static findProperty<T>(fieldName: string, options: ProcessWhereOptions<T>): EntityProperty<T> | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Merges multiple orderBy sources with key-level deduplication (first-seen key wins).
|
|
32
|
+
* RawQueryFragment symbol keys are never deduped (each is unique).
|
|
33
|
+
*/
|
|
34
|
+
static mergeOrderBy<T>(...sources: (QueryOrderMap<T> | QueryOrderMap<T>[] | undefined)[]): QueryOrderMap<T>[];
|
|
29
35
|
}
|
|
30
36
|
interface ProcessWhereOptions<T> {
|
|
31
37
|
where: FilterQuery<T>;
|
package/utils/QueryHelper.js
CHANGED
|
@@ -300,4 +300,29 @@ export class QueryHelper {
|
|
|
300
300
|
const meta = entityName ? options.metadata.find(entityName) : undefined;
|
|
301
301
|
return meta?.properties[propName];
|
|
302
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* Merges multiple orderBy sources with key-level deduplication (first-seen key wins).
|
|
305
|
+
* RawQueryFragment symbol keys are never deduped (each is unique).
|
|
306
|
+
*/
|
|
307
|
+
static mergeOrderBy(...sources) {
|
|
308
|
+
const result = [];
|
|
309
|
+
const seenKeys = new Set();
|
|
310
|
+
for (const source of sources) {
|
|
311
|
+
if (source == null) {
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
for (const item of Utils.asArray(source)) {
|
|
315
|
+
for (const key of Utils.getObjectQueryKeys(item)) {
|
|
316
|
+
if (typeof key === 'symbol') {
|
|
317
|
+
result.push({ [key]: item[key] });
|
|
318
|
+
}
|
|
319
|
+
else if (!seenKeys.has(key)) {
|
|
320
|
+
seenKeys.add(key);
|
|
321
|
+
result.push({ [key]: item[key] });
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
303
328
|
}
|
package/utils/Utils.js
CHANGED
|
@@ -123,7 +123,7 @@ export function parseJsonSafe(value) {
|
|
|
123
123
|
}
|
|
124
124
|
export class Utils {
|
|
125
125
|
static PK_SEPARATOR = '~~~';
|
|
126
|
-
static #ORM_VERSION = '7.0.0-dev.
|
|
126
|
+
static #ORM_VERSION = '7.0.0-dev.230';
|
|
127
127
|
/**
|
|
128
128
|
* Checks if the argument is instance of `Object`. Returns false for arrays.
|
|
129
129
|
*/
|