diff --git a/test/types/models.test.ts b/test/types/models.test.ts index 218c4c9056..fbe411225f 100644 --- a/test/types/models.test.ts +++ b/test/types/models.test.ts @@ -215,8 +215,8 @@ function find() { Project.find({}); Project.find({ name: 'Hello' }); - // just callback - Project.find((error: CallbackError, result: IProject[]) => console.log(error, result)); + // just callback; this is no longer supported on .find() + expectError(Project.find((error: CallbackError, result: IProject[]) => console.log(error, result))); // filter + projection Project.find({}, undefined); @@ -977,3 +977,29 @@ function testWithLevel1NestedPaths() { 'foo.one': string | null | undefined }>({} as Test2); } + +function gh14764TestFilterQueryRestrictions() { + const TestModel = model<{ validKey: number }>('Test', new Schema({})); + // A key not in the schema should be invalid + expectError(TestModel.find({ invalidKey: 0 })); + // A key not in the schema should be invalid for simple root operators + expectError(TestModel.find({ $and: [{ invalidKey: 0 }] })); + + // Any "nested" keys should be valid + TestModel.find({ 'validKey.subkey': 0 }); + + // And deeply "nested" keys should be valid + TestModel.find({ 'validKey.deep.nested.key': 0 }); + TestModel.find({ validKey: { deep: { nested: { key: 0 } } } }); + + // Any Query should be accepted as the root argument (due to merge support) + TestModel.find(TestModel.find()); + // A Query should not be a valid type for a FilterQuery within an op like $and + expectError(TestModel.find({ $and: [TestModel.find()] })); + + const id = new Types.ObjectId(); + // Any ObjectId should be accepted as the root argument + TestModel.find(id); + // A ObjectId should not be a valid type for a FilterQuery within an op like $and + expectError(TestModel.find({ $and: [id] })); +} diff --git a/test/types/queryhelpers.test.ts b/test/types/queryhelpers.test.ts index c96aaffbaa..34a55b6bd2 100644 --- a/test/types/queryhelpers.test.ts +++ b/test/types/queryhelpers.test.ts @@ -8,7 +8,7 @@ interface Project { type ProjectModelType = Model; // Query helpers should return `Query> & ProjectQueryHelpers` // to enable chaining. -type ProjectModelQuery = Query, ProjectQueryHelpers> & ProjectQueryHelpers; +type ProjectModelQuery = Query, ProjectQueryHelpers, any> & ProjectQueryHelpers; interface ProjectQueryHelpers { byName(this: ProjectModelQuery, name: string): ProjectModelQuery; } diff --git a/types/models.d.ts b/types/models.d.ts index c042305a82..4c2403fd51 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -188,7 +188,7 @@ declare module 'mongoose' { export interface ReplaceOneModel { /** The filter to limit the replaced document. */ - filter: FilterQuery; + filter: RootFilterQuery; /** The document with which to replace the matched document. */ replacement: mongodb.WithoutId; /** Specifies a collation. */ @@ -203,7 +203,7 @@ declare module 'mongoose' { export interface UpdateOneModel { /** The filter to limit the updated documents. */ - filter: FilterQuery; + filter: RootFilterQuery; /** A document or pipeline containing update operators. */ update: UpdateQuery; /** A set of filters specifying to which array elements an update should apply. */ @@ -220,7 +220,7 @@ declare module 'mongoose' { export interface UpdateManyModel { /** The filter to limit the updated documents. */ - filter: FilterQuery; + filter: RootFilterQuery; /** A document or pipeline containing update operators. */ update: UpdateQuery; /** A set of filters specifying to which array elements an update should apply. */ @@ -237,7 +237,7 @@ declare module 'mongoose' { export interface DeleteOneModel { /** The filter to limit the deleted documents. */ - filter: FilterQuery; + filter: RootFilterQuery; /** Specifies a collation. */ collation?: mongodb.CollationOptions; /** The index to use. If specified, then the query system will only consider plans using the hinted index. */ @@ -246,7 +246,7 @@ declare module 'mongoose' { export interface DeleteManyModel { /** The filter to limit the deleted documents. */ - filter: FilterQuery; + filter: RootFilterQuery; /** Specifies a collation. */ collation?: mongodb.CollationOptions; /** The index to use. If specified, then the query system will only consider plans using the hinted index. */ @@ -318,7 +318,7 @@ declare module 'mongoose' { /** Creates a `countDocuments` query: counts the number of documents that match `filter`. */ countDocuments( - filter?: FilterQuery, + filter?: RootFilterQuery, options?: (mongodb.CountOptions & MongooseBaseQueryOptions) | null ): QueryWithHelpers< number, @@ -357,7 +357,7 @@ declare module 'mongoose' { * regardless of the `single` option. */ deleteMany( - filter?: FilterQuery, + filter?: RootFilterQuery, options?: (mongodb.DeleteOptions & MongooseBaseQueryOptions) | null ): QueryWithHelpers< mongodb.DeleteResult, @@ -368,7 +368,7 @@ declare module 'mongoose' { TInstanceMethods >; deleteMany( - filter: FilterQuery + filter: RootFilterQuery ): QueryWithHelpers< mongodb.DeleteResult, THydratedDocumentType, @@ -384,7 +384,7 @@ declare module 'mongoose' { * `single` option. */ deleteOne( - filter?: FilterQuery, + filter?: RootFilterQuery, options?: (mongodb.DeleteOptions & MongooseBaseQueryOptions) | null ): QueryWithHelpers< mongodb.DeleteResult, @@ -395,7 +395,7 @@ declare module 'mongoose' { TInstanceMethods >; deleteOne( - filter: FilterQuery + filter: RootFilterQuery ): QueryWithHelpers< mongodb.DeleteResult, THydratedDocumentType, @@ -446,7 +446,7 @@ declare module 'mongoose' { /** Finds one document. */ findOne( - filter: FilterQuery, + filter: RootFilterQuery, projection: ProjectionType | null | undefined, options: QueryOptions & { lean: true } ): QueryWithHelpers< @@ -458,16 +458,16 @@ declare module 'mongoose' { TInstanceMethods >; findOne( - filter?: FilterQuery, + filter?: RootFilterQuery, projection?: ProjectionType | null, options?: QueryOptions | null ): QueryWithHelpers; findOne( - filter?: FilterQuery, + filter?: RootFilterQuery, projection?: ProjectionType | null ): QueryWithHelpers; findOne( - filter?: FilterQuery + filter?: RootFilterQuery ): QueryWithHelpers; /** @@ -621,7 +621,7 @@ declare module 'mongoose' { /** Creates a `distinct` query: returns the distinct values of the given `field` that match `filter`. */ distinct( field: DocKey, - filter?: FilterQuery, + filter?: RootFilterQuery, options?: QueryOptions ): QueryWithHelpers< Array< @@ -651,7 +651,7 @@ declare module 'mongoose' { * the given `filter`, and `null` otherwise. */ exists( - filter: FilterQuery + filter: RootFilterQuery ): QueryWithHelpers< { _id: InferId } | null, THydratedDocumentType, @@ -663,7 +663,7 @@ declare module 'mongoose' { /** Creates a `find` query: gets a list of documents that match `filter`. */ find( - filter: FilterQuery, + filter: RootFilterQuery, projection: ProjectionType | null | undefined, options: QueryOptions & { lean: true } ): QueryWithHelpers< @@ -675,16 +675,16 @@ declare module 'mongoose' { TInstanceMethods >; find( - filter: FilterQuery, + filter: RootFilterQuery, projection?: ProjectionType | null | undefined, options?: QueryOptions | null | undefined ): QueryWithHelpers, ResultDoc, TQueryHelpers, TRawDocType, 'find', TInstanceMethods>; find( - filter: FilterQuery, + filter: RootFilterQuery, projection?: ProjectionType | null | undefined ): QueryWithHelpers, ResultDoc, TQueryHelpers, TRawDocType, 'find', TInstanceMethods>; find( - filter: FilterQuery + filter: RootFilterQuery ): QueryWithHelpers, ResultDoc, TQueryHelpers, TRawDocType, 'find', TInstanceMethods>; find( ): QueryWithHelpers, ResultDoc, TQueryHelpers, TRawDocType, 'find', TInstanceMethods>; @@ -712,7 +712,7 @@ declare module 'mongoose' { /** Creates a `findOneAndUpdate` query, filtering by the given `_id`. */ findByIdAndUpdate( - filter: FilterQuery, + filter: RootFilterQuery, update: UpdateQuery, options: QueryOptions & { includeResultMetadata: true, lean: true } ): QueryWithHelpers< @@ -757,7 +757,7 @@ declare module 'mongoose' { /** Creates a `findOneAndDelete` query: atomically finds the given document, deletes it, and returns the document as it was before deletion. */ findOneAndDelete( - filter: FilterQuery, + filter: RootFilterQuery, options: QueryOptions & { lean: true } ): QueryWithHelpers< GetLeanResultType | null, @@ -768,17 +768,17 @@ declare module 'mongoose' { TInstanceMethods >; findOneAndDelete( - filter: FilterQuery, + filter: RootFilterQuery, options: QueryOptions & { includeResultMetadata: true } ): QueryWithHelpers, ResultDoc, TQueryHelpers, TRawDocType, 'findOneAndDelete', TInstanceMethods>; findOneAndDelete( - filter?: FilterQuery | null, + filter?: RootFilterQuery | null, options?: QueryOptions | null ): QueryWithHelpers; /** Creates a `findOneAndReplace` query: atomically finds the given document and replaces it with `replacement`. */ findOneAndReplace( - filter: FilterQuery, + filter: RootFilterQuery, replacement: TRawDocType | AnyObject, options: QueryOptions & { lean: true } ): QueryWithHelpers< @@ -790,24 +790,24 @@ declare module 'mongoose' { TInstanceMethods >; findOneAndReplace( - filter: FilterQuery, + filter: RootFilterQuery, replacement: TRawDocType | AnyObject, options: QueryOptions & { includeResultMetadata: true } ): QueryWithHelpers, ResultDoc, TQueryHelpers, TRawDocType, 'findOneAndReplace', TInstanceMethods>; findOneAndReplace( - filter: FilterQuery, + filter: RootFilterQuery, replacement: TRawDocType | AnyObject, options: QueryOptions & { upsert: true } & ReturnsNewDoc ): QueryWithHelpers; findOneAndReplace( - filter?: FilterQuery, + filter?: RootFilterQuery, replacement?: TRawDocType | AnyObject, options?: QueryOptions | null ): QueryWithHelpers; /** Creates a `findOneAndUpdate` query: atomically find the first document that matches `filter` and apply `update`. */ findOneAndUpdate( - filter: FilterQuery, + filter: RootFilterQuery, update: UpdateQuery, options: QueryOptions & { includeResultMetadata: true, lean: true } ): QueryWithHelpers< @@ -819,7 +819,7 @@ declare module 'mongoose' { TInstanceMethods >; findOneAndUpdate( - filter: FilterQuery, + filter: RootFilterQuery, update: UpdateQuery, options: QueryOptions & { lean: true } ): QueryWithHelpers< @@ -831,24 +831,24 @@ declare module 'mongoose' { TInstanceMethods >; findOneAndUpdate( - filter: FilterQuery, + filter: RootFilterQuery, update: UpdateQuery, options: QueryOptions & { includeResultMetadata: true } ): QueryWithHelpers, ResultDoc, TQueryHelpers, TRawDocType, 'findOneAndUpdate', TInstanceMethods>; findOneAndUpdate( - filter: FilterQuery, + filter: RootFilterQuery, update: UpdateQuery, options: QueryOptions & { upsert: true } & ReturnsNewDoc ): QueryWithHelpers; findOneAndUpdate( - filter?: FilterQuery, + filter?: RootFilterQuery, update?: UpdateQuery, options?: QueryOptions | null ): QueryWithHelpers; /** Creates a `replaceOne` query: finds the first document that matches `filter` and replaces it with `replacement`. */ replaceOne( - filter?: FilterQuery, + filter?: RootFilterQuery, replacement?: TRawDocType | AnyObject, options?: (mongodb.ReplaceOptions & MongooseQueryOptions) | null ): QueryWithHelpers; @@ -861,14 +861,14 @@ declare module 'mongoose' { /** Creates a `updateMany` query: updates all documents that match `filter` with `update`. */ updateMany( - filter?: FilterQuery, + filter?: RootFilterQuery, update?: UpdateQuery | UpdateWithAggregationPipeline, options?: (mongodb.UpdateOptions & MongooseUpdateQueryOptions) | null ): QueryWithHelpers; /** Creates a `updateOne` query: updates the first document that matches `filter` with `update`. */ updateOne( - filter?: FilterQuery, + filter?: RootFilterQuery, update?: UpdateQuery | UpdateWithAggregationPipeline, options?: (mongodb.UpdateOptions & MongooseUpdateQueryOptions) | null ): QueryWithHelpers; diff --git a/types/query.d.ts b/types/query.d.ts index 66a0042d34..adbe3ae297 100644 --- a/types/query.d.ts +++ b/types/query.d.ts @@ -10,9 +10,11 @@ declare module 'mongoose' { * { age: { $gte: 30 } } * ``` */ - type FilterQuery = { + type RootFilterQuery = FilterQuery | Query | Types.ObjectId; + + type FilterQuery ={ [P in keyof T]?: Condition; - } & RootQuerySelector; + } & RootQuerySelector & { _id?: Condition; }; type MongooseBaseQueryOptionKeys = | 'context' @@ -115,8 +117,9 @@ declare module 'mongoose' { /** @see https://www.mongodb.com/docs/manual/reference/operator/query/comment/#op._S_comment */ $comment?: string; // we could not find a proper TypeScript generic to support nested queries e.g. 'user.friends.name' - // this will mark all unrecognized properties as any (including nested queries) - [key: string]: any; + // this will mark all unrecognized properties as any (including nested queries) only if + // they include a "." (to avoid generically allowing any unexpected keys) + [nestedSelector: `${string}.${string}`]: any; }; interface QueryTimestampsConfig { @@ -303,7 +306,7 @@ declare module 'mongoose' { /** Specifies this query as a `countDocuments` query. */ countDocuments( - criteria?: FilterQuery, + criteria?: RootFilterQuery, options?: QueryOptions ): QueryWithHelpers; @@ -319,10 +322,10 @@ declare module 'mongoose' { * collection, regardless of the value of `single`. */ deleteMany( - filter?: FilterQuery, + filter?: RootFilterQuery, options?: QueryOptions ): QueryWithHelpers; - deleteMany(filter: FilterQuery): QueryWithHelpers< + deleteMany(filter: RootFilterQuery): QueryWithHelpers< any, DocType, THelpers, @@ -338,10 +341,10 @@ declare module 'mongoose' { * option. */ deleteOne( - filter?: FilterQuery, + filter?: RootFilterQuery, options?: QueryOptions ): QueryWithHelpers; - deleteOne(filter: FilterQuery): QueryWithHelpers< + deleteOne(filter: RootFilterQuery): QueryWithHelpers< any, DocType, THelpers, @@ -354,7 +357,7 @@ declare module 'mongoose' { /** Creates a `distinct` query: returns the distinct values of the given `field` that match `filter`. */ distinct( field: DocKey, - filter?: FilterQuery, + filter?: RootFilterQuery, options?: QueryOptions ): QueryWithHelpers< Array< @@ -407,52 +410,52 @@ declare module 'mongoose' { /** Creates a `find` query: gets a list of documents that match `filter`. */ find( - filter: FilterQuery, + filter: RootFilterQuery, projection?: ProjectionType | null, options?: QueryOptions | null ): QueryWithHelpers, DocType, THelpers, RawDocType, 'find', TInstanceMethods>; find( - filter: FilterQuery, + filter: RootFilterQuery, projection?: ProjectionType | null ): QueryWithHelpers, DocType, THelpers, RawDocType, 'find', TInstanceMethods>; find( - filter: FilterQuery + filter: RootFilterQuery ): QueryWithHelpers, DocType, THelpers, RawDocType, 'find', TInstanceMethods>; find(): QueryWithHelpers, DocType, THelpers, RawDocType, 'find', TInstanceMethods>; /** Declares the query a findOne operation. When executed, returns the first found document. */ findOne( - filter?: FilterQuery, + filter?: RootFilterQuery, projection?: ProjectionType | null, options?: QueryOptions | null ): QueryWithHelpers; findOne( - filter?: FilterQuery, + filter?: RootFilterQuery, projection?: ProjectionType | null ): QueryWithHelpers; findOne( - filter?: FilterQuery + filter?: RootFilterQuery ): QueryWithHelpers; /** Creates a `findOneAndDelete` query: atomically finds the given document, deletes it, and returns the document as it was before deletion. */ findOneAndDelete( - filter?: FilterQuery, + filter?: RootFilterQuery, options?: QueryOptions | null ): QueryWithHelpers; /** Creates a `findOneAndUpdate` query: atomically find the first document that matches `filter` and apply `update`. */ findOneAndUpdate( - filter: FilterQuery, + filter: RootFilterQuery, update: UpdateQuery, options: QueryOptions & { includeResultMetadata: true } ): QueryWithHelpers, DocType, THelpers, RawDocType, 'findOneAndUpdate', TInstanceMethods>; findOneAndUpdate( - filter: FilterQuery, + filter: RootFilterQuery, update: UpdateQuery, options: QueryOptions & { upsert: true } & ReturnsNewDoc ): QueryWithHelpers; findOneAndUpdate( - filter?: FilterQuery, + filter?: RootFilterQuery, update?: UpdateQuery, options?: QueryOptions | null ): QueryWithHelpers; @@ -603,7 +606,7 @@ declare module 'mongoose' { maxTimeMS(ms: number): this; /** Merges another Query or conditions object into this one. */ - merge(source: Query | FilterQuery): this; + merge(source: RootFilterQuery): this; /** Specifies a `$mod` condition, filters documents for documents whose `path` property is a number that is equal to `remainder` modulo `divisor`. */ mod(path: K, val: number): this; @@ -722,7 +725,7 @@ declare module 'mongoose' { * not accept any [atomic](https://www.mongodb.com/docs/manual/tutorial/model-data-for-atomic-operations/#pattern) operators (`$set`, etc.) */ replaceOne( - filter?: FilterQuery, + filter?: RootFilterQuery, replacement?: DocType | AnyObject, options?: QueryOptions | null ): QueryWithHelpers; @@ -830,7 +833,7 @@ declare module 'mongoose' { * the `multi` option. */ updateMany( - filter?: FilterQuery, + filter?: RootFilterQuery, update?: UpdateQuery | UpdateWithAggregationPipeline, options?: QueryOptions | null ): QueryWithHelpers; @@ -840,7 +843,7 @@ declare module 'mongoose' { * `update()`, except it does not support the `multi` or `overwrite` options. */ updateOne( - filter?: FilterQuery, + filter?: RootFilterQuery, update?: UpdateQuery | UpdateWithAggregationPipeline, options?: QueryOptions | null ): QueryWithHelpers;