Skip to content

Commit

Permalink
Merge branch 'main' into update-vitest-3
Browse files Browse the repository at this point in the history
  • Loading branch information
yasuaki640 committed Feb 3, 2025
2 parents 6002b2d + 65edaf2 commit 1e014f9
Show file tree
Hide file tree
Showing 16 changed files with 2,201 additions and 6,336 deletions.
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
node-version: '20.x'
- uses: oven-sh/setup-bun@v2
with:
bun-version: '1.1.33'
bun-version: '1.1.39'
- run: bun install
- run: bun run format
- run: bun run lint
Expand Down Expand Up @@ -95,7 +95,8 @@ jobs:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: '1.1.33'
bun-version: '1.1.39'
- run: bun install
- run: bun run test:bun
- uses: actions/upload-artifact@v4
with:
Expand Down
2,031 changes: 2,031 additions & 0 deletions bun.lock

Large diffs are not rendered by default.

Binary file removed bun.lockb
Binary file not shown.
3 changes: 0 additions & 3 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
[install.lockfile]
print = "yarn"

[test]
coverage = true
coverageReporter = ["text", "lcov"]
Expand Down
8 changes: 1 addition & 7 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,9 @@ After that, please install the dependency environment.
bun install
```

If you can't do that, there is also a `yarn.lock` file, so you can do the same with the `yarn` command.

```bash
yarn install --frozen-lockfile
```

## PRs

Please ensure your PR passes tests with `bun run test` or `yarn test`.
Please ensure your PR passes tests with `bun run test`.

## Third-party middleware

Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hono",
"version": "4.6.19",
"version": "4.6.20",
"description": "Web framework built on Web Standards",
"main": "dist/cjs/index.js",
"type": "module",
Expand Down Expand Up @@ -632,21 +632,21 @@
"@types/jsdom": "^21.1.7",
"@types/node": "20.11.4",
"@types/supertest": "^2.0.16",
"@vitest/coverage-v8": "^3.0.4",
"@vitest/coverage-v8": "^3.0.5",
"arg": "^5.0.2",
"bun-types": "^1.1.34",
"bun-types": "^1.1.39",
"esbuild": "^0.15.18",
"eslint": "^9.10.0",
"glob": "^11.0.0",
"jsdom": "^22.1.0",
"msw": "^2.6.0",
"np": "7.7.0",
"np": "10.2.0",
"prettier": "^2.6.2",
"publint": "^0.1.16",
"supertest": "^6.3.4",
"typescript": "^5.3.3",
"vite-plugin-fastly-js-compute": "^0.4.2",
"vitest": "^3.0.4",
"vitest": "^3.0.5",
"wrangler": "3.58.0",
"ws": "^8.18.0",
"zod": "^3.23.8"
Expand Down
86 changes: 85 additions & 1 deletion src/adapter/lambda-edge/handler.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { describe } from 'vitest'
import { setCookie } from '../../helper/cookie'
import { Hono } from '../../hono'
import { encodeBase64 } from '../../utils/encode'
import { createBody, isContentTypeBinary } from './handler'
import type { CloudFrontEdgeEvent } from './handler'
import { createBody, handle, isContentTypeBinary } from './handler'

describe('isContentTypeBinary', () => {
it('Should determine whether it is binary', () => {
Expand Down Expand Up @@ -36,3 +40,83 @@ describe('createBody', () => {
expect(createBody('POST', body)).not.toEqual(undefined)
})
})

describe('handle', () => {
const cloudFrontEdgeEvent: CloudFrontEdgeEvent = {
Records: [
{
cf: {
config: {
distributionDomainName: 'd111111abcdef8.cloudfront.net',
distributionId: 'EDFDVBD6EXAMPLE',
eventType: 'viewer-request',
requestId: '4TyzHTaYWb1GX1qTfsHhEqV6HUDd_BzoBZnwfnvQc_1oF26ClkoUSEQ==',
},
request: {
clientIp: '1.2.3.4',
headers: {
host: [
{
key: 'Host',
value: 'hono.dev',
},
],
accept: [
{
key: 'accept',
value: '*/*',
},
],
},
method: 'GET',
querystring: '',
uri: '/test-path',
},
},
},
],
}

it('Should support alternate domain names', async () => {
const app = new Hono()
app.get('/test-path', (c) => {
return c.text(c.req.url)
})
const handler = handle(app)

const res = await handler(cloudFrontEdgeEvent)

expect(res.body).toBe('https://hono.dev/test-path')
})

it('Should support multiple cookies', async () => {
const app = new Hono()
app.get('/test-path', (c) => {
setCookie(c, 'cookie1', 'value1')
setCookie(c, 'cookie2', 'value2')
return c.text('')
})
const handler = handle(app)

const res = await handler(cloudFrontEdgeEvent)

expect(res.headers).toEqual({
'content-type': [
{
key: 'content-type',
value: 'text/plain; charset=UTF-8',
},
],
'set-cookie': [
{
key: 'set-cookie',
value: 'cookie1=value1; Path=/',
},
{
key: 'set-cookie',
value: 'cookie2=value2; Path=/',
},
],
})
})
})
10 changes: 8 additions & 2 deletions src/adapter/lambda-edge/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,10 @@ interface CloudFrontResult {
const convertHeaders = (headers: Headers): CloudFrontHeaders => {
const cfHeaders: CloudFrontHeaders = {}
headers.forEach((value, key) => {
cfHeaders[key.toLowerCase()] = [{ key: key.toLowerCase(), value }]
cfHeaders[key.toLowerCase()] = [
...(cfHeaders[key.toLowerCase()] || []),
{ key: key.toLowerCase(), value },
]
})
return cfHeaders
}
Expand Down Expand Up @@ -147,7 +150,10 @@ const createResult = async (res: Response): Promise<CloudFrontResult> => {

const createRequest = (event: CloudFrontEdgeEvent): Request => {
const queryString = event.Records[0].cf.request.querystring
const urlPath = `https://${event.Records[0].cf.config.distributionDomainName}${event.Records[0].cf.request.uri}`
const host =
event.Records[0].cf.request.headers?.host?.[0]?.value ||
event.Records[0].cf.config.distributionDomainName
const urlPath = `https://${host}${event.Records[0].cf.request.uri}`
const url = queryString ? `${urlPath}?${queryString}` : urlPath

