Open martinbonnin opened 3 years ago
And we need relayStylePagination helper function. https://www.apollographql.com/docs/react/pagination/cursor-based/#relay-style-cursor-pagination
We have a paginated query that returns us a list of our app items, like so…
query ItemsQuery($itemArgs: ItemArgs!) {
items(args: $itemArgs) {
edges {
id
node
}
pageInfo
}
}
type ItemsArgs {
first: Float
after: String
last: Float
before: String
filterBy: ItemsFilterByArgs
}
where the pageInfo
contains the pagination info, and ItemsArgs
allows us to request a “bucket” of data (usually a month of time’s worth).
The use case is for us to regularly sync our items from the server to the normalised cache, and our UI will watch the cache.
It seems the only way for us to do this is to iterate over the pages in the query, and manually writeOperation
the cache to populate it with all the edges (and key it against the original query with no pagination parameter).
It would be great if there was internal workings in the apolloClient to help us manage this.
Hey @martinbonnin 👋
Did this get prioritised internally? Just wondering if this is something we can expect in the short/medium term?
Thanks! 🙏
Yes, it's one of the next big thing on the todo list after https://github.com/apollographql/apollo-kotlin/issues/3566. Pretty hard to commit to a date at this point but the coming months are the current goal.
Hi 👋
We started investigating these APIs. You can read more about it in the design document.
The tldr; is:
// build.gradle.kts
dependencies {
implementation("com.apollographql.apollo3:apollo-normalized-cache-incubating")
}
extend type Query @typePolicy(connectionFields: "usersConnection")
val apolloStore = ApolloStore(
normalizedCacheFactory = cacheFactory,
cacheKeyGenerator = TypePolicyCacheKeyGenerator,
metadataGenerator = ConnectionMetadataGenerator(Pagination.connectionTypes),
apolloResolver = FieldPolicyApolloResolver,
recordMerger = ConnectionRecordMerger
)
Updating the cache now merges new items with the existing ones
[!WARNING] The persisted database format is different so you can't use it together with the non-incubating DB
It's still the early days and too early to use in production but any feedback is warmly welcome.
@martinbonnin Do you have plans to support the following format?
type UserConnection {
pageInfo: PageInfo!
nodes: [User!]!
}
Shopify and GitHub APIs have nodes in addition to edges. In Modern Relay, Cursors are no longer used, and instead startCursor and endCursor in PageInfo are used. Currently, Edge are an unnecessary layer.
https://shopify.dev/docs/api/admin-graphql/2024-01/queries/customers#returns
Hey @sonatard, this is great feedback! I've created issue apollographql/apollo-kotlin#5735 to follow-up on this. Short answer is the current system is flexible enough to manually configure it to work with queries selecting nodes
instead of edges
but it would be better if it just worked automatically with the dedicated support we have for Relay style - I think we can do it without too much hurdles.
Hi 👋 The apollo-kotlin-normalized-cache-incubating
artifacts now support this.
Documentation is at https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/pagination.html.
Let us know what you think.
@martinbonnin I tried it right away, and it's working smoothly even with nodes! Thank you!
query StorefrontCollectionProductsPage($id: ID!, $sordKey: ProductCollectionSortKeys, $reverse: Boolean, $first: Int!, $after: String, $filters: [ProductFilter!]!, $countryCode: CountryCode!, $languageCode: LanguageCode!) @inContext(country: $countryCode, language: $languageCode) {
collection(id: $id) {
id
title
products(first: $first, sortKey: $sordKey, reverse: $reverse, after: $after, filters: $filters) {
nodes {
id
... ProductItemFragment
}
pageInfo {
endCursor
hasNextPage
}
}
}
}
val afterQuery = q.copy(after = Optional.presentIfNotNull(collection.products.pageInfo.endCursor))
```diff
.normalizedCache(
normalizedCacheFactory = memoryCacheFactory,
cacheKeyGenerator = IdCacheKeyGenerator,
@martinbonnin The cacheResolver has been removed from normalizedCache. How can I use the id field as the key for the cache? Or will the id field automatically be used as the cache key without any configuration?
@sonatard Glad that it works well for you! Also you should be able to remove extend type Collection @fieldPolicy(forField: "products" paginationArgs: "after")
as it's done automatically with connectionFields
.
About the cache resolver: it's still there but the API changed a bit: resolveField
now takes a ResolverContext
that contains the values that were previously passed as arguments separately - it should be straightforward to migrate to the new signature but don't hesitate to tell us if this isn't clear!
@BoD
Also you should be able to remove extend type Collection @fieldPolicy(forField: "products" paginationArgs: "after") as it's done automatically with connectionFields
Thanks!! I removed it.
About the cache resolver: it's still there but the API changed a bit: resolveField now takes a ResolverContext that contains the values that were previously passed as arguments separately - it should be straightforward to migrate to the new signature but don't hesitate to tell us if this isn't clear!
I found this PR, but it seems it hasn't been released yet. Is there a planned release date? https://github.com/apollographql/apollo-kotlin-normalized-cache-incubating/pull/20/files
I created an IdCacheResolver using ApolloResolver, but in the next release, ApolloResolver will be replaced with CacheResolver.
version 0.0.2
object IdCacheResolver : ApolloResolver {
override fun resolveField(context: ResolverContext): Any? {
val id = context.field.argumentValue("id", context.variables).getOrNull()?.toString()
if (id != null) {
return CacheKey(id)
}
return DefaultApolloResolver.resolveField(context)
}
}
.normalizedCache(
normalizedCacheFactory = memoryCacheFactory,
cacheKeyGenerator = IdCacheKeyGenerator,
Woops you are right @sonatard, sorry about the confusion, I forgot this hadn't been released yet. I just published a new release, v0.0.3, which contains the change. It should be available in a few minutes.
@BoD Thank you for your prompt response. It turned out to be a simple diff.
version 0.0.3
object IdCacheResolver : CacheResolver {
override fun resolveField(context: ResolverContext): Any? {
val id =
context.field
.argumentValue("id", context.variables)
.getOrNull()
?.toString()
if (id != null) {
return CacheKey(id)
}
return DefaultCacheResolver.resolveField(context)
}
}
.normalizedCache(
normalizedCacheFactory = memoryCacheFactory,
cacheKeyGenerator = IdCacheKeyGenerator,
cacheResolver = IdCacheResolver,
+ metadataGenerator = ConnectionMetadataGenerator(connectionTypes = connectionTypes),
+ recordMerger = ConnectionRecordMerger
Everything worked perfectly. Thank you for the excellent work. I am looking forward to its official release.
Using the normalized cache with lists and especially pages is cumbersome as different pages get stored in different records:
This will store the following root Record:
Because the data is spread between two Record fields, there's no easy way for a watcher to retrieve the full list.
API is yet to be defined.
Part of apollographql/apollo-kotlin#2331