apollographql / apollo-kotlin-normalized-cache-incubating

Apollo Kotlin Incubating Normalized Cache
https://apollographql.github.io/apollo-kotlin-normalized-cache-incubating/
MIT License
8 stars 2 forks source link

RFC: Pagination helpers #38

Open martinbonnin opened 3 years ago

martinbonnin commented 3 years ago

Using the normalized cache with lists and especially pages is cumbersome as different pages get stored in different records:

query GetRepositories($first: Int, $offset: Int) {
  repositories(first: $first, offset: $offset) {
    id
    owner
    name
  }
}

This will store the following root Record:

{
  "repositories(first: 100, offset: 0)": [
     { "owner": .. },
     { "owner": .. },
  ], 
  "repositories(first: 100, offset: 100)": [
     { "owner": .. },
     { "owner": .. },
  ], 
}

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

sonatard commented 2 years ago

And we need relayStylePagination helper function. https://www.apollographql.com/docs/react/pagination/cursor-based/#relay-style-cursor-pagination

pauldavies83 commented 2 years ago

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.

pauldavies83 commented 2 years ago

Hey @martinbonnin 👋

Did this get prioritised internally? Just wondering if this is something we can expect in the short/medium term?

Thanks! 🙏

martinbonnin commented 2 years ago

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.

martinbonnin commented 8 months ago

Hi 👋

We started investigating these APIs. You can read more about it in the design document.

The tldr; is:

  1. Import the incubating artifact:
// build.gradle.kts

dependencies {
  implementation("com.apollographql.apollo3:apollo-normalized-cache-incubating")
}
  1. extend your schema:
extend type Query @typePolicy(connectionFields: "usersConnection")
  1. configure your ApolloStore:
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

  1. reading the cache now returns the full non-paginated list.

It's still the early days and too early to use in production but any feedback is warmly welcome.

sonatard commented 8 months ago

@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.

スクリーンショット 2024-03-18 22 08 45 スクリーンショット 2024-03-18 22 09 14

https://shopify.dev/docs/api/admin-graphql/2024-01/queries/customers#returns

BoD commented 8 months ago

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.

martinbonnin commented 2 months ago

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.

sonatard commented 2 months ago

@martinbonnin I tried it right away, and it's working smoothly even with nodes! Thank you!

sonatard commented 2 months ago

@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?

BoD commented 2 months ago

@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!

sonatard commented 2 months ago

@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.

BoD commented 2 months ago

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.

sonatard commented 2 months ago

@BoD Thank you for your prompt response. It turned out to be a simple diff.

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