Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.

Part 2: New Documentation (#556)

* Fix index page missing from sidebar

* Hide legend for empty headings

* Fix legend item spacing

* add short chapter about the way we send errors back

* Rename a couple of routes and files to match titles

* add note about graphql error

* Edit "Getting Started" guide (1st pass)

* Adjust after meeting with Joe

* correct some typo's

* add missing punctuation

* change mutation wording

* fix lint error

* Improve line breaks and legend spacing

* Edit "Queries"

* rewrite computed-queries

* rewrite custom-updaes

* Edit "Mutations"

* add reading-on sections

* edit schema-awareness

* Add reference to request policies to "Document Caching" doc

* core-package.md update

* Edit "Errors"

* Edit "Basics" and "Concepts" index pages

* Add index page for "Advanced"

* update exchanges.md grammar

* update philosophy.md & stream-patterns.md

* move help.md

* Update basics code examples and mutation errors

* Remove "Common Questions" page

* Fix homepage styling

* Expand "Server-side Rendering" page with more content

* Update intro section of "Testing"

* Add disclaimer and intro to "Auto-populate Mutations"

* Add table styling in mdx.js, remove required column(s)

* Add "Graphcache" index page content

* Fix typo in server-side rendering doc

* Add special styling for inline-code table cells

* adjust computed-queries

* remove spacing from api docs

* Add more explanation to "Normalized Caching"

* elaborate on partial results

* Edit "Custom Updates"

* Add initial "Showcase" content

* Upgrade react-static-plugin-md-pages (fixes raw HTML)

* Adjust "Showcase" styling

* Edit "API @urql/core" docs

* Edit "urql (React)" docs

* Fix links edge cases (sidebar and markdown links with hashes)

* Resolve TODO comments in the docs pages

* Add initial "@urql/exchange-graphcache" API docs

* Add finalised "@urql/exchange-graphcache API" content

* Remove frontmatter from help.md

* Update yarn.lock file

* Remove "Common Questions" reference from docs index page

* Add new installation instructions to "Auto-populate Mutations"

* Fix table formatting on Graphcache API page

* Add deployment stages to Circle CI

Add surge CLI

Exclude private packages from build

Add deployment tasks to docs

Add deploy scripts

Set threading limit for react-static

Call surge with yarn

Publish to surge using commit hash in subdomain

* basic loader implementation

* Fix mobile menu max width

* Deploy to PR_NUM instead of COMMIT_HASH

* add scroll to table on small vw

* add white-space pre to Pre code blocks

Co-authored-by: Jovi De Croock <decroockjovi@gmail.com>
Co-authored-by: Will Golledge <will.golledge@formidable.com>

+52 -8
.circleci/config.yml
···
-
version: 2
+
version: 2.1
+
+
executors:
+
node:
+
docker:
+
- image: circleci/node:12-buster
aliases:
-
- &docker
-
- image: circleci/node:12-buster
- &yarn_cache
restore_cache:
name: Restore Yarn cache
···
jobs:
setup:
-
docker: *docker
+
executor: node
steps:
- checkout
- *yarn_cache
···
- ~/.cache/yarn
lint:
-
docker: *docker
+
executor: node
steps:
- checkout
- *yarn_cache
···
command: yarn run lint
typescript:
-
docker: *docker
+
executor: node
steps:
- checkout
- *yarn_cache
···
command: yarn run check
test:
-
docker: *docker
+
executor: node
steps:
- checkout
- *yarn_cache
···
command: yarn run test --maxWorkers=2
build:
-
docker: *docker
+
executor: node
parallelism: 4
steps:
- checkout
···
name: Build
command: yarn run build
+
staging_site:
+
executor: node
+
steps:
+
- checkout
+
- *yarn_cache
+
- *yarn
+
- run:
+
name: Deploy Staging Site
+
command: |
+
cd packages/site
+
yarn run build --staging
+
node scripts/deploy/surge.js
+
+
production_site:
+
executor: node
+
steps:
+
- run:
+
name: Install AWS CLI
+
command: sudo apt-get -y -qq install awscli
+
- checkout
+
- *yarn_cache
+
- *yarn
+
- run:
+
name: Deploy Production Site
+
command: |
+
cd packages/site
+
yarn run build
+
node scripts/deploy/aws.js
+
workflows:
version: 2
stable:
···
- build:
requires:
- setup
+
- staging_site:
+
filters:
+
branches:
+
ignore: master
+
requires:
+
- setup
+
- production_site:
+
filters:
+
branches:
+
only: master
+
requires:
+
- setup
+4 -10
docs/README.md
···
## Constituent Parts
`urql` can be understood as a collection of connected parts and packages. When you [get
-
started](./basics/setting-up-the-client.md) you'll only need to install a single package for your
+
started](./basics/getting-started.md) you'll only need to install a single package for your
framework of choice. We're then able to declaratively send GraphQL requests to our API.
All framework packages — like `urql` and `@urql/preact` — wrap the core package, which we can
···
interest.
- **Basics** is the section where we find the ["Getting Started"
-
guide](./basics/setting-up-the-client.md) and usage patterns for our framework of choice.
+
guide](./basics/getting-started.md) and usage patterns for our framework of choice.
- **Main Concepts** then explains more about how `urql` functions, what it's made up of, and covers
the main aspects of the `Client` and GraphQL clients in general, on the ["Philosophy"
page](./concepts/philosophy.md)
···
- **Graphcache** documents one of the most important addons to `urql`, which adds ["Normalized
Caching" support](./graphcache/normalized-caching.md) to the `Client` and enables more complex
use-cases, smarter caching, and more dynamic apps to function.
+
- **Showcase** aims to list some companies that use `urql`, third-party packages, and other helpful
+
resources, like tutorials or guides.
- **API** contains a detailed list of all helpers, utilities, components, and other parts of each of
`urql`'s packages, which may contain all details of each part and package.
-
-
Apart from these main sections there is also some additional content that doesn't fit into any of
-
the other sections.
-
-
- **Showcase** aims to list some companies that use `urql`, third-party packages, and other helpful
-
resources.
-
- **Common Questions** lists frequently asked questions and problems that we may encounter
-
infrequently (but shall answer nonetheless.)
We hope you grow to love `urql`!
+11
docs/advanced/README.md
···
---
# Advanced
+
+
In this chapter we'll dive into various topics of "advanced" `urql` usage. This is admittedly a
+
catch-all chapter of various use-cases that can only be covered after [the "Concepts"
+
chapter.](../coconcepts/README.md)
+
+
- **Subscriptions** covers how to use `useSubscription` and how to set up GraphQL subscriptions with
+
`urql`.
+
- **Server-side Rendering** guides us through how to set up server-side rendering and rehydration.
+
- **Auto-populate Mutations** presents the `populateExchange` addon which can make it easier to
+
update normalized data after mutations.
+
- **Testing** covers how to test components that use `urql` particularly in React.
+33 -10
docs/advanced/auto-populate-mutations.md
···
# Automatically populating Mutations
-
`populate` is an exchange for auto-populating fields in your mutations.
+
The `populateExchange` allows you to auto-populate selection sets in your mutations using the
+
`@populate` directive. In combination with [Graphcache](../graphcache/README.md) this is a useful
+
tool to update the data in your application automatically following a mutation, when your app grows
+
and it becomes harder to track all fields that have been queried before.
-
## How to use
+
> **NOTE:** The `populateExchange` is currently _experimental_! Certain patterns and usage paths
+
> like GraphQL field arguments aren't covered yet, and the exchange hasn't been extensively used
+
> yet.
-
As with any exchange, add this to your client.
+
## Installation and Setup
-
> Note: Populate needs to be declared before the cache in order to ensure the `populate` directive is replaced with the respective attributes.
+
The `populateExchange` can be installed via the `@urql/exchange-populate` package.
+
+
```sh
+
yarn add @urql/exchange-populate
+
# or
+
npm install --save @urql/exchange-populate
+
```
+
+
Afterwards we can set the `populateExchange` up by adding it to our list of `exchanges` in the
+
client options.
-
```tsx
+
```ts
+
import { createClient, dedupExchange, populateExchange, fetchExchange } from '@urql/core';
import { populateExchange } from '@urql/exchange-graphcache';
const client = createClient({
// ...
exchanges: [dedupExchange, populateExchange, cacheExchange, fetchExchange],
-
})
+
});
```
+
+
The `populateExchange` should be placed in front of the `cacheExchange`, especially if you're using
+
[Graphcache](../graphcache/README.md), since it won't understand the `@populate` directive on its
+
own. It should also be placed after the `dedupExchange` to avoid unnecessary work.
+
+
Adding the `populateExchange` now enables us to use the `@populate` directive in our mutations.
## Example usage
Consider the following queries which have been requested in other parts of your application:
-
```graphql
# Query 1
{
···
}
```
-
> Note: The above two mutations produce an identical GraphQL request.
-
### Choosing when to populate
You may not want to populate your whole mutation response. In order to reduce your payload, pass populate lower in your query.
···
### Using aliases
-
If you find yourself using multiple queries with variables, it may be necessary to [use aliases](https://graphql.org/learn/queries/#aliases) in order to allow merging of queries.
+
If you find yourself using multiple queries with variables, it may be necessary to
+
[use aliases](https://graphql.org/learn/queries/#aliases) in order to allow merging of queries.
+
+
> **Note:** This caveat may change in the future or this restriction may be lifted.
**Invalid usage**
+
```graphql
# Query 1
{
···
```
**Usage with aliases**
+
```graphql
# Query 1
{
+217 -22
docs/advanced/server-side-rendering.md
···
# Server-side Rendering
-
`urql` supports server-side rendering through the `ssrExchange`, this exchange
-
will gather all results that are fetched during the server side render and store
-
them. This data can then be used on the client when the application is being
-
hydrated.
+
In server-side rendered applications we often need to set our application up so that data will be
+
fetched on the server-side and later sent down to the client for hydration. `urql` supports this
+
through the `ssrExchange.`
+
+
## The SSR Exchange
-
## Setting up
+
The `ssrExchange` has two functions. On the server-side it's able to gather all results as they're
+
being fetched, which can then be serialised and sent to the client. On the client-side it's able to
+
use these serialised results to rehydrate and render the application without refetching this data.
-
To start out with the `ssrExchange` we have to add the exchange
+
To start out with the `ssrExchange` we have to add the exchange to our `Client`:
```js
-
const isClient = typeof window !== 'undefined';
+
import {
+
createClient,
+
dedupExchange,
+
cacheExchange,
+
fetchExchange,
+
ssrExchange
+
} from '@urql/core';
+
+
const isServerSide = typeof window === 'undefined';
+
+
// The `ssrExchange` must be initialised with `isClient` and `initialState`
const ssr = ssrExchange({
-
initialData: isClient ? window.URQL_DATA : undefined,
-
// This will need to be passed explicitly to ssrExchange:
-
isClient: !!isClient
+
isClient: !isServerSide,
+
initialState: !isServerSide ? window.__URQL_DATA__ : undefined,
});
-
const client = createClient({
+
const client createClient({
exchanges: [
dedupExchange,
cacheExchange,
-
ssr,
+
ssr, // Add `ssr` in front of the `fetchExchange`
fetchExchange,
]
-
})
+
});
+
```
+
+
The `ssrExchange` must be initialised with the `isClient` and `initialState` options. The `isClient`
+
option tells the exchange whether it's on the server- or client-side. In our example we use `typeof window` to determine this, but in Webpack environments you may also be able to use `process.browser`.
+
+
The `initialState` option should be set to the serialised data you retrieve on your server-side.
+
This data may be retrieved using methods on `ssrExchange()`. You can retrive the serialised data
+
after server-side rendering using `ssr.extractData()`:
+
+
```js
+
// Extract and serialise the data like so from the `ssr` instance
+
// we've previously created by calling `ssrExchange()`
+
const data = JSON.stringify(ssr.extractData());
+
+
const markup = ''; // The render code for our framework goes here
+
+
const html = `
+
<html>
+
<body>
+
<div id="root">${markup}</div>
+
<script>
+
window.__URQL_DATA__ = JSON.parse(${data});
+
</script>
+
</body>
+
</html>
+
`;
+
```
+
+
This will provide `__URQL_DATA__` globally which we've used in our first example to inject data into
+
the `ssrExchange` on the client-side.
+
+
Alternatively you can also call `restoreData` as long as this call happens synchronously before the
+
`client` starts receiving queries.
+
+
```js
+
const isServerSide = typeof window === 'undefined';
+
const ssr = ssrExchange({ isClient: !isServerSide });
+
+
if (!isServerSide) {
+
ssr.restoreData(window.__URQL_DATA__);
+
}
+
```
+
+
## Using `react-ssr-prepass`
+
+
In the previous examples we've set up the `ssrExchange`, however with React this still requires us
+
to manually execute our queries before rendering a server-side React app [using `renderToString`
+
or `renderToNodeStream`](https://reactjs.org/docs/react-dom-server.html#rendertostring).
+
+
For React, `urql` has a "Suspense mode" that [allows data fetching to interrupt
+
rendering](https://reactjs.org/docs/concurrent-mode-suspense.html). However, suspense is currently
+
not supported by React during server-side rendering.
+
+
Using [the `react-ssr-prepass` package](https://github.com/FormidableLabs/react-ssr-prepass) however,
+
we can implement a prerendering step before we let React server-side render, which allows us to
+
automatically fetch all data that the app requires with Suspense. This technique is commonly
+
referred to as a "two-pass approach", since our React element is traversed twice.
+
+
To set this up, first we'll install `react-ssr-prepass`. It has a peer dependency on `react-is`
+
and `react`.
+
+
```sh
+
yarn add react-ssr-prepass react-is react-dom
+
# or
+
npm install --save react-ssr-prepass react-is react-dom
+
```
+
+
Next, we'll modify our server-side code and add `react-ssr-prepass` in front of `renderToString`.
+
+
```jsx
+
import { renderToString } from 'react-dom/server';
+
import prepass from 'react-ssr-prepass';
+
+
import {
+
createClient,
+
dedupExchange,
+
cacheExchange,
+
fetchExchange,
+
ssrExchange
+
} from 'urql';
+
+
const handleRequest = async (req, res) => {
+
// ...
+
const ssr = ssrExchange({ isClient: false });
+
+
const client createClient({
+
suspense: true, // This activates urql's Suspense mode on the server-side
+
exchanges: [dedupExchange, cacheExchange, ssr, fetchExchange]
+
});
+
+
const element = (
+
<Provider value={client}>
+
<App />
+
</Provider>
+
);
+
+
// Using `react-ssr-prepass` this prefetches all data
+
await prepass(element);
+
// This is the usual React SSR rendering code
+
const markup = renderToString(element);
+
// Extract the data after prepass and rendering
+
const data = JSON.stringify(ssr.extractData());
+
+
res.status(200).send(`
+
<html>
+
<body>
+
<div id="root">${markup}</div>
+
<script>
+
window.__URQL_DATA__ = JSON.parse(${data});
+
</script>
+
</body>
+
</html>
+
`);
+
};
```
-
The `ssrExchange` allows you to pass in an object with two options, one being `isClient`,
-
this option tells the exchange you're in the browser rather than in the process of a server-side
-
render. The other option is called `initialState`, this being a mapping of `operationKey` to `operationResult`,
-
this could for instance be `window.__URQL_DATA__`.
+
It's important to set enable the `suspense` option on the `Client`, which switches it to support
+
React suspense.
-
The returned exchange will have two methods available on it, `restoreData` and `extractData`, during your ssr
-
we extract the gathered data and serialize it into a `<script>` tag inside head, this should bind it to a unique
-
property on `window` for instance `window.__URQL_DATA__`.
+
### With Preact
-
When `isClient` is true it will use the `initialState` to restore the gathered data.
+
If you're using Preact instead of React, there's a drop-in replacement package for
+
`react-ssr-prepass`, which is called `preact-ssr-prepass`. It only has a peer dependency on Preact
+
and we can install it like so:
-
## Next
+
```sh
+
yarn add preact-ssr-prepass preact
+
# or
+
npm install --save preact-ssr-prepass preact
+
```
+
+
All above examples for `react-ssr-prepass` will still be the exact same, except that instead of
+
using the `urql` package we'll have to import from `@urql/preact`, and instead of `react-ssr-prepass`
+
we'll have to import from. `preact-ssr-prepass`.
+
+
## Next.js
+
+
If you're using [Next.js](https://nextjs.org/) you can save yourself a lot of work by using
+
`next-urql`. The `next-urql` package includes setup for `react-ssr-prepass` already, which automates
+
a lot of the complexity of setting up server-side rendering with `urql`.
We have a custom integration with [`Next.js`](https://nextjs.org/), being [`next-urql`](https://github.com/FormidableLabs/next-urql)
this integration contains convenience methods specifically for `Next.js`.
These will simplify the above setup for SSR.
+
+
To setup `next-urql`, first we'll install `next-urql` with `react-is` and `isomorphic-unfetch` as
+
peer dependencies:
+
+
```sh
+
yarn add next-urql react-is isomorphic-unfetch
+
# or
+
npm install --save next-urql react-is isomorphic-unfetch
+
```
+
+
The peer dependency on `react-is` is inherited from `react-ssr-prepass` requiring it, and the peer
+
dependency on `isomorphic-unfetch` exists, since `next-urql` automatically injects it as a `fetch`
+
polyfill.
+
+
We're now able to wrap any page or `_app.js` using the `withUrqlClient` higher-order component. If
+
we wrap `_app.js` we won't have to wrap any individual page, but we also won't be able to make use
+
of Next's ["Automatic Static
+
Optimization"](https://nextjs.org/docs/advanced-features/automatic-static-optimization).
+
+
```js
+
// pages/index.js
+
import React from 'react';
+
import Head from 'next/head';
+
import { withUrqlClient } from 'next-urql';
+
+
const Index = () => {
+
const [result] = useQuery({
+
query: '{ test }',
+
});
+
+
// ...
+
};
+
+
export default withUrqlClient(ctx => ({
+
// ...add your Client options here
+
url: 'http://localhost:3000/graphql',
+
}))(Index);
+
```
+
+
This will automatically set up server-side rendering on the page. The `withUrqlClient` higher-order
+
component function accepts the usual `Client` options as an argument. This may either just be an
+
object or a function that receives the Next.js' `getInitialProps` context.
+
+
One added caveat is that these options may not include the `exchanges` option because `next-urql`
+
injects the `ssrExchange` automatically at the right location. If you're setting up custom exchanges
+
you'll need to instead provide them in a custom `mergeExchanges` function as the second argument:
+
+
```js
+
import { dedupExchange, cacheExchange, fetchExchange } from '@urql/core';
+
+
import { withUrqlClient } from 'next-urql';
+
+
// Modify this array to include custom exchanges:
+
const mergeExchanges = ssrExchange => [dedupExchange, cacheExchange, ssrExchange, fetchExchange];
+
+
export default withUrqlClient({ url: 'http://localhost:3000/graphql' }, mergeExchanges)(Index);
+
```
+6 -9
docs/advanced/testing.md
···
order: 4
---
-
# Testing
-
When testing your components, you're likely going to want to check arguments and force different states for your components using Urql.
+
Testing with `urql` can be done in a multitude of ways. The most effective and straightforward
+
method is to mock the `Client` to force your components into a fixed state during testing.
-
> **Note:** Examples demonstrate the _React hooks_ version of Urql being used but underlying patterns apply to all implementations.
+
The following examples demonstrate this method of testing for React and the `urql` package only,
+
however the pattern itself can be adapted for any framework-bindings of `urql`.
## Mocking the client
···
name: 'Carla',
};
-
wrapper
-
.find('input')
-
.simulate('change', { currentTarget: { value: variables.name } });
+
wrapper.find('input').simulate('change', { currentTarget: { value: variables.name } });
wrapper.find('button').simulate('click');
expect(mockClient.executeMutation).toBeCalledTimes(1);
-
expect(mockClient.executeMutation).toBeCalledWith(
-
expect.objectContaining({ variables })
-
);
+
expect(mockClient.executeMutation).toBeCalledWith(expect.objectContaining({ variables }));
});
```
+10 -1
docs/api/README.md
···
# API
-
<!-- List of packages and subpages -->
+
`urql` is a collection of multiple packages. You'll likely be using one of the framework bindings
+
package or exchange packages, which are all listed in this section.
+
+
Most of these packages will refer to or use utilities and types from the `@urql/core` package. [Read
+
more about the core package on the "Core Package" page.](../concepts/core-package.md)
+
+
- [`@urql/core` API docs](./core.md)
+
- [`urql` React API docs](./urql.md)
+
- [`@urql/preact` Preact API docs](./preact.md)
+
- [`@urql/exchange-graphcache` API docs](./graphcache.md)
+265 -180
docs/api/core.md
···
# @urql/core
-
## The Client and related types
+
The `@urql/core` package is the basis of all framework bindings. Every bindings package,
+
like [`urql` for React](./urql.md) or [`@urql/preact`](./preact.md) will reuse the core logic and
+
reexport all exports from `@urql/core`.
+
Therefore if you're not accessing utilities directly, aren't in a Node.js environment, and are using
+
framework bindings, you'll likely want to import from your framework bindings package directly.
-
### Client (class)
+
[Read more about `urql`'s core on the "Core Package" page.](../concepts/core-package.md)
-
The client manages all operations and ongoing requests to the exchange pipeline.
-
It accepts a bunch of inputs when it's created
+
## Client
-
| Input | Type | Description |
-
| ------------ | ---------------------------------- | --------------------------------------------------------------------------------------------------------------- |
-
| url | `string` | The GraphQL API URL as used by `fetchExchange` |
-
| fetchOptions | `RequestInit \| () => RequestInit` | Additional `fetchOptions` that `fetch` in `fetchExchange` should use to make a request |
-
| fetch | `typeof fetch` | An alternative implementation of `fetch` that will be used by the `fetchExchange` instead of `window.fetch` |
-
| suspense | `?boolean` | Activates the experimental React suspense mode, which can be used during server-side rendering to prefetch data |
-
| exchanges | `Exchange[]` | An array of `Exchange`s that the client should use instead of the list of `defaultExchanges` |
+
The `Client` manages all operations and ongoing requests to the exchange pipeline.
+
It accepts several options on creation.
-
`urql` also exposes `createClient()` that is just a convenient alternative to calling `new Client()`.
+
`@urql/core` also exposes `createClient()` that is just a convenient alternative to calling `new Client()`.
-
#### .executeQuery(), .executeSubscription(), and .executeMutation()
+
| Input | Type | Description |
+
| --------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+
| exchanges | `Exchange[]` | An array of `Exchange`s that the client should use instead of the list of `defaultExchanges` |
+
| url | `string` | The GraphQL API URL as used by `fetchExchange` |
+
| fetchOptions | `RequestInit \| () => RequestInit` | Additional `fetchOptions` that `fetch` in `fetchExchange` should use to make a request |
+
| fetch | `typeof fetch` | An alternative implementation of `fetch` that will be used by the `fetchExchange` instead of `window.fetch` |
+
| suspense | `?boolean` | Activates the experimental React suspense mode, which can be used during server-side rendering to prefetch data |
+
| requestPolicy | `?RequestPolicy` | Changes the default request policy that will be used. By default this will be `cache-first`. |
+
| preferGetMethod | `?boolean` | This is picked up by the `fetchExchange` and will force all queries (not mutations) to be sent using the HTTP GET method instead of POST. |
+
| maskTypename | `?boolean` | Enables the `Client` to automatically apply the `maskTypename` utility to all `data` on [`OperationResult`s](#operationresult). This makes the `__typename` properties non-enumerable. |
-
These methods are used by `<Query>` & `useQuery()`, `<Subscription>` & `useSubscription()`,
-
and `<Mutation>` & `useMutation()` respectively.
+
### client.executeQuery
-
They accept a `GraphQLRequest` object as their first argument and optionally
-
a partial `OperationContext` as their second.
+
Accepts a [`GraphQLRequest`](#graphqlrequest) and optionally `Partial<OperationContext>`, and returns a
+
[`Source<OperationResult>`](#operationresult) — a stream of query results that can be subscribed to.
-
Internally they then create an `Operation` and call `.executeRequestOperation()` with
-
the `Operation`. This then returns a `Source<OperationResult>`, i.e. a stream of
-
`OperationResult`s.
+
Internally, subscribing to the returned source will create an [`Operation`](#operation), with
+
`operationName` 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.
-
#### .query and .mutation
+
- [Instead of using this method directly, you may want to use the `client.query` shortcut
+
instead.](#clientquery)
+
- [See `createRequest` for a utility that creates `GraphQLRequest` objects.](#createrequest)
-
These two methods accept a `query`, `variables` and a `context`, these two methods
-
are really similar to the above in the sense that they return you a `Source<OperationResult>`
-
you can subscribe to. The difference is that this returned value has a method on it called
-
`toPromise`, when invoked it will convert the `Source` to a one-time promise. These methods
-
are ideal for SSR, like for example the `getInitialProps` method in [Next.js](https://nextjs.org/).
+
A feature that is specific to `client.executeQuery` and isn't supported by
+
`client.executeSubscription` and `client.executeMutation` is polling. You may optionally pass a
+
`pollInterval` option on the `OperationContext` object, which will instruct the query to reexecute
+
repeatedly in the interval you pass.
-
#### .executeRequestOperation()
+
### client.executeSubscription
-
This method accepts an `Operation` and handles the flow of said `Operation`. Every `Operation`
-
that is executed must pass through this method.
+
This is functionally the same as `client.executeQuery`, but creates operations for subscriptions
+
instead, with `operationName` set to `'mutation'`.
-
It creates a filtered `Source<OperationResult>` that only contains the `OperationResult`s
-
relevant to this `Operation` by filtering by the operation `key` and track the subscriptions
-
to this `Source`.
+
### client.executeMutation
-
This is important as a cache exchange can call `reexecuteOperation` to inform the
-
client about an invalidation. Whenever an operation needs to be updated with new
-
network data, it's important to know whether any component is still interested in
-
this operation.
+
This is functionally the same as `client.executeQuery`, but creates operations for mutations
+
instead, with `operationName` set to `'mutation'`.
+
+
A mutation source is always guaranteed to only respond with a single [`OperationResult`](#operationresult) and then complete.
+
+
### client.query
+
+
This is a shorthand method for [`client.executeQuery`](#clientexecutequery), which accepts a query
+
(`DocumentNode | string`) and variables separately and creates a [`GraphQLRequest`](#graphqlrequest) [`createRequest`](#createrequest) automatically.
+
+
The returned `Source<OperationResult>` will also have an added `toPromise` method so the stream can
+
be conveniently converted to a promise.
+
+
```js
+
import { pipe, subscribe } from 'wonka';
+
+
const { subscribe } = pipe(
+
client.query('{ test }', {
+
/* vars */
+
}),
+
subscribe(result => {
+
console.log(result); // OperationResult
+
})
+
);
+
+
// or with toPromise, which also limits this to one result
+
client
+
.query('{ test }', {
+
/* vars */
+
})
+
.then(result => {
+
console.log(result); // OperationResult
+
});
+
```
+
+
[Read more about how to use this API on the "Core Package"
+
page.](../concepts/core-package.md#one-off-queries-and-mutations)
+
+
### client.mutation
+
+
This is similar to [`client.query`](#clientquery), but dispatches mutations instead.
-
To track this, this method ensures that a mapping is updated that counts up
-
for each subscription to the `Source` and counts down for each unsubscription.
+
[Read more about how to use this API on the "Core Package"
+
page.](../concepts/core-package.md#one-off-queries-and-mutations)
-
The `Operation` that has been passed to this method will be dispatched
-
when the first subscription is started. When the last subscription unsubscribes
-
from the returned source, this method will ensure that a `teardown` operation
-
is dispatched.
+
#### client.reexecuteOperation
-
> _Note:_ This does not apply to mutations, which are one-off calls and
-
> hence aren't shared, cancelled, or tracked in the cache.
+
This method is commonly used in _Exchanges_ to reexecute an [`Operation`](#operation) on the
+
`Client`. It will only reexecute when there are still subscribers for the given
+
[`Operation`](#operation).
-
The return value is the filtered `Source<OperationResult>`.
+
For an example, this method is used by the `cacheExchange` when an
+
[`OperationResult`](#operationresult) is invalidated in the cache and needs to be refetched.
-
#### .reexecuteOperation()
+
## CombinedError
-
This method accepts an `Operation` and will dispatch this `Operation` if there
-
are any subscriptions from `executeRequestOperation`'s `Source<OperationResult>`
-
to this particular `Operation`.
+
The `CominedError` is used in `urql` to normalize network errors and `GraphQLError`s if anything
+
goes wrong during a GraphQL request.
-
This is called by `cacheExchange` when an `Operation`'s `OperationResult` is
-
invalidated in the cache.
+
| Input | Type | Description |
+
| ------------- | -------------------------------- | --------------------------------------------------------------------------------- |
+
| networkError | `?Error` | An unexpected error that might've occured when trying to send the GraphQL request |
+
| graphQLErrors | `?Array<string \| GraphQLError>` | GraphQL Errors (if any) that were returned by the GraphQL API |
+
| response | `?any` | The raw response object (if any) from the `fetch` call |
-
#### .createRequestOperation()
+
[Read more about errors in `urql` on the "Error" page.](../basics/errors.md)
-
This is called by the `executeQuery`, `executeSubscription` and `executeMutation`
-
methods to create `Operation`s. It accepts:
+
## Types
-
- `OperationType`
-
- `GraphQLRequest`
-
- and; the optional partial `OperationContext` (`Partial<OperationContext>`)
+
### GraphQLRequest
-
It returns an `Operation`.
+
This often comes up as the **input** for every GraphQL request.
+
It consists of `query` and optionally `variables`.
-
#### .dispatchOperation()
+
| Prop | Type | Description |
+
| --------- | -------------- | --------------------------------------------------------------------------------------------------------------------- |
+
| key | `number` | A unique key that identifies this exact combination of `query` and `variables`, which is derived using a stable hash. |
+
| query | `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. |
-
This method dispatches an `Operation` to the exchange pipeline. This is only
-
used directly by the Client and shouldn't normally be called externally, except
-
when the tracking logic of active `Operation`s needs to be bypassed.
+
The `key` property is a hash of both the `query` and the `variables`, to uniquely
+
identify the request. When `variables` are passed it is ensured that they're stabily stringified so
+
that the same variables in a different order will result in the same `key`, since variables are
+
order-independent in GraphQL.
-
These `Operation`s are streamed from the `operations$: Source<Operation>` stream.
-
The results of all exchanges are similarly output to `results$: Source<OperationResult>`.
+
[A `GraphQLRequest` may be manually created using the `createRequest` helper.](#createrequest)
-
### OperationType (type)
+
### OperationType
This determines what _kind of operation_ the exchanges need to perform.
This is one of:
···
any ongoing operations with the same key as the `'teardown'` operation that is
received.
-
### RequestPolicy (type)
+
### Operation
+
+
The input for every exchange that informs GraphQL requests.
+
It extends the [GraphQLRequest](#graphqlrequest) type and contains these additional properties:
+
+
| Prop | Type | Description |
+
| ------------- | ------------------ | --------------------------------------------- |
+
| operationName | `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 dervied from the GraphQL `DocumentNode`, but instead a type of operation, like `'query'` or
+
> `'teardown'`
+
+
### RequestPolicy
This determines the strategy that a cache exchange should use to fulfill an operation.
When you implement a custom cache exchange it's recommended that these policies are
···
- `'network-only'`
- `'cache-and-network'`
-
### GraphQLRequest (type)
+
[Read more about request policies on the "Queries" page.](../basics/queries.md#request-policies)
-
This often comes up as the **input** for every GraphQL request.
-
It consists of `query` and optional `variables`.
+
### OperationContext
-
| Prop | Type | Description | Required | |
-
| --------- | -------------- | --------------------------------------------------------------------------------------------------------------------- | -------- | --- |
-
| key | `number` | An optional [request policy](/basics/querying-data#request-policy) that should be used specifying the cache strategy. | No |
-
| query | `DocumentNode` | The query to be executed. Accepts as a plain string query or GraphQL DocumentNode. | Yes |
-
| variables | `object` | The variables to be used with the GraphQL request. | No |
+
The context often carries options or metadata for individual exchanges, but may also contain custom
+
data that can be passed from almost all API methods in `urql` that deal with
+
[`Operation`s](#operation).
-
&nbsp;
+
Some of these options are set when the `Client` is initialised, so in the following list of
+
properties you'll likely see some options that exist on the `Client` as well.
-
The `key` property is a hash of both the `query` and the `variables`, to uniquely
-
identify the request.
-
-
### OperationContext (type)
+
| Prop | Type | Description |
+
| --------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
+
| fetchOptions | `?RequestInit \| (() => RequestInit)` | An optional [request policy](/basics/querying-data#request-policy) that should be used specifying the cache strategy. |
+
| fetch | `typeof fetch` | An alternative implementation of `fetch` that will be used by the `fetchExchange` instead of `window.fetch` |
+
| requestPolicy | `RequestPolicy` | An optional [request policy](/basics/querying-data#request-policy) that should be used specifying the cache strategy. |
+
| url | `string` | The GraphQL endpoint |
+
| pollInterval | `?number` | Every `pollInterval` milliseconds the query will be refetched. |
+
| meta | `?OperationDebugMeta` | Metadata that is only available in development for devtools. |
+
| suspense | `?boolean` | Whether suspense is enabled. |
+
| preferGetMethod | `?number` | Instructs the `fetchExchange` to use HTTP GET for queries. |
-
This type is used to give an operation additional metadata and information.
-
-
| Prop | Type | Description | Always Present | |
-
| --------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------- | -------------- | --- |
-
| fetchOptions | `RequestInit \| (() => RequestInit)` | An optional [request policy](/basics/querying-data#request-policy) that should be used specifying the cache strategy. | No |
-
| requestPolicy | `RequestPolicy` | An optional [request policy](/basics/querying-data#request-policy) that should be used specifying the cache strategy. | Yes |
-
| url | `string` | The GraphQL endpoint | Yes |
-
| pollInterval | `number` | Every `pollInterval` milliseconds the query will be refetched. | No |
-
| meta | `OperationDebugMeta` | Metadata that is only available in development for devtools. | No |
-
| suspense | `boolean` | Whether suspense is enabled. | No |
-
| preferGetMethod | `number` | Whether to use HTTP GET for queries. | No |
-
-
&nbsp;
-
-
It contains a lot of the above mentioned Client options and also `requestPolicy`.
-
It accepts additional, untyped parameters that can be used to send more
+
It also accepts additional, untyped parameters that can be used to send more
information to custom exchanges.
-
### Operation (type)
+
### OperationResult
-
The input for every exchange that informs GraphQL requests.
-
It contains all properties in the [GraphQLRequest](#graphqlrequest-type) type, as well as the additional properties below.
+
The result of every GraphQL request, i.e. an `Operation`. It's very similar to what comes back from
+
a typical GraphQL API, but slightly enriched and normalized.
-
| Prop | Type | Description | Required | |
-
| ------------- | ------------------ | --------------------------------------------- | -------- | --- |
-
| operationName | `OperationType` | The type of GraphQL operation being executed. | Yes |
-
| context | `OperationContext` | Additional metadata passed to exchange. | Yes |
+
| Prop | Type | Description |
+
| ---------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
+
| operation | `Operation` | The operation that this is a result for |
+
| data | `?any` | Data returned by the specified query |
+
| error | `?CombinedError` | A [`CombinedError`](#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. |
-
### OperationResult (type)
+
### ExchangeInput
-
The result of every GraphQL request, i.e. an `Operation`.
-
It's very similar to what comes back from a typical GraphQL API, but
-
slightly enriched.
+
This is the input that an [`Exchange`](#exchange) receives when it's initialized by the
+
[`Client`](#client)
-
| Prop | Type | Description | Always Present |
-
| ---------- | --------------------- | ----------------------------------------------------- | -------------- |
-
| operation | `Operation` | The operation that this is a result for | Yes |
-
| data | Generic | Data returned by the specified query | No |
-
| error | `CombinedError` | The query error | No |
-
| extensions | `Record<string, any>` | Extensions that the GraphQL server may have returned. | No |
+
| Input | Type | Description |
+
| ------- | ------------ | ----------------------------------------------------------------------------------------------------------------------- |
+
| forward | `ExchangeIO` | The unction responsible for receiving an observable operation and returning a result |
+
| client | `Client` | The URQL application-wide client library. Each execute method starts a GraphQL request and returns a stream of results. |
-
### CombinedError (class)
+
### Exchange
-
| Input | Type | Description | Required |
-
| ------------- | ------------------------------- | --------------------------------------------------------------------------------- | -------- |
-
| networkError | `Error` | An unexpected error that might've occured when trying to send the GraphQL request | No |
-
| graphQLErrors | `Array<string \| GraphQLError>` | GraphQL Errors (if any) that were returned by the GraphQL API | No |
-
| response | `any` | The raw response object (if any) from the `fetch` call | No |
+
An exchange represents abstractions of small chunks of logic in `urql`.
+
They're small building blocks and similar to "middleware".
-
These are both inputs and properties on the `CombinedError`. Additionally it exposes a default `message`
-
that combines all errors it has received.
-
-
This is on every `OperationResult` that has one or more errors and groups the usual `errors` property
-
that a GraphQL result might have normally.
-
-
## Exchanges and their utilities
-
-
### ExchangeInput (type)
-
-
| Input | Type | Description | Required |
-
| ------- | ------------ | ----------------------------------------------------------------------------------------------------------------------- | -------- |
-
| forward | `ExchangeIO` | The unction responsible for receiving an observable operation and returning a result | Yes |
-
| client | `Client` | The URQL application-wide client library. Each execute method starts a GraphQL request and returns a stream of results. | Yes |
+
[Read more about _Exchanges_ on the "Exchanges" page.](../concepts/exchanges.md)
-
### ExchangeIO (type)
-
-
A function that receives a stream of operations and must return a stream
-
of results.
+
An exchange is defined to be a function that receives [`ExchangeInput`](#exchangeinput) and returns
+
an `ExchangeIO` function. The `ExchangeIO` function in turn will receive a stream of operations, and
+
must return a stream of results. If the exchange is purely transforming data, like the
+
`dedupExchange` for instance, it'll call `forward`, which is the next Exchange's `ExchangeIO`
+
function to get a stream of results.
```js
type ExchangeIO = (Source<Operation>) => Source<OperationResult>;
-
```
-
-
### Exchange (type)
-
-
Similar to `redux-observable`'s epics, kind of related to Apollo's links,
-
also somehow similar to Express' middleware.
-
-
```js
type Exchange = ExchangeInput => ExchangeIO;
```
-
This works since every exchange receives `forward` with the `ExchangeInput`.
-
Exchanges can therefore be chained. They can alter and filter `Operation`s
-
that go into the next exchange, and they can alter, filter, or return
-
`OperationResult`s that are returned.
+
[If you haven't yet seen `Source`, read more about "Stream
+
Patterns".](../concepts/stream-patterns.md)
-
### composeExchanges (function)
+
## Exchanges
-
This utility accepts multiple exchanges and composes them into a single one.
-
It chains them in the order that they're given, left to right.
-
-
```js
-
function composeExchanges(Exchange[]): Exchange;
-
```
-
-
This can be used to combine some exchanges and is also used by `Client`
-
to handle the `exchanges` input.
-
-
### cacheExchange (Exchange)
+
### cacheExchange
-
The `cacheExchange` as [described in the Basics section](https://formidable.com/open-source/urql/docs/basics#cacheexchange).
-
It's of type `Exchange`.
+
The `cacheExchange` as [described on the "Document Caching" page.](../basics/document-caching.md). It's of type `Exchange`.
-
### subscriptionExchange (Exchange factory)
+
### subscriptionExchange
-
The `subscriptionExchange` as [described in the Basics section](https://formidable.com/open-source/urql/docs/basics#subscriptions).
-
It's of type `Options => Exchange`.
+
The `subscriptionExchange` as [described on the "Subscriptions" page.](../advanced/subscriptions.md). It's of type `Options => Exchange`.
It accepts a single input: `{ forwardSubscription }`. This is a function that
receives an enriched operation and must return an Observable-like object that
streams `GraphQLResult`s with `data` and `errors`.
-
### ssrExchange (Exchange factory)
+
The `forwardSubscription` function is commonly connected to the [`subscriptions-transport-ws`
+
package](https://github.com/apollographql/subscriptions-transport-ws).
-
The `ssrExchange` as [described in the Basics section](https://formidable.com/open-source/urql/docs/basics#server-side-rendering).
+
### ssrExchange
+
+
The `ssrExchange` as [described on the "Server-side Rendering"
+
page.](../advanced/server-side-rendering.md).
It's of type `Options => Exchange`.
It accepts two inputs, `initialState` which is completely
···
`cacheExchange`, but before any _asynchronous_ Exchange like
the `fetchExchange`.
-
### debugExchange (Exchange)
+
### debugExchange
An exchange that writes incoming `Operation`s to `console.log` and
writes completed `OperationResult`s to `console.log`.
-
### dedupExchange (Exchange)
+
### dedupExchange
An exchange that keeps track of ongoing `Operation`s that haven't returned had
a corresponding `OperationResult` yet. Any duplicate `Operation` that it
receives is filtered out if the same `Operation` has already been received
and is still waiting for a result.
-
### fallbackExchangeIO (ExchangeIO)
+
### fetchExchange
+
+
The `fetchExchange` of type `Exchange` is responsible for sending operations of type `'query'` and
+
`'mutation'` to a GraphQL API using `fetch`.
+
+
## Utilities
+
+
### stringifyVariables
+
+
This function is a variation of `JSON.stringify` that sorts any object's keys that is being
+
stringified to ensure that two objects with a different order of keys will be stabily stringified to
+
the same string.
+
+
```js
+
stringifyVariables({ a: 1, b: 2 }); // {"a":1,"b":2}
+
stringifyVariables({ b: 2, a: 1 }); // {"a":1,"b":2}
+
```
+
+
### createRequest
+
+
This utility accepts a GraphQL query of type `string | DocumentNode` and optionally an object of
+
variables, and returns a [`GraphQLRequest` object](#graphqlrequest).
+
+
Since the [`client.executeQuery`](#clientexecutequery) and other execute methods only accept
+
[`GraphQLRequest`s](#graphqlrequest), this helper is commonly used to create that request first. The
+
[`client.query`](#clientquery) and [`client.mutation`](#clientmutation) methods use this helper as
+
well to create requests.
+
+
The helper takes are of creating a unique `key` for the `GraphQLRequest`. This is a hash of the
+
`query` and `variables` if they're passed. The `variables` will be stringified using
+
[`stringifyVariables`](#stringifyvariables), which outputs a stable JSON string.
+
+
Additionally, this utility will ensure that the `query` reference will remain stable. This means
+
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.
+
+
### makeResult
-
This is an `ExchangeIO` function that the `Client` adds on after all
-
exchanges. This function is responsible from filtering `teardown` operations
-
out of the output and also warns you of unhandled `operationName`s which
-
can occur when a subscription is used without adding a `subscriptionExchange`.
+
This is a helper function that converts a GraphQL API result to an
+
[`OperationResult`](#operationresult).
+
+
It accepts an [`Operation`](#operation), the API result, and optionally the original `FetchResponse`
+
for debugging as arguments, in that order.
+
+
### makeErrorResult
+
+
This is a helper function that creates an [`OperationResult`](#operationresult) for GraphQL API
+
requests that failed with a generic or network error.
+
+
It accepts an [`Operation`](#operation), the error, and optionally the original `FetchResponse`
+
for debugging as arguments, in that order.
+
+
### formatDocument
+
+
This utility is used by the [`cacheExchange`](#cacheexchange) and by
+
[Graphcache](../graphcache/README.md) to add `__typename` fields to GraphQL `DocumentNode`s.
+
+
### maskTypename
+
+
This utility accepts a GraphQL `data` object, like `data` on [`OperationResult`s](#operationresult)
+
and marks every `__typename` property as non-enumerable.
-
### fetchExchange (Exchange)
+
The [`formatDocument`](#formatdocument) is often used by `urql` automatically and adds `__typename`
+
fields to all results. However, this means that data can often not be passed back into variables or
+
inputs on mutations, which is a common use-case. This utility hides these fields which can solves
+
this problem.
-
The `fetchExchange` as [described in the Basics section](https://formidable.com/open-source/urql/docs/basics#fetchexchange).
-
It's of type `Exchange`.
+
It's used by the [`Client`](#client) when the `maskTypename` option is enabled.
-
### defaultExchanges (Exchange[])
+
### defaultExchanges
-
An array of the default exchanges that the `Client` uses when it wasn't passed
-
an `exchanges` option.
+
This is an array of the default `Exchange`s that the `Client` uses when the `exchanges` option isn't
+
passed.
```js
const defaultExchanges = [dedupExchange, cacheExchange, fetchExchange];
```
+
+
### composeExchanges
+
+
This utility accepts an array of `Exchange`s and composes them into a single one.
+
It chains them in the order that they're given, left to right.
+
+
```js
+
function composeExchanges(Exchange[]): Exchange;
+
```
+
+
This can be used to combine some exchanges and is also used by [`Client`](#client)
+
to handle the `exchanges` input.
+424
docs/api/graphcache.md
···
# @urql/exchange-graphcache
+
The `@urql/exchange-graphcache` package contains an addon `cacheExchange` for `urql` that may be
+
used to replace the default [`cacheExchange`](./core.md#cacheexchange), which switches `urql` from
+
using ["Document Caching"](../basics/document-caching.md) to ["Normalized
+
Caching"](../graphcache/normalized-caching.md).
+
+
[Read more about how to use and configure _Graphcache_ in the "Graphcache"
+
section](../graphcache/README.md)
+
+
## cacheExchange
+
+
The `cacheExchange` function, as exported by `@urql/exchange-graphcache`, accepts a single object of
+
options and returns an [`Exchange`](./core.md#exchange).
+
+
| Input | Description |
+
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+
| _keys_ | A mapping of key generator functions for types that are used to override the default key generation that _Graphcache_ uses to normalize data for given types. |
+
| _resolvers_ | A nested mapping of resolvers, which are used to override the record or entity that _Graphcache_ resolves for a given field for a type. |
+
| _updates_ | A nested mapping of updater functions for mutation and subscription fields, which may be used to add side-effects that update other parts of the cache when the given subscription or mutation field is written to the cache. |
+
| _optimistic_ | A mapping of mutation fields to resolvers that may be used to provide _Graphcache_ with an optimistic result for a given mutation field that should be applied to the cached data temporarily. |
+
| _schema_ | A serialized GraphQL schema that is used by _Graphcache_ to resolve partial data, to resolve interfaces and enums, and to provide helpful warnings. |
+
+
### `keys` option
+
+
This is a mapping of typenames to `KeyGenerator` functions.
+
+
```ts
+
interface KeyingConfig {
+
[typename: string]: (data: Data) => null | string;
+
}
+
```
+
+
It may be used to alter how _Graphcache_ generates the key it uses for normalization for individual
+
types. The key generator function may also always return `null` when a type should always be
+
embedded.
+
+
[Read more about how to set up `keys` in the "Key Generation" section of the "Normalized Caching"
+
page.](../graphcache/normalized-caching.md#key-generation)
+
+
### `resolvers` option
+
+
This configuration is a mapping of typenames to field names to `Resolver` functions.
+
A resolver may be defined to override the entity or record that a given field on a type should
+
resolve on the cache.
+
+
```ts
+
interface ResolverConfig {
+
[typeName: string]: {
+
[fieldName: string]: Resolver;
+
};
+
}
+
```
+
+
A `Resolver` receives four arguments when it's called: `parent`, `args`, `cache`, and
+
`info`.
+
+
| Argument | Type | Description |
+
| -------- | -------- | ----------------------------------------------------------------------------------------------------------- |
+
| parent | `Data` | The parent entity that the given field is on. |
+
| args | `object` | The arguments for the given field the updater is executed on. |
+
| cache | `Cache` | The cache using which data can be read or written. [See `Cache`.](#cache) |
+
| info | `Info` | Additional metadata and information about the current operation and the current field. [See `Info`.](#info) |
+
+
[Read more about how to set up `resolvers` on the "Computed Queries"
+
page.](../graphcache/computed-queries.md)
+
+
### `updates` option
+
+
The `updates` configuration is a mapping of `'Mutation' | 'Subscription'` to field names to
+
`UpdateResolver` functions. An update resolver may be defined to add side-effects that run when a
+
given mutation field or subscription field is written to the cache. These side-effects are helpful
+
to update data in the cache that is implicitly changed on the GraphQL API, that _Graphcache_ can't
+
know about automatically.
+
+
```ts
+
interface UpdatesConfig {
+
Mutation: {
+
[fieldName: string]: UpdateResolver;
+
};
+
Subscription: {
+
[fieldName: string]: UpdateResolver;
+
};
+
}
+
```
+
+
An `UpdateResolver` receives four arguments when it's called: `result`, `args`, `cache`, and
+
`info`.
+
+
| Argument | Type | Description |
+
| -------- | -------- | ----------------------------------------------------------------------------------------------------------- |
+
| result | `any` | Always the entire `data` object from the mutation or subscription. |
+
| args | `object` | The arguments for the given field the updater is executed on. |
+
| cache | `Cache` | The cache using which data can be read or written. [See `Cache`.](#cache) |
+
| info | `Info` | Additional metadata and information about the current operation and the current field. [See `Info`.](#info) |
+
+
[Read more about how to set up `updates` on the "Custom Updates"
+
page.](../graphcache/custom-updates.md)
+
+
### `optimistic` option
+
+
The `optimistic` configuration is a mapping of Mutation field names to `OptimisticMutationResolver`
+
functions, which return optimistic mutation results for given fields. These results are used by
+
_Graphcache_ to optimistically update the cache data, which provides an immediate and temporary
+
change to its data before a mutation completes.
+
+
```ts
+
interface OptimisticMutationConfig {
+
[mutationFieldName: string]: OptimisticMutationResolver;
+
}
+
```
+
+
A `OptimisticMutationResolver` receives three arguments when it's called: `variables`, `cache`, and
+
`info`.
+
+
| Argument | Type | Description |
+
| --------- | -------- | ----------------------------------------------------------------------------------------------------------- |
+
| variables | `object` | The variables that the given mutation received. |
+
| cache | `Cache` | The cache using which data can be read or written. [See `Cache`.](#cache) |
+
| info | `Info` | Additional metadata and information about the current operation and the current field. [See `Info`.](#info) |
+
+
[Read more about how to set up `optimistic` on the "Custom Updates"
+
page.](../graphcache/custom-updates.md)
+
+
### `schema` option
+
+
The `schema` option may be used to pass a `IntrospectionQuery` data to _Graphcache_, in other words
+
it's used to provide schema information to it. This schema is then used to resolve and return
+
partial results when querying, which are results that the cache can partially resolve as long as no
+
required fields are missing.
+
+
[Read more about how to use the `schema` option on the "Schema Awareness"
+
page.](../graphcache/schema-awareness.md)
+
+
## Cache
+
+
An instance of the `Cache` interface is passed to every resolvers and updater function. It may be
+
used to read cached data or write cached data, which may be used in combination with the
+
[`cacheExchange` configuration](#cacheexchange) to alter the default behaviour of _Graphcache_.
+
+
### keyOfEntity
+
+
The `cache.keyOfEntity` method may be called with a partial `Data` object and will return the key
+
for that object, or `null` if it's not keyable.
+
+
An object may not be keyable if it's missing the `__typename` or `id` (which falls back to `_id`)
+
fields. This method does take the [`keys` configuration](#keys-option) into account.
+
+
```js
+
cache.keyOfEntity({ __typename: 'Todo', id: 1 }); // 'Todo:1'
+
cache.keyOfEntity({ __typename: 'Query' }); // 'Query'
+
cache.keyOfEntity({ __typename: 'Unknown' }); // null
+
```
+
+
There's an alternative method, `cache.keyOfField` which generates a key for a given field. This is
+
only rarely needed but similar to `cache.keyOfEntity`. This method accepts a field name and
+
optionally a field's arguments.
+
+
```js
+
cache.keyOfField('todo'); // 'todo'
+
cache.keyOfField('todo', { id: 1 }); // 'todo({"id":1})'
+
```
+
+
Internally, these are the keys that records and links are stored on per entity.
+
+
### resolve
+
+
This method retrieves a value or link for a given field, given a partially keyable `Data` object or
+
entity, a field name, and optionally the field's arguments. Internally this method accesses the
+
cache by using `cache.keyOfEntity` and `cache.keyOfField`.
+
+
```js
+
// This may resolve a link:
+
cache.resolve({ __typename: 'Query' }, 'todo', { id: 1 }); // 'Todo:1'
+
+
// This may also resolve records / scalar values:
+
cache.resolve({ __typename: 'Todo', id: 1 }, 'id'); // 1
+
+
// You can also chain multiple calls to `cache.resolve`!
+
cache.resolve(cache.resolve({ __typename: 'Query' }, 'todo', { id: 1 }), 'id'); // 1
+
```
+
+
As you can see in the last example of this code snippet, the `Data` object can also be replaced by
+
an entity key, which makes it possible to pass a key from `cache.keyOfEntity` or another call to
+
`cache.resolve` instead of the partial entity.
+
+
> **Note:** Because `cache.resolve` may return either a scalar value or another entity key, it may
+
> be dangerous to use in some cases. It's a good idea to make sure first whether the field you're
+
> reading will be a key or a value.
+
+
There's an alternative method, `cache.resolveFieldByKey` which accepts a field key that may be
+
generated using `cache.keyOfField`.
+
+
```js
+
cache.resolveFieldByKey({ __typename: 'Query' }, cache.keyOfField('todo', { id: 1 })); // 'Todo:1'
+
```
+
+
This specialised method is likely only going to be useful in combination with
+
[`cache.inspectFields`](#inspectfields).
+
+
### inspectFields
+
+
The `cache.inspectFields` method may be used to interrogate the cache about all available fields on
+
a specific entity. It accepts a partial entity or an entity key, like [`cache.resolve`](#resolve)'s
+
first argument.
+
+
When calling the method this returns an array of `FieldInfo` objects, one per field (including
+
differing arguments) that is known to the cache. The `FieldInfo` interface has three properties:
+
`fieldKey`, `fieldName`, and `arguments`:
+
+
| Argument | Type | Description |
+
| --------- | ---------------- | ------------------------------------------------------------------------------- |
+
| fieldName | `string` | The field's name (without any arguments, just the name) |
+
| arguments | `object \| null` | The field's arguments, or `null` if the field doesn't have any arguments |
+
| fieldKey | `string` | The field's cache key, which is similar to what `cache.keyOfField` would return |
+
+
This works on any given entity. When calling this method the cache works in reverse on its data
+
structure, by parsing the entity's individual field keys.
+
+
```js
+
cache.inspectFields({ __typename: 'Query' });
+
+
/*
+
[
+
{ fieldName: 'todo', arguments: { id: 1 }, fieldKey: 'id({"id":1})' },
+
{ fieldName: 'todo', arguments: { id: 2 }, fieldKey: 'id({"id":2})' },
+
...
+
]
+
*/
+
```
+
+
### readFragment
+
+
`cache.readFragment` accepts a GraphQL `DocumentNode` as the first argument and a partial entity or
+
an entity key as the second, like [`cache.resolve`](#resolve)'s first argument.
+
+
The method will then attempt to read the entity according to the fragment entirely from the cached
+
data. If any data is uncached and missing it'll return `null`.
+
+
```js
+
import gql from 'graphql-tag';
+
+
cache.readFragment(
+
gql`
+
fragment _ on Todo {
+
id
+
text
+
}
+
`,
+
{ id: 1 }
+
); // Data or null
+
```
+
+
Note that the `__typename` may be left out on the partial entity, since the `__typename` is already
+
present on the fragment itself.
+
+
[Read more about using `readFragment` on the ["Computed Queries"
+
page.](../graphcache/computed-queries.md#reading-a-fragment)
+
+
### readQuery
+
+
The `cache.readQuery` method is similar to `cache.readFragment`, but instead of reading a fragment
+
from cache, it reads an entire query. The only difference between how these two methods are used is
+
`cache.readQuery`'s input, which is an object instead of two arguments.
+
+
The method accepts a `{ query, variables }` object as the first argument, where `query` may either
+
be a `DocumentNode` or a `string` and variables may optionally be an object.
+
+
```js
+
cache.readQuery({
+
query: `
+
query ($id: ID!) {
+
todo(id: $id) { id, text }
+
}
+
`,
+
variables: {
+
id: 1
+
}
+
); // Data or null
+
```
+
+
[Read more about using `readQuery` on the ["Computed Queries"
+
page.](../graphcache/computed-queries.md#reading-a-query)
+
+
### writeFragment
+
+
Corresponding to [`cache.readFragment`](#readfragments), the `cache.writeFragment` method allows
+
data in the cache to be updated.
+
+
The arguments for `cache.writeFragment` are identical to [`cache.readFragment`](#readfragment),
+
however the second argument, `data`, should not only contain properties that are necessary to derive
+
an entity key from the given data, but also the fields that will be written:
+
+
```js
+
import gql from 'graphql-tag';
+
+
cache.writeFragment(
+
gql`
+
fragment _ on Todo {
+
text
+
}
+
`,
+
{ id: 1, text: 'New Todo Text' }
+
);
+
```
+
+
In the example we can see that the `writeFragment` method returns `undefined`. Furthermore we pass
+
`id` in our `data` object so that an entity key can be written, but the fragment itself doesn't have
+
to include these fields.
+
+
[Read more about using `writeFragment` on the ["Custom Updates"
+
page.](../graphcache/custom-updates.md#cachewritefragment)
+
+
### updateQuery
+
+
Similarly to [`cache.writeFragment`](#writefragment), there's an analogous method for
+
[`cache.readQuery`](#readquery) that may be used to update query data.
+
+
The `cache.updateQuery` method accepts the same `{ query, variables }` object input as its first
+
argument, which is the query we'd like to write to the cache. As a second argument the method
+
accepts an updater function. This function will be called with the query data that is already in the
+
cache (which may be `null` if the data is uncached) and must return the new data that should be
+
written to the cache.
+
+
```js
+
const TodoQuery = `
+
query ($id: ID!) {
+
todo(id: $id) { id, text }
+
}
+
`;
+
+
cache.updateQuery({ query: TodoQuery, variables: { id: 1 } }, data => {
+
if (!data) return null;
+
data.todo.text = 'New Todo Text';
+
return data;
+
});
+
```
+
+
As we can see, our updater may return `null` to cancel updating any data, which we do in case the
+
query data is uncached.
+
+
We can also see that data can simply be mutated and doesn't have to be altered immutably. This is
+
because all data from the cache is already a deep copy and hence we can do to it whatever we want.
+
+
[Read more about using `updateQuery` on the "Custom Updates"
+
page.](../graphcache/custom-updates.md#cacheupdatequery)
+
+
### invalidate
+
+
The `cache.invalidate` method can be used to delete (i.e. "evict") an entity from the cache
+
entirely. This will cause it to disappear from all queries in _Graphcache_.
+
+
Since deleting an entity will lead to some queries containing missing and uncached data, calling
+
`invalidate` may lead to additional GraphQL requests being sent, unless you're using [_Graphcache_'s
+
"Schema Awareness" feature](../graphcache/schema-awareness.md), which takes optional fields into
+
account.
+
+
This method accepts a partial entity or an entity key as its only argument, similar to
+
[`cache.resolve`](#resolve)'s first argument.
+
+
```js
+
cache.invalidate({ __typename: 'Todo', id: 1 }); // Invalidates Todo:1
+
```
+
+
## Info
+
+
This is a metadata object that is passed to every resolver and updater function. It contains basic
+
information about the current GraphQL document and query, and also some information on the current
+
field that a given resolver or updater is called on.
+
+
| Argument | Type | Description |
+
| -------------- | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
+
| parentTypeName | `string` | The field's parent entity's typename |
+
| parentKey | `string` | The field's parent entity's cache key (if any) |
+
| parentFieldKey | `string` | The current key's cache key, which is the parent entity's key combined with the current field's key (This is mostly obsolete) |
+
| fieldName | `string` | The current field's name |
+
| fragments | `{ [name: string]: FragmentDefinitionNode }` | A dictionary of fragments from the current GraphQL document |
+
| variables | `object` | The current GraphQL operation's variables (may be an empty object) |
+
| partial | `?boolean` | This may be set to `true` at any point in time (by your custom resolver or by _Graphcache_) to indicate that some data is uncached and missing |
+
| optimistic | `?boolean` | This is only `true` when an optimistic mutation update is running |
+
+
> **Note:** Using `info` is regarded as a last resort. Please only use information from it if
+
> there's no other solution to get to the metadata you need. We don't regard the `Info` API as
+
> stable and may change it with a simple minor version bump.
+
+
## The `/extras` import
+
+
The `extras` subpackage is published with _Graphcache_ and contains helpers and utilities that don't
+
have to be included in every app or aren't needed by all users of _Graphcache_.
+
All utilities from extras may be imported from `@urql/exchange-graphcache/extras`.
+
+
Currently the `extras` subpackage only contains the [pagination resolvers that have been mentioned
+
on the "Computed Queries" page.](../graphcache/computed-queries.md#pagination)
+
+
### simplePagination
+
+
Accepts a single object of optional options and returns a resolver that can be inserted into the
+
[`cacheExchange`'s](#cacheexchange) [`resolvers` configuration.](#resolvers-option)
+
+
| Argument | Type | Description |
+
| -------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+
| offsetArgument | `?string` | The field arguments's property, as passed to the resolver, that contains the current offset, i.e. the number of items to be skipped. Defaults to `'skip'`. |
+
| limitArgument | `?string` | The field arguments's property, as passed to the resolver, that contains the current page size limit, i.e. the number of items on each page. Defaults to `'limit'`. |
+
+
Once set up, the resulting resolver is able to automatically concatenate all pages of a given field
+
automatically. Queries to this resolvers will from then on only return the infinite, combined list
+
of all pages.
+
+
[Read more about `simplePagination` on the "Computed Queries"
+
page.](../graphcache/computed-queries.md#simple-pagination)
+
+
### relayPagination
+
+
Accepts a single object of optional options and returns a resolver that can be inserted into the
+
[`cacheExchange`'s](#cacheexchange) [`resolvers` configuration.](#resolvers-option)
+
+
| Argument | Type | Description |
+
| --------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+
| mergeMode | `'outwards' \| 'inwards'` | With Relay pagination, pages can be queried forwards and backwards using `after` and `before` cursors. This option defines whether pages that have been quiered backwards should be concatenated before (outwards) or after (inwards) all pages that have been queried forwards. |
+
+
Once set up, the resulting resolver is able to automatically concatenate all pages of a given field
+
automatically. Queries to this resolvers will from then on only return the infinite, combined list
+
of all pages.
+
+
[Read more about `relayPagnation` on the "Computed Queries"
+
page.](../graphcache/computed-queries.md#relay-pagination)
+2 -1
docs/api/preact.md
···
# @urql/preact
-
Please refer to [the React API section](/api/urql) for details of the Preact API.
+
The `@urql/preact` API is the same as the React `urql` API.
+
Please refer to [the "urql" API docs](./urql.md) for details on the Preact API.
+97 -128
docs/api/urql.md
···
# React API
-
## Hooks
+
## useQuery
-
### useQuery
+
Accepts a single required options object as an input with the following properties:
-
#### useQuery Parameters
-
Accepts a single required `options` object as an input with the following properties:
+
| Prop | 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. |
+
| requestPolicy | `?RequestPolicy` | An optional [request policy](./core.md#requestpolicy) that should be used specifying the cache strategy. |
+
| pause | `?boolean` | A boolean flag instructing [execution to be paused](../basics/queries.md#pausing-usequery). |
+
| pollInterval | `?number` | Every `pollInterval` milliseconds the query will be reexecuted. |
+
| context | `?object` | Holds the contextual information for the query. |
-
| Prop | Type | Description | Required | |
-
| ------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------- | -------- |
-
| query | `string \| DocumentNode` | The query to be executed. Accepts as a plain string query or GraphQL DocumentNode. | Yes |
-
| variables | `object` | The variables to be used with the GraphQL request. | No |
-
| requestPolicy | `RequestPolicy` | An optional [request policy](/basics/querying-data#request-policy) that should be used specifying the cache strategy. | No |
-
| pause | `boolean` | A boolean flag instructing `Query` to pause execution of the subsequent query operation. | No |
-
| pollInterval | `number` | Every `pollInterval` milliseconds the query will be refetched. | No |
-
| context | `object` | Holds the contextual information for the query. | No |
+
This hook returns a tuple of the shape `[result, executeQuery]`.
-
#### useQuery Returned Data
+
- The `result` is an object with the shape of an [`OperationResult`](./core.md#operationresult) with
+
an added `fetching: boolean` property, indicating whether the query is currently being fetched.
+
- The `executeQuery` function optionally accepts
+
[`Partial<OperationContext>`](./core.md#operationcontext) and reexecutes the current query when
+
it's called. When `pause` is set to `true` this executes the query, overriding the otherwise
+
paused hook.
-
A tuple is returned with item one being the current query's state object and item two being an `executeQuery` function.
+
[Read more about how to use the `useQuery` API on the "Queries" page.](../basics/queries.md)
-
The shape of the current state is an [OperationResult Type](/api/core#operationresult-type)
+
## useMutation
-
&nbsp;
+
Accepts a single `query` argument of type `string | DocumentNode` and returns a tuple of the shape
+
`[result, executeMutation]`.
-
The `executeQuery` function optionally accepts a partial `OperationContext`.
+
- The `result` is an object with the shape of an [`OperationResult`](./core.md#operationresult) with
+
an added `fetching: boolean` property, indicating whether the mutation is currently being executed.
+
- The `executeMutation` function accepts variables and optionally
+
[`Partial<OperationContext>`](./core.md#operationcontext) and may be used to start executing a
+
mutation. It returns a `Promise` resolving to an [`OperationResult`](./core.md#operationresult).
-
[More information on how to use this hook can be found in the Basics section.](/basics/querying-data#queries)
+
[Read more about how to use the `useMutation` API on the "Mutations" page.](../basics/mutations.md)
-
### useMutation
+
## useSubscription
-
#### useMutation Parameters
+
Accepts a single required options object as an input with the following properties:
-
Accepts a single `query` argument of type `string`.
+
| Prop | 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. |
+
| pause | `?boolean` | A boolean flag instructing [execution to be paused](../basics/queries.md#pausing-usequery). |
+
| context | `?object` | Holds the contextual information for the query. |
-
#### useMutation Returned Data
-
-
A tuple is returned with item one being the current query's state object and item two being an `executeQuery` function.
-
-
The shape of the current state is an [OperationResult Type](/api/core#operationresult-type)
-
&nbsp;
-
-
The `executeQuery` function optionally accepts a partial `OperationContext`.
-
-
[More information on how to use this hook can be found in the Basics section.](/basics/mutating-data#mutations)
-
-
### useSubscription
-
-
#### useSubscription Parameters
-
Accepts an `options` object as the required first parameter, and a second optional parameter that is the subscription's handler function.
-
-
The `options` object's property breakdown:
-
-
| Prop | Type | Description | Required | |
-
| --------- | ------------------------ | ---------------------------------------------------------------------------------- | -------- |
-
| query | `string \| DocumentNode` | The query to be executed. Accepts as a plain string query or GraphQL DocumentNode. | Yes |
-
| variables | `object` | The variables to be used with the GraphQL request. | No |
-
| context | `object` | Holds the contextual information for the query. | No |
-
-
&nbsp;
-
-
The subscription handler's type signature:
+
The hook optionally accepts a second argument, which may be a handler function with a type signature
+
of:
```js
-
type SubscriptionHandler<T, R> = (prev: R | undefined, data: T) => R;
+
type SubscriptionHandler<T, R> = (previousData: R | undefined, data: T) => R;
```
-
This means that the subscription handler receives the previous data or undefined
-
and the current, incoming subscription event data.
+
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`.
-
#### useSubscription Returned Data
+
This hook returns a tuple of the shape `[result, executeQuery]`.
-
The shape of the current state is an [OperationResult Type](/api/core#operationresult-type) without the first `operation` prop.
+
- The `result` is an object with the shape of an [`OperationResult`](./core.md#operationresult).
+
- The `executeSubscription` function optionally accepts
+
[`Partial<OperationContext>`](./core.md#operationcontext) and restarts the current subscription when
+
it's called. When `pause` is set to `true` this starts the subscription, overriding the otherwise
+
paused hook.
-
More information can be found in the [Subscriptions](/advanced/subscriptions) section.
+
Since a subscription may proactively closed by the server, the additional `fetching: boolean`
+
property on the `result` may update to `false` when the server ends the subscription.
+
By default `urql` is not able to start subscriptions, since this requires some additional setup.
-
## Components
+
[Read more about how to use the `useSubscription` API on the "Subscriptions"
+
page.](../advanced/subscriptions.md)
-
### Query
+
## Query Component
-
#### Props
+
This component is a wrapper around [`useQuery`](#usequery), exposing a [render prop
+
API](https://reactjs.org/docs/render-props.html) for cases where hooks aren't desirable.
-
| Prop | Type | Description | Required |
-
| ------------- | -------------------------- | ----------------------------------------------------------------------------------------------------- | -------- |
-
| query | `string` | The GraphQL request's query | Yes |
-
| variables | `object` | The GraphQL request's variables | Yes |
-
| context | `?object` | The GraphQL request's context | No |
-
| requestPolicy | `?RequestPolicy` | An optional request policy that should be used | No |
-
| pause | `?boolean` | A boolean flag instructing `Query` to pause execution of the subsequent query operation | No |
-
| pollInterval | `?number` | Every `pollInterval` milliseconds the query will be refetched | No |
-
| children | `RenderProps => ReactNode` | A function that follows the typical render props pattern. The shape of the render props is as follows | N/A |
-
-
#### Render Props
-
-
| Prop | Type | Description |
-
| ------------ | ----------------------------------- | ------------------------------------------------------------------------------------------------------- |
-
| fetching | `boolean` | Whether the `Query` is currently waiting for a GraphQL result |
-
| data | `?any` | The GraphQL request's result |
-
| error | `?CombinedError` | The `CombinedError` containing any errors that might've occured |
-
| extensions | `?Record<string, any>` | Optional extensions that the GraphQL server may have returned. |
-
| executeQuery | `Partial<OperationContext> => void` | A function that can force the operation to be sent again with the given context (Useful for refetching) |
-
-
&nbsp;
-
-
[More information on how to use this hook can be found in the Basics section.](/basics/querying-data#queries)
+
The API of the `Query` component mirrors the API of [`useQuery`](#usequery). The props that `<Query>`
+
accepts are the same as `useQuery`'s options object.
-
### Mutation
-
-
#### Props
+
A function callback must be passed to `children` that receives the query result and must return a
+
React element. The second argument of the hook's tuple, `executeQuery` is passed as an added property
+
on the query result.
-
| Prop | Type | Description |
-
| -------- | -------------------------- | ----------------------------------------------------------------------------------------------------- |
-
| query | `string` | The GraphQL request's query |
-
| children | `RenderProps => ReactNode` | A function that follows the typical render props pattern. The shape of the render props is as follows |
+
## Mutation Component
-
#### Render Props
+
This component is a wrapper around [`useMutation`](#usemutation), exposing a [render prop
+
API](https://reactjs.org/docs/render-props.html) for cases where hooks aren't desirable.
-
| Prop | Type | Description |
-
| --------------- | ------------------------------------------------------------------ | ---------------------------------------------------------------- |
-
| fetching | `boolean` | Whether the `Mutation` is currently waiting for a GraphQL result |
-
| data | `?any` | The GraphQL request's result |
-
| error | `?CombinedError` | The `CombinedError` containing any errors that might've occured |
-
| extensions | `?Record<string, any>` | Optional extensions that the GraphQL server may have returned. |
-
| executeMutation | `(variables: object, context?: Partial<OperationContext>) => void` | A function that accepts variables and starts the mutation |
+
The `Mutation` component accepts a `query` prop and a function callback must be passed to `children`
+
that receives the mutation result and must return a React element. The second argument of
+
`useMutation`'s returned tuple, `executeMutation` is passed as an added property on the mutation
+
result object.
-
[More information on how to use this hook can be found in the Basics section.](/basics/mutating-data#mutations)
+
## Subscription Component
-
### Subscription
+
This component is a wrapper around [`useSubscription`](#usesubscription), exposing a [render prop
+
API](https://reactjs.org/docs/render-props.html) for cases where hooks aren't desirable.
-
[More information on how to use this component can be found in the Basics section.](https://formidable.com/open-source/urql/docs/basics#subscriptions)
+
The API of the `Subscription` component mirrors the API of [`useSubscription`](#usesubscription).
+
The props that `<Mutation>` accepts are the same as `useSubscription`'s options object, with an
+
added, optional `handler` prop that may be passed, which for the `useSubscription` hook is instead
+
the second argument.
-
#### Props
+
A function callback must be passed to `children` that receives the subscription result and must
+
return a React element. The second argument of the hook's tuple, `executeSubscription` is passed as
+
an added property on the subscription result.
-
| Prop | Type | Description |
-
| --------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
-
| query | `string` | The GraphQL subscription's query |
-
| variables | `object` | The GraphQL subscriptions' variables |
-
| context | `?Partial<OperationContext>` | The GraphQL subscriptions' context |
-
| handler | `undefined \| (prev: R \| undefined, data: T) => R` | The handler that should combine/update the subscription's data with incoming data |
-
| children | `RenderProps => ReactNode` | A function that follows the typical render props pattern. The shape of the render props is as follows |
+
## Context
-
#### Render Props
+
`urql` is used in React by adding a provider around where the [`Client`](./core.md#client) is
+
supposed to be used. Internally this means that `urql` creates a
+
[React Context](https://reactjs.org/docs/context.html).
-
| Prop | Type | Description |
-
| ---------- | ---------------------- | --------------------------------------------------------------- |
-
| fetching | `boolean` | Whether the `Subscription` is currently ongoing |
-
| data | `?any` | The GraphQL subscription's data |
-
| error | `?CombinedError` | The `CombinedError` containing any errors that might've occured |
-
| extensions | `?Record<string, any>` | Optional extensions that the GraphQL server may have returned. |
+
All created parts of this context are exported by `urql`, namely:
-
More information can be found in the [Subscriptions](/advanced/subscriptions) section.
+
- `Context`
+
- `Provider`
+
- `Consumer`
-
### Context
+
To keep examples brief, `urql` creates a default client with the `url` set to `'/graphql'`. This
+
client will be used when no `Provider` wraps any of `urql`'s hooks. However, to prevent this default
+
client from being used accidentally, a warning is output in the console for the default client.
-
`urql` comes with the two context components `Consumer` and `Provider` as returned
-
by React's `createContext` utility. It also exports the `Context` itself which can
-
be used in combination with the `useContext` hook.
+
### useClient
-
E.g.
+
`urql` also exports a `useClient` hook, which is a convenience wrapper like the following:
```js
-
<App>
-
<UrqlProvider>
-
<UrqlConsumer>
-
{urqlData => (
-
<MyComponent data={urqlData} />
-
)}
-
</UrqlConsumer>
-
</UrqlProvider>
-
</App>
+
import React from 'react';
+
import { Context } from 'urql';
+
+
const useClient = () => React.useContext(Context);
```
+
+
However, this hook is also responsible for outputting the default client warning that's mentioned
+
above, and should thus be preferred over manually using `useContext` with `urql`'s `Context`.
docs/assets/logos/egghead.png

This is a binary file and will not be displayed.

docs/assets/logos/github.png

This is a binary file and will not be displayed.

docs/assets/logos/tripadvisor.png

This is a binary file and will not be displayed.

+12 -4
docs/basics/README.md
···
# Basics
-
In this chapter we'll explain the basics of `urql`, you'll learn
-
[how to set up your client](./setting-up-the-client.md). You'll
-
see how you [query data](./querying-data.md) and find out how we
-
can alter that data by [mutating it](./mutating-data.md).
+
In this chapter we'll explain the basics of `urql` and how to get started with using it without any
+
prior knowledge. It's split into multiple pages that are best read in order.
+
- **Getting started** covers how to install the package and set it up.
+
- **Queries** explains how to write your first GraphQL queries with `urql`.
+
- **Mutations** follows up with how to define mutations.
+
- **Document Caching** explains the default cache mechanism of `urql`, as opposed to the [Normalized
+
Cache](../graphcache/normalized-caching.md) which is not the default.
+
- **Errors** contains information on error handling in `urql`.
+
+
After reading "Basics" you may want to [read the "Concepts" chapter of the
+
documentation](../concepts/README.md) as it explains the motivation and architecture that drives
+
`urql`.
+11
docs/basics/document-caching.md
···
This is an aggressive form of cache invalidation. However, it works well for content-driven sites,
although it doesn't deal with normalized data or IDs.
+
## Request Policies
+
+
[We previously covered request policies on the "Queries" page.](./queries.md)
+
+
The _request policy_ that is defined will alter what the default document cache does. By default the
+
cache will prefer cached results and will otherwise send a request, which is called `cache-first`,
+
but there's also `cache-and-network`, `cache-only`, and `network-only`.
+
+
[Read more about which request policies are available in the API
+
docs.](../api/core.md#requestpolicy-type)
+
## Document Cache Gotchas
This cache has a small trade-off! If we request a list of data and the API returns an empty list,
+29
docs/basics/errors.md
···
+
---
+
title: Errors
+
order: 4
+
---
+
+
# Error handling
+
+
When we use a GraphQL API there are two kinds of errors we may encounter: Network Errors and GraphQL
+
Errors from the API. Since it's common to encounter either of them in development, there's a
+
[`CombinedError`](../api/core.md#combinederror-class) class that can hold and abstract either.
+
+
We may encounter a `CombinedError` when using `urql` wherever an `error` may be returned, typically
+
in results from the API. The `CombinedError` can have one of two properties that describe what went
+
wrong.
+
+
- The `networkError` property will contain any error that stopped `urql` from making a network
+
request.
+
- The `graphQLErrors` property may be an array that contains [normalized `GraphQLError`s as they
+
were returned in the `errors` array from a GraphQL API.](https://graphql.org/graphql-js/error/)
+
+
Additionally, the `message` of the error will be generated and combined from the errors for
+
debugging purposes.
+
+
![Combined errors](../assets/urql-combined-error.png)
+
+
It's worth noting that an `error` can coexit and be returned in a successful request alongside
+
`data`. This is because in GraphQL a query can have partially failed but still contain some data.
+
In that case `CombinedError` will be passed to us with `graphQLErrors`, while `data` may still be
+
set.
+97
docs/basics/getting-started.md
···
+
---
+
title: Getting started
+
order: 0
+
---
+
+
# Getting started
+
+
In the ["Introduction"](./README.md) we read how `urql` consists of multiple constituent parts that
+
make up a GraphQL client. Hence there are multiple packages — one for each framework we officially
+
support — that you'll get started with.
+
+
## React & Preact
+
+
This "Getting Started" guide covers how to install and set up `urql` and the Client, for React and
+
Preact. Since the `urql` and `@urql/preact` packages share most of their API one-to-one, when
+
reading the documentation on React, all examples are essentially the same, except that we'll use the
+
`@urql/preact` package instead of the `urql` package for Preact.
+
+
### Installation
+
+
Installing `urql` is as quick as you'd expect and you won't need any other packages to get started
+
with at first. We'll install the package with our package manager of choice.
+
+
```sh
+
yarn add urql graphql
+
# or
+
npm install --save urql graphql
+
```
+
+
To use urql with Preact, we have to install `@urql/preact` instead of `urql` and import from
+
that package instead. Otherwise all examples for Preact will be the same.
+
+
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, which is why we'll need
+
to install `graphql` as well.
+
+
Both the `urql` packages and `graphql` follow [semantic versioning](https://semver.org) and `urql`
+
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` and `@urql/preact` packages export 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';
+
+
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 object or a
+
function returning an object to this option.
+
+
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 React & Preact we will have to provide it via the
+
[Context API](https://reactjs.org/docs/context.html). This may be done with the help of
+
the `Provider` export.
+
+
```jsx
+
import { createClient, Provider } from 'urql';
+
+
const client = createClient({
+
url: 'http://localhost:3000/graphql',
+
});
+
+
const App = () => (
+
<Provider value={client}>
+
<YourRoutes />
+
</Provider>
+
);
+
```
+
+
Now every component and element inside and under the `Provider` are able to use GraphQL queries that
+
will be sent to our API.
-61
docs/basics/mutating-data.md
···
-
---
-
title: Mutations
-
order: 2
-
---
-
-
# Mutations
-
-
Now that we know how to query our data we'll also need to know
-
how to mutate that data.
-
We'll see how we can dispatch mutations to our back-end and view
-
the result of these mutations.
-
-
## React/Preact
-
-
`urql` exposes the `useMutation` hook and the `Mutation` component to send out mutations.
-
-
### Sending a mutation
-
-
Let's set up a mutation allowing us to change the name of our todo.
-
-
```jsx
-
const Todo = ({ id, title }) => {
-
const [updateTodoResult, updateTodo] = useMutation(`
-
mutation ($id: ID!, $title: String!) {
-
updateTodo (id: $id, title: $title) {
-
id
-
}
-
}
-
`);
-
}
-
```
-
-
Similar to the `useQuery` output, `useMutation` returns a tuple. The first item in the tuple being our `result`
-
containing: `fetching`, `error`, and `data`. At this point in time, no mutation has been performed.
-
To mutate the data we first have to invoke the second item in the tuple - the function here named `updateTodo`.
-
-
### Using the mutation result
-
-
When calling this `updateTodo` function we have two ways of getting the response from the server,
-
we can get it from `updateTodoResult` or we can await the promise returned from our mutation trigger function.
-
-
```jsx
-
const Todo = ({ id, title }) => {
-
const [updateTodoResult, updateTodo] = useMutation(`
-
mutation ($id: ID!, $title: String!) {
-
updateTodo (id: $id, title: $title) {
-
id
-
}
-
}
-
`);
-
-
const submit = (newTitle) => {
-
updateTodo({ variables: { id, title: newTitle } }).then((data) => {
-
// this data variable will be the same as updateTodoResult.data
-
});
-
}
-
}
-
```
-
-
This means that we can react to a completed todo in the body of the `.then` or
-
with a `useEffect`.
+101
docs/basics/mutations.md
···
+
---
+
title: Mutations
+
order: 2
+
---
+
+
# Mutations
+
+
In this chapter we'll learn how to execute mutations and view their results.
+
Sending mutations to our GraphQL API is similar to what we've learned about sending queries to our
+
API [previously on the "Queries" page.](./queries.md)
+
+
## React & Preact
+
+
This guide covers how to query data with React and Preact, which share almost the same API.
+
+
Both libraries offer a `useMutation` hook and a `Mutation` component. The latter accepts the same
+
parameters but we won't cover it in this guide. [Look it up in the API docs if you prefer
+
render-props components.](../api/urql.md#components)
+
+
### Sending a mutation
+
+
Let's pick up an example in an imaginary todo items GraphQL API and dive into an example!
+
We'll set up a mutation that _updates_ a todo item.
+
+
```jsx
+
const UpdateTodo = `
+
mutation ($id: ID!, $title: String!) {
+
updateTodo (id: $id, title: $title) {
+
id
+
}
+
}
+
`;
+
+
const Todo = ({ id, title }) => {
+
const [updateTodoResult, updateTodo] = useMutation(UpdateTodo);
+
};
+
```
+
+
Similar to the `useQuery` output, `useMutation` returns a tuple. The first item in the tuple again
+
contains `fetching`, `error`, and `data` — it's identical since this is a common pattern of how
+
`urql` presents _operation results_.
+
+
Unlike the `useQuery` hook, the `useMutation` hook doesn't execute automatically. At this point in
+
our example, no mutation will be performed. To execute our mutation we instead have to call the
+
execute function — `updateTodo` in our example — which is the second item in the tuple.
+
+
### Using the mutation result
+
+
When calling our `updateTodo` function we have two ways of getting to the result as it comes back
+
from our API. We can either use the first value of the returned tuple — our `updateTodoResult` — or
+
we can use the promise that `updateTodo` returns.
+
+
```jsx
+
const Todo = ({ id, title }) => {
+
const [updateTodoResult, updateTodo] = useMutation(UpdateTodo);
+
+
const submit = newTitle => {
+
const variables = { id, title: newTitle || '' };
+
updateTodo(variables).then(result => {
+
// The result is almost identical to `updateTodoResult` with the exception
+
// of `result.fetching` not being set.
+
});
+
};
+
};
+
```
+
+
This is useful when your UI has to display progress or results on the mutation, and the returned
+
promise is particularly useful when you're adding side-effects that run after the mutation has
+
completed.
+
+
### Handling mutation errors
+
+
It's worth noting that the promise we receive when calling the execute function will never
+
reject. Instead it will always return a promise that resolves to a result.
+
+
If you're checking for errors, you should use `result.error` instead, which will be set
+
to a `CombinedError` when any kind of errors occurred while executing your mutation.
+
[Read more about errors on our "Errors" page.](./errors.md)
+
+
```jsx
+
const Todo = ({ id, title }) => {
+
const [updateTodoResult, updateTodo] = useMutation(UpdateTodo);
+
+
const submit = newTitle => {
+
const variables = { id, title: newTitle || '' };
+
updateTodo(variables).then(result => {
+
if (result.error) {
+
console.error('Oh no!', result.error);
+
}
+
});
+
};
+
};
+
```
+
+
### Reading on
+
+
There are some more tricks we can use with `useMutation`. [Read more about its API in the API docs for
+
it.](../api/urql.md#usemutation)
+
+
[On the next page we'll learn about "Document Caching", `urql`'s default caching
+
mechanism.](./document-caching.md)
+189
docs/basics/queries.md
···
+
---
+
title: Queries
+
order: 1
+
---
+
+
# Queries
+
+
Let's get to querying our data! This page will teach us how we can retrieve our data declaratively
+
from our GraphQL API with the help of `urql`.
+
+
## React & Preact
+
+
This guide covers how to query data with React and Preact, which share almost the same API.
+
+
Both libraries offer a `useQuery` hook and a `Query` component. The latter accepts the same
+
parameters but we won't cover it in this guide. [Look it up in the API docs if you prefer
+
render-props components.](../api/urql.md#components)
+
+
### Run a first query
+
+
For the following examples imagine we are querying data from a GraphQL API that contains todo items
+
Let's dive right into an example!
+
+
```jsx
+
import { useQuery } from 'urql';
+
+
const TodosQuery = `
+
query {
+
todos {
+
id
+
title
+
}
+
}
+
`;
+
+
const Todos = () => {
+
const [result, reexecuteQuery] = useQuery({
+
query: TodosQuery,
+
});
+
+
const { data, fetching, error } = result;
+
+
if (fetching) return <p>Loading...</p>;
+
if (error) return <p>Oh no... {error.message}</p>;
+
+
return (
+
<ul>
+
{data.todos.map(todo => (
+
<li key={todo.id}>{todo.title}</li>
+
))}
+
</ul>
+
);
+
};
+
```
+
+
Here we have implemented our first GraphQL query to fetch todos. We see that using `useQuery`
+
accepts options — in this case we've set `query` to our GraphQL query — and returns a tuple — an
+
array that contains a result and a reexecute function.
+
+
The result contains several properties. The `fetching` field indicates whether we're currently loading
+
data, `data` contains the actual `data` from the API's result, and `error` is set when either the
+
request to the API has failed or when our result contained some `GraphQLError`s, which
+
we'll get into later on the ["Errors" page](./errors.md).
+
+
### Variables
+
+
Typically we'll also need to pass variables to our queries, for instance What if we are dealing with
+
pagination? For this purpose the `useQuery` hook also accepts a `variables` option, which we can use
+
to supply variables to our query.
+
+
```jsx
+
const TodosListQuery = `
+
query ($from: Int!, $limit: Int!) {
+
todos (from: $from, limit: $limit) {
+
id
+
title
+
}
+
}
+
`;
+
+
const Todos = ({ from, limit }) => {
+
const [result, reexecuteQuery] = useQuery({
+
query: TodosListQuery,
+
variables: { from, limit },
+
});
+
+
// ...
+
};
+
```
+
+
As when we're sending GraphQL queries manually using `fetch`, the variables will be attached to the
+
`POST` request that is sent to our GraphQL API.
+
+
Whenever the `variables` (or the `query`) option on
+
the `useQuery` hook changes `fetching` will return to being `true` and a new request will be sent to
+
our API, unless a result has already been cached previously.
+
+
### Pausing `useQuery`
+
+
In some cases we may want `useQuery` to execute a query when a precondition has been met. For
+
instance, we may be building a form and want validation to only take place when a field has been
+
filled out.
+
+
In the previous example we've defined a query with mandatory arguments. The `$from` and `$limit`
+
variables have been defined to be non-nullable `Int!` values.
+
+
Let's pause the query we've just
+
written to not execute when these variables are empty, to prevent `null` variables from being
+
executed. We can do this by means of the `pause` option.
+
+
```jsx
+
const Todos = ({ from, limit }) => {
+
const [result, reexecuteQuery] = useQuery({
+
query: TodosListQuery,
+
variables: { from, limit },
+
pause: !from || !limit,
+
});
+
+
// ...
+
};
+
```
+
+
Now whenever the mandatory `$from` or `$limit` variables aren't supplied the query won't be executed.
+
This also means that `result.data` won't change, which means we'll still have access to our old data
+
even though the variables may have changed.
+
+
### Request Policies
+
+
As has become clear in the previous sections of this page, the `useQuery` hook accepts more options
+
than just `query` and `variables`. Another option we should touch on is `requestPolicy`.
+
+
The `requestPolicy` option determines how results are retrieved from our `Client`'s cache. By
+
default this is set to `cache-first`, which means that we prefer to get results from our cache, but
+
are falling back to sending an API request.
+
+
In total there are four different policies that we can use:
+
+
- `cache-first` (the default) prefers cached results and falls back to sending an API request when
+
no prior result is cached.
+
- `cache-and-network` returns cached results but also always sends an API request, which is perfect
+
for displaying data quickly while keeping it up-to-date.
+
- `network-only` will always send an API request and will ignore cached results.
+
- `cache-only` will always return cached results or `null`.
+
+
The `cache-and-network` policy is particularly useful, since it allows us to display data instantly
+
if it has been cached, but also refreshes data in our cache in the background. This means though
+
that `fetching` will be `false` for cached results although an API request may still be ongoing in
+
the background.
+
+
For this reason there's another field on results, `result.stale`, which indicates that the cached
+
result is either outdated or that another request is being sent in the background.
+
+
### Reexecuting Queries
+
+
The `useQuery` hook updates and executes queries whenever its inputs, like the `query` or
+
`variables` change, but in some cases we may find that we need to programmatically trigger a new
+
query. This is the purpose of the `reexecuteQuery` function which is the second item in the tuple
+
that `useQuery` returns.
+
+
Triggering a query programmatically may be useful in a couple of cases. It can for instance be used
+
to refresh data that is currently being displayed. In these cases we may also override the
+
`requestPolicy` of our query just once and set it to `network-only` to skip the cache.
+
+
```jsx
+
const Todos = ({ from, limit }) => {
+
const [result, reexecuteQuery] = useQuery({
+
query: TodosListQuery,
+
variables: { from, limit },
+
});
+
+
const refresh = () => {
+
// Refetch the query and skip the cache
+
reexecuteQuery({ requestPolicy: 'network-only' });
+
};
+
};
+
```
+
+
Calling `refresh` in the above example will execute the query again forcefully, and will skip the
+
cache, since we're passing `requestPolicy: 'network-only'`.
+
+
Furthermore the `reexecuteQuery` function can also be used to programmatically start a query even
+
when `pause` is set to `true`, which would usually stop all automatic queries.
+
+
### Reading on
+
+
There are some more tricks we can use with `useQuery`. [Read more about its API in the API docs for
+
it.](../api/urql.md#usequery)
+
+
[On the next page we'll learn about "Mutations" rather than Queries.](./mutations.md)
-107
docs/basics/querying-data.md
···
-
---
-
title: Queries
-
order: 1
-
---
-
-
# Queries
-
-
Let's get to querying our data! This section will teach us how we can
-
retrieve our data from the server with the help of `urql`.
-
-
## React/Preact
-
-
Let's get to querying our first piece of data, we offer both a
-
`render-props` component named `Query` and a hook named `useQuery` as
-
a means to query data.
-
-
The examples will show the hooks-version but it will be the same for the component.
-
-
### Run your first query
-
-
For the following examples imagine we are querying a server offering us todo's, let's
-
dive right into it!
-
-
```jsx
-
const Todos = () => {
-
const [{ data, fetching, error }, reexecuteFetch] = useQuery({
-
query: `
-
query {
-
todos {
-
id
-
title
-
}
-
}
-
`,
-
});
-
-
if (fetching) return <p>Loading...</p>
-
if (error) return <p>Oh no... {error.message}</p>
-
-
return (
-
<ul>
-
{data.todos.map(todo => (
-
<li key={todo.id}>
-
{todo.title}
-
</li>
-
))}
-
</ul>
-
);
-
}
-
```
-
-
### Variables
-
-
We have fetched our first set of todos. We can see the `useQuery` hook returns a tuple,
-
the first being the result indicating whether it's fetching, it has errored and the result.
-
The second can be used to refetch the query forcefully.
-
-
What if we are dealing with pagination? We'd need a way to pass that right?
-
We have the `variables` property to supply the variables to our query.
-
-
```jsx
-
const Todos = ({ from, limit }) => {
-
const [{ data, fetching, error }, reexecuteFetch] = useQuery({
-
query: `
-
query ($from: Int!, $limit: Int!) {
-
todos (from: $from, limit: $limit) {
-
id
-
title
-
}
-
}
-
`,
-
variables: { from, limit },
-
});
-
...
-
}
-
```
-
-
### Skipping queries
-
-
As you can see we are enforcing `from` and `limit` as mandatory (notice the "!" after the `Int` type)
-
this means that if we don't supply them this query will fail, we need some way of pausing this query
-
when we don't have these. We can do exactly this by means of the `skip` property.
-
-
```jsx
-
const Todos = ({ from, limit }) => {
-
const [{ data, fetching, error }, reexecuteFetch] = useQuery({
-
...
-
skip: (!from || !limit)
-
});
-
...
-
}
-
```
-
-
Now whenever one of these two mandatory variables isn't supplied the query won't be executed.
-
-
### Request policy
-
-
We're almost there, there's one last thing we should touch on and that's the `requestPolicy`,
-
this property tells the client how you want to get the result for your query, there are four values:
-
-
- `cache-first` (default), this means we want to first look in our cache and see if the result is there, if not
-
we fetch it and update our cache with the result.
-
- `cache-and-network`, here we'll go to the cache and see if there's a result if there's not we fetch it, if there
-
is a result we return it to the `query` and dispatch another operation to refresh this data.
-
- `network-only`, this policy bypasses the cache and will just query your server.
-
- `cache-only`, here we'll look for your data in the cache, if it's there you'll get the data returned, if not
-
there will be a `null` return.
-81
docs/basics/setting-up-the-client.md
···
-
---
-
title: Getting started
-
order: 0
-
---
-
-
# Getting started
-
-
## React/Preact
-
-
### Installation
-
-
Installing `urql` is as quick as you'd expect. Firstly, install it
-
with your package manager of choice, Note: this installation is specific for React:
-
-
```sh
-
yarn add urql graphql
-
# or
-
npm install --save urql graphql
-
```
-
-
To use urql with Preact, you have to install `@urql/preact` instead of urql and import from
-
that package instead.
-
-
> _Note:_ Most libraries related to GraphQL specify `graphql` as their peer
-
> dependency so that they can adapt to your specific versioning
-
> requirements.
-
> The library is updated frequently and remains very backwards compatible,
-
> but make sure it will work with other GraphQL tooling you might have installed.
-
-
### Setting up the client
-
-
The package will export a method called `createClient` we can use this to create the
-
client that will be used to dispatch our queries, mutations, etc.
-
```js
-
import { createClient } from 'urql';
-
-
const client = createClient({
-
url: 'http://localhost:3000/graphql',
-
});
-
```
-
-
This is the bare minimum you need to get started with your client.
-
-
One option you will most likely need in most applications is the `fetchOptions`,
-
this option allows you to customize the `fetch` request sent to the given url.
-
-
This is a function or an object, in the following example we tell our client a token
-
should be added whenever it's present.
-
-
```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 this client in (P)React we will have to provide the client through
-
the context API. This is done with the help of the `Provider` export.
-
-
```jsx
-
import { createClient, Provider } from 'urql';
-
-
const client = createClient({
-
url: 'http://localhost:3000/graphql',
-
});
-
-
const App = () => (
-
<Provider value={client}>
-
<YourRoutes />
-
</Provider>
-
);
-
```
-
-
Now all the children of `<App />` will have access to the client we declared.
-6
docs/common-questions.md
···
-
---
-
title: Common Questions
-
order: 6
-
---
-
-
# Common Questions
+20
docs/concepts/README.md
···
---
# Main Concepts
+
+
In this chapter we'll learn about the motivation behind `urql`, the architecture that drives it, the
+
inner workings of the `Client`, and how to write extensions and addons, also known as _Exchanges_.
+
+
Each page goes a little further in explaning a core concept of `urql`.
+
+
- **Philosophy** gives a quick overview of the different aspects of GraphQL clients and `urql` in
+
particular, which shines a light on why you may want to use `urql`.
+
- **Stream Pattern** explains the inner working of `urql`, which is _stream-based_, also known as
+
Observable patterns in JS.
+
- **Core Package** defines why a shared package exists that contains the main logic of `urql`, and
+
how we can use it directly in Node.js.
+
- **Exchanges** finally introduces _Exchanges_ and how to write extensions or addons and use them
+
in `urql`.
+
+
Finally, some _Exchanges_ are covered in different sections of the documentation, like
+
["Subscriptions"](../advanced/subscriptions.md), ["Server-side
+
Rendering"](../advanced/server-side-rendering.md), or ["Normalized
+
Caching"](../graphcache/normalized-caching.md). It's advisable to read this chapter before moving on
+
to using _Exchanges_.
+30 -34
docs/concepts/core-package.md
···
# The Core Package
-
Previously, [the "Philosophy" page](./philosophy.md) explained how `urql` solves different aspects
+
The `@urql/core` package contains `urql`'s `Client`, some common utilities, and some default
+
_Exchanges_. These are the shared, default parts of `urql` that we will be using no matter which
+
framework we're interacting with.
+
+
Therefore those are also the parts of `urql` that contain its most important logic — like the
+
[`Client`](../api/core.md#client) — and the package that we need to know about if we're either integrating `urql` with a new
+
framework, or if we're using the "raw" `Client` in Node.js.
+
+
## Background
+
+
The ["Philosophy"](./philosophy.md) page explains how `urql` solves some of problems encountered when different aspects
of having a GraphQL client handle declarative querying and being a central point of extensibiliy.
By extension there are three parts of `urql` you'll come in contact with when you add it to your
app:
-
<!-- TODO: Add more package links -->
-
- the framework integration that allows you to declaratively write queries and mutations in your
-
preferred framework, which are currently the `urql` or `@urql/preact` packages
-
- the `Client` that manages the operation lifecycle and results
-
- and; exchanges that may either be some default exchanges or some from external packages
-
-
On this page we'll learn about the latter two points, which is shared logic that isn't specific to
-
any framework, like React code or Preact code.
-
-
[We'll learn more about _Exchanges_ on the next page.](./exchanges.md)
-
-
## Contents of Core
+
preferred framework, which are currently the [`urql`](../api/urql.md) or
+
[`@urql/preact`](../api/preact.md) packages
+
- the [`Client`](../api/core.md#client) that manages the operation lifecycle and results
+
- and, exchanges that may either be some default exchanges or some from external packages
-
The `@urql/core` package contains `urql`'s `Client`, some common utilities, and some default
-
_Exchanges_. These are the shared, default parts of `urql` that we will be using no matter which
-
framework we're interacting with.
+
On this page we'll learn about the latter two points - shared logic that isn't specific to
+
a particular library or framework (such as React or Preact code).
-
Therefore those are also the parts of `urql` that contain its most important logic — like the
-
`Client` — and the package that we need to know about if we're either integrating `urql` with a new
-
framework, or if we're using the "raw" `Client` in Node.js.
+
_Exchanges_ are discussed in more detail on the [next page](./exchanges.md).
## Usage with Node.js
The largest part of `urql` itself and the core package is the aforementioned `Client`. It's often
-
used directly if you're just using `urql` in Node.js without any other integration.
+
used directly when using `urql` in Node.js without any other integration.
-
[We've previously seen how we can use the `Client`'s stream methods directly, in "Stream
-
Patterns".](./stream-patterns.md) However, the `Client` also has plenty of convenience methods that
-
make interacting with the `Client` directly a lot easier.
+
[We've previously seen how we can use the `Client`'s stream methods directly, in [Stream
+
Patterns](./stream-patterns.md). However, the [`Client`](../api/core.md#client) also has plenty of
+
convenience methods that make interacting with the `Client` directly a lot easier.
### One-off Queries and Mutations
···
}
`;
-
client.query(QUERY, { id: 'test' })
+
client
+
.query(QUERY, { id: 'test' })
.toPromise()
.then(result => {
console.log(result); // { data: ... }
···
```
Since the streams in `urql` operate synchronously, internally this method subscribes to
-
`client.executeQuery` but unsubscribes immediately. If a result is available in the cache it will be
-
resolved synchronosuly before we immediately unsubscribe, otherwise this will cause no request to be
-
sent to the GraphQL API.
+
`client.executeQuery` and unsubscribes immediately. If a result is available in the cache it will be
+
resolved synchronosuly prior to the unsubscribe. If not, the query is cancelled and no request will be sent to the GraphQL API.
## Common Utilities in Core
The `@urql/core` package contains other utilities that are shared between multiple addon packages.
This is a short but non-exhaustive list. It contains,
-
<!-- TODO: Add links to other docs pages where appropriate -->
-
-
- `CombinedError`, our abstraction to combine `GraphQLError`s and a `NetworkError`
-
- `makeResult` and `makeErrorResult`, utilities to create _Operation Results_
-
- `createRequest`, a utility function to create a request from a query and some variables (which
+
- [`CombinedError`](../api/core.md#combinederror) - our abstraction to combine one or more `GraphQLError`(s) and a `NetworkError`
+
- `makeResult` and `makeErrorResult` - utilities to create _Operation Results_
+
- [`createRequest`](../api/core.md#createrequest) - a utility function to create a request from a query and some variables (which
generates a stable _Operation Key_)
-
There are more utilities. [Read more about the `@urql/core` API in the API docs for
-
it.](../api/core.md)
+
There are other utilities not mentioned here. Read more about the `@urql/core` API in the [API docs](../api/core.md).
+65 -67
docs/concepts/exchanges.md
···
# Exchanges
-
As we've learned [on the "Stream Patterns" page](./stream-patterns.md), `urql`'s `Client` structures
+
As we've learned on the [Stream Patterns](./stream-patterns.md) page, `urql`'s `Client` structures
its data as an event hub. We have an input stream of operations, which are instructions for the
-
`Client` to provide a result. These results come from an output stream of operation results.
+
`Client` to provide a result. These results then come from an output stream of operation results.
-
The important part how we get from the operations stream to the results stream, which is the
-
responsibility of _Exchanges_. _Exchanges_ are handler functions that deal with these input and
-
output streams. They're the main construct of `urql` to implement every piece of logic, like
-
caching, fetching, deduplicating requests, and more. In other words, _Exchanges_ are handlers that
+
_Exchanges_ are responsible for performing the important transform from the operations (input) stream to the results stream. Exchanges are handler functions that deal with these input and
+
output streams. They're one of `urql`'s key components, and are needed to implement vital pieces of logic such as
+
caching, fetching, deduplicating requests, and more. In other words, Exchanges are handlers that
fulfill our GraphQL requests and can change the stream of operations or results.
-
The default exchanges that `@urql/core` contains and applies by default to a `Client` without custom
-
exchanges are the:
+
The default set of exchanges that `@urql/core` contains and applies to a `Client` are:
- `dedupExchange`: Deduplicates pending operations (pending = waiting for a result)
- `cacheExchange`: The default caching logic with ["Document Caching"](../basics/document-caching.md)
- `fetchExchange`: Sends an operation to the API using `fetch` and adds results to the output stream
+
It is also possible to apply custom exchanges to override the default logic.
+
## An Exchange Signature
-
Because of how _Exchanges_ work they're akin to [middleware in
-
Redux](https://redux.js.org/advanced/middleware).
+
Exchanges are akin to [middleware in
+
Redux](https://redux.js.org/advanced/middleware) due to the way that they apply transforms.
```ts
import { Client, Operation, OperationResult } from '@urql/core';
-
type ExchangeInput = { forward: ExchangeIO, client: Client };
+
type ExchangeInput = { forward: ExchangeIO; client: Client };
type Exchange = (input: ExchangeInput) => ExchangeIO;
type ExchangeIO = (ops$: Source<Operation>) => Source<OperationResult>;
```
-
Their signature is a function that receives a `forward` function, which is the next _Exchange_ in a
-
chain of them and returns the `ExchangeIO` function, which accepts the source of _Operations_ and
-
returns a source of _Operation Results_:
+
The first parameter to an exchange is a `forward` function that refers to the next Exchange in the
+
chain. The second second parameter is the `Client` being used. Exchanges always return an `ExchangeIO`
+
function (this applies to the `forward` funtion as well), which accepts the source of
+
[_Operations_](../api/core.md#operation) and returns a source of [_Operation
+
Results_](../api/core.md#operationresult).
+
+
- [Read more about streams on the "Stream Patterns" page.](../concepts/stream-patterns.md)
+
- [Read more about the _Exchange_ type signature on the API docs.](../api/core.md#exchange)
## Using Exchanges
-
The `Client` accepts an `exchanges` option which is by default the list of default exchanges, as
-
discussed above. When we pass a custom list of exchanges the `Client` uses the `composeExchanges`
+
The `Client` accepts an `exchanges` option that defaults to the three default exchanges mentioned above. When we pass a custom list of exchanges the `Client` uses the `composeExchanges`
utiliy, which starts chaining these exchanges.
In essence these exchanges build a pipeline that runs in the order they're passed; _Operations_ flow
-
in from the start to the end, and _Results_ come back in reverse, through this chain.
+
in from the start to the end, and _Results_ are returned through the chain in reverse.
If we look at our list of default exchanges — `dedupExchange`, `cacheExchange`, and then
`fetchExchange` — an incoming operation is treated as follows:
**First,** ongoing operations are deduplicated. It wouldn't make sense to send the
-
same operation / request twice at the same time.
+
same operation / request twice in parralel.
-
**Second,** operations are checked against the cache. Depending on the `requestPolicy`
+
**Second,** operations are checked against the cache. Depending on the `requestPolicy`,
cached results can be resolved instead and results from network requests are cached.
-
**Third,** operations are sent to the API and the result is normalized. The result then travels
-
backwards through the returned stream of results.
+
**Third,** operations are sent to the API and the result is normalized. The normalized result then travels
+
backwards through the stream.
## The Rules of Exchanges
-
Before we can start writing some exchanges, there are a couple of patterns and limitations that
-
always remain the same when writing an exchange. We call these the "rules of _Exchanges_", which
-
also come in useful when trying to learn what _Exchanges_ actually are.
+
Before we can start writing some exchanges, there are a couple of consistent patterns and limitations that
+
must be adhered to when writing an exchange. We call these the "rules of Exchanges", which
+
also come in useful when trying to learn what Exchanges actually are.
For reference, this is a basic template for an exchange:
```js
const noopExchange = ({ client, forward }) => {
-
return operation$ => { // <-- The ExchangeIO function
+
return operation$ => {
+
// <-- The ExchangeIO function
const operationResult$ = forward(operations$);
return operationResult$;
};
···
```
This exchange does nothing else than forward all operations and return all results. Hence, it's
-
called a `noopExchange`, an exchange that doesn't do anything.
+
called a `noopExchange` - an exchange that doesn't do anything.
### Forward and Return Composition
When you create a `Client` and pass it an array of exchanges, `urql` composes them left-to-right.
-
If we look at our previous `noopExchange` example in context, we can track what it does if it sat
-
in-between the `dedupExchange` and the `fetchExchange`.
+
If we look at our previous `noopExchange` example in context, we can track what it does if it is located between the `dedupExchange` and the `fetchExchange`.
```js
import { Client, dedupExchange, fetchExchange } from 'urql';
const noopExchange = ({ client, forward }) => {
-
return operation$ => { // <-- The ExchangeIO function
+
return operation$ => {
+
// <-- The ExchangeIO function
// We receive a stream of Operations from `dedupExchange` which
// we can modify before...
const forwardOperations$ = operations$;
···
});
```
-
### One operations stream only
+
### Only One Operations Stream
-
When writing an _Exchange_ we have to be careful not to "split" the stream into multiple ones by
-
subscribing multiple times. Streams are lazy and immutable by default. Every time you use them, you
-
create a new chain of streaming operators, but since _Exchanges_ are side-effects, we don't want to
+
When writing an Exchange we have to be careful not to _split_ the stream into multiple ones by
+
subscribing multiple times. Streams are lazy and immutable by default. Every time you use them, a new chain of streaming operators is created; since Exchanges are technically side-effects, we don't want to
accidentally have multiple instances of them in parallel.
-
Your `ExchangeIO` function receives an `operations$` stream, and you must be careful to either only
+
The `ExchangeIO` function receives an `operations$` stream. It's important to be careful to either only
use it once, or to _share_ its subscription.
```js
import { pipe, filter, merge, share } from 'wonka';
// DON'T: split use operations$ twice
-
({ forward }) => operations$ => { // <-- The ExchangeIO function (inline)
+
({ forward }) => operations$ => {
+
// <-- The ExchangeIO function (inline)
const queries = pipe(
operations$,
filter(op => op.operationName === 'query')
···
};
// DO: share operations$ if you have to use it twice
-
({ forward }) => operations$ => { // <-- The ExchangeIO function (inline)
-
const shared = pipe(
-
operations$,
-
share
-
);
+
({ forward }) => operations$ => {
+
// <-- The ExchangeIO function (inline)
+
const shared = pipe(operations$, share);
const queries = pipe(
shared,
filter(op => op.operationName === 'query')
···
};
// DO: use operations$ only once alternatively
-
({ forward }) => operations$ => // <-- The ExchangeIO function (inline)
+
({ forward }) => (
+
operations$ // <-- The ExchangeIO function (inline)
+
) =>
pipe(
operations$,
map(op => {
···
use Wonka's [`share`](https://wonka.kitten.sh/api/operators#share) operator, to share the underlying
subscription between all your streams.
-
### Don't accidentally drop operations
+
### How to Avoid Accidentally Dropping Operations
Typically the `operations$` stream will send you `query`, `mutation`,
`subscription`, and `teardown`. There is no constraint for new operations
···
// DO: forward operations that you don't handle
({ forward }) => operations$ => {
-
const shared = pipe(
-
operations$,
-
share
-
);
+
const shared = pipe(operations$, share);
const queries = pipe(
shared,
filter(op => op.operationName === 'query')
···
};
```
-
If you group and or filter operations by what your exchange is handling,
-
also make sure that you have a stream of operations that it's not handling,
-
which you should also forward.
+
If operations are grouped and/or filtered by what the exchange is handling, then it's also important to
+
make that any streams of operations not handled by the exchange should also be forwarded.
### Synchronous first, Asynchronous last
By default exchanges and Wonka streams are as predictable as possible.
-
Every operator in Wonka runs synchronously until you actually introduce
-
asynchronicity.
+
Every operator in Wonka runs synchronously until asynchronicity is introduced.
-
This may happen when you use a timing utility from Wonka, like
+
This may happen when using a timing utility from Wonka, like
[`delay`](https://wonka.kitten.sh/api/operators#delay) or
[`throttle`](https://wonka.kitten.sh/api/operators#throttle)
-
Or this could happen because your exchange inherently does something asynchronous, like fetching some
-
data or use a promise.
+
This can also happen because the exchange inherently does something asynchronous, like fetching some
+
data or using a promise.
-
When you write exchanges, some will inevitably be asynchronous, if
+
When writing exchanges, some will inevitably be asynchronous. For example if
they're fetching results, performing authentication, or other tasks
that you have to wait for.
This can cause problems, because the behavior in `urql` is built
-
to be _synchronous_ first. This helps us build our suspense mode,
-
and it helps your components receive cached data on their initial
+
to be _synchronous_ first. This is very helpful for suspense mode and allowing components receive cached data on their initial
mount without rerendering.
This why **all exchanges should be ordered synchronous first and
···
The default order of exchanges is:
```js
-
import { dedupExchange, cacheExchange, fetchExchange } from 'wonka';
+
import { dedupExchange, cacheExchange, fetchExchange } from 'urql';
// Also exported as `defaultExchanges`:
[dedupExchange, cacheExchange, fetchExchange];
```
Both the `dedupExchange` and `cacheExchange` are completely
-
synchronous and only the `fetchExchange` is asynchronous since
+
synchronous. The `fetchExchange` is asynchronous since
it makes a `fetch` request and waits for a server response.
-
When you're adding more exchanges you obviously have a reason
-
to put them in a specific order. For instance, an authentication exchange
-
needs to go before the `fetchExchange`. And a secondary cache would
-
maybe go in front of the default cache exchange.
+
When you're adding more exchanges it's often crucial
+
to put them in a specific order. For instance - an authentication exchange
+
will need to go before the `fetchExchange`, a secondary cache will probably have to
+
go in front of the default cache exchange.
-
But to ensure the correct behavior of suspense mode and
-
the initialization of our hooks, it's vital to order your exchanges
-
so that synchronous exchanges come first and asynchronous ones
-
come last.
+
To ensure the correct behavior of suspense mode and
+
the initialization of our hooks, it's vital to order exchanges
+
so that synchronous ones come before asynchronous ones.
+29 -33
docs/concepts/philosophy.md
···
[core behavior in the core package](./core-package.md).
By default, we aim to provide features that allow you to build your app quickly with minimal
-
configuration. `urql` is a client that grows with you. As you go from building your first
-
GraphQL app to a full experience, we give you he tools to extend and customize `urql` based on
+
configuration. `urql` is designed to be a client that grows with you. As you go from building your first
+
GraphQL app to a utilising the full functionality, the toopls are available to extend and customize `urql` based on
your needs.
-
In this guide, we will walkthrough how `urql` is set up internally and how all pieces of the puzzle
+
In this guide, we will walk through how `urql` is set up internally and how all pieces of the puzzle
— the building blocks of `urql` — interact with one another.
## Hello World
-
[We previously read about how to set up a `Client` in "Getting
-
Started".](../basics/setting-up-the-client.md)
+
We previously read about how to set up a `Client` in [Getting
+
Started](../basics/getting-started.md).
-
When you use `urql` you will always create and set up a `Client` for which a `createClient`
-
convenience helper exists.
+
When you use `urql` you will always create and set up a `Client`. There is a `createClient`
+
convenience helper to do just that.
```js
import { Client } from 'urql';
···
## Using GraphQL Clients
-
You may have worked on a GraphQL API previously and noticed that using GraphQL in your app can be
+
You may have worked with a GraphQL API previously and noticed that using GraphQL in your app can be
as straightforward as sending a plain HTTP request with your query to fetch some data.
-
But GraphQL provides an opportunity to abstract away a lot of the manual work that goes with
-
sending these queries and managing the data. This ultimately lets you focus on building
-
your app without handling the technical details of state management in detail.
+
GraphQL also provides an opportunity to abstract away a lot of the manual work that goes with
+
sending these queries and managing the data. Ultimately, this lets you focus on building
+
your app without having to handle the technical details of state management in detail.
-
Specifically `urql` simplifies three common aspects of using GraphQL easily:
+
Specifically, `urql` simplifies three common aspects of using GraphQL:
- Sending queries and mutations and receiving results _declaratively_
- Abstracting _caching_ and state management internally
···
![Operations and Results](../assets/urql-event-hub.png)
-
This all happens in the background while you just declare that you'd like to have data for a given
+
This all happens in the background, allowing you to simply declare that you'd like to have data for a given
query.
## Caching and State
When we use GraphQL queries and mutations declaratively with `urql`, we expect them to interact
-
and update automaticaly.
+
and update automatically.
-
Furthermore, we don't wish to send more requests for a query, when we've done so before. We'd like
-
to instead cache results in-memory and notify other parts of an app when these results chane or
-
are invalidated by mutations or subscriptions.
+
Furthermore, when we've already received the results from a query, we may not wish to send another request. To solve this, results can be cached in-memory and notifications can be sent to other parts of an app when the results change or
+
are invalidated by mutations/subscriptions.
-
GraphQL clients have access to some amount of type information for any GraphQL API and can hence
+
GraphQL clients have access to some type information for any GraphQL API and hence can
cache the results of queries automatically. In `urql` the `Client` can be extended with several
-
cache implementations, but all of them mean that you'll never mix your declarative query or mutation
-
code with cache-implementation details, which mostly happen behind the scenes.
+
cache implementations; all of them allow you to prevent mixing your declarative query or mutation
+
code with cache-implementation details, as they mostly happen behind the scenes.
-
[We previously read about the default "Document Caching".](../basics/document-caching.md)
+
We previously read about the default [Document Caching](../basics/document-caching.md).
Some GraphQL clients also resort to caching data in a normalized format. This is similar to
[how you may store data in Redux.](https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape/)
···
in the cache and structures it in a graph, which leads to more shared data, and hence more shared
updates in your UI!
-
[Read more about how to add "Normalized Caching" to an app.](../graphcache/normalized-caching.md)
+
[Read more](../graphcache/normalized-caching.md) on how to add Normalized Caching to an app.
## Extensibility and Integration
-
With any kind of API come other concerns apart from caching and state mangagement that concern
-
the global behavior or business logic of your application.
-
-
For instance, you may want to add authentication, retry-logic for failed requests, or a global
+
With any kind of API there can be concerns outside of caching and state mangagement. For example,
+
the global behavior or business logic of your application. For instance, you may want to add authentication, retry-logic for failed requests, or a global
error handler.
-
`urql` provides a concept of _Exchanges_ to abstract the details of how the `Client` interacts with
+
`urql` introduces the concept of _Exchanges_ in order to abstract the details of how the `Client` interacts with
your framework of choice, your app, and your GraphQL API. They are akin to
[middleware in Redux](https://redux.js.org/advanced/middleware) and have access to all operations
and all results.
-
[Read more about _Exchanges_ in a later page of the documentation.](./exchanges.md)
+
Read more about [Exchanges](./exchanges.md) later on in the documentation.
All default behavior in the [core package](./core-package.md) is implemented using
-
_Exchanges_, which is possible because all operations and all results are treated as a stream
-
of events. We call these events "Operations".
+
Exchanges. This is possible as all operations and all results are treated as a stream
+
of events; we call these events "Operations".
![Operation Signature](../assets/urql-signals.png)
-
From our perspective, thinking about your GraphQL queries and results in terms of
-
streams of operations and results allows us to implement any given complex behaviour,
-
which we'll learn more about in the next section.
+
Thinking about GraphQL queries and results in
+
streams of operations and results allow us to implement complex behaviour in addition to allowing deep customisation over how the operations/results are handled. We'll learn more about this in the next section - [the Core Package](./core-package.md).
+29 -25
docs/concepts/stream-patterns.md
···
# Stream Patterns
-
As we've learned [on the last page](./philosophy.md), `urql`'s main way of handling GraphQL requests
-
is by abstracting them as streams of operations and results
+
As we've learned in the previous section on [philosophy](./philosophy.md), `urql`'s main way of handling GraphQL requests
+
is by abstracting them as streams of operations and results.
## Streams on the Client
-
Mainly, the client abstracts GraphQL requests as _Operations_, descriptions of the GraphQL request,
-
its query and variables, and also additional information that is configured on the `Client`, like
-
the `url` and `fetchOptions`.
+
The client abstracts GraphQL requests in a number of ways:
+
+
- as _Operations_
+
- descriptions of the GraphQL request
+
- the query and related variables
+
- additional information that is configured on the `Client`, such as
+
the `url` and `fetchOptions`.
![Operations stream and results stream](../assets/urql-client-architecture.png)
Internally the `Client` is an event hub. It defines a stream of operations as inputs, sends them
-
through a layer that will ultimately send GraphQL requests to an API, and then sends the results
-
onto another stream.
+
through a layer that will ultimately send GraphQL requests to an API, and then send the corresponding results
+
to another stream.
-
As a user, in framework code, we never interact with these streams directly, but they describe
-
every interaction between the declarative queries we write and how `urql` fulfills them.
+
As a user working with framewrk code we never interact with these streams directly, but it's helpful to know that they describe
+
every interaction between the declarative queries we write and the way that `urql` fulfills them.
## Streams in JavaScript
Generally we refer to _streams_ as abstractions that allow us to program with asynchronous streams of
-
events over time, but more specifically in JavaScript, we're thinking specifically of
+
events over time. Within the JavaScript context we're thinking specifically in terms of of
[Observables](https://github.com/tc39/proposal-observable)
and [Reactive Programming with Observables.](http://reactivex.io/documentation/observable.html)
···
methods on arrays, so you're likely to see `map` and `filter` — amongst other utlities — in those
libraries.
-
[Read this Gist for a more in-depth
-
explanation.](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754)
+
Read [this Gist](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754) for a more in-depth
+
explanation.
## The Wonka library
`urql` utilises the [Wonka](https://github.com/kitten/wonka) library for its streams. It has a
-
couple of advantages that are specifically tailored for the `urql` library and ecosystem.
+
few advantages that are specifically tailored for the `urql` library and ecosystem:
-
- It is extremely lightweight and treeshakeable, weighing around 3.7kB minzipped.
+
- It is extremely lightweight and treeshakeable, with a size of around 3.7kB minzipped.
- It's cross-platform and cross-language compatible, having been written in
-
[Reason](https://reasonml.github.io/) and providing support for [Flow](https://flow.org/)
+
[Reason](https://reasonml.github.io/) and provides support for [Flow](https://flow.org/)
and [TypeScript](typescriptlang.org/).
-
- It's predictable and also an iterable toolchain, emitting synchronous events whenever possible.
+
- It's a predictable and iterable toolchain, emitting synchronous events whenever possible.
Typical usage of Wonka will involve creating a _source_ of some values and a _sink_.
···
fromArray([1, 2, 3]),
map(x => x * 2),
subscribe(x => {
-
console.log(x); // 1, 2, 3
+
console.log(x); // 2, 4, 6
})
);
```
-
In Wonka, like with Observables, streams are cancellable by calling `unsubscribe` that a
+
In Wonka, like with Observables, streams are cancellable by calling the `unsubscribe` method that a
subscription returns.
-
[Read more about Wonka in its documentation.](https://wonka.kitten.sh/basics/background)
+
Read more about Wonka in its [documentation](https://wonka.kitten.sh/basics/background).
-
## The Client's query streams
+
## Client Query Streams
Internally the `Client` has methods that may be used to execute queries, mutations, and
-
subscriptions. These methods typically return `Wonka` streams that when subscribed to will
+
subscriptions. These methods typically return `Wonka` streams that, when subscribed to, will
emit results for a given query.
When a result can be retrieved from an in-memory cache, the stream may even emit the result
synchronously — rather than asynchronously.
-
There are three methods for each different type of operation that GraphQL supports, there's an
-
`executeQuery`, `executeMutation`, and `executeSubscription` method. All these methods are
+
There are three methods for each type of operation that GraphQL supports;
+
`executeQuery`, `executeMutation`, and `executeSubscription`. All these methods are
convenience wrappers around `executeRequestOperation` that create an operation and return a stream.
There are also convenience wrappers around the "execute" methods that are useful when using `urql`
-
in a Node.js environment. Those are `query`, `mutation`, and `subscription`.
+
in a Node.js environment. They are the `query`, `mutation`, and `subscription` methods.
```js
import { pipe, subscribe } from 'wonka';
···
There are several of these convenience methods in `urql` that make it easier to work with the
concept of GraphQL operation and result streams.
-
[Read more about the available APIs on the `Client` in the Core API docs.](../api/core.md)
+
Read more about the available APIs on the `Client` in the [Core API docs](../api/core.md).
+77 -1
docs/graphcache/README.md
···
# Graphcache
-
<!-- TODO: Link to subpages -->
+
In `urql`, caching is fully configurable via [_Exchanges_](../concepts/exchanges.md) and the default
+
`cacheExchange` in `urql` offers a ["Document Cache"](../basics/document-caching.md), which is
+
sufficient for sites that heavily rely and render static content. However as an app grows more
+
complex it's likely that the data and state that `urql` manages, will also grow more complex and
+
introduce interdependencies between data.
+
+
To solve this problem most GraphQL clients resort to caching data in a normalized format, similar to
+
how [data is often structured in
+
Redux.](https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape/)
+
+
In `urql`, normalized caching is an opt-in feature which is provided by the
+
`@urql/exchange-graphcache` package, _Graphcache_ for short.
+
+
## Features
+
+
The following pages introduce different features in _Graphcache_ which together make it a compelling
+
alternative to the standard [document cache](../basics/document-caching.md) that `urql` uses by
+
default.
+
+
- 🔁 [**Fully reactive, normalized caching.**](./normalized-caching.md) _Graphcache_ stores data in
+
a normalized data structure. Query, mutation, and subscription results may update one another if
+
they share data, and the app will rerender or refetch data accordingly. This often allows your app
+
to make less API requests, since data may already be in the cache.
+
- 💾 [**Custom cache resolvers**](./computed-queries.md) Since all queries are fully resolved in the
+
cache before and after they're sent, you can add custom resolvers that enable you to format data,
+
implement pagination, or implement cache redirects.
+
- 💭 [**Subscription and Mutation updates**](./custom-updates.md) You can implement update functions
+
that tell _Graphcache_ how to update its data after a mutation has been executed, or whenever a
+
subscription sends a new event. This allows the cache to reactively update itself without queries
+
having to perform a refetch.
+
- 🏃 [**Optimistic mutation updates**](./custom-updates.md) When implemented, optimistic updates can
+
provide the data that the GraphQL API is expected to send back before the request succeeds, which
+
allows the app to instantly render an update while the GraphQL mutation is executed in the
+
background.
+
- 🧠 [**Opt-in schema awareness**](./schema-awareness.md) _Graphcache_ also optionally accepts your
+
entire schema, which allows it to resolve _partial data_ before making a request to the GraphQL
+
API, allowing an app to render everything that's cached before receiving all missing data. It also
+
allows _Graphcache_ to output more helpful warnings and to handle interfaces and enums correctly
+
without heuristics.
+
- 📡 **Offline support** (work in progress) _Graphcache_ can persist and rehydrate its entire state,
+
allowing an offline application to be built that is able to execute queries against the cache
+
although the device is offline.
+
+
## Installation and Setup
+
+
We can add _Graphcache_ by installing the `@urql/exchange-graphcache` package.
+
Using the package won't increase your bundle size by as much as platforms like
+
[Bundlephobia](https://bundlephobia.com/result?p=@urql/exchange-graphcache) may suggest, since it
+
shares the dependency on `wonka` and `@urql/core` with the framework bindings package, e.g. `urql`
+
or `@urql/preact`, that you're already using.
+
+
```sh
+
yarn add @urql/exchange-graphcache
+
# or
+
npm install --save @urql/exchange-graphcache
+
```
+
+
The package exports the `cacheExchange` which replaces the default `cacheExchange` in `@urql/core`.
+
This new `cacheExchange` must be instantiated using some options, which are used to customise
+
_Graphcache_ as introduced in the ["Features" section above.](#features) Howver, you can get started
+
without passing any options.
+
+
```js
+
import { createClient, dedupExchange, fetchExchange } from 'urql';
+
import { cacheExchange } from '@urql/exchange-graphcache';
+
+
const client = createClient({
+
url: 'http://localhost:3000/graphql',
+
exchanges: [dedupExchange, cacheExchange({}), fetchExchange],
+
});
+
```
+
+
This will automatically enable normalized caching, and you may find that in a lot of cases,
+
_Graphcache_ already does what you'd expect it to do without any additional configuration. We'll
+
explore how to customize and set up different parts of _Graphcache_ on the following pages.
+
+
[Read more about "Normalized Caching" on the next page.](./normalized-caching.md)
+58 -29
docs/graphcache/computed-queries.md
···
---
-
title: Computed-queries
+
title: Computed Queries
order: 2
---
-
# Resolvers
+
# Computed Queries
-
`resolvers` are a way to alter the response you'll receive from the cache.
-
Let's look at an example to get a better understanding.
+
When dealing with data we could have special cases where we want to format a date
+
or if we for instance have a list of a certain entity in cache and next we want
+
to query a specific entity, chances are this will already be (partially) available
+
in that list.
+
+
These cases can be solved with the concept of `resolvers`.
+
+
## Resolvers
+
+
Let's look at how we can introduce these `resolvers` to our `cacheExchange`.
```js
const cache = cacheExchange({
resolvers: {
-
Todo: { text: () => 'Secret' },
+
Todo: { updatedAt: ({ date }) => Date.format(date) },
},
});
```
Now when we query our `todos` every time we encounter an object with `Todo`
-
as the `__typename` it will change the `text` to `'Secret'`. In this way we
+
as the `__typename` it will format the `updatedAt` property. This way we
can effectively change how we handle a certain property on an entity.
-
This may seem pretty useless right now, but let's look at the arguments
-
passed to `resolvers` to get a better sense of how powerful they are.
-
-
A `resolver` gets four arguments:
+
Let's look at the arguments passed to `resolvers` to get a better sense of
+
what we can do, there are four arguments (these are in order):
- `parent` – The original entity in the cache. In the example above, this
would be the full `Todo` object.
- `arguments` – The arguments used in this field.
-
- `cache` – This is the normalized cache. The cache provides us with `resolve`, `readQuery` and `readFragment` methods;
-
see more about this [below](#cache.resolve).
+
- `cache` – This is the normalized cache. The cache provides us with `resolve`, `readQuery` and `readFragment` methods,
+
read more about this [below](#cache.resolve).
- `info` – This contains the fragments used in the query and the field arguments in the query.
## Cache parameter
+
+
This is the main point of communication with the cache, it will give us access to
+
all cached data.
### resolve
···
- `field` – The field you want data for. This can be a relation or a single property.
- `arguments` – The arguments to include on the field.
-
To get a better grasp let's look at a few examples.
-
Consider the following data structure:
+
To get a better grasp let's look at a few examples,
+
consider the following data structure:
```js
todos: [
···
];
```
-
Using `cache.resolve` to get the author would look like this:
+
Let's get the `author` for a todo.
```js
const parent = {
···
author: undefined,
__typename: 'Todo',
};
-
const result = cache.resolve(parent, 'author');
-
console.log(result); // 'Author:2'
+
+
console.log(cache.resolve(parent, 'author')); // 'Author:2'
```
Now we have a stringed key that identifies our author. We
···
});
```
-
will do the trick.
+
Returning a `__typename` and `key` (`id`/`_id`/custom key) is sufficient to make the
+
cache resolve this to the full entity.
+
+
Note that resolving from a list to details can lead to partial data, this will result in
+
a network-request to get the full data when fields are missing.
+
When graphcache isn't [aware of your schema](./schema-awareness.md) it won't show partial data.
### Reading a query
···
accepts an object of `query` and optionally `variables`.
```js
-
const data = cache.readQuery({ query: Todos, variables: { from: 0, limit: 10 } })`
+
cache.readQuery({ query: Todos, variables: { from: 0, limit: 10 } })`
```
-
This way we'll get the stored data for the `TodosQuery` with given variables.
+
This way we'll get the stored data for the `TodosQuery` for the given `variables`.
### Reading a fragment
The store allows the user to also read a fragment for a certain entity, this function
accepts a `fragment` and an `id`. This looks like the following.
-
```js
-
const data = cache.readFragment(gql`
-
fragment _ on Todo {
-
id
-
text
-
}
-
`, '1');
+
```js
+
import gql from 'graphql-tag';
+
+
const data = cache.readFragment(
+
gql`
+
fragment _ on Todo {
+
id
+
text
+
}
+
`,
+
'1'
+
);
```
+
> **Note:** In the above example, we've used
+
> [graphql-tag](https://github.com/apollographql/graphql-tag) because `writeFragment` only accepts
+
> GraphQL `DocumentNode`s as inputs, and not strings.
+
This way we'll get the Todo with id 1 and the relevant data we are askng for in the
fragment.
## Pagination
-
### Simple
+
`Graphcache` offers some preset `resolvers` to help us out with endless scrolling pagination.
+
+
### Simple Pagination
Given you have a schema that uses some form of `offset` and `limit` based pagination you can use the
`simplePagination` exported from `@urql/exchange-graphcache/extras` to achieve an endless scroller.
···
and `skip` respectively. This way you can use the keywords that you are using in
your queries.
-
### Relay
+
### Relay Pagination
Given you have a [relay-compatible schema](https://facebook.github.io/relay/graphql/connections.htm)
on your backend we offer the possibility of endless data resolving.
···
With inwards merging the nodes will be in this order: `[1, 2, ..., 89, 99]`
And with outwards merging: `[..., 89, 99, 1, 2, ...]`
+
+
### Reading on
+
+
[On the next page we'll learn about "Custom updates".](./custom-updates.md)
+147 -90
docs/graphcache/custom-updates.md
···
---
-
title: Custom-updates
+
title: Custom Updates
order: 3
---
-
# Custom Updates (on Mutations or Subscriptions),
+
# Custom Updates
-
## Data-updates
+
_Graphcache_ will attempt to automatically react to your mutations' and subscriptions' results
+
but sometimes this isn't possible. While it will update all normalized entities that it finds in
+
those results, it can't for instance tell whether a new item should be appended or removed from a
+
list.
-
When the cache receives a response it will try and do its best to
-
incorporate that response into the current cache. However, for adding and
-
deleting entities it can't really make assumptions.
+
Specifically, a normalized cache can't automatically assume that unrelated links have changed due to
+
a mutation, since this is server-side specific logic. Instead, we may use the `updates`
+
configuration to set up manual updates that react to mutations or subscriptions.
-
That's where updates come into play. Analogous to our `resolvers`,
-
`updates` get arguments, but instead of the `parent` argument we get the
-
result given from the server due to a subscription trigger or a mutation.
+
## Data Updates
-
> Note that this result will look like result.data, this means that in
-
> the example of us adding a todo by means of `addTodo` it will look like
-
> `{ addTodo: addedTodo }`.
+
The `updates` configuration is similar to our `resolvers` configuration that we've [previously looked
+
at on the "Computed Queries" page.](./computed-queries.md) We pass a resolver-like function into the
+
configuration that receives cache-specific arguments. Instead of the `parent` argument we'll however
+
receive the subscription's or mutation's `data` instead.
-
Let's look at three additional methods provided by the cache to enable
-
updates.
+
```js
+
const cache = cacheExchange({
+
updates: {
+
Mutation: {
+
addTodo: (result, args, cache, info) => {
+
// ...
+
},
+
},
+
Subscription: {
+
newTodo: (result, args, cache, info) => {
+
// ...
+
},
+
},
+
},
+
});
+
```
-
The first we'll look at is `updateQuery`. This method, given an object
-
containg the query and optionally some variables as first argument and
-
a callback with the result from this query as the second, will update the
-
cache.
+
Inside these update functions, apart from the `cache` methods that we've seen in ["Computed
+
Query"](./computed-queries.md) to read from the cached data, we can also use other `cache` methods to
+
write to the cached data.
+
+
### cache.updateQuery
+
+
The first we'll look at is `cache.updateQuery`. This method accepts a `{ query, variables }` object
+
as the first argument and an updater callback as the second. The updater function receives the query
+
data as its' only argument and must return the updated version of said data.
+
+
Note that we don't have to update the query data immutably. _Graphcache_ never returns raw data and
+
will instead always return copies of data. This means that we can also mutate query data inside the
+
`updateQuery` callback.
```js
-
const Todos = gql`
+
const TodosQuery = gql`
query {
__typename
todos {
···
}
`;
-
cache.updateQuery({ query: Todos, variables: { page: 1 } }, data => {
-
data.todos.push({
-
id: '2',
-
text: 'Learn updates and resolvers',
-
complete: false,
-
__typename: 'Todo',
-
});
-
return data;
+
const cache = cacheExchange({
+
updates: {
+
Mutation: {
+
addTodo: (result, args, cache, info) => {
+
cache.updateQuery({ query: TodosQuery }, data => {
+
data.todos.push(result.addTodo);
+
return data;
+
});
+
},
+
},
+
},
});
```
-
In the above code sample, we start by supplying a query to the `cache`.
-
This allows it to fetch the required data. This data is passed as the
-
second argument to the `updater`, which allows you to alter it before
-
returning. When you have returned to the cache it will update the relevant
-
query with your given input.
+
In the above example, we add an updater configuration for `Mutation.addTodo`. Whenever a mutation's
+
result contains `addTodo` our new updater will be called.
+
+
Inside the updater we use `cache.updateQuery` to update a list of todos with the new todo that has
+
been created by `addTodo`, which we can access using `result.addTodo`.
+
We could also alter this todo before returning our updated `data`.
+
+
With this configuration any query that requests `Query.todos` will automatically update and contain
+
our new todo, when a mutation with `Mutation.addTodo` completes.
+
+
It's worth noting that it doesn't matter whether the `TodosQuery` is the same one that you use in
+
your application code. We're simply updating the normalized data of `Query.todos` across the
+
normalized store, which will be reflected in any query that depends on `Query.todos`.
+
+
### cache.writeFragment
-
> Note that you have to supply \_\_typename.
+
Similar to `cache.updateQuery`, we can also update data for a fragment using `cache.writeFragment`,
+
instead of an entire query. This method accepts a GraphQL fragment instead of an entire GraphQL
+
query. It's also not an updater method but a direct write method instead.
-
The second method we have available on the `cache` is `updateFragment`.
-
This method allows you to supply a GraphQL fragment to update, such that you
-
don't have to supply the full object entity (in this case `Todo`) if you just
-
want to update specific fields.
+
The first argument for `cache.writeFragment`, similarly to `readFragment`, must be a GraphQL
+
fragment. The second argument is the data that should be written to the cache. This data object must
+
contain `id` or another field if the type has a custom `keys` configuration.
```js
-
cache.updateFragment(
+
import gql from 'graphql-tag';
+
+
cache.writeFragment(
gql`
fragment _ on Todo {
id
···
);
```
-
In the example above, we supply a fragment on `Todo` to `updateFragment`. You'll
-
notice we include the `id` property such that the cache knows which entity to update,
-
as well as the some fields we want to update. So in this case we update `Todo` with `id`
-
`'1'` to have text `'update'`. The rest of the properties (complete and \_\_typename)
-
will stay untouched.
+
> **Note:** In the above example, we've used
+
> [graphql-tag](https://github.com/apollographql/graphql-tag) because `writeFragment` only accepts
+
> GraphQL `DocumentNode`s as inputs, and not strings.
+
+
This can be useful for instance if we have a mutation that doesn't return the type that the GraphQL
+
API will alter in the background. Suppose we had a `updateTodoText` mutation that doesn't allow us
+
to access the updated `Todo`. In such a case `cache.writeFragment` allows us to make the change
+
manually:
```js
+
import gql from 'graphql-tag';
+
const cache = cacheExchange({
updates: {
Mutation: {
-
addTodo: (result, args, cache, info) => {
-
cache.updateQuery({ query: Todos }, data => {
-
data.todos.push(result);
-
return data;
-
});
-
},
-
},
-
Subscription: {
-
newTodo: (result, args, cache) => {
-
cache.updateQuery({ query: Todos }, data => {
-
data.todos.push(result);
-
return data;
-
});
+
updateTodoText: (result, args, cache, info) => {
+
cache.writeFragment(
+
gql`
+
fragment _ on Todo {
+
id
+
text
+
}
+
`,
+
{ id: args.id, text: args.text }
+
);
},
},
},
});
```
-
The last method we'll look at is essentially an escape hatch
-
this is called `invalidateQuery` and accepts a `query` as
-
the first argument and variables for that query as the second.
+
In this example we haven't used `result` at all, but have written to a `Todo` fragment using the
+
arguments (`args`) that have been supplied to `Mutation.updateTodoText`. This can also be used in
+
combination with `cache.readFragment` or `cache.resolve` if we need to retrieve arbitrary data from
+
the cache, before using `cache.writeFragment` to update some data.
+
+
## cache.invalidate
-
This method should only be needed when a mutation has some sort
-
of side-effect, let's say when a user subscribes to a certain subject
-
that user gets an additional agenda.
+
The `cache.invalidate` method is useful for evicting a single entity from the cache. When a user
+
logs out or a mutation deletes an item from a list it can be tedious to update every link in our
+
normalized data to said entity, instead the `cache.invalidate` method can be used to quickly remove
+
the entity itself.
-
This can't really be derived from the mutation response so we opt
-
to invalidate our agenda's as followed:
+
Note that this may lead to an additional request to the GraphQL API, unless you're making use of the
+
["Schema Awareness" feature](./schema-awareness.md), since deleting an entity may cause cache
+
misses for all queries that depend on this entity.
```js
const cache = cacheExchange({
updates: {
Mutation: {
-
subscribeToSubject: (result, args, cache, info) => {
-
cache.invalidateQuery(AgendasForUser, { userId: args.userId });
+
deleteTodo: (result, args, cache, info) => {
+
cache.invalidate({ __typename: 'Todo', id: args.id });
},
},
},
});
```
-
Next time we hit the query for agendas this will be refetched.
+
The above example deletes a `Todo` with a given `id` from the arguments, when `Mutation.deleteTodo`
+
is executed. This will cause all queries that reference this `Todo` to automatically update.
## Optimistic updates
-
Let's say we want to work offline or we don't want to wait for
-
responses from the server.
+
If we know what result a mutation may return, why wait for the GraphQL API to fulfill our mutations?
+
The _Optimistic Updates_ configuration allows us to set up "temporary" results for mutations, which
+
will be applied immediately. This is a great solution to reduce the waiting time for the user.
-
Optimistic responses can be a great solution to this problem. Optimisitc
-
responses are simply a mapping of the name of a mutation to a function.
-
This function gets three
-
arguments:
+
This technique is often used with one-off mutations that are assumed to succeed, like starring a
+
repository, or liking a tweet. In such cases it's often desirable to make the interaction feel
+
as instant as possible.
+
+
The `optimistic` configurations are similar to `resolvers` as well. We supply an function to
+
`Mutation` fields that must return some data. When said mutation is then executed, _Graphcache_
+
applies a temporary update using the supplied optimistic data that is reverted when the real
+
mutation completes and an actual result comes back from the API. The temporary update is also
+
reverted if the GraphQL mutation fails.
+
+
The `optimistic` functions receive the same arguments as `resolvers` functions, except for `parent`:
- `variables` – The variables used to execute the mutation.
-
- `cache` – The cache we've already seen in [resolvers](./resolvers.md) and
-
[updates](./updates.md). This can be used to get a certain entity/property
-
from the cache.
+
- `cache` – The cache we've already seen in [resolvers](./computed-queries.md) and in the previous
+
examples on this page. In optimistic updates it's useful to retrieve more data from the cache.
- `info` – Contains the used fragments and field arguments.
-
Let's see an example.
+
In the following example we assume that we'd like to implement an optimistic result for a
+
`favoriteTodo` mutation, which sets `favorite` on a `Todo` to `true`:
```js
-
const myGraphCache = cacheExchange({
+
const cache = cacheExchange({
optimistic: {
-
addTodo: (variables, cache, info) => {
-
console.log(variables); // { id: '1', text: 'optimistic' }
-
return {
-
...variables,
-
__typename: 'Todo', // We still have to let the cache know what entity we are on.
-
};
-
},
+
favoriteTodo: (variables, cache, info) => ({
+
__typename: 'Todo',
+
id: variables.id,
+
favorite: true,
+
}),
},
});
```
-
Now that we return `variables` our `Todo:1` will be updated to have
-
the new `text` property. In our cache this will form a layer above
-
the property. When a response from the server comes in this layer
-
will be removed and the response from the server will be used to
-
replace the original properties.
+
Since we return an assumed result in our `optimistic` configuration, `Mutation.favoriteTodo` will be
+
automatically applied and `favorite` will seemingly flip to `true` instantly for the queries that
+
observe `Todo.query`.
+
+
### Reading on
+
+
[On the next page we'll learn about "Schema-awareness".](./schema-awareness.md)
+1 -6
docs/graphcache/help.md exchanges/graphcache/help.md
···
-
---
-
title: Help
-
order: 6
-
---
-
# Help!
**This document lists out all errors and warnings in `@urql/exchange-graphcache`.**
···
## (19) Can't generate a key for invalidate(...)
> Can't generate a key for invalidate(...).
-
> You need to pass in a valid key (__typename:id) or an object with the "__typename" property and an "id" or "_id" property.
+
> You need to pass in a valid key (**typename:id) or an object with the "**typename" property and an "id" or "\_id" property.
You probably have called `cache.invalidate` with data that the cache can't generate a key for.
+75 -33
docs/graphcache/normalized-caching.md
···
# Normalized Caching
-
In urql we have the option to utilize a normalized caching mechanism,
-
this opens up a world of new features ranging from automatic updates
-
to offline capabilities.
+
With _Graphcache_ all data is stored in a normalized data structure. It automatically uses
+
`__typename` information and `id` fields on entities to create a normalized table of data. Since
+
GraphQL deals with connected data in a tree structure, each entity may link to other entities or
+
even lists of entities, which we call "links". The scalar fields on entities like numbers, strings,
+
etc is what we call "records."
-
Instead of storing a query by its `operationKey` we'll store the entities
-
we get back and normalize them so for instance:
+
Instead of storing query results whole documents, like `urql` does with [its default "Document
+
Caching"](../basics/document-caching.md), _Graphcache_ flattens all data it receives automatically.
+
If we looked at doing this manually on the following piece of data, we'd separate each object into a
+
list of key-value entries per entity.
-
```js
-
todo: {
-
__typename: 'Todo',
-
id: 1,
-
title: 'implement graphcache',
-
author: {
-
__typename: 'Author',
-
id: 1,
-
name: 'urql-team',
+
```json
+
{
+
"__typename": "Query",
+
"todo": {
+
"__typename": "Todo",
+
"id": 1,
+
"title": "implement graphcache",
+
"author": {
+
"__typename": "Author",
+
"id": 1,
+
"name": "urql-team"
+
}
}
}
```
-
will become
+
The above would look like the following once we normalized the data:
-
```js
+
```json
{
-
"Todo:1.title": 'implement graphcache',
-
"Todo:1.author": 1,
-
"Author:1.name": 'urql-team',
+
"Query": {
+
"todo": "Todo:1" // link
+
},
+
"Todo:1": {
+
"__typename": "Todo",
+
"id": "1", // record
+
"title": "implement graphcache", // record
+
"author": "Author:1" // link
+
},
+
"Author:1": {
+
"__typename": "Todo",
+
"id": "1", // record
+
"name": "urql-team" // record
+
}
}
```
-
This allows us to for instance take the result of an update mutation to
-
`Todo:1` and automatcally update altered properties, this also allows us to
-
reuse entities. We will always try to create a key with the `__typename` and the
-
`id` or `_id` whichever is present.
+
This is vewry similar to how we'd go about creating a state management store manually, except that
+
_Graphcache_ can use the GraphQL document and the `__typename` field to perform this normalization
+
automatically.
+
+
The interesting part of normalization starts when we read from the cache instead of writing to it.
+
Multiple results may refer to the same piece of data — a mutation for instance may update `"Todo:1"`
+
later on in the app. This would automatically cause _Graphcache_ to update any related queries in
+
the entire app, because all references to each each entity are shared.
+
+
## Key Generation
+
+
As we saw in the previous example, by default _Graphcache_ will attempt to generate a key by
+
combining the `__typename` of a piece of data with the `id` or `_id` fields, if they're present. For
+
instance, `{ __typename: 'Author', id: 1 }` becomes `"Author:1"`.
-
The custom `keys` property comes into play when we don't have an `id` or `_id`,
-
in this scenario graphcache will warn us and ask to create a key for said entity.
+
_Graphcache_ will log a warning when these fields weren't requested as part of a query's selection
+
set or aren't present in the data. This can be useful if we forget to include them in our queries.
+
In general, _Graphcache_ will always output warnings in development when it assumes that something
+
went wrong.
+
+
However, in your schema you may have types that don't have an `id` or `_id` field, say maybe some
+
types have a `key` field instead. In such cases the custom `keys` configuration comes into play
Let's look at an example. Say we have a set of todos each with a `__typename`
of `Todo`, but instead of identifying on `id` or `_id` we want to identify
-
each record by its `name`.
+
each record by its `name`:
```js
-
import { cacheExchange } from '@urql/exchange-graphcache';
+
import { cacheExchange } from '@urql/exchange-graphcache';
-
const myGraphCache = cacheExchange({
+
const cache = cacheExchange({
keys: {
Todo: data => data.name,
},
});
```
-
Now when we query or write a Todo it will use `name` to identify the record
-
in the cache. All other records will be resolved the traditional way.
+
This will cause our cache to generate a key from `__typename` and `name` instead if an entity's type
+
is `Todo`.
+
+
Similarly some pieces of data shouldn't be normalized at all. If _Graphcache_ can't find the `id` or
+
`_id` fields it will log a warning and _embed the data_ instead. Embedding the data means that it
+
won't be normalized because the generated key is `null` and will instead only be referenced by the
+
parent entity.
-
In the same way, you could say that a Todo meant only for admin access is
-
prefixed with `admin`.
+
You can force this behaviour and silence the warning by making a `keys` function that returns `null`
+
immediately. This can be useful for types that aren't globally unique, like a `GeoPoint`:
```js
const myGraphCache = cacheExchange({
keys: {
-
Todo: data => (data.isAdminOnly ? `admin-${data.name}` : data.name),
+
GeoPoint: () => null,
},
});
```
+
+
### Reading on
+
+
[On the next page we'll learn about "Computed queries".](./computed-queries.md)
+16 -2
docs/graphcache/schema-awareness.md
···
---
-
title: Schema-awareness
+
title: Schema Awareness
order: 4
---
-
# Schema-awareness
+
# Schema Awareness
As mentioned in the docs we allow for the schema to be passed
to the `cacheExchange` this allows for partial results and deterministic
···
With deterministic fragment matching we mean that if you use an interface
or a union we will be 100% sure you're allowed to do so, we'll check if the
type you request can actually be returned from this union/interface.
+
+
## Getting your schema
But how do you get this schema? Well let's consider some steps, first
make sure `introspection` is turned on on your server. This is very crucial
···
});
```
+
## Integrating
+
next up we can just import this schema and add it to the cacheExchange:
```js
···
const cache = cacheExchange({ schema });
```
+
+
So what benefits do we have now that graphCache is aware of the shape of our schema?
+
+
### Partial results
+
+
Let's approach this with the example from [Computed queries](./computed-queries.md#resolve) we have
+
our `TodosQuery` result (a list) and now we want to get a specific `Todo` we wire these up through
+
`resolve` but we are missing an optional field for this, without a schema we don't know this is optional
+
and we will not show you the partial result. Now that we have a schema we can check if this is allowed to
+
be left out, we'll return you the entity and fetch the missing properties in the background.
+30
docs/showcase.md
···
# Showcase
+
`urql` wouldn't be the same without our growing and loving community of users,
+
maintainers, and supporters. This page is specifically dedicated to all of you!
+
## Used by folks at
+
+
<a href="https://tripadvisor.com">
+
<img alt="TripAdvisor" height="60" src="./assets/logos/tripadvisor.png" />
+
</a>
+
+
<a href="https://github.com">
+
<img alt="GitHub" height="60" src="./assets/logos/github.png" />
+
</a>
+
+
<a href="https://egghead.io">
+
<img alt="Egghead" height="60" src="./assets/logos/egghead.png" />
+
</a>
+
+
## Articles & Tutorials
+
+
- [Egghead Course](https://egghead.io/lessons/graphql-set-up-an-urql-graphql-provider-in-react?pl=introduction-to-urql-a-react-graphql-client-faaa2bf5)
+
by [Ian Jones](https://twitter.com/_jonesian).
+
- [How To GraphQL: React + urql](https://www.howtographql.com/react-urql/0-introduction/)
+
+
## Community packages
+
+
- [`reason-urql`](https://github.com/FormidableLabs/reason-urql): The official Reason bindings for
+
`urql`.
+
- [`urql-persisted-queries`](https://github.com/Daniel15/urql-persisted-queries): Support for
+
Apollo-style persisted queries.
+
- [`urql-computed-queries`](https://github.com/Drawbotics/urql-computed-exchange): An exchange to
+
compute fields on-the-fly using the `@computed` directive.
+1 -1
exchanges/shared/src/helpers/help.ts
···
type DebugNode = ExecutableDefinitionNode | InlineFragmentNode;
const helpUrl =
-
'\nhttps://github.com/FormidableLabs/urql/blob/master/docs/graphcache/help.md#';
+
'\nhttps://github.com/FormidableLabs/urql/tree/master/exchanges/graphcache/help.md#';
const cache = new Set<string>();
export const currentDebugStack: string[] = [];
+8 -4
packages/site/package.json
···
"build": "react-static build",
"lint": "eslint --ext=js,jsx .",
"clean": "rimraf dist",
-
"prepublishOnly": "run-s clean build"
+
"prepublishOnly": "run-s clean build",
+
"stage:build": "react-static build --staging",
+
"stage:deploy": "node scripts/deploy/surge.js",
+
"prod:build": "react-static build",
+
"prod:deploy": "node scripts/deploy/aws.js"
},
"babel": {
"presets": [
···
"react-router-ga": "^1.0.0",
"react-scroll": "^1.7.15",
"react-static": "^7.2.3",
-
"react-static-plugin-md-pages": "^0.1.1",
+
"react-static-plugin-md-pages": "^0.1.3",
"styled-components": "^5.0.1"
},
"devDependencies": {
···
"@mdx-js/mdx": "^1.5.5",
"config": "^3.0.0",
"lodash": "^4.17.11",
-
"react-static-plugin-mdx": "^7.2.2",
"react-static-plugin-react-router": "^7.2.3",
"react-static-plugin-sitemap": "^7.0.0",
-
"react-static-plugin-styled-components": "^7.2.2"
+
"react-static-plugin-styled-components": "^7.2.2",
+
"surge": "^0.21.3"
}
}
+60
packages/site/scripts/deploy/aws.js
···
+
// TODO: Currently disabled to prevent oopsies
+
process.exit(0);
+
+
/**
+
* Upload docs to appropriate s3 subdirectory.
+
*/
+
const path = require('path');
+
const chalk = require('chalk');
+
const execa = require('execa');
+
+
const PROJECT = 'urql';
+
const DOCS_PATH = `open-source/${PROJECT}`;
+
+
const SRC = path.resolve(__dirname, '../../dist');
+
const BUCKET_NAME = 'formidable.com';
+
const DEST = `s3://${path.join(BUCKET_NAME, DOCS_PATH)}`;
+
+
const AWS_DRY_RUN_FLAG = '--dryrun';
+
const AWS_EXCLUDES = ['*.DS_Store*'];
+
+
// Cache values (in seconds)
+
const CACHE_MAX_AGE_DEFAULT = 10 * 60; // eslint-disable-line no-magic-numbers
+
+
const EXECA_OPTS = {
+
stdio: 'inherit',
+
};
+
+
const { log } = console;
+
const logMsg = msg => log(chalk`[{cyan deploy/aws}] ${msg}`);
+
+
const main = async ({ isDryRun }) => {
+
logMsg(chalk`Uploading files to {cyan ${DEST}}`);
+
await execa(
+
'aws',
+
[
+
's3',
+
'sync',
+
isDryRun ? AWS_DRY_RUN_FLAG : '',
+
'--cache-control',
+
`max-age=${CACHE_MAX_AGE_DEFAULT},public`,
+
'--delete',
+
...AWS_EXCLUDES.reduce(
+
(memo, exc) => memo.concat(['--exclude', exc]),
+
[]
+
),
+
SRC,
+
DEST,
+
].filter(Boolean),
+
EXECA_OPTS
+
);
+
};
+
+
if (require.main === module) {
+
main({
+
isDryRun: process.argv.indexOf('--dryrun') > -1,
+
}).catch(err => {
+
console.error(err); // eslint-disable-line no-console
+
process.exit(1); // eslint-disable-line no-process-exit
+
});
+
}
+42
packages/site/scripts/deploy/surge.js
···
+
/**
+
* Upload docs to surge.
+
*/
+
const path = require('path');
+
const chalk = require('chalk');
+
const execa = require('execa');
+
+
if (!process.env.SURGE_LOGIN) {
+
console.warn('No SURGE_* env variables received. Skipping.');
+
process.exit(0);
+
}
+
+
const { CI_PULL_REQUEST = '' } = process.env;
+
const parts = CI_PULL_REQUEST.split('/');
+
const PR_NUM = parts[parts.length - 1];
+
if (!PR_NUM) {
+
console.warn('Not a pull request. Skipping');
+
process.exit(0);
+
}
+
+
const PROJECT = 'urql';
+
const SRC = path.resolve(__dirname, '../../dist');
+
const DOMAIN = `formidable-com-${PROJECT}-staging-${PR_NUM}.surge.sh`;
+
+
const EXECA_OPTS = {
+
stdio: 'inherit',
+
};
+
+
const { log } = console;
+
const logMsg = msg => log(chalk`[{cyan deploy/surge}] ${msg}`);
+
+
const main = async () => {
+
logMsg(chalk`Uploading files to {cyan ${DOMAIN}}`);
+
await execa('yarn', ['run', 'surge', '--project', SRC, '--domain', DOMAIN], EXECA_OPTS);
+
};
+
+
if (require.main === module) {
+
main().catch(err => {
+
console.error(err); // eslint-disable-line no-console
+
process.exit(1); // eslint-disable-line no-process-exit
+
});
+
}
+6 -7
packages/site/src/app.js
···
import { GlobalStyle } from './styles/global';
import * as theme from './styles/theme';
import Analytics from './google-analytics';
-
+
import { Loading } from './components/loading';
// TODO: import NotFound from './screens/404';
const App = () => {
return (
<Root>
-
{/* TODO: create a better fallback component */}
-
<React.Suspense fallback={<h1>Loading</h1>}>
-
<ThemeProvider theme={theme}>
-
<GlobalStyle />
+
<ThemeProvider theme={theme}>
+
<GlobalStyle />
+
<React.Suspense fallback={<Loading />}>
<Analytics id={constants.googleAnalyticsId}>
<Routes />
</Analytics>
-
</ThemeProvider>
-
</React.Suspense>
+
</React.Suspense>
+
</ThemeProvider>
</Root>
);
};
+5 -3
packages/site/src/components/footer.js
···
const FooterDescription = styled.p`
flex: 2;
font-size: 1.4rem;
-
letter-spacing: 0.05rem;
line-height: 1.6;
margin: 2rem 0 0;
max-width: 56rem;
···
}
& a {
color: white;
-
letter-spacing: 0.05em;
transition: opacity 0.4s;
}
& a:hover {
···
list-style: none;
padding: 0px 8px;
text-transform: uppercase;
+
& li {
-
margin-bottom: 1.4rem;
+
margin: 0.2rem 0;
}
+
& a {
color: white;
letter-spacing: 0.05em;
transition: opacity 0.4s;
}
+
& a:hover {
opacity: 0.7;
}
+
& a:visited {
color: white;
}
+2 -2
packages/site/src/components/header.js
···
width: 12rem;
flex-direction: column;
color: #ffffff;
-
p {
-
}
+
text-decoration: none;
`;
const HeaderText = styled.p`
text-transform: uppercase;
+
font-size: 1.5rem;
margin-left: 14px;
line-height: 1.9rem;
margin-bottom: 0;
+85
packages/site/src/components/loading.js
···
+
import React from 'react';
+
import styled, { keyframes } from 'styled-components';
+
import { Container as DocsContainer } from '../screens/docs';
+
import Header from '../screens/docs/header';
+
+
const Container = styled.div`
+
height: 100vh;
+
width: 100%;
+
`;
+
const Loader = styled.div`
+
position: relative;
+
margin: 0 auto;
+
width: ${p => p.theme.spacing.xl};
+
top: calc(50% - ${p => p.theme.spacing.xl});
+
&:before {
+
content: '';
+
display: block;
+
padding-top: 100%;
+
}
+
`;
+
+
const rotate = keyframes`
+
100% {
+
transform: rotate(360deg);
+
}
+
`;
+
+
const dash = keyframes`
+
0% {
+
stroke-dasharray: 1, 200;
+
stroke-dashoffset: 0;
+
}
+
50% {
+
stroke-dasharray: 89, 200;
+
stroke-dashoffset: -35px;
+
}
+
100% {
+
stroke-dasharray: 89, 200;
+
stroke-dashoffset: -124px;
+
}
+
`;
+
+
const Svg = styled.svg`
+
animation: ${rotate} 2s linear infinite;
+
height: 100%;
+
transform-origin: center center;
+
width: 100%;
+
position: absolute;
+
top: 0;
+
bottom: 0;
+
left: 0;
+
right: 0;
+
margin: auto;
+
`;
+
+
const Circle = styled.circle`
+
stroke: ${p => p.theme.colors.accent};
+
stroke-dasharray: 1, 200;
+
stroke-dashoffset: 0;
+
animation: ${dash} 1.5s ease-in-out infinite;
+
stroke-linecap: round;
+
`;
+
+
export const Loading = () => {
+
return (
+
<DocsContainer>
+
<Container>
+
<Header />
+
<Loader>
+
<Svg className="circular" viewBox="25 25 50 50">
+
<Circle
+
className="path"
+
cx="50"
+
cy="50"
+
r="20"
+
fill="none"
+
strokeWidth="2"
+
strokeMiterlimit="10"
+
/>
+
</Svg>
+
</Loader>
+
</Container>
+
</DocsContainer>
+
);
+
};
+106 -16
packages/site/src/components/mdx.js
···
import React from 'react';
-
import styled from 'styled-components';
+
import styled, { css } from 'styled-components';
import { MDXProvider } from '@mdx-js/react';
import Highlight, { Prism } from 'prism-react-renderer';
···
};
const Pre = styled.pre`
-
background: ${p => p.theme.colors.passiveBg};
+
background: ${p => p.theme.colors.codeBg};
border: 1px solid ${p => p.theme.colors.border};
-
line-height: ${p => p.theme.lineHeights.code};
-
font-size: ${p => p.theme.fontSizes.code};
-
padding: ${p => p.theme.spacing.sm};
border-radius: ${p => p.theme.spacing.xs};
-
-webkit-overflow-scrolling: touch;
+
+
font-size: ${p => p.theme.fontSizes.code};
+
line-height: ${p => p.theme.lineHeights.code};
+
+
max-width: 100%;
overflow-x: auto;
+
-webkit-overflow-scrolling: touch;
+
padding: ${p => p.theme.spacing.sm};
position: relative;
-
max-width: 100%;
+
white-space: pre;
`;
const Code = styled.code`
···
white-space: pre;
`;
-
const InlineCode = styled.code`
-
background: ${p => p.theme.colors.passiveBg};
+
const InlineCode = styled(props => {
+
const children = props.children.replace(/\\\|/g, '|');
+
return <code {...props}>{children}</code>;
+
})`
+
background: ${p => p.theme.colors.codeBg};
color: ${p => p.theme.colors.code};
font-family: ${p => p.theme.fonts.code};
font-size: ${p => p.theme.fontSizes.small};
···
font-feature-settings: normal;
padding: 0 0.2em;
margin: 0;
+
+
a > & {
+
text-decoration: underline;
+
}
+
`;
+
+
const InlineImage = styled.img`
+
display: inline-block;
+
margin: 0 ${p => p.theme.spacing.sm} ${p => p.theme.spacing.md} 0;
+
padding: ${p => p.theme.spacing.xs} ${p => p.theme.spacing.sm};
+
border: 1px solid ${p => p.theme.colors.border};
+
border-radius: ${p => p.theme.spacing.xs};
`;
const ImageWrapper = styled.div`
···
display: block;
padding: ${p => p.theme.spacing.xs} ${p => p.theme.spacing.sm};
border-top: 1px solid ${p => p.theme.colors.border};
-
background: ${p => p.theme.colors.passiveBg};
+
background: ${p => p.theme.colors.codeBg};
font-size: ${p => p.theme.fontSizes.small};
`;
-
const Image = ({ alt, src }) => (
-
<ImageWrapper>
-
<img alt={alt} src={src} />
-
<ImageAlt>{alt}</ImageAlt>
-
</ImageWrapper>
-
);
+
const Image = props => {
+
const { height, width, alt, src } = props;
+
if (height || width) return <InlineImage {...props} />;
+
+
return (
+
<ImageWrapper>
+
<img alt={alt} src={src} />
+
<ImageAlt>{alt}</ImageAlt>
+
</ImageWrapper>
+
);
+
};
const HighlightCode = ({ className = '', children }) => {
const language = getLanguage(className);
···
}
`;
+
const sharedTableCellStyling = css`
+
padding: ${p => p.theme.spacing.xs} ${p => p.theme.spacing.sm};
+
border-left: 1px solid ${p => p.theme.colors.passiveBg};
+
border-bottom: 1px solid ${p => p.theme.colors.passiveBg};
+
`;
+
+
const TableHeader = styled.th`
+
text-align: left;
+
white-space: nowrap;
+
${sharedTableCellStyling}
+
`;
+
+
const TableCell = styled.td`
+
width: min-content;
+
${sharedTableCellStyling}
+
+
${p => {
+
const isCodeOnly = React.Children.toArray(p.children).every(
+
x => x.props && x.props.mdxType === 'inlineCode'
+
);
+
return (
+
isCodeOnly &&
+
css`
+
background-color: ${p.theme.colors.codeBg};
+
+
& > ${InlineCode} {
+
background: none;
+
padding: 0;
+
margin: 0;
+
}
+
`
+
);
+
}}
+
+
&:last-child {
+
min-width: 20rem;
+
width: max-content;
+
}
+
+
&:first-child {
+
white-space: nowrap;
+
}
+
+
&:nth-child(2) {
+
overflow-wrap: break-word;
+
min-width: 20rem;
+
}
+
`;
+
+
const TableOverflow = styled.div`
+
overflow-x: scroll;
+
`;
+
+
const Table = styled.table`
+
border: 1px solid ${p => p.theme.colors.passiveBg};
+
border-collapse: collapse;
+
`;
+
+
const TableWithOverflow = props => (
+
<TableOverflow>
+
<Table {...props} />
+
</TableOverflow>
+
);
+
const components = {
pre: Pre,
img: Image,
blockquote: Blockquote,
inlineCode: InlineCode,
code: HighlightCode,
+
table: TableWithOverflow,
+
th: TableHeader,
+
td: TableCell,
};
export const MDXComponents = ({ children }) => (
+13 -7
packages/site/src/components/navigation.js
···
export const SidebarContainer = styled.div`
display: ${p => (p.hidden ? 'none' : 'block')};
position: absolute;
+
@media ${({ theme }) => theme.media.sm} {
display: block;
-
position: static;
+
position: relative;
width: ${p => p.theme.layout.sidebar};
}
`;
···
export const SideBarStripes = styled.div`
border-left: ${p => p.theme.layout.stripes} solid #8196ff;
border-right: ${p => p.theme.layout.stripes} solid #bcc6fa;
+
position: absolute;
height: 100%;
width: 0;
-
position: fixed;
left: 0;
top: 0;
bottom: 0;
···
z-index: 1;
overflow-y: scroll;
min-height: 100%;
-
width: 100%;
-
padding: ${p => p.theme.spacing.md};
-
padding-right: ${p => p.theme.spacing.sm};
-
background: ${p => p.theme.colors.bg};
line-height: ${p => p.theme.lineHeights.body};
font-size: ${p => p.theme.fontSizes.small};
+
padding: ${p => p.theme.spacing.md};
+
padding-right: ${p => p.theme.spacing.sm};
+
+
background-color: ${p => p.theme.colors.bg};
+
border-right: 1px solid ${p => p.theme.colors.border};
+
border-top: 1px solid ${p => p.theme.colors.border};
+
@media ${({ theme }) => theme.media.sm} {
+
border: none;
+
background: none;
+
padding-top: ${p => p.theme.spacing.lg};
width: ${p => p.theme.layout.sidebar};
}
`;
···
export const SidebarNavSubItemWrapper = styled.div`
padding-left: ${p => p.theme.spacing.sm};
-
border-left: 1px solid ${p => p.theme.colors.activeBorder};
margin-bottom: ${p => p.theme.spacing.xs};
`;
+1 -1
packages/site/src/components/section-title.js
···
export const SectionTitle = styled.h2`
color: #fff;
-
font-size: 2.5rem;
+
font-size: 4.5rem;
flex: auto;
line-height: 1.3;
margin: 2rem 0 3rem;
+10 -1
packages/site/src/components/sidebar.js
···
width: ${p => p.theme.layout.logo};
margin-bottom: ${p => p.theme.spacing.sm};
align-self: center;
+
@media ${p => p.theme.media.sm} {
display: block;
}
···
flex-direction: column;
padding-top: ${p => p.theme.spacing.xs};
padding-bottom: ${p => p.theme.spacing.lg};
+
padding-left: ${p => p.theme.spacing.sm};
`;
const relative = (from, to) => {
if (!from || !to) return null;
let pathname = path.relative(path.dirname(from), to);
+
if (!pathname)
+
pathname = path.join(path.relative(from, to), path.basename(to));
if (from.endsWith('/')) pathname = '../' + pathname;
return { pathname };
};
···
return null;
}
-
return tree.children.map(page => {
+
let children = tree.children;
+
if (tree.frontmatter && tree.originalPath) {
+
children = [{ ...tree, children: undefined }, ...children];
+
}
+
+
return children.map(page => {
return (
<Fragment key={page.key}>
<SidebarNavItem to={relative(pathname, page.path)}>
+34 -21
packages/site/src/screens/docs/article.js
···
flex: 1;
width: 100%;
position: sticky;
-
-
display: ${p => (p.sidebarOpen ? 'none' : 'flex')};
+
display: flex;
flex-direction: row-reverse;
`;
···
}))`
flex: 1;
min-height: 100vh;
-
margin: 0 ${p => p.theme.spacing.md};
-
padding: ${p => p.theme.spacing.md} 0;
+
background: ${p => p.theme.colors.bg};
+
padding: ${p => p.theme.spacing.md};
+
+
@media ${p => p.theme.media.lg} {
+
padding: ${p => p.theme.spacing.lg};
+
}
+
+
overflow-wrap: break-word;
+
word-wrap: break-word;
+
word-break: break-word;
+
hyphens: auto;
`;
const Legend = styled.aside`
···
position: sticky;
top: ${p => p.theme.layout.header};
max-height: 100vh;
-
width: ${p => p.theme.layout.legend};
-
max-width: ${p => p.theme.layout.legendMaxWidth};
-
padding: ${p => p.theme.spacing.md} 0;
-
margin: ${p => p.theme.spacing.sm} ${p => p.theme.spacing.md};
+
width: 100%;
+
max-width: ${p => p.theme.layout.legend};
+
margin: 0 ${p => p.theme.spacing.md};
+
padding: ${p => p.theme.spacing.lg} 0;
}
`;
···
const HeadingList = styled.ul`
list-style-type: none;
margin: 0;
-
padding-left: ${p => p.theme.spacing.sm};
-
border-left: 1px solid ${p => p.theme.colors.border};
+
padding: 0;
`;
-
const HeadingItem = styled.a`
-
font-size: ${p => p.theme.fontSizes.small};
-
font-weight: ${p => p.theme.fontWeights.body};
-
color: ${p => p.theme.colors.heading};
-
text-decoration: none;
-
opacity: 0.7;
+
const HeadingItem = styled.li`
+
line-height: ${p => p.theme.lineHeights.heading};
+
margin-bottom: ${p => p.theme.spacing.xs};
+
+
> a {
+
font-size: ${p => p.theme.fontSizes.small};
+
font-weight: ${p => p.theme.fontWeights.body};
+
color: ${p => p.theme.colors.heading};
+
text-decoration: none;
+
opacity: 0.7;
+
}
`;
const SectionList = () => {
···
if (!page) return null;
const headings = page.headings.filter(x => x.depth > 1);
+
if (headings.length === 0) return null;
return (
<>
<LegendTitle>In this section</LegendTitle>
<HeadingList>
{headings.map(heading => (
-
<li key={heading.slug}>
-
<HeadingItem href={`#${heading.slug}`}>{heading.value}</HeadingItem>
-
</li>
+
<HeadingItem key={heading.slug}>
+
<a href={`#${heading.slug}`}>{heading.value}</a>
+
</HeadingItem>
))}
</HeadingList>
</>
);
};
-
const Article = ({ children, sidebarOpen }) => (
-
<Container sidebarOpen={sidebarOpen}>
+
const Article = ({ children }) => (
+
<Container>
<Legend>
<SectionList />
</Legend>
+7 -10
packages/site/src/screens/docs/header.js
···
import formidableLogo from '../../assets/logos/logo-formidable.svg';
-
const Fixed = styled.div`
+
const Fixed = styled.header`
position: fixed;
top: 0;
left: 0;
right: 0;
width: 100%;
z-index: 1;
+
+
background: ${p => p.theme.colors.bg};
+
border-bottom: 1px solid ${p => p.theme.colors.border};
+
padding: 0 ${p => p.theme.spacing.md};
+
box-shadow: ${p => p.theme.shadows.header};
`;
-
const Wrapper = styled.header`
+
const Wrapper = styled.div`
width: 100%;
max-width: ${p => p.theme.layout.page};
margin: 0 auto;
-
background: ${p => p.theme.colors.bg};
-
height: ${p => p.theme.layout.header};
-
padding: 0 ${p => p.theme.spacing.md};
padding-top: 2px;
-
-
border-bottom: 1px solid ${p => p.theme.colors.border};
-
border-left: 1px solid ${p => p.theme.colors.border};
-
border-right: 1px solid ${p => p.theme.colors.border};
-
display: flex;
flex-direction: row;
align-items: center;
+4 -8
packages/site/src/screens/docs/index.js
···
import burger from '../../assets/burger.svg';
import closeButton from '../../assets/close.svg';
-
const Container = styled.div`
+
export const Container = styled.div`
display: flex;
flex-direction: row;
width: 100%;
max-width: ${p => p.theme.layout.page};
margin: 0 auto;
-
-
background: ${p => p.theme.colors.bg};
-
border-left: 1px solid ${p => p.theme.colors.border};
-
border-right: 1px solid ${p => p.theme.colors.border};
-
margin-top: ${p => p.theme.layout.header};
`;
···
cursor: pointer;
display: block;
margin: ${p => p.theme.spacing.sm} ${p => p.theme.spacing.md};
-
position: absolute;
+
position: fixed;
right: 0;
top: 0;
z-index: 1;
+
@media ${p => p.theme.media.sm} {
display: none;
}
···
onClick={() => setSidebarOpen(prev => !prev)}
/>
<Sidebar sidebarOpen={sidebarOpen} />
-
<Article sidebarOpen={sidebarOpen}>{props.children}</Article>
+
<Article>{props.children}</Article>
</Container>
</>
);
+3 -7
packages/site/src/screens/home/hero.js
···
const HeroTitle = styled.h1`
font-size: 5rem;
-
letter-spacing: 0.15em;
margin: 0 0 2rem;
text-align: center;
text-transform: uppercase;
width: 100%;
+
color: #fff;
+
@media (min-width: 768px) {
font-size: 5.8rem;
margin: 4rem 0 2rem;
···
`;
const HeroBody = styled.p`
-
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
-
letter-spacing: 0.08em;
font-size: 2rem;
line-height: 3rem;
text-align: left;
···
padding: 0.33rem 1.5rem;
line-height: 3.44rem;
font-size: 14px;
-
letter-spacing: 0.2px;
margin: 0;
`;
const HeroNPMButton = styled.button`
···
font-style: normal;
font-stretch: normal;
line-height: normal;
-
letter-spacing: 1px;
color: #383838;
border: 0;
text-transform: uppercase;
···
line-height: 4rem;
text-align: center;
text-transform: uppercase;
-
letter-spacing: 1px;
color: #383838;
border: 0;
margin-top: 5rem;
···
color: white;
display: inline-block;
font-size: 1.7rem;
-
letter-spacing: 0.05em;
transition: opacity 0.4s;
text-transform: uppercase;
+
text-decoration: none;
}
& li a:hover {
color: #8196ff;
+1 -1
packages/site/src/styles/global.js
···
font-weight: ${p => p.theme.fontWeights.links};
}
-
p, h1, h2, h3 {
+
table, pre, p, h1, h2, h3 {
margin: 0 0 ${p => p.theme.spacing.md} 0;
}
+5 -4
packages/site/src/styles/theme.js
···
];
export const colors = {
-
passiveBg: '#f6f6f6',
+
passiveBg: '#f2f2f2',
+
codeBg: '#f0f7fb',
bg: '#ffffff',
border: '#ececec',
activeBorder: '#a2b1ff',
···
export const layout = {
page: '144rem',
header: '4.8rem',
-
stripes: '0.9rem',
+
stripes: '1rem',
sidebar: '28rem',
-
legend: '100%',
-
legendMaxWidth: '25rem',
+
legend: '22rem',
logo: '12rem',
};
···
};
export const shadows = {
+
header: 'rgba(0, 0, 0, 0.09) 0px 2px 10px -3px',
input: 'rgba(0, 0, 0, 0.09) 0px 2px 10px -3px',
};
+7 -1
packages/site/static.config.js
···
+
import * as os from 'os';
import { resolve } from 'path';
import constants from './src/constants';
import Document from './src/html';
+
+
const isStaging = process.env.REACT_STATIC_STAGING === 'true';
+
const basePath = 'open-source/urql';
export default {
plugins: [
···
paths: {
src: 'src',
-
dist: 'dist',
+
dist: isStaging ? `dist/${basePath}` : 'dist',
buildArtifacts: 'node_modules/.cache/react-static/artifacts/',
devDist: 'node_modules/.cache/react-static/dist/',
temp: 'node_modules/.cache/react-static/temp/',
···
getSiteData: () => ({
title: constants.docsTitle,
}),
+
+
maxThreads: Math.min(8, os.cpus().length / 2),
getRoutes: async () => [
{
+3 -3
scripts/rollup/build.js
···
const workspaceRoot = path.resolve(__dirname, '../../');
-
let packages = glob('{packages,exchanges}/*/package.json').map(pkg => {
-
return path.resolve(pkg, '../');
-
});
+
let packages = glob('{packages,exchanges}/*/package.json')
+
.filter(pkg => !require(path.join(workspaceRoot, pkg)).private)
+
.map(pkg => path.resolve(pkg, '../'));
// CircleCI parallelism
// See: https://github.com/facebook/react/blob/901d76bc5c8dcd0fa15bb32d1dfe05709aa5d273/scripts/rollup/build.js#L705-L710
+317 -37
yarn.lock
···
version "2.0.0"
resolved "https://registry.yarnpkg.com/@kristoferbaxter/estree-walker/-/estree-walker-2.0.0.tgz#52ee40f9f8b59bbf00cac477a777aeb830ea5952"
-
"@mdx-js/loader@^1.3.0":
-
version "1.5.5"
-
resolved "https://registry.yarnpkg.com/@mdx-js/loader/-/loader-1.5.5.tgz#b658534153b3faab8f93ffc790c868dacc5b43d3"
-
integrity sha512-2/2WX73qj79Kv2cYk14kQsN/aypAH3RPzuNMx1gxwZjj77G0N6tzhM9WFkEDM/SXjasWep03ZmSRb9d//b2D8w==
-
dependencies:
-
"@mdx-js/mdx" "^1.5.5"
-
"@mdx-js/react" "^1.5.5"
-
loader-utils "1.2.3"
-
"@mdx-js/mdx@^1.5.5":
version "1.5.5"
resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.5.5.tgz#09dc8932af84e5baf5add2625ad0250a117c3363"
···
resolved "https://registry.yarnpkg.com/async-sema/-/async-sema-3.0.0.tgz#9e22d6783f0ab66a1cf330e21a905e39b3b3a975"
integrity sha512-zyCMBDl4m71feawrxYcVbHxv/UUkqm4nKJiLu3+l9lfiQha6jQ/9dxhrXLnzzBXVFqCTDwiUkZOz9XFbdEGQsg==
+
async@0.2.x, async@~0.2.9:
+
version "0.2.10"
+
resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
+
integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E=
+
async@^2.6.2:
version "2.6.3"
resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
···
blob@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683"
+
+
block-stream@*:
+
version "0.0.9"
+
resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
+
integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=
+
dependencies:
+
inherits "~2.0.0"
bluebird@^3.5.0, bluebird@^3.5.1, bluebird@^3.5.5:
version "3.7.2"
···
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.2.0.tgz#e8b988d9206c692302d8ee834e7a85c0144d8f77"
integrity sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ==
+
cli-table3@^0.5.1:
+
version "0.5.1"
+
resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202"
+
integrity sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==
+
dependencies:
+
object-assign "^4.1.0"
+
string-width "^2.1.1"
+
optionalDependencies:
+
colors "^1.1.2"
+
cli-truncate@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574"
···
color-convert "^1.9.1"
color-string "^1.5.2"
+
colors@0.6.x:
+
version "0.6.2"
+
resolved "https://registry.yarnpkg.com/colors/-/colors-0.6.2.tgz#2423fe6678ac0c5dae8852e5d0e5be08c997abcc"
+
integrity sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w=
+
colors@1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM=
+
+
colors@^1.1.2:
+
version "1.4.0"
+
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
+
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
version "1.0.8"
···
dependencies:
array-find-index "^1.0.1"
+
cycle@1.0.x:
+
version "1.0.3"
+
resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2"
+
integrity sha1-IegLK+hYD5i0aPN5QwZisEbDStI=
+
cyclist@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
···
dedent@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
+
+
deep-equal@*:
+
version "2.0.1"
+
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.1.tgz#fc12bbd6850e93212f21344748682ccc5a8813cf"
+
integrity sha512-7Et6r6XfNW61CPPCIYfm1YPGSmh6+CliYeL4km7GWJcpX5LTAflGF8drLLR+MZX+2P3NZfAfSduutBbSWqER4g==
+
dependencies:
+
es-abstract "^1.16.3"
+
es-get-iterator "^1.0.1"
+
is-arguments "^1.0.4"
+
is-date-object "^1.0.1"
+
is-regex "^1.0.4"
+
isarray "^2.0.5"
+
object-is "^1.0.1"
+
object-keys "^1.1.1"
+
regexp.prototype.flags "^1.2.0"
+
side-channel "^1.0.1"
+
which-boxed-primitive "^1.0.1"
+
which-collection "^1.0.0"
deep-equal@^1.0.1:
version "1.1.1"
···
dependencies:
is-arrayish "^0.2.1"
-
es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.4:
+
es-abstract@^1.16.3, es-abstract@^1.17.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.4:
version "1.17.4"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184"
dependencies:
···
string.prototype.trimleft "^2.1.1"
string.prototype.trimright "^2.1.1"
+
es-get-iterator@^1.0.1:
+
version "1.1.0"
+
resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.0.tgz#bb98ad9d6d63b31aacdc8f89d5d0ee57bcb5b4c8"
+
integrity sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==
+
dependencies:
+
es-abstract "^1.17.4"
+
has-symbols "^1.0.1"
+
is-arguments "^1.0.4"
+
is-map "^2.0.1"
+
is-set "^2.0.1"
+
is-string "^1.0.5"
+
isarray "^2.0.5"
+
es-to-primitive@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
···
version "1.4.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
+
eyes@0.1.x:
+
version "0.1.8"
+
resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0"
+
integrity sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=
+
fast-deep-equal@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
···
version "2.1.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805"
+
fstream@>=1.0.12:
+
version "1.0.12"
+
resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045"
+
integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==
+
dependencies:
+
graceful-fs "^4.1.2"
+
inherits "~2.0.0"
+
mkdirp ">=0.5 0"
+
rimraf "2"
+
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
···
slash "^3.0.0"
which-pm-runs "^1.0.0"
+
i@0.3.x:
+
version "0.3.6"
+
resolved "https://registry.yarnpkg.com/i/-/i-0.3.6.tgz#d96c92732076f072711b6b10fd7d4f65ad8ee23d"
+
integrity sha1-2WyScyB28HJxG2sQ/X1PZa2O4j0=
+
iconv-lite@0.4.24, iconv-lite@^0.4.24:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
···
once "^1.3.0"
wrappy "1"
-
inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
+
inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
···
figures "^2.0.0"
run-async "^2.3.0"
-
inquirer@^6.5.1:
+
inquirer@^6.2.2, inquirer@^6.5.1:
version "6.5.2"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca"
dependencies:
···
version "0.3.2"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
+
is-bigint@^1.0.0:
+
version "1.0.0"
+
resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.0.tgz#73da8c33208d00f130e9b5e15d23eac9215601c4"
+
integrity sha512-t5mGUXC/xRheCK431ylNiSkGGpBp8bHENBcENTkDT6ppwPzEVxNGZRvgvmOEfbWkFhA7D2GEuE2mmQTr78sl2g==
+
is-binary-path@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
···
dependencies:
binary-extensions "^2.0.0"
-
is-boolean-object@^1.0.1:
+
is-boolean-object@^1.0.0, is-boolean-object@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e"
integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ==
···
resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.0.0.tgz#2cb0df0e75e2d064fe1864c37cdeacb7b2dcf25b"
integrity sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==
+
is-domain@0.0.1:
+
version "0.0.1"
+
resolved "https://registry.yarnpkg.com/is-domain/-/is-domain-0.0.1.tgz#7ffb288d5cced6b07c4f2df91c9be9153511348e"
+
integrity sha1-f/sojVzO1rB8Ty35HJvpFTURNI4=
+
is-extendable@^0.1.0, is-extendable@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
···
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7"
+
is-map@^2.0.1:
+
version "2.0.1"
+
resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.1.tgz#520dafc4307bb8ebc33b813de5ce7c9400d644a1"
+
integrity sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==
+
is-module@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
···
version "4.0.1"
resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
-
is-number-object@^1.0.4:
+
is-number-object@^1.0.3, is-number-object@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197"
integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==
···
version "1.2.0"
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4"
+
is-set@^2.0.1:
+
version "2.0.1"
+
resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.1.tgz#d1604afdab1724986d30091575f54945da7e5f43"
+
integrity sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==
+
is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
···
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3"
-
is-string@^1.0.5:
+
is-string@^1.0.4, is-string@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6"
···
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
+
is-weakmap@^2.0.1:
+
version "2.0.1"
+
resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2"
+
integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==
+
+
is-weakset@^2.0.1:
+
version "2.0.1"
+
resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83"
+
integrity sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==
+
is-whitespace-character@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz#0858edd94a95594c7c9dd0b5c174ec6e45ee4aa7"
···
version "2.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e"
+
isarray@^2.0.5:
+
version "2.0.5"
+
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
+
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
+
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
···
node-fetch "^2.2.0"
unfetch "^4.0.0"
-
isstream@~0.1.2:
+
isstream@0.1.x, isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
···
loader-utils@1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
+
integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
dependencies:
big.js "^5.2.2"
emojis-list "^2.0.0"
···
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
-
minimatch@3.0.4, minimatch@^3.0.4:
+
minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
dependencies:
···
minimist@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
+
+
minimist@1.1.1:
+
version "1.1.1"
+
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.1.tgz#1bc2bc71658cdca5712475684363615b0b4f695b"
+
integrity sha1-G8K8cWWM3KVxJHVoQ2NhWwtPaVs=
minimist@1.x, minimist@^1.1.1, minimist@^1.2.0:
version "1.2.0"
···
version "0.3.5"
resolved "https://registry.yarnpkg.com/mixme/-/mixme-0.3.5.tgz#304652cdaf24a3df0487205e61ac6162c6906ddd"
-
mkdirp@0.5.1, mkdirp@0.x, mkdirp@^0.5.1, mkdirp@~0.5.1:
+
mkdirp@0.5.1, mkdirp@0.x, mkdirp@0.x.x, "mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@~0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
dependencies:
minimist "0.0.8"
+
+
moniker@0.1.2:
+
version "0.1.2"
+
resolved "https://registry.yarnpkg.com/moniker/-/moniker-0.1.2.tgz#872dfba575dcea8fa04a5135b13d5f24beccc97e"
+
integrity sha1-hy37pXXc6o+gSlE1sT1fJL7MyX4=
moo@^0.5.0:
version "0.5.1"
···
version "0.0.7"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
-
mute-stream@0.0.8:
+
mute-stream@0.0.8, mute-stream@~0.0.4:
version "0.0.8"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
···
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
+
ncp@0.4.x:
+
version "0.4.2"
+
resolved "https://registry.yarnpkg.com/ncp/-/ncp-0.4.2.tgz#abcc6cbd3ec2ed2a729ff6e7c1fa8f01784a8574"
+
integrity sha1-q8xsvT7C7Spyn/bnwfqPAXhKhXQ=
+
nearley@^2.7.10:
version "2.19.1"
resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.19.1.tgz#4af4006e16645ff800e9f993c3af039857d9dbdc"
···
neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
+
+
netrc@0.1.4:
+
version "0.1.4"
+
resolved "https://registry.yarnpkg.com/netrc/-/netrc-0.1.4.tgz#6be94fcaca8d77ade0a9670dc460914c94472444"
+
integrity sha1-a+lPysqNd63gqWcNxGCRTJRHJEQ=
next-tick@~1.0.0:
version "1.0.0"
···
dependencies:
find-up "^3.0.0"
+
pkginfo@0.3.x:
+
version "0.3.1"
+
resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.3.1.tgz#5b29f6a81f70717142e09e765bbeab97b4f81e21"
+
integrity sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=
+
+
pkginfo@0.x.x:
+
version "0.4.1"
+
resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff"
+
integrity sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8=
+
please-upgrade-node@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
···
version "0.11.10"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
+
progress@1.1.8:
+
version "1.1.8"
+
resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be"
+
integrity sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=
+
progress@^2.0.0, progress@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
···
integrity sha1-SJZUxpJha4qlWwck+oCbt9tJxb8=
dependencies:
asap "~2.0.3"
+
+
prompt@~0.2.14:
+
version "0.2.14"
+
resolved "https://registry.yarnpkg.com/prompt/-/prompt-0.2.14.tgz#57754f64f543fd7b0845707c818ece618f05ffdc"
+
integrity sha1-V3VPZPVD/XsIRXB8gY7OYY8F/9w=
+
dependencies:
+
pkginfo "0.x.x"
+
read "1.0.x"
+
revalidator "0.1.x"
+
utile "0.2.x"
+
winston "0.8.x"
prompts@^1.1.1:
version "1.2.1"
···
dependencies:
object-is "^1.0.2"
-
react-static-plugin-md-pages@^0.1.1:
-
version "0.1.1"
-
resolved "https://registry.yarnpkg.com/react-static-plugin-md-pages/-/react-static-plugin-md-pages-0.1.1.tgz#7cb19dad439eb81e820d17f48e11a948b248bb93"
-
integrity sha512-g374YtRDgPK6dIMRXaRbFuOYwXta/kASUVAY3rc+z7/YIiCqyjkS1Dkt/pyBAaTaXGsrXI4wZcIUxgN5Z0Fz3w==
+
react-static-plugin-md-pages@^0.1.3:
+
version "0.1.3"
+
resolved "https://registry.yarnpkg.com/react-static-plugin-md-pages/-/react-static-plugin-md-pages-0.1.3.tgz#2c019e9e5e407b5d7701669b9b4581d506662ab5"
+
integrity sha512-aONsWlmeZECJgAz5Er7Jw83RlGLXgf08OFMmn2e3+BADhbheKp7ZWvC50l3AP8jwRr6wslxCO/2kwx99GC0u6A==
dependencies:
"@mdx-js/mdx" "^1.5.5"
"@mdx-js/react" "^1.5.5"
···
unist-util-select "^3.0.1"
unist-util-visit "^2.0.2"
yaml "^1.7.2"
-
-
react-static-plugin-mdx@^7.2.2:
-
version "7.2.2"
-
resolved "https://registry.yarnpkg.com/react-static-plugin-mdx/-/react-static-plugin-mdx-7.2.2.tgz#c90884103ea1c6007a502bf0efadd18e4be4f4a7"
-
integrity sha512-f0T/vq5cryIRvztt2tjpp3AjNdvR1krxl9ADR27AVK2StnFk2T20HraVsTdFDa1FUDrYwRP07JczUZV9gcFtzw==
-
dependencies:
-
"@mdx-js/loader" "^1.3.0"
react-static-plugin-react-router@^7.2.3:
version "7.2.3"
···
pify "^4.0.1"
strip-bom "^3.0.0"
+
read@1.0.5:
+
version "1.0.5"
+
resolved "https://registry.yarnpkg.com/read/-/read-1.0.5.tgz#007a3d169478aa710a491727e453effb92e76203"
+
integrity sha1-AHo9FpR4qnEKSRcn5FPv+5LnYgM=
+
dependencies:
+
mute-stream "~0.0.4"
+
+
read@1.0.x:
+
version "1.0.7"
+
resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4"
+
integrity sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=
+
dependencies:
+
mute-stream "~0.0.4"
+
"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
···
version "0.12.0"
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
+
revalidator@0.1.x:
+
version "0.1.8"
+
resolved "https://registry.yarnpkg.com/revalidator/-/revalidator-0.1.8.tgz#fece61bfa0c1b52a206bd6b18198184bdd523a3b"
+
integrity sha1-/s5hv6DBtSoga9axgZgYS91SOjs=
+
rework-visit@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/rework-visit/-/rework-visit-1.0.0.tgz#9945b2803f219e2f7aca00adb8bc9f640f842c9a"
···
version "1.0.0"
resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3"
-
rimraf@2.6.3:
-
version "2.6.3"
-
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
+
rimraf@2, rimraf@2.x.x, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.3:
+
version "2.7.1"
+
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
dependencies:
glob "^7.1.3"
-
rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.3:
-
version "2.7.1"
-
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
+
rimraf@2.6.3:
+
version "2.6.3"
+
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
dependencies:
glob "^7.1.3"
···
version "0.0.2"
resolved "https://registry.yarnpkg.com/shorthash/-/shorthash-0.0.2.tgz#59b268eecbde59038b30da202bcfbddeb2c4a4eb"
-
side-channel@^1.0.2:
+
side-channel@^1.0.1, side-channel@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.2.tgz#df5d1abadb4e4bf4af1cd8852bf132d2f7876947"
dependencies:
···
dependencies:
extend-shallow "^3.0.0"
+
split@0.3.1:
+
version "0.3.1"
+
resolved "https://registry.yarnpkg.com/split/-/split-0.3.1.tgz#cebcf142bf61bbb64b141628e6db482a2914654c"
+
integrity sha1-zrzxQr9hu7ZLFBYo5ttIKikUZUw=
+
dependencies:
+
through "2"
+
split@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9"
···
version "0.1.8"
resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
+
stack-trace@0.0.x:
+
version "0.0.10"
+
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
+
integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=
+
stack-utils@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8"
···
has-flag "^4.0.0"
supports-color "^7.0.0"
+
surge-fstream-ignore@^1.0.6:
+
version "1.0.6"
+
resolved "https://registry.yarnpkg.com/surge-fstream-ignore/-/surge-fstream-ignore-1.0.6.tgz#e30efce95574b91fd571b5b02f32a611ea829731"
+
integrity sha512-hNN52cz2fYCAzhlHmWPn4aE3bFbpBt01AkWFLljrtSzFvxlipLAeLuLtQ3t4f0RKoUkjzXWCAFK13WoET2iM1A==
+
dependencies:
+
fstream ">=1.0.12"
+
inherits "2"
+
minimatch "^3.0.0"
+
+
surge-ignore@0.2.0:
+
version "0.2.0"
+
resolved "https://registry.yarnpkg.com/surge-ignore/-/surge-ignore-0.2.0.tgz#5a7f8a20a71188cf9e75a2cfe8eb182de90daf3b"
+
integrity sha1-Wn+KIKcRiM+edaLP6OsYLekNrzs=
+
+
surge@^0.21.3:
+
version "0.21.3"
+
resolved "https://registry.yarnpkg.com/surge/-/surge-0.21.3.tgz#8abfba77bac551d0db9bae0fc14a450874fd9e8d"
+
integrity sha512-2rTlyC6ku3alrMAyI/8xexCZeiQu0X11rc3Sg8k2SC+9+V+X2dsohCnsTgFbDANZzlL2WojzxmnzLsJFEu/WIQ==
+
dependencies:
+
cli-table3 "^0.5.1"
+
inquirer "^6.2.2"
+
is-domain "0.0.1"
+
minimist "1.1.1"
+
moniker "0.1.2"
+
netrc "0.1.4"
+
progress "1.1.8"
+
prompt "~0.2.14"
+
read "1.0.5"
+
request "^2.88.0"
+
split "0.3.1"
+
surge-fstream-ignore "^1.0.6"
+
surge-ignore "0.2.0"
+
tarr "1.1.0"
+
url-parse-as-address "1.0.0"
+
svelte@^3.16.7:
version "3.18.1"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.18.1.tgz#db0f82cc46394ca8c9a9d183995e1ebfeea3bdd0"
···
fs-constants "^1.0.0"
inherits "^2.0.3"
readable-stream "^3.1.1"
+
+
tarr@1.1.0:
+
version "1.1.0"
+
resolved "https://registry.yarnpkg.com/tarr/-/tarr-1.1.0.tgz#d7a9532ce97f08f5200b78ae0a82a6883173c8c8"
+
integrity sha512-tENbQ43IQckay71stp1p1lljRhoEZpZk10FzEZKW2tJcMcnLwV3CfZdxBAERlH6nwnFvnHMS9eJOJl6IzSsG0g==
+
dependencies:
+
block-stream "*"
+
fstream ">=1.0.12"
+
inherits "2"
term-size@^1.2.0:
version "1.2.0"
···
mime "^2.4.4"
schema-utils "^2.5.0"
+
url-parse-as-address@1.0.0:
+
version "1.0.0"
+
resolved "https://registry.yarnpkg.com/url-parse-as-address/-/url-parse-as-address-1.0.0.tgz#fb80901883f338b3cbed3538f5faa26adaf7f2e7"
+
integrity sha1-+4CQGIPzOLPL7TU49fqiatr38uc=
+
url-parse-lax@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c"
···
version "0.4.0"
resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c"
+
utile@0.2.x:
+
version "0.2.1"
+
resolved "https://registry.yarnpkg.com/utile/-/utile-0.2.1.tgz#930c88e99098d6220834c356cbd9a770522d90d7"
+
integrity sha1-kwyI6ZCY1iIINMNWy9mncFItkNc=
+
dependencies:
+
async "~0.2.9"
+
deep-equal "*"
+
i "0.3.x"
+
mkdirp "0.x.x"
+
ncp "0.4.x"
+
rimraf "2.x.x"
+
utils-merge@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
···
tr46 "^1.0.1"
webidl-conversions "^4.0.2"
+
which-boxed-primitive@^1.0.1:
+
version "1.0.1"
+
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.1.tgz#cbe8f838ebe91ba2471bb69e9edbda67ab5a5ec1"
+
integrity sha512-7BT4TwISdDGBgaemWU0N0OU7FeAEJ9Oo2P1PHRm/FCWoEi2VLWC9b6xvxAA3C/NMpxg3HXVgi0sMmGbNUbNepQ==
+
dependencies:
+
is-bigint "^1.0.0"
+
is-boolean-object "^1.0.0"
+
is-number-object "^1.0.3"
+
is-string "^1.0.4"
+
is-symbol "^1.0.2"
+
+
which-collection@^1.0.0:
+
version "1.0.1"
+
resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906"
+
integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==
+
dependencies:
+
is-map "^2.0.1"
+
is-set "^2.0.1"
+
is-weakmap "^2.0.1"
+
is-weakset "^2.0.1"
+
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
···
resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc"
dependencies:
string-width "^2.1.1"
+
+
winston@0.8.x:
+
version "0.8.3"
+
resolved "https://registry.yarnpkg.com/winston/-/winston-0.8.3.tgz#64b6abf4cd01adcaefd5009393b1d8e8bec19db0"
+
integrity sha1-ZLar9M0Brcrv1QCTk7HY6L7BnbA=
+
dependencies:
+
async "0.2.x"
+
colors "0.6.x"
+
cycle "1.0.x"
+
eyes "0.1.x"
+
isstream "0.1.x"
+
pkginfo "0.3.x"
+
stack-trace "0.0.x"
"wonka@^3.2.1 || ^4.0.0", wonka@^4.0.6, wonka@^4.0.7:
version "4.0.7"