apollographql / apollo-feature-requests

🧑‍🚀 Apollo Client Feature Requests | (no 🐛 please).
Other
130 stars 7 forks source link

Allow querying cached queries and their variables #62

Open wmertens opened 5 years ago

wmertens commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

I think this may be good starting point: https://github.com/apollographql/apollo-feature-requests/issues/97

bdrobinson commented 5 years ago

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 commented 5 years ago

@bdrobinson take a look at my suggestion https://github.com/apollographql/apollo-feature-requests/issues/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 commented 5 years ago

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 commented 4 years ago

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: https://github.com/apollographql/apollo-client/issues/3505#issuecomment-535388194 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 commented 4 years ago

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

stalniy commented 4 years ago

@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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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}" }
....
}

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:

Possible solution:

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.