Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

(feat) Add support for the top-level extensions key in the GraphQL specification #355

Merged
merged 6 commits into from
Jul 29, 2019
Merged

Conversation

adamscybot
Copy link
Contributor

Resolves #353.

Copy link
Collaborator

@JoviDeCroock JoviDeCroock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very good to me, thanks for taking your time to contribute this! 👍

setState(s => ({
fetching: true,
data: handler !== undefined ? handler(s.data, data) : data,
error,
extensions,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should pass this on to the "user-level" as exchanges would be the only consumers of these special fields.

Or is there any use case you can think of? I'm not sure whether Apollo passes them on?

This can especially confusing because cached results wouldn't have any extensions, which makes this an easy pitfall as well

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think its the case that exchanges are the only consumers -- what's in there is entirely arbitrary and actually could have functional reasons. For us, we are passing down informations regarding authorisations and cross-cutting UI concerns that arent actually in the "graph". This is quite a purist view of graphQL -- but there are definitely use cases.

Another use case might be to surface some performance metrics in the UI.

This can especially confusing because cached results wouldn't have any extensions, which makes this an easy pitfall as well

Ah, I was hoping they would come in. I think my changes to the cacheExchange sees to that?

Copy link
Contributor Author

@adamscybot adamscybot Jul 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heres an example we actually have in production: Sometimes a user can see part of an entity but they aren't allowed to see the wider detail of it -- hence, they cant "click through" on these entities. Data about this is sent in the extensions and the UI reacts to it. We dont send this data in the data itself as its very much meta and a cross cutting thing that can affect any entity.

Apollo doesn't pass them on. But it's because its a bug: apollographql/react-apollo#2059. The intended fix in apollo is to cache these things alongside everything else.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this very much could also be part of the data bit fair enough. The problem here though is that we have to assume that the extensions are bound to the specific request.

This means that we shouldn't cache it as it's stale when it's read back from the cache, especially if it contains tracing data and performance data. This probably should also not be included in the SSR result as the frontend shouldn't be concerned about request information from the backend.

I know this is controversial, but I have the feeling that this is very much "non best-practice territory".

If we're thinking of authorisation in general, if it's meta data then a custom exchange would possibly handle that. Otherwise it may be better to include it in data.

A normalised cache would never be able to cache this data, as queries can be fulfilled entirely offline.

So there's definitely a point I believe to surfacing this to the user for "advanced use cases" but the extensions probably shouldn't be cached, shouldn't be part of the SSR serialised data, and shouldn't be guaranteed.

I'm unsure however about some details around this 😅

Copy link
Contributor Author

@adamscybot adamscybot Jul 26, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I can see where you are coming from. It doesn't help the spec for extensions is essentially "object for stuff". We ourselves have wondered hard if we are overloading this thing. We could stretch the definition of an error for some of these use cases and use that. Those have a path in the spec back to the data and so are inherently linked.

If the extensions relate to the data, they could be cached. If the extensions don't relate directly to the data (perf metrics), they should not be cached. You're right, thats "custom exhcnage" territory.

Back to the drawing board :(. I guess my next questions is should I just limit this whole PR to the fetchExchange? People could then do what they want with them in their own exchanges and potentially opt to put them in the data where they will be cached.

It works for our use case and isn't making any assumptions about what is in there for everybody else.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, I'm definitely all for passing extensions on. Yea, that's a nice way of getting some extra data out of exchanges.

But none of the default behaviour should cache or guarantee the extensions. That's definitely custom exchange territory.

So the ssrExchange should probably be modified to remove extensions from the serialised result.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, so just so Im 100% clear before I sort this out: We want to surface extensions in the hooks, we just want to remove my bad assumption re: caching the extensions?

I think as well as ssrExchange i need to modify cache.ts to filter extensions before it does a cache set operation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to surface extensions in the hooks, we just want to remove my bad assumption re: caching the extensions?

yea, let's surface it. We probably won't put too much focus on it in the docs, but it's particularly useful for also adding (client-only) information/metadata/data from the exchanges, without modifying data, like you said.

I think as well as ssrExchange i need to modify cache.ts to filter extensions before it does a cache set operation.

I think you might need to keep it when you set, because otherwise it may be missing during SSR after suspense is done 🤔 But we definitely want to filter it out from the data that is returned by serializeData, so that it's not sent to the client from the server.

Cheers for taking the time to do this btw 🙌

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've committed a fix to ssrExchange to not add extensions during serialisation.

I think I'm getting slightly confused over the cache in ssrExchange and cacheExchange. In cacheExchange I've now removed the setting of extensions in there by filtering it -- I think we might accidentally be referring to different caches in above conversations.

Might be best for you to sanity check what I've just done.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I'll give it a quick review again 👌

@kitten
Copy link
Member

kitten commented Jul 26, 2019

so, on exchanges/ssr we need to filter out the extensions on ssr.extractData, since that's the ssr cache data that will be serialized and sent to the client. Unfortunately it won't be enough to remove it when we deserialize 😅

@adamscybot
Copy link
Contributor Author

so, on exchanges/ssr we need to filter out the extensions on ssr.extractData, since that's the ssr cache data that will be serialized and sent to the client. Unfortunately it won't be enough to remove it when we deserialize 😅

I think we are fine because serializeResult already only considers data and errors and not extensions so it does not get serialised (I think anyway...the SSR part I'm the least certain about).

@kitten
Copy link
Member

kitten commented Jul 29, 2019

Yep, that's right! I've reviewed the full thing now and it looks perfect! I'll merge it and see if we need more tests and start seeing whether it all behaves as expected 👍

Thanks again for taking the time to submit this!

@kitten kitten merged commit 09494f2 into urql-graphql:master Jul 29, 2019
@adamscybot
Copy link
Contributor Author

Thanks! Happy to help.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Top level extensions are not passed through
3 participants