Skip to content

Commit

Permalink
feat(graphql-hooks): add headers to client response (#1186)
Browse files Browse the repository at this point in the history
* feat(graphql-hooks): add headers to client response

* chore: remove colons and update the documentation
  • Loading branch information
madshall authored Jun 11, 2024
1 parent 4546fd3 commit e61f713
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 14 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ const client = new GraphQLClient(config)
- `middleware`: Accepts an array of middleware functions, default: none, see more in [middlewares readme](packages/graphql-hooks/src/middlewares/README.md)
- `onError({ operation, result })`: Custom error handler
- `operation`: Object with `query`, `variables` and `operationName`
- `result`: Object containing `data` and `error` object that contains `fetchError`, `httpError` and `graphqlErrors`
- `result`: Object containing `data`, `headers` and `error` object that contains `fetchError`, `httpError` and `graphqlErrors`

### `client` methods

Expand Down Expand Up @@ -270,6 +270,7 @@ const { loading, error, data, refetch, cacheHit } = useQuery(QUERY)

- `loading`: Boolean - `true` if the query is in flight
- `data`: Object - the result of your GraphQL query
- `headers`: Object - response headers
- `refetch(options)`: Function - useful when refetching the same query after a mutation; NOTE this presets `skipCache=true` & will bypass the `options.updateData` function that was passed into `useQuery`. You can pass a new `updateData` into `refetch` if necessary.
- `options`: Object - options that will be merged into the `options` that were passed into `useQuery` (see above).
- `cacheHit`: Boolean - `true` if the query result came from the cache, useful for debugging
Expand Down Expand Up @@ -405,6 +406,7 @@ The `options` object that can be passed either to `useMutation(mutation, options
In addition, there is an option to reset the current state before calling the mutation again, by calling `resetFn(desiredState)` where `desiredState` is optional and if passed, it will override the initial state with:

- `data`: Object - the data
- `headers`: Object - response headers
- `error`: Error - the error
- `loading`: Boolean - true if it is still loading
- `cacheHit`: Boolean - true if the result was cached
Expand Down
13 changes: 8 additions & 5 deletions packages/graphql-hooks/src/GraphQLClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ class GraphQLClient {
fetchError,
httpError,
graphQLErrors,
data
data,
headers
}: GenerateResultOptions<ResponseData, TGraphQLError>): Result<
ResponseData,
TGraphQLError
Expand All @@ -169,8 +170,8 @@ class GraphQLClient {
httpError
)
return !errorFound
? { data }
: { data, error: { fetchError, httpError, graphQLErrors } }
? { data, headers }
: { data, error: { fetchError, httpError, graphQLErrors }, headers }
}

getCacheKey<Variables = object>(
Expand Down Expand Up @@ -339,7 +340,8 @@ class GraphQLClient {
status,
statusText,
body
}
},
headers: response.headers
})
})
} else {
Expand All @@ -350,7 +352,8 @@ class GraphQLClient {
// enrich data with responseReducer if defined
(typeof options.responseReducer === 'function' &&
options.responseReducer(data, response)) ||
data
data,
headers: response.headers,
})
})
}
Expand Down
2 changes: 2 additions & 0 deletions packages/graphql-hooks/src/types/common-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export interface Result<
> {
data?: ResponseData
error?: APIError<TGraphQLError>
headers?: Response['headers']
}

export interface RequestOptions {
Expand All @@ -163,6 +164,7 @@ export interface GenerateResultOptions<
httpError?: HttpError
graphQLErrors?: TGraphQLError[]
data?: ResponseData
headers?: Response['headers']
}

export interface UseClientRequestOptions<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('GraphQLClient', () => {
})

it('caches + logs', async () => {
const MOCK_DATA = { data: 'data' }
const MOCK_DATA = { data: 'data', headers: expect.any(Headers) }
fetchMock.mockResponseOnce(JSON.stringify(MOCK_DATA))

await client.request({
Expand Down Expand Up @@ -81,7 +81,7 @@ describe('GraphQLClient', () => {
})

it('Handles async response hooks in correct order', async () => {
const MOCK_DATA = { data: 'data' }
const MOCK_DATA = { data: 'data', headers: expect.any(Headers) }
fetchMock.mockResponseOnce(JSON.stringify(MOCK_DATA))

await client.request({
Expand Down Expand Up @@ -111,7 +111,7 @@ describe('GraphQLClient', () => {
})

it('logs request start and end', async () => {
const MOCK_DATA = { data: 'data' }
const MOCK_DATA = { data: 'data', headers: expect.any(Headers) }
fetchMock.mockResponseOnce(JSON.stringify(MOCK_DATA))

const res = await client.request({
Expand Down Expand Up @@ -142,7 +142,7 @@ describe('GraphQLClient', () => {
})

it('caches first result', async () => {
const MOCK_DATA = { data: 'data' }
const MOCK_DATA = { data: 'data', headers: expect.any(Headers) }
fetchMock.mockResponseOnce(() =>
Promise.resolve(JSON.stringify(MOCK_DATA))
)
Expand Down
64 changes: 61 additions & 3 deletions packages/graphql-hooks/test-jsdom/unit/GraphQLClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,8 @@ describe('GraphQLClient', () => {
}
const result = client.generateResult(data)
expect(result).toEqual({
data: data.data
data: data.data,
headers: undefined
})
})

Expand All @@ -353,9 +354,37 @@ describe('GraphQLClient', () => {
fetchError: data.fetchError,
httpError: data.httpError
},
data: data.data
data: data.data,
headers: undefined
})
})

it('returns headers if available', () => {
const client = new GraphQLClient({ ...validConfig })
const data = {
graphQLErrors: [
{ message: 'graphQL error 1' },
{ message: 'graphQL error 2' }
],
fetchError: new Error('fetch error'),
httpError: { status: 400, statusText: '', body: 'http error' },
data: 'data!',
headers: new Headers({
'header-name': 'header-value'
})
}
const result = client.generateResult(data)
expect(result).toEqual({
error: {
graphQLErrors: data.graphQLErrors,
fetchError: data.fetchError,
httpError: data.httpError
},
data: data.data,
headers: expect.any(Headers)
})
expect(result.headers?.get('header-name')).toBe('header-value')
})
})

describe('getCacheKey', () => {
Expand Down Expand Up @@ -554,7 +583,8 @@ describe('GraphQLClient', () => {
status: 403,
statusText: 'Forbidden'
}
}
},
headers: expect.any(Headers)
}
})
})
Expand Down Expand Up @@ -633,6 +663,34 @@ describe('GraphQLClient', () => {
expect(_data.contentType).toEqual(headers['content-type'])
})

it('will pass headers to middleware', async () => {
const data = { some: 'data' },
status = 200,
statusText = 'OK',
headers = {
'content-type': 'application/json',
'trace-id': '123456789000'
}
let responseHeaders = new Headers;
const client = new GraphQLClient({ ...validConfig, middleware: [({ addResponseHook }, next) => {
addResponseHook(response => {
responseHeaders = response.headers as Headers
return response
})
next()
}] })
fetchMock.mockResponseOnce(JSON.stringify({ data }), {
status,
statusText,
headers
})
const { data: _data, headers: _headers } = await client.request<any>(
{ query: TEST_QUERY },
)
expect((_headers as Headers).get('trace-id')).toBe('123456789000')
expect(responseHeaders.get('trace-id')).toBe('123456789000')
})

describe('GET Support', () => {
it('should support client.fetchOptions.method=GET', async () => {
fetchMock.mockResponseOnce(JSON.stringify({ data: 'data' }))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const TEST_QUERY = /* GraphQL */ `
`

describe('APQMiddleware', () => {
const MOCK_DATA = { data: [{ id: 1 }, { id: 2 }, { id: 3 }] }
const MOCK_DATA = { data: [{ id: 1 }, { id: 2 }, { id: 3 }], headers: expect.any(Headers) }
const MOCK_ERROR_RESP = {
errors: [
{
Expand Down

0 comments on commit e61f713

Please sign in to comment.