-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Only attach distinct fragments to document #906
Only attach distinct fragments to document #906
Conversation
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.
@jnwng This is a great PR, and I think we should merge it, but as far as I can tell it won't solve #897, because the issue there is that the query document already exists with all the fragment definitions, and then we're adding the same fragments again, which this PR won't prevent as far as I can tell.
Could you update it to fix #897 as well?
@@ -154,6 +155,6 @@ export function addFragmentsToDocument(queryDoc: Document, | |||
} | |||
checkDocument(queryDoc); | |||
return assign({}, queryDoc, { | |||
definitions: queryDoc.definitions.concat(fragments), | |||
definitions: queryDoc.definitions.concat(uniq(fragments)), |
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 won't be sufficient to avoid duplicates if one copy is already present in the definitions and another copy is added. An easy fix would be to apply uniq
to the entire list of definitions, not just the fragments added.
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.
good point, will update.
Attaching multiple references to the same fragment results in the creation of an invalid document - we want to make sure we’re only attaching _distinct_ fragments to the document.
Its possible that fragments have already been defined in the document itself - we want to check against those to make sure we’re only attaching fragments that either 1) don’t already exist in the document 2) are distinct in the fragments to be attached to the document
e859801
to
0187354
Compare
what you said made sense - the fragment definition could exist in the document already. however, just using basically, i'm filtered the fragments that we're attaching by their name so we don't duplicate the fragment names, however this may result in some unintended behavior (basically, if there's a name collision here, we probably need to throw a warning or something, because those fragments could have different bodies, and those fragments may get dropped silently). now that i've written that out, its also possible to that the fragments that are being attached are named the same but have different bodies, which will also fail the i've updated this so we can discuss, but i think i'm going to have to take another pass to check the fragment name and the SelectionSet. or is that doing too much? |
We get a free pass here because |
Oh, by the way - |
Fix error handling for observer.next
@stubailo for some reason the fragment definition coming from the query body isn't matching the one i'm attaching via |
As mentioned in the feedback, we can actually just run `uniq` on the concat of the definitions and the fragments because the definitions that already exist in the document will always win over the attached fragments. However, slightly unexpectedly, FragmentDefinitions generated in the query document don’t `===` the same definition via `createFragment`, so we’ll use object equality to remove duplicate definitions.
@@ -154,6 +156,6 @@ export function addFragmentsToDocument(queryDoc: Document, | |||
} | |||
checkDocument(queryDoc); | |||
return assign({}, queryDoc, { | |||
definitions: queryDoc.definitions.concat(fragments), | |||
definitions: uniqwith(queryDoc.definitions.concat(fragments), isequal), |
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.
@stubailo unfortunately, with the test i provide in this PR: console.log(queryDoc.definitions[1] === fragments[0]); // false
, even though the fragment definition itself is identical. not exactly sure why that is given that you mentioned that graphql-tag
should be returning the same definition, but in any case, using the object equality check here properly removes the duplicate definitions.
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.
@jnwng Thanks! I'll take a look to find out what's going on so we can merge this today.
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.
@jnwng the reason they're not ===
is that graphql-tag ensures that documents are ===
, but not that individual definitions are ===
. If you create the exact same document twice, the definitions will be ===
, but that's more by accident than by design.
As far as I can tell we have two options:
- use
uniqwith
withisequal
as you did - use
uniq
and say that nobody should be adding fragments to a doc in two different ways
My preference is option 1. Option 2 would have better performance, but unless we put in checks that make sure people don't add fragments in ways that are not supported, it's going to be harder to debug when things go wrong.
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.
Actually, there's a third option, which is to copy just the query or mutation definitions of any document passed to apollo-client, and pass all fragment definitions through createFragment
before re-attaching them. Personally I don't like that option because I think it's overly complicated.
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.
After talking to @stubailo we decided to go with option 2 for performance reasons, and because we're planning on doing a refactor that takes fragment handling completely out of apollo-client. I've made the test more comprehensive to make sure solution 2 actually works in the cases we expect to see.
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.
Makes sense to me. Thanks for the explanation and context @helfer!
Also relevant: apollographql/graphql-tag#17 |
See this issue on
react-apollo
to see what lead to this coming to this particular repo, and #897 for information on the bug / temporary workarounds.Update CHANGELOG.md with your change(omitted because I'm not fully sure I should be doing this)If this was a change that affects the external API, update the docs and post a link to the PR in the discussion(omitted because it does not change an external API)Attaching multiple references to the same fragment results in the creation of an invalid document - we want to make sure we’re only attaching distinct fragments to the document.
Steps to Reproduce
Attaching multiple references to the same fragment like:
results in a query document that looks like
(note that the fragment definition gets attached more than once). Per the GraphQL spec, having multiple fragments with the same name is invalid, and while we guard against registering multiple fragments with the same name here, we need to also make sure we're not attaching multiple instances of the same fragment in our document.
Do also note that the example above is boiled down to the exact steps that it would take to reproduce the issue, but in more realistic use cases this occurs when sibling fragments both include a dependency on another fragment (they wouldn't know that the other fragment had already included the required fragment in the document).