From 3839e8d6ed7c2856981673321e16796544e723ad Mon Sep 17 00:00:00 2001 From: Andrei Alecu Date: Fri, 10 Jul 2020 19:29:45 +0300 Subject: [PATCH] fix(mongoose): allow specifying objectids as strings in create (#45610) * fix(mongoose): allow specifying objectids as strings in create * add additional types * allow number for decimal128 * refactor * ensure bad enum value is not allowed and add additional tests * improve performance and refactor --- types/mongoose/index.d.ts | 39 +++++++++++++++---------- types/mongoose/test/model.ts | 56 ++++++++++++++++++++++++++++++++++-- 2 files changed, 78 insertions(+), 17 deletions(-) diff --git a/types/mongoose/index.d.ts b/types/mongoose/index.d.ts index 8defbb2271..a9e6a3c535 100644 --- a/types/mongoose/index.d.ts +++ b/types/mongoose/index.d.ts @@ -101,27 +101,36 @@ declare module "mongoose" { type OmitReadonly = Omit>; - // used to exclude functions from all levels of the schema - type DeepNonFunctionProperties = + type MongooseBuiltIns = mongodb.ObjectID | mongodb.Decimal128 | Date | number | boolean; + + type ImplicitMongooseConversions = + T extends MongooseBuiltIns + ? T extends (boolean | mongodb.Decimal128 | Date) ? T | string | number // accept numbers for these + : T | string + : T; + + type DeepCreateObjectTransformer = + T extends MongooseBuiltIns + ? T + : T extends object + ? { [V in keyof NonFunctionProperties>]: T[V] extends object | undefined + ? ImplicitMongooseConversions>> + : ImplicitMongooseConversions } + : + T; + + // removes functions from schema from all levels + type DeepCreateTransformer = T extends Map // handle map values // Maps are not scrubbed, replace below line with this once minimum TS version is 3.7: // ? Map> - ? { [key: string]: DeepNonFunctionProperties } | [KM, KV][] | Map + ? { [key: string]: DeepCreateTransformer } | [KM, KV][] | Map : T extends Array - ? (U extends object - ? { [V in keyof NonFunctionProperties>]: U[V] extends object | undefined - ? DeepNonFunctionProperties> - : U[V] } - : U)[] + ? Array> : - T extends object - ? { [V in keyof NonFunctionProperties>]: T[V] extends object | undefined - ? DeepNonFunctionProperties> - : T[V] } - : - T; + DeepCreateObjectTransformer; // mongoose allows Map to be specified either as a Map or a Record type DeepMapAsObject = T extends object | undefined @@ -140,7 +149,7 @@ declare module "mongoose" { /* Helper type to extract a definition type from a Document type */ type DocumentDefinition = Omit>; - type ScrubCreateDefinition = DeepMapAsObject> + type ScrubCreateDefinition = DeepMapAsObject> type CreateDocumentDefinition = ScrubCreateDefinition>; diff --git a/types/mongoose/test/model.ts b/types/mongoose/test/model.ts index c10faf0de3..07af9e8519 100644 --- a/types/mongoose/test/model.ts +++ b/types/mongoose/test/model.ts @@ -612,13 +612,31 @@ enum SchemaEnum { Bar } +enum StringSchemaEnum { + Foo = "foo", + Bar = "bar" +} + interface ModelWithFunction extends mongoose.Document { name: string; someFunc: () => any; + objectId?: mongoose.Types.ObjectId; + + date?: Date; + + boolean?: boolean; + + decimal?: mongodb.Decimal128; + + number?: number; + enum?: SchemaEnum; + enum2?: StringSchemaEnum; + + selfRef?: ModelWithFunction | mongodb.ObjectID; selfRef2?: ModelWithFunction | mongodb.ObjectID; @@ -650,6 +668,7 @@ interface ModelWithFunction extends mongoose.Document { title: string, func: () => {}, // should be excluded in CreateQuery tuple?: [number, number] + objectId?: mongoose.Types.ObjectId; mapWithFuncs?: Map {} // should be excluded in CreateQuery readonly readonly: unknown; // should be excluded in CreateQuery + objectId?: mongoose.Types.ObjectId; }> }>; }> @@ -699,13 +719,15 @@ ModelWithFunctionInSchema.create({ deepArray: [{ title: "test", tuple: [1, 2], + objectId: "valid-object-id-source", mapWithFuncs: { test: { title: "test", innerMap: { test: { - title: "hello" - } + title: "hello", + objectId: "valid-object-id-source" + }, } } } @@ -762,3 +784,33 @@ ModelWithFunctionInSchema.create({ name: "test", jobs: [] }).then(ref => { ModelWithFunctionInSchema.create({ name: "test", jobs: [], selfRefArray2: [id, id] }); ModelWithFunctionInSchema.create({ name: "test", jobs: [], selfRefArray2: [ref, ref] }); }); + +ModelWithFunctionInSchema.create({ name: "test", jobs: [], objectId: "valid-object-id-source" }); +ModelWithFunctionInSchema.create({ + name: "test", + jobs: [], + objectId: new mongodb.ObjectID("valid-object-id-source") +}); + +ModelWithFunctionInSchema.create({ + name: "test", + jobs: [], + date: new Date() +}); +ModelWithFunctionInSchema.create({ name: "test", jobs: [], date: "2020-01-01" }); + +// allow strings, since mongoose can cast them +ModelWithFunctionInSchema.create({ name: "test", jobs: [], boolean: "true" }); +ModelWithFunctionInSchema.create({ name: "test", jobs: [], boolean: 1 }); +ModelWithFunctionInSchema.create({ name: "test", jobs: [], decimal: "1" }); +ModelWithFunctionInSchema.create({ name: "test", jobs: [], decimal: 1 }); +ModelWithFunctionInSchema.create({ name: "test", jobs: [], number: "1" }); + +// $ExpectError +ModelWithFunctionInSchema.create({ name: "test", jobs: [], enum2: "bad value" }); + +// $ExpectError +ModelWithFunctionInSchema.create({ name: "test", jobs: [], date: [123] }); + +// $ExpectError +ModelWithFunctionInSchema.create({ name: "test", jobs: [], objectId: [123] });