From da37597fba9a808cd1dd3254bd593a711e79503e Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 14 Oct 2024 11:53:46 -0400 Subject: [PATCH 1/3] feat: add AutoInferredSchema to work around schema inference issues in #14954 --- lib/mongoose.js | 9 +++++++++ test/types/inferrawdoctype.test.ts | 21 +++++++++++++++++---- types/index.d.ts | 23 +++++++++++++++++++++++ types/inferrawdoctype.d.ts | 11 ++++++++--- 4 files changed, 57 insertions(+), 7 deletions(-) diff --git a/lib/mongoose.js b/lib/mongoose.js index f314e4c399a..1d2bf6eec38 100644 --- a/lib/mongoose.js +++ b/lib/mongoose.js @@ -937,6 +937,15 @@ Mongoose.prototype.Mongoose = Mongoose; Mongoose.prototype.Schema = Schema; +/** + * Identical to `Schema` at runtime. Exists purely to work around some TypeScript compatibility issues. + * + * @method AutoInferredSchema + * @api public + */ + +Mongoose.prototype.AutoInferredSchema = Schema; + /** * The Mongoose [SchemaType](https://mongoosejs.com/docs/api/schematype.html#SchemaType()) constructor * diff --git a/test/types/inferrawdoctype.test.ts b/test/types/inferrawdoctype.test.ts index 7d162b03975..42ba10b9dcc 100644 --- a/test/types/inferrawdoctype.test.ts +++ b/test/types/inferrawdoctype.test.ts @@ -1,4 +1,4 @@ -import { InferRawDocType } from 'mongoose'; +import { InferRawDocType, Schema, AutoInferredSchema } from 'mongoose'; import { expectType, expectError } from 'tsd'; function gh14839() { @@ -17,9 +17,22 @@ function gh14839() { dateOfBirth: { type: Date, required: true - } + }, + subdoc: new AutoInferredSchema({ + name: { type: String, required: true }, + l2: new AutoInferredSchema({ + myProp: { type: Number, required: true } + }) + }), + docArr: [new AutoInferredSchema({ test: { type: String, required: true } })] }; - type UserType = InferRawDocType< typeof schemaDefinition>; - expectType<{ email: string, password: string, dateOfBirth: Date }>({} as UserType); + type UserType = InferRawDocType; + expectType<{ + email: string, + password: string, + dateOfBirth: Date, + subdoc?: { name: string, l2?: { myProp: number } | null } | null, + docArr: { test: string }[] + }>({} as UserType); } diff --git a/types/index.d.ts b/types/index.d.ts index 3ec72ac281d..b55be8e593b 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -252,6 +252,29 @@ declare module 'mongoose' { TVirtuals, TStaticMethods> = (schema: Schema, opts?: any) => void; + export class AutoInferredSchema< + SchemaDef = unknown, + RawDocType = any, + TModelType = Model, + TInstanceMethods = {}, + TQueryHelpers = {}, + TVirtuals = {}, + TStaticMethods = {}, + TSchemaOptions = DefaultSchemaOptions, + DocType extends ApplySchemaOptions< + ObtainDocumentType>, + ResolveSchemaOptions + > = ApplySchemaOptions< + ObtainDocumentType>, + ResolveSchemaOptions + >, + THydratedDocumentType = HydratedDocument, TVirtuals & TInstanceMethods> + > extends Schema { + constructor(definition?: SchemaDef, options?: SchemaOptions, TInstanceMethods, TQueryHelpers, TStaticMethods, TVirtuals, THydratedDocumentType> | ResolveSchemaOptions); + + _schemaDefinition: SchemaDef; + } + export class Schema< RawDocType = any, TModelType = Model, diff --git a/types/inferrawdoctype.d.ts b/types/inferrawdoctype.d.ts index 605571057a0..9e3c92bce84 100644 --- a/types/inferrawdoctype.d.ts +++ b/types/inferrawdoctype.d.ts @@ -6,6 +6,7 @@ import { PathWithTypePropertyBaseType, PathEnumOrString } from './inferschematype'; +import { InferSchemaType } from 'mongoose'; declare module 'mongoose' { export type InferRawDocType< @@ -35,6 +36,10 @@ declare module 'mongoose' { TypeKey >; + type InferRawDocTypeFromSchema = TSchema extends AutoInferredSchema + ? InferRawDocType + : InferSchemaType; + /** * Same as inferSchemaType, except: * @@ -50,11 +55,11 @@ declare module 'mongoose' { */ type ResolveRawPathType = {}, TypeKey extends string = DefaultSchemaOptions['typeKey']> = PathValueType extends Schema ? - InferSchemaType : + InferRawDocTypeFromSchema : PathValueType extends (infer Item)[] ? IfEquals> : + Array> : Item extends Record ? Item[TypeKey] extends Function | String ? // If Item has a type key that's a string or a callable, it must be a scalar, @@ -73,7 +78,7 @@ declare module 'mongoose' { >: PathValueType extends ReadonlyArray ? IfEquals> : + Array> : Item extends Record ? Item[TypeKey] extends Function | String ? ObtainRawDocumentPathType[] : From ad7824f473e7423d74356c8d74d5b0163fce2222 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 14 Oct 2024 12:50:45 -0400 Subject: [PATCH 2/3] test cleanup --- test/types/inferrawdoctype.test.ts | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/types/inferrawdoctype.test.ts b/test/types/inferrawdoctype.test.ts index 42ba10b9dcc..32779d0fd6f 100644 --- a/test/types/inferrawdoctype.test.ts +++ b/test/types/inferrawdoctype.test.ts @@ -2,6 +2,39 @@ import { InferRawDocType, Schema, AutoInferredSchema } from 'mongoose'; import { expectType, expectError } from 'tsd'; function gh14839() { + const schemaDefinition = { + email: { + type: String, + trim: true, + required: true, + unique: true, + lowercase: true + }, + password: { + type: String, + required: true + }, + dateOfBirth: { + type: Date, + required: true + }, + subdoc: new AutoInferredSchema({ + name: { type: String, required: true } + }), + docArr: [new AutoInferredSchema({ test: { type: String, required: true } })] + }; + + type UserType = InferRawDocType; + expectType<{ + email: string, + password: string, + dateOfBirth: Date, + subdoc?: { name: string } | null, + docArr: { test: string }[] + }>({} as UserType); +} + +function gh14954() { const schemaDefinition = { email: { type: String, From f6c7a6a6bb1f3745ae3e410fad35f0149b8a3196 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Tue, 20 May 2025 14:52:48 -0400 Subject: [PATCH 3/3] types: correctly pull schema definition from AutoInferredSchema --- types/index.d.ts | 6 +- types/inferrawdoctype.d.ts | 114 +++++++++++++++++++------------------ 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/types/index.d.ts b/types/index.d.ts index 93888cad4a9..db7aa52d492 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -260,7 +260,7 @@ declare module 'mongoose' { export class AutoInferredSchema< SchemaDef = unknown, - RawDocType = any, + RawDocType = InferRawDocType, TModelType = Model, TInstanceMethods = {}, TQueryHelpers = {}, @@ -276,9 +276,7 @@ declare module 'mongoose' { >, THydratedDocumentType = HydratedDocument, TVirtuals & TInstanceMethods> > extends Schema { - constructor(definition?: SchemaDef, options?: SchemaOptions, TInstanceMethods, TQueryHelpers, TStaticMethods, TVirtuals, THydratedDocumentType> | ResolveSchemaOptions); - - _schemaDefinition: SchemaDef; + constructor(definition: SchemaDef, options?: SchemaOptions, TInstanceMethods, TQueryHelpers, TStaticMethods, TVirtuals, THydratedDocumentType> | ResolveSchemaOptions); } export class Schema< diff --git a/types/inferrawdoctype.d.ts b/types/inferrawdoctype.d.ts index 686594ef0e9..c284e27b5c6 100644 --- a/types/inferrawdoctype.d.ts +++ b/types/inferrawdoctype.d.ts @@ -36,8 +36,8 @@ declare module 'mongoose' { TypeKey >; - type InferRawDocTypeFromSchema = TSchema extends AutoInferredSchema - ? InferRawDocType + type InferRawDocTypeFromSchema = TSchema extends AutoInferredSchema + ? InferRawDocType : InferSchemaType; /** @@ -54,35 +54,22 @@ declare module 'mongoose' { * @returns Number, "Number" or "number" will be resolved to number type. */ type ResolveRawPathType = {}, TypeKey extends string = DefaultSchemaOptions['typeKey']> = - PathValueType extends Schema ? - InferRawDocTypeFromSchema : - PathValueType extends (infer Item)[] ? - IfEquals> : - Item extends Record ? - Item[TypeKey] extends Function | String ? - // If Item has a type key that's a string or a callable, it must be a scalar, - // so we can directly obtain its path type. - ObtainRawDocumentPathType[] : - // If the type key isn't callable, then this is an array of objects, in which case - // we need to call InferRawDocType to correctly infer its type. - Array> : - IsSchemaTypeFromBuiltinClass extends true ? - ObtainRawDocumentPathType[] : - IsItRecordAndNotAny extends true ? - Item extends Record ? - ObtainRawDocumentPathType[] : - Array> : - ObtainRawDocumentPathType[] - >: - PathValueType extends ReadonlyArray ? + PathValueType extends AutoInferredSchema ? + InferRawDocType : + PathValueType extends Schema ? + InferSchemaType : + PathValueType extends (infer Item)[] ? IfEquals> : Item extends Record ? Item[TypeKey] extends Function | String ? + // If Item has a type key that's a string or a callable, it must be a scalar, + // so we can directly obtain its path type. ObtainRawDocumentPathType[] : - InferRawDocType[]: + // If the type key isn't callable, then this is an array of objects, in which case + // we need to call InferRawDocType to correctly infer its type. + Array> : IsSchemaTypeFromBuiltinClass extends true ? ObtainRawDocumentPathType[] : IsItRecordAndNotAny extends true ? @@ -91,34 +78,49 @@ declare module 'mongoose' { Array> : ObtainRawDocumentPathType[] >: - PathValueType extends StringSchemaDefinition ? PathEnumOrString : - IfEquals extends true ? PathEnumOrString : - IfEquals extends true ? PathEnumOrString : - PathValueType extends NumberSchemaDefinition ? Options['enum'] extends ReadonlyArray ? Options['enum'][number] : number : - IfEquals extends true ? number : - PathValueType extends DateSchemaDefinition ? NativeDate : - IfEquals extends true ? NativeDate : - PathValueType extends typeof Buffer | 'buffer' | 'Buffer' | typeof Schema.Types.Buffer ? Buffer : - PathValueType extends BooleanSchemaDefinition ? boolean : - IfEquals extends true ? boolean : - PathValueType extends ObjectIdSchemaDefinition ? Types.ObjectId : - IfEquals extends true ? Types.ObjectId : - IfEquals extends true ? Types.ObjectId : - PathValueType extends 'decimal128' | 'Decimal128' | typeof Schema.Types.Decimal128 ? Types.Decimal128 : - IfEquals extends true ? Types.Decimal128 : - IfEquals extends true ? Types.Decimal128 : - IfEquals extends true ? bigint : - IfEquals extends true ? bigint : - PathValueType extends 'bigint' | 'BigInt' | typeof Schema.Types.BigInt | typeof BigInt ? bigint : - PathValueType extends 'uuid' | 'UUID' | typeof Schema.Types.UUID ? Buffer : - IfEquals extends true ? Buffer : - PathValueType extends MapConstructor | 'Map' ? Map> : - IfEquals extends true ? 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'] : - PathValueType extends Record ? InferRawDocType : - unknown; + PathValueType extends ReadonlyArray ? + IfEquals> : + Item extends Record ? + Item[TypeKey] extends Function | String ? + ObtainRawDocumentPathType[] : + InferRawDocType[]: + IsSchemaTypeFromBuiltinClass extends true ? + ObtainRawDocumentPathType[] : + IsItRecordAndNotAny extends true ? + Item extends Record ? + ObtainRawDocumentPathType[] : + Array> : + ObtainRawDocumentPathType[] + >: + PathValueType extends StringSchemaDefinition ? PathEnumOrString : + IfEquals extends true ? PathEnumOrString : + IfEquals extends true ? PathEnumOrString : + PathValueType extends NumberSchemaDefinition ? Options['enum'] extends ReadonlyArray ? Options['enum'][number] : number : + IfEquals extends true ? number : + PathValueType extends DateSchemaDefinition ? NativeDate : + IfEquals extends true ? NativeDate : + PathValueType extends typeof Buffer | 'buffer' | 'Buffer' | typeof Schema.Types.Buffer ? Buffer : + PathValueType extends BooleanSchemaDefinition ? boolean : + IfEquals extends true ? boolean : + PathValueType extends ObjectIdSchemaDefinition ? Types.ObjectId : + IfEquals extends true ? Types.ObjectId : + IfEquals extends true ? Types.ObjectId : + PathValueType extends 'decimal128' | 'Decimal128' | typeof Schema.Types.Decimal128 ? Types.Decimal128 : + IfEquals extends true ? Types.Decimal128 : + IfEquals extends true ? Types.Decimal128 : + IfEquals extends true ? bigint : + IfEquals extends true ? bigint : + PathValueType extends 'bigint' | 'BigInt' | typeof Schema.Types.BigInt | typeof BigInt ? bigint : + PathValueType extends 'uuid' | 'UUID' | typeof Schema.Types.UUID ? Buffer : + IfEquals extends true ? Buffer : + PathValueType extends MapConstructor | 'Map' ? Map> : + IfEquals extends true ? 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'] : + PathValueType extends Record ? InferRawDocType : + unknown; }