Skip to content

Commit

Permalink
Provide default empty StoreObject for root IDs like ROOT_QUERY.
Browse files Browse the repository at this point in the history
This change means the existence of root objects like ROOT_QUERY and
ROOT_MUTATION will no longer depend on whether other queries/mutations
have previously written data into the cache.

This matters because (until just recently) cache.read would return null
for a completely missing ROOT_QUERY object, but throw missing field errors
if ROOT_QUERY existed but did not have the requested fields.

In other words, this commit hides the difference between the absence of
ROOT_QUERY and its incompleteness, so unrelated cache writes can no longer
unexpectedly influence the null-returning vs. exception-throwing behavior
of cache.read.

As an additional motivation, with AC3 field policies, it's possible now to
define synthetic root query fields that have a chance of returning useful
values even if the cache is completely empty. Providing a default empty
object for root IDs like ROOT_QUERY is important to let those read
functions be called, instead of prematurely returning null from cache.read
when ROOT_QUERY does not exist.

Note: this PR builds on PR #7098.
  • Loading branch information
benjamn committed Sep 30, 2020
1 parent c97fbd3 commit 63c7cb0
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 2 deletions.
52 changes: 52 additions & 0 deletions src/cache/inmemory/__tests__/readFromStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,58 @@ describe('reading from the store', () => {
});
});

it("read functions for root query fields work with empty cache", () => {
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
uuid() {
return "8d573b9c-cfcf-4e3e-98dd-14d255af577e";
},
null() {
return null;
},
}
},
},
});

expect(cache.readQuery({
query: gql`query { uuid null }`,
})).toEqual({
uuid: "8d573b9c-cfcf-4e3e-98dd-14d255af577e",
null: null,
});

expect(cache.extract()).toEqual({});

expect(cache.readFragment({
id: "ROOT_QUERY",
fragment: gql`
fragment UUIDFragment on Query {
null
uuid
}
`,
})).toEqual({
uuid: "8d573b9c-cfcf-4e3e-98dd-14d255af577e",
null: null,
});

expect(cache.extract()).toEqual({});

expect(cache.readFragment({
id: "does not exist",
fragment: gql`
fragment F on Never {
whatever
}
`,
})).toBe(null);

expect(cache.extract()).toEqual({});
});

it("custom read functions can map/filter dangling references", () => {
const cache = new InMemoryCache({
typePolicies: {
Expand Down
14 changes: 12 additions & 2 deletions src/cache/inmemory/entityStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,18 @@ export abstract class EntityStore implements NormalizedCache {
// should not rely on this dependency, since the contents could change
// without the object being added or removed.
if (dependOnExistence) this.group.depend(dataId, "__exists");
return hasOwn.call(this.data, dataId) ? this.data[dataId] :
this instanceof Layer ? this.parent.lookup(dataId, dependOnExistence) : void 0;

if (hasOwn.call(this.data, dataId)) {
return this.data[dataId];
}

if (this instanceof Layer) {
return this.parent.lookup(dataId, dependOnExistence);
}

if (this.policies.rootTypenamesById[dataId]) {
return Object.create(null);
}
}

public merge(dataId: string, incoming: StoreObject): void {
Expand Down

0 comments on commit 63c7cb0

Please sign in to comment.