Skip to content

Commit

Permalink
feat(persisted): Replace persistedFetchExchange with generic persiste…
Browse files Browse the repository at this point in the history
…dExchange (#3057)
  • Loading branch information
kitten authored Mar 16, 2023
1 parent 20ce654 commit 7241181
Show file tree
Hide file tree
Showing 28 changed files with 487 additions and 750 deletions.
5 changes: 5 additions & 0 deletions .changeset/pretty-cows-dance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@urql/core': patch
---

Add logic for `request.extensions.persistedQuery` to `@urql/core` to omit sending `query` as needed.
5 changes: 5 additions & 0 deletions .changeset/strange-apples-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@urql/exchange-persisted-fetch': major
---

Remove `persistedFetchExchange` and instead implement `persistedExchange`. This exchange must be placed in front of a terminating exchange (such as the default `fetchExchange` or a `subscriptionExchange` that supports persisted queries), and only modifies incoming operations to contain `extensions.persistedQuery`, which is sent on via the API. If the API expects Automatic Persisted Queries, requests are retried by this exchange internally.
5 changes: 5 additions & 0 deletions .changeset/three-poets-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@urql/exchange-persisted-fetch': major
---

Rename `@urql/exchange-persisted-fetch` to `@urql/exchange-persisted`
106 changes: 37 additions & 69 deletions docs/advanced/persistence-and-uploads.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ order: 1
# Persisted Queries and Uploads

`urql` supports both [Automatic Persisted
Queries](https://www.apollographql.com/docs/apollo-server/performance/apq/) and [File
Uploads](https://www.apollographql.com/docs/apollo-server/data/file-uploads/).
Both of these features are implemented by enhancing or swapping out the default
[`fetchExchange`](../api/core.md#fetchexchange).
Queries](https://www.apollographql.com/docs/apollo-server/performance/apq/), Persisted Queries, and
[File Uploads](https://www.apollographql.com/docs/apollo-server/data/file-uploads/).

While File Uploads should work without any modifications, an additional exchange must be installed
and added for Persisted Queries to work.

## Automatic Persisted Queries

Expand All @@ -29,33 +30,33 @@ Additionally, we could also decide to send these hashed queries as GET requests
requests. If we only send the persisted queries with hashes as GET requests then they become a lot
easier for a CDN to cache, as by default most caches would not cache POST requests automatically.

In `urql`, we may use the `@urql/exchange-persisted-fetch` package's `persistedFetchExchange` to
implement Automatic Persisted Queries. This exchange works alongside other fetch exchanges and only
handles `query` operations.
In `urql`, we may use the `@urql/exchange-persisted` package's `persistedExchange` to
implement Automatic Persisted Queries. This exchange works alongside the default `fetchExchange`
and other exchanges by adding the `extensions.persistedQuery` parameters to a GraphQL request.

### Installation & Setup

First install `@urql/exchange-persisted-fetch` alongside `urql`:
First install `@urql/exchange-persisted` alongside `urql`:

```sh
yarn add @urql/exchange-persisted-fetch
yarn add @urql/exchange-persisted
# or
npm install --save @urql/exchange-persisted-fetch
npm install --save @urql/exchange-persisted
```

You'll then need to add the `persistedFetchExchange` method, that this package exposes,
You'll then need to add the `persistedExchange` function, that this package exposes,
to your `exchanges`.

```js
import { createClient, dedupExchange, fetchExchange, cacheExchange } from 'urql';
import { persistedFetchExchange } from '@urql/exchange-persisted-fetch';
import { persistedExchange } from '@urql/exchange-persisted-fetch';

const client = createClient({
url: 'http://localhost:1234/graphql',
exchanges: [
dedupExchange,
cacheExchange,
persistedFetchExchange({
persistedExchange({
preferGetForPersistedQueries: true,
}),
fetchExchange,
Expand All @@ -65,24 +66,24 @@ const client = createClient({

As we can see, typically it's recommended to set `preferGetForPersistedQueries` to `true` to force
all persisted queries to use GET requests instead of POST so that CDNs can do their job.
We also added the `persistedFetchExchange` in front of the usual `fetchExchange`, since it only
handles queries but not mutations.
We also added the `persistedExchange` in front of the usual `fetchExchange`, since it has to
update operations before they reach an exchange that talks to an API.

The `preferGetForPersistedQueries` is similar to the [`Client`'s
`preferGetMethod`](../api/core.md#client) but only switches persisted queries to use GET requests
instead. This is preferable since sometimes the GraphQL query can grow too large for a simple GET
query to handle, while the `persistedFetchExchange`'s SHA256 hashes will remain predictably small.
query to handle, while the `persistedExchange`'s SHA256 hashes will remain predictably small.

### Customizing Hashing

The `persistedFetchExchange` also accepts a `generateHash` option. This may be used to swap out the
The `persistedExchange` also accepts a `generateHash` option. This may be used to swap out the
exchange's default method of generating SHA256 hashes. By default, the exchange will use the
built-in [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) on the
browser, which has been implemented to support IE11 as well. In Node.js it'll use the [Node
Crypto Module](https://nodejs.org/api/crypto.html) instead.
built-in [Web Crypto API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) when it's
available, and in Node.js it'll use the [Node Crypto Module](https://nodejs.org/api/crypto.html)
instead.

If you're using [the `graphql-persisted-document-loader` for
Webpack](https://github.com/leoasis/graphql-persisted-document-loader) for instance, then you will
Webpack](https://github.com/leoasis/graphql-persisted-document-loader), for instance, then you will
already have a loader generating SHA256 hashes for you at compile time. In that case we could swap
out the `generateHash` function with a much simpler one that uses the `generateHash` function's
second argument, a GraphQL `DocumentNode` object.
Expand All @@ -94,7 +95,7 @@ persistedFetchExchange({
```

If you're using **React Native** then you may not have access to the Web Crypto API, which means
that you have to provide your own SHA256 function to the `persistedFetchExchange`. Luckily we can do
that you have to provide your own SHA256 function to the `persistedExchange`. Luckily, we can do
so easily by using the first argument `generateHash` receives, a GraphQL query as a string.

```js
Expand All @@ -108,58 +109,25 @@ persistedFetchExchange({
```

Additionally, if the API only expects persisted queries and not arbitrary ones and all queries are
pre-registered against the API then the `persistedFetchExchange` may be put into a **non-automatic**
pre-registered against the API then the `persistedExchange` may be put into a **non-automatic**
persisted queries mode by giving it the `enforcePersistedQueries: true` option. This disables any
retry logic and assumes that persisted queries will be handled like regular GraphQL requests.

[Read more about `@urql/persisted-fetch-exchange` in our API
docs.](../api/persisted-fetch-exchange.md)

## File Uploads

GraphQL server frameworks like [Apollo Server support an unofficial spec for file
uploads.](https://www.apollographql.com/docs/apollo-server/data/file-uploads/) This allows us to
define mutations on our API that accept an `Upload` input, which on the client would be a variable
that we can set to a [File](https://developer.mozilla.org/en-US/docs/Web/API/File), which we'd
typically retrieve via a [file input for
instance](https://developer.mozilla.org/en-US/docs/Web/API/File/Using_files_from_web_applications).

In `urql`, we may use the `@urql/exchange-multipart-fetch` package's `multipartFetchExchange` to
support file uploads, which is a drop-in replacement for the default
[`fetchExchange`](../api/core.md#fetchexchange). It may also be used [alongside the
`persistedFetchExchange`](#automatic-persisted-queries).
Many GraphQL server frameworks and APIs support the ["GraphQL Multipart Request
Spec](https://github.com/jaydenseric/graphql-multipart-request-spec) to allow files to be uploaded.
Often, this is defined in schemas using a `File` or `Upload` input.
This allows us to pass a `File` or `Blob` directly to our GraphQL requests as variables, and the
spec requires us to perform this request as a multipart upload.

It works by using the [`extract-files` package](https://www.npmjs.com/package/extract-files). When
the `multipartFetchExchange` sees at least one `File` in the variables it receives for a mutation,
then it will send a `multipart/form-data` POST request instead of a standard `application/json`
one. This is basically the same kind of request that we'd expect to send for regular HTML forms.

### Installation & Setup

First install `@urql/exchange-multipart-fetch` alongside `urql`:

```sh
yarn add @urql/exchange-multipart-fetch
# or
npm install --save @urql/exchange-multipart-fetch
```

The `multipartFetchExchange` is a drop-in replacement for the `fetchExchange`, which should be
replaced in the list of `exchanges`:

```js
import { createClient, dedupExchange, cacheExchange } from 'urql';
import { multipartFetchExchange } from '@urql/exchange-multipart-fetch';

const client = createClient({
url: 'http://localhost:3000/graphql',
exchanges: [dedupExchange, cacheExchange, multipartFetchExchange],
});
```
Files are often handled in the browser via the [File API](https://developer.mozilla.org/en-US/docs/Web/API/File),
which we may typically get to via a [file input](https://developer.mozilla.org/en-US/docs/Web/API/File/Using_files_from_web_applications)
for example.

If you're using the `persistedFetchExchange` then put the `persistedFetchExchange` in front of the
`multipartFetchExchange`, since only the latter is a full replacement for the `fetchExchange`, and
the former only handled query operations.
In `urql`, these are supported natively, so as long as your JS environment supports either `File` or
`Blob`s, you can pass these directly to any `urql` API via your `variables`, and the default
`fetchExchange` will swich to using a multipart request instead.

[Read more about `@urql/multipart-fetch-exchange` in our API
docs.](../api/multipart-fetch-exchange.md)
Previously, this worked by installing the [`@urql/multipart-fetch-exchange` package](../api/multipart-fetch-exchange.md),
however, this package has been deprecated and file uploads are now built into `@urql/core`.
1 change: 0 additions & 1 deletion docs/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ more about the core package on the "Core" page.](../basics/core.md)
- [`@urql/exchange-retry` API docs](./retry-exchange.md)
- [`@urql/exchange-execute` API docs](./execute-exchange.md)
- [`@urql/exchange-multipart-fetch` API docs](./multipart-fetch-exchange.md)
- [`@urql/exchange-persisted-fetch` API docs](./persisted-fetch-exchange.md)
- [`@urql/exchange-request-policy` API docs](./request-policy-exchange.md)
- [`@urql/exchange-auth` API docs](./auth-exchange.md)
- [`@urql/exchange-refocus` API docs](./refocus-exchange.md)
3 changes: 1 addition & 2 deletions docs/api/multipart-fetch-exchange.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ It follows the unofficial [GraphQL Multipart Request
Spec](https://github.com/jaydenseric/graphql-multipart-request-spec) which is supported by the
[Apollo Sever package](https://www.apollographql.com/docs/apollo-server/data/file-uploads/).

This exchange uses the same fetch logic as the [`fetchExchange`](./core.md#fetchexchange) and the
[`persistedFetchExchange`](./persisted-fetch-exchange.md) by reusing logic from `@urql/core/internal`.
This exchange uses the same fetch logic as the [`fetchExchange`](./core.md#fetchexchange) and by reusing logic from `@urql/core/internal`.
The `multipartFetchExchange` is a drop-in replacement for the default
[`fetchExchange`](./core.md#fetchexchange) and will act exactly like the `fetchExchange` unless the
`variables` that it receives for mutations contain any `File`s as detected by the `extract-files` package.
Expand Down
68 changes: 0 additions & 68 deletions docs/api/persisted-fetch-exchange.md

This file was deleted.

Loading

0 comments on commit 7241181

Please sign in to comment.