-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
fix: Strict Projection Object Typing #15327
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
base: master
Are you sure you want to change the base?
fix: Strict Projection Object Typing #15327
Conversation
@vkarpov15 Hey, can you take a look at this? :) |
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.
Pull Request Overview
This PR fixes strict projection object typing issues as described in the related GitHub issue. Key changes include updating the chaining type test for schema plugins and enhancing TS tests for projection validations in queries.
Reviewed Changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
File | Description |
---|---|
test/types/schema.test.ts | Updated plugin chaining test to validate strict type preservation. |
test/types/queries.test.ts | Added and updated TS assertions for projection types and query validations. |
Test.find({}, { child: 1 }); // Dot notation should be able to use a combination with objects | ||
Test.find({}, { 'docs.profiles': { name: 1 } }); // should support a combination of dot notation and objects | ||
expectError(Test.find({}, { 'docs.profiles': { name: 'aa' } })); // should support a combination of dot notation and objects | ||
expectError(Test.find({}, { endDate: { toString: 1 } })); |
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'd love to see a test of using as ProjectionType<>
to explicitly cast in case the type checking is incorrect for some reason (or if user is dealing with 5-level deep path)
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.
@vkarpov15
something like this you mean?
Test.find({}, { docs: { unknownParams: 1 }} as ProjectionType<ITest>);
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.
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.
Looks OK to me, but makes typegoose's types not work anymore. Some of the errors i could workaround, but the last one i couldnt find the cause of, also bisecting this PR only caused errors everywhere.
As a side question, why change some unknown
's to any
s?
Typegoose Errors in more detail
Errors without any modifications:
$ tsc -p tsconfig.buildTests.json
src/typegoose.ts:103:9 - error TS2322: Type 'Model<ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>>, ... 4 more ..., Schema<...>>' is not assignable to type 'Model<any, {}, {}, {}, any, any>'.
The types returned by 'findById(...).exec()' are incompatible between these types.
Type 'Promise<(ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>> extends any[] ? Default__v<...>[] : Default__v<...>) | null>' is not assignable to type 'Promise<(FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; })[] | (FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; }) | null>'.
Type '(ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>> extends any[] ? Default__v<...>[] : Default__v<...>) | null' is not assignable to type '(FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; })[] | (FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; }) | null'.
103 const compiledModel: mongoose.Model<any> = modelFn(name, buildSchema(cl, mergedOptions));
~~~~~~~~~~~~~
src/typeguards.ts:21:12 - error TS2677: A type predicate's type must be assignable to its parameter's type.
Type 'Array<DocumentType<NonNullable<T>>>' is not assignable to type 'Array<Ref<T, S>>'.
Type 'DocumentType<NonNullable<T>>' is not assignable to type 'Ref<T, S>'.
Type 'DocumentType<NonNullable<T>>' is not assignable to type 'DocumentType<T, BeAnObject>'.
Type 'DocumentType<NonNullable<T>>' is not assignable to type 'Document<unknown, BeAnObject, T>'.
21 ): docs is mongoose.Types.Array<DocumentType<NonNullable<T>>>;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/typeguards.ts:27:102 - error TS2677: A type predicate's type must be assignable to its parameter's type.
Type 'DocumentType<NonNullable<T>>[]' is not assignable to type 'Ref<T, S>[]'.
Type 'DocumentType<NonNullable<T>>' is not assignable to type 'Ref<T, S>'.
Type 'DocumentType<NonNullable<T>>' is not assignable to type 'DocumentType<T, BeAnObject>'.
Type 'DocumentType<NonNullable<T>>' is not assignable to type 'Document<unknown, BeAnObject, T>'.
27 export function isDocumentArray<T, S extends RefType>(docs: Ref<T, S>[] | null | undefined): docs is DocumentType<NonNullable<T>>[];
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Found 3 errors in 2 files.
Errors Files
1 src/typegoose.ts:103
2 src/typeguards.ts:21
error Command failed with exit code 2.
Errors with modifictions:
$ tsc -p tsconfig.buildTests.json
src/typegoose.ts:103:9 - error TS2322: Type 'Model<ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>>, ... 4 more ..., Schema<...>>' is not assignable to type 'Model<any, {}, {}, {}, any, any>'.
The types returned by 'findById(...).exec()' are incompatible between these types.
Type 'Promise<(ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>> extends any[] ? Default__v<...>[] : Default__v<...>) | null>' is not assignable to type 'Promise<(FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; })[] | (FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; }) | null>'.
Type '(ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>> extends any[] ? Default__v<...>[] : Default__v<...>) | null' is not assignable to type '(FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; })[] | (FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; }) | null'.
103 const compiledModel: mongoose.Model<any> = modelFn(name, buildSchema(cl, mergedOptions));
~~~~~~~~~~~~~
Found 1 error in src/typegoose.ts:103
error Command failed with exit code 2.
Modifications:
diff --git a/src/typeguards.ts b/src/typeguards.ts
index 52886460..7e62e66c 100644
--- a/src/typeguards.ts
+++ b/src/typeguards.ts
@@ -18,13 +18,13 @@ export function isDocument<T, S extends RefType>(doc: Ref<T, S> | null | undefin
*/
export function isDocumentArray<T, S extends RefType>(
docs: mongoose.Types.Array<Ref<T, S>> | null | undefined
-): docs is mongoose.Types.Array<DocumentType<NonNullable<T>>>;
+): docs is mongoose.Types.Array<DocumentType<T>>;
/**
* Check if the given array is fully populated
* Only returns "true" if all members in the array are populated
* @param docs The Array of Refs with uncertain type
*/
-export function isDocumentArray<T, S extends RefType>(docs: Ref<T, S>[] | null | undefined): docs is DocumentType<NonNullable<T>>[];
+export function isDocumentArray<T, S extends RefType>(docs: Ref<T, S>[] | null | undefined): docs is DocumentType<T>[];
export function isDocumentArray(docs: Ref<any, any>[] | null | undefined): unknown {
// its "any" & "unknown" because this is not listed as an overload
return Array.isArray(docs) && docs.every((v) => isDocument(v));
Note that everything runs fine without this PR.
Also finally, with this PR has compile times of ~30s and also kinda high resource (cpu, ram) usage, though this might just be because its taking error paths.
Normal time is around ~7s for typegoose (before this PR) and way lower resource usage.
Typegoose at b3ad7940b96baf9b3a46573bb417479be8bf94d8.
I dont really see how just changing QueryOptions
projection
could cause those errors. (i already tried reverting some of the other changes regarding unknown
-> any
to rule them out)
As a final note, i think some of those types should maybe live in their own file to be better organized than in big type files, though that might not be in scope for this PR.
EDIT: sorry, i had some issues while posting this, so the original message was incomplete and i didnt know it was posted, hence my next (hidden) message
types/query.d.ts
Outdated
@@ -128,7 +128,7 @@ declare module 'mongoose' { | |||
updatedAt?: boolean; | |||
} | |||
|
|||
interface QueryOptions<DocType = unknown> extends | |||
interface QueryOptions<DocType = any> extends |
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.
Isnt DocType
now unused here again?
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.
That is true. QueryOptions
is used in lots of places.
The projection is passed like this, I am not sure why the projection is used in this interface(or if anyone uses this, instead of passing it as the second - because you are already passing the projection as one argument). it causes some circular reference as well.
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.
@hasezoey Just added a new commit. leaving the projection
in the QueryOptions
as it was before(not using ProjectionType
).
I am trying to find out why Typegoose is facing errors, the one that I get is this one:
Type 'Model<ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>>, ... 4 more ..., Schema<...>>' is not assignable to type 'Model<any, {}, {}, {}, any, any>'.
The types returned by 'findById(...).exec()' are incompatible between these types.
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.
Yes that is the error i could not figure out why it was happening.
I mean i know that typegoose is not 100% correct with mongoose types, but it worked before this PR (and on master) and this error just seems unrelated.
@vkarpov15 After pulling master, the number of instantiation exceeds. TypeScript Diagnostics Comparison
|
For building the Projection, we need the interface, not the DocType
Summary
Fixes this issue: #13840
This was a PR which was merged and then reverted for releasing of 8.1
Checkout the PR, as this is already once reviewed and discussed: #13993
I just rebased the branch and pushed a commit to fix the TS tests.