Skip to content

Commit

Permalink
Merge pull request #1489 from honojs/next
Browse files Browse the repository at this point in the history
  • Loading branch information
yusukebe authored Sep 21, 2023
2 parents d6478aa + 6c37462 commit 3eb52bc
Show file tree
Hide file tree
Showing 28 changed files with 601 additions and 51 deletions.
1 change: 1 addition & 0 deletions benchmarks/routers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"find-my-way": "^7.4.0",
"koa-router": "^12.0.0",
"koa-tree-router": "^0.12.1",
"memoirist": "^0.1.4",
"mitata": "^0.1.6",
"radix3": "^1.0.1",
"trek-router": "^1.2.0"
Expand Down
2 changes: 2 additions & 0 deletions benchmarks/routers/src/bench.mts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { regExpRouter, trieRouter } from './hono.mts'
import { koaRouter } from './koa-router.mts'
import { koaTreeRouter } from './koa-tree-router.mts'
import { medleyRouter } from './medley-router.mts'
import { memoiristRouter } from './memoirist.mts'
import { radix3Router } from './radix3.mts'
import type { Route, RouterInterface } from './tool.mts'
import { trekRouter } from './trek-router.mts'
Expand All @@ -19,6 +20,7 @@ const routers: RouterInterface[] = [
expressRouter,
koaRouter,
radix3Router,
memoiristRouter,
]

medleyRouter.match({ method: 'GET', path: '/user' })
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/routers/src/hono.mts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Router } from '../../../src/router.ts'
import { RegExpRouter } from '../../../src/router/reg-exp-router/index.ts'
import { TrieRouter } from '../../../src/router/trie-router/index.ts'
import type { Router } from '../../../src/router.ts'
import type { RouterInterface } from './tool.mts'
import { routes, handler } from './tool.mts'

Expand Down
17 changes: 17 additions & 0 deletions benchmarks/routers/src/memoirist.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Memoirist } from 'memoirist'
import type { RouterInterface } from './tool.mts'
import { routes, handler } from './tool.mts'

const name = 'Memoirist'
const router = new Memoirist()

for (const route of routes) {
router.add(route.method, route.path, handler)
}

