Skip to content

Commit

Permalink
Pretend __typename exists on root Query when fragment matching. (#4853)
Browse files Browse the repository at this point in the history
Should fix #4848 by allowing fragments to match against the root `Query` selection set.

Note that this implementation does not inject `__typename` into the query document using
`addTypenameToDocument`, since that would needlessly increase the size of every
query/response, and we can always infer the `__typename` of the `Query`.
  • Loading branch information
benjamn authored May 23, 2019
1 parent f466635 commit 758f6f7
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 13 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
**Note:** This is a cumulative changelog that outlines all of the Apollo Client project child package changes that were bundled into a specific `apollo-client` release.

## Apollo Client (vNEXT)

### Apollo Cache In-Memory

- Pretend that `__typename` exists on the root Query when matching fragments. <br/>
[@benjamn](https://github.com/benjamn) in [#4853](https://github.com/apollographql/apollo-client/pull/4853)

## Apollo Client (2.6.0)

- In production, `invariant(condition, message)` failures will now include
Expand Down
52 changes: 48 additions & 4 deletions packages/apollo-cache-inmemory/src/__tests__/fragmentMatcher.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,50 @@
import { IntrospectionFragmentMatcher } from '../fragmentMatcher';
import { defaultNormalizedCacheFactory } from '../objectCache';
import { ReadStoreContext } from '..';
import { ReadStoreContext } from '../types';
import { InMemoryCache } from '../inMemoryCache';
import gql from 'graphql-tag';

describe('FragmentMatcher', () => {
it('can match against the root Query', () => {
const cache = new InMemoryCache({
addTypename: true,
});

const query = gql`
query AllPeople {
people {
id
name
}
...PeopleTypes
}
fragment PeopleTypes on Query {
__type(name: "Person") {
name
kind
}
}
`;

const data = {
people: [
{
__typename: 'Person',
id: 123,
name: 'Ben',
},
],
__type: {
__typename: '__Type',
name: 'Person',
kind: 'OBJECT',
},
};

cache.writeQuery({ query, data });
expect(cache.readQuery({ query })).toEqual(data);
});
});

describe('IntrospectionFragmentMatcher', () => {
it('will throw an error if match is called if it is not ready', () => {
Expand Down Expand Up @@ -42,12 +86,12 @@ describe('IntrospectionFragmentMatcher', () => {
generated: false,
};

const readStoreContext: ReadStoreContext = {
const readStoreContext = {
store,
returnPartialData: false,
hasMissingField: false,
customResolvers: {},
} as any;
cacheRedirects: {},
} as ReadStoreContext;

expect(ifm.match(idValue as any, 'Item', readStoreContext)).toBe(true);
expect(ifm.match(idValue as any, 'NotAnItem', readStoreContext)).toBe(
Expand Down
20 changes: 13 additions & 7 deletions packages/apollo-cache-inmemory/src/fragmentMatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,16 @@ export class HeuristicFragmentMatcher implements FragmentMatcherInterface {
context: ReadStoreContext,
): boolean | 'heuristic' {
const obj = context.store.get(idValue.id);
const isRootQuery = idValue.id === 'ROOT_QUERY';

if (!obj) {
// https://github.com/apollographql/apollo-client/pull/3507
return idValue.id === 'ROOT_QUERY';
return isRootQuery;
}

if (!obj.__typename) {
const { __typename = isRootQuery && 'Query' } = obj;

if (!__typename) {
if (shouldWarn()) {
invariant.warn(`You're using fragments in your queries, but either don't have the addTypename:
true option set in Apollo Client, or you are trying to write a fragment to the store without the __typename.
Expand All @@ -67,7 +70,7 @@ export class HeuristicFragmentMatcher implements FragmentMatcherInterface {
return 'heuristic';
}

if (obj.__typename === typeCondition) {
if (__typename === typeCondition) {
return true;
}

Expand Down Expand Up @@ -120,25 +123,28 @@ export class IntrospectionFragmentMatcher implements FragmentMatcherInterface {
);

const obj = context.store.get(idValue.id);
const isRootQuery = idValue.id === 'ROOT_QUERY';

if (!obj) {
// https://github.com/apollographql/apollo-client/pull/4620
return idValue.id === 'ROOT_QUERY';
return isRootQuery;
}

const { __typename = isRootQuery && 'Query' } = obj;

invariant(
obj.__typename,
__typename,
`Cannot match fragment because __typename property is missing: ${JSON.stringify(
obj,
)}`,
);

if (obj.__typename === typeCondition) {
if (__typename === typeCondition) {
return true;
}

const implementingTypes = this.possibleTypesMap[typeCondition];
if (implementingTypes && implementingTypes.indexOf(obj.__typename) > -1) {
if (implementingTypes && implementingTypes.indexOf(__typename) > -1) {
return true;
}

Expand Down
5 changes: 3 additions & 2 deletions packages/apollo-cache-inmemory/src/writeToStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,12 @@ export class StoreWriter {
// TODO we need to rewrite the fragment matchers for this to work properly and efficiently
// Right now we have to pretend that we're passing in an idValue and that there's a store
// on the context.
const idValue = toIdValue({ id: 'self', typename: undefined });
const id = dataId || 'self';
const idValue = toIdValue({ id, typename: undefined });
const fakeContext: ReadStoreContext = {
// NOTE: fakeContext always uses ObjectCache
// since this is only to ensure the return value of 'matches'
store: new ObjectCache({ self: result }),
store: new ObjectCache({ [id]: result }),
cacheRedirects: {},
};
const match = context.fragmentMatcherFunction(
Expand Down

0 comments on commit 758f6f7

Please sign in to comment.