-
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
Fragment matcher #1483
Fragment matcher #1483
Conversation
This is a failing test to demonstrate issue #1363.
d72d77e
to
53948ba
Compare
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.
A few requests. There are also some comments left in from debugging.
src/data/fragmentMatcher.ts
Outdated
isTest, | ||
} from '../util/environment'; | ||
|
||
export interface FragmentMatcherInstance { |
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 name is confusing because I usually thing Instance
means not the interface. Perhaps FragmentMatcher
or FragmentMatcherInterface
would be a better name.
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.
Yeah, I'll probably go for interface then, because there's already a FragmentMatcher
in graphql-anywhere, and that one's a function.
src/core/QueryManager.ts
Outdated
// This function runs fetchQuery without initializing the fragment matcher. | ||
// We always want to initialize the fragment matcher, so this function should not be accessible outside. | ||
// The only place we call this function from is within fetchQuery after initializing the fragment matcher. | ||
private fetchQueryWithoutInit<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 call this fetchQueryAfterFragmentMatcherInit
to be really explicit that you should only call it in that circumstance.
src/data/fragmentMatcher.ts
Outdated
throw new Error('FragmentMatcher.match() was called before FragmentMatcher.init()'); | ||
} | ||
|
||
// invariant(isIdValue(idValue), 'boo hoo, I am sad'); |
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.
Should be deleted
src/data/fragmentMatcher.ts
Outdated
return; | ||
}) | ||
.catch( (err: any) => { | ||
throw err; |
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.
There's definitely a possibility of a bad outcome here if we couldn't fetch the introspection result. I think a good option could be to set this.readyPromise = null
so that we try to init again on the next query.
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.
Yeah, probably worth a try 👍
src/data/fragmentMatcher.ts
Outdated
/** | ||
* The init method has to get called before the match function can be used. | ||
*/ | ||
public init(queryManager: QueryManager): Promise<void> { |
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 bit nitpicky, but I think a name like ensureReady
would be better because that would indicate that this function is called every time, not just once to initialize.
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.
yup, agreed. The function evolved quite a bit, and it's no longer just an init function.
src/data/fragmentMatcher.ts
Outdated
export interface FragmentMatcherInstance { | ||
canBypassInit: (query: DocumentNode) => boolean; | ||
init(queryManager?: QueryManager): Promise<void>; | ||
match(idValue: IdValue, typeCondition: string, context: ReadStoreContext): boolean; |
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.
Should this take an obj
instead of an idValue
? Seems like both implementations just do obj = context.store[idValue.id]
as the first statement. I can see that we still want context
so we can set returnPartialData
.
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.
Yeah, I thought about that for a second, but then decided against it, because we might have a store in the future where the typename is stored in the path or in the id, in which case we could theoretically save the lookup. It also saves us from having to update graphql-anywhere or wrapping the fragmentMatcher function before passing it in. I think it's a little bit simpler this way, so we should keep it.
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.
Sounds good. This is a small enough piece that if we change the store format or anything else later, rewriting this will be the least of our worries.
It's worth noting that |
Yeah I guess there's no way to get around that if store reading is synchronous. |
Hurray! Where will be the next RC release? |
@csillag I'll release it in a couple of minutes, but I won't tag it as latest yet, so you'll have to install that specific version or To use the introspection fragment matcher for now, you'll also need to specifically provide it:
I'm doing this because I'd like to make sure that there will not be any unintended consequences of having to fetch (or pre-populate) some schema information before enabling it for everyone by default. |
Awesome. I will take it for a spin on Monday. |
cc @fubhy @mdebbar @bgentry @TSMMark @thebigredgeek I've merged a solution for fragment matching. It would be great if you could test it by |
reposting from slack: @helfer AFAICT the Interface type stuff is fixed 🎉 Unfortunately the upgrade breaks some of my tests around error handling of 4xx server responses. I thought that my observer's I manipulated a cookie on my app to achieve the same effect outside of tests, and the result is that my actual app is broken by this change. The reason is due to the fact that this change adds the following introspection query, which runs before any of my app's actual queries: {
__schema {
types {
kind
name
possibleTypes {
name
}
}
}
} My tests assume that the first request that's made is one to load the current user, and the server just always returns a 401 in these tests (as it would IRL when an auth token is invalid for some reason). Thus the introspection query fails on a 401 status. Likewise, my actual app (outside of tests) tries to load the current user immediately to check if auth is valid, and if not will invalidate the session and cause a login. But my current user query is never even running, presumably due to an error received on this introspection query. I don't think this is viable because it requires the graphql endpoint to be publicly accessible with no authentication requirement, at least for the sake of this introspection query. My server's graphql endpoint is only available with a valid session, and I don't think that's a use case that can safely be ignored. |
@bgentry would adding a build step to inline that type information on startup work for you? Do you have the ability to run introspection locally from your development environment? |
@bgentry I don't think there's any way of doing this without schema information. Having schema information requires that you either A) Include the schema at build time (for example by importing, or by doing an additional build step) For option B importing is only reasonable if your schema is already in JS, which isn't the case for everyone. It would also incur a bundle size penalty. It's pretty easy to implement such a fragment matcher with the interface available today, so if that's the option you want to go for, I'm happy to assist. One of our most important goals with Apollo is that it works out of the box and doesn't require a build step or tooling for the most common use-cases. @bgentry: just so I understand. How is it that your current user query can succeed but not the introspection query? Or are you using the fact that the currentUser query fails to set in motion some kind of login process? If so, could you just use the fact that the introspectionQuery fails for doing the same thing? |
@TSMMark yes, it should work out of the box. Are you passing |
@bgentry how about if you could do option (2), but you had a script that generated the necessary data? So basically:
In this setup, you would need re-run the script from (1) when you modify the interfaces or unions to your schema.
How does this compare to the process above for you? |
Whoops, my bad @helfer. We had a custom const dataIdFromObject = (result) => {
if (!result.__typename) {
throw new Error('GraphQL response missing __typename: ' + JSON.stringify(result))
} I guess Edit: It appears that our |
Yep, totally get that!
I think this is an important goal and it's one that I share.
I am using the fact that the As far as bundling my schema, I am indeed already doing that for usage in my test suite, so it wouldn't be much different to put it in a place where the app itself could use it too. Since Apollo Client never used the schema before this fragment matcher was added, am I correct to assume that there isn't a documented way to provide a schema? Would I need to implement a custom |
@bgentry the current design allows you to pass a static object with the data in the constructor: https://github.com/apollographql/apollo-client/pull/1483/files#diff-2223acc12993ca0745c660713b0c4f78R40 |
Hi, I arrived at this issue from #1436 and wanted to try this out. Upgrading from rc.6 to rc.7 and adding:
Appears to have broken a few of my initial startup queries (which use fragments), which were previously working in rc.6. |
It doesn't seem to work for me either. I've added the |
Here are my test results: Behavior with 1.0.0-rc.6:
Behavior with 1.0.0-rc.7, with
|
... so, to summarize: |
Further analysis shows that the problem with the React wrapper is caused by the ObservableQuery.currentResult function being broken. |
One more bit of info: as far as I can tell, not engaging the the |
Thanks @csillag! That's very interesting, let me look into it. |
@rovansteen @booboothefool @csillag Could you guys try if your queries work when you pass PS: We recently decided to set the default of |
For me, it doesn't seem to do anything. ( However, I can confirm that using |
Thanks for the feedback @csillag. Would you be able to make a PR with a failing test or provide a reproduction with react-apollo-error-template? For me all the tests pass with |
I'll try to do that tomorrow. FWIW, I didn't use |
I think |
OK, so I have prepared a reproduction based on Please test the following two branches:
Testing manual query executionRun this command on the console: apollo.query({query: getPeople}).then(result => console.log(result.data))
Testing the React wrapperThe loaded data is automatically displayed on both the browser console and rendered on the UI. |
Any progress on this? I see 1.0 is released but it looks like this bug is still there. |
Rc.9 works for me flawlessly!
2017. márc. 30. 23:43 ezt írta ("Robert van Steen" <[email protected]
…):
Any progress on this? I see 1.0 is released but it looks like this bug is
still there.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#1483 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AB_y4A5P3RC88seOSRedrdFjYHI4liV5ks5rrCIFgaJpZM4MncH1>
.
|
@rovansteen We didn't make it the default, so you have to specifically pass the fragment matcher and initialize it with the relevant part of your schema:
First we also wanted the fragment matcher initialize itself, but the code ended up being more complicated to maintain than we wanted, so we ditched that for now. We'll try to provide some instructions for how to seed the fragment matcher with a build script or server-side rendering. |
@helfer Is there any clear documentation on this? I can't get it to work. If I don't setup the IntrospectionFragmentMatcher my first result works fine, but if I use the fetchMore method I get no data, even though the server returns the data without any problems. If I do use the fragmentMatcher, I get no data at all for all my queries. I tried it with a partial schema (just the union part) and a complete schema of everything. Both break all my queries. Edit: Ok, small correction. The fragmentMatcher breaking all my queries was to a mistake on my side in my schema. Now I've got the schema correct, but the issue remains that I've created a separate issue with example repo (the one from csillag with a few changes, thanks!) to illustrate this issue. #1523 |
The problem is solved with the solution, but before we found the solution, |
Fixes #1337 #1363 #1379 #1113 #857 #771 #1297 #1273 #961 #933
Not 100% sure all of these issues will be fixed, but it should fix a good number of them.
TODO: