Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Caching, fetching and inspection #12

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Prev Previous commit
Next Next commit
Update readme
tornqvist committed Nov 5, 2020

Verified

This commit was signed with the committer’s verified signature.
tornqvist Carl Törnqvist
commit be8bc0f1ccf06ca6ab7d7fbfe2a6d1755daa75aa
70 changes: 62 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
[![downloads][8]][9] [![js-standard-style][10]][11]

Tiny GraphQL client library. Compiles queries, fetches them and caches them, all
in one tiny pacakge.
in one tiny package.

## Usage
```js
@@ -22,6 +22,65 @@ const { errors, data } = graphql(Query({ name: 'Back to the Future' }))

```

## API
### ``query = gql`[query]` ``
Create a new graphql query function.

### `operation = query([data])`
Create a new operation object that holds all data necessary to execute the query
against an endpoint. An operation can be stringified to a query (`toString`),
serialized to a plain object (`toJSON`) or iterated over.

### `cache = nanographql(string[, opts])`
Create a managed cache which fetches data as it is requested.

#### Options
- **`cache`:** a custom cache store. Should implement `get` and `set` methods.
*Default: `new Map()`*.
- **`fetch`:** a custom [`fetch`]([12]) implementation.
*Default: `window.fetch`*.

### `result = cache(operation[, opts])`
Query the cache and fetch query if necessary. The arguments match that of
[`fetch`]([12]) with a couple extra options.

#### Options
The options are forwarded to the [`fetch`]([12]) implementation but a few are
also used to determine when to use the cache and how to format the request.

##### Default options
- **`cache`:** The default behavior of nanographql mimics that of `force-cache`
as it will always try and read from the cache unless specified otherwise. Any
of the values `no-store`, `reload`, `no-cache`, `default` will cause
nanographql to bypass the cache and call the fetch implmentation. The value
`no-store` will also prevent the response from being cached locally.
- **`body`:** If a body is defined, nanographql will make no changes to headers
or the body itself. You'll have to append the operation to the body yourself.
- **`method`:** If the operation is a `mutation` or if the stringified
operation is too long to be transferred as `GET` parameters, the method will
be set to `POST`, unless specified otherwise.

##### Extra options
- **`key|key(variables, cached)`:** A unique identifier for the requested data.
Can be a string or a function. Functions will be called with the variables and
the cached data, if there is any. This can be used to determine the key of
e.g. a mutation where the key is not known untill a response is retrieved. The
default is the `id` variable, if deined, otherwise all variables as a
serialized string, or a stringified representation of the query if no
variables are provided.
- **`parse(response, cached)`:** Parse the incoming data before comitting to the
cache.
- **`mutate(cached)`:** Mutate the cached data prior to reading from cache or
fetching data. This is useful for e.g. immedately updating the UI while
submitting changes to the back end.

## Advanced usage
One of the benefits of GraphQL is the strucuted format of the queries. When
passing a query to the `gql` tag, nanographql will parse the string identifying
individual queries, mutations, subscritions and fragments and expose these as
individual functions. It will also mix in interpolated fragments from other
queries.

```js
const { gql, nanographql } = require('nanographql')

@@ -60,18 +119,12 @@ function render () {
`

function onsubmit (event) {
graphql(SaveUser({ id: 'abc123', name: this.username }))
graphql(SaveUser({ id: 'abc123', name: this.username.value }))
event.preventDefault()
}
}
```

## API
### `query = gql(string)`
Create a new graphql query function.

### `json = query([data])`
Create a new query object that can be sent as `application/json` to a server.

## License
[MIT](https://tldrlegal.com/license/mit-license)
@@ -88,3 +141,4 @@ Create a new query object that can be sent as `application/json` to a server.
[9]: https://npmjs.org/package/nanographql
[10]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square
[11]: https://github.com/feross/standard
[12]: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters
18 changes: 13 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
@@ -45,7 +45,11 @@ function nanographql (url, opts = {}) {
if (!cache) cache = new Map()
if (typeof fetch !== 'function') {
fetch = function (url, opts, cb) {
window.fetch(url, opts).then((res) => res.json()).then((res) => cb(null, res), cb)
window.fetch(url, opts).then(function (res) {
return res.json()
}).then(function (res) {
return cb(null, res)
}, cb)
}
}

@@ -60,8 +64,10 @@ function nanographql (url, opts = {}) {
const querystring = operation.toString()
let href = url.toString()

let key = opts.key || (variables ? serialize(variables) : querystring)
if (typeof key === 'function') key = opts.key(variables)
let key = opts.key
if (!key) key = variables ? variables.id || serialize(variables) : querystring
else if (typeof key === 'function') key = opts.key(variables)

let useCache = !body && type !== 'mutation' && !bypass.includes(opts.cache)
let store = cache.get(operation.key)
if (!store) cache.set(operation.key, store = {})
@@ -80,7 +86,7 @@ function nanographql (url, opts = {}) {
}
} else {
let [domainpath, query] = href.split('?')
query = query ? query + `&${querystring}` : querystring
query = query ? `${query}&${querystring}` : querystring
href = `${domainpath}?${query}`
}

@@ -140,7 +146,9 @@ function parse (strings, values) {
if (last) last.query += template.substring(last.index)

// Inject fragment into operation query
const fragments = operations.filter((operation) => operation.type === 'fragment')
const fragments = operations.filter(function (operation) {
return operation.type === 'fragment'
})
if (fragments.length) {
for (const operation of operations) {
if (operation.type === 'fragment') continue