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

806 Views Asked by At

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! :)

1

There are 1 best solutions below

0
On BEST ANSWER

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! :)