Skip to content

Commit

Permalink
Merge pull request #14436 from Automattic/vkarpov15/gh-14398
Browse files Browse the repository at this point in the history
types: improve the typing of FilterQuery<T> type to prevent it from only getting typed to any
  • Loading branch information
vkarpov15 committed Mar 18, 2024
2 parents 8116374 + fa0bc8d commit e3f3205
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 13 deletions.
36 changes: 28 additions & 8 deletions test/types/queries.test.ts
@@ -1,4 +1,5 @@
import {
Condition,
HydratedDocument,
Schema,
model,
Expand Down Expand Up @@ -84,10 +85,15 @@ Test.find({ parent: { $in: ['0'.repeat(24)] } });
Test.find({ name: { $in: ['Test'] } }).exec().then((res: Array<ITest>) => console.log(res));
Test.find({ tags: 'test' }).exec();
Test.find({ tags: { $in: ['test'] } }).exec();
Test.find({ tags: /test/ }).exec();
Test.find({ tags: { $in: [/test/] } }).exec();

// Implicit `$in`
Test.find({ name: ['Test1', 'Test2'] }).exec();

// Implicit `$in` for regex string
Test.find({ name: [/Test1/, /Test2/] });

Test.find({ name: 'test' }, (err: Error | null, docs: ITest[]) => {
console.log(!!err, docs[0].age);
});
Expand Down Expand Up @@ -307,20 +313,34 @@ function autoTypedQuery() {
}

function gh11964() {
class Repository<T extends { id: string }> {
find(id: string) {
const idCondition: Condition<T['id']> = id as Condition<T['id']>;

// `as` is necessary because `T` can be `{ id: never }`,
// so we need to explicitly coerce
const filter: FilterQuery<T> = { id } as FilterQuery<T>;
}
}
}

function gh14397() {
type Condition<T> = ApplyBasicQueryCasting<T> | QuerySelector<ApplyBasicQueryCasting<T>>; // redefined here because it's not exported by mongoose

type WithId<T extends object> = T & { id: string };

class Repository<T extends object> {
/* ... */
type TestUser = {
name: string;
age: number;
};

find(id: string) {
const idCondition: Condition<WithId<T>>['id'] = id; // error :(
const filter: FilterQuery<WithId<T>> = { id }; // error :(
const id: string = 'Test Id';

/* ... */
}
}
let idCondition: Condition<WithId<TestUser>['id']>;
let filter: FilterQuery<WithId<TestUser>>;

expectAssignable<typeof idCondition>(id);
expectAssignable<typeof filter>({ id });
}

function gh12091() {
Expand Down
22 changes: 17 additions & 5 deletions types/query.d.ts
@@ -1,8 +1,20 @@
declare module 'mongoose' {
import mongodb = require('mongodb');

export type ApplyBasicQueryCasting<T> = T | T[] | (T extends (infer U)[] ? U : any) | any;
type Condition<T> = ApplyBasicQueryCasting<T> | QuerySelector<ApplyBasicQueryCasting<T>>;
type StringQueryTypeCasting = string | RegExp;
type ObjectIdQueryTypeCasting = Types.ObjectId | string;
type UUIDQueryTypeCasting = Types.UUID | string;

type QueryTypeCasting<T> = T extends string
? StringQueryTypeCasting
: T extends Types.ObjectId
? ObjectIdQueryTypeCasting
: T extends Types.UUID
? UUIDQueryTypeCasting
: T | any;

export type ApplyBasicQueryCasting<T> = T | T[] | (T extends (infer U)[] ? QueryTypeCasting<U> : T);
export type Condition<T> = ApplyBasicQueryCasting<QueryTypeCasting<T>> | QuerySelector<ApplyBasicQueryCasting<QueryTypeCasting<T>>>;

type _FilterQuery<T> = {
[P in keyof T]?: Condition<T[P]>;
Expand Down Expand Up @@ -385,7 +397,7 @@ declare module 'mongoose' {
): QueryWithHelpers<Array<DocType>, DocType, THelpers, RawDocType, 'find'>;
find(
filter: FilterQuery<RawDocType>
): QueryWithHelpers<Array<RawDocType>, DocType, THelpers, RawDocType, 'find'>;
): QueryWithHelpers<Array<DocType>, DocType, THelpers, RawDocType, 'find'>;
find(): QueryWithHelpers<Array<DocType>, DocType, THelpers, RawDocType, 'find'>;

/** Declares the query a findOne operation. When executed, returns the first found document. */
Expand Down Expand Up @@ -481,7 +493,7 @@ declare module 'mongoose' {
get(path: string): any;

/** Returns the current query filter (also known as conditions) as a POJO. */
getFilter(): FilterQuery<RawDocType>;
getFilter(): FilterQuery<DocType>;

/** Gets query options. */
getOptions(): QueryOptions<DocType>;
Expand All @@ -490,7 +502,7 @@ declare module 'mongoose' {
getPopulatedPaths(): Array<string>;

/** Returns the current query filter. Equivalent to `getFilter()`. */
getQuery(): FilterQuery<RawDocType>;
getQuery(): FilterQuery<DocType>;

/** Returns the current update operations as a JSON object. */
getUpdate(): UpdateQuery<DocType> | UpdateWithAggregationPipeline | null;
Expand Down

0 comments on commit e3f3205

Please sign in to comment.