Repository: FormidableLabs/urql
Pull request #1631 (2021-05-11 14:17:47)

(graphcache) - Fix edge-case of erroring introspection on schema-awareness
Resolve https://github.com/dotansimha/graphql-code-generator/issues/5981

- Add missing test for formatted introspection query on introspected schema-aware store
- Add cases for built-in typenames to `ast/schemaPredicates`

changeset-bot[bot]: ###  🦋  Changeset detected

Latest commit: 89984c57a88993da037aceb27f871200a62a189f

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name                      | Type  |
| ------------------------- | ----- |
| @urql/exchange-graphcache | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/FormidableLabs/urql/new/fix/introspection-query-error?filename=.changeset/orange-ladybugs-tan.md&value=---%0A%22%40urql%2Fexchange-graphcache%22%3A%20patch%0A---%0A%0A(graphcache)%20-%20Fix%20edge-case%20of%20erroring%20introspection%20on%20schema-awareness%0A)



Patch:
diff --git a/.changeset/ten-pets-remain.md b/.changeset/ten-pets-remain.md
new file mode 100644
index 00000000..32d86c1a
--- /dev/null
+++ b/.changeset/ten-pets-remain.md
@@ -0,0 +1,5 @@
+---
+'@urql/exchange-graphcache': patch
+---
+
+Fix an edge-case for which an introspection query during runtime could fail when schema-awareness was enabled in Graphcache, since built-in types weren't recognised as existent.
diff --git a/exchanges/graphcache/src/ast/schemaPredicates.ts b/exchanges/graphcache/src/ast/schemaPredicates.ts
index b7c27c1a..a471efc9 100644
--- a/exchanges/graphcache/src/ast/schemaPredicates.ts
+++ b/exchanges/graphcache/src/ast/schemaPredicates.ts
@@ -11,14 +11,13 @@ import {
   OptimisticMutationConfig,
 } from '../types';
 
-const BUILTIN_FIELD_RE = /^__/;
+const BUILTIN_NAME_RE = /^__/;
 
 export const isFieldNullable = (
   schema: SchemaIntrospector,
   typename: string,
   fieldName: string
 ): boolean => {
-  if (BUILTIN_FIELD_RE.test(fieldName)) return true;
   const field = getField(schema, typename, fieldName);
   return !!field && field.type.kind !== 'NON_NULL';
 };
