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

(svelte) - Reimplement @urql/svelte bindings with new API approach #1016

Merged
merged 17 commits into from
Oct 1, 2020
Merged
Show file tree
Hide file tree
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
12 changes: 12 additions & 0 deletions .changeset/real-horses-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@urql/svelte': major
---

Reimplement the `@urql/svelte` API, which is now marked as stable.
The new `@urql/svelte` API features the `query`, `mutation`, and `subscription` utilities, which are
called as part of a component's normal lifecycle and accept `operationStore` stores. These are
writable stores that encapsulate both a GraphQL operation's inputs and outputs (the result)!

Learn more about how to use `@urql/svelte` [in our new API
docs](https://formidable.com/open-source/urql/docs/api/svelte/) or starting from the [Basics
pages.](https://formidable.com/open-source/urql/docs/basics/)
59 changes: 58 additions & 1 deletion docs/advanced/subscriptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ we return to the `subscriptionExchange` inside `forwardSubscription`.
## React & Preact

The `useSubscription` hooks comes with a similar API to `useQuery`, which [we've learned about in
the "Queries" page in the "Basics" section.](../basics/queries.md)
the "Queries" page in the "Basics" section.](../basics/queries.md#react--preact)

Its usage is extremely similar in that it accepts options, which may contain `query` and
`variables`. However, it also accepts a second argument, which is a reducer function, similar to
Expand Down Expand Up @@ -130,6 +130,62 @@ the `handleSubscription` function. This works over time, so as
new messages come in, we will append them to the list of previous
messages.

## Svelte

The `subscription` function in `@urql/svelte` comes with a similar API to `query`, which [we've
learned about in the "Queries" page in the "Basics" section.](../basics/queries.md#svelte)

Its usage is extremely similar in that it accepts an `operationStore`, which will typically contain
our GraphQL subscription query. However, `subscription` also accepts a second argument, which is
a reducer function, similar to what you would pass to `Array.prototype.reduce`.

It receives the previous set of data that this function has returned or `undefined`.
As the second argument, it receives the event that has come in from the subscription.
You can use this to accumulate the data over time, which is useful for a
list for example.

In the following example, we create a subscription that informs us of
new messages. We will concatenate the incoming messages so that we
can display all messages that have come in over the subscription across
events.

```js
<script>
import { operationStore, subscription } from '@urql/svelte';

const messages = operationStore(`
subscription MessageSub {
newMessages {
id
from
text
}
}
`);

const handleSubscription = (messages = [], data) => {
return [data.newMessages, ...messages];
};

subsription(messages, handleSubscription);
</script>

{#if !$result.data}
<p>No new messages</p>
{:else}
<ul>
{#each $messages.data as message}
<li>{message.from}: "{message.text}"</li>
{/each}
</ul>
{/if}

```

As we can see, the `$result.data` is being updated and transformed by the `handleSubscription`
function. This works over time, so as new messages come in, we will append them to
the list of previous messages.

## One-off Subscriptions

When you're using subscriptions directly without `urql`'s framework bindings, you can use the `Client`'s `subscription` method for one-off subscriptions. This method is similar to the ones for mutations and subscriptions [that we've seen before on the "Core Package" page.](../concepts/core-package.md#one-off-queries-and-mutations)
Expand All @@ -155,3 +211,4 @@ const { unsubscribe } = pipe(
console.log(result); // { data: ... }
})
);
```
1 change: 1 addition & 0 deletions docs/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ more about the core package on the "Core Package" page.](../concepts/core-packag
- [`@urql/core` API docs](./core.md)
- [`urql` React API docs](./urql.md)
- [`@urql/preact` Preact API docs](./preact.md)
- [`@urql/svelte` Svelte API docs](./svelte.md)
- [`@urql/exchange-graphcache` API docs](./graphcache.md)
- [`@urql/exchange-retry` API docs](./retry-exchange.md)
- [`@urql/exchange-execute` API docs](./execute-exchange.md)
Expand Down
2 changes: 1 addition & 1 deletion docs/api/auth-exchange.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: '@urql/exchange-auth'
order: 9
order: 10
---

# Authentication Exchange
Expand Down
2 changes: 1 addition & 1 deletion docs/api/execute-exchange.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: '@urql/exchange-execute'
order: 5
order: 6
---

# Execute Exchange
Expand Down
2 changes: 1 addition & 1 deletion docs/api/graphcache.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: '@urql/exchange-graphcache'
order: 3
order: 4
---

# @urql/exchange-graphcache
Expand Down
2 changes: 1 addition & 1 deletion docs/api/multipart-fetch-exchange.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: '@urql/exchange-multipart-fetch'
order: 6
order: 7
---

# Multipart Fetch Exchange
Expand Down
2 changes: 1 addition & 1 deletion docs/api/persisted-fetch-exchange.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: '@urql/exchange-persisted-fetch'
order: 7
order: 8
---

# Persisted Fetch Exchange
Expand Down
2 changes: 1 addition & 1 deletion docs/api/refocus-exchange.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: '@urql/exchange-refocus'
order: 10
order: 11
---

# Refocus exchange
Expand Down
2 changes: 1 addition & 1 deletion docs/api/request-policy-exchange.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: '@urql/exchange-request-policy'
order: 8
order: 9
---

# Request Policy Exchange
Expand Down
2 changes: 1 addition & 1 deletion docs/api/retry-exchange.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: '@urql/exchange-retry'
order: 4
order: 5
---

# Retry Exchange
Expand Down
106 changes: 106 additions & 0 deletions docs/api/svelte.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
title: '@urql/svelte'
order: 3
---

# Svelte API

## operationStore

Accepts three arguments as inputs, where only the first one — `query` — is required.

| Argument | Type | Description |
| --------- | ------------------------ | ---------------------------------------------------------------------------------- |
| query | `string \| DocumentNode` | The query to be executed. Accepts as a plain string query or GraphQL DocumentNode. |
| variables | `?object` | The variables to be used with the GraphQL request. |
| context | `?object` | Holds the contextual information for the query. |

This is a [Svelte Writable Store](https://svelte.dev/docs#writable) that is used by other utilities
listed in these docs to read [`Operation` inputs](./core.md#operation) from and write
[`OperationResult` outputs](./core.md#operationresult) to.

The store has several properties on its value. The **writable properties** of it are inputs that are
used by either [`query`](#query), [`mutation`](#mutation), or [`subscription`](#subscription) to
create an [`Operation`](./core.md#operation) to execute. These are `query`, `variables`, and
`context`; the same properties that the `operationStore` accepts as arguments on creation.

Furthermore the store exposes some **readonly properties** which represent the operation's progress
and [result](./core.md#operationresult).

| Prop | Type | Description |
| ----------------------------------------------------------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| data | `?any` | Data returned by the specified query |
| error | `?CombinedError` | A [`CombinedError`](./core.md#combinederror) instances that wraps network or `GraphQLError`s (if any) |
| extensions | `?Record<string, any>` | Extensions that the GraphQL server may have returned. |
| stale | `boolean` | A flag that may be set to `true` by exchanges to indicate that the `data` is incomplete or out-of-date, and that the result will be updated soon. |
| fetching | `boolean` | A flag that indicates whether the operation is currently |
| in progress, which means that the `data` and `error` is out-of-date for the given inputs. |

All of the writable properties are updatable either via the common Svelte Writable's `set` or
`update` methods or directly. The `operationStore` exposes setters for the writable properties which
will automatically update the store and notify reactive subscribers.

In development, trying to update the _readonly_ properties directly or via the `set` or `update`
method will result in a `TypeError` being thrown.

[Read more about `writable` stores on the Svelte API docs.](https://svelte.dev/docs#writable)

## query

The `query` utility function only accepts an `operationStore` as its only argument. Per
`operationStore` it should only be called once per component as it lives alongside the component and
hooks into its `onDestroy` lifecycle method. This means that we must avoid passing a reactive
variable to it, and instead must pass the raw `operationStore`.

This function will return the `operationStore` itself that has been passed.

[Read more about how to use the `query` API on the "Queries" page.](../basics/queries.md#svelte)

## subscription

The `subscription` utility function accepts an `operationStore` as its first argument, like the
[`query` function](#query). It should also per `operationStore` be called once per component.

The function also optionally accepts a second argument, a `handler` function. This function has the
following type signature:

```js
type SubscriptionHandler<T, R> = (previousData: R | undefined, data: T) => R;
```

This function will be called with the previous data (or `undefined`) and the new data that's
incoming from a subscription event, and may be used to "reduce" the data over time, altering the
value of `result.data`.

`subscription` itself will return the `operationStore` that has been passed when called.

[Read more about how to use the `subscription` API on the "Subscriptions"
page.](../advanced/subscriptions.md#svelte)

## mutation

The `mutation` utility function either accepts an `operationStore` as its only argument or an object
containing `query`, `variables`, and `context` properties. When it receives the latter it will
create an `operationStore` automatically.

The function will return an `executeMutation` callback, which can be used to trigger the mutation.
This callback optionally accepts a `variables` argument and a `context` argument of type
[`Partial<OperationContext>`](./core.md#operationcontext). If these arguments are passed, they will
automatically update the `operationStore` before starting the mutation.

The `executeMutation` callback will return a promise which resolves to the `operationStore` once the
mutation has been completed.

[Read more about how to use the `mutation` API on the "Mutations"
page.](../basics/mutations.md#svelte)

## Context API

In Svelte the [`Client`](./core.md#client) is passed around using [Svelte's Context
API](https://svelte.dev/tutorial/context-api). `@urql/svelte` wraps around Svelte's
[`setContext`](https://svelte.dev/docs#setContext) and
[`getContext`](https://svelte.dev/docs#getContext) functions and exposes:

- `setClient`
- `getClient`
- `initClient` (a shortcut for `createClient` + `setClient`)
101 changes: 101 additions & 0 deletions docs/basics/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,104 @@ const App = () => (

Now every component and element inside and under the `Provider` are able to use GraphQL queries that
will be sent to our API.

[On the next page we'll learn about executing "Queries".](./queries.md#react--preact)

## Svelte

This "Getting Started" guide covers how to install and set up `urql` and provide a `Client` for
Svelte. The `@urql/svelte` package, which provides bindings for Svelte, doesn't fundamentally
function differently from `@urql/preact` or `urql` and uses the same [Core Package and
`Client`](../concepts/core-package.md).

### Installation

Installing `@urql/svelte` is quick and no other packages are immediately necessary.

```sh
yarn add @urql/svelte graphql
# or
npm install --save @urql/svelte graphql
```

Most libraries related to GraphQL also need the `graphql` package to be installed as a peer
dependency, so that they can adapt to your specific versioning requirements. That's why we'll need
to install `graphql` alongside `@urql/svelte`.

Both the `@urql/svelte` and `graphql` packages follow [semantic versioning](https://semver.org) and
all `@urql/svelte` packages will define a range of compatible versions of `graphql`. Watch out
for breaking changes in the future however, in which case your package manager may warn you about
`graphql` being out of the defined peer dependency range.

### Setting up the `Client`

The `@urql/svelte` package exports a method called `createClient` which we can use to create
the GraphQL client. This central `Client` manages all of our GraphQL requests and results.

```js
import { createClient } from '@urql/svelte';

