Skip to content

Include original response in client errors#1476

Merged
jasonkuhrt merged 5 commits intograffle-js:graphql-requestfrom
adambrgmn:client-error-original-response
Dec 12, 2025
Merged

Include original response in client errors#1476
jasonkuhrt merged 5 commits intograffle-js:graphql-requestfrom
adambrgmn:client-error-original-response

Conversation

@adambrgmn
Copy link
Copy Markdown

This PR adds the original fetch response to client errors to be able to act on non-graphql responses returned by the api.

The reason we need this is because we integrate with an api that might at any time return a non-graphql response in case of an error. And the body returned might hold reasons for the error which can't be parsed by the graphql parser.

We have tested this as a local patch for a while and think it is very useful, hence this PR.

This is connected to issue #1473.

By including the original fetch response when returning a ClientError
call sites can use the full response to debug why the error happened.

This opens up for e.g. utilizing api's that might at any time return a
non-graphql response when an error occurs.
@jasonkuhrt
Copy link
Copy Markdown
Member

jasonkuhrt commented Dec 12, 2025

Thanks for the PR @adambrgmn! I've pushed a refactored approach that I think better addresses your use case.

Changes Made

Instead of exposing the full Response object (whose body stream is already consumed by the time you receive the error), I've exposed the useful data directly on GraphQLResponse:

interface GraphQLResponse<T = unknown> {
  // ... existing fields
  headers: Headers    // ✅ Access response headers (e.g., Kill-Switch)
  body: string        // ✅ The response body text for non-GraphQL responses
}

Why This Approach?

  1. No consumed-stream footgun: The original Response object's body can only be read once. Since runRequest already calls response.text() to parse the body, originalResponse.text() would return an empty string - a confusing gotcha.

  2. Direct access: You can access headers and body directly without needing the Response wrapper:

    if (error instanceof ClientError) {
      const hasKillSwitch = error.response.headers.get('Kill-Switch')
      if (hasKillSwitch) {
        const reason = JSON.parse(error.response.body)
        throw new KillSwitchError(reason)
      }
    }
  3. No breaking changes to ClientError constructor: The constructor signature stays the same.

Test Added

I added a test case that validates the exact use case from your issue - accessing custom headers and parsing non-GraphQL response bodies.


Does this approach work for your use case? If you need anything else from the Response object that I missed, let me know!

Instead of exposing the full fetch Response (whose body stream is already
consumed), expose the useful data directly on GraphQLResponse:
- headers: Headers - for accessing response headers like Kill-Switch
- rawBody: string - the raw response text for non-GraphQL responses

This avoids the "consumed body stream" footgun while still enabling the
use case from issue graffle-js#1473 (accessing non-GraphQL error responses).
@jasonkuhrt jasonkuhrt force-pushed the client-error-original-response branch from 9809932 to 6ead4b5 Compare December 12, 2025 15:18
Copy link
Copy Markdown
Author

@adambrgmn adambrgmn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That improvement is great. My solution was very basic. I did res.clone().json() in my approach since it was already read. But I'm sure this is more efficient and it will suit our use-case well 👍

Update tests to account for the new `body` property on GraphQLResponse:
- Destructure `body` out of results in equality assertions
- Update inline snapshots to include the body field
- Add body property to GraphQLClientResponse type (was only on GraphQLResponse)
- Remove toBeInstanceOf(Headers) check that fails in jsdom environment
@jasonkuhrt jasonkuhrt merged commit 42eeab1 into graffle-js:graphql-request Dec 12, 2025
8 checks passed
@adambrgmn adambrgmn deleted the client-error-original-response branch December 13, 2025 09:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants