Skip to content

Commit

Permalink
Merge branch 'v4' into feat/bodyParser
Browse files Browse the repository at this point in the history
  • Loading branch information
EdamAme-x authored Jan 28, 2024
2 parents fa1c8ae + a96f5ec commit 2cd9444
Show file tree
Hide file tree
Showing 40 changed files with 825 additions and 388 deletions.
3 changes: 0 additions & 3 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,3 @@ RUN curl -fsSL https://gist.githubusercontent.com/LukeChannings/09d53f5c36439104
# Install Bun
ENV BUN_INSTALL=/usr/local
RUN curl -fsSL https://bun.sh/install | bash

# Install Lagon
RUN yarn global add @lagon/cli esbuild
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
],
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
},
"typescript.tsdk": "node_modules/typescript/lib"
}
4 changes: 2 additions & 2 deletions deno_dist/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { PropsForRenderer } from './middleware/jsx-renderer/index.ts'
import type { HonoRequest } from './request.ts'
import type { Env, FetchEventLike, NotFoundHandler, Input, TypedResponse } from './types.ts'
import { resolveCallback, HtmlEscapedCallbackPhase } from './utils/html.ts'
import type { StatusCode } from './utils/http-status.ts'
import type { RedirectStatusCode, StatusCode } from './utils/http-status.ts'
import type { JSONValue, InterfaceToType, JSONParsed } from './utils/types.ts'

