Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: honojs/hono
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v4.7.4
Choose a base ref
...
head repository: honojs/hono
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v4.7.5
Choose a head ref
  • 5 commits
  • 7 files changed
  • 4 contributors

Commits on Mar 15, 2025

  1. docs(MIGRATION): Fix typo (#3999)

    movahhedi authored Mar 15, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    BurntSushi Andrew Gallant
    Copy the full SHA
    d629c62 View commit details

Commits on Mar 16, 2025

  1. fix(bun): export BunWebSocketData and BunWebSocketHandler (#4002)

    yusukebe authored Mar 16, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    BurntSushi Andrew Gallant
    Copy the full SHA
    6659ea1 View commit details
  2. types(compose): follow up #3932 (#3934)

    * chore: moving the setup file of vitest
    
    * chore(compose): follow up #3932
    
    * fix tests
    EdamAme-x authored Mar 16, 2025
    Copy the full SHA
    035c2d7 View commit details

Commits on Mar 19, 2025

  1. fix(adapter/aws-lambda): APIGWProxyResult should contain either of he…

    …aders or multiValueHeaders, not both (#3883)
    
    * fix(adapter/aws-lambda): API GW Proxy Result should contain either of headers or multiValueHeaders, not both, to avoid confusing
    header merge mechanism
    
    * fix(adapter/aws-lambda): Infer response type from event
    exoego authored Mar 19, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    0e4dfe5 View commit details

Commits on Mar 20, 2025

  1. 4.7.5

    yusukebe committed Mar 20, 2025
    Copy the full SHA
    5473682 View commit details
Showing with 72 additions and 50 deletions.
  1. +1 −1 docs/MIGRATION.md
  2. +1 −1 package.json
  3. +21 −6 runtime-tests/lambda/index.test.ts
  4. +42 −36 src/adapter/aws-lambda/handler.ts
  5. +1 −0 src/adapter/bun/index.ts
  6. +2 −2 src/compose.test.ts
  7. +4 −4 src/compose.ts
2 changes: 1 addition & 1 deletion docs/MIGRATION.md
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@

### `deno.land/x` to JSR

There is no braking change, but we no longer publish the module from `deno.land/x`. If you want to use Hono on Deno, use JSR instead of it.
There is no breaking change, but we no longer publish the module from `deno.land/x`. If you want to use Hono on Deno, use JSR instead of it.

If you migrate, replace the path `deno.land/x` with JSR's.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hono",
"version": "4.7.4",
"version": "4.7.5",
"description": "Web framework built on Web Standards",
"main": "dist/cjs/index.js",
"type": "module",
27 changes: 21 additions & 6 deletions runtime-tests/lambda/index.test.ts
Original file line number Diff line number Diff line change
@@ -249,6 +249,7 @@ describe('AWS Lambda Adapter for Hono', () => {
expect(response.statusCode).toBe(200)
expect(response.body).toBe('Hello Lambda!')
expect(response.headers['content-type']).toMatch(/^text\/plain/)
expect(response.multiValueHeaders).toBeUndefined()
expect(response.isBase64Encoded).toBe(false)
})

@@ -268,6 +269,7 @@ describe('AWS Lambda Adapter for Hono', () => {
expect(response.statusCode).toBe(200)
expect(response.body).toBe('RmFrZSBJbWFnZQ==')
expect(response.headers['content-type']).toMatch(/^image\/png/)
expect(response.multiValueHeaders).toBeUndefined()
expect(response.isBase64Encoded).toBe(true)
})

@@ -289,6 +291,7 @@ describe('AWS Lambda Adapter for Hono', () => {
expect(response.statusCode).toBe(200)
expect(response.body).toBe('Hello Lambda!')
expect(response.headers['content-type']).toMatch(/^text\/plain/)
expect(response.multiValueHeaders).toBeUndefined()
expect(response.isBase64Encoded).toBe(false)
})

@@ -309,6 +312,7 @@ describe('AWS Lambda Adapter for Hono', () => {
expect(response.statusCode).toBe(200)
expect(response.body).toBe('Hello Lambda!')
expect(response.headers['content-type']).toMatch(/^text\/plain/)
expect(response.multiValueHeaders).toBeUndefined()
expect(response.isBase64Encoded).toBe(false)
})

@@ -540,6 +544,7 @@ describe('AWS Lambda Adapter for Hono', () => {
'content-type': 'application/json',
})
)
expect(albResponse.multiValueHeaders).toBeUndefined()
})

