Exchanges are the mechanism by which rescript-urql modifies requests before they are sent to your GraphQL API and alters responses as they are received. If you want to add some additional functionality to your GraphQL operations, this is a great place to do that.
The Exchanges module is a submodule of the Client module and can be referenced at ReScriptUrql.Client.Exchanges. The following exchanges are provided out of the box with rescript-urql.
urql ships with a set of core exchanges that are baked right into @urql/core and can be referenced safely in rescript-urql without installing any additional packages. These are detailed below.
The cacheExchange provides basic caching support for your GraphQL operations. It is of type Exchanges.t.
The dedupExchange will deduplicate pending operations waiting for a response. For example, if a user attempts to execute the same query by clicking a button in rapid succession, the dedupExchange will filter these events to a single request. It is of type Exchanges.t.
The fetchExchange is responsible for actually sending your request to your GraphQL API and handling the response. It is of type Exchanges.t.
The above three exchanges make up urql's defaultExchanges. When you create a client in rescript-urql these exchanges are already applied by default. If you specify an exchanges array, be sure to include the specific exchanges you need. You almost always want the defaultExchanges, so make sure to include them using Array.concat or Array.append.
open ReScriptUrql
let client = Client.make(
~url="https://mygraphql.com/api",
~exchanges=[
myCustomExchange,
Client.Exchanges.dedupExchange,
Client.Exchanges.cacheExchange,
Client.Exchanges.fetchExchange
],
()
)The debugExchange is useful for tracking how operations are passing through the exchanges pipeline. It simply logs all incoming and outgoing operations to the console. Be sure to remove this in production! It is of type Exchanges.t.
The subscriptionExchange should be used in the event that you intend to support GraphQL subscriptions in your application through use of useSubscription or the client's executeSubscription method.
In order to use the subscriptionExchange, you'll need to do a little bit of setup. Specifically, you'll need to configure a subscription client of some kind that will handle the websocket connection to your GraphQL API. In examples/5-subscription we have some simple bindings to subscriptions-transport-ws that allow us to use this protocol (which is supported by Apollo). Here's an example of how to set everything up:
/* Create the subscriptionClient using APIs from your subscription client of choice.
In this case we use custom bindings to SubscriptionsTransportWS. */
let subscriptionClient =
SubscriptionsTransportWS.make(
~url="ws://localhost:4001/graphql",
~subscriptionClientConfig=SubscriptionsTransportWS.makeClientOptions(),
(),
)
/* Implement the forwardSubscription function. This tells rescript-urql how to handle
incoming operations of operationType 'subscription'. */
let forwardSubscription = operation => subscriptionClient["request"](operation)
/* Include the subscriptionExchange in your client's exchanges array. */
let client = Client.(
make(
~url="https://localhost:3000/graphql",
~exchanges=Array.append(
Exchanges.defaultExchanges,
[Client.Exchanges.subscriptionExchange(~forwardSubscription, ())]
),
()
)
)The ssrExchange accepts a single optional argument, ~ssrExchangeParams, a record with two fields:
~initialState- which populates the server-side rendered data with a rehydrated cache.~isClient– tell the exchange whether or not it is executing on the client or the server. By default,rescript-urqlwill look at thesuspenseparameter passed to the client to determine this ifisClientis not provided.
If using the ssrExchange, it should be placed after any caching exchanges, like cacheExchange, but before any asynchronous exchanges, like fetchExchange.
open ReScriptUrql
let ssrCache = Exchanges.ssrExchange()
let client = Client.Client.make(
~url="http://localhost:3000",
~exchanges=[
Client.Exchanges.dedupExchange,
Client.Exchanges.cacheExchange,
ssrCache,
Client.Exchanges.fetchExchange
],
()
)The resulting data structure returned from creating the ssrExchange can be accessed using two getters:
extractData– this is typically used on the server-side to extract data returned from your GraphQL requests after they've been executed on the server.
open ReScriptUrql
let ssrCache = Client.Exchanges.ssrExchange(~ssrExchangeParams, ())
/* Extract data from the ssrCache. (Server-side) */
let extractedData = Client.Exchanges.extractData(~exchange=ssrCache)restoreDatais typically used to rehydrate the client with data from the server. Therestoreargument is what allows you to reference the data returned from the server to the client.
open ReScriptUrql
let ssrCache = Client.Exchanges.ssrExchange(~ssrExchangeParams, ())
/* Extract data from the ssrCache. */
let extractedData = Client.Exchanges.restoreData(~exchange=ssrCache, ~restore=urqlData)This part of the API is still quite experimental, as server-side rendering in ReScript with Next.js is still in its infancy. Use with caution. For more information, read urql's server-side rendering guide here. To see an example of server-side rendering with rescript-urql, check out our SSR example.
composeExchanges is a helper function that will compose a single exchange function from an array of exchanges. Operations will be run through the provided exchanges in the order that they were provided to composeExchanges.
In addition to the core exchanges exposed by @urql/core, urql also supports more abstracted exchanges that meet particular needs that may or may not be critical to your use case. In contrast to the core exchanges, these ecosystem exchanges should be used when you have a specific use case that warrants them. Many of these exchanges require additional packages to be installed. rescript-urql is in the process of adding bindings for these exchanges; if the ecosystem exchange you're interested in isn't outlined below, the bindings may not have yet been implemented. Community contributions are very welcome in this space!
The multipartFetchExchange builds on the fetchExchange but adds additional functionality for multipart file uploads. It should replace the use of the fetchExchange if you need to support traditional fetches and multipart file uploads.
To use the multipartFetchExchange, add the package to your dependencies:
yarn add @urql/exchange-multipart-fetchThen, substitute the fetchExchange with the multipartFetchExchange:
open ReScriptUrql
let client = Client.make(
~url="http://localhost:3000",
~exchanges=[
Client.Exchanges.dedupExchange,
Client.Exchanges.cacheExchange,
Client.Exchanges.multipartFetchExchange,
],
()
)Read more on the multipartFetchExchange here.
The persistedFetchExchange adds support for persisted queries, building off of the standard fetchExchange.
To use the persistedFetchExchange, add the package to your dependencies:
yarn add @urql/exchange-persisted-fetchThen, add the exchange to your array of exchanges, sepcifying the options you want to configure:
open ReScriptUrql
let persistedFetchExchangeOptions = Client.Exchanges.makePersistedFetchExchangeOptions(
~preferGetForPersistedQueries=true,
(),
)
let client = Client.make(
~url="http://localhost:3000",
~exchanges=[
Client.Exchanges.dedupExchange,
Client.Exchanges.cacheExchange,
Client.Exchanges.persistedFetchExchange(persistedFetchExchangeOptions),
// Keep the fetchExchange to handle mutations.
// The persistedFetchExchange only handles queries.
Client.Exchanges.fetchExchange,
],
()
)Read more about the persistedFetchExchange here.
The refocusExchange allows rescript-urql to redispatch active operations when the window regains focus.
To use the refocusExchange, add the package to your dependencies:
yarn add @urql/exchange-refocusThen, add the exchange to your array of exchanges. The refocusExchange should be added after the dedupeExchange, to prevent doing additional work on requests that are later deduplicated, and before the fetchExchange:
open ReScriptUrql
let client = Client.make(
~url="http://localhost:3000",
~exchanges=[
Client.Exchanges.dedupExchange,
Client.Exchanges.refocusExchange(),
Client.Exchanges.cacheExchange,
Client.Exchanges.fetchExchange,
],
()
)The requestPolicyExchange allows rescript-urql to automatically upgrade an operation's requestPolicy on a time-to-live basis. When the specified TTL has elapsed, rescript-urql will either:
- Upgrade the
requestPolicyof the operation tocache-and-networkif noshouldUpgradecallback is specified, or: - Run the
shouldUpgradefunction to determine whether or not to upgrade the specific operation.
To use the requestPolicyExchange, add the package to your dependencies:
yarn add @urql/exchange-request-policyThen, add the exchange to your array of exchanges, specifying the options you want to configure:
open ReScriptUrql
let shouldUpgrade = (operation: Types.operation) =>
operation.context.requestPolicy !== #CacheOnly
let requestPolicyExchangeOptions = Client.Exchanges.makeRequestPolicyExchangeOptions(
~shouldUpgrade,
~ttl=2000,
(),
)
let client = Client.make(
~url="http://localhost:3000",
~exchanges=[
Client.Exchanges.dedupExchange,
Client.Exchanges.cacheExchange,
Client.Exchanges.requestPolicyExchange(requestPolicyExchangeOptions),
Client.Exchanges.fetchExchange,
],
()
)Read more about the requestPolicyExchange here.
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:
yarn add @urql/exchange-retryThen, add the exchange to your array of exchanges, specifying the options you want to configure:
open ReScriptUrql
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:
{
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.
open ReScriptUrql
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,
],
()
)Read more on the retryExchange here.
rescript-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.
The signature of an exchange in rescript-urql is:
type t = (
exchangeInput,
. Wonka.Source.t<Types.operation>,
) => Wonka.Source.t<Types.operationResultJs<Js.Json.t>>exchangeInput here is a record containing two fields:
forward– a function for forwarding the current operation onto the next exchange.client– yourrescript-urqlclient instance.
Let's look at an example to see how to implement our own exchanges.
To see how we can write our own custom exchange, we'll reimplement the urql's native debugExchange in ReScript:
open ReScriptUrql
// This is the native debugExchange that ships with `urql`, re-implemented in ReScript.
// Typically, you'd just add Exchanges.debugExchange to the Client's exchange array.
let debugExchange = ({Client.Exchanges.forward: forward}, . ops) =>
ops
// Execute the supplied callback as an operation is sent out.
|> Wonka.tap((. op) => Js.log2("[debugExchange]: Incoming operation: ", op))
// Forward the operation on to the next exchange.
|> forward
// Execute the supplied callback as an operation comes back in (i.e. an API request resolves).
|> Wonka.tap((. res) => Js.log2("[debugExchange]: Completed operation: ", res))That's it! We've successfully re-implemented urql's debugExchange in ReScript. Because the Wonka library used for much of urql's exchange architecture is written in Reason, writing our own exchanges is often more ergonomic than the JS experience. When you've written your exchange, just supply it to your client like so:
open ReScriptUrql
let debugExchange = ({Client.Exchanges.forward: forward}, . ops) =>
ops
|> Wonka.tap((. op) => Js.log2("[debugExchange]: Incoming operation: ", op))
|> forward
|> Wonka.tap((. res) => Js.log2("[debugExchange]: Completed operation: ", res))
let client = Client.make(
~url="https://my-graphql-endpoint.com/graphql",
~exchanges=[
debugExchange,
Exchanges.dedupExchange,
Exchanges.cacheExchange,
Exchanges.fetchExchange
],
(),
)