dividab/graphql-norm

Improve fragment matching (resolveType)

Opened this issue · 4 comments

Currently the default resolveType function assumes that every object has a __typename.

The only time resolveType is called is when we need to determine if a certain fragment should be applied or not. Most of the time you have a single fragment but becuase of union types you can have multiple fragments on the same object. Consider this example:

query GetBooks {
  schoolBooks {
    title
    ... on TextBook {
      classes {
        name
      }
    }
    ... on ColoringBook {
      colors {
        name
      }
    }
  }
}

Here the first fragment should be applied if the SchoolBook.__typename === "TextBook" and the second fragment if SchoolBook.__typename === "ColoringBook". In order to know this we run resolveType() and compare the __typename.

You can run into problems with denormalize if you don't have __typename everywhere (even if you had it for normalize).

Perhaps we can replace resolveType with something like shouldApplyFragment which by default returns true so all fragments are always applied. This way the simple cases with a single fragment would work without having to add __typename everywhere in the query.

It seems apollo has something called FragmentMatcher, and IntrospectionFragmentMatcher which seems to serve a similar purpose.

By default, Apollo Client's cache will use a heuristic fragment matcher, which assumes that a fragment matched if the result included all the fields in its selection set, and didn't match when any field was missing.

Apollo seems to be moving away from fragment matcher:

https://github.com/apollographql/apollo-client/blob/379bbb024a0909714e949d76584a3b76067649cb/CHANGELOG.md

[BREAKING] FragmentMatcher, HeuristicFragmentMatcher, and IntrospectionFragmentMatcher have all been removed. We now recommend using InMemoryCache’s possibleTypes option instead. For more information see the Defining possibleTypes manually section of the docs.
@benjamn in #5073

This is the apollo way now:

https://www.apollographql.com/docs/react/v3.0-beta/data/fragments/#defining-possibletypes-manually

You can pass a possibleTypes option to the InMemoryCache constructor to specify supertype-subtype relationships in your schema. This object maps the name of an interface or union type (the supertype) to the types that implement or belong to it (the subtypes).

Here's an example possibleTypes declaration:

const cache = new InMemoryCache({
  possibleTypes: {
    Character: ["Jedi", "Droid"],
    Test: ["PassingTest", "FailingTest", "SkippedTest"],
    Snake: ["Viper", "Python"],
  },
});

I guess this means they always know '__typename`. Perhaps their old solution is a better fit for graphql-norm.

This seems to be the old apollo way:

export type FragmentMatcher = (
  rootValue: any,
  typeCondition: string,
  context: any,
) => boolean;

So some kind of callback that returns a boolean seems like the way to go...