it('Should extract single-value headers and return 200 (APIGatewayProxyEvent)', async () => {
@@ -687,6 +692,7 @@ describe('AWS Lambda Adapter for Hono', () => {
expect(albResponse.statusCode).toBe(200)
expect(albResponse.body).toBe('Valid Cookies')
expect(albResponse.headers['content-type']).toMatch(/^text\/plain/)
expect(albResponse.multiValueHeaders).toBeUndefined()
expect(albResponse.isBase64Encoded).toBe(false)
})

@@ -709,7 +715,10 @@ describe('AWS Lambda Adapter for Hono', () => {

expect(albResponse.statusCode).toBe(200)
expect(albResponse.body).toBe('Valid Cookies')
expect(albResponse.headers['content-type']).toMatch(/^text\/plain/)
expect(albResponse.headers).toBeUndefined()
expect(albResponse.multiValueHeaders['content-type']).toEqual([
expect.stringMatching(/^text\/plain/),
])
expect(albResponse.isBase64Encoded).toBe(false)
})

@@ -759,9 +768,8 @@ describe('AWS Lambda Adapter for Hono', () => {

expect(albResponse.statusCode).toBe(200)
expect(albResponse.body).toBe('Cookies Set')
expect(albResponse.headers['content-type']).toMatch(/^text\/plain/)
expect(albResponse.multiValueHeaders).toBeDefined()
expect(albResponse.multiValueHeaders && albResponse.multiValueHeaders['set-cookie']).toEqual(
expect(albResponse.headers).toBeUndefined()
expect(albResponse.multiValueHeaders['set-cookie']).toEqual(
expect.arrayContaining([testCookie1.serialized, testCookie2.serialized])
)
expect(albResponse.isBase64Encoded).toBe(false)
@@ -794,6 +802,7 @@ describe('AWS Lambda Adapter for Hono', () => {
})
)
expect(albResponse.headers['content-type']).toMatch(/^application\/json/)
expect(albResponse.multiValueHeaders).toBeUndefined()
expect(albResponse.isBase64Encoded).toBe(false)
})

@@ -823,7 +832,10 @@ describe('AWS Lambda Adapter for Hono', () => {
key2: 'value2',
})
)
expect(albResponse.headers['content-type']).toMatch(/^application\/json/)
expect(albResponse.headers).toBeUndefined()
expect(albResponse.multiValueHeaders['content-type']).toEqual([
expect.stringMatching(/^application\/json/),
])
expect(albResponse.isBase64Encoded).toBe(false)
})

@@ -853,7 +865,10 @@ describe('AWS Lambda Adapter for Hono', () => {
key2: ['value2', 'otherValue2'],
})
)
expect(albResponse.headers['content-type']).toMatch(/^application\/json/)
expect(albResponse.headers).toBeUndefined()
expect(albResponse.multiValueHeaders['content-type']).toEqual([
expect.stringMatching(/^application\/json/),
])
expect(albResponse.isBase64Encoded).toBe(false)
})
})
78 changes: 42 additions & 36 deletions src/adapter/aws-lambda/handler.ts
Original file line number Diff line number Diff line change
@@ -70,17 +70,22 @@ export interface ALBProxyEvent {
requestContext: ALBRequestContext
}

export interface APIGatewayProxyResult {
type WithHeaders = {
headers: Record<string, string>
multiValueHeaders?: undefined
}
type WithMultiValueHeaders = {
headers?: undefined
multiValueHeaders: Record<string, string[]>
}

export type APIGatewayProxyResult = {
statusCode: number
statusDescription?: string
body: string
headers: Record<string, string>
cookies?: string[]
multiValueHeaders?: {
[headerKey: string]: string[]
}
isBase64Encoded: boolean
}
} & (WithHeaders | WithMultiValueHeaders)

