Skip to content

Commit

Permalink
fix(mongoose): allow specifying objectids as strings in create (#45610)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
andreialecu committed Jul 10, 2020
1 parent 070f243 commit 3839e8d
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 17 deletions.
39 changes: 24 additions & 15 deletions types/mongoose/index.d.ts
Expand Up @@ -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
Expand All @@ -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>>;

Expand Down
56 changes: 54 additions & 2 deletions types/mongoose/test/model.ts
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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;
}>
}>;
}>
Expand Down Expand Up @@ -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"
},
}
}
}
Expand Down Expand Up @@ -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] });

0 comments on commit 3839e8d

Please sign in to comment.