-
-
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
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
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.
LGTM on my end. ts-benchmark seems to succeed with latest TypeScript (5.8.3) and in this PR it goes over the instantiations limit by 500, which is fairly trivial, we can just bump the instantiations limit.
@hasezoey can you please re-review and see if this causes problems for Typegoose?
I found an issue here that I have to fix(for hydrated documents in schema, we are also creating projection for the methods - which is not correct). Trying to find a way to strip it before creating the projection. I let you know when it is finished. |
From what i can tell the Error with typescript 5.8.3src/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'.
Type 'ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>> extends any[] ? Default__v<...>[] : Default__v<...>' is not assignable to type '(FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; })[] | (FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; }) | null'.
Type 'Default__v<Require_id<FlattenMaps<ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>> extends Buffer<...> ? Binary : ObtainDocumentType<...> extends Document<...> ? Document<...> & ObtainDocumentType<...> : ObtainDocumentType<...> extends TreatAsPrimitives ? Trea...' is not assignable to type '(FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; })[] | (FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; }) | null'.
Type 'Default__v<Require_id<FlattenMaps<ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>> extends Buffer<...> ? Binary : ObtainDocumentType<...> extends Document<...> ? Document<...> & ObtainDocumentType<...> : ObtainDocumentType<...> extends TreatAsPrimitives ? Trea...' is not assignable to type '(FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; })[] | (FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; }) | null'.
Type 'Default__v<FlattenMaps<ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>> extends Buffer<...> ? Binary : ObtainDocumentType<...> extends Document<...> ? Document<...> & ObtainDocumentType<...> : ObtainDocumentType<...> extends TreatAsPrimitives ? TreatAsPrimitiv...' is not assignable to type '(FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; })[] | (FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; }) | null'.
Type 'Default__v<FlattenMaps<ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>> extends Buffer<...> ? Binary : ObtainDocumentType<...> extends Document<...> ? Document<...> & ObtainDocumentType<...> : ObtainDocumentType<...> extends TreatAsPrimitives ? TreatAsPrimitiv...' is not assignable to type '(FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; })[] | (FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; }) | null'.
Type '(FlattenMaps<ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>> extends Buffer<...> ? Binary : ObtainDocumentType<...> extends Document<...> ? Document<...> & ObtainDocumentType<...> : ObtainDocumentType<...> extends TreatAsPrimitives ? TreatAsPrimitives & Obtai...' is not assignable to type '(FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; })[] | (FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; }) | null'.
Type 'FlattenMaps<ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>> extends Buffer<...> ? Binary : ObtainDocumentType<...> extends Document<...> ? Document<...> & ObtainDocumentType<...> : ObtainDocumentType<...> extends TreatAsPrimitives ? TreatAsPrimitives & Obtain...' is not assignable to type '(FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; })[] | (FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; }) | null'.
Type 'FlattenMaps<ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>> extends Buffer<...> ? Binary : ObtainDocumentType<...> extends Document<...> ? Document<...> & ObtainDocumentType<...> : ObtainDocumentType<...> extends TreatAsPrimitives ? TreatAsPrimitives & Obtain...' is not assignable to type 'FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; }'.
Property '__v' is missing in type 'FlattenMaps<ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>> extends Buffer<...> ? Binary : ObtainDocumentType<...> extends Document<...> ? Document<...> & ObtainDocumentType<...> : ObtainDocumentType<...> extends TreatAsPrimitives ? TreatAsPrimitives & Obtain...' but required in type '{ __v: number; }'.
Type 'Default__v<FlattenMaps<ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>> extends Buffer<...> ? Binary : ObtainDocumentType<...> extends Document<...> ? Document<...> & ObtainDocumentType<...> : ObtainDocumentType<...> extends TreatAsPrimitives ? TreatAsPrimitiv...' is not assignable to type 'FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; }'.
Type '(FlattenMaps<ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>> extends Buffer<...> ? Binary : ObtainDocumentType<...> extends Document<...> ? Document<...> & ObtainDocumentType<...> : ObtainDocumentType<...> extends TreatAsPrimitives ? TreatAsPrimitives & Obtai...' is not assignable to type 'FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; }'.
Type 'FlattenMaps<ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>> extends Buffer<...> ? Binary : ObtainDocumentType<...> extends Document<...> ? Document<...> & ObtainDocumentType<...> : ObtainDocumentType<...> extends TreatAsPrimitives ? TreatAsPrimitives & Obtain...' is not assignable to type 'FlattenMaps<any> & Required<{ _id: unknown; }> & { __v: number; }'.
Property '__v' is missing in type 'FlattenMaps<ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>> extends Buffer<...> ? Binary : ObtainDocumentType<...> extends Document<...> ? Document<...> & ObtainDocumentType<...> : ObtainDocumentType<...> extends TreatAsPrimitives ? TreatAsPrimitives & Obtain...' but required in type '{ __v: number; }'.
Type 'Default__v<FlattenMaps<ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>> extends Buffer<...> ? Binary : ObtainDocumentType<...> extends Document<...> ? Document<...> & ObtainDocumentType<...> : ObtainDocumentType<...> extends TreatAsPrimitives ? TreatAsPrimitiv...' is not assignable to type '{ __v: number; }'.
Type '(FlattenMaps<ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>> extends Buffer<...> ? Binary : ObtainDocumentType<...> extends Document<...> ? Document<...> & ObtainDocumentType<...> : ObtainDocumentType<...> extends TreatAsPrimitives ? TreatAsPrimitives & Obtai...' is not assignable to type '{ __v: number; }'.
Property '__v' is missing in type 'FlattenMaps<ObtainDocumentType<any, DocumentType<InstanceType<U & Func>>, ResolveSchemaOptions<DefaultSchemaOptions>> extends Buffer<...> ? Binary : ObtainDocumentType<...> extends Document<...> ? Document<...> & ObtainDocumentType<...> : ObtainDocumentType<...> extends TreatAsPrimitives ? TreatAsPrimitives & Obtain...' but required in type '{ __v: number; }'.
103 const compiledModel: mongoose.Model<any> = modelFn(name, buildSchema(cl, mergedOptions));
~~~~~~~~~~~~~
../mongoose/types/index.d.ts:144:13
144 : T & { __v: number };
~~~
'__v' is declared here.
../mongoose/types/index.d.ts:144:13
144 : T & { __v: number };
~~~
'__v' is declared here.
../mongoose/types/index.d.ts:144:13
144 : T & { __v: number };
~~~
'__v' is declared here. Note that i have already tried changing I still dont understand what this PR changed that the types for But there are multiple fixes i can do on typegoose:
The cause for why this is happening in typegoose might be because When fixing the above error (which is internal only and so does not actually matter much), another comes up in the types tests: Error: No overload matches this call.
Overload 1 of 3, '(from: Model<any, any, any, any, any, any>, cl: typeof Child, options?: IModelOptions | undefined): ReturnModelType<typeof Child, BeAnObject>', gave the following error.
Argument of type 'ReturnModelType<typeof Base, BeAnObject>' is not assignable to parameter of type 'Model<any, any, any, any, any, any>'.
The types returned by 'insertMany(...)' are incompatible between these types.
Type 'Promise<InsertManyResult<Base & Required<{ _id: string; }>> & { mongoose: { validationErrors: (CastError | ValidatorError)[]; results: (Object | ... 1 more ... | DocumentType<...>)[]; }; }>' is not assignable to type 'Promise<InsertManyResult<any> & { mongoose: { validationErrors: (CastError | ValidatorError)[]; results: any[]; }; }>'.
Type 'InsertManyResult<Base & Required<{ _id: string; }>> & { mongoose: { validationErrors: (CastError | ValidatorError)[]; results: (Object | ... 1 more ... | DocumentType<...>)[]; }; }' is not assignable to type 'InsertManyResult<any> & { mongoose: { validationErrors: (CastError | ValidatorError)[]; results: any[]; }; }'.
Types of property 'insertedIds' are incompatible.
Type '{ [key: number]: string; }' is not assignable to type '{ [key: number]: ObjectId; }'.
'number' index signatures are incompatible.
Type 'string' is not assignable to type 'ObjectId'.
Overload 2 of 3, '(from: Model<any, any, any, any, any, any>, cl: typeof Child, value?: string | undefined): ReturnModelType<typeof Child, BeAnObject>', gave the following error.
Argument of type 'ReturnModelType<typeof Base, BeAnObject>' is not assignable to parameter of type 'Model<any, any, any, any, any, any>'.
Overload 3 of 3, '(from: Model<any, any, any, any, any, any>, cl: typeof Child, value?: string | undefined, options?: IModelOptions | undefined): ReturnModelType<typeof Child, BeAnObject>', gave the following error.
Argument of type 'ReturnModelType<typeof Base, BeAnObject>' is not assignable to parameter of type 'Model<any, any, any, any, any, any>'. ts(2769)
368 | const BaseModel = typegoose.getModelForClass(Base);
369 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
370 | const ChildModel = typegoose.getDiscriminatorModelForClass(BaseModel, Child); But this confuses me even more, as the PR did not actually touch any of this, right? (it still works on master) (reviewed at d94f1b7) |
I took a look and if you remove all the nested paths logic Typegoose compiles fine. type ArrayOperators = { $slice: number | [number, number]; $elemMatch?: undefined } | { $elemMatch: object; $slice?: undefined };
/**
* This Type Assigns `Element | undefined` recursively to the `T` type.
* if it is an array it will do this to the element of the array, if it is an object it will do this for the properties of the object.
* `Element` is the truthy or falsy values that are going to be used as the value of the projection.(1 | true or 0 | false)
* For the elements of the array we will use: `Element | `undefined` | `ArrayOperators`
* @example
* type CalculatedType = Projector<{ a: string, b: number, c: { d: string }, d: string[] }, true>
* type CalculatedType = {
a?: true | undefined;
b?: true | undefined;
c?: true | {
d?: true | undefined;
} | undefined;
d?: true | ArrayOperators | undefined;
}
*/
type Projector<T, Element> = T extends Array<infer U>
? Projector<U, Element> | ArrayOperators
: T extends object
? {
[K in keyof T]?: T[K] extends object ? Projector<T[K], Element> | Element : Element;
}
: Element;
type _IDType = { _id?: boolean | 1 | 0 };
type InclusionProjection<T> = Projector<T, true | 1> & _IDType;
type ExclusionProjection<T> = Projector<T, false | 0> & _IDType;
type ProjectionUnion<T> = InclusionProjection<T> | ExclusionProjection<T>;
/**
* This types are equivalent to primary types
*/
type SpecialTypes = DateSchemaDefinition | Date | ObjectIdSchemaDefinition | globalThis.Date | DateConstructor | Types.Buffer | Types.Decimal128 | Types.Buffer | BooleanSchemaDefinition | NumberSchemaDefinition;
type Replacer<T> = T extends SpecialTypes ? string : T;
/**
* Date type is like a Primitive type for us and we do not want to project something inside it.
* ObjectId is also similar.
*/
type ReplaceSpecialTypes<T> = T extends SpecialTypes
? Replacer<T>
: T extends Array<infer U>
? Array<ReplaceSpecialTypes<U>>
: T extends object
? {
[K in keyof T]?: ReplaceSpecialTypes<T[K]>;
}
: Replacer<T>;
export type ProjectionType<T> = (ProjectionUnion<ReplaceSpecialTypes<T>> & AnyObject) | string; I'll do some more digging and see what I can find. |
types: stricter projection typing with 1-level deep nesting
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.