0
0
mirror of https://github.com/honojs/hono.git synced 2024-11-29 17:46:30 +01:00
hono/deno_dist/hono.ts

275 lines
7.5 KiB
TypeScript
Raw Normal View History

2022-07-02 08:09:45 +02:00
import { compose } from './compose.ts'
import { Context } from './context.ts'
import type { ExecutionContext } from './context.ts'
2022-07-02 08:09:45 +02:00
import type { Router } from './router.ts'
import { METHOD_NAME_ALL, METHOD_NAME_ALL_LOWERCASE, METHODS } from './router.ts'
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'
import type {
TypeResponse,
HandlerInterface,
ToAppType,
Handler,
ErrorHandler,
NotFoundHandler,
Environment,
Route,
} from './types.ts'
2022-07-02 08:09:45 +02:00
import { getPathFromURL, mergePath } from './utils/url.ts'
type Methods = typeof METHODS[number] | typeof METHOD_NAME_ALL_LOWERCASE
2022-07-02 08:09:45 +02:00
interface RouterRoute {
path: string
method: string
handler: Handler
}
2022-07-02 08:09:45 +02:00
function defineDynamicClass(): {
new <
E extends Partial<Environment> = {},
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_M extends string = string,
P extends string = string,
I = {},
O = {}
>(): {
[M in Methods]: HandlerInterface<E, M, P, I, O>
2022-07-02 08:09:45 +02:00
}
} {
return class {} as never
2022-07-02 08:09:45 +02:00
}
export class Hono<
E extends Partial<Environment> = {},
R extends Route = Route,
I = {},
O = {}
> extends defineDynamicClass()<E, R['method'], R['path'], I, O> {
readonly router: Router<Handler> = new SmartRouter({
routers: [new StaticRouter(), new RegExpRouter(), new TrieRouter()],
})
2022-07-02 08:09:45 +02:00
readonly strict: boolean = true // strict routing - default is true
private _tempPath: string = ''
private path: string = '/'
routes: RouterRoute[] = []
2022-07-02 08:09:45 +02:00
constructor(init: Partial<Pick<Hono, 'router' | 'strict'>> = {}) {
super()
const allMethods = [...METHODS, METHOD_NAME_ALL_LOWERCASE]
2022-07-02 08:09:45 +02:00
allMethods.map((method) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this[method] = (args1: string | Handler, ...args: Handler[]) => {
2022-07-02 08:09:45 +02:00
if (typeof args1 === 'string') {
this.path = args1
} else {
this.addRoute(method, this.path, args1)
2022-07-02 08:09:45 +02:00
}
args.map((handler) => {
if (typeof handler !== 'string') {
this.addRoute(method, this.path, handler)
2022-07-02 08:09:45 +02:00
}
})
return this
2022-07-02 08:09:45 +02:00
}
})
Object.assign(this, init)
}
private notFoundHandler: NotFoundHandler<E> = (c: Context<E>) => {
return c.text('404 Not Found', 404)
2022-07-02 08:09:45 +02:00
}
private errorHandler: ErrorHandler<E> = (err: Error, c: Context<E>) => {
2022-12-12 14:10:14 +01:00
console.trace(err)
2022-07-02 08:09:45 +02:00
const message = 'Internal Server Error'
return c.text(message, 500)
}
2023-01-10 13:24:21 +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) => {
this.addRoute(r.method, r.path, r.handler)
2022-07-02 08:09:45 +02:00
})
this._tempPath = ''
}
return this
}
use(...middleware: Handler<E>[]): Hono<E, { method: 'all'; path: string }, I, O>
use<Path extends string, E2 extends Partial<Environment> = E>(
arg1: Path,
...middleware: Handler<E2>[]
): Hono<E, { method: 'all'; path: Path }, I, O>
use(arg1: string | Handler<E>, ...handlers: Handler<E>[]) {
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
}
on<Method extends string, Path extends string>(
method: Method,
path: Path,
...handlers: Handler<E, { method: Method; path: Path }>[]
): Hono<E, { method: Method; path: Path }, I, O>
on(method: string, path: string, ...handlers: Handler<E>[]) {
if (!method) return this
this.path = path
handlers.map((handler) => {
this.addRoute(method.toUpperCase(), this.path, handler)
})
return this
}
build = (): ToAppType<typeof this> => {
const app = {} as typeof this
type AppType = ToAppType<typeof app>
return {} as AppType
}
onError(handler: ErrorHandler<E>) {
this.errorHandler = handler
2022-07-02 08:09:45 +02:00
return this
}
notFound(handler: NotFoundHandler<E>) {
this.notFoundHandler = handler
2022-07-02 08:09:45 +02:00
return this
}
showRoutes() {
const length = 8
this.routes.map((route) => {
console.log(
`\x1b[32m${route.method}\x1b[0m ${' '.repeat(length - route.method.length)} ${route.path}`
)
})
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private addRoute(method: string, path: string, handler: Handler<any, any>) {
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)
const r: RouterRoute = { 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)
}
private handleError(err: unknown, c: Context<E>) {
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,
env?: E['Bindings']
): Response | Promise<Response> {
2023-01-10 13:24:21 +01:00
const [path, queryIndex] = getPathFromURL(request.url, this.strict)
2022-07-02 08:09:45 +02:00
const method = request.method
const result = this.matchRoute(method, path)
const paramData = result?.params
2022-07-02 08:09:45 +02:00
const c = new Context(request, {
env,
executionCtx: eventOrExecutionCtx,
notFoundHandler: this.notFoundHandler,
paramData,
2023-01-10 13:24:21 +01:00
queryIndex,
})
2022-07-02 08:09:45 +02:00
// Do not `compose` if it has only one handler
if (result && result.handlers.length === 1) {
const handler = result.handlers[0] as unknown as Handler<E>
let res: ReturnType<Handler>
try {
res = handler(c, async () => {})
if (!res) {
return this.notFoundHandler(c)
}
} catch (err) {
return this.handleError(err, c)
}
if ('response' in res) {
res = res.response
}
if (res instanceof Response) {
return res
}
return (async () => {
let awaited: Response | TypeResponse | void
try {
awaited = await res
if (awaited !== undefined && 'response' in awaited) {
awaited = awaited['response'] as Response
}
if (!awaited) {
return this.notFoundHandler(c)
}
} catch (err) {
return this.handleError(err, c)
}
return awaited
})()
}
const handlers = result ? result.handlers : [this.notFoundHandler]
const composed = compose<Context<E>, E>(handlers, this.notFoundHandler, this.errorHandler)
2022-07-02 08:09:45 +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) {
return this.handleError(err, c)
}
})()
2022-07-02 08:09:45 +02:00
}
handleEvent = (event: FetchEvent) => {
return this.dispatch(event.request, event)
2022-07-02 08:09:45 +02:00
}
fetch = (request: Request, Environment?: E['Bindings'], executionCtx?: ExecutionContext) => {
return this.dispatch(request, executionCtx, Environment)
2022-07-02 08:09:45 +02: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)
return await this.fetch(req)
2022-07-02 08:09:45 +02:00
}
}