-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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: Refactor LeanDocument type #11767
Conversation
9d71615
to
716e93e
Compare
oh please please please don't do this. This does not fix leandocument -- it neuters it. If you can give me some specific tests that you want to ensure work I will make sure they work without breaking everything that I spent days making sure worked. I promise there are reasons for all of the things that you just pulled out -- every single one of them. I would not have spent a week making sure they all worked otherwise. |
Hello @taxilian ! |
@vkarpov15 @Uzlopak Please don't merge this PR until @taxilian explain why these changes are bad to have. |
The whole reason behind the "BaseDocumentType" abstraction is that you can't always get an accurate representation of the underlying type simply by subtracting the members that Document adds from the original. This makes it possible to get the original interface that was used to create the document instead of just wildly guessing and hoping that nobody did something unexpected like reusing something that document normally provides itself. It gives you a lot more accuracy and also makes types much more readable -- at least when following the normal patterns. If you read the getBaseDocumentType unit test which you completely slashed that should be clear -- there is no other way that you can actually get back the underlying type which you started with. That said, this code isn't actually being used at all in the particular test that started the other thread, so I'm still working through your fix while I fix it in a way which doesn't undo all my hard work and make mongoose impossible to use with my code without providing my own version of the types (which I've been doing for years and would prefer not to do) |
So the goal of
What this code does is instead says "Hey, I have an interface that I'll strip some array types out of and otherwise leave it alone". It basically doesn't try to pull out any of the things that LeanDocument was intended to pull out and removes 90% of the utility. I have a fix for the original issue in #11761 mostly done which doesn't remove all the functionality, but I get the feeling there are additional things we should consider so if I can get any other tests to fix at the same time I'm happy to look at those as well. If there is a better place we can chat about this I'm happy to do so. |
So honestly I wonder if the best / easiest way to address this is to just change the toObject return value like so:
We could make LeanDocument always add a _id but in some cases it may be used on a subdocument which may not have a _id (I always turn them off in my documents as they add bloat and are almost never needed for my stuff, but I don't know how common that is). It seems like it might be better to only assume they will be there on the root document, so the easiest way would be to just modify lean() and toObject to use Require_id and add the _id if it isn't there. @mohammad0-0ahmad and/or @vkarpov15 thoughts? Also, I don't understand why this would be new because the branch of types which I added isn't even being triggered for this test case..... but this is the test I'm using:
|
AHH! I see why it happened; because I changed the return type of |
See #11769 |
With all you said @taxilian, I don't really get the reason why mongoose must have the mentioned types.
|
I am leaving the final word for @vkarpov15 @Uzlopak to check both and merge the better one. |
@mohammad0-0ahmad given that the tests that you pulled out all specifically test a particular aspect of LeanDocument, yes if you pull that aspect out and then test only those things then of course the tests will pass -- that's the whole point of unit tests is to break down a problem into smaller pieces so you can test that unit of code. Just because you can test a unit of code separately does not mean that the unit of code is not needed! It also doesn't change the fact that even completely ignoring those tests this PR fundamentally breaks LeanDocument by no longer removing the things added by Document! Since it is apparently necessary I will make some tests to help demonstrate -- though if you actually look at the type output it should be pretty clear the difference, which is significant. (also, I apologize I'm dealing with a minor catastrophe at work right now so taking time to do this is frustrating me more than it actually should, which is definitely not your fault -- trying to compensate, but having these removed would be absolutely disasterous for my usage of mongoose, which is why I spent most of a week writing these improvements and testing them in the first place. I don't have time to do this right now, but I also can't afford to have this discarded because the purpose wasn't understood.) |
@taxilian as I said I've pulled out them because these tests needs the mentioned types. |
I do see that I misunderstood a few pieces of what is actually happening -- I'm still pretty sure that this will be a problem for my code, and it definitely results in less readable output in the IDE since it doesn't find its way back to the original types. I need to run this through all the tests with my code to see where it falls down which will take me a few hours -- but it's definitely fair for you to want me to check my assumptions, no matter how frustrated I am by other things or emotionally invested in my changes I may be =] |
Yeah, with this change there are now 37 errors in my codebase all due to LeanDocument related types. Unfortunately none of them are easy to track down -- it may take me a few hours to distill this into an explainable case / unit test. Things like |
@taxilian Can you just provide code snippet to one of them ? |
The tricky bit is that I'm using an extra abstraction, so I actually define my schemas as a class with decorators -- it's a really nice system which lets me define things only once (instead of needing to define the types and schema separately) and also easily find the actual implementation of any bit of code using the browser. As nice as that is, I don't want to make compatibility with my abstraction the foundation of my argument for doing things a particular way if I can avoid it, so I want to try to reproduce this without my abstraction, but that requires a more contrived example, which is why it's going to take some time. |
https://github.com/gradecam/mongoose-decorators-ts/tree/feature/mongoose6 - this is the project, but I am pretty confident that I can demonstrate the issues in a more specific way. |
I'm still reviewing this PR and #11769, I just wanted to make a quick comment about The reason why we have So adding |
In this PR, I think we don't need to add Require_id to the result of toObject(), |
Okay, here is a simple test that shows one issue, though it's pretty contrived: interface ThingType {
name: string;
}
const thingSchema = new Schema<ThingType>({
name: Schema.Types.String
});
const ThingModel = model('Thing', thingSchema);
type ThingDocument = HydratedDocument<ThingType>;
// or
type ThingyDocument = InstanceType<typeof ThingModel>;
function AcceptLeanDoc(val: LeanDocument<ThingDocument>) {
// This should fail but it thinks the type is valid
val.populate('');
} Your type isn't actually constructing a lean type, it's starting with a type that is already lean -- which is great when you can do that, but there are cases where you can't or it's impractical. At best you have an extra type you have to pass around everywhere. |
types/document.d.ts
Outdated
@@ -132,7 +132,7 @@ declare module 'mongoose' { | |||
getChanges(): UpdateQuery<this>; | |||
|
|||
/** The string version of this documents _id. */ | |||
id?: any; | |||
id?: string; |
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 is a hard no. This would break anyone's code who uses extends Document
with an id
field that isn't a string.
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 would break anyone's code who uses extends Document with an id field that isn't a string.
But then we have to update docs because it says it will be string.
Or users can omit id type before in type overriding case.
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.
You can do new Schema({ id: Number }), that's perfectly valid in Mongoose. Mongoose just overrides the default id virtual.
So I guess there exists the possibility that you don't see it as a needed thing to use LeanDocument on a HydratedDocument type and have it create the result of .lean(), but this would definitely be a breaking change since previously it did attempt to do that and now it doesn't |
Yes, you can do that -- it requires passing around an extra type, though, and it is definitely a breaking change. I don't know if anyone besides me is relying on it -- it would break all of my code, though I could probably work around it, it would just add a ton of complexity. It fundamentally changes how LeanDocument works and I'm not at all confident that we aren't using it somewhere else to do what it was originally intended to do -- convert a Document into a lean version of that document |
I think Document third generic "DocType" is ThingType interface in your example, which is the default value for toObject Fn. |
ad1827c
to
1b20863
Compare
@vkarpov15 I am testing TS performance locally, and I got the following result, Do you have any idea why I got that? I am wondering if this workflow is returns consistent benchmark results. |
It's worth noting that even as relatively good as this benchmark is (and I found it super helpful when I was working on things) the results are really inconsistent across different systems with different loads. Just on my laptop I got significantly different numbers when I ran things e.g. 30 minutes later, even if it was the same thing. Presumably this is due to system load. Sometimes the difference would be negligible and sometimes significant -- I started looking at the complexity numbers more than the time because the time simply wasn't reliable enough across different runs. |
@taxilian Would you like to review the new changes ? |
At first glance I'm completely baffled by a number of things, so I'm going to have to dig into it and I don't know if I can do it today. The first thing that I'm really confused about, though, is why you would move all the stuff you pulled out (the stuff that my code all needed, but you say isn't needed) into a unit test -- if it's not needed then it's not needed. If it is needed then it is needed; I can't imagine any situation where having it in a unit test would do anyone any good -- though if I can find some time to dig into it more deeply I may find something I'm missing. |
So I really don't understand what you are trying to fix with the updated changes. You are fundamentally changing what LeanDocument does. I get that you don't think that it needs to do what it does -- that's a totally valid argument and discussion which can be had, since you obviously don't need it to do what it does. I however do need it to work the way that it has worked, though ideally it should work actually a little bit better than that (hence my changes). You can't just say "it doesn't need to do that because I've never needed to do that" and then make a breaking change in types as a bugfix... that's exactly what I accidentally did which started the whole discussion. LeanDocument worked in a certain way... with this change it doesn't. It can certainly be argued that there are other ways to do those things, which seems to be your point, but that doesn't make it not a breaking change for anyone who is relying on it to do those things. This PR fundamentally changes what LeanDocument does -- this isn't something that should be done in a patch release. What problem are you solving that isn't solved by the PR that he merged yesterday? |
Also, I did actually try the branch -- and all my stuff is broken exactly as expected because nested documents, document arrays, etc are all not correctly translated. It's possible that I could restructure my types in such a way that they would be, but that's the point -- this is a breaking change. |
Look guys. I think I have to make the unpopular decision and ask you to stop on this topic and end this "conflict". LeanDocument got patched. For now we have fixed the issue. Maybe not the best way to do it, but it works now. Like the saying: "a solution is a temporary fix which becomes permanent". I assume you, @mohammad0-0ahmad are eager to simplify the typings, that is fine. But not now is not the time. We have no time pressure and we can be sure that with the next release that bug is not shipped. I can guarantee you, that @vkarpov15 will not merge it as there is already no confidence that it wont break anything. So, my decision is: I close this PR and propose another path to solve this: I ask you to add more typings tests and cover all edge cases if necessary and provide a PR. Only test files for typings and if you find small bugs and you can fix them with little work, thats fine. But not more, no big typing changes regarding the LeanDocument topic. When taxilian provided the typings tests, or says they are complete, we will review the PR. Like they say regarding TDD: "First measure and then cut." You are then free to provide a PR with refactored typings and as we already defined the tests, we would not need to touch the tests. So we can assure that there is no regression. @taxilian I hope you stay motivated and dont take all this personal. Best Regards |
Very much agreed -- and please let me reiterate that I have no hard feelings towards anyone here, and I apologize for any "harsh tone" which I've probably used despite my best efforts -- it's mostly from fighting a losing battle with my ceph cluster all week. @Uzlopak the PR which was merged by @vkarpov15 already has all of my things working, so I don't have any currently broken things that I need to fix, just things which this PR would have broken. @mohammad0-0ahmad I actually (somewhat grudgingly ;-]) like the direction you're going with this, I was just concerned about trying to do it here in what I have perceived as being something intended to merge into 6.3.x. I would be happy to continue a discussion on a PR with longer term plans where we can try to hammer out some better type tests -- which I think we all can agree needs to happen. I really like what I've seen so far on the automatic type stuff, just trying to figure out how I can mesh it with how I'm doing things. please feel free to loop me in on anything you'd like reviewed for future stuff and we can go back and forth as needed in a less stressful way =] Again, sorry my tone wasn't always great and for any disrespect that I conveyed, it was not intentional. |
That is really disappointing that you are closing this PR again in that way. Firstly, I really need you @taxilian to understand that I am not targeting your code because it is yours, but I am just make things better and more straight forward, Secondly, please don't state something without be sure about it. You are saying that I am fundamentally changing what Lean Document does "which might possible", but please don't state that before reviewing the code. And it works, like it worked, "I believe". I am really know that I can't just say "it doesn't need to do that because I've never needed to do that". And I would never like to make a breaking change in types as a bug-fix or waste someone else efforts or time. @Uzlopak with all my respect, but I don't take it as a conflict and as I said my only goal is better, simple code and more reliable intellisense even if @vkarpov15 would not like to merge it now, It can still as suggestion that can be discussed to get possibly better result in the feature. Talking about some new unit tests, I would like to encourage you to create tests to checks if __v is persistence in lean document and think about what should happening when supplying a generic to lean function, right now it doesn't add _id by doing so. It might be also fantastic if we can get consistence lean document type when customizing toObject options argument. At the end I do really apologize @taxilian @vkarpov15 @Uzlopak If you feel that I waste your time, or I waste your efforts @taxilian that you spent days for making sure it will work. And I will not be working on this one anymore for now, like you wish. Best regards! |
I'll see if I can take some more time after I fix my other crisis to look over this, but I was not stating something without checking: Previously with LeanDocument you could pass it a HydratedDocument type. With your change you cannot. I did review the code enough to be sure that was still the case, at least in every test that I did (though admittedly I only ran through things for about 10 minutes to see if I was missing anything, which was not thorough). That is specifically the breaking change I was referring to -- you could pass in a HydratedDocument and get the base type out and in this PR you cannot. |
First of all, it was not about me feeling wasting my time or so. I was under the impression, that this gets a heated discussion. I wanted to moderate and avoid getting it more heated. Because you both confirmed that this is not a heated discussion and claim to want to work further on this I assume there is no harm to reopen it. Keep up the great work. :) |
Alright, I've not noticed that, but that is great you could find that breaking change. |
Can you please fix the merge conflicts? |
* @summary Lean document base type. | ||
*/ | ||
interface LeanDocumentBaseType { | ||
_id: Types.ObjectId; |
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 isn't sufficiently flexible. Not all lean documents have _id
as an ObjectId. Maybe interface LeanDocumentBaseType<IdType = Types.ObjectId> { _id: IdType; }
?
@@ -221,11 +253,11 @@ declare module 'mongoose' { | |||
set(value: any): this; | |||
|
|||
/** The return value of this method is used in calls to JSON.stringify(doc). */ | |||
toJSON<T = LeanDocument<DocType>>(options?: ToObjectOptions & { flattenMaps?: true }): FlattenMaps<T>; | |||
toJSON<T = LeanDocument<DocType>>(options: ToObjectOptions & { flattenMaps: false }): T; | |||
toJSON<T = DocType>(options?: ToObjectOptions & { flattenMaps?: true }): FlattenMaps<LeanDocument<T>>; |
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 rather keep toJSON<T = LeanDocument<T>>
to allow opting out in cases where LeanDocument
doesn't quite work with how people wrote their code
type IfUnknown<IFTYPE, THENTYPE> = unknown extends IFTYPE ? THENTYPE : IFTYPE; | ||
|
||
// tests for these two types are located in test/types/lean.test.ts | ||
export type DocTypeFromUnion<T> = T extends (Document<infer T1, infer T2, infer T3> & infer U) ? |
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.
Are these helpers necessary? Don't seem used.
I was waiting for @taxilian response, but never mind, I will close this PR. |
Related to #11761