Search code examples
graphqlapollo-clientvuejs3vue-apolloapollo-cache-inmemory

Why do I not have an Apollo cache-hit between a multi-response query and a single-item query for the same type?


I'm working on a vue3 project using @vue/apollo-composable and @graphql-codegen.

My index page does a search query. Each result from that query has a tile made on the page. I'm expecting the tile queries will be answered by the cache, but instead, they always miss.

At the page level I do this query:

query getTokens($limit: Int!) {
    tokens(limit: $limit) {
        ...tokenInfo
    }
}

Inside of the tile component I execute:

query getToken($id: uuid!){
    token(id: $id) {
        ...tokenInfo
    }
}

The fragment looks like this:

fragment tokenInfo on token {
    id
    name
}

Expectation: The cache would handle 100% of the queries inside the tile components. (I'm hoping to avoid the downfalls of serializing this data to vuex).

Reality: I get n+1 backend calls. I've tried a bunch of permutations including getting rid of the fragment. If I send the getToken call with fetchPolicy: 'cache-only' no data is returned.

The apollo client configuration is very basic:


const cache = new InMemoryCache();

const defaultClient = new ApolloClient({
  uri: 'http://localhost:8080/v1/graphql',
  cache: cache,
  connectToDevTools: true,
});

const app = createApp(App)
  .use(Store, StateKey)
  .use(router)
  .provide(DefaultApolloClient, defaultClient);

I'm also attaching a screenshot of my apollo dev tools. It appears that the cache is in fact getting populated with normalized data:

apollo-cache-screenshot

Any help would be greatly appreciated! :)


Solution

  • I've gotten this worked out thanks to @xadm's comment as well as some feedback I received on the Vue discord. Really my confusion is down to me being new to so many of these tools. Deciding to live on the edge and be a vue3 early adopter (which I love in many ways) made it even easier for me to be confused with the variance in documentation qualities right now.

    That said, here is what I've got as a solution.

    Problem: The actual problem is that, as configured, Apollo has no way to know that getTokens and getToken return the same type (token).

    Solution: The minimum configuration I've found that resolves this is as follows:

    const cache = new InMemoryCache({
      typePolicies: {
        Query: {
          fields: {
            token(_, { args, toReference }) {
              return toReference({
                __typename: 'token',
                id: args?.id,
              });
            },
          },
        },
      },
    });
    

    However, the feels.... kinda gross to me. Ideally, I'd love to see a way to just point apollo at a copy of my schema, or a schema introspection, and have it figure this out for me. If someone is aware of a better way to do that please let me know.

    Better(?) Solution: In the short term here what I feel is a slightly more scalable solution:

    type CacheRedirects = Record<string, FieldReadFunction>;
    
    function generateCacheRedirects(types: string[]): CacheRedirects {
      const redirects: CacheRedirects = {};
    
      for (const type of types) {
        redirects[type] = (_, { args, toReference }) => {
          return toReference({
            __typename: type,
            id: args?.id,
          });
        };
      }
    
      return redirects;
    }
    
    const cache = new InMemoryCache({
      typePolicies: {
        Query: {
          fields: {
            ...generateCacheRedirects(['token']),
          },
        },
      },
    });
    

    If anyone has any improvements on these, please add a comment/solution! :)