Skip to content

Commit

Permalink
feat: TypedDocumentNode support & strict variable typings (#350)
Browse files Browse the repository at this point in the history
Co-authored-by: Charly POLY <cpoly55@gmail.com>
Co-authored-by: Jason Kuhrt <jasonkuhrt@me.com>
  • Loading branch information
3 people authored Aug 2, 2022
1 parent fec536b commit a7ac904
Show file tree
Hide file tree
Showing 21 changed files with 648 additions and 457 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 [security@prisma.io](mailto:security@prisma.io).
If you have a security issue to report, please contact us at [security@prisma.io](mailto:security@prisma.io).
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
19 changes: 19 additions & 0 deletions examples/typed-document-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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'

const query: TypedDocumentNode<{ greetings: string }, never | Record<any, never>> = parse(/* GraphQL */ `
query greetings {
greetings
}
`)

const variables = {}

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

console.log(data.greetings)
})().catch(console.error)
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"release:pr": "dripip pr"
},
"dependencies": {
"@graphql-typed-document-node/core": "^3.1.1",
"cross-fetch": "^3.1.5",
"extract-files": "^9.0.0",
"form-data": "^3.0.0"
Expand Down
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 a7ac904

Please sign in to comment.