-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/ts type improvements #11650
Feature/ts type improvements #11650
Changes from 11 commits
a4532e7
d2f9cc9
a16d077
bac4a82
659436d
2210de8
2594b79
a9792a0
2423440
0ae960b
e099a59
e4b007b
9534282
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1482,6 +1482,26 @@ declare module 'mongoose' { | |
|
||
type ProjectionFields<DocType> = { [Key in keyof Omit<LeanDocument<DocType>, '__v'>]?: any } & Record<string, any>; | ||
|
||
type _QueryManyForModel<T, ResultDoc, TQueryHelpers = {}> = | ||
QueryWithHelpers<Array<ResultDoc>, ResultDoc, TQueryHelpers, T>; | ||
type _QueryOneForModel<T, ResultDoc, TQueryHelpers = {}> = | ||
QueryWithHelpers<ResultDoc, ResultDoc, TQueryHelpers, T>; | ||
|
||
/** | ||
* Helper type for getting a find Query type for a given Model (return of Model.find) | ||
*/ | ||
type QueryForModel<T extends Model<any>> = | ||
T extends Model<infer MT, infer TQueryHelpers, infer TMethodsAndOverrides, infer TVirtuals> ? | ||
_QueryManyForModel<MT, HydratedDocument<MT, TMethodsAndOverrides, TVirtuals>, TQueryHelpers> | ||
: never; | ||
/** | ||
* Helper type for getting a findOne Query type for a given Model (return of Model.findOne) | ||
*/ | ||
type QueryOneForModel<T extends Model<any>> = | ||
T extends Model<infer MT, infer TQueryHelpers, infer TMethodsAndOverrides, infer TVirtuals> ? | ||
_QueryOneForModel<MT, HydratedDocument<MT, TMethodsAndOverrides, TVirtuals>, TQueryHelpers> | ||
: never; | ||
|
||
class Query<ResultType, DocType, THelpers = {}, RawDocType = DocType> { | ||
_mongooseOptions: MongooseQueryOptions; | ||
|
||
|
@@ -1583,7 +1603,7 @@ declare module 'mongoose' { | |
|
||
/** Specifies a `$elemMatch` query condition. When called with one argument, the most recent path passed to `where()` is used. */ | ||
elemMatch(val: Function | any): this; | ||
elemMatch(path: string, val: Function | any): this; | ||
elemMatch<KEY extends keyof FilterQuery<DocType>>(path: KEY, val: FilterQuery<DocType>[KEY]['$elemMatch']): this; | ||
|
||
/** | ||
* Gets/sets the error flag on this query. If this flag is not null or | ||
|
@@ -1600,7 +1620,7 @@ declare module 'mongoose' { | |
|
||
/** Specifies a `$exists` query condition. When called with one argument, the most recent path passed to `where()` is used. */ | ||
exists(val: boolean): this; | ||
exists(path: string, val: boolean): this; | ||
exists<KEY extends keyof FilterQuery<DocType>>(path: KEY, val: FilterQuery<DocType>[KEY]['$exists']): this; | ||
|
||
/** | ||
* Sets the [`explain` option](https://docs.mongodb.com/manual/reference/method/cursor.explain/), | ||
|
@@ -1680,18 +1700,18 @@ declare module 'mongoose' { | |
|
||
/** Specifies a `$gt` query condition. When called with one argument, the most recent path passed to `where()` is used. */ | ||
gt(val: number): this; | ||
gt(path: string, val: number): this; | ||
gte<KEY extends keyof FilterQuery<DocType>>(path: KEY, val: FilterQuery<DocType>[KEY]['$gt']): this; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hey, I was just reading through the diffs and noticed this minor error. You changed the name of the function. From There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ack! You're right, good catch! @vkarpov15 very sorry about that! I'm assuming you'll want to just fix it since it's merged already? Let me know if you need me for anything There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. covered by #11760 |
||
|
||
/** Specifies a `$gte` query condition. When called with one argument, the most recent path passed to `where()` is used. */ | ||
gte(val: number): this; | ||
gte(path: string, val: number): this; | ||
gte<KEY extends keyof FilterQuery<DocType>>(path: KEY, val: FilterQuery<DocType>[KEY]['$gte']): this; | ||
|
||
/** Sets query hints. */ | ||
hint(val: any): this; | ||
|
||
/** Specifies an `$in` query condition. When called with one argument, the most recent path passed to `where()` is used. */ | ||
in(val: Array<any>): this; | ||
in(path: string, val: Array<any>): this; | ||
in<KEY extends keyof FilterQuery<DocType>>(path: KEY, val: FilterQuery<DocType>[KEY]['$in']): this; | ||
|
||
/** Declares an intersects query for `geometry()`. */ | ||
intersects(arg?: any): this; | ||
|
@@ -1707,11 +1727,11 @@ declare module 'mongoose' { | |
|
||
/** Specifies a `$lt` query condition. When called with one argument, the most recent path passed to `where()` is used. */ | ||
lt(val: number): this; | ||
lt(path: string, val: number): this; | ||
lt<KEY extends keyof FilterQuery<DocType>>(path: KEY, val: FilterQuery<DocType>[KEY]['$lt']): this; | ||
|
||
/** Specifies a `$lte` query condition. When called with one argument, the most recent path passed to `where()` is used. */ | ||
lte(val: number): this; | ||
lte(path: string, val: number): this; | ||
lte<KEY extends keyof FilterQuery<DocType>>(path: KEY, val: FilterQuery<DocType>[KEY]['$lte']): this; | ||
|
||
/** | ||
* Runs a function `fn` and treats the return value of `fn` as the new value | ||
|
@@ -1738,7 +1758,7 @@ declare module 'mongoose' { | |
|
||
/** Specifies a `$mod` condition, filters documents for documents whose `path` property is a number that is equal to `remainder` modulo `divisor`. */ | ||
mod(val: Array<number>): this; | ||
mod(path: string, val: Array<number>): this; | ||
mod<KEY extends keyof FilterQuery<DocType>>(path: KEY, val: FilterQuery<DocType>[KEY]['$mod']): this; | ||
|
||
/** The model this query was created from */ | ||
model: typeof Model; | ||
|
@@ -1751,15 +1771,15 @@ declare module 'mongoose' { | |
|
||
/** Specifies a `$ne` query condition. When called with one argument, the most recent path passed to `where()` is used. */ | ||
ne(val: any): this; | ||
ne(path: string, val: any): this; | ||
ne<KEY extends keyof FilterQuery<DocType>>(path: KEY, val: FilterQuery<DocType>[KEY]['$ne']): this; | ||
|
||
/** Specifies a `$near` or `$nearSphere` condition */ | ||
near(val: any): this; | ||
near(path: string, val: any): this; | ||
near<KEY extends keyof FilterQuery<DocType>>(path: KEY, val: FilterQuery<DocType>[KEY]['$near']): this; | ||
|
||
/** Specifies an `$nin` query condition. When called with one argument, the most recent path passed to `where()` is used. */ | ||
nin(val: Array<any>): this; | ||
nin(path: string, val: Array<any>): this; | ||
nin<KEY extends keyof FilterQuery<DocType>>(path: KEY, val: FilterQuery<DocType>[KEY]['$nin']): this; | ||
|
||
/** Specifies arguments for an `$nor` condition. */ | ||
nor(array: Array<FilterQuery<DocType>>): this; | ||
|
@@ -1795,7 +1815,7 @@ declare module 'mongoose' { | |
|
||
/** Specifies a `$regex` query condition. When called with one argument, the most recent path passed to `where()` is used. */ | ||
regex(val: string | RegExp): this; | ||
regex(path: string, val: string | RegExp): this; | ||
regex<KEY extends keyof FilterQuery<DocType>>(path: KEY, val: FilterQuery<DocType>[KEY]['$regex']): this; | ||
|
||
/** | ||
* Declare and/or execute this query as a remove() operation. `remove()` is | ||
|
@@ -1848,7 +1868,7 @@ declare module 'mongoose' { | |
|
||
/** Specifies an `$size` query condition. When called with one argument, the most recent path passed to `where()` is used. */ | ||
size(val: number): this; | ||
size(path: string, val: number): this; | ||
size<KEY extends keyof FilterQuery<DocType>>(path: KEY, val: FilterQuery<DocType>[KEY]['$size']): this; | ||
|
||
/** Specifies the number of documents to skip. */ | ||
skip(val: number): this; | ||
|
@@ -2084,13 +2104,24 @@ declare module 'mongoose' { | |
type TreatAsPrimitives = actualPrimitives | | ||
NativeDate | RegExp | symbol | Error | BigInt | Types.ObjectId; | ||
|
||
// This will -- when possible -- extract the original type of the subdocument in question | ||
type LeanSubdocument<T> = T extends (Types.Subdocument<Require_id<T>['_id']> & infer U) ? LeanDocument<U> : Omit<LeanDocument<T>, '$isSingleNested' | 'ownerDocument' | 'parent'>; | ||
|
||
type LeanType<T> = | ||
0 extends (1 & T) ? T : // any | ||
T extends TreatAsPrimitives ? T : // primitives | ||
T extends Types.Subdocument ? Omit<LeanDocument<T>, '$isSingleNested' | 'ownerDocument' | 'parent'> : // subdocs | ||
T extends Types.Subdocument ? LeanSubdocument<T> : // subdocs | ||
LeanDocument<T>; // Documents and everything else | ||
|
||
type LeanArray<T extends unknown[]> = T extends unknown[][] ? LeanArray<T[number]>[] : LeanType<T[number]>[]; | ||
// Used only when collapsing lean arrays for ts performance reasons: | ||
type LeanTypeOrArray<T> = T extends unknown[] ? LeanArray<T> : LeanType<T>; | ||
taxilian marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
type LeanArray<T extends unknown[]> = | ||
// By checking if it extends Types.Array we can get the original base type before collapsing down, | ||
// rather than trying to manually remove the old types. This matches both Array and DocumentArray | ||
T extends Types.Array<infer U> ? LeanTypeOrArray<U>[] : | ||
// If it isn't a custom mongoose type we fall back to "do our best" | ||
T extends unknown[][] ? LeanArray<T[number]>[] : LeanType<T[number]>[]; | ||
|
||
export type _LeanDocument<T> = { | ||
[K in keyof T]: LeanDocumentElement<T[K]>; | ||
|
@@ -2100,18 +2131,46 @@ declare module 'mongoose' { | |
// This way, the conditional type is distributive over union types. | ||
// This is required for PopulatedDoc. | ||
type LeanDocumentElement<T> = | ||
T extends unknown[] ? LeanArray<T> : // Array | ||
T extends Document ? LeanDocument<T> : // Subdocument | ||
T; | ||
0 extends (1 & T) ? T :// any | ||
T extends unknown[] ? LeanArray<T> : // Array | ||
T extends Document ? LeanDocument<T> : // Subdocument | ||
T; | ||
|
||
export type SchemaDefinitionType<T> = T extends Document ? Omit<T, Exclude<keyof Document, '_id' | 'id' | '__v'>> : T; | ||
|
||
// Helpers to simplify checks | ||
type IfAny<IFTYPE, THENTYPE> = 0 extends (1 & IFTYPE) ? THENTYPE : IFTYPE; | ||
type IfUnknown<IFTYPE, THENTYPE> = unknown extends IFTYPE ? THENTYPE : IFTYPE; | ||
|
||
// tests for these two types are located in test/types/lean.test.ts | ||
export type DocTypeFromUnion<T> = T extends (Document<infer T1, infer T2, infer T3> & infer U) ? | ||
[U] extends [Document<T1, T2, T3> & infer U] ? IfUnknown<IfAny<U, false>, false> : false : false; | ||
|
||
export type DocTypeFromGeneric<T> = T extends Document<infer IdType, infer TQueryHelpers, infer DocType> ? | ||
IfUnknown<IfAny<DocType, false>, false> : false; | ||
|
||
/** | ||
* Helper to choose the best option between two type helpers | ||
*/ | ||
export type _pickObject<T1, T2, Fallback> = T1 extends false ? T2 extends false ? Fallback : T2 : T1; | ||
|
||
/** | ||
* There may be a better way to do this, but the goal is to return the DocType if it can be infered | ||
* and if not to return a type which is easily identified as "not valid" so we fall back to | ||
* "strip out known things added by extending Document" | ||
* There are three basic ways to mix in Document -- "Document & T", "Document<ObjId, mixins, T>", | ||
* and "T extends Document". In the last case there is no type without Document mixins, so we can only | ||
* strip things out. In the other two cases we can infer the type, so we should | ||
*/ | ||
export type BaseDocumentType<T> = _pickObject<DocTypeFromUnion<T>, DocTypeFromGeneric<T>, false>; | ||
|
||
/** | ||
* Documents returned from queries with the lean option enabled. | ||
* Plain old JavaScript object documents (POJO). | ||
* @see https://mongoosejs.com/docs/tutorials/lean.html | ||
*/ | ||
export type LeanDocument<T> = Omit<_LeanDocument<T>, Exclude<keyof Document, '_id' | 'id' | '__v'> | '$isSingleNested'>; | ||
export type LeanDocument<T> = BaseDocumentType<T> extends Document ? _LeanDocument<BaseDocumentType<T>> : | ||
Omit<_LeanDocument<T>, Exclude<keyof Document, '_id' | 'id' | '__v'> | '$isSingleNested'>; | ||
|
||
export type LeanDocumentOrArray<T> = 0 extends (1 & T) ? T : | ||
T extends unknown[] ? LeanDocument<T[number]>[] : | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm very wary of using
infer
because it has caused some serious performance issues for us in the past. Can you please provide an example of why this type is helpful?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just a helper utility type, so you can always not use it if it causes performance issues. Example of use would just be any time you need to return a query or create one but want to assign it to a variable declared earlier.
How I was using it:
I certainly could have constructed the query differently, but I wanted to keep type checking as much as possible and it's much easier to do
QueryForModel<MyModel>
than it is to doQueryWithHelpers<ResultDoc[], ResultDoc, {}, DocType>
-- not to mention that you have to figure out what all of those are. My original plan was to just dotype MyQueryType = ReturnType<typeof MyModel.find>
but since the return type of the different.find
overrides aren't all identical that doesn't return a useful type.Since this is just a utility method I can pull it and put it in my own code -- just seemed like something that others would also find useful and since it's purely optional it didn't seem like something which would cost anything to put in. Your call, just let me know.