Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add exchange readme #186

Closed
wants to merge 3 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ GraphQL clients!
Let's get up and running! This section explains how to
install `urql` and use its React components and hooks.
This contains everything you need to know to get started
and just use the default `urql`!
using the default configuration!

### [Architecture](architecture.md)

127 changes: 127 additions & 0 deletions docs/extending-and-experimenting.md
Original file line number Diff line number Diff line change
@@ -147,3 +147,130 @@ subscribe to the result of `executeQuery`, keep track
of the unsubscription (`teardown`) and update
some state. This all can be reapplied when you write your
own APIs.

## Making your own Exchange

As with any library, you're going to find yourself wanting to change (or adapt) the way things work.
Since day one, with `urql`, we have aimed to make adaptability our number one priority through the use of _Exchanges_.
Lets run through how this works by creating our own Dedup exchange - a middleware to debounce incoming query operations.

> Note: We've already provided a bunch of exchanges that we thought you would find
> useful. Check out the [exchanges source code](https://github.com/FormidableLabs/urql/blob/master/src/exchanges) for examples and usage information.
Before you begin, it is assumed that you understand the fundamentals of _Operations_
and _Exchanges_ in `urql`. There's a detailed description in the [Architecture](./architecture.md) section of the docs, but here's a quick recap:

- Every operation in `urql` is passed to exchanges
- Each exchange can do one of two things
- Forward the operation to the next exchange in line
- Return a result of the operation to the client

First thing first, lets set up our boilerplate by making an _Exchange_ that takes an operation stream and passes it immediately to the next exchange in line. We'll also add in some initial logic which will later allow us to track which operations are in progress.

```ts
import { filter, pipe, tap } from 'wonka';
import { Exchange } from 'urql';

export const dedupExchange: Exchange = ({ forward }) => {
const inFlight = new Set<string>();

return ops$ => forward(ops$);
};
```

You can see from the above example that an exchange is a [curried function](https://medium.com/javascript-scene/curry-and-function-composition-2c208d774983). It takes some setup arguments and returns a function which receives an operation stream (`ops$`) and forwards it to the next exchange (`forward`).

Next thing we want to do is track unique incoming operations and add them to our tracking set. We also want to delete them from our set once the operation has completed. In order to do this we will be using the `pipe` and `tap` functions provided by `wonka`.

> Note: The easiest way to uniquely identify an operation is by checking it's `key` property. This is a uniquely generated hash intended for this purpose.
```ts
import { filter, pipe, tap } from 'wonka';
import { Exchange } from 'urql';

export const dedupExchange: Exchange = ({ forward }) => {
const inFlight = new Set<string>();

return ops$ => {
const preFlight$ = pipe(
ops$,
// "tap" is called every time a new operation comes through.
// "tap" is invisible to subsequent stream operations
tap(operation => {
if (operation.operationName !== 'query') {
return;
}

inFlight.add(operation.key);
})
);

return pipe(
forward(preFlight$),
// This `tap` is called after an operation has been completed
tap(response => inFlight.delete(response.operation.key))
);
};
};
```

Our exchange now tracks operations and their completion, but in order for this to be useful,
we need to stop in flight operations being forwarded to the next exchange. In order to do this,
we're now going to have to use the `filter` function. To simplify things further, we can group all our pre-flight logic together.

```ts
import { filter, pipe, tap } from 'wonka';
import { Exchange } from 'urql';

export const dedupExchange: Exchange = ({ forward }) => {
const inFlight = new Set<string>();

return ops$ => {
const preFlight$ = pipe(
ops$,
filter(operation => {
// Don't filter non-query operations
if (operation.operationName !== 'query') {
return true;
}

const isInFlight = inFlight.has(operation.key);
inFlight.add(operation.key);

return isInFlight;
})
);

return pipe(
forward(preFlight$),
// This `tap` is called after an operation has been completed
tap(response => inFlight.delete(response.operation.key))
);
};
};
```

Hopefully you've been following this example along, if that's the case, congratulations! You just created your first exchange. The only thing left is to add it to `urql` on client creation.

> Note: Remember, operations are forwarded in a linear fashion. Depending on your exchange, ordering may be important (in this example, dedup needs to come before fetch otherwise it wouldn't do anything).
```ts
import {
createClient,
cacheExchange,
debugExchange,
fetchExchange,
} from 'urql';
import { dedupExchange } from './myDedupExchange';

export const urqlClient = createClient({
url: 'https://localhost:3000/graphql',
exchanges: [dedupExchange, debugExchange, cacheExchange, fetchExchange],
});
```

To conclude, here's a quick recap of what we've achieved:

- We created our own _Exchange_!
- When duplicate operations come through which are already in flight we block them.
- Both client side components will be updated when the single operation is completed (this is due to how the `urql` client subscribes to operations).
67 changes: 67 additions & 0 deletions src/exchanges/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Exchanges

## Usage

You can chain together any of the following exchanges (or create your own) during client creation.

```ts
import {
cacheExchange,
debugExchange,
fetchExchange,
fallbackExchange,
} from 'urql';

createClient({
url: 'http://localhost:3000/graphql',
exchanges: [debugExchange, cacheExchange, fetchExchange, fallbackExchange],
});
```

## Built in exchanges

### Cache (_default_)

A simple cache which supports caching policies and invalidation of data on mutation execution.

Following caching policies can be specified at query execution:

- `cache-and-network`: Return cached data (if available) and update on completing a network request.
- `cache-only`: Present cached data only.
- `network-only`: Do not use cache.

### Debug

_Intended for development/debugging purposes._

Prints incoming operations and their results to the console.

- If you wish to print every operation, ensure that it is the first in the list of exchanges.

### Dedup (_default_)

Debounces operations which are already in flight.

- Ensure that this is specified before the _Fetch_ exchange.

### Fallback (_default_)

> _Intended for development/debugging purposes._
A stub exchange for catching unhandled operations and printing alerts to the console.

- This exchange should be the last in the list of exchanges.

### Fetch (_default_)

Executes _Query_ and _Mutation_ operations via HTTP to the GraphQL server endpoint.

- HTTP endpoint is passed at client creation and specified using the `url` property.
- Additional fetch parameters can also be passed during client creation using the `fetchOptions` property. For more information about supported fetch options, see type definitions or [MDN web docs](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters).

### Subscription

Executes _Subscription_ operations via a provided websocket adapter.

- Expects to receive an argument with the property `forwardSubscription` being the websocket adapter.
- An [example implementation](https://github.com/FormidableLabs/urql/blob/master/examples/2-using-subscriptions/src/app/index.tsx) can be found in the examples.
6 changes: 1 addition & 5 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -8,11 +8,7 @@ export { ExecutionResult } from 'graphql';
export type OperationType = 'subscription' | 'query' | 'mutation' | 'teardown';

/** The strategy that is used to request results from network and/or the cache. */
export type RequestPolicy =
| 'cache-first'
| 'cache-only'
| 'network-only'
| 'cache-and-network';
export type RequestPolicy = 'cache-only' | 'network-only' | 'cache-and-network';

/** A Graphql query, mutation, or subscription. */
export interface GraphQLRequest {