From 3c85c6ec44f2f1d74e24dd8e76d310d3f5919624 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Tue, 7 Jun 2022 17:16:21 +0200 Subject: [PATCH 01/15] Add virtuals property to schema options --- test/types/schema.test.ts | 16 ++++++++++++++++ types/index.d.ts | 2 +- types/schemaoptions.d.ts | 12 ++++++++++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 6843e12cfaa..06bb6f87723 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -465,6 +465,10 @@ export function autoTypedSchema() { type: String, required: [true, 'userName is required'] }, + email: { + type: String, + required: [true, 'email is required'] + }, description: String, nested: new Schema({ age: { @@ -517,6 +521,17 @@ export function autoTypedSchema() { expectAssignable>(this); return this.where({ userName }); } + }, + virtuals: { + domain: { + get() { + expectType & AutoTypedSchemaType['schema']>(this); + return this.email.slice(this.email.indexOf('@') + 1); + }, + set() { + expectType & AutoTypedSchemaType['schema']>(this); + } + } } }); @@ -532,6 +547,7 @@ export function autoTypedSchema() { export type AutoTypedSchemaType = { schema: { userName: string; + email: string; description?: string; nested?: { age: number; diff --git a/types/index.d.ts b/types/index.d.ts index 3cd944d0d8f..81a46d61875 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -164,7 +164,7 @@ declare module 'mongoose' { /** * Create a new schema */ - constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions, TInstanceMethods, TQueryHelpers, TStaticMethods>); + constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions, TInstanceMethods, TQueryHelpers, TVirtuals, TStaticMethods>); /** 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 02791ab4171..ec25f36a9b3 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 @@ -199,7 +199,7 @@ declare module 'mongoose' { methods?: Record, ...args: any) => unknown> | InstanceMethods, /** - * Query helper functions + * Query helper functions. */ query?: Record>(this: T, ...args: any) => T> | QueryHelpers, @@ -208,5 +208,13 @@ declare module 'mongoose' { * @default true */ castNonArrays?: boolean; + + /** + * Virtual paths. + */ + virtuals?: Record & DocType) => unknown; + set?: (this: Document & DocType, ...args: any) => unknown; + }> | TVirtuals, } } From de8246818023a9840fb9aac2389cd1933861b10b Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Tue, 7 Jun 2022 17:42:40 +0200 Subject: [PATCH 02/15] Move general utilities to utility file. --- types/inferschematype.d.ts | 21 ++------------------- types/utility.d.ts | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/types/inferschematype.d.ts b/types/inferschematype.d.ts index b32a97b9ef7..7b08cdffdfa 100644 --- a/types/inferschematype.d.ts +++ b/types/inferschematype.d.ts @@ -11,7 +11,8 @@ import { DateSchemaDefinition, ObtainDocumentType, DefaultTypeKey, - ObjectIdSchemaDefinition + ObjectIdSchemaDefinition, + IfEquals } from 'mongoose'; declare module 'mongoose' { @@ -58,24 +59,6 @@ declare module 'mongoose' { }[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 = IfEquals ? true : 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 Checks if a document path is required or optional. diff --git a/types/utility.d.ts b/types/utility.d.ts index 79bb6ad8361..febdc14fcbc 100644 --- a/types/utility.d.ts +++ b/types/utility.d.ts @@ -19,4 +19,23 @@ declare module 'mongoose' { */ type FlatRecord = { [K in keyof T]: T[K] }; + /** + * @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 = IfEquals ? true : 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; + } From f207f729b09c59a3a964cabcc6036ebeead05d8e Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Tue, 7 Jun 2022 18:03:06 +0200 Subject: [PATCH 03/15] Support enforced virtuals type. --- types/index.d.ts | 1 + types/schemaoptions.d.ts | 5 +---- types/virtuals.d.ts | 12 ++++++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 types/virtuals.d.ts diff --git a/types/index.d.ts b/types/index.d.ts index 81a46d61875..6932dfe067e 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -21,6 +21,7 @@ /// /// /// +/// declare class NativeDate extends global.Date { } diff --git a/types/schemaoptions.d.ts b/types/schemaoptions.d.ts index ec25f36a9b3..52877b1a206 100644 --- a/types/schemaoptions.d.ts +++ b/types/schemaoptions.d.ts @@ -212,9 +212,6 @@ declare module 'mongoose' { /** * Virtual paths. */ - virtuals?: Record & DocType) => unknown; - set?: (this: Document & DocType, ...args: any) => unknown; - }> | TVirtuals, + virtuals?: VirtualsSchemaOptionsPropertyType, } } diff --git a/types/virtuals.d.ts b/types/virtuals.d.ts new file mode 100644 index 00000000000..1aab33bd6da --- /dev/null +++ b/types/virtuals.d.ts @@ -0,0 +1,12 @@ +/// + +declare module 'mongoose' { + type VirtualPathFunctions = { + get?: (this: Document & DocType) => pathType; + set?: (this: Document & DocType, ...args: any) => unknown; + }; + + type VirtualsSchemaOptionsPropertyType> = { + [K in keyof virtualPaths]: VirtualPathFunctions extends true ? DocType : any, virtualPaths[K]> + }; +} \ No newline at end of file From 7df135b1f2404f16f03e9367ef56d7fd456113aa Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Tue, 7 Jun 2022 18:48:25 +0200 Subject: [PATCH 04/15] Make virtual paths visible as a doc property. --- test/types/schema.test.ts | 3 +++ test/types/virtuals.test.ts | 7 +++++++ types/index.d.ts | 2 +- types/models.d.ts | 3 ++- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 06bb6f87723..dc1714b1ae4 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -567,6 +567,9 @@ export type AutoTypedSchemaType = { methods: { instanceFn: () => 'Returned from DocumentInstanceFn' }, + virtuals: { + domain: string + } }; // discriminator diff --git a/test/types/virtuals.test.ts b/test/types/virtuals.test.ts index 2011634bd5f..7507768f2f3 100644 --- a/test/types/virtuals.test.ts +++ b/test/types/virtuals.test.ts @@ -1,5 +1,6 @@ import { Document, Model, Schema, model } from 'mongoose'; import { expectType } from 'tsd'; +import { autoTypedModel } from './models.test'; interface IPerson { _id: number; @@ -86,3 +87,9 @@ function gh11543() { expectType(personSchema.virtuals); } + +function autoTypedVirtuals() { + const AutoTypedModel = autoTypedModel(); + const doc = new AutoTypedModel(); + expectType(doc.domain); +} \ No newline at end of file diff --git a/types/index.d.ts b/types/index.d.ts index 6932dfe067e..806563566b5 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -157,7 +157,7 @@ declare module 'mongoose' { type QueryResultType = T extends Query ? ResultType : never; - export class Schema, TInstanceMethods = {}, TQueryHelpers = {}, TVirtuals = any, + export class Schema, TInstanceMethods = {}, TQueryHelpers = {}, TVirtuals = {}, TStaticMethods = {}, TPathTypeKey extends TypeKeyBaseType = DefaultTypeKey, DocType extends ObtainDocumentType = ObtainDocumentType> diff --git a/types/models.d.ts b/types/models.d.ts index 9fae6afa66e..17385597a17 100644 --- a/types/models.d.ts +++ b/types/models.d.ts @@ -119,7 +119,8 @@ declare module 'mongoose' { AcceptsDiscriminator, IndexManager, SessionStarter { - new (doc?: DocType, fields?: any | null, options?: boolean | AnyObject): HydratedDocument & ObtainSchemaGeneric; + new (doc?: DocType, fields?: any | null, options?: boolean | AnyObject): HydratedDocument, TVirtuals>> & ObtainSchemaGeneric; aggregate(pipeline?: PipelineStage[], options?: mongodb.AggregateOptions, callback?: Callback): Aggregate>; aggregate(pipeline: PipelineStage[], callback?: Callback): Aggregate>; From b0f49122aba83f32013959cdc906b90feb81ce68 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Tue, 7 Jun 2022 22:37:40 +0200 Subject: [PATCH 05/15] Add options property to virtual path to be able to apply passed options while creating virtual path. --- test/types/schema.test.ts | 3 ++- types/schemaoptions.d.ts | 8 ++++---- types/virtuals.d.ts | 9 +++++---- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index dc1714b1ae4..8d78753d099 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -530,7 +530,8 @@ export function autoTypedSchema() { }, set() { expectType & AutoTypedSchemaType['schema']>(this); - } + }, + options: {} } } }); diff --git a/types/schemaoptions.d.ts b/types/schemaoptions.d.ts index 52877b1a206..d8de1490006 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, ...args: any) => unknown> | StaticMethods, + statics?: Record, ...args: any) => unknown> | TStaticMethods, /** * Document instance methods. */ - methods?: Record, ...args: any) => unknown> | InstanceMethods, + methods?: Record, ...args: any) => unknown> | TInstanceMethods, /** * Query helper functions. @@ -212,6 +212,6 @@ declare module 'mongoose' { /** * Virtual paths. */ - virtuals?: VirtualsSchemaOptionsPropertyType, + virtuals?: VirtualsSchemaOptionsPropertyType, } } diff --git a/types/virtuals.d.ts b/types/virtuals.d.ts index 1aab33bd6da..160581e0bb8 100644 --- a/types/virtuals.d.ts +++ b/types/virtuals.d.ts @@ -1,12 +1,13 @@ /// declare module 'mongoose' { - type VirtualPathFunctions = { - get?: (this: Document & DocType) => pathType; + type VirtualPathFunctions = { + get?: (this: Document & DocType) => PathType; set?: (this: Document & DocType, ...args: any) => unknown; + options?: VirtualTypeOptions, DocType>; }; - type VirtualsSchemaOptionsPropertyType> = { - [K in keyof virtualPaths]: VirtualPathFunctions extends true ? DocType : any, virtualPaths[K]> + type VirtualsSchemaOptionsPropertyType, TInstanceMethods = {}> = { + [K in keyof virtualPaths]: VirtualPathFunctions extends true ? DocType : any, virtualPaths[K], TInstanceMethods> }; } \ No newline at end of file From 95260aa1c9abdf3cb7efd3262b45efb74386f183 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Wed, 8 Jun 2022 01:00:22 +0200 Subject: [PATCH 06/15] Activate this feature in JS. --- lib/schema.js | 18 ++++++++++++++++++ test/schema.test.js | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/lib/schema.js b/lib/schema.js index 781ccdbbcf8..9396c348ff2 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -133,6 +133,24 @@ function Schema(obj, options) { this.add(obj); } + // build virtual paths + if (options && options.virtuals) { + const virtuals = options.virtuals; + const pathNames = Object.keys(virtuals); + for (const pathName of pathNames) { + const pathOptions = virtuals[pathName].options ? virtuals[pathName].options : undefined; + const virtual = this.virtual(pathName, pathOptions); + + if (virtuals[pathName].get) { + virtual.get(virtuals[pathName].get); + } + + if (virtuals[pathName].set) { + virtual.set(virtuals[pathName].set); + } + } + } + // check if _id's value is a subdocument (gh-2276) const _idSubDoc = obj && obj._id && utils.isObject(obj._id); diff --git a/test/schema.test.js b/test/schema.test.js index 7bc6299e23e..51ed0e9ef23 100644 --- a/test/schema.test.js +++ b/test/schema.test.js @@ -2792,4 +2792,44 @@ describe('schema', function() { }); }, /Cannot use schema-level projections.*subdocument_mapping.not_selected/); }); + + it('enable defining virtual paths by using schema constructor (gh-11908)', async function() { + function get() {return this.email.slice(this.email.indexOf('@') + 1);} + function set(v) { this.email = [this.email.slice(0, this.email.indexOf('@')), v].join('@');} + const options = { + getters: true + }; + + const definition = { + email: { type: String } + }; + const TestSchema1 = new Schema(definition); + TestSchema1.virtual('domain', options).set(set).get(get); + + const TestSchema2 = new Schema({ + email: { type: String } + }, { + virtuals: { + domain: { + get, + set, + options + } + } + }); + + assert.deepEqual(TestSchema2.virtuals, TestSchema1.virtuals); + + const doc1 = new (mongoose.model('schema1', TestSchema1))({ email: 'test@m0_0a.com' }); + const doc2 = new (mongoose.model('schema2', TestSchema2))({ email: 'test@m0_0a.com' }); + + assert.equal(doc1.domain, doc2.domain); + + const mongooseDomain = 'mongoose.com'; + doc1.domain = mongooseDomain; + doc2.domain = mongooseDomain; + + assert.equal(doc1.domain, mongooseDomain); + assert.equal(doc1.domain, doc2.domain); + }); }); From dd20c26a6736a03d6439174639ba10bbaf9db86f Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Wed, 8 Jun 2022 01:51:11 +0200 Subject: [PATCH 07/15] Refactor related types. --- types/schemaoptions.d.ts | 2 +- types/virtuals.d.ts | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/types/schemaoptions.d.ts b/types/schemaoptions.d.ts index d8de1490006..5d465d76977 100644 --- a/types/schemaoptions.d.ts +++ b/types/schemaoptions.d.ts @@ -212,6 +212,6 @@ declare module 'mongoose' { /** * Virtual paths. */ - virtuals?: VirtualsSchemaOptionsPropertyType, + virtuals?: SchemaOptionsVirtualsPropertyType, } } diff --git a/types/virtuals.d.ts b/types/virtuals.d.ts index 160581e0bb8..c9dfe17ce00 100644 --- a/types/virtuals.d.ts +++ b/types/virtuals.d.ts @@ -1,13 +1,16 @@ /// declare module 'mongoose' { - type VirtualPathFunctions = { - get?: (this: Document & DocType) => PathType; - set?: (this: Document & DocType, ...args: any) => unknown; + type VirtualPathFunctions = { + get?: TVirtualPathFN; + set?: TVirtualPathFN; options?: VirtualTypeOptions, DocType>; }; - type VirtualsSchemaOptionsPropertyType, TInstanceMethods = {}> = { - [K in keyof virtualPaths]: VirtualPathFunctions extends true ? DocType : any, virtualPaths[K], TInstanceMethods> + type TVirtualPathFN = + >(this: Document & DocType, value: PathType, virtual: VirtualType, doc: Document & DocType) => TReturn; + + type SchemaOptionsVirtualsPropertyType, TInstanceMethods = {}> = { + [K in keyof VirtualPaths]: VirtualPathFunctions extends true ? DocType : any, VirtualPaths[K], TInstanceMethods> }; } \ No newline at end of file From ba3fc15dce2bbb4f47fb3b7eca47e7757dc7fd22 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Wed, 8 Jun 2022 02:53:23 +0200 Subject: [PATCH 08/15] Create InferDocType utility. --- test/types/schema.test.ts | 8 +++++++- types/inferschematype.d.ts | 14 +++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 8d78753d099..6f91edc753b 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -9,7 +9,9 @@ import { SchemaType, Query, HydratedDocument, - SchemaOptions + SchemaOptions, + InferDocType, + FlatRecord } from 'mongoose'; import { expectType, expectError, expectAssignable } from 'tsd'; @@ -542,6 +544,10 @@ export function autoTypedSchema() { expectError({} as InferredSchemaType); + type InferredDocType = InferDocType; + + expectType>({} as InferredDocType); + return AutoTypedSchema; } diff --git a/types/inferschematype.d.ts b/types/inferschematype.d.ts index 7b08cdffdfa..fd0b248686d 100644 --- a/types/inferschematype.d.ts +++ b/types/inferschematype.d.ts @@ -38,7 +38,19 @@ declare module 'mongoose' { * // result * type UserType = {userName?: string} */ - type InferSchemaType = ObtainSchemaGeneric ; + type InferSchemaType = ObtainSchemaGeneric; + + /** + * @summary Obtains document plain doc type from Schema instance. + * @description Exactly like {@link InferSchemaType}, but it add virtual paths. + * @param {SchemaType} SchemaType A generic of schema type instance. + * @example + * const userSchema = new Schema({userName:String}); + * type UserType = InferDocType; + * // result + * type UserType = {userName?: string} + */ + type InferDocType = FlatRecord & ObtainSchemaGeneric>; /** * @summary Obtains schema Generic type by using generic alias. From 7b0f7f4ae594870ddf9f23f7822494c5d6e2a2c7 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Wed, 8 Jun 2022 12:11:45 +0200 Subject: [PATCH 09/15] Delete reference comment. --- types/virtuals.d.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/types/virtuals.d.ts b/types/virtuals.d.ts index c9dfe17ce00..566ad87f594 100644 --- a/types/virtuals.d.ts +++ b/types/virtuals.d.ts @@ -1,5 +1,3 @@ -/// - declare module 'mongoose' { type VirtualPathFunctions = { get?: TVirtualPathFN; From 8a5414a194dcd7216c622390c3c9c5140e5388a6 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Wed, 8 Jun 2022 23:00:55 +0200 Subject: [PATCH 10/15] Describe these changes in virtuals.md file. --- docs/typescript/virtuals.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/typescript/virtuals.md b/docs/typescript/virtuals.md index b9ccc9cacdf..8684aa678c4 100644 --- a/docs/typescript/virtuals.md +++ b/docs/typescript/virtuals.md @@ -1,6 +1,34 @@ # Virtuals in TypeScript [Virtuals](/docs/tutorials/virtuals.html) are computed properties: you can access virtuals on hydrated Mongoose documents, but virtuals are **not** stored in MongoDB. +Mongoose supports auto typed virtuals so you don't need to define additional typescript interface anymore but you are still able to do so. + +### 1- Auto types: + +To make mongoose able to infer virtuals type, You have to define them in schema constructor as following: + +```ts +import { Schema, Model, model } from 'mongoose'; + +const schema = new Schema( + { + firstName: String, + lastName: String, + }, + { + virtuals:{ + fullName:{ + get(){ + return `${this.firstName} ${this.lastName}`; + } + // virtual setter and options can be defined here as well. + } + } + } +); +``` + +### 2- Manual types: You shouldn't define virtuals in your TypeScript [document interface](/docs/typescript.html). Instead, you should define a separate interface for your virtuals, and pass this interface to `Model` and `Schema`. From b6487b70d8bb4efbac51b69b7cdb5bd8b8526870 Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Tue, 21 Jun 2022 23:19:41 +0200 Subject: [PATCH 11/15] Rearrange SchemaOptions generics order. --- 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 806563566b5..24c358f9a95 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -165,7 +165,7 @@ declare module 'mongoose' { /** * Create a new schema */ - constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions, TInstanceMethods, TQueryHelpers, TVirtuals, TStaticMethods>); + constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions, TInstanceMethods, TQueryHelpers, TStaticMethods, TVirtuals>); /** 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 5d465d76977..1a87a8ccf9f 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 2984df47287f889de926205b72c066fc8dcde9bb Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Tue, 21 Jun 2022 23:40:43 +0200 Subject: [PATCH 12/15] Make standalone test for virtuals auto types. --- test/types/schema.test.ts | 28 +--------------------------- test/types/virtuals.test.ts | 35 +++++++++++++++++++++++++++++++---- types/inferschematype.d.ts | 4 ++-- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 6f91edc753b..8a0dd423801 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -9,9 +9,7 @@ import { SchemaType, Query, HydratedDocument, - SchemaOptions, - InferDocType, - FlatRecord + SchemaOptions } from 'mongoose'; import { expectType, expectError, expectAssignable } from 'tsd'; @@ -467,10 +465,6 @@ export function autoTypedSchema() { type: String, required: [true, 'userName is required'] }, - email: { - type: String, - required: [true, 'email is required'] - }, description: String, nested: new Schema({ age: { @@ -523,18 +517,6 @@ export function autoTypedSchema() { expectAssignable>(this); return this.where({ userName }); } - }, - virtuals: { - domain: { - get() { - expectType & AutoTypedSchemaType['schema']>(this); - return this.email.slice(this.email.indexOf('@') + 1); - }, - set() { - expectType & AutoTypedSchemaType['schema']>(this); - }, - options: {} - } } }); @@ -544,17 +526,12 @@ export function autoTypedSchema() { expectError({} as InferredSchemaType); - type InferredDocType = InferDocType; - - expectType>({} as InferredDocType); - return AutoTypedSchema; } export type AutoTypedSchemaType = { schema: { userName: string; - email: string; description?: string; nested?: { age: number; @@ -573,9 +550,6 @@ export type AutoTypedSchemaType = { }, methods: { instanceFn: () => 'Returned from DocumentInstanceFn' - }, - virtuals: { - domain: string } }; diff --git a/test/types/virtuals.test.ts b/test/types/virtuals.test.ts index 7507768f2f3..70a214004e7 100644 --- a/test/types/virtuals.test.ts +++ b/test/types/virtuals.test.ts @@ -1,6 +1,5 @@ -import { Document, Model, Schema, model } from 'mongoose'; +import { Document, Model, Schema, model, InferSchemaType, InferDocumentType, FlatRecord } from 'mongoose'; import { expectType } from 'tsd'; -import { autoTypedModel } from './models.test'; interface IPerson { _id: number; @@ -89,7 +88,35 @@ function gh11543() { } function autoTypedVirtuals() { - const AutoTypedModel = autoTypedModel(); - const doc = new AutoTypedModel(); + type AutoTypedSchemaType = InferSchemaType; + type VirtualsType = { domain: string }; + type InferredDocType = InferDocumentType; + + const testSchema = new Schema({ + email: { + type: String, + required: [true, 'email is required'] + } + }, { + virtuals: { + domain: { + get() { + expectType & AutoTypedSchemaType>(this); + return this.email.slice(this.email.indexOf('@') + 1); + }, + set() { + expectType & AutoTypedSchemaType>(this); + }, + options: {} + } + } + }); + + + const TestModel = model('AutoTypedVirtuals', testSchema); + + const doc = new TestModel(); expectType(doc.domain); + + expectType>({} as InferredDocType); } \ No newline at end of file diff --git a/types/inferschematype.d.ts b/types/inferschematype.d.ts index fd0b248686d..20f1772665c 100644 --- a/types/inferschematype.d.ts +++ b/types/inferschematype.d.ts @@ -46,11 +46,11 @@ declare module 'mongoose' { * @param {SchemaType} SchemaType A generic of schema type instance. * @example * const userSchema = new Schema({userName:String}); - * type UserType = InferDocType; + * type UserType = InferDocumentType; * // result * type UserType = {userName?: string} */ - type InferDocType = FlatRecord & ObtainSchemaGeneric>; + type InferDocumentType = FlatRecord & ObtainSchemaGeneric>; /** * @summary Obtains schema Generic type by using generic alias. From 3e9ea71159a8cb8ef3c05f235b7fb03076cd057c Mon Sep 17 00:00:00 2001 From: mohammad0-0ahmad Date: Tue, 21 Jun 2022 23:56:53 +0200 Subject: [PATCH 13/15] Refactor virtuals.md --- docs/typescript/virtuals.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/typescript/virtuals.md b/docs/typescript/virtuals.md index 8684aa678c4..b73602960a2 100644 --- a/docs/typescript/virtuals.md +++ b/docs/typescript/virtuals.md @@ -3,7 +3,7 @@ [Virtuals](/docs/tutorials/virtuals.html) are computed properties: you can access virtuals on hydrated Mongoose documents, but virtuals are **not** stored in MongoDB. Mongoose supports auto typed virtuals so you don't need to define additional typescript interface anymore but you are still able to do so. -### 1- Auto types: +### Automatically Inferred Types: To make mongoose able to infer virtuals type, You have to define them in schema constructor as following: @@ -28,7 +28,7 @@ const schema = new Schema( ); ``` -### 2- Manual types: +### Set virtuals type manually: You shouldn't define virtuals in your TypeScript [document interface](/docs/typescript.html). Instead, you should define a separate interface for your virtuals, and pass this interface to `Model` and `Schema`. From f5983e63892861a0a73b23bdebbb90805bb539b5 Mon Sep 17 00:00:00 2001 From: Mohammad Date: Tue, 19 Jul 2022 05:02:05 +0200 Subject: [PATCH 14/15] Delete InferDocumentType helper & fix linting --- test/types/virtuals.test.ts | 8 ++++---- types/inferschematype.d.ts | 12 ------------ types/utility.d.ts | 2 +- types/virtuals.d.ts | 2 +- 4 files changed, 6 insertions(+), 18 deletions(-) diff --git a/test/types/virtuals.test.ts b/test/types/virtuals.test.ts index 70a214004e7..a5b07f54419 100644 --- a/test/types/virtuals.test.ts +++ b/test/types/virtuals.test.ts @@ -1,4 +1,4 @@ -import { Document, Model, Schema, model, InferSchemaType, InferDocumentType, FlatRecord } from 'mongoose'; +import { Document, Model, Schema, model, InferSchemaType, FlatRecord, ObtainSchemaGeneric } from 'mongoose'; import { expectType } from 'tsd'; interface IPerson { @@ -90,7 +90,7 @@ function gh11543() { function autoTypedVirtuals() { type AutoTypedSchemaType = InferSchemaType; type VirtualsType = { domain: string }; - type InferredDocType = InferDocumentType; + type InferredDocType = FlatRecord>; const testSchema = new Schema({ email: { @@ -118,5 +118,5 @@ function autoTypedVirtuals() { const doc = new TestModel(); expectType(doc.domain); - expectType>({} as InferredDocType); -} \ No newline at end of file + expectType>({} as InferredDocType); +} diff --git a/types/inferschematype.d.ts b/types/inferschematype.d.ts index 20f1772665c..5041f830477 100644 --- a/types/inferschematype.d.ts +++ b/types/inferschematype.d.ts @@ -40,18 +40,6 @@ declare module 'mongoose' { */ type InferSchemaType = ObtainSchemaGeneric; - /** - * @summary Obtains document plain doc type from Schema instance. - * @description Exactly like {@link InferSchemaType}, but it add virtual paths. - * @param {SchemaType} SchemaType A generic of schema type instance. - * @example - * const userSchema = new Schema({userName:String}); - * type UserType = InferDocumentType; - * // result - * type UserType = {userName?: string} - */ - type InferDocumentType = FlatRecord & ObtainSchemaGeneric>; - /** * @summary Obtains schema Generic type by using generic alias. * @param {TSchema} TSchema A generic of schema type instance. diff --git a/types/utility.d.ts b/types/utility.d.ts index febdc14fcbc..bfc531aa8f2 100644 --- a/types/utility.d.ts +++ b/types/utility.d.ts @@ -19,7 +19,7 @@ declare module 'mongoose' { */ type FlatRecord = { [K in keyof T]: T[K] }; - /** + /** * @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. diff --git a/types/virtuals.d.ts b/types/virtuals.d.ts index 566ad87f594..2ec48a496f1 100644 --- a/types/virtuals.d.ts +++ b/types/virtuals.d.ts @@ -11,4 +11,4 @@ declare module 'mongoose' { type SchemaOptionsVirtualsPropertyType, TInstanceMethods = {}> = { [K in keyof VirtualPaths]: VirtualPathFunctions extends true ? DocType : any, VirtualPaths[K], TInstanceMethods> }; -} \ No newline at end of file +} From 917d3311d42264c92559fcd5068a994b2a39e5b8 Mon Sep 17 00:00:00 2001 From: Mohammad Date: Tue, 19 Jul 2022 05:54:07 +0200 Subject: [PATCH 15/15] Refactor a relevant example in docs --- docs/guide.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/guide.md b/docs/guide.md index 624cc658836..84db9d7c3f7 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -341,6 +341,23 @@ And what if you want to do some extra processing on the name, like define a `fullName` property that won't get persisted to MongoDB. ```javascript +// That can be done either by adding it to schema options: +const personSchema = new Schema({ + name: { + first: String, + last: String + } +},{ + virtuals:{ + fullName:{ + get() { + return this.name.first + ' ' + this.name.last; + } + } + } +}); + +// Or by using the virtual method as following: personSchema.virtual('fullName').get(function() { return this.name.first + ' ' + this.name.last; }); @@ -363,6 +380,27 @@ You can also add a custom setter to your virtual that will let you set both first name and last name via the `fullName` virtual. ```javascript +// Again that can be done either by adding it to schema options: +const personSchema = new Schema({ + name: { + first: String, + last: String + } +},{ + virtuals:{ + fullName:{ + get() { + return this.name.first + ' ' + this.name.last; + } + set(v) { + this.name.first = v.substr(0, v.indexOf(' ')); + this.name.last = v.substr(v.indexOf(' ') + 1); + } + } + } +}); + +// Or by using the virtual method as following: personSchema.virtual('fullName'). get(function() { return this.name.first + ' ' + this.name.last;