2022-07-02 08:09:45 +02:00
|
|
|
import { compose } from './compose.ts'
|
2022-11-03 07:53:41 +01:00
|
|
|
import { Context } from './context.ts'
|
2022-07-02 08:09:45 +02:00
|
|
|
import { extendRequestPrototype } from './request.ts'
|
|
|
|
import type { Router } from './router.ts'
|
2022-10-31 16:07:56 +01:00
|
|
|
import { METHOD_NAME_ALL, METHOD_NAME_ALL_LOWERCASE, METHODS } from './router.ts'
|
2022-09-12 13:49:18 +02:00
|
|
|
import { RegExpRouter } from './router/reg-exp-router/index.ts'
|
|
|
|
import { SmartRouter } from './router/smart-router/index.ts'
|
|
|
|
import { StaticRouter } from './router/static-router/index.ts'
|
|
|
|
import { TrieRouter } from './router/trie-router/index.ts'
|
2022-10-31 16:07:56 +01:00
|
|
|
import type { Handler, Environment, ParamKeys, ErrorHandler, NotFoundHandler } from './types.ts'
|
2022-07-02 08:09:45 +02:00
|
|
|
import { getPathFromURL, mergePath } from './utils/url.ts'
|
2022-10-23 01:10:00 +02:00
|
|
|
import type { Schema } from './validator/schema.ts'
|
2022-07-02 08:09:45 +02:00
|
|
|
|
2022-10-23 01:10:00 +02:00
|
|
|
interface HandlerInterface<
|
|
|
|
P extends string,
|
|
|
|
E extends Partial<Environment>,
|
|
|
|
S extends Partial<Schema>,
|
|
|
|
U = Hono<E, P, S>
|
|
|
|
> {
|
2022-08-31 11:01:33 +02:00
|
|
|
// app.get(handler...)
|
2022-10-23 01:10:00 +02:00
|
|
|
<Path extends string, Data extends Schema>(
|
2022-09-26 14:18:48 +02:00
|
|
|
...handlers: Handler<ParamKeys<Path> extends never ? string : ParamKeys<Path>, E, Data>[]
|
2022-07-02 08:09:45 +02:00
|
|
|
): U
|
2022-10-23 01:10:00 +02:00
|
|
|
(...handlers: Handler<string, E, S>[]): U
|
|
|
|
|
2022-08-31 11:01:33 +02:00
|
|
|
// app.get('/', handler, handler...)
|
2022-10-23 01:10:00 +02:00
|
|
|
<Path extends string, Data extends Partial<Schema> = Schema>(
|
2022-08-31 11:01:33 +02:00
|
|
|
path: Path,
|
2022-09-26 14:18:48 +02:00
|
|
|
...handlers: Handler<ParamKeys<Path> extends never ? string : ParamKeys<Path>, E, Data>[]
|
2022-07-02 08:09:45 +02:00
|
|
|
): U
|
2022-10-23 01:10:00 +02:00
|
|
|
<Path extends string, Data extends Schema>(path: Path, ...handlers: Handler<string, E, Data>[]): U
|
|
|
|
(path: string, ...handlers: Handler<string, E, S>[]): U
|
2022-07-02 08:09:45 +02:00
|
|
|
}
|
|
|
|
|
2022-09-12 13:49:18 +02:00
|
|
|
type Methods = typeof METHODS[number] | typeof METHOD_NAME_ALL_LOWERCASE
|
2022-07-02 08:09:45 +02:00
|
|
|
|
|
|
|
function defineDynamicClass(): {
|
2022-10-23 01:10:00 +02:00
|
|
|
new <
|
|
|
|
E extends Partial<Environment> = Environment,
|
|
|
|
P extends string = string,
|
|
|
|
S extends Partial<Schema> = Schema,
|
|
|
|
U = Hono<E, P, S>
|
|
|
|
>(): {
|
|
|
|
[K in Methods]: HandlerInterface<P, E, S, U>
|
2022-07-02 08:09:45 +02:00
|
|
|
}
|
|
|
|
} {
|
2022-10-23 01:10:00 +02:00
|
|
|
return class {} as never
|
2022-07-02 08:09:45 +02:00
|
|
|
}
|
|
|
|
|
2022-09-20 03:11:34 +02:00
|
|
|
interface Route<
|
2022-10-23 01:10:00 +02:00
|
|
|
P extends string = string,
|
2022-09-20 03:11:34 +02:00
|
|
|
E extends Partial<Environment> = Environment,
|
2022-10-23 01:10:00 +02:00
|
|
|
S extends Partial<Schema> = Schema
|
2022-09-20 03:11:34 +02:00
|
|
|
> {
|
2022-07-02 08:09:45 +02:00
|
|
|
path: string
|
|
|
|
method: string
|
2022-10-23 01:10:00 +02:00
|
|
|
handler: Handler<P, E, S>
|
2022-07-02 08:09:45 +02:00
|
|
|
}
|
|
|
|
|
2022-08-23 13:53:25 +02:00
|
|
|
export class Hono<
|
2022-08-28 11:16:51 +02:00
|
|
|
E extends Partial<Environment> = Environment,
|
2022-09-20 03:11:34 +02:00
|
|
|
P extends string = '/',
|
2022-10-23 01:10:00 +02:00
|
|
|
S extends Partial<Schema> = Schema
|
|
|
|
> extends defineDynamicClass()<E, P, S, Hono<E, P, S>> {
|
|
|
|
readonly router: Router<Handler<P, E, S>> = new SmartRouter({
|
2022-09-14 01:01:14 +02:00
|
|
|
routers: [new StaticRouter(), new RegExpRouter(), new TrieRouter()],
|
2022-09-12 13:49:18 +02:00
|
|
|
})
|
2022-07-02 08:09:45 +02:00
|
|
|
readonly strict: boolean = true // strict routing - default is true
|
|
|
|
private _tempPath: string = ''
|
|
|
|
private path: string = '/'
|
|
|
|
|
2022-10-23 01:10:00 +02:00
|
|
|
routes: Route<P, E, S>[] = []
|
2022-07-02 08:09:45 +02:00
|
|
|
|
|
|
|
constructor(init: Partial<Pick<Hono, 'router' | 'strict'>> = {}) {
|
|
|
|
super()
|
|
|
|
|
|
|
|
extendRequestPrototype()
|
|
|
|
|
2022-09-12 13:49:18 +02:00
|
|
|
const allMethods = [...METHODS, METHOD_NAME_ALL_LOWERCASE]
|
2022-07-02 08:09:45 +02:00
|
|
|
allMethods.map((method) => {
|
2022-10-23 01:10:00 +02:00
|
|
|
this[method] = <Path extends string, Env extends Environment, Data extends Schema>(
|
|
|
|
args1: Path | Handler<ParamKeys<Path>, Env, Data>,
|
|
|
|
...args: [Handler<ParamKeys<Path>, Env, Data>]
|
2022-08-27 16:57:33 +02:00
|
|
|
): this => {
|
2022-07-02 08:09:45 +02:00
|
|
|
if (typeof args1 === 'string') {
|
|
|
|
this.path = args1
|
|
|
|
} else {
|
2022-10-23 01:10:00 +02:00
|
|
|
this.addRoute(method, this.path, args1 as unknown as Handler<P, E, S>)
|
2022-07-02 08:09:45 +02:00
|
|
|
}
|
|
|
|
args.map((handler) => {
|
|
|
|
if (typeof handler !== 'string') {
|
2022-10-23 01:10:00 +02:00
|
|
|
this.addRoute(method, this.path, handler as unknown as Handler<P, E, S>)
|
2022-07-02 08:09:45 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
Object.assign(this, init)
|
|
|
|
}
|
|
|
|
|
2022-11-03 07:53:41 +01:00
|
|
|
private notFoundHandler: NotFoundHandler<E> = (c: Context<string, E>) => {
|
2022-09-21 16:05:26 +02:00
|
|
|
return c.text('404 Not Found', 404)
|
2022-07-02 08:09:45 +02:00
|
|
|
}
|
|
|
|
|
2022-11-03 07:53:41 +01:00
|
|
|
private errorHandler: ErrorHandler<E> = (err: Error, c: Context<string, E>) => {
|
2022-09-27 14:43:55 +02:00
|
|
|
console.trace(err.message)
|
2022-07-02 08:09:45 +02:00
|
|
|
const message = 'Internal Server Error'
|
|
|
|
return c.text(message, 500)
|
|
|
|
}
|
|
|
|
|
2022-11-03 07:53:41 +01:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
|
route(path: string, app?: Hono<any>) {
|
2022-07-02 08:09:45 +02:00
|
|
|
this._tempPath = path
|
|
|
|
if (app) {
|
|
|
|
app.routes.map((r) => {
|
2022-11-11 07:23:05 +01:00
|
|
|
this.addRoute(r.method, r.path, r.handler as unknown as Handler<P, E, S>)
|
2022-07-02 08:09:45 +02:00
|
|
|
})
|
|
|
|
this._tempPath = ''
|
|
|
|
}
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
2022-10-23 01:10:00 +02:00
|
|
|
use<Path extends string = string, Data extends Partial<Schema> = Schema>(
|
2022-09-26 14:18:48 +02:00
|
|
|
...middleware: Handler<Path, E, Data>[]
|
2022-11-19 07:46:09 +01:00
|
|
|
): Hono<E, P, S>
|
2022-10-23 01:10:00 +02:00
|
|
|
use<Path extends string = string, Data extends Partial<Schema> = Schema>(
|
2022-09-26 14:18:48 +02:00
|
|
|
arg1: string,
|
|
|
|
...middleware: Handler<Path, E, Data>[]
|
2022-11-19 07:46:09 +01:00
|
|
|
): Hono<E, P, S>
|
2022-10-23 01:10:00 +02:00
|
|
|
use(arg1: string | Handler<P, E, S>, ...handlers: Handler<P, E, S>[]) {
|
2022-07-02 08:09:45 +02:00
|
|
|
if (typeof arg1 === 'string') {
|
|
|
|
this.path = arg1
|
|
|
|
} else {
|
|
|
|
handlers.unshift(arg1)
|
|
|
|
}
|
|
|
|
handlers.map((handler) => {
|
|
|
|
this.addRoute(METHOD_NAME_ALL, this.path, handler)
|
|
|
|
})
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
2022-11-07 19:17:34 +01:00
|
|
|
on(method: string, path: string, ...handlers: Handler<P, E, S>[]) {
|
|
|
|
if (!method) return this
|
|
|
|
this.path = path
|
|
|
|
handlers.map((handler) => {
|
|
|
|
this.addRoute(method.toUpperCase(), this.path, handler)
|
|
|
|
})
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
2022-11-03 07:53:41 +01:00
|
|
|
onError(handler: ErrorHandler<E>) {
|
2022-08-27 16:57:33 +02:00
|
|
|
this.errorHandler = handler
|
2022-07-02 08:09:45 +02:00
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
2022-11-03 07:53:41 +01:00
|
|
|
notFound(handler: NotFoundHandler<E>) {
|
2022-08-23 13:53:25 +02:00
|
|
|
this.notFoundHandler = handler
|
2022-07-02 08:09:45 +02:00
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
2022-11-05 11:04:01 +01:00
|
|
|
showRoutes() {
|
|
|
|
const length = 8
|
|
|
|
this.routes.map((route) => {
|
|
|
|
console.log(
|
|
|
|
`\x1b[32m${route.method}\x1b[0m ${' '.repeat(length - route.method.length)} ${route.path}`
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-10-31 16:07:56 +01:00
|
|
|
private addRoute(method: string, path: string, handler: Handler<P, E, S>) {
|
2022-07-02 08:09:45 +02:00
|
|
|
method = method.toUpperCase()
|
|
|
|
if (this._tempPath) {
|
|
|
|
path = mergePath(this._tempPath, path)
|
|
|
|
}
|
|
|
|
this.router.add(method, path, handler)
|
2022-10-23 01:10:00 +02:00
|
|
|
const r: Route<P, E, S> = { path: path, method: method, handler: handler }
|
2022-07-02 08:09:45 +02:00
|
|
|
this.routes.push(r)
|
|
|
|
}
|
|
|
|
|
|
|
|
private matchRoute(method: string, path: string) {
|
|
|
|
return this.router.match(method, path)
|
|
|
|
}
|
|
|
|
|
2022-11-11 07:23:05 +01:00
|
|
|
private handleError(err: unknown, c: Context<string, E>) {
|
2022-09-26 14:12:53 +02:00
|
|
|
if (err instanceof Error) {
|
|
|
|
return this.errorHandler(err, c)
|
|
|
|
}
|
|
|
|
throw err
|
|
|
|
}
|
|
|
|
|
|
|
|
private dispatch(
|
2022-07-02 08:09:45 +02:00
|
|
|
request: Request,
|
|
|
|
eventOrExecutionCtx?: ExecutionContext | FetchEvent,
|
2022-08-23 13:53:25 +02:00
|
|
|
env?: E['Bindings']
|
2022-09-26 14:12:53 +02:00
|
|
|
) {
|
2022-07-02 08:09:45 +02:00
|
|
|
const path = getPathFromURL(request.url, this.strict)
|
|
|
|
const method = request.method
|
|
|
|
|
|
|
|
const result = this.matchRoute(method, path)
|
|
|
|
request.paramData = result?.params
|
|
|
|
|
2022-11-03 07:53:41 +01:00
|
|
|
const c = new Context<string, E, S>(request, env, eventOrExecutionCtx, this.notFoundHandler)
|
2022-07-02 08:09:45 +02:00
|
|
|
|
2022-09-01 15:39:50 +02:00
|
|
|
// Do not `compose` if it has only one handler
|
|
|
|
if (result && result.handlers.length === 1) {
|
|
|
|
const handler = result.handlers[0]
|
2022-10-23 01:10:00 +02:00
|
|
|
let res: ReturnType<Handler<P>>
|
2022-09-26 14:12:53 +02:00
|
|
|
|
2022-09-01 15:39:50 +02:00
|
|
|
try {
|
2022-09-26 14:12:53 +02:00
|
|
|
res = handler(c, async () => {})
|
2022-11-11 07:23:05 +01:00
|
|
|
if (!res) return this.notFoundHandler(c as Context)
|
2022-09-02 14:13:47 +02:00
|
|
|
} catch (err) {
|
2022-11-11 07:23:05 +01:00
|
|
|
return this.handleError(err, c as Context)
|
2022-09-02 14:13:47 +02:00
|
|
|
}
|
2022-09-26 14:12:53 +02:00
|
|
|
|
|
|
|
if (res instanceof Response) return res
|
|
|
|
|
|
|
|
return (async () => {
|
|
|
|
let awaited: Response | undefined | void
|
|
|
|
try {
|
|
|
|
awaited = await res
|
2022-11-05 11:02:19 +01:00
|
|
|
if (!awaited) {
|
2022-11-11 07:23:05 +01:00
|
|
|
return this.notFoundHandler(c as Context)
|
2022-11-05 11:02:19 +01:00
|
|
|
}
|
2022-09-26 14:12:53 +02:00
|
|
|
} catch (err) {
|
2022-11-11 07:23:05 +01:00
|
|
|
return this.handleError(err, c as Context)
|
2022-09-26 14:12:53 +02:00
|
|
|
}
|
|
|
|
return awaited
|
|
|
|
})()
|
2022-09-01 15:39:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const handlers = result ? result.handlers : [this.notFoundHandler]
|
2022-11-03 07:53:41 +01:00
|
|
|
const composed = compose<Context<P, E, S>, E>(handlers, this.notFoundHandler, this.errorHandler)
|
2022-07-02 08:09:45 +02:00
|
|
|
|
2022-09-26 14:12:53 +02:00
|
|
|
return (async () => {
|
|
|
|
try {
|
|
|
|
const tmp = composed(c)
|
|
|
|
const context = tmp instanceof Promise ? await tmp : tmp
|
|
|
|
if (!context.finalized) {
|
|
|
|
throw new Error(
|
|
|
|
'Context is not finalized. You may forget returning Response object or `await next()`'
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return context.res
|
|
|
|
} catch (err) {
|
2022-11-11 07:23:05 +01:00
|
|
|
return this.handleError(err, c as Context)
|
2022-09-26 14:12:53 +02:00
|
|
|
}
|
|
|
|
})()
|
2022-07-02 08:09:45 +02:00
|
|
|
}
|
|
|
|
|
2022-09-26 14:12:53 +02:00
|
|
|
handleEvent = (event: FetchEvent) => {
|
2022-09-30 04:24:23 +02:00
|
|
|
return this.dispatch(event.request, event)
|
2022-07-02 08:09:45 +02:00
|
|
|
}
|
|
|
|
|
2022-08-23 13:53:25 +02:00
|
|
|
fetch = (request: Request, Environment?: E['Bindings'], executionCtx?: ExecutionContext) => {
|
|
|
|
return this.dispatch(request, executionCtx, Environment)
|
2022-07-02 08:09:45 +02:00
|
|
|
}
|
|
|
|
|
2022-11-03 07:39:25 +01:00
|
|
|
request = async (input: Request | string, requestInit?: RequestInit) => {
|
2022-07-02 08:09:45 +02:00
|
|
|
const req = input instanceof Request ? input : new Request(input, requestInit)
|
2022-09-26 14:12:53 +02:00
|
|
|
return await this.fetch(req)
|
2022-07-02 08:09:45 +02:00
|
|
|
}
|
|
|
|
}
|