diff --git a/deno_dist/client/client.ts b/deno_dist/client/client.ts index 4c4dc171c..37f670da9 100644 --- a/deno_dist/client/client.ts +++ b/deno_dist/client/client.ts @@ -115,7 +115,7 @@ export const hc = >( baseUrl: string, options?: ClientRequestOptions ) => - createProxy(async (opts) => { + createProxy((opts) => { const parts = [...opts.path] let method = '' @@ -128,6 +128,10 @@ export const hc = >( const path = parts.join('/') const url = mergePath(baseUrl, path) + if (method === 'url') { + return new URL(url) + } + const req = new ClientRequestImpl(url, method) if (method) { options ??= {} diff --git a/deno_dist/client/types.ts b/deno_dist/client/types.ts index ba6e650ef..13b5e4fb9 100644 --- a/deno_dist/client/types.ts +++ b/deno_dist/client/types.ts @@ -17,6 +17,8 @@ type ClientRequest = { options?: ClientRequestOptions ) => Promise> : never +} & { + $url: () => URL } type BlankRecordToNever = T extends Record diff --git a/deno_dist/context.ts b/deno_dist/context.ts index 57329e0da..b09a8826f 100644 --- a/deno_dist/context.ts +++ b/deno_dist/context.ts @@ -14,8 +14,15 @@ export interface ExecutionContext { waitUntil(promise: Promise): void passThroughOnException(): void } + export interface ContextVariableMap {} +export interface ContextRenderer {} +interface DefaultRenderer { + (content: string): Response | Promise +} +type Renderer = ContextRenderer extends Function ? ContextRenderer : DefaultRenderer + interface Get { (key: Key): ContextVariableMap[Key] (key: Key): E['Variables'][Key] @@ -87,16 +94,17 @@ export class Context< > { req: HonoRequest env: E['Bindings'] = {} + private _var: E['Variables'] = {} finalized: boolean = false error: Error | undefined = undefined private _status: StatusCode = 200 private _exCtx: FetchEventLike | ExecutionContext | undefined // _executionCtx - private _map: Record | undefined private _h: Headers | undefined = undefined // _headers private _pH: Record | undefined = undefined // _preparedHeaders private _res: Response | undefined private _init = true + private _renderer: Renderer = (content: string) => this.html(content) private notFoundHandler: NotFoundHandler = () => new Response() constructor(req: HonoRequest, options?: ContextOptions) { @@ -143,6 +151,25 @@ export class Context< this.finalized = true } + /** + * @experimental + * `c.render()` is an experimental feature. + * The API might be changed. + */ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-explicit-any + render: Renderer = (...args: any[]) => this._renderer(...args) + + /** + * @experimental + * `c.setRenderer()` is an experimental feature. + * The API might be changed. + */ + setRenderer = (renderer: Renderer) => { + this._renderer = renderer + } + header = (name: string, value: string | undefined, options?: { append?: boolean }): void => { // Clear the header if (value === undefined) { @@ -187,12 +214,17 @@ export class Context< } set: Set = (key: string, value: unknown) => { - this._map ||= {} - this._map[key as string] = value + this._var ??= {} + this._var[key as string] = value } get: Get = (key: string) => { - return this._map ? this._map[key] : undefined + return this._var ? this._var[key] : undefined + } + + // c.var.propName is a read-only + get var(): Readonly { + return { ...this._var } } newResponse: NewResponse = ( @@ -309,11 +341,15 @@ export class Context< : T : never > => { + const response = + typeof arg === 'number' ? this.json(object, arg, headers) : this.json(object, arg) + return { - response: typeof arg === 'number' ? this.json(object, arg, headers) : this.json(object, arg), + response, // eslint-disable-next-line @typescript-eslint/no-explicit-any data: object as any, format: 'json', + status: response.status, } } diff --git a/deno_dist/helper/factory/index.ts b/deno_dist/helper/factory/index.ts new file mode 100644 index 000000000..76ab5d53d --- /dev/null +++ b/deno_dist/helper/factory/index.ts @@ -0,0 +1,10 @@ +import type { Env, Input, MiddlewareHandler } from '../../types.ts' + +/** + * @experimental + * `middleware()` is an experimental feature. + * The API might be changed. + */ +export const middleware = ( + middleware: MiddlewareHandler +) => middleware diff --git a/deno_dist/jsx/index.test.tsx b/deno_dist/jsx/index.test.tsx index e1847562c..225ac20d8 100644 --- a/deno_dist/jsx/index.test.tsx +++ b/deno_dist/jsx/index.test.tsx @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { html } from '../helper/html/index.ts' import { Hono } from '../hono.ts' +import type { FC } from './index.ts' // eslint-disable-next-line @typescript-eslint/no-unused-vars import { jsx, memo, Fragment } from './index.ts' @@ -271,6 +272,32 @@ describe('render to string', () => { }) }) + describe('FC', () => { + it('Should define the type correctly', () => { + const Layout: FC<{ title: string }> = (props) => { + return ( + + + {props.title} + + {props.children} + + ) + } + + const Top = ( + +

Hono

+

Hono is great

+
+ ) + + expect(Top.toString()).toBe( + 'Home page

Hono

Hono is great

' + ) + }) + }) + describe('style attribute', () => { it('should convert the object to strings', () => { const template = ( diff --git a/deno_dist/jsx/index.ts b/deno_dist/jsx/index.ts index 68a150074..49691d4fe 100644 --- a/deno_dist/jsx/index.ts +++ b/deno_dist/jsx/index.ts @@ -198,7 +198,7 @@ const jsxFn = ( } } -type FC = (props: T) => HtmlEscapedString +export type FC = (props: T & { children?: Child }) => HtmlEscapedString const shallowEqual = (a: Props, b: Props): boolean => { if (a === b) { @@ -226,7 +226,7 @@ export const memo = ( ): FC => { let computed = undefined let prevProps: T | undefined = undefined - return ((props: T): HtmlEscapedString => { + return ((props: T & { children?: Child }): HtmlEscapedString => { if (prevProps && !propsAreEqual(prevProps, props)) { computed = undefined } diff --git a/deno_dist/middleware/bearer-auth/index.ts b/deno_dist/middleware/bearer-auth/index.ts index 53fb2962f..70eb575c6 100644 --- a/deno_dist/middleware/bearer-auth/index.ts +++ b/deno_dist/middleware/bearer-auth/index.ts @@ -24,7 +24,7 @@ export const bearerAuth = (options: { const realm = options.realm?.replace(/"/g, '\\"') return async (c, next) => { - const headerToken = c.req.headers.get('Authorization') + const headerToken = c.req.header('Authorization') if (!headerToken) { // No Authorization header diff --git a/deno_dist/middleware/cors/index.ts b/deno_dist/middleware/cors/index.ts index f30ecfab4..9bebfca35 100644 --- a/deno_dist/middleware/cors/index.ts +++ b/deno_dist/middleware/cors/index.ts @@ -36,7 +36,7 @@ export const cors = (options?: CORSOptions): MiddlewareHandler => { c.res.headers.set(key, value) } - const allowOrigin = findAllowOrigin(c.req.headers.get('origin') || '') + const allowOrigin = findAllowOrigin(c.req.header('origin') || '') if (allowOrigin) { set('Access-Control-Allow-Origin', allowOrigin) } @@ -70,7 +70,7 @@ export const cors = (options?: CORSOptions): MiddlewareHandler => { let headers = opts.allowHeaders if (!headers?.length) { - const requestHeaders = c.req.headers.get('Access-Control-Request-Headers') + const requestHeaders = c.req.header('Access-Control-Request-Headers') if (requestHeaders) { headers = requestHeaders.split(/\s*,\s*/) } diff --git a/deno_dist/middleware/etag/index.ts b/deno_dist/middleware/etag/index.ts index 68e75b537..2e6b3b6e7 100644 --- a/deno_dist/middleware/etag/index.ts +++ b/deno_dist/middleware/etag/index.ts @@ -30,7 +30,7 @@ export const etag = (options?: ETagOptions): MiddlewareHandler => { const weak = options?.weak ?? false return async (c, next) => { - const ifNoneMatch = c.req.headers.get('If-None-Match') + const ifNoneMatch = c.req.header('If-None-Match') ?? null await next() diff --git a/deno_dist/mod.ts b/deno_dist/mod.ts index 462499c6d..e8210ad42 100644 --- a/deno_dist/mod.ts +++ b/deno_dist/mod.ts @@ -20,7 +20,7 @@ export type { ToSchema, TypedResponse, } from './types.ts' -export type { Context, ContextVariableMap } from './context.ts' +export type { Context, ContextVariableMap, ContextRenderer } from './context.ts' export type { HonoRequest } from './request.ts' export { Hono } export { HTTPException } from './http-exception.ts' diff --git a/deno_dist/request.ts b/deno_dist/request.ts index 4b7286ab9..e4ce308cf 100644 --- a/deno_dist/request.ts +++ b/deno_dist/request.ts @@ -181,27 +181,63 @@ export class HonoRequest

{ get url() { return this.raw.url } + get method() { return this.raw.method } + + /** @deprecated + * Use `c.req.raw.headers` instead of `c.req.headers`. The `c.req.headers` will be removed in v4. + * Or you can get the header values with using `c.req.header`. + * @example + * + * app.get('/', (c) => { + * const userAgent = c.req.header('User-Agent') + * //... + * }) + */ get headers() { return this.raw.headers } + + /** @deprecated + * Use `c.req.raw.body` instead of `c.req.body`. The `c.req.body` will be removed in v4. + */ get body() { return this.raw.body } + + /** @deprecated + * Use `c.req.raw.bodyUsed` instead of `c.req.bodyUsed`. The `c.req.bodyUsed` will be removed in v4. + */ get bodyUsed() { return this.raw.bodyUsed } + + /** @deprecated + * Use `c.req.raw.integrity` instead of `c.req.integrity`. The `c.req.integrity` will be removed in v4. + */ get integrity() { return this.raw.integrity } + + /** @deprecated + * Use `c.req.raw.keepalive` instead of `c.req.keepalive`. The `c.req.keepalive` will be removed in v4. + */ get keepalive() { return this.raw.keepalive } + + /** @deprecated + * Use `c.req.raw.referrer` instead of `c.req.referrer`. The `c.req.referrer` will be removed in v4. + */ get referrer() { return this.raw.referrer } + + /** @deprecated + * Use `c.req.raw.signal` instead of `c.req.signal`. The `c.req.signal` will be removed in v4. + */ get signal() { return this.raw.signal } diff --git a/deno_dist/types.ts b/deno_dist/types.ts index 557b99d80..2f77b3286 100644 --- a/deno_dist/types.ts +++ b/deno_dist/types.ts @@ -3,6 +3,7 @@ /* eslint-disable @typescript-eslint/ban-types */ import type { Context } from './context.ts' import type { Hono } from './hono.ts' +import type { StatusCode } from './utils/http-status.ts' import type { UnionToIntersection } from './utils/types.ts' //////////////////////////////////////// @@ -50,8 +51,9 @@ export type H< E extends Env = any, P extends string = any, I extends Input = {}, + E2 extends Env = E, R extends HandlerResponse = any -> = Handler | MiddlewareHandler +> = Handler | MiddlewareHandler export type NotFoundHandler = (c: Context) => Response | Promise export type ErrorHandler = ( @@ -73,13 +75,26 @@ export interface HandlerInterface< > { //// app.get(...handlers[]) + // app.get(handler) + < + P extends string = ExtractKey extends never ? BasePath : ExtractKey, + I extends Input = {}, + R extends HandlerResponse = any, + Temp extends Env = E + >( + handler: H + ): Hono>, BasePath> + // app.get(handler, handler) < P extends string = ExtractKey extends never ? BasePath : ExtractKey, I extends Input = {}, - R extends HandlerResponse = any + R extends HandlerResponse = any, + E2 extends Env = E, + E3 extends Env = E, + Temp extends Env = E & E2 >( - ...handlers: [H, H] + ...handlers: [H, H] ): Hono>, BasePath> // app.get(handler x 3) @@ -88,9 +103,13 @@ export interface HandlerInterface< R extends HandlerResponse = any, I extends Input = {}, I2 extends Input = I, - I3 extends Input = I & I2 + I3 extends Input = I & I2, + E2 extends Env = E, + E3 extends Env = E, + E4 extends Env = E, + Temp extends Env = E & E2 & E3 >( - ...handlers: [H, H, H] + ...handlers: [H, H, H] ): Hono>, BasePath> // app.get(handler x 4) @@ -100,9 +119,19 @@ export interface HandlerInterface< I extends Input = {}, I2 extends Input = I, I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3 + I4 extends Input = I & I2 & I3, + E2 extends Env = E, + E3 extends Env = E, + E4 extends Env = E, + E5 extends Env = E, + Temp extends Env = E & E2 & E3 & E4 >( - ...handlers: [H, H, H, H] + ...handlers: [ + H, + H, + H, + H + ] ): Hono>, BasePath> // app.get(handler x 5) @@ -113,9 +142,21 @@ export interface HandlerInterface< I2 extends Input = I, I3 extends Input = I & I2, I4 extends Input = I2 & I3, - I5 extends Input = I & I2 & I3 & I4 + I5 extends Input = I & I2 & I3 & I4, + E2 extends Env = E, + E3 extends Env = E, + E4 extends Env = E, + E5 extends Env = E, + E6 extends Env = E, + Temp extends Env = E & E2 & E3 & E4 & E5 >( - ...handlers: [H, H, H, H, H] + ...handlers: [ + H, + H, + H, + H, + H + ] ): Hono>, BasePath> // app.get(...handlers[]) @@ -124,21 +165,45 @@ export interface HandlerInterface< I extends Input = {}, R extends HandlerResponse = any >( - ...handlers: Handler[] + ...handlers: H[] ): Hono>, BasePath> + //// app.get(path) + + // app.get(path) +

= any, I extends Input = {}>(path: P): Hono< + E, + S & ToSchema, I['in'], MergeTypedResponseData>, + BasePath + > + //// app.get(path, ...handlers[]) // app.get(path, handler) -

= any, I extends Input = {}>( + < + P extends string, + R extends HandlerResponse = any, + I extends Input = {}, + Temp extends Env = E + >( path: P, - handler: H, I, R> + handler: H, I, Temp, R> ): Hono, I['in'], MergeTypedResponseData>, BasePath> // app.get(path, handler, handler) -

= any, I extends Input = {}>( + < + P extends string, + R extends HandlerResponse = any, + I extends Input = {}, + E2 extends Env = E, + E3 extends Env = E, + Temp extends Env = E & E2 + >( path: P, - ...handlers: [H, I, R>, H, I, R>] + ...handlers: [ + H, I, E2, R>, + H, I, Temp, R> + ] ): Hono, I['in'], MergeTypedResponseData>, BasePath> // app.get(path, handler x3) @@ -147,13 +212,17 @@ export interface HandlerInterface< R extends HandlerResponse = any, I extends Input = {}, I2 extends Input = I, - I3 extends Input = I & I2 + I3 extends Input = I & I2, + E2 extends Env = E, + E3 extends Env = E, + E4 extends Env = E, + Temp extends Env = E & E2 & E3 >( path: P, ...handlers: [ - H, I, R>, - H, I2, R>, - H, I3, R> + H, I, E2, R>, + H, I2, E3, R>, + H, I3, Temp, R> ] ): Hono, I3['in'], MergeTypedResponseData>, BasePath> @@ -164,14 +233,19 @@ export interface HandlerInterface< I extends Input = {}, I2 extends Input = I, I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3 + I4 extends Input = I & I2 & I3, + E2 extends Env = E, + E3 extends Env = E, + E4 extends Env = E, + E5 extends Env = E, + Temp extends Env = E & E2 & E3 & E4 >( path: P, ...handlers: [ - H, I, R>, - H, I2, R>, - H, I3, R>, - H, I4, R> + H, I, E2, R>, + H, I2, E3, R>, + H, I3, E4, R>, + H, I4, Temp, R> ] ): Hono, I4['in'], MergeTypedResponseData>, BasePath> @@ -183,22 +257,28 @@ export interface HandlerInterface< I2 extends Input = I, I3 extends Input = I & I2, I4 extends Input = I2 & I3, - I5 extends Input = I & I2 & I3 & I4 + I5 extends Input = I & I2 & I3 & I4, + E2 extends Env = E, + E3 extends Env = E, + E4 extends Env = E, + E5 extends Env = E, + E6 extends Env = E, + Temp extends Env = E & E2 & E3 & E4 & E5 >( path: P, ...handlers: [ - H, I, R>, - H, I2, R>, - H, I3, R>, - H, I4, R>, - H, I5, R> + H, I, E2, R>, + H, I2, E3, R>, + H, I3, E4, R>, + H, I4, E5, R>, + H, I5, Temp, R> ] ): Hono, I5['in'], MergeTypedResponseData>, BasePath> // app.get(path, ...handlers[])

= any>( path: P, - ...handlers: H, I, R>[] + ...handlers: H, I, E, R>[] ): Hono, I['in'], MergeTypedResponseData>, BasePath> } @@ -235,10 +315,20 @@ export interface OnHandlerInterface< BasePath extends string = '/' > { // app.on(method, path, handler, handler) - = any, I extends Input = {}>( + < + M extends string, + P extends string, + R extends HandlerResponse = any, + I extends Input = {}, + E2 extends Env = E, + E3 extends Env = E + >( method: M, path: P, - ...handlers: [H, I, R>, H, I, R>] + ...handlers: [ + H, I, E2, R>, + H, I, E & E2, R> + ] ): Hono, I['in'], MergeTypedResponseData>, BasePath> // app.get(method, path, handler x3) @@ -248,17 +338,19 @@ export interface OnHandlerInterface< R extends HandlerResponse = any, I extends Input = {}, I2 extends Input = I, - I3 extends Input = I & I2 + I3 extends Input = I & I2, + E2 extends Env = E, + E3 extends Env = E, + E4 extends Env = E >( method: M, path: P, ...handlers: [ - H, I, R>, - H, I2, R>, - H, I3, R> + H, I, E2, R>, + H, I2, E3, R>, + H, I3, E & E2 & E3, R> ] ): Hono, I3['in'], MergeTypedResponseData>, BasePath> - // app.get(method, path, handler x4) < M extends string, @@ -267,15 +359,19 @@ export interface OnHandlerInterface< I extends Input = {}, I2 extends Input = I, I3 extends Input = I & I2, - I4 extends Input = I2 & I3 + I4 extends Input = I & I2 & I3, + E2 extends Env = E, + E3 extends Env = E, + E4 extends Env = E, + E5 extends Env = E >( method: M, path: P, ...handlers: [ - H, I, R>, - H, I2, R>, - H, I3, R>, - H, I4, R> + H, I, E2, R>, + H, I2, E3, R>, + H, I3, E4, R>, + H, I4, E & E2 & E3 & E4, R> ] ): Hono, I4['in'], MergeTypedResponseData>, BasePath> @@ -287,31 +383,36 @@ export interface OnHandlerInterface< I extends Input = {}, I2 extends Input = I, I3 extends Input = I & I2, - I4 extends Input = I2 & I3, - I5 extends Input = I3 & I4 + I4 extends Input = I & I2 & I3, + I5 extends Input = I & I2 & I3 & I4, + E2 extends Env = E, + E3 extends Env = E, + E4 extends Env = E, + E5 extends Env = E, + E6 extends Env = E >( method: M, path: P, ...handlers: [ - H, I, R>, - H, I2, R>, - H, I3, R>, - H, I4, R>, - H, I5, R> + H, I, E2, R>, + H, I2, E3, R>, + H, I3, E4, R>, + H, I4, E5, R>, + H, I5, E & E2 & E3 & E4 & E5, R> ] ): Hono, I5['in'], MergeTypedResponseData>, BasePath> = any, I extends Input = {}>( method: M, path: P, - ...handlers: H, I, R>[] + ...handlers: H, I, E, R>[] ): Hono, I['in'], MergeTypedResponseData>, BasePath> // app.on(method[], path, ...handler)

= any, I extends Input = {}>( methods: string[], path: P, - ...handlers: H, I, R>[] + ...handlers: H, I, E, R>[] ): Hono< E, S & ToSchema, I['in'], MergeTypedResponseData>, @@ -385,6 +486,7 @@ export type TypedResponse = { response: Response | Promise data: T format: 'json' // Currently, support only `json` with `c.jsonT()` + status: StatusCode } type ExtractResponseData = T extends Promise diff --git a/package.json b/package.json index 8190883ef..2a253e9e1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hono", - "version": "3.5.8", + "version": "3.6.0-rc.2", "description": "Ultrafast web framework for the Edges", "main": "dist/cjs/index.js", "type": "module", @@ -189,6 +189,11 @@ "import": "./dist/helper/adapter/index.js", "require": "./dist/cjs/helper/adapter/index.js" }, + "./factory": { + "types": "./dist/types/helper/factory/index.d.ts", + "import": "./dist/helper/factory/index.js", + "require": "./dist/cjs/helper/factory/index.js" + }, "./cloudflare-workers": { "types": "./dist/types/adapter/cloudflare-workers/index.d.ts", "import": "./dist/adapter/cloudflare-workers/index.js", @@ -322,6 +327,9 @@ "adapter": [ "./dist/types/helper/adapter/index.d.ts" ], + "factory": [ + "./dist/types/helper/factory/index.d.ts" + ], "cloudflare-workers": [ "./dist/types/adapter/cloudflare-workers" ], diff --git a/src/client/client.test.ts b/src/client/client.test.ts index 579638655..07d87d243 100644 --- a/src/client/client.test.ts +++ b/src/client/client.test.ts @@ -437,6 +437,10 @@ describe('Merge path with `app.route()`', () => { const data = await res.json() type verify = Expect> }) + it('Should work with $url', async () => { + const url = client.api.bar.$url() + expect(url.href).toBe('http://localhost/api/bar') + }) }) }) diff --git a/src/client/client.ts b/src/client/client.ts index 997e333be..7111f4154 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -115,7 +115,7 @@ export const hc = >( baseUrl: string, options?: ClientRequestOptions ) => - createProxy(async (opts) => { + createProxy((opts) => { const parts = [...opts.path] let method = '' @@ -128,6 +128,10 @@ export const hc = >( const path = parts.join('/') const url = mergePath(baseUrl, path) + if (method === 'url') { + return new URL(url) + } + const req = new ClientRequestImpl(url, method) if (method) { options ??= {} diff --git a/src/client/types.ts b/src/client/types.ts index a39f25b7d..34fb7df2e 100644 --- a/src/client/types.ts +++ b/src/client/types.ts @@ -17,6 +17,8 @@ type ClientRequest = { options?: ClientRequestOptions ) => Promise> : never +} & { + $url: () => URL } type BlankRecordToNever = T extends Record diff --git a/src/compose.test.ts b/src/compose.test.ts index 0e8903eb0..0c0b64ba1 100644 --- a/src/compose.test.ts +++ b/src/compose.test.ts @@ -85,7 +85,7 @@ describe('Handler and middlewares', () => { const c: Context = new Context(req) const mHandlerFoo = async (c: Context, next: Function) => { - c.req.headers.append('x-header-foo', 'foo') + c.req.raw.headers.append('x-header-foo', 'foo') await next() } @@ -198,7 +198,7 @@ describe('compose with Context - next() below', () => { return c.text(message) } const mHandler = async (c: Context, next: Function) => { - c.req.headers.append('x-custom', 'foo') + c.req.raw.headers.append('x-custom', 'foo') await next() } diff --git a/src/context.test.ts b/src/context.test.ts index a0171a8d4..ac68f2b02 100644 --- a/src/context.test.ts +++ b/src/context.test.ts @@ -251,6 +251,7 @@ describe('Pass a ResponseInit to respond methods', () => { const res = tRes['response'] as Response expect(res.headers.get('content-type')).toMatch(/^application\/json/) expect(await res.json()).toEqual({ foo: 'bar' }) + expect(tRes.status).toEqual(200) }) it('c.html()', async () => { @@ -260,3 +261,34 @@ describe('Pass a ResponseInit to respond methods', () => { expect(await res.text()).toBe('

foo

') }) }) + +declare module './context' { + interface ContextRenderer { + (content: string, head: { title: string }): Response + } +} + +describe('c.render', () => { + const req = new HonoRequest(new Request('http://localhost/')) + let c: Context + beforeEach(() => { + c = new Context(req) + }) + + it('Should return a Response from the default renderer', async () => { + c.header('foo', 'bar') + const res = c.render('

content

', { title: 'dummy ' }) + expect(res.headers.get('foo')).toBe('bar') + expect(await res.text()).toBe('

content

') + }) + + it('Should return a Response from the custom renderer', async () => { + c.setRenderer((content, head) => { + return c.html(`${head.title}${content}`) + }) + c.header('foo', 'bar') + const res = c.render('

content

', { title: 'title' }) + expect(res.headers.get('foo')).toBe('bar') + expect(await res.text()).toBe('title

content

') + }) +}) diff --git a/src/context.ts b/src/context.ts index 5ab11451c..f1efa78fa 100644 --- a/src/context.ts +++ b/src/context.ts @@ -14,8 +14,15 @@ export interface ExecutionContext { waitUntil(promise: Promise): void passThroughOnException(): void } + export interface ContextVariableMap {} +export interface ContextRenderer {} +interface DefaultRenderer { + (content: string): Response | Promise +} +type Renderer = ContextRenderer extends Function ? ContextRenderer : DefaultRenderer + interface Get { (key: Key): ContextVariableMap[Key] (key: Key): E['Variables'][Key] @@ -87,16 +94,17 @@ export class Context< > { req: HonoRequest env: E['Bindings'] = {} + private _var: E['Variables'] = {} finalized: boolean = false error: Error | undefined = undefined private _status: StatusCode = 200 private _exCtx: FetchEventLike | ExecutionContext | undefined // _executionCtx - private _map: Record | undefined private _h: Headers | undefined = undefined // _headers private _pH: Record | undefined = undefined // _preparedHeaders private _res: Response | undefined private _init = true + private _renderer: Renderer = (content: string) => this.html(content) private notFoundHandler: NotFoundHandler = () => new Response() constructor(req: HonoRequest, options?: ContextOptions) { @@ -143,6 +151,25 @@ export class Context< this.finalized = true } + /** + * @experimental + * `c.render()` is an experimental feature. + * The API might be changed. + */ + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // eslint-disable-next-line @typescript-eslint/no-explicit-any + render: Renderer = (...args: any[]) => this._renderer(...args) + + /** + * @experimental + * `c.setRenderer()` is an experimental feature. + * The API might be changed. + */ + setRenderer = (renderer: Renderer) => { + this._renderer = renderer + } + header = (name: string, value: string | undefined, options?: { append?: boolean }): void => { // Clear the header if (value === undefined) { @@ -187,12 +214,17 @@ export class Context< } set: Set = (key: string, value: unknown) => { - this._map ||= {} - this._map[key as string] = value + this._var ??= {} + this._var[key as string] = value } get: Get = (key: string) => { - return this._map ? this._map[key] : undefined + return this._var ? this._var[key] : undefined + } + + // c.var.propName is a read-only + get var(): Readonly { + return { ...this._var } } newResponse: NewResponse = ( @@ -309,11 +341,15 @@ export class Context< : T : never > => { + const response = + typeof arg === 'number' ? this.json(object, arg, headers) : this.json(object, arg) + return { - response: typeof arg === 'number' ? this.json(object, arg, headers) : this.json(object, arg), + response, // eslint-disable-next-line @typescript-eslint/no-explicit-any data: object as any, format: 'json', + status: response.status, } } diff --git a/src/helper/factory/index.test.ts b/src/helper/factory/index.test.ts new file mode 100644 index 000000000..5966ab4de --- /dev/null +++ b/src/helper/factory/index.test.ts @@ -0,0 +1,32 @@ +import { hc } from '../../client' +import { Hono } from '../../index' +import { middleware } from './index' + +describe('middleware', () => { + type Env = { Variables: { foo: string } } + const app = new Hono() + + const mw = (message: string) => + middleware(async (c, next) => { + c.set('foo', 'bar') + await next() + c.header('X-Message', message) + }) + + const route = app.get('/message', mw('Hello Middleware'), (c) => { + return c.text(`Hey, ${c.var.foo}`) + }) + + it('Should return the correct header and the content', async () => { + const res = await app.request('/message') + expect(res.status).toBe(200) + expect(res.headers.get('x-message')).toBe('Hello Middleware') + expect(await res.text()).toBe('Hey, bar') + }) + + it('Should provide the correct types', async () => { + const client = hc('http://localhost') + const url = client.message.$url() + expect(url.pathname).toBe('/message') + }) +}) diff --git a/src/helper/factory/index.ts b/src/helper/factory/index.ts new file mode 100644 index 000000000..71b81e4f0 --- /dev/null +++ b/src/helper/factory/index.ts @@ -0,0 +1,10 @@ +import type { Env, Input, MiddlewareHandler } from '../../types' + +/** + * @experimental + * `middleware()` is an experimental feature. + * The API might be changed. + */ +export const middleware = ( + middleware: MiddlewareHandler +) => middleware diff --git a/src/hono.test.ts b/src/hono.test.ts index 4ba256329..9813b09b5 100644 --- a/src/hono.test.ts +++ b/src/hono.test.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import { vi } from 'vitest' import type { Context } from './context' import { Hono } from './hono' @@ -8,8 +9,8 @@ import { poweredBy } from './middleware/powered-by' import { SmartRouter } from './mod' import { RegExpRouter } from './router/reg-exp-router' import { TrieRouter } from './router/trie-router' -import type { Handler, Next } from './types' -import type { Equal, Expect } from './utils/types' +import type { Handler, MiddlewareHandler, Next } from './types' +import type { Expect, Equal } from './utils/types' import { getPath } from './utils/url' // https://stackoverflow.com/a/65666402 @@ -822,7 +823,7 @@ describe('Middleware', () => { const app = new Hono() app .use('/chained/*', async (c, next) => { - c.req.headers.append('x-before', 'abc') + c.req.raw.headers.append('x-before', 'abc') await next() }) .use(async (c, next) => { @@ -849,7 +850,7 @@ describe('Middleware', () => { .use( '/multiple/*', async (c, next) => { - c.req.headers.append('x-before', 'abc') + c.req.raw.headers.append('x-before', 'abc') await next() }, async (c, next) => { @@ -934,7 +935,7 @@ describe('Middleware with app.HTTP_METHOD', () => { }) const customHeader = async (c: Context, next: Next) => { - c.req.headers.append('x-custom-foo', 'bar') + c.req.raw.headers.append('x-custom-foo', 'bar') await next() } @@ -2356,3 +2357,297 @@ describe('HEAD method', () => { expect(res.body).toBe(null) }) }) + +declare module './context' { + interface ContextRenderer { + (content: string, head: { title: string }): Response + } +} + +describe('Context render and setRenderer', () => { + const app = new Hono() + app.get('/default', (c) => { + return c.render('

content

', { title: 'dummy ' }) + }) + app.use('/page', async (c, next) => { + c.setRenderer((content, head) => { + return new Response( + `${head.title}

${content}

` + ) + }) + await next() + }) + app.get('/page', (c) => { + return c.render('page content', { + title: 'page title', + }) + }) + + it('Should return a Response from the default renderer', async () => { + const res = await app.request('/default') + expect(await res.text()).toBe('

content

') + }) + + it('Should return a Response from the custom renderer', async () => { + const res = await app.request('/page') + expect(await res.text()).toBe( + 'page title

page content

' + ) + }) +}) + +describe('c.var - with testing types', () => { + const app = new Hono<{ + Bindings: { + Token: string + } + }>() + + const mw = + (): MiddlewareHandler<{ + Variables: { + echo: (str: string) => string + } + }> => + async (c, next) => { + c.set('echo', (str) => str) + await next() + } + + const mw2 = + (): MiddlewareHandler<{ + Variables: { + echo2: (str: string) => string + } + }> => + async (c, next) => { + c.set('echo2', (str) => str) + await next() + } + + const mw3 = + (): MiddlewareHandler<{ + Variables: { + echo3: (str: string) => string + } + }> => + async (c, next) => { + c.set('echo3', (str) => str) + await next() + } + + const mw4 = + (): MiddlewareHandler<{ + Variables: { + echo4: (str: string) => string + } + }> => + async (c, next) => { + c.set('echo4', (str) => str) + await next() + } + + const mw5 = + (): MiddlewareHandler<{ + Variables: { + echo5: (str: string) => string + } + }> => + async (c, next) => { + c.set('echo5', (str) => str) + await next() + } + + app.use('/no-path/1').get(mw(), (c) => { + return c.text(c.var.echo('hello')) + }) + + app.use('/no-path/2').get(mw(), mw2(), (c) => { + return c.text(c.var.echo('hello') + c.var.echo2('hello2')) + }) + + app.use('/no-path/3').get(mw(), mw2(), mw3(), (c) => { + return c.text(c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3')) + }) + + app.use('/no-path/4').get(mw(), mw2(), mw3(), mw4(), (c) => { + return c.text( + c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3') + c.var.echo4('hello4') + ) + }) + + app.use('/no-path/5').get(mw(), mw2(), mw3(), mw4(), mw5(), (c) => { + return c.text( + // @ts-ignore + c.var.echo('hello') + + c.var.echo2('hello2') + + c.var.echo3('hello3') + + c.var.echo4('hello4') + + c.var.echo5('hello5') + ) + }) + + app.get('/path/1', mw(), (c) => { + return c.text(c.var.echo('hello')) + }) + + app.get('/path/2', mw(), mw2(), (c) => { + return c.text(c.var.echo('hello') + c.var.echo2('hello2')) + }) + + app.get('/path/3', mw(), mw2(), mw3(), (c) => { + return c.text(c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3')) + }) + + app.get('/path/4', mw(), mw2(), mw3(), mw4(), (c) => { + return c.text( + c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3') + c.var.echo4('hello4') + ) + }) + + // @ts-expect-error + app.get('/path/5', mw(), mw2(), mw3(), mw4(), mw5(), (c) => { + return c.text( + // @ts-expect-error + c.var.echo('hello') + + // @ts-expect-error + c.var.echo2('hello2') + + // @ts-expect-error + c.var.echo3('hello3') + + // @ts-expect-error + c.var.echo4('hello4') + + // @ts-expect-error + c.var.echo5('hello5') + ) + }) + + app.on('GET', '/on/1', mw(), (c) => { + return c.text(c.var.echo('hello')) + }) + + app.on('GET', '/on/2', mw(), mw2(), (c) => { + return c.text(c.var.echo('hello') + c.var.echo2('hello2')) + }) + + app.on('GET', '/on/3', mw(), mw2(), mw3(), (c) => { + return c.text(c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3')) + }) + + app.on('GET', '/on/4', mw(), mw2(), mw3(), mw4(), (c) => { + return c.text( + c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3') + c.var.echo4('hello4') + ) + }) + + // @ts-expect-error + app.on('GET', '/on/5', mw(), mw2(), mw3(), mw4(), mw5(), (c) => { + return c.text( + // @ts-expect-error + c.var.echo('hello') + + // @ts-expect-error + c.var.echo2('hello2') + + // @ts-expect-error + c.var.echo3('hello3') + + // @ts-expect-error + c.var.echo4('hello4') + + // @ts-expect-error + c.var.echo5('hello5') + ) + }) + + it('Should return the correct response - no-path', async () => { + let res = await app.request('/no-path/1') + expect(res.status).toBe(200) + expect(await res.text()).toBe('hello') + + res = await app.request('/no-path/2') + expect(res.status).toBe(200) + expect(await res.text()).toBe('hellohello2') + + res = await app.request('/no-path/3') + expect(res.status).toBe(200) + expect(await res.text()).toBe('hellohello2hello3') + + res = await app.request('/no-path/4') + expect(res.status).toBe(200) + expect(await res.text()).toBe('hellohello2hello3hello4') + + res = await app.request('/no-path/5') + expect(res.status).toBe(200) + expect(await res.text()).toBe('hellohello2hello3hello4hello5') + }) + + it('Should return the correct response - path', async () => { + let res = await app.request('/path/1') + expect(res.status).toBe(200) + expect(await res.text()).toBe('hello') + + res = await app.request('/path/2') + expect(res.status).toBe(200) + expect(await res.text()).toBe('hellohello2') + + res = await app.request('/path/3') + expect(res.status).toBe(200) + expect(await res.text()).toBe('hellohello2hello3') + + res = await app.request('/path/4') + expect(res.status).toBe(200) + expect(await res.text()).toBe('hellohello2hello3hello4') + + res = await app.request('/path/5') + expect(res.status).toBe(200) + expect(await res.text()).toBe('hellohello2hello3hello4hello5') + }) + + it('Should return the correct response - on', async () => { + let res = await app.request('/on/1') + expect(res.status).toBe(200) + expect(await res.text()).toBe('hello') + + res = await app.request('/on/2') + expect(res.status).toBe(200) + expect(await res.text()).toBe('hellohello2') + + res = await app.request('/on/3') + expect(res.status).toBe(200) + expect(await res.text()).toBe('hellohello2hello3') + + res = await app.request('/on/4') + expect(res.status).toBe(200) + expect(await res.text()).toBe('hellohello2hello3hello4') + + res = await app.request('/on/5') + expect(res.status).toBe(200) + expect(await res.text()).toBe('hellohello2hello3hello4hello5') + }) + + it('Should not throw type errors', () => { + const app = new Hono<{ + Variables: { + hello: () => string + } + }>() + + app.get(mw()) + app.get(mw(), mw2()) + app.get(mw(), mw2(), mw3()) + app.get(mw(), mw2(), mw3(), mw4()) + app.get(mw(), mw2(), mw3(), mw4(), mw5()) + + app.get('/', mw()) + app.get('/', mw(), mw2()) + app.get('/', mw(), mw2(), mw3()) + app.get('/', mw(), mw2(), mw3(), mw4()) + app.get('/', mw(), mw2(), mw3(), mw4(), mw5()) + }) + + it('Should be a read-only', () => { + expect(() => { + app.get('/path/1', mw(), (c) => { + // @ts-expect-error + c.var.echo = 'hello' + return c.text(c.var.echo('hello')) + }) + }).toThrow() + }) +}) diff --git a/src/index.ts b/src/index.ts index 916f4582d..05d19f20a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,7 +14,7 @@ export type { ToSchema, TypedResponse, } from './types' -export type { Context, ContextVariableMap } from './context' +export type { Context, ContextVariableMap, ContextRenderer } from './context' export type { HonoRequest } from './request' export type { InferRequestType, InferResponseType, ClientRequestOptions } from './client' diff --git a/src/jsx/index.test.tsx b/src/jsx/index.test.tsx index 0b38539c3..498b4a8a2 100644 --- a/src/jsx/index.test.tsx +++ b/src/jsx/index.test.tsx @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { html } from '../helper/html' import { Hono } from '../hono' +import type { FC } from './index' // eslint-disable-next-line @typescript-eslint/no-unused-vars import { jsx, memo, Fragment } from './index' @@ -271,6 +272,32 @@ describe('render to string', () => { }) }) + describe('FC', () => { + it('Should define the type correctly', () => { + const Layout: FC<{ title: string }> = (props) => { + return ( + + + {props.title} + + {props.children} + + ) + } + + const Top = ( + +

Hono

+

Hono is great

+
+ ) + + expect(Top.toString()).toBe( + 'Home page

Hono

Hono is great

' + ) + }) + }) + describe('style attribute', () => { it('should convert the object to strings', () => { const template = ( diff --git a/src/jsx/index.ts b/src/jsx/index.ts index 8a16b0664..8ac4642e1 100644 --- a/src/jsx/index.ts +++ b/src/jsx/index.ts @@ -198,7 +198,7 @@ const jsxFn = ( } } -type FC = (props: T) => HtmlEscapedString +export type FC = (props: T & { children?: Child }) => HtmlEscapedString const shallowEqual = (a: Props, b: Props): boolean => { if (a === b) { @@ -226,7 +226,7 @@ export const memo = ( ): FC => { let computed = undefined let prevProps: T | undefined = undefined - return ((props: T): HtmlEscapedString => { + return ((props: T & { children?: Child }): HtmlEscapedString => { if (prevProps && !propsAreEqual(prevProps, props)) { computed = undefined } diff --git a/src/middleware/bearer-auth/index.ts b/src/middleware/bearer-auth/index.ts index 245abd309..2664ab3a0 100644 --- a/src/middleware/bearer-auth/index.ts +++ b/src/middleware/bearer-auth/index.ts @@ -24,7 +24,7 @@ export const bearerAuth = (options: { const realm = options.realm?.replace(/"/g, '\\"') return async (c, next) => { - const headerToken = c.req.headers.get('Authorization') + const headerToken = c.req.header('Authorization') if (!headerToken) { // No Authorization header diff --git a/src/middleware/cors/index.ts b/src/middleware/cors/index.ts index 2c13cc1b8..fc87d03c4 100644 --- a/src/middleware/cors/index.ts +++ b/src/middleware/cors/index.ts @@ -36,7 +36,7 @@ export const cors = (options?: CORSOptions): MiddlewareHandler => { c.res.headers.set(key, value) } - const allowOrigin = findAllowOrigin(c.req.headers.get('origin') || '') + const allowOrigin = findAllowOrigin(c.req.header('origin') || '') if (allowOrigin) { set('Access-Control-Allow-Origin', allowOrigin) } @@ -70,7 +70,7 @@ export const cors = (options?: CORSOptions): MiddlewareHandler => { let headers = opts.allowHeaders if (!headers?.length) { - const requestHeaders = c.req.headers.get('Access-Control-Request-Headers') + const requestHeaders = c.req.header('Access-Control-Request-Headers') if (requestHeaders) { headers = requestHeaders.split(/\s*,\s*/) } diff --git a/src/middleware/etag/index.ts b/src/middleware/etag/index.ts index e3a224dcb..65d7efd99 100644 --- a/src/middleware/etag/index.ts +++ b/src/middleware/etag/index.ts @@ -30,7 +30,7 @@ export const etag = (options?: ETagOptions): MiddlewareHandler => { const weak = options?.weak ?? false return async (c, next) => { - const ifNoneMatch = c.req.headers.get('If-None-Match') + const ifNoneMatch = c.req.header('If-None-Match') ?? null await next() diff --git a/src/mod.ts b/src/mod.ts index 7f69cf639..01561bcdb 100644 --- a/src/mod.ts +++ b/src/mod.ts @@ -20,7 +20,7 @@ export type { ToSchema, TypedResponse, } from './types' -export type { Context, ContextVariableMap } from './context' +export type { Context, ContextVariableMap, ContextRenderer } from './context' export type { HonoRequest } from './request' export { Hono } export { HTTPException } from './http-exception' diff --git a/src/request.ts b/src/request.ts index c1bdcd9b1..0e1a8fe37 100644 --- a/src/request.ts +++ b/src/request.ts @@ -181,27 +181,63 @@ export class HonoRequest

{ get url() { return this.raw.url } + get method() { return this.raw.method } + + /** @deprecated + * Use `c.req.raw.headers` instead of `c.req.headers`. The `c.req.headers` will be removed in v4. + * Or you can get the header values with using `c.req.header`. + * @example + * + * app.get('/', (c) => { + * const userAgent = c.req.header('User-Agent') + * //... + * }) + */ get headers() { return this.raw.headers } + + /** @deprecated + * Use `c.req.raw.body` instead of `c.req.body`. The `c.req.body` will be removed in v4. + */ get body() { return this.raw.body } + + /** @deprecated + * Use `c.req.raw.bodyUsed` instead of `c.req.bodyUsed`. The `c.req.bodyUsed` will be removed in v4. + */ get bodyUsed() { return this.raw.bodyUsed } + + /** @deprecated + * Use `c.req.raw.integrity` instead of `c.req.integrity`. The `c.req.integrity` will be removed in v4. + */ get integrity() { return this.raw.integrity } + + /** @deprecated + * Use `c.req.raw.keepalive` instead of `c.req.keepalive`. The `c.req.keepalive` will be removed in v4. + */ get keepalive() { return this.raw.keepalive } + + /** @deprecated + * Use `c.req.raw.referrer` instead of `c.req.referrer`. The `c.req.referrer` will be removed in v4. + */ get referrer() { return this.raw.referrer } + + /** @deprecated + * Use `c.req.raw.signal` instead of `c.req.signal`. The `c.req.signal` will be removed in v4. + */ get signal() { return this.raw.signal } diff --git a/src/types.ts b/src/types.ts index fd3c18713..17f1cd7c9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,6 +3,7 @@ /* eslint-disable @typescript-eslint/ban-types */ import type { Context } from './context' import type { Hono } from './hono' +import type { StatusCode } from './utils/http-status' import type { UnionToIntersection } from './utils/types' //////////////////////////////////////// @@ -50,8 +51,9 @@ export type H< E extends Env = any, P extends string = any, I extends Input = {}, + E2 extends Env = E, R extends HandlerResponse = any -> = Handler | MiddlewareHandler +> = Handler | MiddlewareHandler export type NotFoundHandler = (c: Context) => Response | Promise export type ErrorHandler = ( @@ -73,13 +75,26 @@ export interface HandlerInterface< > { //// app.get(...handlers[]) + // app.get(handler) + < + P extends string = ExtractKey extends never ? BasePath : ExtractKey, + I extends Input = {}, + R extends HandlerResponse = any, + Temp extends Env = E + >( + handler: H + ): Hono>, BasePath> + // app.get(handler, handler) < P extends string = ExtractKey extends never ? BasePath : ExtractKey, I extends Input = {}, - R extends HandlerResponse = any + R extends HandlerResponse = any, + E2 extends Env = E, + E3 extends Env = E, + Temp extends Env = E & E2 >( - ...handlers: [H, H] + ...handlers: [H, H] ): Hono>, BasePath> // app.get(handler x 3) @@ -88,9 +103,13 @@ export interface HandlerInterface< R extends HandlerResponse = any, I extends Input = {}, I2 extends Input = I, - I3 extends Input = I & I2 + I3 extends Input = I & I2, + E2 extends Env = E, + E3 extends Env = E, + E4 extends Env = E, + Temp extends Env = E & E2 & E3 >( - ...handlers: [H, H, H] + ...handlers: [H, H, H] ): Hono>, BasePath> // app.get(handler x 4) @@ -100,9 +119,19 @@ export interface HandlerInterface< I extends Input = {}, I2 extends Input = I, I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3 + I4 extends Input = I & I2 & I3, + E2 extends Env = E, + E3 extends Env = E, + E4 extends Env = E, + E5 extends Env = E, + Temp extends Env = E & E2 & E3 & E4 >( - ...handlers: [H, H, H, H] + ...handlers: [ + H, + H, + H, + H + ] ): Hono>, BasePath> // app.get(handler x 5) @@ -113,9 +142,21 @@ export interface HandlerInterface< I2 extends Input = I, I3 extends Input = I & I2, I4 extends Input = I2 & I3, - I5 extends Input = I & I2 & I3 & I4 + I5 extends Input = I & I2 & I3 & I4, + E2 extends Env = E, + E3 extends Env = E, + E4 extends Env = E, + E5 extends Env = E, + E6 extends Env = E, + Temp extends Env = E & E2 & E3 & E4 & E5 >( - ...handlers: [H, H, H, H, H] + ...handlers: [ + H, + H, + H, + H, + H + ] ): Hono>, BasePath> // app.get(...handlers[]) @@ -124,21 +165,45 @@ export interface HandlerInterface< I extends Input = {}, R extends HandlerResponse = any >( - ...handlers: Handler[] + ...handlers: H[] ): Hono>, BasePath> + //// app.get(path) + + // app.get(path) +

= any, I extends Input = {}>(path: P): Hono< + E, + S & ToSchema, I['in'], MergeTypedResponseData>, + BasePath + > + //// app.get(path, ...handlers[]) // app.get(path, handler) -

= any, I extends Input = {}>( + < + P extends string, + R extends HandlerResponse = any, + I extends Input = {}, + Temp extends Env = E + >( path: P, - handler: H, I, R> + handler: H, I, Temp, R> ): Hono, I['in'], MergeTypedResponseData>, BasePath> // app.get(path, handler, handler) -

= any, I extends Input = {}>( + < + P extends string, + R extends HandlerResponse = any, + I extends Input = {}, + E2 extends Env = E, + E3 extends Env = E, + Temp extends Env = E & E2 + >( path: P, - ...handlers: [H, I, R>, H, I, R>] + ...handlers: [ + H, I, E2, R>, + H, I, Temp, R> + ] ): Hono, I['in'], MergeTypedResponseData>, BasePath> // app.get(path, handler x3) @@ -147,13 +212,17 @@ export interface HandlerInterface< R extends HandlerResponse = any, I extends Input = {}, I2 extends Input = I, - I3 extends Input = I & I2 + I3 extends Input = I & I2, + E2 extends Env = E, + E3 extends Env = E, + E4 extends Env = E, + Temp extends Env = E & E2 & E3 >( path: P, ...handlers: [ - H, I, R>, - H, I2, R>, - H, I3, R> + H, I, E2, R>, + H, I2, E3, R>, + H, I3, Temp, R> ] ): Hono, I3['in'], MergeTypedResponseData>, BasePath> @@ -164,14 +233,19 @@ export interface HandlerInterface< I extends Input = {}, I2 extends Input = I, I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3 + I4 extends Input = I & I2 & I3, + E2 extends Env = E, + E3 extends Env = E, + E4 extends Env = E, + E5 extends Env = E, + Temp extends Env = E & E2 & E3 & E4 >( path: P, ...handlers: [ - H, I, R>, - H, I2, R>, - H, I3, R>, - H, I4, R> + H, I, E2, R>, + H, I2, E3, R>, + H, I3, E4, R>, + H, I4, Temp, R> ] ): Hono, I4['in'], MergeTypedResponseData>, BasePath> @@ -183,22 +257,28 @@ export interface HandlerInterface< I2 extends Input = I, I3 extends Input = I & I2, I4 extends Input = I2 & I3, - I5 extends Input = I & I2 & I3 & I4 + I5 extends Input = I & I2 & I3 & I4, + E2 extends Env = E, + E3 extends Env = E, + E4 extends Env = E, + E5 extends Env = E, + E6 extends Env = E, + Temp extends Env = E & E2 & E3 & E4 & E5 >( path: P, ...handlers: [ - H, I, R>, - H, I2, R>, - H, I3, R>, - H, I4, R>, - H, I5, R> + H, I, E2, R>, + H, I2, E3, R>, + H, I3, E4, R>, + H, I4, E5, R>, + H, I5, Temp, R> ] ): Hono, I5['in'], MergeTypedResponseData>, BasePath> // app.get(path, ...handlers[])

= any>( path: P, - ...handlers: H, I, R>[] + ...handlers: H, I, E, R>[] ): Hono, I['in'], MergeTypedResponseData>, BasePath> } @@ -235,10 +315,20 @@ export interface OnHandlerInterface< BasePath extends string = '/' > { // app.on(method, path, handler, handler) - = any, I extends Input = {}>( + < + M extends string, + P extends string, + R extends HandlerResponse = any, + I extends Input = {}, + E2 extends Env = E, + E3 extends Env = E + >( method: M, path: P, - ...handlers: [H, I, R>, H, I, R>] + ...handlers: [ + H, I, E2, R>, + H, I, E & E2, R> + ] ): Hono, I['in'], MergeTypedResponseData>, BasePath> // app.get(method, path, handler x3) @@ -248,17 +338,19 @@ export interface OnHandlerInterface< R extends HandlerResponse = any, I extends Input = {}, I2 extends Input = I, - I3 extends Input = I & I2 + I3 extends Input = I & I2, + E2 extends Env = E, + E3 extends Env = E, + E4 extends Env = E >( method: M, path: P, ...handlers: [ - H, I, R>, - H, I2, R>, - H, I3, R> + H, I, E2, R>, + H, I2, E3, R>, + H, I3, E & E2 & E3, R> ] ): Hono, I3['in'], MergeTypedResponseData>, BasePath> - // app.get(method, path, handler x4) < M extends string, @@ -267,15 +359,19 @@ export interface OnHandlerInterface< I extends Input = {}, I2 extends Input = I, I3 extends Input = I & I2, - I4 extends Input = I2 & I3 + I4 extends Input = I & I2 & I3, + E2 extends Env = E, + E3 extends Env = E, + E4 extends Env = E, + E5 extends Env = E >( method: M, path: P, ...handlers: [ - H, I, R>, - H, I2, R>, - H, I3, R>, - H, I4, R> + H, I, E2, R>, + H, I2, E3, R>, + H, I3, E4, R>, + H, I4, E & E2 & E3 & E4, R> ] ): Hono, I4['in'], MergeTypedResponseData>, BasePath> @@ -287,31 +383,36 @@ export interface OnHandlerInterface< I extends Input = {}, I2 extends Input = I, I3 extends Input = I & I2, - I4 extends Input = I2 & I3, - I5 extends Input = I3 & I4 + I4 extends Input = I & I2 & I3, + I5 extends Input = I & I2 & I3 & I4, + E2 extends Env = E, + E3 extends Env = E, + E4 extends Env = E, + E5 extends Env = E, + E6 extends Env = E >( method: M, path: P, ...handlers: [ - H, I, R>, - H, I2, R>, - H, I3, R>, - H, I4, R>, - H, I5, R> + H, I, E2, R>, + H, I2, E3, R>, + H, I3, E4, R>, + H, I4, E5, R>, + H, I5, E & E2 & E3 & E4 & E5, R> ] ): Hono, I5['in'], MergeTypedResponseData>, BasePath> = any, I extends Input = {}>( method: M, path: P, - ...handlers: H, I, R>[] + ...handlers: H, I, E, R>[] ): Hono, I['in'], MergeTypedResponseData>, BasePath> // app.on(method[], path, ...handler)

= any, I extends Input = {}>( methods: string[], path: P, - ...handlers: H, I, R>[] + ...handlers: H, I, E, R>[] ): Hono< E, S & ToSchema, I['in'], MergeTypedResponseData>, @@ -385,6 +486,7 @@ export type TypedResponse = { response: Response | Promise data: T format: 'json' // Currently, support only `json` with `c.jsonT()` + status: StatusCode } type ExtractResponseData = T extends Promise