-
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
Custom query document transforms #10481
Comments
@nyteshade, I'd love to get your feedback here and see if this proposal better addresses your original goal you laid out in #10228. |
@jerelmiller I think this is mostly sound. I only really have one question upon seeing what has been laid out. I want to make sure that our transform function runs for every query and mutation and is not overly cached; does this mean I would place the corresponding function in the On an adjacent note, I'd like to say that custom query transforms will go a long way to helping companies not need to monkey patch the client and provide a lot of opportunities to reduce code elsewhere in companies' codebases. Thank you for considering it. |
@nyteshade That's correct! Looking through the codebase, it looks like the result from Glad to hear this is a lot more in line with your expectation! Thanks so much! |
Thanks so much for taking the time to understand the issues and being open to the potential it brings. |
I greatly appreciate your work on this proposal, @jerelmiller. I'm curious what the delta is between this and full-fledged custom client directive support — in our case, for our custom |
@adamesque that is a great question! I view this proposal as strictly the ability to transform query documents from some initial value to another before the query is sent to the server. Two examples in this repo are 1) adding The only reason I see a need for this to exist is because of the fact that I've thought about this a lot the last couple days and I've actually been wondering... is there any reason we couldn't do custom directives in the link chain today? I'd still like to do a proof of concept to try this out, but I'd be curious if you can think of any reasons why
Thoughts? |
@jerelmiller Hi, I haven't really been involved in this thread but I've tried doing query transformations in the link chain before so I thought I'd share my input. Transforming the query document itself isn't terribly difficult with graphql-js's |
@dylanwulf thanks for chiming in! Glad to hear you've got some practice with this in some form.
Definitely understand this, which is why I think providing an out-of-the-box |
@jerelmiller Here is a custom client-only directive I made for our app. Our backend has a restriction that it can only return a max of 250 results in one request, so I wrote this |
Oh sweet. Thanks for sharing @dylanwulf! |
Great question! The main issue I ran into was that after data is returned from the terminating link, the cache writer writes to the cache using the original untransformed query document. In our case, since our custom directive fetches fragment bodies that don't appear in the original document, this means the cache writer explodes when walking the doc to write the results, since the fragment spread refers to a fragment that only exists in the transformed doc. (We work around this by adding our fetched fragment bodies to the Fragment Registry) I was thinking this would prohibit using a link to implement any kind of custom directive, but I hadn't considered that the correct way to do that in most cases would be intercept the result and transform it to match the original untransformed query structure. I don't know if there are other use-cases where cache writing would break if the cache isn't aware of the result of a document-transforming Link and the original document might be considered invalid. Could something like Relay's |
@adamesque you bring up some great points! I'm going to do some more exploration in the coming days to figure out how the cache would interact. Appreciate your thoughts as it gives me more to think about! |
We've been discussing another concrete use-case for client directives, to reduce our reliance on plumbing operation variables through query fragments just to turn on/off fields under experimentation (A/B testing). Our idea was to implement a client-only set of experiment-aware skip/include directives, to transform a document like this: query MyQuery {
foo {
...MyFooFragment
baz
}
}
fragment MyFooFragment on Foo {
bar @includeInExperiment(name: 'my_experiment', value: 1)
} into a vanilla GraphQL query MyQuery($my_experiment_1: boolean) {
foo {
...MyFooFragment
baz
}
}
fragment MyFooFragment on Foo {
bar @include(if: $my_experiment_1)
} with operation variables: {
"my_experiment_1": true
} We can't build this today in a Link because I believe it would break cache reads. The cache wouldn't have the transformed document, and so wouldn't understand that it could potentially skip the I was hoping that we could use the APIs from this proposal/PR to resolve this but I don't think that's possible either since everything hinges upon the ability to not only transform the document, but also to supply operation variables per-read (whether from cache or from network). We starting thinking through this while imagining how calling |
Hey @adamesque 👋 This is a really interesting use case. We've been having a lot of discussions internally about this API and we have some ideas on how to make it more powerful, including some built-in caching mechanisms to avoid having to recompute the document more than is necessary . I'll be sharing some more about this idea soon. This use case gives me a good stress test to see if we could execute on something like this given the direction we want to go with the new API. |
Wonderful, thanks again! |
This issue has been completed with #10509. If anyone would like to try this out and provide feedback, this has made it into our first
For now, the API is documented in the PR. We plan to document this in the Apollo docs in the coming days/weeks. I'm excited to get this in your hands! |
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
This is an extension of #10228 originally opened by @nyteshade.
Background
#10228 laid out a well thought-out proposal for defining a custom preprocessor to be able to transform queries before they run through Apollo core. The idea of a preprocessor was necessary because of the order in which Apollo runs local resolvers/cache
read
functions for@client
directives in a query relative to the link chain.Initially I had thought this could be resolved simply by allowing
@client
directives to make it to the link chain, where a custom link could then make behavioral decisions based on the query at that point. While that work is a necessary step in the effort to move local resolvers into the link chain, I've come to realize this solution is incomplete as doing work in a link is too late in the process.Without repeating too much detail from #10228, the goal with the
@persistent
directive was to determine whether to convert that directive to an@client
directive, or drop the directive altogether. The problem with my initial solution is that@client
fields run local resolvers/cacheread
functions in Apollo core, before they ever reach the link chain. If you try and perform the work necessary to transform the query document in an Apollo link, the work is performed too late as the local resolvers have already been run by that point.Proposal
After reading through the proposal several more times, and with more discussion on the topic, I'm understanding the primitive ask is the ability to define custom GraphQL document transforms. We already perform document transforms in Apollo core (e.g. stripping out
@client
and adding__typename
), but currently have no way for users to register their own.The
Cache
abstraction currently provides 2 extension points for cache implementations to make query document transforms:transformDocument
- This is intended to make static changes to the query, such as adding__typename
to the document. This transformed document is heavily cached to avoid performing this work more than necessary. In other words, this function will only be called once per unique GraphQL query document.transformForLink
- This is run once per query and allows the cache to tweak the document before the request is sent.InMemoryCache
cache takes advantage of this hook to fill in fragments from the fragment registry in the query.Given these hooks, I propose we add an option to allow registration of query transforms with
InMemoryCache
. We'll want to separate the idea of a "static" transform and "dynamic" transform so that we can run the appropriate transforms intransformDocument
andtransformForLink
. This allows allows the user to make the determination on how often the query transformation is run.API
Here is a "back of the napkin" idea on what this API might look like to provide a starting point for discussion. I expect the names and API to evolve before released. This idea is meant to lay out the pieces involved:
These transforms would be run in order they are defined, where index 0 would be run first.
How is this proposal different than #10228?
This proposal is very similar to #10228 with the notable exceptions that query transforms are defined in
InMemoryCache
and don't require an Apollo Link or to call a registration function onApolloClient
. I thought it would be easier to lay out the idea in totality using a separate issue rather than trying to define all of this in a comment in the original RFC.What this means for #10346
While #10346 is now less relevant to this proposal, its still an important step in the ability to be able to move local state to an Apollo Link. There is however one change I'd like to make. I'd like to remove the
transformQuery
option passed to theApolloClient
constructor as it simply doesn't meet the goal that the original RFC was intended to address. Until we get local resolvers in the link chain, I don't see much use in this option.What this proposal is not
There is an ask in our feature requests for the ability to define custom directives (apollographql/apollo-feature-requests#166). This updated proposal is not meant to address this feature request as its a different set of concerns. This proposal is strictly the ability to transform a query document before it runs through the process of making the request.
The text was updated successfully, but these errors were encountered: