Skip to content

Commit

Permalink
feat: add support for TypedDocumentNode
Browse files Browse the repository at this point in the history
  • Loading branch information
charlypoly committed Aug 2, 2022
1 parent a277fa1 commit 362cc9b
Show file tree
Hide file tree
Showing 19 changed files with 565 additions and 507 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}
}
69 changes: 34 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@ Minimal GraphQL client supporting Node and browsers for scripts or simple apps
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [Features](#features)
- [Install](#install)
- [Quickstart](#quickstart)
- [Usage](#usage)
- [Node Version Support](#node-version-support)
- [Community](#community)
- [GraphQL Code Generator's GraphQL-Request TypeScript Plugin](#graphql-code-generators-graphql-request-typescript-plugin)
- [GraphQL Code Generator's GraphQL-Request TypeScript Plugin](#graphql-code-generators-graphql-request-typescript-plugin)
- [Examples](#examples)
- [Authentication via HTTP header](#authentication-via-http-header)
- [Incrementally setting headers](#incrementally-setting-headers)
Expand All @@ -37,9 +36,9 @@ Minimal GraphQL client supporting Node and browsers for scripts or simple apps
- [Cancellation](#cancellation)
- [Middleware](#middleware)
- [FAQ](#faq)
- [Why do I have to install `graphql`?](#why-do-i-have-to-install-graphql)
- [Do I need to wrap my GraphQL documents inside the `gql` template exported by `graphql-request`?](#do-i-need-to-wrap-my-graphql-documents-inside-the-gql-template-exported-by-graphql-request)
- [What's the difference between `graphql-request`, Apollo and Relay?](#whats-the-difference-between-graphql-request-apollo-and-relay)
- [Why do I have to install `graphql`?](#why-do-i-have-to-install-graphql)
- [Do I need to wrap my GraphQL documents inside the `gql` template exported by `graphql-request`?](#do-i-need-to-wrap-my-graphql-documents-inside-the-gql-template-exported-by-graphql-request)
- [What's the difference between `graphql-request`, Apollo and Relay?](#whats-the-difference-between-graphql-request-apollo-and-relay)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

Expand Down Expand Up @@ -167,7 +166,7 @@ client.setHeader('authorization', 'Bearer MY_TOKEN')
// Override all existing headers
client.setHeaders({
authorization: 'Bearer MY_TOKEN',
anotherheader: 'header_value'
anotherheader: 'header_value',
})
```

Expand All @@ -181,14 +180,12 @@ import { GraphQLClient } from 'graphql-request'
const client = new GraphQLClient(endpoint)

client.setEndpoint(newEndpoint)

```

#### passing-headers-in-each-request

It is possible to pass custom headers for each request. `request()` and `rawRequest()` accept a header object as the third parameter


```js
import { GraphQLClient } from 'graphql-request'

Expand All @@ -210,7 +207,7 @@ const variables = {
}

const requestHeaders = {
authorization: 'Bearer MY_TOKEN'
authorization: 'Bearer MY_TOKEN',
}

// Overrides the clients headers with the passed values
Expand All @@ -225,11 +222,9 @@ To do that, pass a function that returns the headers to the `headers` property w
```js
import { GraphQLClient } from 'graphql-request'

const client = new GraphQLClient(endpoint,
{
headers: () => ({ 'X-Sent-At-Time': Date.now() })
}
)
const client = new GraphQLClient(endpoint, {
headers: () => ({ 'X-Sent-At-Time': Date.now() }),
})

const query = gql`
query getCars {
Expand Down Expand Up @@ -345,7 +340,7 @@ async function main() {
parse: JSON.parse,
stringify: JSON.stringify,
},
});
})

const query = gql`
query getMovie($title: String!) {
Expand Down Expand Up @@ -608,16 +603,15 @@ request('/api/graphql', UploadUserAvatar, {

[TypeScript Source](examples/receiving-a-raw-response.ts)


### Batching

It is possible with `graphql-request` to use [batching](https://github.com/graphql/graphql-over-http/blob/main/rfcs/Batching.md) via the `batchRequests()` function. Example available at [examples/batching-requests.ts](examples/batching-requests.ts)

```ts
import { batchRequests } from 'graphql-request';
import { batchRequests } from 'graphql-request'

(async function () {
const endpoint = 'https://api.spacex.land/graphql/';
;(async function () {
const endpoint = 'https://api.spacex.land/graphql/'

const query1 = /* GraphQL */ `
query ($id: ID!) {
Expand All @@ -626,15 +620,15 @@ import { batchRequests } from 'graphql-request';
landings
}
}
`;
`

const query2 = /* GraphQL */ `
{
rockets(limit: 10) {
active
}
}
`;
`

const data = await batchRequests(endpoint, [
{ document: query1, variables: { id: 'C105' } },
Expand All @@ -651,42 +645,43 @@ It is possible to cancel a request using an `AbortController` signal.
You can define the `signal` in the `GraphQLClient` constructor:

```ts
const abortController = new AbortController()
const abortController = new AbortController()

const client = new GraphQLClient(endpoint, { signal: abortController.signal })
client.request(query)
const client = new GraphQLClient(endpoint, { signal: abortController.signal })
client.request(query)

abortController.abort()
abortController.abort()
```

You can also set the signal per request (this will override an existing GraphQLClient signal):

```ts
const abortController = new AbortController()
const abortController = new AbortController()

const client = new GraphQLClient(endpoint)
client.request({ document: query, signal: abortController.signal })
const client = new GraphQLClient(endpoint)
client.request({ document: query, signal: abortController.signal })

abortController.abort()
abortController.abort()
```

In Node environment, `AbortController` is supported since version v14.17.0.
For Node.js v12 you can use [abort-controller](https://github.com/mysticatea/abort-controller) polyfill.

````
```
import 'abort-controller/polyfill'
const abortController = new AbortController()
````
```

### Middleware

It's possible to use a middleware to pre-process any request or handle raw response.

Request middleware example (set actual auth token to each request):

```ts
function middleware(request: RequestInit) {
const token = getToken();
const token = getToken()
return {
...request,
headers: { ...request.headers, 'x-auth-token': token },
Expand All @@ -697,12 +692,13 @@ const client = new GraphQLClient(endpoint, { requestMiddleware: middleware })
```

Response middleware example (log request trace id if error caused):

```ts
function middleware(response: Response<unknown>) {
if (response.errors) {
const traceId = response.headers.get('x-b3-traceid') || 'unknown'
console.error(
`[${traceId}] Request error:
`[${traceId}] Request error:
status ${response.status}
details: ${response.errors}`
)
Expand All @@ -714,20 +710,23 @@ const client = new GraphQLClient(endpoint, { responseMiddleware: middleware })

### ErrorPolicy

By default GraphQLClient will throw when an error is received. However, sometimes you still want to resolve the (partial) data you received.
By default GraphQLClient will throw when an error is received. However, sometimes you still want to resolve the (partial) data you received.
You can define `errorPolicy` in the `GraphQLClient` constructor.

```ts
const client = new GraphQLClient(endpoint, {errorPolicy: "all"});
const client = new GraphQLClient(endpoint, { errorPolicy: 'all' })
```

#### None (default)

Allow no errors at all. If you receive a GraphQL error the client will throw.

#### Ignore

Ignore incoming errors and resolve like no errors occurred

#### All

Return both the errors and data, only works with `rawRequest`.

## FAQ
Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Security Policy

If you have a security issue to report, please contact us at [[email protected]](mailto:[email protected]).
If you have a security issue to report, please contact us at [[email protected]](mailto:[email protected]).
4 changes: 2 additions & 2 deletions examples/custom-fetch.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import fetch from 'cross-fetch';
import fetch from 'cross-fetch'
import { GraphQLClient } from '../src'
;(async function () {
const endpoint = 'https://api.graph.cool/simple/v1/cixos23120m0n0173veiiwrjr'

const graphQLClient = new GraphQLClient(endpoint, { fetch: fetch})
const graphQLClient = new GraphQLClient(endpoint, { fetch: fetch })

const query = /* GraphQL */ `
{
Expand Down
2 changes: 1 addition & 1 deletion examples/passing-custom-header-per-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { GraphQLClient } from '../src'

const requestHeaders = {
authorization: 'Bearer MY_TOKEN_2',
'x-custom': 'foo'
'x-custom': 'foo',
}

interface TData {
Expand Down
78 changes: 3 additions & 75 deletions examples/typed-document-node.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { TypedDocumentNode } from "@graphql-typed-document-node/core";
import { parse } from "graphql";
import { TypedDocumentNode } from '@graphql-typed-document-node/core'
import { parse } from 'graphql'

import { request } from '../src'

;(async function () {
const endpoint = 'https://graphql-yoga.com/api/graphql'

Expand All @@ -15,77 +14,6 @@ import { request } from '../src'
const variables = {}

const data = await request(endpoint, query, variables)

console.log(data.greetings)
})().catch((error) => console.error(error))

/** TypeScript API Tests */

async function _() {
const endpoint = 'noop'

const query: TypedDocumentNode<{ echo: string }, { str: string }> = parse(/* GraphQL */ `
query greetings($str: String!) {
echo(str: $echo)
}
`)

// variables are mandatory here!

// @ts-expect-error 'str' is declared here.
const _data = await request(endpoint, query, {})
// @ts-expect-error Arguments for the rest parameter '_variablesAndRequestHeaders' were not provided.
const __data = await request(endpoint, query)


const data = await request(endpoint, query, { str: "Hi" })
data.echo
}

async function __() {
const endpoint = 'noop'

const document: TypedDocumentNode<{ echo: string }, { str: string }> = parse(/* GraphQL */ `
query greetings($str: String!) {
echo(str: $echo)
}
`)

// variables are mandatory here!

// @ts-expect-error 'variables' is declared here.
await request({
url: endpoint,
document,
})

await request({
url: endpoint,
document,
// @ts-expect-error Property 'str' is missing in type '{}' but required in type '{ str: string; }'.
variables: {}
})

await request({
url: endpoint,
document,
// @ts-expect-error Type '{ aaa: string; }' is not assignable to type '{ str: string; }'.
variables: { aaa: "aaa" }
})

await request({
url: endpoint,
document,
// @ts-expect-error Type 'number' is not assignable to type 'string'.ts(2322)
variables: { str: 1 }
})

const data = await request({
url: endpoint,
document,
variables: {
str: "foo"
}
})
data.echo
}
6 changes: 3 additions & 3 deletions src/defaultJsonSerializer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { JsonSerializer } from "./types.dom";
import { JsonSerializer } from './types.dom'

export const defaultJsonSerializer: JsonSerializer = {
parse: JSON.parse,
stringify: JSON.stringify
}
stringify: JSON.stringify,
}
Loading

0 comments on commit 362cc9b

Please sign in to comment.