-
-
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 6 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> ? | ||
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. I'm very wary of using 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. 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: function applyQueryRestrictions(userQ: mongoose.QueryForModel<typeof UserModel>, requiredPerm?: string) {
if (requiredPerm) {
userQ.where('permissions', requiredPerm);
}
userQ.select('passInvalid username call first last email modified permissions')
.select('verifiedCall.callsign verifiedCall.displayName verifiedCall.sigdate')
.select('providers.type');
userQ.populate({
path: 'veDocs',
select: 'isTeamLead active deleted number display_name vec call',
populate: {
path: 'ulsRecord',
select: 'callsign frn license_class expired_date',
}
});
return userQ;
} 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 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. |
||
_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,30 @@ 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]>[]; | ||
|
||
type _WithoutFunctions<T extends {}> = { | ||
[K in keyof T]: [0] extends [1 & T[K]] ? K : // keep any | ||
T[K] extends Function ? never : K; | ||
}; | ||
type StripFunctions<T extends {}> = Pick<T, _WithoutFunctions<T>[keyof T]>; | ||
taxilian marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
export type _LeanDocument<T> = { | ||
[K in keyof T]: LeanDocumentElement<T[K]>; | ||
|
@@ -2100,9 +2137,10 @@ 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; | ||
|
||
|
@@ -2111,7 +2149,8 @@ declare module 'mongoose' { | |
* 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> = T extends mongoose.HydratedDocument<infer DocType, infer MethodsOverrides, infer TVirtuals> ? _LeanDocument<StripFunctions<DocType>> : | ||
Omit<_LeanDocument<StripFunctions<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.
Good change 👍
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.
Well, I thought it was, but it turns out it breaks a few cases -- specifically cases where someone
extends Document
.I'm still working through the tests
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.
After some tinkering I've made the following changes to
toJSON
andtoObject
:The downside to this is that if your interface extends Document instead of using the new recommended way then you'll get a
any
type out of both methods unless you manually provide a type to the generic.On the positive side, though, I could not find any way to use
this
in the return type which did not cause circular reference issues with my code in multiple places -- this change fixes that. Additionally, the "worst case" is that you get anany
type out, so it won't break existing code it just may not catch issues that someone would expect it to catch. That can be mitigated by either migrating to the recommended method or manually providing the type.