Repository: FormidableLabs/urql
Pull request #519 (2020-02-03 09:54:13)

implement conversion for useHTTPGetForQueries
The only uncertainty (no documentation anywhere) is how to use fragments for this.


Patch:
diff --git a/src/client.ts b/src/client.ts
index 6aec9aab..65cdc353 100755
--- a/src/client.ts
+++ b/src/client.ts
@@ -66,6 +66,8 @@ export interface ClientOptions {
   suspense?: boolean;
   /** The default request policy for requests. */
   requestPolicy?: RequestPolicy;
+  /** Use HTTP GET for queries. */
+  preferGetMethod?: boolean;
 }
 
 interface ActiveOperations {
@@ -82,6 +84,7 @@ export class Client {
   fetchOptions?: RequestInit | (() => RequestInit);
   exchange: Exchange;
   suspense: boolean;
+  preferGetMethod: boolean;
   requestPolicy: RequestPolicy;
 
   // These are internals to be used to keep track of operations
@@ -94,11 +97,13 @@ export class Client {
     if (process.env.NODE_ENV !== 'production' && !opts.url) {
       throw new Error('You are creating an urql-client without a url.');
     }
+
     this.url = opts.url;
     this.fetchOptions = opts.fetchOptions;
     this.fetch = opts.fetch;
     this.suspense = !!opts.suspense;
     this.requestPolicy = opts.requestPolicy || 'cache-first';
+    this.preferGetMethod = !!opts.preferGetMethod;
 
     // This subject forms the input of operations; executeOperation may be
     // called to dispatch a new operation on the subject
@@ -152,6 +157,7 @@ export class Client {
     url: this.url,
     fetchOptions: this.fetchOptions,
     fetch: this.fetch,
+    preferGetMethod: this.preferGetMethod,
     ...opts,
     requestPolicy: (opts || {}).requestPolicy || this.requestPolicy,
   });
diff --git a/src/exchanges/fetch.ts b/src/exchanges/fetch.ts
index d13b4fee..aca8dd8c 100755
--- a/src/exchanges/fetch.ts
+++ b/src/exchanges/fetch.ts
@@ -29,7 +29,14 @@ export const fetchExchange: Exchange = ({ forward }) => {
           filter(op => op.operationName === 'teardown' && op.key === key)
         );
 
-        return pipe(createFetchSource(operation), takeUntil(teardown$));
+        return pipe(
+          createFetchSource(
+            operation,
+            operation.operationName === 'query' &&
+              !!operation.context.preferGetMethod
+          ),
+          takeUntil(teardown$)
+        );
       })
     );
 
@@ -53,7 +60,7 @@ const getOperationName = (query: DocumentNode): string | null => {
   return node !== undefined && node.name ? node.name.value : null;
 };
 
