@mikro-orm/core 7.1.16-dev.1 → 7.1.16-dev.10
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 +1 -0
- package/drivers/DatabaseDriver.js +19 -10
- package/entity/EntityLoader.d.ts +7 -1
- package/entity/EntityLoader.js +34 -4
- package/entity/defineEntity.d.ts +6 -4
- package/enums.d.ts +2 -0
- package/enums.js +2 -0
- package/package.json +1 -1
- package/typings.d.ts +1 -0
- package/utils/Utils.js +6 -2
package/EntityManager.js
CHANGED
|
@@ -184,8 +184,14 @@ export class DatabaseDriver {
|
|
|
184
184
|
}, {});
|
|
185
185
|
return { [prop]: value };
|
|
186
186
|
}
|
|
187
|
-
const
|
|
188
|
-
const
|
|
187
|
+
const dirStr = direction.toString().toLowerCase();
|
|
188
|
+
const desc = direction === QueryOrderNumeric.DESC || dirStr.startsWith('desc');
|
|
189
|
+
let dir = Utils.xor(desc, isLast) ? 'desc' : 'asc';
|
|
190
|
+
const nullsFirst = dirStr.includes('nulls first');
|
|
191
|
+
// backward pagination reverses the whole ordering, so the nulls placement flips with the direction
|
|
192
|
+
if (nullsFirst || dirStr.includes('nulls last')) {
|
|
193
|
+
dir += Utils.xor(nullsFirst, isLast) ? ' nulls first' : ' nulls last';
|
|
194
|
+
}
|
|
189
195
|
return { [prop]: dir };
|
|
190
196
|
};
|
|
191
197
|
// the cursor condition is created at the driver level, after the EM already converted custom types
|
|
@@ -251,10 +257,12 @@ export class DatabaseDriver {
|
|
|
251
257
|
Object.assign(o, createCondition(key, direction[key], offset?.[key], eq, `${path}.${key}`, childProps ?? {}, insideJson));
|
|
252
258
|
return o;
|
|
253
259
|
}, {});
|
|
254
|
-
|
|
260
|
+
// an unconstrained group must stay unconstrained, an empty object condition would instead
|
|
261
|
+
// match only rows where every child value is null (e.g. object embeddables)
|
|
262
|
+
return Utils.hasObjectKeys(value) ? { [prop]: value } : {};
|
|
255
263
|
}
|
|
256
|
-
const isDesc = direction === QueryOrderNumeric.DESC || direction.toString().toLowerCase() === 'desc';
|
|
257
264
|
const dirStr = direction.toString().toLowerCase();
|
|
265
|
+
const isDesc = direction === QueryOrderNumeric.DESC || dirStr.startsWith('desc');
|
|
258
266
|
let nullsFirst;
|
|
259
267
|
if (dirStr.includes('nulls first')) {
|
|
260
268
|
nullsFirst = true;
|
|
@@ -274,13 +282,14 @@ export class DatabaseDriver {
|
|
|
274
282
|
offset = this.mapCursorOffset(propMeta, offset, insideJson);
|
|
275
283
|
// Handle null offset (intentional null cursor value)
|
|
276
284
|
if (offset === null) {
|
|
285
|
+
// hasItemsAfterNull: forward + nullsFirst, or backward + nullsLast
|
|
286
|
+
const hasItemsAfterNull = Utils.xor(nullsFirst, inverse);
|
|
277
287
|
if (eq) {
|
|
278
|
-
//
|
|
279
|
-
|
|
288
|
+
// the `>=` half of the keyset condition: every row when the nulls come first in this
|
|
289
|
+
// direction, only the null ones when they come last
|
|
290
|
+
return hasItemsAfterNull ? {} : { [prop]: null };
|
|
280
291
|
}
|
|
281
292
|
// Strict comparison with null cursor value
|
|
282
|
-
// hasItemsAfterNull: forward + nullsFirst, or backward + nullsLast
|
|
283
|
-
const hasItemsAfterNull = Utils.xor(nullsFirst, inverse);
|
|
284
293
|
if (hasItemsAfterNull) {
|
|
285
294
|
return { [prop]: { $ne: null } };
|
|
286
295
|
}
|
|
@@ -422,8 +431,8 @@ export class DatabaseDriver {
|
|
|
422
431
|
const props = prop.embeddedProps;
|
|
423
432
|
let unknownProp = false;
|
|
424
433
|
Object.keys(data[prop.name]).forEach(kk => {
|
|
425
|
-
// explicitly allow `$exists`, `$eq`, `$ne` and `$
|
|
426
|
-
const operator = Object.keys(data[prop.name]).some(f => Utils.isOperator(f) && !['$exists', '$ne', '$eq', '$elemMatch'].includes(f));
|
|
434
|
+
// explicitly allow `$exists`, `$eq`, `$ne`, `$elemMatch` and `$all` operators here as they can't be misused this way
|
|
435
|
+
const operator = Object.keys(data[prop.name]).some(f => Utils.isOperator(f) && !['$exists', '$ne', '$eq', '$elemMatch', '$all'].includes(f));
|
|
427
436
|
if (operator) {
|
|
428
437
|
throw ValidationError.cannotUseOperatorsInsideEmbeddables(meta.class, prop.name, data);
|
|
429
438
|
}
|
package/entity/EntityLoader.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnyEntity, AutoPath, ConnectionType, EntityName, EntityProperty, FilterQuery, PopulateHintOptions, PopulateOptions } from '../typings.js';
|
|
1
|
+
import type { AnyEntity, AutoPath, ConnectionType, EntityName, EntityProperty, FilterQuery, ObjectQuery, PopulateHintOptions, PopulateOptions } from '../typings.js';
|
|
2
2
|
import type { EntityManager } from '../EntityManager.js';
|
|
3
3
|
import { LoadStrategy, type LockMode, type PopulateHint, PopulatePath, type QueryOrderMap } from '../enums.js';
|
|
4
4
|
import type { InflightQueryAbortStrategy } from '../connections/Connection.js';
|
|
@@ -14,6 +14,8 @@ export interface EntityLoaderOptions<Entity, Fields extends string = never, Excl
|
|
|
14
14
|
where?: FilterQuery<Entity>;
|
|
15
15
|
/** Controls how `where` conditions are applied to populated relations. */
|
|
16
16
|
populateWhere?: PopulateHint | `${PopulateHint}`;
|
|
17
|
+
/** @see FindOptions.populateFilter */
|
|
18
|
+
populateFilter?: ObjectQuery<Entity>;
|
|
17
19
|
/** Ordering for populated relations. */
|
|
18
20
|
orderBy?: QueryOrderMap<Entity> | QueryOrderMap<Entity>[];
|
|
19
21
|
/** Whether to reload already loaded entities. */
|
|
@@ -77,6 +79,10 @@ export declare class EntityLoader {
|
|
|
77
79
|
/** @internal */
|
|
78
80
|
findChildrenFromPivotTable<Entity extends object>(filtered: Entity[], prop: EntityProperty<Entity>, options: Required<EntityLoaderOptions<Entity>>, orderBy?: QueryOrderMap<Entity>[], populate?: PopulateOptions<Entity>, pivotJoin?: boolean): Promise<AnyEntity[][]>;
|
|
79
81
|
private extractChildCondition;
|
|
82
|
+
/** Extracts the part of `options.populateFilter` that applies to the given relation. */
|
|
83
|
+
private extractChildPopulateFilter;
|
|
84
|
+
/** Splits an extracted populate filter into the conditions on the populated entity and those on its own relations. */
|
|
85
|
+
private splitPopulateFilter;
|
|
80
86
|
private buildFields;
|
|
81
87
|
private getChildReferences;
|
|
82
88
|
private filterCollections;
|
package/entity/EntityLoader.js
CHANGED
|
@@ -334,7 +334,7 @@ export class EntityLoader {
|
|
|
334
334
|
// When targetKey is set, use it for FK lookup instead of the PK
|
|
335
335
|
let fk = prop.targetKey ?? Utils.getPrimaryKeyHash(meta.primaryKeys);
|
|
336
336
|
let schema = options.schema;
|
|
337
|
-
const partial = !Utils.isEmpty(prop.where) || !Utils.isEmpty(options.where);
|
|
337
|
+
const partial = !Utils.isEmpty(prop.where) || !Utils.isEmpty(options.where) || !Utils.isEmpty(options.populateFilter);
|
|
338
338
|
let polymorphicOwnerProp;
|
|
339
339
|
const ownerProp = prop.kind === ReferenceKind.ONE_TO_MANY || (prop.kind === ReferenceKind.MANY_TO_MANY && !prop.owner)
|
|
340
340
|
? meta.properties[prop.mappedBy]
|
|
@@ -394,12 +394,20 @@ export class EntityLoader {
|
|
|
394
394
|
if (!Utils.isEmpty(prop.where) || Raw.hasObjectFragments(prop.where)) {
|
|
395
395
|
where = { $and: [where, prop.where] };
|
|
396
396
|
}
|
|
397
|
+
const childFilter = options.populateFilter ? await this.extractChildPopulateFilter(options, prop) : undefined;
|
|
398
|
+
// conditions on the populated entity itself have no sink in the child query (the joined strategy puts
|
|
399
|
+
// them on the join), only the nested relation ones can be forwarded as its own `populateFilter`
|
|
400
|
+
const [ownFilter, nestedFilter] = this.splitPopulateFilter(childFilter, meta);
|
|
401
|
+
if (ownFilter) {
|
|
402
|
+
where = { $and: [where, ownFilter] };
|
|
403
|
+
}
|
|
397
404
|
const orderBy = QueryHelper.mergeOrderBy(options.orderBy, prop.orderBy);
|
|
398
405
|
const findOptions = {
|
|
399
406
|
filters,
|
|
400
407
|
convertCustomTypes,
|
|
401
408
|
lockMode,
|
|
402
409
|
populateWhere,
|
|
410
|
+
populateFilter: nestedFilter,
|
|
403
411
|
logging,
|
|
404
412
|
orderBy,
|
|
405
413
|
populate: populate.children ?? populate.all ?? [],
|
|
@@ -575,6 +583,9 @@ export class EntityLoader {
|
|
|
575
583
|
filters,
|
|
576
584
|
ignoreLazyScalarProperties,
|
|
577
585
|
populateWhere,
|
|
586
|
+
populateFilter: options.populateFilter
|
|
587
|
+
? (await this.extractChildPopulateFilter(options, prop))
|
|
588
|
+
: undefined,
|
|
578
589
|
connectionType,
|
|
579
590
|
logging,
|
|
580
591
|
schema,
|
|
@@ -611,7 +622,7 @@ export class EntityLoader {
|
|
|
611
622
|
const fields = this.buildFields(options.fields, prop);
|
|
612
623
|
// oxfmt-ignore
|
|
613
624
|
const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
|
|
614
|
-
const populateFilter = options.populateFilter
|
|
625
|
+
const populateFilter = options.populateFilter ? await this.extractChildPopulateFilter(options, prop) : undefined;
|
|
615
626
|
const options2 = { ...options, fields, exclude, populateFilter };
|
|
616
627
|
['limit', 'offset', 'first', 'last', 'before', 'after', 'overfetch'].forEach(prop => delete options2[prop]);
|
|
617
628
|
options2.populate = populate?.children ?? [];
|
|
@@ -656,7 +667,8 @@ export class EntityLoader {
|
|
|
656
667
|
}
|
|
657
668
|
async extractChildCondition(options, prop, filters = false) {
|
|
658
669
|
const where = options.where;
|
|
659
|
-
|
|
670
|
+
// shallow copy, the operator normalization below must not mutate the caller's condition
|
|
671
|
+
const subCond = Utils.isPlainObject(where[prop.name]) ? { ...where[prop.name] } : {};
|
|
660
672
|
const meta2 = prop.targetMeta;
|
|
661
673
|
const pk = Utils.getPrimaryKeyHash(meta2.primaryKeys);
|
|
662
674
|
['$and', '$or'].forEach(op => {
|
|
@@ -679,8 +691,8 @@ export class EntityLoader {
|
|
|
679
691
|
});
|
|
680
692
|
const operators = Object.keys(subCond).filter(key => Utils.isOperator(key, false));
|
|
681
693
|
if (operators.length > 0) {
|
|
694
|
+
subCond[pk] = Utils.isPlainObject(subCond[pk]) ? { ...subCond[pk] } : (subCond[pk] ?? {});
|
|
682
695
|
operators.forEach(op => {
|
|
683
|
-
subCond[pk] ??= {};
|
|
684
696
|
subCond[pk][op] = subCond[op];
|
|
685
697
|
delete subCond[op];
|
|
686
698
|
});
|
|
@@ -690,6 +702,24 @@ export class EntityLoader {
|
|
|
690
702
|
}
|
|
691
703
|
return subCond;
|
|
692
704
|
}
|
|
705
|
+
/** Extracts the part of `options.populateFilter` that applies to the given relation. */
|
|
706
|
+
async extractChildPopulateFilter(options, prop) {
|
|
707
|
+
const filter = await this.extractChildCondition({ ...options, where: options.populateFilter }, prop);
|
|
708
|
+
return Utils.isEmpty(filter) ? undefined : filter;
|
|
709
|
+
}
|
|
710
|
+
/** Splits an extracted populate filter into the conditions on the populated entity and those on its own relations. */
|
|
711
|
+
splitPopulateFilter(filter, meta) {
|
|
712
|
+
if (!filter) {
|
|
713
|
+
return [undefined, undefined];
|
|
714
|
+
}
|
|
715
|
+
const own = {};
|
|
716
|
+
const nested = {};
|
|
717
|
+
for (const key of Object.keys(filter)) {
|
|
718
|
+
const target = meta.relations.some(rel => rel.name === key) ? nested : own;
|
|
719
|
+
target[key] = filter[key];
|
|
720
|
+
}
|
|
721
|
+
return [Utils.isEmpty(own) ? undefined : own, Utils.isEmpty(nested) ? undefined : nested];
|
|
722
|
+
}
|
|
693
723
|
buildFields(fields = [], prop, ref) {
|
|
694
724
|
if (ref) {
|
|
695
725
|
fields = prop.targetMeta.primaryKeys.map(targetPkName => `${prop.name}.${targetPkName}`);
|
package/entity/defineEntity.d.ts
CHANGED
|
@@ -22,7 +22,7 @@ type HasKind<Options, K extends string> = Options extends {
|
|
|
22
22
|
kind: infer X extends string;
|
|
23
23
|
} ? X extends K ? true : false : false;
|
|
24
24
|
/** Lightweight chain result type for property builders - reduces type instantiation cost by avoiding full class resolution. */
|
|
25
|
-
export interface PropertyChain<Value, Options> {
|
|
25
|
+
export interface PropertyChain<in out Value, in out Options> {
|
|
26
26
|
'~type'?: {
|
|
27
27
|
value: Value;
|
|
28
28
|
};
|
|
@@ -180,7 +180,7 @@ export interface PropertyChain<Value, Options> {
|
|
|
180
180
|
foreignKeyName(foreignKeyName: string): HasKind<Options, 'm:1' | '1:m' | '1:1' | 'm:n'> extends true ? PropertyChain<Value, Options> : never;
|
|
181
181
|
}
|
|
182
182
|
/** @internal */
|
|
183
|
-
export declare class UniversalPropertyOptionsBuilder<Value, Options, IncludeKeys extends BuilderKeys> implements Record<Exclude<UniversalPropertyKeys, ExcludeKeys>, any> {
|
|
183
|
+
export declare class UniversalPropertyOptionsBuilder<in out Value, in out Options, in out IncludeKeys extends BuilderKeys> implements Record<Exclude<UniversalPropertyKeys, ExcludeKeys>, any> {
|
|
184
184
|
'~options': Options;
|
|
185
185
|
'~type'?: {
|
|
186
186
|
value: Value;
|
|
@@ -726,16 +726,18 @@ type InferTypeByString<T extends string> = T extends keyof typeof types ? InferJ
|
|
|
726
726
|
type InferJSType<T> = T extends typeof Type<infer TValue, any> ? NonNullable<TValue> : never;
|
|
727
727
|
type InferColumnType<T extends string> = T extends 'int' | 'int4' | 'integer' | 'bigint' | 'int8' | 'int2' | 'tinyint' | 'smallint' | 'mediumint' ? number : T extends 'double' | 'double precision' | 'real' | 'float8' | 'decimal' | 'numeric' | 'float' | 'float4' ? number : T extends 'datetime' | 'time' | 'time with time zone' | 'timestamp' | 'timestamp with time zone' | 'timetz' | 'timestamptz' | 'date' | 'interval' ? Date : T extends 'ObjectId' | 'objectId' | 'character varying' | 'varchar' | 'char' | 'character' | 'uuid' | 'text' | 'tinytext' | 'mediumtext' | 'longtext' | 'enum' ? string : T extends 'boolean' | 'bool' | 'bit' ? boolean : T extends 'blob' | 'tinyblob' | 'mediumblob' | 'longblob' | 'bytea' ? Buffer : T extends 'point' | 'line' | 'lseg' | 'box' | 'circle' | 'path' | 'polygon' | 'geometry' ? number[] : T extends 'tsvector' | 'tsquery' ? string[] : T extends 'json' | 'jsonb' ? any : any;
|
|
728
728
|
type BaseEntityMethodKeys = 'toObject' | 'toPOJO' | 'serialize' | 'assign' | 'populate' | 'init' | 'toReference';
|
|
729
|
+
interface BaseEntityMethods<in out Entity extends object> extends Pick<IWrappedEntity<Entity>, BaseEntityMethodKeys> {
|
|
730
|
+
}
|
|
729
731
|
/** Infers the entity type from a `defineEntity()` properties map, resolving builders, base classes, and primary keys. */
|
|
730
732
|
export type InferEntityFromProperties<Properties extends Record<string, any>, PK extends (keyof Properties)[] | undefined = undefined, Base = never, Repository = never, ForceObject extends boolean = false, BaseDiscriminatorColumn extends string | undefined = undefined, DiscriminatorValue extends string | number | undefined = undefined, Embeddable extends boolean = false> = (IsNever<Base> extends true ? {} : Base extends {
|
|
731
733
|
toObject(...args: any[]): any;
|
|
732
|
-
} ?
|
|
734
|
+
} ? BaseEntityMethods<{
|
|
733
735
|
-readonly [K in keyof Properties]: InferBuilderValue<MaybeReturnType<Properties[K]>>;
|
|
734
736
|
} & {
|
|
735
737
|
[PrimaryKeyProp]?: InferCombinedPrimaryKey<Properties, PK, Base>;
|
|
736
738
|
} & (IsNever<Repository> extends true ? {} : {
|
|
737
739
|
[EntityRepositoryType]?: Repository extends Constructor<infer R> ? R : Repository;
|
|
738
|
-
}) & NarrowDiscriminator<Omit<Base, typeof PrimaryKeyProp>, BaseDiscriminatorColumn, DiscriminatorValue, Embeddable
|
|
740
|
+
}) & NarrowDiscriminator<Omit<Base, typeof PrimaryKeyProp>, BaseDiscriminatorColumn, DiscriminatorValue, Embeddable>> : {}) & {
|
|
739
741
|
-readonly [K in keyof Properties]: InferBuilderValue<MaybeReturnType<Properties[K]>>;
|
|
740
742
|
} & {
|
|
741
743
|
[PrimaryKeyProp]?: InferCombinedPrimaryKey<Properties, PK, Base>;
|
package/enums.d.ts
CHANGED
|
@@ -39,6 +39,8 @@ export declare enum QueryOperator {
|
|
|
39
39
|
$in = "in",
|
|
40
40
|
/** Not included in the given list. */
|
|
41
41
|
$nin = "not in",
|
|
42
|
+
/** Contains all of the given values, supported on collection properties and on mongo arrays. */
|
|
43
|
+
$all = "all",
|
|
42
44
|
/** Greater than. */
|
|
43
45
|
$gt = ">",
|
|
44
46
|
/** Greater than or equal to. */
|
package/enums.js
CHANGED
|
@@ -41,6 +41,8 @@ export var QueryOperator;
|
|
|
41
41
|
QueryOperator["$in"] = "in";
|
|
42
42
|
/** Not included in the given list. */
|
|
43
43
|
QueryOperator["$nin"] = "not in";
|
|
44
|
+
/** Contains all of the given values, supported on collection properties and on mongo arrays. */
|
|
45
|
+
QueryOperator["$all"] = "all";
|
|
44
46
|
/** Greater than. */
|
|
45
47
|
QueryOperator["$gt"] = ">";
|
|
46
48
|
/** Greater than or equal to. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/core",
|
|
3
|
-
"version": "7.1.16-dev.
|
|
3
|
+
"version": "7.1.16-dev.10",
|
|
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/typings.d.ts
CHANGED
|
@@ -314,6 +314,7 @@ export type OperatorMap<T> = {
|
|
|
314
314
|
$ne?: ExpandScalar<T> | readonly ExpandScalar<T>[] | Subquery;
|
|
315
315
|
$in?: readonly ExpandScalar<T>[] | readonly Primary<T>[] | Raw | Subquery;
|
|
316
316
|
$nin?: readonly ExpandScalar<T>[] | readonly Primary<T>[] | Raw | Subquery;
|
|
317
|
+
$all?: readonly ExpandQuery<T>[];
|
|
317
318
|
$not?: ExpandQuery<T>;
|
|
318
319
|
$none?: ExpandQuery<T>;
|
|
319
320
|
$some?: ExpandQuery<T>;
|
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.1.16-dev.
|
|
156
|
+
static #ORM_VERSION = '7.1.16-dev.10';
|
|
157
157
|
/**
|
|
158
158
|
* Checks if the argument is instance of `Object`. Returns false for arrays.
|
|
159
159
|
*/
|
|
@@ -854,7 +854,11 @@ export class Utils {
|
|
|
854
854
|
return await import(module);
|
|
855
855
|
}
|
|
856
856
|
catch (err) {
|
|
857
|
-
|
|
857
|
+
// only a missing module is expected here, anything else is a real failure inside the module
|
|
858
|
+
if (!['ERR_MODULE_NOT_FOUND', 'MODULE_NOT_FOUND'].includes(err.code)) {
|
|
859
|
+
throw err;
|
|
860
|
+
}
|
|
861
|
+
if (warning) {
|
|
858
862
|
// eslint-disable-next-line no-console
|
|
859
863
|
console.warn(warning);
|
|
860
864
|
}
|