Repository: FormidableLabs/urql (ts)

Issue:
operationName is never set by fetchExchange
**Describe the bug**
When executing a query via urql, our graphql server (apollo) is not receiving an `operationName`. When using the apollo playground, even individual queries have an `operationName` computed.

I understand that `operationName` is only required for multiple operations in a query (as per https://graphql.org/learn/serving-over-http/#post-request) but from my experience, even multiple queries inside a transaction isn't setting it.

**Steps to reproduce**
Steps to reproduce the behavior:
1. Create an apollo graphql server
2. As part of it, create an extension (such as this one) and consume it in the `extensions` of apollo config
``` js
import { GraphQLExtension } from 'graphql-extensions';

export default class CustomExtension extends GraphQLExtension {
  requestDidStart({ operationName }) {
    console.log(`operation name is "${operationName}"`);
  }
}
```
3. fire a call to the apollo server and you observe the server console

**Expected behavior**
The `operationName` should be picked intelligently such as the name of the query being executed (as apollo client does)

**Actual behavior**
The `operationName` parameter is `undefined`

**Additional context**
n/a

Pull request description:
Add operationName field to fetch request
Resolve #400

Patch:
diff --git a/src/exchanges/fetch.ts b/src/exchanges/fetch.ts
index 6e31800d..a77cef50 100755
--- a/src/exchanges/fetch.ts
+++ b/src/exchanges/fetch.ts
@@ -1,9 +1,15 @@
 /* eslint-disable @typescript-eslint/no-use-before-define */
-import { print } from 'graphql';
+import { Kind, DocumentNode, OperationDefinitionNode, print } from 'graphql';
 import { filter, make, merge, mergeMap, pipe, share, takeUntil } from 'wonka';
 import { Exchange, Operation, OperationResult } from '../types';
 import { makeResult, makeErrorResult } from '../utils';
 
+interface Body {
+  query: string;
+  variables: void | object;
+  operationName?: string;
+}
+
 /** A default exchange for fetching GraphQL requests. */
 export const fetchExchange: Exchange = ({ forward }) => {
   const isOperationFetchable = (operation: Operation) => {
@@ -40,6 +46,16 @@ export const fetchExchange: Exchange = ({ forward }) => {
   };
 };
 
+const getOperationName = (query: DocumentNode): string | null => {
+  const node = query.definitions.find(
+    (node: any): node is OperationDefinitionNode => {
+      return node.kind === Kind.OPERATION_DEFINITION && node.name;
+    }
+  );
+
+  return node !== undefined && node.name ? node.name.value : null;
+};
+
 const createFetchSource = (operation: Operation) => {
   if (
     process.env.NODE_ENV !== 'production' &&
@@ -63,11 +79,19 @@ const createFetchSource = (operation: Operation) => {
         ? context.fetchOptions()
         : context.fetchOptions || {};
 
+    const operationName = getOperationName(operation.query);
+
+    const body: Body = {
+      query: print(operation.query),
+      variables: operation.variables,
+    };
+
+    if (operationName !== null) {
+      body.operationName = operationName;
+    }
+
     const fetchOptions = {
-      body: JSON.stringify({
-        query: print(operation.query),
-        variables: operation.variables,
-      }),
+      body: JSON.stringify(body),
       method: 'POST',
       ...extraOptions,
       headers: {


Test patch:
diff --git a/src/exchanges/__snapshots__/fetch.test.ts.snap b/src/exchanges/__snapshots__/fetch.test.ts.snap
index dbd02bd0..e71a5eb2 100644
--- a/src/exchanges/__snapshots__/fetch.test.ts.snap
+++ b/src/exchanges/__snapshots__/fetch.test.ts.snap
@@ -408,3 +408,5 @@ Object {
   },
 }
 `;
+
+exports[`on success returns response data 2`] = `"{\\"query\\":\\"query getUser($name: String) {\\\\n  user(name: $name) {\\\\n    id\\\\n    firstName\\\\n    lastName\\\\n  }\\\\n}\\\\n\\",\\"variables\\":{\\"name\\":\\"Clara\\"},\\"operationName\\":\\"getUser\\"}"`;
diff --git a/src/exchanges/fetch.test.ts b/src/exchanges/fetch.test.ts
index 162a34d9..cd05aa84 100755
--- a/src/exchanges/fetch.test.ts
+++ b/src/exchanges/fetch.test.ts
@@ -70,6 +70,7 @@ describe('on success', () => {
 
     expect(data).toMatchSnapshot();
     expect(fetchOptions).toHaveBeenCalled();
+    expect(fetch.mock.calls[0][1].body).toMatchSnapshot();
   });
 });