-
Notifications
You must be signed in to change notification settings - Fork 23
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
refactor: Improve projection type safety #712
refactor: Improve projection type safety #712
Conversation
This will cause a merge conflict with #711 - if that PR is merged first, we'll need to update the projection type signature in this PR. |
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.
This looks amazing and so simple.
I wonder if we need to treat this as a breaking change since we changed the arguments to the Projection
type, which is exported.
@@ -78,32 +78,32 @@ export interface Model<TSchema extends BaseSchema, TOptions extends SchemaOption | |||
options?: Omit<FindOptions<TSchema>, 'limit' | 'projection' | 'skip' | 'sort'> | |||
) => Promise<boolean>; | |||
|
|||
find: <TProjection extends Projection<TSchema> | undefined>( | |||
find: <TProjection extends Projection<TProjection, TSchema> | undefined>( |
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.
TIL that you can use the same generic identifier in both the left side and right side of the extends
. How is that even possible? Isn't there some kind of recursion limit?
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.
Circular generic constraints are really interesting and make quite a bit of sense, but they only currently work when they're non-immediate. Definitely pushes the boundaries of what TypeScript is able to deliver.
Here is a really good thread if you want to read a bit more about them.
And there is a recursion limit, but it's pretty sizable and we'd hit that when building the schema path options already if we were going to hit it, so there's no additional risk here.
I just checked our private codebase and we definitely use |
BREAKING CHANGE: Changes arguments of `Projection` type to exclude extraneous properties
4ef0675
to
30e1bbe
Compare
@avaly Rebased and marked as breaking 👍 |
projection: { | ||
// @ts-expect-error `nested.baz` is not present on the schema | ||
'nested.baz': 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.
Can you please combine some valid properties with this invalid one here?
@joshuat Here's a edge case with this change found in our private codebase: async function doSomething<
TProjection extends Projection<TProjection, FooDocument>
>(projection: TProjection): void {
const items = await Foo.find(
{ identifier: { $in: ['foo', 'bar'] } },
{
projection: {
...projection,
identifier: 1
}
}
);
// do something with items
} This throws an error on the
If I add Any clue if we can solve this case? |
@joshuat I found another broken case from our private repo. Here's a simplified version of the code that breaks: async function iterate<
TDocument extends BaseSchema,
TOptions extends SchemaOptions<TDocument>,
TProjection extends Projection<TProjection, TDocument>
>(
params: IterateParams<TDocument, TOptions, TProjection>,
mutator: (doc: ProjectionType<TDocument, TProjection>) => unknown
): Promise<void> {}
await iterate(
{
match: { type: JobType.VIONLABS },
model: Job
},
async job => {
job.ref; // TS throws an error because it does not know `ref` (a valid property on the model)
}
); The actual error seems to be:
It looks like without sending a |
11f9414
to
d1e1366
Compare
@avaly Hmmm. Appreciate you surfacing these - we're not using Papr in this way, so I wouldn't have caught them myself. I'll work through these over the week but mostly over the weekend and add some test cases to make sure we're covered from all directions. |
I tried to resolve this a few times over the past few weeks but got nowhere - it seems like we're hitting a hard wall around exact type restrictions, specifically on non-"fresh" objects that already have a type assignment. Once microsoft/TypeScript#12936 is resolved we can give this another try, but for now, I think we have to accept maybe we're at the limits of what we're able to enforce with TypeScript. Sorry everyone - we got close! |
Rather than using the built-in
Partial
utility, we're adding anExactPartial
utility that allows us to simulate the behavior ofPartial
while excluding extraneous properties.BREAKING CHANGE: Changes arguments of
Projection
type to exclude extraneous propertiesCloses: #683