Repository: apollographql/apollo-client

Problem statement:
Support custom directives in InMemory Cache
**Intended outcome:**
I would like to add custom directive support to "apollo-cache-inmemory". More precisely fix it to prevent corrupted cache.

If you are not familiar with custom directives you can checkout my new module: [graphql-directive](https://github.com/smooth-code/graphql-directive).

**Actual outcome:**
"apollo-cache-inmemory" relies on [`getStoreKeyName` function](https://github.com/apollographql/apollo-client/blob/master/packages/apollo-utilities/src/storeUtils.ts#L167) either for writing or for reading cache. By reading the code of this method you can see that a field `firstName @upperCase` and `firstName` will use the same cache key: "firstName". Even if the value is not the same, it corrupts cache.

**How to reproduce the issue:**
```js
const gql = require('graphql-tag')
const { InMemoryCache } = require('apollo-cache-inmemory')

const cache = new InMemoryCache()

const query = gql`
  query Foo {
    book(id: 10) {
      __typename
      name @uppercase
    }
  }
`

cache.writeQuery({
  query,
  data: {
    book: {
      __typename: 'BOOK',
      name: 'harry',
    },
  },
})

console.log(cache.extract())
console.log(cache.readQuery({ query }))
```


**Version**
- apollo-cache-inmemory@1.1.4
- apollo-utilities@1.0.3



Hints:

Patch:
diff --git a/packages/apollo-utilities/src/storeUtils.ts b/packages/apollo-utilities/src/storeUtils.ts
--- a/packages/apollo-utilities/src/storeUtils.ts
+++ b/packages/apollo-utilities/src/storeUtils.ts
@@ -164,6 +164,8 @@ export type Directives = {
   };
 };
 
+const KNOWN_DIRECTIVES: string[] = ['connection', 'include', 'skip'];
+
 export function getStoreKeyName(
   fieldName: string,
   args?: Object,
@@ -197,13 +199,25 @@ export function getStoreKeyName(
     }
   }
 
+  let completeFieldName: string = fieldName;
+
   if (args) {
     const stringifiedArgs: string = JSON.stringify(args);
+    completeFieldName += `(${stringifiedArgs})`;
+  }
 
-    return `${fieldName}(${stringifiedArgs})`;
+  if (directives) {
+    Object.keys(directives).forEach(key => {
+      if (KNOWN_DIRECTIVES.indexOf(key) !== -1) return;
+      if (directives[key] && Object.keys(directives[key]).length) {
+        completeFieldName += `@${key}(${JSON.stringify(directives[key])})`;
+      } else {
+        completeFieldName += `@${key}`;
+      }
+    });
   }
 
-  return fieldName;
+  return completeFieldName;
 }
 
 export function argumentsObjectFromField(


Test patch:
diff --git a/packages/apollo-cache-inmemory/src/__tests__/readFromStore.ts b/packages/apollo-cache-inmemory/src/__tests__/readFromStore.ts
--- a/packages/apollo-cache-inmemory/src/__tests__/readFromStore.ts
+++ b/packages/apollo-cache-inmemory/src/__tests__/readFromStore.ts
@@ -165,6 +165,38 @@ describe('reading from the store', () => {
     });
   });
 
+  it('runs a basic query with custom directives', () => {
+    const query = gql`
+      query {
+        id
+        firstName @include(if: true)
+        lastName @upperCase
+        birthDate @dateFormat(format: "DD-MM-YYYY")
+      }
+    `;
+
+    const store = defaultNormalizedCacheFactory({
+      ROOT_QUERY: {
+        id: 'abcd',
+        firstName: 'James',
+        'lastName@upperCase': 'BOND',
+        'birthDate@dateFormat({"format":"DD-MM-YYYY"})': '20-05-1940',
+      },
+    });
+
+    const result = readQueryFromStore({
+      store,
+      query,
+    });
+
+    expect(result).toEqual({
+      id: 'abcd',
+      firstName: 'James',
+      lastName: 'BOND',
+      birthDate: '20-05-1940',
+    });
+  });
+
   it('runs a basic query with default values for arguments', () => {
     const query = gql`
       query someBigQuery(
diff --git a/packages/apollo-cache-inmemory/src/__tests__/writeToStore.ts b/packages/apollo-cache-inmemory/src/__tests__/writeToStore.ts
--- a/packages/apollo-cache-inmemory/src/__tests__/writeToStore.ts
+++ b/packages/apollo-cache-inmemory/src/__tests__/writeToStore.ts
@@ -221,6 +221,38 @@ describe('writing to the store', () => {
     });
   });
 
+  it('properly normalizes a query with custom directives', () => {
+    const query = gql`
+      query {
+        id
+        firstName @include(if: true)
+        lastName @upperCase
+        birthDate @dateFormat(format: "DD-MM-YYYY")
+      }
+    `;
+
+    const result: any = {
+      id: 'abcd',
+      firstName: 'James',
+      lastName: 'BOND',
+      birthDate: '20-05-1940',
+    };
+
+    const normalized = writeQueryToStore({
+      result,
+      query,
+    });
+
+    expect(normalized.toObject()).toEqual({
+      ROOT_QUERY: {
+        id: 'abcd',
+        firstName: 'James',
+        'lastName@upperCase': 'BOND',
+        'birthDate@dateFormat({"format":"DD-MM-YYYY"})': '20-05-1940',
+      },
+    });
+  });
+
   it('properly normalizes a nested object with an ID', () => {
     const query = gql`
       {