diff --git a/types/mongoose/index.d.ts b/types/mongoose/index.d.ts index 7b1a6abac8..a6ef3740d1 100644 --- a/types/mongoose/index.d.ts +++ b/types/mongoose/index.d.ts @@ -35,6 +35,7 @@ // Thomas Pischulski // Sam Kim // Dongjun Lee +// Valentin Agachi // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped // TypeScript Version: 3.0 @@ -81,6 +82,30 @@ declare module "mongoose" { /* Helper type to extract a definition type from a Document type */ type DocumentDefinition = Omit>; + /** + * Patched version of FilterQuery to also allow: + * - documents, ObjectId and strings for `_id` + * - strings for properties defined as ObjectId + * + * Uses `[]` tuple syntax around the `Extract` to avoid distributing on unions. + * See: https://devblogs.microsoft.com/typescript/announcing-typescript-2-8-2/#conditional-types + * + * Negates the condition with `never`, because the following condition: + * Extract extends mongodb.ObjectId + * Would result in `never extends mongodb.ObjectId` for non-ObjectId properties, + * which would result in a passing conditional, because `never` is included in all types. + */ + export type MongooseFilterQuery = { + [P in keyof T]?: P extends '_id' ? + mongodb.Condition : + [Extract] extends [never] ? + mongodb.Condition : + mongodb.Condition; + } & mongodb.RootQuerySelector; + + /* FilterQuery alias type for using as type for filter/conditions parameters */ + export type FilterQuery = MongooseFilterQuery>; + /** * Gets and optionally overwrites the function used to pluralize collection names * @param fn function to use for pluralization of collection names @@ -1857,7 +1882,7 @@ declare module "mongoose" { * @param criteria mongodb selector */ count(callback?: (err: any, count: number) => void): Query & QueryHelpers; - count(criteria: any, callback?: (err: any, count: number) => void): Query & QueryHelpers; + count(criteria: FilterQuery, callback?: (err: any, count: number) => void): Query & QueryHelpers; /** * Specifies this query as a `countDocuments()` query. Behaves like `count()`, @@ -1879,7 +1904,7 @@ declare module "mongoose" { * @return {Query} this */ countDocuments(callback?: (err: any, count: number) => void): Query & QueryHelpers; - countDocuments(criteria: any, callback?: (err: any, count: number) => void): Query & QueryHelpers; + countDocuments(criteria: FilterQuery, callback?: (err: any, count: number) => void): Query & QueryHelpers; /** * Estimates the number of documents in the MongoDB collection. Faster than @@ -1929,7 +1954,7 @@ declare module "mongoose" { * @param criteria mongodb selector */ find(callback?: (err: any, res: DocType[]) => void): DocumentQuery & QueryHelpers; - find(criteria: any, + find(criteria: FilterQuery, callback?: (err: any, res: DocType[]) => void): DocumentQuery & QueryHelpers; /** @@ -1940,7 +1965,7 @@ declare module "mongoose" { * @param projection optional fields to return */ findOne(callback?: (err: any, res: DocType | null) => void): DocumentQuery & QueryHelpers; - findOne(criteria: any, + findOne(criteria: FilterQuery, callback?: (err: any, res: DocType | null) => void): DocumentQuery & QueryHelpers; /** @@ -1952,12 +1977,12 @@ declare module "mongoose" { * https://mongoosejs.com/docs/api.html#mongoose_Mongoose-set */ findOneAndRemove(callback?: (error: any, doc: DocType | null, result: any) => void): DocumentQuery & QueryHelpers; - findOneAndRemove(conditions: any, + findOneAndRemove(conditions: FilterQuery, callback?: (error: any, doc: DocType | null, result: any) => void): DocumentQuery & QueryHelpers; - findOneAndRemove(conditions: any, options: { rawResult: true } & QueryFindOneAndRemoveOptions, + findOneAndRemove(conditions: FilterQuery, options: { rawResult: true } & QueryFindOneAndRemoveOptions, callback?: (error: any, doc: mongodb.FindAndModifyWriteOpResultObject, result: any) => void) : Query> & QueryHelpers; - findOneAndRemove(conditions: any, options: QueryFindOneAndRemoveOptions, + findOneAndRemove(conditions: FilterQuery, options: QueryFindOneAndRemoveOptions, callback?: (error: any, doc: DocType | null, result: any) => void): DocumentQuery & QueryHelpers; /** @@ -1971,19 +1996,19 @@ declare module "mongoose" { findOneAndUpdate(callback?: (err: any, doc: DocType | null) => void): DocumentQuery & QueryHelpers; findOneAndUpdate(update: any, callback?: (err: any, doc: DocType | null, res: any) => void): DocumentQuery & QueryHelpers; - findOneAndUpdate(query: any, update: any, + findOneAndUpdate(query: FilterQuery, update: any, callback?: (err: any, doc: DocType | null, res: any) => void): DocumentQuery & QueryHelpers; - findOneAndUpdate(query: any, update: any, + findOneAndUpdate(query: FilterQuery, update: any, options: { rawResult: true } & { upsert: true } & { new: true } & QueryFindOneAndUpdateOptions, callback?: (err: any, doc: mongodb.FindAndModifyWriteOpResultObject, res: any) => void) : Query> & QueryHelpers; - findOneAndUpdate(query: any, update: any, + findOneAndUpdate(query: FilterQuery, update: any, options: { upsert: true } & { new: true } & QueryFindOneAndUpdateOptions, callback?: (err: any, doc: DocType, res: any) => void): DocumentQuery & QueryHelpers; - findOneAndUpdate(query: any, update: any, options: { rawResult: true } & QueryFindOneAndUpdateOptions, + findOneAndUpdate(query: FilterQuery, update: any, options: { rawResult: true } & QueryFindOneAndUpdateOptions, callback?: (err: any, doc: mongodb.FindAndModifyWriteOpResultObject, res: any) => void) : Query> & QueryHelpers; - findOneAndUpdate(query: any, update: any, options: QueryFindOneAndUpdateOptions, + findOneAndUpdate(query: FilterQuery, update: any, options: QueryFindOneAndUpdateOptions, callback?: (err: any, doc: DocType | null, res: any) => void): DocumentQuery & QueryHelpers; /** @@ -2206,7 +2231,7 @@ declare module "mongoose" { * @param criteria mongodb selector */ remove(callback?: (err: any) => void): Query & QueryHelpers; - remove(criteria: any | Query, callback?: (err: any) => void): Query & QueryHelpers; + remove(criteria: FilterQuery | Query, callback?: (err: any) => void): Query & QueryHelpers; /** Specifies which document fields to include or exclude (also known as the query "projection") */ select(arg: string | any): this; @@ -2293,9 +2318,9 @@ declare module "mongoose" { */ update(callback?: (err: any, affectedRows: number) => void): Query & QueryHelpers; update(doc: any, callback?: (err: any, affectedRows: number) => void): Query & QueryHelpers; - update(criteria: any, doc: any, + update(criteria: FilterQuery, doc: any, callback?: (err: any, affectedRows: number) => void): Query & QueryHelpers; - update(criteria: any, doc: any, options: QueryUpdateOptions, + update(criteria: FilterQuery, doc: any, options: QueryUpdateOptions, callback?: (err: any, affectedRows: number) => void): Query & QueryHelpers; /** Specifies a path for use with chaining. */ @@ -3011,7 +3036,7 @@ declare module "mongoose" { aggregate(aggregations: any[], cb: Function): Promise; /** Counts number of matching documents in a database collection. */ - count(conditions: any, callback?: (err: any, count: number) => void): Query & QueryHelpers; + count(conditions: FilterQuery, callback?: (err: any, count: number) => void): Query & QueryHelpers; /** * Counts number of documents matching `criteria` in a database collection. @@ -3026,7 +3051,7 @@ declare module "mongoose" { * @return {Query} */ countDocuments(callback?: (err: any, count: number) => void): Query & QueryHelpers; - countDocuments(criteria: any, callback?: (err: any, count: number) => void): Query & QueryHelpers; + countDocuments(criteria: FilterQuery, callback?: (err: any, count: number) => void): Query & QueryHelpers; /** * Estimates the number of documents in the MongoDB collection. Faster than @@ -3119,17 +3144,17 @@ declare module "mongoose" { * Returns true if at least one document exists in the database that matches * the given `filter`, and false otherwise. */ - exists(filter: any, callback?: (err: any, res: boolean) => void): Promise; + exists(filter: FilterQuery, callback?: (err: any, res: boolean) => void): Promise; /** * Finds documents. * @param projection optional fields to return */ find(callback?: (err: any, res: T[]) => void): DocumentQuery & QueryHelpers; - find(conditions: any, callback?: (err: any, res: T[]) => void): DocumentQuery & QueryHelpers; - find(conditions: any, projection?: any | null, + find(conditions: FilterQuery, callback?: (err: any, res: T[]) => void): DocumentQuery & QueryHelpers; + find(conditions: FilterQuery, projection?: any | null, callback?: (err: any, res: T[]) => void): DocumentQuery & QueryHelpers; - find(conditions: any, projection?: any | null, options?: any | null, + find(conditions: FilterQuery, projection?: any | null, options?: any | null, callback?: (err: any, res: T[]) => void): DocumentQuery & QueryHelpers; @@ -3206,11 +3231,11 @@ declare module "mongoose" { * The conditions are cast to their respective SchemaTypes before the command is sent. * @param projection optional fields to return */ - findOne(conditions?: any, + findOne(conditions?: FilterQuery, callback?: (err: any, res: T | null) => void): DocumentQuery & QueryHelpers; - findOne(conditions: any, projection: any, + findOne(conditions: FilterQuery, projection: any, callback?: (err: any, res: T | null) => void): DocumentQuery & QueryHelpers; - findOne(conditions: any, projection: any, options: any, + findOne(conditions: FilterQuery, projection: any, options: any, callback?: (err: any, res: T | null) => void): DocumentQuery & QueryHelpers; /** @@ -3225,12 +3250,12 @@ declare module "mongoose" { * */ findOneAndRemove(): DocumentQuery & QueryHelpers; - findOneAndRemove(conditions: any, + findOneAndRemove(conditions: FilterQuery, callback?: (err: any, res: T | null) => void): DocumentQuery & QueryHelpers; - findOneAndRemove(conditions: any, options: { rawResult: true } & QueryFindOneAndRemoveOptions, + findOneAndRemove(conditions: FilterQuery, options: { rawResult: true } & QueryFindOneAndRemoveOptions, callback?: (err: any, doc: mongodb.FindAndModifyWriteOpResultObject, res: any) => void) : Query> & QueryHelpers; - findOneAndRemove(conditions: any, options: QueryFindOneAndRemoveOptions, callback?: (err: any, res: T | null) => void): DocumentQuery & QueryHelpers; + findOneAndRemove(conditions: FilterQuery, options: QueryFindOneAndRemoveOptions, callback?: (err: any, res: T | null) => void): DocumentQuery & QueryHelpers; /** * Issues a mongodb findOneAndDelete command. @@ -3241,12 +3266,12 @@ declare module "mongoose" { * */ findOneAndDelete(): DocumentQuery & QueryHelpers; - findOneAndDelete(conditions: any, + findOneAndDelete(conditions: FilterQuery, callback?: (err: any, res: T | null) => void): DocumentQuery & QueryHelpers; - findOneAndDelete(conditions: any, options: { rawResult: true } & QueryFindOneAndRemoveOptions, + findOneAndDelete(conditions: FilterQuery, options: { rawResult: true } & QueryFindOneAndRemoveOptions, callback?: (err: any, doc: mongodb.FindAndModifyWriteOpResultObject, res: any) => void) : Query> & QueryHelpers; - findOneAndDelete(conditions: any, options: QueryFindOneAndRemoveOptions, callback?: (err: any, res: T | null) => void): DocumentQuery & QueryHelpers; + findOneAndDelete(conditions: FilterQuery, options: QueryFindOneAndRemoveOptions, callback?: (err: any, res: T | null) => void): DocumentQuery & QueryHelpers; /** * Issues a mongodb findAndModify update command. @@ -3258,20 +3283,20 @@ declare module "mongoose" { + * https://mongoosejs.com/docs/api.html#mongoose_Mongoose-set */ findOneAndUpdate(): DocumentQuery & QueryHelpers; - findOneAndUpdate(conditions: any, update: any, + findOneAndUpdate(conditions: FilterQuery, update: any, callback?: (err: any, doc: T | null, res: any) => void): DocumentQuery & QueryHelpers; - findOneAndUpdate(conditions: any, update: any, + findOneAndUpdate(conditions: FilterQuery, update: any, options: { rawResult : true } & { upsert: true, new: true } & QueryFindOneAndUpdateOptions, callback?: (err: any, doc: mongodb.FindAndModifyWriteOpResultObject, res: any) => void) : Query> & QueryHelpers; - findOneAndUpdate(conditions: any, update: any, + findOneAndUpdate(conditions: FilterQuery, update: any, options: { upsert: true, new: true } & QueryFindOneAndUpdateOptions, callback?: (err: any, doc: T, res: any) => void): DocumentQuery & QueryHelpers; - findOneAndUpdate(conditions: any, update: any, + findOneAndUpdate(conditions: FilterQuery, update: any, options: { rawResult: true } & QueryFindOneAndUpdateOptions, callback?: (err: any, doc: mongodb.FindAndModifyWriteOpResultObject, res: any) => void) : Query> & QueryHelpers; - findOneAndUpdate(conditions: any, update: any, + findOneAndUpdate(conditions: FilterQuery, update: any, options: QueryFindOneAndUpdateOptions, callback?: (err: any, doc: T | null, res: any) => void): DocumentQuery & QueryHelpers; @@ -3350,32 +3375,32 @@ declare module "mongoose" { callback?: (err: any, res: T) => void): Promise; /** Removes documents from the collection. */ - remove(conditions: any, callback?: (err: any) => void): Query & QueryHelpers; - deleteOne(conditions: any, callback?: (err: any) => void): Query & QueryHelpers; - deleteOne(conditions: any, options: ModelOptions, callback?: (err: any) => void): Query & QueryHelpers; - deleteMany(conditions: any, callback?: (err: any) => void): Query & QueryHelpers; + remove(conditions: FilterQuery, callback?: (err: any) => void): Query & QueryHelpers; + deleteOne(conditions: FilterQuery, callback?: (err: any) => void): Query & QueryHelpers; + deleteOne(conditions: FilterQuery, options: ModelOptions, callback?: (err: any) => void): Query & QueryHelpers; + deleteMany(conditions: FilterQuery, callback?: (err: any) => void): Query & QueryHelpers; /** * Same as update(), except MongoDB replace the existing document with the given document (no atomic operators like $set). * This function triggers the following middleware: replaceOne */ - replaceOne(conditions: any, replacement: any, callback?: (err: any, raw: any) => void): Query & QueryHelpers; + replaceOne(conditions: FilterQuery, replacement: any, callback?: (err: any, raw: any) => void): Query & QueryHelpers; /** * Updates documents in the database without returning them. * All update values are cast to their appropriate SchemaTypes before being sent. */ - update(conditions: any, doc: any, + update(conditions: FilterQuery, doc: any, callback?: (err: any, raw: any) => void): Query & QueryHelpers; - update(conditions: any, doc: any, options: ModelUpdateOptions, + update(conditions: FilterQuery, doc: any, options: ModelUpdateOptions, callback?: (err: any, raw: any) => void): Query & QueryHelpers; - updateOne(conditions: any, doc: any, + updateOne(conditions: FilterQuery, doc: any, callback?: (err: any, raw: any) => void): Query & QueryHelpers; - updateOne(conditions: any, doc: any, options: ModelUpdateOptions, + updateOne(conditions: FilterQuery, doc: any, options: ModelUpdateOptions, callback?: (err: any, raw: any) => void): Query & QueryHelpers; - updateMany(conditions: any, doc: any, + updateMany(conditions: FilterQuery, doc: any, callback?: (err: any, raw: any) => void): Query & QueryHelpers; - updateMany(conditions: any, doc: any, options: ModelUpdateOptions, + updateMany(conditions: FilterQuery, doc: any, options: ModelUpdateOptions, callback?: (err: any, raw: any) => void): Query & QueryHelpers; /** Creates a Query, applies the passed conditions, and returns the Query. */ diff --git a/types/mongoose/mongoose-tests.ts b/types/mongoose/mongoose-tests.ts index 38b1387a06..9859adf2b5 100644 --- a/types/mongoose/mongoose-tests.ts +++ b/types/mongoose/mongoose-tests.ts @@ -1,3 +1,4 @@ +import * as mongodb from 'mongodb'; import * as mongoose from 'mongoose'; // dummy variables @@ -1302,14 +1303,66 @@ query.lean() // true query.lean(false) query.lean({}) +interface OtherLocation extends mongoose.Document { + type: string; +} interface Location1 extends mongoose.Document { - _id: mongoose.Types.ObjectId; + _id: mongodb.ObjectId; name: string; address: string; rating: number; reviews: any[]; + ref1: mongodb.ObjectId; + // This type is useful for using with `populate()` + ref2: mongodb.ObjectId | OtherLocation; }; +var loc1Document = {}; var loc1Query = >{}; +loc1Query.count({ name: 'foo' }); +// $ExpectError +loc1Query.count({ name: 123 }); +loc1Query.countDocuments({ name: 'foo' }); +loc1Query.find({ _id: new mongodb.ObjectId() }); +loc1Query.find({ _id: 'string-allowed' }); +loc1Query.find({ _id: loc1Document }); +// $ExpectError +loc1Query.find({ _id: 123 }); +// $ExpectError +loc1Query.find({ _id: { foo: 'bar' } }); +loc1Query.find({ ref1: new mongodb.ObjectId() }); +loc1Query.find({ ref1: 'string-allowed' }); +// $ExpectError +loc1Query.find({ ref1: 123 }); +loc1Query.find({ ref2: new mongodb.ObjectId() }); +loc1Query.find({ ref2: 'string-allowed' }); +// $ExpectError +loc1Query.find({ ref2: 123 }); +loc1Query.find({ + name: 'foo', + address: /bar/, // strings are allowed as RegExp + rating: 10, + facilities: { $exists: true }, + 'reviews.0': { $exists: true } // additional queries are allowed +}); +// $ExpectError +loc1Query.find({ name: 123 }); +// $ExpectError +loc1Query.find({ rating: 'foo' }); +loc1Query.findOne({ name: 'foo', rating: 10 }); +// $ExpectError +loc1Query.findOne({ rating: 'foo' }); +loc1Query.findOneAndRemove({ name: 'foo', rating: 10 }); +// $ExpectError +loc1Query.findOneAndRemove({ rating: 'foo' }); +loc1Query.findOneAndUpdate({ name: 'foo', rating: 10 }, { rating: 20 }); +// $ExpectError +loc1Query.findOneAndUpdate({ rating: 'foo' }, { rating: 20 }); +loc1Query.remove({ name: 'foo', rating: 10 }); +// $ExpectError +loc1Query.remove({ rating: 'foo' }); +loc1Query.update({ name: 'foo', rating: 10 }, { rating: 20 }); +// $ExpectError +loc1Query.update({ rating: 'foo' }, { rating: 20 }); loc1Query.lean().then(location => { if (location) { // $ExpectType ObjectId @@ -1921,6 +1974,7 @@ MongoModel.findById(999, function (err, user) { console.log(user); }); }); +// $ExpectError MongoModel.find(999, function (err, users) { var opts = [{ path: 'company', match: { x: 1 }, select: 'name' }] var promise = MongoModel.populate(users, opts); @@ -1986,7 +2040,7 @@ MongoModel.find({ .exec(); /* practical example */ interface Location extends mongoose.Document { - _id: mongoose.Types.ObjectId; + _id: mongodb.ObjectId; name: string; address: string; rating: number; @@ -2004,6 +2058,7 @@ const locationSchema = new mongoose.Schema({ openingTimes: [mongoose.Schema.Types.Mixed], reviews: [mongoose.SchemaTypes.Mixed] }); +var locDocument = {}; var LocModel = mongoose.model("Location", locationSchema); LocModel.findById(999) .select("-reviews -rating") @@ -2035,16 +2090,42 @@ LocModel.find({}).$where('') locations[0].name; locations[1].openingTimes; }); -LocModel.count({}) +LocModel.find({ + name: 'foo', + address: /bar/, // strings are allowed as RegExp + rating: 10, + facilities: { $exists: true }, + 'reviews.0': { $exists: true } // additional queries are allowed +}); +LocModel.find({ _id: new mongodb.ObjectId() }); +LocModel.find({ _id: 'string-allowed' }); +LocModel.find({ _id: locDocument }); +// $ExpectError +LocModel.find({ _id: 123 }); +// $ExpectError +LocModel.find({ _id: { foo: 'bar' } }); +// $ExpectError +LocModel.find({ name: 123 }); +// $ExpectError +LocModel.find({ rating: 'foo' }); +LocModel.count({ name: 'foo'}) .exec(function (err, count) { count.toFixed(); }); +// $ExpectError +LocModel.count({ name: 123 }); +LocModel.countDocuments({ name: 'foo' }); +// $ExpectError +LocModel.countDocuments({ name: 123 }); LocModel.distinct('') .select('-review') .exec(function (err, distinct) { distinct.concat; }) .then(cb).catch(cb); +LocModel.exists({ name: 'foo' }); +// $ExpectError +LocModel.exists({ name: 123 }); LocModel.findByIdAndRemove() .exec(function (err, doc) { if (!doc) { @@ -2066,7 +2147,7 @@ LocModel.findOne({}, function (err, doc) { } }); LocModel - .findOne({ name: 'foo' }) + .findOne({ name: 'foo', rating: 10 }) .lean() .exec() .then(function(doc) { @@ -2085,6 +2166,8 @@ LocModel.findOneAndRemove() location.name; } }); +LocModel.findOneAndRemove({ name: 'foo', rating: 10 }); +LocModel.findOneAndDelete({ name: 'foo', rating: 10 }); LocModel.findOneAndUpdate().exec().then(function (arg) { if (arg) { arg.openingTimes; @@ -2102,6 +2185,14 @@ LocModel.geoSearch({}, { near: [1, 2], maxDistance: 22 }, function (err, res) { res[0].openingTimes; }); +LocModel.remove({ name: 'foo' }); +LocModel.deleteOne({ name: 'foo' }); +LocModel.deleteMany({ name: 'foo' }); +LocModel.replaceOne({ name: 'foo' }, { name: 'bar' }); +LocModel.update({ name: 'foo' }, { name: 'bar' }); +LocModel.updateOne({ name: 'foo' }, { name: 'bar' }); +LocModel.updateMany({ name: 'foo' }, { name: 'bar' }); + interface IStatics { staticMethod2: (a: number) => string; }