From 6b125a082f2dd1ddd870ce4a23fca862f2d1c05e Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 1 Sep 2023 17:56:55 -0400 Subject: [PATCH 1/5] WIP alternative approach for #13523: create separate generic for schema definition --- test/types/querycursor.test.ts | 4 +++- test/types/schema.test.ts | 4 ++-- types/index.d.ts | 29 +++++++++++++++-------------- types/inferschematype.d.ts | 5 +++-- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/test/types/querycursor.test.ts b/test/types/querycursor.test.ts index 611e9543977..47bf90257c4 100644 --- a/test/types/querycursor.test.ts +++ b/test/types/querycursor.test.ts @@ -1,7 +1,7 @@ import { Schema, model, Model, Types } from 'mongoose'; import { expectType } from 'tsd'; -const schema = new Schema({ name: { type: 'String' } }); +const schema = new Schema({ name: { type: 'String' as const } }); const Test = model('Test', schema); @@ -20,3 +20,5 @@ Test.find().cursor(). expectType(i); }). then(() => console.log('Done!')); + +expectType<{ name: { type: 'String' } }>(schema.obj); \ No newline at end of file diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 45dd2653c31..c8e8152acb7 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -1007,7 +1007,7 @@ function gh12869() { const dbExample = new Schema( { - active: { type: String, enum: ['foo', 'bar'], required: true } + active: { type: String, enum: ['foo', 'bar'] as ['foo', 'bar'], required: true } } ); @@ -1031,7 +1031,7 @@ function gh12882() { const arrNum = new Schema({ fooArray: { type: [{ - type: 'Number', + type: 'Number' as const, required: true }], required: true diff --git a/types/index.d.ts b/types/index.d.ts index 95653baf5a4..4c1a52b0d68 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -79,16 +79,16 @@ declare module 'mongoose' { collection?: string, options?: CompileModelOptions ): Model< - InferSchemaType, - ObtainSchemaGeneric, - ObtainSchemaGeneric, - ObtainSchemaGeneric, - HydratedDocument< - InferSchemaType, - ObtainSchemaGeneric & ObtainSchemaGeneric, - ObtainSchemaGeneric - >, - TSchema + InferSchemaType, + ObtainSchemaGeneric, + ObtainSchemaGeneric, + ObtainSchemaGeneric, + HydratedDocument< + InferSchemaType, + ObtainSchemaGeneric & ObtainSchemaGeneric, + ObtainSchemaGeneric + >, + TSchema > & ObtainSchemaGeneric; export function model(name: string, schema?: Schema | Schema, collection?: string, options?: CompileModelOptions): Model; @@ -223,11 +223,12 @@ declare module 'mongoose' { TVirtuals = {}, TStaticMethods = {}, TSchemaOptions = DefaultSchemaOptions, + TSchemaDefinition = any, DocType extends ApplySchemaOptions< - ObtainDocumentType>, + ObtainDocumentType>, ResolveSchemaOptions > = ApplySchemaOptions< - ObtainDocumentType>, + ObtainDocumentType>, ResolveSchemaOptions >, THydratedDocumentType = HydratedDocument @@ -236,7 +237,7 @@ declare module 'mongoose' { /** * Create a new schema */ - constructor(definition?: SchemaDefinition> | DocType, options?: SchemaOptions | ResolveSchemaOptions); + constructor(definition?: TSchemaDefinition, options?: SchemaOptions | ResolveSchemaOptions); /** Adds key path / schema type pairs to this schema. */ add(obj: SchemaDefinition> | Schema, prefix?: string): this; @@ -300,7 +301,7 @@ declare module 'mongoose' { methods: { [F in keyof TInstanceMethods]: TInstanceMethods[F] } & AnyObject; /** The original object passed to the schema constructor */ - obj: SchemaDefinition>; + obj: TSchemaDefinition; /** Gets/sets schema paths. */ path>(path: string): ResultType; diff --git a/types/inferschematype.d.ts b/types/inferschematype.d.ts index 75240ed1675..5c373836558 100644 --- a/types/inferschematype.d.ts +++ b/types/inferschematype.d.ts @@ -46,8 +46,8 @@ declare module 'mongoose' { * @param {TSchema} TSchema A generic of schema type instance. * @param {alias} alias Targeted generic alias. */ - type ObtainSchemaGeneric = - TSchema extends Schema + type ObtainSchemaGeneric = + TSchema extends Schema ? { EnforcedDocType: EnforcedDocType; M: M; @@ -56,6 +56,7 @@ declare module 'mongoose' { TVirtuals: TVirtuals; TStaticMethods: TStaticMethods; TSchemaOptions: TSchemaOptions; + TSchemaDefinition: TSchemaDefinition; DocType: DocType; }[alias] : unknown; From 6830fc0bde49cdd29dcd7fc617a4c2e353a27fb4 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 12 Sep 2023 11:11:46 -0400 Subject: [PATCH 2/5] types: fixed all but one TS test --- test/types/docArray.test.ts | 2 +- test/types/document.test.ts | 7 +++++++ test/types/schema.test.ts | 12 ++++++------ test/types/virtuals.test.ts | 5 +++-- types/index.d.ts | 2 +- types/inferschematype.d.ts | 4 ++-- 6 files changed, 20 insertions(+), 12 deletions(-) diff --git a/test/types/docArray.test.ts b/test/types/docArray.test.ts index ebe8092a5bb..e16e045139a 100644 --- a/test/types/docArray.test.ts +++ b/test/types/docArray.test.ts @@ -45,7 +45,7 @@ function gh13087() { required: true, type: [Number] // [longitude, latitude] } - }, + } as const, { _id: false } ); diff --git a/test/types/document.test.ts b/test/types/document.test.ts index f4fb56fc359..55f36b6b7fc 100644 --- a/test/types/document.test.ts +++ b/test/types/document.test.ts @@ -230,6 +230,12 @@ async function gh11960() { type ParentModelType = Model; + const schemaDefinition = { + username: { type: String }, + map: { type: Map, of: String }, + nested: { type: NestedSchema }, + nestedArray: [{ type: NestedSchema }] + } as const; const ParentSchema = new Schema< Parent, ParentModelType, @@ -238,6 +244,7 @@ async function gh11960() { {}, {}, DefaultSchemaOptions, + typeof schemaDefinition, Parent, ParentDocument >({ diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index c8e8152acb7..5c55b94e068 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -463,7 +463,7 @@ export function autoTypedSchema() { decimal1: Schema.Types.Decimal128, decimal2: 'Decimal128', decimal3: 'decimal128' - }); + } as const); type InferredTestSchemaType = InferSchemaType; @@ -483,7 +483,7 @@ export function autoTypedSchema() { const AutoTypedSchema = new Schema({ userName: { type: String, - required: [true, 'userName is required'] + required: [true, 'userName is required'] as [true, string] }, description: String, nested: new Schema({ @@ -519,7 +519,7 @@ export function autoTypedSchema() { }) ] } - }, { + } as const, { statics: { staticFn() { expectType>>(this); @@ -558,8 +558,8 @@ export type AutoTypedSchemaType = { date: Date; messages?: number; }> - } - , statics: { + }, + statics: { staticFn: () => 'Returned from staticFn' }, methods: { @@ -760,7 +760,7 @@ function pluginOptions() { } const schema = new Schema({}); - expectType>(schema.plugin(pluginFunction)); // test that chaining would be possible + expectAssignable>(schema.plugin(pluginFunction)); // test that chaining would be possible // could not add strict tests that the parameters are inferred correctly, because i dont know how this would be done in tsd diff --git a/test/types/virtuals.test.ts b/test/types/virtuals.test.ts index 0ea393eae45..654f9d9de72 100644 --- a/test/types/virtuals.test.ts +++ b/test/types/virtuals.test.ts @@ -95,14 +95,15 @@ async function autoTypedVirtuals() { const testSchema = new Schema({ email: { type: String, - required: [true, 'email is required'] + required: [true, 'email is required'] as [true, string] } }, { virtuals: { domain: { get() { expectType & AutoTypedSchemaType>(this); - return this.email.slice(this.email.indexOf('@') + 1); + const email = this.email; + return email.slice(email.indexOf('@') + 1); }, set() { expectType & AutoTypedSchemaType>(this); diff --git a/types/index.d.ts b/types/index.d.ts index 4c1a52b0d68..ba5cc5895a5 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -223,7 +223,7 @@ declare module 'mongoose' { TVirtuals = {}, TStaticMethods = {}, TSchemaOptions = DefaultSchemaOptions, - TSchemaDefinition = any, + TSchemaDefinition extends SchemaDefinition> = any, DocType extends ApplySchemaOptions< ObtainDocumentType>, ResolveSchemaOptions diff --git a/types/inferschematype.d.ts b/types/inferschematype.d.ts index 5c373836558..bb74327a2c4 100644 --- a/types/inferschematype.d.ts +++ b/types/inferschematype.d.ts @@ -87,13 +87,13 @@ type IsPathDefaultUndefined = PathType extends { default: undefined } * @param {TypeKey} TypeKey A generic of literal string type."Refers to the property used for path type definition". */ type IsPathRequired = - P extends { required: true | [true, string | undefined] | { isRequired: true } } | ArrayConstructor | any[] + P extends { required: true | [true, string | undefined] | { isRequired: true } } | ArrayConstructor | any[] | ReadonlyArray ? true : P extends { required: boolean } ? P extends { required: false } ? false : true - : P extends (Record) + : P extends (Record>) ? IsPathDefaultUndefined