-const createFetchSource = (operation: Operation) => {
+const createFetchSource = (operation: Operation, shouldUseGet: boolean) => {
   if (
     process.env.NODE_ENV !== 'production' &&
     operation.operationName === 'subscription'
@@ -88,9 +95,9 @@ const createFetchSource = (operation: Operation) => {
     }
 
     const fetchOptions = {
-      body: JSON.stringify(body),
-      method: 'POST',
       ...extraOptions,
+      body: shouldUseGet ? undefined : JSON.stringify(body),
+      method: shouldUseGet ? 'GET' : 'POST',
       headers: {
         'content-type': 'application/json',
         ...extraOptions.headers,
@@ -99,6 +106,10 @@ const createFetchSource = (operation: Operation) => {
         abortController !== undefined ? abortController.signal : undefined,
     };
 
+    if (shouldUseGet) {
+      operation.context.url = convertToGet(operation.context.url, body);
+    }
+
     let ended = false;
 
     Promise.resolve()
@@ -146,3 +157,15 @@ const executeFetch = (
       }
     });
 };
+
+export const convertToGet = (uri: string, body: Body): string => {
+  const queryParams: string[] = [`query=${encodeURIComponent(body.query)}`];
+
+  if (body.variables) {
+    queryParams.push(
+      `variables=${encodeURIComponent(JSON.stringify(body.variables))}`
+    );
+  }
+
+  return uri + '?' + queryParams.join('&');
+};
diff --git a/src/types.ts b/src/types.ts
index 9c046cea..42abefe0 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -50,6 +50,7 @@ export interface OperationContext {
   pollInterval?: number;
   meta?: OperationDebugMeta;
   suspense?: boolean;
+  preferGetMethod?: boolean;
 }
 
 /** A [query]{@link Query} or [mutation]{@link Mutation} with additional metadata for use during transmission. */


Test patch:
diff --git a/src/__snapshots__/client.test.ts.snap b/src/__snapshots__/client.test.ts.snap
index ec12ddf3..c102db58 100644
--- a/src/__snapshots__/client.test.ts.snap
+++ b/src/__snapshots__/client.test.ts.snap
@@ -13,6 +13,7 @@ Client {
   "fetch": undefined,
   "fetchOptions": undefined,
   "operations$": [Function],
+  "preferGetMethod": false,
   "reexecuteOperation": [Function],
   "requestPolicy": "cache-first",
   "results$": [Function],
diff --git a/src/__snapshots__/context.test.ts.snap b/src/__snapshots__/context.test.ts.snap
index f223c558..ee0beffb 100644
--- a/src/__snapshots__/context.test.ts.snap
+++ b/src/__snapshots__/context.test.ts.snap
@@ -26,6 +26,7 @@ Object {
       "fetch": undefined,
       "fetchOptions": undefined,
       "operations$": [Function],
+      "preferGetMethod": false,
       "reexecuteOperation": [Function],
       "requestPolicy": "cache-first",
       "results$": [Function],
@@ -44,6 +45,7 @@ Object {
       "fetch": undefined,
       "fetchOptions": undefined,
       "operations$": [Function],
+      "preferGetMethod": false,
       "reexecuteOperation": [Function],
       "requestPolicy": "cache-first",
       "results$": [Function],
@@ -81,6 +83,7 @@ Object {
       "fetch": undefined,
       "fetchOptions": undefined,
       "operations$": [Function],
+      "preferGetMethod": false,
       "reexecuteOperation": [Function],
       "requestPolicy": "cache-first",
       "results$": [Function],
@@ -99,6 +102,7 @@ Object {
       "fetch": undefined,
       "fetchOptions": undefined,
       "operations$": [Function],
+      "preferGetMethod": false,
       "reexecuteOperation": [Function],
       "requestPolicy": "cache-first",
       "results$": [Function],
diff --git a/src/client.test.ts b/src/client.test.ts
index e6cb1233..573303fb 100755
--- a/src/client.test.ts
+++ b/src/client.test.ts
@@ -91,6 +91,7 @@ describe('promisified methods', () => {
       fetchOptions: undefined,
       fetch: undefined,
       suspense: false,
+      preferGetMethod: false,
     });
     expect(queryResult).toHaveProperty('then');
   });
@@ -119,6 +120,7 @@ describe('promisified methods', () => {
       requestPolicy: 'cache-and-network',
       fetchOptions: undefined,
       fetch: undefined,
+      preferGetMethod: false,
     });
     expect(mutationResult).toHaveProperty('then');
   });
diff --git a/src/exchanges/fetch.test.ts b/src/exchanges/fetch.test.ts
index 4f3a0d88..53dc4c46 100755
--- a/src/exchanges/fetch.test.ts
+++ b/src/exchanges/fetch.test.ts
@@ -2,7 +2,8 @@ import { empty, fromValue, pipe, Source, subscribe, toPromise } from 'wonka';
 import { Client } from '../client';
 import { queryOperation } from '../test-utils';
 import { OperationResult, OperationType } from '../types';
-import { fetchExchange } from './fetch';
+import { fetchExchange, convertToGet } from './fetch';
+import gql from 'graphql-tag';
 
 const fetch = (global as any).fetch as jest.Mock;
 const abort = jest.fn();
@@ -151,3 +152,38 @@ describe('on teardown', () => {
     expect(abort).toHaveBeenCalledTimes(0);
   });
 });
+
+describe('convert for GET', () => {
+  it('should do a basic conversion', () => {
+    const query = `query ($id: ID!) { node(id: $id) { id } }`;
+    const variables = { id: 2 };
+    expect(convertToGet('http://localhost:3000', { query, variables })).toBe(
+      `http://localhost:3000?query=${encodeURIComponent(
+        query
+      )}&variables=${encodeURIComponent(JSON.stringify(variables))}`
+    );
+  });
+
+  it('should do a basic conversion with fragments', () => {
+    const nodeFragment = gql`
+      fragment nodeFragment on Node {
+        id
+      }
+    `;
+    const variables = { id: 2 };
+    const query = gql`
+      query($id: ID!) {
+        node(id: $id) {
+          ...nodeFragment
+        }
+      }
+      ${nodeFragment}
+    `;
+
+    expect(convertToGet('http://localhost:3000', { query, variables })).toBe(
+      `http://localhost:3000?query=${encodeURIComponent(
+        query
+      )}&variables=${encodeURIComponent(JSON.stringify(variables))}`
+    );
+  });
+});