Skip to content

Commit

Permalink
fix: global Headers class reference (#218)
Browse files Browse the repository at this point in the history
Couple things that have been done to avoid similar problem in future:

1. Use namespaced import style to avoid apparent references to global
   Headers that are actually references to the imported TypeScript
   interface.
2. Run tests in jsDOM and Node environments.

closes #206
  • Loading branch information
Jason Kuhrt authored Oct 15, 2020
1 parent 0290616 commit e805be9
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 36 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ jobs:
strategy:
matrix:
node: [10.x, 12.x, 14.x]
environment: [dom, node]
name: Node ${{ matrix.node }}
steps:
- uses: actions/checkout@v2
Expand All @@ -28,4 +29,4 @@ jobs:
${{ runner.os }}-yarn-
- run: yarn install
- run: yarn build
- run: yarn test
- run: yarn test:${{ matrix.environment }}
3 changes: 2 additions & 1 deletion .github/workflows/trunk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ jobs:
strategy:
matrix:
node: [10.x, 12.x, 14.x]
environment: [dom, node]
name: Node ${{ matrix.node }}
steps:
- uses: actions/checkout@v2
Expand All @@ -28,7 +29,7 @@ jobs:
${{ runner.os }}-yarn-
- run: yarn install
- run: yarn build
- run: yarn test
- run: yarn test:${{ matrix.environment }}
release:
needs: [tests]
runs-on: ubuntu-latest
Expand Down
34 changes: 14 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ main().catch((error) => console.error(error))

[TypeScript Source](examples/authentication-via-http-header.ts)

#### Dynamically setting headers
#### Incrementally setting headers

If you want to set headers after the GraphQLClient has been initialised, you can use the `setHeader()` or `setHeaders()` functions.

Expand All @@ -106,7 +106,7 @@ client.setHeaders({
})
```

### Passing more options to fetch
### Passing more options to `fetch`

```js
import { GraphQLClient, gql } from 'graphql-request'
Expand Down Expand Up @@ -139,7 +139,7 @@ main().catch((error) => console.error(error))

[TypeScript Source](examples/passing-more-options-to-fetch.ts)

### Using variables
### Using GraphQL Document variables

```js
import { request, gql } from 'graphql-request'
Expand Down Expand Up @@ -169,7 +169,7 @@ async function main() {
main().catch((error) => console.error(error))
```

### Mutations
### GraphQL Mutations

```js
import { GraphQLClient, gql } from 'graphql-request'
Expand All @@ -184,17 +184,17 @@ async function main() {
})

const mutation = gql`
mutation AddMovie($title: String!, $releaseDate: Int!) {
insert_movies_one(object: { title: $title, releaseDate: $releaseDate }) {
title
releaseDate
}
mutation AddMovie($title: String!, $releaseDate: Int!) {
insert_movies_one(object: { title: $title, releaseDate: $releaseDate }) {
title
releaseDate
}
}
`

const variables = {
title: 'Inception',
releaseDate: 2010
releaseDate: 2010,
}
const data = await graphQLClient.request(mutation, variables)

Expand All @@ -204,7 +204,6 @@ async function main() {
main().catch((error) => console.error(error))
```


[TypeScript Source](examples/using-variables.ts)

### Error handling
Expand Down Expand Up @@ -306,22 +305,22 @@ main().catch((error) => console.error(error))

[TypeScript Source](examples/cookie-support-for-node)

### Using a custom fetch method
### Using a custom `fetch` method

```sh
npm install fetch-cookie
```

```js
import { GraphQLClient, gql } from 'graphql-request'
import crossFetch from 'cross-fetch';
import crossFetch from 'cross-fetch'

async function main() {
const endpoint = 'https://api.graph.cool/simple/v1/cixos23120m0n0173veiiwrjr'

// a cookie jar scoped to the client object
const fetch = require('fetch-cookie')(crossFetch)
const graphQLClient = new GraphQLClient(endpoint, { fetch: fetch})
const graphQLClient = new GraphQLClient(endpoint, { fetch })

const query = gql`
{
Expand Down Expand Up @@ -389,7 +388,7 @@ request('/api/graphql', UploadUserAvatar, {
})
```

#### NodeJS
#### Node

```js
import { createReadStream } from 'fs'
Expand All @@ -409,11 +408,6 @@ request('/api/graphql', UploadUserAvatar, {

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

### More examples coming soon...

- Fragments
- Using [`graphql-tag`](https://github.com/apollographql/graphql-tag)

## FAQ

#### Why do I have to install `graphql`?
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
"format": "prettier --write .",
"prepublishOnly": "yarn build",
"build": "rm -rf dist && tsc -d",
"test": "jest",
"test:node": "jest --testEnvironment node",
"test:dom": "jest --testEnvironment jsdom",
"test": "yarn test:node && yarn test:dom",
"release:stable": "dripip stable",
"release:preview": "dripip preview",
"release:pr": "dripip pr"
Expand Down
30 changes: 18 additions & 12 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import crossFetch from 'cross-fetch'
import crossFetch, * as CrossFetch from 'cross-fetch'
import { print } from 'graphql/language/printer'
import createRequestBody from './createRequestBody'
import { ClientError, GraphQLError, RequestDocument, Variables } from './types'
import { Headers, RequestInit, Response } from './types.dom'
import * as Dom from './types.dom'

export { ClientError } from './types'

const resolveHeaders = (headers: RequestInit['headers']): Record<string, string> => {
/**
* Convert the given headers configuration into a plain object.
*/
const resolveHeaders = (headers: Dom.RequestInit['headers']): Record<string, string> => {
let oHeaders: Record<string, string> = {}
if (headers) {
if (headers instanceof Headers) {
if (
(typeof Headers !== 'undefined' && headers instanceof Headers) ||
headers instanceof CrossFetch.Headers
) {
oHeaders = HeadersInstanceToPlainObject(headers)
} else if (headers instanceof Array) {
} else if (Array.isArray(headers)) {
headers.forEach(([name, value]) => {
oHeaders[name] = value
})
Expand All @@ -28,17 +34,17 @@ const resolveHeaders = (headers: RequestInit['headers']): Record<string, string>
*/
export class GraphQLClient {
private url: string
private options: RequestInit
private options: Dom.RequestInit

constructor(url: string, options?: RequestInit) {
constructor(url: string, options?: Dom.RequestInit) {
this.url = url
this.options = options || {}
}

async rawRequest<T = any, V = Variables>(
query: string,
variables?: V
): Promise<{ data?: T; extensions?: any; headers: Headers; status: number; errors?: GraphQLError[] }> {
): Promise<{ data?: T; extensions?: any; headers: Dom.Headers; status: number; errors?: GraphQLError[] }> {
let { headers, fetch: localFetch = crossFetch, ...others } = this.options
const body = createRequestBody(query, variables)
headers = resolveHeaders(headers)
Expand Down Expand Up @@ -96,7 +102,7 @@ export class GraphQLClient {
}
}

setHeaders(headers: RequestInit['headers']): GraphQLClient {
setHeaders(headers: Dom.RequestInit['headers']): GraphQLClient {
this.options.headers = headers
return this
}
Expand Down Expand Up @@ -126,7 +132,7 @@ export async function rawRequest<T = any, V = Variables>(
url: string,
query: string,
variables?: V
): Promise<{ data?: T; extensions?: any; headers: Headers; status: number; errors?: GraphQLError[] }> {
): Promise<{ data?: T; extensions?: any; headers: Dom.Headers; status: number; errors?: GraphQLError[] }> {
const client = new GraphQLClient(url)
return client.rawRequest<T, V>(query, variables)
}
Expand Down Expand Up @@ -179,7 +185,7 @@ export default request
/**
* todo
*/
function getResult(response: Response): Promise<any> {
function getResult(response: Dom.Response): Promise<any> {
const contentType = response.headers.get('Content-Type')
if (contentType && contentType.startsWith('application/json')) {
return response.json()
Expand Down Expand Up @@ -221,7 +227,7 @@ export function gql(chunks: TemplateStringsArray, ...variables: any[]): string {
/**
* Convert Headers instance into regular object
*/
function HeadersInstanceToPlainObject(headers: Response['headers']): Record<string, string> {
function HeadersInstanceToPlainObject(headers: Dom.Response['headers']): Record<string, string> {
const o: any = {}
headers.forEach((v, k) => {
o[k] = v
Expand Down
5 changes: 4 additions & 1 deletion tests/headers.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as CrossFetch from 'cross-fetch'
import { GraphQLClient } from '../src'
import { setupTestServer } from './__helpers'

Expand All @@ -15,7 +16,9 @@ describe('using class', () => {
describe('.setHeaders() sets headers that get sent to the server', () => {
test('with headers instance', async () => {
const client = new GraphQLClient(ctx.url)
client.setHeaders(new Headers({ 'x-foo': 'bar' }))
// Headers not defined globally in Node
const H = typeof Headers === 'undefined' ? CrossFetch.Headers : Headers
client.setHeaders(new H({ 'x-foo': 'bar' }))
const mock = ctx.res()
await client.request(`{ me { id } }`)
expect(mock.requests[0].headers['x-foo']).toEqual('bar')
Expand Down

0 comments on commit e805be9

Please sign in to comment.