Repository: FormidableLabs/urql (ts)

Issue:
Provide custom fetch implementation
We have our custom fetch implementation (that mirrors the API for the default implementation) that does things like:

- Authentication.
- Handling custom response headers.
- Configuring and appending the API root.
- Handling the server-specific nuances.

This is quite a common pattern I believe. With `apollo-link-http` you can pass a fetch function down and it uses that.

Is it possible to do something similar? This is more generic and flexible than the current limitation of only configuring headers -- as you can't add any global/cross-cutting custom response handling using the options.

I could make my own exchange, but that feels like a large overhead when I just want the default fetch exchange but using my fetch function.

This might help mocking fetch in test env's too.

Pull request:
Add fetch option to Client args
Fix #352

Allow a custom fetch implementation to be passed to the client, which will be used in the fetchExchange.

We previously removed this option, but it may make sense to reintroduce it for ease of use.

Patch:
diff --git a/docs/api.md b/docs/api.md
index ca102454..a99ec228 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -177,6 +177,7 @@ It accepts a bunch of inputs when it's created
 | ------------ | ---------------------------------- | --------------------------------------------------------------------------------------------------------------- |
 | url          | `string`                           | The GraphQL API URL as used by `fetchExchange`                                                                  |
 | fetchOptions | `RequestInit \| () => RequestInit` | Additional `fetchOptions` that `fetch` in `fetchExchange` should use to make a request                          |
+| fetch        | `typeof fetch`                     | An alternative implementation of `fetch` that will be used by the `fetchExchange` instead of `window.fetch`     |
 | suspense     | `?boolean`                         | Activates the experimental React suspense mode, which can be used during server-side rendering to prefetch data |
 | exchanges    | `Exchange[]`                       | An array of `Exchange`s that the client should use instead of the list of `defaultExchanges`                    |
 
diff --git a/src/client.ts b/src/client.ts
index 75827692..e35263a7 100755
--- a/src/client.ts
+++ b/src/client.ts
@@ -33,6 +33,8 @@ export interface ClientOptions {
   url: string;
   /** Any additional options to pass to fetch. */
   fetchOptions?: RequestInit | (() => RequestInit);
+  /** An alternative fetch implementation. */
+  fetch?: typeof fetch;
   /** An ordered array of Exchanges. */
   exchanges?: Exchange[];
   /** Activates support for Suspense. */
@@ -49,6 +51,7 @@ export const createClient = (opts: ClientOptions) => new Client(opts);
 export class Client {
   // These are variables derived from ClientOptions
   url: string;
+  fetch?: typeof fetch;
   fetchOptions?: RequestInit | (() => RequestInit);
   exchange: Exchange;
   suspense: boolean;
@@ -62,6 +65,7 @@ export class Client {
   constructor(opts: ClientOptions) {
     this.url = opts.url;
     this.fetchOptions = opts.fetchOptions;
+    this.fetch = opts.fetch;
     this.suspense = !!opts.suspense;
 
     // This subject forms the input of operations; executeOperation may be
diff --git a/src/exchanges/fetch.ts b/src/exchanges/fetch.ts
index f8ff9c1f..cc381855 100755
--- a/src/exchanges/fetch.ts
+++ b/src/exchanges/fetch.ts
@@ -76,6 +76,7 @@ const createFetchSource = (operation: Operation) => {
     };
 
     const startTime = Date.now();
+
     executeFetch(operation, fetchOptions).then(result => {
       if (result !== undefined) {
         next({
@@ -99,13 +100,13 @@ const createFetchSource = (operation: Operation) => {
 
 const executeFetch = (operation: Operation, opts: RequestInit) => {
   let response: Response | undefined;
-  const { url } = operation.context;
+  const { url, fetch: fetcher } = operation.context;
 
-  return fetch(url, opts)
+  return (fetcher || fetch)(url, opts)
     .then(res => {
       response = res;
-      checkStatus(opts.redirect, response);
-      return response.json();
+      checkStatus(opts.redirect, res);
+      return res.json();
     })
     .then(result => ({
       operation,


Test patch:
diff --git a/src/__snapshots__/client.test.ts.snap b/src/__snapshots__/client.test.ts.snap
index cff7bd7e..4885bab4 100644
--- a/src/__snapshots__/client.test.ts.snap
+++ b/src/__snapshots__/client.test.ts.snap
@@ -10,6 +10,7 @@ Client {
   "executeMutation": [Function],
   "executeQuery": [Function],
   "executeSubscription": [Function],
+  "fetch": undefined,
   "fetchOptions": undefined,
   "operations$": [Function],
   "reexecuteOperation": [Function],
diff --git a/src/__snapshots__/context.test.ts.snap b/src/__snapshots__/context.test.ts.snap
index 64a38f79..3a40f8a5 100644
--- a/src/__snapshots__/context.test.ts.snap
+++ b/src/__snapshots__/context.test.ts.snap
@@ -23,6 +23,7 @@ Object {
       "executeMutation": [Function],
       "executeQuery": [Function],
       "executeSubscription": [Function],
+      "fetch": undefined,
       "fetchOptions": undefined,
       "operations$": [Function],
       "reexecuteOperation": [Function],
@@ -39,6 +40,7 @@ Object {
       "executeMutation": [Function],
       "executeQuery": [Function],
       "executeSubscription": [Function],
+      "fetch": undefined,
       "fetchOptions": undefined,
       "operations$": [Function],
       "reexecuteOperation": [Function],
@@ -74,6 +76,7 @@ Object {
       "executeMutation": [Function],
       "executeQuery": [Function],
       "executeSubscription": [Function],
+      "fetch": undefined,
       "fetchOptions": undefined,
       "operations$": [Function],
       "reexecuteOperation": [Function],
@@ -90,6 +93,7 @@ Object {
       "executeMutation": [Function],
       "executeQuery": [Function],
       "executeSubscription": [Function],
+      "fetch": undefined,
       "fetchOptions": undefined,
       "operations$": [Function],
       "reexecuteOperation": [Function],