Skip to content

Commit

Permalink
Merge pull request #8643 from apollographql/issue-8639-nested-keyFiel…
Browse files Browse the repository at this point in the history
…ds-problem

Fix error thrown by nested `keyFields: ["a", ["b", "c"], "d"]` syntax when some of the fields are aliased
  • Loading branch information
benjamn authored Aug 13, 2021
2 parents bd03ff0 + 6486740 commit 1d5664c
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 6 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## Apollo Client 3.4.8 (not yet released)

### Bug Fixes

- Fix error thrown by nested `keyFields: ["a", ["b", "c"], "d"]` type policies when writing results into the cache where any of the key fields (`.a`, `.a.b`, `.a.c`, or `.d`) have been renamed by query field alias syntax. <br/>
[@benjamn](https://github.com/benjamn) in [#8643](https://github.com/apollographql/apollo-client/pull/8643)

## Apollo Client 3.4.7

### Bug Fixes
Expand Down
57 changes: 57 additions & 0 deletions src/cache/inmemory/__tests__/policies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,63 @@ describe("type policies", function () {
checkAuthorName(cache);
});

it("can specify nested keyFields with alias", function () {
const cache = new InMemoryCache({
typePolicies: {
Book: {
keyFields: ["title", "author", ["name"]],
},
},
});

const aliasQuery = gql`
query {
book {
title
writer: author {
alias: name
}
}
}
`;

const { author, ...rest } = theInformationBookData;
const aliasBookData = {
...rest,
writer: {
alias: author.name,
},
};

cache.writeQuery({
query: aliasQuery,
data: {
book: aliasBookData,
},
});

expect(cache.extract(true)).toEqual({
ROOT_QUERY: {
__typename: "Query",
book: {
__ref: 'Book:{"title":"The Information","author":{"name":"James Gleick"}}',
},
},
'Book:{"title":"The Information","author":{"name":"James Gleick"}}': {
__typename: "Book",
title: "The Information",
// Note that "author" and "name" are stored internally, since they are
// the true names of their fields (according to the schema), despite the
// writer:author and alias:name aliases.
author: {
name: "James Gleick"
},
},
});

checkAuthorName(cache);
});

it("keeps keyFields in specified order", function () {
const cache = new InMemoryCache({
typePolicies: {
Expand Down
20 changes: 14 additions & 6 deletions src/cache/inmemory/policies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1067,22 +1067,30 @@ function computeKeyObject(
// so we are careful to build keyObj in the order of keys given in
// specifier.
const keyObj = Object.create(null);
let prevKey: string | undefined;

// The lastResponseKey variable tracks keys as seen in actual GraphQL response
// objects, potentially affected by aliasing. The lastActualKey variable
// tracks the corresponding key after removing aliases.
let lastResponseKey: string | undefined;
let lastActualKey: string | undefined;

specifier.forEach(s => {
if (Array.isArray(s)) {
if (typeof prevKey === "string") {
if (typeof lastActualKey === "string" &&
typeof lastResponseKey === "string") {
const subsets = aliasMap && aliasMap.subsets;
const subset = subsets && subsets[prevKey];
keyObj[prevKey] = computeKeyObject(response[prevKey], s, strict, subset);
const subset = subsets && subsets[lastActualKey];
keyObj[lastActualKey] =
computeKeyObject(response[lastResponseKey], s, strict, subset);
}
} else {
const aliases = aliasMap && aliasMap.aliases;
const responseName = aliases && aliases[s] || s;
if (hasOwn.call(response, responseName)) {
keyObj[prevKey = s] = response[responseName];
keyObj[lastActualKey = s] = response[lastResponseKey = responseName];
} else {
invariant(!strict, `Missing field '${responseName}' while computing key fields`);
prevKey = void 0;
lastResponseKey = lastActualKey = void 0;
}
}
});
Expand Down

0 comments on commit 1d5664c

Please sign in to comment.