diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a9364ee534..c7d94bceb26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,9 @@ - The schema link package (`@apollo/client/link/schema`) will now validate incoming queries against its client-side schema, and return `errors` as a GraphQL server would.
[@amannn](https://github.com/amannn) in [#7094](https://github.com/apollographql/apollo-client/pull/7094) +- Allow optional arguments in `keyArgs: [...]` arrays for `InMemoryCache` field policies.
+ [@benjamn](https://github.com/benjamn) in [#7109](https://github.com/apollographql/apollo-client/pull/7109) + ## Apollo Client 3.2.2 ## Bug Fixes diff --git a/src/cache/inmemory/__tests__/__snapshots__/policies.ts.snap b/src/cache/inmemory/__tests__/__snapshots__/policies.ts.snap index 1ebbd93b8f7..296372a0cd1 100644 --- a/src/cache/inmemory/__tests__/__snapshots__/policies.ts.snap +++ b/src/cache/inmemory/__tests__/__snapshots__/policies.ts.snap @@ -839,6 +839,378 @@ Object { } `; +exports[`type policies field policies can include optional arguments in keyArgs 1`] = ` +Object { + "Author:{\\"name\\":\\"Nadia Eghbal\\"}": Object { + "__typename": "Author", + "name": "Nadia Eghbal", + "writings:{\\"type\\":\\"Book\\"}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + }, + "ROOT_QUERY": Object { + "__typename": "Query", + "author": Object { + "__ref": "Author:{\\"name\\":\\"Nadia Eghbal\\"}", + }, + }, +} +`; + +exports[`type policies field policies can include optional arguments in keyArgs 2`] = ` +Object { + "Author:{\\"name\\":\\"Nadia Eghbal\\"}": Object { + "__typename": "Author", + "name": "Nadia Eghbal", + "writings:{\\"a\\":1,\\"b\\":2,\\"type\\":\\"Book\\"}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"type\\":\\"Book\\"}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + }, + "ROOT_QUERY": Object { + "__typename": "Query", + "author": Object { + "__ref": "Author:{\\"name\\":\\"Nadia Eghbal\\"}", + }, + }, +} +`; + +exports[`type policies field policies can include optional arguments in keyArgs 3`] = ` +Object { + "Author:{\\"name\\":\\"Nadia Eghbal\\"}": Object { + "__typename": "Author", + "name": "Nadia Eghbal", + "writings:{\\"a\\":1,\\"b\\":2,\\"type\\":\\"Book\\"}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"a\\":1,\\"b\\":2}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"type\\":\\"Book\\"}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + }, + "ROOT_QUERY": Object { + "__typename": "Query", + "author": Object { + "__ref": "Author:{\\"name\\":\\"Nadia Eghbal\\"}", + }, + }, +} +`; + +exports[`type policies field policies can include optional arguments in keyArgs 4`] = ` +Object { + "Author:{\\"name\\":\\"Nadia Eghbal\\"}": Object { + "__typename": "Author", + "name": "Nadia Eghbal", + "writings:{\\"a\\":1,\\"b\\":2,\\"type\\":\\"Book\\"}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"a\\":1,\\"b\\":2}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"b\\":2}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"type\\":\\"Book\\"}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + }, + "ROOT_QUERY": Object { + "__typename": "Query", + "author": Object { + "__ref": "Author:{\\"name\\":\\"Nadia Eghbal\\"}", + }, + }, +} +`; + +exports[`type policies field policies can include optional arguments in keyArgs 5`] = ` +Object { + "Author:{\\"name\\":\\"Nadia Eghbal\\"}": Object { + "__typename": "Author", + "name": "Nadia Eghbal", + "writings:{\\"a\\":1,\\"b\\":2,\\"type\\":\\"Book\\"}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"a\\":1,\\"b\\":2}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"a\\":3}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"b\\":2}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"type\\":\\"Book\\"}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + }, + "ROOT_QUERY": Object { + "__typename": "Query", + "author": Object { + "__ref": "Author:{\\"name\\":\\"Nadia Eghbal\\"}", + }, + }, +} +`; + +exports[`type policies field policies can include optional arguments in keyArgs 6`] = ` +Object { + "Author:{\\"name\\":\\"Nadia Eghbal\\"}": Object { + "__typename": "Author", + "name": "Nadia Eghbal", + "writings:{\\"a\\":1,\\"b\\":2,\\"type\\":\\"Book\\"}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"a\\":1,\\"b\\":2}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"a\\":3}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"b\\":2}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"type\\":\\"Book\\"}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + }, + "ROOT_QUERY": Object { + "__typename": "Query", + "author": Object { + "__ref": "Author:{\\"name\\":\\"Nadia Eghbal\\"}", + }, + }, +} +`; + +exports[`type policies field policies can include optional arguments in keyArgs 7`] = ` +Object { + "Author:{\\"name\\":\\"Nadia Eghbal\\"}": Object { + "__typename": "Author", + "name": "Nadia Eghbal", + "writings:{\\"a\\":1,\\"b\\":2,\\"type\\":\\"Book\\"}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"a\\":1,\\"b\\":2}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"a\\":3}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"b\\":2}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"b\\":4}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"type\\":\\"Book\\"}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + }, + "ROOT_QUERY": Object { + "__typename": "Query", + "author": Object { + "__ref": "Author:{\\"name\\":\\"Nadia Eghbal\\"}", + }, + }, +} +`; + +exports[`type policies field policies can include optional arguments in keyArgs 8`] = ` +Object { + "Author:{\\"name\\":\\"Nadia Eghbal\\"}": Object { + "__typename": "Author", + "name": "Nadia Eghbal", + "writings": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"a\\":1,\\"b\\":2,\\"type\\":\\"Book\\"}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"a\\":1,\\"b\\":2}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"a\\":3}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"b\\":2}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"b\\":4}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{\\"type\\":\\"Book\\"}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + "writings:{}": Array [ + Object { + "__typename": "Book", + "isbn": "0578675862", + "title": "Working in Public: The Making and Maintenance of Open Source Software", + }, + ], + }, + "ROOT_QUERY": Object { + "__typename": "Query", + "author": Object { + "__ref": "Author:{\\"name\\":\\"Nadia Eghbal\\"}", + }, + }, +} +`; + exports[`type policies field policies read, merge, and modify functions can access options.storage 1`] = ` Object { "ROOT_QUERY": Object { diff --git a/src/cache/inmemory/__tests__/policies.ts b/src/cache/inmemory/__tests__/policies.ts index dcf75babe1b..4b9c5ad0006 100644 --- a/src/cache/inmemory/__tests__/policies.ts +++ b/src/cache/inmemory/__tests__/policies.ts @@ -2,7 +2,7 @@ import gql from "graphql-tag"; import { InMemoryCache } from "../inMemoryCache"; import { ReactiveVar, makeVar } from "../reactiveVars"; -import { Reference, StoreObject, ApolloClient, NetworkStatus, TypedDocumentNode } from "../../../core"; +import { Reference, StoreObject, ApolloClient, NetworkStatus, TypedDocumentNode, DocumentNode } from "../../../core"; import { MissingFieldError } from "../.."; import { relayStylePagination } from "../../../utilities"; import { MockLink } from '../../../utilities/testing/mocking/mockLink'; @@ -742,6 +742,190 @@ describe("type policies", function () { }); }); + it("can include optional arguments in keyArgs", function () { + const cache = new InMemoryCache({ + typePolicies: { + Author: { + keyFields: ["name"], + fields: { + writings: { + keyArgs: ["a", "b", "type"] + }, + }, + }, + }, + }); + + const data = { + author: { + __typename: "Author", + name: "Nadia Eghbal", + writings: [{ + __typename: "Book", + isbn: "0578675862", + title: "Working in Public: The Making and Maintenance of " + + "Open Source Software", + }], + }, + }; + + function check( + query: DocumentNode | TypedDocumentNode, + variables?: TVars, + ) { + cache.writeQuery({ query, variables, data }); + expect(cache.readQuery({ query, variables })).toEqual(data); + } + + check(gql` + query { + author { + name + writings(type: "Book") { + ... on Book { + title + isbn + } + } + } + } + `); + expect(cache.extract()).toMatchSnapshot(); + + check(gql` + query { + author { + name + writings(type: "Book", b: 2, a: 1) { + ... on Book { + title + isbn + } + } + } + } + `); + expect(cache.extract()).toMatchSnapshot(); + + check(gql` + query { + author { + name + writings(b: 2, a: 1) { + ... on Book { + title + isbn + } + } + } + } + `); + expect(cache.extract()).toMatchSnapshot(); + + check(gql` + query { + author { + name + writings(b: 2) { + ... on Book { + title + isbn + } + } + } + } + `); + expect(cache.extract()).toMatchSnapshot(); + + check(gql` + query { + author { + name + writings(a: 3) { + ... on Book { + title + isbn + } + } + } + } + `); + expect(cache.extract()).toMatchSnapshot(); + + check(gql` + query { + author { + name + writings(unrelated: "oyez") { + ... on Book { + title + isbn + } + } + } + } + `); + expect(cache.extract()).toMatchSnapshot(); + + check(gql` + query AuthorWritings ($type: String) { + author { + name + writings(b: 4, type: $type, unrelated: "oyez") { + ... on Book { + title + isbn + } + } + } + } + `, { type: void 0 as any }); + expect(cache.extract()).toMatchSnapshot(); + + check(gql` + query { + author { + name + writings { + ... on Book { + title + isbn + } + } + } + } + `); + expect(cache.extract()).toMatchSnapshot(); + + const storeFieldNames: string[] = []; + + cache.modify({ + id: cache.identify({ + __typename: "Author", + name: "Nadia Eghbal", + }), + + fields: { + writings(value, { storeFieldName }) { + storeFieldNames.push(storeFieldName); + expect(value).toEqual(data.author.writings); + return value; + }, + }, + }) + + expect(storeFieldNames.sort()).toEqual([ + "writings", + 'writings:{"a":1,"b":2,"type":"Book"}', + 'writings:{"a":1,"b":2}', + 'writings:{"a":3}', + 'writings:{"b":2}', + 'writings:{"b":4}', + 'writings:{"type":"Book"}', + "writings:{}", + ]); + }); + it("can return KeySpecifier arrays from keyArgs functions", function () { const cache = new InMemoryCache({ typePolicies: { diff --git a/src/cache/inmemory/policies.ts b/src/cache/inmemory/policies.ts index 8f2160ba9f5..b9dfc376a1b 100644 --- a/src/cache/inmemory/policies.ts +++ b/src/cache/inmemory/policies.ts @@ -882,7 +882,7 @@ function keyArgsFnFromSpecifier( ): KeyArgsFunction { return (args, context) => { return args ? `${context.fieldName}:${ - JSON.stringify(computeKeyObject(args, specifier)) + JSON.stringify(computeKeyObject(args, specifier, false)) }` : context.fieldName; }; } @@ -907,7 +907,7 @@ function keyFieldsFnFromSpecifier( } const keyObject = context.keyObject = - computeKeyObject(object, specifier, aliasMap); + computeKeyObject(object, specifier, true, aliasMap); return `${context.typename}:${JSON.stringify(keyObject)}`; }; @@ -959,6 +959,7 @@ function makeAliasMap( function computeKeyObject( response: Record, specifier: KeySpecifier, + strict: boolean, aliasMap?: AliasMap, ): Record { // The order of adding properties to keyObj affects its JSON serialization, @@ -971,17 +972,17 @@ function computeKeyObject( if (typeof prevKey === "string") { const subsets = aliasMap && aliasMap.subsets; const subset = subsets && subsets[prevKey]; - keyObj[prevKey] = computeKeyObject(response[prevKey], s, subset); + keyObj[prevKey] = computeKeyObject(response[prevKey], s, strict, subset); } } else { const aliases = aliasMap && aliasMap.aliases; const responseName = aliases && aliases[s] || s; - invariant( - hasOwn.call(response, responseName), - // TODO Make this appropriate for keyArgs as well - `Missing field '${responseName}' while computing key fields`, - ); - keyObj[prevKey = s] = response[responseName]; + if (hasOwn.call(response, responseName)) { + keyObj[prevKey = s] = response[responseName]; + } else { + invariant(!strict, `Missing field '${responseName}' while computing key fields`); + prevKey = void 0; + } } }); return keyObj;