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

V1 subscriptions #160

Merged
merged 2 commits into from
Jan 18, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Additions

- `Client` / `createClient` config object has additional optional value `exchanges`.
- `Client` adds a config option for handling subscriptions: `forwardSubscription` with a type of `(operation, observer) => {unsubscribe: () => void}`
- `Connect` child function argument `mutations` is now typed.

## Changes
Expand All @@ -13,6 +14,8 @@
- `createClient` returns an object with a function for creating a client instance - see documentation for further information.
- `Connect` component prop `query` now only supports a single query element (multiple GraphQL query strings can be declared in a single Query object).
- `Connect` component prop `mutation` is now named `mutations`.
- `Connect` component prop `subscription` is now named `subscriptions` and is an array of subscriptions.
- `Connect` component prop `updateSubscription` signature slightly changed to the following: `(type, state, data) => newState`.
- `Connect` child function argument now groups mutations into a single `mutations` property.
- `Connect` child function argument property `refetch` now takes a single boolean value for refreshing cache.
- `Exchanges` have changed substantially, please see documentation for more information (default exchanges should work as expected).
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@

- [ChildArgs](types/child-args.md)
- [CombinedError](types/combined-error.md)
- [Mutation/Query](types/mutation-query.md)
- [Mutation/Query/Subscription](types/mutation-query-subscription.md)
28 changes: 28 additions & 0 deletions docs/about/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import { createClient, createQuery } from 'urql';

const client = createClient({
url: 'https://my-host/graphql',
forwardSubscription(operation, observer) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure whether it makes sense to have options like this on the client. Wouldn't these options be mostly forwarded to exchanges' factory functions? It might be worth to keep as many exchanges as possible opt-in only

observer.next({data: {}, error: null, operation}),
return { unsubscribe };
},
});
```

Expand All @@ -28,6 +32,10 @@ const todoClient = client.createInstance({
console.log('The following values changed: ', changed);
myState = { ...myState, ...changed };
},
onSubscriptionUpdate: update => {
console.log('new data pushed from the server!', update);
myState = { ...myState, ...update };
},
});
```

Expand Down Expand Up @@ -57,6 +65,26 @@ const mutation = createMutation(AddTodo, args);
todoClient.executeMutation(mutation);
```

### Executing a subscription

Executing a subscription works in a similar way to executing a query.

> Notice! In order to use subscriptions, your client needs to be configured with a `forwardSubscription` option.

```jsx
const subscription = createSubscription(TodoAdded, args);

todoClient.executeSubscription(subscription);
```

**Unsubscribe**

To unsubscribe from a subscription with your backend, pass the same subscription to the unsubscribe function.

```jsx
todoClient.executeUnsubscribeSubscription(subscription);
```

### Terminating the client

Spoiler alert! The magic behind queries being automatically fetched is by-part thanks to the use of streaming. This comes with a catch however as our client instance is now latched onto a stream.
Expand Down
80 changes: 80 additions & 0 deletions docs/about/subscriptions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
## Urql Subscriptions

Urql holds a transport-agnostic opinion and API on subscriptions because the GraphQL spec does not define subscriptions yet. Urql will handle the internal
state and updates, while delegating the actual request operation to a third part (e.g., `subscriptions-transport-ws`).

There are a few steps to make subscriptions work:

1. Create a client with a `forwardSubscription` option.
1. pass subscriptions into a Connect component or HOC configuration.
1. pass an `updateSubscription` method to handle the merging of state.

In more detail:

### Creating a client

To get started, as usual, you will want to create a client but make sure to pass in the `forwardSubscription` handler. It has a method signature of:

```jsx
type forwardSubscription = (
operation: Operation,
observer: Observer
) => { unsubscribe: () => void };
```

This works out especially well with Apollo's `subscriptions-transport-ws` as it returns the same unsubscribe shape. Here's an example of it:

```jsx
import { createClient, createQuery } from 'urql';
import { SubscriptionClient } from 'subscriptions-transport-ws';
const subscriptionClient = new SubscriptionClient(
'ws://localhost:3001/graphql',
{}
);

const client = createClient({
url: 'https://my-host/graphql',
forwardSubscription(operation, observer) => subscribe.request(operation).subscribe({
next: (data) => observer.next({data, error: null, operation}),
error: (data) => observer.error({data: null, error, operation}),
})
});
```

### Component/HOC definitions

Subscriptions are an array for multiple subscriptions a component can listen to. In an example todo app, a subscription for when a todo is added will look like this:

```jsx
import { createSubscription, Connect, ConnectHOC } from 'urql';

const subscriptions = [createSubscription(TodoAdded)];

