Skip to content
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: improve populate type narrowing #11503

Merged
merged 8 commits into from Mar 11, 2022
32 changes: 31 additions & 1 deletion test/types/populate.test.ts
@@ -1,7 +1,7 @@
import { Schema, model, Document, PopulatedDoc, Types, HydratedDocument } from 'mongoose';
// Use the mongodb ObjectId to make instanceof calls possible
import { ObjectId } from 'mongodb';
import { expectError } from 'tsd';
import { expectAssignable, expectError, expectType } from 'tsd';

interface Child {
name: string;
Expand Down Expand Up @@ -149,4 +149,34 @@ function gh11321(): void {
return 'foo';
}
});
}

function gh11496() {
interface Friend {
blocked: boolean
}
const FriendSchema = new Schema<Friend>({
blocked: Boolean
});

interface User {
friends: Types.ObjectId[];
}
const UserSchema = new Schema<User>({
friends: [{ type: Schema.Types.ObjectId, ref: 'friends' }]
});
const Users = model<User>('friends', UserSchema);


Users.findOne({}).populate('friends').then(user => {
expectType<Types.ObjectId | undefined>(user?.friends[0]);
expectError(user?.friends[0].blocked);
expectError(user?.friends.map(friend => friend.blocked));
});

Users.findOne({}).populate<{friends: Friend[]}>('friends').then(user => {
expectAssignable<Friend | undefined>(user?.friends[0]);
user?.friends[0].blocked;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do expectType<boolean> here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So is it now fixed?

user?.friends.map(friend => friend.blocked);
});
}
4 changes: 2 additions & 2 deletions test/types/queries.test.ts
Expand Up @@ -18,11 +18,11 @@ const schema: Schema<ITest, Model<ITest, QueryHelpers>, {}, QueryHelpers> = new
endDate: Date
});

schema.query._byName = function(name: string): QueryWithHelpers<any, ITest, QueryHelpers> {
schema.query._byName = function(name: string): QueryWithHelpers<ITest[], ITest, QueryHelpers> {
return this.find({ name });
};

schema.query.byName = function(name: string): QueryWithHelpers<any, ITest, QueryHelpers> {
schema.query.byName = function(name: string): QueryWithHelpers<ITest[], ITest, QueryHelpers> {
expectError(this.notAQueryHelper());
return this._byName(name);
};
Expand Down
3 changes: 2 additions & 1 deletion types/index.d.ts
Expand Up @@ -1813,7 +1813,8 @@ declare module 'mongoose' {

type QueryWithHelpers<ResultType, DocType, THelpers = {}, RawDocType = DocType> = Query<ResultType, DocType, THelpers, RawDocType> & THelpers;

type UnpackedIntersection<T, U> = T extends (infer V)[] ? (V & U)[] : T & U;
type UnpackedIntersection<T, U> = T extends (infer A)[] ? (A & U)[] : keyof U extends never ? T & U: { [P in keyof U]: U[P] extends (infer B)[] ? (B & T)[] : U[P] & T };
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the last bit be something like T[P] & U[P]? I think T should come first, and then we should be merging T's keys as well as opposed to merging T into U's keys.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check the new commit


type UnpackedIntersectionWithNull<T, U> = T extends null ? UnpackedIntersection<T, U> | null : UnpackedIntersection<T, U>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need UnpackedIntersectionWithNull type?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We introduced UnpackedIntersectionWithNull to fix #11041. Although it looks like the test for that issue still passes with this change, so looks like we don't need it after all.


type ProjectionFields<DocType> = {[Key in keyof Omit<LeanDocument<DocType>, '__v'>]?: any} & Record<string, any>;
Expand Down