From fb7f1e983cd78d49a56d01f19d58562d65e53832 Mon Sep 17 00:00:00 2001 From: parkerziegler Date: Sun, 27 Dec 2020 14:41:26 -0700 Subject: [PATCH 1/3] feat(exchanges): add binding for @urql/retry-exchange --- __tests__/Client_test.res | 38 +++++++++++ .../__snapshots__/Client_test.bs.js.snap | 44 ++++++++++++ docs/exchanges.md | 68 ++++++++++++++++++- package.json | 1 + src/Client.res | 26 +++++++ src/Client.resi | 20 ++++++ yarn.lock | 10 ++- 7 files changed, 203 insertions(+), 4 deletions(-) diff --git a/__tests__/Client_test.res b/__tests__/Client_test.res index 8aae130..91903f2 100644 --- a/__tests__/Client_test.res +++ b/__tests__/Client_test.res @@ -224,5 +224,43 @@ describe("Client", () => { open Expect expect(client) |> toMatchSnapshot }) + + describe("retryExchange", () => { + it("should apply the default retryExchange options from urql if none are applied", () => { + let retryExchangeOptions = Client.Exchanges.makeRetryExchangeOptions() + + open Expect + expect(retryExchangeOptions) |> toMatchSnapshot + }) + + it("should apply any specified options to the retryExchange", () => { + let retryExchangeOptions = Client.Exchanges.makeRetryExchangeOptions( + ~initialDelayMs=200, + ~randomDelay=false, + (), + ) + + open Expect + expect(retryExchangeOptions) |> toMatchSnapshot + }) + + it("should allow passing the retryExchange", () => { + let retryExchangeOptions = Client.Exchanges.makeRetryExchangeOptions() + + let client = Client.make( + ~url="https://localhost:3000", + ~exchanges=[ + Client.Exchanges.dedupExchange, + Client.Exchanges.cacheExchange, + Client.Exchanges.retryExchange(retryExchangeOptions), + Client.Exchanges.fetchExchange, + ], + (), + ) + + open Expect + expect(client) |> toMatchSnapshot + }) + }) }) }) diff --git a/__tests__/__snapshots__/Client_test.bs.js.snap b/__tests__/__snapshots__/Client_test.bs.js.snap index 50b28ad..fb555b2 100644 --- a/__tests__/__snapshots__/Client_test.bs.js.snap +++ b/__tests__/__snapshots__/Client_test.bs.js.snap @@ -151,6 +151,50 @@ Client { } `; +exports[`Client Ecosystem exchanges retryExchange should allow passing the retryExchange 1`] = ` +Client { + "activeOperations": Object {}, + "createOperationContext": [Function], + "createRequestOperation": [Function], + "dispatchOperation": [Function], + "executeMutation": [Function], + "executeQuery": [Function], + "executeSubscription": [Function], + "fetch": undefined, + "fetchOptions": undefined, + "maskTypename": false, + "operations$": [Function], + "preferGetMethod": false, + "queue": Array [], + "reexecuteOperation": [Function], + "requestPolicy": "cache-first", + "results$": [Function], + "subscribeToDebugTarget": [Function], + "suspense": false, + "url": "https://localhost:3000", +} +`; + +exports[`Client Ecosystem exchanges retryExchange should apply any specified options to the retryExchange 1`] = ` +Object { + "initialDelayMs": 200, + "maxDelayMs": undefined, + "maxNumberAttempts": undefined, + "randomDelay": false, + "retryIf": undefined, +} +`; + +exports[`Client Ecosystem exchanges retryExchange should apply the default retryExchange options from urql if none are applied 1`] = ` +Object { + "initialDelayMs": undefined, + "maxDelayMs": undefined, + "maxNumberAttempts": undefined, + "randomDelay": undefined, + "retryIf": undefined, +} +`; + exports[`Client Ecosystem exchanges should support passing the multipartFetchExchange 1`] = ` Client { "activeOperations": Object {}, diff --git a/docs/exchanges.md b/docs/exchanges.md index 069a77d..264d10b 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -157,22 +157,84 @@ yarn add @urql/exchange-multipart-fetch Then, substitute the `fetchExchange` with the `multipartFetchExchange`: -```res +```rescript open ReasonUrql let client = Client.make( ~url="http://localhost:3000", - ~exchanges=[| + ~exchanges=[ Client.Exchanges.dedupExchange, Client.Exchanges.cacheExchange, Client.Exchanges.multipartFetchExchange - |], + ], () ) ``` Read more on the `multipartFetchExchange` [here](https://github.com/FormidableLabs/urql/tree/main/exchanges/multipart-fetch). +### `retryExchange` + +The `retryExchange` is useful for retrying particular operations. By default, adding this exchange with the base options will retry any operations that failed due to network errors. However, we can customize the exchange to catch more specific error cases as well. + +To use the `retryExchange`, add the package to your dependencies: + +```sh +yarn add @urql/exchange-retry +``` + +Then, add the exchange to your array of `exchanges`, specifying the options you want to configure: + +```rescript +open ReasonUrql + +let retryExchangeOptions = + Client.Exchanges.makeRetryExchangeOptions(~initialDelayMs=2000, ~randomDelay=false, ()) + +let client = Client.make( + ~url="http://localhost:3000", + ~exchanges=[ + Client.Exchanges.dedupExchange, + Client.Exchanges.cacheExchange, + Client.Exchanges.retryExchange(retryExchangeOptions), + Client.Exchanges.fetchExchange + ], + () +) +``` + +By default, `urql` will apply the following configuration for you: + +```typescript +{ + initialDelayMs: 1000, + maxDelayMs: 15000, + randomDelay: true, + maxNumberAttempts: 2, + retryIf: err => err && err.networkError, +} +``` + +If you want to use the defaults from `urql`, call `makeRetryExchangeOptions` with just a `unit` parameter. + +```rescript +open ReasonUrql + +let retryExchangeOptions = + Client.Exchanges.makeRetryExchangeOptions() + +let client = Client.make( + ~url="http://localhost:3000", + ~exchanges=[ + Client.Exchanges.dedupExchange, + Client.Exchanges.cacheExchange, + Client.Exchanges.retryExchange(retryExchangeOptions), + Client.Exchanges.fetchExchange + ], + () +) +``` + ## Custom Exchanges `reason-urql` also allows you to write your own exchanges to modify outgoing GraphQL requests and incoming responses. To read up on the basics of exchanges, check out the excellent [`urql` documentation](https://formidable.com/open-source/urql/docs/concepts/exchanges/). diff --git a/package.json b/package.json index a4e77e1..4c914e9 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@glennsl/bs-jest": "^0.6.0", "@reasonml-community/graphql-ppx": "^1.0.1", "@urql/exchange-multipart-fetch": "^0.1.11", + "@urql/exchange-retry": "^0.2.0", "all-contributors-cli": "^6.19.0", "babel-jest": "^26.6.3", "bs-platform": "^8.3.2", diff --git a/src/Client.res b/src/Client.res index 4e0753e..3b25090 100644 --- a/src/Client.res +++ b/src/Client.res @@ -74,6 +74,32 @@ module Exchanges = { @module("@urql/exchange-multipart-fetch") external multipartFetchExchange: t = "multipartFetchExchange" + type retryExchangeOptions = { + initialDelayMs: option, + maxDelayMs: option, + randomDelay: option, + maxNumberAttempts: option, + retryIf: option<(CombinedError.t, Types.operation) => bool>, + } + + let makeRetryExchangeOptions = ( + ~initialDelayMs=?, + ~maxDelayMs=?, + ~randomDelay=?, + ~maxNumberAttempts=?, + ~retryIf=?, + (), + ) => { + initialDelayMs: initialDelayMs, + maxDelayMs: maxDelayMs, + randomDelay: randomDelay, + maxNumberAttempts: maxNumberAttempts, + retryIf: retryIf, + } + + @module("@urql/exchange-retry") + external retryExchange: retryExchangeOptions => t = "retryExchange" + /* Specific types for the subscriptionExchange. */ type observerLike<'value> = { next: 'value => unit, diff --git a/src/Client.resi b/src/Client.resi index e024df0..b94fd98 100644 --- a/src/Client.resi +++ b/src/Client.resi @@ -56,6 +56,26 @@ module Exchanges: { @module("@urql/exchange-multipart-fetch") external multipartFetchExchange: t = "multipartFetchExchange" + type retryExchangeOptions = { + initialDelayMs: option, + maxDelayMs: option, + randomDelay: option, + maxNumberAttempts: option, + retryIf: option<(CombinedError.t, Types.operation) => bool>, + } + + let makeRetryExchangeOptions: ( + ~initialDelayMs: int=?, + ~maxDelayMs: int=?, + ~randomDelay: bool=?, + ~maxNumberAttempts: int=?, + ~retryIf: (CombinedError.t, Types.operation) => bool=?, + unit, + ) => retryExchangeOptions + + @module("@urql/exchange-retry") + external retryExchange: retryExchangeOptions => t = "retryExchange" + /* Specific types for the subscriptionExchange. */ type observerLike<'value> = { next: 'value => unit, diff --git a/yarn.lock b/yarn.lock index 28d0c21..4ae9d21 100644 --- a/yarn.lock +++ b/yarn.lock @@ -918,7 +918,7 @@ dependencies: "@types/yargs-parser" "*" -"@urql/core@>=1.14.1": +"@urql/core@>=1.14.1", "@urql/core@>=1.15.0": version "1.16.1" resolved "https://registry.yarnpkg.com/@urql/core/-/core-1.16.1.tgz#a41487dd4436cbdc92fce714c6c1fc04315761d3" integrity sha512-lcEMCS/rB+ug7jKSRDVZIsqNhZxRooTNa1kHfvSjJT2k4SXDyPmjNSfXBUJF2pDJmvv9EIKl9Tk0AF1CvG3Q/g== @@ -943,6 +943,14 @@ extract-files "^8.1.0" wonka "^4.0.14" +"@urql/exchange-retry@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@urql/exchange-retry/-/exchange-retry-0.2.0.tgz#e90a99bf8280ad2b8926bea7f2157a6f59dc8aec" + integrity sha512-eawDIkTSVudv+zMaOlm898UX9lkJnUry2PYqD7INeFWghkHmlIPm6wg5J/GBGAyFjqaOj1OWgAWYcu7sV4eljg== + dependencies: + "@urql/core" ">=1.15.0" + wonka "^4.0.14" + abab@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" From cd0759ad69f216c17d1356868cf649337b71e2b4 Mon Sep 17 00:00:00 2001 From: parkerziegler Date: Sun, 27 Dec 2020 14:58:59 -0700 Subject: [PATCH 2/3] docs: link to retryExchange documentation --- docs/exchanges.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/exchanges.md b/docs/exchanges.md index 264d10b..8292673 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -235,6 +235,8 @@ let client = Client.make( ) ``` +Read more on the `retryExchange` [here](https://formidable.com/open-source/urql/docs/advanced/retry-operations/). + ## Custom Exchanges `reason-urql` also allows you to write your own exchanges to modify outgoing GraphQL requests and incoming responses. To read up on the basics of exchanges, check out the excellent [`urql` documentation](https://formidable.com/open-source/urql/docs/concepts/exchanges/). From 738f08852faf70ebc42e828daa62ba5dff604027 Mon Sep 17 00:00:00 2001 From: parkerziegler Date: Mon, 28 Dec 2020 17:46:43 -0700 Subject: [PATCH 3/3] remove unnecessary Client snapshots --- __tests__/Client_test.res | 49 ++++--------- .../__snapshots__/Client_test.bs.js.snap | 68 ------------------- 2 files changed, 14 insertions(+), 103 deletions(-) diff --git a/__tests__/Client_test.res b/__tests__/Client_test.res index 91903f2..b70674c 100644 --- a/__tests__/Client_test.res +++ b/__tests__/Client_test.res @@ -210,27 +210,18 @@ describe("Client", () => { })) describe("Ecosystem exchanges", () => { - it("should support passing the multipartFetchExchange", () => { - let client = Client.make( - ~url="https://localhost:3000", - ~exchanges=[ - Client.Exchanges.dedupExchange, - Client.Exchanges.cacheExchange, - Client.Exchanges.multipartFetchExchange, - ], - (), - ) - - open Expect - expect(client) |> toMatchSnapshot - }) - describe("retryExchange", () => { it("should apply the default retryExchange options from urql if none are applied", () => { let retryExchangeOptions = Client.Exchanges.makeRetryExchangeOptions() open Expect - expect(retryExchangeOptions) |> toMatchSnapshot + expect(retryExchangeOptions) |> toEqual({ + Client.Exchanges.initialDelayMs: None, + maxDelayMs: None, + maxNumberAttempts: None, + randomDelay: None, + retryIf: None, + }) }) it("should apply any specified options to the retryExchange", () => { @@ -241,25 +232,13 @@ describe("Client", () => { ) open Expect - expect(retryExchangeOptions) |> toMatchSnapshot - }) - - it("should allow passing the retryExchange", () => { - let retryExchangeOptions = Client.Exchanges.makeRetryExchangeOptions() - - let client = Client.make( - ~url="https://localhost:3000", - ~exchanges=[ - Client.Exchanges.dedupExchange, - Client.Exchanges.cacheExchange, - Client.Exchanges.retryExchange(retryExchangeOptions), - Client.Exchanges.fetchExchange, - ], - (), - ) - - open Expect - expect(client) |> toMatchSnapshot + expect(retryExchangeOptions) |> toEqual({ + Client.Exchanges.initialDelayMs: Some(200), + maxDelayMs: None, + maxNumberAttempts: None, + randomDelay: Some(false), + retryIf: None, + }) }) }) }) diff --git a/__tests__/__snapshots__/Client_test.bs.js.snap b/__tests__/__snapshots__/Client_test.bs.js.snap index fb555b2..e928563 100644 --- a/__tests__/__snapshots__/Client_test.bs.js.snap +++ b/__tests__/__snapshots__/Client_test.bs.js.snap @@ -151,74 +151,6 @@ Client { } `; -exports[`Client Ecosystem exchanges retryExchange should allow passing the retryExchange 1`] = ` -Client { - "activeOperations": Object {}, - "createOperationContext": [Function], - "createRequestOperation": [Function], - "dispatchOperation": [Function], - "executeMutation": [Function], - "executeQuery": [Function], - "executeSubscription": [Function], - "fetch": undefined, - "fetchOptions": undefined, - "maskTypename": false, - "operations$": [Function], - "preferGetMethod": false, - "queue": Array [], - "reexecuteOperation": [Function], - "requestPolicy": "cache-first", - "results$": [Function], - "subscribeToDebugTarget": [Function], - "suspense": false, - "url": "https://localhost:3000", -} -`; - -exports[`Client Ecosystem exchanges retryExchange should apply any specified options to the retryExchange 1`] = ` -Object { - "initialDelayMs": 200, - "maxDelayMs": undefined, - "maxNumberAttempts": undefined, - "randomDelay": false, - "retryIf": undefined, -} -`; - -exports[`Client Ecosystem exchanges retryExchange should apply the default retryExchange options from urql if none are applied 1`] = ` -Object { - "initialDelayMs": undefined, - "maxDelayMs": undefined, - "maxNumberAttempts": undefined, - "randomDelay": undefined, - "retryIf": undefined, -} -`; - -exports[`Client Ecosystem exchanges should support passing the multipartFetchExchange 1`] = ` -Client { - "activeOperations": Object {}, - "createOperationContext": [Function], - "createRequestOperation": [Function], - "dispatchOperation": [Function], - "executeMutation": [Function], - "executeQuery": [Function], - "executeSubscription": [Function], - "fetch": undefined, - "fetchOptions": undefined, - "maskTypename": false, - "operations$": [Function], - "preferGetMethod": false, - "queue": Array [], - "reexecuteOperation": [Function], - "requestPolicy": "cache-first", - "results$": [Function], - "subscribeToDebugTarget": [Function], - "suspense": false, - "url": "https://localhost:3000", -} -`; - exports[`Client ssrExchange should exist and be callable 1`] = `[Function]`; exports[`Client with custom fetch implementation should accept a custom fetch implementation 1`] = `