From 5af7b4fc9126e17303f50c2f801da7be0cc2e0e5 Mon Sep 17 00:00:00 2001
From: Anthony Skorupskyy <50280805+askorupskyy@users.noreply.github.com>
Date: Sun, 5 Jan 2025 02:44:05 -0600
Subject: [PATCH] fix(context): single body overrides other returns (#3800)

Co-authored-by: askorupskyy <50280805+rcbxd@users.noreply.github.com>
---
 src/context.test.ts |  6 +++---
 src/context.ts      | 24 ++++++++++++++++--------
 src/types.test.ts   | 17 +++++++++++++++++
 3 files changed, 36 insertions(+), 11 deletions(-)

diff --git a/src/context.test.ts b/src/context.test.ts
index 8230a9548..eefa00663 100644
--- a/src/context.test.ts
+++ b/src/context.test.ts
@@ -151,11 +151,11 @@ describe('Context', () => {
     c.header('X-Foo', 'Bar')
     c.header('X-Foo', undefined)
     c.header('X-Foo2', 'Bar')
-    let res = c.body('Hi')
+    const res = c.body('Hi')
     expect(res.headers.get('X-Foo')).toBe(null)
     c.header('X-Foo2', undefined)
-    res = c.res
-    expect(res.headers.get('X-Foo2')).toBe(null)
+    const res2 = c.body('Hi')
+    expect(res2.headers.get('X-Foo2')).toBe(null)
   })
 
   it('c.header() - clear the header when append is true', async () => {
diff --git a/src/context.ts b/src/context.ts
index 0c86d3dd8..a5f6b4c6a 100644
--- a/src/context.ts
+++ b/src/context.ts
@@ -116,10 +116,14 @@ interface NewResponse {
  */
 interface BodyRespond {
   // if we return content, only allow the status codes that allow for returning the body
-  (data: Data, status?: ContentfulStatusCode, headers?: HeaderRecord): Response
-  (data: null, status?: StatusCode, headers?: HeaderRecord): Response
-  (data: Data, init?: ResponseOrInit<ContentfulStatusCode>): Response
-  (data: null, init?: ResponseOrInit): Response
+  <U extends ContentfulStatusCode>(data: Data, status?: U, headers?: HeaderRecord): Response &
+    TypedResponse<unknown, U, 'body'>
+  <U extends StatusCode>(data: null, status?: U, headers?: HeaderRecord): Response &
+    TypedResponse<null, U, 'body'>
+  <U extends ContentfulStatusCode>(data: Data, init?: ResponseOrInit<U>): Response &
+    TypedResponse<unknown, U, 'body'>
+  <U extends StatusCode>(data: null, init?: ResponseOrInit<U>): Response &
+    TypedResponse<null, U, 'body'>
 }
 
 /**
@@ -723,10 +727,14 @@ export class Context<
    * })
    * ```
    */
-  body: BodyRespond = (data, arg?: StatusCode | RequestInit, headers?: HeaderRecord) => {
-    return typeof arg === 'number'
-      ? this.#newResponse(data, arg, headers)
-      : this.#newResponse(data, arg)
+  body: BodyRespond = (
+    data: Data | null,
+    arg?: StatusCode | RequestInit,
+    headers?: HeaderRecord
+  ): ReturnType<BodyRespond> => {
+    return (
+      typeof arg === 'number' ? this.#newResponse(data, arg, headers) : this.#newResponse(data, arg)
+    ) as ReturnType<BodyRespond>
   }
 
   /**
diff --git a/src/types.test.ts b/src/types.test.ts
index bad8ab2ca..9a4720029 100644
--- a/src/types.test.ts
+++ b/src/types.test.ts
@@ -2328,4 +2328,21 @@ describe('status code', () => {
     // @ts-expect-error 204 is not contentful status code
     app.get('/', async (c) => c.html('<h1>title</h1>', { status: 204 }))
   })
+
+  it('.body() not override other responses in hono client', async () => {
+    const router = app.get('/', async (c) => {
+      if (c.req.header('Content-Type') === 'application/json') {
+        return c.text('Hello', 200)
+      }
+
+      if (c.req.header('Content-Type') === 'application/x-www-form-urlencoded') {
+        return c.body('Hello', 201)
+      }
+
+      return c.body(null, 204)
+    })
+
+    type Actual = ExtractSchema<typeof router>['/']['$get']['status']
+    expectTypeOf<Actual>().toEqualTypeOf<204 | 201 | 200>()
+  })
 })