Skip to content
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

Allow querying cached queries and their variables #62

Open
wmertens opened this issue Oct 23, 2018 · 11 comments
Open

Allow querying cached queries and their variables #62

wmertens opened this issue Oct 23, 2018 · 11 comments
Labels
📕 cache Feature requests related to the cache project-apollo-client (legacy) LEGACY TAG DO NOT USE

Comments

@wmertens
Copy link

Example use case: after creating a new item, updating a list of objects that is queried with server-side filtering and sorting.

It should be possible to ask for a list of cached queries and their variables, so they can be iterated over with readQuery/writeQuery.

Alternative is to refetchQueries the queries by name, but that only works for one query and causes unnecessary network traffic. (See apollographql/apollo-client#3540 - it also includes some code for hacking your way to the observable queries list)

@ghost
Copy link

ghost commented Nov 26, 2018

I'd like to chime in with my support for this idea.

It exposes the central source of truth and knowledge of cache keys is necessary for any generic cache management, especially when you consider common use-cases like list-filters, which result in numerous similar yet different queries.

@stalniy
Copy link

stalniy commented Mar 4, 2019

I think this may be good starting point: #97

@bdrobinson
Copy link

This is exactly what I need, and feel like it would be quite a minimal API change. I had something like this in mind:

interface ApolloClient {
  readCachedVariablesForQuery(query)
}

Then you can just do

apolloClient.readCachedVariablesForQuery(GET_TODOS).forEach(variables => {
  const cachedData = apolloClient.readQuery({ query: GET_TODOS, variables })
  const newCachedData = ... // perform my update
  apolloClient.writeQuery({query: GET_TODOS, variables, data: newCachedData})
})

That would definitely solve my issues (updating server-side filtered lists upon add/removing items). I'd be interested to see how hard it would be to implement.

@stalniy
Copy link

stalniy commented May 2, 2019

@bdrobinson take a look at my suggestion #97. I already implemented patched version for the suggestion and plan to create and release it as a temporary lib so everybody can use it

@bdrobinson
Copy link

Yeah that does look like an interesting approach. I'd be interested to see what the apollo maintainers think of it.

Your proposal feels like a slightly bigger change (in terms of both API surface and implementation complexity) compared to my suggestion above, but perhaps it would serve more usecases. I haven't got a very good idea of all the different usecases apollo is targeting so I'm not really in a very good position to evaluate different approaches.

@eric-burel
Copy link

eric-burel commented Sep 27, 2019

Hi,

Huge +1 (so a +100?) for this feature. We don't even need a fancy find feature, just an official way to list the cache keys and get their variable.

I've detailed how it could help us here: apollographql/apollo-client#3505 (comment)
We eventually managed to update lists with dynamic filters after a mutation thanks to a hack that provides this feature.

There is 0 other way to implement such a feature correctly, especially if you want to update a list that has been server rendered, because you can't even track the quey by yourself in this case.

Note that for some reason in our implementation we need not to match the query name, but the resolver name within the query. So for multiCustomersQuery { customers({ someVariables }) { whatever } }, in the cache we need to match all queries named customers({*}), not multiCustomersQuery. I am not familiar enough with Apollo to understand why.

@stalniy approach is also interesting but maybe require bigger changes compared to what Apollo provides now? While providing a function to list the keys in the Apollo cache seems to be an almost trivial feature (at least with a naive implementation).

@SachaG
Copy link

SachaG commented Sep 27, 2019

Agree with @eric-burel, this seems like a big missing feature in Apollo and would definitely be helpful to implement smoother user experiences.

@stalniy
Copy link

stalniy commented Sep 28, 2019

@eric-burel cache is stored in normalized form. Query variables are part of cache key. So, in other words there is no way to get variables for a particular query.

The whole cache is a one big flat normalized POJO.

What you can do is to get variables for a top level field but it will very inefficient. Because:

  1. You need to get all keys from POJO (cache grows with growing amount of requested data)
  2. Filter out those that starts with your top level key
  3. Slice out variables from the key
  4. Json.parse variables into POJO

@eric-burel
Copy link

Yeah that's what @ngryman "hack" does: https://gist.github.com/ngryman/6856c7eb8f9a15b1095032a6ba478c5c

You're right it will have bad perfs at a certain point because its complexity is O(cache size). Hence the need for an official support. That's indeed not as easy to implement as it sounds and I agree listing keys is actually insufficient.

But that's not a crazy feature either, a performant version would need to store a bit more info when it adds data to the cache (in the writeToStore file if I get it right?), so that later field lookup would be free. (of course I understand this is a free, open source project so it's easy for us to require feature, harder to actually implement them for the core team).

Maybe it could take the form of a "plugin" for the cache or a callback triggered on cache write, so we can maintain our own cache and implement such features directly without involving Apollo?

@wmertens
Copy link
Author

The key is decided here, it seems (the logic is pretty hard to follow though): https://github.com/apollographql/apollo-client/blob/1098e428a013f94281d422fbaebc4bc741a007d1/packages/apollo-cache-inmemory/src/writeToStore.ts#L282
const storeFieldName: string = storeKeyNameFromField(field, variables);

So it looks like it might be sufficient to maintain a mapping object with the key being storeKeyNameFromField(field) and then maybe simply an array of storeFieldName? If we need to have the variables too then perhaps they can be stored in the cache or as part of the array entries.

@eric-burel
Copy link

eric-burel commented Sep 30, 2019

Yep technically this implementation does the trick.
However you will have trouble deleting variables so a map could be more appropriate than an array. A map like this would be better imo:

{
   "customers" : { "{foo:'bar'}", "{}", "{foo:'bar', search:42}" }
....
}
  • During mutation update: searching matching queries have a complexity of O(number of type a similar query with different params has been sent), which is what we want. Currently it is O(total number of queries) which is bad, because we have to list all queries.
  • During cache update: writing is O(1), deleting is O(1) (with an array deleting would be O(nb queries for this field))

Also I am trying to think about a more generic solution. Because it would have been great to be able to write such a feature by ourselves in the first place.

You may also want more complex optimization, eg having faster search on variables, that would require to store variables another way than in an array. That's an extreme case but after all we are talking optimization for heavy data consuming apps so why not.

What would block me currently to freely implement this as an independent package:

  • I don't know how to listen by myself on the events that trigger cache update. Could be solved by digging the code. I could then write my own link to maintain this cache to do so but...
  • then we miss a way to pass a custom param to the mutation update callback. We can only access the cache, so currently our feature requires hard written changes in apollo inmemmory cache.

Possible solution:

  • having an onWriteStore callback as an option of the inmemory cache. This callback could be called with variables and storeFieldName(field), we could build/update our field name/variable cache here
  • having a way to pass more params to our update function. For example this onWriteStore callback could also have a writeable context as an additional param, that could then be read as cache.context on update.

Maybe this is too circumvented though and a direct implementation could be fine, but with those 2 features we could have developed our own cache system.

@jerelmiller jerelmiller added project-apollo-client (legacy) LEGACY TAG DO NOT USE 📕 cache Feature requests related to the cache labels Apr 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
📕 cache Feature requests related to the cache project-apollo-client (legacy) LEGACY TAG DO NOT USE
Projects
None yet
Development

No branches or pull requests

6 participants