// <Connect {...subscriptions} />
// or
// ConnectHOC({subscriptions})
```

### updateSubscription handler

When a subscription receives a new event from the server, it is up to the user to manage the merging of old state, with the new change. This handler is attached to the component/HOC and looks like this:

```jsx
<Connect
subscriptions={[
createSubscription(TodoAdded),
createSubscription(TodoRemoved),
]}
updateSubscription={(type, state, data) => {
if (type === 'todoAdded') {
state.push(data);
return state;
}

if (type === 'todoRemoved') {
state.splice(state.indexOf(data), 1);
return state;
}
}}
/>
```
19 changes: 14 additions & 5 deletions docs/components/connect-hoc.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ A HOC alternative implementation to the [Connect](Connect) component.

### Props

| Name | Type | Default | Description |
| --------- | ------------------------------------------------------------ | --------- | ------------------------------------------------------------ |
| query | [Query](../types/mutation-query.md)? | undefined | The query you want connected to your component. |
| mutations | Object { [string]: [Mutation](../types/mutation-query.md) }? | undefined | The mutation/mutations you want connected to your component. |
| Name | Type | Default | Description |
| ------------------ | ------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------ |
| query | [Query](../types/mutation-query-subscription.md)? | undefined | The query you want connected to your component. |
| mutations | Object { [string]: [Mutation](../types/mutation-query-subscription.md) }? | undefined | The mutation/mutations you want connected to your component. |
| subscription | Array<[Subscription](../types/mutation-query-subscription.md)>? | undefined | The subscriptions you want connected to your component. |
| updateSubscription | (type, state, data) => newData? | undefined | An updator function to merge the new data with the existing state. |

### Example

```jsx
import { ConnectHOC, createMutation, createQuery } from 'urql';
import { ConnectHOC, createMutation, createQuery, createSubscription } from 'urql';
import { TodoList } from './components';

// ...
Expand All @@ -21,6 +23,13 @@ const connectArgs = {
mutations: {
addTodos: createMutation(AddTodos),
},
subscriptions: [createSubscription(TodoAdded)]
updateSubscription(type, state, data) => {
if (type === 'todoAdded') {
state.push(data);
return state;
}
},
};

// ...
Expand Down
21 changes: 15 additions & 6 deletions docs/components/connect.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,32 @@ Connect is a ReactJS component that is used to execute queries and mutations and

### Props

| Name | Type | Default | Description |
| --------- | ------------------------------------------------------------ | --------- | ------------------------------------------------------------------ |
| query | [Query](../types/mutation-query.md)? | undefined | The query you want connected to your component |
| mutations | Object { [string]: [Mutation](../types/mutation-query.md) }? | undefined | The mutation/mutations you want connected to your component |
| children | ([ChildArgs\<T\>](../types/child-args.md)) => ReactNode | undefined | A child function to accept the connected state and render elements |
| Name | Type | Default | Description |
| ------------------ | ------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------ |
| query | [Query](../types/mutation-query-subscription.md)? | undefined | The query you want connected to your component |
| mutations | Object { [string]: [Mutation](../types/mutation-query-subscription.md) }? | undefined | The mutation/mutations you want connected to your component |
| subscription | Array<[Subscription](../types/mutation-query-subscription.md)>? | undefined | The subscriptions you want connected to your component. |
| updateSubscription | (type, state, data) => newData? | undefined | An updator function to merge the new data with the existing state. |
| children | ([ChildArgs\<T\>](../types/child-args.md)) => ReactNode | undefined | A child function to accept the connected state and render elements |

### Example

```jsx
import { Connect, createQuery, createMutation } from 'urql';
import { Connect, createQuery, createMutation, createSubscription } from 'urql';

// ...
<Connect
query={createQuery(GetTodos)}
mutations={{
addTodo: createMutation(AddTodo)
}}
subscriptions={[createSubscription(TodoAdded)]}
updateSubscription={(type, state, data) => {
if (type === 'todoAdded') {
state.push(data);
return state;
}
}}
children={({ fetching, data }) => {
return fetching ? <Loading/> : <List data={data.todos}>
}}
Expand Down
10 changes: 0 additions & 10 deletions docs/types/mutation-query.md

This file was deleted.

2 changes: 1 addition & 1 deletion docs/utils/createMutation.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
### Description

Create a [Mutation](../types/mutation-query.md) from a mutation query.
Create a [Mutation](../types/mutation-query-subscription.md) from a mutation query.

### Arguments

Expand Down
2 changes: 1 addition & 1 deletion docs/utils/createQuery.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
### Description

Create a [Query](../types/mutation-query.md) from a GraphQL query.
Create a [Query](../types/mutation-query-subscription.md) from a GraphQL query.

### Arguments

Expand Down
27 changes: 27 additions & 0 deletions docs/utils/createSubscription.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
### Description

Create a [Subscription](../types/mutation-query-subscription.md) from a GraphQL query.

### Arguments

| Name | Type | Description |
| --------- | ------- | ---------------------------------------- |
| query | string | A GraphQL subscription string. |
| variables | object? | A collection of GraphQL query variables. |

### Example

```jsx
import { createSubscription } from 'urql';

const subscriptionString = `
subscription {
todoAdded {
id
text
}
}
`;

const subscription = createSubscription(subscriptionString);
```
Loading