const client = createClient({
url: 'http://localhost:3000/graphql',
});
```

At the bare minimum we'll need to pass an API's `url` when we create a `Client` to get started.

Another common option is `fetchOptions`. This option allows us to customize the options that will be
passed to `fetch` when a request is sent to the given API `url`. We may pass in an options object or
a function returning an options object.

In the following example we'll add a token to each `fetch` request that our `Client` sends to our
GraphQL API.

```js
const client = createClient({
url: 'http://localhost:3000/graphql',
fetchOptions: () => {
const token = getToken();
return {
headers: { authorization: token ? `Bearer ${token}` : '' },
};
},
});
```

### Providing the `Client`

To make use of the `Client` in Svelte we will have to provide it via the
[Context API](https://svelte.dev/tutorial/context-api). From a parent component to its child
components. This will share one `Client` with the rest of our app, if we for instance provide the
`Client`

```html
<script>
import { createClient, setClient } from '@urql/svelte';

const client = createClient({
url: 'http://localhost:3000/graphql',
});

setClient(client);
</script>
```

The `setClient` method internally calls [Svelte's `setContext`
function](https://svelte.dev/docs#setContext). The `@urql/svelte` package also exposes a `getClient`
function that uses [`getContext`](https://svelte.dev/docs#getContext) to retrieve the `Client` in
child components. This is used throughout `@urql/svelte`'s API.

We can also use a convenience function, `initClient`. This function combines the `createClient` and
`setClient` calls into one.

```html
<script>
import { initClient } from '@urql/svelte';

initClient({
url: 'http://localhost:3000/graphql',
});
</script>
```

[On the next page we'll learn about executing "Queries".](./queries.md#svelte)
Loading