Repository: FormidableLabs/urql
Pull request #622 (2020-03-16 10:52:36)

(graphcache) - Change Store constructor to accept options object
This changes `cacheExchange` to just pass on its options and `Store` to accept an options object as well to make it easier to use directly.

```js
// before:
new Store(
  undefined, // schema
  { /* resolvers */ },
  { /* updates */ },
  { /* optimistic */ },
  { /* keys */ },
);

// after:
new Store({
  schema: undefined,
  resolvers: {},
  updates: {},
  optimistic: {},
  keys: {},
});
```

`storage` is exempted since the rehydration logic is coupled to the `cacheExchange` (for now). Bundle size remains unchanged.

changeset-bot[bot]: ###  💥  No Changeset

Latest commit: 70ab2320f4c39ebc7f94af79cf62816ee24f7960

Merging this PR will not cause any packages to be released. If these changes should not cause updates to packages in this repo, this is fine 🙂

**If these changes should be published to npm, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/FormidableLabs/urql/new/fix/store-constructor-interface?filename=.changeset/shy-books-scream.md&value=---%0A%22%40urql%2Fexchange-graphcache%22%3A%20patch%0A---%0A%0AChange%20Store%20constructor%20to%20accept%20options%20object%0A)



Patch:
diff --git a/.changeset/fresh-cars-sip.md b/.changeset/fresh-cars-sip.md
new file mode 100644
index 00000000..825d372a
--- /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)
diff --git a/exchanges/graphcache/src/cacheExchange.ts b/exchanges/graphcache/src/cacheExchange.ts
index 0d66a67e..50c9b21b 100644
--- a/exchanges/graphcache/src/cacheExchange.ts
+++ b/exchanges/graphcache/src/cacheExchange.ts
@@ -86,18 +86,10 @@ export const cacheExchange = (opts?: CacheExchangeOpts): Exchange => ({
   forward,
   client,
 }) => {
-  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<void>;
-  if (opts.storage) {
+  if (opts && opts.storage) {
     hydration = opts.storage.read().then(entries => {
       hydrateData(store.data, opts!.storage!, entries);
     });
diff --git a/exchanges/graphcache/src/store/store.ts b/exchanges/graphcache/src/store/store.ts
index f335b6b8..4e9ed362 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<UpdatesConfig>;
+  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<UpdatesConfig>,
-    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();


Test patch:
diff --git a/exchanges/graphcache/src/extras/relayPagination.test.ts b/exchanges/graphcache/src/extras/relayPagination.test.ts
index e8e517a3..afa7301a 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 db607503..bcf326f2 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 1d6f3a21..9068a15e 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 7f296c11..cc83f38a 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 4768a94e..16c2ceda 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 67a76903..7fee5d2a 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 e0f39521..30195e16 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 37cce338..486b99aa 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',
+          },
+        }),
+      },
     },
   });