Skip to content

Commit

Permalink
(core) - Deprecate the "operationName" property and move to "kind" (#…
Browse files Browse the repository at this point in the history
…1045)

* Deprecate the Operation.operationName property in core/src/client

* Add some missing files

* Update the core exchanges

* Add a changeset

* Update the multipart-fetch exchange

* Update the persisted-fetch exchange

* Update exchanges and move getOperationName to utils

* Remove confusing destructuring

* Update the changeset

* Update the testing doc

* Update a reference to getOperationName

* Update a reference to makeOperation

* Make client.createOperationContext public

* Changes for PR feedback

* Update makeOperation and move addMetadata

The addMetadata utility was definitely kept in the wrong
place since it concerns operations.
`makeOperation` has been updated to fill out the context
if it isn't passed which may become convenient in a couple
of places, instead setting it to the `operation.context`.

* Update operation spreads to use `makeOperation`

* Fix additional operation mapping case in cacheExchange

* Update core docs and add callout to changeset

* Replace all remaining operation spreads with makeOperation

Co-authored-by: Phil Pluckthun <[email protected]>
bkonkle and kitten authored Oct 28, 2020
1 parent 9aed08f commit 85a0946
Showing 51 changed files with 841 additions and 635 deletions.
20 changes: 20 additions & 0 deletions .changeset/twenty-olives-applaud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
'@urql/exchange-auth': patch
'@urql/exchange-execute': patch
'@urql/exchange-graphcache': patch
'@urql/exchange-multipart-fetch': patch
'@urql/exchange-persisted-fetch': patch
'@urql/exchange-populate': patch
'@urql/exchange-refocus': patch
'@urql/exchange-retry': patch
'@urql/exchange-suspense': patch
'@urql/core': minor
'urql': patch
---

Deprecate the `Operation.operationName` property in favor of `Operation.kind`. This name was
previously confusing as `operationName` was effectively referring to two different things. You can
safely upgrade to this new version, however to mute all deprecation warnings you will have to
**upgrade** all `urql` packages you use. If you have custom exchanges that spread operations, please
use [the new `makeOperation` helper
function](https://formidable.com/open-source/urql/docs/api/core/#makeoperation) instead.
50 changes: 21 additions & 29 deletions docs/advanced/authentication.md
Original file line number Diff line number Diff line change
@@ -82,7 +82,7 @@ const getAuth = async ({ authState }) => {
}

return null;
}
};
```

We check that the `authState` doesn't already exist (this indicates that it is the first time this exchange is executed and not an auth failure) and fetch the auth state from
@@ -104,7 +104,7 @@ const getAuth = async ({ authState, mutate }) => {
}

return null;
}
};
```

### Configuring `addAuthToOperation`
@@ -113,10 +113,9 @@ The purpose of `addAuthToOperation` is to take apply your auth state to each req
you've returned from `getAuth` and not at all constrained by the exchange:

```js
const addAuthToOperation = ({
authState,
operation,
}) => {
import { makeOperation } from '@urql/core';

const addAuthToOperation = ({ authState, operation }) => {
if (!authState || !authState.token) {
return operation;
}
@@ -126,20 +125,17 @@ const addAuthToOperation = ({
? operation.context.fetchOptions()
: operation.context.fetchOptions || {};

return {
...operation,
context: {
...operation.context,
fetchOptions: {
...fetchOptions,
headers: {
...fetchOptions.headers,
"Authorization": authState.token,
},
return makeOperation(operation.kind, operation, {
...operation.context,
fetchOptions: {
...fetchOptions,
headers: {
...fetchOptions.headers,
Authorization: authState.token,
},
},
};
}
});
};
```

