diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 1efe02f0c1..3909f981bb 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -2,5 +2,4 @@ - [ ] Add tests - [ ] Run tests -- [ ] `bun denoify` to generate files for Deno - [ ] `bun run format:fix && bun run lint:fix` to format the code diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f475f5d41a..a90117e7cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,26 +20,6 @@ jobs: - run: bun run build - run: bun run test - denoify: - name: "Checking if you've done denoify" - runs-on: ubuntu-latest - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: '18.x' - - uses: oven-sh/setup-bun@v1 - with: - bun-version: '1.0.25' - - run: bun install - - run: | - bun run denoify - if [[ `git status --porcelain` ]]; then - exit 1 - fi - jsr-dry-run: name: "Checking if it's valid for JSR" runs-on: ubuntu-latest diff --git a/bun.lockb b/bun.lockb index e10923e7e7..fda42049f4 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/deno.json b/deno.json index e7a96a8311..5605e66267 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@hono/do-not-use-this", - "version": "0.0.1-1", + "version": "0.0.3", "compilerOptions": { "lib": [ "dom", @@ -8,20 +8,32 @@ "deno.ns" ] }, + "unstable": [ + "sloppy-imports" + ], "exports": { - ".": "./deno_dist/mod.ts", - "./jsx/jsx-runtime": "./deno_dist/jsx/jsx-runtime.ts" + ".": "./src/mod.ts", + "./jsx/jsx-runtime": "./src/jsx/jsx-runtime.ts" }, "publish": { "include": [ "deno.json", "LICENSE", "README.md", - "deno_dist/**/*.ts" + "src/**/*.ts" ], "exclude": [ - "deno_dist/**/*.test.ts", - "deno_dist/**/*.test.tsx" + "src/index.ts", + "src/adapter/aws-lambda/**/*.ts", + "src/adapter/bun/**/*.ts", + "src/adapter/cloudflare-pages/**/*.ts", + "src/adapter/cloudflare-workers/**/*.ts", + "src/adapter/lambda-edge/**/*.ts", + "src/adapter/netlify/**/*.ts", + "src/adapter/vercel/**/*.ts", + "src/test-utils/**/*.ts", + "src/**/*.test.ts", + "src/**/*.test.tsx" ] } } \ No newline at end of file diff --git a/deno_dist/LICENSE b/deno_dist/LICENSE deleted file mode 100644 index bd174facca..0000000000 --- a/deno_dist/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 - present, Yusuke Wada and Hono contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/deno_dist/README.md b/deno_dist/README.md deleted file mode 100644 index 1cfe2bde4f..0000000000 --- a/deno_dist/README.md +++ /dev/null @@ -1,92 +0,0 @@ -
- - Hono - -
- -
- -

-Documentation :point_right: hono.dev
-v4 has been released! Migration guide -

- -
- -[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/honojs/hono/ci.yml?branch=main)](https://github.com/honojs/hono/actions) -[![GitHub](https://img.shields.io/github/license/honojs/hono)](https://github.com/honojs/hono/blob/main/LICENSE) -[![npm](https://img.shields.io/npm/v/hono)](https://www.npmjs.com/package/hono) -[![npm](https://img.shields.io/npm/dm/hono)](https://www.npmjs.com/package/hono) -[![Bundle Size](https://img.shields.io/bundlephobia/min/hono)](https://bundlephobia.com/result?p=hono) -[![Bundle Size](https://img.shields.io/bundlephobia/minzip/hono)](https://bundlephobia.com/result?p=hono) -[![npm type definitions](https://img.shields.io/npm/types/hono)](https://www.npmjs.com/package/hono) -[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/honojs/hono)](https://github.com/honojs/hono/pulse) -[![GitHub last commit](https://img.shields.io/github/last-commit/honojs/hono)](https://github.com/honojs/hono/commits/main) -[![Deno badge](https://img.shields.io/endpoint?url=https%3A%2F%2Fdeno-visualizer.danopia.net%2Fshields%2Flatest-version%2Fx%2Fhono%2Fmod.ts)](https://doc.deno.land/https/deno.land/x/hono/mod.ts) -[![Discord badge](https://img.shields.io/discord/1011308539819597844?label=Discord&logo=Discord)](https://discord.gg/KMh2eNSdxV) - -Hono - _**\[η‚Ž\] means flameπŸ”₯ in Japanese**_ - is a small, simple, and ultrafast web framework for the Edges. -It works on any JavaScript runtime: Cloudflare Workers, Fastly Compute, Deno, Bun, Vercel, AWS Lambda, Lambda@Edge, and Node.js. - -Fast, but not only fast. - -```ts -import { Hono } from 'hono' -const app = new Hono() - -app.get('/', (c) => c.text('Hono!')) - -export default app -``` - -## Quick Start - -``` -npm create hono@latest -``` - -## Features - -- **Ultrafast** πŸš€ - The router `RegExpRouter` is really fast. Not using linear loops. Fast. -- **Lightweight** πŸͺΆ - The `hono/tiny` preset is under 13kB. Hono has zero dependencies and uses only the Web Standard API. -- **Multi-runtime** 🌍 - Works on Cloudflare Workers, Fastly Compute, Deno, Bun, AWS Lambda, Lambda@Edge, or Node.js. The same code runs on all platforms. -- **Batteries Included** πŸ”‹ - Hono has built-in middleware, custom middleware, and third-party middleware. Batteries included. -- **Delightful DX** πŸ˜ƒ - Super clean APIs. First-class TypeScript support. Now, we've got "Types". - -## Documentation - -The documentation is available on [hono.dev](https://hono.dev). - -## Migration - -The migration guide is available on [docs/MIGRATION.md](docs/MIGRATION.md). - -## Communication - -[Twitter](https://twitter.com/honojs) and [Discord channel](https://discord.gg/KMh2eNSdxV) are available. - -## Contributing - -Contributions Welcome! You can contribute in the following ways. - -- Create an Issue - Propose a new feature. Report a bug. -- Pull Request - Fix a bug and typo. Refactor the code. -- Create third-party middleware - Instruct below. -- Share - Share your thoughts on the Blog, Twitter, and others. -- Make your application - Please try to use Hono. - -For more details, see [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md). - -## Contributors - -Thanks to [all contributors](https://github.com/honojs/hono/graphs/contributors)! - -## Authors - -Yusuke Wada - -_RegExpRouter_, _SmartRouter_, _LinearRouter_, and _PatternRouter_ are created by Taku Amano - -## License - -Distributed under the MIT License. See [LICENSE](LICENSE) for more information. diff --git a/deno_dist/adapter/deno/deno.d.ts b/deno_dist/adapter/deno/deno.d.ts deleted file mode 100644 index d85d8eac61..0000000000 --- a/deno_dist/adapter/deno/deno.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -declare namespace Deno { - /** - * Creates a new directory with the specified path. - * - * @param path The path to create a directory. - * @param options Options for creating a directory. - * @returns A promise that resolves when the directory is created. - */ - export function mkdir(path: string, options?: { recursive?: boolean }): Promise - - /** - * Write a new file, with the specified path and data. - * - * @param path The path to the file to write. - * @param data The data to write to the file. - * @returns A promise that resolves when the file is written. - */ - export function writeFile(path: string, data: Uint8Array): Promise - - export function upgradeWebSocket(req: Request): { - response: Response - socket: WebSocket - } -} diff --git a/deno_dist/adapter/deno/index.ts b/deno_dist/adapter/deno/index.ts deleted file mode 100644 index 163167eb2e..0000000000 --- a/deno_dist/adapter/deno/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { serveStatic } from './serve-static.ts' -export { toSSG, denoFileSystemModule } from './ssg.ts' -export { upgradeWebSocket } from './websocket.ts' diff --git a/deno_dist/adapter/deno/serve-static.ts b/deno_dist/adapter/deno/serve-static.ts deleted file mode 100644 index 2661efb249..0000000000 --- a/deno_dist/adapter/deno/serve-static.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { ServeStaticOptions } from '../../middleware/serve-static/index.ts' -import { serveStatic as baseServeStatic } from '../../middleware/serve-static/index.ts' -import type { Env, MiddlewareHandler } from '../../types.ts' - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -const { open } = Deno - -export const serveStatic = ( - options: ServeStaticOptions -): MiddlewareHandler => { - return async function serveStatic(c, next) { - const getContent = async (path: string) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let file: any - try { - file = await open(path) - } catch (e) { - console.warn(`${e}`) - } - return file ? file.readable : null - } - const pathResolve = (path: string) => { - return `./${path}` - } - return baseServeStatic({ - ...options, - getContent, - pathResolve, - })(c, next) - } -} diff --git a/deno_dist/adapter/deno/ssg.ts b/deno_dist/adapter/deno/ssg.ts deleted file mode 100644 index 33924c24ec..0000000000 --- a/deno_dist/adapter/deno/ssg.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { toSSG as baseToSSG } from '../../helper/ssg/index.ts' -import type { FileSystemModule, ToSSGAdaptorInterface } from '../../helper/ssg/index.ts' - -/** - * @experimental - * `denoFileSystemModule` is an experimental feature. - * The API might be changed. - */ -export const denoFileSystemModule: FileSystemModule = { - writeFile: async (path, data) => { - const uint8Data = - typeof data === 'string' ? new TextEncoder().encode(data) : new Uint8Array(data) - await Deno.writeFile(path, uint8Data) - }, - mkdir: async (path, options) => { - return Deno.mkdir(path, { recursive: options?.recursive ?? false }) - }, -} - -/** - * @experimental - * `toSSG` is an experimental feature. - * The API might be changed. - */ -export const toSSG: ToSSGAdaptorInterface = async (app, options) => { - return baseToSSG(app, denoFileSystemModule, options) -} diff --git a/deno_dist/adapter/deno/websocket.ts b/deno_dist/adapter/deno/websocket.ts deleted file mode 100644 index 77cf622082..0000000000 --- a/deno_dist/adapter/deno/websocket.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { UpgradeWebSocket, WSContext, WSReadyState } from '../../helper/websocket/index.ts' - -export const upgradeWebSocket: UpgradeWebSocket = (createEvents) => async (c, next) => { - if (c.req.header('upgrade') !== 'websocket') { - return await next() - } - const { response, socket } = Deno.upgradeWebSocket(c.req.raw) - - const events = await createEvents(c) - - const wsContext: WSContext = { - binaryType: 'arraybuffer', - close: (code, reason) => socket.close(code, reason), - get protocol() { - return socket.protocol - }, - raw: socket, - get readyState() { - return socket.readyState as WSReadyState - }, - url: socket.url ? new URL(socket.url) : null, - send: (source) => socket.send(source), - } - socket.onopen = (evt) => events.onOpen && events.onOpen(evt, wsContext) - socket.onmessage = (evt) => events.onMessage && events.onMessage(evt, wsContext) - socket.onclose = (evt) => events.onClose && events.onClose(evt, wsContext) - socket.onerror = (evt) => events.onError && events.onError(evt, wsContext) - - return response -} diff --git a/deno_dist/adapter/netlify/handler.ts b/deno_dist/adapter/netlify/handler.ts deleted file mode 100644 index 9711d03979..0000000000 --- a/deno_dist/adapter/netlify/handler.ts +++ /dev/null @@ -1,17 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import type { Context } from 'https://edge.netlify.com/' -import type { Hono } from '../../hono.ts' - -export type Env = { - Bindings: { - context: Context - } -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const handle = (app: Hono) => { - return (req: Request, context: Context) => { - return app.fetch(req, { context }) - } -} diff --git a/deno_dist/adapter/netlify/index.ts b/deno_dist/adapter/netlify/index.ts deleted file mode 100644 index 8833f3bef2..0000000000 --- a/deno_dist/adapter/netlify/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './mod.ts' diff --git a/deno_dist/adapter/netlify/mod.ts b/deno_dist/adapter/netlify/mod.ts deleted file mode 100644 index dc56ad619f..0000000000 --- a/deno_dist/adapter/netlify/mod.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { handle } from './handler.ts' -export type { Env } from './handler.ts' diff --git a/deno_dist/client/client.ts b/deno_dist/client/client.ts deleted file mode 100644 index 632b2c5d1c..0000000000 --- a/deno_dist/client/client.ts +++ /dev/null @@ -1,190 +0,0 @@ -import type { Hono } from '../hono.ts' -import type { ValidationTargets } from '../types.ts' -import { serialize } from '../utils/cookie.ts' -import type { UnionToIntersection } from '../utils/types.ts' -import type { Callback, Client, ClientRequestOptions } from './types.ts' -import { - deepMerge, - mergePath, - removeIndexString, - replaceUrlParam, - replaceUrlProtocol, -} from './utils.ts' - -const createProxy = (callback: Callback, path: string[]) => { - const proxy: unknown = new Proxy(() => {}, { - get(_obj, key) { - if (typeof key !== 'string' || key === 'then') { - return undefined - } - return createProxy(callback, [...path, key]) - }, - apply(_1, _2, args) { - return callback({ - path, - args, - }) - }, - }) - return proxy -} - -class ClientRequestImpl { - private url: string - private method: string - private queryParams: URLSearchParams | undefined = undefined - private pathParams: Record = {} - private rBody: BodyInit | undefined - private cType: string | undefined = undefined - - constructor(url: string, method: string) { - this.url = url - this.method = method - } - fetch = async ( - args?: ValidationTargets & { - param?: Record - }, - opt?: ClientRequestOptions - ) => { - if (args) { - if (args.query) { - for (const [k, v] of Object.entries(args.query)) { - if (v === undefined) { - continue - } - - this.queryParams ||= new URLSearchParams() - if (Array.isArray(v)) { - for (const v2 of v) { - this.queryParams.append(k, v2) - } - } else { - this.queryParams.set(k, v) - } - } - } - - if (args.form) { - const form = new FormData() - for (const [k, v] of Object.entries(args.form)) { - form.append(k, v) - } - this.rBody = form - } - - if (args.json) { - this.rBody = JSON.stringify(args.json) - this.cType = 'application/json' - } - - if (args.param) { - this.pathParams = args.param - } - } - - let methodUpperCase = this.method.toUpperCase() - - const headerValues: Record = { - ...(args?.header ?? {}), - ...(typeof opt?.headers === 'function' - ? await opt.headers() - : opt?.headers - ? opt.headers - : {}), - } - - if (args?.cookie) { - const cookies: string[] = [] - for (const [key, value] of Object.entries(args.cookie)) { - cookies.push(serialize(key, value, { path: '/' })) - } - headerValues['Cookie'] = cookies.join(',') - } - - if (this.cType) { - headerValues['Content-Type'] = this.cType - } - - const headers = new Headers(headerValues ?? undefined) - let url = this.url - - url = removeIndexString(url) - url = replaceUrlParam(url, this.pathParams) - - if (this.queryParams) { - url = url + '?' + this.queryParams.toString() - } - methodUpperCase = this.method.toUpperCase() - const setBody = !(methodUpperCase === 'GET' || methodUpperCase === 'HEAD') - - // Pass URL string to 1st arg for testing with MSW and node-fetch - return (opt?.fetch || fetch)(url, { - body: setBody ? this.rBody : undefined, - method: methodUpperCase, - headers: headers, - ...opt?.init, - }) - } -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const hc = >( - baseUrl: string, - options?: ClientRequestOptions -) => - createProxy(function proxyCallback(opts) { - const parts = [...opts.path] - - // allow calling .toString() and .valueOf() on the proxy - if (parts[parts.length - 1] === 'toString') { - if (parts[parts.length - 2] === 'name') { - // e.g. hc().somePath.name.toString() -> "somePath" - return parts[parts.length - 3] || '' - } - // e.g. hc().somePath.toString() - return proxyCallback.toString() - } - - if (parts[parts.length - 1] === 'valueOf') { - if (parts[parts.length - 2] === 'name') { - // e.g. hc().somePath.name.valueOf() -> "somePath" - return parts[parts.length - 3] || '' - } - // e.g. hc().somePath.valueOf() - return proxyCallback - } - - let method = '' - if (/^\$/.test(parts[parts.length - 1])) { - const last = parts.pop() - if (last) { - method = last.replace(/^\$/, '') - } - } - - const path = parts.join('/') - const url = mergePath(baseUrl, path) - if (method === 'url') { - if (opts.args[0] && opts.args[0].param) { - return new URL(replaceUrlParam(url, opts.args[0].param)) - } - return new URL(url) - } - if (method === 'ws') { - const targetUrl = replaceUrlProtocol( - opts.args[0] && opts.args[0].param ? replaceUrlParam(url, opts.args[0].param) : url, - 'ws' - ) - - return new WebSocket(targetUrl) - } - - const req = new ClientRequestImpl(url, method) - if (method) { - options ??= {} - const args = deepMerge(options, { ...(opts.args[1] ?? {}) }) - return req.fetch(opts.args[0], args) - } - return req - }, []) as UnionToIntersection> diff --git a/deno_dist/client/index.ts b/deno_dist/client/index.ts deleted file mode 100644 index 5be1ce9771..0000000000 --- a/deno_dist/client/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export { hc } from './client.ts' -export type { - InferResponseType, - InferRequestType, - Fetch, - ClientRequestOptions, - ClientRequest, - ClientResponse, -} from './types.ts' diff --git a/deno_dist/client/types.ts b/deno_dist/client/types.ts deleted file mode 100644 index 0b3c776d0d..0000000000 --- a/deno_dist/client/types.ts +++ /dev/null @@ -1,179 +0,0 @@ -import type { Hono } from '../hono.ts' -import type { Endpoint, ResponseFormat, Schema } from '../types.ts' -import type { StatusCode, SuccessStatusCode } from '../utils/http-status.ts' -import type { HasRequiredKeys } from '../utils/types.ts' - -type HonoRequest = (typeof Hono.prototype)['request'] - -export type ClientRequestOptions = { - fetch?: typeof fetch | HonoRequest - /** - * Standard `RequestInit`, caution that this take highest priority - * and could be used to overwrite things that Hono sets for you, like `body | method | headers`. - * - * If you want to add some headers, use in `headers` instead of `init` - */ - init?: RequestInit -} & (keyof T extends never - ? { - headers?: - | Record - | (() => Record | Promise>) - } - : { - headers: T | (() => T | Promise) - }) - -export type ClientRequest = { - [M in keyof S]: S[M] extends Endpoint & { input: infer R } - ? R extends object - ? HasRequiredKeys extends true - ? (args: R, options?: ClientRequestOptions) => Promise> - : (args?: R, options?: ClientRequestOptions) => Promise> - : never - : never -} & { - $url: ( - arg?: S[keyof S] extends { input: infer R } - ? R extends { param: infer P } - ? { param: P } - : {} - : {} - ) => URL -} & (S['$get'] extends { outputFormat: 'ws' } - ? S['$get'] extends { input: infer I } - ? { - $ws: (args?: I) => WebSocket - } - : {} - : {}) - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type BlankRecordToNever = T extends any - ? T extends null - ? null - : keyof T extends never - ? never - : T - : never - -type ClientResponseOfEndpoint = T extends { - output: infer O - outputFormat: infer F - status: infer S -} - ? ClientResponse - : never - -export interface ClientResponse< - T, - U extends number = StatusCode, - F extends ResponseFormat = ResponseFormat -> extends globalThis.Response { - readonly body: ReadableStream | null - readonly bodyUsed: boolean - ok: U extends SuccessStatusCode - ? true - : U extends Exclude - ? false - : boolean - status: U - statusText: string - headers: Headers - url: string - redirect(url: string, status: number): Response - clone(): Response - json(): F extends 'text' - ? Promise - : F extends 'json' - ? Promise> - : Promise - text(): F extends 'text' ? (T extends string ? Promise : Promise) : Promise - blob(): Promise - formData(): Promise - arrayBuffer(): Promise -} - -export interface Response extends ClientResponse {} - -export type Fetch = ( - args?: InferRequestType, - opt?: ClientRequestOptions -) => Promise>> - -type InferEndpointType = T extends ( - args: infer R, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - options: any | undefined -) => Promise - ? U extends ClientResponse - ? { input: NonNullable; output: O; outputFormat: F; status: S } extends Endpoint - ? { input: NonNullable; output: O; outputFormat: F; status: S } - : never - : never - : never - -export type InferResponseType = InferResponseTypeFromEndpoint< - InferEndpointType, - U -> - -type InferResponseTypeFromEndpoint = T extends { - output: infer O - status: infer S -} - ? S extends U - ? O - : never - : never - -export type InferRequestType = T extends ( - args: infer R, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - options: any | undefined -) => Promise> - ? NonNullable - : never - -export type InferRequestOptionsType = T extends ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - args: any, - options: infer R -) => Promise> - ? NonNullable - : never - -type PathToChain< - Path extends string, - E extends Schema, - Original extends string = '' -> = Path extends `/${infer P}` - ? PathToChain - : Path extends `${infer P}/${infer R}` - ? { [K in P]: PathToChain } - : { - [K in Path extends '' ? 'index' : Path]: ClientRequest< - E extends Record ? E[Original] : never - > - } - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type Client = T extends Hono - ? S extends Record - ? K extends string - ? PathToChain - : never - : never - : never - -export type Callback = (opts: CallbackOptions) => unknown - -interface CallbackOptions { - path: string[] - // eslint-disable-next-line @typescript-eslint/no-explicit-any - args: any[] -} - -export type ObjectType = { - [key: string]: T -} diff --git a/deno_dist/client/utils.ts b/deno_dist/client/utils.ts deleted file mode 100644 index c9b43bcbba..0000000000 --- a/deno_dist/client/utils.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { ObjectType } from './types.ts' - -export const mergePath = (base: string, path: string) => { - base = base.replace(/\/+$/, '') - base = base + '/' - path = path.replace(/^\/+/, '') - return base + path -} - -export const replaceUrlParam = (urlString: string, params: Record) => { - for (const [k, v] of Object.entries(params)) { - const reg = new RegExp('/:' + k + '(?:{[^/]+})?') - urlString = urlString.replace(reg, `/${v}`) - } - return urlString -} - -export const replaceUrlProtocol = (urlString: string, protocol: 'ws' | 'http') => { - switch (protocol) { - case 'ws': - return urlString.replace(/^http/, 'ws') - case 'http': - return urlString.replace(/^ws/, 'http') - } -} - -export const removeIndexString = (urlSting: string) => { - if (/^https?:\/\/[^\/]+?\/index$/.test(urlSting)) { - return urlSting.replace(/\/index$/, '/') - } - return urlSting.replace(/\/index$/, '') -} - -function isObject(item: unknown): item is ObjectType { - return typeof item === 'object' && item !== null && !Array.isArray(item) -} - -export function deepMerge(target: T, source: Record): T { - if (!isObject(target) && !isObject(source)) { - return source as T - } - const merged = { ...target } as ObjectType - - for (const key in source) { - const value = source[key] - if (isObject(merged[key]) && isObject(value)) { - merged[key] = deepMerge(merged[key], value) - } else { - merged[key] = value as T[keyof T] & T - } - } - - return merged as T -} diff --git a/deno_dist/compose.ts b/deno_dist/compose.ts deleted file mode 100644 index b03903a23d..0000000000 --- a/deno_dist/compose.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Context } from './context.ts' -import type { ParamIndexMap, Params } from './router.ts' -import type { Env, NotFoundHandler, ErrorHandler } from './types.ts' - -interface ComposeContext { - finalized: boolean - res: unknown -} - -// Based on the code in the MIT licensed `koa-compose` package. -export const compose = ( - middleware: [[Function, unknown], ParamIndexMap | Params][], - onError?: ErrorHandler, - onNotFound?: NotFoundHandler -) => { - return (context: C, next?: Function) => { - let index = -1 - return dispatch(0) - - async function dispatch(i: number): Promise { - if (i <= index) { - throw new Error('next() called multiple times') - } - index = i - - let res - let isError = false - let handler - - if (middleware[i]) { - handler = middleware[i][0][0] - if (context instanceof Context) { - context.req.routeIndex = i - } - } else { - handler = (i === middleware.length && next) || undefined - } - - if (!handler) { - if (context instanceof Context && context.finalized === false && onNotFound) { - res = await onNotFound(context) - } - } else { - try { - res = await handler(context, () => { - return dispatch(i + 1) - }) - } catch (err) { - if (err instanceof Error && context instanceof Context && onError) { - context.error = err - res = await onError(err, context) - isError = true - } else { - throw err - } - } - } - - if (res && (context.finalized === false || isError)) { - context.res = res - } - return context - } - } -} diff --git a/deno_dist/context.ts b/deno_dist/context.ts deleted file mode 100644 index ce647e4cfa..0000000000 --- a/deno_dist/context.ts +++ /dev/null @@ -1,572 +0,0 @@ -import type { HonoRequest } from './request.ts' -import type { Env, FetchEventLike, Input, NotFoundHandler, TypedResponse } from './types.ts' -import { HtmlEscapedCallbackPhase, resolveCallback } from './utils/html.ts' -import type { RedirectStatusCode, StatusCode } from './utils/http-status.ts' -import type { JSONValue, JSONParsed, IsAny, Simplify } from './utils/types.ts' - -type HeaderRecord = Record -export type Data = string | ArrayBuffer | ReadableStream - -export interface ExecutionContext { - waitUntil(promise: Promise): void - passThroughOnException(): void -} - -export interface ContextVariableMap {} - -export interface ContextRenderer {} -interface DefaultRenderer { - (content: string | Promise): Response | Promise -} - -export type Renderer = ContextRenderer extends Function ? ContextRenderer : DefaultRenderer -export type PropsForRenderer = [...Required>] extends [unknown, infer Props] - ? Props - : unknown - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type Layout> = (props: T) => any - -interface Get { - (key: Key): ContextVariableMap[Key] - (key: Key): E['Variables'][Key] -} - -interface Set { - (key: Key, value: ContextVariableMap[Key]): void - (key: Key, value: E['Variables'][Key]): void -} - -interface NewResponse { - (data: Data | null, status?: StatusCode, headers?: HeaderRecord): Response - (data: Data | null, init?: ResponseInit): Response -} - -interface BodyRespond extends NewResponse {} - -interface TextRespond { - (text: T, status?: U, headers?: HeaderRecord): Response & - TypedResponse - (text: T, init?: ResponseInit): Response & - TypedResponse -} - -interface JSONRespond { - , U extends StatusCode>( - object: T, - status?: U, - headers?: HeaderRecord - ): Response & - TypedResponse< - Simplify extends JSONValue - ? JSONValue extends Simplify - ? never - : JSONParsed - : never, - U, - 'json' - > - , U extends StatusCode>( - object: Simplify extends JSONValue ? T : Simplify, - init?: ResponseInit - ): Response & - TypedResponse< - Simplify extends JSONValue - ? JSONValue extends Simplify - ? never - : JSONParsed - : never, - U, - 'json' - > -} - -interface HTMLRespond { - (html: string | Promise, status?: StatusCode, headers?: HeaderRecord): - | Response - | Promise - (html: string | Promise, init?: ResponseInit): Response | Promise -} - -type ContextOptions = { - env: E['Bindings'] - executionCtx?: FetchEventLike | ExecutionContext | undefined - notFoundHandler?: NotFoundHandler -} - -export const TEXT_PLAIN = 'text/plain; charset=UTF-8' - -const setHeaders = (headers: Headers, map: Record = {}) => { - Object.entries(map).forEach(([key, value]) => headers.set(key, value)) - return headers -} - -export class Context< - // eslint-disable-next-line @typescript-eslint/no-explicit-any - E extends Env = any, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - P extends string = any, - I extends Input = {} -> { - /** - * `.req` is the instance of {@link HonoRequest}. - */ - req: HonoRequest - /** - * `.env` can get bindings (environment variables, secrets, KV namespaces, D1 database, R2 bucket etc.) in Cloudflare Workers. - * @example - * ```ts - * // Environment object for Cloudflare Workers - * app.get('*', async c => { - * const counter = c.env.COUNTER - * }) - * ``` - * @see https://hono.dev/api/context#env - */ - env: E['Bindings'] = {} - private _var: E['Variables'] = {} - finalized: boolean = false - /** - * `.error` can get the error object from the middleware if the Handler throws an error. - * @example - * ```ts - * app.use('*', async (c, next) => { - * await next() - * if (c.error) { - * // do something... - * } - * }) - * ``` - * @see https://hono.dev/api/context#error - */ - error: Error | undefined = undefined - - #status: StatusCode = 200 - #executionCtx: FetchEventLike | ExecutionContext | undefined - #headers: Headers | undefined = undefined - #preparedHeaders: Record | undefined = undefined - #res: Response | undefined - #isFresh = true - private layout: Layout | undefined = undefined - private renderer: Renderer = (content: string | Promise) => this.html(content) - private notFoundHandler: NotFoundHandler = () => new Response() - - constructor(req: HonoRequest, options?: ContextOptions) { - this.req = req - if (options) { - this.#executionCtx = options.executionCtx - this.env = options.env - if (options.notFoundHandler) { - this.notFoundHandler = options.notFoundHandler - } - } - } - - /** - * @see https://hono.dev/api/context#event - */ - get event(): FetchEventLike { - if (this.#executionCtx && 'respondWith' in this.#executionCtx) { - return this.#executionCtx - } else { - throw Error('This context has no FetchEvent') - } - } - - /** - * @see https://hono.dev/api/context#executionctx - */ - get executionCtx(): ExecutionContext { - if (this.#executionCtx) { - return this.#executionCtx as ExecutionContext - } else { - throw Error('This context has no ExecutionContext') - } - } - - /** - * @see https://hono.dev/api/context#res - */ - get res(): Response { - this.#isFresh = false - return (this.#res ||= new Response('404 Not Found', { status: 404 })) - } - - set res(_res: Response | undefined) { - this.#isFresh = false - if (this.#res && _res) { - this.#res.headers.delete('content-type') - for (const [k, v] of this.#res.headers.entries()) { - if (k === 'set-cookie') { - const cookies = this.#res.headers.getSetCookie() - _res.headers.delete('set-cookie') - for (const cookie of cookies) { - _res.headers.append('set-cookie', cookie) - } - } else { - _res.headers.set(k, v) - } - } - } - this.#res = _res - this.finalized = true - } - - /** - * `.render()` can create a response within a layout. - * @example - * ```ts - * app.get('/', (c) => { - * return c.render('Hello!') - * }) - * ``` - * @see https://hono.dev/api/context#render-setrenderer - */ - render: Renderer = (...args) => this.renderer(...args) - - setLayout = ( - layout: Layout - ): Layout< - PropsForRenderer & { - Layout: Layout - } - > => (this.layout = layout) - - getLayout = () => this.layout - - /** - * `.setRenderer()` can set the layout in the custom middleware. - * @example - * ```tsx - * app.use('*', async (c, next) => { - * c.setRenderer((content) => { - * return c.html( - * - * - *

{content}

- * - * - * ) - * }) - * await next() - * }) - * ``` - * @see https://hono.dev/api/context#render-setrenderer - */ - setRenderer = (renderer: Renderer) => { - this.renderer = renderer - } - - /** - * `.header()` can set headers. - * @example - * ```ts - * app.get('/welcome', (c) => { - * // Set headers - * c.header('X-Message', 'Hello!') - * c.header('Content-Type', 'text/plain') - * - * return c.body('Thank you for coming') - * }) - * ``` - * @see https://hono.dev/api/context#body - */ - header = (name: string, value: string | undefined, options?: { append?: boolean }): void => { - // Clear the header - if (value === undefined) { - if (this.#headers) { - this.#headers.delete(name) - } else if (this.#preparedHeaders) { - delete this.#preparedHeaders[name.toLocaleLowerCase()] - } - if (this.finalized) { - this.res.headers.delete(name) - } - return - } - - if (options?.append) { - if (!this.#headers) { - this.#isFresh = false - this.#headers = new Headers(this.#preparedHeaders) - this.#preparedHeaders = {} - } - this.#headers.append(name, value) - } else { - if (this.#headers) { - this.#headers.set(name, value) - } else { - this.#preparedHeaders ??= {} - this.#preparedHeaders[name.toLowerCase()] = value - } - } - - if (this.finalized) { - if (options?.append) { - this.res.headers.append(name, value) - } else { - this.res.headers.set(name, value) - } - } - } - - status = (status: StatusCode): void => { - this.#isFresh = false - this.#status = status - } - - /** - * `.set()` can set the value specified by the key. - * @example - * ```ts - * app.use('*', async (c, next) => { - * c.set('message', 'Hono is cool!!') - * await next() - * }) - * ``` - * @see https://hono.dev/api/context#set-get -``` - */ - set: Set = (key: string, value: unknown) => { - this._var ??= {} - this._var[key as string] = value - } - - /** - * `.get()` can use the value specified by the key. - * @example - * ```ts - * app.get('/', (c) => { - * const message = c.get('message') - * return c.text(`The message is "${message}"`) - * }) - * ``` - * @see https://hono.dev/api/context#set-get - */ - get: Get = (key: string) => { - return this._var ? this._var[key] : undefined - } - - /** - * `.var` can access the value of a variable. - * @example - * ```ts - * const result = c.var.client.oneMethod() - * ``` - * @see https://hono.dev/api/context#var - */ - // c.var.propName is a read-only - get var(): Readonly< - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ContextVariableMap & (IsAny extends true ? Record : E['Variables']) - > { - return { ...this._var } as never - } - - newResponse: NewResponse = ( - data: Data | null, - arg?: StatusCode | ResponseInit, - headers?: HeaderRecord - ): Response => { - // Optimized - if (this.#isFresh && !headers && !arg && this.#status === 200) { - return new Response(data, { - headers: this.#preparedHeaders, - }) - } - - if (arg && typeof arg !== 'number') { - const header = new Headers(arg.headers) - if (this.#headers) { - // If the header is set by c.header() and arg.headers, c.header() will be prioritized. - this.#headers.forEach((v, k) => { - header.set(k, v) - }) - } - const headers = setHeaders(header, this.#preparedHeaders) - return new Response(data, { - headers, - status: arg.status ?? this.#status, - }) - } - - const status = typeof arg === 'number' ? arg : this.#status - this.#preparedHeaders ??= {} - - this.#headers ??= new Headers() - setHeaders(this.#headers, this.#preparedHeaders) - - if (this.#res) { - this.#res.headers.forEach((v, k) => { - if (k === 'set-cookie') { - this.#headers?.append(k, v) - } else { - this.#headers?.set(k, v) - } - }) - setHeaders(this.#headers, this.#preparedHeaders) - } - - headers ??= {} - for (const [k, v] of Object.entries(headers)) { - if (typeof v === 'string') { - this.#headers.set(k, v) - } else { - this.#headers.delete(k) - for (const v2 of v) { - this.#headers.append(k, v2) - } - } - } - - return new Response(data, { - status, - headers: this.#headers, - }) - } - - /** - * `.body()` can return the HTTP response. - * You can set headers with `.header()` and set HTTP status code with `.status`. - * This can also be set in `.text()`, `.json()` and so on. - * @example - * ```ts - * app.get('/welcome', (c) => { - * // Set headers - * c.header('X-Message', 'Hello!') - * c.header('Content-Type', 'text/plain') - * // Set HTTP status code - * c.status(201) - * - * // Return the response body - * return c.body('Thank you for coming') - * }) - * ``` - * @see https://hono.dev/api/context#body - */ - body: BodyRespond = ( - data: Data | null, - arg?: StatusCode | ResponseInit, - headers?: HeaderRecord - ): Response => { - return typeof arg === 'number' - ? this.newResponse(data, arg, headers) - : this.newResponse(data, arg) - } - - /** - * `.text()` can render text as `Content-Type:text/plain`. - * @example - * ```ts - * app.get('/say', (c) => { - * return c.text('Hello!') - * }) - * ``` - * @see https://hono.dev/api/context#text - */ - text: TextRespond = ( - text: string, - arg?: StatusCode | ResponseInit, - headers?: HeaderRecord - ): ReturnType => { - // If the header is empty, return Response immediately. - // Content-Type will be added automatically as `text/plain`. - if (!this.#preparedHeaders) { - if (this.#isFresh && !headers && !arg) { - // @ts-expect-error `Response` due to missing some types-only keys - return new Response(text) - } - this.#preparedHeaders = {} - } - this.#preparedHeaders['content-type'] = TEXT_PLAIN - // @ts-expect-error `Response` due to missing some types-only keys - return typeof arg === 'number' - ? this.newResponse(text, arg, headers) - : this.newResponse(text, arg) - } - - /** - * `.json()` can render JSON as `Content-Type:application/json`. - * @example - * ```ts - * app.get('/api', (c) => { - * return c.json({ message: 'Hello!' }) - * }) - * ``` - * @see https://hono.dev/api/context#json - */ - json: JSONRespond = , U extends StatusCode>( - object: T, - arg?: U | ResponseInit, - headers?: HeaderRecord - ): ReturnType => { - const body = JSON.stringify(object) - this.#preparedHeaders ??= {} - this.#preparedHeaders['content-type'] = 'application/json; charset=UTF-8' - /* eslint-disable @typescript-eslint/no-explicit-any */ - return ( - typeof arg === 'number' ? this.newResponse(body, arg, headers) : this.newResponse(body, arg) - ) as any - } - - html: HTMLRespond = ( - html: string | Promise, - arg?: StatusCode | ResponseInit, - headers?: HeaderRecord - ): Response | Promise => { - this.#preparedHeaders ??= {} - this.#preparedHeaders['content-type'] = 'text/html; charset=UTF-8' - - if (typeof html === 'object') { - if (!(html instanceof Promise)) { - html = (html as string).toString() // HtmlEscapedString object to string - } - if ((html as string | Promise) instanceof Promise) { - return (html as unknown as Promise) - .then((html) => resolveCallback(html, HtmlEscapedCallbackPhase.Stringify, false, {})) - .then((html) => { - return typeof arg === 'number' - ? this.newResponse(html, arg, headers) - : this.newResponse(html, arg) - }) - } - } - - return typeof arg === 'number' - ? this.newResponse(html as string, arg, headers) - : this.newResponse(html as string, arg) - } - - /** - * `.redirect()` can Redirect, default status code is 302. - * @example - * ```ts - * app.get('/redirect', (c) => { - * return c.redirect('/') - * }) - * app.get('/redirect-permanently', (c) => { - * return c.redirect('/', 301) - * }) - * ``` - * @see https://hono.dev/api/context#redirect - */ - redirect = (location: string, status: RedirectStatusCode = 302): Response => { - this.#headers ??= new Headers() - this.#headers.set('Location', location) - return this.newResponse(null, status) - } - - /** - * `.notFound()` can return the Not Found Response. - * @example - * ```ts - * app.get('/notfound', (c) => { - * return c.notFound() - * }) - * ``` - * @see https://hono.dev/api/context#notfound - */ - notFound = (): Response | Promise => { - return this.notFoundHandler(this) - } -} diff --git a/deno_dist/helper.ts b/deno_dist/helper.ts deleted file mode 100644 index a3dd6d265c..0000000000 --- a/deno_dist/helper.ts +++ /dev/null @@ -1,13 +0,0 @@ -// This file is for Deno to import helpers from `hono/helper.ts`. -export * from './helper/accepts/index.ts' -export * from './helper/adapter/index.ts' -export * from './helper/cookie/index.ts' -export * from './helper/css/index.ts' -export * from './helper/factory/index.ts' -export * from './helper/html/index.ts' -export * from './helper/streaming/index.ts' -export * from './helper/testing/index.ts' -export * from './helper/dev/index.ts' -export * from './adapter/deno/ssg.ts' -export * from './adapter/deno/websocket.ts' -export { decode as jwtDecode, sign as jwtSign, verify as jwtVerify } from './middleware/jwt/index.ts' diff --git a/deno_dist/helper/accepts/accepts.ts b/deno_dist/helper/accepts/accepts.ts deleted file mode 100644 index bdf64fd934..0000000000 --- a/deno_dist/helper/accepts/accepts.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type { Context } from '../../context.ts' - -export type AcceptHeader = - | 'Accept' - | 'Accept-Charset' - | 'Accept-Encoding' - | 'Accept-Language' - | 'Accept-Patch' - | 'Accept-Post' - | 'Accept-Ranges' - -export interface Accept { - type: string - params: Record - q: number -} - -export interface acceptsConfig { - header: AcceptHeader - supports: string[] - default: string -} - -export interface acceptsOptions extends acceptsConfig { - match?: (accepts: Accept[], config: acceptsConfig) => string -} - -export const parseAccept = (acceptHeader: string): Accept[] => { - // Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 - const accepts = acceptHeader.split(',') // ['text/html', 'application/xhtml+xml', 'application/xml;q=0.9', 'image/webp', '*/*;q=0.8'] - return accepts.map((accept) => { - const [type, ...params] = accept.trim().split(';') // ['application/xml', 'q=0.9'] - const q = params.find((param) => param.startsWith('q=')) // 'q=0.9' - return { - type, - params: params.reduce((acc, param) => { - const [key, value] = param.split('=') - return { ...acc, [key.trim()]: value.trim() } - }, {}), - q: q ? parseFloat(q.split('=')[1]) : 1, - } - }) -} - -export const defaultMatch = (accepts: Accept[], config: acceptsConfig): string => { - const { supports, default: defaultSupport } = config - const accept = accepts.sort((a, b) => b.q - a.q).find((accept) => supports.includes(accept.type)) - return accept ? accept.type : defaultSupport -} - -/** - * Match the accept header with the given options. - * @example - * ```ts - * app.get('/users', (c) => { - * const lang = accepts(c, { - * header: 'Accept-Language', - * supports: ['en', 'zh'], - * default: 'en', - * }) - * }) - * ``` - */ -export const accepts = (c: Context, options: acceptsOptions): string => { - const acceptHeader = c.req.header(options.header) - if (!acceptHeader) { - return options.default - } - const accepts = parseAccept(acceptHeader) - const match = options.match || defaultMatch - - return match(accepts, options) -} diff --git a/deno_dist/helper/accepts/index.ts b/deno_dist/helper/accepts/index.ts deleted file mode 100644 index e7723d2af6..0000000000 --- a/deno_dist/helper/accepts/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { accepts } from './accepts.ts' diff --git a/deno_dist/helper/adapter/index.ts b/deno_dist/helper/adapter/index.ts deleted file mode 100644 index c9e46833a0..0000000000 --- a/deno_dist/helper/adapter/index.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { Context } from '../../context.ts' - -export type Runtime = 'node' | 'deno' | 'bun' | 'workerd' | 'fastly' | 'edge-light' | 'other' - -export const env = , C extends Context = Context<{}>>( - c: C, - runtime?: Runtime -): T & C['env'] => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const global = globalThis as any - const globalEnv = global?.process?.env as T - - runtime ??= getRuntimeKey() - - const runtimeEnvHandlers: Record T> = { - bun: () => globalEnv, - node: () => globalEnv, - 'edge-light': () => globalEnv, - deno: () => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - return Deno.env.toObject() as T - }, - workerd: () => c.env, - // On Fastly Compute, you can use the ConfigStore to manage user-defined data. - fastly: () => ({} as T), - other: () => ({} as T), - } - - return runtimeEnvHandlers[runtime]() -} - -export const getRuntimeKey = (): Runtime => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const global = globalThis as any - - if (global?.Deno !== undefined) { - return 'deno' - } - if (global?.Bun !== undefined) { - return 'bun' - } - if (typeof global?.WebSocketPair === 'function') { - return 'workerd' - } - if (typeof global?.EdgeRuntime === 'string') { - return 'edge-light' - } - if (global?.fastly !== undefined) { - return 'fastly' - } - if (global?.process?.release?.name === 'node') { - return 'node' - } - - return 'other' -} diff --git a/deno_dist/helper/cookie/index.ts b/deno_dist/helper/cookie/index.ts deleted file mode 100644 index 7c3840fac4..0000000000 --- a/deno_dist/helper/cookie/index.ts +++ /dev/null @@ -1,125 +0,0 @@ -import type { Context } from '../../context.ts' -import { parse, parseSigned, serialize, serializeSigned } from '../../utils/cookie.ts' -import type { CookieOptions, Cookie, SignedCookie, CookiePrefixOptions } from '../../utils/cookie.ts' - -interface GetCookie { - (c: Context, key: string): string | undefined - (c: Context): Cookie - (c: Context, key: string, prefixOptions: CookiePrefixOptions): string | undefined -} - -interface GetSignedCookie { - (c: Context, secret: string | BufferSource, key: string): Promise - (c: Context, secret: string): Promise - ( - c: Context, - secret: string | BufferSource, - key: string, - prefixOptions: CookiePrefixOptions - ): Promise -} - -export const getCookie: GetCookie = (c, key?, prefix?: CookiePrefixOptions) => { - const cookie = c.req.raw.headers.get('Cookie') - if (typeof key === 'string') { - if (!cookie) { - return undefined - } - let finalKey = key - if (prefix === 'secure') { - finalKey = '__Secure-' + key - } else if (prefix === 'host') { - finalKey = '__Host-' + key - } - const obj = parse(cookie, finalKey) - return obj[finalKey] - } - if (!cookie) { - return {} - } - const obj = parse(cookie) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return obj as any -} - -export const getSignedCookie: GetSignedCookie = async ( - c, - secret, - key?, - prefix?: CookiePrefixOptions -) => { - const cookie = c.req.raw.headers.get('Cookie') - if (typeof key === 'string') { - if (!cookie) { - return undefined - } - let finalKey = key - if (prefix === 'secure') { - finalKey = '__Secure-' + key - } else if (prefix === 'host') { - finalKey = '__Host-' + key - } - const obj = await parseSigned(cookie, secret, finalKey) - return obj[finalKey] - } - if (!cookie) { - return {} - } - const obj = await parseSigned(cookie, secret) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return obj as any -} - -export const setCookie = (c: Context, name: string, value: string, opt?: CookieOptions): void => { - // Cookie names prefixed with __Secure- can be used only if they are set with the secure attribute. - // Cookie names prefixed with __Host- can be used only if they are set with the secure attribute, must have a path of / (meaning any path at the host) - // and must not have a Domain attribute. - // Read more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#cookie_prefixes' - let cookie - if (opt?.prefix === 'secure') { - cookie = serialize('__Secure-' + name, value, { path: '/', ...opt, secure: true }) - } else if (opt?.prefix === 'host') { - cookie = serialize('__Host-' + name, value, { - ...opt, - path: '/', - secure: true, - domain: undefined, - }) - } else { - cookie = serialize(name, value, { path: '/', ...opt }) - } - c.header('set-cookie', cookie, { append: true }) -} - -export const setSignedCookie = async ( - c: Context, - name: string, - value: string, - secret: string | BufferSource, - opt?: CookieOptions -): Promise => { - let cookie - if (opt?.prefix === 'secure') { - cookie = await serializeSigned('__Secure-' + name, value, secret, { - path: '/', - ...opt, - secure: true, - }) - } else if (opt?.prefix === 'host') { - cookie = await serializeSigned('__Host-' + name, value, secret, { - ...opt, - path: '/', - secure: true, - domain: undefined, - }) - } else { - cookie = await serializeSigned(name, value, secret, { path: '/', ...opt }) - } - c.header('set-cookie', cookie, { append: true }) -} - -export const deleteCookie = (c: Context, name: string, opt?: CookieOptions): string | undefined => { - const deletedCookie = getCookie(c, name) - setCookie(c, name, '', { ...opt, maxAge: 0 }) - return deletedCookie -} diff --git a/deno_dist/helper/css/common.ts b/deno_dist/helper/css/common.ts deleted file mode 100644 index aff38a2806..0000000000 --- a/deno_dist/helper/css/common.ts +++ /dev/null @@ -1,243 +0,0 @@ -// provide utility functions for css helper both on server and client -export const PSEUDO_GLOBAL_SELECTOR = ':-hono-global' -export const isPseudoGlobalSelectorRe = new RegExp(`^${PSEUDO_GLOBAL_SELECTOR}{(.*)}$`) -export const DEFAULT_STYLE_ID = 'hono-css' - -export const SELECTOR: unique symbol = Symbol() -export const CLASS_NAME: unique symbol = Symbol() -export const STYLE_STRING: unique symbol = Symbol() -export const SELECTORS: unique symbol = Symbol() -export const EXTERNAL_CLASS_NAMES: unique symbol = Symbol() -const CSS_ESCAPED: unique symbol = Symbol() - -export interface CssClassName { - [SELECTOR]: string - [CLASS_NAME]: string - [STYLE_STRING]: string - [SELECTORS]: CssClassName[] - [EXTERNAL_CLASS_NAMES]: string[] -} - -export const IS_CSS_ESCAPED = Symbol() - -interface CssEscapedString { - [CSS_ESCAPED]: string -} - -/** - * @experimental - * `rawCssString` is an experimental feature. - * The API might be changed. - */ -export const rawCssString = (value: string): CssEscapedString => { - return { - [CSS_ESCAPED]: value, - } -} - -/** - * Used the goober'code as a reference: - * https://github.com/cristianbote/goober/blob/master/src/core/to-hash.js - * MIT License, Copyright (c) 2019 Cristian Bote - */ -const toHash = (str: string): string => { - let i = 0, - out = 11 - while (i < str.length) { - out = (101 * out + str.charCodeAt(i++)) >>> 0 - } - return 'css-' + out -} - -const cssStringReStr: string = [ - '"(?:(?:\\\\[\\s\\S]|[^"\\\\])*)"', // double quoted string - // eslint-disable-next-line quotes - "'(?:(?:\\\\[\\s\\S]|[^'\\\\])*)'", // single quoted string -].join('|') -const minifyCssRe: RegExp = new RegExp( - [ - '(' + cssStringReStr + ')', // $1: quoted string - - '(?:' + - [ - '^\\s+', // head whitespace - '\\/\\*.*?\\*\\/\\s*', // multi-line comment - '\\/\\/.*\\n\\s*', // single-line comment - '\\s+$', // tail whitespace - ].join('|') + - ')', - - '\\s*;\\s*(}|$)\\s*', // $2: trailing semicolon - '\\s*([{};:,])\\s*', // $3: whitespace around { } : , ; - '(\\s)\\s+', // $4: 2+ spaces - ].join('|'), - 'g' -) - -export const minify = (css: string): string => { - return css.replace(minifyCssRe, (_, $1, $2, $3, $4) => $1 || $2 || $3 || $4 || '') -} - -type CssVariableBasicType = - | CssClassName - | CssEscapedString - | string - | number - | boolean - | null - | undefined -type CssVariableAsyncType = Promise -type CssVariableArrayType = (CssVariableBasicType | CssVariableAsyncType)[] -export type CssVariableType = CssVariableBasicType | CssVariableAsyncType | CssVariableArrayType - -export const buildStyleString = ( - strings: TemplateStringsArray, - values: CssVariableType[] -): [string, string, CssClassName[], string[]] => { - const selectors: CssClassName[] = [] - const externalClassNames: string[] = [] - - const label = strings[0].match(/^\s*\/\*(.*?)\*\//)?.[1] || '' - let styleString = '' - for (let i = 0, len = strings.length; i < len; i++) { - styleString += strings[i] - let vArray = values[i] - if (typeof vArray === 'boolean' || vArray === null || vArray === undefined) { - continue - } - - if (!Array.isArray(vArray)) { - vArray = [vArray] - } - for (let j = 0, len = vArray.length; j < len; j++) { - let value = vArray[j] - if (typeof value === 'boolean' || value === null || value === undefined) { - continue - } - if (typeof value === 'string') { - if (/([\\"'\/])/.test(value)) { - styleString += value.replace(/([\\"']|(?<=<)\/)/g, '\\$1') - } else { - styleString += value - } - } else if (typeof value === 'number') { - styleString += value - } else if ((value as CssEscapedString)[CSS_ESCAPED]) { - styleString += (value as CssEscapedString)[CSS_ESCAPED] - } else if ((value as CssClassName)[CLASS_NAME].startsWith('@keyframes ')) { - selectors.push(value as CssClassName) - styleString += ` ${(value as CssClassName)[CLASS_NAME].substring(11)} ` - } else { - if (strings[i + 1]?.match(/^\s*{/)) { - // assume this value is a class name - selectors.push(value as CssClassName) - value = `.${(value as CssClassName)[CLASS_NAME]}` - } else { - selectors.push(...(value as CssClassName)[SELECTORS]) - externalClassNames.push(...(value as CssClassName)[EXTERNAL_CLASS_NAMES]) - value = (value as CssClassName)[STYLE_STRING] - const valueLen = value.length - if (valueLen > 0) { - const lastChar = value[valueLen - 1] - if (lastChar !== ';' && lastChar !== '}') { - value += ';' - } - } - } - styleString += `${value || ''}` - } - } - } - - return [label, minify(styleString), selectors, externalClassNames] -} - -export const cssCommon = ( - strings: TemplateStringsArray, - values: CssVariableType[] -): CssClassName => { - let [label, thisStyleString, selectors, externalClassNames] = buildStyleString(strings, values) - const isPseudoGlobal = isPseudoGlobalSelectorRe.exec(thisStyleString) - if (isPseudoGlobal) { - thisStyleString = isPseudoGlobal[1] - } - const selector = (isPseudoGlobal ? PSEUDO_GLOBAL_SELECTOR : '') + toHash(label + thisStyleString) - const className = ( - isPseudoGlobal ? selectors.map((s) => s[CLASS_NAME]) : [selector, ...externalClassNames] - ).join(' ') - - return { - [SELECTOR]: selector, - [CLASS_NAME]: className, - [STYLE_STRING]: thisStyleString, - [SELECTORS]: selectors, - [EXTERNAL_CLASS_NAMES]: externalClassNames, - } -} - -export const cxCommon = ( - args: (string | boolean | null | undefined | CssClassName)[] -): (string | boolean | null | undefined | CssClassName)[] => { - for (let i = 0, len = args.length; i < len; i++) { - const arg = args[i] - if (typeof arg === 'string') { - args[i] = { - [SELECTOR]: '', - [CLASS_NAME]: '', - [STYLE_STRING]: '', - [SELECTORS]: [], - [EXTERNAL_CLASS_NAMES]: [arg], - } - } - } - - return args -} - -export const keyframesCommon = ( - strings: TemplateStringsArray, - ...values: CssVariableType[] -): CssClassName => { - const [label, styleString] = buildStyleString(strings, values) - return { - [SELECTOR]: '', - [CLASS_NAME]: `@keyframes ${toHash(label + styleString)}`, - [STYLE_STRING]: styleString, - [SELECTORS]: [], - [EXTERNAL_CLASS_NAMES]: [], - } -} - -type ViewTransitionType = { - (strings: TemplateStringsArray, values: CssVariableType[]): CssClassName - (content: CssClassName): CssClassName - (): CssClassName -} - -let viewTransitionNameIndex = 0 -export const viewTransitionCommon: ViewTransitionType = (( - strings: TemplateStringsArray | CssClassName | undefined, - values: CssVariableType[] -): CssClassName => { - if (!strings) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - strings = [`/* h-v-t ${viewTransitionNameIndex++} */`] as any - } - const content = Array.isArray(strings) - ? cssCommon(strings as TemplateStringsArray, values) - : (strings as CssClassName) - - const transitionName = content[CLASS_NAME] - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const res = cssCommon(['view-transition-name:', ''] as any, [transitionName]) - - content[CLASS_NAME] = PSEUDO_GLOBAL_SELECTOR + content[CLASS_NAME] - content[STYLE_STRING] = content[STYLE_STRING].replace( - /(?<=::view-transition(?:[a-z-]*)\()(?=\))/g, - transitionName - ) - res[CLASS_NAME] = res[SELECTOR] = transitionName - res[SELECTORS] = [...content[SELECTORS], content] - - return res -}) as ViewTransitionType diff --git a/deno_dist/helper/css/index.ts b/deno_dist/helper/css/index.ts deleted file mode 100644 index 33d53e6199..0000000000 --- a/deno_dist/helper/css/index.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { raw } from '../../helper/html/index.ts' -import { DOM_RENDERER } from '../../jsx/constants.ts' -import { createCssJsxDomObjects } from '../../jsx/dom/css.ts' -import type { HtmlEscapedCallback, HtmlEscapedString } from '../../utils/html.ts' -import type { CssClassName as CssClassNameCommon, CssVariableType } from './common.ts' -import { - SELECTOR, - CLASS_NAME, - STYLE_STRING, - SELECTORS, - PSEUDO_GLOBAL_SELECTOR, - DEFAULT_STYLE_ID, - cssCommon, - cxCommon, - keyframesCommon, - viewTransitionCommon, -} from './common.ts' -export { rawCssString } from './common.ts' - -type CssClassName = HtmlEscapedString & CssClassNameCommon - -type usedClassNameData = [ - Record, // class name to add - Record // class name already added -] - -interface CssType { - (strings: TemplateStringsArray, ...values: CssVariableType[]): Promise -} - -interface CxType { - ( - ...args: (CssClassName | Promise | string | boolean | null | undefined)[] - ): Promise -} - -interface KeyframesType { - (strings: TemplateStringsArray, ...values: CssVariableType[]): CssClassNameCommon -} - -interface ViewTransitionType { - (strings: TemplateStringsArray, ...values: CssVariableType[]): Promise - (content: Promise): Promise - (): Promise -} - -interface StyleType { - (args?: { children?: Promise }): HtmlEscapedString -} - -/** - * @experimental - * `createCssContext` is an experimental feature. - * The API might be changed. - */ -export const createCssContext = ({ id }: { id: Readonly }): DefaultContextType => { - const [cssJsxDomObject, StyleRenderToDom] = createCssJsxDomObjects({ id }) - - const contextMap: WeakMap = new WeakMap() - - const replaceStyleRe = new RegExp(`()`) - - const newCssClassNameObject = (cssClassName: CssClassNameCommon): Promise => { - const appendStyle: HtmlEscapedCallback = ({ buffer, context }): Promise | undefined => { - const [toAdd, added] = contextMap.get(context) as usedClassNameData - const names = Object.keys(toAdd) - - if (!names.length) { - return - } - - let stylesStr = '' - names.forEach((className) => { - added[className] = true - stylesStr += className.startsWith(PSEUDO_GLOBAL_SELECTOR) - ? toAdd[className] - : `${className[0] === '@' ? '' : '.'}${className}{${toAdd[className]}}` - }) - contextMap.set(context, [{}, added]) - - if (buffer && replaceStyleRe.test(buffer[0])) { - buffer[0] = buffer[0].replace(replaceStyleRe, (_, pre, post) => `${pre}${stylesStr}${post}`) - return - } - - const appendStyleScript = `` - if (buffer) { - buffer[0] = `${appendStyleScript}${buffer[0]}` - return - } - - return Promise.resolve(appendStyleScript) - } - - const addClassNameToContext: HtmlEscapedCallback = ({ context }) => { - if (!contextMap.get(context)) { - contextMap.set(context, [{}, {}]) - } - const [toAdd, added] = contextMap.get(context) as usedClassNameData - let allAdded = true - if (!added[cssClassName[SELECTOR]]) { - allAdded = false - toAdd[cssClassName[SELECTOR]] = cssClassName[STYLE_STRING] - } - cssClassName[SELECTORS].forEach( - ({ [CLASS_NAME]: className, [STYLE_STRING]: styleString }) => { - if (!added[className]) { - allAdded = false - toAdd[className] = styleString - } - } - ) - if (allAdded) { - return - } - - return Promise.resolve(raw('', [appendStyle])) - } - - const className = new String(cssClassName[CLASS_NAME]) as CssClassName - Object.assign(className, cssClassName) - ;(className as HtmlEscapedString).isEscaped = true - ;(className as HtmlEscapedString).callbacks = [addClassNameToContext] - const promise = Promise.resolve(className) - Object.assign(promise, cssClassName) - // eslint-disable-next-line @typescript-eslint/unbound-method - promise.toString = cssJsxDomObject.toString - return promise - } - - const css: CssType = (strings, ...values) => { - return newCssClassNameObject(cssCommon(strings, values)) - } - - const cx: CxType = (...args) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - args = cxCommon(args as any) as any - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return css(Array(args.length).fill('') as any, ...args) - } - - const keyframes = keyframesCommon - - const viewTransition: ViewTransitionType = (( - strings: TemplateStringsArray | Promise | undefined, - ...values: CssVariableType[] - ) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return newCssClassNameObject(viewTransitionCommon(strings as any, values)) - }) as ViewTransitionType - - const Style: StyleType = ({ children } = {}) => - children - ? raw(``) - : raw(``) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ;(Style as any)[DOM_RENDERER] = StyleRenderToDom - - return { - css, - cx, - keyframes, - viewTransition: viewTransition as ViewTransitionType, - Style, - } -} - -interface DefaultContextType { - css: CssType - cx: CxType - keyframes: KeyframesType - viewTransition: ViewTransitionType - Style: StyleType -} - -const defaultContext: DefaultContextType = createCssContext({ - id: DEFAULT_STYLE_ID, -}) - -/** - * @experimental - * `css` is an experimental feature. - * The API might be changed. - */ -export const css = defaultContext.css - -/** - * @experimental - * `cx` is an experimental feature. - * The API might be changed. - */ -export const cx = defaultContext.cx - -/** - * @experimental - * `keyframes` is an experimental feature. - * The API might be changed. - */ -export const keyframes = defaultContext.keyframes - -/** - * @experimental - * `viewTransition` is an experimental feature. - * The API might be changed. - */ -export const viewTransition = defaultContext.viewTransition - -/** - * @experimental - * `Style` is an experimental feature. - * The API might be changed. - */ -export const Style = defaultContext.Style diff --git a/deno_dist/helper/dev/index.ts b/deno_dist/helper/dev/index.ts deleted file mode 100644 index 7ba8613ee3..0000000000 --- a/deno_dist/helper/dev/index.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { Hono } from '../../hono.ts' -import type { Env, RouterRoute } from '../../types.ts' -import { getColorEnabled } from '../../utils/color.ts' -import { findTargetHandler, isMiddleware } from '../../utils/handler.ts' - -interface ShowRoutesOptions { - verbose?: boolean - colorize?: boolean -} - -interface RouteData { - path: string - method: string - name: string - isMiddleware: boolean -} - -const handlerName = (handler: Function): string => { - return handler.name || (isMiddleware(handler) ? '[middleware]' : '[handler]') -} - -export const inspectRoutes = (hono: Hono): RouteData[] => { - return hono.routes.map(({ path, method, handler }: RouterRoute) => { - const targetHandler = findTargetHandler(handler) - return { - path, - method, - name: handlerName(targetHandler), - isMiddleware: isMiddleware(targetHandler), - } - }) -} - -export const showRoutes = (hono: Hono, opts?: ShowRoutesOptions) => { - const colorEnabled = opts?.colorize ?? getColorEnabled() - const routeData: Record = {} - let maxMethodLength = 0 - let maxPathLength = 0 - - inspectRoutes(hono) - .filter(({ isMiddleware }) => opts?.verbose || !isMiddleware) - .map((route) => { - const key = `${route.method}-${route.path}` - ;(routeData[key] ||= []).push(route) - if (routeData[key].length > 1) { - return - } - maxMethodLength = Math.max(maxMethodLength, route.method.length) - maxPathLength = Math.max(maxPathLength, route.path.length) - return { method: route.method, path: route.path, routes: routeData[key] } - }) - .forEach((data) => { - if (!data) { - return - } - const { method, path, routes } = data - - const methodStr = colorEnabled ? `\x1b[32m${method}\x1b[0m` : method - console.log(`${methodStr} ${' '.repeat(maxMethodLength - method.length)} ${path}`) - - if (!opts?.verbose) { - return - } - - routes.forEach(({ name }) => { - console.log(`${' '.repeat(maxMethodLength + 3)} ${name}`) - }) - }) -} - -export const getRouterName = (app: Hono): string => { - app.router.match('GET', '/') - return app.router.name -} diff --git a/deno_dist/helper/factory/index.ts b/deno_dist/helper/factory/index.ts deleted file mode 100644 index 2478031a72..0000000000 --- a/deno_dist/helper/factory/index.ts +++ /dev/null @@ -1,241 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { Hono } from '../../hono.ts' -import type { Env, H, HandlerResponse, Input, MiddlewareHandler } from '../../types.ts' - -type InitApp = (app: Hono) => void - -interface CreateHandlersInterface { - = any>(handler1: H): [ - H - ] - // handler x2 - = any>( - handler1: H, - handler2: H - ): [H, H] - - // handler x3 - < - I extends Input = {}, - I2 extends Input = I, - I3 extends Input = I & I2, - R extends HandlerResponse = any - >( - handler1: H, - handler2: H, - handler3: H - ): [H, H, H] - - // handler x4 - < - I extends Input = {}, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - R extends HandlerResponse = any - >( - handler1: H, - handler2: H, - handler3: H, - handler4: H - ): [H, H, H, H] - - // handler x5 - < - I extends Input = {}, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - R extends HandlerResponse = any - >( - handler1: H, - handler2: H, - handler3: H, - handler4: H, - handler5: H - ): [H, H, H, H, H] - - // handler x6 - < - I extends Input = {}, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - R extends HandlerResponse = any - >( - handler1: H, - handler2: H, - handler3: H, - handler4: H, - handler5: H, - handler6: H - ): [H, H, H, H, H, H] - - // handler x7 - < - I extends Input = {}, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - I7 extends Input = I & I2 & I3 & I4 & I5 & I6, - R extends HandlerResponse = any - >( - handler1: H, - handler2: H, - handler3: H, - handler4: H, - handler5: H, - handler6: H, - handler7: H - ): [ - H, - H, - H, - H, - H, - H, - H - ] - - // handler x8 - < - I extends Input = {}, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - I7 extends Input = I & I2 & I3 & I4 & I5 & I6, - I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7, - R extends HandlerResponse = any - >( - handler1: H, - handler2: H, - handler3: H, - handler4: H, - handler5: H, - handler6: H, - handler7: H, - handler8: H - ): [ - H, - H, - H, - H, - H, - H, - H, - H - ] - - // handler x9 - < - I extends Input = {}, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - I7 extends Input = I & I2 & I3 & I4 & I5 & I6, - I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7, - I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8, - R extends HandlerResponse = any - >( - handler1: H, - handler2: H, - handler3: H, - handler4: H, - handler5: H, - handler6: H, - handler7: H, - handler8: H, - handler9: H - ): [ - H, - H, - H, - H, - H, - H, - H, - H, - H - ] - - // handler x10 - < - I extends Input = {}, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - I7 extends Input = I & I2 & I3 & I4 & I5 & I6, - I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7, - I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8, - I10 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8 & I9, - R extends HandlerResponse = any - >( - handler1: H, - handler2: H, - handler3: H, - handler4: H, - handler5: H, - handler6: H, - handler7: H, - handler8: H, - handler9: H, - handler10: H - ): [ - H, - H, - H, - H, - H, - H, - H, - H, - H, - H - ] -} - -export class Factory { - private initApp?: InitApp - - constructor(init?: { initApp?: InitApp }) { - this.initApp = init?.initApp - } - - /** - * @experimental - * `createApp` is an experimental feature. - */ - createApp = (): Hono => { - const app = new Hono() - if (this.initApp) { - this.initApp(app) - } - return app - } - - createMiddleware = (middleware: MiddlewareHandler) => middleware - - createHandlers: CreateHandlersInterface = (...handlers: any) => { - // @ts-expect-error this should not be typed - return handlers.filter((handler) => handler !== undefined) - } -} - -export const createFactory = (init?: { - initApp?: InitApp -}): Factory => new Factory(init) - -export const createMiddleware = ( - middleware: MiddlewareHandler -): MiddlewareHandler => createFactory().createMiddleware(middleware) diff --git a/deno_dist/helper/html/index.ts b/deno_dist/helper/html/index.ts deleted file mode 100644 index dc48e99bdd..0000000000 --- a/deno_dist/helper/html/index.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { escapeToBuffer, stringBufferToString, raw } from '../../utils/html.ts' -import type { StringBuffer, HtmlEscaped, HtmlEscapedString } from '../../utils/html.ts' - -export { raw } - -export const html = ( - strings: TemplateStringsArray, - ...values: unknown[] -): HtmlEscapedString | Promise => { - const buffer: StringBuffer = [''] - - for (let i = 0, len = strings.length - 1; i < len; i++) { - buffer[0] += strings[i] - - const children = - values[i] instanceof Array ? (values[i] as Array).flat(Infinity) : [values[i]] - for (let i = 0, len = children.length; i < len; i++) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const child = children[i] as any - if (typeof child === 'string') { - escapeToBuffer(child, buffer) - } else if (typeof child === 'number') { - ;(buffer[0] as string) += child - } else if (typeof child === 'boolean' || child === null || child === undefined) { - continue - } else if (typeof child === 'object' && (child as HtmlEscaped).isEscaped) { - if ((child as HtmlEscapedString).callbacks) { - buffer.unshift('', child) - } else { - const tmp = child.toString() - if (tmp instanceof Promise) { - buffer.unshift('', tmp) - } else { - buffer[0] += tmp - } - } - } else if (child instanceof Promise) { - buffer.unshift('', child) - } else { - escapeToBuffer(child.toString(), buffer) - } - } - } - buffer[0] += strings[strings.length - 1] - - return buffer.length === 1 ? raw(buffer[0]) : stringBufferToString(buffer) -} diff --git a/deno_dist/helper/ssg/index.ts b/deno_dist/helper/ssg/index.ts deleted file mode 100644 index 813a9d4d54..0000000000 --- a/deno_dist/helper/ssg/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from './ssg.ts' -export { - X_HONO_DISABLE_SSG_HEADER_KEY, - ssgParams, - isSSGContext, - disableSSG, - onlySSG, -} from './middleware.ts' diff --git a/deno_dist/helper/ssg/middleware.ts b/deno_dist/helper/ssg/middleware.ts deleted file mode 100644 index 4d81fc54bf..0000000000 --- a/deno_dist/helper/ssg/middleware.ts +++ /dev/null @@ -1,79 +0,0 @@ -import type { Context } from '../../context.ts' -import type { Env, MiddlewareHandler } from '../../types.ts' - -export const SSG_CONTEXT = 'HONO_SSG_CONTEXT' -export const X_HONO_DISABLE_SSG_HEADER_KEY = 'x-hono-disable-ssg' - -/** - * @deprecated - * Use `X_HONO_DISABLE_SSG_HEADER_KEY` instead. - * This constant will be removed in the next minor version. - */ -export const SSG_DISABLED_RESPONSE = (() => { - try { - return new Response('SSG is disabled', { - status: 404, - headers: { [X_HONO_DISABLE_SSG_HEADER_KEY]: 'true' }, - }) - } catch (e) { - return null - } -})() as Response - -interface SSGParam { - [key: string]: string -} -export type SSGParams = SSGParam[] - -interface SSGParamsMiddleware { - ( - generateParams: (c: Context) => SSGParams | Promise - ): MiddlewareHandler - (params: SSGParams): MiddlewareHandler -} - -export type AddedSSGDataRequest = Request & { - ssgParams?: SSGParams -} - -/** - * Define SSG Route - */ -export const ssgParams: SSGParamsMiddleware = (params) => async (c, next) => { - ;(c.req.raw as AddedSSGDataRequest).ssgParams = Array.isArray(params) ? params : await params(c) - await next() -} - -/** - * @experimental - * `isSSGContext` is an experimental feature. - * The API might be changed. - */ -export const isSSGContext = (c: Context): boolean => !!c.env?.[SSG_CONTEXT] - -/** - * @experimental - * `disableSSG` is an experimental feature. - * The API might be changed. - */ -export const disableSSG = (): MiddlewareHandler => - async function disableSSG(c, next) { - if (isSSGContext(c)) { - c.header(X_HONO_DISABLE_SSG_HEADER_KEY, 'true') - return c.notFound() - } - await next() - } - -/** - * @experimental - * `onlySSG` is an experimental feature. - * The API might be changed. - */ -export const onlySSG = (): MiddlewareHandler => - async function onlySSG(c, next) { - if (!isSSGContext(c)) { - return c.notFound() - } - await next() - } diff --git a/deno_dist/helper/ssg/ssg.ts b/deno_dist/helper/ssg/ssg.ts deleted file mode 100644 index 9fb1b5ab92..0000000000 --- a/deno_dist/helper/ssg/ssg.ts +++ /dev/null @@ -1,316 +0,0 @@ -import { replaceUrlParam } from '../../client/utils.ts' -import type { Hono } from '../../hono.ts' -import type { Env, Schema } from '../../types.ts' -import { createPool } from '../../utils/concurrent.ts' -import { getExtension } from '../../utils/mime.ts' -import type { AddedSSGDataRequest, SSGParams } from './middleware.ts' -import { X_HONO_DISABLE_SSG_HEADER_KEY, SSG_CONTEXT } from './middleware.ts' -import { joinPaths, dirname, filterStaticGenerateRoutes } from './utils.ts' - -const DEFAULT_CONCURRENCY = 2 // default concurrency for ssg - -/** - * @experimental - * `FileSystemModule` is an experimental feature. - * The API might be changed. - */ -export interface FileSystemModule { - writeFile(path: string, data: string | Uint8Array): Promise - mkdir(path: string, options: { recursive: boolean }): Promise -} - -/** - * @experimental - * `ToSSGResult` is an experimental feature. - * The API might be changed. - */ -export interface ToSSGResult { - success: boolean - files: string[] - error?: Error -} - -const generateFilePath = ( - routePath: string, - outDir: string, - mimeType: string, - extensionMap?: Record -) => { - const extension = determineExtension(mimeType, extensionMap) - - if (routePath.endsWith(`.${extension}`)) { - return joinPaths(outDir, routePath) - } - - if (routePath === '/') { - return joinPaths(outDir, `index.${extension}`) - } - if (routePath.endsWith('/')) { - return joinPaths(outDir, routePath, `index.${extension}`) - } - return joinPaths(outDir, `${routePath}.${extension}`) -} - -const parseResponseContent = async (response: Response): Promise => { - const contentType = response.headers.get('Content-Type') - - try { - if (contentType?.includes('text') || contentType?.includes('json')) { - return await response.text() - } else { - return await response.arrayBuffer() - } - } catch (error) { - throw new Error( - `Error processing response: ${error instanceof Error ? error.message : 'Unknown error'}` - ) - } -} - -export const defaultExtensionMap: Record = { - 'text/html': 'html', - 'text/xml': 'xml', - 'application/xml': 'xml', - 'application/yaml': 'yaml', -} - -const determineExtension = ( - mimeType: string, - userExtensionMap?: Record -): string => { - const extensionMap = userExtensionMap || defaultExtensionMap - if (mimeType in extensionMap) { - return extensionMap[mimeType] - } - return getExtension(mimeType) || 'html' -} - -export type BeforeRequestHook = (req: Request) => Request | false | Promise -export type AfterResponseHook = (res: Response) => Response | false | Promise -export type AfterGenerateHook = (result: ToSSGResult) => void | Promise - -export interface ToSSGOptions { - dir?: string - beforeRequestHook?: BeforeRequestHook - afterResponseHook?: AfterResponseHook - afterGenerateHook?: AfterGenerateHook - concurrency?: number - extensionMap?: Record -} - -/** - * @experimental - * `fetchRoutesContent` is an experimental feature. - * The API might be changed. - */ -export const fetchRoutesContent = function* < - E extends Env = Env, - S extends Schema = {}, - BasePath extends string = '/' ->( - app: Hono, - beforeRequestHook?: BeforeRequestHook, - afterResponseHook?: AfterResponseHook, - concurrency?: number -): Generator< - Promise< - | Generator< - Promise<{ routePath: string; mimeType: string; content: string | ArrayBuffer } | undefined> - > - | undefined - > -> { - const baseURL = 'http://localhost' - const pool = createPool({ concurrency }) - - for (const route of filterStaticGenerateRoutes(app)) { - // GET Route Info - const thisRouteBaseURL = new URL(route.path, baseURL).toString() - - let forGetInfoURLRequest = new Request(thisRouteBaseURL) as AddedSSGDataRequest - - // eslint-disable-next-line no-async-promise-executor - yield new Promise(async (resolveGetInfo, rejectGetInfo) => { - try { - if (beforeRequestHook) { - const maybeRequest = await beforeRequestHook(forGetInfoURLRequest) - if (!maybeRequest) { - resolveGetInfo(undefined) - return - } - forGetInfoURLRequest = maybeRequest as unknown as AddedSSGDataRequest - } - - await pool.run(() => app.fetch(forGetInfoURLRequest)) - - if (!forGetInfoURLRequest.ssgParams) { - if (isDynamicRoute(route.path)) { - resolveGetInfo(undefined) - return - } - forGetInfoURLRequest.ssgParams = [{}] - } - - const requestInit = { - method: forGetInfoURLRequest.method, - headers: forGetInfoURLRequest.headers, - } - - resolveGetInfo( - (function* () { - for (const param of forGetInfoURLRequest.ssgParams as SSGParams) { - // eslint-disable-next-line no-async-promise-executor - yield new Promise(async (resolveReq, rejectReq) => { - try { - const replacedUrlParam = replaceUrlParam(route.path, param) - let response = await pool.run(() => - app.request(replacedUrlParam, requestInit, { - [SSG_CONTEXT]: true, - }) - ) - if (response.headers.get(X_HONO_DISABLE_SSG_HEADER_KEY)) { - resolveReq(undefined) - return - } - if (afterResponseHook) { - const maybeResponse = await afterResponseHook(response) - if (!maybeResponse) { - resolveReq(undefined) - return - } - response = maybeResponse - } - const mimeType = - response.headers.get('Content-Type')?.split(';')[0] || 'text/plain' - const content = await parseResponseContent(response) - resolveReq({ - routePath: replacedUrlParam, - mimeType, - content, - }) - } catch (error) { - rejectReq(error) - } - }) - } - })() - ) - } catch (error) { - rejectGetInfo(error) - } - }) - } -} - -const isDynamicRoute = (path: string): boolean => { - return path.split('/').some((segment) => segment.startsWith(':') || segment.includes('*')) -} - -/** - * @experimental - * `saveContentToFile` is an experimental feature. - * The API might be changed. - */ -const createdDirs: Set = new Set() -export const saveContentToFile = async ( - data: Promise<{ routePath: string; content: string | ArrayBuffer; mimeType: string } | undefined>, - fsModule: FileSystemModule, - outDir: string, - extensionMap?: Record -): Promise => { - const awaitedData = await data - if (!awaitedData) { - return - } - const { routePath, content, mimeType } = awaitedData - const filePath = generateFilePath(routePath, outDir, mimeType, extensionMap) - const dirPath = dirname(filePath) - - if (!createdDirs.has(dirPath)) { - await fsModule.mkdir(dirPath, { recursive: true }) - createdDirs.add(dirPath) - } - if (typeof content === 'string') { - await fsModule.writeFile(filePath, content) - } else if (content instanceof ArrayBuffer) { - await fsModule.writeFile(filePath, new Uint8Array(content)) - } - return filePath -} - -/** - * @experimental - * `ToSSGInterface` is an experimental feature. - * The API might be changed. - */ -export interface ToSSGInterface { - ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - app: Hono, - fsModule: FileSystemModule, - options?: ToSSGOptions - ): Promise -} - -/** - * @experimental - * `ToSSGAdaptorInterface` is an experimental feature. - * The API might be changed. - */ -export interface ToSSGAdaptorInterface< - E extends Env = Env, - S extends Schema = {}, - BasePath extends string = '/' -> { - (app: Hono, options?: ToSSGOptions): Promise -} - -/** - * @experimental - * `toSSG` is an experimental feature. - * The API might be changed. - */ -export const toSSG: ToSSGInterface = async (app, fs, options) => { - let result: ToSSGResult | undefined - const getInfoPromises: Promise[] = [] - const savePromises: Promise[] = [] - try { - const outputDir = options?.dir ?? './static' - const concurrency = options?.concurrency ?? DEFAULT_CONCURRENCY - - const getInfoGen = fetchRoutesContent( - app, - options?.beforeRequestHook, - options?.afterResponseHook, - concurrency - ) - for (const getInfo of getInfoGen) { - getInfoPromises.push( - getInfo.then((getContentGen) => { - if (!getContentGen) { - return - } - for (const content of getContentGen) { - savePromises.push(saveContentToFile(content, fs, outputDir).catch((e) => e)) - } - }) - ) - } - await Promise.all(getInfoPromises) - const files: string[] = [] - for (const savePromise of savePromises) { - const fileOrError = await savePromise - if (typeof fileOrError === 'string') { - files.push(fileOrError) - } else if (fileOrError) { - throw fileOrError - } - } - result = { success: true, files } - } catch (error) { - const errorObj = error instanceof Error ? error : new Error(String(error)) - result = { success: false, files: [], error: errorObj } - } - await options?.afterGenerateHook?.(result) - return result -} diff --git a/deno_dist/helper/ssg/utils.ts b/deno_dist/helper/ssg/utils.ts deleted file mode 100644 index 3a37eb290f..0000000000 --- a/deno_dist/helper/ssg/utils.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type { Hono } from '../../hono.ts' -import { METHOD_NAME_ALL } from '../../router.ts' -import type { Env, RouterRoute } from '../../types.ts' -import { findTargetHandler, isMiddleware } from '../../utils/handler.ts' - -/** - * Get dirname - * @param path File Path - * @returns Parent dir path - */ -export const dirname = (path: string) => { - const splittedPath = path.split(/[\/\\]/) - return splittedPath.slice(0, -1).join('/') // Windows supports slash path -} - -const normalizePath = (path: string) => { - return path.replace(/(\\)/g, '/').replace(/\/$/g, '') -} - -const handleDotDot = (resultPaths: string[]) => { - if (resultPaths.length === 0) { - resultPaths.push('..') - } else { - resultPaths.pop() - } -} - -const handleNonDot = (path: string, resultPaths: string[]) => { - path = path.replace(/^\.(?!.)/, '') - if (path !== '') { - resultPaths.push(path) - } -} - -const handleSegments = (paths: string[], resultPaths: string[]) => { - for (const path of paths) { - // Handle `..` or `../` - if (path === '..') { - handleDotDot(resultPaths) - } else { - // Handle `.` or `./` - handleNonDot(path, resultPaths) - } - } -} - -export const joinPaths = (...paths: string[]) => { - paths = paths.map(normalizePath) - const resultPaths: string[] = [] - handleSegments(paths.join('/').split('/'), resultPaths) - return (paths[0][0] === '/' ? '/' : '') + resultPaths.join('/') -} - -interface FilterStaticGenerateRouteData { - path: string -} - -export const filterStaticGenerateRoutes = ( - hono: Hono -): FilterStaticGenerateRouteData[] => { - return hono.routes.reduce((acc, { method, handler, path }: RouterRoute) => { - const targetHandler = findTargetHandler(handler) - if (['GET', METHOD_NAME_ALL].includes(method) && !isMiddleware(targetHandler)) { - acc.push({ path }) - } - return acc - }, [] as FilterStaticGenerateRouteData[]) -} diff --git a/deno_dist/helper/streaming/index.ts b/deno_dist/helper/streaming/index.ts deleted file mode 100644 index c19ca0e79b..0000000000 --- a/deno_dist/helper/streaming/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { stream } from './stream.ts' -export type { SSEMessage } from './sse.ts' -export { streamSSE, SSEStreamingApi } from './sse.ts' -export { streamText } from './text.ts' diff --git a/deno_dist/helper/streaming/sse.ts b/deno_dist/helper/streaming/sse.ts deleted file mode 100644 index 4fa30be01e..0000000000 --- a/deno_dist/helper/streaming/sse.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { Context } from '../../context.ts' -import { StreamingApi } from '../../utils/stream.ts' - -export interface SSEMessage { - data: string - event?: string - id?: string - retry?: number -} - -export class SSEStreamingApi extends StreamingApi { - constructor(writable: WritableStream, readable: ReadableStream) { - super(writable, readable) - } - - async writeSSE(message: SSEMessage) { - const data = message.data - .split('\n') - .map((line) => { - return `data: ${line}` - }) - .join('\n') - - const sseData = - [ - message.event && `event: ${message.event}`, - data, - message.id && `id: ${message.id}`, - message.retry && `retry: ${message.retry}`, - ] - .filter(Boolean) - .join('\n') + '\n\n' - - await this.write(sseData) - } -} - -const run = async ( - stream: SSEStreamingApi, - cb: (stream: SSEStreamingApi) => Promise, - onError?: (e: Error, stream: SSEStreamingApi) => Promise -) => { - try { - await cb(stream) - } catch (e) { - if (e instanceof Error && onError) { - await onError(e, stream) - - await stream.writeSSE({ - event: 'error', - data: e.message, - }) - } else { - console.error(e) - } - } finally { - stream.close() - } -} - -export const streamSSE = ( - c: Context, - cb: (stream: SSEStreamingApi) => Promise, - onError?: (e: Error, stream: SSEStreamingApi) => Promise -): Response => { - const { readable, writable } = new TransformStream() - const stream = new SSEStreamingApi(writable, readable) - - c.header('Transfer-Encoding', 'chunked') - c.header('Content-Type', 'text/event-stream') - c.header('Cache-Control', 'no-cache') - c.header('Connection', 'keep-alive') - - run(stream, cb, onError) - - return c.newResponse(stream.responseReadable) -} diff --git a/deno_dist/helper/streaming/stream.ts b/deno_dist/helper/streaming/stream.ts deleted file mode 100644 index 34ce0ca3a8..0000000000 --- a/deno_dist/helper/streaming/stream.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { Context } from '../../context.ts' -import { StreamingApi } from '../../utils/stream.ts' - -export const stream = ( - c: Context, - cb: (stream: StreamingApi) => Promise, - onError?: (e: Error, stream: StreamingApi) => Promise -): Response => { - const { readable, writable } = new TransformStream() - const stream = new StreamingApi(writable, readable) - ;(async () => { - try { - await cb(stream) - } catch (e) { - if (e instanceof Error && onError) { - await onError(e, stream) - } else { - console.error(e) - } - } finally { - stream.close() - } - })() - return c.newResponse(stream.responseReadable) -} diff --git a/deno_dist/helper/streaming/text.ts b/deno_dist/helper/streaming/text.ts deleted file mode 100644 index fbdd8a5fe8..0000000000 --- a/deno_dist/helper/streaming/text.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { Context } from '../../context.ts' -import { TEXT_PLAIN } from '../../context.ts' -import type { StreamingApi } from '../../utils/stream.ts' -import { stream } from './index.ts' - -export const streamText = ( - c: Context, - cb: (stream: StreamingApi) => Promise, - onError?: (e: Error, stream: StreamingApi) => Promise -): Response => { - c.header('Content-Type', TEXT_PLAIN) - c.header('X-Content-Type-Options', 'nosniff') - c.header('Transfer-Encoding', 'chunked') - return stream(c, cb, onError) -} diff --git a/deno_dist/helper/testing/index.ts b/deno_dist/helper/testing/index.ts deleted file mode 100644 index b542ef5dd6..0000000000 --- a/deno_dist/helper/testing/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { hc } from '../../client/index.ts' -import type { Client } from '../../client/types.ts' -import type { ExecutionContext } from '../../context.ts' -import type { Hono } from '../../hono.ts' -import type { UnionToIntersection } from '../../utils/types.ts' - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type ExtractEnv = T extends Hono ? E : never - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const testClient = >( - app: T, - Env?: ExtractEnv['Bindings'] | {}, - executionCtx?: ExecutionContext -): UnionToIntersection> => { - const customFetch = (input: RequestInfo | URL, init?: RequestInit) => { - return app.request(input, init, Env, executionCtx) - } - - return hc('http://localhost', { fetch: customFetch }) -} diff --git a/deno_dist/helper/websocket/index.ts b/deno_dist/helper/websocket/index.ts deleted file mode 100644 index f8089f6304..0000000000 --- a/deno_dist/helper/websocket/index.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import type { Context } from '../../context.ts' -import type { MiddlewareHandler } from '../../types.ts' - -/** - * WebSocket Event Listeners type - */ -export interface WSEvents { - onOpen?: (evt: Event, ws: WSContext) => void - onMessage?: (evt: MessageEvent, ws: WSContext) => void - onClose?: (evt: CloseEvent, ws: WSContext) => void - onError?: (evt: Event, ws: WSContext) => void -} - -/** - * Upgrade WebSocket Type - */ -export type UpgradeWebSocket = ( - createEvents: (c: Context) => WSEvents | Promise -) => MiddlewareHandler< - any, - string, - { - outputFormat: 'ws' - } -> - -export type WSReadyState = 0 | 1 | 2 | 3 - -export type WSContext = { - send( - source: string | ArrayBuffer | Uint8Array, - options?: { - compress: boolean - } - ): void - raw?: unknown - binaryType: BinaryType - readyState: WSReadyState - url: URL | null - protocol: string | null - close(code?: number, reason?: string): void -} - -export type WSMessageReceive = string | Blob | ArrayBufferLike - -export const createWSMessageEvent = (source: WSMessageReceive): MessageEvent => { - return new MessageEvent('message', { - data: source, - }) -} diff --git a/deno_dist/hono-base.ts b/deno_dist/hono-base.ts deleted file mode 100644 index 508b46234e..0000000000 --- a/deno_dist/hono-base.ts +++ /dev/null @@ -1,425 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { compose } from './compose.ts' -import { Context } from './context.ts' -import type { ExecutionContext } from './context.ts' -import { HTTPException } from './http-exception.ts' -import { HonoRequest } from './request.ts' -import type { Router } from './router.ts' -import { METHOD_NAME_ALL, METHOD_NAME_ALL_LOWERCASE, METHODS } from './router.ts' -import type { - Env, - ErrorHandler, - H, - HandlerInterface, - MiddlewareHandler, - MiddlewareHandlerInterface, - Next, - NotFoundHandler, - OnHandlerInterface, - MergePath, - MergeSchemaPath, - FetchEventLike, - Schema, - RouterRoute, -} from './types.ts' -import { getPath, getPathNoStrict, getQueryStrings, mergePath } from './utils/url.ts' - -export const COMPOSED_HANDLER = Symbol('composedHandler') - -const notFoundHandler = (c: Context) => { - return c.text('404 Not Found', 404) -} - -const errorHandler = (err: Error, c: Context) => { - if (err instanceof HTTPException) { - return err.getResponse() - } - console.error(err) - return c.text('Internal Server Error', 500) -} - -type GetPath = (request: Request, options?: { env?: E['Bindings'] }) => string - -export type HonoOptions = { - /** - * `strict` option specifies whether to distinguish whether the last path is a directory or not. - * @default true - * @see https://hono.dev/api/hono#strict-mode - */ - strict?: boolean - /** - * `router` option specifices which router to use. - * ```ts - * const app = new Hono({ router: new RegExpRouter() }) - * ``` - * @see https://hono.dev/api/hono#router-option - */ - router?: Router<[H, RouterRoute]> - /** - * `getPath` can handle the host header value. - * @example - * ```ts - * const app = new Hono({ - * getPath: (req) => - * '/' + req.headers.get('host') + req.url.replace(/^https?:\/\/[^/]+(\/[^?]*)/, '$1'), - * }) - * - * app.get('/www1.example.com/hello', () => c.text('hello www1')) - * - * // A following request will match the route: - * // new Request('http://www1.example.com/hello', { - * // headers: { host: 'www1.example.com' }, - * // }) - * ``` - * @see https://hono.dev/api/routing#routing-with-host-header-value - */ - getPath?: GetPath -} - -abstract class SuperClass< - E extends Env = Env, - S extends Schema = {}, - BasePath extends string = '/' -> { - // The implementations of these methods are fake. - get: HandlerInterface = () => new Hono() - post: HandlerInterface = () => new Hono() - put: HandlerInterface = () => new Hono() - delete: HandlerInterface = () => new Hono() - options: HandlerInterface = () => new Hono() - patch: HandlerInterface = () => new Hono() - all: HandlerInterface = () => new Hono() - on: OnHandlerInterface = () => new Hono() - use: MiddlewareHandlerInterface = () => new Hono() -} - -class Hono< - E extends Env = Env, - S extends Schema = {}, - BasePath extends string = '/' -> extends SuperClass { - /* - This class is like an abstract class and does not have a router. - To use it, inherit the class and implement router in the constructor. - */ - router!: Router<[H, RouterRoute]> - readonly getPath: GetPath - // Cannot use `#` because it requires visibility at JavaScript runtime. - private _basePath: string = '/' - #path: string = '/' - - routes: RouterRoute[] = [] - - constructor(options: HonoOptions = {}) { - super() - - // Implementation of app.get(...handlers[]) or app.get(path, ...handlers[]) - const allMethods = [...METHODS, METHOD_NAME_ALL_LOWERCASE] - allMethods.forEach((method) => { - this[method] = (args1: string | H, ...args: H[]) => { - if (typeof args1 === 'string') { - this.#path = args1 - } else { - this.addRoute(method, this.#path, args1) - } - args.forEach((handler) => { - if (typeof handler !== 'string') { - this.addRoute(method, this.#path, handler) - } - }) - return this as any - } - }) - - // Implementation of app.on(method, path, ...handlers[]) - this.on = (method: string | string[], path: string | string[], ...handlers: H[]) => { - if (!method) { - return this - } - for (const p of [path].flat()) { - this.#path = p - for (const m of [method].flat()) { - handlers.map((handler) => { - this.addRoute(m.toUpperCase(), this.#path, handler) - }) - } - } - return this as any - } - - // Implementation of app.use(...handlers[]) or app.use(path, ...handlers[]) - this.use = (arg1: string | MiddlewareHandler, ...handlers: MiddlewareHandler[]) => { - if (typeof arg1 === 'string') { - this.#path = arg1 - } else { - this.#path = '*' - handlers.unshift(arg1) - } - handlers.forEach((handler) => { - this.addRoute(METHOD_NAME_ALL, this.#path, handler) - }) - return this as any - } - - const strict = options.strict ?? true - delete options.strict - Object.assign(this, options) - this.getPath = strict ? options.getPath ?? getPath : getPathNoStrict - } - - private clone(): Hono { - const clone = new Hono({ - router: this.router, - getPath: this.getPath, - }) - clone.routes = this.routes - return clone - } - - private notFoundHandler: NotFoundHandler = notFoundHandler - private errorHandler: ErrorHandler = errorHandler - - route< - SubPath extends string, - SubEnv extends Env, - SubSchema extends Schema, - SubBasePath extends string - >( - path: SubPath, - app?: Hono - ): Hono> & S, BasePath> { - const subApp = this.basePath(path) - - if (!app) { - return subApp - } - - app.routes.map((r) => { - let handler - if (app.errorHandler === errorHandler) { - handler = r.handler - } else { - handler = async (c: Context, next: Next) => - (await compose([], app.errorHandler)(c, () => r.handler(c, next))).res - ;(handler as any)[COMPOSED_HANDLER] = r.handler - } - - subApp.addRoute(r.method, r.path, handler) - }) - return this - } - - /** - * `.basePath()` allows base paths to be specified. - * @example - * ```ts - * const api = new Hono().basePath('/api') - * ``` - * @see https://hono.dev/api/routing#base-path - */ - basePath(path: SubPath): Hono> { - const subApp = this.clone() - subApp._basePath = mergePath(this._basePath, path) - return subApp - } - - /** - * `.onError()` handles an error and returns a customized Response. - * ```ts - * app.onError((err, c) => { - * console.error(`${err}`) - * return c.text('Custom Error Message', 500) - * }) - * ``` - */ - onError = (handler: ErrorHandler): Hono => { - this.errorHandler = handler - return this - } - - /** - * `.notFound()` allows you to customize a Not Found Response. - * ```ts - * app.notFound((c) => { - * return c.text('Custom 404 Message', 404) - * }) - * ``` - * @see https://hono.dev/api/hono#not-found - */ - notFound = (handler: NotFoundHandler): Hono => { - this.notFoundHandler = handler - return this - } - - mount( - path: string, - applicationHandler: (request: Request, ...args: any) => Response | Promise, - optionHandler?: (c: Context) => unknown - ): Hono { - const mergedPath = mergePath(this._basePath, path) - const pathPrefixLength = mergedPath === '/' ? 0 : mergedPath.length - - const handler: MiddlewareHandler = async (c, next) => { - let executionContext: ExecutionContext | undefined = undefined - try { - executionContext = c.executionCtx - } catch {} // Do nothing - const options = optionHandler ? optionHandler(c) : [c.env, executionContext] - const optionsArray = Array.isArray(options) ? options : [options] - - const queryStrings = getQueryStrings(c.req.url) - const res = await applicationHandler( - new Request( - new URL((c.req.path.slice(pathPrefixLength) || '/') + queryStrings, c.req.url), - c.req.raw - ), - ...optionsArray - ) - - if (res) { - return res - } - - await next() - } - this.addRoute(METHOD_NAME_ALL, mergePath(path, '*'), handler) - return this - } - - private addRoute(method: string, path: string, handler: H) { - method = method.toUpperCase() - path = mergePath(this._basePath, path) - const r: RouterRoute = { path: path, method: method, handler: handler } - this.router.add(method, path, [handler, r]) - this.routes.push(r) - } - - private matchRoute(method: string, path: string) { - return this.router.match(method, path) - } - - private handleError(err: unknown, c: Context) { - if (err instanceof Error) { - return this.errorHandler(err, c) - } - throw err - } - - private dispatch( - request: Request, - executionCtx: ExecutionContext | FetchEventLike | undefined, - env: E['Bindings'], - method: string - ): Response | Promise { - // Handle HEAD method - if (method === 'HEAD') { - return (async () => - new Response(null, await this.dispatch(request, executionCtx, env, 'GET')))() - } - - const path = this.getPath(request, { env }) - const matchResult = this.matchRoute(method, path) - - const c = new Context(new HonoRequest(request, path, matchResult), { - env, - executionCtx, - notFoundHandler: this.notFoundHandler, - }) - - // Do not `compose` if it has only one handler - if (matchResult[0].length === 1) { - let res: ReturnType - try { - res = matchResult[0][0][0][0](c, async () => { - c.res = await this.notFoundHandler(c) - }) - } catch (err) { - return this.handleError(err, c) - } - - 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(matchResult[0], this.errorHandler, this.notFoundHandler) - - return (async () => { - try { - const context = await composed(c) - 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) - } - })() - } - - /** - * `.fetch()` will be entry point of your app. - * @see https://hono.dev/api/hono#fetch - */ - fetch: ( - request: Request, - Env?: E['Bindings'] | {}, - executionCtx?: ExecutionContext - ) => Response | Promise = (request, ...rest) => { - return this.dispatch(request, rest[1], rest[0], request.method) - } - - /** - * `.request()` is a useful method for testing. - * You can pass a URL or pathname to send a GET request. - * app will return a Response object. - * ```ts - * test('GET /hello is ok', async () => { - * const res = await app.request('/hello') - * expect(res.status).toBe(200) - * }) - * ``` - * @see https://hono.dev/api/hono#request - */ - request = ( - input: RequestInfo | URL, - requestInit?: RequestInit, - Env?: E['Bindings'] | {}, - executionCtx?: ExecutionContext - ): Response | Promise => { - if (input instanceof Request) { - if (requestInit !== undefined) { - input = new Request(input, requestInit) - } - return this.fetch(input, Env, executionCtx) - } - input = input.toString() - const path = /^https?:\/\//.test(input) ? input : `http://localhost${mergePath('/', input)}` - const req = new Request(path, requestInit) - return this.fetch(req, Env, executionCtx) - } - - /** - * `.fire()` automatically adds a global fetch event listener. - * This can be useful for environments that adhere to the Service Worker API, such as non-ES module Cloudflare Workers. - * @see https://hono.dev/api/hono#fire - * @see https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API - * @see https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/ - */ - fire = () => { - // @ts-expect-error `event` is not the type expected by addEventListener - addEventListener('fetch', (event: FetchEventLike): void => { - event.respondWith(this.dispatch(event.request, event, undefined, event.request.method)) - }) - } -} - -export { Hono as HonoBase } diff --git a/deno_dist/hono.ts b/deno_dist/hono.ts deleted file mode 100644 index 522c189680..0000000000 --- a/deno_dist/hono.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { HonoBase } from './hono-base.ts' -import type { HonoOptions } from './hono-base.ts' -import { RegExpRouter } from './router/reg-exp-router/index.ts' -import { SmartRouter } from './router/smart-router/index.ts' -import { TrieRouter } from './router/trie-router/index.ts' -import type { BlankSchema, Env, Schema } from './types.ts' - -export class Hono< - E extends Env = Env, - S extends Schema = BlankSchema, - BasePath extends string = '/' -> extends HonoBase { - constructor(options: HonoOptions = {}) { - super(options) - this.router = - options.router ?? - new SmartRouter({ - routers: [new RegExpRouter(), new TrieRouter()], - }) - } -} diff --git a/deno_dist/http-exception.ts b/deno_dist/http-exception.ts deleted file mode 100644 index 6b29559d9b..0000000000 --- a/deno_dist/http-exception.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { StatusCode } from './utils/http-status.ts' - -type HTTPExceptionOptions = { - res?: Response - message?: string - cause?: unknown -} - -/** - * `HTTPException` must be used when a fatal error such as authentication failure occurs. - * @example - * ```ts - * import { HTTPException } from 'hono/http-exception' - * - * // ... - * - * app.post('/auth', async (c, next) => { - * // authentication - * if (authorized === false) { - * throw new HTTPException(401, { message: 'Custom error message' }) - * } - * await next() - * }) - * ``` - * @see https://hono.dev/api/exception - */ -export class HTTPException extends Error { - readonly res?: Response - readonly status: StatusCode - - constructor(status: StatusCode = 500, options?: HTTPExceptionOptions) { - super(options?.message, { cause: options?.cause }) - this.res = options?.res - this.status = status - } - - getResponse(): Response { - if (this.res) { - return this.res - } - return new Response(this.message, { - status: this.status, - }) - } -} diff --git a/deno_dist/jsx/base.ts b/deno_dist/jsx/base.ts deleted file mode 100644 index a2a31b3797..0000000000 --- a/deno_dist/jsx/base.ts +++ /dev/null @@ -1,360 +0,0 @@ -import { raw } from '../helper/html/index.ts' -import { escapeToBuffer, stringBufferToString } from '../utils/html.ts' -import type { StringBuffer, HtmlEscaped, HtmlEscapedString } from '../utils/html.ts' -import type { Context } from './context.ts' -import { globalContexts } from './context.ts' -import type { IntrinsicElements as IntrinsicElementsDefined } from './intrinsic-elements.ts' -import type { Hono } from './intrinsic-elements.ts' -import { normalizeIntrinsicElementProps, styleObjectForEach } from './utils.ts' - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type Props = Record -export type FC

= { - (props: P): HtmlEscapedString | Promise - defaultProps?: Partial

| undefined - displayName?: string | undefined -} -export type DOMAttributes = Hono.HTMLAttributes - -// eslint-disable-next-line @typescript-eslint/no-namespace -export namespace JSX { - export type Element = HtmlEscapedString | Promise - export interface ElementChildrenAttribute { - children: Child - } - export interface IntrinsicElements extends IntrinsicElementsDefined { - [tagName: string]: Props - } -} - -const emptyTags = [ - 'area', - 'base', - 'br', - 'col', - 'embed', - 'hr', - 'img', - 'input', - 'keygen', - 'link', - 'meta', - 'param', - 'source', - 'track', - 'wbr', -] -const booleanAttributes = [ - 'allowfullscreen', - 'async', - 'autofocus', - 'autoplay', - 'checked', - 'controls', - 'default', - 'defer', - 'disabled', - 'formnovalidate', - 'hidden', - 'inert', - 'ismap', - 'itemscope', - 'loop', - 'multiple', - 'muted', - 'nomodule', - 'novalidate', - 'open', - 'playsinline', - 'readonly', - 'required', - 'reversed', - 'selected', -] - -const childrenToStringToBuffer = (children: Child[], buffer: StringBuffer): void => { - for (let i = 0, len = children.length; i < len; i++) { - const child = children[i] - if (typeof child === 'string') { - escapeToBuffer(child, buffer) - } else if (typeof child === 'boolean' || child === null || child === undefined) { - continue - } else if (child instanceof JSXNode) { - child.toStringToBuffer(buffer) - } else if ( - typeof child === 'number' || - (child as unknown as { isEscaped: boolean }).isEscaped - ) { - ;(buffer[0] as string) += child - } else if (child instanceof Promise) { - buffer.unshift('', child) - } else { - // `child` type is `Child[]`, so stringify recursively - childrenToStringToBuffer(child, buffer) - } - } -} - -type LocalContexts = [Context, unknown][] -export type Child = - | string - | Promise - | number - | JSXNode - | null - | undefined - | boolean - | Child[] -export class JSXNode implements HtmlEscaped { - tag: string | Function - props: Props - key?: string - children: Child[] - isEscaped: true = true as const - localContexts?: LocalContexts - constructor(tag: string | Function, props: Props, children: Child[]) { - this.tag = tag - this.props = props - this.children = children - } - - get type(): string | Function { - return this.tag as string - } - - // Added for compatibility with libraries that rely on React's internal structure - // eslint-disable-next-line @typescript-eslint/no-explicit-any - get ref(): any { - return this.props.ref || null - } - - toString(): string | Promise { - const buffer: StringBuffer = [''] - this.localContexts?.forEach(([context, value]) => { - context.values.push(value) - }) - try { - this.toStringToBuffer(buffer) - } finally { - this.localContexts?.forEach(([context]) => { - context.values.pop() - }) - } - return buffer.length === 1 ? buffer[0] : stringBufferToString(buffer) - } - - toStringToBuffer(buffer: StringBuffer): void { - const tag = this.tag as string - const props = this.props - let { children } = this - - buffer[0] += `<${tag}` - - const propsKeys = Object.keys(props || {}) - - for (let i = 0, len = propsKeys.length; i < len; i++) { - const key = propsKeys[i] - const v = props[key] - if (key === 'children') { - // skip children - } else if (key === 'style' && typeof v === 'object') { - // object to style strings - let styleStr = '' - styleObjectForEach(v, (property, value) => { - if (value != null) { - styleStr += `${styleStr ? ';' : ''}${property}:${value}` - } - }) - buffer[0] += ' style="' - escapeToBuffer(styleStr, buffer) - buffer[0] += '"' - } else if (typeof v === 'string') { - buffer[0] += ` ${key}="` - escapeToBuffer(v, buffer) - buffer[0] += '"' - } else if (v === null || v === undefined) { - // Do nothing - } else if (typeof v === 'number' || (v as HtmlEscaped).isEscaped) { - buffer[0] += ` ${key}="${v}"` - } else if (typeof v === 'boolean' && booleanAttributes.includes(key)) { - if (v) { - buffer[0] += ` ${key}=""` - } - } else if (key === 'dangerouslySetInnerHTML') { - if (children.length > 0) { - throw 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.' - } - - children = [raw(v.__html)] - } else if (v instanceof Promise) { - buffer[0] += ` ${key}="` - buffer.unshift('"', v) - } else if (typeof v === 'function') { - if (!key.startsWith('on')) { - throw `Invalid prop '${key}' of type 'function' supplied to '${tag}'.` - } - // maybe event handler for client components, just ignore in server components - } else { - buffer[0] += ` ${key}="` - escapeToBuffer(v.toString(), buffer) - buffer[0] += '"' - } - } - - if (emptyTags.includes(tag as string) && children.length === 0) { - buffer[0] += '/>' - return - } - - buffer[0] += '>' - - childrenToStringToBuffer(children, buffer) - - buffer[0] += `` - } -} - -class JSXFunctionNode extends JSXNode { - toStringToBuffer(buffer: StringBuffer): void { - const { children } = this - - const res = (this.tag as Function).call(null, { - ...this.props, - children: children.length <= 1 ? children[0] : children, - }) - - if (res instanceof Promise) { - if (globalContexts.length === 0) { - buffer.unshift('', res) - } else { - // save current contexts for resuming - const currentContexts: LocalContexts = globalContexts.map((c) => [c, c.values.at(-1)]) - buffer.unshift( - '', - res.then((childRes) => { - if (childRes instanceof JSXNode) { - childRes.localContexts = currentContexts - } - return childRes - }) - ) - } - } else if (res instanceof JSXNode) { - res.toStringToBuffer(buffer) - } else if (typeof res === 'number' || (res as HtmlEscaped).isEscaped) { - buffer[0] += res - } else { - escapeToBuffer(res, buffer) - } - } -} - -export class JSXFragmentNode extends JSXNode { - toStringToBuffer(buffer: StringBuffer): void { - childrenToStringToBuffer(this.children, buffer) - } -} - -export const jsx = ( - tag: string | Function, - props: Props | null, - ...children: (string | number | HtmlEscapedString)[] -): JSXNode => { - props ??= {} - if (children.length) { - props.children = children.length === 1 ? children[0] : children - } - - const key = props.key - delete props['key'] - - const node = jsxFn(tag, props, children) - node.key = key - return node -} - -export const jsxFn = ( - tag: string | Function, - props: Props, - children: (string | number | HtmlEscapedString)[] -): JSXNode => { - if (typeof tag === 'function') { - return new JSXFunctionNode(tag, props, children) - } else { - normalizeIntrinsicElementProps(props) - return new JSXNode(tag, props, children) - } -} - -const shallowEqual = (a: Props, b: Props): boolean => { - if (a === b) { - return true - } - - const aKeys = Object.keys(a).sort() - const bKeys = Object.keys(b).sort() - if (aKeys.length !== bKeys.length) { - return false - } - - for (let i = 0, len = aKeys.length; i < len; i++) { - if ( - aKeys[i] === 'children' && - bKeys[i] === 'children' && - !a.children?.length && - !b.children?.length - ) { - continue - } else if (a[aKeys[i]] !== b[aKeys[i]]) { - return false - } - } - - return true -} - -export const memo = ( - component: FC, - propsAreEqual: (prevProps: Readonly, nextProps: Readonly) => boolean = shallowEqual -): FC => { - let computed: HtmlEscapedString | Promise | undefined = undefined - let prevProps: T | undefined = undefined - return ((props: T & { children?: Child }): HtmlEscapedString | Promise => { - if (prevProps && !propsAreEqual(prevProps, props)) { - computed = undefined - } - prevProps = props - return (computed ||= component(props)) - }) as FC -} - -export const Fragment = ({ - children, -}: { - key?: string - children?: Child | HtmlEscapedString -}): HtmlEscapedString => { - return new JSXFragmentNode( - '', - { - children, - }, - Array.isArray(children) ? children : children ? [children] : [] - ) as never -} - -export const isValidElement = (element: unknown): element is JSXNode => { - return !!(element && typeof element === 'object' && 'tag' in element && 'props' in element) -} - -export const cloneElement = ( - element: T, - props: Partial, - ...children: Child[] -): T => { - return jsx( - (element as JSXNode).tag, - { ...(element as JSXNode).props, ...props }, - ...(children as (string | number | HtmlEscapedString)[]) - ) as T -} diff --git a/deno_dist/jsx/children.ts b/deno_dist/jsx/children.ts deleted file mode 100644 index 377bdeb3cd..0000000000 --- a/deno_dist/jsx/children.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Child } from './base.ts' - -export const toArray = (children: Child): Child[] => - Array.isArray(children) ? children : [children] -export const Children = { - map: (children: Child[], fn: (child: Child, index: number) => Child): Child[] => - toArray(children).map(fn), - forEach: (children: Child[], fn: (child: Child, index: number) => void): void => { - toArray(children).forEach(fn) - }, - count: (children: Child[]): number => toArray(children).length, - only: (_children: Child[]): Child => { - const children = toArray(_children) - if (children.length !== 1) { - throw new Error('Children.only() expects only one child') - } - return children[0] - }, - toArray, -} diff --git a/deno_dist/jsx/components.ts b/deno_dist/jsx/components.ts deleted file mode 100644 index 00ac0d99cf..0000000000 --- a/deno_dist/jsx/components.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { raw } from '../helper/html/index.ts' -import type { HtmlEscapedString, HtmlEscapedCallback } from '../utils/html.ts' -import { HtmlEscapedCallbackPhase, resolveCallback } from '../utils/html.ts' -import { DOM_RENDERER } from './constants.ts' -import { ErrorBoundary as ErrorBoundaryDomRenderer } from './dom/components.ts' -import type { HasRenderToDom } from './dom/render.ts' -import type { FC, PropsWithChildren, Child } from './index.ts' - -let errorBoundaryCounter = 0 - -export const childrenToString = async (children: Child[]): Promise => { - try { - return children - .flat() - .map((c) => (c == null || typeof c === 'boolean' ? '' : c.toString())) as HtmlEscapedString[] - } catch (e) { - if (e instanceof Promise) { - await e - return childrenToString(children) - } else { - throw e - } - } -} - -export type ErrorHandler = (error: Error) => void -export type FallbackRender = (error: Error) => Child - -/** - * @experimental - * `ErrorBoundary` is an experimental feature. - * The API might be changed. - */ -export const ErrorBoundary: FC< - PropsWithChildren<{ - fallback?: Child - fallbackRender?: FallbackRender - onError?: ErrorHandler - }> -> = async ({ children, fallback, fallbackRender, onError }) => { - if (!children) { - return raw('') - } - - if (!Array.isArray(children)) { - children = [children] - } - - let fallbackStr: string | undefined - const fallbackRes = (error: Error): HtmlEscapedString => { - onError?.(error) - return (fallbackStr || fallbackRender?.(error) || '').toString() as HtmlEscapedString - } - let resArray: HtmlEscapedString[] | Promise[] = [] - try { - resArray = children.map((c) => - c == null || typeof c === 'boolean' ? '' : c.toString() - ) as HtmlEscapedString[] - } catch (e) { - fallbackStr = await fallback?.toString() - if (e instanceof Promise) { - resArray = [ - e.then(() => childrenToString(children as Child[])).catch((e) => fallbackRes(e)), - ] as Promise[] - } else { - resArray = [fallbackRes(e as Error)] - } - } - - if (resArray.some((res) => (res as {}) instanceof Promise)) { - fallbackStr ||= await fallback?.toString() - const index = errorBoundaryCounter++ - const replaceRe = RegExp(`(.*?)(.*?)()`) - const caught = false - const catchCallback = ({ error, buffer }: { error: Error; buffer?: [string] }) => { - if (caught) { - return '' - } - - const fallbackResString = fallbackRes(error) - if (buffer) { - buffer[0] = buffer[0].replace(replaceRe, fallbackResString) - } - return buffer - ? '' - : `` - } - return raw(``, [ - ({ phase, buffer, context }) => { - if (phase === HtmlEscapedCallbackPhase.BeforeStream) { - return - } - return Promise.all(resArray) - .then(async (htmlArray) => { - htmlArray = htmlArray.flat() - const content = htmlArray.join('') - let html = buffer - ? '' - : `` - - if (htmlArray.every((html) => !(html as HtmlEscapedString).callbacks?.length)) { - if (buffer) { - buffer[0] = buffer[0].replace(replaceRe, content) - } - return html - } - - if (buffer) { - buffer[0] = buffer[0].replace( - replaceRe, - (_all, pre, _, post) => `${pre}${content}${post}` - ) - } - - const callbacks = htmlArray - .map((html) => (html as HtmlEscapedString).callbacks || []) - .flat() - - if (phase === HtmlEscapedCallbackPhase.Stream) { - html = await resolveCallback( - html, - HtmlEscapedCallbackPhase.BeforeStream, - true, - context - ) - } - - let resolvedCount = 0 - const promises = callbacks.map( - (c) => - (...args) => - c(...args) - ?.then((content) => { - resolvedCount++ - - if (buffer) { - if (resolvedCount === callbacks.length) { - buffer[0] = buffer[0].replace(replaceRe, (_all, _pre, content) => content) - } - buffer[0] += content - return raw('', (content as HtmlEscapedString).callbacks) - } - - return raw( - content + - (resolvedCount !== callbacks.length - ? '' - : ``), - (content as HtmlEscapedString).callbacks - ) - }) - .catch((error) => catchCallback({ error, buffer })) - ) - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return raw(html, promises as any) - }) - .catch((error) => catchCallback({ error, buffer })) - }, - ]) - } else { - return raw(resArray.join('')) - } -} -;(ErrorBoundary as HasRenderToDom)[DOM_RENDERER] = ErrorBoundaryDomRenderer diff --git a/deno_dist/jsx/constants.ts b/deno_dist/jsx/constants.ts deleted file mode 100644 index 1c5a37df73..0000000000 --- a/deno_dist/jsx/constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const DOM_RENDERER = Symbol('RENDERER') -export const DOM_ERROR_HANDLER = Symbol('ERROR_HANDLER') -export const DOM_STASH = Symbol('STASH') -export const DOM_INTERNAL_TAG = Symbol('INTERNAL') diff --git a/deno_dist/jsx/context.ts b/deno_dist/jsx/context.ts deleted file mode 100644 index 55a8a98dea..0000000000 --- a/deno_dist/jsx/context.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { raw } from '../helper/html/index.ts' -import type { HtmlEscapedString } from '../utils/html.ts' -import { JSXFragmentNode } from './base.ts' -import { DOM_RENDERER } from './constants.ts' -import { createContextProviderFunction } from './dom/context.ts' -import type { FC, PropsWithChildren } from './index.ts' - -export interface Context { - values: T[] - Provider: FC> -} - -export const globalContexts: Context[] = [] - -export const createContext = (defaultValue: T): Context => { - const values = [defaultValue] - const context: Context = { - values, - Provider(props): HtmlEscapedString | Promise { - values.push(props.value) - let string - try { - string = props.children - ? (Array.isArray(props.children) - ? new JSXFragmentNode('', {}, props.children) - : props.children - ).toString() - : '' - } finally { - values.pop() - } - - if (string instanceof Promise) { - return string.then((resString) => - raw(resString, (resString as HtmlEscapedString).callbacks) - ) - } else { - return raw(string) - } - }, - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ;(context.Provider as any)[DOM_RENDERER] = createContextProviderFunction(values) - - globalContexts.push(context as Context) - - return context -} - -export const useContext = (context: Context): T => { - return context.values.at(-1) as T -} diff --git a/deno_dist/jsx/dom/components.ts b/deno_dist/jsx/dom/components.ts deleted file mode 100644 index fe90977c27..0000000000 --- a/deno_dist/jsx/dom/components.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { FC, PropsWithChildren, Child } from '../index.ts' -import type { FallbackRender, ErrorHandler } from '../components.ts' -import { DOM_ERROR_HANDLER } from '../constants.ts' -import { Fragment } from './jsx-runtime.ts' - -/* eslint-disable @typescript-eslint/no-explicit-any */ -export const ErrorBoundary: FC< - PropsWithChildren<{ - fallback?: Child - fallbackRender?: FallbackRender - onError?: ErrorHandler - }> -> = (({ children, fallback, fallbackRender, onError }: any) => { - const res = Fragment({ children }) - ;(res as any)[DOM_ERROR_HANDLER] = (err: any) => { - if (err instanceof Promise) { - throw err - } - onError?.(err) - return fallbackRender?.(err) || fallback - } - return res -}) as any - -export const Suspense: FC> = (({ - children, - fallback, -}: any) => { - const res = Fragment({ children }) - ;(res as any)[DOM_ERROR_HANDLER] = (err: any, retry: () => void) => { - if (!(err instanceof Promise)) { - throw err - } - err.finally(retry) - return fallback - } - return res -}) as any -/* eslint-enable @typescript-eslint/no-explicit-any */ diff --git a/deno_dist/jsx/dom/context.ts b/deno_dist/jsx/dom/context.ts deleted file mode 100644 index 0089d052b7..0000000000 --- a/deno_dist/jsx/dom/context.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { Child } from '../base.ts' -import { DOM_ERROR_HANDLER } from '../constants.ts' -import type { Context } from '../context.ts' -import { globalContexts } from '../context.ts' -import { Fragment } from './jsx-runtime.ts' -import { setInternalTagFlag } from './utils.ts' - -export const createContextProviderFunction = (values: T[]): Function => - setInternalTagFlag(({ value, children }: { value: T; children: Child[] }) => { - if (!children) { - return undefined - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const props: { children: any } = { - children: [ - { - tag: setInternalTagFlag(() => { - values.push(value) - }), - props: {}, - }, - ], - } - if (Array.isArray(children)) { - props.children.push(...children.flat()) - } else { - props.children.push(children) - } - props.children.push({ - tag: setInternalTagFlag(() => { - values.pop() - }), - props: {}, - }) - const res = Fragment(props) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ;(res as any)[DOM_ERROR_HANDLER] = (err: unknown) => { - values.pop() - throw err - } - return res - }) - -export const createContext = (defaultValue: T): Context => { - const values = [defaultValue] - const context = { - values, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - Provider: createContextProviderFunction(values) as any, - } - globalContexts.push(context) - return context -} diff --git a/deno_dist/jsx/dom/css.ts b/deno_dist/jsx/dom/css.ts deleted file mode 100644 index 338c078d22..0000000000 --- a/deno_dist/jsx/dom/css.ts +++ /dev/null @@ -1,220 +0,0 @@ -import type { FC, PropsWithChildren } from '../index.ts' -import type { CssClassName, CssVariableType } from '../../helper/css/common.ts' -import { - SELECTOR, - CLASS_NAME, - STYLE_STRING, - SELECTORS, - PSEUDO_GLOBAL_SELECTOR, - DEFAULT_STYLE_ID, - cssCommon, - cxCommon, - keyframesCommon, - viewTransitionCommon, -} from '../../helper/css/common.ts' -export { rawCssString } from '../../helper/css/common.ts' - -const splitRule = (rule: string): string[] => { - const result: string[] = [] - let startPos = 0 - let depth = 0 - for (let i = 0, len = rule.length; i < len; i++) { - const char = rule[i] - - // consume quote - // eslint-disable-next-line quotes - if (char === "'" || char === '"') { - const quote = char - i++ - for (; i < len; i++) { - if (rule[i] === '\\') { - i++ - continue - } - if (rule[i] === quote) { - break - } - } - continue - } - - // comments are removed from the rule in advance - - if (char === '{') { - depth++ - continue - } - if (char === '}') { - depth-- - if (depth === 0) { - result.push(rule.slice(startPos, i + 1)) - startPos = i + 1 - } - continue - } - } - return result -} - -interface CreateCssJsxDomObjectsType { - (args: { id: Readonly }): readonly [ - { - toString(this: CssClassName): string - }, - FC> - ] -} - -export const createCssJsxDomObjects: CreateCssJsxDomObjectsType = ({ id }) => { - let styleSheet: CSSStyleSheet | null | undefined = undefined - const findStyleSheet = (): [CSSStyleSheet, Set] | [] => { - if (!styleSheet) { - styleSheet = document.querySelector(`style#${id}`) - ?.sheet as CSSStyleSheet | null - if (styleSheet) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ;(styleSheet as any).addedStyles = new Set() - } - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return styleSheet ? [styleSheet, (styleSheet as any).addedStyles] : [] - } - - const insertRule = (className: string, styleString: string) => { - const [sheet, addedStyles] = findStyleSheet() - if (!sheet || !addedStyles) { - Promise.resolve().then(() => { - if (!findStyleSheet()[0]) { - throw new Error('style sheet not found') - } - insertRule(className, styleString) - }) - return - } - - if (!addedStyles.has(className)) { - addedStyles.add(className) - ;(className.startsWith(PSEUDO_GLOBAL_SELECTOR) - ? splitRule(styleString) - : [`${className[0] === '@' ? '' : '.'}${className}{${styleString}}`] - ).forEach((rule) => { - sheet.insertRule(rule, sheet.cssRules.length) - }) - } - } - - const cssObject = { - toString(this: CssClassName): string { - const selector = this[SELECTOR] - insertRule(selector, this[STYLE_STRING]) - this[SELECTORS].forEach(({ [CLASS_NAME]: className, [STYLE_STRING]: styleString }) => { - insertRule(className, styleString) - }) - - return this[CLASS_NAME] - }, - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const Style: FC> = ({ children }) => - ({ - tag: 'style', - props: { - id, - children: - children && - (Array.isArray(children) ? children : [children]).map( - (c) => (c as unknown as CssClassName)[STYLE_STRING] - ), - }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any) - - return [cssObject, Style] as const -} - -/** - * @experimental - * `createCssContext` is an experimental feature. - * The API might be changed. - */ -export const createCssContext = ({ id }: { id: Readonly }) => { - const [cssObject, Style] = createCssJsxDomObjects({ id }) - - const newCssClassNameObject = (cssClassName: CssClassName): string => { - // eslint-disable-next-line @typescript-eslint/unbound-method - cssClassName.toString = cssObject.toString - return cssClassName as unknown as string - } - - const css = (strings: TemplateStringsArray, ...values: CssVariableType[]): string => { - return newCssClassNameObject(cssCommon(strings, values)) - } - - const cx = (...args: (string | boolean | null | undefined)[]): string => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - args = cxCommon(args as any) as any - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return css(Array(args.length).fill('') as any, ...args) - } - - const keyframes = keyframesCommon - - type ViewTransitionType = { - (strings: TemplateStringsArray, ...values: CssVariableType[]): string - (content: string): string - (): string - } - const viewTransition: ViewTransitionType = (( - strings: TemplateStringsArray | string | undefined, - ...values: CssVariableType[] - ) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return newCssClassNameObject(viewTransitionCommon(strings as any, values)) - }) as ViewTransitionType - - return { - css, - cx, - keyframes, - viewTransition, - Style, - } -} - -const defaultContext = createCssContext({ id: DEFAULT_STYLE_ID }) - -/** - * @experimental - * `css` is an experimental feature. - * The API might be changed. - */ -export const css = defaultContext.css - -/** - * @experimental - * `cx` is an experimental feature. - * The API might be changed. - */ -export const cx = defaultContext.cx - -/** - * @experimental - * `keyframes` is an experimental feature. - * The API might be changed. - */ -export const keyframes = defaultContext.keyframes - -/** - * @experimental - * `viewTransition` is an experimental feature. - * The API might be changed. - */ -export const viewTransition = defaultContext.viewTransition - -/** - * @experimental - * `Style` is an experimental feature. - * The API might be changed. - */ -export const Style = defaultContext.Style diff --git a/deno_dist/jsx/dom/index.ts b/deno_dist/jsx/dom/index.ts deleted file mode 100644 index 5a0f9f3f93..0000000000 --- a/deno_dist/jsx/dom/index.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { memo, isValidElement } from '../base.ts' -import type { Props, Child, DOMAttributes, JSXNode } from '../base.ts' -import type { JSX } from '../base.ts' -import { Children } from '../children.ts' -import { useContext } from '../context.ts' -import { - useState, - useEffect, - useRef, - useCallback, - use, - startTransition, - useTransition, - useDeferredValue, - startViewTransition, - useViewTransition, - useMemo, - useLayoutEffect, - useReducer, - useId, - useDebugValue, - createRef, - forwardRef, - useImperativeHandle, - useSyncExternalStore, -} from '../hooks/index.ts' -import { Suspense, ErrorBoundary } from './components.ts' -import { createContext } from './context.ts' -import { jsx, Fragment } from './jsx-runtime.ts' -import { flushSync, createPortal } from './render.ts' - -export { render } from './render.ts' - -const createElement = ( - tag: string | ((props: Props) => JSXNode), - props: Props | null, - ...children: Child[] -): JSXNode => { - const jsxProps: Props = props ? { ...props } : {} - if (children.length) { - jsxProps.children = children.length === 1 ? children[0] : children - } - - let key = undefined - if ('key' in jsxProps) { - key = jsxProps.key - delete jsxProps.key - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return jsx(tag, jsxProps, key) as any -} - -const cloneElement = ( - element: T, - props: Props, - ...children: Child[] -): T => { - return jsx( - (element as JSXNode).tag, - { - ...(element as JSXNode).props, - ...props, - children: children.length ? children : (element as JSXNode).props.children, - }, - (element as JSXNode).key - ) as T -} - -export { - createElement as jsx, - useState, - useEffect, - useRef, - useCallback, - use, - startTransition, - useTransition, - useDeferredValue, - startViewTransition, - useViewTransition, - useMemo, - useLayoutEffect, - useReducer, - useId, - useDebugValue, - createRef, - forwardRef, - useImperativeHandle, - useSyncExternalStore, - Suspense, - ErrorBoundary, - createContext, - useContext, - memo, - isValidElement, - createElement, - cloneElement, - Children, - Fragment, - DOMAttributes, - flushSync, - createPortal, -} - -export default { - useState, - useEffect, - useRef, - useCallback, - use, - startTransition, - useTransition, - useDeferredValue, - startViewTransition, - useViewTransition, - useMemo, - useLayoutEffect, - useReducer, - useId, - useDebugValue, - createRef, - forwardRef, - useImperativeHandle, - useSyncExternalStore, - Suspense, - ErrorBoundary, - createContext, - useContext, - memo, - isValidElement, - createElement, - cloneElement, - Children, - Fragment, - flushSync, - createPortal, -} - -export type { Context } from '../context.ts' - -// TODO: change to `export type *` after denoify bug is fixed -// https://github.com/garronej/denoify/issues/124 -export * from '../types.ts' diff --git a/deno_dist/jsx/dom/jsx-dev-runtime.ts b/deno_dist/jsx/dom/jsx-dev-runtime.ts deleted file mode 100644 index fc1837012e..0000000000 --- a/deno_dist/jsx/dom/jsx-dev-runtime.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { Props, JSXNode } from '../base.ts' -import { normalizeIntrinsicElementProps } from '../utils.ts' - -const JSXNodeCompatPrototype = { - type: { - get(this: { tag: string | Function }): string | Function { - return this.tag - }, - }, - ref: { - get(this: { props?: { ref: unknown } }): unknown { - return this.props?.ref - }, - }, -} - -export const jsxDEV = (tag: string | Function, props: Props, key?: string): JSXNode => { - if (typeof tag === 'string') { - normalizeIntrinsicElementProps(props) - } - return Object.defineProperties( - { - tag, - props, - key, - }, - JSXNodeCompatPrototype - ) as JSXNode -} - -export const Fragment = (props: Record): JSXNode => jsxDEV('', props, undefined) diff --git a/deno_dist/jsx/dom/jsx-runtime.ts b/deno_dist/jsx/dom/jsx-runtime.ts deleted file mode 100644 index 16a57c172e..0000000000 --- a/deno_dist/jsx/dom/jsx-runtime.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { jsxDEV as jsx, Fragment } from './jsx-dev-runtime.ts' -export { jsxDEV as jsxs } from './jsx-dev-runtime.ts' diff --git a/deno_dist/jsx/dom/render.ts b/deno_dist/jsx/dom/render.ts deleted file mode 100644 index efeefd6a0b..0000000000 --- a/deno_dist/jsx/dom/render.ts +++ /dev/null @@ -1,611 +0,0 @@ -import type { JSXNode } from '../base.ts' -import type { FC, Child, Props } from '../base.ts' -import { toArray } from '../children.ts' -import { DOM_RENDERER, DOM_ERROR_HANDLER, DOM_STASH, DOM_INTERNAL_TAG } from '../constants.ts' -import type { Context as JSXContext } from '../context.ts' -import { globalContexts as globalJSXContexts, useContext } from '../context.ts' -import type { EffectData } from '../hooks/index.ts' -import { STASH_EFFECT } from '../hooks/index.ts' -import { styleObjectForEach } from '../utils.ts' -import { createContext } from './context.ts' // import dom-specific versions - -const HONO_PORTAL_ELEMENT = '_hp' - -const eventAliasMap: Record = { - Change: 'Input', - DoubleClick: 'DblClick', -} as const - -const nameSpaceMap: Record = { - svg: 'http://www.w3.org/2000/svg', - math: 'http://www.w3.org/1998/Math/MathML', -} as const - -const skipProps = new Set(['children']) - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type HasRenderToDom = FC & { [DOM_RENDERER]: FC } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type ErrorHandler = (error: any, retry: () => void) => Child | undefined - -type Container = HTMLElement | DocumentFragment -type LocalJSXContexts = [JSXContext, unknown][] | undefined -type SupportedElement = HTMLElement | SVGElement | MathMLElement - -export type NodeObject = { - pP: Props | undefined // previous props - nN: Node | undefined // next node - vC: Node[] // virtual dom children - vR: Node[] // virtual dom children to remove - s?: Node[] // shadow virtual dom children - n?: string // namespace - c: Container | undefined // container - e: SupportedElement | Text | undefined // rendered element - [DOM_STASH]: - | [ - number, // current hook index - // eslint-disable-next-line @typescript-eslint/no-explicit-any - any[][], // stash for hooks - LocalJSXContexts // context - ] - | [ - number, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - any[][] - ] -} & JSXNode -type NodeString = { - t: string // text content - d: boolean // is dirty -} & { - e?: Text - // like a NodeObject - vC: undefined - nN: undefined - // from JSXNode - key: undefined - tag: undefined -} -export type Node = NodeString | NodeObject - -export type PendingType = - | 0 // no pending - | 1 // global - | 2 // hook -export type UpdateHook = ( - context: Context, - node: Node, - cb: (context: Context) => void -) => Promise -export type Context = - | [ - PendingType, // PendingType - boolean, // got an error - UpdateHook, // update hook - boolean, // is in view transition - boolean // is in top level render - ] - | [PendingType, boolean, UpdateHook, boolean] - | [PendingType, boolean, UpdateHook] - | [PendingType, boolean] - | [PendingType] - | [] - -export const buildDataStack: [Context, Node][] = [] - -let nameSpaceContext: JSXContext | undefined = undefined - -const isNodeString = (node: Node): node is NodeString => 't' in (node as NodeString) - -const getEventSpec = (key: string): [string, boolean] | undefined => { - const match = key.match(/^on([A-Z][a-zA-Z]+?(?:PointerCapture)?)(Capture)?$/) - if (match) { - const [, eventName, capture] = match - return [(eventAliasMap[eventName] || eventName).toLowerCase(), !!capture] - } - return undefined -} - -const applyProps = (container: SupportedElement, attributes: Props, oldAttributes?: Props) => { - attributes ||= {} - for (const [key, value] of Object.entries(attributes)) { - if (!skipProps.has(key) && (!oldAttributes || oldAttributes[key] !== value)) { - const eventSpec = getEventSpec(key) - if (eventSpec) { - if (oldAttributes) { - container.removeEventListener(eventSpec[0], oldAttributes[key], eventSpec[1]) - } - if (value != null) { - if (typeof value !== 'function') { - throw new Error(`Event handler for "${key}" is not a function`) - } - container.addEventListener(eventSpec[0], value, eventSpec[1]) - } - } else if (key === 'dangerouslySetInnerHTML' && value) { - container.innerHTML = value.__html - } else if (key === 'ref') { - if (typeof value === 'function') { - value(container) - } else if (value && 'current' in value) { - value.current = container - } - } else if (key === 'style') { - const style = container.style - if (typeof value === 'string') { - style.cssText = value - } else { - style.cssText = '' - if (value != null) { - styleObjectForEach(value, style.setProperty.bind(style)) - } - } - } else { - const nodeName = container.nodeName - if (key === 'value') { - if (nodeName === 'INPUT' || nodeName === 'TEXTAREA' || nodeName === 'SELECT') { - ;(container as HTMLInputElement).value = - value === null || value === undefined || value === false ? null : value - - if (nodeName === 'TEXTAREA') { - container.textContent = value - continue - } else if (nodeName === 'SELECT') { - if ((container as HTMLSelectElement).selectedIndex === -1) { - ;(container as HTMLSelectElement).selectedIndex = 0 - } - continue - } - } - } else if ( - (key === 'checked' && nodeName === 'INPUT') || - (key === 'selected' && nodeName === 'OPTION') - ) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ;(container as any)[key] = value - } - - if (value === null || value === undefined || value === false) { - container.removeAttribute(key) - } else if (value === true) { - container.setAttribute(key, '') - } else if (typeof value === 'string' || typeof value === 'number') { - container.setAttribute(key, value as string) - } else { - container.setAttribute(key, value.toString()) - } - } - } - } - if (oldAttributes) { - for (const [key, value] of Object.entries(oldAttributes)) { - if (!skipProps.has(key) && !(key in attributes)) { - const eventSpec = getEventSpec(key) - if (eventSpec) { - container.removeEventListener(eventSpec[0], value, eventSpec[1]) - } else if (key === 'ref') { - if (typeof value === 'function') { - value(null) - } else { - value.current = null - } - } else { - container.removeAttribute(key) - } - } - } - } -} - -const invokeTag = (context: Context, node: NodeObject): Child[] => { - if (node.s) { - const res = node.s - node.s = undefined - return res as Child[] - } - - node[DOM_STASH][0] = 0 - buildDataStack.push([context, node]) - const func = (node.tag as HasRenderToDom)[DOM_RENDERER] || node.tag - try { - return [ - func.call(null, { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ...((func as any).defaultProps || {}), - ...node.props, - }), - ] - } finally { - buildDataStack.pop() - } -} - -const getNextChildren = ( - node: NodeObject, - container: Container, - nextChildren: Node[], - childrenToRemove: Node[], - callbacks: EffectData[] -) => { - childrenToRemove.push(...node.vR) - if (typeof node.tag === 'function') { - node[DOM_STASH][1][STASH_EFFECT]?.forEach((data: EffectData) => callbacks.push(data)) - } - node.vC.forEach((child) => { - if (isNodeString(child)) { - nextChildren.push(child) - } else { - if (typeof child.tag === 'function' || child.tag === '') { - child.c = container - getNextChildren(child, container, nextChildren, childrenToRemove, callbacks) - } else { - nextChildren.push(child) - childrenToRemove.push(...child.vR) - } - } - }) -} - -const findInsertBefore = (node: Node | undefined): ChildNode | null => { - if (!node) { - return null - } else if (node.e) { - return node.e - } - - if (node.vC) { - for (let i = 0, len = node.vC.length; i < len; i++) { - const e = findInsertBefore(node.vC[i]) - if (e) { - return e - } - } - } - - return findInsertBefore(node.nN) -} - -const removeNode = (node: Node) => { - if (!isNodeString(node)) { - node[DOM_STASH]?.[1][STASH_EFFECT]?.forEach((data: EffectData) => data[2]?.()) - - if (node.e && node.props?.ref) { - if (typeof node.props.ref === 'function') { - node.props.ref(null) - } else { - node.props.ref.current = null - } - } - node.vC?.forEach(removeNode) - } - if (node.tag !== HONO_PORTAL_ELEMENT) { - node.e?.remove() - } - if (typeof node.tag === 'function') { - updateMap.delete(node) - fallbackUpdateFnArrayMap.delete(node) - } -} - -const apply = (node: NodeObject, container: Container) => { - node.c = container - applyNodeObject(node, container) -} - -const applyNode = (node: Node, container: Container) => { - if (isNodeString(node)) { - container.textContent = node.t - } else { - applyNodeObject(node, container) - } -} - -const findChildNodeIndex = ( - childNodes: NodeListOf, - child: ChildNode | null | undefined -): number | undefined => { - if (!child) { - return - } - - for (let i = 0, len = childNodes.length; i < len; i++) { - if (childNodes[i] === child) { - return i - } - } - - return -} - -const applyNodeObject = (node: NodeObject, container: Container) => { - const next: Node[] = [] - const remove: Node[] = [] - const callbacks: EffectData[] = [] - getNextChildren(node, container, next, remove, callbacks) - - const childNodes = container.childNodes - let offset = - findChildNodeIndex(childNodes, findInsertBefore(node.nN)) ?? - findChildNodeIndex(childNodes, next.find((n) => n.e)?.e) ?? - childNodes.length - - for (let i = 0, len = next.length; i < len; i++, offset++) { - const child = next[i] - - let el: SupportedElement | Text - if (isNodeString(child)) { - if (child.e && child.d) { - child.e.textContent = child.t - } - child.d = false - el = child.e ||= document.createTextNode(child.t) - } else { - el = child.e ||= child.n - ? (document.createElementNS(child.n, child.tag as string) as SVGElement | MathMLElement) - : document.createElement(child.tag as string) - applyProps(el as HTMLElement, child.props, child.pP) - applyNode(child, el as HTMLElement) - } - if ( - childNodes[offset] !== el && - childNodes[offset - 1] !== child.e && - child.tag !== HONO_PORTAL_ELEMENT - ) { - container.insertBefore(el, childNodes[offset] || null) - } - } - remove.forEach(removeNode) - callbacks.forEach(([, cb]) => cb?.()) - requestAnimationFrame(() => { - callbacks.forEach(([, , , cb]) => cb?.()) - }) -} - -const fallbackUpdateFnArrayMap = new WeakMap< - NodeObject, - Array<() => Promise> ->() -export const build = ( - context: Context, - node: NodeObject, - topLevelErrorHandlerNode: NodeObject | undefined, - children?: Child[] -): void => { - let errorHandler: ErrorHandler | undefined - children ||= - typeof node.tag == 'function' ? invokeTag(context, node) : toArray(node.props.children) - if ((children[0] as JSXNode)?.tag === '') { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - errorHandler = (children[0] as any)[DOM_ERROR_HANDLER] as ErrorHandler - topLevelErrorHandlerNode ||= node - } - const oldVChildren: Node[] = node.vC ? [...node.vC] : [] - const vChildren: Node[] = [] - const vChildrenToRemove: Node[] = [] - let prevNode: Node | undefined - try { - children.flat().forEach((c: Child) => { - let child = buildNode(c) - if (child) { - if (prevNode) { - prevNode.nN = child - } - prevNode = child - - if ( - typeof child.tag === 'function' && - // eslint-disable-next-line @typescript-eslint/no-explicit-any - !(child.tag as any)[DOM_INTERNAL_TAG] && - globalJSXContexts.length > 0 - ) { - child[DOM_STASH][2] = globalJSXContexts.map((c) => [c, c.values.at(-1)]) - } - - let oldChild: Node | undefined - const i = oldVChildren.findIndex((c) => c.key === (child as Node).key) - if (i !== -1) { - oldChild = oldVChildren[i] - oldVChildren.splice(i, 1) - } - - if (oldChild) { - if (isNodeString(child)) { - if (!isNodeString(oldChild)) { - vChildrenToRemove.push(oldChild) - } else { - if (oldChild.t !== child.t) { - oldChild.t = child.t // update text content - oldChild.d = true - } - child = oldChild - } - } else if (oldChild.tag !== child.tag) { - vChildrenToRemove.push(oldChild) - } else { - oldChild.pP = oldChild.props - oldChild.props = child.props - if (typeof child.tag === 'function') { - oldChild[DOM_STASH][2] = child[DOM_STASH][2] || [] - } - child = oldChild - } - } else if (!isNodeString(child) && nameSpaceContext) { - const ns = useContext(nameSpaceContext) - if (ns) { - child.n = ns - } - } - - if (!isNodeString(child)) { - build(context, child, topLevelErrorHandlerNode) - } - vChildren.push(child) - } - }) - node.vC = vChildren - vChildrenToRemove.push(...oldVChildren) - node.vR = vChildrenToRemove - } catch (e) { - if (errorHandler) { - const fallbackUpdateFn = () => - update([0, false, context[2] as UpdateHook], topLevelErrorHandlerNode as NodeObject) - const fallbackUpdateFnArray = - fallbackUpdateFnArrayMap.get(topLevelErrorHandlerNode as NodeObject) || [] - fallbackUpdateFnArray.push(fallbackUpdateFn) - fallbackUpdateFnArrayMap.set(topLevelErrorHandlerNode as NodeObject, fallbackUpdateFnArray) - const fallback = errorHandler(e, () => { - const fnArray = fallbackUpdateFnArrayMap.get(topLevelErrorHandlerNode as NodeObject) - if (fnArray) { - const i = fnArray.indexOf(fallbackUpdateFn) - if (i !== -1) { - fnArray.splice(i, 1) - return fallbackUpdateFn() - } - } - }) - if (fallback) { - if (context[0] === 1) { - context[1] = true - } else { - build(context, node, topLevelErrorHandlerNode, [fallback]) - } - return - } - } - throw e - } -} - -const buildNode = (node: Child): Node | undefined => { - if (node === undefined || node === null || typeof node === 'boolean') { - return undefined - } else if (typeof node === 'string' || typeof node === 'number') { - return { t: node.toString(), d: true } as NodeString - } else { - if (typeof (node as JSXNode).tag === 'function') { - if ((node as NodeObject)[DOM_STASH]) { - node = { ...node } as NodeObject - } - ;(node as NodeObject)[DOM_STASH] = [0, []] - } else { - const ns = nameSpaceMap[(node as JSXNode).tag as string] - if (ns) { - ;(node as NodeObject).n = ns - nameSpaceContext ||= createContext('') - ;(node as JSXNode).props.children = [ - { - tag: nameSpaceContext.Provider, - props: { - value: ns, - children: (node as JSXNode).props.children, - }, - }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ] as any - } - } - return node as NodeObject - } -} - -const replaceContainer = (node: NodeObject, from: DocumentFragment, to: Container) => { - if (node.c === from) { - node.c = to - node.vC.forEach((child) => replaceContainer(child as NodeObject, from, to)) - } -} - -const updateSync = (context: Context, node: NodeObject) => { - node[DOM_STASH][2]?.forEach(([c, v]) => { - c.values.push(v) - }) - build(context, node, undefined) - node[DOM_STASH][2]?.forEach(([c]) => { - c.values.pop() - }) - if (context[0] !== 1 || !context[1]) { - apply(node, node.c as Container) - } -} - -type UpdateMapResolve = (node: NodeObject | undefined) => void -const updateMap = new WeakMap() -const currentUpdateSets: Set[] = [] -export const update = async ( - context: Context, - node: NodeObject -): Promise => { - const existing = updateMap.get(node) - if (existing) { - // execute only the last update() call, so the previous update will be canceled. - existing[0](undefined) - } - - let resolve: UpdateMapResolve | undefined - const promise = new Promise((r) => (resolve = r)) - updateMap.set(node, [ - resolve as UpdateMapResolve, - () => { - if (context[2]) { - context[2](context, node, (context) => { - updateSync(context, node) - }).then(() => (resolve as UpdateMapResolve)(node)) - } else { - updateSync(context, node) - ;(resolve as UpdateMapResolve)(node) - } - }, - ]) - - if (currentUpdateSets.length) { - ;(currentUpdateSets.at(-1) as Set).add(node) - } else { - await Promise.resolve() - - const latest = updateMap.get(node) - if (latest) { - updateMap.delete(node) - latest[1]() - } - } - - return promise -} - -export const render = (jsxNode: unknown, container: Container) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const node = buildNode({ tag: '', props: { children: jsxNode } } as any) as NodeObject - const context: Context = [] - ;(context as Context)[4] = true // start top level render - build(context, node, undefined) - ;(context as Context)[4] = false // finish top level render - - const fragment = document.createDocumentFragment() - apply(node, fragment) - replaceContainer(node, fragment, container) - container.replaceChildren(fragment) -} - -export const flushSync = (callback: () => void) => { - const set = new Set() - currentUpdateSets.push(set) - callback() - set.forEach((node) => { - const latest = updateMap.get(node) - if (latest) { - updateMap.delete(node) - latest[1]() - } - }) - currentUpdateSets.pop() -} - -export const createPortal = (children: Child, container: HTMLElement, key?: string): Child => - ({ - tag: HONO_PORTAL_ELEMENT, - props: { - children, - }, - key, - e: container, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any) diff --git a/deno_dist/jsx/dom/utils.ts b/deno_dist/jsx/dom/utils.ts deleted file mode 100644 index 8036521555..0000000000 --- a/deno_dist/jsx/dom/utils.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { DOM_INTERNAL_TAG } from '../constants.ts' - -export const setInternalTagFlag = (fn: Function): Function => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ;(fn as any)[DOM_INTERNAL_TAG] = true - return fn -} diff --git a/deno_dist/jsx/hooks/index.ts b/deno_dist/jsx/hooks/index.ts deleted file mode 100644 index 6cece83171..0000000000 --- a/deno_dist/jsx/hooks/index.ts +++ /dev/null @@ -1,423 +0,0 @@ -import type { JSX } from '../base.ts' -import { DOM_STASH } from '../constants.ts' -import { buildDataStack, update, build } from '../dom/render.ts' -import type { Node, NodeObject, Context, PendingType, UpdateHook } from '../dom/render.ts' - -type UpdateStateFunction = (newState: T | ((currentState: T) => T)) => void - -const STASH_SATE = 0 -export const STASH_EFFECT = 1 -const STASH_CALLBACK = 2 -const STASH_USE = 3 -const STASH_MEMO = 4 -const STASH_REF = 5 - -export type EffectData = [ - readonly unknown[] | undefined, // deps - (() => void | (() => void)) | undefined, // layout effect - (() => void) | undefined, // cleanup - (() => void) | undefined // effect -] - -const resolvedPromiseValueMap: WeakMap, unknown> = new WeakMap< - Promise, - unknown ->() - -const isDepsChanged = ( - prevDeps: readonly unknown[] | undefined, - deps: readonly unknown[] | undefined -): boolean => - !prevDeps || - !deps || - prevDeps.length !== deps.length || - deps.some((dep, i) => dep !== prevDeps[i]) - -let viewTransitionState: - | [ - boolean, // isUpdating - boolean // useViewTransition() is called - ] - | undefined = undefined - -const documentStartViewTransition: (cb: () => void) => { finished: Promise } = (cb) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if ((document as any)?.startViewTransition) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return (document as any).startViewTransition(cb) - } else { - cb() - return { finished: Promise.resolve() } - } -} - -let updateHook: UpdateHook | undefined = undefined -const viewTransitionHook = ( - context: Context, - node: Node, - cb: (context: Context) => void -): Promise => { - const state: [boolean, boolean] = [true, false] - let lastVC = node.vC - return documentStartViewTransition(() => { - if (lastVC === node.vC) { - viewTransitionState = state - cb(context) - viewTransitionState = undefined - lastVC = node.vC - } - }).finished.then(() => { - if (state[1] && lastVC === node.vC) { - state[0] = false - viewTransitionState = state - cb(context) - viewTransitionState = undefined - } - }) -} - -export const startViewTransition = (callback: () => void): void => { - updateHook = viewTransitionHook - - try { - callback() - } finally { - updateHook = undefined - } -} - -export const useViewTransition = (): [boolean, (callback: () => void) => void] => { - const buildData = buildDataStack.at(-1) as [Context, NodeObject] - if (!buildData) { - return [false, () => {}] - } - - if (viewTransitionState) { - viewTransitionState[1] = true - } - return [!!viewTransitionState?.[0], startViewTransition] -} - -const pendingStack: PendingType[] = [] -const runCallback = (type: PendingType, callback: Function): void => { - pendingStack.push(type) - try { - callback() - } finally { - pendingStack.pop() - } -} - -export const startTransition = (callback: () => void): void => { - runCallback(1, callback) -} -const startTransitionHook = (callback: () => void): void => { - runCallback(2, callback) -} - -export const useTransition = (): [boolean, (callback: () => void) => void] => { - const buildData = buildDataStack.at(-1) as [Context, NodeObject] - if (!buildData) { - return [false, () => {}] - } - const [context] = buildData - return [context[0] === 2, startTransitionHook] -} - -export const useDeferredValue = (value: T): T => { - const buildData = buildDataStack.at(-1) as [Context, NodeObject] - if (buildData) { - buildData[0][0] = 1 - } - return value -} - -const setShadow = (node: Node) => { - if (node.vC) { - node.s = node.vC - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ;(node as any).vC = undefined - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ;(node as any).s?.forEach(setShadow) -} - -type UseStateType = { - (initialState: T | (() => T)): [T, UpdateStateFunction] - (): [T | undefined, UpdateStateFunction] -} -export const useState: UseStateType = ( - initialState?: T | (() => T) -): [T, UpdateStateFunction] => { - const resolveInitialState = () => - typeof initialState === 'function' ? (initialState as () => T)() : (initialState as T) - - const buildData = buildDataStack.at(-1) as [unknown, NodeObject] - if (!buildData) { - return [resolveInitialState(), () => {}] - } - const [, node] = buildData - - const stateArray = (node[DOM_STASH][1][STASH_SATE] ||= []) - const hookIndex = node[DOM_STASH][0]++ - - return (stateArray[hookIndex] ||= [ - resolveInitialState(), - (newState: T | ((currentState: T) => T)) => { - const localUpdateHook = updateHook - const stateData = stateArray[hookIndex] - if (typeof newState === 'function') { - newState = (newState as (currentState: T) => T)(stateData[0]) - } - - if (!Object.is(newState, stateData[0])) { - stateData[0] = newState - if (pendingStack.length) { - const pendingType = pendingStack.at(-1) as PendingType - update([pendingType, false, localUpdateHook as UpdateHook], node).then((node) => { - if (!node || pendingType !== 2) { - return - } - - const lastVC = node.vC - - const addUpdateTask = () => { - setTimeout(() => { - // `node` is not rerendered after current transition - if (lastVC !== node.vC) { - return - } - - const shadowNode = Object.assign({}, node) as NodeObject - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ;(shadowNode as any).vC = undefined // delete the prev build data and build with clean state - build([], shadowNode, undefined) - setShadow(shadowNode) // save the `shadowNode.vC` of the virtual DOM of the build result as a result of shadow virtual DOM `shadowNode.s` - - node.s = shadowNode.s - update([0, false, localUpdateHook as UpdateHook], node) - }) - } - - if (localUpdateHook) { - // wait for next animation frame, then invoke `update()` - requestAnimationFrame(addUpdateTask) - } else { - addUpdateTask() - } - }) - } else { - update([0, false, localUpdateHook as UpdateHook], node) - } - } - }, - ]) -} - -export const useReducer = ( - reducer: (state: T, action: A) => T, - initialArg: T, - init?: (initialState: T) => T -): [T, (action: A) => void] => { - const [state, setState] = useState(() => (init ? init(initialArg) : initialArg)) - return [ - state, - (action: A) => { - setState((state) => reducer(state, action)) - }, - ] -} - -const useEffectCommon = ( - index: number, - effect: () => void | (() => void), - deps?: readonly unknown[] -): void => { - const buildData = buildDataStack.at(-1) as [unknown, NodeObject] - if (!buildData) { - return - } - const [, node] = buildData - - const effectDepsArray = (node[DOM_STASH][1][STASH_EFFECT] ||= []) - const hookIndex = node[DOM_STASH][0]++ - - const [prevDeps, , prevCleanup] = (effectDepsArray[hookIndex] ||= []) - if (isDepsChanged(prevDeps, deps)) { - if (prevCleanup) { - prevCleanup() - } - const runner = () => { - data[index] = undefined // clear this effect in order to avoid calling effect twice - data[2] = effect() as (() => void) | undefined - } - const data: EffectData = [deps, undefined, undefined, undefined] - data[index] = runner - effectDepsArray[hookIndex] = data - } -} -export const useEffect = (effect: () => void | (() => void), deps?: readonly unknown[]): void => - useEffectCommon(3, effect, deps) -export const useLayoutEffect = ( - effect: () => void | (() => void), - deps?: readonly unknown[] -): void => useEffectCommon(1, effect, deps) - -export const useCallback = unknown>( - callback: T, - deps: readonly unknown[] -): T => { - const buildData = buildDataStack.at(-1) as [unknown, NodeObject] - if (!buildData) { - return callback - } - const [, node] = buildData - - const callbackArray = (node[DOM_STASH][1][STASH_CALLBACK] ||= []) - const hookIndex = node[DOM_STASH][0]++ - - const prevDeps = callbackArray[hookIndex] - if (isDepsChanged(prevDeps?.[1], deps)) { - callbackArray[hookIndex] = [callback, deps] - } else { - callback = callbackArray[hookIndex][0] as T - } - return callback -} - -export type RefObject = { current: T | null } -export const useRef = (initialValue: T | null): RefObject => { - const buildData = buildDataStack.at(-1) as [unknown, NodeObject] - if (!buildData) { - return { current: initialValue } - } - const [, node] = buildData - - const refArray = (node[DOM_STASH][1][STASH_REF] ||= []) - const hookIndex = node[DOM_STASH][0]++ - - return (refArray[hookIndex] ||= { current: initialValue }) -} - -export const use = (promise: Promise): T => { - const cachedRes = resolvedPromiseValueMap.get(promise) as [T] | [undefined, unknown] | undefined - if (cachedRes) { - if (cachedRes.length === 2) { - throw cachedRes[1] - } - return cachedRes[0] as T - } - promise.then( - (res) => resolvedPromiseValueMap.set(promise, [res]), - (e) => resolvedPromiseValueMap.set(promise, [undefined, e]) - ) - - const buildData = buildDataStack.at(-1) as [unknown, NodeObject] - if (!buildData) { - throw promise - } - const [, node] = buildData - - const promiseArray = (node[DOM_STASH][1][STASH_USE] ||= []) - const hookIndex = node[DOM_STASH][0]++ - - promise.then( - (res) => { - promiseArray[hookIndex] = [res] - }, - (e) => { - promiseArray[hookIndex] = [undefined, e] - } - ) - - const res = promiseArray[hookIndex] - if (res) { - if (res.length === 2) { - throw res[1] - } - return res[0] as T - } - - throw promise -} - -export const useMemo = (factory: () => T, deps: readonly unknown[]): T => { - const buildData = buildDataStack.at(-1) as [unknown, NodeObject] - if (!buildData) { - return factory() - } - const [, node] = buildData - - const memoArray = (node[DOM_STASH][1][STASH_MEMO] ||= []) - const hookIndex = node[DOM_STASH][0]++ - - const prevDeps = memoArray[hookIndex] - if (isDepsChanged(prevDeps?.[1], deps)) { - memoArray[hookIndex] = [factory(), deps] - } - return memoArray[hookIndex][0] as T -} - -let idCounter = 0 -export const useId = (): string => useMemo(() => `:r${(idCounter++).toString(32)}:`, []) - -// Define to avoid errors. This hook currently does nothing. -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export const useDebugValue = (_value: unknown, _formatter?: (value: unknown) => string): void => {} - -export const createRef = (): RefObject => { - return { current: null } -} - -export const forwardRef = ( - Component: (props: P, ref: RefObject) => JSX.Element -): ((props: P & { ref: RefObject }) => JSX.Element) => { - return (props) => { - const { ref, ...rest } = props - return Component(rest as P, ref) - } -} - -export const useImperativeHandle = ( - ref: RefObject, - createHandle: () => T, - deps: readonly unknown[] -): void => { - useEffect(() => { - ref.current = createHandle() - return () => { - ref.current = null - } - }, deps) -} - -export const useSyncExternalStore = ( - subscribe: (callback: (value: T) => void) => () => void, - getSnapshot: () => T, - getServerSnapshot?: () => T -): T => { - const buildData = buildDataStack.at(-1) as [Context, unknown] - if (!buildData) { - // now a stringify process, maybe in server side - if (!getServerSnapshot) { - throw new Error('getServerSnapshot is required for server side rendering') - } - return getServerSnapshot() - } - - const [serverSnapshotIsUsed] = useState(!!(buildData[0][4] && getServerSnapshot)) - const [state, setState] = useState(() => - serverSnapshotIsUsed ? (getServerSnapshot as () => T)() : getSnapshot() - ) - useEffect(() => { - if (serverSnapshotIsUsed) { - setState(getSnapshot()) - } - return subscribe(() => { - setState(getSnapshot()) - }) - }, []) - - return state -} diff --git a/deno_dist/jsx/index.ts b/deno_dist/jsx/index.ts deleted file mode 100644 index 005c04b967..0000000000 --- a/deno_dist/jsx/index.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { jsx, memo, Fragment, isValidElement, cloneElement } from './base.ts' -import type { DOMAttributes } from './base.ts' -import { Children } from './children.ts' -import { ErrorBoundary } from './components.ts' -import { createContext, useContext } from './context.ts' -import { - useState, - useEffect, - useRef, - useCallback, - use, - startTransition, - useTransition, - useDeferredValue, - startViewTransition, - useViewTransition, - useMemo, - useLayoutEffect, - useReducer, - useId, - useDebugValue, - createRef, - forwardRef, - useImperativeHandle, - useSyncExternalStore, -} from './hooks/index.ts' -import { Suspense } from './streaming.ts' - -export { - jsx, - memo, - Fragment, - isValidElement, - jsx as createElement, - cloneElement, - ErrorBoundary, - createContext, - useContext, - useState, - useEffect, - useRef, - useCallback, - useReducer, - useId, - useDebugValue, - use, - startTransition, - useTransition, - useDeferredValue, - startViewTransition, - useViewTransition, - useMemo, - useLayoutEffect, - createRef, - forwardRef, - useImperativeHandle, - useSyncExternalStore, - Suspense, - Children, - DOMAttributes, -} - -export default { - memo, - Fragment, - isValidElement, - createElement: jsx, - cloneElement, - ErrorBoundary, - createContext, - useContext, - useState, - useEffect, - useRef, - useCallback, - useReducer, - useId, - useDebugValue, - use, - startTransition, - useTransition, - useDeferredValue, - startViewTransition, - useViewTransition, - useMemo, - useLayoutEffect, - createRef, - forwardRef, - useImperativeHandle, - useSyncExternalStore, - Suspense, - Children, -} - -// TODO: change to `export type *` after denoify bug is fixed -// https://github.com/garronej/denoify/issues/124 -export * from './types.ts' diff --git a/deno_dist/jsx/intrinsic-elements.ts b/deno_dist/jsx/intrinsic-elements.ts deleted file mode 100644 index 67caba3346..0000000000 --- a/deno_dist/jsx/intrinsic-elements.ts +++ /dev/null @@ -1,729 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -/** - * This code is based on React. - * https://github.com/facebook/react - * MIT License - * Copyright (c) Meta Platforms, Inc. and affiliates. - */ - -// eslint-disable-next-line @typescript-eslint/no-namespace -export namespace Hono { - export type CrossOrigin = 'anonymous' | 'use-credentials' | '' | undefined - export type CSSProperties = {} - type AnyAttributes = { [attributeName: string]: any } - - interface JSXAttributes { - dangerouslySetInnerHTML?: { - __html: string - } - } - - interface EventAttributes { - onScroll?: (event: Event) => void - onScrollCapture?: (event: Event) => void - onScrollEnd?: (event: Event) => void - onScrollEndCapture?: (event: Event) => void - onWheel?: (event: WheelEvent) => void - onWheelCapture?: (event: WheelEvent) => void - onAnimationCancel?: (event: AnimationEvent) => void - onAnimationCancelCapture?: (event: AnimationEvent) => void - onAnimationEnd?: (event: AnimationEvent) => void - onAnimationEndCapture?: (event: AnimationEvent) => void - onAnimationIteration?: (event: AnimationEvent) => void - onAnimationIterationCapture?: (event: AnimationEvent) => void - onAnimationStart?: (event: AnimationEvent) => void - onAnimationStartCapture?: (event: AnimationEvent) => void - onCopy?: (event: ClipboardEvent) => void - onCopyCapture?: (event: ClipboardEvent) => void - onCut?: (event: ClipboardEvent) => void - onCutCapture?: (event: ClipboardEvent) => void - onPaste?: (event: ClipboardEvent) => void - onPasteCapture?: (event: ClipboardEvent) => void - onCompositionEnd?: (event: CompositionEvent) => void - onCompositionEndCapture?: (event: CompositionEvent) => void - onCompositionStart?: (event: CompositionEvent) => void - onCompositionStartCapture?: (event: CompositionEvent) => void - onCompositionUpdate?: (event: CompositionEvent) => void - onCompositionUpdateCapture?: (event: CompositionEvent) => void - onBlur?: (event: FocusEvent) => void - onBlurCapture?: (event: FocusEvent) => void - onFocus?: (event: FocusEvent) => void - onFocusCapture?: (event: FocusEvent) => void - onFocusIn?: (event: FocusEvent) => void - onFocusInCapture?: (event: FocusEvent) => void - onFocusOut?: (event: FocusEvent) => void - onFocusOutCapture?: (event: FocusEvent) => void - onFullscreenChange?: (event: Event) => void - onFullscreenChangeCapture?: (event: Event) => void - onFullscreenError?: (event: Event) => void - onFullscreenErrorCapture?: (event: Event) => void - onKeyDown?: (event: KeyboardEvent) => void - onKeyDownCapture?: (event: KeyboardEvent) => void - onKeyPress?: (event: KeyboardEvent) => void - onKeyPressCapture?: (event: KeyboardEvent) => void - onKeyUp?: (event: KeyboardEvent) => void - onKeyUpCapture?: (event: KeyboardEvent) => void - onAuxClick?: (event: MouseEvent) => void - onAuxClickCapture?: (event: MouseEvent) => void - onClick?: (event: MouseEvent) => void - onClickCapture?: (event: MouseEvent) => void - onContextMenu?: (event: MouseEvent) => void - onContextMenuCapture?: (event: MouseEvent) => void - onDoubleClick?: (event: MouseEvent) => void - onDoubleClickCapture?: (event: MouseEvent) => void - onMouseDown?: (event: MouseEvent) => void - onMouseDownCapture?: (event: MouseEvent) => void - onMouseEnter?: (event: MouseEvent) => void - onMouseEnterCapture?: (event: MouseEvent) => void - onMouseLeave?: (event: MouseEvent) => void - onMouseLeaveCapture?: (event: MouseEvent) => void - onMouseMove?: (event: MouseEvent) => void - onMouseMoveCapture?: (event: MouseEvent) => void - onMouseOut?: (event: MouseEvent) => void - onMouseOutCapture?: (event: MouseEvent) => void - onMouseOver?: (event: MouseEvent) => void - onMouseOverCapture?: (event: MouseEvent) => void - onMouseUp?: (event: MouseEvent) => void - onMouseUpCapture?: (event: MouseEvent) => void - onMouseWheel?: (event: WheelEvent) => void - onMouseWheelCapture?: (event: WheelEvent) => void - onGotPointerCapture?: (event: PointerEvent) => void - onGotPointerCaptureCapture?: (event: PointerEvent) => void - onLostPointerCapture?: (event: PointerEvent) => void - onLostPointerCaptureCapture?: (event: PointerEvent) => void - onPointerCancel?: (event: PointerEvent) => void - onPointerCancelCapture?: (event: PointerEvent) => void - onPointerDown?: (event: PointerEvent) => void - onPointerDownCapture?: (event: PointerEvent) => void - onPointerEnter?: (event: PointerEvent) => void - onPointerEnterCapture?: (event: PointerEvent) => void - onPointerLeave?: (event: PointerEvent) => void - onPointerLeaveCapture?: (event: PointerEvent) => void - onPointerMove?: (event: PointerEvent) => void - onPointerMoveCapture?: (event: PointerEvent) => void - onPointerOut?: (event: PointerEvent) => void - onPointerOutCapture?: (event: PointerEvent) => void - onPointerOver?: (event: PointerEvent) => void - onPointerOverCapture?: (event: PointerEvent) => void - onPointerUp?: (event: PointerEvent) => void - onPointerUpCapture?: (event: PointerEvent) => void - onTouchCancel?: (event: TouchEvent) => void - onTouchCancelCapture?: (event: TouchEvent) => void - onTouchEnd?: (event: TouchEvent) => void - onTouchEndCapture?: (event: TouchEvent) => void - onTouchMove?: (event: TouchEvent) => void - onTouchMoveCapture?: (event: TouchEvent) => void - onTouchStart?: (event: TouchEvent) => void - onTouchStartCapture?: (event: TouchEvent) => void - onTransitionCancel?: (event: TransitionEvent) => void - onTransitionCancelCapture?: (event: TransitionEvent) => void - onTransitionEnd?: (event: TransitionEvent) => void - onTransitionEndCapture?: (event: TransitionEvent) => void - onTransitionRun?: (event: TransitionEvent) => void - onTransitionRunCapture?: (event: TransitionEvent) => void - onTransitionStart?: (event: TransitionEvent) => void - onTransitionStartCapture?: (event: TransitionEvent) => void - onFormData?: (event: FormDataEvent) => void - onFormDataCapture?: (event: FormDataEvent) => void - onReset?: (event: Event) => void - onResetCapture?: (event: Event) => void - onSubmit?: (event: Event) => void - onSubmitCapture?: (event: Event) => void - onInvalid?: (event: Event) => void - onInvalidCapture?: (event: Event) => void - onSelect?: (event: Event) => void - onSelectCapture?: (event: Event) => void - onSelectChange?: (event: Event) => void - onSelectChangeCapture?: (event: Event) => void - onInput?: (event: InputEvent) => void - onInputCapture?: (event: InputEvent) => void - onBeforeInput?: (event: InputEvent) => void - onBeforeInputCapture?: (event: InputEvent) => void - onChange?: (event: Event) => void - onChangeCapture?: (event: Event) => void - } - - export interface HTMLAttributes extends JSXAttributes, EventAttributes, AnyAttributes { - accesskey?: string | undefined - autofocus?: boolean | undefined - class?: string | Promise | undefined - contenteditable?: boolean | 'inherit' | undefined - contextmenu?: string | undefined - dir?: string | undefined - draggable?: boolean | undefined - hidden?: boolean | undefined - id?: string | undefined - lang?: string | undefined - nonce?: string | undefined - placeholder?: string | undefined - slot?: string | undefined - spellcheck?: boolean | undefined - style?: CSSProperties | undefined - tabindex?: number | undefined - title?: string | undefined - translate?: 'yes' | 'no' | undefined - } - - type HTMLAttributeReferrerPolicy = - | '' - | 'no-referrer' - | 'no-referrer-when-downgrade' - | 'origin' - | 'origin-when-cross-origin' - | 'same-origin' - | 'strict-origin' - | 'strict-origin-when-cross-origin' - | 'unsafe-url' - - type HTMLAttributeAnchorTarget = '_self' | '_blank' | '_parent' | '_top' | string - - interface AnchorHTMLAttributes extends HTMLAttributes { - download?: any - href?: string | undefined - hreflang?: string | undefined - media?: string | undefined - ping?: string | undefined - target?: HTMLAttributeAnchorTarget | undefined - type?: string | undefined - referrerpolicy?: HTMLAttributeReferrerPolicy | undefined - } - - interface AudioHTMLAttributes extends MediaHTMLAttributes {} - - interface AreaHTMLAttributes extends HTMLAttributes { - alt?: string | undefined - coords?: string | undefined - download?: any - href?: string | undefined - hreflang?: string | undefined - media?: string | undefined - referrerpolicy?: HTMLAttributeReferrerPolicy | undefined - shape?: string | undefined - target?: string | undefined - } - - interface BaseHTMLAttributes extends HTMLAttributes { - href?: string | undefined - target?: string | undefined - } - - interface BlockquoteHTMLAttributes extends HTMLAttributes { - cite?: string | undefined - } - - interface ButtonHTMLAttributes extends HTMLAttributes { - disabled?: boolean | undefined - form?: string | undefined - formenctype?: string | undefined - formmethod?: string | undefined - formnovalidate?: boolean | undefined - formtarget?: string | undefined - name?: string | undefined - type?: 'submit' | 'reset' | 'button' | undefined - value?: string | ReadonlyArray | number | undefined - } - - interface CanvasHTMLAttributes extends HTMLAttributes { - height?: number | string | undefined - width?: number | string | undefined - } - - interface ColHTMLAttributes extends HTMLAttributes { - span?: number | undefined - width?: number | string | undefined - } - - interface ColgroupHTMLAttributes extends HTMLAttributes { - span?: number | undefined - } - - interface DataHTMLAttributes extends HTMLAttributes { - value?: string | ReadonlyArray | number | undefined - } - - interface DetailsHTMLAttributes extends HTMLAttributes { - open?: boolean | undefined - } - - interface DelHTMLAttributes extends HTMLAttributes { - cite?: string | undefined - dateTime?: string | undefined - } - - interface DialogHTMLAttributes extends HTMLAttributes { - open?: boolean | undefined - } - - interface EmbedHTMLAttributes extends HTMLAttributes { - height?: number | string | undefined - src?: string | undefined - type?: string | undefined - width?: number | string | undefined - } - - interface FieldsetHTMLAttributes extends HTMLAttributes { - disabled?: boolean | undefined - form?: string | undefined - name?: string | undefined - } - - interface FormHTMLAttributes extends HTMLAttributes { - 'accept-charset'?: string | undefined - autocomplete?: string | undefined - enctype?: string | undefined - method?: string | undefined - name?: string | undefined - novalidate?: boolean | undefined - target?: string | undefined - } - - interface HtmlHTMLAttributes extends HTMLAttributes { - manifest?: string | undefined - } - - interface IframeHTMLAttributes extends HTMLAttributes { - allow?: string | undefined - allowfullscreen?: boolean | undefined - height?: number | string | undefined - loading?: 'eager' | 'lazy' | undefined - name?: string | undefined - referrerpolicy?: HTMLAttributeReferrerPolicy | undefined - sandbox?: string | undefined - seamless?: boolean | undefined - src?: string | undefined - srcdoc?: string | undefined - width?: number | string | undefined - } - - interface ImgHTMLAttributes extends HTMLAttributes { - alt?: string | undefined - crossorigin?: CrossOrigin - decoding?: 'async' | 'auto' | 'sync' | undefined - height?: number | string | undefined - loading?: 'eager' | 'lazy' | undefined - referrerpolicy?: HTMLAttributeReferrerPolicy | undefined - sizes?: string | undefined - src?: string | undefined - srcset?: string | undefined - usemap?: string | undefined - width?: number | string | undefined - } - - interface InsHTMLAttributes extends HTMLAttributes { - cite?: string | undefined - datetime?: string | undefined - } - - type HTMLInputTypeAttribute = - | 'button' - | 'checkbox' - | 'color' - | 'date' - | 'datetime-local' - | 'email' - | 'file' - | 'hidden' - | 'image' - | 'month' - | 'number' - | 'password' - | 'radio' - | 'range' - | 'reset' - | 'search' - | 'submit' - | 'tel' - | 'text' - | 'time' - | 'url' - | 'week' - | string - - interface InputHTMLAttributes extends HTMLAttributes { - accept?: string | undefined - alt?: string | undefined - autocomplete?: string | undefined - capture?: boolean | 'user' | 'environment' | undefined // https://www.w3.org/TR/html-media-capture/#the-capture-attribute - checked?: boolean | undefined - disabled?: boolean | undefined - enterkeyhint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send' | undefined - form?: string | undefined - formenctype?: string | undefined - formmethod?: string | undefined - formnovalidate?: boolean | undefined - formtarget?: string | undefined - height?: number | string | undefined - list?: string | undefined - max?: number | string | undefined - maxlength?: number | undefined - min?: number | string | undefined - minlength?: number | undefined - multiple?: boolean | undefined - name?: string | undefined - pattern?: string | undefined - placeholder?: string | undefined - readonly?: boolean | undefined - required?: boolean | undefined - size?: number | undefined - src?: string | undefined - step?: number | string | undefined - type?: HTMLInputTypeAttribute | undefined - value?: string | ReadonlyArray | number | undefined - width?: number | string | undefined - } - - interface KeygenHTMLAttributes extends HTMLAttributes { - challenge?: string | undefined - disabled?: boolean | undefined - form?: string | undefined - keytype?: string | undefined - name?: string | undefined - } - - interface LabelHTMLAttributes extends HTMLAttributes { - form?: string | undefined - for?: string | undefined - } - - interface LiHTMLAttributes extends HTMLAttributes { - value?: string | ReadonlyArray | number | undefined - } - - interface LinkHTMLAttributes extends HTMLAttributes { - as?: string | undefined - crossorigin?: CrossOrigin - href?: string | undefined - hreflang?: string | undefined - integrity?: string | undefined - media?: string | undefined - imagesrcset?: string | undefined - imagesizes?: string | undefined - referrerpolicy?: HTMLAttributeReferrerPolicy | undefined - sizes?: string | undefined - type?: string | undefined - charSet?: string | undefined - } - - interface MapHTMLAttributes extends HTMLAttributes { - name?: string | undefined - } - - interface MenuHTMLAttributes extends HTMLAttributes { - type?: string | undefined - } - - interface MediaHTMLAttributes extends HTMLAttributes { - autoplay?: boolean | undefined - controls?: boolean | undefined - controlslist?: string | undefined - crossorigin?: CrossOrigin - loop?: boolean | undefined - mediagroup?: string | undefined - muted?: boolean | undefined - playsinline?: boolean | undefined - preload?: string | undefined - src?: string | undefined - } - - interface MetaHTMLAttributes extends HTMLAttributes { - charset?: string | undefined - 'http-equiv'?: string | undefined - name?: string | undefined - media?: string | undefined - content?: string | undefined - } - - interface MeterHTMLAttributes extends HTMLAttributes { - form?: string | undefined - high?: number | undefined - low?: number | undefined - max?: number | string | undefined - min?: number | string | undefined - optimum?: number | undefined - value?: string | ReadonlyArray | number | undefined - } - - interface QuoteHTMLAttributes extends HTMLAttributes { - cite?: string | undefined - } - - interface ObjectHTMLAttributes extends HTMLAttributes { - data?: string | undefined - form?: string | undefined - height?: number | string | undefined - name?: string | undefined - type?: string | undefined - usemap?: string | undefined - width?: number | string | undefined - } - - interface OlHTMLAttributes extends HTMLAttributes { - reversed?: boolean | undefined - start?: number | undefined - type?: '1' | 'a' | 'A' | 'i' | 'I' | undefined - } - - interface OptgroupHTMLAttributes extends HTMLAttributes { - disabled?: boolean | undefined - label?: string | undefined - } - - interface OptionHTMLAttributes extends HTMLAttributes { - disabled?: boolean | undefined - label?: string | undefined - selected?: boolean | undefined - value?: string | ReadonlyArray | number | undefined - } - - interface OutputHTMLAttributes extends HTMLAttributes { - form?: string | undefined - for?: string | undefined - name?: string | undefined - } - - interface ParamHTMLAttributes extends HTMLAttributes { - name?: string | undefined - value?: string | ReadonlyArray | number | undefined - } - - interface ProgressHTMLAttributes extends HTMLAttributes { - max?: number | string | undefined - value?: string | ReadonlyArray | number | undefined - } - - interface SlotHTMLAttributes extends HTMLAttributes { - name?: string | undefined - } - - interface ScriptHTMLAttributes extends HTMLAttributes { - async?: boolean | undefined - crossorigin?: CrossOrigin - defer?: boolean | undefined - integrity?: string | undefined - nomodule?: boolean | undefined - referrerpolicy?: HTMLAttributeReferrerPolicy | undefined - src?: string | undefined - type?: string | undefined - } - - interface SelectHTMLAttributes extends HTMLAttributes { - autocomplete?: string | undefined - disabled?: boolean | undefined - form?: string | undefined - multiple?: boolean | undefined - name?: string | undefined - required?: boolean | undefined - size?: number | undefined - value?: string | ReadonlyArray | number | undefined - } - - interface SourceHTMLAttributes extends HTMLAttributes { - height?: number | string | undefined - media?: string | undefined - sizes?: string | undefined - src?: string | undefined - srcset?: string | undefined - type?: string | undefined - width?: number | string | undefined - } - - interface StyleHTMLAttributes extends HTMLAttributes { - media?: string | undefined - scoped?: boolean | undefined - type?: string | undefined - } - - interface TableHTMLAttributes extends HTMLAttributes { - align?: 'left' | 'center' | 'right' | undefined - bgcolor?: string | undefined - border?: number | undefined - cellpadding?: number | string | undefined - cellspacing?: number | string | undefined - frame?: boolean | undefined - rules?: 'none' | 'groups' | 'rows' | 'columns' | 'all' | undefined - summary?: string | undefined - width?: number | string | undefined - } - - interface TextareaHTMLAttributes extends HTMLAttributes { - autocomplete?: string | undefined - cols?: number | undefined - dirname?: string | undefined - disabled?: boolean | undefined - form?: string | undefined - maxlength?: number | undefined - minlength?: number | undefined - name?: string | undefined - placeholder?: string | undefined - readonly?: boolean | undefined - required?: boolean | undefined - rows?: number | undefined - value?: string | ReadonlyArray | number | undefined - wrap?: string | undefined - } - - interface TdHTMLAttributes extends HTMLAttributes { - align?: 'left' | 'center' | 'right' | 'justify' | 'char' | undefined - colspan?: number | undefined - headers?: string | undefined - rowspan?: number | undefined - scope?: string | undefined - abbr?: string | undefined - height?: number | string | undefined - width?: number | string | undefined - valign?: 'top' | 'middle' | 'bottom' | 'baseline' | undefined - } - - interface ThHTMLAttributes extends HTMLAttributes { - align?: 'left' | 'center' | 'right' | 'justify' | 'char' | undefined - colspan?: number | undefined - headers?: string | undefined - rowspan?: number | undefined - scope?: string | undefined - abbr?: string | undefined - } - - interface TimeHTMLAttributes extends HTMLAttributes { - datetime?: string | undefined - } - - interface TrackHTMLAttributes extends HTMLAttributes { - default?: boolean | undefined - kind?: string | undefined - label?: string | undefined - src?: string | undefined - srclang?: string | undefined - } - - interface VideoHTMLAttributes extends MediaHTMLAttributes { - height?: number | string | undefined - playsinline?: boolean | undefined - poster?: string | undefined - width?: number | string | undefined - disablePictureInPicture?: boolean | undefined - disableRemotePlayback?: boolean | undefined - } - - export interface IntrinsicElements { - a: AnchorHTMLAttributes - abbr: HTMLAttributes - address: HTMLAttributes - area: AreaHTMLAttributes - article: HTMLAttributes - aside: HTMLAttributes - audio: AudioHTMLAttributes - b: HTMLAttributes - base: BaseHTMLAttributes - bdi: HTMLAttributes - bdo: HTMLAttributes - big: HTMLAttributes - blockquote: BlockquoteHTMLAttributes - body: HTMLAttributes - br: HTMLAttributes - button: ButtonHTMLAttributes - canvas: CanvasHTMLAttributes - caption: HTMLAttributes - center: HTMLAttributes - cite: HTMLAttributes - code: HTMLAttributes - col: ColHTMLAttributes - colgroup: ColgroupHTMLAttributes - data: DataHTMLAttributes - datalist: HTMLAttributes - dd: HTMLAttributes - del: DelHTMLAttributes - details: DetailsHTMLAttributes - dfn: HTMLAttributes - dialog: DialogHTMLAttributes - div: HTMLAttributes - dl: HTMLAttributes - dt: HTMLAttributes - em: HTMLAttributes - embed: EmbedHTMLAttributes - fieldset: FieldsetHTMLAttributes - figcaption: HTMLAttributes - figure: HTMLAttributes - footer: HTMLAttributes - form: FormHTMLAttributes - h1: HTMLAttributes - h2: HTMLAttributes - h3: HTMLAttributes - h4: HTMLAttributes - h5: HTMLAttributes - h6: HTMLAttributes - head: HTMLAttributes - header: HTMLAttributes - hgroup: HTMLAttributes - hr: HTMLAttributes - html: HtmlHTMLAttributes - i: HTMLAttributes - iframe: IframeHTMLAttributes - img: ImgHTMLAttributes - input: InputHTMLAttributes - ins: InsHTMLAttributes - kbd: HTMLAttributes - keygen: KeygenHTMLAttributes - label: LabelHTMLAttributes - legend: HTMLAttributes - li: LiHTMLAttributes - link: LinkHTMLAttributes - main: HTMLAttributes - map: MapHTMLAttributes - mark: HTMLAttributes - menu: MenuHTMLAttributes - menuitem: HTMLAttributes - meta: MetaHTMLAttributes - meter: MeterHTMLAttributes - nav: HTMLAttributes - noscript: HTMLAttributes - object: ObjectHTMLAttributes - ol: OlHTMLAttributes - optgroup: OptgroupHTMLAttributes - option: OptionHTMLAttributes - output: OutputHTMLAttributes - p: HTMLAttributes - param: ParamHTMLAttributes - picture: HTMLAttributes - pre: HTMLAttributes - progress: ProgressHTMLAttributes - q: QuoteHTMLAttributes - rp: HTMLAttributes - rt: HTMLAttributes - ruby: HTMLAttributes - s: HTMLAttributes - samp: HTMLAttributes - search: HTMLAttributes - slot: SlotHTMLAttributes - script: ScriptHTMLAttributes - section: HTMLAttributes - select: SelectHTMLAttributes - small: HTMLAttributes - source: SourceHTMLAttributes - span: HTMLAttributes - strong: HTMLAttributes - style: StyleHTMLAttributes - sub: HTMLAttributes - summary: HTMLAttributes - sup: HTMLAttributes - table: TableHTMLAttributes - template: HTMLAttributes - tbody: HTMLAttributes - td: TdHTMLAttributes - textarea: TextareaHTMLAttributes - tfoot: HTMLAttributes - th: ThHTMLAttributes - thead: HTMLAttributes - time: TimeHTMLAttributes - title: HTMLAttributes - tr: HTMLAttributes - track: TrackHTMLAttributes - u: HTMLAttributes - ul: HTMLAttributes - var: HTMLAttributes - video: VideoHTMLAttributes - wbr: HTMLAttributes - } -} - -export interface IntrinsicElements extends Hono.IntrinsicElements {} diff --git a/deno_dist/jsx/jsx-dev-runtime.ts b/deno_dist/jsx/jsx-dev-runtime.ts deleted file mode 100644 index 5c9f8fd106..0000000000 --- a/deno_dist/jsx/jsx-dev-runtime.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { HtmlEscapedString } from '../utils/html.ts' -import { jsxFn } from './base.ts' -import type { JSXNode } from './base.ts' -export { Fragment } from './base.ts' -export type { JSX } from './base.ts' - -export function jsxDEV( - tag: string | Function, - props: Record, - key?: string -): JSXNode { - let node: JSXNode - if (!props || !('children' in props)) { - node = jsxFn(tag, props, []) - } else { - const children = props.children as string | HtmlEscapedString - node = Array.isArray(children) ? jsxFn(tag, props, children) : jsxFn(tag, props, [children]) - } - node.key = key - return node -} diff --git a/deno_dist/jsx/jsx-runtime.ts b/deno_dist/jsx/jsx-runtime.ts deleted file mode 100644 index bdfebcec90..0000000000 --- a/deno_dist/jsx/jsx-runtime.ts +++ /dev/null @@ -1,13 +0,0 @@ -export { jsxDEV as jsx, Fragment } from './jsx-dev-runtime.ts' -export { jsxDEV as jsxs } from './jsx-dev-runtime.ts' -export type { JSX } from './jsx-dev-runtime.ts' - -import { raw, html } from '../helper/html/index.ts' -import type { HtmlEscapedString } from '../utils/html.ts' -export { html as jsxTemplate } -export const jsxAttr = ( - name: string, - value: string | Promise -): HtmlEscapedString | Promise => - typeof value === 'string' ? raw(name + '="' + html`${value}` + '"') : html`${name}="${value}"` -export const jsxEscape = (value: string) => value diff --git a/deno_dist/jsx/streaming.ts b/deno_dist/jsx/streaming.ts deleted file mode 100644 index de419bdbc3..0000000000 --- a/deno_dist/jsx/streaming.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { raw } from '../helper/html/index.ts' -import { HtmlEscapedCallbackPhase, resolveCallback } from '../utils/html.ts' -import type { HtmlEscapedString } from '../utils/html.ts' -import { childrenToString } from './components.ts' -import { DOM_RENDERER, DOM_STASH } from './constants.ts' -import { Suspense as SuspenseDomRenderer } from './dom/components.ts' -import { buildDataStack } from './dom/render.ts' -import type { HasRenderToDom, NodeObject } from './dom/render.ts' -import type { FC, PropsWithChildren, Child } from './index.ts' - -let suspenseCounter = 0 - -/** - * @experimental - * `Suspense` is an experimental feature. - * The API might be changed. - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const Suspense: FC> = async ({ - children, - fallback, -}) => { - if (!children) { - return fallback.toString() - } - if (!Array.isArray(children)) { - children = [children] - } - - let resArray: HtmlEscapedString[] | Promise[] = [] - - // for use() hook - const stackNode = { [DOM_STASH]: [0, []] } as unknown as NodeObject - const popNodeStack = (value?: unknown) => { - buildDataStack.pop() - return value - } - - try { - stackNode[DOM_STASH][0] = 0 - buildDataStack.push([[], stackNode]) - resArray = children.map((c) => - c == null || typeof c === 'boolean' ? '' : c.toString() - ) as HtmlEscapedString[] - } catch (e) { - if (e instanceof Promise) { - resArray = [ - e.then(() => { - stackNode[DOM_STASH][0] = 0 - buildDataStack.push([[], stackNode]) - return childrenToString(children as Child[]).then(popNodeStack) - }), - ] as Promise[] - } else { - throw e - } - } finally { - popNodeStack() - } - - if (resArray.some((res) => (res as {}) instanceof Promise)) { - const index = suspenseCounter++ - const fallbackStr = await fallback.toString() - return raw(`${fallbackStr}`, [ - ...(fallbackStr.callbacks || []), - ({ phase, buffer, context }) => { - if (phase === HtmlEscapedCallbackPhase.BeforeStream) { - return - } - return Promise.all(resArray).then(async (htmlArray) => { - htmlArray = htmlArray.flat() - const content = htmlArray.join('') - if (buffer) { - buffer[0] = buffer[0].replace( - new RegExp(`.*?`), - content - ) - } - let html = buffer - ? '' - : `` - - const callbacks = htmlArray - .map((html) => (html as HtmlEscapedString).callbacks || []) - .flat() - if (!callbacks.length) { - return html - } - - if (phase === HtmlEscapedCallbackPhase.Stream) { - html = await resolveCallback(html, HtmlEscapedCallbackPhase.BeforeStream, true, context) - } - - return raw(html, callbacks) - }) - }, - ]) - } else { - return raw(resArray.join('')) - } -} -;(Suspense as HasRenderToDom)[DOM_RENDERER] = SuspenseDomRenderer - -const textEncoder = new TextEncoder() -/** - * @experimental - * `renderToReadableStream()` is an experimental feature. - * The API might be changed. - */ -export const renderToReadableStream = ( - str: HtmlEscapedString | Promise, - onError: (e: unknown) => void = console.trace -): ReadableStream => { - const reader = new ReadableStream({ - async start(controller) { - try { - const tmp = str instanceof Promise ? await str : await str.toString() - const context = typeof tmp === 'object' ? tmp : {} - const resolved = await resolveCallback( - tmp, - HtmlEscapedCallbackPhase.BeforeStream, - true, - context - ) - controller.enqueue(textEncoder.encode(resolved)) - - let resolvedCount = 0 - const callbacks: Promise[] = [] - const then = (promise: Promise) => { - callbacks.push( - promise - .catch((err) => { - console.log(err) - onError(err) - return '' - }) - .then(async (res) => { - res = await resolveCallback( - res, - HtmlEscapedCallbackPhase.BeforeStream, - true, - context - ) - ;(res as HtmlEscapedString).callbacks - ?.map((c) => c({ phase: HtmlEscapedCallbackPhase.Stream, context })) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .filter>(Boolean as any) - .forEach(then) - resolvedCount++ - controller.enqueue(textEncoder.encode(res)) - }) - ) - } - ;(resolved as HtmlEscapedString).callbacks - ?.map((c) => c({ phase: HtmlEscapedCallbackPhase.Stream, context })) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .filter>(Boolean as any) - .forEach(then) - while (resolvedCount !== callbacks.length) { - await Promise.all(callbacks) - } - } catch (e) { - // maybe the connection was closed - onError(e) - } - - controller.close() - }, - }) - return reader -} diff --git a/deno_dist/jsx/types.ts b/deno_dist/jsx/types.ts deleted file mode 100644 index 6f060bf2bd..0000000000 --- a/deno_dist/jsx/types.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * All types exported from "hono/jsx" are in this file. - */ -import type { Child, JSXNode } from './base.ts' -import type { Hono } from './intrinsic-elements.ts' - -export type { Child, JSXNode, FC } from './base.ts' -export type { RefObject } from './hooks/index.ts' -export type { Context } from './context.ts' - -export type PropsWithChildren

= P & { children?: Child | undefined } -export type CSSProperties = Hono.CSSProperties - -/** - * React types - */ - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type ReactElement

= JSXNode & { - type: T - props: P - key: string | null -} -type ReactNode = ReactElement | string | number | boolean | null | undefined -// eslint-disable-next-line @typescript-eslint/no-unused-vars -type ComponentClass

= unknown - -export type { ReactElement, ReactNode, ComponentClass } - -export type Event = globalThis.Event -export type MouseEvent = globalThis.MouseEvent -export type KeyboardEvent = globalThis.KeyboardEvent -export type FocusEvent = globalThis.FocusEvent -export type ClipboardEvent = globalThis.ClipboardEvent -export type InputEvent = globalThis.InputEvent -export type PointerEvent = globalThis.PointerEvent -export type TouchEvent = globalThis.TouchEvent -export type WheelEvent = globalThis.WheelEvent -export type AnimationEvent = globalThis.AnimationEvent -export type TransitionEvent = globalThis.TransitionEvent -export type DragEvent = globalThis.DragEvent diff --git a/deno_dist/jsx/utils.ts b/deno_dist/jsx/utils.ts deleted file mode 100644 index dc8926ace6..0000000000 --- a/deno_dist/jsx/utils.ts +++ /dev/null @@ -1,20 +0,0 @@ -export const normalizeIntrinsicElementProps = (props: Record): void => { - if (props && 'className' in props) { - props['class'] = props['className'] - delete props['className'] - } -} - -export const styleObjectForEach = ( - style: Record, - fn: (key: string, value: string | null) => void -): void => { - for (const [k, v] of Object.entries(style)) { - fn( - k[0] === '-' - ? k // CSS variable - : k.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`), // style property. convert to kebab-case - v == null ? null : typeof v === 'number' ? v + 'px' : (v as string) - ) - } -} diff --git a/deno_dist/middleware.ts b/deno_dist/middleware.ts deleted file mode 100644 index 5b421668b9..0000000000 --- a/deno_dist/middleware.ts +++ /dev/null @@ -1,20 +0,0 @@ -// This file is for Deno to import middleware from `hono/middleware.ts`. -export * from './middleware/basic-auth/index.ts' -export * from './middleware/bearer-auth/index.ts' -export * from './middleware/body-limit/index.ts' -export * from './middleware/cache/index.ts' -export * from './middleware/compress/index.ts' -export * from './middleware/cors/index.ts' -export * from './middleware/csrf/index.ts' -export * from './middleware/etag/index.ts' -export * from './jsx/index.ts' -export * from './middleware/jsx-renderer/index.ts' -export { jwt } from './middleware/jwt/index.ts' -export * from './middleware/logger/index.ts' -export * from './middleware/method-override/index.ts' -export * from './middleware/powered-by/index.ts' -export * from './middleware/timing/index.ts' -export * from './middleware/pretty-json/index.ts' -export * from './middleware/secure-headers/index.ts' -export * from './middleware/trailing-slash/index.ts' -export * from './adapter/deno/serve-static.ts' diff --git a/deno_dist/middleware/basic-auth/index.ts b/deno_dist/middleware/basic-auth/index.ts deleted file mode 100644 index cd05874d2a..0000000000 --- a/deno_dist/middleware/basic-auth/index.ts +++ /dev/null @@ -1,93 +0,0 @@ -import type { Context } from '../../context.ts' -import { HTTPException } from '../../http-exception.ts' -import type { HonoRequest } from '../../request.ts' -import type { MiddlewareHandler } from '../../types.ts' -import { timingSafeEqual } from '../../utils/buffer.ts' -import { decodeBase64 } from '../../utils/encode.ts' - -const CREDENTIALS_REGEXP = /^ *(?:[Bb][Aa][Ss][Ii][Cc]) +([A-Za-z0-9._~+/-]+=*) *$/ -const USER_PASS_REGEXP = /^([^:]*):(.*)$/ -const utf8Decoder = new TextDecoder() -const auth = (req: HonoRequest) => { - const match = CREDENTIALS_REGEXP.exec(req.header('Authorization') || '') - if (!match) { - return undefined - } - - let userPass = undefined - // If an invalid string is passed to atob(), it throws a `DOMException`. - try { - userPass = USER_PASS_REGEXP.exec(utf8Decoder.decode(decodeBase64(match[1]))) - } catch {} // Do nothing - - if (!userPass) { - return undefined - } - - return { username: userPass[1], password: userPass[2] } -} - -type BasicAuthOptions = - | { - username: string - password: string - realm?: string - hashFunction?: Function - } - | { - verifyUser: (username: string, password: string, c: Context) => boolean | Promise - realm?: string - hashFunction?: Function - } - -export const basicAuth = ( - options: BasicAuthOptions, - ...users: { username: string; password: string }[] -): MiddlewareHandler => { - const usernamePasswordInOptions = 'username' in options && 'password' in options - const verifyUserInOptions = 'verifyUser' in options - - if (!(usernamePasswordInOptions || verifyUserInOptions)) { - throw new Error( - 'basic auth middleware requires options for "username and password" or "verifyUser"' - ) - } - - if (!options.realm) { - options.realm = 'Secure Area' - } - - if (usernamePasswordInOptions) { - users.unshift({ username: options.username, password: options.password }) - } - - return async function basicAuth(ctx, next) { - const requestUser = auth(ctx.req) - if (requestUser) { - if (verifyUserInOptions) { - if (await options.verifyUser(requestUser.username, requestUser.password, ctx)) { - await next() - return - } - } else { - for (const user of users) { - const [usernameEqual, passwordEqual] = await Promise.all([ - timingSafeEqual(user.username, requestUser.username, options.hashFunction), - timingSafeEqual(user.password, requestUser.password, options.hashFunction), - ]) - if (usernameEqual && passwordEqual) { - await next() - return - } - } - } - } - const res = new Response('Unauthorized', { - status: 401, - headers: { - 'WWW-Authenticate': 'Basic realm="' + options.realm?.replace(/"/g, '\\"') + '"', - }, - }) - throw new HTTPException(401, { res }) - } -} diff --git a/deno_dist/middleware/bearer-auth/index.ts b/deno_dist/middleware/bearer-auth/index.ts deleted file mode 100644 index 116ed39cc5..0000000000 --- a/deno_dist/middleware/bearer-auth/index.ts +++ /dev/null @@ -1,91 +0,0 @@ -import type { Context } from '../../context.ts' -import { HTTPException } from '../../http-exception.ts' -import type { MiddlewareHandler } from '../../types.ts' -import { timingSafeEqual } from '../../utils/buffer.ts' - -const TOKEN_STRINGS = '[A-Za-z0-9._~+/-]+=*' -const PREFIX = 'Bearer' -const HEADER = 'Authorization' - -type BearerAuthOptions = - | { - token: string | string[] - realm?: string - prefix?: string - headerName?: string - hashFunction?: Function - } - | { - realm?: string - prefix?: string - headerName?: string - verifyToken: (token: string, c: Context) => boolean | Promise - hashFunction?: Function - } - -export const bearerAuth = (options: BearerAuthOptions): MiddlewareHandler => { - if (!('token' in options || 'verifyToken' in options)) { - throw new Error('bearer auth middleware requires options for "token"') - } - if (!options.realm) { - options.realm = '' - } - if (!options.prefix) { - options.prefix = PREFIX - } - - const realm = options.realm?.replace(/"/g, '\\"') - - return async function bearerAuth(c, next) { - const headerToken = c.req.header(options.headerName || HEADER) - - if (!headerToken) { - // No Authorization header - const res = new Response('Unauthorized', { - status: 401, - headers: { - 'WWW-Authenticate': `${options.prefix} realm="` + realm + '"', - }, - }) - throw new HTTPException(401, { res }) - } else { - const regexp = new RegExp('^' + options.prefix + ' +(' + TOKEN_STRINGS + ') *$') - const match = regexp.exec(headerToken) - if (!match) { - // Invalid Request - const res = new Response('Bad Request', { - status: 400, - headers: { - 'WWW-Authenticate': `${options.prefix} error="invalid_request"`, - }, - }) - throw new HTTPException(400, { res }) - } else { - let equal = false - if ('verifyToken' in options) { - equal = await options.verifyToken(match[1], c) - } else if (typeof options.token === 'string') { - equal = await timingSafeEqual(options.token, match[1], options.hashFunction) - } else if (Array.isArray(options.token) && options.token.length > 0) { - for (const token of options.token) { - if (await timingSafeEqual(token, match[1], options.hashFunction)) { - equal = true - break - } - } - } - if (!equal) { - // Invalid Token - const res = new Response('Unauthorized', { - status: 401, - headers: { - 'WWW-Authenticate': `${options.prefix} error="invalid_token"`, - }, - }) - throw new HTTPException(401, { res }) - } - } - } - await next() - } -} diff --git a/deno_dist/middleware/body-limit/index.ts b/deno_dist/middleware/body-limit/index.ts deleted file mode 100644 index 61ac304d96..0000000000 --- a/deno_dist/middleware/body-limit/index.ts +++ /dev/null @@ -1,96 +0,0 @@ -import type { Context } from '../../context.ts' -import { HTTPException } from '../../http-exception.ts' -import type { MiddlewareHandler } from '../../types.ts' - -const ERROR_MESSAGE = 'Payload Too Large' - -type OnError = (c: Context) => Response | Promise -type BodyLimitOptions = { - maxSize: number - onError?: OnError -} - -class BodyLimitError extends Error { - constructor(message: string) { - super(message) - this.name = 'BodyLimitError' - } -} - -/** - * Body Limit Middleware - * - * @example - * ```ts - * app.post( - * '/hello', - * bodyLimit({ - * maxSize: 100 * 1024, // 100kb - * onError: (c) => { - * return c.text('overflow :(', 413) - * } - * }), - * (c) => { - * return c.text('pass :)') - * } - * ) - * ``` - */ -export const bodyLimit = (options: BodyLimitOptions): MiddlewareHandler => { - const onError: OnError = - options.onError || - (() => { - const res = new Response(ERROR_MESSAGE, { - status: 413, - }) - throw new HTTPException(413, { res }) - }) - const maxSize = options.maxSize - - return async function bodyLimit(c, next) { - if (!c.req.raw.body) { - // maybe GET or HEAD request - return next() - } - - if (c.req.raw.headers.has('content-length')) { - // we can trust content-length header because it's already validated by server - const contentLength = parseInt(c.req.raw.headers.get('content-length') || '0', 10) - return contentLength > maxSize ? onError(c) : next() - } - - // maybe chunked transfer encoding - - let size = 0 - const rawReader = c.req.raw.body.getReader() - const reader = new ReadableStream({ - async start(controller) { - try { - for (;;) { - const { done, value } = await rawReader.read() - if (done) { - break - } - size += value.length - if (size > maxSize) { - controller.error(new BodyLimitError(ERROR_MESSAGE)) - break - } - - controller.enqueue(value) - } - } finally { - controller.close() - } - }, - }) - - c.req.raw = new Request(c.req.raw, { body: reader }) - - await next() - - if (c.error instanceof BodyLimitError) { - c.res = await onError(c) - } - } -} diff --git a/deno_dist/middleware/cache/index.ts b/deno_dist/middleware/cache/index.ts deleted file mode 100644 index 5abd6f5d4f..0000000000 --- a/deno_dist/middleware/cache/index.ts +++ /dev/null @@ -1,90 +0,0 @@ -import type { Context } from '../../context.ts' -import type { MiddlewareHandler } from '../../types.ts' - -export const cache = (options: { - cacheName: string - wait?: boolean - cacheControl?: string - vary?: string | string[] -}): MiddlewareHandler => { - if (!globalThis.caches) { - console.log('Cache Middleware is not enabled because caches is not defined.') - return async (_c, next) => await next() - } - - if (options.wait === undefined) { - options.wait = false - } - - const cacheControlDirectives = options.cacheControl - ?.split(',') - .map((directive) => directive.toLowerCase()) - const varyDirectives = Array.isArray(options.vary) - ? options.vary - : options.vary?.split(',').map((directive) => directive.trim()) - // RFC 7231 Section 7.1.4 specifies that "*" is not allowed in Vary header. - // See: https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.4 - if (options.vary?.includes('*')) { - throw new Error( - 'Middleware vary configuration cannot include "*", as it disallows effective caching.' - ) - } - - const addHeader = (c: Context) => { - if (cacheControlDirectives) { - const existingDirectives = - c.res.headers - .get('Cache-Control') - ?.split(',') - .map((d) => d.trim().split('=', 1)[0]) ?? [] - for (const directive of cacheControlDirectives) { - let [name, value] = directive.trim().split('=', 2) - name = name.toLowerCase() - if (!existingDirectives.includes(name)) { - c.header('Cache-Control', `${name}${value ? `=${value}` : ''}`, { append: true }) - } - } - } - - if (varyDirectives) { - const existingDirectives = - c.res.headers - .get('Vary') - ?.split(',') - .map((d) => d.trim()) ?? [] - - const vary = Array.from( - new Set( - [...existingDirectives, ...varyDirectives].map((directive) => directive.toLowerCase()) - ) - ).sort() - - if (vary.includes('*')) { - c.header('Vary', '*') - } else { - c.header('Vary', vary.join(', ')) - } - } - } - - return async function cache(c, next) { - const key = c.req.url - const cache = await caches.open(options.cacheName) - const response = await cache.match(key) - if (response) { - return new Response(response.body, response) - } - - await next() - if (!c.res.ok) { - return - } - addHeader(c) - const res = c.res.clone() - if (options.wait) { - await cache.put(key, res) - } else { - c.executionCtx.waitUntil(cache.put(key, res)) - } - } -} diff --git a/deno_dist/middleware/compress/index.ts b/deno_dist/middleware/compress/index.ts deleted file mode 100644 index 46052adfed..0000000000 --- a/deno_dist/middleware/compress/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { MiddlewareHandler } from '../../types.ts' - -const ENCODING_TYPES = ['gzip', 'deflate'] as const - -interface CompressionOptions { - encoding?: (typeof ENCODING_TYPES)[number] -} - -export const compress = (options?: CompressionOptions): MiddlewareHandler => { - return async function compress(ctx, next) { - await next() - const accepted = ctx.req.header('Accept-Encoding') - const encoding = - options?.encoding ?? ENCODING_TYPES.find((encoding) => accepted?.includes(encoding)) - if (!encoding || !ctx.res.body) { - return - } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const stream = new CompressionStream(encoding) - ctx.res = new Response(ctx.res.body.pipeThrough(stream), ctx.res) - ctx.res.headers.delete('Content-Length') - ctx.res.headers.set('Content-Encoding', encoding) - } -} diff --git a/deno_dist/middleware/cors/index.ts b/deno_dist/middleware/cors/index.ts deleted file mode 100644 index fe3b530e2a..0000000000 --- a/deno_dist/middleware/cors/index.ts +++ /dev/null @@ -1,91 +0,0 @@ -import type { Context } from '../../context.ts' -import type { MiddlewareHandler } from '../../types.ts' - -type CORSOptions = { - origin: string | string[] | ((origin: string, c: Context) => string | undefined | null) - allowMethods?: string[] - allowHeaders?: string[] - maxAge?: number - credentials?: boolean - exposeHeaders?: string[] -} - -export const cors = (options?: CORSOptions): MiddlewareHandler => { - const defaults: CORSOptions = { - origin: '*', - allowMethods: ['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH'], - allowHeaders: [], - exposeHeaders: [], - } - const opts = { - ...defaults, - ...options, - } - - const findAllowOrigin = ((optsOrigin) => { - if (typeof optsOrigin === 'string') { - return () => optsOrigin - } else if (typeof optsOrigin === 'function') { - return optsOrigin - } else { - return (origin: string) => (optsOrigin.includes(origin) ? origin : optsOrigin[0]) - } - })(opts.origin) - - return async function cors(c, next) { - function set(key: string, value: string) { - c.res.headers.set(key, value) - } - - const allowOrigin = findAllowOrigin(c.req.header('origin') || '', c) - if (allowOrigin) { - set('Access-Control-Allow-Origin', allowOrigin) - } - - // Suppose the server sends a response with an Access-Control-Allow-Origin value with an explicit origin (rather than the "*" wildcard). - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin - if (opts.origin !== '*') { - set('Vary', 'Origin') - } - - if (opts.credentials) { - set('Access-Control-Allow-Credentials', 'true') - } - - if (opts.exposeHeaders?.length) { - set('Access-Control-Expose-Headers', opts.exposeHeaders.join(',')) - } - - if (c.req.method === 'OPTIONS') { - if (opts.maxAge != null) { - set('Access-Control-Max-Age', opts.maxAge.toString()) - } - - if (opts.allowMethods?.length) { - set('Access-Control-Allow-Methods', opts.allowMethods.join(',')) - } - - let headers = opts.allowHeaders - if (!headers?.length) { - const requestHeaders = c.req.header('Access-Control-Request-Headers') - if (requestHeaders) { - headers = requestHeaders.split(/\s*,\s*/) - } - } - if (headers?.length) { - set('Access-Control-Allow-Headers', headers.join(',')) - c.res.headers.append('Vary', 'Access-Control-Request-Headers') - } - - c.res.headers.delete('Content-Length') - c.res.headers.delete('Content-Type') - - return new Response(null, { - headers: c.res.headers, - status: 204, - statusText: c.res.statusText, - }) - } - await next() - } -} diff --git a/deno_dist/middleware/csrf/index.ts b/deno_dist/middleware/csrf/index.ts deleted file mode 100644 index dc6d4e4cc2..0000000000 --- a/deno_dist/middleware/csrf/index.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { Context } from '../../context.ts' -import { HTTPException } from '../../http-exception.ts' -import type { MiddlewareHandler } from '../../types.ts' - -type IsAllowedOriginHandler = (origin: string, context: Context) => boolean -interface CSRFOptions { - origin?: string | string[] | IsAllowedOriginHandler -} - -const isSafeMethodRe = /^(GET|HEAD)$/ -const isRequestedByFormElementRe = - /^\b(application\/x-www-form-urlencoded|multipart\/form-data|text\/plain)\b/ - -export const csrf = (options?: CSRFOptions): MiddlewareHandler => { - const handler: IsAllowedOriginHandler = ((optsOrigin) => { - if (!optsOrigin) { - return (origin, c) => origin === new URL(c.req.url).origin - } else if (typeof optsOrigin === 'string') { - return (origin) => origin === optsOrigin - } else if (typeof optsOrigin === 'function') { - return optsOrigin - } else { - return (origin) => optsOrigin.includes(origin) - } - })(options?.origin) - const isAllowedOrigin = (origin: string | undefined, c: Context) => { - if (origin === undefined) { - // denied always when origin header is not present - return false - } - return handler(origin, c) - } - - return async function cors(c, next) { - if ( - !isSafeMethodRe.test(c.req.method) && - isRequestedByFormElementRe.test(c.req.header('content-type') || '') && - !isAllowedOrigin(c.req.header('origin'), c) - ) { - const res = new Response('Forbidden', { - status: 403, - }) - throw new HTTPException(403, { res }) - } - - await next() - } -} diff --git a/deno_dist/middleware/etag/index.ts b/deno_dist/middleware/etag/index.ts deleted file mode 100644 index 6c44ae6fd8..0000000000 --- a/deno_dist/middleware/etag/index.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { MiddlewareHandler } from '../../types.ts' -import { sha1 } from '../../utils/crypto.ts' - -type ETagOptions = { - retainedHeaders?: string[] - weak?: boolean -} - -/** - * Default headers to pass through on 304 responses. From the spec: - * > The response must not contain a body and must include the headers that - * > would have been sent in an equivalent 200 OK response: Cache-Control, - * > Content-Location, Date, ETag, Expires, and Vary. - */ -const RETAINED_304_HEADERS = [ - 'cache-control', - 'content-location', - 'date', - 'etag', - 'expires', - 'vary', -] - -function etagMatches(etag: string, ifNoneMatch: string | null) { - return ifNoneMatch != null && ifNoneMatch.split(/,\s*/).indexOf(etag) > -1 -} - -export const etag = (options?: ETagOptions): MiddlewareHandler => { - const retainedHeaders = options?.retainedHeaders ?? RETAINED_304_HEADERS - const weak = options?.weak ?? false - - return async function etag(c, next) { - const ifNoneMatch = c.req.header('If-None-Match') ?? null - - await next() - - const res = c.res as Response - let etag = res.headers.get('ETag') - - if (!etag) { - const hash = await sha1(res.clone().body || '') - etag = weak ? `W/"${hash}"` : `"${hash}"` - } - - if (etagMatches(etag, ifNoneMatch)) { - await c.res.blob() // Force using body - c.res = new Response(null, { - status: 304, - statusText: 'Not Modified', - headers: { - ETag: etag, - }, - }) - c.res.headers.forEach((_, key) => { - if (retainedHeaders.indexOf(key.toLowerCase()) === -1) { - c.res.headers.delete(key) - } - }) - } else { - c.res.headers.set('ETag', etag) - } - } -} diff --git a/deno_dist/middleware/jsx-renderer/index.ts b/deno_dist/middleware/jsx-renderer/index.ts deleted file mode 100644 index 0d76102e36..0000000000 --- a/deno_dist/middleware/jsx-renderer/index.ts +++ /dev/null @@ -1,95 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import type { Context, PropsForRenderer } from '../../context.ts' -import { html, raw } from '../../helper/html/index.ts' -import { jsx, createContext, useContext, Fragment } from '../../jsx/index.ts' -import type { FC, PropsWithChildren, JSXNode } from '../../jsx/index.ts' -import type { Context as JSXContext } from '../../jsx/index.ts' -import { renderToReadableStream } from '../../jsx/streaming.ts' -import type { Env, Input, MiddlewareHandler } from '../../types.ts' -import type { HtmlEscapedString } from '../../utils/html.ts' - -export const RequestContext: JSXContext | null> = - createContext(null) - -type RendererOptions = { - docType?: boolean | string - stream?: boolean | Record -} - -type Component = ( - props: PropsForRenderer & { Layout: FC }, - c: Context -) => HtmlEscapedString | Promise - -type ComponentWithChildren = ( - props: PropsWithChildren, - c: Context -) => HtmlEscapedString | Promise - -const createRenderer = - (c: Context, Layout: FC, component?: Component, options?: RendererOptions) => - (children: JSXNode, props: PropsForRenderer) => { - const docType = - typeof options?.docType === 'string' - ? options.docType - : options?.docType === false - ? '' - : '' - - const currentLayout = component - ? jsx( - (props: any) => component(props, c), - { - ...{ Layout, ...(props as any) }, - }, - children as any - ) - : children - - const body = html`${raw(docType)}${jsx( - RequestContext.Provider, - { value: c }, - currentLayout as any - )}` - - if (options?.stream) { - if (options.stream === true) { - c.header('Transfer-Encoding', 'chunked') - c.header('Content-Type', 'text/html; charset=UTF-8') - } else { - for (const [key, value] of Object.entries(options.stream)) { - c.header(key, value) - } - } - return c.body(renderToReadableStream(body)) - } else { - return c.html(body) - } - } - -export const jsxRenderer = ( - component?: ComponentWithChildren, - options?: RendererOptions -): MiddlewareHandler => - function jsxRenderer(c, next) { - const Layout = (c.getLayout() ?? Fragment) as FC - if (component) { - c.setLayout((props) => { - return component({ ...props, Layout }, c) - }) - } - c.setRenderer(createRenderer(c, Layout, component, options) as any) - return next() - } - -export const useRequestContext = < - E extends Env = any, - P extends string = any, - I extends Input = {} ->(): Context => { - const c = useContext(RequestContext) - if (!c) { - throw new Error('RequestContext is not provided.') - } - return c -} diff --git a/deno_dist/middleware/jwt/index.ts b/deno_dist/middleware/jwt/index.ts deleted file mode 100644 index 490177fd63..0000000000 --- a/deno_dist/middleware/jwt/index.ts +++ /dev/null @@ -1,106 +0,0 @@ -import type { Context } from '../../context.ts' -import { getCookie } from '../../helper/cookie/index.ts' -import { HTTPException } from '../../http-exception.ts' -import type { MiddlewareHandler } from '../../types.ts' -import { Jwt } from '../../utils/jwt/index.ts' -import '../../context.ts' -import type { SignatureAlgorithm } from '../../utils/jwt/jwa.ts' - -declare module '../../context.ts' { - interface ContextVariableMap { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - jwtPayload: any - } -} - -export const jwt = (options: { - secret: string - cookie?: string - alg?: SignatureAlgorithm -}): MiddlewareHandler => { - if (!options) { - throw new Error('JWT auth middleware requires options for "secret') - } - - if (!crypto.subtle || !crypto.subtle.importKey) { - throw new Error('`crypto.subtle.importKey` is undefined. JWT auth middleware requires it.') - } - - return async function jwt(ctx, next) { - const credentials = ctx.req.raw.headers.get('Authorization') - let token - if (credentials) { - const parts = credentials.split(/\s+/) - if (parts.length !== 2) { - const errDescription = 'invalid credentials structure' - throw new HTTPException(401, { - message: errDescription, - res: unauthorizedResponse({ - ctx, - error: 'invalid_request', - errDescription, - }), - }) - } else { - token = parts[1] - } - } else if (options.cookie) { - token = getCookie(ctx)[options.cookie] - } - - if (!token) { - const errDescription = 'no authorization included in request' - throw new HTTPException(401, { - message: errDescription, - res: unauthorizedResponse({ - ctx, - error: 'invalid_request', - errDescription, - }), - }) - } - - let payload - let cause - try { - payload = await Jwt.verify(token, options.secret, options.alg) - } catch (e) { - cause = e - } - if (!payload) { - throw new HTTPException(401, { - message: 'Unauthorized', - res: unauthorizedResponse({ - ctx, - error: 'invalid_token', - statusText: 'Unauthorized', - errDescription: 'token verification failure', - }), - cause, - }) - } - - ctx.set('jwtPayload', payload) - - await next() - } -} - -function unauthorizedResponse(opts: { - ctx: Context - error: string - errDescription: string - statusText?: string -}) { - return new Response('Unauthorized', { - status: 401, - statusText: opts.statusText, - headers: { - 'WWW-Authenticate': `Bearer realm="${opts.ctx.req.url}",error="${opts.error}",error_description="${opts.errDescription}"`, - }, - }) -} - -export const verify = Jwt.verify -export const decode = Jwt.decode -export const sign = Jwt.sign diff --git a/deno_dist/middleware/logger/index.ts b/deno_dist/middleware/logger/index.ts deleted file mode 100644 index 2d10050f87..0000000000 --- a/deno_dist/middleware/logger/index.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { MiddlewareHandler } from '../../types.ts' -import { getColorEnabled } from '../../utils/color.ts' -import { getPath } from '../../utils/url.ts' - -enum LogPrefix { - Outgoing = '-->', - Incoming = '<--', - Error = 'xxx', -} - -const humanize = (times: string[]) => { - const [delimiter, separator] = [',', '.'] - - const orderTimes = times.map((v) => v.replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1' + delimiter)) - - return orderTimes.join(separator) -} - -const time = (start: number) => { - const delta = Date.now() - start - return humanize([delta < 1000 ? delta + 'ms' : Math.round(delta / 1000) + 's']) -} - -const colorStatus = (status: number) => { - const colorEnabled = getColorEnabled() - const out: { [key: string]: string } = { - 7: colorEnabled ? `\x1b[35m${status}\x1b[0m` : `${status}`, - 5: colorEnabled ? `\x1b[31m${status}\x1b[0m` : `${status}`, - 4: colorEnabled ? `\x1b[33m${status}\x1b[0m` : `${status}`, - 3: colorEnabled ? `\x1b[36m${status}\x1b[0m` : `${status}`, - 2: colorEnabled ? `\x1b[32m${status}\x1b[0m` : `${status}`, - 1: colorEnabled ? `\x1b[32m${status}\x1b[0m` : `${status}`, - 0: colorEnabled ? `\x1b[33m${status}\x1b[0m` : `${status}`, - } - - const calculateStatus = (status / 100) | 0 - - return out[calculateStatus] -} - -type PrintFunc = (str: string, ...rest: string[]) => void - -function log( - fn: PrintFunc, - prefix: string, - method: string, - path: string, - status: number = 0, - elapsed?: string -) { - const out = - prefix === LogPrefix.Incoming - ? ` ${prefix} ${method} ${path}` - : ` ${prefix} ${method} ${path} ${colorStatus(status)} ${elapsed}` - fn(out) -} - -export const logger = (fn: PrintFunc = console.log): MiddlewareHandler => { - return async function logger(c, next) { - const { method } = c.req - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const path = getPath(c.req.raw) - - log(fn, LogPrefix.Incoming, method, path) - - const start = Date.now() - - await next() - - log(fn, LogPrefix.Outgoing, method, path, c.res.status, time(start)) - } -} diff --git a/deno_dist/middleware/method-override/index.ts b/deno_dist/middleware/method-override/index.ts deleted file mode 100644 index f9748990b2..0000000000 --- a/deno_dist/middleware/method-override/index.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { URLSearchParams } from 'node:url' -import type { Context, ExecutionContext } from '../../context.ts' -import type { Hono } from '../../hono.ts' -import type { MiddlewareHandler } from '../../types.ts' -import { parseBody } from '../../utils/body.ts' - -type MethodOverrideOptions = { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - app: Hono -} & ( - | { - // Default is 'form' and the value is `_method` - form?: string - header?: never - query?: never - } - | { - form?: never - header: string - query?: never - } - | { - form?: never - header?: never - query: string - } -) - -const DEFAULT_METHOD_FORM_NAME = '_method' - -/** - * Method Override Middleware - * - * @example - * // with form input method - * const app = new Hono() - * app.use('/books/*', methodOverride({ app })) // the default `form` value is `_method` - * app.use('/authors/*', methodOverride({ app, form: 'method' })) - * - * @example - * // with custom header - * app.use('/books/*', methodOverride({ app, header: 'X-HTTP-METHOD-OVERRIDE' })) - * - * @example - * // with query parameter - * app.use('/books/*', methodOverride({ app, query: '_method' })) - */ -export const methodOverride = (options: MethodOverrideOptions): MiddlewareHandler => - async function methodOverride(c, next) { - if (c.req.method === 'GET') { - return await next() - } - - const app = options.app - // Method override by form - if (!(options.header || options.query)) { - const contentType = c.req.header('content-type') - const methodFormName = options.form || DEFAULT_METHOD_FORM_NAME - const clonedRequest = c.req.raw.clone() - const newRequest = clonedRequest.clone() - // Content-Type is `multipart/form-data` - if (contentType?.startsWith('multipart/form-data')) { - const form = await clonedRequest.formData() - const method = form.get(methodFormName) - if (method) { - const newForm = await newRequest.formData() - newForm.delete(methodFormName) - const newHeaders = new Headers(clonedRequest.headers) - newHeaders.delete('content-type') - newHeaders.delete('content-length') - const request = new Request(c.req.url, { - body: newForm, - headers: newHeaders, - method: method as string, - }) - return app.fetch(request, c.env, getExecutionCtx(c)) - } - } - // Content-Type is `application/x-www-form-urlencoded` - if (contentType === 'application/x-www-form-urlencoded') { - const params = await parseBody>(clonedRequest) - const method = params[methodFormName] - if (method) { - delete params[methodFormName] - const newParams = new URLSearchParams(params) - const request = new Request(newRequest, { - body: newParams, - method: method as string, - }) - return app.fetch(request, c.env, getExecutionCtx(c)) - } - } - } - // Method override by header - else if (options.header) { - const headerName = options.header - const method = c.req.header(headerName) - if (method) { - const newHeaders = new Headers(c.req.raw.headers) - newHeaders.delete(headerName) - const request = new Request(c.req.raw, { - headers: newHeaders, - method, - }) - return app.fetch(request, c.env, getExecutionCtx(c)) - } - } - // Method override by query - else if (options.query) { - const queryName = options.query - const method = c.req.query(queryName) - if (method) { - const url = new URL(c.req.url) - url.searchParams.delete(queryName) - const request = new Request(url.toString(), { - body: c.req.raw.body, - headers: c.req.raw.headers, - method, - }) - return app.fetch(request, c.env, getExecutionCtx(c)) - } - } - await next() - } - -const getExecutionCtx = (c: Context) => { - let executionCtx: ExecutionContext | undefined - try { - executionCtx = c.executionCtx - } catch { - // Do nothing - } - return executionCtx -} diff --git a/deno_dist/middleware/powered-by/index.ts b/deno_dist/middleware/powered-by/index.ts deleted file mode 100644 index 4b1e12db07..0000000000 --- a/deno_dist/middleware/powered-by/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { MiddlewareHandler } from '../../types.ts' - -export const poweredBy = (): MiddlewareHandler => { - return async function poweredBy(c, next) { - await next() - c.res.headers.set('X-Powered-By', 'Hono') - } -} diff --git a/deno_dist/middleware/pretty-json/index.ts b/deno_dist/middleware/pretty-json/index.ts deleted file mode 100644 index 002631f2d3..0000000000 --- a/deno_dist/middleware/pretty-json/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { MiddlewareHandler } from '../../types.ts' - -type prettyOptions = { - space: number -} - -export const prettyJSON = (options: prettyOptions = { space: 2 }): MiddlewareHandler => { - return async function prettyJSON(c, next) { - const pretty = c.req.query('pretty') || c.req.query('pretty') === '' ? true : false - await next() - if (pretty && c.res.headers.get('Content-Type')?.startsWith('application/json')) { - const obj = await c.res.json() - c.res = new Response(JSON.stringify(obj, null, options.space), c.res) - } - } -} diff --git a/deno_dist/middleware/secure-headers/index.ts b/deno_dist/middleware/secure-headers/index.ts deleted file mode 100644 index 3d978eeb94..0000000000 --- a/deno_dist/middleware/secure-headers/index.ts +++ /dev/null @@ -1,233 +0,0 @@ -import { Buffer } from "node:buffer"; -import type { Context } from '../../context.ts' -import type { MiddlewareHandler } from '../../types.ts' - -declare module '../../context.ts' { - interface ContextVariableMap { - secureHeadersNonce?: string - } -} - -export type ContentSecurityPolicyOptionHandler = (ctx: Context, directive: string) => string -type ContentSecurityPolicyOptionValue = (string | ContentSecurityPolicyOptionHandler)[] - -interface ContentSecurityPolicyOptions { - defaultSrc?: ContentSecurityPolicyOptionValue - baseUri?: ContentSecurityPolicyOptionValue - childSrc?: ContentSecurityPolicyOptionValue - connectSrc?: ContentSecurityPolicyOptionValue - fontSrc?: ContentSecurityPolicyOptionValue - formAction?: ContentSecurityPolicyOptionValue - frameAncestors?: ContentSecurityPolicyOptionValue - frameSrc?: ContentSecurityPolicyOptionValue - imgSrc?: ContentSecurityPolicyOptionValue - manifestSrc?: ContentSecurityPolicyOptionValue - mediaSrc?: ContentSecurityPolicyOptionValue - objectSrc?: ContentSecurityPolicyOptionValue - reportTo?: string - sandbox?: ContentSecurityPolicyOptionValue - scriptSrc?: ContentSecurityPolicyOptionValue - scriptSrcAttr?: ContentSecurityPolicyOptionValue - scriptSrcElem?: ContentSecurityPolicyOptionValue - styleSrc?: ContentSecurityPolicyOptionValue - styleSrcAttr?: ContentSecurityPolicyOptionValue - styleSrcElem?: ContentSecurityPolicyOptionValue - upgradeInsecureRequests?: ContentSecurityPolicyOptionValue - workerSrc?: ContentSecurityPolicyOptionValue -} - -interface ReportToOptions { - group: string - max_age: number - endpoints: ReportToEndpoint[] -} - -interface ReportToEndpoint { - url: string -} - -interface ReportingEndpointOptions { - name: string - url: string -} - -type overridableHeader = boolean | string - -interface SecureHeadersOptions { - contentSecurityPolicy?: ContentSecurityPolicyOptions - crossOriginEmbedderPolicy?: overridableHeader - crossOriginResourcePolicy?: overridableHeader - crossOriginOpenerPolicy?: overridableHeader - originAgentCluster: overridableHeader - referrerPolicy?: overridableHeader - reportingEndpoints?: ReportingEndpointOptions[] - reportTo?: ReportToOptions[] - strictTransportSecurity?: overridableHeader - xContentTypeOptions?: overridableHeader - xDnsPrefetchControl?: overridableHeader - xDownloadOptions?: overridableHeader - xFrameOptions?: overridableHeader - xPermittedCrossDomainPolicies?: overridableHeader - xXssProtection?: overridableHeader -} - -type HeadersMap = { - [key in keyof SecureHeadersOptions]: [string, string] -} - -const HEADERS_MAP: HeadersMap = { - crossOriginEmbedderPolicy: ['Cross-Origin-Embedder-Policy', 'require-corp'], - crossOriginResourcePolicy: ['Cross-Origin-Resource-Policy', 'same-origin'], - crossOriginOpenerPolicy: ['Cross-Origin-Opener-Policy', 'same-origin'], - originAgentCluster: ['Origin-Agent-Cluster', '?1'], - referrerPolicy: ['Referrer-Policy', 'no-referrer'], - strictTransportSecurity: ['Strict-Transport-Security', 'max-age=15552000; includeSubDomains'], - xContentTypeOptions: ['X-Content-Type-Options', 'nosniff'], - xDnsPrefetchControl: ['X-DNS-Prefetch-Control', 'off'], - xDownloadOptions: ['X-Download-Options', 'noopen'], - xFrameOptions: ['X-Frame-Options', 'SAMEORIGIN'], - xPermittedCrossDomainPolicies: ['X-Permitted-Cross-Domain-Policies', 'none'], - xXssProtection: ['X-XSS-Protection', '0'], -} - -const DEFAULT_OPTIONS: SecureHeadersOptions = { - crossOriginEmbedderPolicy: false, - crossOriginResourcePolicy: true, - crossOriginOpenerPolicy: true, - originAgentCluster: true, - referrerPolicy: true, - strictTransportSecurity: true, - xContentTypeOptions: true, - xDnsPrefetchControl: true, - xDownloadOptions: true, - xFrameOptions: true, - xPermittedCrossDomainPolicies: true, - xXssProtection: true, -} - -type SecureHeadersCallback = ( - ctx: Context, - headersToSet: [string, string | string[]][] -) => [string, string][] - -const generateNonce = () => { - const buffer = new Uint8Array(16) - crypto.getRandomValues(buffer) - return Buffer.from(buffer).toString('base64') -} -export const NONCE: ContentSecurityPolicyOptionHandler = (ctx) => { - const nonce = - ctx.get('secureHeadersNonce') || - (() => { - const newNonce = generateNonce() - ctx.set('secureHeadersNonce', newNonce) - return newNonce - })() - return `'nonce-${nonce}'` -} - -export const secureHeaders = (customOptions?: Partial): MiddlewareHandler => { - const options = { ...DEFAULT_OPTIONS, ...customOptions } - const headersToSet = getFilteredHeaders(options) - const callbacks: SecureHeadersCallback[] = [] - - if (options.contentSecurityPolicy) { - const [callback, value] = getCSPDirectives(options.contentSecurityPolicy) - if (callback) { - callbacks.push(callback) - } - headersToSet.push(['Content-Security-Policy', value as string]) - } - - if (options.reportingEndpoints) { - headersToSet.push(['Reporting-Endpoints', getReportingEndpoints(options.reportingEndpoints)]) - } - - if (options.reportTo) { - headersToSet.push(['Report-To', getReportToOptions(options.reportTo)]) - } - - return async function secureHeaders(ctx, next) { - // should evaluate callbacks before next() - // some callback calls ctx.set() for embedding nonce to the page - const headersToSetForReq = - callbacks.length === 0 - ? headersToSet - : callbacks.reduce((acc, cb) => cb(ctx, acc), headersToSet) - await next() - setHeaders(ctx, headersToSetForReq) - 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: ContentSecurityPolicyOptions -): [SecureHeadersCallback | undefined, string | string[]] { - const callbacks: ((ctx: Context, values: string[]) => void)[] = [] - const resultValues: string[] = [] - - for (const [directive, value] of Object.entries(contentSecurityPolicy)) { - const valueArray = Array.isArray(value) ? value : [value] - - valueArray.forEach((value, i) => { - if (typeof value === 'function') { - const index = i * 2 + 2 + resultValues.length - callbacks.push((ctx, values) => { - values[index] = value(ctx, directive) - }) - } - }) - - resultValues.push( - directive.replace(/[A-Z]+(?![a-z])|[A-Z]/g, (match, offset) => - offset ? '-' + match.toLowerCase() : match.toLowerCase() - ), - ...valueArray.flatMap((value) => [' ', value]), - '; ' - ) - } - resultValues.pop() - - return callbacks.length === 0 - ? [undefined, resultValues.join('')] - : [ - (ctx, headersToSet) => - headersToSet.map((values) => { - if (values[0] === 'Content-Security-Policy') { - const clone = values[1].slice() as unknown as string[] - callbacks.forEach((cb) => { - cb(ctx, clone) - }) - return [values[0], clone.join('')] - } else { - return values as [string, string] - } - }), - resultValues, - ] -} - -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) - }) -} diff --git a/deno_dist/middleware/serve-static/index.ts b/deno_dist/middleware/serve-static/index.ts deleted file mode 100644 index ffe71e3923..0000000000 --- a/deno_dist/middleware/serve-static/index.ts +++ /dev/null @@ -1,89 +0,0 @@ -import type { Context, Data } from '../../context.ts' -import type { Env, MiddlewareHandler } from '../../types.ts' -import { getFilePath, getFilePathWithoutDefaultDocument } from '../../utils/filepath.ts' -import { getMimeType } from '../../utils/mime.ts' - -export type ServeStaticOptions = { - root?: string - path?: string - mimes?: Record - rewriteRequestPath?: (path: string) => string - onNotFound?: (path: string, c: Context) => void | Promise -} - -const DEFAULT_DOCUMENT = 'index.html' -const defaultPathResolve = (path: string) => path - -/** - * This middleware is not directly used by the user. Create a wrapper specifying `getContent()` by the environment such as Deno or Bun. - */ -export const serveStatic = ( - options: ServeStaticOptions & { - getContent: (path: string, c: Context) => Promise - pathResolve?: (path: string) => string - } -): MiddlewareHandler => { - return async (c, next) => { - // Do nothing if Response is already set - if (c.finalized) { - await next() - return - } - - let filename = options.path ?? decodeURI(c.req.path) - filename = options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename - const root = options.root - - let path = getFilePath({ - filename, - root, - defaultDocument: DEFAULT_DOCUMENT, - }) - - if (!path) { - return await next() - } - - const getContent = options.getContent - const pathResolve = options.pathResolve ?? defaultPathResolve - - path = pathResolve(path) - let content = await getContent(path, c) - - if (!content) { - let pathWithOutDefaultDocument = getFilePathWithoutDefaultDocument({ - filename, - root, - }) - if (!pathWithOutDefaultDocument) { - return await next() - } - pathWithOutDefaultDocument = pathResolve(pathWithOutDefaultDocument) - content = await getContent(pathWithOutDefaultDocument, c) - if (content) { - path = pathWithOutDefaultDocument - } - } - - if (content instanceof Response) { - return c.newResponse(content.body, content) - } - - if (content) { - let mimeType: string | undefined - if (options.mimes) { - mimeType = getMimeType(path, options.mimes) ?? getMimeType(path) - } else { - mimeType = getMimeType(path) - } - if (mimeType) { - c.header('Content-Type', mimeType) - } - return c.body(content) - } - - await options.onNotFound?.(path, c) - await next() - return - } -} diff --git a/deno_dist/middleware/timing/index.ts b/deno_dist/middleware/timing/index.ts deleted file mode 100644 index 3881f8ce4b..0000000000 --- a/deno_dist/middleware/timing/index.ts +++ /dev/null @@ -1,139 +0,0 @@ -import type { Context } from '../../context.ts' -import type { MiddlewareHandler } from '../../types.ts' -import '../../context.ts' - -declare module '../../context.ts' { - interface ContextVariableMap { - metric?: { - headers: string[] - timers: Map - } - } -} - -interface Timer { - description?: string - start: number -} - -interface TimingOptions { - total: boolean - enabled: boolean | ((c: Context) => boolean) - totalDescription: string - autoEnd: boolean - crossOrigin: boolean | string | ((c: Context) => boolean | string) -} - -const getTime = (): number => { - try { - return performance.now() - } catch {} - return Date.now() -} - -export const timing = (config?: Partial): MiddlewareHandler => { - const options: TimingOptions = { - ...{ - total: true, - enabled: true, - totalDescription: 'Total Response Time', - autoEnd: true, - crossOrigin: false, - }, - ...config, - } - return async function timing(c, next) { - const headers: string[] = [] - const timers = new Map() - c.set('metric', { headers, timers }) - - if (options.total) { - startTime(c, 'total', options.totalDescription) - } - await next() - - if (options.total) { - endTime(c, 'total') - } - - if (options.autoEnd) { - timers.forEach((_, key) => endTime(c, key)) - } - - const enabled = typeof options.enabled === 'function' ? options.enabled(c) : options.enabled - - if (enabled) { - c.res.headers.append('Server-Timing', headers.join(',')) - - const crossOrigin = - typeof options.crossOrigin === 'function' ? options.crossOrigin(c) : options.crossOrigin - - if (crossOrigin) { - c.res.headers.append( - 'Timing-Allow-Origin', - typeof crossOrigin === 'string' ? crossOrigin : '*' - ) - } - } - } -} - -interface SetMetric { - (c: Context, name: string, value: number, description?: string, precision?: number): void - - (c: Context, name: string, description?: string): void -} - -export const setMetric: SetMetric = ( - c: Context, - name: string, - valueDescription: number | string | undefined, - description?: string, - precision?: number -) => { - const metrics = c.get('metric') - if (!metrics) { - console.warn('Metrics not initialized! Please add the `timing()` middleware to this route!') - return - } - if (typeof valueDescription === 'number') { - const dur = valueDescription.toFixed(precision || 1) - - const metric = description ? `${name};dur=${dur};desc="${description}"` : `${name};dur=${dur}` - - metrics.headers.push(metric) - } else { - // Value-less metric - const metric = valueDescription ? `${name};desc="${valueDescription}"` : `${name}` - - metrics.headers.push(metric) - } -} - -export const startTime = (c: Context, name: string, description?: string) => { - const metrics = c.get('metric') - if (!metrics) { - console.warn('Metrics not initialized! Please add the `timing()` middleware to this route!') - return - } - metrics.timers.set(name, { description, start: getTime() }) -} - -export const endTime = (c: Context, name: string, precision?: number) => { - const metrics = c.get('metric') - if (!metrics) { - console.warn('Metrics not initialized! Please add the `timing()` middleware to this route!') - return - } - const timer = metrics.timers.get(name) - if (!timer) { - console.warn(`Timer "${name}" does not exist!`) - return - } - const { description, start } = timer - - const duration = getTime() - start - - setMetric(c, name, duration, description, precision) - metrics.timers.delete(name) -} diff --git a/deno_dist/middleware/trailing-slash/index.ts b/deno_dist/middleware/trailing-slash/index.ts deleted file mode 100644 index f09492eb0a..0000000000 --- a/deno_dist/middleware/trailing-slash/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { MiddlewareHandler } from '../../types.ts' - -/** - * Trim the trailing slash from the URL if it does have one. For example, `/path/to/page/` will be redirected to `/path/to/page`. - * @access public - * @example app.use(trimTrailingSlash()) - */ -export const trimTrailingSlash = (): MiddlewareHandler => { - return async function trimTrailingSlash(c, next) { - await next() - - if ( - c.res.status === 404 && - c.req.method === 'GET' && - c.req.path !== '/' && - c.req.path[c.req.path.length - 1] === '/' - ) { - const url = new URL(c.req.url) - url.pathname = url.pathname.substring(0, url.pathname.length - 1) - - c.res = c.redirect(url.toString(), 301) - } - } -} - -/** - * Append a trailing slash to the URL if it doesn't have one. For example, `/path/to/page` will be redirected to `/path/to/page/`. - * @access public - * @example app.use(appendTrailingSlash()) - */ -export const appendTrailingSlash = (): MiddlewareHandler => { - return async function appendTrailingSlash(c, next) { - await next() - - if ( - c.res.status === 404 && - c.req.method === 'GET' && - c.req.path[c.req.path.length - 1] !== '/' - ) { - const url = new URL(c.req.url) - url.pathname += '/' - - c.res = c.redirect(url.toString(), 301) - } - } -} diff --git a/deno_dist/mod.ts b/deno_dist/mod.ts deleted file mode 100644 index fe45b66339..0000000000 --- a/deno_dist/mod.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Hono } from './hono.ts' - -export type { - Env, - ErrorHandler, - Handler, - MiddlewareHandler, - Next, - NotFoundHandler, - ValidationTargets, - Input, - Schema, - ToSchema, - TypedResponse, -} from './types.ts' -export type { Context, ContextVariableMap, ContextRenderer, ExecutionContext } from './context.ts' -export type { HonoRequest } from './request.ts' -export { Hono } -export { HTTPException } from './http-exception.ts' - -// Router -export { RegExpRouter } from './router/reg-exp-router/index.ts' -export { TrieRouter } from './router/trie-router/index.ts' -export { SmartRouter } from './router/smart-router/index.ts' -export { PatternRouter } from './router/pattern-router/index.ts' -export { LinearRouter } from './router/linear-router/index.ts' - -// Validator -export { validator } from './validator/index.ts' - -// Client -export { hc } from './client/index.ts' -export type { InferRequestType, InferResponseType, ClientRequestOptions } from './client/index.ts' diff --git a/deno_dist/preset/quick.ts b/deno_dist/preset/quick.ts deleted file mode 100644 index 39b6a468c1..0000000000 --- a/deno_dist/preset/quick.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { HonoBase } from '../hono-base.ts' -import type { HonoOptions } from '../hono-base.ts' -import { LinearRouter } from '../router/linear-router/index.ts' -import { SmartRouter } from '../router/smart-router/index.ts' -import { TrieRouter } from '../router/trie-router/index.ts' -import type { BlankSchema, Env, Schema } from '../types.ts' - -export class Hono< - E extends Env = Env, - S extends Schema = BlankSchema, - BasePath extends string = '/' -> extends HonoBase { - constructor(options: HonoOptions = {}) { - super(options) - this.router = new SmartRouter({ - routers: [new LinearRouter(), new TrieRouter()], - }) - } -} diff --git a/deno_dist/preset/tiny.ts b/deno_dist/preset/tiny.ts deleted file mode 100644 index 435a8d9686..0000000000 --- a/deno_dist/preset/tiny.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { HonoBase } from '../hono-base.ts' -import type { HonoOptions } from '../hono-base.ts' -import { PatternRouter } from '../router/pattern-router/index.ts' -import type { BlankSchema, Env, Schema } from '../types.ts' - -export class Hono< - E extends Env = Env, - S extends Schema = BlankSchema, - BasePath extends string = '/' -> extends HonoBase { - constructor(options: HonoOptions = {}) { - super(options) - this.router = new PatternRouter() - } -} diff --git a/deno_dist/request.ts b/deno_dist/request.ts deleted file mode 100644 index 6fd80d42ba..0000000000 --- a/deno_dist/request.ts +++ /dev/null @@ -1,348 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import type { Result } from './router.ts' -import type { - Input, - InputToDataByTarget, - ParamKeys, - ParamKeyToRecord, - RemoveQuestion, - ValidationTargets, - RouterRoute, -} from './types.ts' -import { parseBody } from './utils/body.ts' -import type { BodyData, ParseBodyOptions } from './utils/body.ts' -import type { UnionToIntersection } from './utils/types.ts' -import { getQueryParam, getQueryParams, decodeURIComponent_ } from './utils/url.ts' - -type Body = { - json: any - text: string - arrayBuffer: ArrayBuffer - blob: Blob - formData: FormData -} -type BodyCache = Partial - -export class HonoRequest

{ - /** - * `.raw` can get the raw Request object. - * @example - * ```ts - * // For Cloudflare Workers - * app.post('/', async (c) => { - * const metadata = c.req.raw.cf?.hostMetadata? - * ... - * }) - * ``` - * @see https://hono.dev/api/request#raw - */ - raw: Request - - #validatedData: { [K in keyof ValidationTargets]?: {} } // Short name of validatedData - #matchResult: Result<[unknown, RouterRoute]> - routeIndex: number = 0 - /** - * `.path` can get the pathname of the request. - * @example - * ```ts - * app.get('/about/me', (c) => { - * const pathname = c.req.path // `/about/me` - * }) - * ``` - * @see https://hono.dev/api/request#path - */ - path: string - bodyCache: BodyCache = {} - - constructor( - request: Request, - path: string = '/', - matchResult: Result<[unknown, RouterRoute]> = [[]] - ) { - this.raw = request - this.path = path - this.#matchResult = matchResult - this.#validatedData = {} - } - - /** - * `.req.param()` gets the path parameters. - * @example - * ```ts - * const name = c.req.param('name') - * // or all parameters at once - * const { id, comment_id } = c.req.param() - * ``` - * @see https://hono.dev/api/routing#path-parameter - */ - param = ParamKeys

>(key: P2 extends `${infer _}?` ? never : P2): string - param> = RemoveQuestion>>( - key: P2 - ): string | undefined - param(key: string): string | undefined - param(): UnionToIntersection>> - param(key?: string): unknown { - return key ? this.getDecodedParam(key) : this.getAllDecodedParams() - } - - 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 { - const decoded: Record = {} - - 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 - } - - /** - * `.query()` can get querystring parameters. - * @example - * ```ts - * // Query params - * app.get('/search', (c) => { - * const query = c.req.query('q') - * }) - * - * // Get all params at once - * app.get('/search', (c) => { - * const { q, limit, offset } = c.req.query() - * }) - * ``` - * @see https://hono.dev/api/request#query - */ - query(key: string): string | undefined - query(): Record - query(key?: string) { - return getQueryParam(this.url, key) - } - - /** - * `.queries()` can get multiple querystring parameter values, e.g. /search?tags=A&tags=B - * @example - * ```ts - * app.get('/search', (c) => { - * // tags will be string[] - * const tags = c.req.queries('tags') - * }) - * ``` - * @see https://hono.dev/api/request#queries - */ - queries(key: string): string[] | undefined - queries(): Record - queries(key?: string) { - return getQueryParams(this.url, key) - } - - /** - * `.header()` can get the request header value. - * @example - * ```ts - * app.get('/', (c) => { - * const userAgent = c.req.header('User-Agent') - * }) - * ``` - * @see https://hono.dev/api/request#header - */ - header(name: string): string | undefined - header(): Record - header(name?: string) { - if (name) { - return this.raw.headers.get(name.toLowerCase()) ?? undefined - } - - const headerData: Record = {} - this.raw.headers.forEach((value, key) => { - headerData[key] = value - }) - return headerData - } - - /** - * `.parseBody()` can parse Request body of type `multipart/form-data` or `application/x-www-form-urlencoded` - * @example - * ```ts - * app.post('/entry', async (c) => { - * const body = await c.req.parseBody() - * }) - * ``` - * @see https://hono.dev/api/request#parsebody - */ - async parseBody(options?: ParseBodyOptions): Promise { - if (this.bodyCache.parsedBody) { - return this.bodyCache.parsedBody as T - } - const parsedBody = await parseBody(this, options) - this.bodyCache.parsedBody = parsedBody - return parsedBody - } - - private cachedBody = (key: keyof Body) => { - const { bodyCache, raw } = this - const cachedBody = bodyCache[key] - - if (cachedBody) { - return cachedBody - } - - if (!bodyCache[key]) { - for (const keyOfBodyCache of Object.keys(bodyCache)) { - if (keyOfBodyCache === 'parsedBody') { - continue - } - return (async () => { - // @ts-expect-error bodyCache[keyOfBodyCache] can be passed as a body - let body = await bodyCache[keyOfBodyCache] - if (keyOfBodyCache === 'json') { - body = JSON.stringify(body) - } - return await new Response(body)[key]() - })() - } - } - - return (bodyCache[key] = raw[key]()) - } - - /** - * `.json()` can parse Request body of type `application/json` - * @example - * ```ts - * app.post('/entry', async (c) => { - * const body = await c.req.json() - * }) - * ``` - * @see https://hono.dev/api/request#json - */ - json(): Promise { - return this.cachedBody('json') - } - - /** - * `.text()` can parse Request body of type `text/plain` - * @example - * ```ts - * app.post('/entry', async (c) => { - * const body = await c.req.text() - * }) - * ``` - * @see https://hono.dev/api/request#text - */ - text(): Promise { - return this.cachedBody('text') - } - - /** - * `.arrayBuffer()` parse Request body as an `ArrayBuffer` - * @example - * ```ts - * app.post('/entry', async (c) => { - * const body = await c.req.arrayBuffer() - * }) - * ``` - * @see https://hono.dev/api/request#arraybuffer - */ - arrayBuffer(): Promise { - return this.cachedBody('arrayBuffer') - } - - blob(): Promise { - return this.cachedBody('blob') - } - - formData(): Promise { - return this.cachedBody('formData') - } - - addValidatedData(target: keyof ValidationTargets, data: {}) { - this.#validatedData[target] = data - } - - valid(target: T): InputToDataByTarget - valid(target: keyof ValidationTargets) { - return this.#validatedData[target] as unknown - } - - /** - * `.url()` can get the request url strings. - * @example - * ```ts - * app.get('/about/me', (c) => { - * const url = c.req.url // `http://localhost:8787/about/me` - * ... - * }) - * ``` - * @see https://hono.dev/api/request#url - */ - get url(): string { - return this.raw.url - } - - /** - * `.method()` can get the method name of the request. - * @example - * ```ts - * app.get('/about/me', (c) => { - * const method = c.req.method // `GET` - * }) - * ``` - * @see https://hono.dev/api/request#method - */ - get method(): string { - return this.raw.method - } - - /** - * `.matchedRoutes()` can return a matched route in the handler - * @example - * ```ts - * app.use('*', async function logger(c, next) { - * await next() - * c.req.matchedRoutes.forEach(({ handler, method, path }, i) => { - * const name = handler.name || (handler.length < 2 ? '[handler]' : '[middleware]') - * console.log( - * method, - * ' ', - * path, - * ' '.repeat(Math.max(10 - path.length, 0)), - * name, - * i === c.req.routeIndex ? '<- respond from here' : '' - * ) - * }) - * }) - * ``` - * @see https://hono.dev/api/request#matchedroutes - */ - get matchedRoutes(): RouterRoute[] { - return this.#matchResult[0].map(([[, route]]) => route) - } - - /** - * `routePath()` can retrieve the path registered within the handler - * @example - * ```ts - * app.get('/posts/:id', (c) => { - * return c.json({ path: c.req.routePath }) - * }) - * ``` - * @see https://hono.dev/api/request#routepath - */ - get routePath(): string { - return this.#matchResult[0].map(([[, route]]) => route)[this.routeIndex].path - } -} diff --git a/deno_dist/router.ts b/deno_dist/router.ts deleted file mode 100644 index 78cd65e54d..0000000000 --- a/deno_dist/router.ts +++ /dev/null @@ -1,42 +0,0 @@ -export const METHOD_NAME_ALL = 'ALL' as const -export const METHOD_NAME_ALL_LOWERCASE = 'all' as const -export const METHODS = ['get', 'post', 'put', 'delete', 'options', 'patch'] as const -export const MESSAGE_MATCHER_IS_ALREADY_BUILT = - 'Can not add a route since the matcher is already built.' - -export interface Router { - name: string - add(method: string, path: string, handler: T): void - match(method: string, path: string): Result -} - -export type ParamIndexMap = Record -export type ParamStash = string[] -export type Params = Record -export type Result = [[T, ParamIndexMap][], ParamStash] | [[T, Params][]] -/* -The router returns the result of `match` in either format. - -[[handler, paramIndexMap][], paramArray] -e.g. -[ - [ - [middlewareA, {}], // '*' - [funcA, {'id': 0}], // '/user/:id/*' - [funcB, {'id': 0, 'action': 1}], // '/user/:id/:action' - ], - ['123', 'abc'] -] - -[[handler, params][]] -e.g. -[ - [ - [middlewareA, {}], // '*' - [funcA, {'id': '123'}], // '/user/:id/*' - [funcB, {'id': '123', 'action': 'abc'}], // '/user/:id/:action' - ] -] -*/ - -export class UnsupportedPathError extends Error {} diff --git a/deno_dist/router/linear-router/index.ts b/deno_dist/router/linear-router/index.ts deleted file mode 100644 index 5cc0dcfa78..0000000000 --- a/deno_dist/router/linear-router/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { LinearRouter } from './router.ts' diff --git a/deno_dist/router/linear-router/router.ts b/deno_dist/router/linear-router/router.ts deleted file mode 100644 index f0d0e65d04..0000000000 --- a/deno_dist/router/linear-router/router.ts +++ /dev/null @@ -1,132 +0,0 @@ -import type { Router, Result, Params } from '../../router.ts' -import { METHOD_NAME_ALL, UnsupportedPathError } from '../../router.ts' -import { checkOptionalParameter } from '../../utils/url.ts' - -type RegExpMatchArrayWithIndices = RegExpMatchArray & { indices: [number, number][] } - -const emptyParams = Object.create(null) - -const splitPathRe = /\/(:\w+(?:{(?:(?:{[\d,]+})|[^}])+})?)|\/[^\/\?]+|(\?)/g -const splitByStarRe = /\*/ -export class LinearRouter implements Router { - name: string = 'LinearRouter' - routes: [string, string, T][] = [] - - add(method: string, path: string, handler: T) { - ;(checkOptionalParameter(path) || [path]).forEach((p) => { - this.routes.push([method, p, handler]) - }) - } - - match(method: string, path: string): Result { - const handlers: [T, Params][] = [] - ROUTES_LOOP: for (let i = 0, len = this.routes.length; i < len; i++) { - const [routeMethod, routePath, handler] = this.routes[i] - if (routeMethod !== method && routeMethod !== METHOD_NAME_ALL) { - continue - } - if (routePath === '*' || routePath === '/*') { - handlers.push([handler, emptyParams]) - continue - } - - const hasStar = routePath.indexOf('*') !== -1 - const hasLabel = routePath.indexOf(':') !== -1 - if (!hasStar && !hasLabel) { - if (routePath === path || routePath + '/' === path) { - handlers.push([handler, emptyParams]) - } - } else if (hasStar && !hasLabel) { - const endsWithStar = routePath.charCodeAt(routePath.length - 1) === 42 - const parts = (endsWithStar ? routePath.slice(0, -2) : routePath).split(splitByStarRe) - - const lastIndex = parts.length - 1 - for (let j = 0, pos = 0, len = parts.length; j < len; j++) { - const part = parts[j] - const index = path.indexOf(part, pos) - if (index !== pos) { - continue ROUTES_LOOP - } - pos += part.length - if (j === lastIndex) { - if ( - !endsWithStar && - pos !== path.length && - !(pos === path.length - 1 && path.charCodeAt(pos) === 47) - ) { - continue ROUTES_LOOP - } - } else { - const index = path.indexOf('/', pos) - if (index === -1) { - continue ROUTES_LOOP - } - pos = index - } - } - handlers.push([handler, emptyParams]) - } else if (hasLabel && !hasStar) { - const params: Record = Object.create(null) - const parts = routePath.match(splitPathRe) as string[] - - const lastIndex = parts.length - 1 - for (let j = 0, pos = 0, len = parts.length; j < len; j++) { - if (pos === -1 || pos >= path.length) { - continue ROUTES_LOOP - } - - const part = parts[j] - if (part.charCodeAt(1) === 58) { - // /:label - let name = part.slice(2) - let value - - if (name.charCodeAt(name.length - 1) === 125) { - // :label{pattern} - const openBracePos = name.indexOf('{') - const pattern = name.slice(openBracePos + 1, -1) - const restPath = path.slice(pos + 1) - const match = new RegExp(pattern, 'd').exec(restPath) as RegExpMatchArrayWithIndices - if (!match || match.indices[0][0] !== 0 || match.indices[0][1] === 0) { - continue ROUTES_LOOP - } - name = name.slice(0, openBracePos) - value = restPath.slice(...match.indices[0]) - pos += match.indices[0][1] + 1 - } else { - let endValuePos = path.indexOf('/', pos + 1) - if (endValuePos === -1) { - if (pos + 1 === path.length) { - continue ROUTES_LOOP - } - endValuePos = path.length - } - value = path.slice(pos + 1, endValuePos) - pos = endValuePos - } - - params[name] ||= value as string - } else { - const index = path.indexOf(part, pos) - if (index !== pos) { - continue ROUTES_LOOP - } - pos += part.length - } - - if (j === lastIndex) { - if (pos !== path.length && !(pos === path.length - 1 && path.charCodeAt(pos) === 47)) { - continue ROUTES_LOOP - } - } - } - - handlers.push([handler, params]) - } else if (hasLabel && hasStar) { - throw new UnsupportedPathError() - } - } - - return [handlers] - } -} diff --git a/deno_dist/router/pattern-router/index.ts b/deno_dist/router/pattern-router/index.ts deleted file mode 100644 index 7690015114..0000000000 --- a/deno_dist/router/pattern-router/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { PatternRouter } from './router.ts' diff --git a/deno_dist/router/pattern-router/router.ts b/deno_dist/router/pattern-router/router.ts deleted file mode 100644 index 30898ed3c6..0000000000 --- a/deno_dist/router/pattern-router/router.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { Result, Router, Params } from '../../router.ts' -import { METHOD_NAME_ALL, UnsupportedPathError } from '../../router.ts' - -type Route = [RegExp, string, T] // [pattern, method, handler, path] - -export class PatternRouter implements Router { - name: string = 'PatternRouter' - private routes: Route[] = [] - - add(method: string, path: string, handler: T) { - const endsWithWildcard = path[path.length - 1] === '*' - if (endsWithWildcard) { - path = path.slice(0, -2) - } - if (path.at(-1) === '?') { - path = path.slice(0, -1) - this.add(method, path.replace(/\/[^/]+$/, ''), handler) - } - - const parts = (path.match(/\/?(:\w+(?:{(?:(?:{[\d,]+})|[^}])+})?)|\/?[^\/\?]+/g) || []).map( - (part) => { - const match = part.match(/^\/:([^{]+)(?:{(.*)})?/) - return match - ? `/(?<${match[1]}>${match[2] || '[^/]+'})` - : part === '/*' - ? '/[^/]+' - : part.replace(/[.\\+*[^\]$()]/g, '\\$&') - } - ) - - let re - try { - re = new RegExp(`^${parts.join('')}${endsWithWildcard ? '' : '/?$'}`) - } catch (e) { - throw new UnsupportedPathError() - } - this.routes.push([re, method, handler]) - } - - match(method: string, path: string): Result { - const handlers: [T, Params][] = [] - - for (const [pattern, routeMethod, handler] of this.routes) { - if (routeMethod === METHOD_NAME_ALL || routeMethod === method) { - const match = pattern.exec(path) - if (match) { - handlers.push([handler, match.groups || Object.create(null)]) - } - } - } - - return [handlers] - } -} diff --git a/deno_dist/router/reg-exp-router/index.ts b/deno_dist/router/reg-exp-router/index.ts deleted file mode 100644 index c9347fb82b..0000000000 --- a/deno_dist/router/reg-exp-router/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { RegExpRouter } from './router.ts' diff --git a/deno_dist/router/reg-exp-router/node.ts b/deno_dist/router/reg-exp-router/node.ts deleted file mode 100644 index c894c898f2..0000000000 --- a/deno_dist/router/reg-exp-router/node.ts +++ /dev/null @@ -1,159 +0,0 @@ -const LABEL_REG_EXP_STR = '[^/]+' -const ONLY_WILDCARD_REG_EXP_STR = '.*' -const TAIL_WILDCARD_REG_EXP_STR = '(?:|/.*)' -export const PATH_ERROR = Symbol() - -export type ParamAssocArray = [string, number][] -export interface Context { - varIndex: number -} - -const regExpMetaChars = new Set('.\\+*[^]$()') - -/** - * Sort order: - * 1. literal - * 2. special pattern (e.g. :label{[0-9]+}) - * 3. common label pattern (e.g. :label) - * 4. wildcard - */ -function compareKey(a: string, b: string): number { - if (a.length === 1) { - return b.length === 1 ? (a < b ? -1 : 1) : -1 - } - if (b.length === 1) { - return 1 - } - - // wildcard - if (a === ONLY_WILDCARD_REG_EXP_STR || a === TAIL_WILDCARD_REG_EXP_STR) { - return 1 - } else if (b === ONLY_WILDCARD_REG_EXP_STR || b === TAIL_WILDCARD_REG_EXP_STR) { - return -1 - } - - // label - if (a === LABEL_REG_EXP_STR) { - return 1 - } else if (b === LABEL_REG_EXP_STR) { - return -1 - } - - return a.length === b.length ? (a < b ? -1 : 1) : b.length - a.length -} - -export class Node { - index?: number - varIndex?: number - children: Record = Object.create(null) - - insert( - tokens: readonly string[], - index: number, - paramMap: ParamAssocArray, - context: Context, - pathErrorCheckOnly: boolean - ): void { - if (tokens.length === 0) { - if (this.index !== undefined) { - throw PATH_ERROR - } - if (pathErrorCheckOnly) { - return - } - - this.index = index - return - } - - const [token, ...restTokens] = tokens - const pattern = - token === '*' - ? restTokens.length === 0 - ? ['', '', ONLY_WILDCARD_REG_EXP_STR] // '*' matches to all the trailing paths - : ['', '', LABEL_REG_EXP_STR] - : token === '/*' - ? ['', '', TAIL_WILDCARD_REG_EXP_STR] // '/path/to/*' is /\/path\/to(?:|/.*)$ - : token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/) - - let node - if (pattern) { - const name = pattern[1] - let regexpStr = pattern[2] || LABEL_REG_EXP_STR - if (name && pattern[2]) { - regexpStr = regexpStr.replace(/^\((?!\?:)(?=[^)]+\)$)/, '(?:') // (a|b) => (?:a|b) - if (/\((?!\?:)/.test(regexpStr)) { - // prefix(?:a|b) is allowed, but prefix(a|b) is not - throw PATH_ERROR - } - } - - node = this.children[regexpStr] - if (!node) { - if ( - Object.keys(this.children).some( - (k) => k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR - ) - ) { - throw PATH_ERROR - } - if (pathErrorCheckOnly) { - return - } - node = this.children[regexpStr] = new Node() - if (name !== '') { - node.varIndex = context.varIndex++ - } - } - if (!pathErrorCheckOnly && name !== '') { - paramMap.push([name, node.varIndex as number]) - } - } else { - node = this.children[token] - if (!node) { - if ( - Object.keys(this.children).some( - (k) => - k.length > 1 && k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR - ) - ) { - throw PATH_ERROR - } - if (pathErrorCheckOnly) { - return - } - node = this.children[token] = new Node() - } - } - - node.insert(restTokens, index, paramMap, context, pathErrorCheckOnly) - } - - buildRegExpStr(): string { - const childKeys = Object.keys(this.children).sort(compareKey) - - const strList = childKeys.map((k) => { - const c = this.children[k] - return ( - (typeof c.varIndex === 'number' - ? `(${k})@${c.varIndex}` - : regExpMetaChars.has(k) - ? `\\${k}` - : k) + c.buildRegExpStr() - ) - }) - - if (typeof this.index === 'number') { - strList.unshift(`#${this.index}`) - } - - if (strList.length === 0) { - return '' - } - if (strList.length === 1) { - return strList[0] - } - - return '(?:' + strList.join('|') + ')' - } -} diff --git a/deno_dist/router/reg-exp-router/router.ts b/deno_dist/router/reg-exp-router/router.ts deleted file mode 100644 index 1302c6d173..0000000000 --- a/deno_dist/router/reg-exp-router/router.ts +++ /dev/null @@ -1,273 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -import type { Router, Result, ParamIndexMap } from '../../router.ts' -import { - METHOD_NAME_ALL, - UnsupportedPathError, - MESSAGE_MATCHER_IS_ALREADY_BUILT, -} from '../../router.ts' -import { checkOptionalParameter } from '../../utils/url.ts' -import { PATH_ERROR, type ParamAssocArray } from './node.ts' -import { Trie } from './trie.ts' - -type HandlerData = [T, ParamIndexMap][] -type StaticMap = Record> -type Matcher = [RegExp, HandlerData[], StaticMap] -type HandlerWithMetadata = [T, number] // [handler, paramCount] - -const emptyParam: string[] = [] -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const nullMatcher: Matcher = [/^$/, [], Object.create(null)] - -let wildcardRegExpCache: Record = Object.create(null) -function buildWildcardRegExp(path: string): RegExp { - return (wildcardRegExpCache[path] ??= new RegExp( - path === '*' - ? '' - : `^${path.replace(/\/\*$|([.\\+*[^\]$()])/g, (_, metaChar) => - metaChar ? `\\${metaChar}` : '(?:|/.*)' - )}$` - )) -} - -function clearWildcardRegExpCache() { - wildcardRegExpCache = Object.create(null) -} - -function buildMatcherFromPreprocessedRoutes( - routes: [string, HandlerWithMetadata[]][] -): Matcher { - const trie = new Trie() - const handlerData: HandlerData[] = [] - if (routes.length === 0) { - return nullMatcher - } - - const routesWithStaticPathFlag = routes - .map( - (route) => [!/\*|\/:/.test(route[0]), ...route] as [boolean, string, HandlerWithMetadata[]] - ) - .sort(([isStaticA, pathA], [isStaticB, pathB]) => - isStaticA ? 1 : isStaticB ? -1 : pathA.length - pathB.length - ) - - const staticMap: StaticMap = Object.create(null) - for (let i = 0, j = -1, len = routesWithStaticPathFlag.length; i < len; i++) { - const [pathErrorCheckOnly, path, handlers] = routesWithStaticPathFlag[i] - if (pathErrorCheckOnly) { - staticMap[path] = [handlers.map(([h]) => [h, Object.create(null)]), emptyParam] - } else { - j++ - } - - let paramAssoc: ParamAssocArray - try { - paramAssoc = trie.insert(path, j, pathErrorCheckOnly) - } catch (e) { - throw e === PATH_ERROR ? new UnsupportedPathError(path) : e - } - - if (pathErrorCheckOnly) { - continue - } - - handlerData[j] = handlers.map(([h, paramCount]) => { - const paramIndexMap: ParamIndexMap = Object.create(null) - paramCount -= 1 - for (; paramCount >= 0; paramCount--) { - const [key, value] = paramAssoc[paramCount] - paramIndexMap[key] = value - } - return [h, paramIndexMap] - }) - } - - const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp() - for (let i = 0, len = handlerData.length; i < len; i++) { - for (let j = 0, len = handlerData[i].length; j < len; j++) { - const map = handlerData[i][j]?.[1] - if (!map) { - continue - } - const keys = Object.keys(map) - for (let k = 0, len = keys.length; k < len; k++) { - map[keys[k]] = paramReplacementMap[map[keys[k]]] - } - } - } - - const handlerMap: HandlerData[] = [] - // using `in` because indexReplacementMap is a sparse array - for (const i in indexReplacementMap) { - handlerMap[i] = handlerData[indexReplacementMap[i]] - } - - return [regexp, handlerMap, staticMap] as Matcher -} - -function findMiddleware( - middleware: Record | undefined, - path: string -): T[] | undefined { - if (!middleware) { - return undefined - } - - for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length)) { - if (buildWildcardRegExp(k).test(path)) { - return [...middleware[k]] - } - } - - return undefined -} - -export class RegExpRouter implements Router { - name: string = 'RegExpRouter' - middleware?: Record[]>> - routes?: Record[]>> - - constructor() { - this.middleware = { [METHOD_NAME_ALL]: Object.create(null) } - this.routes = { [METHOD_NAME_ALL]: Object.create(null) } - } - - add(method: string, path: string, handler: T) { - const { middleware, routes } = this - - if (!middleware || !routes) { - throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT) - } - - if (!middleware[method]) { - ;[middleware, routes].forEach((handlerMap) => { - handlerMap[method] = Object.create(null) - Object.keys(handlerMap[METHOD_NAME_ALL]).forEach((p) => { - handlerMap[method][p] = [...handlerMap[METHOD_NAME_ALL][p]] - }) - }) - } - - if (path === '/*') { - path = '*' - } - - const paramCount = (path.match(/\/:/g) || []).length - - if (/\*$/.test(path)) { - const re = buildWildcardRegExp(path) - if (method === METHOD_NAME_ALL) { - Object.keys(middleware).forEach((m) => { - middleware[m][path] ||= - findMiddleware(middleware[m], path) || - findMiddleware(middleware[METHOD_NAME_ALL], path) || - [] - }) - } else { - middleware[method][path] ||= - findMiddleware(middleware[method], path) || - findMiddleware(middleware[METHOD_NAME_ALL], path) || - [] - } - Object.keys(middleware).forEach((m) => { - if (method === METHOD_NAME_ALL || method === m) { - Object.keys(middleware[m]).forEach((p) => { - re.test(p) && middleware[m][p].push([handler, paramCount]) - }) - } - }) - - Object.keys(routes).forEach((m) => { - if (method === METHOD_NAME_ALL || method === m) { - Object.keys(routes[m]).forEach( - (p) => re.test(p) && routes[m][p].push([handler, paramCount]) - ) - } - }) - - return - } - - const paths = checkOptionalParameter(path) || [path] - for (let i = 0, len = paths.length; i < len; i++) { - const path = paths[i] - - Object.keys(routes).forEach((m) => { - if (method === METHOD_NAME_ALL || method === m) { - routes[m][path] ||= [ - ...(findMiddleware(middleware[m], path) || - findMiddleware(middleware[METHOD_NAME_ALL], path) || - []), - ] - routes[m][path].push([handler, paramCount - len + i + 1]) - } - }) - } - } - - match(method: string, path: string): Result { - clearWildcardRegExpCache() // no longer used. - - const matchers = this.buildAllMatchers() - - this.match = (method, path) => { - const matcher = (matchers[method] || matchers[METHOD_NAME_ALL]) as Matcher - - const staticMatch = matcher[2][path] - if (staticMatch) { - return staticMatch - } - - const match = path.match(matcher[0]) - if (!match) { - return [[], emptyParam] - } - - const index = match.indexOf('', 1) - return [matcher[1][index], match] - } - - return this.match(method, path) - } - - private buildAllMatchers(): Record | null> { - const matchers: Record | null> = Object.create(null) - - ;[...Object.keys(this.routes!), ...Object.keys(this.middleware!)].forEach((method) => { - matchers[method] ||= this.buildMatcher(method) - }) - - // Release cache - this.middleware = this.routes = undefined - - return matchers - } - - private buildMatcher(method: string): Matcher | null { - const routes: [string, HandlerWithMetadata[]][] = [] - - let hasOwnRoute = method === METHOD_NAME_ALL - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - ;[this.middleware!, this.routes!].forEach((r) => { - const ownRoute = r[method] - ? Object.keys(r[method]).map((path) => [path, r[method][path]]) - : [] - if (ownRoute.length !== 0) { - hasOwnRoute ||= true - routes.push(...(ownRoute as [string, HandlerWithMetadata[]][])) - } else if (method !== METHOD_NAME_ALL) { - routes.push( - ...(Object.keys(r[METHOD_NAME_ALL]).map((path) => [path, r[METHOD_NAME_ALL][path]]) as [ - string, - HandlerWithMetadata[] - ][]) - ) - } - }) - - if (!hasOwnRoute) { - return null - } else { - return buildMatcherFromPreprocessedRoutes(routes) - } - } -} diff --git a/deno_dist/router/reg-exp-router/trie.ts b/deno_dist/router/reg-exp-router/trie.ts deleted file mode 100644 index 3e00ad0c05..0000000000 --- a/deno_dist/router/reg-exp-router/trie.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { ParamAssocArray, Context } from './node.ts' -import { Node } from './node.ts' - -export type ReplacementMap = number[] - -export class Trie { - context: Context = { varIndex: 0 } - root: Node = new Node() - - insert(path: string, index: number, pathErrorCheckOnly: boolean): ParamAssocArray { - const paramAssoc: ParamAssocArray = [] - - const groups: [string, string][] = [] // [mark, original string] - for (let i = 0; ; ) { - let replaced = false - path = path.replace(/\{[^}]+\}/g, (m) => { - const mark = `@\\${i}` - groups[i] = [mark, m] - i++ - replaced = true - return mark - }) - if (!replaced) { - break - } - } - - /** - * - pattern (:label, :label{0-9]+}, ...) - * - /* wildcard - * - character - */ - const tokens = path.match(/(?::[^\/]+)|(?:\/\*$)|./g) || [] - for (let i = groups.length - 1; i >= 0; i--) { - const [mark] = groups[i] - for (let j = tokens.length - 1; j >= 0; j--) { - if (tokens[j].indexOf(mark) !== -1) { - tokens[j] = tokens[j].replace(mark, groups[i][1]) - break - } - } - } - - this.root.insert(tokens, index, paramAssoc, this.context, pathErrorCheckOnly) - - return paramAssoc - } - - buildRegExp(): [RegExp, ReplacementMap, ReplacementMap] { - let regexp = this.root.buildRegExpStr() - if (regexp === '') { - return [/^$/, [], []] // never match - } - - let captureIndex = 0 - const indexReplacementMap: ReplacementMap = [] - const paramReplacementMap: ReplacementMap = [] - - regexp = regexp.replace(/#(\d+)|@(\d+)|\.\*\$/g, (_, handlerIndex, paramIndex) => { - if (typeof handlerIndex !== 'undefined') { - indexReplacementMap[++captureIndex] = Number(handlerIndex) - return '$()' - } - if (typeof paramIndex !== 'undefined') { - paramReplacementMap[Number(paramIndex)] = ++captureIndex - return '' - } - - return '' - }) - - return [new RegExp(`^${regexp}`), indexReplacementMap, paramReplacementMap] - } -} diff --git a/deno_dist/router/smart-router/index.ts b/deno_dist/router/smart-router/index.ts deleted file mode 100644 index 8832c6c46c..0000000000 --- a/deno_dist/router/smart-router/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { SmartRouter } from './router.ts' diff --git a/deno_dist/router/smart-router/router.ts b/deno_dist/router/smart-router/router.ts deleted file mode 100644 index f0ade4671c..0000000000 --- a/deno_dist/router/smart-router/router.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -import type { Router, Result } from '../../router.ts' -import { UnsupportedPathError, MESSAGE_MATCHER_IS_ALREADY_BUILT } from '../../router.ts' - -export class SmartRouter implements Router { - name: string = 'SmartRouter' - routers: Router[] = [] - routes?: [string, string, T][] = [] - - constructor(init: Pick, 'routers'>) { - Object.assign(this, init) - } - - add(method: string, path: string, handler: T) { - if (!this.routes) { - throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT) - } - - this.routes.push([method, path, handler]) - } - - match(method: string, path: string): Result { - if (!this.routes) { - throw new Error('Fatal error') - } - - const { routers, routes } = this - const len = routers.length - let i = 0 - let res - for (; i < len; i++) { - const router = routers[i] - try { - routes.forEach((args) => { - router.add(...args) - }) - res = router.match(method, path) - } catch (e) { - if (e instanceof UnsupportedPathError) { - continue - } - throw e - } - - this.match = router.match.bind(router) - this.routers = [router] - this.routes = undefined - break - } - - if (i === len) { - // not found - throw new Error('Fatal error') - } - - // e.g. "SmartRouter + RegExpRouter" - this.name = `SmartRouter + ${this.activeRouter.name}` - - return res as Result - } - - get activeRouter(): Router { - if (this.routes || this.routers.length !== 1) { - throw new Error('No active router has been determined yet.') - } - - return this.routers[0] - } -} diff --git a/deno_dist/router/trie-router/index.ts b/deno_dist/router/trie-router/index.ts deleted file mode 100644 index 6417d095bd..0000000000 --- a/deno_dist/router/trie-router/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { TrieRouter } from './router.ts' diff --git a/deno_dist/router/trie-router/node.ts b/deno_dist/router/trie-router/node.ts deleted file mode 100644 index 30f99c85b8..0000000000 --- a/deno_dist/router/trie-router/node.ts +++ /dev/null @@ -1,205 +0,0 @@ -import type { Params } from '../../router.ts' -import { METHOD_NAME_ALL } from '../../router.ts' -import type { Pattern } from '../../utils/url.ts' -import { splitPath, splitRoutingPath, getPattern } from '../../utils/url.ts' - -type HandlerSet = { - handler: T - possibleKeys: string[] - score: number - name: string // For debug -} - -type HandlerParamsSet = HandlerSet & { - params: Record -} - -export class Node { - methods: Record>[] - - children: Record> - patterns: Pattern[] - order: number = 0 - name: string - params: Record = Object.create(null) - - constructor(method?: string, handler?: T, children?: Record>) { - this.children = children || Object.create(null) - this.methods = [] - this.name = '' - if (method && handler) { - const m: Record> = Object.create(null) - m[method] = { handler, possibleKeys: [], score: 0, name: this.name } - this.methods = [m] - } - this.patterns = [] - } - - insert(method: string, path: string, handler: T): Node { - this.name = `${method} ${path}` - this.order = ++this.order - - // eslint-disable-next-line @typescript-eslint/no-this-alias - let curNode: Node = this - const parts = splitRoutingPath(path) - - const possibleKeys: string[] = [] - - for (let i = 0, len = parts.length; i < len; i++) { - const p: string = parts[i] - - if (Object.keys(curNode.children).includes(p)) { - curNode = curNode.children[p] - const pattern = getPattern(p) - if (pattern) { - possibleKeys.push(pattern[1]) - } - continue - } - - curNode.children[p] = new Node() - - const pattern = getPattern(p) - if (pattern) { - curNode.patterns.push(pattern) - possibleKeys.push(pattern[1]) - } - curNode = curNode.children[p] - } - - if (!curNode.methods.length) { - curNode.methods = [] - } - - const m: Record> = Object.create(null) - - const handlerSet: HandlerSet = { - handler, - possibleKeys: possibleKeys.filter((v, i, a) => a.indexOf(v) === i), - name: this.name, - score: this.order, - } - - m[method] = handlerSet - curNode.methods.push(m) - - return curNode - } - - // getHandlerSets - private gHSets( - node: Node, - method: string, - nodeParams: Record, - params: Record - ): HandlerParamsSet[] { - const handlerSets: HandlerParamsSet[] = [] - for (let i = 0, len = node.methods.length; i < len; i++) { - const m = node.methods[i] - const handlerSet = (m[method] || m[METHOD_NAME_ALL]) as HandlerParamsSet - const processedSet: Record = Object.create(null) - if (handlerSet !== undefined) { - handlerSet.params = Object.create(null) - handlerSet.possibleKeys.forEach((key) => { - const processed = processedSet[handlerSet.name] - handlerSet.params[key] = - params[key] && !processed ? params[key] : nodeParams[key] ?? params[key] - processedSet[handlerSet.name] = true - }) - handlerSets.push(handlerSet) - } - } - return handlerSets - } - - search(method: string, path: string): [[T, Params][]] { - const handlerSets: HandlerParamsSet[] = [] - this.params = Object.create(null) - - // eslint-disable-next-line @typescript-eslint/no-this-alias - const curNode: Node = this - let curNodes = [curNode] - const parts = splitPath(path) - - for (let i = 0, len = parts.length; i < len; i++) { - const part: string = parts[i] - const isLast = i === len - 1 - const tempNodes: Node[] = [] - - for (let j = 0, len2 = curNodes.length; j < len2; j++) { - const node = curNodes[j] - const nextNode = node.children[part] - - if (nextNode) { - nextNode.params = node.params - if (isLast === true) { - // '/hello/*' => match '/hello' - if (nextNode.children['*']) { - handlerSets.push( - ...this.gHSets(nextNode.children['*'], method, node.params, Object.create(null)) - ) - } - handlerSets.push(...this.gHSets(nextNode, method, node.params, Object.create(null))) - } else { - tempNodes.push(nextNode) - } - } - - for (let k = 0, len3 = node.patterns.length; k < len3; k++) { - const pattern = node.patterns[k] - - const params = { ...node.params } - - // Wildcard - // '/hello/*/foo' => match /hello/bar/foo - if (pattern === '*') { - const astNode = node.children['*'] - if (astNode) { - handlerSets.push(...this.gHSets(astNode, method, node.params, Object.create(null))) - tempNodes.push(astNode) - } - continue - } - - if (part === '') { - continue - } - - const [key, name, matcher] = pattern - - const child = node.children[key] - - // `/js/:filename{[a-z]+.js}` => match /js/chunk/123.js - const restPathString = parts.slice(i).join('/') - if (matcher instanceof RegExp && matcher.test(restPathString)) { - params[name] = restPathString - handlerSets.push(...this.gHSets(child, method, node.params, params)) - continue - } - - if (matcher === true || (matcher instanceof RegExp && matcher.test(part))) { - if (typeof key === 'string') { - params[name] = part - if (isLast === true) { - handlerSets.push(...this.gHSets(child, method, params, node.params)) - if (child.children['*']) { - handlerSets.push(...this.gHSets(child.children['*'], method, params, node.params)) - } - } else { - child.params = params - tempNodes.push(child) - } - } - } - } - } - - curNodes = tempNodes - } - const results = handlerSets.sort((a, b) => { - return a.score - b.score - }) - - return [results.map(({ handler, params }) => [handler, params] as [T, Params])] - } -} diff --git a/deno_dist/router/trie-router/router.ts b/deno_dist/router/trie-router/router.ts deleted file mode 100644 index 8ae4548c2e..0000000000 --- a/deno_dist/router/trie-router/router.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { Result, Router } from '../../router.ts' -import { checkOptionalParameter } from '../../utils/url.ts' -import { Node } from './node.ts' - -export class TrieRouter implements Router { - name: string = 'TrieRouter' - node: Node - - constructor() { - this.node = new Node() - } - - add(method: string, path: string, handler: T) { - const results = checkOptionalParameter(path) - if (results) { - for (const p of results) { - this.node.insert(method, p, handler) - } - return - } - - this.node.insert(method, path, handler) - } - - match(method: string, path: string): Result { - return this.node.search(method, path) - } -} diff --git a/deno_dist/types.ts b/deno_dist/types.ts deleted file mode 100644 index 00dddc994c..0000000000 --- a/deno_dist/types.ts +++ /dev/null @@ -1,1839 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* 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 { - IfAnyThenEmptyObject, - IsAny, - JSONValue, - Prettify, - RemoveBlankRecord, - Simplify, - UnionToIntersection, -} from './utils/types.ts' - -//////////////////////////////////////// -////// ////// -////// Values ////// -////// ////// -//////////////////////////////////////// - -export type Bindings = Record -export type Variables = Record - -export type Env = { - Bindings?: Bindings - Variables?: Variables -} - -export type Next = () => Promise - -export type ExtractInput = I extends Input - ? unknown extends I['in'] - ? {} - : I['in'] - : I -export type Input = { - in?: {} - out?: {} - outputFormat?: ResponseFormat -} - -export type BlankSchema = {} -export type BlankInput = {} - -//////////////////////////////////////// -////// ////// -////// Routes ////// -////// ////// -//////////////////////////////////////// - -export interface RouterRoute { - path: string - method: string - handler: H -} - -//////////////////////////////////////// -////// ////// -////// Handlers ////// -////// ////// -//////////////////////////////////////// - -export type HandlerResponse = Response | TypedResponse | Promise> - -export type Handler< - E extends Env = any, - P extends string = any, - I extends Input = BlankInput, - R extends HandlerResponse = any -> = (c: Context, next: Next) => R - -export type MiddlewareHandler< - E extends Env = any, - P extends string = string, - I extends Input = {} -> = (c: Context, next: Next) => Promise - -export type H< - E extends Env = any, - P extends string = any, - I extends Input = BlankInput, - R extends HandlerResponse = any -> = Handler | MiddlewareHandler - -export type NotFoundHandler = (c: Context) => Response | Promise -export type ErrorHandler = ( - err: Error, - c: Context -) => Response | Promise - -//////////////////////////////////////// -////// ////// -////// HandlerInterface ////// -////// ////// -//////////////////////////////////////// - -export interface HandlerInterface< - E extends Env = Env, - M extends string = string, - S extends Schema = {}, - BasePath extends string = '/' -> { - // app.get(handler) - < - P extends string = ExtractKey extends never ? BasePath : ExtractKey, - I extends Input = BlankInput, - R extends HandlerResponse = any, - E2 extends Env = E - >( - handler: H - ): Hono, S & ToSchema>, BasePath> - - // app.get(path, handler) - < - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - E2 extends Env = E - >( - path: P, - handler: H - ): Hono< - IntersectNonAnyTypes<[E, E2]>, - S & ToSchema, I, MergeTypedResponse>, - BasePath - > - - // app.get(handler x2) - < - P extends string = ExtractKey extends never ? BasePath : ExtractKey, - I extends Input = BlankInput, - I2 extends Input = I, - R extends HandlerResponse = any, - E2 extends Env = E, - E3 extends Env = IntersectNonAnyTypes<[E, E2]> - >( - ...handlers: [H, H] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3]>, - S & ToSchema>, - BasePath - > - - // app.get(path, handler x2) - < - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - E2 extends Env = E, - E3 extends Env = IntersectNonAnyTypes<[E, E2]> - >( - path: P, - ...handlers: [H, H] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3]>, - S & ToSchema, I2, MergeTypedResponse>, - BasePath - > - - // app.get(handler x 3) - < - P extends string = ExtractKey extends never ? BasePath : ExtractKey, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]> - >( - ...handlers: [H, H, H] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4]>, - S & ToSchema>, - BasePath - > - - // app.get(path, handler x3) - < - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]> - >( - path: P, - ...handlers: [H, H, H] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4]>, - S & ToSchema, I3, MergeTypedResponse>, - BasePath - > - - // app.get(handler x 4) - < - P extends string = ExtractKey extends never ? BasePath : ExtractKey, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]> - >( - ...handlers: [H, H, H, H] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5]>, - S & ToSchema>, - BasePath - > - - // app.get(path, handler x4) - < - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]> - >( - path: P, - ...handlers: [ - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5]>, - S & ToSchema, I4, MergeTypedResponse>, - BasePath - > - - // app.get(handler x 5) - < - P extends string = ExtractKey extends never ? BasePath : ExtractKey, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - 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 = IntersectNonAnyTypes<[E, E2, E3, E4, E5]> - >( - ...handlers: [H, H, H, H, H] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>, - S & ToSchema>, - BasePath - > - - // app.get(path, handler x5) - < - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - 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 = IntersectNonAnyTypes<[E, E2, E3, E4, E5]> - >( - path: P, - ...handlers: [ - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>, - S & ToSchema, I5, MergeTypedResponse>, - BasePath - > - - // app.get(handler x 6) - < - P extends string = ExtractKey extends never ? BasePath : ExtractKey, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]> - >( - ...handlers: [ - H, - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>, - S & ToSchema>, - BasePath - > - - // app.get(path, handler x6) - < - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]> - >( - path: P, - ...handlers: [ - H, - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>, - S & ToSchema, I6, MergeTypedResponse>, - BasePath - > - - // app.get(handler x 7) - < - P extends string = ExtractKey extends never ? BasePath : ExtractKey, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - I7 extends Input = I & I2 & I3 & I4 & I5 & I6, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = E, - E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]> - >( - ...handlers: [ - H, - H, - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>, - S & ToSchema>, - BasePath - > - - // app.get(path, handler x7) - < - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - I7 extends Input = I & I2 & I3 & I4 & I5 & I6, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = E, - E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]> - >( - path: P, - ...handlers: [ - H, - H, - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>, - S & ToSchema, I7, MergeTypedResponse>, - BasePath - > - - // app.get(handler x 8) - < - P extends string = ExtractKey extends never ? BasePath : ExtractKey, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - I7 extends Input = I & I2 & I3 & I4 & I5 & I6, - I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = E, - E8 extends Env = E, - E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]> - >( - ...handlers: [ - H, - H, - H, - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>, - S & ToSchema>, - BasePath - > - - // app.get(path, handler x8) - < - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - I7 extends Input = I & I2 & I3 & I4 & I5 & I6, - I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = E, - E8 extends Env = E, - E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]> - >( - path: P, - ...handlers: [ - H, - H, - H, - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>, - S & ToSchema, I8, MergeTypedResponse>, - BasePath - > - - // app.get(handler x 9) - < - P extends string = ExtractKey extends never ? BasePath : ExtractKey, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - I7 extends Input = I & I2 & I3 & I4 & I5 & I6, - I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7, - I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = E, - E8 extends Env = E, - E9 extends Env = E, - E10 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]> - >( - ...handlers: [ - H, - H, - H, - H, - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]>, - S & ToSchema>, - BasePath - > - - // app.get(path, handler x9) - < - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - I7 extends Input = I & I2 & I3 & I4 & I5 & I6, - I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7, - I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = E, - E8 extends Env = E, - E9 extends Env = E, - E10 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]> - >( - path: P, - ...handlers: [ - H, - H, - H, - H, - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]>, - S & ToSchema, I9, MergeTypedResponse>, - BasePath - > - - // app.get(handler x 10) - < - P extends string = ExtractKey extends never ? BasePath : ExtractKey, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - I7 extends Input = I & I2 & I3 & I4 & I5 & I6, - I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7, - I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8, - I10 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8 & I9, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = E, - E8 extends Env = E, - E9 extends Env = E, - E10 extends Env = E, - E11 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]> - >( - ...handlers: [ - H, - H, - H, - H, - H, - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11]>, - S & ToSchema>, - BasePath - > - - // app.get(path, handler x10) - < - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - I7 extends Input = I & I2 & I3 & I4 & I5 & I6, - I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7, - I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8, - I10 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8 & I9, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = E, - E8 extends Env = E, - E9 extends Env = E, - E10 extends Env = E, - E11 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]> - >( - path: P, - ...handlers: [ - H, - H, - H, - H, - H, - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11]>, - S & ToSchema, I10, MergeTypedResponse>, - BasePath - > - - // app.get(...handlers[]) - < - P extends string = ExtractKey extends never ? BasePath : ExtractKey, - I extends Input = BlankInput, - R extends HandlerResponse = any - >( - ...handlers: H[] - ): Hono>, BasePath> - - // app.get(path, ...handlers[]) -

= any>( - path: P, - ...handlers: H, I, R>[] - ): Hono, I, MergeTypedResponse>, BasePath> - - // app.get(path) -

= any, I extends Input = {}>(path: P): Hono< - E, - S & ToSchema, I, MergeTypedResponse>, - BasePath - > -} - -//////////////////////////////////////// -////// ////// -////// MiddlewareHandlerInterface ////// -////// ////// -//////////////////////////////////////// - -export interface MiddlewareHandlerInterface< - E extends Env = Env, - S extends Schema = {}, - BasePath extends string = '/' -> { - //// app.use(...handlers[]) - ( - ...handlers: MiddlewareHandler>>[] - ): Hono, S, BasePath> - - // app.use(handler) - (handler: MiddlewareHandler>>): Hono< - IntersectNonAnyTypes<[E, E2]>, - S, - BasePath - > - - // app.use(handler x2) - < - E2 extends Env = E, - E3 extends Env = IntersectNonAnyTypes<[E, E2]>, - P extends string = MergePath> - >( - ...handlers: [MiddlewareHandler, MiddlewareHandler] - ): Hono, S, BasePath> - - // app.use(handler x3) - < - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>, - P extends string = MergePath> - >( - ...handlers: [MiddlewareHandler, MiddlewareHandler, MiddlewareHandler] - ): Hono, S, BasePath> - - // app.use(handler x4) - < - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>, - P extends string = MergePath> - >( - ...handlers: [ - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler - ] - ): Hono, S, BasePath> - - // app.use(handler x5) - < - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>, - P extends string = MergePath> - >( - ...handlers: [ - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler - ] - ): Hono, S, BasePath> - - // app.use(handler x6) - < - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>, - P extends string = MergePath> - >( - ...handlers: [ - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler - ] - ): Hono, S, BasePath> - - // app.use(handler x7) - < - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = E, - E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>, - P extends string = MergePath> - >( - ...handlers: [ - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler - ] - ): Hono, S, BasePath> - - // app.use(handler x8) - < - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = E, - E8 extends Env = E, - E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>, - P extends string = MergePath> - >( - ...handlers: [ - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler - ] - ): Hono, S, BasePath> - - // app.use(handler x9) - < - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = E, - E8 extends Env = E, - E9 extends Env = E, - E10 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>, - P extends string = MergePath> - >( - ...handlers: [ - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler - ] - ): Hono, S, BasePath> - - // app.use(handler x10) - < - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = E, - E8 extends Env = E, - E9 extends Env = E, - E10 extends Env = E, - E11 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]>, - P extends string = MergePath> - >( - ...handlers: [ - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler, - MiddlewareHandler - ] - ): Hono, S, BasePath> - - //// app.use(path, ...handlers[]) -

( - path: P, - ...handlers: MiddlewareHandler>[] - ): Hono -} - -//////////////////////////////////////// -////// ////// -////// OnHandlerInterface ////// -////// ////// -//////////////////////////////////////// - -export interface OnHandlerInterface< - E extends Env = Env, - S extends Schema = {}, - BasePath extends string = '/' -> { - // app.on(method, path, handler) - < - M extends string, - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - E2 extends Env = E - >( - method: M, - path: P, - handler: H - ): Hono< - IntersectNonAnyTypes<[E, E2]>, - S & ToSchema, I, MergeTypedResponse>, - BasePath - > - - // app.on(method, path, handler x2) - < - M extends string, - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - E2 extends Env = E, - E3 extends Env = IntersectNonAnyTypes<[E, E2]> - >( - method: M, - path: P, - ...handlers: [H, H] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3]>, - S & ToSchema, I2, MergeTypedResponse>, - BasePath - > - - // app.on(method, path, handler x3) - < - M extends string, - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]> - >( - method: M, - path: P, - ...handlers: [H, H, H] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4]>, - S & ToSchema, I3, MergeTypedResponse>, - BasePath - > - - // app.on(method, path, handler x4) - < - M extends string, - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]> - >( - method: M, - path: P, - ...handlers: [ - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5]>, - S & ToSchema, I4, MergeTypedResponse>, - BasePath - > - - // app.on(method, path, handler x5) - < - M extends string, - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - 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 = IntersectNonAnyTypes<[E, E2, E3, E4, E5]> - >( - method: M, - path: P, - ...handlers: [ - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>, - S & ToSchema, I5, MergeTypedResponse>, - BasePath - > - - // app.on(method, path, handler x6) - < - M extends string, - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]> - >( - method: M, - path: P, - ...handlers: [ - H, - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>, - S & ToSchema, I6, MergeTypedResponse>, - BasePath - > - - // app.on(method, path, handler x7) - < - M extends string, - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - I7 extends Input = I & I2 & I3 & I4 & I5 & I6, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = E, - E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]> - >( - method: M, - path: P, - ...handlers: [ - H, - H, - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>, - S & ToSchema, I7, MergeTypedResponse>, - BasePath - > - - // app.on(method, path, handler x8) - < - M extends string, - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - I7 extends Input = I & I2 & I3 & I4 & I5 & I6, - I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = E, - E8 extends Env = E, - E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]> - >( - method: M, - path: P, - ...handlers: [ - H, - H, - H, - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>, - S & ToSchema, I8, MergeTypedResponse>, - BasePath - > - - // app.on(method, path, handler x9) - < - M extends string, - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - I7 extends Input = I & I2 & I3 & I4 & I5 & I6, - I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7, - I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = E, - E8 extends Env = E, - E9 extends Env = E, - E10 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]> - >( - method: M, - path: P, - ...handlers: [ - H, - H, - H, - H, - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]>, - S & ToSchema, I9, MergeTypedResponse>, - BasePath - > - - // app.on(method, path, handler x10) - < - M extends string, - P extends string, - MergedPath extends MergePath = MergePath, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - I7 extends Input = I & I2 & I3 & I4 & I5 & I6, - I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7, - I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8, - I10 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8 & I9, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = E, - E8 extends Env = E, - E9 extends Env = E, - E10 extends Env = E, - E11 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]> - >( - method: M, - path: P, - ...handlers: [ - H, - H, - H, - H, - H, - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11]>, - S & ToSchema, I10, MergeTypedResponse>>, - BasePath - > - - // app.get(method, path, ...handler) - = any, I extends Input = {}>( - method: M, - path: P, - ...handlers: H, I, R>[] - ): Hono, I, MergeTypedResponse>, BasePath> - - // app.get(method[], path, handler) - < - Ms extends string[], - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - E2 extends Env = E - >( - methods: Ms, - path: P, - handler: H - ): Hono< - IntersectNonAnyTypes<[E, E2]>, - S & ToSchema, I, MergeTypedResponse>, - BasePath - > - - // app.get(method[], path, handler x2) - < - Ms extends string[], - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - E2 extends Env = E, - E3 extends Env = IntersectNonAnyTypes<[E, E2]> - >( - methods: Ms, - path: P, - ...handlers: [H, H] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3]>, - S & ToSchema, I2, MergeTypedResponse>, - BasePath - > - - // app.get(method[], path, handler x3) - < - Ms extends string[], - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]> - >( - methods: Ms, - path: P, - ...handlers: [H, H, H] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4]>, - S & ToSchema, I3, MergeTypedResponse>, - BasePath - > - - // app.get(method[], path, handler x4) - < - Ms extends string[], - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]> - >( - methods: Ms, - path: P, - ...handlers: [ - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5]>, - S & ToSchema, I4, MergeTypedResponse>, - BasePath - > - - // app.get(method[], path, handler x5) - < - Ms extends string[], - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - 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 = IntersectNonAnyTypes<[E, E2, E3, E4, E5]> - >( - methods: Ms, - path: P, - ...handlers: [ - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>, - S & ToSchema, I5, MergeTypedResponse>, - BasePath - > - - // app.get(method[], path, handler x6) - < - Ms extends string[], - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]> - >( - methods: Ms, - path: P, - ...handlers: [ - H, - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>, - S & ToSchema, I6, MergeTypedResponse>, - BasePath - > - - // app.get(method[], path, handler x7) - < - Ms extends string[], - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - I7 extends Input = I & I2 & I3 & I4 & I5 & I6, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = E, - E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]> - >( - methods: Ms, - path: P, - ...handlers: [ - H, - H, - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>, - S & ToSchema, I7, MergeTypedResponse>, - BasePath - > - - // app.get(method[], path, handler x8) - < - Ms extends string[], - P extends string, - MergedPath extends MergePath = MergePath, - R extends HandlerResponse = any, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - I7 extends Input = I & I2 & I3 & I4 & I5 & I6, - I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = E, - E8 extends Env = E, - E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]> - >( - methods: Ms, - path: P, - ...handlers: [ - H, - H, - H, - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>, - S & ToSchema, I8, MergeTypedResponse>, - BasePath - > - - // app.get(method[], path, handler x9) - < - Ms extends string[], - P extends string, - MergedPath extends MergePath = MergePath, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - I7 extends Input = I & I2 & I3 & I4 & I5 & I6, - I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7, - I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = E, - E8 extends Env = E, - E9 extends Env = E, - E10 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]> - >( - methods: Ms, - path: P, - ...handlers: [ - H, - H, - H, - H, - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]>, - S & ToSchema, I9, MergeTypedResponse>>, - BasePath - > - - // app.get(method[], path, handler x10) - < - Ms extends string[], - P extends string, - MergedPath extends MergePath = MergePath, - I extends Input = BlankInput, - I2 extends Input = I, - I3 extends Input = I & I2, - I4 extends Input = I & I2 & I3, - I5 extends Input = I & I2 & I3 & I4, - I6 extends Input = I & I2 & I3 & I4 & I5, - I7 extends Input = I & I2 & I3 & I4 & I5 & I6, - I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7, - I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8, - I10 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8 & I9, - E2 extends Env = E, - E3 extends Env = E, - E4 extends Env = E, - E5 extends Env = E, - E6 extends Env = E, - E7 extends Env = E, - E8 extends Env = E, - E9 extends Env = E, - E10 extends Env = E, - E11 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]> - >( - methods: Ms, - path: P, - ...handlers: [ - H, - H, - H, - H, - H, - H, - H, - H, - H, - H - ] - ): Hono< - IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11]>, - S & ToSchema, I10, MergeTypedResponse>>, - BasePath - > - - // app.on(method[], path, ...handler) -

= any, I extends Input = {}>( - methods: string[], - path: P, - ...handlers: H, I, R>[] - ): Hono, I, MergeTypedResponse>, BasePath> - - // app.on(method | method[], path[], ...handlers[]) - = any>( - methods: string | string[], - paths: string[], - ...handlers: H[] - ): Hono>, BasePath> -} - -type ExtractKey = S extends Record - ? Key extends string - ? Key - : never - : string - -//////////////////////////////////////// -////// ////// -////// ToSchema ////// -////// ////// -//////////////////////////////////////// - -export type ToSchema< - M extends string, - P extends string, - I extends Input | Input['in'], - RorO // Response or Output -> = Prettify<{ - [K in P]: { - [K2 in M as AddDollar]: Simplify< - { - input: AddParam, P> - } & (IsAny extends true - ? { - output: {} - outputFormat: ResponseFormat - status: StatusCode - } - : RorO extends TypedResponse - ? { - output: unknown extends T ? {} : T - outputFormat: I extends { outputFormat: string } ? I['outputFormat'] : F - status: U - } - : { - output: unknown extends RorO ? {} : RorO - outputFormat: unknown extends RorO - ? 'json' - : I extends { outputFormat: string } - ? I['outputFormat'] - : 'json' - status: StatusCode - }) - > - } -}> - -export type Schema = { - [Path: string]: { - [Method: `$${Lowercase}`]: Endpoint - } -} - -export type Endpoint = { - input: Partial - output: any - outputFormat: ResponseFormat - status: StatusCode -} - -type ExtractParams = string extends Path - ? Record - : Path extends `${infer _Start}:${infer Param}/${infer Rest}` - ? { [K in Param | keyof ExtractParams<`/${Rest}`>]: string } - : Path extends `${infer _Start}:${infer Param}` - ? { [K in Param]: string } - : never - -type FlattenIfIntersect = T extends infer O ? { [K in keyof O]: O[K] } : never - -export type MergeSchemaPath = Prettify<{ - [P in keyof OrigSchema as MergePath]: { - [M in keyof OrigSchema[P]]: MergeEndpointParamsWithPath - } -}> - -type MergeEndpointParamsWithPath = T extends { - input: infer Input - output: infer Output - outputFormat: infer OutputFormat - status: infer Status -} - ? { - input: Input extends { param: infer _ } - ? ExtractParams extends never - ? Input - : FlattenIfIntersect< - Input & { - param: { - // Maps extracted keys, stripping braces, to a string-typed record. - [K in keyof ExtractParams as K extends `${infer Prefix}{${infer _}}` - ? Prefix - : K]: string - } - } - > - : RemoveBlankRecord> extends never - ? Input - : Input & { - // Maps extracted keys, stripping braces, to a string-typed record. - param: { - [K in keyof ExtractParams as K extends `${infer Prefix}{${infer _}}` - ? Prefix - : K]: string - } - } - output: Output - outputFormat: OutputFormat - status: Status - } - : never - -export type AddParam = ParamKeys

extends never - ? I - : I extends { param: infer _ } - ? I - : I & { param: UnionToIntersection>> } - -type AddDollar = `$${Lowercase}` - -export type MergePath = B extends '' - ? MergePath - : A extends '' - ? B - : A extends '/' - ? B - : A extends `${infer P}/` - ? B extends `/${infer Q}` - ? `${P}/${Q}` - : `${P}/${B}` - : B extends `/${infer Q}` - ? Q extends '' - ? A - : `${A}/${Q}` - : `${A}/${B}` - -//////////////////////////////////////// -////// ////// -////// TypedResponse ////// -////// ////// -//////////////////////////////////////// - -export type KnownResponseFormat = 'json' | 'text' -export type ResponseFormat = KnownResponseFormat | string - -export type TypedResponse< - T = unknown, - U extends StatusCode = StatusCode, - F extends ResponseFormat = T extends string - ? 'text' - : T extends JSONValue - ? 'json' - : ResponseFormat -> = { - data: T - status: U - format: F -} - -type MergeTypedResponse = T extends Promise - ? T2 extends TypedResponse - ? T2 - : TypedResponse - : T extends TypedResponse - ? T - : TypedResponse - -//////////////////////////////////////// -////// ///// -////// ValidationTargets ///// -////// ///// -//////////////////////////////////////// - -export type ValidationTargets = { - json: any - form: Record - query: Record - param: Record | Record - header: Record - cookie: Record -} - -//////////////////////////////////////// -////// ////// -////// Path parameters ////// -////// ////// -//////////////////////////////////////// - -type ParamKeyName = NameWithPattern extends `${infer Name}{${infer Rest}` - ? Rest extends `${infer _Pattern}?` - ? `${Name}?` - : Name - : NameWithPattern - -type ParamKey = Component extends `:${infer NameWithPattern}` - ? ParamKeyName - : never - -export type ParamKeys = Path extends `${infer Component}/${infer Rest}` - ? ParamKey | ParamKeys - : ParamKey - -export type ParamKeyToRecord = T extends `${infer R}?` - ? Record - : { [K in T]: string } - -//////////////////////////////////////// -////// ////// -///// For HonoRequest ////// -////// ////// -//////////////////////////////////////// - -export type InputToDataByTarget< - T extends Input['out'], - Target extends keyof ValidationTargets -> = T extends { - [K in Target]: infer R -} - ? R - : never - -export type RemoveQuestion = T extends `${infer R}?` ? R : T - -//////////////////////////////////////// -////// ////// -////// Utilities ////// -////// ////// -//////////////////////////////////////// - -export type ExtractSchema = UnionToIntersection< - T extends Hono ? S : never -> - -type EnvOrEmpty = T extends Env ? (Env extends T ? {} : T) : T -type IntersectNonAnyTypes = T extends [infer Head, ...infer Rest] - ? IfAnyThenEmptyObject> & IntersectNonAnyTypes - : {} - -//////////////////////////////////////// -////// ////// -////// FetchEvent ////// -////// ////// -//////////////////////////////////////// - -export abstract class FetchEventLike { - abstract readonly request: Request - abstract respondWith(promise: Response | Promise): void - abstract passThroughOnException(): void - abstract waitUntil(promise: Promise): void -} diff --git a/deno_dist/utils/body.ts b/deno_dist/utils/body.ts deleted file mode 100644 index 8323c93073..0000000000 --- a/deno_dist/utils/body.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { HonoRequest } from '../request.ts' - -export type BodyData = Record -export type ParseBodyOptions = { - /** - * Determines whether all fields with multiple values should be parsed as arrays. - * @default false - * @example - * const data = new FormData() - * data.append('file', 'aaa') - * data.append('file', 'bbb') - * data.append('message', 'hello') - * - * If all is false: - * parseBody should return { file: 'bbb', message: 'hello' } - * - * If all is true: - * parseBody should return { file: ['aaa', 'bbb'], message: 'hello' } - */ - all?: boolean -} - -export const parseBody = async ( - request: HonoRequest | Request, - options: ParseBodyOptions = { all: false } -): Promise => { - const headers = request instanceof HonoRequest ? request.raw.headers : request.headers - const contentType = headers.get('Content-Type') - - if ( - (contentType !== null && contentType.startsWith('multipart/form-data')) || - (contentType !== null && contentType.startsWith('application/x-www-form-urlencoded')) - ) { - return parseFormData(request, options) - } - - return {} as T -} - -async function parseFormData( - request: HonoRequest | Request, - options: ParseBodyOptions -): Promise { - const formData = await (request as Request).formData() - - if (formData) { - return convertFormDataToBodyData(formData, options) - } - - return {} as T -} - -function convertFormDataToBodyData( - formData: FormData, - options: ParseBodyOptions -): T { - const form: BodyData = {} - - formData.forEach((value, key) => { - const shouldParseAllValues = options.all || key.endsWith('[]') - - if (!shouldParseAllValues) { - form[key] = value - } else { - handleParsingAllValues(form, key, value) - } - }) - - return form as T -} - -const handleParsingAllValues = (form: BodyData, key: string, value: FormDataEntryValue): void => { - const formKey = form[key] as (string | File)[] - - if (form[key] && Array.isArray(form[key])) { - formKey.push(value) - } else if (form[key]) { - const parsedKey = [...formKey].join('').replace(',', '') - - form[key] = [parsedKey, value] - } else { - form[key] = value - } -} diff --git a/deno_dist/utils/buffer.ts b/deno_dist/utils/buffer.ts deleted file mode 100644 index 5ba55a4e00..0000000000 --- a/deno_dist/utils/buffer.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { sha256 } from './crypto.ts' - -export const equal = (a: ArrayBuffer, b: ArrayBuffer) => { - if (a === b) { - return true - } - if (a.byteLength !== b.byteLength) { - return false - } - - const va = new DataView(a) - const vb = new DataView(b) - - let i = va.byteLength - while (i--) { - if (va.getUint8(i) !== vb.getUint8(i)) { - return false - } - } - - return true -} - -export const timingSafeEqual = async ( - a: string | object | boolean, - b: string | object | boolean, - hashFunction?: Function -) => { - if (!hashFunction) { - hashFunction = sha256 - } - - const sa = await hashFunction(a) - const sb = await hashFunction(b) - - if (!sa || !sb) { - return false - } - - return sa === sb && a === b -} - -export const bufferToString = (buffer: ArrayBuffer): string => { - if (buffer instanceof ArrayBuffer) { - const enc = new TextDecoder('utf-8') - return enc.decode(buffer) - } - return buffer -} - -export const bufferToFormData = ( - arrayBuffer: ArrayBuffer, - contentType: string -): Promise => { - const response = new Response(arrayBuffer, { - headers: { - 'Content-Type': contentType, - }, - }) - return response.formData() -} diff --git a/deno_dist/utils/color.ts b/deno_dist/utils/color.ts deleted file mode 100644 index 7f354e32d1..0000000000 --- a/deno_dist/utils/color.ts +++ /dev/null @@ -1,14 +0,0 @@ -export function getColorEnabled(): boolean { - // 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 - - return !isNoColor -} diff --git a/deno_dist/utils/concurrent.ts b/deno_dist/utils/concurrent.ts deleted file mode 100644 index c39859a92c..0000000000 --- a/deno_dist/utils/concurrent.ts +++ /dev/null @@ -1,50 +0,0 @@ -const DEFAULT_CONCURRENCY = 1024 - -export interface Pool { - run(fn: () => T): Promise -} - -export const createPool = ({ - concurrency, - interval, -}: { - concurrency?: number - interval?: number -} = {}): Pool => { - concurrency ||= DEFAULT_CONCURRENCY - - if (concurrency === Infinity) { - // unlimited - return { - run: async (fn) => fn(), - } - } - - const pool: Set<{}> = new Set() - const run = async ( - fn: () => T, - promise?: Promise, - resolve?: (result: T) => void - ): Promise => { - if (pool.size >= (concurrency as number)) { - promise ||= new Promise((r) => (resolve = r)) - setTimeout(() => run(fn, promise, resolve)) - return promise - } - const marker = {} - pool.add(marker) - const result = await fn() - if (interval) { - setTimeout(() => pool.delete(marker), interval) - } else { - pool.delete(marker) - } - if (resolve) { - resolve(result) - return promise as Promise - } else { - return result - } - } - return { run } -} diff --git a/deno_dist/utils/cookie.ts b/deno_dist/utils/cookie.ts deleted file mode 100644 index b8c18d6074..0000000000 --- a/deno_dist/utils/cookie.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { decodeURIComponent_ } from './url.ts' - -export type Cookie = Record -export type SignedCookie = Record - -type PartitionCookieConstraint = - | { partition: true; secure: true } - | { partition?: boolean; secure?: boolean } // reset to default -type SecureCookieConstraint = { secure: true } -type HostCookieConstraint = { secure: true; path: '/'; domain?: undefined } - -export type CookieOptions = { - domain?: string - expires?: Date - httpOnly?: boolean - maxAge?: number - path?: string - secure?: boolean - signingSecret?: string - sameSite?: 'Strict' | 'Lax' | 'None' - partitioned?: boolean - prefix?: CookiePrefixOptions -} & PartitionCookieConstraint -export type CookiePrefixOptions = 'host' | 'secure' - -export type CookieConstraint = Name extends `__Secure-${string}` - ? CookieOptions & SecureCookieConstraint - : Name extends `__Host-${string}` - ? CookieOptions & HostCookieConstraint - : CookieOptions - -const algorithm = { name: 'HMAC', hash: 'SHA-256' } - -const getCryptoKey = async (secret: string | BufferSource): Promise => { - const secretBuf = typeof secret === 'string' ? new TextEncoder().encode(secret) : secret - return await crypto.subtle.importKey('raw', secretBuf, algorithm, false, ['sign', 'verify']) -} - -const makeSignature = async (value: string, secret: string | BufferSource): Promise => { - const key = await getCryptoKey(secret) - const signature = await crypto.subtle.sign(algorithm.name, key, new TextEncoder().encode(value)) - // the returned base64 encoded signature will always be 44 characters long and end with one or two equal signs - return btoa(String.fromCharCode(...new Uint8Array(signature))) -} - -const verifySignature = async ( - base64Signature: string, - value: string, - secret: CryptoKey -): Promise => { - try { - const signatureBinStr = atob(base64Signature) - const signature = new Uint8Array(signatureBinStr.length) - for (let i = 0, len = signatureBinStr.length; i < len; i++) { - signature[i] = signatureBinStr.charCodeAt(i) - } - return await crypto.subtle.verify(algorithm, secret, signature, new TextEncoder().encode(value)) - } catch (e) { - return false - } -} - -// all alphanumeric chars and all of _!#$%&'*.^`|~+- -// (see: https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1) -const validCookieNameRegEx = /^[\w!#$%&'*.^`|~+-]+$/ - -// all ASCII chars 32-126 except 34, 59, and 92 (i.e. space to tilde but not double quote, semicolon, or backslash) -// (see: https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1) -// -// note: the spec also prohibits comma and space, but we allow both since they are very common in the real world -// (see: https://github.com/golang/go/issues/7243) -const validCookieValueRegEx = /^[ !#-:<-[\]-~]*$/ - -export const parse = (cookie: string, name?: string): Cookie => { - const pairs = cookie.trim().split(';') - return pairs.reduce((parsedCookie, pairStr) => { - pairStr = pairStr.trim() - const valueStartPos = pairStr.indexOf('=') - if (valueStartPos === -1) { - return parsedCookie - } - - const cookieName = pairStr.substring(0, valueStartPos).trim() - if ((name && name !== cookieName) || !validCookieNameRegEx.test(cookieName)) { - return parsedCookie - } - - let cookieValue = pairStr.substring(valueStartPos + 1).trim() - if (cookieValue.startsWith('"') && cookieValue.endsWith('"')) { - cookieValue = cookieValue.slice(1, -1) - } - if (validCookieValueRegEx.test(cookieValue)) { - parsedCookie[cookieName] = decodeURIComponent_(cookieValue) - } - - return parsedCookie - }, {} as Cookie) -} - -export const parseSigned = async ( - cookie: string, - secret: string | BufferSource, - name?: string -): Promise => { - const parsedCookie: SignedCookie = {} - const secretKey = await getCryptoKey(secret) - - for (const [key, value] of Object.entries(parse(cookie, name))) { - const signatureStartPos = value.lastIndexOf('.') - if (signatureStartPos < 1) { - continue - } - - const signedValue = value.substring(0, signatureStartPos) - const signature = value.substring(signatureStartPos + 1) - if (signature.length !== 44 || !signature.endsWith('=')) { - continue - } - - const isVerified = await verifySignature(signature, signedValue, secretKey) - parsedCookie[key] = isVerified ? signedValue : false - } - - return parsedCookie -} - -const _serialize = (name: string, value: string, opt: CookieOptions = {}): string => { - let cookie = `${name}=${value}` - - if (name.startsWith('__Secure-') && !opt.secure) { - // FIXME: replace link to RFC - // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-13#section-4.1.3.1 - throw new Error('__Secure- Cookie must have Secure attributes') - } - - if (name.startsWith('__Host-')) { - // FIXME: replace link to RFC - // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-13#section-4.1.3.2 - if (!opt.secure) { - throw new Error('__Host- Cookie must have Secure attributes') - } - - if (opt.path !== '/') { - throw new Error('__Host- Cookie must have Path attributes with "/"') - } - - if (opt.domain) { - throw new Error('__Host- Cookie must not have Domain attributes') - } - } - - if (opt && typeof opt.maxAge === 'number' && opt.maxAge >= 0) { - if (opt.maxAge > 34560000) { - // FIXME: replace link to RFC - // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-13#section-4.1.2.2 - throw new Error( - 'Cookies Max-Age SHOULD NOT be greater than 400 days (34560000 seconds) in duration.' - ) - } - cookie += `; Max-Age=${Math.floor(opt.maxAge)}` - } - - if (opt.domain && opt.prefix !== 'host') { - cookie += `; Domain=${opt.domain}` - } - - if (opt.path) { - cookie += `; Path=${opt.path}` - } - - if (opt.expires) { - if (opt.expires.getTime() - Date.now() > 34560000_000) { - // FIXME: replace link to RFC - // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-13#section-4.1.2.1 - throw new Error( - 'Cookies Expires SHOULD NOT be greater than 400 days (34560000 seconds) in the future.' - ) - } - cookie += `; Expires=${opt.expires.toUTCString()}` - } - - if (opt.httpOnly) { - cookie += '; HttpOnly' - } - - if (opt.secure) { - cookie += '; Secure' - } - - if (opt.sameSite) { - cookie += `; SameSite=${opt.sameSite}` - } - - if (opt.partitioned) { - // FIXME: replace link to RFC - // https://www.ietf.org/archive/id/draft-cutler-httpbis-partitioned-cookies-01.html#section-2.3 - if (!opt.secure) { - throw new Error('Partitioned Cookie must have Secure attributes') - } - cookie += '; Partitioned' - } - - return cookie -} - -export const serialize = ( - name: Name, - value: string, - opt?: CookieConstraint -): string => { - value = encodeURIComponent(value) - return _serialize(name, value, opt) -} - -export const serializeSigned = async ( - name: string, - value: string, - secret: string | BufferSource, - opt: CookieOptions = {} -): Promise => { - const signature = await makeSignature(value, secret) - value = `${value}.${signature}` - value = encodeURIComponent(value) - return _serialize(name, value, opt) -} diff --git a/deno_dist/utils/crypto.ts b/deno_dist/utils/crypto.ts deleted file mode 100644 index b763b507ca..0000000000 --- a/deno_dist/utils/crypto.ts +++ /dev/null @@ -1,60 +0,0 @@ -type Algorithm = { - name: string - alias: string -} - -type Data = string | boolean | number | object | ArrayBufferView | ArrayBuffer | ReadableStream - -export const sha256 = async (data: Data) => { - const algorithm: Algorithm = { name: 'SHA-256', alias: 'sha256' } - const hash = await createHash(data, algorithm) - return hash -} - -export const sha1 = async (data: Data) => { - const algorithm: Algorithm = { name: 'SHA-1', alias: 'sha1' } - const hash = await createHash(data, algorithm) - return hash -} - -export const md5 = async (data: Data) => { - const algorithm: Algorithm = { name: 'MD5', alias: 'md5' } - const hash = await createHash(data, algorithm) - return hash -} - -export const createHash = async (data: Data, algorithm: Algorithm): Promise => { - let sourceBuffer: ArrayBufferView | ArrayBuffer - - if (data instanceof ReadableStream) { - let body = '' - const reader = data.getReader() - await reader?.read().then(async (chuck) => { - const value = await createHash(chuck.value || '', algorithm) - body += value - }) - return body - } - if (ArrayBuffer.isView(data) || data instanceof ArrayBuffer) { - sourceBuffer = data - } else { - if (typeof data === 'object') { - data = JSON.stringify(data) - } - sourceBuffer = new TextEncoder().encode(String(data)) - } - - if (crypto && crypto.subtle) { - const buffer = await crypto.subtle.digest( - { - name: algorithm.name, - }, - sourceBuffer as ArrayBuffer - ) - const hash = Array.prototype.map - .call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2)) - .join('') - return hash - } - return null -} diff --git a/deno_dist/utils/encode.ts b/deno_dist/utils/encode.ts deleted file mode 100644 index fe06feec26..0000000000 --- a/deno_dist/utils/encode.ts +++ /dev/null @@ -1,29 +0,0 @@ -export const decodeBase64Url = (str: string): Uint8Array => { - return decodeBase64(str.replace(/_|-/g, (m) => ({ _: '/', '-': '+' }[m] ?? m))) -} - -export const encodeBase64Url = (buf: ArrayBufferLike): string => - encodeBase64(buf).replace(/\/|\+/g, (m) => ({ '/': '_', '+': '-' }[m] ?? m)) - -// This approach is written in MDN. -// btoa does not support utf-8 characters. So we need a little bit hack. -export const encodeBase64 = (buf: ArrayBufferLike): string => { - let binary = '' - const bytes = new Uint8Array(buf) - for (let i = 0, len = bytes.length; i < len; i++) { - binary += String.fromCharCode(bytes[i]) - } - return btoa(binary) -} - -// atob does not support utf-8 characters. So we need a little bit hack. -export const decodeBase64 = (str: string): Uint8Array => { - const binary = atob(str) - const bytes = new Uint8Array(new ArrayBuffer(binary.length)) - const half = binary.length / 2 - for (let i = 0, j = binary.length - 1; i <= half; i++, j--) { - bytes[i] = binary.charCodeAt(i) - bytes[j] = binary.charCodeAt(j) - } - return bytes -} diff --git a/deno_dist/utils/filepath.ts b/deno_dist/utils/filepath.ts deleted file mode 100644 index 35c6159f7c..0000000000 --- a/deno_dist/utils/filepath.ts +++ /dev/null @@ -1,51 +0,0 @@ -type FilePathOptions = { - filename: string - root?: string - defaultDocument?: string -} - -export const getFilePath = (options: FilePathOptions): string | undefined => { - let filename = options.filename - const defaultDocument = options.defaultDocument || 'index.html' - - if (filename.endsWith('/')) { - // /top/ => /top/index.html - filename = filename.concat(defaultDocument) - } else if (!filename.match(/\.[a-zA-Z0-9]+$/)) { - // /top => /top/index.html - filename = filename.concat('/' + defaultDocument) - } - - const path = getFilePathWithoutDefaultDocument({ - root: options.root, - filename, - }) - - return path -} - -export const getFilePathWithoutDefaultDocument = ( - options: Omit -) => { - let root = options.root || '' - let filename = options.filename - - if (/(?:^|[\/\\])\.\.(?:$|[\/\\])/.test(filename)) { - return - } - - // /foo.html => foo.html - filename = filename.replace(/^\.?[\/\\]/, '') - - // foo\bar.txt => foo/bar.txt - filename = filename.replace(/\\/, '/') - - // assets/ => assets - root = root.replace(/\/$/, '') - - // ./assets/foo.html => assets/foo.html - let path = root ? root + '/' + filename : filename - path = path.replace(/^\.?\//, '') - - return path -} diff --git a/deno_dist/utils/handler.ts b/deno_dist/utils/handler.ts deleted file mode 100644 index 29049e64b4..0000000000 --- a/deno_dist/utils/handler.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { COMPOSED_HANDLER } from '../hono-base.ts' - -export const isMiddleware = (handler: Function) => handler.length > 1 -export const findTargetHandler = (handler: Function): Function => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return (handler as any)[COMPOSED_HANDLER] - ? // eslint-disable-next-line @typescript-eslint/no-explicit-any - findTargetHandler((handler as any)[COMPOSED_HANDLER]) - : handler -} diff --git a/deno_dist/utils/html.ts b/deno_dist/utils/html.ts deleted file mode 100644 index 8b6fdebeb9..0000000000 --- a/deno_dist/utils/html.ts +++ /dev/null @@ -1,150 +0,0 @@ -export const HtmlEscapedCallbackPhase = { - Stringify: 1, - BeforeStream: 2, - Stream: 3, -} as const -type HtmlEscapedCallbackOpts = { - buffer?: [string] - phase: (typeof HtmlEscapedCallbackPhase)[keyof typeof HtmlEscapedCallbackPhase] - context: object // An object unique to each JSX tree. This object is used as the WeakMap key. -} -export type HtmlEscapedCallback = (opts: HtmlEscapedCallbackOpts) => Promise | undefined -export type HtmlEscaped = { - isEscaped: true - callbacks?: HtmlEscapedCallback[] -} -export type HtmlEscapedString = string & HtmlEscaped - -/** - * StringBuffer contains string and Promise alternately - * The length of the array will be odd, the odd numbered element will be a string, - * and the even numbered element will be a Promise. - * When concatenating into a single string, it must be processed from the tail. - * @example - * [ - * 'framework.', - * Promise.resolve('ultra fast'), - * 'a ', - * Promise.resolve('is '), - * 'Hono', - * ] - */ -export type StringBuffer = (string | Promise)[] - -export const raw = (value: unknown, callbacks?: HtmlEscapedCallback[]): HtmlEscapedString => { - const escapedString = new String(value) as HtmlEscapedString - escapedString.isEscaped = true - escapedString.callbacks = callbacks - - return escapedString -} - -// The `escapeToBuffer` implementation is based on code from the MIT licensed `react-dom` package. -// https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/server/escapeTextForBrowser.js - -const escapeRe = /[&<>'"]/ - -export const stringBufferToString = async (buffer: StringBuffer): Promise => { - let str = '' - const callbacks: HtmlEscapedCallback[] = [] - for (let i = buffer.length - 1; ; i--) { - str += buffer[i] - i-- - if (i < 0) { - break - } - - let r = await buffer[i] - if (typeof r === 'object') { - callbacks.push(...((r as HtmlEscapedString).callbacks || [])) - } - - const isEscaped = (r as HtmlEscapedString).isEscaped - r = await (typeof r === 'object' ? (r as HtmlEscapedString).toString() : r) - if (typeof r === 'object') { - callbacks.push(...((r as HtmlEscapedString).callbacks || [])) - } - - if ((r as HtmlEscapedString).isEscaped ?? isEscaped) { - str += r - } else { - const buf = [str] - escapeToBuffer(r, buf) - str = buf[0] - } - } - - return raw(str, callbacks) -} - -export const escapeToBuffer = (str: string, buffer: StringBuffer): void => { - const match = str.search(escapeRe) - if (match === -1) { - buffer[0] += str - return - } - - let escape - let index - let lastIndex = 0 - - for (index = match; index < str.length; index++) { - switch (str.charCodeAt(index)) { - case 34: // " - escape = '"' - break - case 39: // ' - escape = ''' - break - case 38: // & - escape = '&' - break - case 60: // < - escape = '<' - break - case 62: // > - escape = '>' - break - default: - continue - } - - buffer[0] += str.substring(lastIndex, index) + escape - lastIndex = index + 1 - } - - buffer[0] += str.substring(lastIndex, index) -} - -export const resolveCallback = async ( - str: string | HtmlEscapedString, - phase: (typeof HtmlEscapedCallbackPhase)[keyof typeof HtmlEscapedCallbackPhase], - preserveCallbacks: boolean, - context: object, - buffer?: [string] -): Promise => { - const callbacks = (str as HtmlEscapedString).callbacks as HtmlEscapedCallback[] - if (!callbacks?.length) { - return Promise.resolve(str) - } - if (buffer) { - buffer[0] += str - } else { - buffer = [str] - } - - const resStr = Promise.all(callbacks.map((c) => c({ phase, buffer, context }))).then((res) => - Promise.all( - res - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .filter(Boolean as any) - .map((str) => resolveCallback(str, phase, false, context, buffer)) - ).then(() => (buffer as [string])[0]) - ) - - if (preserveCallbacks) { - return raw(await resStr, callbacks) - } else { - return resStr - } -} diff --git a/deno_dist/utils/http-status.ts b/deno_dist/utils/http-status.ts deleted file mode 100644 index b4d594c1f9..0000000000 --- a/deno_dist/utils/http-status.ts +++ /dev/null @@ -1,57 +0,0 @@ -export type InfoStatusCode = 100 | 101 | 102 | 103 -export type SuccessStatusCode = 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226 -export type DeprecatedStatusCode = 305 | 306 -export type RedirectStatusCode = 300 | 301 | 302 | 303 | 304 | DeprecatedStatusCode | 307 | 308 -export type ClientErrorStatusCode = - | 400 - | 401 - | 402 - | 403 - | 404 - | 405 - | 406 - | 407 - | 408 - | 409 - | 410 - | 411 - | 412 - | 413 - | 414 - | 415 - | 416 - | 417 - | 418 - | 421 - | 422 - | 423 - | 424 - | 425 - | 426 - | 428 - | 429 - | 431 - | 451 -export type ServerErrorStatusCode = 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 -/** - * `UnOfficalStatusCode` can be used to specify an informal status code. - * @example - * - * ```ts - * app.get('/', (c) => { - * return c.text("hono is cool", 666 as UnOfficalStatusCode) - * }) - * ``` - */ -export type UnOfficalStatusCode = -1 - -/** - * If you want to use an unofficial status, use `UnOfficalStatusCode`. - */ -export type StatusCode = - | InfoStatusCode - | SuccessStatusCode - | RedirectStatusCode - | ClientErrorStatusCode - | ServerErrorStatusCode - | UnOfficalStatusCode diff --git a/deno_dist/utils/jwt/index.ts b/deno_dist/utils/jwt/index.ts deleted file mode 100644 index 53d2eda744..0000000000 --- a/deno_dist/utils/jwt/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { sign, verify, decode } from './jwt.ts' -export const Jwt = { sign, verify, decode } diff --git a/deno_dist/utils/jwt/jwa.ts b/deno_dist/utils/jwt/jwa.ts deleted file mode 100644 index 8512b811c4..0000000000 --- a/deno_dist/utils/jwt/jwa.ts +++ /dev/null @@ -1,20 +0,0 @@ -// JSON Web Algorithms (JWA) -// https://datatracker.ietf.org/doc/html/rfc7518 - -export enum AlgorithmTypes { - HS256 = 'HS256', - HS384 = 'HS384', - HS512 = 'HS512', - RS256 = 'RS256', - RS384 = 'RS384', - RS512 = 'RS512', - PS256 = 'PS256', - PS384 = 'PS384', - PS512 = 'PS512', - ES256 = 'ES256', - ES384 = 'ES384', - ES512 = 'ES512', - EdDSA = 'EdDSA', -} - -export type SignatureAlgorithm = keyof typeof AlgorithmTypes diff --git a/deno_dist/utils/jwt/jws.ts b/deno_dist/utils/jwt/jws.ts deleted file mode 100644 index 9578c68e20..0000000000 --- a/deno_dist/utils/jwt/jws.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { getRuntimeKey } from '../../helper/adapter/index.ts' -import { decodeBase64 } from '../encode.ts' -import type { SignatureAlgorithm } from './jwa.ts' -import { JwtAlgorithmNotImplemented } from './types.ts' -import { CryptoKeyUsage } from './types.ts' -import { utf8Encoder } from './utf8.ts' - -// JSON Web Signature (JWS) -// https://datatracker.ietf.org/doc/html/rfc7515 - -type KeyImporterAlgorithm = Parameters[2] -type KeyAlgorithm = - | AlgorithmIdentifier - | RsaHashedImportParams - | (RsaPssParams & RsaHashedImportParams) - | (EcdsaParams & EcKeyImportParams) - | HmacImportParams - -export type SignatureKey = string | JsonWebKey | CryptoKey - -export async function signing( - privateKey: SignatureKey, - alg: SignatureAlgorithm, - data: BufferSource -): Promise { - const algorithm = getKeyAlgorithm(alg) - const cryptoKey = await importPrivateKey(privateKey, algorithm) - return await crypto.subtle.sign(algorithm, cryptoKey, data) -} - -export async function verifying( - publicKey: SignatureKey, - alg: SignatureAlgorithm, - signature: BufferSource, - data: BufferSource -): Promise { - const algorithm = getKeyAlgorithm(alg) - const cryptoKey = await importPublicKey(publicKey, algorithm) - return await crypto.subtle.verify(algorithm, cryptoKey, signature, data) -} - -function pemToBinary(pem: string): Uint8Array { - return decodeBase64(pem.replace(/-+(BEGIN|END).*/g, '').replace(/\s/g, '')) -} - -async function importPrivateKey(key: SignatureKey, alg: KeyImporterAlgorithm): Promise { - if (!crypto.subtle || !crypto.subtle.importKey) { - throw new Error('`crypto.subtle.importKey` is undefined. JWT auth middleware requires it.') - } - if (isCryptoKey(key)) { - if (key.type !== 'private') { - throw new Error(`unexpected non private key: CryptoKey.type is ${key.type}`) - } - return key - } - const usages = [CryptoKeyUsage.Sign] - if (typeof key === 'object') { - // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#json_web_key_import - return await crypto.subtle.importKey('jwk', key, alg, false, usages) - } - if (key.includes('PRIVATE')) { - // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#pkcs_8_import - return await crypto.subtle.importKey('pkcs8', pemToBinary(key), alg, false, usages) - } - return await crypto.subtle.importKey('raw', utf8Encoder.encode(key), alg, false, usages) -} - -async function importPublicKey(key: SignatureKey, alg: KeyImporterAlgorithm): Promise { - if (!crypto.subtle || !crypto.subtle.importKey) { - throw new Error('`crypto.subtle.importKey` is undefined. JWT auth middleware requires it.') - } - if (isCryptoKey(key)) { - if (key.type === 'public' || key.type === 'secret') { - return key - } - key = await exportPublicJwkFrom(key) - } - if (typeof key === 'string' && key.includes('PRIVATE')) { - // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#pkcs_8_import - const privateKey = await crypto.subtle.importKey('pkcs8', pemToBinary(key), alg, true, [ - CryptoKeyUsage.Sign, - ]) - key = await exportPublicJwkFrom(privateKey) - } - const usages = [CryptoKeyUsage.Verify] - if (typeof key === 'object') { - // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#json_web_key_import - return await crypto.subtle.importKey('jwk', key, alg, false, usages) - } - if (key.includes('PUBLIC')) { - // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#subjectpublickeyinfo_import - return await crypto.subtle.importKey('spki', pemToBinary(key), alg, false, usages) - } - return await crypto.subtle.importKey('raw', utf8Encoder.encode(key), alg, false, usages) -} - -// https://datatracker.ietf.org/doc/html/rfc7517 -async function exportPublicJwkFrom(privateKey: CryptoKey): Promise { - if (privateKey.type !== 'private') { - throw new Error(`unexpected key type: ${privateKey.type}`) - } - if (!privateKey.extractable) { - throw new Error('unexpected private key is unextractable') - } - const jwk = await crypto.subtle.exportKey('jwk', privateKey) - const { kty } = jwk // common - const { alg, e, n } = jwk // rsa - const { crv, x, y } = jwk // elliptic-curve - return { kty, alg, e, n, crv, x, y, key_ops: [CryptoKeyUsage.Verify] } -} - -function getKeyAlgorithm(name: SignatureAlgorithm): KeyAlgorithm { - switch (name) { - case 'HS256': - return { - name: 'HMAC', - hash: { - name: 'SHA-256', - }, - } satisfies HmacImportParams - case 'HS384': - return { - name: 'HMAC', - hash: { - name: 'SHA-384', - }, - } satisfies HmacImportParams - case 'HS512': - return { - name: 'HMAC', - hash: { - name: 'SHA-512', - }, - } satisfies HmacImportParams - case 'RS256': - return { - name: 'RSASSA-PKCS1-v1_5', - hash: { - name: 'SHA-256', - }, - } satisfies RsaHashedImportParams - case 'RS384': - return { - name: 'RSASSA-PKCS1-v1_5', - hash: { - name: 'SHA-384', - }, - } satisfies RsaHashedImportParams - case 'RS512': - return { - name: 'RSASSA-PKCS1-v1_5', - hash: { - name: 'SHA-512', - }, - } satisfies RsaHashedImportParams - case 'PS256': - return { - name: 'RSA-PSS', - hash: { - name: 'SHA-256', - }, - saltLength: 32, // 256 >> 3 - } satisfies RsaPssParams & RsaHashedImportParams - case 'PS384': - return { - name: 'RSA-PSS', - hash: { - name: 'SHA-384', - }, - saltLength: 48, // 384 >> 3 - } satisfies RsaPssParams & RsaHashedImportParams - case 'PS512': - return { - name: 'RSA-PSS', - hash: { - name: 'SHA-512', - }, - saltLength: 64, // 512 >> 3, - } satisfies RsaPssParams & RsaHashedImportParams - case 'ES256': - return { - name: 'ECDSA', - hash: { - name: 'SHA-256', - }, - namedCurve: 'P-256', - } satisfies EcdsaParams & EcKeyImportParams - case 'ES384': - return { - name: 'ECDSA', - hash: { - name: 'SHA-384', - }, - namedCurve: 'P-384', - } satisfies EcdsaParams & EcKeyImportParams - case 'ES512': - return { - name: 'ECDSA', - hash: { - name: 'SHA-512', - }, - namedCurve: 'P-521', - } satisfies EcdsaParams & EcKeyImportParams - case 'EdDSA': - // Currently, supported only Safari and Deno, Node.js. - // See: https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/verify - return { - name: 'Ed25519', - namedCurve: 'Ed25519', - } - default: - throw new JwtAlgorithmNotImplemented(name) - } -} - -function isCryptoKey(key: SignatureKey): key is CryptoKey { - const runtime = getRuntimeKey() - // @ts-expect-error CryptoKey hasn't exported to global in node v18 - if (runtime === 'node' && !!crypto.webcrypto) { - // @ts-expect-error CryptoKey hasn't exported to global in node v18 - return key instanceof crypto.webcrypto.CryptoKey - } - return key instanceof CryptoKey -} diff --git a/deno_dist/utils/jwt/jwt.ts b/deno_dist/utils/jwt/jwt.ts deleted file mode 100644 index b6beef8a8b..0000000000 --- a/deno_dist/utils/jwt/jwt.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { encodeBase64Url, decodeBase64Url } from '../../utils/encode.ts' -import type { SignatureAlgorithm } from './jwa.ts' -import { AlgorithmTypes } from './jwa.ts' -import type { SignatureKey } from './jws.ts' -import { signing, verifying } from './jws.ts' -import { JwtHeaderInvalid, type JWTPayload } from './types.ts' -import { - JwtTokenInvalid, - JwtTokenNotBefore, - JwtTokenExpired, - JwtTokenSignatureMismatched, - JwtTokenIssuedAt, -} from './types.ts' -import { utf8Decoder, utf8Encoder } from './utf8.ts' - -const encodeJwtPart = (part: unknown): string => - encodeBase64Url(utf8Encoder.encode(JSON.stringify(part))).replace(/=/g, '') -const encodeSignaturePart = (buf: ArrayBufferLike): string => encodeBase64Url(buf).replace(/=/g, '') - -const decodeJwtPart = (part: string): unknown => - JSON.parse(utf8Decoder.decode(decodeBase64Url(part))) - -export interface TokenHeader { - alg: SignatureAlgorithm - typ?: 'JWT' -} - -// eslint-disable-next-line -export function isTokenHeader(obj: any): obj is TokenHeader { - return ( - typeof obj === 'object' && - obj !== null && - 'alg' in obj && - Object.values(AlgorithmTypes).includes(obj.alg) && - (!('typ' in obj) || obj.typ === 'JWT') - ) -} - -export const sign = async ( - payload: JWTPayload, - privateKey: SignatureKey, - alg: SignatureAlgorithm = 'HS256' -): Promise => { - const encodedPayload = encodeJwtPart(payload) - const encodedHeader = encodeJwtPart({ alg, typ: 'JWT' } satisfies TokenHeader) - - const partialToken = `${encodedHeader}.${encodedPayload}` - - const signaturePart = await signing(privateKey, alg, utf8Encoder.encode(partialToken)) - const signature = encodeSignaturePart(signaturePart) - - return `${partialToken}.${signature}` -} - -export const verify = async ( - token: string, - publicKey: SignatureKey, - alg: SignatureAlgorithm = 'HS256' - // eslint-disable-next-line @typescript-eslint/no-explicit-any -): Promise => { - const tokenParts = token.split('.') - if (tokenParts.length !== 3) { - throw new JwtTokenInvalid(token) - } - - const { header, payload } = decode(token) - if (!isTokenHeader(header)) { - throw new JwtHeaderInvalid(header) - } - const now = Math.floor(Date.now() / 1000) - if (payload.nbf && payload.nbf > now) { - throw new JwtTokenNotBefore(token) - } - if (payload.exp && payload.exp <= now) { - throw new JwtTokenExpired(token) - } - if (payload.iat && now < payload.iat) { - throw new JwtTokenIssuedAt(now, payload.iat) - } - - const headerPayload = token.substring(0, token.lastIndexOf('.')) - const verified = await verifying( - publicKey, - alg, - decodeBase64Url(tokenParts[2]), - utf8Encoder.encode(headerPayload) - ) - if (!verified) { - throw new JwtTokenSignatureMismatched(token) - } - - return payload -} - -// eslint-disable-next-line -export const decode = (token: string): { header: any; payload: any } => { - try { - const [h, p] = token.split('.') - const header = decodeJwtPart(h) - const payload = decodeJwtPart(p) - return { - header, - payload, - } - } catch (e) { - throw new JwtTokenInvalid(token) - } -} diff --git a/deno_dist/utils/jwt/types.ts b/deno_dist/utils/jwt/types.ts deleted file mode 100644 index 738afd5728..0000000000 --- a/deno_dist/utils/jwt/types.ts +++ /dev/null @@ -1,80 +0,0 @@ -export class JwtAlgorithmNotImplemented extends Error { - constructor(alg: string) { - super(`${alg} is not an implemented algorithm`) - this.name = 'JwtAlgorithmNotImplemented' - } -} - -export class JwtTokenInvalid extends Error { - constructor(token: string) { - super(`invalid JWT token: ${token}`) - this.name = 'JwtTokenInvalid' - } -} - -export class JwtTokenNotBefore extends Error { - constructor(token: string) { - super(`token (${token}) is being used before it's valid`) - this.name = 'JwtTokenNotBefore' - } -} - -export class JwtTokenExpired extends Error { - constructor(token: string) { - super(`token (${token}) expired`) - this.name = 'JwtTokenExpired' - } -} - -export class JwtTokenIssuedAt extends Error { - constructor(currentTimestamp: number, iat: number) { - super(`Incorrect "iat" claim must be a older than "${currentTimestamp}" (iat: "${iat}")`) - this.name = 'JwtTokenIssuedAt' - } -} - -export class JwtHeaderInvalid extends Error { - constructor(header: object) { - super(`jwt header is invalid: ${JSON.stringify(header)}`) - this.name = 'JwtHeaderInvalid' - } -} - -export class JwtTokenSignatureMismatched extends Error { - constructor(token: string) { - super(`token(${token}) signature mismatched`) - this.name = 'JwtTokenSignatureMismatched' - } -} - -export enum CryptoKeyUsage { - Encrypt = 'encrypt', - Decrypt = 'decrypt', - Sign = 'sign', - Verify = 'verify', - DeriveKey = 'deriveKey', - DeriveBits = 'deriveBits', - WrapKey = 'wrapKey', - UnwrapKey = 'unwrapKey', -} - -/** - * JWT Payload - */ -export type JWTPayload = - | (unknown & {}) - | { - [key: string]: unknown - /** - * The token is checked to ensure it has not expired. - */ - exp?: number - /** - * The token is checked to ensure it is not being used before a specified time. - */ - nbf?: number - /** - * The token is checked to ensure it is not issued in the future. - */ - iat?: number - } diff --git a/deno_dist/utils/jwt/utf8.ts b/deno_dist/utils/jwt/utf8.ts deleted file mode 100644 index 01d1972a1f..0000000000 --- a/deno_dist/utils/jwt/utf8.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const utf8Encoder: TextEncoder = new TextEncoder() -export const utf8Decoder: TextDecoder = new TextDecoder() diff --git a/deno_dist/utils/mime.ts b/deno_dist/utils/mime.ts deleted file mode 100644 index f0322abc67..0000000000 --- a/deno_dist/utils/mime.ts +++ /dev/null @@ -1,79 +0,0 @@ -export const getMimeType = (filename: string, mimes = baseMimes): string | undefined => { - const regexp = /\.([a-zA-Z0-9]+?)$/ - const match = filename.match(regexp) - if (!match) { - return - } - let mimeType = mimes[match[1]] - if ((mimeType && mimeType.startsWith('text')) || mimeType === 'application/json') { - mimeType += '; charset=utf-8' - } - return mimeType -} - -export const getExtension = (mimeType: string): string | undefined => { - for (const ext in baseMimes) { - if (baseMimes[ext] === mimeType) { - return ext - } - } -} - -export { baseMimes as mimes } -const baseMimes: Record = { - aac: 'audio/aac', - avi: 'video/x-msvideo', - avif: 'image/avif', - av1: 'video/av1', - bin: 'application/octet-stream', - bmp: 'image/bmp', - css: 'text/css', - csv: 'text/csv', - eot: 'application/vnd.ms-fontobject', - epub: 'application/epub+zip', - gif: 'image/gif', - gz: 'application/gzip', - htm: 'text/html', - html: 'text/html', - ico: 'image/x-icon', - ics: 'text/calendar', - jpeg: 'image/jpeg', - jpg: 'image/jpeg', - js: 'text/javascript', - json: 'application/json', - jsonld: 'application/ld+json', - map: 'application/json', - mid: 'audio/x-midi', - midi: 'audio/x-midi', - mjs: 'text/javascript', - mp3: 'audio/mpeg', - mp4: 'video/mp4', - mpeg: 'video/mpeg', - oga: 'audio/ogg', - ogv: 'video/ogg', - ogx: 'application/ogg', - opus: 'audio/opus', - otf: 'font/otf', - pdf: 'application/pdf', - png: 'image/png', - rtf: 'application/rtf', - svg: 'image/svg+xml', - tif: 'image/tiff', - tiff: 'image/tiff', - ts: 'video/mp2t', - ttf: 'font/ttf', - txt: 'text/plain', - wasm: 'application/wasm', - webm: 'video/webm', - weba: 'audio/webm', - webp: 'image/webp', - woff: 'font/woff', - woff2: 'font/woff2', - xhtml: 'application/xhtml+xml', - xml: 'application/xml', - zip: 'application/zip', - '3gp': 'video/3gpp', - '3g2': 'video/3gpp2', - gltf: 'model/gltf+json', - glb: 'model/gltf-binary', -} diff --git a/deno_dist/utils/stream.ts b/deno_dist/utils/stream.ts deleted file mode 100644 index b129caf913..0000000000 --- a/deno_dist/utils/stream.ts +++ /dev/null @@ -1,71 +0,0 @@ -export class StreamingApi { - private writer: WritableStreamDefaultWriter - private encoder: TextEncoder - private writable: WritableStream - private abortSubscribers: (() => void | Promise)[] = [] - responseReadable: ReadableStream - - constructor(writable: WritableStream, _readable: ReadableStream) { - this.writable = writable - this.writer = writable.getWriter() - this.encoder = new TextEncoder() - - const reader = _readable.getReader() - - // in case the user disconnects, let the reader know to cancel - // this in-turn results in responseReadable being closed - // and writeSSE method no longer blocks indefinitely - this.abortSubscribers.push(async () => { - await reader.cancel() - }) - - this.responseReadable = new ReadableStream({ - async pull(controller) { - const { done, value } = await reader.read() - done ? controller.close() : controller.enqueue(value) - }, - cancel: () => { - this.abortSubscribers.forEach((subscriber) => subscriber()) - }, - }) - } - - async write(input: Uint8Array | string): Promise { - try { - if (typeof input === 'string') { - input = this.encoder.encode(input) - } - await this.writer.write(input) - } catch (e) { - // Do nothing. If you want to handle errors, create a stream by yourself. - } - return this - } - - async writeln(input: string): Promise { - await this.write(input + '\n') - return this - } - - sleep(ms: number): Promise { - return new Promise((res) => setTimeout(res, ms)) - } - - async close() { - try { - await this.writer.close() - } catch (e) { - // Do nothing. If you want to handle errors, create a stream by yourself. - } - } - - async pipe(body: ReadableStream) { - this.writer.releaseLock() - await body.pipeTo(this.writable, { preventClose: true }) - this.writer = this.writable.getWriter() - } - - onAbort(listener: () => void | Promise) { - this.abortSubscribers.push(listener) - } -} diff --git a/deno_dist/utils/types.ts b/deno_dist/utils/types.ts deleted file mode 100644 index cedaa1bc56..0000000000 --- a/deno_dist/utils/types.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -export type Expect = T -export type Equal = (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 - ? true - : false -export type NotEqual = true extends Equal ? false : true - -export type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( - k: infer I -) => void - ? I - : never - -export type RemoveBlankRecord = T extends Record - ? K extends string - ? T - : never - : never - -export type IfAnyThenEmptyObject = 0 extends 1 & T ? {} : T - -export type JSONPrimitive = string | boolean | number | null | undefined -export type JSONArray = (JSONPrimitive | JSONObject | JSONArray)[] -export type JSONObject = { [key: string]: JSONPrimitive | JSONArray | JSONObject | object } -export type JSONValue = JSONObject | JSONArray | JSONPrimitive -// Non-JSON values such as `Date` implement `.toJSON()`, so they can be transformed to a value assignable to `JSONObject`: -export type JSONParsed = T extends { toJSON(): infer J } - ? (() => J) extends () => JSONObject - ? J - : JSONParsed - : T extends JSONPrimitive - ? T - : T extends Array - ? Array> - : T extends object - ? { [K in keyof T]: JSONParsed } - : never - -// from sindresorhus/type-fest -export type Simplify = { [KeyType in keyof T]: T[KeyType] } & {} - -export type InterfaceToType = T extends Function ? T : { [K in keyof T]: InterfaceToType } - -export type RequiredKeysOf = Exclude< - { - [Key in keyof BaseType]: BaseType extends Record ? Key : never - }[keyof BaseType], - undefined -> - -export type HasRequiredKeys = RequiredKeysOf extends never - ? false - : true - -export type IsAny = boolean extends (T extends never ? true : false) ? true : false - -export type Prettify = { - [K in keyof T]: T[K] -} & {} diff --git a/deno_dist/utils/url.ts b/deno_dist/utils/url.ts deleted file mode 100644 index b37a5539af..0000000000 --- a/deno_dist/utils/url.ts +++ /dev/null @@ -1,268 +0,0 @@ -export type Pattern = readonly [string, string, RegExp | true] | '*' - -export const splitPath = (path: string): string[] => { - const paths = path.split('/') - if (paths[0] === '') { - paths.shift() - } - return paths -} - -export const splitRoutingPath = (routePath: string): string[] => { - const { groups, path } = extractGroupsFromPath(routePath) - - const paths = splitPath(path) - return replaceGroupMarks(paths, groups) -} - -const extractGroupsFromPath = (path: string): { groups: [string, string][]; path: string } => { - const groups: [string, string][] = [] - - path = path.replace(/\{[^}]+\}/g, (match, index) => { - const mark = `@${index}` - groups.push([mark, match]) - return mark - }) - - return { groups, path } -} - -const replaceGroupMarks = (paths: string[], groups: [string, string][]): string[] => { - for (let i = groups.length - 1; i >= 0; i--) { - const [mark] = groups[i] - - for (let j = paths.length - 1; j >= 0; j--) { - if (paths[j].includes(mark)) { - paths[j] = paths[j].replace(mark, groups[i][1]) - break - } - } - } - - return paths -} - -const patternCache: { [key: string]: Pattern } = {} -export const getPattern = (label: string): Pattern | null => { - // * => wildcard - // :id{[0-9]+} => ([0-9]+) - // :id => (.+) - //const name = '' - - if (label === '*') { - return '*' - } - - const match = label.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/) - if (match) { - if (!patternCache[label]) { - if (match[2]) { - patternCache[label] = [label, match[1], new RegExp('^' + match[2] + '$')] - } else { - patternCache[label] = [label, match[1], true] - } - } - - return patternCache[label] - } - - return null -} - -export const getPath = (request: Request): string => { - // Optimized: indexOf() + slice() is faster than RegExp - const url = request.url - const queryIndex = url.indexOf('?', 8) - return url.slice(url.indexOf('/', 8), queryIndex === -1 ? undefined : queryIndex) -} - -export const getQueryStrings = (url: string): string => { - const queryIndex = url.indexOf('?', 8) - return queryIndex === -1 ? '' : '?' + url.slice(queryIndex + 1) -} - -export const getPathNoStrict = (request: Request): string => { - const result = getPath(request) - - // if strict routing is false => `/hello/hey/` and `/hello/hey` are treated the same - return result.length > 1 && result[result.length - 1] === '/' ? result.slice(0, -1) : result -} - -export const mergePath = (...paths: string[]): string => { - let p: string = '' - let endsWithSlash = false - - for (let path of paths) { - /* ['/hey/','/say'] => ['/hey', '/say'] */ - if (p[p.length - 1] === '/') { - p = p.slice(0, -1) - endsWithSlash = true - } - - /* ['/hey','say'] => ['/hey', '/say'] */ - if (path[0] !== '/') { - path = `/${path}` - } - - /* ['/hey/', '/'] => `/hey/` */ - if (path === '/' && endsWithSlash) { - p = `${p}/` - } else if (path !== '/') { - p = `${p}${path}` - } - - /* ['/', '/'] => `/` */ - if (path === '/' && p === '') { - p = '/' - } - } - - return p -} - -export const checkOptionalParameter = (path: string): string[] | null => { - /* - If path is `/api/animals/:type?` it will return: - [`/api/animals`, `/api/animals/:type`] - in other cases it will return null - */ - - if (!path.match(/\:.+\?$/)) { - return null - } - - const segments = path.split('/') - const results: string[] = [] - let basePath = '' - - segments.forEach((segment) => { - if (segment !== '' && !/\:/.test(segment)) { - basePath += '/' + segment - } else if (/\:/.test(segment)) { - if (/\?/.test(segment)) { - if (results.length === 0 && basePath === '') { - results.push('/') - } else { - results.push(basePath) - } - const optionalSegment = segment.replace('?', '') - basePath += '/' + optionalSegment - results.push(basePath) - } else { - basePath += '/' + segment - } - } - }) - - return results.filter((v, i, a) => a.indexOf(v) === i) -} - -// Optimized -const _decodeURI = (value: string) => { - if (!/[%+]/.test(value)) { - return value - } - if (value.indexOf('+') !== -1) { - value = value.replace(/\+/g, ' ') - } - return /%/.test(value) ? decodeURIComponent_(value) : value -} - -const _getQueryParam = ( - url: string, - key?: string, - multiple?: boolean -): string | undefined | Record | string[] | Record => { - let encoded - - if (!multiple && key && !/[%+]/.test(key)) { - // optimized for unencoded key - - let keyIndex = url.indexOf(`?${key}`, 8) - if (keyIndex === -1) { - keyIndex = url.indexOf(`&${key}`, 8) - } - while (keyIndex !== -1) { - const trailingKeyCode = url.charCodeAt(keyIndex + key.length + 1) - if (trailingKeyCode === 61) { - const valueIndex = keyIndex + key.length + 2 - const endIndex = url.indexOf('&', valueIndex) - return _decodeURI(url.slice(valueIndex, endIndex === -1 ? undefined : endIndex)) - } else if (trailingKeyCode == 38 || isNaN(trailingKeyCode)) { - return '' - } - keyIndex = url.indexOf(`&${key}`, keyIndex + 1) - } - - encoded = /[%+]/.test(url) - if (!encoded) { - return undefined - } - // fallback to default routine - } - - const results: Record | Record = {} - encoded ??= /[%+]/.test(url) - - let keyIndex = url.indexOf('?', 8) - while (keyIndex !== -1) { - const nextKeyIndex = url.indexOf('&', keyIndex + 1) - let valueIndex = url.indexOf('=', keyIndex) - if (valueIndex > nextKeyIndex && nextKeyIndex !== -1) { - valueIndex = -1 - } - let name = url.slice( - keyIndex + 1, - valueIndex === -1 ? (nextKeyIndex === -1 ? undefined : nextKeyIndex) : valueIndex - ) - if (encoded) { - name = _decodeURI(name) - } - - keyIndex = nextKeyIndex - - if (name === '') { - continue - } - - let value - if (valueIndex === -1) { - value = '' - } else { - value = url.slice(valueIndex + 1, nextKeyIndex === -1 ? undefined : nextKeyIndex) - if (encoded) { - value = _decodeURI(value) - } - } - - if (multiple) { - if (!(results[name] && Array.isArray(results[name]))) { - results[name] = [] - } - ;(results[name] as string[]).push(value) - } else { - results[name] ??= value - } - } - - return key ? results[key] : results -} - -export const getQueryParam: ( - url: string, - key?: string -) => string | undefined | Record = _getQueryParam as ( - url: string, - key?: string -) => string | undefined | Record - -export const getQueryParams = ( - url: string, - key?: string -): string[] | undefined | Record => { - return _getQueryParam(url, key, true) as string[] | undefined | Record -} - -// `decodeURIComponent` is a long name. -// By making it a function, we can use it commonly when minified, reducing the amount of code. -export const decodeURIComponent_ = decodeURIComponent diff --git a/deno_dist/validator/index.ts b/deno_dist/validator/index.ts deleted file mode 100644 index 0e1bcd1d14..0000000000 --- a/deno_dist/validator/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { validator } from './validator.ts' -export type { ValidationFunction } from './validator.ts' diff --git a/deno_dist/validator/validator.ts b/deno_dist/validator/validator.ts deleted file mode 100644 index 20571fd95a..0000000000 --- a/deno_dist/validator/validator.ts +++ /dev/null @@ -1,143 +0,0 @@ -import type { Context } from '../context.ts' -import { getCookie } from '../helper/cookie/index.ts' -import { HTTPException } from '../http-exception.ts' -import type { Env, ValidationTargets, MiddlewareHandler, TypedResponse } from '../types.ts' -import type { BodyData } from '../utils/body.ts' -import { bufferToFormData } from '../utils/buffer.ts' - -type ValidationTargetKeysWithBody = 'form' | 'json' -type ValidationTargetByMethod = M extends 'get' | 'head' // GET and HEAD request must not have a body content. - ? Exclude - : keyof ValidationTargets - -export type ValidationFunction< - InputType, - OutputType, - E extends Env = {}, - P extends string = string -> = ( - value: InputType, - c: Context -) => OutputType | Response | Promise | Promise - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type ExcludeResponseType = T extends Response & TypedResponse ? never : T - -export const validator = < - InputType, - P extends string, - M extends string, - U extends ValidationTargetByMethod, - OutputType = ValidationTargets[U], - OutputTypeExcludeResponseType = ExcludeResponseType, - P2 extends string = P, - V extends { - in: { - [K in U]: K extends 'json' - ? unknown extends InputType - ? OutputTypeExcludeResponseType - : InputType - : { [K2 in keyof OutputTypeExcludeResponseType]: ValidationTargets[K][K2] } - } - out: { [K in U]: OutputTypeExcludeResponseType } - } = { - in: { - [K in U]: K extends 'json' - ? unknown extends InputType - ? OutputTypeExcludeResponseType - : InputType - : { [K2 in keyof OutputTypeExcludeResponseType]: ValidationTargets[K][K2] } - } - out: { [K in U]: OutputTypeExcludeResponseType } - }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - E extends Env = any ->( - target: U, - validationFunc: ValidationFunction< - unknown extends InputType ? ValidationTargets[U] : InputType, - OutputType, - E, - P2 - > -): MiddlewareHandler => { - return async (c, next) => { - let value = {} - const contentType = c.req.header('Content-Type') - - switch (target) { - case 'json': - if (!contentType || !/^application\/([a-z-]+\+)?json/.test(contentType)) { - const message = `Invalid HTTP header: Content-Type=${contentType}` - throw new HTTPException(400, { message }) - } - try { - value = await c.req.json() - } catch { - const message = 'Malformed JSON in request body' - throw new HTTPException(400, { message }) - } - break - case 'form': { - if (!contentType) { - break - } - - if (c.req.bodyCache.formData) { - value = await c.req.bodyCache.formData - break - } - - try { - const arrayBuffer = await c.req.arrayBuffer() - const formData = await bufferToFormData(arrayBuffer, contentType) - const form: BodyData = {} - formData.forEach((value, key) => { - if (key.endsWith('[]')) { - if (form[key] === undefined) { - form[key] = [value] - } else if (Array.isArray(form[key])) { - ;(form[key] as unknown[]).push(value) - } - } else { - form[key] = value - } - }) - value = form - c.req.bodyCache.formData = formData - } catch (e) { - let message = 'Malformed FormData request.' - message += e instanceof Error ? ` ${e.message}` : ` ${String(e)}` - throw new HTTPException(400, { message }) - } - break - } - case 'query': - value = Object.fromEntries( - Object.entries(c.req.queries()).map(([k, v]) => { - return v.length === 1 ? [k, v[0]] : [k, v] - }) - ) - break - case 'param': - value = c.req.param() as Record - break - case 'header': - value = c.req.header() - break - case 'cookie': - value = getCookie(c) - break - } - - const res = await validationFunc(value as never, c as never) - - if (res instanceof Response) { - return res - } - - c.req.addValidatedData(target, res as never) - - await next() - } -} diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index b50c6e0b99..fdd3c33d65 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -36,7 +36,7 @@ yarn install --frozen-lockfile ## PRs -Please ensure your PR passes tests with `bun run test` or `yarn test`. Also please ensure the Deno code is generated with `yarn denoify`. +Please ensure your PR passes tests with `bun run test` or `yarn test`. ## Third-party middleware @@ -59,4 +59,4 @@ If you want to do it, create the issue about your middleware. git clone git@github.com:honojs/hono.git && cd hono/.devcontainer && yarn install --frozen-lockfile docker compose up -d --build docker compose exec hono bash -``` \ No newline at end of file +``` diff --git a/package.json b/package.json index 7c8de46710..5b091a6377 100644 --- a/package.json +++ b/package.json @@ -24,13 +24,12 @@ "lint:fix": "eslint --ext js,ts,tsx src runtime_tests --fix", "format": "prettier --check --cache \"src/**/*.{js,ts,tsx}\" \"runtime_tests/**/*.{js,ts,tsx}\"", "format:fix": "prettier --write --cache --cache-strategy metadata \"src/**/*.{js,ts,tsx}\" \"runtime_tests/**/*.{js,ts,tsx}\"", - "denoify": "rimraf deno_dist && denoify && rimraf \"deno_dist/**/*.test.{ts,tsx}\"", "copy:package.cjs.json": "cp ./package.cjs.json ./dist/cjs/package.json && cp ./package.cjs.json ./dist/types/package.json ", "build": "rimraf dist && tsx ./build.ts && bun run copy:package.cjs.json", "postbuild": "publint", "watch": "rimraf dist && tsx ./build.ts --watch && bun run copy:package.cjs.json", "coverage": "vitest --run --coverage", - "prerelease": "bun denoify && bun test:deno && bun run build", + "prerelease": "bun test:deno && bun run build", "release": "np" }, "exports": { @@ -557,7 +556,6 @@ "@vitest/coverage-v8": "^1.1.0", "arg": "^5.0.2", "crypto-js": "^4.1.1", - "denoify": "^1.6.6", "esbuild": "^0.15.12", "eslint": "^8.55.0", "glob": "7.2.3", @@ -578,4 +576,4 @@ "engines": { "node": ">=16.0.0" } -} +} \ No newline at end of file diff --git a/runtime_tests/deno-jsx/deno.precompile.json b/runtime_tests/deno-jsx/deno.precompile.json index 0ce541ca92..31f0aec649 100644 --- a/runtime_tests/deno-jsx/deno.precompile.json +++ b/runtime_tests/deno-jsx/deno.precompile.json @@ -2,10 +2,16 @@ "compilerOptions": { "jsx": "precompile", "jsxImportSource": "hono/jsx", - "lib": ["deno.ns", "dom"] + "lib": [ + "deno.ns", + "dom" + ] }, + "unstable": [ + "sloppy-imports" + ], "imports": { - "hono/jsx/jsx-runtime": "../../deno_dist/jsx/jsx-runtime.ts", - "../../deno_dist/jsx/jsx-runtime": "../../deno_dist/jsx/jsx-runtime.ts" + "hono/jsx/jsx-runtime": "../../src/jsx/jsx-runtime.ts", + "../../deno_dist/jsx/jsx-runtime": "../../src/jsx/jsx-runtime.ts" } -} +} \ No newline at end of file diff --git a/runtime_tests/deno-jsx/deno.react-jsx.json b/runtime_tests/deno-jsx/deno.react-jsx.json index 50c4bdf2b7..0e1fd1d356 100644 --- a/runtime_tests/deno-jsx/deno.react-jsx.json +++ b/runtime_tests/deno-jsx/deno.react-jsx.json @@ -2,10 +2,16 @@ "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "hono/jsx", - "lib": ["deno.ns", "dom"] + "lib": [ + "deno.ns", + "dom" + ] }, + "unstable": [ + "sloppy-imports" + ], "imports": { - "hono/jsx/jsx-runtime": "../../deno_dist/jsx/jsx-runtime.ts", - "../../deno_dist/jsx/jsx-runtime": "../../deno_dist/jsx/jsx-runtime.ts" + "hono/jsx/jsx-runtime": "../../src/jsx/jsx-runtime.ts", + "../../deno_dist/jsx/jsx-runtime": "../../src/jsx/jsx-runtime.ts" } -} +} \ No newline at end of file diff --git a/runtime_tests/deno-jsx/jsx.test.tsx b/runtime_tests/deno-jsx/jsx.test.tsx index d54c271706..2bd506e4ff 100644 --- a/runtime_tests/deno-jsx/jsx.test.tsx +++ b/runtime_tests/deno-jsx/jsx.test.tsx @@ -1,8 +1,8 @@ -/** @jsxImportSource ../../deno_dist/jsx */ -import { Style, css } from '../../deno_dist/helper/css/index.ts' -import { Suspense, renderToReadableStream } from '../../deno_dist/jsx/streaming.ts' -import type { HtmlEscapedString } from '../../deno_dist/utils/html.ts' -import { resolveCallback, HtmlEscapedCallbackPhase } from '../../deno_dist/utils/html.ts' +/** @jsxImportSource ../../src/jsx */ +import { Style, css } from '../../src/helper/css/index.ts' +import { Suspense, renderToReadableStream } from '../../src/jsx/streaming.ts' +import type { HtmlEscapedString } from '../../src/utils/html.ts' +import { resolveCallback, HtmlEscapedCallbackPhase } from '../../src/utils/html.ts' import { assertEquals } from '../deno/deps.ts' Deno.test('JSX', () => { diff --git a/runtime_tests/deno/deno.json b/runtime_tests/deno/deno.json index 4590d724e2..92e4f77228 100644 --- a/runtime_tests/deno/deno.json +++ b/runtime_tests/deno/deno.json @@ -8,7 +8,10 @@ "dom.iterable" ] }, + "unstable": [ + "sloppy-imports" + ], "imports": { - "hono/jsx/jsx-runtime": "../../deno_dist/jsx/jsx-runtime.ts" + "hono/jsx/jsx-runtime": "../../src/jsx/jsx-runtime.ts" } } \ No newline at end of file diff --git a/runtime_tests/deno/hono.test.ts b/runtime_tests/deno/hono.test.ts index d9f9ffefad..bd12848694 100644 --- a/runtime_tests/deno/hono.test.ts +++ b/runtime_tests/deno/hono.test.ts @@ -1,7 +1,7 @@ -import { Context } from '../../deno_dist/context.ts' -import { env, getRuntimeKey } from '../../deno_dist/helper.ts' -import { Hono } from '../../deno_dist/mod.ts' -import { HonoRequest } from '../../deno_dist/request.ts' +import { Context } from '../../src/context.ts' +import { env, getRuntimeKey } from '../../src/helper.ts' +import { Hono } from '../../src/mod.ts' +import { HonoRequest } from '../../src/request.ts' import { assertEquals } from './deps.ts' // Test just only minimal patterns. diff --git a/runtime_tests/deno/middleware.test.tsx b/runtime_tests/deno/middleware.test.tsx index 52dc4e6a38..10b66bebad 100644 --- a/runtime_tests/deno/middleware.test.tsx +++ b/runtime_tests/deno/middleware.test.tsx @@ -1,5 +1,5 @@ -import { basicAuth, serveStatic, jwt } from '../../deno_dist/middleware.ts' -import { Hono } from '../../deno_dist/mod.ts' +import { basicAuth, serveStatic, jwt } from '../../src/middleware.ts' +import { Hono } from '../../src/mod.ts' import { assertEquals, assertMatch, assertSpyCall, assertSpyCalls, spy } from './deps.ts' // Test just only minimal patterns. diff --git a/runtime_tests/deno/ssg.test.tsx b/runtime_tests/deno/ssg.test.tsx index 3f9cf07df2..b267834d2a 100644 --- a/runtime_tests/deno/ssg.test.tsx +++ b/runtime_tests/deno/ssg.test.tsx @@ -1,5 +1,5 @@ -import { toSSG } from '../../deno_dist/helper.ts' -import { Hono } from '../../deno_dist/mod.ts' +import { toSSG } from '../../src/helper.ts' +import { Hono } from '../../src/mod.ts' import { assertEquals } from '../deno/deps.ts' Deno.test('toSSG function', async () => { diff --git a/src/adapter/aws-lambda/awslambda.d.ts b/src/adapter/aws-lambda/awslambda.d.ts index 6ec758a4aa..0154fb3ab2 100644 --- a/src/adapter/aws-lambda/awslambda.d.ts +++ b/src/adapter/aws-lambda/awslambda.d.ts @@ -1,4 +1,3 @@ -// @denoify-ignore /* eslint-disable @typescript-eslint/no-explicit-any */ import type { LambdaContext, Handler } from './types' diff --git a/src/adapter/aws-lambda/custom-context.ts b/src/adapter/aws-lambda/custom-context.ts index 789184cbeb..b9dd48e264 100644 --- a/src/adapter/aws-lambda/custom-context.ts +++ b/src/adapter/aws-lambda/custom-context.ts @@ -1,5 +1,3 @@ -// @denoify-ignore - interface ClientCert { clientCertPem: string subjectDN: string diff --git a/src/adapter/aws-lambda/handler.ts b/src/adapter/aws-lambda/handler.ts index bfb2e0f029..460a30e338 100644 --- a/src/adapter/aws-lambda/handler.ts +++ b/src/adapter/aws-lambda/handler.ts @@ -1,4 +1,3 @@ -// @denoify-ignore import crypto from 'crypto' import type { Hono } from '../../hono' import type { Env, Schema } from '../../types' diff --git a/src/adapter/aws-lambda/index.ts b/src/adapter/aws-lambda/index.ts index 0c72667144..62c7a2e240 100644 --- a/src/adapter/aws-lambda/index.ts +++ b/src/adapter/aws-lambda/index.ts @@ -1,4 +1,3 @@ -// @denoify-ignore export { handle, streamHandle } from './handler' export type { APIGatewayProxyResult, LambdaEvent } from './handler' export type { diff --git a/src/adapter/aws-lambda/types.ts b/src/adapter/aws-lambda/types.ts index 76fbd0a2b8..d1054a7e0e 100644 --- a/src/adapter/aws-lambda/types.ts +++ b/src/adapter/aws-lambda/types.ts @@ -1,4 +1,3 @@ -// @denoify-ignore /* eslint-disable @typescript-eslint/no-explicit-any */ export interface CognitoIdentity { diff --git a/src/adapter/bun/index.ts b/src/adapter/bun/index.ts index 6b562afe4a..815e82b034 100644 --- a/src/adapter/bun/index.ts +++ b/src/adapter/bun/index.ts @@ -1,4 +1,3 @@ -// @denoify-ignore export { serveStatic } from './serve-static' export { bunFileSystemModule, toSSG } from './ssg' export { createBunWebSocket } from './websocket' diff --git a/src/adapter/bun/serve-static.ts b/src/adapter/bun/serve-static.ts index 4b95f6fdea..768dffef8b 100644 --- a/src/adapter/bun/serve-static.ts +++ b/src/adapter/bun/serve-static.ts @@ -1,4 +1,3 @@ -// @denoify-ignore /* eslint-disable @typescript-eslint/ban-ts-comment */ import { serveStatic as baseServeStatic } from '../../middleware/serve-static' import type { ServeStaticOptions } from '../../middleware/serve-static' diff --git a/src/adapter/bun/ssg.ts b/src/adapter/bun/ssg.ts index 2694cd9613..f20f1b0cd3 100644 --- a/src/adapter/bun/ssg.ts +++ b/src/adapter/bun/ssg.ts @@ -1,4 +1,3 @@ -// @denoify-ignore /* eslint-disable @typescript-eslint/ban-ts-comment */ import { toSSG as baseToSSG } from '../../helper/ssg' import type { FileSystemModule, ToSSGAdaptorInterface } from '../../helper/ssg' diff --git a/src/adapter/bun/websocket.ts b/src/adapter/bun/websocket.ts index 003f06c35e..58d0ce8776 100644 --- a/src/adapter/bun/websocket.ts +++ b/src/adapter/bun/websocket.ts @@ -1,4 +1,3 @@ -// @denoify-ignore import { createWSMessageEvent, type UpgradeWebSocket, diff --git a/src/adapter/cloudflare-pages/handler.ts b/src/adapter/cloudflare-pages/handler.ts index 3d6fe11245..7e3f25e1ee 100644 --- a/src/adapter/cloudflare-pages/handler.ts +++ b/src/adapter/cloudflare-pages/handler.ts @@ -1,4 +1,3 @@ -// @denoify-ignore import type { Hono } from '../../hono' import type { MiddlewareHandler } from '../../types' diff --git a/src/adapter/cloudflare-pages/index.ts b/src/adapter/cloudflare-pages/index.ts index 8515498f75..72daa24c16 100644 --- a/src/adapter/cloudflare-pages/index.ts +++ b/src/adapter/cloudflare-pages/index.ts @@ -1,3 +1,2 @@ -// @denoify-ignore export { handle, serveStatic } from './handler' export type { EventContext } from './handler' diff --git a/src/adapter/cloudflare-workers/index.ts b/src/adapter/cloudflare-workers/index.ts index 78072f589e..fa15c483ca 100644 --- a/src/adapter/cloudflare-workers/index.ts +++ b/src/adapter/cloudflare-workers/index.ts @@ -1,3 +1,2 @@ -// @denoify-ignore export { serveStatic } from './serve-static-module' export { upgradeWebSocket } from './websocket' diff --git a/src/adapter/cloudflare-workers/serve-static-module.ts b/src/adapter/cloudflare-workers/serve-static-module.ts index 0625909d43..7fe14530a8 100644 --- a/src/adapter/cloudflare-workers/serve-static-module.ts +++ b/src/adapter/cloudflare-workers/serve-static-module.ts @@ -1,4 +1,3 @@ -// @denoify-ignore // For ES module mode import type { Env } from '../../types' import type { ServeStaticOptions } from './serve-static' diff --git a/src/adapter/cloudflare-workers/serve-static.ts b/src/adapter/cloudflare-workers/serve-static.ts index e491b60e2d..6250b04bb7 100644 --- a/src/adapter/cloudflare-workers/serve-static.ts +++ b/src/adapter/cloudflare-workers/serve-static.ts @@ -1,4 +1,3 @@ -// @denoify-ignore import type { KVNamespace } from '@cloudflare/workers-types' import { serveStatic as baseServeStatic } from '../../middleware/serve-static' import type { ServeStaticOptions as BaseServeStaticOptions } from '../../middleware/serve-static' diff --git a/src/adapter/cloudflare-workers/utils.ts b/src/adapter/cloudflare-workers/utils.ts index 62fcac8028..d6624e665f 100644 --- a/src/adapter/cloudflare-workers/utils.ts +++ b/src/adapter/cloudflare-workers/utils.ts @@ -1,4 +1,3 @@ -// @denoify-ignore import type { KVNamespace } from '@cloudflare/workers-types' declare const __STATIC_CONTENT: KVNamespace declare const __STATIC_CONTENT_MANIFEST: string diff --git a/src/adapter/cloudflare-workers/websocket.ts b/src/adapter/cloudflare-workers/websocket.ts index c6fa457e94..f40eab971c 100644 --- a/src/adapter/cloudflare-workers/websocket.ts +++ b/src/adapter/cloudflare-workers/websocket.ts @@ -1,4 +1,3 @@ -// @denoify-ignore /* eslint-disable @typescript-eslint/ban-ts-comment */ import type { WebSocketPair } from '@cloudflare/workers-types' import type { UpgradeWebSocket, WSContext, WSReadyState } from '../../helper/websocket' diff --git a/src/adapter/lambda-edge/handler.ts b/src/adapter/lambda-edge/handler.ts index dff79c7325..cb7d2a8d80 100644 --- a/src/adapter/lambda-edge/handler.ts +++ b/src/adapter/lambda-edge/handler.ts @@ -1,4 +1,3 @@ -// @denoify-ignore import crypto from 'crypto' import type { Hono } from '../../hono' diff --git a/src/adapter/lambda-edge/index.ts b/src/adapter/lambda-edge/index.ts index 1ff371d05e..bd0b6943e4 100644 --- a/src/adapter/lambda-edge/index.ts +++ b/src/adapter/lambda-edge/index.ts @@ -1,4 +1,3 @@ -// @denoify-ignore export { handle } from './handler' export type { Callback, diff --git a/src/adapter/netlify/handler.ts b/src/adapter/netlify/handler.ts index ba9b2b3963..60214e1bbd 100644 --- a/src/adapter/netlify/handler.ts +++ b/src/adapter/netlify/handler.ts @@ -1,5 +1,4 @@ // eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore import type { Context } from 'https://edge.netlify.com/' import type { Hono } from '../../hono' diff --git a/src/adapter/vercel/handler.ts b/src/adapter/vercel/handler.ts index 3f931e0589..2cfacd7b3a 100644 --- a/src/adapter/vercel/handler.ts +++ b/src/adapter/vercel/handler.ts @@ -1,4 +1,3 @@ -// @denoify-ignore /* eslint-disable @typescript-eslint/no-explicit-any */ import type { Hono } from '../../hono' import type { FetchEventLike } from '../../types' diff --git a/src/adapter/vercel/index.ts b/src/adapter/vercel/index.ts index fe303c423e..0151182848 100644 --- a/src/adapter/vercel/index.ts +++ b/src/adapter/vercel/index.ts @@ -1,2 +1 @@ -// @denoify-ignore export { handle } from './handler' diff --git a/src/helper/streaming/text.ts b/src/helper/streaming/text.ts index 8ff8e3df18..fe707cc67a 100644 --- a/src/helper/streaming/text.ts +++ b/src/helper/streaming/text.ts @@ -1,7 +1,7 @@ import type { Context } from '../../context' import { TEXT_PLAIN } from '../../context' import type { StreamingApi } from '../../utils/stream' -import { stream } from '.' +import { stream } from './' export const streamText = ( c: Context, diff --git a/src/index.ts b/src/index.ts index 6f1e124d35..848fb3b015 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,3 @@ -// @denoify-ignore - import { Hono } from './hono' export type { Env, diff --git a/src/jsx/components.ts b/src/jsx/components.ts index df67557627..4ed7b8f877 100644 --- a/src/jsx/components.ts +++ b/src/jsx/components.ts @@ -4,7 +4,7 @@ import { HtmlEscapedCallbackPhase, resolveCallback } from '../utils/html' import { DOM_RENDERER } from './constants' import { ErrorBoundary as ErrorBoundaryDomRenderer } from './dom/components' import type { HasRenderToDom } from './dom/render' -import type { FC, PropsWithChildren, Child } from '.' +import type { FC, PropsWithChildren, Child } from './' let errorBoundaryCounter = 0 diff --git a/src/jsx/context.ts b/src/jsx/context.ts index 04afe16a40..22e46f75ae 100644 --- a/src/jsx/context.ts +++ b/src/jsx/context.ts @@ -3,7 +3,7 @@ import type { HtmlEscapedString } from '../utils/html' import { JSXFragmentNode } from './base' import { DOM_RENDERER } from './constants' import { createContextProviderFunction } from './dom/context' -import type { FC, PropsWithChildren } from '.' +import type { FC, PropsWithChildren } from './' export interface Context { values: T[] diff --git a/src/jsx/dom/components.ts b/src/jsx/dom/components.ts index c7bf318271..57e4ba6031 100644 --- a/src/jsx/dom/components.ts +++ b/src/jsx/dom/components.ts @@ -1,4 +1,4 @@ -import type { FC, PropsWithChildren, Child } from '..' +import type { FC, PropsWithChildren, Child } from '../' import type { FallbackRender, ErrorHandler } from '../components' import { DOM_ERROR_HANDLER } from '../constants' import { Fragment } from './jsx-runtime' diff --git a/src/jsx/dom/css.ts b/src/jsx/dom/css.ts index 5a145ad7fb..d8dc1cc193 100644 --- a/src/jsx/dom/css.ts +++ b/src/jsx/dom/css.ts @@ -1,4 +1,4 @@ -import type { FC, PropsWithChildren } from '..' +import type { FC, PropsWithChildren } from '../' import type { CssClassName, CssVariableType } from '../../helper/css/common' import { SELECTOR, diff --git a/src/jsx/dom/index.ts b/src/jsx/dom/index.ts index 724b7f2ead..4184ac6225 100644 --- a/src/jsx/dom/index.ts +++ b/src/jsx/dom/index.ts @@ -138,6 +138,4 @@ export default { export type { Context } from '../context' -// TODO: change to `export type *` after denoify bug is fixed -// https://github.com/garronej/denoify/issues/124 -export * from '../types' +export type * from '../types' diff --git a/src/jsx/index.test.tsx b/src/jsx/index.test.tsx index 6505efb08f..004802e4b9 100644 --- a/src/jsx/index.test.tsx +++ b/src/jsx/index.test.tsx @@ -1,5 +1,4 @@ /** @jsxImportSource ./ */ -// @denoify-ignore /* eslint-disable @typescript-eslint/no-explicit-any */ import { html } from '../helper/html' import { Hono } from '../hono' diff --git a/src/jsx/index.ts b/src/jsx/index.ts index ab85197ae3..7ed8c6e52f 100644 --- a/src/jsx/index.ts +++ b/src/jsx/index.ts @@ -92,6 +92,4 @@ export default { Children, } -// TODO: change to `export type *` after denoify bug is fixed -// https://github.com/garronej/denoify/issues/124 -export * from './types' +export type * from './types' diff --git a/src/jsx/jsx-runtime.test.tsx b/src/jsx/jsx-runtime.test.tsx index 33cb28f9d0..a7ba4af469 100644 --- a/src/jsx/jsx-runtime.test.tsx +++ b/src/jsx/jsx-runtime.test.tsx @@ -1,6 +1,5 @@ /** @jsxRuntime automatic **/ /** @jsxImportSource . **/ -// @denoify-ignore import { Hono } from '../hono' describe('jsx-runtime', () => { diff --git a/src/jsx/streaming.ts b/src/jsx/streaming.ts index 5df7a3e4ec..6a037bb6bb 100644 --- a/src/jsx/streaming.ts +++ b/src/jsx/streaming.ts @@ -6,7 +6,7 @@ import { DOM_RENDERER, DOM_STASH } from './constants' import { Suspense as SuspenseDomRenderer } from './dom/components' import { buildDataStack } from './dom/render' import type { HasRenderToDom, NodeObject } from './dom/render' -import type { FC, PropsWithChildren, Child } from '.' +import type { FC, PropsWithChildren, Child } from './' let suspenseCounter = 0 diff --git a/src/middleware/method-override/index.ts b/src/middleware/method-override/index.ts index 6102078d87..028fd985bb 100644 --- a/src/middleware/method-override/index.ts +++ b/src/middleware/method-override/index.ts @@ -1,4 +1,3 @@ -import { URLSearchParams } from 'url' import type { Context, ExecutionContext } from '../../context' import type { Hono } from '../../hono' import type { MiddlewareHandler } from '../../types' diff --git a/src/middleware/secure-headers/index.ts b/src/middleware/secure-headers/index.ts index ed80f8222f..6746edd9fb 100644 --- a/src/middleware/secure-headers/index.ts +++ b/src/middleware/secure-headers/index.ts @@ -1,5 +1,6 @@ import type { Context } from '../../context' import type { MiddlewareHandler } from '../../types' +import { encodeBase64 } from '../../utils/encode' declare module '../../context' { interface ContextVariableMap { @@ -112,7 +113,7 @@ type SecureHeadersCallback = ( const generateNonce = () => { const buffer = new Uint8Array(16) crypto.getRandomValues(buffer) - return Buffer.from(buffer).toString('base64') + return encodeBase64(buffer) } export const NONCE: ContentSecurityPolicyOptionHandler = (ctx) => { const nonce = diff --git a/src/test-utils/setup-vitest.ts b/src/test-utils/setup-vitest.ts index eef20bb04c..216c9845ac 100644 --- a/src/test-utils/setup-vitest.ts +++ b/src/test-utils/setup-vitest.ts @@ -1,4 +1,3 @@ -// @denoify-ignore import * as nodeCrypto from 'node:crypto' import { vi } from 'vitest' diff --git a/yarn.lock b/yarn.lock index f85ae4625d..e282fd073c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,6 +1,6 @@ # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. # yarn lockfile v1 -# bun ./bun.lockb --hash: 03E95F4C55D78A53-2aac63394bface86-D16648EEBEA28A46-92573638c505f925 +# bun ./bun.lockb --hash: B0F80BF47311212B-e1ead61e28c636b5-7295CFAA56CB98E9-44e94466d8a0cab6 "@aashutoshrathi/word-wrap@^1.2.3": @@ -594,107 +594,6 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@octokit/auth-token@^2.4.4": - version "2.5.0" - resolved "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz" - integrity sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g== - dependencies: - "@octokit/types" "^6.0.3" - -"@octokit/core@>=2", "@octokit/core@>=3", "@octokit/core@^3.5.1": - version "3.6.0" - resolved "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz" - integrity sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q== - dependencies: - "@octokit/auth-token" "^2.4.4" - "@octokit/graphql" "^4.5.8" - "@octokit/request" "^5.6.3" - "@octokit/request-error" "^2.0.5" - "@octokit/types" "^6.0.3" - before-after-hook "^2.2.0" - universal-user-agent "^6.0.0" - -"@octokit/endpoint@^6.0.1": - version "6.0.12" - resolved "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz" - integrity sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA== - dependencies: - "@octokit/types" "^6.0.3" - is-plain-object "^5.0.0" - universal-user-agent "^6.0.0" - -"@octokit/graphql@^4.5.8": - version "4.8.0" - resolved "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz" - integrity sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg== - dependencies: - "@octokit/request" "^5.6.0" - "@octokit/types" "^6.0.3" - universal-user-agent "^6.0.0" - -"@octokit/openapi-types@^12.11.0": - version "12.11.0" - resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz" - integrity sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ== - -"@octokit/plugin-paginate-rest@^2.16.8": - version "2.21.3" - resolved "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz" - integrity sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw== - dependencies: - "@octokit/types" "^6.40.0" - -"@octokit/plugin-request-log@^1.0.4": - version "1.0.4" - resolved "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz" - integrity sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA== - -"@octokit/plugin-rest-endpoint-methods@^5.12.0": - version "5.16.2" - resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz" - integrity sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw== - dependencies: - "@octokit/types" "^6.39.0" - deprecation "^2.3.1" - -"@octokit/request@^5.6.0", "@octokit/request@^5.6.3": - version "5.6.3" - resolved "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz" - integrity sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A== - dependencies: - "@octokit/endpoint" "^6.0.1" - "@octokit/request-error" "^2.1.0" - "@octokit/types" "^6.16.1" - is-plain-object "^5.0.0" - node-fetch "^2.6.7" - universal-user-agent "^6.0.0" - -"@octokit/request-error@^2.0.5", "@octokit/request-error@^2.1.0": - version "2.1.0" - resolved "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz" - integrity sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg== - dependencies: - "@octokit/types" "^6.0.3" - deprecation "^2.0.0" - once "^1.4.0" - -"@octokit/rest@^18.0.0": - version "18.12.0" - resolved "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz" - integrity sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q== - dependencies: - "@octokit/core" "^3.5.1" - "@octokit/plugin-paginate-rest" "^2.16.8" - "@octokit/plugin-request-log" "^1.0.4" - "@octokit/plugin-rest-endpoint-methods" "^5.12.0" - -"@octokit/types@^6.0.3", "@octokit/types@^6.16.1", "@octokit/types@^6.39.0", "@octokit/types@^6.40.0": - version "6.41.0" - resolved "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz" - integrity sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg== - dependencies: - "@octokit/openapi-types" "^12.11.0" - "@open-draft/until@^1.0.3": version "1.0.3" resolved "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz" @@ -821,11 +720,6 @@ "@types/node" "*" "@types/responselike" "^1.0.0" -"@types/comment-json@^1.1.1": - version "1.1.1" - resolved "https://registry.npmjs.org/@types/comment-json/-/comment-json-1.1.1.tgz" - integrity sha512-U70oEqvnkeSSp8BIJwJclERtT13rd9ejK7XkIzMCQQePZe3VW1b7iQggXyW4ZvfGtGeXD0pZw24q5iWNe++HqQ== - "@types/cookie@^0.4.1": version "0.4.1" resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz" @@ -1413,11 +1307,6 @@ base64-js@^1.3.1: resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -before-after-hook@^2.2.0: - version "2.2.3" - resolved "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz" - integrity sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ== - binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" @@ -1794,26 +1683,11 @@ commander@^2.20.0: resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz" - integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== - commander@^9.4.1: version "9.5.0" resolved "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz" integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== -comment-json@^3.0.2: - version "3.0.3" - resolved "https://registry.npmjs.org/comment-json/-/comment-json-3.0.3.tgz" - integrity sha512-P7XwYkC3qjIK45EAa9c5Y3lR7SMXhJqwFdWg3niAIAcbk3zlpKDdajV8Hyz/Y3sGNn3l+YNMl8A2N/OubSArHg== - dependencies: - core-util-is "^1.0.2" - esprima "^4.0.1" - has-own-prop "^2.0.0" - repeat-string "^1.6.1" - component-emitter@^1.3.0: version "1.3.1" resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz" @@ -1856,12 +1730,7 @@ cookiejar@^2.1.4: resolved "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz" integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== -core-util-is@^1.0.2: - version "1.0.3" - resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: +cosmiconfig@^7.0.0: version "7.1.0" resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz" integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== @@ -2039,32 +1908,6 @@ delayed-stream@~1.0.0: resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -denoify@^1.6.6: - version "1.6.9" - resolved "https://registry.npmjs.org/denoify/-/denoify-1.6.9.tgz" - integrity sha512-/X5rewN7sCNRNx46b8JDnbohTPT+KK2TBQBlnsOktPyny5Zy70iwLvFNToPJ183vVFNBe91NeF9aCRTLZDiqsQ== - dependencies: - "@octokit/rest" "^18.0.0" - "@types/comment-json" "^1.1.1" - commander "^4.1.1" - comment-json "^3.0.2" - cosmiconfig "^7.0.1" - evt "2.5.7" - get-github-default-branch-name "^1.0.0" - gitignore-parser "0.0.2" - glob "^7.1.6" - minimal-polyfills "^2.2.3" - node-fetch "^2.6.7" - path-depth "^1.0.0" - scripting-tools "^0.19.14" - tsafe "^1.6.6" - url-join "^4.0.1" - -deprecation@^2.0.0, deprecation@^2.3.1: - version "2.3.1" - resolved "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz" - integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== - dezalgo@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz" @@ -2602,11 +2445,6 @@ espree@^9.6.0, espree@^9.6.1: acorn-jsx "^5.3.2" eslint-visitor-keys "^3.4.1" -esprima@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - esquery@^1.4.2: version "1.5.0" resolved "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz" @@ -2648,15 +2486,6 @@ events@^3.3.0: resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== -evt@2.5.7: - version "2.5.7" - resolved "https://registry.npmjs.org/evt/-/evt-2.5.7.tgz" - integrity sha512-dr7Wd16ry5F8WNU1xXLKpFpO3HsoAGg8zC48e08vDdzMzGWCP9/QFGt1PQptEEDh8SwYP3EL8M+d/Gb0kgUp6g== - dependencies: - minimal-polyfills "^2.2.3" - run-exclusive "^2.2.19" - tsafe "^1.6.6" - execa@^5.0.0: version "5.1.1" resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" @@ -2886,14 +2715,6 @@ get-func-name@^2.0.1, get-func-name@^2.0.2: resolved "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz" integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ== -get-github-default-branch-name@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/get-github-default-branch-name/-/get-github-default-branch-name-1.0.0.tgz" - integrity sha512-BqOokYoLjPIgkehneFPUZW9nNtU0LCXVMJ87YKaHGMWgCSZkTBHWGa76xsyVeXg+nQzRJBi9KDwQbTH3HLntwQ== - dependencies: - "@octokit/rest" "^18.0.0" - scripting-tools "^0.19.12" - get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: version "1.2.2" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz" @@ -2956,12 +2777,7 @@ github-url-from-git@^1.5.0: resolved "https://registry.npmjs.org/github-url-from-git/-/github-url-from-git-1.5.0.tgz" integrity sha512-WWOec4aRI7YAykQ9+BHmzjyNlkfJFG8QLXnDTsLz/kZefq7qkzdfo4p6fkYYMIq1aj+gZcQs/1HQhQh3DPPxlQ== -gitignore-parser@0.0.2: - version "0.0.2" - resolved "https://registry.npmjs.org/gitignore-parser/-/gitignore-parser-0.0.2.tgz" - integrity sha512-X6mpqUv59uWLGD4n3hZ8Cu8KbF2PMWPSFYmxZjdkpm3yOU7hSUYnzTkZI1mcWqchphvqyuz3/BhgBR4E/JtkCg== - -glob@7.2.3, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@7.2.3, glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -3130,11 +2946,6 @@ has-flag@^4.0.0: resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-own-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz" - integrity sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ== - has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz" @@ -3609,11 +3420,6 @@ is-plain-obj@^1.1.0: resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz" integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== -is-plain-object@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz" - integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== - is-potential-custom-element-name@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz" @@ -4215,11 +4021,6 @@ miniflare@3.20231030.1: youch "^3.2.2" zod "^3.20.6" -minimal-polyfills@^2.2.3: - version "2.2.3" - resolved "https://registry.npmjs.org/minimal-polyfills/-/minimal-polyfills-2.2.3.tgz" - integrity sha512-oxdmJ9cL+xV72h0xYxp4tP2d5/fTBpP45H8DIOn9pASuF8a3IYTf+25fMGDYGiWW+MFsuog6KD6nfmhZJQ+uUw== - minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" @@ -4816,11 +4617,6 @@ parse5@^7.0.0, parse5@^7.1.2: dependencies: entities "^4.4.0" -path-depth@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/path-depth/-/path-depth-1.0.0.tgz" - integrity sha512-dEiwdXAQyLvOi6ktLqhFhjVelJiVsdp2xBX3BaUtYCCkMRZTwUiq7cha+A0myvAVXRHbXfjhfTf4mNoAWzm2iA== - path-exists@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" @@ -5110,11 +4906,6 @@ regjsparser@^0.9.1: dependencies: jsesc "~0.5.0" -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz" - integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" @@ -5266,13 +5057,6 @@ run-async@^2.2.0, run-async@^2.4.0: resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz" integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== -run-exclusive@^2.2.19: - version "2.2.19" - resolved "https://registry.npmjs.org/run-exclusive/-/run-exclusive-2.2.19.tgz" - integrity sha512-K3mdoAi7tjJ/qT7Flj90L7QyPozwUaAG+CVhkdDje4HLKXUYC3N/Jzkau3flHVDLQVhiHBtcimVodMjN9egYbA== - dependencies: - minimal-polyfills "^2.2.3" - run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" @@ -5342,11 +5126,6 @@ scoped-regex@^2.0.0: resolved "https://registry.npmjs.org/scoped-regex/-/scoped-regex-2.1.0.tgz" integrity sha512-g3WxHrqSWCZHGHlSrF51VXFdjImhwvH8ZO/pryFH56Qi0cDsZfylQa/t0jCzVQFNbNvM00HfHjkDPEuarKDSWQ== -scripting-tools@^0.19.12, scripting-tools@^0.19.14: - version "0.19.14" - resolved "https://registry.npmjs.org/scripting-tools/-/scripting-tools-0.19.14.tgz" - integrity sha512-KGRES70dEmcaCdpx3R88bLWmfA4mQ/EGikCQy0FGTZwx3y9F5yYkzEhwp02+ZTgpvF25JcNOhDBbOqL6z92kwg== - selfsigned@^2.0.1: version "2.4.1" resolved "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz" @@ -5871,11 +5650,6 @@ ts-api-utils@^1.0.1: resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz" integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg== -tsafe@^1.6.6: - version "1.6.6" - resolved "https://registry.npmjs.org/tsafe/-/tsafe-1.6.6.tgz" - integrity sha512-gzkapsdbMNwBnTIjgO758GujLCj031IgHK/PKr2mrmkCSJMhSOR5FeOuSxKLMUoYc0vAA4RGEYYbjt/v6afD3g== - tsconfig-paths@^3.15.0: version "3.15.0" resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz" @@ -6066,11 +5840,6 @@ unique-string@^2.0.0: dependencies: crypto-random-string "^2.0.0" -universal-user-agent@^6.0.0: - version "6.0.1" - resolved "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz" - integrity sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ== - universalify@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz" @@ -6103,11 +5872,6 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -url-join@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz" - integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA== - url-parse@^1.5.3: version "1.5.10" resolved "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz"