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
This commit is contained in:
Andrei Alecu 2020-07-10 19:29:45 +03:00 committed by GitHub
parent 070f243047
commit 3839e8d6ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 78 additions and 17 deletions

View File

@ -101,27 +101,36 @@ declare module "mongoose" {
type OmitReadonly<T> = Omit<T, ReadonlyKeysOf<T>>;
// used to exclude functions from all levels of the schema
type DeepNonFunctionProperties<T> =
type MongooseBuiltIns = mongodb.ObjectID | mongodb.Decimal128 | Date | number | boolean;
type ImplicitMongooseConversions<T> =
T extends MongooseBuiltIns
? T extends (boolean | mongodb.Decimal128 | Date) ? T | string | number // accept numbers for these
: T | string
: T;
type DeepCreateObjectTransformer<T> =
T extends MongooseBuiltIns
? T
: T extends object
? { [V in keyof NonFunctionProperties<OmitReadonly<T>>]: T[V] extends object | undefined
? ImplicitMongooseConversions<DeepCreateTransformer<NonNullable<T[V]>>>
: ImplicitMongooseConversions<T[V]> }
:
T;
// removes functions from schema from all levels
type DeepCreateTransformer<T> =
T extends Map<infer KM, infer KV>
// handle map values
// Maps are not scrubbed, replace below line with this once minimum TS version is 3.7:
// ? Map<KM, DeepNonFunctionProperties<KV>>
? { [key: string]: DeepNonFunctionProperties<KV> } | [KM, KV][] | Map<KM, KV>
? { [key: string]: DeepCreateTransformer<KV> } | [KM, KV][] | Map<KM, KV>
:
T extends Array<infer U>
? (U extends object
? { [V in keyof NonFunctionProperties<OmitReadonly<U>>]: U[V] extends object | undefined
? DeepNonFunctionProperties<NonNullable<U[V]>>
: U[V] }
: U)[]
? Array<DeepCreateObjectTransformer<U>>
:
T extends object
? { [V in keyof NonFunctionProperties<OmitReadonly<T>>]: T[V] extends object | undefined
? DeepNonFunctionProperties<NonNullable<T[V]>>
: T[V] }
:
T;
DeepCreateObjectTransformer<T>;
// mongoose allows Map<K, V> to be specified either as a Map or a Record<K, V>
type DeepMapAsObject<T> = T extends object | undefined
@ -140,7 +149,7 @@ declare module "mongoose" {
/* Helper type to extract a definition type from a Document type */
type DocumentDefinition<T> = Omit<T, Exclude<keyof Document, '_id'>>;
type ScrubCreateDefinition<T> = DeepMapAsObject<DeepNonFunctionProperties<T>>
type ScrubCreateDefinition<T> = DeepMapAsObject<DeepCreateTransformer<T>>
type CreateDocumentDefinition<T> = ScrubCreateDefinition<DocumentDefinition<T>>;

View File

@ -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<T>
tuple?: [number, number]
objectId?: mongoose.Types.ObjectId;
mapWithFuncs?: Map<string, {
title: string,
@ -658,6 +677,7 @@ interface ModelWithFunction extends mongoose.Document {
title: string,
func: () => {} // should be excluded in CreateQuery<T>
readonly readonly: unknown; // should be excluded in CreateQuery<T>
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] });