const getRequestContext = (
event: LambdaEvent
@@ -162,7 +167,16 @@ export const streamHandle = <
*/
export const handle = <E extends Env = Env, S extends Schema = {}, BasePath extends string = '/'>(
app: Hono<E, S, BasePath>
): ((event: LambdaEvent, lambdaContext?: LambdaContext) => Promise<APIGatewayProxyResult>) => {
): (<L extends LambdaEvent>(
event: L,
lambdaContext?: LambdaContext
) => Promise<
APIGatewayProxyResult &
(L extends { multiValueHeaders: Record<string, string[]> }
? WithMultiValueHeaders
: WithHeaders)
>) => {
// @ts-expect-error FIXME: Fix return typing
return async (event, lambdaContext?) => {
const processor = getProcessor(event)

@@ -190,11 +204,7 @@ export abstract class EventProcessor<E extends LambdaEvent> {

protected abstract getCookies(event: E, headers: Headers): void

protected abstract setCookiesToResult(
event: E,
result: APIGatewayProxyResult,
cookies: string[]
): void
protected abstract setCookiesToResult(result: APIGatewayProxyResult, cookies: string[]): void

createRequest(event: E): Request {
const queryString = this.getQueryString(event)
@@ -234,19 +244,27 @@ export abstract class EventProcessor<E extends LambdaEvent> {

const result: APIGatewayProxyResult = {
body: body,
headers: {},
multiValueHeaders: event.multiValueHeaders ? {} : undefined,
statusCode: res.status,
isBase64Encoded,
...(event.multiValueHeaders
? {
multiValueHeaders: {},
}
: {
headers: {},
}),
}

this.setCookies(event, res, result)
res.headers.forEach((value, key) => {
result.headers[key] = value
if (event.multiValueHeaders && result.multiValueHeaders) {
if (result.multiValueHeaders) {
res.headers.forEach((value, key) => {
result.multiValueHeaders[key] = [value]
}
})
})
} else {
res.headers.forEach((value, key) => {
result.headers[key] = value
})
}

return result
}
@@ -260,7 +278,7 @@ export abstract class EventProcessor<E extends LambdaEvent> {
.map(([, v]) => v)

if (Array.isArray(cookies)) {
this.setCookiesToResult(event, result, cookies)
this.setCookiesToResult(result, cookies)
res.headers.delete('set-cookie')
}
}
@@ -286,11 +304,7 @@ export class EventV2Processor extends EventProcessor<APIGatewayProxyEventV2> {
}
}

protected setCookiesToResult(
_: APIGatewayProxyEventV2,
result: APIGatewayProxyResult,
cookies: string[]
): void {
protected setCookiesToResult(result: APIGatewayProxyResult, cookies: string[]): void {
result.cookies = cookies
}

@@ -365,11 +379,7 @@ export class EventV1Processor extends EventProcessor<Exclude<LambdaEvent, APIGat
return headers
}

protected setCookiesToResult(
_: APIGatewayProxyEvent,
result: APIGatewayProxyResult,
cookies: string[]
): void {
protected setCookiesToResult(result: APIGatewayProxyResult, cookies: string[]): void {
result.multiValueHeaders = {
'set-cookie': cookies,
}
@@ -446,13 +456,9 @@ export class ALBProcessor extends EventProcessor<ALBProxyEvent> {
}
}

protected setCookiesToResult(
event: ALBProxyEvent,
result: APIGatewayProxyResult,
cookies: string[]
): void {
protected setCookiesToResult(result: APIGatewayProxyResult, cookies: string[]): void {
// when multi value headers is enabled
if (event.multiValueHeaders && result.multiValueHeaders) {
if (result.multiValueHeaders) {
result.multiValueHeaders['set-cookie'] = cookies
} else {
// otherwise serialize the set-cookie
1 change: 1 addition & 0 deletions src/adapter/bun/index.ts
Original file line number Diff line number Diff line change
@@ -6,4 +6,5 @@
export { serveStatic } from './serve-static'
export { bunFileSystemModule, toSSG } from './ssg'
export { createBunWebSocket } from './websocket'
export type { BunWebSocketData, BunWebSocketHandler } from './websocket'
export { getConnInfo } from './conninfo'
4 changes: 2 additions & 2 deletions src/compose.test.ts
Original file line number Diff line number Diff line change
@@ -740,10 +740,10 @@ describe('Compose', function () {
ctx.set('middleware', 0)
ctx.set('next', 0)

await compose(middleware)(ctx, (ctx: Context, next: Next) => {
await compose(middleware)(ctx, ((ctx: Context, next: Next) => {
ctx.set('next', ctx.get('next') + 1)
return next()
})
}) as Next)

expect(ctx.get('middleware')).toEqual(1)
expect(ctx.get('next')).toEqual(1)
8 changes: 4 additions & 4 deletions src/compose.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import type { Context } from './context'
import type { Env, ErrorHandler, NotFoundHandler } from './types'
import type { Env, ErrorHandler, Next, NotFoundHandler } from './types'

/**
* Compose middleware functions into a single function based on `koa-compose` package.
*
* @template E - The environment type.
*
* @param {[[Function, unknown], ParamIndexMap | Params][]} middleware - An array of middleware functions and their corresponding parameters.
* @param {[[Function, unknown], unknown][] | [[Function]][]} middleware - An array of middleware functions and their corresponding parameters.
* @param {ErrorHandler<E>} [onError] - An optional error handler function.
* @param {NotFoundHandler<E>} [onNotFound] - An optional not-found handler function.
*
* @returns {(context: Context, next?: Function) => Promise<>} - A composed middleware function.
* @returns {(context: Context, next?: Next) => Promise<Context>} - A composed middleware function.
*/
export const compose = <E extends Env = Env>(
middleware: [[Function, unknown], unknown][] | [[Function]][],
onError?: ErrorHandler<E>,
onNotFound?: NotFoundHandler<E>
): ((context: Context, next?: Function) => Promise<Context>) => {
): ((context: Context, next?: Next) => Promise<Context>) => {
return (context, next) => {
let index = -1