From 2dbe28842ad4f6f415174a74328d3203653e3517 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Thu, 24 Mar 2022 16:27:57 +0100 Subject: [PATCH 01/41] Add TS utilities to infer schema type automatically. --- types/inferschematype.d.ts | 134 +++++++++++++++++++++++++++++++++++++ types/schemaoptions.d.ts | 3 + 2 files changed, 137 insertions(+) create mode 100644 types/inferschematype.d.ts diff --git a/types/inferschematype.d.ts b/types/inferschematype.d.ts new file mode 100644 index 00000000000..6894145e512 --- /dev/null +++ b/types/inferschematype.d.ts @@ -0,0 +1,134 @@ +import { Schema, InferSchemaType, SchemaType, SchemaTypeOptions, TypeKeyBaseType } from 'mongoose'; + +declare module 'mongoose' { + /** + * @summary Obtains document schema type. + * @description Obtains document schema type from document Definition OR returns enforced schema type if it's provided. + * @param {DocDefinition} DocDefinition A generic equals to the type of document definition "provided in as first parameter in Schema constructor". + * @param {EnforcedDocType} EnforcedDocType A generic type enforced by user "provided before schema constructor". + * @param {TypeKey} TypeKey A generic of literal string type. + */ + type ObtainDocumentType = + IsItRecordAndNotAny extends true ? EnforcedDocType : { + [K in keyof (RequiredPaths & + OptionalPaths)]: ObtainDocumentPathType; + }; + + /** + * @summary Obtains document schema type from Schema instance. + * @param {SchemaType} SchemaType A generic of schema type instance. + * @example + * const userSchema = new Schema({userName:String}); + * type UserType = InferSchemaType; + * // result + * type UserType = {userName?: string} + */ + type InferSchemaType = SchemaType extends Schema + ? IsItRecordAndNotAny extends true ? DocType : ObtainSchemaGeneric + : unknown; + + /** + * @summary Obtains schema Generic type by using generic alias. + * @param {TSchema} TSchema A generic of schema type instance. + * @param {alias} alias Targeted generic alias. + */ + type ObtainSchemaGeneric = + TSchema extends Schema + ? { EnforcedDocType: EnforcedDocType, M: M, TInstanceMethods: TInstanceMethods, TQueryHelpers: TQueryHelpers, TPathTypeKey:TPathTypeKey, DocType: DocType, TStaticMethods: TStaticMethods }[alias] + : unknown; +} +/** + * @summary Checks if a type is "Record" or "any". + * @description It Helps to check if user has provided schema type "EnforcedDocType" + * @param {T} T A generic type to be checked. + * @returns true if {@link T} is Record OR false if {@link T} is of any type. + */ +type IsItRecordAndNotAny = T extends any[] ? false : T extends Record ? true : false; + +/** + * @summary Required path base type. + * @description It helps to check whereas if a path is required OR optional. + */ +type RequiredPathBaseType = { required: true | [true, string | undefined] } + +/** + * @summary Path base type defined by using TypeKey + * @description It helps to check if a path is defined by TypeKey OR not. + * @param {TypeKey} TypeKey A literal string refers to path type property key. + */ +type PathWithTypePropertyBaseType = { [k in TypeKey]: any } + +/** + * @summary A Utility to obtain schema's required path keys. + * @param {T} T A generic refers to document definition. + * @returns required paths keys of document definition. + */ +type RequiredPathKeys = { + [K in keyof T]: T[K] extends RequiredPathBaseType ? K : never; +}[keyof T]; + +/** + * @summary A Utility to obtain schema's required paths. + * @param {T} T A generic refers to document definition. + * @returns a record contains required paths with the corresponding type. + */ +type RequiredPaths = { + [K in RequiredPathKeys]: T[K]; +}; + +/** + * @summary A Utility to obtain schema's optional path keys. + * @param {T} T A generic refers to document definition. + * @returns optional paths keys of document definition. + */ +type OptionalPathKeys = { + [K in keyof T]: T[K] extends RequiredPathBaseType ? never : K; +}[keyof T]; + +/** + * @summary A Utility to obtain schema's optional paths. + * @param {T} T A generic refers to document definition. + * @returns a record contains optional paths with the corresponding type. + */ +type OptionalPaths = { + [K in OptionalPathKeys]?: T[K]; +}; + +/** + * @summary Obtains schema Path type. + * @description Obtains Path type by calling {@link ResolvePathType} OR by calling {@link InferSchemaType} if path of schema type. + * @param {PathValueType} PathValueType Document definition path type. + * @param {TypeKey} TypeKey A generic refers to document definition. + */ +type ObtainDocumentPathType = PathValueType extends Schema + ? InferSchemaType + : ResolvePathType< + PathValueType extends PathWithTypePropertyBaseType ? PathValueType[TypeKey] : PathValueType, + PathValueType extends PathWithTypePropertyBaseType ? Omit : {} + >; + +/** + * @param {T} T A generic refers to string path enums. + * @returns Path enum values type as literal strings or string. + */ +type PathEnumOrString['enum']> = T extends (infer E)[] ? E : T extends { values: any } ? PathEnumOrString : string; + +/** + * @summary Resolve path type by returning the corresponding type. + * @param {PathValueType} PathValueType Document definition path type. + * @param {Options} Options Document definition path options except path type. + * @returns Number, "Number" or "number" will be resolved to string type. + */ +type ResolvePathType = {}> = +PathValueType extends (infer Item)[] ? ResolvePathType[] : +PathValueType extends StringConstructor | 'string' | 'String' | typeof Schema.Types.String ? PathEnumOrString : +PathValueType extends NumberConstructor | 'number' | 'Number' | typeof Schema.Types.Number ? number : +PathValueType extends DateConstructor | 'date' | 'Date' | typeof Schema.Types.Date ? Date : +PathValueType extends BufferConstructor | 'buffer' | 'Buffer' | typeof Schema.Types.Buffer ? Buffer : +PathValueType extends BooleanConstructor | 'boolean' | 'Boolean' | typeof Schema.Types.Boolean ? boolean : +PathValueType extends 'objectId' | 'ObjectId' | typeof Schema.Types.ObjectId ? Schema.Types.ObjectId : +PathValueType extends ObjectConstructor | typeof Schema.Types.Mixed ? Schema.Types.Mixed : +keyof PathValueType extends never ? Schema.Types.Mixed : +PathValueType extends MapConstructor ? Map> : +PathValueType extends typeof SchemaType ? PathValueType['prototype'] : +unknown \ No newline at end of file diff --git a/types/schemaoptions.d.ts b/types/schemaoptions.d.ts index 505077c31bd..abd7904e569 100644 --- a/types/schemaoptions.d.ts +++ b/types/schemaoptions.d.ts @@ -7,6 +7,9 @@ declare module 'mongoose' { currentTime?: () => (NativeDate | number); } + type TypeKeyBaseType = string; + + type DefaultTypeKey = 'type' interface SchemaOptions { /** * By default, Mongoose's init() function creates all the indexes defined in your model's schema by From 03e34db6ee0a9d575a9c53b39bb7d14673c6df2f Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Thu, 24 Mar 2022 16:41:30 +0100 Subject: [PATCH 02/41] Import inferschematype in index.d.ts file & refactor Schema class. --- types/index.d.ts | 13 +++++++++---- types/inferschematype.d.ts | 40 +++++++++++++++++++------------------- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/types/index.d.ts b/types/index.d.ts index 3695eadcd9b..dd9db496c97 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -19,6 +19,7 @@ /// /// /// +/// declare class NativeDate extends global.Date { } @@ -120,14 +121,18 @@ declare module 'mongoose' { useProjection?: boolean; } - export class Schema, TInstanceMethods = {}, TQueryHelpers = {}, TVirtuals = any> extends events.EventEmitter { + export class Schema, TInstanceMethods = {}, TQueryHelpers = {}, TVirtuals = any, + TPathTypeKey extends TypeKeyBaseType = DefaultTypeKey, + DocType extends ObtainDocumentType = ObtainDocumentType, + TStaticMethods = {}> + extends events.EventEmitter { /** * Create a new schema */ - constructor(definition?: SchemaDefinition>, options?: SchemaOptions); + constructor(definition?: SchemaDefinition>, options?: SchemaOptions); /** Adds key path / schema type pairs to this schema. */ - add(obj: SchemaDefinition> | Schema, prefix?: string): this; + add(obj: SchemaDefinition> | Schema, prefix?: string): this; /** * Array of child schemas (from document arrays and single nested subdocs) @@ -180,7 +185,7 @@ declare module 'mongoose' { methods: { [F in keyof TInstanceMethods]: TInstanceMethods[F] } & AnyObject; /** The original object passed to the schema constructor */ - obj: SchemaDefinition>; + obj: SchemaDefinition>; /** Gets/sets schema paths. */ path(path: string): ResultType; diff --git a/types/inferschematype.d.ts b/types/inferschematype.d.ts index 6894145e512..6d401ef9e30 100644 --- a/types/inferschematype.d.ts +++ b/types/inferschematype.d.ts @@ -11,7 +11,7 @@ declare module 'mongoose' { type ObtainDocumentType = IsItRecordAndNotAny extends true ? EnforcedDocType : { [K in keyof (RequiredPaths & - OptionalPaths)]: ObtainDocumentPathType; + OptionalPaths)]: ObtainDocumentPathType; }; /** @@ -32,10 +32,10 @@ declare module 'mongoose' { * @param {TSchema} TSchema A generic of schema type instance. * @param {alias} alias Targeted generic alias. */ - type ObtainSchemaGeneric = - TSchema extends Schema - ? { EnforcedDocType: EnforcedDocType, M: M, TInstanceMethods: TInstanceMethods, TQueryHelpers: TQueryHelpers, TPathTypeKey:TPathTypeKey, DocType: DocType, TStaticMethods: TStaticMethods }[alias] - : unknown; + type ObtainSchemaGeneric = + TSchema extends Schema + ? { EnforcedDocType: EnforcedDocType, M: M, TInstanceMethods: TInstanceMethods, TQueryHelpers: TQueryHelpers, TVirtuals: TVirtuals, TPathTypeKey: TPathTypeKey, DocType: DocType, TStaticMethods: TStaticMethods }[alias] + : unknown; } /** * @summary Checks if a type is "Record" or "any". @@ -49,14 +49,14 @@ type IsItRecordAndNotAny = T extends any[] ? false : T extends Record = { [k in TypeKey]: any } +type PathWithTypePropertyBaseType = { [k in TypeKey]: any }; /** * @summary A Utility to obtain schema's required path keys. @@ -103,8 +103,8 @@ type OptionalPaths = { type ObtainDocumentPathType = PathValueType extends Schema ? InferSchemaType : ResolvePathType< - PathValueType extends PathWithTypePropertyBaseType ? PathValueType[TypeKey] : PathValueType, - PathValueType extends PathWithTypePropertyBaseType ? Omit : {} + PathValueType extends PathWithTypePropertyBaseType ? PathValueType[TypeKey] : PathValueType, + PathValueType extends PathWithTypePropertyBaseType ? Omit : {} >; /** @@ -121,14 +121,14 @@ type PathEnumOrString['enum']> = T extends ( */ type ResolvePathType = {}> = PathValueType extends (infer Item)[] ? ResolvePathType[] : -PathValueType extends StringConstructor | 'string' | 'String' | typeof Schema.Types.String ? PathEnumOrString : -PathValueType extends NumberConstructor | 'number' | 'Number' | typeof Schema.Types.Number ? number : -PathValueType extends DateConstructor | 'date' | 'Date' | typeof Schema.Types.Date ? Date : -PathValueType extends BufferConstructor | 'buffer' | 'Buffer' | typeof Schema.Types.Buffer ? Buffer : -PathValueType extends BooleanConstructor | 'boolean' | 'Boolean' | typeof Schema.Types.Boolean ? boolean : -PathValueType extends 'objectId' | 'ObjectId' | typeof Schema.Types.ObjectId ? Schema.Types.ObjectId : -PathValueType extends ObjectConstructor | typeof Schema.Types.Mixed ? Schema.Types.Mixed : -keyof PathValueType extends never ? Schema.Types.Mixed : -PathValueType extends MapConstructor ? Map> : -PathValueType extends typeof SchemaType ? PathValueType['prototype'] : -unknown \ No newline at end of file + PathValueType extends StringConstructor | 'string' | 'String' | typeof Schema.Types.String ? PathEnumOrString : + PathValueType extends NumberConstructor | 'number' | 'Number' | typeof Schema.Types.Number ? number : + PathValueType extends DateConstructor | 'date' | 'Date' | typeof Schema.Types.Date ? Date : + PathValueType extends BufferConstructor | 'buffer' | 'Buffer' | typeof Schema.Types.Buffer ? Buffer : + PathValueType extends BooleanConstructor | 'boolean' | 'Boolean' | typeof Schema.Types.Boolean ? boolean : + PathValueType extends 'objectId' | 'ObjectId' | typeof Schema.Types.ObjectId ? Schema.Types.ObjectId : + PathValueType extends ObjectConstructor | typeof Schema.Types.Mixed ? Schema.Types.Mixed : + keyof PathValueType extends never ? Schema.Types.Mixed : + PathValueType extends MapConstructor ? Map> : + PathValueType extends typeof SchemaType ? PathValueType['prototype'] : + unknown; \ No newline at end of file From ec6eb3d631302a86e75c396222205bb45397a954 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Thu, 24 Mar 2022 16:49:46 +0100 Subject: [PATCH 03/41] Refactor model FN type & Model interface type. --- types/index.d.ts | 9 +++++---- types/models.d.ts | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/types/index.d.ts b/types/index.d.ts index dd9db496c97..d5e0105316b 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -62,13 +62,14 @@ declare module 'mongoose' { /* ! ignore */ export type CompileModelOptions = { overwriteModels?: boolean, connection?: Connection }; - export function model(name: string, schema?: Schema | Schema, collection?: string, options?: CompileModelOptions): Model; - export function model( + export function model( name: string, - schema?: Schema, + schema?: TSchema, collection?: string, options?: CompileModelOptions - ): U; + ): U extends Model + ? U + : Model, TQueryHelpers, ObtainSchemaGeneric, {}, TSchema>; /** Returns an array of model names created on this instance of Mongoose. */ export function modelNames(): Array; diff --git a/types/models.d.ts b/types/models.d.ts index 3de92a2823f..99235327b4d 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -114,7 +114,7 @@ declare module 'mongoose' { } const Model: Model; - interface Model extends + interface Model extends NodeJS.EventEmitter, AcceptsDiscriminator, IndexManager, From 9321fd59b3e79cbc340841713d7a21dad87aec10 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Thu, 24 Mar 2022 16:56:42 +0100 Subject: [PATCH 04/41] Make schema type obtainable from schema its constructor parameter "definition". --- test/types/schema.test.ts | 152 +++++++++++++++++++++++++++++++++++++- types/index.d.ts | 2 +- 2 files changed, 150 insertions(+), 4 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index bcc13d2484b..b2bc97fbc59 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -1,5 +1,5 @@ -import { Schema, Document, SchemaDefinition, Model, Types } from 'mongoose'; -import { expectType, expectNotType, expectError } from 'tsd'; +import { Schema, Document, SchemaDefinition, Model, Types, InferSchemaType, SchemaType } from 'mongoose'; +import { expectType, expectError } from 'tsd'; enum Genre { Action, @@ -318,4 +318,150 @@ function gh10900(): void { const patientSchema = new Schema({ menuStatus: { type: Schema.Types.Mixed, default: {} } }); -} \ No newline at end of file +} +export type M0_0aAutoTypedSchemaType = { + schema: { + userName: string; + description?: string; + nested?: { + age: number; + hobby?: string + }, + favoritDrink?: 'Tea' | 'Coffee', + favoritColorMode: 'dark' | 'light' + } + , statics: { + staticFn: () => 'Returned from staticFn' + }, + methods: { + instanceFn: () => 'Returned from DocumentInstanceFn' + } +}; + +export function m0_0aSchema() { + const AutoTypedSchema = new Schema({ + userName: { + type: String, + required: [true, 'userName is required'] + }, + description: String, + nested: new Schema({ + age: { + type: Number, + required: true + }, + hobby: { + type: String, + required: false + } + }), + favoritDrink: { + type: String, + enum: ['Coffee', 'Tea'] + }, + favoritColorMode: { + type: String, + enum: { + values: ['dark', 'light'], + message: '{VALUE} is not supported' + }, + required: true + } + }); + + type InferredSchemaType = InferSchemaType; + + expectType({} as InferredSchemaType); + + expectError({} as InferredSchemaType); + + // Test auto schema type obtaining with all possible path types. + + class Int8 extends SchemaType { + constructor(key, options) { + super(key, options, 'Int8'); + } + cast(val) { + let _val = Number(val); + if (isNaN(_val)) { + throw new Error('Int8: ' + val + ' is not a number'); + } + _val = Math.round(_val); + if (_val < -0x80 || _val > 0x7F) { + throw new Error('Int8: ' + val + + ' is outside of the range of valid 8-bit ints'); + } + return _val; + } + } + + type TestSchemaType = { + string1?: string; + string2?: string; + string3?: string; + string4?: string; + number1?: number; + number2?: number; + number3?: number; + number4?: number; + date1?: Date; + date2?: Date; + date3?: Date; + date4?: Date; + buffer1?: Buffer; + buffer2?: Buffer; + buffer3?: Buffer; + buffer4?: Buffer; + boolean1?: boolean; + boolean2?: boolean; + boolean3?: boolean; + boolean4?: boolean; + mixed1?: Schema.Types.Mixed; + mixed2?: Schema.Types.Mixed; + mixed3?: Schema.Types.Mixed; + objectId1?: Schema.Types.ObjectId; + objectId2?: Schema.Types.ObjectId; + objectId3?: Schema.Types.ObjectId; + customSchema?: Int8; + map1?: Map; + map2?: Map; + }; + + const TestSchema = new Schema({ + string1: String, + string2: 'String', + string3: 'string', + string4: Schema.Types.String, + number1: Number, + number2: 'Number', + number3: 'number', + number4: Schema.Types.Number, + date1: Date, + date2: 'Date', + date3: 'date', + date4: Schema.Types.Date, + buffer1: Buffer, + buffer2: 'Buffer', + buffer3: 'buffer', + buffer4: Schema.Types.Buffer, + boolean1: Boolean, + boolean2: 'Boolean', + boolean3: 'boolean', + boolean4: Schema.Types.Boolean, + mixed1: Object, + mixed2: {}, + mixed3: Schema.Types.Mixed, + objectId1: Schema.Types.ObjectId, + objectId2: 'ObjectId', + objectId3: 'objectId', + customSchema: Int8, + map1: { type: Map, of: String }, + map2: { type: Map, of: Number } + }); + + type InferredTestSchemaType = InferSchemaType; + + expectType({} as InferredTestSchemaType); + + return AutoTypedSchema; +} diff --git a/types/index.d.ts b/types/index.d.ts index d5e0105316b..439cf7e7f33 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -130,7 +130,7 @@ declare module 'mongoose' { /** * Create a new schema */ - constructor(definition?: SchemaDefinition>, options?: SchemaOptions); + constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions); /** Adds key path / schema type pairs to this schema. */ add(obj: SchemaDefinition> | Schema, prefix?: string): this; From 2145afa3e6f8dbc2d411ff19cefe521b6172258c Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Thu, 24 Mar 2022 17:01:24 +0100 Subject: [PATCH 05/41] Supporting custom typeKey to infer schema type. --- test/types/schema.test.ts | 10 ++++++++++ types/index.d.ts | 2 +- types/schemaoptions.d.ts | 4 ++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index b2bc97fbc59..2823daa1a56 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -462,6 +462,16 @@ export function m0_0aSchema() { type InferredTestSchemaType = InferSchemaType; expectType({} as InferredTestSchemaType); + const SchemaWithCustomTypeKey = new Schema({ + name: { + customKTypeKey: String, + required: true + } + }, { + typeKey: 'customKTypeKey' + }); + + expectType({} as InferSchemaType['name']); return AutoTypedSchema; } diff --git a/types/index.d.ts b/types/index.d.ts index 439cf7e7f33..0d6c695ac27 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -130,7 +130,7 @@ declare module 'mongoose' { /** * Create a new schema */ - constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions); + constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions); /** Adds key path / schema type pairs to this schema. */ add(obj: SchemaDefinition> | Schema, prefix?: string): this; diff --git a/types/schemaoptions.d.ts b/types/schemaoptions.d.ts index abd7904e569..14e5fc57f07 100644 --- a/types/schemaoptions.d.ts +++ b/types/schemaoptions.d.ts @@ -10,7 +10,7 @@ declare module 'mongoose' { type TypeKeyBaseType = string; type DefaultTypeKey = 'type' - interface SchemaOptions { + interface SchemaOptions { /** * By default, Mongoose's init() function creates all the indexes defined in your model's schema by * calling Model.createIndexes() after you successfully connect to MongoDB. If you want to disable @@ -139,7 +139,7 @@ declare module 'mongoose' { * type declaration. However, for applications like geoJSON, the 'type' property is important. If you want to * control which key mongoose uses to find type declarations, set the 'typeKey' schema option. */ - typeKey?: string; + typeKey?: PathTypeKey; /** * By default, documents are automatically validated before they are saved to the database. This is to From 34ae74cb37d1e8efefd21ad0cf1e398b0fc7e50b Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Thu, 24 Mar 2022 17:40:26 +0100 Subject: [PATCH 06/41] Add statics property in schema options to supporting auto typed model statics. --- lib/schema.js | 2 +- test/model.test.js | 7 + test/types/models.test.ts | 16 +- test/types/schema.test.ts | 4 + types/index.d.ts | 2 +- types/models.d.ts | 463 +++++++++++++++++++------------------- types/schemaoptions.d.ts | 11 +- 7 files changed, 268 insertions(+), 237 deletions(-) diff --git a/lib/schema.js b/lib/schema.js index 4e7729409ea..66da6410c43 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -109,7 +109,7 @@ function Schema(obj, options) { this._indexes = []; this.methods = {}; this.methodOptions = {}; - this.statics = {}; + this.statics = (options && options.statics) || {}; this.tree = {}; this.query = {}; this.childSchemas = []; diff --git a/test/model.test.js b/test/model.test.js index 52a864058df..f1ee48308c6 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -8505,6 +8505,13 @@ describe('Model', function() { }); }); +describe('Check if statics functions that is supplied in schema option is availabe (m0_0a)', function() { + it('should give a static function back rather than undefined', function M0_0aModelJS() { + const testSchema = new mongoose.Schema({}, { statics: { staticFn() { return 'Returned from staticFn'; } } }); + const TestModel = mongoose.model('TestModel', testSchema); + assert.equal(TestModel.staticFn(), 'Returned from staticFn'); + }); +}); diff --git a/test/types/models.test.ts b/test/types/models.test.ts index 1c5c75a6701..9925536639f 100644 --- a/test/types/models.test.ts +++ b/test/types/models.test.ts @@ -1,5 +1,6 @@ import { Schema, Document, Model, Types, connection, model } from 'mongoose'; -import { expectError } from 'tsd'; +import { expectError, expectType } from 'tsd'; +import { M0_0aAutoTypedSchemaType, m0_0aSchema } from './schema.test'; function conventionalSyntax(): void { interface ITest extends Document { @@ -234,4 +235,17 @@ function bulkWrite() { } ]; M.bulkWrite(ops); +} + +export async function m0_0aModel() { + const AutoTypeSchema = m0_0aSchema(); + const AutoTypeModel = model('AutoTypeModel', AutoTypeSchema); + + /* -------------------------------------------------------------------------- */ + /* Model-statics-functions-test */ + /* -------------------------------------------------------------------------- */ + + expectType>(AutoTypeModel.staticFn()); + + return AutoTypeModel; } \ No newline at end of file diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 2823daa1a56..74f8023f2af 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -367,6 +367,10 @@ export function m0_0aSchema() { }, required: true } + }, { + statics: { + staticFn() { return 'Returned from staticFn'; } + } }); type InferredSchemaType = InferSchemaType; diff --git a/types/index.d.ts b/types/index.d.ts index 0d6c695ac27..5d155e22d32 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -130,7 +130,7 @@ declare module 'mongoose' { /** * Create a new schema */ - constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions); + constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions); /** Adds key path / schema type pairs to this schema. */ add(obj: SchemaDefinition> | Schema, prefix?: string): this; diff --git a/types/models.d.ts b/types/models.d.ts index 99235327b4d..59373d2df87 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -114,142 +114,143 @@ declare module 'mongoose' { } const Model: Model; - interface Model extends - NodeJS.EventEmitter, - AcceptsDiscriminator, - IndexManager, - SessionStarter { - new & AnyObject>(doc?: DocType, fields?: any | null, options?: boolean | AnyObject): HydratedDocument; + type Model= + NodeJS.EventEmitter & + AcceptsDiscriminator & + IndexManager & + SessionStarter & + ObtainSchemaGeneric & { + new & AnyObject>(doc?: DocType, fields?: any | null, options?: boolean | AnyObject): HydratedDocument; - aggregate(pipeline?: PipelineStage[], options?: mongodb.AggregateOptions, callback?: Callback): Aggregate>; - aggregate(pipeline: PipelineStage[], callback?: Callback): Aggregate>; + aggregate(pipeline?: PipelineStage[], options?: mongodb.AggregateOptions, callback?: Callback): Aggregate>; + aggregate(pipeline: PipelineStage[], callback?: Callback): Aggregate>; - /** Base Mongoose instance the model uses. */ - base: Mongoose; + /** Base Mongoose instance the model uses. */ + base: Mongoose; - /** + /** * If this is a discriminator model, `baseModelName` is the name of * the base model. */ - baseModelName: string | undefined; + baseModelName: string | undefined; - /** + /** * Sends multiple `insertOne`, `updateOne`, `updateMany`, `replaceOne`, * `deleteOne`, and/or `deleteMany` operations to the MongoDB server in one * command. This is faster than sending multiple independent operations (e.g. * if you use `create()`) because with `bulkWrite()` there is only one network * round trip to the MongoDB server. */ - bulkWrite(writes: Array, options: mongodb.BulkWriteOptions & MongooseBulkWriteOptions, callback: Callback): void; - bulkWrite(writes: Array, callback: Callback): void; - bulkWrite(writes: Array, options?: mongodb.BulkWriteOptions & MongooseBulkWriteOptions): Promise; + bulkWrite(writes: Array, options: mongodb.BulkWriteOptions & MongooseBulkWriteOptions, callback: Callback): void; + bulkWrite(writes: Array, callback: Callback): void; + bulkWrite(writes: Array, options?: mongodb.BulkWriteOptions & MongooseBulkWriteOptions): Promise; - /** + /** * Sends multiple `save()` calls in a single `bulkWrite()`. This is faster than * sending multiple `save()` calls because with `bulkSave()` there is only one * network round trip to the MongoDB server. */ - bulkSave(documents: Array, options?: mongodb.BulkWriteOptions): Promise; - - /** Collection the model uses. */ - collection: Collection; - - /** Creates a `count` query: counts the number of documents that match `filter`. */ - count(callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; - count(filter: FilterQuery, callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; - - /** Creates a `countDocuments` query: counts the number of documents that match `filter`. */ - countDocuments(filter: FilterQuery, options?: QueryOptions, callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; - countDocuments(callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; - - /** Creates a new document or documents */ - create(docs: (AnyKeys | AnyObject)[], options?: SaveOptions): Promise[]>; - create(docs: (AnyKeys | AnyObject)[], callback: Callback[]>): void; - create(doc: AnyKeys | AnyObject): Promise>; - create(doc: AnyKeys | AnyObject, callback: Callback>): void; - create>(docs: DocContents[], options?: SaveOptions): Promise[]>; - create>(docs: DocContents[], callback: Callback[]>): void; - create>(doc: DocContents): Promise>; - create>(...docs: DocContents[]): Promise[]>; - create>(doc: DocContents, callback: Callback>): void; - - /** + bulkSave(documents: Array, options?: mongodb.BulkWriteOptions): Promise; + + /** Collection the model uses. */ + collection: Collection; + + /** Creates a `count` query: counts the number of documents that match `filter`. */ + count(callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; + count(filter: FilterQuery, callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; + + /** Creates a `countDocuments` query: counts the number of documents that match `filter`. */ + countDocuments(filter: FilterQuery, options?: QueryOptions, callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; + countDocuments(callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; + + /** Creates a new document or documents */ + create(docs: (AnyKeys | AnyObject)[], options?: SaveOptions): Promise[]>; + create(docs: (AnyKeys | AnyObject)[], callback: Callback[]>): void; + create(doc: AnyKeys | AnyObject): Promise>; + create(doc: AnyKeys | AnyObject, callback: Callback>): void; + create>(docs: DocContents[], options?: SaveOptions): Promise[]>; + create>(docs: DocContents[], callback: Callback[]>): void; + create>(doc: DocContents): Promise>; + create>(...docs: DocContents[]): Promise[]>; + create>(doc: DocContents, callback: Callback>): void; + + /** * Create the collection for this model. By default, if no indexes are specified, * mongoose will not create the collection for the model until any documents are * created. Use this method to create the collection explicitly. */ - createCollection(options: mongodb.CreateCollectionOptions & Pick | null, callback: Callback>): void; - createCollection(callback: Callback>): void; - createCollection(options?: mongodb.CreateCollectionOptions & Pick): Promise>; + createCollection(options: mongodb.CreateCollectionOptions & Pick | null, callback: Callback>): void; + createCollection(callback: Callback>): void; + createCollection(options?: mongodb.CreateCollectionOptions & Pick): Promise>; - /** Connection the model uses. */ - db: Connection; + /** Connection the model uses. */ + db: Connection; - /** + /** * Deletes all of the documents that match `conditions` from the collection. * Behaves like `remove()`, but deletes all documents that match `conditions` * regardless of the `single` option. */ - deleteMany(filter?: FilterQuery, options?: QueryOptions, callback?: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; - deleteMany(filter: FilterQuery, callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; - deleteMany(callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; + deleteMany(filter?: FilterQuery, options?: QueryOptions, callback?: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; + deleteMany(filter: FilterQuery, callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; + deleteMany(callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; - /** + /** * Deletes the first document that matches `conditions` from the collection. * Behaves like `remove()`, but deletes at most one document regardless of the * `single` option. */ - deleteOne(filter?: FilterQuery, options?: QueryOptions, callback?: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; - deleteOne(filter: FilterQuery, callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; - deleteOne(callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; + deleteOne(filter?: FilterQuery, options?: QueryOptions, callback?: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; + deleteOne(filter: FilterQuery, callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; + deleteOne(callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; - /** + /** * Event emitter that reports any errors that occurred. Useful for global error * handling. */ - events: NodeJS.EventEmitter; + events: NodeJS.EventEmitter; - /** + /** * Finds a single document by its _id field. `findById(id)` is almost* * equivalent to `findOne({ _id: id })`. If you want to query by a document's * `_id`, use `findById()` instead of `findOne()`. */ - findById>( - id: any, - projection?: ProjectionType | null, - options?: QueryOptions | null, - callback?: Callback - ): QueryWithHelpers; - findById>( - id: any, - projection?: ProjectionType | null, - callback?: Callback - ): QueryWithHelpers; - - /** Finds one document. */ - findOne>( - filter?: FilterQuery, - projection?: ProjectionType | null, - options?: QueryOptions | null, - callback?: Callback - ): QueryWithHelpers; - findOne>( - filter?: FilterQuery, - projection?: ProjectionType | null, - callback?: Callback - ): QueryWithHelpers; - findOne>( - filter?: FilterQuery, - callback?: Callback - ): QueryWithHelpers; - - /** + findById>( + id: any, + projection?: ProjectionType | null, + options?: QueryOptions | null, + callback?: Callback + ): QueryWithHelpers; + findById>( + id: any, + projection?: ProjectionType | null, + callback?: Callback + ): QueryWithHelpers; + + /** Finds one document. */ + findOne>( + filter?: FilterQuery, + projection?: ProjectionType | null, + options?: QueryOptions | null, + callback?: Callback + ): QueryWithHelpers; + findOne>( + filter?: FilterQuery, + projection?: ProjectionType | null, + callback?: Callback + ): QueryWithHelpers; + findOne>( + filter?: FilterQuery, + callback?: Callback + ): QueryWithHelpers; + + /** * Shortcut for creating a new Document from existing raw data, pre-saved in the DB. * The document returned has no paths marked as modified initially. */ - hydrate(obj: any): HydratedDocument; + hydrate(obj: any): HydratedDocument; - /** + /** * This function is responsible for building [indexes](https://docs.mongodb.com/manual/indexes/), * unless [`autoIndex`](http://mongoosejs.com/docs/guide.html#autoIndex) is turned off. * Mongoose calls this function automatically when a model is created using @@ -257,163 +258,163 @@ declare module 'mongoose' { * [`connection.model()`](/docs/api.html#connection_Connection-model), so you * don't need to call it. */ - init(callback?: CallbackWithoutResult): Promise>; + init(callback?: CallbackWithoutResult): Promise>; - /** Inserts one or more new documents as a single `insertMany` call to the MongoDB server. */ - insertMany(docs: Array | AnyObject>, options: InsertManyOptions & { rawResult: true }): Promise>; - insertMany(docs: Array | AnyObject>, options?: InsertManyOptions): Promise>>; - insertMany(doc: AnyKeys | AnyObject, options: InsertManyOptions & { rawResult: true }): Promise>; - insertMany(doc: AnyKeys | AnyObject, options?: InsertManyOptions): Promise[]>; - insertMany(doc: AnyKeys | AnyObject, options?: InsertManyOptions, callback?: Callback[] | InsertManyResult>): void; - insertMany(docs: Array | AnyObject>, options?: InsertManyOptions, callback?: Callback> | InsertManyResult>): void; + /** Inserts one or more new documents as a single `insertMany` call to the MongoDB server. */ + insertMany(docs: Array | AnyObject>, options: InsertManyOptions & { rawResult: true }): Promise>; + insertMany(docs: Array | AnyObject>, options?: InsertManyOptions): Promise>>; + insertMany(doc: AnyKeys | AnyObject, options: InsertManyOptions & { rawResult: true }): Promise>; + insertMany(doc: AnyKeys | AnyObject, options?: InsertManyOptions): Promise[]>; + insertMany(doc: AnyKeys | AnyObject, options?: InsertManyOptions, callback?: Callback[] | InsertManyResult>): void; + insertMany(docs: Array | AnyObject>, options?: InsertManyOptions, callback?: Callback> | InsertManyResult>): void; - /** The name of the model */ - modelName: string; + /** The name of the model */ + modelName: string; - /** Populates document references. */ - populate(docs: Array, options: PopulateOptions | Array | string, - callback?: Callback<(HydratedDocument)[]>): Promise>>; - populate(doc: any, options: PopulateOptions | Array | string, - callback?: Callback>): Promise>; + /** Populates document references. */ + populate(docs: Array, options: PopulateOptions | Array | string, + callback?: Callback<(HydratedDocument)[]>): Promise>>; + populate(doc: any, options: PopulateOptions | Array | string, + callback?: Callback>): Promise>; - /** Casts and validates the given object against this model's schema, passing the given `context` to custom validators. */ - validate(callback?: CallbackWithoutResult): Promise; - validate(optional: any, callback?: CallbackWithoutResult): Promise; - validate(optional: any, pathsToValidate: PathsToValidate, callback?: CallbackWithoutResult): Promise; + /** Casts and validates the given object against this model's schema, passing the given `context` to custom validators. */ + validate(callback?: CallbackWithoutResult): Promise; + validate(optional: any, callback?: CallbackWithoutResult): Promise; + validate(optional: any, pathsToValidate: PathsToValidate, callback?: CallbackWithoutResult): Promise; - /** Watches the underlying collection for changes using [MongoDB change streams](https://docs.mongodb.com/manual/changeStreams/). */ - watch(pipeline?: Array>, options?: mongodb.ChangeStreamOptions): mongodb.ChangeStream; + /** Watches the underlying collection for changes using [MongoDB change streams](https://docs.mongodb.com/manual/changeStreams/). */ + watch(pipeline?: Array>, options?: mongodb.ChangeStreamOptions): mongodb.ChangeStream; - /** Adds a `$where` clause to this query */ - $where(argument: string | Function): QueryWithHelpers>, HydratedDocument, TQueryHelpers, T>; + /** Adds a `$where` clause to this query */ + $where(argument: string | Function): QueryWithHelpers>, HydratedDocument, TQueryHelpers, T>; - /** Registered discriminators for this model. */ - discriminators: { [name: string]: Model } | undefined; + /** Registered discriminators for this model. */ + discriminators: { [name: string]: Model } | undefined; - /** Translate any aliases fields/conditions so the final query or document object is pure */ - translateAliases(raw: any): any; + /** Translate any aliases fields/conditions so the final query or document object is pure */ + translateAliases(raw: any): any; - /** Creates a `distinct` query: returns the distinct values of the given `field` that match `filter`. */ - distinct(field: string, filter?: FilterQuery, callback?: Callback): QueryWithHelpers, HydratedDocument, TQueryHelpers, T>; + /** Creates a `distinct` query: returns the distinct values of the given `field` that match `filter`. */ + distinct(field: string, filter?: FilterQuery, callback?: Callback): QueryWithHelpers, HydratedDocument, TQueryHelpers, T>; - /** Creates a `estimatedDocumentCount` query: counts the number of documents in the collection. */ - estimatedDocumentCount(options?: QueryOptions, callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; + /** Creates a `estimatedDocumentCount` query: counts the number of documents in the collection. */ + estimatedDocumentCount(options?: QueryOptions, callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; - /** + /** * Returns a document with its `_id` if at least one document exists in the database that matches * the given `filter`, and `null` otherwise. */ - exists(filter: FilterQuery, callback: Callback, '_id'> | null>): QueryWithHelpers, '_id'> | null, HydratedDocument, TQueryHelpers, T>; - exists(filter: FilterQuery): QueryWithHelpers, '_id'> | null, HydratedDocument, TQueryHelpers, T>; - - /** Creates a `find` query: gets a list of documents that match `filter`. */ - find>(callback?: Callback): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; - find>( - filter: FilterQuery, - projection?: ProjectionType | null, - callback?: Callback - ): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; - find>(filter: FilterQuery, callback?: Callback): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; - find>(filter: FilterQuery, projection?: ProjectionType | null, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; - - /** Creates a `findByIdAndDelete` query, filtering by the given `_id`. */ - findByIdAndDelete>(id?: mongodb.ObjectId | any, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; - - /** Creates a `findByIdAndRemove` query, filtering by the given `_id`. */ - findByIdAndRemove>(id?: mongodb.ObjectId | any, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; - - /** Creates a `findOneAndUpdate` query, filtering by the given `_id`. */ - findByIdAndUpdate>(id: mongodb.ObjectId | any, update: UpdateQuery, options: QueryOptions & { rawResult: true }, callback?: (err: CallbackError, doc: any, res: any) => void): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; - findByIdAndUpdate>(id: mongodb.ObjectId | any, update: UpdateQuery, options: QueryOptions & { upsert: true } & ReturnsNewDoc, callback?: (err: CallbackError, doc: ResultDoc, res: any) => void): QueryWithHelpers; - findByIdAndUpdate>(id?: mongodb.ObjectId | any, update?: UpdateQuery, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; - findByIdAndUpdate>(id: mongodb.ObjectId | any, update: UpdateQuery, callback: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; - - /** Creates a `findOneAndDelete` query: atomically finds the given document, deletes it, and returns the document as it was before deletion. */ - findOneAndDelete>(filter?: FilterQuery, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; - - /** Creates a `findOneAndRemove` query: atomically finds the given document and deletes it. */ - findOneAndRemove>(filter?: FilterQuery, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; - - /** Creates a `findOneAndReplace` query: atomically finds the given document and replaces it with `replacement`. */ - findOneAndReplace>(filter: FilterQuery, replacement: T | AnyObject, options: QueryOptions & { upsert: true } & ReturnsNewDoc, callback?: (err: CallbackError, doc: ResultDoc, res: any) => void): QueryWithHelpers; - findOneAndReplace>(filter?: FilterQuery, replacement?: T | AnyObject, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; - - /** Creates a `findOneAndUpdate` query: atomically find the first document that matches `filter` and apply `update`. */ - findOneAndUpdate>( - filter: FilterQuery, - update: UpdateQuery, - options: QueryOptions & { rawResult: true }, - callback?: (err: CallbackError, doc: any, res: any) => void - ): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; - findOneAndUpdate>( - filter: FilterQuery, - update: UpdateQuery, - options: QueryOptions & { upsert: true } & ReturnsNewDoc, - callback?: (err: CallbackError, doc: ResultDoc, res: any) => void - ): QueryWithHelpers; - findOneAndUpdate>( - filter?: FilterQuery, - update?: UpdateQuery, - options?: QueryOptions | null, - callback?: (err: CallbackError, doc: T | null, res: any) => void - ): QueryWithHelpers; - - geoSearch>( - filter?: FilterQuery, - options?: GeoSearchOptions, - callback?: Callback> - ): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; - - /** Executes a mapReduce command. */ - mapReduce( - o: MapReduceOptions, - callback?: Callback - ): Promise; - - remove>(filter?: any, callback?: CallbackWithoutResult): QueryWithHelpers; - - /** Creates a `replaceOne` query: finds the first document that matches `filter` and replaces it with `replacement`. */ - replaceOne>( - filter?: FilterQuery, - replacement?: T | AnyObject, - options?: QueryOptions | null, - callback?: Callback - ): QueryWithHelpers; - - /** Schema the model uses. */ - schema: Schema; - - /** + exists(filter: FilterQuery, callback: Callback, '_id'> | null>): QueryWithHelpers, '_id'> | null, HydratedDocument, TQueryHelpers, T>; + exists(filter: FilterQuery): QueryWithHelpers, '_id'> | null, HydratedDocument, TQueryHelpers, T>; + + /** Creates a `find` query: gets a list of documents that match `filter`. */ + find>(callback?: Callback): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; + find>( + filter: FilterQuery, + projection?: ProjectionType | null, + callback?: Callback + ): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; + find>(filter: FilterQuery, callback?: Callback): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; + find>(filter: FilterQuery, projection?: ProjectionType | null, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; + + /** Creates a `findByIdAndDelete` query, filtering by the given `_id`. */ + findByIdAndDelete>(id?: mongodb.ObjectId | any, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; + + /** Creates a `findByIdAndRemove` query, filtering by the given `_id`. */ + findByIdAndRemove>(id?: mongodb.ObjectId | any, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; + + /** Creates a `findOneAndUpdate` query, filtering by the given `_id`. */ + findByIdAndUpdate>(id: mongodb.ObjectId | any, update: UpdateQuery, options: QueryOptions & { rawResult: true }, callback?: (err: CallbackError, doc: any, res: any) => void): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; + findByIdAndUpdate>(id: mongodb.ObjectId | any, update: UpdateQuery, options: QueryOptions & { upsert: true } & ReturnsNewDoc, callback?: (err: CallbackError, doc: ResultDoc, res: any) => void): QueryWithHelpers; + findByIdAndUpdate>(id?: mongodb.ObjectId | any, update?: UpdateQuery, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; + findByIdAndUpdate>(id: mongodb.ObjectId | any, update: UpdateQuery, callback: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; + + /** Creates a `findOneAndDelete` query: atomically finds the given document, deletes it, and returns the document as it was before deletion. */ + findOneAndDelete>(filter?: FilterQuery, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; + + /** Creates a `findOneAndRemove` query: atomically finds the given document and deletes it. */ + findOneAndRemove>(filter?: FilterQuery, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; + + /** Creates a `findOneAndReplace` query: atomically finds the given document and replaces it with `replacement`. */ + findOneAndReplace>(filter: FilterQuery, replacement: T | AnyObject, options: QueryOptions & { upsert: true } & ReturnsNewDoc, callback?: (err: CallbackError, doc: ResultDoc, res: any) => void): QueryWithHelpers; + findOneAndReplace>(filter?: FilterQuery, replacement?: T | AnyObject, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; + + /** Creates a `findOneAndUpdate` query: atomically find the first document that matches `filter` and apply `update`. */ + findOneAndUpdate>( + filter: FilterQuery, + update: UpdateQuery, + options: QueryOptions & { rawResult: true }, + callback?: (err: CallbackError, doc: any, res: any) => void + ): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; + findOneAndUpdate>( + filter: FilterQuery, + update: UpdateQuery, + options: QueryOptions & { upsert: true } & ReturnsNewDoc, + callback?: (err: CallbackError, doc: ResultDoc, res: any) => void + ): QueryWithHelpers; + findOneAndUpdate>( + filter?: FilterQuery, + update?: UpdateQuery, + options?: QueryOptions | null, + callback?: (err: CallbackError, doc: T | null, res: any) => void + ): QueryWithHelpers; + + geoSearch>( + filter?: FilterQuery, + options?: GeoSearchOptions, + callback?: Callback> + ): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; + + /** Executes a mapReduce command. */ + mapReduce( + o: MapReduceOptions, + callback?: Callback + ): Promise; + + remove>(filter?: any, callback?: CallbackWithoutResult): QueryWithHelpers; + + /** Creates a `replaceOne` query: finds the first document that matches `filter` and replaces it with `replacement`. */ + replaceOne>( + filter?: FilterQuery, + replacement?: T | AnyObject, + options?: QueryOptions | null, + callback?: Callback + ): QueryWithHelpers; + + /** Schema the model uses. */ + schema: Schema; + + /** * @deprecated use `updateOne` or `updateMany` instead. * Creates a `update` query: updates one or many documents that match `filter` with `update`, based on the `multi` option. */ - update>( - filter?: FilterQuery, - update?: UpdateQuery | UpdateWithAggregationPipeline, - options?: QueryOptions | null, - callback?: Callback - ): QueryWithHelpers; - - /** Creates a `updateMany` query: updates all documents that match `filter` with `update`. */ - updateMany>( - filter?: FilterQuery, - update?: UpdateQuery | UpdateWithAggregationPipeline, - options?: QueryOptions | null, - callback?: Callback - ): QueryWithHelpers; - - /** Creates a `updateOne` query: updates the first document that matches `filter` with `update`. */ - updateOne>( - filter?: FilterQuery, - update?: UpdateQuery | UpdateWithAggregationPipeline, - options?: QueryOptions | null, - callback?: Callback - ): QueryWithHelpers; - - /** Creates a Query, applies the passed conditions, and returns the Query. */ - where>(path: string, val?: any): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; - where>(obj: object): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; - where>(): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; - } + update>( + filter?: FilterQuery, + update?: UpdateQuery | UpdateWithAggregationPipeline, + options?: QueryOptions | null, + callback?: Callback + ): QueryWithHelpers; + + /** Creates a `updateMany` query: updates all documents that match `filter` with `update`. */ + updateMany>( + filter?: FilterQuery, + update?: UpdateQuery | UpdateWithAggregationPipeline, + options?: QueryOptions | null, + callback?: Callback + ): QueryWithHelpers; + + /** Creates a `updateOne` query: updates the first document that matches `filter` with `update`. */ + updateOne>( + filter?: FilterQuery, + update?: UpdateQuery | UpdateWithAggregationPipeline, + options?: QueryOptions | null, + callback?: Callback + ): QueryWithHelpers; + + /** Creates a Query, applies the passed conditions, and returns the Query. */ + where>(path: string, val?: any): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; + where>(obj: object): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; + where>(): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; + }; } \ No newline at end of file diff --git a/types/schemaoptions.d.ts b/types/schemaoptions.d.ts index 14e5fc57f07..a1b7e5edd97 100644 --- a/types/schemaoptions.d.ts +++ b/types/schemaoptions.d.ts @@ -9,8 +9,8 @@ declare module 'mongoose' { type TypeKeyBaseType = string; - type DefaultTypeKey = 'type' - interface SchemaOptions { + type DefaultTypeKey = 'type'; + interface SchemaOptions { /** * By default, Mongoose's init() function creates all the indexes defined in your model's schema by * calling Model.createIndexes() after you successfully connect to MongoDB. If you want to disable @@ -186,6 +186,11 @@ declare module 'mongoose' { * You can suppress the warning by setting { supressReservedKeysWarning: true } schema options. Keep in mind that this * can break plugins that rely on these reserved names. */ - supressReservedKeysWarning?: boolean + supressReservedKeysWarning?: boolean, + + /** + * Model Statics methods. + */ + statics?: StaticMethods, } } \ No newline at end of file From 967bd653c0730d4e95fe09a47894ea3991e6923b Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Thu, 24 Mar 2022 17:53:31 +0100 Subject: [PATCH 07/41] Add methods property in schema options to supporting auto typed document instance methods. --- lib/schema.js | 2 +- test/document.test.js | 9 +++++++++ test/types/models.test.ts | 12 ++++++++++++ test/types/schema.test.ts | 3 +++ types/index.d.ts | 2 +- types/schemaoptions.d.ts | 7 ++++++- 6 files changed, 32 insertions(+), 3 deletions(-) diff --git a/lib/schema.js b/lib/schema.js index 66da6410c43..a8527d157a6 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -107,7 +107,7 @@ function Schema(obj, options) { this.inherits = {}; this.callQueue = []; this._indexes = []; - this.methods = {}; + this.methods = (options && options.methods) || {}; this.methodOptions = {}; this.statics = (options && options.statics) || {}; this.tree = {}; diff --git a/test/document.test.js b/test/document.test.js index 115d87a10d8..a7ad980e645 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -11455,3 +11455,12 @@ describe('document', function() { assert.ok(baz.populated('bar')); }); }); + +describe('Check if instance functions that is supplied in schema option is availabe (m0_0a)', function() { + it('should give an instance function back rather than undefined', function M0_0aModelJS() { + const testSchema = new mongoose.Schema({}, { methods: { instanceFn() { return 'Returned from DocumentInstanceFn'; } } }); + const TestModel = mongoose.model('TestModel', testSchema); + const TestDocument = new TestModel({}); + assert.equal(TestDocument.instanceFn(), 'Returned from DocumentInstanceFn'); + }); +}); \ No newline at end of file diff --git a/test/types/models.test.ts b/test/types/models.test.ts index 9925536639f..69eac8b61f6 100644 --- a/test/types/models.test.ts +++ b/test/types/models.test.ts @@ -247,5 +247,17 @@ export async function m0_0aModel() { expectType>(AutoTypeModel.staticFn()); + /* -------------------------------------------------------------------------- */ + /* Instance-Test */ + /* -------------------------------------------------------------------------- */ + + const AutoTypeModelInstance = new AutoTypeModel({}); + + /* -------------------------------------------------------------------------- */ + /* Document-Instance-Methods */ + /* -------------------------------------------------------------------------- */ + + expectType>(AutoTypeModelInstance.instanceFn()); + return AutoTypeModel; } \ No newline at end of file diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 74f8023f2af..48aaf80d21f 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -370,6 +370,9 @@ export function m0_0aSchema() { }, { statics: { staticFn() { return 'Returned from staticFn'; } + }, + methods: { + instanceFn() { return 'Returned from DocumentInstanceFn'; } } }); diff --git a/types/index.d.ts b/types/index.d.ts index 5d155e22d32..2cafa1ede3a 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -130,7 +130,7 @@ declare module 'mongoose' { /** * Create a new schema */ - constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions); + constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions); /** Adds key path / schema type pairs to this schema. */ add(obj: SchemaDefinition> | Schema, prefix?: string): this; diff --git a/types/schemaoptions.d.ts b/types/schemaoptions.d.ts index a1b7e5edd97..6b48d7bb14a 100644 --- a/types/schemaoptions.d.ts +++ b/types/schemaoptions.d.ts @@ -10,7 +10,7 @@ declare module 'mongoose' { type TypeKeyBaseType = string; type DefaultTypeKey = 'type'; - interface SchemaOptions { + interface SchemaOptions { /** * By default, Mongoose's init() function creates all the indexes defined in your model's schema by * calling Model.createIndexes() after you successfully connect to MongoDB. If you want to disable @@ -192,5 +192,10 @@ declare module 'mongoose' { * Model Statics methods. */ statics?: StaticMethods, + + /** + * Document instance methods. + */ + methods?: InstanceMethods, } } \ No newline at end of file From e75d4b6b530868cd14233d3a2ae0a6628e536bc2 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Thu, 24 Mar 2022 18:17:49 +0100 Subject: [PATCH 08/41] Improve create FN type to show actual schema type instead of any --- test/types/create.test.ts | 2 +- test/types/models.test.ts | 22 +++++++++++++++++++--- test/types/schema.test.ts | 1 + types/index.d.ts | 3 +++ types/models.d.ts | 14 +++++--------- 5 files changed, 29 insertions(+), 13 deletions(-) diff --git a/test/types/create.test.ts b/test/types/create.test.ts index 4492d113fca..83372dd2b30 100644 --- a/test/types/create.test.ts +++ b/test/types/create.test.ts @@ -3,7 +3,7 @@ import { Schema, model, Document, Types } from 'mongoose'; const schema: Schema = new Schema({ name: { type: 'String' } }); interface ITest extends Document { - _id?: Types.ObjectId; + _id?: Types.ObjectId | string; name?: string; } diff --git a/test/types/models.test.ts b/test/types/models.test.ts index 69eac8b61f6..48853cfc511 100644 --- a/test/types/models.test.ts +++ b/test/types/models.test.ts @@ -242,19 +242,35 @@ export async function m0_0aModel() { const AutoTypeModel = model('AutoTypeModel', AutoTypeSchema); /* -------------------------------------------------------------------------- */ - /* Model-statics-functions-test */ + /* Model-functions-test */ + /* -------------------------------------------------------------------------- */ + // Create should works with arbitrary objects. + const randomObject = await AutoTypeModel.create({ unExistKey: 'unExistKey', description: 'st' }); + expectType(randomObject.unExistKey); + expectType(randomObject.userName); + + const testDoc1 = await AutoTypeModel.create({ userName: 'M0_0a' }); + expectType(testDoc1.userName); + expectType(testDoc1.description); + + /* -------------------------------------------------------------------------- */ + /* Model-statics-functions-test */ /* -------------------------------------------------------------------------- */ expectType>(AutoTypeModel.staticFn()); /* -------------------------------------------------------------------------- */ - /* Instance-Test */ + /* Instance-tests */ /* -------------------------------------------------------------------------- */ const AutoTypeModelInstance = new AutoTypeModel({}); + expectType(AutoTypeModelInstance.userName); + expectType(AutoTypeModelInstance.favoritDrink); + expectType(AutoTypeModelInstance.favoritColorMode); + /* -------------------------------------------------------------------------- */ - /* Document-Instance-Methods */ + /* Document-Instance-Methods-tests */ /* -------------------------------------------------------------------------- */ expectType>(AutoTypeModelInstance.instanceFn()); diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 48aaf80d21f..8356825d217 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -469,6 +469,7 @@ export function m0_0aSchema() { type InferredTestSchemaType = InferSchemaType; expectType({} as InferredTestSchemaType); + const SchemaWithCustomTypeKey = new Schema({ name: { customKTypeKey: String, diff --git a/types/index.d.ts b/types/index.d.ts index 2cafa1ede3a..c7c9d8ad2b2 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -93,6 +93,9 @@ declare module 'mongoose' { export interface AnyObject { [k: string]: any } + + export type FlexibleObject = { [P in keyof (T & Omit)]?: P extends keyof T ? T[P] : any }; + export type Require_id = T extends { _id?: any } ? (T & { _id: T['_id'] }) : (T & { _id: Types.ObjectId }); export type HydratedDocument = DocType extends Document ? Require_id : (Document & Require_id & TVirtuals & TMethodsAndOverrides); diff --git a/types/models.d.ts b/types/models.d.ts index 59373d2df87..8e9e2ff256d 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -164,15 +164,11 @@ declare module 'mongoose' { countDocuments(callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; /** Creates a new document or documents */ - create(docs: (AnyKeys | AnyObject)[], options?: SaveOptions): Promise[]>; - create(docs: (AnyKeys | AnyObject)[], callback: Callback[]>): void; - create(doc: AnyKeys | AnyObject): Promise>; - create(doc: AnyKeys | AnyObject, callback: Callback>): void; - create>(docs: DocContents[], options?: SaveOptions): Promise[]>; - create>(docs: DocContents[], callback: Callback[]>): void; - create>(doc: DocContents): Promise>; - create>(...docs: DocContents[]): Promise[]>; - create>(doc: DocContents, callback: Callback>): void; + create>(docs: Array, options?: SaveOptions): Promise, TMethodsAndOverrides, TVirtuals>[]>; + create>(docs: Array, callback: Callback, TMethodsAndOverrides, TVirtuals>[]>): void; + create>(doc: T | DocContents): Promise, TMethodsAndOverrides, TVirtuals>>; + create>(...docs: Array): Promise, TMethodsAndOverrides, TVirtuals>[]>; + create>(doc: T | DocContents, callback: Callback, TMethodsAndOverrides, TVirtuals>>): void; /** * Create the collection for this model. By default, if no indexes are specified, From ff2bf3e8b3abe0e09a523465e5db8743083702b9 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Thu, 24 Mar 2022 18:24:43 +0100 Subject: [PATCH 09/41] Improve ApplyBasicQueryCasting type to improve find FN suggestions --- test/types/models.test.ts | 4 ++++ types/query.d.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/test/types/models.test.ts b/test/types/models.test.ts index 48853cfc511..0372fc27cad 100644 --- a/test/types/models.test.ts +++ b/test/types/models.test.ts @@ -253,6 +253,10 @@ export async function m0_0aModel() { expectType(testDoc1.userName); expectType(testDoc1.description); + const testDoc2 = await AutoTypeModel.findOne({ userName: 'M0_0a' }); + expectType(testDoc2?.userName); + expectType(testDoc2?.description); + /* -------------------------------------------------------------------------- */ /* Model-statics-functions-test */ /* -------------------------------------------------------------------------- */ diff --git a/types/query.d.ts b/types/query.d.ts index ab7174f11b0..7062c52f5c7 100644 --- a/types/query.d.ts +++ b/types/query.d.ts @@ -1,7 +1,7 @@ declare module 'mongoose' { import mongodb = require('mongodb'); - type ApplyBasicQueryCasting = T | T[] | any; + export type ApplyBasicQueryCasting = T extends any[] ? T[0] & defaultT: defaultT; type Condition = ApplyBasicQueryCasting | QuerySelector>; type _FilterQuery = { From 1bf940ed38b7265d0c76187226f54f3d16dcc140 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Thu, 24 Mar 2022 18:44:23 +0100 Subject: [PATCH 10/41] Refact create FN returned type --- test/types/create.test.ts | 2 +- types/models.d.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/types/create.test.ts b/test/types/create.test.ts index 83372dd2b30..4492d113fca 100644 --- a/test/types/create.test.ts +++ b/test/types/create.test.ts @@ -3,7 +3,7 @@ import { Schema, model, Document, Types } from 'mongoose'; const schema: Schema = new Schema({ name: { type: 'String' } }); interface ITest extends Document { - _id?: Types.ObjectId | string; + _id?: Types.ObjectId; name?: string; } diff --git a/types/models.d.ts b/types/models.d.ts index 8e9e2ff256d..46357e1da20 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -164,11 +164,11 @@ declare module 'mongoose' { countDocuments(callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; /** Creates a new document or documents */ - create>(docs: Array, options?: SaveOptions): Promise, TMethodsAndOverrides, TVirtuals>[]>; - create>(docs: Array, callback: Callback, TMethodsAndOverrides, TVirtuals>[]>): void; - create>(doc: T | DocContents): Promise, TMethodsAndOverrides, TVirtuals>>; - create>(...docs: Array): Promise, TMethodsAndOverrides, TVirtuals>[]>; - create>(doc: T | DocContents, callback: Callback, TMethodsAndOverrides, TVirtuals>>): void; + create>(docs: Array, options?: SaveOptions): Promise, TMethodsAndOverrides, TVirtuals>[]>; + create>(docs: Array, callback: Callback, TMethodsAndOverrides, TVirtuals>[]>): void; + create>(doc: T | DocContents): Promise, TMethodsAndOverrides, TVirtuals>>; + create>(...docs: Array): Promise, TMethodsAndOverrides, TVirtuals>[]>; + create>(doc: T | DocContents, callback: Callback, TMethodsAndOverrides, TVirtuals>>): void; /** * Create the collection for this model. By default, if no indexes are specified, From 4a5d712058e109a3db0250bded38524dbecb79c2 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Sun, 27 Mar 2022 00:05:57 +0100 Subject: [PATCH 11/41] Refact Model new FN & some related tests. --- test/types/document.test.ts | 31 ++++++++++++++++++++++++++----- test/types/models.test.ts | 16 ---------------- types/index.d.ts | 2 +- types/models.d.ts | 4 ++-- 4 files changed, 29 insertions(+), 24 deletions(-) diff --git a/test/types/document.test.ts b/test/types/document.test.ts index 3f8d8ce2176..bc57a9316c9 100644 --- a/test/types/document.test.ts +++ b/test/types/document.test.ts @@ -1,5 +1,7 @@ import { Schema, model, Model, Document, Types } from 'mongoose'; -import { expectError, expectType } from 'tsd'; +import { expectAssignable, expectError, expectType } from 'tsd'; +import { m0_0aModel } from './models.test'; +import { M0_0aAutoTypedSchemaType } from './schema.test'; const Drink = model('Drink', new Schema({ name: String @@ -52,15 +54,16 @@ void async function run() { test.validateSync({ pathsToSkip: ['name', 'age'] }); test.validateSync({ pathsToSkip: 'name age' }); test.validateSync({ pathsToSkip: 'name age', blub: 1 }); - expectType>(test.save()); - expectType>(test.save({})); + const x = test.save(); + expectAssignable>(test.save()); + expectAssignable>(test.save({})); expectType(test.save({}, (err, doc) => { expectType(err); - expectType(doc); + expectAssignable(doc); })); expectType(test.save((err, doc) => { expectType(err); - expectType(doc); + expectAssignable(doc); })); })(); @@ -180,4 +183,22 @@ function gh11435() { async function gh11598() { const doc = await Test.findOne().orFail(); doc.populate('favoritDrink', undefined, model('temp', new Schema())); +} + +async function m0_0aDocument() { + const AutoTypeModel = await m0_0aModel(); + const AutoTypeModelInstance = new AutoTypeModel({ unExistProperty: 1, description: 2 }); + + expectType(AutoTypeModelInstance.userName); + expectType(AutoTypeModelInstance.favoritDrink); + expectType(AutoTypeModelInstance.favoritColorMode); + expectType(AutoTypeModelInstance.unExistProperty); + expectType(AutoTypeModelInstance.description); + + /* -------------------------------------------------------------------------- */ + /* Document-Instance-Methods-tests */ + /* -------------------------------------------------------------------------- */ + + expectType>(AutoTypeModelInstance.instanceFn()); + } \ No newline at end of file diff --git a/test/types/models.test.ts b/test/types/models.test.ts index 0372fc27cad..9740e6b10c1 100644 --- a/test/types/models.test.ts +++ b/test/types/models.test.ts @@ -263,21 +263,5 @@ export async function m0_0aModel() { expectType>(AutoTypeModel.staticFn()); - /* -------------------------------------------------------------------------- */ - /* Instance-tests */ - /* -------------------------------------------------------------------------- */ - - const AutoTypeModelInstance = new AutoTypeModel({}); - - expectType(AutoTypeModelInstance.userName); - expectType(AutoTypeModelInstance.favoritDrink); - expectType(AutoTypeModelInstance.favoritColorMode); - - /* -------------------------------------------------------------------------- */ - /* Document-Instance-Methods-tests */ - /* -------------------------------------------------------------------------- */ - - expectType>(AutoTypeModelInstance.instanceFn()); - return AutoTypeModel; } \ No newline at end of file diff --git a/types/index.d.ts b/types/index.d.ts index c7c9d8ad2b2..174a6cd6cd1 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -62,7 +62,7 @@ declare module 'mongoose' { /* ! ignore */ export type CompileModelOptions = { overwriteModels?: boolean, connection?: Connection }; - export function model( + export function model( name: string, schema?: TSchema, collection?: string, diff --git a/types/models.d.ts b/types/models.d.ts index 46357e1da20..7f73f9e07c6 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -114,13 +114,13 @@ declare module 'mongoose' { } const Model: Model; - type Model= + type Model = NodeJS.EventEmitter & AcceptsDiscriminator & IndexManager & SessionStarter & ObtainSchemaGeneric & { - new & AnyObject>(doc?: DocType, fields?: any | null, options?: boolean | AnyObject): HydratedDocument; + new(doc?: DocType, fields?: any | null, options?: boolean | AnyObject): HydratedDocument, TMethodsAndOverrides, TVirtuals>; aggregate(pipeline?: PipelineStage[], options?: mongodb.AggregateOptions, callback?: Callback): Aggregate>; aggregate(pipeline: PipelineStage[], callback?: Callback): Aggregate>; From 295bad267d0c0a8b3cc0339723c087747a2308ca Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Sun, 27 Mar 2022 00:12:31 +0100 Subject: [PATCH 12/41] Fix typo from the previous PR --- test/types/document.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/types/document.test.ts b/test/types/document.test.ts index bc57a9316c9..a34221ac693 100644 --- a/test/types/document.test.ts +++ b/test/types/document.test.ts @@ -186,8 +186,8 @@ async function gh11598() { } async function m0_0aDocument() { - const AutoTypeModel = await m0_0aModel(); - const AutoTypeModelInstance = new AutoTypeModel({ unExistProperty: 1, description: 2 }); + const AutoTypedModel = await m0_0aModel(); + const AutoTypeModelInstance = new AutoTypedModel({ unExistProperty: 1, description: 2 }); expectType(AutoTypeModelInstance.userName); expectType(AutoTypeModelInstance.favoritDrink); @@ -196,7 +196,7 @@ async function m0_0aDocument() { expectType(AutoTypeModelInstance.description); /* -------------------------------------------------------------------------- */ - /* Document-Instance-Methods-tests */ + /* Document-Methods-tests */ /* -------------------------------------------------------------------------- */ expectType>(AutoTypeModelInstance.instanceFn()); From cd215a7166b12008449576d78d7f650edb40b522 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Sun, 27 Mar 2022 00:52:46 +0100 Subject: [PATCH 13/41] Fix typo --- test/types/schema.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 8356825d217..3937dcd4d2f 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -472,11 +472,11 @@ export function m0_0aSchema() { const SchemaWithCustomTypeKey = new Schema({ name: { - customKTypeKey: String, + customTypeKey: String, required: true } }, { - typeKey: 'customKTypeKey' + typeKey: 'customTypeKey' }); expectType({} as InferSchemaType['name']); From a7967d9cded860f1241314f12cfc2c01fe5b8829 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Sun, 27 Mar 2022 01:53:23 +0100 Subject: [PATCH 14/41] Improve Model.insertMany FN type --- test/types/models.test.ts | 10 +++++++--- test/types/schema.test.ts | 8 ++++++-- types/index.d.ts | 2 -- types/models.d.ts | 22 +++++++++++----------- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/test/types/models.test.ts b/test/types/models.test.ts index 9740e6b10c1..dd5df44a067 100644 --- a/test/types/models.test.ts +++ b/test/types/models.test.ts @@ -253,9 +253,13 @@ export async function m0_0aModel() { expectType(testDoc1.userName); expectType(testDoc1.description); - const testDoc2 = await AutoTypeModel.findOne({ userName: 'M0_0a' }); - expectType(testDoc2?.userName); - expectType(testDoc2?.description); + const testDoc2 = await AutoTypeModel.insertMany([{ userName: 'M0_0a' }]); + expectType(testDoc2[0].userName); + expectType(testDoc2[0]?.description); + + const testDoc3 = await AutoTypeModel.findOne({ userName: 'M0_0a' }); + expectType(testDoc3?.userName); + expectType(testDoc3?.description); /* -------------------------------------------------------------------------- */ /* Model-statics-functions-test */ diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 3937dcd4d2f..7299f0504a6 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -369,10 +369,14 @@ export function m0_0aSchema() { } }, { statics: { - staticFn() { return 'Returned from staticFn'; } + staticFn() { + return 'Returned from staticFn'; + } }, methods: { - instanceFn() { return 'Returned from DocumentInstanceFn'; } + instanceFn() { + return 'Returned from DocumentInstanceFn'; + } } }); diff --git a/types/index.d.ts b/types/index.d.ts index 174a6cd6cd1..9e5e3eef3ed 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -94,8 +94,6 @@ declare module 'mongoose' { [k: string]: any } - export type FlexibleObject = { [P in keyof (T & Omit)]?: P extends keyof T ? T[P] : any }; - export type Require_id = T extends { _id?: any } ? (T & { _id: T['_id'] }) : (T & { _id: Types.ObjectId }); export type HydratedDocument = DocType extends Document ? Require_id : (Document & Require_id & TVirtuals & TMethodsAndOverrides); diff --git a/types/models.d.ts b/types/models.d.ts index 7f73f9e07c6..e706ee34ad8 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -164,11 +164,11 @@ declare module 'mongoose' { countDocuments(callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; /** Creates a new document or documents */ - create>(docs: Array, options?: SaveOptions): Promise, TMethodsAndOverrides, TVirtuals>[]>; - create>(docs: Array, callback: Callback, TMethodsAndOverrides, TVirtuals>[]>): void; - create>(doc: T | DocContents): Promise, TMethodsAndOverrides, TVirtuals>>; - create>(...docs: Array): Promise, TMethodsAndOverrides, TVirtuals>[]>; - create>(doc: T | DocContents, callback: Callback, TMethodsAndOverrides, TVirtuals>>): void; + create(docs: Array, options?: SaveOptions): Promise, TMethodsAndOverrides, TVirtuals>[]>; + create(docs: Array, callback: Callback, TMethodsAndOverrides, TVirtuals>[]>): void; + create(doc: T | DocContents): Promise, TMethodsAndOverrides, TVirtuals>>; + create(...docs: Array): Promise, TMethodsAndOverrides, TVirtuals>[]>; + create(doc: T | DocContents, callback: Callback, TMethodsAndOverrides, TVirtuals>>): void; /** * Create the collection for this model. By default, if no indexes are specified, @@ -257,12 +257,12 @@ declare module 'mongoose' { init(callback?: CallbackWithoutResult): Promise>; /** Inserts one or more new documents as a single `insertMany` call to the MongoDB server. */ - insertMany(docs: Array | AnyObject>, options: InsertManyOptions & { rawResult: true }): Promise>; - insertMany(docs: Array | AnyObject>, options?: InsertManyOptions): Promise>>; - insertMany(doc: AnyKeys | AnyObject, options: InsertManyOptions & { rawResult: true }): Promise>; - insertMany(doc: AnyKeys | AnyObject, options?: InsertManyOptions): Promise[]>; - insertMany(doc: AnyKeys | AnyObject, options?: InsertManyOptions, callback?: Callback[] | InsertManyResult>): void; - insertMany(docs: Array | AnyObject>, options?: InsertManyOptions, callback?: Callback> | InsertManyResult>): void; + insertMany(docs: Array, options: InsertManyOptions & { rawResult: true }): Promise>>; + insertMany(docs: Array, options?: InsertManyOptions): Promise, TMethodsAndOverrides, TVirtuals>>>; + insertMany(doc: DocContents, options: InsertManyOptions & { rawResult: true }): Promise>>; + insertMany(doc: DocContents, options?: InsertManyOptions): Promise, TMethodsAndOverrides, TVirtuals>[]>; + insertMany(doc: DocContents, options?: InsertManyOptions, callback?: Callback, TMethodsAndOverrides, TVirtuals>[] | InsertManyResult>>): void; + insertMany(docs: Array, options?: InsertManyOptions, callback?: Callback, TMethodsAndOverrides, TVirtuals>> | InsertManyResult>>): void; /** The name of the model */ modelName: string; From e9d4491690421887293fb4d6f82e4917f03c3626 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Mon, 28 Mar 2022 17:52:52 +0200 Subject: [PATCH 15/41] Add query property in schema options to support auto typed query helpers FNs --- lib/schema.js | 2 +- test/query.test.js | 27 +++++++ test/types/document.test.ts | 6 +- test/types/models.test.ts | 44 +++++------ test/types/queries.test.ts | 9 ++- test/types/schema.test.ts | 143 +++++++++++++++++++----------------- types/index.d.ts | 4 +- types/schemaoptions.d.ts | 7 +- 8 files changed, 146 insertions(+), 96 deletions(-) diff --git a/lib/schema.js b/lib/schema.js index a8527d157a6..e63bc861aee 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -111,7 +111,7 @@ function Schema(obj, options) { this.methodOptions = {}; this.statics = (options && options.statics) || {}; this.tree = {}; - this.query = {}; + this.query = (options && options.query) || {}; this.childSchemas = []; this.plugins = []; // For internal debugging. Do not use this to try to save a schema in MDB. diff --git a/test/query.test.js b/test/query.test.js index ab65be08332..9938a6c8872 100644 --- a/test/query.test.js +++ b/test/query.test.js @@ -3962,4 +3962,31 @@ describe('Query', function() { assert.equal(result.length, 1); assert.equal(result[0].name, '@foo.com'); }); + + it('should return query helper supplied in schema options query property instead of undefined', function(done) { + + const Model = db.model('Test', new Schema({ + userName: { + type: String, + required: [true, 'userName is required'] + } + }, { + query: { + byUserName(userName) { + return this.where({ userName }); + } + } + })); + + Model.create({ userName: 'test' }, function(error) { + assert.ifError(error); + Model.find().byUserName('test').exec(function(error, docs) { + assert.ifError(error); + assert.equal(docs.length, 1); + assert.equal(docs[0].userName, 'test'); + done(); + }); + }); + }); + }); diff --git a/test/types/document.test.ts b/test/types/document.test.ts index a34221ac693..2ba4d4994d1 100644 --- a/test/types/document.test.ts +++ b/test/types/document.test.ts @@ -1,6 +1,6 @@ import { Schema, model, Model, Document, Types } from 'mongoose'; import { expectAssignable, expectError, expectType } from 'tsd'; -import { m0_0aModel } from './models.test'; +import { autoTypedModel } from './models.test'; import { M0_0aAutoTypedSchemaType } from './schema.test'; const Drink = model('Drink', new Schema({ @@ -185,8 +185,8 @@ async function gh11598() { doc.populate('favoritDrink', undefined, model('temp', new Schema())); } -async function m0_0aDocument() { - const AutoTypedModel = await m0_0aModel(); +function m0_0aDocument() { + const AutoTypedModel = autoTypedModel(); const AutoTypeModelInstance = new AutoTypedModel({ unExistProperty: 1, description: 2 }); expectType(AutoTypeModelInstance.userName); diff --git a/test/types/models.test.ts b/test/types/models.test.ts index dd5df44a067..a0272907447 100644 --- a/test/types/models.test.ts +++ b/test/types/models.test.ts @@ -1,6 +1,6 @@ import { Schema, Document, Model, Types, connection, model } from 'mongoose'; import { expectError, expectType } from 'tsd'; -import { M0_0aAutoTypedSchemaType, m0_0aSchema } from './schema.test'; +import { M0_0aAutoTypedSchemaType, autoTypedSchema } from './schema.test'; function conventionalSyntax(): void { interface ITest extends Document { @@ -237,35 +237,37 @@ function bulkWrite() { M.bulkWrite(ops); } -export async function m0_0aModel() { - const AutoTypeSchema = m0_0aSchema(); - const AutoTypeModel = model('AutoTypeModel', AutoTypeSchema); +export function autoTypedModel() { + const AutoTypedSchema = autoTypedSchema(); + const AutoTypedModel = model('AutoTypeModel', AutoTypedSchema); + (async() => { /* -------------------------------------------------------------------------- */ /* Model-functions-test */ /* -------------------------------------------------------------------------- */ // Create should works with arbitrary objects. - const randomObject = await AutoTypeModel.create({ unExistKey: 'unExistKey', description: 'st' }); - expectType(randomObject.unExistKey); - expectType(randomObject.userName); + const randomObject = await AutoTypedModel.create({ unExistKey: 'unExistKey', description: 'st' }); + expectType(randomObject.unExistKey); + expectType(randomObject.userName); - const testDoc1 = await AutoTypeModel.create({ userName: 'M0_0a' }); - expectType(testDoc1.userName); - expectType(testDoc1.description); + const testDoc1 = await AutoTypedModel.create({ userName: 'M0_0a' }); + expectType(testDoc1.userName); + expectType(testDoc1.description); - const testDoc2 = await AutoTypeModel.insertMany([{ userName: 'M0_0a' }]); - expectType(testDoc2[0].userName); - expectType(testDoc2[0]?.description); + const testDoc2 = await AutoTypedModel.insertMany([{ userName: 'M0_0a' }]); + expectType(testDoc2[0].userName); + expectType(testDoc2[0]?.description); - const testDoc3 = await AutoTypeModel.findOne({ userName: 'M0_0a' }); - expectType(testDoc3?.userName); - expectType(testDoc3?.description); + const testDoc3 = await AutoTypedModel.findOne({ userName: 'M0_0a' }); + expectType(testDoc3?.userName); + expectType(testDoc3?.description); - /* -------------------------------------------------------------------------- */ - /* Model-statics-functions-test */ - /* -------------------------------------------------------------------------- */ + /* -------------------------------------------------------------------------- */ + /* Model-statics-functions-test */ + /* -------------------------------------------------------------------------- */ - expectType>(AutoTypeModel.staticFn()); + expectType>(AutoTypedModel.staticFn()); - return AutoTypeModel; + })(); + return AutoTypedModel; } \ No newline at end of file diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts index 1f029a99158..d03e8eaac25 100644 --- a/test/types/queries.test.ts +++ b/test/types/queries.test.ts @@ -1,6 +1,8 @@ import { HydratedDocument, Schema, model, Document, Types, Query, Model, QueryWithHelpers, PopulatedDoc, FilterQuery, UpdateQuery } from 'mongoose'; import { ObjectId } from 'mongodb'; import { expectError, expectType } from 'tsd'; +import { autoTypedModel } from './models.test'; +import { M0_0aAutoTypedSchemaType } from './schema.test'; interface QueryHelpers { _byName(this: QueryWithHelpers, name: string): QueryWithHelpers, ITest, QueryHelpers>; @@ -286,4 +288,9 @@ async function gh11602(): Promise { expectError(updateResult.lastErrorObject?.modifiedCount); expectType(updateResult.lastErrorObject?.updatedExisting); expectType(updateResult.lastErrorObject?.upserted); -} \ No newline at end of file +} + +function autoTypedQuery() { + const AutoTypedModel = autoTypedModel(); + expectType(AutoTypedModel.find().byUserName); +} diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 7299f0504a6..add95fe1d6c 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -1,4 +1,4 @@ -import { Schema, Document, SchemaDefinition, Model, Types, InferSchemaType, SchemaType } from 'mongoose'; +import { Schema, Document, SchemaDefinition, Model, Types, InferSchemaType, SchemaType, Query } from 'mongoose'; import { expectType, expectError } from 'tsd'; enum Genre { @@ -319,73 +319,8 @@ function gh10900(): void { menuStatus: { type: Schema.Types.Mixed, default: {} } }); } -export type M0_0aAutoTypedSchemaType = { - schema: { - userName: string; - description?: string; - nested?: { - age: number; - hobby?: string - }, - favoritDrink?: 'Tea' | 'Coffee', - favoritColorMode: 'dark' | 'light' - } - , statics: { - staticFn: () => 'Returned from staticFn' - }, - methods: { - instanceFn: () => 'Returned from DocumentInstanceFn' - } -}; - -export function m0_0aSchema() { - const AutoTypedSchema = new Schema({ - userName: { - type: String, - required: [true, 'userName is required'] - }, - description: String, - nested: new Schema({ - age: { - type: Number, - required: true - }, - hobby: { - type: String, - required: false - } - }), - favoritDrink: { - type: String, - enum: ['Coffee', 'Tea'] - }, - favoritColorMode: { - type: String, - enum: { - values: ['dark', 'light'], - message: '{VALUE} is not supported' - }, - required: true - } - }, { - statics: { - staticFn() { - return 'Returned from staticFn'; - } - }, - methods: { - instanceFn() { - return 'Returned from DocumentInstanceFn'; - } - } - }); - - type InferredSchemaType = InferSchemaType; - - expectType({} as InferredSchemaType); - - expectError({} as InferredSchemaType); +export function autoTypedSchema() { // Test auto schema type obtaining with all possible path types. class Int8 extends SchemaType { @@ -485,5 +420,79 @@ export function m0_0aSchema() { expectType({} as InferSchemaType['name']); + const AutoTypedSchema = new Schema({ + userName: { + type: String, + required: [true, 'userName is required'] + }, + description: String, + nested: new Schema({ + age: { + type: Number, + required: true + }, + hobby: { + type: String, + required: false + } + }), + favoritDrink: { + type: String, + enum: ['Coffee', 'Tea'] + }, + favoritColorMode: { + type: String, + enum: { + values: ['dark', 'light'], + message: '{VALUE} is not supported' + }, + required: true + } + }, { + statics: { + staticFn() { + return 'Returned from staticFn'; + } + }, + methods: { + instanceFn() { + return 'Returned from DocumentInstanceFn'; + } + }, + query: { + byUserName(userName) { + return this.where({ userName }); + } + } + }); + + type InferredSchemaType = InferSchemaType; + + expectType({} as InferredSchemaType); + + expectError({} as InferredSchemaType); + return AutoTypedSchema; } + +export type M0_0aAutoTypedSchemaType = { + schema: { + userName: string; + description?: string; + nested?: { + age: number; + hobby?: string + }, + favoritDrink?: 'Tea' | 'Coffee', + favoritColorMode: 'dark' | 'light' + } + , statics: { + staticFn: () => 'Returned from staticFn' + }, + methods: { + instanceFn: () => 'Returned from DocumentInstanceFn' + }, + query: { + byUserName: >(this: T, userName: any) => T + } +}; \ No newline at end of file diff --git a/types/index.d.ts b/types/index.d.ts index 9e5e3eef3ed..80eb9ffefea 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -69,7 +69,7 @@ declare module 'mongoose' { options?: CompileModelOptions ): U extends Model ? U - : Model, TQueryHelpers, ObtainSchemaGeneric, {}, TSchema>; + : Model, TQueryHelpers & ObtainSchemaGeneric, ObtainSchemaGeneric, {}, TSchema>; /** Returns an array of model names created on this instance of Mongoose. */ export function modelNames(): Array; @@ -131,7 +131,7 @@ declare module 'mongoose' { /** * Create a new schema */ - constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions); + constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions); /** Adds key path / schema type pairs to this schema. */ add(obj: SchemaDefinition> | Schema, prefix?: string): this; diff --git a/types/schemaoptions.d.ts b/types/schemaoptions.d.ts index 6b48d7bb14a..66cbe656068 100644 --- a/types/schemaoptions.d.ts +++ b/types/schemaoptions.d.ts @@ -10,7 +10,7 @@ declare module 'mongoose' { type TypeKeyBaseType = string; type DefaultTypeKey = 'type'; - interface SchemaOptions { + interface SchemaOptions { /** * By default, Mongoose's init() function creates all the indexes defined in your model's schema by * calling Model.createIndexes() after you successfully connect to MongoDB. If you want to disable @@ -197,5 +197,10 @@ declare module 'mongoose' { * Document instance methods. */ methods?: InstanceMethods, + + /** + * Query helper functions + */ + query?: Record>(this: T, ...args: any) => T> | QueryHelpers, } } \ No newline at end of file From 526ff6a7e387beb99030055d1797aa36a6a1cfd0 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Mon, 28 Mar 2022 22:37:55 +0200 Subject: [PATCH 16/41] Rearrang Schema class generics --- types/index.d.ts | 4 ++-- types/inferschematype.d.ts | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/types/index.d.ts b/types/index.d.ts index 80eb9ffefea..4d0bc823787 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -124,9 +124,9 @@ declare module 'mongoose' { } export class Schema, TInstanceMethods = {}, TQueryHelpers = {}, TVirtuals = any, + TStaticMethods = {}, TPathTypeKey extends TypeKeyBaseType = DefaultTypeKey, - DocType extends ObtainDocumentType = ObtainDocumentType, - TStaticMethods = {}> + DocType extends ObtainDocumentType = ObtainDocumentType> extends events.EventEmitter { /** * Create a new schema diff --git a/types/inferschematype.d.ts b/types/inferschematype.d.ts index 6d401ef9e30..8596e35c653 100644 --- a/types/inferschematype.d.ts +++ b/types/inferschematype.d.ts @@ -32,9 +32,18 @@ declare module 'mongoose' { * @param {TSchema} TSchema A generic of schema type instance. * @param {alias} alias Targeted generic alias. */ - type ObtainSchemaGeneric = - TSchema extends Schema - ? { EnforcedDocType: EnforcedDocType, M: M, TInstanceMethods: TInstanceMethods, TQueryHelpers: TQueryHelpers, TVirtuals: TVirtuals, TPathTypeKey: TPathTypeKey, DocType: DocType, TStaticMethods: TStaticMethods }[alias] + type ObtainSchemaGeneric = + TSchema extends Schema + ? { + EnforcedDocType: EnforcedDocType; + M: M; + TInstanceMethods: TInstanceMethods; + TQueryHelpers: TQueryHelpers; + TVirtuals: TVirtuals; + TStaticMethods: TStaticMethods; + TPathTypeKey: TPathTypeKey; + DocType: DocType; + }[alias] : unknown; } /** From 411350baca4ee80642e49b50b682681e04000de4 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Mon, 28 Mar 2022 22:48:27 +0200 Subject: [PATCH 17/41] Rearrange SchemaOptions generics. --- types/index.d.ts | 2 +- types/schemaoptions.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/types/index.d.ts b/types/index.d.ts index 4d0bc823787..63d42fb98ae 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -131,7 +131,7 @@ declare module 'mongoose' { /** * Create a new schema */ - constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions); + constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions); /** Adds key path / schema type pairs to this schema. */ add(obj: SchemaDefinition> | Schema, prefix?: string): this; diff --git a/types/schemaoptions.d.ts b/types/schemaoptions.d.ts index 66cbe656068..cb9160dc3d9 100644 --- a/types/schemaoptions.d.ts +++ b/types/schemaoptions.d.ts @@ -10,7 +10,7 @@ declare module 'mongoose' { type TypeKeyBaseType = string; type DefaultTypeKey = 'type'; - interface SchemaOptions { + interface SchemaOptions { /** * By default, Mongoose's init() function creates all the indexes defined in your model's schema by * calling Model.createIndexes() after you successfully connect to MongoDB. If you want to disable From b942ce832b9de2f1989e4b9c4acf6abb711948e1 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Tue, 29 Mar 2022 13:43:44 +0200 Subject: [PATCH 18/41] refactor query type in SchemaOptions & related tests. --- test/types/queries.test.ts | 3 ++- test/types/schema.test.ts | 4 +--- types/schemaoptions.d.ts | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts index d03e8eaac25..e137782d8a2 100644 --- a/test/types/queries.test.ts +++ b/test/types/queries.test.ts @@ -292,5 +292,6 @@ async function gh11602(): Promise { function autoTypedQuery() { const AutoTypedModel = autoTypedModel(); - expectType(AutoTypedModel.find().byUserName); + const query = AutoTypedModel.find(); + expectType(AutoTypedModel.find().byUserName('')); } diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index add95fe1d6c..872d14efff6 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -461,6 +461,7 @@ export function autoTypedSchema() { }, query: { byUserName(userName) { + expectType>(this); return this.where({ userName }); } } @@ -492,7 +493,4 @@ export type M0_0aAutoTypedSchemaType = { methods: { instanceFn: () => 'Returned from DocumentInstanceFn' }, - query: { - byUserName: >(this: T, userName: any) => T - } }; \ No newline at end of file diff --git a/types/schemaoptions.d.ts b/types/schemaoptions.d.ts index cb9160dc3d9..307f3bd0c45 100644 --- a/types/schemaoptions.d.ts +++ b/types/schemaoptions.d.ts @@ -201,6 +201,6 @@ declare module 'mongoose' { /** * Query helper functions */ - query?: Record>(this: T, ...args: any) => T> | QueryHelpers, + query?: Record>(this: T, ...args: any) => T> | QueryHelpers, } } \ No newline at end of file From 6bcb7a4362644688c7346ff15a50ab619e74f33a Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Tue, 29 Mar 2022 14:34:46 +0200 Subject: [PATCH 19/41] refactor statics & methods properties in SchemaOptions to get better intellisense. --- test/types/schema.test.ts | 10 ++++++---- types/schemaoptions.d.ts | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 872d14efff6..dd6ed03d2d9 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -1,5 +1,5 @@ import { Schema, Document, SchemaDefinition, Model, Types, InferSchemaType, SchemaType, Query } from 'mongoose'; -import { expectType, expectError } from 'tsd'; +import { expectType, expectError, expectAssignable, expectNotAssignable } from 'tsd'; enum Genre { Action, @@ -451,17 +451,19 @@ export function autoTypedSchema() { }, { statics: { staticFn() { - return 'Returned from staticFn'; + expectAssignable>(this); + return 'Returned from staticFn' as const; } }, methods: { instanceFn() { - return 'Returned from DocumentInstanceFn'; + expectAssignable(this); + return 'Returned from DocumentInstanceFn' as const; } }, query: { byUserName(userName) { - expectType>(this); + expectAssignable>(this); return this.where({ userName }); } } diff --git a/types/schemaoptions.d.ts b/types/schemaoptions.d.ts index 307f3bd0c45..fd24243e74d 100644 --- a/types/schemaoptions.d.ts +++ b/types/schemaoptions.d.ts @@ -191,12 +191,12 @@ declare module 'mongoose' { /** * Model Statics methods. */ - statics?: StaticMethods, + statics?: Record>(this: T, ...args: any) => unknown> | StaticMethods, /** * Document instance methods. */ - methods?: InstanceMethods, + methods?: Record(this: T, ...args: any) => unknown> | InstanceMethods, /** * Query helper functions From 56af40b027e1199fd772444b4d39a66b2290d8f6 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Tue, 29 Mar 2022 19:37:22 +0200 Subject: [PATCH 20/41] Pass DocType to statics, methods & query props in SchemaOptions to get better intellisense. --- test/types/schema.test.ts | 8 ++++---- types/index.d.ts | 2 +- types/schemaoptions.d.ts | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index dd6ed03d2d9..06aa12053e7 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -1,5 +1,5 @@ import { Schema, Document, SchemaDefinition, Model, Types, InferSchemaType, SchemaType, Query } from 'mongoose'; -import { expectType, expectError, expectAssignable, expectNotAssignable } from 'tsd'; +import { expectType, expectError, expectAssignable } from 'tsd'; enum Genre { Action, @@ -451,19 +451,19 @@ export function autoTypedSchema() { }, { statics: { staticFn() { - expectAssignable>(this); + expectType>(this); return 'Returned from staticFn' as const; } }, methods: { instanceFn() { - expectAssignable(this); + expectAssignable>(this); return 'Returned from DocumentInstanceFn' as const; } }, query: { byUserName(userName) { - expectAssignable>(this); + expectAssignable>(this); return this.where({ userName }); } } diff --git a/types/index.d.ts b/types/index.d.ts index 63d42fb98ae..e649d8c3ccd 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -131,7 +131,7 @@ declare module 'mongoose' { /** * Create a new schema */ - constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions); + constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions); /** Adds key path / schema type pairs to this schema. */ add(obj: SchemaDefinition> | Schema, prefix?: string): this; diff --git a/types/schemaoptions.d.ts b/types/schemaoptions.d.ts index fd24243e74d..8a8e514f886 100644 --- a/types/schemaoptions.d.ts +++ b/types/schemaoptions.d.ts @@ -10,7 +10,7 @@ declare module 'mongoose' { type TypeKeyBaseType = string; type DefaultTypeKey = 'type'; - interface SchemaOptions { + interface SchemaOptions { /** * By default, Mongoose's init() function creates all the indexes defined in your model's schema by * calling Model.createIndexes() after you successfully connect to MongoDB. If you want to disable @@ -191,16 +191,16 @@ declare module 'mongoose' { /** * Model Statics methods. */ - statics?: Record>(this: T, ...args: any) => unknown> | StaticMethods, + statics?: Record>(this: T, ...args: any) => unknown> | StaticMethods, /** * Document instance methods. */ - methods?: Record(this: T, ...args: any) => unknown> | InstanceMethods, + methods?: Record>(this: T, ...args: any) => unknown> | InstanceMethods, /** * Query helper functions */ - query?: Record>(this: T, ...args: any) => T> | QueryHelpers, + query?: Record>(this: T, ...args: any) => T> | QueryHelpers, } } \ No newline at end of file From 9cdec80c54abcf66c222d80f276696ef0039dfb4 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Tue, 29 Mar 2022 23:51:32 +0200 Subject: [PATCH 21/41] Refactor statics & methods props in SchemaOptions. --- test/types/document.test.ts | 2 +- test/types/schema.test.ts | 4 ++-- types/schemaoptions.d.ts | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/types/document.test.ts b/test/types/document.test.ts index 2ba4d4994d1..639775d370c 100644 --- a/test/types/document.test.ts +++ b/test/types/document.test.ts @@ -199,6 +199,6 @@ function m0_0aDocument() { /* Document-Methods-tests */ /* -------------------------------------------------------------------------- */ - expectType>(AutoTypeModelInstance.instanceFn()); + expectType>(new AutoTypedModel().instanceFn()); } \ No newline at end of file diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 06aa12053e7..5064fbf7888 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -1,4 +1,4 @@ -import { Schema, Document, SchemaDefinition, Model, Types, InferSchemaType, SchemaType, Query } from 'mongoose'; +import { Schema, Document, SchemaDefinition, Model, Types, InferSchemaType, SchemaType, Query, HydratedDocument } from 'mongoose'; import { expectType, expectError, expectAssignable } from 'tsd'; enum Genre { @@ -457,7 +457,7 @@ export function autoTypedSchema() { }, methods: { instanceFn() { - expectAssignable>(this); + expectType>(this); return 'Returned from DocumentInstanceFn' as const; } }, diff --git a/types/schemaoptions.d.ts b/types/schemaoptions.d.ts index 8a8e514f886..689383a3f2d 100644 --- a/types/schemaoptions.d.ts +++ b/types/schemaoptions.d.ts @@ -10,7 +10,7 @@ declare module 'mongoose' { type TypeKeyBaseType = string; type DefaultTypeKey = 'type'; - interface SchemaOptions { + interface SchemaOptions { /** * By default, Mongoose's init() function creates all the indexes defined in your model's schema by * calling Model.createIndexes() after you successfully connect to MongoDB. If you want to disable @@ -191,12 +191,12 @@ declare module 'mongoose' { /** * Model Statics methods. */ - statics?: Record>(this: T, ...args: any) => unknown> | StaticMethods, + statics?: Record, ...args: any) => unknown> | StaticMethods, /** * Document instance methods. */ - methods?: Record>(this: T, ...args: any) => unknown> | InstanceMethods, + methods?: Record, ...args: any) => unknown> | InstanceMethods, /** * Query helper functions From b3e96681425030f20a04f59b37aa79e416259cdb Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Mon, 4 Apr 2022 22:11:17 +0200 Subject: [PATCH 22/41] Add ability to infer path of mixed array type. --- test/types/schema.test.ts | 11 ++++++++++- types/inferschematype.d.ts | 34 +++++++++++++++++++--------------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 5064fbf7888..6f69b8d6895 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -1,5 +1,6 @@ import { Schema, Document, SchemaDefinition, Model, Types, InferSchemaType, SchemaType, Query, HydratedDocument } from 'mongoose'; import { expectType, expectError, expectAssignable } from 'tsd'; +import { IsTAny } from '../../types/inferschematype'; enum Genre { Action, @@ -371,6 +372,10 @@ export function autoTypedSchema() { customSchema?: Int8; map1?: Map; map2?: Map; + array1?: string[]; + array2?: Schema.Types.Mixed[]; + array3?: Schema.Types.Mixed[]; + array4?: Schema.Types.Mixed[]; }; const TestSchema = new Schema({ @@ -402,7 +407,11 @@ export function autoTypedSchema() { objectId3: 'objectId', customSchema: Int8, map1: { type: Map, of: String }, - map2: { type: Map, of: Number } + map2: { type: Map, of: Number }, + array1: { type: [String] }, + array2: { type: Array }, + array3: { type: [Schema.Types.Mixed] }, + array4: { type: [{}] } }); type InferredTestSchemaType = InferSchemaType; diff --git a/types/inferschematype.d.ts b/types/inferschematype.d.ts index 8596e35c653..8ba5f237c44 100644 --- a/types/inferschematype.d.ts +++ b/types/inferschematype.d.ts @@ -23,8 +23,8 @@ declare module 'mongoose' { * // result * type UserType = {userName?: string} */ - type InferSchemaType = SchemaType extends Schema - ? IsItRecordAndNotAny extends true ? DocType : ObtainSchemaGeneric + type InferSchemaType = SchemaType extends Schema + ? IsItRecordAndNotAny extends true ? EnforcedDocType : ObtainSchemaGeneric : unknown; /** @@ -52,7 +52,9 @@ declare module 'mongoose' { * @param {T} T A generic type to be checked. * @returns true if {@link T} is Record OR false if {@link T} is of any type. */ -type IsItRecordAndNotAny = T extends any[] ? false : T extends Record ? true : false; +type IsItRecordAndNotAny = IsTAny extends true ? false : T extends Record ? true : false; + +type IsTAny = keyof any extends keyof T ? (unknown extends T ? true : false) : false; /** * @summary Required path base type. @@ -129,15 +131,17 @@ type PathEnumOrString['enum']> = T extends ( * @returns Number, "Number" or "number" will be resolved to string type. */ type ResolvePathType = {}> = -PathValueType extends (infer Item)[] ? ResolvePathType[] : - PathValueType extends StringConstructor | 'string' | 'String' | typeof Schema.Types.String ? PathEnumOrString : - PathValueType extends NumberConstructor | 'number' | 'Number' | typeof Schema.Types.Number ? number : - PathValueType extends DateConstructor | 'date' | 'Date' | typeof Schema.Types.Date ? Date : - PathValueType extends BufferConstructor | 'buffer' | 'Buffer' | typeof Schema.Types.Buffer ? Buffer : - PathValueType extends BooleanConstructor | 'boolean' | 'Boolean' | typeof Schema.Types.Boolean ? boolean : - PathValueType extends 'objectId' | 'ObjectId' | typeof Schema.Types.ObjectId ? Schema.Types.ObjectId : - PathValueType extends ObjectConstructor | typeof Schema.Types.Mixed ? Schema.Types.Mixed : - keyof PathValueType extends never ? Schema.Types.Mixed : - PathValueType extends MapConstructor ? Map> : - PathValueType extends typeof SchemaType ? PathValueType['prototype'] : - unknown; \ No newline at end of file + IsTAny extends true ? Schema.Types.Mixed: + PathValueType extends (infer Item)[] ? ResolvePathType[] : + PathValueType extends StringConstructor | 'string' | 'String' | typeof Schema.Types.String ? PathEnumOrString : + PathValueType extends NumberConstructor | 'number' | 'Number' | typeof Schema.Types.Number ? number : + PathValueType extends DateConstructor | 'date' | 'Date' | typeof Schema.Types.Date ? Date : + PathValueType extends BufferConstructor | 'buffer' | 'Buffer' | typeof Schema.Types.Buffer ? Buffer : + PathValueType extends BooleanConstructor | 'boolean' | 'Boolean' | typeof Schema.Types.Boolean ? boolean : + PathValueType extends 'objectId' | 'ObjectId' | typeof Schema.Types.ObjectId ? Schema.Types.ObjectId : + PathValueType extends ObjectConstructor | typeof Schema.Types.Mixed ? Schema.Types.Mixed : + PathValueType extends MapConstructor ? Map> : + PathValueType extends ArrayConstructor ? Schema.Types.Mixed[] : + keyof PathValueType extends keyof {} ? Schema.Types.Mixed : + PathValueType extends typeof SchemaType ? PathValueType['prototype'] : + unknown; From a42bbde14691bc8b5f33aa5a38e5e1a864d1c8fc Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Tue, 5 Apr 2022 00:52:15 +0200 Subject: [PATCH 23/41] Refactor the previous PR. --- test/types/schema.test.ts | 11 ++++++----- types/inferschematype.d.ts | 16 ++++++++++++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 6f69b8d6895..517697fc34e 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -1,6 +1,5 @@ import { Schema, Document, SchemaDefinition, Model, Types, InferSchemaType, SchemaType, Query, HydratedDocument } from 'mongoose'; import { expectType, expectError, expectAssignable } from 'tsd'; -import { IsTAny } from '../../types/inferschematype'; enum Genre { Action, @@ -376,6 +375,7 @@ export function autoTypedSchema() { array2?: Schema.Types.Mixed[]; array3?: Schema.Types.Mixed[]; array4?: Schema.Types.Mixed[]; + array5?: Schema.Types.Mixed[]; }; const TestSchema = new Schema({ @@ -408,10 +408,11 @@ export function autoTypedSchema() { customSchema: Int8, map1: { type: Map, of: String }, map2: { type: Map, of: Number }, - array1: { type: [String] }, - array2: { type: Array }, - array3: { type: [Schema.Types.Mixed] }, - array4: { type: [{}] } + array1: [String], + array2: Array, + array3: [Schema.Types.Mixed], + array4: [{}], + array5: [] }); type InferredTestSchemaType = InferSchemaType; diff --git a/types/inferschematype.d.ts b/types/inferschematype.d.ts index 8ba5f237c44..27f82bd20bc 100644 --- a/types/inferschematype.d.ts +++ b/types/inferschematype.d.ts @@ -52,9 +52,18 @@ declare module 'mongoose' { * @param {T} T A generic type to be checked. * @returns true if {@link T} is Record OR false if {@link T} is of any type. */ -type IsItRecordAndNotAny = IsTAny extends true ? false : T extends Record ? true : false; +type IsItRecordAndNotAny = IfEquals ? true : false>; -type IsTAny = keyof any extends keyof T ? (unknown extends T ? true : false) : false; +/** + * @summary Checks if two types are identical. + * @param {T} T The first type to be compared with {@link U}. + * @param {U} U The seconde type to be compared with {@link T}. + * @param {Y} Y A type to be returned if {@link T} & {@link U} are identical. + * @param {N} N A type to be returned if {@link T} & {@link U} are not identical. + */ +type IfEquals = + (() => G extends T ? 1 : 0) extends + (() => G extends U ? 1 : 0) ? Y : N; /** * @summary Required path base type. @@ -131,8 +140,7 @@ type PathEnumOrString['enum']> = T extends ( * @returns Number, "Number" or "number" will be resolved to string type. */ type ResolvePathType = {}> = - IsTAny extends true ? Schema.Types.Mixed: - PathValueType extends (infer Item)[] ? ResolvePathType[] : + PathValueType extends (infer Item)[] ? IfEquals>[] : PathValueType extends StringConstructor | 'string' | 'String' | typeof Schema.Types.String ? PathEnumOrString : PathValueType extends NumberConstructor | 'number' | 'Number' | typeof Schema.Types.Number ? number : PathValueType extends DateConstructor | 'date' | 'Date' | typeof Schema.Types.Date ? Date : From 302de56d74d6b46d61e9aef2185002b81dd283cf Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Wed, 6 Apr 2022 00:50:54 +0200 Subject: [PATCH 24/41] Refactor some function names & comment lines. --- test/document.test.js | 4 ++-- test/model.test.js | 4 ++-- test/query.test.js | 4 +++- test/types/document.test.ts | 17 +++++++---------- test/types/models.test.ts | 27 +++++++++++---------------- test/types/queries.test.ts | 2 +- test/types/schema.test.ts | 12 ++++++------ 7 files changed, 32 insertions(+), 38 deletions(-) diff --git a/test/document.test.js b/test/document.test.js index a7ad980e645..8882e9b2835 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -11456,8 +11456,8 @@ describe('document', function() { }); }); -describe('Check if instance functions that is supplied in schema option is availabe (m0_0a)', function() { - it('should give an instance function back rather than undefined', function M0_0aModelJS() { +describe('Check if instance functions that is supplied in schema option is availabe', function() { + it('should give an instance function back rather than undefined', function ModelJS() { const testSchema = new mongoose.Schema({}, { methods: { instanceFn() { return 'Returned from DocumentInstanceFn'; } } }); const TestModel = mongoose.model('TestModel', testSchema); const TestDocument = new TestModel({}); diff --git a/test/model.test.js b/test/model.test.js index f1ee48308c6..ed1579aba47 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -8505,8 +8505,8 @@ describe('Model', function() { }); }); -describe('Check if statics functions that is supplied in schema option is availabe (m0_0a)', function() { - it('should give a static function back rather than undefined', function M0_0aModelJS() { +describe('Check if statics functions that is supplied in schema option is availabe', function() { + it('should give a static function back rather than undefined', function ModelJS() { const testSchema = new mongoose.Schema({}, { statics: { staticFn() { return 'Returned from staticFn'; } } }); const TestModel = mongoose.model('TestModel', testSchema); assert.equal(TestModel.staticFn(), 'Returned from staticFn'); diff --git a/test/query.test.js b/test/query.test.js index 9938a6c8872..fc35bf2fb78 100644 --- a/test/query.test.js +++ b/test/query.test.js @@ -3981,7 +3981,9 @@ describe('Query', function() { Model.create({ userName: 'test' }, function(error) { assert.ifError(error); Model.find().byUserName('test').exec(function(error, docs) { - assert.ifError(error); + if (error instanceof Error) { + return done(error); + } assert.equal(docs.length, 1); assert.equal(docs[0].userName, 'test'); done(); diff --git a/test/types/document.test.ts b/test/types/document.test.ts index 639775d370c..3319a4a59a6 100644 --- a/test/types/document.test.ts +++ b/test/types/document.test.ts @@ -1,7 +1,7 @@ import { Schema, model, Model, Document, Types } from 'mongoose'; import { expectAssignable, expectError, expectType } from 'tsd'; import { autoTypedModel } from './models.test'; -import { M0_0aAutoTypedSchemaType } from './schema.test'; +import { AutoTypedSchemaType } from './schema.test'; const Drink = model('Drink', new Schema({ name: String @@ -185,20 +185,17 @@ async function gh11598() { doc.populate('favoritDrink', undefined, model('temp', new Schema())); } -function m0_0aDocument() { +function autoTypedDocument() { const AutoTypedModel = autoTypedModel(); const AutoTypeModelInstance = new AutoTypedModel({ unExistProperty: 1, description: 2 }); - expectType(AutoTypeModelInstance.userName); - expectType(AutoTypeModelInstance.favoritDrink); - expectType(AutoTypeModelInstance.favoritColorMode); + expectType(AutoTypeModelInstance.userName); + expectType(AutoTypeModelInstance.favoritDrink); + expectType(AutoTypeModelInstance.favoritColorMode); expectType(AutoTypeModelInstance.unExistProperty); expectType(AutoTypeModelInstance.description); - /* -------------------------------------------------------------------------- */ - /* Document-Methods-tests */ - /* -------------------------------------------------------------------------- */ - - expectType>(new AutoTypedModel().instanceFn()); + // Document-Methods-tests + expectType>(new AutoTypedModel().instanceFn()); } \ No newline at end of file diff --git a/test/types/models.test.ts b/test/types/models.test.ts index a0272907447..92a838a4a7b 100644 --- a/test/types/models.test.ts +++ b/test/types/models.test.ts @@ -1,6 +1,6 @@ import { Schema, Document, Model, Types, connection, model } from 'mongoose'; import { expectError, expectType } from 'tsd'; -import { M0_0aAutoTypedSchemaType, autoTypedSchema } from './schema.test'; +import { AutoTypedSchemaType, autoTypedSchema } from './schema.test'; function conventionalSyntax(): void { interface ITest extends Document { @@ -242,31 +242,26 @@ export function autoTypedModel() { const AutoTypedModel = model('AutoTypeModel', AutoTypedSchema); (async() => { - /* -------------------------------------------------------------------------- */ - /* Model-functions-test */ - /* -------------------------------------------------------------------------- */ + // Model-functions-test // Create should works with arbitrary objects. const randomObject = await AutoTypedModel.create({ unExistKey: 'unExistKey', description: 'st' }); expectType(randomObject.unExistKey); - expectType(randomObject.userName); + expectType(randomObject.userName); const testDoc1 = await AutoTypedModel.create({ userName: 'M0_0a' }); - expectType(testDoc1.userName); - expectType(testDoc1.description); + expectType(testDoc1.userName); + expectType(testDoc1.description); const testDoc2 = await AutoTypedModel.insertMany([{ userName: 'M0_0a' }]); - expectType(testDoc2[0].userName); - expectType(testDoc2[0]?.description); + expectType(testDoc2[0].userName); + expectType(testDoc2[0]?.description); const testDoc3 = await AutoTypedModel.findOne({ userName: 'M0_0a' }); - expectType(testDoc3?.userName); - expectType(testDoc3?.description); + expectType(testDoc3?.userName); + expectType(testDoc3?.description); - /* -------------------------------------------------------------------------- */ - /* Model-statics-functions-test */ - /* -------------------------------------------------------------------------- */ - - expectType>(AutoTypedModel.staticFn()); + // Model-statics-functions-test + expectType>(AutoTypedModel.staticFn()); })(); return AutoTypedModel; diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts index e137782d8a2..d755f9b0489 100644 --- a/test/types/queries.test.ts +++ b/test/types/queries.test.ts @@ -2,7 +2,7 @@ import { HydratedDocument, Schema, model, Document, Types, Query, Model, QueryWi import { ObjectId } from 'mongodb'; import { expectError, expectType } from 'tsd'; import { autoTypedModel } from './models.test'; -import { M0_0aAutoTypedSchemaType } from './schema.test'; +import { AutoTypedSchemaType } from './schema.test'; interface QueryHelpers { _byName(this: QueryWithHelpers, name: string): QueryWithHelpers, ITest, QueryHelpers>; diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 517697fc34e..cf85c5c0e5c 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -461,19 +461,19 @@ export function autoTypedSchema() { }, { statics: { staticFn() { - expectType>(this); + expectType>(this); return 'Returned from staticFn' as const; } }, methods: { instanceFn() { - expectType>(this); + expectType>(this); return 'Returned from DocumentInstanceFn' as const; } }, query: { byUserName(userName) { - expectAssignable>(this); + expectAssignable>(this); return this.where({ userName }); } } @@ -481,14 +481,14 @@ export function autoTypedSchema() { type InferredSchemaType = InferSchemaType; - expectType({} as InferredSchemaType); + expectType({} as InferredSchemaType); - expectError({} as InferredSchemaType); + expectError({} as InferredSchemaType); return AutoTypedSchema; } -export type M0_0aAutoTypedSchemaType = { +export type AutoTypedSchemaType = { schema: { userName: string; description?: string; From 96571b125b9dff1499ab2a029b6a6c7fc0cea698 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Wed, 6 Apr 2022 00:57:15 +0200 Subject: [PATCH 25/41] Refactor test in query.test.js file. --- test/query.test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/query.test.js b/test/query.test.js index fc35bf2fb78..db67ef687fc 100644 --- a/test/query.test.js +++ b/test/query.test.js @@ -3964,7 +3964,6 @@ describe('Query', function() { }); it('should return query helper supplied in schema options query property instead of undefined', function(done) { - const Model = db.model('Test', new Schema({ userName: { type: String, @@ -3979,7 +3978,9 @@ describe('Query', function() { })); Model.create({ userName: 'test' }, function(error) { - assert.ifError(error); + if (error instanceof Error) { + return done(error); + } Model.find().byUserName('test').exec(function(error, docs) { if (error instanceof Error) { return done(error); From 897452bcf8864017dfa5eb6cd483de83129e25a7 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Tue, 19 Apr 2022 21:47:57 +0200 Subject: [PATCH 26/41] Resolve Mixed type as any type. --- test/types/schema.test.ts | 14 +++++++------- types/inferschematype.d.ts | 31 +++++++++++++++---------------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index cf85c5c0e5c..39a2167bfbc 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -362,9 +362,9 @@ export function autoTypedSchema() { boolean2?: boolean; boolean3?: boolean; boolean4?: boolean; - mixed1?: Schema.Types.Mixed; - mixed2?: Schema.Types.Mixed; - mixed3?: Schema.Types.Mixed; + mixed1?: any; + mixed2?: any; + mixed3?: any; objectId1?: Schema.Types.ObjectId; objectId2?: Schema.Types.ObjectId; objectId3?: Schema.Types.ObjectId; @@ -372,10 +372,10 @@ export function autoTypedSchema() { map1?: Map; map2?: Map; array1?: string[]; - array2?: Schema.Types.Mixed[]; - array3?: Schema.Types.Mixed[]; - array4?: Schema.Types.Mixed[]; - array5?: Schema.Types.Mixed[]; + array2?: any[]; + array3?: any[]; + array4?: any[]; + array5?: any[]; }; const TestSchema = new Schema({ diff --git a/types/inferschematype.d.ts b/types/inferschematype.d.ts index 27f82bd20bc..01a336136e0 100644 --- a/types/inferschematype.d.ts +++ b/types/inferschematype.d.ts @@ -23,9 +23,7 @@ declare module 'mongoose' { * // result * type UserType = {userName?: string} */ - type InferSchemaType = SchemaType extends Schema - ? IsItRecordAndNotAny extends true ? EnforcedDocType : ObtainSchemaGeneric - : unknown; + type InferSchemaType = ObtainSchemaGeneric ; /** * @summary Obtains schema Generic type by using generic alias. @@ -84,7 +82,7 @@ type PathWithTypePropertyBaseType = { [k in Typ * @returns required paths keys of document definition. */ type RequiredPathKeys = { - [K in keyof T]: T[K] extends RequiredPathBaseType ? K : never; + [K in keyof T]: T[K] extends RequiredPathBaseType ? IfEquals : never; }[keyof T]; /** @@ -140,16 +138,17 @@ type PathEnumOrString['enum']> = T extends ( * @returns Number, "Number" or "number" will be resolved to string type. */ type ResolvePathType = {}> = - PathValueType extends (infer Item)[] ? IfEquals>[] : - PathValueType extends StringConstructor | 'string' | 'String' | typeof Schema.Types.String ? PathEnumOrString : - PathValueType extends NumberConstructor | 'number' | 'Number' | typeof Schema.Types.Number ? number : - PathValueType extends DateConstructor | 'date' | 'Date' | typeof Schema.Types.Date ? Date : - PathValueType extends BufferConstructor | 'buffer' | 'Buffer' | typeof Schema.Types.Buffer ? Buffer : - PathValueType extends BooleanConstructor | 'boolean' | 'Boolean' | typeof Schema.Types.Boolean ? boolean : - PathValueType extends 'objectId' | 'ObjectId' | typeof Schema.Types.ObjectId ? Schema.Types.ObjectId : - PathValueType extends ObjectConstructor | typeof Schema.Types.Mixed ? Schema.Types.Mixed : - PathValueType extends MapConstructor ? Map> : - PathValueType extends ArrayConstructor ? Schema.Types.Mixed[] : - keyof PathValueType extends keyof {} ? Schema.Types.Mixed : + PathValueType extends (infer Item)[] ? IfEquals>[] : + PathValueType extends StringConstructor | 'string' | 'String' | typeof Schema.Types.String ? PathEnumOrString : + PathValueType extends NumberConstructor | 'number' | 'Number' | typeof Schema.Types.Number ? number : + PathValueType extends DateConstructor | 'date' | 'Date' | typeof Schema.Types.Date ? Date : + PathValueType extends BufferConstructor | 'buffer' | 'Buffer' | typeof Schema.Types.Buffer ? Buffer : + PathValueType extends BooleanConstructor | 'boolean' | 'Boolean' | typeof Schema.Types.Boolean ? boolean : + PathValueType extends 'objectId' | 'ObjectId' | typeof Schema.Types.ObjectId ? Schema.Types.ObjectId : + PathValueType extends MapConstructor ? Map> : + PathValueType extends ArrayConstructor ? any[] : + PathValueType extends typeof Schema.Types.Mixed ? any: + IfEquals extends true ? any: + IfEquals extends true ? any: PathValueType extends typeof SchemaType ? PathValueType['prototype'] : - unknown; + unknown; \ No newline at end of file From 503c0d1f7f5572ad2ff267beae347b964fee1e90 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Fri, 22 Apr 2022 21:44:38 +0200 Subject: [PATCH 27/41] Refactor doc.populate FN to make it accept any Model type. --- types/document.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types/document.d.ts b/types/document.d.ts index 8d26ad5ff00..726e074a400 100644 --- a/types/document.d.ts +++ b/types/document.d.ts @@ -193,8 +193,8 @@ declare module 'mongoose' { /** Populates document references. */ populate(path: string | PopulateOptions | (string | PopulateOptions)[]): Promise; populate(path: string | PopulateOptions | (string | PopulateOptions)[], callback: Callback): void; - populate(path: string, select?: string | AnyObject, model?: Model, match?: AnyObject, options?: PopulateOptions): Promise; - populate(path: string, select?: string | AnyObject, model?: Model, match?: AnyObject, options?: PopulateOptions, callback?: Callback): void; + populate(path: string, select?: string | AnyObject, model?: Model, match?: AnyObject, options?: PopulateOptions): Promise; + populate(path: string, select?: string | AnyObject, model?: Model, match?: AnyObject, options?: PopulateOptions, callback?: Callback): void; /** Gets _id(s) used during population of the given `path`. If the path was not populated, returns `undefined`. */ populated(path: string): any; From aa6497da5d399628997db44931c1457214f84687 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Fri, 29 Apr 2022 00:54:23 +0200 Subject: [PATCH 28/41] Refactor Model new FN --- types/models.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/models.d.ts b/types/models.d.ts index e706ee34ad8..c4d103fb11c 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -120,7 +120,7 @@ declare module 'mongoose' { IndexManager & SessionStarter & ObtainSchemaGeneric & { - new(doc?: DocType, fields?: any | null, options?: boolean | AnyObject): HydratedDocument, TMethodsAndOverrides, TVirtuals>; + new(doc?: DocType, fields?: any | null, options?: boolean | AnyObject): HydratedDocument & DocType, TMethodsAndOverrides, TVirtuals>; aggregate(pipeline?: PipelineStage[], options?: mongodb.AggregateOptions, callback?: Callback): Aggregate>; aggregate(pipeline: PipelineStage[], callback?: Callback): Aggregate>; From a3b32ac177d5ffdd4c6a83bb8e1685883bf952db Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Fri, 29 Apr 2022 02:33:12 +0200 Subject: [PATCH 29/41] Refactor model FN to optimize TS server performance. --- types/index.d.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/types/index.d.ts b/types/index.d.ts index e649d8c3ccd..09e0a006f6a 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -62,14 +62,21 @@ declare module 'mongoose' { /* ! ignore */ export type CompileModelOptions = { overwriteModels?: boolean, connection?: Connection }; - export function model( + export function model( name: string, schema?: TSchema, collection?: string, options?: CompileModelOptions - ): U extends Model - ? U - : Model, TQueryHelpers & ObtainSchemaGeneric, ObtainSchemaGeneric, {}, TSchema>; + ): Model, ObtainSchemaGeneric, ObtainSchemaGeneric, {}, TSchema>; + + export function model(name: string, schema?: Schema | Schema, collection?: string, options?: CompileModelOptions): Model; + + export function model( + name: string, + schema?: Schema, + collection?: string, + options?: CompileModelOptions + ): U; /** Returns an array of model names created on this instance of Mongoose. */ export function modelNames(): Array; From 569d4ebc6e74c9ba5c2d6af9fde67caddfdcf8f9 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Fri, 29 Apr 2022 07:33:35 +0200 Subject: [PATCH 30/41] create MergeBOntoA And implement instead of UnpackedIntersection in some types. --- test/types/create.test.ts | 2 +- types/models.d.ts | 24 ++++++++++++------------ types/utility.d.ts | 3 +++ 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/test/types/create.test.ts b/test/types/create.test.ts index 4492d113fca..bdde5043e64 100644 --- a/test/types/create.test.ts +++ b/test/types/create.test.ts @@ -9,7 +9,7 @@ interface ITest extends Document { const Test = model('Test', schema); -Test.create({ _id: '0'.repeat(24), name: 'test' }).then((doc: ITest) => console.log(doc.name)); +Test.create({ _id: new Types.ObjectId('0'.repeat(24)), name: 'test' }).then((doc: ITest) => console.log(doc.name)); Test.create([{ name: 'test' }], { validateBeforeSave: false }).then((docs: ITest[]) => console.log(docs[0].name)); diff --git a/types/models.d.ts b/types/models.d.ts index c4d103fb11c..155c44d074c 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -120,7 +120,7 @@ declare module 'mongoose' { IndexManager & SessionStarter & ObtainSchemaGeneric & { - new(doc?: DocType, fields?: any | null, options?: boolean | AnyObject): HydratedDocument & DocType, TMethodsAndOverrides, TVirtuals>; + new(doc?: DocType, fields?: any | null, options?: boolean | AnyObject): HydratedDocument, TMethodsAndOverrides, TVirtuals>; aggregate(pipeline?: PipelineStage[], options?: mongodb.AggregateOptions, callback?: Callback): Aggregate>; aggregate(pipeline: PipelineStage[], callback?: Callback): Aggregate>; @@ -164,11 +164,11 @@ declare module 'mongoose' { countDocuments(callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; /** Creates a new document or documents */ - create(docs: Array, options?: SaveOptions): Promise, TMethodsAndOverrides, TVirtuals>[]>; - create(docs: Array, callback: Callback, TMethodsAndOverrides, TVirtuals>[]>): void; - create(doc: T | DocContents): Promise, TMethodsAndOverrides, TVirtuals>>; - create(...docs: Array): Promise, TMethodsAndOverrides, TVirtuals>[]>; - create(doc: T | DocContents, callback: Callback, TMethodsAndOverrides, TVirtuals>>): void; + create(docs: Array, options?: SaveOptions): Promise, TMethodsAndOverrides, TVirtuals>[]>; + create(docs: Array, callback: Callback, TMethodsAndOverrides, TVirtuals>[]>): void; + create(doc: DocContents | T): Promise, TMethodsAndOverrides, TVirtuals>>; + create(...docs: Array): Promise, TMethodsAndOverrides, TVirtuals>[]>; + create(doc: T | DocContents, callback: Callback, TMethodsAndOverrides, TVirtuals>>): void; /** * Create the collection for this model. By default, if no indexes are specified, @@ -257,12 +257,12 @@ declare module 'mongoose' { init(callback?: CallbackWithoutResult): Promise>; /** Inserts one or more new documents as a single `insertMany` call to the MongoDB server. */ - insertMany(docs: Array, options: InsertManyOptions & { rawResult: true }): Promise>>; - insertMany(docs: Array, options?: InsertManyOptions): Promise, TMethodsAndOverrides, TVirtuals>>>; - insertMany(doc: DocContents, options: InsertManyOptions & { rawResult: true }): Promise>>; - insertMany(doc: DocContents, options?: InsertManyOptions): Promise, TMethodsAndOverrides, TVirtuals>[]>; - insertMany(doc: DocContents, options?: InsertManyOptions, callback?: Callback, TMethodsAndOverrides, TVirtuals>[] | InsertManyResult>>): void; - insertMany(docs: Array, options?: InsertManyOptions, callback?: Callback, TMethodsAndOverrides, TVirtuals>> | InsertManyResult>>): void; + insertMany(docs: Array, options: InsertManyOptions & { rawResult: true }): Promise>>; + insertMany(docs: Array, options?: InsertManyOptions): Promise, TMethodsAndOverrides, TVirtuals>>>; + insertMany(doc: DocContents, options: InsertManyOptions & { rawResult: true }): Promise>>; + insertMany(doc: DocContents, options?: InsertManyOptions): Promise, TMethodsAndOverrides, TVirtuals>[]>; + insertMany(doc: DocContents, options?: InsertManyOptions, callback?: Callback, TMethodsAndOverrides, TVirtuals>[] | InsertManyResult>>): void; + insertMany(docs: Array, options?: InsertManyOptions, callback?: Callback, TMethodsAndOverrides, TVirtuals>> | InsertManyResult>>): void; /** The name of the model */ modelName: string; diff --git a/types/utility.d.ts b/types/utility.d.ts index efa98ebe580..3154d74a5c9 100644 --- a/types/utility.d.ts +++ b/types/utility.d.ts @@ -10,4 +10,7 @@ declare module 'mongoose' { ? T : Omit & U; + type MergeBOntoA = Omit & B; + + } From 0fe492881ead06a7d95b50f8106bdf1fb909fd4c Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Fri, 29 Apr 2022 19:07:54 +0200 Subject: [PATCH 31/41] Add some details about auto typed schema. --- docs/typescript/schemas.md | 63 +++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/docs/typescript/schemas.md b/docs/typescript/schemas.md index 79299b49834..dbc890ac584 100644 --- a/docs/typescript/schemas.md +++ b/docs/typescript/schemas.md @@ -1,10 +1,14 @@ # Schemas in TypeScript Mongoose [schemas](/docs/guide.html) are how you tell Mongoose what your documents look like. -Mongoose schemas are separate from TypeScript interfaces, so you need to define both a _document interface_ and a _schema_. +Mongoose schemas are separate from TypeScript interfaces, so you need to define both a _document interface_ and a _schema_ until V6.3.1. +Mongoose supports auto typed schemas in V6.3.2 so you don't need to define a typescript interface anymore but you still able to do so. +Mongoose released as well in V6.3.2 new TS utility `InferSchemaType` that helps to get the type of the auto typed schema document if it's needed. + +`Until mongoose V6.3.1:` ```typescript -import { Schema } from 'mongoose'; +import { Schema } from "mongoose"; // Document interface interface User { @@ -17,8 +21,39 @@ interface User { const schema = new Schema({ name: { type: String, required: true }, email: { type: String, required: true }, - avatar: String + avatar: String, +}); +``` + +`In mongoose V6.3.2:` + +```typescript +import { Schema, InferSchemaType } from "mongoose"; + +// Document interface +// No needs to define TS interface any more. +// interface User { +// name: string; +// email: string; +// avatar?: string; +// } + +// Schema +const schema = new Schema({ + name: { type: String, required: true }, + email: { type: String, required: true }, + avatar: String, }); + +type User = InferSchemaType; +// This will become: +// type User = { +// name: string; +// email: string; +// avatar?: string; +// } + + ``` By default, Mongoose does **not** check if your document interface lines up with your schema. @@ -51,7 +86,7 @@ Mongoose wraps `DocType` in a Mongoose document for cases like the `this` parame For example: ```typescript -schema.pre('save', function(): void { +schema.pre("save", function (): void { console.log(this.name); // TypeScript knows that `this` is a `mongoose.Document & User` by default }); ``` @@ -69,7 +104,7 @@ Mongoose checks to make sure that every path in your schema is defined in your d For example, the below code will fail to compile because `emaill` is a path in the schema, but not in the `DocType` interface. ```typescript -import { Schema, Model } from 'mongoose'; +import { Schema, Model } from "mongoose"; interface User { name: string; @@ -82,7 +117,7 @@ interface User { const schema = new Schema({ name: { type: String, required: true }, emaill: { type: String, required: true }, - avatar: String + avatar: String, }); ``` @@ -90,7 +125,7 @@ However, Mongoose does **not** check for paths that exist in the document interf For example, the below code compiles. ```typescript -import { Schema, Model } from 'mongoose'; +import { Schema, Model } from "mongoose"; interface User { name: string; @@ -102,7 +137,7 @@ interface User { const schema = new Schema>({ name: { type: String, required: true }, email: { type: String, required: true }, - avatar: String + avatar: String, }); ``` @@ -113,7 +148,7 @@ This is because Mongoose has numerous features that add paths to your schema tha When you define an array in a document interface, we recommend using Mongoose's `Types.Array` type for primitive arrays or `Types.DocumentArray` for arrays of documents. ```typescript -import { Schema, Model, Types } from 'mongoose'; +import { Schema, Model, Types } from "mongoose"; interface BlogPost { _id: Types.ObjectId; @@ -121,13 +156,13 @@ interface BlogPost { } interface User { - tags: Types.Array, - blogPosts: Types.DocumentArray + tags: Types.Array; + blogPosts: Types.DocumentArray; } const schema = new Schema>({ tags: [String], - blogPosts: [{ title: String }] + blogPosts: [{ title: String }], }); ``` @@ -138,5 +173,5 @@ If you use `Types.DocumentArray` in the above case, you'll be able to `push()` a ```typescript const user = new User({ blogPosts: [] }); -user.blogPosts.push({ title: 'test' }); // Would not work if you did `blogPosts: BlogPost[]` -``` \ No newline at end of file +user.blogPosts.push({ title: "test" }); // Would not work if you did `blogPosts: BlogPost[]` +``` From f4f17d7aa6dbed8275db04137a257edcb0961b4d Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Fri, 29 Apr 2022 21:49:58 +0200 Subject: [PATCH 32/41] Add some details about auto typed statics functions --- docs/typescript/statics-and-methods.md | 21 ++++++++++++ docs/typescript/statics.md | 47 ++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 docs/typescript/statics.md diff --git a/docs/typescript/statics-and-methods.md b/docs/typescript/statics-and-methods.md index 6897ccad225..b377538f006 100644 --- a/docs/typescript/statics-and-methods.md +++ b/docs/typescript/statics-and-methods.md @@ -65,6 +65,27 @@ const User = model('User', schema); const answer: number = User.myStaticMethod(); // 42 ``` +Mongoose does support auto typed static functions now that it is supplied in schema options. +Statics functions can be defined as following: + +```typescript +import { Schema, model } from 'mongoose'; + +const schema = new Schema( + { name: String }, + { + statics: { + myStaticMethod() { + return 42; + }, + }, + } +); + +const User = model('User', schema); + +const answer = User.myStaticMethod(); // 42 +``` ## Both Methods and Statics Below is how you can define a model that has both methods and statics. diff --git a/docs/typescript/statics.md b/docs/typescript/statics.md new file mode 100644 index 00000000000..f9c1f8865fb --- /dev/null +++ b/docs/typescript/statics.md @@ -0,0 +1,47 @@ +# Statics in TypeScript + +Mongoose [models](/docs/models.html) do **not** have an explicit generic parameter for [statics](/docs/guide.html#statics). +If your model has statics, we recommend creating an interface that [extends](https://www.typescriptlang.org/docs/handbook/interfaces.html) Mongoose's `Model` interface as shown below. + +```typescript +import { Model, Schema, model } from "mongoose"; + +interface IUser { + name: string; +} + +interface UserModel extends Model { + myStaticMethod(): number; +} + +const schema = new Schema({ name: String }); +schema.static("myStaticMethod", function myStaticMethod() { + return 42; +}); + +const User = model("User", schema); + +const answer: number = User.myStaticMethod(); // 42 +``` + +Mongoose V6.3.2 does support auto typed static functions that it is supplied in schema options. +Statics functions can be defined as following: + +```typescript +import { Schema, model } from "mongoose"; + +const schema = new Schema( + { name: String }, + { + statics: { + myStaticMethod() { + return 42; + }, + }, + } +); + +const User = model("User", schema); + +const answer = User.myStaticMethod(); // 42 +``` From 6ef2f6c0782175365e390288318c5f4edec1c0e8 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Fri, 29 Apr 2022 22:10:54 +0200 Subject: [PATCH 33/41] Add some details about auto typed query helpers. --- docs/typescript/query-helpers.md | 33 +++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/docs/typescript/query-helpers.md b/docs/typescript/query-helpers.md index a9f99f6b32f..3b8cb9ce335 100644 --- a/docs/typescript/query-helpers.md +++ b/docs/typescript/query-helpers.md @@ -14,7 +14,10 @@ var Project = mongoose.model('Project', ProjectSchema); Project.find().where('stars').gt(1000).byName('mongoose'); ``` -In TypeScript, Mongoose's `Model` takes 3 generic parameters: +In TypeScript, Mongoose does support manually typed & auto typed Query Helpers. + +1- Manually typed: +Mongoose's `Model` takes 3 generic parameters: 1. The `DocType` 2. a `TQueryHelpers` type @@ -58,4 +61,32 @@ async function run(): Promise { // Equivalent to `ProjectModel.find({ stars: { $gt: 1000 }, name: 'mongoose' })` await ProjectModel.find().where('stars').gt(1000).byName('mongoose'); } +``` + +2- Automatically typed: +Mongoose V6.3.2 does support auto typed Query Helpers that it is supplied in schema options. +Query Helpers functions can be defined as following: + +```typescript +import { Schema, model } from 'mongoose'; + + const schema = new Schema({ + name: { type: String, required: true }, + stars: { type: Number, required: true } + }, { + query: { + byName(name) { + return this.find({ name: name }); + } + } + }); + + const ProjectModel = model( + 'Project', + schema + ); + + // Equivalent to `ProjectModel.find({ stars: { $gt: 1000 }, name: 'mongoose' })` + await ProjectModel.find().where('stars').gt(1000).byName('mongoose'); +} ``` \ No newline at end of file From 6a77f0944f2f1fd72b034e3fa535298140694cc0 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Sat, 30 Apr 2022 02:54:29 +0200 Subject: [PATCH 34/41] Add details about auto typed statics, query-helpers and instance methods in guide.md file --- docs/guide.md | 48 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/docs/guide.md b/docs/guide.md index d78226415f5..d5243322f4d 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -143,9 +143,18 @@ We may also define our own custom document instance methods. ```javascript // define a schema -const animalSchema = new Schema({ name: String, type: String }); +const animalSchema = new Schema({ name: String, type: String }, +{ + // Assign a function to the "methods" object of our animalSchema through schema options. + // this approach support auto typed instance methods then no need to create separate TS types. + methods:{ + findSimilarTypes(cb){ + return mongoose.model('Animal').find({ type: this.type }, cb); + } + } +}); -// assign a function to the "methods" object of our animalSchema +// Or, assign a function to the "methods" object of our animalSchema animalSchema.methods.findSimilarTypes = function(cb) { return mongoose.model('Animal').find({ type: this.type }, cb); }; @@ -169,14 +178,28 @@ dog.findSimilarTypes((err, dogs) => {

Statics

-You can also add static functions to your model. There are two equivalent +You can also add static functions to your model. There are three equivalent ways to add a static: +- Add a function property to schema constructor second argument `statics` - Add a function property to `schema.statics` - Call the [`Schema#static()` function](/docs/api.html#schema_Schema-static) ```javascript -// Assign a function to the "statics" object of our animalSchema + +// define a schema +const animalSchema = new Schema({ name: String, type: String }, +{ + // Assign a function to the "statics" object of our animalSchema through schema options. + // this approach support auto typed static methods then no need to create separate TS types. + statics:{ + findByName(name){ + return this.find({ name: new RegExp(name, 'i') }); + } + } +}); + +// Or, Assign a function to the "statics" object of our animalSchema animalSchema.statics.findByName = function(name) { return this.find({ name: new RegExp(name, 'i') }); }; @@ -197,6 +220,20 @@ but for mongoose queries. Query helper methods let you extend mongoose's [chainable query builder API](./queries.html). ```javascript + +// define a schema +const animalSchema = new Schema({ name: String, type: String }, +{ + // Assign a function to the "query" object of our animalSchema through schema options. + // this approach support auto typed query helpers methods then no need to create separate TS types. + statics:{ + byName(name){ + return this.where({ name: new RegExp(name, 'i') }) + } + } +}); + +// Or, Assign a function to the "query" object of our animalSchema animalSchema.query.byName = function(name) { return this.where({ name: new RegExp(name, 'i') }) }; @@ -409,6 +446,7 @@ Valid options: - [read](#read) - [writeConcern](#writeConcern) - [shardKey](#shardKey) +- [statics](#statics) - [strict](#strict) - [strictQuery](#strictQuery) - [toJSON](#toJSON) @@ -423,6 +461,8 @@ Valid options: - [skipVersioning](#skipVersioning) - [timestamps](#timestamps) - [storeSubdocValidationError](#storeSubdocValidationError) +- [methods](#methods) +- [query](#query-helpers)

option: autoIndex

From f21dfbecf77aa10133526d4b312f7385cac1338f Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Sat, 30 Apr 2022 04:12:47 +0200 Subject: [PATCH 35/41] Refactor docs. --- docs/typescript/query-helpers.md | 2 +- docs/typescript/schemas.md | 6 +++--- docs/typescript/statics.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/typescript/query-helpers.md b/docs/typescript/query-helpers.md index 3b8cb9ce335..ef266d95eea 100644 --- a/docs/typescript/query-helpers.md +++ b/docs/typescript/query-helpers.md @@ -64,7 +64,7 @@ async function run(): Promise { ``` 2- Automatically typed: -Mongoose V6.3.2 does support auto typed Query Helpers that it is supplied in schema options. +Mongoose does support auto typed Query Helpers that it is supplied in schema options. Query Helpers functions can be defined as following: ```typescript diff --git a/docs/typescript/schemas.md b/docs/typescript/schemas.md index dbc890ac584..e9d3363b175 100644 --- a/docs/typescript/schemas.md +++ b/docs/typescript/schemas.md @@ -2,8 +2,8 @@ Mongoose [schemas](/docs/guide.html) are how you tell Mongoose what your documents look like. Mongoose schemas are separate from TypeScript interfaces, so you need to define both a _document interface_ and a _schema_ until V6.3.1. -Mongoose supports auto typed schemas in V6.3.2 so you don't need to define a typescript interface anymore but you still able to do so. -Mongoose released as well in V6.3.2 new TS utility `InferSchemaType` that helps to get the type of the auto typed schema document if it's needed. +Mongoose supports auto typed schemas now so you don't need to define a typescript interface anymore but you still able to do so. +Mongoose released new TS utility `InferSchemaType` that helps to get the type of the auto typed schema document if it's needed. `Until mongoose V6.3.1:` @@ -25,7 +25,7 @@ const schema = new Schema({ }); ``` -`In mongoose V6.3.2:` +`another approach:` ```typescript import { Schema, InferSchemaType } from "mongoose"; diff --git a/docs/typescript/statics.md b/docs/typescript/statics.md index f9c1f8865fb..e32c35ad382 100644 --- a/docs/typescript/statics.md +++ b/docs/typescript/statics.md @@ -24,7 +24,7 @@ const User = model("User", schema); const answer: number = User.myStaticMethod(); // 42 ``` -Mongoose V6.3.2 does support auto typed static functions that it is supplied in schema options. +Mongoose does support auto typed static functions that it is supplied in schema options. Statics functions can be defined as following: ```typescript From 7093195af8c0e71f7cb88558b76d3d26eac712d3 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Mon, 2 May 2022 00:03:58 +0200 Subject: [PATCH 36/41] Undo auto docs formatting changes. --- docs/typescript/schemas.md | 20 ++++++++++---------- docs/typescript/statics.md | 10 +++++----- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/typescript/schemas.md b/docs/typescript/schemas.md index e9d3363b175..657778a366e 100644 --- a/docs/typescript/schemas.md +++ b/docs/typescript/schemas.md @@ -8,7 +8,7 @@ Mongoose released new TS utility `InferSchemaType` that helps to get the type of `Until mongoose V6.3.1:` ```typescript -import { Schema } from "mongoose"; +import { Schema } from 'mongoose'; // Document interface interface User { @@ -21,14 +21,14 @@ interface User { const schema = new Schema({ name: { type: String, required: true }, email: { type: String, required: true }, - avatar: String, + avatar: String }); ``` `another approach:` ```typescript -import { Schema, InferSchemaType } from "mongoose"; +import { Schema, InferSchemaType } from 'mongoose'; // Document interface // No needs to define TS interface any more. @@ -42,7 +42,7 @@ import { Schema, InferSchemaType } from "mongoose"; const schema = new Schema({ name: { type: String, required: true }, email: { type: String, required: true }, - avatar: String, + avatar: String }); type User = InferSchemaType; @@ -86,7 +86,7 @@ Mongoose wraps `DocType` in a Mongoose document for cases like the `this` parame For example: ```typescript -schema.pre("save", function (): void { +schema.pre('save', function (): void { console.log(this.name); // TypeScript knows that `this` is a `mongoose.Document & User` by default }); ``` @@ -104,7 +104,7 @@ Mongoose checks to make sure that every path in your schema is defined in your d For example, the below code will fail to compile because `emaill` is a path in the schema, but not in the `DocType` interface. ```typescript -import { Schema, Model } from "mongoose"; +import { Schema, Model } from 'mongoose'; interface User { name: string; @@ -117,7 +117,7 @@ interface User { const schema = new Schema({ name: { type: String, required: true }, emaill: { type: String, required: true }, - avatar: String, + avatar: String }); ``` @@ -125,7 +125,7 @@ However, Mongoose does **not** check for paths that exist in the document interf For example, the below code compiles. ```typescript -import { Schema, Model } from "mongoose"; +import { Schema, Model } from 'mongoose'; interface User { name: string; @@ -148,7 +148,7 @@ This is because Mongoose has numerous features that add paths to your schema tha When you define an array in a document interface, we recommend using Mongoose's `Types.Array` type for primitive arrays or `Types.DocumentArray` for arrays of documents. ```typescript -import { Schema, Model, Types } from "mongoose"; +import { Schema, Model, Types } from 'mongoose'; interface BlogPost { _id: Types.ObjectId; @@ -173,5 +173,5 @@ If you use `Types.DocumentArray` in the above case, you'll be able to `push()` a ```typescript const user = new User({ blogPosts: [] }); -user.blogPosts.push({ title: "test" }); // Would not work if you did `blogPosts: BlogPost[]` +user.blogPosts.push({ title: 'test' }); // Would not work if you did `blogPosts: BlogPost[]` ``` diff --git a/docs/typescript/statics.md b/docs/typescript/statics.md index e32c35ad382..21d8a48a324 100644 --- a/docs/typescript/statics.md +++ b/docs/typescript/statics.md @@ -4,7 +4,7 @@ Mongoose [models](/docs/models.html) do **not** have an explicit generic paramet If your model has statics, we recommend creating an interface that [extends](https://www.typescriptlang.org/docs/handbook/interfaces.html) Mongoose's `Model` interface as shown below. ```typescript -import { Model, Schema, model } from "mongoose"; +import { Model, Schema, model } from 'mongoose'; interface IUser { name: string; @@ -15,11 +15,11 @@ interface UserModel extends Model { } const schema = new Schema({ name: String }); -schema.static("myStaticMethod", function myStaticMethod() { +schema.static('myStaticMethod', function myStaticMethod() { return 42; }); -const User = model("User", schema); +const User = model('User', schema); const answer: number = User.myStaticMethod(); // 42 ``` @@ -28,7 +28,7 @@ Mongoose does support auto typed static functions that it is supplied in schema Statics functions can be defined as following: ```typescript -import { Schema, model } from "mongoose"; +import { Schema, model } from 'mongoose'; const schema = new Schema( { name: String }, @@ -41,7 +41,7 @@ const schema = new Schema( } ); -const User = model("User", schema); +const User = model('User', schema); const answer = User.myStaticMethod(); // 42 ``` From 555db64e77ba4f9c7d9e0b401e118fc61ffcb26b Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Tue, 3 May 2022 07:43:51 +0200 Subject: [PATCH 37/41] Refactor docs & some comments in tests files. --- docs/guide.md | 10 +++++----- docs/typescript/query-helpers.md | 4 ++-- docs/typescript/schemas.md | 8 ++++---- docs/typescript/statics.md | 4 ++-- test/document.test.js | 2 +- test/model.test.js | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/guide.md b/docs/guide.md index d5243322f4d..ed64cc5a521 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -146,7 +146,7 @@ We may also define our own custom document instance methods. const animalSchema = new Schema({ name: String, type: String }, { // Assign a function to the "methods" object of our animalSchema through schema options. - // this approach support auto typed instance methods then no need to create separate TS types. + // By following this approach, there is no need to create a separate TS type to define the type of the instance functions. methods:{ findSimilarTypes(cb){ return mongoose.model('Animal').find({ type: this.type }, cb); @@ -181,7 +181,7 @@ dog.findSimilarTypes((err, dogs) => { You can also add static functions to your model. There are three equivalent ways to add a static: -- Add a function property to schema constructor second argument `statics` +- Add a function property to the second argument of the schema-constructor (`statics`) - Add a function property to `schema.statics` - Call the [`Schema#static()` function](/docs/api.html#schema_Schema-static) @@ -191,7 +191,7 @@ ways to add a static: const animalSchema = new Schema({ name: String, type: String }, { // Assign a function to the "statics" object of our animalSchema through schema options. - // this approach support auto typed static methods then no need to create separate TS types. + // By following this approach, there is no need to create a separate TS type to define the type of the statics functions. statics:{ findByName(name){ return this.find({ name: new RegExp(name, 'i') }); @@ -225,8 +225,8 @@ but for mongoose queries. Query helper methods let you extend mongoose's const animalSchema = new Schema({ name: String, type: String }, { // Assign a function to the "query" object of our animalSchema through schema options. - // this approach support auto typed query helpers methods then no need to create separate TS types. - statics:{ + // By following this approach, there is no need to create a separate TS type to define the type of the query functions. + query:{ byName(name){ return this.where({ name: new RegExp(name, 'i') }) } diff --git a/docs/typescript/query-helpers.md b/docs/typescript/query-helpers.md index ef266d95eea..7c108d465f2 100644 --- a/docs/typescript/query-helpers.md +++ b/docs/typescript/query-helpers.md @@ -14,7 +14,7 @@ var Project = mongoose.model('Project', ProjectSchema); Project.find().where('stars').gt(1000).byName('mongoose'); ``` -In TypeScript, Mongoose does support manually typed & auto typed Query Helpers. +In TypeScript, Mongoose does support manually typed and automatically typed Query Helpers. 1- Manually typed: Mongoose's `Model` takes 3 generic parameters: @@ -64,7 +64,7 @@ async function run(): Promise { ``` 2- Automatically typed: -Mongoose does support auto typed Query Helpers that it is supplied in schema options. +Mongoose does support auto typed Query Helpers that it are supplied in schema options. Query Helpers functions can be defined as following: ```typescript diff --git a/docs/typescript/schemas.md b/docs/typescript/schemas.md index 657778a366e..001b3ab0f03 100644 --- a/docs/typescript/schemas.md +++ b/docs/typescript/schemas.md @@ -2,8 +2,8 @@ Mongoose [schemas](/docs/guide.html) are how you tell Mongoose what your documents look like. Mongoose schemas are separate from TypeScript interfaces, so you need to define both a _document interface_ and a _schema_ until V6.3.1. -Mongoose supports auto typed schemas now so you don't need to define a typescript interface anymore but you still able to do so. -Mongoose released new TS utility `InferSchemaType` that helps to get the type of the auto typed schema document if it's needed. +Mongoose supports auto typed schemas so you don't need to define additional typescript interface anymore but you are still able to do so. +Mongoose provides a `InferSchemaType`, which infers the type of the auto typed schema document when needed. `Until mongoose V6.3.1:` @@ -31,7 +31,7 @@ const schema = new Schema({ import { Schema, InferSchemaType } from 'mongoose'; // Document interface -// No needs to define TS interface any more. +// No need to define TS interface any more. // interface User { // name: string; // email: string; @@ -46,7 +46,7 @@ const schema = new Schema({ }); type User = InferSchemaType; -// This will become: +// InferSchemaType will determine the type as follows: // type User = { // name: string; // email: string; diff --git a/docs/typescript/statics.md b/docs/typescript/statics.md index 21d8a48a324..c8f163c8b37 100644 --- a/docs/typescript/statics.md +++ b/docs/typescript/statics.md @@ -24,8 +24,8 @@ const User = model('User', schema); const answer: number = User.myStaticMethod(); // 42 ``` -Mongoose does support auto typed static functions that it is supplied in schema options. -Statics functions can be defined as following: +Mongoose does support auto typed static functions that it are supplied in schema options. +Static functions can be defined by: ```typescript import { Schema, model } from 'mongoose'; diff --git a/test/document.test.js b/test/document.test.js index 8882e9b2835..e1202951975 100644 --- a/test/document.test.js +++ b/test/document.test.js @@ -11456,7 +11456,7 @@ describe('document', function() { }); }); -describe('Check if instance functions that is supplied in schema option is availabe', function() { +describe('Check if instance function that is supplied in schema option is availabe', function() { it('should give an instance function back rather than undefined', function ModelJS() { const testSchema = new mongoose.Schema({}, { methods: { instanceFn() { return 'Returned from DocumentInstanceFn'; } } }); const TestModel = mongoose.model('TestModel', testSchema); diff --git a/test/model.test.js b/test/model.test.js index ed1579aba47..3872cc0e3d7 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -8505,7 +8505,7 @@ describe('Model', function() { }); }); -describe('Check if statics functions that is supplied in schema option is availabe', function() { +describe('Check if statics function that is supplied in schema option is availabe', function() { it('should give a static function back rather than undefined', function ModelJS() { const testSchema = new mongoose.Schema({}, { statics: { staticFn() { return 'Returned from staticFn'; } } }); const TestModel = mongoose.model('TestModel', testSchema); From 0131b7edd26abe57bd759b848fa88d8ec3805902 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Tue, 17 May 2022 19:58:39 +0200 Subject: [PATCH 38/41] fix typo --- test/model.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/model.test.js b/test/model.test.js index 3872cc0e3d7..cf2f53c60ed 100644 --- a/test/model.test.js +++ b/test/model.test.js @@ -8505,7 +8505,7 @@ describe('Model', function() { }); }); -describe('Check if statics function that is supplied in schema option is availabe', function() { +describe('Check if static function that is supplied in schema option is available', function() { it('should give a static function back rather than undefined', function ModelJS() { const testSchema = new mongoose.Schema({}, { statics: { staticFn() { return 'Returned from staticFn'; } } }); const TestModel = mongoose.model('TestModel', testSchema); From 47469d61adb52f1df10b747c7a250b7d90c41da5 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Thu, 2 Jun 2022 17:54:41 +0200 Subject: [PATCH 39/41] Cover decimal128 --- test/types/schema.test.ts | 8 +++++++- types/inferschematype.d.ts | 15 ++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 39a2167bfbc..a510596d045 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -376,6 +376,9 @@ export function autoTypedSchema() { array3?: any[]; array4?: any[]; array5?: any[]; + decimal1?: Schema.Types.Decimal128; + decimal2?: Schema.Types.Decimal128; + decimal3?: Schema.Types.Decimal128; }; const TestSchema = new Schema({ @@ -412,7 +415,10 @@ export function autoTypedSchema() { array2: Array, array3: [Schema.Types.Mixed], array4: [{}], - array5: [] + array5: [], + decimal1: Schema.Types.Decimal128, + decimal2: 'Decimal128', + decimal3: 'decimal128' }); type InferredTestSchemaType = InferSchemaType; diff --git a/types/inferschematype.d.ts b/types/inferschematype.d.ts index 01a336136e0..cc8c89e5cab 100644 --- a/types/inferschematype.d.ts +++ b/types/inferschematype.d.ts @@ -145,10 +145,11 @@ type ResolvePathType> : - PathValueType extends ArrayConstructor ? any[] : - PathValueType extends typeof Schema.Types.Mixed ? any: - IfEquals extends true ? any: - IfEquals extends true ? any: - PathValueType extends typeof SchemaType ? PathValueType['prototype'] : - unknown; \ No newline at end of file + PathValueType extends 'decimal128' | 'Decimal128' | typeof Schema.Types.Decimal128 ? Schema.Types.Decimal128 : + PathValueType extends MapConstructor ? Map> : + PathValueType extends ArrayConstructor ? any[] : + PathValueType extends typeof Schema.Types.Mixed ? any: + IfEquals extends true ? any: + IfEquals extends true ? any: + PathValueType extends typeof SchemaType ? PathValueType['prototype'] : + unknown; \ No newline at end of file From e0d299ff545caed9dc194c3c5ce5bfdfd1fa1b4a Mon Sep 17 00:00:00 2001 From: uzlopak Date: Thu, 2 Jun 2022 16:42:04 +0200 Subject: [PATCH 40/41] patch autotyping to handle properly population and interfaces --- test/types/create.test.ts | 178 ++++++++++- test/types/models.test.ts | 19 +- test/types/populate.test.ts | 1 + test/types/queries.test.ts | 4 +- test/types/utility.test.ts | 13 + tsconfig.json | 1 + types/index.d.ts | 12 +- types/models.d.ts | 582 +++++++++++++++++++----------------- types/utility.d.ts | 7 +- 9 files changed, 511 insertions(+), 306 deletions(-) create mode 100644 test/types/utility.test.ts diff --git a/test/types/create.test.ts b/test/types/create.test.ts index bdde5043e64..9665c88f837 100644 --- a/test/types/create.test.ts +++ b/test/types/create.test.ts @@ -1,25 +1,187 @@ -import { Schema, model, Document, Types } from 'mongoose'; +import { Schema, model, Types, CallbackError } from 'mongoose'; +import { expectError, expectType } from 'tsd'; const schema: Schema = new Schema({ name: { type: 'String' } }); -interface ITest extends Document { +interface ITest { _id?: Types.ObjectId; name?: string; } const Test = model('Test', schema); -Test.create({ _id: new Types.ObjectId('0'.repeat(24)), name: 'test' }).then((doc: ITest) => console.log(doc.name)); +Test.create({ _id: '000000000000000000000000', name: 'test' }).then(doc => { + expectType(doc._id); + expectType(doc.name); + expectType(doc.isNew); +}); -Test.create([{ name: 'test' }], { validateBeforeSave: false }).then((docs: ITest[]) => console.log(docs[0].name)); +Test.create({ _id: new Types.ObjectId('000000000000000000000000'), name: 'test' }).then((doc) => { + expectType(doc._id); + expectType(doc.name); + expectType(doc.isNew); +}); -Test.create({ name: 'test' }, { name: 'test2' }).then((docs: ITest[]) => console.log(docs[0].name)); +Test.create([{ name: 'test' }], { validateBeforeSave: false }).then(docs => { + expectType(docs[0]._id); + expectType(docs[0].name); + expectType(docs[0].isNew); +}); -Test.insertMany({ name: 'test' }).then((docs: ITest[]) => console.log(docs[0].name)); +Test.create({ name: 'test' }, { name: 'test2' }).then(docs => { + expectType(docs[0]._id); + expectType(docs[0].name); + expectType(docs[1]._id); + expectType(docs[1].name); +}); -Test.create([{ name: 'test' }], { validateBeforeSave: true }).then((docs: ITest[]) => console.log(docs[0].name)); +Test.create([{ name: 'test' }], { validateBeforeSave: true }).then(docs => { + expectType(docs[0]._id); + expectType(docs[0].name); +}); -(async() => { + +Test.insertMany({ name: 'test' }, {}, (err, docs) => { + expectType(err); + expectType(docs[0]._id); + expectType(docs[0].name); + expectType(docs[0].isNew); +}); + +Test.insertMany({ name: 'test' }, { lean: true }, (err, docs) => { + expectType(err); + expectType(docs[0]._id); + expectType(docs[0].name); + expectError(docs[0].isNew); +}); + +Test.insertMany({ name: 'test' }, (err, docs) => { + expectType(err); + expectType(docs[0]._id); + expectType(docs[0].name); + expectType(docs[0].isNew); +}); + +Test.insertMany({ name: 'test' }, {}, (err, docs) => { + expectType(err); + expectType(docs[0]._id); + expectType(docs[0].name); + expectType(docs[0].isNew); +}); + +Test.insertMany([{ name: 'test' }], { rawResult: true }, (err, result) => { + expectType(err); + expectType(result.acknowledged); + expectType(result.insertedCount); + expectType<{[key: number]: Types.ObjectId;}>(result.insertedIds) +}); + +Test.insertMany([{ name: 'test' }], { rawResult: true }, (err, result) => { + expectType(err); + expectType(result.acknowledged); + expectType(result.insertedCount); + expectType<{[key: number]: Types.ObjectId;}>(result.insertedIds) +}); + +Test.insertMany([{ name: 'test' }], { lean: true }, (err, docs) => { + expectType(err); + expectType(docs[0]._id); + expectType(docs[0].name); + expectError(docs[0].isNew); +}); + +Test.insertMany([{ name: 'test' }], (err, docs) => { + expectType(err); + expectType(docs[0]._id); + expectType(docs[0].name); + expectType(docs[0].isNew); +}); + +Test.insertMany({ _id: '000000000000000000000000', name: 'test' }, (err, docs) => { + expectType(docs[0]._id); + expectType(docs[0].name); + expectType(docs[0].isNew); +}); + +Test.insertMany({ _id: new Types.ObjectId('000000000000000000000000')}, (err, docs) => { + expectType(docs[0]._id); + expectType(docs[0].name); + expectType(docs[0].isNew); +}); + +Test.insertMany({ name: 'test' }, {}).then(docs => { + expectType(docs[0]._id); + expectType(docs[0].name); + expectType(docs[0].isNew); +}); + +Test.insertMany({ name: 'test' }, { lean: true }).then(docs => { + expectType(docs[0]._id); + expectType(docs[0].name); + expectError(docs[0].isNew); +}); + +Test.insertMany({ name: 'test' }).then(docs => { + expectType(docs[0]._id); + expectType(docs[0].name); + expectType(docs[0].isNew); +}); + +Test.insertMany({ name: 'test' }, {}).then(docs => { + expectType(docs[0]._id); + expectType(docs[0].name); + expectType(docs[0].isNew); +}); + +Test.insertMany([{ name: 'test' }], { rawResult: true }).then(result => { + expectType(result.acknowledged); + expectType(result.insertedCount); + expectType<{[key: number]: Types.ObjectId;}>(result.insertedIds) +}); + +Test.insertMany([{ name: 'test' }], { rawResult: true }).then(result => { + expectType(result.acknowledged); + expectType(result.insertedCount); + expectType<{[key: number]: Types.ObjectId;}>(result.insertedIds) +}); + +Test.insertMany([{ name: 'test' }], { lean: true }).then(docs => { + expectType(docs[0]._id); + expectType(docs[0].name); + expectError(docs[0].isNew); +}); + +Test.insertMany([{ name: 'test' }], { lean: false }).then(docs => { + expectType(docs[0]._id); + expectType(docs[0].name); + expectType(docs[0].isNew); +}); + +Test.insertMany([{ name: 'test' }], { }).then(docs => { + expectType(docs[0]._id); + expectType(docs[0].name); + expectType(docs[0].isNew); +}); + +Test.insertMany([{ name: 'test' }]).then(docs => { + expectType(docs[0]._id); + expectType(docs[0].name); + expectType(docs[0].isNew); +}); + +Test.insertMany({ _id: '000000000000000000000000', name: 'test' }).then(docs => { + expectType(docs[0]._id); + expectType(docs[0].name); + expectType(docs[0].isNew); +}); + +Test.insertMany({ _id: new Types.ObjectId('000000000000000000000000'), name: 'test' }).then(docs => { + expectType(docs[0]._id); + expectType(docs[0].name); + expectType(docs[0].isNew); +}); + +(async () => { const [t1] = await Test.create([{ name: 'test' }]); const [t2, t3, t4] = await Test.create({ name: 'test' }, { name: 'test' }, { name: 'test' }); (await Test.create([{ name: 'test' }]))[0]; diff --git a/test/types/models.test.ts b/test/types/models.test.ts index 92a838a4a7b..29b1fe4ebb0 100644 --- a/test/types/models.test.ts +++ b/test/types/models.test.ts @@ -1,4 +1,5 @@ -import { Schema, Document, Model, Types, connection, model } from 'mongoose'; +import { ObjectId } from 'bson'; +import { Schema, Document, Model, connection, model, Types } from 'mongoose'; import { expectError, expectType } from 'tsd'; import { AutoTypedSchemaType, autoTypedSchema } from './schema.test'; @@ -41,9 +42,7 @@ function rawDocSyntax(): void { const Test = connection.model('Test', TestSchema); - const bar = (SomeModel: Model) => console.log(SomeModel); - - bar(Test); + expectType>(Test); const doc = new Test({ foo: '42' }); console.log(doc.foo); @@ -84,7 +83,7 @@ async function insertManyTest() { }); const res = await Test.insertMany([{ foo: 'bar' }], { rawResult: true }); - const ids: Types.ObjectId[] = Object.values(res.insertedIds); + expectType(res.insertedIds[0]); } function schemaStaticsWithoutGenerics() { @@ -140,13 +139,17 @@ async function gh10359() { lastName: string; } - async function foo(model: Model): Promise { - const doc: T | null = await model.findOne({ groupId: 'test' }).lean().exec(); + async function foo(model: Model) { + const doc = await model.findOne({ groupId: 'test' }).lean().exec(); + expectType(doc?.firstName); + expectType(doc?.lastName); + expectType(doc?._id); + expectType(doc?.groupId); return doc; } const UserModel = model('gh10359', new Schema({ firstName: String, lastName: String, groupId: String })); - const u: User | null = await foo(UserModel); + foo(UserModel); } const ExpiresSchema = new Schema({ diff --git a/test/types/populate.test.ts b/test/types/populate.test.ts index e122cf7f47c..cdcd4d68969 100644 --- a/test/types/populate.test.ts +++ b/test/types/populate.test.ts @@ -72,6 +72,7 @@ const Story = model('Story', storySchema); await story.populate('author'); await story.populate({ path: 'fans' }); + await story.populate({ path: 'fans', model: Person }); await story.populate(['author']); await story.populate([{ path: 'fans' }]); await story.populate(['author', { path: 'fans' }]); diff --git a/test/types/queries.test.ts b/test/types/queries.test.ts index d755f9b0489..5d5ad58948f 100644 --- a/test/types/queries.test.ts +++ b/test/types/queries.test.ts @@ -163,10 +163,10 @@ function testGenericQuery(): void { function eachAsync(): void { Test.find().cursor().eachAsync((doc) => { - expectType<(ITest & { _id: any; })>(doc); + expectType<(ITest & { _id: Types.ObjectId; })>(doc); }); Test.find().cursor().eachAsync((docs) => { - expectType<(ITest & { _id: any; })[]>(docs); + expectType<(ITest & { _id: Types.ObjectId; })[]>(docs); }, { batchSize: 2 }); } diff --git a/test/types/utility.test.ts b/test/types/utility.test.ts new file mode 100644 index 00000000000..bc07a6c22be --- /dev/null +++ b/test/types/utility.test.ts @@ -0,0 +1,13 @@ +import { MergeType } from 'mongoose'; +import { expectType } from 'tsd'; + +type A = { a: string, c: number}; +type B = { a: number, b: string }; + +expectType({} as MergeType["a"]); +expectType({} as MergeType["b"]); +expectType({} as MergeType["c"]); + +expectType({} as MergeType["a"]); +expectType({} as MergeType["b"]); +expectType({} as MergeType["c"]); diff --git a/tsconfig.json b/tsconfig.json index 98bd39acf40..10f087f4bf2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "strict": true, "strictNullChecks": true, "paths": { "mongoose" : ["./types/index.d.ts"] diff --git a/types/index.d.ts b/types/index.d.ts index 09e0a006f6a..09e99ff7b48 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -67,7 +67,7 @@ declare module 'mongoose' { schema?: TSchema, collection?: string, options?: CompileModelOptions - ): Model, ObtainSchemaGeneric, ObtainSchemaGeneric, {}, TSchema>; + ): Model, ObtainSchemaGeneric, ObtainSchemaGeneric, {}, TSchema> & ObtainSchemaGeneric; export function model(name: string, schema?: Schema | Schema, collection?: string, options?: CompileModelOptions): Model; @@ -101,7 +101,15 @@ declare module 'mongoose' { [k: string]: any } - export type Require_id = T extends { _id?: any } ? (T & { _id: T['_id'] }) : (T & { _id: Types.ObjectId }); + export type Require_id = T extends { _id?: infer U } + ? U extends any + ? (T & { _id: Types.ObjectId }) + : T & Required<{ _id: U }> + : T & { _id: Types.ObjectId }; + + export type RequireOnlyTypedId = T extends { _id?: infer U; } + ? Required<{ _id: U }> + : { _id: Types.ObjectId }; export type HydratedDocument = DocType extends Document ? Require_id : (Document & Require_id & TVirtuals & TMethodsAndOverrides); diff --git a/types/models.d.ts b/types/models.d.ts index 155c44d074c..d15675dbb1b 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -114,303 +114,321 @@ declare module 'mongoose' { } const Model: Model; - type Model = - NodeJS.EventEmitter & - AcceptsDiscriminator & - IndexManager & - SessionStarter & - ObtainSchemaGeneric & { - new(doc?: DocType, fields?: any | null, options?: boolean | AnyObject): HydratedDocument, TMethodsAndOverrides, TVirtuals>; - - aggregate(pipeline?: PipelineStage[], options?: mongodb.AggregateOptions, callback?: Callback): Aggregate>; - aggregate(pipeline: PipelineStage[], callback?: Callback): Aggregate>; - - /** Base Mongoose instance the model uses. */ - base: Mongoose; - - /** - * If this is a discriminator model, `baseModelName` is the name of - * the base model. - */ - baseModelName: string | undefined; - - /** - * Sends multiple `insertOne`, `updateOne`, `updateMany`, `replaceOne`, - * `deleteOne`, and/or `deleteMany` operations to the MongoDB server in one - * command. This is faster than sending multiple independent operations (e.g. - * if you use `create()`) because with `bulkWrite()` there is only one network - * round trip to the MongoDB server. - */ - bulkWrite(writes: Array, options: mongodb.BulkWriteOptions & MongooseBulkWriteOptions, callback: Callback): void; - bulkWrite(writes: Array, callback: Callback): void; - bulkWrite(writes: Array, options?: mongodb.BulkWriteOptions & MongooseBulkWriteOptions): Promise; - - /** - * Sends multiple `save()` calls in a single `bulkWrite()`. This is faster than - * sending multiple `save()` calls because with `bulkSave()` there is only one - * network round trip to the MongoDB server. - */ - bulkSave(documents: Array, options?: mongodb.BulkWriteOptions): Promise; - - /** Collection the model uses. */ - collection: Collection; - - /** Creates a `count` query: counts the number of documents that match `filter`. */ - count(callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; - count(filter: FilterQuery, callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; - - /** Creates a `countDocuments` query: counts the number of documents that match `filter`. */ - countDocuments(filter: FilterQuery, options?: QueryOptions, callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; - countDocuments(callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; - - /** Creates a new document or documents */ - create(docs: Array, options?: SaveOptions): Promise, TMethodsAndOverrides, TVirtuals>[]>; - create(docs: Array, callback: Callback, TMethodsAndOverrides, TVirtuals>[]>): void; - create(doc: DocContents | T): Promise, TMethodsAndOverrides, TVirtuals>>; - create(...docs: Array): Promise, TMethodsAndOverrides, TVirtuals>[]>; - create(doc: T | DocContents, callback: Callback, TMethodsAndOverrides, TVirtuals>>): void; - - /** - * Create the collection for this model. By default, if no indexes are specified, - * mongoose will not create the collection for the model until any documents are - * created. Use this method to create the collection explicitly. - */ - createCollection(options: mongodb.CreateCollectionOptions & Pick | null, callback: Callback>): void; - createCollection(callback: Callback>): void; - createCollection(options?: mongodb.CreateCollectionOptions & Pick): Promise>; - - /** Connection the model uses. */ - db: Connection; - - /** - * Deletes all of the documents that match `conditions` from the collection. - * Behaves like `remove()`, but deletes all documents that match `conditions` - * regardless of the `single` option. - */ - deleteMany(filter?: FilterQuery, options?: QueryOptions, callback?: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; - deleteMany(filter: FilterQuery, callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; - deleteMany(callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; - - /** - * Deletes the first document that matches `conditions` from the collection. - * Behaves like `remove()`, but deletes at most one document regardless of the - * `single` option. - */ - deleteOne(filter?: FilterQuery, options?: QueryOptions, callback?: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; - deleteOne(filter: FilterQuery, callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; - deleteOne(callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; + interface Model extends + NodeJS.EventEmitter, + AcceptsDiscriminator, + IndexManager, + SessionStarter { + new (doc?: DocType, fields?: any | null, options?: boolean | AnyObject): HydratedDocument, TMethodsAndOverrides, TVirtuals> & ObtainSchemaGeneric; + + aggregate(pipeline?: PipelineStage[], options?: mongodb.AggregateOptions, callback?: Callback): Aggregate>; + aggregate(pipeline: PipelineStage[], callback?: Callback): Aggregate>; + + /** Base Mongoose instance the model uses. */ + base: Mongoose; + + /** + * If this is a discriminator model, `baseModelName` is the name of + * the base model. + */ + baseModelName: string | undefined; + + /** + * Sends multiple `insertOne`, `updateOne`, `updateMany`, `replaceOne`, + * `deleteOne`, and/or `deleteMany` operations to the MongoDB server in one + * command. This is faster than sending multiple independent operations (e.g. + * if you use `create()`) because with `bulkWrite()` there is only one network + * round trip to the MongoDB server. + */ + bulkWrite(writes: Array, options: mongodb.BulkWriteOptions & MongooseBulkWriteOptions, callback: Callback): void; + bulkWrite(writes: Array, callback: Callback): void; + bulkWrite(writes: Array, options?: mongodb.BulkWriteOptions & MongooseBulkWriteOptions): Promise; + + /** + * Sends multiple `save()` calls in a single `bulkWrite()`. This is faster than + * sending multiple `save()` calls because with `bulkSave()` there is only one + * network round trip to the MongoDB server. + */ + bulkSave(documents: Array, options?: mongodb.BulkWriteOptions): Promise; + + /** Collection the model uses. */ + collection: Collection; + + /** Creates a `count` query: counts the number of documents that match `filter`. */ + count(callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; + count(filter: FilterQuery, callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; + + /** Creates a `countDocuments` query: counts the number of documents that match `filter`. */ + countDocuments(filter: FilterQuery, options?: QueryOptions, callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; + countDocuments(callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; + + /** Creates a new document or documents */ + create(docs: Array, options?: SaveOptions): Promise, RequireOnlyTypedId>, TMethodsAndOverrides, TVirtuals>[]>; + create(docs: Array, callback: Callback, RequireOnlyTypedId>, TMethodsAndOverrides, TVirtuals>[]>): void; + create(doc: DocContents | T): Promise, RequireOnlyTypedId>, TMethodsAndOverrides, TVirtuals>>; + create(...docs: Array): Promise, RequireOnlyTypedId>, TMethodsAndOverrides, TVirtuals>[]>; + create(doc: T | DocContents, callback: Callback, RequireOnlyTypedId>, TMethodsAndOverrides, TVirtuals>>): void; + + /** + * Create the collection for this model. By default, if no indexes are specified, + * mongoose will not create the collection for the model until any documents are + * created. Use this method to create the collection explicitly. + */ + createCollection(options: mongodb.CreateCollectionOptions & Pick | null, callback: Callback>): void; + createCollection(callback: Callback>): void; + createCollection(options?: mongodb.CreateCollectionOptions & Pick): Promise>; - /** - * Event emitter that reports any errors that occurred. Useful for global error - * handling. - */ - events: NodeJS.EventEmitter; + /** Connection the model uses. */ + db: Connection; - /** - * Finds a single document by its _id field. `findById(id)` is almost* - * equivalent to `findOne({ _id: id })`. If you want to query by a document's - * `_id`, use `findById()` instead of `findOne()`. - */ - findById>( - id: any, - projection?: ProjectionType | null, - options?: QueryOptions | null, - callback?: Callback - ): QueryWithHelpers; - findById>( - id: any, - projection?: ProjectionType | null, - callback?: Callback - ): QueryWithHelpers; - - /** Finds one document. */ - findOne>( - filter?: FilterQuery, - projection?: ProjectionType | null, - options?: QueryOptions | null, - callback?: Callback - ): QueryWithHelpers; - findOne>( - filter?: FilterQuery, - projection?: ProjectionType | null, - callback?: Callback - ): QueryWithHelpers; - findOne>( - filter?: FilterQuery, - callback?: Callback - ): QueryWithHelpers; + /** + * Deletes all of the documents that match `conditions` from the collection. + * Behaves like `remove()`, but deletes all documents that match `conditions` + * regardless of the `single` option. + */ + deleteMany(filter?: FilterQuery, options?: QueryOptions, callback?: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; + deleteMany(filter: FilterQuery, callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; + deleteMany(callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; + + /** + * Deletes the first document that matches `conditions` from the collection. + * Behaves like `remove()`, but deletes at most one document regardless of the + * `single` option. + */ + deleteOne(filter?: FilterQuery, options?: QueryOptions, callback?: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; + deleteOne(filter: FilterQuery, callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; + deleteOne(callback: CallbackWithoutResult): QueryWithHelpers, TQueryHelpers, T>; - /** - * Shortcut for creating a new Document from existing raw data, pre-saved in the DB. - * The document returned has no paths marked as modified initially. - */ - hydrate(obj: any): HydratedDocument; + /** + * Event emitter that reports any errors that occurred. Useful for global error + * handling. + */ + events: NodeJS.EventEmitter; - /** - * This function is responsible for building [indexes](https://docs.mongodb.com/manual/indexes/), - * unless [`autoIndex`](http://mongoosejs.com/docs/guide.html#autoIndex) is turned off. - * Mongoose calls this function automatically when a model is created using - * [`mongoose.model()`](/docs/api.html#mongoose_Mongoose-model) or - * [`connection.model()`](/docs/api.html#connection_Connection-model), so you - * don't need to call it. - */ - init(callback?: CallbackWithoutResult): Promise>; + /** + * Finds a single document by its _id field. `findById(id)` is almost* + * equivalent to `findOne({ _id: id })`. If you want to query by a document's + * `_id`, use `findById()` instead of `findOne()`. + */ + findById>( + id: any, + projection?: ProjectionType | null, + options?: QueryOptions | null, + callback?: Callback + ): QueryWithHelpers; + findById>( + id: any, + projection?: ProjectionType | null, + callback?: Callback + ): QueryWithHelpers; + + /** Finds one document. */ + findOne>( + filter?: FilterQuery, + projection?: ProjectionType | null, + options?: QueryOptions | null, + callback?: Callback + ): QueryWithHelpers; + findOne>( + filter?: FilterQuery, + projection?: ProjectionType | null, + callback?: Callback + ): QueryWithHelpers; + findOne>( + filter?: FilterQuery, + callback?: Callback + ): QueryWithHelpers; + + /** + * Shortcut for creating a new Document from existing raw data, pre-saved in the DB. + * The document returned has no paths marked as modified initially. + */ + hydrate(obj: any): HydratedDocument; + + /** + * This function is responsible for building [indexes](https://docs.mongodb.com/manual/indexes/), + * unless [`autoIndex`](http://mongoosejs.com/docs/guide.html#autoIndex) is turned off. + * Mongoose calls this function automatically when a model is created using + * [`mongoose.model()`](/docs/api.html#mongoose_Mongoose-model) or + * [`connection.model()`](/docs/api.html#connection_Connection-model), so you + * don't need to call it. + */ + init(callback?: CallbackWithoutResult): Promise>; - /** Inserts one or more new documents as a single `insertMany` call to the MongoDB server. */ - insertMany(docs: Array, options: InsertManyOptions & { rawResult: true }): Promise>>; - insertMany(docs: Array, options?: InsertManyOptions): Promise, TMethodsAndOverrides, TVirtuals>>>; - insertMany(doc: DocContents, options: InsertManyOptions & { rawResult: true }): Promise>>; - insertMany(doc: DocContents, options?: InsertManyOptions): Promise, TMethodsAndOverrides, TVirtuals>[]>; - insertMany(doc: DocContents, options?: InsertManyOptions, callback?: Callback, TMethodsAndOverrides, TVirtuals>[] | InsertManyResult>>): void; - insertMany(docs: Array, options?: InsertManyOptions, callback?: Callback, TMethodsAndOverrides, TVirtuals>> | InsertManyResult>>): void; + /** Inserts one or more new documents as a single `insertMany` call to the MongoDB server. */ + insertMany(docs: Array, options: InsertManyOptions & { lean: true; }, callback: Callback, RequireOnlyTypedId>>>): void; + insertMany(docs: Array, options: InsertManyOptions & { rawResult: true; }, callback: Callback>): void; + insertMany(docs: Array, callback: Callback, RequireOnlyTypedId>, TMethodsAndOverrides, TVirtuals>>>): void; + insertMany(doc: DocContents, options: InsertManyOptions & { lean: true; }, callback: Callback, RequireOnlyTypedId>>>): void; + insertMany(doc: DocContents, options: InsertManyOptions & { rawResult: true; }, callback: Callback>): void; + insertMany(doc: DocContents, options: InsertManyOptions & { lean?: false | undefined }, callback: Callback, RequireOnlyTypedId>, TMethodsAndOverrides, TVirtuals>>>): void; + insertMany(doc: DocContents, callback: Callback, RequireOnlyTypedId>, TMethodsAndOverrides, TVirtuals>>>): void; - /** The name of the model */ - modelName: string; + insertMany(docs: Array, options: InsertManyOptions & { lean: true; }): Promise, RequireOnlyTypedId>>>; + insertMany(docs: Array, options: InsertManyOptions & { rawResult: true; }): Promise>; + insertMany(docs: Array): Promise, RequireOnlyTypedId>, TMethodsAndOverrides, TVirtuals>>>; + insertMany(doc: DocContents, options: InsertManyOptions & { lean: true; }): Promise, RequireOnlyTypedId>>>; + insertMany(doc: DocContents, options: InsertManyOptions & { rawResult: true; }): Promise>; + insertMany(doc: DocContents, options: InsertManyOptions): Promise, RequireOnlyTypedId>, TMethodsAndOverrides, TVirtuals>>>; + insertMany(doc: DocContents): Promise, RequireOnlyTypedId>, TMethodsAndOverrides, TVirtuals>>>; - /** Populates document references. */ - populate(docs: Array, options: PopulateOptions | Array | string, - callback?: Callback<(HydratedDocument)[]>): Promise>>; - populate(doc: any, options: PopulateOptions | Array | string, - callback?: Callback>): Promise>; + /** The name of the model */ + modelName: string; + /** Populates document references. */ + populate(docs: Array, options: PopulateOptions | Array | string, + callback?: Callback<(HydratedDocument)[]>): Promise>>; + populate(doc: any, options: PopulateOptions | Array | string, + callback?: Callback>): Promise>; - /** Casts and validates the given object against this model's schema, passing the given `context` to custom validators. */ - validate(callback?: CallbackWithoutResult): Promise; - validate(optional: any, callback?: CallbackWithoutResult): Promise; - validate(optional: any, pathsToValidate: PathsToValidate, callback?: CallbackWithoutResult): Promise; - /** Watches the underlying collection for changes using [MongoDB change streams](https://docs.mongodb.com/manual/changeStreams/). */ - watch(pipeline?: Array>, options?: mongodb.ChangeStreamOptions): mongodb.ChangeStream; + /** Casts and validates the given object against this model's schema, passing the given `context` to custom validators. */ + validate(callback?: CallbackWithoutResult): Promise; + validate(optional: any, callback?: CallbackWithoutResult): Promise; + validate(optional: any, pathsToValidate: PathsToValidate, callback?: CallbackWithoutResult): Promise; - /** Adds a `$where` clause to this query */ - $where(argument: string | Function): QueryWithHelpers>, HydratedDocument, TQueryHelpers, T>; + /** Watches the underlying collection for changes using [MongoDB change streams](https://docs.mongodb.com/manual/changeStreams/). */ + watch(pipeline?: Array>, options?: mongodb.ChangeStreamOptions): mongodb.ChangeStream; - /** Registered discriminators for this model. */ - discriminators: { [name: string]: Model } | undefined; + /** Adds a `$where` clause to this query */ + $where(argument: string | Function): QueryWithHelpers>, HydratedDocument, TQueryHelpers, T>; - /** Translate any aliases fields/conditions so the final query or document object is pure */ - translateAliases(raw: any): any; + /** Registered discriminators for this model. */ + discriminators: { [name: string]: Model } | undefined; - /** Creates a `distinct` query: returns the distinct values of the given `field` that match `filter`. */ - distinct(field: string, filter?: FilterQuery, callback?: Callback): QueryWithHelpers, HydratedDocument, TQueryHelpers, T>; + /** Translate any aliases fields/conditions so the final query or document object is pure */ + translateAliases(raw: any): any; - /** Creates a `estimatedDocumentCount` query: counts the number of documents in the collection. */ - estimatedDocumentCount(options?: QueryOptions, callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; + /** Creates a `distinct` query: returns the distinct values of the given `field` that match `filter`. */ + distinct(field: string, filter?: FilterQuery, callback?: Callback): QueryWithHelpers, HydratedDocument, TQueryHelpers, T>; - /** - * Returns a document with its `_id` if at least one document exists in the database that matches - * the given `filter`, and `null` otherwise. - */ - exists(filter: FilterQuery, callback: Callback, '_id'> | null>): QueryWithHelpers, '_id'> | null, HydratedDocument, TQueryHelpers, T>; - exists(filter: FilterQuery): QueryWithHelpers, '_id'> | null, HydratedDocument, TQueryHelpers, T>; - - /** Creates a `find` query: gets a list of documents that match `filter`. */ - find>(callback?: Callback): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; - find>( - filter: FilterQuery, - projection?: ProjectionType | null, - callback?: Callback - ): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; - find>(filter: FilterQuery, callback?: Callback): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; - find>(filter: FilterQuery, projection?: ProjectionType | null, options?: QueryOptions | null, callback?: Callback): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; - - /** Creates a `findByIdAndDelete` query, filtering by the given `_id`. */ - findByIdAndDelete>(id?: mongodb.ObjectId | any, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; - - /** Creates a `findByIdAndRemove` query, filtering by the given `_id`. */ - findByIdAndRemove>(id?: mongodb.ObjectId | any, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; - - /** Creates a `findOneAndUpdate` query, filtering by the given `_id`. */ - findByIdAndUpdate>(id: mongodb.ObjectId | any, update: UpdateQuery, options: QueryOptions & { rawResult: true }, callback?: (err: CallbackError, doc: any, res: any) => void): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; - findByIdAndUpdate>(id: mongodb.ObjectId | any, update: UpdateQuery, options: QueryOptions & { upsert: true } & ReturnsNewDoc, callback?: (err: CallbackError, doc: ResultDoc, res: any) => void): QueryWithHelpers; - findByIdAndUpdate>(id?: mongodb.ObjectId | any, update?: UpdateQuery, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; - findByIdAndUpdate>(id: mongodb.ObjectId | any, update: UpdateQuery, callback: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; - - /** Creates a `findOneAndDelete` query: atomically finds the given document, deletes it, and returns the document as it was before deletion. */ - findOneAndDelete>(filter?: FilterQuery, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; - - /** Creates a `findOneAndRemove` query: atomically finds the given document and deletes it. */ - findOneAndRemove>(filter?: FilterQuery, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; - - /** Creates a `findOneAndReplace` query: atomically finds the given document and replaces it with `replacement`. */ - findOneAndReplace>(filter: FilterQuery, replacement: T | AnyObject, options: QueryOptions & { upsert: true } & ReturnsNewDoc, callback?: (err: CallbackError, doc: ResultDoc, res: any) => void): QueryWithHelpers; - findOneAndReplace>(filter?: FilterQuery, replacement?: T | AnyObject, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; - - /** Creates a `findOneAndUpdate` query: atomically find the first document that matches `filter` and apply `update`. */ - findOneAndUpdate>( - filter: FilterQuery, - update: UpdateQuery, - options: QueryOptions & { rawResult: true }, - callback?: (err: CallbackError, doc: any, res: any) => void - ): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; - findOneAndUpdate>( - filter: FilterQuery, - update: UpdateQuery, - options: QueryOptions & { upsert: true } & ReturnsNewDoc, - callback?: (err: CallbackError, doc: ResultDoc, res: any) => void - ): QueryWithHelpers; - findOneAndUpdate>( - filter?: FilterQuery, - update?: UpdateQuery, - options?: QueryOptions | null, - callback?: (err: CallbackError, doc: T | null, res: any) => void - ): QueryWithHelpers; - - geoSearch>( - filter?: FilterQuery, - options?: GeoSearchOptions, - callback?: Callback> - ): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; - - /** Executes a mapReduce command. */ - mapReduce( - o: MapReduceOptions, - callback?: Callback - ): Promise; - - remove>(filter?: any, callback?: CallbackWithoutResult): QueryWithHelpers; - - /** Creates a `replaceOne` query: finds the first document that matches `filter` and replaces it with `replacement`. */ - replaceOne>( - filter?: FilterQuery, - replacement?: T | AnyObject, - options?: QueryOptions | null, - callback?: Callback - ): QueryWithHelpers; - - /** Schema the model uses. */ - schema: Schema; + /** Creates a `estimatedDocumentCount` query: counts the number of documents in the collection. */ + estimatedDocumentCount(options?: QueryOptions, callback?: Callback): QueryWithHelpers, TQueryHelpers, T>; - /** - * @deprecated use `updateOne` or `updateMany` instead. - * Creates a `update` query: updates one or many documents that match `filter` with `update`, based on the `multi` option. - */ - update>( - filter?: FilterQuery, - update?: UpdateQuery | UpdateWithAggregationPipeline, - options?: QueryOptions | null, - callback?: Callback - ): QueryWithHelpers; - - /** Creates a `updateMany` query: updates all documents that match `filter` with `update`. */ - updateMany>( - filter?: FilterQuery, - update?: UpdateQuery | UpdateWithAggregationPipeline, - options?: QueryOptions | null, - callback?: Callback - ): QueryWithHelpers; - - /** Creates a `updateOne` query: updates the first document that matches `filter` with `update`. */ - updateOne>( - filter?: FilterQuery, - update?: UpdateQuery | UpdateWithAggregationPipeline, - options?: QueryOptions | null, - callback?: Callback - ): QueryWithHelpers; - - /** Creates a Query, applies the passed conditions, and returns the Query. */ - where>(path: string, val?: any): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; - where>(obj: object): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; - where>(): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; - }; + /** + * Returns a document with its `_id` if at least one document exists in the database that matches + * the given `filter`, and `null` otherwise. + */ + exists(filter: FilterQuery, callback: Callback, '_id'> | null>): QueryWithHelpers, '_id'> | null, HydratedDocument, TQueryHelpers, T>; + exists(filter: FilterQuery): QueryWithHelpers, '_id'> | null, HydratedDocument, TQueryHelpers, T>; + + /** Creates a `find` query: gets a list of documents that match `filter`. */ + find>( + filter: FilterQuery, + projection: ProjectionType | null, + options: QueryOptions | null, + callback?: Callback + ): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; + find>( + filter: FilterQuery, + projection: ProjectionType | null, + callback?: Callback + ): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; + find>( + filter: FilterQuery, + callback?: Callback + ): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; + find>( + callback?: Callback + ): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; + + /** Creates a `findByIdAndDelete` query, filtering by the given `_id`. */ + findByIdAndDelete>(id?: mongodb.ObjectId | any, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; + + /** Creates a `findByIdAndRemove` query, filtering by the given `_id`. */ + findByIdAndRemove>(id?: mongodb.ObjectId | any, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; + + /** Creates a `findOneAndUpdate` query, filtering by the given `_id`. */ + findByIdAndUpdate>(id: mongodb.ObjectId | any, update: UpdateQuery, options: QueryOptions & { rawResult: true }, callback?: (err: CallbackError, doc: any, res: any) => void): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; + findByIdAndUpdate>(id: mongodb.ObjectId | any, update: UpdateQuery, options: QueryOptions & { upsert: true } & ReturnsNewDoc, callback?: (err: CallbackError, doc: ResultDoc, res: any) => void): QueryWithHelpers; + findByIdAndUpdate>(id?: mongodb.ObjectId | any, update?: UpdateQuery, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; + findByIdAndUpdate>(id: mongodb.ObjectId | any, update: UpdateQuery, callback: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; + + /** Creates a `findOneAndDelete` query: atomically finds the given document, deletes it, and returns the document as it was before deletion. */ + findOneAndDelete>(filter?: FilterQuery, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; + + /** Creates a `findOneAndRemove` query: atomically finds the given document and deletes it. */ + findOneAndRemove>(filter?: FilterQuery, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; + + /** Creates a `findOneAndReplace` query: atomically finds the given document and replaces it with `replacement`. */ + findOneAndReplace>(filter: FilterQuery, replacement: T | AnyObject, options: QueryOptions & { upsert: true } & ReturnsNewDoc, callback?: (err: CallbackError, doc: ResultDoc, res: any) => void): QueryWithHelpers; + findOneAndReplace>(filter?: FilterQuery, replacement?: T | AnyObject, options?: QueryOptions | null, callback?: (err: CallbackError, doc: ResultDoc | null, res: any) => void): QueryWithHelpers; + + /** Creates a `findOneAndUpdate` query: atomically find the first document that matches `filter` and apply `update`. */ + findOneAndUpdate>( + filter: FilterQuery, + update: UpdateQuery, + options: QueryOptions & { rawResult: true }, + callback?: (err: CallbackError, doc: any, res: any) => void + ): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; + findOneAndUpdate>( + filter: FilterQuery, + update: UpdateQuery, + options: QueryOptions & { upsert: true } & ReturnsNewDoc, + callback?: (err: CallbackError, doc: ResultDoc, res: any) => void + ): QueryWithHelpers; + findOneAndUpdate>( + filter?: FilterQuery, + update?: UpdateQuery, + options?: QueryOptions | null, + callback?: (err: CallbackError, doc: T | null, res: any) => void + ): QueryWithHelpers; + + geoSearch>( + filter?: FilterQuery, + options?: GeoSearchOptions, + callback?: Callback> + ): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; + + /** Executes a mapReduce command. */ + mapReduce( + o: MapReduceOptions, + callback?: Callback + ): Promise; + + remove>(filter?: any, callback?: CallbackWithoutResult): QueryWithHelpers; + + /** Creates a `replaceOne` query: finds the first document that matches `filter` and replaces it with `replacement`. */ + replaceOne>( + filter?: FilterQuery, + replacement?: T | AnyObject, + options?: QueryOptions | null, + callback?: Callback + ): QueryWithHelpers; + + /** Schema the model uses. */ + schema: Schema; + + /** + * @deprecated use `updateOne` or `updateMany` instead. + * Creates a `update` query: updates one or many documents that match `filter` with `update`, based on the `multi` option. + */ + update>( + filter?: FilterQuery, + update?: UpdateQuery | UpdateWithAggregationPipeline, + options?: QueryOptions | null, + callback?: Callback + ): QueryWithHelpers; + + /** Creates a `updateMany` query: updates all documents that match `filter` with `update`. */ + updateMany>( + filter?: FilterQuery, + update?: UpdateQuery | UpdateWithAggregationPipeline, + options?: QueryOptions | null, + callback?: Callback + ): QueryWithHelpers; + + /** Creates a `updateOne` query: updates the first document that matches `filter` with `update`. */ + updateOne>( + filter?: FilterQuery, + update?: UpdateQuery | UpdateWithAggregationPipeline, + options?: QueryOptions | null, + callback?: Callback + ): QueryWithHelpers; + + /** Creates a Query, applies the passed conditions, and returns the Query. */ + where>(path: string, val?: any): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; + where>(obj: object): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; + where>(): QueryWithHelpers, ResultDoc, TQueryHelpers, T>; + } } \ No newline at end of file diff --git a/types/utility.d.ts b/types/utility.d.ts index 3154d74a5c9..1a08fa44ec6 100644 --- a/types/utility.d.ts +++ b/types/utility.d.ts @@ -7,10 +7,9 @@ declare module 'mongoose' { type UnpackedIntersection = T extends null ? null : T extends (infer A)[] ? (Omit & U)[] : keyof U extends never - ? T - : Omit & U; - - type MergeBOntoA = Omit & B; + ? T + : Omit & U; + type MergeType, B extends Record> = Omit & B; } From e137a6d03b3f7d781e4b73a284a9d5ae26e1d35b Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Fri, 3 Jun 2022 16:43:36 +0200 Subject: [PATCH 41/41] Fix linting --- test/types/create.test.ts | 12 ++++++------ test/types/utility.test.ts | 14 +++++++------- types/index.d.ts | 12 ++++++------ types/utility.d.ts | 4 ++-- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/test/types/create.test.ts b/test/types/create.test.ts index 9665c88f837..debca167aeb 100644 --- a/test/types/create.test.ts +++ b/test/types/create.test.ts @@ -73,14 +73,14 @@ Test.insertMany([{ name: 'test' }], { rawResult: true }, (err, result) => { expectType(err); expectType(result.acknowledged); expectType(result.insertedCount); - expectType<{[key: number]: Types.ObjectId;}>(result.insertedIds) + expectType<{ [key: number]: Types.ObjectId; }>(result.insertedIds); }); Test.insertMany([{ name: 'test' }], { rawResult: true }, (err, result) => { expectType(err); expectType(result.acknowledged); expectType(result.insertedCount); - expectType<{[key: number]: Types.ObjectId;}>(result.insertedIds) + expectType<{ [key: number]: Types.ObjectId; }>(result.insertedIds); }); Test.insertMany([{ name: 'test' }], { lean: true }, (err, docs) => { @@ -103,7 +103,7 @@ Test.insertMany({ _id: '000000000000000000000000', name: 'test' }, (err, docs) = expectType(docs[0].isNew); }); -Test.insertMany({ _id: new Types.ObjectId('000000000000000000000000')}, (err, docs) => { +Test.insertMany({ _id: new Types.ObjectId('000000000000000000000000') }, (err, docs) => { expectType(docs[0]._id); expectType(docs[0].name); expectType(docs[0].isNew); @@ -136,13 +136,13 @@ Test.insertMany({ name: 'test' }, {}).then(docs => { Test.insertMany([{ name: 'test' }], { rawResult: true }).then(result => { expectType(result.acknowledged); expectType(result.insertedCount); - expectType<{[key: number]: Types.ObjectId;}>(result.insertedIds) + expectType<{ [key: number]: Types.ObjectId; }>(result.insertedIds); }); Test.insertMany([{ name: 'test' }], { rawResult: true }).then(result => { expectType(result.acknowledged); expectType(result.insertedCount); - expectType<{[key: number]: Types.ObjectId;}>(result.insertedIds) + expectType<{ [key: number]: Types.ObjectId; }>(result.insertedIds); }); Test.insertMany([{ name: 'test' }], { lean: true }).then(docs => { @@ -181,7 +181,7 @@ Test.insertMany({ _id: new Types.ObjectId('000000000000000000000000'), name: 'te expectType(docs[0].isNew); }); -(async () => { +(async() => { const [t1] = await Test.create([{ name: 'test' }]); const [t2, t3, t4] = await Test.create({ name: 'test' }, { name: 'test' }, { name: 'test' }); (await Test.create([{ name: 'test' }]))[0]; diff --git a/test/types/utility.test.ts b/test/types/utility.test.ts index bc07a6c22be..03dc3d7e9af 100644 --- a/test/types/utility.test.ts +++ b/test/types/utility.test.ts @@ -1,13 +1,13 @@ import { MergeType } from 'mongoose'; import { expectType } from 'tsd'; -type A = { a: string, c: number}; +type A = { a: string, c: number }; type B = { a: number, b: string }; -expectType({} as MergeType["a"]); -expectType({} as MergeType["b"]); -expectType({} as MergeType["c"]); +expectType({} as MergeType['a']); +expectType({} as MergeType['b']); +expectType({} as MergeType['c']); -expectType({} as MergeType["a"]); -expectType({} as MergeType["b"]); -expectType({} as MergeType["c"]); +expectType({} as MergeType['a']); +expectType({} as MergeType['b']); +expectType({} as MergeType['c']); diff --git a/types/index.d.ts b/types/index.d.ts index 09e99ff7b48..46e4cbc22f5 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -101,15 +101,15 @@ declare module 'mongoose' { [k: string]: any } - export type Require_id = T extends { _id?: infer U } + export type Require_id = T extends { _id?: infer U } ? U extends any ? (T & { _id: Types.ObjectId }) - : T & Required<{ _id: U }> + : T & Required<{ _id: U }> : T & { _id: Types.ObjectId }; - - export type RequireOnlyTypedId = T extends { _id?: infer U; } - ? Required<{ _id: U }> - : { _id: Types.ObjectId }; + + export type RequireOnlyTypedId = T extends { _id?: infer U; } + ? Required<{ _id: U }> + : { _id: Types.ObjectId }; export type HydratedDocument = DocType extends Document ? Require_id : (Document & Require_id & TVirtuals & TMethodsAndOverrides); diff --git a/types/utility.d.ts b/types/utility.d.ts index 1a08fa44ec6..997dcef69bf 100644 --- a/types/utility.d.ts +++ b/types/utility.d.ts @@ -7,8 +7,8 @@ declare module 'mongoose' { type UnpackedIntersection = T extends null ? null : T extends (infer A)[] ? (Omit & U)[] : keyof U extends never - ? T - : Omit & U; + ? T + : Omit & U; type MergeType, B extends Record> = Omit & B;