@@ -39,10 +38,10 @@ export const isFieldAvailableOnType = (
   schema: SchemaIntrospector,
   typename: string,
   fieldName: string
-): boolean => {
-  if (BUILTIN_FIELD_RE.test(fieldName)) return true;
-  return !!getField(schema, typename, fieldName);
-};
+): boolean =>
+  BUILTIN_NAME_RE.test(fieldName) ||
+  BUILTIN_NAME_RE.test(typename) ||
+  !!getField(schema, typename, fieldName);
 
 export const isInterfaceOfType = (
   schema: SchemaIntrospector,
@@ -51,12 +50,15 @@ export const isInterfaceOfType = (
 ): boolean => {
   if (!typename) return false;
   const typeCondition = getTypeCondition(node);
-  if (!typeCondition || typename === typeCondition) return true;
-  if (
+  if (!typeCondition || typename === typeCondition) {
+    return true;
+  } else if (
     schema.types![typeCondition] &&
     schema.types![typeCondition].kind === 'OBJECT'
-  )
+  ) {
     return typeCondition === typename;
+  }
+
   expectAbstractType(schema, typeCondition!);
   expectObjectType(schema, typename!);
   return schema.isSubType(typeCondition, typename);
@@ -67,6 +69,8 @@ const getField = (
   typename: string,
   fieldName: string
 ) => {
+  if (BUILTIN_NAME_RE.test(typename) || BUILTIN_NAME_RE.test(fieldName)) return;
+
   expectObjectType(schema, typename);
   const object = schema.types![typename] as SchemaObject;
   const field = object.fields[fieldName];


Test patch:
diff --git a/exchanges/graphcache/src/store/store.test.ts b/exchanges/graphcache/src/store/store.test.ts
index 4be3ca6b..31faa9ab 100644
--- a/exchanges/graphcache/src/store/store.test.ts
+++ b/exchanges/graphcache/src/store/store.test.ts
@@ -1,9 +1,16 @@
 /* eslint-disable @typescript-eslint/no-var-requires */
 
 import { minifyIntrospectionQuery } from '@urql/introspection';
-import { gql, maskTypename } from '@urql/core';
+import { formatDocument, gql, maskTypename } from '@urql/core';
 import { mocked } from 'ts-jest/utils';
 
+import {
+  executeSync,
+  getIntrospectionQuery,
+  buildClientSchema,
+  parse,
+} from 'graphql';
+
 import { Data, StorageAdapter } from '../types';
 import { makeContext, updateContext } from '../operations/shared';
 import { query } from '../operations/query';
@@ -11,7 +18,6 @@ import { write, writeOptimistic } from '../operations/write';
 import * as InMemoryData from './data';
 import { Store } from './store';
 import { noop } from '../test-utils/utils';
-import { getIntrospectionQuery, parse } from 'graphql';
 
 const Appointment = gql`
   query appointment($id: String) {
@@ -926,16 +932,6 @@ describe('Store with storage', () => {
     expect(fakeUpdater).toBeCalledTimes(1);
   });
 
-  it('should not warn for an introspection result root', function () {
-    // NOTE: Do not wrap this require in `minifyIntrospectionQuery`!
-    // eslint-disable-next-line
-    const schema = require('../test-utils/simple_schema.json');
-    const store = new Store({ schema });
-
-    query(store, { query: parse(getIntrospectionQuery()) }, schema);
-    expect(console.warn).toBeCalledTimes(0);
-  });
-
   it('should warn when __typename is missing when store.writeFragment is called', () => {
     InMemoryData.initDataState('write', store.data, null);
 
@@ -968,6 +964,52 @@ describe('Store with storage', () => {
   });
 });
 
+describe('Store introspection', () => {
+  it('should not warn for an introspection result root (of an unminified schema)', function () {
+    // NOTE: Do not wrap this require in `minifyIntrospectionQuery`!
+    // eslint-disable-next-line
+    const schema = require('../test-utils/simple_schema.json');
+    const store = new Store({ schema });
+
+    const introspectionQuery = formatDocument(parse(getIntrospectionQuery()));
+
+    query(store, { query: introspectionQuery }, schema);
+    expect(console.warn).toBeCalledTimes(0);
+  });
+
+  it('should not warn for an introspection result root (of a minified schema)', function () {
+    // NOTE: Do not wrap this require in `minifyIntrospectionQuery`!
+    // eslint-disable-next-line
+    const schema = require('../test-utils/simple_schema.json');
+    const store = new Store({ schema: minifyIntrospectionQuery(schema) });
+
+    const introspectionQuery = formatDocument(parse(getIntrospectionQuery()));
+
+    query(store, { query: introspectionQuery }, schema);
+    expect(console.warn).toBeCalledTimes(0);
+  });
+
+  it('should not warn for an introspection result with typenames', function () {
+    const schema = buildClientSchema(
+      require('../test-utils/simple_schema.json')
+    );
+    const introspectionQuery = formatDocument(parse(getIntrospectionQuery()));
+
+    const introspectionResult = executeSync({
+      document: introspectionQuery,
+      schema,
+    }).data as any;
+
+    const store = new Store({
+      schema: minifyIntrospectionQuery(introspectionResult),
+    });
+
+    write(store, { query: introspectionQuery }, introspectionResult);
+    query(store, { query: introspectionQuery });
+    expect(console.warn).toBeCalledTimes(0);
+  });
+});
+
 it('should link up entities', () => {
   const store = new Store();
   const todo = gql`