Wrong ProjectionType when suppressing _id field from the projection
Closed this issue · 6 comments
Hi!
The ProjectionType util doesn't support the suppression of _id field from the projection.
Example:
const projection = {
_id: 0,
firstName: 1
}
type UserProjected = ProjectionType<UserDocument, typeof projection>;
// UserProjected type is { _id: ObjectId, firstName: string}
// However, UserProjected type should be {firstName: string}
Thanks!
That's because the ProjectionType
util returns a WithId
generic.
Lines 102 to 109 in 0c3d992
It would be very complex to detect if you want to suppress _id
with _id: 0
so they probably decided to always return it on the type and I think it's very unusual for your to decide to not project the _id
since you can just omit it before returning it. My suggestion would be to just omit it on the UserProjected
using Omit<ProjectionType<UserDocument, typeof projection>, "_id">
but maybe that's open for discussion.
Let me write here one experiment I was doing yesterday
type User = {
_id: number;
firstName: number;
};
type GetProjection<
Schema,
Projection extends Record<string, 0 | 1> | undefined,
> = undefined extends Projection
? 'undefined'
: Projection extends { _id: 0 }
? 'withoutId'
: 'withId';
const projection1 = {
_id: 0,
firstName: 1,
} as const;
const projection2 = {
_id: 1,
firstName: 1,
} as const;
type UserProjected1 = GetProjection<User, typeof projection1>; // type is 'withoutId'
type UserProjected2 = GetProjection<User, typeof projection2>; // type is 'withId'
type UserProjected3 = GetProjection<User, undefined>; // type is 'undefined'
I was trying to substitute the strings 'undefined', 'withId', 'withoutId' by the real types, but it was not working to me. Maybe we could try to figure out what is failing with this approach when using the real types. Any idea?
Check if this works for you:
export type ProjectionType<
TSchema extends BaseSchema,
Projection extends
| Partial<Record<Join<NestedPaths<WithId<TSchema>, []>, ".">, number>>
| undefined
> = undefined extends Projection
? WithId<TSchema>
: Projection extends { _id: 0 }
? Omit<DeepPick<TSchema, "_id" | (keyof Projection & string)>, "_id">
: WithId<DeepPick<TSchema, "_id" | (keyof Projection & string)>>;
const projection = {
_id: 0,
firstName: 1,
} as const;
type UserProjected = ProjectionType<UserDocument, typeof projection>;
Yes, that's working.
I was trying things like
export type ProjectionType<
TSchema extends BaseSchema,
Projection extends
| Partial<Record<Join<NestedPaths<WithId<TSchema>, []>, ".">, number>>
| undefined
> = undefined extends Projection
? WithId<TSchema>
: Projection extends { _id: 0 }
? DeepPick<TSchema,(keyof Projection & string)>
: WithId<DeepPick<TSchema, "_id" | (keyof Projection & string)>>;
without success.
But your approach it's working and it's simple.
Thanks!
@avaly what do you think about the proposed solution?
Hi folks!
It's truly wonderful for our team to see so many folks taking an interest in our project in the first place.
I would approve a PR with the proposed changes in this GHI. If no one has time to open one, I'll get around to making this change once my schedule allows it.
Thanks!
@avaly I've put some tests and modified the documentation.
I think the code is working perfectly. However, there is a breaking change when using a projection.
With the new behaviour we need to identify if we are including or excluding (1 or 0) one field in the projection. Therefore, we must use as const
to get the correct type.