From 5f738971b57b54077b9932fb7b916cf8517534a7 Mon Sep 17 00:00:00 2001 From: Kriday Dave Date: Sat, 18 Jul 2026 04:31:41 +0530 Subject: [PATCH 1/4] docs(combine): close quote in combine JSDoc example (#5126) --- src/middleware/combine/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/combine/index.ts b/src/middleware/combine/index.ts index 1090e31e82..3b3c1d286e 100644 --- a/src/middleware/combine/index.ts +++ b/src/middleware/combine/index.ts @@ -128,7 +128,7 @@ export const every = (...middleware: (MiddlewareHandler | Condition)[]): Middlew * @example * ```ts * import { except } from 'hono/combine' - * import { bearerAuth } from 'hono/bearer-auth + * import { bearerAuth } from 'hono/bearer-auth' * * // If client is accessing public API, then skip authentication. * // Otherwise, require a valid token. From 21b44b6db6118fc77fabb49832fc4cadd0ef8ec8 Mon Sep 17 00:00:00 2001 From: Yusuke Wada Date: Sat, 18 Jul 2026 08:01:57 +0900 Subject: [PATCH 2/4] refactor(aws-lambada): remove FIXME in `@ts-expect-error` (#5130) --- src/adapter/aws-lambda/handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adapter/aws-lambda/handler.ts b/src/adapter/aws-lambda/handler.ts index 4a258e9e08..caba081b3b 100644 --- a/src/adapter/aws-lambda/handler.ts +++ b/src/adapter/aws-lambda/handler.ts @@ -248,7 +248,7 @@ export const handle = ) => { - // @ts-expect-error FIXME: Fix return typing + // @ts-expect-error conditional return type is not inferable return async (event, lambdaContext?) => { const processor = getProcessor(event) From 80959d47d56ac18c2985336ad311917dc56497c5 Mon Sep 17 00:00:00 2001 From: Yusuke Wada Date: Sat, 18 Jul 2026 08:26:28 +0900 Subject: [PATCH 3/4] fix(utils/body): reuse cached formData in `parseBody()` (#5131) --- src/request.test.ts | 16 ++++++++++++++++ src/utils/body.ts | 6 ++++++ 2 files changed, 22 insertions(+) diff --git a/src/request.test.ts b/src/request.test.ts index d9ae581c1b..e4f254698f 100644 --- a/src/request.test.ts +++ b/src/request.test.ts @@ -381,6 +381,22 @@ describe('Body methods with caching', () => { expect(async () => await req.blob()).not.toThrow() }) + it('should parse form data even if formData() is called before parseBody()', async () => { + const data = new FormData() + data.append('foo', 'bar') + data.append('file', new File(['hello'], 't.txt', { type: 'text/plain' })) + const req = new HonoRequest( + new Request('http://localhost', { + method: 'POST', + body: data, + }) + ) + await req.formData() + const body = await req.parseBody() + expect(body['foo']).toBe('bar') + expect(body['file']).toBeInstanceOf(File) + }) + describe('should not break body methods after parseBody() with non-form content-type', () => { const createReq = () => new HonoRequest( diff --git a/src/utils/body.ts b/src/utils/body.ts index 96c801497f..62e3c11617 100644 --- a/src/utils/body.ts +++ b/src/utils/body.ts @@ -124,6 +124,12 @@ async function parseFormData( request: HonoRequest | Request, options: ParseBodyOptions ): Promise { + if (!isRawRequest(request) && request.bodyCache.formData) { + return convertFormDataToBodyData( + await (request.bodyCache.formData as FormData | Promise), + options + ) + } const headers = isRawRequest(request) ? request.headers : request.raw.headers const arrayBuffer = await (request as Request).arrayBuffer() const formDataPromise = bufferToFormData(arrayBuffer, headers.get('Content-Type') || '') From d7964503c956ae4af78597b7b11a05f9e5e73d2d Mon Sep 17 00:00:00 2001 From: Yusuke Wada Date: Sat, 18 Jul 2026 08:43:46 +0900 Subject: [PATCH 4/4] fix(request): fix multipart boundary mismatch in `cloneRawRequest` (#5133) --- src/request.test.ts | 24 ++++++++++++++++++++++++ src/request.ts | 12 ++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/request.test.ts b/src/request.test.ts index e4f254698f..8b894cba4e 100644 --- a/src/request.test.ts +++ b/src/request.test.ts @@ -556,6 +556,30 @@ describe('cloneRawRequest', () => { expect(req.raw, 'cloned request should contain the same properties').toMatchObject(clonedReq) }) + test('clones consumed multipart request with a matching boundary', async () => { + const data = new FormData() + data.append('foo', 'bar') + data.append('file', new File(['hello'], 't.txt', { type: 'text/plain' })) + const req = new HonoRequest( + new Request('http://localhost', { + method: 'POST', + body: data, + }) + ) + await req.formData() + + const clonedReq = await cloneRawRequest(req) + + const contentType = clonedReq.headers.get('Content-Type') ?? '' + const boundary = contentType.split('boundary=')[1] + const bodyText = await clonedReq.clone().text() + expect(bodyText.startsWith(`--${boundary}`)).toBe(true) + + const formData = await clonedReq.formData() + expect(formData.get('foo')).toBe('bar') + expect(formData.get('file')).toBeInstanceOf(File) + }) + test('clones GET request without body', async () => { const req = new HonoRequest( new Request('http://localhost', { diff --git a/src/request.ts b/src/request.ts index 41fde0d2b7..c3cfc32ba1 100644 --- a/src/request.ts +++ b/src/request.ts @@ -486,11 +486,19 @@ export const cloneRawRequest = async (req: HonoRequest): Promise => { }) } + const body = await req[cacheKey]() + const headers = req.header() + if (body instanceof FormData) { + // The FormData is re-serialized with a fresh multipart boundary, so the original + // Content-Type header no longer matches. Let the Request constructor generate it. + delete headers['content-type'] + } + const requestInit: RequiredRequestInit = { - body: await req[cacheKey](), + body, cache: req.raw.cache, credentials: req.raw.credentials, - headers: req.header(), + headers, integrity: req.raw.integrity, keepalive: req.raw.keepalive, method: req.method,