First we check that we have an `authState` and a `token`. Then we apply it to the request `fetchOptions` as an `Authorization` header.
@@ -174,10 +170,8 @@ is the recommended approach. We'll be able to determine whether any of the Graph
```js
const didAuthError = ({ error }) => {
return error.graphQLErrors.some(
e => e.extensions?.code === 'FORBIDDEN',
);
}
return error.graphQLErrors.some(e => e.extensions?.code === 'FORBIDDEN');
};
```
For some GraphQL APIs, the auth error is communicated via an 401 HTTP response as is common in RESTful APIs:
@@ -225,7 +219,7 @@ const getAuth = async ({ authState }) => {
logout();

return null;
}
};
```
Here, `logout()` is a placeholder that is called when we got an error, so that we can redirect to a login page again and clear our tokens from local storage or otherwise.
@@ -299,14 +293,12 @@ const client = createClient({
cacheExchange,
errorExchange({
onError: error => {
const isAuthError = error.graphQLErrors.some(
e => e.extensions?.code === 'FORBIDDEN',
);
const isAuthError = error.graphQLErrors.some(e => e.extensions?.code === 'FORBIDDEN');

if (isAuthError) {
logout();
}
}
},
}),
authExchange({
/* config */
@@ -331,14 +323,14 @@ const App = ({ isLoggedIn }: { isLoggedIn: boolean | null }) => {
if (isLoggedIn === null) {
return null;
}

return createClient({ /* config */ });
}, [isLoggedIn]);

if (!client) {
return null;
}

return {
<GraphQLProvider value={client}>
{/* app content */}
8 changes: 3 additions & 5 deletions docs/advanced/testing.md
Original file line number Diff line number Diff line change
@@ -191,17 +191,15 @@ If you prefer to have more control on when the new data is arriving you can use
Here's an example of testing a list component which uses a subscription.

```tsx
import { OperationContext, makeOperation } from '@urql/core';