const headers = new Headers()
Expand Down
5 changes: 5 additions & 0 deletions src/helper/streaming/sse.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ describe('SSE Streaming helper', () => {
})

it('Check streamSSE Response if aborted by abort signal', async () => {
// Emulate an old version of Bun (version 1.1.0) for this specific test case
// @ts-expect-error Bun is not typed
global.Bun = {
version: '1.1.0',
}
const ac = new AbortController()
const req = new Request('http://localhost/', { signal: ac.signal })
const c = new Context(req)
Expand Down
17 changes: 11 additions & 6 deletions src/helper/streaming/sse.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Context } from '../../context'
import { HtmlEscapedCallbackPhase, resolveCallback } from '../../utils/html'
import { StreamingApi } from '../../utils/stream'
import { isOldBunVersion } from './utils'

export interface SSEMessage {
data: string | Promise<string>
Expand Down Expand Up @@ -61,6 +62,7 @@ const run = async (
}

const contextStash: WeakMap<ReadableStream, Context> = new WeakMap<ReadableStream, Context>()

export const streamSSE = (
c: Context,
cb: (stream: SSEStreamingApi) => Promise<void>,
Expand All @@ -69,12 +71,15 @@ export const streamSSE = (
const { readable, writable } = new TransformStream()
const stream = new SSEStreamingApi(writable, readable)

// bun does not cancel response stream when request is canceled, so detect abort by signal
c.req.raw.signal.addEventListener('abort', () => {
if (!stream.closed) {
stream.abort()
}
})
// Until Bun v1.1.27, Bun didn't call cancel() on the ReadableStream for Response objects from Bun.serve()
if (isOldBunVersion()) {
c.req.raw.signal.addEventListener('abort', () => {
if (!stream.closed) {
stream.abort()
}
})
}

// in bun, `c` is destroyed when the request is returned, so hold it until the end of streaming
contextStash.set(stream.responseReadable, c)

Expand Down
15 changes: 15 additions & 0 deletions src/helper/streaming/stream.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Context } from '../../context'
import { isOldBunVersion } from './utils'

Check warning on line 2 in src/helper/streaming/stream.test.ts

View workflow job for this annotation

GitHub Actions / Main

'isOldBunVersion' is defined but never used
import { stream } from '.'

describe('Basic Streaming Helper', () => {
Expand Down Expand Up @@ -47,6 +48,11 @@ describe('Basic Streaming Helper', () => {
})

it('Check stream Response if aborted by abort signal', async () => {
// Emulate an old version of Bun (version 1.1.0) for this specific test case
// @ts-expect-error Bun is not typed
global.Bun = {
version: '1.1.0',
}
const ac = new AbortController()
const req = new Request('http://localhost/', { signal: ac.signal })
const c = new Context(req)
Expand All @@ -69,9 +75,16 @@ describe('Basic Streaming Helper', () => {
expect(value).toEqual(new Uint8Array([0]))
ac.abort()
expect(aborted).toBeTruthy()
// @ts-expect-error Bun is not typed
delete global.Bun
})

it('Check stream Response if pipe is aborted by abort signal', async () => {
// Emulate an old version of Bun (version 1.1.0) for this specific test case
// @ts-expect-error Bun is not typed
global.Bun = {
version: '1.1.0',
}
const ac = new AbortController()
const req = new Request('http://localhost/', { signal: ac.signal })
const c = new Context(req)
Expand All @@ -91,6 +104,8 @@ describe('Basic Streaming Helper', () => {
ac.abort()
await pReading
expect(aborted).toBeTruthy()
// @ts-expect-error Bun is not typed
delete global.Bun
})

it('Check stream Response if error occurred', async () => {
Expand Down
17 changes: 11 additions & 6 deletions src/helper/streaming/stream.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { Context } from '../../context'
import { StreamingApi } from '../../utils/stream'
import { isOldBunVersion } from './utils'

const contextStash: WeakMap<ReadableStream, Context> = new WeakMap<ReadableStream, Context>()

export const stream = (
c: Context,
cb: (stream: StreamingApi) => Promise<void>,
Expand All @@ -10,12 +12,15 @@ export const stream = (
const { readable, writable } = new TransformStream()
const stream = new StreamingApi(writable, readable)

// bun does not cancel response stream when request is canceled, so detect abort by signal
c.req.raw.signal.addEventListener('abort', () => {
if (!stream.closed) {
stream.abort()
}
})
// Until Bun v1.1.27, Bun didn't call cancel() on the ReadableStream for Response objects from Bun.serve()
if (isOldBunVersion()) {
c.req.raw.signal.addEventListener('abort', () => {
if (!stream.closed) {
stream.abort()
}
})
}

// in bun, `c` is destroyed when the request is returned, so hold it until the end of streaming
contextStash.set(stream.responseReadable, c)
;(async () => {
Expand Down
11 changes: 11 additions & 0 deletions src/helper/streaming/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export let isOldBunVersion = (): boolean => {
// @ts-expect-error @types/bun is not installed
const version: string = typeof Bun !== 'undefined' ? Bun.version : undefined
if (version === undefined) {
return false
}
const result = version.startsWith('1.1') || version.startsWith('1.0') || version.startsWith('0.')
// Avoid running this check on every call
isOldBunVersion = () => result
return result
}
14 changes: 14 additions & 0 deletions src/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,20 @@ describe('headers', () => {
const foo = req.header('foo')
expect(foo).toEqual('')
})

test('Keys of the arguments for req.header() are not case-sensitive', () => {
const req = new HonoRequest(
new Request('http://localhost', {
headers: {
'Content-Type': 'application/json',
apikey: 'abc',
lowercase: 'lowercase value',
},
})
)
expect(req.header('Content-Type')).toBe('application/json')
expect(req.header('ApiKey')).toBe('abc')
})
})

const text = '{"foo":"bar"}'
Expand Down
2 changes: 1 addition & 1 deletion src/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
header(): Record<RequestHeader | (string & CustomHeader), string>
header(name?: string) {
if (name) {
return this.raw.headers.get(name.toLowerCase()) ?? undefined
return this.raw.headers.get(name) ?? undefined
}

const headerData: Record<string, string | undefined> = {}
Expand Down
Loading

0 comments on commit 1e014f9

Please sign in to comment.