Repository: urql-graphql/urql
Pull request #3324 (2023-07-21 12:35:35)

feat(persisted): Allow nullish hashes for skipping persisted logic
## Summary

This allows `generateHash` to resolve to `null | undefined`, effectively allowing the persisted logic to be skipped selectively for individual operations.

## Set of changes

- Allow `null | undefined` to be returned by `generateHash`
- Add test for new case



Patch:
diff --git a/.changeset/serious-balloons-cheat.md b/.changeset/serious-balloons-cheat.md
new file mode 100644
index 00000000..6771508a
--- /dev/null
+++ b/.changeset/serious-balloons-cheat.md
@@ -0,0 +1,5 @@
+---
+'urql': patch
+---
+
+Fix edge case that causes execute functions from `useQuery` and `useSubscription` to fail when they’re called in their state after a render that changes `pause`. This would previously cause internal dependencies to be outdated and the source to be discarded immediately in some cases.
diff --git a/.changeset/seven-adults-fix.md b/.changeset/seven-adults-fix.md
new file mode 100644
index 00000000..fc0ee6f4
--- /dev/null
+++ b/.changeset/seven-adults-fix.md
@@ -0,0 +1,5 @@
+---
+'@urql/exchange-persisted': minor
+---
+
+Allow persisted query logic to be skipped by the `persistedExchange` if the passed `generateHash` function resolves to a nullish value. This allows (A)PQ to be selectively disabled for individual operations.
diff --git a/docs/graphcache/cache-updates.md b/docs/graphcache/cache-updates.md
index b98aa7e6..41c33cea 100644
--- a/docs/graphcache/cache-updates.md
+++ b/docs/graphcache/cache-updates.md
@@ -1,6 +1,6 @@
 ---
 title: Cache Updates
-order: 3
+order: 4
 ---
 
 # Cache Updates
diff --git a/docs/graphcache/errors.md b/docs/graphcache/errors.md
index 6aa6e027..ed293069 100644
--- a/docs/graphcache/errors.md
+++ b/docs/graphcache/errors.md
@@ -1,6 +1,6 @@
 ---
 title: Errors
-order: 7
+order: 8
 ---
 
 # Help!
@@ -411,3 +411,8 @@ write to the cache when it's being queried.
 
 Please make sure that you're not calling `cache.updateQuery`,
 `cache.writeFragment`, or `cache.link` inside `resolvers`.
