Repository: mswjs/msw (ts)

Issue:
Unable to use HttpResponse.error() with TypeScript
### Prerequisites

- [X] I confirm my issue is not in the [opened issues](https://github.com/mswjs/msw/issues)
- [X] I confirm the [Frequently Asked Questions](https://mswjs.io/docs/faq) didn't contain the answer to my issue

### Environment check

- [X] I'm using the [latest](https://github.com/mswjs/msw/releases/latest) `msw` version
- [X] I'm using Node.js version 18 or higher

### Node.js version

v20.12.1

### Reproduction repository

https://codesandbox.io/p/sandbox/friendly-pare-8tkfgz?file=%2Fmocks%2Fhandlers.ts

### Reproduction steps

`npx tsc`

### Current behavior

Adding a `return HttpResponse.error()` results in a TS error:

```
mocks/handlers.ts:10:31 - error TS2345: Argument of type '() => Response' is not assignable to parameter of type 'GraphQLResponseResolver<GraphQLQuery, GraphQLVariables>'.
  Type 'Response' is not assignable to type 'AsyncResponseResolverReturnType<GraphQLResponseBody<GraphQLQuery>>'.
    Property '[bodyType]' is missing in type 'Response' but required in type 'StrictResponse<GraphQLResponseBody<GraphQLQuery>>'.

10   graphql.query('ListMovies', () => {
                                 ~~~~~~~
```

### Expected behavior

No error.

Pull request:
fix(TypeScript): support `Response.error()` and `HttpResponse.error()` as mocked responses
- Fixes #2130


Patch:
diff --git a/src/core/HttpResponse.ts b/src/core/HttpResponse.ts
index 611f5edc..2995e309 100644
--- a/src/core/HttpResponse.ts
+++ b/src/core/HttpResponse.ts
@@ -10,7 +10,10 @@ export interface HttpResponseInit extends ResponseInit {
   type?: ResponseType
 }
 
-const bodyType: unique symbol = Symbol('bodyType')
+export const bodyType: unique symbol = Symbol('bodyType')
+export type DefaultUnsafeFetchResponse = Response & {
+  [bodyType]?: never
+}
 
 export interface StrictRequest<BodyType extends JsonBodyType> extends Request {
   json(): Promise<BodyType>
@@ -46,6 +49,10 @@ export class HttpResponse<
     decorateResponse(this, responseInit)
   }
 
+  static error(): HttpResponse<any> {
+    return super.error() as HttpResponse<any>
+  }
+
   /**
    * Create a `Response` with a `Content-Type: "text/plain"` body.
    * @example
diff --git a/src/core/handlers/RequestHandler.ts b/src/core/handlers/RequestHandler.ts
index 452d3ebd..d7ed8c83 100644
--- a/src/core/handlers/RequestHandler.ts
+++ b/src/core/handlers/RequestHandler.ts
@@ -6,8 +6,13 @@ import {
 } from '../utils/internal/isIterable'
 import type { ResponseResolutionContext } from '../utils/executeHandlers'
 import type { MaybePromise } from '../typeUtils'
-import type { HttpResponse, StrictRequest } from '..//HttpResponse'
+import {
+  StrictRequest,
+  HttpResponse,
+  DefaultUnsafeFetchResponse,
+} from '../HttpResponse'
 import type { HandlerKind } from './common'
+import type { GraphQLRequestBody } from './GraphQLHandler'
 
 export type DefaultRequestMultipartBody = Record<
   string,
@@ -42,9 +47,19 @@ export interface RequestHandlerInternalInfo {
 export type ResponseResolverReturnType<
   ResponseBodyType extends DefaultBodyType = undefined,
 > =
+  // If ResponseBodyType is a union and one of the types is `undefined`,
+  // allow plain Response as the type.
   | ([ResponseBodyType] extends [undefined]
       ? Response
-      : HttpResponse<ResponseBodyType>)
+      : /**
+         * Treat GraphQL response body type as a special case.
+         * For esome reason, making the default HttpResponse<T> | DefaultUnsafeFetchResponse
+         * union breaks the body type inference for HTTP requests.
+         * @see https://github.com/mswjs/msw/issues/2130
+         */
+        ResponseBodyType extends GraphQLRequestBody<any>
+        ? HttpResponse<ResponseBodyType> | DefaultUnsafeFetchResponse
+        : HttpResponse<ResponseBodyType>)
   | undefined
   | void
 


Test patch:
diff --git a/test/typings/graphql.test-d.ts b/test/typings/graphql.test-d.ts
index 28242c2b..837db2b6 100644
--- a/test/typings/graphql.test-d.ts
+++ b/test/typings/graphql.test-d.ts
@@ -157,6 +157,23 @@ it('graphql handlers allow passthrough responses', () => {
   })
 })
 
+it('supports Response.error()', () => {
+  graphql.query<{ id: string }>('GetUser', () => HttpResponse.error())
+  graphql.mutation('UpdatePost', () => HttpResponse.error())
+  graphql.operation(() => HttpResponse.error())
+
+  graphql.query('GetUser', async () => HttpResponse.error())
+  graphql.query('GetUser', function* () {
+    return HttpResponse.error()
+  })
+
+  graphql.query('GetUser', () => Response.error())
+  graphql.query('GetUser', async () => Response.error())
+  graphql.query('GetUser', function* () {
+    return Response.error()
+  })
+})
+
 it("graphql variables cannot extract type from the runtime 'DocumentNode'", () => {
   /**
    * Supports `DocumentNode` as the GraphQL operation name.
diff --git a/test/typings/http.test-d.ts b/test/typings/http.test-d.ts
index 87a219e9..972a3920 100644
--- a/test/typings/http.test-d.ts
+++ b/test/typings/http.test-d.ts
@@ -295,3 +295,17 @@ it('treats non-typed HttpResponse body type as matching', () => {
     return new HttpResponse(null, { status: 500 })
   })
 })
+
+it('supports returning Response.error()', () => {
+  http.get('/resource', () => Response.error())
+  http.get('/resource', async () => Response.error())
+  http.get('/resource', function* () {
+    return Response.error()
+  })
+
+  http.get<never, never, string>('/resource', () => HttpResponse.error())
+  http.get<never, never, string>('/resource', async () => HttpResponse.error())
+  http.get<never, never, string>('/resource', function* () {
+    return HttpResponse.error()
+  })
+})