From 72d2b54d97d43f64bcf19328369da6b2693b2747 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Mon, 16 Mar 2020 10:49:41 +0000 Subject: [PATCH 1/4] Update Store constructor to accept options --- exchanges/graphcache/src/cacheExchange.ts | 8 +----- exchanges/graphcache/src/store/store.ts | 32 +++++++++++++---------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/exchanges/graphcache/src/cacheExchange.ts b/exchanges/graphcache/src/cacheExchange.ts index 0d66a67e14..efa654e49e 100644 --- a/exchanges/graphcache/src/cacheExchange.ts +++ b/exchanges/graphcache/src/cacheExchange.ts @@ -88,13 +88,7 @@ export const cacheExchange = (opts?: CacheExchangeOpts): Exchange => ({ }) => { if (!opts) opts = {}; - const store = new Store( - opts.schema, - opts.resolvers, - opts.updates, - opts.optimistic, - opts.keys - ); + const store = new Store(opts); let hydration: void | Promise; if (opts.storage) { diff --git a/exchanges/graphcache/src/store/store.ts b/exchanges/graphcache/src/store/store.ts index f335b6b8b1..4e9ed36273 100644 --- a/exchanges/graphcache/src/store/store.ts +++ b/exchanges/graphcache/src/store/store.ts @@ -30,6 +30,14 @@ import * as InMemoryData from './data'; type RootField = 'query' | 'mutation' | 'subscription'; +export interface StoreOpts { + updates?: Partial; + resolvers?: ResolverConfig; + optimistic?: OptimisticMutationConfig; + keys?: KeyingConfig; + schema?: IntrospectionQuery; +} + export class Store implements Cache { data: InMemoryData.InMemoryData; @@ -42,27 +50,23 @@ export class Store implements Cache { rootFields: { query: string; mutation: string; subscription: string }; rootNames: { [name: string]: RootField }; - constructor( - rawSchema?: IntrospectionQuery, - resolvers?: ResolverConfig, - updates?: Partial, - optimisticMutations?: OptimisticMutationConfig, - keys?: KeyingConfig - ) { - this.resolvers = resolvers || {}; - this.optimisticMutations = optimisticMutations || {}; - this.keys = keys || {}; + constructor(opts?: StoreOpts) { + if (!opts) opts = {}; + + this.resolvers = opts.resolvers || {}; + this.optimisticMutations = opts.optimistic || {}; + this.keys = opts.keys || {}; this.updates = { - Mutation: (updates && updates.Mutation) || {}, - Subscription: (updates && updates.Subscription) || {}, + Mutation: (opts.updates && opts.updates.Mutation) || {}, + Subscription: (opts.updates && opts.updates.Subscription) || {}, } as UpdatesConfig; let queryName = 'Query'; let mutationName = 'Mutation'; let subscriptionName = 'Subscription'; - if (rawSchema) { - const schema = (this.schema = buildClientSchema(rawSchema)); + if (opts.schema) { + const schema = (this.schema = buildClientSchema(opts.schema)); const queryType = schema.getQueryType(); const mutationType = schema.getMutationType(); const subscriptionType = schema.getSubscriptionType(); From fe2abcd8e8f9010a1b959a0c14e8b146b749fc58 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Mon, 16 Mar 2020 10:49:53 +0000 Subject: [PATCH 2/4] Adjust tests for change --- .../src/extras/relayPagination.test.ts | 112 ++++++++++-------- .../src/extras/simplePagination.test.ts | 40 ++++--- .../src/operations/invalidate.test.ts | 2 +- .../graphcache/src/operations/query.test.ts | 6 +- .../graphcache/src/operations/write.test.ts | 2 +- exchanges/graphcache/src/store/store.test.ts | 20 ++-- .../src/test-utils/examples-1.test.ts | 90 +++++++------- .../src/test-utils/examples-2.test.ts | 96 ++++++++------- 8 files changed, 207 insertions(+), 161 deletions(-) diff --git a/exchanges/graphcache/src/extras/relayPagination.test.ts b/exchanges/graphcache/src/extras/relayPagination.test.ts index e8e517a3be..afa7301aad 100644 --- a/exchanges/graphcache/src/extras/relayPagination.test.ts +++ b/exchanges/graphcache/src/extras/relayPagination.test.ts @@ -34,9 +34,11 @@ it('works with forward pagination', () => { } `; - const store = new Store(undefined, { - Query: { - items: relayPagination(), + const store = new Store({ + resolvers: { + Query: { + items: relayPagination(), + }, }, }); @@ -102,9 +104,11 @@ it('works with backwards pagination', () => { } `; - const store = new Store(undefined, { - Query: { - items: relayPagination(), + const store = new Store({ + resolvers: { + Query: { + items: relayPagination(), + }, }, }); @@ -170,9 +174,11 @@ it('handles duplicate edges', () => { } `; - const store = new Store(undefined, { - Query: { - items: relayPagination(), + const store = new Store({ + resolvers: { + Query: { + items: relayPagination(), + }, }, }); @@ -244,9 +250,11 @@ it('works with simultaneous forward and backward pagination (outwards merging)', } `; - const store = new Store(undefined, { - Query: { - items: relayPagination({ mergeMode: 'outwards' }), + const store = new Store({ + resolvers: { + Query: { + items: relayPagination({ mergeMode: 'outwards' }), + }, }, }); @@ -360,9 +368,11 @@ it('works with simultaneous forward and backward pagination (inwards merging)', } `; - const store = new Store(undefined, { - Query: { - items: relayPagination({ mergeMode: 'inwards' }), + const store = new Store({ + resolvers: { + Query: { + items: relayPagination({ mergeMode: 'inwards' }), + }, }, }); @@ -474,9 +484,11 @@ it('prevents overlapping of pagination on different arguments', () => { } `; - const store = new Store(undefined, { - Query: { - items: relayPagination(), + const store = new Store({ + resolvers: { + Query: { + items: relayPagination(), + }, }, }); @@ -544,9 +556,11 @@ it('returns an empty array of edges when the cache has zero edges stored', () => } `; - const store = new Store(undefined, { - Query: { - items: relayPagination(), + const store = new Store({ + resolvers: { + Query: { + items: relayPagination(), + }, }, }); @@ -581,9 +595,11 @@ it('returns other fields on the same level as the edges', () => { } `; - const store = new Store(undefined, { - Query: { - items: relayPagination(), + const store = new Store({ + resolvers: { + Query: { + items: relayPagination(), + }, }, }); @@ -632,14 +648,14 @@ it('returns a subset of the cached items if the query requests less items than t } `; - const store = new Store( - require('../test-utils/relayPagination_schema.json'), - { + const store = new Store({ + schema: require('../test-utils/relayPagination_schema.json'), + resolvers: { Query: { items: relayPagination({ mergeMode: 'outwards' }), }, - } - ); + }, + }); const results = { __typename: 'Query', @@ -690,14 +706,14 @@ it("returns the cached items even if they don't fullfil the query", () => { } `; - const store = new Store( - require('../test-utils/relayPagination_schema.json'), - { + const store = new Store({ + schema: require('../test-utils/relayPagination_schema.json'), + resolvers: { Query: { items: relayPagination(), }, - } - ); + }, + }); const results = { __typename: 'Query', @@ -752,14 +768,14 @@ it('returns the cached items even when they come from a different query', () => } `; - const store = new Store( - require('../test-utils/relayPagination_schema.json'), - { + const store = new Store({ + schema: require('../test-utils/relayPagination_schema.json'), + resolvers: { Query: { items: relayPagination(), }, - } - ); + }, + }); const results = { __typename: 'Query', @@ -810,14 +826,14 @@ it('caches and retrieves correctly queries with inwards pagination', () => { } `; - const store = new Store( - require('../test-utils/relayPagination_schema.json'), - { + const store = new Store({ + schema: require('../test-utils/relayPagination_schema.json'), + resolvers: { Query: { items: relayPagination(), }, - } - ); + }, + }); const results = { __typename: 'Query', @@ -872,9 +888,11 @@ it('does not include a previous result when adding parameters', () => { } `; - const store = new Store(undefined, { - Query: { - items: relayPagination(), + const store = new Store({ + resolvers: { + Query: { + items: relayPagination(), + }, }, }); diff --git a/exchanges/graphcache/src/extras/simplePagination.test.ts b/exchanges/graphcache/src/extras/simplePagination.test.ts index db60750345..bcf326f2ce 100644 --- a/exchanges/graphcache/src/extras/simplePagination.test.ts +++ b/exchanges/graphcache/src/extras/simplePagination.test.ts @@ -14,9 +14,11 @@ it('works with simple pagination', () => { } `; - const store = new Store(undefined, { - Query: { - persons: simplePagination(), + const store = new Store({ + resolvers: { + Query: { + persons: simplePagination(), + }, }, }); @@ -82,9 +84,11 @@ it('handles duplicates', () => { } `; - const store = new Store(undefined, { - Query: { - persons: simplePagination(), + const store = new Store({ + resolvers: { + Query: { + persons: simplePagination(), + }, }, }); @@ -138,9 +142,11 @@ it('should not return previous result when adding a parameter', () => { } `; - const store = new Store(undefined, { - Query: { - persons: simplePagination(), + const store = new Store({ + resolvers: { + Query: { + persons: simplePagination(), + }, }, }); @@ -187,9 +193,11 @@ it('should preserve the correct order', () => { } `; - const store = new Store(undefined, { - Query: { - persons: simplePagination(), + const store = new Store({ + resolvers: { + Query: { + persons: simplePagination(), + }, }, }); @@ -243,9 +251,11 @@ it('prevents overlapping of pagination on different arguments', () => { } `; - const store = new Store(undefined, { - Query: { - persons: simplePagination(), + const store = new Store({ + resolvers: { + Query: { + persons: simplePagination(), + }, }, }); diff --git a/exchanges/graphcache/src/operations/invalidate.test.ts b/exchanges/graphcache/src/operations/invalidate.test.ts index 1d6f3a21b2..9068a15e1e 100644 --- a/exchanges/graphcache/src/operations/invalidate.test.ts +++ b/exchanges/graphcache/src/operations/invalidate.test.ts @@ -29,7 +29,7 @@ describe('Query', () => { }); beforeEach(() => { - store = new Store(schema); + store = new Store({ schema }); write( store, { query: TODO_QUERY }, diff --git a/exchanges/graphcache/src/operations/query.test.ts b/exchanges/graphcache/src/operations/query.test.ts index 7f296c1144..cc83f38a11 100644 --- a/exchanges/graphcache/src/operations/query.test.ts +++ b/exchanges/graphcache/src/operations/query.test.ts @@ -34,7 +34,7 @@ describe('Query', () => { }); beforeEach(() => { - store = new Store(schema); + store = new Store({ schema }); write( store, { query: TODO_QUERY }, @@ -120,7 +120,7 @@ describe('Query', () => { } `; // Use new store to ensure bug reproduction - const store = new Store(schema); + const store = new Store({ schema }); let { data } = query(store, { query: VALID_QUERY }); expect(data).toEqual(null); @@ -151,7 +151,7 @@ describe('Query', () => { } `; - const store = new Store(alteredRoot); + const store = new Store({ schema: alteredRoot }); let { data } = query(store, { query: QUERY }); expect(data).toEqual(null); diff --git a/exchanges/graphcache/src/operations/write.test.ts b/exchanges/graphcache/src/operations/write.test.ts index 4768a94e09..16c2ceda1a 100644 --- a/exchanges/graphcache/src/operations/write.test.ts +++ b/exchanges/graphcache/src/operations/write.test.ts @@ -28,7 +28,7 @@ describe('Query', () => { }); beforeEach(() => { - store = new Store(schema); + store = new Store({ schema }); write( store, { query: TODO_QUERY }, diff --git a/exchanges/graphcache/src/store/store.test.ts b/exchanges/graphcache/src/store/store.test.ts index 67a7690326..7fee5d2a24 100644 --- a/exchanges/graphcache/src/store/store.test.ts +++ b/exchanges/graphcache/src/store/store.test.ts @@ -34,9 +34,11 @@ const Todos = gql` describe('Store with KeyingConfig', () => { it('generates keys from custom keying function', () => { - const store = new Store(undefined, undefined, undefined, undefined, { - User: () => 'me', - None: () => null, + const store = new Store({ + keys: { + User: () => 'me', + None: () => null, + }, }); expect(store.keyOfEntity({ __typename: 'Any', id: '123' })).toBe('Any:123'); @@ -53,11 +55,13 @@ describe('Store with OptimisticMutationConfig', () => { let store, todosData; beforeEach(() => { - store = new Store(undefined, undefined, undefined, { - addTodo: variables => { - return { - ...variables, - } as Data; + store = new Store({ + optimistic: { + addTodo: variables => { + return { + ...variables, + } as Data; + }, }, }); todosData = { diff --git a/exchanges/graphcache/src/test-utils/examples-1.test.ts b/exchanges/graphcache/src/test-utils/examples-1.test.ts index e0f39521b7..30195e16cc 100644 --- a/exchanges/graphcache/src/test-utils/examples-1.test.ts +++ b/exchanges/graphcache/src/test-utils/examples-1.test.ts @@ -55,7 +55,7 @@ const NestedClearNameTodo = gql` `; it('passes the "getting-started" example', () => { - const store = new Store(undefined); + const store = new Store(); const todosData = { __typename: 'Query', todos: [ @@ -163,10 +163,12 @@ it('resolves missing, nullable arguments on fields', () => { }); it('should link entities', () => { - const store = new Store(undefined, { - Query: { - todo: (_parent, args) => { - return { __typename: 'Todo', ...args }; + const store = new Store({ + resolvers: { + Query: { + todo: (_parent, args) => { + return { __typename: 'Todo', ...args }; + }, }, }, }); @@ -194,8 +196,10 @@ it('should link entities', () => { }); it('respects property-level resolvers when given', () => { - const store = new Store(undefined, { - Todo: { text: () => 'hi' }, + const store = new Store({ + resolvers: { + Todo: { text: () => 'hi' }, + }, }); const todosData = { __typename: 'Query', @@ -254,31 +258,33 @@ it('respects property-level resolvers when given', () => { }); it('respects Mutation update functions', () => { - const store = new Store(undefined, undefined, { - Mutation: { - toggleTodo: function toggleTodo(result, _, cache) { - cache.updateQuery({ query: Todos }, data => { - if ( - data && - data.todos && - result && - result.toggleTodo && - (result.toggleTodo as any).id === '1' - ) { - data.todos[1] = { - id: '1', - text: `${data.todos[1].text} (Updated)`, - complete: (result.toggleTodo as any).complete, - __typename: 'Todo', - }; - } else if (data && data.todos) { - data.todos[Number((result.toggleTodo as any).id)] = { - ...data.todos[Number((result.toggleTodo as any).id)], - complete: (result.toggleTodo as any).complete, - }; - } - return data as Data; - }); + const store = new Store({ + updates: { + Mutation: { + toggleTodo: function toggleTodo(result, _, cache) { + cache.updateQuery({ query: Todos }, data => { + if ( + data && + data.todos && + result && + result.toggleTodo && + (result.toggleTodo as any).id === '1' + ) { + data.todos[1] = { + id: '1', + text: `${data.todos[1].text} (Updated)`, + complete: (result.toggleTodo as any).complete, + __typename: 'Todo', + }; + } else if (data && data.todos) { + data.todos[Number((result.toggleTodo as any).id)] = { + ...data.todos[Number((result.toggleTodo as any).id)], + complete: (result.toggleTodo as any).complete, + }; + } + return data as Data; + }); + }, }, }, }); @@ -342,15 +348,17 @@ it('respects Mutation update functions', () => { }); it('correctly resolves optimistic updates on Relay schemas', () => { - const store = new Store(undefined, undefined, undefined, { - updateItem: variables => ({ - __typename: 'UpdateItemPayload', - item: { - __typename: 'Item', - id: variables.id as string, - name: 'Offline', - }, - }), + const store = new Store({ + optimistic: { + updateItem: variables => ({ + __typename: 'UpdateItemPayload', + item: { + __typename: 'Item', + id: variables.id as string, + name: 'Offline', + }, + }), + }, }); const queryData = { diff --git a/exchanges/graphcache/src/test-utils/examples-2.test.ts b/exchanges/graphcache/src/test-utils/examples-2.test.ts index 37cce338c2..486b99aa2d 100644 --- a/exchanges/graphcache/src/test-utils/examples-2.test.ts +++ b/exchanges/graphcache/src/test-utils/examples-2.test.ts @@ -52,28 +52,30 @@ const Pagination = gql` `; it('allows custom resolvers to resolve nested, unkeyed data', () => { - const store = new Store(undefined, { - Query: { - todos: () => ({ - __typename: 'TodosConnection', - edges: [ - { - __typename: 'TodoEdge', - node: { - __typename: 'Todo', - id: '1', - // The `complete` field will be overwritten here, but we're - // leaving out the `text` field - complete: true, + const store = new Store({ + resolvers: { + Query: { + todos: () => ({ + __typename: 'TodosConnection', + edges: [ + { + __typename: 'TodoEdge', + node: { + __typename: 'Todo', + id: '1', + // The `complete` field will be overwritten here, but we're + // leaving out the `text` field + complete: true, + }, }, + ], + pageInfo: { + __typename: 'PageInfo', + hasNextPage: true, + endCursor: '1', }, - ], - pageInfo: { - __typename: 'PageInfo', - hasNextPage: true, - endCursor: '1', - }, - }), + }), + }, }, }); @@ -120,23 +122,25 @@ it('allows custom resolvers to resolve nested, unkeyed data', () => { }); it('allows custom resolvers to resolve nested, unkeyed data with embedded links', () => { - const store = new Store(undefined, { - Query: { - todos: (_, __, cache) => ({ - __typename: 'TodosConnection', - edges: [ - { - __typename: 'TodoEdge', - // This is a key instead of the full entity: - node: cache.keyOfEntity({ __typename: 'Todo', id: '1' }), + const store = new Store({ + resolvers: { + Query: { + todos: (_, __, cache) => ({ + __typename: 'TodosConnection', + edges: [ + { + __typename: 'TodoEdge', + // This is a key instead of the full entity: + node: cache.keyOfEntity({ __typename: 'Todo', id: '1' }), + }, + ], + pageInfo: { + __typename: 'PageInfo', + hasNextPage: true, + endCursor: '1', }, - ], - pageInfo: { - __typename: 'PageInfo', - hasNextPage: true, - endCursor: '1', - }, - }), + }), + }, }, }); @@ -181,15 +185,17 @@ it('allows custom resolvers to resolve nested, unkeyed data with embedded links' }); it('allows custom resolvers to resolve mixed data (keyable and unkeyable)', () => { - const store = new Store(undefined, { - Query: { - todo: () => ({ - __typename: 'Todo', - id: '1', - details: { - __typename: 'TodoDetails', - }, - }), + const store = new Store({ + resolvers: { + Query: { + todo: () => ({ + __typename: 'Todo', + id: '1', + details: { + __typename: 'TodoDetails', + }, + }), + }, }, }); From f4495154513a7fcb2d50ed452d54383b6eb228d0 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Mon, 16 Mar 2020 10:54:57 +0000 Subject: [PATCH 3/4] Move null-ish check for opts in cacheExchange --- exchanges/graphcache/src/cacheExchange.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/exchanges/graphcache/src/cacheExchange.ts b/exchanges/graphcache/src/cacheExchange.ts index efa654e49e..50c9b21b8c 100644 --- a/exchanges/graphcache/src/cacheExchange.ts +++ b/exchanges/graphcache/src/cacheExchange.ts @@ -86,12 +86,10 @@ export const cacheExchange = (opts?: CacheExchangeOpts): Exchange => ({ forward, client, }) => { - if (!opts) opts = {}; - const store = new Store(opts); let hydration: void | Promise; - if (opts.storage) { + if (opts && opts.storage) { hydration = opts.storage.read().then(entries => { hydrateData(store.data, opts!.storage!, entries); }); From 17d88dfe2a4f07789b76ef67039df122e1207cf5 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Mon, 16 Mar 2020 11:07:54 +0000 Subject: [PATCH 4/4] Add changeset --- .changeset/fresh-cars-sip.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fresh-cars-sip.md diff --git a/.changeset/fresh-cars-sip.md b/.changeset/fresh-cars-sip.md new file mode 100644 index 0000000000..825d372a05 --- /dev/null +++ b/.changeset/fresh-cars-sip.md @@ -0,0 +1,5 @@ +--- +'@urql/exchange-graphcache': patch +--- + +Improve Store constructor to accept an options object instead of separate arguments, identical to the cacheExchange options. (This is a patch, not a minor, since we consider Store part of the private API)