+
+## (28) Resolver and directive match the same field
+
+When you have a resolver defined on a field you shouln't be combining it with a directive as the directive
+will apply and the resolver will be void.
diff --git a/docs/graphcache/local-directives.md b/docs/graphcache/local-directives.md
new file mode 100644
index 00000000..923153b8
--- /dev/null
+++ b/docs/graphcache/local-directives.md
@@ -0,0 +1,35 @@
+---
+title: Local Directives
+order: 3
+---
+
+# Local Directives
+
+Graphcache supports adding directives to GraphQL Documents, when we prefix a
+directive with an underscore (`_`) it will be stripped from the document and stored
+on the `_directives` property on the AST-node.
+
+> Ensure you prefix directives with `_` if you only want to alter local behavior.
+
+By default graphcache will add two directives `@_optional` and `@_required` which
+allow you to mark fields as being optional or mandatory.
+
+If you want to add directives yourself you can do so by performing
+
+```js
+cacheExchange({
+  directives: {
+    // If you now add `@_pagination` to your document we will execute this
+    pagination: directiveArguments => () => {
+      /* Resolver */
+    },
+  },
+});
+```
+
+The function signature of a directive is a function which receives the arguments the directive is called with in the document.
+That function should returns a [Resolver](./local-directives.md).
+
+### Reading on
+
+[On the next page we'll learn about "Cache Updates".](./cache-updates.md)
diff --git a/docs/graphcache/local-resolvers.md b/docs/graphcache/local-resolvers.md
index 0c004d13..fdf19a44 100644
--- a/docs/graphcache/local-resolvers.md
+++ b/docs/graphcache/local-resolvers.md
@@ -565,4 +565,4 @@ cannot be stiched together.
 
 ### Reading on
 
-[On the next page we'll learn about "Cache Updates".](./cache-updates.md)
+[On the next page we'll learn about "Cache Directives".](./local-directives.md)
diff --git a/docs/graphcache/offline.md b/docs/graphcache/offline.md
index 6a326dce..868bd83e 100644
--- a/docs/graphcache/offline.md
+++ b/docs/graphcache/offline.md
@@ -1,6 +1,6 @@
 ---
 title: Offline Support
-order: 6
+order: 7
 ---
 
 # Offline Support
diff --git a/docs/graphcache/schema-awareness.md b/docs/graphcache/schema-awareness.md
index a8b81eac..445e6699 100644
--- a/docs/graphcache/schema-awareness.md
+++ b/docs/graphcache/schema-awareness.md
@@ -1,6 +1,6 @@
 ---
 title: Schema Awareness
-order: 4
+order: 5
 ---
 
 # Schema Awareness
@@ -19,9 +19,9 @@ on `cacheExchange` allows us to pass an introspected schema to Graphcache:
 ```js
 const introspectedSchema = {
   __schema: {
-    queryType: { name: 'Query', },
-    mutationType: { name: 'Mutation', },
-    subscriptionType: { name: 'Subscription', },
+    queryType: { name: 'Query' },
+    mutationType: { name: 'Mutation' },
+    subscriptionType: { name: 'Subscription' },
   },
 };
 
diff --git a/exchanges/graphcache/src/ast/variables.ts b/exchanges/graphcache/src/ast/variables.ts
index 28b72486..bac920c0 100644
--- a/exchanges/graphcache/src/ast/variables.ts
+++ b/exchanges/graphcache/src/ast/variables.ts
@@ -1,5 +1,6 @@
 import {
   FieldNode,
+  DirectiveNode,
   OperationDefinitionNode,
   valueFromASTUntyped,
 } from '@0no-co/graphql.web';
@@ -10,7 +11,7 @@ import { Variables } from '../types';
 
 /** Evaluates a fields arguments taking vars into account */
 export const getFieldArguments = (
-  node: FieldNode,
+  node: FieldNode | DirectiveNode,
   vars: Variables
 ): null | Variables => {
   let args: null | Variables = null;
diff --git a/exchanges/graphcache/src/helpers/help.ts b/exchanges/graphcache/src/helpers/help.ts
index 76366536..17a02f23 100644
--- a/exchanges/graphcache/src/helpers/help.ts
+++ b/exchanges/graphcache/src/helpers/help.ts
@@ -36,7 +36,8 @@ export type ErrorCode =
   | 24
   | 25
   | 26
-  | 27;
+  | 27
+  | 28;
 
 type DebugNode = ExecutableDefinitionNode | InlineFragmentNode;
 
diff --git a/exchanges/graphcache/src/operations/query.ts b/exchanges/graphcache/src/operations/query.ts
index 96ca957c..e87f1045 100644
--- a/exchanges/graphcache/src/operations/query.ts
+++ b/exchanges/graphcache/src/operations/query.ts
@@ -16,6 +16,7 @@ import {
   getMainOperation,
   normalizeVariables,
   getFieldArguments,
+  getDirectives,
 } from '../ast';
 
 import {
@@ -25,6 +26,7 @@ import {
   Link,
   OperationRequest,
   Dependencies,
+  Resolver,
 } from '../types';
 
 import { joinKeys, keyOfField } from '../store/keys';
@@ -295,6 +297,43 @@ export const _queryFragment = (
   return result;
 };
 
+function getFieldResolver(
+  ctx: Context,
+  node: FormattedNode<FieldNode>,
+  typename: string,
+  fieldName: string
+): Resolver | void {
+  const resolvers = ctx.store.resolvers[typename];
+  const fieldResolver = resolvers && resolvers[fieldName];
+  const directives = getDirectives(node);
+
+  let directiveResolver: Resolver | undefined;
+  for (const name in directives) {
+    const directiveNode = directives[name];
+    if (
+      directiveNode &&
+      name !== 'include' &&
+      name !== 'skip' &&
+      ctx.store.directives[name]
+    ) {
+      directiveResolver = ctx.store.directives[name](
+        getFieldArguments(directiveNode, ctx.variables)
+      );
+      if (process.env.NODE_ENV === 'production') return directiveResolver;
+      break;
+    }
+  }
+
+  if (fieldResolver && directiveResolver) {
+    warn(
+      `A resolver and directive is being used at "${typename}.${fieldName}" simultaneously. Only the directive will apply.`,
+      28
+    );
+  }
+
+  return directiveResolver || fieldResolver;
+}
+
 const readSelection = (
   ctx: Context,
   key: string,
@@ -340,7 +379,6 @@ const readSelection = (
     return;
   }
 
-  const resolvers = store.resolvers[typename];
   const iterate = makeSelectionIterator(
     typename,
     entityKey,
@@ -361,6 +399,7 @@ const readSelection = (
     const fieldArgs = getFieldArguments(node, ctx.variables);
     const fieldAlias = getFieldAlias(node);
     const fieldKey = keyOfField(fieldName, fieldArgs);
+    const resolver = getFieldResolver(ctx, node, typename, fieldName);
     const key = joinKeys(entityKey, fieldKey);
     const fieldValue = InMemoryData.readRecord(entityKey, fieldKey);
     const resultValue = result ? result[fieldName] : undefined;
@@ -373,7 +412,7 @@ const readSelection = (
     ctx.__internal.path.push(fieldAlias);
     // We temporarily store the data field in here, but undefined
     // means that the value is missing from the cache
-    let dataFieldValue: void | DataField;
+    let dataFieldValue: void | DataField = undefined;
 
     if (fieldName === '__typename') {
       // We directly assign the typename as it's already available
@@ -381,11 +420,7 @@ const readSelection = (
     } else if (resultValue !== undefined && node.selectionSet === undefined) {
       // The field is a scalar and can be retrieved directly from the result
       dataFieldValue = resultValue;
-    } else if (
-      InMemoryData.currentOperation === 'read' &&
-      resolvers &&
-      resolvers[fieldName]
-    ) {
+    } else if (InMemoryData.currentOperation === 'read' && resolver) {
       // We have to update the information in context to reflect the info
       // that the resolver will receive
       updateContext(ctx, output, typename, entityKey, key, fieldName);
@@ -396,7 +431,7 @@ const readSelection = (
         output[fieldAlias] = fieldValue;
       }
 
-      dataFieldValue = resolvers[fieldName]!(
+      dataFieldValue = resolver(
         output,
         fieldArgs || ({} as Variables),
         store,
diff --git a/exchanges/graphcache/src/store/store.ts b/exchanges/graphcache/src/store/store.ts
index ccf9a667..9c6fbfb0 100644
--- a/exchanges/graphcache/src/store/store.ts
+++ b/exchanges/graphcache/src/store/store.ts
@@ -15,6 +15,7 @@ import {
   KeyingConfig,
   Entity,
   CacheExchangeOpts,
+  DirectivesConfig,
 } from '../types';
 
 import { invariant } from '../helpers/help';
@@ -37,6 +38,17 @@ import {
 type DocumentNode = TypedDocumentNode<any, any>;
 type RootField = 'query' | 'mutation' | 'subscription';
 
+const defaultDirectives: DirectivesConfig = {
+  optional: () => (_parent, args, cache, info) => {
+    const result = cache.resolve(info.parentFieldKey, info.fieldName, args);
+    return result === undefined ? null : result;
+  },
+  required: () => (_parent, args, cache, info) => {
+    const result = cache.resolve(info.parentFieldKey, info.fieldName, args);
+    return result === null ? undefined : result;
+  },
+};
+
 /** Implementation of the {@link Cache} interface as created internally by the {@link cacheExchange}.
  * @internal
  */
@@ -46,6 +58,7 @@ export class Store<
 {
   data: InMemoryData.InMemoryData;
 
+  directives: DirectivesConfig;
   resolvers: ResolverConfig;
   updates: UpdatesConfig;
   optimisticMutations: OptimisticMutationConfig;
@@ -60,6 +73,8 @@ export class Store<
     if (!opts) opts = {} as C;
 
     this.resolvers = opts.resolvers || {};
+    this.directives =
+      { ...defaultDirectives, ...opts.directives } || defaultDirectives;
     this.optimisticMutations = opts.optimistic || {};
     this.keys = opts.keys || {};
 
diff --git a/exchanges/graphcache/src/types.ts b/exchanges/graphcache/src/types.ts
index 9446da19..dfeb6aab 100644
--- a/exchanges/graphcache/src/types.ts
+++ b/exchanges/graphcache/src/types.ts
@@ -555,6 +555,14 @@ export type CacheExchangeOpts = {
    * @see {@link https://urql.dev/goto/docs/graphcache/local-resolvers} for the full resolvers docs.
    */
   resolvers?: ResolverConfig;
+  /** Configures directives which can perform custom logic on fields.
+   *
+   * @remarks
+   * A {@link DirectivesConfig} may be passed to allow local directives to be used. For example, when `@_custom` is placed on a field and the configuration contains `custom` then this directive is executed by Graphcache.
+   *
+   * @see {@link https://urql.dev/goto/docs/graphcache/local-directives} for the full directives docs.
+   */
+  directives?: DirectivesConfig;
   /** Configures optimistic updates to react to mutations instantly before an API response.
    *
    * @remarks
@@ -693,6 +701,14 @@ export type ResolverConfig = {
   } | void;
 };
 
+export type Directive = (
+  directiveArguments: Record<string, unknown> | null
+) => Resolver;
+
+export type DirectivesConfig = {
+  [directiveName: string]: Directive;
+};
+
 /** Cache Updater, which defines additional cache updates after cache writes.
  *
  * @param parent - The GraphQL object that is currently being written to the cache.
diff --git a/exchanges/persisted/src/persistedExchange.ts b/exchanges/persisted/src/persistedExchange.ts
index a2628d88..6ddfdca8 100644
--- a/exchanges/persisted/src/persistedExchange.ts
+++ b/exchanges/persisted/src/persistedExchange.ts
@@ -68,6 +68,10 @@ export interface PersistedExchangeOptions {
    * hashes at compile-time, or need to use a custom SHA-256 function,
    * you may pass one here.
    *
+   * If `generateHash` returns either `null` or `undefined`, the
+   * operation will not be treated as a persisted operation, which
+   * essentially skips this exchange’s logic for a given operation.
+   *
    * Hint: The default SHA-256 function uses the WebCrypto API. This
    * API is unavailable on React Native, which may require you to
    * pass a custom function here.
@@ -75,7 +79,7 @@ export interface PersistedExchangeOptions {
   generateHash?(
     query: string,
     document: TypedDocumentNode<any, any>
-  ): Promise<string>;
+  ): Promise<string | undefined | null>;
   /** Enables persisted queries to be used for mutations.
    *
    * @remarks
diff --git a/packages/react-urql/src/hooks/useQuery.ts b/packages/react-urql/src/hooks/useQuery.ts
index 1b14e8b5..0b6ecda1 100644
--- a/packages/react-urql/src/hooks/useQuery.ts
+++ b/packages/react-urql/src/hooks/useQuery.ts
@@ -376,9 +376,9 @@ export function useQuery<
       cache,
       request,
       suspense,
-      getSnapshot,
       args.requestPolicy,
       args.context,
+      args.pause,
     ]
   );
 
diff --git a/packages/react-urql/src/hooks/useSubscription.ts b/packages/react-urql/src/hooks/useSubscription.ts
index 3c3d0614..debdf4e4 100644
--- a/packages/react-urql/src/hooks/useSubscription.ts
+++ b/packages/react-urql/src/hooks/useSubscription.ts
@@ -300,7 +300,7 @@ export function useSubscription<
 
       deferDispatch(setState, state => [source, state[1], deps]);
     },
-    [client, args.context, request]
+    [client, request, args.context, args.pause]
   );
 
   return [currentResult, executeSubscription];


Test patch:
diff --git a/exchanges/graphcache/src/cacheExchange.test.ts b/exchanges/graphcache/src/cacheExchange.test.ts
index e2d16a54..2dd212be 100644
--- a/exchanges/graphcache/src/cacheExchange.test.ts
+++ b/exchanges/graphcache/src/cacheExchange.test.ts
@@ -6,6 +6,7 @@ import {
   OperationResult,
   CombinedError,
 } from '@urql/core';
+import { print, stripIgnoredCharacters } from 'graphql';
 
 import { vi, expect, it, describe } from 'vitest';
 
@@ -677,6 +678,147 @@ describe('data dependencies', () => {
   });
 });
 
+describe('directives', () => {
+  it('returns optional fields as partial', () => {
+    const client = createClient({
+      url: 'http://0.0.0.0',
+      exchanges: [],
+    });
+    const { source: ops$, next } = makeSubject<Operation>();
+
+    const query = gql`
+      {
+        todos {
+          id
+          text
+          completed @_optional
+        }
+      }
+    `;
+
+    const operation = client.createRequestOperation('query', {
+      key: 1,
+      query,
+      variables: undefined,
+    });
+
+    const queryResult: OperationResult = {
+      ...queryResponse,
+      operation,
+      data: {
+        __typename: 'Query',
+        todos: [
+          {
+            id: '1',
+            text: 'learn urql',
+            __typename: 'Todo',
+          },
+        ],
+      },
+    };
+
+    const reexecuteOperation = vi
+      .spyOn(client, 'reexecuteOperation')
+      .mockImplementation(next);
+
+    const response = vi.fn((forwardOp: Operation): OperationResult => {
+      if (forwardOp.key === 1) return queryResult;
+      return undefined as any;
+    });
+
+    const result = vi.fn();
+    const forward: ExchangeIO = ops$ => pipe(ops$, map(response), share);
+
+    pipe(
+      cacheExchange({})({ forward, client, dispatchDebug })(ops$),
+      tap(result),
+      publish
+    );
+
+    next(operation);
+
+    expect(response).toHaveBeenCalledTimes(1);
+    expect(result).toHaveBeenCalledTimes(1);
+    expect(reexecuteOperation).toHaveBeenCalledTimes(0);
+    expect(result.mock.calls[0][0].data).toEqual({
+      todos: [
+        {
+          completed: null,
+          id: '1',
+          text: 'learn urql',
+        },
+      ],
+    });
+  });
+
+  it('does not return missing required fields', () => {
+    const client = createClient({
+      url: 'http://0.0.0.0',
+      exchanges: [],
+    });
+    const { source: ops$, next } = makeSubject<Operation>();
+
+    const query = gql`
+      {
+        todos {
+          id
+          text
+          completed @_required
+        }
+      }
+    `;
+
+    const operation = client.createRequestOperation('query', {
+      key: 1,
+      query,
+      variables: undefined,
+    });
+
+    const queryResult: OperationResult = {
+      ...queryResponse,
+      operation,
+      data: {
+        __typename: 'Query',
+        todos: [
+          {
+            id: '1',
+            text: 'learn urql',
+            __typename: 'Todo',
+          },
+        ],
+      },
+    };
+
+    const reexecuteOperation = vi
+      .spyOn(client, 'reexecuteOperation')
+      .mockImplementation(next);
+
+    const response = vi.fn((forwardOp: Operation): OperationResult => {
+      if (forwardOp.key === 1) return queryResult;
+      return undefined as any;
+    });
+
+    const result = vi.fn();
+    const forward: ExchangeIO = ops$ => pipe(ops$, map(response), share);
+
+    pipe(
+      cacheExchange({})({ forward, client, dispatchDebug })(ops$),
+      tap(result),
+      publish
+    );
+
+    next(operation);
+
+    expect(response).toHaveBeenCalledTimes(1);
+    expect(result).toHaveBeenCalledTimes(1);
+    expect(
+      stripIgnoredCharacters(print(response.mock.calls[0][0].query))
+    ).toEqual('{todos{id text completed __typename}}');
+    expect(reexecuteOperation).toHaveBeenCalledTimes(0);
+    expect(result.mock.calls[0][0].data).toEqual(null);
+  });
+});
+
 describe('optimistic updates', () => {
   it('writes optimistic mutations to the cache', () => {
     vi.useFakeTimers();
diff --git a/exchanges/graphcache/src/extras/relayPagination.test.ts b/exchanges/graphcache/src/extras/relayPagination.test.ts
index f6f4fdaa..3b668f73 100644
--- a/exchanges/graphcache/src/extras/relayPagination.test.ts
+++ b/exchanges/graphcache/src/extras/relayPagination.test.ts
@@ -1,5 +1,5 @@
 import { gql } from '@urql/core';
-import { it, expect } from 'vitest';
+import { it, expect, describe } from 'vitest';
 import { __initAnd_query as query } from '../operations/query';
 import { __initAnd_write as write } from '../operations/write';
 import { Store } from '../store/store';
@@ -19,1458 +19,1598 @@ function itemEdge(numItem: number) {
   };
 }
 
-it('works with forward pagination', () => {
-  const Pagination = gql`
-    query ($cursor: String) {
-      __typename
-      items(first: 1, after: $cursor) {
+describe('as resolver', () => {
+  it('works with forward pagination', () => {
+    const Pagination = gql`
+      query ($cursor: String) {
         __typename
-        edges {
+        items(first: 1, after: $cursor) {
           __typename
-          node {
+          edges {
+            __typename
+            node {
+              __typename
+              id
+            }
+          }
+          nodes {
             __typename
             id
           }
-        }
-        nodes {
-          __typename
-          id
-        }
-        pageInfo {
-          __typename
-          hasNextPage
-          endCursor
+          pageInfo {
+            __typename
+            hasNextPage
+            endCursor
+          }
         }
       }
-    }
-  `;
+    `;
 
-  const store = new Store({
-    resolvers: {
-      Query: {
-        items: relayPagination(),
+    const store = new Store({
+      resolvers: {
+        Query: {
+          items: relayPagination(),
+        },
       },
-    },
-  });
+    });
 
-  const pageOne = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(1)],
-      nodes: [itemNode(1)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: true,
-        endCursor: '1',
+    const pageOne = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(1)],
+        nodes: [itemNode(1)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: true,
+          endCursor: '1',
+        },
       },
-    },
-  };
+    };
 
-  const pageTwo = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(2)],
-      nodes: [itemNode(2)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: false,
-        endCursor: null,
+    const pageTwo = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(2)],
+        nodes: [itemNode(2)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: false,
+          endCursor: null,
+        },
       },
-    },
-  };
+    };
 
-  write(store, { query: Pagination, variables: { cursor: null } }, pageOne);
-  write(store, { query: Pagination, variables: { cursor: '1' } }, pageTwo);
+    write(store, { query: Pagination, variables: { cursor: null } }, pageOne);
+    write(store, { query: Pagination, variables: { cursor: '1' } }, pageTwo);
 
-  const res = query(store, { query: Pagination });
+    const res = query(store, { query: Pagination });
 
-  expect(res.partial).toBe(false);
-  expect(res.data).toEqual({
-    ...pageTwo,
-    items: {
-      ...pageTwo.items,
-      edges: [pageOne.items.edges[0], pageTwo.items.edges[0]],
-      nodes: [pageOne.items.nodes[0], pageTwo.items.nodes[0]],
-    },
+    expect(res.partial).toBe(false);
+    expect(res.data).toEqual({
+      ...pageTwo,
+      items: {
+        ...pageTwo.items,
+        edges: [pageOne.items.edges[0], pageTwo.items.edges[0]],
+        nodes: [pageOne.items.nodes[0], pageTwo.items.nodes[0]],
+      },
+    });
   });
-});
 
-it('works with backwards pagination', () => {
-  const Pagination = gql`
-    query ($cursor: String) {
-      __typename
-      items(last: 1, before: $cursor) {
+  it('works with backwards pagination', () => {
+    const Pagination = gql`
+      query ($cursor: String) {
         __typename
-        edges {
+        items(last: 1, before: $cursor) {
           __typename
-          node {
+          edges {
+            __typename
+            node {
+              __typename
+              id
+            }
+          }
+          nodes {
             __typename
             id
           }
-        }
-        nodes {
-          __typename
-          id
-        }
-        pageInfo {
-          __typename
-          hasPreviousPage
-          startCursor
+          pageInfo {
+            __typename
+            hasPreviousPage
+            startCursor
+          }
         }
       }
-    }
-  `;
+    `;
 
-  const store = new Store({
-    resolvers: {
-      Query: {
-        items: relayPagination(),
+    const store = new Store({
+      resolvers: {
+        Query: {
+          items: relayPagination(),
+        },
       },
-    },
-  });
+    });
 
-  const pageOne = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(2)],
-      nodes: [itemNode(2)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasPreviousPage: true,
-        startCursor: '2',
+    const pageOne = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(2)],
+        nodes: [itemNode(2)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasPreviousPage: true,
+          startCursor: '2',
+        },
       },
-    },
-  };
+    };
 
-  const pageTwo = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(1)],
-      nodes: [itemNode(1)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasPreviousPage: false,
-        startCursor: null,
+    const pageTwo = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(1)],
+        nodes: [itemNode(1)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasPreviousPage: false,
+          startCursor: null,
+        },
       },
-    },
-  };
+    };
 
-  write(store, { query: Pagination, variables: { cursor: null } }, pageOne);
-  write(store, { query: Pagination, variables: { cursor: '2' } }, pageTwo);
+    write(store, { query: Pagination, variables: { cursor: null } }, pageOne);
+    write(store, { query: Pagination, variables: { cursor: '2' } }, pageTwo);
 
-  const res = query(store, { query: Pagination });
+    const res = query(store, { query: Pagination });
 
-  expect(res.partial).toBe(false);
-  expect(res.data).toEqual({
-    ...pageTwo,
-    items: {
-      ...pageTwo.items,
-      edges: [pageTwo.items.edges[0], pageOne.items.edges[0]],
-      nodes: [pageTwo.items.nodes[0], pageOne.items.nodes[0]],
-    },
+    expect(res.partial).toBe(false);
+    expect(res.data).toEqual({
+      ...pageTwo,
+      items: {
+        ...pageTwo.items,
+        edges: [pageTwo.items.edges[0], pageOne.items.edges[0]],
+        nodes: [pageTwo.items.nodes[0], pageOne.items.nodes[0]],
+      },
+    });
   });
-});
 
-it('handles duplicate edges', () => {
-  const Pagination = gql`
-    query ($cursor: String) {
-      __typename
-      items(first: 2, after: $cursor) {
+  it('handles duplicate edges', () => {
+    const Pagination = gql`
+      query ($cursor: String) {
         __typename
-        edges {
+        items(first: 2, after: $cursor) {
           __typename
-          node {
+          edges {
+            __typename
+            node {
+              __typename
+              id
+            }
+          }
+          nodes {
             __typename
             id
           }
-        }
-        nodes {
-          __typename
-          id
-        }
-        pageInfo {
-          __typename
-          hasNextPage
-          endCursor
+          pageInfo {
+            __typename
+            hasNextPage
+            endCursor
+          }
         }
       }
-    }
-  `;
+    `;
 
-  const store = new Store({
-    resolvers: {
-      Query: {
-        items: relayPagination(),
+    const store = new Store({
+      resolvers: {
+        Query: {
+          items: relayPagination(),
+        },
       },
-    },
-  });
+    });
 
-  const pageOne = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(1), itemEdge(2)],
-      nodes: [itemNode(1), itemNode(2)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: true,
-        endCursor: '2',
+    const pageOne = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(1), itemEdge(2)],
+        nodes: [itemNode(1), itemNode(2)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: true,
+          endCursor: '2',
+        },
       },
-    },
-  };
+    };
 
-  const pageTwo = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(2), itemEdge(3)],
-      nodes: [itemNode(2), itemNode(3)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: false,
-        endCursor: null,
+    const pageTwo = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(2), itemEdge(3)],
+        nodes: [itemNode(2), itemNode(3)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: false,
+          endCursor: null,
+        },
       },
-    },
-  };
+    };
+
+    write(store, { query: Pagination, variables: { cursor: null } }, pageOne);
+    write(store, { query: Pagination, variables: { cursor: '1' } }, pageTwo);
+
+    const res = query(store, { query: Pagination });
 
-  write(store, { query: Pagination, variables: { cursor: null } }, pageOne);
-  write(store, { query: Pagination, variables: { cursor: '1' } }, pageTwo);
-
-  const res = query(store, { query: Pagination });
-
-  expect(res.partial).toBe(false);
-  expect(res.data).toEqual({
-    ...pageTwo,
-    items: {
-      ...pageTwo.items,
-      edges: [
-        pageOne.items.edges[0],
-        pageTwo.items.edges[0],
-        pageTwo.items.edges[1],
-      ],
-      nodes: [
-        pageOne.items.nodes[0],
-        pageTwo.items.nodes[0],
-        pageTwo.items.nodes[1],
-      ],
-    },
+    expect(res.partial).toBe(false);
+    expect(res.data).toEqual({
+      ...pageTwo,
+      items: {
+        ...pageTwo.items,
+        edges: [
+          pageOne.items.edges[0],
+          pageTwo.items.edges[0],
+          pageTwo.items.edges[1],
+        ],
+        nodes: [
+          pageOne.items.nodes[0],
+          pageTwo.items.nodes[0],
+          pageTwo.items.nodes[1],
+        ],
+      },
+    });
   });
-});
 
-it('works with simultaneous forward and backward pagination (outwards merging)', () => {
-  const Pagination = gql`
-    query ($first: Int, $last: Int, $before: String, $after: String) {
-      __typename
-      items(first: $first, last: $last, before: $before, after: $after) {
+  it('works with simultaneous forward and backward pagination (outwards merging)', () => {
+    const Pagination = gql`
+      query ($first: Int, $last: Int, $before: String, $after: String) {
         __typename
-        edges {
+        items(first: $first, last: $last, before: $before, after: $after) {
           __typename
-          node {
+          edges {
+            __typename
+            node {
+              __typename
+              id
+            }
+          }
+          nodes {
             __typename
             id
           }
-        }
-        nodes {
-          __typename
-          id
-        }
-        pageInfo {
-          __typename
-          hasPreviousPage
-          hasNextPage
-          startCursor
-          endCursor
+          pageInfo {
+            __typename
+            hasPreviousPage
+            hasNextPage
+            startCursor
+            endCursor
+          }
         }
       }
-    }
-  `;
+    `;
 
-  const store = new Store({
-    resolvers: {
-      Query: {
-        items: relayPagination({ mergeMode: 'outwards' }),
+    const store = new Store({
+      resolvers: {
+        Query: {
+          items: relayPagination({ mergeMode: 'outwards' }),
+        },
       },
-    },
-  });
+    });
 
-  const pageOne = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(1)],
-      nodes: [itemNode(1)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: true,
-        hasPreviousPage: false,
-        startCursor: null,
-        endCursor: '1',
+    const pageOne = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(1)],
+        nodes: [itemNode(1)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: true,
+          hasPreviousPage: false,
+          startCursor: null,
+          endCursor: '1',
+        },
       },
-    },
-  };
+    };
 
-  const pageTwo = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(2)],
-      nodes: [itemNode(2)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: true,
-        hasPreviousPage: true,
-        startCursor: '2',
-        endCursor: '2',
+    const pageTwo = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(2)],
+        nodes: [itemNode(2)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: true,
+          hasPreviousPage: true,
+          startCursor: '2',
+          endCursor: '2',
+        },
       },
-    },
-  };
+    };
 
-  const pageThree = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(-1)],
-      nodes: [itemNode(-1)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: false,
-        hasPreviousPage: true,
-        startCursor: '-1',
-        endCursor: null,
+    const pageThree = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(-1)],
+        nodes: [itemNode(-1)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: false,
+          hasPreviousPage: true,
+          startCursor: '-1',
+          endCursor: null,
+        },
       },
-    },
-  };
-
-  write(
-    store,
-    { query: Pagination, variables: { after: '1', first: 1 } },
-    pageOne
-  );
-  write(
-    store,
-    { query: Pagination, variables: { after: '2', first: 1 } },
-    pageTwo
-  );
-  write(
-    store,
-    { query: Pagination, variables: { before: '1', last: 1 } },
-    pageThree
-  );
-
-  const res = query(store, {
-    query: Pagination,
-    variables: { before: '1', last: 1 },
-  });
-
-  expect(res.partial).toBe(false);
-  expect(res.data).toEqual({
-    ...pageThree,
-    items: {
-      ...pageThree.items,
-      edges: [
-        pageThree.items.edges[0],
-        pageOne.items.edges[0],
-        pageTwo.items.edges[0],
-      ],
-      nodes: [
-        pageThree.items.nodes[0],
-        pageOne.items.nodes[0],
-        pageTwo.items.nodes[0],
-      ],
-      pageInfo: {
-        ...pageThree.items.pageInfo,
-        hasPreviousPage: true,
-        hasNextPage: true,
-        startCursor: '-1',
-        endCursor: '2',
+    };
+
+    write(
+      store,
+      { query: Pagination, variables: { after: '1', first: 1 } },
+      pageOne
+    );
+    write(
+      store,
+      { query: Pagination, variables: { after: '2', first: 1 } },
+      pageTwo
+    );
+    write(
+      store,
+      { query: Pagination, variables: { before: '1', last: 1 } },
+      pageThree
+    );
+
+    const res = query(store, {
+      query: Pagination,
+      variables: { before: '1', last: 1 },
+    });
+
+    expect(res.partial).toBe(false);
+    expect(res.data).toEqual({
+      ...pageThree,
+      items: {
+        ...pageThree.items,
+        edges: [
+          pageThree.items.edges[0],
+          pageOne.items.edges[0],
+          pageTwo.items.edges[0],
+        ],
+        nodes: [
+          pageThree.items.nodes[0],
+          pageOne.items.nodes[0],
+          pageTwo.items.nodes[0],
+        ],
+        pageInfo: {
+          ...pageThree.items.pageInfo,
+          hasPreviousPage: true,
+          hasNextPage: true,
+          startCursor: '-1',
+          endCursor: '2',
+        },
       },
-    },
+    });
   });
-});
 
-it('works with simultaneous forward and backward pagination (inwards merging)', () => {
-  const Pagination = gql`
-    query ($first: Int, $last: Int, $before: String, $after: String) {
-      __typename
-      items(first: $first, last: $last, before: $before, after: $after) {
+  it('works with simultaneous forward and backward pagination (inwards merging)', () => {
+    const Pagination = gql`
+      query ($first: Int, $last: Int, $before: String, $after: String) {
         __typename
-        edges {
+        items(first: $first, last: $last, before: $before, after: $after) {
           __typename
-          node {
+          edges {
+            __typename
+            node {
+              __typename
+              id
+            }
+          }
+          nodes {
             __typename
             id
           }
-        }
-        nodes {
-          __typename
-          id
-        }
-        pageInfo {
-          __typename
-          hasPreviousPage
-          hasNextPage
-          startCursor
-          endCursor
+          pageInfo {
+            __typename
+            hasPreviousPage
+            hasNextPage
+            startCursor
+            endCursor
+          }
         }
       }
-    }
-  `;
+    `;
 
-  const store = new Store({
-    resolvers: {
-      Query: {
-        items: relayPagination({ mergeMode: 'inwards' }),
+    const store = new Store({
+      resolvers: {
+        Query: {
+          items: relayPagination({ mergeMode: 'inwards' }),
+        },
       },
-    },
-  });
+    });
 
-  const pageOne = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(1)],
-      nodes: [itemNode(1)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: true,
-        hasPreviousPage: false,
-        startCursor: null,
-        endCursor: '1',
+    const pageOne = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(1)],
+        nodes: [itemNode(1)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: true,
+          hasPreviousPage: false,
+          startCursor: null,
+          endCursor: '1',
+        },
       },
-    },
-  };
+    };
 
-  const pageTwo = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(2)],
-      nodes: [itemNode(2)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: true,
-        hasPreviousPage: true,
-        startCursor: '2',
-        endCursor: '2',
+    const pageTwo = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(2)],
+        nodes: [itemNode(2)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: true,
+          hasPreviousPage: true,
+          startCursor: '2',
+          endCursor: '2',
+        },
       },
-    },
-  };
+    };
 
-  const pageThree = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(-1)],
-      nodes: [itemNode(-1)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: false,
-        hasPreviousPage: true,
-        startCursor: '-1',
-        endCursor: null,
+    const pageThree = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(-1)],
+        nodes: [itemNode(-1)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: false,
+          hasPreviousPage: true,
+          startCursor: '-1',
+          endCursor: null,
+        },
       },
-    },
-  };
-
-  write(
-    store,
-    { query: Pagination, variables: { after: '1', first: 1 } },
-    pageOne
-  );
-  write(
-    store,
-    { query: Pagination, variables: { after: '2', first: 1 } },
-    pageTwo
-  );
-  write(
-    store,
-    { query: Pagination, variables: { before: '1', last: 1 } },
-    pageThree
-  );
-
-  const res = query(store, {
-    query: Pagination,
-    variables: { before: '1', last: 1 },
-  });
-
-  expect(res.partial).toBe(false);
-  expect(res.data).toEqual({
-    ...pageThree,
-    items: {
-      ...pageThree.items,
-      edges: [
-        pageOne.items.edges[0],
-        pageTwo.items.edges[0],
-        pageThree.items.edges[0],
-      ],
-      nodes: [
-        pageOne.items.nodes[0],
-        pageTwo.items.nodes[0],
-        pageThree.items.nodes[0],
-      ],
-      pageInfo: {
-        ...pageThree.items.pageInfo,
-        hasPreviousPage: true,
-        hasNextPage: true,
-        startCursor: '-1',
-        endCursor: '2',
+    };
+
+    write(
+      store,
+      { query: Pagination, variables: { after: '1', first: 1 } },
+      pageOne
+    );
+    write(
+      store,
+      { query: Pagination, variables: { after: '2', first: 1 } },
+      pageTwo
+    );
+    write(
+      store,
+      { query: Pagination, variables: { before: '1', last: 1 } },
+      pageThree
+    );
+
+    const res = query(store, {
+      query: Pagination,
+      variables: { before: '1', last: 1 },
+    });
+
+    expect(res.partial).toBe(false);
+    expect(res.data).toEqual({
+      ...pageThree,
+      items: {
+        ...pageThree.items,
+        edges: [
+          pageOne.items.edges[0],
+          pageTwo.items.edges[0],
+          pageThree.items.edges[0],
+        ],
+        nodes: [
+          pageOne.items.nodes[0],
+          pageTwo.items.nodes[0],
+          pageThree.items.nodes[0],
+        ],
+        pageInfo: {
+          ...pageThree.items.pageInfo,
+          hasPreviousPage: true,
+          hasNextPage: true,
+          startCursor: '-1',
+          endCursor: '2',
+        },
       },
-    },
+    });
   });
-});
 
-it('prevents overlapping of pagination on different arguments', () => {
-  const Pagination = gql`
-    query ($filter: String) {
-      items(first: 1, filter: $filter) {
-        __typename
-        edges {
+  it('prevents overlapping of pagination on different arguments', () => {
+    const Pagination = gql`
+      query ($filter: String) {
+        items(first: 1, filter: $filter) {
           __typename
-          node {
+          edges {
+            __typename
+            node {
+              __typename
+              id
+            }
+          }
+          nodes {
             __typename
             id
           }
-        }
-        nodes {
-          __typename
-          id
-        }
-        pageInfo {
-          __typename
-          hasNextPage
-          endCursor
+          pageInfo {
+            __typename
+            hasNextPage
+            endCursor
+          }
         }
       }
-    }
-  `;
+    `;
 
-  const store = new Store({
-    resolvers: {
-      Query: {
-        items: relayPagination(),
+    const store = new Store({
+      resolvers: {
+        Query: {
+          items: relayPagination(),
+        },
       },
-    },
-  });
+    });
 
-  const page = withId => ({
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(withId)],
-      nodes: [itemNode(withId)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: false,
-        endCursor: null,
+    const page = withId => ({
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(withId)],
+        nodes: [itemNode(withId)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: false,
+          endCursor: null,
+        },
       },
-    },
-  });
-
-  write(
-    store,
-    { query: Pagination, variables: { filter: 'one' } },
-    page('one')
-  );
-  write(
-    store,
-    { query: Pagination, variables: { filter: 'two' } },
-    page('two')
-  );
-
-  const resOne = query(store, {
-    query: Pagination,
-    variables: { filter: 'one' },
+    });
+
+    write(
+      store,
+      { query: Pagination, variables: { filter: 'one' } },
+      page('one')
+    );
+    write(
+      store,
+      { query: Pagination, variables: { filter: 'two' } },
+      page('two')
+    );
+
+    const resOne = query(store, {
+      query: Pagination,
+      variables: { filter: 'one' },
+    });
+    const resTwo = query(store, {
+      query: Pagination,
+      variables: { filter: 'two' },
+    });
+    const resThree = query(store, {
+      query: Pagination,
+      variables: { filter: 'three' },
+    });
+
+    expect(resOne.data).toHaveProperty('items.edges[0].node.id', 'one');
+    expect(resOne.data).toHaveProperty('items.edges.length', 1);
+
+    expect(resTwo.data).toHaveProperty('items.edges[0].node.id', 'two');
+    expect(resTwo.data).toHaveProperty('items.edges.length', 1);
+
+    expect(resThree.data).toEqual(null);
   });
-  const resTwo = query(store, {
-    query: Pagination,
-    variables: { filter: 'two' },
-  });
-  const resThree = query(store, {
-    query: Pagination,
-    variables: { filter: 'three' },
-  });
-
-  expect(resOne.data).toHaveProperty('items.edges[0].node.id', 'one');
-  expect(resOne.data).toHaveProperty('items.edges.length', 1);
-
-  expect(resTwo.data).toHaveProperty('items.edges[0].node.id', 'two');
-  expect(resTwo.data).toHaveProperty('items.edges.length', 1);
 
-  expect(resThree.data).toEqual(null);
-});
-
-it('returns an empty array of edges when the cache has zero edges stored', () => {
-  const Pagination = gql`
-    query {
-      items(first: 1) {
-        __typename
-        edges {
-          __typename
-        }
-        nodes {
+  it('returns an empty array of edges when the cache has zero edges stored', () => {
+    const Pagination = gql`
+      query {
+        items(first: 1) {
           __typename
+          edges {
+            __typename
+          }
+          nodes {
+            __typename
+          }
         }
       }
-    }
-  `;
+    `;
 
-  const store = new Store({
-    resolvers: {
-      Query: {
-        items: relayPagination(),
+    const store = new Store({
+      resolvers: {
+        Query: {
+          items: relayPagination(),
+        },
       },
-    },
-  });
-
-  write(
-    store,
-    { query: Pagination },
-    {
-      __typename: 'Query',
-      items: {
-        __typename: 'ItemsConnection',
-        edges: [],
-        nodes: [],
-      },
-    }
-  );
+    });
+
+    write(
+      store,
+      { query: Pagination },
+      {
+        __typename: 'Query',
+        items: {
+          __typename: 'ItemsConnection',
+          edges: [],
+          nodes: [],
+        },
+      }
+    );
 
-  const res = query(store, {
-    query: Pagination,
-  });
+    const res = query(store, {
+      query: Pagination,
+    });
 
-  expect(res.data).toHaveProperty('items', {
-    __typename: 'ItemsConnection',
-    edges: [],
-    nodes: [],
+    expect(res.data).toHaveProperty('items', {
+      __typename: 'ItemsConnection',
+      edges: [],
+      nodes: [],
+    });
   });
-});
 
-it('returns other fields on the same level as the edges', () => {
-  const Pagination = gql`
-    query {
-      __typename
-      items(first: 1) {
+  it('returns other fields on the same level as the edges', () => {
+    const Pagination = gql`
+      query {
         __typename
-        totalCount
+        items(first: 1) {
+          __typename
+          totalCount
+        }
       }
-    }
-  `;
+    `;
 
-  const store = new Store({
-    resolvers: {
-      Query: {
-        items: relayPagination(),
+    const store = new Store({
+      resolvers: {
+        Query: {
+          items: relayPagination(),
+        },
       },
-    },
-  });
-
-  write(
-    store,
-    { query: Pagination },
-    {
-      __typename: 'Query',
-      items: {
-        __typename: 'ItemsConnection',
-        totalCount: 2,
-      },
-    }
-  );
+    });
+
+    write(
+      store,
+      { query: Pagination },
+      {
+        __typename: 'Query',
+        items: {
+          __typename: 'ItemsConnection',
+          totalCount: 2,
+        },
+      }
+    );
 
-  const resOne = query(store, {
-    query: Pagination,
-  });
+    const resOne = query(store, {
+      query: Pagination,
+    });
 
-  expect(resOne.data).toHaveProperty('items', {
-    __typename: 'ItemsConnection',
-    totalCount: 2,
+    expect(resOne.data).toHaveProperty('items', {
+      __typename: 'ItemsConnection',
+      totalCount: 2,
+    });
   });
-});
 
-it('returns a subset of the cached items if the query requests less items than the cached ones', () => {
-  const Pagination = gql`
-    query ($first: Int, $last: Int, $before: String, $after: String) {
-      __typename
-      items(first: $first, last: $last, before: $before, after: $after) {
+  it('returns a subset of the cached items if the query requests less items than the cached ones', () => {
+    const Pagination = gql`
+      query ($first: Int, $last: Int, $before: String, $after: String) {
         __typename
-        edges {
+        items(first: $first, last: $last, before: $before, after: $after) {
           __typename
-          node {
+          edges {
+            __typename
+            node {
+              __typename
+              id
+            }
+          }
+          nodes {
             __typename
             id
           }
-        }
-        nodes {
-          __typename
-          id
-        }
-        pageInfo {
-          __typename
-          hasPreviousPage
-          hasNextPage
-          startCursor
-          endCursor
+          pageInfo {
+            __typename
+            hasPreviousPage
+            hasNextPage
+            startCursor
+            endCursor
+          }
         }
       }
-    }
-  `;
-
-  const store = new Store({
-    schema: require('../test-utils/relayPagination_schema.json'),
-    resolvers: {
-      Query: {
-        items: relayPagination({ mergeMode: 'outwards' }),
+    `;
+
+    const store = new Store({
+      schema: require('../test-utils/relayPagination_schema.json'),
+      resolvers: {
+        Query: {
+          items: relayPagination({ mergeMode: 'outwards' }),
+        },
       },
-    },
-  });
+    });
 
-  const results = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(1), itemEdge(2), itemEdge(3), itemEdge(4), itemEdge(5)],
-      nodes: [itemNode(1), itemNode(2), itemNode(3), itemNode(4), itemNode(5)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: true,
-        hasPreviousPage: false,
-        startCursor: '1',
-        endCursor: '5',
+    const results = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [
+          itemEdge(1),
+          itemEdge(2),
+          itemEdge(3),
+          itemEdge(4),
+          itemEdge(5),
+        ],
+        nodes: [
+          itemNode(1),
+          itemNode(2),
+          itemNode(3),
+          itemNode(4),
+          itemNode(5),
+        ],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: true,
+          hasPreviousPage: false,
+          startCursor: '1',
+          endCursor: '5',
+        },
       },
-    },
-  };
+    };
 
-  write(store, { query: Pagination, variables: { first: 2 } }, results);
+    write(store, { query: Pagination, variables: { first: 2 } }, results);
 
-  const res = query(store, {
-    query: Pagination,
-    variables: { first: 2 },
-  });
+    const res = query(store, {
+      query: Pagination,
+      variables: { first: 2 },
+    });
 
-  expect(res.partial).toBe(false);
-  expect(res.data).toEqual(results);
-});
+    expect(res.partial).toBe(false);
+    expect(res.data).toEqual(results);
+  });
 
-it("returns the cached items even if they don't fullfil the query", () => {
-  const Pagination = gql`
-    query ($first: Int, $last: Int, $before: String, $after: String) {
-      __typename
-      items(first: $first, last: $last, before: $before, after: $after) {
+  it("returns the cached items even if they don't fullfil the query", () => {
+    const Pagination = gql`
+      query ($first: Int, $last: Int, $before: String, $after: String) {
         __typename
-        edges {
+        items(first: $first, last: $last, before: $before, after: $after) {
           __typename
-          node {
+          edges {
+            __typename
+            node {
+              __typename
+              id
+            }
+          }
+          nodes {
             __typename
             id
           }
-        }
-        nodes {
-          __typename
-          id
-        }
-        pageInfo {
-          __typename
-          hasPreviousPage
-          hasNextPage
-          startCursor
-          endCursor
+          pageInfo {
+            __typename
+            hasPreviousPage
+            hasNextPage
+            startCursor
+            endCursor
+          }
         }
       }
-    }
-  `;
-
-  const store = new Store({
-    schema: require('../test-utils/relayPagination_schema.json'),
-    resolvers: {
-      Query: {
-        items: relayPagination(),
+    `;
+
+    const store = new Store({
+      schema: require('../test-utils/relayPagination_schema.json'),
+      resolvers: {
+        Query: {
+          items: relayPagination(),
+        },
       },
-    },
-  });
+    });
 
-  const results = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(1), itemEdge(2), itemEdge(3), itemEdge(4), itemEdge(5)],
-      nodes: [itemNode(1), itemNode(2), itemNode(3), itemNode(4), itemNode(5)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: true,
-        hasPreviousPage: false,
-        startCursor: '1',
-        endCursor: '5',
+    const results = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [
+          itemEdge(1),
+          itemEdge(2),
+          itemEdge(3),
+          itemEdge(4),
+          itemEdge(5),
+        ],
+        nodes: [
+          itemNode(1),
+          itemNode(2),
+          itemNode(3),
+          itemNode(4),
+          itemNode(5),
+        ],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: true,
+          hasPreviousPage: false,
+          startCursor: '1',
+          endCursor: '5',
+        },
       },
-    },
-  };
+    };
 
-  write(
-    store,
-    { query: Pagination, variables: { after: '3', first: 3, last: 3 } },
-    results
-  );
+    write(
+      store,
+      { query: Pagination, variables: { after: '3', first: 3, last: 3 } },
+      results
+    );
 
-  const res = query(store, {
-    query: Pagination,
-    variables: { after: '3', first: 3, last: 3 },
-  });
+    const res = query(store, {
+      query: Pagination,
+      variables: { after: '3', first: 3, last: 3 },
+    });
 
-  expect(res.partial).toBe(false);
-  expect(res.data).toEqual(results);
-});
+    expect(res.partial).toBe(false);
+    expect(res.data).toEqual(results);
+  });
 
-it('returns the cached items even when they come from a different query', () => {
-  const Pagination = gql`
-    query ($first: Int, $last: Int, $before: String, $after: String) {
-      __typename
-      items(first: $first, last: $last, before: $before, after: $after) {
+  it('returns the cached items even when they come from a different query', () => {
+    const Pagination = gql`
+      query ($first: Int, $last: Int, $before: String, $after: String) {
         __typename
-        edges {
+        items(first: $first, last: $last, before: $before, after: $after) {
           __typename
-          node {
+          edges {
+            __typename
+            node {
+              __typename
+              id
+            }
+          }
+          nodes {
             __typename
             id
           }
-        }
-        nodes {
-          __typename
-          id
-        }
-        pageInfo {
-          __typename
-          hasPreviousPage
-          hasNextPage
-          startCursor
-          endCursor
+          pageInfo {
+            __typename
+            hasPreviousPage
+            hasNextPage
+            startCursor
+            endCursor
+          }
         }
       }
-    }
-  `;
-
-  const store = new Store({
-    schema: require('../test-utils/relayPagination_schema.json'),
-    resolvers: {
-      Query: {
-        items: relayPagination(),
+    `;
+
+    const store = new Store({
+      schema: require('../test-utils/relayPagination_schema.json'),
+      resolvers: {
+        Query: {
+          items: relayPagination(),
+        },
       },
-    },
-  });
+    });
 
-  const results = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(1), itemEdge(2), itemEdge(3), itemEdge(4), itemEdge(5)],
-      nodes: [itemNode(1), itemNode(2), itemNode(3), itemNode(4), itemNode(5)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: true,
-        hasPreviousPage: false,
-        startCursor: '1',
-        endCursor: '5',
+    const results = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [
+          itemEdge(1),
+          itemEdge(2),
+          itemEdge(3),
+          itemEdge(4),
+          itemEdge(5),
+        ],
+        nodes: [
+          itemNode(1),
+          itemNode(2),
+          itemNode(3),
+          itemNode(4),
+          itemNode(5),
+        ],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: true,
+          hasPreviousPage: false,
+          startCursor: '1',
+          endCursor: '5',
+        },
       },
-    },
-  };
+    };
 
-  write(store, { query: Pagination, variables: { first: 5 } }, results);
+    write(store, { query: Pagination, variables: { first: 5 } }, results);
 
-  const res = query(store, {
-    query: Pagination,
-    variables: { after: '3', first: 2, last: 2 },
-  });
+    const res = query(store, {
+      query: Pagination,
+      variables: { after: '3', first: 2, last: 2 },
+    });
 
-  expect(res.partial).toBe(true);
-  expect(res.data).toEqual(results);
-});
+    expect(res.partial).toBe(true);
+    expect(res.data).toEqual(results);
+  });
 
-it('caches and retrieves correctly queries with inwards pagination', () => {
-  const Pagination = gql`
-    query ($first: Int, $last: Int, $before: String, $after: String) {
-      __typename
-      items(first: $first, last: $last, before: $before, after: $after) {
+  it('caches and retrieves correctly queries with inwards pagination', () => {
+    const Pagination = gql`
+      query ($first: Int, $last: Int, $before: String, $after: String) {
         __typename
-        edges {
+        items(first: $first, last: $last, before: $before, after: $after) {
           __typename
-          node {
+          edges {
+            __typename
+            node {
+              __typename
+              id
+            }
+          }
+          nodes {
             __typename
             id
           }
-        }
-        nodes {
-          __typename
-          id
-        }
-        pageInfo {
-          __typename
-          hasPreviousPage
-          hasNextPage
-          startCursor
-          endCursor
+          pageInfo {
+            __typename
+            hasPreviousPage
+            hasNextPage
+            startCursor
+            endCursor
+          }
         }
       }
-    }
-  `;
-
-  const store = new Store({
-    schema: require('../test-utils/relayPagination_schema.json'),
-    resolvers: {
-      Query: {
-        items: relayPagination(),
+    `;
+
+    const store = new Store({
+      schema: require('../test-utils/relayPagination_schema.json'),
+      resolvers: {
+        Query: {
+          items: relayPagination(),
+        },
       },
-    },
-  });
+    });
 
-  const results = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(1), itemEdge(2), itemEdge(3), itemEdge(4), itemEdge(5)],
-      nodes: [itemNode(1), itemNode(2), itemNode(3), itemNode(4), itemNode(5)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: true,
-        hasPreviousPage: false,
-        startCursor: '1',
-        endCursor: '5',
+    const results = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [
+          itemEdge(1),
+          itemEdge(2),
+          itemEdge(3),
+          itemEdge(4),
+          itemEdge(5),
+        ],
+        nodes: [
+          itemNode(1),
+          itemNode(2),
+          itemNode(3),
+          itemNode(4),
+          itemNode(5),
+        ],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: true,
+          hasPreviousPage: false,
+          startCursor: '1',
+          endCursor: '5',
+        },
       },
-    },
-  };
+    };
 
-  write(
-    store,
-    { query: Pagination, variables: { after: '2', first: 2, last: 2 } },
-    results
-  );
+    write(
+      store,
+      { query: Pagination, variables: { after: '2', first: 2, last: 2 } },
+      results
+    );
 
-  const res = query(store, {
-    query: Pagination,
-    variables: { after: '2', first: 2, last: 2 },
-  });
+    const res = query(store, {
+      query: Pagination,
+      variables: { after: '2', first: 2, last: 2 },
+    });
 
-  expect(res.partial).toBe(false);
-  expect(res.data).toEqual(results);
-});
+    expect(res.partial).toBe(false);
+    expect(res.data).toEqual(results);
+  });
 
-it('does not include a previous result when adding parameters', () => {
-  const Pagination = gql`
-    query ($first: Int, $filter: String) {
-      __typename
-      items(first: $first, filter: $filter) {
+  it('does not include a previous result when adding parameters', () => {
+    const Pagination = gql`
+      query ($first: Int, $filter: String) {
         __typename
-        edges {
+        items(first: $first, filter: $filter) {
           __typename
-          node {
+          edges {
+            __typename
+            node {
+              __typename
+              id
+            }
+          }
+          nodes {
             __typename
             id
           }
-        }
-        nodes {
-          __typename
-          id
-        }
-        pageInfo {
-          __typename
-          hasPreviousPage
-          hasNextPage
-          startCursor
-          endCursor
+          pageInfo {
+            __typename
+            hasPreviousPage
+            hasNextPage
+            startCursor
+            endCursor
+          }
         }
       }
-    }
-  `;
+    `;
 
-  const store = new Store({
-    resolvers: {
-      Query: {
-        items: relayPagination(),
+    const store = new Store({
+      resolvers: {
+        Query: {
+          items: relayPagination(),
+        },
       },
-    },
-  });
+    });
 
-  const results = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(1), itemEdge(2)],
-      nodes: [itemNode(1), itemNode(2)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: true,
-        hasPreviousPage: false,
-        startCursor: '1',
-        endCursor: '2',
+    const results = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(1), itemEdge(2)],
+        nodes: [itemNode(1), itemNode(2)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: true,
+          hasPreviousPage: false,
+          startCursor: '1',
+          endCursor: '2',
+        },
       },
-    },
-  };
+    };
 
-  const results2 = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [],
-      nodes: [],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: false,
-        hasPreviousPage: false,
-        startCursor: '1',
-        endCursor: '2',
+    const results2 = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [],
+        nodes: [],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: false,
+          hasPreviousPage: false,
+          startCursor: '1',
+          endCursor: '2',
+        },
       },
-    },
-  };
+    };
 
-  write(store, { query: Pagination, variables: { first: 2 } }, results);
+    write(store, { query: Pagination, variables: { first: 2 } }, results);
 
-  write(
-    store,
-    { query: Pagination, variables: { first: 2, filter: 'b' } },
-    results2
-  );
+    write(
+      store,
+      { query: Pagination, variables: { first: 2, filter: 'b' } },
+      results2
+    );
 
-  const res = query(store, {
-    query: Pagination,
-    variables: { first: 2, filter: 'b' },
+    const res = query(store, {
+      query: Pagination,
+      variables: { first: 2, filter: 'b' },
+    });
+    expect(res.data).toEqual(results2);
   });
-  expect(res.data).toEqual(results2);
-});
 
-it('Works with edges absent from query', () => {
-  const Pagination = gql`
-    query ($first: Int, $last: Int, $before: String, $after: String) {
-      __typename
-      items(first: $first, last: $last, before: $before, after: $after) {
+  it('Works with edges absent from query', () => {
+    const Pagination = gql`
+      query ($first: Int, $last: Int, $before: String, $after: String) {
         __typename
-        nodes {
+        items(first: $first, last: $last, before: $before, after: $after) {
           __typename
-          id
-        }
-        pageInfo {
-          __typename
-          hasPreviousPage
-          hasNextPage
-          startCursor
-          endCursor
+          nodes {
+            __typename
+            id
+          }
+          pageInfo {
+            __typename
+            hasPreviousPage
+            hasNextPage
+            startCursor
+            endCursor
+          }
         }
       }
-    }
-  `;
-
-  const store = new Store({
-    schema: require('../test-utils/relayPagination_schema.json'),
-    resolvers: {
-      Query: {
-        items: relayPagination({ mergeMode: 'outwards' }),
+    `;
+
+    const store = new Store({
+      schema: require('../test-utils/relayPagination_schema.json'),
+      resolvers: {
+        Query: {
+          items: relayPagination({ mergeMode: 'outwards' }),
+        },
       },
-    },
-  });
+    });
 
-  const results = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      nodes: [itemNode(1), itemNode(2), itemNode(3), itemNode(4), itemNode(5)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: true,
-        hasPreviousPage: false,
-        startCursor: '1',
-        endCursor: '5',
+    const results = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        nodes: [
+          itemNode(1),
+          itemNode(2),
+          itemNode(3),
+          itemNode(4),
+          itemNode(5),
+        ],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: true,
+          hasPreviousPage: false,
+          startCursor: '1',
+          endCursor: '5',
+        },
       },
-    },
-  };
+    };
 
-  write(store, { query: Pagination, variables: { first: 2 } }, results);
+    write(store, { query: Pagination, variables: { first: 2 } }, results);
 
-  const res = query(store, {
-    query: Pagination,
-    variables: { first: 2 },
-  });
+    const res = query(store, {
+      query: Pagination,
+      variables: { first: 2 },
+    });
 
-  expect(res.partial).toBe(false);
-  expect(res.data).toEqual(results);
-});
+    expect(res.partial).toBe(false);
+    expect(res.data).toEqual(results);
+  });
 
-it('Works with nodes absent from query', () => {
-  const Pagination = gql`
-    query ($first: Int, $last: Int, $before: String, $after: String) {
-      __typename
-      items(first: $first, last: $last, before: $before, after: $after) {
+  it('Works with nodes absent from query', () => {
+    const Pagination = gql`
+      query ($first: Int, $last: Int, $before: String, $after: String) {
         __typename
-        edges {
+        items(first: $first, last: $last, before: $before, after: $after) {
           __typename
-          node {
+          edges {
             __typename
-            id
+            node {
+              __typename
+              id
+            }
+          }
+          pageInfo {
+            __typename
+            hasPreviousPage
+            hasNextPage
+            startCursor
+            endCursor
           }
-        }
-        pageInfo {
-          __typename
-          hasPreviousPage
-          hasNextPage
-          startCursor
-          endCursor
         }
       }
-    }
-  `;
-
-  const store = new Store({
-    schema: require('../test-utils/relayPagination_schema.json'),
-    resolvers: {
-      Query: {
-        items: relayPagination({ mergeMode: 'outwards' }),
+    `;
+
+    const store = new Store({
+      schema: require('../test-utils/relayPagination_schema.json'),
+      resolvers: {
+        Query: {
+          items: relayPagination({ mergeMode: 'outwards' }),
+        },
       },
-    },
-  });
+    });
 
-  const results = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(1), itemEdge(2), itemEdge(3), itemEdge(4), itemEdge(5)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: true,
-        hasPreviousPage: false,
-        startCursor: '1',
-        endCursor: '5',
+    const results = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [
+          itemEdge(1),
+          itemEdge(2),
+          itemEdge(3),
+          itemEdge(4),
+          itemEdge(5),
+        ],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: true,
+          hasPreviousPage: false,
+          startCursor: '1',
+          endCursor: '5',
+        },
       },
-    },
-  };
+    };
 
-  write(store, { query: Pagination, variables: { first: 2 } }, results);
+    write(store, { query: Pagination, variables: { first: 2 } }, results);
 
-  const res = query(store, {
-    query: Pagination,
-    variables: { first: 2 },
-  });
+    const res = query(store, {
+      query: Pagination,
+      variables: { first: 2 },
+    });
 
-  expect(res.partial).toBe(false);
-  expect(res.data).toEqual(results);
-});
+    expect(res.partial).toBe(false);
+    expect(res.data).toEqual(results);
+  });
 
-it('handles subsequent queries with larger last values', () => {
-  const Pagination = gql`
-    query ($last: Int!) {
-      __typename
-      items(last: $last) {
+  it('handles subsequent queries with larger last values', () => {
+    const Pagination = gql`
+      query ($last: Int!) {
         __typename
-        edges {
+        items(last: $last) {
           __typename
-          node {
+          edges {
+            __typename
+            node {
+              __typename
+              id
+            }
+          }
+          nodes {
             __typename
             id
           }
-        }
-        nodes {
-          __typename
-          id
-        }
-        pageInfo {
-          __typename
-          hasPreviousPage
-          startCursor
+          pageInfo {
+            __typename
+            hasPreviousPage
+            startCursor
+          }
         }
       }
-    }
-  `;
+    `;
 
-  const store = new Store({
-    resolvers: {
-      Query: {
-        items: relayPagination(),
+    const store = new Store({
+      resolvers: {
+        Query: {
+          items: relayPagination(),
+        },
       },
-    },
-  });
+    });
 
-  const pageOne = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(2)],
-      nodes: [itemNode(2)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasPreviousPage: true,
-        startCursor: '2',
+    const pageOne = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(2)],
+        nodes: [itemNode(2)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasPreviousPage: true,
+          startCursor: '2',
+        },
       },
-    },
-  };
+    };
 
-  const pageTwo = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(1), itemEdge(2)],
-      nodes: [itemNode(1), itemNode(2)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasPreviousPage: false,
-        startCursor: '1',
+    const pageTwo = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(1), itemEdge(2)],
+        nodes: [itemNode(1), itemNode(2)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasPreviousPage: false,
+          startCursor: '1',
+        },
       },
-    },
-  };
+    };
 
-  const pageThree = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(0), itemEdge(1), itemEdge(2)],
-      nodes: [itemNode(0), itemNode(1), itemNode(2)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasPreviousPage: false,
-        startCursor: '0',
+    const pageThree = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(0), itemEdge(1), itemEdge(2)],
+        nodes: [itemNode(0), itemNode(1), itemNode(2)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasPreviousPage: false,
+          startCursor: '0',
+        },
       },
-    },
-  };
+    };
 
-  write(store, { query: Pagination, variables: { last: 1 } }, pageOne);
-  write(store, { query: Pagination, variables: { last: 2 } }, pageTwo);
+    write(store, { query: Pagination, variables: { last: 1 } }, pageOne);
+    write(store, { query: Pagination, variables: { last: 2 } }, pageTwo);
 
-  let res = query(store, { query: Pagination, variables: { last: 2 } });
+    let res = query(store, { query: Pagination, variables: { last: 2 } });
 
-  expect(res.partial).toBe(false);
-  expect(res.data).toEqual(pageTwo);
+    expect(res.partial).toBe(false);
+    expect(res.data).toEqual(pageTwo);
 
-  write(store, { query: Pagination, variables: { last: 3 } }, pageThree);
+    write(store, { query: Pagination, variables: { last: 3 } }, pageThree);
 
-  res = query(store, { query: Pagination, variables: { last: 3 } });
+    res = query(store, { query: Pagination, variables: { last: 3 } });
 
-  expect(res.partial).toBe(false);
-  expect(res.data).toEqual(pageThree);
-});
+    expect(res.partial).toBe(false);
+    expect(res.data).toEqual(pageThree);
+  });
 
-it('handles subsequent queries with larger first values', () => {
-  const Pagination = gql`
-    query ($first: Int!) {
-      __typename
-      items(first: $first) {
+  it('handles subsequent queries with larger first values', () => {
+    const Pagination = gql`
+      query ($first: Int!) {
         __typename
-        edges {
+        items(first: $first) {
           __typename
-          node {
+          edges {
+            __typename
+            node {
+              __typename
+              id
+            }
+          }
+          nodes {
             __typename
             id
           }
-        }
-        nodes {
-          __typename
-          id
-        }
-        pageInfo {
-          __typename
-          hasNextPage
-          endCursor
+          pageInfo {
+            __typename
+            hasNextPage
+            endCursor
+          }
         }
       }
-    }
-  `;
+    `;
 
-  const store = new Store({
-    resolvers: {
-      Query: {
-        items: relayPagination(),
+    const store = new Store({
+      resolvers: {
+        Query: {
+          items: relayPagination(),
+        },
       },
-    },
-  });
+    });
 
-  const pageOne = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(1)],
-      nodes: [itemNode(1)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: true,
-        endCursor: '1',
+    const pageOne = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(1)],
+        nodes: [itemNode(1)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: true,
+          endCursor: '1',
+        },
       },
-    },
-  };
+    };
 
-  const pageTwo = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      edges: [itemEdge(1), itemEdge(2)],
-      nodes: [itemNode(1), itemNode(2)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        hasNextPage: false,
-        endCursor: '2',
+    const pageTwo = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(1), itemEdge(2)],
+        nodes: [itemNode(1), itemNode(2)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: false,
+          endCursor: '2',
+        },
       },
-    },
-  };
+    };
 
-  write(store, { query: Pagination, variables: { first: 1 } }, pageOne);
-  write(store, { query: Pagination, variables: { first: 2 } }, pageTwo);
+    write(store, { query: Pagination, variables: { first: 1 } }, pageOne);
+    write(store, { query: Pagination, variables: { first: 2 } }, pageTwo);
 
-  const res = query(store, { query: Pagination, variables: { first: 2 } });
+    const res = query(store, { query: Pagination, variables: { first: 2 } });
 
-  expect(res.partial).toBe(false);
-  expect(res.data).toEqual(pageTwo);
-});
+    expect(res.partial).toBe(false);
+    expect(res.data).toEqual(pageTwo);
+  });
 
-it('ignores empty pages when paginating', () => {
-  const PaginationForward = gql`
-    query ($first: Int!, $after: String) {
-      __typename
-      items(first: $first, after: $after) {
+  it('ignores empty pages when paginating', () => {
+    const PaginationForward = gql`
+      query ($first: Int!, $after: String) {
         __typename
-        nodes {
+        items(first: $first, after: $after) {
           __typename
-          id
-        }
-        pageInfo {
-          __typename
-          startCursor
-          endCursor
+          nodes {
+            __typename
+            id
+          }
+          pageInfo {
+            __typename
+            startCursor
+            endCursor
+          }
         }
       }
-    }
-  `;
-  const PaginationBackward = gql`
-    query ($last: Int!, $before: String) {
-      __typename
-      items(last: $last, before: $before) {
+    `;
+    const PaginationBackward = gql`
+      query ($last: Int!, $before: String) {
         __typename
-        nodes {
+        items(last: $last, before: $before) {
           __typename
-          id
-        }
-        pageInfo {
-          __typename
-          startCursor
-          endCursor
+          nodes {
+            __typename
+            id
+          }
+          pageInfo {
+            __typename
+            startCursor
+            endCursor
+          }
         }
       }
-    }
-  `;
+    `;
 
-  const store = new Store({
-    resolvers: {
-      Query: {
-        items: relayPagination(),
+    const store = new Store({
+      resolvers: {
+        Query: {
+          items: relayPagination(),
+        },
       },
-    },
-  });
+    });
 
-  const forwardOne = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      nodes: [itemNode(1), itemNode(2)],
-      pageInfo: {
-        __typename: 'PageInfo',
-        startCursor: '1',
-        endCursor: '2',
+    const forwardOne = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        nodes: [itemNode(1), itemNode(2)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          startCursor: '1',
+          endCursor: '2',
+        },
       },
-    },
-  };
-  const forwardAfter = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      nodes: [],
-      pageInfo: {
-        __typename: 'PageInfo',
-        startCursor: null,
-        endCursor: null,
+    };
+    const forwardAfter = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        nodes: [],
+        pageInfo: {
+          __typename: 'PageInfo',
+          startCursor: null,
+          endCursor: null,
+        },
       },
-    },
-  };
-  const backwardBefore = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      nodes: [],
-      pageInfo: {
-        __typename: 'PageInfo',
-        startCursor: null,
-        endCursor: null,
+    };
+    const backwardBefore = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        nodes: [],
+        pageInfo: {
+          __typename: 'PageInfo',
+          startCursor: null,
+          endCursor: null,
+        },
       },
-    },
-  };
-
-  write(
-    store,
-    { query: PaginationForward, variables: { first: 2 } },
-    forwardOne
-  );
-  write(
-    store,
-    { query: PaginationBackward, variables: { last: 1, before: '1' } },
-    backwardBefore
-  );
-
-  const res = query(store, {
-    query: PaginationForward,
-    variables: { first: 2 },
+    };
+
+    write(
+      store,
+      { query: PaginationForward, variables: { first: 2 } },
+      forwardOne
+    );
+    write(
+      store,
+      { query: PaginationBackward, variables: { last: 1, before: '1' } },
+      backwardBefore
+    );
+
+    const res = query(store, {
+      query: PaginationForward,
+      variables: { first: 2 },
+    });
+
+    expect(res.partial).toBe(false);
+    expect(res.data).toEqual(forwardOne);
+    write(
+      store,
+      { query: PaginationForward, variables: { first: 1, after: '2' } },
+      forwardAfter
+    );
+
+    expect(res.partial).toBe(false);
+    expect(res.data).toEqual(forwardOne);
   });
 
-  expect(res.partial).toBe(false);
-  expect(res.data).toEqual(forwardOne);
-  write(
-    store,
-    { query: PaginationForward, variables: { first: 1, after: '2' } },
-    forwardAfter
-  );
+  it('allows for an empty page when this is the only result', () => {
+    const Pagination = gql`
+      query ($first: Int!, $after: String) {
+        __typename
+        items(first: $first, after: $after) {
+          __typename
+          nodes {
+            __typename
+            id
+          }
+          pageInfo {
+            __typename
+            startCursor
+            endCursor
+          }
+        }
+      }
+    `;
 
-  expect(res.partial).toBe(false);
-  expect(res.data).toEqual(forwardOne);
+    const store = new Store({
+      resolvers: {
+        Query: {
+          items: relayPagination(),
+        },
+      },
+    });
+
+    const pageOne = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        nodes: [],
+        pageInfo: {
+          __typename: 'PageInfo',
+          startCursor: null,
+          endCursor: null,
+        },
+      },
+    };
+
+    write(store, { query: Pagination, variables: { first: 2 } }, pageOne);
+    const res = query(store, {
+      query: Pagination,
+      variables: { first: 2 },
+    });
+
+    expect(res.partial).toBe(false);
+    expect(res.data).toEqual(pageOne);
+  });
 });
 
-it('allows for an empty page when this is the only result', () => {
-  const Pagination = gql`
-    query ($first: Int!, $after: String) {
-      __typename
-      items(first: $first, after: $after) {
+describe('as directive', () => {
+  it('works with forward pagination', () => {
+    const Pagination = gql`
+      query ($cursor: String) {
         __typename
-        nodes {
+        items(first: 1, after: $cursor) @_relayPagination {
           __typename
-          id
-        }
-        pageInfo {
-          __typename
-          startCursor
-          endCursor
+          edges {
+            __typename
+            node {
+              __typename
+              id
+            }
+          }
+          nodes {
+            __typename
+            id
+          }
+          pageInfo {
+            __typename
+            hasNextPage
+            endCursor
+          }
         }
       }
-    }
-  `;
+    `;
 
-  const store = new Store({
-    resolvers: {
-      Query: {
-        items: relayPagination(),
+    const store = new Store({
+      directives: {
+        relayPagination: () => relayPagination(),
       },
-    },
-  });
+    });
 
-  const pageOne = {
-    __typename: 'Query',
-    items: {
-      __typename: 'ItemsConnection',
-      nodes: [],
-      pageInfo: {
-        __typename: 'PageInfo',
-        startCursor: null,
-        endCursor: null,
+    const pageOne = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(1)],
+        nodes: [itemNode(1)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: true,
+          endCursor: '1',
+        },
       },
-    },
-  };
+    };
 
-  write(store, { query: Pagination, variables: { first: 2 } }, pageOne);
-  const res = query(store, {
-    query: Pagination,
-    variables: { first: 2 },
-  });
+    const pageTwo = {
+      __typename: 'Query',
+      items: {
+        __typename: 'ItemsConnection',
+        edges: [itemEdge(2)],
+        nodes: [itemNode(2)],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: false,
+          endCursor: null,
+        },
+      },
+    };
+
+    write(store, { query: Pagination, variables: { cursor: null } }, pageOne);
+    write(store, { query: Pagination, variables: { cursor: '1' } }, pageTwo);
+
+    const res = query(store, { query: Pagination });
 
-  expect(res.partial).toBe(false);
-  expect(res.data).toEqual(pageOne);
+    expect(res.partial).toBe(false);
+    expect(res.data).toEqual({
+      ...pageTwo,
+      items: {
+        ...pageTwo.items,
+        edges: [pageOne.items.edges[0], pageTwo.items.edges[0]],
+        nodes: [pageOne.items.nodes[0], pageTwo.items.nodes[0]],
+      },
+    });
+  });
 });
diff --git a/exchanges/graphcache/src/extras/simplePagination.test.ts b/exchanges/graphcache/src/extras/simplePagination.test.ts
index a79103c9..dbe14b06 100644
--- a/exchanges/graphcache/src/extras/simplePagination.test.ts
+++ b/exchanges/graphcache/src/extras/simplePagination.test.ts
@@ -1,437 +1,584 @@
 import { gql } from '@urql/core';
-import { it, expect } from 'vitest';
+import { it, expect, describe } from 'vitest';
 import { __initAnd_query as query } from '../operations/query';
 import { __initAnd_write as write } from '../operations/write';
 import { Store } from '../store/store';
-import { simplePagination } from './simplePagination';
+import { MergeMode, simplePagination } from './simplePagination';
 
-it('works with forward pagination', () => {
-  const Pagination = gql`
-    query ($skip: Number, $limit: Number) {
-      __typename
-      persons(skip: $skip, limit: $limit) {
+describe('as resolver', () => {
+  it('works with forward pagination', () => {
+    const Pagination = gql`
+      query ($skip: Number, $limit: Number) {
         __typename
-        id
-        name
+        persons(skip: $skip, limit: $limit) {
+          __typename
+          id
+          name
+        }
       }
-    }
-  `;
+    `;
 
-  const store = new Store({
-    resolvers: {
-      Query: {
-        persons: simplePagination(),
+    const store = new Store({
+      resolvers: {
+        Query: {
+          persons: simplePagination(),
+        },
       },
-    },
+    });
+
+    const pageOne = {
+      __typename: 'Query',
+      persons: [
+        { id: 1, name: 'Jovi', __typename: 'Person' },
+        { id: 2, name: 'Phil', __typename: 'Person' },
+        { id: 3, name: 'Andy', __typename: 'Person' },
+      ],
+    };
+
+    const pageTwo = {
+      __typename: 'Query',
+      persons: [
+        { id: 4, name: 'Kadi', __typename: 'Person' },
+        { id: 5, name: 'Dom', __typename: 'Person' },
+        { id: 6, name: 'Sofia', __typename: 'Person' },
+      ],
+    };
+
+    write(
+      store,
+      { query: Pagination, variables: { skip: 0, limit: 3 } },
+      pageOne
+    );
+    const pageOneResult = query(store, {
+      query: Pagination,
+      variables: { skip: 0, limit: 3 },
+    });
+    expect(pageOneResult.data).toEqual(pageOne);
+
+    write(
+      store,
+      { query: Pagination, variables: { skip: 3, limit: 3 } },
+      pageTwo
+    );
+
+    const pageTwoResult = query(store, {
+      query: Pagination,
+      variables: { skip: 3, limit: 3 },
+    });
+    expect((pageTwoResult.data as any).persons).toEqual([
+      ...pageOne.persons,
+      ...pageTwo.persons,
+    ]);
+
+    const pageThreeResult = query(store, {
+      query: Pagination,
+      variables: { skip: 6, limit: 3 },
+    });
+    expect(pageThreeResult.data).toEqual(null);
   });
 
-  const pageOne = {
-    __typename: 'Query',
-    persons: [
-      { id: 1, name: 'Jovi', __typename: 'Person' },
-      { id: 2, name: 'Phil', __typename: 'Person' },
-      { id: 3, name: 'Andy', __typename: 'Person' },
-    ],
-  };
-
-  const pageTwo = {
-    __typename: 'Query',
-    persons: [
-      { id: 4, name: 'Kadi', __typename: 'Person' },
-      { id: 5, name: 'Dom', __typename: 'Person' },
-      { id: 6, name: 'Sofia', __typename: 'Person' },
-    ],
-  };
-
-  write(
-    store,
-    { query: Pagination, variables: { skip: 0, limit: 3 } },
-    pageOne
-  );
-  const pageOneResult = query(store, {
-    query: Pagination,
-    variables: { skip: 0, limit: 3 },
-  });
-  expect(pageOneResult.data).toEqual(pageOne);
-
-  write(
-    store,
-    { query: Pagination, variables: { skip: 3, limit: 3 } },
-    pageTwo
-  );
-
-  const pageTwoResult = query(store, {
-    query: Pagination,
-    variables: { skip: 3, limit: 3 },
-  });
-  expect((pageTwoResult.data as any).persons).toEqual([
-    ...pageOne.persons,
-    ...pageTwo.persons,
-  ]);
-
-  const pageThreeResult = query(store, {
-    query: Pagination,
-    variables: { skip: 6, limit: 3 },
-  });
-  expect(pageThreeResult.data).toEqual(null);
-});
-
-it('works with backwards pagination', () => {
-  const Pagination = gql`
-    query ($skip: Number, $limit: Number) {
-      __typename
-      persons(skip: $skip, limit: $limit) {
+  it('works with backwards pagination', () => {
+    const Pagination = gql`
+      query ($skip: Number, $limit: Number) {
         __typename
-        id
-        name
+        persons(skip: $skip, limit: $limit) {
+          __typename
+          id
+          name
+        }
       }
-    }
-  `;
+    `;
 
-  const store = new Store({
-    resolvers: {
-      Query: {
-        persons: simplePagination({ mergeMode: 'before' }),
+    const store = new Store({
+      resolvers: {
+        Query: {
+          persons: simplePagination({ mergeMode: 'before' }),
+        },
       },
-    },
-  });
-
-  const pageOne = {
-    __typename: 'Query',
-    persons: [
-      { id: 7, name: 'Jovi', __typename: 'Person' },
-      { id: 8, name: 'Phil', __typename: 'Person' },
-      { id: 9, name: 'Andy', __typename: 'Person' },
-    ],
-  };
-
-  const pageTwo = {
-    __typename: 'Query',
-    persons: [
-      { id: 4, name: 'Kadi', __typename: 'Person' },
-      { id: 5, name: 'Dom', __typename: 'Person' },
-      { id: 6, name: 'Sofia', __typename: 'Person' },
-    ],
-  };
-
-  write(
-    store,
-    { query: Pagination, variables: { skip: 0, limit: 3 } },
-    pageOne
-  );
-  const pageOneResult = query(store, {
-    query: Pagination,
-    variables: { skip: 0, limit: 3 },
-  });
-  expect(pageOneResult.data).toEqual(pageOne);
-
-  write(
-    store,
-    { query: Pagination, variables: { skip: 3, limit: 3 } },
-    pageTwo
-  );
-
-  const pageTwoResult = query(store, {
-    query: Pagination,
-    variables: { skip: 3, limit: 3 },
-  });
-  expect((pageTwoResult.data as any).persons).toEqual([
-    ...pageTwo.persons,
-    ...pageOne.persons,
-  ]);
-
-  const pageThreeResult = query(store, {
-    query: Pagination,
-    variables: { skip: 6, limit: 3 },
+    });
+
+    const pageOne = {
+      __typename: 'Query',
+      persons: [
+        { id: 7, name: 'Jovi', __typename: 'Person' },
+        { id: 8, name: 'Phil', __typename: 'Person' },
+        { id: 9, name: 'Andy', __typename: 'Person' },
+      ],
+    };
+
+    const pageTwo = {
+      __typename: 'Query',
+      persons: [
+        { id: 4, name: 'Kadi', __typename: 'Person' },
+        { id: 5, name: 'Dom', __typename: 'Person' },
+        { id: 6, name: 'Sofia', __typename: 'Person' },
+      ],
+    };
+
+    write(
+      store,
+      { query: Pagination, variables: { skip: 0, limit: 3 } },
+      pageOne
+    );
+    const pageOneResult = query(store, {
+      query: Pagination,
+      variables: { skip: 0, limit: 3 },
+    });
+    expect(pageOneResult.data).toEqual(pageOne);
+
+    write(
+      store,
+      { query: Pagination, variables: { skip: 3, limit: 3 } },
+      pageTwo
+    );
+
+    const pageTwoResult = query(store, {
+      query: Pagination,
+      variables: { skip: 3, limit: 3 },
+    });
+    expect((pageTwoResult.data as any).persons).toEqual([
+      ...pageTwo.persons,
+      ...pageOne.persons,
+    ]);
+
+    const pageThreeResult = query(store, {
+      query: Pagination,
+      variables: { skip: 6, limit: 3 },
+    });
+    expect(pageThreeResult.data).toEqual(null);
   });
-  expect(pageThreeResult.data).toEqual(null);
-});
 
-it('handles duplicates', () => {
-  const Pagination = gql`
-    query ($skip: Number, $limit: Number) {
-      __typename
-      persons(skip: $skip, limit: $limit) {
+  it('handles duplicates', () => {
+    const Pagination = gql`
+      query ($skip: Number, $limit: Number) {
         __typename
-        id
-        name
+        persons(skip: $skip, limit: $limit) {
+          __typename
+          id
+          name
+        }
       }
-    }
-  `;
+    `;
 
-  const store = new Store({
-    resolvers: {
-      Query: {
-        persons: simplePagination(),
+    const store = new Store({
+      resolvers: {
+        Query: {
+          persons: simplePagination(),
+        },
       },
-    },
-  });
-
-  const pageOne = {
-    __typename: 'Query',
-    persons: [
-      { id: 1, name: 'Jovi', __typename: 'Person' },
-      { id: 2, name: 'Phil', __typename: 'Person' },
-      { id: 3, name: 'Andy', __typename: 'Person' },
-    ],
-  };
-
-  const pageTwo = {
-    __typename: 'Query',
-    persons: [
-      { id: 3, name: 'Andy', __typename: 'Person' },
-      { id: 4, name: 'Kadi', __typename: 'Person' },
-      { id: 5, name: 'Dom', __typename: 'Person' },
-    ],
-  };
-
-  write(
-    store,
-    { query: Pagination, variables: { skip: 0, limit: 3 } },
-    pageOne
-  );
-  write(
-    store,
-    { query: Pagination, variables: { skip: 2, limit: 3 } },
-    pageTwo
-  );
-
-  const result = query(store, {
-    query: Pagination,
-    variables: { skip: 2, limit: 3 },
-  });
-  expect(result.data).toEqual({
-    __typename: 'Query',
-    persons: [...pageOne.persons, pageTwo.persons[1], pageTwo.persons[2]],
+    });
+
+    const pageOne = {
+      __typename: 'Query',
+      persons: [
+        { id: 1, name: 'Jovi', __typename: 'Person' },
+        { id: 2, name: 'Phil', __typename: 'Person' },
+        { id: 3, name: 'Andy', __typename: 'Person' },
+      ],
+    };
+
+    const pageTwo = {
+      __typename: 'Query',
+      persons: [
+        { id: 3, name: 'Andy', __typename: 'Person' },
+        { id: 4, name: 'Kadi', __typename: 'Person' },
+        { id: 5, name: 'Dom', __typename: 'Person' },
+      ],
+    };
+
+    write(
+      store,
+      { query: Pagination, variables: { skip: 0, limit: 3 } },
+      pageOne
+    );
+    write(
+      store,
+      { query: Pagination, variables: { skip: 2, limit: 3 } },
+      pageTwo
+    );
+
+    const result = query(store, {
+      query: Pagination,
+      variables: { skip: 2, limit: 3 },
+    });
+    expect(result.data).toEqual({
+      __typename: 'Query',
+      persons: [...pageOne.persons, pageTwo.persons[1], pageTwo.persons[2]],
+    });
   });
-});
 
-it('should not return previous result when adding a parameter', () => {
-  const Pagination = gql`
-    query ($skip: Number, $limit: Number, $filter: String) {
-      __typename
-      persons(skip: $skip, limit: $limit, filter: $filter) {
+  it('should not return previous result when adding a parameter', () => {
+    const Pagination = gql`
+      query ($skip: Number, $limit: Number, $filter: String) {
         __typename
-        id
-        name
+        persons(skip: $skip, limit: $limit, filter: $filter) {
+          __typename
+          id
+          name
+        }
       }
-    }
-  `;
+    `;
 
-  const store = new Store({
-    resolvers: {
-      Query: {
-        persons: simplePagination(),
+    const store = new Store({
+      resolvers: {
+        Query: {
+          persons: simplePagination(),
+        },
       },
-    },
+    });
+
+    const pageOne = {
+      __typename: 'Query',
+      persons: [
+        { id: 1, name: 'Jovi', __typename: 'Person' },
+        { id: 2, name: 'Phil', __typename: 'Person' },
+        { id: 3, name: 'Andy', __typename: 'Person' },
+      ],
+    };
+
+    const emptyPage = {
+      __typename: 'Query',
+      persons: [],
+    };
+
+    write(
+      store,
+      { query: Pagination, variables: { skip: 0, limit: 3 } },
+      pageOne
+    );
+    write(
+      store,
+      { query: Pagination, variables: { skip: 0, limit: 3, filter: 'b' } },
+      emptyPage
+    );
+
+    const res = query(store, {
+      query: Pagination,
+      variables: { skip: 0, limit: 3, filter: 'b' },
+    });
+    expect(res.data).toEqual({ __typename: 'Query', persons: [] });
   });
 
-  const pageOne = {
-    __typename: 'Query',
-    persons: [
-      { id: 1, name: 'Jovi', __typename: 'Person' },
-      { id: 2, name: 'Phil', __typename: 'Person' },
-      { id: 3, name: 'Andy', __typename: 'Person' },
-    ],
-  };
-
-  const emptyPage = {
-    __typename: 'Query',
-    persons: [],
-  };
-
-  write(
-    store,
-    { query: Pagination, variables: { skip: 0, limit: 3 } },
-    pageOne
-  );
-  write(
-    store,
-    { query: Pagination, variables: { skip: 0, limit: 3, filter: 'b' } },
-    emptyPage
-  );
-
-  const res = query(store, {
-    query: Pagination,
-    variables: { skip: 0, limit: 3, filter: 'b' },
-  });
-  expect(res.data).toEqual({ __typename: 'Query', persons: [] });
-});
-
-it('should preserve the correct order in forward pagination', () => {
-  const Pagination = gql`
-    query ($skip: Number, $limit: Number) {
-      __typename
-      persons(skip: $skip, limit: $limit) {
+  it('should preserve the correct order in forward pagination', () => {
+    const Pagination = gql`
+      query ($skip: Number, $limit: Number) {
         __typename
-        id
-        name
+        persons(skip: $skip, limit: $limit) {
+          __typename
+          id
+          name
+        }
       }
-    }
-  `;
+    `;
 
-  const store = new Store({
-    resolvers: {
-      Query: {
-        persons: simplePagination({ mergeMode: 'after' }),
+    const store = new Store({
+      resolvers: {
+        Query: {
+          persons: simplePagination({ mergeMode: 'after' }),
+        },
       },
-    },
-  });
-
-  const pageOne = {
-    __typename: 'Query',
-    persons: [
-      { id: 1, name: 'Jovi', __typename: 'Person' },
-      { id: 2, name: 'Phil', __typename: 'Person' },
-      { id: 3, name: 'Andy', __typename: 'Person' },
-    ],
-  };
-
-  const pageTwo = {
-    __typename: 'Query',
-    persons: [
-      { id: 4, name: 'Kadi', __typename: 'Person' },
-      { id: 5, name: 'Dom', __typename: 'Person' },
-      { id: 6, name: 'Sofia', __typename: 'Person' },
-    ],
-  };
-
-  write(
-    store,
-    { query: Pagination, variables: { skip: 3, limit: 3 } },
-    pageTwo
-  );
-  write(
-    store,
-    { query: Pagination, variables: { skip: 0, limit: 3 } },
-    pageOne
-  );
-
-  const result = query(store, {
-    query: Pagination,
-    variables: { skip: 3, limit: 3 },
+    });
+
+    const pageOne = {
+      __typename: 'Query',
+      persons: [
+        { id: 1, name: 'Jovi', __typename: 'Person' },
+        { id: 2, name: 'Phil', __typename: 'Person' },
+        { id: 3, name: 'Andy', __typename: 'Person' },
+      ],
+    };
+
+    const pageTwo = {
+      __typename: 'Query',
+      persons: [
+        { id: 4, name: 'Kadi', __typename: 'Person' },
+        { id: 5, name: 'Dom', __typename: 'Person' },
+        { id: 6, name: 'Sofia', __typename: 'Person' },
+      ],
+    };
+
+    write(
+      store,
+      { query: Pagination, variables: { skip: 3, limit: 3 } },
+      pageTwo
+    );
+    write(
+      store,
+      { query: Pagination, variables: { skip: 0, limit: 3 } },
+      pageOne
+    );
+
+    const result = query(store, {
+      query: Pagination,
+      variables: { skip: 3, limit: 3 },
+    });
+    expect(result.data).toEqual({
+      __typename: 'Query',
+      persons: [...pageOne.persons, ...pageTwo.persons],
+    });
   });
-  expect(result.data).toEqual({
-    __typename: 'Query',
-    persons: [...pageOne.persons, ...pageTwo.persons],
-  });
-});
 
-it('should preserve the correct order in backward pagination', () => {
-  const Pagination = gql`
-    query ($skip: Number, $limit: Number) {
-      __typename
-      persons(skip: $skip, limit: $limit) {
+  it('should preserve the correct order in backward pagination', () => {
+    const Pagination = gql`
+      query ($skip: Number, $limit: Number) {
         __typename
-        id
-        name
+        persons(skip: $skip, limit: $limit) {
+          __typename
+          id
+          name
+        }
       }
-    }
-  `;
+    `;
 
-  const store = new Store({
-    resolvers: {
-      Query: {
-        persons: simplePagination({ mergeMode: 'before' }),
+    const store = new Store({
+      resolvers: {
+        Query: {
+          persons: simplePagination({ mergeMode: 'before' }),
+        },
       },
-    },
+    });
+
+    const pageOne = {
+      __typename: 'Query',
+      persons: [
+        { id: 7, name: 'Jovi', __typename: 'Person' },
+        { id: 8, name: 'Phil', __typename: 'Person' },
+        { id: 9, name: 'Andy', __typename: 'Person' },
+      ],
+    };
+
+    const pageTwo = {
+      __typename: 'Query',
+      persons: [
+        { id: 4, name: 'Kadi', __typename: 'Person' },
+        { id: 5, name: 'Dom', __typename: 'Person' },
+        { id: 6, name: 'Sofia', __typename: 'Person' },
+      ],
+    };
+
+    write(
+      store,
+      { query: Pagination, variables: { skip: 3, limit: 3 } },
+      pageTwo
+    );
+    write(
+      store,
+      { query: Pagination, variables: { skip: 0, limit: 3 } },
+      pageOne
+    );
+
+    const result = query(store, {
+      query: Pagination,
+      variables: { skip: 3, limit: 3 },
+    });
+
+    expect(result.data).toEqual({
+      __typename: 'Query',
+      persons: [...pageTwo.persons, ...pageOne.persons],
+    });
   });
 
-  const pageOne = {
-    __typename: 'Query',
-    persons: [
-      { id: 7, name: 'Jovi', __typename: 'Person' },
-      { id: 8, name: 'Phil', __typename: 'Person' },
-      { id: 9, name: 'Andy', __typename: 'Person' },
-    ],
-  };
-
-  const pageTwo = {
-    __typename: 'Query',
-    persons: [
-      { id: 4, name: 'Kadi', __typename: 'Person' },
-      { id: 5, name: 'Dom', __typename: 'Person' },
-      { id: 6, name: 'Sofia', __typename: 'Person' },
-    ],
-  };
-
-  write(
-    store,
-    { query: Pagination, variables: { skip: 3, limit: 3 } },
-    pageTwo
-  );
-  write(
-    store,
-    { query: Pagination, variables: { skip: 0, limit: 3 } },
-    pageOne
-  );
-
-  const result = query(store, {
-    query: Pagination,
-    variables: { skip: 3, limit: 3 },
-  });
+  it('prevents overlapping of pagination on different arguments', () => {
+    const Pagination = gql`
+      query ($skip: Number, $limit: Number, $filter: string) {
+        __typename
+        persons(skip: $skip, limit: $limit, filter: $filter) {
+          __typename
+          id
+          name
+        }
+      }
+    `;
 
-  expect(result.data).toEqual({
-    __typename: 'Query',
-    persons: [...pageTwo.persons, ...pageOne.persons],
+    const store = new Store({
+      resolvers: {
+        Query: {
+          persons: simplePagination(),
+        },
+      },
+    });
+
+    const page = withId => ({
+      __typename: 'Query',
+      persons: [{ id: withId, name: withId, __typename: 'Person' }],
+    });
+
+    write(
+      store,
+      { query: Pagination, variables: { filter: 'one', skip: 0, limit: 1 } },
+      page('one')
+    );
+
+    write(
+      store,
+      { query: Pagination, variables: { filter: 'two', skip: 1, limit: 1 } },
+      page('two')
+    );
+
+    const resOne = query(store, {
+      query: Pagination,
+      variables: { filter: 'one', skip: 0, limit: 1 },
+    });
+    const resTwo = query(store, {
+      query: Pagination,
+      variables: { filter: 'two', skip: 1, limit: 1 },
+    });
+    const resThree = query(store, {
+      query: Pagination,
+      variables: { filter: 'three', skip: 2, limit: 1 },
+    });
+
+    expect(resOne.data).toHaveProperty('persons[0].id', 'one');
+    expect(resOne.data).toHaveProperty('persons.length', 1);
+
+    expect(resTwo.data).toHaveProperty('persons[0].id', 'two');
+    expect(resTwo.data).toHaveProperty('persons.length', 1);
+
+    expect(resThree.data).toEqual(null);
   });
 });
 
-it('prevents overlapping of pagination on different arguments', () => {
-  const Pagination = gql`
-    query ($skip: Number, $limit: Number, $filter: string) {
-      __typename
-      persons(skip: $skip, limit: $limit, filter: $filter) {
+describe('as directive', () => {
+  it('works with forward pagination', () => {
+    const Pagination = gql`
+      query ($skip: Number, $limit: Number) {
         __typename
-        id
-        name
+        persons(skip: $skip, limit: $limit) @_simplePagination {
+          __typename
+          id
+          name
+        }
       }
-    }
-  `;
+    `;
 
-  const store = new Store({
-    resolvers: {
-      Query: {
-        persons: simplePagination(),
+    const store = new Store({
+      directives: {
+        simplePagination: () => simplePagination(),
       },
-    },
+    });
+
+    const pageOne = {
+      __typename: 'Query',
+      persons: [
+        { id: 1, name: 'Jovi', __typename: 'Person' },
+        { id: 2, name: 'Phil', __typename: 'Person' },
+        { id: 3, name: 'Andy', __typename: 'Person' },
+      ],
+    };
+
+    const pageTwo = {
+      __typename: 'Query',
+      persons: [
+        { id: 4, name: 'Kadi', __typename: 'Person' },
+        { id: 5, name: 'Dom', __typename: 'Person' },
+        { id: 6, name: 'Sofia', __typename: 'Person' },
+      ],
+    };
+
+    write(
+      store,
+      { query: Pagination, variables: { skip: 0, limit: 3 } },
+      pageOne
+    );
+    const pageOneResult = query(store, {
+      query: Pagination,
+      variables: { skip: 0, limit: 3 },
+    });
+    expect(pageOneResult.data).toEqual(pageOne);
+
+    write(
+      store,
+      { query: Pagination, variables: { skip: 3, limit: 3 } },
+      pageTwo
+    );
+
+    const pageTwoResult = query(store, {
+      query: Pagination,
+      variables: { skip: 3, limit: 3 },
+    });
+
+    expect((pageTwoResult.data as any).persons).toEqual([
+      ...pageOne.persons,
+      ...pageTwo.persons,
+    ]);
+
+    const pageThreeResult = query(store, {
+      query: Pagination,
+      variables: { skip: 6, limit: 3 },
+    });
+    expect(pageThreeResult.data).toEqual(null);
   });
 
-  const page = withId => ({
-    __typename: 'Query',
-    persons: [{ id: withId, name: withId, __typename: 'Person' }],
-  });
-
-  write(
-    store,
-    { query: Pagination, variables: { filter: 'one', skip: 0, limit: 1 } },
-    page('one')
-  );
-
-  write(
-    store,
-    { query: Pagination, variables: { filter: 'two', skip: 1, limit: 1 } },
-    page('two')
-  );
-
-  const resOne = query(store, {
-    query: Pagination,
-    variables: { filter: 'one', skip: 0, limit: 1 },
-  });
-  const resTwo = query(store, {
-    query: Pagination,
-    variables: { filter: 'two', skip: 1, limit: 1 },
-  });
-  const resThree = query(store, {
-    query: Pagination,
-    variables: { filter: 'three', skip: 2, limit: 1 },
+  it('works with backwards pagination', () => {
+    const Pagination = gql`
+      query ($skip: Number, $limit: Number) {
+        __typename
+        persons(skip: $skip, limit: $limit)
+          @_simplePagination(mergeMode: "before") {
+          __typename
+          id
+          name
+        }
+      }
+    `;
+
+    const store = new Store({
+      directives: {
+        simplePagination: directiveArguments =>
+          simplePagination({
+            mergeMode: directiveArguments!.mergeMode as MergeMode,
+          }),
+      },
+    });
+
+    const pageOne = {
+      __typename: 'Query',
+      persons: [
+        { id: 7, name: 'Jovi', __typename: 'Person' },
+        { id: 8, name: 'Phil', __typename: 'Person' },
+        { id: 9, name: 'Andy', __typename: 'Person' },
+      ],
+    };
+
+    const pageTwo = {
+      __typename: 'Query',
+      persons: [
+        { id: 4, name: 'Kadi', __typename: 'Person' },
+        { id: 5, name: 'Dom', __typename: 'Person' },
+        { id: 6, name: 'Sofia', __typename: 'Person' },
+      ],
+    };
+
+    write(
+      store,
+      { query: Pagination, variables: { skip: 0, limit: 3 } },
+      pageOne
+    );
+    const pageOneResult = query(store, {
+      query: Pagination,
+      variables: { skip: 0, limit: 3 },
+    });
+    expect(pageOneResult.data).toEqual(pageOne);
+
+    write(
+      store,
+      { query: Pagination, variables: { skip: 3, limit: 3 } },
+      pageTwo
+    );
+
+    const pageTwoResult = query(store, {
+      query: Pagination,
+      variables: { skip: 3, limit: 3 },
+    });
+    expect((pageTwoResult.data as any).persons).toEqual([
+      ...pageTwo.persons,
+      ...pageOne.persons,
+    ]);
+
+    const pageThreeResult = query(store, {
+      query: Pagination,
+      variables: { skip: 6, limit: 3 },
+    });
+    expect(pageThreeResult.data).toEqual(null);
   });
-
-  expect(resOne.data).toHaveProperty('persons[0].id', 'one');
-  expect(resOne.data).toHaveProperty('persons.length', 1);
-
-  expect(resTwo.data).toHaveProperty('persons[0].id', 'two');
-  expect(resTwo.data).toHaveProperty('persons.length', 1);
-
-  expect(resThree.data).toEqual(null);
 });
diff --git a/exchanges/persisted/src/persistedExchange.test.ts b/exchanges/persisted/src/persistedExchange.test.ts
index 8e013801..15632568 100644
--- a/exchanges/persisted/src/persistedExchange.test.ts
+++ b/exchanges/persisted/src/persistedExchange.test.ts
@@ -119,6 +119,27 @@ it('retries query persisted query resulted in unsupported', async () => {
   expect(operations[2].extensions).toEqual(undefined);
 });
 
+it('skips operation when generateHash returns a nullish value', async () => {
+  const { result, operations, exchangeArgs } = makeExchangeArgs();
+
+  result.mockImplementationOnce(operation => ({
+    ...queryResponse,
+    operation,
+    data: null,
+  }));
+
+  const res = await pipe(
+    fromValue(queryOperation),
+    persistedExchange({ generateHash: async () => null })(exchangeArgs),
+    take(1),
+    toPromise
+  );
+
+  expect(res.operation.context.persistAttempt).toBe(true);
+  expect(operations.length).toBe(1);
+  expect(operations[0]).not.toHaveProperty('extensions.persistedQuery');
+});
+
 it.each([true, 'force', 'within-url-limit'] as const)(
   'sets `context.preferGetMethod` to %s when `options.preferGetForPersistedQueries` is %s',
   async preferGetMethodValue => {