type HeaderRecord = Record<string, string | string[]>
Expand Down Expand Up @@ -528,7 +528,7 @@ export class Context<
* ```
* @see https://hono.dev/api/context#redirect
*/
redirect = (location: string, status: StatusCode = 302): Response => {
redirect = (location: string, status: RedirectStatusCode = 302): Response => {
this.#headers ??= new Headers()
this.#headers.set('Location', location)
return this.newResponse(null, status)
Expand Down
12 changes: 1 addition & 11 deletions deno_dist/helper/adapter/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
import type { Context } from '../../context.ts'

export type Runtime =
| 'node'
| 'deno'
| 'bun'
| 'workerd'
| 'fastly'
| 'edge-light'
| 'lagon'
| 'other'
export type Runtime = 'node' | 'deno' | 'bun' | 'workerd' | 'fastly' | 'edge-light' | 'other'

export const env = <T extends Record<string, unknown>, C extends Context = Context<{}>>(
c: C,
Expand All @@ -24,7 +16,6 @@ export const env = <T extends Record<string, unknown>, C extends Context = Conte
bun: () => globalEnv,
node: () => globalEnv,
'edge-light': () => globalEnv,
lagon: () => globalEnv,
deno: () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
Expand All @@ -48,7 +39,6 @@ export const getRuntimeKey = () => {
if (typeof global?.WebSocketPair === 'function') return 'workerd'
if (typeof global?.EdgeRuntime === 'string') return 'edge-light'
if (global?.fastly !== undefined) return 'fastly'
if (global?.__lagon__ !== undefined) return 'lagon'
if (global?.process?.release?.name === 'node') return 'node'

return 'other'
Expand Down
12 changes: 11 additions & 1 deletion deno_dist/helper/dev/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ export const inspectRoutes = <E extends Env>(hono: Hono<E>): RouteData[] => {
}

export const showRoutes = <E extends Env>(hono: Hono<E>, opts?: ShowRoutesOptions) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const { process, Deno } = globalThis as any
const isNoColor =
typeof process !== 'undefined'
? // eslint-disable-next-line no-unsafe-optional-chaining
'NO_COLOR' in process?.env
: typeof Deno?.noColor === 'boolean'
? (Deno.noColor as boolean)
: false
const colorEnabled = opts?.colorize ?? !isNoColor
const routeData: Record<string, RouteData[]> = {}
let maxMethodLength = 0
let maxPathLength = 0
Expand All @@ -61,7 +71,7 @@ export const showRoutes = <E extends Env>(hono: Hono<E>, opts?: ShowRoutesOption
}
const { method, path, routes } = data

const methodStr = opts?.colorize ?? true ? `\x1b[32m${method}\x1b[0m` : method
const methodStr = colorEnabled ? `\x1b[32m${method}\x1b[0m` : method
console.log(`${methodStr} ${' '.repeat(maxMethodLength - method.length)} ${path}`)

if (!opts?.verbose) {
Expand Down
2 changes: 1 addition & 1 deletion deno_dist/helper/factory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,5 +210,5 @@ export class Factory<E extends Env = any, P extends string = any> {
export const createFactory = <E extends Env = any, P extends string = any>() => new Factory<E, P>()

export const createMiddleware = <E extends Env = any, P extends string = any, I extends Input = {}>(
middleware: MiddlewareHandler
middleware: MiddlewareHandler<E, P, I>
) => createFactory<E, P>().createMiddleware<I>(middleware)
6 changes: 3 additions & 3 deletions deno_dist/helper/ssg/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export interface FileSystemModule {
*/
export interface ToSSGResult {
success: boolean
files?: string[]
files: string[]
error?: Error
}

Expand Down Expand Up @@ -238,8 +238,8 @@ export const toSSG: ToSSGInterface = async (app, fs, options) => {
result = { success: true, files }
} catch (error) {
const errorObj = error instanceof Error ? error : new Error(String(error))
result = { success: false, error: errorObj }
result = { success: false, files: [], error: errorObj }
}
options?.afterGenerateHook?.(result)
await options?.afterGenerateHook?.(result)
return result
}
38 changes: 13 additions & 25 deletions deno_dist/hono-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ const errorHandler = (err: Error, c: Context) => {
return err.getResponse()
}
console.error(err)
const message = 'Internal Server Error'
return c.text(message, 500)
return c.text('Internal Server Error', 500)
}

type GetPath<E extends Env> = (request: Request, options?: { env?: E['Bindings'] }) => string
Expand Down Expand Up @@ -189,9 +188,7 @@ class Hono<
): Hono<E, MergeSchemaPath<SubSchema, MergePath<BasePath, SubPath>> & S, BasePath> {
const subApp = this.basePath(path)

if (!app) {
return subApp
}
if (!app) return subApp

app.routes.map((r) => {
let handler
Expand Down Expand Up @@ -298,9 +295,7 @@ class Hono<
}

private handleError(err: unknown, c: Context<E>) {
if (err instanceof Error) {
return this.errorHandler(err, c)
}
if (err instanceof Error) return this.errorHandler(err, c)
throw err
}

Expand Down Expand Up @@ -330,27 +325,19 @@ class Hono<
let res: ReturnType<H>
try {
res = matchResult[0][0][0][0](c, async () => {})
if (!res) {
return this.notFoundHandler(c)
}
if (!res) return this.notFoundHandler(c)
} catch (err) {
return this.handleError(err, c)
}

if (res instanceof Response) return res

return (async () => {
let awaited: Response | void
try {
awaited = await res
if (!awaited) {
return this.notFoundHandler(c)
}
} catch (err) {
return this.handleError(err, c)
}
return awaited
})()
return res instanceof Promise
? res
.then(
(resolved: Response | undefined) =>
resolved || (c.finalized ? c.res : this.notFoundHandler(c))
)
.catch((err: Error) => this.handleError(err, c))
: res
}

const composed = compose<Context>(matchResult[0], this.errorHandler, this.notFoundHandler)
Expand All @@ -363,6 +350,7 @@ class Hono<
'Context is not finalized. You may forget returning Response object or `await next()`'
)
}

return context.res
} catch (err) {
return this.handleError(err, c)
Expand Down
4 changes: 2 additions & 2 deletions deno_dist/jsx/dom/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ const findInsertBefore = (node: Node | undefined): ChildNode | null => {
}

if (node.vC) {
for (let i = 0; i < node.vC.length; i++) {
for (let i = 0, len = node.vC.length; i < len; i++) {
const e = findInsertBefore(node.vC[i])
if (e) {
return e
Expand Down Expand Up @@ -235,7 +235,7 @@ const applyNodeObject = (node: NodeObject, container: Container) => {
}
}

for (let i = 0; i < next.length; i++, offset++) {
for (let i = 0, len = next.length; i < len; i++, offset++) {
const child = next[i]

let el: HTMLElement | Text
Expand Down
72 changes: 44 additions & 28 deletions deno_dist/middleware/secure-headers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Context } from '../../context.ts'
import type { MiddlewareHandler } from '../../types.ts'

interface ContentSecurityPolicyOptions {
Expand Down Expand Up @@ -96,46 +97,61 @@ const DEFAULT_OPTIONS: SecureHeadersOptions = {

export const secureHeaders = (customOptions?: Partial<SecureHeadersOptions>): MiddlewareHandler => {
const options = { ...DEFAULT_OPTIONS, ...customOptions }
const headersToSet = Object.entries(HEADERS_MAP)
.filter(([key]) => options[key as keyof SecureHeadersOptions])
.map(([key, defaultValue]) => {
const overrideValue = options[key as keyof SecureHeadersOptions]
if (typeof overrideValue === 'string') return [defaultValue[0], overrideValue]
return defaultValue
})
const headersToSet = getFilteredHeaders(options)

if (options.contentSecurityPolicy) {
const cspDirectives = Object.entries(options.contentSecurityPolicy)
.map(([directive, value]) => {
// convert camelCase to kebab-case directives (e.g. `defaultSrc` -> `default-src`)
directive = directive.replace(
/[A-Z]+(?![a-z])|[A-Z]/g,
(match, offset) => (offset ? '-' : '') + match.toLowerCase()
)
return `${directive} ${Array.isArray(value) ? value.join(' ') : value}`
})
.join('; ')
headersToSet.push(['Content-Security-Policy', cspDirectives])
headersToSet.push(['Content-Security-Policy', getCSPDirectives(options.contentSecurityPolicy)])
}

if (options.reportingEndpoints) {
const reportingEndpoints = options.reportingEndpoints
.map((endpoint) => `${endpoint.name}="${endpoint.url}"`)
.join(', ')
headersToSet.push(['Reporting-Endpoints', reportingEndpoints])
headersToSet.push(['Reporting-Endpoints', getReportingEndpoints(options.reportingEndpoints)])
}

if (options.reportTo) {
const reportToOptions = options.reportTo.map((option) => JSON.stringify(option)).join(', ')
headersToSet.push(['Report-To', reportToOptions])
headersToSet.push(['Report-To', getReportToOptions(options.reportTo)])
}

return async function secureHeaders(ctx, next) {
await next()
headersToSet.forEach(([header, value]) => {
ctx.res.headers.set(header, value)
})

setHeaders(ctx, headersToSet)
ctx.res.headers.delete('X-Powered-By')
}
}

function getFilteredHeaders(options: SecureHeadersOptions): [string, string][] {
return Object.entries(HEADERS_MAP)
.filter(([key]) => options[key as keyof SecureHeadersOptions])
.map(([key, defaultValue]) => {
const overrideValue = options[key as keyof SecureHeadersOptions]
return typeof overrideValue === 'string' ? [defaultValue[0], overrideValue] : defaultValue
})
}

function getCSPDirectives(
contentSecurityPolicy: SecureHeadersOptions['contentSecurityPolicy']
): string {
return Object.entries(contentSecurityPolicy || [])
.map(([directive, value]) => {
const kebabCaseDirective = directive.replace(/[A-Z]+(?![a-z])|[A-Z]/g, (match, offset) =>
offset ? '-' + match.toLowerCase() : match.toLowerCase()
)
return `${kebabCaseDirective} ${Array.isArray(value) ? value.join(' ') : value}`
})
.join('; ')
}

function getReportingEndpoints(
reportingEndpoints: SecureHeadersOptions['reportingEndpoints'] = []
): string {
return reportingEndpoints.map((endpoint) => `${endpoint.name}="${endpoint.url}"`).join(', ')
}

function getReportToOptions(reportTo: SecureHeadersOptions['reportTo'] = []): string {
return reportTo.map((option) => JSON.stringify(option)).join(', ')
}

function setHeaders(ctx: Context, headersToSet: [string, string][]) {
headersToSet.forEach(([header, value]) => {
ctx.res.headers.set(header, value)
})
}
45 changes: 23 additions & 22 deletions deno_dist/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,31 +81,32 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
): UndefinedIfHavingQuestion<ParamKeys<P2>>
param<P2 extends string = P>(): UnionToIntersection<ParamKeyToRecord<ParamKeys<P2>>>
param(key?: string): unknown {
if (key) {
const param = (
this.#matchResult[1]
? this.#matchResult[1][this.#matchResult[0][this.routeIndex][1][key] as any]
: this.#matchResult[0][this.routeIndex][1][key]
) as string | undefined
return param ? (/\%/.test(param) ? decodeURIComponent_(param) : param) : undefined
} else {
const decoded: Record<string, string> = {}
return key ? this.getDecodedParam(key) : this.getAllDecodedParams()
}

const keys = Object.keys(this.#matchResult[0][this.routeIndex][1])
for (let i = 0, len = keys.length; i < len; i++) {
const key = keys[i]
const value = (
this.#matchResult[1]
? this.#matchResult[1][this.#matchResult[0][this.routeIndex][1][key] as any]
: this.#matchResult[0][this.routeIndex][1][key]
) as string | undefined
if (value && typeof value === 'string') {
decoded[key] = /\%/.test(value) ? decodeURIComponent_(value) : value
}
}
private getDecodedParam(key: string): string | undefined {
const paramKey = this.#matchResult[0][this.routeIndex][1][key]
const param = this.getParamValue(paramKey)

return param ? (/\%/.test(param) ? decodeURIComponent_(param) : param) : undefined
}

private getAllDecodedParams(): Record<string, string> {
const decoded: Record<string, string> = {}

return decoded
const keys = Object.keys(this.#matchResult[0][this.routeIndex][1])
for (const key of keys) {
const value = this.getParamValue(this.#matchResult[0][this.routeIndex][1][key])
if (value && typeof value === 'string') {
decoded[key] = /\%/.test(value) ? decodeURIComponent_(value) : value
}
}

return decoded
}

private getParamValue(paramKey: any): string | undefined {
return this.#matchResult[1] ? this.#matchResult[1][paramKey as any] : paramKey
}

/**
Expand Down
Loading

0 comments on commit 2cd9444

Please sign in to comment.