extends true ? false : true From 585806182324fc79e2ea3b1b9c8e2806440b4e2c Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 12 Sep 2023 16:42:48 -0400 Subject: [PATCH 3/5] clean up all but 1 test --- test/types/docArray.test.ts | 3 +++ test/types/schema.test.ts | 37 +++++++++++++++++++++++++++++++++++-- types/index.d.ts | 2 +- types/inferschematype.d.ts | 2 +- 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/test/types/docArray.test.ts b/test/types/docArray.test.ts index e16e045139a..0b23b544f13 100644 --- a/test/types/docArray.test.ts +++ b/test/types/docArray.test.ts @@ -16,6 +16,9 @@ async function gh10293() { }); const TestModel = model('gh10293TestModel', testSchema); + const doc = new TestModel(); + expectType(doc.name); + expectType(doc.arrayOfArray); testSchema.methods.getArrayOfArray = function(this: InstanceType): string[][] { // <-- function to return Array of Array const test = this.toObject(); diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 5c55b94e068..8518751a992 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -596,17 +596,24 @@ function gh11828() { } }; - new Schema({ + const schema = new Schema({ name: { type: String, default: () => 'Hafez' }, age: { type: Number, default: () => 27 }, bornAt: { type: Date, default: () => new Date() }, isActive: { type: Boolean, - default(): boolean { + default(this: HydratedDocument): boolean { return this.name === 'Hafez'; } } }); + + const UserModel = model('User', schema); + const doc = new UserModel(); + expectType(doc.name); + expectType(doc.age); + expectType(doc.bornAt); + expectType(doc.isActive); } function gh11997() { @@ -1154,6 +1161,32 @@ function gh13514() { const str: string = doc.email; } +function defaultFn() { + const schema = new Schema({ + name: String, + email: { + type: String, + default() { + return this.name + '@test.com'; + } + } + }); + type X = ObtainSchemaGeneric; + const x: X = { + name: String, + email: { + type: String, + default() { + return this.name + '@test.com'; + } + } + } + const Test = model('Test', schema); + + const doc = new Test({ name: 'john' }); + const str: string = doc.email; +} + function gh13633() { const schema = new Schema({ name: String }); diff --git a/types/index.d.ts b/types/index.d.ts index ba5cc5895a5..4c1a52b0d68 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -223,7 +223,7 @@ declare module 'mongoose' { TVirtuals = {}, TStaticMethods = {}, TSchemaOptions = DefaultSchemaOptions, - TSchemaDefinition extends SchemaDefinition> = any, + TSchemaDefinition = any, DocType extends ApplySchemaOptions< ObtainDocumentType>, ResolveSchemaOptions diff --git a/types/inferschematype.d.ts b/types/inferschematype.d.ts index bb74327a2c4..34d6bdb82ca 100644 --- a/types/inferschematype.d.ts +++ b/types/inferschematype.d.ts @@ -77,7 +77,7 @@ declare module 'mongoose' { type IsPathDefaultUndefined = PathType extends { default: undefined } ? true : - PathType extends { default: (...args: any[]) => undefined } ? + PathType extends { default: (this: any, ...args: any[]) => undefined } ? true : false; From 0e77ec94ba74906888f479f4159d6b9ef0e54ebc Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 12 Sep 2023 16:45:07 -0400 Subject: [PATCH 4/5] test: quick tweak --- test/types/schema.test.ts | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index 8518751a992..a4fd2e6298d 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -1166,21 +1166,12 @@ function defaultFn() { name: String, email: { type: String, - default() { + // Requires `this: any` even in 7.0. + default(this: any) { return this.name + '@test.com'; } } }); - type X = ObtainSchemaGeneric; - const x: X = { - name: String, - email: { - type: String, - default() { - return this.name + '@test.com'; - } - } - } const Test = model('Test', schema); const doc = new Test({ name: 'john' }); From 18ee0eb411464a98ee3bc941a2d9bb167a2f3f77 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 25 Sep 2023 15:04:22 -0400 Subject: [PATCH 5/5] quick fixes --- test/types/schema.test.ts | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/test/types/schema.test.ts b/test/types/schema.test.ts index d4f6699deff..92565dc3c0c 100644 --- a/test/types/schema.test.ts +++ b/test/types/schema.test.ts @@ -1014,8 +1014,8 @@ function gh12869() { const dbExample = new Schema( { - active: { type: String, enum: ['foo', 'bar'] as ['foo', 'bar'], required: true } - } + active: { type: String, enum: ['foo', 'bar'], required: true } + } as const ); type Example = InferSchemaType; @@ -1247,10 +1247,22 @@ async function gh13797() { interface IUser { name: string; } - new Schema({ name: { type: String, required: function() { - expectType(this); return true; - } } }); - new Schema({ name: { type: String, default: function() { - expectType(this); return ''; - } } }); + new Schema({ + name: { + type: String, + required: function() { + expectType(this); + return true; + } + } + }); + new Schema({ + name: { + type: String, + default: function() { + expectType(this); + return ''; + } + } + }); }