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

228 lines
6.4 KiB
TypeScript
Raw Normal View History

import type { Environment, NotFoundHandler, ContextVariableMap, Bindings } from './types.ts'
2022-07-10 17:17:29 +02:00
import type { CookieOptions } from './utils/cookie.ts'
import { serialize } from './utils/cookie.ts'
2022-07-02 08:09:45 +02:00
import type { StatusCode } from './utils/http-status.ts'
import type { Schema, SchemaToProp } from './validator/schema.ts'
2022-07-02 08:09:45 +02:00
type HeaderField = [string, string]
type Headers = Record<string, string | string[]>
type Runtime = 'node' | 'deno' | 'bun' | 'cloudflare' | 'fastly' | 'vercel' | 'other'
2022-07-02 08:09:45 +02:00
export type Data = string | ArrayBuffer | ReadableStream
export class Context<
P extends string = string,
E extends Partial<Environment> = Environment,
2022-11-01 09:42:25 +01:00
S extends Partial<Schema> | unknown = Schema
> {
2022-11-01 09:42:25 +01:00
req: Request<P, S extends Schema ? SchemaToProp<S> : any>
env: E['Bindings']
finalized: boolean
error: Error | undefined = undefined
_status: StatusCode = 200
2022-07-17 11:11:09 +02:00
private _executionCtx: FetchEvent | ExecutionContext | undefined
2022-07-02 08:09:45 +02:00
private _pretty: boolean = false
private _prettySpace: number = 2
private _map: Record<string, unknown> | undefined
private _headers: Record<string, string[]> | undefined
2022-07-02 08:09:45 +02:00
private _res: Response | undefined
private notFoundHandler: NotFoundHandler<E>
2022-07-02 08:09:45 +02:00
constructor(
req: Request<P>,
env: E['Bindings'] = {},
2022-07-17 11:11:09 +02:00
executionCtx: FetchEvent | ExecutionContext | undefined = undefined,
notFoundHandler: NotFoundHandler<E> = () => new Response()
2022-07-02 08:09:45 +02:00
) {
2022-07-17 11:11:09 +02:00
this._executionCtx = executionCtx
2022-11-01 09:42:25 +01:00
this.req = req as Request<P, S extends Schema ? SchemaToProp<S> : any>
2022-08-28 11:16:51 +02:00
this.env = env || ({} as Bindings)
2022-07-02 08:09:45 +02:00
2022-07-17 11:11:09 +02:00
this.notFoundHandler = notFoundHandler
this.finalized = false
}
get event(): FetchEvent {
if (this._executionCtx instanceof FetchEvent) {
return this._executionCtx
2022-07-02 08:09:45 +02:00
} else {
2022-07-17 11:11:09 +02:00
throw Error('This context has no FetchEvent')
2022-07-02 08:09:45 +02:00
}
2022-07-17 11:11:09 +02:00
}
2022-07-02 08:09:45 +02:00
2022-07-17 11:11:09 +02:00
get executionCtx(): ExecutionContext {
if (this._executionCtx) {
return this._executionCtx
} else {
throw Error('This context has no ExecutionContext')
}
2022-07-02 08:09:45 +02:00
}
get res(): Response {
return (this._res ||= new Response('404 Not Found', { status: 404 }))
2022-07-02 08:09:45 +02:00
}
set res(_res: Response) {
this._res = _res
this.finalized = true
}
header(name: string, value: string, options?: { append?: boolean }): void {
2022-07-02 08:09:45 +02:00
this._headers ||= {}
const key = name.toLowerCase()
let shouldAppend = false
if (options && options.append) {
const vAlreadySet = this._headers[key]
if (vAlreadySet && vAlreadySet.length) {
shouldAppend = true
}
}
if (shouldAppend) {
this._headers[key].push(value)
} else {
this._headers[key] = [value]
}
2022-07-02 08:09:45 +02:00
if (this.finalized) {
if (shouldAppend) {
this.res.headers.append(name, value)
} else {
this.res.headers.set(name, value)
}
2022-07-02 08:09:45 +02:00
}
}
status(status: StatusCode): void {
this._status = status
}
set<Key extends keyof ContextVariableMap>(key: Key, value: ContextVariableMap[Key]): void
set<Key extends keyof E['Variables']>(key: Key, value: E['Variables'][Key]): void
set(key: string, value: unknown): void
set(key: string, value: unknown): void {
2022-07-02 08:09:45 +02:00
this._map ||= {}
this._map[key] = value
}
get<Key extends keyof ContextVariableMap>(key: Key): ContextVariableMap[Key]
get<Key extends keyof E['Variables']>(key: Key): E['Variables'][Key]
get<T>(key: string): T
2022-07-02 08:09:45 +02:00
get(key: string) {
if (!this._map) {
return undefined
}
return this._map[key]
}
pretty(prettyJSON: boolean, space: number = 2): void {
this._pretty = prettyJSON
this._prettySpace = space
}
newResponse(data: Data | null, status: StatusCode, headers: Headers = {}): Response {
return new Response(data, {
status: status || this._status || 200,
headers: this._finalizeHeaders(headers),
})
}
private _finalizeHeaders(incomingHeaders: Headers): HeaderField[] {
const finalizedHeaders: HeaderField[] = []
const headersKv = this._headers || {}
// If Response is already set
2022-07-02 08:09:45 +02:00
if (this._res) {
this._res.headers.forEach((v, k) => {
headersKv[k] = [v]
2022-07-02 08:09:45 +02:00
})
}
for (const key of Object.keys(incomingHeaders)) {
const value = incomingHeaders[key]
if (typeof value === 'string') {
finalizedHeaders.push([key, value])
} else {
for (const v of value) {
finalizedHeaders.push([key, v])
}
}
delete headersKv[key]
}
for (const key of Object.keys(headersKv)) {
for (const value of headersKv[key]) {
const kv: HeaderField = [key, value]
finalizedHeaders.push(kv)
}
}
return finalizedHeaders
2022-07-02 08:09:45 +02:00
}
body(data: Data | null, status: StatusCode = this._status, headers: Headers = {}): Response {
return this.newResponse(data, status, headers)
}
text(text: string, status: StatusCode = this._status, headers: Headers = {}): Response {
headers['content-type'] = 'text/plain; charset=UTF-8'
2022-07-02 08:09:45 +02:00
return this.body(text, status, headers)
}
json<T>(object: T, status: StatusCode = this._status, headers: Headers = {}): Response {
const body = this._pretty
? JSON.stringify(object, null, this._prettySpace)
: JSON.stringify(object)
headers['content-type'] = 'application/json; charset=UTF-8'
2022-07-02 08:09:45 +02:00
return this.body(body, status, headers)
}
html(html: string, status: StatusCode = this._status, headers: Headers = {}): Response {
headers['content-type'] = 'text/html; charset=UTF-8'
2022-07-02 08:09:45 +02:00
return this.body(html, status, headers)
}
redirect(location: string, status: StatusCode = 302): Response {
return this.newResponse(null, status, {
Location: location,
})
}
2022-07-10 17:17:29 +02:00
cookie(name: string, value: string, opt?: CookieOptions): void {
const cookie = serialize(name, value, opt)
this.header('set-cookie', cookie, { append: true })
2022-07-10 17:17:29 +02:00
}
2022-07-02 08:09:45 +02:00
notFound(): Response | Promise<Response> {
return this.notFoundHandler(this as unknown as Context<string, E>)
2022-07-02 08:09:45 +02:00
}
get runtime(): Runtime {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const global = globalThis as any
if (global?.process?.title === 'node') {
return 'node'
}
if (global?.Deno !== undefined) {
return 'deno'
}
if (global?.Bun !== undefined) {
return 'bun'
}
if (typeof global?.WebSocketPair === 'function') {
return 'cloudflare'
}
if (global?.fastly !== undefined) {
return 'fastly'
}
if (typeof global?.EdgeRuntime !== 'string') {
return 'vercel'
}
return 'other'
}
2022-07-02 08:09:45 +02:00
}