export const memoiristRouter: RouterInterface = {
name,
match: (route) => {
router.find(route.method, route.path)
},
}
15 changes: 14 additions & 1 deletion deno_dist/client/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Hono } from '../hono.ts'
import type { ValidationTargets } from '../types.ts'
import { serialize } from '../utils/cookie.ts'
import type { UnionToIntersection } from '../utils/types.ts'
import type { Callback, Client, ClientRequestOptions } from './types.ts'
import { deepMerge, mergePath, removeIndexString, replaceUrlParam } from './utils.ts'
Expand Down Expand Up @@ -86,7 +87,19 @@ class ClientRequestImpl {
let methodUpperCase = this.method.toUpperCase()
let setBody = !(methodUpperCase === 'GET' || methodUpperCase === 'HEAD')

const headerValues: Record<string, string> = opt?.headers ? opt.headers : {}
const headerValues: Record<string, string> = {
...(args?.header ?? {}),
...(opt?.headers ? opt.headers : {}),
}

if (args?.cookie) {
const cookies: string[] = []
for (const [key, value] of Object.entries(args.cookie)) {
cookies.push(serialize(key, value, { path: '/' }))
}
headerValues['Cookie'] = cookies.join(',')
}

if (this.cType) headerValues['Content-Type'] = this.cType

const headers = new Headers(headerValues ?? undefined)
Expand Down
37 changes: 26 additions & 11 deletions deno_dist/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@ import type { RemoveBlankRecord } from '../utils/types.ts'

type HonoRequest = typeof Hono.prototype['request']

export type ClientRequestOptions = {
headers?: Record<string, string>
fetch?: typeof fetch | HonoRequest
}
export type ClientRequestOptions<T = unknown> = keyof T extends never
? {
headers?: Record<string, string>
fetch?: typeof fetch | HonoRequest
}
: {
headers: T
fetch?: typeof fetch | HonoRequest
}

type ClientRequest<S extends Schema> = {
[M in keyof S]: S[M] extends { input: infer R; output: infer O }
? RemoveBlankRecord<R> extends never
? (args?: {}, options?: ClientRequestOptions) => Promise<ClientResponse<O>>
: (
// Client does not support `header` and `cookie`
args: Omit<R, 'header' | 'cookie'>,
options?: ClientRequestOptions
) => Promise<ClientResponse<O>>
: (args: R, options?: ClientRequestOptions) => Promise<ClientResponse<O>>
: never
} & {
$url: () => URL
Expand Down Expand Up @@ -53,12 +54,26 @@ export type Fetch<T> = (

export type InferResponseType<T> = T extends (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args: any | undefined
args: any | undefined,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
options: any | undefined
) => Promise<ClientResponse<infer O>>
? O
: never

export type InferRequestType<T> = T extends (args: infer R) => Promise<ClientResponse<unknown>>
export type InferRequestType<T> = T extends (
args: infer R,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
options: any | undefined
) => Promise<ClientResponse<unknown>>
? NonNullable<R>
: never

export type InferRequestOptionsType<T> = T extends (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args: any,
options: infer R
) => Promise<ClientResponse<unknown>>
? NonNullable<R>
: never

Expand Down
31 changes: 30 additions & 1 deletion deno_dist/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { Env, NotFoundHandler, Input, TypedResponse } from './types.ts'
import type { CookieOptions } from './utils/cookie.ts'
import { serialize } from './utils/cookie.ts'
import type { StatusCode } from './utils/http-status.ts'
import { StreamingApi } from './utils/stream.ts'
import type { JSONValue, InterfaceToType } from './utils/types.ts'

type Runtime = 'node' | 'deno' | 'bun' | 'workerd' | 'fastly' | 'edge-light' | 'lagon' | 'other'
Expand Down Expand Up @@ -85,6 +86,8 @@ type ContextOptions<E extends Env> = {
notFoundHandler?: NotFoundHandler<E>
}

const TEXT_PLAIN = 'text/plain; charset=UTF-8'

export class Context<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
E extends Env = any,
Expand Down Expand Up @@ -310,7 +313,7 @@ export class Context<
// If Content-Type is not set, we don't have to set `text/plain`.
// Fewer the header values, it will be faster.
if (this._pH['content-type']) {
this._pH['content-type'] = 'text/plain; charset=UTF-8'
this._pH['content-type'] = TEXT_PLAIN
}
return typeof arg === 'number'
? this.newResponse(text, arg, headers)
Expand Down Expand Up @@ -371,6 +374,32 @@ export class Context<
return this.newResponse(null, status)
}

streamText = (
cb: (stream: StreamingApi) => Promise<void>,
arg?: StatusCode | ResponseInit,
headers?: HeaderRecord
): Response => {
headers ??= {}
this.header('content-type', TEXT_PLAIN)
this.header('x-content-type-options', 'nosniff')
this.header('transfer-encoding', 'chunked')
return this.stream(cb, arg, headers)
}

stream = (
cb: (stream: StreamingApi) => Promise<void>,
arg?: StatusCode | ResponseInit,
headers?: HeaderRecord
): Response => {
const { readable, writable } = new TransformStream()
const stream = new StreamingApi(writable)
cb(stream).finally(() => stream.close())

return typeof arg === 'number'
? this.newResponse(readable, arg, headers)
: this.newResponse(readable, arg)
}

/** @deprecated
* Use Cookie Middleware instead of `c.cookie()`. The `c.cookie()` will be removed in v4.
*
Expand Down
18 changes: 18 additions & 0 deletions deno_dist/helper/testing/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { hc } from '../../client/index.ts'
import type { Hono } from '../../hono.ts'

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ExtractEnv<T> = T extends Hono<infer E, any, any> ? E : never

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const testClient = <T extends Hono<any, any, any>>(
app: T,
Env?: ExtractEnv<T>['Bindings'] | {},
executionCtx?: ExecutionContext
) => {
const customFetch = (input: RequestInfo | URL, init?: RequestInit) => {
return app.request(input, init, Env, executionCtx)
}

return hc<typeof app>('', { fetch: customFetch })
}
4 changes: 4 additions & 0 deletions deno_dist/middleware/jwt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,7 @@ export const jwt = (options: {
await next()
}
}

export const verify = Jwt.verify
export const decode = Jwt.decode
export const sign = Jwt.sign
14 changes: 12 additions & 2 deletions deno_dist/utils/body.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { HonoRequest } from '../request.ts'

export type BodyData = Record<string, string | File>
export type BodyData = Record<string, string | string[] | File>

export const parseBody = async <T extends BodyData = BodyData>(
request: HonoRequest | Request
Expand All @@ -17,7 +17,17 @@ export const parseBody = async <T extends BodyData = BodyData>(
if (formData) {
const form: BodyData = {}
formData.forEach((value, key) => {
form[key] = value
if (key.slice(-2) === '[]') {
if (!form[key]) {
form[key] = [value.toString()]
} else {
if (Array.isArray(form[key])) {
;(form[key] as string[]).push(value.toString())
}
}
} else {
form[key] = value
}
})
body = form
}
Expand Down
11 changes: 8 additions & 3 deletions deno_dist/utils/cookie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type CookieOptions = {
secure?: boolean
signingSecret?: string
sameSite?: 'Strict' | 'Lax' | 'None'
partitioned?: boolean
}

const algorithm = { name: 'HMAC', hash: 'SHA-256' }
Expand Down Expand Up @@ -104,15 +105,15 @@ const _serialize = (name: string, value: string, opt: CookieOptions = {}): strin
}

if (opt.domain) {
cookie += '; Domain=' + opt.domain
cookie += `; Domain=${opt.domain}`
}

if (opt.path) {
cookie += '; Path=' + opt.path
cookie += `; Path=${opt.path}`
}

if (opt.expires) {
cookie += '; Expires=' + opt.expires.toUTCString()
cookie += `; Expires=${opt.expires.toUTCString()}`
}

if (opt.httpOnly) {
Expand All @@ -127,6 +128,10 @@ const _serialize = (name: string, value: string, opt: CookieOptions = {}): strin
cookie += `; SameSite=${opt.sameSite}`
}

if (opt.partitioned) {
cookie += '; Partitioned'
}

return cookie
}

Expand Down
46 changes: 46 additions & 0 deletions deno_dist/utils/stream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export class StreamingApi {
private writer: WritableStreamDefaultWriter<Uint8Array>
private encoder: TextEncoder
private writable: WritableStream

constructor(writable: WritableStream) {
this.writable = writable
this.writer = writable.getWriter()
this.encoder = new TextEncoder()
}

async write(input: Uint8Array | string) {
try {
if (typeof input === 'string') {
input = this.encoder.encode(input)
}
await this.writer.write(input)
} catch (e) {
// Do nothing. If you want to handle errors, create a stream by yourself.
}
return this
}

async writeln(input: string) {
await this.write(input + '\n')
return this
}

sleep(ms: number) {
return new Promise((res) => setTimeout(res, ms))
}

async close() {
try {
await this.writer.close()
} catch (e) {
// Do nothing. If you want to handle errors, create a stream by yourself.
}
}

async pipe(body: ReadableStream) {
this.writer.releaseLock()
await body.pipeTo(this.writable, { preventClose: true })
this.writer = this.writable.getWriter()
}
}
10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hono",
"version": "3.6.3",
"version": "3.7.0-rc.1",
"description": "Ultrafast web framework for the Edges",
"main": "dist/cjs/index.js",
"type": "module",
Expand Down Expand Up @@ -233,6 +233,11 @@
"types": "./dist/types/adapter/lambda-edge/index.d.ts",
"import": "./dist/adapter/lambda-edge/index.js",
"require": "./dist/cjs/adapter/lambda-edge/index.js"
},
"./testing": {
"types": "./dist/types/helper/testing/index.d.ts",
"import": "./dist/helper/testing/index.js",
"require": "./dist/cjs/helper/testing/index.js"
}
},
"typesVersions": {
Expand Down Expand Up @@ -353,6 +358,9 @@
],
"lambda-edge": [
"./dist/types/adapter/lambda-edge"
],
"testing": [
"./dist/types/helper/testing"
]
}
},
Expand Down
Loading

0 comments on commit 3eb52bc

Please sign in to comment.