const mockClient = {
executeSubscription: jest.fn(query =>
pipe(
interval(200),
map((i: number) => ({
// To mock a full result, we need to pass a mock operation back as well
operation: {
operationName: 'subscription,
context: {},
...query,
},
operation: makeOperation('subscription', query, {} as OperationContext),
data: { posts: { id: i, title: 'Post title', content: 'This is a post' } },
}))
)
51 changes: 40 additions & 11 deletions docs/api/core.md
Original file line number Diff line number Diff line change
@@ -37,7 +37,7 @@ Accepts a [`GraphQLRequest`](#graphqlrequest) and optionally `Partial<OperationC
[`Source<OperationResult>`](#operationresult) — a stream of query results that can be subscribed to.

Internally, subscribing to the returned source will create an [`Operation`](#operation), with
`operationName` set to `'query'`, and dispatch it on the
`kind` set to `'query'`, and dispatch it on the
exchanges pipeline. If no subscribers are listening to this operation anymore and unsubscribe from
the query sources, the `Client` will dispatch a "teardown" operation.

@@ -53,12 +53,12 @@ repeatedly in the interval you pass.
### client.executeSubscription

This is functionally the same as `client.executeQuery`, but creates operations for subscriptions
instead, with `operationName` set to `'subscription'`.
instead, with `kind` set to `'subscription'`.

### client.executeMutation

This is functionally the same as `client.executeQuery`, but creates operations for mutations
instead, with `operationName` set to `'mutation'`.
instead, with `kind` set to `'mutation'`.

A mutation source is always guaranteed to only respond with a single [`OperationResult`](#operationresult) and then complete.

@@ -168,16 +168,15 @@ received.
### Operation

The input for every exchange that informs GraphQL requests.
It extends the [GraphQLRequest](#graphqlrequest) type and contains these additional properties:
It extends the [`GraphQLRequest` type](#graphqlrequest) and contains these additional properties:

| Prop | Type | Description |
| --------------- | ------------------ | --------------------------------------------- |
| `operationName` | `OperationType` | The type of GraphQL operation being executed. |
| `context` | `OperationContext` | Additional metadata passed to exchange. |
| Prop | Type | Description |
| --------- | ------------------ | --------------------------------------------- |
| `kind` | `OperationType` | The type of GraphQL operation being executed. |
| `context` | `OperationContext` | Additional metadata passed to exchange. |

> **Note:** In `urql` the `operationName` on the `Operation` isn't the actual name of an operation
> and derived from the GraphQL `DocumentNode`, but instead a type of operation, like `'query'` or
> `'teardown'`
An `Operation` also contains the `operationName` property, which is a deprecated alias of the `kind`
property and outputs a deprecation warning if it's used.

### RequestPolicy

@@ -379,6 +378,36 @@ Additionally, this utility will ensure that the `query` reference will remain st
that if the same `query` will be passed in as a string or as a fresh `DocumentNode`, then the output
will always have the same `DocumentNode` reference.

### makeOperation

This utility is used to either turn a [`GraphQLRequest` object](#graphqlrequest) into a new
[`Operation` object](#operation) or to copy an `Operation`. It adds the `kind` property and the
`operationName` alias that outputs a deprecation warning.

It accepts three arguments:

- An `Operation`'s `kind` (See [`OperationType`](#operationtype)
- A [`GraphQLRequest` object](#graphqlrequest) or another [`Operation`](#operation) that should be
copied.
- and; optionally a [partial `OperationContext` object.](#operationcontext). This argument may be
left out if the context is to be copied from the operation that may be passed as a second argument.

Hence some valid uses of the utility are:

```js
// Create a new operation from scratch
makeOperation('query', createRequest(query, variables), client.createOperationContext(opts));

// Turn an operation into a 'teardown' operation
makeOperation('teardown', operation);

// Copy an existing operation while modifying its context
makeOperation(operation.kind, operation, {
...operation.context,
preferGetMethod: true,
});
```

### makeResult

This is a helper function that converts a GraphQL API result to an
12 changes: 8 additions & 4 deletions docs/common-questions.md
Original file line number Diff line number Diff line change
@@ -10,17 +10,21 @@ order: 6
If you need `async fetchOptions` you can add an exchange that looks like this:

```js
import { makeOperation } from '@urql/core';

export const fetchOptionsExchange = (fn: any): Exchange => ({ forward }) => ops$ => {
return pipe(
ops$,
mergeMap((operation: Operation) => {
const result = fn(operation.context.fetchOptions);
return pipe(
typeof result.then === 'function' ? fromPromise(result) : fromValue(result),
map((fetchOptions: RequestInit | (() => RequestInit)) => ({
...operation,
context: { ...operation.context, fetchOptions },
}))
map((fetchOptions: RequestInit | (() => RequestInit)) => {
return makeOperation(operation.kind, operation, {
...operation.context,
fetchOptions,
});
})
);
}),
forward
16 changes: 8 additions & 8 deletions docs/concepts/exchanges.md
Original file line number Diff line number Diff line change
@@ -190,11 +190,11 @@ import { pipe, filter, merge, share } from 'wonka';
// <-- The ExchangeIO function (inline)
const queries = pipe(
operations$,
filter(op => op.operationName === 'query')
filter(op => op.kind === 'query')
);
const others = pipe(
operations$,
filter(op => op.operationName !== 'query')
filter(op => op.kind !== 'query')
);
return forward(merge([queries, others]));
};
@@ -205,11 +205,11 @@ import { pipe, filter, merge, share } from 'wonka';
const shared = pipe(operations$, share);
const queries = pipe(
shared,
filter(op => op.operationName === 'query')
filter(op => op.kind === 'query')
);
const others = pipe(
shared,
filter(op => op.operationName !== 'query')
filter(op => op.kind !== 'query')
);
return forward(merge([queries, others]));
};
@@ -221,7 +221,7 @@ import { pipe, filter, merge, share } from 'wonka';
pipe(
operations$,
map(op => {
if (op.operationName === 'query') {
if (op.kind === 'query') {
/* ... */
} else {
/* ... */
@@ -252,7 +252,7 @@ import { pipe, filter, merge, share } from 'wonka';
// This doesn't handle operations that aren't queries
const queries = pipe(
operations$,
filter(op => op.operationName === 'query')
filter(op => op.kind === 'query')
);
return forward(queries);
};
@@ -262,11 +262,11 @@ import { pipe, filter, merge, share } from 'wonka';
const shared = pipe(operations$, share);
const queries = pipe(
shared,
filter(op => op.operationName === 'query')
filter(op => op.kind === 'query')
);
const rest = pipe(
shared,
filter(op => op.operationName !== 'query')
filter(op => op.kind !== 'query')
);
return forward(merge([queries, rest]));
};
10 changes: 6 additions & 4 deletions exchanges/auth/README.md
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ You'll then need to add the `authExchange`, that this package exposes to your `u

```js
import { createClient, dedupExchange, cacheExchange, fetchExchange } from 'urql';
import { makeOperation } from '@urql/core';
import { authExchange } from '@urql/exchange-auth';

const client = createClient({
@@ -41,9 +42,10 @@ const client = createClient({
? operation.context.fetchOptions()
: operation.context.fetchOptions || {};

return {
...operation,
context: {
return makeOperation(
operation.kind,
operation,
{
...operation.context,
fetchOptions: {
...fetchOptions,
@@ -53,7 +55,7 @@ const client = createClient({
},
},
},
};
);
},
willAuthError: ({ authState }) => {
if (!authState) return true;
Loading

0 comments on commit 85a0946

Please sign in to comment.