[
  {
    "path": ".devcontainer/Dockerfile",
    "content": "FROM mcr.microsoft.com/devcontainers/typescript-node:20\n\n# Install Deno\nENV DENO_INSTALL=/usr/local\nRUN curl -fsSL https://deno.land/install.sh | sh\n\n# Install Bun\nENV BUN_INSTALL=/usr/local\nRUN curl -fsSL https://bun.sh/install | bash\n\nWORKDIR /hono\n"
  },
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "{\n  \"build\": {\n    \"dockerfile\": \"Dockerfile\"\n  },\n  \"containerEnv\": {\n    \"HOME\": \"/home/node\"\n  },\n  \"customizations\": {\n    \"vscode\": {\n      \"settings\": {\n        \"deno.enable\": false,\n        \"eslint.validate\": [\"javascript\", \"javascriptreact\", \"typescript\", \"typescriptreact\"],\n        \"editor.codeActionsOnSave\": {\n          \"source.fixAll.eslint\": \"explicit\"\n        }\n      },\n      \"extensions\": [\"dbaeumer.vscode-eslint\", \"esbenp.prettier-vscode\"]\n    }\n  }\n}\n"
  },
  {
    "path": ".devcontainer/docker-compose.yml",
    "content": "services:\n  hono:\n    build: .\n    container_name: hono\n    volumes:\n      - ../:/hono\n    networks:\n      - hono\n    command: bash\n    stdin_open: true\n    tty: true\n    restart: 'no'\n\nnetworks:\n  hono:\n    driver: bridge\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\ninsert_final_newline = true\nend_of_line = lf\n\n[*.{js,jsx,ts,tsx,json,json5,jsonc}]\nindent_style = space\nindent_size = 2\ntrim_trailing_whitespace = true\n\n[*.{md,yaml,yml}]\nindent_style = space\nindent_size = 2\ntrim_trailing_whitespace = false\n\n[*.{lockb,pxm}]\ncharset = unset\ninsert_final_newline = unset\nend_of_line = unset\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: ['yusukebe', 'usualoma']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1-bug-report.yml",
    "content": "name: 🐛 Bug Report\ndescription: Report an issue that should be fixed\nlabels: [triage]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thank you for submitting a bug report. It helps make Hono better.\n\n        If you need help or support using Hono, and are not reporting a bug, please ask questions in [our Discord](https://discord.gg/KMh2eNSdxV) or [GitHub Discussions](https://github.com/orgs/honojs/discussions).\n\n        Please try to include as much information as possible.\n  - type: input\n    attributes:\n      label: What version of Hono are you using?\n      placeholder: 0.0.0\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: What runtime/platform is your app running on? (with version if possible)\n      placeholder: Cloudflare Workers, Deno, Bun, etc.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: What steps can reproduce the bug?\n      description: Explain the bug and provide a code snippet that can reproduce it.\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: What is the expected behavior?\n  - type: textarea\n    attributes:\n      label: What do you see instead?\n  - type: textarea\n    attributes:\n      label: Additional information\n      description: Is there anything else you think we should know?\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/2-feature-request.yml",
    "content": "name: 🚀 Feature Request\ndescription: Suggest an idea, feature, or enhancement\nlabels: [enhancement]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thank you for submitting an idea. It helps make Hono better.\n\n  - type: textarea\n    attributes:\n      label: What is the feature you are proposing?\n      description: A clear description of what you want to happen.\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: true\ncontact_links:\n  - name: ❓ Questions\n    url: https://github.com/orgs/honojs/discussions\n    about: Ask your questions on the GitHub Discussions.\n  - name: 🗣️ Discord\n    url: https://discord.gg/KMh2eNSdxV\n    about: Join our Discord server to chat.\n"
  },
  {
    "path": ".github/actions/perf-measures/action.yml",
    "content": "name: 'Performance Measures'\ndescription: 'Run type check and bundle size performance measurements'\n\ninputs:\n  target-ref:\n    description: 'Target ref (main or auto). Set to \"auto\" to delegate ref detection to octocov.'\n    required: false\n    default: 'auto'\n\nruns:\n  using: 'composite'\n  steps:\n    - uses: actions/checkout@v6\n    - uses: oven-sh/setup-bun@v2\n      with:\n        bun-version-file: '.tool-versions'\n    - run: |\n        bun install --frozen-lockfile\n        bun tsc --build\n      shell: bash\n\n    - name: Performance measurement of type check (tsc)\n      run: |\n        bun scripts/generate-app.ts\n        bun tsc -p tsconfig.build.json --diagnostics | bun scripts/process-results.ts > diagnostics-tsc.json\n      shell: bash\n      working-directory: perf-measures/type-check\n      env:\n        BENCHMARK_TS_IMPL_LABEL: tsc\n\n    - name: Performance measurement of type check (typescript-go)\n      run: |\n        bun scripts/generate-app.ts\n        bun tsgo -p tsconfig.build.json --diagnostics | bun scripts/process-results.ts > diagnostics-tsgo.json\n      shell: bash\n      working-directory: perf-measures/type-check\n      env:\n        BENCHMARK_TS_IMPL_LABEL: typescript-go\n\n    - name: Performance measurement of bundle check\n      run: |\n        bun run build\n        bun perf-measures/bundle-check/scripts/check-bundle-size.ts > perf-measures/bundle-check/size.json\n      shell: bash\n\n    - name: Run octocov\n      if: ${{ inputs.target-ref == 'auto' }}\n      uses: k1LoW/octocov-action@v1\n      with:\n        config: perf-measures/.octocov.consolidated.perf-measures.yml\n      env:\n        OCTOCOV_CUSTOM_METRICS_BUNDLE_SIZE_CHECK: perf-measures/bundle-check/size.json\n        OCTOCOV_CUSTOM_METRICS_DIAGNOSTICS_TSC: perf-measures/type-check/diagnostics-tsc.json\n        OCTOCOV_CUSTOM_METRICS_DIAGNOSTICS_TSGO: perf-measures/type-check/diagnostics-tsgo.json\n\n    - name: Run octocov with custom target ref\n      if: ${{ inputs.target-ref == 'main' }}\n      uses: k1LoW/octocov-action@v1\n      with:\n        config: perf-measures/.octocov.consolidated.perf-measures.main.yml\n      env:\n        OCTOCOV_GITHUB_REF: 'refs/heads/main'\n        OCTOCOV_CUSTOM_METRICS_BUNDLE_SIZE_CHECK: perf-measures/bundle-check/size.json\n        OCTOCOV_CUSTOM_METRICS_DIAGNOSTICS_TSC: perf-measures/type-check/diagnostics-tsc.json\n        OCTOCOV_CUSTOM_METRICS_DIAGNOSTICS_TSGO: perf-measures/type-check/diagnostics-tsgo.json\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "### The author should do the following, if applicable\n\n- [ ] Add tests\n- [ ] Run tests\n- [ ] `bun run format:fix && bun run lint:fix` to format the code\n- [ ] Add [TSDoc](https://tsdoc.org/)/[JSDoc](https://jsdoc.app/about-getting-started) to document the code\n"
  },
  {
    "path": ".github/workflows/autofix.yml",
    "content": "name: autofix.ci\n\non:\n  pull_request:\n  push:\n    branches: [main]\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\npermissions:\n  contents: read\n\njobs:\n  autofix:\n    name: autofix\n    runs-on: ubuntu-latest\n    if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n      - uses: oven-sh/setup-bun@v2\n        with:\n          bun-version-file: '.tool-versions'\n      - run: bun install --frozen-lockfile\n      - run: bun run format:fix\n      - run: bun run lint:fix\n      - name: Apply fixes\n        uses: autofix-ci/action@v1\n        with:\n          commit-message: 'ci: apply automated fixes'\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: ci\non:\n  push:\n    branches: [main, next]\n  pull_request:\n    branches: ['*']\n    paths-ignore:\n      - 'docs/**'\n      - '.vscode/**'\n      - 'README.md'\n      - '.gitignore'\n      - 'LICENSE'\n\njobs:\n  coverage:\n    name: 'Coverage'\n    runs-on: ubuntu-latest\n    needs:\n      - main\n      - bun\n      - deno\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/download-artifact@v6\n        with:\n          pattern: coverage-*\n          merge-multiple: true\n          path: ./coverage\n      - uses: codecov/codecov-action@v5\n        with:\n          fail_ci_if_error: true\n          directory: ./coverage\n\n  main:\n    name: 'Main'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/setup-node@v6\n        with:\n          node-version-file: '.tool-versions'\n      - uses: oven-sh/setup-bun@v2\n        with:\n          bun-version-file: '.tool-versions'\n      - run: bun install --frozen-lockfile\n      - run: bun run format\n      - run: bun run lint\n      - run: bun run editorconfig-checker -format github-actions\n      - run: bun run build\n      - run: bun run test\n      - uses: actions/upload-artifact@v5\n        with:\n          name: coverage-main\n          path: coverage/\n\n  jsr-dry-run:\n    name: \"Checking if it's valid for JSR\"\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: denoland/setup-deno@v2\n        with:\n          deno-version-file: '.tool-versions'\n      - uses: oven-sh/setup-bun@v2\n        with:\n          bun-version-file: '.tool-versions'\n      - run: bunx jsr publish --dry-run\n\n  deno:\n    name: 'Deno'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: denoland/setup-deno@v2\n        with:\n          deno-version-file: '.tool-versions'\n      - run: env NAME=Deno deno test --coverage=coverage/raw/deno-runtime --allow-read --allow-env --allow-write --allow-net -c runtime-tests/deno/deno.json runtime-tests/deno\n      - run: deno test -c runtime-tests/deno-jsx/deno.precompile.json --coverage=coverage/raw/deno-precompile-jsx runtime-tests/deno-jsx\n      - run: deno test -c runtime-tests/deno-jsx/deno.react-jsx.json --coverage=coverage/raw/deno-react-jsx runtime-tests/deno-jsx\n      - run: grep -R '\"url\":' coverage | grep -v runtime-tests | sed -e 's/.*file:..//;s/.,//' | xargs deno cache --unstable-sloppy-imports\n      - run: deno coverage --lcov > coverage/deno-runtime-coverage-lcov.info\n      - uses: actions/upload-artifact@v5\n        with:\n          name: coverage-deno\n          path: coverage/\n\n  bun:\n    name: 'Bun'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: oven-sh/setup-bun@v2\n        with:\n          bun-version-file: '.tool-versions'\n      - run: bun install --frozen-lockfile\n      - run: bun run test:bun\n      - uses: actions/upload-artifact@v5\n        with:\n          name: coverage-bun\n          path: coverage/\n\n  bun-windows:\n    name: 'Bun - Windows'\n    runs-on: windows-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: oven-sh/setup-bun@v2\n        with:\n          bun-version-file: '.tool-versions'\n      - run: bun run test:bun\n\n  fastly:\n    name: 'Fastly Compute'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: oven-sh/setup-bun@v2\n        with:\n          bun-version-file: '.tool-versions'\n      - run: bun install --frozen-lockfile\n      - run: bun run build\n      - run: bun run test:fastly\n\n  node:\n    name: 'Node.js v${{ matrix.node }}'\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        node: ['18.18.2', '20.x', '22.x']\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/setup-node@v6\n        with:\n          node-version: ${{ matrix.node }}\n      - uses: oven-sh/setup-bun@v2\n        with:\n          bun-version-file: '.tool-versions'\n      - run: bun install --frozen-lockfile\n      - run: bun run build\n      - run: bun run test:node\n\n  workerd:\n    name: 'workerd'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/setup-node@v6\n        with:\n          node-version-file: '.tool-versions'\n      - uses: oven-sh/setup-bun@v2\n        with:\n          bun-version-file: '.tool-versions'\n      - run: bun install --frozen-lockfile\n      - run: bun run build\n      - run: bun run test:workerd\n\n  lambda:\n    name: 'AWS Lambda'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: oven-sh/setup-bun@v2\n        with:\n          bun-version-file: '.tool-versions'\n      - run: bun install --frozen-lockfile\n      - run: bun run build\n      - run: bun run test:lambda\n\n  lambda-edge:\n    name: 'Lambda@Edge'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: oven-sh/setup-bun@v2\n        with:\n          bun-version-file: '.tool-versions'\n      - run: bun install --frozen-lockfile\n      - run: bun run build\n      - run: bun run test:lambda-edge\n\n  perf-measures-check-on-pr:\n    name: 'Type & Bundle size Check on PR'\n    runs-on: ubuntu-latest\n    if: github.event_name == 'pull_request'\n    steps:\n      - uses: actions/checkout@v6\n      - uses: ./.github/actions/perf-measures\n        with:\n          target-ref: 'auto'\n\n  http-benchmark-on-pr:\n    name: 'HTTP Speed Check on PR'\n    runs-on: ubuntu-latest\n    if: github.event_name == 'pull_request'\n    steps:\n      - uses: actions/checkout@v6\n      - uses: oven-sh/setup-bun@v2\n        with:\n          bun-version-file: '.tool-versions'\n      - run: bun install --frozen-lockfile\n      - name: Install bombardier\n        run: |\n          wget -O bombardier https://github.com/codesenberg/bombardier/releases/download/v2.0.1/bombardier-linux-amd64\n          chmod +x bombardier\n          sudo mv bombardier /usr/local/bin/\n      - name: Run HTTP benchmark\n        run: |\n          cd benchmarks/http-server\n          bun run benchmark.ts\n      - name: Comment PR\n        uses: actions/github-script@v7\n        if: github.event.pull_request.head.repo.full_name == github.repository\n        with:\n          script: |\n            const fs = require('fs');\n            const results = fs.readFileSync('benchmarks/http-server/benchmark-results.md', 'utf8');\n\n            // Minimize previous benchmark comments\n            const comments = await github.rest.issues.listComments({\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              issue_number: context.issue.number\n            });\n\n            for (const comment of comments.data) {\n              if (comment.body.includes('## HTTP Performance Benchmark')) {\n                await github.graphql(`\n                  mutation {\n                    minimizeComment(input: { subjectId: \"${comment.node_id}\", classifier: OUTDATED }) {\n                      minimizedComment {\n                        isMinimized\n                      }\n                    }\n                  }\n                `);\n              }\n            }\n\n            // Post new comment\n            await github.rest.issues.createComment({\n              issue_number: context.issue.number,\n              owner: context.repo.owner,\n              repo: context.repo.repo,\n              body: results\n            });\n      - name: Show benchmark results for forks\n        if: github.event.pull_request.head.repo.full_name != github.repository\n        run: |\n          echo \"## HTTP Performance Benchmark Results\"\n          echo \"Note: Cannot post comment due to security restrictions on fork PRs\"\n          cat benchmarks/http-server/benchmark-results.md\n\n  perf-measures-check-on-main:\n    name: 'Type & Bundle size Check on Main'\n    runs-on: ubuntu-latest\n    if: github.ref == 'refs/heads/main'\n    steps:\n      - uses: actions/checkout@v6\n      - uses: ./.github/actions/perf-measures\n        with:\n          target-ref: 'main'\n"
  },
  {
    "path": ".github/workflows/cr.yml",
    "content": "name: cr\non:\n  push:\n    branches: [main]\n    tags: ['!**'] # Avoid publishing on tags\n  pull_request:\n    types: [opened, synchronize, labeled] # Run on PR creation, updates, and when labels are added\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.number }} # Concurrency group for each PR\n  cancel-in-progress: true # Cancel in progress builds for the same PR\n\njobs:\n  publish:\n    if: github.repository == 'honojs/hono' && (github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'cr-tracked'))\n    runs-on: ubuntu-latest\n    name: 'Publish: pkg.pr.new'\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n\n      - uses: actions/setup-node@v6\n        with:\n          node-version-file: '.tool-versions'\n\n      - uses: oven-sh/setup-bun@v2\n        with:\n          bun-version-file: '.tool-versions'\n\n      - name: Install Dependencies\n        run: bun install --frozen-lockfile\n\n      - name: Build\n        run: bun run build\n\n      - name: Publish to StackBlitz\n        run: |\n          bun pkg-pr-new publish --compact\n"
  },
  {
    "path": ".github/workflows/no-response.yml",
    "content": "name: Close stale issues with \"not bug\" label\n\non:\n  schedule:\n    - cron: '0 0 * * *'\n\npermissions:\n  contents: write\n  issues: write\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Close stale issues with \"not bug\" label\n        uses: actions/stale@v8\n        with:\n          days-before-stale: 7\n          days-before-close: 2\n          stale-issue-message: 'This issue has been marked as stale due to inactivity.'\n          close-issue-message: 'Closing this issue due to inactivity.'\n          exempt-issue-labels: ''\n          stale-issue-label: 'stale'\n          only-labels: 'not bug'\n          operations-per-run: 30\n          remove-stale-when-updated: true\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: release\n\non:\n  push:\n    tags:\n      - '*'\n\njobs:\n  jsr:\n    name: publish-to-jsr\n    runs-on: ubuntu-latest\n\n    permissions:\n      contents: read\n      id-token: write\n\n    steps:\n      - uses: actions/checkout@v6\n      - name: Install deno\n        uses: denoland/setup-deno@v2\n        with:\n          deno-version-file: '.tool-versions'\n      - run: deno install --no-lock --allow-scripts\n      - name: Publish to JSR\n        run: deno run -A jsr:@david/publish-on-tag@0.1.4\n"
  },
  {
    "path": ".gitignore",
    "content": "dist\nsandbox\n\n# Cloudflare Workers\nworker\n.wrangler\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional stylelint cache\n.stylelintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variable files\n.env\n.env.development.local\n.env.test.local\n.env.production.local\n.env.local\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\nout\n\n# Nuxt.js build / generate output\n.nuxt\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# vuepress v2.x temp and cache directory\n.temp\n.cache\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.*\n\n# IDE-specific settings\n.idea\n\n# Claude Code local files\nCLAUDE.local.md\nsettings.local.json\n"
  },
  {
    "path": ".gitpod.yml",
    "content": "tasks:\n  - name: Setup\n    init: bun install --frozen-lockfile\nimage:\n  file: ./.devcontainer/Dockerfile\nvscode:\n  extensions:\n    - oven.bun-vscode\n    - vitest.explorer\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"printWidth\": 100,\n  \"trailingComma\": \"es5\",\n  \"tabWidth\": 2,\n  \"semi\": false,\n  \"singleQuote\": true,\n  \"jsxSingleQuote\": true,\n  \"endOfLine\": \"lf\"\n}\n"
  },
  {
    "path": ".tool-versions",
    "content": "nodejs 24.7.0\nbun  1.2.19\ndeno 2.4.5\n"
  },
  {
    "path": ".vitest.config/setup-vitest.ts",
    "content": "import * as nodeCrypto from 'node:crypto'\nimport { vi } from 'vitest'\n\n/**\n * crypto\n */\nif (!globalThis.crypto) {\n  vi.stubGlobal('crypto', nodeCrypto)\n  vi.stubGlobal('CryptoKey', nodeCrypto.webcrypto.CryptoKey)\n}\n\n/**\n * Cache API\n */\ntype StoreMap = Map<string | Request, Response>\n\nclass MockCache {\n  name: string\n  store: StoreMap\n\n  constructor(name: string, store: StoreMap) {\n    this.name = name\n    this.store = store\n  }\n\n  async match(key: Request | string): Promise<Response | null> {\n    return this.store.get(key) || null\n  }\n\n  async keys() {\n    return this.store.keys()\n  }\n\n  async put(key: Request | string, response: Response): Promise<void> {\n    this.store.set(key, response)\n  }\n}\n\nconst globalStore: Map<string | Request, Response> = new Map()\n\nconst caches = {\n  open: (name: string) => {\n    return new MockCache(name, globalStore)\n  },\n}\n\nvi.stubGlobal('caches', caches)\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\"EditorConfig.EditorConfig\"]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"deno.enable\": false,\n  \"eslint.validate\": [\"javascript\", \"javascriptreact\", \"typescript\", \"typescriptreact\"],\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": \"explicit\"\n  },\n  \"typescript.tsdk\": \"node_modules/typescript/lib\"\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 - present, Yusuke Wada and Hono contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n  <a href=\"https://hono.dev\">\n    <img src=\"https://raw.githubusercontent.com/honojs/hono/main/docs/images/hono-title.png\" width=\"500\" height=\"auto\" alt=\"Hono\"/>\n  </a>\n</div>\n\n<hr />\n\n[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/honojs/hono/ci.yml?branch=main)](https://github.com/honojs/hono/actions)\n[![GitHub](https://img.shields.io/github/license/honojs/hono)](https://github.com/honojs/hono/blob/main/LICENSE)\n[![npm](https://img.shields.io/npm/v/hono)](https://www.npmjs.com/package/hono)\n[![npm](https://img.shields.io/npm/dm/hono)](https://www.npmjs.com/package/hono)\n[![JSR](https://jsr.io/badges/@hono/hono)](https://jsr.io/@hono/hono)\n[![Bundle Size](https://img.shields.io/bundlephobia/min/hono)](https://bundlephobia.com/result?p=hono)\n[![Bundle Size](https://img.shields.io/bundlephobia/minzip/hono)](https://bundlephobia.com/result?p=hono)\n[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/honojs/hono)](https://github.com/honojs/hono/pulse)\n[![GitHub last commit](https://img.shields.io/github/last-commit/honojs/hono)](https://github.com/honojs/hono/commits/main)\n[![codecov](https://codecov.io/github/honojs/hono/graph/badge.svg)](https://codecov.io/github/honojs/hono)\n[![Discord badge](https://img.shields.io/discord/1011308539819597844?label=Discord&logo=Discord)](https://discord.gg/KMh2eNSdxV)\n[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/honojs/hono)\n\nHono - _**means flame🔥 in Japanese**_ - is a small, simple, and ultrafast web framework built on Web Standards. It works on any JavaScript runtime: Cloudflare Workers, Fastly Compute, Deno, Bun, Vercel, AWS Lambda, Lambda@Edge, and Node.js.\n\nFast, but not only fast.\n\n```ts\nimport { Hono } from 'hono'\nconst app = new Hono()\n\napp.get('/', (c) => c.text('Hono!'))\n\nexport default app\n```\n\n## Quick Start\n\n```bash\nnpm create hono@latest\n```\n\n## Features\n\n- **Ultrafast** 🚀 - The router `RegExpRouter` is really fast. Not using linear loops. Fast.\n- **Lightweight** 🪶 - The `hono/tiny` preset is under 12kB. Hono has zero dependencies and uses only the Web Standard API.\n- **Multi-runtime** 🌍 - Works on Cloudflare Workers, Fastly Compute, Deno, Bun, AWS Lambda, Lambda@Edge, or Node.js. The same code runs on all platforms.\n- **Batteries Included** 🔋 - Hono has built-in middleware, custom middleware, and third-party middleware. Batteries included.\n- **Delightful DX** 😃 - Super clean APIs. First-class TypeScript support. Now, we've got \"Types\".\n\n## Documentation\n\nThe documentation is available on [hono.dev](https://hono.dev).\n\n## Migration\n\nThe migration guide is available on [docs/MIGRATION.md](docs/MIGRATION.md).\n\n## Communication\n\n[X](https://x.com/honojs) and [Discord channel](https://discord.gg/KMh2eNSdxV) are available.\n\n## Contributing\n\nContributions Welcome! You can contribute in the following ways.\n\n- Create an Issue - Propose a new feature. Report a bug.\n- Pull Request - Fix a bug or typo. Refactor the code.\n- Create third-party middleware - See instructions below.\n- Share - Share your thoughts on the Blog, X, and others.\n- Make your application - Please try to use Hono.\n\nFor more details, see [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md).\n\n## Contributors\n\nThanks to [all contributors](https://github.com/honojs/hono/graphs/contributors)!\n\n## Authors\n\nYusuke Wada <https://github.com/yusukebe>\n\n_RegExpRouter_, _SmartRouter_, _LinearRouter_, and _PatternRouter_ are created by Taku Amano <https://github.com/usualoma>\n\n## License\n\nDistributed under the MIT License. See [LICENSE](LICENSE) for more information.\n"
  },
  {
    "path": "benchmarks/deno/.gitignore",
    "content": "*.sqlite\n"
  },
  {
    "path": "benchmarks/deno/.vscode/settings.json",
    "content": "{\n  \"eslint.validate\": [\"javascript\", \"javascriptreact\", \"typescript\", \"typescriptreact\"],\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": true\n  },\n  \"deno.enable\": true\n}\n"
  },
  {
    "path": "benchmarks/deno/fast.ts",
    "content": "import fast from 'https://deno.land/x/fast@4.0.0-beta.1/mod.ts'\nimport type { Context } from 'https://deno.land/x/fast@4.0.0-beta.1/mod.ts'\n\nconst app = fast()\n\napp.get('/user', () => {})\napp.get('/user/comments', () => {})\napp.get('/user/avatar', () => {})\napp.get('/user/lookup/email/:address', () => {})\napp.get('/event/:id', () => {})\napp.get('/event/:id/comments', () => {})\napp.post('/event/:id/comments', () => {})\napp.post('/status', () => {})\napp.get('/very/deeply/nested/route/hello/there', () => {})\napp.get('/user/lookup/username/:username', (ctx: Context) => {\n  return { message: `Hello ${ctx.params.username}` }\n})\n\nawait app.serve({\n  port: 8000,\n})\n"
  },
  {
    "path": "benchmarks/deno/faster.ts",
    "content": "import { res, Server } from 'https://deno.land/x/faster@v5.7/mod.ts'\nconst app = new Server()\n\napp.get('/user', () => {})\napp.get('/user/comments', () => {})\napp.get('/user/avatar', () => {})\napp.get('/user/lookup/email/:address', () => {})\napp.get('/event/:id', () => {})\napp.get('/event/:id/comments', () => {})\napp.post('/event/:id/comments', () => {})\napp.post('/status', () => {})\napp.get('/very/deeply/nested/route/hello/there', () => {})\napp.get('/user/lookup/username/:username', res('json'), async (ctx: any, next: any) => {\n  ctx.res.body = { message: `Hello ${ctx.params.username}` }\n  await next()\n})\n\nawait app.listen({ port: 8000 })\n"
  },
  {
    "path": "benchmarks/deno/hono.ts",
    "content": "import { Hono } from '../../src/index.ts'\nimport { RegExpRouter } from '../../src/router/reg-exp-router/index.ts'\n\nconst app = new Hono({ router: new RegExpRouter() })\n\napp.get('/user', (c) => c.text('User'))\napp.get('/user/comments', (c) => c.text('User Comments'))\napp.get('/user/avatar', (c) => c.text('User Avatar'))\napp.get('/user/lookup/email/:address', (c) => c.text('User Lookup Email Address'))\napp.get('/event/:id', (c) => c.text('Event'))\napp.get('/event/:id/comments', (c) => c.text('Event Comments'))\napp.post('/event/:id/comments', (c) => c.text('POST Event Comments'))\napp.post('/status', (c) => c.text('Status'))\napp.get('/very/deeply/nested/route/hello/there', (c) => c.text('Very Deeply Nested Route'))\napp.get('/user/lookup/username/:username', (c) => {\n  return c.json({ message: `Hello ${c.req.param('username')}` })\n})\n\nDeno.serve(app.fetch, {\n  port: 8000,\n})\n"
  },
  {
    "path": "benchmarks/deno/magalo.ts",
    "content": "import { Megalo } from 'https://deno.land/x/megalo@v0.3.0/mod.ts'\n\nconst app = new Megalo()\n\napp.get('/user', () => {})\napp.get('/user/comments', () => {})\napp.get('/user/avatar', () => {})\napp.get('/user/lookup/email/:address', () => {})\napp.get('/event/:id', () => {})\napp.get('/event/:id/comments', () => {})\napp.post('/event/:id/comments', () => {})\napp.post('/status', () => {})\napp.get('/very/deeply/nested/route/hello/there', () => {})\napp.get('/user/lookup/username/:username', ({ params }, res) => {\n  res.json({\n    message: `Hello ${params.username}`,\n  })\n})\n\napp.listen({ port: 8000 })\n"
  },
  {
    "path": "benchmarks/deno/oak.ts",
    "content": "import { Application, Router } from 'https://deno.land/x/oak@v10.5.1/mod.ts'\n\nconst router = new Router()\n\nrouter.get('/user', () => {})\nrouter.get('/user/comments', () => {})\nrouter.get('/user/avatar', () => {})\nrouter.get('/user/lookup/email/:address', () => {})\nrouter.get('/event/:id', () => {})\nrouter.get('/event/:id/comments', () => {})\nrouter.post('/event/:id/comments', () => {})\nrouter.post('/status', () => {})\nrouter.get('/very/deeply/nested/route/hello/there', () => {})\nrouter.get('/user/lookup/username/:username', (ctx) => {\n  ctx.response.body = {\n    message: `Hello ${ctx.params.username}`,\n  }\n})\n\nconst app = new Application()\napp.use(router.routes())\napp.use(router.allowedMethods())\n\nawait app.listen({ port: 8000 })\n"
  },
  {
    "path": "benchmarks/deno/opine.ts",
    "content": "import { opine } from 'https://deno.land/x/opine@2.2.0/mod.ts'\n\nconst app = opine()\n\napp.get('/user', () => {})\napp.get('/user/comments', () => {})\napp.get('/user/avatar', () => {})\napp.get('/user/lookup/email/:address', () => {})\napp.get('/event/:id', () => {})\napp.get('/event/:id/comments', () => {})\napp.post('/event/:id/comments', () => {})\napp.post('/status', () => {})\napp.get('/very/deeply/nested/route/hello/there', () => {})\napp.get('/user/lookup/username/:username', (req, res) => {\n  res.send({ message: `Hello ${req.params.username}` })\n})\n\napp.listen(8000)\n"
  },
  {
    "path": "benchmarks/handle-event/index.js",
    "content": "import Benchmark from 'benchmark'\nimport { makeEdgeEnv } from 'edge-mock'\nimport { Router as IttyRouter } from 'itty-router'\nimport { Request, Response } from 'node-fetch'\nimport { Router as SunderRouter, Sunder } from 'sunder'\nimport { Router as WorktopRouter } from 'worktop'\nimport { Hono } from '../../dist/hono'\nimport { RegExpRouter } from '../../dist/router/reg-exp-router'\n\nglobalThis.Request = Request\nglobalThis.Response = Response\n\nconst initHono = (hono) => {\n  hono.get('/user', (c) => c.text('User'))\n  hono.get('/user/comments', (c) => c.text('User Comments'))\n  hono.get('/user/avatar', (c) => c.text('User Avatar'))\n  hono.get('/user/lookup/email/:address', (c) => c.text('User Lookup Email Address'))\n  hono.get('/event/:id', (c) => c.text('Event'))\n  hono.get('/event/:id/comments', (c) => c.text('Event Comments'))\n  hono.post('/event/:id/comments', (c) => c.text('POST Event Comments'))\n  hono.post('/status', (c) => c.text('Status'))\n  hono.get('/very/deeply/nested/route/hello/there', (c) => c.text('Very Deeply Nested Route'))\n  hono.get('/user/lookup/username/:username', (c) => {\n    return c.text(`Hello ${c.req.param('username')}`)\n  })\n  return hono\n}\n\nconst hono = initHono(new Hono({ router: new RegExpRouter() }))\n\n// itty-router\nconst ittyRouter = IttyRouter()\nittyRouter.get('/user', () => new Response('User'))\nittyRouter.get('/user/comments', () => new Response('User Comments'))\nittyRouter.get('/user/avatar', () => new Response('User Avatar'))\nittyRouter.get('/user/lookup/email/:address', () => new Response('User Lookup Email Address'))\nittyRouter.get('/event/:id', () => new Response('Event'))\nittyRouter.get('/event/:id/comments', () => new Response('Event Comments'))\nittyRouter.post('/event/:id/comments', () => new Response('POST Event Comments'))\nittyRouter.post('/status', () => new Response('Status'))\nittyRouter.get(\n  '/very/deeply/nested/route/hello/there',\n  () => new Response('Very Deeply Nested Route')\n)\nittyRouter.get('/user/lookup/username/:username', ({ params }) => {\n  return new Response(`Hello ${params.username}`, {\n    status: 200,\n    headers: {\n      'Content-Type': 'text/plain;charset=UTF-8',\n    },\n  })\n})\n\n// Sunder\nconst sunderRouter = new SunderRouter()\nsunderRouter.get('/user', (ctx) => {\n  ctx.response.body = 'User'\n})\nsunderRouter.get('/user/comments', (ctx) => {\n  ctx.response.body = 'User Comments'\n})\nsunderRouter.get('/user/avatar', (ctx) => {\n  ctx.response.body = 'User Avatar'\n})\nsunderRouter.get('/user/lookup/email/:address', (ctx) => {\n  ctx.response.body = 'User Lookup Email Address'\n})\nsunderRouter.get('/event/:id', (ctx) => {\n  ctx.response.body = 'Event'\n})\nsunderRouter.get('/event/:id/comments', (ctx) => {\n  ctx.response.body = 'Event Comments'\n})\nsunderRouter.post('/event/:id/comments', (ctx) => {\n  ctx.response.body = 'POST Event Comments'\n})\nsunderRouter.post('/status', (ctx) => {\n  ctx.response.body = 'Status'\n})\nsunderRouter.get('/very/deeply/nested/route/hello/there', (ctx) => {\n  ctx.response.body = 'Very Deeply Nested Route'\n})\n//sunderRouter.get('/static/*', () => {})\nsunderRouter.get('/user/lookup/username/:username', (ctx) => {\n  ctx.response.body = `Hello ${ctx.params.username}`\n})\nconst sunderApp = new Sunder()\nsunderApp.use(sunderRouter.middleware)\n\n// worktop\nconst worktopRouter = new WorktopRouter()\nworktopRouter.add('GET', '/user', async (_req, res) => res.send(200, 'User'))\nworktopRouter.add('GET', '/user/comments', (_req, res) => res.send(200, 'User Comments'))\nworktopRouter.add('GET', '/user/avatar', (_req, res) => res.send(200, 'User Avatar'))\nworktopRouter.add('GET', '/user/lookup/email/:address', (_req, res) =>\n  res.send(200, 'User Lookup Email Address')\n)\nworktopRouter.add('GET', '/event/:id', (_req, res) => res.send(200, 'Event'))\nworktopRouter.add('POST', '/event/:id/comments', (_req, res) =>\n  res.send(200, 'POST Event Comments')\n)\nworktopRouter.add('POST', '/status', (_req, res) => res.send(200, 'Status'))\nworktopRouter.add('GET', '/very/deeply/nested/route/hello/there', (_req, res) =>\n  res.send(200, 'Very Deeply Nested Route')\n)\nworktopRouter.add('GET', '/user/lookup/username/:username', (req, res) =>\n  res.send(200, `Hello ${req.params.username}`)\n)\n\n// Request Object\nconst request = new Request('http://localhost/user/lookup/username/hey', { method: 'GET' })\n\nmakeEdgeEnv()\n\n// FetchEvent Object\n// eslint-disable-next-line no-undef\nconst event = new FetchEvent('fetch', { request })\n\nconst fn = async () => {\n  let res = await hono.fetch(event.request)\n  console.log(await res.text())\n  res = await ittyRouter.handle(event.request)\n  console.log(await res.text())\n  res = await sunderApp.handle(event)\n  console.log(await res.text())\n  res = await worktopRouter.run(event)\n  console.log(await res.text())\n}\nfn()\n\nconst suite = new Benchmark.Suite()\n\nsuite\n  .add('Hono', async () => {\n    await hono.fetch(event.request)\n  })\n  .add('itty-router', async () => {\n    await ittyRouter.handle(event.request)\n  })\n  .add('sunder', async () => {\n    await sunderApp.handle(event)\n  })\n  .add('worktop', async () => {\n    await worktopRouter.run(event)\n  })\n  .on('cycle', (event) => {\n    console.log(String(event.target))\n  })\n  .on('complete', function () {\n    console.log(`Fastest is ${this.filter('fastest').map('name')}`)\n  })\n  .run({ async: true })\n"
  },
  {
    "path": "benchmarks/handle-event/package.json",
    "content": "{\n  \"name\": \"hono-benchmark\",\n  \"version\": \"0.0.1\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n    \"start\": \"node --experimental-specifier-resolution=node index.js\"\n  },\n  \"type\": \"module\",\n  \"author\": \"Yusuke Wada <yusuke@kamawada.com> (https://github.com/yusukebe)\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"benchmark\": \"^2.1.4\",\n    \"edge-mock\": \"^0.0.15\",\n    \"itty-router\": \"^3.0.11\",\n    \"node-fetch\": \"^3.2.10\",\n    \"sunder\": \"^0.10.1\",\n    \"worktop\": \"^0.7.3\"\n  }\n}\n"
  },
  {
    "path": "benchmarks/http-server/.gitignore",
    "content": ".benchmark-temp/\nbenchmark-results.md\n"
  },
  {
    "path": "benchmarks/http-server/README.md",
    "content": "# Hono HTTP Benchmark\n\nHTTP performance benchmarking tool that compares main vs current versions.\n\n## Usage\n\n### In Pull Requests\n\nHTTP benchmarks are automatically run for each pull request and results are commented on the PR.\n\n### Local Development\n\n```bash\ncd benchmarks/http-server\nbun run benchmark.ts\n```\n\n## Prerequisites\n\n- Bun v1.0+\n- bombardier (`brew install bombardier` on macOS)\n"
  },
  {
    "path": "benchmarks/http-server/benchmark.ts",
    "content": "/**\n * Hono HTTP Performance Benchmark\n *\n * Inspired by https://github.com/SaltyAom/bun-http-framework-benchmark\n *\n * Usage:\n *   bun run benchmark.ts [options]\n *\n * Options:\n *   --baseline=<ref>    Git reference for baseline (default: main)\n *   --target=<ref>      Git reference for target (default: current)\n *   --runs=<number>     Number of benchmark runs (default: 1)\n *   --duration=<number> Duration of each test in seconds (default: 10)\n *   --skip-tests        Skip endpoint validation tests\n */\n\nimport { spawn } from 'node:child_process'\nimport { existsSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'\nimport { join } from 'node:path'\n\n// Configuration from command line arguments\nconst baseline =\n  process.argv.find((arg) => arg.startsWith('--baseline='))?.split('=')[1] || 'origin/main'\nconst target = process.argv.find((arg) => arg.startsWith('--target='))?.split('=')[1] || 'current'\nconst runs = parseInt(process.argv.find((arg) => arg.startsWith('--runs='))?.split('=')[1] || '1')\nconst duration = parseInt(\n  process.argv.find((arg) => arg.startsWith('--duration='))?.split('=')[1] || '10'\n)\nconst concurrency = 500\nconst skipTests = process.argv.includes('--skip-tests')\n\nconst SCRIPT_DIR = import.meta.dirname\nconst TEMP_DIR = join(SCRIPT_DIR, '.benchmark-temp')\nconst HONO_ROOT = join(SCRIPT_DIR, '../..')\n\n// Test app template (embedded to avoid file dependency issues)\nconst getAppTemplate = () => `import { Hono } from './src/index.ts'\nimport { RegExpRouter } from './src/router/reg-exp-router/index.ts'\n\nconst app = new Hono({ router: new RegExpRouter() })\n\napp\n  .get('/', (c) => c.text('Hi'))\n  .post('/json', (c) => c.req.json().then(c.json))\n  .get('/id/:id', (c) => {\n    const id = c.req.param('id')\n    const name = c.req.query('name')\n    c.header('x-powered-by', 'benchmark')\n    return c.text(\\`\\${id} \\${name}\\`)\n  })\n\nexport default app`\n\nconst sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))\n\nconst runCommand = async (command: string, cwd: string) => {\n  const parts = command.split(' ')\n  const proc = spawn(parts[0], parts.slice(1), { cwd })\n\n  let stdout = ''\n  let stderr = ''\n\n  proc.stdout.on('data', (data) => {\n    stdout += data\n  })\n  proc.stderr.on('data', (data) => {\n    stderr += data\n  })\n\n  const exitCode = await new Promise<number>((resolve) => {\n    proc.on('close', resolve)\n  })\n\n  if (exitCode !== 0) {\n    console.error(`Command failed: ${command}`)\n    console.error(`Exit code: ${exitCode}`)\n    console.error(`Stdout: ${stdout}`)\n    console.error(`Stderr: ${stderr}`)\n    throw new Error(`Command failed: ${command}`)\n  }\n\n  return { stdout, stderr }\n}\n\nconst setupTemp = () => {\n  if (existsSync(TEMP_DIR)) {\n    rmSync(TEMP_DIR, { recursive: true })\n  }\n  mkdirSync(TEMP_DIR, { recursive: true })\n  writeFileSync(join(TEMP_DIR, 'body.json'), '{\"hello\":\"world\"}')\n}\n\nconst buildVersion = async (version: string, name: string) => {\n  console.log(`📦 Preparing ${name} (${version})...`)\n\n  let needsRestore = false\n  let stashRef = ''\n\n  if (version === 'current') {\n    // No build needed - use src directly\n  } else {\n    // Ensure we have the latest remote refs\n    await runCommand('git fetch origin', HONO_ROOT)\n\n    try {\n      const stashResult = await runCommand('git stash push -m \"benchmark-temp\"', HONO_ROOT)\n      needsRestore = stashResult.stdout.includes('Saved working directory')\n      if (needsRestore) {\n        stashRef = 'stash@{0}'\n      }\n    } catch {\n      // No changes to stash\n    }\n\n    await runCommand(`git checkout ${version}`, HONO_ROOT)\n    await runCommand('bun install --frozen-lockfile', HONO_ROOT)\n    // No build needed - use src directly\n  }\n\n  const versionDir = join(TEMP_DIR, name)\n  mkdirSync(versionDir, { recursive: true })\n  await runCommand(`cp -r ${HONO_ROOT}/src ${versionDir}/src`, process.cwd())\n\n  const appPath = join(versionDir, 'app.ts')\n  writeFileSync(appPath, getAppTemplate())\n\n  // Test endpoints (optional)\n  if (!skipTests) {\n    console.log(`🧪 Testing endpoints for ${name}...`)\n    const server = spawn('bun', [appPath], {\n      cwd: TEMP_DIR,\n      env: { ...process.env, NODE_ENV: 'production' },\n    })\n    await sleep(2000)\n\n    try {\n      const res1 = await fetch('http://127.0.0.1:3000/')\n      if ((await res1.text()) !== 'Hi') {\n        throw new Error('[GET /] test failed')\n      }\n\n      const res2 = await fetch('http://127.0.0.1:3000/id/1?name=bun')\n      if (res2.headers.get('x-powered-by') !== 'benchmark' || (await res2.text()) !== '1 bun') {\n        throw new Error('[GET /id/:id] test failed')\n      }\n\n      const body = JSON.stringify({ hello: 'world' })\n      const res3 = await fetch('http://127.0.0.1:3000/json', {\n        method: 'POST',\n        body,\n        headers: { 'content-type': 'application/json', 'content-length': body.length.toString() },\n      })\n      if (\n        !res3.headers.get('content-type')?.includes('application/json') ||\n        (await res3.text()) !== body\n      ) {\n        throw new Error('[POST /json] test failed')\n      }\n\n      console.log(`  ✅ Tests passed for ${name}`)\n    } finally {\n      server.kill()\n      await sleep(1000)\n    }\n  } else {\n    console.log(`  ⏭️ Skipping endpoint tests for ${name}`)\n  }\n\n  // Restore git state\n  if (version !== 'current' && needsRestore) {\n    await runCommand('git checkout -', HONO_ROOT)\n    await runCommand(`git stash pop ${stashRef}`, HONO_ROOT)\n  } else if (version !== 'current') {\n    await runCommand('git checkout -', HONO_ROOT)\n  }\n\n  return appPath\n}\n\nconst runBenchmark = async (appPath: string, name: string) => {\n  console.log(`⚡ Running HTTP benchmark for ${name}...`)\n\n  const bodyFile = join(TEMP_DIR, 'body.json')\n  const commands = [\n    `bombardier --fasthttp -c ${concurrency} -d ${duration}s http://127.0.0.1:3000/`,\n    `bombardier --fasthttp -c ${concurrency} -d ${duration}s http://127.0.0.1:3000/id/1?name=bun`,\n    `bombardier --fasthttp -c ${concurrency} -d ${duration}s -m POST -H Content-Type:application/json -f ${bodyFile} http://127.0.0.1:3000/json`,\n  ]\n\n  const allRuns: number[][] = []\n\n  for (let run = 0; run < runs; run++) {\n    console.log(`  Run ${run + 1}/${runs}`)\n\n    const server = spawn('bun', [appPath], {\n      cwd: TEMP_DIR,\n      env: { ...process.env, NODE_ENV: 'production' },\n    })\n    await sleep(1000)\n\n    const runResults: number[] = []\n\n    try {\n      for (const command of commands) {\n        const result = await runCommand(command, process.cwd())\n        console.log(result.stdout)\n\n        const match = result.stdout.match(/Reqs\\/sec\\s+(\\d+[.|,]\\d+)/)\n        if (match) {\n          runResults.push(parseFloat(match[1].replace(',', '')))\n        } else {\n          console.log('❌ Failed to parse result')\n          runResults.push(0)\n        }\n      }\n    } finally {\n      server.kill()\n      await sleep(500)\n    }\n\n    allRuns.push(runResults)\n  }\n\n  const average = (arr: number[]) => arr.reduce((a, b) => a + b, 0) / arr.length\n\n  const ping = average(allRuns.map((run) => run[0]))\n  const query = average(allRuns.map((run) => run[1]))\n  const body = average(allRuns.map((run) => run[2]))\n  const overall = (ping + query + body) / 3\n\n  return { name, average: overall, ping, query, body, runs: allRuns.map((run) => average(run)) }\n}\n\nconst main = async () => {\n  console.log('🏁 Hono HTTP Benchmark')\n  console.log('======================')\n  console.log(`Baseline: ${baseline}`)\n  console.log(`Target: ${target}`)\n  console.log(`Runs: ${runs}`)\n  console.log(`Duration: ${duration}s`)\n  console.log(`Concurrency: ${concurrency}`)\n  console.log(`Skip Tests: ${skipTests}`)\n  console.log('')\n\n  setupTemp()\n\n  try {\n    // Compare baseline vs target\n    const baselinePath = await buildVersion(baseline, 'baseline')\n    const targetPath = await buildVersion(target, 'target')\n\n    const baselineResult = await runBenchmark(baselinePath, 'baseline')\n    const targetResult = await runBenchmark(targetPath, 'target')\n\n    // Calculate changes\n    const calculateChange = (target: number, baseline: number) =>\n      (((target - baseline) / baseline) * 100).toFixed(2)\n\n    const changes = {\n      average: calculateChange(targetResult.average, baselineResult.average),\n      ping: calculateChange(targetResult.ping, baselineResult.ping),\n      query: calculateChange(targetResult.query, baselineResult.query),\n      body: calculateChange(targetResult.body, baselineResult.body),\n    }\n\n    // Format numbers\n    const format = (num: number) => num.toFixed(2).replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',')\n    const formatChange = (change: string) => (Number(change) >= 0 ? '+' : '') + change + '%'\n\n    // Generate table data\n    const rows = [\n      {\n        framework: `hono (${baseline})`,\n        runtime: 'bun',\n        average: format(baselineResult.average),\n        ping: format(baselineResult.ping),\n        query: format(baselineResult.query),\n        body: format(baselineResult.body),\n      },\n      {\n        framework: `hono (${target})`,\n        runtime: 'bun',\n        average: format(targetResult.average),\n        ping: format(targetResult.ping),\n        query: format(targetResult.query),\n        body: format(targetResult.body),\n      },\n      {\n        framework: 'Change',\n        runtime: '',\n        average: formatChange(changes.average),\n        ping: formatChange(changes.ping),\n        query: formatChange(changes.query),\n        body: formatChange(changes.body),\n      },\n    ]\n\n    const table = [\n      '| Framework | Runtime | Average | Ping | Query | Body |',\n      '| --- | --- | --- | --- | --- | --- |',\n      ...rows.map(\n        (row) =>\n          `| ${row.framework} | ${row.runtime} | ${row.average} | ${row.ping} | ${row.query} | ${row.body} |`\n      ),\n    ]\n\n    // Console output\n    console.log('')\n    table.forEach((line) => console.log(line))\n    console.log('')\n\n    // Markdown output\n    const markdownOutput = ['## HTTP Performance Benchmark', '', ...table].join('\\n')\n\n    writeFileSync(join(SCRIPT_DIR, 'benchmark-results.md'), markdownOutput)\n  } catch (error) {\n    console.error('❌ Benchmark failed:', error)\n    throw error\n  } finally {\n    if (existsSync(TEMP_DIR)) {\n      rmSync(TEMP_DIR, { recursive: true })\n    }\n  }\n}\n\nmain()\n"
  },
  {
    "path": "benchmarks/jsx/package.json",
    "content": "{\n  \"name\": \"jsx\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"bench:node\": \"esbuild --bundle src/benchmark.ts | node\",\n    \"bench:bun\": \"bun run src/benchmark.ts\",\n    \"bench:react-jsx:node\": \"esbuild --bundle src/react-jsx/benchmark.ts | node\",\n    \"compare-bundle-size\": \"esbuild --minify --minify-syntax --tree-shaking=true --bundle src/{hono,react,preact,nano}.ts --outdir=dist\"\n  },\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"esbuild\": \"^0.19.8\",\n    \"node-html-parser\": \"^6.1.11\"\n  },\n  \"dependencies\": {\n    \"@types/benchmark\": \"^2.1.5\",\n    \"@types/react\": \"^18.2.40\",\n    \"@types/react-dom\": \"^18.2.17\",\n    \"benchmark\": \"^2.1.4\",\n    \"hono\": \"^3.10.4\",\n    \"nano-jsx\": \"^0.1.0\",\n    \"preact\": \"^10.19.2\",\n    \"preact-render-to-string\": \"^6.3.1\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"typescript\": \"^5.3.2\"\n  }\n}\n"
  },
  {
    "path": "benchmarks/jsx/src/benchmark.ts",
    "content": "import { Suite } from 'benchmark'\nimport { parse } from 'node-html-parser'\n\nimport { render as renderHono } from './hono'\nimport { render as renderNano } from './nano'\nimport { render as renderPreact } from './preact'\nimport { render as renderReact } from './react'\n\nconst suite = new Suite()\n\n;[renderHono, renderReact, renderPreact, renderNano].forEach((render) => {\n  const html = render()\n  const doc = parse(html)\n  if (doc.querySelector('p#c').textContent !== '3\\nc') {\n    throw new Error('Invalid output')\n  }\n})\n\nsuite\n  .add('Hono', () => {\n    renderHono()\n  })\n  .add('React', () => {\n    renderReact()\n  })\n  .add('Preact', () => {\n    renderPreact()\n  })\n  .add('Nano', () => {\n    renderNano()\n  })\n  .on('cycle', (ev) => {\n    console.log(String(ev.target))\n  })\n  .on('complete', (ev) => {\n    console.log(`Fastest is ${ev.currentTarget.filter('fastest').map('name')}`)\n  })\n  .run({ async: true })\n"
  },
  {
    "path": "benchmarks/jsx/src/hono.ts",
    "content": "import { jsx, Fragment } from '../../../src/jsx'\nimport { buildPage } from './page'\n\nexport const render = () => buildPage({ jsx, Fragment })().toString()\n"
  },
  {
    "path": "benchmarks/jsx/src/nano.ts",
    "content": "import { h, Fragment, renderSSR } from 'nano-jsx'\nimport { buildPage } from './page'\n\nexport const render = () => renderSSR(buildPage({ jsx: h, Fragment }))\n"
  },
  {
    "path": "benchmarks/jsx/src/page-react.tsx",
    "content": "/** @jsx jsx */\n/** @jsxFrag Fragment */\n\nexport const buildPage = ({ jsx, Fragment }: { jsx: any; Fragment: any }) => {\n  const Content = () => (\n    <>\n      <p id='a' className='class-name'>\n        1<br />a\n      </p>\n      <p id='b' className='class-name'>\n        2<br />b\n      </p>\n      <div dangerouslySetInnerHTML={{ __html: '<p id=\"c\" class=\"class-name\">3<br/>c</p>' }} />\n      {null}\n      {undefined}\n    </>\n  )\n\n  const Form = () => (\n    <form>\n      <input type='text' value='1234567890 < 1234567891' readOnly tabIndex={1} />\n      <input type='checkbox' value='1234567890 < 1234567891' defaultChecked={true} tabIndex={2} />\n      <input type='checkbox' value='1234567890 < 1234567891' defaultChecked={true} tabIndex={3} />\n      <input type='checkbox' value='1234567890 < 1234567891' defaultChecked={false} tabIndex={4} />\n      <input type='checkbox' value='1234567890 < 1234567891' defaultChecked={false} tabIndex={5} />\n    </form>\n  )\n\n  return () => (\n    <html>\n      <body>\n        <Content />\n        <Form />\n      </body>\n    </html>\n  )\n}\n"
  },
  {
    "path": "benchmarks/jsx/src/page.tsx",
    "content": "/** @jsx jsx */\n/** @jsxFrag Fragment */\n\nexport const buildPage = ({ jsx, Fragment }: { jsx: any; Fragment: any }) => {\n  const Content = () => (\n    <>\n      <p id='a' class='class-name'>\n        1<br />a\n      </p>\n      <p id='b' class='class-name'>\n        2<br />b\n      </p>\n      <div dangerouslySetInnerHTML={{ __html: '<p id=\"c\" class=\"class-name\">3<br/>c</p>' }} />\n      {null}\n      {undefined}\n    </>\n  )\n\n  const Form = () => (\n    <form>\n      <input type='text' value='1234567890 < 1234567891' readonly tabindex={1} />\n      <input type='checkbox' value='1234567890 < 1234567891' checked={true} tabindex={2} />\n      <input type='checkbox' value='1234567890 < 1234567891' checked={true} tabindex={3} />\n      <input type='checkbox' value='1234567890 < 1234567891' checked={false} tabindex={4} />\n      <input type='checkbox' value='1234567890 < 1234567891' checked={false} tabindex={5} />\n    </form>\n  )\n\n  return () => (\n    <html>\n      <body>\n        <Content />\n        <Form />\n      </body>\n    </html>\n  )\n}\n"
  },
  {
    "path": "benchmarks/jsx/src/preact.ts",
    "content": "import { h, Fragment } from 'preact'\nimport { renderToString } from 'preact-render-to-string'\nimport { buildPage } from './page'\n\nexport const render = () => renderToString(buildPage({ jsx: h, Fragment })() as any)\n"
  },
  {
    "path": "benchmarks/jsx/src/react-jsx/benchmark.ts",
    "content": "import { Suite } from 'benchmark'\nimport { parse } from 'node-html-parser'\n\nimport { render as renderHono } from './hono'\nimport { render as renderNano } from './nano'\nimport { render as renderPreact } from './preact'\nimport { render as renderReact } from './react'\n\nconst suite = new Suite()\n\n;[renderHono, renderReact, renderPreact, renderNano].forEach((render) => {\n  const html = render()\n  const doc = parse(html)\n  if (doc.querySelector('p#c').textContent !== '3\\nc') {\n    throw new Error('Invalid output')\n  }\n})\n\nsuite\n  .add('Hono', () => {\n    renderHono()\n  })\n  .add('React', () => {\n    renderReact()\n  })\n  .add('Preact', () => {\n    renderPreact()\n  })\n  .add('Nano', () => {\n    renderNano()\n  })\n  .on('cycle', (ev) => {\n    console.log(String(ev.target))\n  })\n  .on('complete', (ev) => {\n    console.log(`Fastest is ${ev.currentTarget.filter('fastest').map('name')}`)\n  })\n  .run({ async: true })\n"
  },
  {
    "path": "benchmarks/jsx/src/react-jsx/hono.ts",
    "content": "import { buildPage } from './page-hono'\n\nexport const render = () => buildPage()().toString()\n"
  },
  {
    "path": "benchmarks/jsx/src/react-jsx/nano.ts",
    "content": "import { renderSSR } from 'nano-jsx'\nimport { buildPage } from './page-nano.tsx'\n\nexport const render = () => renderSSR(buildPage())\n"
  },
  {
    "path": "benchmarks/jsx/src/react-jsx/page-hono.tsx",
    "content": "/** @jsxImportSource ../../../../src/jsx **/\n\nexport const buildPage = () => {\n  const Content = () => (\n    <>\n      <p id='a' class='class-name'>\n        1<br />a\n      </p>\n      <p id='b' class='class-name'>\n        2<br />b\n      </p>\n      <div dangerouslySetInnerHTML={{ __html: '<p id=\"c\" class=\"class-name\">3<br/>c</p>' }} />\n      {null}\n      {undefined}\n    </>\n  )\n\n  const Form = () => (\n    <form>\n      <input type='text' value='1234567890 < 1234567891' readonly tabindex={1} />\n      <input type='checkbox' value='1234567890 < 1234567891' checked={true} tabindex={2} />\n      <input type='checkbox' value='1234567890 < 1234567891' checked={true} tabindex={3} />\n      <input type='checkbox' value='1234567890 < 1234567891' checked={false} tabindex={4} />\n      <input type='checkbox' value='1234567890 < 1234567891' checked={false} tabindex={5} />\n    </form>\n  )\n\n  return () => (\n    <html>\n      <body>\n        <Content />\n        <Form />\n      </body>\n    </html>\n  )\n}\n"
  },
  {
    "path": "benchmarks/jsx/src/react-jsx/page-nano.tsx",
    "content": "/** @jsxImportSource nano-jsx/lib **/\n\nexport const buildPage = () => {\n  const Content = () => (\n    <>\n      <p id='a' class='class-name'>\n        1<br />a\n      </p>\n      <p id='b' class='class-name'>\n        2<br />b\n      </p>\n      <div dangerouslySetInnerHTML={{ __html: '<p id=\"c\" class=\"class-name\">3<br/>c</p>' }} />\n      {null}\n      {undefined}\n    </>\n  )\n\n  const Form = () => (\n    <form>\n      <input type='text' value='1234567890 < 1234567891' readonly tabindex={1} />\n      <input type='checkbox' value='1234567890 < 1234567891' checked={true} tabindex={2} />\n      <input type='checkbox' value='1234567890 < 1234567891' checked={true} tabindex={3} />\n      <input type='checkbox' value='1234567890 < 1234567891' checked={false} tabindex={4} />\n      <input type='checkbox' value='1234567890 < 1234567891' checked={false} tabindex={5} />\n    </form>\n  )\n\n  return () => (\n    <html>\n      <body>\n        <Content />\n        <Form />\n      </body>\n    </html>\n  )\n}\n"
  },
  {
    "path": "benchmarks/jsx/src/react-jsx/page-preact.tsx",
    "content": "/** @jsxImportSource preact **/\n\nexport const buildPage = () => {\n  const Content = () => (\n    <>\n      <p id='a' class='class-name'>\n        1<br />a\n      </p>\n      <p id='b' class='class-name'>\n        2<br />b\n      </p>\n      <div dangerouslySetInnerHTML={{ __html: '<p id=\"c\" class=\"class-name\">3<br/>c</p>' }} />\n      {null}\n      {undefined}\n    </>\n  )\n\n  const Form = () => (\n    <form>\n      <input type='text' value='1234567890 < 1234567891' readonly tabindex={1} />\n      <input type='checkbox' value='1234567890 < 1234567891' checked={true} tabindex={2} />\n      <input type='checkbox' value='1234567890 < 1234567891' checked={true} tabindex={3} />\n      <input type='checkbox' value='1234567890 < 1234567891' checked={false} tabindex={4} />\n      <input type='checkbox' value='1234567890 < 1234567891' checked={false} tabindex={5} />\n    </form>\n  )\n\n  return () => (\n    <html>\n      <body>\n        <Content />\n        <Form />\n      </body>\n    </html>\n  )\n}\n"
  },
  {
    "path": "benchmarks/jsx/src/react-jsx/page-react.tsx",
    "content": "/** @jsxImportSource react **/\n\nexport const buildPage = () => {\n  const Content = () => (\n    <>\n      <p id='a' className='class-name'>\n        1<br />a\n      </p>\n      <p id='b' className='class-name'>\n        2<br />b\n      </p>\n      <div dangerouslySetInnerHTML={{ __html: '<p id=\"c\" class=\"class-name\">3<br/>c</p>' }} />\n      {null}\n      {undefined}\n    </>\n  )\n\n  const Form = () => (\n    <form>\n      <input type='text' value='1234567890 < 1234567891' readOnly tabIndex={1} />\n      <input type='checkbox' value='1234567890 < 1234567891' defaultChecked={true} tabIndex={2} />\n      <input type='checkbox' value='1234567890 < 1234567891' defaultChecked={true} tabIndex={3} />\n      <input type='checkbox' value='1234567890 < 1234567891' defaultChecked={false} tabIndex={4} />\n      <input type='checkbox' value='1234567890 < 1234567891' defaultChecked={false} tabIndex={5} />\n    </form>\n  )\n\n  return () => (\n    <html>\n      <body>\n        <Content />\n        <Form />\n      </body>\n    </html>\n  )\n}\n"
  },
  {
    "path": "benchmarks/jsx/src/react-jsx/preact.ts",
    "content": "import { renderToString } from 'preact-render-to-string'\nimport { buildPage } from './page-preact'\n\nexport const render = () => renderToString(buildPage()() as any)\n"
  },
  {
    "path": "benchmarks/jsx/src/react-jsx/react.ts",
    "content": "import { renderToString } from 'react-dom/server'\nimport { buildPage } from './page-react.tsx'\n\nexport const render = () => renderToString(buildPage()() as any)\n"
  },
  {
    "path": "benchmarks/jsx/src/react-jsx/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"jsx\": \"react-jsx\"\n  }\n}\n"
  },
  {
    "path": "benchmarks/jsx/src/react.ts",
    "content": "import { createElement, Fragment } from 'react'\nimport { renderToString } from 'react-dom/server'\nimport { buildPage } from './page-react'\n\nexport const render = () => renderToString(buildPage({ jsx: createElement, Fragment })() as any)\n"
  },
  {
    "path": "benchmarks/jsx/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"jsx\": \"react\"\n  }\n}\n"
  },
  {
    "path": "benchmarks/query-param/package.json",
    "content": "{\n  \"scripts\": {\n    \"bench:node\": \"tsx ./src/bench.mts\",\n    \"bench:bun\": \"bun run ./src/bench.mts\"\n  },\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@types/qs\": \"^6.9.17\",\n    \"tsx\": \"^3.12.2\"\n  },\n  \"dependencies\": {\n    \"fast-querystring\": \"^1.1.1\",\n    \"mitata\": \"^0.1.6\",\n    \"qs\": \"^6.13.0\"\n  }\n}\n"
  },
  {
    "path": "benchmarks/query-param/src/bench.mts",
    "content": "import { run, group, bench } from 'mitata'\nimport { getQueryStrings } from '../../../src/utils/url'\nimport fastQuerystring from './fast-querystring.mts'\nimport hono from './hono.mts'\nimport qs from './qs.mts'\n;[\n  {\n    url: 'http://example.com/?page=1',\n    key: 'page',\n  },\n  {\n    url: 'http://example.com/?url=http://example.com&page=1',\n    key: 'page',\n  },\n  {\n    url: 'http://example.com/?page=1',\n    key: undefined,\n  },\n  {\n    url: 'http://example.com/?url=http://example.com&page=1',\n    key: undefined,\n  },\n  {\n    url: 'http://example.com/?url=http://example.com/very/very/deep/path/to/something&search=very-long-search-string',\n    key: undefined,\n  },\n  {\n    url: 'http://example.com/?search=Hono+is+a+small,+simple,+and+ultrafast+web+framework+for+the+Edge.&page=1',\n    key: undefined,\n  },\n  {\n    url: 'http://example.com/?a=1&b=2&c=3&d=4&e=5&f=6&g=7&h=8&i=9&j=10',\n    key: undefined,\n  },\n].forEach((data) => {\n  const { url, key } = data\n\n  group(JSON.stringify(data), () => {\n    bench('hono', () => hono(url, key))\n    bench('fastQuerystring', () => fastQuerystring(url, key))\n    bench('qs', () => qs(url, key))\n    bench('URLSearchParams', () => {\n      const params = new URLSearchParams(getQueryStrings(url))\n      if (key) {\n        return params.get(key)\n      }\n      const obj = {}\n      for (const [k, v] of params) {\n        obj[k] = v\n      }\n      return obj\n    })\n  })\n})\n\nrun()\n"
  },
  {
    "path": "benchmarks/query-param/src/fast-querystring.mts",
    "content": "import { parse } from 'fast-querystring'\n\nconst getQueryStringFromURL = (url: string): string => {\n  const queryIndex = url.indexOf('?', 8)\n  const result = queryIndex !== -1 ? url.slice(queryIndex + 1) : ''\n  return result\n}\n\nexport default (url, key?) => {\n  const data = parse(getQueryStringFromURL(url))\n  return key !== undefined ? data[key] : data \n}\n"
  },
  {
    "path": "benchmarks/query-param/src/hono.mts",
    "content": "import { getQueryParam } from '../../../src/utils/url'\n\nexport default (url, key?) => {\n  return getQueryParam(url, key)\n}\n"
  },
  {
    "path": "benchmarks/query-param/src/qs.mts",
    "content": "import qs from 'qs'\n\nconst getQueryStringFromURL = (url: string): string => {\n  const queryIndex = url.indexOf('?', 8)\n  const result = queryIndex !== -1 ? url.slice(queryIndex + 1) : ''\n  return result\n}\n\nexport default (url, key?) => {\n  const data = qs.parse(getQueryStringFromURL(url))\n  return key !== undefined ? data[key] : data\n}\n"
  },
  {
    "path": "benchmarks/routers/README.md",
    "content": "# Router benchmarks\n\nBenchmark of the most commonly used HTTP routers.\n\nTested routes:\n\n- [find-my-way](https://github.com/delvedor/find-my-way)\n- [express](https://www.npmjs.com/package/express)\n- [koa-router](https://github.com/alexmingoia/koa-router)\n- [koa-tree-router](https://github.com/steambap/koa-tree-router)\n- [trek-router](https://www.npmjs.com/package/trek-router)\n- [@medley/router](https://www.npmjs.com/package/@medley/router)\n- [Hono RegExpRouter](https://github.com/honojs/hono)\n- [Hono TrieRouter](https://github.com/honojs/hono)\n\nInstall:\n\n```\nbun install --frozen-lockfile\n```\n\nFor Node.js:\n\n```\nbun run bench:node\n```\n\nFor Bun:\n\n```\nbun run bench:bun\n```\n\nThis project is heavily impaired by [delvedor/router-benchmark](https://github.com/delvedor/router-benchmark)\n\n## License\n\nMIT\n"
  },
  {
    "path": "benchmarks/routers/package.json",
    "content": "{\n  \"scripts\": {\n    \"bench:node\": \"tsx ./src/bench.mts\",\n    \"bench:bun\": \"bun run ./src/bench.mts\",\n    \"bench-includes-init:node\": \"tsx ./src/bench-includes-init.mts\",\n    \"bench-includes-init:bun\": \"bun run ./src/bench-includes-init.mts\"\n  },\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"tsx\": \"^4.20.5\"\n  },\n  \"dependencies\": {\n    \"@medley/router\": \"^0.2.1\",\n    \"express\": \"^4.21.2\",\n    \"find-my-way\": \"^9.3.0\",\n    \"koa-router\": \"^12.0.1\",\n    \"koa-tree-router\": \"^0.13.1\",\n    \"memoirist\": \"^0.4.0\",\n    \"mitata\": \"^1.0.34\",\n    \"radix3\": \"^1.1.2\",\n    \"rou3\": \"^0.7.3\",\n    \"trek-router\": \"^1.2.0\"\n  }\n}\n"
  },
  {
    "path": "benchmarks/routers/src/bench-includes-init.mts",
    "content": "import MedleyRouter from '@medley/router'\nimport type { HTTPMethod } from 'find-my-way'\nimport findMyWay from 'find-my-way'\nimport KoaRouter from 'koa-tree-router'\nimport { run, bench, group } from 'mitata'\nimport TrekRouter from 'trek-router'\nimport { LinearRouter } from '../../../src/router/linear-router/index.ts'\nimport {\n  RegExpRouter,\n  PreparedRegExpRouter,\n  buildInitParams,\n} from '../../../src/router/reg-exp-router/index.ts'\nimport { TrieRouter } from '../../../src/router/trie-router/index.ts'\nimport type { Route } from './tool.mts'\nimport { routes } from './tool.mts'\n\nconst preparedParams = buildInitParams({\n  paths: routes.map((r) => r.path),\n})\n\nconst benchRoutes: (Route & { name: string })[] = [\n  {\n    name: 'short static',\n    method: 'GET',\n    path: '/user',\n  },\n  {\n    name: 'static with same radix',\n    method: 'GET',\n    path: '/user/comments',\n  },\n  {\n    name: 'dynamic route',\n    method: 'GET',\n    path: '/user/lookup/username/hey',\n  },\n  {\n    name: 'mixed static dynamic',\n    method: 'GET',\n    path: '/event/abcd1234/comments',\n  },\n  {\n    name: 'post',\n    method: 'POST',\n    path: '/event/abcd1234/comment',\n  },\n  {\n    name: 'long static',\n    method: 'GET',\n    path: '/very/deeply/nested/route/hello/there',\n  },\n  {\n    name: 'wildcard',\n    method: 'GET',\n    path: '/static/index.html',\n  },\n]\n\nfor (const benchRoute of benchRoutes) {\n  group(`${benchRoute.method} ${benchRoute.path}`, () => {\n    bench('RegExpRouter', () => {\n      const router = new RegExpRouter()\n      for (const route of routes) {\n        router.add(route.method, route.path, () => {})\n      }\n      router.match(benchRoute.method, benchRoute.path)\n    })\n    bench('PreparedRegExpRouter', () => {\n      const router = new PreparedRegExpRouter(preparedParams[0], preparedParams[1])\n      for (const route of routes) {\n        router.add(route.method, route.path, () => {})\n      }\n      router.match(benchRoute.method, benchRoute.path)\n    })\n    bench('TrieRouter', () => {\n      const router = new TrieRouter()\n      for (const route of routes) {\n        router.add(route.method, route.path, () => {})\n      }\n      router.match(benchRoute.method, benchRoute.path)\n    })\n    bench('LinearRouter', () => {\n      const router = new LinearRouter()\n      for (const route of routes) {\n        router.add(route.method, route.path, () => {})\n      }\n      router.match(benchRoute.method, benchRoute.path)\n    })\n    bench('MedleyRouter', () => {\n      const router = new MedleyRouter()\n      for (const route of routes) {\n        const store = router.register(route.path)\n        store[route.method] = () => {}\n      }\n      const match = router.find(benchRoute.path)\n      match.store[benchRoute.method] // get handler\n    })\n    bench('FindMyWay', () => {\n      const router = findMyWay()\n      for (const route of routes) {\n        router.on(route.method as HTTPMethod, route.path, () => {})\n      }\n      router.find(benchRoute.method as HTTPMethod, benchRoute.path)\n    })\n    bench('KoaTreeRouter', () => {\n      const router = new KoaRouter()\n      for (const route of routes) {\n        router.on(route.method, route.path.replace('*', '*foo'), () => {})\n      }\n      // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n      // @ts-ignore\n      router.find(benchRoute.method, benchRoute.path)\n    })\n    bench('TrekRouter', () => {\n      const router = new TrekRouter()\n      for (const route of routes) {\n        router.add(route.method, route.path, () => {})\n      }\n      router.find(benchRoute.method, benchRoute.path)\n    })\n  })\n}\nawait run()\n"
  },
  {
    "path": "benchmarks/routers/src/bench.mts",
    "content": "import { run, bench, group, summary } from 'mitata'\nimport { expressRouter } from './express.mts'\nimport { findMyWayRouter } from './find-my-way.mts'\nimport { regExpRouter, trieRouter, patternRouter } from './hono.mts'\nimport { koaRouter } from './koa-router.mts'\nimport { koaTreeRouter } from './koa-tree-router.mts'\nimport { medleyRouter } from './medley-router.mts'\nimport { memoiristRouter } from './memoirist.mts'\nimport { radix3Router } from './radix3.mts'\nimport { rou3Router } from './rou3.mts'\nimport type { Route, RouterInterface } from './tool.mts'\nimport { trekRouter } from './trek-router.mts'\n\nconst routers: RouterInterface[] = [\n  regExpRouter,\n  trieRouter,\n  patternRouter,\n  medleyRouter,\n  findMyWayRouter,\n  koaTreeRouter,\n  trekRouter,\n  expressRouter,\n  koaRouter,\n  radix3Router,\n  memoiristRouter,\n  rou3Router,\n]\n\nmedleyRouter.match({ method: 'GET', path: '/user' })\n\nconst routes: (Route & { name: string })[] = [\n  {\n    name: 'short static',\n    method: 'GET',\n    path: '/user',\n  },\n  {\n    name: 'static with same radix',\n    method: 'GET',\n    path: '/user/comments',\n  },\n  {\n    name: 'dynamic route',\n    method: 'GET',\n    path: '/user/lookup/username/hey',\n  },\n  {\n    name: 'mixed static dynamic',\n    method: 'GET',\n    path: '/event/abcd1234/comments',\n  },\n  {\n    name: 'post',\n    method: 'POST',\n    path: '/event/abcd1234/comment',\n  },\n  {\n    name: 'long static',\n    method: 'GET',\n    path: '/very/deeply/nested/route/hello/there',\n  },\n  {\n    name: 'wildcard',\n    method: 'GET',\n    path: '/static/index.html',\n  },\n]\n\nfor (const route of routes) {\n  summary(() => {\n    group(`${route.name} - ${route.method} ${route.path}`, () => {\n      for (const router of routers) {\n        bench(router.name, async () => {\n          router.match(route)\n        })\n      }\n    })\n  })\n}\n\ngroup('all together', () => {\n  summary(() => {\n    for (const router of routers) {\n      bench(router.name, async () => {\n        for (const route of routes) {\n          router.match(route)\n        }\n      })\n    }\n  })\n})\n\nawait run()\n"
  },
  {
    "path": "benchmarks/routers/src/express.mts",
    "content": "import routerFunc from 'express/lib/router/index.js'\nimport type { RouterInterface } from './tool.mts'\nimport { routes, handler } from './tool.mts'\n\nconst router = routerFunc()\nconst name = 'express (WARNING: includes handling)'\n\nfor (const route of routes) {\n  if (route.method === 'GET') {\n    router.route(route.path).get(handler)\n  } else {\n    router.route(route.path).post(handler)\n  }\n}\n\nexport const expressRouter: RouterInterface = {\n  name,\n  match: (route) => {\n    router.handle({ method: route.method, url: route.path })\n  },\n}\n"
  },
  {
    "path": "benchmarks/routers/src/find-my-way.mts",
    "content": "import type { HTTPMethod } from 'find-my-way'\nimport findMyWay from 'find-my-way'\nimport type { RouterInterface } from './tool.mts'\nimport { routes, handler } from './tool.mts'\n\nconst name = 'find-my-way'\nconst router = findMyWay()\n\nfor (const route of routes) {\n  router.on(route.method as HTTPMethod, route.path, handler)\n}\n\nexport const findMyWayRouter: RouterInterface = {\n  name,\n  match: (route) => {\n    router.find(route.method as HTTPMethod, route.path)\n  },\n}\n"
  },
  {
    "path": "benchmarks/routers/src/hono.mts",
    "content": "import { PatternRouter } from '../../../src/router/pattern-router/index.ts'\nimport { RegExpRouter } from '../../../src/router/reg-exp-router/index.ts'\nimport { TrieRouter } from '../../../src/router/trie-router/index.ts'\nimport type { Router } from '../../../src/router.ts'\nimport type { RouterInterface } from './tool.mts'\nimport { routes, handler } from './tool.mts'\n\nconst createHonoRouter = (name: string, router: Router<unknown>): RouterInterface => {\n  for (const route of routes) {\n    router.add(route.method, route.path, handler)\n  }\n  return {\n    name: `Hono ${name}`,\n    match: (route) => {\n      router.match(route.method, route.path)\n    },\n  }\n}\n\nexport const regExpRouter = createHonoRouter('RegExpRouter', new RegExpRouter())\nexport const trieRouter = createHonoRouter('TrieRouter', new TrieRouter())\nexport const patternRouter = createHonoRouter('PatternRouter', new PatternRouter())\n"
  },
  {
    "path": "benchmarks/routers/src/koa-router.mts",
    "content": "import KoaRouter from 'koa-router'\nimport type { RouterInterface } from './tool.mts'\nimport { routes, handler } from './tool.mts'\n\nconst name = 'koa-router'\nconst router = new KoaRouter()\n\nfor (const route of routes) {\n  if (route.method === 'GET') {\n    router.get(route.path.replace('*', '(.*)'), handler)\n  } else {\n    router.post(route.path, handler)\n  }\n}\n\nexport const koaRouter: RouterInterface = {\n  name,\n  match: (route) => {\n    router.match(route.path, route.method) // only matching\n  },\n}\n"
  },
  {
    "path": "benchmarks/routers/src/koa-tree-router.mts",
    "content": "import KoaRouter from 'koa-tree-router'\nimport type { RouterInterface } from './tool.mts'\nimport { routes, handler } from './tool.mts'\n\nconst name = 'koa-tree-router'\nconst router = new KoaRouter()\n\nfor (const route of routes) {\n  router.on(route.method, route.path.replace('*', '*foo'), handler)\n}\n\nexport const koaTreeRouter: RouterInterface = {\n  name,\n  match: (route) => {\n    // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n    // @ts-ignore\n    router.find(route.method, route.path)\n  },\n}\n"
  },
  {
    "path": "benchmarks/routers/src/medley-router.mts",
    "content": "import Router from '@medley/router'\nimport type { RouterInterface } from './tool.mts'\nimport { routes, handler } from './tool.mts'\n\nconst name = '@medley/router'\nconst router = new Router()\n\nfor (const route of routes) {\n  const store = router.register(route.path)\n  store[route.method] = handler\n}\n\nexport const medleyRouter: RouterInterface = {\n  name,\n  match: (route) => {\n    const match = router.find(route.path)\n    match.store[route.method] // get handler\n  },\n}\n"
  },
  {
    "path": "benchmarks/routers/src/memoirist.mts",
    "content": "import { Memoirist } from 'memoirist'\nimport type { RouterInterface } from './tool.mts'\nimport { routes, handler } from './tool.mts'\n\nconst name = 'Memoirist'\nconst router = new Memoirist()\n\nfor (const route of routes) {\n  router.add(route.method, route.path, handler)\n}\n\nexport const memoiristRouter: RouterInterface = {\n  name,\n  match: (route) => {\n    router.find(route.method, route.path)\n  },\n}\n"
  },
  {
    "path": "benchmarks/routers/src/radix3.mts",
    "content": "import { createRouter } from 'radix3'\nimport type { RouterInterface } from './tool.mts'\nimport { routes, handler } from './tool.mts'\n\nconst name = 'radix3'\nconst router = createRouter()\n\nfor (const route of routes) {\n  router.insert(route.path, handler)\n}\n\nexport const radix3Router: RouterInterface = {\n  name,\n  match: (route) => {\n    router.lookup(route.path)\n  },\n}\n"
  },
  {
    "path": "benchmarks/routers/src/rou3.mts",
    "content": "import { addRoute, createRouter, findRoute } from 'rou3'\nimport type { RouterInterface } from './tool.mts'\nimport { handler, routes } from './tool.mts'\n\nconst name = 'rou3'\nconst router = createRouter()\n\nfor (const route of routes) {\n  addRoute(router, route.path, route.method, handler)\n}\n\nexport const rou3Router: RouterInterface = {\n  name,\n  match: (route) => {\n    findRoute(router, route.path, route.method, {\n      ignoreParams: false, // Don't ignore params\n    })\n  },\n}\n"
  },
  {
    "path": "benchmarks/routers/src/tool.mts",
    "content": "export const handler = () => {}\n\nexport type Route = {\n  method: 'GET' | 'POST'\n  path: string\n}\n\nexport interface RouterInterface {\n  name: string\n  match: (route: Route) => unknown\n}\n\nexport const routes: Route[] = [\n  { method: 'GET', path: '/user' },\n  { method: 'GET', path: '/user/comments' },\n  { method: 'GET', path: '/user/avatar' },\n  { method: 'GET', path: '/user/lookup/username/:username' },\n  { method: 'GET', path: '/user/lookup/email/:address' },\n  { method: 'GET', path: '/event/:id' },\n  { method: 'GET', path: '/event/:id/comments' },\n  { method: 'POST', path: '/event/:id/comment' },\n  { method: 'GET', path: '/map/:location/events' },\n  { method: 'GET', path: '/status' },\n  { method: 'GET', path: '/very/deeply/nested/route/hello/there' },\n  { method: 'GET', path: '/static/*' },\n]\n"
  },
  {
    "path": "benchmarks/routers/src/trek-router.mts",
    "content": "import TrekRouter from 'trek-router'\nimport type { RouterInterface } from './tool.mts'\nimport { routes, handler } from './tool.mts'\n\nconst name = 'trek-router'\n\nconst router = new TrekRouter()\nfor (const route of routes) {\n  router.add(route.method, route.path, handler())\n}\n\nexport const trekRouter: RouterInterface = {\n  name,\n  match: (route) => {\n    router.find(route.method, route.path)\n  },\n}\n"
  },
  {
    "path": "benchmarks/routers/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"allowImportingTsExtensions\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"NodeNext\"\n  },\n  \"include\": [\"./src\"]\n}\n"
  },
  {
    "path": "benchmarks/routers-deno/.vscode/settings.json",
    "content": "{\n  \"eslint.validate\": [\"javascript\", \"javascriptreact\", \"typescript\", \"typescriptreact\"],\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": true\n  },\n  \"deno.enable\": true\n}\n"
  },
  {
    "path": "benchmarks/routers-deno/README.md",
    "content": "# Router benchmarks\n\nBenchmark of the most commonly used HTTP routers.\n\nTested routes:\n\n- [find-my-way](https://github.com/delvedor/find-my-way)\n- [koa-router](https://github.com/alexmingoia/koa-router)\n- [koa-tree-router](https://github.com/steambap/koa-tree-router)\n- [trek-router](https://www.npmjs.com/package/trek-router)\n- [@medley/router](https://www.npmjs.com/package/@medley/router)\n- [Hono RegExpRouter](https://github.com/honojs/hono)\n- [Hono TrieRouter](https://github.com/honojs/hono)\n\nFor Deno:\n\n```\ndeno run --allow-read --allow-run src/bench.mts\n```\n\nThis project is heavily impaired by [delvedor/router-benchmark](https://github.com/delvedor/router-benchmark)\n\n## License\n\nMIT\n"
  },
  {
    "path": "benchmarks/routers-deno/deno.json",
    "content": "{\n  \"imports\": {\n    \"npm/\": \"https://unpkg.com/\"\n  }\n}\n"
  },
  {
    "path": "benchmarks/routers-deno/src/bench.mts",
    "content": "import { run, bench, group } from 'npm:mitata'\nimport { findMyWayRouter } from './find-my-way.mts'\nimport { regExpRouter, trieRouter, patternRouter } from './hono.mts'\nimport { koaRouter } from './koa-router.mts'\nimport { koaTreeRouter } from './koa-tree-router.mts'\nimport { medleyRouter } from './medley-router.mts'\nimport type { Route, RouterInterface } from './tool.mts'\nimport { trekRouter } from './trek-router.mts'\n\nconst routers: RouterInterface[] = [\n  regExpRouter,\n  trieRouter,\n  patternRouter,\n  medleyRouter,\n  findMyWayRouter,\n  koaTreeRouter,\n  trekRouter,\n  koaRouter,\n]\n\nmedleyRouter.match({ method: 'GET', path: '/user' })\n\nconst routes: (Route & { name: string })[] = [\n  {\n    name: 'short static',\n    method: 'GET',\n    path: '/user',\n  },\n  {\n    name: 'static with same radix',\n    method: 'GET',\n    path: '/user/comments',\n  },\n  {\n    name: 'dynamic route',\n    method: 'GET',\n    path: '/user/lookup/username/hey',\n  },\n  {\n    name: 'mixed static dynamic',\n    method: 'GET',\n    path: '/event/abcd1234/comments',\n  },\n  {\n    name: 'post',\n    method: 'POST',\n    path: '/event/abcd1234/comment',\n  },\n  {\n    name: 'long static',\n    method: 'GET',\n    path: '/very/deeply/nested/route/hello/there',\n  },\n  {\n    name: 'wildcard',\n    method: 'GET',\n    path: '/static/index.html',\n  },\n]\n\nfor (const route of routes) {\n  group(`${route.name} - ${route.method} ${route.path}`, () => {\n    for (const router of routers) {\n      bench(router.name, async () => {\n        router.match(route)\n      })\n    }\n  })\n}\n\ngroup('all together', () => {\n  for (const router of routers) {\n    bench(router.name, async () => {\n      for (const route of routes) {\n        router.match(route)\n      }\n    })\n  }\n})\n\nawait run()\n"
  },
  {
    "path": "benchmarks/routers-deno/src/find-my-way.mts",
    "content": "import type { HTTPMethod } from 'npm:find-my-way'\nimport findMyWay from 'npm:find-my-way'\nimport type { RouterInterface } from './tool.mts'\nimport { routes, handler } from './tool.mts'\n\nconst name = 'find-my-way'\nconst router = findMyWay()\n\nfor (const route of routes) {\n  router.on(route.method as HTTPMethod, route.path, handler)\n}\n\nexport const findMyWayRouter: RouterInterface = {\n  name,\n  match: (route) => {\n    router.find(route.method as HTTPMethod, route.path)\n  },\n}\n"
  },
  {
    "path": "benchmarks/routers-deno/src/hono.mts",
    "content": "import { PatternRouter } from '../../../src/router/pattern-router/index.ts'\nimport { RegExpRouter } from '../../../src/router/reg-exp-router/index.ts'\nimport { TrieRouter } from '../../../src/router/trie-router/index.ts'\nimport type { Router } from '../../../src/router.ts'\nimport type { RouterInterface } from './tool.mts'\nimport { routes, handler } from './tool.mts'\n\nconst createHonoRouter = (name: string, router: Router<unknown>): RouterInterface => {\n  for (const route of routes) {\n    router.add(route.method, route.path, handler)\n  }\n  return {\n    name: `Hono ${name}`,\n    match: (route) => {\n      router.match(route.method, route.path)\n    },\n  }\n}\n\nexport const regExpRouter = createHonoRouter('RegExpRouter', new RegExpRouter())\nexport const trieRouter = createHonoRouter('TrieRouter', new TrieRouter())\nexport const patternRouter = createHonoRouter('PatternRouter', new PatternRouter())\n"
  },
  {
    "path": "benchmarks/routers-deno/src/koa-router.mts",
    "content": "import KoaRouter from 'npm:koa-router'\nimport type { RouterInterface } from './tool.mts'\nimport { routes, handler } from './tool.mts'\n\nconst name = 'koa-router'\nconst router = new KoaRouter()\n\nfor (const route of routes) {\n  if (route.method === 'GET') {\n    router.get(route.path.replace('*', '(.*)'), handler)\n  } else {\n    router.post(route.path, handler)\n  }\n}\n\nexport const koaRouter: RouterInterface = {\n  name,\n  match: (route) => {\n    router.match(route.path, route.method) // only matching\n  },\n}\n"
  },
  {
    "path": "benchmarks/routers-deno/src/koa-tree-router.mts",
    "content": "import KoaRouter from 'npm:koa-tree-router'\nimport type { RouterInterface } from './tool.mts'\nimport { routes, handler } from './tool.mts'\n\nconst name = 'koa-tree-router'\nconst router = new KoaRouter()\n\nfor (const route of routes) {\n  router.on(route.method, route.path.replace('*', '*foo'), handler)\n}\n\nexport const koaTreeRouter: RouterInterface = {\n  name,\n  match: (route) => {\n    // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n    // @ts-ignore\n    router.find(route.method, route.path)\n  },\n}\n"
  },
  {
    "path": "benchmarks/routers-deno/src/medley-router.mts",
    "content": "import Router from 'npm:@medley/router'\nimport type { RouterInterface } from './tool.mts'\nimport { routes, handler } from './tool.mts'\n\nconst name = '@medley/router'\nconst router = new Router()\n\nfor (const route of routes) {\n  const store = router.register(route.path)\n  store[route.method] = handler\n}\n\nexport const medleyRouter: RouterInterface = {\n  name,\n  match: (route) => {\n    const match = router.find(route.path)\n    match.store[route.method] // get handler\n  },\n}\n"
  },
  {
    "path": "benchmarks/routers-deno/src/tool.mts",
    "content": "export const handler = () => {}\n\nexport type Route = {\n  method: 'GET' | 'POST'\n  path: string\n}\n\nexport interface RouterInterface {\n  name: string\n  match: (route: Route) => unknown\n}\n\nexport const routes: Route[] = [\n  { method: 'GET', path: '/user' },\n  { method: 'GET', path: '/user/comments' },\n  { method: 'GET', path: '/user/avatar' },\n  { method: 'GET', path: '/user/lookup/username/:username' },\n  { method: 'GET', path: '/user/lookup/email/:address' },\n  { method: 'GET', path: '/event/:id' },\n  { method: 'GET', path: '/event/:id/comments' },\n  { method: 'POST', path: '/event/:id/comment' },\n  { method: 'GET', path: '/map/:location/events' },\n  { method: 'GET', path: '/status' },\n  { method: 'GET', path: '/very/deeply/nested/route/hello/there' },\n  { method: 'GET', path: '/static/*' },\n]\n"
  },
  {
    "path": "benchmarks/routers-deno/src/trek-router.mts",
    "content": "import TrekRouter from 'npm:trek-router'\nimport type { RouterInterface } from './tool.mts'\nimport { routes, handler } from './tool.mts'\n\nconst name = 'trek-router'\n\nconst router = new TrekRouter()\nfor (const route of routes) {\n  router.add(route.method, route.path, handler())\n}\n\nexport const trekRouter: RouterInterface = {\n  name,\n  match: (route) => {\n    router.find(route.method, route.path)\n  },\n}\n"
  },
  {
    "path": "benchmarks/utils/.gitignore",
    "content": "yarn.lock\nbun.lockb\n"
  },
  {
    "path": "benchmarks/utils/package.json",
    "content": "{\n  \"type\": \"module\",\n  \"devDependencies\": {\n    \"mitata\": \"^0.1.11\"\n  }\n}\n"
  },
  {
    "path": "benchmarks/utils/src/get-path.ts",
    "content": "import { run, group, bench } from 'mitata'\n\nbench('noop', () => {})\n\nconst request = new Request('http://localhost/about/me')\n\ngroup('getPath', () => {\n  bench('slice + indexOf : w/o decodeURI', () => {\n    const url = request.url\n    const queryIndex = url.indexOf('?', 8)\n    return url.slice(url.indexOf('/', 8), queryIndex === -1 ? undefined : queryIndex)\n  })\n\n  bench('regexp : w/o decodeURI', () => {\n    const match = request.url.match(/^https?:\\/\\/[^/]+(\\/[^?]*)/)\n    return match ? match[1] : ''\n  })\n\n  bench('slice + indexOf', () => {\n    const url = request.url\n    const queryIndex = url.indexOf('?', 8)\n    const path = url.slice(url.indexOf('/', 8), queryIndex === -1 ? undefined : queryIndex)\n    return path.includes('%') ? decodeURIComponent(path) : path\n  })\n\n  bench('slice + for-loop + flag', () => {\n    const url = request.url\n    const start = url.indexOf('/', 8)\n    let i = start\n    let hasPercentEncoding = false\n    for (; i < url.length; i++) {\n      const charCode = url.charCodeAt(i)\n      if (charCode === 37) {\n        // '%'\n        hasPercentEncoding = true\n      } else if (charCode === 63) {\n        // '?'\n        break\n      }\n    }\n    return hasPercentEncoding ? decodeURIComponent(url.slice(start, i)) : url.slice(start, i)\n  })\n\n  bench('slice + for-loop + immediate return', () => {\n    const url = request.url\n    const start = url.indexOf('/', 8)\n    let i = start\n    for (; i < url.length; i++) {\n      const charCode = url.charCodeAt(i)\n      if (charCode === 37) {\n        // '%'\n        // If the path contains percent encoding, use `indexOf()` to find '?' and return the result immediately.\n        // Although this is a performance disadvantage, it is acceptable since we prefer cases that do not include percent encoding.\n        const queryIndex = url.indexOf('?', i)\n        const path = url.slice(start, queryIndex === -1 ? undefined : queryIndex)\n        return decodeURI(path.includes('%25') ? path.replace(/%25/g, '%2525') : path)\n      } else if (charCode === 63) {\n        // '?'\n        break\n      }\n    }\n    return url.slice(start, i)\n  })\n})\n\nrun()\n"
  },
  {
    "path": "benchmarks/utils/src/loop.js",
    "content": "import { run, group, bench } from 'mitata'\n\nconst arr = new Array(100000).fill(Math.random())\n\nbench('noop', () => {})\n\ngroup('loop', () => {\n  bench('map', () => {\n    const newArr = []\n    arr.map((e) => {\n      newArr.push(e)\n    })\n  })\n\n  bench('forEach', () => {\n    const newArr = []\n    arr.forEach((e) => {\n      newArr.push(e)\n    })\n  })\n\n  bench('for of', () => {\n    const newArr = []\n    for (const e of arr) {\n      newArr.push(e)\n    }\n  })\n\n  bench('for', () => {\n    const newArr = []\n    for (let i = 0; i < arr.length; i++) {\n      newArr.push(arr[i])\n    }\n  })\n})\n\nrun()\n"
  },
  {
    "path": "benchmarks/webapp/.gitignore",
    "content": "yarn.lock\n"
  },
  {
    "path": "benchmarks/webapp/hono.js",
    "content": "import { Hono } from '../../dist/hono'\n//import { Hono } from 'hono'\n\nconst hono = new Hono()\nhono.get('/user', (c) => c.text('User'))\nhono.get('/user/comments', (c) => c.text('User Comments'))\nhono.get('/user/avatar', (c) => c.text('User Avatar'))\nhono.get('/user/lookup/email/:address', (c) => c.text('User Lookup Email Address'))\nhono.get('/event/:id', (c) => c.text('Event'))\nhono.get('/event/:id/comments', (c) => c.text('Event Comments'))\nhono.post('/event/:id/comments', (c) => c.text('POST Event Comments'))\nhono.post('/status', (c) => c.text('Status'))\nhono.get('/very/deeply/nested/route/hello/there', (c) => c.text('Very Deeply Nested Route'))\nhono.get('/user/lookup/username/:username', (c) => {\n  return new Response(`Hello ${c.req.param('username')}`)\n})\n\nhono.fire()\n"
  },
  {
    "path": "benchmarks/webapp/itty-router.js",
    "content": "import { Router } from 'itty-router'\n\nconst ittyRouter = Router()\nittyRouter.get('/user', () => new Response('User'))\nittyRouter.get('/user/comments', () => new Response('User Comments'))\nittyRouter.get('/user/avatar', () => new Response('User Avatar'))\nittyRouter.get('/user/lookup/email/:address', () => new Response('User Lookup Email Address'))\nittyRouter.get('/event/:id', () => new Response('Event'))\nittyRouter.get('/event/:id/comments', () => new Response('Event Comments'))\nittyRouter.post('/event/:id/comments', () => new Response('POST Event Comments'))\nittyRouter.post('/status', () => new Response('Status'))\nittyRouter.get(\n  '/very/deeply/nested/route/hello/there',\n  () => new Response('Very Deeply Nested Route')\n)\nittyRouter.get('/user/lookup/username/:username', ({ params }) => {\n  return new Response(`Hello ${params.username}`, {\n    status: 200,\n    headers: {\n      'Content-Type': 'text/plain;charset=UTF-8',\n    },\n  })\n})\n\naddEventListener('fetch', (event) => event.respondWith(ittyRouter.handle(event.request)))\n"
  },
  {
    "path": "benchmarks/webapp/package.json",
    "content": "{\n  \"name\": \"webapp\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"start:hono\": \"wrangler dev hono.js --local --port 8787\",\n    \"start:itty-router\": \"wrangler dev itty-router.js --local --port 8788\",\n    \"start:sunder\": \"wrangler dev sunder.js --local --port 8789\"\n  },\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"itty-router\": \"^2.6.1\",\n    \"sunder\": \"^0.10.1\"\n  }\n}\n"
  },
  {
    "path": "benchmarks/webapp/sunder.js",
    "content": "import { Sunder, Router } from 'sunder'\n\nconst sunderRouter = new Router()\nsunderRouter.get('/user', (ctx) => {\n  ctx.response.body = 'User'\n})\nsunderRouter.get('/user/comments', (ctx) => {\n  ctx.response.body = 'User Comments'\n})\nsunderRouter.get('/user/avatar', (ctx) => {\n  ctx.response.body = 'User Avatar'\n})\nsunderRouter.get('/user/lookup/email/:address', (ctx) => {\n  ctx.response.body = 'User Lookup Email Address'\n})\nsunderRouter.get('/event/:id', (ctx) => {\n  ctx.response.body = 'Event'\n})\nsunderRouter.get('/event/:id/comments', (ctx) => {\n  ctx.response.body = 'Event Comments'\n})\nsunderRouter.post('/event/:id/comments', (ctx) => {\n  ctx.response.body = 'POST Event Comments'\n})\nsunderRouter.post('/status', (ctx) => {\n  ctx.response.body = 'Status'\n})\nsunderRouter.get('/very/deeply/nested/route/hello/there', (ctx) => {\n  ctx.response.body = 'Very Deeply Nested Route'\n})\n//sunderRouter.get('/static/*', () => {})\nsunderRouter.get('/user/lookup/username/:username', (ctx) => {\n  ctx.response.body = `Hello ${ctx.params.username}`\n})\nconst sunderApp = new Sunder()\nsunderApp.use(sunderRouter.middleware)\n\naddEventListener('fetch', (event) => {\n  event.respondWith(sunderApp.handle(event))\n})\n"
  },
  {
    "path": "build/build.ts",
    "content": "/*\n  This script is heavily inspired by `built.ts` used in @kaze-style/react.\n  https://github.com/taishinaritomi/kaze-style/blob/main/scripts/build.ts\n  MIT License\n  Copyright (c) 2022 Taishi Naritomi\n*/\n\n/// <reference types=\"bun-types/bun\" />\n\nimport arg from 'arg'\nimport { $ } from 'bun'\nimport { build, context } from 'esbuild'\nimport type { Plugin, PluginBuild, BuildOptions } from 'esbuild'\nimport * as glob from 'glob'\nimport fs from 'fs'\nimport path from 'path'\nimport { removePrivateFields } from './remove-private-fields'\nimport { validateExports } from './validate-exports'\n\nconst args = arg({\n  '--watch': Boolean,\n})\n\nconst isWatch = args['--watch'] || false\n\nconst readJsonExports = (path: string) => JSON.parse(fs.readFileSync(path, 'utf-8')).exports\n\nconst [packageJsonExports, jsrJsonExports] = ['./package.json', './jsr.json'].map(readJsonExports)\n\n// Validate exports of package.json and jsr.json\nvalidateExports(packageJsonExports, jsrJsonExports, 'jsr.json')\nvalidateExports(jsrJsonExports, packageJsonExports, 'package.json')\n\nconst entryPoints = glob.sync('./src/**/*.ts', {\n  ignore: ['./src/**/*.test.ts', './src/mod.ts', './src/middleware.ts', './src/deno/**/*.ts'],\n})\n\n/*\n  This plugin is inspired by the following.\n  https://github.com/evanw/esbuild/issues/622#issuecomment-769462611\n*/\nconst addExtension = (extension: string = '.js', fileExtension: string = '.ts'): Plugin => ({\n  name: 'add-extension',\n  setup(build: PluginBuild) {\n    build.onResolve({ filter: /.*/ }, (args) => {\n      if (args.importer) {\n        const p = path.join(args.resolveDir, args.path)\n        let tsPath = `${p}${fileExtension}`\n\n        let importPath = ''\n        if (fs.existsSync(tsPath)) {\n          importPath = args.path + extension\n        } else {\n          tsPath = path.join(args.resolveDir, args.path, `index${fileExtension}`)\n          if (fs.existsSync(tsPath)) {\n            if (args.path.endsWith('/')) {\n              importPath = `${args.path}index${extension}`\n            } else {\n              importPath = `${args.path}/index${extension}`\n            }\n          }\n        }\n        return { path: importPath, external: true }\n      }\n    })\n  },\n})\n\nconst commonOptions: BuildOptions = {\n  entryPoints,\n  logLevel: 'info',\n  platform: 'node',\n}\n\nconst cjsConfig: BuildOptions = {\n  ...commonOptions,\n  outbase: './src',\n  outdir: './dist/cjs',\n  format: 'cjs',\n}\n\nconst esmConfig: BuildOptions = {\n  ...commonOptions,\n  bundle: true,\n  outbase: './src',\n  outdir: './dist',\n  format: 'esm',\n  plugins: [addExtension('.js')],\n}\n\nconst runBuild = async (config: BuildOptions) => {\n  if (isWatch) {\n    const ctx = await context(config)\n    await ctx.watch()\n  } else {\n    await build(config)\n  }\n}\n\nawait Promise.all([\n  runBuild(esmConfig),\n  runBuild(cjsConfig),\n  $`tsc ${\n    isWatch ? '-w' : ''\n  } --emitDeclarationOnly --declaration --project tsconfig.build.json`.nothrow(),\n])\n\n// Remove #private fields\nconst dtsEntries = glob.globSync('./dist/types/**/*.d.ts')\nawait removePrivateFields(dtsEntries)\n"
  },
  {
    "path": "build/remove-private-fields.test.ts",
    "content": "/// <reference types=\"vitest/globals\" />\n\nimport { parseSync } from 'oxc-parser'\nimport { removePrivateFieldFromSourceCode } from './remove-private-fields'\n\ndescribe('removePrivateFields', () => {\n  it('should remove private fields from declarations', () => {\n    const sourceCode = `\n    import type { Result, Router } from '../../router';\n    export declare class PatternRouter<T> implements Router<T> {\n        #private;\n        name: string;\n        add(method: string, path: string, handler: T): void;\n        match(method: string, path: string): Result<T>;\n    }\n    `.trim()\n\n    const ast = parseSync('types.d.ts', sourceCode)\n    const result = removePrivateFieldFromSourceCode(ast, sourceCode)\n    expect(result).toBeDefined()\n\n    // expected code should be same, but the `#private;` is replaced with spaces\n    const expected = sourceCode.replace('#private;', ' '.repeat(9))\n    expect(result).toMatch(expected)\n  })\n})\n"
  },
  {
    "path": "build/remove-private-fields.ts",
    "content": "import type { PropertyDefinition, ParseResult } from 'oxc-parser'\nimport { parseSync, Visitor } from 'oxc-parser'\nimport { readFile, writeFile } from 'fs/promises'\n\nexport async function removePrivateFields(files: string[]) {\n  const start = performance.now()\n  const parsed = await Promise.all(\n    files.map(async (file) => {\n      const sourceCode = await readFile(file, 'utf-8')\n      const ast = parseSync(file, sourceCode)\n      return { file, sourceCode, ast }\n    })\n  )\n\n  await Promise.all(\n    parsed.map(async ({ file, sourceCode, ast }) => {\n      const sourceCodeWithoutPrivateFields = removePrivateFieldFromSourceCode(ast, sourceCode)\n      if (sourceCodeWithoutPrivateFields) {\n        await writeFile(file, sourceCodeWithoutPrivateFields)\n      }\n    })\n  )\n  const end = performance.now()\n  console.log(`Done removing private fields in ${(end - start).toFixed(2)}ms`)\n}\n\nexport function removePrivateFieldFromSourceCode(ast: ParseResult, sourceCode: string) {\n  const removals: PropertyDefinition[] = []\n  new Visitor({\n    ClassDeclaration: (node) => {\n      node.body.body.forEach((elem) => {\n        if (elem.type === 'PropertyDefinition' && elem.key.type === 'PrivateIdentifier') {\n          removals.push(elem)\n        }\n      })\n    },\n  }).visit(ast.program)\n\n  if (removals.length === 0) {\n    return\n  }\n\n  let sourceCodeWithoutPrivateFields = sourceCode\n  for (const elem of removals) {\n    sourceCodeWithoutPrivateFields = removeRange(\n      sourceCodeWithoutPrivateFields,\n      elem.start,\n      elem.end\n    )\n  }\n\n  return sourceCodeWithoutPrivateFields\n}\n\nfunction removeRange(str: string, start: number, end: number) {\n  return str.slice(0, start) + ' '.repeat(end - start) + str.slice(end)\n}\n"
  },
  {
    "path": "build/validate-exports.test.ts",
    "content": "/// <reference types=\"vitest/globals\" />\n\nimport { validateExports } from './validate-exports'\n\nconst mockExports1 = {\n  './a': './a.ts',\n  './b': './b.ts',\n  './c/a': './c.ts',\n  './d/*': './d/*.ts',\n}\n\nconst mockExports2 = {\n  './a': './a.ts',\n  './b': './b.ts',\n  './c/a': './c.ts',\n  './d/a': './d/a.ts',\n}\n\nconst mockExports3 = {\n  './a': './a.ts',\n  './c/a': './c.ts',\n  './d/*': './d/*.ts',\n}\n\ndescribe('validateExports', () => {\n  it('Works', async () => {\n    expect(() => validateExports(mockExports1, mockExports1, 'package.json')).not.toThrowError()\n    expect(() => validateExports(mockExports1, mockExports2, 'jsr.json')).not.toThrowError()\n    expect(() => validateExports(mockExports1, mockExports3, 'package.json')).toThrowError()\n  })\n})\n"
  },
  {
    "path": "build/validate-exports.ts",
    "content": "export const validateExports = (\n  source: Record<string, unknown>,\n  target: Record<string, unknown>,\n  fileName: string\n) => {\n  const isEntryInTarget = (entry: string): boolean => {\n    if (entry in target) {\n      return true\n    }\n\n    // e.g., \"./utils/*\" -> \"./utils\"\n    const wildcardPrefix = entry.replace(/\\/\\*$/, '')\n    if (entry.endsWith('/*')) {\n      return Object.keys(target).some(\n        (targetEntry) =>\n          targetEntry.startsWith(wildcardPrefix + '/') && targetEntry !== wildcardPrefix\n      )\n    }\n\n    const separatedEntry = entry.split('/')\n    while (separatedEntry.length > 0) {\n      const pattern = `${separatedEntry.join('/')}/*`\n      if (pattern in target) {\n        return true\n      }\n      separatedEntry.pop()\n    }\n\n    return false\n  }\n\n  Object.keys(source).forEach((sourceEntry) => {\n    if (!isEntryInTarget(sourceEntry)) {\n      throw new Error(`Missing \"${sourceEntry}\" in '${fileName}'`)\n    }\n  })\n}\n"
  },
  {
    "path": "bunfig.toml",
    "content": "[test]\ncoverage = true\ncoverageReporter = [\"text\", \"lcov\"]\ncoverageDir = \"coverage/raw/bun\"\n"
  },
  {
    "path": "codecov.yml",
    "content": "# Edit \"test.coverage.exclude\" option in vitest.config.ts to exclude specific files from coverage reports.\n# We can also use \"ignore\" option in codecov.yml, but it is not recognized by vitest, so results may differ on local.\n\ncoverage:\n  status:\n    patch:\n      default:\n        target: 80%\n        informational: true # Don't fail the build even if coverage is below target\n    project:\n      default:\n        target: 75%\n        threshold: 1%\n"
  },
  {
    "path": "docs/CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n- Demonstrating empathy and kindness toward other people\n- Being respectful of differing opinions, viewpoints, and experiences\n- Giving and gracefully accepting constructive feedback\n- Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n- Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n- The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n- Trolling, insulting or derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n- Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, or offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\nyusuke@kamawada.com.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or a series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of the community\nstandards, including sustained inappropriate behavior, harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "docs/CONTRIBUTING.md",
    "content": "# Contribution Guide\n\nContributions Welcome! We will be glad for your help.\nYou can contribute in the following ways.\n\n- Create an Issue - Propose a new feature. Report a bug.\n- Pull Request - Fix a bug or typo. Refactor the code.\n- Create third-party middleware - See instructions below.\n- Share - Share your thoughts on the Blog, X, and others.\n- Make your application - Please try to use Hono.\n\nNote:\nThis project is started by Yusuke Wada ([@yusukebe](https://github.com/yusukebe)) for the hobby proposal.\nIt was just for fun. For now, this stance has not been changed basically.\nI want to write the code as I like.\nSo, if you propose great ideas, but I do not appropriate them, the idea may not be accepted.\n\nAlthough, don't worry!\nHono is tested well, polished by the contributors, and used by many developers. And I'll try my best to make Hono cool and hot, beautiful, and ultrafast.\n\n## Installing dependencies\n\nThe `honojs/hono` project uses [Bun](https://bun.sh/) as its package manager. Developers should install Bun.\n\nAfter that, please install the dependency environment.\n\n```bash\nbun install --frozen-lockfile\n```\n\n## PRs\n\nPlease ensure your PR passes tests with `bun run test`.\n\n## Third-party middleware\n\nThird-party middleware is not in the core.\nIt is allowed to depend on other libraries or work only in specific environments, such as Cloudflare Workers. For example:\n\n- GraphQL Server middleware\n- Firebase Auth middleware\n- Sentry middleware\n\nYou can make a third-party middleware by yourself.\nIt may be under the \"honojs organization\" and distributed in the `@honojs` namespace.\n\nThe monorepo \"[honojs/middleware](https://github.com/honojs/middleware)\" manages these middleware.\nIf you want to do it, create an issue about your middleware.\n\n## Local Development\n\n```bash\ngit clone git@github.com:honojs/hono.git && cd hono/.devcontainer && bun install --frozen-lockfile\ndocker compose up -d --build\ndocker compose exec hono bash\n```\n"
  },
  {
    "path": "docs/MIGRATION.md",
    "content": "# Migration Guide\n\n## v4.3.11 to v4.4.0\n\n### `deno.land/x` to JSR\n\nThere is no breaking change, but we no longer publish the module from `deno.land/x`. If you want to use Hono on Deno, use JSR instead of it.\n\nIf you migrate, replace the path `deno.land/x` with JSR's.\n\n```ts\n// From\nimport { Hono } from 'https://deno.land/x/hono/mod.ts'\n\n// To\nimport { Hono } from 'jsr:@hono/hono'\n```\n\nYou can see more details on our website: https://hono.dev/getting-started/deno\n\n## v3.12.x to v4.0.0\n\nThere are some breaking changes.\n\n### Removal of deprecated features\n\n- AWS Lambda Adapter - `LambdaFunctionUrlRequestContext` is obsolete. Use `ApiGatewayRequestContextV2` instead.\n- Next.js Adapter - `hono/nextjs` is obsolete. Use `hono/vercel` instead.\n- Context - `c.jsonT()` is obsolete. Use `c.json()` instead.\n- Context - `c.stream()` and `c.streamText()` are obsolete. Use `stream()` and `streamText()` in `hono/streaming` instead.\n- Context - `c.env()` is obsolete. Use `getRuntimeKey()` in `hono/adapter` instead.\n- Hono - `app.showRoutes()` is obsolete. Use `showRoutes()` in `hono/dev` instead.\n- Hono - `app.routerName` is obsolete. Use `getRouterName()` in `hono/dev` instead.\n- Hono - `app.head()` is no longer used. `app.get()` implicitly handles the HEAD method.\n- Hono - `app.handleEvent()` is obsolete. Use `app.fetch()` instead.\n- HonoRequest - `req.cookie()` is obsolete. Use `getCookie()` in `hono/cookie` instead.\n- HonoRequest - `headers()`, `body()`, `bodyUsed()`, `integrity()`, `keepalive()`, `referrer()`, and `signal()` are obsolete. Use the methods in `req.raw` such as `req.raw.headers()`.\n\n### `serveStatic` in Cloudflare Workers Adapter requires `manifest`\n\nIf you use the Cloudflare Workers adapter's `serve-static`, you should specify the `manifest` option.\n\n```ts\nimport manifest from '__STATIC_CONTENT_MANIFEST'\n\n// ...\n\napp.use('/static/*', serveStatic({ root: './assets', manifest }))\n```\n\n### Others\n\n- The default value of the `docType` option in JSX Renderer Middleware is now `true`.\n- `FC` in `hono/jsx` does not pass `children`. Use `PropsWithChildren`.\n- Some Mime Types are removed https://github.com/honojs/hono/pull/2119.\n- Types for chaining routes with middleware matter: https://github.com/honojs/hono/pull/2046.\n- Types for the validator matter: https://github.com/honojs/hono/pull/2130.\n\n## v2.7.8 to v3.0.0\n\nThere are some breaking changes.\nIn addition to the following, type mismatches may occur.\n\n### `c.req` is now `HonoRequest`\n\n`c.req` becomes `HonoRequest`, not `Request`.\nAlthough APIs are almost the same, if you want to access `Request`, use `c.req.raw`.\n\n```ts\napp.post('/', async (c) => {\n  const metadata = c.req.raw.cf?.hostMetadata?\n  ...\n})\n```\n\n### StaticRouter is obsolete\n\nYou can't use `StaticRouter`.\n\n### Validator has been changed\n\nPrevious Validator Middleware is obsolete.\nYou can still use `hono/validator`, but the API has been changed.\nSee [the document](https://hono.dev).\n\n### `serveStatic` is provided from the Adapter\n\nServe Static Middleware is obsolete. Use Adapters instead.\n\n```ts\n// For Cloudflare Workers\nimport { serveStatic } from 'hono/cloudflare-workers'\n\n// For Bun\n// import { serveStatic } from 'hono/bun'\n\n// For Deno\n// import { serveStatic } from 'npm:hono/deno'\n\n// ...\n\napp.get('/static/*', serveStatic({ root: './' }))\n```\n\n### `serveStatic` for Cloudflare Workers \"Service Worker mode\" is obsolete\n\nFor Cloudflare Workers, the `serveStatic` is obsolete in Service Worker mode.\n\nNote: Service Worker mode is that using `app.fire()`.\nWe recommend use \"Module Worker\" mode with `export default app`.\n\n### Use `type` to define the Generics for `new Hono`\n\nYou must use `type` to define the Generics for `new Hono`. Do not use `interface`.\n\n```ts\n// Should use `type`\ntype Bindings = {\n  TOKEN: string\n}\n\nconst app = new Hono<{ Bindings: Bindings }>()\n```\n\n## v2.7.1 - v2.x.x\n\n### Current Validator Middleware is deprecated\n\nAt the next major version, Validator Middleware will be changed with \"breaking changes\". Therefore, the current Validator Middleware will be deprecated; please use 3rd-party Validator libraries such as [Zod](https://zod.dev) or [TypeBox](https://github.com/sinclairzx81/typebox).\n\n```ts\nimport * as z from 'zod'\n\n//...\n\nconst schema = z.object({\n  title: z.string().max(100),\n})\n\napp.post('/posts', async (c) => {\n  const body = await c.req.parseBody()\n  const res = schema.safeParse(body)\n  if (!res.success) {\n    return c.text('Invalid!', 400)\n  }\n  return c.text('Valid!')\n})\n```\n\n## v2.2.5 to v2.3.0\n\nThere is a breaking change associated with the security update.\n\n### Basic Auth Middleware and Bearer Auth Middleware\n\nIf you are using Basic Auth and Bearer Auth in your Handler (nested), change as follows:\n\n```ts\napp.use('/auth/*', async (c, next) => {\n  const auth = basicAuth({ username: c.env.USERNAME, password: c.env.PASSWORD })\n  return auth(c, next) // Older: `await auth(c, next)`\n})\n```\n\n## v2.0.9 to v2.1.0\n\nThere are two BREAKING CHANGES.\n\n### `c.req.parseBody` does not parse JSON, text, and ArrayBuffer\n\n**DO NOT** use `c.req.parseBody` for parsing **JSON**, **text**, or **ArrayBuffer**.\n\n`c.req.parseBody` now only parses FormData with content type `multipart/form` or `application/x-www-form-urlencoded`. If you want to parse JSON, text, or ArrayBuffer, use `c.req.json()`, `c.req.text()`, or `c.req.arrayBuffer()`.\n\n```ts\n// `multipart/form` or `application/x-www-form-urlencoded`\nconst data = await c.req.parseBody()\n\nconst jsonData = await c.req.json() // for JSON body\nconst text = await c.req.text() // for text body\nconst arrayBuffer = await c.req.arrayBuffer() // for ArrayBuffer\n```\n\n### The arguments of Generics for `new Hono` have been changed\n\nNow, the constructor of \"Hono\" receives `Variables` and `Bindings`.\n\"Bindings\" is for types of environment variables for Cloudflare Workers. \"Variables\" is for types of `c.set`/`c.get`\n\n```ts\ntype Bindings = {\n  KV: KVNamespace\n  Storage: R2Bucket\n}\n\ntype WebClient = {\n  user: string\n  pass: string\n}\n\ntype Variables = {\n  client: WebClient\n}\n\nconst app = new Hono<{ Variables: Variables; Bindings: Bindings }>()\n\napp.get('/foo', (c) => {\n  const client = c.get('client') // client is WebClient\n  const kv = c.env.KV // kv is KVNamespace\n  //...\n})\n```\n\n## v1.6.4 to v2.0.0\n\nThere are many BREAKING CHANGES. Please follow the instructions below.\n\n### The way to import Middleware on Deno has been changed\n\n**DO NOT** import middleware from `hono/mod.ts`.\n\n```ts\nimport { Hono, poweredBy } from 'https://deno.land/x/hono/mod.ts' // <--- NG\n```\n\n`hono/mod.ts` does not export middleware.\nTo import middleware, use `hono/middleware.ts`:\n\n```ts\nimport { Hono } from 'https://deno.land/x/hono/mod.ts'\nimport { poweredBy, basicAuth } from 'https://deno.land/x/hono/middleware.ts'\n```\n\n### Cookie middleware is obsolete\n\n**DO NOT** use `cookie` middleware.\n\n```ts\nimport { cookie } from 'hono/cookie' // <--- Obsolete!\n```\n\nYou do not have to use Cookie middleware to parse or set cookies.\nThey become default functions:\n\n```ts\n// Parse cookie\napp.get('/entry/:id', (c) => {\n  const value = c.req.cookie('name')\n  ...\n})\n```\n\n```ts\napp.get('/', (c) => {\n  c.cookie('delicious_cookie', 'choco')\n  return c.text('Do you like cookie?')\n})\n```\n\n### Body parse middleware is obsolete\n\n**DO NOT** use `body-parse` middleware.\n\n```ts\nimport { bodyParse } from 'hono/body-parse' // <--- Obsolete!\n```\n\nYou do not have to use Body parse middleware to parse the request body. Use `c.req.parseBody()` method instead.\n\n```ts\n// Parse Request body\napp.post('', (c) => {\n  const body = c.req.parseBody()\n  ...\n})\n```\n\n### GraphQL Server middleware is obsolete\n\n**DO NOT** use `graphql-server` middleware.\n\n```ts\nimport { graphqlServer } from 'hono/graphql-server' // <--- Obsolete!\n```\n\nIt might be distributed as third-party middleware.\n\n### Mustache middleware is obsolete\n\n**DO NOT** use `mustache` middleware.\n\n```ts\nimport { mustache } from 'hono/mustache' // <--- Obsolete!\n```\n\nIt will no longer be implemented.\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import baseConfig from '@hono/eslint-config'\nimport { defineConfig, globalIgnores } from 'eslint/config'\n\n// Disable all TypeScript rules that require type information\nconst typeCheckedRules = {\n  '@typescript-eslint/await-thenable': 'off',\n  '@typescript-eslint/no-base-to-string': 'off',\n  '@typescript-eslint/no-confusing-void-expression': 'off',\n  '@typescript-eslint/no-duplicate-type-constituents': 'off',\n  '@typescript-eslint/no-floating-promises': 'off',\n  '@typescript-eslint/no-for-in-array': 'off',\n  '@typescript-eslint/no-implied-eval': 'off',\n  '@typescript-eslint/no-meaningless-void-operator': 'off',\n  '@typescript-eslint/no-misused-promises': 'off',\n  '@typescript-eslint/no-mixed-enums': 'off',\n  '@typescript-eslint/no-redundant-type-constituents': 'off',\n  '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'off',\n  '@typescript-eslint/no-unnecessary-condition': 'off',\n  '@typescript-eslint/no-unnecessary-template-expression': 'off',\n  '@typescript-eslint/no-unnecessary-type-arguments': 'off',\n  '@typescript-eslint/no-unnecessary-type-assertion': 'off',\n  \"@typescript-eslint/no-unnecessary-type-conversion\": 'off',\n  '@typescript-eslint/no-unsafe-argument': 'off',\n  '@typescript-eslint/no-unsafe-assignment': 'off',\n  '@typescript-eslint/no-unsafe-call': 'off',\n  '@typescript-eslint/no-unsafe-enum-comparison': 'off',\n  '@typescript-eslint/no-unsafe-member-access': 'off',\n  '@typescript-eslint/no-unsafe-return': 'off',\n  '@typescript-eslint/no-unsafe-unary-minus': 'off',\n  '@typescript-eslint/only-throw-error': 'off',\n  '@typescript-eslint/prefer-includes': 'off',\n  '@typescript-eslint/prefer-nullish-coalescing': 'off',\n  '@typescript-eslint/prefer-optional-chain': 'off',\n  '@typescript-eslint/prefer-promise-reject-errors': 'off',\n  '@typescript-eslint/prefer-reduce-type-parameter': 'off',\n  '@typescript-eslint/prefer-regexp-exec': 'off',\n  '@typescript-eslint/prefer-return-this-type': 'off',\n  '@typescript-eslint/prefer-string-starts-ends-with': 'off',\n  '@typescript-eslint/require-await': 'off',\n  '@typescript-eslint/restrict-plus-operands': 'off',\n  '@typescript-eslint/restrict-template-expressions': 'off',\n  '@typescript-eslint/return-await': 'off',\n  '@typescript-eslint/strict-boolean-expressions': 'off',\n  '@typescript-eslint/unbound-method': 'off',\n  '@typescript-eslint/use-unknown-in-catch-callback-variable': 'off',\n  '@typescript-eslint/prefer-find': 'off',\n  '@typescript-eslint/no-misused-spread': 'off',\n  '@typescript-eslint/related-getter-setter-pairs': 'off',\n  '@typescript-eslint/prefer-literal-enum-member': 'off',\n\n  // Stylistic rules\n  '@typescript-eslint/consistent-indexed-object-style': 'off',\n  '@typescript-eslint/consistent-type-definitions': 'off',\n  '@typescript-eslint/dot-notation': 'off',\n  '@typescript-eslint/no-array-delete': 'off',\n  '@typescript-eslint/no-confusing-non-null-assertion': 'off',\n  '@typescript-eslint/no-deprecated': 'off',\n  '@typescript-eslint/no-dynamic-delete': 'off',\n  '@typescript-eslint/no-invalid-void-type': 'off',\n  '@typescript-eslint/no-non-null-assertion': 'off',\n  '@typescript-eslint/no-unnecessary-type-parameters': 'off',\n  '@typescript-eslint/no-useless-constructor': 'off',\n  \"@typescript-eslint/no-useless-default-assignment\": 'off',\n  '@typescript-eslint/non-nullable-type-assertion-style': 'off',\n  '@typescript-eslint/prefer-for-of': 'off',\n  '@typescript-eslint/prefer-function-type': 'off',\n  '@typescript-eslint/unified-signatures': 'off',\n  '@typescript-eslint/consistent-generic-constructors': 'off',\n  '@typescript-eslint/array-type': 'off',\n  '@typescript-eslint/no-extraneous-class': 'off',\n}\n\nexport default defineConfig(globalIgnores(['.wrangler', '**/coverage', '**/dist']), {\n  extends: baseConfig,\n  linterOptions: {\n    reportUnusedDisableDirectives: 'error',\n    reportUnusedInlineConfigs: 'error',\n  },\n  rules: typeCheckedRules,\n})\n"
  },
  {
    "path": "jsr.json",
    "content": "{\n  \"name\": \"@hono/hono\",\n  \"version\": \"0.0.0\",\n  \"compilerOptions\": {\n    \"lib\": [\"dom\", \"dom.iterable\", \"deno.ns\"]\n  },\n  \"unstable\": [\"sloppy-imports\"],\n  \"exports\": {\n    \".\": \"./src/index.ts\",\n    \"./request\": \"./src/request.ts\",\n    \"./types\": \"./src/types.ts\",\n    \"./hono-base\": \"./src/hono-base.ts\",\n    \"./tiny\": \"./src/preset/tiny.ts\",\n    \"./quick\": \"./src/preset/quick.ts\",\n    \"./http-exception\": \"./src/http-exception.ts\",\n    \"./basic-auth\": \"./src/middleware/basic-auth/index.ts\",\n    \"./bearer-auth\": \"./src/middleware/bearer-auth/index.ts\",\n    \"./body-limit\": \"./src/middleware/body-limit/index.ts\",\n    \"./ip-restriction\": \"./src/middleware/ip-restriction/index.ts\",\n    \"./cache\": \"./src/middleware/cache/index.ts\",\n    \"./route\": \"./src/helper/route/index.ts\",\n    \"./cookie\": \"./src/helper/cookie/index.ts\",\n    \"./accepts\": \"./src/helper/accepts/index.ts\",\n    \"./compress\": \"./src/middleware/compress/index.ts\",\n    \"./context-storage\": \"./src/middleware/context-storage/index.ts\",\n    \"./cors\": \"./src/middleware/cors/index.ts\",\n    \"./csrf\": \"./src/middleware/csrf/index.ts\",\n    \"./etag\": \"./src/middleware/etag/index.ts\",\n    \"./trailing-slash\": \"./src/middleware/trailing-slash/index.ts\",\n    \"./html\": \"./src/helper/html/index.ts\",\n    \"./css\": \"./src/helper/css/index.ts\",\n    \"./jsx\": \"./src/jsx/index.ts\",\n    \"./jsx/jsx-dev-runtime\": \"./src/jsx/jsx-dev-runtime.ts\",\n    \"./jsx/jsx-runtime\": \"./src/jsx/jsx-runtime.ts\",\n    \"./jsx/streaming\": \"./src/jsx/streaming.ts\",\n    \"./jsx-renderer\": \"./src/middleware/jsx-renderer/index.ts\",\n    \"./jsx/dom\": \"./src/jsx/dom/index.ts\",\n    \"./jsx/dom/jsx-dev-runtime\": \"./src/jsx/dom/jsx-dev-runtime.ts\",\n    \"./jsx/dom/jsx-runtime\": \"./src/jsx/dom/jsx-runtime.ts\",\n    \"./jsx/dom/client\": \"./src/jsx/dom/client.ts\",\n    \"./jsx/dom/css\": \"./src/jsx/dom/css.ts\",\n    \"./jsx/dom/server\": \"./src/jsx/dom/server.ts\",\n    \"./jwt\": \"./src/middleware/jwt/jwt.ts\",\n    \"./jwk\": \"./src/middleware/jwk/jwk.ts\",\n    \"./timeout\": \"./src/middleware/timeout/index.ts\",\n    \"./timing\": \"./src/middleware/timing/timing.ts\",\n    \"./logger\": \"./src/middleware/logger/index.ts\",\n    \"./method-override\": \"./src/middleware/method-override/index.ts\",\n    \"./powered-by\": \"./src/middleware/powered-by/index.ts\",\n    \"./pretty-json\": \"./src/middleware/pretty-json/index.ts\",\n    \"./request-id\": \"./src/middleware/request-id/request-id.ts\",\n    \"./language\": \"./src/middleware/language/language.ts\",\n    \"./secure-headers\": \"./src/middleware/secure-headers/secure-headers.ts\",\n    \"./combine\": \"./src/middleware/combine/index.ts\",\n    \"./ssg\": \"./src/helper/ssg/index.ts\",\n    \"./streaming\": \"./src/helper/streaming/index.ts\",\n    \"./validator\": \"./src/validator/index.ts\",\n    \"./router\": \"./src/router.ts\",\n    \"./router/reg-exp-router\": \"./src/router/reg-exp-router/index.ts\",\n    \"./router/smart-router\": \"./src/router/smart-router/index.ts\",\n    \"./router/trie-router\": \"./src/router/trie-router/index.ts\",\n    \"./router/pattern-router\": \"./src/router/pattern-router/index.ts\",\n    \"./router/linear-router\": \"./src/router/linear-router/index.ts\",\n    \"./client\": \"./src/client/index.ts\",\n    \"./adapter\": \"./src/helper/adapter/index.ts\",\n    \"./factory\": \"./src/helper/factory/index.ts\",\n    \"./serve-static\": \"./src/middleware/serve-static/index.ts\",\n    \"./cloudflare-workers\": \"./src/adapter/cloudflare-workers/index.ts\",\n    \"./cloudflare-pages\": \"./src/adapter/cloudflare-pages/index.ts\",\n    \"./deno\": \"./src/adapter/deno/index.ts\",\n    \"./bun\": \"./src/adapter/bun/index.ts\",\n    \"./aws-lambda\": \"./src/adapter/aws-lambda/index.ts\",\n    \"./vercel\": \"./src/adapter/vercel/index.ts\",\n    \"./netlify\": \"./src/adapter/netlify/index.ts\",\n    \"./lambda-edge\": \"./src/adapter/lambda-edge/index.ts\",\n    \"./service-worker\": \"./src/adapter/service-worker/index.ts\",\n    \"./testing\": \"./src/helper/testing/index.ts\",\n    \"./dev\": \"./src/helper/dev/index.ts\",\n    \"./ws\": \"./src/helper/websocket/index.ts\",\n    \"./conninfo\": \"./src/helper/conninfo/index.ts\",\n    \"./proxy\": \"./src/helper/proxy/index.ts\",\n    \"./utils/body\": \"./src/utils/body.ts\",\n    \"./utils/buffer\": \"./src/utils/buffer.ts\",\n    \"./utils/color\": \"./src/utils/color.ts\",\n    \"./utils/concurrent\": \"./src/utils/concurrent.ts\",\n    \"./utils/cookie\": \"./src/utils/cookie.ts\",\n    \"./utils/crypto\": \"./src/utils/crypto.ts\",\n    \"./utils/encode\": \"./src/utils/encode.ts\",\n    \"./utils/filepath\": \"./src/utils/filepath.ts\",\n    \"./utils/handler\": \"./src/utils/handler.ts\",\n    \"./utils/headers\": \"./src/utils/headers.ts\",\n    \"./utils/html\": \"./src/utils/html.ts\",\n    \"./utils/http-status\": \"./src/utils/http-status.ts\",\n    \"./utils/accept\": \"./src/utils/accept.ts\",\n    \"./utils/jwt\": \"./src/utils/jwt/index.ts\",\n    \"./utils/jwt/jwa\": \"./src/utils/jwt/jwa.ts\",\n    \"./utils/jwt/jws\": \"./src/utils/jwt/jws.ts\",\n    \"./utils/jwt/jwt\": \"./src/utils/jwt/jwt.ts\",\n    \"./utils/jwt/types\": \"./src/utils/jwt/types.ts\",\n    \"./utils/jwt/utf8\": \"./src/utils/jwt/utf8.ts\",\n    \"./utils/mime\": \"./src/utils/mime.ts\",\n    \"./utils/stream\": \"./src/utils/stream.ts\",\n    \"./utils/types\": \"./src/utils/types.ts\",\n    \"./utils/url\": \"./src/utils/url.ts\",\n    \"./utils/ipaddr\": \"./src/utils/ipaddr.ts\"\n  },\n  \"publish\": {\n    \"include\": [\"jsr.json\", \"LICENSE\", \"README.md\", \"src/**/*.ts\"],\n    \"exclude\": [\"src/**/*.test.ts\", \"src/**/*.test.tsx\"]\n  }\n}\n"
  },
  {
    "path": "package.cjs.json",
    "content": "{\n  \"type\": \"commonjs\"\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"hono\",\n  \"version\": \"4.12.8\",\n  \"description\": \"Web framework built on Web Standards\",\n  \"main\": \"dist/cjs/index.js\",\n  \"type\": \"module\",\n  \"module\": \"dist/index.js\",\n  \"types\": \"dist/types/index.d.ts\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"scripts\": {\n    \"test\": \"tsc --noEmit && vitest --run\",\n    \"test:watch\": \"vitest --watch\",\n    \"test:deno\": \"deno test --allow-read --allow-env --allow-write --allow-net -c runtime-tests/deno/deno.json runtime-tests/deno && deno test --no-lock -c runtime-tests/deno-jsx/deno.precompile.json runtime-tests/deno-jsx && deno test --no-lock -c runtime-tests/deno-jsx/deno.react-jsx.json runtime-tests/deno-jsx\",\n    \"test:bun\": \"bun test --jsx-import-source ../../src/jsx runtime-tests/bun/*\",\n    \"test:fastly\": \"vitest --run --project fastly\",\n    \"test:node\": \"vitest --run --project node\",\n    \"test:workerd\": \"vitest --run --project workerd\",\n    \"test:lambda\": \"vitest --run --project lambda\",\n    \"test:lambda-edge\": \"vitest --run --project lambda-edge\",\n    \"test:all\": \"bun run test && bun test:deno && bun test:bun\",\n    \"lint\": \"eslint src runtime-tests build perf-measures benchmarks\",\n    \"lint:fix\": \"eslint src runtime-tests build perf-measures benchmarks --fix\",\n    \"format\": \"prettier --check --cache \\\"src/**/*.{js,ts,tsx}\\\" \\\"runtime-tests/**/*.{js,ts,tsx}\\\" \\\"build/**/*.{js,ts,tsx}\\\" \\\"perf-measures/**/*.{js,ts,tsx}\\\" \\\"benchmarks/**/*.{js,ts,tsx}\\\"\",\n    \"format:fix\": \"prettier --write --cache --cache-strategy metadata \\\"src/**/*.{js,ts,tsx}\\\" \\\"runtime-tests/**/*.{js,ts,tsx}\\\" \\\"build/**/*.{js,ts,tsx}\\\" \\\"perf-measures/**/*.{js,ts,tsx}\\\" \\\"benchmarks/**/*.{js,ts,tsx}\\\"\",\n    \"editorconfig-checker\": \"editorconfig-checker\",\n    \"copy:package.cjs.json\": \"cp ./package.cjs.json ./dist/cjs/package.json && cp ./package.cjs.json ./dist/types/package.json\",\n    \"build\": \"bun run --shell bun remove-dist && bun ./build/build.ts && bun run copy:package.cjs.json\",\n    \"postbuild\": \"publint\",\n    \"watch\": \"bun run --shell bun remove-dist && bun ./build/build.ts --watch && bun run copy:package.cjs.json\",\n    \"coverage\": \"vitest --run --coverage\",\n    \"prerelease\": \"bun test:deno && bun run build\",\n    \"release\": \"np\",\n    \"remove-dist\": \"rm -rf dist\"\n  },\n  \"exports\": {\n    \".\": {\n      \"types\": \"./dist/types/index.d.ts\",\n      \"import\": \"./dist/index.js\",\n      \"require\": \"./dist/cjs/index.js\"\n    },\n    \"./request\": {\n      \"types\": \"./dist/types/request.d.ts\",\n      \"import\": \"./dist/request.js\",\n      \"require\": \"./dist/cjs/request.js\"\n    },\n    \"./types\": {\n      \"types\": \"./dist/types/types.d.ts\",\n      \"import\": \"./dist/types.js\",\n      \"require\": \"./dist/cjs/types.js\"\n    },\n    \"./hono-base\": {\n      \"types\": \"./dist/types/hono-base.d.ts\",\n      \"import\": \"./dist/hono-base.js\",\n      \"require\": \"./dist/cjs/hono-base.js\"\n    },\n    \"./tiny\": {\n      \"types\": \"./dist/types/preset/tiny.d.ts\",\n      \"import\": \"./dist/preset/tiny.js\",\n      \"require\": \"./dist/cjs/preset/tiny.js\"\n    },\n    \"./quick\": {\n      \"types\": \"./dist/types/preset/quick.d.ts\",\n      \"import\": \"./dist/preset/quick.js\",\n      \"require\": \"./dist/cjs/preset/quick.js\"\n    },\n    \"./http-exception\": {\n      \"types\": \"./dist/types/http-exception.d.ts\",\n      \"import\": \"./dist/http-exception.js\",\n      \"require\": \"./dist/cjs/http-exception.js\"\n    },\n    \"./basic-auth\": {\n      \"types\": \"./dist/types/middleware/basic-auth/index.d.ts\",\n      \"import\": \"./dist/middleware/basic-auth/index.js\",\n      \"require\": \"./dist/cjs/middleware/basic-auth/index.js\"\n    },\n    \"./bearer-auth\": {\n      \"types\": \"./dist/types/middleware/bearer-auth/index.d.ts\",\n      \"import\": \"./dist/middleware/bearer-auth/index.js\",\n      \"require\": \"./dist/cjs/middleware/bearer-auth/index.js\"\n    },\n    \"./body-limit\": {\n      \"types\": \"./dist/types/middleware/body-limit/index.d.ts\",\n      \"import\": \"./dist/middleware/body-limit/index.js\",\n      \"require\": \"./dist/cjs/middleware/body-limit/index.js\"\n    },\n    \"./ip-restriction\": {\n      \"types\": \"./dist/types/middleware/ip-restriction/index.d.ts\",\n      \"import\": \"./dist/middleware/ip-restriction/index.js\",\n      \"require\": \"./dist/cjs/middleware/ip-restriction/index.js\"\n    },\n    \"./cache\": {\n      \"types\": \"./dist/types/middleware/cache/index.d.ts\",\n      \"import\": \"./dist/middleware/cache/index.js\",\n      \"require\": \"./dist/cjs/middleware/cache/index.js\"\n    },\n    \"./route\": {\n      \"types\": \"./dist/types/helper/route/index.d.ts\",\n      \"import\": \"./dist/helper/route/index.js\",\n      \"require\": \"./dist/cjs/helper/route/index.js\"\n    },\n    \"./cookie\": {\n      \"types\": \"./dist/types/helper/cookie/index.d.ts\",\n      \"import\": \"./dist/helper/cookie/index.js\",\n      \"require\": \"./dist/cjs/helper/cookie/index.js\"\n    },\n    \"./accepts\": {\n      \"types\": \"./dist/types/helper/accepts/index.d.ts\",\n      \"import\": \"./dist/helper/accepts/index.js\",\n      \"require\": \"./dist/cjs/helper/accepts/index.js\"\n    },\n    \"./compress\": {\n      \"types\": \"./dist/types/middleware/compress/index.d.ts\",\n      \"import\": \"./dist/middleware/compress/index.js\",\n      \"require\": \"./dist/cjs/middleware/compress/index.js\"\n    },\n    \"./context-storage\": {\n      \"types\": \"./dist/types/middleware/context-storage/index.d.ts\",\n      \"import\": \"./dist/middleware/context-storage/index.js\",\n      \"require\": \"./dist/cjs/middleware/context-storage/index.js\"\n    },\n    \"./cors\": {\n      \"types\": \"./dist/types/middleware/cors/index.d.ts\",\n      \"import\": \"./dist/middleware/cors/index.js\",\n      \"require\": \"./dist/cjs/middleware/cors/index.js\"\n    },\n    \"./csrf\": {\n      \"types\": \"./dist/types/middleware/csrf/index.d.ts\",\n      \"import\": \"./dist/middleware/csrf/index.js\",\n      \"require\": \"./dist/cjs/middleware/csrf/index.js\"\n    },\n    \"./etag\": {\n      \"types\": \"./dist/types/middleware/etag/index.d.ts\",\n      \"import\": \"./dist/middleware/etag/index.js\",\n      \"require\": \"./dist/cjs/middleware/etag/index.js\"\n    },\n    \"./trailing-slash\": {\n      \"types\": \"./dist/types/middleware/trailing-slash/index.d.ts\",\n      \"import\": \"./dist/middleware/trailing-slash/index.js\",\n      \"require\": \"./dist/cjs/middleware/trailing-slash/index.js\"\n    },\n    \"./html\": {\n      \"types\": \"./dist/types/helper/html/index.d.ts\",\n      \"import\": \"./dist/helper/html/index.js\",\n      \"require\": \"./dist/cjs/helper/html/index.js\"\n    },\n    \"./css\": {\n      \"types\": \"./dist/types/helper/css/index.d.ts\",\n      \"import\": \"./dist/helper/css/index.js\",\n      \"require\": \"./dist/cjs/helper/css/index.js\"\n    },\n    \"./jsx\": {\n      \"types\": \"./dist/types/jsx/index.d.ts\",\n      \"import\": \"./dist/jsx/index.js\",\n      \"require\": \"./dist/cjs/jsx/index.js\"\n    },\n    \"./jsx/jsx-dev-runtime\": {\n      \"types\": \"./dist/types/jsx/jsx-dev-runtime.d.ts\",\n      \"import\": \"./dist/jsx/jsx-dev-runtime.js\",\n      \"require\": \"./dist/cjs/jsx/jsx-dev-runtime.js\"\n    },\n    \"./jsx/jsx-runtime\": {\n      \"types\": \"./dist/types/jsx/jsx-runtime.d.ts\",\n      \"import\": \"./dist/jsx/jsx-runtime.js\",\n      \"require\": \"./dist/cjs/jsx/jsx-runtime.js\"\n    },\n    \"./jsx/streaming\": {\n      \"types\": \"./dist/types/jsx/streaming.d.ts\",\n      \"import\": \"./dist/jsx/streaming.js\",\n      \"require\": \"./dist/cjs/jsx/streaming.js\"\n    },\n    \"./jsx-renderer\": {\n      \"types\": \"./dist/types/middleware/jsx-renderer/index.d.ts\",\n      \"import\": \"./dist/middleware/jsx-renderer/index.js\",\n      \"require\": \"./dist/cjs/middleware/jsx-renderer/index.js\"\n    },\n    \"./jsx/dom\": {\n      \"types\": \"./dist/types/jsx/dom/index.d.ts\",\n      \"import\": \"./dist/jsx/dom/index.js\",\n      \"require\": \"./dist/cjs/jsx/dom/index.js\"\n    },\n    \"./jsx/dom/jsx-dev-runtime\": {\n      \"types\": \"./dist/types/jsx/dom/jsx-dev-runtime.d.ts\",\n      \"import\": \"./dist/jsx/dom/jsx-dev-runtime.js\",\n      \"require\": \"./dist/cjs/jsx/dom/jsx-dev-runtime.js\"\n    },\n    \"./jsx/dom/jsx-runtime\": {\n      \"types\": \"./dist/types/jsx/dom/jsx-runtime.d.ts\",\n      \"import\": \"./dist/jsx/dom/jsx-runtime.js\",\n      \"require\": \"./dist/cjs/jsx/dom/jsx-runtime.js\"\n    },\n    \"./jsx/dom/client\": {\n      \"types\": \"./dist/types/jsx/dom/client.d.ts\",\n      \"import\": \"./dist/jsx/dom/client.js\",\n      \"require\": \"./dist/cjs/jsx/dom/client.js\"\n    },\n    \"./jsx/dom/css\": {\n      \"types\": \"./dist/types/jsx/dom/css.d.ts\",\n      \"import\": \"./dist/jsx/dom/css.js\",\n      \"require\": \"./dist/cjs/jsx/dom/css.js\"\n    },\n    \"./jsx/dom/server\": {\n      \"types\": \"./dist/types/jsx/dom/server.d.ts\",\n      \"import\": \"./dist/jsx/dom/server.js\",\n      \"require\": \"./dist/cjs/jsx/dom/server.js\"\n    },\n    \"./jwt\": {\n      \"types\": \"./dist/types/middleware/jwt/index.d.ts\",\n      \"import\": \"./dist/middleware/jwt/index.js\",\n      \"require\": \"./dist/cjs/middleware/jwt/index.js\"\n    },\n    \"./jwk\": {\n      \"types\": \"./dist/types/middleware/jwk/index.d.ts\",\n      \"import\": \"./dist/middleware/jwk/index.js\",\n      \"require\": \"./dist/cjs/middleware/jwk/index.js\"\n    },\n    \"./timeout\": {\n      \"types\": \"./dist/types/middleware/timeout/index.d.ts\",\n      \"import\": \"./dist/middleware/timeout/index.js\",\n      \"require\": \"./dist/cjs/middleware/timeout/index.js\"\n    },\n    \"./timing\": {\n      \"types\": \"./dist/types/middleware/timing/index.d.ts\",\n      \"import\": \"./dist/middleware/timing/index.js\",\n      \"require\": \"./dist/cjs/middleware/timing/index.js\"\n    },\n    \"./logger\": {\n      \"types\": \"./dist/types/middleware/logger/index.d.ts\",\n      \"import\": \"./dist/middleware/logger/index.js\",\n      \"require\": \"./dist/cjs/middleware/logger/index.js\"\n    },\n    \"./method-override\": {\n      \"types\": \"./dist/types/middleware/method-override/index.d.ts\",\n      \"import\": \"./dist/middleware/method-override/index.js\",\n      \"require\": \"./dist/cjs/middleware/method-override/index.js\"\n    },\n    \"./powered-by\": {\n      \"types\": \"./dist/types/middleware/powered-by/index.d.ts\",\n      \"import\": \"./dist/middleware/powered-by/index.js\",\n      \"require\": \"./dist/cjs/middleware/powered-by/index.js\"\n    },\n    \"./pretty-json\": {\n      \"types\": \"./dist/types/middleware/pretty-json/index.d.ts\",\n      \"import\": \"./dist/middleware/pretty-json/index.js\",\n      \"require\": \"./dist/cjs/middleware/pretty-json/index.js\"\n    },\n    \"./request-id\": {\n      \"types\": \"./dist/types/middleware/request-id/index.d.ts\",\n      \"import\": \"./dist/middleware/request-id/index.js\",\n      \"require\": \"./dist/cjs/middleware/request-id/index.js\"\n    },\n    \"./language\": {\n      \"types\": \"./dist/types/middleware/language/index.d.ts\",\n      \"import\": \"./dist/middleware/language/index.js\",\n      \"require\": \"./dist/cjs/middleware/language/index.js\"\n    },\n    \"./secure-headers\": {\n      \"types\": \"./dist/types/middleware/secure-headers/index.d.ts\",\n      \"import\": \"./dist/middleware/secure-headers/index.js\",\n      \"require\": \"./dist/cjs/middleware/secure-headers/index.js\"\n    },\n    \"./combine\": {\n      \"types\": \"./dist/types/middleware/combine/index.d.ts\",\n      \"import\": \"./dist/middleware/combine/index.js\",\n      \"require\": \"./dist/cjs/middleware/combine/index.js\"\n    },\n    \"./ssg\": {\n      \"types\": \"./dist/types/helper/ssg/index.d.ts\",\n      \"import\": \"./dist/helper/ssg/index.js\",\n      \"require\": \"./dist/cjs/helper/ssg/index.js\"\n    },\n    \"./streaming\": {\n      \"types\": \"./dist/types/helper/streaming/index.d.ts\",\n      \"import\": \"./dist/helper/streaming/index.js\",\n      \"require\": \"./dist/cjs/helper/streaming/index.js\"\n    },\n    \"./validator\": {\n      \"types\": \"./dist/types/validator/index.d.ts\",\n      \"import\": \"./dist/validator/index.js\",\n      \"require\": \"./dist/cjs/validator/index.js\"\n    },\n    \"./router\": {\n      \"types\": \"./dist/types/router.d.ts\",\n      \"import\": \"./dist/router.js\",\n      \"require\": \"./dist/cjs/router.js\"\n    },\n    \"./router/reg-exp-router\": {\n      \"types\": \"./dist/types/router/reg-exp-router/index.d.ts\",\n      \"import\": \"./dist/router/reg-exp-router/index.js\",\n      \"require\": \"./dist/cjs/router/reg-exp-router/index.js\"\n    },\n    \"./router/smart-router\": {\n      \"types\": \"./dist/types/router/smart-router/index.d.ts\",\n      \"import\": \"./dist/router/smart-router/index.js\",\n      \"require\": \"./dist/cjs/router/smart-router/index.js\"\n    },\n    \"./router/trie-router\": {\n      \"types\": \"./dist/types/router/trie-router/index.d.ts\",\n      \"import\": \"./dist/router/trie-router/index.js\",\n      \"require\": \"./dist/cjs/router/trie-router/index.js\"\n    },\n    \"./router/pattern-router\": {\n      \"types\": \"./dist/types/router/pattern-router/index.d.ts\",\n      \"import\": \"./dist/router/pattern-router/index.js\",\n      \"require\": \"./dist/cjs/router/pattern-router/index.js\"\n    },\n    \"./router/linear-router\": {\n      \"types\": \"./dist/types/router/linear-router/index.d.ts\",\n      \"import\": \"./dist/router/linear-router/index.js\",\n      \"require\": \"./dist/cjs/router/linear-router/index.js\"\n    },\n    \"./utils/jwt\": {\n      \"types\": \"./dist/types/utils/jwt/index.d.ts\",\n      \"import\": \"./dist/utils/jwt/index.js\",\n      \"require\": \"./dist/cjs/utils/jwt/index.js\"\n    },\n    \"./utils/*\": {\n      \"types\": \"./dist/types/utils/*.d.ts\",\n      \"import\": \"./dist/utils/*.js\",\n      \"require\": \"./dist/cjs/utils/*.js\"\n    },\n    \"./client\": {\n      \"types\": \"./dist/types/client/index.d.ts\",\n      \"import\": \"./dist/client/index.js\",\n      \"require\": \"./dist/cjs/client/index.js\"\n    },\n    \"./adapter\": {\n      \"types\": \"./dist/types/helper/adapter/index.d.ts\",\n      \"import\": \"./dist/helper/adapter/index.js\",\n      \"require\": \"./dist/cjs/helper/adapter/index.js\"\n    },\n    \"./factory\": {\n      \"types\": \"./dist/types/helper/factory/index.d.ts\",\n      \"import\": \"./dist/helper/factory/index.js\",\n      \"require\": \"./dist/cjs/helper/factory/index.js\"\n    },\n    \"./serve-static\": {\n      \"types\": \"./dist/types/middleware/serve-static/index.d.ts\",\n      \"import\": \"./dist/middleware/serve-static/index.js\",\n      \"require\": \"./dist/cjs/middleware/serve-static/index.js\"\n    },\n    \"./cloudflare-workers\": {\n      \"types\": \"./dist/types/adapter/cloudflare-workers/index.d.ts\",\n      \"import\": \"./dist/adapter/cloudflare-workers/index.js\",\n      \"require\": \"./dist/cjs/adapter/cloudflare-workers/index.js\"\n    },\n    \"./cloudflare-pages\": {\n      \"types\": \"./dist/types/adapter/cloudflare-pages/index.d.ts\",\n      \"import\": \"./dist/adapter/cloudflare-pages/index.js\",\n      \"require\": \"./dist/cjs/adapter/cloudflare-pages/index.js\"\n    },\n    \"./deno\": {\n      \"types\": \"./dist/types/adapter/deno/index.d.ts\",\n      \"import\": \"./dist/adapter/deno/index.js\",\n      \"require\": \"./dist/cjs/adapter/deno/index.js\"\n    },\n    \"./bun\": {\n      \"types\": \"./dist/types/adapter/bun/index.d.ts\",\n      \"import\": \"./dist/adapter/bun/index.js\",\n      \"require\": \"./dist/cjs/adapter/bun/index.js\"\n    },\n    \"./aws-lambda\": {\n      \"types\": \"./dist/types/adapter/aws-lambda/index.d.ts\",\n      \"import\": \"./dist/adapter/aws-lambda/index.js\",\n      \"require\": \"./dist/cjs/adapter/aws-lambda/index.js\"\n    },\n    \"./vercel\": {\n      \"types\": \"./dist/types/adapter/vercel/index.d.ts\",\n      \"import\": \"./dist/adapter/vercel/index.js\",\n      \"require\": \"./dist/cjs/adapter/vercel/index.js\"\n    },\n    \"./netlify\": {\n      \"types\": \"./dist/types/adapter/netlify/index.d.ts\",\n      \"import\": \"./dist/adapter/netlify/index.js\",\n      \"require\": \"./dist/cjs/adapter/netlify/index.js\"\n    },\n    \"./lambda-edge\": {\n      \"types\": \"./dist/types/adapter/lambda-edge/index.d.ts\",\n      \"import\": \"./dist/adapter/lambda-edge/index.js\",\n      \"require\": \"./dist/cjs/adapter/lambda-edge/index.js\"\n    },\n    \"./service-worker\": {\n      \"types\": \"./dist/types/adapter/service-worker/index.d.ts\",\n      \"import\": \"./dist/adapter/service-worker/index.js\",\n      \"require\": \"./dist/cjs/adapter/service-worker/index.js\"\n    },\n    \"./testing\": {\n      \"types\": \"./dist/types/helper/testing/index.d.ts\",\n      \"import\": \"./dist/helper/testing/index.js\",\n      \"require\": \"./dist/cjs/helper/testing/index.js\"\n    },\n    \"./dev\": {\n      \"types\": \"./dist/types/helper/dev/index.d.ts\",\n      \"import\": \"./dist/helper/dev/index.js\",\n      \"require\": \"./dist/cjs/helper/dev/index.js\"\n    },\n    \"./ws\": {\n      \"types\": \"./dist/types/helper/websocket/index.d.ts\",\n      \"import\": \"./dist/helper/websocket/index.js\",\n      \"require\": \"./dist/cjs/helper/websocket/index.js\"\n    },\n    \"./conninfo\": {\n      \"types\": \"./dist/types/helper/conninfo/index.d.ts\",\n      \"import\": \"./dist/helper/conninfo/index.js\",\n      \"require\": \"./dist/cjs/helper/conninfo/index.js\"\n    },\n    \"./proxy\": {\n      \"types\": \"./dist/types/helper/proxy/index.d.ts\",\n      \"import\": \"./dist/helper/proxy/index.js\",\n      \"require\": \"./dist/cjs/helper/proxy/index.js\"\n    }\n  },\n  \"typesVersions\": {\n    \"*\": {\n      \"request\": [\n        \"./dist/types/request\"\n      ],\n      \"types\": [\n        \"./dist/types/types\"\n      ],\n      \"hono-base\": [\n        \"./dist/types/hono-base\"\n      ],\n      \"tiny\": [\n        \"./dist/types/preset/tiny\"\n      ],\n      \"quick\": [\n        \"./dist/types/preset/quick\"\n      ],\n      \"http-exception\": [\n        \"./dist/types/http-exception\"\n      ],\n      \"basic-auth\": [\n        \"./dist/types/middleware/basic-auth\"\n      ],\n      \"bearer-auth\": [\n        \"./dist/types/middleware/bearer-auth\"\n      ],\n      \"body-limit\": [\n        \"./dist/types/middleware/body-limit\"\n      ],\n      \"ip-restriction\": [\n        \"./dist/types/middleware/ip-restriction\"\n      ],\n      \"cache\": [\n        \"./dist/types/middleware/cache\"\n      ],\n      \"route\": [\n        \"./dist/types/helper/route\"\n      ],\n      \"cookie\": [\n        \"./dist/types/helper/cookie\"\n      ],\n      \"accepts\": [\n        \"./dist/types/helper/accepts\"\n      ],\n      \"compress\": [\n        \"./dist/types/middleware/compress\"\n      ],\n      \"context-storage\": [\n        \"./dist/types/middleware/context-storage\"\n      ],\n      \"cors\": [\n        \"./dist/types/middleware/cors\"\n      ],\n      \"csrf\": [\n        \"./dist/types/middleware/csrf\"\n      ],\n      \"etag\": [\n        \"./dist/types/middleware/etag\"\n      ],\n      \"trailing-slash\": [\n        \"./dist/types/middleware/trailing-slash\"\n      ],\n      \"html\": [\n        \"./dist/types/helper/html\"\n      ],\n      \"css\": [\n        \"./dist/types/helper/css\"\n      ],\n      \"jsx\": [\n        \"./dist/types/jsx\"\n      ],\n      \"jsx/jsx-runtime\": [\n        \"./dist/types/jsx/jsx-runtime.d.ts\"\n      ],\n      \"jsx/jsx-dev-runtime\": [\n        \"./dist/types/jsx/jsx-dev-runtime.d.ts\"\n      ],\n      \"jsx/streaming\": [\n        \"./dist/types/jsx/streaming.d.ts\"\n      ],\n      \"jsx-renderer\": [\n        \"./dist/types/middleware/jsx-renderer\"\n      ],\n      \"jsx/dom\": [\n        \"./dist/types/jsx/dom\"\n      ],\n      \"jsx/dom/client\": [\n        \"./dist/types/jsx/dom/client.d.ts\"\n      ],\n      \"jsx/dom/css\": [\n        \"./dist/types/jsx/dom/css.d.ts\"\n      ],\n      \"jsx/dom/server\": [\n        \"./dist/types/jsx/dom/server.d.ts\"\n      ],\n      \"jwt\": [\n        \"./dist/types/middleware/jwt\"\n      ],\n      \"timeout\": [\n        \"./dist/types/middleware/timeout\"\n      ],\n      \"timing\": [\n        \"./dist/types/middleware/timing\"\n      ],\n      \"logger\": [\n        \"./dist/types/middleware/logger\"\n      ],\n      \"method-override\": [\n        \"./dist/types/middleware/method-override\"\n      ],\n      \"powered-by\": [\n        \"./dist/types/middleware/powered-by\"\n      ],\n      \"pretty-json\": [\n        \"./dist/types/middleware/pretty-json\"\n      ],\n      \"request-id\": [\n        \"./dist/types/middleware/request-id\"\n      ],\n      \"language\": [\n        \"./dist/types/middleware/language\"\n      ],\n      \"streaming\": [\n        \"./dist/types/helper/streaming\"\n      ],\n      \"ssg\": [\n        \"./dist/types/helper/ssg\"\n      ],\n      \"secure-headers\": [\n        \"./dist/types/middleware/secure-headers\"\n      ],\n      \"combine\": [\n        \"./dist/types/middleware/combine\"\n      ],\n      \"validator\": [\n        \"./dist/types/validator/index.d.ts\"\n      ],\n      \"router\": [\n        \"./dist/types/router.d.ts\"\n      ],\n      \"router/reg-exp-router\": [\n        \"./dist/types/router/reg-exp-router/router.d.ts\"\n      ],\n      \"router/smart-router\": [\n        \"./dist/types/router/smart-router/router.d.ts\"\n      ],\n      \"router/trie-router\": [\n        \"./dist/types/router/trie-router/router.d.ts\"\n      ],\n      \"router/pattern-router\": [\n        \"./dist/types/router/pattern-router/router.d.ts\"\n      ],\n      \"router/linear-router\": [\n        \"./dist/types/router/linear-router/router.d.ts\"\n      ],\n      \"utils/jwt\": [\n        \"./dist/types/utils/jwt/index.d.ts\"\n      ],\n      \"utils/*\": [\n        \"./dist/types/utils/*\"\n      ],\n      \"client\": [\n        \"./dist/types/client/index.d.ts\"\n      ],\n      \"adapter\": [\n        \"./dist/types/helper/adapter/index.d.ts\"\n      ],\n      \"factory\": [\n        \"./dist/types/helper/factory/index.d.ts\"\n      ],\n      \"serve-static\": [\n        \"./dist/types/middleware/serve-static\"\n      ],\n      \"cloudflare-workers\": [\n        \"./dist/types/adapter/cloudflare-workers\"\n      ],\n      \"cloudflare-pages\": [\n        \"./dist/types/adapter/cloudflare-pages\"\n      ],\n      \"deno\": [\n        \"./dist/types/adapter/deno\"\n      ],\n      \"bun\": [\n        \"./dist/types/adapter/bun\"\n      ],\n      \"nextjs\": [\n        \"./dist/types/adapter/nextjs\"\n      ],\n      \"aws-lambda\": [\n        \"./dist/types/adapter/aws-lambda\"\n      ],\n      \"vercel\": [\n        \"./dist/types/adapter/vercel\"\n      ],\n      \"lambda-edge\": [\n        \"./dist/types/adapter/lambda-edge\"\n      ],\n      \"service-worker\": [\n        \"./dist/types/adapter/service-worker\"\n      ],\n      \"testing\": [\n        \"./dist/types/helper/testing\"\n      ],\n      \"dev\": [\n        \"./dist/types/helper/dev\"\n      ],\n      \"ws\": [\n        \"./dist/types/helper/websocket\"\n      ],\n      \"conninfo\": [\n        \"./dist/types/helper/conninfo\"\n      ],\n      \"proxy\": [\n        \"./dist/types/helper/proxy\"\n      ]\n    }\n  },\n  \"author\": \"Yusuke Wada <yusuke@kamawada.com> (https://github.com/yusukebe)\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/honojs/hono.git\"\n  },\n  \"publishConfig\": {\n    \"registry\": \"https://registry.npmjs.org\"\n  },\n  \"homepage\": \"https://hono.dev\",\n  \"keywords\": [\n    \"hono\",\n    \"web\",\n    \"app\",\n    \"http\",\n    \"application\",\n    \"framework\",\n    \"router\",\n    \"cloudflare\",\n    \"workers\",\n    \"fastly\",\n    \"compute\",\n    \"deno\",\n    \"bun\",\n    \"lambda\",\n    \"nodejs\"\n  ],\n  \"devDependencies\": {\n    \"@hono/eslint-config\": \"^2.1.0\",\n    \"@hono/node-server\": \"^1.13.5\",\n    \"@types/glob\": \"^9.0.0\",\n    \"@types/jsdom\": \"^21.1.7\",\n    \"@types/node\": \"^24.3.0\",\n    \"@types/ws\": \"^8.18.1\",\n    \"@typescript/native-preview\": \"7.0.0-dev.20260210.1\",\n    \"@vitest/coverage-v8\": \"^3.2.4\",\n    \"arg\": \"^5.0.2\",\n    \"bun-types\": \"^1.2.20\",\n    \"editorconfig-checker\": \"6.1.1\",\n    \"esbuild\": \"^0.27.1\",\n    \"eslint\": \"^9.39.3\",\n    \"glob\": \"^11.0.0\",\n    \"jsdom\": \"22.1.0\",\n    \"msw\": \"^2.6.0\",\n    \"np\": \"10.2.0\",\n    \"oxc-parser\": \"^0.96.0\",\n    \"pkg-pr-new\": \"^0.0.53\",\n    \"prettier\": \"3.7.4\",\n    \"publint\": \"0.3.15\",\n    \"typescript\": \"^5.9.2\",\n    \"undici\": \"^6.21.3\",\n    \"vite-plugin-fastly-js-compute\": \"^0.4.2\",\n    \"vitest\": \"^3.2.4\",\n    \"wrangler\": \"4.12.0\",\n    \"ws\": \"^8.18.0\",\n    \"zod\": \"^3.23.8\"\n  },\n  \"packageManager\": \"bun@1.2.20\",\n  \"engines\": {\n    \"node\": \">=16.9.0\"\n  }\n}\n"
  },
  {
    "path": "perf-measures/.octocov.consolidated.perf-measures.main.yml",
    "content": "locale: 'en'\nrepository: ${GITHUB_REPOSITORY}/perf-measures\ncoverage:\n  if: false\ncodeToTestRatio:\n  if: false\ntestExecutionTime:\n  if: false\nreport:\n  datastores:\n    - artifact://${GITHUB_REPOSITORY}\nsummary:\n  if: true\ncustomMetrics:\n  bundle-size-check:\n    key: bundle-size-check\n  speed-check:\n    key: speed-check\n  diagnostics-tsc:\n    key: diagnostics-tsc\n  diagnostics-typescript-go:\n    key: diagnostics-typescript-go\n"
  },
  {
    "path": "perf-measures/.octocov.consolidated.perf-measures.yml",
    "content": "locale: 'en'\nrepository: ${GITHUB_REPOSITORY}/perf-measures\ncoverage:\n  if: false\ncodeToTestRatio:\n  if: false\ntestExecutionTime:\n  if: false\ndiff:\n  datastores:\n    - artifact://${GITHUB_REPOSITORY}\ncomment:\n  if: is_pull_request\nsummary:\n  if: true\ncustomMetrics:\n  bundle-size-check:\n    key: bundle-size-check\n  diagnostics-tsc:\n    key: diagnostics-tsc\n  diagnostics-typescript-go:\n    key: diagnostics-typescript-go\n"
  },
  {
    "path": "perf-measures/bundle-check/.gitignore",
    "content": "generated\n!generated/.gitkeep\nsize.json\n"
  },
  {
    "path": "perf-measures/bundle-check/scripts/check-bundle-size.ts",
    "content": "import * as esbuild from 'esbuild'\nimport * as fs from 'node:fs'\nimport * as os from 'os'\nimport * as path from 'path'\n\nasync function main() {\n  const tempDir = os.tmpdir()\n  const tempFilePath = path.join(tempDir, 'bundle.tmp.js')\n\n  try {\n    await esbuild.build({\n      entryPoints: ['dist/index.js'],\n      bundle: true,\n      minify: true,\n      format: 'esm' as esbuild.Format,\n      target: 'es2022',\n      outfile: tempFilePath,\n    })\n\n    const bundleSize = fs.statSync(tempFilePath).size\n    const metrics = []\n\n    metrics.push({\n      key: 'bundle-size-b',\n      name: 'Bundle Size (B)',\n      value: bundleSize,\n      unit: 'B',\n    })\n\n    metrics.push({\n      key: 'bundle-size-kb',\n      name: 'Bundle Size (KB)',\n      value: parseFloat((bundleSize / 1024).toFixed(2)),\n      unit: 'K',\n    })\n\n    const benchmark = {\n      key: 'bundle-size-check',\n      name: 'Bundle size check',\n      metrics,\n    }\n    console.log(JSON.stringify(benchmark, null, 2))\n  } catch (error) {\n    console.error('Build failed:', error)\n  } finally {\n    if (fs.existsSync(tempFilePath)) {\n      fs.unlinkSync(tempFilePath)\n    }\n  }\n}\n\nmain()\n"
  },
  {
    "path": "perf-measures/type-check/.gitignore",
    "content": "generated\n!generated/.gitkeep\ntrace\n*result.txt\ndiagnostics.json\n"
  },
  {
    "path": "perf-measures/type-check/client.ts",
    "content": "import { hc } from '../../src/client'\nimport type { app } from './generated/app'\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nconst client = hc<typeof app>('/')\n"
  },
  {
    "path": "perf-measures/type-check/scripts/generate-app.ts",
    "content": "import { writeFile } from 'node:fs'\nimport * as path from 'node:path'\n\nconst count = 200\n\nconst generateRoutes = (count: number) => {\n  let routes = `import { Hono } from '../../../src'\nexport const app = new Hono()`\n  for (let i = 1; i <= count; i++) {\n    routes += `\n  .get('/route${i}/:id', (c) => {\n    return c.json({\n      ok: true\n    })\n  })`\n  }\n  return routes\n}\n\nconst routes = generateRoutes(count)\n\nwriteFile(path.join(import.meta.dirname, '../generated/app.ts'), routes, (err) => {\n  if (err) {\n    throw err\n  }\n  console.log(`${count} routes have been written to app.ts`)\n})\n"
  },
  {
    "path": "perf-measures/type-check/scripts/process-results.ts",
    "content": "import * as readline from 'node:readline'\n\nasync function main() {\n  const rl = readline.createInterface({\n    input: process.stdin,\n    output: process.stdout,\n    terminal: false,\n  })\n  const tsImplLabel = process.env['BENCHMARK_TS_IMPL_LABEL']\n  if (!tsImplLabel) {\n    throw new Error('BENCHMARK_TS_IMPL_LABEL must be set')\n  }\n\n  const toKebabCase = (str: string): string => {\n    return str\n      .replace(/([a-z])([A-Z])/g, '$1-$2')\n      .replace(/[\\s_\\/]+/g, '-')\n      .toLowerCase()\n  }\n  const metrics = []\n  for await (const line of rl) {\n    if (!line || line.trim() === '') {\n      continue\n    }\n    const [name, value] = line.split(':')\n    const unitMatch = value?.trim().match(/^(\\d+(\\.\\d+)?)([a-zA-Z]*)$/)\n    if (unitMatch) {\n      const [, number, , unit] = unitMatch\n      metrics.push({\n        key: toKebabCase(name?.trim()),\n        name: name?.trim(),\n        value: parseFloat(number),\n        unit: unit || undefined,\n      })\n    } else {\n      metrics.push({\n        key: toKebabCase(name?.trim()),\n        name: name?.trim(),\n        value: parseFloat(value?.trim()),\n      })\n    }\n  }\n  const benchmark = {\n    key: `diagnostics-${toKebabCase(tsImplLabel)}`,\n    name: `Compiler Diagnostics (${tsImplLabel})`,\n    metrics,\n  }\n  console.log(JSON.stringify(benchmark, null, 2))\n}\n\nmain()\n"
  },
  {
    "path": "perf-measures/type-check/scripts/tsconfig.json",
    "content": "{\n  \"extends\": \"../../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"module\": \"esnext\",\n    \"noEmit\": true\n  }\n}\n"
  },
  {
    "path": "perf-measures/type-check/tsconfig.build.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true\n  },\n  \"exclude\": [\"dist\", \"scripts\"],\n  \"references\": [\n    { \"path\": \"../../tsconfig.build.json\" }\n  ]\n}\n"
  },
  {
    "path": "runtime-tests/bun/.static/plain.txt",
    "content": "Bun!!\n"
  },
  {
    "path": "runtime-tests/bun/color.test.ts",
    "content": "import { expect, test } from 'bun:test'\n\ntest('Bun.build compatibility test', async () => {\n  try {\n    const result = await Bun.build({\n      entrypoints: ['./src/utils/color.ts'],\n      format: 'esm',\n      minify: true,\n      external: [],\n    })\n\n    expect(result.success).toBe(true)\n    expect(result.logs).toHaveLength(0)\n    expect(result.outputs).toHaveLength(1)\n\n    const outputContent = await result.outputs[0].text()\n    expect(outputContent).toBeDefined()\n  } catch (error) {\n    throw new Error(`Bun.build failed: ${error}`)\n  }\n})\n"
  },
  {
    "path": "runtime-tests/bun/index.test.tsx",
    "content": "import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'\nimport fs from 'fs/promises'\nimport path from 'path'\nimport { stream, streamSSE } from '../..//src/helper/streaming'\nimport { serveStatic, toSSG } from '../../src/adapter/bun'\nimport { createBunWebSocket } from '../../src/adapter/bun/websocket'\nimport type { BunWebSocketData } from '../../src/adapter/bun/websocket'\nimport { Context } from '../../src/context'\nimport { env, getRuntimeKey } from '../../src/helper/adapter'\nimport type { WSMessageReceive } from '../../src/helper/websocket'\nimport { Hono } from '../../src/index'\nimport type { PropsWithChildren } from '../../src/jsx'\nimport { basicAuth } from '../../src/middleware/basic-auth'\nimport { jwt } from '../../src/middleware/jwt'\n\ndeclare module '../../src/index' {\n  interface ContextRenderer {\n    (content: string | Promise<string>, head: { title: string }): Response | Promise<Response>\n  }\n}\n\n// Test just only minimal patterns.\n// Because others are tested well in Cloudflare Workers environment already.\n\nBun.env.NAME = 'Bun'\n\ndescribe('Basic', () => {\n  const app = new Hono()\n  app.get('/a/:foo', (c) => {\n    c.header('x-param', c.req.param('foo'))\n    c.header('x-query', c.req.query('q'))\n    return c.text('Hello Bun!')\n  })\n\n  it('Should return 200 Response', async () => {\n    const req = new Request('http://localhost/a/foo?q=bar')\n    const res = await app.request(req)\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('Hello Bun!')\n    expect(res.headers.get('x-param')).toBe('foo')\n    expect(res.headers.get('x-query')).toBe('bar')\n  })\n\n  it('returns current runtime (bun)', async () => {\n    expect(getRuntimeKey()).toBe('bun')\n  })\n})\n\ndescribe('Environment Variables', () => {\n  it('Should return the environment variable', async () => {\n    const c = new Context(new Request('http://localhost/'))\n    const { NAME } = env<{ NAME: string }>(c)\n    expect(NAME).toBe('Bun')\n  })\n})\n\ndescribe('Basic Auth Middleware', () => {\n  const app = new Hono()\n\n  const username = 'hono-user-a'\n  const password = 'hono-password-a'\n  app.use(\n    '/auth/*',\n    basicAuth({\n      username,\n      password,\n    })\n  )\n\n  app.get('/auth/*', () => new Response('auth'))\n\n  it('Should not authorize, return 401 Response', async () => {\n    const req = new Request('http://localhost/auth/a')\n    const res = await app.request(req)\n    expect(res.status).toBe(401)\n    expect(await res.text()).toBe('Unauthorized')\n  })\n\n  it('Should authorize, return 200 Response', async () => {\n    const credential = 'aG9uby11c2VyLWE6aG9uby1wYXNzd29yZC1h'\n    const req = new Request('http://localhost/auth/a')\n    req.headers.set('Authorization', `Basic ${credential}`)\n    const res = await app.request(req)\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('auth')\n  })\n})\n\ndescribe('Serve Static Middleware', () => {\n  const app = new Hono()\n  const onNotFound = vi.fn(() => {})\n  app.all('/favicon.ico', serveStatic({ path: './runtime-tests/bun/favicon.ico' }))\n  app.all(\n    '/favicon-notfound.ico',\n    serveStatic({ path: './runtime-tests/bun/favicon-notfound.ico', onNotFound })\n  )\n  app.use('/favicon-notfound.ico', async (c, next) => {\n    await next()\n    c.header('X-Custom', 'Bun')\n  })\n  app.get(\n    '/static/*',\n    serveStatic({\n      root: './runtime-tests/bun/',\n      onNotFound,\n    })\n  )\n  app.get(\n    '/dot-static/*',\n    serveStatic({\n      root: './runtime-tests/bun/',\n      rewriteRequestPath: (path) => path.replace(/^\\/dot-static/, './.static'),\n    })\n  )\n\n  app.all('/static-absolute-root/*', serveStatic({ root: path.dirname(__filename) }))\n\n  beforeEach(() => onNotFound.mockClear())\n\n  it('Should return static file correctly', async () => {\n    const res = await app.request(new Request('http://localhost/favicon.ico'))\n    await res.arrayBuffer()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Type')).toBe('image/x-icon')\n  })\n\n  it('Should return 404 response', async () => {\n    const res = await app.request(new Request('http://localhost/favicon-notfound.ico'))\n    expect(res.status).toBe(404)\n    expect(res.headers.get('X-Custom')).toBe('Bun')\n    expect(onNotFound).toHaveBeenCalledWith(\n      process.platform === 'win32'\n        ? 'runtime-tests\\\\bun\\\\favicon-notfound.ico'\n        : 'runtime-tests/bun/favicon-notfound.ico',\n      expect.anything()\n    )\n  })\n\n  it('Should return 200 response - /static/plain.txt', async () => {\n    const res = await app.request(new Request('http://localhost/static/plain.txt'))\n    expect(res.status).toBe(200)\n    expect(await res.text()).toMatch(/^Bun!(\\r?\\n)?$/)\n    expect(onNotFound).not.toHaveBeenCalled()\n  })\n\n  it('Should return 200 response - /static/download', async () => {\n    const res = await app.request(new Request('http://localhost/static/download'))\n    expect(res.status).toBe(200)\n    expect(await res.text()).toMatch(/^download(\\r?\\n)?$/)\n    expect(onNotFound).not.toHaveBeenCalled()\n  })\n\n  it('Should return 200 response - /dot-static/plain.txt', async () => {\n    const res = await app.request(new Request('http://localhost/dot-static/plain.txt'))\n    expect(res.status).toBe(200)\n    expect(await res.text()).toMatch(/^Bun!!(\\r?\\n)?$/)\n  })\n\n  it('Should return 200 response - /static/helloworld', async () => {\n    const res = await app.request('http://localhost/static/helloworld')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toMatch(/Hi\\r?\\n/)\n  })\n\n  it('Should return 200 response - /static/hello.world', async () => {\n    const res = await app.request('http://localhost/static/hello.world')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toMatch(/Hi\\r?\\n/)\n  })\n\n  it('Should return 200 response - /static-absolute-root/plain.txt', async () => {\n    const res = await app.request('http://localhost/static-absolute-root/plain.txt')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toMatch(/^Bun!(\\r?\\n)?$/)\n    expect(onNotFound).not.toHaveBeenCalled()\n  })\n})\n\n// Bun support WebCrypto since v0.2.2\n// So, JWT middleware works well.\ndescribe('JWT Auth Middleware', () => {\n  const app = new Hono()\n  app.use('/jwt/*', jwt({ secret: 'a-secret', alg: 'HS256' }))\n  app.get('/jwt/a', (c) => c.text('auth'))\n\n  it('Should not authorize, return 401 Response', async () => {\n    const req = new Request('http://localhost/jwt/a')\n    const res = await app.request(req)\n    expect(res.status).toBe(401)\n    expect(await res.text()).toBe('Unauthorized')\n  })\n\n  it('Should authorize, return 200 Response', async () => {\n    const credential =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n    const req = new Request('http://localhost/jwt/a')\n    req.headers.set('Authorization', `Bearer ${credential}`)\n    const res = await app.request(req)\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('auth')\n  })\n})\n\n// To enable JSX middleware,\n// set \"jsxImportSource\": \"hono/jsx\" in the tsconfig.json\ndescribe('JSX Middleware', () => {\n  const app = new Hono()\n\n  const Layout = (props: PropsWithChildren) => {\n    return <html>{props.children}</html>\n  }\n\n  app.get('/', (c) => {\n    return c.html(<h1>Hello</h1>)\n  })\n  app.get('/nest', (c) => {\n    return c.html(\n      <h1>\n        <a href='/top'>Hello</a>\n      </h1>\n    )\n  })\n  app.get('/layout', (c) => {\n    return c.html(\n      <Layout>\n        <p>hello</p>\n      </Layout>\n    )\n  })\n\n  it('Should return rendered HTML', async () => {\n    const res = await app.request(new Request('http://localhost/'))\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Type')).toBe('text/html; charset=UTF-8')\n    expect(await res.text()).toBe('<h1>Hello</h1>')\n  })\n\n  it('Should return rendered HTML with nest', async () => {\n    const res = await app.request(new Request('http://localhost/nest'))\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Type')).toBe('text/html; charset=UTF-8')\n    expect(await res.text()).toBe('<h1><a href=\"/top\">Hello</a></h1>')\n  })\n\n  it('Should return rendered HTML with Layout', async () => {\n    const res = await app.request(new Request('http://localhost/layout'))\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Type')).toBe('text/html; charset=UTF-8')\n    expect(await res.text()).toBe('<html><p>hello</p></html>')\n  })\n})\n\ndescribe('toSSG function', () => {\n  let app: Hono\n\n  beforeEach(() => {\n    app = new Hono()\n    app.get('/', (c) => c.text('Hello, World!'))\n    app.get('/about', (c) => c.text('About Page'))\n    app.get('/about/some', (c) => c.text('About Page 2tier'))\n    app.post('/about/some/thing', (c) => c.text('About Page 3tier'))\n    app.get('/bravo', (c) => c.html('Bravo Page'))\n    app.get('/Charlie', async (c, next) => {\n      c.setRenderer((content, head) => {\n        return c.html(\n          <html>\n            <head>\n              <title>{head.title || ''}</title>\n            </head>\n            <body>\n              <p>{content}</p>\n            </body>\n          </html>\n        )\n      })\n      await next()\n    })\n    app.get('/Charlie', (c) => {\n      return c.render('Hello!', { title: 'Charlies Page' })\n    })\n  })\n\n  it('Should correctly generate static HTML files for Hono routes', async () => {\n    const result = await toSSG(app, { dir: './static' })\n    expect(result.success).toBeTruthy()\n    expect(result.error).toBeUndefined()\n    expect(result.files).toBeDefined()\n    afterAll(async () => {\n      await deleteDirectory('./static')\n    })\n  })\n})\n\ndescribe('WebSockets Helper', () => {\n  const app = new Hono()\n  const { websocket, upgradeWebSocket } = createBunWebSocket()\n\n  it('Should websockets is working', async () => {\n    const receivedMessagePromise = new Promise<WSMessageReceive>((resolve) =>\n      app.get(\n        '/ws',\n        upgradeWebSocket(() => ({\n          onMessage(evt) {\n            resolve(evt.data)\n          },\n        }))\n      )\n    )\n    const upgradedData = await new Promise<BunWebSocketData>((resolve) =>\n      app.fetch(new Request('http://localhost/ws'), {\n        upgrade: (_req: Request, data: { data: BunWebSocketData }) => {\n          resolve(data.data)\n        },\n      })\n    )\n    const message = Math.random().toString()\n    websocket.message(\n      {\n        close: () => undefined,\n        readyState: 3,\n        data: upgradedData,\n        send: () => undefined,\n      },\n      message\n    )\n    const receivedMessage = await receivedMessagePromise\n    expect(receivedMessage).toBe(message)\n  })\n})\n\nasync function deleteDirectory(dirPath: string) {\n  if (\n    await fs\n      .stat(dirPath)\n      .then((stat) => stat.isDirectory())\n      .catch(() => false)\n  ) {\n    for (const entry of await fs.readdir(dirPath)) {\n      const entryPath = path.join(dirPath, entry)\n      await deleteDirectory(entryPath)\n    }\n    await fs.rmdir(dirPath)\n  } else {\n    await fs.unlink(dirPath)\n  }\n}\n\ndescribe('streaming', () => {\n  const app = new Hono()\n  let server: ReturnType<typeof Bun.serve>\n  let aborted = false\n\n  app.get('/stream', (c) => {\n    return stream(c, async (stream) => {\n      stream.onAbort(() => {\n        aborted = true\n      })\n      return new Promise<void>((resolve) => {\n        stream.onAbort(resolve)\n      })\n    })\n  })\n  app.get('/streamHello', (c) => {\n    return stream(c, async (stream) => {\n      stream.onAbort(() => {\n        aborted = true\n      })\n      await stream.write('Hello')\n    })\n  })\n  app.get('/streamSSE', (c) => {\n    return streamSSE(c, async (stream) => {\n      stream.onAbort(() => {\n        aborted = true\n      })\n      return new Promise<void>((resolve) => {\n        stream.onAbort(resolve)\n      })\n    })\n  })\n  app.get('/streamSSEHello', (c) => {\n    return streamSSE(c, async (stream) => {\n      stream.onAbort(() => {\n        aborted = true\n      })\n      await stream.write('Hello')\n    })\n  })\n\n  beforeEach(() => {\n    aborted = false\n    server = Bun.serve({ port: 0, fetch: app.fetch })\n  })\n\n  afterEach(() => {\n    server.stop()\n  })\n\n  describe('stream', () => {\n    it('Should call onAbort', async () => {\n      const ac = new AbortController()\n      const req = new Request(`http://localhost:${server.port}/stream`, {\n        signal: ac.signal,\n      })\n      expect(aborted).toBe(false)\n      const res = fetch(req).catch(() => {})\n      await new Promise((resolve) => setTimeout(resolve, 10))\n      ac.abort()\n      await res\n      while (!aborted) {\n        await new Promise((resolve) => setTimeout(resolve))\n      }\n      expect(aborted).toBe(true)\n    })\n\n    it('Should not be called onAbort if already closed', async () => {\n      expect(aborted).toBe(false)\n      const res = await fetch(`http://localhost:${server.port}/streamHello`)\n      expect(await res.text()).toBe('Hello')\n      expect(aborted).toBe(false)\n    })\n  })\n\n  describe('streamSSE', () => {\n    it('Should call onAbort', async () => {\n      const ac = new AbortController()\n      const req = new Request(`http://localhost:${server.port}/streamSSE`, {\n        signal: ac.signal,\n      })\n      const res = fetch(req).catch(() => {})\n      await new Promise((resolve) => setTimeout(resolve, 10))\n      ac.abort()\n      await res\n      while (!aborted) {\n        await new Promise((resolve) => setTimeout(resolve))\n      }\n      expect(aborted).toBe(true)\n    })\n\n    it('Should not be called onAbort if already closed', async () => {\n      expect(aborted).toBe(false)\n      const res = await fetch(`http://localhost:${server.port}/streamSSEHello`)\n      expect(await res.text()).toBe('Hello')\n      expect(aborted).toBe(false)\n    })\n  })\n})\n\ndescribe('Buffers', () => {\n  const app = new Hono().get('/', async (c) => {\n    return c.body(Buffer.from('hello'))\n  })\n\n  it('should allow returning buffers', async () => {\n    const res = await app.request(new Request('http://localhost/'))\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hello')\n  })\n})\n"
  },
  {
    "path": "runtime-tests/bun/static/download",
    "content": "download\n"
  },
  {
    "path": "runtime-tests/bun/static/hello.world/index.html",
    "content": "Hi\n"
  },
  {
    "path": "runtime-tests/bun/static/helloworld/index.html",
    "content": "Hi\n"
  },
  {
    "path": "runtime-tests/bun/static/plain.txt",
    "content": "Bun!\n"
  },
  {
    "path": "runtime-tests/bun/static-absolute-root/plain.txt",
    "content": "Bun!\n"
  },
  {
    "path": "runtime-tests/bun/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"jsx\": \"react-jsx\",\n    \"jsxImportSource\": \"hono/jsx\",\n    \"noEmit\": true,\n    \"types\": [\"bun-types\"]\n  },\n  \"references\": [\n    { \"path\": \"../../tsconfig.build.json\" }\n  ]\n}\n"
  },
  {
    "path": "runtime-tests/deno/.static/plain.txt",
    "content": "Deno!!\n"
  },
  {
    "path": "runtime-tests/deno/.vscode/settings.json",
    "content": "{\n  \"eslint.validate\": [\"javascript\", \"javascriptreact\", \"typescript\", \"typescriptreact\"],\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": \"explicit\"\n  },\n  \"deno.enable\": true\n}\n"
  },
  {
    "path": "runtime-tests/deno/deno.json",
    "content": "{\n  \"compilerOptions\": {\n    \"jsx\": \"react-jsx\",\n    \"jsxImportSource\": \"hono/jsx\",\n    \"lib\": [\"deno.ns\", \"dom\", \"dom.iterable\"]\n  },\n  \"unstable\": [\"sloppy-imports\"],\n  \"imports\": {\n    \"@std/assert\": \"jsr:@std/assert@^1.0.3\",\n    \"@std/path\": \"jsr:@std/path@^1.0.3\",\n    \"@std/testing\": \"jsr:@std/testing@^1.0.1\",\n    \"hono/jsx/jsx-runtime\": \"../../src/jsx/jsx-runtime.ts\"\n  }\n}\n"
  },
  {
    "path": "runtime-tests/deno/hono.test.ts",
    "content": "import { assertEquals } from '@std/assert'\n\nimport { Context } from '../../src/context.ts'\nimport { env, getRuntimeKey } from '../../src/helper/adapter/index.ts'\nimport { Hono } from '../../src/hono.ts'\n\n// Test just only minimal patterns.\n// Because others are tested well in Cloudflare Workers environment already.\n\nDeno.env.set('NAME', 'Deno')\n\nDeno.test('Hello World', async () => {\n  const app = new Hono()\n  app.get('/:foo', (c) => {\n    c.header('x-param', c.req.param('foo'))\n    c.header('x-query', c.req.query('q') || '')\n    return c.text('Hello Deno!')\n  })\n\n  const res = await app.request('/foo?q=bar')\n  assertEquals(res.status, 200)\n  assertEquals(await res.text(), 'Hello Deno!')\n  assertEquals(res.headers.get('x-param'), 'foo')\n  assertEquals(res.headers.get('x-query'), 'bar')\n})\n\nDeno.test('runtime', () => {\n  assertEquals(getRuntimeKey(), 'deno')\n})\n\nDeno.test('environment variables', () => {\n  const c = new Context(new Request('http://localhost/'))\n  const { NAME } = env<{ NAME: string }>(c)\n  assertEquals(NAME, 'Deno')\n})\n"
  },
  {
    "path": "runtime-tests/deno/middleware.test.tsx",
    "content": "import { assertEquals, assertMatch } from '@std/assert'\nimport { dirname, fromFileUrl } from '@std/path'\nimport { assertSpyCall, assertSpyCalls, spy } from '@std/testing/mock'\nimport { serveStatic } from '../../src/adapter/deno/index.ts'\nimport { Hono } from '../../src/hono.ts'\nimport { basicAuth } from '../../src/middleware/basic-auth/index.ts'\nimport { jwt } from '../../src/middleware/jwt/index.ts'\n\n// Test just only minimal patterns.\n// Because others are already tested well in Cloudflare Workers environment.\n\nDeno.test('Basic Auth Middleware', async () => {\n  const app = new Hono()\n\n  const username = 'hono'\n  const password = 'ahotproject'\n\n  app.use(\n    '/auth/*',\n    basicAuth({\n      username,\n      password,\n    })\n  )\n\n  app.get('/auth/*', () => new Response('auth'))\n\n  const res = await app.request('http://localhost/auth/a')\n  assertEquals(res.status, 401)\n  assertEquals(await res.text(), 'Unauthorized')\n\n  const credential = 'aG9ubzphaG90cHJvamVjdA=='\n\n  const req = new Request('http://localhost/auth/a')\n  req.headers.set('Authorization', `Basic ${credential}`)\n  const resOK = await app.request(req)\n  assertEquals(resOK.status, 200)\n  assertEquals(await resOK.text(), 'auth')\n\n  const invalidCredential = 'G9ubzphY29vbHByb2plY3Q='\n\n  const req2 = new Request('http://localhost/auth/a')\n  req2.headers.set('Authorization', `Basic ${invalidCredential}`)\n  const resNG = await app.request(req2)\n  assertEquals(resNG.status, 401)\n  assertEquals(await resNG.text(), 'Unauthorized')\n})\n\nDeno.test('JSX middleware', async () => {\n  const app = new Hono()\n  app.get('/', (c) => {\n    return c.html(<h1>Hello</h1>)\n  })\n  const res = await app.request('http://localhost/')\n  assertEquals(res.status, 200)\n  assertEquals(res.headers.get('Content-Type'), 'text/html; charset=UTF-8')\n  assertEquals(await res.text(), '<h1>Hello</h1>')\n\n  // Fragment\n  const template = (\n    <>\n      <p>1</p>\n      <p>2</p>\n    </>\n  )\n  assertEquals(template.toString(), '<p>1</p><p>2</p>')\n})\n\nDeno.test('Serve Static middleware', async () => {\n  const app = new Hono()\n  const onNotFound = spy(() => {})\n  app.all('/favicon.ico', serveStatic({ path: './runtime-tests/deno/favicon.ico' }))\n  app.all(\n    '/favicon-notfound.ico',\n    serveStatic({ path: './runtime-tests/deno/favicon-notfound.ico', onNotFound })\n  )\n  app.use('/favicon-notfound.ico', async (c, next) => {\n    await next()\n    c.header('X-Custom', 'Deno')\n  })\n\n  app.get(\n    '/static/*',\n    serveStatic({\n      root: './runtime-tests/deno',\n      onNotFound,\n    })\n  )\n\n  app.get(\n    '/dot-static/*',\n    serveStatic({\n      root: './runtime-tests/deno',\n      rewriteRequestPath: (path) => path.replace(/^\\/dot-static/, './.static'),\n    })\n  )\n\n  app.get('/static-absolute-root/*', serveStatic({ root: dirname(fromFileUrl(import.meta.url)) }))\n\n  let res = await app.request('http://localhost/favicon.ico')\n  assertEquals(res.status, 200)\n  assertEquals(res.headers.get('Content-Type'), 'image/x-icon')\n  await res.body?.cancel()\n\n  res = await app.request('http://localhost/favicon-notfound.ico')\n  assertEquals(res.status, 404)\n  assertMatch(res.headers.get('Content-Type') || '', /^text\\/plain/)\n  assertEquals(res.headers.get('X-Custom'), 'Deno')\n  assertSpyCall(onNotFound, 0)\n\n  res = await app.request('http://localhost/static/plain.txt')\n  assertEquals(res.status, 200)\n  assertMatch(await res.text(), /^Deno!(\\r?\\n)?$/)\n\n  res = await app.request('http://localhost/static/download')\n  assertEquals(res.status, 200)\n  assertMatch(await res.text(), /^download(\\r?\\n)?$/)\n\n  res = await app.request('http://localhost/dot-static/plain.txt')\n  assertEquals(res.status, 200)\n  assertMatch(await res.text(), /^Deno!!(\\r?\\n)?$/)\n  assertSpyCalls(onNotFound, 1)\n\n  res = await app.fetch({\n    method: 'GET',\n    url: 'http://localhost/static/%2e%2e/static/plain.txt',\n  } as Request)\n  assertEquals(res.status, 404)\n  assertEquals(await res.text(), '404 Not Found')\n\n  res = await app.request('http://localhost/static/helloworld')\n  assertEquals(res.status, 200)\n  assertEquals(await res.text(), 'Hi\\n')\n\n  res = await app.request('http://localhost/static/hello.world')\n  assertEquals(res.status, 200)\n  assertEquals(await res.text(), 'Hi\\n')\n\n  res = await app.request('http://localhost/static-absolute-root/plain.txt')\n  assertEquals(res.status, 200)\n  assertMatch(await res.text(), /^Deno!(\\r?\\n)?$/)\n\n  res = await app.request('http://localhost/static')\n  assertEquals(res.status, 404)\n  assertEquals(await res.text(), '404 Not Found')\n\n  res = await app.request('http://localhost/static/dir')\n  assertEquals(res.status, 404)\n  assertEquals(await res.text(), '404 Not Found')\n\n  res = await app.request('http://localhost/static/helloworld/nested')\n  assertEquals(res.status, 404)\n  assertEquals(await res.text(), '404 Not Found')\n\n  res = await app.request('http://localhost/static/helloworld/../')\n  assertEquals(res.status, 404)\n  assertEquals(await res.text(), '404 Not Found')\n})\n\nDeno.test('JWT Authentication middleware', async () => {\n  const app = new Hono<{ Variables: { 'x-foo': string } }>()\n  app.use('/*', async (c, next) => {\n    await next()\n    c.header('x-foo', c.get('x-foo') || '')\n  })\n  app.use('/auth/*', jwt({ secret: 'a-secret', alg: 'HS256' }))\n  app.get('/auth/*', (c) => {\n    c.set('x-foo', 'bar')\n    return new Response('auth')\n  })\n\n  const req = new Request('http://localhost/auth/a')\n  const res = await app.request(req)\n  assertEquals(res.status, 401)\n  assertEquals(await res.text(), 'Unauthorized')\n  assertEquals(res.headers.get('x-foo'), '')\n\n  const credential =\n    'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n  const reqOK = new Request('http://localhost/auth/a')\n  reqOK.headers.set('Authorization', `Bearer ${credential}`)\n  const resOK = await app.request(reqOK)\n  assertEquals(resOK.status, 200)\n  assertEquals(await resOK.text(), 'auth')\n  assertEquals(resOK.headers.get('x-foo'), 'bar')\n})\n"
  },
  {
    "path": "runtime-tests/deno/ssg.test.tsx",
    "content": "import { assertEquals } from '@std/assert'\nimport { toSSG } from '../../src/adapter/deno/ssg.ts'\nimport { Hono } from '../../src/hono.ts'\n\nDeno.test('toSSG function', async () => {\n  const app = new Hono()\n  app.get('/', (c) => c.text('Hello, World!'))\n  app.get('/about', (c) => c.text('About Page'))\n  app.get('/about/some', (c) => c.text('About Page 2tier'))\n  app.post('/about/some/thing', (c) => c.text('About Page 3tier'))\n  app.get('/bravo', (c) => c.html('Bravo Page'))\n  app.get('/Charlie', async (c, next) => {\n    c.setRenderer((content) => {\n      return c.html(\n        <html>\n          <body>\n            <p>{content}</p>\n          </body>\n        </html>\n      )\n    })\n    await next()\n  })\n  app.get('/Charlie', (c) => {\n    return c.render('Hello!')\n  })\n\n  const result = await toSSG(app, { dir: './ssg-static' })\n  assertEquals(result.success, true)\n  assertEquals(result.error, undefined)\n  assertEquals(result.files !== undefined, true)\n\n  await deleteDirectory('./ssg-static')\n})\n\nasync function deleteDirectory(dirPath: string): Promise<void> {\n  try {\n    const stat = await Deno.stat(dirPath)\n\n    if (stat.isDirectory) {\n      for await (const dirEntry of Deno.readDir(dirPath)) {\n        const entryPath = `${dirPath}/${dirEntry.name}`\n        await deleteDirectory(entryPath)\n      }\n      await Deno.remove(dirPath)\n    } else {\n      await Deno.remove(dirPath)\n    }\n  } catch (error) {\n    console.error(`Error deleting directory: ${error}`)\n  }\n}\n"
  },
  {
    "path": "runtime-tests/deno/static/download",
    "content": "download\n"
  },
  {
    "path": "runtime-tests/deno/static/hello.world/index.html",
    "content": "Hi\n"
  },
  {
    "path": "runtime-tests/deno/static/helloworld/index.html",
    "content": "Hi\n"
  },
  {
    "path": "runtime-tests/deno/static/plain.txt",
    "content": "Deno!\n"
  },
  {
    "path": "runtime-tests/deno/static-absolute-root/plain.txt",
    "content": "Deno!\n"
  },
  {
    "path": "runtime-tests/deno/stream.test.ts",
    "content": "import { assertEquals } from '@std/assert'\nimport { stream, streamSSE } from '../../src/helper/streaming/index.ts'\nimport { Hono } from '../../src/hono.ts'\n\nDeno.test('Should call onAbort via stream', async () => {\n  const app = new Hono()\n  let streamStarted = false\n  let aborted = false\n  app.get('/stream', (c) => {\n    return stream(c, (stream) => {\n      streamStarted = true\n      stream.onAbort(() => {\n        aborted = true\n      })\n      return new Promise<void>((resolve) => {\n        stream.onAbort(resolve)\n      })\n    })\n  })\n\n  const server = Deno.serve({ port: 0 }, app.fetch)\n  const ac = new AbortController()\n  const req = new Request(`http://localhost:${server.addr.port}/stream`, {\n    signal: ac.signal,\n  })\n  const res = fetch(req).catch(() => {})\n  assertEquals(aborted, false)\n  while (!streamStarted) {\n    await new Promise((resolve) => setTimeout(resolve, 10))\n  }\n  ac.abort()\n  await res\n  while (!aborted) {\n    await new Promise((resolve) => setTimeout(resolve))\n  }\n  assertEquals(aborted, true)\n\n  await server.shutdown()\n})\n\nDeno.test('Should not call onAbort via stream if already closed', async () => {\n  const app = new Hono()\n  let aborted = false\n  app.get('/stream', (c) => {\n    return stream(c, async (stream) => {\n      stream.onAbort(() => {\n        aborted = true\n      })\n      await stream.write('Hello')\n    })\n  })\n\n  const server = Deno.serve({ port: 0 }, app.fetch)\n  assertEquals(aborted, false)\n  const res = await fetch(`http://localhost:${server.addr.port}/stream`)\n  assertEquals(await res.text(), 'Hello')\n  assertEquals(aborted, false)\n  await server.shutdown()\n})\n\nDeno.test('Should call onAbort via streamSSE', async () => {\n  const app = new Hono()\n  let streamStarted = false\n  let aborted = false\n  app.get('/stream', (c) => {\n    return streamSSE(c, (stream) => {\n      streamStarted = true\n      stream.onAbort(() => {\n        aborted = true\n      })\n      return new Promise<void>((resolve) => {\n        stream.onAbort(resolve)\n      })\n    })\n  })\n\n  const server = Deno.serve({ port: 0 }, app.fetch)\n  const ac = new AbortController()\n  const req = new Request(`http://localhost:${server.addr.port}/stream`, {\n    signal: ac.signal,\n  })\n  const res = fetch(req).catch(() => {})\n  assertEquals(aborted, false)\n  while (!streamStarted) {\n    await new Promise((resolve) => setTimeout(resolve, 10))\n  }\n  ac.abort()\n  await res\n  while (!aborted) {\n    await new Promise((resolve) => setTimeout(resolve))\n  }\n  assertEquals(aborted, true)\n\n  await server.shutdown()\n})\n\nDeno.test('Should not call onAbort via streamSSE if already closed', async () => {\n  const app = new Hono()\n  let aborted = false\n  app.get('/stream', (c) => {\n    return streamSSE(c, async (stream) => {\n      stream.onAbort(() => {\n        aborted = true\n      })\n      await stream.write('Hello')\n    })\n  })\n\n  const server = Deno.serve({ port: 0 }, app.fetch)\n  assertEquals(aborted, false)\n  const res = await fetch(`http://localhost:${server.addr.port}/stream`)\n  assertEquals(await res.text(), 'Hello')\n  assertEquals(aborted, false)\n  await server.shutdown()\n})\n"
  },
  {
    "path": "runtime-tests/deno-jsx/deno.precompile.json",
    "content": "{\n  \"compilerOptions\": {\n    \"jsx\": \"precompile\",\n    \"jsxImportSource\": \"hono/jsx\",\n    \"lib\": [\"deno.ns\", \"dom\", \"dom.iterable\"]\n  },\n  \"unstable\": [\"sloppy-imports\"],\n  \"imports\": {\n    \"@std/assert\": \"jsr:@std/assert@^1.0.3\",\n    \"hono/jsx/jsx-runtime\": \"../../src/jsx/jsx-runtime.ts\"\n  }\n}\n"
  },
  {
    "path": "runtime-tests/deno-jsx/deno.react-jsx.json",
    "content": "{\n  \"compilerOptions\": {\n    \"jsx\": \"react-jsx\",\n    \"jsxImportSource\": \"hono/jsx\",\n    \"lib\": [\"deno.ns\", \"dom\", \"dom.iterable\"]\n  },\n  \"unstable\": [\"sloppy-imports\"],\n  \"imports\": {\n    \"@std/assert\": \"jsr:@std/assert@^1.0.3\",\n    \"hono/jsx/jsx-runtime\": \"../../src/jsx/jsx-runtime.ts\"\n  }\n}\n"
  },
  {
    "path": "runtime-tests/deno-jsx/jsx.test.tsx",
    "content": "/** @jsxImportSource ../../src/jsx */\nimport { assertEquals } from '@std/assert'\nimport { Style, css } from '../../src/helper/css/index.ts'\nimport { Suspense, renderToReadableStream } from '../../src/jsx/streaming.ts'\nimport type { HtmlEscapedString } from '../../src/utils/html.ts'\nimport { HtmlEscapedCallbackPhase, resolveCallback } from '../../src/utils/html.ts'\n\nDeno.test('JSX', () => {\n  const Component = ({ name }: { name: string }) => <span>{name}</span>\n  const html = (\n    <div>\n      <h1 id={'<Hello>'}>\n        <Component name={'<Hono>'} />\n      </h1>\n    </div>\n  )\n\n  assertEquals(html.toString(), '<div><h1 id=\"&lt;Hello&gt;\"><span>&lt;Hono&gt;</span></h1></div>')\n})\n\nDeno.test('JSX: Fragment', () => {\n  const fragment = (\n    <>\n      <p>1</p>\n      <p>2</p>\n    </>\n  )\n  assertEquals(fragment.toString(), '<p>1</p><p>2</p>')\n})\n\nDeno.test('JSX: Empty Fragment', () => {\n  const Component = () => <></>\n  const html = <Component />\n  assertEquals(html.toString(), '')\n})\n\nDeno.test('JSX: Async Component', async () => {\n  const Component = async ({ name }: { name: string }) =>\n    new Promise<HtmlEscapedString>((resolve) => setTimeout(() => resolve(<span>{name}</span>), 10))\n  const stream = renderToReadableStream(\n    <div>\n      <Component name={'<Hono>'} />\n    </div>\n  )\n\n  const chunks: string[] = []\n  const textDecoder = new TextDecoder()\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  for await (const chunk of stream as any) {\n    chunks.push(textDecoder.decode(chunk))\n  }\n\n  assertEquals(chunks.join(''), '<div><span>&lt;Hono&gt;</span></div>')\n})\n\nDeno.test('JSX: Suspense', async () => {\n  const Content = () => {\n    const content = new Promise<HtmlEscapedString>((resolve) =>\n      setTimeout(() => resolve(<h1>Hello</h1>), 10)\n    )\n    return content\n  }\n\n  const stream = renderToReadableStream(\n    <Suspense fallback={<p>Loading...</p>}>\n      <Content />\n    </Suspense>\n  )\n\n  const chunks: string[] = []\n  const textDecoder = new TextDecoder()\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  for await (const chunk of stream as any) {\n    chunks.push(textDecoder.decode(chunk))\n  }\n\n  assertEquals(chunks, [\n    '<template id=\"H:0\"></template><p>Loading...</p><!--/$-->',\n    `<template data-hono-target=\"H:0\"><h1>Hello</h1></template><script>\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:0')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script>`,\n  ])\n})\n\nDeno.test('JSX: css', async () => {\n  const className = css`\n    color: red;\n  `\n  const html = (\n    <html>\n      <head>\n        <Style />\n      </head>\n      <body>\n        <div class={className}></div>\n      </body>\n    </html>\n  )\n\n  const awaitedHtml = await html\n  const htmlEscapedString = 'callbacks' in awaitedHtml ? awaitedHtml : await awaitedHtml.toString()\n  assertEquals(\n    await resolveCallback(htmlEscapedString, HtmlEscapedCallbackPhase.Stringify, false, {}),\n    '<html><head><style id=\"hono-css\">.css-3142110215{color:red}</style></head><body><div class=\"css-3142110215\"></div></body></html>'\n  )\n})\n\nDeno.test('JSX: css with CSP nonce', async () => {\n  const className = css`\n    color: red;\n  `\n  const html = (\n    <html>\n      <head>\n        <Style nonce='1234' />\n      </head>\n      <body>\n        <div class={className}></div>\n      </body>\n    </html>\n  )\n\n  const awaitedHtml = await html\n  const htmlEscapedString = 'callbacks' in awaitedHtml ? awaitedHtml : await awaitedHtml.toString()\n  assertEquals(\n    await resolveCallback(htmlEscapedString, HtmlEscapedCallbackPhase.Stringify, false, {}),\n    '<html><head><style id=\"hono-css\" nonce=\"1234\">.css-3142110215{color:red}</style></head><body><div class=\"css-3142110215\"></div></body></html>'\n  )\n})\n\nDeno.test('JSX: normalize key', async () => {\n  const className = <div className='foo'></div>\n  const htmlFor = <div htmlFor='foo'></div>\n  const crossOrigin = <div crossOrigin='foo'></div>\n  const httpEquiv = <div httpEquiv='foo'></div>\n  const itemProp = <div itemProp='foo'></div>\n  const fetchPriority = <div fetchPriority='foo'></div>\n  const noModule = <div noModule='foo'></div>\n  const formAction = <div formAction='foo'></div>\n\n  assertEquals(className.toString(), '<div class=\"foo\"></div>')\n  assertEquals(htmlFor.toString(), '<div for=\"foo\"></div>')\n  assertEquals(crossOrigin.toString(), '<div crossorigin=\"foo\"></div>')\n  assertEquals(httpEquiv.toString(), '<div http-equiv=\"foo\"></div>')\n  assertEquals(itemProp.toString(), '<div itemprop=\"foo\"></div>')\n  assertEquals(fetchPriority.toString(), '<div fetchpriority=\"foo\"></div>')\n  assertEquals(noModule.toString(), '<div nomodule=\"foo\"></div>')\n  assertEquals(formAction.toString(), '<div formaction=\"foo\"></div>')\n})\n\nDeno.test('JSX: null or undefined', async () => {\n  const nullHtml = <div className={null}></div>\n  const undefinedHtml = <div className={undefined}></div>\n\n  // react-jsx : <div>\n  // precompile : <div > // Extra whitespace is allowed because it is a specification.\n\n  assertEquals(nullHtml.toString().replace(/\\s+/g, ''), '<div></div>')\n  assertEquals(undefinedHtml.toString().replace(/\\s+/g, ''), '<div></div>')\n})\n\nDeno.test('JSX: boolean attributes', async () => {\n  const trueHtml = <div disabled={true}></div>\n  const falseHtml = <div disabled={false}></div>\n\n  // output is different, but semantics as HTML is the same, so both are OK\n  // react-jsx : <div disabled=\"\">\n  // precompile : <div disabled>\n\n  assertEquals(trueHtml.toString().replace('=\"\"', ''), '<div disabled></div>')\n  assertEquals(falseHtml.toString(), '<div></div>')\n})\n\nDeno.test('JSX: number', async () => {\n  const html = <div tabindex={1}></div>\n\n  assertEquals(html.toString(), '<div tabindex=\"1\"></div>')\n})\n\nDeno.test('JSX: style', async () => {\n  const html = <div style={{ fontSize: '12px', color: null }}></div>\n  assertEquals(html.toString(), '<div style=\"font-size:12px\"></div>')\n})\n"
  },
  {
    "path": "runtime-tests/fastly/index.test.ts",
    "content": "import { createHash } from 'crypto'\nimport { getRuntimeKey } from '../../src/helper/adapter'\nimport { Hono } from '../../src/index'\nimport { basicAuth } from '../../src/middleware/basic-auth'\nimport { jwt } from '../../src/middleware/jwt'\n\ndeclare global {\n  var __fastlyComputeNodeDefaultCrypto: boolean | undefined\n}\n\nbeforeAll(() => {\n  vi.stubGlobal('fastly', true)\n  vi.stubGlobal('navigator', undefined)\n})\n\nafterAll(() => {\n  vi.unstubAllGlobals()\n})\n\nconst app = new Hono()\n\ndescribe('Hello World', () => {\n  app.get('/', (c) => c.text('Hello! Compute!'))\n  app.get('/runtime-name', (c) => {\n    return c.text(getRuntimeKey())\n  })\n\n  it('Should return 200', async () => {\n    const res = await app.request('http://localhost/')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('Hello! Compute!')\n  })\n\n  it('Should return the correct runtime name', async () => {\n    const res = await app.request('http://localhost/runtime-name')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('fastly')\n  })\n})\n\ndescribe('Basic Auth Middleware without `hashFunction`', () => {\n  const app = new Hono()\n\n  const username = 'hono-user-a'\n  const password = 'hono-password-a'\n  app.use(\n    '/auth/*',\n    basicAuth({\n      username,\n      password,\n    })\n  )\n\n  app.get('/auth/*', () => new Response('auth'))\n\n  it('Should not authorize, return 401 Response', async () => {\n    const req = new Request('http://localhost/auth/a')\n    const res = await app.request(req)\n    expect(res.status).toBe(401)\n    expect(await res.text()).toBe('Unauthorized')\n  })\n})\n\ndescribe('Basic Auth Middleware with `hashFunction`', () => {\n  const app = new Hono()\n\n  const username = 'hono-user-a'\n  const password = 'hono-password-a'\n  app.use(\n    '/auth/*',\n    basicAuth({\n      username,\n      password,\n      hashFunction: (m: string) => createHash('sha256').update(m).digest('hex'),\n    })\n  )\n\n  app.get('/auth/*', () => new Response('auth'))\n\n  it('Should not authorize, return 401 Response', async () => {\n    const req = new Request('http://localhost/auth/a')\n    const res = await app.request(req)\n    expect(res.status).toBe(401)\n    expect(await res.text()).toBe('Unauthorized')\n  })\n\n  it('Should authorize, return 200 Response', async () => {\n    const credential = 'aG9uby11c2VyLWE6aG9uby1wYXNzd29yZC1h'\n    const req = new Request('http://localhost/auth/a')\n    req.headers.set('Authorization', `Basic ${credential}`)\n    const res = await app.request(req)\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('auth')\n  })\n})\n\ndescribe('JWT Auth Middleware does not work', () => {\n  const app = new Hono()\n\n  // Since nodejs 20 or later, global WebCrypto object becomes stable (experimental on nodejs 18)\n  // but WebCrypto does not have compatibility with Fastly Compute runtime (lacking some objects/methods in Fastly)\n  // so following test should run only be polyfill-ed via vite-plugin-fastly-js-compute plugin.\n  // To confirm polyfill-ed or not, check __fastlyComputeNodeDefaultCrypto field is true.\n  it.runIf(!globalThis.__fastlyComputeNodeDefaultCrypto)('Should throw error', () => {\n    expect(() => {\n      app.use('/jwt/*', jwt({ alg: 'HS256', secret: 'secret' }))\n    }).toThrow(/`crypto.subtle.importKey` is undefined/)\n  })\n})\n"
  },
  {
    "path": "runtime-tests/fastly/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true,\n    \"types\": [\"vitest/globals\"]\n  },\n  \"references\": [\n    { \"path\": \"../../tsconfig.build.json\" }\n  ]\n}\n"
  },
  {
    "path": "runtime-tests/fastly/vitest.config.ts",
    "content": "import fastlyCompute from 'vite-plugin-fastly-js-compute'\nimport { defineProject } from 'vitest/config'\n\nexport default defineProject({\n  plugins: [fastlyCompute()],\n  test: {\n    globals: true,\n    name: 'fastly',\n  },\n})\n"
  },
  {
    "path": "runtime-tests/lambda/index.test.ts",
    "content": "import { Readable } from 'stream'\nimport {\n  ALBProcessor,\n  EventV1Processor,\n  EventV2Processor,\n  getProcessor,\n  handle,\n  streamHandle,\n} from '../../src/adapter/aws-lambda/handler'\nimport type {\n  ALBProxyEvent,\n  APIGatewayProxyEventV2,\n  LatticeProxyEventV2,\n  LambdaEvent,\n} from '../../src/adapter/aws-lambda/handler'\nimport type {\n  ApiGatewayRequestContext,\n  ApiGatewayRequestContextV2,\n  LatticeRequestContextV2,\n  LambdaContext,\n} from '../../src/adapter/aws-lambda/types'\nimport { getCookie, setCookie } from '../../src/helper/cookie'\nimport { streamSSE, streamText } from '../../src/helper/streaming'\nimport { Hono } from '../../src/hono'\nimport { basicAuth } from '../../src/middleware/basic-auth'\nimport './mock'\n\ntype Bindings = {\n  event: LambdaEvent\n  lambdaContext: LambdaContext\n  requestContext: ApiGatewayRequestContext | ApiGatewayRequestContextV2\n}\n\nconst testApiGatewayRequestContextV2 = {\n  accountId: '123456789012',\n  apiId: 'urlid',\n  authentication: null,\n  authorizer: {\n    iam: {\n      accessKey: 'AKIA...',\n      accountId: '111122223333',\n      callerId: 'AIDA...',\n      cognitoIdentity: null,\n      principalOrgId: null,\n      userArn: 'arn:aws:iam::111122223333:user/example-user',\n      userId: 'AIDA...',\n    },\n  },\n  domainName: 'example.com',\n  domainPrefix: '<url-id>',\n  http: {\n    method: 'POST',\n    path: '/my/path',\n    protocol: 'HTTP/1.1',\n    sourceIp: '123.123.123.123',\n    userAgent: 'agent',\n  },\n  requestId: 'id',\n  routeKey: '$default',\n  stage: '$default',\n  time: '12/Mar/2020:19:03:58 +0000',\n  timeEpoch: 1583348638390,\n  customProperty: 'customValue',\n}\n\nconst testLatticeRequestContext: LatticeRequestContextV2 = {\n  serviceNetworkArn: 'arn:aws:vpc-lattice:us-east-1:111122223333:servicenetwork/sn-a1b2c3',\n  serviceArn: 'arn:aws:vpc-lattice:us-east-1:111122223333:service/svc-a1b2c3',\n  targetGroupArn: 'arn:aws:vpc-lattice:us-east-1:111122223333:targetgroup/tg-a1b2c3',\n  region: 'us-east-1',\n  timeEpoch: '1759915938150314',\n  identity: {\n    sourceVpcArn: 'arn:aws:ec2:us-east-1:111122223333:vpc/vpc-a1b2c3',\n  },\n}\n\ndescribe('AWS Lambda Adapter for Hono', () => {\n  const app = new Hono<{ Bindings: Bindings }>()\n\n  app.get('/', (c) => {\n    return c.text('Hello Lambda!')\n  })\n\n  app.get('/binary', (c) => {\n    return c.body('Fake Image', 200, {\n      'Content-Type': 'image/png',\n    })\n  })\n\n  app.post('/post', async (c) => {\n    const body = (await c.req.parseBody()) as { message: string }\n    return c.text(body.message)\n  })\n\n  app.post('/post/binary', async (c) => {\n    const body = await c.req.blob()\n    return c.text(`${body.size} bytes`)\n  })\n\n  const username = 'hono-user-a'\n  const password = 'hono-password-a'\n  app.use('/auth/*', basicAuth({ username, password }))\n  app.get('/auth/abc', (c) => c.text('Good Night Lambda!'))\n\n  app.get('/lambda-event', (c) => {\n    const event = c.env.event\n    return c.json(event)\n  })\n\n  app.get('/lambda-context', (c) => {\n    const fnctx = c.env.lambdaContext\n    return c.json(fnctx)\n  })\n\n  app.get('/custom-context/v1/apigw', (c) => {\n    const lambdaContext = c.env.requestContext\n    return c.json(lambdaContext)\n  })\n\n  app.get('/custom-context/apigw', (c) => {\n    const lambdaContext = c.env.event.requestContext\n    return c.json(lambdaContext)\n  })\n\n  app.get('/custom-context/v1/lambda', (c) => {\n    const lambdaContext = c.env.requestContext\n    return c.json(lambdaContext)\n  })\n\n  app.get('/custom-context/lambda', (c) => {\n    const lambdaContext = c.env.event.requestContext\n    return c.json(lambdaContext)\n  })\n\n  app.get('/query-params', (c) => {\n    const queryParams = c.req.query()\n    return c.json(queryParams)\n  })\n\n  app.get('/multi-query-params', (c) => {\n    const multiQueryParams = c.req.queries()\n    return c.json(multiQueryParams)\n  })\n\n  const testCookie1 = {\n    key: 'id',\n    value: crypto.randomUUID(),\n    get serialized() {\n      return `${this.key}=${this.value}; Path=/`\n    },\n  }\n  const testCookie2 = {\n    key: 'secret',\n    value: crypto.randomUUID(),\n    get serialized() {\n      return `${this.key}=${this.value}; Path=/`\n    },\n  }\n\n  app.post('/cookie', (c) => {\n    setCookie(c, testCookie1.key, testCookie1.value)\n    setCookie(c, testCookie2.key, testCookie2.value)\n    return c.text('Cookies Set')\n  })\n\n  app.get('/cookie', (c) => {\n    const validCookies =\n      getCookie(c, testCookie1.key) === testCookie1.value &&\n      getCookie(c, testCookie2.key) === testCookie2.value\n    if (!validCookies) {\n      return c.text('Invalid Cookies')\n    }\n    return c.text('Valid Cookies')\n  })\n\n  app.post('/headers', (c) => {\n    if (c.req.header('foo')?.includes('bar')) {\n      return c.json({ message: 'ok' })\n    }\n    return c.json({ message: 'fail' }, 400)\n  })\n\n  const handler = handle(app)\n\n  const testApiGatewayRequestContext = {\n    accountId: '123456789012',\n    apiId: 'id',\n    authorizer: {\n      claims: null,\n      scopes: null,\n    },\n    domainName: 'example.com',\n    domainPrefix: 'id',\n    extendedRequestId: 'request-id',\n    httpMethod: 'GET',\n    identity: {\n      sourceIp: 'IP',\n      userAgent: 'user-agent',\n    },\n    path: '/my/path',\n    protocol: 'HTTP/1.1',\n    requestId: 'id=',\n    requestTime: '04/Mar/2020:19:15:17 +0000',\n    requestTimeEpoch: 1583349317135,\n    resourcePath: '/',\n    stage: '$default',\n    customProperty: 'customValue',\n  }\n\n  const testALBRequestContext = {\n    elb: {\n      targetGroupArn:\n        'arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/lambda-279XGJDqGZ5rsrHC2Fjr/49e9d65c45c6791a',\n    },\n  }\n\n  it('Should handle a GET request and return a 200 response', async () => {\n    const event = {\n      version: '1.0',\n      resource: '/',\n      httpMethod: 'GET',\n      headers: { 'content-type': 'text/plain' },\n      path: '/',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testApiGatewayRequestContext,\n    }\n\n    const response = await handler(event)\n    expect(response.statusCode).toBe(200)\n    expect(response.body).toBe('Hello Lambda!')\n    expect(response.headers['content-type']).toMatch(/^text\\/plain/)\n    expect(response.multiValueHeaders).toBeUndefined()\n    expect(response.isBase64Encoded).toBe(false)\n  })\n\n  it('Should handle a GET request and return a 200 response with binary', async () => {\n    const event = {\n      version: '1.0',\n      resource: '/binary',\n      httpMethod: 'GET',\n      headers: {},\n      path: '/binary',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testApiGatewayRequestContext,\n    }\n\n    const response = await handler(event)\n    expect(response.statusCode).toBe(200)\n    expect(response.body).toBe('RmFrZSBJbWFnZQ==')\n    expect(response.headers['content-type']).toMatch(/^image\\/png/)\n    expect(response.multiValueHeaders).toBeUndefined()\n    expect(response.isBase64Encoded).toBe(true)\n  })\n\n  it('Should handle a GET request and return a 200 response (LambdaFunctionUrlEvent)', async () => {\n    const event = {\n      version: '2.0',\n      routeKey: '$default',\n      headers: { 'content-type': 'text/plain' },\n      rawPath: '/',\n      rawQueryString: '',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testApiGatewayRequestContextV2,\n    }\n\n    testApiGatewayRequestContextV2.http.method = 'GET'\n\n    const response = await handler(event)\n    expect(response.statusCode).toBe(200)\n    expect(response.body).toBe('Hello Lambda!')\n    expect(response.headers['content-type']).toMatch(/^text\\/plain/)\n    expect(response.multiValueHeaders).toBeUndefined()\n    expect(response.isBase64Encoded).toBe(false)\n  })\n\n  it('Should handle a GET request and return a 200 response (ALBEvent)', async () => {\n    const event = {\n      headers: { 'content-type': 'text/plain' },\n      httpMethod: 'GET',\n      path: '/',\n      queryStringParameters: {\n        query: '1234ABCD',\n      },\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testALBRequestContext,\n    }\n\n    const response = await handler(event)\n    expect(response.statusCode).toBe(200)\n    expect(response.body).toBe('Hello Lambda!')\n    expect(response.headers['content-type']).toMatch(/^text\\/plain/)\n    expect(response.multiValueHeaders).toBeUndefined()\n    expect(response.isBase64Encoded).toBe(false)\n  })\n\n  it('Should handle a GET request and return a 200 response (LatticeProxyEvent)', async () => {\n    const event: LatticeProxyEventV2 = {\n      version: '2.0',\n      path: '/?query=1234ABCD',\n      method: 'GET',\n      headers: { 'content-type': ['text/plain'] },\n      queryStringParameters: {},\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testLatticeRequestContext,\n    }\n\n    const response = await handler(event)\n    expect(response.statusCode).toBe(200)\n    expect(response.body).toBe('Hello Lambda!')\n    expect(response.headers['content-type']).toMatch(/^text\\/plain/)\n    expect(response.multiValueHeaders).toBeUndefined()\n    expect(response.isBase64Encoded).toBe(false)\n  })\n\n  it('Should handle a GET request and return a 404 response', async () => {\n    const event = {\n      version: '1.0',\n      resource: '/nothing',\n      httpMethod: 'GET',\n      headers: { 'content-type': 'text/plain' },\n      path: '/nothing',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testApiGatewayRequestContext,\n    }\n\n    const response = await handler(event)\n    expect(response.statusCode).toBe(404)\n  })\n\n  it('Should handle a POST request and return a 200 response', async () => {\n    const searchParam = new URLSearchParams()\n    searchParam.append('message', 'Good Morning Lambda!')\n    const event = {\n      version: '1.0',\n      resource: '/post',\n      httpMethod: 'POST',\n      headers: {\n        'Content-Type': 'application/x-www-form-urlencoded',\n      },\n      path: '/post',\n      body: Buffer.from(searchParam.toString()).toString('base64'),\n      isBase64Encoded: true,\n      requestContext: testApiGatewayRequestContext,\n    }\n\n    const response = await handler(event)\n    expect(response.statusCode).toBe(200)\n    expect(response.body).toBe('Good Morning Lambda!')\n  })\n\n  it('Should handle a POST request and return a 200 response (LambdaFunctionUrlEvent)', async () => {\n    const searchParam = new URLSearchParams()\n    searchParam.append('message', 'Good Morning Lambda!')\n    const event = {\n      version: '2.0',\n      routeKey: '$default',\n      headers: {\n        'Content-Type': 'application/x-www-form-urlencoded',\n      },\n      rawPath: '/post',\n      rawQueryString: '',\n      body: Buffer.from(searchParam.toString()).toString('base64'),\n      isBase64Encoded: true,\n      requestContext: testApiGatewayRequestContextV2,\n    }\n\n    testApiGatewayRequestContextV2.http.method = 'POST'\n\n    const response = await handler(event)\n    expect(response.statusCode).toBe(200)\n    expect(response.body).toBe('Good Morning Lambda!')\n  })\n\n  it('Should handle a POST request with binary and return a 200 response', async () => {\n    const array = new Uint8Array([0xc0, 0xff, 0xee])\n    const buffer = Buffer.from(array)\n    const event = {\n      version: '1.0',\n      resource: '/post/binary',\n      httpMethod: 'POST',\n      headers: {\n        'Content-Type': 'application/x-www-form-urlencoded',\n      },\n      path: '/post/binary',\n      body: buffer.toString('base64'),\n      isBase64Encoded: true,\n      requestContext: testApiGatewayRequestContext,\n    }\n\n    const response = await handler(event)\n    expect(response.statusCode).toBe(200)\n    expect(response.body).toBe('3 bytes')\n  })\n\n  it('Should handle a request and return a 401 response with Basic auth', async () => {\n    const event = {\n      version: '1.0',\n      resource: '/auth/abc',\n      httpMethod: 'GET',\n      headers: {\n        'Content-Type': 'plain/text',\n      },\n      path: '/auth/abc',\n      body: null,\n      isBase64Encoded: true,\n      requestContext: testApiGatewayRequestContext,\n    }\n\n    const response = await handler(event)\n    expect(response.statusCode).toBe(401)\n  })\n\n  it('Should handle a request and return a 200 response with Basic auth', async () => {\n    const credential = 'aG9uby11c2VyLWE6aG9uby1wYXNzd29yZC1h'\n    const event = {\n      version: '1.0',\n      resource: '/auth/abc',\n      httpMethod: 'GET',\n      headers: {\n        'Content-Type': 'plain/text',\n        Authorization: `Basic ${credential}`,\n      },\n      path: '/auth/abc',\n      body: null,\n      isBase64Encoded: true,\n      requestContext: testApiGatewayRequestContext,\n    }\n\n    const response = await handler(event)\n    expect(response.statusCode).toBe(200)\n    expect(response.body).toBe('Good Night Lambda!')\n  })\n\n  it('Should handle a GET request and return custom context', async () => {\n    const event = {\n      version: '1.0',\n      resource: '/custom-context/apigw',\n      httpMethod: 'GET',\n      headers: { 'content-type': 'application/json' },\n      path: '/custom-context/apigw',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testApiGatewayRequestContext,\n    }\n\n    const response = await handler(event)\n    expect(response.statusCode).toBe(200)\n    expect(JSON.parse(response.body).customProperty).toEqual('customValue')\n  })\n\n  it('Should handle a GET request and context', async () => {\n    const event = {\n      version: '1.0',\n      resource: '/lambda-context',\n      httpMethod: 'GET',\n      headers: { 'content-type': 'application/json' },\n      path: '/lambda-context',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testApiGatewayRequestContext,\n    }\n    const context: LambdaContext = {\n      callbackWaitsForEmptyEventLoop: false,\n      functionName: 'myLambdaFunction',\n      functionVersion: '1.0.0',\n      invokedFunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:myLambdaFunction',\n      memoryLimitInMB: '128',\n      awsRequestId: 'c6af9ac6-a7b0-11e6-80f5-76304dec7eb7',\n      logGroupName: '/aws/lambda/myLambdaFunction',\n      logStreamName: '2016/11/14/[$LATEST]f2d4b21cfb33490da2e8f8ef79a483s4',\n      getRemainingTimeInMillis: () => {\n        return 60000 // 60 seconds\n      },\n    }\n    const response = await handler(event, context)\n    expect(response.statusCode).toBe(200)\n    expect(JSON.parse(response.body).callbackWaitsForEmptyEventLoop).toEqual(false)\n  })\n\n  it('Should handle a POST request and return a 200 response with cookies set (APIGatewayProxyEvent V1 and V2)', async () => {\n    const apiGatewayEvent = {\n      version: '1.0',\n      resource: '/cookie',\n      httpMethod: 'POST',\n      headers: { 'content-type': 'text/plain' },\n      path: '/cookie',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testApiGatewayRequestContext,\n    }\n\n    const apiGatewayResponse = await handler(apiGatewayEvent)\n\n    expect(apiGatewayResponse.statusCode).toBe(200)\n    expect(apiGatewayResponse.multiValueHeaders).toHaveProperty('set-cookie', [\n      testCookie1.serialized,\n      testCookie2.serialized,\n    ])\n\n    const apiGatewayEventV2 = {\n      version: '2.0',\n      routeKey: '$default',\n      httpMethod: 'POST',\n      headers: { 'content-type': 'text/plain' },\n      rawPath: '/cookie',\n      rawQueryString: '',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testApiGatewayRequestContextV2,\n    }\n\n    const apiGatewayResponseV2 = await handler(apiGatewayEventV2)\n\n    expect(apiGatewayResponseV2.statusCode).toBe(200)\n    expect(apiGatewayResponseV2).toHaveProperty('cookies', [\n      testCookie1.serialized,\n      testCookie2.serialized,\n    ])\n  })\n\n  it('Should handle a POST request and return a 200 response with cookies set (LatticeProxyEvent V2)', async () => {\n    const latticeProxyEvent: LatticeProxyEventV2 = {\n      version: '2.0',\n      path: '/cookie',\n      method: 'POST',\n      headers: { 'content-type': ['text/plain'] },\n      queryStringParameters: {},\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testLatticeRequestContext,\n    }\n\n    const latticeResponse = await handler(latticeProxyEvent)\n\n    expect(latticeResponse.statusCode).toBe(200)\n    expect(latticeResponse.headers).toHaveProperty(\n      'set-cookie',\n      [testCookie1.serialized, testCookie2.serialized].join(', ')\n    )\n  })\n\n  describe('headers', () => {\n    describe('single-value headers', () => {\n      it('Should extract single-value headers and return 200 (ALBProxyEvent)', async () => {\n        const event = {\n          body: '{}',\n          httpMethod: 'POST',\n          isBase64Encoded: false,\n          path: '/headers',\n          headers: {\n            host: 'localhost',\n            foo: 'bar',\n          },\n          requestContext: testALBRequestContext,\n        }\n        const albResponse = await handler(event)\n        expect(albResponse.statusCode).toBe(200)\n        expect(albResponse.headers).toEqual(\n          expect.objectContaining({\n            'content-type': 'application/json',\n          })\n        )\n        expect(albResponse.multiValueHeaders).toBeUndefined()\n      })\n\n      it('Should extract single-value headers and return 200 (APIGatewayProxyEvent)', async () => {\n        const apigatewayProxyEvent = {\n          version: '1.0',\n          resource: '/headers',\n          httpMethod: 'POST',\n          headers: {\n            host: 'localhost',\n            foo: 'bar',\n          },\n          path: '/headers',\n          body: null,\n          isBase64Encoded: false,\n          requestContext: testApiGatewayRequestContext,\n        }\n        const apiGatewayResponseV2 = await handler(apigatewayProxyEvent)\n        expect(apiGatewayResponseV2.statusCode).toBe(200)\n      })\n\n      it('Should extract single-value headers and return 200 (APIGatewayProxyEventV2)', async () => {\n        const apigatewayProxyV2Event = {\n          version: '2.0',\n          routeKey: '$default',\n          headers: {\n            host: 'localhost',\n            foo: 'bar',\n          },\n          rawPath: '/headers',\n          rawQueryString: '',\n          requestContext: testApiGatewayRequestContextV2,\n          resource: '/headers',\n          body: null,\n          isBase64Encoded: false,\n        }\n        const apiGatewayResponseV2 = await handler(apigatewayProxyV2Event)\n        expect(apiGatewayResponseV2.statusCode).toBe(200)\n      })\n    })\n\n    describe('multi-value headers', () => {\n      it('Should extract multi-value headers and return 200 (ALBProxyEvent)', async () => {\n        const event = {\n          body: '{}',\n          httpMethod: 'POST',\n          isBase64Encoded: false,\n          path: '/headers',\n          multiValueHeaders: {\n            host: ['localhost'],\n            foo: ['bar'],\n          },\n          requestContext: testALBRequestContext,\n        }\n        const albResponse = await handler(event)\n        expect(albResponse.statusCode).toBe(200)\n        expect(albResponse.multiValueHeaders).toBeDefined()\n        expect(albResponse.multiValueHeaders).toEqual(\n          expect.objectContaining({\n            'content-type': ['application/json'],\n          })\n        )\n      })\n\n      it('Should extract multi-value headers and return 200 (APIGatewayProxyEvent)', async () => {\n        const apigatewayProxyEvent = {\n          version: '1.0',\n          resource: '/headers',\n          httpMethod: 'POST',\n          headers: {},\n          multiValueHeaders: {\n            host: ['localhost'],\n            foo: ['bar'],\n          },\n          path: '/headers',\n          body: null,\n          isBase64Encoded: false,\n          requestContext: testApiGatewayRequestContext,\n        }\n        const apiGatewayResponseV2 = await handler(apigatewayProxyEvent)\n        expect(apiGatewayResponseV2.statusCode).toBe(200)\n      })\n\n      it('Should extract multi-value headers and return 200 (LatticeProxyEvent)', async () => {\n        const event: LatticeProxyEventV2 = {\n          version: '2.0',\n          path: '/headers',\n          method: 'POST',\n          headers: {\n            host: ['localhost'],\n            foo: ['bar'],\n          },\n          queryStringParameters: {},\n          body: null,\n          isBase64Encoded: false,\n          requestContext: testLatticeRequestContext,\n        }\n\n        const response = await handler(event)\n\n        expect(response.statusCode).toBe(200)\n      })\n    })\n  })\n\n  it('Should handle a POST request and return a 200 response if cookies match (APIGatewayProxyEvent V1 and V2)', async () => {\n    const apiGatewayEvent = {\n      version: '1.0',\n      resource: '/cookie',\n      httpMethod: 'GET',\n      headers: {\n        'content-type': 'text/plain',\n        cookie: [testCookie1.serialized, testCookie2.serialized].join('; '),\n      },\n      path: '/cookie',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testApiGatewayRequestContext,\n    }\n\n    const apiGatewayResponse = await handler(apiGatewayEvent)\n\n    expect(apiGatewayResponse.statusCode).toBe(200)\n    expect(apiGatewayResponse.body).toBe('Valid Cookies')\n    expect(apiGatewayResponse.headers['content-type']).toMatch(/^text\\/plain/)\n    expect(apiGatewayResponse.isBase64Encoded).toBe(false)\n\n    const apiGatewayEventV2 = {\n      version: '2.0',\n      routeKey: '$default',\n      headers: { 'content-type': 'text/plain' },\n      rawPath: '/cookie',\n      cookies: [testCookie1.serialized, testCookie2.serialized],\n      rawQueryString: '',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testApiGatewayRequestContextV2,\n    }\n    testApiGatewayRequestContextV2.http.method = 'GET'\n\n    const apiGatewayResponseV2 = await handler(apiGatewayEventV2)\n\n    expect(apiGatewayResponseV2.statusCode).toBe(200)\n    expect(apiGatewayResponseV2.body).toBe('Valid Cookies')\n    expect(apiGatewayResponseV2.headers['content-type']).toMatch(/^text\\/plain/)\n    expect(apiGatewayResponseV2.isBase64Encoded).toBe(false)\n  })\n\n  it('Should handle a GET request and return a 200 response if cookies match (ALBProxyEvent) with default headers', async () => {\n    const albEventDefaultHeaders = {\n      version: '1.0',\n      resource: '/cookie',\n      httpMethod: 'GET',\n      headers: {\n        'content-type': 'text/plain',\n        cookie: [testCookie1.serialized, testCookie2.serialized].join('; '),\n      },\n      path: '/cookie',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testALBRequestContext,\n    }\n\n    const albResponse = await handler(albEventDefaultHeaders)\n\n    expect(albResponse.statusCode).toBe(200)\n    expect(albResponse.body).toBe('Valid Cookies')\n    expect(albResponse.headers['content-type']).toMatch(/^text\\/plain/)\n    expect(albResponse.multiValueHeaders).toBeUndefined()\n    expect(albResponse.isBase64Encoded).toBe(false)\n  })\n\n  it('Should handle a GET request and return a 200 response if cookies match (ALBProxyEvent) with multi value headers', async () => {\n    const albEventMultiValueHeaders = {\n      version: '1.0',\n      resource: '/cookie',\n      httpMethod: 'GET',\n      multiValueHeaders: {\n        'content-type': ['text/plain'],\n        cookie: [testCookie1.serialized, testCookie2.serialized],\n      },\n      path: '/cookie',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testALBRequestContext,\n    }\n\n    const albResponse = await handler(albEventMultiValueHeaders)\n\n    expect(albResponse.statusCode).toBe(200)\n    expect(albResponse.body).toBe('Valid Cookies')\n    expect(albResponse.headers).toBeUndefined()\n    expect(albResponse.multiValueHeaders['content-type']).toEqual([\n      expect.stringMatching(/^text\\/plain/),\n    ])\n    expect(albResponse.isBase64Encoded).toBe(false)\n  })\n\n  it('Should handle a POST request and return a 200 response with cookies (ALBProxyEvent) with default headers', async () => {\n    const albEventDefaultHeaders = {\n      version: '1.0',\n      resource: '/cookie',\n      httpMethod: 'POST',\n      headers: {\n        'content-type': 'text/plain',\n        cookie: [testCookie1.serialized, testCookie2.serialized].join(', '),\n      },\n      path: '/cookie',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testALBRequestContext,\n    }\n\n    const albResponse = await handler(albEventDefaultHeaders)\n\n    expect(albResponse.statusCode).toBe(200)\n    expect(albResponse.body).toBe('Cookies Set')\n    expect(albResponse.headers['content-type']).toMatch(/^text\\/plain/)\n    expect(albResponse.multiValueHeaders).toBeUndefined()\n    expect(albResponse.headers['set-cookie']).toEqual(\n      [testCookie1.serialized, testCookie2.serialized].join(', ')\n    )\n    expect(albResponse.isBase64Encoded).toBe(false)\n  })\n\n  it('Should handle a POST request and return a 200 response with cookies (ALBProxyEvent) with multi value headers', async () => {\n    const albEventDefaultHeaders = {\n      version: '1.0',\n      resource: '/cookie',\n      httpMethod: 'POST',\n      multiValueHeaders: {\n        'content-type': ['text/plain'],\n        cookie: [testCookie1.serialized, testCookie2.serialized],\n      },\n      path: '/cookie',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testALBRequestContext,\n    }\n\n    const albResponse = await handler(albEventDefaultHeaders)\n\n    expect(albResponse.statusCode).toBe(200)\n    expect(albResponse.body).toBe('Cookies Set')\n    expect(albResponse.headers).toBeUndefined()\n    expect(albResponse.multiValueHeaders['set-cookie']).toEqual(\n      expect.arrayContaining([testCookie1.serialized, testCookie2.serialized])\n    )\n    expect(albResponse.isBase64Encoded).toBe(false)\n  })\n\n  it('Should handle a GET request and return a 200 response with queryStringParameters (ALBProxyEvent)', async () => {\n    const albEventDefaultHeaders = {\n      resource: '/query-params',\n      httpMethod: 'GET',\n      headers: {\n        'content-type': 'application/json',\n      },\n      queryStringParameters: {\n        key1: 'value1',\n        key2: 'value2',\n      },\n      path: '/query-params',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testALBRequestContext,\n    }\n\n    const albResponse = await handler(albEventDefaultHeaders)\n\n    expect(albResponse.statusCode).toBe(200)\n    expect(albResponse.body).toContain(\n      JSON.stringify({\n        key1: 'value1',\n        key2: 'value2',\n      })\n    )\n    expect(albResponse.headers['content-type']).toMatch(/^application\\/json/)\n    expect(albResponse.multiValueHeaders).toBeUndefined()\n    expect(albResponse.isBase64Encoded).toBe(false)\n  })\n\n  it('Should handle a GET request and return a 200 response with single value multiQueryStringParameters (ALBProxyEvent)', async () => {\n    const albEventDefaultHeaders = {\n      resource: '/query-params',\n      httpMethod: 'GET',\n      multiValueHeaders: {\n        'content-type': ['application/json'],\n      },\n      multiValueQueryStringParameters: {\n        key1: ['value1'],\n        key2: ['value2'],\n      },\n      path: '/query-params',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testALBRequestContext,\n    }\n\n    const albResponse = await handler(albEventDefaultHeaders)\n\n    expect(albResponse.statusCode).toBe(200)\n    expect(albResponse.body).toContain(\n      JSON.stringify({\n        key1: 'value1',\n        key2: 'value2',\n      })\n    )\n    expect(albResponse.headers).toBeUndefined()\n    expect(albResponse.multiValueHeaders['content-type']).toEqual([\n      expect.stringMatching(/^application\\/json/),\n    ])\n    expect(albResponse.isBase64Encoded).toBe(false)\n  })\n\n  it('Should handle a GET request and return a 200 response with multi value multiQueryStringParameters (ALBProxyEvent)', async () => {\n    const albEventDefaultHeaders = {\n      resource: '/query-params',\n      httpMethod: 'GET',\n      multiValueHeaders: {\n        'content-type': ['application/json'],\n      },\n      multiValueQueryStringParameters: {\n        key1: ['value1'],\n        key2: ['value2', 'otherValue2'],\n      },\n      path: '/multi-query-params',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testALBRequestContext,\n    }\n\n    const albResponse = await handler(albEventDefaultHeaders)\n\n    expect(albResponse.statusCode).toBe(200)\n    expect(albResponse.body).toContain(\n      JSON.stringify({\n        key1: ['value1'],\n        key2: ['value2', 'otherValue2'],\n      })\n    )\n    expect(albResponse.headers).toBeUndefined()\n    expect(albResponse.multiValueHeaders['content-type']).toEqual([\n      expect.stringMatching(/^application\\/json/),\n    ])\n    expect(albResponse.isBase64Encoded).toBe(false)\n  })\n})\n\ndescribe('streamHandle function', () => {\n  const app = new Hono<{ Bindings: Bindings }>()\n\n  app.get('/', (c) => {\n    return c.text('Hello Lambda!')\n  })\n\n  app.get('/stream/text', async (c) => {\n    return streamText(c, async (stream) => {\n      for (let i = 0; i < 3; i++) {\n        await stream.writeln(`${i}`)\n        await stream.sleep(1)\n      }\n    })\n  })\n\n  app.get('/sse', async (c) => {\n    return streamSSE(c, async (stream) => {\n      let id = 0\n      const maxIterations = 2\n\n      while (id < maxIterations) {\n        const message = `Message\\nIt is ${id}`\n        await stream.writeSSE({ data: message, event: 'time-update', id: String(id++) })\n        await stream.sleep(10)\n      }\n    })\n  })\n\n  const handler = streamHandle(app)\n\n  it('Should streamHandle a GET request and return a 200 response (LambdaFunctionUrlEvent)', async () => {\n    const event = {\n      headers: { 'content-type': ' binary/octet-stream' },\n      rawPath: '/stream/text',\n      rawQueryString: '',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testApiGatewayRequestContextV2,\n    }\n\n    testApiGatewayRequestContextV2.http.method = 'GET'\n\n    const mockReadableStream = new Readable({\n      // eslint-disable-next-line @typescript-eslint/no-empty-function\n      read() {},\n    })\n\n    mockReadableStream.push('0\\n')\n    mockReadableStream.push('1\\n')\n    mockReadableStream.push('2\\n')\n    mockReadableStream.push('3\\n')\n    mockReadableStream.push(null) // EOF\n\n    // @ts-expect-error should this be a ReadbleStream?\n    await handler(event, mockReadableStream, vi.fn())\n\n    const chunks = []\n    for await (const chunk of mockReadableStream) {\n      chunks.push(chunk)\n    }\n    expect(chunks.join('')).toContain('0\\n1\\n2\\n3\\n')\n  })\n\n  it('Should handle a GET request for an SSE stream and return the correct chunks', async () => {\n    const event = {\n      headers: { 'content-type': 'text/event-stream' },\n      rawPath: '/sse',\n      rawQueryString: '',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testApiGatewayRequestContextV2,\n    }\n\n    testApiGatewayRequestContextV2.http.method = 'GET'\n\n    const mockReadableStream = new Readable({\n      // eslint-disable-next-line @typescript-eslint/no-empty-function\n      read() {},\n    })\n\n    const initContentType = {\n      'Content-Type': 'application/vnd.awslambda.http-integration-response',\n    }\n    mockReadableStream.push(JSON.stringify(initContentType))\n\n    // Send JSON formatted response headers, followed by 8 NULL characters as a separator\n    const httpResponseMetadata = {\n      statusCode: 200,\n      headers: { 'Custom-Header': 'value' },\n      cookies: ['session=abcd1234'],\n    }\n    const jsonResponsePrelude = JSON.stringify(httpResponseMetadata) + Buffer.alloc(8, 0).toString()\n    mockReadableStream.push(jsonResponsePrelude)\n\n    mockReadableStream.push('data: Message\\ndata: It is 0\\n\\n')\n    mockReadableStream.push('data: Message\\ndata: It is 1\\n\\n')\n    mockReadableStream.push(null) // EOF\n\n    // @ts-expect-error should this be a ReadbleStream?\n    await handler(event, mockReadableStream, vi.fn())\n\n    const chunks = []\n    for await (const chunk of mockReadableStream) {\n      chunks.push(chunk)\n    }\n\n    // If you have chunks, you might want to convert them to strings before checking\n    const output = Buffer.concat(chunks).toString()\n    expect(output).toContain('data: Message\\ndata: It is 0\\n\\n')\n    expect(output).toContain('data: Message\\ndata: It is 1\\n\\n')\n\n    // Assertions for the newly added header and prelude\n    expect(output).toContain('application/vnd.awslambda.http-integration-response')\n    expect(output).toContain('Custom-Header')\n    expect(output).toContain('session=abcd1234')\n\n    // Check for JSON prelude and NULL sequence\n    const nullSequence = '\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000\\u0000'\n    expect(output).toContain(jsonResponsePrelude.replace(nullSequence, ''))\n  })\n})\n\ndescribe('getProcessor function', () => {\n  it('Should return ALBProcessor for an ALBProxyEvent event', () => {\n    const event: ALBProxyEvent = {\n      httpMethod: 'GET',\n      path: '/',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: {\n        elb: {\n          targetGroupArn:\n            'arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/lambda-279XGJDqGZ5rsrHC2Fjr/49e9d65c45c6791a',\n        },\n      },\n    }\n\n    const processor = getProcessor(event)\n    expect(processor).toBeInstanceOf(ALBProcessor)\n  })\n\n  it('Should return EventV1Processor for an event without requestContext', () => {\n    const event = {\n      httpMethod: 'GET',\n      path: '/',\n      body: null,\n      isBase64Encoded: false,\n    }\n\n    // while LambdaEvent RequestContext property is mandatory, it can be absent when testing through invoke-api or AWS Console\n    // in such cases, a V1 processor should be returned\n    const processor = getProcessor(event as unknown as LambdaEvent)\n    expect(processor).toBeInstanceOf(EventV1Processor)\n  })\n\n  it('Should return EventV2Processor for an APIGatewayProxyEventV2 event', () => {\n    const event: APIGatewayProxyEventV2 = {\n      version: '2.0',\n      routeKey: '$default',\n      headers: { 'content-type': 'text/plain' },\n      rawPath: '/',\n      rawQueryString: '',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testApiGatewayRequestContextV2,\n    }\n\n    const processor = getProcessor(event)\n    expect(processor).toBeInstanceOf(EventV2Processor)\n  })\n})\n"
  },
  {
    "path": "runtime-tests/lambda/mock.ts",
    "content": "import { vi } from 'vitest'\nimport type { LambdaEvent } from '../../src/adapter/aws-lambda/handler'\nimport type { LambdaContext } from '../../src/adapter/aws-lambda/types'\n\ntype StreamifyResponseHandler = (\n  handlerFunc: (\n    event: LambdaEvent,\n    responseStream: NodeJS.WritableStream,\n    context: LambdaContext\n  ) => Promise<void>\n) => (event: LambdaEvent, context: LambdaContext) => Promise<NodeJS.WritableStream>\n\nconst mockStreamifyResponse: StreamifyResponseHandler = (handlerFunc) => {\n  return async (event, context) => {\n    const mockWritableStream: NodeJS.WritableStream = new (require('stream').Writable)({\n      write(chunk: Buffer, _encoding: string, callback: () => void) {\n        console.log('Writing chunk:', chunk.toString())\n        callback()\n      },\n      final(callback: () => void) {\n        console.log('Finalizing stream.')\n        callback()\n      },\n    })\n    mockWritableStream.on('finish', () => {\n      console.log('Stream has finished')\n    })\n    await handlerFunc(event, mockWritableStream, context)\n    mockWritableStream.end()\n    return mockWritableStream\n  }\n}\n\nconst awslambda = {\n  streamifyResponse: mockStreamifyResponse,\n}\n\nvi.stubGlobal('awslambda', awslambda)\n"
  },
  {
    "path": "runtime-tests/lambda/stream-mock.ts",
    "content": "import { vi } from 'vitest'\nimport { Writable } from 'node:stream'\nimport type {\n  APIGatewayProxyEvent,\n  APIGatewayProxyEventV2,\n} from '../../src/adapter/aws-lambda/handler'\nimport type { LambdaContext } from '../../src/adapter/aws-lambda/types'\n\ntype StreamifyResponseHandler = (\n  handlerFunc: (\n    event: APIGatewayProxyEvent | APIGatewayProxyEventV2,\n    responseStream: Writable,\n    context: LambdaContext\n  ) => Promise<void>\n) => (event: APIGatewayProxyEvent, context: LambdaContext) => Promise<NodeJS.WritableStream>\n\nconst mockStreamifyResponse: StreamifyResponseHandler = (handlerFunc) => {\n  return async (event, context) => {\n    const chunks: unknown[] = []\n    const mockWritableStream = new Writable({\n      write(chunk, _encoding, callback) {\n        chunks.push(chunk)\n        callback()\n      },\n    })\n    // @ts-expect-error chunks property for testing\n    mockWritableStream.chunks = chunks\n    await handlerFunc(event, mockWritableStream, context)\n    mockWritableStream.end()\n    return mockWritableStream\n  }\n}\n\nconst awslambda = {\n  streamifyResponse: mockStreamifyResponse,\n  HttpResponseStream: {\n    from: (stream: Writable, httpResponseMetadata: unknown): Writable => {\n      stream.write(Buffer.from(JSON.stringify(httpResponseMetadata)))\n      return stream\n    },\n  },\n}\n\nvi.stubGlobal('awslambda', awslambda)\n"
  },
  {
    "path": "runtime-tests/lambda/stream.test.ts",
    "content": "import { streamHandle } from '../../src/adapter/aws-lambda/handler'\nimport type { LambdaEvent } from '../../src/adapter/aws-lambda/handler'\nimport type {\n  ApiGatewayRequestContext,\n  ApiGatewayRequestContextV2,\n  LambdaContext,\n} from '../../src/adapter/aws-lambda/types'\nimport { Hono } from '../../src/hono'\nimport './stream-mock'\n\ntype Bindings = {\n  event: LambdaEvent\n  lambdaContext: LambdaContext\n  requestContext: ApiGatewayRequestContext | ApiGatewayRequestContextV2\n}\n\nconst testApiGatewayRequestContextV2 = {\n  accountId: '123456789012',\n  apiId: 'urlid',\n  authentication: null,\n  authorizer: {\n    iam: {\n      accessKey: 'AKIA...',\n      accountId: '111122223333',\n      callerId: 'AIDA...',\n      cognitoIdentity: null,\n      principalOrgId: null,\n      userArn: 'arn:aws:iam::111122223333:user/example-user',\n      userId: 'AIDA...',\n    },\n  },\n  domainName: 'example.com',\n  domainPrefix: '<url-id>',\n  http: {\n    method: 'GET',\n    path: '/my/path',\n    protocol: 'HTTP/1.1',\n    sourceIp: '123.123.123.123',\n    userAgent: 'agent',\n  },\n  requestId: 'id',\n  routeKey: '$default',\n  stage: '$default',\n  time: '12/Mar/2020:19:03:58 +0000',\n  timeEpoch: 1583348638390,\n  customProperty: 'customValue',\n}\n\ndescribe('streamHandle function', () => {\n  const app = new Hono<{ Bindings: Bindings }>()\n\n  app.get('/cookies', async (c) => {\n    c.res.headers.append('Set-Cookie', 'cookie1=value1')\n    c.res.headers.append('Set-Cookie', 'cookie2=value2')\n    return c.text('Cookies Set')\n  })\n\n  const handler = streamHandle(app)\n\n  it('to write multiple cookies into the headers', async () => {\n    const event = {\n      headers: { 'content-type': 'text/plain' },\n      rawPath: '/cookies',\n      rawQueryString: '',\n      body: null,\n      isBase64Encoded: false,\n      requestContext: testApiGatewayRequestContextV2,\n    }\n\n    const stream = await handler(event, {} as LambdaContext, vi.fn())\n\n    const metadata = JSON.parse(stream.chunks[0].toString())\n    expect(metadata.cookies).toEqual(['cookie1=value1', 'cookie2=value2'])\n  })\n})\n"
  },
  {
    "path": "runtime-tests/lambda/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true,\n    \"types\": [\"vitest/globals\"]\n  },\n  \"references\": [\n    { \"path\": \"../../tsconfig.build.json\" }\n  ]\n}\n"
  },
  {
    "path": "runtime-tests/lambda/vitest.config.ts",
    "content": "import { defineProject } from 'vitest/config'\n\nexport default defineProject({\n  test: {\n    env: {\n      NAME: 'Node',\n    },\n    globals: true,\n    name: 'lambda',\n  },\n})\n"
  },
  {
    "path": "runtime-tests/lambda-edge/index.test.ts",
    "content": "import type {\n  Callback,\n  CloudFrontConfig,\n  CloudFrontRequest,\n  CloudFrontResponse,\n} from '../../src/adapter/lambda-edge/handler'\nimport { handle } from '../../src/adapter/lambda-edge/handler'\nimport { Hono } from '../../src/hono'\nimport { basicAuth } from '../../src/middleware/basic-auth'\n\ntype Bindings = {\n  callback: Callback\n  config: CloudFrontConfig\n  request: CloudFrontRequest\n  response: CloudFrontResponse\n}\n\ndescribe('Lambda@Edge Adapter for Hono', () => {\n  const app = new Hono<{ Bindings: Bindings }>()\n\n  app.get('/', (c) => {\n    return c.text('Hello Lambda!')\n  })\n\n  app.get('/binary', (c) => {\n    return c.body('Fake Image', 200, {\n      'Content-Type': 'image/png',\n    })\n  })\n\n  app.post('/post', async (c) => {\n    const body = (await c.req.parseBody()) as { message: string }\n    return c.text(body.message)\n  })\n\n  app.get('/callback/request', async (c, next) => {\n    await next()\n    c.env.callback(null, c.env.request)\n  })\n\n  app.get('/config/eventCheck', async (c, next) => {\n    await next()\n    if (c.env.config.eventType in ['viewer-request', 'origin-request']) {\n      c.env.callback(null, c.env.request)\n    } else {\n      c.env.callback(null, c.env.response)\n    }\n  })\n\n  app.get('/callback/response', async (c, next) => {\n    await next()\n    c.env.callback(null, c.env.response)\n  })\n\n  app.post('/post/binary', async (c) => {\n    const body = await c.req.blob()\n    return c.text(`${body.size} bytes`)\n  })\n\n  const username = 'hono-user-a'\n  const password = 'hono-password-a'\n  app.use('/auth/*', basicAuth({ username, password }))\n  app.get('/auth/abc', (c) => c.text('Good Night Lambda!'))\n\n  app.get('/header/add', async (c, next) => {\n    c.env.response.headers['Strict-Transport-Security'.toLowerCase()] = [\n      {\n        key: 'Strict-Transport-Security',\n        value: 'max-age=63072000; includeSubdomains; preload',\n      },\n    ]\n    c.env.response.headers['X-Custom'.toLowerCase()] = [\n      {\n        key: 'X-Custom',\n        value: 'Foo',\n      },\n    ]\n    await next()\n    c.env.callback(null, c.env.response)\n  })\n\n  const handler = handle(app)\n\n  it('Should handle a GET request and return a 200 response (Lambda@Edge viewer request)', async () => {\n    const event = {\n      Records: [\n        {\n          cf: {\n            config: {\n              distributionDomainName: 'd111111abcdef8.cloudfront.net',\n              distributionId: 'EDFDVBD6EXAMPLE',\n              eventType: 'viewer-request',\n              requestId: '4TyzHTaYWb1GX1qTfsHhEqV6HUDd_BzoBZnwfnvQc_1oF26ClkoUSEQ==',\n            },\n            request: {\n              clientIp: '203.0.113.178',\n              headers: {\n                host: [\n                  {\n                    key: 'Host',\n                    value: 'd111111abcdef8.cloudfront.net',\n                  },\n                ],\n                'user-agent': [\n                  {\n                    key: 'User-Agent',\n                    value: 'curl/7.66.0',\n                  },\n                ],\n                accept: [\n                  {\n                    key: 'accept',\n                    value: '*/*',\n                  },\n                ],\n              },\n              method: 'GET',\n              querystring: '',\n              uri: '/',\n            },\n          },\n        },\n      ],\n    }\n    const response = await handler(event)\n    expect(response.status).toBe('200')\n    expect(response.body).toBe('Hello Lambda!')\n    if (response.headers && response.headers['content-type']) {\n      expect(response.headers['content-type'][0].value).toMatch(/^text\\/plain/)\n    } else {\n      throw new Error(\"'content-type' header is missing in the response\")\n    }\n  })\n\n  it('Should handle a GET request and return a 200 response (Lambda@Edge origin request)', async () => {\n    const event = {\n      Records: [\n        {\n          cf: {\n            config: {\n              distributionDomainName: 'd111111abcdef8.cloudfront.net',\n              distributionId: 'EDFDVBD6EXAMPLE',\n              eventType: 'origin-request',\n              requestId: '4TyzHTaYWb1GX1qTfsHhEqV6HUDd_BzoBZnwfnvQc_1oF26ClkoUSEQ==',\n            },\n            request: {\n              clientIp: '203.0.113.178',\n              headers: {\n                'x-forwarded-for': [\n                  {\n                    key: 'X-Forwarded-For',\n                    value: '203.0.113.178',\n                  },\n                ],\n                'user-agent': [\n                  {\n                    key: 'User-Agent',\n                    value: 'Amazon CloudFront',\n                  },\n                ],\n                via: [\n                  {\n                    key: 'Via',\n                    value: '2.0 2afae0d44e2540f472c0635ab62c232b.cloudfront.net (CloudFront)',\n                  },\n                ],\n                host: [\n                  {\n                    key: 'Host',\n                    value: 'example.org',\n                  },\n                ],\n                'cache-control': [\n                  {\n                    key: 'Cache-Control',\n                    value: 'no-cache',\n                  },\n                ],\n              },\n              method: 'GET',\n              origin: {\n                custom: {\n                  customHeaders: {},\n                  domainName: 'example.org',\n                  keepaliveTimeout: 5,\n                  path: '',\n                  port: 443,\n                  protocol: 'https',\n                  readTimeout: 30,\n                  sslProtocols: ['TLSv1', 'TLSv1.1', 'TLSv1.2'],\n                },\n              },\n              querystring: '',\n              uri: '/',\n            },\n          },\n        },\n      ],\n    }\n    const response = await handler(event)\n    expect(response.status).toBe('200')\n    expect(response.body).toBe('Hello Lambda!')\n    if (response.headers && response.headers['content-type']) {\n      expect(response.headers['content-type'][0].value).toMatch(/^text\\/plain/)\n    } else {\n      throw new Error(\"'content-type' header is missing in the response\")\n    }\n  })\n\n  it('Should handle a GET request and return a 200 response (Lambda@Edge viewer response)', async () => {\n    const event = {\n      Records: [\n        {\n          cf: {\n            config: {\n              distributionDomainName: 'd111111abcdef8.cloudfront.net',\n              distributionId: 'EDFDVBD6EXAMPLE',\n              eventType: 'viewer-response',\n              requestId: '4TyzHTaYWb1GX1qTfsHhEqV6HUDd_BzoBZnwfnvQc_1oF26ClkoUSEQ==',\n            },\n            request: {\n              clientIp: '203.0.113.178',\n              headers: {\n                host: [\n                  {\n                    key: 'Host',\n                    value: 'd111111abcdef8.cloudfront.net',\n                  },\n                ],\n                'user-agent': [\n                  {\n                    key: 'User-Agent',\n                    value: 'curl/7.66.0',\n                  },\n                ],\n                accept: [\n                  {\n                    key: 'accept',\n                    value: '*/*',\n                  },\n                ],\n              },\n              method: 'GET',\n              querystring: '',\n              uri: '/',\n            },\n            response: {\n              headers: {\n                'access-control-allow-credentials': [\n                  {\n                    key: 'Access-Control-Allow-Credentials',\n                    value: 'true',\n                  },\n                ],\n                'access-control-allow-origin': [\n                  {\n                    key: 'Access-Control-Allow-Origin',\n                    value: '*',\n                  },\n                ],\n                date: [\n                  {\n                    key: 'Date',\n                    value: 'Mon, 13 Jan 2020 20:14:56 GMT',\n                  },\n                ],\n                'referrer-policy': [\n                  {\n                    key: 'Referrer-Policy',\n                    value: 'no-referrer-when-downgrade',\n                  },\n                ],\n                server: [\n                  {\n                    key: 'Server',\n                    value: 'ExampleCustomOriginServer',\n                  },\n                ],\n                'x-content-type-options': [\n                  {\n                    key: 'X-Content-Type-Options',\n                    value: 'nosniff',\n                  },\n                ],\n                'x-frame-options': [\n                  {\n                    key: 'X-Frame-Options',\n                    value: 'DENY',\n                  },\n                ],\n                'x-xss-protection': [\n                  {\n                    key: 'X-XSS-Protection',\n                    value: '1; mode=block',\n                  },\n                ],\n                age: [\n                  {\n                    key: 'Age',\n                    value: '2402',\n                  },\n                ],\n                'content-type': [\n                  {\n                    key: 'Content-Type',\n                    value: 'text/html; charset=utf-8',\n                  },\n                ],\n                'content-length': [\n                  {\n                    key: 'Content-Length',\n                    value: '9593',\n                  },\n                ],\n              },\n              status: '200',\n              statusDescription: 'OK',\n            },\n          },\n        },\n      ],\n    }\n    const response = await handler(event)\n    expect(response.status).toBe('200')\n    expect(response.body).toBe('Hello Lambda!')\n    if (response.headers && response.headers['content-type']) {\n      expect(response.headers['content-type'][0].value).toMatch(/^text\\/plain/)\n    } else {\n      throw new Error(\"'content-type' header is missing in the response\")\n    }\n  })\n\n  it('Should handle a GET request and return a 200 response (Lambda@Edge origin response)', async () => {\n    const event = {\n      Records: [\n        {\n          cf: {\n            config: {\n              distributionDomainName: 'd111111abcdef8.cloudfront.net',\n              distributionId: 'EDFDVBD6EXAMPLE',\n              eventType: 'origin-response',\n              requestId: '4TyzHTaYWb1GX1qTfsHhEqV6HUDd_BzoBZnwfnvQc_1oF26ClkoUSEQ==',\n            },\n            request: {\n              clientIp: '203.0.113.178',\n              headers: {\n                'x-forwarded-for': [\n                  {\n                    key: 'X-Forwarded-For',\n                    value: '203.0.113.178',\n                  },\n                ],\n                'user-agent': [\n                  {\n                    key: 'User-Agent',\n                    value: 'Amazon CloudFront',\n                  },\n                ],\n                via: [\n                  {\n                    key: 'Via',\n                    value: '2.0 8f22423015641505b8c857a37450d6c0.cloudfront.net (CloudFront)',\n                  },\n                ],\n                host: [\n                  {\n                    key: 'Host',\n                    value: 'example.org',\n                  },\n                ],\n                'cache-control': [\n                  {\n                    key: 'Cache-Control',\n                    value: 'no-cache',\n                  },\n                ],\n              },\n              method: 'GET',\n              origin: {\n                custom: {\n                  customHeaders: {},\n                  domainName: 'example.org',\n                  keepaliveTimeout: 5,\n                  path: '',\n                  port: 443,\n                  protocol: 'https',\n                  readTimeout: 30,\n                  sslProtocols: ['TLSv1', 'TLSv1.1', 'TLSv1.2'],\n                },\n              },\n              querystring: '',\n              uri: '/',\n            },\n            response: {\n              headers: {\n                'access-control-allow-credentials': [\n                  {\n                    key: 'Access-Control-Allow-Credentials',\n                    value: 'true',\n                  },\n                ],\n                'access-control-allow-origin': [\n                  {\n                    key: 'Access-Control-Allow-Origin',\n                    value: '*',\n                  },\n                ],\n                date: [\n                  {\n                    key: 'Date',\n                    value: 'Mon, 13 Jan 2020 20:12:38 GMT',\n                  },\n                ],\n                'referrer-policy': [\n                  {\n                    key: 'Referrer-Policy',\n                    value: 'no-referrer-when-downgrade',\n                  },\n                ],\n                server: [\n                  {\n                    key: 'Server',\n                    value: 'ExampleCustomOriginServer',\n                  },\n                ],\n                'x-content-type-options': [\n                  {\n                    key: 'X-Content-Type-Options',\n                    value: 'nosniff',\n                  },\n                ],\n                'x-frame-options': [\n                  {\n                    key: 'X-Frame-Options',\n                    value: 'DENY',\n                  },\n                ],\n                'x-xss-protection': [\n                  {\n                    key: 'X-XSS-Protection',\n                    value: '1; mode=block',\n                  },\n                ],\n                'content-type': [\n                  {\n                    key: 'Content-Type',\n                    value: 'text/html; charset=utf-8',\n                  },\n                ],\n                'content-length': [\n                  {\n                    key: 'Content-Length',\n                    value: '9593',\n                  },\n                ],\n              },\n              status: '200',\n              statusDescription: 'OK',\n            },\n          },\n        },\n      ],\n    }\n    const response = await handler(event)\n    expect(response.status).toBe('200')\n    expect(response.body).toBe('Hello Lambda!')\n    if (response.headers && response.headers['content-type']) {\n      expect(response.headers['content-type'][0].value).toMatch(/^text\\/plain/)\n    } else {\n      throw new Error(\"'content-type' header is missing in the response\")\n    }\n  })\n\n  it('Should handle a GET request and return a 200 response with binary', async () => {\n    const event = {\n      Records: [\n        {\n          cf: {\n            config: {\n              distributionDomainName: 'example.com',\n              distributionId: 'EXAMPLE123',\n              eventType: 'viewer-request',\n              requestId: 'exampleRequestId',\n            },\n            request: {\n              clientIp: '123.123.123.123',\n              headers: {\n                host: [\n                  {\n                    key: 'Host',\n                    value: 'example.com',\n                  },\n                ],\n              },\n              method: 'GET',\n              querystring: '',\n              uri: '/binary',\n            },\n          },\n        },\n      ],\n    }\n\n    const response = await handler(event)\n\n    expect(response.status).toBe('200')\n    expect(response.body).toBe('RmFrZSBJbWFnZQ==') // base64 encoded fake image\n    if (response.headers && response.headers['content-type']) {\n      expect(response.headers['content-type'][0].value).toMatch(/^image\\/png/)\n    } else {\n      throw new Error(\"'content-type' header is missing in the response\")\n    }\n  })\n\n  it('Should handle a GET request and return a 404 response', async () => {\n    const event = {\n      Records: [\n        {\n          cf: {\n            config: {\n              distributionDomainName: 'example.com',\n              distributionId: 'EXAMPLE123',\n              eventType: 'viewer-request',\n              requestId: 'exampleRequestId',\n            },\n            request: {\n              clientIp: '123.123.123.123',\n              headers: {\n                host: [\n                  {\n                    key: 'Host',\n                    value: 'example.com',\n                  },\n                ],\n              },\n              method: 'GET',\n              querystring: '',\n              uri: '/nothing',\n            },\n          },\n        },\n      ],\n    }\n\n    const response = await handler(event)\n\n    expect(response.status).toBe('404')\n  })\n\n  it('Should handle a POST request and return a 200 response', async () => {\n    const searchParam = new URLSearchParams()\n    searchParam.append('message', 'Good Morning Lambda!')\n\n    const event = {\n      Records: [\n        {\n          cf: {\n            config: {\n              distributionDomainName: 'example.com',\n              distributionId: 'EXAMPLE123',\n              eventType: 'viewer-request',\n              requestId: 'exampleRequestId',\n            },\n            request: {\n              clientIp: '123.123.123.123',\n              headers: {\n                host: [\n                  {\n                    key: 'Host',\n                    value: 'example.com',\n                  },\n                ],\n                'content-type': [\n                  {\n                    key: 'Content-Type',\n                    value: 'application/x-www-form-urlencoded',\n                  },\n                ],\n              },\n              method: 'POST',\n              querystring: '',\n              uri: '/post',\n              body: {\n                inputTruncated: false,\n                action: 'read-only',\n                encoding: 'base64',\n                data: Buffer.from(searchParam.toString()).toString('base64'),\n              },\n            },\n          },\n        },\n      ],\n    }\n\n    const response = await handler(event)\n\n    expect(response.status).toBe('200')\n    expect(response.body).toBe('Good Morning Lambda!')\n  })\n\n  it('Should handle a POST request with binary and return a 200 response', async () => {\n    const array = new Uint8Array([0xc0, 0xff, 0xee])\n    const buffer = Buffer.from(array)\n    const event = {\n      Records: [\n        {\n          cf: {\n            config: {\n              distributionDomainName: 'example.com',\n              distributionId: 'EXAMPLE123',\n              eventType: 'viewer-request',\n              requestId: 'exampleRequestId',\n            },\n            request: {\n              clientIp: '123.123.123.123',\n              headers: {\n                host: [\n                  {\n                    key: 'Host',\n                    value: 'example.com',\n                  },\n                ],\n                'content-type': [\n                  {\n                    key: 'Content-Type',\n                    value: 'application/x-www-form-urlencoded',\n                  },\n                ],\n              },\n              method: 'POST',\n              querystring: '',\n              uri: '/post/binary',\n              body: {\n                inputTruncated: false,\n                action: 'read-only',\n                encoding: 'base64',\n                data: buffer.toString('base64'),\n              },\n            },\n          },\n        },\n      ],\n    }\n\n    const response = await handler(event)\n    expect(response.status).toBe('200')\n    expect(response.body).toBe('3 bytes')\n  })\n\n  it('Should handle a request and return a 401 response with Basic auth', async () => {\n    const event = {\n      Records: [\n        {\n          cf: {\n            config: {\n              distributionDomainName: 'example.com',\n              distributionId: 'EXAMPLE123',\n              eventType: 'viewer-request',\n              requestId: 'exampleRequestId',\n            },\n            request: {\n              clientIp: '123.123.123.123',\n              headers: {\n                host: [\n                  {\n                    key: 'Host',\n                    value: 'example.com',\n                  },\n                ],\n                'content-type': [\n                  {\n                    key: 'Content-Type',\n                    value: 'plain/text',\n                  },\n                ],\n              },\n              method: 'GET',\n              querystring: '',\n              uri: '/auth/abc',\n            },\n          },\n        },\n      ],\n    }\n\n    const response = await handler(event)\n\n    expect(response.status).toBe('401')\n  })\n\n  it('Should handle a request and return a 401 response with Basic auth', async () => {\n    const event = {\n      Records: [\n        {\n          cf: {\n            config: {\n              distributionDomainName: 'example.com',\n              distributionId: 'EXAMPLE123',\n              eventType: 'viewer-request',\n              requestId: 'exampleRequestId',\n            },\n            request: {\n              clientIp: '123.123.123.123',\n              headers: {\n                host: [\n                  {\n                    key: 'Host',\n                    value: 'example.com',\n                  },\n                ],\n                'content-type': [\n                  {\n                    key: 'Content-Type',\n                    value: 'plain/text',\n                  },\n                ],\n              },\n              method: 'GET',\n              querystring: '',\n              uri: '/auth/abc',\n            },\n          },\n        },\n      ],\n    }\n\n    const response = await handler(event)\n\n    expect(response.status).toBe('401')\n  })\n\n  it('Should call a callback to continue processing the request', async () => {\n    const event = {\n      Records: [\n        {\n          cf: {\n            config: {\n              distributionDomainName: 'example.com',\n              distributionId: 'EXAMPLE123',\n              eventType: 'viewer-request',\n              requestId: 'exampleRequestId',\n            },\n            request: {\n              clientIp: '123.123.123.123',\n              headers: {},\n              method: 'GET',\n              querystring: '',\n              uri: '/callback/request',\n            },\n          },\n        },\n      ],\n    }\n\n    let called = false\n    let requestClientIp = ''\n\n    await handler(event, {}, (_err, result) => {\n      if (result && 'clientIp' in result) {\n        requestClientIp = result.clientIp\n      }\n      called = true\n    })\n\n    expect(called).toBe(true)\n    expect(requestClientIp).toBe('123.123.123.123')\n  })\n\n  it('Should call a callback to continue processing the response', async () => {\n    const event = {\n      Records: [\n        {\n          cf: {\n            config: {\n              distributionDomainName: 'example.com',\n              distributionId: 'EDFDVBD6EXAMPLE',\n              eventType: 'viewer-response',\n              requestId: '4TyzHTaYWb1GX1qTfsHhEqV6HUDd_BzoBZnwfnvQc_1oF26ClkoUSEQ==',\n            },\n            request: {\n              clientIp: '203.0.113.178',\n              headers: {\n                host: [\n                  {\n                    key: 'Host',\n                    value: 'example.com',\n                  },\n                ],\n                'user-agent': [\n                  {\n                    key: 'User-Agent',\n                    value: 'curl/7.66.0',\n                  },\n                ],\n                accept: [\n                  {\n                    key: 'accept',\n                    value: '*/*',\n                  },\n                ],\n              },\n              method: 'GET',\n              querystring: '',\n              uri: '/callback/response',\n            },\n            response: {\n              headers: {\n                'access-control-allow-credentials': [\n                  {\n                    key: 'Access-Control-Allow-Credentials',\n                    value: 'true',\n                  },\n                ],\n                'access-control-allow-origin': [\n                  {\n                    key: 'Access-Control-Allow-Origin',\n                    value: '*',\n                  },\n                ],\n                date: [\n                  {\n                    key: 'Date',\n                    value: 'Mon, 13 Jan 2020 20:14:56 GMT',\n                  },\n                ],\n                'referrer-policy': [\n                  {\n                    key: 'Referrer-Policy',\n                    value: 'no-referrer-when-downgrade',\n                  },\n                ],\n                server: [\n                  {\n                    key: 'Server',\n                    value: 'ExampleCustomOriginServer',\n                  },\n                ],\n                'x-content-type-options': [\n                  {\n                    key: 'X-Content-Type-Options',\n                    value: 'nosniff',\n                  },\n                ],\n                'x-frame-options': [\n                  {\n                    key: 'X-Frame-Options',\n                    value: 'DENY',\n                  },\n                ],\n                'x-xss-protection': [\n                  {\n                    key: 'X-XSS-Protection',\n                    value: '1; mode=block',\n                  },\n                ],\n                age: [\n                  {\n                    key: 'Age',\n                    value: '2402',\n                  },\n                ],\n                'content-type': [\n                  {\n                    key: 'Content-Type',\n                    value: 'text/html; charset=utf-8',\n                  },\n                ],\n                'content-length': [\n                  {\n                    key: 'Content-Length',\n                    value: '9593',\n                  },\n                ],\n              },\n              status: '200',\n              statusDescription: 'OK',\n            },\n          },\n        },\n      ],\n    }\n\n    interface CloudFrontHeaders {\n      [name: string]: [\n        {\n          key: string\n          value: string\n        },\n      ]\n    }\n    let called = false\n    let headers: CloudFrontHeaders = {}\n    await handler(event, {}, (_err, result) => {\n      if (result && result.headers) {\n        headers = result.headers as CloudFrontHeaders\n      }\n      called = true\n    })\n\n    expect(called).toBe(true)\n    expect(headers['access-control-allow-credentials']).toEqual([\n      {\n        key: 'Access-Control-Allow-Credentials',\n        value: 'true',\n      },\n    ])\n    expect(headers['access-control-allow-origin']).toEqual([\n      {\n        key: 'Access-Control-Allow-Origin',\n        value: '*',\n      },\n    ])\n  })\n\n  it('Should handle a GET request and add header (Lambda@Edge viewer response)', async () => {\n    const event = {\n      Records: [\n        {\n          cf: {\n            config: {\n              distributionDomainName: 'example.com',\n              distributionId: 'EDFDVBD6EXAMPLE',\n              eventType: 'viewer-response',\n              requestId: '4TyzHTaYWb1GX1qTfsHhEqV6HUDd_BzoBZnwfnvQc_1oF26ClkoUSEQ==',\n            },\n            request: {\n              clientIp: '203.0.113.178',\n              headers: {\n                host: [\n                  {\n                    key: 'Host',\n                    value: 'example.com',\n                  },\n                ],\n                'user-agent': [\n                  {\n                    key: 'User-Agent',\n                    value: 'curl/7.66.0',\n                  },\n                ],\n                accept: [\n                  {\n                    key: 'accept',\n                    value: '*/*',\n                  },\n                ],\n              },\n              method: 'GET',\n              querystring: '',\n              uri: '/header/add',\n            },\n            response: {\n              headers: {\n                'access-control-allow-credentials': [\n                  {\n                    key: 'Access-Control-Allow-Credentials',\n                    value: 'true',\n                  },\n                ],\n                'access-control-allow-origin': [\n                  {\n                    key: 'Access-Control-Allow-Origin',\n                    value: '*',\n                  },\n                ],\n                date: [\n                  {\n                    key: 'Date',\n                    value: 'Mon, 13 Jan 2020 20:14:56 GMT',\n                  },\n                ],\n                'referrer-policy': [\n                  {\n                    key: 'Referrer-Policy',\n                    value: 'no-referrer-when-downgrade',\n                  },\n                ],\n                server: [\n                  {\n                    key: 'Server',\n                    value: 'ExampleCustomOriginServer',\n                  },\n                ],\n                'x-content-type-options': [\n                  {\n                    key: 'X-Content-Type-Options',\n                    value: 'nosniff',\n                  },\n                ],\n                'x-frame-options': [\n                  {\n                    key: 'X-Frame-Options',\n                    value: 'DENY',\n                  },\n                ],\n                'x-xss-protection': [\n                  {\n                    key: 'X-XSS-Protection',\n                    value: '1; mode=block',\n                  },\n                ],\n                age: [\n                  {\n                    key: 'Age',\n                    value: '2402',\n                  },\n                ],\n                'content-type': [\n                  {\n                    key: 'Content-Type',\n                    value: 'text/html; charset=utf-8',\n                  },\n                ],\n                'content-length': [\n                  {\n                    key: 'Content-Length',\n                    value: '9593',\n                  },\n                ],\n              },\n              status: '200',\n              statusDescription: 'OK',\n            },\n          },\n        },\n      ],\n    }\n\n    interface CloudFrontHeaders {\n      [name: string]: [\n        {\n          key: string\n          value: string\n        },\n      ]\n    }\n    let called = false\n    let headers: CloudFrontHeaders = {}\n    await handler(event, {}, (_err, result) => {\n      if (result && result.headers) {\n        headers = result.headers as CloudFrontHeaders\n      }\n      called = true\n    })\n\n    expect(called).toBe(true)\n    expect(headers['strict-transport-security']).toEqual([\n      {\n        key: 'Strict-Transport-Security',\n        value: 'max-age=63072000; includeSubdomains; preload',\n      },\n    ])\n    expect(headers['x-custom']).toEqual([\n      {\n        key: 'X-Custom',\n        value: 'Foo',\n      },\n    ])\n  })\n\n  it('Callback Event (Lambda@Edge response)', async () => {\n    const event = {\n      Records: [\n        {\n          cf: {\n            config: {\n              distributionDomainName: 'example.com',\n              distributionId: 'EDFDVBD6EXAMPLE',\n              eventType: 'viewer-response',\n              requestId: '4TyzHTaYWb1GX1qTfsHhEqV6HUDd_BzoBZnwfnvQc_1oF26ClkoUSEQ==',\n            },\n            request: {\n              clientIp: '203.0.113.178',\n              headers: {\n                host: [\n                  {\n                    key: 'Host',\n                    value: 'example.com',\n                  },\n                ],\n              },\n              method: 'GET',\n              querystring: '',\n              uri: '/config/eventCheck',\n            },\n          },\n        },\n      ],\n    }\n\n    let called = false\n    await handler(event, {}, () => {\n      called = true\n    })\n\n    expect(called).toBe(true)\n  })\n\n  it('Should return a response where bodyEncoding is \"base64\" with binary', async () => {\n    const event = {\n      Records: [\n        {\n          cf: {\n            config: {\n              distributionDomainName: 'example.com',\n              distributionId: 'EXAMPLE123',\n              eventType: 'viewer-request',\n              requestId: 'exampleRequestId',\n            },\n            request: {\n              clientIp: '123.123.123.123',\n              headers: {\n                host: [\n                  {\n                    key: 'Host',\n                    value: 'example.com',\n                  },\n                ],\n              },\n              method: 'GET',\n              querystring: '',\n              uri: '/binary',\n            },\n          },\n        },\n      ],\n    }\n\n    const response = await handler(event)\n\n    expect(response.body).toBe('RmFrZSBJbWFnZQ==') // base64 encoded \"Fake Image\"\n    expect(response.bodyEncoding).toBe('base64')\n  })\n})\n"
  },
  {
    "path": "runtime-tests/lambda-edge/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true,\n    \"types\": [\"vitest/globals\"]\n  },\n  \"references\": [\n    { \"path\": \"../../tsconfig.build.json\" }\n  ]\n}\n"
  },
  {
    "path": "runtime-tests/lambda-edge/vitest.config.ts",
    "content": "import { defineProject } from 'vitest/config'\n\nexport default defineProject({\n  test: {\n    env: {\n      NAME: 'Node',\n    },\n    globals: true,\n    name: 'lambda-edge',\n  },\n})\n"
  },
  {
    "path": "runtime-tests/node/index.test.ts",
    "content": "import { createAdaptorServer, serve } from '@hono/node-server'\nimport * as undici from 'undici'\nimport { once } from 'node:events'\nimport type { Server } from 'node:http'\nimport type { AddressInfo } from 'node:net'\nimport { Hono } from '../../src'\nimport { Context } from '../../src/context'\nimport { env, getRuntimeKey } from '../../src/helper/adapter'\nimport { stream, streamSSE } from '../../src/helper/streaming'\nimport { basicAuth } from '../../src/middleware/basic-auth'\nimport { compress } from '../../src/middleware/compress'\nimport { jwt } from '../../src/middleware/jwt'\n\n// Test only minimal patterns.\n// See <https://github.com/honojs/node-server> for more tests and information.\n\ndescribe('Basic', () => {\n  const app = new Hono()\n\n  app.get('/', (c) => {\n    return c.text('Hello! Node.js!')\n  })\n  app.get('/runtime-name', (c) => {\n    return c.text(getRuntimeKey())\n  })\n\n  const agent = createAgent(app)\n\n  it('Should return 200 response', async () => {\n    const res = await agent.get('/')\n    expect(res.status).toBe(200)\n    await expect(res.text()).resolves.toBe('Hello! Node.js!')\n  })\n\n  it('Should return correct runtime name', async () => {\n    const res = await agent.get('/runtime-name')\n    expect(res.status).toBe(200)\n    await expect(res.text()).resolves.toBe('node')\n  })\n})\n\ndescribe('Environment Variables', () => {\n  it('Should return the environment variable', async () => {\n    const c = new Context(new Request('http://localhost/'))\n    const { NAME } = env<{ NAME: string }>(c)\n    expect(NAME).toBe('Node')\n  })\n})\n\ndescribe('Basic Auth Middleware', () => {\n  const app = new Hono()\n\n  const username = 'hono-user-a'\n  const password = 'hono-password-a'\n  app.use(\n    '/auth/*',\n    basicAuth({\n      username,\n      password,\n    })\n  )\n\n  app.get('/auth/*', () => new Response('auth'))\n\n  const agent = createAgent(app)\n\n  it('Should not authorize, return 401 Response', async () => {\n    const res = await agent.get('/auth/a')\n    expect(res.status).toBe(401)\n    await expect(res.text()).resolves.toBe('Unauthorized')\n  })\n\n  it('Should authorize, return 200 Response', async () => {\n    const credential = 'aG9uby11c2VyLWE6aG9uby1wYXNzd29yZC1h'\n    const res = await agent.get('/auth/a', { headers: { Authorization: `Basic ${credential}` } })\n    expect(res.status).toBe(200)\n    await expect(res.text()).resolves.toBe('auth')\n  })\n})\n\ndescribe('JWT Auth Middleware', () => {\n  const app = new Hono()\n\n  app.use('/jwt/*', jwt({ secret: 'a-secret', alg: 'HS256' }))\n  app.get('/jwt/a', (c) => c.text('auth'))\n\n  const agent = createAgent(app)\n\n  it('Should not authorize, return 401 Response', async () => {\n    const res = await agent.get('/jwt/a')\n    expect(res.status).toBe(401)\n    await expect(res.text()).resolves.toBe('Unauthorized')\n  })\n\n  it('Should authorize, return 200 Response', async () => {\n    const credential =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n    const res = await agent.get('/jwt/a', { headers: { Authorization: `Bearer ${credential}` } })\n    expect(res.status).toBe(200)\n    await expect(res.text()).resolves.toBe('auth')\n  })\n})\n\ndescribe('stream', () => {\n  const app = new Hono()\n\n  let aborted = false\n\n  app.get('/stream', (c) => {\n    return stream(c, async (stream) => {\n      stream.onAbort(() => {\n        aborted = true\n      })\n      return new Promise<void>((resolve) => {\n        stream.onAbort(resolve)\n      })\n    })\n  })\n  app.get('/streamHello', (c) => {\n    return stream(c, async (stream) => {\n      stream.onAbort(() => {\n        aborted = true\n      })\n      await stream.write('Hello')\n    })\n  })\n\n  const agent = createAgent(app)\n\n  beforeEach(() => {\n    aborted = false\n  })\n\n  it('Should call onAbort', async () => {\n    const controller = new AbortController()\n    const res = expect(agent.get('/stream', { signal: controller.signal })).rejects.toThrow(\n      'This operation was aborted'\n    )\n\n    expect(aborted).toBe(false)\n    await new Promise((resolve) => setTimeout(resolve, 10))\n    controller.abort()\n    await res\n    while (!aborted) {\n      await new Promise((resolve) => setTimeout(resolve))\n    }\n    expect(aborted).toBe(true)\n  })\n\n  it('Should not be called onAbort if already closed', async () => {\n    expect(aborted).toBe(false)\n    const res = await agent.get('/streamHello')\n    expect(res.status).toBe(200)\n    await expect(res.text()).resolves.toBe('Hello')\n    expect(aborted).toBe(false)\n  })\n})\n\ndescribe('streamSSE', () => {\n  const app = new Hono()\n\n  let aborted = false\n\n  app.get('/stream', (c) => {\n    return streamSSE(c, async (stream) => {\n      stream.onAbort(() => {\n        aborted = true\n      })\n      return new Promise<void>((resolve) => {\n        stream.onAbort(resolve)\n      })\n    })\n  })\n  app.get('/streamHello', (c) => {\n    return streamSSE(c, async (stream) => {\n      stream.onAbort(() => {\n        aborted = true\n      })\n      await stream.write('Hello')\n    })\n  })\n\n  const agent = createAgent(app)\n\n  beforeEach(() => {\n    aborted = false\n  })\n\n  it('Should call onAbort', async () => {\n    const controller = new AbortController()\n    const res = expect(agent.get('/stream', { signal: controller.signal })).rejects.toThrow(\n      'This operation was aborted'\n    )\n\n    expect(aborted).toBe(false)\n    await new Promise((resolve) => setTimeout(resolve, 10))\n    controller.abort()\n    await res\n    while (!aborted) {\n      await new Promise((resolve) => setTimeout(resolve))\n    }\n    expect(aborted).toBe(true)\n  })\n\n  it('Should not be called onAbort if already closed', async () => {\n    expect(aborted).toBe(false)\n    const res = await agent.get('/streamHello')\n    expect(res.status).toBe(200)\n    await expect(res.text()).resolves.toBe('Hello')\n    expect(aborted).toBe(false)\n  })\n})\n\ndescribe('compress', async () => {\n  const cssContent = Array.from({ length: 60 }, () => 'body { color: red; }').join('\\n')\n  const [externalServer, serverInfo] = await new Promise<[Server, AddressInfo]>((resolve) => {\n    const externalApp = new Hono()\n    externalApp.get('/style.css', (c) =>\n      c.text(cssContent, {\n        headers: {\n          'Content-Type': 'text/css',\n        },\n      })\n    )\n    const server = serve(\n      {\n        fetch: externalApp.fetch,\n        port: 0,\n        hostname: '0.0.0.0',\n      },\n      (serverInfo) => {\n        resolve([server as Server, serverInfo])\n      }\n    )\n  })\n\n  const app = new Hono()\n  app.use(compress())\n  app.get('/fetch/:file', (c) => {\n    return fetch(`http://${serverInfo.address}:${serverInfo.port}/${c.req.param('file')}`)\n  })\n  const agent = createAgent(app)\n\n  afterAll(() => {\n    externalServer.close()\n  })\n\n  it('Should be compressed a fetch response', async () => {\n    const res = await agent.get('/fetch/style.css')\n    expect(res.status).toBe(200)\n    expect(res.headers.get('content-encoding')).toBe('gzip')\n    await expect(res.text()).resolves.toBe(cssContent)\n  })\n})\n\ndescribe('Buffers', () => {\n  const app = new Hono()\n    .get('/', async (c) => {\n      return c.body(Buffer.from('hello'))\n    })\n    .get('/uint8array', async (c) => {\n      return c.body(Uint8Array.from('hello'.split(''), (c) => c.charCodeAt(0)))\n    })\n\n  const agent = createAgent(app)\n\n  it('should allow returning buffers', async () => {\n    const res = await agent.get('/')\n    expect(res.status).toBe(200)\n    await expect(res.text()).resolves.toBe('hello')\n  })\n\n  it('should allow returning uint8array as well', async () => {\n    const res = await agent.get('/uint8array')\n    expect(res.status).toBe(200)\n    await expect(res.text()).resolves.toBe('hello')\n  })\n})\n\nfunction createAgent(app: Hono) {\n  const server = createAdaptorServer(app)\n  const listening = once(server.listen(), 'listening')\n\n  return {\n    async get(path: string, init?: undici.RequestInit) {\n      await listening\n      const url = new URL(path, getOrigin())\n      return undici.fetch(url, init)\n    },\n  }\n\n  function getOrigin(): string {\n    let address = server.address()\n\n    if (typeof address === 'object') {\n      address = address?.port ? `http://localhost:${address.port}` : 'http://localhost'\n    }\n\n    return address\n  }\n}\n"
  },
  {
    "path": "runtime-tests/node/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true,\n    \"types\": [\"vitest/globals\"]\n  },\n  \"references\": [\n    { \"path\": \"../../tsconfig.build.json\" }\n  ]\n}\n"
  },
  {
    "path": "runtime-tests/node/vitest.config.ts",
    "content": "import { defineProject } from 'vitest/config'\n\nexport default defineProject({\n  test: {\n    env: {\n      NAME: 'Node',\n    },\n    globals: true,\n    name: 'node',\n  },\n})\n"
  },
  {
    "path": "runtime-tests/workerd/index.test.ts",
    "content": "import { unstable_dev } from 'wrangler'\nimport type { Unstable_DevWorker } from 'wrangler'\nimport { WebSocket } from 'ws'\n\ndescribe('workerd', () => {\n  let worker: Unstable_DevWorker\n\n  beforeAll(async () => {\n    worker = await unstable_dev('./runtime-tests/workerd/index.ts', {\n      vars: {\n        NAME: 'Hono',\n      },\n      experimental: { disableExperimentalWarning: true },\n    })\n  })\n\n  afterAll(async () => {\n    await worker.stop()\n  })\n\n  it('Should return 200 response with the runtime key', async () => {\n    const res = await worker.fetch('/')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('Hello from workerd')\n  })\n\n  it('Should return 200 response with the environment variable', async () => {\n    const res = await worker.fetch('/env')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('Hono')\n  })\n\n  it('Should return 200 response with the true message', async () => {\n    const res = await worker.fetch('/color')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('True')\n  })\n})\n\ndescribe('workerd with WebSocket', () => {\n  // worker.fetch does not support WebSocket:\n  // https://github.com/cloudflare/workers-sdk/issues/4573#issuecomment-1850420973\n  it('Should handle the WebSocket connection correctly', async () => {\n    const worker = await unstable_dev('./runtime-tests/workerd/index.ts', {\n      experimental: { disableExperimentalWarning: true },\n    })\n    const ws = new WebSocket(`ws://${worker.address}:${worker.port}/ws`)\n\n    const openHandler = vi.fn()\n    const messageHandler = vi.fn()\n    const closeHandler = vi.fn()\n\n    const waitForOpen = new Promise((resolve) => {\n      ws.addEventListener('open', () => {\n        openHandler()\n        ws.send('Hello')\n      })\n      ws.addEventListener('close', async () => {\n        closeHandler()\n        resolve(undefined)\n      })\n      ws.addEventListener('message', async (event) => {\n        messageHandler(event.data)\n        ws.close()\n      })\n    })\n\n    await waitForOpen\n    await worker.stop()\n\n    expect(openHandler).toHaveBeenCalled()\n    expect(messageHandler).toHaveBeenCalledWith('Hello')\n    expect(closeHandler).toHaveBeenCalled()\n  })\n})\n\ndescribe('workerd with NO_COLOR', () => {\n  let worker: Unstable_DevWorker\n\n  beforeAll(async () => {\n    worker = await unstable_dev('./runtime-tests/workerd/index.ts', {\n      vars: {\n        NO_COLOR: true,\n      },\n      experimental: { disableExperimentalWarning: true },\n    })\n  })\n\n  afterAll(async () => {\n    await worker.stop()\n  })\n\n  it('Should return 200 response with the false message', async () => {\n    const res = await worker.fetch('/color')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('False')\n  })\n})\n"
  },
  {
    "path": "runtime-tests/workerd/index.ts",
    "content": "import { upgradeWebSocket } from '../../src/adapter/cloudflare-workers'\nimport { env, getRuntimeKey } from '../../src/helper/adapter'\nimport { Hono } from '../../src/hono'\nimport { getColorEnabledAsync } from '../../src/utils/color'\n\nconst app = new Hono()\n\napp.get('/', (c) => c.text(`Hello from ${getRuntimeKey()}`))\n\napp.get('/env', (c) => {\n  const { NAME } = env<{ NAME: string }>(c)\n  return c.text(NAME)\n})\n\napp.get(\n  '/ws',\n  upgradeWebSocket(() => {\n    return {\n      onMessage(event, ws) {\n        ws.send(event.data as string)\n      },\n    }\n  })\n)\n\napp.get('/color', async (c) => {\n  return c.text((await getColorEnabledAsync()) ? 'True' : 'False')\n})\n\nexport default app\n"
  },
  {
    "path": "runtime-tests/workerd/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true,\n    \"types\": [\"vitest/globals\"]\n  },\n  \"references\": [\n    { \"path\": \"../../tsconfig.build.json\" }\n  ]\n}\n"
  },
  {
    "path": "runtime-tests/workerd/vitest.config.ts",
    "content": "import { defineProject } from 'vitest/config'\n\nexport default defineProject({\n  test: {\n    globals: true,\n    name: 'workerd',\n  },\n})\n"
  },
  {
    "path": "src/adapter/aws-lambda/conninfo.test.ts",
    "content": "import { Context } from '../../context'\nimport { getConnInfo } from './conninfo'\n\ndescribe('getConnInfo', () => {\n  describe('API Gateway v1', () => {\n    it('Should return the client IP from identity.sourceIp', () => {\n      const ip = '203.0.113.42'\n      const c = new Context(new Request('http://localhost/'), {\n        env: {\n          requestContext: {\n            identity: {\n              sourceIp: ip,\n              userAgent: 'test',\n            },\n            accountId: '123',\n            apiId: 'abc',\n            authorizer: {},\n            domainName: 'example.com',\n            domainPrefix: 'api',\n            extendedRequestId: 'xxx',\n            httpMethod: 'GET',\n            path: '/',\n            protocol: 'HTTP/1.1',\n            requestId: 'req-1',\n            requestTime: '',\n            requestTimeEpoch: 0,\n            resourcePath: '/',\n            stage: 'prod',\n          },\n        },\n      })\n\n      const info = getConnInfo(c)\n\n      expect(info.remote.address).toBe(ip)\n    })\n  })\n\n  describe('API Gateway v2', () => {\n    it('Should return the client IP from http.sourceIp', () => {\n      const ip = '198.51.100.23'\n      const c = new Context(new Request('http://localhost/'), {\n        env: {\n          requestContext: {\n            http: {\n              method: 'GET',\n              path: '/',\n              protocol: 'HTTP/1.1',\n              sourceIp: ip,\n              userAgent: 'test',\n            },\n            accountId: '123',\n            apiId: 'abc',\n            authentication: null,\n            authorizer: {},\n            domainName: 'example.com',\n            domainPrefix: 'api',\n            requestId: 'req-1',\n            routeKey: 'GET /',\n            stage: 'prod',\n            time: '',\n            timeEpoch: 0,\n          },\n        },\n      })\n\n      const info = getConnInfo(c)\n\n      expect(info.remote.address).toBe(ip)\n    })\n  })\n\n  describe('ALB', () => {\n    it.each([\n      {\n        description: 'ALB appends real client IP',\n        xff: '10.0.0.1, 192.0.2.50',\n        expected: '192.0.2.50',\n      },\n      {\n        description: 'attacker-controlled first IP',\n        xff: '127.0.0.1, 192.168.1.100',\n        expected: '192.168.1.100',\n      },\n    ])('Should return the last IP from x-forwarded-for ($description)', ({ xff, expected }) => {\n      const req = new Request('http://localhost/', {\n        headers: { 'x-forwarded-for': xff },\n      })\n      const c = new Context(req, {\n        env: {\n          requestContext: {\n            elb: {\n              targetGroupArn: 'arn:aws:elasticloadbalancing:...',\n            },\n          },\n        },\n      })\n      const info = getConnInfo(c)\n      expect(info.remote.address).toBe(expected)\n    })\n\n    it('Should return undefined when no x-forwarded-for header', () => {\n      const c = new Context(new Request('http://localhost/'), {\n        env: {\n          requestContext: {\n            elb: {\n              targetGroupArn: 'arn:aws:elasticloadbalancing:...',\n            },\n          },\n        },\n      })\n\n      const info = getConnInfo(c)\n\n      expect(info.remote.address).toBeUndefined()\n    })\n  })\n})\n"
  },
  {
    "path": "src/adapter/aws-lambda/conninfo.ts",
    "content": "import type { Context } from '../../context'\nimport type { GetConnInfo } from '../../helper/conninfo'\nimport type {\n  ApiGatewayRequestContext,\n  ApiGatewayRequestContextV2,\n  ALBRequestContext,\n} from './types'\n\ntype LambdaRequestContext =\n  | ApiGatewayRequestContext\n  | ApiGatewayRequestContextV2\n  | ALBRequestContext\n\ntype Env = {\n  Bindings: {\n    requestContext: LambdaRequestContext\n  }\n}\n\n/**\n * Get connection information from AWS Lambda\n *\n * Extracts client IP from various Lambda event sources:\n * - API Gateway v1 (REST API): requestContext.identity.sourceIp\n * - API Gateway v2 (HTTP API/Function URLs): requestContext.http.sourceIp\n * - ALB: Falls back to x-forwarded-for header\n *\n * @param c - Context\n * @returns Connection information including remote address\n * @example\n * ```ts\n * import { Hono } from 'hono'\n * import { handle, getConnInfo } from 'hono/aws-lambda'\n *\n * const app = new Hono()\n *\n * app.get('/', (c) => {\n *   const info = getConnInfo(c)\n *   return c.text(`Your IP: ${info.remote.address}`)\n * })\n *\n * export const handler = handle(app)\n * ```\n */\nexport const getConnInfo: GetConnInfo = (c: Context<Env>) => {\n  const requestContext = c.env.requestContext\n\n  let address: string | undefined\n\n  // API Gateway v1 - has identity object\n  if ('identity' in requestContext && requestContext.identity?.sourceIp) {\n    address = requestContext.identity.sourceIp\n  }\n  // API Gateway v2 - has http object\n  else if ('http' in requestContext && requestContext.http?.sourceIp) {\n    address = requestContext.http.sourceIp\n  }\n  // ALB - use X-Forwarded-For header\n  else {\n    const xff = c.req.header('x-forwarded-for')\n    if (xff) {\n      const ips = xff.split(',')\n      // ALB appends the real client IP to the end of the header\n      address = ips[ips.length - 1].trim()\n    }\n  }\n\n  return {\n    remote: {\n      address,\n    },\n  }\n}\n"
  },
  {
    "path": "src/adapter/aws-lambda/handler.test.ts",
    "content": "import type { LambdaEvent, LatticeProxyEventV2 } from './handler'\nimport { getProcessor, isContentEncodingBinary, defaultIsContentTypeBinary } from './handler'\n\n// Base event objects to reduce duplication\nconst baseV1Event: LambdaEvent = {\n  version: '1.0',\n  resource: '/my/path',\n  path: '/my/path',\n  httpMethod: 'GET',\n  headers: {},\n  multiValueHeaders: {},\n  queryStringParameters: {},\n  requestContext: {\n    accountId: '123456789012',\n    apiId: 'id',\n    authorizer: { claims: null, scopes: null },\n    domainName: 'id.execute-api.us-east-1.amazonaws.com',\n    domainPrefix: 'id',\n    extendedRequestId: 'request-id',\n    httpMethod: 'GET',\n    identity: {\n      sourceIp: '192.0.2.1',\n      userAgent: 'user-agent',\n      clientCert: {\n        clientCertPem: 'CERT_CONTENT',\n        subjectDN: 'www.example.com',\n        issuerDN: 'Example issuer',\n        serialNumber: 'a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1',\n        validity: {\n          notBefore: 'May 28 12:30:02 2019 GMT',\n          notAfter: 'Aug  5 09:36:04 2021 GMT',\n        },\n      },\n    },\n    path: '/my/path',\n    protocol: 'HTTP/1.1',\n    requestId: 'id=',\n    requestTime: '04/Mar/2020:19:15:17 +0000',\n    requestTimeEpoch: 1583349317135,\n    resourcePath: '/my/path',\n    stage: '$default',\n  },\n  pathParameters: {},\n  stageVariables: {},\n  body: null,\n  isBase64Encoded: false,\n}\n\nconst baseV2Event: LambdaEvent = {\n  version: '2.0',\n  routeKey: '$default',\n  rawPath: '/my/path',\n  rawQueryString: '',\n  cookies: [],\n  headers: {},\n  queryStringParameters: {},\n  requestContext: {\n    accountId: '123456789012',\n    apiId: 'api-id',\n    authentication: null,\n    authorizer: {},\n    domainName: 'id.execute-api.us-east-1.amazonaws.com',\n    domainPrefix: 'id',\n    http: {\n      method: 'POST',\n      path: '/my/path',\n      protocol: 'HTTP/1.1',\n      sourceIp: '192.0.2.1',\n      userAgent: 'agent',\n    },\n    requestId: 'id',\n    routeKey: '$default',\n    stage: '$default',\n    time: '12/Mar/2020:19:03:58 +0000',\n    timeEpoch: 1583348638390,\n  },\n  body: null,\n  pathParameters: {},\n  isBase64Encoded: false,\n  stageVariables: {},\n}\n\ndescribe('isContentTypeBinary', () => {\n  it.each([\n    ['image/png', true],\n    ['font/woff2', true],\n    ['image/svg+xml', false],\n    ['image/svg+xml; charset=UTF-8', false],\n    ['text/plain', false],\n    ['text/plain; charset=UTF-8', false],\n    ['text/css', false],\n    ['text/javascript', false],\n    ['application/json', false],\n    ['application/ld+json', false],\n    ['application/json', false],\n    ['application/vnd.openxmlformats-officedocument.wordprocessingml.document', true],\n    ['application/msword', true],\n    ['application/epub+zip', true],\n    ['application/ld+json', false],\n    ['application/vnd.oasis.opendocument.text', true],\n  ])('Should determine whether %s it is binary', (mimeType: string, expected: boolean) => {\n    expect(defaultIsContentTypeBinary(mimeType)).toBe(expected)\n  })\n})\n\ndescribe('isContentEncodingBinary', () => {\n  it('Should determine whether it is compressed', () => {\n    expect(isContentEncodingBinary('gzip')).toBe(true)\n    expect(isContentEncodingBinary('compress')).toBe(true)\n    expect(isContentEncodingBinary('deflate')).toBe(true)\n    expect(isContentEncodingBinary('br')).toBe(true)\n    expect(isContentEncodingBinary('deflate, gzip')).toBe(true)\n    expect(isContentEncodingBinary('')).toBe(false)\n    expect(isContentEncodingBinary('unknown')).toBe(false)\n  })\n})\n\ndescribe('EventProcessor.createResult with contentTypesAsBinary', () => {\n  const event = baseV1Event\n\n  it('Should encode as base64 when content-type is in contentTypesAsBinary array', async () => {\n    const processor = getProcessor(event)\n    const response = new Response('test content', {\n      headers: { 'content-type': 'application/custom' },\n    })\n\n    const result = await processor.createResult(event, response, {\n      isContentTypeBinary: (contentType: string) => contentType === 'application/custom',\n    })\n\n    expect(result.isBase64Encoded).toBe(true)\n    expect(result.body).toBe('dGVzdCBjb250ZW50')\n  })\n\n  it('Should not encode as base64 when content-type is not in isContentTypeBinary array', async () => {\n    const processor = getProcessor(event)\n    const response = new Response('test content', {\n      headers: { 'content-type': 'application/json' },\n    })\n\n    const result = await processor.createResult(event, response, {\n      isContentTypeBinary: (contentType: string) => contentType === 'application/custom',\n    })\n\n    expect(result.isBase64Encoded).toBe(false)\n    expect(result.body).toBe('test content')\n  })\n\n  it('Should use defaultIsContentTypeBinary when isContentTypeBinary is undefined with binary content', async () => {\n    const processor = getProcessor(event)\n    const response = new Response('test image content', {\n      headers: { 'content-type': 'image/png' },\n    })\n\n    // Pass undefined for isContentTypeBinary to test default behavior\n    const result = await processor.createResult(event, response, {\n      isContentTypeBinary: undefined,\n    })\n\n    expect(result.isBase64Encoded).toBe(true)\n    expect(result.body).toBe('dGVzdCBpbWFnZSBjb250ZW50')\n  })\n\n  it('Should use defaultIsContentTypeBinary when isContentTypeBinary is undefined with non-binary content', async () => {\n    const processor = getProcessor(event)\n    const response = new Response('test text content', {\n      headers: { 'content-type': 'text/plain' },\n    })\n\n    // Pass undefined for isContentTypeBinary to test default behavior\n    const result = await processor.createResult(event, response, {\n      isContentTypeBinary: undefined,\n    })\n\n    expect(result.isBase64Encoded).toBe(false)\n    expect(result.body).toBe('test text content')\n  })\n})\n\ndescribe('EventProcessor.createRequest', () => {\n  it('Should preserve percent-encoded values in query string for version 1.0', () => {\n    const event: LambdaEvent = {\n      ...baseV1Event,\n      // API Gateway provides decoded values\n      multiValueQueryStringParameters: {\n        path: ['/book/{bookId}/'], // Originally %7BbookId%7D\n        name: ['John Doe'], // Originally John%20Doe\n        tag: ['日本語'], // Originally %E6%97%A5%E6%9C%AC%E8%AA%9E\n      },\n    }\n\n    const processor = getProcessor(event)\n    const request = processor.createRequest(event)\n\n    // URL should contain properly encoded values\n    expect(request.url).toEqual(\n      'https://id.execute-api.us-east-1.amazonaws.com/my/path?path=%2Fbook%2F%7BbookId%7D%2F&name=John%20Doe&tag=%E6%97%A5%E6%9C%AC%E8%AA%9E'\n    )\n  })\n\n  it('Should handle special characters correctly in queryStringParameters for version 1.0', () => {\n    const event: LambdaEvent = {\n      ...baseV1Event,\n      queryStringParameters: {\n        'key with spaces': 'value with spaces',\n        'special!@#$%^&*()': 'chars!@#$%^&*()',\n        equals: 'a=b=c',\n        ampersand: 'a&b&c',\n      },\n    }\n\n    const processor = getProcessor(event)\n    const request = processor.createRequest(event)\n\n    // Verify the URL is properly encoded\n    const url = new URL(request.url)\n    expect(url.searchParams.get('key with spaces')).toBe('value with spaces')\n    expect(url.searchParams.get('special!@#$%^&*()')).toBe('chars!@#$%^&*()')\n    expect(url.searchParams.get('equals')).toBe('a=b=c')\n    expect(url.searchParams.get('ampersand')).toBe('a&b&c')\n  })\n\n  it('Should return valid Request object from version 1.0 API Gateway event', () => {\n    const event: LambdaEvent = {\n      ...baseV1Event,\n      headers: {\n        'content-type': 'application/json',\n        header1: 'value1',\n        header2: 'value1',\n      },\n      multiValueHeaders: {\n        header1: ['value1'],\n        header2: ['value1', 'value2', 'value3'],\n      },\n      // This value doesn't match multi value's content.\n      // We want to assert handler is using the multi value's content when both are available.\n      queryStringParameters: {\n        parameter2: 'value',\n      },\n      multiValueQueryStringParameters: {\n        parameter1: ['value1', 'value2'],\n        parameter2: ['value'],\n      },\n    }\n\n    const processor = getProcessor(event)\n    const request = processor.createRequest(event)\n\n    expect(request.method).toEqual('GET')\n    // Note: Values are now properly encoded\n    expect(request.url).toEqual(\n      'https://id.execute-api.us-east-1.amazonaws.com/my/path?parameter1=value1&parameter1=value2&parameter2=value'\n    )\n    expect(Object.fromEntries(request.headers)).toEqual({\n      'content-type': 'application/json',\n      header1: 'value1',\n      header2: 'value1, value2, value3',\n    })\n  })\n\n  it('Should return valid Request object from version 2.0 API Gateway event', () => {\n    const event: LambdaEvent = {\n      ...baseV2Event,\n      rawQueryString: 'parameter1=value1&parameter1=value2&parameter2=value',\n      cookies: ['cookie1', 'cookie2'],\n      headers: {\n        'content-type': 'application/json',\n        header1: 'value1',\n        header2: 'value1,value2',\n      },\n      queryStringParameters: {\n        parameter1: 'value1,value2',\n        parameter2: 'value',\n      },\n      body: 'Hello from Lambda',\n      pathParameters: {\n        parameter1: 'value1',\n      },\n      stageVariables: {\n        stageVariable1: 'value1',\n        stageVariable2: 'value2',\n      },\n    }\n\n    const processor = getProcessor(event)\n    const request = processor.createRequest(event)\n\n    expect(request.method).toEqual('POST')\n    expect(request.url).toEqual(\n      'https://id.execute-api.us-east-1.amazonaws.com/my/path?parameter1=value1&parameter1=value2&parameter2=value'\n    )\n    expect(Object.fromEntries(request.headers)).toEqual({\n      'content-type': 'application/json',\n      cookie: 'cookie1; cookie2',\n      header1: 'value1',\n      header2: 'value1,value2',\n    })\n  })\n\n  it('Should return valid Request object from version 2.0 Lattice event', async () => {\n    const event: LatticeProxyEventV2 = {\n      version: '2.0',\n      // query string parameters from the path take precedence over the explicit notation below\n      path: '/my/path?parameter1=value1&parameter1=value2&parameter2=value',\n      method: 'POST',\n      headers: {\n        cookie: ['cookie1=value1; cookie2=value2'],\n        'content-type': ['application/x-www-form-urlencoded'],\n        header1: ['value1'],\n        header2: ['value1', 'value2'],\n        host: ['my-service-a1b2c3.x1y2z3.vpc-lattice-svcs.us-east-1.on.aws'],\n      },\n      queryStringParameters: {\n        parameter1: ['value1', 'value2'],\n        parameter2: ['value'],\n      },\n      body: 'SGVsbG8gZnJvbSBMYW1iZGE=',\n      isBase64Encoded: true,\n      requestContext: {\n        serviceNetworkArn: '',\n        serviceArn: '',\n        targetGroupArn: '',\n        identity: {},\n        region: 'us-east-1',\n        timeEpoch: '1583348638390123',\n      },\n    }\n\n    const processor = getProcessor(event)\n    const request = processor.createRequest(event)\n\n    expect(await request.text()).toEqual('Hello from Lambda')\n    expect(request.method).toEqual('POST')\n    expect(request.url).toEqual(\n      'https://my-service-a1b2c3.x1y2z3.vpc-lattice-svcs.us-east-1.on.aws/my/path?parameter1=value1&parameter1=value2&parameter2=value'\n    )\n    expect(Object.fromEntries(request.headers)).toEqual({\n      'content-type': 'application/x-www-form-urlencoded',\n      cookie: 'cookie1=value1; cookie2=value2',\n      header1: 'value1',\n      header2: 'value1, value2',\n      host: 'my-service-a1b2c3.x1y2z3.vpc-lattice-svcs.us-east-1.on.aws',\n    })\n  })\n\n  describe('non-ASCII header value processing', () => {\n    it('Should encode non-ASCII header values with encodeURIComponent', async () => {\n      const event: LambdaEvent = {\n        ...baseV1Event,\n        headers: {\n          'x-city': '炎', // Non-ASCII character\n        },\n      }\n\n      const processor = getProcessor(event)\n      const request = processor.createRequest(event)\n\n      const xCity = request.headers.get('x-city') ?? ''\n      expect(decodeURIComponent(xCity)).toBe('炎')\n    })\n  })\n})\n"
  },
  {
    "path": "src/adapter/aws-lambda/handler.ts",
    "content": "import type { Hono } from '../../hono'\nimport type { Env, Schema } from '../../types'\nimport { decodeBase64, encodeBase64 } from '../../utils/encode'\nimport type {\n  ALBRequestContext,\n  ApiGatewayRequestContext,\n  ApiGatewayRequestContextV2,\n  Handler,\n  LambdaContext,\n  LatticeRequestContextV2,\n} from './types'\n\nfunction sanitizeHeaderValue(value: string): string {\n  // Check if the value contains non-ASCII characters (char codes > 127)\n  // eslint-disable-next-line no-control-regex\n  const hasNonAscii = /[^\\x00-\\x7F]/.test(value)\n  if (!hasNonAscii) {\n    return value\n  }\n  return encodeURIComponent(value)\n}\n\nexport type LambdaEvent =\n  | APIGatewayProxyEvent\n  | APIGatewayProxyEventV2\n  | ALBProxyEvent\n  | LatticeProxyEventV2\n\nexport interface LatticeProxyEventV2 {\n  version: string\n  path: string\n  method: string\n  headers: Record<string, string[] | undefined>\n  queryStringParameters: Record<string, string[] | undefined>\n  body: string | null\n  isBase64Encoded: boolean\n  requestContext: LatticeRequestContextV2\n}\n\n// When calling HTTP API or Lambda directly through function urls\nexport interface APIGatewayProxyEventV2 {\n  version: string\n  routeKey: string\n  headers: Record<string, string | undefined>\n  multiValueHeaders?: undefined\n  cookies?: string[]\n  rawPath: string\n  rawQueryString: string\n  body: string | null\n  isBase64Encoded: boolean\n  requestContext: ApiGatewayRequestContextV2\n  queryStringParameters?: {\n    [name: string]: string | undefined\n  }\n  pathParameters?: {\n    [name: string]: string | undefined\n  }\n  stageVariables?: {\n    [name: string]: string | undefined\n  }\n}\n\n// When calling Lambda through an API Gateway\nexport interface APIGatewayProxyEvent {\n  version: string\n  httpMethod: string\n  headers: Record<string, string | undefined>\n  multiValueHeaders?: {\n    [headerKey: string]: string[]\n  }\n  path: string\n  body: string | null\n  isBase64Encoded: boolean\n  queryStringParameters?: Record<string, string | undefined>\n  requestContext: ApiGatewayRequestContext\n  resource: string\n  multiValueQueryStringParameters?: {\n    [parameterKey: string]: string[]\n  }\n  pathParameters?: Record<string, string>\n  stageVariables?: Record<string, string>\n}\n\n// When calling Lambda through an Application Load Balancer\nexport interface ALBProxyEvent {\n  httpMethod: string\n  headers?: Record<string, string | undefined>\n  multiValueHeaders?: Record<string, string[] | undefined>\n  path: string\n  body: string | null\n  isBase64Encoded: boolean\n  queryStringParameters?: Record<string, string | undefined>\n  multiValueQueryStringParameters?: {\n    [parameterKey: string]: string[]\n  }\n  requestContext: ALBRequestContext\n}\n\ntype WithHeaders = {\n  headers: Record<string, string>\n  multiValueHeaders?: undefined\n}\ntype WithMultiValueHeaders = {\n  headers?: undefined\n  multiValueHeaders: Record<string, string[]>\n}\n\nexport type APIGatewayProxyResult = {\n  statusCode: number\n  statusDescription?: string\n  body: string\n  cookies?: string[]\n  isBase64Encoded: boolean\n} & (WithHeaders | WithMultiValueHeaders)\n\nconst getRequestContext = (\n  event: LambdaEvent\n):\n  | ApiGatewayRequestContext\n  | ApiGatewayRequestContextV2\n  | ALBRequestContext\n  | LatticeRequestContextV2 => {\n  return event.requestContext\n}\n\nconst streamToNodeStream = async (\n  reader: ReadableStreamDefaultReader<Uint8Array>,\n  writer: NodeJS.WritableStream\n): Promise<void> => {\n  let readResult = await reader.read()\n  while (!readResult.done) {\n    writer.write(readResult.value)\n    readResult = await reader.read()\n  }\n  writer.end()\n}\n\nexport const streamHandle = <\n  E extends Env = Env,\n  S extends Schema = {},\n  BasePath extends string = '/',\n>(\n  app: Hono<E, S, BasePath>\n): Handler => {\n  // @ts-expect-error awslambda is not a standard API\n  return awslambda.streamifyResponse(\n    async (event: LambdaEvent, responseStream: NodeJS.WritableStream, context: LambdaContext) => {\n      const processor = getProcessor(event)\n      try {\n        const req = processor.createRequest(event)\n        const requestContext = getRequestContext(event)\n\n        const res = await app.fetch(req, {\n          event,\n          requestContext,\n          context,\n        })\n\n        const headers: Record<string, string> = {}\n        const cookies: string[] = []\n        res.headers.forEach((value, name) => {\n          if (name === 'set-cookie') {\n            cookies.push(value)\n          } else {\n            headers[name] = value\n          }\n        })\n\n        // Check content type\n        const httpResponseMetadata = {\n          statusCode: res.status,\n          headers,\n          cookies,\n        }\n\n        // Update response stream\n        // @ts-expect-error awslambda is not a standard API\n        responseStream = awslambda.HttpResponseStream.from(responseStream, httpResponseMetadata)\n\n        if (res.body) {\n          await streamToNodeStream(res.body.getReader(), responseStream)\n        } else {\n          responseStream.write('')\n        }\n      } catch (error) {\n        console.error('Error processing request:', error)\n        responseStream.write('Internal Server Error')\n      } finally {\n        responseStream.end()\n      }\n    }\n  )\n}\n\ntype HandleOptions = {\n  isContentTypeBinary: ((contentType: string) => boolean) | undefined\n}\n\n/**\n * Converts a Hono application to an AWS Lambda handler.\n *\n * Accepts events from API Gateway (v1 and v2), Application Load Balancer (ALB),\n * and Lambda Function URLs.\n *\n * @param app - The Hono application instance\n * @param options - Optional configuration\n * @param options.isContentTypeBinary - A function to determine if the content type is binary.\n *                                      If not provided, the default function will be used.\n * @returns Lambda handler function\n *\n * @example\n * ```js\n * import { Hono } from 'hono'\n * import { handle } from 'hono/aws-lambda'\n *\n * const app = new Hono()\n *\n * app.get('/', (c) => c.text('Hello from Lambda'))\n * app.get('/json', (c) => c.json({ message: 'Hello JSON' }))\n *\n * export const handler = handle(app)\n * ```\n *\n * @example\n * ```js\n * // With custom binary content type detection\n * import { handle, defaultIsContentTypeBinary } from 'hono/aws-lambda'\n * export const handler = handle(app, {\n *   isContentTypeBinary: (contentType) => {\n *     if (defaultIsContentTypeBinary(contentType)) {\n *       // default logic same as prior to v4.8.4\n *       return true\n *     }\n *     return contentType.startsWith('image/') || contentType === 'application/pdf'\n *   }\n * })\n * ```\n */\nexport const handle = <E extends Env = Env, S extends Schema = {}, BasePath extends string = '/'>(\n  app: Hono<E, S, BasePath>,\n  { isContentTypeBinary }: HandleOptions = { isContentTypeBinary: undefined }\n): (<L extends LambdaEvent>(\n  event: L,\n  lambdaContext?: LambdaContext\n) => Promise<\n  APIGatewayProxyResult &\n    (L extends { multiValueHeaders: Record<string, string[]> }\n      ? WithMultiValueHeaders\n      : WithHeaders)\n>) => {\n  // @ts-expect-error FIXME: Fix return typing\n  return async (event, lambdaContext?) => {\n    const processor = getProcessor(event)\n\n    const req = processor.createRequest(event)\n    const requestContext = getRequestContext(event)\n\n    const res = await app.fetch(req, {\n      event,\n      requestContext,\n      lambdaContext,\n    })\n\n    return processor.createResult(event, res, { isContentTypeBinary })\n  }\n}\n\nexport abstract class EventProcessor<E extends LambdaEvent> {\n  protected abstract getPath(event: E): string\n\n  protected abstract getMethod(event: E): string\n\n  protected abstract getQueryString(event: E): string\n\n  protected abstract getHeaders(event: E): Headers\n\n  protected abstract getCookies(event: E, headers: Headers): void\n\n  protected abstract setCookiesToResult(result: APIGatewayProxyResult, cookies: string[]): void\n\n  protected getHeaderValue(headers: E['headers'], key: string): string | undefined {\n    const value = headers\n      ? Array.isArray(headers[key])\n        ? headers[key][0]\n        : headers[key]\n      : undefined\n\n    return value\n  }\n\n  protected getDomainName(event: E): string | undefined {\n    if (event.requestContext && 'domainName' in event.requestContext) {\n      return event.requestContext.domainName\n    }\n\n    const hostFromHeaders = this.getHeaderValue(event.headers, 'host')\n\n    if (hostFromHeaders) {\n      return hostFromHeaders\n    }\n\n    const multiValueHeaders = 'multiValueHeaders' in event ? event.multiValueHeaders : {}\n    const hostFromMultiValueHeaders = this.getHeaderValue(multiValueHeaders, 'host')\n\n    return hostFromMultiValueHeaders\n  }\n\n  createRequest(event: E): Request {\n    const queryString = this.getQueryString(event)\n    const domainName = this.getDomainName(event)\n    const path = this.getPath(event)\n    const urlPath = `https://${domainName}${path}`\n    const url = queryString ? `${urlPath}?${queryString}` : urlPath\n\n    const headers = this.getHeaders(event)\n\n    const method = this.getMethod(event)\n    const requestInit: RequestInit = {\n      headers,\n      method,\n    }\n\n    if (event.body) {\n      requestInit.body = event.isBase64Encoded ? decodeBase64(event.body) : event.body\n    }\n\n    return new Request(url, requestInit)\n  }\n\n  async createResult(\n    event: E,\n    res: Response,\n    options: Pick<HandleOptions, 'isContentTypeBinary'>\n  ): Promise<APIGatewayProxyResult> {\n    // determine whether the response body should be base64 encoded\n    const contentType = res.headers.get('content-type')\n    const isContentTypeBinary = options.isContentTypeBinary ?? defaultIsContentTypeBinary // overwrite default function if provided\n    let isBase64Encoded = contentType && isContentTypeBinary(contentType) ? true : false\n\n    if (!isBase64Encoded) {\n      const contentEncoding = res.headers.get('content-encoding')\n      isBase64Encoded = isContentEncodingBinary(contentEncoding)\n    }\n\n    const body = isBase64Encoded ? encodeBase64(await res.arrayBuffer()) : await res.text()\n\n    const result: APIGatewayProxyResult = {\n      body: body,\n      statusCode: res.status,\n      isBase64Encoded,\n      ...('multiValueHeaders' in event && event.multiValueHeaders\n        ? {\n            multiValueHeaders: {},\n          }\n        : {\n            headers: {},\n          }),\n    }\n\n    this.setCookies(event, res, result)\n    if (result.multiValueHeaders) {\n      res.headers.forEach((value, key) => {\n        result.multiValueHeaders[key] = [value]\n      })\n    } else {\n      res.headers.forEach((value, key) => {\n        result.headers[key] = value\n      })\n    }\n\n    return result\n  }\n\n  setCookies(_event: E, res: Response, result: APIGatewayProxyResult) {\n    if (res.headers.has('set-cookie')) {\n      const cookies = res.headers.getSetCookie\n        ? res.headers.getSetCookie()\n        : Array.from(res.headers.entries())\n            .filter(([k]) => k === 'set-cookie')\n            .map(([, v]) => v)\n\n      if (Array.isArray(cookies)) {\n        this.setCookiesToResult(result, cookies)\n        res.headers.delete('set-cookie')\n      }\n    }\n  }\n}\n\nexport class EventV2Processor extends EventProcessor<APIGatewayProxyEventV2> {\n  protected getPath(event: APIGatewayProxyEventV2): string {\n    return event.rawPath\n  }\n\n  protected getMethod(event: APIGatewayProxyEventV2): string {\n    return event.requestContext.http.method\n  }\n\n  protected getQueryString(event: APIGatewayProxyEventV2): string {\n    return event.rawQueryString\n  }\n\n  protected getCookies(event: APIGatewayProxyEventV2, headers: Headers): void {\n    if (Array.isArray(event.cookies)) {\n      headers.set('Cookie', event.cookies.join('; '))\n    }\n  }\n\n  protected setCookiesToResult(result: APIGatewayProxyResult, cookies: string[]): void {\n    result.cookies = cookies\n  }\n\n  protected getHeaders(event: APIGatewayProxyEventV2): Headers {\n    const headers = new Headers()\n    this.getCookies(event, headers)\n    if (event.headers) {\n      for (const [k, v] of Object.entries(event.headers)) {\n        if (v) {\n          headers.set(k, v)\n        }\n      }\n    }\n    return headers\n  }\n}\n\nconst v2Processor: EventV2Processor = new EventV2Processor()\n\nexport class EventV1Processor extends EventProcessor<APIGatewayProxyEvent> {\n  protected getPath(event: APIGatewayProxyEvent): string {\n    return event.path\n  }\n\n  protected getMethod(event: APIGatewayProxyEvent): string {\n    return event.httpMethod\n  }\n\n  protected getQueryString(event: APIGatewayProxyEvent): string {\n    // In the case of gateway Integration either queryStringParameters or multiValueQueryStringParameters can be present not both\n    // API Gateway passes decoded values, so we need to re-encode them to preserve the original URL\n    if (event.multiValueQueryStringParameters) {\n      return Object.entries(event.multiValueQueryStringParameters || {})\n        .filter(([, value]) => value)\n        .map(([key, values]) =>\n          values.map((value) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join('&')\n        )\n        .join('&')\n    } else {\n      return Object.entries(event.queryStringParameters || {})\n        .filter(([, value]) => value)\n        .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value || '')}`)\n        .join('&')\n    }\n  }\n\n  protected getCookies(_event: APIGatewayProxyEvent, _headers: Headers): void {\n    // nop\n  }\n\n  protected getHeaders(event: APIGatewayProxyEvent): Headers {\n    const headers = new Headers()\n    this.getCookies(event, headers)\n    if (event.headers) {\n      for (const [k, v] of Object.entries(event.headers)) {\n        if (v) {\n          headers.set(k, sanitizeHeaderValue(v))\n        }\n      }\n    }\n    if (event.multiValueHeaders) {\n      for (const [k, values] of Object.entries(event.multiValueHeaders)) {\n        if (values) {\n          // avoid duplicating already set headers\n          const foundK = headers.get(k)\n          values.forEach((v) => {\n            const sanitizedValue = sanitizeHeaderValue(v)\n            return (\n              (!foundK || !foundK.includes(sanitizedValue)) && headers.append(k, sanitizedValue)\n            )\n          })\n        }\n      }\n    }\n    return headers\n  }\n\n  protected setCookiesToResult(result: APIGatewayProxyResult, cookies: string[]): void {\n    result.multiValueHeaders = {\n      'set-cookie': cookies,\n    }\n  }\n}\n\nconst v1Processor: EventV1Processor = new EventV1Processor()\n\nexport class ALBProcessor extends EventProcessor<ALBProxyEvent> {\n  protected getHeaders(event: ALBProxyEvent): Headers {\n    const headers = new Headers()\n    // if multiValueHeaders is present the ALB will use it instead of the headers field\n    // https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers\n    if (event.multiValueHeaders) {\n      for (const [key, values] of Object.entries(event.multiValueHeaders)) {\n        if (values && Array.isArray(values)) {\n          // https://www.rfc-editor.org/rfc/rfc9110.html#name-common-rules-for-defining-f\n          const sanitizedValue = sanitizeHeaderValue(values.join('; '))\n          headers.set(key, sanitizedValue)\n        }\n      }\n    } else {\n      for (const [key, value] of Object.entries(event.headers ?? {})) {\n        if (value) {\n          headers.set(key, sanitizeHeaderValue(value))\n        }\n      }\n    }\n    return headers\n  }\n\n  protected getPath(event: ALBProxyEvent): string {\n    return event.path\n  }\n\n  protected getMethod(event: ALBProxyEvent): string {\n    return event.httpMethod\n  }\n\n  protected getQueryString(event: ALBProxyEvent): string {\n    // In the case of ALB Integration either queryStringParameters or multiValueQueryStringParameters can be present not both\n    /*\n      In other cases like when using the serverless framework, the event object does contain both queryStringParameters and multiValueQueryStringParameters:\n      Below is an example event object for this URL: /payment/b8c55e69?select=amount&select=currency\n      {\n        ...\n        queryStringParameters: { select: 'currency' },\n        multiValueQueryStringParameters: { select: [ 'amount', 'currency' ] },\n      }\n      The expected results is for select to be an array with two items. However the pre-fix code is only returning one item ('currency') in the array.\n      A simple fix would be to invert the if statement and check the multiValueQueryStringParameters first.\n    */\n    if (event.multiValueQueryStringParameters) {\n      return Object.entries(event.multiValueQueryStringParameters || {})\n        .filter(([, value]) => value)\n        .map(([key, value]) => `${key}=${value.join(`&${key}=`)}`)\n        .join('&')\n    } else {\n      return Object.entries(event.queryStringParameters || {})\n        .filter(([, value]) => value)\n        .map(([key, value]) => `${key}=${value}`)\n        .join('&')\n    }\n  }\n\n  protected getCookies(event: ALBProxyEvent, headers: Headers): void {\n    let cookie\n    if (event.multiValueHeaders) {\n      cookie = event.multiValueHeaders['cookie']?.join('; ')\n    } else {\n      cookie = event.headers ? event.headers['cookie'] : undefined\n    }\n    if (cookie) {\n      headers.append('Cookie', cookie)\n    }\n  }\n\n  protected setCookiesToResult(result: APIGatewayProxyResult, cookies: string[]): void {\n    // when multi value headers is enabled\n    if (result.multiValueHeaders) {\n      result.multiValueHeaders['set-cookie'] = cookies\n    } else {\n      // otherwise serialize the set-cookie\n      result.headers['set-cookie'] = cookies.join(', ')\n    }\n  }\n}\n\nconst albProcessor: ALBProcessor = new ALBProcessor()\n\nexport class LatticeV2Processor extends EventProcessor<LatticeProxyEventV2> {\n  protected getPath(event: LatticeProxyEventV2): string {\n    return event.path\n  }\n\n  protected getMethod(event: LatticeProxyEventV2): string {\n    return event.method\n  }\n\n  protected getQueryString(): string {\n    return ''\n  }\n\n  protected getHeaders(event: LatticeProxyEventV2): Headers {\n    const headers = new Headers()\n\n    if (event.headers) {\n      for (const [k, values] of Object.entries(event.headers)) {\n        if (values) {\n          // avoid duplicating already set headers\n          const foundK = headers.get(k)\n          values.forEach((v) => {\n            const sanitizedValue = sanitizeHeaderValue(v)\n            return (\n              (!foundK || !foundK.includes(sanitizedValue)) && headers.append(k, sanitizedValue)\n            )\n          })\n        }\n      }\n    }\n\n    return headers\n  }\n\n  protected getCookies(): void {\n    // nop\n  }\n\n  protected setCookiesToResult(result: APIGatewayProxyResult, cookies: string[]): void {\n    result.headers = {\n      ...result.headers,\n      'set-cookie': cookies.join(', '),\n    }\n  }\n}\n\nconst latticeV2Processor: LatticeV2Processor = new LatticeV2Processor()\n\nexport const getProcessor = (event: LambdaEvent): EventProcessor<LambdaEvent> => {\n  if (isProxyEventALB(event)) {\n    return albProcessor\n  }\n  if (isProxyEventV2(event)) {\n    return v2Processor\n  }\n  if (isLatticeEventV2(event)) {\n    return latticeV2Processor\n  }\n\n  return v1Processor\n}\n\nconst isProxyEventALB = (event: LambdaEvent): event is ALBProxyEvent => {\n  if (event.requestContext) {\n    return Object.hasOwn(event.requestContext, 'elb')\n  }\n  return false\n}\n\nconst isProxyEventV2 = (event: LambdaEvent): event is APIGatewayProxyEventV2 => {\n  return Object.hasOwn(event, 'rawPath')\n}\n\nconst isLatticeEventV2 = (event: LambdaEvent): event is LatticeProxyEventV2 => {\n  if (event.requestContext) {\n    return Object.hasOwn(event.requestContext, 'serviceArn')\n  }\n  return false\n}\n\n/**\n * Check if the given content type is binary.\n * This is a default function and may be overwritten by the user via `isContentTypeBinary` option in handler().\n * @param contentType The content type to check.\n * @returns True if the content type is binary, false otherwise.\n */\nexport const defaultIsContentTypeBinary = (contentType: string): boolean => {\n  return !/^text\\/(?:plain|html|css|javascript|csv)|(?:\\/|\\+)(?:json|xml)\\s*(?:;|$)/.test(\n    contentType\n  )\n}\n\nexport const isContentEncodingBinary = (contentEncoding: string | null) => {\n  if (contentEncoding === null) {\n    return false\n  }\n  return /^(gzip|deflate|compress|br)/.test(contentEncoding)\n}\n"
  },
  {
    "path": "src/adapter/aws-lambda/index.ts",
    "content": "/**\n * @module\n * AWS Lambda Adapter for Hono.\n */\n\nexport { handle, streamHandle, defaultIsContentTypeBinary } from './handler'\nexport { getConnInfo } from './conninfo'\nexport type { APIGatewayProxyResult, LambdaEvent } from './handler'\nexport type {\n  ApiGatewayRequestContext,\n  ApiGatewayRequestContextV2,\n  ALBRequestContext,\n  LambdaContext,\n} from './types'\n"
  },
  {
    "path": "src/adapter/aws-lambda/types.ts",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\n\nexport interface CognitoIdentity {\n  cognitoIdentityId: string\n  cognitoIdentityPoolId: string\n}\n\nexport interface ClientContext {\n  client: ClientContextClient\n\n  Custom?: any\n  env: ClientContextEnv\n}\n\nexport interface ClientContextClient {\n  installationId: string\n  appTitle: string\n  appVersionName: string\n  appVersionCode: string\n  appPackageName: string\n}\n\nexport interface ClientContextEnv {\n  platformVersion: string\n  platform: string\n  make: string\n  model: string\n  locale: string\n}\n\n/**\n * {@link Handler} context parameter.\n * See {@link https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html AWS documentation}.\n */\nexport interface LambdaContext {\n  callbackWaitsForEmptyEventLoop: boolean\n  functionName: string\n  functionVersion: string\n  invokedFunctionArn: string\n  memoryLimitInMB: string\n  awsRequestId: string\n  logGroupName: string\n  logStreamName: string\n  identity?: CognitoIdentity | undefined\n  clientContext?: ClientContext | undefined\n\n  getRemainingTimeInMillis(): number\n}\n\ntype Callback<TResult = any> = (error?: Error | string | null, result?: TResult) => void\n\nexport type Handler<TEvent = any, TResult = any> = (\n  event: TEvent,\n  context: LambdaContext,\n  callback: Callback<TResult>\n) => void | Promise<TResult>\n\ninterface ClientCert {\n  clientCertPem: string\n  subjectDN: string\n  issuerDN: string\n  serialNumber: string\n  validity: {\n    notBefore: string\n    notAfter: string\n  }\n}\n\ninterface Identity {\n  accessKey?: string\n  accountId?: string\n  caller?: string\n  cognitoAuthenticationProvider?: string\n  cognitoAuthenticationType?: string\n  cognitoIdentityId?: string\n  cognitoIdentityPoolId?: string\n  principalOrgId?: string\n  sourceIp: string\n  user?: string\n  userAgent: string\n  userArn?: string\n  clientCert?: ClientCert\n}\n\nexport interface ApiGatewayRequestContext {\n  accountId: string\n  apiId: string\n  authorizer: {\n    claims?: unknown\n    scopes?: unknown\n  }\n  domainName: string\n  domainPrefix: string\n  extendedRequestId: string\n  httpMethod: string\n  identity: Identity\n  path: string\n  protocol: string\n  requestId: string\n  requestTime: string\n  requestTimeEpoch: number\n  resourceId?: string\n  resourcePath: string\n  stage: string\n}\n\ninterface Authorizer {\n  iam?: {\n    accessKey: string\n    accountId: string\n    callerId: string\n    cognitoIdentity: null\n    principalOrgId: null\n    userArn: string\n    userId: string\n  }\n}\n\nexport interface ApiGatewayRequestContextV2 {\n  accountId: string\n  apiId: string\n  authentication: null\n  authorizer: Authorizer\n  domainName: string\n  domainPrefix: string\n  http: {\n    method: string\n    path: string\n    protocol: string\n    sourceIp: string\n    userAgent: string\n  }\n  requestId: string\n  routeKey: string\n  stage: string\n  time: string\n  timeEpoch: number\n}\n\nexport interface ALBRequestContext {\n  elb: {\n    targetGroupArn: string\n  }\n}\n\nexport interface LatticeRequestContextV2 {\n  serviceNetworkArn: string\n  serviceArn: string\n  targetGroupArn: string\n  region: string\n  timeEpoch: string\n  identity: {\n    sourceVpcArn?: string\n    type?: string\n    principal?: string\n    principalOrgID?: string\n    sessionName?: string\n    x509IssuerOu?: string\n    x509SanDns?: string\n    x509SanNameCn?: string\n    x509SanUri?: string\n    x509SubjectCn?: string\n  }\n}\n"
  },
  {
    "path": "src/adapter/bun/conninfo.test.ts",
    "content": "import { Context } from '../../context'\nimport type { AddressType } from '../../helper/conninfo'\nimport { getConnInfo } from './conninfo'\n\nconst createRandomBunServer = ({\n  address = Math.random().toString(),\n  port = Math.floor(Math.random() * (65535 + 1)),\n  family = 'IPv6',\n}: {\n  address?: string\n  port?: number\n  family?: AddressType | string\n} = {}) => {\n  return {\n    address,\n    port,\n    server: {\n      requestIP() {\n        return {\n          address,\n          family,\n          port,\n        }\n      },\n    },\n  }\n}\ndescribe('getConnInfo', () => {\n  it('Should info is valid', () => {\n    const { port, server, address } = createRandomBunServer()\n    const c = new Context(new Request('http://localhost/'), { env: server })\n    const info = getConnInfo(c)\n\n    expect(info.remote.port).toBe(port)\n    expect(info.remote.address).toBe(address)\n    expect(info.remote.addressType).toBe('IPv6')\n    expect(info.remote.transport).toBeUndefined()\n  })\n  it('Should getConnInfo works when env is { server: server }', () => {\n    const { port, server, address } = createRandomBunServer()\n    const c = new Context(new Request('http://localhost/'), { env: { server } })\n\n    const info = getConnInfo(c)\n\n    expect(info.remote.port).toBe(port)\n    expect(info.remote.address).toBe(address)\n    expect(info.remote.addressType).toBe('IPv6')\n    expect(info.remote.transport).toBeUndefined()\n  })\n  it('should return undefined when addressType is invalid string', () => {\n    const { server } = createRandomBunServer({ family: 'invalid' })\n    const c = new Context(new Request('http://localhost/'), { env: { server } })\n\n    const info = getConnInfo(c)\n\n    expect(info.remote.addressType).toBeUndefined()\n  })\n  it('Should throw error when user did not give server', () => {\n    const c = new Context(new Request('http://localhost/'), { env: {} })\n\n    expect(() => getConnInfo(c)).toThrowError(TypeError)\n  })\n  it('Should throw error when requestIP is not function', () => {\n    const c = new Context(new Request('http://localhost/'), {\n      env: {\n        requestIP: 0,\n      },\n    })\n    expect(() => getConnInfo(c)).toThrowError(TypeError)\n  })\n  it('Should return empty remote when requestIP returns null', () => {\n    const c = new Context(new Request('http://localhost/'), {\n      env: {\n        requestIP() {\n          return null\n        },\n      },\n    })\n\n    const info = getConnInfo(c)\n\n    expect(info.remote).toEqual({})\n  })\n})\n"
  },
  {
    "path": "src/adapter/bun/conninfo.ts",
    "content": "import type { Context } from '../..'\nimport type { GetConnInfo } from '../../helper/conninfo'\nimport { getBunServer } from './server'\n\n/**\n * Get ConnInfo with Bun\n * @param c Context\n * @returns ConnInfo\n */\nexport const getConnInfo: GetConnInfo = (c: Context) => {\n  const server = getBunServer<{\n    requestIP?: (req: Request) => {\n      address: string\n      family: string\n      port: number\n    } | null\n  }>(c)\n\n  if (!server) {\n    throw new TypeError('env has to include the 2nd argument of fetch.')\n  }\n  if (typeof server.requestIP !== 'function') {\n    throw new TypeError('server.requestIP is not a function.')\n  }\n\n  // https://bun.sh/docs/runtime/http/server#server-requestip-request\n  // Returns null for closed requests or Unix domain sockets.\n  const info = server.requestIP(c.req.raw)\n\n  if (!info) {\n    return {\n      remote: {},\n    }\n  }\n\n  return {\n    remote: {\n      address: info.address,\n      addressType: info.family === 'IPv6' || info.family === 'IPv4' ? info.family : undefined,\n      port: info.port,\n    },\n  }\n}\n"
  },
  {
    "path": "src/adapter/bun/index.ts",
    "content": "/**\n * @module\n * Bun Adapter for Hono.\n */\n\nexport { serveStatic } from './serve-static'\nexport { bunFileSystemModule, toSSG } from './ssg'\nexport { createBunWebSocket, upgradeWebSocket, websocket } from './websocket'\nexport type { BunWebSocketData, BunWebSocketHandler } from './websocket'\nexport { getConnInfo } from './conninfo'\nexport { getBunServer } from './server'\n"
  },
  {
    "path": "src/adapter/bun/serve-static.ts",
    "content": "/* eslint-disable @typescript-eslint/ban-ts-comment */\nimport { stat } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { serveStatic as baseServeStatic } from '../../middleware/serve-static'\nimport type { ServeStaticOptions } from '../../middleware/serve-static'\nimport type { Env, MiddlewareHandler } from '../../types'\n\nexport const serveStatic = <E extends Env = Env>(\n  options: ServeStaticOptions<E>\n): MiddlewareHandler => {\n  return async function serveStatic(c, next) {\n    const getContent = async (path: string) => {\n      // @ts-ignore\n      const file = Bun.file(path)\n      return (await file.exists()) ? file : null\n    }\n    const isDir = async (path: string) => {\n      let isDir\n      try {\n        const stats = await stat(path)\n        isDir = stats.isDirectory()\n      } catch {}\n      return isDir\n    }\n    return baseServeStatic({\n      ...options,\n      getContent,\n      join,\n      isDir,\n    })(c, next)\n  }\n}\n"
  },
  {
    "path": "src/adapter/bun/server.test.ts",
    "content": "import { Context } from '../../context'\nimport { getBunServer } from './server'\n\ndescribe('getBunServer', () => {\n  it('Should success to pick Server', () => {\n    const server = {}\n\n    expect(getBunServer(new Context(new Request('http://localhost/'), { env: server }))).toBe(\n      server\n    )\n    expect(getBunServer(new Context(new Request('http://localhost/'), { env: { server } }))).toBe(\n      server\n    )\n  })\n})\n"
  },
  {
    "path": "src/adapter/bun/server.ts",
    "content": "/**\n * Getting Bun Server Object for Bun adapters\n * @module\n */\nimport type { Context } from '../../context'\n\n/**\n * Get Bun Server Object from Context\n * @template T - The type of Bun Server\n * @param c Context\n * @returns Bun Server\n */\nexport const getBunServer = <T>(c: Context): T | undefined =>\n  ('server' in c.env ? c.env.server : c.env) as T | undefined\n"
  },
  {
    "path": "src/adapter/bun/ssg.ts",
    "content": "/* eslint-disable @typescript-eslint/ban-ts-comment */\nimport { toSSG as baseToSSG } from '../../helper/ssg'\nimport type { FileSystemModule, ToSSGAdaptorInterface } from '../../helper/ssg'\n\n// @ts-ignore\nconst { write } = Bun\n\n/**\n * @experimental\n * `bunFileSystemModule` is an experimental feature.\n * The API might be changed.\n */\nexport const bunFileSystemModule: FileSystemModule = {\n  writeFile: async (path, data) => {\n    await write(path, data)\n  },\n  mkdir: async () => {},\n}\n\n/**\n * @experimental\n * `toSSG` is an experimental feature.\n * The API might be changed.\n */\nexport const toSSG: ToSSGAdaptorInterface = async (app, options) => {\n  return baseToSSG(app, bunFileSystemModule, options)\n}\n"
  },
  {
    "path": "src/adapter/bun/websocket.test.ts",
    "content": "import { Context } from '../../context'\nimport type { BunWebSocketData, BunServerWebSocket } from './websocket'\nimport { createWSContext, websocket, upgradeWebSocket, createBunWebSocket } from './websocket'\n\ndescribe('createWSContext()', () => {\n  it('Should send() and close() works', () => {\n    const send = vi.fn()\n    const close = vi.fn()\n    const ws = createWSContext({\n      send(data) {\n        send(data)\n      },\n      close(code, reason) {\n        close(code, reason)\n      },\n      data: {},\n    } as BunServerWebSocket<BunWebSocketData>)\n    ws.send('message')\n    expect(send).toBeCalled()\n    ws.close()\n    expect(close).toBeCalled()\n  })\n})\ndescribe('upgradeWebSocket()', () => {\n  beforeAll(() => {\n    // @ts-expect-error patch global\n    globalThis.CloseEvent = Event\n  })\n  afterAll(() => {\n    // @ts-expect-error patch global\n    delete globalThis.CloseEvent\n  })\n  it('Should throw error when server is null', async () => {\n    const run = async () =>\n      await upgradeWebSocket(() => ({}))(\n        new Context(new Request('http://localhost'), {\n          env: {\n            server: null,\n          },\n        }),\n        () => Promise.resolve()\n      )\n\n    await expect(run).rejects.toThrowError(/env has/)\n  })\n  it('Should response null when upgraded', async () => {\n    const upgraded = await upgradeWebSocket(() => ({}))(\n      new Context(new Request('http://localhost'), {\n        env: {\n          upgrade: () => true,\n        },\n      }),\n      () => Promise.resolve()\n    )\n    expect(upgraded).toBeTruthy()\n  })\n  it('Should response undefined when upgrade failed', async () => {\n    const upgraded = await upgradeWebSocket(() => ({}))(\n      new Context(new Request('http://localhost'), {\n        env: {\n          upgrade: () => undefined,\n        },\n      }),\n      () => Promise.resolve()\n    )\n    expect(upgraded).toBeFalsy()\n  })\n  it('Should events are called', async () => {\n    const open = vi.fn()\n    const message = vi.fn()\n    const close = vi.fn()\n\n    const ws = {\n      data: {\n        events: {\n          // eslint-disable-next-line @typescript-eslint/no-unused-vars\n          onOpen(evt, ws) {\n            open()\n          },\n          // eslint-disable-next-line @typescript-eslint/no-unused-vars\n          onMessage(evt, ws) {\n            message()\n            if (evt.data instanceof ArrayBuffer) {\n              receivedArrayBuffer = evt.data\n            }\n          },\n          // eslint-disable-next-line @typescript-eslint/no-unused-vars\n          onClose(evt, ws) {\n            close()\n          },\n        },\n      },\n    } as BunServerWebSocket<BunWebSocketData>\n\n    let receivedArrayBuffer: ArrayBuffer | undefined = undefined\n    await upgradeWebSocket(() => ({}))(\n      new Context(new Request('http://localhost'), {\n        env: {\n          upgrade() {\n            return true\n          },\n        },\n      }),\n      () => Promise.resolve()\n    )\n\n    websocket.open(ws)\n    expect(open).toBeCalled()\n\n    websocket.message(ws, 'message')\n    expect(message).toBeCalled()\n\n    websocket.message(ws, new Uint8Array(16))\n    expect(receivedArrayBuffer).toBeInstanceOf(ArrayBuffer)\n    expect(receivedArrayBuffer!.byteLength).toBe(16)\n    websocket.close(ws)\n    expect(close).toBeCalled()\n  })\n})\n\ndescribe('createBunWebSocket()', () => {\n  it('Should return upgradeWebSocket and websocket', () => {\n    const result = createBunWebSocket()\n    expect(result.upgradeWebSocket).toBe(upgradeWebSocket)\n    expect(result.websocket).toBe(websocket)\n  })\n})\n"
  },
  {
    "path": "src/adapter/bun/websocket.ts",
    "content": "import type { UpgradeWebSocket, WSEvents, WSMessageReceive } from '../../helper/websocket'\nimport { createWSMessageEvent, defineWebSocketHelper, WSContext } from '../../helper/websocket'\nimport { getBunServer } from './server'\n\n/**\n * @internal\n */\nexport interface BunServerWebSocket<T> {\n  send(data: string | ArrayBuffer | Uint8Array, compress?: boolean): void\n  close(code?: number, reason?: string): void\n  data: T\n  readyState: 0 | 1 | 2 | 3\n}\n\nexport interface BunWebSocketHandler<T> {\n  open(ws: BunServerWebSocket<T>): void\n  close(ws: BunServerWebSocket<T>, code?: number, reason?: string): void\n  message(ws: BunServerWebSocket<T>, message: string | { buffer: ArrayBufferLike }): void\n}\ninterface CreateWebSocket<T> {\n  upgradeWebSocket: UpgradeWebSocket<T>\n  websocket: BunWebSocketHandler<BunWebSocketData>\n}\nexport interface BunWebSocketData {\n  events: WSEvents\n  url: URL\n  protocol: string\n}\n\n/**\n * @internal\n */\nexport const createWSContext = (ws: BunServerWebSocket<BunWebSocketData>): WSContext => {\n  return new WSContext({\n    send: (source, options) => {\n      ws.send(source, options?.compress)\n    },\n    raw: ws,\n    readyState: ws.readyState,\n    url: ws.data.url,\n    protocol: ws.data.protocol,\n    close(code, reason) {\n      ws.close(code, reason)\n    },\n  })\n}\n\nexport const upgradeWebSocket: UpgradeWebSocket<any> = defineWebSocketHelper((c, events) => {\n  const server = getBunServer<{\n    upgrade<T>(\n      req: Request,\n      options?: {\n        data: T\n      }\n    ): boolean\n  }>(c)\n\n  if (!server) {\n    throw new TypeError('env has to include the 2nd argument of fetch.')\n  }\n  const upgradeResult = server.upgrade<BunWebSocketData>(c.req.raw, {\n    data: {\n      events,\n      url: new URL(c.req.url),\n      protocol: c.req.url,\n    },\n  })\n  if (upgradeResult) {\n    return new Response(null)\n  }\n  return // failed\n})\n\nexport const websocket: BunWebSocketHandler<BunWebSocketData> = {\n  open(ws) {\n    const websocketListeners = ws.data.events\n    if (websocketListeners.onOpen) {\n      websocketListeners.onOpen(new Event('open'), createWSContext(ws))\n    }\n  },\n  close(ws, code, reason) {\n    const websocketListeners = ws.data.events\n    if (websocketListeners.onClose) {\n      websocketListeners.onClose(\n        new CloseEvent('close', {\n          code,\n          reason,\n        }),\n        createWSContext(ws)\n      )\n    }\n  },\n  message(ws, message) {\n    const websocketListeners = ws.data.events\n    if (websocketListeners.onMessage) {\n      const normalizedReceiveData: WSMessageReceive =\n        typeof message === 'string' ? message : message.buffer\n\n      websocketListeners.onMessage(createWSMessageEvent(normalizedReceiveData), createWSContext(ws))\n    }\n  },\n}\n\n/**\n * @deprecated Import `upgradeWebSocket` and `websocket` directly from `hono/bun` instead.\n * @returns A function to create a Bun WebSocket handler.\n */\nexport const createBunWebSocket = <T>(): CreateWebSocket<T> => ({\n  upgradeWebSocket,\n  websocket,\n})\n"
  },
  {
    "path": "src/adapter/cloudflare-pages/conninfo.test.ts",
    "content": "import { Context } from '../../context'\nimport { getConnInfo } from './conninfo'\n\ndescribe('getConnInfo', () => {\n  it('Should return the client IP from cf-connecting-ip header', () => {\n    const address = Math.random().toString()\n    const req = new Request('http://localhost/', {\n      headers: {\n        'cf-connecting-ip': address,\n      },\n    })\n    const c = new Context(req)\n\n    const info = getConnInfo(c)\n\n    expect(info.remote.address).toBe(address)\n    expect(info.remote.addressType).toBeUndefined()\n  })\n\n  it('Should return undefined when cf-connecting-ip header is not present', () => {\n    const c = new Context(new Request('http://localhost/'))\n\n    const info = getConnInfo(c)\n\n    expect(info.remote.address).toBeUndefined()\n  })\n})\n"
  },
  {
    "path": "src/adapter/cloudflare-pages/conninfo.ts",
    "content": "import type { GetConnInfo } from '../../helper/conninfo'\n\n/**\n * Get connection information from Cloudflare Pages\n * @param c - Context\n * @returns Connection information including remote address\n * @example\n * ```ts\n * import { Hono } from 'hono'\n * import { handle, getConnInfo } from 'hono/cloudflare-pages'\n *\n * const app = new Hono()\n *\n * app.get('/', (c) => {\n *   const info = getConnInfo(c)\n *   return c.text(`Your IP: ${info.remote.address}`)\n * })\n *\n * export const onRequest = handle(app)\n * ```\n */\nexport const getConnInfo: GetConnInfo = (c) => ({\n  remote: {\n    address: c.req.header('cf-connecting-ip'),\n  },\n})\n"
  },
  {
    "path": "src/adapter/cloudflare-pages/handler.test.ts",
    "content": "import { getCookie } from '../../helper/cookie'\nimport { Hono } from '../../hono'\nimport { HTTPException } from '../../http-exception'\nimport type { EventContext } from './handler'\nimport { handle, handleMiddleware, serveStatic } from './handler'\n\ntype Env = {\n  Bindings: {\n    TOKEN: string\n  }\n}\n\nfunction createEventContext(\n  context: Partial<EventContext<Env['Bindings']>>\n): EventContext<Env['Bindings']> {\n  return {\n    data: {},\n    env: {\n      ...context.env,\n      ASSETS: { fetch: vi.fn(), ...context.env?.ASSETS },\n      TOKEN: context.env?.TOKEN ?? 'HONOISHOT',\n    },\n    functionPath: '_worker.js',\n    next: vi.fn(),\n    params: {},\n    passThroughOnException: vi.fn(),\n    props: {},\n    request: new Request('http://localhost/api/foo'),\n    waitUntil: vi.fn(),\n    ...context,\n  }\n}\n\ndescribe('Adapter for Cloudflare Pages', () => {\n  it('Should return 200 response', async () => {\n    const request = new Request('http://localhost/api/foo')\n    const env = {\n      ASSETS: { fetch },\n      TOKEN: 'HONOISHOT',\n    }\n    const waitUntil = vi.fn()\n    const passThroughOnException = vi.fn()\n    const props = {}\n    const eventContext = createEventContext({\n      request,\n      env,\n      waitUntil,\n      passThroughOnException,\n    })\n    const app = new Hono<Env>()\n    const appFetchSpy = vi.spyOn(app, 'fetch')\n    app.get('/api/foo', (c) => {\n      return c.json({ TOKEN: c.env.TOKEN, requestURL: c.req.url })\n    })\n    const handler = handle(app)\n    const res = await handler(eventContext)\n    expect(appFetchSpy).toHaveBeenCalledWith(\n      request,\n      { ...env, eventContext },\n      { waitUntil, passThroughOnException, props }\n    )\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({\n      TOKEN: 'HONOISHOT',\n      requestURL: 'http://localhost/api/foo',\n    })\n  })\n\n  it('Should not use `basePath()` if path argument is not passed', async () => {\n    const request = new Request('http://localhost/api/error')\n    const eventContext = createEventContext({ request })\n    const app = new Hono().basePath('/api')\n\n    app.onError((e) => {\n      throw e\n    })\n    app.get('/error', () => {\n      throw new Error('Custom Error')\n    })\n\n    const handler = handle(app)\n    // It does throw the error if app is NOT \"subApp\"\n    expect(() => handler(eventContext)).toThrowError('Custom Error')\n  })\n})\n\ndescribe('Middleware adapter for Cloudflare Pages', () => {\n  it('Should return the middleware response', async () => {\n    const request = new Request('http://localhost/api/foo', {\n      headers: {\n        Cookie: 'my_cookie=1234',\n      },\n    })\n    const next = vi.fn().mockResolvedValue(Response.json('From Cloudflare Pages'))\n    const eventContext = createEventContext({ request, next })\n    const handler = handleMiddleware(async (c, next) => {\n      const cookie = getCookie(c, 'my_cookie')\n\n      await next()\n\n      return c.json({ cookie, response: await c.res.json() })\n    })\n\n    const res = await handler(eventContext)\n\n    expect(next).toHaveBeenCalled()\n\n    expect(await res.json()).toEqual({\n      cookie: '1234',\n      response: 'From Cloudflare Pages',\n    })\n  })\n\n  it('Should return the middleware response when exceptions are handled', async () => {\n    const request = new Request('http://localhost/api/foo')\n    const handler = handleMiddleware(async (c, next) => {\n      await next()\n\n      return c.json({ error: c.error?.message })\n    })\n\n    const next = vi.fn().mockRejectedValue(new Error('Error from next()'))\n    const eventContext = createEventContext({ request, next })\n    const res = await handler(eventContext)\n\n    expect(next).toHaveBeenCalled()\n\n    expect(await res.json()).toEqual({\n      error: 'Error from next()',\n    })\n  })\n\n  it('Should return the middleware response if next() is not called', async () => {\n    const request = new Request('http://localhost/api/foo')\n    const handler = handleMiddleware(async (c) => {\n      return c.json({ response: 'Skip Cloudflare Pages' })\n    })\n\n    const next = vi.fn()\n    const eventContext = createEventContext({ request, next })\n    const res = await handler(eventContext)\n\n    expect(next).not.toHaveBeenCalled()\n\n    expect(await res.json()).toEqual({\n      response: 'Skip Cloudflare Pages',\n    })\n  })\n\n  it('Should return the Pages response if the middleware does not return a response', async () => {\n    const request = new Request('http://localhost/api/foo')\n    const handler = handleMiddleware((_c, next) => next())\n\n    const next = vi.fn().mockResolvedValue(Response.json('From Cloudflare Pages'))\n    const eventContext = createEventContext({ request, next })\n    const res = await handler(eventContext)\n\n    expect(next).toHaveBeenCalled()\n\n    expect(await res.json()).toEqual('From Cloudflare Pages')\n  })\n\n  it('Should handle a HTTPException by returning error.getResponse()', async () => {\n    const request = new Request('http://localhost/api/foo')\n    const handler = handleMiddleware(() => {\n      const res = new Response('Unauthorized', { status: 401 })\n      throw new HTTPException(401, { res })\n    })\n\n    const next = vi.fn()\n    const eventContext = createEventContext({ request, next })\n    const res = await handler(eventContext)\n\n    expect(next).not.toHaveBeenCalled()\n\n    expect(res.status).toBe(401)\n    expect(await res.text()).toBe('Unauthorized')\n  })\n\n  it('Should handle an HTTPException thrown by next()', async () => {\n    const request = new Request('http://localhost/api/foo')\n    const handler = handleMiddleware((_c, next) => next())\n\n    const next = vi\n      .fn()\n      .mockRejectedValue(new HTTPException(401, { res: Response.json('Unauthorized') }))\n    const eventContext = createEventContext({ request, next })\n    const res = await handler(eventContext)\n\n    expect(next).toHaveBeenCalled()\n\n    expect(await res.json()).toEqual('Unauthorized')\n  })\n\n  it('Should handle an Error thrown by next()', async () => {\n    const request = new Request('http://localhost/api/foo')\n    const handler = handleMiddleware((_c, next) => next())\n\n    const next = vi.fn().mockRejectedValue(new Error('Error from next()'))\n    const eventContext = createEventContext({ request, next })\n    await expect(handler(eventContext)).rejects.toThrowError('Error from next()')\n    expect(next).toHaveBeenCalled()\n  })\n\n  it('Should handle a non-Error thrown by next()', async () => {\n    const request = new Request('http://localhost/api/foo')\n    const handler = handleMiddleware((_c, next) => next())\n\n    const next = vi.fn().mockRejectedValue('Error from next()')\n    const eventContext = createEventContext({ request, next })\n    await expect(handler(eventContext)).rejects.toThrowError('Error from next()')\n    expect(next).toHaveBeenCalled()\n  })\n\n  it('Should rethrow an Error', async () => {\n    const request = new Request('http://localhost/api/foo')\n    const handler = handleMiddleware(() => {\n      throw new Error('Something went wrong')\n    })\n\n    const next = vi.fn()\n    const eventContext = createEventContext({ request, next })\n    await expect(handler(eventContext)).rejects.toThrowError('Something went wrong')\n    expect(next).not.toHaveBeenCalled()\n  })\n\n  it('Should rethrow non-Error exceptions', async () => {\n    const request = new Request('http://localhost/api/foo')\n    const handler = handleMiddleware(() => Promise.reject('Something went wrong'))\n    const next = vi.fn()\n    const eventContext = createEventContext({ request, next })\n    await expect(handler(eventContext)).rejects.toThrowError('Something went wrong')\n    expect(next).not.toHaveBeenCalled()\n  })\n\n  it('Should set the data in eventContext.data', async () => {\n    const next = vi.fn()\n    const eventContext = createEventContext({ next })\n    const handler = handleMiddleware(async (c, next) => {\n      c.env.eventContext.data.user = 'Joe'\n      await next()\n    })\n    expect(eventContext.data.user).toBeUndefined()\n    await handler(eventContext)\n    expect(eventContext.data.user).toBe('Joe')\n  })\n})\n\ndescribe('serveStatic()', () => {\n  it('Should pass the raw request to ASSETS.fetch', async () => {\n    const assetsFetch = vi.fn().mockResolvedValue(new Response('foo.png'))\n    const request = new Request('http://localhost/foo.png')\n    const env = {\n      ASSETS: { fetch: assetsFetch },\n      TOKEN: 'HONOISHOT',\n    }\n\n    const eventContext = createEventContext({ request, env })\n    const app = new Hono<Env>()\n    app.use(serveStatic())\n    const handler = handle(app)\n    const res = await handler(eventContext)\n\n    expect(assetsFetch).toHaveBeenCalledWith(request)\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('foo.png')\n  })\n\n  it('Should respond with 404 if ASSETS.fetch returns a 404 response', async () => {\n    const assetsFetch = vi.fn().mockResolvedValue(new Response(null, { status: 404 }))\n    const request = new Request('http://localhost/foo.png')\n    const env = {\n      ASSETS: { fetch: assetsFetch },\n      TOKEN: 'HONOISHOT',\n    }\n\n    const eventContext = createEventContext({ request, env })\n    const app = new Hono<Env>()\n    app.use(serveStatic())\n    const handler = handle(app)\n    const res = await handler(eventContext)\n\n    expect(assetsFetch).toHaveBeenCalledWith(request)\n    expect(res.status).toBe(404)\n  })\n})\n"
  },
  {
    "path": "src/adapter/cloudflare-pages/handler.ts",
    "content": "import { Context } from '../../context'\nimport type { Hono } from '../../hono'\nimport { HTTPException } from '../../http-exception'\nimport type { BlankSchema, Env, Input, MiddlewareHandler, Schema } from '../../types'\n\n// Ref: https://github.com/cloudflare/workerd/blob/main/types/defines/pages.d.ts\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype Params<P extends string = any> = Record<P, string | string[]>\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type EventContext<Env = {}, P extends string = any, Data = Record<string, unknown>> = {\n  request: Request\n  functionPath: string\n  waitUntil: (promise: Promise<unknown>) => void\n  passThroughOnException: () => void\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  props: any\n  next: (input?: Request | string, init?: RequestInit) => Promise<Response>\n  env: Env & { ASSETS: { fetch: typeof fetch } }\n  params: Params<P>\n  data: Data\n}\n\ndeclare type PagesFunction<\n  Env = unknown,\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  Params extends string = any,\n  Data extends Record<string, unknown> = Record<string, unknown>,\n> = (context: EventContext<Env, Params, Data>) => Response | Promise<Response>\n\nexport const handle =\n  <E extends Env = Env, S extends Schema = BlankSchema, BasePath extends string = '/'>(\n    app: Hono<E, S, BasePath>\n  ): PagesFunction<E['Bindings']> =>\n  (eventContext) => {\n    return app.fetch(\n      eventContext.request,\n      { ...eventContext.env, eventContext },\n      {\n        waitUntil: eventContext.waitUntil,\n        passThroughOnException: eventContext.passThroughOnException,\n        props: {},\n      }\n    )\n  }\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function handleMiddleware<E extends Env = {}, P extends string = any, I extends Input = {}>(\n  middleware: MiddlewareHandler<\n    E & {\n      Bindings: {\n        eventContext: EventContext\n      }\n    },\n    P,\n    I\n  >\n): PagesFunction<E['Bindings']> {\n  return async (executionCtx) => {\n    const context = new Context(executionCtx.request, {\n      env: { ...executionCtx.env, eventContext: executionCtx },\n      executionCtx,\n    })\n\n    let response: Response | void = undefined\n\n    try {\n      response = await middleware(context, async () => {\n        try {\n          context.res = await executionCtx.next()\n        } catch (error) {\n          if (error instanceof Error) {\n            context.error = error\n          } else {\n            throw error\n          }\n        }\n      })\n    } catch (error) {\n      if (error instanceof Error) {\n        context.error = error\n      } else {\n        throw error\n      }\n    }\n\n    if (response) {\n      return response\n    }\n\n    if (context.error instanceof HTTPException) {\n      return context.error.getResponse()\n    }\n\n    if (context.error) {\n      throw context.error\n    }\n\n    return context.res\n  }\n}\n\ndeclare abstract class FetcherLike {\n  fetch(input: RequestInfo, init?: RequestInit): Promise<Response>\n}\n\n/**\n *\n * @description `serveStatic()` is for advanced mode:\n * https://developers.cloudflare.com/pages/platform/functions/advanced-mode/#set-up-a-function\n *\n */\nexport const serveStatic = (): MiddlewareHandler => {\n  return async (c) => {\n    const env = c.env as { ASSETS: FetcherLike }\n    const res = await env.ASSETS.fetch(c.req.raw)\n    if (res.status === 404) {\n      return c.notFound()\n    }\n    return res\n  }\n}\n"
  },
  {
    "path": "src/adapter/cloudflare-pages/index.ts",
    "content": "/**\n * @module\n * Cloudflare Pages Adapter for Hono.\n */\n\nexport { handle, handleMiddleware, serveStatic } from './handler'\nexport { getConnInfo } from './conninfo'\nexport type { EventContext } from './handler'\n"
  },
  {
    "path": "src/adapter/cloudflare-workers/conninfo.test.ts",
    "content": "import { Context } from '../../context'\nimport { getConnInfo } from './conninfo'\n\ndescribe('getConnInfo', () => {\n  it('Should getConnInfo works', () => {\n    const address = Math.random().toString()\n    const req = new Request('http://localhost/', {\n      headers: {\n        'cf-connecting-ip': address,\n      },\n    })\n    const c = new Context(req)\n\n    const info = getConnInfo(c)\n\n    expect(info.remote.address).toBe(address)\n    expect(info.remote.addressType).toBeUndefined()\n  })\n})\n"
  },
  {
    "path": "src/adapter/cloudflare-workers/conninfo.ts",
    "content": "import type { GetConnInfo } from '../../helper/conninfo'\n\nexport const getConnInfo: GetConnInfo = (c) => ({\n  remote: {\n    address: c.req.header('cf-connecting-ip'),\n  },\n})\n"
  },
  {
    "path": "src/adapter/cloudflare-workers/index.ts",
    "content": "/**\n * @module\n * Cloudflare Workers Adapter for Hono.\n */\n\nexport { serveStatic } from './serve-static-module'\nexport { upgradeWebSocket } from './websocket'\nexport { getConnInfo } from './conninfo'\n"
  },
  {
    "path": "src/adapter/cloudflare-workers/serve-static-module.ts",
    "content": "// For ES module mode\nimport type { Env, MiddlewareHandler } from '../../types'\nimport type { ServeStaticOptions } from './serve-static'\nimport { serveStatic } from './serve-static'\n\nconst module = <E extends Env = Env>(\n  options: Omit<ServeStaticOptions<E>, 'namespace'>\n): MiddlewareHandler => {\n  return serveStatic<E>(options)\n}\n\nexport { module as serveStatic }\n"
  },
  {
    "path": "src/adapter/cloudflare-workers/serve-static.test.ts",
    "content": "import type { Context } from '../../context'\nimport { Hono } from '../../hono'\nimport type { Next } from '../../types'\nimport { serveStatic } from './serve-static'\n\n// Mock\nconst store: Record<string, string> = {\n  'assets/static/plain.abcdef.txt': 'This is plain.txt',\n  'assets/static/hono.abcdef.html': '<h1>Hono!</h1>',\n  'assets/static/top/index.abcdef.html': '<h1>Top</h1>',\n  'static-no-root/plain.abcdef.txt': 'That is plain.txt',\n  'assets/static/options/foo.abcdef.txt': 'With options',\n  'assets/.static/plain.abcdef.txt': 'In the dot',\n  'assets/static/video/morning-routine.abcdef.m3u8': 'Good morning',\n  'assets/static/video/morning-routine1.abcdef.ts': 'Good',\n  'assets/static/video/introduction.abcdef.mp4': 'Let me introduce myself',\n  'assets/static/download': 'download',\n}\nconst manifest = JSON.stringify({\n  'assets/static/plain.txt': 'assets/static/plain.abcdef.txt',\n  'assets/static/hono.html': 'assets/static/hono.abcdef.html',\n  'assets/static/top/index.html': 'assets/static/top/index.abcdef.html',\n  'static-no-root/plain.txt': 'static-no-root/plain.abcdef.txt',\n  'assets/.static/plain.txt': 'assets/.static/plain.abcdef.txt',\n  'assets/static/download': 'assets/static/download',\n})\n\nObject.assign(global, { __STATIC_CONTENT_MANIFEST: manifest })\nObject.assign(global, {\n  __STATIC_CONTENT: {\n    get: (path: string) => {\n      return store[path]\n    },\n  },\n})\n\ndescribe('ServeStatic Middleware', () => {\n  const app = new Hono()\n  const onNotFound = vi.fn(() => {})\n  app.use('/static/*', serveStatic({ root: './assets', onNotFound, manifest }))\n  app.use('/static-no-root/*', serveStatic({ manifest }))\n  app.use(\n    '/dot-static/*',\n    serveStatic({\n      root: './assets',\n      rewriteRequestPath: (path) => path.replace(/^\\/dot-static/, '/.static'),\n      manifest,\n    })\n  )\n\n  beforeEach(() => onNotFound.mockClear())\n\n  it('Should return plain.txt', async () => {\n    const res = await app.request('http://localhost/static/plain.txt')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('This is plain.txt')\n    expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8')\n    expect(onNotFound).not.toHaveBeenCalled()\n  })\n\n  it('Should return hono.html', async () => {\n    const res = await app.request('http://localhost/static/hono.html')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('<h1>Hono!</h1>')\n    expect(res.headers.get('Content-Type')).toBe('text/html; charset=utf-8')\n    expect(onNotFound).not.toHaveBeenCalled()\n  })\n\n  it('Should return 404 response', async () => {\n    const res = await app.request('http://localhost/static/not-found.html')\n    expect(res.status).toBe(404)\n    expect(onNotFound).toHaveBeenCalledWith('assets/static/not-found.html', expect.anything())\n  })\n\n  it('Should return plan.txt', async () => {\n    const res = await app.request('http://localhost/static-no-root/plain.txt')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('That is plain.txt')\n    expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8')\n  })\n\n  // Serve static on Cloudflare Workers cannot determine whether the target path is a directory or not\n  it.skip('Should return index.html', async () => {\n    const res = await app.request('http://localhost/static/top')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('<h1>Top</h1>')\n    expect(res.headers.get('Content-Type')).toBe('text/html; charset=utf-8')\n  })\n\n  it('Should return plain.txt with a rewriteRequestPath option', async () => {\n    const res = await app.request('http://localhost/dot-static/plain.txt')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('In the dot')\n    expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8')\n  })\n})\n\ndescribe('With options', () => {\n  const manifest = {\n    'assets/static/options/foo.txt': 'assets/static/options/foo.abcdef.txt',\n  }\n\n  const app = new Hono()\n  app.use('/static/*', serveStatic({ root: './assets', manifest: manifest }))\n\n  it('Should return foo.txt', async () => {\n    const res = await app.request('http://localhost/static/options/foo.txt')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('With options')\n    expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8')\n  })\n})\n\ndescribe('With `file` options', () => {\n  const app = new Hono()\n  app.get('/foo/*', serveStatic({ path: './assets/static/hono.html', manifest }))\n  app.get('/bar/*', serveStatic({ path: './static/hono.html', root: './assets', manifest }))\n\n  it('Should return hono.html', async () => {\n    const res = await app.request('http://localhost/foo/fallback')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('<h1>Hono!</h1>')\n  })\n\n  it('Should return hono.html - with `root` option', async () => {\n    const res = await app.request('http://localhost/bar/fallback')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('<h1>Hono!</h1>')\n  })\n})\n\ndescribe('With `mimes` options', () => {\n  const mimes = {\n    m3u8: 'application/vnd.apple.mpegurl',\n    ts: 'video/mp2t',\n  }\n  const manifest = {\n    'assets/static/video/morning-routine.m3u8': 'assets/static/video/morning-routine.abcdef.m3u8',\n    'assets/static/video/morning-routine1.ts': 'assets/static/video/morning-routine1.abcdef.ts',\n    'assets/static/video/introduction.mp4': 'assets/static/video/introduction.abcdef.mp4',\n  }\n\n  const app = new Hono()\n  app.use('/static/*', serveStatic({ root: './assets', mimes, manifest }))\n\n  it('Should return content-type of m3u8', async () => {\n    const res = await app.request('http://localhost/static/video/morning-routine.m3u8')\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Type')).toBe('application/vnd.apple.mpegurl')\n  })\n  it('Should return content-type of ts', async () => {\n    const res = await app.request('http://localhost/static/video/morning-routine1.ts')\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Type')).toBe('video/mp2t')\n  })\n  it('Should return content-type of default on Hono', async () => {\n    const res = await app.request('http://localhost/static/video/introduction.mp4')\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Type')).toBe('video/mp4')\n  })\n})\n\ndescribe('With middleware', () => {\n  const app = new Hono()\n  const md1 = async (c: Context, next: Next) => {\n    await next()\n    c.res.headers.append('x-foo', 'bar')\n  }\n  const md2 = async (c: Context, next: Next) => {\n    await next()\n    c.res.headers.append('x-foo2', 'bar2')\n  }\n\n  app.use('/static/*', md1)\n  app.use('/static/*', md2)\n  app.use('/static/*', serveStatic({ root: './assets', manifest }))\n  app.get('/static/foo', (c) => {\n    return c.text('bar')\n  })\n\n  it('Should return plain.txt with correct headers', async () => {\n    const res = await app.request('http://localhost/static/plain.txt')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('This is plain.txt')\n    expect(res.headers.get('Content-Type')).toBe('text/plain; charset=utf-8')\n    expect(res.headers.get('x-foo')).toBe('bar')\n    expect(res.headers.get('x-foo2')).toBe('bar2')\n  })\n\n  it('Should return 200 Response', async () => {\n    const res = await app.request('http://localhost/static/foo')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('bar')\n  })\n\n  it('Should handle a file without an extension', async () => {\n    const res = await app.request('http://localhost/static/download')\n    expect(res.status).toBe(200)\n  })\n})\n\ndescribe('Types of middleware', () => {\n  it('Should pass env type from generics of serveStatic', async () => {\n    type Env = {\n      Bindings: {\n        HOGE: string\n      }\n    }\n    const app = new Hono<Env>()\n    app.use(\n      '/static/*',\n      serveStatic<Env>({\n        root: './assets',\n        onNotFound: (_, c) => {\n          expectTypeOf(c.env).toEqualTypeOf<Env['Bindings']>()\n        },\n        manifest,\n      })\n    )\n  })\n})\n"
  },
  {
    "path": "src/adapter/cloudflare-workers/serve-static.ts",
    "content": "import { serveStatic as baseServeStatic } from '../../middleware/serve-static'\nimport type { ServeStaticOptions as BaseServeStaticOptions } from '../../middleware/serve-static'\nimport type { Env, MiddlewareHandler } from '../../types'\nimport { getContentFromKVAsset } from './utils'\n\nexport type ServeStaticOptions<E extends Env = Env> = BaseServeStaticOptions<E> & {\n  // namespace is KVNamespace\n  namespace?: unknown\n  manifest: object | string\n}\n\n/**\n * @deprecated\n * `serveStatic` in the Cloudflare Workers adapter is deprecated.\n * You can serve static files directly using Cloudflare Static Assets.\n * @see https://developers.cloudflare.com/workers/static-assets/\n * Cloudflare Static Assets is currently in open beta. If this doesn't work for you,\n * please consider using Cloudflare Pages. You can start to create the Cloudflare Pages\n * application with the `npm create hono@latest` command.\n */\nexport const serveStatic = <E extends Env = Env>(\n  options: ServeStaticOptions<E>\n): MiddlewareHandler => {\n  return async function serveStatic(c, next) {\n    const getContent = async (path: string) => {\n      return getContentFromKVAsset(path, {\n        manifest: options.manifest,\n        // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n        // @ts-ignore\n        namespace: options.namespace\n          ? options.namespace\n          : c.env\n            ? c.env.__STATIC_CONTENT\n            : undefined,\n      })\n    }\n    return baseServeStatic({\n      ...options,\n      getContent,\n    })(c, next)\n  }\n}\n"
  },
  {
    "path": "src/adapter/cloudflare-workers/utils.test.ts",
    "content": "import { getContentFromKVAsset } from './utils'\n\n// Mock\nconst store: { [key: string]: string } = {\n  'index.abcdef.html': 'This is index',\n  'assets/static/plain.abcdef.txt': 'Asset text',\n}\nconst manifest = JSON.stringify({\n  'index.html': 'index.abcdef.html',\n  'assets/static/plain.txt': 'assets/static/plain.abcdef.txt',\n})\n\nObject.assign(global, { __STATIC_CONTENT_MANIFEST: manifest })\nObject.assign(global, {\n  __STATIC_CONTENT: {\n    get: (path: string) => {\n      return store[path]\n    },\n  },\n})\n\ndescribe('Utils for Cloudflare Workers', () => {\n  it('getContentFromKVAsset', async () => {\n    let content = await getContentFromKVAsset('not-found.txt')\n    expect(content).toBeFalsy()\n    content = await getContentFromKVAsset('index.html')\n    expect(content).toBeTruthy()\n    expect(content).toBe('This is index')\n    content = await getContentFromKVAsset('assets/static/plain.txt')\n    expect(content).toBeTruthy()\n    expect(content).toBe('Asset text')\n  })\n})\n"
  },
  {
    "path": "src/adapter/cloudflare-workers/utils.ts",
    "content": "// __STATIC_CONTENT is KVNamespace\ndeclare const __STATIC_CONTENT: unknown\ndeclare const __STATIC_CONTENT_MANIFEST: string\n\nexport type KVAssetOptions = {\n  manifest?: object | string\n  // namespace is KVNamespace\n  namespace?: unknown\n}\n\nexport const getContentFromKVAsset = async (\n  path: string,\n  options?: KVAssetOptions\n): Promise<ReadableStream | null> => {\n  let ASSET_MANIFEST: Record<string, string>\n\n  if (options && options.manifest) {\n    if (typeof options.manifest === 'string') {\n      ASSET_MANIFEST = JSON.parse(options.manifest)\n    } else {\n      ASSET_MANIFEST = options.manifest as Record<string, string>\n    }\n  } else {\n    if (typeof __STATIC_CONTENT_MANIFEST === 'string') {\n      ASSET_MANIFEST = JSON.parse(__STATIC_CONTENT_MANIFEST)\n    } else {\n      ASSET_MANIFEST = __STATIC_CONTENT_MANIFEST\n    }\n  }\n\n  // ASSET_NAMESPACE is KVNamespace\n  let ASSET_NAMESPACE: unknown\n  if (options && options.namespace) {\n    ASSET_NAMESPACE = options.namespace\n  } else {\n    ASSET_NAMESPACE = __STATIC_CONTENT\n  }\n\n  const key = ASSET_MANIFEST[path]\n  if (!key) {\n    return null\n  }\n\n  // @ts-expect-error ASSET_NAMESPACE is not typed\n  const content = await ASSET_NAMESPACE.get(key, { type: 'stream' })\n  if (!content) {\n    return null\n  }\n  return content as unknown as ReadableStream\n}\n"
  },
  {
    "path": "src/adapter/cloudflare-workers/websocket.test.ts",
    "content": "import { Hono } from '../..'\nimport { Context } from '../../context'\nimport { upgradeWebSocket } from '.'\n\ndescribe('upgradeWebSocket middleware', () => {\n  const server = new EventTarget()\n\n  // @ts-expect-error Cloudflare API\n  globalThis.WebSocketPair = class {\n    0: WebSocket // client\n    1: WebSocket // server\n    constructor() {\n      this[0] = {} as WebSocket\n      this[1] = server as WebSocket\n    }\n  }\n\n  const app = new Hono()\n\n  const wsPromise = new Promise((resolve) =>\n    app.get(\n      '/ws',\n      upgradeWebSocket(() => ({\n        onMessage(evt, ws) {\n          resolve([evt.data, ws.readyState || 1])\n        },\n      }))\n    )\n  )\n  it('Should receive message and readyState is valid', async () => {\n    const sendingData = Math.random().toString()\n    await app.request('/ws', {\n      headers: {\n        Upgrade: 'websocket',\n      },\n    })\n    server.dispatchEvent(\n      new MessageEvent('message', {\n        data: sendingData,\n      })\n    )\n\n    expect([sendingData, 1]).toStrictEqual(await wsPromise)\n  })\n  it('Should call next() when header does not have upgrade', async () => {\n    const next = vi.fn()\n    await upgradeWebSocket(() => ({}))(\n      new Context(\n        new Request('http://localhost', {\n          headers: {\n            Upgrade: 'example',\n          },\n        })\n      ),\n      next\n    )\n    expect(next).toBeCalled()\n  })\n})\n"
  },
  {
    "path": "src/adapter/cloudflare-workers/websocket.ts",
    "content": "import { WSContext, defineWebSocketHelper } from '../../helper/websocket'\nimport type { UpgradeWebSocket, WSEvents, WSReadyState } from '../../helper/websocket'\n\n// Based on https://github.com/honojs/hono/issues/1153#issuecomment-1767321332\nexport const upgradeWebSocket: UpgradeWebSocket<\n  WebSocket,\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  any,\n  Omit<WSEvents<WebSocket>, 'onOpen'>\n> = defineWebSocketHelper(async (c, events) => {\n  const upgradeHeader = c.req.header('Upgrade')\n  if (upgradeHeader !== 'websocket') {\n    return\n  }\n\n  // @ts-expect-error WebSocketPair is not typed\n  const webSocketPair = new WebSocketPair()\n  const client: WebSocket = webSocketPair[0]\n  const server: WebSocket = webSocketPair[1]\n\n  const wsContext = new WSContext<WebSocket>({\n    close: (code, reason) => server.close(code, reason),\n    get protocol() {\n      return server.protocol\n    },\n    raw: server,\n    get readyState() {\n      return server.readyState as WSReadyState\n    },\n    url: server.url ? new URL(server.url) : null,\n    send: (source) => server.send(source),\n  })\n\n  // note: cloudflare workers doesn't support 'open' event\n\n  if (events.onClose) {\n    server.addEventListener('close', (evt: CloseEvent) => events.onClose?.(evt, wsContext))\n  }\n  if (events.onMessage) {\n    server.addEventListener('message', (evt: MessageEvent) => events.onMessage?.(evt, wsContext))\n  }\n  if (events.onError) {\n    server.addEventListener('error', (evt: Event) => events.onError?.(evt, wsContext))\n  }\n\n  // @ts-expect-error - server.accept is not typed\n  server.accept?.()\n  return new Response(null, {\n    status: 101,\n    // @ts-expect-error - webSocket is not typed\n    webSocket: client,\n  })\n})\n"
  },
  {
    "path": "src/adapter/deno/conninfo.test.ts",
    "content": "import { Context } from '../../context'\nimport { getConnInfo } from './conninfo'\n\ndescribe('getConnInfo', () => {\n  it('Should info is valid', () => {\n    const transport = 'tcp'\n    const address = Math.random().toString()\n    const port = Math.floor(Math.random() * (65535 + 1))\n    const c = new Context(new Request('http://localhost/'), {\n      env: {\n        remoteAddr: {\n          transport,\n          hostname: address,\n          port,\n        },\n      },\n    })\n    const info = getConnInfo(c)\n\n    expect(info.remote.port).toBe(port)\n    expect(info.remote.address).toBe(address)\n    expect(info.remote.addressType).toBeUndefined()\n    expect(info.remote.transport).toBe(transport)\n  })\n})\n"
  },
  {
    "path": "src/adapter/deno/conninfo.ts",
    "content": "import type { GetConnInfo } from '../../helper/conninfo'\n\n/**\n * Get conninfo with Deno\n * @param c Context\n * @returns ConnInfo\n */\nexport const getConnInfo: GetConnInfo = (c) => {\n  const { remoteAddr } = c.env\n  return {\n    remote: {\n      address: remoteAddr.hostname,\n      port: remoteAddr.port,\n      transport: remoteAddr.transport,\n    },\n  }\n}\n"
  },
  {
    "path": "src/adapter/deno/deno.d.ts",
    "content": "declare namespace Deno {\n  interface FileHandleLike {\n    readonly readable: ReadableStream<Uint8Array>\n  }\n\n  /**\n   * Open the file using the specified path.\n   *\n   * @param path The path to open the file.\n   * @returns FileHandle object.\n   */\n  export function open(path: string): Promise<FileHandleLike>\n\n  interface StatsLike {\n    isDirectory: boolean\n  }\n\n  /**\n   * Get stats with the specified path.\n   *\n   * @param path The path to get stats.\n   * @returns Stats object.\n   */\n  export function lstatSync(path: string): StatsLike\n\n  /**\n   * Creates a new directory with the specified path.\n   *\n   * @param path The path to create a directory.\n   * @param options Options for creating a directory.\n   * @returns A promise that resolves when the directory is created.\n   */\n  export function mkdir(path: string, options?: { recursive?: boolean }): Promise<void>\n\n  /**\n   * Write a new file, with the specified path and data.\n   *\n   * @param path The path to the file to write.\n   * @param data The data to write into the file.\n   * @returns A promise that resolves when the file is written.\n   */\n  export function writeFile(path: string, data: Uint8Array): Promise<void>\n\n  /**\n   * Errors of Deno\n   */\n  export const errors: Record<string, Function>\n\n  export function upgradeWebSocket(\n    req: Request,\n    options: UpgradeWebSocketOptions\n  ): {\n    response: Response\n    socket: WebSocket\n  }\n\n  /**\n   * Options of `upgradeWebSocket`\n   */\n  export interface UpgradeWebSocketOptions {\n    /**\n     * Sets the `.protocol` property on the client-side web socket to the\n     * value provided here, which should be one of the strings specified in the\n     * `protocols` parameter when requesting the web socket. This is intended\n     * for clients and servers to specify sub-protocols to use to communicate to\n     * each other.\n     */\n    protocol?: string\n    /**\n     * If the client does not respond to this frame with a\n     * `pong` within the timeout specified, the connection is deemed\n     * unhealthy and is closed. The `close` and `error` events will be emitted.\n     *\n     * The unit is seconds, with a default of 30.\n     * Set to `0` to disable timeouts.\n     */\n    idleTimeout?: number\n  }\n}\n"
  },
  {
    "path": "src/adapter/deno/index.ts",
    "content": "/**\n * @module\n * Deno Adapter for Hono.\n */\n\nexport { serveStatic } from './serve-static'\nexport { toSSG, denoFileSystemModule } from './ssg'\nexport { upgradeWebSocket } from './websocket'\nexport { getConnInfo } from './conninfo'\n"
  },
  {
    "path": "src/adapter/deno/serve-static.ts",
    "content": "import { join } from 'node:path'\nimport type { ServeStaticOptions } from '../../middleware/serve-static'\nimport { serveStatic as baseServeStatic } from '../../middleware/serve-static'\nimport type { Env, MiddlewareHandler } from '../../types'\n\nconst { open, lstatSync, errors } = Deno\n\nexport const serveStatic = <E extends Env = Env>(\n  options: ServeStaticOptions<E>\n): MiddlewareHandler => {\n  return async function serveStatic(c, next) {\n    const getContent = async (path: string) => {\n      try {\n        if (isDir(path)) {\n          return null\n        }\n\n        const file = await open(path)\n        return file.readable\n      } catch (e) {\n        if (!(e instanceof errors.NotFound)) {\n          console.warn(`${e}`)\n        }\n        return null\n      }\n    }\n    const isDir = (path: string) => {\n      let isDir\n      try {\n        const stat = lstatSync(path)\n        isDir = stat.isDirectory\n      } catch {}\n      return isDir\n    }\n    return baseServeStatic({\n      ...options,\n      getContent,\n      join,\n      isDir,\n    })(c, next)\n  }\n}\n"
  },
  {
    "path": "src/adapter/deno/ssg.ts",
    "content": "import { toSSG as baseToSSG } from '../../helper/ssg/index'\nimport type { FileSystemModule, ToSSGAdaptorInterface } from '../../helper/ssg/index'\n\n/**\n * @experimental\n * `denoFileSystemModule` is an experimental feature.\n * The API might be changed.\n */\nexport const denoFileSystemModule: FileSystemModule = {\n  writeFile: async (path, data) => {\n    const uint8Data =\n      typeof data === 'string' ? new TextEncoder().encode(data) : new Uint8Array(data)\n    await Deno.writeFile(path, uint8Data)\n  },\n  mkdir: async (path, options) => {\n    return Deno.mkdir(path, { recursive: options?.recursive ?? false })\n  },\n}\n\n/**\n * @experimental\n * `toSSG` is an experimental feature.\n * The API might be changed.\n */\nexport const toSSG: ToSSGAdaptorInterface = async (app, options) => {\n  return baseToSSG(app, denoFileSystemModule, options)\n}\n"
  },
  {
    "path": "src/adapter/deno/websocket.test.ts",
    "content": "import { Hono } from '../..'\nimport { Context } from '../../context'\nimport { upgradeWebSocket } from './websocket'\n\nglobalThis.Deno = {} as typeof Deno\n\ndescribe('WebSockets', () => {\n  let app: Hono\n  beforeEach(() => {\n    app = new Hono()\n  })\n\n  it('Should receive data is valid', async () => {\n    const messagePromise = new Promise((resolve) =>\n      app.get(\n        '/ws',\n        upgradeWebSocket(() => ({\n          onMessage: (evt) => resolve(evt.data),\n        }))\n      )\n    )\n    const socket = new EventTarget() as WebSocket\n    Deno.upgradeWebSocket = () => {\n      return {\n        response: new Response(),\n        socket,\n      }\n    }\n    await app.request('/ws', {\n      headers: {\n        upgrade: 'websocket',\n      },\n    })\n    const data = Math.random().toString()\n    socket.onmessage &&\n      socket.onmessage(\n        new MessageEvent('message', {\n          data,\n        })\n      )\n    expect(await messagePromise).toBe(data)\n  })\n\n  it('Should receive data is valid with Options', async () => {\n    const messagePromise = new Promise((resolve) =>\n      app.get(\n        '/ws',\n        upgradeWebSocket(\n          () => ({\n            onMessage: (evt) => resolve(evt.data),\n          }),\n          {\n            idleTimeout: 5000,\n          }\n        )\n      )\n    )\n    const socket = new EventTarget() as WebSocket\n    Deno.upgradeWebSocket = () => {\n      return {\n        response: new Response(),\n        socket,\n      }\n    }\n    await app.request('/ws', {\n      headers: {\n        upgrade: 'websocket',\n      },\n    })\n    const data = Math.random().toString()\n    socket.onmessage &&\n      socket.onmessage(\n        new MessageEvent('message', {\n          data,\n        })\n      )\n    expect(await messagePromise).toBe(data)\n  })\n  it('Should call next() when header does not have upgrade', async () => {\n    const next = vi.fn()\n    await upgradeWebSocket(() => ({}))(\n      new Context(\n        new Request('http://localhost', {\n          headers: {\n            Upgrade: 'example',\n          },\n        })\n      ),\n      next\n    )\n    expect(next).toBeCalled()\n  })\n})\n"
  },
  {
    "path": "src/adapter/deno/websocket.ts",
    "content": "import type { UpgradeWebSocket, WSReadyState } from '../../helper/websocket'\nimport { WSContext, defineWebSocketHelper } from '../../helper/websocket'\n\nexport const upgradeWebSocket: UpgradeWebSocket<WebSocket, Deno.UpgradeWebSocketOptions> =\n  defineWebSocketHelper(async (c, events, options) => {\n    if (c.req.header('upgrade') !== 'websocket') {\n      return\n    }\n\n    const { response, socket } = Deno.upgradeWebSocket(c.req.raw, options ?? {})\n\n    const wsContext: WSContext<WebSocket> = new WSContext({\n      close: (code, reason) => socket.close(code, reason),\n      get protocol() {\n        return socket.protocol\n      },\n      raw: socket,\n      get readyState() {\n        return socket.readyState as WSReadyState\n      },\n      url: socket.url ? new URL(socket.url) : null,\n      send: (source) => socket.send(source),\n    })\n    socket.onopen = (evt) => events.onOpen?.(evt, wsContext)\n    socket.onmessage = (evt) => events.onMessage?.(evt, wsContext)\n    socket.onclose = (evt) => events.onClose?.(evt, wsContext)\n    socket.onerror = (evt) => events.onError?.(evt, wsContext)\n\n    return response\n  })\n"
  },
  {
    "path": "src/adapter/lambda-edge/conninfo.test.ts",
    "content": "import { Context } from '../../context'\nimport { getConnInfo } from './conninfo'\nimport type { CloudFrontEdgeEvent } from './handler'\n\ndescribe('getConnInfo', () => {\n  it('Should info is valid', () => {\n    const clientIp = Math.random().toString()\n    const env = {\n      event: {\n        Records: [\n          {\n            cf: {\n              request: {\n                clientIp,\n              },\n            },\n          },\n        ],\n      } as CloudFrontEdgeEvent,\n    }\n\n    const c = new Context(new Request('http://localhost/'), { env })\n    const info = getConnInfo(c)\n\n    expect(info.remote.address).toBe(clientIp)\n  })\n})\n"
  },
  {
    "path": "src/adapter/lambda-edge/conninfo.ts",
    "content": "import type { Context } from '../../context'\nimport type { GetConnInfo } from '../../helper/conninfo'\nimport type { CloudFrontEdgeEvent } from './handler'\n\ntype Env = {\n  Bindings: {\n    event: CloudFrontEdgeEvent\n  }\n}\n\nexport const getConnInfo: GetConnInfo = (c: Context<Env>) => ({\n  remote: {\n    address: c.env.event.Records[0].cf.request.clientIp,\n  },\n})\n"
  },
  {
    "path": "src/adapter/lambda-edge/handler.test.ts",
    "content": "import { describe } from 'vitest'\nimport { setCookie } from '../../helper/cookie'\nimport { Hono } from '../../hono'\nimport { encodeBase64 } from '../../utils/encode'\nimport type { Callback, CloudFrontEdgeEvent } from './handler'\nimport { createBody, handle, isContentTypeBinary } from './handler'\n\ndescribe('isContentTypeBinary', () => {\n  it('Should determine whether it is binary', () => {\n    expect(isContentTypeBinary('image/png')).toBe(true)\n    expect(isContentTypeBinary('font/woff2')).toBe(true)\n    expect(isContentTypeBinary('image/svg+xml')).toBe(false)\n    expect(isContentTypeBinary('image/svg+xml; charset=UTF-8')).toBe(false)\n    expect(isContentTypeBinary('text/plain')).toBe(false)\n    expect(isContentTypeBinary('text/plain; charset=UTF-8')).toBe(false)\n    expect(isContentTypeBinary('text/css')).toBe(false)\n    expect(isContentTypeBinary('text/javascript')).toBe(false)\n    expect(isContentTypeBinary('application/json')).toBe(false)\n    expect(isContentTypeBinary('application/ld+json')).toBe(false)\n    expect(isContentTypeBinary('application/json')).toBe(false)\n  })\n})\n\ndescribe('createBody', () => {\n  it('Should the request be a GET or HEAD, the Request must not include a Body', () => {\n    const encoder = new TextEncoder()\n    const data = encoder.encode('test')\n    const body = {\n      action: 'read-only',\n      data: encodeBase64(data.buffer),\n      encoding: 'base64',\n      inputTruncated: false,\n    }\n\n    expect(createBody('GET', body)).toEqual(undefined)\n    expect(createBody('GET', body)).not.toEqual(data)\n    expect(createBody('HEAD', body)).toEqual(undefined)\n    expect(createBody('HEAD', body)).not.toEqual(data)\n    expect(createBody('POST', body)).toEqual(data)\n    expect(createBody('POST', body)).not.toEqual(undefined)\n  })\n})\n\ndescribe('handle', () => {\n  const cloudFrontEdgeEvent: CloudFrontEdgeEvent = {\n    Records: [\n      {\n        cf: {\n          config: {\n            distributionDomainName: 'd111111abcdef8.cloudfront.net',\n            distributionId: 'EDFDVBD6EXAMPLE',\n            eventType: 'viewer-request',\n            requestId: '4TyzHTaYWb1GX1qTfsHhEqV6HUDd_BzoBZnwfnvQc_1oF26ClkoUSEQ==',\n          },\n          request: {\n            clientIp: '1.2.3.4',\n            headers: {\n              host: [\n                {\n                  key: 'Host',\n                  value: 'hono.dev',\n                },\n              ],\n              accept: [\n                {\n                  key: 'accept',\n                  value: '*/*',\n                },\n              ],\n            },\n            method: 'GET',\n            querystring: '',\n            uri: '/test-path',\n          },\n        },\n      },\n    ],\n  }\n\n  it('Should support alternate domain names', async () => {\n    const app = new Hono()\n    app.get('/test-path', (c) => {\n      return c.text(c.req.url)\n    })\n    const handler = handle(app)\n\n    const res = await handler(cloudFrontEdgeEvent)\n\n    expect(res.body).toBe('https://hono.dev/test-path')\n  })\n\n  it('Should expose async handler arity compatible with NODEJS_24_X', () => {\n    const app = new Hono()\n    const handler = handle(app)\n\n    expect(handler.length).toBeLessThanOrEqual(2)\n  })\n\n  it('Should preserve positional callback compatibility', async () => {\n    type Env = { Bindings: { callback: Callback } }\n    const app = new Hono<Env>()\n    const callback = vi.fn()\n\n    app.get('/test-path', (c) => {\n      c.env.callback?.(null, {\n        status: '200',\n        headers: {\n          'x-test': [{ key: 'x-test', value: 'ok' }],\n        },\n      })\n      return c.text('ok')\n    })\n\n    const handler = handle(app)\n    await handler(cloudFrontEdgeEvent, undefined, callback)\n\n    expect(callback).toHaveBeenCalledWith(null, {\n      status: '200',\n      headers: {\n        'x-test': [{ key: 'x-test', value: 'ok' }],\n      },\n    })\n  })\n\n  it('Should support multiple cookies', async () => {\n    const app = new Hono()\n    app.get('/test-path', (c) => {\n      setCookie(c, 'cookie1', 'value1')\n      setCookie(c, 'cookie2', 'value2')\n      return c.text('')\n    })\n    const handler = handle(app)\n\n    const res = await handler(cloudFrontEdgeEvent)\n\n    expect(res.headers).toEqual({\n      'content-type': [\n        {\n          key: 'content-type',\n          value: 'text/plain; charset=UTF-8',\n        },\n      ],\n      'set-cookie': [\n        {\n          key: 'set-cookie',\n          value: 'cookie1=value1; Path=/',\n        },\n        {\n          key: 'set-cookie',\n          value: 'cookie2=value2; Path=/',\n        },\n      ],\n    })\n  })\n})\n"
  },
  {
    "path": "src/adapter/lambda-edge/handler.ts",
    "content": "import crypto from 'node:crypto'\nimport type { Hono } from '../../hono'\n\nimport { decodeBase64, encodeBase64 } from '../../utils/encode'\n\n// eslint-disable-next-line @typescript-eslint/ban-ts-comment\n// @ts-ignore\nglobalThis.crypto ??= crypto\n\ninterface CloudFrontHeader {\n  key: string\n  value: string\n}\n\ninterface CloudFrontHeaders {\n  [name: string]: CloudFrontHeader[]\n}\n\ninterface CloudFrontCustomOrigin {\n  customHeaders: CloudFrontHeaders\n  domainName: string\n  keepaliveTimeout: number\n  path: string\n  port: number\n  protocol: string\n  readTimeout: number\n  sslProtocols: string[]\n}\n// https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html\ninterface CloudFrontS3Origin {\n  authMethod: 'origin-access-identity' | 'none'\n  customHeaders: CloudFrontHeaders\n  domainName: string\n  path: string\n  region: string\n}\ntype CloudFrontOrigin =\n  | { s3: CloudFrontS3Origin; custom?: never }\n  | { custom: CloudFrontCustomOrigin; s3?: never }\n\nexport interface CloudFrontRequest {\n  clientIp: string\n  headers: CloudFrontHeaders\n  method: string\n  querystring: string\n  uri: string\n  body?: {\n    inputTruncated: boolean\n    action: string\n    encoding: string\n    data: string\n  }\n  origin?: CloudFrontOrigin\n}\n\nexport interface CloudFrontResponse {\n  headers: CloudFrontHeaders\n  status: string\n  statusDescription?: string\n}\n\nexport interface CloudFrontConfig {\n  distributionDomainName: string\n  distributionId: string\n  eventType: string\n  requestId: string\n}\n\ninterface CloudFrontEvent {\n  cf: {\n    config: CloudFrontConfig\n    request: CloudFrontRequest\n    response?: CloudFrontResponse\n  }\n}\n\nexport interface CloudFrontEdgeEvent {\n  Records: CloudFrontEvent[]\n}\n\ntype CloudFrontContext = {}\n\nexport interface Callback {\n  (err: Error | null, result?: CloudFrontRequest | CloudFrontResult): void\n}\n\n// https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-generating-http-responses-in-requests.html#lambda-generating-http-responses-programming-model\ninterface CloudFrontResult {\n  status: string\n  statusDescription?: string\n  headers?: {\n    [header: string]: {\n      key: string\n      value: string\n    }[]\n  }\n  body?: string\n  bodyEncoding?: 'text' | 'base64'\n}\n\n/**\n * Accepts events from 'Lambda@Edge' event\n * https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html\n */\nconst convertHeaders = (headers: Headers): CloudFrontHeaders => {\n  const cfHeaders: CloudFrontHeaders = {}\n  headers.forEach((value, key) => {\n    cfHeaders[key.toLowerCase()] = [\n      ...(cfHeaders[key.toLowerCase()] || []),\n      { key: key.toLowerCase(), value },\n    ]\n  })\n  return cfHeaders\n}\n\nexport const handle = (\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  app: Hono<any>\n): ((\n  event: CloudFrontEdgeEvent,\n  context?: CloudFrontContext,\n  callback?: Callback\n) => Promise<CloudFrontResult>) => {\n  return async (event, ...args: [context?: CloudFrontContext, callback?: Callback]) => {\n    const [context, callback] = args\n    const res = await app.fetch(createRequest(event), {\n      event,\n      context,\n      callback: (err: Error | null, result?: CloudFrontResult | CloudFrontRequest) => {\n        callback?.(err, result)\n      },\n      config: event.Records[0].cf.config,\n      request: event.Records[0].cf.request,\n      response: event.Records[0].cf.response,\n    })\n    return createResult(res)\n  }\n}\n\nconst createResult = async (res: Response): Promise<CloudFrontResult> => {\n  const isBase64Encoded = isContentTypeBinary(res.headers.get('content-type') || '')\n  const body = isBase64Encoded ? encodeBase64(await res.arrayBuffer()) : await res.text()\n\n  return {\n    status: res.status.toString(),\n    headers: convertHeaders(res.headers),\n    body,\n    ...(isBase64Encoded && { bodyEncoding: 'base64' }),\n  }\n}\n\nconst createRequest = (event: CloudFrontEdgeEvent): Request => {\n  const queryString = event.Records[0].cf.request.querystring\n  const host =\n    event.Records[0].cf.request.headers?.host?.[0]?.value ||\n    event.Records[0].cf.config.distributionDomainName\n  const urlPath = `https://${host}${event.Records[0].cf.request.uri}`\n  const url = queryString ? `${urlPath}?${queryString}` : urlPath\n\n  const headers = new Headers()\n  Object.entries(event.Records[0].cf.request.headers).forEach(([k, v]) => {\n    v.forEach((header) => headers.set(k, header.value))\n  })\n\n  const requestBody = event.Records[0].cf.request.body\n  const method = event.Records[0].cf.request.method\n  const body = createBody(method, requestBody)\n\n  return new Request(url, {\n    headers,\n    method,\n    body,\n  })\n}\n\nexport const createBody = (\n  method: string,\n  requestBody: CloudFrontRequest['body']\n): string | Uint8Array<ArrayBuffer> | undefined => {\n  if (!requestBody || !requestBody.data) {\n    return undefined\n  }\n  if (method === 'GET' || method === 'HEAD') {\n    return undefined\n  }\n  if (requestBody.encoding === 'base64') {\n    return decodeBase64(requestBody.data)\n  }\n  return requestBody.data\n}\n\nexport const isContentTypeBinary = (contentType: string): boolean => {\n  return !/^(text\\/(plain|html|css|javascript|csv).*|application\\/(.*json|.*xml).*|image\\/svg\\+xml.*)$/.test(\n    contentType\n  )\n}\n"
  },
  {
    "path": "src/adapter/lambda-edge/index.ts",
    "content": "/**\n * @module\n * Lambda@Edge Adapter for Hono.\n */\n\nexport { handle } from './handler'\nexport { getConnInfo } from './conninfo'\nexport type {\n  Callback,\n  CloudFrontConfig,\n  CloudFrontRequest,\n  CloudFrontResponse,\n  CloudFrontEdgeEvent,\n} from './handler'\n"
  },
  {
    "path": "src/adapter/netlify/conninfo.test.ts",
    "content": "import { Context } from '../../context'\nimport { getConnInfo } from './conninfo'\n\ndescribe('getConnInfo', () => {\n  it('Should return the client IP from context.ip', () => {\n    const ip = '203.0.113.50'\n    const c = new Context(new Request('http://localhost/'), {\n      env: {\n        context: {\n          ip,\n        },\n      },\n    })\n\n    const info = getConnInfo(c)\n\n    expect(info.remote.address).toBe(ip)\n  })\n\n  it('Should return undefined when context.ip is not present', () => {\n    const c = new Context(new Request('http://localhost/'), {\n      env: {\n        context: {},\n      },\n    })\n\n    const info = getConnInfo(c)\n\n    expect(info.remote.address).toBeUndefined()\n  })\n\n  it('Should return undefined when context is not present', () => {\n    const c = new Context(new Request('http://localhost/'), {\n      env: {},\n    })\n\n    const info = getConnInfo(c)\n\n    expect(info.remote.address).toBeUndefined()\n  })\n})\n"
  },
  {
    "path": "src/adapter/netlify/conninfo.ts",
    "content": "import type { Context } from '../../context'\nimport type { GetConnInfo } from '../../helper/conninfo'\n\n/**\n * Netlify context type\n * @see https://docs.netlify.com/functions/api/\n */\ntype NetlifyContext = {\n  ip?: string\n  geo?: {\n    city?: string\n    country?: {\n      code?: string\n      name?: string\n    }\n    subdivision?: {\n      code?: string\n      name?: string\n    }\n    latitude?: number\n    longitude?: number\n    timezone?: string\n    postalCode?: string\n  }\n  requestId?: string\n}\n\ntype Env = {\n  Bindings: {\n    context: NetlifyContext\n  }\n}\n\n/**\n * Get connection information from Netlify\n * @param c - Context\n * @returns Connection information including remote address\n * @example\n * ```ts\n * import { Hono } from 'hono'\n * import { handle, getConnInfo } from 'hono/netlify'\n *\n * const app = new Hono()\n *\n * app.get('/', (c) => {\n *   const info = getConnInfo(c)\n *   return c.text(`Your IP: ${info.remote.address}`)\n * })\n *\n * export default handle(app)\n * ```\n */\nexport const getConnInfo: GetConnInfo = (c: Context<Env>) => ({\n  remote: {\n    address: c.env.context?.ip,\n  },\n})\n"
  },
  {
    "path": "src/adapter/netlify/handler.ts",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { Hono } from '../../hono'\n\nexport const handle = (\n  app: Hono<any, any>\n): ((req: Request, context: any) => Response | Promise<Response>) => {\n  return (req: Request, context: any) => {\n    return app.fetch(req, { context })\n  }\n}\n"
  },
  {
    "path": "src/adapter/netlify/index.ts",
    "content": "/**\n * @module\n * Netlify Adapter for Hono.\n */\n\nexport * from './mod'\n"
  },
  {
    "path": "src/adapter/netlify/mod.ts",
    "content": "export { handle } from './handler'\nexport { getConnInfo } from './conninfo'\n"
  },
  {
    "path": "src/adapter/service-worker/handler.test.ts",
    "content": "import { Hono } from '../../hono'\nimport { handle } from './handler'\nimport type { FetchEvent } from './types'\n\nbeforeAll(() => {\n  // fetch errors when it's not bound to globalThis in service worker\n  // set a fetch stub to emulate that behavior\n  vi.stubGlobal(\n    'fetch',\n    function fetch(this: undefined | typeof globalThis, arg0: string | Request) {\n      if (this !== globalThis) {\n        const error = new Error(\n          \"Failed to execute 'fetch' on 'WorkerGlobalScope': Illegal invocation\"\n        )\n        error.name = 'TypeError'\n        throw error\n      }\n      if (arg0 instanceof Request && arg0.url === 'http://localhost/fallback') {\n        return new Response('hello world')\n      }\n      return Response.error()\n    }\n  )\n})\nafterAll(() => {\n  vi.unstubAllGlobals()\n})\n\ndescribe('handle', () => {\n  it('Success to fetch', async () => {\n    const app = new Hono()\n    app.get('/', (c) => {\n      return c.json({ hello: 'world' })\n    })\n    const handler = handle(app)\n    const json = await new Promise<Response>((resolve) => {\n      handler({\n        request: new Request('http://localhost/'),\n        respondWith(res) {\n          resolve(res)\n        },\n      } as FetchEvent)\n    }).then((res) => res.json())\n    expect(json).toStrictEqual({ hello: 'world' })\n  })\n  it('Fallback 404', async () => {\n    const app = new Hono()\n    const handler = handle(app)\n    const text = await new Promise<Response>((resolve) => {\n      handler({\n        request: new Request('http://localhost/fallback'),\n        respondWith(res) {\n          resolve(res)\n        },\n      } as FetchEvent)\n    }).then((res) => res.text())\n    expect(text).toBe('hello world')\n  })\n  it('Fallback 404 with explicit fetch', async () => {\n    const app = new Hono()\n    const handler = handle(app, {\n      async fetch() {\n        return new Response('hello world')\n      },\n    })\n    const text = await new Promise<Response>((resolve) => {\n      handler({\n        request: new Request('http://localhost/'),\n        respondWith(res) {\n          resolve(res)\n        },\n      } as FetchEvent)\n    }).then((res) => res.text())\n    expect(text).toBe('hello world')\n  })\n  it('Do not fallback 404 when fetch is undefined', async () => {\n    const app = new Hono()\n    app.get('/', (c) => c.text('Not found', 404))\n    const handler = handle(app, {\n      fetch: undefined,\n    })\n    const result = await new Promise<Response>((resolve) =>\n      handler({\n        request: new Request('https://localhost/'),\n        respondWith(r) {\n          resolve(r)\n        },\n      } as FetchEvent)\n    )\n\n    expect(result.status).toBe(404)\n    expect(await result.text()).toBe('Not found')\n  })\n\n  it('Should pass FetchEvent as second argument to app.fetch', async () => {\n    const app = new Hono()\n\n    app.get('/', (c) => {\n      return c.json({\n        // @ts-expect-error executionCtx is FetchEvent but not typed well\n        clientId: c.executionCtx.clientId,\n      })\n    })\n\n    const handler = handle(app)\n    // @ts-expect-error Force mocking FetchEvent including custom values\n    const mockFetchEvent = {\n      clientId: 'test-client-id',\n      respondWith: vi.fn(),\n      request: new Request('http://localhost'),\n    } as FetchEvent\n\n    const response = await new Promise<Response>((resolve) => {\n      mockFetchEvent.respondWith = resolve\n      handler(mockFetchEvent)\n    })\n\n    const json = await response.json()\n    expect(json.clientId).toBe('test-client-id')\n  })\n})\n"
  },
  {
    "path": "src/adapter/service-worker/handler.ts",
    "content": "/**\n * Handler for Service Worker\n * @module\n */\n\nimport type { Hono } from '../../hono'\nimport type { Env, Schema } from '../../types'\nimport type { FetchEvent } from './types'\n\ntype Handler = (evt: FetchEvent) => void\nexport type HandleOptions = {\n  fetch?: typeof fetch\n}\n\n/**\n * Adapter for Service Worker\n */\nexport const handle = <E extends Env, S extends Schema, BasePath extends string>(\n  app: Hono<E, S, BasePath>,\n  opts: HandleOptions = {\n    // To use `fetch` on a Service Worker correctly, bind it to `globalThis`.\n    fetch: globalThis.fetch.bind(globalThis),\n  }\n): Handler => {\n  return (evt) => {\n    evt.respondWith(\n      (async () => {\n        // @ts-expect-error Passing FetchEvent but app.fetch expects ExecutionContext\n        const res = await app.fetch(evt.request, {}, evt)\n        if (opts.fetch && res.status === 404) {\n          return await opts.fetch(evt.request)\n        }\n        return res\n      })()\n    )\n  }\n}\n"
  },
  {
    "path": "src/adapter/service-worker/index.ts",
    "content": "/**\n * Service Worker Adapter for Hono.\n * @module\n */\nimport type { Hono } from '../../hono'\nimport type { Env, Schema } from '../../types'\nimport { handle } from './handler'\nimport type { HandleOptions } from './handler'\n\n/**\n * Registers a Hono app to handle fetch events in a service worker.\n * This sets up `addEventListener('fetch', handle(app, options))` for the provided app.\n *\n * @param app - The Hono application instance\n * @param options - Options for handling requests (fetch defaults to undefined)\n * @example\n * ```ts\n * import { Hono } from 'hono'\n * import { fire } from 'hono/service-worker'\n *\n * const app = new Hono()\n *\n * app.get('/', (c) => c.text('Hi'))\n *\n * fire(app)\n * ```\n */\nconst fire = <E extends Env, S extends Schema, BasePath extends string>(\n  app: Hono<E, S, BasePath>,\n  options: HandleOptions = {\n    fetch: undefined,\n  }\n): void => {\n  // @ts-expect-error addEventListener is not typed well in ServiceWorker-like contexts, see: https://github.com/microsoft/TypeScript/issues/14877\n  addEventListener('fetch', handle(app, options))\n}\n\nexport { handle, fire }\n"
  },
  {
    "path": "src/adapter/service-worker/types.ts",
    "content": "interface ExtendableEvent extends Event {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  waitUntil(f: Promise<any>): void\n}\n\nexport interface FetchEvent extends ExtendableEvent {\n  readonly clientId: string\n  readonly handled: Promise<void>\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  readonly preloadResponse: Promise<any>\n  readonly request: Request\n  readonly resultingClientId: string\n  respondWith(r: Response | PromiseLike<Response>): void\n}\n"
  },
  {
    "path": "src/adapter/vercel/conninfo.test.ts",
    "content": "import { Context } from '../../context'\nimport { getConnInfo } from './conninfo'\n\ndescribe('getConnInfo', () => {\n  it('Should getConnInfo works', () => {\n    const address = Math.random().toString()\n    const req = new Request('http://localhost/', {\n      headers: {\n        'x-real-ip': address,\n      },\n    })\n    const c = new Context(req)\n\n    const info = getConnInfo(c)\n\n    expect(info.remote.address).toBe(address)\n  })\n})\n"
  },
  {
    "path": "src/adapter/vercel/conninfo.ts",
    "content": "import type { GetConnInfo } from '../../helper/conninfo'\n\nexport const getConnInfo: GetConnInfo = (c) => ({\n  remote: {\n    // https://github.com/vercel/vercel/blob/b70bfb5fbf28a4650d4042ce68ca5c636d37cf44/packages/edge/src/edge-headers.ts#L10-L12C32\n    address: c.req.header('x-real-ip'),\n  },\n})\n"
  },
  {
    "path": "src/adapter/vercel/handler.test.ts",
    "content": "import { Hono } from '../../hono'\nimport { handle } from './handler'\n\ndescribe('Adapter for Next.js', () => {\n  it('Should return 200 response', async () => {\n    const app = new Hono()\n    app.get('/api/author/:name', async (c) => {\n      const name = c.req.param('name')\n      return c.json({\n        path: '/api/author/:name',\n        name,\n      })\n    })\n    const handler = handle(app)\n    const req = new Request('http://localhost/api/author/hono')\n    const res = await handler(req)\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({\n      path: '/api/author/:name',\n      name: 'hono',\n    })\n  })\n\n  it('Should not use `route()` if path argument is not passed', async () => {\n    const app = new Hono().basePath('/api')\n\n    app.onError((e) => {\n      throw e\n    })\n    app.get('/error', () => {\n      throw new Error('Custom Error')\n    })\n\n    const handler = handle(app)\n    const req = new Request('http://localhost/api/error')\n    expect(() => handler(req)).toThrowError('Custom Error')\n  })\n})\n"
  },
  {
    "path": "src/adapter/vercel/handler.ts",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { Hono } from '../../hono'\n\nexport const handle =\n  (app: Hono<any, any, any>) =>\n  (req: Request): Response | Promise<Response> => {\n    return app.fetch(req)\n  }\n"
  },
  {
    "path": "src/adapter/vercel/index.ts",
    "content": "/**\n * @module\n * Vercel Adapter for Hono.\n */\n\nexport { handle } from './handler'\nexport { getConnInfo } from './conninfo'\n"
  },
  {
    "path": "src/client/client.test.ts",
    "content": "/* eslint-disable @typescript-eslint/no-unused-vars */\n\n/* eslint-disable @typescript-eslint/ban-ts-comment */\nimport { HttpResponse, http } from 'msw'\nimport { setupServer } from 'msw/node'\nimport { expectTypeOf, vi } from 'vitest'\nimport { upgradeWebSocket } from '../adapter/deno/websocket'\nimport { Hono } from '../hono'\nimport { parse } from '../utils/cookie'\nimport type { Equal, Expect, JSONValue, SimplifyDeepArray } from '../utils/types'\nimport { validator } from '../validator'\nimport { hc } from './client'\nimport type {\n  ClientResponse,\n  InferRequestType,\n  InferResponseType,\n  ApplyGlobalResponse,\n} from './types'\n\nclass SafeBigInt {\n  unsafe = BigInt(42)\n\n  toJSON() {\n    return {\n      value: '42n',\n    }\n  }\n}\n\ndescribe('Basic - JSON', () => {\n  const app = new Hono()\n\n  const route = app\n    .post(\n      '/posts',\n      validator('cookie', () => {\n        return {} as {\n          debug: string\n        }\n      }),\n      validator('header', () => {\n        return {} as {\n          'x-message': string\n        }\n      }),\n      validator('json', () => {\n        return {} as {\n          id: number\n          title: string\n        }\n      }),\n      (c) => {\n        return c.json({\n          success: true,\n          message: 'dummy',\n          requestContentType: 'dummy',\n          requestHono: 'dummy',\n          requestMessage: 'dummy',\n          requestBody: {\n            id: 123,\n            title: 'dummy',\n          },\n        })\n      }\n    )\n    .get('/hello-not-found', (c) => c.notFound())\n    .get('/null', (c) => c.json(null))\n    .get('/empty', (c) => c.json({}))\n    .get('/bigint', (c) => c.json({ value: BigInt(42) }))\n    .get('/safe-bigint', (c) => c.json(new SafeBigInt()))\n\n  type AppType = typeof route\n\n  const server = setupServer(\n    http.post('http://localhost/posts', async ({ request }) => {\n      const requestContentType = request.headers.get('content-type')\n      const requestHono = request.headers.get('x-hono')\n      const requestMessage = request.headers.get('x-message')\n      const requestBody = await request.json()\n      const payload = {\n        message: 'Hello!',\n        success: true,\n        requestContentType,\n        requestHono,\n        requestMessage,\n        requestBody,\n      }\n      return HttpResponse.json(payload)\n    }),\n    http.get('http://localhost/hello-not-found', () => {\n      return HttpResponse.text(null, {\n        status: 404,\n      })\n    }),\n    http.get('http://localhost/null', () => {\n      return HttpResponse.json(null)\n    }),\n    http.get('http://localhost/empty', () => {\n      return HttpResponse.json({})\n    }),\n    http.get('http://localhost/bigint', () => {\n      return HttpResponse.json({ value: BigInt(42) })\n    }),\n    http.get('http://localhost/safe-bigint', () => {\n      return HttpResponse.json(new SafeBigInt())\n    }),\n    http.get('http://localhost/api/string', () => {\n      return HttpResponse.json('a-string')\n    }),\n    http.get('http://localhost/api/number', async () => {\n      return HttpResponse.json(37)\n    }),\n    http.get('http://localhost/api/boolean', async () => {\n      return HttpResponse.json(true)\n    }),\n    http.get('http://localhost/api/generic', async () => {\n      return HttpResponse.json(Math.random() > 0.5 ? Boolean(Math.random()) : Math.random())\n    })\n  )\n\n  beforeAll(() => server.listen())\n  afterEach(() => server.resetHandlers())\n  afterAll(() => server.close())\n\n  const payload = {\n    id: 123,\n    title: 'Hello! Hono!',\n  }\n\n  const client = hc<AppType>('http://localhost', { headers: { 'x-hono': 'hono' } })\n\n  it('Should get 200 response', async () => {\n    const res = await client.posts.$post(\n      {\n        json: payload,\n        header: {\n          'x-message': 'foobar',\n        },\n        cookie: {\n          debug: 'true',\n        },\n      },\n      {}\n    )\n\n    expect(res.ok).toBe(true)\n    const data = await res.json()\n    expect(data.success).toBe(true)\n    expect(data.message).toBe('Hello!')\n    expect(data.requestContentType).toBe('application/json')\n    expect(data.requestHono).toBe('hono')\n    expect(data.requestMessage).toBe('foobar')\n    expect(data.requestBody).toEqual(payload)\n  })\n\n  it('Should get 404 response', async () => {\n    const res = await client['hello-not-found'].$get()\n    expect(res.status).toBe(404)\n  })\n\n  it('Should get a `null` content', async () => {\n    const client = hc<AppType>('http://localhost')\n    const res = await client.null.$get()\n    const data = await res.json()\n    expectTypeOf(data).toMatchTypeOf<null>()\n    expect(data).toBe(null)\n  })\n\n  it('Should get a `{}` content', async () => {\n    const client = hc<AppType>('http://localhost')\n    const res = await client.empty.$get()\n    const data = await res.json()\n    expectTypeOf(data).toMatchTypeOf<{}>()\n    expect(data).toStrictEqual({})\n  })\n\n  it('Should get a `{}` content', async () => {\n    const client = hc<AppType>('http://localhost')\n    const res = await client['safe-bigint'].$get()\n    const data = await res.json()\n    expectTypeOf(data).toMatchTypeOf<{ value: string }>()\n    expect(data).toStrictEqual({ value: '42n' })\n  })\n\n  it('Should get an error response', async () => {\n    const client = hc<AppType>('http://localhost')\n    const res = await client.bigint.$get()\n    const data = await res.json()\n    expectTypeOf(data).toMatchTypeOf<never>()\n    expect(res.status).toBe(500)\n    expect(data).toMatchObject({\n      message: 'Do not know how to serialize a BigInt',\n      name: 'TypeError',\n    })\n  })\n\n  it('Should have correct types - primitives', async () => {\n    const app = new Hono()\n    const route = app\n      .get('/api/string', (c) => c.json('a-string'))\n      .get('/api/number', (c) => c.json(37))\n      .get('/api/boolean', (c) => c.json(true))\n      .get('/api/generic', (c) =>\n        c.json(Math.random() > 0.5 ? Boolean(Math.random()) : Math.random())\n      )\n    type AppType = typeof route\n    const client = hc<AppType>('http://localhost')\n    const stringFetch = await client.api.string.$get()\n    const stringRes = await stringFetch.json()\n    const numberFetch = await client.api.number.$get()\n    const numberRes = await numberFetch.json()\n    const booleanFetch = await client.api.boolean.$get()\n    const booleanRes = await booleanFetch.json()\n    const genericFetch = await client.api.generic.$get()\n    const genericRes = await genericFetch.json()\n    type stringVerify = Expect<Equal<'a-string', typeof stringRes>>\n    expect(stringRes).toBe('a-string')\n    type numberVerify = Expect<Equal<37, typeof numberRes>>\n    expect(numberRes).toBe(37)\n    type booleanVerify = Expect<Equal<true, typeof booleanRes>>\n    expect(booleanRes).toBe(true)\n    type genericVerify = Expect<Equal<number | boolean, typeof genericRes>>\n    expect(typeof genericRes === 'number' || typeof genericRes === 'boolean').toBe(true)\n\n    // using .text() on json endpoint should return string\n    type textTest = Expect<Equal<Promise<string>, ReturnType<typeof genericFetch.text>>>\n  })\n})\n\ndescribe('Basic - query, queries, form, path params, header and cookie', () => {\n  const app = new Hono()\n\n  const route = app\n    .get(\n      '/search',\n      validator('query', () => {\n        return {} as { q: string; tag: string[]; filter: string }\n      }),\n      (c) => {\n        return c.json({\n          q: 'fake',\n          tag: ['fake'],\n          filter: 'fake',\n        })\n      }\n    )\n    .put(\n      '/posts/:id',\n      validator('form', () => {\n        return {\n          title: 'Hello',\n        }\n      }),\n      (c) => {\n        const data = c.req.valid('form')\n        return c.json(data)\n      }\n    )\n    .get(\n      '/header',\n      validator('header', () => {\n        return {\n          'x-message-id': 'Hello',\n        }\n      }),\n      (c) => {\n        const data = c.req.valid('header')\n        return c.json(data)\n      }\n    )\n    .get(\n      '/cookie',\n      validator('cookie', () => {\n        return {\n          hello: 'world',\n        }\n      }),\n      (c) => {\n        const data = c.req.valid('cookie')\n        return c.json(data)\n      }\n    )\n\n  const server = setupServer(\n    http.get('http://localhost/api/search', ({ request }) => {\n      const url = new URL(request.url)\n      const query = url.searchParams.get('q')\n      const tag = url.searchParams.getAll('tag')\n      const filter = url.searchParams.get('filter')\n      return HttpResponse.json({\n        q: query,\n        tag,\n        filter,\n      })\n    }),\n    http.get('http://localhost/api/posts', ({ request }) => {\n      const url = new URL(request.url)\n      const tags = url.searchParams.getAll('tags')\n      return HttpResponse.json({\n        tags: tags,\n      })\n    }),\n    http.put('http://localhost/api/posts/123', async ({ request }) => {\n      const buffer = await request.arrayBuffer()\n      // @ts-ignore\n      const string = String.fromCharCode.apply('', new Uint8Array(buffer))\n      return HttpResponse.text(string)\n    }),\n    http.get('http://localhost/api/header', async ({ request }) => {\n      const message = await request.headers.get('x-message-id')\n      return HttpResponse.json({ 'x-message-id': message })\n    }),\n    http.get('http://localhost/api/cookie', async ({ request }) => {\n      const obj = parse(request.headers.get('cookie') || '')\n      const value = obj['hello']\n      return HttpResponse.json({ hello: value })\n    })\n  )\n\n  beforeAll(() => server.listen())\n  afterEach(() => server.resetHandlers())\n  afterAll(() => server.close())\n\n  type AppType = typeof route\n\n  const client = hc<AppType>('http://localhost/api')\n\n  it('Should get 200 response - query', async () => {\n    const res = await client.search.$get({\n      query: {\n        q: 'foobar',\n        tag: ['a', 'b'],\n        // @ts-expect-error\n        filter: undefined,\n      },\n    })\n\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({\n      q: 'foobar',\n      tag: ['a', 'b'],\n      filter: null,\n    })\n  })\n\n  it('Should get 200 response - form, params', async () => {\n    const res = await client.posts[':id'].$put({\n      form: {\n        title: 'Good Night',\n      },\n      param: {\n        id: '123',\n      },\n    })\n\n    expect(res.status).toBe(200)\n    expect(await res.text()).toMatch('Good Night')\n  })\n\n  it('Should get 200 response - header', async () => {\n    const header = {\n      'x-message-id': 'Hello',\n    }\n    const res = await client.header.$get({\n      header,\n    })\n\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual(header)\n  })\n\n  it('Should get 200 response - cookie', async () => {\n    const cookie = {\n      hello: 'world',\n    }\n    const res = await client.cookie.$get({\n      cookie,\n    })\n\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual(cookie)\n  })\n})\n\ndescribe('Basic - $url()', () => {\n  const api = new Hono().get('/', (c) => c.text('API')).get('/posts/:id', (c) => c.text('Post'))\n  const content = new Hono().get(\n    '/search',\n    validator('query', () => {\n      return { page: '1', limit: '10' }\n    }),\n    (c) => c.text('Search')\n  )\n  const app = new Hono()\n    .get('/', (c) => c.text('Index'))\n    .route('/api', api)\n    .route('/content', content)\n\n  it('Should return a correct url via $url().href', async () => {\n    const client = hc<typeof app>('http://fake')\n    expect(client.index.$url().href).toBe('http://fake/')\n    expect(\n      client.index.$url({\n        query: {\n          page: '123',\n          limit: '20',\n        },\n      }).href\n    ).toBe('http://fake/?page=123&limit=20')\n    expect(client.api.$url().href).toBe('http://fake/api')\n    expect(\n      client.api.posts[':id'].$url({\n        param: {\n          id: '123',\n        },\n      }).href\n    ).toBe('http://fake/api/posts/123')\n    expect(\n      client.content.search.$url({\n        query: {\n          page: '123',\n          limit: '20',\n        },\n      }).href\n    ).toBe('http://fake/content/search?page=123&limit=20')\n  })\n\n  it.each(['http://fake', 'http://fake/', 'http://fake//', 'http://fake/api'])(\n    'Should return a correct path via $path() regardless of %s',\n    async (baseURL) => {\n      const client = hc<typeof app>(baseURL)\n      expect(client.index.$path()).toBe('/')\n      expect(\n        client.index.$path({\n          query: {\n            page: '123',\n            limit: '20',\n          },\n        })\n      ).toBe('/?page=123&limit=20')\n      expect(client.api.$path()).toBe('/api')\n      expect(\n        client.api.posts[':id'].$path({\n          param: {\n            id: '123',\n          },\n        })\n      ).toBe('/api/posts/123')\n      expect(\n        client.content.search.$path({\n          query: {\n            page: '123',\n            limit: '20',\n          },\n        })\n      ).toBe('/content/search?page=123&limit=20')\n    }\n  )\n})\n\ndescribe('Form - Multiple Values', () => {\n  const server = setupServer(\n    http.post('http://localhost/multiple-values', async ({ request }) => {\n      const data = await request.formData()\n      return HttpResponse.json(data.getAll('key'))\n    })\n  )\n\n  beforeAll(() => server.listen())\n  afterEach(() => server.resetHandlers())\n  afterAll(() => server.close())\n\n  const client = hc('http://localhost/')\n\n  it('Should get 200 response - query', async () => {\n    // @ts-expect-error `client['multiple-values'].$post` is not typed\n    const res = await client['multiple-values'].$post({\n      form: {\n        key: ['foo', 'bar'],\n      },\n    })\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual(['foo', 'bar'])\n  })\n})\n\ndescribe('Form - Undefined Values', () => {\n  const server = setupServer(\n    http.post('http://localhost/form-undefined', async ({ request }) => {\n      const data = await request.formData()\n      return HttpResponse.json({\n        keys: [...data.keys()],\n        title: data.get('title'),\n        optional: data.get('optional'),\n      })\n    })\n  )\n\n  beforeAll(() => server.listen())\n  afterEach(() => server.resetHandlers())\n  afterAll(() => server.close())\n\n  const client = hc('http://localhost/')\n\n  it('Should skip undefined values in form data', async () => {\n    // @ts-expect-error `client['form-undefined'].$post` is not typed\n    const res = await client['form-undefined'].$post({\n      form: {\n        title: 'Hello',\n        optional: undefined,\n      },\n    })\n    expect(res.status).toBe(200)\n    const json = await res.json()\n    expect(json).toEqual({\n      keys: ['title'],\n      title: 'Hello',\n      optional: null,\n    })\n  })\n})\n\ndescribe('Infer the response/request type', () => {\n  const app = new Hono()\n  const route = app.get(\n    '/',\n    validator('query', () => {\n      return {\n        name: 'dummy',\n        age: 'dummy',\n      }\n    }),\n    validator('header', () => {\n      return {\n        'x-request-id': 'dummy',\n      }\n    }),\n    validator('cookie', () => {\n      return {\n        name: 'dummy',\n      }\n    }),\n    (c) =>\n      c.json({\n        id: 123,\n        title: 'Morning!',\n      })\n  )\n\n  type AppType = typeof route\n\n  it('Should infer response type the type correctly', () => {\n    const client = hc<AppType>('/')\n    const req = client.index.$get\n\n    type Actual = InferResponseType<typeof req>\n    type Expected = {\n      id: number\n      title: string\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  it('Should infer request type the type correctly', () => {\n    const client = hc<AppType>('/')\n    const req = client.index.$get\n\n    type Actual = InferRequestType<typeof req>\n    type Expected = {\n      age: string | string[]\n      name: string | string[]\n    }\n    type verify = Expect<Equal<Expected, Actual['query']>>\n  })\n\n  it('Should infer request header type the type correctly', () => {\n    const client = hc<AppType>('/')\n    const req = client.index.$get\n    type c = typeof req\n\n    type Actual = InferRequestType<c>\n    type Expected = {\n      'x-request-id': string\n    }\n    type verify = Expect<Equal<Expected, Actual['header']>>\n  })\n\n  it('Should infer request cookie type the type correctly', () => {\n    const client = hc<AppType>('/')\n    const req = client.index.$get\n    type c = typeof req\n\n    type Actual = InferRequestType<c>\n    type Expected = {\n      name: string\n    }\n    type verify = Expect<Equal<Expected, Actual['cookie']>>\n  })\n\n  describe('Without input', () => {\n    const route = app.get('/', (c) => c.json({ ok: true }))\n    type AppType = typeof route\n\n    it('Should infer response type the type correctly', () => {\n      const client = hc<AppType>('/')\n      const req = client.index.$get\n\n      type Actual = InferResponseType<typeof req>\n      type Expected = { ok: true }\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n\n    it('Should infer request type the type correctly', () => {\n      const client = hc<AppType>('/')\n      const req = client.index.$get\n\n      type Actual = InferRequestType<typeof req>\n      type Expected = {}\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n  })\n})\n\ndescribe('Merge path with `app.route()`', () => {\n  const server = setupServer(\n    http.get('http://localhost/api/search', async () => {\n      return HttpResponse.json({\n        ok: true,\n      })\n    }),\n    http.get('http://localhost/api/searchArray', async () => {\n      return HttpResponse.json([\n        {\n          ok: true,\n        },\n      ])\n    }),\n    http.get('http://localhost/api/foo', async () => {\n      return HttpResponse.json({\n        ok: true,\n      })\n    }),\n    http.post('http://localhost/api/bar', async () => {\n      return HttpResponse.json({\n        ok: true,\n      })\n    }),\n    http.get('http://localhost/v1/book', async () => {\n      return HttpResponse.json({\n        ok: true,\n      })\n    })\n  )\n\n  beforeAll(() => server.listen())\n  afterEach(() => server.resetHandlers())\n  afterAll(() => server.close())\n\n  type Env = {\n    Bindings: {\n      TOKEN: string\n    }\n  }\n\n  it('Should have correct types', async () => {\n    const api = new Hono<Env>().get('/search', (c) => c.json({ ok: true }))\n    const app = new Hono<Env>().route('/api', api)\n    type AppType = typeof app\n    const client = hc<AppType>('http://localhost')\n    const res = await client.api.search.$get()\n    const data = await res.json()\n    type verify = Expect<Equal<true, typeof data.ok>>\n    expect(data.ok).toBe(true)\n  })\n\n  it('Should have correct types - basePath() then get()', async () => {\n    const base = new Hono<Env>().basePath('/api')\n    const app = base.get('/search', (c) => c.json({ ok: true }))\n    type AppType = typeof app\n    const client = hc<AppType>('http://localhost')\n    const res = await client.api.search.$get()\n    const data = await res.json()\n    type verify = Expect<Equal<true, typeof data.ok>>\n    expect(data.ok).toBe(true)\n  })\n\n  it('Should have correct types - basePath(), route(), get()', async () => {\n    const book = new Hono().get('/', (c) => c.json({ ok: true }))\n    const app = new Hono().basePath('/v1').route('/book', book)\n    type AppType = typeof app\n    const client = hc<AppType>('http://localhost')\n    const res = await client.v1.book.$get()\n    const data = await res.json()\n    type verify = Expect<Equal<true, typeof data.ok>>\n    expect(data.ok).toBe(true)\n  })\n\n  it('Should have correct types - with interface', async () => {\n    interface Result {\n      ok: boolean\n      okUndefined?: boolean\n    }\n    const result: Result = { ok: true }\n    const base = new Hono<Env>().basePath('/api')\n    const app = base.get('/search', (c) => c.json(result))\n    type AppType = typeof app\n    const client = hc<AppType>('http://localhost')\n    const res = await client.api.search.$get()\n    const data = await res.json()\n    type verify = Expect<Equal<Result, typeof data>>\n    expect(data.ok).toBe(true)\n\n    // A few more types only tests\n    interface DeepInterface {\n      l2: {\n        l3: Result\n      }\n    }\n    interface ExtraDeepInterface {\n      l4: DeepInterface\n    }\n    type verifyDeepInterface = Expect<\n      Equal<SimplifyDeepArray<DeepInterface> extends JSONValue ? true : false, true>\n    >\n    type verifyExtraDeepInterface = Expect<\n      Equal<SimplifyDeepArray<ExtraDeepInterface> extends JSONValue ? true : false, true>\n    >\n  })\n\n  it('Should have correct types - with array of interfaces', async () => {\n    interface Result {\n      ok: boolean\n      okUndefined?: boolean\n    }\n    type Results = Result[]\n\n    const results: Results = [{ ok: true }]\n    const base = new Hono<Env>().basePath('/api')\n    const app = base.get('/searchArray', (c) => c.json(results))\n    type AppType = typeof app\n    const client = hc<AppType>('http://localhost')\n    const res = await client.api.searchArray.$get()\n    const data = await res.json()\n    type verify = Expect<Equal<Results, typeof data>>\n    expect(data[0].ok).toBe(true)\n\n    // A few more types only tests\n    type verifyNestedArrayTyped = Expect<\n      Equal<SimplifyDeepArray<[string, Results]> extends JSONValue ? true : false, true>\n    >\n    type verifyNestedArrayInterfaceArray = Expect<\n      Equal<SimplifyDeepArray<[string, Result[]]> extends JSONValue ? true : false, true>\n    >\n    type verifyExtraNestedArrayTyped = Expect<\n      Equal<SimplifyDeepArray<[string, Results[]]> extends JSONValue ? true : false, true>\n    >\n    type verifyExtraNestedArrayInterfaceArray = Expect<\n      Equal<SimplifyDeepArray<[string, Result[][]]> extends JSONValue ? true : false, true>\n    >\n  })\n\n  it('Should allow a Date object and return it as a string', async () => {\n    const app = new Hono()\n    const route = app.get('/api/foo', (c) => c.json({ datetime: new Date() }))\n    type AppType = typeof route\n    const client = hc<AppType>('http://localhost')\n    const res = await client.api.foo.$get()\n    const { datetime } = await res.json()\n    type verify = Expect<Equal<string, typeof datetime>>\n  })\n\n  describe('Multiple endpoints', () => {\n    const api = new Hono()\n      .get('/foo', (c) => c.json({ foo: '' }))\n      .post('/bar', (c) => c.json({ bar: 0 }))\n    const app = new Hono().route('/api', api)\n    type AppType = typeof app\n    const client = hc<typeof app>('http://localhost')\n\n    it('Should return correct types - GET /api/foo', async () => {\n      const res = await client.api.foo.$get()\n      const data = await res.json()\n      type verify = Expect<Equal<string, typeof data.foo>>\n    })\n\n    it('Should return correct types - POST /api/bar', async () => {\n      const res = await client.api.bar.$post()\n      const data = await res.json()\n      type verify = Expect<Equal<number, typeof data.bar>>\n    })\n    it('Should work with $url', async () => {\n      const url = client.api.bar.$url()\n      expect(url.href).toBe('http://localhost/api/bar')\n    })\n    it('Should work with $path', async () => {\n      const path = client.api.bar.$path()\n      expect(path).toBe('/api/bar')\n    })\n  })\n\n  describe('With a blank path', () => {\n    const app = new Hono().basePath('/api/v1')\n    const routes = app.route(\n      '/me',\n      new Hono().route(\n        '',\n        new Hono().get('', async (c) => {\n          return c.json({ name: 'hono' })\n        })\n      )\n    )\n    const client = hc<typeof routes>('http://localhost')\n\n    it('Should infer paths correctly', async () => {\n      // Should not a throw type error\n      const url = client.api.v1.me.$url()\n      expectTypeOf<URL>(url)\n      expect(url.href).toBe('http://localhost/api/v1/me')\n\n      const path = client.api.v1.me.$path()\n      expectTypeOf<'/api/v1/me'>(path)\n      expect(path).toBe('/api/v1/me')\n    })\n  })\n\n  describe('With endpoint pathname', () => {\n    const app = new Hono().basePath('/api/v1')\n    const routes = app.route(\n      '/me',\n      new Hono().route(\n        '',\n        new Hono().get('', async (c) => {\n          return c.json({ name: 'hono' })\n        })\n      )\n    )\n    const client = hc<typeof routes>('http://localhost/proxy')\n\n    it('Should infer paths correctly', async () => {\n      // Should not a throw type error\n      const url = client.api.v1.me.$url()\n      expectTypeOf<URL>(url)\n      expect(url.href).toBe('http://localhost/proxy/api/v1/me')\n\n      const path = client.api.v1.me.$path()\n      expectTypeOf<'/api/v1/me'>(path)\n      expect(path).toBe('/api/v1/me')\n    })\n  })\n})\n\ndescribe('Use custom fetch method', () => {\n  it('Should call the custom fetch method when provided', async () => {\n    const fetchMock = vi.fn()\n\n    const api = new Hono().get('/search', (c) => c.json({ ok: true }))\n    const app = new Hono().route('/api', api)\n    type AppType = typeof app\n    const client = hc<AppType>('http://localhost', { fetch: fetchMock })\n    await client.api.search.$get()\n    expect(fetchMock).toHaveBeenCalledTimes(1)\n  })\n\n  it('Should return Response from custom fetch method', async () => {\n    const fetchMock = vi.fn()\n    const returnValue = new Response(null, { status: 200 })\n    fetchMock.mockReturnValueOnce(returnValue)\n\n    const api = new Hono().get('/search', (c) => c.json({ ok: true }))\n    const app = new Hono().route('/api', api)\n    type AppType = typeof app\n    const client = hc<AppType>('http://localhost', { fetch: fetchMock })\n    const res = await client.api.search.$get()\n    expect(res.ok).toBe(true)\n    expect(res).toEqual(returnValue)\n  })\n})\n\ndescribe('Use custom fetch (app.request) method', () => {\n  it('Should return Response from app request method', async () => {\n    const app = new Hono().get('/search', (c) => c.json({ ok: true }))\n    type AppType = typeof app\n    const client = hc<AppType>('', { fetch: app.request })\n    const res = await client.search.$get()\n    expect(res.ok).toBe(true)\n  })\n})\n\ndescribe('Optional parameters in JSON response', () => {\n  it('Should return the correct type', async () => {\n    const app = new Hono().get('/', (c) => {\n      return c.json({ message: 'foo' } as { message?: string })\n    })\n    type AppType = typeof app\n    const client = hc<AppType>('', { fetch: app.request })\n    const res = await client.index.$get()\n    const data = await res.json()\n    expectTypeOf(data).toEqualTypeOf<{\n      message?: string\n    }>()\n  })\n})\n\ndescribe('ClientResponse<T>.json() returns a Union type correctly', () => {\n  const condition = () => true\n  const app = new Hono().get('/', async (c) => {\n    const ok = condition()\n    if (ok) {\n      return c.json({ data: 'foo' })\n    }\n    return c.json({ message: 'error' })\n  })\n\n  const client = hc<typeof app>('', { fetch: app.request })\n  it('Should be a Union type', async () => {\n    const res = await client.index.$get()\n    const json = await res.json()\n    expectTypeOf(json).toEqualTypeOf<{ data: string } | { message: string }>()\n  })\n})\n\ndescribe('Response with different status codes', () => {\n  const condition = () => true\n  const app = new Hono().get('/', async (c) => {\n    const ok = condition()\n    if (ok) {\n      return c.json({ data: 'foo' }, 200)\n    }\n    if (!ok) {\n      return c.json({ message: 'error' }, 400)\n    }\n    return c.json(null)\n  })\n\n  const client = hc<typeof app>('', { fetch: app.request })\n\n  it('all', async () => {\n    const res = await client.index.$get()\n    const json = await res.json()\n    expectTypeOf(json).toEqualTypeOf<{ data: string } | { message: string } | null>()\n  })\n\n  it('status 200', async () => {\n    const res = await client.index.$get()\n    if (res.status === 200) {\n      const json = await res.json()\n      expectTypeOf(json).toEqualTypeOf<{ data: string } | null>()\n    }\n  })\n\n  it('status 400', async () => {\n    const res = await client.index.$get()\n    if (res.status === 400) {\n      const json = await res.json()\n      expectTypeOf(json).toEqualTypeOf<{ message: string } | null>()\n    }\n  })\n\n  it('response is ok', async () => {\n    const res = await client.index.$get()\n    if (res.ok) {\n      const json = await res.json()\n      expectTypeOf(json).toEqualTypeOf<{ data: string } | null>()\n    }\n  })\n\n  it('response is not ok', async () => {\n    const res = await client.index.$get()\n    if (!res.ok) {\n      const json = await res.json()\n      expectTypeOf(json).toEqualTypeOf<{ message: string } | null>()\n    }\n  })\n})\n\ndescribe('Infer the response type with different status codes', () => {\n  const condition = () => true\n  const app = new Hono().get('/', async (c) => {\n    const ok = condition()\n    if (ok) {\n      return c.json({ data: 'foo' }, 200)\n    }\n    if (!ok) {\n      return c.json({ message: 'error' }, 400)\n    }\n    return c.json(null)\n  })\n\n  const client = hc<typeof app>('', { fetch: app.request })\n\n  it('Should infer response type correctly', () => {\n    const req = client.index.$get\n\n    type Actual = InferResponseType<typeof req>\n    type Expected =\n      | {\n          data: string\n        }\n      | {\n          message: string\n        }\n      | null\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  it('Should infer response type of status 200 correctly', () => {\n    const req = client.index.$get\n\n    type Actual = InferResponseType<typeof req, 200>\n    type Expected = {\n      data: string\n    } | null\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n})\n\ndescribe('Infer the response types from middlewares', () => {\n  const app = new Hono()\n    .get(\n      '/',\n      validator('query', (input, c) => {\n        if (!input.page || typeof input.page !== 'string') {\n          return c.json({ error: 'Bad request' as const }, 400)\n        }\n\n        return input as { page: string }\n      }),\n      async (c) => {\n        const query = c.req.valid('query')\n        return c.json({ data: 'foo', page: query.page }, 200)\n      }\n    )\n    .post(\n      '/posts',\n      async (c, next) => {\n        const auth = c.req.header('authorization')\n        if (!auth || !auth.startsWith('Bearer ')) {\n          return c.json({ error: 'Unauthorized' as const }, 401)\n        }\n        return next()\n      },\n      validator('json', (input, c) => {\n        if (!input.title) {\n          return c.json({ error: 'Bad request' as const }, 400)\n        }\n\n        return input as { title: string }\n      }),\n      (c) => {\n        const data = c.req.valid('json')\n        return c.json(data, 200)\n      }\n    )\n\n  type AppType = typeof app\n  const client = hc<AppType>('', { fetch: app.request })\n\n  it('Should infer response type of status 200 correctly', () => {\n    const req = client.posts.$post\n\n    type Actual = InferResponseType<typeof req, 200>\n    type Expected = {\n      title: string\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  it('Should infer response type of status 400 correctly', () => {\n    const req = client.posts.$post\n\n    type Actual = InferResponseType<typeof req, 400>\n    type Expected = {\n      error: 'Bad request'\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  it('Should infer response type of status 401 correctly', () => {\n    const req = client.posts.$post\n\n    type Actual = InferResponseType<typeof req, 401>\n    type Expected = {\n      error: 'Unauthorized'\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  it('Should infer all possible response statuses', async () => {\n    const req = await client.posts.$post({\n      json: {\n        title: 'hello',\n      },\n    })\n\n    type Actual = typeof req.status\n    type Expected = 200 | 400 | 401\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  it('Should properly assign response to corresponding status', async () => {\n    const req = await client.posts.$post({\n      json: {\n        title: 'hello',\n      },\n    })\n\n    if (req.status === 200) {\n      const data = await req.json()\n\n      expectTypeOf(data).toEqualTypeOf<{ title: string }>()\n    } else if (req.status === 400) {\n      const data = await req.json()\n\n      expectTypeOf(data).toEqualTypeOf<{ error: 'Bad request' }>()\n    } else if (req.status === 401) {\n      const data = await req.json()\n\n      expectTypeOf(data).toEqualTypeOf<{ error: 'Unauthorized' }>()\n    }\n  })\n})\n\nconst pathname = <T extends URL | string>(value: T): string =>\n  value instanceof URL ? value.pathname : value\n\ndescribe.each(['$path', '$url'] as const)('%s() with a param option', (cmd) => {\n  const app = new Hono()\n    .get('/posts/:id/comments', (c) => c.json({ ok: true }))\n    .get('/something/:firstId/:secondId/:version?', (c) => c.json({ ok: true }))\n  type AppType = typeof app\n  const client = hc<AppType>('http://localhost')\n\n  it('Should return the correct url path - /posts/123/comments', async () => {\n    const value = client.posts[':id'].comments[cmd]({\n      param: {\n        id: '123',\n      },\n    })\n    expect(pathname(value)).toBe('/posts/123/comments')\n  })\n\n  it('Should return the correct path - /posts/:id/comments', async () => {\n    const value = client.posts[':id'].comments[cmd]()\n    expect(pathname(value)).toBe('/posts/:id/comments')\n  })\n\n  it('Should return the correct path - /something/123/456', async () => {\n    const value = client.something[':firstId'][':secondId'][':version?'][cmd]({\n      param: {\n        firstId: '123',\n        secondId: '456',\n        version: undefined,\n      },\n    })\n    expect(pathname(value)).toBe('/something/123/456')\n  })\n})\n\ndescribe('$url() / $path() with a query option', () => {\n  const app = new Hono().get(\n    '/posts',\n    validator('query', () => {\n      return {} as { filter: 'test' }\n    }),\n    (c) => c.json({ ok: true })\n  )\n  type AppType = typeof app\n  const client = hc<AppType>('http://localhost')\n\n  it('Should return the correct path - /posts?filter=test', async () => {\n    const url = client.posts.$url({\n      query: {\n        filter: 'test',\n      },\n    })\n    expect(url.search).toBe('?filter=test')\n\n    const path = client.posts.$path({\n      query: {\n        filter: 'test',\n      },\n    })\n    expect(path).toBe('/posts?filter=test')\n  })\n})\n\ndescribe('Client can be awaited', () => {\n  it('Can be awaited without side effects', async () => {\n    const client = hc('http://localhost')\n\n    const awaited = await client\n\n    expect(awaited).toEqual(client)\n  })\n})\n\ndescribe('Dynamic headers', () => {\n  const app = new Hono()\n\n  const route = app.post('/posts', (c) => {\n    return c.json({\n      requestDynamic: 'dummy',\n    })\n  })\n\n  type AppType = typeof route\n\n  const server = setupServer(\n    http.post('http://localhost/posts', async ({ request }) => {\n      const requestDynamic = request.headers.get('x-dynamic')\n      const payload = {\n        requestDynamic,\n      }\n      return HttpResponse.json(payload)\n    })\n  )\n\n  beforeAll(() => server.listen())\n  afterEach(() => server.resetHandlers())\n  afterAll(() => server.close())\n\n  let dynamic = ''\n\n  const client = hc<AppType>('http://localhost', {\n    headers: () => ({ 'x-hono': 'hono', 'x-dynamic': dynamic }),\n  })\n\n  it('Should have \"x-dynamic\": \"one\"', async () => {\n    dynamic = 'one'\n\n    const res = await client.posts.$post()\n\n    expect(res.ok).toBe(true)\n    const data = await res.json()\n    expect(data.requestDynamic).toEqual('one')\n  })\n\n  it('Should have \"x-dynamic\": \"two\"', async () => {\n    dynamic = 'two'\n\n    const res = await client.posts.$post()\n\n    expect(res.ok).toBe(true)\n    const data = await res.json()\n    expect(data.requestDynamic).toEqual('two')\n  })\n})\n\ndescribe('RequestInit work as expected', () => {\n  const app = new Hono()\n\n  const route = app\n    .get('/credentials', (c) => {\n      return c.text('' as RequestCredentials)\n    })\n    .get('/headers', (c) => {\n      return c.json({} as Record<string, string>)\n    })\n    .post('/headers', (c) => c.text('Not found', 404))\n\n  type AppType = typeof route\n\n  const server = setupServer(\n    http.get('http://localhost/credentials', ({ request }) => {\n      return HttpResponse.text(request.credentials)\n    }),\n    http.get('http://localhost/headers', ({ request }) => {\n      const allHeaders: Record<string, string> = {}\n      for (const [k, v] of request.headers.entries()) {\n        allHeaders[k] = v\n      }\n\n      return HttpResponse.json(allHeaders)\n    }),\n    http.post('http://localhost/headers', () => {\n      return HttpResponse.text('Should not be here', {\n        status: 400,\n      })\n    })\n  )\n\n  beforeAll(() => server.listen())\n  afterEach(() => server.resetHandlers())\n  afterAll(() => server.close())\n\n  const client = hc<AppType>('http://localhost', {\n    headers: { 'x-hono': 'fire' },\n    init: {\n      credentials: 'include',\n    },\n  })\n\n  it('Should overwrite method and fail', async () => {\n    const res = await client.headers.$get(undefined, { init: { method: 'POST' } })\n\n    expect(res.ok).toBe(false)\n  })\n\n  it('Should clear headers', async () => {\n    const res = await client.headers.$get(undefined, { init: { headers: undefined } })\n\n    expect(res.ok).toBe(true)\n    const data = await res.json()\n    expect(data).toEqual({})\n  })\n\n  it('Should overwrite headers', async () => {\n    const res = await client.headers.$get(undefined, {\n      init: { headers: new Headers({ 'x-hono': 'awesome' }) },\n    })\n\n    expect(res.ok).toBe(true)\n    const data = await res.json()\n    expect(data).toEqual({ 'x-hono': 'awesome' })\n  })\n\n  it('credentials is include', async () => {\n    const res = await client.credentials.$get()\n\n    expect(res.ok).toBe(true)\n    const data = await res.text()\n    expect(data).toEqual('include')\n  })\n\n  it('deepMerge should works and not unset credentials', async () => {\n    const res = await client.credentials.$get(undefined, { init: { headers: { hi: 'hello' } } })\n\n    expect(res.ok).toBe(true)\n    const data = await res.text()\n    expect(data).toEqual('include')\n  })\n\n  it('Should unset credentials', async () => {\n    const res = await client.credentials.$get(undefined, { init: { credentials: undefined } })\n\n    expect(res.ok).toBe(true)\n    const data = await res.text()\n    expect(data).toEqual('same-origin')\n  })\n})\n\ndescribe('WebSocket URL Protocol Translation', () => {\n  const app = new Hono()\n  const route = app.get(\n    '/',\n    upgradeWebSocket((c) => ({\n      onMessage(event, ws) {\n        console.log(`Message from client: ${event.data}`)\n        ws.send('Hello from server!')\n      },\n      onClose: () => {\n        console.log('Connection closed')\n      },\n    }))\n  )\n\n  type AppType = typeof route\n\n  const server = setupServer()\n  const webSocketMock = vi.fn()\n\n  beforeAll(() => server.listen())\n  beforeEach(() => {\n    vi.stubGlobal('WebSocket', webSocketMock)\n  })\n  afterEach(() => {\n    vi.clearAllMocks()\n    server.resetHandlers()\n  })\n  afterAll(() => server.close())\n\n  it('Translates HTTP to ws', async () => {\n    const client = hc<AppType>('http://localhost')\n    client.index.$ws()\n    expect(webSocketMock).toHaveBeenCalledWith('ws://localhost/index')\n  })\n\n  it('Translates HTTPS to wss', async () => {\n    const client = hc<AppType>('https://localhost')\n    client.index.$ws()\n    expect(webSocketMock).toHaveBeenCalledWith('wss://localhost/index')\n  })\n\n  it('Keeps ws unchanged', async () => {\n    const client = hc<AppType>('ws://localhost')\n    client.index.$ws()\n    expect(webSocketMock).toHaveBeenCalledWith('ws://localhost/index')\n  })\n\n  it('Keeps wss unchanged', async () => {\n    const client = hc<AppType>('wss://localhost')\n    client.index.$ws()\n    expect(webSocketMock).toHaveBeenCalledWith('wss://localhost/index')\n  })\n})\n\ndescribe('WebSocket URL Protocol Translation with Query Parameters', () => {\n  const app = new Hono()\n  const route = app.get(\n    '/',\n    upgradeWebSocket((c) => ({\n      onMessage(event, ws) {\n        ws.send('Hello from server!')\n      },\n      onClose: () => {\n        console.log('Connection closed')\n      },\n    }))\n  )\n\n  type AppType = typeof route\n\n  const server = setupServer()\n  const webSocketMock = vi.fn()\n\n  beforeAll(() => server.listen())\n  beforeEach(() => {\n    vi.stubGlobal('WebSocket', webSocketMock)\n  })\n  afterEach(() => {\n    vi.clearAllMocks()\n    server.resetHandlers()\n  })\n  afterAll(() => server.close())\n\n  it('Translates HTTP to ws and includes query parameters', async () => {\n    const client = hc<AppType>('http://localhost')\n    client.index.$ws({\n      query: {\n        id: '123',\n        type: 'test',\n        tag: ['a', 'b'],\n      },\n    })\n    expect(webSocketMock).toHaveBeenCalledWith('ws://localhost/index?id=123&type=test&tag=a&tag=b')\n  })\n\n  it('Translates HTTPS to wss and includes query parameters', async () => {\n    const client = hc<AppType>('https://localhost')\n    client.index.$ws({\n      query: {\n        id: '456',\n        type: 'secure',\n      },\n    })\n    expect(webSocketMock).toHaveBeenCalledWith('wss://localhost/index?id=456&type=secure')\n  })\n\n  it('Keeps ws unchanged and includes query parameters', async () => {\n    const client = hc<AppType>('ws://localhost')\n    client.index.$ws({\n      query: {\n        id: '789',\n        type: 'plain',\n      },\n    })\n    expect(webSocketMock).toHaveBeenCalledWith('ws://localhost/index?id=789&type=plain')\n  })\n\n  it('Keeps wss unchanged and includes query parameters', async () => {\n    const client = hc<AppType>('wss://localhost')\n    client.index.$ws({\n      query: {\n        id: '1011',\n        type: 'secure',\n      },\n    })\n    expect(webSocketMock).toHaveBeenCalledWith('wss://localhost/index?id=1011&type=secure')\n  })\n})\n\ndescribe('Client can be console.log in react native', () => {\n  it('Returns a function name with function.name.toString', async () => {\n    const client = hc('http://localhost')\n    // @ts-ignore\n    expect(client.posts.name.toString()).toEqual('posts')\n  })\n\n  it('Returns a function name with function.name.valueOf', async () => {\n    const client = hc('http://localhost')\n    // @ts-ignore\n    expect(client.posts.name.valueOf()).toEqual('posts')\n  })\n\n  it('Returns a function with function.valueOf', async () => {\n    const client = hc('http://localhost')\n    expect(typeof client.posts.valueOf()).toEqual('function')\n  })\n\n  it('Returns a function source with function.toString', async () => {\n    const client = hc('http://localhost')\n    expect(client.posts.toString()).toMatch('function proxyCallback')\n  })\n})\n\ndescribe('Text response', () => {\n  const text = 'My name is Hono'\n  const obj = { ok: true }\n  const server = setupServer(\n    http.get('http://localhost/about/me', async () => {\n      return HttpResponse.text(text)\n    }),\n    http.get('http://localhost/api', async ({ request }) => {\n      return HttpResponse.json(obj)\n    })\n  )\n\n  beforeAll(() => server.listen())\n  afterEach(() => server.resetHandlers())\n  afterAll(() => server.close())\n\n  const app = new Hono().get('/about/me', (c) => c.text(text)).get('/api', (c) => c.json(obj))\n  const client = hc<typeof app>('http://localhost/')\n\n  it('Should be never with res.json() - /about/me', async () => {\n    const res = await client.about.me.$get()\n    type Actual = ReturnType<typeof res.json>\n    type Expected = Promise<never>\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  it('Should be \"Hello, World!\" with res.text() - /about/me', async () => {\n    const res = await client.about.me.$get()\n    const data = await res.text()\n    expectTypeOf(data).toEqualTypeOf<'My name is Hono'>()\n    expect(data).toBe(text)\n  })\n\n  /**\n   * Also check the type of JSON response with res.text().\n   */\n  it('Should be string with res.text() - /api', async () => {\n    const res = await client.api.$get()\n    type Actual = ReturnType<typeof res.text>\n    type Expected = Promise<string>\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n})\n\ndescribe('Redirect response - only types', () => {\n  const server = setupServer(\n    http.get('http://localhost/', async () => {\n      return HttpResponse.redirect('/')\n    })\n  )\n\n  beforeAll(() => server.listen())\n  afterEach(() => server.resetHandlers())\n  afterAll(() => server.close())\n\n  const condition = () => true\n  const app = new Hono().get('/', async (c) => {\n    const ok = condition()\n    const temporary = condition()\n    if (ok) {\n      return c.json({ ok: true }, 200)\n    }\n    if (temporary) {\n      return c.redirect('/302')\n    }\n    return c.redirect('/301', 301)\n  })\n\n  const client = hc<typeof app>('http://localhost/')\n  const req = client.index.$get\n\n  it('Should infer request type the type correctly', () => {\n    type Actual = InferResponseType<typeof req>\n    type Expected =\n      | {\n          ok: true\n        }\n      | undefined\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  it('Should infer response type correctly', async () => {\n    const res = await req()\n    if (res.ok) {\n      const data = await res.json()\n      expectTypeOf(data).toMatchTypeOf({ ok: true })\n    }\n    if (res.status === 301) {\n      type Expected = ClientResponse<undefined, 301, 'redirect'>\n      type verify = Expect<Equal<Expected, typeof res>>\n    }\n    if (res.status === 302) {\n      type Expected = ClientResponse<undefined, 302, 'redirect'>\n      type verify = Expect<Equal<Expected, typeof res>>\n    }\n  })\n})\n\ndescribe('WebSocket Provider Integration', () => {\n  const app = new Hono()\n  const route = app.get(\n    '/',\n    upgradeWebSocket((c) => ({\n      onMessage(event, ws) {\n        ws.send('Hello from server!')\n      },\n      onClose() {\n        console.log('Connection closed')\n      },\n    }))\n  )\n\n  type AppType = typeof route\n\n  const server = setupServer()\n  beforeAll(() => server.listen())\n  afterEach(() => {\n    vi.clearAllMocks()\n    server.resetHandlers()\n  })\n  afterAll(() => server.close())\n\n  it.each([\n    {\n      description: 'should initialize the WebSocket provider correctly',\n      url: 'http://localhost',\n      query: undefined,\n      expectedUrl: 'ws://localhost/index',\n    },\n    {\n      description: 'should correctly add query parameters to the WebSocket URL',\n      url: 'http://localhost',\n      query: { id: '123', type: 'test', tag: ['a', 'b'] },\n      expectedUrl: 'ws://localhost/index?id=123&type=test&tag=a&tag=b',\n    },\n  ])('$description', ({ url, expectedUrl, query }) => {\n    const webSocketMock = vi.fn()\n    const client = hc<AppType>(url, {\n      webSocket(url, options) {\n        return webSocketMock(url, options)\n      },\n    })\n    client.index.$ws({ query })\n    expect(webSocketMock).toHaveBeenCalledWith(expectedUrl, undefined)\n  })\n})\n\ndescribe('Custom buildSearchParams', () => {\n  const app = new Hono()\n  const route = app.get(\n    '/search',\n    validator('query', () => {\n      return {} as { q: string; tags: string[] }\n    }),\n    (c) => {\n      return c.json({\n        message: 'success',\n        queryString: '',\n      })\n    }\n  )\n\n  type AppType = typeof route\n\n  const server = setupServer(\n    http.get('http://localhost/search', ({ request }) => {\n      const url = new URL(request.url)\n      return HttpResponse.json({\n        message: 'success',\n        queryString: url.search,\n      })\n    })\n  )\n\n  beforeAll(() => server.listen())\n  afterEach(() => server.resetHandlers())\n  afterAll(() => server.close())\n\n  // Custom buildSearchParams that uses bracket notation for arrays (key[]=value)\n  const customBuildSearchParams = (query: Record<string, string | string[]>) => {\n    const searchParams = new URLSearchParams()\n    for (const [k, v] of Object.entries(query)) {\n      if (v === undefined) {\n        continue\n      }\n      if (Array.isArray(v)) {\n        v.forEach((item) => searchParams.append(`${k}[]`, item))\n      } else {\n        searchParams.set(k, v)\n      }\n    }\n    return searchParams\n  }\n\n  it('Should use custom buildSearchParams for query serialization', async () => {\n    const client = hc<AppType>('http://localhost', { buildSearchParams: customBuildSearchParams })\n    const res = await client.search.$get({ query: { q: 'test', tags: ['tag1', 'tag2', 'tag3'] } })\n    const data = await res.json()\n\n    expect(res.status).toBe(200)\n    expect(data.queryString).toBe('?q=test&tags%5B%5D=tag1&tags%5B%5D=tag2&tags%5B%5D=tag3')\n  })\n\n  it('Should use default buildSearchParams when custom one is not provided', async () => {\n    const client = hc<AppType>('http://localhost')\n    const res = await client.search.$get({ query: { q: 'test', tags: ['tag1', 'tag2'] } })\n    const data = await res.json()\n\n    expect(res.status).toBe(200)\n    expect(data.queryString).toBe('?q=test&tags=tag1&tags=tag2')\n  })\n\n  it('Should use custom buildSearchParams in $url() method', () => {\n    const client = hc<AppType>('http://localhost', { buildSearchParams: customBuildSearchParams })\n    const url = client.search.$url({ query: { q: 'test', tags: ['tag1', 'tag2'] } })\n\n    expect(url.href).toBe('http://localhost/search?q=test&tags%5B%5D=tag1&tags%5B%5D=tag2')\n  })\n\n  it('Should use default buildSearchParams in $url() when custom one is not provided', () => {\n    const client = hc<AppType>('http://localhost')\n    const url = client.search.$url({ query: { q: 'test', tags: ['tag1', 'tag2'] } })\n\n    expect(url.href).toBe('http://localhost/search?q=test&tags=tag1&tags=tag2')\n  })\n})\n\ndescribe('ApplyGlobalResponse Type Helper', () => {\n  const server = setupServer(\n    http.get('http://localhost/api/users', () => {\n      return HttpResponse.json({ users: ['alice', 'bob'] })\n    }),\n    http.get('http://localhost/api/error', () => {\n      return HttpResponse.json(\n        { error: 'Internal Server Error', message: 'Something went wrong' },\n        { status: 500 }\n      )\n    }),\n    http.get('http://localhost/api/unauthorized', () => {\n      return HttpResponse.json({ error: 'Unauthorized', message: 'Please login' }, { status: 401 })\n    })\n  )\n\n  beforeAll(() => server.listen())\n  afterEach(() => server.resetHandlers())\n  afterAll(() => server.close())\n\n  it('Should add global error response types to all routes', () => {\n    // Use explicit status codes for proper type narrowing\n    const app = new Hono().get('/api/users', (c) => c.json({ users: ['alice', 'bob'] }, 200))\n\n    // Apply global error responses with new object syntax\n    type AppWithGlobalErrors = ApplyGlobalResponse<\n      typeof app,\n      {\n        401: { json: { error: string; message: string } }\n        500: { json: { error: string; message: string } }\n      }\n    >\n\n    const client = hc<AppWithGlobalErrors>('http://localhost')\n    const req = client.api.users.$get\n\n    // Type should be a union of normal response and global errors\n    type ResponseType = InferResponseType<typeof req>\n    type Expected = { users: string[] } | { error: string; message: string }\n\n    type verify = Expect<Equal<ResponseType, Expected>>\n  })\n\n  it('Should support multiple global error status codes', async () => {\n    const app = new Hono()\n      .get('/api/users', (c) => c.json({ users: ['alice', 'bob'] }, 200))\n      .get('/api/unauthorized', (c) =>\n        c.json({ error: 'Unauthorized', message: 'Please login' }, 401)\n      )\n      .get('/api/error', (c) =>\n        c.json({ error: 'Internal Server Error', message: 'Something went wrong' }, 500)\n      )\n\n    // Apply multiple global error types in one definition\n    type AppWithGlobalErrors = ApplyGlobalResponse<\n      typeof app,\n      {\n        401: { json: { error: string; message: string } }\n        500: { json: { error: string; message: string } }\n      }\n    >\n\n    const client = hc<AppWithGlobalErrors>('http://localhost')\n\n    // Verify runtime behavior for different status codes\n    const usersRes = await client.api.users.$get()\n    expect(usersRes.status).toBe(200)\n\n    const unauthorizedRes = await client.api.unauthorized.$get()\n    expect(unauthorizedRes.status).toBe(401)\n    expect(await unauthorizedRes.json()).toEqual({ error: 'Unauthorized', message: 'Please login' })\n\n    const errorRes = await client.api.error.$get()\n    expect(errorRes.status).toBe(500)\n    expect(await errorRes.json()).toEqual({\n      error: 'Internal Server Error',\n      message: 'Something went wrong',\n    })\n  })\n\n  it('Should work with onError handler pattern', () => {\n    // Simulating typical Hono app with onError handler\n    // Use explicit status code for proper type narrowing\n    const app = new Hono().get('/api/users', (c) => c.json({ users: ['alice', 'bob'] }, 200))\n\n    // In real app: app.onError((err, c) => c.json({ error: err.message }, 500))\n    type AppWithOnError = ApplyGlobalResponse<\n      typeof app,\n      {\n        500: { json: { error: string } }\n      }\n    >\n\n    const client = hc<AppWithOnError>('http://localhost')\n    const req = client.api.users.$get\n\n    // RPC client should know about the error format\n    type ResponseType = InferResponseType<typeof req>\n    type Expected = { users: string[] } | { error: string }\n\n    type verify = Expect<Equal<ResponseType, Expected>>\n  })\n\n  it('Should keep route() paths when global responses are applied', () => {\n    const users = new Hono().get('/users', (c) => c.json({ users: ['alice', 'bob'] }, 200))\n    const app = new Hono().route('/api', users)\n\n    type AppWithGlobalErrors = ApplyGlobalResponse<\n      typeof app,\n      {\n        500: { json: { error: string } }\n      }\n    >\n\n    const client = hc<AppWithGlobalErrors>('http://localhost')\n    const req = client.api.users.$get\n\n    type ResponseType = InferResponseType<typeof req>\n    type Expected = { users: string[] } | { error: string }\n\n    type verify = Expect<Equal<ResponseType, Expected>>\n  })\n})\n"
  },
  {
    "path": "src/client/client.ts",
    "content": "import type { Hono } from '../hono'\nimport type { FormValue, ValidationTargets } from '../types'\nimport { serialize } from '../utils/cookie'\nimport type { UnionToIntersection } from '../utils/types'\nimport type { BuildSearchParamsFn, Callback, Client, ClientRequestOptions } from './types'\nimport {\n  buildSearchParams,\n  deepMerge,\n  mergePath,\n  removeIndexString,\n  replaceUrlParam,\n  replaceUrlProtocol,\n} from './utils'\n\nconst createProxy = (callback: Callback, path: string[]) => {\n  const proxy: unknown = new Proxy(() => {}, {\n    get(_obj, key) {\n      if (typeof key !== 'string' || key === 'then') {\n        return undefined\n      }\n      return createProxy(callback, [...path, key])\n    },\n    apply(_1, _2, args) {\n      return callback({\n        path,\n        args,\n      })\n    },\n  })\n  return proxy\n}\n\nclass ClientRequestImpl {\n  private url: string\n  private method: string\n  private buildSearchParams: BuildSearchParamsFn\n  private queryParams: URLSearchParams | undefined = undefined\n  private pathParams: Record<string, string> = {}\n  private rBody: BodyInit | undefined\n  private cType: string | undefined = undefined\n\n  constructor(\n    url: string,\n    method: string,\n    options: {\n      buildSearchParams: BuildSearchParamsFn\n    }\n  ) {\n    this.url = url\n    this.method = method\n    this.buildSearchParams = options.buildSearchParams\n  }\n  fetch = async (\n    args?: ValidationTargets<FormValue> & {\n      param?: Record<string, string>\n    },\n    opt?: ClientRequestOptions\n  ) => {\n    if (args) {\n      if (args.query) {\n        this.queryParams = this.buildSearchParams(args.query)\n      }\n\n      if (args.form) {\n        const form = new FormData()\n        for (const [k, v] of Object.entries(args.form)) {\n          if (v === undefined) {\n            continue\n          }\n          if (Array.isArray(v)) {\n            for (const v2 of v) {\n              form.append(k, v2)\n            }\n          } else {\n            form.append(k, v)\n          }\n        }\n        this.rBody = form\n      }\n\n      if (args.json) {\n        this.rBody = JSON.stringify(args.json)\n        this.cType = 'application/json'\n      }\n\n      if (args.param) {\n        this.pathParams = args.param\n      }\n    }\n\n    let methodUpperCase = this.method.toUpperCase()\n\n    const headerValues: Record<string, string> = {\n      ...args?.header,\n      ...(typeof opt?.headers === 'function' ? await opt.headers() : opt?.headers),\n    }\n\n    if (args?.cookie) {\n      const cookies: string[] = []\n      for (const [key, value] of Object.entries(args.cookie)) {\n        cookies.push(serialize(key, value, { path: '/' }))\n      }\n      headerValues['Cookie'] = cookies.join(',')\n    }\n\n    if (this.cType) {\n      headerValues['Content-Type'] = this.cType\n    }\n\n    const headers = new Headers(headerValues ?? undefined)\n    let url = this.url\n\n    url = removeIndexString(url)\n    url = replaceUrlParam(url, this.pathParams)\n\n    if (this.queryParams) {\n      url = url + '?' + this.queryParams.toString()\n    }\n    methodUpperCase = this.method.toUpperCase()\n    const setBody = !(methodUpperCase === 'GET' || methodUpperCase === 'HEAD')\n\n    // Pass URL string to 1st arg for testing with MSW and node-fetch\n    return (opt?.fetch || fetch)(url, {\n      body: setBody ? this.rBody : undefined,\n      method: methodUpperCase,\n      headers: headers,\n      ...opt?.init,\n    })\n  }\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const hc = <T extends Hono<any, any, any>, Prefix extends string = string>(\n  baseUrl: Prefix,\n  options?: ClientRequestOptions\n) =>\n  createProxy(function proxyCallback(opts) {\n    const buildSearchParamsOption = options?.buildSearchParams ?? buildSearchParams\n    const parts = [...opts.path]\n    const lastParts = parts.slice(-3).reverse()\n\n    // allow calling .toString() and .valueOf() on the proxy\n    if (lastParts[0] === 'toString') {\n      if (lastParts[1] === 'name') {\n        // e.g. hc().somePath.name.toString() -> \"somePath\"\n        return lastParts[2] || ''\n      }\n      // e.g. hc().somePath.toString()\n      return proxyCallback.toString()\n    }\n\n    if (lastParts[0] === 'valueOf') {\n      if (lastParts[1] === 'name') {\n        // e.g. hc().somePath.name.valueOf() -> \"somePath\"\n        return lastParts[2] || ''\n      }\n      // e.g. hc().somePath.valueOf()\n      return proxyCallback\n    }\n\n    let method = ''\n    if (/^\\$/.test(lastParts[0] as string)) {\n      const last = parts.pop()\n      if (last) {\n        method = last.replace(/^\\$/, '')\n      }\n    }\n\n    const path = parts.join('/')\n    const url = mergePath(baseUrl, path)\n    if (method === 'url' || method === 'path') {\n      let result = url\n      if (opts.args[0]) {\n        if (opts.args[0].param) {\n          result = replaceUrlParam(url, opts.args[0].param)\n        }\n        if (opts.args[0].query) {\n          result = result + '?' + buildSearchParamsOption(opts.args[0].query).toString()\n        }\n      }\n      result = removeIndexString(result)\n      if (method === 'url') {\n        return new URL(result)\n      }\n      return result.slice(baseUrl.replace(/\\/+$/, '').length).replace(/^\\/?/, '/')\n    }\n    if (method === 'ws') {\n      const webSocketUrl = replaceUrlProtocol(\n        opts.args[0] && opts.args[0].param ? replaceUrlParam(url, opts.args[0].param) : url,\n        'ws'\n      )\n      const targetUrl = new URL(webSocketUrl)\n\n      const queryParams: Record<string, string | string[]> | undefined = opts.args[0]?.query\n      if (queryParams) {\n        Object.entries(queryParams).forEach(([key, value]) => {\n          if (Array.isArray(value)) {\n            value.forEach((item) => targetUrl.searchParams.append(key, item))\n          } else {\n            targetUrl.searchParams.set(key, value)\n          }\n        })\n      }\n      const establishWebSocket = (...args: ConstructorParameters<typeof WebSocket>) => {\n        if (options?.webSocket !== undefined && typeof options.webSocket === 'function') {\n          return options.webSocket(...args)\n        }\n        return new WebSocket(...args)\n      }\n\n      return establishWebSocket(targetUrl.toString())\n    }\n\n    const req = new ClientRequestImpl(url, method, {\n      buildSearchParams: buildSearchParamsOption,\n    })\n    if (method) {\n      options ??= {}\n      const args = deepMerge<ClientRequestOptions>(options, { ...opts.args[1] })\n      return req.fetch(opts.args[0], args)\n    }\n    return req\n  }, []) as UnionToIntersection<Client<T, Prefix>>\n"
  },
  {
    "path": "src/client/fetch-result-please.ts",
    "content": "/**\n * @description This file is a modified version of `fetch-result-please` (`ofetch`), minimalized and adapted to Hono's custom needs.\n *\n * @link https://www.npmjs.com/package/fetch-result-please\n */\n\nconst nullBodyResponses = new Set([101, 204, 205, 304])\n\n/**\n * Smartly parses and return the consumable result from a fetch `Response`.\n *\n * Throwing a structured error if the response is not `ok`. ({@link DetailedError})\n */\nexport async function fetchRP(fetchRes: Response | Promise<Response>): Promise<any> {\n  const _fetchRes = (await fetchRes) as unknown as Response & {\n    _data: any\n    /**\n     * @description BodyInit property from whatwg-fetch polyfill\n     *\n     * @link https://github.com/JakeChampion/fetch/blob/main/fetch.js#L238\n     */\n    _bodyInit?: any\n  }\n\n  const hasBody =\n    (_fetchRes.body || _fetchRes._bodyInit) && !nullBodyResponses.has(_fetchRes.status)\n\n  if (hasBody) {\n    const responseType = detectResponseType(_fetchRes)\n    _fetchRes._data = await _fetchRes[responseType]()\n  }\n\n  if (!_fetchRes.ok) {\n    throw new DetailedError(`${_fetchRes.status} ${_fetchRes.statusText}`, {\n      statusCode: _fetchRes?.status,\n      detail: {\n        data: _fetchRes?._data,\n        statusText: _fetchRes?.statusText,\n      },\n    })\n  }\n\n  return _fetchRes._data\n}\n\nexport class DetailedError extends Error {\n  /**\n   * Additional `message` that will be logged AND returned to client\n   */\n  public detail?: any\n  /**\n   * Additional `code` that will be logged AND returned to client\n   */\n  public code?: any\n  /**\n   * Additional value that will be logged AND NOT returned to client\n   */\n  public log?: any\n  /**\n   * Optionally set the status code to return, in a web server context\n   */\n  public statusCode?: any\n\n  constructor(\n    message: string,\n    options: { detail?: any; code?: any; statusCode?: number; log?: any } = {}\n  ) {\n    super(message)\n    this.name = 'DetailedError'\n    this.log = options.log\n    this.detail = options.detail\n    this.code = options.code\n    this.statusCode = options.statusCode\n  }\n}\n\n// This is used to match the content-type header for 'json'\nconst jsonRegex = /^application\\/(?:[\\w!#$%&*.^`~-]*\\+)?json(?:;.+)?$/i\n\nfunction detectResponseType(response: Response): 'json' | 'text' {\n  const _contentType = response.headers.get('content-type')\n\n  if (!_contentType) {\n    return 'text'\n  }\n\n  // `_contentType` might look like: `application/json; charset=utf-8`, `text/plain`, so we get the first part before `;`\n  const contentType = _contentType.split(';').shift()!\n\n  if (jsonRegex.test(contentType)) {\n    return 'json'\n  }\n\n  return 'text'\n}\n"
  },
  {
    "path": "src/client/index.ts",
    "content": "/**\n * @module\n * The HTTP Client for Hono.\n */\n\nexport { hc } from './client'\nexport { parseResponse, DetailedError } from './utils'\nexport type {\n  InferResponseType,\n  InferRequestType,\n  Fetch,\n  ClientRequestOptions,\n  ClientRequest,\n  ClientResponse,\n  ApplyGlobalResponse,\n} from './types'\n"
  },
  {
    "path": "src/client/types.test.ts",
    "content": "/* eslint-disable @typescript-eslint/no-unused-vars */\nimport { expectTypeOf } from 'vitest'\nimport { Hono } from '..'\nimport { upgradeWebSocket } from '../adapter/deno/websocket'\nimport type { TypedURL } from './types'\nimport { hc } from '.'\n\ndescribe('WebSockets', () => {\n  const app = new Hono()\n    .get(\n      '/ws',\n      upgradeWebSocket(() => ({}))\n    )\n    .get('/', (c) => c.json({}))\n  const client = hc<typeof app>('/')\n\n  it('WebSocket route', () => {\n    expectTypeOf(client.ws).toMatchTypeOf<{\n      $ws: () => WebSocket\n    }>()\n  })\n  it('Not WebSocket Route', () => {\n    expectTypeOf<\n      typeof client.index extends { $ws: () => WebSocket } ? false : true\n    >().toEqualTypeOf(true)\n  })\n})\n\ndescribe('without the leading slash', () => {\n  const app = new Hono()\n    .get('foo', (c) => c.json({}))\n    .get('foo/bar', (c) => c.json({}))\n    .get('foo/:id/baz', (c) => c.json({}))\n  const client = hc<typeof app, 'http://localhost'>('http://localhost')\n  it('`foo` should have `$get`', () => {\n    expectTypeOf(client.foo).toHaveProperty('$get')\n    expectTypeOf(client.foo.$url()).toEqualTypeOf<TypedURL<'http:', 'localhost', '', '/foo', ''>>()\n    expectTypeOf(client.foo.$path()).toEqualTypeOf<'/foo'>()\n  })\n  it('`foo.bar` should not have `$get`', () => {\n    expectTypeOf(client.foo.bar).toHaveProperty('$get')\n    expectTypeOf(client.foo.bar.$url()).toEqualTypeOf<\n      TypedURL<'http:', 'localhost', '', '/foo/bar', ''>\n    >()\n    expectTypeOf(client.foo.bar.$path()).toEqualTypeOf<'/foo/bar'>()\n  })\n  it('`foo[\":id\"].baz` should have `$get`', () => {\n    expectTypeOf(client.foo[':id'].baz).toHaveProperty('$get')\n    expectTypeOf(client.foo[':id'].baz.$url()).toEqualTypeOf<\n      TypedURL<'http:', 'localhost', '', '/foo/:id/baz', ''>\n    >()\n    expectTypeOf(\n      client.foo[':id'].baz.$url({\n        param: { id: '123' },\n      })\n    ).toEqualTypeOf<TypedURL<'http:', 'localhost', '', '/foo/123/baz', ''>>()\n    expectTypeOf(\n      client.foo[':id'].baz.$url({\n        param: { id: '123' },\n        query: { q: 'hono' },\n      })\n    ).toEqualTypeOf<TypedURL<'http:', 'localhost', '', '/foo/123/baz', `?${string}`>>()\n\n    expectTypeOf(client.foo[':id'].baz.$path()).toEqualTypeOf<'/foo/:id/baz'>()\n    expectTypeOf(\n      client.foo[':id'].baz.$path({\n        param: { id: '123' },\n      })\n    ).toEqualTypeOf<'/foo/123/baz'>()\n    expectTypeOf(\n      client.foo[':id'].baz.$path({\n        param: { id: '123' },\n        query: { q: 'hono' },\n      })\n    ).toEqualTypeOf<`/foo/123/baz?${string}`>()\n  })\n})\n\ndescribe('with the leading slash', () => {\n  const app = new Hono()\n    .get('/foo', (c) => c.json({}))\n    .get('/foo/bar', (c) => c.json({}))\n    .get('/foo/:id/baz', (c) => c.json({}))\n  const client = hc<typeof app>('')\n  it('`foo` should have `$get`', () => {\n    expectTypeOf(client.foo).toHaveProperty('$get')\n  })\n  it('`foo.bar` should not have `$get`', () => {\n    expectTypeOf(client.foo.bar).toHaveProperty('$get')\n  })\n  it('`foo[\":id\"].baz` should have `$get`', () => {\n    expectTypeOf(client.foo[':id'].baz).toHaveProperty('$get')\n  })\n})\n\ndescribe('app.all()', () => {\n  const app = new Hono()\n    .all('/all-route', (c) => c.json({ msg: 'all methods' }))\n    .get('/get-route', (c) => c.json({ msg: 'get only' }))\n  const client = hc<typeof app>('http://localhost', { fetch: app.request })\n\n  it('should NOT expose $all on the client', () => {\n    expectTypeOf<\n      (typeof client)['all-route'] extends { $all: unknown } ? true : false\n    >().toEqualTypeOf<false>()\n  })\n\n  it('should still expose valid HTTP methods like $get', () => {\n    expectTypeOf(client['get-route']).toHaveProperty('$get')\n  })\n\n  it('should expose all standard HTTP methods for routes defined with app.all()', () => {\n    // $all routes should have all standard HTTP methods typed\n    expectTypeOf(client['all-route']).toHaveProperty('$get')\n    expectTypeOf(client['all-route']).toHaveProperty('$post')\n    expectTypeOf(client['all-route']).toHaveProperty('$put')\n    expectTypeOf(client['all-route']).toHaveProperty('$delete')\n    expectTypeOf(client['all-route']).toHaveProperty('$options')\n    expectTypeOf(client['all-route']).toHaveProperty('$patch')\n  })\n\n  it('should have correct return type for expanded methods', async () => {\n    // The response type should match the original handler's return type\n    const res = await client['all-route'].$get()\n    expectTypeOf(res.json()).resolves.toEqualTypeOf<{ msg: string }>()\n  })\n})\n\ndescribe('with base URL pathname', () => {\n  const app = new Hono()\n    .get('foo', (c) => c.json({}))\n    .get('foo/bar', (c) => c.json({}))\n    .get('foo/:id/baz', (c) => c.json({}))\n  const client = hc<typeof app, 'http://localhost/api'>('http://localhost/api')\n  it('$path', () => {\n    expectTypeOf(client.foo.$path()).toEqualTypeOf<'/foo'>()\n    expectTypeOf(client.foo.bar.$path()).toEqualTypeOf<'/foo/bar'>()\n    expectTypeOf(\n      client.foo[':id'].baz.$path({\n        param: { id: '123' },\n        query: { q: 'hono' },\n      })\n    ).toEqualTypeOf<`/foo/123/baz?${string}`>()\n  })\n})\n"
  },
  {
    "path": "src/client/types.ts",
    "content": "import type { Hono } from '../hono'\nimport type { HonoBase } from '../hono-base'\nimport type { METHODS, METHOD_NAME_ALL_LOWERCASE } from '../router'\nimport type { Endpoint, ExtractSchema, KnownResponseFormat, ResponseFormat, Schema } from '../types'\nimport type { StatusCode, SuccessStatusCode } from '../utils/http-status'\nimport type { HasRequiredKeys } from '../utils/types'\n\n/**\n * Type representing the '$all' method name\n */\ntype MethodNameAll = `$${typeof METHOD_NAME_ALL_LOWERCASE}`\n\n/**\n * Type representing all standard HTTP methods prefixed with '$'\n * e.g., '$get' | '$post' | '$put' | '$delete' | '$options' | '$patch'\n */\ntype StandardMethods = `$${(typeof METHODS)[number]}`\n\n/**\n * Expands '$all' into all standard HTTP methods.\n * If the schema contains '$all', it creates a type where all standard HTTP methods\n * point to the same endpoint definition as '$all', while removing '$all' itself.\n */\ntype ExpandAllMethod<S> = MethodNameAll extends keyof S\n  ? { [M in StandardMethods]: S[MethodNameAll] } & Omit<S, MethodNameAll>\n  : S\n\ntype HonoRequest = (typeof Hono.prototype)['request']\n\nexport type BuildSearchParamsFn = (query: Record<string, string | string[]>) => URLSearchParams\n\nexport type ClientRequestOptions<T = unknown> = {\n  fetch?: typeof fetch | HonoRequest\n  webSocket?: (...args: ConstructorParameters<typeof WebSocket>) => WebSocket\n  /**\n   * Standard `RequestInit`, caution that this take highest priority\n   * and could be used to overwrite things that Hono sets for you, like `body | method | headers`.\n   *\n   * If you want to add some headers, use in `headers` instead of `init`\n   */\n  init?: RequestInit\n  /**\n   * Custom function to serialize query parameters into URLSearchParams.\n   * By default, arrays are serialized as multiple parameters with the same key (e.g., `key=a&key=b`).\n   * You can provide a custom function to change this behavior, for example to use bracket notation (e.g., `key[]=a&key[]=b`).\n   *\n   * @example\n   * ```ts\n   * const client = hc('http://localhost', {\n   *   buildSearchParams: (query) => {\n   *     return new URLSearchParams(qs.stringify(query))\n   *   }\n   * })\n   * ```\n   */\n  buildSearchParams?: BuildSearchParamsFn\n} & (keyof T extends never\n  ? {\n      headers?:\n        | Record<string, string>\n        | (() => Record<string, string> | Promise<Record<string, string>>)\n    }\n  : {\n      headers: T | (() => T | Promise<T>)\n    })\n\nexport type ClientRequest<Prefix extends string, Path extends string, S extends Schema> = {\n  [M in keyof ExpandAllMethod<S>]: ExpandAllMethod<S>[M] extends Endpoint & { input: infer R }\n    ? R extends object\n      ? HasRequiredKeys<R> extends true\n        ? (\n            args: R,\n            options?: ClientRequestOptions\n          ) => Promise<ClientResponseOfEndpoint<ExpandAllMethod<S>[M]>>\n        : (\n            args?: R,\n            options?: ClientRequestOptions\n          ) => Promise<ClientResponseOfEndpoint<ExpandAllMethod<S>[M]>>\n      : never\n    : never\n} & {\n  $url: <\n    const Arg extends\n      | (S[keyof S] extends { input: infer R }\n          ? R extends { param: infer P }\n            ? R extends { query: infer Q }\n              ? { param: P; query: Q }\n              : { param: P }\n            : R extends { query: infer Q }\n              ? { query: Q }\n              : {}\n          : {})\n      | undefined = undefined,\n  >(\n    arg?: Arg\n  ) => HonoURL<Prefix, Path, Arg>\n  $path: <\n    const Arg extends\n      | (S[keyof S] extends { input: infer R }\n          ? R extends { param: infer P }\n            ? R extends { query: infer Q }\n              ? { param: P; query: Q }\n              : { param: P }\n            : R extends { query: infer Q }\n              ? { query: Q }\n              : {}\n          : {})\n      | undefined = undefined,\n  >(\n    arg?: Arg\n  ) => BuildPath<Path, Arg>\n} & (S['$get'] extends { outputFormat: 'ws' }\n    ? S['$get'] extends { input: infer I }\n      ? {\n          $ws: (args?: I) => WebSocket\n        }\n      : {}\n    : {})\n\ntype ClientResponseOfEndpoint<T extends Endpoint = Endpoint> = T extends {\n  output: infer O\n  outputFormat: infer F\n  status: infer S\n}\n  ? ClientResponse<O, S extends number ? S : never, F extends ResponseFormat ? F : never>\n  : never\n\nexport interface ClientResponse<\n  T,\n  U extends number = StatusCode,\n  F extends ResponseFormat = ResponseFormat,\n> {\n  readonly body: ReadableStream | null\n  readonly bodyUsed: boolean\n  ok: U extends SuccessStatusCode\n    ? true\n    : U extends Exclude<StatusCode, SuccessStatusCode>\n      ? false\n      : boolean\n  redirected: boolean\n  status: U\n  statusText: string\n  type: 'basic' | 'cors' | 'default' | 'error' | 'opaque' | 'opaqueredirect'\n  headers: Headers\n  url: string\n  redirect(url: string, status: number): Response\n  clone(): Response\n  bytes(): Promise<Uint8Array<ArrayBuffer>>\n  json(): F extends 'text' ? Promise<never> : F extends 'json' ? Promise<T> : Promise<unknown>\n  text(): F extends 'text' ? (T extends string ? Promise<T> : Promise<never>) : Promise<string>\n  blob(): Promise<Blob>\n  formData(): Promise<FormData>\n  arrayBuffer(): Promise<ArrayBuffer>\n}\n\ntype BuildSearch<Arg, Key extends 'query'> = Arg extends { [K in Key]: infer Query }\n  ? IsEmptyObject<Query> extends true\n    ? ''\n    : `?${string}`\n  : ''\n\ntype BuildPathname<P extends string, Arg> = Arg extends { param: infer Param }\n  ? `${ApplyParam<TrimStartSlash<P>, Param>}`\n  : `/${TrimStartSlash<P>}`\n\ntype BuildPath<P extends string, Arg> = `${BuildPathname<P, Arg>}${BuildSearch<Arg, 'query'>}`\n\ntype BuildTypedURL<\n  Protocol extends string,\n  Host extends string,\n  Port extends string,\n  P extends string,\n  Arg,\n> = TypedURL<`${Protocol}:`, Host, Port, BuildPathname<P, Arg>, BuildSearch<Arg, 'query'>>\n\ntype HonoURL<Prefix extends string, Path extends string, Arg> =\n  IsLiteral<Prefix> extends true\n    ? TrimEndSlash<Prefix> extends `${infer Protocol}://${infer Rest}`\n      ? Rest extends `${infer Hostname}/${infer P}`\n        ? ParseHostName<Hostname> extends [infer Host extends string, infer Port extends string]\n          ? BuildTypedURL<Protocol, Host, Port, P, Arg>\n          : never\n        : ParseHostName<Rest> extends [infer Host extends string, infer Port extends string]\n          ? BuildTypedURL<Protocol, Host, Port, Path, Arg>\n          : never\n      : URL\n    : URL\ntype ParseHostName<T extends string> = T extends `${infer Host}:${infer Port}`\n  ? [Host, Port]\n  : [T, '']\ntype TrimStartSlash<T extends string> = T extends `/${infer R}` ? TrimStartSlash<R> : T\ntype TrimEndSlash<T extends string> = T extends `${infer R}/` ? TrimEndSlash<R> : T\ntype IsLiteral<T extends string> = [T] extends [never] ? false : string extends T ? false : true\ntype ApplyParam<\n  Path extends string,\n  P,\n  Result extends string = '',\n> = Path extends `${infer Head}/${infer Rest}`\n  ? Head extends `:${infer Param}`\n    ? P extends Record<Param, infer Value extends string>\n      ? IsLiteral<Value> extends true\n        ? ApplyParam<Rest, P, `${Result}/${Value & string}`>\n        : ApplyParam<Rest, P, `${Result}/${Head}`>\n      : ApplyParam<Rest, P, `${Result}/${Head}`>\n    : ApplyParam<Rest, P, `${Result}/${Head}`>\n  : Path extends `:${infer Param}`\n    ? P extends Record<Param, infer Value extends string>\n      ? IsLiteral<Value> extends true\n        ? `${Result}/${Value & string}`\n        : `${Result}/${Path}`\n      : `${Result}/${Path}`\n    : `${Result}/${Path}`\ntype IsEmptyObject<T> = keyof T extends never ? true : false\n\nexport interface TypedURL<\n  Protocol extends string,\n  Hostname extends string,\n  Port extends string,\n  Pathname extends string,\n  Search extends string,\n> extends URL {\n  protocol: Protocol\n  hostname: Hostname\n  port: Port\n  host: Port extends '' ? Hostname : `${Hostname}:${Port}`\n  origin: `${Protocol}//${Hostname}${Port extends '' ? '' : `:${Port}`}`\n  pathname: Pathname\n  search: Search\n  href: `${Protocol}//${Hostname}${Port extends '' ? '' : `:${Port}`}${Pathname}${Search}`\n}\n\nexport interface Response extends ClientResponse<unknown> {}\n\nexport type Fetch<T> = (\n  args?: InferRequestType<T>,\n  opt?: ClientRequestOptions\n) => Promise<ClientResponseOfEndpoint<InferEndpointType<T>>>\n\ntype InferEndpointType<T> = T extends (\n  args: infer R,\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  options: any | undefined\n) => Promise<infer U>\n  ? U extends ClientResponse<infer O, infer S, infer F>\n    ? { input: NonNullable<R>; output: O; outputFormat: F; status: S } extends Endpoint\n      ? { input: NonNullable<R>; output: O; outputFormat: F; status: S }\n      : never\n    : never\n  : never\n\nexport type InferResponseType<T, U extends StatusCode = StatusCode> = InferResponseTypeFromEndpoint<\n  InferEndpointType<T>,\n  U\n>\n\ntype InferResponseTypeFromEndpoint<T extends Endpoint, U extends StatusCode> = T extends {\n  output: infer O\n  status: infer S\n}\n  ? S extends U\n    ? O\n    : never\n  : never\n\nexport type InferRequestType<T> = T extends (\n  args: infer R,\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  options: any | undefined\n) => Promise<ClientResponse<unknown>>\n  ? NonNullable<R>\n  : never\n\nexport type InferRequestOptionsType<T> = T extends (\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  args: any,\n  options: infer R\n) => Promise<ClientResponse<unknown>>\n  ? NonNullable<R>\n  : never\n\n/**\n * Filter a ClientResponse type so it only includes responses of specific status codes.\n */\nexport type FilterClientResponseByStatusCode<\n  T extends ClientResponse<any, any, any>,\n  U extends number = StatusCode,\n> =\n  T extends ClientResponse<infer RT, infer RC, infer RF>\n    ? RC extends U\n      ? ClientResponse<RT, RC, RF>\n      : never\n    : never\n\ntype PathToChain<\n  Prefix extends string,\n  Path extends string,\n  E extends Schema,\n  Original extends string = Path,\n> = Path extends `/${infer P}`\n  ? PathToChain<Prefix, P, E, Path>\n  : Path extends `${infer P}/${infer R}`\n    ? { [K in P]: PathToChain<Prefix, R, E, Original> }\n    : {\n        [K in Path extends '' ? 'index' : Path]: ClientRequest<\n          Prefix,\n          Original,\n          E extends Record<string, unknown> ? E[Original] : never\n        >\n      }\n\nexport type Client<T, Prefix extends string> =\n  T extends HonoBase<any, infer S, any>\n    ? S extends Record<infer K, Schema>\n      ? K extends string\n        ? PathToChain<Prefix, K, S>\n        : never\n      : never\n    : never\n\nexport type Callback = (opts: CallbackOptions) => unknown\n\ninterface CallbackOptions {\n  path: string[]\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  args: any[]\n}\n\nexport type ObjectType<T = unknown> = {\n  [key: string]: T\n}\n\ntype GlobalResponseDefinition = {\n  [S in StatusCode]?: {\n    [F in KnownResponseFormat]?: unknown\n  }\n}\n\ntype ToEndpoints<Def extends GlobalResponseDefinition, R> = {\n  [S in keyof Def & StatusCode]: {\n    [F in keyof Def[S] & KnownResponseFormat]: Omit<R, 'output' | 'status' | 'outputFormat'> & {\n      output: Def[S][F]\n      status: S\n      outputFormat: F\n    }\n  }[keyof Def[S] & KnownResponseFormat]\n}[keyof Def & StatusCode]\n\ntype ModRoute<R, Def extends GlobalResponseDefinition> = R extends Endpoint\n  ? R | ToEndpoints<Def, R>\n  : R\n\ntype ModSchema<D, Def extends GlobalResponseDefinition> = {\n  [K in keyof D]: {\n    [M in keyof D[K]]: ModRoute<D[K][M], Def>\n  }\n}\n\nexport type ApplyGlobalResponse<App, Def extends GlobalResponseDefinition> =\n  App extends HonoBase<infer E, infer _ extends Schema, infer B>\n    ? ModSchema<ExtractSchema<App>, Def> extends infer S extends Schema\n      ? Hono<E, S, B>\n      : never\n    : never\n"
  },
  {
    "path": "src/client/utils.test.ts",
    "content": "import { HttpResponse, http } from 'msw'\nimport { setupServer } from 'msw/node'\nimport { Hono } from '../hono'\nimport type { Expect, Equal } from '../utils/types'\nimport { hc } from './client'\nimport {\n  buildSearchParams,\n  deepMerge,\n  parseResponse,\n  mergePath,\n  removeIndexString,\n  replaceUrlParam,\n  replaceUrlProtocol,\n} from './utils'\n\ndescribe('mergePath', () => {\n  it('Should merge paths correctly', () => {\n    expect(mergePath('http://localhost', '/api')).toBe('http://localhost/api')\n    expect(mergePath('http://localhost/', '/api')).toBe('http://localhost/api')\n    expect(mergePath('http://localhost', 'api')).toBe('http://localhost/api')\n    expect(mergePath('http://localhost/', 'api')).toBe('http://localhost/api')\n    expect(mergePath('http://localhost/', '/')).toBe('http://localhost/')\n  })\n})\n\ndescribe('replaceUrlParams', () => {\n  it('Should replace correctly', () => {\n    const url = 'http://localhost/posts/:postId/comments/:commentId'\n    const params = {\n      postId: '123',\n      commentId: '456',\n    }\n    const replacedUrl = replaceUrlParam(url, params)\n    expect(replacedUrl).toBe('http://localhost/posts/123/comments/456')\n  })\n\n  it('Should replace correctly when there is regex pattern', () => {\n    const url = 'http://localhost/posts/:postId{[abc]+}/comments/:commentId{[0-9]+}'\n    const params = {\n      postId: 'abc',\n      commentId: '456',\n    }\n    const replacedUrl = replaceUrlParam(url, params)\n    expect(replacedUrl).toBe('http://localhost/posts/abc/comments/456')\n  })\n\n  it('Should replace correctly when there is regex pattern with length limit', () => {\n    const url = 'http://localhost/year/:year{[1-9]{1}[0-9]{3}}/month/:month{[0-9]{2}}'\n    const params = {\n      year: '2024',\n      month: '2',\n    }\n    const replacedUrl = replaceUrlParam(url, params)\n    expect(replacedUrl).toBe('http://localhost/year/2024/month/2')\n  })\n\n  it('Should replace correctly when it has optional parameters', () => {\n    const url = 'http://localhost/something/:firstId/:secondId/:version?'\n    const params = {\n      firstId: '123',\n      secondId: '456',\n      version: undefined,\n    }\n    const replacedUrl = replaceUrlParam(url, params)\n    expect(replacedUrl).toBe('http://localhost/something/123/456')\n  })\n})\n\ndescribe('buildSearchParams', () => {\n  it('Should build URLSearchParams correctly', () => {\n    const query = {\n      id: '123',\n      type: 'test',\n      tag: ['a', 'b'],\n    }\n    const searchParams = buildSearchParams(query)\n    expect(searchParams.toString()).toBe('id=123&type=test&tag=a&tag=b')\n  })\n})\n\ndescribe('replaceUrlProtocol', () => {\n  it('Should replace http to ws', () => {\n    const url = 'http://localhost'\n    const newUrl = replaceUrlProtocol(url, 'ws')\n    expect(newUrl).toBe('ws://localhost')\n  })\n\n  it('Should replace https to wss', () => {\n    const url = 'https://localhost'\n    const newUrl = replaceUrlProtocol(url, 'ws')\n    expect(newUrl).toBe('wss://localhost')\n  })\n\n  it('Should replace ws to http', () => {\n    const url = 'ws://localhost'\n    const newUrl = replaceUrlProtocol(url, 'http')\n    expect(newUrl).toBe('http://localhost')\n  })\n\n  it('Should replace wss to https', () => {\n    const url = 'wss://localhost'\n    const newUrl = replaceUrlProtocol(url, 'http')\n    expect(newUrl).toBe('https://localhost')\n  })\n})\n\ndescribe('removeIndexString', () => {\n  it('Should remove last `/index` string', () => {\n    let url = 'http://localhost/index'\n    let newUrl = removeIndexString(url)\n    expect(newUrl).toBe('http://localhost/')\n\n    url = '/index'\n    newUrl = removeIndexString(url)\n    expect(newUrl).toBe('')\n\n    url = '/sub/index'\n    newUrl = removeIndexString(url)\n    expect(newUrl).toBe('/sub')\n\n    url = '/subindex'\n    newUrl = removeIndexString(url)\n    expect(newUrl).toBe('/subindex')\n  })\n\n  it('Should remove `/index` with query parameters', () => {\n    let url = 'http://localhost/index?page=123&limit=20'\n    let newUrl = removeIndexString(url)\n    expect(newUrl).toBe('http://localhost/?page=123&limit=20')\n\n    url = 'https://example.com/index?q=search'\n    newUrl = removeIndexString(url)\n    expect(newUrl).toBe('https://example.com/?q=search')\n\n    url = '/api/index?filter=test'\n    newUrl = removeIndexString(url)\n    expect(newUrl).toBe('/api?filter=test')\n\n    url = '/index?a=1&b=2&c=3'\n    newUrl = removeIndexString(url)\n    expect(newUrl).toBe('?a=1&b=2&c=3')\n  })\n})\n\ndescribe('deepMerge', () => {\n  it('should return the source object if the target object is not an object', () => {\n    const target = null\n    const source = 'not an object' as unknown as Record<string, unknown>\n    const result = deepMerge(target, source)\n    expect(result).toEqual(source)\n  })\n\n  it('should merge two objects with object properties', () => {\n    expect(\n      deepMerge(\n        { headers: { hono: '1' }, timeout: 2, params: {} },\n        { headers: { hono: '2', demo: 2 }, params: undefined }\n      )\n    ).toStrictEqual({\n      params: undefined,\n      headers: { hono: '2', demo: 2 },\n      timeout: 2,\n    })\n  })\n})\n\ndescribe('parseResponse', async () => {\n  const _app = new Hono()\n    .get('/text', (c) => c.text('hi'))\n    .get('/json', (c) => c.json({ message: 'hi' }))\n    .get('/might-error-json', (c) => {\n      if (Math.random() > 0.5) {\n        return c.json({ error: 'error' }, 500)\n      }\n      return c.json({ data: [{ id: 1 }, { id: 2 }] })\n    })\n    .get('/might-error-mixed-json-text', (c) => {\n      if (Math.random() > 0.5) {\n        return c.text('500 Internal Server Error', 500)\n      }\n      return c.json({ message: 'Success' })\n    })\n    .get('/200-explicit', (c) => c.text('OK', 200))\n    .get('/404', (c) => c.text('404 Not Found', 404))\n    .get('/500', (c) => c.text('500 Internal Server Error', 500))\n    .get('/raw', (c) => {\n      c.header('content-type', '')\n      return c.body('hello')\n    })\n    .get('/rawUnknown', (c) => {\n      c.header('content-type', 'x/custom-type')\n      return c.body('hello')\n    })\n    .get('/rawBuffer', (c) => {\n      c.header('content-type', 'x/custom-type')\n      return c.body(new TextEncoder().encode('hono'))\n    })\n\n  const client = hc<typeof _app>('http://localhost')\n\n  const server = setupServer(\n    http.get('http://localhost/text', () => {\n      return HttpResponse.text('hi')\n    }),\n    http.get('http://localhost/json', () => {\n      return HttpResponse.json({ message: 'hi' })\n    }),\n    http.get('http://localhost/might-error-json', () => {\n      if (Math.random() > 0.5) {\n        return HttpResponse.json({ error: 'error' }, { status: 500 })\n      }\n      return HttpResponse.json({ data: [{ id: 1 }, { id: 2 }] })\n    }),\n    http.get('http://localhost/might-error-mixed-json-text', () => {\n      if (Math.random() > 0.5) {\n        return HttpResponse.text('500 Internal Server Error', { status: 500 })\n      }\n      return HttpResponse.json({ message: 'Success' })\n    }),\n    http.get('http://localhost/200-explicit', () => {\n      return HttpResponse.text('OK', { status: 200 })\n    }),\n    http.get('http://localhost/404', () => {\n      return HttpResponse.text('404 Not Found', { status: 404 })\n    }),\n    http.get('http://localhost/500', () => {\n      return HttpResponse.text('500 Internal Server Error', { status: 500 })\n    }),\n    http.get('http://localhost/raw', () => {\n      return HttpResponse.text('hello', {\n        headers: {\n          'content-type': '',\n        },\n      })\n    }),\n    http.get('http://localhost/rawUnknown', () => {\n      return HttpResponse.text('hello', {\n        headers: {\n          'content-type': 'x/custom-type',\n        },\n      })\n    }),\n    http.get('http://localhost/rawBuffer', () => {\n      return HttpResponse.arrayBuffer(new TextEncoder().encode('hono').buffer, {\n        headers: {\n          'content-type': 'x/custom-type',\n        },\n      })\n    })\n  )\n\n  beforeAll(() => server.listen())\n  afterEach(() => server.resetHandlers())\n  afterAll(() => server.close())\n\n  await Promise.all([\n    it('should auto parse the text response - async fetch', async () => {\n      const result = await parseResponse(client.text.$get())\n      expect(result).toBe('hi')\n      type _verify = Expect<Equal<typeof result, 'hi'>>\n    }),\n    it('should auto parse the text response - sync fetch', async () => {\n      const result = await parseResponse(await client.text.$get())\n      expect(result).toBe('hi')\n      type _verify = Expect<Equal<typeof result, 'hi'>>\n    }),\n    it('should auto parse text response - explicit 200', async () => {\n      const result = await parseResponse(client['200-explicit'].$get())\n      expect(result).toBe('OK')\n      type _verify = Expect<Equal<typeof result, 'OK'>>\n    }),\n    it('should auto parse the json response - async fetch', async () => {\n      const result = await parseResponse(client.json.$get())\n      expect(result).toEqual({ message: 'hi' })\n      type _verify = Expect<Equal<typeof result, { message: string }>>\n    }),\n    it('should auto parse the json response - sync fetch', async () => {\n      const result = await parseResponse(await client.json.$get())\n      expect(result).toEqual({ message: 'hi' })\n      type _verify = Expect<Equal<typeof result, { message: string }>>\n    }),\n    it('should throw error when the response is not ok', async () => {\n      await expect(parseResponse(client['404'].$get())).rejects.toThrowError('404 Not Found')\n    }),\n    it('should parse as text for raw responses without content-type header', async () => {\n      const result = await parseResponse(client.raw.$get())\n      expect(result).toBe('hello')\n      type _verify = Expect<Equal<typeof result, 'hello'>>\n    }),\n    it('should parse as unknown string for raw buffer responses with unknown content-type header', async () => {\n      const result = await parseResponse(client.rawBuffer.$get())\n      expect(result).toMatchInlineSnapshot('\"hono\"')\n      type _verify = Expect<Equal<typeof result, string>>\n    }),\n    it('should throw error matching snapshots', async () => {\n      // Defined 404 route\n      await expect(parseResponse(client['404'].$get())).rejects.toThrowErrorMatchingInlineSnapshot(\n        '[DetailedError: 404 Not Found]'\n      )\n\n      // Defined 500 route\n      await expect(parseResponse(client['500'].$get())).rejects.toThrowErrorMatchingInlineSnapshot(\n        '[DetailedError: 500 Internal Server Error]'\n      )\n\n      // Not defined route\n      // Note: the error in this test case is thrown at the `fetch` call (.$get()), not during the `parseResponse` call, so I think `parseResponse` should not try to catch and wrap it into a structured error, which could be inconsistent, if the user awaited the fetch.\n      await expect(\n        // @ts-expect-error noRoute is not defined\n        parseResponse(client['noRoute'].$get())\n      ).rejects.toThrowErrorMatchingInlineSnapshot('[TypeError: fetch failed]')\n    }),\n    it('(type-only) should bypass error responses in the result type inference - simple 404', async () => {\n      type ResultType = Awaited<\n        ReturnType<typeof parseResponse<Awaited<ReturnType<(typeof client)['404']['$get']>>>>\n      >\n      type _verify = Expect<Equal<Awaited<ResultType>, undefined>>\n    }),\n    it('(type-only) should bypass error responses in the result type inference - conditional - json', async () => {\n      type ResultType = Awaited<\n        ReturnType<\n          typeof parseResponse<Awaited<ReturnType<(typeof client)['might-error-json']['$get']>>>\n        >\n      >\n      type _verify = Expect<Equal<ResultType, { data: { id: number }[] }>>\n    }),\n    it('(type-only) should bypass error responses in the result type inference - conditional - mixed json/text', async () => {\n      type ResultType = Awaited<\n        ReturnType<\n          typeof parseResponse<\n            Awaited<ReturnType<(typeof client)['might-error-mixed-json-text']['$get']>>\n          >\n        >\n      >\n      type _verify = Expect<Equal<ResultType, { message: string }>>\n    }),\n  ])\n\n  it('should parse json response with _bodyInit when body is undefined', async () => {\n    const mockFetch = vi.fn().mockResolvedValue({\n      ok: true,\n      status: 200,\n      headers: new Headers({ 'content-type': 'application/json' }),\n      body: undefined,\n      _bodyInit: '{\"message\":\"test\"}',\n      json: async () => ({ message: 'test' }),\n    })\n\n    global.fetch = mockFetch\n\n    const mockClientResponse = {\n      $get: mockFetch,\n    }\n\n    const result = await parseResponse(mockClientResponse.$get())\n    expect(result).toEqual({ message: 'test' })\n  })\n})\n"
  },
  {
    "path": "src/client/utils.ts",
    "content": "import type {\n  ClientErrorStatusCode,\n  ContentfulStatusCode,\n  ServerErrorStatusCode,\n} from '../utils/http-status'\nimport { fetchRP, DetailedError } from './fetch-result-please'\nimport type { ClientResponse, FilterClientResponseByStatusCode, ObjectType } from './types'\n\nexport { DetailedError }\n\nexport const mergePath = (base: string, path: string) => {\n  base = base.replace(/\\/+$/, '')\n  base = base + '/'\n  path = path.replace(/^\\/+/, '')\n  return base + path\n}\n\nexport const replaceUrlParam = (urlString: string, params: Record<string, string | undefined>) => {\n  for (const [k, v] of Object.entries(params)) {\n    const reg = new RegExp('/:' + k + '(?:{[^/]+})?\\\\??')\n    urlString = urlString.replace(reg, v ? `/${v}` : '')\n  }\n  return urlString\n}\n\nexport const buildSearchParams = (query: Record<string, string | string[]>) => {\n  const searchParams = new URLSearchParams()\n\n  for (const [k, v] of Object.entries(query)) {\n    if (v === undefined) {\n      continue\n    }\n\n    if (Array.isArray(v)) {\n      for (const v2 of v) {\n        searchParams.append(k, v2)\n      }\n    } else {\n      searchParams.set(k, v)\n    }\n  }\n\n  return searchParams\n}\n\nexport const replaceUrlProtocol = (urlString: string, protocol: 'ws' | 'http') => {\n  switch (protocol) {\n    case 'ws':\n      return urlString.replace(/^http/, 'ws')\n    case 'http':\n      return urlString.replace(/^ws/, 'http')\n  }\n}\n\nexport const removeIndexString = (urlString: string) => {\n  if (/^https?:\\/\\/[^\\/]+?\\/index(?=\\?|$)/.test(urlString)) {\n    return urlString.replace(/\\/index(?=\\?|$)/, '/')\n  }\n  return urlString.replace(/\\/index(?=\\?|$)/, '')\n}\n\nfunction isObject(item: unknown): item is ObjectType {\n  return typeof item === 'object' && item !== null && !Array.isArray(item)\n}\n\nexport function deepMerge<T>(target: T, source: Record<string, unknown>): T {\n  if (!isObject(target) && !isObject(source)) {\n    return source as T\n  }\n  const merged = { ...target } as ObjectType<T>\n\n  for (const key in source) {\n    const value = source[key]\n    if (isObject(merged[key]) && isObject(value)) {\n      merged[key] = deepMerge(merged[key], value)\n    } else {\n      merged[key] = value as T[keyof T] & T\n    }\n  }\n\n  return merged as T\n}\n\n/**\n * Shortcut to get a consumable response from `hc`'s fetch calls (Response), with types inference.\n *\n * Smartly parse the response data, throwing a structured error if the response is not `ok`. ({@link DetailedError})\n *\n * @example const result = await parseResponse(client.posts.$get())\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport async function parseResponse<T extends ClientResponse<any>>(\n  fetchRes: T | Promise<T>\n): Promise<\n  FilterClientResponseByStatusCode<\n    T,\n    Exclude<ContentfulStatusCode, ClientErrorStatusCode | ServerErrorStatusCode> // Filter out the error responses\n  > extends never\n    ? // Filtered responses does not include any contentful responses, exit with undefined\n      undefined\n    : // Filtered responses includes contentful responses, proceed to infer the type\n      FilterClientResponseByStatusCode<\n          T,\n          Exclude<ContentfulStatusCode, ClientErrorStatusCode | ServerErrorStatusCode>\n        > extends ClientResponse<infer RT, infer _, infer RF>\n      ? RF extends 'json'\n        ? RT\n        : RT extends string\n          ? RT\n          : string\n      : undefined\n> {\n  return fetchRP(fetchRes)\n}\n"
  },
  {
    "path": "src/compose.test.ts",
    "content": "import { compose } from './compose'\nimport { Context } from './context'\nimport type { Params } from './router'\nimport type { Next } from './types'\n\ntype MiddlewareTuple = [[Function, unknown], Params]\n\nclass ExpectedError extends Error {}\n\nfunction buildMiddlewareTuple(fn: Function, params?: Params): MiddlewareTuple {\n  return [[fn, undefined], params || {}]\n}\n\ndescribe('compose', () => {\n  const middleware: MiddlewareTuple[] = []\n\n  const a = async (c: Context, next: Next) => {\n    c.set('log', 'log')\n    await next()\n  }\n\n  const b = async (c: Context, next: Next) => {\n    await next()\n    c.header('x-custom-header', 'custom-header')\n  }\n\n  const c = async (c: Context, next: Next) => {\n    c.set('xxx', 'yyy')\n    await next()\n    c.set('zzz', 'xxx')\n  }\n\n  const handler = async (c: Context, next: Next) => {\n    c.set('log', `${c.get('log')} message`)\n    await next()\n    return c.json({ message: 'new response' })\n  }\n\n  middleware.push(buildMiddlewareTuple(a))\n  middleware.push(buildMiddlewareTuple(b))\n  middleware.push(buildMiddlewareTuple(c))\n  middleware.push(buildMiddlewareTuple(handler))\n\n  it('Request', async () => {\n    const composed = compose(middleware)\n    const context = await composed(new Context(new Request('http://localhost/')))\n    expect(context.get('log')).not.toBeNull()\n    expect(context.get('log')).toBe('log message')\n    expect(context.get('xxx')).toBe('yyy')\n  })\n  it('Response', async () => {\n    const composed = compose(middleware)\n    const context = await composed(new Context(new Request('http://localhost/')))\n    expect(context.res.headers.get('x-custom-header')).not.toBeNull()\n    expect(context.res.headers.get('x-custom-header')).toBe('custom-header')\n    expect((await context.res.json())['message']).toBe('new response')\n    expect(context.get('zzz')).toBe('xxx')\n  })\n})\n\ndescribe('compose with returning a promise, non-async function', () => {\n  const handlers: MiddlewareTuple[] = [\n    buildMiddlewareTuple(() => {\n      return new Promise((resolve) =>\n        setTimeout(() => {\n          resolve(\n            new Response(JSON.stringify({ message: 'new response' }), {\n              headers: {\n                'Content-Type': 'application/json',\n              },\n            })\n          )\n        })\n      )\n    }),\n  ]\n\n  it('Response', async () => {\n    const composed = compose(handlers)\n    const context = await composed(new Context(new Request('http://localhost/')))\n    expect((await context.res.json())['message']).toBe('new response')\n  })\n})\n\ndescribe('Handler and middlewares', () => {\n  const middleware: MiddlewareTuple[] = []\n\n  const req = new Request('http://localhost/')\n  const c: Context = new Context(req)\n\n  const mHandlerFoo = async (c: Context, next: Next) => {\n    c.req.raw.headers.append('x-header-foo', 'foo')\n    await next()\n  }\n\n  const mHandlerBar = async (c: Context, next: Next) => {\n    await next()\n    c.header('x-header-bar', 'bar')\n  }\n\n  const handler = (c: Context) => {\n    const foo = c.req.header('x-header-foo') || ''\n    return c.text(foo)\n  }\n\n  middleware.push(buildMiddlewareTuple(mHandlerFoo))\n  middleware.push(buildMiddlewareTuple(mHandlerBar))\n  middleware.push(buildMiddlewareTuple(handler))\n\n  it('Should return 200 Response', async () => {\n    const composed = compose(middleware)\n    const context = await composed(c)\n    const res = context.res\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('foo')\n    expect(res.headers.get('x-header-bar')).toBe('bar')\n  })\n})\n\ndescribe('compose with Context - 200 success', () => {\n  const middleware: MiddlewareTuple[] = []\n\n  const req = new Request('http://localhost/')\n  const c: Context = new Context(req)\n  const handler = (c: Context) => {\n    return c.text('Hello')\n  }\n  const mHandler = async (_c: Context, next: Next) => {\n    await next()\n  }\n\n  middleware.push(buildMiddlewareTuple(handler))\n  middleware.push(buildMiddlewareTuple(mHandler))\n\n  it('Should return 200 Response', async () => {\n    const composed = compose(middleware)\n    const context = await composed(c)\n    expect(context.res).not.toBeNull()\n    expect(context.res.status).toBe(200)\n    expect(await context.res.text()).toBe('Hello')\n  })\n})\n\ndescribe('compose with Context - 404 not found', () => {\n  const middleware: MiddlewareTuple[] = []\n\n  const req = new Request('http://localhost/')\n  const onNotFound = (c: Context) => {\n    return c.text('onNotFound', 404)\n  }\n  const onNotFoundAsync = async (c: Context) => {\n    return c.text('onNotFoundAsync', 404)\n  }\n  const mHandler = async (_c: Context, next: Next) => {\n    await next()\n  }\n\n  middleware.push(buildMiddlewareTuple(mHandler))\n\n  it('Should return 404 Response', async () => {\n    const c: Context = new Context(req)\n    const composed = compose(middleware, undefined, onNotFound)\n    const context = await composed(c)\n    expect(context.res).not.toBeNull()\n    expect(context.res.status).toBe(404)\n    expect(await context.res.text()).toBe('onNotFound')\n    expect(context.finalized).toBe(true)\n  })\n\n  it('Should return 404 Response - async handler', async () => {\n    const c: Context = new Context(req)\n    const composed = compose(middleware, undefined, onNotFoundAsync)\n    const context = await composed(c)\n    expect(context.res).not.toBeNull()\n    expect(context.res.status).toBe(404)\n    expect(await context.res.text()).toBe('onNotFoundAsync')\n    expect(context.finalized).toBe(true)\n  })\n})\n\ndescribe('compose with Context - 401 not authorized', () => {\n  const middleware: MiddlewareTuple[] = []\n\n  const req = new Request('http://localhost/')\n  const c: Context = new Context(req)\n  const handler = (c: Context) => {\n    return c.text('Hello')\n  }\n  const mHandler = async (c: Context, next: Next) => {\n    await next()\n    c.res = new Response('Not authorized', { status: 401 })\n  }\n\n  middleware.push(buildMiddlewareTuple(mHandler))\n  middleware.push(buildMiddlewareTuple(handler))\n\n  it('Should return 401 Response', async () => {\n    const composed = compose(middleware)\n    const context = await composed(c)\n    expect(context.res).not.toBeNull()\n    expect(context.res.status).toBe(401)\n    expect(await context.res.text()).toBe('Not authorized')\n    expect(context.finalized).toBe(true)\n  })\n})\n\ndescribe('compose with Context - next() below', () => {\n  const middleware: MiddlewareTuple[] = []\n\n  const req = new Request('http://localhost/')\n  const c: Context = new Context(req)\n  const handler = (c: Context) => {\n    const message = c.req.header('x-custom') || 'blank'\n    return c.text(message)\n  }\n  const mHandler = async (c: Context, next: Next) => {\n    c.req.raw.headers.append('x-custom', 'foo')\n    await next()\n  }\n\n  middleware.push(buildMiddlewareTuple(mHandler))\n  middleware.push(buildMiddlewareTuple(handler))\n\n  it('Should return 200 Response', async () => {\n    const composed = compose(middleware)\n    const context = await composed(c)\n    expect(context.res).not.toBeNull()\n    expect(context.res.status).toBe(200)\n    expect(await context.res.text()).toBe('foo')\n    expect(context.finalized).toBe(true)\n  })\n})\n\ndescribe('compose with Context - 500 error', () => {\n  const middleware: MiddlewareTuple[] = []\n\n  const req = new Request('http://localhost/')\n  const c: Context = new Context(req)\n\n  it('Error on handler', async () => {\n    const handler = () => {\n      throw new Error()\n    }\n\n    const mHandler = async (_c: Context, next: Next) => {\n      await next()\n    }\n\n    middleware.push(buildMiddlewareTuple(mHandler))\n    middleware.push(buildMiddlewareTuple(handler))\n\n    const onNotFound = (c: Context) => c.text('NotFound', 404)\n    const onError = (_error: Error, c: Context) => c.text('onError', 500)\n\n    const composed = compose(middleware, onError, onNotFound)\n    const context = await composed(c)\n    expect(context.res).not.toBeNull()\n    expect(context.res.status).toBe(500)\n    expect(await context.res.text()).toBe('onError')\n    expect(context.finalized).toBe(true)\n  })\n\n  it('Error on handler - async', async () => {\n    const handler = () => {\n      throw new Error()\n    }\n\n    middleware.push(buildMiddlewareTuple(handler))\n    const onError = async (_error: Error, c: Context) => c.text('onError', 500)\n\n    const composed = compose(middleware, onError)\n    const context = await composed(c)\n    expect(context.res).not.toBeNull()\n    expect(context.res.status).toBe(500)\n    expect(await context.res.text()).toBe('onError')\n    expect(context.finalized).toBe(true)\n  })\n\n  it('Run all the middlewares', async () => {\n    const stack: number[] = []\n    const middlewares = [\n      async (_ctx: Context, next: Next) => {\n        stack.push(0)\n        await next()\n      },\n      async (_ctx: Context, next: Next) => {\n        stack.push(1)\n        await next()\n      },\n      async (_ctx: Context, next: Next) => {\n        stack.push(2)\n        await next()\n      },\n    ].map((h) => buildMiddlewareTuple(h))\n    const composed = compose(middlewares)\n    await composed(new Context(new Request('http://localhost/')))\n    expect(stack).toEqual([0, 1, 2])\n  })\n})\ndescribe('compose with Context - not finalized', () => {\n  const req = new Request('http://localhost/')\n  const c: Context = new Context(req)\n  const onNotFound = (c: Context) => {\n    return c.text('onNotFound', 404)\n  }\n\n  it('Should not be finalized - lack `next()`', async () => {\n    const middleware: MiddlewareTuple[] = []\n    const mHandler = async (_c: Context, next: Next) => {\n      await next()\n    }\n    const mHandler2 = async () => {}\n\n    middleware.push(buildMiddlewareTuple(mHandler))\n    middleware.push(buildMiddlewareTuple(mHandler2))\n    const composed = compose(middleware, undefined, onNotFound)\n    const context = await composed(c)\n    expect(context.finalized).toBe(false)\n  })\n\n  it('Should not be finalized - lack `return Response`', async () => {\n    const middleware2: MiddlewareTuple[] = []\n    const mHandler3 = async (_c: Context, next: Next) => {\n      await next()\n    }\n    const handler = async () => {}\n    middleware2.push(buildMiddlewareTuple(mHandler3))\n    middleware2.push(buildMiddlewareTuple(handler))\n\n    const composed = compose(middleware2, undefined, onNotFound)\n    const context = await composed(c)\n    expect(context.finalized).toBe(false)\n  })\n})\ndescribe('compose with Context - next', () => {\n  const req = new Request('http://localhost/')\n  const c: Context = new Context(req)\n\n  it('Should throw multiple call error', async () => {\n    const middleware: MiddlewareTuple[] = []\n    const mHandler = async (_c: Context, next: Next) => {\n      await next()\n    }\n    const mHandler2 = async (_c: Context, next: Next) => {\n      await next()\n      await next()\n    }\n\n    middleware.push(buildMiddlewareTuple(mHandler))\n    middleware.push(buildMiddlewareTuple(mHandler2))\n\n    const composed = compose(middleware)\n    try {\n      await composed(c)\n    } catch (err) {\n      expect(err).toStrictEqual(new Error('next() called multiple times'))\n    }\n  })\n})\n\ndescribe('Compose', function () {\n  it('should get executed order one by one', async () => {\n    const arr: number[] = []\n    const stack = []\n    const called: boolean[] = []\n\n    stack.push(\n      buildMiddlewareTuple(async (_context: Context, next: Next) => {\n        called.push(true)\n\n        arr.push(1)\n        await next()\n        arr.push(6)\n      })\n    )\n\n    stack.push(\n      buildMiddlewareTuple(async (_context: Context, next: Next) => {\n        called.push(true)\n\n        arr.push(2)\n        await next()\n        arr.push(5)\n      })\n    )\n\n    stack.push(\n      buildMiddlewareTuple(async (_context: Context, next: Next) => {\n        called.push(true)\n\n        arr.push(3)\n        await next()\n        arr.push(4)\n      })\n    )\n\n    await compose(stack)(new Context(new Request('http://localhost/')))\n    expect(called).toEqual([true, true, true])\n    expect(arr).toEqual([1, 2, 3, 4, 5, 6])\n  })\n\n  it('should not get executed if previous next() not triggered', async () => {\n    const arr: number[] = []\n    const stack = []\n    const called: boolean[] = []\n\n    stack.push(\n      buildMiddlewareTuple(async (_context: Context, next: Next) => {\n        called.push(true)\n\n        arr.push(1)\n        await next()\n        arr.push(6)\n      })\n    )\n\n    stack.push(\n      buildMiddlewareTuple(async () => {\n        called.push(true)\n        arr.push(2)\n      })\n    )\n\n    stack.push(\n      buildMiddlewareTuple(async (_context: Context, next: Next) => {\n        called.push(true)\n\n        arr.push(3)\n        await next()\n        arr.push(4)\n      })\n    )\n\n    await compose(stack)(new Context(new Request('http://localhost/')))\n    expect(called).toEqual([true, true])\n    expect(arr).toEqual([1, 2, 6])\n  })\n\n  it('should be able to be called twice', async () => {\n    const stack = []\n\n    stack.push(\n      buildMiddlewareTuple(async (context: Context, next: Next) => {\n        context.get('arr').push(1)\n        await next()\n        context.get('arr').push(6)\n      })\n    )\n\n    stack.push(\n      buildMiddlewareTuple(async (context: Context, next: Next) => {\n        context.get('arr').push(2)\n        await next()\n        context.get('arr').push(5)\n      })\n    )\n\n    stack.push(\n      buildMiddlewareTuple(async (context: Context, next: Next) => {\n        context.get('arr').push(3)\n        await next()\n        context.get('arr').push(4)\n      })\n    )\n\n    const fn = compose(stack)\n    const ctx1 = new Context(new Request('http://localhost/'))\n\n    ctx1.set('arr', [])\n\n    const ctx2 = new Context(new Request('http://localhost/'))\n\n    ctx2.set('arr', [])\n\n    const out = [1, 2, 3, 4, 5, 6]\n\n    await fn(ctx1)\n\n    expect(out).toEqual(ctx1.get('arr'))\n    await fn(ctx2)\n\n    expect(out).toEqual(ctx2.get('arr'))\n  })\n\n  it('should create next functions that return a Promise', async () => {\n    const stack = []\n    const arr: unknown[] = []\n    for (let i = 0; i < 5; i++) {\n      stack.push(\n        buildMiddlewareTuple((_context: Context, next: Next) => {\n          arr.push(next())\n        })\n      )\n    }\n\n    await compose(stack)(new Context(new Request('http://localhost/')))\n\n    for (const next of arr) {\n      const isPromise = !!(next as { then?: Function })?.then\n      expect(isPromise).toBe(true)\n    }\n  })\n\n  it('should work with 0 middleware', async () => {\n    await compose([])(new Context(new Request('http://localhost/')))\n  })\n\n  it('should work when yielding at the end of the stack', async () => {\n    const stack = []\n    let called = false\n\n    stack.push(\n      buildMiddlewareTuple(async (_ctx: Context, next: Next) => {\n        await next()\n        called = true\n      })\n    )\n\n    await compose(stack)(new Context(new Request('http://localhost/')))\n    expect(called).toBe(true)\n  })\n\n  it('should reject on errors in middleware', async () => {\n    const stack = []\n\n    stack.push(\n      buildMiddlewareTuple(() => {\n        throw new ExpectedError()\n      })\n    )\n\n    try {\n      await compose(stack)(new Context(new Request('http://localhost/')))\n      throw new Error('promise was not rejected')\n    } catch (e) {\n      expect(e).toBeInstanceOf(ExpectedError)\n    }\n  })\n\n  it('should keep the context', async () => {\n    const ctx = new Context(new Request('http://localhost/'))\n\n    const stack = []\n\n    stack.push(\n      buildMiddlewareTuple(async (ctx2: Context, next: Next) => {\n        await next()\n        expect(ctx2).toEqual(ctx)\n      })\n    )\n\n    stack.push(\n      buildMiddlewareTuple(async (ctx2: Context, next: Next) => {\n        await next()\n        expect(ctx2).toEqual(ctx)\n      })\n    )\n\n    stack.push(\n      buildMiddlewareTuple(async (ctx2: Context, next: Next) => {\n        await next()\n        expect(ctx2).toEqual(ctx)\n      })\n    )\n\n    await compose(stack)(ctx)\n  })\n\n  it('should catch downstream errors', async () => {\n    const arr: number[] = []\n    const stack = []\n\n    stack.push(\n      buildMiddlewareTuple(async (_ctx: Context, next: Next) => {\n        arr.push(1)\n        try {\n          arr.push(6)\n          await next()\n          arr.push(7)\n        } catch {\n          arr.push(2)\n        }\n        arr.push(3)\n      })\n    )\n\n    stack.push(\n      buildMiddlewareTuple(async () => {\n        arr.push(4)\n        throw new Error()\n      })\n    )\n\n    await compose(stack)(new Context(new Request('http://localhost/')))\n    expect(arr).toEqual([1, 6, 4, 2, 3])\n  })\n\n  it('should compose w/ next', async () => {\n    let called = false\n\n    await compose([])(new Context(new Request('http://localhost/')), async () => {\n      called = true\n    })\n    expect(called).toBe(true)\n  })\n\n  it('should handle errors in wrapped non-async functions', async () => {\n    const stack = []\n\n    stack.push(\n      buildMiddlewareTuple(function () {\n        throw new ExpectedError()\n      })\n    )\n\n    try {\n      await compose(stack)(new Context(new Request('http://localhost/')))\n      throw new Error('promise was not rejected')\n    } catch (e) {\n      expect(e).toBeInstanceOf(ExpectedError)\n    }\n  })\n\n  // https://github.com/koajs/compose/pull/27#issuecomment-143109739\n  it('should compose w/ other compositions', async () => {\n    const called: number[] = []\n\n    await compose([\n      buildMiddlewareTuple(\n        compose([\n          buildMiddlewareTuple((_ctx: Context, next: Next) => {\n            called.push(1)\n            return next()\n          }),\n          buildMiddlewareTuple((_ctx: Context, next: Next) => {\n            called.push(2)\n            return next()\n          }),\n        ])\n      ),\n      buildMiddlewareTuple((_ctx: Context, next: Next) => {\n        called.push(3)\n        return next()\n      }),\n    ])(new Context(new Request('http://localhost/')))\n\n    expect(called).toEqual([1, 2, 3])\n  })\n\n  it('should throw if next() is called multiple times', async () => {\n    try {\n      await compose([\n        buildMiddlewareTuple(async (_ctx: Context, next: Next) => {\n          await next()\n          await next()\n        }),\n      ])(new Context(new Request('http://localhost/')))\n      throw new Error('boom')\n    } catch (err) {\n      expect(err instanceof Error && /multiple times/.test(err.message)).toBe(true)\n    }\n  })\n\n  it('should return a valid middleware', async () => {\n    let val = 0\n    await compose([\n      buildMiddlewareTuple(\n        compose([\n          buildMiddlewareTuple((_ctx: Context, next: Next) => {\n            val++\n            return next()\n          }),\n          buildMiddlewareTuple((_ctx: Context, next: Next) => {\n            val++\n            return next()\n          }),\n        ])\n      ),\n      buildMiddlewareTuple((_ctx: Context, next: Next) => {\n        val++\n        return next()\n      }),\n    ])(new Context(new Request('http://localhost/')))\n\n    expect(val).toEqual(3)\n  })\n\n  it('should return last return value', async () => {\n    const stack = []\n\n    stack.push(\n      buildMiddlewareTuple(async (ctx: Context, next: Next) => {\n        await next()\n        expect(ctx.get('val')).toEqual(2)\n        ctx.set('val', 1)\n      })\n    )\n\n    stack.push(\n      buildMiddlewareTuple(async (ctx: Context, next: Next) => {\n        ctx.set('val', 2)\n        await next()\n        expect(ctx.get('val')).toEqual(2)\n      })\n    )\n\n    const res = await compose(stack)(new Context(new Request('http://localhost/')))\n    expect(res.get('val')).toEqual(1)\n  })\n\n  it('should not affect the original middleware array', () => {\n    const middleware: MiddlewareTuple[] = []\n    const fn1 = (_ctx: Context, next: Next) => {\n      return next()\n    }\n    middleware.push(buildMiddlewareTuple(fn1))\n\n    for (const [[fn]] of middleware) {\n      expect(fn).toEqual(fn1)\n    }\n\n    compose(middleware)\n\n    for (const [[fn]] of middleware) {\n      expect(fn).toEqual(fn1)\n    }\n  })\n\n  it('should not get stuck on the passed in next', async () => {\n    const middleware = [\n      buildMiddlewareTuple((ctx: Context, next: Next) => {\n        ctx.set('middleware', ctx.get('middleware') + 1)\n        return next()\n      }),\n    ]\n\n    const ctx = new Context(new Request('http://localhost/'))\n\n    ctx.set('middleware', 0)\n    ctx.set('next', 0)\n\n    await compose(middleware)(ctx, ((ctx: Context, next: Next) => {\n      ctx.set('next', ctx.get('next') + 1)\n      return next()\n    }) as Next)\n\n    expect(ctx.get('middleware')).toEqual(1)\n    expect(ctx.get('next')).toEqual(1)\n  })\n})\n"
  },
  {
    "path": "src/compose.ts",
    "content": "import type { Context } from './context'\nimport type { Env, ErrorHandler, Next, NotFoundHandler } from './types'\n\n/**\n * Compose middleware functions into a single function based on `koa-compose` package.\n *\n * @template E - The environment type.\n *\n * @param {[[Function, unknown], unknown][] | [[Function]][]} middleware - An array of middleware functions and their corresponding parameters.\n * @param {ErrorHandler<E>} [onError] - An optional error handler function.\n * @param {NotFoundHandler<E>} [onNotFound] - An optional not-found handler function.\n *\n * @returns {(context: Context, next?: Next) => Promise<Context>} - A composed middleware function.\n */\nexport const compose = <E extends Env = Env>(\n  middleware: [[Function, unknown], unknown][] | [[Function]][],\n  onError?: ErrorHandler<E>,\n  onNotFound?: NotFoundHandler<E>\n): ((context: Context, next?: Next) => Promise<Context>) => {\n  return (context, next) => {\n    let index = -1\n\n    return dispatch(0)\n\n    /**\n     * Dispatch the middleware functions.\n     *\n     * @param {number} i - The current index in the middleware array.\n     *\n     * @returns {Promise<Context>} - A promise that resolves to the context.\n     */\n    async function dispatch(i: number): Promise<Context> {\n      if (i <= index) {\n        throw new Error('next() called multiple times')\n      }\n      index = i\n\n      let res\n      let isError = false\n      let handler\n\n      if (middleware[i]) {\n        handler = middleware[i][0][0]\n        context.req.routeIndex = i\n      } else {\n        handler = (i === middleware.length && next) || undefined\n      }\n\n      if (handler) {\n        try {\n          res = await handler(context, () => dispatch(i + 1))\n        } catch (err) {\n          if (err instanceof Error && onError) {\n            context.error = err\n            res = await onError(err, context)\n            isError = true\n          } else {\n            throw err\n          }\n        }\n      } else {\n        if (context.finalized === false && onNotFound) {\n          res = await onNotFound(context)\n        }\n      }\n\n      if (res && (context.finalized === false || isError)) {\n        context.res = res\n      }\n      return context\n    }\n  }\n}\n"
  },
  {
    "path": "src/context.test.ts",
    "content": "import { Context } from './context'\nimport { setCookie } from './helper/cookie'\n\nconst makeResponseHeaderImmutable = (res: Response) => {\n  Object.defineProperty(res, 'headers', {\n    value: new Proxy(res.headers, {\n      set(target, prop, value) {\n        if (prop === 'set') {\n          throw new TypeError('Cannot modify headers: Headers are immutable')\n        }\n        return Reflect.set(target, prop, value)\n      },\n      get(target, prop, receiver) {\n        if (prop === 'set') {\n          return function () {\n            throw new TypeError('Cannot modify headers: Headers are immutable')\n          }\n        }\n        const value = Reflect.get(target, prop)\n        if (typeof value === 'function') {\n          return Object.defineProperties(\n            function (...args: unknown[]) {\n              // @ts-expect-error: `this` context is intentionally dynamic for proxy method binding\n              return Reflect.apply(value, this === receiver ? target : this, args)\n            },\n            {\n              name: { value: value.name },\n              length: { value: value.length },\n            }\n          )\n        }\n        return value\n      },\n    }),\n    writable: false,\n  })\n  return res\n}\n\ndescribe('Context', () => {\n  const req = new Request('http://localhost/')\n\n  let c: Context\n  beforeEach(() => {\n    c = new Context(req)\n  })\n\n  it('c.text()', async () => {\n    const res = c.text('text in c', 201, { 'X-Custom': 'Message' })\n    expect(res.status).toBe(201)\n    expect(res.headers.get('Content-Type')).toMatch(/^text\\/plain/)\n    expect(await res.text()).toBe('text in c')\n    expect(res.headers.get('X-Custom')).toBe('Message')\n  })\n\n  it('c.text() with c.status()', async () => {\n    c.status(404)\n    const res = c.text('not found')\n    expect(res.status).toBe(404)\n    expect(res.headers.get('Content-Type')).toMatch(/^text\\/plain/)\n    expect(await res.text()).toBe('not found')\n  })\n\n  it('c.json()', async () => {\n    const res = c.json({ message: 'Hello' }, 201, { 'X-Custom': 'Message' })\n    expect(res.status).toBe(201)\n    expect(res.headers.get('Content-Type')).toMatch('application/json')\n    const text = await res.text()\n    expect(text).toBe('{\"message\":\"Hello\"}')\n    expect(res.headers.get('X-Custom')).toBe('Message')\n  })\n\n  it('c.html()', async () => {\n    const res: Response = c.html('<h1>Hello! Hono!</h1>', 201, { 'X-Custom': 'Message' })\n    expect(res.status).toBe(201)\n    expect(res.headers.get('Content-Type')).toMatch('text/html')\n    expect(await res.text()).toBe('<h1>Hello! Hono!</h1>')\n    expect(res.headers.get('X-Custom')).toBe('Message')\n  })\n\n  it('c.html() with async', async () => {\n    const resPromise: Promise<Response> = c.html(\n      new Promise<string>((resolve) => setTimeout(() => resolve('<h1>Hello! Hono!</h1>'), 0)),\n      201,\n      {\n        'X-Custom': 'Message',\n      }\n    )\n    const res = await resPromise\n    expect(res.status).toBe(201)\n    expect(res.headers.get('Content-Type')).toMatch('text/html')\n    expect(await res.text()).toBe('<h1>Hello! Hono!</h1>')\n    expect(res.headers.get('X-Custom')).toBe('Message')\n  })\n\n  it('c.redirect()', async () => {\n    let res = c.redirect('/destination')\n    expect(res.status).toBe(302)\n    expect(res.headers.get('Location')).toBe('/destination')\n    res = c.redirect('https://example.com/destination')\n    expect(res.status).toBe(302)\n    expect(res.headers.get('Location')).toBe('https://example.com/destination')\n  })\n\n  it('c.redirect() w/ URL', async () => {\n    const res = c.redirect(new URL('/destination', 'https://example.com'))\n    expect(res.status).toBe(302)\n    expect(res.headers.get('Location')).toBe('https://example.com/destination')\n  })\n\n  it('c.redirect() w/ multibytes', async () => {\n    const res = c.redirect('https://example.com/こんにちは')\n    expect(res.headers.get('Location')).toBe(\n      'https://example.com/%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF'\n    )\n  })\n\n  const unchangedURLString = [\n    'https://example.com/%hello', // invalid ASCII chars\n    'https://example.com/%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF?abc',\n    'https://localhost/api?redirect_uri=https%3A%2F%2Fexample.com', // with ://\n    'https://localhost/api?redirect_uri=https%3A%2F%2Fexample.com&scope=email%20profile', // with spaces and ://\n  ]\n\n  unchangedURLString.forEach((urlString) => {\n    it(`c.redirect() w/ ${urlString}`, () => {\n      const res = c.redirect(urlString)\n      expect(res.headers.get('Location')).toBe(urlString)\n    })\n  })\n\n  it('c.header()', async () => {\n    c.header('X-Foo', 'Bar')\n    const res = c.body('Hi')\n    const foo = res.headers.get('X-Foo')\n    expect(foo).toBe('Bar')\n  })\n\n  it('c.header() - append', async () => {\n    c.header('X-Foo', 'Bar')\n    c.header('X-Foo', 'Buzz', { append: true })\n    const res = c.body('Hi')\n    const foo = res.headers.get('X-Foo')\n    expect(foo).toBe('Bar, Buzz')\n  })\n\n  it('c.set() and c.get()', async () => {\n    expect(c.get('foo')).toBe(undefined)\n    c.set('foo', 'bar')\n    expect(c.get('foo')).toBe('bar')\n    expect(c.get('foo2')).toBe(undefined)\n  })\n\n  it('c.var', async () => {\n    expect(c.var.foo).toBe(undefined)\n    c.set('foo', 'bar')\n    expect(c.var.foo).toBe('bar')\n    expect(c.var.foo2).toBe(undefined)\n  })\n\n  it('c.notFound()', async () => {\n    const res = c.notFound()\n    expect(res).instanceOf(Response)\n  })\n\n  it('Should set headers if already this.#headers is created by `c.header()`', async () => {\n    c.header('X-Foo', 'Bar')\n    c.header('X-Foo', 'Buzz', { append: true })\n    const res = c.body('Hi', {\n      headers: {\n        'X-Message': 'Hi',\n      },\n    })\n    expect(res.headers.get('X-Foo')).toBe('Bar, Buzz')\n    expect(res.headers.get('X-Message')).toBe('Hi')\n  })\n\n  it('c.header() - append, c.html()', async () => {\n    c.header('X-Foo', 'Bar', { append: true })\n    const res = await c.html('<h1>This rendered fine</h1>')\n    expect(res.headers.get('content-type')).toMatch(/^text\\/html/)\n  })\n\n  it('c.header() - clear the header', async () => {\n    c.header('X-Foo', 'Bar')\n    c.header('X-Foo', undefined)\n    c.header('X-Foo2', 'Bar')\n    const res = c.body('Hi')\n    expect(res.headers.get('X-Foo')).toBe(null)\n    c.header('X-Foo2', undefined)\n    const res2 = c.body('Hi')\n    expect(res2.headers.get('X-Foo2')).toBe(null)\n  })\n\n  it('c.header() - clear the header when append is true', async () => {\n    c.header('X-Foo', 'Bar', { append: true })\n    c.header('X-Foo', undefined)\n    expect(c.res.headers.get('X-Foo')).toBe(null)\n  })\n\n  it('c.body() - multiple header', async () => {\n    const res = c.body('Hi', 200, {\n      'X-Foo': ['Bar', 'Buzz'],\n    })\n    const foo = res.headers.get('X-Foo')\n    expect(foo).toBe('Bar, Buzz')\n  })\n\n  it('c.body() - content-type cannot be overridden by the default response when append headers', async () => {\n    c.header('Vary', 'Accept-Encoding', { append: true })\n    c.res\n    c.header('Content-Type', 'text/html')\n    const res = c.body('<h1>Hi</h1>')\n    expect(res.headers.get('Content-Type')).toMatch('text/html')\n  })\n\n  it('c.body() - content-type can set explicitly via c.res.headers', async () => {\n    c.header('Vary', 'Accept-Encoding', { append: true })\n    c.res.headers.set('Content-Type', 'text/html')\n    const res = c.body('<h1>Hi</h1>')\n    expect(res.headers.get('Content-Type')).toMatch('text/html')\n  })\n\n  it('c.body() - Different header settings require ensuring order', async () => {\n    c.header('Vary', 'Accept-Encoding', { append: true })\n    c.header('Content-Type', 'image/png')\n    c.res.headers.set('Content-Type', 'text/html')\n    const res = c.body('<h1>Hi</h1>')\n    expect(res.headers.get('Content-Type')).toMatch('text/html')\n  })\n\n  it('c.status()', async () => {\n    c.status(201)\n    const res = c.body('Hi')\n    expect(res.status).toBe(201)\n  })\n\n  it('Complex pattern', async () => {\n    c.status(404)\n    const res = c.json({ hono: 'great app' })\n    expect(res.status).toBe(404)\n    expect(res.headers.get('Content-Type')).toMatch('application/json')\n    const obj: { [key: string]: string } = await res.json()\n    expect(obj['hono']).toBe('great app')\n  })\n\n  it('Has headers and status', async () => {\n    c.header('x-custom1', 'Message1')\n    c.header('x-custom2', 'Message2')\n    c.status(200)\n    const res = c.newResponse('this is body', 201, {\n      'x-custom3': 'Message3',\n      'x-custom2': 'Message2-Override',\n    })\n    expect(res.headers.get('x-Custom1')).toBe('Message1')\n    expect(res.headers.get('x-Custom2')).toBe('Message2-Override')\n    expect(res.headers.get('x-Custom3')).toBe('Message3')\n    expect(res.status).toBe(201)\n\n    // res is already set.\n    c.res = res\n    c.header('X-Custom4', 'Message4')\n    c.status(202)\n    expect(c.res.headers.get('X-Custom4')).toBe('Message4')\n    expect(c.res.status).toBe(201)\n    expect(await res.text()).toBe('this is body')\n  })\n\n  it('Inherit current status if not specified', async () => {\n    c.status(201)\n    const res = c.newResponse('this is body', {\n      headers: {\n        'x-custom3': 'Message3',\n        'x-custom2': 'Message2-Override',\n      },\n    })\n    expect(res.headers.get('x-Custom2')).toBe('Message2-Override')\n    expect(res.headers.get('x-Custom3')).toBe('Message3')\n    expect(res.status).toBe(201)\n    expect(await res.text()).toBe('this is body')\n  })\n\n  it('Should append the previous headers to new Response', () => {\n    c.res.headers.set('x-Custom1', 'Message1')\n    const res2 = new Response('foo2', {\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    })\n    res2.headers.set('x-Custom2', 'Message2')\n    c.res = res2\n    expect(c.res.headers.get('x-Custom1')).toBe('Message1')\n    expect(c.res.headers.get('Content-Type')).toBe('application/json')\n  })\n\n  it('Should return 200 response', async () => {\n    const res = c.text('Text')\n    expect(res.status).toBe(200)\n  })\n\n  it('Should return 204 response', async () => {\n    c.status(204)\n    const res = c.body(null)\n    expect(res.status).toBe(204)\n    expect(await res.text()).toBe('')\n  })\n\n  it('Should be able read env', async () => {\n    const req = new Request('http://localhost/')\n    const key = 'a-secret-key'\n    const ctx = new Context(req, {\n      env: {\n        API_KEY: key,\n      },\n    })\n    expect(ctx.env.API_KEY).toBe(key)\n  })\n\n  it('set and set', async () => {\n    const ctx = new Context(req)\n    expect(ctx.get('k-foo')).toEqual(undefined)\n    ctx.set('k-foo', 'v-foo')\n    expect(ctx.get('k-foo')).toEqual('v-foo')\n    expect(ctx.get('k-bar')).toEqual(undefined)\n    ctx.set('k-bar', { k: 'v' })\n    expect(ctx.get('k-bar')).toEqual({ k: 'v' })\n  })\n\n  it('has res object by default', async () => {\n    c = new Context(req)\n    c.res.headers.append('foo', 'bar')\n    const res = c.text('foo')\n    expect(res.headers.get('foo')).not.toBeNull()\n    expect(res.headers.get('foo')).toBe('bar')\n  })\n})\n\ndescribe('event and executionCtx', () => {\n  const req = new Request('http://localhost/')\n\n  it('Should return the event if accessing c.event', () => {\n    const respondWith = vi.fn()\n    const c = new Context(req, {\n      // @ts-expect-error the type is not correct\n      executionCtx: {\n        respondWith: respondWith,\n      },\n    })\n    expect(() => c.event).not.toThrowError()\n    c.event.respondWith(new Response())\n    expect(respondWith).toHaveBeenCalled()\n  })\n\n  it('Should throw an error if accessing c.event', () => {\n    const c = new Context(req)\n    expect(() => c.event).toThrowError()\n  })\n\n  it('Should return the executionCtx if accessing c.executionCtx', () => {\n    const pathThroughOnException = vi.fn()\n    const waitUntil = vi.fn()\n    const c = new Context(req, {\n      executionCtx: {\n        passThroughOnException: pathThroughOnException,\n        waitUntil: waitUntil,\n        props: {},\n      },\n      env: {},\n    })\n    expect(() => c.executionCtx).not.toThrowError()\n    c.executionCtx.passThroughOnException()\n    expect(pathThroughOnException).toHaveBeenCalled()\n    const asyncFunc = async () => {}\n    c.executionCtx.waitUntil(asyncFunc())\n    expect(waitUntil).toHaveBeenCalled()\n  })\n\n  it('Should throw an error if accessing c.executionCtx', () => {\n    const c = new Context(req)\n    expect(() => c.executionCtx).toThrowError()\n  })\n})\n\ndescribe('Context header', () => {\n  const req = new Request('http://localhost/')\n  let c: Context\n  beforeEach(() => {\n    c = new Context(req)\n  })\n\n  it('Should return only one content-type value', async () => {\n    c.header('Content-Type', 'foo')\n    const res = await c.html('foo')\n    expect(res.headers.get('Content-Type')).toBe('text/html; charset=UTF-8')\n  })\n\n  it('Should rewrite header values correctly', async () => {\n    c.res = await c.html('foo')\n    const res = c.text('foo')\n    expect(res.headers.get('Content-Type')).toMatch(/^text\\/plain/)\n  })\n\n  it('Should set header values if the #this.headers is set and the arg is ResponseInit', async () => {\n    c.header('foo', 'bar')\n    const res = c.body('foo', {\n      headers: {\n        'Content-Type': 'text/plain',\n      },\n    })\n    expect(res.headers.get('foo')).toBe('bar')\n  })\n\n  it('Should set cookie headers when re-assigning Response to `c.res`', () => {\n    const cookies = ['foo=bar; Path=/', 'foo2=bar2; Path=/']\n    const res = new Response(null)\n    res.headers.append('set-cookie', cookies[0])\n    res.headers.append('set-cookie', cookies[1])\n    c.res = res\n    expect(c.res.headers.getSetCookie().length).toBe(2)\n\n    // Re-assign\n    const newCookies = ['foo3=bar3; Path=/']\n    const newResponse = new Response(null)\n    newResponse.headers.append('set-cookie', newCookies[0])\n    c.res = newResponse\n    expect(c.res.headers.getSetCookie().length).toBe(cookies.length)\n    expect(c.res.headers.getSetCookie()).toEqual(cookies)\n  })\n\n  it('Should keep previous cookies in response headers', () => {\n    c.res.headers.append('set-cookie', 'foo=bar; Path=/')\n    setCookie(c, 'foo2', 'bar2', { path: '/' })\n    const res = c.json({ message: 'Hello' })\n    const cookies = res.headers.getSetCookie()\n    expect(cookies.includes('foo=bar; Path=/')).toBe(true)\n    expect(cookies.includes('foo2=bar2; Path=/')).toBe(true)\n  })\n\n  it('Should set set-cookie header values if c.res is already defined', () => {\n    c.res = new Response(null, {\n      headers: [\n        ['set-cookie', 'a'],\n        ['set-cookie', 'b'],\n        ['set-cookie', 'c'],\n      ],\n    })\n    const res = c.text('Hi')\n    expect(res.headers.get('set-cookie')).toBe('a, b, c')\n  })\n\n  it('Should be able to overwrite a fetch response with a new response.', async () => {\n    c.res = makeResponseHeaderImmutable(new Response('bar'))\n    c.res = new Response('foo', {\n      headers: {\n        'X-Custom': 'Message',\n      },\n    })\n    expect(await c.res.text()).toBe('foo')\n    expect(c.res.headers.get('X-Custom')).toBe('Message')\n  })\n\n  it('Should be able to overwrite a response with a fetch response.', async () => {\n    c.res = new Response('foo', {\n      headers: {\n        'X-Custom': 'Message',\n      },\n    })\n    c.res = makeResponseHeaderImmutable(new Response('bar'))\n    expect(await c.res.text()).toBe('bar')\n    expect(c.res.headers.get('X-Custom')).toBe('Message')\n  })\n\n  it('Should be able to set headers if the context is finalized', async () => {\n    c.res = makeResponseHeaderImmutable(new Response('bar'))\n    expect(c.finalized).toBe(true)\n    c.header('X-Custom', 'Message')\n    expect(c.res.headers.get('X-Custom')).toBe('Message')\n  })\n\n  it('Should handle headers with array values correctly', async () => {\n    c.header('X-Array', 'value1')\n    const res = c.json({ test: 'data' }, 200, {\n      'X-Array': ['new1', 'new2'],\n    })\n    expect(res.headers.get('X-Array')).toBe('new1, new2')\n  })\n\n  it('Should remove existing header when new value is empty array', async () => {\n    c.header('X-Test', 'existing')\n    const res = c.json({ test: 'data' }, 200, {\n      'X-Test': [],\n    })\n    expect(res.headers.get('X-Test')).toBeNull()\n  })\n})\n\ndescribe('Pass a ResponseInit to respond methods', () => {\n  const req = new Request('http://localhost/')\n  let c: Context\n  beforeEach(() => {\n    c = new Context(req)\n  })\n\n  it('c.json()', async () => {\n    const originalResponse = new Response('Unauthorized', {\n      headers: {\n        'content-type': 'text/plain',\n        'x-custom': 'custom message',\n      },\n      status: 401,\n    })\n    const res = c.json(\n      {\n        message: 'Unauthorized',\n      },\n      originalResponse\n    )\n    expect(res.status).toBe(401)\n    expect(res.headers.get('content-type')).toMatch(/^application\\/json/)\n    expect(res.headers.get('x-custom')).toBe('custom message')\n    expect(await res.json()).toEqual({\n      message: 'Unauthorized',\n    })\n  })\n\n  it('c.body()', async () => {\n    const originalResponse = new Response('<h1>Hello</h1>', {\n      headers: {\n        'content-type': 'text/html',\n      },\n    })\n    const res = c.body('<h2>Hello</h2>', originalResponse)\n    expect(res.headers.get('content-type')).toMatch(/^text\\/html/)\n    expect(await res.text()).toBe('<h2>Hello</h2>')\n  })\n\n  it('c.body() should retain context cookies from context and original response', async () => {\n    setCookie(c, 'context', '1')\n    setCookie(c, 'context', '2')\n\n    const originalResponse = new Response('', {\n      headers: {\n        'set-cookie': 'response=1; Path=/',\n      },\n    })\n    const res = c.body('', originalResponse)\n    const cookies = res.headers.getSetCookie()\n    expect(cookies.includes('context=1; Path=/')).toBe(true)\n    expect(cookies.includes('context=2; Path=/')).toBe(true)\n    expect(cookies.includes('response=1; Path=/')).toBe(true)\n  })\n\n  it('c.text()', async () => {\n    const originalResponse = new Response(JSON.stringify({ foo: 'bar' }))\n    const res = c.text('foo', originalResponse)\n    expect(res.headers.get('content-type')).toMatch(/^text\\/plain/)\n    expect(await res.text()).toBe('foo')\n  })\n\n  it('c.html()', async () => {\n    const originalResponse = new Response('foo')\n    const res = await c.html('<h1>foo</h1>', originalResponse)\n    expect(res.headers.get('content-type')).toMatch(/^text\\/html/)\n    expect(await res.text()).toBe('<h1>foo</h1>')\n  })\n})\n\ndeclare module './context' {\n  interface ContextRenderer {\n    (content: string | Promise<string>, head: { title: string }): Response | Promise<Response>\n  }\n}\n\ndescribe('c.render', () => {\n  const req = new Request('http://localhost/')\n  let c: Context\n  beforeEach(() => {\n    c = new Context(req)\n  })\n\n  it('Should return a Response from the default renderer', async () => {\n    c.header('foo', 'bar')\n    const res = await c.render('<h1>content</h1>', { title: 'dummy ' })\n    expect(res.headers.get('foo')).toBe('bar')\n    expect(await res.text()).toBe('<h1>content</h1>')\n  })\n\n  it('Should return a Response from the custom renderer', async () => {\n    c.setRenderer((content, head) => {\n      return c.html(`<html><head>${head.title}</head><body>${content}</body></html>`)\n    })\n    c.header('foo', 'bar')\n    const res = await c.render('<h1>content</h1>', { title: 'title' })\n    expect(res.headers.get('foo')).toBe('bar')\n    expect(await res.text()).toBe('<html><head>title</head><body><h1>content</h1></body></html>')\n  })\n})\n"
  },
  {
    "path": "src/context.ts",
    "content": "import { HonoRequest } from './request'\nimport type { Result } from './router'\nimport type {\n  Env,\n  FetchEventLike,\n  H,\n  Input,\n  NotFoundHandler,\n  RouterRoute,\n  TypedResponse,\n} from './types'\nimport type { ResponseHeader } from './utils/headers'\nimport { HtmlEscapedCallbackPhase, resolveCallback } from './utils/html'\nimport type { ContentfulStatusCode, RedirectStatusCode, StatusCode } from './utils/http-status'\nimport type { BaseMime } from './utils/mime'\nimport type { InvalidJSONValue, IsAny, JSONParsed, JSONValue } from './utils/types'\n\ntype HeaderRecord =\n  | Record<'Content-Type', BaseMime>\n  | Record<ResponseHeader, string | string[]>\n  | Record<string, string | string[]>\n\n/**\n * Data type can be a string, ArrayBuffer, Uint8Array (buffer), or ReadableStream.\n */\nexport type Data = string | ArrayBuffer | ReadableStream | Uint8Array<ArrayBuffer>\n\n/**\n * Interface for the execution context in a web worker or similar environment.\n */\nexport interface ExecutionContext {\n  /**\n   * Extends the lifetime of the event callback until the promise is settled.\n   *\n   * @param promise - A promise to wait for.\n   */\n  waitUntil(promise: Promise<unknown>): void\n  /**\n   * Allows the event to be passed through to subsequent event listeners.\n   */\n  passThroughOnException(): void\n  /**\n   * For compatibility with Wrangler 4.x.\n   */\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  props: any\n  /**\n   * For compatibility with Wrangler 4.x.\n   */\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  exports?: any\n}\n\n/**\n * Interface for context variable mapping.\n */\nexport interface ContextVariableMap {}\n\n/**\n * Interface for context renderer.\n */\nexport interface ContextRenderer {}\n\n/**\n * Interface representing a renderer for content.\n *\n * @interface DefaultRenderer\n * @param {string | Promise<string>} content - The content to be rendered, which can be either a string or a Promise resolving to a string.\n * @returns {Response | Promise<Response>} - The response after rendering the content, which can be either a Response or a Promise resolving to a Response.\n */\ninterface DefaultRenderer {\n  (content: string | Promise<string>): Response | Promise<Response>\n}\n\n/**\n * Renderer type which can either be a ContextRenderer or DefaultRenderer.\n */\nexport type Renderer = ContextRenderer extends Function ? ContextRenderer : DefaultRenderer\n\n/**\n * Extracts the props for the renderer.\n */\nexport type PropsForRenderer = [...Required<Parameters<Renderer>>] extends [unknown, infer Props]\n  ? Props\n  : unknown\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type Layout<T = Record<string, any>> = (props: T) => any\n\n/**\n * Interface for getting context variables.\n *\n * @template E - Environment type.\n */\ninterface Get<E extends Env> {\n  <Key extends keyof E['Variables']>(key: Key): E['Variables'][Key]\n  <Key extends keyof ContextVariableMap>(key: Key): ContextVariableMap[Key]\n}\n\n/**\n * Interface for setting context variables.\n *\n * @template E - Environment type.\n */\ninterface Set<E extends Env> {\n  <Key extends keyof E['Variables']>(key: Key, value: E['Variables'][Key]): void\n  <Key extends keyof ContextVariableMap>(key: Key, value: ContextVariableMap[Key]): void\n}\n\n/**\n * Interface for creating a new response.\n */\ninterface NewResponse {\n  (data: Data | null, status?: StatusCode, headers?: HeaderRecord): Response\n  (data: Data | null, init?: ResponseOrInit): Response\n}\n\n/**\n * Interface for responding with a body.\n */\ninterface BodyRespond {\n  // if we return content, only allow the status codes that allow for returning the body\n  <T extends Data, U extends ContentfulStatusCode>(\n    data: T,\n    status?: U,\n    headers?: HeaderRecord\n  ): Response & TypedResponse<T, U, 'body'>\n  <T extends Data, U extends ContentfulStatusCode>(\n    data: T,\n    init?: ResponseOrInit<U>\n  ): Response & TypedResponse<T, U, 'body'>\n  <T extends null, U extends StatusCode>(\n    data: T,\n    status?: U,\n    headers?: HeaderRecord\n  ): Response & TypedResponse<null, U, 'body'>\n  <T extends null, U extends StatusCode>(\n    data: T,\n    init?: ResponseOrInit<U>\n  ): Response & TypedResponse<null, U, 'body'>\n}\n\n/**\n * Interface for responding with text.\n *\n * @interface TextRespond\n * @template T - The type of the text content.\n * @template U - The type of the status code.\n *\n * @param {T} text - The text content to be included in the response.\n * @param {U} [status] - An optional status code for the response.\n * @param {HeaderRecord} [headers] - An optional record of headers to include in the response.\n *\n * @returns {Response & TypedResponse<T, U, 'text'>} - The response after rendering the text content, typed with the provided text and status code types.\n */\ninterface TextRespond {\n  <T extends string, U extends ContentfulStatusCode = ContentfulStatusCode>(\n    text: T,\n    status?: U,\n    headers?: HeaderRecord\n  ): Response & TypedResponse<T, U, 'text'>\n  <T extends string, U extends ContentfulStatusCode = ContentfulStatusCode>(\n    text: T,\n    init?: ResponseOrInit<U>\n  ): Response & TypedResponse<T, U, 'text'>\n}\n\n/**\n * Interface for responding with JSON.\n *\n * @interface JSONRespond\n * @template T - The type of the JSON value or simplified unknown type.\n * @template U - The type of the status code.\n *\n * @param {T} object - The JSON object to be included in the response.\n * @param {U} [status] - An optional status code for the response.\n * @param {HeaderRecord} [headers] - An optional record of headers to include in the response.\n *\n * @returns {JSONRespondReturn<T, U>} - The response after rendering the JSON object, typed with the provided object and status code types.\n */\ninterface JSONRespond {\n  <\n    T extends JSONValue | {} | InvalidJSONValue,\n    U extends ContentfulStatusCode = ContentfulStatusCode,\n  >(\n    object: T,\n    status?: U,\n    headers?: HeaderRecord\n  ): JSONRespondReturn<T, U>\n  <\n    T extends JSONValue | {} | InvalidJSONValue,\n    U extends ContentfulStatusCode = ContentfulStatusCode,\n  >(\n    object: T,\n    init?: ResponseOrInit<U>\n  ): JSONRespondReturn<T, U>\n}\n\n/**\n * @template T - The type of the JSON value or simplified unknown type.\n * @template U - The type of the status code.\n *\n * @returns {Response & TypedResponse<JSONParsed<T>, U, 'json'>} - The response after rendering the JSON object, typed with the provided object and status code types.\n */\ntype JSONRespondReturn<\n  T extends JSONValue | {} | InvalidJSONValue,\n  U extends ContentfulStatusCode,\n> = Response & TypedResponse<JSONParsed<T>, U, 'json'>\n\n/**\n * Interface representing a function that responds with HTML content.\n *\n * @param html - The HTML content to respond with, which can be a string or a Promise that resolves to a string.\n * @param status - (Optional) The HTTP status code for the response.\n * @param headers - (Optional) A record of headers to include in the response.\n * @param init - (Optional) The response initialization object.\n *\n * @returns A Response object or a Promise that resolves to a Response object.\n */\ninterface HTMLRespond {\n  <T extends string | Promise<string>>(\n    html: T,\n    status?: ContentfulStatusCode,\n    headers?: HeaderRecord\n  ): T extends string ? Response : Promise<Response>\n  <T extends string | Promise<string>>(\n    html: T,\n    init?: ResponseOrInit<ContentfulStatusCode>\n  ): T extends string ? Response : Promise<Response>\n}\n\n/**\n * Options for configuring the context.\n *\n * @template E - Environment type.\n */\ntype ContextOptions<E extends Env> = {\n  /**\n   * Bindings for the environment.\n   */\n  env: E['Bindings']\n  /**\n   * Execution context for the request.\n   */\n  executionCtx?: FetchEventLike | ExecutionContext | undefined\n  /**\n   * Handler for not found responses.\n   */\n  notFoundHandler?: NotFoundHandler<E>\n  matchResult?: Result<[H, RouterRoute]>\n  path?: string\n}\n\ninterface SetHeadersOptions {\n  append?: boolean\n}\n\ninterface SetHeaders {\n  (name: 'Content-Type', value?: BaseMime, options?: SetHeadersOptions): void\n  (name: ResponseHeader, value?: string, options?: SetHeadersOptions): void\n  (name: string, value?: string, options?: SetHeadersOptions): void\n}\n\ntype ResponseHeadersInit =\n  | [string, string][]\n  | Record<'Content-Type', BaseMime>\n  | Record<ResponseHeader, string>\n  | Record<string, string>\n  | Headers\n\ninterface ResponseInit<T extends StatusCode = StatusCode> {\n  headers?: ResponseHeadersInit\n  status?: T\n  statusText?: string\n}\n\ntype ResponseOrInit<T extends StatusCode = StatusCode> = ResponseInit<T> | Response\n\nexport const TEXT_PLAIN = 'text/plain; charset=UTF-8'\n\nconst setDefaultContentType = (contentType: string, headers?: HeaderRecord): HeaderRecord => {\n  return {\n    'Content-Type': contentType,\n    ...headers,\n  }\n}\n\nconst createResponseInstance = (\n  body?: BodyInit | null | undefined,\n  init?: globalThis.ResponseInit\n): Response => new Response(body, init)\n\nexport class Context<\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  E extends Env = any,\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  P extends string = any,\n  I extends Input = {},\n> {\n  #rawRequest: Request\n  #req: HonoRequest<P, I['out']> | undefined\n  /**\n   * `.env` can get bindings (environment variables, secrets, KV namespaces, D1 database, R2 bucket etc.) in Cloudflare Workers.\n   *\n   * @see {@link https://hono.dev/docs/api/context#env}\n   *\n   * @example\n   * ```ts\n   * // Environment object for Cloudflare Workers\n   * app.get('*', async c => {\n   *   const counter = c.env.COUNTER\n   * })\n   * ```\n   */\n  env: E['Bindings'] = {}\n  #var: Map<unknown, unknown> | undefined\n  finalized: boolean = false\n  /**\n   * `.error` can get the error object from the middleware if the Handler throws an error.\n   *\n   * @see {@link https://hono.dev/docs/api/context#error}\n   *\n   * @example\n   * ```ts\n   * app.use('*', async (c, next) => {\n   *   await next()\n   *   if (c.error) {\n   *     // do something...\n   *   }\n   * })\n   * ```\n   */\n  error: Error | undefined\n\n  #status: StatusCode | undefined\n  #executionCtx: FetchEventLike | ExecutionContext | undefined\n  #res: Response | undefined\n  #layout: Layout<PropsForRenderer & { Layout: Layout }> | undefined\n  #renderer: Renderer | undefined\n  #notFoundHandler: NotFoundHandler<E> | undefined\n  #preparedHeaders: Headers | undefined\n\n  #matchResult: Result<[H, RouterRoute]> | undefined\n  #path: string | undefined\n\n  /**\n   * Creates an instance of the Context class.\n   *\n   * @param req - The Request object.\n   * @param options - Optional configuration options for the context.\n   */\n  constructor(req: Request, options?: ContextOptions<E>) {\n    this.#rawRequest = req\n    if (options) {\n      this.#executionCtx = options.executionCtx\n      this.env = options.env\n      this.#notFoundHandler = options.notFoundHandler\n      this.#path = options.path\n      this.#matchResult = options.matchResult\n    }\n  }\n\n  /**\n   * `.req` is the instance of {@link HonoRequest}.\n   */\n  get req(): HonoRequest<P, I['out']> {\n    this.#req ??= new HonoRequest(this.#rawRequest, this.#path, this.#matchResult)\n    return this.#req\n  }\n\n  /**\n   * @see {@link https://hono.dev/docs/api/context#event}\n   * The FetchEvent associated with the current request.\n   *\n   * @throws Will throw an error if the context does not have a FetchEvent.\n   */\n  get event(): FetchEventLike {\n    if (this.#executionCtx && 'respondWith' in this.#executionCtx) {\n      return this.#executionCtx\n    } else {\n      throw Error('This context has no FetchEvent')\n    }\n  }\n\n  /**\n   * @see {@link https://hono.dev/docs/api/context#executionctx}\n   * The ExecutionContext associated with the current request.\n   *\n   * @throws Will throw an error if the context does not have an ExecutionContext.\n   */\n  get executionCtx(): ExecutionContext {\n    if (this.#executionCtx) {\n      return this.#executionCtx as ExecutionContext\n    } else {\n      throw Error('This context has no ExecutionContext')\n    }\n  }\n\n  /**\n   * @see {@link https://hono.dev/docs/api/context#res}\n   * The Response object for the current request.\n   */\n  get res(): Response {\n    return (this.#res ||= createResponseInstance(null, {\n      headers: (this.#preparedHeaders ??= new Headers()),\n    }))\n  }\n\n  /**\n   * Sets the Response object for the current request.\n   *\n   * @param _res - The Response object to set.\n   */\n  set res(_res: Response | undefined) {\n    if (this.#res && _res) {\n      _res = createResponseInstance(_res.body, _res)\n      for (const [k, v] of this.#res.headers.entries()) {\n        if (k === 'content-type') {\n          continue\n        }\n        if (k === 'set-cookie') {\n          const cookies = this.#res.headers.getSetCookie()\n          _res.headers.delete('set-cookie')\n          for (const cookie of cookies) {\n            _res.headers.append('set-cookie', cookie)\n          }\n        } else {\n          _res.headers.set(k, v)\n        }\n      }\n    }\n    this.#res = _res\n    this.finalized = true\n  }\n\n  /**\n   * `.render()` can create a response within a layout.\n   *\n   * @see {@link https://hono.dev/docs/api/context#render-setrenderer}\n   *\n   * @example\n   * ```ts\n   * app.get('/', (c) => {\n   *   return c.render('Hello!')\n   * })\n   * ```\n   */\n  render: Renderer = (...args) => {\n    this.#renderer ??= (content: string | Promise<string>) => this.html(content)\n    return this.#renderer(...args)\n  }\n\n  /**\n   * Sets the layout for the response.\n   *\n   * @param layout - The layout to set.\n   * @returns The layout function.\n   */\n  setLayout = (\n    layout: Layout<PropsForRenderer & { Layout: Layout }>\n  ): Layout<\n    PropsForRenderer & {\n      Layout: Layout\n    }\n  > => (this.#layout = layout)\n\n  /**\n   * Gets the current layout for the response.\n   *\n   * @returns The current layout function.\n   */\n  getLayout = (): Layout<PropsForRenderer & { Layout: Layout }> | undefined => this.#layout\n\n  /**\n   * `.setRenderer()` can set the layout in the custom middleware.\n   *\n   * @see {@link https://hono.dev/docs/api/context#render-setrenderer}\n   *\n   * @example\n   * ```tsx\n   * app.use('*', async (c, next) => {\n   *   c.setRenderer((content) => {\n   *     return c.html(\n   *       <html>\n   *         <body>\n   *           <p>{content}</p>\n   *         </body>\n   *       </html>\n   *     )\n   *   })\n   *   await next()\n   * })\n   * ```\n   */\n  setRenderer = (renderer: Renderer): void => {\n    this.#renderer = renderer\n  }\n\n  /**\n   * `.header()` can set headers.\n   *\n   * @see {@link https://hono.dev/docs/api/context#header}\n   *\n   * @example\n   * ```ts\n   * app.get('/welcome', (c) => {\n   *   // Set headers\n   *   c.header('X-Message', 'Hello!')\n   *   c.header('Content-Type', 'text/plain')\n   *\n   *   return c.body('Thank you for coming')\n   * })\n   * ```\n   */\n  header: SetHeaders = (name, value, options): void => {\n    if (this.finalized) {\n      this.#res = createResponseInstance((this.#res as Response).body, this.#res)\n    }\n    const headers = this.#res ? this.#res.headers : (this.#preparedHeaders ??= new Headers())\n    if (value === undefined) {\n      headers.delete(name)\n    } else if (options?.append) {\n      headers.append(name, value)\n    } else {\n      headers.set(name, value)\n    }\n  }\n\n  status = (status: StatusCode): void => {\n    this.#status = status\n  }\n\n  /**\n   * `.set()` can set the value specified by the key.\n   *\n   * @see {@link https://hono.dev/docs/api/context#set-get}\n   *\n   * @example\n   * ```ts\n   * app.use('*', async (c, next) => {\n   *   c.set('message', 'Hono is hot!!')\n   *   await next()\n   * })\n   * ```\n   */\n  set: Set<\n    IsAny<E> extends true\n      ? {\n          // eslint-disable-next-line @typescript-eslint/no-explicit-any\n          Variables: ContextVariableMap & Record<string, any>\n        }\n      : E\n  > = (key: string, value: unknown) => {\n    this.#var ??= new Map()\n    this.#var.set(key, value)\n  }\n\n  /**\n   * `.get()` can use the value specified by the key.\n   *\n   * @see {@link https://hono.dev/docs/api/context#set-get}\n   *\n   * @example\n   * ```ts\n   * app.get('/', (c) => {\n   *   const message = c.get('message')\n   *   return c.text(`The message is \"${message}\"`)\n   * })\n   * ```\n   */\n  get: Get<\n    IsAny<E> extends true\n      ? {\n          // eslint-disable-next-line @typescript-eslint/no-explicit-any\n          Variables: ContextVariableMap & Record<string, any>\n        }\n      : E\n  > = (key: string) => {\n    return this.#var ? this.#var.get(key) : undefined\n  }\n\n  /**\n   * `.var` can access the value of a variable.\n   *\n   * @see {@link https://hono.dev/docs/api/context#var}\n   *\n   * @example\n   * ```ts\n   * const result = c.var.client.oneMethod()\n   * ```\n   */\n  // c.var.propName is a read-only\n  get var(): Readonly<\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    ContextVariableMap & (IsAny<E['Variables']> extends true ? Record<string, any> : E['Variables'])\n  > {\n    if (!this.#var) {\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      return {} as any\n    }\n    return Object.fromEntries(this.#var)\n  }\n\n  #newResponse(\n    data: Data | null,\n    arg?: StatusCode | ResponseOrInit,\n    headers?: HeaderRecord\n  ): Response {\n    const responseHeaders = this.#res\n      ? new Headers(this.#res.headers)\n      : (this.#preparedHeaders ?? new Headers())\n\n    if (typeof arg === 'object' && 'headers' in arg) {\n      const argHeaders = arg.headers instanceof Headers ? arg.headers : new Headers(arg.headers)\n      for (const [key, value] of argHeaders) {\n        if (key.toLowerCase() === 'set-cookie') {\n          responseHeaders.append(key, value)\n        } else {\n          responseHeaders.set(key, value)\n        }\n      }\n    }\n\n    if (headers) {\n      for (const [k, v] of Object.entries(headers)) {\n        if (typeof v === 'string') {\n          responseHeaders.set(k, v)\n        } else {\n          responseHeaders.delete(k)\n          for (const v2 of v) {\n            responseHeaders.append(k, v2)\n          }\n        }\n      }\n    }\n\n    const status = typeof arg === 'number' ? arg : (arg?.status ?? this.#status)\n    return createResponseInstance(data, { status, headers: responseHeaders })\n  }\n\n  newResponse: NewResponse = (...args) => this.#newResponse(...(args as Parameters<NewResponse>))\n\n  /**\n   * `.body()` can return the HTTP response.\n   * You can set headers with `.header()` and set HTTP status code with `.status`.\n   * This can also be set in `.text()`, `.json()` and so on.\n   *\n   * @see {@link https://hono.dev/docs/api/context#body}\n   *\n   * @example\n   * ```ts\n   * app.get('/welcome', (c) => {\n   *   // Set headers\n   *   c.header('X-Message', 'Hello!')\n   *   c.header('Content-Type', 'text/plain')\n   *   // Set HTTP status code\n   *   c.status(201)\n   *\n   *   // Return the response body\n   *   return c.body('Thank you for coming')\n   * })\n   * ```\n   */\n  body: BodyRespond = (\n    data: Data | null,\n    arg?: StatusCode | RequestInit,\n    headers?: HeaderRecord\n  ): ReturnType<BodyRespond> => this.#newResponse(data, arg, headers) as ReturnType<BodyRespond>\n\n  /**\n   * `.text()` can render text as `Content-Type:text/plain`.\n   *\n   * @see {@link https://hono.dev/docs/api/context#text}\n   *\n   * @example\n   * ```ts\n   * app.get('/say', (c) => {\n   *   return c.text('Hello!')\n   * })\n   * ```\n   */\n  text: TextRespond = (\n    text: string,\n    arg?: ContentfulStatusCode | ResponseOrInit,\n    headers?: HeaderRecord\n  ): ReturnType<TextRespond> => {\n    return !this.#preparedHeaders && !this.#status && !arg && !headers && !this.finalized\n      ? (new Response(text) as ReturnType<TextRespond>)\n      : (this.#newResponse(\n          text,\n          arg,\n          setDefaultContentType(TEXT_PLAIN, headers)\n        ) as ReturnType<TextRespond>)\n  }\n\n  /**\n   * `.json()` can render JSON as `Content-Type:application/json`.\n   *\n   * @see {@link https://hono.dev/docs/api/context#json}\n   *\n   * @example\n   * ```ts\n   * app.get('/api', (c) => {\n   *   return c.json({ message: 'Hello!' })\n   * })\n   * ```\n   */\n  json: JSONRespond = <\n    T extends JSONValue | {} | InvalidJSONValue,\n    U extends ContentfulStatusCode = ContentfulStatusCode,\n  >(\n    object: T,\n    arg?: U | ResponseOrInit<U>,\n    headers?: HeaderRecord\n  ): JSONRespondReturn<T, U> => {\n    return this.#newResponse(\n      JSON.stringify(object),\n      arg,\n      setDefaultContentType('application/json', headers)\n    ) /* eslint-disable @typescript-eslint/no-explicit-any */ as any\n  }\n\n  html: HTMLRespond = (\n    html: string | Promise<string>,\n    arg?: ContentfulStatusCode | ResponseOrInit<ContentfulStatusCode>,\n    headers?: HeaderRecord\n  ): Response | Promise<Response> => {\n    const res = (html: string) =>\n      this.#newResponse(html, arg, setDefaultContentType('text/html; charset=UTF-8', headers))\n    return typeof html === 'object'\n      ? resolveCallback(html, HtmlEscapedCallbackPhase.Stringify, false, {}).then(res)\n      : res(html)\n  }\n\n  /**\n   * `.redirect()` can Redirect, default status code is 302.\n   *\n   * @see {@link https://hono.dev/docs/api/context#redirect}\n   *\n   * @example\n   * ```ts\n   * app.get('/redirect', (c) => {\n   *   return c.redirect('/')\n   * })\n   * app.get('/redirect-permanently', (c) => {\n   *   return c.redirect('/', 301)\n   * })\n   * ```\n   */\n  redirect = <T extends RedirectStatusCode = 302>(\n    location: string | URL,\n    status?: T\n  ): Response & TypedResponse<undefined, T, 'redirect'> => {\n    const locationString = String(location)\n    this.header(\n      'Location',\n      // Multibyes should be encoded\n      // eslint-disable-next-line no-control-regex\n      !/[^\\x00-\\xFF]/.test(locationString) ? locationString : encodeURI(locationString)\n    )\n    return this.newResponse(null, status ?? 302) as any\n  }\n\n  /**\n   * `.notFound()` can return the Not Found Response.\n   *\n   * @see {@link https://hono.dev/docs/api/context#notfound}\n   *\n   * @example\n   * ```ts\n   * app.get('/notfound', (c) => {\n   *   return c.notFound()\n   * })\n   * ```\n   */\n  notFound = (): ReturnType<NotFoundHandler> => {\n    this.#notFoundHandler ??= () => createResponseInstance()\n    return this.#notFoundHandler(this)\n  }\n}\n"
  },
  {
    "path": "src/helper/accepts/accepts.test.ts",
    "content": "import { Hono } from '../..'\nimport { parseAccept } from '../../utils/accept'\nimport type { Accept, acceptsConfig, acceptsOptions } from './accepts'\nimport { accepts, defaultMatch } from './accepts'\n\ndescribe('parseAccept', () => {\n  test('should parse accept header', () => {\n    const acceptHeader =\n      'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8;level=1;foo=bar'\n    const accepts = parseAccept(acceptHeader)\n    expect(accepts).toEqual([\n      { type: 'text/html', params: {}, q: 1 },\n      { type: 'application/xhtml+xml', params: {}, q: 1 },\n      { type: 'image/webp', params: {}, q: 1 },\n      { type: 'application/xml', params: { q: '0.9' }, q: 0.9 },\n      { type: '*/*', params: { q: '0.8', level: '1', foo: 'bar' }, q: 0.8 },\n    ])\n  })\n})\n\ndescribe('defaultMatch', () => {\n  test('should return default support', () => {\n    const accepts: Accept[] = [\n      { type: 'text/html', params: {}, q: 1 },\n      { type: 'application/xhtml+xml', params: {}, q: 1 },\n      { type: 'application/xml', params: { q: '0.9' }, q: 0.9 },\n      { type: 'image/webp', params: {}, q: 1 },\n      { type: '*/*', params: { q: '0.8' }, q: 0.8 },\n    ]\n    const config: acceptsConfig = {\n      header: 'Accept',\n      supports: ['text/html'],\n      default: 'text/html',\n    }\n    const result = defaultMatch(accepts, config)\n    expect(result).toBe('text/html')\n  })\n})\n\ndescribe('accepts', () => {\n  test('should return matched support', () => {\n    const c = {\n      req: {\n        header: () => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',\n      },\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    } as any\n    const options: acceptsConfig = {\n      header: 'Accept',\n      supports: ['application/xml', 'text/html'],\n      default: 'application/json',\n    }\n    const result = accepts(c, options)\n    expect(result).toBe('text/html')\n  })\n\n  test('should return default support if no matched support', () => {\n    const c = {\n      req: {\n        header: () => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',\n      },\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    } as any\n    const options: acceptsConfig = {\n      header: 'Accept',\n      supports: ['application/json'],\n      default: 'text/html',\n    }\n    const result = accepts(c, options)\n    expect(result).toBe('text/html')\n  })\n\n  test('should return default support if no accept header', () => {\n    const c = {\n      req: {\n        header: () => undefined,\n      },\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    } as any\n    const options: acceptsConfig = {\n      header: 'Accept',\n      supports: ['application/json'],\n      default: 'text/html',\n    }\n    const result = accepts(c, options)\n    expect(result).toBe('text/html')\n  })\n\n  test('should return matched support with custom match function', () => {\n    const c = {\n      req: {\n        header: () => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',\n      },\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    } as any\n    // this match function will return the least q value\n    const match = (accepts: Accept[], config: acceptsConfig) => {\n      const { supports, default: defaultSupport } = config\n      const accept = accepts\n        .sort((a, b) => a.q - b.q)\n        .find((accept) => supports.includes(accept.type))\n      return accept ? accept.type : defaultSupport\n    }\n    const options: acceptsOptions = {\n      header: 'Accept',\n      supports: ['application/xml', 'text/html'],\n      default: 'application/json',\n      match,\n    }\n    const result = accepts(c, options)\n    expect(result).toBe('application/xml')\n  })\n})\n\ndescribe('Usage', () => {\n  test('decide compression by Accept-Encoding header', async () => {\n    const app = new Hono()\n    app.get('/compressed', async (c) => {\n      const encoding = accepts(c, {\n        header: 'Accept-Encoding',\n        supports: ['gzip', 'deflate'],\n        default: 'identity',\n      })\n      const COMPRESS_DATA = 'COMPRESS_DATA'\n      const readable = new ReadableStream({\n        start(controller) {\n          controller.enqueue(new TextEncoder().encode(COMPRESS_DATA))\n          controller.close()\n        },\n      })\n\n      if (encoding === 'gzip') {\n        c.header('Content-Encoding', 'gzip')\n        return c.body(readable.pipeThrough(new CompressionStream('gzip')))\n      }\n      if (encoding === 'deflate') {\n        c.header('Content-Encoding', 'deflate')\n        return c.body(readable.pipeThrough(new CompressionStream('deflate')))\n      }\n\n      c.body(COMPRESS_DATA)\n    })\n\n    const req1 = await app.request('/compressed', { headers: { 'Accept-Encoding': 'deflate' } })\n    const req2 = await app.request('/compressed', { headers: { 'Accept-Encoding': 'gzip' } })\n    const req3 = await app.request('/compressed', {\n      headers: { 'Accept-Encoding': 'gzip;q=0.5,deflate' },\n    })\n    const req4 = await app.request('/compressed', { headers: { 'Accept-Encoding': 'br' } })\n\n    expect(req1.headers.get('Content-Encoding')).toBe('deflate')\n    expect(req2.headers.get('Content-Encoding')).toBe('gzip')\n    expect(req3.headers.get('Content-Encoding')).toBe('deflate')\n    expect(req4.headers.get('Content-Encoding')).toBeNull()\n  })\n\n  test('decide language by Accept-Language header', async () => {\n    const app = new Hono()\n    const SUPPORTED_LANGS = ['en', 'ja', 'zh']\n    app.get('/*', async (c) => {\n      const lang = accepts(c, {\n        header: 'Accept-Language',\n        supports: SUPPORTED_LANGS,\n        default: 'en',\n      })\n      const isLangedPath = SUPPORTED_LANGS.some((l) => c.req.path.startsWith(`/${l}`))\n      if (isLangedPath) {\n        return c.body(`lang: ${lang}`)\n      }\n\n      return c.redirect(`/${lang}${c.req.path}`)\n    })\n\n    const req1 = await app.request('/foo', { headers: { 'Accept-Language': 'en=0.8,ja' } })\n    const req2 = await app.request('/en/foo', { headers: { 'Accept-Language': 'en' } })\n\n    expect(req1.status).toBe(302)\n    expect(req1.headers.get('Location')).toBe('/ja/foo')\n    expect(await req2.text()).toBe('lang: en')\n  })\n})\n"
  },
  {
    "path": "src/helper/accepts/accepts.ts",
    "content": "import type { Context } from '../../context'\nimport { parseAccept } from '../../utils/accept'\nimport type { AcceptHeader } from '../../utils/headers'\n\nexport interface Accept {\n  type: string\n  params: Record<string, string>\n  q: number\n}\n\nexport interface acceptsConfig {\n  header: AcceptHeader\n  supports: string[]\n  default: string\n}\n\nexport interface acceptsOptions extends acceptsConfig {\n  match?: (accepts: Accept[], config: acceptsConfig) => string\n}\n\nexport const defaultMatch = (accepts: Accept[], config: acceptsConfig): string => {\n  const { supports, default: defaultSupport } = config\n  const accept = accepts.sort((a, b) => b.q - a.q).find((accept) => supports.includes(accept.type))\n  return accept ? accept.type : defaultSupport\n}\n\n/**\n * Match the accept header with the given options.\n * @example\n * ```ts\n * app.get('/users', (c) => {\n *   const lang = accepts(c, {\n *     header: 'Accept-Language',\n *     supports: ['en', 'zh'],\n *     default: 'en',\n *   })\n * })\n * ```\n */\nexport const accepts = (c: Context, options: acceptsOptions): string => {\n  const acceptHeader = c.req.header(options.header)\n  if (!acceptHeader) {\n    return options.default\n  }\n  const accepts = parseAccept(acceptHeader)\n  const match = options.match || defaultMatch\n\n  return match(accepts, options)\n}\n"
  },
  {
    "path": "src/helper/accepts/index.ts",
    "content": "/**\n * @module\n * Accepts Helper for Hono.\n */\n\nexport { accepts } from './accepts'\n"
  },
  {
    "path": "src/helper/adapter/index.test.ts",
    "content": "import { Hono } from '../../hono'\nimport { env, getRuntimeKey } from '.'\n\ndescribe('getRuntimeKey', () => {\n  it('Should return the current runtime key', () => {\n    // Now, using the `bun run test` command.\n    // But `vitest` depending Node.js will run this test so the RuntimeKey will be `node`.\n    expect(getRuntimeKey()).toBe('node')\n  })\n})\n\ndescribe('env', () => {\n  describe('Types', () => {\n    type Env = {\n      Bindings: {\n        MY_VAR: string\n      }\n    }\n\n    it('Should not throw type errors with env has generics', () => {\n      const app = new Hono()\n      app.get('/var', (c) => {\n        const { MY_VAR } = env<{ MY_VAR: string }>(c)\n        expectTypeOf<string>(MY_VAR)\n        return c.json({\n          var: MY_VAR,\n        })\n      })\n    })\n\n    it('Should not throw type errors with Hono has generics', () => {\n      const app = new Hono<Env>()\n\n      app.get('/var', (c) => {\n        const { MY_VAR } = env(c)\n        expectTypeOf<string>(MY_VAR)\n        return c.json({\n          var: MY_VAR,\n        })\n      })\n    })\n\n    it('Should not throw type errors with env and Hono have generics', () => {\n      const app = new Hono<Env>()\n\n      app.get('/var', (c) => {\n        const { MY_VAR } = env<{ MY_VAR: string }>(c)\n        expectTypeOf<string>(MY_VAR)\n        return c.json({\n          var: MY_VAR,\n        })\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/helper/adapter/index.ts",
    "content": "/**\n * @module\n * Adapter Helper for Hono.\n */\n\nimport type { Context } from '../../context'\n\nexport type Runtime = 'node' | 'deno' | 'bun' | 'workerd' | 'fastly' | 'edge-light' | 'other'\n\nexport const env = <\n  T extends Record<string, unknown>,\n  C extends Context = Context<{\n    Bindings: T\n  }>,\n>(\n  c: T extends Record<string, unknown> ? Context : C,\n  runtime?: Runtime\n): T & C['env'] => {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  const global = globalThis as any\n  const globalEnv = global?.process?.env as T\n\n  runtime ??= getRuntimeKey()\n\n  const runtimeEnvHandlers: Record<string, () => T> = {\n    bun: () => globalEnv,\n    node: () => globalEnv,\n    'edge-light': () => globalEnv,\n    deno: () => {\n      // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n      // @ts-ignore\n      return Deno.env.toObject() as T\n    },\n    workerd: () => c.env,\n    // On Fastly Compute, you can use the ConfigStore to manage user-defined data.\n    fastly: () => ({}) as T,\n    other: () => ({}) as T,\n  }\n\n  return runtimeEnvHandlers[runtime]()\n}\n\nexport const knownUserAgents: Partial<Record<Runtime, string>> = {\n  deno: 'Deno',\n  bun: 'Bun',\n  workerd: 'Cloudflare-Workers',\n  node: 'Node.js',\n}\n\nexport const getRuntimeKey = (): Runtime => {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  const global = globalThis as any\n\n  // check if the current runtime supports navigator.userAgent\n  const userAgentSupported =\n    typeof navigator !== 'undefined' && typeof navigator.userAgent === 'string'\n\n  // if supported, check the user agent\n  if (userAgentSupported) {\n    for (const [runtimeKey, userAgent] of Object.entries(knownUserAgents)) {\n      if (checkUserAgentEquals(userAgent)) {\n        return runtimeKey as Runtime\n      }\n    }\n  }\n\n  // check if running on Edge Runtime\n  if (typeof global?.EdgeRuntime === 'string') {\n    return 'edge-light'\n  }\n\n  // check if running on Fastly\n  if (global?.fastly !== undefined) {\n    return 'fastly'\n  }\n\n  // userAgent isn't supported before Node v21.1.0; so fallback to the old way\n  if (global?.process?.release?.name === 'node') {\n    return 'node'\n  }\n\n  // couldn't detect the runtime\n  return 'other'\n}\n\nexport const checkUserAgentEquals = (platform: string): boolean => {\n  const userAgent = navigator.userAgent\n\n  return userAgent.startsWith(platform)\n}\n"
  },
  {
    "path": "src/helper/conninfo/index.ts",
    "content": "/**\n * @module\n * ConnInfo Helper for Hono.\n */\n\nexport type { AddressType, NetAddrInfo, ConnInfo, GetConnInfo } from './types'\n"
  },
  {
    "path": "src/helper/conninfo/types.ts",
    "content": "import type { Context } from '../../context'\n\nexport type AddressType = 'IPv6' | 'IPv4' | undefined\n\nexport type NetAddrInfo = {\n  /**\n   * Transport protocol type\n   */\n  transport?: 'tcp' | 'udp'\n  /**\n   * Transport port number\n   */\n  port?: number\n\n  address?: string\n  addressType?: AddressType\n} & (\n  | {\n      /**\n       * Host name such as IP Addr\n       */\n      address: string\n\n      /**\n       * Host name type\n       */\n      addressType: AddressType\n    }\n  | {}\n)\n\n/**\n * HTTP Connection information\n */\nexport interface ConnInfo {\n  /**\n   * Remote information\n   */\n  remote: NetAddrInfo\n}\n\n/**\n * Helper type\n */\nexport type GetConnInfo = (c: Context) => ConnInfo\n"
  },
  {
    "path": "src/helper/cookie/index.test.ts",
    "content": "import { Hono } from '../../hono'\nimport {\n  deleteCookie,\n  getCookie,\n  getSignedCookie,\n  setCookie,\n  setSignedCookie,\n  generateCookie,\n  generateSignedCookie,\n} from '.'\n\ndescribe('Cookie Middleware', () => {\n  describe('Parse cookie', () => {\n    const apps: Record<string, Hono> = {}\n    apps['get by name'] = (() => {\n      const app = new Hono()\n\n      app.get('/cookie', (c) => {\n        const yummyCookie = getCookie(c, 'yummy_cookie')\n        const tastyCookie = getCookie(c, 'tasty_cookie')\n        const res = new Response('Good cookie')\n        if (yummyCookie && tastyCookie) {\n          res.headers.set('Yummy-Cookie', yummyCookie)\n          res.headers.set('Tasty-Cookie', tastyCookie)\n        }\n        return res\n      })\n\n      return app\n    })()\n\n    apps['get all as an object'] = (() => {\n      const app = new Hono()\n\n      app.get('/cookie', (c) => {\n        const { yummy_cookie: yummyCookie, tasty_cookie: tastyCookie } = getCookie(c)\n        const res = new Response('Good cookie')\n        res.headers.set('Yummy-Cookie', yummyCookie)\n        res.headers.set('Tasty-Cookie', tastyCookie)\n        return res\n      })\n\n      return app\n    })()\n\n    describe.each(Object.keys(apps))('%s', (name) => {\n      const app = apps[name]\n      it('Parse cookie with getCookie()', async () => {\n        const req = new Request('http://localhost/cookie')\n        const cookieString = 'yummy_cookie=choco; tasty_cookie = strawberry'\n        req.headers.set('Cookie', cookieString)\n        const res = await app.request(req)\n\n        expect(res.headers.get('Yummy-Cookie')).toBe('choco')\n        expect(res.headers.get('Tasty-Cookie')).toBe('strawberry')\n      })\n    })\n\n    const app = new Hono()\n\n    app.get('/cookie-signed-get-all', async (c) => {\n      const secret = 'secret lucky charm'\n      const { fortune_cookie: fortuneCookie, fruit_cookie: fruitCookie } = await getSignedCookie(\n        c,\n        secret\n      )\n      const res = new Response('Signed fortune cookie')\n      if (typeof fortuneCookie !== 'undefined' && typeof fruitCookie !== 'undefined') {\n        // just examples for tests sake\n        res.headers.set('Fortune-Cookie', fortuneCookie || 'INVALID')\n        res.headers.set('Fruit-Cookie', fruitCookie || 'INVALID')\n      }\n      return res\n    })\n\n    app.get('/cookie-signed-get-one', async (c) => {\n      const secret = 'secret lucky charm'\n      const fortuneCookie = await getSignedCookie(c, secret, 'fortune_cookie')\n      const res = new Response('Signed fortune cookie')\n      if (typeof fortuneCookie !== 'undefined') {\n        // just an example for tests sake\n        res.headers.set('Fortune-Cookie', fortuneCookie || 'INVALID')\n      }\n      return res\n    })\n\n    it('Get signed cookies', async () => {\n      const req = new Request('http://localhost/cookie-signed-get-all')\n      const cookieString =\n        'fortune_cookie=lots-of-money.UO6vMygDM6NCDU4LdvBnzdVb2Xcdj+h+ZTnmS8X7iH8%3D; fruit_cookie=mango.lRwgtW9ooM9%2Fd9ZZA%2FInNRG64CbQsfWGXQyFLPM9520%3D'\n      req.headers.set('Cookie', cookieString)\n      const res = await app.request(req)\n      expect(res.headers.get('Fortune-Cookie')).toBe('lots-of-money')\n      expect(res.headers.get('Fruit-Cookie')).toBe('mango')\n    })\n\n    it('Get signed cookies invalid signature', async () => {\n      const req = new Request('http://localhost/cookie-signed-get-all')\n      // fruit_cookie has invalid signature\n      const cookieString =\n        'fortune_cookie=lots-of-money.UO6vMygDM6NCDU4LdvBnzdVb2Xcdj+h+ZTnmS8X7iH8%3D; fruit_cookie=mango.LAa7RX43t2vCrLNcKmNG65H41OkyV02sraRPuY5RuVg%3D'\n      req.headers.set('Cookie', cookieString)\n      const res = await app.request(req)\n      expect(res.headers.get('Fortune-Cookie')).toBe('lots-of-money')\n      expect(res.headers.get('Fruit-Cookie')).toBe('INVALID')\n    })\n\n    it('Get signed cookie', async () => {\n      const req = new Request('http://localhost/cookie-signed-get-one')\n      const cookieString =\n        'fortune_cookie=lots-of-money.UO6vMygDM6NCDU4LdvBnzdVb2Xcdj+h+ZTnmS8X7iH8%3D; fruit_cookie=mango.lRwgtW9ooM9%2Fd9ZZA%2FInNRG64CbQsfWGXQyFLPM9520%3D'\n      req.headers.set('Cookie', cookieString)\n      const res = await app.request(req)\n      expect(res.headers.get('Fortune-Cookie')).toBe('lots-of-money')\n    })\n\n    it('Get signed cookie with invalid signature', async () => {\n      const req = new Request('http://localhost/cookie-signed-get-one')\n      // fortune_cookie has invalid signature\n      const cookieString =\n        'fortune_cookie=lots-of-money.LAa7RX43t2vCrLNcKmNG65H41OkyV02sraRPuY5RuVg=; fruit_cookie=mango.lRwgtW9ooM9%2Fd9ZZA%2FInNRG64CbQsfWGXQyFLPM9520%3D'\n      req.headers.set('Cookie', cookieString)\n      const res = await app.request(req)\n      expect(res.headers.get('Fortune-Cookie')).toBe('INVALID')\n    })\n\n    describe('get null if the value is undefined', () => {\n      const app = new Hono()\n\n      app.get('/cookie', (c) => {\n        const yummyCookie = getCookie(c, 'yummy_cookie')\n        const res = new Response('Good cookie')\n        if (yummyCookie) {\n          res.headers.set('Yummy-Cookie', yummyCookie)\n        }\n        return res\n      })\n\n      it('Should be null', async () => {\n        const req = new Request('http://localhost/cookie')\n        const cookieString = 'yummy_cookie='\n        req.headers.set('Cookie', cookieString)\n        const res = await app.request(req)\n        expect(res.headers.get('Yummy-Cookie')).toBe(null)\n      })\n    })\n  })\n\n  describe('Set cookie', () => {\n    const app = new Hono()\n\n    app.get('/set-cookie', (c) => {\n      setCookie(c, 'delicious_cookie', 'macha')\n      return c.text('Give cookie')\n    })\n\n    it('Set cookie with setCookie()', async () => {\n      const res = await app.request('http://localhost/set-cookie')\n      expect(res.status).toBe(200)\n      const header = res.headers.get('Set-Cookie')\n      expect(header).toBe('delicious_cookie=macha; Path=/')\n    })\n\n    app.get('/a/set-cookie-path', (c) => {\n      setCookie(c, 'delicious_cookie', 'macha', { path: '/a' })\n      return c.text('Give cookie')\n    })\n\n    it('Set cookie with setCookie() and path option', async () => {\n      const res = await app.request('http://localhost/a/set-cookie-path')\n      expect(res.status).toBe(200)\n      const header = res.headers.get('Set-Cookie')\n      expect(header).toBe('delicious_cookie=macha; Path=/a')\n    })\n\n    app.get('/set-signed-cookie', async (c) => {\n      const secret = 'secret chocolate chips'\n      await setSignedCookie(c, 'delicious_cookie', 'macha', secret)\n      return c.text('Give signed cookie')\n    })\n\n    it('Set signed cookie with setSignedCookie()', async () => {\n      const res = await app.request('http://localhost/set-signed-cookie')\n      expect(res.status).toBe(200)\n      const header = res.headers.get('Set-Cookie')\n      expect(header).toBe(\n        'delicious_cookie=macha.diubJPY8O7hI1pLa42QSfkPiyDWQ0I4DnlACH%2FN2HaA%3D; Path=/'\n      )\n    })\n\n    app.get('/a/set-signed-cookie-path', async (c) => {\n      const secret = 'secret chocolate chips'\n      await setSignedCookie(c, 'delicious_cookie', 'macha', secret, { path: '/a' })\n      return c.text('Give signed cookie')\n    })\n\n    it('Set signed cookie with setSignedCookie() and path option', async () => {\n      const res = await app.request('http://localhost/a/set-signed-cookie-path')\n      expect(res.status).toBe(200)\n      const header = res.headers.get('Set-Cookie')\n      expect(header).toBe(\n        'delicious_cookie=macha.diubJPY8O7hI1pLa42QSfkPiyDWQ0I4DnlACH%2FN2HaA%3D; Path=/a'\n      )\n    })\n\n    app.get('/get-secure-prefix-cookie', async (c) => {\n      const cookie = getCookie(c, 'delicious_cookie', 'secure')\n      if (cookie) {\n        return c.text(cookie)\n      } else {\n        return c.notFound()\n      }\n    })\n\n    app.get('/get-host-prefix-cookie', async (c) => {\n      const cookie = getCookie(c, 'delicious_cookie', 'host')\n      if (cookie) {\n        return c.text(cookie)\n      } else {\n        return c.notFound()\n      }\n    })\n\n    app.get('/set-secure-prefix-cookie', (c) => {\n      setCookie(c, 'delicious_cookie', 'macha', {\n        prefix: 'secure',\n        secure: false, // this will be ignore\n      })\n      return c.text('Set secure prefix cookie')\n    })\n\n    it('Set cookie with secure prefix', async () => {\n      const res = await app.request('http://localhost/set-secure-prefix-cookie')\n      expect(res.status).toBe(200)\n      const header = res.headers.get('Set-Cookie')\n      expect(header).toBe('__Secure-delicious_cookie=macha; Path=/; Secure')\n    })\n\n    it('Get cookie with secure prefix', async () => {\n      const setCookie = await app.request('http://localhost/set-secure-prefix-cookie')\n      const header = setCookie.headers.get('Set-Cookie')\n      if (!header) {\n        assert.fail('invalid header')\n      }\n      const res = await app.request('http://localhost/get-secure-prefix-cookie', {\n        headers: {\n          Cookie: header,\n        },\n      })\n      const response = await res.text()\n      expect(res.status).toBe(200)\n      expect(response).toBe('macha')\n    })\n\n    app.get('/set-host-prefix-cookie', (c) => {\n      setCookie(c, 'delicious_cookie', 'macha', {\n        prefix: 'host',\n        path: '/foo', // this will be ignored\n        domain: 'example.com', // this will be ignored\n        secure: false, // this will be ignored\n      })\n      return c.text('Set host prefix cookie')\n    })\n\n    it('Set cookie with host prefix', async () => {\n      const res = await app.request('http://localhost/set-host-prefix-cookie')\n      expect(res.status).toBe(200)\n      const header = res.headers.get('Set-Cookie')\n      expect(header).toBe('__Host-delicious_cookie=macha; Path=/; Secure')\n    })\n\n    it('Get cookie with host prefix', async () => {\n      const setCookie = await app.request('http://localhost/set-host-prefix-cookie')\n      const header = setCookie.headers.get('Set-Cookie')\n      if (!header) {\n        assert.fail('invalid header')\n      }\n      const res = await app.request('http://localhost/get-host-prefix-cookie', {\n        headers: {\n          Cookie: header,\n        },\n      })\n      const response = await res.text()\n      expect(res.status).toBe(200)\n      expect(response).toBe('macha')\n    })\n\n    app.get('/set-signed-secure-prefix-cookie', async (c) => {\n      await setSignedCookie(c, 'delicious_cookie', 'macha', 'secret choco chips', {\n        prefix: 'secure',\n      })\n      return c.text('Set secure prefix cookie')\n    })\n\n    it('Set signed cookie with secure prefix', async () => {\n      const res = await app.request('http://localhost/set-signed-secure-prefix-cookie')\n      expect(res.status).toBe(200)\n      const header = res.headers.get('Set-Cookie')\n      expect(header).toBe(\n        '__Secure-delicious_cookie=macha.i225faTyCrJUY8TvpTuJHI20HBWbQ89B4GV7lT4E%2FB0%3D; Path=/; Secure'\n      )\n    })\n\n    app.get('/set-signed-host-prefix-cookie', async (c) => {\n      await setSignedCookie(c, 'delicious_cookie', 'macha', 'secret choco chips', {\n        prefix: 'host',\n        domain: 'example.com', // this will be ignored\n        path: 'example.com', // thi will be ignored\n        secure: false, // this will be ignored\n      })\n      return c.text('Set host prefix cookie')\n    })\n\n    it('Set signed cookie with host prefix', async () => {\n      const res = await app.request('http://localhost/set-signed-host-prefix-cookie')\n      expect(res.status).toBe(200)\n      const header = res.headers.get('Set-Cookie')\n      expect(header).toBe(\n        '__Host-delicious_cookie=macha.i225faTyCrJUY8TvpTuJHI20HBWbQ89B4GV7lT4E%2FB0%3D; Path=/; Secure'\n      )\n    })\n\n    app.get('/set-cookie-complex', (c) => {\n      setCookie(c, 'great_cookie', 'banana', {\n        path: '/',\n        secure: true,\n        domain: 'example.com',\n        httpOnly: true,\n        maxAge: 1000,\n        expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)),\n        sameSite: 'Strict',\n      })\n      return c.text('Give cookie')\n    })\n\n    it('Complex pattern', async () => {\n      const res = await app.request('http://localhost/set-cookie-complex')\n      expect(res.status).toBe(200)\n      const header = res.headers.get('Set-Cookie')\n      expect(header).toBe(\n        'great_cookie=banana; Max-Age=1000; Domain=example.com; Path=/; Expires=Sun, 24 Dec 2000 10:30:59 GMT; HttpOnly; Secure; SameSite=Strict'\n      )\n    })\n\n    app.get('/set-signed-cookie-complex', async (c) => {\n      const secret = 'secret chocolate chips'\n      await setSignedCookie(c, 'great_cookie', 'banana', secret, {\n        path: '/',\n        secure: true,\n        domain: 'example.com',\n        httpOnly: true,\n        maxAge: 1000,\n        expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)),\n        sameSite: 'Strict',\n      })\n      return c.text('Give signed cookie')\n    })\n\n    it('Complex pattern (signed)', async () => {\n      const res = await app.request('http://localhost/set-signed-cookie-complex')\n      expect(res.status).toBe(200)\n      const header = res.headers.get('Set-Cookie')\n      expect(header).toBe(\n        'great_cookie=banana.hSo6gB7YT2db0WBiEAakEmh7dtwEL0DSp76G23WvHuQ%3D; Max-Age=1000; Domain=example.com; Path=/; Expires=Sun, 24 Dec 2000 10:30:59 GMT; HttpOnly; Secure; SameSite=Strict'\n      )\n    })\n\n    app.get('/set-cookie-multiple', (c) => {\n      setCookie(c, 'delicious_cookie', 'macha')\n      setCookie(c, 'delicious_cookie', 'choco')\n      return c.text('Give cookie')\n    })\n\n    it('Multiple values', async () => {\n      const res = await app.request('http://localhost/set-cookie-multiple')\n      expect(res.status).toBe(200)\n      const header = res.headers.get('Set-Cookie')\n      expect(header).toBe('delicious_cookie=macha; Path=/, delicious_cookie=choco; Path=/')\n    })\n  })\n\n  describe('Delete cookie', () => {\n    const app = new Hono()\n\n    app.get('/delete-cookie', (c) => {\n      deleteCookie(c, 'delicious_cookie')\n      return c.text('Give cookie')\n    })\n\n    it('Delete cookie', async () => {\n      const res2 = await app.request('http://localhost/delete-cookie')\n      expect(res2.status).toBe(200)\n      const header2 = res2.headers.get('Set-Cookie')\n      expect(header2).toBe('delicious_cookie=; Max-Age=0; Path=/')\n    })\n\n    app.get('/delete-cookie-multiple', (c) => {\n      deleteCookie(c, 'delicious_cookie')\n      deleteCookie(c, 'delicious_cookie2')\n      return c.text('Give cookie')\n    })\n\n    it('Delete multiple cookies', async () => {\n      const res2 = await app.request('http://localhost/delete-cookie-multiple')\n      expect(res2.status).toBe(200)\n      const header2 = res2.headers.get('Set-Cookie')\n      expect(header2).toBe(\n        'delicious_cookie=; Max-Age=0; Path=/, delicious_cookie2=; Max-Age=0; Path=/'\n      )\n    })\n\n    app.get('/delete-cookie-with-options', (c) => {\n      deleteCookie(c, 'delicious_cookie', {\n        path: '/',\n        secure: true,\n        domain: 'example.com',\n      })\n      return c.text('Give cookie')\n    })\n\n    it('Delete cookie with options', async () => {\n      const res2 = await app.request('http://localhost/delete-cookie-with-options')\n      expect(res2.status).toBe(200)\n      const header2 = res2.headers.get('Set-Cookie')\n      expect(header2).toBe('delicious_cookie=; Max-Age=0; Domain=example.com; Path=/; Secure')\n    })\n\n    app.get('/delete-cookie-with-deleted-value', (c) => {\n      const deleted = deleteCookie(c, 'delicious_cookie')\n      return c.text(deleted || '')\n    })\n\n    it('Get deleted value', async () => {\n      const cookieString = 'delicious_cookie=choco'\n      const req = new Request('http://localhost/delete-cookie-with-deleted-value')\n      req.headers.set('Cookie', cookieString)\n      const res = await app.request(req)\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('choco')\n    })\n\n    app.get('/delete-cookie-with-prefix', (c) => {\n      const deleted = deleteCookie(c, 'delicious_cookie', { prefix: 'secure' })\n      return c.text(deleted || '')\n    })\n\n    it('Get deleted value with prefix', async () => {\n      const cookieString = '__Secure-delicious_cookie=choco'\n      const req = new Request('http://localhost/delete-cookie-with-prefix')\n      req.headers.set('Cookie', cookieString)\n      const res = await app.request(req)\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('choco')\n    })\n  })\n\n  describe('Generate cookie', () => {\n    it('should generate a cookie', () => {\n      const cookie = generateCookie('delicious_cookie', 'macha')\n      expect(cookie).toBe('delicious_cookie=macha; Path=/')\n    })\n\n    it('should generate a cookie with options', () => {\n      const cookie = generateCookie('delicious_cookie', 'macha', {\n        path: '/',\n        secure: true,\n        httpOnly: true,\n        domain: 'example.com',\n      })\n      expect(cookie).toBe('delicious_cookie=macha; Domain=example.com; Path=/; HttpOnly; Secure')\n    })\n\n    it('should generate a signed cookie', async () => {\n      const cookie = await generateSignedCookie(\n        'delicious_cookie',\n        'macha',\n        'secret chocolate chips'\n      )\n      expect(cookie).toBe(\n        'delicious_cookie=macha.diubJPY8O7hI1pLa42QSfkPiyDWQ0I4DnlACH%2FN2HaA%3D; Path=/'\n      )\n    })\n\n    it('should generate a signed cookie with options', async () => {\n      const cookie = await generateSignedCookie(\n        'delicious_cookie',\n        'macha',\n        'secret chocolate chips',\n        {\n          path: '/',\n          secure: true,\n          httpOnly: true,\n          domain: 'example.com',\n        }\n      )\n      expect(cookie).toBe(\n        'delicious_cookie=macha.diubJPY8O7hI1pLa42QSfkPiyDWQ0I4DnlACH%2FN2HaA%3D; Domain=example.com; Path=/; HttpOnly; Secure'\n      )\n    })\n  })\n})\n"
  },
  {
    "path": "src/helper/cookie/index.ts",
    "content": "/**\n * @module\n * Cookie Helper for Hono.\n */\n\nimport type { Context } from '../../context'\nimport { parse, parseSigned, serialize, serializeSigned } from '../../utils/cookie'\nimport type { Cookie, CookieOptions, CookiePrefixOptions, SignedCookie } from '../../utils/cookie'\n\ninterface GetCookie {\n  (c: Context, key: string): string | undefined\n  (c: Context): Cookie\n  (c: Context, key: string, prefixOptions?: CookiePrefixOptions): string | undefined\n}\n\ninterface GetSignedCookie {\n  (c: Context, secret: string | BufferSource, key: string): Promise<string | undefined | false>\n  (c: Context, secret: string | BufferSource): Promise<SignedCookie>\n  (\n    c: Context,\n    secret: string | BufferSource,\n    key: string,\n    prefixOptions?: CookiePrefixOptions\n  ): Promise<string | undefined | false>\n}\n\nexport const getCookie: GetCookie = (c, key?, prefix?: CookiePrefixOptions) => {\n  const cookie = c.req.raw.headers.get('Cookie')\n  if (typeof key === 'string') {\n    if (!cookie) {\n      return undefined\n    }\n    let finalKey = key\n    if (prefix === 'secure') {\n      finalKey = '__Secure-' + key\n    } else if (prefix === 'host') {\n      finalKey = '__Host-' + key\n    }\n    const obj = parse(cookie, finalKey)\n    return obj[finalKey]\n  }\n  if (!cookie) {\n    return {}\n  }\n  const obj = parse(cookie)\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  return obj as any\n}\n\nexport const getSignedCookie: GetSignedCookie = async (\n  c,\n  secret,\n  key?,\n  prefix?: CookiePrefixOptions\n) => {\n  const cookie = c.req.raw.headers.get('Cookie')\n  if (typeof key === 'string') {\n    if (!cookie) {\n      return undefined\n    }\n    let finalKey = key\n    if (prefix === 'secure') {\n      finalKey = '__Secure-' + key\n    } else if (prefix === 'host') {\n      finalKey = '__Host-' + key\n    }\n    const obj = await parseSigned(cookie, secret, finalKey)\n    return obj[finalKey]\n  }\n  if (!cookie) {\n    return {}\n  }\n  const obj = await parseSigned(cookie, secret)\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  return obj as any\n}\n\nexport const generateCookie = (name: string, value: string, opt?: CookieOptions): string => {\n  // Cookie names prefixed with __Secure- can be used only if they are set with the secure attribute.\n  // 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)\n  // and must not have a Domain attribute.\n  // Read more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#cookie_prefixes'\n  let cookie\n  if (opt?.prefix === 'secure') {\n    cookie = serialize('__Secure-' + name, value, { path: '/', ...opt, secure: true })\n  } else if (opt?.prefix === 'host') {\n    cookie = serialize('__Host-' + name, value, {\n      ...opt,\n      path: '/',\n      secure: true,\n      domain: undefined,\n    })\n  } else {\n    cookie = serialize(name, value, { path: '/', ...opt })\n  }\n  return cookie\n}\n\nexport const setCookie = (c: Context, name: string, value: string, opt?: CookieOptions): void => {\n  const cookie = generateCookie(name, value, opt)\n  c.header('Set-Cookie', cookie, { append: true })\n}\n\nexport const generateSignedCookie = async (\n  name: string,\n  value: string,\n  secret: string | BufferSource,\n  opt?: CookieOptions\n): Promise<string> => {\n  let cookie\n  if (opt?.prefix === 'secure') {\n    cookie = await serializeSigned('__Secure-' + name, value, secret, {\n      path: '/',\n      ...opt,\n      secure: true,\n    })\n  } else if (opt?.prefix === 'host') {\n    cookie = await serializeSigned('__Host-' + name, value, secret, {\n      ...opt,\n      path: '/',\n      secure: true,\n      domain: undefined,\n    })\n  } else {\n    cookie = await serializeSigned(name, value, secret, { path: '/', ...opt })\n  }\n  return cookie\n}\n\nexport const setSignedCookie = async (\n  c: Context,\n  name: string,\n  value: string,\n  secret: string | BufferSource,\n  opt?: CookieOptions\n): Promise<void> => {\n  const cookie = await generateSignedCookie(name, value, secret, opt)\n  c.header('set-cookie', cookie, { append: true })\n}\n\nexport const deleteCookie = (c: Context, name: string, opt?: CookieOptions): string | undefined => {\n  const deletedCookie = getCookie(c, name, opt?.prefix)\n  setCookie(c, name, '', { ...opt, maxAge: 0 })\n  return deletedCookie\n}\n"
  },
  {
    "path": "src/helper/css/common.case.test.tsx",
    "content": "/** @jsxImportSource ../../jsx */\nimport type {\n  Style as StyleComponent,\n  css as cssHelper,\n  keyframes as keyframesHelper,\n  rawCssString as rawCssStringHelper,\n  viewTransition as viewTransitionHelper,\n} from './index'\n\ninterface Support {\n  nest: boolean\n}\nexport const renderTest = (\n  getEnv: () => {\n    css: typeof cssHelper\n    keyframes: typeof keyframesHelper\n    viewTransition: typeof viewTransitionHelper\n    rawCssString: typeof rawCssStringHelper\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    toString: (template: any) => Promise<string>\n    Style: typeof StyleComponent\n    support: Support\n  }\n) => {\n  const { support } = getEnv()\n\n  let css: typeof cssHelper\n  let keyframes: typeof keyframesHelper\n  let viewTransition: typeof viewTransitionHelper\n  let rawCssString: typeof rawCssStringHelper\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  let toString: (template: any) => Promise<string>\n  let Style: typeof StyleComponent\n  beforeEach(() => {\n    ;({ css, keyframes, viewTransition, rawCssString, toString, Style } = getEnv())\n  })\n\n  describe('render css', () => {\n    it('Should render CSS styles with JSX', async () => {\n      const headerClass = css`\n        background-color: blue;\n      `\n      const template = (\n        <>\n          <Style />\n          <h1 class={headerClass}>Hello!</h1>\n        </>\n      )\n      expect(await toString(template)).toBe(\n        '<style id=\"hono-css\">.css-2458908649{background-color:blue}</style><h1 class=\"css-2458908649\">Hello!</h1>'\n      )\n    })\n\n    it('Should render CSS with keyframes', async () => {\n      const animation = keyframes`\n        from {\n          opacity: 0;\n        }\n        to {\n          opacity: 1;\n        }\n      `\n      const headerClass = css`\n        background-color: blue;\n        animation: ${animation} 1s ease-in-out;\n      `\n      const template = (\n        <>\n          <Style />\n          <h1 class={headerClass}>Hello!</h1>\n        </>\n      )\n      expect(await toString(template)).toBe(\n        '<style id=\"hono-css\">.css-1580801783{background-color:blue;animation:css-9294673 1s ease-in-out}@keyframes css-9294673{from{opacity:0}to{opacity:1}}</style><h1 class=\"css-1580801783\">Hello!</h1>'\n      )\n    })\n\n    it('Should not output the same class name multiple times.', async () => {\n      const headerClass = css`\n        background-color: blue;\n      `\n      const headerClass2 = css`\n        background-color: blue;\n      `\n      const template = (\n        <>\n          <Style />\n          <h1 class={headerClass}>Hello!</h1>\n          <h1 class={headerClass2}>Hello2!</h1>\n        </>\n      )\n      expect(await toString(template)).toBe(\n        '<style id=\"hono-css\">.css-2458908649{background-color:blue}</style><h1 class=\"css-2458908649\">Hello!</h1><h1 class=\"css-2458908649\">Hello2!</h1>'\n      )\n    })\n\n    it('Should render CSS with variable', async () => {\n      const headerClass = css`\n        background-color: blue;\n        content: '${\"I'm a variable!\"}';\n      `\n      const template = (\n        <>\n          <Style />\n          <h1 class={headerClass}>Hello!</h1>\n        </>\n      )\n      expect(await toString(template)).toBe(\n        '<style id=\"hono-css\">.css-4027435072{background-color:blue;content:\\'I\\\\\\'m a variable!\\'}</style><h1 class=\"css-4027435072\">Hello!</h1>'\n      )\n    })\n\n    it('Should escape </style>', async () => {\n      const headerClass = css`\n        background-color: blue;\n        content: '${'</style>'}';\n      `\n      const template = (\n        <>\n          <Style />\n          <h1 class={headerClass}>Hello!</h1>\n        </>\n      )\n      expect(await toString(template)).toBe(\n        '<style id=\"hono-css\">.css-372954897{background-color:blue;content:\\'<\\\\/style>\\'}</style><h1 class=\"css-372954897\">Hello!</h1>'\n      )\n    })\n\n    it('Should not escape URL', async () => {\n      const headerClass = css`\n        background-color: blue;\n        background: url('${'http://www.example.com/path/to/file.jpg'}');\n      `\n      const template = (\n        <>\n          <Style />\n          <h1 class={headerClass}>Hello!</h1>\n        </>\n      )\n      expect(await toString(template)).toBe(\n        '<style id=\"hono-css\">.css-1321888780{background-color:blue;background:url(\\'http://www.example.com/path/to/file.jpg\\')}</style><h1 class=\"css-1321888780\">Hello!</h1>'\n      )\n    })\n\n    it('Should render CSS with escaped variable', async () => {\n      const headerClass = css`\n        background-color: blue;\n        content: '${rawCssString('say \"Hello!\"')}';\n      `\n      const template = (\n        <>\n          <Style />\n          <h1 class={headerClass}>Hello!</h1>\n        </>\n      )\n      expect(await toString(template)).toBe(\n        '<style id=\"hono-css\">.css-2238574885{background-color:blue;content:\\'say \"Hello!\"\\'}</style><h1 class=\"css-2238574885\">Hello!</h1>'\n      )\n    })\n\n    it('Should render CSS with number', async () => {\n      const headerClass = css`\n        background-color: blue;\n        font-size: ${1}rem;\n      `\n      const template = (\n        <>\n          <Style />\n          <h1 class={headerClass}>Hello!</h1>\n        </>\n      )\n      expect(await toString(template)).toBe(\n        '<style id=\"hono-css\">.css-1847536026{background-color:blue;font-size:1rem}</style><h1 class=\"css-1847536026\">Hello!</h1>'\n      )\n    })\n\n    it('Should render CSS with array', async () => {\n      const animation = keyframes`\n        from {\n          opacity: 0;\n        }\n        to {\n          opacity: 1;\n        }\n      `\n      const headerClass = css`\n        background-color: blue;\n        animation: ${animation} 1s ease-in-out;\n      `\n      const extendedHeaderClass = css`\n        ${headerClass}\n        color: red;\n      `\n      const template = (\n        <>\n          <Style />\n          <h1 class={extendedHeaderClass}>Hello!</h1>\n        </>\n      )\n      expect(await toString(template)).toBe(\n        '<style id=\"hono-css\">.css-2558359670{background-color:blue;animation:css-9294673 1s ease-in-out;color:red}@keyframes css-9294673{from{opacity:0}to{opacity:1}}</style><h1 class=\"css-2558359670\">Hello!</h1>'\n      )\n    })\n\n    it.runIf(support.nest)(\n      'Should be used as a class name for syntax `${className} {`',\n      async () => {\n        const headerClass = css`\n          font-weight: bold;\n        `\n        const containerClass = css`\n          ${headerClass} {\n            h1 {\n              color: red;\n            }\n          }\n        `\n        const template = (\n          <>\n            <Style />\n            <div class={containerClass}>\n              <h1 class={headerClass}>Hello!</h1>\n            </div>\n          </>\n        )\n        expect(await toString(template)).toBe(\n          '<style id=\"hono-css\">.css-4220297002{.css-1032195302{h1{color:red}}}.css-1032195302{font-weight:bold}</style><div class=\"css-4220297002\"><h1 class=\"css-1032195302\">Hello!</h1></div>'\n        )\n      }\n    )\n\n    it('Should be inserted to global if style string starts with :-hono-root', async () => {\n      const globalClass = css`\n        :-hono-global {\n          html {\n            color: red;\n          }\n          body {\n            display: flex;\n          }\n        }\n      `\n      const template = (\n        <>\n          <Style />\n          <div class={globalClass}>\n            <h1>Hello!</h1>\n          </div>\n        </>\n      )\n      expect(await toString(template)).toBe(\n        '<style id=\"hono-css\">html{color:red}body{display:flex}</style><div class=\"\"><h1>Hello!</h1></div>'\n      )\n    })\n\n    it.runIf(support.nest)(\n      'Should be inserted to global if style string starts with :-hono-root and extends class name',\n      async () => {\n        const headerClass = css`\n          display: flex;\n        `\n        const specialHeaderClass = css`\n          :-hono-global {\n            ${headerClass} {\n              h1 {\n                color: red;\n              }\n            }\n          }\n        `\n        const template = (\n          <>\n            <Style />\n            <div class={specialHeaderClass}>\n              <h1>Hello!</h1>\n            </div>\n          </>\n        )\n        expect(await toString(template)).toBe(\n          '<style id=\"hono-css\">.css-3980466870{h1{color:red}}.css-3980466870{display:flex}</style><div class=\"css-3980466870\"><h1>Hello!</h1></div>'\n        )\n      }\n    )\n\n    it('Should be inserted as global css if passed css`` to Style component', async () => {\n      const headerClass = css`\n        font-size: 1rem;\n      `\n      const template = (\n        <>\n          <Style>{css`\n            html {\n              color: red;\n            }\n            body {\n              display: flex;\n            }\n          `}</Style>\n          <div>\n            <h1 class={headerClass}>Hello!</h1>\n          </div>\n        </>\n      )\n      expect(await toString(template)).toBe(\n        '<style id=\"hono-css\">html{color:red}body{display:flex}.css-1740067317{font-size:1rem}</style><div><h1 class=\"css-1740067317\">Hello!</h1></div>'\n      )\n    })\n\n    it('Should be ignored :-hono-root inside Style component', async () => {\n      const headerClass = css`\n        font-size: 1rem;\n      `\n      const template = (\n        <>\n          <Style>{css`\n            :-hono-global {\n              html {\n                color: red;\n              }\n              body {\n                display: flex;\n              }\n            }\n          `}</Style>\n          <div>\n            <h1 class={headerClass}>Hello!</h1>\n          </div>\n        </>\n      )\n      expect(await toString(template)).toBe(\n        '<style id=\"hono-css\">html{color:red}body{display:flex}.css-1740067317{font-size:1rem}</style><div><h1 class=\"css-1740067317\">Hello!</h1></div>'\n      )\n    })\n\n    describe('viewTransition', () => {\n      it('Should render CSS with unique view-transition-name', async () => {\n        const transition = viewTransition()\n        const template = (\n          <>\n            <Style />\n            <h1 class={transition}>Hello!</h1>\n          </>\n        )\n        expect(await toString(template)).toBe(\n          '<style id=\"hono-css\">.css-1644952339{view-transition-name:css-1644952339}</style><h1 class=\"css-1644952339\">Hello!</h1>'\n        )\n      })\n\n      it('Should render CSS with css and keyframes', async () => {\n        const kf = keyframes`\n        from {\n          opacity: 0;\n        }\n        to {\n          opacity: 1;\n        }\n      `\n        const transition = viewTransition(css`\n          ::view-transition-old() {\n            animation-name: ${kf};\n          }\n          ::view-transition-new() {\n            animation-name: ${kf};\n          }\n        `)\n        const headerClass = css`\n          ${transition}\n          background-color: blue;\n        `\n        const template = (\n          <>\n            <Style />\n            <h1 class={headerClass}>Hello!</h1>\n          </>\n        )\n        expect(await toString(template)).toBe(\n          '<style id=\"hono-css\">.css-1245070278{view-transition-name:css-399742870;background-color:blue}@keyframes css-9294673{from{opacity:0}to{opacity:1}}::view-transition-old(css-399742870){animation-name:css-9294673}::view-transition-new(css-399742870){animation-name:css-9294673}</style><h1 class=\"css-1245070278\">Hello!</h1>'\n        )\n      })\n\n      it('Should works as a template tag function', async () => {\n        const kf = keyframes`\n        from {\n          opacity: 0;\n        }\n        to {\n          opacity: 1;\n        }\n      `\n        const transition = viewTransition`\n          ::view-transition-old() {\n            animation-name: ${kf};\n          }\n          ::view-transition-new() {\n            animation-name: ${kf};\n          }\n        `\n        const headerClass = css`\n          ${transition}\n          background-color: blue;\n        `\n        const template = (\n          <>\n            <Style />\n            <h1 class={headerClass}>Hello!</h1>\n          </>\n        )\n        expect(await toString(template)).toBe(\n          '<style id=\"hono-css\">.css-1245070278{view-transition-name:css-399742870;background-color:blue}@keyframes css-9294673{from{opacity:0}to{opacity:1}}::view-transition-old(css-399742870){animation-name:css-9294673}::view-transition-new(css-399742870){animation-name:css-9294673}</style><h1 class=\"css-1245070278\">Hello!</h1>'\n        )\n      })\n    })\n\n    it.runIf(support.nest)('Should render sub CSS with keyframe', async () => {\n      const headerClass = css`\n        background-color: blue;\n        ${[1, 2].map(\n          (i) => css`\n            :nth-child(${i}) {\n              color: red;\n            }\n          `\n        )}\n      `\n      const template = (\n        <>\n          <Style />\n          <h1 class={headerClass}>Hello!</h1>\n        </>\n      )\n      expect(await toString(template)).toBe(\n        '<style id=\"hono-css\">.css-1539881271{background-color:blue;:nth-child(1){color:red}:nth-child(2){color:red}}</style><h1 class=\"css-1539881271\">Hello!</h1>'\n      )\n    })\n\n    it('Should be generated deferent class name for deferent first line comment even if the content is the same', async () => {\n      const headerClassA = css`\n        /* class A */\n        display: flex;\n      `\n      const headerClassB = css`\n        /* class B */\n        display: flex;\n      `\n      const template = (\n        <>\n          <Style />\n          <h1 class={headerClassA}>Hello!</h1>\n          <h1 class={headerClassB}>Hello!</h1>\n        </>\n      )\n      expect(await toString(template)).toBe(\n        '<style id=\"hono-css\">.css-3170754153{display:flex}.css-896513246{display:flex}</style><h1 class=\"css-3170754153\">Hello!</h1><h1 class=\"css-896513246\">Hello!</h1>'\n      )\n    })\n\n    describe('Booleans, Null, and Undefined Are Ignored', () => {\n      it.each([true, false, undefined, null])('%s', async (value) => {\n        const headerClass = css`\n          ${value}\n          background-color: blue;\n        `\n        const template = (\n          <>\n            <Style />\n            <h1 class={headerClass}>Hello!</h1>\n          </>\n        )\n        expect(await toString(template)).toBe(\n          '<style id=\"hono-css\">.css-2458908649{background-color:blue}</style><h1 class=\"css-2458908649\">Hello!</h1>'\n        )\n      })\n\n      it('falsy value', async () => {\n        const value = 0\n        const headerClass = css`\n          padding: ${value};\n        `\n        const template = (\n          <>\n            <Style />\n            <h1 class={headerClass}>Hello!</h1>\n          </>\n        )\n        expect(await toString(template)).toBe(\n          '<style id=\"hono-css\">.css-478287868{padding:0}</style><h1 class=\"css-478287868\">Hello!</h1>'\n        )\n      })\n\n      it('Should render CSS styles with CSP nonce', async () => {\n        const headerClass = css`\n          background-color: blue;\n        `\n        const template = (\n          <>\n            <Style nonce='1234' />\n            <h1 class={headerClass}>Hello!</h1>\n          </>\n        )\n        expect(await toString(template)).toBe(\n          '<style id=\"hono-css\" nonce=\"1234\">.css-2458908649{background-color:blue}</style><h1 class=\"css-2458908649\">Hello!</h1>'\n        )\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "src/helper/css/common.ts",
    "content": "// provide utility functions for css helper both on server and client\nexport const PSEUDO_GLOBAL_SELECTOR = ':-hono-global'\nexport const isPseudoGlobalSelectorRe = new RegExp(`^${PSEUDO_GLOBAL_SELECTOR}{(.*)}$`)\nexport const DEFAULT_STYLE_ID = 'hono-css'\n\nexport const SELECTOR: unique symbol = Symbol()\nexport const CLASS_NAME: unique symbol = Symbol()\nexport const STYLE_STRING: unique symbol = Symbol()\nexport const SELECTORS: unique symbol = Symbol()\nexport const EXTERNAL_CLASS_NAMES: unique symbol = Symbol()\nconst CSS_ESCAPED: unique symbol = Symbol()\n\nexport interface CssClassName {\n  [SELECTOR]: string\n  [CLASS_NAME]: string\n  [STYLE_STRING]: string\n  [SELECTORS]: CssClassName[]\n  [EXTERNAL_CLASS_NAMES]: string[]\n}\n\nexport const IS_CSS_ESCAPED = Symbol()\n\ninterface CssEscapedString {\n  [CSS_ESCAPED]: string\n}\n\n/**\n * @experimental\n * `rawCssString` is an experimental feature.\n * The API might be changed.\n */\nexport const rawCssString = (value: string): CssEscapedString => {\n  return {\n    [CSS_ESCAPED]: value,\n  }\n}\n\n/**\n * Used the goober'code as a reference:\n * https://github.com/cristianbote/goober/blob/master/src/core/to-hash.js\n * MIT License, Copyright (c) 2019 Cristian Bote\n */\nconst toHash = (str: string): string => {\n  let i = 0,\n    out = 11\n  while (i < str.length) {\n    out = (101 * out + str.charCodeAt(i++)) >>> 0\n  }\n  return 'css-' + out\n}\n\nconst cssStringReStr: string = [\n  '\"(?:(?:\\\\\\\\[\\\\s\\\\S]|[^\"\\\\\\\\])*)\"', // double quoted string\n\n  \"'(?:(?:\\\\\\\\[\\\\s\\\\S]|[^'\\\\\\\\])*)'\", // single quoted string\n].join('|')\nconst minifyCssRe: RegExp = new RegExp(\n  [\n    '(' + cssStringReStr + ')', // $1: quoted string\n\n    '(?:' +\n      [\n        '^\\\\s+', // head whitespace\n        '\\\\/\\\\*.*?\\\\*\\\\/\\\\s*', // multi-line comment\n        '\\\\/\\\\/.*\\\\n\\\\s*', // single-line comment\n        '\\\\s+$', // tail whitespace\n      ].join('|') +\n      ')',\n\n    '\\\\s*;\\\\s*(}|$)\\\\s*', // $2: trailing semicolon\n    '\\\\s*([{};:,])\\\\s*', // $3: whitespace around { } : , ;\n    '(\\\\s)\\\\s+', // $4: 2+ spaces\n  ].join('|'),\n  'g'\n)\n\nexport const minify = (css: string): string => {\n  return css.replace(minifyCssRe, (_, $1, $2, $3, $4) => $1 || $2 || $3 || $4 || '')\n}\n\ntype CssVariableBasicType =\n  | CssClassName\n  | CssEscapedString\n  | string\n  | number\n  | boolean\n  | null\n  | undefined\ntype CssVariableAsyncType = Promise<CssVariableBasicType>\ntype CssVariableArrayType = (CssVariableBasicType | CssVariableAsyncType)[]\nexport type CssVariableType = CssVariableBasicType | CssVariableAsyncType | CssVariableArrayType\n\nexport const buildStyleString = (\n  strings: TemplateStringsArray,\n  values: CssVariableType[]\n): [string, string, CssClassName[], string[]] => {\n  const selectors: CssClassName[] = []\n  const externalClassNames: string[] = []\n\n  const label = strings[0].match(/^\\s*\\/\\*(.*?)\\*\\//)?.[1] || ''\n  let styleString = ''\n  for (let i = 0, len = strings.length; i < len; i++) {\n    styleString += strings[i]\n    let vArray = values[i]\n    if (typeof vArray === 'boolean' || vArray === null || vArray === undefined) {\n      continue\n    }\n\n    if (!Array.isArray(vArray)) {\n      vArray = [vArray]\n    }\n    for (let j = 0, len = vArray.length; j < len; j++) {\n      let value = vArray[j]\n      if (typeof value === 'boolean' || value === null || value === undefined) {\n        continue\n      }\n      if (typeof value === 'string') {\n        if (/([\\\\\"'\\/])/.test(value)) {\n          styleString += value.replace(/([\\\\\"']|(?<=<)\\/)/g, '\\\\$1')\n        } else {\n          styleString += value\n        }\n      } else if (typeof value === 'number') {\n        styleString += value\n      } else if ((value as CssEscapedString)[CSS_ESCAPED]) {\n        styleString += (value as CssEscapedString)[CSS_ESCAPED]\n      } else if ((value as CssClassName)[CLASS_NAME].startsWith('@keyframes ')) {\n        selectors.push(value as CssClassName)\n        styleString += ` ${(value as CssClassName)[CLASS_NAME].substring(11)} `\n      } else {\n        if (strings[i + 1]?.match(/^\\s*{/)) {\n          // assume this value is a class name\n          selectors.push(value as CssClassName)\n          value = `.${(value as CssClassName)[CLASS_NAME]}`\n        } else {\n          selectors.push(...(value as CssClassName)[SELECTORS])\n          externalClassNames.push(...(value as CssClassName)[EXTERNAL_CLASS_NAMES])\n          value = (value as CssClassName)[STYLE_STRING]\n          const valueLen = value.length\n          if (valueLen > 0) {\n            const lastChar = value[valueLen - 1]\n            if (lastChar !== ';' && lastChar !== '}') {\n              value += ';'\n            }\n          }\n        }\n        styleString += `${value || ''}`\n      }\n    }\n  }\n\n  return [label, minify(styleString), selectors, externalClassNames]\n}\n\nexport const cssCommon = (\n  strings: TemplateStringsArray,\n  values: CssVariableType[]\n): CssClassName => {\n  let [label, thisStyleString, selectors, externalClassNames] = buildStyleString(strings, values)\n  const isPseudoGlobal = isPseudoGlobalSelectorRe.exec(thisStyleString)\n  if (isPseudoGlobal) {\n    thisStyleString = isPseudoGlobal[1]\n  }\n  const selector = (isPseudoGlobal ? PSEUDO_GLOBAL_SELECTOR : '') + toHash(label + thisStyleString)\n  const className = (\n    isPseudoGlobal ? selectors.map((s) => s[CLASS_NAME]) : [selector, ...externalClassNames]\n  ).join(' ')\n\n  return {\n    [SELECTOR]: selector,\n    [CLASS_NAME]: className,\n    [STYLE_STRING]: thisStyleString,\n    [SELECTORS]: selectors,\n    [EXTERNAL_CLASS_NAMES]: externalClassNames,\n  }\n}\n\nexport const cxCommon = (\n  args: (string | boolean | null | undefined | CssClassName)[]\n): (string | boolean | null | undefined | CssClassName)[] => {\n  for (let i = 0, len = args.length; i < len; i++) {\n    const arg = args[i]\n    if (typeof arg === 'string') {\n      args[i] = {\n        [SELECTOR]: '',\n        [CLASS_NAME]: '',\n        [STYLE_STRING]: '',\n        [SELECTORS]: [],\n        [EXTERNAL_CLASS_NAMES]: [arg],\n      }\n    }\n  }\n\n  return args\n}\n\nexport const keyframesCommon = (\n  strings: TemplateStringsArray,\n  ...values: CssVariableType[]\n): CssClassName => {\n  const [label, styleString] = buildStyleString(strings, values)\n  return {\n    [SELECTOR]: '',\n    [CLASS_NAME]: `@keyframes ${toHash(label + styleString)}`,\n    [STYLE_STRING]: styleString,\n    [SELECTORS]: [],\n    [EXTERNAL_CLASS_NAMES]: [],\n  }\n}\n\ntype ViewTransitionType = {\n  (strings: TemplateStringsArray, values: CssVariableType[]): CssClassName\n  (content: CssClassName): CssClassName\n  (): CssClassName\n}\n\nlet viewTransitionNameIndex = 0\nexport const viewTransitionCommon: ViewTransitionType = ((\n  strings: TemplateStringsArray | CssClassName | undefined,\n  values: CssVariableType[]\n): CssClassName => {\n  if (!strings) {\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    strings = [`/* h-v-t ${viewTransitionNameIndex++} */`] as any\n  }\n  const content = Array.isArray(strings)\n    ? cssCommon(strings as TemplateStringsArray, values)\n    : (strings as CssClassName)\n\n  const transitionName = content[CLASS_NAME]\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  const res = cssCommon(['view-transition-name:', ''] as any, [transitionName])\n\n  content[CLASS_NAME] = PSEUDO_GLOBAL_SELECTOR + content[CLASS_NAME]\n  content[STYLE_STRING] = content[STYLE_STRING].replace(\n    /(?<=::view-transition(?:[a-z-]*)\\()(?=\\))/g,\n    transitionName\n  )\n  res[CLASS_NAME] = res[SELECTOR] = transitionName\n  res[SELECTORS] = [...content[SELECTORS], content]\n\n  return res\n}) as ViewTransitionType\n"
  },
  {
    "path": "src/helper/css/index.test.tsx",
    "content": "/** @jsxImportSource ../../jsx */\nimport { Hono } from '../../'\nimport { html } from '../../helper/html'\nimport type { JSXNode } from '../../jsx'\nimport { isValidElement } from '../../jsx'\nimport { Suspense, renderToReadableStream } from '../../jsx/streaming'\nimport type { HtmlEscapedString } from '../../utils/html'\nimport { HtmlEscapedCallbackPhase, resolveCallback } from '../../utils/html'\nimport { renderTest } from './common.case.test'\nimport { Style, createCssContext, css, cx, keyframes, rawCssString, viewTransition } from './index'\n\nasync function toString(\n  template: JSXNode | Promise<HtmlEscapedString> | Promise<string> | HtmlEscapedString\n) {\n  if (template instanceof Promise) {\n    template = (await template) as HtmlEscapedString\n  }\n  if (isValidElement(template)) {\n    template = template.toString() as Promise<HtmlEscapedString>\n  }\n  return resolveCallback(await template, HtmlEscapedCallbackPhase.Stringify, false, template)\n}\n\nasync function toCSS(\n  template: JSXNode | Promise<HtmlEscapedString> | Promise<string> | HtmlEscapedString\n) {\n  return (await toString(template))\n    .replace(/.*?=(\".*\")<\\/script.*/, '$1')\n    .replace(/\\.css-\\d+/g, '.css-123')\n}\n\ndescribe('CSS Helper', () => {\n  renderTest(() => {\n    return {\n      css,\n      Style,\n      keyframes,\n      viewTransition,\n      rawCssString,\n      createCssContext,\n      toString,\n      toCSS,\n      support: {\n        nest: true,\n      },\n    }\n  })\n\n  describe('with `html` tag function', () => {\n    it('Should render CSS styles with `html` tag function', async () => {\n      const headerClass = css`\n        background-color: blue;\n      `\n      const template = html`${Style()}\n        <h1 class=\"${headerClass}\">Hello!</h1>`\n      expect(await toString(template)).toBe(\n        `<style id=\"hono-css\">.css-2458908649{background-color:blue}</style>\n        <h1 class=\"css-2458908649\">Hello!</h1>`\n      )\n    })\n\n    it('Should render CSS styles with `html` tag function and CSP nonce', async () => {\n      const headerClass = css`\n        background-color: blue;\n      `\n      const template = html`${Style({ nonce: '1234' })}\n        <h1 class=\"${headerClass}\">Hello!</h1>`\n      expect(await toString(template)).toBe(\n        `<style id=\"hono-css\" nonce=\"1234\">.css-2458908649{background-color:blue}</style>\n        <h1 class=\"css-2458908649\">Hello!</h1>`\n      )\n    })\n  })\n\n  describe('cx()', () => {\n    it('Should render CSS with cx()', async () => {\n      const btn = css`\n        border-radius: 4px;\n      `\n      const btnPrimary = css`\n        background-color: blue;\n        color: white;\n      `\n\n      const template = (\n        <>\n          <Style />\n          <h1 class={cx(btn, btnPrimary)}>Hello!</h1>\n        </>\n      )\n      expect(await toString(template)).toBe(\n        '<style id=\"hono-css\">.css-2395710522{border-radius:4px;background-color:blue;color:white}</style><h1 class=\"css-2395710522\">Hello!</h1>'\n      )\n    })\n\n    it('Should render CSS with cx() includes external class name', async () => {\n      const btn = css`\n        border-radius: 4px;\n      `\n\n      const template = (\n        <>\n          <Style />\n          <h1 class={cx(btn, 'external-class')}>Hello!</h1>\n        </>\n      )\n      expect(await toString(template)).toBe(\n        '<style id=\"hono-css\">.css-3467431616{border-radius:4px}</style><h1 class=\"css-3467431616 external-class\">Hello!</h1>'\n      )\n    })\n\n    it('Should render CSS with cx() includes nested external class name', async () => {\n      const btn = css`\n        border-radius: 4px;\n      `\n      const btn2 = cx(btn, 'external-class')\n      const btn3 = css`\n        ${btn2}\n        color: white;\n      `\n      const btn4 = cx(btn3, 'external-class2')\n      const template = (\n        <>\n          <Style />\n          <h1 class={btn4}>Hello!</h1>\n        </>\n      )\n      expect(await toString(template)).toBe(\n        '<style id=\"hono-css\">.css-3358636561{border-radius:4px;color:white}</style><h1 class=\"css-3358636561 external-class external-class2\">Hello!</h1>'\n      )\n    })\n  })\n\n  describe('minify', () => {\n    const data: [string, Promise<string>, string][] = [\n      [\n        'basic CSS styles',\n        css`\n          background-color: blue;\n          color: white;\n          padding: 1rem;\n        `,\n        '.css-123{background-color:blue;color:white;padding:1rem}',\n      ],\n      [\n        'remove comments',\n        css`\n          /* background-color: blue; */\n          color: white;\n          padding: 1rem;\n          // inline comment\n          margin: 1rem;\n        `,\n        '.css-123{color:white;padding:1rem;margin:1rem}',\n      ],\n      [\n        'preserve string',\n        css`\n          background-color: blue;\n          color: white;\n          padding: 1rem;\n          content: \"Hel  \\\\\\n  \\\\'  lo!\";\n          content: 'Hel  \\\\\\n  \\\\\"  lo!';\n        `,\n        '.css-123{background-color:blue;color:white;padding:1rem;content:\"Hel  \\\\\\n  \\\\\\'  lo!\";content:\\'Hel  \\\\\\n  \\\\\"  lo!\\'}',\n      ],\n      [\n        'preserve nested selectors',\n        css`\n          padding: 1rem;\n          &:hover {\n            padding: 2rem;\n          }\n        `,\n        '.css-123{padding:1rem;&:hover{padding:2rem}}',\n      ],\n    ]\n    data.forEach(([name, str, expected]) => {\n      it(`Should be minified while preserving content accurately: ${name}`, async () => {\n        expect(JSON.parse(await toCSS(str))).toBe(expected)\n      })\n    })\n  })\n\n  describe('createCssContext()', () => {\n    it('Should create a new CSS context', async () => {\n      const { css: css1, Style: Style1 } = createCssContext({ id: 'context1' })\n      const { css: css2, Style: Style2 } = createCssContext({ id: 'context2' })\n      const headerClass1 = css1`\n        background-color: blue;\n      `\n      const headerClass2 = css2`\n        background-color: red;\n      `\n      const template = (\n        <>\n          <Style1 />\n          <Style2 />\n          <h1 class={headerClass1}>Hello!</h1>\n          <h1 class={headerClass2}>Hello!</h1>\n        </>\n      )\n      expect(await toString(template)).toBe(\n        '<style id=\"context1\">.css-2458908649{background-color:blue}</style><style id=\"context2\">.css-960045552{background-color:red}</style><h1 class=\"css-2458908649\">Hello!</h1><h1 class=\"css-960045552\">Hello!</h1>'\n      )\n    })\n  })\n\n  describe('with application', () => {\n    const app = new Hono()\n\n    const headerClass = css`\n      background-color: blue;\n    `\n\n    app.get('/sync', (c) =>\n      c.html(\n        <>\n          <Style />\n          <h1 class={headerClass}>Hello!</h1>\n        </>\n      )\n    )\n\n    app.get('/stream', (c) => {\n      const stream = renderToReadableStream(\n        <>\n          <Style />\n          <Suspense fallback={<p>Loading...</p>}>\n            <h1 class={headerClass}>Hello!</h1>\n          </Suspense>\n        </>\n      )\n      return c.body(stream, {\n        headers: {\n          'Content-Type': 'text/html; charset=UTF-8',\n          'Transfer-Encoding': 'chunked',\n        },\n      })\n    })\n\n    app.get('/stream-with-nonce', (c) => {\n      const stream = renderToReadableStream(\n        <>\n          <Style nonce='1234' />\n          <Suspense fallback={<p>Loading...</p>}>\n            <h1 class={headerClass}>Hello!</h1>\n          </Suspense>\n        </>\n      )\n      return c.body(stream, {\n        headers: {\n          'Content-Type': 'text/html; charset=UTF-8',\n          'Transfer-Encoding': 'chunked',\n        },\n      })\n    })\n\n    it('/sync', async () => {\n      const res = await app.request('http://localhost/sync')\n      expect(res).not.toBeNull()\n      expect(await res.text()).toBe(\n        '<style id=\"hono-css\">.css-2458908649{background-color:blue}</style><h1 class=\"css-2458908649\">Hello!</h1>'\n      )\n    })\n\n    it('/stream', async () => {\n      const res = await app.request('http://localhost/stream')\n      expect(res).not.toBeNull()\n      expect(await res.text()).toBe(\n        `<style id=\"hono-css\"></style><template id=\"H:0\"></template><p>Loading...</p><!--/$--><script>document.querySelector('#hono-css').textContent+=\".css-2458908649{background-color:blue}\"</script><template data-hono-target=\"H:0\"><h1 class=\"css-2458908649\">Hello!</h1></template><script>\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:0')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script>`\n      )\n    })\n\n    it('/stream-with-nonce', async () => {\n      const res = await app.request('http://localhost/stream-with-nonce')\n      expect(res).not.toBeNull()\n      expect(await res.text()).toBe(\n        `<style id=\"hono-css\" nonce=\"1234\"></style><template id=\"H:1\"></template><p>Loading...</p><!--/$--><script nonce=\"1234\">document.querySelector('#hono-css').textContent+=\".css-2458908649{background-color:blue}\"</script><template data-hono-target=\"H:1\"><h1 class=\"css-2458908649\">Hello!</h1></template><script>\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:1')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script>`\n      )\n    })\n  })\n})\n"
  },
  {
    "path": "src/helper/css/index.ts",
    "content": "/**\n * @module\n * css Helper for Hono.\n */\n\nimport { raw } from '../../helper/html'\nimport { DOM_RENDERER } from '../../jsx/constants'\nimport { createCssJsxDomObjects } from '../../jsx/dom/css'\nimport type { HtmlEscapedCallback, HtmlEscapedString } from '../../utils/html'\nimport type { CssClassName as CssClassNameCommon, CssVariableType } from './common'\nimport {\n  CLASS_NAME,\n  DEFAULT_STYLE_ID,\n  PSEUDO_GLOBAL_SELECTOR,\n  SELECTOR,\n  SELECTORS,\n  STYLE_STRING,\n  cssCommon,\n  cxCommon,\n  keyframesCommon,\n  viewTransitionCommon,\n} from './common'\nexport { rawCssString } from './common'\n\ntype CssClassName = HtmlEscapedString & CssClassNameCommon\n\ntype usedClassNameData = [\n  Record<string, string>, // class name to add\n  Record<string, true>, // class name already added\n]\n\ninterface CssType {\n  (strings: TemplateStringsArray, ...values: CssVariableType[]): Promise<string>\n}\n\ninterface CxType {\n  (\n    ...args: (CssClassName | Promise<string> | string | boolean | null | undefined)[]\n  ): Promise<string>\n}\n\ninterface KeyframesType {\n  (strings: TemplateStringsArray, ...values: CssVariableType[]): CssClassNameCommon\n}\n\ninterface ViewTransitionType {\n  (strings: TemplateStringsArray, ...values: CssVariableType[]): Promise<string>\n  (content: Promise<string>): Promise<string>\n  (): Promise<string>\n}\n\ninterface StyleType {\n  (args?: { children?: Promise<string>; nonce?: string }): HtmlEscapedString\n}\n\n/**\n * @experimental\n * `createCssContext` is an experimental feature.\n * The API might be changed.\n */\nexport const createCssContext = ({ id }: { id: Readonly<string> }): DefaultContextType => {\n  const [cssJsxDomObject, StyleRenderToDom] = createCssJsxDomObjects({ id })\n\n  const contextMap: WeakMap<object, usedClassNameData> = new WeakMap()\n  const nonceMap: WeakMap<object, string | undefined> = new WeakMap()\n\n  const replaceStyleRe = new RegExp(`(<style id=\"${id}\"(?: nonce=\"[^\"]*\")?>.*?)(</style>)`)\n\n  const newCssClassNameObject = (cssClassName: CssClassNameCommon): Promise<string> => {\n    const appendStyle: HtmlEscapedCallback = ({ buffer, context }): Promise<string> | undefined => {\n      const [toAdd, added] = contextMap.get(context) as usedClassNameData\n      const names = Object.keys(toAdd)\n\n      if (!names.length) {\n        return\n      }\n\n      let stylesStr = ''\n      names.forEach((className) => {\n        added[className] = true\n        stylesStr += className.startsWith(PSEUDO_GLOBAL_SELECTOR)\n          ? toAdd[className]\n          : `${className[0] === '@' ? '' : '.'}${className}{${toAdd[className]}}`\n      })\n      contextMap.set(context, [{}, added])\n\n      if (buffer && replaceStyleRe.test(buffer[0])) {\n        buffer[0] = buffer[0].replace(replaceStyleRe, (_, pre, post) => `${pre}${stylesStr}${post}`)\n        return\n      }\n\n      const nonce = nonceMap.get(context)\n      const appendStyleScript = `<script${\n        nonce ? ` nonce=\"${nonce}\"` : ''\n      }>document.querySelector('#${id}').textContent+=${JSON.stringify(stylesStr)}</script>`\n\n      if (buffer) {\n        buffer[0] = `${appendStyleScript}${buffer[0]}`\n        return\n      }\n\n      return Promise.resolve(appendStyleScript)\n    }\n\n    const addClassNameToContext: HtmlEscapedCallback = ({ context }) => {\n      if (!contextMap.has(context)) {\n        contextMap.set(context, [{}, {}])\n      }\n      const [toAdd, added] = contextMap.get(context) as usedClassNameData\n      let allAdded = true\n      if (!added[cssClassName[SELECTOR]]) {\n        allAdded = false\n        toAdd[cssClassName[SELECTOR]] = cssClassName[STYLE_STRING]\n      }\n      cssClassName[SELECTORS].forEach(\n        ({ [CLASS_NAME]: className, [STYLE_STRING]: styleString }) => {\n          if (!added[className]) {\n            allAdded = false\n            toAdd[className] = styleString\n          }\n        }\n      )\n      if (allAdded) {\n        return\n      }\n\n      return Promise.resolve(raw('', [appendStyle]))\n    }\n\n    const className = new String(cssClassName[CLASS_NAME]) as CssClassName\n    Object.assign(className, cssClassName)\n    ;(className as HtmlEscapedString).isEscaped = true\n    ;(className as HtmlEscapedString).callbacks = [addClassNameToContext]\n    const promise = Promise.resolve(className)\n    Object.assign(promise, cssClassName)\n\n    promise.toString = cssJsxDomObject.toString\n    return promise\n  }\n\n  const css: CssType = (strings, ...values) => {\n    return newCssClassNameObject(cssCommon(strings, values))\n  }\n\n  const cx: CxType = (...args) => {\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    args = cxCommon(args as any) as any\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    return css(Array(args.length).fill('') as any, ...args)\n  }\n\n  const keyframes = keyframesCommon\n\n  const viewTransition: ViewTransitionType = ((\n    strings: TemplateStringsArray | Promise<string> | undefined,\n    ...values: CssVariableType[]\n  ) => {\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    return newCssClassNameObject(viewTransitionCommon(strings as any, values))\n  }) as ViewTransitionType\n\n  const Style: StyleType = ({ children, nonce } = {}) =>\n    raw(\n      `<style id=\"${id}\"${nonce ? ` nonce=\"${nonce}\"` : ''}>${\n        children ? (children as unknown as CssClassName)[STYLE_STRING] : ''\n      }</style>`,\n      [\n        ({ context }) => {\n          nonceMap.set(context, nonce)\n          return undefined\n        },\n      ]\n    )\n\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  ;(Style as any)[DOM_RENDERER] = StyleRenderToDom\n\n  return {\n    css,\n    cx,\n    keyframes,\n    viewTransition: viewTransition as ViewTransitionType,\n    Style,\n  }\n}\n\ninterface DefaultContextType {\n  css: CssType\n  cx: CxType\n  keyframes: KeyframesType\n  viewTransition: ViewTransitionType\n  Style: StyleType\n}\n\nconst defaultContext: DefaultContextType = createCssContext({\n  id: DEFAULT_STYLE_ID,\n})\n\n/**\n * @experimental\n * `css` is an experimental feature.\n * The API might be changed.\n */\nexport const css = defaultContext.css\n\n/**\n * @experimental\n * `cx` is an experimental feature.\n * The API might be changed.\n */\nexport const cx = defaultContext.cx\n\n/**\n * @experimental\n * `keyframes` is an experimental feature.\n * The API might be changed.\n */\nexport const keyframes = defaultContext.keyframes\n\n/**\n * @experimental\n * `viewTransition` is an experimental feature.\n * The API might be changed.\n */\nexport const viewTransition = defaultContext.viewTransition\n\n/**\n * @experimental\n * `Style` is an experimental feature.\n * The API might be changed.\n */\nexport const Style = defaultContext.Style\n"
  },
  {
    "path": "src/helper/dev/index.test.ts",
    "content": "import { Hono } from '../../hono'\nimport { RegExpRouter } from '../../router/reg-exp-router'\nimport type { Handler, MiddlewareHandler } from '../../types'\nimport { getRouterName, inspectRoutes, showRoutes } from '.'\n\nconst namedMiddleware: MiddlewareHandler = (_, next) => next()\nconst namedHandler: Handler = (c) => c.text('hi')\nconst app = new Hono()\n  .use('*', (_c, next) => next())\n  .get(\n    '/',\n    (_, next) => next(),\n    (c) => c.text('hi')\n  )\n  .get('/named', namedMiddleware, namedHandler)\n  .post('/', (c) => c.text('hi'))\n  .put('/', (c) => c.text('hi'))\n  .patch('/', (c) => c.text('hi'))\n  .delete('/', (c) => c.text('hi'))\n  .options('/', (c) => c.text('hi'))\n  .get('/static', () => new Response('hi'))\n\ndescribe('inspectRoutes()', () => {\n  it('should return correct data', async () => {\n    expect(inspectRoutes(app)).toEqual([\n      { path: '/*', method: 'ALL', name: '[middleware]', isMiddleware: true },\n      { path: '/', method: 'GET', name: '[middleware]', isMiddleware: true },\n      { path: '/', method: 'GET', name: '[handler]', isMiddleware: false },\n      { path: '/named', method: 'GET', name: 'namedMiddleware', isMiddleware: true },\n      { path: '/named', method: 'GET', name: 'namedHandler', isMiddleware: false },\n      { path: '/', method: 'POST', name: '[handler]', isMiddleware: false },\n      { path: '/', method: 'PUT', name: '[handler]', isMiddleware: false },\n      { path: '/', method: 'PATCH', name: '[handler]', isMiddleware: false },\n      { path: '/', method: 'DELETE', name: '[handler]', isMiddleware: false },\n      { path: '/', method: 'OPTIONS', name: '[handler]', isMiddleware: false },\n      { path: '/static', method: 'GET', name: '[handler]', isMiddleware: false },\n    ])\n  })\n\n  it('should return [handler] also for sub app', async () => {\n    const subApp = new Hono()\n\n    subApp.get('/', (c) => c.json(0))\n    subApp.onError((_, c) => c.json(0))\n\n    const mainApp = new Hono()\n    mainApp.route('/', subApp)\n    expect(inspectRoutes(mainApp)).toEqual([\n      {\n        isMiddleware: false,\n        method: 'GET',\n        name: '[handler]',\n        path: '/',\n      },\n    ])\n  })\n})\n\ndescribe('showRoutes()', () => {\n  let logs: string[] = []\n\n  let originalLog: typeof console.log\n  beforeAll(() => {\n    originalLog = console.log\n    console.log = (...args) => logs.push(...args)\n  })\n  afterAll(() => {\n    console.log = originalLog\n  })\n\n  beforeEach(() => {\n    logs = []\n  })\n  it('should render simple output', async () => {\n    showRoutes(app)\n    expect(logs).toEqual([\n      '\\x1b[32mGET\\x1b[0m      /',\n      '\\x1b[32mGET\\x1b[0m      /named',\n      '\\x1b[32mPOST\\x1b[0m     /',\n      '\\x1b[32mPUT\\x1b[0m      /',\n      '\\x1b[32mPATCH\\x1b[0m    /',\n      '\\x1b[32mDELETE\\x1b[0m   /',\n      '\\x1b[32mOPTIONS\\x1b[0m  /',\n      '\\x1b[32mGET\\x1b[0m      /static',\n    ])\n  })\n\n  it('should render output includes handlers and middlewares', async () => {\n    showRoutes(app, { verbose: true })\n    expect(logs).toEqual([\n      '\\x1b[32mALL\\x1b[0m      /*',\n      '           [middleware]',\n      '\\x1b[32mGET\\x1b[0m      /',\n      '           [middleware]',\n      '           [handler]',\n      '\\x1b[32mGET\\x1b[0m      /named',\n      '           namedMiddleware',\n      '           namedHandler',\n      '\\x1b[32mPOST\\x1b[0m     /',\n      '           [handler]',\n      '\\x1b[32mPUT\\x1b[0m      /',\n      '           [handler]',\n      '\\x1b[32mPATCH\\x1b[0m    /',\n      '           [handler]',\n      '\\x1b[32mDELETE\\x1b[0m   /',\n      '           [handler]',\n      '\\x1b[32mOPTIONS\\x1b[0m  /',\n      '           [handler]',\n      '\\x1b[32mGET\\x1b[0m      /static',\n      '           [handler]',\n    ])\n  })\n\n  it('should render not colorized output', async () => {\n    showRoutes(app, { colorize: false })\n    expect(logs).toEqual([\n      'GET      /',\n      'GET      /named',\n      'POST     /',\n      'PUT      /',\n      'PATCH    /',\n      'DELETE   /',\n      'OPTIONS  /',\n      'GET      /static',\n    ])\n  })\n})\n\ndescribe('showRoutes() in NO_COLOR', () => {\n  let logs: string[] = []\n\n  let originalLog: typeof console.log\n  beforeAll(() => {\n    vi.stubEnv('NO_COLOR', '1')\n    originalLog = console.log\n    console.log = (...args) => logs.push(...args)\n  })\n  afterAll(() => {\n    vi.unstubAllEnvs()\n    console.log = originalLog\n  })\n\n  beforeEach(() => {\n    logs = []\n  })\n  it('should render not colorized output', async () => {\n    showRoutes(app)\n    expect(logs).toEqual([\n      'GET      /',\n      'GET      /named',\n      'POST     /',\n      'PUT      /',\n      'PATCH    /',\n      'DELETE   /',\n      'OPTIONS  /',\n      'GET      /static',\n    ])\n  })\n  it('should render colorized output if colorize: true', async () => {\n    showRoutes(app, { colorize: true })\n    expect(logs).toEqual([\n      '\\x1b[32mGET\\x1b[0m      /',\n      '\\x1b[32mGET\\x1b[0m      /named',\n      '\\x1b[32mPOST\\x1b[0m     /',\n      '\\x1b[32mPUT\\x1b[0m      /',\n      '\\x1b[32mPATCH\\x1b[0m    /',\n      '\\x1b[32mDELETE\\x1b[0m   /',\n      '\\x1b[32mOPTIONS\\x1b[0m  /',\n      '\\x1b[32mGET\\x1b[0m      /static',\n    ])\n  })\n})\n\ndescribe('getRouterName()', () => {\n  it('Should return the correct router name', async () => {\n    const app = new Hono({\n      router: new RegExpRouter(),\n    })\n    expect(getRouterName(app)).toBe('RegExpRouter')\n  })\n})\n"
  },
  {
    "path": "src/helper/dev/index.ts",
    "content": "/**\n * @module\n * Dev Helper for Hono.\n */\n\nimport type { Hono } from '../../hono'\nimport type { Env, RouterRoute } from '../../types'\nimport { getColorEnabled } from '../../utils/color'\nimport { findTargetHandler, isMiddleware } from '../../utils/handler'\n\ninterface ShowRoutesOptions {\n  verbose?: boolean\n  colorize?: boolean\n}\n\ninterface RouteData {\n  path: string\n  method: string\n  name: string\n  isMiddleware: boolean\n}\n\nconst handlerName = (handler: Function): string => {\n  return handler.name || (isMiddleware(handler) ? '[middleware]' : '[handler]')\n}\n\nexport const inspectRoutes = <E extends Env>(hono: Hono<E>): RouteData[] => {\n  return hono.routes.map(({ path, method, handler }: RouterRoute) => {\n    const targetHandler = findTargetHandler(handler)\n    return {\n      path,\n      method,\n      name: handlerName(targetHandler),\n      isMiddleware: isMiddleware(targetHandler),\n    }\n  })\n}\n\nexport const showRoutes = <E extends Env>(hono: Hono<E>, opts?: ShowRoutesOptions): void => {\n  const colorEnabled = opts?.colorize ?? getColorEnabled()\n  const routeData: Record<string, RouteData[]> = {}\n  let maxMethodLength = 0\n  let maxPathLength = 0\n\n  inspectRoutes(hono)\n    .filter(({ isMiddleware }) => opts?.verbose || !isMiddleware)\n    .map((route) => {\n      const key = `${route.method}-${route.path}`\n      ;(routeData[key] ||= []).push(route)\n      if (routeData[key].length > 1) {\n        return\n      }\n      maxMethodLength = Math.max(maxMethodLength, route.method.length)\n      maxPathLength = Math.max(maxPathLength, route.path.length)\n      return { method: route.method, path: route.path, routes: routeData[key] }\n    })\n    .forEach((data) => {\n      if (!data) {\n        return\n      }\n      const { method, path, routes } = data\n\n      const methodStr = colorEnabled ? `\\x1b[32m${method}\\x1b[0m` : method\n      console.log(`${methodStr} ${' '.repeat(maxMethodLength - method.length)} ${path}`)\n\n      if (!opts?.verbose) {\n        return\n      }\n\n      routes.forEach(({ name }) => {\n        console.log(`${' '.repeat(maxMethodLength + 3)} ${name}`)\n      })\n    })\n}\n\nexport const getRouterName = <E extends Env>(app: Hono<E>): string => {\n  app.router.match('GET', '/')\n  return app.router.name\n}\n"
  },
  {
    "path": "src/helper/factory/index.test.ts",
    "content": "/* eslint-disable @typescript-eslint/no-unused-vars */\nimport { expectTypeOf } from 'vitest'\nimport { hc } from '../../client'\nimport type { ClientRequest } from '../../client/types'\nimport { Hono } from '../../index'\nimport type { Env, ExtractSchema, H, MiddlewareHandler, ToSchema, TypedResponse } from '../../types'\nimport type { ContentfulStatusCode } from '../../utils/http-status'\nimport type { Equal, Expect } from '../../utils/types'\nimport { validator } from '../../validator'\nimport { createFactory, createMiddleware } from './index'\n\ndescribe('createMiddleware', () => {\n  type Env = { Variables: { foo: string } }\n  const app = new Hono()\n\n  const mw = (message: string) =>\n    createMiddleware<Env>(async (c, next) => {\n      expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n      c.set('foo', 'bar')\n      await next()\n      c.header('X-Message', message)\n    })\n\n  const route = app.get('/message', mw('Hello Middleware'), (c) => {\n    return c.text(`Hey, ${c.var.foo}`)\n  })\n\n  it('Should return the correct header and the content', async () => {\n    const res = await app.request('/message')\n    expect(res.status).toBe(200)\n    expect(res.headers.get('x-message')).toBe('Hello Middleware')\n    expect(await res.text()).toBe('Hey, bar')\n  })\n\n  it('Should provide the correct types', async () => {\n    const client = hc<typeof route>('http://localhost')\n    const url = client.message.$url()\n    expect(url.pathname).toBe('/message')\n  })\n\n  it('Should pass generics types to chained handlers', () => {\n    type Bindings = {\n      MY_VAR_IN_BINDINGS: string\n    }\n\n    type Variables = {\n      MY_VAR: string\n    }\n\n    const app = new Hono<{ Bindings: Bindings }>()\n\n    app.get(\n      '/',\n      createMiddleware<{ Variables: Variables }>(async (c, next) => {\n        await next()\n      }),\n      createMiddleware(async (c, next) => {\n        await next()\n      }),\n      async (c) => {\n        const v = c.get('MY_VAR')\n        expectTypeOf(v).toEqualTypeOf<string>()\n      }\n    )\n  })\n\n  it('Should default to MiddlewareHandler<E, P, I, Response>', async () => {\n    const middleware = createMiddleware(async (c, next) => {\n      await next()\n    })\n    type Expected = MiddlewareHandler<any, string, {}, Response>\n    type _verify = Expect<Equal<Expected, typeof middleware>>\n  })\n})\n\ndescribe('createHandler', () => {\n  const mw = (message: string) =>\n    createMiddleware(async (c, next) => {\n      await next()\n      c.header('x-message', message)\n    })\n\n  describe('Basic', () => {\n    const factory = createFactory()\n    const app = new Hono()\n\n    const handlersA = factory.createHandlers((c) => {\n      return c.text('A')\n    })\n    const routesA = app.get('/a', ...handlersA)\n\n    const handlersB = factory.createHandlers(mw('B'), (c) => {\n      return c.text('B')\n    })\n    app.get('/b', ...handlersB)\n\n    it('Should return 200 response - GET /a', async () => {\n      const res = await app.request('/a')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('A')\n    })\n\n    it('Should return 200 response with a custom header - GET /b', async () => {\n      const res = await app.request('/b')\n      expect(res.status).toBe(200)\n      expect(res.headers.get('x-message')).toBe('B')\n      expect(await res.text()).toBe('B')\n    })\n\n    it('Should return correct path types - /a', () => {\n      const client = hc<typeof routesA>('/')\n      expectTypeOf(client).toEqualTypeOf<{\n        a: ClientRequest<\n          string,\n          '/a',\n          {\n            $get: {\n              input: {}\n              output: 'A'\n              outputFormat: 'text'\n              status: ContentfulStatusCode\n            }\n          }\n        >\n      }>()\n    })\n  })\n\n  describe('Types', () => {\n    type Env = { Variables: { foo: string } }\n\n    const factory = createFactory<Env>()\n    const app = new Hono<Env>()\n\n    const handlers = factory.createHandlers(\n      validator('query', () => {\n        return {\n          page: '1',\n        }\n      }),\n      (c) => {\n        const foo = c.var.foo\n        const { page } = c.req.valid('query')\n        return c.json({ page, foo })\n      }\n    )\n    const routes = app.get('/posts', ...handlers)\n\n    type Expected = Hono<\n      Env,\n      ToSchema<\n        'get',\n        '/posts',\n        {\n          in: {\n            query: {\n              page: string\n            }\n          }\n        },\n        TypedResponse<{\n          page: string\n          foo: string\n        }>\n      >,\n      '/'\n    >\n\n    it('Should return correct types', () => {\n      expectTypeOf(routes).toEqualTypeOf<Expected>()\n    })\n  })\n\n  // It's difficult to cover all possible patterns,\n  // so these tests will only cover the minimal cases.\n\n  describe('Types - Complex', () => {\n    type Env = { Variables: { foo: string } }\n\n    const factory = createFactory<Env>()\n    const app = new Hono<Env>()\n\n    const handlers = factory.createHandlers(\n      validator('header', () => {\n        return {\n          auth: 'token',\n        }\n      }),\n      validator('query', () => {\n        return {\n          page: '1',\n        }\n      }),\n      validator('json', () => {\n        return {\n          id: 123,\n        }\n      }),\n      (c) => {\n        const foo = c.var.foo\n        const { auth } = c.req.valid('header')\n        const { page } = c.req.valid('query')\n        const { id } = c.req.valid('json')\n        return c.json({ auth, page, foo, id })\n      }\n    )\n    const routes = app.get('/posts', ...handlers)\n\n    type Expected = Hono<\n      Env,\n      ToSchema<\n        'get',\n        '/posts',\n        {\n          in: {\n            header: {\n              auth: string\n            }\n          } & {\n            query: {\n              page: string\n            }\n          } & {\n            json: {\n              id: number\n            }\n          }\n        },\n        TypedResponse<{\n          auth: string\n          page: string\n          foo: string\n          id: number\n        }>\n      >,\n      '/'\n    >\n\n    it('Should return correct types', () => {\n      expectTypeOf(routes).toEqualTypeOf<Expected>()\n    })\n  })\n\n  describe('Types - Context Env with Multiple Middlewares', () => {\n    const factory = createFactory()\n\n    const mw1 = createMiddleware<\n      { Variables: { foo1: string } },\n      string,\n      { out: { query: { bar1: number } } }\n    >(async () => {})\n    const mw2 = createMiddleware<\n      { Variables: { foo2: string } },\n      string,\n      { out: { query: { bar2: number } } }\n    >(async () => {})\n    const mw3 = createMiddleware<\n      { Variables: { foo3: string } },\n      string,\n      { out: { query: { bar3: number } } }\n    >(async () => {})\n    const mw4 = createMiddleware<\n      { Variables: { foo4: string } },\n      string,\n      { out: { query: { bar4: number } } }\n    >(async () => {})\n    const mw5 = createMiddleware<\n      { Variables: { foo5: string } },\n      string,\n      { out: { query: { bar5: number } } }\n    >(async () => {})\n    const mw6 = createMiddleware<\n      { Variables: { foo6: string } },\n      string,\n      { out: { query: { bar6: number } } }\n    >(async () => {})\n    const mw7 = createMiddleware<\n      { Variables: { foo7: string } },\n      string,\n      { out: { query: { bar7: number } } }\n    >(async () => {})\n    const mw8 = createMiddleware<\n      { Variables: { foo8: string } },\n      string,\n      { out: { query: { bar8: number } } }\n    >(async () => {})\n    const mw9 = createMiddleware<\n      { Variables: { foo9: string } },\n      string,\n      { out: { query: { bar9: number } } }\n    >(async () => {})\n\n    it('Should not throw type error', () => {\n      factory.createHandlers(\n        mw1,\n        mw2,\n        mw3,\n        mw4,\n        mw5,\n        mw6,\n        mw7,\n        mw8,\n        async (c) => {\n          expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n          expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n          expectTypeOf(c.var.foo3).toEqualTypeOf<string>()\n          expectTypeOf(c.var.foo4).toEqualTypeOf<string>()\n          expectTypeOf(c.var.foo5).toEqualTypeOf<string>()\n          expectTypeOf(c.var.foo6).toEqualTypeOf<string>()\n          expectTypeOf(c.var.foo7).toEqualTypeOf<string>()\n          expectTypeOf(c.var.foo8).toEqualTypeOf<string>()\n        },\n        (c) => c.json(0)\n      )\n\n      factory.createHandlers(mw1, mw2, mw3, mw4, mw5, mw6, mw7, mw8, mw9, (c) => {\n        expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo3).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo4).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo5).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo6).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo7).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo8).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo9).toEqualTypeOf<string>()\n\n        return c.json({\n          foo1: c.get('foo1'),\n          foo2: c.get('foo2'),\n          foo3: c.get('foo3'),\n          foo4: c.get('foo4'),\n          foo5: c.get('foo5'),\n          foo6: c.get('foo6'),\n          foo7: c.get('foo7'),\n          foo8: c.get('foo8'),\n          foo9: c.get('foo9'),\n        })\n      })\n    })\n  })\n\n  describe('Types - Multiple Handlers', () => {\n    const factory = createFactory()\n\n    const [handler1, handler2] = factory.createHandlers(\n      (c) => c.json({ first: 1 }),\n      (c) => c.json({ second: 'second' as const })\n    )\n\n    type ExtractOutput<R> = R extends H<any, any, any, TypedResponse<infer Inner>> ? Inner : never\n\n    type Handler1Output = ExtractOutput<typeof handler1>\n    type Handler2Output = ExtractOutput<typeof handler2>\n\n    it('Should allow multiple handlers with independent return types', () => {\n      expectTypeOf<Handler1Output>().toEqualTypeOf<{ first: number }>()\n      expectTypeOf<Handler2Output>().toEqualTypeOf<{ second: 'second' }>()\n    })\n  })\n})\n\ndescribe('createFactory', () => {\n  describe('createApp', () => {\n    type Env = { Variables: { foo: string } }\n    const factory = createFactory<Env>({\n      initApp: (app) => {\n        app.use((c, next) => {\n          c.set('foo', 'bar')\n          return next()\n        })\n      },\n    })\n    const app = factory.createApp()\n    it('Should set the correct type and initialize the app', async () => {\n      app.get('/', (c) => {\n        expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n        return c.text(c.var.foo)\n      })\n      const res = await app.request('/')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('bar')\n    })\n  })\n\n  describe('createMiddleware', () => {\n    it('Should omit the `*` wildcard from the generated schema', () => {\n      const factory = createFactory()\n\n      const middleware = factory.createMiddleware(async (_, next) => {\n        await next()\n      })\n\n      const routes = new Hono().use('*', middleware)\n      type Actual = ExtractSchema<typeof routes>\n      type Expected = {}\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n\n    it('Should default to MiddlewareHandler<E, P, I, Response>', async () => {\n      const factory = createFactory()\n\n      const middleware = factory.createMiddleware(async (c, next) => {\n        await next()\n      })\n      type Expected = MiddlewareHandler<Env, string, {}, Response>\n      type _verify = Expect<Equal<Expected, typeof middleware>>\n    })\n  })\n\n  it('Should use the default app options', async () => {\n    const app = createFactory({ defaultAppOptions: { strict: false } }).createApp()\n    app.get('/hello', (c) => c.text('hello'))\n    const res = await app.request('/hello/')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hello')\n  })\n\n  it('Should override the default app options when creating', async () => {\n    const app = createFactory({ defaultAppOptions: { strict: true } }).createApp({ strict: false })\n    app.get('/hello', (c) => c.text('hello'))\n    const res = await app.request('/hello/')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hello')\n  })\n})\n\ndescribe('Lint rules', () => {\n  it('Should not throw a eslint `unbound-method` error if destructed', () => {\n    const { createApp, createHandlers, createMiddleware } = createFactory()\n    expect(createApp).toBeDefined()\n    expect(createHandlers).toBeDefined()\n    expect(createMiddleware).toBeDefined()\n  })\n})\n"
  },
  {
    "path": "src/helper/factory/index.ts",
    "content": "/**\n * @module\n * Factory Helper for Hono.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { Hono } from '../../hono'\nimport type { HonoOptions } from '../../hono-base'\nimport type {\n  Env,\n  H,\n  HandlerResponse,\n  Input,\n  IntersectNonAnyTypes,\n  MiddlewareHandler,\n} from '../../types'\n\ntype InitApp<E extends Env = Env> = (app: Hono<E>) => void\n\nexport interface CreateHandlersInterface<E extends Env, P extends string> {\n  <I extends Input = {}, R extends HandlerResponse<any> = any, E2 extends Env = E>(\n    handler1: H<E2, P, I, R>\n  ): [H<E2, P, I, R>]\n\n  // handler x2\n  <\n    I extends Input = {},\n    I2 extends Input = I,\n    R extends HandlerResponse<any> = any,\n    R2 extends HandlerResponse<any> = any,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n  >(\n    handler1: H<E2, P, I, R>,\n    handler2: H<E3, P, I2, R2>\n  ): [H<E2, P, I, R>, H<E3, P, I2, R2>]\n\n  // handler x3\n  <\n    I extends Input = {},\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    R extends HandlerResponse<any> = any,\n    R2 extends HandlerResponse<any> = any,\n    R3 extends HandlerResponse<any> = any,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n  >(\n    handler1: H<E2, P, I, R>,\n    handler2: H<E3, P, I2, R2>,\n    handler3: H<E4, P, I3, R3>\n  ): [H<E2, P, I, R>, H<E3, P, I2, R2>, H<E4, P, I3, R3>]\n\n  // handler x4\n  <\n    I extends Input = {},\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    R extends HandlerResponse<any> = any,\n    R2 extends HandlerResponse<any> = any,\n    R3 extends HandlerResponse<any> = any,\n    R4 extends HandlerResponse<any> = any,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n  >(\n    handler1: H<E2, P, I, R>,\n    handler2: H<E3, P, I2, R2>,\n    handler3: H<E4, P, I3, R3>,\n    handler4: H<E5, P, I4, R4>\n  ): [H<E2, P, I, R>, H<E3, P, I2, R2>, H<E4, P, I3, R3>, H<E5, P, I4, R4>]\n\n  // handler x5\n  <\n    I extends Input = {},\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    R extends HandlerResponse<any> = any,\n    R2 extends HandlerResponse<any> = any,\n    R3 extends HandlerResponse<any> = any,\n    R4 extends HandlerResponse<any> = any,\n    R5 extends HandlerResponse<any> = any,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n  >(\n    handler1: H<E2, P, I, R>,\n    handler2: H<E3, P, I2, R2>,\n    handler3: H<E4, P, I3, R3>,\n    handler4: H<E5, P, I4, R4>,\n    handler5: H<E6, P, I5, R5>\n  ): [H<E2, P, I, R>, H<E3, P, I2, R2>, H<E4, P, I3, R3>, H<E5, P, I4, R4>, H<E6, P, I5, R5>]\n\n  // handler x6\n  <\n    I extends Input = {},\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    R extends HandlerResponse<any> = any,\n    R2 extends HandlerResponse<any> = any,\n    R3 extends HandlerResponse<any> = any,\n    R4 extends HandlerResponse<any> = any,\n    R5 extends HandlerResponse<any> = any,\n    R6 extends HandlerResponse<any> = any,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n  >(\n    handler1: H<E2, P, I, R>,\n    handler2: H<E3, P, I2, R2>,\n    handler3: H<E4, P, I3, R3>,\n    handler4: H<E5, P, I4, R4>,\n    handler5: H<E6, P, I5, R5>,\n    handler6: H<E7, P, I6, R6>\n  ): [\n    H<E2, P, I, R>,\n    H<E3, P, I2, R2>,\n    H<E4, P, I3, R3>,\n    H<E5, P, I4, R4>,\n    H<E6, P, I5, R5>,\n    H<E7, P, I6, R6>,\n  ]\n\n  // handler x7\n  <\n    I extends Input = {},\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    I7 extends Input = I & I2 & I3 & I4 & I5 & I6,\n    R extends HandlerResponse<any> = any,\n    R2 extends HandlerResponse<any> = any,\n    R3 extends HandlerResponse<any> = any,\n    R4 extends HandlerResponse<any> = any,\n    R5 extends HandlerResponse<any> = any,\n    R6 extends HandlerResponse<any> = any,\n    R7 extends HandlerResponse<any> = any,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n  >(\n    handler1: H<E2, P, I, R>,\n    handler2: H<E3, P, I2, R2>,\n    handler3: H<E4, P, I3, R3>,\n    handler4: H<E5, P, I4, R4>,\n    handler5: H<E6, P, I5, R5>,\n    handler6: H<E7, P, I6, R6>,\n    handler7: H<E8, P, I7, R7>\n  ): [\n    H<E2, P, I, R>,\n    H<E3, P, I2, R2>,\n    H<E4, P, I3, R3>,\n    H<E5, P, I4, R4>,\n    H<E6, P, I5, R5>,\n    H<E7, P, I6, R6>,\n    H<E8, P, I7, R7>,\n  ]\n\n  // handler x8\n  <\n    I extends Input = {},\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    I7 extends Input = I & I2 & I3 & I4 & I5 & I6,\n    I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7,\n    R extends HandlerResponse<any> = any,\n    R2 extends HandlerResponse<any> = any,\n    R3 extends HandlerResponse<any> = any,\n    R4 extends HandlerResponse<any> = any,\n    R5 extends HandlerResponse<any> = any,\n    R6 extends HandlerResponse<any> = any,\n    R7 extends HandlerResponse<any> = any,\n    R8 extends HandlerResponse<any> = any,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n  >(\n    handler1: H<E2, P, I, R>,\n    handler2: H<E3, P, I2, R2>,\n    handler3: H<E4, P, I3, R3>,\n    handler4: H<E5, P, I4, R4>,\n    handler5: H<E6, P, I5, R5>,\n    handler6: H<E7, P, I6, R6>,\n    handler7: H<E8, P, I7, R7>,\n    handler8: H<E9, P, I8, R8>\n  ): [\n    H<E2, P, I, R>,\n    H<E3, P, I2, R2>,\n    H<E4, P, I3, R3>,\n    H<E5, P, I4, R4>,\n    H<E6, P, I5, R5>,\n    H<E7, P, I6, R6>,\n    H<E8, P, I7, R7>,\n    H<E9, P, I8, R8>,\n  ]\n\n  // handler x9\n  <\n    I extends Input = {},\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    I7 extends Input = I & I2 & I3 & I4 & I5 & I6,\n    I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7,\n    I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8,\n    R extends HandlerResponse<any> = any,\n    R2 extends HandlerResponse<any> = any,\n    R3 extends HandlerResponse<any> = any,\n    R4 extends HandlerResponse<any> = any,\n    R5 extends HandlerResponse<any> = any,\n    R6 extends HandlerResponse<any> = any,\n    R7 extends HandlerResponse<any> = any,\n    R8 extends HandlerResponse<any> = any,\n    R9 extends HandlerResponse<any> = any,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n    E10 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>,\n  >(\n    handler1: H<E2, P, I, R>,\n    handler2: H<E3, P, I2, R2>,\n    handler3: H<E4, P, I3, R3>,\n    handler4: H<E5, P, I4, R4>,\n    handler5: H<E6, P, I5, R5>,\n    handler6: H<E7, P, I6, R6>,\n    handler7: H<E8, P, I7, R7>,\n    handler8: H<E9, P, I8, R8>,\n    handler9: H<E10, P, I9, R9>\n  ): [\n    H<E2, P, I, R>,\n    H<E3, P, I2, R>,\n    H<E4, P, I3, R>,\n    H<E5, P, I4, R>,\n    H<E6, P, I5, R>,\n    H<E7, P, I6, R>,\n    H<E8, P, I7, R>,\n    H<E9, P, I8, R>,\n    H<E10, P, I9, R>,\n  ]\n\n  // handler x10\n  <\n    I extends Input = {},\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    I7 extends Input = I & I2 & I3 & I4 & I5 & I6,\n    I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7,\n    I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8,\n    I10 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8 & I9,\n    R extends HandlerResponse<any> = any,\n    R2 extends HandlerResponse<any> = any,\n    R3 extends HandlerResponse<any> = any,\n    R4 extends HandlerResponse<any> = any,\n    R5 extends HandlerResponse<any> = any,\n    R6 extends HandlerResponse<any> = any,\n    R7 extends HandlerResponse<any> = any,\n    R8 extends HandlerResponse<any> = any,\n    R9 extends HandlerResponse<any> = any,\n    R10 extends HandlerResponse<any> = any,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n    E10 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>,\n    E11 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]>,\n  >(\n    handler1: H<E2, P, I, R>,\n    handler2: H<E3, P, I2, R2>,\n    handler3: H<E4, P, I3, R3>,\n    handler4: H<E5, P, I4, R4>,\n    handler5: H<E6, P, I5, R5>,\n    handler6: H<E7, P, I6, R6>,\n    handler7: H<E8, P, I7, R7>,\n    handler8: H<E9, P, I8, R8>,\n    handler9: H<E10, P, I9, R9>,\n    handler10: H<E11, P, I10, R10>\n  ): [\n    H<E2, P, I, R>,\n    H<E3, P, I2, R2>,\n    H<E4, P, I3, R3>,\n    H<E5, P, I4, R4>,\n    H<E6, P, I5, R5>,\n    H<E7, P, I6, R6>,\n    H<E8, P, I7, R7>,\n    H<E9, P, I8, R8>,\n    H<E10, P, I9, R9>,\n    H<E11, P, I10, R10>,\n  ]\n}\n\nexport class Factory<E extends Env = Env, P extends string = string> {\n  private initApp?: InitApp<E>\n  #defaultAppOptions?: HonoOptions<E>\n\n  constructor(init?: { initApp?: InitApp<E>; defaultAppOptions?: HonoOptions<E> }) {\n    this.initApp = init?.initApp\n    this.#defaultAppOptions = init?.defaultAppOptions\n  }\n\n  createApp = (options?: HonoOptions<E>): Hono<E> => {\n    const app = new Hono<E>(\n      options && this.#defaultAppOptions\n        ? { ...this.#defaultAppOptions, ...options }\n        : (options ?? this.#defaultAppOptions)\n    )\n    if (this.initApp) {\n      this.initApp(app)\n    }\n    return app\n  }\n\n  createMiddleware = <I extends Input = {}, R extends HandlerResponse<any> | void = void>(\n    middleware: MiddlewareHandler<E, P, I, R extends void ? Response : R>\n  ): MiddlewareHandler<E, P, I, R extends void ? Response : R> => middleware\n\n  createHandlers: CreateHandlersInterface<E, P> = (...handlers: any) => {\n    // @ts-expect-error this should not be typed\n    return handlers.filter((handler) => handler !== undefined)\n  }\n}\n\nexport const createFactory = <E extends Env = Env, P extends string = string>(init?: {\n  initApp?: InitApp<E>\n  defaultAppOptions?: HonoOptions<E>\n}): Factory<E, P> => new Factory<E, P>(init)\n\nexport const createMiddleware = <\n  E extends Env = any,\n  P extends string = string,\n  I extends Input = {},\n  R extends HandlerResponse<any> | void = void,\n>(\n  middleware: MiddlewareHandler<E, P, I, R extends void ? Response : R>\n): MiddlewareHandler<E, P, I, R extends void ? Response : R> => middleware\n"
  },
  {
    "path": "src/helper/html/index.test.ts",
    "content": "import { HtmlEscapedCallbackPhase, resolveCallback } from '../../utils/html'\nimport { html, raw } from '.'\n\ndescribe('Tagged Template Literals', () => {\n  it('Should escape special characters', () => {\n    const name = 'John \"Johnny\" Smith'\n\n    expect(html`<p>I'm ${name}.</p>`.toString()).toBe(\"<p>I'm John &quot;Johnny&quot; Smith.</p>\")\n  })\n\n  describe('Booleans, Null, and Undefined Are Ignored', () => {\n    it.each([true, false, undefined, null])('%s', (item) => {\n      expect(html`${item}`.toString()).toBe('')\n    })\n\n    it('falsy value', () => {\n      expect(html`${0}`.toString()).toBe('0')\n    })\n  })\n\n  it('Should call $array.flat(Infinity)', () => {\n    const values = [\n      'Name:',\n      ['John \"Johnny\" Smith', undefined, null],\n      ' Contact:',\n      [html`<a href=\"http://example.com/\">My Website</a>`],\n    ]\n    expect(html`<p>${values}</p>`.toString()).toBe(\n      '<p>Name:John &quot;Johnny&quot; Smith Contact:<a href=\"http://example.com/\">My Website</a></p>'\n    )\n  })\n\n  describe('Promise', () => {\n    it('Should return Promise<string> when some variables contains Promise<string> in variables', async () => {\n      const name = Promise.resolve('John \"Johnny\" Smith')\n      const res = html`<p>I'm ${name}.</p>`\n      expect(res).toBeInstanceOf(Promise)\n\n      expect((await res).toString()).toBe(\"<p>I'm John &quot;Johnny&quot; Smith.</p>\")\n    })\n\n    it('Should return raw value when some variables contains Promise<HtmlEscapedString> in variables', async () => {\n      const name = Promise.resolve(raw('John \"Johnny\" Smith'))\n      const res = html`<p>I'm ${name}.</p>`\n      expect(res).toBeInstanceOf(Promise)\n      expect((await res).toString()).toBe('<p>I\\'m John \"Johnny\" Smith.</p>')\n    })\n  })\n\n  describe('HtmlEscapedString', () => {\n    it('Should preserve callbacks', async () => {\n      const name = raw('Hono', [\n        ({ buffer }) => {\n          if (buffer) {\n            buffer[0] = buffer[0].replace('Hono', 'Hono!')\n          }\n          return undefined\n        },\n      ])\n      const res = html`<p>I'm ${name}.</p>`\n      expect(res).toBeInstanceOf(Promise)\n\n      expect((await res).toString()).toBe(\"<p>I'm Hono.</p>\")\n      expect(await resolveCallback(await res, HtmlEscapedCallbackPhase.Stringify, false, {})).toBe(\n        \"<p>I'm Hono!.</p>\"\n      )\n    })\n  })\n})\n\ndescribe('raw', () => {\n  it('Should be marked as escaped.', () => {\n    const name = 'John &quot;Johnny&quot; Smith'\n    expect(html`<p>I'm ${raw(name)}.</p>`.toString()).toBe(\n      \"<p>I'm John &quot;Johnny&quot; Smith.</p>\"\n    )\n  })\n})\n"
  },
  {
    "path": "src/helper/html/index.ts",
    "content": "/**\n * @module\n * html Helper for Hono.\n */\n\nimport { escapeToBuffer, raw, resolveCallbackSync, stringBufferToString } from '../../utils/html'\nimport type { HtmlEscaped, HtmlEscapedString, StringBufferWithCallbacks } from '../../utils/html'\n\nexport { raw }\n\nexport const html = (\n  strings: TemplateStringsArray,\n  ...values: unknown[]\n): HtmlEscapedString | Promise<HtmlEscapedString> => {\n  const buffer: StringBufferWithCallbacks = [''] as StringBufferWithCallbacks\n\n  for (let i = 0, len = strings.length - 1; i < len; i++) {\n    buffer[0] += strings[i]\n\n    const children = Array.isArray(values[i])\n      ? (values[i] as Array<unknown>).flat(Infinity)\n      : [values[i]]\n    for (let i = 0, len = children.length; i < len; i++) {\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      const child = children[i] as any\n      if (typeof child === 'string') {\n        escapeToBuffer(child, buffer)\n      } else if (typeof child === 'number') {\n        ;(buffer[0] as string) += child\n      } else if (typeof child === 'boolean' || child === null || child === undefined) {\n        continue\n      } else if (typeof child === 'object' && (child as HtmlEscaped).isEscaped) {\n        if ((child as HtmlEscapedString).callbacks) {\n          buffer.unshift('', child)\n        } else {\n          const tmp = child.toString()\n          if (tmp instanceof Promise) {\n            buffer.unshift('', tmp)\n          } else {\n            buffer[0] += tmp\n          }\n        }\n      } else if (child instanceof Promise) {\n        buffer.unshift('', child)\n      } else {\n        escapeToBuffer(child.toString(), buffer)\n      }\n    }\n  }\n  buffer[0] += strings.at(-1) as string\n\n  return buffer.length === 1\n    ? 'callbacks' in buffer\n      ? raw(resolveCallbackSync(raw(buffer[0], buffer.callbacks)))\n      : raw(buffer[0])\n    : stringBufferToString(buffer, buffer.callbacks)\n}\n"
  },
  {
    "path": "src/helper/proxy/index.test.ts",
    "content": "import { Hono } from '../../hono'\nimport { proxy } from '.'\n\ndescribe('Proxy Middleware', () => {\n  describe('proxy', () => {\n    beforeEach(() => {\n      global.fetch = vi.fn().mockImplementation(async (req) => {\n        if (req.url === 'https://example.com/ok') {\n          return Promise.resolve(new Response('ok'))\n        } else if (req.url === 'https://example.com/disconnect') {\n          const reader = req.body.getReader()\n          let response\n\n          req.signal.addEventListener('abort', () => {\n            response = req.signal.reason\n            reader.cancel()\n          })\n\n          await reader.read()\n\n          return Promise.resolve(new Response(response))\n        } else if (req.url === 'https://example.com/compressed') {\n          return Promise.resolve(\n            new Response('ok', {\n              headers: {\n                'Content-Encoding': 'gzip',\n                'Content-Length': '1',\n                'Content-Range': 'bytes 0-2/1024',\n                'X-Response-Id': '456',\n              },\n            })\n          )\n        } else if (req.url === 'https://example.com/uncompressed') {\n          return Promise.resolve(\n            new Response('ok', {\n              headers: {\n                'Content-Length': '2',\n                'Content-Range': 'bytes 0-2/1024',\n                'X-Response-Id': '456',\n              },\n            })\n          )\n        } else if (req.url === 'https://example.com/post' && req.method === 'POST') {\n          return Promise.resolve(new Response(`request body: ${await req.text()}`))\n        } else if (req.url === 'https://example.com/hop-by-hop') {\n          return Promise.resolve(\n            new Response('ok', {\n              headers: {\n                'Transfer-Encoding': 'chunked',\n              },\n            })\n          )\n        } else if (req.url === 'https://example.com/set-cookie') {\n          return Promise.resolve(\n            new Response('ok', {\n              headers: {\n                'Set-Cookie': 'test=123',\n              },\n            })\n          )\n        }\n        return Promise.resolve(new Response('not found', { status: 404 }))\n      })\n    })\n\n    it('compressed', async () => {\n      const app = new Hono()\n      app.get('/proxy/:path', (c) =>\n        proxy(\n          new Request(`https://example.com/${c.req.param('path')}`, {\n            headers: {\n              'X-Request-Id': '123',\n              'Accept-Encoding': 'gzip',\n            },\n          })\n        )\n      )\n      const res = await app.request('/proxy/compressed')\n      const req = (global.fetch as ReturnType<typeof vi.fn>).mock.calls[0][0]\n\n      expect(req.url).toBe('https://example.com/compressed')\n      expect(req.headers.get('X-Request-Id')).toBe('123')\n      expect(req.headers.get('Accept-Encoding')).toBeNull()\n\n      expect(res.status).toBe(200)\n      expect(res.headers.get('X-Response-Id')).toBe('456')\n      expect(res.headers.get('Content-Encoding')).toBeNull()\n      expect(res.headers.get('Content-Length')).toBeNull()\n      expect(res.headers.get('Content-Range')).toBe('bytes 0-2/1024')\n    })\n\n    it('uncompressed', async () => {\n      const app = new Hono()\n      app.get('/proxy/:path', (c) =>\n        proxy(\n          new Request(`https://example.com/${c.req.param('path')}`, {\n            headers: {\n              'X-Request-Id': '123',\n              'Accept-Encoding': 'gzip',\n            },\n          })\n        )\n      )\n      const res = await app.request('/proxy/uncompressed')\n      const req = (global.fetch as ReturnType<typeof vi.fn>).mock.calls[0][0]\n\n      expect(req.url).toBe('https://example.com/uncompressed')\n      expect(req.headers.get('X-Request-Id')).toBe('123')\n      expect(req.headers.get('Accept-Encoding')).toBeNull()\n\n      expect(res.status).toBe(200)\n      expect(res.headers.get('X-Response-Id')).toBe('456')\n      expect(res.headers.get('Content-Length')).toBe('2')\n      expect(res.headers.get('Content-Range')).toBe('bytes 0-2/1024')\n    })\n\n    it('POST request', async () => {\n      const app = new Hono()\n      app.all('/proxy/:path', (c) => {\n        return proxy(`https://example.com/${c.req.param('path')}`, {\n          ...c.req,\n          headers: {\n            ...c.req.header(),\n            'X-Request-Id': '123',\n            'Accept-Encoding': 'gzip',\n          },\n        })\n      })\n      const res = await app.request('/proxy/post', {\n        method: 'POST',\n        body: 'test',\n      })\n      const req = (global.fetch as ReturnType<typeof vi.fn>).mock.calls[0][0]\n\n      expect(req.url).toBe('https://example.com/post')\n\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('request body: test')\n    })\n\n    it('remove hop-by-hop headers', async () => {\n      const app = new Hono()\n      app.get('/proxy/:path', (c) => proxy(`https://example.com/${c.req.param('path')}`, c.req))\n\n      const res = await app.request('/proxy/hop-by-hop', {\n        headers: {\n          Host: 'example.com',\n          Connection: 'keep-alive, custom-header',\n          'Keep-Alive': 'timeout=5, max=1000',\n          'Proxy-Authorization': 'Basic 123456',\n          'Custom-Header': 'test',\n          'Allowed-Custom-Header': 'test',\n        },\n      })\n      const req = (global.fetch as ReturnType<typeof vi.fn>).mock.calls[0][0]\n\n      expect(req.headers.get('Host')).toBe('example.com')\n      expect(req.headers.get('Connection')).toBeNull()\n      expect(req.headers.get('Keep-Alive')).toBeNull()\n      expect(req.headers.get('Proxy-Authorization')).toBeNull()\n      expect(req.headers.get('Custom-Header')).toBe('test')\n      expect(req.headers.get('Allowed-Custom-Header')).toBe('test')\n\n      expect(res.headers.get('Transfer-Encoding')).toBeNull()\n    })\n\n    it('remove hop-by-hop headers with strictConnectionProcessing', async () => {\n      const app = new Hono()\n      app.get('/proxy/:path', (c) =>\n        proxy(`https://example.com/${c.req.param('path')}`, {\n          ...c.req,\n          strictConnectionProcessing: true,\n        })\n      )\n\n      const res = await app.request('/proxy/hop-by-hop', {\n        headers: {\n          Host: 'example.com',\n          Connection: 'keep-alive, custom-header',\n          'Keep-Alive': 'timeout=5, max=1000',\n          'Proxy-Authorization': 'Basic 123456',\n          'Custom-Header': 'test',\n          'Allowed-Custom-Header': 'test',\n        },\n      })\n      const req = (global.fetch as ReturnType<typeof vi.fn>).mock.calls[0][0]\n\n      expect(req.headers.get('Host')).toBe('example.com')\n      expect(req.headers.get('Connection')).toBeNull()\n      expect(req.headers.get('Keep-Alive')).toBeNull()\n      expect(req.headers.get('Proxy-Authorization')).toBeNull()\n      expect(req.headers.get('Custom-Header')).toBeNull()\n      expect(req.headers.get('Allowed-Custom-Header')).toBe('test')\n\n      expect(res.headers.get('Transfer-Encoding')).toBeNull()\n    })\n\n    it('invalid hop-by-hop headers with strictConnectionProcessing', async () => {\n      const app = new Hono()\n      app.get('/proxy/:path', (c) =>\n        proxy(`https://example.com/${c.req.param('path')}`, {\n          ...c.req,\n          strictConnectionProcessing: true,\n        })\n      )\n\n      const res = await app.request('/proxy/hop-by-hop', {\n        headers: {\n          Host: 'example.com',\n          Connection: 'keep-alive, invalid-header invalid-header',\n          'Keep-Alive': 'timeout=5, max=1000',\n        },\n      })\n\n      expect(res.status).toBe(400)\n    })\n\n    it('specify hop-by-hop header by options', async () => {\n      const app = new Hono()\n      app.get('/proxy/:path', (c) =>\n        proxy(`https://example.com/${c.req.param('path')}`, {\n          headers: {\n            'Proxy-Authorization': 'Basic 123456',\n          },\n        })\n      )\n\n      const res = await app.request('/proxy/hop-by-hop')\n      const req = (global.fetch as ReturnType<typeof vi.fn>).mock.calls[0][0]\n\n      expect(req.headers.get('Proxy-Authorization')).toBe('Basic 123456')\n\n      expect(res.headers.get('Transfer-Encoding')).toBeNull()\n    })\n\n    it('modify header', async () => {\n      const app = new Hono()\n      app.get('/proxy/:path', (c) =>\n        proxy(`https://example.com/${c.req.param('path')}`, {\n          headers: {\n            'Set-Cookie': 'test=123',\n          },\n        }).then((res) => {\n          res.headers.delete('Set-Cookie')\n          res.headers.set('X-Response-Id', '456')\n          return res\n        })\n      )\n      const res = await app.request('/proxy/set-cookie')\n      expect(res.headers.get('Set-Cookie')).toBeNull()\n      expect(res.headers.get('X-Response-Id')).toBe('456')\n    })\n\n    it('does not propagate undefined request headers', async () => {\n      const app = new Hono()\n      app.get('/proxy/:path', (c) =>\n        proxy(`https://example.com/${c.req.param('path')}`, {\n          headers: {\n            ...c.req.header(),\n            Authorization: undefined,\n          },\n        })\n      )\n      await app.request('/proxy/ok', {\n        headers: {\n          Authorization: 'Bearer 123',\n        },\n      })\n      const req = (global.fetch as ReturnType<typeof vi.fn>).mock.calls[0][0]\n      expect(req.headers.get('Authorization')).toBeNull()\n    })\n\n    it('client disconnect', async () => {\n      const app = new Hono()\n      const controller = new AbortController()\n      app.post('/proxy/:path', (c) => proxy(`https://example.com/${c.req.param('path')}`, c.req))\n      const resPromise = app.request('/proxy/disconnect', {\n        method: 'POST',\n        body: 'test',\n        signal: controller.signal,\n      })\n      controller.abort('client disconnect')\n      const res = await resPromise\n      expect(await res.text()).toBe('client disconnect')\n    })\n\n    it('not found', async () => {\n      const app = new Hono()\n      app.get('/proxy/:path', (c) => proxy(`https://example.com/${c.req.param('path')}`))\n      const res = await app.request('/proxy/404')\n      expect(res.status).toBe(404)\n    })\n\n    it('pass a Request object to proxyInit', async () => {\n      const app = new Hono()\n      app.get('/proxy/:path', (c) => {\n        const req = new Request(c.req.raw, {\n          headers: {\n            'X-Request-Id': '123',\n            'Accept-Encoding': 'gzip',\n          },\n        })\n        return proxy(`https://example.com/${c.req.param('path')}`, req)\n      })\n      const res = await app.request('/proxy/compressed')\n      const req = (global.fetch as ReturnType<typeof vi.fn>).mock.calls[0][0]\n\n      expect(req.url).toBe('https://example.com/compressed')\n      expect(req.headers.get('X-Request-Id')).toBe('123')\n      expect(req.headers.get('Accept-Encoding')).toBeNull()\n\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('ok')\n    })\n\n    it('Should call the custom fetch method when specified', async () => {\n      const customFetch = vi.fn().mockImplementation(async (req: Request) => {\n        const text = await req.text()\n        return new Response('custom fetch response. message:' + text)\n      })\n      const app = new Hono()\n      app.post('/', (c) => {\n        return proxy(`https://example.com/`, {\n          customFetch,\n          ...c.req,\n        })\n      })\n      const res = await app.request('/', {\n        method: 'POST',\n        body: 'hi',\n      })\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('custom fetch response. message:hi')\n    })\n  })\n})\n"
  },
  {
    "path": "src/helper/proxy/index.ts",
    "content": "/**\n * @module\n * Proxy Helper for Hono.\n */\n\nimport { HTTPException } from '../../http-exception'\nimport type { RequestHeader } from '../../utils/headers'\n\n// https://datatracker.ietf.org/doc/html/rfc2616#section-13.5.1\nconst hopByHopHeaders = [\n  'connection',\n  'keep-alive',\n  'proxy-authenticate',\n  'proxy-authorization',\n  'te',\n  'trailer',\n  'transfer-encoding',\n  'upgrade',\n]\n\n// https://datatracker.ietf.org/doc/html/rfc9110#section-5.6.2\nconst ALLOWED_TOKEN_PATTERN = /^[!#$%&'*+\\-.0-9A-Z^_`a-z|~]+$/\n\ninterface ProxyRequestInit extends Omit<RequestInit, 'headers'> {\n  raw?: Request\n  headers?:\n    | HeadersInit\n    | [string, string][]\n    | Record<RequestHeader, string | undefined>\n    | Record<string, string | undefined>\n  customFetch?: (request: Request) => Promise<Response>\n  /**\n   * Enable strict RFC 9110 compliance for Connection header processing.\n   *\n   * - `false` (default): Ignores Connection header to prevent potential\n   *   Hop-by-Hop Header Injection attacks. Recommended for untrusted clients.\n   * - `true`: Processes Connection header per RFC 9110 and removes listed headers.\n   *   Only use in trusted environments.\n   *\n   * @default false\n   * @see https://datatracker.ietf.org/doc/html/rfc9110#section-7.6.1\n   */\n  strictConnectionProcessing?: boolean\n}\n\ninterface ProxyFetch {\n  (input: string | URL | Request, init?: ProxyRequestInit): Promise<Response>\n}\n\nconst buildRequestInitFromRequest = (\n  request: Request | undefined,\n  strictConnectionProcessing: boolean\n): RequestInit & { duplex?: 'half' } => {\n  if (!request) {\n    return {}\n  }\n\n  const headers = new Headers(request.headers)\n\n  if (strictConnectionProcessing) {\n    // https://datatracker.ietf.org/doc/html/rfc9110#section-7.6.1\n    // Parse Connection header and remove listed headers (MUST per RFC 9110)\n    const connectionValue = headers.get('connection')\n    if (connectionValue) {\n      const headerNames = connectionValue.split(',').map((h) => h.trim())\n      // Validate header names per RFC 9110 Section 5.6.2 (token syntax)\n      const invalidHeaders = headerNames.filter((h) => !ALLOWED_TOKEN_PATTERN.test(h))\n\n      if (invalidHeaders.length > 0) {\n        throw new HTTPException(400, {\n          message: `Invalid Connection header value: ${invalidHeaders.join(', ')}`,\n        })\n      }\n      headerNames.forEach((headerName) => {\n        headers.delete(headerName)\n      })\n    }\n  }\n\n  hopByHopHeaders.forEach((header) => {\n    headers.delete(header)\n  })\n\n  return {\n    method: request.method,\n    body: request.body,\n    duplex: request.body ? 'half' : undefined,\n    headers,\n    signal: request.signal,\n  }\n}\n\nconst preprocessRequestInit = (requestInit: RequestInit): RequestInit => {\n  if (\n    !requestInit.headers ||\n    Array.isArray(requestInit.headers) ||\n    requestInit.headers instanceof Headers\n  ) {\n    return requestInit\n  }\n\n  const headers = new Headers()\n  for (const [key, value] of Object.entries(requestInit.headers)) {\n    if (value == null) {\n      // delete header if value is null or undefined\n      headers.delete(key)\n    } else {\n      headers.set(key, value)\n    }\n  }\n  requestInit.headers = headers\n  return requestInit\n}\n\n/**\n * Fetch API wrapper for proxy.\n * The parameters and return value are the same as for `fetch` (except for the proxy-specific options).\n *\n * The “Accept-Encoding” header is replaced with an encoding that the current runtime can handle.\n * Unnecessary response headers are deleted and a Response object is returned that can be returned\n * as is as a response from the handler.\n *\n * @example\n * ```ts\n * app.get('/proxy/:path', (c) => {\n *   return proxy(`http://${originServer}/${c.req.param('path')}`, {\n *     headers: {\n *       ...c.req.header(), // optional, specify only when forwarding all the request data (including credentials) is necessary.\n *       'X-Forwarded-For': '127.0.0.1',\n *       'X-Forwarded-Host': c.req.header('host'),\n *       Authorization: undefined, // do not propagate request headers contained in c.req.header('Authorization')\n *     },\n *   }).then((res) => {\n *     res.headers.delete('Set-Cookie')\n *     return res\n *   })\n * })\n *\n * app.all('/proxy/:path', (c) => {\n *   return proxy(`http://${originServer}/${c.req.param('path')}`, {\n *     ...c.req, // optional, specify only when forwarding all the request data (including credentials) is necessary.\n *     headers: {\n *       ...c.req.header(),\n *       'X-Forwarded-For': '127.0.0.1',\n *       'X-Forwarded-Host': c.req.header('host'),\n *       Authorization: undefined, // do not propagate request headers contained in c.req.header('Authorization')\n *     },\n *   })\n * })\n *\n * // Strict RFC compliance mode (use only in trusted environments)\n * app.get('/internal-proxy/:path', (c) => {\n *   return proxy(`http://${internalServer}/${c.req.param('path')}`, {\n *     ...c.req,\n *     strictConnectionProcessing: true,\n *   })\n * })\n * ```\n */\nexport const proxy: ProxyFetch = async (input, proxyInit) => {\n  const {\n    raw,\n    customFetch,\n    strictConnectionProcessing = false,\n    ...requestInit\n  } = proxyInit instanceof Request ? { raw: proxyInit } : (proxyInit ?? {})\n\n  const req = new Request(input, {\n    ...buildRequestInitFromRequest(raw, strictConnectionProcessing),\n    ...preprocessRequestInit(requestInit as RequestInit),\n  })\n  req.headers.delete('accept-encoding')\n\n  const res = await (customFetch || fetch)(req)\n  const resHeaders = new Headers(res.headers)\n  hopByHopHeaders.forEach((header) => {\n    resHeaders.delete(header)\n  })\n  if (resHeaders.has('content-encoding')) {\n    resHeaders.delete('content-encoding')\n    // Content-Length is the size of the compressed content, not the size of the original content\n    resHeaders.delete('content-length')\n  }\n\n  return new Response(res.body, {\n    status: res.status,\n    statusText: res.statusText,\n    headers: resHeaders,\n  })\n}\n"
  },
  {
    "path": "src/helper/route/index.test.ts",
    "content": "import { Context } from '../../context'\nimport { matchedRoutes, routePath, baseRoutePath, basePath } from '.'\n\nconst defaultContextOptions = {\n  executionCtx: {\n    waitUntil: () => {},\n    passThroughOnException: () => {},\n    props: {},\n  },\n  env: {},\n}\n\ndescribe('matchedRoutes', () => {\n  it('should return matched routes', () => {\n    const handlerA = () => {}\n    const handlerB = () => {}\n    const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')\n    const c = new Context(rawRequest, {\n      path: '/123/key',\n      matchResult: [\n        [\n          [\n            [handlerA, { basePath: '/', handler: handlerA, method: 'GET', path: '/:id' }],\n            { id: '123' },\n          ],\n          [\n            [handlerA, { basePath: '/', handler: handlerB, method: 'GET', path: '/:id/:name' }],\n            { id: '456', name: 'key' },\n          ],\n        ],\n      ],\n      ...defaultContextOptions,\n    })\n\n    expect(matchedRoutes(c)).toEqual([\n      { basePath: '/', handler: handlerA, method: 'GET', path: '/:id' },\n      { basePath: '/', handler: handlerB, method: 'GET', path: '/:id/:name' },\n    ])\n  })\n})\n\ndescribe('routePath', () => {\n  it('should return route path', () => {\n    const handlerA = () => {}\n    const handlerB = () => {}\n    const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')\n    const c = new Context(rawRequest, {\n      path: '/123/key',\n      matchResult: [\n        [\n          [\n            [handlerA, { basePath: '/', handler: handlerA, method: 'GET', path: '/:id' }],\n            { id: '123' },\n          ],\n          [\n            [handlerA, { basePath: '/', handler: handlerB, method: 'GET', path: '/:id/:name' }],\n            { id: '456', name: 'key' },\n          ],\n        ],\n      ],\n      ...defaultContextOptions,\n    })\n\n    expect(routePath(c)).toBe('/:id')\n\n    expect(routePath(c, 0)).toBe('/:id')\n    expect(routePath(c, 1)).toBe('/:id/:name')\n    expect(routePath(c, -1)).toBe('/:id/:name')\n    expect(routePath(c, 2)).toBe('')\n\n    c.req.routeIndex = 1\n    expect(routePath(c)).toBe('/:id/:name')\n  })\n})\n\ndescribe('baseRoutePath', () => {\n  it('should return raw basePath', () => {\n    const handlerA = () => {}\n    const handlerB = () => {}\n    const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')\n    const c = new Context(rawRequest, {\n      path: '/123/key',\n      matchResult: [\n        [\n          [\n            [handlerA, { basePath: '/', handler: handlerA, method: 'GET', path: '/:id' }],\n            { id: '123' },\n          ],\n          [\n            [handlerA, { basePath: '/sub', handler: handlerB, method: 'GET', path: '/:id/:name' }],\n            { id: '456', name: 'key' },\n          ],\n          [\n            [handlerA, { basePath: '/:sub', handler: handlerB, method: 'GET', path: '/:id/:name' }],\n            { id: '456', name: 'key' },\n          ],\n        ],\n      ],\n      ...defaultContextOptions,\n    })\n\n    expect(baseRoutePath(c)).toBe('/')\n\n    expect(baseRoutePath(c, 0)).toBe('/')\n    expect(baseRoutePath(c, 1)).toBe('/sub')\n    expect(baseRoutePath(c, 2)).toBe('/:sub')\n    expect(baseRoutePath(c, -1)).toBe('/:sub')\n    expect(baseRoutePath(c, 3)).toBe('')\n\n    c.req.routeIndex = 1\n    expect(baseRoutePath(c)).toBe('/sub')\n\n    c.req.routeIndex = 2\n    expect(baseRoutePath(c)).toBe('/:sub')\n  })\n})\n\ndescribe('basePath', () => {\n  it('should return basePath without parameters', () => {\n    const handlerA = () => {}\n    const handlerB = () => {}\n    const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')\n    const c = new Context(rawRequest, {\n      path: '/123/key',\n      matchResult: [\n        [\n          [\n            [handlerA, { basePath: '/', handler: handlerA, method: 'GET', path: '/:id' }],\n            { id: '123' },\n          ],\n          [\n            [handlerA, { basePath: '/sub', handler: handlerB, method: 'GET', path: '/:id/:name' }],\n            { id: '456', name: 'key' },\n          ],\n        ],\n      ],\n      ...defaultContextOptions,\n    })\n\n    expect(basePath(c)).toBe('/')\n    expect(basePath(c)).toBe('/') // cached value\n\n    expect(basePath(c, 0)).toBe('/')\n    expect(basePath(c, 1)).toBe('/sub')\n    expect(basePath(c, -1)).toBe('/sub')\n    expect(basePath(c, 2)).toBe('')\n\n    c.req.routeIndex = 1\n    expect(basePath(c)).toBe('/sub')\n  })\n\n  it('should return basePath with embedded parameters', () => {\n    const handlerA = () => {}\n    const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')\n    const c = new Context(rawRequest, {\n      path: '/sub-app-path/123/key',\n      matchResult: [\n        [\n          [\n            [handlerA, { basePath: '/:sub', handler: handlerA, method: 'GET', path: '/:id' }],\n            { id: '123' },\n          ],\n        ],\n      ],\n      ...defaultContextOptions,\n    })\n\n    expect(basePath(c)).toBe('/sub-app-path')\n  })\n\n  it('should return basePath with wildcard', () => {\n    const handlerA = () => {}\n    const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')\n    const c = new Context(rawRequest, {\n      path: '/sub-app-path/foo/app/123/key',\n      matchResult: [\n        [\n          [\n            [\n              handlerA,\n              { basePath: '/sub-app-path/*/app', handler: handlerA, method: 'GET', path: '/:id' },\n            ],\n            { id: '123' },\n          ],\n        ],\n      ],\n      ...defaultContextOptions,\n    })\n\n    expect(basePath(c)).toBe('/sub-app-path/foo/app')\n  })\n\n  it('should return basePath with custom regex pattern', () => {\n    const handlerA = () => {}\n    const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')\n    const c = new Context(rawRequest, {\n      path: '/sub-app-path/foo/123/key',\n      matchResult: [\n        [\n          [\n            [\n              handlerA,\n              {\n                basePath: '/sub-app-path/:sub{foo|bar}',\n                handler: handlerA,\n                method: 'GET',\n                path: '/:id',\n              },\n            ],\n            { sub: 'foo', id: '123' },\n          ],\n        ],\n      ],\n      ...defaultContextOptions,\n    })\n\n    expect(basePath(c)).toBe('/sub-app-path/foo')\n  })\n})\n"
  },
  {
    "path": "src/helper/route/index.ts",
    "content": "import type { Context } from '../../context'\nimport { GET_MATCH_RESULT } from '../../request/constants'\nimport type { RouterRoute } from '../../types'\nimport { getPattern, splitRoutingPath } from '../../utils/url'\n\n/**\n * Get matched routes in the handler\n *\n * @param {Context} c - The context object\n * @returns An array of matched routes\n *\n * @example\n * ```ts\n * import { matchedRoutes } from 'hono/route'\n *\n * app.use('*', async function logger(c, next) {\n *   await next()\n *   matchedRoutes(c).forEach(({ handler, method, path }, i) => {\n *     const name = handler.name || (handler.length < 2 ? '[handler]' : '[middleware]')\n *     console.log(\n *       method,\n *       ' ',\n *       path,\n *       ' '.repeat(Math.max(10 - path.length, 0)),\n *       name,\n *       i === c.req.routeIndex ? '<- respond from here' : ''\n *     )\n *   })\n * })\n * ```\n */\nexport const matchedRoutes = (c: Context): RouterRoute[] =>\n  // @ts-expect-error c.req[GET_MATCH_RESULT] is not typed\n  (c.req as unknown)[GET_MATCH_RESULT][0].map(([[, route]]) => route)\n\n/**\n * Get the route path registered within the handler\n *\n * @param {Context} c - The context object\n * @param {number} index - The index of the root from which to retrieve the path, similar to Array.prototype.at(), where a negative number is the index counted from the end of the matching root. Defaults to the current root index.\n * @returns The route path registered within the handler\n *\n * @example\n * ```ts\n * import { routePath } from 'hono/route'\n *\n * app.use('*', (c, next) => {\n *   console.log(routePath(c)) // '*'\n *   console.log(routePath(c, -1)) // '/posts/:id'\n *   return next()\n * })\n *\n * app.get('/posts/:id', (c) => {\n *   return c.text(routePath(c)) // '/posts/:id'\n * })\n * ```\n */\nexport const routePath = (c: Context, index?: number): string =>\n  matchedRoutes(c).at(index ?? c.req.routeIndex)?.path ?? ''\n\n/**\n * Get the basePath of the as-is route specified by routing.\n *\n * @param {Context} c - The context object\n * @param {number} index - The index of the root from which to retrieve the path, similar to Array.prototype.at(), where a negative number is the index counted from the end of the matching root. Defaults to the current root index.\n * @returns The basePath of the as-is route specified by routing.\n *\n * @example\n * ```ts\n * import { baseRoutePath } from 'hono/route'\n *\n * const app = new Hono()\n *\n * const subApp = new Hono()\n * subApp.get('/posts/:id', (c) => {\n *   return c.text(baseRoutePath(c)) // '/:sub'\n * })\n *\n * app.route('/:sub', subApp)\n * ```\n */\nexport const baseRoutePath = (c: Context, index?: number): string =>\n  matchedRoutes(c).at(index ?? c.req.routeIndex)?.basePath ?? ''\n\n/**\n * Get the basePath with embedded parameters\n *\n * @param {Context} c - The context object\n * @param {number} index - The index of the root from which to retrieve the path, similar to Array.prototype.at(), where a negative number is the index counted from the end of the matching root. Defaults to the current root index.\n * @returns The basePath with embedded parameters.\n *\n * @example\n * ```ts\n * import { basePath } from 'hono/route'\n *\n * const app = new Hono()\n *\n * const subApp = new Hono()\n * subApp.get('/posts/:id', (c) => {\n *   return c.text(basePath(c)) // '/requested-sub-app-path'\n * })\n *\n * app.route('/:sub', subApp)\n * ```\n */\nconst basePathCacheMap: WeakMap<Context, Record<number, string>> = new WeakMap()\nexport const basePath = (c: Context, index?: number): string => {\n  index ??= c.req.routeIndex\n\n  const cache = basePathCacheMap.get(c) || []\n  if (typeof cache[index] === 'string') {\n    return cache[index]\n  }\n\n  let result: string\n  const rp = baseRoutePath(c, index)\n  if (!/[:*]/.test(rp)) {\n    result = rp\n  } else {\n    const paths = splitRoutingPath(rp)\n\n    const reqPath = c.req.path\n    let basePathLength = 0\n    for (let i = 0, len = paths.length; i < len; i++) {\n      const pattern = getPattern(paths[i], paths[i + 1])\n      if (pattern) {\n        const re = pattern[2] === true || pattern === '*' ? /[^\\/]+/ : pattern[2]\n        basePathLength += reqPath.substring(basePathLength + 1).match(re)?.[0].length || 0\n      } else {\n        basePathLength += paths[i].length\n      }\n      basePathLength += 1 // for '/'\n    }\n    result = reqPath.substring(0, basePathLength)\n  }\n\n  cache[index] = result\n  basePathCacheMap.set(c, cache)\n\n  return result\n}\n"
  },
  {
    "path": "src/helper/ssg/index.ts",
    "content": "/**\n * @module\n * SSG Helper for Hono.\n */\n\nexport * from './ssg'\nexport {\n  X_HONO_DISABLE_SSG_HEADER_KEY,\n  ssgParams,\n  isSSGContext,\n  disableSSG,\n  onlySSG,\n} from './middleware'\nexport { defaultPlugin, redirectPlugin } from './plugins'\n"
  },
  {
    "path": "src/helper/ssg/middleware.ts",
    "content": "import type { Context } from '../../context'\nimport type { Env, MiddlewareHandler } from '../../types'\nimport { isDynamicRoute } from './utils'\n\nexport const SSG_CONTEXT = 'HONO_SSG_CONTEXT'\nexport const X_HONO_DISABLE_SSG_HEADER_KEY = 'x-hono-disable-ssg'\n\n/**\n * @deprecated\n * Use `X_HONO_DISABLE_SSG_HEADER_KEY` instead.\n * This constant will be removed in the next minor version.\n */\nexport const SSG_DISABLED_RESPONSE = (() => {\n  try {\n    return new Response('SSG is disabled', {\n      status: 404,\n      headers: { [X_HONO_DISABLE_SSG_HEADER_KEY]: 'true' },\n    })\n  } catch {\n    return null\n  }\n})() as Response\n\ninterface SSGParam {\n  [key: string]: string\n}\nexport type SSGParams = SSGParam[]\n\ninterface SSGParamsMiddleware {\n  <E extends Env = Env>(\n    generateParams: (c: Context<E>) => SSGParams | Promise<SSGParams>\n  ): MiddlewareHandler<E>\n  <E extends Env = Env>(params: SSGParams): MiddlewareHandler<E>\n}\n\nexport type AddedSSGDataRequest = Request & {\n  ssgParams?: SSGParams\n}\n\n/**\n * Define SSG Route\n */\nexport const ssgParams: SSGParamsMiddleware = (params) => async (c, next) => {\n  if (isDynamicRoute(c.req.path)) {\n    ;(c.req.raw as AddedSSGDataRequest).ssgParams = Array.isArray(params) ? params : await params(c)\n    return c.notFound() // Prevent subsequent handler execution after ssgParams\n  }\n  await next()\n}\n\n/**\n * @experimental\n * `isSSGContext` is an experimental feature.\n * The API might be changed.\n */\nexport const isSSGContext = (c: Context): boolean => !!c.env?.[SSG_CONTEXT]\n\n/**\n * @experimental\n * `disableSSG` is an experimental feature.\n * The API might be changed.\n */\nexport const disableSSG = (): MiddlewareHandler =>\n  async function disableSSG(c, next) {\n    if (isSSGContext(c)) {\n      c.header(X_HONO_DISABLE_SSG_HEADER_KEY, 'true')\n      return c.notFound()\n    }\n    await next()\n  }\n\n/**\n * @experimental\n * `onlySSG` is an experimental feature.\n * The API might be changed.\n */\nexport const onlySSG = (): MiddlewareHandler =>\n  async function onlySSG(c, next) {\n    if (!isSSGContext(c)) {\n      return c.notFound()\n    }\n    await next()\n  }\n"
  },
  {
    "path": "src/helper/ssg/plugins.test.tsx",
    "content": "import { Hono } from '../../hono'\nimport type { RedirectStatusCode, StatusCode } from '../../utils/http-status'\nimport * as plugins from './plugins'\nimport { toSSG } from './ssg'\nimport type { FileSystemModule } from './ssg'\n\nconst { defaultPlugin, redirectPlugin } = plugins\n\ndescribe('Built-in SSG plugins', () => {\n  let app: Hono\n  let fsMock: FileSystemModule\n\n  beforeEach(() => {\n    app = new Hono()\n    app.get('/', (c) => c.html('<h1>Home</h1>'))\n    app.get('/about', (c) => c.html('<h1>About</h1>'))\n    app.get('/blog', (c) => c.html('<h1>Blog</h1>'))\n    app.get('/created', (c) => c.text('201 Created', 201))\n    app.get('/redirect', (c) => c.redirect('/'))\n    app.get('/notfound', (c) => c.notFound())\n    app.get('/error', (c) => c.text('500 Error', 500))\n\n    fsMock = {\n      writeFile: vi.fn(() => Promise.resolve()),\n      mkdir: vi.fn(() => Promise.resolve()),\n    }\n  })\n\n  describe('default plugin', () => {\n    it('uses defaultPlugin when plugins option is omitted', async () => {\n      const defaultPluginSpy = vi.spyOn(plugins, 'defaultPlugin')\n      await toSSG(app, fsMock, { dir: './static' })\n      expect(defaultPluginSpy).toHaveBeenCalled()\n      defaultPluginSpy.mockRestore()\n    })\n\n    it('skips non-200 responses with defaultPlugin', async () => {\n      const result = await toSSG(app, fsMock, { plugins: [defaultPlugin()], dir: './static' })\n      expect(fsMock.writeFile).toHaveBeenCalledWith('static/index.html', '<h1>Home</h1>')\n      expect(fsMock.writeFile).toHaveBeenCalledWith('static/about.html', '<h1>About</h1>')\n      expect(fsMock.writeFile).toHaveBeenCalledWith('static/blog.html', '<h1>Blog</h1>')\n      expect(fsMock.writeFile).not.toHaveBeenCalledWith('static/created.txt', expect.any(String))\n      expect(fsMock.writeFile).not.toHaveBeenCalledWith('static/redirect.txt', expect.any(String))\n      expect(fsMock.writeFile).not.toHaveBeenCalledWith('static/notfound.txt', expect.any(String))\n      expect(fsMock.writeFile).not.toHaveBeenCalledWith('static/error.txt', expect.any(String))\n      expect(result.files.some((f) => f.includes('created'))).toBe(false)\n      expect(result.files.some((f) => f.includes('redirect'))).toBe(false)\n      expect(result.files.some((f) => f.includes('notfound'))).toBe(false)\n      expect(result.files.some((f) => f.includes('error'))).toBe(false)\n      expect(result.success).toBe(true)\n    })\n  })\n\n  describe('redirect plugin', () => {\n    it('generates redirect HTML for status codes requiring Location per HTTP Semantics specification', async () => {\n      const statusCodes = [301, 302, 303, 307, 308] satisfies RedirectStatusCode[]\n      for (const statusCode of statusCodes) {\n        const writtenFiles: Record<string, string> = {}\n        const fsMockLocal: FileSystemModule = {\n          writeFile: (path, data) => {\n            writtenFiles[path] = typeof data === 'string' ? data : data.toString()\n            return Promise.resolve()\n          },\n          mkdir: vi.fn(() => Promise.resolve()),\n        }\n        const redirectApp = new Hono()\n        redirectApp.get('/old', (c) => c.redirect('/new', statusCode)) // Default is 302\n        redirectApp.get('/new', (c) => c.html('New Page'))\n\n        await toSSG(redirectApp, fsMockLocal, { dir: './static', plugins: [redirectPlugin()] })\n\n        expect(writtenFiles['static/old.html']).toBeDefined()\n        const content = writtenFiles['static/old.html']\n        // Should contain meta refresh\n        expect(content).toContain('meta http-equiv=\"refresh\" content=\"0;url=/new\"')\n        // Should contain canonical\n        expect(content).toContain('rel=\"canonical\" href=\"/new\"')\n        // Should contain robots noindex\n        expect(content).toContain('<meta name=\"robots\" content=\"noindex\" />')\n        // Should contain link anchor\n        expect(content).toContain('<a href=\"/new\">Redirecting to <code>/new</code></a>')\n        // Should contain a body element that includes the anchor\n        expect(content).toMatch(/<body[^>]*>[\\s\\S]*<a href=\\\"\\/new\\\">[\\s\\S]*<\\/body>/)\n      }\n    })\n\n    it('skips generating redirect HTML for status codes requiring Location when Location header is missing', async () => {\n      const statusCodes = [301, 302, 303, 307, 308] satisfies RedirectStatusCode[]\n      for (const statusCode of statusCodes) {\n        const writtenFiles: Record<string, string> = {}\n        const fsMockLocal: FileSystemModule = {\n          writeFile: (path, data) => {\n            writtenFiles[path] = typeof data === 'string' ? data : data.toString()\n            return Promise.resolve()\n          },\n          mkdir: vi.fn(() => Promise.resolve()),\n        }\n        const redirectApp = new Hono()\n        redirectApp.get('/bad', () => new Response(null, { status: statusCode }))\n\n        await toSSG(redirectApp, fsMockLocal, { dir: './static', plugins: [redirectPlugin()] })\n\n        expect(writtenFiles['static/bad.html']).toBeUndefined()\n      }\n    })\n\n    it('skips generating redirect HTML for status codes not requiring Location per HTTP Semantics specification', async () => {\n      const statusCodes = [300, 304, 305, 306] satisfies RedirectStatusCode[]\n      for (const statusCode of statusCodes) {\n        const writtenFiles: Record<string, string> = {}\n        const fsMockLocal: FileSystemModule = {\n          writeFile: (path, data) => {\n            writtenFiles[path] = typeof data === 'string' ? data : data.toString()\n            return Promise.resolve()\n          },\n          mkdir: vi.fn(() => Promise.resolve()),\n        }\n        const redirectApp = new Hono()\n\n        redirectApp.get(\n          '/response',\n          () => new Response(null, { status: statusCode, headers: { Location: '/' } })\n        )\n\n        await toSSG(redirectApp, fsMockLocal, { dir: './static', plugins: [redirectPlugin()] })\n\n        expect(writtenFiles['static/response.html']).toBeUndefined()\n      }\n    })\n\n    it('does not apply redirect HTML for non-redirect status codes even with Location header', async () => {\n      const statusCodes = [200, 201, 400, 404, 500] satisfies Exclude<\n        StatusCode,\n        RedirectStatusCode\n      >[]\n      for (const statusCode of statusCodes) {\n        const writtenFiles: Record<string, string> = {}\n        const fsMockLocal: FileSystemModule = {\n          writeFile: (path, data) => {\n            writtenFiles[path] = typeof data === 'string' ? data : data.toString()\n            return Promise.resolve()\n          },\n          mkdir: vi.fn(() => Promise.resolve()),\n        }\n        const redirectApp = new Hono()\n\n        redirectApp.get(\n          '/response',\n          () => new Response('Response Body', { status: statusCode, headers: { Location: '/' } })\n        )\n\n        await toSSG(redirectApp, fsMockLocal, { dir: './static', plugins: [redirectPlugin()] })\n\n        expect(writtenFiles['static/response.txt']).toBeDefined()\n        expect(writtenFiles['static/response.txt']).toBe('Response Body')\n      }\n    })\n\n    it('escapes Location header values when generating redirect HTML', async () => {\n      const writtenFiles: Record<string, string> = {}\n      const fsMockLocal: FileSystemModule = {\n        writeFile: (path, data) => {\n          writtenFiles[path] = typeof data === 'string' ? data : data.toString()\n          return Promise.resolve()\n        },\n        mkdir: vi.fn(() => Promise.resolve()),\n      }\n\n      const maliciousLocation = '/new\"> <script>alert(1)</script>'\n      const redirectApp = new Hono()\n      redirectApp.get(\n        '/evil',\n        () => new Response(null, { status: 301, headers: { Location: maliciousLocation } })\n      )\n\n      await toSSG(redirectApp, fsMockLocal, { dir: './static', plugins: [redirectPlugin()] })\n\n      const content = writtenFiles['static/evil.html']\n      expect(content).toBeDefined()\n      expect(content).not.toContain('<script>alert(1)</script>')\n      expect(content).toContain('&lt;script&gt;alert(1)&lt;/script&gt;')\n      expect(content).toContain('&quot;')\n    })\n\n    it('redirectPlugin before defaultPlugin generates redirect HTML', async () => {\n      const writtenFiles: Record<string, string> = {}\n      const fsMockLocal: FileSystemModule = {\n        writeFile: (path, data) => {\n          writtenFiles[path] = typeof data === 'string' ? data : data.toString()\n          return Promise.resolve()\n        },\n        mkdir: vi.fn(() => Promise.resolve()),\n      }\n\n      const redirectApp = new Hono()\n      redirectApp.get('/old', (c) => c.redirect('/new'))\n      redirectApp.get('/new', (c) => c.html('New Page'))\n\n      await toSSG(redirectApp, fsMockLocal, {\n        dir: './static',\n        plugins: [redirectPlugin(), defaultPlugin()],\n      })\n      expect(writtenFiles['static/old.html']).toBeDefined()\n    })\n\n    it('redirectPlugin after defaultPlugin does not generate redirect HTML', async () => {\n      const writtenFiles: Record<string, string> = {}\n      const fsMockLocal: FileSystemModule = {\n        writeFile: (path, data) => {\n          writtenFiles[path] = typeof data === 'string' ? data : data.toString()\n          return Promise.resolve()\n        },\n        mkdir: vi.fn(() => Promise.resolve()),\n      }\n\n      const redirectApp = new Hono()\n      redirectApp.get('/old', (c) => c.redirect('/new'))\n      redirectApp.get('/new', (c) => c.html('New Page'))\n\n      await toSSG(redirectApp, fsMockLocal, {\n        dir: './static',\n        plugins: [defaultPlugin(), redirectPlugin()],\n      })\n      expect(writtenFiles['static/old.html']).toBeUndefined()\n    })\n  })\n})\n"
  },
  {
    "path": "src/helper/ssg/plugins.ts",
    "content": "import { html } from '../html'\nimport type { SSGPlugin } from './ssg'\n\n/**\n * The default plugin that defines the recommended behavior.\n *\n * @experimental\n * `defaultPlugin` is an experimental feature.\n * The API might be changed.\n */\nexport const defaultPlugin = (): SSGPlugin => {\n  return {\n    afterResponseHook: (res) => {\n      if (res.status !== 200) {\n        return false\n      }\n      return res\n    },\n  }\n}\n\nconst REDIRECT_STATUS_CODES = new Set([301, 302, 303, 307, 308])\n\nconst generateRedirectHtml = (location: string) => {\n  // prettier-ignore\n  const content = html`<!DOCTYPE html>\n<title>Redirecting to: ${location}</title>\n<meta http-equiv=\"refresh\" content=\"0;url=${location}\" />\n<meta name=\"robots\" content=\"noindex\" />\n<link rel=\"canonical\" href=\"${location}\" />\n<body>\n<a href=\"${location}\">Redirecting to <code>${location}</code></a>\n</body>\n`\n  return content.toString().replace(/\\n/g, '')\n}\n\n/**\n * The redirect plugin that generates HTML redirect pages for HTTP redirect responses for status codes 301, 302, 303, 307 and 308.\n *\n * When used with `defaultPlugin`, place `redirectPlugin` before it, because `defaultPlugin` skips non-200 responses.\n *\n * ```ts\n * // ✅ Will work as expected\n * toSSG(app, fs, { plugins: [redirectPlugin(), defaultPlugin()] })\n *\n * // ❌ Will not work as expected\n * toSSG(app, fs, { plugins: [defaultPlugin(), redirectPlugin()] })\n * ```\n *\n * @experimental\n * `redirectPlugin` is an experimental feature.\n * The API might be changed.\n */\nexport const redirectPlugin = (): SSGPlugin => {\n  return {\n    afterResponseHook: (res) => {\n      if (REDIRECT_STATUS_CODES.has(res.status)) {\n        const location = res.headers.get('Location')\n        if (!location) {\n          return false\n        }\n        const htmlBody = generateRedirectHtml(location)\n        return new Response(htmlBody, {\n          status: 200,\n          headers: { 'Content-Type': 'text/html; charset=utf-8' },\n        })\n      }\n      return res\n    },\n  }\n}\n"
  },
  {
    "path": "src/helper/ssg/ssg.test.tsx",
    "content": "/* eslint-disable @typescript-eslint/no-unused-vars */\n/** @jsxImportSource ../../jsx */\nimport { Hono } from '../../hono'\nimport { poweredBy } from '../../middleware/powered-by'\nimport {\n  X_HONO_DISABLE_SSG_HEADER_KEY,\n  disableSSG,\n  isSSGContext,\n  onlySSG,\n  ssgParams,\n} from './middleware'\nimport { defaultExtensionMap, fetchRoutesContent, saveContentToFile, toSSG } from './ssg'\nimport type {\n  AfterGenerateHook,\n  AfterResponseHook,\n  BeforeRequestHook,\n  FileSystemModule,\n  ToSSGResult,\n  SSGPlugin,\n  ToSSGOptions,\n} from './ssg'\n\nconst resolveRoutesContent = async (res: ReturnType<typeof fetchRoutesContent>) => {\n  const htmlMap = new Map<string, { content: string | ArrayBuffer; mimeType: string }>()\n  for (const getInfoPromise of res) {\n    const getInfo = await getInfoPromise\n    if (!getInfo) {\n      continue\n    }\n    for (const dataPromise of getInfo) {\n      const data = await dataPromise\n      if (!data) {\n        continue\n      }\n      htmlMap.set(data.routePath, {\n        content: data.content,\n        mimeType: data.mimeType,\n      })\n    }\n  }\n  return htmlMap\n}\n\ndescribe('toSSG function', () => {\n  let app: Hono\n  let fsMock: FileSystemModule\n\n  const postParams = [{ post: '1' }, { post: '2' }]\n\n  beforeEach(() => {\n    app = new Hono()\n    app.all('/', (c) => c.html('Hello, World!'))\n    app.get('/about', (c) => c.html('About Page'))\n    app.get('/about/some', (c) => c.text('About Page 2tier'))\n    app.post('/about/some/thing', (c) => c.text('About Page 3tier'))\n    app.get('/bravo', (c) => c.html('Bravo Page'))\n    app.get('/Charlie', async (c, next) => {\n      c.setRenderer((content, head) => {\n        return c.html(\n          <html>\n            <head>\n              <title>{head.title || ''}</title>\n            </head>\n            <body>\n              <p>{content}</p>\n            </body>\n          </html>\n        )\n      })\n      await next()\n    })\n    app.get('/Charlie', (c) => {\n      return c.render('Hello!', { title: 'Charlies Page' })\n    })\n\n    // Included params\n    app.get(\n      '/post/:post',\n      ssgParams(() => postParams),\n      (c) => c.html(<h1>{c.req.param('post')}</h1>)\n    )\n\n    app.get(\n      '/user/:user_id',\n      ssgParams([{ user_id: '1' }, { user_id: '2' }, { user_id: '3' }]),\n      (c) => c.html(<h1>{c.req.param('user_id')}</h1>)\n    )\n\n    type Env = {\n      Bindings: {\n        FOO_DB: string\n      }\n      Variables: {\n        FOO_VAR: string\n      }\n    }\n\n    app.get(\n      '/env-type-check',\n      ssgParams<Env>((c) => {\n        expectTypeOf<typeof c.env.FOO_DB>().toBeString()\n        expectTypeOf<typeof c.var.FOO_VAR>().toBeString()\n        return []\n      })\n    )\n\n    fsMock = {\n      writeFile: vi.fn(() => Promise.resolve()),\n      mkdir: vi.fn(() => Promise.resolve()),\n    }\n  })\n  it('Should correctly generate static HTML files for Hono routes', async () => {\n    const writtenFiles: Record<string, string> = {}\n    const fsMock: FileSystemModule = {\n      writeFile: (path, data) => {\n        writtenFiles[path] = typeof data === 'string' ? data : data.toString()\n        return Promise.resolve()\n      },\n      mkdir: vi.fn(() => Promise.resolve()),\n    }\n\n    const result = await toSSG(app, fsMock, { dir: './static' })\n\n    for (const postParam of postParams) {\n      const html = writtenFiles[`static/post/${postParam.post}.html`]\n      expect(html).toBe(`<h1>${postParam.post}</h1>`)\n    }\n\n    for (let i = 1; i <= 3; i++) {\n      const html = writtenFiles[`static/user/${i}.html`]\n      expect(html).toBe(`<h1>${i}</h1>`)\n    }\n\n    expect(result.files.length).toBe(10)\n    expect(fsMock.mkdir).toHaveBeenCalledWith(expect.any(String), {\n      recursive: true,\n    })\n  })\n\n  it('Should handle file system errors correctly in saveContentToFiles', async () => {\n    const fsMock: FileSystemModule = {\n      writeFile: vi.fn(() => Promise.reject(new Error('Write error'))),\n      mkdir: vi.fn(() => Promise.resolve()),\n    }\n\n    const result = await toSSG(app, fsMock, { dir: './static' })\n    expect(result.success).toBe(false)\n    expect(result.files).toStrictEqual([])\n    expect(result.error?.message).toBe('Write error')\n  })\n\n  it('Should handle overall process errors correctly in toSSG', async () => {\n    const fsMock: FileSystemModule = {\n      writeFile: vi.fn(() => Promise.reject(new Error('Write error'))),\n      mkdir: vi.fn(() => Promise.resolve()),\n    }\n\n    const result = await toSSG(app, fsMock, { dir: './static' })\n\n    expect(result.success).toBe(false)\n    expect(result.error).toBeDefined()\n    expect(result.files).toStrictEqual([])\n  })\n\n  it('Should correctly generate files with the expected paths', async () => {\n    app.get('/data', (c) =>\n      c.text(JSON.stringify({ title: 'hono' }), 200, {\n        'Content-Type': 'text/x-foo',\n      })\n    )\n    await toSSG(app, fsMock, {\n      dir: './static',\n      extensionMap: {\n        ...defaultExtensionMap,\n        'text/x-foo': 'foo',\n      },\n    })\n\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/index.html', expect.any(String))\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/about.html', expect.any(String))\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/about/some.txt', expect.any(String))\n    expect(fsMock.writeFile).not.toHaveBeenCalledWith(\n      'static/about/some/thing.txt',\n      expect.any(String)\n    )\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/about.html', expect.any(String))\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/bravo.html', expect.any(String))\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/Charlie.html', expect.any(String))\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/data.foo', expect.any(String))\n  })\n\n  it('should modify the request if the hook is provided', async () => {\n    const beforeRequestHook: BeforeRequestHook = (req) => {\n      if (req.method === 'GET') {\n        return req\n      }\n      return false\n    }\n    const result = await toSSG(app, fsMock, { beforeRequestHook })\n    expect(result.files).toHaveLength(10)\n  })\n\n  it('should skip the route if the request hook returns false', async () => {\n    const beforeRequest: BeforeRequestHook = () => false\n    const result = await toSSG(app, fsMock, { beforeRequestHook: beforeRequest })\n    expect(result.success).toBe(true)\n    expect(result.files).toStrictEqual([])\n  })\n\n  it('should modify the response if the hook is provided', async () => {\n    const afterResponseHook: AfterResponseHook = (res) => {\n      if (res.status === 200 || res.status === 500) {\n        return res\n      }\n      return false\n    }\n    const result = await toSSG(app, fsMock, { afterResponseHook })\n    expect(result.files).toHaveLength(10)\n  })\n\n  it('should skip the route if the response hook returns false', async () => {\n    const afterResponse: AfterResponseHook = () => false\n    const result = await toSSG(app, fsMock, { afterResponseHook: afterResponse })\n    expect(result.success).toBe(true)\n    expect(result.files).toStrictEqual([])\n  })\n\n  it('should execute additional processing using afterGenerateHook', async () => {\n    const fsMock: FileSystemModule = {\n      writeFile: vi.fn(() => Promise.resolve()),\n      mkdir: vi.fn(() => Promise.resolve()),\n    }\n    const afterGenerateHookMock: AfterGenerateHook = vi.fn<AfterGenerateHook>(\n      (result, fsModule, options) => {\n        if (result.files) {\n          result.files.forEach((file) => console.log(file))\n        }\n      }\n    )\n\n    await toSSG(app, fsMock, { dir: './static', afterGenerateHook: afterGenerateHookMock })\n\n    expect(afterGenerateHookMock).toHaveBeenCalled()\n    expect(afterGenerateHookMock).toHaveBeenCalledWith(\n      expect.anything(), // result\n      expect.anything(), // fsModule\n      expect.anything() // options\n    )\n  })\n\n  it('should handle asynchronous beforeRequestHook correctly', async () => {\n    const beforeRequestHook: BeforeRequestHook = async (req) => {\n      await new Promise((resolve) => setTimeout(resolve, 10))\n      if (req.url.includes('/skip')) {\n        return false\n      }\n      return req\n    }\n\n    const result = await toSSG(app, fsMock, { beforeRequestHook })\n    expect(result.files).not.toContain(expect.stringContaining('/skip'))\n    expect(result.success).toBe(true)\n    expect(result.files.length).toBeGreaterThan(0)\n  })\n\n  it('should handle asynchronous afterResponseHook correctly', async () => {\n    const afterResponseHook: AfterResponseHook = async (res) => {\n      await new Promise((resolve) => setTimeout(resolve, 10))\n      if (res.headers.get('X-Skip') === 'true') {\n        return false\n      }\n      return res\n    }\n\n    const result = await toSSG(app, fsMock, { afterResponseHook })\n    expect(result.files).not.toContain(expect.stringContaining('/skip'))\n    expect(result.success).toBe(true)\n    expect(result.files.length).toBeGreaterThan(0)\n  })\n\n  it('should handle asynchronous afterGenerateHook correctly', async () => {\n    const afterGenerateHook: AfterGenerateHook = async (result, fsModule, options) => {\n      await new Promise((resolve) => setTimeout(resolve, 10))\n      console.log(`Generated ${result.files.length} files.`)\n    }\n\n    const result = await toSSG(app, fsMock, { afterGenerateHook })\n    expect(result.success).toBe(true)\n    expect(result.files.length).toBeGreaterThan(0)\n  })\n\n  it('should avoid memory leak from `req.signal.addEventListener()`', async () => {\n    const fsMock: FileSystemModule = {\n      writeFile: vi.fn(() => Promise.resolve()),\n      mkdir: vi.fn(() => Promise.resolve()),\n    }\n\n    const signalAddEventListener = vi.fn(() => {})\n    const app = new Hono()\n    app.get('/post/:post', ssgParams([{ post: '1' }, { post: '2' }]), (c) =>\n      c.html(<h1>{c.req.param('post')}</h1>)\n    )\n    await toSSG(app, fsMock, {\n      beforeRequestHook: (req) => {\n        req.signal.addEventListener = signalAddEventListener\n        return req\n      },\n    })\n\n    expect(signalAddEventListener).not.toHaveBeenCalled()\n  })\n})\n\ndescribe('fetchRoutesContent function', () => {\n  let app: Hono\n\n  beforeEach(() => {\n    app = new Hono()\n    app.get('/text', (c) => c.text('Text Response'))\n    app.get('/text-utf8', (c) => {\n      return c.text('Text Response', 200, { 'Content-Type': 'text/plain;charset=UTF-8' })\n    })\n    app.get('/html', (c) => c.html('<p>HTML Response</p>'))\n    app.get('/json', (c) => c.json({ message: 'JSON Response' }))\n    app.use('*', poweredBy())\n  })\n\n  it('should fetch the correct content and MIME type for each route', async () => {\n    const htmlMap = await resolveRoutesContent(fetchRoutesContent(app))\n\n    expect(htmlMap.get('/text')).toEqual({\n      content: 'Text Response',\n      mimeType: 'text/plain',\n    })\n    expect(htmlMap.get('/text-utf8')).toEqual({\n      content: 'Text Response',\n      mimeType: 'text/plain',\n    })\n    expect(htmlMap.get('/html')).toEqual({\n      content: '<p>HTML Response</p>',\n      mimeType: 'text/html',\n    })\n    expect(htmlMap.get('/json')).toEqual({\n      content: '{\"message\":\"JSON Response\"}',\n      mimeType: 'application/json',\n    })\n  })\n\n  it('should skip middleware routes', async () => {\n    const htmlMap = await resolveRoutesContent(fetchRoutesContent(app))\n    expect(htmlMap.has('*')).toBeFalsy()\n  })\n\n  it('should handle errors correctly', async () => {\n    vi.spyOn(app, 'fetch').mockRejectedValue(new Error('Network error'))\n    await expect(resolveRoutesContent(fetchRoutesContent(app))).rejects.toThrow('Network error')\n    vi.restoreAllMocks()\n  })\n})\n\ndescribe('saveContentToFile function', () => {\n  // tar.gz, testdir/test.txt\n  const gzFileBuffer = Buffer.from(\n    'H4sIAAAAAAAAA+3SQQrCMBSE4aw9RU6gSc3LO0/FLgqukgj29qZgsQgqCEHE/9vMIoEMTMqQy3FMO9OQq1RkTq/i1rkwPkiMUXWvnXG+U/XGSstSi3MufbLWHIZ0mvLYP7v37vxHldv+c27LpbR4Yx44hvBi/3DfX3zdP0j9Eta1KPPoz/ef+mnz7Q4AAAAAAAAAAAAAAAAAPnMFqt1/BQAoAAA=',\n    'base64'\n  )\n  const gzFileArrayBuffer = gzFileBuffer.buffer.slice(\n    gzFileBuffer.byteOffset,\n    gzFileBuffer.byteLength + gzFileBuffer.byteOffset\n  )\n  // PNG, red dot (1x1)\n  const pngFileBuffer = Buffer.from(\n    'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCCAAAALw',\n    'base64'\n  )\n  const pngFileArrayBuffer = pngFileBuffer.buffer.slice(\n    pngFileBuffer.byteOffset,\n    pngFileBuffer.byteLength + pngFileBuffer.byteOffset\n  )\n\n  const fileData = [\n    { routePath: '/', content: 'Home Page', mimeType: 'text/html' },\n    { routePath: '/index.html', content: 'Home Page2', mimeType: 'text/html' },\n    { routePath: '/about', content: 'About Page', mimeType: 'text/html' },\n    { routePath: '/about/', content: 'About Page', mimeType: 'text/html' },\n    { routePath: '/bravo/index.html', content: 'About Page', mimeType: 'text/html' },\n    { routePath: '/bravo/release-4.0.0', content: 'Release 4.0.0', mimeType: 'text/html' },\n    {\n      routePath: '/bravo/2024.02.18-sweet-memories',\n      content: 'Sweet Memories',\n      mimeType: 'text/html',\n    },\n    { routePath: '/bravo/deep.dive.to.html', content: 'Deep Dive To HTML', mimeType: 'text/html' },\n    { routePath: '/bravo/alert.js', content: 'alert(\"evil content\")', mimeType: 'text/html' },\n    { routePath: '/bravo.text/index.html', content: 'About Page', mimeType: 'text/html' },\n    { routePath: '/bravo.text/', content: 'Bravo Page', mimeType: 'text/html' },\n    {\n      routePath: '/bravo/index.tar.gz',\n      content: gzFileArrayBuffer,\n      mimeType: 'application/gzip',\n    },\n    {\n      routePath: '/bravo/dot.png',\n      content: pngFileArrayBuffer,\n      mimeType: 'image/png',\n    },\n  ]\n\n  let fsMock: FileSystemModule\n\n  beforeEach(() => {\n    fsMock = {\n      writeFile: vi.fn(() => Promise.resolve()),\n      mkdir: vi.fn(() => Promise.resolve()),\n    }\n  })\n\n  it('should correctly create files with the right content and paths', async () => {\n    for (const data of fileData) {\n      await saveContentToFile(Promise.resolve(data), fsMock, './static')\n    }\n\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/index.html', 'Home Page')\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/index.html', 'Home Page2')\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/about.html', 'About Page')\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/about/index.html', 'About Page')\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/bravo/index.html', 'About Page')\n    expect(fsMock.writeFile).toHaveBeenCalledWith(\n      'static/bravo/release-4.0.0.html',\n      'Release 4.0.0'\n    )\n    expect(fsMock.writeFile).toHaveBeenCalledWith(\n      'static/bravo/deep.dive.to.html',\n      'Deep Dive To HTML'\n    )\n    expect(fsMock.writeFile).toHaveBeenCalledWith(\n      'static/bravo/2024.02.18-sweet-memories.html',\n      'Sweet Memories'\n    )\n    expect(fsMock.writeFile).toHaveBeenCalledWith(\n      'static/bravo/alert.js.html',\n      'alert(\"evil content\")'\n    )\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/bravo.text/index.html', 'About Page')\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/bravo.text/index.html', 'Bravo Page')\n    // binary files\n    expect(fsMock.writeFile).toHaveBeenCalledWith(\n      'static/bravo/index.tar.gz',\n      new Uint8Array(gzFileArrayBuffer)\n    )\n    expect(fsMock.writeFile).toHaveBeenCalledWith(\n      'static/bravo/dot.png',\n      new Uint8Array(pngFileArrayBuffer)\n    )\n  })\n\n  it('should correctly create directories if they do not exist', async () => {\n    await saveContentToFile(\n      Promise.resolve({\n        routePath: '/new-dir/index.html',\n        content: 'New Page',\n        mimeType: 'text/html',\n      }),\n      fsMock,\n      './static'\n    )\n    expect(fsMock.mkdir).toHaveBeenCalledWith('static/new-dir', { recursive: true })\n  })\n\n  it('should handle file writing or directory creation errors', async () => {\n    const fsMock: FileSystemModule = {\n      writeFile: vi.fn(() => Promise.resolve()),\n      mkdir: vi.fn(() => Promise.reject(new Error('File write error'))),\n    }\n\n    await expect(\n      saveContentToFile(\n        Promise.resolve({\n          routePath: '/error-dir/index.html',\n          content: 'New Page',\n          mimeType: 'text/html',\n        }),\n        fsMock,\n        './static'\n      )\n    ).rejects.toThrow('File write error')\n  })\n  it('check extensions', async () => {\n    for (const data of fileData) {\n      await saveContentToFile(Promise.resolve(data), fsMock, './static-check-extensions')\n    }\n    expect(fsMock.mkdir).toHaveBeenCalledWith('static-check-extensions', { recursive: true })\n  })\n\n  it('should correctly create .yaml files for YAML content', async () => {\n    const yamlContent = 'title: YAML Example\\nvalue: This is a YAML file.'\n    const mimeType = 'application/yaml'\n    const routePath = '/example'\n\n    const yamlData = {\n      routePath: routePath,\n      content: yamlContent,\n      mimeType: mimeType,\n    }\n\n    const fsMock: FileSystemModule = {\n      writeFile: vi.fn(() => Promise.resolve()),\n      mkdir: vi.fn(() => Promise.resolve()),\n    }\n\n    await saveContentToFile(Promise.resolve(yamlData), fsMock, './static')\n\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/example.yaml', yamlContent)\n  })\n\n  it('should correctly create .yml files for YAML content', async () => {\n    const yamlContent = 'title: YAML Example\\nvalue: This is a YAML file.'\n    const yamlMimeType = 'application/yaml'\n    const yamlRoutePath = '/yaml'\n\n    const yamlData = {\n      routePath: yamlRoutePath,\n      content: yamlContent,\n      mimeType: yamlMimeType,\n    }\n\n    const yamlMimeType2 = 'x-yaml'\n    const yamlRoutePath2 = '/yaml2'\n    const yamlData2 = {\n      routePath: yamlRoutePath2,\n      content: yamlContent,\n      mimeType: yamlMimeType2,\n    }\n\n    const htmlMimeType = 'text/html'\n    const htmlRoutePath = '/html'\n\n    const htmlData = {\n      routePath: htmlRoutePath,\n      content: yamlContent,\n      mimeType: htmlMimeType,\n    }\n\n    const fsMock: FileSystemModule = {\n      writeFile: vi.fn(() => Promise.resolve()),\n      mkdir: vi.fn(() => Promise.resolve()),\n    }\n\n    const extensionMap = {\n      'application/yaml': 'yml',\n      'x-yaml': 'xyml',\n    }\n    await saveContentToFile(Promise.resolve(yamlData), fsMock, './static', extensionMap)\n    await saveContentToFile(Promise.resolve(yamlData2), fsMock, './static', extensionMap)\n    await saveContentToFile(Promise.resolve(htmlData), fsMock, './static', extensionMap)\n    await saveContentToFile(Promise.resolve(htmlData), fsMock, './static', {\n      ...defaultExtensionMap,\n      ...extensionMap,\n    })\n\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/yaml.yml', yamlContent)\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/yaml2.xyml', yamlContent)\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/html.htm', yamlContent) // extensionMap\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/html.html', yamlContent) // default + extensionMap\n  })\n})\n\ndescribe('Dynamic route handling', () => {\n  let app: Hono\n  beforeEach(() => {\n    app = new Hono()\n    app.get('/shops/:id', (c) => c.html('Shop Page'))\n    app.get('/shops/:id/:comments([0-9]+)', (c) => c.html('Comments Page'))\n    app.get('/foo/*', (c) => c.html('Foo Page'))\n    app.get('/foo:bar', (c) => c.html('Foo Bar Page'))\n  })\n\n  it('should skip /shops/:id dynamic route', async () => {\n    const htmlMap = await resolveRoutesContent(fetchRoutesContent(app))\n    expect(htmlMap.has('/shops/:id')).toBeFalsy()\n  })\n\n  it('should skip /shops/:id/:comments([0-9]+) dynamic route', async () => {\n    const htmlMap = await resolveRoutesContent(fetchRoutesContent(app))\n    expect(htmlMap.has('/shops/:id/:comments([0-9]+)')).toBeFalsy()\n  })\n\n  it('should skip /foo/* dynamic route', async () => {\n    const htmlMap = await resolveRoutesContent(fetchRoutesContent(app))\n    expect(htmlMap.has('/foo/*')).toBeFalsy()\n  })\n\n  it('should not skip /foo:bar dynamic route', async () => {\n    const htmlMap = await resolveRoutesContent(fetchRoutesContent(app))\n    expect(htmlMap.has('/foo:bar')).toBeTruthy()\n  })\n})\n\ndescribe('isSSGContext()', () => {\n  const app = new Hono()\n  app.get('/', (c) => c.html(<h1>{isSSGContext(c) ? 'SSG' : 'noSSG'}</h1>))\n\n  const fsMock: FileSystemModule = {\n    writeFile: vi.fn(() => Promise.resolve()),\n    mkdir: vi.fn(() => Promise.resolve()),\n  }\n\n  it('Should not generate the page if disableSSG is set', async () => {\n    await toSSG(app, fsMock, { dir: './static' })\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/index.html', '<h1>SSG</h1>')\n  })\n\n  it('Should return 404 response if onlySSG() is set', async () => {\n    const res = await app.request('/')\n    expect(await res.text()).toBe('<h1>noSSG</h1>')\n  })\n})\n\ndescribe('disableSSG/onlySSG middlewares', () => {\n  const app = new Hono()\n  app.get('/', (c) => c.html(<h1>Hello</h1>))\n  app.get('/api', disableSSG(), (c) => c.text('an-api'))\n  app.get('/disable-by-response', (c) =>\n    c.text('', 404, { [X_HONO_DISABLE_SSG_HEADER_KEY]: 'true' })\n  )\n  app.get('/static-page', onlySSG(), (c) => c.html(<h1>Welcome to my site</h1>))\n\n  const fsMock: FileSystemModule = {\n    writeFile: vi.fn(() => Promise.resolve()),\n    mkdir: vi.fn(() => Promise.resolve()),\n  }\n\n  it('Should not generate the page if disableSSG is set', async () => {\n    await toSSG(app, fsMock, { dir: './static' })\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/index.html', expect.any(String))\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/static-page.html', expect.any(String))\n    expect(fsMock.writeFile).not.toHaveBeenCalledWith('static/api.html', expect.any(String))\n    expect(fsMock.writeFile).not.toHaveBeenCalledWith(\n      'static/disable-by-response.html',\n      expect.any(String)\n    )\n  })\n\n  it('Should return 404 response if onlySSG() is set', async () => {\n    const res = await app.request('/static-page')\n    expect(res.status).toBe(404)\n  })\n})\n\ndescribe('Request hooks - filterPathsBeforeRequestHook and denyPathsBeforeRequestHook', () => {\n  let app: Hono\n  let fsMock: FileSystemModule\n\n  const filterPathsBeforeRequestHook = (allowedPaths: string | string[]): BeforeRequestHook => {\n    const baseURL = 'http://localhost'\n    return async (req: Request): Promise<Request | false> => {\n      const paths = Array.isArray(allowedPaths) ? allowedPaths : [allowedPaths]\n      const pathname = new URL(req.url, baseURL).pathname\n\n      if (paths.some((path) => pathname === path || pathname.startsWith(`${path}/`))) {\n        return req\n      }\n\n      return false\n    }\n  }\n\n  const denyPathsBeforeRequestHook = (deniedPaths: string | string[]): BeforeRequestHook => {\n    const baseURL = 'http://localhost'\n    return async (req: Request): Promise<Request | false> => {\n      const paths = Array.isArray(deniedPaths) ? deniedPaths : [deniedPaths]\n      const pathname = new URL(req.url, baseURL).pathname\n\n      if (!paths.some((path) => pathname === path || pathname.startsWith(`${path}/`))) {\n        return req\n      }\n      return false\n    }\n  }\n\n  beforeEach(() => {\n    app = new Hono()\n    app.get('/allowed-path', (c) => c.html('Allowed Path Page'))\n    app.get('/denied-path', (c) => c.html('Denied Path Page'))\n    app.get('/other-path', (c) => c.html('Other Path Page'))\n\n    fsMock = {\n      writeFile: vi.fn(() => Promise.resolve()),\n      mkdir: vi.fn(() => Promise.resolve()),\n    }\n  })\n\n  it('should only process requests for allowed paths with filterPathsBeforeRequestHook', async () => {\n    const allowedPathsHook = filterPathsBeforeRequestHook(['/allowed-path'])\n\n    const result = await toSSG(app, fsMock, {\n      dir: './static',\n      beforeRequestHook: allowedPathsHook,\n    })\n\n    expect(result.files.some((file) => file.includes('allowed-path.html'))).toBe(true)\n    expect(result.files.some((file) => file.includes('other-path.html'))).toBe(false)\n  })\n\n  it('should deny requests for specified paths with denyPathsBeforeRequestHook', async () => {\n    const deniedPathsHook = denyPathsBeforeRequestHook(['/denied-path'])\n\n    const result = await toSSG(app, fsMock, { dir: './static', beforeRequestHook: deniedPathsHook })\n\n    expect(result.files.some((file) => file.includes('denied-path.html'))).toBe(false)\n\n    expect(result.files.some((file) => file.includes('allowed-path.html'))).toBe(true)\n    expect(result.files.some((file) => file.includes('other-path.html'))).toBe(true)\n  })\n})\n\ndescribe('Combined Response hooks - modify response content', () => {\n  let app: Hono\n  let fsMock: FileSystemModule\n\n  const prependContentAfterResponseHook = (prefix: string): AfterResponseHook => {\n    return async (res: Response): Promise<Response> => {\n      const originalText = await res.text()\n      return new Response(`${prefix}${originalText}`, res)\n    }\n  }\n\n  const appendContentAfterResponseHook = (suffix: string): AfterResponseHook => {\n    return async (res: Response): Promise<Response> => {\n      const originalText = await res.text()\n      return new Response(`${originalText}${suffix}`, res)\n    }\n  }\n\n  beforeEach(() => {\n    app = new Hono()\n    app.get('/content-path', (c) => c.text('Original Content'))\n\n    fsMock = {\n      writeFile: vi.fn(() => Promise.resolve()),\n      mkdir: vi.fn(() => Promise.resolve()),\n    }\n  })\n\n  it('should modify response content with combined AfterResponseHooks', async () => {\n    const prefixHook = prependContentAfterResponseHook('Prefix-')\n    const suffixHook = appendContentAfterResponseHook('-Suffix')\n\n    const combinedHook = [prefixHook, suffixHook]\n\n    await toSSG(app, fsMock, {\n      dir: './static',\n      afterResponseHook: combinedHook,\n    })\n\n    // Assert that the response content is modified by both hooks\n    // This assumes you have a way to inspect the content of saved files or you need to mock/stub the Response text method correctly.\n    expect(fsMock.writeFile).toHaveBeenCalledWith(\n      'static/content-path.txt',\n      'Prefix-Original Content-Suffix'\n    )\n  })\n})\n\ndescribe('Combined Generate hooks - AfterGenerateHook', () => {\n  let app: Hono\n  let fsMock: FileSystemModule\n\n  const logResultAfterGenerateHook = (): AfterGenerateHook => {\n    return async (\n      result: ToSSGResult,\n      fsModule: FileSystemModule,\n      options?: ToSSGOptions\n    ): Promise<void> => {\n      console.log('Generation completed with status:', result.success) // Log the generation success\n    }\n  }\n\n  const appendFilesAfterGenerateHook = (additionalFiles: string[]): AfterGenerateHook => {\n    return async (\n      result: ToSSGResult,\n      fsModule: FileSystemModule,\n      options?: ToSSGOptions\n    ): Promise<void> => {\n      result.files = result.files.concat(additionalFiles) // Append additional files to the result\n    }\n  }\n\n  beforeEach(() => {\n    app = new Hono()\n    app.get('/path', (c) => c.text('Page Content'))\n\n    fsMock = {\n      writeFile: vi.fn(() => Promise.resolve()),\n      mkdir: vi.fn(() => Promise.resolve()),\n    }\n  })\n\n  it('should execute combined AfterGenerateHooks affecting the result', async () => {\n    const logHook = logResultAfterGenerateHook()\n    const appendHook = appendFilesAfterGenerateHook(['/extra/file1.html', '/extra/file2.html'])\n\n    const combinedHook = [logHook, appendHook]\n\n    const consoleSpy = vi.spyOn(console, 'log')\n    const result = await toSSG(app, fsMock, {\n      dir: './static',\n      afterGenerateHook: combinedHook,\n    })\n\n    // Check that the log function was called correctly\n    expect(consoleSpy).toHaveBeenCalledWith('Generation completed with status:', true)\n\n    // Check that additional files were appended to the result\n    expect(result.files).toContain('/extra/file1.html')\n    expect(result.files).toContain('/extra/file2.html')\n  })\n})\n\ndescribe('SSG Plugin System', () => {\n  let app: Hono\n  let fsMock: FileSystemModule\n\n  beforeEach(() => {\n    app = new Hono()\n    app.get('/', (c) => c.html('<h1>Home</h1>'))\n    app.get('/about', (c) => c.html('<h1>About</h1>'))\n    app.get('/blog', (c) => c.html('<h1>Blog</h1>'))\n    app.get('/created', (c) => c.text('201 Created', 201))\n    app.get('/redirect', (c) => c.redirect('/'))\n    app.get('/notfound', (c) => c.notFound())\n    app.get('/error', (c) => c.text('500 Error', 500))\n\n    fsMock = {\n      writeFile: vi.fn(() => Promise.resolve()),\n      mkdir: vi.fn(() => Promise.resolve()),\n    }\n  })\n\n  it('should correctly apply plugins with beforeRequestHook', async () => {\n    const plugin: SSGPlugin = {\n      beforeRequestHook: (req) => {\n        // Skip requests to the blog page\n        const url = new URL(req.url)\n        if (url.pathname === '/blog') {\n          return false\n        }\n        return req\n      },\n    }\n\n    const result = await toSSG(app, fsMock, {\n      plugins: [plugin],\n    })\n\n    expect(result.files).toHaveLength(6)\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/index.html', '<h1>Home</h1>')\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/about.html', '<h1>About</h1>')\n    expect(fsMock.writeFile).not.toHaveBeenCalledWith('static/blog.html', '<h1>Blog</h1>')\n  })\n\n  it('should correctly apply plugins with afterResponseHook', async () => {\n    const plugin: SSGPlugin = {\n      afterResponseHook: async (res) => {\n        const text = await res.text()\n        return new Response(text.replace('</h1>', ' - Modified</h1>'), res)\n      },\n    }\n\n    await toSSG(app, fsMock, {\n      plugins: [plugin],\n    })\n\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/index.html', '<h1>Home - Modified</h1>')\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/about.html', '<h1>About - Modified</h1>')\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/blog.html', '<h1>Blog - Modified</h1>')\n  })\n\n  it('should correctly apply plugins with afterGenerateHook', async () => {\n    const additionalFiles = ['sitemap.xml', 'robots.txt']\n    const plugin: SSGPlugin = {\n      afterGenerateHook: (result) => {\n        result.files.push(...additionalFiles)\n      },\n    }\n\n    const result = await toSSG(app, fsMock, {\n      plugins: [plugin],\n    })\n\n    expect(result.files).toContain('sitemap.xml')\n    expect(result.files).toContain('robots.txt')\n  })\n\n  it('should correctly combine multiple plugins', async () => {\n    const skipBlogPlugin: SSGPlugin = {\n      beforeRequestHook: (req) => {\n        const url = new URL(req.url)\n        if (url.pathname === '/blog') {\n          return false\n        }\n        return req\n      },\n    }\n\n    const prefixPlugin: SSGPlugin = {\n      afterResponseHook: async (res) => {\n        const text = await res.text()\n        return new Response(`[Prefix] ${text}`, res)\n      },\n    }\n\n    const sitemapPlugin: SSGPlugin = {\n      afterGenerateHook: (result, fsModule, options) => {\n        result.files.push('sitemap.xml')\n      },\n    }\n\n    const result = await toSSG(app, fsMock, {\n      plugins: [skipBlogPlugin, prefixPlugin, sitemapPlugin],\n    })\n\n    expect(result.files).toHaveLength(7)\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/index.html', '[Prefix] <h1>Home</h1>')\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/about.html', '[Prefix] <h1>About</h1>')\n    expect(fsMock.writeFile).not.toHaveBeenCalledWith('static/blog.html', expect.any(String))\n    expect(result.files).toContain('sitemap.xml')\n  })\n\n  it('should correctly combine plugin hooks with option hooks', async () => {\n    const plugin: SSGPlugin = {\n      afterResponseHook: async (res) => {\n        const text = await res.text()\n        return new Response(`${text} [Plugin]`, res)\n      },\n    }\n\n    const afterResponseHook: AfterResponseHook = async (res) => {\n      const text = await res.text()\n      return new Response(`${text} [Option]`, res)\n    }\n\n    await toSSG(app, fsMock, {\n      plugins: [plugin],\n      afterResponseHook,\n    })\n\n    expect(fsMock.writeFile).toHaveBeenCalledWith(\n      'static/index.html',\n      '<h1>Home</h1> [Option] [Plugin]'\n    )\n    expect(fsMock.writeFile).toHaveBeenCalledWith(\n      'static/about.html',\n      '<h1>About</h1> [Option] [Plugin]'\n    )\n    expect(fsMock.writeFile).toHaveBeenCalledWith(\n      'static/blog.html',\n      '<h1>Blog</h1> [Option] [Plugin]'\n    )\n  })\n})\n\ndescribe('ssgParams', () => {\n  it('should invoke callback only once', async () => {\n    const app = new Hono()\n    const cb = vi.fn(() => [{ post: '1' }, { post: '2' }])\n    app.get('/post/:post', ssgParams(cb), (c) => c.html(<h1>{c.req.param('post')}</h1>))\n    const fsMock: FileSystemModule = {\n      writeFile: vi.fn(() => Promise.resolve()),\n      mkdir: vi.fn(() => Promise.resolve()),\n    }\n    await toSSG(app, fsMock)\n\n    expect(cb).toHaveBeenCalledTimes(1)\n  })\n\n  it('should not invoke handler after ssgParams for dynamic route request', async () => {\n    const app = new Hono()\n    const log = vi.fn()\n    app.get(\n      '/shops/:id',\n      ssgParams(() => [{ id: 'shop1' }]),\n      async (c) => {\n        const id = c.req.param('id')\n        log(id)\n        return c.html(id)\n      }\n    )\n    const fsMock: FileSystemModule = {\n      writeFile: vi.fn(() => Promise.resolve()),\n      mkdir: vi.fn(() => Promise.resolve()),\n    }\n    await toSSG(app, fsMock)\n\n    expect(log).toHaveBeenCalledTimes(1)\n    expect(log).toHaveBeenCalledWith('shop1')\n    expect(fsMock.writeFile).toHaveBeenCalledTimes(1)\n    expect(fsMock.writeFile).toHaveBeenCalledWith('static/shops/shop1.html', 'shop1')\n  })\n})\n"
  },
  {
    "path": "src/helper/ssg/ssg.ts",
    "content": "import { replaceUrlParam } from '../../client/utils'\nimport type { Hono } from '../../hono'\nimport type { Env, Schema } from '../../types'\nimport { createPool } from '../../utils/concurrent'\nimport { getExtension } from '../../utils/mime'\nimport type { AddedSSGDataRequest, SSGParams } from './middleware'\nimport { SSG_CONTEXT, X_HONO_DISABLE_SSG_HEADER_KEY } from './middleware'\nimport { defaultPlugin } from './plugins'\nimport { dirname, filterStaticGenerateRoutes, isDynamicRoute, joinPaths } from './utils'\n\nconst DEFAULT_CONCURRENCY = 2 // default concurrency for ssg\n\n// 'default_content_type' is designed according to Bun's performance optimization,\n//  which omits Content-Type by default for text responses.\n//  This is based on benchmarks showing performance gains without Content-Type.\n//  In Hono, using `c.text()` without a Content-Type implicitly assumes 'text/plain; charset=UTF-8'.\n//  This approach maintains performance consistency across different environments.\n//  For details, see GitHub issues: oven-sh/bun#8530 and https://github.com/honojs/hono/issues/2284.\nconst DEFAULT_CONTENT_TYPE = 'text/plain'\n\nexport const DEFAULT_OUTPUT_DIR = './static'\n\n/**\n * @experimental\n * `FileSystemModule` is an experimental feature.\n * The API might be changed.\n */\nexport interface FileSystemModule {\n  writeFile(path: string, data: string | Uint8Array): Promise<void>\n  mkdir(path: string, options: { recursive: boolean }): Promise<void | string>\n}\n\n/**\n * @experimental\n * `ToSSGResult` is an experimental feature.\n * The API might be changed.\n */\nexport interface ToSSGResult {\n  success: boolean\n  files: string[]\n  error?: Error\n}\n\nconst generateFilePath = (\n  routePath: string,\n  outDir: string,\n  mimeType: string,\n  extensionMap?: Record<string, string>\n): string => {\n  const extension = determineExtension(mimeType, extensionMap)\n\n  if (routePath.endsWith(`.${extension}`)) {\n    return joinPaths(outDir, routePath)\n  }\n\n  if (routePath === '/') {\n    return joinPaths(outDir, `index.${extension}`)\n  }\n  if (routePath.endsWith('/')) {\n    return joinPaths(outDir, routePath, `index.${extension}`)\n  }\n  return joinPaths(outDir, `${routePath}.${extension}`)\n}\n\nconst parseResponseContent = async (response: Response): Promise<string | ArrayBuffer> => {\n  const contentType = response.headers.get('Content-Type')\n\n  try {\n    if (contentType?.includes('text') || contentType?.includes('json')) {\n      return await response.text()\n    } else {\n      return await response.arrayBuffer()\n    }\n  } catch (error) {\n    throw new Error(\n      `Error processing response: ${error instanceof Error ? error.message : 'Unknown error'}`\n    )\n  }\n}\n\nexport const defaultExtensionMap: Record<string, string> = {\n  'text/html': 'html',\n  'text/xml': 'xml',\n  'application/xml': 'xml',\n  'application/yaml': 'yaml',\n}\n\nconst determineExtension = (\n  mimeType: string,\n  userExtensionMap?: Record<string, string>\n): string => {\n  const extensionMap = userExtensionMap || defaultExtensionMap\n  if (mimeType in extensionMap) {\n    return extensionMap[mimeType]\n  }\n  return getExtension(mimeType) || 'html'\n}\n\nexport type BeforeRequestHook = (req: Request) => Request | false | Promise<Request | false>\nexport type AfterResponseHook = (res: Response) => Response | false | Promise<Response | false>\nexport type AfterGenerateHook = (\n  result: ToSSGResult,\n  fsModule: FileSystemModule,\n  options?: ToSSGOptions\n) => void | Promise<void>\n\nexport const combineBeforeRequestHooks = (\n  hooks: BeforeRequestHook | BeforeRequestHook[]\n): BeforeRequestHook => {\n  if (!Array.isArray(hooks)) {\n    return hooks\n  }\n  return async (req: Request): Promise<Request | false> => {\n    let currentReq = req\n    for (const hook of hooks) {\n      const result = await hook(currentReq)\n      if (result === false) {\n        return false\n      }\n      if (result instanceof Request) {\n        currentReq = result\n      }\n    }\n    return currentReq\n  }\n}\n\nexport const combineAfterResponseHooks = (\n  hooks: AfterResponseHook | AfterResponseHook[]\n): AfterResponseHook => {\n  if (!Array.isArray(hooks)) {\n    return hooks\n  }\n  return async (res: Response): Promise<Response | false> => {\n    let currentRes = res\n    for (const hook of hooks) {\n      const result = await hook(currentRes)\n      if (result === false) {\n        return false\n      }\n      if (result instanceof Response) {\n        currentRes = result\n      }\n    }\n    return currentRes\n  }\n}\n\nexport const combineAfterGenerateHooks = (\n  hooks: AfterGenerateHook | AfterGenerateHook[],\n  fsModule: FileSystemModule,\n  options?: ToSSGOptions\n): AfterGenerateHook => {\n  if (!Array.isArray(hooks)) {\n    return hooks\n  }\n  return async (result: ToSSGResult): Promise<void> => {\n    for (const hook of hooks) {\n      await hook(result, fsModule, options)\n    }\n  }\n}\n\nexport interface SSGPlugin {\n  beforeRequestHook?: BeforeRequestHook | BeforeRequestHook[]\n  afterResponseHook?: AfterResponseHook | AfterResponseHook[]\n  afterGenerateHook?: AfterGenerateHook | AfterGenerateHook[]\n}\n\nexport interface ToSSGOptions {\n  dir?: string\n  /**\n   * @deprecated Use plugins[].beforeRequestHook instead.\n   */\n  beforeRequestHook?: BeforeRequestHook | BeforeRequestHook[]\n  /**\n   * @deprecated Use plugins[].afterResponseHook instead.\n   */\n  afterResponseHook?: AfterResponseHook | AfterResponseHook[]\n  /**\n   * @deprecated Use plugins[].afterGenerateHook instead.\n   */\n  afterGenerateHook?: AfterGenerateHook | AfterGenerateHook[]\n  concurrency?: number\n  extensionMap?: Record<string, string>\n  plugins?: SSGPlugin[]\n}\n\n/**\n * @experimental\n * `fetchRoutesContent` is an experimental feature.\n * The API might be changed.\n */\nexport const fetchRoutesContent = function* <\n  E extends Env = Env,\n  S extends Schema = {},\n  BasePath extends string = '/',\n>(\n  app: Hono<E, S, BasePath>,\n  beforeRequestHook?: BeforeRequestHook,\n  afterResponseHook?: AfterResponseHook,\n  concurrency?: number\n): Generator<\n  Promise<\n    | Generator<\n        Promise<{ routePath: string; mimeType: string; content: string | ArrayBuffer } | undefined>\n      >\n    | undefined\n  >\n> {\n  const baseURL = 'http://localhost'\n  const pool = createPool({ concurrency })\n\n  for (const route of filterStaticGenerateRoutes(app)) {\n    // GET Route Info\n    const thisRouteBaseURL = new URL(route.path, baseURL).toString()\n\n    let forGetInfoURLRequest = new Request(thisRouteBaseURL) as AddedSSGDataRequest\n\n    // eslint-disable-next-line no-async-promise-executor\n    yield new Promise(async (resolveGetInfo, rejectGetInfo) => {\n      try {\n        if (beforeRequestHook) {\n          const maybeRequest = await beforeRequestHook(forGetInfoURLRequest)\n          if (!maybeRequest) {\n            resolveGetInfo(undefined)\n            return\n          }\n          forGetInfoURLRequest = maybeRequest as unknown as AddedSSGDataRequest\n        }\n\n        await pool.run(() => app.fetch(forGetInfoURLRequest))\n\n        if (!forGetInfoURLRequest.ssgParams) {\n          if (isDynamicRoute(route.path)) {\n            resolveGetInfo(undefined)\n            return\n          }\n          forGetInfoURLRequest.ssgParams = [{}]\n        }\n\n        const requestInit = {\n          method: forGetInfoURLRequest.method,\n          headers: forGetInfoURLRequest.headers,\n        }\n\n        resolveGetInfo(\n          (function* () {\n            for (const param of forGetInfoURLRequest.ssgParams as SSGParams) {\n              // eslint-disable-next-line no-async-promise-executor\n              yield new Promise(async (resolveReq, rejectReq) => {\n                try {\n                  const replacedUrlParam = replaceUrlParam(route.path, param)\n                  let response = await pool.run(() =>\n                    app.request(replacedUrlParam, requestInit, {\n                      [SSG_CONTEXT]: true,\n                    })\n                  )\n                  if (response.headers.get(X_HONO_DISABLE_SSG_HEADER_KEY)) {\n                    resolveReq(undefined)\n                    return\n                  }\n                  if (afterResponseHook) {\n                    const maybeResponse = await afterResponseHook(response)\n                    if (!maybeResponse) {\n                      resolveReq(undefined)\n                      return\n                    }\n                    response = maybeResponse\n                  }\n                  const mimeType =\n                    response.headers.get('Content-Type')?.split(';')[0] || DEFAULT_CONTENT_TYPE\n                  const content = await parseResponseContent(response)\n                  resolveReq({\n                    routePath: replacedUrlParam,\n                    mimeType,\n                    content,\n                  })\n                } catch (error) {\n                  rejectReq(error)\n                }\n              })\n            }\n          })()\n        )\n      } catch (error) {\n        rejectGetInfo(error)\n      }\n    })\n  }\n}\n\n/**\n * @experimental\n * `saveContentToFile` is an experimental feature.\n * The API might be changed.\n */\nconst createdDirs: Set<string> = new Set()\nexport const saveContentToFile = async (\n  data: Promise<{ routePath: string; content: string | ArrayBuffer; mimeType: string } | undefined>,\n  fsModule: FileSystemModule,\n  outDir: string,\n  extensionMap?: Record<string, string>\n): Promise<string | undefined> => {\n  const awaitedData = await data\n  if (!awaitedData) {\n    return\n  }\n  const { routePath, content, mimeType } = awaitedData\n  const filePath = generateFilePath(routePath, outDir, mimeType, extensionMap)\n  const dirPath = dirname(filePath)\n\n  if (!createdDirs.has(dirPath)) {\n    await fsModule.mkdir(dirPath, { recursive: true })\n    createdDirs.add(dirPath)\n  }\n  if (typeof content === 'string') {\n    await fsModule.writeFile(filePath, content)\n  } else if (content instanceof ArrayBuffer) {\n    await fsModule.writeFile(filePath, new Uint8Array(content))\n  }\n  return filePath\n}\n\n/**\n * @experimental\n * `ToSSGInterface` is an experimental feature.\n * The API might be changed.\n */\nexport interface ToSSGInterface {\n  (\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    app: Hono<any, any, any>,\n    fsModule: FileSystemModule,\n    options?: ToSSGOptions\n  ): Promise<ToSSGResult>\n}\n\n/**\n * @experimental\n * `ToSSGAdaptorInterface` is an experimental feature.\n * The API might be changed.\n */\nexport interface ToSSGAdaptorInterface<\n  E extends Env = Env,\n  S extends Schema = {},\n  BasePath extends string = '/',\n> {\n  (app: Hono<E, S, BasePath>, options?: ToSSGOptions): Promise<ToSSGResult>\n}\n\n/**\n * @experimental\n * `toSSG` is an experimental feature.\n * The API might be changed.\n */\nexport const toSSG: ToSSGInterface = async (app, fs, options) => {\n  let result: ToSSGResult | undefined\n  const getInfoPromises: Promise<unknown>[] = []\n  const savePromises: Promise<string | undefined>[] = []\n  const plugins = options?.plugins || [defaultPlugin()]\n  const beforeRequestHooks: BeforeRequestHook[] = []\n  const afterResponseHooks: AfterResponseHook[] = []\n  const afterGenerateHooks: AfterGenerateHook[] = []\n  if (options?.beforeRequestHook) {\n    beforeRequestHooks.push(\n      ...(Array.isArray(options.beforeRequestHook)\n        ? options.beforeRequestHook\n        : [options.beforeRequestHook])\n    )\n  }\n  if (options?.afterResponseHook) {\n    afterResponseHooks.push(\n      ...(Array.isArray(options.afterResponseHook)\n        ? options.afterResponseHook\n        : [options.afterResponseHook])\n    )\n  }\n  if (options?.afterGenerateHook) {\n    afterGenerateHooks.push(\n      ...(Array.isArray(options.afterGenerateHook)\n        ? options.afterGenerateHook\n        : [options.afterGenerateHook])\n    )\n  }\n  for (const plugin of plugins) {\n    if (plugin.beforeRequestHook) {\n      beforeRequestHooks.push(\n        ...(Array.isArray(plugin.beforeRequestHook)\n          ? plugin.beforeRequestHook\n          : [plugin.beforeRequestHook])\n      )\n    }\n    if (plugin.afterResponseHook) {\n      afterResponseHooks.push(\n        ...(Array.isArray(plugin.afterResponseHook)\n          ? plugin.afterResponseHook\n          : [plugin.afterResponseHook])\n      )\n    }\n    if (plugin.afterGenerateHook) {\n      afterGenerateHooks.push(\n        ...(Array.isArray(plugin.afterGenerateHook)\n          ? plugin.afterGenerateHook\n          : [plugin.afterGenerateHook])\n      )\n    }\n  }\n  try {\n    const outputDir = options?.dir ?? DEFAULT_OUTPUT_DIR\n    const concurrency = options?.concurrency ?? DEFAULT_CONCURRENCY\n\n    const combinedBeforeRequestHook = combineBeforeRequestHooks(\n      beforeRequestHooks.length > 0 ? beforeRequestHooks : [(req) => req]\n    )\n    const combinedAfterResponseHook = combineAfterResponseHooks(\n      afterResponseHooks.length > 0 ? afterResponseHooks : [(req) => req]\n    )\n    const getInfoGen = fetchRoutesContent(\n      app,\n      combinedBeforeRequestHook,\n      combinedAfterResponseHook,\n      concurrency\n    )\n    for (const getInfo of getInfoGen) {\n      getInfoPromises.push(\n        getInfo.then((getContentGen) => {\n          if (!getContentGen) {\n            return\n          }\n          for (const content of getContentGen) {\n            savePromises.push(\n              saveContentToFile(content, fs, outputDir, options?.extensionMap).catch((e) => e)\n            )\n          }\n        })\n      )\n    }\n    await Promise.all(getInfoPromises)\n    const files: string[] = []\n    for (const savePromise of savePromises) {\n      const fileOrError = await savePromise\n      if (typeof fileOrError === 'string') {\n        files.push(fileOrError)\n      } else if (fileOrError) {\n        throw fileOrError\n      }\n    }\n    result = { success: true, files }\n  } catch (error) {\n    const errorObj = error instanceof Error ? error : new Error(String(error))\n    result = { success: false, files: [], error: errorObj }\n  }\n  if (afterGenerateHooks.length > 0) {\n    const combinedAfterGenerateHooks = combineAfterGenerateHooks(afterGenerateHooks, fs, options)\n    await combinedAfterGenerateHooks(result, fs, options)\n  }\n  return result\n}\n"
  },
  {
    "path": "src/helper/ssg/utils.test.ts",
    "content": "import { describe, expect, it } from 'vitest'\nimport { dirname, joinPaths } from './utils'\n\ndescribe('joinPath', () => {\n  it('Should joined path is valid.', () => {\n    expect(joinPaths('test')).toBe('test') //single\n    expect(joinPaths('.test')).toBe('.test') //single with dot\n    expect(joinPaths('/.test')).toBe('/.test') //single with dot with root\n    expect(joinPaths('test', 'test2')).toBe('test/test2') // single and single\n    expect(joinPaths('test', 'test2', '../test3')).toBe('test/test3') // single and single and single with parent\n    expect(joinPaths('.', '../')).toBe('..') // dot and parent\n    expect(joinPaths('test/', 'test2/')).toBe('test/test2') // trailing slashes\n    expect(joinPaths('./test', './test2')).toBe('test/test2') // dot and slash\n    expect(joinPaths('', 'test')).toBe('test') // empty path\n    expect(joinPaths('/test', '/test2')).toBe('/test/test2') // root path\n    expect(joinPaths('../', 'test')).toBe('../test') // parent and single\n    expect(joinPaths('test', '..', 'test2')).toBe('test2') // single triple dot and single\n    expect(joinPaths('test', '...', 'test2')).toBe('test/.../test2') // single triple dot and single\n    expect(joinPaths('test', './test2', '.test3.')).toBe('test/test2/.test3.') // single and single with slash and single with dot\n    expect(joinPaths('test', '../', '.test2')).toBe('.test2') // single and parent and single with dot\n    expect(joinPaths('..', '..', 'test')).toBe('../../test') // parent and parent and single\n    expect(joinPaths('..', '..')).toBe('../..') // parent and parent\n    expect(joinPaths('.test../test2/../')).toBe('.test..') //shuffle\n    expect(joinPaths('.test./.test2/../')).toBe('.test.') //shuffle2\n  })\n  it('Should windows path is valid.', () => {\n    expect(joinPaths('a\\\\b\\\\c', 'd\\\\e')).toBe('a/b/c/d/e')\n  })\n})\ndescribe('dirname', () => {\n  it('Should dirname is valid.', () => {\n    expect(dirname('parent/child')).toBe('parent')\n    expect(dirname('windows\\\\test.txt')).toBe('windows')\n  })\n})\n"
  },
  {
    "path": "src/helper/ssg/utils.ts",
    "content": "import type { Hono } from '../../hono'\nimport { METHOD_NAME_ALL } from '../../router'\nimport type { Env, RouterRoute } from '../../types'\nimport { findTargetHandler, isMiddleware } from '../../utils/handler'\n\n/**\n * Get dirname\n * @param path File Path\n * @returns Parent dir path\n */\nexport const dirname = (path: string): string => {\n  const separatedPath = path.split(/[\\/\\\\]/)\n  return separatedPath.slice(0, -1).join('/') // Windows supports slash path\n}\n\nconst normalizePath = (path: string): string => {\n  return path.replace(/(\\\\)/g, '/').replace(/\\/$/g, '')\n}\n\nconst handleParent = (resultPaths: string[], beforeParentFlag: boolean): void => {\n  if (resultPaths.length === 0 || beforeParentFlag) {\n    resultPaths.push('..')\n  } else {\n    resultPaths.pop()\n  }\n}\n\nconst handleNonDot = (path: string, resultPaths: string[]): void => {\n  path = path.replace(/^\\.(?!.)/, '')\n  if (path !== '') {\n    resultPaths.push(path)\n  }\n}\n\nconst handleSegments = (paths: string[], resultPaths: string[]): void => {\n  let beforeParentFlag = false\n  for (const path of paths) {\n    // Handle `..`\n    if (path === '..') {\n      handleParent(resultPaths, beforeParentFlag)\n      beforeParentFlag = true\n    } else {\n      // Handle `.` or `abc`\n      handleNonDot(path, resultPaths)\n      beforeParentFlag = false\n    }\n  }\n}\n\nexport const joinPaths = (...paths: string[]): string => {\n  paths = paths.map(normalizePath)\n  const resultPaths: string[] = []\n  handleSegments(paths.join('/').split('/'), resultPaths)\n  return (paths[0][0] === '/' ? '/' : '') + resultPaths.join('/')\n}\n\ninterface FilterStaticGenerateRouteData {\n  path: string\n}\n\nexport const filterStaticGenerateRoutes = <E extends Env>(\n  hono: Hono<E>\n): FilterStaticGenerateRouteData[] => {\n  return hono.routes.reduce((acc, { method, handler, path }: RouterRoute) => {\n    const targetHandler = findTargetHandler(handler)\n    if (['GET', METHOD_NAME_ALL].includes(method) && !isMiddleware(targetHandler)) {\n      acc.push({ path })\n    }\n    return acc\n  }, [] as FilterStaticGenerateRouteData[])\n}\n\nexport const isDynamicRoute = (path: string): boolean => {\n  return path.split('/').some((segment) => segment.startsWith(':') || segment.includes('*'))\n}\n"
  },
  {
    "path": "src/helper/streaming/index.ts",
    "content": "/**\n * @module\n * Streaming Helper for Hono.\n */\n\nexport { stream } from './stream'\nexport type { SSEMessage } from './sse'\nexport { streamSSE, SSEStreamingApi } from './sse'\nexport { streamText } from './text'\n"
  },
  {
    "path": "src/helper/streaming/sse.test.tsx",
    "content": "/** @jsxImportSource ../../jsx */\nimport { Context } from '../../context'\nimport { ErrorBoundary } from '../../jsx'\nimport { streamSSE } from '.'\n\ndescribe('SSE Streaming helper', () => {\n  const req = new Request('http://localhost/')\n  let c: Context\n  beforeEach(() => {\n    c = new Context(req)\n  })\n\n  it('Check streamSSE Response', async () => {\n    let spy\n    const res = streamSSE(c, async (stream) => {\n      spy = vi.spyOn(stream, 'close').mockImplementation(async () => {})\n\n      let id = 0\n      const maxIterations = 5\n\n      while (id < maxIterations) {\n        const message = `Message\\nIt is ${id}`\n        await stream.writeSSE({ data: message, event: 'time-update', id: String(id++) })\n        await stream.sleep(10)\n      }\n    })\n\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Transfer-Encoding')).toEqual('chunked')\n    expect(res.headers.get('Content-Type')).toEqual('text/event-stream')\n    expect(res.headers.get('Cache-Control')).toEqual('no-cache')\n    expect(res.headers.get('Connection')).toEqual('keep-alive')\n\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const decoder = new TextDecoder()\n    for (let i = 0; i < 5; i++) {\n      const { value } = await reader.read()\n      const decodedValue = decoder.decode(value)\n\n      // Check the structure and content of the SSE message\n      let expectedValue = 'event: time-update\\n'\n      expectedValue += 'data: Message\\n'\n      expectedValue += `data: It is ${i}\\n`\n      expectedValue += `id: ${i}\\n\\n`\n      expect(decodedValue).toBe(expectedValue)\n    }\n    await new Promise((resolve) => setTimeout(resolve, 100))\n    expect(spy).toHaveBeenCalled()\n  })\n\n  it('Check streamSSE Response if aborted by client', async () => {\n    let aborted = false\n    const res = streamSSE(c, async (stream) => {\n      stream.onAbort(() => {\n        aborted = true\n      })\n      for (let i = 0; i < 3; i++) {\n        await stream.writeSSE({\n          data: `Message ${i}`,\n        })\n        await stream.sleep(1)\n      }\n    })\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const { value } = await reader.read()\n    expect(value).toEqual(new TextEncoder().encode('data: Message 0\\n\\n'))\n    reader.cancel()\n    expect(aborted).toBeTruthy()\n  })\n\n  it('Check streamSSE Response if aborted by abort signal', async () => {\n    // Emulate an old version of Bun (version 1.1.0) for this specific test case\n    // @ts-expect-error Bun is not typed\n    global.Bun = {\n      version: '1.1.0',\n    }\n    const ac = new AbortController()\n    const req = new Request('http://localhost/', { signal: ac.signal })\n    const c = new Context(req)\n\n    let aborted = false\n    const res = streamSSE(c, async (stream) => {\n      stream.onAbort(() => {\n        aborted = true\n      })\n      for (let i = 0; i < 3; i++) {\n        await stream.writeSSE({\n          data: `Message ${i}`,\n        })\n        await stream.sleep(1)\n      }\n    })\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const { value } = await reader.read()\n    expect(value).toEqual(new TextEncoder().encode('data: Message 0\\n\\n'))\n    ac.abort()\n    expect(aborted).toBeTruthy()\n  })\n\n  it('Should include retry in the SSE message', async () => {\n    const retryTime = 3000 // 3 seconds\n    const res = streamSSE(c, async (stream) => {\n      await stream.writeSSE({\n        data: 'This is a test message',\n        retry: retryTime,\n      })\n    })\n\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const decoder = new TextDecoder()\n    const { value } = await reader.read()\n    const decodedValue = decoder.decode(value)\n\n    // Check if the retry parameter is included in the SSE message\n    const expectedRetryValue = `retry: ${retryTime}\\n\\n`\n    expect(decodedValue).toContain(expectedRetryValue)\n  })\n\n  it('Check stream Response if error occurred', async () => {\n    const onError = vi.fn()\n    const res = streamSSE(\n      c,\n      async () => {\n        throw new Error('Test error')\n      },\n      onError\n    )\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const decoder = new TextDecoder()\n    const { value } = await reader.read()\n    const decodedValue = decoder.decode(value)\n    expect(decodedValue).toBe('event: error\\ndata: Test error\\n\\n')\n    expect(onError).toBeCalledTimes(1)\n    expect(onError).toBeCalledWith(new Error('Test error'), expect.anything()) // 2nd argument is StreamingApi instance\n  })\n\n  it('Check streamSSE Response via Promise<string>', async () => {\n    const res = streamSSE(c, async (stream) => {\n      await stream.writeSSE({ data: Promise.resolve('Async Message') })\n    })\n\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const decoder = new TextDecoder()\n    const { value } = await reader.read()\n    const decodedValue = decoder.decode(value)\n    expect(decodedValue).toBe('data: Async Message\\n\\n')\n  })\n\n  it('Check streamSSE Response via JSX.Element', async () => {\n    const res = streamSSE(c, async (stream) => {\n      await stream.writeSSE({ data: <div>Hello</div> })\n    })\n\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const decoder = new TextDecoder()\n    const { value } = await reader.read()\n    const decodedValue = decoder.decode(value)\n    expect(decodedValue).toBe('data: <div>Hello</div>\\n\\n')\n  })\n\n  it('Check streamSSE Response via ErrorBoundary in success case', async () => {\n    const AsyncComponent = async () => Promise.resolve(<div>Async Hello</div>)\n    const res = streamSSE(c, async (stream) => {\n      await stream.writeSSE({\n        data: (\n          <ErrorBoundary fallback={<div>Error</div>}>\n            <AsyncComponent />\n          </ErrorBoundary>\n        ),\n      })\n    })\n\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const decoder = new TextDecoder()\n    const { value } = await reader.read()\n    const decodedValue = decoder.decode(value)\n    expect(decodedValue).toBe('data: <div>Async Hello</div>\\n\\n')\n  })\n\n  it('Check streamSSE Response via ErrorBoundary in error case', async () => {\n    const AsyncComponent = async () => Promise.reject()\n    const res = streamSSE(c, async (stream) => {\n      await stream.writeSSE({\n        data: (\n          <ErrorBoundary fallback={<div>Error</div>}>\n            <AsyncComponent />\n          </ErrorBoundary>\n        ),\n      })\n    })\n\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const decoder = new TextDecoder()\n    const { value } = await reader.read()\n    const decodedValue = decoder.decode(value)\n    expect(decodedValue).toBe('data: <div>Error</div>\\n\\n')\n  })\n\n  it('Check streamSSE handles \\\\r (CR) line ending correctly', async () => {\n    const res = streamSSE(c, async (stream) => {\n      await stream.writeSSE({\n        data: 'Line1\\rLine2',\n        event: 'test-cr',\n      })\n    })\n\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const decoder = new TextDecoder()\n    const { value } = await reader.read()\n    const decodedValue = decoder.decode(value)\n\n    expect(decodedValue).toBe('event: test-cr\\ndata: Line1\\ndata: Line2\\n\\n')\n  })\n\n  it('Check streamSSE handles \\\\r\\\\n (CRLF) line ending correctly', async () => {\n    const res = streamSSE(c, async (stream) => {\n      await stream.writeSSE({\n        data: 'Line1\\r\\nLine2',\n        event: 'test-crlf',\n      })\n    })\n\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const decoder = new TextDecoder()\n    const { value } = await reader.read()\n    const decodedValue = decoder.decode(value)\n\n    expect(decodedValue).toBe('event: test-crlf\\ndata: Line1\\ndata: Line2\\n\\n')\n  })\n\n  it('Check streamSSE handles mixed line endings correctly', async () => {\n    const res = streamSSE(c, async (stream) => {\n      await stream.writeSSE({\n        data: 'A\\nB\\rC\\r\\nD',\n        event: 'test-mixed',\n      })\n    })\n\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const decoder = new TextDecoder()\n    const { value } = await reader.read()\n    const decodedValue = decoder.decode(value)\n\n    expect(decodedValue).toBe('event: test-mixed\\ndata: A\\ndata: B\\ndata: C\\ndata: D\\n\\n')\n  })\n\n  it('Should throw error if event contains \\\\n', async () => {\n    const onError = vi.fn()\n    const res = streamSSE(\n      c,\n      async (stream) => {\n        await stream.writeSSE({ data: 'test', event: 'test\\nevent' })\n      },\n      onError\n    )\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const decoder = new TextDecoder()\n    const { value } = await reader.read()\n    const decodedValue = decoder.decode(value)\n    expect(decodedValue).toContain('event: error')\n    expect(onError).toBeCalledTimes(1)\n  })\n\n  it('Should throw error if event contains \\\\r', async () => {\n    const onError = vi.fn()\n    const res = streamSSE(\n      c,\n      async (stream) => {\n        await stream.writeSSE({ data: 'test', event: 'test\\revent' })\n      },\n      onError\n    )\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const decoder = new TextDecoder()\n    const { value } = await reader.read()\n    const decodedValue = decoder.decode(value)\n    expect(decodedValue).toContain('event: error')\n    expect(onError).toBeCalledTimes(1)\n  })\n\n  it('Should throw error if id contains \\\\n', async () => {\n    const onError = vi.fn()\n    const res = streamSSE(\n      c,\n      async (stream) => {\n        await stream.writeSSE({ data: 'test', id: 'test\\nid' })\n      },\n      onError\n    )\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const decoder = new TextDecoder()\n    const { value } = await reader.read()\n    const decodedValue = decoder.decode(value)\n    expect(decodedValue).toContain('event: error')\n    expect(onError).toBeCalledTimes(1)\n  })\n\n  it('Should throw error if id contains \\\\r', async () => {\n    const onError = vi.fn()\n    const res = streamSSE(\n      c,\n      async (stream) => {\n        await stream.writeSSE({ data: 'test', id: 'test\\rid' })\n      },\n      onError\n    )\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const decoder = new TextDecoder()\n    const { value } = await reader.read()\n    const decodedValue = decoder.decode(value)\n    expect(decodedValue).toContain('event: error')\n    expect(onError).toBeCalledTimes(1)\n  })\n\n  it('Check streamSSE handles consecutive \\\\r correctly', async () => {\n    const res = streamSSE(c, async (stream) => {\n      await stream.writeSSE({\n        data: 'Left\\r\\rRight',\n        event: 'test-double-cr',\n      })\n    })\n\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const decoder = new TextDecoder()\n    const { value } = await reader.read()\n    const decodedValue = decoder.decode(value)\n\n    // Two \\r should produce an empty line in between\n    expect(decodedValue).toBe('event: test-double-cr\\ndata: Left\\ndata: \\ndata: Right\\n\\n')\n  })\n})\n"
  },
  {
    "path": "src/helper/streaming/sse.ts",
    "content": "import type { Context } from '../../context'\nimport { HtmlEscapedCallbackPhase, resolveCallback } from '../../utils/html'\nimport { StreamingApi } from '../../utils/stream'\nimport { isOldBunVersion } from './utils'\n\nexport interface SSEMessage {\n  data: string | Promise<string>\n  event?: string\n  id?: string\n  retry?: number\n}\n\nexport class SSEStreamingApi extends StreamingApi {\n  constructor(writable: WritableStream, readable: ReadableStream) {\n    super(writable, readable)\n  }\n\n  async writeSSE(message: SSEMessage) {\n    const data = await resolveCallback(message.data, HtmlEscapedCallbackPhase.Stringify, false, {})\n    const dataLines = (data as string)\n      .split(/\\r\\n|\\r|\\n/)\n      .map((line) => {\n        return `data: ${line}`\n      })\n      .join('\\n')\n\n    for (const key of ['event', 'id', 'retry'] as (keyof SSEMessage)[]) {\n      if (message[key] && /[\\r\\n]/.test(message[key] as string)) {\n        throw new Error(`${key} must not contain \"\\\\r\" or \"\\\\n\"`)\n      }\n    }\n\n    const sseData =\n      [\n        message.event && `event: ${message.event}`,\n        dataLines,\n        message.id && `id: ${message.id}`,\n        message.retry && `retry: ${message.retry}`,\n      ]\n        .filter(Boolean)\n        .join('\\n') + '\\n\\n'\n\n    await this.write(sseData)\n  }\n}\n\nconst run = async (\n  stream: SSEStreamingApi,\n  cb: (stream: SSEStreamingApi) => Promise<void>,\n  onError?: (e: Error, stream: SSEStreamingApi) => Promise<void>\n): Promise<void> => {\n  try {\n    await cb(stream)\n  } catch (e) {\n    if (e instanceof Error && onError) {\n      await onError(e, stream)\n\n      await stream.writeSSE({\n        event: 'error',\n        data: e.message,\n      })\n    } else {\n      console.error(e)\n    }\n  } finally {\n    stream.close()\n  }\n}\n\nconst contextStash: WeakMap<ReadableStream, Context> = new WeakMap<ReadableStream, Context>()\n\nexport const streamSSE = (\n  c: Context,\n  cb: (stream: SSEStreamingApi) => Promise<void>,\n  onError?: (e: Error, stream: SSEStreamingApi) => Promise<void>\n): Response => {\n  const { readable, writable } = new TransformStream()\n  const stream = new SSEStreamingApi(writable, readable)\n\n  // Until Bun v1.1.27, Bun didn't call cancel() on the ReadableStream for Response objects from Bun.serve()\n  if (isOldBunVersion()) {\n    c.req.raw.signal.addEventListener('abort', () => {\n      if (!stream.closed) {\n        stream.abort()\n      }\n    })\n  }\n\n  // in bun, `c` is destroyed when the request is returned, so hold it until the end of streaming\n  contextStash.set(stream.responseReadable, c)\n\n  c.header('Transfer-Encoding', 'chunked')\n  c.header('Content-Type', 'text/event-stream')\n  c.header('Cache-Control', 'no-cache')\n  c.header('Connection', 'keep-alive')\n\n  run(stream, cb, onError)\n\n  return c.newResponse(stream.responseReadable)\n}\n"
  },
  {
    "path": "src/helper/streaming/stream.test.ts",
    "content": "import { Context } from '../../context'\nimport { stream } from '.'\n\ndescribe('Basic Streaming Helper', () => {\n  const req = new Request('http://localhost/')\n  let c: Context\n  beforeEach(() => {\n    c = new Context(req)\n  })\n\n  it('Check stream Response', async () => {\n    const res = stream(c, async (stream) => {\n      for (let i = 0; i < 3; i++) {\n        await stream.write(new Uint8Array([i]))\n        await stream.sleep(1)\n      }\n    })\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    for (let i = 0; i < 3; i++) {\n      const { value } = await reader.read()\n      expect(value).toEqual(new Uint8Array([i]))\n    }\n  })\n\n  it('Check stream Response if aborted by client', async () => {\n    let aborted = false\n    const res = stream(c, async (stream) => {\n      stream.onAbort(() => {\n        aborted = true\n      })\n      for (let i = 0; i < 3; i++) {\n        await stream.write(new Uint8Array([i]))\n        await stream.sleep(1)\n      }\n    })\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const { value } = await reader.read()\n    expect(value).toEqual(new Uint8Array([0]))\n    reader.cancel()\n    expect(aborted).toBeTruthy()\n  })\n\n  it('Check stream Response if aborted by abort signal', async () => {\n    // Emulate an old version of Bun (version 1.1.0) for this specific test case\n    // @ts-expect-error Bun is not typed\n    global.Bun = {\n      version: '1.1.0',\n    }\n    const ac = new AbortController()\n    const req = new Request('http://localhost/', { signal: ac.signal })\n    const c = new Context(req)\n\n    let aborted = false\n    const res = stream(c, async (stream) => {\n      stream.onAbort(() => {\n        aborted = true\n      })\n      for (let i = 0; i < 3; i++) {\n        await stream.write(new Uint8Array([i]))\n        await stream.sleep(1)\n      }\n    })\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const { value } = await reader.read()\n    expect(value).toEqual(new Uint8Array([0]))\n    ac.abort()\n    expect(aborted).toBeTruthy()\n    // @ts-expect-error Bun is not typed\n    delete global.Bun\n  })\n\n  it('Check stream Response if pipe is aborted by abort signal', async () => {\n    // Emulate an old version of Bun (version 1.1.0) for this specific test case\n    // @ts-expect-error Bun is not typed\n    global.Bun = {\n      version: '1.1.0',\n    }\n    const ac = new AbortController()\n    const req = new Request('http://localhost/', { signal: ac.signal })\n    const c = new Context(req)\n\n    let aborted = false\n    const res = stream(c, async (stream) => {\n      stream.onAbort(() => {\n        aborted = true\n      })\n      await stream.pipe(new ReadableStream())\n    })\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const pReading = reader.read()\n    ac.abort()\n    await pReading\n    expect(aborted).toBeTruthy()\n    // @ts-expect-error Bun is not typed\n    delete global.Bun\n  })\n\n  it('Check stream Response if error occurred', async () => {\n    const onError = vi.fn()\n    const res = stream(\n      c,\n      async () => {\n        throw new Error('error')\n      },\n      onError\n    )\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const { value } = await reader.read()\n    expect(value).toBeUndefined()\n    expect(onError).toBeCalledTimes(1)\n    expect(onError).toBeCalledWith(new Error('error'), expect.anything()) // 2nd argument is StreamingApi instance\n  })\n})\n"
  },
  {
    "path": "src/helper/streaming/stream.ts",
    "content": "import type { Context } from '../../context'\nimport { StreamingApi } from '../../utils/stream'\nimport { isOldBunVersion } from './utils'\n\nconst contextStash: WeakMap<ReadableStream, Context> = new WeakMap<ReadableStream, Context>()\n\nexport const stream = (\n  c: Context,\n  cb: (stream: StreamingApi) => Promise<void>,\n  onError?: (e: Error, stream: StreamingApi) => Promise<void>\n): Response => {\n  const { readable, writable } = new TransformStream()\n  const stream = new StreamingApi(writable, readable)\n\n  // Until Bun v1.1.27, Bun didn't call cancel() on the ReadableStream for Response objects from Bun.serve()\n  if (isOldBunVersion()) {\n    c.req.raw.signal.addEventListener('abort', () => {\n      if (!stream.closed) {\n        stream.abort()\n      }\n    })\n  }\n\n  // in bun, `c` is destroyed when the request is returned, so hold it until the end of streaming\n  contextStash.set(stream.responseReadable, c)\n  ;(async () => {\n    try {\n      await cb(stream)\n    } catch (e) {\n      if (e === undefined) {\n        // If reading is canceled without a reason value (e.g. by StreamingApi)\n        // then the .pipeTo() promise will reject with undefined.\n        // In this case, do nothing because the stream is already closed.\n      } else if (e instanceof Error && onError) {\n        await onError(e, stream)\n      } else {\n        console.error(e)\n      }\n    } finally {\n      stream.close()\n    }\n  })()\n\n  return c.newResponse(stream.responseReadable)\n}\n"
  },
  {
    "path": "src/helper/streaming/text.test.ts",
    "content": "import { Context } from '../../context'\nimport { streamText } from '.'\n\ndescribe('Text Streaming Helper', () => {\n  const req = new Request('http://localhost/')\n  let c: Context\n  beforeEach(() => {\n    c = new Context(req)\n  })\n\n  it('Check streamText Response', async () => {\n    const res = streamText(c, async (stream) => {\n      for (let i = 0; i < 3; i++) {\n        await stream.write(`${i}`)\n        await stream.sleep(1)\n      }\n    })\n\n    expect(res.status).toBe(200)\n    expect(res.headers.get('content-type')).toMatch(/^text\\/plain/)\n    expect(res.headers.get('x-content-type-options')).toBe('nosniff')\n    expect(res.headers.get('transfer-encoding')).toBe('chunked')\n\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n    const reader = res.body.getReader()\n    const decoder = new TextDecoder()\n    for (let i = 0; i < 3; i++) {\n      const { value } = await reader.read()\n      expect(decoder.decode(value)).toEqual(`${i}`)\n    }\n  })\n})\n"
  },
  {
    "path": "src/helper/streaming/text.ts",
    "content": "import type { Context } from '../../context'\nimport { TEXT_PLAIN } from '../../context'\nimport type { StreamingApi } from '../../utils/stream'\nimport { stream } from './'\n\nexport const streamText = (\n  c: Context,\n  cb: (stream: StreamingApi) => Promise<void>,\n  onError?: (e: Error, stream: StreamingApi) => Promise<void>\n): Response => {\n  c.header('Content-Type', TEXT_PLAIN)\n  c.header('X-Content-Type-Options', 'nosniff')\n  c.header('Transfer-Encoding', 'chunked')\n  return stream(c, cb, onError)\n}\n"
  },
  {
    "path": "src/helper/streaming/utils.ts",
    "content": "export let isOldBunVersion = (): boolean => {\n  // @ts-expect-error @types/bun is not installed\n  const version: string = typeof Bun !== 'undefined' ? Bun.version : undefined\n  if (version === undefined) {\n    return false\n  }\n  const result = version.startsWith('1.1') || version.startsWith('1.0') || version.startsWith('0.')\n  // Avoid running this check on every call\n  isOldBunVersion = () => result\n  return result\n}\n"
  },
  {
    "path": "src/helper/testing/index.test.ts",
    "content": "import { Hono } from '../../hono'\nimport { testClient } from '.'\n\ndescribe('hono testClient', () => {\n  it('Should return the correct search result', async () => {\n    const app = new Hono().get('/search', (c) => c.json({ hello: 'world' }))\n    const res = await testClient(app).search.$get()\n    expect(await res.json()).toEqual({ hello: 'world' })\n  })\n\n  it('Should return the correct environment variables value', async () => {\n    type Bindings = { hello: string }\n    const app = new Hono<{ Bindings: Bindings }>().get('/search', (c) => {\n      return c.json({ hello: c.env.hello })\n    })\n    const res = await testClient(app, { hello: 'world' }).search.$get()\n    expect(await res.json()).toEqual({ hello: 'world' })\n  })\n\n  it('Should use the passed in headers', async () => {\n    const app = new Hono().get('/search', (c) => {\n      return c.json({ query: c.req.header('x-query') })\n    })\n    const res = await testClient(app, undefined, undefined, {\n      headers: { 'x-query': 'abc' },\n    }).search.$get()\n    expect(await res.json()).toEqual({ query: 'abc' })\n  })\n\n  it('Should return a correct URL with out throwing an error', async () => {\n    const app = new Hono().get('/abc', (c) => c.json(0))\n    const url = testClient(app).abc.$url()\n    expect(url.pathname).toBe('/abc')\n  })\n\n  it('Should not throw an error with $ws()', async () => {\n    vi.stubGlobal('WebSocket', class {})\n    const app = new Hono().get('/ws', (c) => c.text('Fake response of a WebSocket'))\n    // @ts-expect-error $ws is not typed correctly\n    expect(() => testClient(app).ws.$ws()).not.toThrowError()\n  })\n})\n"
  },
  {
    "path": "src/helper/testing/index.ts",
    "content": "/**\n * @module\n * Testing Helper for Hono.\n */\n\nimport { hc } from '../../client'\nimport type { Client, ClientRequestOptions } from '../../client/types'\nimport type { ExecutionContext } from '../../context'\nimport type { Hono } from '../../hono'\nimport type { Schema } from '../../types'\nimport type { UnionToIntersection } from '../../utils/types'\n\ntype ExtractEnv<T> = T extends Hono<infer E, Schema, string> ? E : never\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const testClient = <T extends Hono<any, Schema, string>>(\n  app: T,\n  Env?: ExtractEnv<T>['Bindings'] | {},\n  executionCtx?: ExecutionContext,\n  options?: Omit<ClientRequestOptions, 'fetch'>\n): UnionToIntersection<Client<T, 'http://localhost'>> => {\n  const customFetch = (input: RequestInfo | URL, init?: RequestInit) => {\n    return app.request(input, init, Env, executionCtx)\n  }\n\n  return hc<typeof app, 'http://localhost'>('http://localhost', { ...options, fetch: customFetch })\n}\n"
  },
  {
    "path": "src/helper/websocket/index.test.ts",
    "content": "import { Context } from '../../context'\nimport type { WSContextInit } from '.'\nimport { WSContext, createWSMessageEvent, defineWebSocketHelper } from '.'\n\ndescribe('`createWSMessageEvent`', () => {\n  it('Should `createWSMessageEvent` is working for string', () => {\n    const randomString = Math.random().toString()\n    const event = createWSMessageEvent(randomString)\n\n    expect(event.data).toBe(randomString)\n  })\n  it('Should `createWSMessageEvent` type is `message`', () => {\n    const event = createWSMessageEvent('')\n    expect(event.type).toBe('message')\n  })\n})\ndescribe('defineWebSocketHelper', () => {\n  it('defineWebSocketHelper should work', async () => {\n    const upgradeWebSocket = defineWebSocketHelper(() => {\n      return new Response('Hello World', {\n        status: 200,\n      })\n    })\n    const response = await upgradeWebSocket(() => ({}))(\n      new Context(new Request('http://localhost')),\n      () => Promise.resolve()\n    )\n    expect(response).toBeTruthy()\n    expect((response as Response).status).toBe(200)\n  })\n  it('When response is undefined, should call next()', async () => {\n    const upgradeWebSocket = defineWebSocketHelper(() => {\n      return\n    })\n    const next = vi.fn()\n    await upgradeWebSocket(() => ({}))(new Context(new Request('http://localhost')), next)\n    expect(next).toBeCalled()\n  })\n  it('Use upgradeWebSocket in return', async () => {\n    const upgradeWebSocket = defineWebSocketHelper(() => {\n      return new Response('Hello World', {\n        status: 200,\n      })\n    })\n    const c = new Context(new Request('http://localhost'))\n    const res = await upgradeWebSocket(c, {})\n    expect(res.status).toBe(200)\n  })\n  it('When upgrading failed and use it in handler, it should throw error', async () => {\n    const upgradeWebSocket = defineWebSocketHelper(() => {\n      return\n    })\n    const c = new Context(new Request('http://localhost'))\n    expect(() => upgradeWebSocket(c, {})).rejects.toThrow()\n  })\n})\ndescribe('WSContext', () => {\n  it('Should close() works', async () => {\n    type Result = [number | undefined, string | undefined]\n    let ws!: WSContext\n    const promise = new Promise<Result>((resolve) => {\n      ws = new WSContext({\n        close(code, reason) {\n          resolve([code, reason])\n        },\n      } as WSContextInit)\n    })\n    ws.close(0, 'reason')\n    const [code, reason] = await promise\n    expect(code).toBe(0)\n    expect(reason).toBe('reason')\n  })\n  it('Should send() works', async () => {\n    let ws!: WSContext\n    const promise = new Promise<string | ArrayBuffer | Uint8Array<ArrayBuffer>>((resolve) => {\n      ws = new WSContext({\n        send(data: string | ArrayBuffer | Uint8Array<ArrayBuffer>, _options) {\n          resolve(data)\n        },\n      } as WSContextInit)\n    })\n    ws.send('Hello')\n    expect(await promise).toBe('Hello')\n  })\n  it('Should readyState works', () => {\n    const ws = new WSContext({\n      readyState: 0,\n    } as WSContextInit)\n    expect(ws.readyState).toBe(0)\n  })\n  it('Should normalize URL', () => {\n    const stringURLWS = new WSContext({\n      url: 'http://localhost',\n    } as WSContextInit)\n    expect(stringURLWS.url).toBeInstanceOf(URL)\n\n    const urlURLWS = new WSContext({\n      url: new URL('http://localhost'),\n    } as WSContextInit)\n    expect(urlURLWS.url).toBeInstanceOf(URL)\n\n    const nullURLWS = new WSContext({\n      url: undefined,\n    } as WSContextInit)\n    expect(nullURLWS.url).toBeNull()\n  })\n  it('Should normalize message in send()', () => {\n    let data: string | ArrayBuffer | Uint8Array | null = null\n    const wsContext = new WSContext({\n      send(received, _options) {\n        data = received\n      },\n    } as WSContextInit)\n\n    wsContext.send('string')\n    expect(data).toBe('string')\n\n    wsContext.send(new ArrayBuffer(16))\n    expect(data).toBeInstanceOf(ArrayBuffer)\n\n    wsContext.send(new Uint8Array(16))\n    expect(data).toBeInstanceOf(Uint8Array)\n  })\n})\n"
  },
  {
    "path": "src/helper/websocket/index.ts",
    "content": "/**\n * @module\n * WebSocket Helper for Hono.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { Context } from '../../context'\nimport type { MiddlewareHandler, TypedResponse } from '../../types'\nimport type { StatusCode } from '../../utils/http-status'\n\n/**\n * WebSocket Event Listeners type\n */\nexport interface WSEvents<T = unknown> {\n  onOpen?: (evt: Event, ws: WSContext<T>) => void\n  onMessage?: (evt: MessageEvent<WSMessageReceive>, ws: WSContext<T>) => void\n  onClose?: (evt: CloseEvent, ws: WSContext<T>) => void\n  onError?: (evt: Event, ws: WSContext<T>) => void\n}\n\n/**\n * Upgrade WebSocket Type\n */\nexport interface UpgradeWebSocket<T = unknown, U = any, _WSEvents = WSEvents<T>> {\n  (\n    createEvents: (c: Context) => _WSEvents | Promise<_WSEvents>,\n    options?: U\n  ): MiddlewareHandler<\n    any,\n    string,\n    {\n      outputFormat: 'ws'\n    }\n  >\n  (\n    c: Context,\n    events: _WSEvents,\n    options?: U\n  ): Promise<Response & TypedResponse<{}, StatusCode, 'ws'>>\n}\n\n/**\n * ReadyState for WebSocket\n */\nexport type WSReadyState = 0 | 1 | 2 | 3\n\n/**\n * An argument for WSContext class\n */\nexport interface WSContextInit<T = unknown> {\n  send(data: string | ArrayBuffer | Uint8Array, options: SendOptions): void\n  close(code?: number, reason?: string): void\n\n  raw?: T\n  readyState: WSReadyState\n  url?: string | URL | null\n  protocol?: string | null\n}\n\n/**\n * Options for sending message\n */\nexport interface SendOptions {\n  compress?: boolean\n}\n\n/**\n * A context for controlling WebSockets\n */\nexport class WSContext<T = unknown> {\n  #init: WSContextInit<T>\n  constructor(init: WSContextInit<T>) {\n    this.#init = init\n    this.raw = init.raw\n    this.url = init.url ? new URL(init.url) : null\n    this.protocol = init.protocol ?? null\n  }\n  send(source: string | ArrayBuffer | Uint8Array<ArrayBuffer>, options?: SendOptions): void {\n    this.#init.send(source, options ?? {})\n  }\n  raw?: T\n  binaryType: BinaryType = 'arraybuffer'\n  get readyState(): WSReadyState {\n    return this.#init.readyState\n  }\n  url: URL | null\n  protocol: string | null\n  close(code?: number, reason?: string) {\n    this.#init.close(code, reason)\n  }\n}\n\nexport type WSMessageReceive = string | Blob | ArrayBufferLike\n\nexport const createWSMessageEvent = (source: WSMessageReceive): MessageEvent<WSMessageReceive> => {\n  return new MessageEvent<WSMessageReceive>('message', {\n    data: source,\n  })\n}\n\nexport interface WebSocketHelperDefineContext {}\nexport type WebSocketHelperDefineHandler<T, U> = (\n  c: Context,\n  events: WSEvents<T>,\n  options?: U\n) => Promise<Response | void> | Response | void\n\n/**\n * Create a WebSocket adapter/helper\n */\nexport const defineWebSocketHelper = <T = unknown, U = any>(\n  handler: WebSocketHelperDefineHandler<T, U>\n): UpgradeWebSocket<T, U> => {\n  return ((\n    ...args:\n      | [createEvents: (c: Context) => WSEvents<T> | Promise<WSEvents<T>>, options?: U]\n      | [c: Context, events: WSEvents<T>, options?: U]\n  ) => {\n    if (typeof args[0] === 'function') {\n      const [createEvents, options] = args\n      return async function upgradeWebSocket(c, next) {\n        const events = await createEvents(c)\n        const result = await handler(c, events, options as U)\n        if (result) {\n          return result\n        }\n        await next()\n      }\n    } else {\n      const [c, events, options] = args as [c: Context, events: WSEvents<T>, options?: U]\n      return (async () => {\n        const upgraded = await handler(c, events, options as U)\n        if (!upgraded) {\n          throw new Error('Failed to upgrade WebSocket')\n        }\n        return upgraded\n      })()\n    }\n  }) as UpgradeWebSocket<T, U>\n}\n"
  },
  {
    "path": "src/hono-base.ts",
    "content": "/**\n * @module\n * This module is the base module for the Hono object.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { compose } from './compose'\nimport { Context } from './context'\nimport type { ExecutionContext } from './context'\nimport type { Router } from './router'\nimport { METHODS, METHOD_NAME_ALL, METHOD_NAME_ALL_LOWERCASE } from './router'\nimport type {\n  Env,\n  ErrorHandler,\n  FetchEventLike,\n  H,\n  HandlerInterface,\n  MergePath,\n  MergeSchemaPath,\n  MiddlewareHandler,\n  MiddlewareHandlerInterface,\n  Next,\n  NotFoundHandler,\n  OnHandlerInterface,\n  RouterRoute,\n  Schema,\n} from './types'\nimport { COMPOSED_HANDLER } from './utils/constants'\nimport { getPath, getPathNoStrict, mergePath } from './utils/url'\n\nconst notFoundHandler: NotFoundHandler = (c) => {\n  return c.text('404 Not Found', 404)\n}\n\nconst errorHandler: ErrorHandler = (err, c) => {\n  if ('getResponse' in err) {\n    const res = err.getResponse()\n    return c.newResponse(res.body, res)\n  }\n  console.error(err)\n  return c.text('Internal Server Error', 500)\n}\n\ntype GetPath<E extends Env> = (request: Request, options?: { env?: E['Bindings'] }) => string\n\nexport type HonoOptions<E extends Env> = {\n  /**\n   * `strict` option specifies whether to distinguish whether the last path is a directory or not.\n   *\n   * @see {@link https://hono.dev/docs/api/hono#strict-mode}\n   *\n   * @default true\n   */\n  strict?: boolean\n  /**\n   * `router` option specifies which router to use.\n   *\n   * @see {@link https://hono.dev/docs/api/hono#router-option}\n   *\n   * @example\n   * ```ts\n   * const app = new Hono({ router: new RegExpRouter() })\n   * ```\n   */\n  router?: Router<[H, RouterRoute]>\n  /**\n   * `getPath` can handle the host header value.\n   *\n   * @see {@link https://hono.dev/docs/api/routing#routing-with-host-header-value}\n   *\n   * @example\n   * ```ts\n   * const app = new Hono({\n   *  getPath: (req) =>\n   *   '/' + req.headers.get('host') + req.url.replace(/^https?:\\/\\/[^/]+(\\/[^?]*)/, '$1'),\n   * })\n   *\n   * app.get('/www1.example.com/hello', () => c.text('hello www1'))\n   *\n   * // A following request will match the route:\n   * // new Request('http://www1.example.com/hello', {\n   * //  headers: { host: 'www1.example.com' },\n   * // })\n   * ```\n   */\n  getPath?: GetPath<E>\n}\n\ntype MountOptionHandler = (c: Context) => unknown\ntype MountReplaceRequest = (originalRequest: Request) => Request\ntype MountOptions =\n  | MountOptionHandler\n  | {\n      optionHandler?: MountOptionHandler\n      replaceRequest?: MountReplaceRequest | false\n    }\n\nclass Hono<\n  E extends Env = Env,\n  S extends Schema = {},\n  BasePath extends string = '/',\n  CurrentPath extends string = BasePath,\n> {\n  get!: HandlerInterface<E, 'get', S, BasePath, CurrentPath>\n  post!: HandlerInterface<E, 'post', S, BasePath, CurrentPath>\n  put!: HandlerInterface<E, 'put', S, BasePath, CurrentPath>\n  delete!: HandlerInterface<E, 'delete', S, BasePath, CurrentPath>\n  options!: HandlerInterface<E, 'options', S, BasePath, CurrentPath>\n  patch!: HandlerInterface<E, 'patch', S, BasePath, CurrentPath>\n  all!: HandlerInterface<E, 'all', S, BasePath, CurrentPath>\n  on: OnHandlerInterface<E, S, BasePath>\n  use: MiddlewareHandlerInterface<E, S, BasePath>\n\n  /*\n    This class is like an abstract class and does not have a router.\n    To use it, inherit the class and implement router in the constructor.\n  */\n  router!: Router<[H, RouterRoute]>\n  readonly getPath: GetPath<E>\n  // Cannot use `#` because it requires visibility at JavaScript runtime.\n  private _basePath: string = '/'\n  #path: string = '/'\n\n  routes: RouterRoute[] = []\n\n  constructor(options: HonoOptions<E> = {}) {\n    // Implementation of app.get(...handlers[]) or app.get(path, ...handlers[])\n    const allMethods = [...METHODS, METHOD_NAME_ALL_LOWERCASE]\n    allMethods.forEach((method) => {\n      this[method] = (args1: string | H, ...args: H[]) => {\n        if (typeof args1 === 'string') {\n          this.#path = args1\n        } else {\n          this.#addRoute(method, this.#path, args1)\n        }\n        args.forEach((handler) => {\n          this.#addRoute(method, this.#path, handler)\n        })\n        return this as any\n      }\n    })\n\n    // Implementation of app.on(method, path, ...handlers[])\n    this.on = (method: string | string[], path: string | string[], ...handlers: H[]) => {\n      for (const p of [path].flat()) {\n        this.#path = p\n        for (const m of [method].flat()) {\n          handlers.map((handler) => {\n            this.#addRoute(m.toUpperCase(), this.#path, handler)\n          })\n        }\n      }\n      return this as any\n    }\n\n    // Implementation of app.use(...handlers[]) or app.use(path, ...handlers[])\n    this.use = (arg1: string | MiddlewareHandler<any>, ...handlers: MiddlewareHandler<any>[]) => {\n      if (typeof arg1 === 'string') {\n        this.#path = arg1\n      } else {\n        this.#path = '*'\n        handlers.unshift(arg1)\n      }\n      handlers.forEach((handler) => {\n        this.#addRoute(METHOD_NAME_ALL, this.#path, handler)\n      })\n      return this as any\n    }\n\n    const { strict, ...optionsWithoutStrict } = options\n    Object.assign(this, optionsWithoutStrict)\n    this.getPath = (strict ?? true) ? (options.getPath ?? getPath) : getPathNoStrict\n  }\n\n  #clone(): Hono<E, S, BasePath, CurrentPath> {\n    const clone = new Hono<E, S, BasePath, CurrentPath>({\n      router: this.router,\n      getPath: this.getPath,\n    })\n    clone.errorHandler = this.errorHandler\n    clone.#notFoundHandler = this.#notFoundHandler\n    clone.routes = this.routes\n    return clone\n  }\n\n  #notFoundHandler: NotFoundHandler = notFoundHandler\n  // Cannot use `#` because it requires visibility at JavaScript runtime.\n  private errorHandler: ErrorHandler = errorHandler\n\n  /**\n   * `.route()` allows grouping other Hono instance in routes.\n   *\n   * @see {@link https://hono.dev/docs/api/routing#grouping}\n   *\n   * @param {string} path - base Path\n   * @param {Hono} app - other Hono instance\n   * @returns {Hono} routed Hono instance\n   *\n   * @example\n   * ```ts\n   * const app = new Hono()\n   * const app2 = new Hono()\n   *\n   * app2.get(\"/user\", (c) => c.text(\"user\"))\n   * app.route(\"/api\", app2) // GET /api/user\n   * ```\n   */\n  route<\n    SubPath extends string,\n    SubEnv extends Env,\n    SubSchema extends Schema,\n    SubBasePath extends string,\n    SubCurrentPath extends string,\n  >(\n    path: SubPath,\n    app: Hono<SubEnv, SubSchema, SubBasePath, SubCurrentPath>\n  ): Hono<E, MergeSchemaPath<SubSchema, MergePath<BasePath, SubPath>> | S, BasePath, CurrentPath> {\n    const subApp = this.basePath(path)\n    app.routes.map((r) => {\n      let handler\n      if (app.errorHandler === errorHandler) {\n        handler = r.handler\n      } else {\n        handler = async (c: Context, next: Next) =>\n          (await compose([], app.errorHandler)(c, () => r.handler(c, next))).res\n        ;(handler as any)[COMPOSED_HANDLER] = r.handler\n      }\n\n      subApp.#addRoute(r.method, r.path, handler)\n    })\n    return this\n  }\n\n  /**\n   * `.basePath()` allows base paths to be specified.\n   *\n   * @see {@link https://hono.dev/docs/api/routing#base-path}\n   *\n   * @param {string} path - base Path\n   * @returns {Hono} changed Hono instance\n   *\n   * @example\n   * ```ts\n   * const api = new Hono().basePath('/api')\n   * ```\n   */\n  basePath<SubPath extends string>(\n    path: SubPath\n  ): Hono<E, S, MergePath<BasePath, SubPath>, MergePath<BasePath, SubPath>> {\n    const subApp = this.#clone()\n    subApp._basePath = mergePath(this._basePath, path)\n    return subApp\n  }\n\n  /**\n   * `.onError()` handles an error and returns a customized Response.\n   *\n   * @see {@link https://hono.dev/docs/api/hono#error-handling}\n   *\n   * @param {ErrorHandler} handler - request Handler for error\n   * @returns {Hono} changed Hono instance\n   *\n   * @example\n   * ```ts\n   * app.onError((err, c) => {\n   *   console.error(`${err}`)\n   *   return c.text('Custom Error Message', 500)\n   * })\n   * ```\n   */\n  onError = (handler: ErrorHandler<E>): Hono<E, S, BasePath, CurrentPath> => {\n    this.errorHandler = handler\n    return this\n  }\n\n  /**\n   * `.notFound()` allows you to customize a Not Found Response.\n   *\n   * @see {@link https://hono.dev/docs/api/hono#not-found}\n   *\n   * @param {NotFoundHandler} handler - request handler for not-found\n   * @returns {Hono} changed Hono instance\n   *\n   * @example\n   * ```ts\n   * app.notFound((c) => {\n   *   return c.text('Custom 404 Message', 404)\n   * })\n   * ```\n   */\n  notFound = (handler: NotFoundHandler<E>): Hono<E, S, BasePath, CurrentPath> => {\n    this.#notFoundHandler = handler\n    return this\n  }\n\n  /**\n   * `.mount()` allows you to mount applications built with other frameworks into your Hono application.\n   *\n   * @see {@link https://hono.dev/docs/api/hono#mount}\n   *\n   * @param {string} path - base Path\n   * @param {Function} applicationHandler - other Request Handler\n   * @param {MountOptions} [options] - options of `.mount()`\n   * @returns {Hono} mounted Hono instance\n   *\n   * @example\n   * ```ts\n   * import { Router as IttyRouter } from 'itty-router'\n   * import { Hono } from 'hono'\n   * // Create itty-router application\n   * const ittyRouter = IttyRouter()\n   * // GET /itty-router/hello\n   * ittyRouter.get('/hello', () => new Response('Hello from itty-router'))\n   *\n   * const app = new Hono()\n   * app.mount('/itty-router', ittyRouter.handle)\n   * ```\n   *\n   * @example\n   * ```ts\n   * const app = new Hono()\n   * // Send the request to another application without modification.\n   * app.mount('/app', anotherApp, {\n   *   replaceRequest: (req) => req,\n   * })\n   * ```\n   */\n  mount(\n    path: string,\n    applicationHandler: (request: Request, ...args: any) => Response | Promise<Response>,\n    options?: MountOptions\n  ): Hono<E, S, BasePath, CurrentPath> {\n    // handle options\n    let replaceRequest: MountReplaceRequest | undefined\n    let optionHandler: MountOptionHandler | undefined\n    if (options) {\n      if (typeof options === 'function') {\n        optionHandler = options\n      } else {\n        optionHandler = options.optionHandler\n        if (options.replaceRequest === false) {\n          replaceRequest = (request) => request\n        } else {\n          replaceRequest = options.replaceRequest\n        }\n      }\n    }\n\n    // prepare handlers for request\n    const getOptions: (c: Context) => unknown[] = optionHandler\n      ? (c) => {\n          const options = optionHandler!(c)\n          return Array.isArray(options) ? options : [options]\n        }\n      : (c) => {\n          let executionContext: ExecutionContext | undefined = undefined\n          try {\n            executionContext = c.executionCtx\n          } catch {} // Do nothing\n          return [c.env, executionContext]\n        }\n    replaceRequest ||= (() => {\n      const mergedPath = mergePath(this._basePath, path)\n      const pathPrefixLength = mergedPath === '/' ? 0 : mergedPath.length\n      return (request) => {\n        const url = new URL(request.url)\n        url.pathname = url.pathname.slice(pathPrefixLength) || '/'\n        return new Request(url, request)\n      }\n    })()\n\n    const handler: MiddlewareHandler = async (c, next) => {\n      const res = await applicationHandler(replaceRequest(c.req.raw), ...getOptions(c))\n\n      if (res) {\n        return res\n      }\n\n      await next()\n    }\n    this.#addRoute(METHOD_NAME_ALL, mergePath(path, '*'), handler)\n    return this\n  }\n\n  #addRoute(method: string, path: string, handler: H): void {\n    method = method.toUpperCase()\n    path = mergePath(this._basePath, path)\n    const r: RouterRoute = { basePath: this._basePath, path, method, handler }\n    this.router.add(method, path, [handler, r])\n    this.routes.push(r)\n  }\n\n  #handleError(err: unknown, c: Context<E>): Response | Promise<Response> {\n    if (err instanceof Error) {\n      return this.errorHandler(err, c)\n    }\n    throw err\n  }\n\n  #dispatch(\n    request: Request,\n    executionCtx: ExecutionContext | FetchEventLike | undefined,\n    env: E['Bindings'],\n    method: string\n  ): Response | Promise<Response> {\n    // Handle HEAD method\n    if (method === 'HEAD') {\n      return (async () =>\n        new Response(null, await this.#dispatch(request, executionCtx, env, 'GET')))()\n    }\n\n    const path = this.getPath(request, { env })\n    const matchResult = this.router.match(method, path)\n\n    const c = new Context(request, {\n      path,\n      matchResult,\n      env,\n      executionCtx,\n      notFoundHandler: this.#notFoundHandler,\n    })\n\n    // Do not `compose` if it has only one handler\n    if (matchResult[0].length === 1) {\n      let res: ReturnType<H>\n      try {\n        res = matchResult[0][0][0][0](c, async () => {\n          c.res = await this.#notFoundHandler(c)\n        })\n      } catch (err) {\n        return this.#handleError(err, c)\n      }\n\n      return res instanceof Promise\n        ? res\n            .then(\n              (resolved: Response | undefined) =>\n                resolved || (c.finalized ? c.res : this.#notFoundHandler(c))\n            )\n            .catch((err: Error) => this.#handleError(err, c))\n        : (res ?? this.#notFoundHandler(c))\n    }\n\n    const composed = compose(matchResult[0], this.errorHandler, this.#notFoundHandler)\n\n    return (async () => {\n      try {\n        const context = await composed(c)\n        if (!context.finalized) {\n          throw new Error(\n            'Context is not finalized. Did you forget to return a Response object or `await next()`?'\n          )\n        }\n\n        return context.res\n      } catch (err) {\n        return this.#handleError(err, c)\n      }\n    })()\n  }\n\n  /**\n   * `.fetch()` will be entry point of your app.\n   *\n   * @see {@link https://hono.dev/docs/api/hono#fetch}\n   *\n   * @param {Request} request - request Object of request\n   * @param {Env} Env - env Object\n   * @param {ExecutionContext} - context of execution\n   * @returns {Response | Promise<Response>} response of request\n   *\n   */\n  fetch: (\n    request: Request,\n    Env?: E['Bindings'] | {},\n    executionCtx?: ExecutionContext\n  ) => Response | Promise<Response> = (request, ...rest) => {\n    return this.#dispatch(request, rest[1], rest[0], request.method)\n  }\n\n  /**\n   * `.request()` is a useful method for testing.\n   * You can pass a URL or pathname to send a GET request.\n   * app will return a Response object.\n   * ```ts\n   * test('GET /hello is ok', async () => {\n   *   const res = await app.request('/hello')\n   *   expect(res.status).toBe(200)\n   * })\n   * ```\n   * @see https://hono.dev/docs/api/hono#request\n   */\n  request = (\n    input: Request | string | URL,\n    requestInit?: RequestInit,\n    Env?: E['Bindings'] | {},\n    executionCtx?: ExecutionContext\n  ): Response | Promise<Response> => {\n    if (input instanceof Request) {\n      return this.fetch(requestInit ? new Request(input, requestInit) : input, Env, executionCtx)\n    }\n    input = input.toString()\n    return this.fetch(\n      new Request(\n        /^https?:\\/\\//.test(input) ? input : `http://localhost${mergePath('/', input)}`,\n        requestInit\n      ),\n      Env,\n      executionCtx\n    )\n  }\n\n  /**\n   * `.fire()` automatically adds a global fetch event listener.\n   * This can be useful for environments that adhere to the Service Worker API, such as non-ES module Cloudflare Workers.\n   * @deprecated\n   * Use `fire` from `hono/service-worker` instead.\n   * ```ts\n   * import { Hono } from 'hono'\n   * import { fire } from 'hono/service-worker'\n   *\n   * const app = new Hono()\n   * // ...\n   * fire(app)\n   * ```\n   * @see https://hono.dev/docs/api/hono#fire\n   * @see https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API\n   * @see https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/\n   */\n  fire = (): void => {\n    // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n    // @ts-ignore\n    addEventListener('fetch', (event: FetchEventLike): void => {\n      event.respondWith(this.#dispatch(event.request, event, undefined, event.request.method))\n    })\n  }\n}\n\nexport { Hono as HonoBase }\n"
  },
  {
    "path": "src/hono.test.ts",
    "content": "/* eslint-disable @typescript-eslint/no-unused-vars */\n/* eslint-disable @typescript-eslint/ban-ts-comment */\nimport { expectTypeOf } from 'vitest'\nimport { hc } from './client'\nimport type { Context, ExecutionContext } from './context'\nimport { Hono } from './hono'\nimport { HTTPException } from './http-exception'\nimport { logger } from './middleware/logger'\nimport { poweredBy } from './middleware/powered-by'\nimport { RegExpRouter } from './router/reg-exp-router'\nimport { SmartRouter } from './router/smart-router'\nimport { TrieRouter } from './router/trie-router'\nimport type { Handler, MiddlewareHandler, Next } from './types'\nimport type { Equal, Expect } from './utils/types'\nimport { getPath } from './utils/url'\n\n// https://stackoverflow.com/a/65666402\nfunction throwExpression(errorMessage: string): never {\n  throw new Error(errorMessage)\n}\n\ntype Env = {\n  Bindings: {\n    _: string\n  }\n}\n\nconst createResponseProxy = (response: Response) => {\n  return new Proxy(response, {\n    get(target, prop, receiver) {\n      const value = target[prop as keyof Response]\n      if (typeof value === 'function') {\n        return Object.defineProperties(\n          function (...args: unknown[]) {\n            // @ts-expect-error: `this` context is intentionally dynamic for proxy method binding\n            return Reflect.apply(value, this === receiver ? target : this, args)\n          },\n          {\n            name: { value: value.name },\n            length: { value: value.length },\n          }\n        )\n      }\n      return value\n    },\n  })\n}\n\ndescribe('GET Request', () => {\n  describe('without middleware', () => {\n    // In other words, this is a test for cases that do not use `compose()`\n\n    const app = new Hono<Env>()\n\n    app.get('/hello', async () => {\n      return new Response('hello', {\n        status: 200,\n        statusText: 'Hono is OK',\n      })\n    })\n\n    app.get('/hello-with-shortcuts', (c) => {\n      c.header('X-Custom', 'This is Hono')\n      c.status(201)\n      return c.html('<h1>Hono!!!</h1>')\n    })\n\n    app.get('/hello-env', (c) => {\n      return c.json(c.env)\n    })\n\n    app.get('/proxy-object', () => createResponseProxy(new Response('proxy')))\n\n    app.get('/async-proxy-object', async () => createResponseProxy(new Response('proxy')))\n\n    it('GET http://localhost/hello is ok', async () => {\n      const res = await app.request('http://localhost/hello')\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(res.statusText).toBe('Hono is OK')\n      expect(await res.text()).toBe('hello')\n    })\n\n    it('GET httphello is ng', async () => {\n      const res = await app.request('httphello')\n      expect(res.status).toBe(404)\n    })\n\n    it('GET /hello is ok', async () => {\n      const res = await app.request('/hello')\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(res.statusText).toBe('Hono is OK')\n      expect(await res.text()).toBe('hello')\n    })\n\n    it('GET hello is ok', async () => {\n      const res = await app.request('hello')\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(res.statusText).toBe('Hono is OK')\n      expect(await res.text()).toBe('hello')\n    })\n\n    it('GET /hello-with-shortcuts is ok', async () => {\n      const res = await app.request('http://localhost/hello-with-shortcuts')\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(201)\n      expect(res.headers.get('X-Custom')).toBe('This is Hono')\n      expect(res.headers.get('Content-Type')).toMatch(/text\\/html/)\n      expect(await res.text()).toBe('<h1>Hono!!!</h1>')\n    })\n\n    it('GET / is not found', async () => {\n      const res = await app.request('http://localhost/')\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(404)\n    })\n\n    it('GET /hello-env is ok', async () => {\n      const res = await app.request('/hello-env', undefined, { HELLO: 'world' })\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ HELLO: 'world' })\n    })\n\n    it('GET /proxy-object is ok', async () => {\n      const res = await app.request('/proxy-object')\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('proxy')\n    })\n\n    it('GET /async-proxy-object is ok', async () => {\n      const res = await app.request('/proxy-object')\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('proxy')\n    })\n  })\n\n  describe('with middleware', () => {\n    // when using `compose()`\n\n    const app = new Hono<Env>()\n\n    app.use('*', async (ctx, next) => {\n      await next()\n    })\n\n    app.get('/hello', async () => {\n      return new Response('hello', {\n        status: 200,\n        statusText: 'Hono is OK',\n      })\n    })\n\n    app.get('/hello-with-shortcuts', (c) => {\n      c.header('X-Custom', 'This is Hono')\n      c.status(201)\n      return c.html('<h1>Hono!!!</h1>')\n    })\n\n    app.get('/hello-env', (c) => {\n      return c.json(c.env)\n    })\n\n    app.get('/proxy-object', () => createResponseProxy(new Response('proxy')))\n\n    app.get('/async-proxy-object', async () => createResponseProxy(new Response('proxy')))\n\n    it('GET http://localhost/hello is ok', async () => {\n      const res = await app.request('http://localhost/hello')\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(res.statusText).toBe('Hono is OK')\n      expect(await res.text()).toBe('hello')\n    })\n\n    it('GET httphello is ng', async () => {\n      const res = await app.request('httphello')\n      expect(res.status).toBe(404)\n    })\n\n    it('GET /hello is ok', async () => {\n      const res = await app.request('/hello')\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(res.statusText).toBe('Hono is OK')\n      expect(await res.text()).toBe('hello')\n    })\n\n    it('GET hello is ok', async () => {\n      const res = await app.request('hello')\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(res.statusText).toBe('Hono is OK')\n      expect(await res.text()).toBe('hello')\n    })\n\n    it('GET /hello-with-shortcuts is ok', async () => {\n      const res = await app.request('http://localhost/hello-with-shortcuts')\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(201)\n      expect(res.headers.get('X-Custom')).toBe('This is Hono')\n      expect(res.headers.get('Content-Type')).toMatch(/text\\/html/)\n      expect(await res.text()).toBe('<h1>Hono!!!</h1>')\n    })\n\n    it('GET / is not found', async () => {\n      const res = await app.request('http://localhost/')\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(404)\n    })\n\n    it('GET /hello-env is ok', async () => {\n      const res = await app.request('/hello-env', undefined, { HELLO: 'world' })\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ HELLO: 'world' })\n    })\n\n    it('GET /proxy-object is ok', async () => {\n      const res = await app.request('/proxy-object')\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('proxy')\n    })\n\n    it('GET /async-proxy-object is ok', async () => {\n      const res = await app.request('/proxy-object')\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('proxy')\n    })\n  })\n})\n\ndescribe('Register handlers without a path', () => {\n  describe('No basePath', () => {\n    const app = new Hono()\n\n    app.get((c) => {\n      return c.text('Hello')\n    })\n\n    it('GET http://localhost/ is ok', async () => {\n      const res = await app.request('/')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('Hello')\n    })\n\n    it('GET http://localhost/anything is not found', async () => {\n      const res = await app.request('/anything')\n      expect(res.status).toBe(404)\n    })\n  })\n\n  describe('With specifying basePath', () => {\n    const app = new Hono().basePath('/about')\n\n    app.get((c) => {\n      return c.text('About')\n    })\n\n    it('GET http://localhost/about is ok', async () => {\n      const res = await app.request('/about')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('About')\n    })\n\n    it('GET http://localhost/ is not found', async () => {\n      const res = await app.request('/')\n      expect(res.status).toBe(404)\n    })\n  })\n\n  describe('With chaining', () => {\n    const app = new Hono()\n\n    app.post('/books').get((c) => {\n      return c.text('Books')\n    })\n\n    it('GET http://localhost/books is ok', async () => {\n      const res = await app.request('/books')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('Books')\n    })\n\n    it('GET http://localhost/ is not found', async () => {\n      const res = await app.request('/')\n      expect(res.status).toBe(404)\n    })\n  })\n})\n\ndescribe('Options', () => {\n  describe('router option', () => {\n    it('Should be SmartRouter', () => {\n      const app = new Hono()\n      expect(app.router instanceof SmartRouter).toBe(true)\n    })\n    it('Should be RegExpRouter', () => {\n      const app = new Hono({\n        router: new RegExpRouter(),\n      })\n      expect(app.router instanceof RegExpRouter).toBe(true)\n    })\n  })\n\n  describe('strict parameter', () => {\n    describe('strict is true with not slash', () => {\n      const app = new Hono()\n\n      app.get('/hello', (c) => {\n        return c.text('/hello')\n      })\n\n      it('/hello/ is not found', async () => {\n        let res = await app.request('http://localhost/hello')\n        expect(res).not.toBeNull()\n        expect(res.status).toBe(200)\n        res = await app.request('http://localhost/hello/')\n        expect(res).not.toBeNull()\n        expect(res.status).toBe(404)\n      })\n    })\n\n    describe('strict is true with slash', () => {\n      const app = new Hono()\n\n      app.get('/hello/', (c) => {\n        return c.text('/hello/')\n      })\n\n      it('/hello is not found', async () => {\n        let res = await app.request('http://localhost/hello/')\n        expect(res).not.toBeNull()\n        expect(res.status).toBe(200)\n        res = await app.request('http://localhost/hello')\n        expect(res).not.toBeNull()\n        expect(res.status).toBe(404)\n      })\n    })\n\n    describe('strict is false', () => {\n      const app = new Hono({ strict: false })\n\n      app.get('/hello', (c) => {\n        return c.text('/hello')\n      })\n\n      it('/hello and /hello/ are treated as the same', async () => {\n        let res = await app.request('http://localhost/hello')\n        expect(res).not.toBeNull()\n        expect(res.status).toBe(200)\n        res = await app.request('http://localhost/hello/')\n        expect(res).not.toBeNull()\n        expect(res.status).toBe(200)\n      })\n    })\n\n    describe('strict is false with `getPath` option', () => {\n      const app = new Hono({\n        strict: false,\n        getPath: getPath,\n      })\n\n      app.get('/hello', (c) => {\n        return c.text('/hello')\n      })\n\n      it('/hello and /hello/ are treated as the same', async () => {\n        let res = await app.request('http://localhost/hello')\n        expect(res).not.toBeNull()\n        expect(res.status).toBe(200)\n        res = await app.request('http://localhost/hello/')\n        expect(res).not.toBeNull()\n        expect(res.status).toBe(200)\n      })\n    })\n  })\n\n  it('Should not modify the options passed to it', () => {\n    const options = { strict: true }\n    const clone = structuredClone(options)\n    const app = new Hono(clone)\n    expect(clone).toEqual(options)\n  })\n})\n\ndescribe('Destruct functions in context', () => {\n  it('Should return 200 response - text', async () => {\n    const app = new Hono()\n    app.get('/text', ({ text }) => text('foo'))\n    const res = await app.request('http://localhost/text')\n    expect(res.status).toBe(200)\n  })\n  it('Should return 200 response - json', async () => {\n    const app = new Hono()\n    app.get('/json', ({ json }) => json({ foo: 'bar' }))\n    const res = await app.request('http://localhost/json')\n    expect(res.status).toBe(200)\n  })\n})\n\ndescribe('Routing', () => {\n  it('Return it self', async () => {\n    const app = new Hono()\n\n    const app2 = app.get('/', () => new Response('get /'))\n    expect(app2).not.toBeUndefined()\n    app2.delete('/', () => new Response('delete /'))\n\n    let res = await app2.request('http://localhost/', { method: 'GET' })\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('get /')\n\n    res = await app2.request('http://localhost/', { method: 'DELETE' })\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('delete /')\n  })\n\n  it('Nested route', async () => {\n    const app = new Hono()\n\n    const book = app.basePath('/book')\n    book.get('/', (c) => c.text('get /book'))\n    book.get('/:id', (c) => {\n      return c.text('get /book/' + c.req.param('id'))\n    })\n    book.post('/', (c) => c.text('post /book'))\n\n    const user = app.basePath('/user')\n    user.get('/login', (c) => c.text('get /user/login'))\n    user.post('/register', (c) => c.text('post /user/register'))\n\n    const appForEachUser = user.basePath(':id')\n    appForEachUser.get('/profile', (c) => c.text('get /user/' + c.req.param('id') + '/profile'))\n\n    app.get('/add-path-after-route-call', (c) => c.text('get /add-path-after-route-call'))\n\n    let res = await app.request('http://localhost/book', { method: 'GET' })\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('get /book')\n\n    res = await app.request('http://localhost/book/123', { method: 'GET' })\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('get /book/123')\n\n    res = await app.request('http://localhost/book', { method: 'POST' })\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('post /book')\n\n    res = await app.request('http://localhost/book/', { method: 'GET' })\n    expect(res.status).toBe(404)\n\n    res = await app.request('http://localhost/user/login', { method: 'GET' })\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('get /user/login')\n\n    res = await app.request('http://localhost/user/register', { method: 'POST' })\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('post /user/register')\n\n    res = await app.request('http://localhost/user/123/profile', { method: 'GET' })\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('get /user/123/profile')\n\n    res = await app.request('http://localhost/add-path-after-route-call', { method: 'GET' })\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('get /add-path-after-route-call')\n  })\n\n  it('Nested route - subApp with basePath', async () => {\n    const app = new Hono()\n    const book = new Hono().basePath('/book')\n    book.get('/', (c) => c.text('get /book'))\n    app.route('/api', book)\n\n    const res = await app.request('http://localhost/api/book', { method: 'GET' })\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('get /book')\n  })\n\n  it('Multiple route', async () => {\n    const app = new Hono()\n\n    const book = new Hono()\n    book.get('/hello', (c) => c.text('get /book/hello'))\n\n    const user = new Hono()\n    user.get('/hello', (c) => c.text('get /user/hello'))\n\n    app.route('/book', book).route('/user', user)\n\n    let res = await app.request('http://localhost/book/hello', { method: 'GET' })\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('get /book/hello')\n\n    res = await app.request('http://localhost/user/hello', { method: 'GET' })\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('get /user/hello')\n  })\n\n  describe('Nested route with middleware', () => {\n    const api = new Hono()\n    const api2 = api.use('*', async (_c, next) => await next())\n\n    it('Should mount routes with no type errors', () => {\n      const app = new Hono().route('/api', api2)\n    })\n  })\n\n  describe('Grouped route', () => {\n    let one: Hono, two: Hono, three: Hono\n\n    beforeEach(() => {\n      one = new Hono()\n      two = new Hono()\n      three = new Hono()\n    })\n\n    it('only works with correct order', async () => {\n      three.get('/hi', (c) => c.text('hi'))\n      two.route('/three', three)\n      one.route('/two', two)\n\n      const { status } = await one.request('http://localhost/two/three/hi', { method: 'GET' })\n      expect(status).toBe(200)\n    })\n\n    it('fails with incorrect order 1', async () => {\n      three.get('/hi', (c) => c.text('hi'))\n      one.route('/two', two)\n      two.route('/three', three)\n\n      const { status } = await one.request('http://localhost/two/three/hi', { method: 'GET' })\n      expect(status).toBe(404)\n    })\n\n    it('fails with incorrect order 2', async () => {\n      two.route('/three', three)\n      three.get('/hi', (c) => c.text('hi'))\n      one.route('/two', two)\n\n      const { status } = await one.request('http://localhost/two/three/hi', { method: 'GET' })\n      expect(status).toBe(404)\n    })\n\n    it('fails with incorrect order 3', async () => {\n      two.route('/three', three)\n      one.route('/two', two)\n      three.get('/hi', (c) => c.text('hi'))\n\n      const { status } = await one.request('http://localhost/two/three/hi', { method: 'GET' })\n      expect(status).toBe(404)\n    })\n\n    it('fails with incorrect order 4', async () => {\n      one.route('/two', two)\n      three.get('/hi', (c) => c.text('hi'))\n      two.route('/three', three)\n\n      const { status } = await one.request('http://localhost/two/three/hi', { method: 'GET' })\n      expect(status).toBe(404)\n    })\n\n    it('fails with incorrect order 5', async () => {\n      one.route('/two', two)\n      two.route('/three', three)\n      three.get('/hi', (c) => c.text('hi'))\n\n      const { status } = await one.request('http://localhost/two/three/hi', { method: 'GET' })\n      expect(status).toBe(404)\n    })\n  })\n\n  it('routing with hostname', async () => {\n    const app = new Hono({\n      getPath: (req) => req.url.replace(/^https?:\\/(.+?)$/, '$1'),\n    })\n\n    const sub = new Hono()\n    sub.get('/', (c) => c.text('hello sub'))\n    sub.get('/foo', (c) => c.text('hello sub foo'))\n\n    app.get('/www1.example.com/hello', () => new Response('hello www1'))\n    app.get('/www2.example.com/hello', () => new Response('hello www2'))\n\n    app.get('/www1.example.com/', (c) => c.text('hello www1 root'))\n    app.route('/www1.example.com/sub', sub)\n\n    let res = await app.request('http://www1.example.com/hello')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hello www1')\n\n    res = await app.request('http://www2.example.com/hello')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hello www2')\n\n    res = await app.request('http://www1.example.com/')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hello www1 root')\n\n    res = await app.request('http://www1.example.com/sub')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hello sub')\n\n    res = await app.request('http://www1.example.com/sub/foo')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hello sub foo')\n  })\n\n  it('routing with request header', async () => {\n    const app = new Hono({\n      getPath: (req) =>\n        '/' + req.headers.get('host') + req.url.replace(/^https?:\\/\\/[^/]+(\\/[^?]*)/, '$1'),\n    })\n\n    const sub = new Hono()\n    sub.get('/', (c) => c.text('hello sub'))\n    sub.get('/foo', (c) => c.text('hello sub foo'))\n\n    app.get('/www1.example.com/hello', () => new Response('hello www1'))\n    app.get('/www2.example.com/hello', () => new Response('hello www2'))\n\n    app.get('/www1.example.com/', (c) => c.text('hello www1 root'))\n    app.route('/www1.example.com/sub', sub)\n\n    let res = await app.request('http://www1.example.com/hello', {\n      headers: {\n        host: 'www1.example.com',\n      },\n    })\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hello www1')\n\n    res = await app.request('http://www2.example.com/hello', {\n      headers: {\n        host: 'www2.example.com',\n      },\n    })\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hello www2')\n\n    res = await app.request('http://www1.example.com/', {\n      headers: {\n        host: 'www1.example.com',\n      },\n    })\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hello www1 root')\n\n    res = await app.request('http://www1.example.com/sub', {\n      headers: {\n        host: 'www1.example.com',\n      },\n    })\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hello sub')\n\n    res = await app.request('http://www1.example.com/sub/foo', {\n      headers: {\n        host: 'www1.example.com',\n      },\n    })\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hello sub foo')\n    expect(res.status).toBe(200)\n  })\n\n  describe('routing with the bindings value', () => {\n    const app = new Hono<{ Bindings: { host: string } }>({\n      getPath: (req, options) => {\n        const url = new URL(req.url)\n        const host = options?.env?.host\n        const prefix = url.host === host ? '/FOO' : ''\n        return url.pathname === '/' ? prefix : `${prefix}${url.pathname}`\n      },\n    })\n\n    app.get('/about', (c) => c.text('About root'))\n    app.get('/FOO/about', (c) => c.text('About FOO'))\n\n    it('Should return 200 without specifying a hostname', async () => {\n      const res = await app.request('/about')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('About root')\n    })\n\n    it('Should return 200 with specifying the hostname in env', async () => {\n      const req = new Request('http://foo.localhost/about')\n      const res = await app.fetch(req, { host: 'foo.localhost' })\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('About FOO')\n    })\n  })\n\n  describe('Chained route', () => {\n    const app = new Hono()\n\n    app\n      .get('/chained/:abc', (c) => {\n        const abc = c.req.param('abc')\n        return c.text(`GET for ${abc}`)\n      })\n      .post((c) => {\n        const abc = c.req.param('abc')\n        return c.text(`POST for ${abc}`)\n      })\n    it('Should return 200 response from GET request', async () => {\n      const res = await app.request('http://localhost/chained/abc', { method: 'GET' })\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('GET for abc')\n    })\n    it('Should return 200 response from POST request', async () => {\n      const res = await app.request('http://localhost/chained/abc', { method: 'POST' })\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('POST for abc')\n    })\n    it('Should return 404 response from PUT request', async () => {\n      const res = await app.request('http://localhost/chained/abc', { method: 'PUT' })\n      expect(res.status).toBe(404)\n    })\n  })\n\n  describe('Encoded path', () => {\n    let app: Hono\n    beforeEach(() => {\n      app = new Hono()\n    })\n\n    it('should decode path parameter', async () => {\n      app.get('/users/:id', (c) => c.text(`id is ${c.req.param('id')}`))\n\n      const res = await app.request('http://localhost/users/%C3%A7awa%20y%C3%AE%3F')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('id is çawa yî?')\n    })\n\n    it('should decode \"/\"', async () => {\n      app.get('/users/:id', (c) => c.text(`id is ${c.req.param('id')}`))\n\n      const res = await app.request('http://localhost/users/hono%2Fposts') // %2F is '/'\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('id is hono/posts')\n    })\n\n    it('should decode alphabets', async () => {\n      app.get('/users/static', (c) => c.text('static'))\n\n      const res = await app.request('http://localhost/users/%73tatic') // %73 is 's'\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('static')\n    })\n\n    it('should decode alphabets with invalid UTF-8 sequence', async () => {\n      app.get('/static/:path', (c) => {\n        return c.text(`by c.req.param: ${c.req.param('path')}`)\n      })\n\n      const res = await app.request('http://localhost/%73tatic/%A4%A2') // %73 is 's', %A4%A2 is invalid UTF-8 sequence\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('by c.req.param: %A4%A2')\n    })\n\n    it('should decode alphabets with invalid percent encoding', async () => {\n      app.get('/static/:path', (c) => {\n        return c.text(`by c.req.param: ${c.req.param('path')}`)\n      })\n\n      const res = await app.request('http://localhost/%73tatic/%a') // %73 is 's', %a is invalid percent encoding\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('by c.req.param: %a')\n    })\n\n    it('should not double decode', async () => {\n      app.get('/users/:id', (c) => c.text(`posts of ${c.req.param('id')}`))\n\n      const res = await app.request('http://localhost/users/%2525') // %25 is '%'\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('posts of %25')\n    })\n  })\n})\n\ndescribe('param and query', () => {\n  const apps: Record<string, Hono> = {}\n  apps['get by name'] = (() => {\n    const app = new Hono()\n\n    app.get('/entry/:id', (c) => {\n      const id = c.req.param('id')\n      return c.text(`id is ${id}`)\n    })\n\n    app.get('/date/:date{[0-9]+}', (c) => {\n      const date = c.req.param('date')\n      return c.text(`date is ${date}`)\n    })\n\n    app.get('/search', (c) => {\n      const name = c.req.query('name')\n      return c.text(`name is ${name}`)\n    })\n\n    app.get('/multiple-values', (c) => {\n      const queries = c.req.queries('q') ?? throwExpression('missing query values')\n      const limit = c.req.queries('limit') ?? throwExpression('missing query values')\n      return c.text(`q is ${queries[0]} and ${queries[1]}, limit is ${limit[0]}`)\n    })\n\n    app.get('/add-header', (c) => {\n      const bar = c.req.header('X-Foo')\n      return c.text(`foo is ${bar}`)\n    })\n\n    return app\n  })()\n\n  apps['get all as an object'] = (() => {\n    const app = new Hono()\n\n    app.get('/entry/:id', (c) => {\n      const { id } = c.req.param()\n      return c.text(`id is ${id}`)\n    })\n\n    app.get('/date/:date{[0-9]+}', (c) => {\n      const { date } = c.req.param()\n      return c.text(`date is ${date}`)\n    })\n\n    app.get('/search', (c) => {\n      const { name } = c.req.query()\n      return c.text(`name is ${name}`)\n    })\n\n    app.get('/multiple-values', (c) => {\n      const { q, limit } = c.req.queries()\n      return c.text(`q is ${q[0]} and ${q[1]}, limit is ${limit[0]}`)\n    })\n\n    app.get('/add-header', (c) => {\n      const { 'x-foo': bar } = c.req.header()\n      return c.text(`foo is ${bar}`)\n    })\n\n    return app\n  })()\n\n  describe.each(Object.keys(apps))('%s', (name) => {\n    const app = apps[name]\n\n    it('param of /entry/:id is found', async () => {\n      const res = await app.request('http://localhost/entry/123')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('id is 123')\n    })\n\n    it('param of /entry/:id is found, even for Array object method names', async () => {\n      const res = await app.request('http://localhost/entry/key')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('id is key')\n    })\n\n    it('param of /entry/:id is decoded', async () => {\n      const res = await app.request('http://localhost/entry/%C3%A7awa%20y%C3%AE%3F')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('id is çawa yî?')\n    })\n\n    it('param of /date/:date is found', async () => {\n      const res = await app.request('http://localhost/date/0401')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('date is 0401')\n    })\n\n    it('query of /search?name=sam is found', async () => {\n      const res = await app.request('http://localhost/search?name=sam')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('name is sam')\n    })\n\n    it('query of /search?name=sam&name=tom is found', async () => {\n      const res = await app.request('http://localhost/search?name=sam&name=tom')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('name is sam')\n    })\n\n    it('query of /multiple-values?q=foo&q=bar&limit=10 is found', async () => {\n      const res = await app.request('http://localhost/multiple-values?q=foo&q=bar&limit=10')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('q is foo and bar, limit is 10')\n    })\n\n    it('/add-header header - X-Foo is Bar', async () => {\n      const req = new Request('http://localhost/add-header')\n      req.headers.append('X-Foo', 'Bar')\n      const res = await app.request(req)\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('foo is Bar')\n    })\n  })\n\n  describe('param with undefined', () => {\n    const app = new Hono()\n    app.get('/foo/:foo', (c) => {\n      const bar = c.req.param('bar')\n      return c.json({ foo: bar })\n    })\n    it('param of /foo/foo should return undefined not \"undefined\"', async () => {\n      const res = await app.request('http://localhost/foo/foo')\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ foo: undefined })\n    })\n  })\n})\n\ndescribe('c.req.path', () => {\n  const app = new Hono()\n  app.get('/', (c) => c.text(c.req.path))\n  app.get('/search', (c) => c.text(c.req.path))\n\n  it('Should get the path `/` correctly', async () => {\n    const res = await app.request('/')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('/')\n  })\n\n  it('Should get the path `/search` correctly with a query', async () => {\n    const res = await app.request('/search?query=hono')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('/search')\n  })\n})\n\ndescribe('Header', () => {\n  const app = new Hono()\n\n  app.get('/text', (c) => {\n    return c.text('Hello')\n  })\n\n  app.get('/text-with-custom-header', (c) => {\n    c.header('X-Custom', 'Message')\n    return c.text('Hello')\n  })\n\n  it('Should return correct headers - /text', async () => {\n    const res = await app.request('/text')\n    expect(res.status).toBe(200)\n    expect(res.headers.get('content-type')).toMatch(/^text\\/plain/)\n    expect(await res.text()).toBe('Hello')\n  })\n\n  it('Should return correct headers - /text-with-custom-header', async () => {\n    const res = await app.request('/text-with-custom-header')\n    expect(res.status).toBe(200)\n    expect(res.headers.get('x-custom')).toBe('Message')\n    expect(res.headers.get('content-type')).toMatch(/^text\\/plain/)\n    expect(await res.text()).toBe('Hello')\n  })\n})\n\ndescribe('Middleware', () => {\n  describe('Basic', () => {\n    const app = new Hono()\n\n    // Custom Logger\n    app.use('*', async (c, next) => {\n      console.log(`${c.req.method} : ${c.req.url}`)\n      await next()\n    })\n\n    // Append Custom Header\n    app.use('*', async (c, next) => {\n      await next()\n      c.res.headers.append('x-custom', 'root')\n    })\n\n    app.use('/hello', async (c, next) => {\n      await next()\n      c.res.headers.append('x-message', 'custom-header')\n    })\n\n    app.use('/hello/*', async (c, next) => {\n      await next()\n      c.res.headers.append('x-message-2', 'custom-header-2')\n    })\n\n    app.get('/hello', (c) => {\n      return c.text('hello')\n    })\n\n    app.use('/json/*', async (c, next) => {\n      c.res.headers.append('foo', 'bar')\n      await next()\n    })\n\n    app.get('/json', (c) => {\n      // With a raw response\n      return new Response(\n        JSON.stringify({\n          message: 'hello',\n        }),\n        {\n          headers: {\n            'content-type': 'application/json',\n          },\n        }\n      )\n    })\n\n    app.get('/hello/:message', (c) => {\n      const message = c.req.param('message')\n      return c.text(`${message}`)\n    })\n\n    app.get('/error', () => {\n      throw new Error('Error!')\n    })\n\n    app.notFound((c) => {\n      return c.text('Not Found Foo', 404)\n    })\n\n    it('logging and custom header', async () => {\n      const res = await app.request('http://localhost/hello')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('hello')\n      expect(res.headers.get('x-custom')).toBe('root')\n      expect(res.headers.get('x-message')).toBe('custom-header')\n      expect(res.headers.get('x-message-2')).toBe('custom-header-2')\n    })\n\n    it('logging and custom header with named param', async () => {\n      const res = await app.request('http://localhost/hello/message')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('message')\n      expect(res.headers.get('x-custom')).toBe('root')\n      expect(res.headers.get('x-message-2')).toBe('custom-header-2')\n    })\n\n    it('should return correct the content-type header', async () => {\n      const res = await app.request('http://localhost/json')\n      expect(res.status).toBe(200)\n      expect(res.headers.get('content-type')).toMatch(/^application\\/json/)\n    })\n\n    it('not found', async () => {\n      const res = await app.request('http://localhost/foo')\n      expect(res.status).toBe(404)\n      expect(await res.text()).toBe('Not Found Foo')\n    })\n\n    it('internal server error', async () => {\n      const res = await app.request('http://localhost/error')\n      expect(res.status).toBe(500)\n      console.log(await res.text())\n    })\n  })\n\n  describe('Chained route', () => {\n    const app = new Hono()\n    app\n      .use('/chained/*', async (c, next) => {\n        c.req.raw.headers.append('x-before', 'abc')\n        await next()\n      })\n      .use(async (c, next) => {\n        await next()\n        c.header(\n          'x-after',\n          c.req.header('x-before') ?? throwExpression('missing `x-before` header')\n        )\n      })\n      .get('/chained/abc', (c) => {\n        return c.text('GET chained')\n      })\n    it('GET /chained/abc', async () => {\n      const res = await app.request('http://localhost/chained/abc')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('GET chained')\n      expect(res.headers.get('x-after')).toBe('abc')\n    })\n  })\n\n  describe('Multiple handler', () => {\n    const app = new Hono()\n    app\n      .use(\n        '/multiple/*',\n        async (c, next) => {\n          c.req.raw.headers.append('x-before', 'abc')\n          await next()\n        },\n        async (c, next) => {\n          await next()\n          c.header(\n            'x-after',\n            c.req.header('x-before') ?? throwExpression('missing `x-before` header')\n          )\n        }\n      )\n      .get('/multiple/abc', (c) => {\n        return c.text('GET multiple')\n      })\n    it('GET /multiple/abc', async () => {\n      const res = await app.request('http://localhost/multiple/abc')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('GET multiple')\n      expect(res.headers.get('x-after')).toBe('abc')\n    })\n  })\n\n  describe('Overwrite the response from middleware after next()', () => {\n    const app = new Hono()\n\n    app.use('/normal', async (c, next) => {\n      await next()\n      c.res = new Response('Middleware')\n    })\n\n    app.use('/overwrite', async (c, next) => {\n      await next()\n      c.res = undefined\n      c.res = new Response('Middleware')\n    })\n\n    app.get('*', (c) => {\n      c.header('x-custom', 'foo')\n      return c.text('Handler')\n    })\n\n    it('Should have the custom header', async () => {\n      const res = await app.request('/normal')\n      expect(res.headers.get('x-custom')).toBe('foo')\n    })\n\n    it('Should not have the custom header', async () => {\n      const res = await app.request('/overwrite')\n      expect(res.headers.get('x-custom')).toBe(null)\n    })\n  })\n})\n\ndescribe('Builtin Middleware', () => {\n  const app = new Hono()\n  app.use('/abc', poweredBy())\n  app.use('/def', async (c, next) => {\n    const middleware = poweredBy()\n    await middleware(c, next)\n  })\n  app.get('/abc', () => new Response())\n  app.get('/def', () => new Response())\n\n  it('\"powered-by\" middleware', async () => {\n    const res = await app.request('http://localhost/abc')\n    expect(res.headers.get('x-powered-by')).toBe('Hono')\n  })\n\n  it('\"powered-by\" middleware in a handler', async () => {\n    const res = await app.request('http://localhost/def')\n    expect(res.headers.get('x-powered-by')).toBe('Hono')\n  })\n})\n\ndescribe('Middleware with app.HTTP_METHOD', () => {\n  describe('Basic', () => {\n    const app = new Hono()\n\n    app.all('*', async (c, next) => {\n      c.header('x-before-dispatch', 'foo')\n      await next()\n      c.header('x-custom-message', 'hello')\n    })\n\n    const customHeader = async (c: Context, next: Next) => {\n      c.req.raw.headers.append('x-custom-foo', 'bar')\n      await next()\n    }\n\n    const customHeader2 = async (c: Context, next: Next) => {\n      await next()\n      c.header('x-custom-foo-2', 'bar-2')\n    }\n\n    app\n      .get('/abc', customHeader, (c) => {\n        const foo = c.req.header('x-custom-foo') || ''\n        return c.text(foo)\n      })\n      .post(customHeader2, (c) => {\n        return c.text('POST /abc')\n      })\n\n    it('GET /abc', async () => {\n      const res = await app.request('http://localhost/abc')\n      expect(res.status).toBe(200)\n      expect(res.headers.get('x-custom-message')).toBe('hello')\n      expect(res.headers.get('x-before-dispatch')).toBe('foo')\n      expect(await res.text()).toBe('bar')\n    })\n    it('POST /abc', async () => {\n      const res = await app.request('http://localhost/abc', { method: 'POST' })\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('POST /abc')\n      expect(res.headers.get('x-custom-foo-2')).toBe('bar-2')\n    })\n  })\n\n  describe('With builtin middleware', () => {\n    const app = new Hono()\n    app.get('/abc', poweredBy(), (c) => {\n      return c.text('GET /abc')\n    })\n    it('GET /abc', async () => {\n      const res = await app.request('http://localhost/abc')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('GET /abc')\n      expect(res.headers.get('x-powered-by')).toBe('Hono')\n    })\n  })\n})\n\ndescribe('Not Found', () => {\n  const app = new Hono()\n\n  app.notFound((c) => {\n    return c.text('Custom 404 Not Found', 404)\n  })\n\n  app.get('/hello', (c) => {\n    return c.text('hello')\n  })\n\n  app.get('/notfound', (c) => {\n    return c.notFound()\n  })\n\n  it('Custom 404 Not Found', async () => {\n    let res = await app.request('http://localhost/hello')\n    expect(res.status).toBe(200)\n    res = await app.request('http://localhost/notfound')\n    expect(res.status).toBe(404)\n    res = await app.request('http://localhost/foo')\n    expect(res.status).toBe(404)\n    expect(await res.text()).toBe('Custom 404 Not Found')\n  })\n\n  describe('Not Found with a middleware', () => {\n    const app = new Hono()\n\n    app.get('/', (c) => c.text('hello'))\n    app.use('*', async (c, next) => {\n      await next()\n      c.res = new Response((await c.res.text()) + ' + Middleware', c.res)\n    })\n\n    it('Custom 404 Not Found', async () => {\n      let res = await app.request('http://localhost/')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('hello')\n      res = await app.request('http://localhost/foo')\n      expect(res.status).toBe(404)\n      expect(await res.text()).toBe('404 Not Found + Middleware')\n    })\n  })\n\n  describe('Not Found with some middleware', () => {\n    const app = new Hono()\n\n    app.get('/', (c) => c.text('hello'))\n    app.use('*', async (c, next) => {\n      await next()\n      c.res = new Response((await c.res.text()) + ' + Middleware 1', c.res)\n    })\n    app.use('*', async (c, next) => {\n      await next()\n      c.res = new Response((await c.res.text()) + ' + Middleware 2', c.res)\n    })\n\n    it('Custom 404 Not Found', async () => {\n      let res = await app.request('http://localhost/')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('hello')\n      res = await app.request('http://localhost/foo')\n      expect(res.status).toBe(404)\n      expect(await res.text()).toBe('404 Not Found + Middleware 2 + Middleware 1')\n    })\n  })\n\n  describe('No response from a handler', () => {\n    const app = new Hono()\n\n    app.get('/', (c) => c.text('hello'))\n    app.get('/not-found', async (c) => undefined)\n\n    it('Custom 404 Not Found', async () => {\n      let res = await app.request('http://localhost/')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('hello')\n      res = await app.request('http://localhost/not-found')\n      expect(res.status).toBe(404)\n      expect(await res.text()).toBe('404 Not Found')\n    })\n  })\n\n  describe('Custom 404 Not Found with a middleware like Compress Middleware', () => {\n    const app = new Hono()\n\n    // Custom Middleware which creates a new Response object after `next()`.\n    app.use('*', async (c, next) => {\n      await next()\n      c.res = new Response(await c.res.text(), c.res)\n    })\n\n    app.notFound((c) => {\n      return c.text('Custom NotFound', 404)\n    })\n\n    it('Custom 404 Not Found', async () => {\n      const res = await app.request('http://localhost/')\n      expect(res.status).toBe(404)\n      expect(await res.text()).toBe('Custom NotFound')\n    })\n  })\n})\n\ndescribe('Redirect', () => {\n  const app = new Hono()\n  app.get('/redirect', (c) => {\n    return c.redirect('/')\n  })\n\n  it('Absolute URL', async () => {\n    const res = await app.request('https://example.com/redirect')\n    expect(res.status).toBe(302)\n    expect(res.headers.get('Location')).toBe('/')\n  })\n})\n\ndescribe('Error handle', () => {\n  describe('Basic', () => {\n    const app = new Hono()\n\n    app.get('/error', () => {\n      throw new Error('This is Error')\n    })\n\n    app.get('/error-string', () => {\n      throw 'This is Error'\n    })\n\n    app.use('/error-middleware', async () => {\n      throw new Error('This is Middleware Error')\n    })\n\n    app.onError((err, c) => {\n      c.header('x-debug', err.message)\n      return c.text('Custom Error Message', 500)\n    })\n\n    it('Should throw Error if a non-Error object is thrown in a handler', async () => {\n      expect(() => app.request('/error-string')).toThrowError()\n    })\n\n    it('Custom Error Message', async () => {\n      let res = await app.request('https://example.com/error')\n      expect(res.status).toBe(500)\n      expect(await res.text()).toBe('Custom Error Message')\n      expect(res.headers.get('x-debug')).toBe('This is Error')\n\n      res = await app.request('https://example.com/error-middleware')\n      expect(res.status).toBe(500)\n      expect(await res.text()).toBe('Custom Error Message')\n      expect(res.headers.get('x-debug')).toBe('This is Middleware Error')\n    })\n  })\n\n  describe('Async custom handler', () => {\n    const app = new Hono()\n\n    app.get('/error', () => {\n      throw new Error('This is Error')\n    })\n\n    app.use('/error-middleware', async () => {\n      throw new Error('This is Middleware Error')\n    })\n\n    app.onError(async (err, c) => {\n      const promise = new Promise((resolve) =>\n        setTimeout(() => {\n          resolve('Promised')\n        }, 1)\n      )\n      const message = (await promise) as string\n      c.header('x-debug', err.message)\n      return c.text(`Custom Error Message with ${message}`, 500)\n    })\n\n    it('Custom Error Message', async () => {\n      let res = await app.request('https://example.com/error')\n      expect(res.status).toBe(500)\n      expect(await res.text()).toBe('Custom Error Message with Promised')\n      expect(res.headers.get('x-debug')).toBe('This is Error')\n\n      res = await app.request('https://example.com/error-middleware')\n      expect(res.status).toBe(500)\n      expect(await res.text()).toBe('Custom Error Message with Promised')\n      expect(res.headers.get('x-debug')).toBe('This is Middleware Error')\n    })\n  })\n\n  describe('Handle HTTPException', () => {\n    const app = new Hono()\n\n    app.get('/exception', () => {\n      throw new HTTPException(401, {\n        message: 'Unauthorized',\n      })\n    })\n\n    it('Should return 401 response', async () => {\n      const res = await app.request('http://localhost/exception')\n      expect(res.status).toBe(401)\n      expect(await res.text()).toBe('Unauthorized')\n    })\n\n    const app2 = new Hono()\n\n    app2.get('/exception', () => {\n      throw new HTTPException(401)\n    })\n\n    app2.onError((err, c) => {\n      if (err instanceof HTTPException && err.status === 401) {\n        return c.text('Custom Error Message', 401)\n      }\n      return c.text('Internal Server Error', 500)\n    })\n\n    it('Should return 401 response with a custom message', async () => {\n      const res = await app2.request('http://localhost/exception')\n      expect(res.status).toBe(401)\n      expect(await res.text()).toBe('Custom Error Message')\n    })\n  })\n\n  describe('Handle HTTPException like object', () => {\n    const app = new Hono()\n\n    class CustomError extends Error {\n      getResponse() {\n        return new Response('Custom Error', { status: 400 })\n      }\n    }\n\n    app.get('/exception', () => {\n      throw new CustomError()\n    })\n\n    it('Should return 401 response', async () => {\n      const res = await app.request('http://localhost/exception')\n      expect(res.status).toBe(400)\n      expect(await res.text()).toBe('Custom Error')\n    })\n  })\n\n  describe('HTTPException with finally block', () => {\n    const app = new Hono()\n    app.use(async (c) => {\n      try {\n        throw new Error()\n      } catch (cause) {\n        throw new HTTPException(302, {\n          cause,\n          res: c.redirect('/?error=invalid_request', 302),\n        })\n      } finally {\n        c.header('x-custom', 'custom message')\n      }\n    })\n    it('Should have the custom header', async () => {\n      const res = await app.request('http://localhost/')\n      expect(res.status).toBe(302)\n      expect(res.headers.get('x-custom')).toBe('custom message')\n    })\n  })\n})\n\ndescribe('Error handling in middleware', () => {\n  const app = new Hono()\n\n  app.get('/handle-error-in-middleware', async (c, next) => {\n    await next()\n    if (c.error) {\n      const message = c.error.message\n      c.res = c.text(`Handle the error in middleware, original message is ${message}`, 500)\n    }\n  })\n\n  app.get('/handle-error-in-middleware-async', async (c, next) => {\n    await next()\n    if (c.error) {\n      const message = c.error.message\n      c.res = c.text(\n        `Handle the error in middleware with async, original message is ${message}`,\n        500\n      )\n    }\n  })\n\n  app.get('/handle-error-in-middleware', () => {\n    throw new Error('Error message')\n  })\n\n  app.get('/handle-error-in-middleware-async', async () => {\n    throw new Error('Error message')\n  })\n\n  it('Should handle the error in middleware', async () => {\n    const res = await app.request('https://example.com/handle-error-in-middleware')\n    expect(res.status).toBe(500)\n    expect(await res.text()).toBe(\n      'Handle the error in middleware, original message is Error message'\n    )\n  })\n\n  it('Should handle the error in middleware - async', async () => {\n    const res = await app.request('https://example.com/handle-error-in-middleware-async')\n    expect(res.status).toBe(500)\n    expect(await res.text()).toBe(\n      'Handle the error in middleware with async, original message is Error message'\n    )\n  })\n\n  describe('Default route app.use', () => {\n    const app = new Hono()\n    app\n      .use(async (c, next) => {\n        c.header('x-default-use', 'abc')\n        await next()\n      })\n      .get('/multiple/abc', (c) => {\n        return c.text('GET multiple')\n      })\n    it('GET /multiple/abc', async () => {\n      const res = await app.request('http://localhost/multiple/abc')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('GET multiple')\n      expect(res.headers.get('x-default-use')).toBe('abc')\n    })\n  })\n\n  describe('Error in `notFound()`', () => {\n    const app = new Hono()\n\n    app.use('*', async () => {})\n\n    app.notFound(() => {\n      throw new Error('Error in Not Found')\n    })\n\n    app.onError((err, c) => {\n      return c.text(err.message, 400)\n    })\n\n    it('Should handle the error thrown in `notFound()``', async () => {\n      const res = await app.request('http://localhost/')\n      expect(res.status).toBe(400)\n      expect(await res.text()).toBe('Error in Not Found')\n    })\n  })\n})\n\ndescribe('Request methods with custom middleware', () => {\n  const app = new Hono()\n\n  app.use('*', async (c, next) => {\n    const query = c.req.query('foo')\n\n    // @ts-ignore\n    const param = c.req.param('foo') // This will cause a type error.\n    const header = c.req.header('User-Agent')\n    await next()\n    c.header('X-Query-2', query ?? throwExpression('missing `X-Query-2` header'))\n    c.header('X-Param-2', param)\n    c.header('X-Header-2', header ?? throwExpression('missing `X-Header-2` header'))\n  })\n\n  app.get('/:foo', (c) => {\n    const query = c.req.query('foo')\n    const param = c.req.param('foo')\n    const header = c.req.header('User-Agent')\n    c.header('X-Query', query ?? throwExpression('missing `X-Query` header'))\n    c.header('X-Param', param)\n    c.header('X-Header', header ?? throwExpression('missing `X-Header` header'))\n    return c.body('Hono')\n  })\n\n  it('query', async () => {\n    const url = new URL('http://localhost/bar')\n    url.searchParams.append('foo', 'bar')\n    const req = new Request(url.toString())\n    req.headers.append('User-Agent', 'bar')\n    const res = await app.request(req)\n\n    expect(res.status).toBe(200)\n    expect(res.headers.get('X-Query')).toBe('bar')\n    expect(res.headers.get('X-Param')).toBe('bar')\n    expect(res.headers.get('X-Header')).toBe('bar')\n\n    expect(res.headers.get('X-Query-2')).toBe('bar')\n    expect(res.headers.get('X-Param-2')).toBe(null)\n    expect(res.headers.get('X-Header-2')).toBe('bar')\n  })\n})\n\ndescribe('Middleware + c.json(0, requestInit)', () => {\n  const app = new Hono()\n  app.use('/', async (c, next) => {\n    await next()\n  })\n  app.get('/', (c) => {\n    return c.json(0, {\n      status: 200,\n      headers: {\n        foo: 'bar',\n      },\n    })\n  })\n  it('Should return a correct headers', async () => {\n    const res = await app.request('/')\n    expect(res.headers.get('content-type')).toMatch(/^application\\/json/)\n    expect(res.headers.get('foo')).toBe('bar')\n  })\n})\n\ndescribe('Hono with `app.route`', () => {\n  describe('Basic', () => {\n    const app = new Hono()\n    const api = new Hono()\n    const middleware = new Hono()\n\n    api.use('*', async (c, next) => {\n      await next()\n      c.res.headers.append('x-custom-a', 'a')\n    })\n\n    api.get('/posts', (c) => c.text('List'))\n    api.post('/posts', (c) => c.text('Create'))\n    api.get('/posts/:id', (c) => c.text(`GET ${c.req.param('id')}`))\n\n    middleware.use('*', async (c, next) => {\n      await next()\n      c.res.headers.append('x-custom-b', 'b')\n    })\n\n    app.route('/api', middleware)\n    app.route('/api', api)\n\n    app.get('/foo', (c) => c.text('bar'))\n\n    it('Should return not found response', async () => {\n      const res = await app.request('http://localhost/')\n      expect(res.status).toBe(404)\n    })\n\n    it('Should return not found response', async () => {\n      const res = await app.request('http://localhost/posts')\n      expect(res.status).toBe(404)\n    })\n\n    test('GET /api/posts', async () => {\n      const res = await app.request('http://localhost/api/posts')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('List')\n    })\n\n    test('Custom header by middleware', async () => {\n      const res = await app.request('http://localhost/api/posts')\n      expect(res.status).toBe(200)\n      expect(res.headers.get('x-custom-a')).toBe('a')\n      expect(res.headers.get('x-custom-b')).toBe('b')\n    })\n\n    test('POST /api/posts', async () => {\n      const res = await app.request('http://localhost/api/posts', { method: 'POST' })\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('Create')\n    })\n\n    test('GET /api/posts/123', async () => {\n      const res = await app.request('http://localhost/api/posts/123')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('GET 123')\n    })\n\n    test('GET /foo', async () => {\n      const res = await app.request('http://localhost/foo')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('bar')\n    })\n\n    describe('With app.get(...handler)', () => {\n      const app = new Hono()\n      const about = new Hono()\n      about.get((c) => c.text('me'))\n      const subApp = new Hono()\n      subApp.route('/about', about)\n      app.route('/', subApp)\n\n      it('Should return 200 response - /about', async () => {\n        const res = await app.request('/about')\n        expect(res.status).toBe(200)\n        expect(await res.text()).toBe('me')\n      })\n\n      test('Should return 404 response /about/foo', async () => {\n        const res = await app.request('/about/foo')\n        expect(res.status).toBe(404)\n      })\n    })\n\n    describe('With app.get(...handler) and app.basePath()', () => {\n      const app = new Hono()\n      const about = new Hono().basePath('/about')\n      about.get((c) => c.text('me'))\n      app.route('/', about)\n\n      it('Should return 200 response - /about', async () => {\n        const res = await app.request('/about')\n        expect(res.status).toBe(200)\n        expect(await res.text()).toBe('me')\n      })\n\n      test('Should return 404 response /about/foo', async () => {\n        const res = await app.request('/about/foo')\n        expect(res.status).toBe(404)\n      })\n    })\n  })\n\n  describe('Chaining', () => {\n    const app = new Hono()\n    const route = new Hono()\n    route.get('/post', (c) => c.text('GET /POST v2')).post((c) => c.text('POST /POST v2'))\n    app.route('/v2', route)\n\n    it('Should return 200 response - GET /v2/post', async () => {\n      const res = await app.request('http://localhost/v2/post')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('GET /POST v2')\n    })\n\n    it('Should return 200 response - POST /v2/post', async () => {\n      const res = await app.request('http://localhost/v2/post', { method: 'POST' })\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('POST /POST v2')\n    })\n\n    it('Should return 404 response - DELETE /v2/post', async () => {\n      const res = await app.request('http://localhost/v2/post', { method: 'DELETE' })\n      expect(res.status).toBe(404)\n    })\n  })\n\n  describe('Nested', () => {\n    const app = new Hono()\n    const api = new Hono()\n    const book = new Hono()\n\n    book.get('/', (c) => c.text('list books'))\n    book.get('/:id', (c) => c.text(`book ${c.req.param('id')}`))\n\n    api.get('/', (c) => c.text('this is API'))\n    api.route('/book', book)\n\n    app.get('/', (c) => c.text('root'))\n    app.route('/v2', api)\n\n    it('Should return 200 response - GET /', async () => {\n      const res = await app.request('http://localhost/')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('root')\n    })\n\n    it('Should return 200 response - GET /v2', async () => {\n      const res = await app.request('http://localhost/v2')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('this is API')\n    })\n\n    it('Should return 200 response - GET /v2/book', async () => {\n      const res = await app.request('http://localhost/v2/book')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('list books')\n    })\n\n    it('Should return 200 response - GET /v2/book/123', async () => {\n      const res = await app.request('http://localhost/v2/book/123')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('book 123')\n    })\n  })\n\n  describe('onError', () => {\n    const app = new Hono()\n    const sub = new Hono()\n\n    app.use('*', async (c, next) => {\n      await next()\n      if (c.req.query('app-error')) {\n        throw new Error('This is Error')\n      }\n    })\n\n    app.onError((err, c) => {\n      return c.text('onError by app', 500)\n    })\n\n    sub.get('/posts/:id', async (c, next) => {\n      c.header('handler-chain', '1')\n      await next()\n    })\n\n    sub.get('/posts/:id', (c) => {\n      return c.text(`post: ${c.req.param('id')}`)\n    })\n\n    sub.get('/error', () => {\n      throw new Error('This is Error')\n    })\n\n    sub.onError((err, c) => {\n      return c.text('onError by sub', 500)\n    })\n\n    app.route('/sub', sub)\n\n    it('GET /posts/123 for sub', async () => {\n      const res = await app.request('https://example.com/sub/posts/123')\n      expect(res.status).toBe(200)\n      expect(res.headers.get('handler-chain')).toBe('1')\n      expect(await res.text()).toBe('post: 123')\n    })\n\n    it('should be handled by app', async () => {\n      const res = await app.request('https://example.com/sub/ok?app-error=1')\n      expect(res.status).toBe(500)\n      expect(await res.text()).toBe('onError by app')\n    })\n\n    it('should be handled by sub', async () => {\n      const res = await app.request('https://example.com/sub/error')\n      expect(res.status).toBe(500)\n      expect(await res.text()).toBe('onError by sub')\n    })\n  })\n\n  describe('onError for a single handler', () => {\n    const app = new Hono()\n    const sub = new Hono()\n\n    sub.get('/ok', (c) => c.text('OK'))\n\n    sub.get('/error', () => {\n      throw new Error('This is Error')\n    })\n\n    sub.onError((err, c) => {\n      return c.text('onError by sub', 500)\n    })\n\n    app.route('/sub', sub)\n\n    it('ok', async () => {\n      const res = await app.request('https://example.com/sub/ok')\n      expect(res.status).toBe(200)\n    })\n\n    it('error', async () => {\n      const res = await app.request('https://example.com/sub/error')\n      expect(res.status).toBe(500)\n      expect(await res.text()).toBe('onError by sub')\n    })\n  })\n\n  describe('notFound', () => {\n    const app = new Hono()\n    const sub = new Hono()\n\n    app.get('/explicit-404', async (c) => {\n      c.header('explicit', '1')\n    })\n\n    app.notFound((c) => {\n      return c.text('404 Not Found by app', 404)\n    })\n\n    sub.get('/ok', (c) => {\n      return c.text('ok')\n    })\n\n    sub.get('/explicit-404', async (c) => {\n      c.header('explicit', '1')\n    })\n\n    sub.notFound((c) => {\n      return c.text('404 Not Found by sub', 404)\n    })\n\n    app.route('/sub', sub)\n\n    it('/explicit-404 should be handled on app', async () => {\n      const res = await app.request('https://example.com/explicit-404')\n      expect(res.status).toBe(404)\n      expect(res.headers.get('explicit')).toBe('1')\n      expect(await res.text()).toBe('404 Not Found by app')\n    })\n\n    it('/sub/explicit-404 should be handled on app', async () => {\n      const res = await app.request('https://example.com/sub/explicit-404')\n      expect(res.status).toBe(404)\n      expect(res.headers.get('explicit')).toBe('1')\n      expect(await res.text()).toBe('404 Not Found by app')\n    })\n\n    it('/implicit-404 should be handled by app', async () => {\n      const res = await app.request('https://example.com/implicit-404')\n      expect(res.status).toBe(404)\n      expect(res.headers.get('explicit')).toBe(null)\n      expect(await res.text()).toBe('404 Not Found by app')\n    })\n\n    it('/sub/implicit-404 should be handled by sub', async () => {\n      const res = await app.request('https://example.com/sub/implicit-404')\n      expect(res.status).toBe(404)\n      expect(res.headers.get('explicit')).toBe(null)\n      expect(await res.text()).toBe('404 Not Found by app')\n    })\n  })\n})\n\ndescribe('Using other methods with `app.on`', () => {\n  it('Should handle PURGE method with RegExpRouter', async () => {\n    const app = new Hono({ router: new RegExpRouter() })\n\n    app.on('PURGE', '/purge', (c) => c.text('Accepted', 202))\n\n    const req = new Request('http://localhost/purge', {\n      method: 'PURGE',\n    })\n    const res = await app.request(req)\n    expect(res.status).toBe(202)\n    expect(await res.text()).toBe('Accepted')\n  })\n\n  it('Should handle PURGE method with TrieRouter', async () => {\n    const app = new Hono({ router: new TrieRouter() })\n\n    app.on('PURGE', '/purge', (c) => c.text('Accepted', 202))\n\n    const req = new Request('http://localhost/purge', {\n      method: 'PURGE',\n    })\n    const res = await app.request(req)\n    expect(res.status).toBe(202)\n    expect(await res.text()).toBe('Accepted')\n  })\n})\n\ndescribe('Multiple methods with `app.on`', () => {\n  const app = new Hono()\n  app.on(['PUT', 'DELETE'], '/posts/:id', (c) => {\n    return c.json({\n      postId: c.req.param('id'),\n      method: c.req.method,\n    })\n  })\n\n  it('Should return 200 with PUT', async () => {\n    const req = new Request('http://localhost/posts/123', {\n      method: 'PUT',\n    })\n    const res = await app.request(req)\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({\n      postId: '123',\n      method: 'PUT',\n    })\n  })\n\n  it('Should return 200 with DELETE', async () => {\n    const req = new Request('http://localhost/posts/123', {\n      method: 'DELETE',\n    })\n    const res = await app.request(req)\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({\n      postId: '123',\n      method: 'DELETE',\n    })\n  })\n\n  it('Should return 404 with POST', async () => {\n    const req = new Request('http://localhost/posts/123', {\n      method: 'POST',\n    })\n    const res = await app.request(req)\n    expect(res.status).toBe(404)\n  })\n})\n\ndescribe('Multiple paths with one handler', () => {\n  const app = new Hono()\n\n  const paths = ['/hello', '/ja/hello', '/en/hello']\n  app.on('GET', paths, (c) => {\n    return c.json({\n      path: c.req.path,\n      routePath: c.req.routePath,\n    })\n  })\n\n  it('Should handle multiple paths', async () => {\n    paths.map(async (path) => {\n      const res = await app.request(path)\n      expect(res.status).toBe(200)\n      const data = await res.json()\n      expect(data).toEqual({\n        path,\n        routePath: path,\n      })\n    })\n  })\n})\n\ndescribe('Multiple handler', () => {\n  describe('handler + handler', () => {\n    const app = new Hono()\n\n    app.get('/posts/:id', (c) => {\n      const id = c.req.param('id')\n      c.header('foo', 'bar')\n      return c.text(`id is ${id}`)\n    })\n\n    app.get('/:type/:id', (c) => {\n      c.status(404)\n      c.header('foo2', 'bar2')\n      return c.text('foo')\n    })\n    it('Should return response from `specialized` route', async () => {\n      const res = await app.request('http://localhost/posts/123')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('id is 123')\n      expect(res.headers.get('foo')).toBe('bar')\n      expect(res.headers.get('foo2')).toBeNull()\n    })\n  })\n\n  describe('Duplicate param name', () => {\n    describe('basic', () => {\n      const app = new Hono()\n      app.get('/:type/:url', (c) => {\n        return c.text(`type: ${c.req.param('type')}, url: ${c.req.param('url')}`)\n      })\n      app.get('/foo/:type/:url', (c) => {\n        return c.text(`foo type: ${c.req.param('type')}, url: ${c.req.param('url')}`)\n      })\n\n      it('Should return a correct param - GET /car/good-car', async () => {\n        const res = await app.request('/car/good-car')\n        expect(res.ok).toBe(true)\n        expect(await res.text()).toBe('type: car, url: good-car')\n      })\n      it('Should return a correct param - GET /foo/food/good-food', async () => {\n        const res = await app.request('/foo/food/good-food')\n        expect(res.ok).toBe(true)\n        expect(await res.text()).toBe('foo type: food, url: good-food')\n      })\n    })\n\n    describe('self', () => {\n      const app = new Hono()\n      app.get('/:id/:id', (c) => {\n        const id = c.req.param('id')\n        return c.text(`id is ${id}`)\n      })\n      it('Should return 123 - GET /123/456', async () => {\n        const res = await app.request('/123/456')\n        expect(res.status).toBe(200)\n        expect(await res.text()).toBe('id is 123')\n      })\n    })\n\n    describe('hierarchy', () => {\n      const app = new Hono()\n      app.get('/posts/:id/comments/:comment_id', (c) => {\n        return c.text(`post: ${c.req.param('id')}, comment: ${c.req.param('comment_id')}`)\n      })\n      app.get('/posts/:id', (c) => {\n        return c.text(`post: ${c.req.param('id')}`)\n      })\n      it('Should return a correct param - GET /posts/123/comments/456', async () => {\n        const res = await app.request('/posts/123/comments/456')\n        expect(res.status).toBe(200)\n        expect(await res.text()).toBe('post: 123, comment: 456')\n      })\n      it('Should return a correct param - GET /posts/789', async () => {\n        const res = await app.request('/posts/789')\n        expect(res.status).toBe(200)\n        expect(await res.text()).toBe('post: 789')\n      })\n    })\n\n    describe('different regular expression', () => {\n      const app = new Hono()\n      app.get('/:id/:action{create|update}', (c) => {\n        return c.text(`id: ${c.req.param('id')}, action: ${c.req.param('action')}`)\n      })\n      app.get('/:id/:action{delete}', (c) => {\n        return c.text(`id: ${c.req.param('id')}, action: ${c.req.param('action')}`)\n      })\n\n      it('Should return a correct param - GET /123/create', async () => {\n        const res = await app.request('/123/create')\n        expect(res.status).toBe(200)\n        expect(await res.text()).toBe('id: 123, action: create')\n      })\n      it('Should return a correct param - GET /456/update', async () => {\n        const res = await app.request('/467/update')\n        expect(res.status).toBe(200)\n        expect(await res.text()).toBe('id: 467, action: update')\n      })\n      it('Should return a correct param - GET /789/delete', async () => {\n        const res = await app.request('/789/delete')\n        expect(res.status).toBe(200)\n        expect(await res.text()).toBe('id: 789, action: delete')\n      })\n    })\n  })\n})\n\ndescribe('Multiple handler - async', () => {\n  describe('handler + handler', () => {\n    const app = new Hono()\n    app.get('/posts/:id', async (c) => {\n      await new Promise((resolve) => setTimeout(resolve, 1))\n      c.header('foo2', 'bar2')\n      const id = c.req.param('id')\n      return c.text(`id is ${id}`)\n    })\n    app.get('/:type/:id', async (c) => {\n      await new Promise((resolve) => setTimeout(resolve, 1))\n      c.header('foo', 'bar')\n      c.status(404)\n      return c.text('foo')\n    })\n\n    it('Should return response from `specialized` route', async () => {\n      const res = await app.request('http://localhost/posts/123')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('id is 123')\n      expect(res.headers.get('foo')).toBeNull()\n      expect(res.headers.get('foo2')).toBe('bar2')\n    })\n  })\n})\n\ndescribe('Lack returning response with a single handler', () => {\n  const app = new Hono()\n  // @ts-expect-error it should return Response to type it\n  app.get('/sync', () => {})\n  app.get('/async', async () => {})\n\n  it('Should return 404 response if lacking returning response', async () => {\n    const res = await app.request('/sync')\n    expect(res.status).toBe(404)\n  })\n\n  it('Should return 404 response if lacking returning response in an async handler', async () => {\n    const res = await app.request('/async')\n    expect(res.status).toBe(404)\n  })\n})\n\ndescribe('Context is not finalized', () => {\n  it('should throw error - lack `await next()`', async () => {\n    const app = new Hono()\n\n    // @ts-ignore\n    app.use('*', () => {})\n    app.get('/foo', (c) => {\n      return c.text('foo')\n    })\n    app.onError((err, c) => {\n      return c.text(err.message, 500)\n    })\n    const res = await app.request('http://localhost/foo')\n    expect(res.status).toBe(500)\n    expect(await res.text()).toMatch(/^Context is not finalized/)\n  })\n\n  it('should throw error - lack `returning Response`', async () => {\n    const app = new Hono()\n    app.use('*', async (_c, next) => {\n      await next()\n    })\n\n    // @ts-ignore\n    app.get('/foo', () => {})\n    app.onError((err, c) => {\n      return c.text(err.message, 500)\n    })\n    const res = await app.request('http://localhost/foo')\n    expect(res.status).toBe(500)\n    expect(await res.text()).toMatch(/^Context is not finalized/)\n  })\n})\n\ndescribe('Parse Body', () => {\n  const app = new Hono()\n\n  app.post('/json', async (c) => {\n    return c.json<{}, 200>(await c.req.parseBody(), 200)\n  })\n  app.post('/form', async (c) => {\n    return c.json<{}, 200>(await c.req.parseBody(), 200)\n  })\n\n  it('POST with JSON', async () => {\n    const req = new Request('http://localhost/json', {\n      method: 'POST',\n      body: JSON.stringify({ message: 'hello hono' }),\n      headers: new Headers({ 'Content-Type': 'application/json' }),\n    })\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n  })\n\n  it('POST with `multipart/form-data`', async () => {\n    const formData = new FormData()\n    formData.append('message', 'hello')\n    const req = new Request('https://localhost/form', {\n      method: 'POST',\n      body: formData,\n    })\n\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({ message: 'hello' })\n  })\n\n  it('POST with `application/x-www-form-urlencoded`', async () => {\n    const searchParam = new URLSearchParams()\n    searchParam.append('message', 'hello')\n    const req = new Request('https://localhost/form', {\n      method: 'POST',\n      body: searchParam,\n      headers: {\n        'Content-Type': 'application/x-www-form-urlencoded',\n      },\n    })\n\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({ message: 'hello' })\n  })\n})\n\ndescribe('Both two middleware returning response', () => {\n  it('Should return correct Content-Type`', async () => {\n    const app = new Hono()\n    app.use('*', async (c, next) => {\n      await next()\n      return c.html('Foo')\n    })\n    app.get('/', (c) => {\n      return c.text('Bar')\n    })\n    const res = await app.request('http://localhost/')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('Bar')\n    expect(res.headers.get('Content-Type')).toMatch(/^text\\/plain/)\n  })\n})\n\ndescribe('Count of logger called', () => {\n  // It will be added `2` each time the logger is called once.\n  let count = 0\n  let log = ''\n\n  const app = new Hono()\n\n  const logFn = (str: string) => {\n    count++\n    log = str\n  }\n\n  app.use('*', logger(logFn))\n  app.get('/', (c) => c.text('foo'))\n\n  it('Should be called two times', async () => {\n    const res = await app.request('http://localhost/not-found')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(404)\n    expect(await res.text()).toBe('404 Not Found')\n    expect(count).toBe(2)\n    expect(log).toMatch(/404/)\n  })\n\n  it('Should be called two times / Custom Not Found', async () => {\n    app.notFound((c) => c.text('Custom Not Found', 404))\n    const res = await app.request('http://localhost/custom-not-found')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(404)\n    expect(await res.text()).toBe('Custom Not Found')\n    expect(count).toBe(4)\n    expect(log).toMatch(/404/)\n  })\n})\n\ndescribe('Context set/get variables', () => {\n  type Variables = {\n    id: number\n    title: string\n  }\n\n  const app = new Hono<{ Variables: Variables }>()\n\n  it('Should set and get variables with correct types', async () => {\n    app.use('*', async (c, next) => {\n      c.set('id', 123)\n      c.set('title', 'Hello')\n      await next()\n    })\n    app.get('/', (c) => {\n      const id = c.get('id')\n      const title = c.get('title')\n      // type verifyID = Expect<Equal<number, typeof id>>\n      expectTypeOf(id).toEqualTypeOf<number>()\n      // type verifyTitle = Expect<Equal<string, typeof title>>\n      expectTypeOf(title).toEqualTypeOf<string>()\n      return c.text(`${id} is ${title}`)\n    })\n    const res = await app.request('http://localhost/')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('123 is Hello')\n  })\n})\n\ndescribe('Context binding variables', () => {\n  type Bindings = {\n    USER_ID: number\n    USER_NAME: string\n  }\n\n  const app = new Hono<{ Bindings: Bindings }>()\n\n  it('Should get binding variables with correct types', async () => {\n    app.get('/', (c) => {\n      expectTypeOf(c.env).toEqualTypeOf<Bindings>()\n      return c.text('These are verified')\n    })\n    const res = await app.request('http://localhost/')\n    expect(res.status).toBe(200)\n  })\n})\n\ndescribe('Handler as variables', () => {\n  const app = new Hono()\n\n  it('Should be typed correctly', async () => {\n    const handler: Handler = (c) => {\n      const id = c.req.param('id')\n      return c.text(`Post id is ${id}`)\n    }\n    app.get('/posts/:id', handler)\n\n    const res = await app.request('http://localhost/posts/123')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('Post id is 123')\n  })\n})\n\ndescribe('json', () => {\n  const api = new Hono()\n\n  api.get('/message', (c) => {\n    return c.json({\n      message: 'Hello',\n    })\n  })\n\n  api.get('/message-async', async (c) => {\n    return c.json({\n      message: 'Hello',\n    })\n  })\n\n  describe('Single handler', () => {\n    const app = new Hono()\n    app.route('/api', api)\n\n    it('Should return 200 response', async () => {\n      const res = await app.request('http://localhost/api/message')\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({\n        message: 'Hello',\n      })\n    })\n\n    it('Should return 200 response - with async', async () => {\n      const res = await app.request('http://localhost/api/message-async')\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({\n        message: 'Hello',\n      })\n    })\n  })\n\n  describe('With middleware', () => {\n    const app = new Hono()\n    app.use('*', async (_c, next) => {\n      await next()\n    })\n    app.route('/api', api)\n\n    it('Should return 200 response', async () => {\n      const res = await app.request('http://localhost/api/message')\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({\n        message: 'Hello',\n      })\n    })\n\n    it('Should return 200 response - with async', async () => {\n      const res = await app.request('http://localhost/api/message-async')\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({\n        message: 'Hello',\n      })\n    })\n  })\n})\n\ndescribe('Optional parameters', () => {\n  const app = new Hono()\n  app.get('/api/:version/animal/:type?', (c) => {\n    const type1 = c.req.param('type')\n    expectTypeOf(type1).toEqualTypeOf<string | undefined>()\n    const { type, version } = c.req.param()\n    expectTypeOf(version).toEqualTypeOf<string>()\n    expectTypeOf(type).toEqualTypeOf<string | undefined>()\n\n    return c.json({\n      type: type,\n    })\n  })\n\n  it('Should match with an optional parameter', async () => {\n    const res = await app.request('http://localhost/api/v1/animal/bird')\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({\n      type: 'bird',\n    })\n  })\n\n  it('Should match without an optional parameter', async () => {\n    const res = await app.request('http://localhost/api/v1/animal')\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({\n      type: undefined,\n    })\n  })\n\n  it('Should have a correct type with an optional parameter in a regexp path', async () => {\n    const app = new Hono()\n    app.get('/url/:url{.*}?', (c) => {\n      const url = c.req.param('url')\n      expectTypeOf(url).toEqualTypeOf<string | undefined>()\n      return c.json(0)\n    })\n  })\n})\n\ndescribe('app.mount()', () => {\n  describe('Basic', () => {\n    const anotherApp = (req: Request, ...params: unknown[]) => {\n      const path = getPath(req)\n      if (path === '/') {\n        return new Response('AnotherApp')\n      }\n      if (path === '/hello') {\n        return new Response('Hello from AnotherApp')\n      }\n      if (path === '/header') {\n        const message = req.headers.get('x-message')\n        return new Response(message)\n      }\n      if (path === '/with-query') {\n        const queryStrings = new URL(req.url).searchParams.toString()\n        return new Response(queryStrings)\n      }\n      if (path == '/with-params') {\n        return new Response(\n          JSON.stringify({\n            params,\n          }),\n          {\n            headers: {\n              'Content-Type': 'application.json',\n            },\n          }\n        )\n      }\n      if (path === '/undefined') {\n        return undefined as unknown as Response\n      }\n      return new Response('Not Found from AnotherApp', {\n        status: 404,\n      })\n    }\n\n    const app = new Hono()\n    app.use('*', async (c, next) => {\n      await next()\n      c.header('x-message', 'Foo')\n    })\n    app.get('/', (c) => c.text('Hono'))\n    app.notFound((c) => {\n      return c.text('Not Found from App', 404)\n    })\n\n    app.mount('/another-app', anotherApp, () => {\n      return 'params'\n    })\n    app.mount('/another-app-with-array-option', anotherApp, () => {\n      return ['param1', 'param2']\n    })\n    app.mount('/another-app2/sub-slash/', anotherApp)\n\n    const api = new Hono().basePath('/api')\n    api.mount('/another-app', anotherApp)\n\n    it('Should return responses from Hono app', async () => {\n      const res = await app.request('/')\n      expect(res.status).toBe(200)\n      expect(res.headers.get('x-message')).toBe('Foo')\n      expect(await res.text()).toBe('Hono')\n    })\n\n    it('Should return responses from AnotherApp', async () => {\n      let res = await app.request('/another-app')\n      expect(res.status).toBe(200)\n      expect(res.headers.get('x-message')).toBe('Foo')\n      expect(await res.text()).toBe('AnotherApp')\n\n      res = await app.request('/another-app/hello')\n      expect(res.status).toBe(200)\n      expect(res.headers.get('x-message')).toBe('Foo')\n      expect(await res.text()).toBe('Hello from AnotherApp')\n\n      const req = new Request('http://localhost/another-app/header', {\n        headers: {\n          'x-message': 'Message Foo!',\n        },\n      })\n      res = await app.request(req)\n      expect(res.status).toBe(200)\n      expect(res.headers.get('x-message')).toBe('Foo')\n      expect(await res.text()).toBe('Message Foo!')\n\n      res = await app.request('/another-app/not-found')\n      expect(res.status).toBe(404)\n      expect(res.headers.get('x-message')).toBe('Foo')\n      expect(await res.text()).toBe('Not Found from AnotherApp')\n\n      res = await app.request('/another-app/with-query?foo=bar&baz=qux')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('foo=bar&baz=qux')\n\n      res = await app.request('/another-app/with-params')\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({\n        params: ['params'],\n      })\n\n      res = await app.request('/another-app/undefined')\n      expect(res.status).toBe(404)\n      expect(await res.text()).toBe('Not Found from App')\n    })\n\n    it('Should return response from Another app with an array option', async () => {\n      const res = await app.request('/another-app-with-array-option/with-params')\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({\n        params: ['param1', 'param2'],\n      })\n    })\n\n    it('Should return responses from AnotherApp - sub + slash', async () => {\n      const res = await app.request('/another-app2/sub-slash')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('AnotherApp')\n    })\n\n    it('Should return responses from AnotherApp - with `basePath()`', async () => {\n      const res = await api.request('/api/another-app')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('AnotherApp')\n    })\n  })\n\n  describe('With fetch', () => {\n    const anotherApp = async (req: Request, env: {}, executionContext: ExecutionContext) => {\n      const path = getPath(req)\n      if (path === '/') {\n        return new Response(\n          JSON.stringify({\n            env,\n            executionContext,\n          }),\n          {\n            headers: {\n              'Content-Type': 'application/json',\n            },\n          }\n        )\n      }\n      return new Response('Not Found from AnotherApp', {\n        status: 404,\n      })\n    }\n\n    const app = new Hono()\n    app.mount('/another-app', anotherApp)\n\n    it('Should handle Env and ExecuteContext', async () => {\n      const request = new Request('http://localhost/another-app')\n      const res = await app.fetch(\n        request,\n        {\n          TOKEN: 'foo',\n        },\n        {\n          // Force mocking!\n\n          // @ts-ignore\n          waitUntil: 'waitUntil',\n\n          // @ts-ignore\n          passThroughOnException: 'passThroughOnException',\n        }\n      )\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({\n        env: {\n          TOKEN: 'foo',\n        },\n        executionContext: {\n          waitUntil: 'waitUntil',\n          passThroughOnException: 'passThroughOnException',\n        },\n      })\n    })\n  })\n\n  describe('Mount on `/`', () => {\n    const anotherApp = (req: Request, params: unknown) => {\n      const path = getPath(req)\n      if (path === '/') {\n        return new Response('AnotherApp')\n      }\n      if (path === '/hello') {\n        return new Response('Hello from AnotherApp')\n      }\n      if (path === '/good/night') {\n        return new Response('Good Night from AnotherApp')\n      }\n      return new Response('Not Found from AnotherApp', {\n        status: 404,\n      })\n    }\n\n    const app = new Hono()\n    app.mount('/', anotherApp)\n\n    it('Should return responses from AnotherApp - mount on `/`', async () => {\n      let res = await app.request('/')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('AnotherApp')\n      res = await app.request('/hello')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('Hello from AnotherApp')\n      res = await app.request('/good/night')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('Good Night from AnotherApp')\n      res = await app.request('/not-found')\n      expect(res.status).toBe(404)\n      expect(await res.text()).toBe('Not Found from AnotherApp')\n    })\n  })\n\n  describe('With replaceRequest option', () => {\n    const anotherApp = (req: Request) => {\n      const path = getPath(req)\n      if (path === '/app') {\n        return new Response(getPath(req))\n      }\n      return new Response(null, { status: 404 })\n    }\n\n    const app = new Hono()\n    app.mount('/app', anotherApp, {\n      replaceRequest: (req) => req,\n    })\n\n    it('Should return 200 response with the correct path', async () => {\n      const res = await app.request('/app')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('/app')\n    })\n  })\n\n  describe('With replaceRequest: false', () => {\n    const anotherApp = (req: Request) => {\n      const path = getPath(req)\n      if (path === '/app') {\n        return new Response(getPath(req))\n      }\n      return new Response(null, { status: 404 })\n    }\n\n    const app = new Hono()\n    app.mount('/app', anotherApp, { replaceRequest: false })\n\n    it('Should return 200 response with the correct path', async () => {\n      const res = await app.request('/app')\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('/app')\n    })\n  })\n})\n\ndescribe('HEAD method', () => {\n  const app = new Hono()\n\n  app.get('/page', (c) => {\n    c.header('X-Message', 'Foo')\n    c.header('X-Method', c.req.method)\n    return c.text('/page')\n  })\n\n  it('Should return 200 response with body - GET /page', async () => {\n    const res = await app.request('/page')\n    expect(res.status).toBe(200)\n    expect(res.headers.get('X-Message')).toBe('Foo')\n    expect(res.headers.get('X-Method')).toBe('GET')\n    expect(await res.text()).toBe('/page')\n  })\n\n  it('Should return 200 response without body - HEAD /page', async () => {\n    const req = new Request('http://localhost/page', {\n      method: 'HEAD',\n    })\n    const res = await app.request(req)\n    expect(res.status).toBe(200)\n    expect(res.headers.get('X-Message')).toBe('Foo')\n    expect(res.headers.get('X-Method')).toBe('HEAD')\n    expect(res.body).toBe(null)\n  })\n})\n\ndeclare module './context' {\n  interface ContextRenderer {\n    (content: string | Promise<string>, head: { title: string }): Response | Promise<Response>\n  }\n}\n\ndescribe('app.request()', () => {\n  it('Should return response with Request and RequestInit as args', async () => {\n    const app = new Hono()\n    app.get('/foo', (c) => {\n      return c.json(c.req.header('x-message'))\n    })\n    const req = new Request('http://localhost/foo')\n    const headers = new Headers()\n    headers.append('x-message', 'hello')\n    const res = await app.request(req, {\n      headers,\n    })\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('\"hello\"')\n  })\n})\n\ndescribe('app.fire()', () => {\n  it('Should call global.addEventListener', () => {\n    const app = new Hono()\n    const addEventListener = vi.fn()\n    global.addEventListener = addEventListener\n    app.fire()\n    expect(addEventListener).toHaveBeenCalledWith('fetch', expect.any(Function))\n\n    const fetchEventListener = addEventListener.mock.calls[0][1]\n    const respondWith = vi.fn()\n    const request = new Request('http://localhost')\n    fetchEventListener({ respondWith, request })\n    expect(respondWith).toHaveBeenCalledWith(expect.any(Promise))\n  })\n})\n\ndescribe('Context render and setRenderer', () => {\n  const app = new Hono()\n  app.get('/default', (c) => {\n    return c.render('<h1>content</h1>', { title: 'dummy ' })\n  })\n  app.use('/page', async (c, next) => {\n    c.setRenderer((content, head) => {\n      return new Response(\n        `<html><head><title>${head.title}</title></head><body><h1>${content}</h1></body></html>`\n      )\n    })\n    await next()\n  })\n  app.get('/page', (c) => {\n    return c.render('page content', {\n      title: 'page title',\n    })\n  })\n\n  it('Should return a Response from the default renderer', async () => {\n    const res = await app.request('/default')\n    expect(await res.text()).toBe('<h1>content</h1>')\n  })\n\n  it('Should return a Response from the custom renderer', async () => {\n    const res = await app.request('/page')\n    expect(await res.text()).toBe(\n      '<html><head><title>page title</title></head><body><h1>page content</h1></body></html>'\n    )\n  })\n})\n\ndescribe('c.var - with testing types', () => {\n  const app = new Hono<{\n    Bindings: {\n      Token: string\n    }\n  }>()\n\n  const mw =\n    (): MiddlewareHandler<{\n      Variables: {\n        echo: (str: string) => string\n      }\n    }> =>\n    async (c, next) => {\n      c.set('echo', (str) => str)\n      await next()\n    }\n\n  const mw2 =\n    (): MiddlewareHandler<{\n      Variables: {\n        echo2: (str: string) => string\n      }\n    }> =>\n    async (c, next) => {\n      c.set('echo2', (str) => str)\n      await next()\n    }\n\n  const mw3 =\n    (): MiddlewareHandler<{\n      Variables: {\n        echo3: (str: string) => string\n      }\n    }> =>\n    async (c, next) => {\n      c.set('echo3', (str) => str)\n      await next()\n    }\n\n  const mw4 =\n    (): MiddlewareHandler<{\n      Variables: {\n        echo4: (str: string) => string\n      }\n    }> =>\n    async (c, next) => {\n      c.set('echo4', (str) => str)\n      await next()\n    }\n\n  const mw5 =\n    (): MiddlewareHandler<{\n      Variables: {\n        echo5: (str: string) => string\n      }\n    }> =>\n    async (c, next) => {\n      c.set('echo5', (str) => str)\n      await next()\n    }\n\n  const mw6 =\n    (): MiddlewareHandler<{\n      Variables: {\n        echo6: (str: string) => string\n      }\n    }> =>\n    async (c, next) => {\n      c.set('echo6', (str) => str)\n      await next()\n    }\n\n  const mw7 =\n    (): MiddlewareHandler<{\n      Variables: {\n        echo7: (str: string) => string\n      }\n    }> =>\n    async (c, next) => {\n      c.set('echo7', (str) => str)\n      await next()\n    }\n\n  const mw8 =\n    (): MiddlewareHandler<{\n      Variables: {\n        echo8: (str: string) => string\n      }\n    }> =>\n    async (c, next) => {\n      c.set('echo8', (str) => str)\n      await next()\n    }\n\n  const mw9 =\n    (): MiddlewareHandler<{\n      Variables: {\n        echo9: (str: string) => string\n      }\n    }> =>\n    async (c, next) => {\n      c.set('echo9', (str) => str)\n      await next()\n    }\n\n  const mw10 =\n    (): MiddlewareHandler<{\n      Variables: {\n        echo10: (str: string) => string\n      }\n    }> =>\n    async (c, next) => {\n      c.set('echo10', (str) => str)\n      await next()\n    }\n\n  app.use('/no-path/1').get(mw(), (c) => {\n    return c.text(c.var.echo('hello'))\n  })\n\n  app.use('/no-path/2').get(mw(), mw2(), (c) => {\n    return c.text(c.var.echo('hello') + c.var.echo2('hello2'))\n  })\n\n  app.use('/no-path/3').get(mw(), mw2(), mw3(), (c) => {\n    return c.text(c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3'))\n  })\n\n  app.use('/no-path/4').get(mw(), mw2(), mw3(), mw4(), (c) => {\n    return c.text(\n      c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3') + c.var.echo4('hello4')\n    )\n  })\n\n  app.use('/no-path/5').get(mw(), mw2(), mw3(), mw4(), mw5(), (c) => {\n    return c.text(\n      c.var.echo('hello') +\n        c.var.echo2('hello2') +\n        c.var.echo3('hello3') +\n        c.var.echo4('hello4') +\n        c.var.echo5('hello5')\n    )\n  })\n\n  app.use('/no-path/6').get(mw(), mw2(), mw3(), mw4(), mw5(), mw6(), (c) => {\n    return c.text(\n      c.var.echo('hello') +\n        c.var.echo2('hello2') +\n        c.var.echo3('hello3') +\n        c.var.echo4('hello4') +\n        c.var.echo5('hello5') +\n        c.var.echo6('hello6')\n    )\n  })\n\n  app.use('/no-path/7').get(mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), (c) => {\n    return c.text(\n      c.var.echo('hello') +\n        c.var.echo2('hello2') +\n        c.var.echo3('hello3') +\n        c.var.echo4('hello4') +\n        c.var.echo5('hello5') +\n        c.var.echo6('hello6') +\n        c.var.echo7('hello7')\n    )\n  })\n\n  app.use('/no-path/8').get(mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), mw8(), (c) => {\n    return c.text(\n      c.var.echo('hello') +\n        c.var.echo2('hello2') +\n        c.var.echo3('hello3') +\n        c.var.echo4('hello4') +\n        c.var.echo5('hello5') +\n        c.var.echo6('hello6') +\n        c.var.echo7('hello7') +\n        c.var.echo8('hello8')\n    )\n  })\n\n  app.use('/no-path/9').get(mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), mw8(), mw9(), (c) => {\n    return c.text(\n      c.var.echo('hello') +\n        c.var.echo2('hello2') +\n        c.var.echo3('hello3') +\n        c.var.echo4('hello4') +\n        c.var.echo5('hello5') +\n        c.var.echo6('hello6') +\n        c.var.echo7('hello7') +\n        c.var.echo8('hello8') +\n        c.var.echo9('hello9')\n    )\n  })\n\n  app.use('/no-path/10').get(\n    // @ts-expect-error The handlers are more than 10\n    mw(),\n    mw2(),\n    mw3(),\n    mw4(),\n    mw5(),\n    mw6(),\n    mw7(),\n    mw8(),\n    mw9(),\n    mw10(),\n    (c) => {\n      return c.text(\n        // @ts-expect-error\n        c.var.echo('hello') +\n          c.var.echo2('hello2') +\n          c.var.echo3('hello3') +\n          c.var.echo4('hello4') +\n          c.var.echo5('hello5') +\n          c.var.echo6('hello6') +\n          c.var.echo7('hello7') +\n          c.var.echo8('hello8') +\n          c.var.echo9('hello9') +\n          c.var.echo10('hello10')\n      )\n    }\n  )\n\n  app.get('*', mw())\n\n  app.get('/path/1', mw(), (c) => {\n    return c.text(c.var.echo('hello'))\n  })\n\n  app.get('/path/2', mw(), mw2(), (c) => {\n    return c.text(c.var.echo('hello') + c.var.echo2('hello2'))\n  })\n\n  app.get('/path/3', mw(), mw2(), mw3(), (c) => {\n    return c.text(c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3'))\n  })\n\n  app.get('/path/4', mw(), mw2(), mw3(), mw4(), (c) => {\n    return c.text(\n      c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3') + c.var.echo4('hello4')\n    )\n  })\n\n  app.get('/path/5', mw(), mw2(), mw3(), mw4(), mw5(), (c) => {\n    return c.text(\n      c.var.echo('hello') +\n        c.var.echo2('hello2') +\n        c.var.echo3('hello3') +\n        c.var.echo4('hello4') +\n        c.var.echo5('hello5')\n    )\n  })\n\n  app.get('/path/6', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), (c) => {\n    return c.text(\n      c.var.echo('hello') +\n        c.var.echo2('hello2') +\n        c.var.echo3('hello3') +\n        c.var.echo4('hello4') +\n        c.var.echo5('hello5') +\n        c.var.echo6('hello6')\n    )\n  })\n\n  app.get('/path/7', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), (c) => {\n    return c.text(\n      c.var.echo('hello') +\n        c.var.echo2('hello2') +\n        c.var.echo3('hello3') +\n        c.var.echo4('hello4') +\n        c.var.echo5('hello5') +\n        c.var.echo6('hello6') +\n        c.var.echo7('hello7')\n    )\n  })\n\n  app.get('/path/8', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), mw8(), (c) => {\n    return c.text(\n      c.var.echo('hello') +\n        c.var.echo2('hello2') +\n        c.var.echo3('hello3') +\n        c.var.echo4('hello4') +\n        c.var.echo5('hello5') +\n        c.var.echo6('hello6') +\n        c.var.echo7('hello7') +\n        c.var.echo8('hello8')\n    )\n  })\n\n  app.get('/path/9', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), mw8(), mw9(), (c) => {\n    return c.text(\n      c.var.echo('hello') +\n        c.var.echo2('hello2') +\n        c.var.echo3('hello3') +\n        c.var.echo4('hello4') +\n        c.var.echo5('hello5') +\n        c.var.echo6('hello6') +\n        c.var.echo7('hello7') +\n        c.var.echo8('hello8') +\n        c.var.echo9('hello9')\n    )\n  })\n\n  // @ts-expect-error\n  app.get('/path/10', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), mw8(), mw9(), mw10(), (c) => {\n    return c.text(\n      // @ts-expect-error\n      c.var.echo('hello') +\n        // @ts-expect-error\n        c.var.echo2('hello2') +\n        // @ts-expect-error\n        c.var.echo3('hello3') +\n        // @ts-expect-error\n        c.var.echo4('hello4') +\n        // @ts-expect-error\n        c.var.echo5('hello5') +\n        // @ts-expect-error\n        c.var.echo6('hello6') +\n        // @ts-expect-error\n        c.var.echo7('hello7') +\n        // @ts-expect-error\n        c.var.echo8('hello8') +\n        // @ts-expect-error\n        c.var.echo9('hello9') +\n        // @ts-expect-error\n        c.var.echo10('hello10')\n    )\n  })\n\n  app.on('GET', '/on/1', mw(), (c) => {\n    return c.text(c.var.echo('hello'))\n  })\n\n  app.on('GET', '/on/2', mw(), mw2(), (c) => {\n    return c.text(c.var.echo('hello') + c.var.echo2('hello2'))\n  })\n\n  app.on('GET', '/on/3', mw(), mw2(), mw3(), (c) => {\n    return c.text(c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3'))\n  })\n\n  app.on('GET', '/on/4', mw(), mw2(), mw3(), mw4(), (c) => {\n    return c.text(\n      c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3') + c.var.echo4('hello4')\n    )\n  })\n\n  app.on('GET', '/on/5', mw(), mw2(), mw3(), mw4(), mw5(), (c) => {\n    return c.text(\n      c.var.echo('hello') +\n        c.var.echo2('hello2') +\n        c.var.echo3('hello3') +\n        c.var.echo4('hello4') +\n        c.var.echo5('hello5')\n    )\n  })\n\n  app.on('GET', '/on/6', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), (c) => {\n    return c.text(\n      c.var.echo('hello') +\n        c.var.echo2('hello2') +\n        c.var.echo3('hello3') +\n        c.var.echo4('hello4') +\n        c.var.echo5('hello5') +\n        c.var.echo6('hello6')\n    )\n  })\n\n  app.on('GET', '/on/7', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), (c) => {\n    return c.text(\n      c.var.echo('hello') +\n        c.var.echo2('hello2') +\n        c.var.echo3('hello3') +\n        c.var.echo4('hello4') +\n        c.var.echo5('hello5') +\n        c.var.echo6('hello6') +\n        c.var.echo7('hello7')\n    )\n  })\n\n  app.on('GET', '/on/8', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), mw8(), (c) => {\n    return c.text(\n      c.var.echo('hello') +\n        c.var.echo2('hello2') +\n        c.var.echo3('hello3') +\n        c.var.echo4('hello4') +\n        c.var.echo5('hello5') +\n        c.var.echo6('hello6') +\n        c.var.echo7('hello7') +\n        c.var.echo8('hello8')\n    )\n  })\n\n  app.on('GET', '/on/9', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), mw8(), mw9(), (c) => {\n    return c.text(\n      c.var.echo('hello') +\n        c.var.echo2('hello2') +\n        c.var.echo3('hello3') +\n        c.var.echo4('hello4') +\n        c.var.echo5('hello5') +\n        c.var.echo6('hello6') +\n        c.var.echo7('hello7') +\n        c.var.echo8('hello8') +\n        c.var.echo9('hello9')\n    )\n  })\n\n  // @ts-expect-error\n  app.on(\n    'GET',\n    '/on/10',\n    mw(),\n    mw2(),\n    mw3(),\n    mw4(),\n    mw5(),\n    mw6(),\n    mw7(),\n    mw8(),\n    mw9(),\n    mw10(),\n    (c) => {\n      return c.text(\n        // @ts-expect-error\n        c.var.echo('hello') +\n          // @ts-expect-error\n          c.var.echo2('hello2') +\n          // @ts-expect-error\n          c.var.echo3('hello3') +\n          // @ts-expect-error\n          c.var.echo4('hello4') +\n          // @ts-expect-error\n          c.var.echo5('hello5') +\n          // @ts-expect-error\n          c.var.echo6('hello6') +\n          // @ts-expect-error\n          c.var.echo7('hello7') +\n          // @ts-expect-error\n          c.var.echo8('hello8') +\n          // @ts-expect-error\n          c.var.echo9('hello9') +\n          // @ts-expect-error\n          c.var.echo10('hello10')\n      )\n    }\n  )\n\n  app.on(['GET', 'POST'], '/on/1', mw(), (c) => {\n    return c.text(c.var.echo('hello'))\n  })\n\n  app.on(['GET', 'POST'], '/on/2', mw(), mw2(), (c) => {\n    return c.text(c.var.echo('hello') + c.var.echo2('hello2'))\n  })\n\n  app.on(['GET', 'POST'], '/on/3', mw(), mw2(), mw3(), (c) => {\n    return c.text(c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3'))\n  })\n\n  app.on(['GET', 'POST'], '/on/4', mw(), mw2(), mw3(), mw4(), (c) => {\n    return c.text(\n      c.var.echo('hello') + c.var.echo2('hello2') + c.var.echo3('hello3') + c.var.echo4('hello4')\n    )\n  })\n\n  app.on(['GET', 'POST'], '/on/5', mw(), mw2(), mw3(), mw4(), mw5(), (c) => {\n    return c.text(\n      c.var.echo('hello') +\n        c.var.echo2('hello2') +\n        c.var.echo3('hello3') +\n        c.var.echo4('hello4') +\n        c.var.echo5('hello5')\n    )\n  })\n\n  app.on(['GET', 'POST'], '/on/6', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), (c) => {\n    return c.text(\n      c.var.echo('hello') +\n        c.var.echo2('hello2') +\n        c.var.echo3('hello3') +\n        c.var.echo4('hello4') +\n        c.var.echo5('hello5') +\n        c.var.echo6('hello6')\n    )\n  })\n\n  app.on(['GET', 'POST'], '/on/7', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), (c) => {\n    return c.text(\n      c.var.echo('hello') +\n        c.var.echo2('hello2') +\n        c.var.echo3('hello3') +\n        c.var.echo4('hello4') +\n        c.var.echo5('hello5') +\n        c.var.echo6('hello6') +\n        c.var.echo7('hello7')\n    )\n  })\n\n  app.on(['GET', 'POST'], '/on/8', mw(), mw2(), mw3(), mw4(), mw5(), mw6(), mw7(), mw8(), (c) => {\n    return c.text(\n      c.var.echo('hello') +\n        c.var.echo2('hello2') +\n        c.var.echo3('hello3') +\n        c.var.echo4('hello4') +\n        c.var.echo5('hello5') +\n        c.var.echo6('hello6') +\n        c.var.echo7('hello7') +\n        c.var.echo8('hello8')\n    )\n  })\n\n  app.on(\n    ['GET', 'POST'],\n    '/on/9',\n    mw(),\n    mw2(),\n    mw3(),\n    mw4(),\n    mw5(),\n    mw6(),\n    mw7(),\n    mw8(),\n    mw9(),\n    (c) => {\n      return c.text(\n        c.var.echo('hello') +\n          c.var.echo2('hello2') +\n          c.var.echo3('hello3') +\n          c.var.echo4('hello4') +\n          c.var.echo5('hello5') +\n          c.var.echo6('hello6') +\n          c.var.echo7('hello7') +\n          c.var.echo8('hello8') +\n          c.var.echo9('hello9')\n      )\n    }\n  )\n\n  // @ts-expect-error\n  app.on(\n    ['GET', 'POST'],\n    '/on/10',\n    mw(),\n    mw2(),\n    mw3(),\n    mw4(),\n    mw5(),\n    mw6(),\n    mw7(),\n    mw8(),\n    mw9(),\n    mw10(),\n    (c) => {\n      return c.text(\n        // @ts-expect-error\n        c.var.echo('hello') +\n          // @ts-expect-error\n          c.var.echo2('hello2') +\n          // @ts-expect-error\n          c.var.echo3('hello3') +\n          // @ts-expect-error\n          c.var.echo4('hello4') +\n          // @ts-expect-error\n          c.var.echo5('hello5') +\n          // @ts-expect-error\n          c.var.echo6('hello6') +\n          // @ts-expect-error\n          c.var.echo7('hello7') +\n          // @ts-expect-error\n          c.var.echo8('hello8') +\n          // @ts-expect-error\n          c.var.echo9('hello9') +\n          // @ts-expect-error\n          c.var.echo10('hello10')\n      )\n    }\n  )\n\n  it('Should return the correct response - no-path', async () => {\n    let res = await app.request('/no-path/1')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hello')\n\n    res = await app.request('/no-path/2')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hellohello2')\n\n    res = await app.request('/no-path/3')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hellohello2hello3')\n\n    res = await app.request('/no-path/4')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hellohello2hello3hello4')\n\n    res = await app.request('/no-path/5')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hellohello2hello3hello4hello5')\n  })\n\n  it('Should return the correct response - path', async () => {\n    let res = await app.request('/path/1')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hello')\n\n    res = await app.request('/path/2')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hellohello2')\n\n    res = await app.request('/path/3')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hellohello2hello3')\n\n    res = await app.request('/path/4')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hellohello2hello3hello4')\n\n    res = await app.request('/path/5')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hellohello2hello3hello4hello5')\n  })\n\n  it('Should return the correct response - on', async () => {\n    let res = await app.request('/on/1')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hello')\n\n    res = await app.request('/on/2')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hellohello2')\n\n    res = await app.request('/on/3')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hellohello2hello3')\n\n    res = await app.request('/on/4')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hellohello2hello3hello4')\n\n    res = await app.request('/on/5')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('hellohello2hello3hello4hello5')\n  })\n\n  it('Should not throw type errors', () => {\n    const app = new Hono<{\n      Variables: {\n        hello: () => string\n      }\n    }>()\n\n    app.get(mw())\n    app.get(mw(), mw2())\n    app.get(mw(), mw2(), mw3())\n    app.get(mw(), mw2(), mw3(), mw4())\n    app.get(mw(), mw2(), mw3(), mw4(), mw5())\n\n    app.get('/', mw())\n    app.get('/', mw(), mw2())\n    app.get('/', mw(), mw2(), mw3())\n    app.get('/', mw(), mw2(), mw3(), mw4())\n    app.get('/', mw(), mw2(), mw3(), mw4(), mw5())\n  })\n\n  it('Should be a read-only', () => {\n    expect(() => {\n      app.get('/path/1', mw(), (c) => {\n        // @ts-expect-error\n        c.var.echo = 'hello'\n        return c.text(c.var.echo('hello'))\n      })\n    }).toThrow()\n  })\n\n  it('Should not throw a type error', (c) => {\n    const app = new Hono<{\n      Bindings: {\n        TOKEN: string\n      }\n    }>()\n\n    app.get('/', poweredBy(), async (c) => {\n      expectTypeOf(c.env.TOKEN).toEqualTypeOf<string>()\n    })\n\n    app.get('/', async (c, next) => {\n      expectTypeOf(c.env.TOKEN).toEqualTypeOf<string>()\n      const mw = poweredBy()\n      await mw(c, next)\n    })\n\n    app.use(mw())\n    app.use('*', mw())\n\n    const route = app.get('/posts', mw(), (c) => c.json(0))\n    const client = hc<typeof route>('/')\n    type key = keyof typeof client\n    type verify = Expect<Equal<'posts', key>>\n  })\n\n  it('Should throw type errors', (c) => {\n    try {\n      // @ts-expect-error\n      app.get(['foo', 'bar'], poweredBy())\n      // @ts-expect-error\n      app.use(['foo', 'bar'], poweredBy())\n    } catch {}\n  })\n})\n\ndescribe('Compatible with extended Hono classes, such Zod OpenAPI Hono.', () => {\n  class ExtendedHono extends Hono {\n    // @ts-ignore\n    route(path: string, app?: Hono) {\n      // @ts-ignore\n      super.route(path, app)\n      return this\n    }\n    // @ts-ignore\n    basePath(path: string) {\n      return new ExtendedHono(super.basePath(path))\n    }\n  }\n  const a = new ExtendedHono()\n  const sub = new Hono()\n  sub.get('/foo', (c) => c.text('foo'))\n  a.route('/sub', sub)\n\n  it('Should return 200 response', async () => {\n    const res = await a.request('/sub/foo')\n    expect(res.status).toBe(200)\n  })\n})\n\ndescribe('Generics for Bindings and Variables', () => {\n  interface CloudflareBindings {\n    MY_VARIABLE: string\n  }\n\n  it('Should not throw type errors', () => {\n    // @ts-expect-error Bindings should extend object\n    new Hono<{\n      Bindings: number\n    }>()\n\n    const appWithInterface = new Hono<{\n      Bindings: CloudflareBindings\n    }>()\n\n    appWithInterface.get('/', (c) => {\n      expectTypeOf(c.env.MY_VARIABLE).toMatchTypeOf<string>()\n      return c.text('/')\n    })\n\n    const appWithType = new Hono<{\n      Bindings: {\n        foo: string\n      }\n    }>()\n\n    appWithType.get('/', (c) => {\n      expectTypeOf(c.env.foo).toMatchTypeOf<string>()\n      return c.text('Hello Hono!')\n    })\n  })\n})\n\ndescribe('app.basePath() with the internal #clone()', () => {\n  const app = new Hono()\n    .notFound((c) => {\n      return c.text('Custom not found', 404)\n    })\n    .onError((error, c) => {\n      return c.text(`Custom error \"${error.message}\"`, 500)\n    })\n    .basePath('/api')\n    .get('/test', async () => {\n      throw new Error('API Test error')\n    })\n\n  it('Should return the custom not found', async () => {\n    const res = await app.request('/api/not-found')\n    expect(res.status).toBe(404)\n    expect(await res.text()).toBe('Custom not found')\n  })\n\n  it('Should return the custom error', async () => {\n    const res = await app.request('/api/test')\n    expect(res.status).toBe(500)\n    expect(await res.text()).toBe('Custom error \"API Test error\"')\n  })\n})\n\ndescribe('Catch-all route with empty segment', () => {\n  it('Should return empty string for empty catch-all param', async () => {\n    const app = new Hono()\n    app.get('/:remaining{.*}', (c) => {\n      const remaining = c.req.param('remaining')\n      return c.json({ type: typeof remaining, value: remaining })\n    })\n    const res = await app.request('http://localhost/')\n    expect(res.status).toBe(200)\n    const json = await res.json()\n    expect(json).toEqual({ type: 'string', value: '' })\n  })\n})\n"
  },
  {
    "path": "src/hono.ts",
    "content": "import { HonoBase } from './hono-base'\nimport type { HonoOptions } from './hono-base'\nimport { RegExpRouter } from './router/reg-exp-router'\nimport { SmartRouter } from './router/smart-router'\nimport { TrieRouter } from './router/trie-router'\nimport type { BlankEnv, BlankSchema, Env, Schema } from './types'\n\n/**\n * The Hono class extends the functionality of the HonoBase class.\n * It sets up routing and allows for custom options to be passed.\n *\n * @template E - The environment type.\n * @template S - The schema type.\n * @template BasePath - The base path type.\n */\nexport class Hono<\n  E extends Env = BlankEnv,\n  S extends Schema = BlankSchema,\n  BasePath extends string = '/',\n> extends HonoBase<E, S, BasePath> {\n  /**\n   * Creates an instance of the Hono class.\n   *\n   * @param options - Optional configuration options for the Hono instance.\n   */\n  constructor(options: HonoOptions<E> = {}) {\n    super(options)\n    this.router =\n      options.router ??\n      new SmartRouter({\n        routers: [new RegExpRouter(), new TrieRouter()],\n      })\n  }\n}\n"
  },
  {
    "path": "src/http-exception.test.ts",
    "content": "import { HTTPException } from './http-exception'\n\ndescribe('HTTPException', () => {\n  it('Should be 401 HTTP exception object', async () => {\n    // We should throw an exception if is not authorized\n    // because next handlers should not be fired.\n    const exception = new HTTPException(401, {\n      message: 'Unauthorized',\n    })\n    const res = exception.getResponse()\n\n    expect(res.status).toBe(401)\n    expect(await res.text()).toBe('Unauthorized')\n    expect(exception.status).toBe(401)\n    expect(exception.message).toBe('Unauthorized')\n  })\n\n  it('Should be accessible to the object causing the exception', async () => {\n    // We should pass the cause of the error to the cause option\n    // because it makes debugging easier.\n    const error = new Error('Server Error')\n    const exception = new HTTPException(500, {\n      message: 'Internal Server Error',\n      cause: error,\n    })\n    const res = exception.getResponse()\n\n    expect(res.status).toBe(500)\n    expect(await res.text()).toBe('Internal Server Error')\n    expect(exception.status).toBe(500)\n    expect(exception.message).toBe('Internal Server Error')\n    expect(exception.cause).toBe(error)\n  })\n\n  it('Should prioritize the status code over the code in the response', async () => {\n    const exception = new HTTPException(400, {\n      res: new Response('An exception', {\n        status: 200,\n      }),\n    })\n    const res = exception.getResponse()\n    expect(res.status).toBe(400)\n    expect(await res.text()).toBe('An exception')\n  })\n})\n"
  },
  {
    "path": "src/http-exception.ts",
    "content": "/**\n * @module\n * This module provides the `HTTPException` class.\n */\n\nimport type { ContentfulStatusCode } from './utils/http-status'\n\n/**\n * Options for creating an `HTTPException`.\n * @property res - Optional response object to use.\n * @property message - Optional custom error message.\n * @property cause - Optional cause of the error.\n */\ntype HTTPExceptionOptions = {\n  res?: Response\n  message?: string\n  cause?: unknown\n}\n\n/**\n * `HTTPException` must be used when a fatal error such as authentication failure occurs.\n *\n * @see {@link https://hono.dev/docs/api/exception}\n *\n * @param {StatusCode} status - status code of HTTPException\n * @param {HTTPExceptionOptions} options - options of HTTPException\n * @param {HTTPExceptionOptions[\"res\"]} options.res - response of options of HTTPException\n * @param {HTTPExceptionOptions[\"message\"]} options.message - message of options of HTTPException\n * @param {HTTPExceptionOptions[\"cause\"]} options.cause - cause of options of HTTPException\n *\n * @example\n * ```ts\n * import { HTTPException } from 'hono/http-exception'\n *\n * // ...\n *\n * app.post('/auth', async (c, next) => {\n *   // authentication\n *   if (authorized === false) {\n *     throw new HTTPException(401, { message: 'Custom error message' })\n *   }\n *   await next()\n * })\n * ```\n */\nexport class HTTPException extends Error {\n  readonly res?: Response\n  readonly status: ContentfulStatusCode\n\n  /**\n   * Creates an instance of `HTTPException`.\n   * @param status - HTTP status code for the exception. Defaults to 500.\n   * @param options - Additional options for the exception.\n   */\n  constructor(status: ContentfulStatusCode = 500, options?: HTTPExceptionOptions) {\n    super(options?.message, { cause: options?.cause })\n    this.res = options?.res\n    this.status = status\n  }\n\n  /**\n   * Returns the response object associated with the exception.\n   * If a response object is not provided, a new response is created with the error message and status code.\n   * @returns The response object.\n   */\n  getResponse(): Response {\n    if (this.res) {\n      const newResponse = new Response(this.res.body, {\n        status: this.status,\n        headers: this.res.headers,\n      })\n      return newResponse\n    }\n    return new Response(this.message, {\n      status: this.status,\n    })\n  }\n}\n"
  },
  {
    "path": "src/index.ts",
    "content": "/**\n * @module\n *\n * Hono - Web Framework built on Web Standards\n *\n * @example\n * ```ts\n * import { Hono } from 'hono'\n * const app = new Hono()\n *\n * app.get('/', (c) => c.text('Hono!'))\n *\n * export default app\n * ```\n */\n\nimport { Hono } from './hono'\n\n/**\n * Types for environment variables, error handlers, handlers, middleware handlers, and more.\n */\nexport type {\n  Env,\n  ErrorHandler,\n  Handler,\n  MiddlewareHandler,\n  Next,\n  NotFoundResponse,\n  NotFoundHandler,\n  ValidationTargets,\n  Input,\n  Schema,\n  ToSchema,\n  TypedResponse,\n} from './types'\n/**\n * Types for context, context variable map, context renderer, and execution context.\n */\nexport type { Context, ContextVariableMap, ContextRenderer, ExecutionContext } from './context'\n/**\n * Type for HonoRequest.\n */\nexport type { HonoRequest } from './request'\n/**\n * Types for inferring request and response types and client request options.\n */\nexport type { InferRequestType, InferResponseType, ClientRequestOptions } from './client'\n\n/**\n * Hono framework for building web applications.\n */\nexport { Hono }\n"
  },
  {
    "path": "src/jsx/base.test.tsx",
    "content": "/** @jsxImportSource ./ */\n\nimport type { JSXNode } from './base'\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nimport { cloneElement, jsx, Fragment } from './base'\n\ndescribe('cloneElement', () => {\n  it('should clone an element with new props', () => {\n    const element = <div className='original'>Hello</div>\n    const clonedElement = cloneElement(element, { className: 'cloned' })\n    expect((clonedElement as unknown as JSXNode).props.className).toBe('cloned')\n    expect((clonedElement as unknown as JSXNode).props.children).toBe('Hello')\n  })\n\n  it('should clone a children element', () => {\n    const fnElement = ({ message }: { message: string }) => <div>{message}</div>\n    const element = fnElement({ message: 'Hello' })\n    const clonedElement = cloneElement(element, {})\n    expect(element.toString()).toBe('<div>Hello</div>')\n    expect(clonedElement.toString()).toBe('<div>Hello</div>')\n  })\n\n  it('should clone an element with new children', () => {\n    const fnElement = ({ message }: { message: string }) => <div>{message}</div>\n    const element = fnElement({ message: 'Hello' })\n    const clonedElement = cloneElement(element, {}, 'World')\n    expect(element.toString()).toBe('<div>Hello</div>')\n    expect(clonedElement.toString()).toBe('<div>World</div>')\n  })\n\n  it('should self-close a wrapped empty tag', () => {\n    const Hr = ({ ...props }) => <hr {...props} />\n    const element = <Hr />\n    expect(element.toString()).toBe('<hr/>')\n  })\n})\n"
  },
  {
    "path": "src/jsx/base.ts",
    "content": "import { raw } from '../helper/html'\nimport { escapeToBuffer, resolveCallbackSync, stringBufferToString } from '../utils/html'\nimport type { HtmlEscaped, HtmlEscapedString, StringBufferWithCallbacks } from '../utils/html'\nimport { DOM_RENDERER, DOM_MEMO } from './constants'\nimport type { Context } from './context'\nimport { createContext, globalContexts, useContext } from './context'\nimport { domRenderers } from './intrinsic-element/common'\nimport * as intrinsicElementTags from './intrinsic-element/components'\nimport type {\n  JSX as HonoJSX,\n  IntrinsicElements as IntrinsicElementsDefined,\n} from './intrinsic-elements'\nimport { normalizeIntrinsicElementKey, styleObjectForEach } from './utils'\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type Props = Record<string, any>\nexport type FC<P = Props> = {\n  (props: P): HtmlEscapedString | Promise<HtmlEscapedString> | null\n  defaultProps?: Partial<P> | undefined\n  displayName?: string | undefined\n}\nexport type DOMAttributes = HonoJSX.HTMLAttributes\n\n// eslint-disable-next-line @typescript-eslint/no-namespace\nexport namespace JSX {\n  export type Element = HtmlEscapedString | Promise<HtmlEscapedString>\n  export interface ElementChildrenAttribute {\n    children: Child\n  }\n  export interface IntrinsicElements extends IntrinsicElementsDefined {\n    [tagName: string]: Props\n  }\n  export interface IntrinsicAttributes {\n    key?: string | number | bigint | null | undefined\n  }\n}\n\nlet nameSpaceContext: Context<string> | undefined = undefined\nexport const getNameSpaceContext = () => nameSpaceContext\n\nconst toSVGAttributeName = (key: string): string =>\n  /[A-Z]/.test(key) &&\n  // Presentation attributes are findable in style object. \"clip-path\", \"font-size\", \"stroke-width\", etc.\n  // Or other un-deprecated kebab-case attributes. \"overline-position\", \"paint-order\", \"strikethrough-position\", etc.\n  key.match(\n    /^(?:al|basel|clip(?:Path|Rule)$|co|do|fill|fl|fo|gl|let|lig|i|marker[EMS]|o|pai|pointe|sh|st[or]|text[^L]|tr|u|ve|w)/\n  )\n    ? key.replace(/([A-Z])/g, '-$1').toLowerCase()\n    : key\n\nconst emptyTags = [\n  'area',\n  'base',\n  'br',\n  'col',\n  'embed',\n  'hr',\n  'img',\n  'input',\n  'keygen',\n  'link',\n  'meta',\n  'param',\n  'source',\n  'track',\n  'wbr',\n]\nexport const booleanAttributes = [\n  'allowfullscreen',\n  'async',\n  'autofocus',\n  'autoplay',\n  'checked',\n  'controls',\n  'default',\n  'defer',\n  'disabled',\n  'download',\n  'formnovalidate',\n  'hidden',\n  'inert',\n  'ismap',\n  'itemscope',\n  'loop',\n  'multiple',\n  'muted',\n  'nomodule',\n  'novalidate',\n  'open',\n  'playsinline',\n  'readonly',\n  'required',\n  'reversed',\n  'selected',\n]\n\nconst childrenToStringToBuffer = (children: Child[], buffer: StringBufferWithCallbacks): void => {\n  for (let i = 0, len = children.length; i < len; i++) {\n    const child = children[i]\n    if (typeof child === 'string') {\n      escapeToBuffer(child, buffer)\n    } else if (typeof child === 'boolean' || child === null || child === undefined) {\n      continue\n    } else if (child instanceof JSXNode) {\n      child.toStringToBuffer(buffer)\n    } else if (\n      typeof child === 'number' ||\n      (child as unknown as { isEscaped: boolean }).isEscaped\n    ) {\n      ;(buffer[0] as string) += child\n    } else if (child instanceof Promise) {\n      buffer.unshift('', child)\n    } else {\n      // `child` type is `Child[]`, so stringify recursively\n      childrenToStringToBuffer(child, buffer)\n    }\n  }\n}\n\ntype LocalContexts = [Context<unknown>, unknown][]\nexport type Child =\n  | string\n  | Promise<string>\n  | number\n  | JSXNode\n  | null\n  | undefined\n  | boolean\n  | Child[]\nexport class JSXNode implements HtmlEscaped {\n  tag: string | Function\n  props: Props\n  key?: string\n  children: Child[]\n  isEscaped: true = true as const\n  localContexts?: LocalContexts\n  constructor(tag: string | Function, props: Props, children: Child[]) {\n    this.tag = tag\n    this.props = props\n    this.children = children\n  }\n\n  get type(): string | Function {\n    return this.tag as string\n  }\n\n  // Added for compatibility with libraries that rely on React's internal structure\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  get ref(): any {\n    return this.props.ref || null\n  }\n\n  toString(): string | Promise<string> {\n    const buffer: StringBufferWithCallbacks = [''] as StringBufferWithCallbacks\n    this.localContexts?.forEach(([context, value]) => {\n      context.values.push(value)\n    })\n    try {\n      this.toStringToBuffer(buffer)\n    } finally {\n      this.localContexts?.forEach(([context]) => {\n        context.values.pop()\n      })\n    }\n    return buffer.length === 1\n      ? 'callbacks' in buffer\n        ? resolveCallbackSync(raw(buffer[0], buffer.callbacks)).toString()\n        : buffer[0]\n      : stringBufferToString(buffer, buffer.callbacks)\n  }\n\n  toStringToBuffer(buffer: StringBufferWithCallbacks): void {\n    const tag = this.tag as string\n    const props = this.props\n    let { children } = this\n\n    buffer[0] += `<${tag}`\n\n    const normalizeKey: (key: string) => string =\n      nameSpaceContext && useContext(nameSpaceContext) === 'svg'\n        ? (key) => toSVGAttributeName(normalizeIntrinsicElementKey(key))\n        : (key) => normalizeIntrinsicElementKey(key)\n    for (let [key, v] of Object.entries(props)) {\n      key = normalizeKey(key)\n      if (key === 'children') {\n        // skip children\n      } else if (key === 'style' && typeof v === 'object') {\n        // object to style strings\n        let styleStr = ''\n        styleObjectForEach(v, (property, value) => {\n          if (value != null) {\n            styleStr += `${styleStr ? ';' : ''}${property}:${value}`\n          }\n        })\n        buffer[0] += ' style=\"'\n        escapeToBuffer(styleStr, buffer)\n        buffer[0] += '\"'\n      } else if (typeof v === 'string') {\n        buffer[0] += ` ${key}=\"`\n        escapeToBuffer(v, buffer)\n        buffer[0] += '\"'\n      } else if (v === null || v === undefined) {\n        // Do nothing\n      } else if (typeof v === 'number' || (v as HtmlEscaped).isEscaped) {\n        buffer[0] += ` ${key}=\"${v}\"`\n      } else if (typeof v === 'boolean' && booleanAttributes.includes(key)) {\n        if (v) {\n          buffer[0] += ` ${key}=\"\"`\n        }\n      } else if (key === 'dangerouslySetInnerHTML') {\n        if (children.length > 0) {\n          throw new Error('Can only set one of `children` or `props.dangerouslySetInnerHTML`.')\n        }\n\n        children = [raw(v.__html)]\n      } else if (v instanceof Promise) {\n        buffer[0] += ` ${key}=\"`\n        buffer.unshift('\"', v)\n      } else if (typeof v === 'function') {\n        if (!key.startsWith('on') && key !== 'ref') {\n          throw new Error(`Invalid prop '${key}' of type 'function' supplied to '${tag}'.`)\n        }\n        // maybe event handler for client components, just ignore in server components\n      } else {\n        buffer[0] += ` ${key}=\"`\n        escapeToBuffer(v.toString(), buffer)\n        buffer[0] += '\"'\n      }\n    }\n\n    if (emptyTags.includes(tag as string) && children.length === 0) {\n      buffer[0] += '/>'\n      return\n    }\n\n    buffer[0] += '>'\n\n    childrenToStringToBuffer(children, buffer)\n\n    buffer[0] += `</${tag}>`\n  }\n}\n\nclass JSXFunctionNode extends JSXNode {\n  override toStringToBuffer(buffer: StringBufferWithCallbacks): void {\n    const { children } = this\n\n    const props = { ...this.props }\n    if (children.length) {\n      props.children = children.length === 1 ? children[0] : children\n    }\n\n    const res = (this.tag as Function).call(null, props)\n\n    if (typeof res === 'boolean' || res == null) {\n      // boolean or null or undefined\n      return\n    } else if (res instanceof Promise) {\n      if (globalContexts.length === 0) {\n        buffer.unshift('', res)\n      } else {\n        // save current contexts for resuming\n        const currentContexts: LocalContexts = globalContexts.map((c) => [c, c.values.at(-1)])\n        buffer.unshift(\n          '',\n          res.then((childRes) => {\n            if (childRes instanceof JSXNode) {\n              childRes.localContexts = currentContexts\n            }\n            return childRes\n          })\n        )\n      }\n    } else if (res instanceof JSXNode) {\n      res.toStringToBuffer(buffer)\n    } else if (typeof res === 'number' || (res as HtmlEscaped).isEscaped) {\n      buffer[0] += res\n      if (res.callbacks) {\n        buffer.callbacks ||= []\n        buffer.callbacks.push(...res.callbacks)\n      }\n    } else {\n      escapeToBuffer(res, buffer)\n    }\n  }\n}\n\nexport class JSXFragmentNode extends JSXNode {\n  override toStringToBuffer(buffer: StringBufferWithCallbacks): void {\n    childrenToStringToBuffer(this.children, buffer)\n  }\n}\n\nexport const jsx = (\n  tag: string | Function,\n  props: Props | null,\n  ...children: (string | number | HtmlEscapedString)[]\n): JSXNode => {\n  props ??= {}\n  if (children.length) {\n    props.children = children.length === 1 ? children[0] : children\n  }\n\n  const key = props.key\n  delete props['key']\n\n  const node = jsxFn(tag, props, children)\n  node.key = key\n  return node\n}\n\nlet initDomRenderer = false\nexport const jsxFn = (\n  tag: string | Function,\n  props: Props,\n  children: (string | number | HtmlEscapedString)[]\n): JSXNode => {\n  if (!initDomRenderer) {\n    for (const k in domRenderers) {\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      ;(intrinsicElementTags[k as keyof typeof intrinsicElementTags] as any)[DOM_RENDERER] =\n        domRenderers[k]\n    }\n    initDomRenderer = true\n  }\n\n  if (typeof tag === 'function') {\n    return new JSXFunctionNode(tag, props, children)\n  } else if (intrinsicElementTags[tag as keyof typeof intrinsicElementTags]) {\n    return new JSXFunctionNode(\n      intrinsicElementTags[tag as keyof typeof intrinsicElementTags],\n      props,\n      children\n    )\n  } else if (tag === 'svg' || tag === 'head') {\n    nameSpaceContext ||= createContext('')\n    return new JSXNode(tag, props, [\n      new JSXFunctionNode(\n        nameSpaceContext,\n        {\n          value: tag,\n        },\n        children\n      ),\n    ])\n  } else {\n    return new JSXNode(tag, props, children)\n  }\n}\n\nexport const shallowEqual = (a: Props, b: Props): boolean => {\n  if (a === b) {\n    return true\n  }\n\n  const aKeys = Object.keys(a).sort()\n  const bKeys = Object.keys(b).sort()\n  if (aKeys.length !== bKeys.length) {\n    return false\n  }\n\n  for (let i = 0, len = aKeys.length; i < len; i++) {\n    if (\n      aKeys[i] === 'children' &&\n      bKeys[i] === 'children' &&\n      !a.children?.length &&\n      !b.children?.length\n    ) {\n      continue\n    } else if (a[aKeys[i]] !== b[aKeys[i]]) {\n      return false\n    }\n  }\n\n  return true\n}\n\nexport type MemorableFC<T> = FC<T> & {\n  [DOM_MEMO]: (prevProps: Readonly<T>, nextProps: Readonly<T>) => boolean\n}\nexport const memo = <T>(\n  component: FC<T>,\n  propsAreEqual: (prevProps: Readonly<T>, nextProps: Readonly<T>) => boolean = shallowEqual\n): FC<T> => {\n  let computed: ReturnType<FC<T>> = null\n  let prevProps: T | undefined = undefined\n  const wrapper: MemorableFC<T> = ((props: T) => {\n    if (prevProps && !propsAreEqual(prevProps, props)) {\n      computed = null\n    }\n    prevProps = props\n    return (computed ||= component(props))\n  }) as MemorableFC<T>\n\n  // This function is for toString(), but it can also be used for DOM renderer.\n  // So, set DOM_MEMO and DOM_RENDERER for DOM renderer.\n  wrapper[DOM_MEMO] = propsAreEqual\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  ;(wrapper as any)[DOM_RENDERER] = component\n\n  return wrapper as FC<T>\n}\n\nexport const Fragment = ({\n  children,\n}: {\n  key?: string\n  children?: Child | HtmlEscapedString\n}): HtmlEscapedString => {\n  return new JSXFragmentNode(\n    '',\n    {\n      children,\n    },\n    Array.isArray(children) ? children : children ? [children] : []\n  ) as never\n}\n\nexport const isValidElement = (element: unknown): element is JSXNode => {\n  return !!(element && typeof element === 'object' && 'tag' in element && 'props' in element)\n}\n\nexport const cloneElement = <T extends JSXNode | JSX.Element>(\n  element: T,\n  props: Partial<Props>,\n  ...children: Child[]\n): T => {\n  let childrenToClone\n  if (children.length > 0) {\n    childrenToClone = children\n  } else {\n    const c = (element as JSXNode).props.children\n    childrenToClone = Array.isArray(c) ? c : [c]\n  }\n  return jsx(\n    (element as JSXNode).tag,\n    { ...(element as JSXNode).props, ...props },\n    ...childrenToClone\n  ) as T\n}\n\nexport const reactAPICompatVersion = '19.0.0-hono-jsx'\n"
  },
  {
    "path": "src/jsx/children.test.ts",
    "content": "import { Children } from './children'\nimport { createElement } from '.'\n\ndescribe('map', () => {\n  it('should map children', () => {\n    const element = createElement('div', null, 1, 2, 3)\n    const result = Children.map(element.children, (child) => (child as number) * 2)\n    expect(result).toEqual([2, 4, 6])\n  })\n})\n\ndescribe('forEach', () => {\n  it('should iterate children', () => {\n    const element = createElement('div', null, 1, 2, 3)\n    const result: number[] = []\n    Children.forEach(element.children, (child) => {\n      result.push(child as number)\n    })\n    expect(result).toEqual([1, 2, 3])\n  })\n})\n\ndescribe('count', () => {\n  it('should count children', () => {\n    const element = createElement('div', null, 1, 2, 3)\n    const result = Children.count(element.children)\n    expect(result).toBe(3)\n  })\n})\n\ndescribe('only', () => {\n  it('should return the only child', () => {\n    const element = createElement('div', null, 1)\n    const result = Children.only(element.children)\n    expect(result).toBe(1)\n  })\n\n  it('should throw an error if there are multiple children', () => {\n    const element = createElement('div', null, 1, 2)\n    expect(() => Children.only(element.children)).toThrowError(\n      'Children.only() expects only one child'\n    )\n  })\n})\n\ndescribe('toArray', () => {\n  it('should convert children to an array', () => {\n    const element = createElement('div', null, 1, 2, 3)\n    const result = Children.toArray(element.children)\n    expect(result).toEqual([1, 2, 3])\n  })\n})\n"
  },
  {
    "path": "src/jsx/children.ts",
    "content": "import type { Child } from './base'\n\nexport const toArray = (children: Child): Child[] =>\n  Array.isArray(children) ? children : [children]\nexport const Children = {\n  map: (children: Child[], fn: (child: Child, index: number) => Child): Child[] =>\n    toArray(children).map(fn),\n  forEach: (children: Child[], fn: (child: Child, index: number) => void): void => {\n    toArray(children).forEach(fn)\n  },\n  count: (children: Child[]): number => toArray(children).length,\n  only: (_children: Child[]): Child => {\n    const children = toArray(_children)\n    if (children.length !== 1) {\n      throw new Error('Children.only() expects only one child')\n    }\n    return children[0]\n  },\n  toArray,\n}\n"
  },
  {
    "path": "src/jsx/components.test.tsx",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\n/** @jsxImportSource ./ */\nimport { JSDOM } from 'jsdom'\nimport type { HtmlEscapedString } from '../utils/html'\nimport { HtmlEscapedCallbackPhase, resolveCallback as rawResolveCallback } from '../utils/html'\nimport { ErrorBoundary } from './components'\nimport { Suspense, renderToReadableStream, StreamingContext } from './streaming'\n\nfunction resolveCallback(template: string | HtmlEscapedString) {\n  return rawResolveCallback(template, HtmlEscapedCallbackPhase.Stream, false, {})\n}\n\nfunction replacementResult(html: string) {\n  const document = new JSDOM(html, { runScripts: 'dangerously' }).window.document\n  document.querySelectorAll('template, script').forEach((e) => e.remove())\n  return document.body.innerHTML\n}\n\nconst Fallback = () => <div>Out Of Service</div>\n\ndescribe('ErrorBoundary', () => {\n  let errorBoundaryCounter = 0\n  let suspenseCounter = 0\n  afterEach(() => {\n    errorBoundaryCounter++\n    suspenseCounter++\n  })\n\n  describe('sync', async () => {\n    const Component = ({ error }: { error?: boolean }) => {\n      if (error) {\n        throw new Error('Error')\n      }\n      return <div>Hello</div>\n    }\n\n    it('no error', async () => {\n      const html = (\n        <ErrorBoundary fallback={<Fallback />}>\n          <Component />\n        </ErrorBoundary>\n      )\n\n      expect((await resolveCallback(await html.toString())).toString()).toEqual('<div>Hello</div>')\n\n      errorBoundaryCounter--\n      suspenseCounter--\n    })\n\n    it('error', async () => {\n      const html = (\n        <ErrorBoundary fallback={<Fallback />}>\n          <Component error={true} />\n        </ErrorBoundary>\n      )\n\n      expect((await resolveCallback(await html.toString())).toString()).toEqual(\n        '<div>Out Of Service</div>'\n      )\n\n      suspenseCounter--\n    })\n\n    it('nullish', async () => {\n      const html = (\n        <div>\n          <ErrorBoundary fallback={<Fallback />}>{[null, undefined]}</ErrorBoundary>\n        </div>\n      )\n\n      expect((await resolveCallback(await html.toString())).toString()).toEqual('<div></div>')\n\n      errorBoundaryCounter--\n      suspenseCounter--\n    })\n\n    it('boolean', async () => {\n      const html = (\n        <div>\n          <ErrorBoundary fallback={<Fallback />}>{[true, false]}</ErrorBoundary>\n        </div>\n      )\n\n      expect((await resolveCallback(await html.toString())).toString()).toEqual('<div></div>')\n\n      errorBoundaryCounter--\n      suspenseCounter--\n    })\n\n    it('string content', async () => {\n      const html = <ErrorBoundary fallback={<Fallback />}>{'< ok >'}</ErrorBoundary>\n\n      expect((await resolveCallback(await html.toString())).toString()).toEqual('&lt; ok &gt;')\n\n      errorBoundaryCounter--\n      suspenseCounter--\n    })\n\n    it('error: string content', async () => {\n      const html = (\n        <ErrorBoundary fallback={'< error >'}>\n          <Component error={true} />\n        </ErrorBoundary>\n      )\n\n      expect((await resolveCallback(await html.toString())).toString()).toEqual('&lt; error &gt;')\n\n      errorBoundaryCounter--\n      suspenseCounter--\n    })\n\n    it('error: Promise<string> from fallback', async () => {\n      const html = (\n        <ErrorBoundary fallback={Promise.resolve('< error >')}>\n          <Component error={true} />\n        </ErrorBoundary>\n      )\n\n      expect((await resolveCallback(await html.toString())).toString()).toEqual('&lt; error &gt;')\n\n      errorBoundaryCounter--\n      suspenseCounter--\n    })\n\n    it('error: string content from fallbackRender', async () => {\n      const html = (\n        <ErrorBoundary fallbackRender={() => '< error >'}>\n          <Component error={true} />\n        </ErrorBoundary>\n      )\n\n      expect((await resolveCallback(await html.toString())).toString()).toEqual('&lt; error &gt;')\n\n      errorBoundaryCounter--\n      suspenseCounter--\n    })\n\n    it('error: Promise<string> from fallbackRender', async () => {\n      const html = (\n        <ErrorBoundary fallbackRender={() => Promise.resolve('< error >')}>\n          <Component error={true} />\n        </ErrorBoundary>\n      )\n\n      expect((await resolveCallback(await html.toString())).toString()).toEqual('&lt; error &gt;')\n\n      errorBoundaryCounter--\n      suspenseCounter--\n    })\n  })\n\n  describe('async', async () => {\n    const Component = async ({ error }: { error?: boolean }) => {\n      await new Promise((resolve) => setTimeout(resolve, 10))\n      if (error) {\n        throw new Error('Error')\n      }\n      return <div>Hello</div>\n    }\n\n    it('no error', async () => {\n      const html = (\n        <ErrorBoundary fallback={<Fallback />}>\n          <Component />\n        </ErrorBoundary>\n      )\n\n      expect((await resolveCallback(await html.toString())).toString()).toEqual('<div>Hello</div>')\n\n      errorBoundaryCounter--\n      suspenseCounter--\n    })\n\n    it('error', async () => {\n      const html = (\n        <ErrorBoundary fallback={<Fallback />}>\n          <Component error={true} />\n        </ErrorBoundary>\n      )\n\n      expect((await resolveCallback(await html.toString())).toString()).toEqual(\n        '<div>Out Of Service</div>'\n      )\n\n      suspenseCounter--\n    })\n\n    it('error: string content', async () => {\n      const html = (\n        <ErrorBoundary fallback={'< error >'}>\n          <Component error={true} />\n        </ErrorBoundary>\n      )\n\n      expect((await resolveCallback(await html.toString())).toString()).toEqual('&lt; error &gt;')\n\n      suspenseCounter--\n    })\n\n    it('error: Promise<string> from fallback', async () => {\n      const html = (\n        <ErrorBoundary fallback={Promise.resolve('< error >')}>\n          <Component error={true} />\n        </ErrorBoundary>\n      )\n\n      expect((await resolveCallback(await html.toString())).toString()).toEqual('&lt; error &gt;')\n\n      suspenseCounter--\n    })\n\n    it('error: string content from fallbackRender', async () => {\n      const html = (\n        <ErrorBoundary fallbackRender={() => '< error >'}>\n          <Component error={true} />\n        </ErrorBoundary>\n      )\n\n      expect((await resolveCallback(await html.toString())).toString()).toEqual('&lt; error &gt;')\n\n      suspenseCounter--\n    })\n\n    it('error: Promise<string> from fallbackRender', async () => {\n      const html = (\n        <ErrorBoundary fallbackRender={() => Promise.resolve('< error >')}>\n          <Component error={true} />\n        </ErrorBoundary>\n      )\n\n      expect((await resolveCallback(await html.toString())).toString()).toEqual('&lt; error &gt;')\n\n      suspenseCounter--\n    })\n  })\n\n  describe('async : nested', async () => {\n    const handlers: Record<number, { resolve: (value: unknown) => void; reject: () => void }> = {}\n    const Component = async ({ id }: { id: number }) => {\n      await new Promise((resolve, reject) => (handlers[id] = { resolve, reject }))\n      return <div>{id}</div>\n    }\n\n    it('no error', async () => {\n      const html = (\n        <ErrorBoundary fallback={<Fallback />}>\n          <Component id={1} />\n          <ErrorBoundary fallback={<Fallback />}>\n            <Component id={2} />\n          </ErrorBoundary>\n        </ErrorBoundary>\n      ).toString()\n\n      Object.values(handlers).forEach(({ resolve }) => resolve(undefined))\n\n      expect((await resolveCallback(await html)).toString()).toEqual('<div>1</div><div>2</div>')\n\n      errorBoundaryCounter++\n      suspenseCounter--\n    })\n\n    it('error in parent', async () => {\n      const html = (\n        <ErrorBoundary fallback={<Fallback />}>\n          <Component id={1} />\n          <ErrorBoundary fallback={<Fallback />}>\n            <Component id={2} />\n          </ErrorBoundary>\n        </ErrorBoundary>\n      ).toString()\n\n      handlers[2].resolve(undefined)\n      handlers[1].reject()\n\n      expect((await resolveCallback(await html)).toString()).toEqual('<div>Out Of Service</div>')\n\n      errorBoundaryCounter++\n      suspenseCounter--\n    })\n\n    it('error in child', async () => {\n      const html = (\n        <ErrorBoundary fallback={<Fallback />}>\n          <Component id={1} />\n          <ErrorBoundary fallback={<Fallback />}>\n            <Component id={2} />\n          </ErrorBoundary>\n        </ErrorBoundary>\n      ).toString()\n\n      handlers[1].resolve(undefined)\n      handlers[2].reject()\n\n      expect((await resolveCallback(await html)).toString()).toEqual(\n        '<div>1</div><div>Out Of Service</div>'\n      )\n\n      errorBoundaryCounter++\n      suspenseCounter--\n    })\n  })\n\n  describe('async : setTimeout', async () => {\n    const TimeoutSuccessComponent = async () => {\n      await new Promise((resolve) => setTimeout(resolve, 10))\n      return <div>OK</div>\n    }\n    const TimeoutErrorComponent = async () => {\n      await new Promise((resolve) => setTimeout(resolve, 0))\n      throw new Error('Error')\n    }\n\n    it('fallback', async () => {\n      const html = (\n        <>\n          <TimeoutSuccessComponent />\n          <ErrorBoundary fallback={<Fallback />}>\n            <TimeoutErrorComponent />\n          </ErrorBoundary>\n        </>\n      ).toString()\n\n      expect((await resolveCallback(await html)).toString()).toEqual(\n        '<div>OK</div><div>Out Of Service</div>'\n      )\n\n      suspenseCounter--\n    })\n  })\n\n  describe('streaming', async () => {\n    const Component = async ({ error }: { error?: boolean }) => {\n      await new Promise((resolve) => setTimeout(resolve, 10))\n      if (error) {\n        throw new Error('Error')\n      }\n      return <div>Hello</div>\n    }\n\n    it('no error', async () => {\n      const stream = renderToReadableStream(\n        <ErrorBoundary fallback={<Fallback />}>\n          <Suspense fallback={<p>Loading...</p>}>\n            <Component />\n          </Suspense>\n        </ErrorBoundary>\n      )\n      const chunks = []\n      const textDecoder = new TextDecoder()\n      for await (const chunk of stream as any) {\n        chunks.push(textDecoder.decode(chunk))\n      }\n\n      expect(chunks).toEqual([\n        `<template id=\"E:${errorBoundaryCounter}\"></template><!--E:${errorBoundaryCounter}-->`,\n        `<template data-hono-target=\"E:${errorBoundaryCounter}\"><template id=\"H:${suspenseCounter}\"></template><p>Loading...</p><!--/$--></template><script>\n((d,c) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('E:${errorBoundaryCounter}')\nif(!d)return\nd.parentElement.insertBefore(c.content,d.nextSibling)\n})(document)\n</script>`,\n        `<template data-hono-target=\"H:${suspenseCounter}\"><div>Hello</div></template><script>\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:${suspenseCounter}')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script><script>\n((d,c,n) => {\nd=d.getElementById('E:${errorBoundaryCounter}')\nif(!d)return\nn=d.nextSibling\nwhile(n.nodeType!=8||n.nodeValue!='E:${errorBoundaryCounter}'){n=n.nextSibling}\nn.remove()\nd.remove()\n})(document)\n</script>`,\n      ])\n\n      expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n        '<div>Hello</div>'\n      )\n    })\n\n    it('with StreamingContext', async () => {\n      const stream = renderToReadableStream(\n        <StreamingContext value={{ scriptNonce: 'test-nonce' }}>\n          <ErrorBoundary fallback={<Fallback />}>\n            <Suspense fallback={<p>Loading...</p>}>\n              <Component />\n            </Suspense>\n          </ErrorBoundary>\n        </StreamingContext>\n      )\n      const chunks = []\n      const textDecoder = new TextDecoder()\n      for await (const chunk of stream as any) {\n        chunks.push(textDecoder.decode(chunk))\n      }\n\n      expect(chunks).toEqual([\n        `<template id=\"E:${errorBoundaryCounter}\"></template><!--E:${errorBoundaryCounter}-->`,\n        `<template data-hono-target=\"E:${errorBoundaryCounter}\"><template id=\"H:${suspenseCounter}\"></template><p>Loading...</p><!--/$--></template><script nonce=\"test-nonce\">\n((d,c) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('E:${errorBoundaryCounter}')\nif(!d)return\nd.parentElement.insertBefore(c.content,d.nextSibling)\n})(document)\n</script>`,\n        `<template data-hono-target=\"H:${suspenseCounter}\"><div>Hello</div></template><script nonce=\"test-nonce\">\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:${suspenseCounter}')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script><script>\n((d,c,n) => {\nd=d.getElementById('E:${errorBoundaryCounter}')\nif(!d)return\nn=d.nextSibling\nwhile(n.nodeType!=8||n.nodeValue!='E:${errorBoundaryCounter}'){n=n.nextSibling}\nn.remove()\nd.remove()\n})(document)\n</script>`,\n      ])\n\n      expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n        '<div>Hello</div>'\n      )\n    })\n\n    it('error', async () => {\n      const html = (\n        <ErrorBoundary fallback={<Fallback />}>\n          <Component error={true} />\n        </ErrorBoundary>\n      )\n\n      expect((await resolveCallback(await html.toString())).toString()).toEqual(\n        '<div>Out Of Service</div>'\n      )\n    })\n  })\n\n  describe('streaming : contains multiple suspense', async () => {\n    const handlers: Record<number, { resolve: (value: unknown) => void; reject: () => void }> = {}\n    const Component = async ({ id }: { id: number }) => {\n      await new Promise((resolve, reject) => (handlers[id] = { resolve, reject }))\n      return <div>{id}</div>\n    }\n\n    it('no error', async () => {\n      const stream = renderToReadableStream(\n        <ErrorBoundary fallback={<Fallback />}>\n          <Suspense fallback={<p>Loading...</p>}>\n            <Component id={1} />\n          </Suspense>\n          <Suspense fallback={<p>Loading...</p>}>\n            <Component id={2} />\n          </Suspense>\n          <Suspense fallback={<p>Loading...</p>}>\n            <Component id={3} />\n          </Suspense>\n        </ErrorBoundary>\n      )\n\n      Object.values(handlers).forEach(({ resolve }) => resolve(undefined))\n\n      const chunks = []\n      const textDecoder = new TextDecoder()\n      for await (const chunk of stream as any) {\n        chunks.push(textDecoder.decode(chunk))\n      }\n\n      expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n        '<div>1</div><div>2</div><div>3</div>'\n      )\n    })\n\n    it('error', async () => {\n      const stream = renderToReadableStream(\n        <ErrorBoundary fallback={<Fallback />}>\n          <Suspense fallback={<p>Loading...</p>}>\n            <Component id={1} />\n          </Suspense>\n          <Suspense fallback={<p>Loading...</p>}>\n            <Component id={2} />\n          </Suspense>\n          <Suspense fallback={<p>Loading...</p>}>\n            <Component id={3} />\n          </Suspense>\n        </ErrorBoundary>\n      )\n\n      handlers[1].resolve(undefined)\n      handlers[2].resolve(undefined)\n      handlers[3].reject()\n\n      const chunks = []\n      const textDecoder = new TextDecoder()\n      for await (const chunk of stream as any) {\n        chunks.push(textDecoder.decode(chunk))\n      }\n\n      expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n        '<div>Out Of Service</div>'\n      )\n    })\n  })\n\n  describe('streaming : nested', async () => {\n    const handlers: Record<number, { resolve: (value: unknown) => void; reject: () => void }> = {}\n    const Component = async ({ id }: { id: number }) => {\n      await new Promise((resolve, reject) => (handlers[id] = { resolve, reject }))\n      return <div>{id}</div>\n    }\n\n    it('no error', async () => {\n      const stream = renderToReadableStream(\n        <ErrorBoundary fallback={<Fallback />}>\n          <Suspense fallback={<p>Loading...</p>}>\n            <Component id={1} />\n          </Suspense>\n          <ErrorBoundary fallback={<Fallback />}>\n            <Suspense fallback={<p>Loading...</p>}>\n              <Component id={2} />\n            </Suspense>\n          </ErrorBoundary>\n        </ErrorBoundary>\n      )\n\n      Object.values(handlers).forEach(({ resolve }) => resolve(undefined))\n\n      const chunks = []\n      const textDecoder = new TextDecoder()\n      for await (const chunk of stream as any) {\n        chunks.push(textDecoder.decode(chunk))\n      }\n\n      expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n        '<div>1</div><div>2</div>'\n      )\n    })\n\n    it('error in parent', async () => {\n      const stream = renderToReadableStream(\n        <ErrorBoundary fallback={<Fallback />}>\n          <Suspense fallback={<p>Loading...</p>}>\n            <Component id={1} />\n          </Suspense>\n          <ErrorBoundary fallback={<Fallback />}>\n            <Suspense fallback={<p>Loading...</p>}>\n              <Component id={2} />\n            </Suspense>\n          </ErrorBoundary>\n        </ErrorBoundary>\n      )\n\n      handlers[2].resolve(undefined)\n      handlers[1].reject()\n\n      const chunks = []\n      const textDecoder = new TextDecoder()\n      for await (const chunk of stream as any) {\n        chunks.push(textDecoder.decode(chunk))\n      }\n\n      expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n        '<div>Out Of Service</div>'\n      )\n    })\n\n    it('error in child', async () => {\n      const stream = renderToReadableStream(\n        <ErrorBoundary fallback={<Fallback />}>\n          <Suspense fallback={<p>Loading...</p>}>\n            <Component id={1} />\n          </Suspense>\n          <ErrorBoundary fallback={<Fallback />}>\n            <Suspense fallback={<p>Loading...</p>}>\n              <Component id={2} />\n            </Suspense>\n          </ErrorBoundary>\n        </ErrorBoundary>\n      )\n\n      handlers[1].resolve(undefined)\n      handlers[2].reject()\n\n      const chunks = []\n      const textDecoder = new TextDecoder()\n      for await (const chunk of stream as any) {\n        chunks.push(textDecoder.decode(chunk))\n      }\n\n      expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n        '<div>1</div><div>Out Of Service</div>'\n      )\n    })\n  })\n\n  describe('onError', async () => {\n    const Component = ({ error }: { error?: boolean }) => {\n      if (error) {\n        throw new Error('Error')\n      }\n      return <div>Hello</div>\n    }\n\n    it('no error', async () => {\n      const errors: Error[] = []\n      const html = (\n        <ErrorBoundary fallback={<Fallback />} onError={(err) => errors.push(err)}>\n          <Component />\n        </ErrorBoundary>\n      )\n\n      expect((await resolveCallback(await html.toString())).toString()).toEqual('<div>Hello</div>')\n\n      errorBoundaryCounter--\n      suspenseCounter--\n\n      expect(errors).toEqual([])\n    })\n\n    it('error', async () => {\n      const errors: Error[] = []\n      const html = (\n        <ErrorBoundary fallback={<Fallback />} onError={(err) => errors.push(err)}>\n          <Component error={true} />\n        </ErrorBoundary>\n      )\n\n      expect((await resolveCallback(await html.toString())).toString()).toEqual(\n        '<div>Out Of Service</div>'\n      )\n\n      suspenseCounter--\n\n      expect(errors[0]).toEqual(new Error('Error'))\n    })\n  })\n\n  describe('fallbackRender', async () => {\n    const fallbackRenderer = (error: Error) => <div data-error>{error.message}</div>\n    const Component = ({ error }: { error?: boolean }) => {\n      if (error) {\n        throw new Error('Error')\n      }\n      return <div>Hello</div>\n    }\n\n    it('no error', async () => {\n      const errors: Error[] = []\n      const html = (\n        <ErrorBoundary fallbackRender={fallbackRenderer}>\n          <Component />\n        </ErrorBoundary>\n      )\n\n      expect((await resolveCallback(await html.toString())).toString()).toEqual('<div>Hello</div>')\n\n      errorBoundaryCounter--\n      suspenseCounter--\n\n      expect(errors).toEqual([])\n    })\n\n    it('error', async () => {\n      const html = (\n        <ErrorBoundary fallbackRender={fallbackRenderer}>\n          <Component error={true} />\n        </ErrorBoundary>\n      )\n\n      expect((await resolveCallback(await html.toString())).toString()).toEqual(\n        '<div data-error=\"true\">Error</div>'\n      )\n\n      suspenseCounter--\n    })\n  })\n})\n"
  },
  {
    "path": "src/jsx/components.ts",
    "content": "import { raw } from '../helper/html'\nimport type { HtmlEscapedCallback, HtmlEscapedString } from '../utils/html'\nimport { HtmlEscapedCallbackPhase, resolveCallback } from '../utils/html'\nimport { jsx, Fragment } from './base'\nimport { DOM_RENDERER } from './constants'\nimport { useContext } from './context'\nimport { ErrorBoundary as ErrorBoundaryDomRenderer } from './dom/components'\nimport type { HasRenderToDom } from './dom/render'\nimport { StreamingContext } from './streaming'\nimport type { Child, FC, PropsWithChildren } from './'\n\nlet errorBoundaryCounter = 0\n\nexport const childrenToString = async (children: Child[]): Promise<HtmlEscapedString[]> => {\n  try {\n    return children\n      .flat()\n      .map((c) => (c == null || typeof c === 'boolean' ? '' : c.toString())) as HtmlEscapedString[]\n  } catch (e) {\n    if (e instanceof Promise) {\n      await e\n      return childrenToString(children)\n    } else {\n      throw e\n    }\n  }\n}\n\nconst resolveChildEarly = (c: Child): HtmlEscapedString | Promise<HtmlEscapedString> => {\n  if (c == null || typeof c === 'boolean') {\n    return '' as HtmlEscapedString\n  } else if (typeof c === 'string') {\n    return c as HtmlEscapedString\n  } else {\n    const str = c.toString()\n    if (!(str instanceof Promise)) {\n      return raw(str)\n    } else {\n      return str as Promise<HtmlEscapedString>\n    }\n  }\n}\n\nexport type ErrorHandler = (error: Error) => void\nexport type FallbackRender = (error: Error) => Child\n\n/**\n * @experimental\n * `ErrorBoundary` is an experimental feature.\n * The API might be changed.\n */\nexport const ErrorBoundary: FC<\n  PropsWithChildren<{\n    fallback?: Child\n    fallbackRender?: FallbackRender\n    onError?: ErrorHandler\n  }>\n> = async ({ children, fallback, fallbackRender, onError }) => {\n  if (!children) {\n    return raw('')\n  }\n\n  if (!Array.isArray(children)) {\n    children = [children]\n  }\n\n  const nonce = useContext(StreamingContext)?.scriptNonce\n\n  let fallbackStr: string | undefined\n  const resolveFallbackStr = async () => {\n    const awaitedFallback = await fallback\n    if (typeof awaitedFallback === 'string') {\n      fallbackStr = awaitedFallback\n    } else {\n      fallbackStr = await awaitedFallback?.toString()\n      if (typeof fallbackStr === 'string') {\n        // should not apply `raw` if fallbackStr is undefined, null, or boolean\n        fallbackStr = raw(fallbackStr)\n      }\n    }\n  }\n  const fallbackRes = (error: Error): HtmlEscapedString | Promise<HtmlEscapedString> => {\n    onError?.(error)\n    return (fallbackStr ||\n      (fallbackRender && jsx(Fragment, {}, fallbackRender(error) as HtmlEscapedString)) ||\n      '') as HtmlEscapedString\n  }\n  let resArray: HtmlEscapedString[] | Promise<HtmlEscapedString[]>[] = []\n  try {\n    resArray = children.map(resolveChildEarly) as unknown as HtmlEscapedString[]\n  } catch (e) {\n    await resolveFallbackStr()\n    if (e instanceof Promise) {\n      resArray = [\n        e.then(() => childrenToString(children as Child[])).catch((e) => fallbackRes(e)),\n      ] as Promise<HtmlEscapedString[]>[]\n    } else {\n      resArray = [fallbackRes(e as Error) as HtmlEscapedString]\n    }\n  }\n\n  if (resArray.some((res) => (res as {}) instanceof Promise)) {\n    await resolveFallbackStr()\n    const index = errorBoundaryCounter++\n    const replaceRe = RegExp(`(<template id=\"E:${index}\"></template>.*?)(.*?)(<!--E:${index}-->)`)\n    const caught = false\n    const catchCallback = async ({ error, buffer }: { error: Error; buffer?: [string] }) => {\n      if (caught) {\n        return ''\n      }\n\n      const fallbackResString = await Fragment({\n        children: fallbackRes(error),\n      }).toString()\n      if (buffer) {\n        buffer[0] = buffer[0].replace(replaceRe, fallbackResString)\n      }\n      return buffer\n        ? ''\n        : `<template data-hono-target=\"E:${index}\">${fallbackResString}</template><script>\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('E:${index}')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='E:${index}')\nd.replaceWith(c.content)\n})(document)\n</script>`\n    }\n\n    let error: unknown\n    const promiseAll = Promise.all(resArray).catch((e) => (error = e))\n    return raw(`<template id=\"E:${index}\"></template><!--E:${index}-->`, [\n      ({ phase, buffer, context }) => {\n        if (phase === HtmlEscapedCallbackPhase.BeforeStream) {\n          return\n        }\n        return promiseAll\n          .then(async (htmlArray: HtmlEscapedString[]) => {\n            if (error) {\n              throw error\n            }\n            htmlArray = htmlArray.flat()\n            const content = htmlArray.join('')\n            let html = buffer\n              ? ''\n              : `<template data-hono-target=\"E:${index}\">${content}</template><script${\n                  nonce ? ` nonce=\"${nonce}\"` : ''\n                }>\n((d,c) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('E:${index}')\nif(!d)return\nd.parentElement.insertBefore(c.content,d.nextSibling)\n})(document)\n</script>`\n\n            if (htmlArray.every((html) => !(html as HtmlEscapedString).callbacks?.length)) {\n              if (buffer) {\n                buffer[0] = buffer[0].replace(replaceRe, content)\n              }\n              return html\n            }\n\n            if (buffer) {\n              buffer[0] = buffer[0].replace(\n                replaceRe,\n                (_all, pre, _, post) => `${pre}${content}${post}`\n              )\n            }\n\n            const callbacks = htmlArray\n              .map((html) => (html as HtmlEscapedString).callbacks || [])\n              .flat()\n\n            if (phase === HtmlEscapedCallbackPhase.Stream) {\n              html = await resolveCallback(\n                html,\n                HtmlEscapedCallbackPhase.BeforeStream,\n                true,\n                context\n              )\n            }\n\n            let resolvedCount = 0\n            const promises = callbacks.map<HtmlEscapedCallback>(\n              (c) =>\n                (...args) =>\n                  c(...args)\n                    ?.then((content) => {\n                      resolvedCount++\n\n                      if (buffer) {\n                        if (resolvedCount === callbacks.length) {\n                          buffer[0] = buffer[0].replace(replaceRe, (_all, _pre, content) => content)\n                        }\n                        buffer[0] += content\n                        return raw('', (content as HtmlEscapedString).callbacks)\n                      }\n\n                      return raw(\n                        content +\n                          (resolvedCount !== callbacks.length\n                            ? ''\n                            : `<script>\n((d,c,n) => {\nd=d.getElementById('E:${index}')\nif(!d)return\nn=d.nextSibling\nwhile(n.nodeType!=8||n.nodeValue!='E:${index}'){n=n.nextSibling}\nn.remove()\nd.remove()\n})(document)\n</script>`),\n                        (content as HtmlEscapedString).callbacks\n                      )\n                    })\n                    .catch((error) => catchCallback({ error, buffer }))\n            )\n\n            // eslint-disable-next-line @typescript-eslint/no-explicit-any\n            return raw(html, promises as any)\n          })\n          .catch((error) => catchCallback({ error, buffer }))\n      },\n    ])\n  } else {\n    return Fragment({ children: resArray as Child[] })\n  }\n}\n;(ErrorBoundary as HasRenderToDom)[DOM_RENDERER] = ErrorBoundaryDomRenderer\n"
  },
  {
    "path": "src/jsx/constants.ts",
    "content": "export const DOM_RENDERER = Symbol('RENDERER')\nexport const DOM_ERROR_HANDLER = Symbol('ERROR_HANDLER')\nexport const DOM_STASH = Symbol('STASH')\nexport const DOM_INTERNAL_TAG = Symbol('INTERNAL')\nexport const DOM_MEMO = Symbol('MEMO')\nexport const PERMALINK = Symbol('PERMALINK')\n"
  },
  {
    "path": "src/jsx/context.ts",
    "content": "import { raw } from '../helper/html'\nimport type { HtmlEscapedString } from '../utils/html'\nimport { JSXFragmentNode } from './base'\nimport { DOM_RENDERER } from './constants'\nimport { createContextProviderFunction } from './dom/context'\nimport type { FC, PropsWithChildren } from './'\n\nexport interface Context<T> extends FC<PropsWithChildren<{ value: T }>> {\n  values: T[]\n  Provider: FC<PropsWithChildren<{ value: T }>>\n}\n\nexport const globalContexts: Context<unknown>[] = []\n\nexport const createContext = <T>(defaultValue: T): Context<T> => {\n  const values = [defaultValue]\n  const context: Context<T> = ((props): HtmlEscapedString | Promise<HtmlEscapedString> => {\n    values.push(props.value)\n    let string\n    try {\n      string = props.children\n        ? (Array.isArray(props.children)\n            ? new JSXFragmentNode('', {}, props.children)\n            : props.children\n          ).toString()\n        : ''\n    } catch (e) {\n      values.pop()\n      throw e\n    }\n\n    if (string instanceof Promise) {\n      return string\n        .finally(() => values.pop())\n        .then((resString) => raw(resString, (resString as HtmlEscapedString).callbacks))\n    } else {\n      values.pop()\n      return raw(string)\n    }\n  }) as Context<T>\n  context.values = values\n  context.Provider = context\n\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  ;(context as any)[DOM_RENDERER] = createContextProviderFunction(values)\n\n  globalContexts.push(context as Context<unknown>)\n\n  return context\n}\n\nexport const useContext = <T>(context: Context<T>): T => {\n  return context.values.at(-1) as T\n}\n"
  },
  {
    "path": "src/jsx/dom/client.test.tsx",
    "content": "/** @jsxImportSource ../ */\nimport { JSDOM } from 'jsdom'\nimport DefaultExport, { createRoot, hydrateRoot } from './client'\nimport { useEffect } from '.'\n\ndescribe('createRoot', () => {\n  beforeAll(() => {\n    global.requestAnimationFrame = (cb) => setTimeout(cb)\n  })\n\n  let dom: JSDOM\n  let rootElement: HTMLElement\n  beforeEach(() => {\n    dom = new JSDOM('<html><body><div id=\"root\"></div></body></html>', {\n      runScripts: 'dangerously',\n    })\n    global.document = dom.window.document\n    global.HTMLElement = dom.window.HTMLElement\n    global.SVGElement = dom.window.SVGElement\n    global.Text = dom.window.Text\n    rootElement = document.getElementById('root') as HTMLElement\n  })\n\n  it('render / unmount', async () => {\n    const cleanup = vi.fn()\n    const App = () => {\n      useEffect(() => cleanup, [])\n      return <h1>Hello</h1>\n    }\n    const root = createRoot(rootElement)\n    root.render(<App />)\n    expect(rootElement.innerHTML).toBe('<h1>Hello</h1>')\n    await new Promise((resolve) => setTimeout(resolve))\n    root.unmount()\n    await Promise.resolve()\n    expect(rootElement.innerHTML).toBe('')\n    expect(cleanup).toHaveBeenCalled()\n  })\n\n  it('call render twice', async () => {\n    const App = <h1>Hello</h1>\n    const App2 = <h1>World</h1>\n    const root = createRoot(rootElement)\n    root.render(App)\n    expect(rootElement.innerHTML).toBe('<h1>Hello</h1>')\n\n    const createElementSpy = vi.spyOn(dom.window.document, 'createElement')\n\n    root.render(App2)\n    await Promise.resolve()\n    expect(rootElement.innerHTML).toBe('<h1>World</h1>')\n\n    expect(createElementSpy).not.toHaveBeenCalled()\n  })\n\n  it('call render after unmount', async () => {\n    const App = <h1>Hello</h1>\n    const App2 = <h1>World</h1>\n    const root = createRoot(rootElement)\n    root.render(App)\n    expect(rootElement.innerHTML).toBe('<h1>Hello</h1>')\n    root.unmount()\n    expect(() => root.render(App2)).toThrow('Cannot update an unmounted root')\n  })\n})\n\ndescribe('hydrateRoot', () => {\n  let dom: JSDOM\n  let rootElement: HTMLElement\n  beforeEach(() => {\n    dom = new JSDOM('<html><body><div id=\"root\"></div></body></html>', {\n      runScripts: 'dangerously',\n    })\n    global.document = dom.window.document\n    global.HTMLElement = dom.window.HTMLElement\n    global.SVGElement = dom.window.SVGElement\n    global.Text = dom.window.Text\n    rootElement = document.getElementById('root') as HTMLElement\n  })\n\n  it('should return root object', async () => {\n    const cleanup = vi.fn()\n    const App = () => {\n      useEffect(() => cleanup, [])\n      return <h1>Hello</h1>\n    }\n    const root = hydrateRoot(rootElement, <App />)\n    expect(rootElement.innerHTML).toBe('<h1>Hello</h1>')\n    await new Promise((resolve) => setTimeout(resolve))\n    root.unmount()\n    await Promise.resolve()\n    expect(rootElement.innerHTML).toBe('')\n    expect(cleanup).toHaveBeenCalled()\n  })\n\n  it('call render', async () => {\n    const App = <h1>Hello</h1>\n    const App2 = <h1>World</h1>\n    const root = hydrateRoot(rootElement, App)\n    expect(rootElement.innerHTML).toBe('<h1>Hello</h1>')\n\n    const createElementSpy = vi.spyOn(dom.window.document, 'createElement')\n\n    root.render(App2)\n    await Promise.resolve()\n    expect(rootElement.innerHTML).toBe('<h1>World</h1>')\n\n    expect(createElementSpy).not.toHaveBeenCalled()\n  })\n\n  it('call render after unmount', async () => {\n    const App = <h1>Hello</h1>\n    const App2 = <h1>World</h1>\n    const root = hydrateRoot(rootElement, App)\n    expect(rootElement.innerHTML).toBe('<h1>Hello</h1>')\n    root.unmount()\n    expect(() => root.render(App2)).toThrow('Cannot update an unmounted root')\n  })\n})\n\ndescribe('default export', () => {\n  ;['createRoot', 'hydrateRoot'].forEach((key) => {\n    it(key, () => {\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      expect((DefaultExport as any)[key]).toBeDefined()\n    })\n  })\n})\n"
  },
  {
    "path": "src/jsx/dom/client.ts",
    "content": "/**\n * @module\n * This module provides APIs for `hono/jsx/dom/client`, which is compatible with `react-dom/client`.\n */\n\nimport type { Child } from '../base'\nimport { useState } from '../hooks'\nimport { buildNode, renderNode } from './render'\nimport type { NodeObject } from './render'\n\nexport interface Root {\n  render(children: Child): void\n  unmount(): void\n}\nexport type RootOptions = Record<string, unknown>\n\n/**\n * Create a root object for rendering\n * @param element Render target\n * @param options Options for createRoot (not supported yet)\n * @returns Root object has `render` and `unmount` methods\n */\nexport const createRoot = (\n  element: HTMLElement | DocumentFragment,\n  options: RootOptions = {}\n): Root => {\n  let setJsxNode:\n    | undefined // initial state\n    | ((jsxNode: unknown) => void) // rendered\n    | null = // unmounted\n    undefined\n\n  if (Object.keys(options).length > 0) {\n    console.warn('createRoot options are not supported yet')\n  }\n\n  return {\n    render(jsxNode: unknown) {\n      if (setJsxNode === null) {\n        // unmounted\n        throw new Error('Cannot update an unmounted root')\n      }\n      if (setJsxNode) {\n        // rendered\n        setJsxNode(jsxNode)\n      } else {\n        renderNode(\n          buildNode({\n            tag: () => {\n              const [_jsxNode, _setJsxNode] = useState(jsxNode)\n              setJsxNode = _setJsxNode\n              return _jsxNode\n            },\n            props: {},\n            // eslint-disable-next-line @typescript-eslint/no-explicit-any\n          } as any) as NodeObject,\n          element\n        )\n      }\n    },\n    unmount() {\n      setJsxNode?.(null)\n      setJsxNode = null\n    },\n  }\n}\n\n/**\n * Create a root object and hydrate app to the target element.\n * In hono/jsx/dom, hydrate is equivalent to render.\n * @param element Render target\n * @param reactNode A JSXNode to render\n * @param options Options for createRoot (not supported yet)\n * @returns Root object has `render` and `unmount` methods\n */\nexport const hydrateRoot = (\n  element: HTMLElement | DocumentFragment,\n  reactNode: Child,\n  options: RootOptions = {}\n): Root => {\n  const root = createRoot(element, options)\n  root.render(reactNode)\n  return root\n}\n\nexport default {\n  createRoot,\n  hydrateRoot,\n}\n"
  },
  {
    "path": "src/jsx/dom/components.test.tsx",
    "content": "/** @jsxImportSource ../ */\nimport { JSDOM } from 'jsdom'\nimport { ErrorBoundary as ErrorBoundaryCommon, Suspense as SuspenseCommon } from '..' // for common\n// run tests by old style jsx default\n// hono/jsx/jsx-runtime and hono/jsx/dom/jsx-runtime are tested in their respective settings\nimport { use, useState } from '../hooks'\nimport { ErrorBoundary as ErrorBoundaryDom, Suspense as SuspenseDom, render } from '.' // for dom\n\nrunner('Common', SuspenseCommon, ErrorBoundaryCommon)\nrunner('DOM', SuspenseDom, ErrorBoundaryDom)\n\nfunction runner(\n  name: string,\n  Suspense: typeof SuspenseDom,\n  ErrorBoundary: typeof ErrorBoundaryDom\n) {\n  describe(name, () => {\n    beforeAll(() => {\n      global.requestAnimationFrame = (cb) => setTimeout(cb)\n    })\n\n    describe('Suspense', () => {\n      let dom: JSDOM\n      let root: HTMLElement\n      beforeEach(() => {\n        dom = new JSDOM('<html><body><div id=\"root\"></div></body></html>', {\n          runScripts: 'dangerously',\n        })\n        global.document = dom.window.document\n        global.HTMLElement = dom.window.HTMLElement\n        global.Text = dom.window.Text\n        root = document.getElementById('root') as HTMLElement\n      })\n\n      it('has no lazy load content', async () => {\n        const App = <Suspense fallback={<div>Loading...</div>}>Hello</Suspense>\n        render(App, root)\n        expect(root.innerHTML).toBe('Hello')\n      })\n\n      it('with use()', async () => {\n        let resolve: (value: number) => void = () => {}\n        const promise = new Promise<number>((_resolve) => (resolve = _resolve))\n        const Content = () => {\n          const num = use(promise)\n          return <p>{num}</p>\n        }\n        const Component = () => {\n          return (\n            <Suspense fallback={<div>Loading...</div>}>\n              <Content />\n            </Suspense>\n          )\n        }\n        const App = <Component />\n        render(App, root)\n        expect(root.innerHTML).toBe('<div>Loading...</div>')\n        resolve(1)\n        await new Promise((resolve) => setTimeout(resolve))\n        expect(root.innerHTML).toBe('<p>1</p>')\n      })\n\n      it('with use() update', async () => {\n        const counterMap: Record<number, Promise<number>> = {}\n        const getCounter = (count: number) => (counterMap[count] ||= Promise.resolve(count + 1))\n        const Content = ({ count }: { count: number }) => {\n          const num = use(getCounter(count))\n          return (\n            <>\n              <div>{num}</div>\n            </>\n          )\n        }\n        const Component = () => {\n          const [count, setCount] = useState(0)\n          return (\n            <Suspense fallback={<div>Loading...</div>}>\n              <Content count={count} />\n              <button onClick={() => setCount(count + 1)}>Increment</button>\n            </Suspense>\n          )\n        }\n        const App = <Component />\n        render(App, root)\n        expect(root.innerHTML).toBe('<div>Loading...</div>')\n        await Promise.resolve()\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<div>1</div><button>Increment</button>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<div>Loading...</div>')\n        await Promise.resolve()\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<div>2</div><button>Increment</button>')\n      })\n\n      it('with use() nested', async () => {\n        let resolve: (value: number) => void = () => {}\n        const promise = new Promise<number>((_resolve) => (resolve = _resolve))\n        const Content = () => {\n          const num = use(promise)\n          return <p>{num}</p>\n        }\n        let resolve2: (value: number) => void = () => {}\n        const promise2 = new Promise<number>((_resolve) => (resolve2 = _resolve))\n        const Content2 = () => {\n          const num = use(promise2)\n          return <p>{num}</p>\n        }\n        const Component = () => {\n          return (\n            <Suspense fallback={<div>Loading...</div>}>\n              <Content />\n              <Suspense fallback={<div>More...</div>}>\n                <Content2 />\n              </Suspense>\n            </Suspense>\n          )\n        }\n        const App = <Component />\n        render(App, root)\n        expect(root.innerHTML).toBe('<div>Loading...</div>')\n        resolve(1)\n        await new Promise((resolve) => setTimeout(resolve))\n        expect(root.innerHTML).toBe('<p>1</p><div>More...</div>')\n        resolve2(2)\n        await new Promise((resolve) => setTimeout(resolve))\n        expect(root.innerHTML).toBe('<p>1</p><p>2</p>')\n      })\n\n      it('race condition', async () => {\n        let resolve: (value: number) => void = () => {}\n        const promise = new Promise<number>((_resolve) => (resolve = _resolve))\n        const Content = () => {\n          const num = use(promise)\n          return <p>{num}</p>\n        }\n        const Component = () => {\n          const [show, setShow] = useState(false)\n          return (\n            <div>\n              <button onClick={() => setShow((s) => !s)}>{show ? 'Hide' : 'Show'}</button>\n              {show && (\n                <Suspense fallback={<div>Loading...</div>}>\n                  <Content />\n                </Suspense>\n              )}\n            </div>\n          )\n        }\n        const App = <Component />\n        render(App, root)\n        expect(root.innerHTML).toBe('<div><button>Show</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<div><button>Hide</button><div>Loading...</div></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<div><button>Show</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<div><button>Hide</button><div>Loading...</div></div>')\n        resolve(2)\n        await Promise.resolve()\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<div><button>Hide</button><p>2</p></div>')\n      })\n\n      it('Suspense at child', async () => {\n        let resolve: (value: number) => void = () => {}\n        const promise = new Promise<number>((_resolve) => (resolve = _resolve))\n        const Content = () => {\n          const num = use(promise)\n          return <p>{num}</p>\n        }\n\n        const Component = () => {\n          return (\n            <Suspense fallback={<div>Loading...</div>}>\n              <Content />\n            </Suspense>\n          )\n        }\n        const App = () => {\n          const [show, setShow] = useState(false)\n          return (\n            <div>\n              {show && <Component />}\n              <button onClick={() => setShow(true)}>Show</button>\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(root.innerHTML).toBe('<div><button>Show</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<div><div>Loading...</div><button>Show</button></div>')\n        resolve(2)\n        await Promise.resolve()\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<div><p>2</p><button>Show</button></div>')\n      })\n\n      it('Suspense at child counter', async () => {\n        const promiseMap: Record<number, Promise<number>> = {}\n        const Counter = () => {\n          const [count, setCount] = useState(0)\n          const promise = (promiseMap[count] ||= Promise.resolve(count))\n          const value = use(promise)\n          return (\n            <>\n              <p>{value}</p>\n              <button onClick={() => setCount(count + 1)}>Increment</button>\n            </>\n          )\n        }\n        const Component = () => {\n          return (\n            <Suspense fallback={<div>Loading...</div>}>\n              <Counter />\n            </Suspense>\n          )\n        }\n        const App = () => {\n          return (\n            <div>\n              <Component />\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(root.innerHTML).toBe('<div><div>Loading...</div></div>')\n        await Promise.resolve()\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<div><p>0</p><button>Increment</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<div><div>Loading...</div></div>')\n        await Promise.resolve()\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<div><p>1</p><button>Increment</button></div>')\n      })\n    })\n\n    describe('ErrorBoundary', () => {\n      let dom: JSDOM\n      let root: HTMLElement\n      beforeEach(() => {\n        dom = new JSDOM('<html><body><div id=\"root\"></div></body></html>', {\n          runScripts: 'dangerously',\n        })\n        global.document = dom.window.document\n        global.HTMLElement = dom.window.HTMLElement\n        global.Text = dom.window.Text\n        root = document.getElementById('root') as HTMLElement\n      })\n\n      it('has no error', async () => {\n        const App = (\n          <ErrorBoundary fallback={<div>Error</div>}>\n            <div>OK</div>\n          </ErrorBoundary>\n        )\n        render(App, root)\n        expect(root.innerHTML).toBe('<div>OK</div>')\n      })\n\n      it('has error', async () => {\n        const Component = () => {\n          throw new Error('error')\n        }\n        const App = (\n          <ErrorBoundary fallback={<div>Error</div>}>\n            <Component />\n          </ErrorBoundary>\n        )\n        render(App, root)\n        expect(root.innerHTML).toBe('<div>Error</div>')\n      })\n\n      it('has no error with Suspense', async () => {\n        let resolve: (value: number) => void = () => {}\n        const promise = new Promise<number>((_resolve) => (resolve = _resolve))\n        const Content = () => {\n          const num = use(promise)\n          return <p>{num}</p>\n        }\n        const Component = () => {\n          return (\n            <ErrorBoundary fallback={<div>Error</div>}>\n              <Suspense fallback={<div>Loading...</div>}>\n                <Content />\n              </Suspense>\n            </ErrorBoundary>\n          )\n        }\n        const App = <Component />\n        render(App, root)\n        expect(root.innerHTML).toBe('<div>Loading...</div>')\n        resolve(1)\n        await new Promise((resolve) => setTimeout(resolve))\n        expect(root.innerHTML).toBe('<p>1</p>')\n      })\n\n      it('has error with Suspense', async () => {\n        let resolve: (value: number) => void = () => {}\n        const promise = new Promise<number>((_resolve) => (resolve = _resolve))\n        const Content = () => {\n          use(promise)\n          throw new Error('error')\n        }\n        const Component = () => {\n          return (\n            <ErrorBoundary fallback={<div>Error</div>}>\n              <Suspense fallback={<div>Loading...</div>}>\n                <Content />\n              </Suspense>\n            </ErrorBoundary>\n          )\n        }\n        const App = <Component />\n        render(App, root)\n        expect(root.innerHTML).toBe('<div>Loading...</div>')\n        resolve(1)\n        await new Promise((resolve) => setTimeout(resolve))\n        expect(root.innerHTML).toBe('<div>Error</div>')\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "src/jsx/dom/components.ts",
    "content": "import type { Child, FC, PropsWithChildren } from '../'\nimport type { ErrorHandler, FallbackRender } from '../components'\nimport { DOM_ERROR_HANDLER } from '../constants'\nimport { Fragment } from './jsx-runtime'\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\nexport const ErrorBoundary: FC<\n  PropsWithChildren<{\n    fallback?: Child\n    fallbackRender?: FallbackRender\n    onError?: ErrorHandler\n  }>\n> = (({ children, fallback, fallbackRender, onError }: any) => {\n  const res = Fragment({ children })\n  ;(res as any)[DOM_ERROR_HANDLER] = (err: any) => {\n    if (err instanceof Promise) {\n      throw err\n    }\n    onError?.(err)\n    return fallbackRender?.(err) || fallback\n  }\n  return res\n}) as any\n\nexport const Suspense: FC<PropsWithChildren<{ fallback: any }>> = (({\n  children,\n  fallback,\n}: any) => {\n  const res = Fragment({ children })\n  ;(res as any)[DOM_ERROR_HANDLER] = (err: any, retry: () => void) => {\n    if (!(err instanceof Promise)) {\n      throw err\n    }\n    err.finally(retry)\n    return fallback\n  }\n  return res\n}) as any\n/* eslint-enable @typescript-eslint/no-explicit-any */\n"
  },
  {
    "path": "src/jsx/dom/context.test.tsx",
    "content": "/** @jsxImportSource ../ */\nimport { JSDOM } from 'jsdom'\nimport {\n  Suspense,\n  createContext as createContextCommon,\n  use,\n  useContext as useContextCommon,\n} from '..' // for common\n// run tests by old style jsx default\n// hono/jsx/jsx-runtime and hono/jsx/dom/jsx-runtime are tested in their respective settings\nimport { createContext as createContextDom, render, useContext as useContextDom, useState } from '.' // for dom\n\nrunner('Common', createContextCommon, useContextCommon)\nrunner('DOM', createContextDom, useContextDom)\n\nfunction runner(\n  name: string,\n  createContext: typeof createContextCommon,\n  useContext: typeof useContextCommon\n) {\n  describe(name, () => {\n    beforeAll(() => {\n      global.requestAnimationFrame = (cb) => setTimeout(cb)\n    })\n\n    describe('Context', () => {\n      let dom: JSDOM\n      let root: HTMLElement\n      beforeEach(() => {\n        dom = new JSDOM('<html><body><div id=\"root\"></div></body></html>', {\n          runScripts: 'dangerously',\n        })\n        global.document = dom.window.document\n        global.HTMLElement = dom.window.HTMLElement\n        global.Text = dom.window.Text\n        root = document.getElementById('root') as HTMLElement\n      })\n\n      it('simple context', async () => {\n        const Context = createContext(0)\n        const Content = () => {\n          const num = useContext(Context)\n          return <p>{num}</p>\n        }\n        const Component = () => {\n          return (\n            <Context.Provider value={1}>\n              <Content />\n            </Context.Provider>\n          )\n        }\n        const App = <Component />\n        render(App, root)\n        expect(root.innerHTML).toBe('<p>1</p>')\n      })\n\n      it('<Context> as a provider ', async () => {\n        const Context = createContext(0)\n        const Content = () => {\n          const num = useContext(Context)\n          return <p>{num}</p>\n        }\n        const Component = () => {\n          return (\n            <Context value={1}>\n              <Content />\n            </Context>\n          )\n        }\n        const App = <Component />\n        render(App, root)\n        expect(root.innerHTML).toBe('<p>1</p>')\n      })\n\n      it('simple context with state', async () => {\n        const Context = createContext(0)\n        const Content = () => {\n          const [count, setCount] = useState(0)\n          const num = useContext(Context)\n          return (\n            <>\n              <p>\n                {num} - {count}\n              </p>\n              <button onClick={() => setCount(count + 1)}>+</button>\n            </>\n          )\n        }\n        const Component = () => {\n          return (\n            <Context.Provider value={1}>\n              <Content />\n            </Context.Provider>\n          )\n        }\n        const App = <Component />\n        render(App, root)\n        expect(root.innerHTML).toBe('<p>1 - 0</p><button>+</button>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<p>1 - 1</p><button>+</button>')\n      })\n\n      it('multiple provider', async () => {\n        const Context = createContext(0)\n        const Content = () => {\n          const num = useContext(Context)\n          return <p>{num}</p>\n        }\n        const Component = () => {\n          return (\n            <>\n              <Context.Provider value={1}>\n                <Content />\n              </Context.Provider>\n              <Context.Provider value={2}>\n                <Content />\n              </Context.Provider>\n            </>\n          )\n        }\n        const App = <Component />\n        render(App, root)\n        expect(root.innerHTML).toBe('<p>1</p><p>2</p>')\n      })\n\n      it('nested provider', async () => {\n        const Context = createContext(0)\n        const Content = () => {\n          const num = useContext(Context)\n          return <p>{num}</p>\n        }\n        const Component = () => {\n          return (\n            <>\n              <Context.Provider value={1}>\n                <Content />\n                <Context.Provider value={3}>\n                  <Content />\n                </Context.Provider>\n                <Content />\n              </Context.Provider>\n            </>\n          )\n        }\n        const App = <Component />\n        render(App, root)\n        expect(root.innerHTML).toBe('<p>1</p><p>3</p><p>1</p>')\n      })\n\n      it('inside Suspense', async () => {\n        const promise = Promise.resolve(2)\n        const AsyncComponent = () => {\n          const num = use(promise)\n          return <p>{num}</p>\n        }\n        const Context = createContext(0)\n        const Content = () => {\n          const num = useContext(Context)\n          return <p>{num}</p>\n        }\n        const Component = () => {\n          return (\n            <>\n              <Context.Provider value={1}>\n                <Content />\n                <Suspense fallback={<div>Loading...</div>}>\n                  <Context.Provider value={3}>\n                    <Content />\n                    <AsyncComponent />\n                  </Context.Provider>\n                </Suspense>\n                <Content />\n              </Context.Provider>\n            </>\n          )\n        }\n        const App = <Component />\n        render(App, root)\n        expect(root.innerHTML).toBe('<p>1</p><div>Loading...</div><p>1</p>')\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "src/jsx/dom/context.ts",
    "content": "import type { Child } from '../base'\nimport { DOM_ERROR_HANDLER } from '../constants'\nimport type { Context } from '../context'\nimport { globalContexts } from '../context'\nimport { setInternalTagFlag } from './utils'\n\nexport const createContextProviderFunction =\n  <T>(values: T[]): Function =>\n  ({ value, children }: { value: T; children: Child[] }) => {\n    if (!children) {\n      return undefined\n    }\n\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const props: { children: any } = {\n      children: [\n        {\n          tag: setInternalTagFlag(() => {\n            values.push(value)\n          }),\n          props: {},\n        },\n      ],\n    }\n    if (Array.isArray(children)) {\n      props.children.push(...children.flat())\n    } else {\n      props.children.push(children)\n    }\n    props.children.push({\n      tag: setInternalTagFlag(() => {\n        values.pop()\n      }),\n      props: {},\n    })\n    const res = { tag: '', props, type: '' }\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    ;(res as any)[DOM_ERROR_HANDLER] = (err: unknown) => {\n      values.pop()\n      throw err\n    }\n    return res\n  }\n\nexport const createContext = <T>(defaultValue: T): Context<T> => {\n  const values = [defaultValue]\n  const context: Context<T> = createContextProviderFunction(values) as Context<T>\n  context.values = values\n  context.Provider = context\n  globalContexts.push(context as Context<unknown>)\n  return context\n}\n"
  },
  {
    "path": "src/jsx/dom/css.test.tsx",
    "content": "/** @jsxImportSource ../ */\nimport { JSDOM } from 'jsdom'\n// run tests by old style jsx default\n// hono/jsx/jsx-runtime and hono/jsx/dom/jsx-runtime are tested in their respective settings\n\nimport type { JSXNode } from '..'\nimport { Style, createCssContext, css, rawCssString } from '../../helper/css'\nimport { minify } from '../../helper/css/common'\nimport { renderTest } from '../../helper/css/common.case.test'\nimport { render } from '.'\n\ndescribe('Style and css for jsx/dom', () => {\n  beforeAll(() => {\n    global.requestAnimationFrame = (cb) => setTimeout(cb)\n  })\n\n  let dom: JSDOM\n  let root: HTMLElement\n  beforeEach(() => {\n    dom = new JSDOM('<html><body><div id=\"root\"></div></body></html>', {\n      runScripts: 'dangerously',\n    })\n    global.document = dom.window.document\n    global.HTMLElement = dom.window.HTMLElement\n    global.SVGElement = dom.window.SVGElement\n    global.Text = dom.window.Text\n    root = document.getElementById('root') as HTMLElement\n  })\n\n  it('<Style />', async () => {\n    const App = () => {\n      return (\n        <div>\n          <Style />\n          <div\n            class={css`\n              color: red;\n            `}\n          >\n            red\n          </div>\n        </div>\n      )\n    }\n    render(<App />, root)\n    expect(root.innerHTML).toBe(\n      '<div><style id=\"hono-css\"></style><div class=\"css-3142110215\">red</div></div>'\n    )\n    await Promise.resolve()\n    expect(root.querySelector('style')?.sheet?.cssRules[0].cssText).toBe(\n      '.css-3142110215 {color: red;}'\n    )\n  })\n\n  it('<Style nonce=\"1234\" />', async () => {\n    const App = () => {\n      return (\n        <div>\n          <Style nonce='1234' />\n        </div>\n      )\n    }\n    render(<App />, root)\n    expect(root.innerHTML).toBe('<div><style id=\"hono-css\" nonce=\"1234\"></style></div>')\n  })\n\n  it('<Style>{css`global`}</Style>', async () => {\n    const App = () => {\n      return (\n        <div>\n          <Style>{css`\n            color: red;\n          `}</Style>\n          <div\n            class={css`\n              color: red;\n            `}\n          >\n            red\n          </div>\n        </div>\n      )\n    }\n    render(<App />, root)\n    expect(root.innerHTML).toBe(\n      '<div><style id=\"hono-css\">color:red</style><div class=\"css-3142110215\">red</div></div>'\n    )\n  })\n})\n\ndescribe('render', () => {\n  renderTest(() => {\n    const cssContext = createCssContext({ id: 'hono-css' })\n\n    const dom = new JSDOM('<html><body><div id=\"root\"></div></body></html>', {\n      runScripts: 'dangerously',\n    })\n    global.document = dom.window.document\n    global.HTMLElement = dom.window.HTMLElement\n    global.Text = dom.window.Text\n    const root = document.getElementById('root') as HTMLElement\n\n    const toString = async (node: JSXNode) => {\n      render(node, root)\n      await Promise.resolve()\n      const style = root.querySelector('style')\n      if (style) {\n        style.textContent = minify(\n          [...(style.sheet?.cssRules || [])].map((r) => r.cssText).join('') || ''\n        )\n      }\n      return root.innerHTML\n    }\n\n    return {\n      toString,\n      rawCssString,\n      ...cssContext,\n      support: { nest: false },\n    }\n  })\n})\n"
  },
  {
    "path": "src/jsx/dom/css.ts",
    "content": "/**\n * @module\n * This module provides APIs that enable `hono/jsx/dom` to support.\n */\n\nimport type { FC, PropsWithChildren } from '../'\nimport type { CssClassName, CssVariableType } from '../../helper/css/common'\nimport {\n  CLASS_NAME,\n  DEFAULT_STYLE_ID,\n  PSEUDO_GLOBAL_SELECTOR,\n  SELECTOR,\n  SELECTORS,\n  STYLE_STRING,\n  cssCommon,\n  cxCommon,\n  keyframesCommon,\n  viewTransitionCommon,\n} from '../../helper/css/common'\nexport { rawCssString } from '../../helper/css/common'\n\nconst splitRule = (rule: string): string[] => {\n  const result: string[] = []\n  let startPos = 0\n  let depth = 0\n  for (let i = 0, len = rule.length; i < len; i++) {\n    const char = rule[i]\n\n    // consume quote\n\n    if (char === \"'\" || char === '\"') {\n      const quote = char\n      i++\n      for (; i < len; i++) {\n        if (rule[i] === '\\\\') {\n          i++\n          continue\n        }\n        if (rule[i] === quote) {\n          break\n        }\n      }\n      continue\n    }\n\n    // comments are removed from the rule in advance\n    if (char === '{') {\n      depth++\n      continue\n    }\n    if (char === '}') {\n      depth--\n      if (depth === 0) {\n        result.push(rule.slice(startPos, i + 1))\n        startPos = i + 1\n      }\n      continue\n    }\n  }\n  return result\n}\n\ninterface CreateCssJsxDomObjectsType {\n  (args: { id: Readonly<string> }): readonly [\n    {\n      toString(this: CssClassName): string\n    },\n    FC<PropsWithChildren<void>>,\n  ]\n}\n\nexport const createCssJsxDomObjects: CreateCssJsxDomObjectsType = ({ id }) => {\n  let styleSheet: CSSStyleSheet | null | undefined = undefined\n  const findStyleSheet = (): [CSSStyleSheet, Set<string>] | [] => {\n    if (!styleSheet) {\n      styleSheet = document.querySelector<HTMLStyleElement>(`style#${id}`)\n        ?.sheet as CSSStyleSheet | null\n      if (styleSheet) {\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        ;(styleSheet as any).addedStyles = new Set<string>()\n      }\n    }\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    return styleSheet ? [styleSheet, (styleSheet as any).addedStyles] : []\n  }\n\n  const insertRule = (className: string, styleString: string) => {\n    const [sheet, addedStyles] = findStyleSheet()\n    if (!sheet || !addedStyles) {\n      Promise.resolve().then(() => {\n        if (!findStyleSheet()[0]) {\n          throw new Error('style sheet not found')\n        }\n        insertRule(className, styleString)\n      })\n      return\n    }\n\n    if (!addedStyles.has(className)) {\n      addedStyles.add(className)\n      ;(className.startsWith(PSEUDO_GLOBAL_SELECTOR)\n        ? splitRule(styleString)\n        : [`${className[0] === '@' ? '' : '.'}${className}{${styleString}}`]\n      ).forEach((rule) => {\n        sheet.insertRule(rule, sheet.cssRules.length)\n      })\n    }\n  }\n\n  const cssObject = {\n    toString(this: CssClassName): string {\n      const selector = this[SELECTOR]\n      insertRule(selector, this[STYLE_STRING])\n      this[SELECTORS].forEach(({ [CLASS_NAME]: className, [STYLE_STRING]: styleString }) => {\n        insertRule(className, styleString)\n      })\n\n      return this[CLASS_NAME]\n    },\n  }\n\n  const Style: FC<PropsWithChildren<{ nonce?: string }>> = ({ children, nonce }) =>\n    ({\n      tag: 'style',\n      props: {\n        id,\n        nonce,\n        children:\n          children &&\n          (Array.isArray(children) ? children : [children]).map(\n            (c) => (c as unknown as CssClassName)[STYLE_STRING]\n          ),\n      },\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    }) as any\n\n  return [cssObject, Style] as const\n}\n\ninterface CssType {\n  (strings: TemplateStringsArray, ...values: CssVariableType[]): string\n}\n\ninterface CxType {\n  (...args: (string | boolean | null | undefined)[]): string\n}\n\ninterface KeyframesType {\n  (strings: TemplateStringsArray, ...values: CssVariableType[]): CssClassName\n}\n\ninterface ViewTransitionType {\n  (strings: TemplateStringsArray, ...values: CssVariableType[]): string\n  (content: string): string\n  (): string\n}\n\ninterface DefaultContextType {\n  css: CssType\n  cx: CxType\n  keyframes: KeyframesType\n  viewTransition: ViewTransitionType\n  Style: FC<PropsWithChildren<void>>\n}\n\n/**\n * @experimental\n * `createCssContext` is an experimental feature.\n * The API might be changed.\n */\nexport const createCssContext = ({ id }: { id: Readonly<string> }): DefaultContextType => {\n  const [cssObject, Style] = createCssJsxDomObjects({ id })\n\n  const newCssClassNameObject = (cssClassName: CssClassName): string => {\n    cssClassName.toString = cssObject.toString\n    return cssClassName as unknown as string\n  }\n\n  const css: CssType = (strings, ...values) => {\n    return newCssClassNameObject(cssCommon(strings, values))\n  }\n\n  const cx: CxType = (...args) => {\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    args = cxCommon(args as any) as any\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    return css(Array(args.length).fill('') as any, ...args)\n  }\n\n  const keyframes: KeyframesType = keyframesCommon\n\n  const viewTransition: ViewTransitionType = ((\n    strings: TemplateStringsArray | string | undefined,\n    ...values: CssVariableType[]\n  ) => {\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    return newCssClassNameObject(viewTransitionCommon(strings as any, values))\n  }) as ViewTransitionType\n\n  return {\n    css,\n    cx,\n    keyframes,\n    viewTransition,\n    Style,\n  }\n}\n\nconst defaultContext: DefaultContextType = createCssContext({ id: DEFAULT_STYLE_ID })\n\n/**\n * @experimental\n * `css` is an experimental feature.\n * The API might be changed.\n */\nexport const css = defaultContext.css\n\n/**\n * @experimental\n * `cx` is an experimental feature.\n * The API might be changed.\n */\nexport const cx = defaultContext.cx\n\n/**\n * @experimental\n * `keyframes` is an experimental feature.\n * The API might be changed.\n */\nexport const keyframes = defaultContext.keyframes\n\n/**\n * @experimental\n * `viewTransition` is an experimental feature.\n * The API might be changed.\n */\nexport const viewTransition = defaultContext.viewTransition\n\n/**\n * @experimental\n * `Style` is an experimental feature.\n * The API might be changed.\n */\nexport const Style = defaultContext.Style\n"
  },
  {
    "path": "src/jsx/dom/hooks/index.test.tsx",
    "content": "/** @jsxImportSource ../../ */\nimport { JSDOM } from 'jsdom'\nimport { render, useCallback, useState } from '..'\nimport { useActionState, useFormStatus, useOptimistic } from '.'\n\ndescribe('Hooks', () => {\n  beforeAll(() => {\n    global.requestAnimationFrame = (cb) => setTimeout(cb)\n  })\n\n  let dom: JSDOM\n  let root: HTMLElement\n  beforeEach(() => {\n    dom = new JSDOM('<html><body><div id=\"root\"></div></body></html>', {\n      runScripts: 'dangerously',\n    })\n    global.document = dom.window.document\n    global.HTMLElement = dom.window.HTMLElement\n    global.SVGElement = dom.window.SVGElement\n    global.Text = dom.window.Text\n    global.FormData = dom.window.FormData\n    root = document.getElementById('root') as HTMLElement\n  })\n\n  describe('useActionState', () => {\n    it('should return initial state', () => {\n      const [state] = useActionState(() => {}, 'initial')\n      expect(state).toBe('initial')\n    })\n\n    it('should return updated state', async () => {\n      const action = vi.fn().mockReturnValue('updated')\n\n      const App = () => {\n        const [state, formAction] = useActionState(action, 'initial')\n        return (\n          <>\n            <div>{state}</div>\n            <form action={formAction}>\n              <input type='text' name='name' value='updated' />\n              <button>Submit</button>\n            </form>\n          </>\n        )\n      }\n\n      render(<App />, root)\n      expect(root.innerHTML).toBe(\n        '<div>initial</div><form><input type=\"text\" name=\"name\" value=\"updated\"><button>Submit</button></form>'\n      )\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe(\n        '<div>updated</div><form><input type=\"text\" name=\"name\" value=\"updated\"><button>Submit</button></form>'\n      )\n\n      expect(action).toHaveBeenCalledOnce()\n      const [initialState, formData] = action.mock.calls[0]\n      expect(initialState).toBe('initial')\n      expect(formData).toBeInstanceOf(FormData)\n      expect(formData.get('name')).toBe('updated')\n    })\n  })\n\n  describe('useFormStatus', () => {\n    it('should return initial state', () => {\n      const status = useFormStatus()\n      expect(status).toEqual({\n        pending: false,\n        data: null,\n        method: null,\n        action: null,\n      })\n    })\n\n    it('should return updated state', async () => {\n      let formResolve: () => void = () => {}\n      const formPromise = new Promise<void>((r) => (formResolve = r))\n      let status: ReturnType<typeof useFormStatus> | undefined\n      const Status = () => {\n        status = useFormStatus()\n        return null\n      }\n      const App = () => {\n        const [, setCount] = useState(0)\n        const action = useCallback(() => {\n          setCount((count) => count + 1)\n          return formPromise\n        }, [])\n        return (\n          <>\n            <form action={action}>\n              <Status />\n              <input type='text' name='name' value='updated' />\n              <button>Submit</button>\n            </form>\n          </>\n        )\n      }\n\n      render(<App />, root)\n      expect(root.innerHTML).toBe(\n        '<form><input type=\"text\" name=\"name\" value=\"updated\"><button>Submit</button></form>'\n      )\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      await Promise.resolve()\n      await Promise.resolve()\n      await Promise.resolve()\n      expect(status).toEqual({\n        pending: true,\n        data: expect.any(FormData),\n        method: 'post',\n        action: expect.any(Function),\n      })\n      formResolve?.()\n      await Promise.resolve()\n      await Promise.resolve()\n      expect(status).toEqual({\n        pending: false,\n        data: null,\n        method: null,\n        action: null,\n      })\n    })\n  })\n\n  describe('useOptimistic', () => {\n    it('should return updated state', async () => {\n      let formResolve: () => void = () => {}\n      const formPromise = new Promise<void>((r) => (formResolve = r))\n      const App = () => {\n        const [count, setCount] = useState(0)\n        const [optimisticCount, setOptimisticCount] = useOptimistic(count, (_c, n: number) => n)\n        const action = useCallback(async () => {\n          setOptimisticCount(count + 1)\n          await formPromise\n          setCount((count) => count + 2)\n        }, [])\n\n        return (\n          <>\n            <form action={action}>\n              <div>{optimisticCount}</div>\n              <input type='text' name='name' value='updated' />\n              <button>Submit</button>\n            </form>\n          </>\n        )\n      }\n\n      render(<App />, root)\n      expect(root.innerHTML).toBe(\n        '<form><div>0</div><input type=\"text\" name=\"name\" value=\"updated\"><button>Submit</button></form>'\n      )\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe(\n        '<form><div>1</div><input type=\"text\" name=\"name\" value=\"updated\"><button>Submit</button></form>'\n      )\n      formResolve?.()\n      await Promise.resolve()\n      await Promise.resolve()\n      await Promise.resolve()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe(\n        '<form><div>2</div><input type=\"text\" name=\"name\" value=\"updated\"><button>Submit</button></form>'\n      )\n    })\n  })\n})\n"
  },
  {
    "path": "src/jsx/dom/hooks/index.ts",
    "content": "/**\n * Provide hooks used only in jsx/dom\n */\n\nimport { PERMALINK } from '../../constants'\nimport type { Context } from '../../context'\nimport { useContext } from '../../context'\nimport { useCallback, useState } from '../../hooks'\nimport { createContext } from '../context'\n\ntype FormStatus =\n  | {\n      pending: false\n      data: null\n      method: null\n      action: null\n    }\n  | {\n      pending: true\n      data: FormData\n      method: 'get' | 'post'\n      action: string | ((formData: FormData) => void | Promise<void>)\n    }\nexport const FormContext: Context<FormStatus> = createContext<FormStatus>({\n  pending: false,\n  data: null,\n  method: null,\n  action: null,\n})\n\nconst actions: Set<Promise<unknown>> = new Set()\nexport const registerAction = (action: Promise<unknown>) => {\n  actions.add(action)\n  action.finally(() => actions.delete(action))\n}\n\n/**\n * This hook returns the current form status\n * @returns FormStatus\n */\nexport const useFormStatus = (): FormStatus => {\n  return useContext(FormContext)\n}\n\n/**\n * This hook returns the current state and a function to update the state optimistically\n * The current state is updated optimistically and then reverted to the original state when all actions are resolved\n * @param state\n * @param updateState\n * @returns [T, (action: N) => void]\n */\nexport const useOptimistic = <T, N>(\n  state: T,\n  updateState: (currentState: T, action: N) => T\n): [T, (action: N) => void] => {\n  const [optimisticState, setOptimisticState] = useState(state)\n  if (actions.size > 0) {\n    Promise.all(actions).finally(() => {\n      setOptimisticState(state)\n    })\n  } else {\n    setOptimisticState(state)\n  }\n\n  const cb = useCallback((newData: N) => {\n    setOptimisticState((currentState) => updateState(currentState, newData))\n  }, [])\n\n  return [optimisticState, cb]\n}\n\n/**\n * This hook returns the current state and a function to update the state by form action\n * @param fn\n * @param initialState\n * @param permalink\n * @returns [T, (data: FormData) => void]\n */\nexport const useActionState = <T>(\n  fn: Function,\n  initialState: T,\n  permalink?: string\n): [T, Function] => {\n  const [state, setState] = useState(initialState)\n  const actionState = async (data: FormData) => {\n    setState(await fn(state, data))\n  }\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  ;(actionState as any)[PERMALINK] = permalink\n  return [state, actionState]\n}\n"
  },
  {
    "path": "src/jsx/dom/index.test.tsx",
    "content": "/** @jsxImportSource ../ */\nimport { JSDOM } from 'jsdom'\nimport type { Child, FC } from '..'\n// run tests by old style jsx default\n// hono/jsx/jsx-runtime and hono/jsx/dom/jsx-runtime are tested in their respective settings\nimport { createElement, jsx } from '..'\nimport type { RefObject } from '../hooks'\nimport {\n  createRef,\n  useCallback,\n  useEffect,\n  useInsertionEffect,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n} from '../hooks'\nimport DefaultExport, {\n  cloneElement,\n  cloneElement as cloneElementForDom,\n  createElement as createElementForDom,\n  createContext,\n  useContext,\n  createPortal,\n  flushSync,\n  isValidElement,\n  memo,\n  render,\n  version,\n} from '.'\n\ndescribe('Common', () => {\n  ;[createElement, createElementForDom].forEach((createElement) => {\n    describe('createElement', () => {\n      it('simple', () => {\n        const element = createElement('div', { id: 'app' })\n        expect(element).toEqual(expect.objectContaining({ tag: 'div', props: { id: 'app' } }))\n      })\n\n      it('children', () => {\n        const element = createElement('div', { id: 'app' }, 'Hello')\n        expect(element).toEqual(\n          expect.objectContaining({ tag: 'div', props: { id: 'app', children: 'Hello' } })\n        )\n      })\n\n      it('multiple children', () => {\n        const element = createElement('div', { id: 'app' }, 'Hello', 'World')\n        expect(element).toEqual(\n          expect.objectContaining({\n            tag: 'div',\n            props: { id: 'app', children: ['Hello', 'World'] },\n          })\n        )\n      })\n\n      it('key', () => {\n        const element = createElement('div', { id: 'app', key: 'key' })\n        expect(element).toEqual(\n          expect.objectContaining({ tag: 'div', props: { id: 'app' }, key: 'key' })\n        )\n      })\n\n      it('ref', () => {\n        const ref = { current: null }\n        const element = createElement('div', { id: 'app', ref })\n        expect(element).toEqual(expect.objectContaining({ tag: 'div', props: { id: 'app', ref } }))\n        expect(element.ref).toBe(ref)\n      })\n\n      it('type', () => {\n        const element = createElement('div', { id: 'app' })\n        expect(element.type).toBe('div')\n      })\n\n      it('null props', () => {\n        const element = createElement('div', null)\n        expect(element).toEqual(expect.objectContaining({ tag: 'div', props: {} }))\n      })\n    })\n  })\n})\n\ndescribe('DOM', () => {\n  beforeAll(() => {\n    global.requestAnimationFrame = (cb) => setTimeout(cb)\n  })\n\n  let dom: JSDOM\n  let root: HTMLElement\n  beforeEach(() => {\n    dom = new JSDOM('<html><body><div id=\"root\"></div></body></html>', {\n      runScripts: 'dangerously',\n    })\n    global.document = dom.window.document\n    global.HTMLElement = dom.window.HTMLElement\n    global.SVGElement = dom.window.SVGElement\n    global.Text = dom.window.Text\n    root = document.getElementById('root') as HTMLElement\n  })\n\n  it('simple App', () => {\n    const App = <h1>Hello</h1>\n    render(App, root)\n    expect(root.innerHTML).toBe('<h1>Hello</h1>')\n  })\n\n  it('replace', () => {\n    dom.window.document.body.innerHTML = '<div id=\"root\">Existing content</div>'\n    root = document.getElementById('root') as HTMLElement\n    const App = <h1>Hello</h1>\n    render(App, root)\n    expect(root.innerHTML).toBe('<h1>Hello</h1>')\n  })\n\n  it('render text directly', () => {\n    const App = () => <>{'Hello'}</>\n    render(<App />, root)\n    expect(root.innerHTML).toBe('Hello')\n  })\n\n  describe('performance', () => {\n    it('should be O(N) for each additional element', () => {\n      const App = () => (\n        <>\n          {Array.from({ length: 1000 }, (_, i) => (\n            <div>\n              <span>{i}</span>\n            </div>\n          ))}\n        </>\n      )\n      render(<App />, root)\n      expect(root.innerHTML).toBe(\n        Array.from({ length: 1000 }, (_, i) => `<div><span>${i}</span></div>`).join('')\n      )\n    })\n  })\n\n  describe('attribute', () => {\n    it('simple', () => {\n      const App = () => <div id='app' class='app' />\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div id=\"app\" class=\"app\"></div>')\n    })\n\n    it('boolean', () => {\n      const App = () => <div hidden />\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div hidden=\"\"></div>')\n    })\n\n    it('style', () => {\n      const App = () => <div style={{ fontSize: '10px' }} />\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div style=\"font-size: 10px;\"></div>')\n    })\n\n    it('update style', () => {\n      const App = () => <div style={{ fontSize: '10px' }} />\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div style=\"font-size: 10px;\"></div>')\n    })\n\n    it('style with CSS variables - 1', () => {\n      const App = () => <div style={{ '--my-var-1': '15px' }} />\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div style=\"--my-var-1: 15px;\"></div>')\n    })\n\n    it('style with CSS variables - 2', () => {\n      const App = () => <div style={{ '--myVar-2': '20px' }} />\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div style=\"--myVar-2: 20px;\"></div>')\n    })\n\n    it('style with string', async () => {\n      const App = () => {\n        const [style, setStyle] = useState<{ fontSize?: string; color?: string }>({\n          fontSize: '10px',\n        })\n        return <div style={style} onClick={() => setStyle({ color: 'red' })} />\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div style=\"font-size: 10px;\"></div>')\n      root.querySelector('div')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div style=\"color: red;\"></div>')\n    })\n\n    it('toString() is called', () => {\n      const App = () => <div x-value={{ toString: () => 'value' }} />\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div x-value=\"value\"></div>')\n    })\n\n    it('ref', () => {\n      const App = () => {\n        const ref = useRef<HTMLDivElement>(null)\n        return <div ref={ref} />\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div></div>')\n    })\n\n    it('ref with callback', () => {\n      const ref = useRef<HTMLDivElement>(null)\n      const App = () => {\n        return <div ref={(node: HTMLDivElement) => (ref.current = node)} />\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div></div>')\n      expect(ref.current).toBeInstanceOf(HTMLElement)\n    })\n\n    it('ref with null', () => {\n      const App = () => {\n        return <div ref={null} />\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div></div>')\n    })\n\n    it('remove node with ref object', async () => {\n      const ref = createRef<HTMLDivElement>()\n      const App = () => {\n        const [show, setShow] = useState(true)\n        return (\n          <>\n            {show && <div ref={ref} />}\n            <button onClick={() => setShow(false)}>remove</button>\n          </>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div></div><button>remove</button>')\n      expect(ref.current).toBeInstanceOf(dom.window.HTMLDivElement)\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<button>remove</button>')\n      expect(ref.current).toBe(null)\n    })\n\n    it('remove node with ref function', async () => {\n      const ref = vi.fn()\n      const App = () => {\n        const [show, setShow] = useState(true)\n        return (\n          <>\n            {show && <div ref={ref} />}\n            <button onClick={() => setShow(false)}>remove</button>\n          </>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div></div><button>remove</button>')\n      expect(ref).toHaveBeenLastCalledWith(expect.any(dom.window.HTMLDivElement))\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<button>remove</button>')\n      expect(ref).toHaveBeenLastCalledWith(null)\n    })\n\n    it('ref cleanup function', async () => {\n      const cleanup = vi.fn()\n      const ref = vi.fn().mockReturnValue(cleanup)\n      const App = () => {\n        const [show, setShow] = useState(true)\n        return (\n          <>\n            {show && <div ref={ref} />}\n            <button onClick={() => setShow(false)}>remove</button>\n          </>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div></div><button>remove</button>')\n      expect(ref).toHaveBeenLastCalledWith(expect.any(dom.window.HTMLDivElement))\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<button>remove</button>')\n      expect(ref).toBeCalledTimes(1)\n      expect(cleanup).toBeCalledTimes(1)\n    })\n  })\n\n  describe('child component', () => {\n    it('simple', async () => {\n      const Child = vi.fn(({ count }: { count: number }) => <div>{count}</div>)\n      const App = () => {\n        const [count, setCount] = useState(0)\n        return (\n          <>\n            <div>{count}</div>\n            <Child count={Math.floor(count / 2)} />\n            <button onClick={() => setCount(count + 1)}>+</button>\n          </>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div>0</div><div>0</div><button>+</button>')\n      expect(Child).toBeCalledTimes(1)\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div>1</div><div>0</div><button>+</button>')\n      expect(Child).toBeCalledTimes(2)\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div>2</div><div>1</div><button>+</button>')\n      expect(Child).toBeCalledTimes(3)\n    })\n\n    it('multiple children', async () => {\n      const Child = ({ name }: { name: string }) => {\n        const [count, setCount] = useState(0)\n        return (\n          <div>\n            <div>\n              {name} {count}\n            </div>\n            <button onClick={() => setCount(count + 1)}>+</button>\n          </div>\n        )\n      }\n      const App = () => {\n        const [count, setCount] = useState(0)\n        return (\n          <div>\n            <div>parent {count}</div>\n            <button onClick={() => setCount(count + 1)}>+</button>\n            <div>\n              <Child name='child 1' />\n              <Child name='child 2' />\n              <Child name='child 3' />\n            </div>\n          </div>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe(\n        '<div><div>parent 0</div><button>+</button><div><div><div>child 1 0</div><button>+</button></div><div><div>child 2 0</div><button>+</button></div><div><div>child 3 0</div><button>+</button></div></div></div>'\n      )\n      const [parentButton, child1Button, child2Button, child3Button] =\n        root.querySelectorAll('button')\n      parentButton?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe(\n        '<div><div>parent 1</div><button>+</button><div><div><div>child 1 0</div><button>+</button></div><div><div>child 2 0</div><button>+</button></div><div><div>child 3 0</div><button>+</button></div></div></div>'\n      )\n      child2Button?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe(\n        '<div><div>parent 1</div><button>+</button><div><div><div>child 1 0</div><button>+</button></div><div><div>child 2 1</div><button>+</button></div><div><div>child 3 0</div><button>+</button></div></div></div>'\n      )\n      child1Button?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe(\n        '<div><div>parent 1</div><button>+</button><div><div><div>child 1 1</div><button>+</button></div><div><div>child 2 1</div><button>+</button></div><div><div>child 3 0</div><button>+</button></div></div></div>'\n      )\n      child3Button?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe(\n        '<div><div>parent 1</div><button>+</button><div><div><div>child 1 1</div><button>+</button></div><div><div>child 2 1</div><button>+</button></div><div><div>child 3 1</div><button>+</button></div></div></div>'\n      )\n    })\n\n    it('keeps sibling order when a null sibling exists after parent update', async () => {\n      const Empty = () => null\n      const Child = () => {\n        const [count, setCount] = useState(0)\n        return count === 0 ? (\n          <>\n            <div>A0</div>\n            <button id='child' onClick={() => setCount(1)}>\n              child+\n            </button>\n          </>\n        ) : (\n          <>\n            <span>A1</span>\n            <span>A2</span>\n          </>\n        )\n      }\n      const App = () => {\n        const [count, setCount] = useState(0)\n        return (\n          <>\n            <Child />\n            <Empty />\n            <div id='tail'>T{count}</div>\n            <button id='parent' onClick={() => setCount(count + 1)}>\n              parent+\n            </button>\n          </>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe(\n        '<div>A0</div><button id=\"child\">child+</button><div id=\"tail\">T0</div><button id=\"parent\">parent+</button>'\n      )\n      root.querySelector<HTMLButtonElement>('#parent')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe(\n        '<div>A0</div><button id=\"child\">child+</button><div id=\"tail\">T1</div><button id=\"parent\">parent+</button>'\n      )\n      root.querySelector<HTMLButtonElement>('#child')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe(\n        '<span>A1</span><span>A2</span><div id=\"tail\">T1</div><button id=\"parent\">parent+</button>'\n      )\n    })\n\n    it('multiple children with dynamic addition and rerender', async () => {\n      const Child = ({ name }: { name: string }) => {\n        const [count, setCount] = useState(0)\n        return (\n          <div>\n            <div>\n              {name} {count}\n            </div>\n            <button onClick={() => setCount(count + 1)}>+</button>\n          </div>\n        )\n      }\n      const App = () => {\n        const [showThird, setShowThird] = useState(false)\n        return (\n          <div>\n            <Child name='child 1' />\n            <Child name='child 2' />\n            {showThird && <Child name='child 3' />}\n            <button onClick={() => setShowThird(true)}>add</button>\n          </div>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe(\n        '<div><div><div>child 1 0</div><button>+</button></div><div><div>child 2 0</div><button>+</button></div><button>add</button></div>'\n      )\n      // add child 3\n      let buttons = root.querySelectorAll('button')\n      buttons[2]?.click() // add\n      await Promise.resolve()\n      expect(root.innerHTML).toBe(\n        '<div><div><div>child 1 0</div><button>+</button></div><div><div>child 2 0</div><button>+</button></div><div><div>child 3 0</div><button>+</button></div><button>add</button></div>'\n      )\n      // click child 1\n      buttons = root.querySelectorAll('button')\n      buttons[0]?.click() // child 1\n      await Promise.resolve()\n      expect(root.innerHTML).toBe(\n        '<div><div><div>child 1 1</div><button>+</button></div><div><div>child 2 0</div><button>+</button></div><div><div>child 3 0</div><button>+</button></div><button>add</button></div>'\n      )\n      // click child 2 - verify child 2 and child 3 do not swap positions\n      buttons = root.querySelectorAll('button')\n      buttons[1]?.click() // child 2\n      await Promise.resolve()\n      expect(root.innerHTML).toBe(\n        '<div><div><div>child 1 1</div><button>+</button></div><div><div>child 2 1</div><button>+</button></div><div><div>child 3 0</div><button>+</button></div><button>add</button></div>'\n      )\n    })\n  })\n\n  describe('defaultProps', () => {\n    it('simple', () => {\n      const App: FC<{ name?: string }> = ({ name }) => <div>{name}</div>\n      App.defaultProps = { name: 'default' }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div>default</div>')\n    })\n\n    it('override', () => {\n      const App: FC<{ name: string }> = ({ name }) => <div>{name}</div>\n      App.defaultProps = { name: 'default' }\n      render(<App name='override' />, root)\n      expect(root.innerHTML).toBe('<div>override</div>')\n    })\n  })\n\n  describe('replace content', () => {\n    it('text to text', async () => {\n      let setCount: (count: number) => void = () => {}\n      const App = () => {\n        const [count, _setCount] = useState(0)\n        setCount = _setCount\n        return <>{count}</>\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('0')\n      setCount(1)\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('1')\n    })\n\n    it('text to element', async () => {\n      let setCount: (count: number) => void = () => {}\n      const App = () => {\n        const [count, _setCount] = useState(0)\n        setCount = _setCount\n        return count === 0 ? <>{count}</> : <div>{count}</div>\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('0')\n      setCount(1)\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div>1</div>')\n    })\n\n    it('element to element', async () => {\n      let setCount: (count: number) => void = () => {}\n      const App = () => {\n        const [count, _setCount] = useState(0)\n        setCount = _setCount\n        return <div>{count}</div>\n      }\n      const app = <App />\n      render(app, root)\n      expect(root.innerHTML).toBe('<div>0</div>')\n\n      const insertBeforeSpy = vi.spyOn(dom.window.Node.prototype, 'insertBefore')\n      setCount(1)\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div>1</div>')\n      expect(insertBeforeSpy).not.toHaveBeenCalled()\n    })\n\n    it('element to text to element', async () => {\n      let setCount: (count: number) => void = () => {}\n      const App = () => {\n        const [count, _setCount] = useState(0)\n        setCount = _setCount\n        return count % 2 === 0 ? <div>{count}</div> : <>{count}</>\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div>0</div>')\n      setCount(1)\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('1')\n      setCount(2)\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div>2</div>')\n    })\n\n    it('text to child component to text', async () => {\n      let setCount: (count: number) => void = () => {}\n      const Child = () => {\n        return <div>Child</div>\n      }\n      const App = () => {\n        const [count, _setCount] = useState(0)\n        setCount = _setCount\n        return count % 2 === 0 ? <>{count}</> : <Child />\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('0')\n      setCount(1)\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div>Child</div>')\n      setCount(2)\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('2')\n    })\n\n    it('one child is updated', async () => {\n      let setCount: (count: number) => void = () => {}\n      const App = () => {\n        const [count, _setCount] = useState(0)\n        setCount = _setCount\n        return <div>{count}</div>\n      }\n      const app = (\n        <>\n          <App />\n          <div>Footer</div>\n        </>\n      )\n      render(app, root)\n      expect(root.innerHTML).toBe('<div>0</div><div>Footer</div>')\n\n      const insertBeforeSpy = vi.spyOn(dom.window.Node.prototype, 'insertBefore')\n      setCount(1)\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div>1</div><div>Footer</div>')\n      expect(insertBeforeSpy).not.toHaveBeenCalled()\n    })\n\n    it('should not call insertBefore for unchanged complex dom tree', async () => {\n      let setCount: (count: number) => void = () => {}\n      const App = () => {\n        const [count, _setCount] = useState(0)\n        setCount = _setCount\n        return (\n          <form>\n            <div>\n              <label>label</label>\n              <input />\n            </div>\n            <p>{count}</p>\n          </form>\n        )\n      }\n      const app = <App />\n\n      render(app, root)\n      expect(root.innerHTML).toBe('<form><div><label>label</label><input></div><p>0</p></form>')\n\n      const insertBeforeSpy = vi.spyOn(dom.window.Node.prototype, 'insertBefore')\n      setCount(1)\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<form><div><label>label</label><input></div><p>1</p></form>')\n      expect(insertBeforeSpy).not.toHaveBeenCalled()\n    })\n\n    it('should not call textContent for unchanged text', async () => {\n      let setCount: (count: number) => void = () => {}\n      const App = () => {\n        const [count, _setCount] = useState(0)\n        setCount = _setCount\n        return (\n          <>\n            <span>hono</span>\n            <input value={count} />\n          </>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<span>hono</span><input value=\"0\">')\n      setCount(1)\n\n      const textContentSpy = vi.fn()\n      Object.defineProperty(dom.window.Text.prototype, 'textContent', {\n        set: textContentSpy,\n      })\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<span>hono</span><input value=\"1\">')\n      expect(textContentSpy).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('children', () => {\n    it('element', async () => {\n      const Container = ({ children }: { children: Child }) => <div>{children}</div>\n      const App = () => (\n        <Container>\n          <span>Content</span>\n        </Container>\n      )\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><span>Content</span></div>')\n    })\n\n    it('array', async () => {\n      const Container = ({ children }: { children: Child }) => <div>{children}</div>\n      const App = () => <Container>{[<span>1</span>, <span>2</span>]}</Container>\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><span>1</span><span>2</span></div>')\n    })\n\n    it('empty array and non-empty array', async () => {\n      const App = () => (\n        <div>\n          {[]}\n          {[<span>1</span>]}\n        </div>\n      )\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><span>1</span></div>')\n    })\n\n    it('nested array', async () => {\n      const nestedChildren: Child = [[[<span>1</span>], <span>2</span>]]\n      const App = () => <div>{nestedChildren}</div>\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><span>1</span><span>2</span></div>')\n    })\n\n    it('sparse array with nested child', async () => {\n      const sparseChildren: Child[] = []\n      sparseChildren[1] = [<span>1</span>]\n      const App = () => <div>{sparseChildren}</div>\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><span>1</span></div>')\n    })\n\n    it('toggle empty array and non-empty array on update', async () => {\n      let setVisible: (value: boolean) => void = () => {}\n      const App = () => {\n        const [visible, _setVisible] = useState(false)\n        setVisible = _setVisible\n        return (\n          <div>\n            {visible ? [] : [<span key='a'>A</span>]}\n            {visible ? [<span key='b'>B</span>] : []}\n          </div>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><span>A</span></div>')\n\n      setVisible(true)\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><span>B</span></div>')\n\n      setVisible(false)\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><span>A</span></div>')\n    })\n\n    it('reshape nested array on update', async () => {\n      let setPattern: (value: number) => void = () => {}\n      const App = () => {\n        const [pattern, _setPattern] = useState(0)\n        setPattern = _setPattern\n        const children: Child =\n          pattern === 0\n            ? [[<span key='a'>A</span>], <span key='b'>B</span>]\n            : [<span key='a'>A</span>, [<span key='b'>B</span>]]\n        return <div>{children}</div>\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><span>A</span><span>B</span></div>')\n\n      setPattern(1)\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><span>A</span><span>B</span></div>')\n\n      setPattern(0)\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><span>A</span><span>B</span></div>')\n    })\n\n    it('use the same children multiple times', async () => {\n      const MultiChildren = ({ children }: { children: Child }) => (\n        <>\n          {children}\n          <div>{children}</div>\n        </>\n      )\n      const App = () => (\n        <MultiChildren>\n          <span>Content</span>\n        </MultiChildren>\n      )\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<span>Content</span><div><span>Content</span></div>')\n    })\n  })\n\n  describe('update properties', () => {\n    describe('input', () => {\n      it('value', async () => {\n        let setValue: (value: string) => void = () => {}\n        const App = () => {\n          const [value, _setValue] = useState('a')\n          setValue = _setValue\n          return <input value={value} />\n        }\n        render(<App />, root)\n        expect(root.innerHTML).toBe('<input value=\"a\">')\n        const valueSpy = vi.fn()\n        Object.defineProperty(dom.window.HTMLInputElement.prototype, 'value', {\n          set: valueSpy,\n        })\n        setValue('b')\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<input value=\"b\">')\n        expect(valueSpy).toHaveBeenCalledWith('b')\n      })\n\n      it('assign undefined', async () => {\n        let setValue: (value: string | undefined) => void = () => {}\n        const App = () => {\n          const [value, _setValue] = useState<string | undefined>('a')\n          setValue = _setValue\n          return <input value={value} />\n        }\n        render(<App />, root)\n        expect(root.innerHTML).toBe('<input value=\"a\">')\n        const valueSpy = vi.fn()\n        Object.defineProperty(dom.window.HTMLInputElement.prototype, 'value', {\n          set: valueSpy,\n        })\n        setValue(undefined)\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<input>')\n        expect(valueSpy).toHaveBeenCalledWith(null) // assign null means empty string\n      })\n\n      it('checked', async () => {\n        let setValue: (value: string) => void = () => {}\n        const App = () => {\n          const [value, _setValue] = useState('a')\n          setValue = _setValue\n          return <input type='checkbox' checked={value === 'b'} />\n        }\n        render(<App />, root)\n        expect(root.innerHTML).toBe('<input type=\"checkbox\">')\n        const checkedSpy = vi.fn()\n        Object.defineProperty(dom.window.HTMLInputElement.prototype, 'checked', {\n          set: checkedSpy,\n        })\n        setValue('b')\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<input type=\"checkbox\" checked=\"\">')\n        expect(checkedSpy).toHaveBeenCalledWith(true)\n        setValue('a')\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<input type=\"checkbox\">')\n        expect(checkedSpy).toHaveBeenCalledWith(false)\n      })\n    })\n\n    describe('textarea', () => {\n      it('value', async () => {\n        let setValue: (value: string) => void = () => {}\n        const App = () => {\n          const [value, _setValue] = useState('a')\n          setValue = _setValue\n          return <textarea value={value} />\n        }\n        render(<App />, root)\n        expect(root.innerHTML).toBe('<textarea>a</textarea>')\n        const valueSpy = vi.fn()\n        Object.defineProperty(dom.window.HTMLTextAreaElement.prototype, 'value', {\n          set: valueSpy,\n        })\n        setValue('b')\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<textarea>b</textarea>')\n        expect(valueSpy).toHaveBeenCalledWith('b')\n      })\n\n      it('assign undefined', async () => {\n        let setValue: (value: string | undefined) => void = () => {}\n        const App = () => {\n          const [value, _setValue] = useState<string | undefined>('a')\n          setValue = _setValue\n          return <textarea value={value} />\n        }\n        render(<App />, root)\n        expect(root.innerHTML).toBe('<textarea>a</textarea>')\n        const valueSpy = vi.fn()\n        Object.defineProperty(dom.window.HTMLTextAreaElement.prototype, 'value', {\n          set: valueSpy,\n        })\n        setValue(undefined)\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<textarea></textarea>')\n        expect(valueSpy).toHaveBeenCalledWith(null) // assign null means empty string\n      })\n    })\n\n    describe('select', () => {\n      it('value', async () => {\n        let setValue: (value: string) => void = () => {}\n        const App = () => {\n          const [value, _setValue] = useState('a')\n          setValue = _setValue\n          return (\n            <select value={value}>\n              <option value='a'>A</option>\n              <option value='b'>B</option>\n              <option value='c'>C</option>\n            </select>\n          )\n        }\n        render(<App />, root)\n        expect(root.innerHTML).toBe(\n          '<select><option value=\"a\">A</option><option value=\"b\">B</option><option value=\"c\">C</option></select>'\n        )\n        const valueSpy = vi.fn()\n        Object.defineProperty(dom.window.HTMLSelectElement.prototype, 'value', {\n          set: valueSpy,\n        })\n        setValue('b')\n        await Promise.resolve()\n        expect(valueSpy).toHaveBeenCalledWith('b')\n      })\n\n      it('invalid value', async () => {\n        let setValue: (value: string) => void = () => {}\n        const App = () => {\n          const [value, _setValue] = useState('a')\n          setValue = _setValue\n          return (\n            <select value={value}>\n              <option value='a'>A</option>\n              <option value='b'>B</option>\n              <option value='c'>C</option>\n            </select>\n          )\n        }\n        render(<App />, root)\n        expect(root.innerHTML).toBe(\n          '<select><option value=\"a\">A</option><option value=\"b\">B</option><option value=\"c\">C</option></select>'\n        )\n        setValue('z')\n        await Promise.resolve()\n        const select = root.querySelector('select') as HTMLSelectElement\n        expect(select.value).toBe('a') // invalid value is ignored\n      })\n\n      it('assign undefined', async () => {\n        let setValue: (value: string | undefined) => void = () => {}\n        const App = () => {\n          const [value, _setValue] = useState<string | undefined>('a')\n          setValue = _setValue\n          return (\n            <select value={value}>\n              <option value='a'>A</option>\n              <option value='b'>B</option>\n              <option value='c'>C</option>\n            </select>\n          )\n        }\n        render(<App />, root)\n        expect(root.innerHTML).toBe(\n          '<select><option value=\"a\">A</option><option value=\"b\">B</option><option value=\"c\">C</option></select>'\n        )\n        setValue(undefined)\n        await Promise.resolve()\n        const select = root.querySelector('select') as HTMLSelectElement\n        expect(select.value).toBe('a') // select the first option\n      })\n    })\n\n    describe('option', () => {\n      it('selected', async () => {\n        let setValue: (value: string) => void = () => {}\n        const App = () => {\n          const [value, _setValue] = useState('a')\n          setValue = _setValue\n          return (\n            <select>\n              <option value='a'>A</option>\n              <option value='b' selected={value === 'b'}>\n                B\n              </option>\n              <option value='c'>C</option>\n            </select>\n          )\n        }\n        render(<App />, root)\n        expect(root.innerHTML).toBe(\n          '<select><option value=\"a\">A</option><option value=\"b\">B</option><option value=\"c\">C</option></select>'\n        )\n        setValue('b')\n        await Promise.resolve()\n        expect(root.innerHTML).toBe(\n          '<select><option value=\"a\">A</option><option value=\"b\" selected=\"\">B</option><option value=\"c\">C</option></select>'\n        )\n        const select = root.querySelector('select') as HTMLSelectElement\n        expect(select.value).toBe('b')\n        setValue('a')\n        await Promise.resolve()\n        expect(root.innerHTML).toBe(\n          '<select><option value=\"a\">A</option><option value=\"b\">B</option><option value=\"c\">C</option></select>'\n        )\n        expect(select.value).toBe('a')\n      })\n    })\n  })\n\n  describe('dangerouslySetInnerHTML', () => {\n    it('string', () => {\n      const App = () => {\n        return <div dangerouslySetInnerHTML={{ __html: '<p>Hello</p>' }} />\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><p>Hello</p></div>')\n    })\n  })\n\n  describe('Event', () => {\n    it('bubbling phase', async () => {\n      const clicked: string[] = []\n      const App = () => {\n        return (\n          <div\n            onClick={() => {\n              clicked.push('div')\n            }}\n          >\n            <button\n              onClick={() => {\n                clicked.push('button')\n              }}\n            >\n              Click\n            </button>\n          </div>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button>Click</button></div>')\n      root.querySelector('button')?.click()\n      expect(clicked).toEqual(['button', 'div'])\n    })\n\n    it('ev.stopPropagation()', async () => {\n      const clicked: string[] = []\n      const App = () => {\n        return (\n          <div\n            onClick={() => {\n              clicked.push('div')\n            }}\n          >\n            <button\n              onClick={(ev) => {\n                ev.stopPropagation()\n                clicked.push('button')\n              }}\n            >\n              Click\n            </button>\n          </div>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button>Click</button></div>')\n      root.querySelector('button')?.click()\n      expect(clicked).toEqual(['button'])\n    })\n\n    it('capture phase', async () => {\n      const clicked: string[] = []\n      const App = () => {\n        return (\n          <div\n            onClickCapture={(ev) => {\n              ev.stopPropagation()\n              clicked.push('div')\n            }}\n          >\n            <button\n              onClickCapture={() => {\n                clicked.push('button')\n              }}\n            >\n              Click\n            </button>\n          </div>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button>Click</button></div>')\n      root.querySelector('button')?.click()\n      expect(clicked).toEqual(['div'])\n    })\n\n    it('remove capture phase event', async () => {\n      const clicked: string[] = []\n      const App = () => {\n        const [canceled, setCanceled] = useState(false)\n        return (\n          <div\n            {...(canceled\n              ? {}\n              : {\n                  onClickCapture: () => {\n                    clicked.push('div')\n                  },\n                })}\n          >\n            <button\n              onClickCapture={() => {\n                setCanceled(true)\n                clicked.push('button')\n              }}\n            >\n              Click\n            </button>\n          </div>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button>Click</button></div>')\n      root.querySelector('button')?.click()\n      expect(clicked).toEqual(['div', 'button'])\n      await Promise.resolve()\n      root.querySelector('button')?.click()\n      expect(clicked).toEqual(['div', 'button', 'button'])\n    })\n\n    it('onGotPointerCapture', async () => {\n      const App = () => {\n        return <div onGotPointerCapture={() => {}}></div>\n      }\n      const addEventListenerSpy = vi.spyOn(dom.window.Node.prototype, 'addEventListener')\n      render(<App />, root)\n      expect(addEventListenerSpy).toHaveBeenCalledOnce()\n      expect(addEventListenerSpy).toHaveBeenCalledWith(\n        'gotpointercapture',\n        expect.any(Function),\n        false\n      )\n    })\n\n    it('onGotPointerCaptureCapture', async () => {\n      const App = () => {\n        return <div onGotPointerCaptureCapture={() => {}}></div>\n      }\n      const addEventListenerSpy = vi.spyOn(dom.window.Node.prototype, 'addEventListener')\n      render(<App />, root)\n      expect(addEventListenerSpy).toHaveBeenCalledOnce()\n      expect(addEventListenerSpy).toHaveBeenCalledWith(\n        'gotpointercapture',\n        expect.any(Function),\n        true\n      )\n    })\n\n    it('undefined', async () => {\n      const App = () => {\n        return <div onClick={undefined}></div>\n      }\n      const addEventListenerSpy = vi.spyOn(dom.window.Node.prototype, 'addEventListener')\n      render(<App />, root)\n      expect(addEventListenerSpy).not.toHaveBeenCalled()\n    })\n\n    it('invalid event handler value', async () => {\n      const App = () => {\n        return <div onClick={1 as unknown as () => void}></div>\n      }\n      expect(() => render(<App />, root)).toThrow()\n    })\n  })\n\n  it('simple Counter', async () => {\n    const Counter = () => {\n      const [count, setCount] = useState(0)\n      return (\n        <div>\n          <p>Count: {count}</p>\n          <button onClick={() => setCount(count + 1)}>+</button>\n        </div>\n      )\n    }\n    const app = <Counter />\n    render(app, root)\n    expect(root.innerHTML).toBe('<div><p>Count: 0</p><button>+</button></div>')\n    const button = root.querySelector('button') as HTMLButtonElement\n    button.click()\n    await Promise.resolve()\n    expect(root.innerHTML).toBe('<div><p>Count: 1</p><button>+</button></div>')\n  })\n\n  it('multiple useState()', async () => {\n    let called = 0\n    const Counter = () => {\n      const [countA, setCountA] = useState(0)\n      const [countB, setCountB] = useState(0)\n      called++\n      return (\n        <div>\n          <p>A: {countA}</p>\n          <button onClick={() => setCountA(countA + 1)}>+</button>\n          <p>B: {countB}</p>\n          <button onClick={() => setCountB(countB + 1)}>+</button>\n        </div>\n      )\n    }\n    const app = <Counter />\n    render(app, root)\n    expect(root.innerHTML).toBe(\n      '<div><p>A: 0</p><button>+</button><p>B: 0</p><button>+</button></div>'\n    )\n    expect(called).toBe(1)\n    const [buttonA, buttonB] = root.querySelectorAll('button')\n    for (let i = 0; i < 3; i++) {\n      buttonA.click()\n      await Promise.resolve()\n    }\n    for (let i = 0; i < 4; i++) {\n      buttonB.click()\n      await Promise.resolve()\n    }\n    expect(root.innerHTML).toBe(\n      '<div><p>A: 3</p><button>+</button><p>B: 4</p><button>+</button></div>'\n    )\n    expect(called).toBe(8)\n  })\n\n  it('multiple update state calls at once in onClick attributes', async () => {\n    let called = 0\n    const Counter = () => {\n      const [countA, setCountA] = useState(0)\n      const [countB, setCountB] = useState(0)\n      called++\n      return (\n        <div>\n          <button\n            onClick={() => {\n              setCountA(countA + 1)\n              setCountB(countB + 2)\n            }}\n          >\n            +\n          </button>\n          {countA} {countB}\n        </div>\n      )\n    }\n    const app = <Counter />\n    render(app, root)\n    expect(root.innerHTML).toBe('<div><button>+</button>0 0</div>')\n    expect(called).toBe(1)\n    root.querySelector('button')?.click()\n    expect(called).toBe(1)\n    await Promise.resolve()\n    expect(called).toBe(2)\n  })\n\n  it('multiple update state calls at once in dom events', async () => {\n    let called = 0\n    const Counter = () => {\n      const [countA, setCountA] = useState(0)\n      const [countB, setCountB] = useState(0)\n      const buttonRef = useRef<HTMLButtonElement>(null)\n      called++\n\n      useEffect(() => {\n        buttonRef.current?.addEventListener('click', () => {\n          setCountA(countA + 1)\n          setCountB(countB + 2)\n        })\n      }, [])\n\n      return (\n        <div>\n          <button ref={buttonRef}>+</button>\n          {countA} {countB}\n        </div>\n      )\n    }\n    const app = <Counter />\n    render(app, root)\n    expect(root.innerHTML).toBe('<div><button>+</button>0 0</div>')\n    expect(called).toBe(1)\n    await new Promise((resolve) => setTimeout(resolve))\n    root.querySelector('button')?.click()\n    expect(called).toBe(1)\n    await Promise.resolve()\n    expect(called).toBe(2)\n  })\n\n  it('nested useState()', async () => {\n    const ChildCounter = () => {\n      const [count, setCount] = useState(0)\n      return (\n        <div>\n          <p>Child Count: {count}</p>\n          <button onClick={() => setCount(count + 1)}>+</button>\n        </div>\n      )\n    }\n    const Counter = () => {\n      const [count, setCount] = useState(0)\n      return (\n        <div>\n          <p>Count: {count}</p>\n          <button onClick={() => setCount(count + 1)}>+</button>\n          <ChildCounter />\n        </div>\n      )\n    }\n    const app = <Counter />\n    render(app, root)\n    expect(root.innerHTML).toBe(\n      '<div><p>Count: 0</p><button>+</button><div><p>Child Count: 0</p><button>+</button></div></div>'\n    )\n    const [button, childButton] = root.querySelectorAll('button')\n    for (let i = 0; i < 3; i++) {\n      childButton.click()\n      await Promise.resolve()\n    }\n    for (let i = 0; i < 2; i++) {\n      button.click()\n      await Promise.resolve()\n    }\n    for (let i = 0; i < 3; i++) {\n      childButton.click()\n      await Promise.resolve()\n    }\n    for (let i = 0; i < 3; i++) {\n      button.click()\n      await Promise.resolve()\n    }\n    expect(root.innerHTML).toBe(\n      '<div><p>Count: 5</p><button>+</button><div><p>Child Count: 6</p><button>+</button></div></div>'\n    )\n  })\n\n  it('nested useState() with children', async () => {\n    const ChildCounter = () => {\n      const [count, setCount] = useState(0)\n      return (\n        <div>\n          <p>Child Count: {count}</p>\n          <button onClick={() => setCount(count + 1)}>+</button>\n        </div>\n      )\n    }\n    const Counter = ({ children }: { children: Child }) => {\n      const [count, setCount] = useState(0)\n      return (\n        <div>\n          <p>Count: {count}</p>\n          <button onClick={() => setCount(count + 1)}>+</button>\n          {children}\n        </div>\n      )\n    }\n    const app = (\n      <Counter>\n        <ChildCounter />\n      </Counter>\n    )\n    render(app, root)\n    expect(root.innerHTML).toBe(\n      '<div><p>Count: 0</p><button>+</button><div><p>Child Count: 0</p><button>+</button></div></div>'\n    )\n    const [button, childButton] = root.querySelectorAll('button')\n    for (let i = 0; i < 3; i++) {\n      childButton.click()\n      await Promise.resolve()\n    }\n    for (let i = 0; i < 2; i++) {\n      button.click()\n      await Promise.resolve()\n    }\n    for (let i = 0; i < 3; i++) {\n      childButton.click()\n      await Promise.resolve()\n    }\n    for (let i = 0; i < 3; i++) {\n      button.click()\n      await Promise.resolve()\n    }\n    expect(root.innerHTML).toBe(\n      '<div><p>Count: 5</p><button>+</button><div><p>Child Count: 6</p><button>+</button></div></div>'\n    )\n  })\n\n  it('consecutive fragment', async () => {\n    const ComponentA = () => {\n      const [count, setCount] = useState(0)\n      return (\n        <>\n          <div>A: {count}</div>\n          <button id='a-button' onClick={() => setCount(count + 1)}>\n            A: +\n          </button>\n        </>\n      )\n    }\n    const App = () => {\n      const [count, setCount] = useState(0)\n      return (\n        <>\n          <ComponentA />\n          <div>B: {count}</div>\n          <button id='b-button' onClick={() => setCount(count + 1)}>\n            B: +\n          </button>\n        </>\n      )\n    }\n    render(<App />, root)\n    expect(root.innerHTML).toBe(\n      '<div>A: 0</div><button id=\"a-button\">A: +</button><div>B: 0</div><button id=\"b-button\">B: +</button>'\n    )\n    root.querySelector<HTMLButtonElement>('#b-button')?.click()\n    await Promise.resolve()\n    expect(root.innerHTML).toBe(\n      '<div>A: 0</div><button id=\"a-button\">A: +</button><div>B: 1</div><button id=\"b-button\">B: +</button>'\n    )\n    root.querySelector<HTMLButtonElement>('#a-button')?.click()\n    await Promise.resolve()\n    expect(root.innerHTML).toBe(\n      '<div>A: 1</div><button id=\"a-button\">A: +</button><div>B: 1</div><button id=\"b-button\">B: +</button>'\n    )\n  })\n\n  it('switch child component', async () => {\n    const Even = () => <p>Even</p>\n    const Odd = () => <div>Odd</div>\n    const Counter = () => {\n      const [count, setCount] = useState(0)\n      return (\n        <div>\n          {count % 2 === 0 ? <Even /> : <Odd />}\n          <button onClick={() => setCount(count + 1)}>+</button>\n        </div>\n      )\n    }\n    const app = <Counter />\n    render(app, root)\n    expect(root.innerHTML).toBe('<div><p>Even</p><button>+</button></div>')\n    const button = root.querySelector('button') as HTMLButtonElement\n    button.click()\n    await Promise.resolve()\n    expect(root.innerHTML).toBe('<div><div>Odd</div><button>+</button></div>')\n    button.click()\n    await Promise.resolve()\n    expect(root.innerHTML).toBe('<div><p>Even</p><button>+</button></div>')\n  })\n\n  it('add/remove/swap item', async () => {\n    const TodoApp = () => {\n      const [todos, setTodos] = useState(['a', 'b', 'c'])\n      return (\n        <div>\n          {todos.map((todo) => (\n            <div key={todo}>{todo}</div>\n          ))}\n          <button onClick={() => setTodos([...todos, 'd'])}>add</button>\n          <button onClick={() => setTodos(todos.slice(0, -1))}>remove</button>\n          <button onClick={() => setTodos([todos[0], todos[2], todos[1], todos[3] || ''])}>\n            swap\n          </button>\n        </div>\n      )\n    }\n    const app = <TodoApp />\n    render(app, root)\n    expect(root.innerHTML).toBe(\n      '<div><div>a</div><div>b</div><div>c</div><button>add</button><button>remove</button><button>swap</button></div>'\n    )\n    const [addButton] = root.querySelectorAll('button')\n    addButton.click()\n    await Promise.resolve()\n    expect(root.innerHTML).toBe(\n      '<div><div>a</div><div>b</div><div>c</div><div>d</div><button>add</button><button>remove</button><button>swap</button></div>'\n    )\n    const [, , swapButton] = root.querySelectorAll('button')\n    swapButton.click()\n    await Promise.resolve()\n    expect(root.innerHTML).toBe(\n      '<div><div>a</div><div>c</div><div>b</div><div>d</div><button>add</button><button>remove</button><button>swap</button></div>'\n    )\n    const [, removeButton] = root.querySelectorAll('button')\n    removeButton.click()\n    await Promise.resolve()\n    expect(root.innerHTML).toBe(\n      '<div><div>a</div><div>c</div><div>b</div><button>add</button><button>remove</button><button>swap</button></div>'\n    )\n  })\n\n  it('swap deferent type of child component', async () => {\n    const Even = () => <p>Even</p>\n    const Odd = () => <div>Odd</div>\n    const Counter = () => {\n      const [count, setCount] = useState(0)\n      return (\n        <div>\n          {count % 2 === 0 ? (\n            <>\n              <Even />\n              <Odd />\n            </>\n          ) : (\n            <>\n              <Odd />\n              <Even />\n            </>\n          )}\n          <button onClick={() => setCount(count + 1)}>+</button>\n        </div>\n      )\n    }\n    const app = <Counter />\n    render(app, root)\n    expect(root.innerHTML).toBe('<div><p>Even</p><div>Odd</div><button>+</button></div>')\n    const button = root.querySelector('button') as HTMLButtonElement\n\n    const createElementSpy = vi.spyOn(dom.window.document, 'createElement')\n\n    button.click()\n    await Promise.resolve()\n    expect(root.innerHTML).toBe('<div><div>Odd</div><p>Even</p><button>+</button></div>')\n    button.click()\n    await Promise.resolve()\n    expect(root.innerHTML).toBe('<div><p>Even</p><div>Odd</div><button>+</button></div>')\n\n    expect(createElementSpy).not.toHaveBeenCalled()\n  })\n\n  it('useState for unnamed function', async () => {\n    const Input = ({ label, onInput }: { label: string; onInput: (value: string) => void }) => {\n      return (\n        <div>\n          <label>{label}</label>\n          <input\n            onInput={(e: InputEvent) => onInput((e.target as HTMLInputElement)?.value || '')}\n          />\n        </div>\n      )\n    }\n    const Form = () => {\n      const [values, setValues] = useState<{ [key: string]: string }>({})\n      return (\n        <form>\n          <Input label='Name' onInput={(value) => setValues({ ...values, name: value })} />\n          <Input label='Email' onInput={(value) => setValues({ ...values, email: value })} />\n          <span>{JSON.stringify(values)}</span>\n        </form>\n      )\n    }\n    const app = <Form />\n    render(app, root)\n    expect(root.innerHTML).toBe(\n      '<form><div><label>Name</label><input></div><div><label>Email</label><input></div><span>{}</span></form>'\n    )\n    const [nameInput] = root.querySelectorAll('input')\n    nameInput.value = 'John'\n    nameInput.dispatchEvent(new dom.window.Event('input'))\n    await Promise.resolve()\n    expect(root.innerHTML).toBe(\n      '<form><div><label>Name</label><input></div><div><label>Email</label><input></div><span>{\"name\":\"John\"}</span></form>'\n    )\n    const [, emailInput] = root.querySelectorAll('input')\n    emailInput.value = 'john@example.com'\n    emailInput.dispatchEvent(new dom.window.Event('input'))\n    await Promise.resolve()\n    expect(root.innerHTML).toBe(\n      '<form><div><label>Name</label><input></div><div><label>Email</label><input></div><span>{\"name\":\"John\",\"email\":\"john@example.com\"}</span></form>'\n    )\n  })\n\n  it('useState for grand child function', async () => {\n    const GrandChild = () => {\n      const [count, setCount] = useState(0)\n      return (\n        <>\n          {count === 0 ? <p>Zero</p> : <span>Not Zero</span>}\n          <button onClick={() => setCount(count + 1)}>+</button>\n        </>\n      )\n    }\n    const Child = () => {\n      return <GrandChild />\n    }\n    const App = () => {\n      const [show, setShow] = useState(false)\n      return (\n        <div>\n          {show && <Child />}\n          <button onClick={() => setShow(!show)}>toggle</button>\n        </div>\n      )\n    }\n    render(<App />, root)\n    expect(root.innerHTML).toBe('<div><button>toggle</button></div>')\n    root.querySelector('button')?.click()\n    await Promise.resolve()\n    expect(root.innerHTML).toBe('<div><p>Zero</p><button>+</button><button>toggle</button></div>')\n    root.querySelector('button')?.click()\n    await Promise.resolve()\n    expect(root.innerHTML).toBe(\n      '<div><span>Not Zero</span><button>+</button><button>toggle</button></div>'\n    )\n  })\n\n  describe('className', () => {\n    it('should convert to class attribute for intrinsic elements', () => {\n      const App = <h1 className='h1'>Hello</h1>\n      render(App, root)\n      expect(root.innerHTML).toBe('<h1 class=\"h1\">Hello</h1>')\n    })\n\n    it('should convert to class attribute for custom elements', () => {\n      const App = <custom-element className='h1'>Hello</custom-element>\n      render(App, root)\n      expect(root.innerHTML).toBe('<custom-element class=\"h1\">Hello</custom-element>')\n    })\n\n    it('should not convert to class attribute for custom components', () => {\n      const App: FC<{ className: string }> = ({ className }) => (\n        <div data-class-name={className}>Hello</div>\n      )\n      render(<App className='h1' />, root)\n      expect(root.innerHTML).toBe('<div data-class-name=\"h1\">Hello</div>')\n    })\n  })\n\n  describe('memo', () => {\n    it('simple', async () => {\n      let renderCount = 0\n      const Counter = ({ count }: { count: number }) => {\n        renderCount++\n        return (\n          <div>\n            <p>Count: {count}</p>\n          </div>\n        )\n      }\n      const MemoCounter = memo(Counter)\n      const App = () => {\n        const [count, setCount] = useState(0)\n        return (\n          <div>\n            <MemoCounter count={Math.min(count, 1)} />\n            <button onClick={() => setCount(count + 1)}>+</button>\n          </div>\n        )\n      }\n      const app = <App />\n      render(app, root)\n      expect(root.innerHTML).toBe('<div><div><p>Count: 0</p></div><button>+</button></div>')\n      expect(renderCount).toBe(1)\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><div><p>Count: 1</p></div><button>+</button></div>')\n      expect(renderCount).toBe(2)\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><div><p>Count: 1</p></div><button>+</button></div>')\n      expect(renderCount).toBe(2)\n    })\n\n    it('useState', async () => {\n      const Child = vi.fn(({ count }: { count: number }) => {\n        const [count2, setCount2] = useState(0)\n        return (\n          <>\n            <div>\n              {count} : {count2}\n            </div>\n            <button id='child-button' onClick={() => setCount2(count2 + 1)}>\n              Child +\n            </button>\n          </>\n        )\n      })\n      const MemoChild = memo(Child)\n      const App = () => {\n        const [count, setCount] = useState(0)\n        return (\n          <>\n            <button id='app-button' onClick={() => setCount(count + 1)}>\n              App +\n            </button>\n            <MemoChild count={Math.floor(count / 2)} />\n          </>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe(\n        '<button id=\"app-button\">App +</button><div>0 : 0</div><button id=\"child-button\">Child +</button>'\n      )\n      root.querySelector<HTMLButtonElement>('button#app-button')?.click()\n      await Promise.resolve()\n      expect(Child).toBeCalledTimes(1)\n      expect(root.innerHTML).toBe(\n        '<button id=\"app-button\">App +</button><div>0 : 0</div><button id=\"child-button\">Child +</button>'\n      )\n      root.querySelector<HTMLButtonElement>('button#app-button')?.click()\n      await Promise.resolve()\n      expect(Child).toBeCalledTimes(2)\n      expect(root.innerHTML).toBe(\n        '<button id=\"app-button\">App +</button><div>1 : 0</div><button id=\"child-button\">Child +</button>'\n      )\n      root.querySelector<HTMLButtonElement>('button#child-button')?.click()\n      await Promise.resolve()\n      expect(Child).toBeCalledTimes(3)\n      expect(root.innerHTML).toBe(\n        '<button id=\"app-button\">App +</button><div>1 : 1</div><button id=\"child-button\">Child +</button>'\n      )\n    })\n\n    // The react compiler generates code like the following for memoization.\n    it('react compiler', async () => {\n      let renderCount = 0\n      const Counter = ({ count }: { count: number }) => {\n        renderCount++\n        return (\n          <div>\n            <p>Count: {count}</p>\n          </div>\n        )\n      }\n\n      const App = () => {\n        const [cache] = useState<unknown[]>(() => [])\n        const [count, setCount] = useState(0)\n        const countForDisplay = Math.floor(count / 2)\n\n        let localCounter\n        if (cache[0] !== countForDisplay) {\n          localCounter = <Counter count={countForDisplay} />\n          cache[0] = countForDisplay\n          cache[1] = localCounter\n        } else {\n          localCounter = cache[1]\n        }\n\n        return (\n          <div>\n            {localCounter}\n            <button onClick={() => setCount(count + 1)}>+</button>\n          </div>\n        )\n      }\n      const app = <App />\n      render(app, root)\n      expect(root.innerHTML).toBe('<div><div><p>Count: 0</p></div><button>+</button></div>')\n      expect(renderCount).toBe(1)\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><div><p>Count: 0</p></div><button>+</button></div>')\n      expect(renderCount).toBe(1)\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><div><p>Count: 1</p></div><button>+</button></div>')\n      expect(renderCount).toBe(2)\n    })\n\n    it('should not return memoized result when context is not changed', async () => {\n      const Context = createContext<[number, (arg: number | ((value: number) => number)) => void]>([\n        0,\n        () => {},\n      ])\n      const Container: FC<{ children: Child }> = ({ children }) => {\n        const [count, setCount] = useState(0)\n        return <Context.Provider value={[count, setCount]}>{children}</Context.Provider>\n      }\n      const Content = () => {\n        const [count, setCount] = useContext(Context)\n        return (\n          <>\n            <span>{count}</span>\n            <button onClick={() => setCount((c) => c + 1)}>+</button>\n          </>\n        )\n      }\n      const app = (\n        <Container>\n          <Content />\n        </Container>\n      )\n      render(app, root)\n      expect(root.innerHTML).toBe('<span>0</span><button>+</button>')\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<span>1</span><button>+</button>')\n    })\n  })\n\n  describe('useRef', async () => {\n    it('simple', async () => {\n      const Input = ({ label, ref }: { label: string; ref: RefObject<HTMLInputElement> }) => {\n        return (\n          <div>\n            <label>{label}</label>\n            <input ref={ref} />\n          </div>\n        )\n      }\n      const Form = () => {\n        const [values, setValues] = useState<{ [key: string]: string }>({})\n        const nameRef = useRef<HTMLInputElement>(null)\n        const emailRef = useRef<HTMLInputElement>(null)\n        return (\n          <form>\n            <Input label='Name' ref={nameRef} />\n            <Input label='Email' ref={emailRef} />\n            <button\n              onClick={(ev: Event) => {\n                ev.preventDefault()\n                setValues({\n                  name: nameRef.current?.value || '',\n                  email: emailRef.current?.value || '',\n                })\n              }}\n            >\n              serialize\n            </button>\n            <span>{JSON.stringify(values)}</span>\n          </form>\n        )\n      }\n      const app = <Form />\n      render(app, root)\n      expect(root.innerHTML).toBe(\n        '<form><div><label>Name</label><input></div><div><label>Email</label><input></div><button>serialize</button><span>{}</span></form>'\n      )\n      const [nameInput, emailInput] = root.querySelectorAll('input')\n      nameInput.value = 'John'\n      emailInput.value = 'john@example.com'\n      const [button] = root.querySelectorAll('button')\n      button.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe(\n        '<form><div><label>Name</label><input></div><div><label>Email</label><input></div><button>serialize</button><span>{\"name\":\"John\",\"email\":\"john@example.com\"}</span></form>'\n      )\n    })\n\n    it('update current', async () => {\n      const App = () => {\n        const [, setState] = useState(0)\n        const ref = useRef<boolean>(false)\n        return (\n          <>\n            <button\n              onClick={() => {\n                setState((c) => c + 1)\n                ref.current = true\n              }}\n            >\n              update\n            </button>\n            <span>{String(ref.current)}</span>\n          </>\n        )\n      }\n      const app = <App />\n      render(app, root)\n      expect(root.innerHTML).toBe('<button>update</button><span>false</span>')\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<button>update</button><span>true</span>')\n    })\n  })\n\n  describe('useEffect', () => {\n    it('simple', async () => {\n      const Counter = () => {\n        const [count, setCount] = useState(0)\n        useEffect(() => {\n          setCount(count + 1)\n        }, [])\n        return <div>{count}</div>\n      }\n      const app = <Counter />\n      render(app, root)\n      await new Promise((resolve) => setTimeout(resolve))\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div>1</div>')\n    })\n\n    it('multiple', async () => {\n      const Counter = () => {\n        const [count, setCount] = useState(0)\n        useEffect(() => {\n          setCount((c) => c + 1)\n        }, [])\n        useEffect(() => {\n          setCount((c) => c + 1)\n        }, [])\n        return <div>{count}</div>\n      }\n      const app = <Counter />\n      render(app, root)\n      await new Promise((resolve) => setTimeout(resolve))\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div>2</div>')\n    })\n\n    it('cleanup', async () => {\n      const Child = ({ parent }: { parent: RefObject<HTMLElement> }) => {\n        useEffect(() => {\n          return () => {\n            parent.current?.setAttribute('data-cleanup', 'true')\n          }\n        }, [])\n        return <div>Child</div>\n      }\n      const Parent = () => {\n        const [show, setShow] = useState(true)\n        const ref = useRef<HTMLElement>(null)\n        return (\n          <div ref={ref}>\n            {show && <Child parent={ref} />}\n            <button onClick={() => setShow(false)}>hide</button>\n          </div>\n        )\n      }\n      const app = <Parent />\n      render(app, root)\n      expect(root.innerHTML).toBe('<div><div>Child</div><button>hide</button></div>')\n      await new Promise((resolve) => setTimeout(resolve))\n      const [button] = root.querySelectorAll('button')\n      button.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div data-cleanup=\"true\"><button>hide</button></div>')\n    })\n\n    it('cleanup for deps', async () => {\n      let effectCount = 0\n      let cleanupCount = 0\n\n      const App = () => {\n        const [count, setCount] = useState(0)\n        const [count2, setCount2] = useState(0)\n        useEffect(() => {\n          effectCount++\n          return () => {\n            cleanupCount++\n          }\n        }, [count])\n        return (\n          <div>\n            <p>{count}</p>\n            <p>{count2}</p>\n            <button onClick={() => setCount(count + 1)}>+</button>\n            <button onClick={() => setCount2(count2 + 1)}>+</button>\n          </div>\n        )\n      }\n      const app = <App />\n      render(app, root)\n      await new Promise((resolve) => setTimeout(resolve))\n      expect(effectCount).toBe(1)\n      expect(cleanupCount).toBe(0)\n      root.querySelectorAll('button')[0].click() // count++\n      await Promise.resolve()\n      await new Promise((resolve) => setTimeout(resolve))\n      expect(effectCount).toBe(2)\n      expect(cleanupCount).toBe(1)\n      root.querySelectorAll('button')[1].click() // count2++\n      await Promise.resolve()\n      expect(effectCount).toBe(2)\n      expect(cleanupCount).toBe(1)\n    })\n  })\n\n  describe('useLayoutEffect', () => {\n    it('simple', async () => {\n      const Counter = () => {\n        const [count, setCount] = useState(0)\n        useLayoutEffect(() => {\n          setCount(count + 1)\n        }, [])\n        return <div>{count}</div>\n      }\n      const app = <Counter />\n      render(app, root)\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div>1</div>')\n    })\n\n    it('multiple', async () => {\n      const Counter = () => {\n        const [count, setCount] = useState(0)\n        useLayoutEffect(() => {\n          setCount((c) => c + 1)\n        }, [])\n        useLayoutEffect(() => {\n          setCount((c) => c + 1)\n        }, [])\n        return <div>{count}</div>\n      }\n      const app = <Counter />\n      render(app, root)\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div>2</div>')\n    })\n\n    it('cleanup', async () => {\n      const Child = ({ parent }: { parent: RefObject<HTMLElement> }) => {\n        useLayoutEffect(() => {\n          return () => {\n            parent.current?.setAttribute('data-cleanup', 'true')\n          }\n        }, [])\n        return <div>Child</div>\n      }\n      const Parent = () => {\n        const [show, setShow] = useState(true)\n        const ref = useRef<HTMLElement>(null)\n        return (\n          <div ref={ref}>\n            {show && <Child parent={ref} />}\n            <button onClick={() => setShow(false)}>hide</button>\n          </div>\n        )\n      }\n      const app = <Parent />\n      render(app, root)\n      expect(root.innerHTML).toBe('<div><div>Child</div><button>hide</button></div>')\n      const [button] = root.querySelectorAll('button')\n      button.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div data-cleanup=\"true\"><button>hide</button></div>')\n    })\n\n    it('cleanup for deps', async () => {\n      let effectCount = 0\n      let cleanupCount = 0\n\n      const App = () => {\n        const [count, setCount] = useState(0)\n        const [count2, setCount2] = useState(0)\n        useLayoutEffect(() => {\n          effectCount++\n          return () => {\n            cleanupCount++\n          }\n        }, [count])\n        return (\n          <div>\n            <p>{count}</p>\n            <p>{count2}</p>\n            <button onClick={() => setCount(count + 1)}>+</button>\n            <button onClick={() => setCount2(count2 + 1)}>+</button>\n          </div>\n        )\n      }\n      const app = <App />\n      render(app, root)\n      expect(effectCount).toBe(1)\n      expect(cleanupCount).toBe(0)\n      root.querySelectorAll('button')[0].click() // count++\n      await Promise.resolve()\n      expect(effectCount).toBe(2)\n      expect(cleanupCount).toBe(1)\n      root.querySelectorAll('button')[1].click() // count2++\n      await Promise.resolve()\n      expect(effectCount).toBe(2)\n      expect(cleanupCount).toBe(1)\n    })\n  })\n\n  describe('useInsertionEffect', () => {\n    it('simple', async () => {\n      const Counter = () => {\n        const [count, setCount] = useState(0)\n        useInsertionEffect(() => {\n          setCount(count + 1)\n        }, [])\n        return <div>{count}</div>\n      }\n      const app = <Counter />\n      render(app, root)\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div>1</div>')\n    })\n\n    it('multiple', async () => {\n      const Counter = () => {\n        const [count, setCount] = useState(0)\n        useInsertionEffect(() => {\n          setCount((c) => c + 1)\n        }, [])\n        useInsertionEffect(() => {\n          setCount((c) => c + 1)\n        }, [])\n        return <div>{count}</div>\n      }\n      const app = <Counter />\n      render(app, root)\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div>2</div>')\n    })\n\n    it('with useLayoutEffect', async () => {\n      const Counter = () => {\n        const [data, setData] = useState<string[]>([])\n        useLayoutEffect(() => {\n          setData((d) => [...d, 'useLayoutEffect'])\n        }, [])\n        useInsertionEffect(() => {\n          setData((d) => [...d, 'useInsertionEffect'])\n        }, [])\n        return <div>{data.join(',')}</div>\n      }\n      const app = <Counter />\n      render(app, root)\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div>useInsertionEffect,useLayoutEffect</div>')\n    })\n\n    it('cleanup', async () => {\n      const Child = ({ parent }: { parent: RefObject<HTMLElement> }) => {\n        useInsertionEffect(() => {\n          return () => {\n            parent.current?.setAttribute('data-cleanup', 'true')\n          }\n        }, [])\n        return <div>Child</div>\n      }\n      const Parent = () => {\n        const [show, setShow] = useState(true)\n        const ref = useRef<HTMLElement>(null)\n        return (\n          <div ref={ref}>\n            {show && <Child parent={ref} />}\n            <button onClick={() => setShow(false)}>hide</button>\n          </div>\n        )\n      }\n      const app = <Parent />\n      render(app, root)\n      expect(root.innerHTML).toBe('<div><div>Child</div><button>hide</button></div>')\n      const [button] = root.querySelectorAll('button')\n      button.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div data-cleanup=\"true\"><button>hide</button></div>')\n    })\n\n    it('cleanup for deps', async () => {\n      let effectCount = 0\n      let cleanupCount = 0\n\n      const App = () => {\n        const [count, setCount] = useState(0)\n        const [count2, setCount2] = useState(0)\n        useInsertionEffect(() => {\n          effectCount++\n          return () => {\n            cleanupCount++\n          }\n        }, [count])\n        return (\n          <div>\n            <p>{count}</p>\n            <p>{count2}</p>\n            <button onClick={() => setCount(count + 1)}>+</button>\n            <button onClick={() => setCount2(count2 + 1)}>+</button>\n          </div>\n        )\n      }\n      const app = <App />\n      render(app, root)\n      expect(effectCount).toBe(1)\n      expect(cleanupCount).toBe(0)\n      root.querySelectorAll('button')[0].click() // count++\n      await Promise.resolve()\n      expect(effectCount).toBe(2)\n      expect(cleanupCount).toBe(1)\n      root.querySelectorAll('button')[1].click() // count2++\n      await Promise.resolve()\n      expect(effectCount).toBe(2)\n      expect(cleanupCount).toBe(1)\n    })\n  })\n\n  describe('useCallback', () => {\n    it('deferent callbacks', async () => {\n      const callbackSet = new Set<Function>()\n      const Counter = () => {\n        const [count, setCount] = useState(0)\n        const increment = useCallback(() => {\n          setCount(count + 1)\n        }, [count])\n        callbackSet.add(increment)\n        return (\n          <div>\n            <p>{count}</p>\n            <button onClick={increment}>+</button>\n          </div>\n        )\n      }\n      const app = <Counter />\n      render(app, root)\n      expect(root.innerHTML).toBe('<div><p>0</p><button>+</button></div>')\n      const button = root.querySelector('button') as HTMLButtonElement\n      button.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><p>1</p><button>+</button></div>')\n      expect(callbackSet.size).toBe(2)\n    })\n\n    it('same callback', async () => {\n      const callbackSet = new Set<Function>()\n      const Counter = () => {\n        const [count, setCount] = useState(0)\n        const increment = useCallback(() => {\n          setCount(count + 1)\n        }, [count])\n        callbackSet.add(increment)\n\n        const [count2, setCount2] = useState(0)\n        return (\n          <div>\n            <p>{count}</p>\n            <button onClick={increment}>+</button>\n            <p>{count2}</p>\n            <button onClick={() => setCount2(count2 + 1)}>+</button>\n          </div>\n        )\n      }\n      const app = <Counter />\n      render(app, root)\n      expect(root.innerHTML).toBe('<div><p>0</p><button>+</button><p>0</p><button>+</button></div>')\n      const [, button] = root.querySelectorAll('button')\n      button.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><p>0</p><button>+</button><p>1</p><button>+</button></div>')\n      expect(callbackSet.size).toBe(1)\n    })\n\n    it('deferent callbacks', async () => {\n      const callbackSet = new Set<Function>()\n      const Counter = () => {\n        const [count, setCount] = useState(0)\n        const double = useCallback((input: number): number => {\n          return input * 2\n        }, [])\n        callbackSet.add(double)\n        return (\n          <div>\n            <p>{double(count)}</p>\n            <button onClick={() => setCount((c) => c + 1)}>+</button>\n          </div>\n        )\n      }\n      const app = <Counter />\n      render(app, root)\n      expect(root.innerHTML).toBe('<div><p>0</p><button>+</button></div>')\n      const button = root.querySelector('button') as HTMLButtonElement\n      button.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><p>2</p><button>+</button></div>')\n      expect(callbackSet.size).toBe(1)\n    })\n  })\n\n  describe('useMemo', () => {\n    it('simple', async () => {\n      let factoryCalled = 0\n      const Counter = () => {\n        const [count, setCount] = useState(0)\n        const [count2, setCount2] = useState(0)\n        const memo = useMemo(() => {\n          factoryCalled++\n          return count + 1\n        }, [count])\n        return (\n          <div>\n            <p>{count}</p>\n            <p>{count2}</p>\n            <p>{memo}</p>\n            <button onClick={() => setCount(count + 1)}>+</button>\n            <button onClick={() => setCount2(count2 + 1)}>+</button>\n          </div>\n        )\n      }\n      const app = <Counter />\n      render(app, root)\n      expect(root.innerHTML).toBe(\n        '<div><p>0</p><p>0</p><p>1</p><button>+</button><button>+</button></div>'\n      )\n      expect(factoryCalled).toBe(1)\n      root.querySelectorAll('button')[0].click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe(\n        '<div><p>1</p><p>0</p><p>2</p><button>+</button><button>+</button></div>'\n      )\n      expect(factoryCalled).toBe(2)\n      root.querySelectorAll('button')[1].click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe(\n        '<div><p>1</p><p>1</p><p>2</p><button>+</button><button>+</button></div>'\n      )\n      expect(factoryCalled).toBe(2)\n    })\n  })\n\n  describe('isValidElement', () => {\n    it('valid', () => {\n      expect(isValidElement(<div />)).toBe(true)\n    })\n\n    it('invalid', () => {\n      expect(isValidElement({})).toBe(false)\n    })\n  })\n\n  describe('createElement', () => {\n    it('simple', async () => {\n      const App = () => {\n        const [count, setCount] = useState(0)\n        return (\n          <div>{createElement('p', { onClick: () => setCount(count + 1) }, String(count))}</div>\n        )\n      }\n      const app = <App />\n      render(app, root)\n      expect(root.innerHTML).toBe('<div><p>0</p></div>')\n      root.querySelector('p')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><p>1</p></div>')\n    })\n\n    it('title', async () => {\n      const App = () => {\n        return <div>{createElement('title', {}, 'Hello')}</div>\n      }\n      const app = <App />\n      render(app, root)\n      expect(document.head.innerHTML).toBe('<title>Hello</title>')\n      expect(root.innerHTML).toBe('<div></div>')\n    })\n  })\n\n  describe('dom-specific createElement', () => {\n    it('simple', async () => {\n      const App = () => {\n        const [count, setCount] = useState(0)\n        return <div>{createElementForDom('p', { onClick: () => setCount(count + 1) }, count)}</div>\n      }\n      const app = <App />\n      render(app, root)\n      expect(root.innerHTML).toBe('<div><p>0</p></div>')\n      root.querySelector('p')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><p>1</p></div>')\n    })\n\n    it('title', async () => {\n      const App = () => {\n        return <div>{createElementForDom('title', {}, 'Hello')}</div>\n      }\n      const app = <App />\n      render(app, root)\n      expect(document.head.innerHTML).toBe('<title>Hello</title>')\n      expect(root.innerHTML).toBe('<div></div>')\n    })\n  })\n\n  describe('cloneElement', () => {\n    it('simple', async () => {\n      const App = () => {\n        const [count, setCount] = useState(0)\n        return <div>{cloneElement(<p>{count}</p>, { onClick: () => setCount(count + 1) })}</div>\n      }\n      const app = <App />\n      render(app, root)\n      expect(root.innerHTML).toBe('<div><p>0</p></div>')\n      root.querySelector('p')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><p>1</p></div>')\n    })\n  })\n\n  describe('dom-specific cloneElement', () => {\n    it('simple', async () => {\n      const App = () => {\n        const [count, setCount] = useState(0)\n        return (\n          <div>{cloneElementForDom(<p>{count}</p>, { onClick: () => setCount(count + 1) })}</div>\n        )\n      }\n      const app = <App />\n      render(app, root)\n      expect(root.innerHTML).toBe('<div><p>0</p></div>')\n      root.querySelector('p')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><p>1</p></div>')\n    })\n  })\n\n  describe('flushSync', () => {\n    it('simple', async () => {\n      const SubApp = ({ id }: { id: string }) => {\n        const [count, setCount] = useState(0)\n        return (\n          <div id={id}>\n            <p>{count}</p>\n            <button onClick={() => setCount(count + 1)}>+</button>\n          </div>\n        )\n      }\n      const App = () => {\n        return (\n          <div>\n            <SubApp id='a' />\n            <SubApp id='b' />\n          </div>\n        )\n      }\n      const app = <App />\n      render(app, root)\n      expect(root.innerHTML).toBe(\n        '<div><div id=\"a\"><p>0</p><button>+</button></div><div id=\"b\"><p>0</p><button>+</button></div></div>'\n      )\n      root.querySelector<HTMLButtonElement>('#b button')?.click()\n      flushSync(() => {\n        root.querySelector<HTMLButtonElement>('#a button')?.click()\n      })\n      expect(root.innerHTML).toBe(\n        '<div><div id=\"a\"><p>1</p><button>+</button></div><div id=\"b\"><p>0</p><button>+</button></div></div>'\n      )\n      await Promise.resolve()\n      expect(root.innerHTML).toBe(\n        '<div><div id=\"a\"><p>1</p><button>+</button></div><div id=\"b\"><p>1</p><button>+</button></div></div>'\n      )\n    })\n  })\n\n  describe('createPortal', () => {\n    it('simple', async () => {\n      const App = () => {\n        const [count, setCount] = useState(0)\n        return (\n          <div>\n            <button onClick={() => setCount(count + 1)}>+</button>\n            {count <= 1 && createPortal(<p>{count}</p>, document.body)}\n          </div>\n        )\n      }\n      const app = <App />\n      render(app, root)\n      expect(root.innerHTML).toBe('<div><button>+</button></div>')\n      expect(document.body.innerHTML).toBe(\n        '<div id=\"root\"><div><button>+</button></div></div><p>0</p>'\n      )\n      document.body.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><button>+</button></div>')\n      expect(document.body.innerHTML).toBe(\n        '<div id=\"root\"><div><button>+</button></div></div><p>1</p>'\n      )\n      document.body.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(document.body.innerHTML).toBe('<div id=\"root\"><div><button>+</button></div></div>')\n    })\n\n    it('update', async () => {\n      const App = () => {\n        const [count, setCount] = useState(0)\n        return (\n          <div>\n            {createPortal(<p>{count}</p>, document.body)}\n            <button onClick={() => setCount(count + 1)}>+</button>\n            <div>\n              <p>{count}</p>\n            </div>\n          </div>\n        )\n      }\n      const app = <App />\n      render(app, root)\n      expect(root.innerHTML).toBe('<div><button>+</button><div><p>0</p></div></div>')\n      expect(document.body.innerHTML).toBe(\n        '<div id=\"root\"><div><button>+</button><div><p>0</p></div></div></div><p>0</p>'\n      )\n\n      const createElementSpy = vi.spyOn(dom.window.document, 'createElement')\n\n      document.body.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><button>+</button><div><p>1</p></div></div>')\n      expect(document.body.innerHTML).toBe(\n        '<div id=\"root\"><div><button>+</button><div><p>1</p></div></div></div><p>1</p>'\n      )\n      document.body.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(document.body.innerHTML).toBe(\n        '<div id=\"root\"><div><button>+</button><div><p>2</p></div></div></div><p>2</p>'\n      )\n\n      expect(createElementSpy).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('SVG', () => {\n    it('simple', () => {\n      const App = () => {\n        return (\n          <svg>\n            <circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red' />\n          </svg>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe(\n        '<svg><circle cx=\"50\" cy=\"50\" r=\"40\" stroke=\"black\" stroke-width=\"3\" fill=\"red\"></circle></svg>'\n      )\n    })\n\n    it('title element', () => {\n      const App = () => {\n        return (\n          <>\n            <title>Document Title</title>\n            <svg>\n              <title>SVG Title</title>\n            </svg>\n          </>\n        )\n      }\n      render(<App />, root)\n      expect(document.head.innerHTML).toBe('<title>Document Title</title>')\n      expect(root.innerHTML).toBe('<svg><title>SVG Title</title></svg>')\n      expect(document.querySelector('title')).toBeInstanceOf(dom.window.HTMLTitleElement)\n      expect(document.querySelector('svg title')).toBeInstanceOf(dom.window.SVGTitleElement)\n    })\n\n    describe('attribute', () => {\n      describe('camelCase', () => {\n        test.each`\n          key\n          ${'attributeName'}\n          ${'baseFrequency'}\n          ${'calcMode'}\n          ${'clipPathUnits'}\n          ${'diffuseConstant'}\n          ${'edgeMode'}\n          ${'filterUnits'}\n          ${'gradientTransform'}\n          ${'gradientUnits'}\n          ${'kernelMatrix'}\n          ${'kernelUnitLength'}\n          ${'keyPoints'}\n          ${'keySplines'}\n          ${'keyTimes'}\n          ${'lengthAdjust'}\n          ${'limitingConeAngle'}\n          ${'markerHeight'}\n          ${'markerUnits'}\n          ${'markerWidth'}\n          ${'maskContentUnits'}\n          ${'maskUnits'}\n          ${'numOctaves'}\n          ${'pathLength'}\n          ${'patternContentUnits'}\n          ${'patternTransform'}\n          ${'patternUnits'}\n          ${'pointsAtX'}\n          ${'pointsAtY'}\n          ${'pointsAtZ'}\n          ${'preserveAlpha'}\n          ${'preserveAspectRatio'}\n          ${'primitiveUnits'}\n          ${'refX'}\n          ${'refY'}\n          ${'repeatCount'}\n          ${'repeatDur'}\n          ${'specularConstant'}\n          ${'specularExponent'}\n          ${'spreadMethod'}\n          ${'startOffset'}\n          ${'stdDeviation'}\n          ${'stitchTiles'}\n          ${'surfaceScale'}\n          ${'crossorigin'}\n          ${'systemLanguage'}\n          ${'tableValues'}\n          ${'targetX'}\n          ${'targetY'}\n          ${'textLength'}\n          ${'viewBox'}\n          ${'xChannelSelector'}\n          ${'yChannelSelector'}\n        `('$key', ({ key }) => {\n          const App = () => {\n            return (\n              <svg>\n                <g {...{ [key]: 'test' }} />\n              </svg>\n            )\n          }\n          render(<App />, root)\n          expect(root.innerHTML).toBe(`<svg><g ${key}=\"test\"></g></svg>`)\n        })\n      })\n\n      describe('kebab-case', () => {\n        test.each`\n          key\n          ${'alignmentBaseline'}\n          ${'baselineShift'}\n          ${'clipPath'}\n          ${'clipRule'}\n          ${'colorInterpolation'}\n          ${'colorInterpolationFilters'}\n          ${'dominantBaseline'}\n          ${'fillOpacity'}\n          ${'fillRule'}\n          ${'floodColor'}\n          ${'floodOpacity'}\n          ${'fontFamily'}\n          ${'fontSize'}\n          ${'fontSizeAdjust'}\n          ${'fontStretch'}\n          ${'fontStyle'}\n          ${'fontVariant'}\n          ${'fontWeight'}\n          ${'imageRendering'}\n          ${'letterSpacing'}\n          ${'lightingColor'}\n          ${'markerEnd'}\n          ${'markerMid'}\n          ${'markerStart'}\n          ${'overlinePosition'}\n          ${'overlineThickness'}\n          ${'paintOrder'}\n          ${'pointerEvents'}\n          ${'shapeRendering'}\n          ${'stopColor'}\n          ${'stopOpacity'}\n          ${'strikethroughPosition'}\n          ${'strikethroughThickness'}\n          ${'strokeDasharray'}\n          ${'strokeDashoffset'}\n          ${'strokeLinecap'}\n          ${'strokeLinejoin'}\n          ${'strokeMiterlimit'}\n          ${'strokeOpacity'}\n          ${'strokeWidth'}\n          ${'textAnchor'}\n          ${'textDecoration'}\n          ${'textRendering'}\n          ${'transformOrigin'}\n          ${'underlinePosition'}\n          ${'underlineThickness'}\n          ${'unicodeBidi'}\n          ${'vectorEffect'}\n          ${'wordSpacing'}\n          ${'writingMode'}\n        `('$key', ({ key }) => {\n          const App = () => {\n            return (\n              <svg>\n                <g {...{ [key]: 'test' }} />\n              </svg>\n            )\n          }\n          render(<App />, root)\n          expect(root.innerHTML).toBe(\n            `<svg><g ${key.replace(/([A-Z])/g, '-$1').toLowerCase()}=\"test\"></g></svg>`\n          )\n        })\n      })\n\n      describe('data-*', () => {\n        test.each`\n          key\n          ${'data-foo'}\n          ${'data-foo-bar'}\n          ${'data-fooBar'}\n        `('$key', ({ key }) => {\n          const App = () => {\n            return (\n              <svg>\n                <g {...{ [key]: 'test' }} />\n              </svg>\n            )\n          }\n          render(<App />, root)\n          expect(root.innerHTML).toBe(`<svg><g ${key}=\"test\"></g></svg>`)\n        })\n      })\n    })\n  })\n\n  describe('MathML', () => {\n    it('simple', () => {\n      const createElementSpy = vi.spyOn(dom.window.document, 'createElement')\n      const createElementNSSpy = vi.spyOn(dom.window.document, 'createElementNS')\n\n      const App = () => {\n        return (\n          <math>\n            <mrow>\n              <mn>1</mn>\n            </mrow>\n          </math>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<math><mrow><mn>1</mn></mrow></math>')\n\n      expect(createElementSpy).not.toHaveBeenCalled()\n      expect(createElementNSSpy).toHaveBeenCalledWith('http://www.w3.org/1998/Math/MathML', 'math')\n      expect(createElementNSSpy).toHaveBeenCalledWith('http://www.w3.org/1998/Math/MathML', 'mrow')\n    })\n  })\n})\n\ndescribe('jsx', () => {\n  it('exported as an alias of createElement', () => {\n    expect(jsx).toBeDefined()\n    expect(jsx('div', {}, 'Hello')).toBeInstanceOf(Object)\n  })\n})\n\ndescribe('version', () => {\n  it('should be defined with semantic versioning format', () => {\n    expect(version).toMatch(/^\\d+\\.\\d+\\.\\d+-hono-jsx$/)\n  })\n})\n\ndescribe('default export', () => {\n  ;[\n    'version',\n    'memo',\n    'Fragment',\n    'isValidElement',\n    'createElement',\n    'cloneElement',\n    'ErrorBoundary',\n    'createContext',\n    'useContext',\n    'useState',\n    'useEffect',\n    'useRef',\n    'useCallback',\n    'useReducer',\n    'useDebugValue',\n    'createRef',\n    'forwardRef',\n    'useImperativeHandle',\n    'useSyncExternalStore',\n    'use',\n    'startTransition',\n    'useTransition',\n    'useDeferredValue',\n    'startViewTransition',\n    'useViewTransition',\n    'useActionState',\n    'useFormStatus',\n    'useOptimistic',\n    'useMemo',\n    'useLayoutEffect',\n    'Suspense',\n    'Fragment',\n    'flushSync',\n    'createPortal',\n    'StrictMode',\n  ].forEach((key) => {\n    it(key, () => {\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      expect((DefaultExport as any)[key]).toBeDefined()\n    })\n  })\n})\n"
  },
  {
    "path": "src/jsx/dom/index.ts",
    "content": "/**\n * @module\n * This module provides APIs for `hono/jsx/dom`.\n */\n\nimport { isValidElement, reactAPICompatVersion, shallowEqual } from '../base'\nimport type { Child, DOMAttributes, JSX, JSXNode, Props, FC, MemorableFC } from '../base'\nimport { Children } from '../children'\nimport { DOM_MEMO } from '../constants'\nimport { useContext } from '../context'\nimport {\n  createRef,\n  forwardRef,\n  startTransition,\n  startViewTransition,\n  use,\n  useCallback,\n  useDebugValue,\n  useDeferredValue,\n  useEffect,\n  useId,\n  useImperativeHandle,\n  useInsertionEffect,\n  useLayoutEffect,\n  useMemo,\n  useReducer,\n  useRef,\n  useState,\n  useSyncExternalStore,\n  useTransition,\n  useViewTransition,\n} from '../hooks'\nimport { ErrorBoundary, Suspense } from './components'\nimport { createContext } from './context'\nimport { useActionState, useFormStatus, useOptimistic } from './hooks'\nimport { Fragment, jsx } from './jsx-runtime'\nimport { createPortal, flushSync } from './render'\n\nexport { render } from './render'\n\nconst createElement = (\n  tag: string | ((props: Props) => JSXNode),\n  props: Props | null,\n  ...children: Child[]\n): JSXNode => {\n  const jsxProps: Props = props ? { ...props } : {}\n  if (children.length) {\n    jsxProps.children = children.length === 1 ? children[0] : children\n  }\n\n  let key = undefined\n  if ('key' in jsxProps) {\n    key = jsxProps.key\n    delete jsxProps.key\n  }\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  return jsx(tag, jsxProps, key) as any\n}\n\nconst cloneElement = <T extends JSXNode | JSX.Element>(\n  element: T,\n  props: Props,\n  ...children: Child[]\n): T => {\n  return jsx(\n    (element as JSXNode).tag,\n    {\n      ...(element as JSXNode).props,\n      ...props,\n      children: children.length ? children : (element as JSXNode).props.children,\n    },\n    (element as JSXNode).key\n  ) as T\n}\n\nconst memo = <T>(\n  component: FC<T>,\n  propsAreEqual: (prevProps: Readonly<T>, nextProps: Readonly<T>) => boolean = shallowEqual\n): FC<T> => {\n  const wrapper = ((props: T) => component(props)) as MemorableFC<T>\n  wrapper[DOM_MEMO] = propsAreEqual\n  return wrapper as FC<T>\n}\n\nexport {\n  reactAPICompatVersion as version,\n  createElement as jsx,\n  useState,\n  useEffect,\n  useRef,\n  useCallback,\n  use,\n  startTransition,\n  useTransition,\n  useDeferredValue,\n  startViewTransition,\n  useViewTransition,\n  useMemo,\n  useLayoutEffect,\n  useInsertionEffect,\n  useReducer,\n  useId,\n  useDebugValue,\n  createRef,\n  forwardRef,\n  useImperativeHandle,\n  useSyncExternalStore,\n  useFormStatus,\n  useActionState,\n  useOptimistic,\n  Suspense,\n  ErrorBoundary,\n  createContext,\n  useContext,\n  memo,\n  isValidElement,\n  createElement,\n  cloneElement,\n  Children,\n  Fragment,\n  Fragment as StrictMode,\n  DOMAttributes,\n  flushSync,\n  createPortal,\n}\n\nexport default {\n  version: reactAPICompatVersion,\n  useState,\n  useEffect,\n  useRef,\n  useCallback,\n  use,\n  startTransition,\n  useTransition,\n  useDeferredValue,\n  startViewTransition,\n  useViewTransition,\n  useMemo,\n  useLayoutEffect,\n  useInsertionEffect,\n  useReducer,\n  useId,\n  useDebugValue,\n  createRef,\n  forwardRef,\n  useImperativeHandle,\n  useSyncExternalStore,\n  useFormStatus,\n  useActionState,\n  useOptimistic,\n  Suspense,\n  ErrorBoundary,\n  createContext,\n  useContext,\n  memo,\n  isValidElement,\n  createElement,\n  cloneElement,\n  Children,\n  Fragment,\n  StrictMode: Fragment,\n  flushSync,\n  createPortal,\n}\n\nexport type { Context } from '../context'\n\nexport type * from '../types'\n"
  },
  {
    "path": "src/jsx/dom/intrinsic-element/components.test.tsx",
    "content": "/** @jsxImportSource ../../ */\nimport { JSDOM, ResourceLoader } from 'jsdom'\nimport { Suspense, render } from '..'\nimport { useState } from '../../hooks'\nimport { clearCache, composeRef } from './components'\n\ndescribe('intrinsic element', () => {\n  let CustomResourceLoader: typeof ResourceLoader\n  beforeAll(() => {\n    global.requestAnimationFrame = (cb) => setTimeout(cb)\n\n    CustomResourceLoader = class CustomResourceLoader extends ResourceLoader {\n      fetch(url: string) {\n        return url.includes('invalid')\n          ? Promise.reject('Invalid URL')\n          : // eslint-disable-next-line @typescript-eslint/no-explicit-any\n            (Promise.resolve(Buffer.from('')) as any)\n      }\n    }\n  })\n\n  let dom: JSDOM\n  let root: HTMLElement\n  beforeEach(() => {\n    clearCache()\n\n    dom = new JSDOM('<html><head></head><body><div id=\"root\"></div></body></html>', {\n      runScripts: 'dangerously',\n      resources: new CustomResourceLoader(),\n    })\n    global.document = dom.window.document\n    global.HTMLElement = dom.window.HTMLElement\n    global.SVGElement = dom.window.SVGElement\n    global.Text = dom.window.Text\n    global.FormData = dom.window.FormData\n    global.CustomEvent = dom.window.CustomEvent\n    root = document.getElementById('root') as HTMLElement\n  })\n\n  describe('document metadata', () => {\n    describe('title element', () => {\n      it('should be inserted into head', () => {\n        const App = () => {\n          return (\n            <div>\n              <title>Document Title</title>\n              Content\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe('<title>Document Title</title>')\n        expect(root.innerHTML).toBe('<div>Content</div>')\n      })\n\n      it('should be updated', async () => {\n        const App = () => {\n          const [count, setCount] = useState(0)\n          return (\n            <div>\n              <title>Document Title {count}</title>\n              <button onClick={() => setCount(count + 1)}>+</button>\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe('<title>Document Title 0</title>')\n        expect(root.innerHTML).toBe('<div><button>+</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(document.head.innerHTML).toBe('<title>Document Title 1</title>')\n      })\n\n      it('should be removed when unmounted', async () => {\n        const App = () => {\n          const [count, setCount] = useState(0)\n          return (\n            <div>\n              {count === 1 && <title>Document Title {count}</title>}\n              <div>{count}</div>\n              <button onClick={() => setCount(count + 1)}>+</button>\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe('')\n        expect(root.innerHTML).toBe('<div><div>0</div><button>+</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(document.head.innerHTML).toBe('<title>Document Title 1</title>')\n        expect(root.innerHTML).toBe('<div><div>1</div><button>+</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(document.head.innerHTML).toBe('')\n        expect(root.innerHTML).toBe('<div><div>2</div><button>+</button></div>')\n      })\n\n      it('should be inserted bottom of head if existing element is removed', async () => {\n        document.head.innerHTML = '<title>Existing Title</title>'\n\n        const App = () => {\n          const [count, setCount] = useState(0)\n          return (\n            <div>\n              {count === 1 && <title>Document Title {count}</title>}\n              <div>{count}</div>\n              <button onClick={() => setCount(count + 1)}>+</button>\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe('<title>Existing Title</title>')\n        expect(root.innerHTML).toBe('<div><div>0</div><button>+</button></div>')\n        root.querySelector('button')?.click()\n        document.head.querySelector('title')?.remove()\n        await Promise.resolve()\n        expect(document.head.innerHTML).toBe('<title>Document Title 1</title>')\n        expect(root.innerHTML).toBe('<div><div>1</div><button>+</button></div>')\n      })\n\n      it('should be inserted before existing title element', async () => {\n        document.head.innerHTML = '<title>Existing Title</title>'\n\n        const App = () => {\n          const [count, setCount] = useState(0)\n          return (\n            <div>\n              {count === 1 && <title>Document Title {count}</title>}\n              <div>{count}</div>\n              <button onClick={() => setCount(count + 1)}>+</button>\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe('<title>Existing Title</title>')\n        expect(root.innerHTML).toBe('<div><div>0</div><button>+</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(document.head.innerHTML).toBe(\n          '<title>Document Title 1</title><title>Existing Title</title>'\n        )\n        expect(root.innerHTML).toBe('<div><div>1</div><button>+</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(document.head.innerHTML).toBe('<title>Existing Title</title>')\n        expect(root.innerHTML).toBe('<div><div>2</div><button>+</button></div>')\n      })\n    })\n\n    describe('link element', () => {\n      it('should be inserted into head', () => {\n        const App = () => {\n          return (\n            <div>\n              <link rel='stylesheet' href='style.css' precedence='default' />\n              Content\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe(\n          '<link href=\"style.css\" rel=\"stylesheet\" data-precedence=\"default\">'\n        )\n        expect(root.innerHTML).toBe('<div>Content</div>')\n      })\n\n      it('should be updated', async () => {\n        const App = () => {\n          const [count, setCount] = useState(0)\n          return (\n            <div>\n              <link rel='stylesheet' href={`style${count}.css`} precedence='default' />\n              <button onClick={() => setCount(count + 1)}>+</button>\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe(\n          '<link href=\"style0.css\" rel=\"stylesheet\" data-precedence=\"default\">'\n        )\n        expect(root.innerHTML).toBe('<div><button>+</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(document.head.innerHTML).toBe(\n          '<link href=\"style1.css\" rel=\"stylesheet\" data-precedence=\"default\">'\n        )\n      })\n\n      it('should not do special behavior if disabled is present', () => {\n        const App = () => {\n          return (\n            <div>\n              <link rel='stylesheet' href={'style.css'} precedence='default' disabled={true} />\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe('')\n        expect(root.innerHTML).toBe(\n          '<div><link rel=\"stylesheet\" href=\"style.css\" precedence=\"default\" disabled=\"\"></div>'\n        )\n      })\n\n      it('should be ordered by precedence attribute', () => {\n        const App = () => {\n          return (\n            <div>\n              <link rel='stylesheet' href='style-a.css' precedence='default' />\n              <link rel='stylesheet' href='style-b.css' precedence='high' />\n              <link rel='stylesheet' href='style-c.css' precedence='default' />\n              Content\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe(\n          '<link href=\"style-a.css\" rel=\"stylesheet\" data-precedence=\"default\"><link href=\"style-c.css\" rel=\"stylesheet\" data-precedence=\"default\"><link href=\"style-b.css\" rel=\"stylesheet\" data-precedence=\"high\">'\n        )\n        expect(root.innerHTML).toBe('<div>Content</div>')\n      })\n\n      it('should be de-duplicated by href attribute', async () => {\n        const App = () => {\n          const [count, setCount] = useState(0)\n          return (\n            <div>\n              <link rel='stylesheet' href='style-a.css' precedence='default' />\n              <link rel='stylesheet' href='style-b.css' precedence='high' />\n              {count === 1 && (\n                <>\n                  <link rel='stylesheet' href='style-a.css' precedence='default' />\n                  <link rel='stylesheet' href='style-c.css' precedence='other' />\n                </>\n              )}\n              <button onClick={() => setCount(count + 1)}>+</button>\n              {count}\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe(\n          '<link href=\"style-a.css\" rel=\"stylesheet\" data-precedence=\"default\"><link href=\"style-b.css\" rel=\"stylesheet\" data-precedence=\"high\">'\n        )\n        expect(root.innerHTML).toBe('<div><button>+</button>0</div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(document.head.innerHTML).toBe(\n          '<link href=\"style-a.css\" rel=\"stylesheet\" data-precedence=\"default\"><link href=\"style-b.css\" rel=\"stylesheet\" data-precedence=\"high\"><link href=\"style-c.css\" rel=\"stylesheet\" data-precedence=\"other\">'\n        )\n        expect(root.innerHTML).toBe('<div><button>+</button>1</div>')\n      })\n\n      it('should be preserved when unmounted', async () => {\n        const App = () => {\n          const [count, setCount] = useState(0)\n          return (\n            <div>\n              {count === 1 && <link rel='stylesheet' href='style.css' precedence='default' />}\n              <div>{count}</div>\n              <button onClick={() => setCount(count + 1)}>+</button>\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe('')\n        expect(root.innerHTML).toBe('<div><div>0</div><button>+</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(document.head.innerHTML).toBe(\n          '<link href=\"style.css\" rel=\"stylesheet\" data-precedence=\"default\">'\n        )\n        expect(root.innerHTML).toBe('<div><div>1</div><button>+</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(document.head.innerHTML).toBe(\n          '<link href=\"style.css\" rel=\"stylesheet\" data-precedence=\"default\">'\n        )\n        expect(root.innerHTML).toBe('<div><div>2</div><button>+</button></div>')\n      })\n\n      it('should be blocked by blocking attribute', async () => {\n        const Component = () => {\n          return (\n            <Suspense fallback={<div>Loading...</div>}>\n              <div>\n                <link\n                  rel='stylesheet'\n                  href='http://localhost/style.css'\n                  precedence='default'\n                  blocking='render'\n                />\n                Content\n              </div>\n            </Suspense>\n          )\n        }\n        const App = () => {\n          const [show, setShow] = useState(false)\n          return (\n            <div>\n              {show && <Component />}\n              <button onClick={() => setShow(true)}>Show</button>\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe('')\n        expect(root.innerHTML).toBe('<div><button>Show</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<div><div>Loading...</div><button>Show</button></div>')\n        await new Promise((resolve) => setTimeout(resolve))\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<div><div>Content</div><button>Show</button></div>')\n      })\n\n      describe('React 19 compatibility', () => {\n        type LinkSignatureInput = {\n          rel: string\n          href: string\n          hrefLang?: string\n          type?: string\n          title?: string\n          media?: string\n          as?: string\n          crossOrigin?: string\n          sizes?: string\n          dataPrecedence?: string\n        }\n\n        const sig = (v: LinkSignatureInput) =>\n          [\n            v.rel,\n            v.href,\n            v.hrefLang ?? '',\n            v.type ?? '',\n            v.title ?? '',\n            v.media ?? '',\n            v.as ?? '',\n            v.crossOrigin ?? '',\n            v.sizes ?? '',\n            v.dataPrecedence ?? '',\n          ].join('|')\n\n        const headLinkSignatures = () =>\n          Array.from(document.head.querySelectorAll('link')).map((el) =>\n            sig({\n              rel: el.getAttribute('rel') ?? '',\n              href: el.getAttribute('href') ?? '',\n              hrefLang: el.getAttribute('hreflang') ?? '',\n              type: el.getAttribute('type') ?? '',\n              title: el.getAttribute('title') ?? '',\n              media: el.getAttribute('media') ?? '',\n              as: el.getAttribute('as') ?? '',\n              crossOrigin: el.getAttribute('crossorigin') ?? '',\n              sizes: el.getAttribute('sizes') ?? '',\n              dataPrecedence: el.getAttribute('data-precedence') ?? '',\n            })\n          )\n\n        const assertHeadLinks = (\n          node: unknown,\n          expected: string[],\n          expectedRootInnerHTML?: string\n        ) => {\n          render(node as never, root)\n          expect(headLinkSignatures()).toEqual(expected)\n          if (expectedRootInnerHTML !== undefined) {\n            expect(root.innerHTML).toBe(expectedRootInnerHTML)\n          }\n        }\n\n        it('should keep canonical and alternates in source order', () => {\n          assertHeadLinks(\n            <div>\n              <link rel='canonical' href='https://example.com/en/about' />\n              <link rel='alternate' hrefLang='en' href='https://example.com/en/about' />\n              <link rel='alternate' hrefLang='ja' href='https://example.com/ja/about' />\n            </div>,\n            [\n              sig({ rel: 'canonical', href: 'https://example.com/en/about' }),\n              sig({ rel: 'alternate', href: 'https://example.com/en/about', hrefLang: 'en' }),\n              sig({ rel: 'alternate', href: 'https://example.com/ja/about', hrefLang: 'ja' }),\n            ]\n          )\n        })\n\n        it('should keep alternate-canonical-alternate order', () => {\n          assertHeadLinks(\n            <div>\n              <link rel='alternate' hrefLang='en' href='https://example.com/en/about' />\n              <link rel='canonical' href='https://example.com/en/about' />\n              <link rel='alternate' hrefLang='ja' href='https://example.com/ja/about' />\n            </div>,\n            [\n              sig({ rel: 'alternate', href: 'https://example.com/en/about', hrefLang: 'en' }),\n              sig({ rel: 'canonical', href: 'https://example.com/en/about' }),\n              sig({ rel: 'alternate', href: 'https://example.com/ja/about', hrefLang: 'ja' }),\n            ]\n          )\n        })\n\n        it('should not de-duplicate canonical links', () => {\n          assertHeadLinks(\n            <div>\n              <link rel='canonical' href='https://example.com/en/about' />\n              <link rel='canonical' href='https://example.com/en/about' />\n            </div>,\n            [\n              sig({ rel: 'canonical', href: 'https://example.com/en/about' }),\n              sig({ rel: 'canonical', href: 'https://example.com/en/about' }),\n            ]\n          )\n        })\n\n        it('should not de-duplicate alternate links', () => {\n          assertHeadLinks(\n            <div>\n              <link rel='alternate' hrefLang='en' href='https://example.com/en/about' />\n              <link rel='alternate' hrefLang='en' href='https://example.com/en/about' />\n            </div>,\n            [\n              sig({ rel: 'alternate', href: 'https://example.com/en/about', hrefLang: 'en' }),\n              sig({ rel: 'alternate', href: 'https://example.com/en/about', hrefLang: 'en' }),\n            ]\n          )\n        })\n\n        it('should de-duplicate stylesheet with precedence', () => {\n          assertHeadLinks(\n            <div>\n              <link rel='stylesheet' href='/style.css' precedence='default' />\n              <link rel='stylesheet' href='/style.css' precedence='default' />\n            </div>,\n            [sig({ rel: 'stylesheet', href: '/style.css', dataPrecedence: 'default' })]\n          )\n        })\n\n        it('should not de-duplicate stylesheet against preload with same href', () => {\n          assertHeadLinks(\n            <div>\n              <link rel='preload' href='/style.css' as='style' />\n              <link rel='stylesheet' href='/style.css' precedence='default' />\n              <link rel='stylesheet' href='/style.css' precedence='default' />\n            </div>,\n            [\n              sig({ rel: 'stylesheet', href: '/style.css', dataPrecedence: 'default' }),\n              sig({ rel: 'preload', href: '/style.css', as: 'style' }),\n            ]\n          )\n        })\n\n        it('should keep stylesheet links without precedence in root', () => {\n          assertHeadLinks(\n            <div>\n              <link rel='stylesheet' href='/style.css' />\n              <link rel='stylesheet' href='/style.css' />\n            </div>,\n            [],\n            '<div><link rel=\"stylesheet\" href=\"/style.css\"><link rel=\"stylesheet\" href=\"/style.css\"></div>'\n          )\n        })\n\n        it('should keep different stylesheets with same precedence', () => {\n          assertHeadLinks(\n            <div>\n              <link rel='stylesheet' href='/a.css' precedence='default' />\n              <link rel='stylesheet' href='/b.css' precedence='default' />\n            </div>,\n            [\n              sig({ rel: 'stylesheet', href: '/a.css', dataPrecedence: 'default' }),\n              sig({ rel: 'stylesheet', href: '/b.css', dataPrecedence: 'default' }),\n            ]\n          )\n        })\n\n        it('should not de-duplicate preload links', () => {\n          assertHeadLinks(\n            <div>\n              <link rel='preload' href='/font.woff2' as='font' crossOrigin='' />\n              <link rel='preload' href='/font.woff2' as='font' crossOrigin='' />\n            </div>,\n            [\n              sig({ rel: 'preload', href: '/font.woff2', as: 'font', crossOrigin: '' }),\n              sig({ rel: 'preload', href: '/font.woff2', as: 'font', crossOrigin: '' }),\n            ]\n          )\n        })\n\n        it('should not de-duplicate modulepreload links', () => {\n          assertHeadLinks(\n            <div>\n              <link rel='modulepreload' href='/module.js' />\n              <link rel='modulepreload' href='/module.js' />\n            </div>,\n            [\n              sig({ rel: 'modulepreload', href: '/module.js' }),\n              sig({ rel: 'modulepreload', href: '/module.js' }),\n            ]\n          )\n        })\n\n        it('should keep links from two components', () => {\n          const Head = () => (\n            <>\n              <link rel='canonical' href='https://example.com/en/about' />\n              <link rel='alternate' hrefLang='en' href='https://example.com/en/about' />\n              <link rel='alternate' hrefLang='ja' href='https://example.com/ja/about' />\n            </>\n          )\n          const Body = () => (\n            <>\n              <link rel='canonical' href='https://example.com/en/about' />\n              <link rel='alternate' hrefLang='en' href='https://example.com/en/about' />\n              <link rel='alternate' hrefLang='ja' href='https://example.com/ja/about' />\n            </>\n          )\n          assertHeadLinks(\n            <div>\n              <Head />\n              <Body />\n            </div>,\n            [\n              sig({ rel: 'canonical', href: 'https://example.com/en/about' }),\n              sig({ rel: 'alternate', href: 'https://example.com/en/about', hrefLang: 'en' }),\n              sig({ rel: 'alternate', href: 'https://example.com/ja/about', hrefLang: 'ja' }),\n              sig({ rel: 'canonical', href: 'https://example.com/en/about' }),\n              sig({ rel: 'alternate', href: 'https://example.com/en/about', hrefLang: 'en' }),\n              sig({ rel: 'alternate', href: 'https://example.com/ja/about', hrefLang: 'ja' }),\n            ]\n          )\n        })\n\n        it('should hoist from deep nested component and keep duplicates', () => {\n          const Nested = () => (\n            <div>\n              <div>\n                <link rel='canonical' href='https://example.com/en/about' />\n              </div>\n            </div>\n          )\n          assertHeadLinks(\n            <div>\n              <link rel='canonical' href='https://example.com/en/about' />\n              <Nested />\n            </div>,\n            [\n              sig({ rel: 'canonical', href: 'https://example.com/en/about' }),\n              sig({ rel: 'canonical', href: 'https://example.com/en/about' }),\n            ]\n          )\n        })\n\n        it('should keep mixed links and de-duplicate only stylesheet', () => {\n          assertHeadLinks(\n            <div>\n              <link rel='canonical' href='https://example.com/en/about' />\n              <link rel='alternate' hrefLang='en' href='https://example.com/en/about' />\n              <link rel='stylesheet' href='/style.css' precedence='default' />\n              <link rel='preload' href='/font.woff2' as='font' crossOrigin='' />\n              <link rel='canonical' href='https://example.com/en/about' />\n              <link rel='alternate' hrefLang='en' href='https://example.com/en/about' />\n              <link rel='stylesheet' href='/style.css' precedence='default' />\n              <link rel='preload' href='/font.woff2' as='font' crossOrigin='' />\n            </div>,\n            [\n              sig({ rel: 'stylesheet', href: '/style.css', dataPrecedence: 'default' }),\n              sig({ rel: 'canonical', href: 'https://example.com/en/about' }),\n              sig({ rel: 'alternate', href: 'https://example.com/en/about', hrefLang: 'en' }),\n              sig({ rel: 'preload', href: '/font.woff2', as: 'font', crossOrigin: '' }),\n              sig({ rel: 'canonical', href: 'https://example.com/en/about' }),\n              sig({ rel: 'alternate', href: 'https://example.com/en/about', hrefLang: 'en' }),\n              sig({ rel: 'preload', href: '/font.woff2', as: 'font', crossOrigin: '' }),\n            ]\n          )\n        })\n      })\n    })\n\n    describe('style element', () => {\n      it('should be inserted into head', () => {\n        const App = () => {\n          return (\n            <div>\n              <style href='red' precedence='default'>\n                {'body { color: red; }'}\n              </style>\n              Content\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe(\n          '<style data-href=\"red\" data-precedence=\"default\">body { color: red; }</style>'\n        )\n        expect(root.innerHTML).toBe('<div>Content</div>')\n      })\n\n      it('should be updated', async () => {\n        const App = () => {\n          const [count, setCount] = useState(0)\n          return (\n            <div>\n              <style href='color' precedence='default'>{`body { color: ${\n                count % 2 ? 'red' : 'blue'\n              }; }`}</style>\n              <button onClick={() => setCount(count + 1)}>+</button>\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe(\n          '<style data-href=\"color\" data-precedence=\"default\">body { color: blue; }</style>'\n        )\n        expect(root.innerHTML).toBe('<div><button>+</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(document.head.innerHTML).toBe(\n          '<style data-href=\"color\" data-precedence=\"default\">body { color: red; }</style>'\n        )\n      })\n\n      it('should be preserved when unmounted', async () => {\n        const App = () => {\n          const [count, setCount] = useState(0)\n          return (\n            <div>\n              {count === 1 && (\n                <style href='red' precedence='default'>\n                  {'body { color: red; }'}\n                </style>\n              )}\n              <div>{count}</div>\n              <button onClick={() => setCount(count + 1)}>+</button>\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe('')\n        expect(root.innerHTML).toBe('<div><div>0</div><button>+</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(document.head.innerHTML).toBe(\n          '<style data-href=\"red\" data-precedence=\"default\">body { color: red; }</style>'\n        )\n        expect(root.innerHTML).toBe('<div><div>1</div><button>+</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(document.head.innerHTML).toBe(\n          '<style data-href=\"red\" data-precedence=\"default\">body { color: red; }</style>'\n        )\n        expect(root.innerHTML).toBe('<div><div>2</div><button>+</button></div>')\n      })\n\n      it('should be de-duplicated by href attribute', async () => {\n        const App = () => {\n          const [count, setCount] = useState(0)\n          return (\n            <div>\n              <style href='red' precedence='default'>\n                {'body { color: red; }'}\n              </style>\n              <style href='blue' precedence='default'>\n                {'body { color: blue; }'}\n              </style>\n              <style href='green' precedence='default'>\n                {'body { color: green; }'}\n              </style>\n              {count === 1 && (\n                <>\n                  <style href='blue' precedence='default'>\n                    {'body { color: blue; }'}\n                  </style>\n                  <style href='yellow' precedence='default'>\n                    {'body { color: yellow; }'}\n                  </style>\n                </>\n              )}\n              <button onClick={() => setCount(count + 1)}>+</button>\n              {count}\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe(\n          '<style data-href=\"red\" data-precedence=\"default\">body { color: red; }</style><style data-href=\"blue\" data-precedence=\"default\">body { color: blue; }</style><style data-href=\"green\" data-precedence=\"default\">body { color: green; }</style>'\n        )\n        expect(root.innerHTML).toBe('<div><button>+</button>0</div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(document.head.innerHTML).toBe(\n          '<style data-href=\"red\" data-precedence=\"default\">body { color: red; }</style><style data-href=\"blue\" data-precedence=\"default\">body { color: blue; }</style><style data-href=\"green\" data-precedence=\"default\">body { color: green; }</style><style data-href=\"yellow\" data-precedence=\"default\">body { color: yellow; }</style>'\n        )\n        expect(root.innerHTML).toBe('<div><button>+</button>1</div>')\n      })\n\n      it('should be ordered by precedence attribute', () => {\n        const App = () => {\n          return (\n            <div>\n              <style href='red' precedence='default'>\n                {'body { color: red; }'}\n              </style>\n              <style href='green' precedence='high'>\n                {'body { color: green; }'}\n              </style>\n              <style href='blue' precedence='default'>\n                {'body { color: blue; }'}\n              </style>\n              Content\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe(\n          '<style data-href=\"red\" data-precedence=\"default\">body { color: red; }</style><style data-href=\"blue\" data-precedence=\"default\">body { color: blue; }</style><style data-href=\"green\" data-precedence=\"high\">body { color: green; }</style>'\n        )\n        expect(root.innerHTML).toBe('<div>Content</div>')\n      })\n\n      it('should not do special behavior if href is present', () => {\n        const template = (\n          <html>\n            <head></head>\n            <body>\n              <style>{'body { color: red; }'}</style>\n              <h1>World</h1>\n            </body>\n          </html>\n        )\n        render(template, root)\n        expect(document.head.innerHTML).toBe('')\n        expect(root.innerHTML).toBe(\n          '<html><head></head><body><style>body { color: red; }</style><h1>World</h1></body></html>'\n        )\n      })\n    })\n\n    describe('meta element', () => {\n      it('should be inserted into head', () => {\n        const App = () => {\n          return (\n            <div>\n              <meta name='description' content='description' />\n              Content\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe('<meta name=\"description\" content=\"description\">')\n        expect(root.innerHTML).toBe('<div>Content</div>')\n      })\n\n      it('should be updated', async () => {\n        const App = () => {\n          const [count, setCount] = useState(0)\n          return (\n            <div>\n              <meta name='description' content={`description ${count}`} />\n              <button onClick={() => setCount(count + 1)}>+</button>\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe('<meta name=\"description\" content=\"description 0\">')\n        expect(root.innerHTML).toBe('<div><button>+</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(document.head.innerHTML).toBe('<meta name=\"description\" content=\"description 1\">')\n      })\n\n      it('should not do special behavior if itemProp is present', () => {\n        const App = () => {\n          return (\n            <div>\n              <meta name='description' content='description' itemProp='test' />\n              Content\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe('')\n        expect(root.innerHTML).toBe(\n          '<div><meta name=\"description\" content=\"description\" itemprop=\"test\">Content</div>'\n        )\n      })\n\n      it('should ignore precedence attribute', () => {\n        const App = () => {\n          return (\n            <div>\n              <meta name='description-a' content='description-a' precedence='default' />\n              <meta name='description-b' content='description-b' precedence='high' />\n              <meta name='description-c' content='description-c' precedence='default' />\n              Content\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe(\n          '<meta name=\"description-a\" content=\"description-a\"><meta name=\"description-b\" content=\"description-b\"><meta name=\"description-c\" content=\"description-c\">'\n        )\n        expect(root.innerHTML).toBe('<div>Content</div>')\n      })\n\n      it('should be de-duplicated by name attribute', async () => {\n        const App = () => {\n          const [count, setCount] = useState(0)\n          return (\n            <div>\n              <meta name='description-a' content='description-a' />\n              <meta name='description-b' content='description-b' />\n              {count === 1 && (\n                <>\n                  <meta name='description-a' content='description-a' />\n                  <meta name='description-c' content='description-c' />\n                </>\n              )}\n              <button onClick={() => setCount(count + 1)}>+</button>\n              {count}\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe(\n          '<meta name=\"description-a\" content=\"description-a\"><meta name=\"description-b\" content=\"description-b\">'\n        )\n        expect(root.innerHTML).toBe('<div><button>+</button>0</div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(document.head.innerHTML).toBe(\n          '<meta name=\"description-a\" content=\"description-a\"><meta name=\"description-b\" content=\"description-b\"><meta name=\"description-c\" content=\"description-c\">'\n        )\n        expect(root.innerHTML).toBe('<div><button>+</button>1</div>')\n      })\n    })\n\n    describe('script element', () => {\n      it('should be inserted into head', async () => {\n        const App = () => {\n          return (\n            <div>\n              <script src='script.js' async={true} />\n              Content\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe('<script src=\"script.js\" async=\"\"></script>')\n        expect(root.innerHTML).toBe('<div>Content</div>')\n      })\n\n      it('should be updated', async () => {\n        const App = () => {\n          const [count, setCount] = useState(0)\n          return (\n            <div>\n              <script src={`script${count}.js`} async={true} />\n              <button onClick={() => setCount(count + 1)}>+</button>\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe('<script src=\"script0.js\" async=\"\"></script>')\n        expect(root.innerHTML).toBe('<div><button>+</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(document.head.innerHTML).toBe('<script src=\"script1.js\" async=\"\"></script>')\n      })\n\n      it('should be de-duplicated by src attribute with async=true', async () => {\n        const App = () => {\n          const [count, setCount] = useState(0)\n          return (\n            <div>\n              <script src='script-a.js' async={true} />\n              <script src='script-b.js' async={true} />\n              {count === 1 && (\n                <>\n                  <script src='script-a.js' async={true} />\n                  <script src='script-c.js' async={true} />\n                </>\n              )}\n              <button onClick={() => setCount(count + 1)}>+</button>\n              {count}\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe(\n          '<script src=\"script-a.js\" async=\"\"></script><script src=\"script-b.js\" async=\"\"></script>'\n        )\n        expect(root.innerHTML).toBe('<div><button>+</button>0</div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(document.head.innerHTML).toBe(\n          '<script src=\"script-a.js\" async=\"\"></script><script src=\"script-b.js\" async=\"\"></script><script src=\"script-c.js\" async=\"\"></script>'\n        )\n        expect(root.innerHTML).toBe('<div><button>+</button>1</div>')\n      })\n\n      it('should be preserved when unmounted', async () => {\n        const App = () => {\n          const [count, setCount] = useState(0)\n          return (\n            <div>\n              {count === 1 && <script src='script.js' async={true} />}\n              <div>{count}</div>\n              <button onClick={() => setCount(count + 1)}>+</button>\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe('')\n        expect(root.innerHTML).toBe('<div><div>0</div><button>+</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(document.head.innerHTML).toBe('<script src=\"script.js\" async=\"\"></script>')\n        expect(root.innerHTML).toBe('<div><div>1</div><button>+</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(document.head.innerHTML).toBe('<script src=\"script.js\" async=\"\"></script>')\n        expect(root.innerHTML).toBe('<div><div>2</div><button>+</button></div>')\n      })\n\n      it('should be fired onLoad event', async () => {\n        const onLoad = vi.fn()\n        const onError = vi.fn()\n        const App = () => {\n          return (\n            <div>\n              <script\n                src='http://localhost/script.js'\n                async={true}\n                onLoad={onLoad}\n                onError={onError}\n              />\n              Content\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe(\n          '<script src=\"http://localhost/script.js\" async=\"\"></script>'\n        )\n        await Promise.resolve()\n        await new Promise((resolve) => setTimeout(resolve))\n        expect(onLoad).toBeCalledTimes(1)\n        expect(onError).not.toBeCalled()\n      })\n\n      it('should be fired onError event', async () => {\n        const onLoad = vi.fn()\n        const onError = vi.fn()\n        const App = () => {\n          return (\n            <div>\n              <script\n                src='http://localhost/invalid.js'\n                async={true}\n                onLoad={onLoad}\n                onError={onError}\n              />\n              Content\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe(\n          '<script src=\"http://localhost/invalid.js\" async=\"\"></script>'\n        )\n        await Promise.resolve()\n        await new Promise((resolve) => setTimeout(resolve))\n        await Promise.resolve()\n        await new Promise((resolve) => setTimeout(resolve))\n        await Promise.resolve()\n        await new Promise((resolve) => setTimeout(resolve))\n        await Promise.resolve()\n        await new Promise((resolve) => setTimeout(resolve))\n        expect(onLoad).not.toBeCalled()\n        expect(onError).toBeCalledTimes(1)\n      })\n\n      it('should not register load handlers when the tag has no de-duplication key', () => {\n        const addEventListener = vi.fn<HTMLElement['addEventListener']>()\n        const onLoad = vi.fn()\n        const onError = vi.fn()\n\n        const App = () => {\n          return (\n            <div>\n              <title\n                ref={(e: HTMLTitleElement) => {\n                  if (!e) {\n                    return\n                  }\n                  const originalAddEventListener = e.addEventListener.bind(e)\n                  addEventListener.mockImplementation((...args) =>\n                    originalAddEventListener(...args)\n                  )\n                  e.addEventListener = addEventListener\n                }}\n                onLoad={onLoad}\n                onError={onError}\n              >\n                Document Title\n              </title>\n              Content\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe('<title>Document Title</title>')\n        expect(root.innerHTML).toBe('<div>Content</div>')\n        expect(addEventListener).not.toBeCalled()\n        expect(onLoad).not.toBeCalled()\n        expect(onError).not.toBeCalled()\n      })\n\n      it('should be blocked by blocking attribute', async () => {\n        const Component = () => {\n          return (\n            <Suspense fallback={<div>Loading...</div>}>\n              <div>\n                <script src='http://localhost/script.js' async={true} blocking='render' />\n                Content\n              </div>\n            </Suspense>\n          )\n        }\n        const App = () => {\n          const [show, setShow] = useState(false)\n          return (\n            <div>\n              {show && <Component />}\n              <button onClick={() => setShow(true)}>Show</button>\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe('')\n        expect(root.innerHTML).toBe('<div><button>Show</button></div>')\n        root.querySelector('button')?.click()\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<div><div>Loading...</div><button>Show</button></div>')\n        await new Promise((resolve) => setTimeout(resolve))\n        await Promise.resolve()\n        expect(root.innerHTML).toBe('<div><div>Content</div><button>Show</button></div>')\n      })\n\n      it('should be inserted into body if has no props', async () => {\n        const App = () => {\n          return (\n            <div>\n              <script>alert('Hello')</script>\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe('')\n        // prettier-ignore\n        expect(root.innerHTML).toBe('<div><script>alert(\\'Hello\\')</script></div>')\n      })\n\n      it('should be inserted into body if has only src prop', async () => {\n        const App = () => {\n          return (\n            <div>\n              <script src='script.js'></script>\n            </div>\n          )\n        }\n        render(<App />, root)\n        expect(document.head.innerHTML).toBe('')\n        expect(root.innerHTML).toBe('<div><script src=\"script.js\"></script></div>')\n      })\n    })\n\n    it('accept ref object', async () => {\n      const ref = { current: null }\n      const App = () => {\n        return (\n          <div>\n            <script src='script-a.js' ref={ref} async={true} />\n          </div>\n        )\n      }\n      render(<App />, root)\n      expect(ref.current).toBe(document.head.querySelector('script'))\n    })\n\n    it('accept ref function', async () => {\n      const ref = vi.fn()\n      const App = () => {\n        return (\n          <div>\n            <script src='script-a.js' ref={ref} async={true} />\n          </div>\n        )\n      }\n      render(<App />, root)\n      expect(ref).toHaveBeenCalledTimes(1)\n    })\n  })\n\n  describe('form element', () => {\n    it('should accept Function as action', () => {\n      const action = vi.fn()\n      const App = () => {\n        return (\n          <form action={action} method='post'>\n            <input type='text' name='name' value='Hello' />\n            <button type='submit'>Submit</button>\n          </form>\n        )\n      }\n      render(<App />, root)\n      root.querySelector('button')?.click()\n      expect(action).toBeCalledTimes(1)\n      const formData = action.mock.calls[0][0]\n      expect(formData.get('name')).toBe('Hello')\n    })\n\n    it('should accept string as action', () => {\n      const App = () => {\n        return (\n          <form action={'/entries'} method='post'>\n            <button type='submit'>Submit</button>\n          </form>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe(\n        '<form method=\"post\" action=\"/entries\"><button type=\"submit\">Submit</button></form>'\n      )\n    })\n\n    it('toggle show / hide form', async () => {\n      const action = vi.fn()\n      const App = () => {\n        const [show, setShow] = useState(false)\n        return (\n          <div>\n            {show && (\n              <form action={action} method='post'>\n                <input type='text' name='name' value='Hello' />\n              </form>\n            )}\n            <button onClick={() => setShow((status) => !status)}>Toggle</button>\n          </div>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button>Toggle</button></div>')\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe(\n        '<div><form method=\"post\"><input type=\"text\" name=\"name\" value=\"Hello\"></form><button>Toggle</button></div>'\n      )\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      await Promise.resolve()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><button>Toggle</button></div>')\n    })\n  })\n\n  describe('input element', () => {\n    it('should accept Function as formAction', () => {\n      const action = vi.fn()\n      const App = () => {\n        return (\n          <form>\n            <input type='text' name='name' value='Hello' />\n            <input type='submit' value='Submit' formAction={action} />\n          </form>\n        )\n      }\n      render(<App />, root)\n      root.querySelector<HTMLInputElement>('input[type=\"submit\"]')?.click()\n      expect(action).toBeCalledTimes(1)\n      const formData = action.mock.calls[0][0]\n      expect(formData.get('name')).toBe('Hello')\n    })\n\n    it('should accept string as formAction', () => {\n      const App = () => {\n        return (\n          <form method='post'>\n            <input type='submit' formAction={'/entries'} value='Submit' />\n          </form>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<form method=\"post\"><input type=\"submit\" value=\"Submit\"></form>')\n    })\n\n    it('toggle show / hide input', async () => {\n      const action = vi.fn()\n      const App = () => {\n        const [show, setShow] = useState(false)\n        return (\n          <div>\n            {show && (\n              <form method='post'>\n                <input type='submit' formAction={action} value='Submit' />\n              </form>\n            )}\n            <button onClick={() => setShow((status) => !status)}>Toggle</button>\n          </div>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button>Toggle</button></div>')\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe(\n        '<div><form method=\"post\"><input type=\"submit\" value=\"Submit\"></form><button>Toggle</button></div>'\n      )\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      await Promise.resolve()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><button>Toggle</button></div>')\n    })\n  })\n\n  describe('button element', () => {\n    it('should accept Function as formAction', () => {\n      const action = vi.fn()\n      const App = () => {\n        return (\n          <form>\n            <input type='text' name='name' value='Hello' />\n            <button type='submit' formAction={action}>\n              Submit\n            </button>\n          </form>\n        )\n      }\n      render(<App />, root)\n      root.querySelector('button')?.click()\n      expect(action).toBeCalledTimes(1)\n      const formData = action.mock.calls[0][0]\n      expect(formData.get('name')).toBe('Hello')\n    })\n\n    it('should accept string as formAction', () => {\n      const App = () => {\n        return (\n          <form method='post'>\n            <button type='submit' formAction={'/entries'}>\n              Submit\n            </button>\n          </form>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe(\n        '<form method=\"post\"><button type=\"submit\">Submit</button></form>'\n      )\n    })\n\n    it('toggle show / hide', async () => {\n      const action = vi.fn()\n      const App = () => {\n        const [show, setShow] = useState(false)\n        return (\n          <div>\n            {show && (\n              <form method='post'>\n                <button formAction={action}>Submit</button>\n              </form>\n            )}\n            <button id='toggle' onClick={() => setShow((status) => !status)}>\n              Toggle\n            </button>\n          </div>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button id=\"toggle\">Toggle</button></div>')\n      root.querySelector<HTMLButtonElement>('#toggle')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe(\n        '<div><form method=\"post\"><button>Submit</button></form><button id=\"toggle\">Toggle</button></div>'\n      )\n      root.querySelector<HTMLButtonElement>('#toggle')?.click()\n      await Promise.resolve()\n      await Promise.resolve()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><button id=\"toggle\">Toggle</button></div>')\n    })\n  })\n})\n\ndescribe('internal utility method', () => {\n  describe('composeRef()', () => {\n    it('should compose a ref object', () => {\n      const ref = { current: null }\n      const cbCleanUp = vi.fn()\n      const cb = vi.fn().mockReturnValue(cbCleanUp)\n      const composed = composeRef(ref, cb)\n      const cleanup = composed('ref')\n      expect(ref.current).toBe('ref')\n      expect(cb).toBeCalledWith('ref')\n      expect(cbCleanUp).not.toBeCalled()\n      cleanup()\n      expect(ref.current).toBe(null)\n      expect(cbCleanUp).toBeCalledTimes(1)\n    })\n\n    it('should compose a function', () => {\n      const ref = vi.fn()\n      const cbCleanUp = vi.fn()\n      const cb = vi.fn().mockReturnValue(cbCleanUp)\n      const composed = composeRef(ref, cb)\n      const cleanup = composed('ref')\n      expect(ref).toBeCalledWith('ref')\n      expect(cb).toBeCalledWith('ref')\n      expect(cbCleanUp).not.toBeCalled()\n      cleanup()\n      expect(ref).toBeCalledWith(null)\n      expect(cbCleanUp).toBeCalledTimes(1)\n    })\n\n    it('should compose a function returns a cleanup function', () => {\n      const refCleanUp = vi.fn()\n      const ref = vi.fn().mockReturnValue(refCleanUp)\n      const cbCleanUp = vi.fn()\n      const cb = vi.fn().mockReturnValue(cbCleanUp)\n      const composed = composeRef(ref, cb)\n      const cleanup = composed('ref')\n      expect(ref).toBeCalledWith('ref')\n      expect(cb).toBeCalledWith('ref')\n      expect(refCleanUp).not.toBeCalled()\n      expect(cbCleanUp).not.toBeCalled()\n      cleanup()\n      expect(ref).toHaveBeenCalledTimes(1)\n      expect(refCleanUp).toBeCalledTimes(1)\n      expect(cbCleanUp).toBeCalledTimes(1)\n    })\n  })\n})\n"
  },
  {
    "path": "src/jsx/dom/intrinsic-element/components.ts",
    "content": "import type { Props } from '../../base'\nimport { useContext } from '../../context'\nimport { use, useCallback, useMemo, useState } from '../../hooks'\nimport {\n  dataPrecedenceAttr,\n  deDupeKeyMap,\n  domRenderers,\n  isStylesheetLinkWithPrecedence,\n  shouldDeDupeByKey,\n} from '../../intrinsic-element/common'\nimport type { IntrinsicElements } from '../../intrinsic-elements'\nimport type { FC, JSXNode, PropsWithChildren, RefObject } from '../../types'\nimport { FormContext, registerAction } from '../hooks'\nimport type { PreserveNodeType } from '../render'\nimport { createPortal, getNameSpaceContext } from '../render'\n\n// this function is a testing utility and should not be exported to the user\nexport const clearCache = () => {\n  blockingPromiseMap = Object.create(null)\n  createdElements = Object.create(null)\n}\n\n// this function is exported for testing and should not be used by the user\nexport const composeRef = <T>(\n  ref: RefObject<T> | Function | undefined,\n  cb: (e: T) => void | (() => void)\n): ((e: T) => () => void) => {\n  return useMemo(\n    () => (e: T) => {\n      let refCleanup: (() => void) | undefined\n      if (ref) {\n        if (typeof ref === 'function') {\n          refCleanup =\n            ref(e) ||\n            (() => {\n              ref(null)\n            })\n        } else if (ref && 'current' in ref) {\n          ref.current = e\n          refCleanup = () => {\n            ref.current = null\n          }\n        }\n      }\n\n      const cbCleanup = cb(e)\n      return () => {\n        cbCleanup?.()\n        refCleanup?.()\n      }\n    },\n    [ref]\n  )\n}\n\nlet blockingPromiseMap: Record<string, Promise<Event> | undefined> = Object.create(null)\nlet createdElements: Record<string, HTMLElement> = Object.create(null)\nconst documentMetadataTag = (\n  tag: string,\n  props: Props,\n  preserveNodeType: PreserveNodeType | undefined,\n  supportSort: boolean,\n  supportBlocking: boolean\n) => {\n  if (props?.itemProp) {\n    return {\n      tag,\n      props,\n      type: tag,\n      ref: props.ref,\n    }\n  }\n\n  const head = document.head\n\n  let { onLoad, onError, precedence, blocking, ...restProps } = props\n  let element: HTMLElement | null = null\n  let created = false\n\n  const deDupeKeys = deDupeKeyMap[tag]\n  const deDupeByKey = shouldDeDupeByKey(tag, supportSort)\n  const isDeDupeCandidateLink = (e: HTMLElement) =>\n    e.getAttribute('rel') === 'stylesheet' && e.getAttribute(dataPrecedenceAttr) !== null\n  let existingElements: NodeListOf<HTMLElement> | undefined = undefined\n  if (deDupeByKey) {\n    const tags = head.querySelectorAll<HTMLElement>(tag)\n    LOOP: for (const e of tags) {\n      if (tag === 'link' && !isDeDupeCandidateLink(e)) {\n        continue\n      }\n      for (const key of deDupeKeys) {\n        if (e.getAttribute(key) === props[key]) {\n          element = e\n          break LOOP\n        }\n      }\n    }\n\n    if (!element) {\n      const cacheKey = deDupeKeys.reduce(\n        (acc, key) => (props[key] === undefined ? acc : `${acc}-${key}-${props[key]}`),\n        tag\n      )\n      created = !createdElements[cacheKey]\n      element = createdElements[cacheKey] ||= (() => {\n        const e = document.createElement(tag)\n        for (const key of deDupeKeys) {\n          if (props[key] !== undefined) {\n            e.setAttribute(key, props[key] as string)\n          }\n        }\n        if (props.rel) {\n          e.setAttribute('rel', props.rel)\n        }\n        return e\n      })()\n    }\n  } else {\n    existingElements = head.querySelectorAll<HTMLElement>(tag)\n  }\n\n  precedence = supportSort ? (precedence ?? '') : undefined\n  if (supportSort) {\n    restProps[dataPrecedenceAttr] = precedence\n  }\n\n  const insert = useCallback(\n    (e: HTMLElement) => {\n      if (deDupeByKey) {\n        if (tag === 'link' && precedence !== undefined) {\n          let found = false\n          for (const existingElement of head.querySelectorAll<HTMLElement>(tag)) {\n            const existingPrecedence = existingElement.getAttribute(dataPrecedenceAttr)\n            if (existingPrecedence === null) {\n              head.insertBefore(e, existingElement)\n              return\n            }\n            if (found && existingPrecedence !== precedence) {\n              head.insertBefore(e, existingElement)\n              return\n            }\n            if (existingPrecedence === precedence) {\n              found = true\n            }\n          }\n\n          // if sentinel is not found, append to the end\n          head.appendChild(e)\n          return\n        }\n\n        let found = false\n        for (const existingElement of head.querySelectorAll<HTMLElement>(tag)) {\n          if (found && existingElement.getAttribute(dataPrecedenceAttr) !== precedence) {\n            head.insertBefore(e, existingElement)\n            return\n          }\n          if (existingElement.getAttribute(dataPrecedenceAttr) === precedence) {\n            found = true\n          }\n        }\n\n        // if sentinel is not found, append to the end\n        head.appendChild(e)\n      } else if (tag === 'link') {\n        if (!head.contains(e)) {\n          head.appendChild(e)\n        }\n      } else if (existingElements) {\n        let found = false\n        for (const existingElement of existingElements!) {\n          if (existingElement === e) {\n            found = true\n            break\n          }\n        }\n        if (!found) {\n          // newly created element\n          head.insertBefore(\n            e,\n            head.contains(existingElements[0]) ? existingElements[0] : head.querySelector(tag)\n          )\n        }\n        existingElements = undefined\n      }\n    },\n    [deDupeByKey, precedence, tag]\n  )\n\n  const ref = composeRef(props.ref, (e: HTMLElement) => {\n    const key = deDupeKeys[0]\n\n    if (preserveNodeType === 2) {\n      e.innerHTML = ''\n    }\n\n    if (created || existingElements) {\n      insert(e)\n    }\n\n    if (!onError && !onLoad) {\n      return\n    }\n    if (!key) {\n      return\n    }\n\n    let promise = (blockingPromiseMap[e.getAttribute(key) as string] ||= new Promise<Event>(\n      (resolve, reject) => {\n        e.addEventListener('load', resolve)\n        e.addEventListener('error', reject)\n      }\n    ))\n    if (onLoad) {\n      promise = promise.then(onLoad)\n    }\n    if (onError) {\n      promise = promise.catch(onError)\n    }\n    promise.catch(() => {})\n  })\n\n  if (supportBlocking && blocking === 'render') {\n    const key = deDupeKeyMap[tag][0]\n    if (key && props[key]) {\n      const value = props[key]\n      const promise = (blockingPromiseMap[value] ||= new Promise<Event>((resolve, reject) => {\n        insert(element as HTMLElement)\n        element!.addEventListener('load', resolve)\n        element!.addEventListener('error', reject)\n      }))\n      use(promise)\n    }\n  }\n\n  const jsxNode = {\n    tag,\n    type: tag,\n    props: {\n      ...restProps,\n      ref,\n    },\n    ref,\n  } as unknown as JSXNode & { e?: HTMLElement; p?: PreserveNodeType }\n\n  jsxNode.p = preserveNodeType // preserve for unmounting\n  if (element) {\n    jsxNode.e = element\n  }\n\n  return createPortal(\n    jsxNode,\n    head\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  ) as any\n}\nexport const title: FC<PropsWithChildren> = (props) => {\n  const nameSpaceContext = getNameSpaceContext()\n  const ns = nameSpaceContext && useContext(nameSpaceContext)\n  if (ns?.endsWith('svg')) {\n    return {\n      tag: 'title',\n      props,\n      type: 'title',\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      ref: (props as any).ref,\n    } as unknown as JSXNode\n  }\n  return documentMetadataTag('title', props, undefined, false, false)\n}\n\nexport const script: FC<PropsWithChildren<IntrinsicElements['script']>> = (props) => {\n  if (!props || ['src', 'async'].some((k) => !props[k])) {\n    return {\n      tag: 'script',\n      props,\n      type: 'script',\n      ref: props.ref,\n    } as unknown as JSXNode\n  }\n  return documentMetadataTag('script', props, 1, false, true)\n}\n\nexport const style: FC<PropsWithChildren<IntrinsicElements['style']>> = (props) => {\n  if (!props || !['href', 'precedence'].every((k) => k in props)) {\n    return {\n      tag: 'style',\n      props,\n      type: 'style',\n      ref: props.ref,\n    } as unknown as JSXNode\n  }\n  props['data-href'] = props.href\n  delete props.href\n  return documentMetadataTag('style', props, 2, true, true)\n}\n\nexport const link: FC<PropsWithChildren<IntrinsicElements['link']>> = (props) => {\n  if (\n    !props ||\n    ['onLoad', 'onError'].some((k) => k in props) ||\n    (props.rel === 'stylesheet' && (!('precedence' in props) || 'disabled' in props))\n  ) {\n    return {\n      tag: 'link',\n      props,\n      type: 'link',\n      ref: props.ref,\n    } as unknown as JSXNode\n  }\n  return documentMetadataTag('link', props, 1, isStylesheetLinkWithPrecedence(props), true)\n}\n\nexport const meta: FC<PropsWithChildren> = (props) => {\n  return documentMetadataTag('meta', props, undefined, false, false)\n}\n\nconst customEventFormAction = Symbol()\nexport const form: FC<\n  PropsWithChildren<{\n    action?: Function | string\n    method?: 'get' | 'post'\n    ref?: RefObject<HTMLFormElement> | ((e: HTMLFormElement | null) => void | (() => void))\n  }>\n> = (props) => {\n  const { action, ...restProps } = props\n  if (typeof action !== 'function') {\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    ;(restProps as any).action = action\n  }\n\n  const [state, setState] = useState<[FormData | null, boolean]>([null, false]) // [FormData, isDirty]\n  const onSubmit = useCallback<(ev: SubmitEvent | CustomEvent) => void>(\n    async (ev: SubmitEvent | CustomEvent) => {\n      const currentAction = ev.isTrusted\n        ? action\n        : (ev as CustomEvent).detail[customEventFormAction]\n      if (typeof currentAction !== 'function') {\n        return\n      }\n\n      ev.preventDefault()\n      const formData = new FormData(ev.target as HTMLFormElement)\n      setState([formData, true])\n      const actionRes = currentAction(formData)\n      if (actionRes instanceof Promise) {\n        registerAction(actionRes)\n        await actionRes\n      }\n      setState([null, true])\n    },\n    []\n  )\n\n  const ref = composeRef(props.ref, (el: HTMLFormElement) => {\n    el.addEventListener('submit', onSubmit)\n    return () => {\n      el.removeEventListener('submit', onSubmit)\n    }\n  })\n\n  const [data, isDirty] = state\n  state[1] = false\n  return {\n    tag: FormContext as unknown as Function,\n    props: {\n      value: {\n        pending: data !== null,\n        data,\n        method: data ? 'post' : null,\n        action: data ? action : null,\n      },\n      children: {\n        tag: 'form',\n        props: {\n          ...restProps,\n          ref,\n        },\n        type: 'form',\n        ref,\n      },\n    },\n    f: isDirty,\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  } as any\n}\n\nconst formActionableElement = (\n  tag: string,\n  {\n    formAction,\n    ...props\n  }: {\n    formAction?: Function | string\n    ref?: RefObject<HTMLInputElement> | ((e: HTMLInputElement) => void | (() => void))\n  }\n) => {\n  if (typeof formAction === 'function') {\n    const onClick = useCallback<(ev: MouseEvent) => void>((ev: MouseEvent) => {\n      ev.preventDefault()\n      ;(ev.currentTarget! as HTMLInputElement).form!.dispatchEvent(\n        new CustomEvent('submit', { detail: { [customEventFormAction]: formAction } })\n      )\n    }, [])\n\n    props.ref = composeRef(props.ref, (el: HTMLInputElement) => {\n      el.addEventListener('click', onClick)\n      return () => {\n        el.removeEventListener('click', onClick)\n      }\n    })\n  }\n\n  return {\n    tag,\n    props,\n    type: tag,\n    ref: props.ref,\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  } as any\n}\n\nexport const input: FC<PropsWithChildren<IntrinsicElements['input']>> = (props) =>\n  formActionableElement('input', props)\n\nexport const button: FC<PropsWithChildren<IntrinsicElements['button']>> = (props) =>\n  formActionableElement('button', props)\n\nObject.assign(domRenderers, {\n  title,\n  script,\n  style,\n  link,\n  meta,\n  form,\n  input,\n  button,\n})\n"
  },
  {
    "path": "src/jsx/dom/jsx-dev-runtime.ts",
    "content": "/**\n * @module\n * This module provides the `hono/jsx/dom` dev runtime.\n */\n\nimport type { JSXNode, Props } from '../base'\nexport type { JSX } from '../base'\nimport * as intrinsicElementTags from './intrinsic-element/components'\n\nexport const jsxDEV = (tag: string | Function, props: Props, key?: string): JSXNode => {\n  if (typeof tag === 'string' && intrinsicElementTags[tag as keyof typeof intrinsicElementTags]) {\n    tag = intrinsicElementTags[tag as keyof typeof intrinsicElementTags]\n  }\n  return {\n    tag,\n    type: tag,\n    props,\n    key,\n    ref: props.ref,\n  } as JSXNode\n}\n\nexport const Fragment = (props: Record<string, unknown>): JSXNode => jsxDEV('', props, undefined)\n"
  },
  {
    "path": "src/jsx/dom/jsx-runtime.ts",
    "content": "/**\n * @module\n * This module provides the `hono/jsx/dom` runtime.\n */\n\nexport { jsxDEV as jsx, Fragment } from './jsx-dev-runtime'\nexport { jsxDEV as jsxs } from './jsx-dev-runtime'\nexport type { JSX } from './jsx-dev-runtime'\n"
  },
  {
    "path": "src/jsx/dom/render.ts",
    "content": "import type { Child, FC, JSXNode, Props, MemorableFC } from '../base'\nimport { toArray } from '../children'\nimport {\n  DOM_ERROR_HANDLER,\n  DOM_INTERNAL_TAG,\n  DOM_MEMO,\n  DOM_RENDERER,\n  DOM_STASH,\n} from '../constants'\nimport type { Context as JSXContext } from '../context'\nimport { globalContexts as globalJSXContexts, useContext } from '../context'\nimport type { EffectData } from '../hooks'\nimport { STASH_EFFECT } from '../hooks'\nimport { normalizeIntrinsicElementKey, styleObjectForEach } from '../utils'\nimport { createContext } from './context' // import dom-specific versions\n\nconst HONO_PORTAL_ELEMENT = '_hp'\n\nconst eventAliasMap: Record<string, string> = {\n  Change: 'Input',\n  DoubleClick: 'DblClick',\n} as const\n\nconst nameSpaceMap: Record<string, string> = {\n  svg: '2000/svg',\n  math: '1998/Math/MathML',\n} as const\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type HasRenderToDom = FC<any> & { [DOM_RENDERER]: FC<any> }\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type ErrorHandler = (error: any, retry: () => void) => Child | undefined\n\ntype Container = HTMLElement | DocumentFragment\ntype LocalJSXContexts = [JSXContext<unknown>, unknown][] | undefined\ntype SupportedElement = HTMLElement | SVGElement | MathMLElement\nexport type PreserveNodeType =\n  | 1 // preserve only self\n  | 2 // preserve self and children\n\nexport type NodeObject = {\n  pP: Props | undefined // previous props\n  nN: Node | undefined // next node\n  vC: Node[] // virtual dom children\n  pC?: Node[] // previous virtual dom children\n  vR: Node[] // virtual dom children to remove\n  n?: string // namespace\n  f?: boolean // force build\n  s?: boolean // skip build and apply\n  c: Container | undefined // container\n  e: SupportedElement | Text | undefined // rendered element\n  p?: PreserveNodeType // preserve HTMLElement if it will be unmounted\n  a?: boolean // cancel apply() if true\n  o?: NodeObject // original node\n  [DOM_STASH]:\n    | [\n        number, // current hook index\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        any[][], // stash for hooks\n        LocalJSXContexts, // context\n        [Context, Function, NodeObject], // [context, error handler, node] for closest error boundary or suspense\n      ]\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    | [number, any[][]]\n} & JSXNode\ntype NodeString = {\n  t: string // text content\n  d: boolean // is dirty\n  s?: boolean // skip build and apply\n} & {\n  e?: Text\n  // like a NodeObject\n  vC: undefined\n  nN: undefined\n  p?: true\n  // from JSXNode\n  key: undefined\n  tag: undefined\n}\nexport type Node = NodeString | NodeObject\n\nexport type PendingType =\n  | 0 // no pending\n  | 1 // global\n  | 2 // hook\nexport type UpdateHook = (\n  context: Context,\n  node: Node,\n  cb: (context: Context) => void\n) => Promise<void>\nexport type Context =\n  | [\n      PendingType, // PendingType\n      boolean, // got an error\n      UpdateHook, // update hook\n      boolean, // is in view transition\n      boolean, // is in top level render\n      [Context, Function, NodeObject][], //  [context, error handler, node] stack for this context\n    ]\n  | [PendingType, boolean, UpdateHook, boolean]\n  | [PendingType, boolean, UpdateHook]\n  | [PendingType, boolean]\n  | [PendingType]\n  | []\n\nexport const buildDataStack: [Context, Node][] = []\n\nconst refCleanupMap: WeakMap<Element, () => void> = new WeakMap()\n\nlet nameSpaceContext: JSXContext<string> | undefined = undefined\nexport const getNameSpaceContext = () => nameSpaceContext\n\nconst isNodeString = (node: Node): node is NodeString => 't' in (node as NodeString)\n\nconst eventCache: Record<string, [string, boolean]> = {\n  // pre-define events that are used very frequently\n  onClick: ['click', false],\n}\nconst getEventSpec = (key: string): [string, boolean] | undefined => {\n  if (!key.startsWith('on')) {\n    return undefined\n  }\n  if (eventCache[key]) {\n    return eventCache[key]\n  }\n\n  const match = key.match(/^on([A-Z][a-zA-Z]+?(?:PointerCapture)?)(Capture)?$/)\n  if (match) {\n    const [, eventName, capture] = match\n    return (eventCache[key] = [(eventAliasMap[eventName] || eventName).toLowerCase(), !!capture])\n  }\n  return undefined\n}\n\nconst toAttributeName = (element: SupportedElement, key: string): string =>\n  nameSpaceContext &&\n  element instanceof SVGElement &&\n  /[A-Z]/.test(key) &&\n  (key in element.style || // Presentation attributes are findable in style object. \"clip-path\", \"font-size\", \"stroke-width\", etc.\n    key.match(/^(?:o|pai|str|u|ve)/)) // Other un-deprecated kebab-case attributes. \"overline-position\", \"paint-order\", \"strikethrough-position\", etc.\n    ? key.replace(/([A-Z])/g, '-$1').toLowerCase()\n    : key\n\nconst applyProps = (\n  container: SupportedElement,\n  attributes: Props,\n  oldAttributes?: Props\n): void => {\n  attributes ||= {}\n  for (let key in attributes) {\n    const value = attributes[key]\n    if (key !== 'children' && (!oldAttributes || oldAttributes[key] !== value)) {\n      key = normalizeIntrinsicElementKey(key)\n      const eventSpec = getEventSpec(key)\n      if (eventSpec) {\n        if (oldAttributes?.[key] !== value) {\n          if (oldAttributes) {\n            container.removeEventListener(eventSpec[0], oldAttributes[key], eventSpec[1])\n          }\n          if (value != null) {\n            if (typeof value !== 'function') {\n              throw new Error(`Event handler for \"${key}\" is not a function`)\n            }\n            container.addEventListener(eventSpec[0], value, eventSpec[1])\n          }\n        }\n      } else if (key === 'dangerouslySetInnerHTML' && value) {\n        container.innerHTML = value.__html\n      } else if (key === 'ref') {\n        let cleanup\n        if (typeof value === 'function') {\n          cleanup = value(container) || (() => value(null))\n        } else if (value && 'current' in value) {\n          value.current = container\n          cleanup = () => (value.current = null)\n        }\n        refCleanupMap.set(container, cleanup)\n      } else if (key === 'style') {\n        const style = container.style\n        if (typeof value === 'string') {\n          style.cssText = value\n        } else {\n          style.cssText = ''\n          if (value != null) {\n            styleObjectForEach(value, style.setProperty.bind(style))\n          }\n        }\n      } else {\n        if (key === 'value') {\n          const nodeName = container.nodeName\n          if (nodeName === 'INPUT' || nodeName === 'TEXTAREA' || nodeName === 'SELECT') {\n            ;(container as unknown as HTMLInputElement).value =\n              value === null || value === undefined || value === false ? null : value\n\n            if (nodeName === 'TEXTAREA') {\n              container.textContent = value\n              continue\n            } else if (nodeName === 'SELECT') {\n              if ((container as unknown as HTMLSelectElement).selectedIndex === -1) {\n                ;(container as unknown as HTMLSelectElement).selectedIndex = 0\n              }\n              continue\n            }\n          }\n        } else if (\n          (key === 'checked' && container.nodeName === 'INPUT') ||\n          (key === 'selected' && container.nodeName === 'OPTION')\n        ) {\n          // eslint-disable-next-line @typescript-eslint/no-explicit-any\n          ;(container as any)[key] = value\n        }\n\n        const k = toAttributeName(container, key)\n\n        if (value === null || value === undefined || value === false) {\n          container.removeAttribute(k)\n        } else if (value === true) {\n          container.setAttribute(k, '')\n        } else if (typeof value === 'string' || typeof value === 'number') {\n          container.setAttribute(k, value as string)\n        } else {\n          container.setAttribute(k, value.toString())\n        }\n      }\n    }\n  }\n  if (oldAttributes) {\n    for (let key in oldAttributes) {\n      const value = oldAttributes[key]\n      if (key !== 'children' && !(key in attributes)) {\n        key = normalizeIntrinsicElementKey(key)\n        const eventSpec = getEventSpec(key)\n        if (eventSpec) {\n          container.removeEventListener(eventSpec[0], value, eventSpec[1])\n        } else if (key === 'ref') {\n          refCleanupMap.get(container)?.()\n        } else {\n          container.removeAttribute(toAttributeName(container, key))\n        }\n      }\n    }\n  }\n}\n\nconst invokeTag = (context: Context, node: NodeObject): Child[] => {\n  node[DOM_STASH][0] = 0\n  buildDataStack.push([context, node])\n  const func = (node.tag as HasRenderToDom)[DOM_RENDERER] || node.tag\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  const props = (func as any).defaultProps\n    ? {\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        ...(func as any).defaultProps,\n        ...node.props,\n      }\n    : node.props\n  try {\n    return [func.call(null, props)]\n  } finally {\n    buildDataStack.pop()\n  }\n}\n\nconst getNextChildren = (\n  node: NodeObject,\n  container: Container,\n  nextChildren: Node[],\n  childrenToRemove: Node[],\n  callbacks: EffectData[]\n): void => {\n  if (node.vR?.length) {\n    childrenToRemove.push(...node.vR)\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    delete (node as any).vR\n  }\n  if (typeof node.tag === 'function') {\n    node[DOM_STASH][1][STASH_EFFECT]?.forEach((data: EffectData) => callbacks.push(data))\n  }\n  node.vC.forEach((child) => {\n    if (isNodeString(child)) {\n      nextChildren.push(child)\n    } else {\n      if (typeof child.tag === 'function' || child.tag === '') {\n        child.c = container\n        const currentNextChildrenIndex = nextChildren.length\n        getNextChildren(child, container, nextChildren, childrenToRemove, callbacks)\n        if (child.s) {\n          for (let i = currentNextChildrenIndex; i < nextChildren.length; i++) {\n            nextChildren[i].s = true\n          }\n          child.s = false\n        }\n      } else {\n        nextChildren.push(child)\n        if (child.vR?.length) {\n          childrenToRemove.push(...child.vR)\n          // eslint-disable-next-line @typescript-eslint/no-explicit-any\n          delete (child as any).vR\n        }\n      }\n    }\n  })\n}\n\nconst findInsertBefore = (node: Node | undefined): SupportedElement | Text | undefined => {\n  while (node && (node.tag === HONO_PORTAL_ELEMENT || !node.e)) {\n    node = node.tag === HONO_PORTAL_ELEMENT || !node.vC?.[0] ? node.nN : node.vC[0]\n  }\n  return node?.e\n}\n\nconst removeNode = (node: Node): void => {\n  if (!isNodeString(node)) {\n    node[DOM_STASH]?.[1][STASH_EFFECT]?.forEach((data: EffectData) => data[2]?.())\n\n    refCleanupMap.get(node.e as Element)?.()\n    if (node.p === 2) {\n      node.vC?.forEach((n) => (n.p = 2))\n    }\n    node.vC?.forEach(removeNode)\n  }\n  if (!node.p) {\n    node.e?.remove()\n    delete node.e\n  }\n  if (typeof node.tag === 'function') {\n    updateMap.delete(node)\n    fallbackUpdateFnArrayMap.delete(node)\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    delete (node as any)[DOM_STASH][3] // delete explicitly for avoid circular reference\n    node.a = true\n  }\n}\n\nconst apply = (node: NodeObject, container: Container, isNew: boolean): void => {\n  node.c = container\n  applyNodeObject(node, container, isNew)\n}\n\nconst findChildNodeIndex = (\n  childNodes: NodeListOf<ChildNode>,\n  child: ChildNode | undefined\n): number | undefined => {\n  if (!child) {\n    return\n  }\n\n  for (let i = 0, len = childNodes.length; i < len; i++) {\n    if (childNodes[i] === child) {\n      return i\n    }\n  }\n\n  return\n}\n\nconst cancelBuild: symbol = Symbol()\nconst applyNodeObject = (node: NodeObject, container: Container, isNew: boolean): void => {\n  const next: Node[] = []\n  const remove: Node[] = []\n  const callbacks: EffectData[] = []\n  getNextChildren(node, container, next, remove, callbacks)\n  remove.forEach(removeNode)\n\n  const childNodes = (isNew ? undefined : container.childNodes) as NodeListOf<ChildNode>\n  let offset: number\n  let insertBeforeNode: ChildNode | null = null\n  if (isNew) {\n    offset = -1\n  } else if (!childNodes.length) {\n    offset = 0\n  } else {\n    const offsetByNextNode = findChildNodeIndex(childNodes, findInsertBefore(node.nN))\n    if (offsetByNextNode !== undefined) {\n      insertBeforeNode = childNodes[offsetByNextNode]\n      offset = offsetByNextNode\n    } else {\n      offset =\n        findChildNodeIndex(childNodes, next.find((n) => n.tag !== HONO_PORTAL_ELEMENT && n.e)?.e) ??\n        -1\n    }\n\n    if (offset === -1) {\n      isNew = true\n    }\n  }\n\n  for (let i = 0, len = next.length; i < len; i++, offset++) {\n    const child = next[i]\n\n    let el: SupportedElement | Text\n    if (child.s && child.e) {\n      el = child.e\n      child.s = false\n    } else {\n      const isNewLocal = isNew || !child.e\n      if (isNodeString(child)) {\n        if (child.e && child.d) {\n          child.e.textContent = child.t\n        }\n        child.d = false\n        el = child.e ||= document.createTextNode(child.t)\n      } else {\n        el = child.e ||= child.n\n          ? (document.createElementNS(child.n, child.tag as string) as SVGElement | MathMLElement)\n          : document.createElement(child.tag as string)\n        applyProps(el as HTMLElement, child.props, child.pP)\n        applyNodeObject(child, el as HTMLElement, isNewLocal)\n      }\n    }\n    if (child.tag === HONO_PORTAL_ELEMENT) {\n      offset--\n    } else if (isNew) {\n      if (!el.parentNode) {\n        container.appendChild(el)\n      }\n    } else if (childNodes[offset] !== el && childNodes[offset - 1] !== el) {\n      if (childNodes[offset + 1] === el) {\n        // Move extra elements to the back of the container. This is to be done efficiently when elements are swapped.\n        container.appendChild(childNodes[offset])\n      } else {\n        container.insertBefore(el, insertBeforeNode || childNodes[offset] || null)\n      }\n    }\n  }\n  if (node.pP) {\n    node.pP = undefined\n  }\n  if (callbacks.length) {\n    const useLayoutEffectCbs: Array<() => void> = []\n    const useEffectCbs: Array<() => void> = []\n    callbacks.forEach(([, useLayoutEffectCb, , useEffectCb, useInsertionEffectCb]) => {\n      if (useLayoutEffectCb) {\n        useLayoutEffectCbs.push(useLayoutEffectCb)\n      }\n      if (useEffectCb) {\n        useEffectCbs.push(useEffectCb)\n      }\n      useInsertionEffectCb?.() // invoke useInsertionEffect callbacks\n    })\n    useLayoutEffectCbs.forEach((cb) => cb()) // invoke useLayoutEffect callbacks\n    if (useEffectCbs.length) {\n      requestAnimationFrame(() => {\n        useEffectCbs.forEach((cb) => cb()) // invoke useEffect callbacks\n      })\n    }\n  }\n}\n\nconst isSameContext = (\n  oldContexts: LocalJSXContexts,\n  newContexts: NonNullable<LocalJSXContexts>\n): boolean =>\n  !!(\n    oldContexts &&\n    oldContexts.length === newContexts.length &&\n    oldContexts.every((ctx, i) => ctx[1] === newContexts[i][1])\n  )\n\nconst fallbackUpdateFnArrayMap: WeakMap<\n  NodeObject,\n  Array<() => Promise<NodeObject | undefined>>\n> = new WeakMap<NodeObject, Array<() => Promise<NodeObject | undefined>>>()\nexport const build = (context: Context, node: NodeObject, children?: Child[]): void => {\n  const buildWithPreviousChildren = !children && node.pC\n  if (children) {\n    node.pC ||= node.vC\n  }\n\n  let foundErrorHandler: ErrorHandler | undefined\n  try {\n    children ||=\n      typeof node.tag == 'function' ? invokeTag(context, node) : toArray(node.props.children)\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    if ((children[0] as JSXNode)?.tag === '' && (children[0] as any)[DOM_ERROR_HANDLER]) {\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      foundErrorHandler = (children[0] as any)[DOM_ERROR_HANDLER] as ErrorHandler\n      context[5]!.push([context, foundErrorHandler, node])\n    }\n    const oldVChildren: Node[] | undefined = buildWithPreviousChildren\n      ? [...(node.pC as Node[])]\n      : node.vC\n        ? [...node.vC]\n        : undefined\n    const vChildren: Node[] = []\n    let prevNode: Node | undefined\n    for (let i = 0; i < children.length; i++) {\n      if (Array.isArray(children[i])) {\n        children.splice(i, 1, ...((children[i] as unknown[]).flat(Infinity) as Child[]))\n        i--\n        continue\n      }\n      let child = buildNode(children[i])\n      if (child) {\n        if (\n          typeof child.tag === 'function' &&\n          // eslint-disable-next-line @typescript-eslint/no-explicit-any\n          !(child.tag as any)[DOM_INTERNAL_TAG]\n        ) {\n          if (globalJSXContexts.length > 0) {\n            child[DOM_STASH][2] = globalJSXContexts.map((c) => [c, c.values.at(-1)])\n          }\n          if (context[5]?.length) {\n            child[DOM_STASH][3] = context[5].at(-1) as [Context, ErrorHandler, NodeObject]\n          }\n        }\n\n        let oldChild: NodeObject | undefined\n        if (oldVChildren && oldVChildren.length) {\n          const i = oldVChildren.findIndex(\n            isNodeString(child)\n              ? (c) => isNodeString(c)\n              : child.key !== undefined\n                ? (c) => c.key === (child as Node).key && c.tag === (child as Node).tag\n                : (c) => c.tag === (child as Node).tag\n          )\n\n          if (i !== -1) {\n            oldChild = oldVChildren[i] as NodeObject\n            oldVChildren.splice(i, 1)\n          }\n        }\n\n        if (oldChild) {\n          if (isNodeString(child)) {\n            if ((oldChild as unknown as NodeString).t !== child.t) {\n              ;(oldChild as unknown as NodeString).t = child.t // update text content\n              ;(oldChild as unknown as NodeString).d = true\n            }\n            child = oldChild\n          } else {\n            const pP = (oldChild.pP = oldChild.props)\n            oldChild.props = child.props\n            oldChild.f ||= child.f || node.f\n            if (typeof child.tag === 'function') {\n              const oldContexts = oldChild[DOM_STASH][2]\n              oldChild[DOM_STASH][2] = child[DOM_STASH][2] || []\n              oldChild[DOM_STASH][3] = child[DOM_STASH][3]\n\n              if (\n                !oldChild.f &&\n                ((oldChild.o || oldChild) === child.o || // The code generated by the react compiler is memoized under this condition.\n                  (oldChild.tag as MemorableFC<unknown>)[DOM_MEMO]?.(pP, oldChild.props)) && // The `memo` function is memoized under this condition.\n                isSameContext(oldContexts, oldChild[DOM_STASH][2])\n              ) {\n                oldChild.s = true\n              }\n            }\n            child = oldChild\n          }\n        } else if (!isNodeString(child) && nameSpaceContext) {\n          const ns = useContext(nameSpaceContext)\n          if (ns) {\n            child.n = ns\n          }\n        }\n\n        if (!isNodeString(child) && !child.s) {\n          build(context, child)\n          delete child.f\n        }\n        vChildren.push(child)\n\n        if (prevNode && !prevNode.s && !child.s) {\n          for (let p = prevNode; p && !isNodeString(p); p = p.vC?.at(-1) as NodeObject) {\n            p.nN = child\n          }\n        }\n        prevNode = child\n      }\n    }\n    node.vR = buildWithPreviousChildren ? [...node.vC, ...(oldVChildren || [])] : oldVChildren || []\n    node.vC = vChildren\n    if (buildWithPreviousChildren) {\n      delete node.pC\n    }\n  } catch (e) {\n    node.f = true\n    if (e === cancelBuild) {\n      if (foundErrorHandler) {\n        return\n      } else {\n        throw e\n      }\n    }\n\n    const [errorHandlerContext, errorHandler, errorHandlerNode] =\n      node[DOM_STASH]?.[3] || ([] as unknown as [undefined, undefined])\n\n    if (errorHandler) {\n      const fallbackUpdateFn = () =>\n        update([0, false, context[2] as UpdateHook], errorHandlerNode as NodeObject)\n      const fallbackUpdateFnArray =\n        fallbackUpdateFnArrayMap.get(errorHandlerNode as NodeObject) || []\n      fallbackUpdateFnArray.push(fallbackUpdateFn)\n      fallbackUpdateFnArrayMap.set(errorHandlerNode as NodeObject, fallbackUpdateFnArray)\n      const fallback = errorHandler(e, () => {\n        const fnArray = fallbackUpdateFnArrayMap.get(errorHandlerNode as NodeObject)\n        if (fnArray) {\n          const i = fnArray.indexOf(fallbackUpdateFn)\n          if (i !== -1) {\n            fnArray.splice(i, 1)\n            return fallbackUpdateFn()\n          }\n        }\n      })\n      if (fallback) {\n        if (context[0] === 1) {\n          // low priority render\n          context[1] = true\n        } else {\n          build(context, errorHandlerNode, [fallback])\n          if (\n            (errorHandler.length === 1 || context !== errorHandlerContext) &&\n            errorHandlerNode.c\n          ) {\n            // render error boundary immediately\n            apply(errorHandlerNode, errorHandlerNode.c as Container, false)\n            return\n          }\n        }\n        throw cancelBuild\n      }\n    }\n\n    throw e\n  } finally {\n    if (foundErrorHandler) {\n      context[5]!.pop()\n    }\n  }\n}\n\nexport const buildNode = (node: Child): Node | undefined => {\n  if (node === undefined || node === null || typeof node === 'boolean') {\n    return undefined\n  } else if (typeof node === 'string' || typeof node === 'number') {\n    return { t: node.toString(), d: true } as NodeString\n  } else {\n    if ('vR' in node) {\n      node = {\n        tag: (node as NodeObject).tag,\n        props: (node as NodeObject).props,\n        key: (node as NodeObject).key,\n        f: (node as NodeObject).f,\n        type: (node as NodeObject).tag,\n        ref: (node as NodeObject).props.ref,\n        o: (node as NodeObject).o || node,\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      } as any\n    }\n    if (typeof (node as JSXNode).tag === 'function') {\n      ;(node as NodeObject)[DOM_STASH] = [0, []]\n    } else {\n      const ns = nameSpaceMap[(node as JSXNode).tag as string]\n      if (ns) {\n        nameSpaceContext ||= createContext('')\n        ;(node as JSXNode).props.children = [\n          {\n            tag: nameSpaceContext,\n            props: {\n              value: ((node as NodeObject).n = `http://www.w3.org/${ns}`),\n              children: (node as JSXNode).props.children,\n            },\n          },\n        ]\n      }\n    }\n    return node as NodeObject\n  }\n}\n\nconst replaceContainer = (node: NodeObject, from: DocumentFragment, to: Container): void => {\n  if (node.c === from) {\n    node.c = to\n    node.vC.forEach((child) => replaceContainer(child as NodeObject, from, to))\n  }\n}\n\nconst updateSync = (context: Context, node: NodeObject): void => {\n  node[DOM_STASH][2]?.forEach(([c, v]) => {\n    c.values.push(v)\n  })\n  try {\n    build(context, node, undefined)\n  } catch {\n    return\n  }\n  if (node.a) {\n    delete node.a\n    return\n  }\n  node[DOM_STASH][2]?.forEach(([c]) => {\n    c.values.pop()\n  })\n  if (context[0] !== 1 || !context[1]) {\n    apply(node, node.c as Container, false)\n  }\n}\n\ntype UpdateMapResolve = (node: NodeObject | undefined) => void\nconst updateMap: WeakMap<NodeObject, [UpdateMapResolve, Function]> = new WeakMap<\n  NodeObject,\n  [UpdateMapResolve, Function]\n>()\nconst currentUpdateSets: Set<NodeObject>[] = []\nexport const update = async (\n  context: Context,\n  node: NodeObject\n): Promise<NodeObject | undefined> => {\n  context[5] ||= []\n\n  const existing = updateMap.get(node)\n  if (existing) {\n    // execute only the last update() call, so the previous update will be canceled.\n    existing[0](undefined)\n  }\n\n  let resolve: UpdateMapResolve | undefined\n  const promise = new Promise<NodeObject | undefined>((r) => (resolve = r))\n  updateMap.set(node, [\n    resolve as UpdateMapResolve,\n    () => {\n      if (context[2]) {\n        context[2](context, node, (context) => {\n          updateSync(context, node)\n        }).then(() => (resolve as UpdateMapResolve)(node))\n      } else {\n        updateSync(context, node)\n        ;(resolve as UpdateMapResolve)(node)\n      }\n    },\n  ])\n\n  if (currentUpdateSets.length) {\n    ;(currentUpdateSets.at(-1) as Set<NodeObject>).add(node)\n  } else {\n    await Promise.resolve()\n\n    const latest = updateMap.get(node)\n    if (latest) {\n      updateMap.delete(node)\n      latest[1]()\n    }\n  }\n\n  return promise\n}\n\nexport const renderNode = (node: NodeObject, container: Container): void => {\n  const context: Context = []\n  ;(context as Context)[5] = [] // error handler stack\n  ;(context as Context)[4] = true // start top level render\n  build(context, node, undefined)\n  ;(context as Context)[4] = false // finish top level render\n\n  const fragment = document.createDocumentFragment()\n  apply(node, fragment, true)\n  replaceContainer(node, fragment, container)\n  container.replaceChildren(fragment)\n}\n\nexport const render = (jsxNode: Child, container: Container): void => {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  renderNode(buildNode({ tag: '', props: { children: jsxNode } } as any) as NodeObject, container)\n}\n\nexport const flushSync = (callback: () => void): void => {\n  const set = new Set<NodeObject>()\n  currentUpdateSets.push(set)\n  callback()\n  set.forEach((node) => {\n    const latest = updateMap.get(node)\n    if (latest) {\n      updateMap.delete(node)\n      latest[1]()\n    }\n  })\n  currentUpdateSets.pop()\n}\n\nexport const createPortal = (children: Child, container: HTMLElement, key?: string): Child =>\n  ({\n    tag: HONO_PORTAL_ELEMENT,\n    props: {\n      children,\n    },\n    key,\n    e: container,\n    p: 1,\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  }) as any\n"
  },
  {
    "path": "src/jsx/dom/server.test.tsx",
    "content": "/** @jsxImportSource ../ */\nimport { renderToReadableStream, renderToString } from './server'\n\ndescribe('renderToString', () => {\n  it('Should be able to render HTML element', () => {\n    expect(renderToString(<h1>Hello</h1>)).toBe('<h1>Hello</h1>')\n  })\n\n  it('Should be able to render null', () => {\n    expect(renderToString(null)).toBe('')\n  })\n\n  it('Should be able to render undefined', () => {\n    expect(renderToString(undefined)).toBe('')\n  })\n\n  it('Should be able to render number', () => {\n    expect(renderToString(1)).toBe('1')\n  })\n\n  it('Should be able to render string', () => {\n    expect(renderToString('Hono')).toBe('Hono')\n  })\n\n  it('Should omit options', () => {\n    expect(renderToString('Hono', { identifierPrefix: 'test' })).toBe('Hono')\n  })\n\n  it('Should raise error for async component', async () => {\n    const AsyncComponent = async () => <h1>Hello from async component</h1>\n    expect(() => renderToString(<AsyncComponent />)).toThrowError()\n  })\n})\n\ndescribe('renderToReadableStream', () => {\n  const textDecoder = new TextDecoder()\n  const getStringFromStream = async (stream: ReadableStream<Uint8Array>): Promise<string> => {\n    const reader = stream.getReader()\n    let str = ''\n    for (;;) {\n      const { done, value } = await reader.read()\n      if (done) {\n        break\n      }\n      str += textDecoder.decode(value)\n    }\n    return str\n  }\n\n  it('Should be able to render HTML element', async () => {\n    const stream = await renderToReadableStream(<h1>Hello</h1>)\n    const reader = stream.getReader()\n    let { done, value } = await reader.read()\n    expect(done).toBe(false)\n    expect(textDecoder.decode(value)).toBe('<h1>Hello</h1>')\n    done = (await reader.read()).done\n    expect(done).toBe(true)\n  })\n\n  it('Should be able to render null', async () => {\n    expect(await getStringFromStream(await renderToReadableStream(null))).toBe('')\n  })\n\n  it('Should be able to render undefined', async () => {\n    expect(await getStringFromStream(await renderToReadableStream(undefined))).toBe('')\n  })\n\n  it('Should be able to render number', async () => {\n    expect(await getStringFromStream(await renderToReadableStream(1))).toBe('1')\n  })\n\n  it('Should be able to render string', async () => {\n    expect(await getStringFromStream(await renderToReadableStream('Hono'))).toBe('Hono')\n  })\n\n  it('Should be called `onError` if there is an error', async () => {\n    const ErrorComponent = async () => {\n      throw new Error('Server error')\n    }\n\n    const onError = vi.fn()\n    expect(\n      await getStringFromStream(await renderToReadableStream(<ErrorComponent />, { onError }))\n    ).toBe('')\n    expect(onError).toBeCalledWith(new Error('Server error'))\n  })\n\n  it('Should not be called `onError` if there is no error', async () => {\n    const onError = vi.fn(() => 'error')\n    expect(await getStringFromStream(await renderToReadableStream('Hono', { onError }))).toBe(\n      'Hono'\n    )\n    expect(onError).toBeCalledTimes(0)\n  })\n\n  it('Should omit options, except onError', async () => {\n    expect(\n      await getStringFromStream(await renderToReadableStream('Hono', { identifierPrefix: 'test' }))\n    ).toBe('Hono')\n  })\n\n  it('Should be able to render async component', async () => {\n    const ChildAsyncComponent = async () => {\n      await new Promise((resolve) => setTimeout(resolve, 10))\n      return <span>child async component</span>\n    }\n\n    const AsyncComponent = async () => {\n      await new Promise((resolve) => setTimeout(resolve, 10))\n      return (\n        <h1>\n          Hello from async component\n          <ChildAsyncComponent />\n        </h1>\n      )\n    }\n\n    const stream = await renderToReadableStream(<AsyncComponent />)\n    const reader = stream.getReader()\n    let { done, value } = await reader.read()\n    expect(done).toBe(false)\n    expect(textDecoder.decode(value)).toBe(\n      '<h1>Hello from async component<span>child async component</span></h1>'\n    )\n    done = (await reader.read()).done\n    expect(done).toBe(true)\n  })\n})\n"
  },
  {
    "path": "src/jsx/dom/server.ts",
    "content": "/**\n * @module\n * This module provides APIs for `hono/jsx/server`, which is compatible with `react-dom/server`.\n */\n\nimport type { HtmlEscapedString } from '../../utils/html'\nimport type { Child } from '../base'\nimport { renderToReadableStream as renderToReadableStreamHono } from '../streaming'\nimport version from './'\n\nexport interface RenderToStringOptions {\n  identifierPrefix?: string\n}\n\n/**\n * Render JSX element to string.\n * @param element JSX element to render.\n * @param options Options for rendering.\n * @returns Rendered string.\n */\nconst renderToString = (element: Child, options: RenderToStringOptions = {}): string => {\n  if (Object.keys(options).length > 0) {\n    console.warn('options are not supported yet')\n  }\n  const res = element?.toString() ?? ''\n  if (typeof res !== 'string') {\n    throw new Error('Async component is not supported in renderToString')\n  }\n  return res\n}\n\nexport interface RenderToReadableStreamOptions {\n  identifierPrefix?: string\n  namespaceURI?: string\n  nonce?: string\n  bootstrapScriptContent?: string\n  bootstrapScripts?: string[]\n  bootstrapModules?: string[]\n  progressiveChunkSize?: number\n  signal?: AbortSignal\n  onError?: (error: unknown) => string | void\n}\n\n/**\n * Render JSX element to readable stream.\n * @param element JSX element to render.\n * @param options Options for rendering.\n * @returns Rendered readable stream.\n */\nconst renderToReadableStream = async (\n  element: Child,\n  options: RenderToReadableStreamOptions = {}\n): Promise<ReadableStream<Uint8Array>> => {\n  if (Object.keys(options).some((key) => key !== 'onError')) {\n    console.warn('options are not supported yet, except onError')\n  }\n\n  if (!element || typeof element !== 'object') {\n    element = element?.toString() ?? ''\n  }\n\n  return renderToReadableStreamHono(element as HtmlEscapedString, options.onError)\n}\n\nexport { renderToString, renderToReadableStream, version }\nexport default {\n  renderToString,\n  renderToReadableStream,\n  version,\n}\n"
  },
  {
    "path": "src/jsx/dom/utils.ts",
    "content": "import { DOM_INTERNAL_TAG } from '../constants'\n\nexport const setInternalTagFlag = (fn: Function): Function => {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  ;(fn as any)[DOM_INTERNAL_TAG] = true\n  return fn\n}\n"
  },
  {
    "path": "src/jsx/hooks/dom.test.tsx",
    "content": "/** @jsxImportSource ../ */\nimport { JSDOM } from 'jsdom'\n// run tests by old style jsx default\n// hono/jsx/jsx-runtime and hono/jsx/dom/jsx-runtime are tested in their respective settings\nimport { ErrorBoundary, Suspense, render } from '../dom'\nimport {\n  createRef,\n  forwardRef,\n  startTransition,\n  startViewTransition,\n  use,\n  useDebugValue,\n  useDeferredValue,\n  useId,\n  useImperativeHandle,\n  useReducer,\n  useState,\n  useSyncExternalStore,\n  useTransition,\n  useViewTransition,\n} from '.'\n\ndescribe('Hooks', () => {\n  beforeAll(() => {\n    global.requestAnimationFrame = (cb) => setTimeout(cb)\n  })\n\n  let dom: JSDOM\n  let root: HTMLElement\n  beforeEach(() => {\n    dom = new JSDOM('<html><body><div id=\"root\"></div></body></html>', {\n      runScripts: 'dangerously',\n    })\n    global.document = dom.window.document\n    global.HTMLElement = dom.window.HTMLElement\n    global.SVGElement = dom.window.SVGElement\n    global.Text = dom.window.Text\n    root = document.getElementById('root') as HTMLElement\n  })\n\n  describe('useReducer()', () => {\n    it('simple', async () => {\n      const reducer = (state: number, action: number) => state + action\n      const functions: Function[] = []\n      const App = () => {\n        const [state, dispatch] = useReducer(reducer, 0)\n        functions.push(dispatch)\n        return (\n          <div>\n            <button onClick={() => dispatch(1)}>{state}</button>\n          </div>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button>0</button></div>')\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><button>1</button></div>')\n      expect(functions[0]).toBe(functions[1])\n    })\n  })\n\n  describe('startTransition()', () => {\n    it('no error', async () => {\n      const App = () => {\n        const [count, setCount] = useState(0)\n        return (\n          <Suspense fallback={<div>Loading...</div>}>\n            <div>\n              <button\n                onClick={() => {\n                  startTransition(() => {\n                    setCount((c) => c + 1)\n                  })\n                }}\n              >\n                {count}\n              </button>\n            </div>\n          </Suspense>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button>0</button></div>')\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><button>1</button></div>')\n    })\n\n    it('got an error', async () => {\n      let resolve: () => void\n      const promise = new Promise<void>((r) => (resolve = r))\n\n      const Counter = ({ count }: { count: number }) => {\n        use(promise)\n        return <div>{count}</div>\n      }\n\n      const App = () => {\n        const [count, setCount] = useState(0)\n        return (\n          <Suspense fallback={<div>Loading...</div>}>\n            <div>\n              <button\n                onClick={() => {\n                  startTransition(() => {\n                    setCount((c) => c + 1)\n                  })\n                }}\n              >\n                {count ? <Counter count={count} /> : count}\n              </button>\n            </div>\n          </Suspense>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button>0</button></div>')\n      root.querySelector('button')?.click()\n      expect(root.innerHTML).toBe('<div><button>0</button></div>')\n      resolve!()\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<div><button><div>1</div></button></div>')\n    })\n  })\n\n  describe('useTransition()', () => {\n    it('pending', async () => {\n      let called = 0\n      const App = () => {\n        const [count, setCount] = useState(0)\n        const [isPending, startTransition] = useTransition()\n        called++\n\n        return (\n          <div>\n            <button\n              onClick={() => {\n                startTransition(() => {\n                  setCount((c) => c + 1)\n                })\n              }}\n            >\n              {isPending ? 'Pending...' : count}\n            </button>\n          </div>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button>0</button></div>')\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><button>Pending...</button></div>')\n      expect(called).toBe(2)\n      await new Promise((r) => setTimeout(r))\n      await new Promise((r) => setTimeout(r))\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<div><button>1</button></div>')\n      expect(called).toBe(3)\n    })\n\n    it('pending', async () => {\n      let resolve: (() => void) | undefined\n      const promise = new Promise<void>((r) => (resolve = r))\n      let called = 0\n      const App = () => {\n        const [isPending, startTransition] = useTransition()\n        called++\n\n        return (\n          <div>\n            <button\n              onClick={() => {\n                startTransition(async () => await promise)\n              }}\n            >\n              {isPending ? 'Pending...' : 'Click me'}\n            </button>\n          </div>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button>Click me</button></div>')\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><button>Pending...</button></div>')\n      expect(called).toBe(2)\n      resolve!()\n      await new Promise((r) => setTimeout(r))\n      await new Promise((r) => setTimeout(r))\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<div><button>Click me</button></div>')\n      expect(called).toBe(3)\n    })\n\n    it('pending - error', async () => {\n      let reject: (() => void) | undefined\n      const promise = new Promise<void>((_, r) => (reject = r))\n      let called = 0\n      const Component = () => {\n        const [isPending, startTransition] = useTransition()\n        called++\n\n        return (\n          <div>\n            <button\n              onClick={() => {\n                startTransition(async () => await promise)\n              }}\n            >\n              {isPending ? 'Pending...' : 'Click me'}\n            </button>\n          </div>\n        )\n      }\n      const App = () => (\n        <ErrorBoundary fallback={<div>Error</div>}>\n          <Component />\n        </ErrorBoundary>\n      )\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button>Click me</button></div>')\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><button>Pending...</button></div>')\n      expect(called).toBe(2)\n      reject!()\n      await new Promise((r) => setTimeout(r))\n      await new Promise((r) => setTimeout(r))\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<div>Error</div>')\n      expect(called).toBe(2)\n    })\n\n    it('multiple setState at once', async () => {\n      let called = 0\n      const App = () => {\n        const [count1, setCount1] = useState(0)\n        const [count2, setCount2] = useState(0)\n        const [isPending, startTransition] = useTransition()\n        called++\n\n        return (\n          <div>\n            <button\n              onClick={() => {\n                startTransition(() => {\n                  setCount1((c) => c + 1)\n                  setCount2((c) => c + 2)\n                })\n              }}\n            >\n              {isPending ? 'Pending...' : count1 + count2}\n            </button>\n          </div>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button>0</button></div>')\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><button>Pending...</button></div>')\n      expect(called).toBe(2)\n      await new Promise((r) => setTimeout(r))\n      await new Promise((r) => setTimeout(r))\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<div><button>3</button></div>')\n      expect(called).toBe(3)\n    })\n\n    it('multiple startTransaction at once', async () => {\n      let called = 0\n      const App = () => {\n        const [count1, setCount1] = useState(0)\n        const [count2, setCount2] = useState(0)\n        const [isPending, startTransition] = useTransition()\n        called++\n\n        return (\n          <div>\n            <button\n              onClick={() => {\n                startTransition(() => {\n                  setCount1((c) => c + 1)\n                })\n                startTransition(() => {\n                  setCount2((c) => c + 2)\n                })\n              }}\n            >\n              {isPending ? 'Pending...' : count1 + count2}\n            </button>\n          </div>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button>0</button></div>')\n      expect(called).toBe(1)\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><button>Pending...</button></div>')\n      expect(called).toBe(2)\n      await new Promise((r) => setTimeout(r))\n      await new Promise((r) => setTimeout(r))\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<div><button>3</button></div>')\n      expect(called).toBe(3) // + isPending=true + isPending=false\n    })\n  })\n\n  describe('useDeferredValue()', () => {\n    it('deferred', async () => {\n      const promiseMap = {} as Record<number, Promise<number>>\n      const getPromise = (count: number) => {\n        return (promiseMap[count] ||= new Promise((r) => setTimeout(() => r(count + 1))))\n      }\n      const ShowCount = ({ count }: { count: number }) => {\n        if (count === 0) {\n          return <div>0</div>\n        }\n\n        const c = use(getPromise(count))\n        return <div>{c}</div>\n      }\n\n      const App = () => {\n        const [count, setCount] = useState(0)\n        const c = useDeferredValue(count)\n        return (\n          <>\n            <div>\n              <button onClick={() => setCount((c) => c + 1)}>+1</button>\n            </div>\n            <Suspense fallback={<div>Loading...</div>}>\n              <ShowCount count={c} />\n            </Suspense>\n          </>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button>+1</button></div><div>0</div>')\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><button>+1</button></div><div>0</div>')\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<div><button>+1</button></div><div>0</div>')\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<div><button>+1</button></div><div>0</div>')\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<div><button>+1</button></div><div>0</div>')\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<div><button>+1</button></div><div>2</div>')\n    })\n\n    it('initial value', async () => {\n      const promiseMap = {} as Record<number, Promise<number>>\n      const getPromise = (count: number) => {\n        return (promiseMap[count] ||= new Promise((r) => setTimeout(() => r(count + 1))))\n      }\n      const ShowCount = ({ count }: { count: number }) => {\n        if (count === 0 || count === 99) {\n          return <div>{count}</div>\n        }\n\n        const c = use(getPromise(count))\n        return <div>{c}</div>\n      }\n\n      const App = () => {\n        const [count, setCount] = useState(1)\n        const c = useDeferredValue(count, 99)\n        return (\n          <>\n            <div>\n              <button onClick={() => setCount((c) => c + 1)}>+1</button>\n            </div>\n            <Suspense fallback={<div>Loading...</div>}>\n              <ShowCount count={c} />\n            </Suspense>\n          </>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button>+1</button></div><div>99</div>')\n      await new Promise((r) => setTimeout(r))\n      await new Promise((r) => setTimeout(r))\n      await new Promise((r) => setTimeout(r))\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<div><button>+1</button></div><div>2</div>')\n      root.querySelector('button')?.click()\n      await new Promise((r) => setTimeout(r))\n      await new Promise((r) => setTimeout(r))\n      await new Promise((r) => setTimeout(r))\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<div><button>+1</button></div><div>3</div>')\n    })\n  })\n\n  describe('startViewTransition()', () => {\n    afterEach(() => {\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      delete (dom.window.document as any).startViewTransition\n    })\n\n    it('supported browser', async () => {\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      ;(dom.window.document as any).startViewTransition = vi.fn((cb: Function) => {\n        Promise.resolve().then(() => cb())\n        return { finished: Promise.resolve() }\n      })\n\n      const App = () => {\n        const [count, setCount] = useState(0)\n        return (\n          <Suspense fallback={<div>Loading...</div>}>\n            <div>\n              <button\n                onClick={() => {\n                  startViewTransition(() => {\n                    setCount((c) => c + 1)\n                  })\n                }}\n              >\n                {count}\n              </button>\n            </div>\n          </Suspense>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button>0</button></div>')\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><button>0</button></div>')\n      await Promise.resolve() // updated in microtask\n      expect(root.innerHTML).toBe('<div><button>1</button></div>')\n    })\n\n    it('unsupported browser', async () => {\n      const App = () => {\n        const [count, setCount] = useState(0)\n        return (\n          <Suspense fallback={<div>Loading...</div>}>\n            <div>\n              <button\n                onClick={() => {\n                  startViewTransition(() => {\n                    setCount((c) => c + 1)\n                  })\n                }}\n              >\n                {count}\n              </button>\n            </div>\n          </Suspense>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button>0</button></div>')\n      root.querySelector('button')?.click()\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div><button>1</button></div>')\n    })\n\n    it('with useTransition()', async () => {\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      ;(dom.window.document as any).startViewTransition = vi.fn((cb: Function) => {\n        Promise.resolve().then(() => cb())\n        return { finished: Promise.resolve() }\n      })\n\n      let called = 0\n      const App = () => {\n        const [count, setCount] = useState(0)\n        const [isPending, startTransition] = useTransition()\n        called++\n\n        return (\n          <div>\n            <button\n              onClick={() => {\n                startViewTransition(() => {\n                  startTransition(() => {\n                    setCount((c) => c + 1)\n                  })\n                })\n              }}\n            >\n              {isPending ? 'Pending...' : count}\n            </button>\n          </div>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button>0</button></div>')\n      root.querySelector('button')?.click()\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<div><button>Pending...</button></div>')\n      expect(called).toBe(2)\n      await new Promise((r) => setTimeout(r))\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<div><button>1</button></div>')\n      expect(called).toBe(3)\n    })\n  })\n\n  describe('useViewTransition()', () => {\n    afterEach(() => {\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      delete (dom.window.document as any).startViewTransition\n    })\n\n    it('supported browser', async () => {\n      let resolved: (() => void) | undefined\n      const promise = new Promise<void>((r) => (resolved = r))\n      let called = 0\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      ;(global.document as any).startViewTransition = vi.fn((cb: Function) => {\n        Promise.resolve().then(() => cb())\n        return { finished: promise }\n      })\n\n      const App = () => {\n        const [count, setCount] = useState(0)\n        const [isUpdating, startViewTransition] = useViewTransition()\n        called++\n\n        return (\n          <div>\n            <button\n              onClick={() => {\n                startViewTransition(() => {\n                  setCount((c) => c + 1)\n                })\n              }}\n            >\n              {isUpdating ? 'Pending...' : count}\n            </button>\n          </div>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div><button>0</button></div>')\n      root.querySelector('button')?.click()\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<div><button>Pending...</button></div>')\n      expect(called).toBe(2)\n      resolved?.()\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<div><button>1</button></div>')\n      expect(called).toBe(3)\n    })\n  })\n\n  describe('useId()', () => {\n    let dom: JSDOM\n    let root: HTMLElement\n    beforeEach(() => {\n      dom = new JSDOM('<html><body><div id=\"root\"></div></body></html>', {\n        runScripts: 'dangerously',\n      })\n      global.document = dom.window.document\n      global.HTMLElement = dom.window.HTMLElement\n      global.Text = dom.window.Text\n      root = document.getElementById('root') as HTMLElement\n    })\n\n    it('simple', () => {\n      const App = () => {\n        const id = useId()\n        return <div id={id} />\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div id=\":r0:\"></div>')\n    })\n\n    it('memoized', async () => {\n      let setCount: (c: number) => void = () => {}\n      const App = () => {\n        const id = useId()\n        const [count, _setCount] = useState(0)\n        setCount = _setCount\n        return <div id={id}>{count}</div>\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div id=\":r1:\">0</div>')\n      setCount(1)\n      await Promise.resolve()\n      expect(root.innerHTML).toBe('<div id=\":r1:\">1</div>')\n    })\n  })\n\n  describe('useDebugValue()', () => {\n    it('simple', () => {\n      const spy = vi.fn()\n      const App = () => {\n        useDebugValue('hello', spy)\n        return <div />\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div></div>')\n      expect(spy).not.toBeCalled()\n    })\n  })\n\n  describe('createRef()', () => {\n    it('simple', () => {\n      const ref: { current: HTMLElement | null } = createRef<HTMLDivElement>()\n      const App = () => {\n        return <div ref={ref} />\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div></div>')\n      expect(ref.current).toBeInstanceOf(HTMLElement)\n    })\n  })\n\n  describe('forwardRef()', () => {\n    it('simple', () => {\n      const ref: { current: HTMLElement | null } = createRef<HTMLDivElement>()\n      const App = forwardRef((props, ref) => {\n        return <div {...props} ref={ref} />\n      })\n      render(<App ref={ref} />, root)\n      expect(root.innerHTML).toBe('<div></div>')\n      expect(ref.current).toBeInstanceOf(HTMLElement)\n    })\n\n    it('can run without ref', () => {\n      const App = forwardRef((props) => {\n        return <div {...props} />\n      })\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div></div>')\n    })\n  })\n\n  describe('useImperativeHandle()', () => {\n    it('simple', async () => {\n      const ref: { current: { focus: () => void } | null } = createRef()\n      const SubApp = () => {\n        useImperativeHandle(\n          ref,\n          () => ({\n            focus: () => {\n              console.log('focus')\n            },\n          }),\n          []\n        )\n        return <div />\n      }\n      const App = () => {\n        const [show, setShow] = useState(true)\n        return (\n          <>\n            {show && <SubApp />}\n            <button onClick={() => setShow((s) => !s)}>toggle</button>\n          </>\n        )\n      }\n      render(<App />, root)\n      expect(ref.current).toBe(null)\n      await new Promise((r) => setTimeout(r))\n      expect(ref.current).toEqual({ focus: expect.any(Function) })\n      root.querySelector('button')?.click()\n      await new Promise((r) => setTimeout(r))\n      expect(ref.current).toBe(null)\n    })\n  })\n\n  describe('useSyncExternalStore()', () => {\n    it('simple', async () => {\n      let count = 0\n      const unsubscribe = vi.fn()\n      const subscribe = vi.fn(() => unsubscribe)\n      const getSnapshot = vi.fn(() => count++)\n      const SubApp = () => {\n        const count = useSyncExternalStore(subscribe, getSnapshot)\n        return <div>{count}</div>\n      }\n      const App = () => {\n        const [show, setShow] = useState(true)\n        return (\n          <>\n            {show && <SubApp />}\n            <button onClick={() => setShow((s) => !s)}>toggle</button>\n          </>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div>0</div><button>toggle</button>')\n      await new Promise((r) => setTimeout(r))\n      root.querySelector('button')?.click()\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<button>toggle</button>')\n      expect(unsubscribe).toBeCalled()\n      root.querySelector('button')?.click()\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<div>1</div><button>toggle</button>')\n    })\n\n    it('with getServerSnapshot', async () => {\n      let count = 0\n      const unsubscribe = vi.fn()\n      const subscribe = vi.fn(() => unsubscribe)\n      const getSnapshot = vi.fn(() => count++)\n      const getServerSnapshot = vi.fn(() => 100)\n      const SubApp = () => {\n        const count = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n        return <div>{count}</div>\n      }\n      const App = () => {\n        const [show, setShow] = useState(true)\n        return (\n          <>\n            {show && <SubApp />}\n            <button onClick={() => setShow((s) => !s)}>toggle</button>\n          </>\n        )\n      }\n      render(<App />, root)\n      expect(root.innerHTML).toBe('<div>100</div><button>toggle</button>')\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<div>0</div><button>toggle</button>')\n      root.querySelector('button')?.click()\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<button>toggle</button>')\n      expect(unsubscribe).toBeCalled()\n      root.querySelector('button')?.click()\n      await new Promise((r) => setTimeout(r))\n      expect(root.innerHTML).toBe('<div>1</div><button>toggle</button>')\n    })\n  })\n})\n"
  },
  {
    "path": "src/jsx/hooks/index.ts",
    "content": "import type { JSX } from '../base'\nimport { DOM_STASH } from '../constants'\nimport { buildDataStack, update } from '../dom/render'\nimport type { Context, Node, NodeObject, PendingType, UpdateHook } from '../dom/render'\n\ntype UpdateStateFunction<T> = (newState: T | ((currentState: T) => T)) => void\n\nconst STASH_SATE = 0\nexport const STASH_EFFECT = 1\nconst STASH_CALLBACK = 2\nconst STASH_MEMO = 3\nconst STASH_REF = 4\n\nexport type EffectData = [\n  readonly unknown[] | undefined, // deps\n  (() => void | (() => void)) | undefined, // layout effect\n  (() => void) | undefined, // cleanup\n  (() => void) | undefined, // effect\n  (() => void) | undefined, // insertion effect\n]\n\nconst resolvedPromiseValueMap: WeakMap<Promise<unknown>, unknown> = new WeakMap<\n  Promise<unknown>,\n  unknown\n>()\n\nconst isDepsChanged = (\n  prevDeps: readonly unknown[] | undefined,\n  deps: readonly unknown[] | undefined\n): boolean =>\n  !prevDeps ||\n  !deps ||\n  prevDeps.length !== deps.length ||\n  deps.some((dep, i) => dep !== prevDeps[i])\n\nlet viewTransitionState:\n  | [\n      boolean, // isUpdating\n      boolean, // useViewTransition() is called\n    ]\n  | undefined = undefined\n\nconst documentStartViewTransition: (cb: () => void) => { finished: Promise<void> } = (cb) => {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  if ((document as any)?.startViewTransition) {\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    return (document as any).startViewTransition(cb)\n  } else {\n    cb()\n    return { finished: Promise.resolve() }\n  }\n}\n\nlet updateHook: UpdateHook | undefined = undefined\nconst viewTransitionHook = (\n  context: Context,\n  node: Node,\n  cb: (context: Context) => void\n): Promise<void> => {\n  const state: [boolean, boolean] = [true, false]\n  let lastVC = node.vC\n  return documentStartViewTransition(() => {\n    if (lastVC === node.vC) {\n      viewTransitionState = state\n      cb(context)\n      viewTransitionState = undefined\n      lastVC = node.vC\n    }\n  }).finished.then(() => {\n    if (state[1] && lastVC === node.vC) {\n      state[0] = false\n      viewTransitionState = state\n      cb(context)\n      viewTransitionState = undefined\n    }\n  })\n}\n\nexport const startViewTransition = (callback: () => void): void => {\n  updateHook = viewTransitionHook\n\n  try {\n    callback()\n  } finally {\n    updateHook = undefined\n  }\n}\n\nexport const useViewTransition = (): [boolean, (callback: () => void) => void] => {\n  const buildData = buildDataStack.at(-1) as [Context, NodeObject]\n  if (!buildData) {\n    return [false, () => {}]\n  }\n\n  if (viewTransitionState) {\n    viewTransitionState[1] = true\n  }\n  return [!!viewTransitionState?.[0], startViewTransition]\n}\n\n// PendingType is defined in \"../dom/render\", 3 is used for useDeferredValue\nconst pendingStack: [PendingType | 3, Promise<void>][] = []\nconst runCallback = (type: PendingType, callback: Function): void => {\n  let resolve: (() => void) | undefined\n  const promise = new Promise<void>((r) => (resolve = r))\n  pendingStack.push([type, promise])\n  try {\n    const res = callback()\n    if (res instanceof Promise) {\n      res.then(resolve, resolve)\n    } else {\n      resolve!()\n    }\n  } finally {\n    pendingStack.pop()\n  }\n}\n\nexport const startTransition = (callback: () => void): void => {\n  runCallback(1, callback)\n}\nconst startTransitionHook = (callback: () => void | Promise<void>): void => {\n  runCallback(2, callback)\n}\n\nexport const useTransition = (): [boolean, (callback: () => void | Promise<void>) => void] => {\n  const buildData = buildDataStack.at(-1) as [Context, NodeObject]\n  if (!buildData) {\n    return [false, () => {}]\n  }\n\n  const [error, setError] = useState<[Error]>()\n  const [state, updateState] = useState<boolean>()\n  if (error) {\n    throw error[0]\n  }\n  const startTransitionLocalHook = useCallback<typeof startTransitionHook>(\n    (callback) => {\n      startTransitionHook(() => {\n        updateState((state) => !state)\n        let res = callback()\n        if (res instanceof Promise) {\n          res = res.catch((e) => {\n            setError([e])\n          })\n        }\n        return res\n      })\n    },\n    [state]\n  )\n\n  const [context] = buildData\n  return [context[0] === 2, startTransitionLocalHook]\n}\n\ntype UseDeferredValue = <T>(value: T, initialValue?: T) => T\nexport const useDeferredValue: UseDeferredValue = <T>(value: T, ...rest: [T | undefined]): T => {\n  const [values, setValues] = useState<[T, T]>(\n    (rest.length ? [rest[0], rest[0]] : [value, value]) as [T, T]\n  )\n  if (Object.is(values[1], value)) {\n    return values[1]\n  }\n\n  pendingStack.push([3, Promise.resolve()])\n  updateHook = async (context: Context, _, cb: (context: Context) => void) => {\n    cb(context)\n    values[0] = value\n  }\n  setValues([values[0], value])\n  updateHook = undefined\n  pendingStack.pop()\n\n  return values[0]\n}\n\ntype UseStateType = {\n  <T>(initialState: T | (() => T)): [T, UpdateStateFunction<T>]\n  <T = undefined>(): [T | undefined, UpdateStateFunction<T | undefined>]\n}\nexport const useState: UseStateType = <T>(\n  initialState?: T | (() => T)\n): [T, UpdateStateFunction<T>] => {\n  const resolveInitialState = () =>\n    typeof initialState === 'function' ? (initialState as () => T)() : (initialState as T)\n\n  const buildData = buildDataStack.at(-1) as [unknown, NodeObject]\n  if (!buildData) {\n    return [resolveInitialState(), () => {}]\n  }\n  const [, node] = buildData\n\n  const stateArray = (node[DOM_STASH][1][STASH_SATE] ||= [])\n  const hookIndex = node[DOM_STASH][0]++\n\n  return (stateArray[hookIndex] ||= [\n    resolveInitialState(),\n    (newState: T | ((currentState: T) => T)) => {\n      const localUpdateHook = updateHook\n      const stateData = stateArray[hookIndex]\n      if (typeof newState === 'function') {\n        newState = (newState as (currentState: T) => T)(stateData[0])\n      }\n\n      if (!Object.is(newState, stateData[0])) {\n        stateData[0] = newState\n        if (pendingStack.length) {\n          const [pendingType, pendingPromise] = pendingStack.at(-1) as [\n            PendingType | 3,\n            Promise<void>,\n          ]\n          Promise.all([\n            pendingType === 3\n              ? node\n              : update([pendingType, false, localUpdateHook as UpdateHook], node),\n            pendingPromise,\n          ]).then(([node]) => {\n            if (!node || !(pendingType === 2 || pendingType === 3)) {\n              return\n            }\n\n            const lastVC = node.vC\n\n            const addUpdateTask = () => {\n              setTimeout(() => {\n                // return if `node` is rerendered after current transition\n                if (lastVC !== node.vC) {\n                  return\n                }\n                update([pendingType === 3 ? 1 : 0, false, localUpdateHook as UpdateHook], node)\n              })\n            }\n\n            requestAnimationFrame(addUpdateTask)\n          })\n        } else {\n          update([0, false, localUpdateHook as UpdateHook], node)\n        }\n      }\n    },\n  ])\n}\n\nexport const useReducer = <T, A>(\n  reducer: (state: T, action: A) => T,\n  initialArg: T,\n  init?: (initialState: T) => T\n): [T, (action: A) => void] => {\n  const handler = useCallback(\n    (action: A) => {\n      setState((state) => reducer(state, action))\n    },\n    [reducer]\n  )\n  const [state, setState] = useState(() => (init ? init(initialArg) : initialArg))\n  return [state, handler]\n}\n\nconst useEffectCommon = (\n  index: number,\n  effect: () => void | (() => void),\n  deps?: readonly unknown[]\n): void => {\n  const buildData = buildDataStack.at(-1) as [unknown, NodeObject]\n  if (!buildData) {\n    return\n  }\n  const [, node] = buildData\n\n  const effectDepsArray = (node[DOM_STASH][1][STASH_EFFECT] ||= [])\n  const hookIndex = node[DOM_STASH][0]++\n\n  const [prevDeps, , prevCleanup] = (effectDepsArray[hookIndex] ||= [])\n  if (isDepsChanged(prevDeps, deps)) {\n    if (prevCleanup) {\n      prevCleanup()\n    }\n    const runner = () => {\n      data[index] = undefined // clear this effect in order to avoid calling effect twice\n      data[2] = effect() as (() => void) | undefined\n    }\n    const data: EffectData = [deps, undefined, undefined, undefined, undefined]\n    data[index] = runner\n    effectDepsArray[hookIndex] = data\n  }\n}\nexport const useEffect = (effect: () => void | (() => void), deps?: readonly unknown[]): void =>\n  useEffectCommon(3, effect, deps)\nexport const useLayoutEffect = (\n  effect: () => void | (() => void),\n  deps?: readonly unknown[]\n): void => useEffectCommon(1, effect, deps)\nexport const useInsertionEffect = (\n  effect: () => void | (() => void),\n  deps?: readonly unknown[]\n): void => useEffectCommon(4, effect, deps)\n\nexport const useCallback = <T extends Function>(callback: T, deps: readonly unknown[]): T => {\n  const buildData = buildDataStack.at(-1) as [unknown, NodeObject]\n  if (!buildData) {\n    return callback\n  }\n  const [, node] = buildData\n\n  const callbackArray = (node[DOM_STASH][1][STASH_CALLBACK] ||= [])\n  const hookIndex = node[DOM_STASH][0]++\n\n  const prevDeps = callbackArray[hookIndex]\n  if (isDepsChanged(prevDeps?.[1], deps)) {\n    callbackArray[hookIndex] = [callback, deps]\n  } else {\n    callback = callbackArray[hookIndex][0] as T\n  }\n  return callback\n}\n\nexport type RefObject<T> = { current: T | null }\nexport const useRef = <T>(initialValue: T | null): RefObject<T> => {\n  const buildData = buildDataStack.at(-1) as [unknown, NodeObject]\n  if (!buildData) {\n    return { current: initialValue }\n  }\n  const [, node] = buildData\n\n  const refArray = (node[DOM_STASH][1][STASH_REF] ||= [])\n  const hookIndex = node[DOM_STASH][0]++\n\n  return (refArray[hookIndex] ||= { current: initialValue })\n}\n\nexport const use = <T>(promise: Promise<T>): T => {\n  const cachedRes = resolvedPromiseValueMap.get(promise) as [T] | [undefined, unknown] | undefined\n  if (cachedRes) {\n    if (cachedRes.length === 2) {\n      throw cachedRes[1]\n    }\n    return cachedRes[0] as T\n  }\n  promise.then(\n    (res) => resolvedPromiseValueMap.set(promise, [res]),\n    (e) => resolvedPromiseValueMap.set(promise, [undefined, e])\n  )\n\n  throw promise\n}\n\nexport const useMemo = <T>(factory: () => T, deps: readonly unknown[]): T => {\n  const buildData = buildDataStack.at(-1) as [unknown, NodeObject]\n  if (!buildData) {\n    return factory()\n  }\n  const [, node] = buildData\n\n  const memoArray = (node[DOM_STASH][1][STASH_MEMO] ||= [])\n  const hookIndex = node[DOM_STASH][0]++\n\n  const prevDeps = memoArray[hookIndex]\n  if (isDepsChanged(prevDeps?.[1], deps)) {\n    memoArray[hookIndex] = [factory(), deps]\n  }\n  return memoArray[hookIndex][0] as T\n}\n\nlet idCounter = 0\nexport const useId = (): string => useMemo(() => `:r${(idCounter++).toString(32)}:`, [])\n\n// Define to avoid errors. This hook currently does nothing.\nexport const useDebugValue = (_value: unknown, _formatter?: (value: unknown) => string): void => {}\n\nexport const createRef = <T>(): RefObject<T> => {\n  return { current: null }\n}\n\nexport const forwardRef = <T, P = {}>(\n  Component: (props: P, ref?: RefObject<T>) => JSX.Element\n): ((props: P & { ref?: RefObject<T> }) => JSX.Element) => {\n  return (props) => {\n    const { ref, ...rest } = props\n    return Component(rest as P, ref)\n  }\n}\n\nexport const useImperativeHandle = <T>(\n  ref: RefObject<T>,\n  createHandle: () => T,\n  deps: readonly unknown[]\n): void => {\n  useEffect(() => {\n    ref.current = createHandle()\n    return () => {\n      ref.current = null\n    }\n  }, deps)\n}\n\nexport const useSyncExternalStore = <T>(\n  subscribe: (callback: () => void) => () => void,\n  getSnapshot: () => T,\n  getServerSnapshot?: () => T\n): T => {\n  const buildData = buildDataStack.at(-1) as [Context, unknown]\n  if (!buildData) {\n    // now a stringify process, maybe in server side\n    if (!getServerSnapshot) {\n      throw new Error('getServerSnapshot is required for server side rendering')\n    }\n    return getServerSnapshot()\n  }\n\n  const [serverSnapshotIsUsed] = useState<boolean>(!!(buildData[0][4] && getServerSnapshot))\n  const [state, setState] = useState(() =>\n    serverSnapshotIsUsed ? (getServerSnapshot as () => T)() : getSnapshot()\n  )\n  useEffect(() => {\n    if (serverSnapshotIsUsed) {\n      setState(getSnapshot())\n    }\n    return subscribe(() => {\n      setState(getSnapshot())\n    })\n  }, [])\n\n  return state\n}\n"
  },
  {
    "path": "src/jsx/hooks/string.test.tsx",
    "content": "/** @jsxImportSource ../ */\nimport { useState, useSyncExternalStore } from '..'\n\ndescribe('useState', () => {\n  it('should be rendered with initial state', () => {\n    const Component = () => {\n      const [state] = useState('hello')\n      return <span>{state}</span>\n    }\n    const template = <Component />\n    expect(template.toString()).toBe('<span>hello</span>')\n  })\n})\n\ndescribe('useSyncExternalStore', () => {\n  it('should be rendered with result of getServerSnapshot()', () => {\n    const unsubscribe = vi.fn()\n    const subscribe = vi.fn(() => unsubscribe)\n    const getSnapshot = vi.fn()\n    const getServerSnapshot = vi.fn(() => 100)\n    const App = () => {\n      const count = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n      return <div>{count}</div>\n    }\n    const template = <App />\n    expect(template.toString()).toBe('<div>100</div>')\n    expect(unsubscribe).not.toBeCalled()\n    expect(subscribe).not.toBeCalled()\n    expect(getSnapshot).not.toBeCalled()\n  })\n\n  it('should raise an error if getServerShot() is not provided', () => {\n    const App = () => {\n      const count = useSyncExternalStore(vi.fn(), vi.fn())\n      return <div>{count}</div>\n    }\n    const template = <App />\n    expect(() => template.toString()).toThrowError()\n  })\n})\n"
  },
  {
    "path": "src/jsx/index.test.tsx",
    "content": "/** @jsxImportSource ./ */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { html } from '../helper/html'\nimport { Hono } from '../hono'\nimport { Suspense, renderToReadableStream } from './streaming'\nimport DefaultExport, { Fragment, StrictMode, createContext, memo, useContext, version } from '.'\nimport type { Context, FC, PropsWithChildren } from '.'\n\ninterface SiteData {\n  title: string\n  children?: any\n}\n\ndescribe('JSX middleware', () => {\n  let app: Hono\n\n  beforeEach(() => {\n    app = new Hono()\n  })\n\n  it('Should render HTML strings', async () => {\n    app.get('/', (c) => {\n      return c.html(<h1>Hello</h1>)\n    })\n    const res = await app.request('http://localhost/')\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Type')).toBe('text/html; charset=UTF-8')\n    expect(await res.text()).toBe('<h1>Hello</h1>')\n  })\n\n  it('Should be able to be used with html middleware', async () => {\n    const Layout = (props: SiteData) =>\n      html`<!DOCTYPE html>\n        <html>\n          <head>\n            <title>${props.title}</title>\n          </head>\n          <body>\n            ${props.children}\n          </body>\n        </html>`\n\n    const Content = (props: { siteData: SiteData; name: string }) => (\n      <Layout {...props.siteData}>\n        <h1>{props.name}</h1>\n      </Layout>\n    )\n\n    app.get('/', (c) => {\n      const props = {\n        name: 'JSX',\n        siteData: {\n          title: 'with html middleware',\n        },\n      }\n      return c.html(<Content {...props} />)\n    })\n    const res = await app.request('http://localhost/')\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Type')).toBe('text/html; charset=UTF-8')\n    expect(await res.text()).toBe(`<!DOCTYPE html>\n        <html>\n          <head>\n            <title>with html middleware</title>\n          </head>\n          <body>\n            <h1>JSX</h1>\n          </body>\n        </html>`)\n  })\n\n  it('Should render async component', async () => {\n    const ChildAsyncComponent = async () => {\n      await new Promise((resolve) => setTimeout(resolve, 10))\n      return <span>child async component</span>\n    }\n\n    const AsyncComponent = async () => {\n      await new Promise((resolve) => setTimeout(resolve, 10))\n      return (\n        <h1>\n          Hello from async component\n          <ChildAsyncComponent />\n        </h1>\n      )\n    }\n\n    app.get('/', (c) => {\n      return c.html(<AsyncComponent />)\n    })\n    const res = await app.request('http://localhost/')\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Type')).toBe('text/html; charset=UTF-8')\n    expect(await res.text()).toBe(\n      '<h1>Hello from async component<span>child async component</span></h1>'\n    )\n  })\n\n  it('Should render async component with \"html\" tagged template strings', async () => {\n    const AsyncComponent = async () => {\n      await new Promise((resolve) => setTimeout(resolve, 10))\n      return <h1>Hello from async component</h1>\n    }\n\n    app.get('/', (c) => {\n      // prettier-ignore\n      return c.html(\n        html`<html><body>${(<AsyncComponent />)}</body></html>`\n      )\n    })\n    const res = await app.request('http://localhost/')\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Type')).toBe('text/html; charset=UTF-8')\n    expect(await res.text()).toBe('<html><body><h1>Hello from async component</h1></body></html>')\n  })\n\n  it('Should handle async component error', async () => {\n    const componentError = new Error('Error from async error component')\n\n    const AsyncComponent = async () => {\n      await new Promise((resolve) => setTimeout(resolve, 10))\n      return <h1>Hello from async component</h1>\n    }\n    const AsyncErrorComponent = async () => {\n      await new Promise((resolve) => setTimeout(resolve, 0))\n      throw componentError\n    }\n\n    let raisedError: any\n    app.onError((e, c) => {\n      raisedError = e\n      return c.html('<html><body><h1>Error from onError</h1></body></html>', 500)\n    })\n    app.get('/', (c) => {\n      return c.html(\n        <>\n          <AsyncComponent />\n          <AsyncErrorComponent />\n        </>\n      )\n    })\n\n    const res = await app.request('http://localhost/')\n    expect(res.status).toBe(500)\n    expect(res.headers.get('Content-Type')).toBe('text/html; charset=UTF-8')\n    expect(await res.text()).toBe('<html><body><h1>Error from onError</h1></body></html>')\n    expect(raisedError).toBe(componentError)\n  })\n})\n\ndescribe('render to string', () => {\n  it('Nested array', () => {\n    const template = (\n      <p>\n        {[[['a']], [['b']]].map((item1) =>\n          item1.map((item2) => item2.map((item3) => <span>{item3}</span>))\n        )}\n      </p>\n    )\n    expect(template.toString()).toBe('<p><span>a</span><span>b</span></p>')\n  })\n\n  it('Empty elements are rended without closing tag', () => {\n    const template = <input />\n    expect(template.toString()).toBe('<input/>')\n  })\n\n  it('Empty elements with children are rended with children and closing tag', () => {\n    const template = <link>https://example.com</link>\n    expect(template.toString()).toBe('<link>https://example.com</link>')\n  })\n\n  it('Props value is null', () => {\n    const template = <span data-hello={null}>Hello</span>\n    expect(template.toString()).toBe('<span>Hello</span>')\n  })\n\n  it('Props value is undefined', () => {\n    const template = <span data-hello={undefined}>Hello</span>\n    expect(template.toString()).toBe('<span>Hello</span>')\n  })\n\n  describe('dangerouslySetInnerHTML', () => {\n    it('Should render dangerouslySetInnerHTML', () => {\n      const template = <span dangerouslySetInnerHTML={{ __html: '\" is allowed here' }}></span>\n      expect(template.toString()).toBe('<span>\" is allowed here</span>')\n    })\n\n    it('Should get an error if both dangerouslySetInnerHTML and children are specified', () => {\n      expect(() =>\n        (<span dangerouslySetInnerHTML={{ __html: '\" is allowed here' }}>Hello</span>).toString()\n      ).toThrow(Error)\n    })\n  })\n\n  // https://en.reactjs.org/docs/jsx-in-depth.html#booleans-null-and-undefined-are-ignored\n  describe('Booleans, Null, and Undefined Are Ignored', () => {\n    it.each([true, false, undefined, null])('%s', (item) => {\n      expect((<span>{item}</span>).toString()).toBe('<span></span>')\n    })\n\n    it('falsy value', () => {\n      const template = <span>{0}</span>\n      expect(template.toString()).toBe('<span>0</span>')\n    })\n  })\n\n  // https://en.reactjs.org/docs/jsx-in-depth.html#props-default-to-true\n  describe('Props Default to “True”', () => {\n    it('default prop value', () => {\n      const template = <span data-hello>Hello</span>\n      expect(template.toString()).toBe('<span data-hello=\"true\">Hello</span>')\n    })\n  })\n\n  // https://html.spec.whatwg.org/#attributes-3\n  describe('Boolean attribute', () => {\n    it('default prop value for checked', () => {\n      const template = <input type='checkbox' checked />\n      expect(template.toString()).toBe('<input type=\"checkbox\" checked=\"\"/>')\n    })\n\n    it('default prop value for checked={true}', () => {\n      const template = <input type='checkbox' checked={true} />\n      expect(template.toString()).toBe('<input type=\"checkbox\" checked=\"\"/>')\n    })\n\n    it('no prop for checked={false}', () => {\n      const template = <input type='checkbox' checked={false} />\n      expect(template.toString()).toBe('<input type=\"checkbox\"/>')\n    })\n\n    it('default prop value for disabled', () => {\n      const template = <input type='checkbox' disabled />\n      expect(template.toString()).toBe('<input type=\"checkbox\" disabled=\"\"/>')\n    })\n\n    it('default prop value for disabled={true}', () => {\n      const template = <input type='checkbox' disabled={true} />\n      expect(template.toString()).toBe('<input type=\"checkbox\" disabled=\"\"/>')\n    })\n\n    it('no prop for disabled={false}', () => {\n      const template = <input type='checkbox' disabled={false} />\n      expect(template.toString()).toBe('<input type=\"checkbox\"/>')\n    })\n\n    it('default prop value for readonly', () => {\n      const template = <input type='checkbox' readonly />\n      expect(template.toString()).toBe('<input type=\"checkbox\" readonly=\"\"/>')\n    })\n\n    it('default prop value for readonly={true}', () => {\n      const template = <input type='checkbox' readonly={true} />\n      expect(template.toString()).toBe('<input type=\"checkbox\" readonly=\"\"/>')\n    })\n\n    it('no prop for readonly={false}', () => {\n      const template = <input type='checkbox' readonly={false} />\n      expect(template.toString()).toBe('<input type=\"checkbox\"/>')\n    })\n\n    it('default prop value for selected', () => {\n      const template = (\n        <option value='test' selected>\n          Test\n        </option>\n      )\n      expect(template.toString()).toBe('<option value=\"test\" selected=\"\">Test</option>')\n    })\n\n    it('default prop value for selected={true}', () => {\n      const template = (\n        <option value='test' selected={true}>\n          Test\n        </option>\n      )\n      expect(template.toString()).toBe('<option value=\"test\" selected=\"\">Test</option>')\n    })\n\n    it('no prop for selected={false}', () => {\n      const template = (\n        <option value='test' selected={false}>\n          Test\n        </option>\n      )\n      expect(template.toString()).toBe('<option value=\"test\">Test</option>')\n    })\n\n    it('default prop value for multiple select', () => {\n      const template = (\n        <select multiple>\n          <option>test</option>\n        </select>\n      )\n      expect(template.toString()).toBe('<select multiple=\"\"><option>test</option></select>')\n    })\n\n    it('default prop value for select multiple={true}', () => {\n      const template = (\n        <select multiple={true}>\n          <option>test</option>\n        </select>\n      )\n      expect(template.toString()).toBe('<select multiple=\"\"><option>test</option></select>')\n    })\n\n    it('no prop for select multiple={false}', () => {\n      const template = (\n        <select multiple={false}>\n          <option>test</option>\n        </select>\n      )\n      expect(template.toString()).toBe('<select><option>test</option></select>')\n    })\n\n    it('should render \"false\" value properly for other non-defined keys', () => {\n      const template = <input type='checkbox' testkey={false} />\n      expect(template.toString()).toBe('<input type=\"checkbox\" testkey=\"false\"/>')\n    })\n\n    it('should support attributes for elements other than input', () => {\n      const template = (\n        <video controls autoplay>\n          <source src='movie.mp4' type='video/mp4' />\n        </video>\n      )\n      expect(template.toString()).toBe(\n        '<video controls=\"\" autoplay=\"\"><source src=\"movie.mp4\" type=\"video/mp4\"/></video>'\n      )\n    })\n  })\n\n  describe('download attribute', () => {\n    it('<a download={true}></a> should be rendered as <a download=\"\"></a>', () => {\n      const template = <a download={true}></a>\n      expect(template.toString()).toBe('<a download=\"\"></a>')\n    })\n\n    it('<a download={false}></a> should be rendered as <a></a>', () => {\n      const template = <a download={false}></a>\n      expect(template.toString()).toBe('<a></a>')\n    })\n\n    it('<a download></a> should be rendered as <a download=\"\"></a>', () => {\n      const template = <a download></a>\n      expect(template.toString()).toBe('<a download=\"\"></a>')\n    })\n\n    it('<a download=\"test\"></a> should be rendered as <a download=\"test\"></a>', () => {\n      const template = <a download='test'></a>\n      expect(template.toString()).toBe('<a download=\"test\"></a>')\n    })\n  })\n\n  describe('Function', () => {\n    it('should be ignored used in on* props', () => {\n      const onClick = () => {}\n      const template = <button onClick={onClick}>Click</button>\n      expect(template.toString()).toBe('<button>Click</button>')\n    })\n\n    it('should be ignored used in ref props', () => {\n      const ref = () => {}\n      const template = <div ref={ref}>Content</div>\n      expect(template.toString()).toBe('<div>Content</div>')\n    })\n\n    it('should raise an error if used in other props', () => {\n      const onClick = () => {}\n      const template = <button data-handler={onClick}>Click</button>\n      expect(() => template.toString()).toThrow(Error)\n    })\n  })\n\n  // https://en.reactjs.org/docs/jsx-in-depth.html#functions-as-children\n  describe('Functions as Children', () => {\n    it('Function', () => {\n      function Repeat(props: any) {\n        const items = []\n        for (let i = 0; i < props.numTimes; i++) {\n          items.push((props.children as Function)(i))\n        }\n        return <div>{items}</div>\n      }\n\n      function ListOfTenThings() {\n        return (\n          <Repeat numTimes={10}>\n            {(index: string) => <div key={index}>This is item {index} in the list</div>}\n          </Repeat>\n        )\n      }\n\n      const template = <ListOfTenThings />\n      expect(template.toString()).toBe(\n        '<div><div>This is item 0 in the list</div><div>This is item 1 in the list</div><div>This is item 2 in the list</div><div>This is item 3 in the list</div><div>This is item 4 in the list</div><div>This is item 5 in the list</div><div>This is item 6 in the list</div><div>This is item 7 in the list</div><div>This is item 8 in the list</div><div>This is item 9 in the list</div></div>'\n      )\n    })\n  })\n\n  describe('FC', () => {\n    it('Should define the type correctly', () => {\n      const Layout: FC<PropsWithChildren<{ title: string }>> = (props) => {\n        return (\n          <html>\n            <head>\n              <title>{props.title}</title>\n            </head>\n            <body>{props.children}</body>\n          </html>\n        )\n      }\n\n      const Top = (\n        <Layout title='Home page'>\n          <h1>Hono</h1>\n          <p>Hono is great</p>\n        </Layout>\n      )\n\n      expect(Top.toString()).toBe(\n        '<html><head><title>Home page</title></head><body><h1>Hono</h1><p>Hono is great</p></body></html>'\n      )\n    })\n\n    describe('Booleans, Null, and Undefined Are Ignored', () => {\n      it.each([true, false, undefined, null])('%s', (item) => {\n        const Component: FC = (() => {\n          return item\n        }) as FC\n        const template = <Component />\n        expect(template.toString()).toBe('')\n      })\n\n      it('falsy value', () => {\n        const Component: FC = (() => {\n          return 0\n        }) as unknown as FC\n        const template = <Component />\n        expect(template.toString()).toBe('0')\n      })\n    })\n  })\n\n  describe('style attribute', () => {\n    it('should convert the object to strings', () => {\n      const template = (\n        <h1\n          style={{\n            color: 'red',\n            fontSize: 'small',\n            fontFamily: 'Menlo, Consolas, \"DejaVu Sans Mono\", monospace',\n          }}\n        >\n          Hello\n        </h1>\n      )\n      expect(template.toString()).toBe(\n        '<h1 style=\"color:red;font-size:small;font-family:Menlo, Consolas, &quot;DejaVu Sans Mono&quot;, monospace\">Hello</h1>'\n      )\n    })\n    it('should not convert the strings', () => {\n      const template = <h1 style='color:red;font-size:small'>Hello</h1>\n      expect(template.toString()).toBe('<h1 style=\"color:red;font-size:small\">Hello</h1>')\n    })\n    it('should render variable without any name conversion', () => {\n      const template = <h1 style={{ '--myVar': 1 }}>Hello</h1>\n      expect(template.toString()).toBe('<h1 style=\"--myVar:1px\">Hello</h1>')\n    })\n  })\n\n  describe('HtmlEscaped in props', () => {\n    it('should not be double-escaped', () => {\n      const escapedString = html`${'<html-escaped-string>'}`\n      const template = <span data-text={escapedString}>Hello</span>\n      expect(template.toString()).toBe('<span data-text=\"&lt;html-escaped-string&gt;\">Hello</span>')\n    })\n  })\n\n  describe('head', () => {\n    it('Simple head elements should be rendered as is', () => {\n      const template = (\n        <head>\n          <title>Hono!</title>\n          <meta name='description' content='A description' />\n          <script src='script.js'></script>\n        </head>\n      )\n      expect(template.toString()).toBe(\n        '<head><title>Hono!</title><meta name=\"description\" content=\"A description\"/><script src=\"script.js\"></script></head>'\n      )\n    })\n  })\n})\n\ndescribe('className', () => {\n  it('should convert to class attribute for intrinsic elements', () => {\n    const template = <h1 className='h1'>Hello</h1>\n    expect(template.toString()).toBe('<h1 class=\"h1\">Hello</h1>')\n  })\n\n  it('should convert to class attribute for custom elements', () => {\n    const template = <custom-element className='h1'>Hello</custom-element>\n    expect(template.toString()).toBe('<custom-element class=\"h1\">Hello</custom-element>')\n  })\n\n  it('should not convert to class attribute for custom components', () => {\n    const CustomComponent: FC<{ className: string }> = ({ className }) => (\n      <div data-class-name={className}>Hello</div>\n    )\n    const template = <CustomComponent className='h1' />\n    expect(template.toString()).toBe('<div data-class-name=\"h1\">Hello</div>')\n  })\n})\n\ndescribe('memo', () => {\n  it('memoized', () => {\n    let counter = 0\n    const Header = memo(() => <title>Test Site {counter}</title>)\n    const Body = () => <span>{counter}</span>\n\n    let template = (\n      <html>\n        <head>\n          <Header />\n        </head>\n        <body>\n          <Body />\n        </body>\n      </html>\n    )\n    expect(template.toString()).toBe(\n      '<html><head><title>Test Site 0</title></head><body><span>0</span></body></html>'\n    )\n\n    counter++\n    template = (\n      <html>\n        <head>\n          <Header />\n        </head>\n        <body>\n          <Body />\n        </body>\n      </html>\n    )\n    expect(template.toString()).toBe(\n      '<html><head><title>Test Site 0</title></head><body><span>1</span></body></html>'\n    )\n  })\n\n  it('props are updated', () => {\n    const Body = memo(({ counter }: { counter: number }) => <span>{counter}</span>)\n\n    let template = <Body counter={0} />\n    expect(template.toString()).toBe('<span>0</span>')\n\n    template = <Body counter={1} />\n    expect(template.toString()).toBe('<span>1</span>')\n  })\n\n  it('custom propsAreEqual', () => {\n    const Body = memo(\n      ({ counter }: { counter: number; refresh?: boolean }) => <span>{counter}</span>,\n      (_, nextProps) => (typeof nextProps.refresh == 'undefined' ? true : !nextProps.refresh)\n    )\n\n    let template = <Body counter={0} />\n    expect(template.toString()).toBe('<span>0</span>')\n\n    template = <Body counter={1} />\n    expect(template.toString()).toBe('<span>0</span>')\n\n    template = <Body counter={2} refresh={true} />\n    expect(template.toString()).toBe('<span>2</span>')\n  })\n})\n\ndescribe('Fragment', () => {\n  it('Should render children', () => {\n    const template = (\n      <>\n        <p>1</p>\n        <p>2</p>\n      </>\n    )\n    expect(template.toString()).toBe('<p>1</p><p>2</p>')\n  })\n\n  it('Should render children - with `Fragment`', () => {\n    const template = (\n      <Fragment>\n        <p>1</p>\n        <p>2</p>\n      </Fragment>\n    )\n    expect(template.toString()).toBe('<p>1</p><p>2</p>')\n  })\n\n  it('Should render a child', () => {\n    const template = (\n      <>\n        <p>1</p>\n      </>\n    )\n    expect(template.toString()).toBe('<p>1</p>')\n  })\n\n  it('Should render a child - with `Fragment`', () => {\n    const template = (\n      <Fragment>\n        <p>1</p>\n      </Fragment>\n    )\n    expect(template.toString()).toBe('<p>1</p>')\n  })\n\n  it('Should render nothing for empty Fragment', () => {\n    const template = <></>\n    expect(template.toString()).toBe('')\n  })\n\n  it('Should render nothing for undefined', () => {\n    const template = <>{undefined}</>\n    expect(template.toString()).toBe('')\n  })\n})\n\ndescribe('StrictMode', () => {\n  it('Should render children', () => {\n    const template = (\n      <StrictMode>\n        <p>1</p>\n        <p>2</p>\n      </StrictMode>\n    )\n    expect(template.toString()).toBe('<p>1</p><p>2</p>')\n  })\n})\n\ndescribe('SVG', () => {\n  it('simple', () => {\n    const template = (\n      <svg>\n        <circle cx='50' cy='50' r='40' stroke='black' stroke-width='3' fill='red' />\n      </svg>\n    )\n    expect(template.toString()).toBe(\n      '<svg><circle cx=\"50\" cy=\"50\" r=\"40\" stroke=\"black\" stroke-width=\"3\" fill=\"red\"></circle></svg>'\n    )\n  })\n\n  it('title element', () => {\n    const template = (\n      <>\n        <head>\n          <title>Document Title</title>\n        </head>\n        <svg>\n          <title>SVG Title</title>\n        </svg>\n      </>\n    )\n    expect(template.toString()).toBe(\n      '<head><title>Document Title</title></head><svg><title>SVG Title</title></svg>'\n    )\n  })\n\n  describe('attribute', () => {\n    describe('camelCase', () => {\n      test.each`\n        key\n        ${'attributeName'}\n        ${'baseFrequency'}\n        ${'calcMode'}\n        ${'clipPathUnits'}\n        ${'diffuseConstant'}\n        ${'edgeMode'}\n        ${'filterUnits'}\n        ${'gradientTransform'}\n        ${'gradientUnits'}\n        ${'kernelMatrix'}\n        ${'kernelUnitLength'}\n        ${'keyPoints'}\n        ${'keySplines'}\n        ${'keyTimes'}\n        ${'lengthAdjust'}\n        ${'limitingConeAngle'}\n        ${'markerHeight'}\n        ${'markerUnits'}\n        ${'markerWidth'}\n        ${'maskContentUnits'}\n        ${'maskUnits'}\n        ${'numOctaves'}\n        ${'pathLength'}\n        ${'patternContentUnits'}\n        ${'patternTransform'}\n        ${'patternUnits'}\n        ${'pointsAtX'}\n        ${'pointsAtY'}\n        ${'pointsAtZ'}\n        ${'preserveAlpha'}\n        ${'preserveAspectRatio'}\n        ${'primitiveUnits'}\n        ${'refX'}\n        ${'refY'}\n        ${'repeatCount'}\n        ${'repeatDur'}\n        ${'specularConstant'}\n        ${'specularExponent'}\n        ${'spreadMethod'}\n        ${'startOffset'}\n        ${'stdDeviation'}\n        ${'stitchTiles'}\n        ${'surfaceScale'}\n        ${'crossorigin'}\n        ${'systemLanguage'}\n        ${'tableValues'}\n        ${'targetX'}\n        ${'targetY'}\n        ${'textLength'}\n        ${'viewBox'}\n        ${'xChannelSelector'}\n        ${'yChannelSelector'}\n      `('$key', ({ key }) => {\n        const template = (\n          <svg>\n            <g {...{ [key]: 'test' }} />\n          </svg>\n        )\n        expect(template.toString()).toBe(`<svg><g ${key}=\"test\"></g></svg>`)\n      })\n    })\n\n    describe('kebab-case', () => {\n      test.each`\n        key\n        ${'alignmentBaseline'}\n        ${'baselineShift'}\n        ${'clipPath'}\n        ${'clipRule'}\n        ${'colorInterpolation'}\n        ${'colorInterpolationFilters'}\n        ${'dominantBaseline'}\n        ${'fillOpacity'}\n        ${'fillRule'}\n        ${'floodColor'}\n        ${'floodOpacity'}\n        ${'fontFamily'}\n        ${'fontSize'}\n        ${'fontSizeAdjust'}\n        ${'fontStretch'}\n        ${'fontStyle'}\n        ${'fontVariant'}\n        ${'fontWeight'}\n        ${'imageRendering'}\n        ${'letterSpacing'}\n        ${'lightingColor'}\n        ${'markerEnd'}\n        ${'markerMid'}\n        ${'markerStart'}\n        ${'overlinePosition'}\n        ${'overlineThickness'}\n        ${'paintOrder'}\n        ${'pointerEvents'}\n        ${'shapeRendering'}\n        ${'stopColor'}\n        ${'stopOpacity'}\n        ${'strikethroughPosition'}\n        ${'strikethroughThickness'}\n        ${'strokeDasharray'}\n        ${'strokeDashoffset'}\n        ${'strokeLinecap'}\n        ${'strokeLinejoin'}\n        ${'strokeMiterlimit'}\n        ${'strokeOpacity'}\n        ${'strokeWidth'}\n        ${'textAnchor'}\n        ${'textDecoration'}\n        ${'textRendering'}\n        ${'transformOrigin'}\n        ${'underlinePosition'}\n        ${'underlineThickness'}\n        ${'unicodeBidi'}\n        ${'vectorEffect'}\n        ${'wordSpacing'}\n        ${'writingMode'}\n      `('$key', ({ key }) => {\n        const template = (\n          <svg>\n            <g {...{ [key]: 'test' }} />\n          </svg>\n        )\n        expect(template.toString()).toBe(\n          `<svg><g ${key.replace(/([A-Z])/g, '-$1').toLowerCase()}=\"test\"></g></svg>`\n        )\n      })\n    })\n\n    describe('data-*', () => {\n      test.each`\n        key\n        ${'data-foo'}\n        ${'data-foo-bar'}\n        ${'data-fooBar'}\n      `('$key', ({ key }) => {\n        const template = (\n          <svg>\n            <g {...{ [key]: 'test' }} />\n          </svg>\n        )\n        expect(template.toString()).toBe(`<svg><g ${key}=\"test\"></g></svg>`)\n      })\n    })\n  })\n})\n\ndescribe('Context', () => {\n  let ThemeContext: Context<string>\n  let Consumer: FC\n  let ErrorConsumer: FC\n  let AsyncConsumer: FC\n  let AsyncErrorConsumer: FC\n  beforeAll(() => {\n    ThemeContext = createContext('light')\n    Consumer = () => {\n      const theme = useContext(ThemeContext)\n      return <span>{theme}</span>\n    }\n    ErrorConsumer = () => {\n      throw new Error('ErrorConsumer')\n    }\n    AsyncConsumer = async () => {\n      const theme = useContext(ThemeContext)\n      return <span>{theme}</span>\n    }\n    AsyncErrorConsumer = async () => {\n      throw new Error('AsyncErrorConsumer')\n    }\n  })\n\n  describe('with .Provider', () => {\n    it('has a child', () => {\n      const template = (\n        <ThemeContext.Provider value='dark'>\n          <Consumer />\n        </ThemeContext.Provider>\n      )\n      expect(template.toString()).toBe('<span>dark</span>')\n    })\n\n    it('has children', () => {\n      const template = (\n        <ThemeContext.Provider value='dark'>\n          <div>\n            <Consumer />!\n          </div>\n          <div>\n            <Consumer />!\n          </div>\n        </ThemeContext.Provider>\n      )\n      expect(template.toString()).toBe('<div><span>dark</span>!</div><div><span>dark</span>!</div>')\n    })\n\n    it('nested', () => {\n      const template = (\n        <ThemeContext.Provider value='dark'>\n          <Consumer />\n          <ThemeContext.Provider value='black'>\n            <Consumer />\n          </ThemeContext.Provider>\n          <Consumer />\n        </ThemeContext.Provider>\n      )\n      expect(template.toString()).toBe('<span>dark</span><span>black</span><span>dark</span>')\n    })\n\n    it('should reset context by error', () => {\n      const template = (\n        <ThemeContext.Provider value='dark'>\n          <ErrorConsumer />\n        </ThemeContext.Provider>\n      )\n      expect(() => template.toString()).toThrow()\n\n      const nextRequest = <Consumer />\n      expect(nextRequest.toString()).toBe('<span>light</span>')\n    })\n  })\n\n  describe('<Context> as a provider ', () => {\n    it('has a child', () => {\n      const template = (\n        <ThemeContext value='dark'>\n          <Consumer />\n        </ThemeContext>\n      )\n      expect(template.toString()).toBe('<span>dark</span>')\n    })\n  })\n\n  it('default value', () => {\n    const template = <Consumer />\n    expect(template.toString()).toBe('<span>light</span>')\n  })\n\n  describe('with Suspence', () => {\n    const RedTheme = () => (\n      <ThemeContext.Provider value='red'>\n        <Consumer />\n      </ThemeContext.Provider>\n    )\n\n    it('Should preserve context in sync component', async () => {\n      const template = (\n        <ThemeContext.Provider value='dark'>\n          <Suspense fallback={<RedTheme />}>\n            <Consumer />\n            <ThemeContext.Provider value='black'>\n              <Consumer />\n            </ThemeContext.Provider>\n          </Suspense>\n        </ThemeContext.Provider>\n      )\n      const stream = renderToReadableStream(template)\n\n      const chunks = []\n      const textDecoder = new TextDecoder()\n      for await (const chunk of stream as any) {\n        chunks.push(textDecoder.decode(chunk))\n      }\n\n      expect(chunks).toEqual(['<span>dark</span><span>black</span>'])\n    })\n\n    it('Should preserve context in async component', async () => {\n      const template = (\n        <ThemeContext.Provider value='dark'>\n          <Suspense fallback={<RedTheme />}>\n            <Consumer />\n            <ThemeContext.Provider value='black'>\n              <AsyncConsumer />\n            </ThemeContext.Provider>\n          </Suspense>\n        </ThemeContext.Provider>\n      )\n      const stream = renderToReadableStream(template)\n\n      const chunks = []\n      const textDecoder = new TextDecoder()\n      for await (const chunk of stream as any) {\n        chunks.push(textDecoder.decode(chunk))\n      }\n\n      expect(chunks).toEqual([\n        '<template id=\"H:0\"></template><span>red</span><!--/$-->',\n        `<template data-hono-target=\"H:0\"><span>dark</span><span>black</span></template><script>\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:0')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script>`,\n      ])\n    })\n  })\n\n  describe('async component', () => {\n    const ParentAsyncConsumer = async () => {\n      const theme = useContext(ThemeContext)\n      return (\n        <div>\n          <span>{theme}</span>\n          <AsyncConsumer />\n        </div>\n      )\n    }\n\n    const ParentAsyncErrorConsumer = async () => {\n      const theme = useContext(ThemeContext)\n      return (\n        <div>\n          <span>{theme}</span>\n          <AsyncErrorConsumer />\n        </div>\n      )\n    }\n\n    it('simple', async () => {\n      const template = (\n        <ThemeContext.Provider value='dark'>\n          <AsyncConsumer />\n        </ThemeContext.Provider>\n      )\n      expect((await template.toString()).toString()).toBe('<span>dark</span>')\n    })\n\n    it('nested', async () => {\n      const template = (\n        <ThemeContext.Provider value='dark'>\n          <ParentAsyncConsumer />\n        </ThemeContext.Provider>\n      )\n      expect((await template.toString()).toString()).toBe(\n        '<div><span>dark</span><span>dark</span></div>'\n      )\n    })\n\n    it('should reset context by error', async () => {\n      const template = (\n        <ThemeContext.Provider value='dark'>\n          <ParentAsyncErrorConsumer />\n        </ThemeContext.Provider>\n      )\n      await expect(async () => (await template.toString()).toString()).rejects.toThrow()\n\n      const nextRequest = <Consumer />\n      expect(nextRequest.toString()).toBe('<span>light</span>')\n    })\n  })\n\n  describe('async with html helper', () => {\n    it('should preserve context when using await before html helper', async () => {\n      // Regression test for https://github.com/honojs/hono/issues/4582\n      // Context was being popped before async children resolved\n      const AsyncParentWithHtml = async (props: { children?: any }) => {\n        await new Promise((r) => setTimeout(r, 10))\n        return html`<div>${props.children}</div>`\n      }\n\n      const template = (\n        <ThemeContext.Provider value='dark'>\n          <AsyncParentWithHtml>\n            <Consumer />\n          </AsyncParentWithHtml>\n        </ThemeContext.Provider>\n      )\n      expect((await template.toString()).toString()).toBe('<div><span>dark</span></div>')\n    })\n\n    it('should preserve nested context when using await before html helper', async () => {\n      const AsyncParentWithHtml = async (props: { children?: any }) => {\n        await new Promise((r) => setTimeout(r, 10))\n        return html`<div>${props.children}</div>`\n      }\n\n      const template = (\n        <ThemeContext.Provider value='dark'>\n          <AsyncParentWithHtml>\n            <ThemeContext.Provider value='black'>\n              <Consumer />\n            </ThemeContext.Provider>\n          </AsyncParentWithHtml>\n        </ThemeContext.Provider>\n      )\n      expect((await template.toString()).toString()).toBe('<div><span>black</span></div>')\n    })\n  })\n})\n\ndescribe('version', () => {\n  it('should be defined with semantic versioning format', () => {\n    expect(version).toMatch(/^\\d+\\.\\d+\\.\\d+-hono-jsx$/)\n  })\n})\n\ndescribe('default export', () => {\n  ;[\n    'version',\n    'memo',\n    'Fragment',\n    'isValidElement',\n    'createElement',\n    'cloneElement',\n    'ErrorBoundary',\n    'createContext',\n    'useContext',\n    'useState',\n    'useEffect',\n    'useRef',\n    'useCallback',\n    'useReducer',\n    'useDebugValue',\n    'createRef',\n    'forwardRef',\n    'useImperativeHandle',\n    'useSyncExternalStore',\n    'use',\n    'startTransition',\n    'useTransition',\n    'useDeferredValue',\n    'startViewTransition',\n    'useViewTransition',\n    'useMemo',\n    'useLayoutEffect',\n    'useInsertionEffect',\n    'useActionState',\n    'useOptimistic',\n    'Suspense',\n    'StrictMode',\n  ].forEach((key) => {\n    it(key, () => {\n      expect((DefaultExport as any)[key]).toBeDefined()\n    })\n  })\n})\n"
  },
  {
    "path": "src/jsx/index.ts",
    "content": "/**\n * @module\n * JSX for Hono.\n */\n\nimport { Fragment, cloneElement, isValidElement, jsx, memo, reactAPICompatVersion } from './base'\nimport type { DOMAttributes } from './base'\nimport { Children } from './children'\nimport { ErrorBoundary } from './components'\nimport { createContext, useContext } from './context'\nimport { useActionState, useOptimistic } from './dom/hooks'\nimport {\n  createRef,\n  forwardRef,\n  startTransition,\n  startViewTransition,\n  use,\n  useCallback,\n  useDebugValue,\n  useDeferredValue,\n  useEffect,\n  useId,\n  useImperativeHandle,\n  useInsertionEffect,\n  useLayoutEffect,\n  useMemo,\n  useReducer,\n  useRef,\n  useState,\n  useSyncExternalStore,\n  useTransition,\n  useViewTransition,\n} from './hooks'\nimport { Suspense } from './streaming'\n\nexport {\n  reactAPICompatVersion as version,\n  jsx,\n  memo,\n  Fragment,\n  Fragment as StrictMode,\n  isValidElement,\n  jsx as createElement,\n  cloneElement,\n  ErrorBoundary,\n  createContext,\n  useContext,\n  useState,\n  useEffect,\n  useRef,\n  useCallback,\n  useReducer,\n  useId,\n  useDebugValue,\n  use,\n  startTransition,\n  useTransition,\n  useDeferredValue,\n  startViewTransition,\n  useViewTransition,\n  useMemo,\n  useLayoutEffect,\n  useInsertionEffect,\n  createRef,\n  forwardRef,\n  useImperativeHandle,\n  useSyncExternalStore,\n  useActionState,\n  useOptimistic,\n  Suspense,\n  Children,\n  DOMAttributes,\n}\n\nexport default {\n  version: reactAPICompatVersion,\n  memo,\n  Fragment,\n  StrictMode: Fragment,\n  isValidElement,\n  createElement: jsx,\n  cloneElement,\n  ErrorBoundary,\n  createContext,\n  useContext,\n  useState,\n  useEffect,\n  useRef,\n  useCallback,\n  useReducer,\n  useId,\n  useDebugValue,\n  use,\n  startTransition,\n  useTransition,\n  useDeferredValue,\n  startViewTransition,\n  useViewTransition,\n  useMemo,\n  useLayoutEffect,\n  useInsertionEffect,\n  createRef,\n  forwardRef,\n  useImperativeHandle,\n  useSyncExternalStore,\n  useActionState,\n  useOptimistic,\n  Suspense,\n  Children,\n}\n\nexport type * from './types'\n\nexport type { JSX } from './intrinsic-elements'\n"
  },
  {
    "path": "src/jsx/intrinsic-element/common.ts",
    "content": "import type { Props } from '../base'\n\nexport const deDupeKeyMap: Record<string, string[]> = {\n  title: [],\n  script: ['src'],\n  style: ['data-href'],\n  link: ['href'],\n  meta: ['name', 'httpEquiv', 'charset', 'itemProp'],\n}\n\nexport const domRenderers: Record<string, Function> = {}\n\nexport const dataPrecedenceAttr = 'data-precedence'\n\nexport const isStylesheetLinkWithPrecedence = (props: Props): boolean =>\n  props.rel === 'stylesheet' && 'precedence' in props\n\nexport const shouldDeDupeByKey = (tagName: string, supportSort: boolean): boolean => {\n  if (tagName === 'link') {\n    return supportSort\n  }\n  return deDupeKeyMap[tagName].length > 0\n}\n"
  },
  {
    "path": "src/jsx/intrinsic-element/components.test.tsx",
    "content": "/** @jsxImportSource ../ */\nimport { useActionState } from '../'\n\ndescribe('intrinsic element', () => {\n  describe('document metadata', () => {\n    describe('title element', () => {\n      it('should be hoisted title tag', async () => {\n        const template = (\n          <html>\n            <head></head>\n            <body>\n              <title>Hello</title>\n              <h1>World</h1>\n            </body>\n          </html>\n        )\n        expect(template.toString()).toBe(\n          '<html><head><title>Hello</title></head><body><h1>World</h1></body></html>'\n        )\n      })\n    })\n\n    describe('link element', () => {\n      it('should be hoisted link tag', async () => {\n        const template = (\n          <html>\n            <head></head>\n            <body>\n              <link rel='stylesheet' href='style.css' precedence='default' />\n              <h1>World</h1>\n            </body>\n          </html>\n        )\n        expect(template.toString()).toBe(\n          '<html><head><link rel=\"stylesheet\" href=\"style.css\" data-precedence=\"default\"/></head><body><h1>World</h1></body></html>'\n        )\n      })\n\n      it('should be ordered by precedence attribute', () => {\n        const template = (\n          <html>\n            <head></head>\n            <body>\n              <link rel='stylesheet' href='style1.css' precedence='default' />\n              <link rel='stylesheet' href='style2.css' precedence='high' />\n              <link rel='stylesheet' href='style3.css' precedence='default' />\n              <h1>World</h1>\n            </body>\n          </html>\n        )\n        expect(template.toString()).toBe(\n          '<html><head><link rel=\"stylesheet\" href=\"style1.css\" data-precedence=\"default\"/><link rel=\"stylesheet\" href=\"style3.css\" data-precedence=\"default\"/><link rel=\"stylesheet\" href=\"style2.css\" data-precedence=\"high\"/></head><body><h1>World</h1></body></html>'\n        )\n      })\n\n      it('should be de-duped by href', () => {\n        const template = (\n          <html>\n            <head></head>\n            <body>\n              <link rel='stylesheet' href='style1.css' precedence='default' />\n              <link rel='stylesheet' href='style2.css' precedence='high' />\n              <link rel='stylesheet' href='style1.css' precedence='default' />\n              <h1>World</h1>\n            </body>\n          </html>\n        )\n        expect(template.toString()).toBe(\n          '<html><head><link rel=\"stylesheet\" href=\"style1.css\" data-precedence=\"default\"/><link rel=\"stylesheet\" href=\"style2.css\" data-precedence=\"high\"/></head><body><h1>World</h1></body></html>'\n        )\n      })\n\n      it('should be inserted as is if <head> is not present', () => {\n        const template = (\n          <html>\n            <body>\n              <link rel='stylesheet' href='style1.css' precedence='default' />\n              <link rel='stylesheet' href='style2.css' precedence='high' />\n              <h1>World</h1>\n            </body>\n          </html>\n        )\n        expect(template.toString()).toBe(\n          '<html><body><link rel=\"stylesheet\" href=\"style1.css\" data-precedence=\"default\"/><link rel=\"stylesheet\" href=\"style2.css\" data-precedence=\"high\"/><h1>World</h1></body></html>'\n        )\n      })\n\n      it('should not do special behavior if disabled is present', () => {\n        const template = (\n          <html>\n            <head></head>\n            <body>\n              <link rel='stylesheet' href='style1.css' precedence='default' />\n              <link rel='stylesheet' href='style2.css' precedence='default' disabled />\n              <h1>World</h1>\n            </body>\n          </html>\n        )\n        expect(template.toString()).toBe(\n          '<html><head><link rel=\"stylesheet\" href=\"style1.css\" data-precedence=\"default\"/></head><body><link rel=\"stylesheet\" href=\"style2.css\" precedence=\"default\" disabled=\"\"/><h1>World</h1></body></html>'\n        )\n      })\n\n      it('should not be hoisted if has no precedence attribute', () => {\n        const template = (\n          <html>\n            <head></head>\n            <body>\n              <link rel='stylesheet' href='style1.css' />\n              <h1>World</h1>\n            </body>\n          </html>\n        )\n        expect(template.toString()).toBe(\n          '<html><head></head><body><link rel=\"stylesheet\" href=\"style1.css\"/><h1>World</h1></body></html>'\n        )\n      })\n\n      describe('React 19 compatibility', () => {\n        const headContent = (html: string) => html.match(/<head>(.*)<\\/head>/)?.[1] ?? ''\n        const bodyContent = (html: string) => html.match(/<body>(.*)<\\/body>/)?.[1] ?? ''\n        const renderDocument = (children: unknown) =>\n          (\n            <html>\n              <head></head>\n              <body>{children}</body>\n            </html>\n          ).toString()\n        const assertHeadAndBody = (children: unknown, expectedHead: string, expectedBody = '') => {\n          const output = renderDocument(children)\n          expect(headContent(output)).toBe(expectedHead)\n          expect(bodyContent(output)).toBe(expectedBody)\n        }\n\n        const canonical = () => <link rel='canonical' href='https://example.com/en/about' />\n        const alternateEn = () => (\n          <link rel='alternate' hrefLang='en' href='https://example.com/en/about' />\n        )\n        const alternateJa = () => (\n          <link rel='alternate' hrefLang='ja' href='https://example.com/ja/about' />\n        )\n\n        it('should keep canonical and alternates in source order', () => {\n          assertHeadAndBody(\n            <>\n              {canonical()}\n              {alternateEn()}\n              {alternateJa()}\n            </>,\n            '<link rel=\"canonical\" href=\"https://example.com/en/about\"/><link rel=\"alternate\" hrefLang=\"en\" href=\"https://example.com/en/about\"/><link rel=\"alternate\" hrefLang=\"ja\" href=\"https://example.com/ja/about\"/>'\n          )\n        })\n\n        it('should keep alternate-canonical-alternate order', () => {\n          assertHeadAndBody(\n            <>\n              {alternateEn()}\n              {canonical()}\n              {alternateJa()}\n            </>,\n            '<link rel=\"alternate\" hrefLang=\"en\" href=\"https://example.com/en/about\"/><link rel=\"canonical\" href=\"https://example.com/en/about\"/><link rel=\"alternate\" hrefLang=\"ja\" href=\"https://example.com/ja/about\"/>'\n          )\n        })\n\n        it('should not de-dupe canonical links', () => {\n          assertHeadAndBody(\n            <>\n              {canonical()}\n              {canonical()}\n            </>,\n            '<link rel=\"canonical\" href=\"https://example.com/en/about\"/><link rel=\"canonical\" href=\"https://example.com/en/about\"/>'\n          )\n        })\n\n        it('should not de-dupe alternate links', () => {\n          assertHeadAndBody(\n            <>\n              {alternateEn()}\n              {alternateEn()}\n            </>,\n            '<link rel=\"alternate\" hrefLang=\"en\" href=\"https://example.com/en/about\"/><link rel=\"alternate\" hrefLang=\"en\" href=\"https://example.com/en/about\"/>'\n          )\n        })\n\n        it('should de-dupe stylesheet with precedence', () => {\n          assertHeadAndBody(\n            <>\n              <link rel='stylesheet' href='/style.css' precedence='default' />\n              <link rel='stylesheet' href='/style.css' precedence='default' />\n            </>,\n            '<link rel=\"stylesheet\" href=\"/style.css\" data-precedence=\"default\"/>'\n          )\n        })\n\n        it('should not de-dupe stylesheet against preload with same href', () => {\n          assertHeadAndBody(\n            <>\n              <link rel='preload' href='/style.css' as='style' />\n              <link rel='stylesheet' href='/style.css' precedence='default' />\n              <link rel='stylesheet' href='/style.css' precedence='default' />\n            </>,\n            '<link rel=\"stylesheet\" href=\"/style.css\" data-precedence=\"default\"/><link rel=\"preload\" href=\"/style.css\" as=\"style\"/>'\n          )\n        })\n\n        it('should not hoist stylesheet without precedence', () => {\n          assertHeadAndBody(\n            <>\n              <link rel='stylesheet' href='/style.css' />\n              <link rel='stylesheet' href='/style.css' />\n            </>,\n            '',\n            '<link rel=\"stylesheet\" href=\"/style.css\"/><link rel=\"stylesheet\" href=\"/style.css\"/>'\n          )\n        })\n\n        it('should keep different stylesheets with same precedence', () => {\n          assertHeadAndBody(\n            <>\n              <link rel='stylesheet' href='/a.css' precedence='default' />\n              <link rel='stylesheet' href='/b.css' precedence='default' />\n            </>,\n            '<link rel=\"stylesheet\" href=\"/a.css\" data-precedence=\"default\"/><link rel=\"stylesheet\" href=\"/b.css\" data-precedence=\"default\"/>'\n          )\n        })\n\n        it('should not de-dupe preload links', () => {\n          assertHeadAndBody(\n            <>\n              <link rel='preload' href='/font.woff2' as='font' crossOrigin='' />\n              <link rel='preload' href='/font.woff2' as='font' crossOrigin='' />\n            </>,\n            '<link rel=\"preload\" href=\"/font.woff2\" as=\"font\" crossorigin=\"\"/><link rel=\"preload\" href=\"/font.woff2\" as=\"font\" crossorigin=\"\"/>'\n          )\n        })\n\n        it('should not de-dupe modulepreload links', () => {\n          assertHeadAndBody(\n            <>\n              <link rel='modulepreload' href='/module.js' />\n              <link rel='modulepreload' href='/module.js' />\n            </>,\n            '<link rel=\"modulepreload\" href=\"/module.js\"/><link rel=\"modulepreload\" href=\"/module.js\"/>'\n          )\n        })\n\n        it('should keep links from two components', () => {\n          const Head = () => (\n            <>\n              <link rel='canonical' href='https://example.com/en/about' />\n              <link rel='alternate' hrefLang='en' href='https://example.com/en/about' />\n              <link rel='alternate' hrefLang='ja' href='https://example.com/ja/about' />\n            </>\n          )\n          const Body = () => (\n            <>\n              <link rel='canonical' href='https://example.com/en/about' />\n              <link rel='alternate' hrefLang='en' href='https://example.com/en/about' />\n              <link rel='alternate' hrefLang='ja' href='https://example.com/ja/about' />\n            </>\n          )\n          assertHeadAndBody(\n            <>\n              <Head />\n              <Body />\n            </>,\n            '<link rel=\"canonical\" href=\"https://example.com/en/about\"/><link rel=\"alternate\" hrefLang=\"en\" href=\"https://example.com/en/about\"/><link rel=\"alternate\" hrefLang=\"ja\" href=\"https://example.com/ja/about\"/><link rel=\"canonical\" href=\"https://example.com/en/about\"/><link rel=\"alternate\" hrefLang=\"en\" href=\"https://example.com/en/about\"/><link rel=\"alternate\" hrefLang=\"ja\" href=\"https://example.com/ja/about\"/>'\n          )\n        })\n\n        it('should hoist from deep nested component and keep duplicates', () => {\n          const Nested = () => (\n            <div>\n              <div>\n                <link rel='canonical' href='https://example.com/en/about' />\n              </div>\n            </div>\n          )\n          assertHeadAndBody(\n            <>\n              <link rel='canonical' href='https://example.com/en/about' />\n              <Nested />\n            </>,\n            '<link rel=\"canonical\" href=\"https://example.com/en/about\"/><link rel=\"canonical\" href=\"https://example.com/en/about\"/>',\n            '<div><div></div></div>'\n          )\n        })\n\n        it('should keep mixed links and de-dupe only stylesheet', () => {\n          assertHeadAndBody(\n            <>\n              <link rel='canonical' href='https://example.com/en/about' />\n              <link rel='alternate' hrefLang='en' href='https://example.com/en/about' />\n              <link rel='stylesheet' href='/style.css' precedence='default' />\n              <link rel='preload' href='/font.woff2' as='font' crossOrigin='' />\n              <link rel='canonical' href='https://example.com/en/about' />\n              <link rel='alternate' hrefLang='en' href='https://example.com/en/about' />\n              <link rel='stylesheet' href='/style.css' precedence='default' />\n              <link rel='preload' href='/font.woff2' as='font' crossOrigin='' />\n            </>,\n            '<link rel=\"stylesheet\" href=\"/style.css\" data-precedence=\"default\"/><link rel=\"canonical\" href=\"https://example.com/en/about\"/><link rel=\"alternate\" hrefLang=\"en\" href=\"https://example.com/en/about\"/><link rel=\"preload\" href=\"/font.woff2\" as=\"font\" crossorigin=\"\"/><link rel=\"canonical\" href=\"https://example.com/en/about\"/><link rel=\"alternate\" hrefLang=\"en\" href=\"https://example.com/en/about\"/><link rel=\"preload\" href=\"/font.woff2\" as=\"font\" crossorigin=\"\"/>'\n          )\n        })\n      })\n    })\n\n    describe('meta element', () => {\n      it('should be hoisted meta tag', async () => {\n        const template = (\n          <html>\n            <head></head>\n            <body>\n              <meta name='description' content='Hello' />\n              <h1>World</h1>\n            </body>\n          </html>\n        )\n        expect(template.toString()).toBe(\n          '<html><head><meta name=\"description\" content=\"Hello\"/></head><body><h1>World</h1></body></html>'\n        )\n      })\n\n      it('should be de-duped by name', () => {\n        const template = (\n          <html>\n            <head></head>\n            <body>\n              <meta name='description' content='Hello' />\n              <meta name='description' content='World' />\n              <h1>World</h1>\n            </body>\n          </html>\n        )\n        expect(template.toString()).toBe(\n          '<html><head><meta name=\"description\" content=\"Hello\"/></head><body><h1>World</h1></body></html>'\n        )\n      })\n\n      it('should not do special behavior if itemProp is present', () => {\n        const template = (\n          <html>\n            <head></head>\n            <body>\n              <meta name='description' content='Hello' itemProp='test' />\n              <h1>World</h1>\n            </body>\n          </html>\n        )\n        expect(template.toString()).toBe(\n          '<html><head></head><body><meta name=\"description\" content=\"Hello\" itemprop=\"test\"/><h1>World</h1></body></html>'\n        )\n      })\n    })\n\n    describe('script element', () => {\n      it('should be hoisted script tag', async () => {\n        const template = (\n          <html>\n            <head></head>\n            <body>\n              <script src='script.js' async={true} />\n              <h1>World</h1>\n            </body>\n          </html>\n        )\n        expect(template.toString()).toBe(\n          '<html><head><script src=\"script.js\" async=\"\"></script></head><body><h1>World</h1></body></html>'\n        )\n      })\n\n      it('should be de-duped by href with async={true}', () => {\n        const template = (\n          <html>\n            <head></head>\n            <body>\n              <script src='script.js' async />\n              <script src='script.js' async />\n              <h1>World</h1>\n            </body>\n          </html>\n        )\n        expect(template.toString()).toBe(\n          '<html><head><script src=\"script.js\" async=\"\"></script></head><body><h1>World</h1></body></html>'\n        )\n      })\n\n      it('should be omitted \"blocking\", \"onLoad\" and \"onError\" props', () => {\n        const template = (\n          <html>\n            <head></head>\n            <body>\n              <script\n                src='script.js'\n                async={true}\n                onLoad={() => {}}\n                onError={() => {}}\n                blocking='render'\n              />\n              <h1>World</h1>\n            </body>\n          </html>\n        )\n        expect(template.toString()).toBe(\n          '<html><head><script src=\"script.js\" async=\"\"></script></head><body><h1>World</h1></body></html>'\n        )\n      })\n\n      it('should not do special behavior if async is not present', () => {\n        const template = (\n          <html>\n            <head></head>\n            <body>\n              <script src='script.js' />\n              <h1>World</h1>\n            </body>\n          </html>\n        )\n        expect(template.toString()).toBe(\n          '<html><head></head><body><script src=\"script.js\"></script><h1>World</h1></body></html>'\n        )\n      })\n    })\n\n    describe('style element', () => {\n      it('should be hoisted style tag', async () => {\n        const template = (\n          <html>\n            <head></head>\n            <body>\n              <style href='red' precedence='default'>\n                {'body { color: red; }'}\n              </style>\n              <h1>World</h1>\n            </body>\n          </html>\n        )\n        expect(template.toString()).toBe(\n          '<html><head><style data-href=\"red\" data-precedence=\"default\">body { color: red; }</style></head><body><h1>World</h1></body></html>'\n        )\n      })\n\n      it('should be sorted by precedence attribute', async () => {\n        const template = (\n          <html>\n            <head></head>\n            <body>\n              <style href='red' precedence='default'>\n                {'body { color: red; }'}\n              </style>\n              <style href='green' precedence='high'>\n                {'body { color: green; }'}\n              </style>\n              <style href='blue' precedence='default'>\n                {'body { color: blue; }'}\n              </style>\n              <h1>World</h1>\n            </body>\n          </html>\n        )\n        expect(template.toString()).toBe(\n          '<html><head><style data-href=\"red\" data-precedence=\"default\">body { color: red; }</style><style data-href=\"blue\" data-precedence=\"default\">body { color: blue; }</style><style data-href=\"green\" data-precedence=\"high\">body { color: green; }</style></head><body><h1>World</h1></body></html>'\n        )\n      })\n\n      it('should not be hoisted if href is not present', () => {\n        const template = (\n          <html>\n            <head></head>\n            <body>\n              <style>{'body { color: red; }'}</style>\n              <h1>World</h1>\n            </body>\n          </html>\n        )\n        expect(template.toString()).toBe(\n          '<html><head></head><body><style>body { color: red; }</style><h1>World</h1></body></html>'\n        )\n      })\n    })\n  })\n\n  describe('form element', () => {\n    it('should be omitted \"action\" prop if it is a function', () => {\n      const template = (\n        <html>\n          <head></head>\n          <body>\n            <form action={() => {}} method='get'>\n              <input type='text' />\n            </form>\n          </body>\n        </html>\n      )\n      expect(template.toString()).toBe(\n        '<html><head></head><body><form method=\"get\"><input type=\"text\"/></form></body></html>'\n      )\n    })\n\n    it('should be rendered permalink', () => {\n      const [, action] = useActionState(() => {}, {}, 'permalink')\n      const template = (\n        <html>\n          <head></head>\n          <body>\n            <form action={action} method='get'>\n              <input type='text' />\n            </form>\n          </body>\n        </html>\n      )\n      expect(template.toString()).toBe(\n        '<html><head></head><body><form action=\"permalink\" method=\"get\"><input type=\"text\"/></form></body></html>'\n      )\n    })\n\n    it('should not do special behavior if action is a string', () => {\n      const template = (\n        <html>\n          <head></head>\n          <body>\n            <form action='/entries' method='get'>\n              <input type='text' />\n            </form>\n          </body>\n        </html>\n      )\n      expect(template.toString()).toBe(\n        '<html><head></head><body><form action=\"/entries\" method=\"get\"><input type=\"text\"/></form></body></html>'\n      )\n    })\n\n    it('should not do special behavior if no action prop', () => {\n      const template = (\n        <html>\n          <head></head>\n          <body>\n            <form>\n              <input type='text' />\n            </form>\n          </body>\n        </html>\n      )\n      expect(template.toString()).toBe(\n        '<html><head></head><body><form><input type=\"text\"/></form></body></html>'\n      )\n    })\n\n    describe('input element', () => {\n      it('should be rendered as is', () => {\n        const template = <input type='text' />\n        expect(template.toString()).toBe('<input type=\"text\"/>')\n      })\n\n      it('should be omitted \"formAction\" prop if it is a function', () => {\n        const template = (\n          <html>\n            <head></head>\n            <body>\n              <input type='text' formAction={() => {}} />\n            </body>\n          </html>\n        )\n        expect(template.toString()).toBe(\n          '<html><head></head><body><input type=\"text\"/></body></html>'\n        )\n      })\n\n      it('should be rendered permalink', () => {\n        const [, formAction] = useActionState(() => {}, {}, 'permalink')\n        const template = (\n          <html>\n            <head></head>\n            <body>\n              <input type='text' formAction={formAction} />\n            </body>\n          </html>\n        )\n        expect(template.toString()).toBe(\n          '<html><head></head><body><input type=\"text\" formaction=\"permalink\"/></body></html>'\n        )\n      })\n    })\n  })\n})\n\nexport {}\n"
  },
  {
    "path": "src/jsx/intrinsic-element/components.ts",
    "content": "import { raw } from '../../helper/html'\nimport type { HtmlEscapedCallback, HtmlEscapedString } from '../../utils/html'\nimport { JSXNode, getNameSpaceContext } from '../base'\nimport type { Child, Props } from '../base'\nimport { toArray } from '../children'\nimport { PERMALINK } from '../constants'\nimport { useContext } from '../context'\nimport type { IntrinsicElements } from '../intrinsic-elements'\nimport type { FC, PropsWithChildren } from '../types'\nimport {\n  dataPrecedenceAttr,\n  deDupeKeyMap,\n  isStylesheetLinkWithPrecedence,\n  shouldDeDupeByKey,\n} from './common'\n\nconst metaTagMap: WeakMap<\n  object,\n  Record<string, [string, Props, string | undefined][]>\n> = new WeakMap()\nconst insertIntoHead: (\n  tagName: string,\n  tag: string,\n  props: Props,\n  precedence: string | undefined\n) => HtmlEscapedCallback =\n  (tagName, tag, props, precedence) =>\n  ({ buffer, context }): undefined => {\n    if (!buffer) {\n      return\n    }\n    const map = metaTagMap.get(context) || {}\n    metaTagMap.set(context, map)\n    const tags = (map[tagName] ||= [])\n\n    let duped = false\n    const deDupeKeys = deDupeKeyMap[tagName]\n    const deDupeByKey = shouldDeDupeByKey(tagName, precedence !== undefined)\n    if (deDupeByKey) {\n      LOOP: for (const [, tagProps] of tags) {\n        if (\n          tagName === 'link' &&\n          !(tagProps.rel === 'stylesheet' && tagProps[dataPrecedenceAttr] !== undefined)\n        ) {\n          continue\n        }\n        for (const key of deDupeKeys) {\n          if ((tagProps?.[key] ?? null) === props?.[key]) {\n            duped = true\n            break LOOP\n          }\n        }\n      }\n    }\n\n    if (duped) {\n      buffer[0] = buffer[0].replaceAll(tag, '')\n    } else if (deDupeByKey || tagName === 'link') {\n      tags.push([tag, props, precedence])\n    } else {\n      tags.unshift([tag, props, precedence])\n    }\n\n    if (buffer[0].indexOf('</head>') !== -1) {\n      let insertTags\n      if (tagName === 'link' || precedence !== undefined) {\n        const precedences: string[] = []\n        insertTags = tags\n          .map(([tag, , tagPrecedence], index) => {\n            if (tagPrecedence === undefined) {\n              return [tag, Number.MAX_SAFE_INTEGER, index] as [string, number, number]\n            }\n            let order = precedences.indexOf(tagPrecedence as string)\n            if (order === -1) {\n              precedences.push(tagPrecedence as string)\n              order = precedences.length - 1\n            }\n            return [tag, order, index] as [string, number, number]\n          })\n          .sort((a, b) => a[1] - b[1] || a[2] - b[2])\n          .map(([tag]) => tag)\n      } else {\n        insertTags = tags.map(([tag]) => tag)\n      }\n\n      insertTags.forEach((tag) => {\n        buffer[0] = buffer[0].replaceAll(tag, '')\n      })\n      buffer[0] = buffer[0].replace(/(?=<\\/head>)/, insertTags.join(''))\n    }\n  }\n\nconst returnWithoutSpecialBehavior = (tag: string, children: Child, props: Props) =>\n  raw(new JSXNode(tag, props, toArray(children ?? [])).toString())\n\nconst documentMetadataTag = (tag: string, children: Child, props: Props, sort: boolean) => {\n  if ('itemProp' in props) {\n    return returnWithoutSpecialBehavior(tag, children, props)\n  }\n\n  let { precedence, blocking, ...restProps } = props\n  precedence = sort ? (precedence ?? '') : undefined\n  if (sort) {\n    restProps[dataPrecedenceAttr] = precedence\n  }\n\n  const string = new JSXNode(tag, restProps, toArray(children || [])).toString()\n\n  if (string instanceof Promise) {\n    return string.then((resString) =>\n      raw(string, [\n        ...((resString as HtmlEscapedString).callbacks || []),\n        insertIntoHead(tag, resString, restProps, precedence),\n      ])\n    )\n  } else {\n    return raw(string, [insertIntoHead(tag, string, restProps, precedence)])\n  }\n}\n\nexport const title: FC<PropsWithChildren> = ({ children, ...props }) => {\n  const nameSpaceContext = getNameSpaceContext()\n  if (nameSpaceContext) {\n    const context = useContext(nameSpaceContext)\n    if (context === 'svg' || context === 'head') {\n      return new JSXNode(\n        'title',\n        props,\n        toArray(children ?? []) as Child[]\n      ) as unknown as HtmlEscapedString\n    }\n  }\n\n  return documentMetadataTag('title', children, props, false)\n}\nexport const script: FC<PropsWithChildren<IntrinsicElements['script']>> = ({\n  children,\n  ...props\n}) => {\n  const nameSpaceContext = getNameSpaceContext()\n  if (\n    ['src', 'async'].some((k) => !props[k]) ||\n    (nameSpaceContext && useContext(nameSpaceContext) === 'head')\n  ) {\n    return returnWithoutSpecialBehavior('script', children, props)\n  }\n\n  return documentMetadataTag('script', children, props, false)\n}\n\nexport const style: FC<PropsWithChildren<IntrinsicElements['style']>> = ({\n  children,\n  ...props\n}) => {\n  if (!['href', 'precedence'].every((k) => k in props)) {\n    return returnWithoutSpecialBehavior('style', children, props)\n  }\n  props['data-href'] = props.href\n  delete props.href\n  return documentMetadataTag('style', children, props, true)\n}\nexport const link: FC<PropsWithChildren<IntrinsicElements['link']>> = ({ children, ...props }) => {\n  if (\n    ['onLoad', 'onError'].some((k) => k in props) ||\n    (props.rel === 'stylesheet' && (!('precedence' in props) || 'disabled' in props))\n  ) {\n    return returnWithoutSpecialBehavior('link', children, props)\n  }\n  return documentMetadataTag('link', children, props, isStylesheetLinkWithPrecedence(props))\n}\nexport const meta: FC<PropsWithChildren> = ({ children, ...props }) => {\n  const nameSpaceContext = getNameSpaceContext()\n  if (nameSpaceContext && useContext(nameSpaceContext) === 'head') {\n    return returnWithoutSpecialBehavior('meta', children, props)\n  }\n  return documentMetadataTag('meta', children, props, false)\n}\n\nconst newJSXNode = (tag: string, { children, ...props }: PropsWithChildren<unknown>) =>\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  new JSXNode(tag, props, toArray(children ?? []) as Child[]) as any\nexport const form: FC<\n  PropsWithChildren<{\n    action?: Function | string\n    method?: 'get' | 'post'\n  }>\n> = (props) => {\n  if (typeof props.action === 'function') {\n    props.action = PERMALINK in props.action ? (props.action[PERMALINK] as string) : undefined\n  }\n  return newJSXNode('form', props)\n}\n\nconst formActionableElement = (\n  tag: string,\n  props: PropsWithChildren<{\n    formAction?: Function | string\n  }>\n) => {\n  if (typeof props.formAction === 'function') {\n    props.formAction =\n      PERMALINK in props.formAction ? (props.formAction[PERMALINK] as string) : undefined\n  }\n  return newJSXNode(tag, props)\n}\n\nexport const input: (props: PropsWithChildren) => unknown = (props) =>\n  formActionableElement('input', props)\nexport const button: (props: PropsWithChildren) => unknown = (props) =>\n  formActionableElement('button', props)\n"
  },
  {
    "path": "src/jsx/intrinsic-elements.ts",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport type { BaseMime } from '../utils/mime'\nimport type { StringLiteralUnion } from '../utils/types'\n\n/**\n * This code is based on React.\n * https://github.com/facebook/react\n * MIT License\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n */\n\n// eslint-disable-next-line @typescript-eslint/no-namespace\nexport namespace JSX {\n  export type CrossOrigin = 'anonymous' | 'use-credentials' | '' | undefined\n  export interface CSSProperties {\n    [propertyKey: string]: unknown\n  }\n  type AnyAttributes = { [attributeName: string]: any }\n\n  interface JSXAttributes {\n    dangerouslySetInnerHTML?: {\n      __html: string\n    }\n  }\n\n  interface EventAttributes {\n    onScroll?: (event: Event) => void\n    onScrollCapture?: (event: Event) => void\n    onScrollEnd?: (event: Event) => void\n    onScrollEndCapture?: (event: Event) => void\n    onWheel?: (event: WheelEvent) => void\n    onWheelCapture?: (event: WheelEvent) => void\n    onAnimationCancel?: (event: AnimationEvent) => void\n    onAnimationCancelCapture?: (event: AnimationEvent) => void\n    onAnimationEnd?: (event: AnimationEvent) => void\n    onAnimationEndCapture?: (event: AnimationEvent) => void\n    onAnimationIteration?: (event: AnimationEvent) => void\n    onAnimationIterationCapture?: (event: AnimationEvent) => void\n    onAnimationStart?: (event: AnimationEvent) => void\n    onAnimationStartCapture?: (event: AnimationEvent) => void\n    onCopy?: (event: ClipboardEvent) => void\n    onCopyCapture?: (event: ClipboardEvent) => void\n    onCut?: (event: ClipboardEvent) => void\n    onCutCapture?: (event: ClipboardEvent) => void\n    onPaste?: (event: ClipboardEvent) => void\n    onPasteCapture?: (event: ClipboardEvent) => void\n    onCompositionEnd?: (event: CompositionEvent) => void\n    onCompositionEndCapture?: (event: CompositionEvent) => void\n    onCompositionStart?: (event: CompositionEvent) => void\n    onCompositionStartCapture?: (event: CompositionEvent) => void\n    onCompositionUpdate?: (event: CompositionEvent) => void\n    onCompositionUpdateCapture?: (event: CompositionEvent) => void\n    onBlur?: (event: FocusEvent) => void\n    onBlurCapture?: (event: FocusEvent) => void\n    onFocus?: (event: FocusEvent) => void\n    onFocusCapture?: (event: FocusEvent) => void\n    onFocusIn?: (event: FocusEvent) => void\n    onFocusInCapture?: (event: FocusEvent) => void\n    onFocusOut?: (event: FocusEvent) => void\n    onFocusOutCapture?: (event: FocusEvent) => void\n    onFullscreenChange?: (event: Event) => void\n    onFullscreenChangeCapture?: (event: Event) => void\n    onFullscreenError?: (event: Event) => void\n    onFullscreenErrorCapture?: (event: Event) => void\n    onKeyDown?: (event: KeyboardEvent) => void\n    onKeyDownCapture?: (event: KeyboardEvent) => void\n    onKeyPress?: (event: KeyboardEvent) => void\n    onKeyPressCapture?: (event: KeyboardEvent) => void\n    onKeyUp?: (event: KeyboardEvent) => void\n    onKeyUpCapture?: (event: KeyboardEvent) => void\n    onAuxClick?: (event: MouseEvent) => void\n    onAuxClickCapture?: (event: MouseEvent) => void\n    onClick?: (event: MouseEvent) => void\n    onClickCapture?: (event: MouseEvent) => void\n    onContextMenu?: (event: MouseEvent) => void\n    onContextMenuCapture?: (event: MouseEvent) => void\n    onDoubleClick?: (event: MouseEvent) => void\n    onDoubleClickCapture?: (event: MouseEvent) => void\n    onMouseDown?: (event: MouseEvent) => void\n    onMouseDownCapture?: (event: MouseEvent) => void\n    onMouseEnter?: (event: MouseEvent) => void\n    onMouseEnterCapture?: (event: MouseEvent) => void\n    onMouseLeave?: (event: MouseEvent) => void\n    onMouseLeaveCapture?: (event: MouseEvent) => void\n    onMouseMove?: (event: MouseEvent) => void\n    onMouseMoveCapture?: (event: MouseEvent) => void\n    onMouseOut?: (event: MouseEvent) => void\n    onMouseOutCapture?: (event: MouseEvent) => void\n    onMouseOver?: (event: MouseEvent) => void\n    onMouseOverCapture?: (event: MouseEvent) => void\n    onMouseUp?: (event: MouseEvent) => void\n    onMouseUpCapture?: (event: MouseEvent) => void\n    onMouseWheel?: (event: WheelEvent) => void\n    onMouseWheelCapture?: (event: WheelEvent) => void\n    onGotPointerCapture?: (event: PointerEvent) => void\n    onGotPointerCaptureCapture?: (event: PointerEvent) => void\n    onLostPointerCapture?: (event: PointerEvent) => void\n    onLostPointerCaptureCapture?: (event: PointerEvent) => void\n    onPointerCancel?: (event: PointerEvent) => void\n    onPointerCancelCapture?: (event: PointerEvent) => void\n    onPointerDown?: (event: PointerEvent) => void\n    onPointerDownCapture?: (event: PointerEvent) => void\n    onPointerEnter?: (event: PointerEvent) => void\n    onPointerEnterCapture?: (event: PointerEvent) => void\n    onPointerLeave?: (event: PointerEvent) => void\n    onPointerLeaveCapture?: (event: PointerEvent) => void\n    onPointerMove?: (event: PointerEvent) => void\n    onPointerMoveCapture?: (event: PointerEvent) => void\n    onPointerOut?: (event: PointerEvent) => void\n    onPointerOutCapture?: (event: PointerEvent) => void\n    onPointerOver?: (event: PointerEvent) => void\n    onPointerOverCapture?: (event: PointerEvent) => void\n    onPointerUp?: (event: PointerEvent) => void\n    onPointerUpCapture?: (event: PointerEvent) => void\n    onTouchCancel?: (event: TouchEvent) => void\n    onTouchCancelCapture?: (event: TouchEvent) => void\n    onTouchEnd?: (event: TouchEvent) => void\n    onTouchEndCapture?: (event: TouchEvent) => void\n    onTouchMove?: (event: TouchEvent) => void\n    onTouchMoveCapture?: (event: TouchEvent) => void\n    onTouchStart?: (event: TouchEvent) => void\n    onTouchStartCapture?: (event: TouchEvent) => void\n    onTransitionCancel?: (event: TransitionEvent) => void\n    onTransitionCancelCapture?: (event: TransitionEvent) => void\n    onTransitionEnd?: (event: TransitionEvent) => void\n    onTransitionEndCapture?: (event: TransitionEvent) => void\n    onTransitionRun?: (event: TransitionEvent) => void\n    onTransitionRunCapture?: (event: TransitionEvent) => void\n    onTransitionStart?: (event: TransitionEvent) => void\n    onTransitionStartCapture?: (event: TransitionEvent) => void\n    onFormData?: (event: FormDataEvent) => void\n    onFormDataCapture?: (event: FormDataEvent) => void\n    onReset?: (event: Event) => void\n    onResetCapture?: (event: Event) => void\n    onSubmit?: (event: Event) => void\n    onSubmitCapture?: (event: Event) => void\n    onInvalid?: (event: Event) => void\n    onInvalidCapture?: (event: Event) => void\n    onSelect?: (event: Event) => void\n    onSelectCapture?: (event: Event) => void\n    onSelectChange?: (event: Event) => void\n    onSelectChangeCapture?: (event: Event) => void\n    onInput?: (event: InputEvent) => void\n    onInputCapture?: (event: InputEvent) => void\n    onBeforeInput?: (event: InputEvent) => void\n    onBeforeInputCapture?: (event: InputEvent) => void\n    onChange?: (event: Event) => void\n    onChangeCapture?: (event: Event) => void\n  }\n\n  export interface HTMLAttributes extends JSXAttributes, EventAttributes, AnyAttributes {\n    accesskey?: string | undefined\n    autocapitalize?: 'off' | 'none' | 'on' | 'sentences' | 'words' | 'characters' | undefined\n    autofocus?: boolean | undefined\n    class?: string | Promise<string> | undefined\n    contenteditable?: boolean | 'inherit' | 'plaintext-only' | undefined\n    contextmenu?: string | undefined\n    dir?: string | undefined\n    draggable?: 'true' | 'false' | boolean | undefined\n    enterkeyhint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send' | undefined\n    hidden?: boolean | undefined\n    id?: string | undefined\n    inert?: boolean | undefined\n    inputmode?:\n      | 'none'\n      | 'text'\n      | 'tel'\n      | 'url'\n      | 'email'\n      | 'numeric'\n      | 'decimal'\n      | 'search'\n      | undefined\n    is?: string | undefined\n    itemid?: string | undefined\n    itemprop?: string | undefined\n    itemref?: string | undefined\n    itemscope?: boolean | undefined\n    itemtype?: string | undefined\n    lang?: string | undefined\n    nonce?: string | undefined\n    placeholder?: string | undefined\n    /** @see https://developer.mozilla.org/en-US/docs/Web/API/Popover_API */\n    popover?: boolean | 'auto' | 'manual' | undefined\n    slot?: string | undefined\n    spellcheck?: boolean | undefined\n    style?: CSSProperties | string | undefined\n    tabindex?: number | undefined\n    title?: string | undefined\n    translate?: 'yes' | 'no' | undefined\n    itemProp?: string | undefined\n  }\n\n  type HTMLAttributeReferrerPolicy =\n    | ''\n    | 'no-referrer'\n    | 'no-referrer-when-downgrade'\n    | 'origin'\n    | 'origin-when-cross-origin'\n    | 'same-origin'\n    | 'strict-origin'\n    | 'strict-origin-when-cross-origin'\n    | 'unsafe-url'\n\n  type HTMLAttributeAnchorTarget = StringLiteralUnion<'_self' | '_blank' | '_parent' | '_top'>\n\n  interface AnchorHTMLAttributes extends HTMLAttributes {\n    download?: string | boolean | undefined\n    href?: string | undefined\n    hreflang?: string | undefined\n    media?: string | undefined\n    ping?: string | undefined\n    target?: HTMLAttributeAnchorTarget | undefined\n    type?: StringLiteralUnion<BaseMime> | undefined\n    referrerpolicy?: HTMLAttributeReferrerPolicy | undefined\n  }\n\n  interface AudioHTMLAttributes extends MediaHTMLAttributes {}\n\n  interface AreaHTMLAttributes extends HTMLAttributes {\n    alt?: string | undefined\n    coords?: string | undefined\n    download?: string | boolean | undefined\n    href?: string | undefined\n    hreflang?: string | undefined\n    media?: string | undefined\n    referrerpolicy?: HTMLAttributeReferrerPolicy | undefined\n    shape?: string | undefined\n    target?: HTMLAttributeAnchorTarget | undefined\n  }\n\n  interface BaseHTMLAttributes extends HTMLAttributes {\n    href?: string | undefined\n    target?: HTMLAttributeAnchorTarget | undefined\n  }\n\n  interface BlockquoteHTMLAttributes extends HTMLAttributes {\n    cite?: string | undefined\n  }\n\n  /** @see https://developer.mozilla.org/en-US/docs/Web/API/Popover_API */\n  type HTMLAttributePopoverTargetAction = 'show' | 'hide' | 'toggle'\n\n  interface ButtonHTMLAttributes extends HTMLAttributes {\n    disabled?: boolean | undefined\n    form?: string | undefined\n    formenctype?: HTMLAttributeFormEnctype | undefined\n    formmethod?: HTMLAttributeFormMethod | undefined\n    formnovalidate?: boolean | undefined\n    formtarget?: HTMLAttributeAnchorTarget | undefined\n    name?: string | undefined\n    type?: 'submit' | 'reset' | 'button' | undefined\n    value?: string | ReadonlyArray<string> | number | undefined\n    popovertarget?: string | undefined\n    popovertargetaction?: HTMLAttributePopoverTargetAction | undefined\n\n    // React 19 compatibility\n    formAction?: string | Function | undefined\n  }\n\n  interface CanvasHTMLAttributes extends HTMLAttributes {\n    height?: number | string | undefined\n    width?: number | string | undefined\n  }\n\n  interface ColHTMLAttributes extends HTMLAttributes {\n    span?: number | undefined\n    width?: number | string | undefined\n  }\n\n  interface ColgroupHTMLAttributes extends HTMLAttributes {\n    span?: number | undefined\n  }\n\n  interface DataHTMLAttributes extends HTMLAttributes {\n    value?: string | ReadonlyArray<string> | number | undefined\n  }\n\n  interface DetailsHTMLAttributes extends HTMLAttributes {\n    open?: boolean | undefined\n  }\n\n  interface DelHTMLAttributes extends HTMLAttributes {\n    cite?: string | undefined\n    dateTime?: string | undefined\n  }\n\n  interface DialogHTMLAttributes extends HTMLAttributes {\n    open?: boolean | undefined\n  }\n\n  interface EmbedHTMLAttributes extends HTMLAttributes {\n    height?: number | string | undefined\n    src?: string | undefined\n    type?: StringLiteralUnion<BaseMime> | undefined\n    width?: number | string | undefined\n  }\n\n  interface FieldsetHTMLAttributes extends HTMLAttributes {\n    disabled?: boolean | undefined\n    form?: string | undefined\n    name?: string | undefined\n  }\n\n  /** @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#method */\n  type HTMLAttributeFormMethod = 'get' | 'post' | 'dialog'\n  /** @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#enctype */\n  type HTMLAttributeFormEnctype =\n    | 'application/x-www-form-urlencoded'\n    | 'multipart/form-data'\n    | 'text/plain'\n  /** @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#autocomplete */\n  type HTMLAttributeFormAutocomplete = 'on' | 'off'\n\n  interface FormHTMLAttributes extends HTMLAttributes {\n    'accept-charset'?: StringLiteralUnion<'utf-8'> | undefined\n    autocomplete?: HTMLAttributeFormAutocomplete | undefined\n    enctype?: HTMLAttributeFormEnctype | undefined\n    method?: HTMLAttributeFormMethod | undefined\n    name?: string | undefined\n    novalidate?: boolean | undefined\n    target?: HTMLAttributeAnchorTarget | undefined\n\n    // React 19 compatibility\n    action?: string | Function | undefined\n  }\n\n  interface HtmlHTMLAttributes extends HTMLAttributes {\n    manifest?: string | undefined\n  }\n\n  interface IframeHTMLAttributes extends HTMLAttributes {\n    allow?: string | undefined\n    allowfullscreen?: boolean | undefined\n    height?: number | string | undefined\n    loading?: 'eager' | 'lazy' | undefined\n    name?: string | undefined\n    referrerpolicy?: HTMLAttributeReferrerPolicy | undefined\n    sandbox?: string | undefined\n    seamless?: boolean | undefined\n    src?: string | undefined\n    srcdoc?: string | undefined\n    width?: number | string | undefined\n  }\n\n  interface ImgHTMLAttributes extends HTMLAttributes {\n    alt?: string | undefined\n    crossorigin?: CrossOrigin\n    decoding?: 'async' | 'auto' | 'sync' | undefined\n    height?: number | string | undefined\n    loading?: 'eager' | 'lazy' | undefined\n    referrerpolicy?: HTMLAttributeReferrerPolicy | undefined\n    sizes?: string | undefined\n    src?: string | undefined\n    srcset?: string | undefined\n    usemap?: string | undefined\n    width?: number | string | undefined\n  }\n\n  interface InsHTMLAttributes extends HTMLAttributes {\n    cite?: string | undefined\n    datetime?: string | undefined\n  }\n\n  type HTMLInputTypeAttribute = StringLiteralUnion<\n    | 'button'\n    | 'checkbox'\n    | 'color'\n    | 'date'\n    | 'datetime-local'\n    | 'email'\n    | 'file'\n    | 'hidden'\n    | 'image'\n    | 'month'\n    | 'number'\n    | 'password'\n    | 'radio'\n    | 'range'\n    | 'reset'\n    | 'search'\n    | 'submit'\n    | 'tel'\n    | 'text'\n    | 'time'\n    | 'url'\n    | 'week'\n  >\n  type AutoFillAddressKind = 'billing' | 'shipping'\n  type AutoFillBase = '' | 'off' | 'on'\n  type AutoFillContactField =\n    | 'email'\n    | 'tel'\n    | 'tel-area-code'\n    | 'tel-country-code'\n    | 'tel-extension'\n    | 'tel-local'\n    | 'tel-local-prefix'\n    | 'tel-local-suffix'\n    | 'tel-national'\n  type AutoFillContactKind = 'home' | 'mobile' | 'work'\n  type AutoFillCredentialField = 'webauthn'\n  type AutoFillNormalField =\n    | 'additional-name'\n    | 'address-level1'\n    | 'address-level2'\n    | 'address-level3'\n    | 'address-level4'\n    | 'address-line1'\n    | 'address-line2'\n    | 'address-line3'\n    | 'bday-day'\n    | 'bday-month'\n    | 'bday-year'\n    | 'cc-csc'\n    | 'cc-exp'\n    | 'cc-exp-month'\n    | 'cc-exp-year'\n    | 'cc-family-name'\n    | 'cc-given-name'\n    | 'cc-name'\n    | 'cc-number'\n    | 'cc-type'\n    | 'country'\n    | 'country-name'\n    | 'current-password'\n    | 'family-name'\n    | 'given-name'\n    | 'honorific-prefix'\n    | 'honorific-suffix'\n    | 'name'\n    | 'new-password'\n    | 'one-time-code'\n    | 'organization'\n    | 'postal-code'\n    | 'street-address'\n    | 'transaction-amount'\n    | 'transaction-currency'\n    | 'username'\n  type OptionalPrefixToken<T extends string> = `${T} ` | ''\n  type OptionalPostfixToken<T extends string> = ` ${T}` | ''\n  type AutoFillField =\n    | AutoFillNormalField\n    | `${OptionalPrefixToken<AutoFillContactKind>}${AutoFillContactField}`\n  type AutoFillSection = `section-${string}`\n  type AutoFill =\n    | AutoFillBase\n    | `${OptionalPrefixToken<AutoFillSection>}${OptionalPrefixToken<AutoFillAddressKind>}${AutoFillField}${OptionalPostfixToken<AutoFillCredentialField>}`\n\n  interface InputHTMLAttributes extends HTMLAttributes {\n    accept?: string | undefined\n    alt?: string | undefined\n    autocomplete?: StringLiteralUnion<AutoFill> | undefined\n    capture?: boolean | 'user' | 'environment' | undefined // https://www.w3.org/TR/html-media-capture/#the-capture-attribute\n    checked?: boolean | undefined\n    disabled?: boolean | undefined\n    form?: string | undefined\n    formenctype?: HTMLAttributeFormEnctype | undefined\n    formmethod?: HTMLAttributeFormMethod | undefined\n    formnovalidate?: boolean | undefined\n    formtarget?: HTMLAttributeAnchorTarget | undefined\n    height?: number | string | undefined\n    list?: string | undefined\n    max?: number | string | undefined\n    maxlength?: number | undefined\n    min?: number | string | undefined\n    minlength?: number | undefined\n    multiple?: boolean | undefined\n    name?: string | undefined\n    pattern?: string | undefined\n    placeholder?: string | undefined\n    readonly?: boolean | undefined\n    required?: boolean | undefined\n    size?: number | undefined\n    src?: string | undefined\n    step?: number | string | undefined\n    type?: HTMLInputTypeAttribute | undefined\n    value?: string | ReadonlyArray<string> | number | undefined\n    width?: number | string | undefined\n    popovertarget?: string | undefined\n    popovertargetaction?: HTMLAttributePopoverTargetAction | undefined\n\n    // React 19 compatibility\n    formAction?: string | Function | undefined\n  }\n\n  interface KeygenHTMLAttributes extends HTMLAttributes {\n    challenge?: string | undefined\n    disabled?: boolean | undefined\n    form?: string | undefined\n    keytype?: string | undefined\n    name?: string | undefined\n  }\n\n  interface LabelHTMLAttributes extends HTMLAttributes {\n    form?: string | undefined\n    for?: string | undefined\n  }\n\n  interface LiHTMLAttributes extends HTMLAttributes {\n    value?: string | ReadonlyArray<string> | number | undefined\n  }\n\n  interface LinkHTMLAttributes extends HTMLAttributes {\n    as?: string | undefined\n    crossorigin?: CrossOrigin\n    href?: string | undefined\n    hreflang?: string | undefined\n    integrity?: string | undefined\n    media?: string | undefined\n    imagesrcset?: string | undefined\n    imagesizes?: string | undefined\n    referrerpolicy?: HTMLAttributeReferrerPolicy | undefined\n    sizes?: string | undefined\n    type?: StringLiteralUnion<BaseMime> | undefined\n    charSet?: string | undefined\n\n    // React 19 compatibility\n    rel?: string | undefined\n    precedence?: string | undefined\n    title?: string | undefined\n    disabled?: boolean | undefined\n    onError?: ((event: Event) => void) | undefined\n    onLoad?: ((event: Event) => void) | undefined\n    blocking?: 'render' | undefined\n  }\n\n  interface MapHTMLAttributes extends HTMLAttributes {\n    name?: string | undefined\n  }\n\n  interface MenuHTMLAttributes extends HTMLAttributes {\n    type?: string | undefined\n  }\n\n  interface MediaHTMLAttributes extends HTMLAttributes {\n    autoplay?: boolean | undefined\n    controls?: boolean | undefined\n    controlslist?: string | undefined\n    crossorigin?: CrossOrigin\n    loop?: boolean | undefined\n    mediagroup?: string | undefined\n    muted?: boolean | undefined\n    playsinline?: boolean | undefined\n    preload?: string | undefined\n    src?: string | undefined\n  }\n\n  /**\n   * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#http-equiv\n   */\n  type MetaHttpEquiv =\n    | 'content-security-policy'\n    | 'content-type'\n    | 'default-style'\n    | 'x-ua-compatible'\n    | 'refresh'\n  /**\n   * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name\n   */\n  type MetaName =\n    | 'application-name'\n    | 'author'\n    | 'description'\n    | 'generator'\n    | 'keywords'\n    | 'referrer'\n    | 'theme-color'\n    | 'color-scheme'\n    | 'viewport'\n    | 'creator'\n    | 'googlebot'\n    | 'publisher'\n    | 'robots'\n  /**\n   * @see https://ogp.me/\n   */\n  type MetaProperty =\n    | 'og:title'\n    | 'og:type'\n    | 'og:image'\n    | 'og:url'\n    | 'og:audio'\n    | 'og:description'\n    | 'og:determiner'\n    | 'og:locale'\n    | 'og:locale:alternate'\n    | 'og:site_name'\n    | 'og:video'\n    | 'og:image:url'\n    | 'og:image:secure_url'\n    | 'og:image:type'\n    | 'og:image:width'\n    | 'og:image:height'\n    | 'og:image:alt'\n  interface MetaHTMLAttributes extends HTMLAttributes {\n    charset?: StringLiteralUnion<'utf-8'> | undefined\n    'http-equiv'?: StringLiteralUnion<MetaHttpEquiv> | undefined\n    name?: StringLiteralUnion<MetaName> | undefined\n    media?: string | undefined\n    content?: string | undefined\n    property?: StringLiteralUnion<MetaProperty> | undefined\n\n    // React 19 compatibility\n    httpEquiv?: StringLiteralUnion<MetaHttpEquiv> | undefined\n  }\n\n  interface MeterHTMLAttributes extends HTMLAttributes {\n    form?: string | undefined\n    high?: number | undefined\n    low?: number | undefined\n    max?: number | string | undefined\n    min?: number | string | undefined\n    optimum?: number | undefined\n    value?: string | ReadonlyArray<string> | number | undefined\n  }\n\n  interface QuoteHTMLAttributes extends HTMLAttributes {\n    cite?: string | undefined\n  }\n\n  interface ObjectHTMLAttributes extends HTMLAttributes {\n    data?: string | undefined\n    form?: string | undefined\n    height?: number | string | undefined\n    name?: string | undefined\n    type?: StringLiteralUnion<BaseMime> | undefined\n    usemap?: string | undefined\n    width?: number | string | undefined\n  }\n\n  interface OlHTMLAttributes extends HTMLAttributes {\n    reversed?: boolean | undefined\n    start?: number | undefined\n    type?: '1' | 'a' | 'A' | 'i' | 'I' | undefined\n  }\n\n  interface OptgroupHTMLAttributes extends HTMLAttributes {\n    disabled?: boolean | undefined\n    label?: string | undefined\n  }\n\n  interface OptionHTMLAttributes extends HTMLAttributes {\n    disabled?: boolean | undefined\n    label?: string | undefined\n    selected?: boolean | undefined\n    value?: string | ReadonlyArray<string> | number | undefined\n  }\n\n  interface OutputHTMLAttributes extends HTMLAttributes {\n    form?: string | undefined\n    for?: string | undefined\n    name?: string | undefined\n  }\n\n  interface ParamHTMLAttributes extends HTMLAttributes {\n    name?: string | undefined\n    value?: string | ReadonlyArray<string> | number | undefined\n  }\n\n  interface ProgressHTMLAttributes extends HTMLAttributes {\n    max?: number | string | undefined\n    value?: string | ReadonlyArray<string> | number | undefined\n  }\n\n  interface SlotHTMLAttributes extends HTMLAttributes {\n    name?: string | undefined\n  }\n\n  interface ScriptHTMLAttributes extends HTMLAttributes {\n    async?: boolean | undefined\n    crossorigin?: CrossOrigin\n    defer?: boolean | undefined\n    integrity?: string | undefined\n    nomodule?: boolean | undefined\n    referrerpolicy?: HTMLAttributeReferrerPolicy | undefined\n    src?: string | undefined\n    /**\n     * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type\n     */\n    type?: StringLiteralUnion<'' | 'text/javascript' | 'importmap' | 'module'> | undefined\n\n    // React 19 compatibility\n    crossOrigin?: CrossOrigin\n    fetchPriority?: string | undefined\n    noModule?: boolean | undefined\n    referrer?: HTMLAttributeReferrerPolicy | undefined\n    onError?: ((event: Event) => void) | undefined\n    onLoad?: ((event: Event) => void) | undefined\n    blocking?: 'render' | undefined\n  }\n\n  interface SelectHTMLAttributes extends HTMLAttributes {\n    autocomplete?: string | undefined\n    disabled?: boolean | undefined\n    form?: string | undefined\n    multiple?: boolean | undefined\n    name?: string | undefined\n    required?: boolean | undefined\n    size?: number | undefined\n    value?: string | ReadonlyArray<string> | number | undefined\n  }\n\n  type MediaMime = BaseMime & (`image/${string}` | `audio/${string}` | `video/${string}`)\n  interface SourceHTMLAttributes extends HTMLAttributes {\n    height?: number | string | undefined\n    media?: string | undefined\n    sizes?: string | undefined\n    src?: string | undefined\n    srcset?: string | undefined\n    type?: StringLiteralUnion<MediaMime> | undefined\n    width?: number | string | undefined\n  }\n\n  interface StyleHTMLAttributes extends HTMLAttributes {\n    media?: string | undefined\n    scoped?: boolean | undefined\n    /**\n     * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style#type\n     */\n    type?: '' | 'text/css' | undefined\n\n    // React 19 compatibility\n    href?: string | undefined\n    precedence?: string | undefined\n    title?: string | undefined\n    disabled?: boolean | undefined\n    blocking?: 'render' | undefined\n  }\n\n  interface TableHTMLAttributes extends HTMLAttributes {\n    align?: 'left' | 'center' | 'right' | undefined\n    bgcolor?: string | undefined\n    border?: number | undefined\n    cellpadding?: number | string | undefined\n    cellspacing?: number | string | undefined\n    frame?: boolean | undefined\n    rules?: 'none' | 'groups' | 'rows' | 'columns' | 'all' | undefined\n    summary?: string | undefined\n    width?: number | string | undefined\n  }\n\n  interface TextareaHTMLAttributes extends HTMLAttributes {\n    autocomplete?: string | undefined\n    cols?: number | undefined\n    dirname?: string | undefined\n    disabled?: boolean | undefined\n    form?: string | undefined\n    maxlength?: number | undefined\n    minlength?: number | undefined\n    name?: string | undefined\n    placeholder?: string | undefined\n    readonly?: boolean | undefined\n    required?: boolean | undefined\n    rows?: number | undefined\n    value?: string | ReadonlyArray<string> | number | undefined\n    wrap?: string | undefined\n  }\n\n  interface TdHTMLAttributes extends HTMLAttributes {\n    align?: 'left' | 'center' | 'right' | 'justify' | 'char' | undefined\n    colspan?: number | undefined\n    headers?: string | undefined\n    rowspan?: number | undefined\n    scope?: string | undefined\n    abbr?: string | undefined\n    height?: number | string | undefined\n    width?: number | string | undefined\n    valign?: 'top' | 'middle' | 'bottom' | 'baseline' | undefined\n  }\n\n  interface ThHTMLAttributes extends HTMLAttributes {\n    align?: 'left' | 'center' | 'right' | 'justify' | 'char' | undefined\n    colspan?: number | undefined\n    headers?: string | undefined\n    rowspan?: number | undefined\n    scope?: 'row' | 'col' | 'rowgroup' | 'colgroup' | string | undefined\n    abbr?: string | undefined\n  }\n\n  interface TimeHTMLAttributes extends HTMLAttributes {\n    datetime?: string | undefined\n  }\n\n  interface TrackHTMLAttributes extends HTMLAttributes {\n    default?: boolean | undefined\n    kind?: string | undefined\n    label?: string | undefined\n    src?: string | undefined\n    srclang?: string | undefined\n  }\n\n  interface VideoHTMLAttributes extends MediaHTMLAttributes {\n    height?: number | string | undefined\n    playsinline?: boolean | undefined\n    poster?: string | undefined\n    width?: number | string | undefined\n    disablePictureInPicture?: boolean | undefined\n    disableRemotePlayback?: boolean | undefined\n  }\n\n  export interface IntrinsicElements {\n    a: AnchorHTMLAttributes\n    abbr: HTMLAttributes\n    address: HTMLAttributes\n    area: AreaHTMLAttributes\n    article: HTMLAttributes\n    aside: HTMLAttributes\n    audio: AudioHTMLAttributes\n    b: HTMLAttributes\n    base: BaseHTMLAttributes\n    bdi: HTMLAttributes\n    bdo: HTMLAttributes\n    big: HTMLAttributes\n    blockquote: BlockquoteHTMLAttributes\n    body: HTMLAttributes\n    br: HTMLAttributes\n    button: ButtonHTMLAttributes\n    canvas: CanvasHTMLAttributes\n    caption: HTMLAttributes\n    center: HTMLAttributes\n    cite: HTMLAttributes\n    code: HTMLAttributes\n    col: ColHTMLAttributes\n    colgroup: ColgroupHTMLAttributes\n    data: DataHTMLAttributes\n    datalist: HTMLAttributes\n    dd: HTMLAttributes\n    del: DelHTMLAttributes\n    details: DetailsHTMLAttributes\n    dfn: HTMLAttributes\n    dialog: DialogHTMLAttributes\n    div: HTMLAttributes\n    dl: HTMLAttributes\n    dt: HTMLAttributes\n    em: HTMLAttributes\n    embed: EmbedHTMLAttributes\n    fieldset: FieldsetHTMLAttributes\n    figcaption: HTMLAttributes\n    figure: HTMLAttributes\n    footer: HTMLAttributes\n    form: FormHTMLAttributes\n    h1: HTMLAttributes\n    h2: HTMLAttributes\n    h3: HTMLAttributes\n    h4: HTMLAttributes\n    h5: HTMLAttributes\n    h6: HTMLAttributes\n    head: HTMLAttributes\n    header: HTMLAttributes\n    hgroup: HTMLAttributes\n    hr: HTMLAttributes\n    html: HtmlHTMLAttributes\n    i: HTMLAttributes\n    iframe: IframeHTMLAttributes\n    img: ImgHTMLAttributes\n    input: InputHTMLAttributes\n    ins: InsHTMLAttributes\n    kbd: HTMLAttributes\n    keygen: KeygenHTMLAttributes\n    label: LabelHTMLAttributes\n    legend: HTMLAttributes\n    li: LiHTMLAttributes\n    link: LinkHTMLAttributes\n    main: HTMLAttributes\n    map: MapHTMLAttributes\n    mark: HTMLAttributes\n    menu: MenuHTMLAttributes\n    menuitem: HTMLAttributes\n    meta: MetaHTMLAttributes\n    meter: MeterHTMLAttributes\n    nav: HTMLAttributes\n    noscript: HTMLAttributes\n    object: ObjectHTMLAttributes\n    ol: OlHTMLAttributes\n    optgroup: OptgroupHTMLAttributes\n    option: OptionHTMLAttributes\n    output: OutputHTMLAttributes\n    p: HTMLAttributes\n    param: ParamHTMLAttributes\n    picture: HTMLAttributes\n    pre: HTMLAttributes\n    progress: ProgressHTMLAttributes\n    q: QuoteHTMLAttributes\n    rp: HTMLAttributes\n    rt: HTMLAttributes\n    ruby: HTMLAttributes\n    s: HTMLAttributes\n    samp: HTMLAttributes\n    search: HTMLAttributes\n    slot: SlotHTMLAttributes\n    script: ScriptHTMLAttributes\n    section: HTMLAttributes\n    select: SelectHTMLAttributes\n    small: HTMLAttributes\n    source: SourceHTMLAttributes\n    span: HTMLAttributes\n    strong: HTMLAttributes\n    style: StyleHTMLAttributes\n    sub: HTMLAttributes\n    summary: HTMLAttributes\n    sup: HTMLAttributes\n    table: TableHTMLAttributes\n    template: HTMLAttributes\n    tbody: HTMLAttributes\n    td: TdHTMLAttributes\n    textarea: TextareaHTMLAttributes\n    tfoot: HTMLAttributes\n    th: ThHTMLAttributes\n    thead: HTMLAttributes\n    time: TimeHTMLAttributes\n    title: HTMLAttributes\n    tr: HTMLAttributes\n    track: TrackHTMLAttributes\n    u: HTMLAttributes\n    ul: HTMLAttributes\n    var: HTMLAttributes\n    video: VideoHTMLAttributes\n    wbr: HTMLAttributes\n  }\n}\n\nexport interface IntrinsicElements extends JSX.IntrinsicElements {}\n"
  },
  {
    "path": "src/jsx/jsx-dev-runtime.ts",
    "content": "/**\n * @module\n * This module provides Hono's JSX dev runtime.\n */\n\nimport type { HtmlEscapedString } from '../utils/html'\nimport { jsxFn } from './base'\nimport type { JSXNode } from './base'\nexport { Fragment } from './base'\nexport type { JSX } from './base'\n\nexport function jsxDEV(\n  tag: string | Function,\n  props: Record<string, unknown>,\n  key?: string\n): JSXNode {\n  let node: JSXNode\n  if (!props || !('children' in props)) {\n    node = jsxFn(tag, props, [])\n  } else {\n    const children = props.children as string | HtmlEscapedString\n    node = Array.isArray(children) ? jsxFn(tag, props, children) : jsxFn(tag, props, [children])\n  }\n  node.key = key\n  return node\n}\n"
  },
  {
    "path": "src/jsx/jsx-runtime.test.tsx",
    "content": "/** @jsxRuntime automatic **/\n/** @jsxImportSource . **/\nimport { Hono } from '../hono'\n\ndescribe('jsx-runtime', () => {\n  let app: Hono\n\n  beforeEach(() => {\n    app = new Hono()\n  })\n\n  it('Should render HTML strings', async () => {\n    app.get('/', (c) => {\n      return c.html(<h1>Hello</h1>)\n    })\n    const res = await app.request('http://localhost/')\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Type')).toBe('text/html; charset=UTF-8')\n    expect(await res.text()).toBe('<h1>Hello</h1>')\n  })\n\n  // https://en.reactjs.org/docs/jsx-in-depth.html#booleans-null-and-undefined-are-ignored\n  describe('Booleans, Null, and Undefined Are Ignored', () => {\n    it.each([true, false, undefined, null])('%s', (item) => {\n      expect((<span>{item}</span>).toString()).toBe('<span></span>')\n    })\n\n    it('falsy value', () => {\n      const template = <span>{0}</span>\n      expect(template.toString()).toBe('<span>0</span>')\n    })\n  })\n})\n"
  },
  {
    "path": "src/jsx/jsx-runtime.ts",
    "content": "/**\n * @module\n * This module provides Hono's JSX runtime.\n */\n\nexport { jsxDEV as jsx, Fragment } from './jsx-dev-runtime'\nexport { jsxDEV as jsxs } from './jsx-dev-runtime'\nexport type { JSX } from './jsx-dev-runtime'\nimport { html, raw } from '../helper/html'\nimport type { HtmlEscapedString, StringBuffer, HtmlEscaped } from '../utils/html'\nimport { escapeToBuffer, stringBufferToString } from '../utils/html'\nimport { styleObjectForEach } from './utils'\n\nexport { html as jsxTemplate }\n\nexport const jsxAttr = (\n  key: string,\n  v: string | Promise<string> | Record<string, string | number | null | undefined | boolean>\n): HtmlEscapedString | Promise<HtmlEscapedString> => {\n  const buffer: StringBuffer = [`${key}=\"`] as StringBuffer\n  if (key === 'style' && typeof v === 'object') {\n    // object to style strings\n    let styleStr = ''\n    styleObjectForEach(v as Record<string, string | number>, (property, value) => {\n      if (value != null) {\n        styleStr += `${styleStr ? ';' : ''}${property}:${value}`\n      }\n    })\n    escapeToBuffer(styleStr, buffer)\n    buffer[0] += '\"'\n  } else if (typeof v === 'string') {\n    escapeToBuffer(v, buffer)\n    buffer[0] += '\"'\n  } else if (v === null || v === undefined) {\n    return raw('')\n  } else if (typeof v === 'number' || (v as unknown as HtmlEscaped).isEscaped) {\n    buffer[0] += `${v}\"`\n  } else if (v instanceof Promise) {\n    buffer.unshift('\"', v)\n  } else {\n    escapeToBuffer(v.toString(), buffer)\n    buffer[0] += '\"'\n  }\n\n  return buffer.length === 1 ? raw(buffer[0]) : stringBufferToString(buffer, undefined)\n}\n\nexport const jsxEscape = (value: string) => value\n"
  },
  {
    "path": "src/jsx/streaming.test.tsx",
    "content": "/** @jsxImportSource ./ */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { JSDOM } from 'jsdom'\nimport { raw } from '../helper/html'\nimport { HtmlEscapedCallbackPhase, resolveCallback } from '../utils/html'\nimport type { HtmlEscapedString } from '../utils/html'\nimport { use } from './hooks'\nimport { Suspense, renderToReadableStream, StreamingContext } from './streaming'\n\nfunction replacementResult(html: string) {\n  const document = new JSDOM(html, { runScripts: 'dangerously' }).window.document\n  document.querySelectorAll('template, script').forEach((e) => e.remove())\n  return document.body.innerHTML\n}\n\ndescribe('Streaming', () => {\n  let suspenseCounter = 0\n  afterEach(() => {\n    suspenseCounter++\n  })\n\n  it('Suspense / renderToReadableStream', async () => {\n    let contentEvaluatedCount = 0\n    const Content = () => {\n      contentEvaluatedCount++\n      const content = new Promise<HtmlEscapedString>((resolve) =>\n        setTimeout(() => resolve(<h1>Hello</h1>), 10)\n      )\n      return content\n    }\n\n    const stream = renderToReadableStream(\n      <Suspense fallback={<p>Loading...</p>}>\n        <Content />\n      </Suspense>\n    )\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(chunks).toEqual([\n      `<template id=\"H:${suspenseCounter}\"></template><p>Loading...</p><!--/$-->`,\n      `<template data-hono-target=\"H:${suspenseCounter}\"><h1>Hello</h1></template><script>\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:${suspenseCounter}')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script>`,\n    ])\n\n    expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n      '<h1>Hello</h1>'\n    )\n\n    expect(contentEvaluatedCount).toEqual(1)\n  })\n\n  it('Suspense with StreamingContext', async () => {\n    let contentEvaluatedCount = 0\n    const Content = () => {\n      contentEvaluatedCount++\n      const content = new Promise<HtmlEscapedString>((resolve) =>\n        setTimeout(() => resolve(<h1>Hello</h1>), 10)\n      )\n      return content\n    }\n\n    const stream = renderToReadableStream(\n      <StreamingContext value={{ scriptNonce: 'test-nonce' }}>\n        <Suspense fallback={<p>Loading...</p>}>\n          <Content />\n        </Suspense>\n      </StreamingContext>\n    )\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(chunks).toEqual([\n      `<template id=\"H:${suspenseCounter}\"></template><p>Loading...</p><!--/$-->`,\n      `<template data-hono-target=\"H:${suspenseCounter}\"><h1>Hello</h1></template><script nonce=\"test-nonce\">\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:${suspenseCounter}')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script>`,\n    ])\n\n    expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n      '<h1>Hello</h1>'\n    )\n\n    expect(contentEvaluatedCount).toEqual(1)\n  })\n\n  it('`throw promise` inside Suspense', async () => {\n    let contentEvaluatedCount = 0\n    let resolvedContent: HtmlEscapedString | undefined = undefined\n    const Content = () => {\n      contentEvaluatedCount++\n      if (!resolvedContent) {\n        throw new Promise<void>((resolve) =>\n          setTimeout(() => {\n            resolvedContent = (<p>thrown a promise then resolved</p>) as HtmlEscapedString\n            resolve()\n          }, 10)\n        )\n      }\n      return resolvedContent\n    }\n\n    const stream = renderToReadableStream(\n      <Suspense fallback={<p>Loading...</p>}>\n        <Content />\n      </Suspense>\n    )\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(chunks).toEqual([\n      `<template id=\"H:${suspenseCounter}\"></template><p>Loading...</p><!--/$-->`,\n      `<template data-hono-target=\"H:${suspenseCounter}\"><p>thrown a promise then resolved</p></template><script>\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:${suspenseCounter}')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script>`,\n    ])\n\n    expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n      '<p>thrown a promise then resolved</p>'\n    )\n\n    expect(contentEvaluatedCount).toEqual(2)\n  })\n\n  it('simple content inside Suspense', async () => {\n    const Content = () => {\n      return <h1>Hello</h1>\n    }\n\n    const stream = renderToReadableStream(\n      <Suspense fallback={<p>Loading...</p>}>\n        <Content />\n      </Suspense>\n    )\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(chunks).toEqual(['<h1>Hello</h1>'])\n\n    suspenseCounter -= 1 // fallback is not rendered\n  })\n\n  it('nullish children', async () => {\n    const stream = renderToReadableStream(\n      <div>\n        <Suspense fallback={<p>Loading...</p>}>{[null, undefined]}</Suspense>\n      </div>\n    )\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(chunks).toEqual(['<div></div>'])\n\n    suspenseCounter -= 1 // fallback is not rendered\n  })\n\n  it('without children', async () => {\n    const stream = renderToReadableStream(\n      <div>\n        <Suspense fallback={<p>Loading...</p>}></Suspense>\n      </div>\n    )\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(chunks).toEqual(['<div></div>'])\n\n    suspenseCounter -= 1 // fallback is not rendered\n  })\n\n  it('only a \"false\" child', async () => {\n    const stream = renderToReadableStream(\n      <div>\n        <Suspense fallback={<p>Loading...</p>}>{false}</Suspense>\n      </div>\n    )\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(chunks).toEqual(['<div></div>'])\n\n    suspenseCounter -= 1 // fallback is not rendered\n  })\n\n  it('async nullish children', async () => {\n    let resolved = false\n    const Content = () => {\n      if (!resolved) {\n        resolved = true\n        throw new Promise<void>((r) =>\n          setTimeout(() => {\n            resolved = true\n            r()\n          }, 10)\n        )\n      }\n      return <h1>Hello</h1>\n    }\n\n    const stream = renderToReadableStream(\n      <Suspense fallback={<p>Loading...</p>}>\n        <Content />\n        {[null, undefined]}\n      </Suspense>\n    )\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(chunks).toEqual([\n      `<template id=\"H:${suspenseCounter}\"></template><p>Loading...</p><!--/$-->`,\n      `<template data-hono-target=\"H:${suspenseCounter}\"><h1>Hello</h1></template><script>\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:${suspenseCounter}')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script>`,\n    ])\n\n    expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n      '<h1>Hello</h1>'\n    )\n  })\n\n  it('boolean children', async () => {\n    const stream = renderToReadableStream(\n      <div>\n        <Suspense fallback={<p>Loading...</p>}>{[true, false]}</Suspense>\n      </div>\n    )\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(chunks).toEqual(['<div></div>'])\n\n    suspenseCounter -= 1 // fallback is not rendered\n  })\n\n  it('async boolean children', async () => {\n    let resolved = false\n    const Content = () => {\n      if (!resolved) {\n        resolved = true\n        throw new Promise<void>((r) =>\n          setTimeout(() => {\n            resolved = true\n            r()\n          }, 10)\n        )\n      }\n      return <h1>Hello</h1>\n    }\n\n    const stream = renderToReadableStream(\n      <Suspense fallback={<p>Loading...</p>}>\n        <Content />\n        {[true, false]}\n      </Suspense>\n    )\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(chunks).toEqual([\n      `<template id=\"H:${suspenseCounter}\"></template><p>Loading...</p><!--/$-->`,\n      `<template data-hono-target=\"H:${suspenseCounter}\"><h1>Hello</h1></template><script>\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:${suspenseCounter}')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script>`,\n    ])\n\n    expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n      '<h1>Hello</h1>'\n    )\n  })\n\n  it('children Suspense', async () => {\n    const Content1 = () =>\n      new Promise<HtmlEscapedString>((resolve) => setTimeout(() => resolve(<h1>Hello</h1>), 10))\n    const Content2 = () =>\n      new Promise<HtmlEscapedString>((resolve) => setTimeout(() => resolve(<h2>Hono</h2>), 10))\n\n    const stream = renderToReadableStream(\n      <Suspense fallback={<p>Loading...</p>}>\n        <Suspense fallback={<p>Loading sub content1...</p>}>\n          <Content1 />\n        </Suspense>\n        <Suspense fallback={<p>Loading sub content2...</p>}>\n          <Content2 />\n        </Suspense>\n      </Suspense>\n    )\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n      '<h1>Hello</h1><h2>Hono</h2>'\n    )\n\n    suspenseCounter += 2\n  })\n\n  it('children Suspense: Suspense and string', async () => {\n    const Content1 = () =>\n      new Promise<HtmlEscapedString>((resolve) => setTimeout(() => resolve(<h1>Hello</h1>), 10))\n\n    const stream = renderToReadableStream(\n      <Suspense fallback={<p>Loading...</p>}>\n        <Suspense fallback={<p>Loading sub content1...</p>}>\n          <Content1 />\n        </Suspense>\n        Hono\n      </Suspense>\n    )\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n      '<h1>Hello</h1>Hono'\n    )\n\n    suspenseCounter += 1\n  })\n\n  it('resolve(undefined)', async () => {\n    const Content = async () => {\n      const content = await Promise.resolve(undefined)\n      return <p>{content}</p>\n    }\n\n    const stream = renderToReadableStream(\n      <Suspense fallback={<p>Loading...</p>}>\n        <Content />\n      </Suspense>\n    )\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(chunks).toEqual([\n      `<template id=\"H:${suspenseCounter}\"></template><p>Loading...</p><!--/$-->`,\n      `<template data-hono-target=\"H:${suspenseCounter}\"><p></p></template><script>\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:${suspenseCounter}')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script>`,\n    ])\n\n    expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual('<p></p>')\n  })\n\n  it('resolve(null)', async () => {\n    const Content = async () => {\n      const content = await Promise.resolve(null)\n      return <p>{content}</p>\n    }\n\n    const stream = renderToReadableStream(\n      <Suspense fallback={<p>Loading...</p>}>\n        <Content />\n      </Suspense>\n    )\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(chunks).toEqual([\n      `<template id=\"H:${suspenseCounter}\"></template><p>Loading...</p><!--/$-->`,\n      `<template data-hono-target=\"H:${suspenseCounter}\"><p></p></template><script>\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:${suspenseCounter}')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script>`,\n    ])\n\n    expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual('<p></p>')\n  })\n\n  it('reject()', async () => {\n    const Content = async () => {\n      const content = await Promise.reject()\n      return <p>{content}</p>\n    }\n\n    const onError = vi.fn()\n    const stream = renderToReadableStream(\n      <Suspense fallback={<p>Loading...</p>}>\n        <Content />\n      </Suspense>,\n      onError\n    )\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(onError).toBeCalledTimes(1)\n\n    expect(chunks).toEqual([\n      `<template id=\"H:${suspenseCounter}\"></template><p>Loading...</p><!--/$-->`,\n      '',\n    ])\n\n    expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n      '<p>Loading...</p><!--/$-->'\n    )\n  })\n\n  it('closed()', async () => {\n    const Content = async () => {\n      await new Promise<void>((resolve) =>\n        setTimeout(() => {\n          vi.spyOn(ReadableStreamDefaultController.prototype, 'enqueue').mockImplementation(() => {\n            throw new Error('closed')\n          })\n          resolve()\n        }, 10)\n      )\n      return <p>content</p>\n    }\n\n    const onError = vi.fn()\n    const stream = renderToReadableStream(\n      <>\n        <Suspense fallback={<p>Loading...</p>}>\n          <Content />\n        </Suspense>\n        <Suspense fallback={<p>Loading...</p>}>\n          <Content />\n        </Suspense>\n      </>,\n      onError\n    )\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(onError).toBeCalledTimes(1)\n\n    expect(chunks).toEqual([\n      `<template id=\"H:${suspenseCounter}\"></template><p>Loading...</p><!--/$--><template id=\"H:${\n        suspenseCounter + 1\n      }\"></template><p>Loading...</p><!--/$-->`,\n    ])\n\n    expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n      '<p>Loading...</p><!--/$--><p>Loading...</p><!--/$-->'\n    )\n\n    suspenseCounter++\n    await new Promise((resolve) => setTimeout(resolve, 10))\n    vi.restoreAllMocks()\n  })\n\n  it('Multiple \"await\" call', async () => {\n    const delayedContent = new Promise<HtmlEscapedString>((resolve) =>\n      setTimeout(() => resolve(<h1>Hello</h1>), 10)\n    )\n    const delayedContent2 = new Promise<HtmlEscapedString>((resolve) =>\n      setTimeout(() => resolve(<h2>World</h2>), 10)\n    )\n    const Content = async () => {\n      const content = await delayedContent\n      const content2 = await delayedContent2\n      return (\n        <>\n          {content}\n          {content2}\n        </>\n      )\n    }\n\n    const stream = renderToReadableStream(\n      <Suspense fallback={<p>Loading...</p>}>\n        <Content />\n      </Suspense>\n    )\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(chunks).toEqual([\n      `<template id=\"H:${suspenseCounter}\"></template><p>Loading...</p><!--/$-->`,\n      `<template data-hono-target=\"H:${suspenseCounter}\"><h1>Hello</h1><h2>World</h2></template><script>\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:${suspenseCounter}')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script>`,\n    ])\n\n    expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n      '<h1>Hello</h1><h2>World</h2>'\n    )\n  })\n\n  it('Complex fallback content', async () => {\n    const delayedContent = new Promise<HtmlEscapedString>((resolve) =>\n      setTimeout(() => resolve(<h1>Hello</h1>), 10)\n    )\n\n    const Content = async () => {\n      const content = await delayedContent\n      return content\n    }\n\n    const stream = renderToReadableStream(\n      <Suspense\n        fallback={\n          <>\n            Loading<span>...</span>\n          </>\n        }\n      >\n        <Content />\n      </Suspense>\n    )\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(chunks).toEqual([\n      `<template id=\"H:${suspenseCounter}\"></template>Loading<span>...</span><!--/$-->`,\n      `<template data-hono-target=\"H:${suspenseCounter}\"><h1>Hello</h1></template><script>\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:${suspenseCounter}')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script>`,\n    ])\n\n    expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n      '<h1>Hello</h1>'\n    )\n  })\n\n  it('nested Suspense', async () => {\n    const SubContent = () => {\n      const content = new Promise<HtmlEscapedString>((resolve) =>\n        setTimeout(() => resolve(<h2>World</h2>), 10)\n      )\n      return content\n    }\n\n    const Content = () => {\n      const content = new Promise<HtmlEscapedString>((resolve) =>\n        setTimeout(\n          () =>\n            resolve(\n              <>\n                <h1>Hello</h1>\n                <Suspense fallback={<p>Loading sub content...</p>}>\n                  <SubContent />\n                </Suspense>\n              </>\n            ),\n          10\n        )\n      )\n      return content\n    }\n\n    const stream = renderToReadableStream(\n      <Suspense fallback={<p>Loading...</p>}>\n        <Content />\n      </Suspense>\n    )\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(chunks).toEqual([\n      `<template id=\"H:${suspenseCounter}\"></template><p>Loading...</p><!--/$-->`,\n      `<template data-hono-target=\"H:${suspenseCounter}\"><h1>Hello</h1><template id=\\\"H:${\n        suspenseCounter + 1\n      }\\\"></template><p>Loading sub content...</p><!--/$--></template><script>\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:${suspenseCounter}')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script>`,\n      `<template data-hono-target=\"H:${suspenseCounter + 1}\"><h2>World</h2></template><script>\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:${suspenseCounter + 1}')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script>`,\n    ])\n\n    expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n      '<h1>Hello</h1><h2>World</h2>'\n    )\n    suspenseCounter++\n  })\n\n  it('In multiple Suspense, go ahead in the order of resolved', async () => {\n    const SubContent2 = () => {\n      const content = new Promise<HtmlEscapedString>((resolve) =>\n        setTimeout(() => resolve(<p>first</p>), 20)\n      )\n      return content\n    }\n    const SubContent1 = () => {\n      const content = new Promise<HtmlEscapedString>((resolve) =>\n        setTimeout(\n          () =>\n            resolve(\n              <Suspense fallback={<p>Loading content2...</p>}>\n                <SubContent2 />\n              </Suspense>\n            ),\n          10\n        )\n      )\n      return content\n    }\n    const SubContent3 = () => {\n      const content = new Promise<HtmlEscapedString>((resolve) =>\n        setTimeout(() => resolve(<p>last</p>), 40)\n      )\n      return content\n    }\n\n    const Content = () => {\n      const content = new Promise<HtmlEscapedString>((resolve) =>\n        setTimeout(\n          () =>\n            resolve(\n              <>\n                <Suspense fallback={<p>Loading content1...</p>}>\n                  <SubContent1 />\n                </Suspense>\n                <Suspense fallback={<p>Loading content3...</p>}>\n                  <SubContent3 />\n                </Suspense>\n              </>\n            ),\n          10\n        )\n      )\n      return content\n    }\n\n    const stream = renderToReadableStream(\n      <Suspense fallback={<p>Loading...</p>}>\n        <Content />\n      </Suspense>\n    )\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n      '<p>first</p><p>last</p>'\n    )\n  })\n\n  it('Suspense with resolveStream', async () => {\n    let contentEvaluatedCount = 0\n    const Content = () => {\n      contentEvaluatedCount++\n      const content = new Promise<HtmlEscapedString>((resolve) =>\n        setTimeout(() => resolve(<h1>Hello</h1>), 10)\n      )\n      return content\n    }\n\n    const str = await resolveCallback(\n      await (\n        <Suspense fallback={<p>Loading...</p>}>\n          <Content />\n        </Suspense>\n      ).toString(),\n      HtmlEscapedCallbackPhase.Stream,\n      false,\n      {}\n    )\n\n    expect(str).toEqual('<h1>Hello</h1>')\n    expect(contentEvaluatedCount).toEqual(1)\n  })\n\n  it('renderToReadableStream(str: string)', async () => {\n    const str = '<h1>Hello</h1>'\n    const stream = renderToReadableStream(raw(str))\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(chunks).toEqual([str])\n  })\n\n  it('renderToReadableStream(promise: Promise<HtmlEscapedString>)', async () => {\n    const stream = renderToReadableStream(Promise.resolve(raw('<h1>Hello</h1>')))\n\n    const chunks = []\n    const textDecoder = new TextDecoder()\n    for await (const chunk of stream as any) {\n      chunks.push(textDecoder.decode(chunk))\n    }\n\n    expect(chunks).toEqual(['<h1>Hello</h1>'])\n\n    suspenseCounter++\n  })\n\n  describe('use()', async () => {\n    it('render to string', async () => {\n      const promise = new Promise((resolve) => setTimeout(() => resolve('Hello from use()'), 0))\n      const Content = () => {\n        const message = use(promise)\n        return <h1>{message}</h1>\n      }\n\n      const str = await resolveCallback(\n        await (\n          <Suspense fallback={<p>Loading...</p>}>\n            <Content />\n          </Suspense>\n        ).toString(),\n        HtmlEscapedCallbackPhase.Stream,\n        false,\n        {}\n      )\n      expect(str).toEqual('<h1>Hello from use()</h1>')\n    })\n\n    it('render to stream', async () => {\n      const promise = new Promise((resolve) => setTimeout(() => resolve('Hello from use()'), 0))\n      const Content = () => {\n        const message = use(promise)\n        return <h1>{message}</h1>\n      }\n\n      const stream = renderToReadableStream(\n        <Suspense fallback={<p>Loading...</p>}>\n          <Content />\n        </Suspense>\n      )\n\n      const chunks = []\n      const textDecoder = new TextDecoder()\n      for await (const chunk of stream as any) {\n        chunks.push(textDecoder.decode(chunk))\n      }\n\n      expect(chunks).toEqual([\n        `<template id=\"H:${suspenseCounter}\"></template><p>Loading...</p><!--/$-->`,\n        `<template data-hono-target=\"H:${suspenseCounter}\"><h1>Hello from use()</h1></template><script>\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:${suspenseCounter}')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script>`,\n      ])\n\n      expect(replacementResult(`<html><body>${chunks.join('')}</body></html>`)).toEqual(\n        '<h1>Hello from use()</h1>'\n      )\n    })\n  })\n\n  it('should not throw ERR_INVALID_STATE when reader is cancelled during nested Suspense streaming', async () => {\n    const unhandled: unknown[] = []\n    const onRejection = (e: unknown) => unhandled.push(e)\n    process.on('unhandledRejection', onRejection)\n\n    const SubContent = async () => <h2>World</h2>\n    const Content = async () => (\n      <>\n        <h1>Hello</h1>\n        <Suspense fallback={<p>Loading sub...</p>}>\n          <SubContent />\n        </Suspense>\n      </>\n    )\n\n    const onError = vi.fn()\n    const stream = renderToReadableStream(\n      <Suspense fallback={<p>Loading...</p>}>\n        <Content />\n      </Suspense>,\n      onError\n    )\n\n    const reader = stream.getReader()\n    const firstChunk = await reader.read()\n    expect(firstChunk.done).toBe(false)\n\n    // Simulate client disconnect\n    await reader.cancel()\n\n    // Wait for nested Suspense callbacks to fire against the closed controller\n    await new Promise((resolve) => setTimeout(resolve))\n\n    expect(unhandled).toHaveLength(0)\n    expect(onError).not.toHaveBeenCalled()\n\n    process.off('unhandledRejection', onRejection)\n  })\n\n  it('should not call onError when reader is cancelled during a slow callback resolution', async () => {\n    const unhandled: unknown[] = []\n    const onRejection = (e: unknown) => unhandled.push(e)\n    process.on('unhandledRejection', onRejection)\n\n    let signalCallbackStarted!: () => void\n    const callbackStarted = new Promise<void>((r) => {\n      signalCallbackStarted = r\n    })\n\n    const Content = async () =>\n      raw('<p>content</p>', [\n        ((opts: any) => {\n          if (opts.phase === HtmlEscapedCallbackPhase.BeforeStream) {\n            signalCallbackStarted()\n            return new Promise<string>((r) => setTimeout(() => r('')))\n          }\n          return undefined\n        }) as any,\n      ])\n\n    const onError = vi.fn()\n    const stream = renderToReadableStream(\n      <Suspense fallback={<p>Loading...</p>}>\n        <Content />\n      </Suspense>,\n      onError\n    )\n\n    const reader = stream.getReader()\n    await reader.read()\n\n    await callbackStarted\n    await reader.cancel()\n\n    await new Promise((resolve) => setTimeout(resolve))\n\n    expect(unhandled).toHaveLength(0)\n    expect(onError).not.toHaveBeenCalled()\n\n    process.off('unhandledRejection', onRejection)\n  })\n\n  it('should not throw when cancelled before initial content resolves', async () => {\n    const unhandled: unknown[] = []\n    const onRejection = (e: unknown) => unhandled.push(e)\n    process.on('unhandledRejection', onRejection)\n\n    const onError = vi.fn()\n    const stream = renderToReadableStream(\n      Promise.resolve(raw('<p>slow content</p>') as HtmlEscapedString),\n      onError\n    )\n\n    const reader = stream.getReader()\n    await reader.cancel()\n\n    await new Promise((resolve) => setTimeout(resolve))\n\n    expect(unhandled).toHaveLength(0)\n    expect(onError).not.toHaveBeenCalled()\n\n    process.off('unhandledRejection', onRejection)\n  })\n})\n"
  },
  {
    "path": "src/jsx/streaming.ts",
    "content": "/**\n * @module\n * This module enables JSX to supports streaming Response.\n */\n\nimport { raw } from '../helper/html'\nimport { HtmlEscapedCallbackPhase, resolveCallback } from '../utils/html'\nimport type { HtmlEscapedString } from '../utils/html'\nimport { JSXNode } from './base'\nimport { childrenToString } from './components'\nimport { DOM_RENDERER, DOM_STASH } from './constants'\nimport { createContext, useContext } from './context'\nimport { Suspense as SuspenseDomRenderer } from './dom/components'\nimport { buildDataStack } from './dom/render'\nimport type { HasRenderToDom, NodeObject } from './dom/render'\nimport type { Child, FC, PropsWithChildren, Context as JSXContext } from './'\n\n/**\n * Used to specify nonce for scripts generated by `Suspense` and `ErrorBoundary`.\n *\n * @example\n * ```tsx\n * <StreamingContext.Provider value={{ scriptNonce: 'test-nonce' }}>\n *   <Suspense fallback={<p>Loading...</p>}>\n *     <Content />\n *   </Suspense>\n * </StreamingContext.Provider>\n * ```\n */\nexport const StreamingContext: JSXContext<{ scriptNonce: string } | null> = createContext<{\n  scriptNonce: string\n} | null>(null)\n\nlet suspenseCounter = 0\n\n/**\n * @experimental\n * `Suspense` is an experimental feature.\n * The API might be changed.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport const Suspense: FC<PropsWithChildren<{ fallback: any }>> = async ({\n  children,\n  fallback,\n}) => {\n  if (!Array.isArray(children)) {\n    children = [children]\n  }\n\n  const nonce = useContext(StreamingContext)?.scriptNonce\n\n  let resArray: HtmlEscapedString[] | Promise<HtmlEscapedString[]>[] = []\n\n  // for use() hook\n  const stackNode = { [DOM_STASH]: [0, []] } as unknown as NodeObject\n  const popNodeStack = (value?: unknown) => {\n    buildDataStack.pop()\n    return value\n  }\n\n  try {\n    stackNode[DOM_STASH][0] = 0\n    buildDataStack.push([[], stackNode])\n    resArray = children.map((c) =>\n      c == null || typeof c === 'boolean' ? '' : c.toString()\n    ) as HtmlEscapedString[]\n  } catch (e) {\n    if (e instanceof Promise) {\n      resArray = [\n        e.then(() => {\n          stackNode[DOM_STASH][0] = 0\n          buildDataStack.push([[], stackNode])\n          return childrenToString(children as Child[]).then(popNodeStack)\n        }),\n      ] as Promise<HtmlEscapedString[]>[]\n    } else {\n      throw e\n    }\n  } finally {\n    popNodeStack()\n  }\n\n  if (resArray.some((res) => (res as {}) instanceof Promise)) {\n    const index = suspenseCounter++\n    const fallbackStr = await fallback.toString()\n    return raw(`<template id=\"H:${index}\"></template>${fallbackStr}<!--/$-->`, [\n      ...(fallbackStr.callbacks || []),\n      ({ phase, buffer, context }) => {\n        if (phase === HtmlEscapedCallbackPhase.BeforeStream) {\n          return\n        }\n        return Promise.all(resArray).then(async (htmlArray) => {\n          htmlArray = htmlArray.flat()\n          const content = htmlArray.join('')\n          if (buffer) {\n            buffer[0] = buffer[0].replace(\n              new RegExp(`<template id=\"H:${index}\"></template>.*?<!--/\\\\$-->`),\n              content\n            )\n          }\n          let html = buffer\n            ? ''\n            : `<template data-hono-target=\"H:${index}\">${content}</template><script${\n                nonce ? ` nonce=\"${nonce}\"` : ''\n              }>\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:${index}')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script>`\n\n          const callbacks = htmlArray\n            .map((html) => (html as HtmlEscapedString).callbacks || [])\n            .flat()\n          if (!callbacks.length) {\n            return html\n          }\n\n          if (phase === HtmlEscapedCallbackPhase.Stream) {\n            html = await resolveCallback(html, HtmlEscapedCallbackPhase.BeforeStream, true, context)\n          }\n\n          return raw(html, callbacks)\n        })\n      },\n    ])\n  } else {\n    return raw(resArray.join(''))\n  }\n}\n;(Suspense as HasRenderToDom)[DOM_RENDERER] = SuspenseDomRenderer\n\nconst textEncoder = new TextEncoder()\n/**\n * @experimental\n * `renderToReadableStream()` is an experimental feature.\n * The API might be changed.\n */\nexport const renderToReadableStream = (\n  content: HtmlEscapedString | JSXNode | Promise<HtmlEscapedString>,\n  onError: (e: unknown) => string | void = console.trace\n): ReadableStream<Uint8Array> => {\n  let cancelled = false\n  const reader = new ReadableStream<Uint8Array>({\n    async start(controller) {\n      try {\n        if (content instanceof JSXNode) {\n          // aJSXNode.toString() returns a string or Promise<string> and string is already escaped\n          content = content.toString() as HtmlEscapedString | Promise<HtmlEscapedString>\n        }\n        const context = typeof content === 'object' ? content : {}\n        const resolved = await resolveCallback(\n          content,\n          HtmlEscapedCallbackPhase.BeforeStream,\n          true,\n          context\n        )\n        if (!cancelled) {\n          controller.enqueue(textEncoder.encode(resolved))\n        }\n\n        let resolvedCount = 0\n        const callbacks: Promise<void>[] = []\n        const then = (promise: Promise<string>) => {\n          callbacks.push(\n            promise\n              .catch((err) => {\n                console.log(err)\n                onError(err)\n                return ''\n              })\n              .then(async (res) => {\n                res = await resolveCallback(\n                  res,\n                  HtmlEscapedCallbackPhase.BeforeStream,\n                  true,\n                  context\n                )\n                ;(res as HtmlEscapedString).callbacks\n                  ?.map((c) => c({ phase: HtmlEscapedCallbackPhase.Stream, context }))\n                  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n                  .filter<Promise<string>>(Boolean as any)\n                  .forEach(then)\n                resolvedCount++\n                if (!cancelled) {\n                  controller.enqueue(textEncoder.encode(res))\n                }\n              })\n          )\n        }\n        ;(resolved as HtmlEscapedString).callbacks\n          ?.map((c) => c({ phase: HtmlEscapedCallbackPhase.Stream, context }))\n          // eslint-disable-next-line @typescript-eslint/no-explicit-any\n          .filter<Promise<string>>(Boolean as any)\n          .forEach(then)\n        while (resolvedCount !== callbacks.length) {\n          await Promise.all(callbacks)\n        }\n      } catch (e) {\n        // maybe the connection was closed\n        onError(e)\n      }\n\n      if (!cancelled) {\n        controller.close()\n      }\n    },\n    cancel() {\n      cancelled = true\n    },\n  })\n  return reader\n}\n"
  },
  {
    "path": "src/jsx/types.ts",
    "content": "/**\n * All types exported from \"hono/jsx\" are in this file.\n */\nimport type { Child, JSXNode } from './base'\nimport type { JSX } from './intrinsic-elements'\n\nexport type { Child, JSXNode, FC } from './base'\nexport type { RefObject } from './hooks'\nexport type { Context } from './context'\n\nexport type PropsWithChildren<P = unknown> = P & { children?: Child | undefined }\nexport type CSSProperties = JSX.CSSProperties\n\n/**\n * React types\n */\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype ReactElement<P = any, T = string | Function> = JSXNode & {\n  type: T\n  props: P\n  key: string | null\n}\ntype ReactNode = ReactElement | string | number | boolean | null | undefined\ntype ComponentClass<_P = {}, _S = {}> = unknown\n\nexport type { ReactElement, ReactNode, ComponentClass }\n\nexport type Event = globalThis.Event\nexport type MouseEvent = globalThis.MouseEvent\nexport type KeyboardEvent = globalThis.KeyboardEvent\nexport type FocusEvent = globalThis.FocusEvent\nexport type ClipboardEvent = globalThis.ClipboardEvent\nexport type InputEvent = globalThis.InputEvent\nexport type PointerEvent = globalThis.PointerEvent\nexport type TouchEvent = globalThis.TouchEvent\nexport type WheelEvent = globalThis.WheelEvent\nexport type AnimationEvent = globalThis.AnimationEvent\nexport type TransitionEvent = globalThis.TransitionEvent\nexport type DragEvent = globalThis.DragEvent\n"
  },
  {
    "path": "src/jsx/utils.test.ts",
    "content": "import { normalizeIntrinsicElementKey, styleObjectForEach } from './utils'\n\ndescribe('normalizeIntrinsicElementKey', () => {\n  test.each`\n    key                | expected\n    ${'className'}     | ${'class'}\n    ${'htmlFor'}       | ${'for'}\n    ${'crossOrigin'}   | ${'crossorigin'}\n    ${'httpEquiv'}     | ${'http-equiv'}\n    ${'itemProp'}      | ${'itemprop'}\n    ${'fetchPriority'} | ${'fetchpriority'}\n    ${'noModule'}      | ${'nomodule'}\n    ${'formAction'}    | ${'formaction'}\n    ${'href'}          | ${'href'}\n  `('should convert $key to $expected', ({ key, expected }) => {\n    expect(normalizeIntrinsicElementKey(key)).toBe(expected)\n  })\n})\n\ndescribe('styleObjectForEach', () => {\n  describe('Should output the number as it is, when a number type is passed', () => {\n    test.each`\n      property\n      ${'animationIterationCount'}\n      ${'aspectRatio'}\n      ${'borderImageOutset'}\n      ${'borderImageSlice'}\n      ${'borderImageWidth'}\n      ${'columnCount'}\n      ${'columns'}\n      ${'flex'}\n      ${'flexGrow'}\n      ${'flexPositive'}\n      ${'flexShrink'}\n      ${'flexNegative'}\n      ${'flexOrder'}\n      ${'gridArea'}\n      ${'gridRow'}\n      ${'gridRowEnd'}\n      ${'gridRowSpan'}\n      ${'gridRowStart'}\n      ${'gridColumn'}\n      ${'gridColumnEnd'}\n      ${'gridColumnSpan'}\n      ${'gridColumnStart'}\n      ${'fontWeight'}\n      ${'lineClamp'}\n      ${'lineHeight'}\n      ${'opacity'}\n      ${'order'}\n      ${'orphans'}\n      ${'scale'}\n      ${'tabSize'}\n      ${'widows'}\n      ${'zIndex'}\n      ${'zoom'}\n      ${'fillOpacity'}\n      ${'floodOpacity'}\n      ${'stopOpacity'}\n      ${'strokeDasharray'}\n      ${'strokeDashoffset'}\n      ${'strokeMiterlimit'}\n      ${'strokeOpacity'}\n      ${'strokeWidth'}\n    `('$property', ({ property }) => {\n      const fn = vi.fn()\n      styleObjectForEach({ [property]: 1 }, fn)\n      expect(fn).toBeCalledWith(\n        property.replace(/[A-Z]/g, (m: string) => `-${m.toLowerCase()}`),\n        '1'\n      )\n    })\n  })\n  describe('Should output with px suffix, when a number type is passed', () => {\n    test.each`\n      property\n      ${'borderBottomWidth'}\n      ${'borderLeftWidth'}\n      ${'borderRightWidth'}\n      ${'borderTopWidth'}\n      ${'borderWidth'}\n      ${'bottom'}\n      ${'fontSize'}\n      ${'height'}\n      ${'left'}\n      ${'margin'}\n      ${'marginBottom'}\n      ${'marginLeft'}\n      ${'marginRight'}\n      ${'marginTop'}\n      ${'padding'}\n      ${'paddingBottom'}\n      ${'paddingLeft'}\n      ${'paddingRight'}\n      ${'paddingTop'}\n      ${'right'}\n      ${'top'}\n      ${'width'}\n    `('$property', ({ property }) => {\n      const fn = vi.fn()\n      styleObjectForEach({ [property]: 1 }, fn)\n      expect(fn).toBeCalledWith(\n        property.replace(/[A-Z]/g, (m: string) => `-${m.toLowerCase()}`),\n        '1px'\n      )\n    })\n  })\n})\n"
  },
  {
    "path": "src/jsx/utils.ts",
    "content": "const normalizeElementKeyMap: Map<string, string> = new Map([\n  ['className', 'class'],\n  ['htmlFor', 'for'],\n  ['crossOrigin', 'crossorigin'],\n  ['httpEquiv', 'http-equiv'],\n  ['itemProp', 'itemprop'],\n  ['fetchPriority', 'fetchpriority'],\n  ['noModule', 'nomodule'],\n  ['formAction', 'formaction'],\n])\nexport const normalizeIntrinsicElementKey = (key: string): string =>\n  normalizeElementKeyMap.get(key) || key\n\nexport const styleObjectForEach = (\n  style: Record<string, string | number>,\n  fn: (key: string, value: string | null) => void\n): void => {\n  for (const [k, v] of Object.entries(style)) {\n    const key =\n      k[0] === '-' || !/[A-Z]/.test(k)\n        ? k // a CSS variable or a lowercase only property\n        : k.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`) // a camelCase property. convert to kebab-case\n    fn(\n      key,\n      v == null\n        ? null\n        : typeof v === 'number'\n          ? !key.match(\n              /^(?:a|border-im|column(?:-c|s)|flex(?:$|-[^b])|grid-(?:ar|[^a])|font-w|li|or|sca|st|ta|wido|z)|ty$/\n            )\n            ? `${v}px`\n            : `${v}`\n          : v\n    )\n  }\n}\n"
  },
  {
    "path": "src/middleware/basic-auth/index.test.ts",
    "content": "import { createHash } from 'crypto'\nimport { Hono } from '../../hono'\nimport { basicAuth } from '.'\n\ndescribe('Basic Auth by Middleware', () => {\n  let handlerExecuted: boolean\n\n  beforeEach(() => {\n    handlerExecuted = false\n  })\n\n  const app = new Hono()\n\n  const username = 'hono-user-a'\n  const password = 'hono-password-a'\n  const unicodePassword = '炎'\n\n  const usernameB = 'hono-user-b'\n  const passwordB = 'hono-password-b'\n\n  const usernameC = 'hono-user-c'\n  const passwordC = 'hono-password-c'\n\n  app.use(\n    '/auth/*',\n    basicAuth({\n      username,\n      password,\n    })\n  )\n  // Test multiple handlers\n  app.use('/auth/*', async (c, next) => {\n    c.header('x-custom', 'foo')\n    await next()\n  })\n\n  app.use(\n    '/auth-unicode/*',\n    basicAuth({\n      username: username,\n      password: unicodePassword,\n    })\n  )\n\n  app.use(\n    '/auth-multi/*',\n    basicAuth(\n      {\n        username: usernameB,\n        password: passwordB,\n      },\n      {\n        username: usernameC,\n        password: passwordC,\n      }\n    )\n  )\n\n  app.use(\n    '/auth-override-func/*',\n    basicAuth({\n      username: username,\n      password: password,\n      hashFunction: (data: string) => createHash('sha256').update(data).digest('hex'),\n    })\n  )\n\n  app.use('/nested/*', async (c, next) => {\n    const auth = basicAuth({ username: username, password: password })\n    return auth(c, next)\n  })\n\n  app.use('/verify-user/*', async (c, next) => {\n    const auth = basicAuth({\n      verifyUser: (username, password, c) => {\n        return (\n          c.req.path === '/verify-user' &&\n          username === 'dynamic-user' &&\n          password === 'hono-password'\n        )\n      },\n    })\n    return auth(c, next)\n  })\n\n  app.use(\n    '/auth-custom-invalid-user-message-string/*',\n    basicAuth({\n      username,\n      password,\n      invalidUserMessage: 'Custom unauthorized message as string',\n    })\n  )\n\n  app.use(\n    '/auth-custom-invalid-user-message-object/*',\n    basicAuth({\n      username,\n      password,\n      invalidUserMessage: { message: 'Custom unauthorized message as object' },\n    })\n  )\n\n  app.use(\n    '/auth-custom-invalid-user-message-function-string/*',\n    basicAuth({\n      username,\n      password,\n      invalidUserMessage: () => 'Custom unauthorized message as function string',\n    })\n  )\n\n  app.use(\n    '/auth-custom-invalid-user-message-function-object/*',\n    basicAuth({\n      username,\n      password,\n      invalidUserMessage: () => ({ message: 'Custom unauthorized message as function object' }),\n    })\n  )\n\n  app.get('/auth/*', (c) => {\n    handlerExecuted = true\n    return c.text('auth')\n  })\n  app.get('/auth-unicode/*', (c) => {\n    handlerExecuted = true\n    return c.text('auth')\n  })\n  app.get('/auth-multi/*', (c) => {\n    handlerExecuted = true\n    return c.text('auth')\n  })\n  app.get('/auth-override-func/*', (c) => {\n    handlerExecuted = true\n    return c.text('auth')\n  })\n\n  app.get('/nested/*', (c) => {\n    handlerExecuted = true\n    return c.text('nested')\n  })\n\n  app.get('/verify-user', (c) => {\n    handlerExecuted = true\n    return c.text('verify-user')\n  })\n\n  app.get('/auth-custom-invalid-user-message-string/*', (c) => {\n    handlerExecuted = true\n    return c.text('auth')\n  })\n  app.get('/auth-custom-invalid-user-message-object/*', (c) => {\n    handlerExecuted = true\n    return c.text('auth')\n  })\n  app.get('/auth-custom-invalid-user-message-function-string/*', (c) => {\n    handlerExecuted = true\n    return c.text('auth')\n  })\n\n  app.get('/auth-custom-invalid-user-message-function-object/*', (c) => {\n    handlerExecuted = true\n    return c.text('auth')\n  })\n\n  it('Should not authorize', async () => {\n    const req = new Request('http://localhost/auth/a')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(handlerExecuted).toBeFalsy()\n    expect(await res.text()).toBe('Unauthorized')\n    expect(res.headers.get('x-custom')).toBeNull()\n  })\n\n  it('Should authorize', async () => {\n    const credential = Buffer.from(username + ':' + password).toString('base64')\n\n    const req = new Request('http://localhost/auth/a')\n    req.headers.set('Authorization', `Basic ${credential}`)\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(handlerExecuted).toBeTruthy()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('auth')\n    expect(res.headers.get('x-custom')).toBe('foo')\n  })\n\n  it('Should authorize Unicode', async () => {\n    const credential = Buffer.from(username + ':' + unicodePassword).toString('base64')\n\n    const req = new Request('http://localhost/auth-unicode/a')\n    req.headers.set('Authorization', `Basic ${credential}`)\n    const res = await app.request(req)\n    expect(handlerExecuted).toBeTruthy()\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('auth')\n  })\n\n  it('Should authorize multiple users', async () => {\n    let credential = Buffer.from(usernameB + ':' + passwordB).toString('base64')\n\n    let req = new Request('http://localhost/auth-multi/b')\n    req.headers.set('Authorization', `Basic ${credential}`)\n    let res = await app.request(req)\n    expect(handlerExecuted).toBeTruthy()\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('auth')\n\n    handlerExecuted = false\n    credential = Buffer.from(usernameC + ':' + passwordC).toString('base64')\n    req = new Request('http://localhost/auth-multi/c')\n    req.headers.set('Authorization', `Basic ${credential}`)\n    res = await app.request(req)\n    expect(handlerExecuted).toBeTruthy()\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('auth')\n  })\n\n  it('Should authorize with sha256 function override', async () => {\n    const credential = Buffer.from(username + ':' + password).toString('base64')\n\n    const req = new Request('http://localhost/auth-override-func/a')\n    req.headers.set('Authorization', `Basic ${credential}`)\n    const res = await app.request(req)\n    expect(handlerExecuted).toBeTruthy()\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('auth')\n  })\n\n  it('Should authorize - nested', async () => {\n    const credential = Buffer.from(username + ':' + password).toString('base64')\n\n    const req = new Request('http://localhost/nested')\n    req.headers.set('Authorization', `Basic ${credential}`)\n    const res = await app.request(req)\n    expect(handlerExecuted).toBeTruthy()\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('nested')\n  })\n\n  it('Should not authorize - nested', async () => {\n    const credential = Buffer.from('foo' + ':' + 'bar').toString('base64')\n\n    const req = new Request('http://localhost/nested')\n    req.headers.set('Authorization', `Basic ${credential}`)\n    const res = await app.request(req)\n    expect(handlerExecuted).toBeFalsy()\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(await res.text()).toBe('Unauthorized')\n  })\n\n  it('Should authorize - verifyUser', async () => {\n    const credential = Buffer.from('dynamic-user' + ':' + 'hono-password').toString('base64')\n\n    const req = new Request('http://localhost/verify-user')\n    req.headers.set('Authorization', `Basic ${credential}`)\n    const res = await app.request(req)\n    expect(handlerExecuted).toBeTruthy()\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('verify-user')\n  })\n\n  it('Should not authorize - verifyUser', async () => {\n    const credential = Buffer.from('foo' + ':' + 'bar').toString('base64')\n\n    const req = new Request('http://localhost/verify-user')\n    req.headers.set('Authorization', `Basic ${credential}`)\n    const res = await app.request(req)\n    expect(handlerExecuted).toBeFalsy()\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(await res.text()).toBe('Unauthorized')\n  })\n\n  it('Should not authorize - custom invalid user message as string', async () => {\n    const req = new Request('http://localhost/auth-custom-invalid-user-message-string')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(handlerExecuted).toBeFalsy()\n    expect(await res.text()).toBe('Custom unauthorized message as string')\n  })\n\n  it('Should not authorize - custom invalid user message as object', async () => {\n    const req = new Request('http://localhost/auth-custom-invalid-user-message-object')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(res.headers.get('Content-Type')).toMatch('application/json')\n    expect(handlerExecuted).toBeFalsy()\n    expect(await res.text()).toBe('{\"message\":\"Custom unauthorized message as object\"}')\n  })\n\n  it('Should not authorize - custom invalid user message as function string', async () => {\n    const req = new Request('http://localhost/auth-custom-invalid-user-message-function-string')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(handlerExecuted).toBeFalsy()\n    expect(await res.text()).toBe('Custom unauthorized message as function string')\n  })\n\n  it('Should not authorize - custom invalid user message as function object', async () => {\n    const req = new Request('http://localhost/auth-custom-invalid-user-message-function-object')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(res.headers.get('Content-Type')).toMatch('application/json')\n    expect(handlerExecuted).toBeFalsy()\n    expect(await res.text()).toBe('{\"message\":\"Custom unauthorized message as function object\"}')\n  })\n})\n\ndescribe('Basic Auth with onAuthSuccess', () => {\n  const username = 'callback-user'\n  const password = 'callback-pass'\n\n  it('should call onAuthSuccess callback on successful auth', async () => {\n    type Env = { Variables: { custom: string } }\n    const app = new Hono<Env>()\n    let callbackCalled = false\n    let callbackUsername = ''\n\n    app.use(\n      '/*',\n      basicAuth({\n        username,\n        password,\n        onAuthSuccess: (c, u) => {\n          callbackCalled = true\n          callbackUsername = u\n          c.set('custom', 'value')\n        },\n      })\n    )\n    app.get('/', (c) => c.text(c.get('custom') || 'no-custom'))\n\n    const credential = Buffer.from(`${username}:${password}`).toString('base64')\n    const res = await app.request('/', {\n      headers: { Authorization: `Basic ${credential}` },\n    })\n\n    expect(callbackCalled).toBe(true)\n    expect(callbackUsername).toBe(username)\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('value')\n  })\n\n  it('should support async onAuthSuccess callback', async () => {\n    type Env = { Variables: { asyncValue: string } }\n    const app = new Hono<Env>()\n\n    app.use(\n      '/*',\n      basicAuth({\n        username,\n        password,\n        onAuthSuccess: async (c) => {\n          await new Promise((resolve) => setTimeout(resolve, 10))\n          c.set('asyncValue', 'done')\n        },\n      })\n    )\n    app.get('/', (c) => c.text(c.get('asyncValue') || 'not-done'))\n\n    const credential = Buffer.from(`${username}:${password}`).toString('base64')\n    const res = await app.request('/', {\n      headers: { Authorization: `Basic ${credential}` },\n    })\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('done')\n  })\n\n  it('should not call onAuthSuccess on failed auth', async () => {\n    const app = new Hono()\n    let callbackCalled = false\n\n    app.use(\n      '/*',\n      basicAuth({\n        username,\n        password,\n        onAuthSuccess: () => {\n          callbackCalled = true\n        },\n      })\n    )\n    app.get('/', (c) => c.text('ok'))\n\n    const credential = Buffer.from('wrong:wrong').toString('base64')\n    const res = await app.request('/', {\n      headers: { Authorization: `Basic ${credential}` },\n    })\n\n    expect(callbackCalled).toBe(false)\n    expect(res.status).toBe(401)\n  })\n\n  it('should work with verifyUser mode', async () => {\n    type Env = { Variables: { verified: string } }\n    const app = new Hono<Env>()\n    let callbackUsername = ''\n\n    app.use(\n      '/*',\n      basicAuth({\n        verifyUser: (u, p) => u === username && p === password,\n        onAuthSuccess: (c, u) => {\n          callbackUsername = u\n          c.set('verified', 'yes')\n        },\n      })\n    )\n    app.get('/', (c) => c.text(c.get('verified') || 'no'))\n\n    const credential = Buffer.from(`${username}:${password}`).toString('base64')\n    const res = await app.request('/', {\n      headers: { Authorization: `Basic ${credential}` },\n    })\n\n    expect(callbackUsername).toBe(username)\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('yes')\n  })\n})\n"
  },
  {
    "path": "src/middleware/basic-auth/index.ts",
    "content": "/**\n * @module\n * Basic Auth Middleware for Hono.\n */\n\nimport type { Context } from '../../context'\nimport { HTTPException } from '../../http-exception'\nimport type { MiddlewareHandler } from '../../types'\nimport { auth } from '../../utils/basic-auth'\nimport { timingSafeEqual } from '../../utils/buffer'\n\ntype MessageFunction = (c: Context) => string | object | Promise<string | object>\n\ntype BasicAuthOptions =\n  | {\n      username: string\n      password: string\n      realm?: string\n      hashFunction?: Function\n      invalidUserMessage?: string | object | MessageFunction\n      onAuthSuccess?: (c: Context, username: string) => void | Promise<void>\n    }\n  | {\n      verifyUser: (username: string, password: string, c: Context) => boolean | Promise<boolean>\n      realm?: string\n      hashFunction?: Function\n      invalidUserMessage?: string | object | MessageFunction\n      onAuthSuccess?: (c: Context, username: string) => void | Promise<void>\n    }\n\n/**\n * Basic Auth Middleware for Hono.\n *\n * @see {@link https://hono.dev/docs/middleware/builtin/basic-auth}\n *\n * @param {BasicAuthOptions} options - The options for the basic authentication middleware.\n * @param {string} options.username - The username for authentication.\n * @param {string} options.password - The password for authentication.\n * @param {string} [options.realm=\"Secure Area\"] - The realm attribute for the WWW-Authenticate header.\n * @param {Function} [options.hashFunction] - The hash function used for secure comparison.\n * @param {Function} [options.verifyUser] - The function to verify user credentials.\n * @param {string | object | MessageFunction} [options.invalidUserMessage=\"Unauthorized\"] - The invalid user message.\n * @param {Function} [options.onAuthSuccess] - Callback function called on successful authentication.\n * @returns {MiddlewareHandler} The middleware handler function.\n * @throws {HTTPException} If neither \"username and password\" nor \"verifyUser\" options are provided.\n *\n * @example\n * ```ts\n * const app = new Hono()\n *\n * app.use(\n *   '/auth/*',\n *   basicAuth({\n *     username: 'hono',\n *     password: 'ahotproject',\n *   })\n * )\n *\n * app.get('/auth/page', (c) => {\n *   return c.text('You are authorized')\n * })\n * ```\n *\n * @example\n * ```ts\n * // With onAuthSuccess callback\n * app.use(\n *   '/auth/*',\n *   basicAuth({\n *     username: 'hono',\n *     password: 'ahotproject',\n *     onAuthSuccess: (c, username) => {\n *       c.set('user', { name: username, role: 'admin' })\n *       console.log(`User ${username} authenticated`)\n *     },\n *   })\n * )\n * ```\n */\nexport const basicAuth = (\n  options: BasicAuthOptions,\n  ...users: { username: string; password: string }[]\n): MiddlewareHandler => {\n  const usernamePasswordInOptions = 'username' in options && 'password' in options\n  const verifyUserInOptions = 'verifyUser' in options\n\n  if (!(usernamePasswordInOptions || verifyUserInOptions)) {\n    throw new Error(\n      'basic auth middleware requires options for \"username and password\" or \"verifyUser\"'\n    )\n  }\n\n  if (!options.realm) {\n    options.realm = 'Secure Area'\n  }\n\n  if (!options.invalidUserMessage) {\n    options.invalidUserMessage = 'Unauthorized'\n  }\n\n  if (usernamePasswordInOptions) {\n    users.unshift({ username: options.username, password: options.password })\n  }\n\n  return async function basicAuth(ctx, next) {\n    const requestUser = auth(ctx.req.raw)\n    if (requestUser) {\n      if (verifyUserInOptions) {\n        if (await options.verifyUser(requestUser.username, requestUser.password, ctx)) {\n          if (options.onAuthSuccess) {\n            await options.onAuthSuccess(ctx, requestUser.username)\n          }\n          await next()\n          return\n        }\n      } else {\n        for (const user of users) {\n          const [usernameEqual, passwordEqual] = await Promise.all([\n            timingSafeEqual(user.username, requestUser.username, options.hashFunction),\n            timingSafeEqual(user.password, requestUser.password, options.hashFunction),\n          ])\n          if (usernameEqual && passwordEqual) {\n            if (options.onAuthSuccess) {\n              await options.onAuthSuccess(ctx, requestUser.username)\n            }\n            await next()\n            return\n          }\n        }\n      }\n    }\n    // Invalid user.\n    const status = 401\n    const headers = {\n      'WWW-Authenticate': 'Basic realm=\"' + options.realm?.replace(/\"/g, '\\\\\"') + '\"',\n    }\n    const responseMessage =\n      typeof options.invalidUserMessage === 'function'\n        ? await options.invalidUserMessage(ctx)\n        : options.invalidUserMessage\n    const res =\n      typeof responseMessage === 'string'\n        ? new Response(responseMessage, { status, headers })\n        : new Response(JSON.stringify(responseMessage), {\n            status,\n            headers: {\n              ...headers,\n              'content-type': 'application/json',\n            },\n          })\n    throw new HTTPException(status, { res })\n  }\n}\n"
  },
  {
    "path": "src/middleware/bearer-auth/index.test.ts",
    "content": "import { Hono } from '../../hono'\nimport { bearerAuth } from '.'\n\ndescribe('Bearer Auth by Middleware', () => {\n  let app: Hono\n  let handlerExecuted: boolean\n  let token: string\n  let tokens: string[]\n\n  beforeEach(async () => {\n    app = new Hono()\n    handlerExecuted = false\n    token = 'abcdefg12345-._~+/='\n    tokens = ['abcdefg12345-._~+/=', 'alternative']\n\n    app.use('/auth/*', bearerAuth({ token }))\n    app.use('/auth/*', async (c, next) => {\n      c.header('x-custom', 'foo')\n      await next()\n    })\n    app.get('/auth/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n\n    app.use('/authBot/*', bearerAuth({ token, prefix: 'Bot' }))\n    app.get('/authBot/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth bot')\n    })\n\n    app.use('/apiKey/*', bearerAuth({ token, prefix: '', headerName: 'X-Api-Key' }))\n    app.get('/apiKey/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth apiKey')\n    })\n\n    app.use('/nested/*', async (c, next) => {\n      const auth = bearerAuth({ token })\n      return auth(c, next)\n    })\n    app.get('/nested/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth nested')\n    })\n\n    app.use('/auths/*', bearerAuth({ token: tokens }))\n    app.get('/auths/*', (c) => {\n      handlerExecuted = true\n      return c.text('auths')\n    })\n\n    app.use(\n      '/auth-verify-token/*',\n      bearerAuth({\n        verifyToken: async (token, c) => {\n          return c.req.path === '/auth-verify-token' && token === 'dynamic-token'\n        },\n      })\n    )\n    app.get('/auth-verify-token/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth-verify-token')\n    })\n\n    app.use('/auth-custom-header/*', bearerAuth({ token: tokens, headerName: 'X-Auth' }))\n    app.get('/auth-custom-header/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth-custom-header')\n    })\n\n    app.use(\n      '/auth-custom-no-authentication-header-wwwAuthenticateHeader-string/*',\n      bearerAuth({\n        token: tokens,\n        noAuthenticationHeader: {\n          wwwAuthenticateHeader: 'Bearer error=\"Unauthorized\",error_description=\"Unauthorized\"',\n        },\n      })\n    )\n    app.get('/auth-custom-no-authentication-header-wwwAuthenticateHeader-string/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n\n    app.use(\n      '/auth-custom-no-authentication-header-wwwAuthenticateHeader-object/*',\n      bearerAuth({\n        token: tokens,\n        noAuthenticationHeader: {\n          wwwAuthenticateHeader: {\n            error: 'Unauthorized',\n            error_description: 'Unauthorized',\n          },\n        },\n      })\n    )\n    app.get('/auth-custom-no-authentication-header-wwwAuthenticateHeader-object/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n\n    app.use(\n      '/auth-custom-no-authentication-header-wwwAuthenticateHeader-function-string/*',\n      bearerAuth({\n        token: tokens,\n        noAuthenticationHeader: {\n          wwwAuthenticateHeader: () =>\n            'Bearer error=\"Unauthorized\",error_description=\"Unauthorized\"',\n        },\n      })\n    )\n    app.get(\n      '/auth-custom-no-authentication-header-wwwAuthenticateHeader-function-string/*',\n      (c) => {\n        handlerExecuted = true\n        return c.text('auth')\n      }\n    )\n\n    app.use(\n      '/auth-custom-no-authentication-header-wwwAuthenticateHeader-function-object/*',\n      bearerAuth({\n        token: tokens,\n        noAuthenticationHeader: {\n          wwwAuthenticateHeader: () => ({\n            error: 'Unauthorized',\n            error_description: 'Unauthorized',\n          }),\n        },\n      })\n    )\n    app.get(\n      '/auth-custom-no-authentication-header-wwwAuthenticateHeader-function-object/*',\n      (c) => {\n        handlerExecuted = true\n        return c.text('auth')\n      }\n    )\n\n    app.use(\n      '/auth-custom-no-authentication-header-message-string/*',\n      bearerAuth({\n        token,\n        noAuthenticationHeader: { message: 'Custom no authentication header message as string' },\n      })\n    )\n    app.get('/auth-custom-no-authentication-header-message-string/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n\n    app.use(\n      '/auth-custom-no-authentication-header-message-object/*',\n      bearerAuth({\n        token,\n        noAuthenticationHeader: {\n          message: { message: 'Custom no authentication header message as object' },\n        },\n      })\n    )\n    app.get('/auth-custom-no-authentication-header-message-object/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n\n    app.use(\n      '/auth-custom-no-authentication-header-message-function-string/*',\n      bearerAuth({\n        token,\n        noAuthenticationHeader: {\n          message: () => 'Custom no authentication header message as function string',\n        },\n      })\n    )\n    app.get('/auth-custom-no-authentication-header-message-function-string/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n\n    app.use(\n      '/auth-custom-no-authentication-header-message-function-object/*',\n      bearerAuth({\n        token,\n        noAuthenticationHeader: {\n          message: () => ({\n            message: 'Custom no authentication header message as function object',\n          }),\n        },\n      })\n    )\n    app.get('/auth-custom-no-authentication-header-message-function-object/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n\n    app.use(\n      '/auth-custom-invalid-authentication-header-wwwAuthenticateHeader-string/*',\n      bearerAuth({\n        token: tokens,\n        invalidAuthenticationHeader: {\n          wwwAuthenticateHeader: 'Bearer error=\"Unauthorized\",error_description=\"Unauthorized\"',\n        },\n      })\n    )\n    app.get('/auth-custom-invalid-authentication-header-wwwAuthenticateHeader-string/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n\n    app.use(\n      '/auth-custom-invalid-authentication-header-wwwAuthenticateHeader-object/*',\n      bearerAuth({\n        token: tokens,\n        invalidAuthenticationHeader: {\n          wwwAuthenticateHeader: {\n            error: 'Unauthorized',\n            error_description: 'Unauthorized',\n          },\n        },\n      })\n    )\n    app.get('/auth-custom-invalid-authentication-header-wwwAuthenticateHeader-object/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n\n    app.use(\n      '/auth-custom-invalid-authentication-header-wwwAuthenticateHeader-function-string/*',\n      bearerAuth({\n        token: tokens,\n        invalidAuthenticationHeader: {\n          wwwAuthenticateHeader: () =>\n            'Bearer error=\"Unauthorized\",error_description=\"Unauthorized\"',\n        },\n      })\n    )\n    app.get(\n      '/auth-custom-invalid-authentication-header-wwwAuthenticateHeader-function-string/*',\n      (c) => {\n        handlerExecuted = true\n        return c.text('auth')\n      }\n    )\n\n    app.use(\n      '/auth-custom-invalid-authentication-header-wwwAuthenticateHeader-function-object/*',\n      bearerAuth({\n        token: tokens,\n        invalidAuthenticationHeader: {\n          wwwAuthenticateHeader: () => ({\n            error: 'Unauthorized',\n            error_description: 'Unauthorized',\n          }),\n        },\n      })\n    )\n    app.get(\n      '/auth-custom-invalid-authentication-header-wwwAuthenticateHeader-function-object/*',\n      (c) => {\n        handlerExecuted = true\n        return c.text('auth')\n      }\n    )\n\n    app.use(\n      '/auth-custom-invalid-authentication-header-message-string/*',\n      bearerAuth({\n        token,\n        invalidAuthenticationHeader: {\n          message: 'Custom invalid authentication header message as string',\n        },\n      })\n    )\n    app.get('/auth-custom-invalid-authentication-header-message-string/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n\n    app.use(\n      '/auth-custom-invalid-authentication-header-message-object/*',\n      bearerAuth({\n        token,\n        invalidAuthenticationHeader: {\n          message: {\n            message: 'Custom invalid authentication header message as object',\n          },\n        },\n      })\n    )\n    app.get('/auth-custom-invalid-authentication-header-message-object/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n\n    app.use(\n      '/auth-custom-invalid-authentication-header-message-function-string/*',\n      bearerAuth({\n        token,\n        invalidAuthenticationHeader: {\n          message: () => 'Custom invalid authentication header message as function string',\n        },\n      })\n    )\n    app.get('/auth-custom-invalid-authentication-header-message-function-string/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n\n    app.use(\n      '/auth-custom-invalid-authentication-header-message-function-object/*',\n      bearerAuth({\n        token,\n        invalidAuthenticationHeader: {\n          message: () => ({\n            message: 'Custom invalid authentication header message as function object',\n          }),\n        },\n      })\n    )\n    app.get('/auth-custom-invalid-authentication-header-message-function-object/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n\n    app.use(\n      '/auth-custom-invalid-token-wwwAuthenticateHeader-string/*',\n      bearerAuth({\n        token: tokens,\n        invalidToken: {\n          wwwAuthenticateHeader: 'Bearer error=\"Unauthorized\",error_description=\"Unauthorized\"',\n        },\n      })\n    )\n    app.get('/auth-custom-invalid-token-wwwAuthenticateHeader-string/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n\n    app.use(\n      '/auth-custom-invalid-token-wwwAuthenticateHeader-object/*',\n      bearerAuth({\n        token: tokens,\n        invalidToken: {\n          wwwAuthenticateHeader: {\n            error: 'Unauthorized',\n            error_description: 'Unauthorized',\n          },\n        },\n      })\n    )\n    app.get('/auth-custom-invalid-token-wwwAuthenticateHeader-object/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n\n    app.use(\n      '/auth-custom-invalid-token-wwwAuthenticateHeader-function-string/*',\n      bearerAuth({\n        token: tokens,\n        invalidToken: {\n          wwwAuthenticateHeader: () =>\n            'Bearer error=\"Unauthorized\",error_description=\"Unauthorized\"',\n        },\n      })\n    )\n    app.get('/auth-custom-invalid-token-wwwAuthenticateHeader-function-string/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n\n    app.use(\n      '/auth-custom-invalid-token-wwwAuthenticateHeader-function-object/*',\n      bearerAuth({\n        token: tokens,\n        invalidToken: {\n          wwwAuthenticateHeader: () => ({\n            error: 'Unauthorized',\n            error_description: 'Unauthorized',\n          }),\n        },\n      })\n    )\n    app.get('/auth-custom-invalid-token-wwwAuthenticateHeader-function-object/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n\n    app.use(\n      '/auth-custom-invalid-token-message-string/*',\n      bearerAuth({\n        token,\n        invalidToken: { message: 'Custom invalid token message as string' },\n      })\n    )\n    app.get('/auth-custom-invalid-token-message-string/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n\n    app.use(\n      '/auth-custom-invalid-token-message-object/*',\n      bearerAuth({\n        token,\n        invalidToken: { message: { message: 'Custom invalid token message as object' } },\n      })\n    )\n    app.get('/auth-custom-invalid-token-message-object/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n\n    app.use(\n      '/auth-custom-invalid-token-message-function-string/*',\n      bearerAuth({\n        token,\n        invalidToken: { message: () => 'Custom invalid token message as function string' },\n      })\n    )\n    app.get('/auth-custom-invalid-token-message-function-string/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n\n    app.use(\n      '/auth-custom-invalid-token-message-function-object/*',\n      bearerAuth({\n        token,\n        invalidToken: {\n          message: () => ({\n            message: 'Custom invalid token message as function object',\n          }),\n        },\n      })\n    )\n    app.get('/auth-custom-invalid-token-message-function-object/*', (c) => {\n      handlerExecuted = true\n      return c.text('auth')\n    })\n  })\n\n  it('Should authorize', async () => {\n    const req = new Request('http://localhost/auth/a')\n    req.headers.set('Authorization', 'Bearer abcdefg12345-._~+/=')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(handlerExecuted).toBeTruthy()\n    expect(await res.text()).toBe('auth')\n    expect(res.headers.get('x-custom')).toBe('foo')\n  })\n\n  it.each([['bearer'], ['BEARER'], ['BeArEr']])(\n    'Should authorize - prefix is case-insensitive: %s',\n    async (prefix) => {\n      const req = new Request('http://localhost/auth/a')\n      req.headers.set('Authorization', `${prefix} ${token}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(handlerExecuted).toBeTruthy()\n    }\n  )\n\n  it('Should not authorize - no authorization header', async () => {\n    const req = new Request('http://localhost/auth/a')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(handlerExecuted).toBeFalsy()\n    expect(await res.text()).toBe('Unauthorized')\n    expect(res.headers.get('x-custom')).toBeNull()\n  })\n\n  it('Should not authorize - invalid request', async () => {\n    const req = new Request('http://localhost/auth/a')\n    req.headers.set('Authorization', 'Beare abcdefg12345-._~+/=')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(handlerExecuted).toBeFalsy()\n    expect(res.status).toBe(400)\n    expect(await res.text()).toBe('Bad Request')\n    expect(res.headers.get('x-custom')).toBeNull()\n  })\n\n  it('Should not authorize - invalid token', async () => {\n    const req = new Request('http://localhost/auth/a')\n    req.headers.set('Authorization', 'Bearer invalid-token')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(await res.text()).toBe('Unauthorized')\n    expect(res.headers.get('x-custom')).toBeNull()\n  })\n\n  it('Should not authorize - token is case-sensitive', async () => {\n    const req = new Request('http://localhost/auth/a')\n    req.headers.set('Authorization', `Bearer ${token.toUpperCase()}`)\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(await res.text()).toBe('Unauthorized')\n  })\n\n  it('Should authorize', async () => {\n    const req = new Request('http://localhost/authBot/a')\n    req.headers.set('Authorization', 'Bot abcdefg12345-._~+/=')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(handlerExecuted).toBeTruthy()\n    expect(await res.text()).toBe('auth bot')\n  })\n\n  it('Should not authorize - invalid request', async () => {\n    const req = new Request('http://localhost/authBot/a')\n    req.headers.set('Authorization', 'Bearer abcdefg12345-._~+/=')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(handlerExecuted).toBeFalsy()\n    expect(res.status).toBe(400)\n    expect(await res.text()).toBe('Bad Request')\n  })\n\n  it('Should authorize', async () => {\n    const req = new Request('http://localhost/apiKey/a')\n    req.headers.set('X-Api-Key', 'abcdefg12345-._~+/=')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(handlerExecuted).toBeTruthy()\n    expect(await res.text()).toBe('auth apiKey')\n  })\n\n  it('Should not authorize - invalid request', async () => {\n    const req = new Request('http://localhost/apiKey/a')\n    req.headers.set('Authorization', 'Bearer abcdefg12345-._~+/=')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(handlerExecuted).toBeFalsy()\n    expect(res.status).toBe(401)\n    expect(await res.text()).toBe('Unauthorized')\n  })\n\n  it('Should authorize - nested', async () => {\n    const req = new Request('http://localhost/nested/a')\n    req.headers.set('Authorization', 'Bearer abcdefg12345-._~+/=')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(handlerExecuted).toBeTruthy()\n    expect(await res.text()).toBe('auth nested')\n  })\n\n  it('Should not authorize - nested', async () => {\n    const req = new Request('http://localhost/nested/a')\n    req.headers.set('Authorization', 'Bearer invalid-token')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(handlerExecuted).toBeFalsy()\n    expect(res.status).toBe(401)\n    expect(await res.text()).toBe('Unauthorized')\n  })\n\n  it('Should authorize - with any token in list', async () => {\n    const req1 = new Request('http://localhost/auths/a')\n    req1.headers.set('Authorization', 'Bearer abcdefg12345-._~+/=')\n    const res1 = await app.request(req1)\n    expect(res1).not.toBeNull()\n    expect(res1.status).toBe(200)\n    expect(handlerExecuted).toBeTruthy()\n    expect(await res1.text()).toBe('auths')\n\n    const req2 = new Request('http://localhost/auths/a')\n    req2.headers.set('Authorization', 'Bearer alternative')\n    const res2 = await app.request(req2)\n    expect(res2).not.toBeNull()\n    expect(res2.status).toBe(200)\n    expect(handlerExecuted).toBeTruthy()\n    expect(await res2.text()).toBe('auths')\n  })\n\n  it('Should authorize - verifyToken option', async () => {\n    const res = await app.request('/auth-verify-token', {\n      headers: { Authorization: 'Bearer dynamic-token' },\n    })\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(handlerExecuted).toBeTruthy()\n    expect(await res.text()).toBe('auth-verify-token')\n  })\n\n  it('Should not authorize - verifyToken option', async () => {\n    const res = await app.request('/auth-verify-token', {\n      headers: { Authorization: 'Bearer invalid-token' },\n    })\n    expect(res).not.toBeNull()\n    expect(handlerExecuted).toBeFalsy()\n    expect(res.status).toBe(401)\n  })\n\n  it('Should authorize - custom header', async () => {\n    const req = new Request('http://localhost/auth-custom-header/a')\n    req.headers.set('X-Auth', 'Bearer abcdefg12345-._~+/=')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(handlerExecuted).toBeTruthy()\n    expect(await res.text()).toBe('auth-custom-header')\n  })\n\n  it('Should not authorize - custom header', async () => {\n    const req = new Request('http://localhost/auth-custom-header/a')\n    req.headers.set('X-Auth', 'Bearer invalid-token')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(handlerExecuted).toBeFalsy()\n    expect(res.status).toBe(401)\n    expect(await res.text()).toBe('Unauthorized')\n  })\n\n  it('Should not authorize - custom no authorization header wwwAuthenticateHeader as string', async () => {\n    const req = new Request(\n      'http://localhost/auth-custom-no-authentication-header-wwwAuthenticateHeader-string'\n    )\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(handlerExecuted).toBeFalsy()\n    expect(res.headers.get('WWW-Authenticate')).toBe(\n      'Bearer error=\"Unauthorized\",error_description=\"Unauthorized\"'\n    )\n  })\n\n  it('Should not authorize - custom no authorization header wwwAuthenticateHeader as object', async () => {\n    const req = new Request(\n      'http://localhost/auth-custom-no-authentication-header-wwwAuthenticateHeader-object'\n    )\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(handlerExecuted).toBeFalsy()\n    expect(res.headers.get('WWW-Authenticate')).toBe(\n      'Bearer error=\"Unauthorized\",error_description=\"Unauthorized\"'\n    )\n  })\n\n  it('Should not authorize - custom no authorization header wwwAuthenticateHeader as function string', async () => {\n    const req = new Request(\n      'http://localhost/auth-custom-no-authentication-header-wwwAuthenticateHeader-function-string'\n    )\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(handlerExecuted).toBeFalsy()\n    expect(res.headers.get('WWW-Authenticate')).toBe(\n      'Bearer error=\"Unauthorized\",error_description=\"Unauthorized\"'\n    )\n  })\n\n  it('Should not authorize - custom no authorization header wwwAuthenticateHeader as function object', async () => {\n    const req = new Request(\n      'http://localhost/auth-custom-no-authentication-header-wwwAuthenticateHeader-function-object'\n    )\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(handlerExecuted).toBeFalsy()\n    expect(res.headers.get('WWW-Authenticate')).toBe(\n      'Bearer error=\"Unauthorized\",error_description=\"Unauthorized\"'\n    )\n  })\n\n  it('Should not authorize - custom no authorization header message as string', async () => {\n    const req = new Request('http://localhost/auth-custom-no-authentication-header-message-string')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(handlerExecuted).toBeFalsy()\n    expect(await res.text()).toBe('Custom no authentication header message as string')\n  })\n\n  it('Should not authorize - custom no authorization header message as object', async () => {\n    const req = new Request('http://localhost/auth-custom-no-authentication-header-message-object')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(res.headers.get('Content-Type')).toMatch('application/json')\n    expect(handlerExecuted).toBeFalsy()\n    expect(await res.text()).toBe('{\"message\":\"Custom no authentication header message as object\"}')\n  })\n\n  it('Should not authorize - custom no authorization header message as function string', async () => {\n    const req = new Request(\n      'http://localhost/auth-custom-no-authentication-header-message-function-string'\n    )\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(handlerExecuted).toBeFalsy()\n    expect(await res.text()).toBe('Custom no authentication header message as function string')\n  })\n\n  it('Should not authorize - custom no authorization header message as function object', async () => {\n    const req = new Request(\n      'http://localhost/auth-custom-no-authentication-header-message-function-object'\n    )\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(res.headers.get('Content-Type')).toMatch('application/json')\n    expect(handlerExecuted).toBeFalsy()\n    expect(await res.text()).toBe(\n      '{\"message\":\"Custom no authentication header message as function object\"}'\n    )\n  })\n\n  it('Should not authorize - custom invalid authentication header wwwAuthenticateHeader as string', async () => {\n    const req = new Request(\n      'http://localhost/auth-custom-invalid-authentication-header-wwwAuthenticateHeader-string'\n    )\n    req.headers.set('Authorization', 'Beare abcdefg12345-._~+/=')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(400)\n    expect(handlerExecuted).toBeFalsy()\n    expect(res.headers.get('WWW-Authenticate')).toBe(\n      'Bearer error=\"Unauthorized\",error_description=\"Unauthorized\"'\n    )\n  })\n\n  it('Should not authorize - custom invalid authentication header wwwAuthenticateHeader as object', async () => {\n    const req = new Request(\n      'http://localhost/auth-custom-invalid-authentication-header-wwwAuthenticateHeader-object'\n    )\n    req.headers.set('Authorization', 'Beare abcdefg12345-._~+/=')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(400)\n    expect(handlerExecuted).toBeFalsy()\n    expect(res.headers.get('WWW-Authenticate')).toBe(\n      'Bearer error=\"Unauthorized\",error_description=\"Unauthorized\"'\n    )\n  })\n\n  it('Should not authorize - custom invalid authentication header wwwAuthenticateHeader as function string', async () => {\n    const req = new Request(\n      'http://localhost/auth-custom-invalid-authentication-header-wwwAuthenticateHeader-function-string'\n    )\n    req.headers.set('Authorization', 'Beare abcdefg12345-._~+/=')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(400)\n    expect(handlerExecuted).toBeFalsy()\n    expect(res.headers.get('WWW-Authenticate')).toBe(\n      'Bearer error=\"Unauthorized\",error_description=\"Unauthorized\"'\n    )\n  })\n\n  it('Should not authorize - custom invalid authentication header wwwAuthenticateHeader as function object', async () => {\n    const req = new Request(\n      'http://localhost/auth-custom-invalid-authentication-header-wwwAuthenticateHeader-function-object'\n    )\n    req.headers.set('Authorization', 'Beare abcdefg12345-._~+/=')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(400)\n    expect(handlerExecuted).toBeFalsy()\n    expect(res.headers.get('WWW-Authenticate')).toBe(\n      'Bearer error=\"Unauthorized\",error_description=\"Unauthorized\"'\n    )\n  })\n\n  it('Should not authorize - custom invalid authentication header message as string', async () => {\n    const req = new Request(\n      'http://localhost/auth-custom-invalid-authentication-header-message-string'\n    )\n    req.headers.set('Authorization', 'Beare abcdefg12345-._~+/=')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(400)\n    expect(handlerExecuted).toBeFalsy()\n    expect(await res.text()).toBe('Custom invalid authentication header message as string')\n  })\n\n  it('Should not authorize - custom invalid authentication header message as object', async () => {\n    const req = new Request(\n      'http://localhost/auth-custom-invalid-authentication-header-message-object'\n    )\n    req.headers.set('Authorization', 'Beare abcdefg12345-._~+/=')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(400)\n    expect(res.headers.get('Content-Type')).toMatch('application/json')\n    expect(handlerExecuted).toBeFalsy()\n    expect(await res.text()).toBe(\n      '{\"message\":\"Custom invalid authentication header message as object\"}'\n    )\n  })\n\n  it('Should not authorize - custom invalid authentication header message as function string', async () => {\n    const req = new Request(\n      'http://localhost/auth-custom-invalid-authentication-header-message-function-string'\n    )\n    req.headers.set('Authorization', 'Beare abcdefg12345-._~+/=')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(400)\n    expect(handlerExecuted).toBeFalsy()\n    expect(await res.text()).toBe('Custom invalid authentication header message as function string')\n  })\n\n  it('Should not authorize - custom invalid authentication header message as function object', async () => {\n    const req = new Request(\n      'http://localhost/auth-custom-invalid-authentication-header-message-function-object'\n    )\n    req.headers.set('Authorization', 'Beare abcdefg12345-._~+/=')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(400)\n    expect(res.headers.get('Content-Type')).toMatch('application/json')\n    expect(handlerExecuted).toBeFalsy()\n    expect(await res.text()).toBe(\n      '{\"message\":\"Custom invalid authentication header message as function object\"}'\n    )\n  })\n\n  it('Should not authorize - custom invalid token wwwAuthenticateHeader as string', async () => {\n    const req = new Request(\n      'http://localhost/auth-custom-invalid-token-wwwAuthenticateHeader-string'\n    )\n    req.headers.set('Authorization', 'Bearer invalid-token')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(handlerExecuted).toBeFalsy()\n    expect(res.headers.get('WWW-Authenticate')).toBe(\n      'Bearer error=\"Unauthorized\",error_description=\"Unauthorized\"'\n    )\n  })\n\n  it('Should not authorize - custom invalid token wwwAuthenticateHeader as object', async () => {\n    const req = new Request(\n      'http://localhost/auth-custom-invalid-token-wwwAuthenticateHeader-object'\n    )\n    req.headers.set('Authorization', 'Bearer invalid-token')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(handlerExecuted).toBeFalsy()\n    expect(res.headers.get('WWW-Authenticate')).toBe(\n      'Bearer error=\"Unauthorized\",error_description=\"Unauthorized\"'\n    )\n  })\n\n  it('Should not authorize - custom invalid token wwwAuthenticateHeader as function string', async () => {\n    const req = new Request(\n      'http://localhost/auth-custom-invalid-token-wwwAuthenticateHeader-function-string'\n    )\n    req.headers.set('Authorization', 'Bearer invalid-token')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(handlerExecuted).toBeFalsy()\n    expect(res.headers.get('WWW-Authenticate')).toBe(\n      'Bearer error=\"Unauthorized\",error_description=\"Unauthorized\"'\n    )\n  })\n\n  it('Should not authorize - custom invalid token wwwAuthenticateHeader as function object', async () => {\n    const req = new Request(\n      'http://localhost/auth-custom-invalid-token-wwwAuthenticateHeader-function-object'\n    )\n    req.headers.set('Authorization', 'Bearer invalid-token')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(handlerExecuted).toBeFalsy()\n    expect(res.headers.get('WWW-Authenticate')).toBe(\n      'Bearer error=\"Unauthorized\",error_description=\"Unauthorized\"'\n    )\n  })\n\n  it('Should not authorize - custom invalid token message as string', async () => {\n    const req = new Request('http://localhost/auth-custom-invalid-token-message-string')\n    req.headers.set('Authorization', 'Bearer invalid-token')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(handlerExecuted).toBeFalsy()\n    expect(await res.text()).toBe('Custom invalid token message as string')\n  })\n\n  it('Should not authorize - custom invalid token message as object', async () => {\n    const req = new Request('http://localhost/auth-custom-invalid-token-message-object')\n    req.headers.set('Authorization', 'Bearer invalid-token')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(res.headers.get('Content-Type')).toMatch('application/json')\n    expect(handlerExecuted).toBeFalsy()\n    expect(await res.text()).toBe('{\"message\":\"Custom invalid token message as object\"}')\n  })\n\n  it('Should not authorize - custom invalid token message as function string', async () => {\n    const req = new Request('http://localhost/auth-custom-invalid-token-message-function-string')\n    req.headers.set('Authorization', 'Bearer invalid-token')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(handlerExecuted).toBeFalsy()\n    expect(await res.text()).toBe('Custom invalid token message as function string')\n  })\n\n  it('Should not authorize - custom invalid token message as function object', async () => {\n    const req = new Request('http://localhost/auth-custom-invalid-token-message-function-object')\n    req.headers.set('Authorization', 'Bearer invalid-token')\n    const res = await app.request(req)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(401)\n    expect(res.headers.get('Content-Type')).toMatch('application/json')\n    expect(handlerExecuted).toBeFalsy()\n    expect(await res.text()).toBe('{\"message\":\"Custom invalid token message as function object\"}')\n  })\n})\n\ndescribe('Bearer Auth with prefix containing regex metacharacters', () => {\n  let app: Hono\n  let handlerExecuted: boolean\n  const token = 'testtoken123'\n\n  beforeEach(() => {\n    app = new Hono()\n    handlerExecuted = false\n  })\n\n  it('Should not throw when prefix contains regex metacharacters', () => {\n    expect(() => {\n      bearerAuth({ token, prefix: 'Bearer (v2)' })\n    }).not.toThrow()\n  })\n\n  it('Should authorize with prefix containing parentheses', async () => {\n    app.use('/*', bearerAuth({ token, prefix: 'Bearer(v2)' }))\n    app.get('/*', (c) => {\n      handlerExecuted = true\n      return c.text('ok')\n    })\n\n    const req = new Request('http://localhost/')\n    req.headers.set('Authorization', `Bearer(v2) ${token}`)\n    const res = await app.request(req)\n    expect(res.status).toBe(200)\n    expect(handlerExecuted).toBeTruthy()\n  })\n\n  it('Should authorize with prefix containing dots and plus signs', async () => {\n    app.use('/*', bearerAuth({ token, prefix: 'X.Auth+v2' }))\n    app.get('/*', (c) => {\n      handlerExecuted = true\n      return c.text('ok')\n    })\n\n    const req = new Request('http://localhost/')\n    req.headers.set('Authorization', `X.Auth+v2 ${token}`)\n    const res = await app.request(req)\n    expect(res.status).toBe(200)\n    expect(handlerExecuted).toBeTruthy()\n  })\n\n  it('Should not match when prefix metacharacters are interpreted as regex', async () => {\n    app.use('/*', bearerAuth({ token, prefix: 'X.Y' }))\n    app.get('/*', (c) => {\n      handlerExecuted = true\n      return c.text('ok')\n    })\n\n    const req = new Request('http://localhost/')\n    req.headers.set('Authorization', `XZY ${token}`)\n    const res = await app.request(req)\n    expect(res.status).toBe(400)\n    expect(handlerExecuted).toBeFalsy()\n  })\n})\n"
  },
  {
    "path": "src/middleware/bearer-auth/index.ts",
    "content": "/**\n * @module\n * Bearer Auth Middleware for Hono.\n */\n\nimport type { Context } from '../../context'\nimport { HTTPException } from '../../http-exception'\nimport type { MiddlewareHandler } from '../../types'\nimport { timingSafeEqual } from '../../utils/buffer'\nimport type { ContentfulStatusCode } from '../../utils/http-status'\n\nconst TOKEN_STRINGS = '[A-Za-z0-9._~+/-]+=*'\nconst PREFIX = 'Bearer'\nconst HEADER = 'Authorization'\n\ntype MessageFunction = (c: Context) => string | object | Promise<string | object>\ntype CustomizedErrorResponseOptions = {\n  wwwAuthenticateHeader?: string | object | MessageFunction\n  message?: string | object | MessageFunction\n}\n\ntype BearerAuthOptions =\n  | {\n      token: string | string[]\n      realm?: string\n      prefix?: string\n      headerName?: string\n      hashFunction?: Function\n      /**\n       * @deprecated Use noAuthenticationHeader.message instead\n       */\n      noAuthenticationHeaderMessage?: string | object | MessageFunction\n      noAuthenticationHeader?: CustomizedErrorResponseOptions\n      /**\n       * @deprecated Use invalidAuthenticationHeader.message instead\n       */\n      invalidAuthenticationHeaderMessage?: string | object | MessageFunction\n      invalidAuthenticationHeader?: CustomizedErrorResponseOptions\n      /**\n       * @deprecated Use invalidToken.message instead\n       */\n      invalidTokenMessage?: string | object | MessageFunction\n      invalidToken?: CustomizedErrorResponseOptions\n    }\n  | {\n      realm?: string\n      prefix?: string\n      headerName?: string\n      verifyToken: (token: string, c: Context) => boolean | Promise<boolean>\n      hashFunction?: Function\n      /**\n       * @deprecated Use noAuthenticationHeader.message instead\n       */\n      noAuthenticationHeaderMessage?: string | object | MessageFunction\n      noAuthenticationHeader?: CustomizedErrorResponseOptions\n      /**\n       * @deprecated Use invalidAuthenticationHeader.message instead\n       */\n      invalidAuthenticationHeaderMessage?: string | object | MessageFunction\n      invalidAuthenticationHeader?: CustomizedErrorResponseOptions\n      /**\n       * @deprecated Use invalidToken.message instead\n       */\n      invalidTokenMessage?: string | object | MessageFunction\n      invalidToken?: CustomizedErrorResponseOptions\n    }\n\n/**\n * Bearer Auth Middleware for Hono.\n *\n * @see {@link https://hono.dev/docs/middleware/builtin/bearer-auth}\n *\n * @param {BearerAuthOptions} options - The options for the bearer authentication middleware.\n * @param {string | string[]} [options.token] - The string or array of strings to validate the incoming bearer token against.\n * @param {Function} [options.verifyToken] - The function to verify the token.\n * @param {string} [options.realm=\"\"] - The domain name of the realm, as part of the returned WWW-Authenticate challenge header.\n * @param {string} [options.prefix=\"Bearer\"] - The prefix (or known as `schema`) for the Authorization header value. If set to the empty string, no prefix is expected.\n * @param {string} [options.headerName=Authorization] - The header name.\n * @param {Function} [options.hashFunction] - A function to handle hashing for safe comparison of authentication tokens.\n * @param {string | object | MessageFunction} [options.noAuthenticationHeader.message=\"Unauthorized\"] - The no authentication header message.\n * @param {string | object | MessageFunction} [options.noAuthenticationHeader.wwwAuthenticateHeader=\"Bearer realm=\\\"\\\"\"] - The response header value for the WWW-Authenticate header when no authentication header is provided.\n * @param {string | object | MessageFunction} [options.invalidAuthenticationHeader.message=\"Bad Request\"] - The invalid authentication header message.\n * @param {string | object | MessageFunction} [options.invalidAuthenticationHeader.wwwAuthenticateHeader=\"Bearer error=\\\"invalid_request\\\"\"] - The response header value for the WWW-Authenticate header when authentication header is invalid.\n * @param {string | object | MessageFunction} [options.invalidToken.message=\"Unauthorized\"] - The invalid token message.\n * @param {string | object | MessageFunction} [options.invalidToken.wwwAuthenticateHeader=\"Bearer error=\\\"invalid_token\\\"\"] - The response header value for the WWW-Authenticate header when token is invalid.\n * @returns {MiddlewareHandler} The middleware handler function.\n * @throws {Error} If neither \"token\" nor \"verifyToken\" options are provided.\n * @throws {HTTPException} If authentication fails, with 401 status code for missing or invalid token, or 400 status code for invalid request.\n *\n * @example\n * ```ts\n * const app = new Hono()\n *\n * const token = 'honoishot'\n *\n * app.use('/api/*', bearerAuth({ token }))\n *\n * app.get('/api/page', (c) => {\n *   return c.json({ message: 'You are authorized' })\n * })\n * ```\n */\nexport const bearerAuth = (options: BearerAuthOptions): MiddlewareHandler => {\n  if (!('token' in options || 'verifyToken' in options)) {\n    throw new Error('bearer auth middleware requires options for \"token\"')\n  }\n  if (!options.realm) {\n    options.realm = ''\n  }\n  if (options.prefix === undefined) {\n    options.prefix = PREFIX\n  }\n\n  const realm = options.realm?.replace(/\"/g, '\\\\\"')\n  const prefix = options.prefix\n  const tokenRegexp = new RegExp(`^${TOKEN_STRINGS}$`)\n  const wwwAuthenticatePrefix = prefix === '' ? '' : `${prefix} `\n\n  const throwHTTPException = async (\n    c: Context,\n    status: ContentfulStatusCode,\n    wwwAuthenticateHeader: string | object | MessageFunction,\n    messageOption: string | object | MessageFunction\n  ): Promise<Response> => {\n    const wwwAuthenticateHeaderValue: string | object =\n      typeof wwwAuthenticateHeader === 'function'\n        ? await wwwAuthenticateHeader(c)\n        : wwwAuthenticateHeader\n\n    const headers = {\n      'WWW-Authenticate':\n        typeof wwwAuthenticateHeaderValue === 'string'\n          ? wwwAuthenticateHeaderValue\n          : `${wwwAuthenticatePrefix}${Object.entries(wwwAuthenticateHeaderValue)\n              .map(([key, value]) => `${key}=\"${value}\"`)\n              .join(',')}`,\n    }\n    const responseMessage =\n      typeof messageOption === 'function' ? await messageOption(c) : messageOption\n    const res =\n      typeof responseMessage === 'string'\n        ? new Response(responseMessage, { status, headers })\n        : new Response(JSON.stringify(responseMessage), {\n            status,\n            headers: {\n              ...headers,\n              'content-type': 'application/json',\n            },\n          })\n    throw new HTTPException(status, { res })\n  }\n\n  return async function bearerAuth(c, next) {\n    const headerToken = c.req.header(options.headerName || HEADER)\n    if (!headerToken) {\n      // No Authorization header\n      await throwHTTPException(\n        c,\n        401,\n        options.noAuthenticationHeader?.wwwAuthenticateHeader ||\n          `${wwwAuthenticatePrefix}realm=\"${realm}\"`,\n        options.noAuthenticationHeader?.message ||\n          options.noAuthenticationHeaderMessage ||\n          'Unauthorized'\n      )\n    } else {\n      let tokenValue: string | undefined\n\n      if (prefix === '') {\n        tokenValue = headerToken\n      } else {\n        const headerLower = headerToken.toLowerCase()\n        const prefixLower = prefix.toLowerCase()\n        if (headerLower.startsWith(prefixLower) && headerToken[prefix.length] === ' ') {\n          tokenValue = headerToken.slice(prefix.length).trimStart()\n        }\n      }\n\n      if (!tokenValue || !tokenRegexp.test(tokenValue)) {\n        // Invalid Request\n        await throwHTTPException(\n          c,\n          400,\n          options.invalidAuthenticationHeader?.wwwAuthenticateHeader ||\n            `${wwwAuthenticatePrefix}error=\"invalid_request\"`,\n          options.invalidAuthenticationHeader?.message ||\n            options.invalidAuthenticationHeaderMessage ||\n            'Bad Request'\n        )\n      } else {\n        let equal = false\n        if ('verifyToken' in options) {\n          equal = await options.verifyToken(tokenValue, c)\n        } else if (typeof options.token === 'string') {\n          equal = await timingSafeEqual(options.token, tokenValue, options.hashFunction)\n        } else if (Array.isArray(options.token) && options.token.length > 0) {\n          for (const token of options.token) {\n            if (await timingSafeEqual(token, tokenValue, options.hashFunction)) {\n              equal = true\n              break\n            }\n          }\n        }\n        if (!equal) {\n          // Invalid Token\n          await throwHTTPException(\n            c,\n            401,\n            options.invalidToken?.wwwAuthenticateHeader ||\n              `${wwwAuthenticatePrefix}error=\"invalid_token\"`,\n            options.invalidToken?.message || options.invalidTokenMessage || 'Unauthorized'\n          )\n        }\n      }\n    }\n    await next()\n  }\n}\n"
  },
  {
    "path": "src/middleware/body-limit/index.test.ts",
    "content": "import { Hono } from '../../hono'\nimport { bodyLimit } from '.'\n\nconst buildRequestInit = (init: RequestInit = {}): RequestInit & { duplex: 'half' } => {\n  const headers: Record<string, string> = {\n    'Content-Type': 'text/plain',\n  }\n  if (typeof init.body === 'string') {\n    headers['Content-Length'] = init.body.length.toString()\n  }\n  return {\n    method: 'POST',\n    headers,\n    body: null,\n    ...init,\n    duplex: 'half',\n  }\n}\n\ndescribe('Body Limit Middleware', () => {\n  let app: Hono\n\n  const exampleText = 'hono is so hot' // 14byte\n  const exampleText2 = 'hono is so hot and cute' // 23byte\n\n  beforeEach(() => {\n    app = new Hono()\n    app.use('*', bodyLimit({ maxSize: 14 }))\n    app.get('/', (c) => c.text('index'))\n    app.post('/body-limit-15byte', async (c) => {\n      return c.text(await c.req.raw.text())\n    })\n  })\n\n  describe('GET request', () => {\n    it('should return 200 response', async () => {\n      const res = await app.request('/')\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('index')\n    })\n  })\n\n  describe('POST request', () => {\n    describe('string body', () => {\n      it('should return 200 response', async () => {\n        const res = await app.request('/body-limit-15byte', buildRequestInit({ body: exampleText }))\n\n        expect(res).not.toBeNull()\n        expect(res.status).toBe(200)\n        expect(await res.text()).toBe(exampleText)\n      })\n\n      it('should return 413 response', async () => {\n        const res = await app.request(\n          '/body-limit-15byte',\n          buildRequestInit({ body: exampleText2 })\n        )\n\n        expect(res).not.toBeNull()\n        expect(res.status).toBe(413)\n        expect(await res.text()).toBe('Payload Too Large')\n      })\n    })\n\n    describe('ReadableStream body', () => {\n      it('should return 200 response', async () => {\n        const contents = ['a', 'b', 'c']\n        const stream = new ReadableStream({\n          start(controller) {\n            while (contents.length) {\n              controller.enqueue(new TextEncoder().encode(contents.shift() as string))\n            }\n            controller.close()\n          },\n        })\n        const res = await app.request('/body-limit-15byte', buildRequestInit({ body: stream }))\n\n        expect(res).not.toBeNull()\n        expect(res.status).toBe(200)\n        expect(await res.text()).toBe('abc')\n      })\n\n      it('should return 413 response', async () => {\n        const readSpy = vi.fn().mockImplementation(() => {\n          return {\n            done: false,\n            value: new TextEncoder().encode(exampleText),\n          }\n        })\n        const stream = new ReadableStream()\n        vi.spyOn(stream, 'getReader').mockReturnValue({\n          read: readSpy,\n        } as unknown as ReadableStreamDefaultReader)\n        const res = await app.request('/body-limit-15byte', buildRequestInit({ body: stream }))\n\n        expect(res).not.toBeNull()\n        expect(res.status).toBe(413)\n        expect(readSpy).toHaveBeenCalledTimes(2)\n        expect(await res.text()).toBe('Payload Too Large')\n      })\n    })\n  })\n\n  describe('custom error handler', () => {\n    beforeEach(() => {\n      app = new Hono()\n      app.post(\n        '/text-limit-15byte-custom',\n        bodyLimit({\n          maxSize: 15,\n          onError: (c) => {\n            return c.text('no', 413)\n          },\n        }),\n        (c) => {\n          return c.text('yes')\n        }\n      )\n    })\n\n    it('should return the custom error handler', async () => {\n      const res = await app.request(\n        '/text-limit-15byte-custom',\n        buildRequestInit({ body: exampleText2 })\n      )\n\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(413)\n      expect(await res.text()).toBe('no')\n    })\n  })\n\n  describe('Transfer-Encoding and Content-Length headers', () => {\n    beforeEach(() => {\n      app = new Hono()\n      app.use('*', bodyLimit({ maxSize: 10 }))\n      app.post('/test', async (c) => {\n        return c.text(await c.req.text())\n      })\n    })\n\n    it('should prioritize Transfer-Encoding over Content-Length', async () => {\n      // Create a chunked body that exceeds the limit\n      const largeContent = 'this is a large content that exceeds 10 bytes'\n      const chunks = [largeContent.slice(0, 20), largeContent.slice(20)]\n\n      const stream = new ReadableStream({\n        start(controller) {\n          chunks.forEach((chunk) => {\n            controller.enqueue(new TextEncoder().encode(chunk))\n          })\n          controller.close()\n        },\n      })\n\n      const res = await app.request('/test', {\n        method: 'POST',\n        headers: {\n          'Content-Length': '5', // Small content-length (bypass attempt)\n          'Transfer-Encoding': 'chunked', // But chunked encoding with large content\n        },\n        body: stream,\n        duplex: 'half',\n      } as RequestInit)\n\n      // Should reject based on actual chunked content size, not Content-Length\n      expect(res.status).toBe(413)\n    })\n\n    it('should handle only Content-Length header correctly', async () => {\n      const smallContent = 'small'\n      const res = await app.request('/test', buildRequestInit({ body: smallContent }))\n\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe(smallContent)\n    })\n\n    it('should handle only Transfer-Encoding header correctly', async () => {\n      const content = 'test'\n      const stream = new ReadableStream({\n        start(controller) {\n          controller.enqueue(new TextEncoder().encode(content))\n          controller.close()\n        },\n      })\n\n      const res = await app.request('/test', {\n        method: 'POST',\n        headers: {\n          'Transfer-Encoding': 'chunked',\n        },\n        body: stream,\n        duplex: 'half',\n      } as RequestInit)\n\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe(content)\n    })\n  })\n})\n"
  },
  {
    "path": "src/middleware/body-limit/index.ts",
    "content": "/**\n * @module\n * Body Limit Middleware for Hono.\n */\n\nimport type { Context } from '../../context'\nimport { HTTPException } from '../../http-exception'\nimport type { MiddlewareHandler } from '../../types'\n\nconst ERROR_MESSAGE = 'Payload Too Large'\n\ntype OnError = (c: Context) => Response | Promise<Response>\ntype BodyLimitOptions = {\n  maxSize: number\n  onError?: OnError\n}\n\nclass BodyLimitError extends Error {\n  constructor(message: string) {\n    super(message)\n    this.name = 'BodyLimitError'\n  }\n}\n\n/**\n * Body Limit Middleware for Hono.\n *\n * @see {@link https://hono.dev/docs/middleware/builtin/body-limit}\n *\n * @param {BodyLimitOptions} options - The options for the body limit middleware.\n * @param {number} options.maxSize - The maximum body size allowed.\n * @param {OnError} [options.onError] - The error handler to be invoked if the specified body size is exceeded.\n * @returns {MiddlewareHandler} The middleware handler function.\n *\n * @example\n * ```ts\n * const app = new Hono()\n *\n * app.post(\n *   '/upload',\n *   bodyLimit({\n *     maxSize: 50 * 1024, // 50kb\n *     onError: (c) => {\n *       return c.text('overflow :(', 413)\n *     },\n *   }),\n *   async (c) => {\n *     const body = await c.req.parseBody()\n *     if (body['file'] instanceof File) {\n *       console.log(`Got file sized: ${body['file'].size}`)\n *     }\n *     return c.text('pass :)')\n *   }\n * )\n * ```\n */\nexport const bodyLimit = (options: BodyLimitOptions): MiddlewareHandler => {\n  const onError: OnError =\n    options.onError ||\n    (() => {\n      const res = new Response(ERROR_MESSAGE, {\n        status: 413,\n      })\n      throw new HTTPException(413, { res })\n    })\n  const maxSize = options.maxSize\n\n  return async function bodyLimit(c, next) {\n    if (!c.req.raw.body) {\n      // maybe GET or HEAD request\n      return next()\n    }\n\n    const hasTransferEncoding = c.req.raw.headers.has('transfer-encoding')\n    const hasContentLength = c.req.raw.headers.has('content-length')\n\n    // RFC 7230: If both Transfer-Encoding and Content-Length are present,\n    // Transfer-Encoding takes precedence and Content-Length should be ignored\n    if (hasTransferEncoding && hasContentLength) {\n      // Both headers present - follow RFC 7230 and ignore Content-Length\n      // This might indicate request smuggling attempt\n    }\n\n    if (hasContentLength && !hasTransferEncoding) {\n      // Only Content-Length present - we can trust it\n      const contentLength = parseInt(c.req.raw.headers.get('content-length') || '0', 10)\n      return contentLength > maxSize ? onError(c) : next()\n    }\n\n    // Transfer-Encoding present (chunked) or no length headers\n\n    let size = 0\n    const rawReader = c.req.raw.body.getReader()\n    const reader = new ReadableStream({\n      async start(controller) {\n        try {\n          for (;;) {\n            const { done, value } = await rawReader.read()\n            if (done) {\n              break\n            }\n            size += value.length\n            if (size > maxSize) {\n              controller.error(new BodyLimitError(ERROR_MESSAGE))\n              break\n            }\n\n            controller.enqueue(value)\n          }\n        } finally {\n          controller.close()\n        }\n      },\n    })\n\n    const requestInit: RequestInit & { duplex: 'half' } = { body: reader, duplex: 'half' }\n    c.req.raw = new Request(c.req.raw, requestInit as RequestInit)\n\n    await next()\n\n    if (c.error instanceof BodyLimitError) {\n      c.res = await onError(c)\n    }\n  }\n}\n"
  },
  {
    "path": "src/middleware/cache/index.test.ts",
    "content": "import type { ExecutionContext } from '../../context'\nimport { Hono } from '../../hono'\nimport { cache } from '.'\n\n// Mock\nclass Context implements ExecutionContext {\n  passThroughOnException(): void {\n    throw new Error('Method not implemented.')\n  }\n  async waitUntil(promise: Promise<unknown>): Promise<void> {\n    await promise\n  }\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  props: any\n}\n\ndescribe('Customizing Caching Keys', () => {\n  const app = new Hono()\n\n  const dynamicCacheName = 'dynamic-cache-name'\n  app.use(\n    '/dynamic-cache-name/*',\n    cache({\n      cacheName: async () => dynamicCacheName,\n      wait: true,\n      cacheControl: 'max-age=10',\n    })\n  )\n  app.get('/dynamic-cache-name/', (c) => {\n    return c.text('cached')\n  })\n\n  const dynamicCacheKey = 'dynamic-cache-key'\n  app.use(\n    '/dynamic-cache-key/*',\n    cache({\n      cacheName: 'my-app-v1',\n      wait: true,\n      cacheControl: 'max-age=10',\n      keyGenerator: async () => dynamicCacheKey,\n    })\n  )\n  app.get('/dynamic-cache-key/', (c) => {\n    return c.text('cached')\n  })\n\n  app.use(\n    '/dynamic-cache/*',\n    cache({\n      cacheName: async () => dynamicCacheName,\n      cacheControl: 'max-age=10',\n      keyGenerator: async () => dynamicCacheKey,\n    })\n  )\n  app.get('/dynamic-cache/', (c) => {\n    return c.text('cached')\n  })\n\n  const ctx = new Context()\n\n  it('Should use dynamically generated cache name', async () => {\n    await app.request('http://localhost/dynamic-cache-name/', undefined, ctx)\n    const cache = await caches.open(dynamicCacheName)\n    const keys = Array.from(await cache.keys())\n    expect(keys.length).toBe(1)\n  })\n\n  it('Should use dynamically generated cache key', async () => {\n    await app.request('http://localhost/dynamic-cache-key/')\n    const cache = await caches.open('my-app-v1')\n    const response = await cache.match(dynamicCacheKey)\n    expect(response).not.toBeNull()\n  })\n\n  it('Should retrieve cached response with dynamic cache name and key', async () => {\n    await app.request('http://localhost/dynamic-cache/', undefined, undefined, ctx)\n    const res = await app.request('http://localhost/dynamic-cache/', undefined, undefined, ctx)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('cache-control')).toBe('max-age=10')\n  })\n})\n\ndescribe('Cache Middleware', () => {\n  const app = new Hono()\n\n  let count = 1\n  // wait, because this is test.\n  // You don't have to set `wait: true`.\n  app.use('/wait/*', cache({ cacheName: 'my-app-v1', wait: true, cacheControl: 'max-age=10' }))\n  app.get('/wait/', (c) => {\n    c.header('X-Count', `${count}`)\n    count++\n    return c.text('cached')\n  })\n\n  // Default, use `waitUntil`\n  app.use('/not-wait/*', cache({ cacheName: 'my-app-v1', cacheControl: 'max-age=10' }))\n  app.get('/not-wait/', (c) => {\n    return c.text('not cached')\n  })\n\n  app.use('/wait2/*', cache({ cacheName: 'my-app-v1', wait: true, cacheControl: 'max-age=10' }))\n  app.use('/wait2/*', cache({ cacheName: 'my-app-v1', wait: true, cacheControl: 'Max-Age=20' }))\n  app.get('/wait2/', (c) => {\n    return c.text('cached')\n  })\n\n  app.use('/wait3/*', cache({ cacheName: 'my-app-v1', wait: true, cacheControl: 'max-age=10' }))\n  app.use(\n    '/wait3/private/*',\n    cache({ cacheName: 'my-app-v1', wait: true, cacheControl: 'private' })\n  )\n  app.get('/wait3/private/', (c) => {\n    return c.text('cached')\n  })\n\n  app.use('/wait4/*', cache({ cacheName: 'my-app-v1', wait: true, cacheControl: 'max-age=10' }))\n  app.get('/wait4/', (c) => {\n    c.header('Cache-Control', 'private')\n    return c.text('cached')\n  })\n\n  app.use('/vary1/*', cache({ cacheName: 'my-app-v1', wait: true, vary: ['Accept'] }))\n  app.get('/vary1/', (c) => {\n    return c.text('cached')\n  })\n\n  app.use('/vary2/*', cache({ cacheName: 'my-app-v1', wait: true, vary: ['Accept'] }))\n  app.get('/vary2/', (c) => {\n    c.header('Vary', 'Accept-Encoding')\n    return c.text('cached')\n  })\n\n  app.use(\n    '/vary3/*',\n    cache({ cacheName: 'my-app-v1', wait: true, vary: ['Accept', 'Accept-Encoding'] })\n  )\n  app.get('/vary3/', (c) => {\n    c.header('Vary', 'Accept-Language')\n    return c.text('cached')\n  })\n\n  app.use(\n    '/vary4/*',\n    cache({ cacheName: 'my-app-v1', wait: true, vary: ['Accept', 'Accept-Encoding'] })\n  )\n  app.get('/vary4/', (c) => {\n    c.header('Vary', 'Accept, Accept-Language')\n    return c.text('cached')\n  })\n\n  app.use('/vary5/*', cache({ cacheName: 'my-app-v1', wait: true, vary: 'Accept' }))\n  app.get('/vary5/', (c) => {\n    return c.text('cached with Accept and Accept-Encoding headers')\n  })\n\n  app.use(\n    '/vary6/*',\n    cache({ cacheName: 'my-app-v1', wait: true, vary: 'Accept, Accept-Encoding' })\n  )\n  app.get('/vary6/', (c) => {\n    c.header('Vary', 'Accept, Accept-Language')\n    return c.text('cached with Accept and Accept-Encoding headers as array')\n  })\n\n  app.use('/vary7/*', cache({ cacheName: 'my-app-v1', wait: true, vary: ['Accept'] }))\n  app.get('/vary7/', (c) => {\n    c.header('Vary', '*')\n    return c.text('cached')\n  })\n\n  let varyWildcardOnlyCount = 0\n  app.use('/vary-wildcard/*', cache({ cacheName: 'vary-wildcard-test', wait: true }))\n  app.get('/vary-wildcard/only', (c) => {\n    varyWildcardOnlyCount++\n    c.header('X-Count', `${varyWildcardOnlyCount}`)\n    c.header('Vary', '*')\n    return c.text('response')\n  })\n\n  let varyWildcardFirstCount = 0\n  app.get('/vary-wildcard/first', (c) => {\n    varyWildcardFirstCount++\n    c.header('X-Count', `${varyWildcardFirstCount}`)\n    c.header('Vary', '*, Accept')\n    return c.text('response')\n  })\n\n  let varyWildcardMiddleCount = 0\n  app.get('/vary-wildcard/middle', (c) => {\n    varyWildcardMiddleCount++\n    c.header('X-Count', `${varyWildcardMiddleCount}`)\n    c.header('Vary', 'Accept, *')\n    return c.text('response')\n  })\n\n  let varyWildcardComplexCount = 0\n  app.get('/vary-wildcard/complex', (c) => {\n    varyWildcardComplexCount++\n    c.header('X-Count', `${varyWildcardComplexCount}`)\n    c.header('Vary', 'Accept, *, Accept-Encoding')\n    return c.text('response')\n  })\n\n  let varyWildcardSpacesCount = 0\n  app.get('/vary-wildcard/spaces', (c) => {\n    varyWildcardSpacesCount++\n    c.header('X-Count', `${varyWildcardSpacesCount}`)\n    c.header('Vary', ' * ')\n    return c.text('response')\n  })\n\n  app.use('/default/*', cache({ cacheName: 'my-app-v1', wait: true, cacheControl: 'max-age=10' }))\n  app.all('/default/:code/', (c) => {\n    const code = parseInt(c.req.param('code'))\n    // Intended to avoid the following error: `RangeError: init[“status”] must be in the range of 200 to 599, inclusive.`\n    const res = {\n      status: code,\n      headers: new Headers(),\n      clone: () => res,\n    } as Response\n    return res\n  })\n\n  app.use(\n    '/custom/*',\n    cache({\n      cacheName: 'my-app-v1',\n      wait: true,\n      cacheControl: 'max-age=10',\n      cacheableStatusCodes: [200, 201],\n    })\n  )\n  app.get('/custom/:code/', (c) => {\n    const code = parseInt(c.req.param('code'))\n    // Intended to avoid the following error: `RangeError: init[“status”] must be in the range of 200 to 599, inclusive.`\n    const res = {\n      status: code,\n      headers: new Headers(),\n      clone: () => res,\n    } as Response\n    return res\n  })\n\n  const ctx = new Context()\n\n  it('Should return cached response', async () => {\n    await app.request('http://localhost/wait/')\n    const res = await app.request('http://localhost/wait/')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('cache-control')).toBe('max-age=10')\n    expect(res.headers.get('x-count')).toBe('1')\n  })\n\n  it('Should not return cached response', async () => {\n    await app.fetch(new Request('http://localhost/not-wait/'), undefined, ctx)\n    const res = await app.fetch(new Request('http://localhost/not-wait/'), undefined, ctx)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('cache-control')).toBe('max-age=10')\n  })\n\n  it('Should not return duplicate header values', async () => {\n    const res = await app.request('/wait2/')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('cache-control')).toBe('max-age=20')\n  })\n\n  it('Should return composed middleware header values', async () => {\n    const res = await app.request('/wait3/private/')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('cache-control')).toBe('private, max-age=10')\n  })\n\n  it('Should return composed handler header values', async () => {\n    const res = await app.request('/wait4/')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('cache-control')).toBe('private, max-age=10')\n  })\n\n  it('Should correctly apply a single Vary header from middleware', async () => {\n    const res = await app.request('http://localhost/vary1/')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('vary')).toBe('accept')\n  })\n\n  it('Should merge Vary headers from middleware and handler without duplicating', async () => {\n    const res = await app.request('http://localhost/vary2/')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('vary')).toBe('accept, accept-encoding')\n  })\n\n  it('Should deduplicate while merging multiple Vary headers from middleware and handler', async () => {\n    const res = await app.request('http://localhost/vary3/')\n    expect(res.headers.get('vary')).toBe('accept, accept-encoding, accept-language')\n  })\n\n  it('Should prevent duplication of Vary headers when identical ones are set by both middleware and handler', async () => {\n    const res = await app.request('http://localhost/vary4/')\n    expect(res.headers.get('vary')).toBe('accept, accept-encoding, accept-language')\n  })\n\n  it('Should correctly apply and return a single Vary header with Accept specified by middleware', async () => {\n    const res = await app.request('http://localhost/vary5/')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('vary')).toBe('accept')\n  })\n\n  it('Should merge Vary headers specified by middleware as a string with additional headers added by handler', async () => {\n    const res = await app.request('http://localhost/vary6/')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('vary')).toBe('accept, accept-encoding, accept-language')\n  })\n\n  it('Should prioritize the \"*\" Vary header from handler over any set by middleware', async () => {\n    const res = await app.request('http://localhost/vary7/')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('vary')).toBe('*')\n  })\n\n  it('Should not allow \"*\" as a Vary header in middleware configuration due to its impact on caching effectiveness', async () => {\n    expect(() => cache({ cacheName: 'my-app-v1', wait: true, vary: ['*'] })).toThrow()\n    expect(() => cache({ cacheName: 'my-app-v1', wait: true, vary: '*' })).toThrow()\n  })\n\n  it('Should not cache response when Vary: * is set', async () => {\n    await app.request('http://localhost/vary-wildcard/only')\n    const res = await app.request('http://localhost/vary-wildcard/only')\n    expect(res.headers.get('x-count')).toBe('2')\n  })\n\n  it('Should not cache response when Vary: *, Accept is set', async () => {\n    await app.request('http://localhost/vary-wildcard/first')\n    const res = await app.request('http://localhost/vary-wildcard/first')\n    expect(res.headers.get('x-count')).toBe('2')\n  })\n\n  it('Should not cache response when Vary: Accept, * is set', async () => {\n    await app.request('http://localhost/vary-wildcard/middle')\n    const res = await app.request('http://localhost/vary-wildcard/middle')\n    expect(res.headers.get('x-count')).toBe('2')\n  })\n\n  it('Should not cache response when Vary: Accept, *, Accept-Encoding is set', async () => {\n    await app.request('http://localhost/vary-wildcard/complex')\n    const res = await app.request('http://localhost/vary-wildcard/complex')\n    expect(res.headers.get('x-count')).toBe('2')\n  })\n\n  it('Should not cache response when Vary contains * with extra spaces', async () => {\n    await app.request('http://localhost/vary-wildcard/spaces')\n    const res = await app.request('http://localhost/vary-wildcard/spaces')\n    expect(res.headers.get('x-count')).toBe('2')\n  })\n\n  it.each([200])('Should cache %i in default cacheable status codes', async (code) => {\n    await app.request(`http://localhost/default/${code}/`)\n    const res = await app.request(`http://localhost/default/${code}/`)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(code)\n    expect(res.headers.get('cache-control')).toBe('max-age=10')\n  })\n\n  it.each([\n    100, 101, 102, 103, 201, 202, 205, 207, 208, 226, 302, 303, 304, 307, 308, 400, 401, 402, 403,\n    406, 407, 408, 409, 411, 412, 413, 415, 416, 417, 418, 421, 422, 423, 424, 425, 426, 428, 429,\n    431, 451, 500, 502, 503, 504, 505, 506, 507, 508, 510, 511,\n  ])('Should not cache %i in default cacheable status codes', async (code) => {\n    await app.request(`http://localhost/default/${code}/`)\n    const res = await app.request(`http://localhost/default/${code}/`)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(code)\n    expect(res.headers.get('cache-control')).not.toBe('max-age=10')\n  })\n\n  it.each([200, 201])('Should cache %i in custom cacheable status codes', async (code) => {\n    await app.request(`http://localhost/custom/${code}/`)\n    const res = await app.request(`http://localhost/custom/${code}/`)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(code)\n    expect(res.headers.get('cache-control')).toBe('max-age=10')\n  })\n\n  it.each([\n    100, 101, 102, 103, 202, 205, 207, 208, 226, 302, 303, 304, 307, 308, 400, 401, 402, 403, 406,\n    407, 408, 409, 411, 412, 413, 415, 416, 417, 418, 421, 422, 423, 424, 425, 426, 428, 429, 431,\n    451, 500, 502, 503, 504, 505, 506, 507, 508, 510, 511,\n  ])('Should not cache %i in custom cacheable status codes', async (code) => {\n    await app.request(`http://localhost/custom/${code}/`)\n    const res = await app.request(`http://localhost/custom/${code}/`)\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(code)\n    expect(res.headers.get('cache-control')).not.toBe('max-age=10')\n  })\n\n  it('Should not be enabled if caches is not defined', async () => {\n    vi.stubGlobal('caches', undefined)\n    const app = new Hono()\n    app.use(cache({ cacheName: 'my-app-v1', cacheControl: 'max-age=10' }))\n    app.get('/', (c) => {\n      return c.text('cached')\n    })\n    expect(caches).toBeUndefined()\n    const res = await app.request('/')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('cache-control')).toBe(null)\n  })\n})\n\ndescribe('Cache Skipping Logic', () => {\n  let putSpy: ReturnType<typeof vi.fn>\n\n  beforeEach(() => {\n    putSpy = vi.fn()\n    const mockCache = {\n      match: vi.fn().mockResolvedValue(undefined), // Always miss\n      put: putSpy, // We spy on this\n      keys: vi.fn().mockResolvedValue([]),\n    }\n\n    vi.stubGlobal('caches', {\n      open: vi.fn().mockResolvedValue(mockCache),\n    })\n  })\n\n  afterEach(() => {\n    vi.restoreAllMocks()\n  })\n\n  it('Should NOT cache response if Cache-Control contains \"private\"', async () => {\n    const app = new Hono()\n    app.use('*', cache({ cacheName: 'skip-test', wait: true }))\n    app.get('/', (c) => {\n      c.header('Cache-Control', 'private, max-age=3600')\n      return c.text('response')\n    })\n\n    const res = await app.request('/')\n    expect(res.status).toBe(200)\n    // IMPORTANT: put() should NOT be called\n    expect(putSpy).not.toHaveBeenCalled()\n  })\n\n  it('Should NOT cache response if Cache-Control contains \"no-store\"', async () => {\n    const app = new Hono()\n    app.use('*', cache({ cacheName: 'skip-test', wait: true }))\n    app.get('/', (c) => {\n      c.header('Cache-Control', 'no-store')\n      return c.text('response')\n    })\n\n    await app.request('/')\n    expect(putSpy).not.toHaveBeenCalled()\n  })\n\n  it('Should NOT cache response if Cache-Control contains no-cache=\"Set-Cookie\"', async () => {\n    const app = new Hono()\n    app.use('*', cache({ cacheName: 'skip-test', wait: true }))\n    app.get('/', (c) => {\n      c.header('Cache-Control', 'no-cache=\"Set-Cookie\"')\n      return c.text('response')\n    })\n\n    await app.request('/')\n    expect(putSpy).not.toHaveBeenCalled()\n  })\n\n  it('Should NOT cache response if Set-Cookie header is present', async () => {\n    const app = new Hono()\n    app.use('*', cache({ cacheName: 'skip-test', wait: true }))\n    app.get('/', (c) => {\n      c.header('Set-Cookie', 'session=secret')\n      return c.text('response')\n    })\n\n    await app.request('/')\n    expect(putSpy).not.toHaveBeenCalled()\n  })\n\n  it('Should cache normal responses (Control Test)', async () => {\n    const app = new Hono()\n    app.use('*', cache({ cacheName: 'skip-test', wait: true }))\n    app.get('/', (c) => {\n      return c.text('response')\n    })\n\n    await app.request('/')\n    // IMPORTANT: put() SHOULD be called for normal responses\n    expect(putSpy).toHaveBeenCalled()\n  })\n})\n"
  },
  {
    "path": "src/middleware/cache/index.ts",
    "content": "/**\n * @module\n * Cache Middleware for Hono.\n */\n\nimport type { Context } from '../../context'\nimport type { MiddlewareHandler } from '../../types'\nimport type { StatusCode } from '../../utils/http-status'\n\n/**\n * status codes that can be cached by default.\n */\nconst defaultCacheableStatusCodes: ReadonlyArray<StatusCode> = [200]\n\nconst shouldSkipCache = (res: Response) => {\n  const vary = res.headers.get('Vary')\n  if (vary && vary.includes('*')) {\n    return true\n  }\n\n  const cacheControl = res.headers.get('Cache-Control')\n  if (\n    cacheControl &&\n    /(?:^|,\\s*)(?:private|no-(?:store|cache))(?:\\s*(?:=|,|$))/i.test(cacheControl)\n  ) {\n    return true\n  }\n\n  if (res.headers.has('Set-Cookie')) {\n    return true\n  }\n\n  return false\n}\n\n/**\n * Cache Middleware for Hono.\n *\n * @see {@link https://hono.dev/docs/middleware/builtin/cache}\n *\n * @param {Object} options - The options for the cache middleware.\n * @param {string | Function} options.cacheName - The name of the cache. Can be used to store multiple caches with different identifiers.\n * @param {boolean} [options.wait=false] - A boolean indicating if Hono should wait for the Promise of the `cache.put` function to resolve before continuing with the request. Required to be true for the Deno environment.\n * @param {string} [options.cacheControl] - A string of directives for the `Cache-Control` header.\n * @param {string | string[]} [options.vary] - Sets the `Vary` header in the response. If the original response header already contains a `Vary` header, the values are merged, removing any duplicates.\n * @param {Function} [options.keyGenerator] - Generates keys for every request in the `cacheName` store. This can be used to cache data based on request parameters or context parameters.\n * @param {number[]} [options.cacheableStatusCodes=[200]] - An array of status codes that can be cached.\n * @returns {MiddlewareHandler} The middleware handler function.\n * @throws {Error} If the `vary` option includes \"*\".\n *\n * @example\n * ```ts\n * app.get(\n *   '*',\n *   cache({\n *     cacheName: 'my-app',\n *     cacheControl: 'max-age=3600',\n *   })\n * )\n * ```\n */\nexport const cache = (options: {\n  cacheName: string | ((c: Context) => Promise<string> | string)\n  wait?: boolean\n  cacheControl?: string\n  vary?: string | string[]\n  keyGenerator?: (c: Context) => Promise<string> | string\n  cacheableStatusCodes?: StatusCode[]\n}): MiddlewareHandler => {\n  if (!globalThis.caches) {\n    console.log('Cache Middleware is not enabled because caches is not defined.')\n    return async (_c, next) => await next()\n  }\n\n  if (options.wait === undefined) {\n    options.wait = false\n  }\n\n  const cacheControlDirectives = options.cacheControl\n    ?.split(',')\n    .map((directive) => directive.toLowerCase())\n  const varyDirectives = Array.isArray(options.vary)\n    ? options.vary\n    : options.vary?.split(',').map((directive) => directive.trim())\n  // RFC 7231 Section 7.1.4 specifies that \"*\" is not allowed in Vary header.\n  // See: https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.4\n  if (options.vary?.includes('*')) {\n    throw new Error(\n      'Middleware vary configuration cannot include \"*\", as it disallows effective caching.'\n    )\n  }\n\n  const cacheableStatusCodes = new Set<number>(\n    options.cacheableStatusCodes ?? defaultCacheableStatusCodes\n  )\n\n  const addHeader = (c: Context) => {\n    if (cacheControlDirectives) {\n      const existingDirectives =\n        c.res.headers\n          .get('Cache-Control')\n          ?.split(',')\n          .map((d) => d.trim().split('=', 1)[0]) ?? []\n      for (const directive of cacheControlDirectives) {\n        let [name, value] = directive.trim().split('=', 2)\n        name = name.toLowerCase()\n        if (!existingDirectives.includes(name)) {\n          c.header('Cache-Control', `${name}${value ? `=${value}` : ''}`, { append: true })\n        }\n      }\n    }\n\n    if (varyDirectives) {\n      const existingDirectives =\n        c.res.headers\n          .get('Vary')\n          ?.split(',')\n          .map((d) => d.trim()) ?? []\n\n      const vary = Array.from(\n        new Set(\n          [...existingDirectives, ...varyDirectives].map((directive) => directive.toLowerCase())\n        )\n      ).sort()\n\n      if (vary.includes('*')) {\n        c.header('Vary', '*')\n      } else {\n        c.header('Vary', vary.join(', '))\n      }\n    }\n  }\n\n  return async function cache(c, next) {\n    let key = c.req.url\n    if (options.keyGenerator) {\n      key = await options.keyGenerator(c)\n    }\n\n    const cacheName =\n      typeof options.cacheName === 'function' ? await options.cacheName(c) : options.cacheName\n    const cache = await caches.open(cacheName)\n    const response = await cache.match(key)\n    if (response) {\n      return new Response(response.body, response)\n    }\n\n    await next()\n    if (!cacheableStatusCodes.has(c.res.status)) {\n      return\n    }\n    addHeader(c)\n\n    if (shouldSkipCache(c.res)) {\n      return\n    }\n\n    const res = c.res.clone()\n    if (options.wait) {\n      await cache.put(key, res)\n    } else {\n      c.executionCtx.waitUntil(cache.put(key, res))\n    }\n  }\n}\n"
  },
  {
    "path": "src/middleware/combine/index.test.ts",
    "content": "import { Hono } from '../../hono'\nimport type { MiddlewareHandler } from '../../types'\nimport { every, except, some } from '.'\n\nconst nextMiddleware: MiddlewareHandler = async (_, next) => await next()\n\ndescribe('some', () => {\n  let app: Hono\n\n  beforeEach(() => {\n    app = new Hono()\n  })\n\n  it('Should call only the first middleware', async () => {\n    const middleware1 = vi.fn(nextMiddleware)\n    const middleware2 = vi.fn(nextMiddleware)\n\n    app.use('/', some(middleware1, middleware2))\n    app.get('/', (c) => {\n      return c.text('Hello World')\n    })\n    const res = await app.request('http://localhost/')\n\n    expect(middleware1).toBeCalled()\n    expect(middleware2).not.toBeCalled()\n    expect(await res.text()).toBe('Hello World')\n  })\n\n  it('Should try to call the second middleware if the first one throws an error', async () => {\n    const middleware1 = () => {\n      throw new Error('Error')\n    }\n    const middleware2 = vi.fn(nextMiddleware)\n\n    app.use('/', some(middleware1, middleware2))\n    app.get('/', (c) => {\n      return c.text('Hello World')\n    })\n    const res = await app.request('http://localhost/')\n\n    expect(middleware2).toBeCalled()\n    expect(await res.text()).toBe('Hello World')\n  })\n\n  it('Should try to call the second middleware if the first one returns false', async () => {\n    const middleware1 = () => false\n    const middleware2 = vi.fn(nextMiddleware)\n\n    app.use('/', some(middleware1, middleware2))\n    app.get('/', (c) => {\n      return c.text('Hello World')\n    })\n    const res = await app.request('http://localhost/')\n\n    expect(middleware2).toBeCalled()\n    expect(await res.text()).toBe('Hello World')\n  })\n\n  it('Should throw last error if all middleware throw an error', async () => {\n    const middleware1 = () => {\n      throw new Error('Error1')\n    }\n    const middleware2 = () => {\n      throw new Error('Error2')\n    }\n\n    app.use('/', some(middleware1, middleware2))\n    app.get('/', (c) => {\n      return c.text('Hello World')\n    })\n    app.onError((error, c) => {\n      return c.text(error.message)\n    })\n    const res = await app.request('http://localhost/')\n\n    expect(await res.text()).toBe('Error2')\n  })\n\n  it('Should throw error if all middleware return false', async () => {\n    const middleware1 = () => false\n    const middleware2 = () => false\n\n    app.use('/', some(middleware1, middleware2))\n    app.get('/', (c) => {\n      return c.text('Hello World')\n    })\n    app.onError((_, c) => {\n      return c.text('oops')\n    })\n    const res = await app.request('http://localhost/')\n\n    expect(await res.text()).toBe('oops')\n  })\n\n  it('Should not call skipped middleware even if an error is thrown', async () => {\n    const middleware1: MiddlewareHandler = async (_c, next) => {\n      await next()\n    }\n    const middleware2 = vi.fn(() => true)\n\n    app.use(\n      '/',\n      every(some(middleware1, middleware2), () => {\n        throw new Error('Error')\n      })\n    )\n    app.get('/', (c) => c.text('OK'))\n    app.onError((_, c) => {\n      return c.text('oops')\n    })\n    const res = await app.request('http://localhost/')\n\n    expect(middleware2).not.toBeCalled()\n    expect(await res.text()).toBe('oops')\n  })\n\n  it('Should not call skipped middleware even if an error is thrown with returning truthy value middleware', async () => {\n    const middleware1 = () => true\n    const middleware2 = vi.fn(() => true)\n\n    app.use(\n      '/',\n      every(some(middleware1, middleware2), () => {\n        throw new Error('Error')\n      })\n    )\n    app.get('/', (c) => c.text('OK'))\n    app.onError((_, c) => {\n      return c.text('oops')\n    })\n    const res = await app.request('http://localhost/')\n\n    expect(middleware2).not.toBeCalled()\n    expect(await res.text()).toBe('oops')\n  })\n})\n\ndescribe('every', () => {\n  let app: Hono\n\n  beforeEach(() => {\n    app = new Hono()\n  })\n\n  it('Should call all middleware', async () => {\n    const middleware1 = vi.fn(nextMiddleware)\n    const middleware2 = vi.fn(nextMiddleware)\n\n    app.use('/', every(middleware1, middleware2))\n    app.get('/', (c) => {\n      return c.text('Hello World')\n    })\n    const res = await app.request('http://localhost/')\n\n    expect(middleware1).toBeCalled()\n    expect(middleware2).toBeCalled()\n    expect(await res.text()).toBe('Hello World')\n  })\n\n  it('Should throw error if any middleware throws an error', async () => {\n    const middleware1 = () => {\n      throw new Error('Error1')\n    }\n    const middleware2 = vi.fn(nextMiddleware)\n\n    app.use('/', every(middleware1, middleware2))\n    app.get('/', (c) => {\n      return c.text('Hello World')\n    })\n    app.onError((error, c) => {\n      return c.text(error.message)\n    })\n    const res = await app.request('http://localhost/')\n\n    expect(await res.text()).toBe('Error1')\n    expect(middleware2).not.toBeCalled()\n  })\n\n  it('Should throw error if any middleware returns false', async () => {\n    const middleware1 = () => false\n    const middleware2 = vi.fn(nextMiddleware)\n\n    app.use('/', every(middleware1, middleware2))\n    app.get('/', (c) => {\n      return c.text('Hello World')\n    })\n    app.onError((_, c) => {\n      return c.text('oops')\n    })\n    const res = await app.request('http://localhost/')\n\n    expect(await res.text()).toBe('oops')\n    expect(middleware2).not.toBeCalled()\n  })\n\n  it('Should return the same response a middleware returns if it short-circuits the chain', async () => {\n    const middleware1: MiddlewareHandler = async (c) => {\n      return c.text('Hello Middleware 1')\n    }\n    const middleware2 = vi.fn(nextMiddleware)\n\n    app.use('/', every(middleware1, middleware2))\n    app.get('/', (c) => {\n      return c.text('Hello World')\n    })\n    const res = await app.request('http://localhost/')\n\n    expect(await res.text()).toBe('Hello Middleware 1')\n    expect(middleware2).not.toBeCalled()\n  })\n\n  it('Should pass the path params to middlewares', async () => {\n    const app = new Hono()\n    app.use('*', nextMiddleware)\n    const paramMiddleware: MiddlewareHandler = async (c) => {\n      return c.json(c.req.param(), 200)\n    }\n\n    app.use('/:id', every(paramMiddleware))\n    app.get('/:id', (c) => {\n      return c.text('Hello World')\n    })\n\n    const res = await app.request('http://localhost/123')\n    expect(await res.json()).toEqual({ id: '123' })\n  })\n})\n\ndescribe('except', () => {\n  let app: Hono\n\n  beforeEach(() => {\n    app = new Hono()\n  })\n\n  it('Should call all middleware, except the one that matches the condition', async () => {\n    const middleware1 = vi.fn(nextMiddleware)\n    const middleware2 = vi.fn(nextMiddleware)\n\n    app.use('*', except('/maintenance', middleware1, middleware2))\n    app.get('/maintenance', (c) => {\n      return c.text('Hello Maintenance')\n    })\n    app.get('*', (c) => {\n      return c.redirect('/maintenance')\n    })\n    let res = await app.request('http://localhost/')\n\n    expect(middleware1).toBeCalled()\n    expect(middleware2).toBeCalled()\n    expect(res.headers.get('location')).toBe('/maintenance')\n\n    middleware1.mockClear()\n    middleware2.mockClear()\n    res = await app.request('http://localhost/maintenance')\n\n    expect(middleware1).not.toBeCalled()\n    expect(middleware2).not.toBeCalled()\n    expect(await res.text()).toBe('Hello Maintenance')\n  })\n\n  it('Should call all middleware, except the one that matches some of the conditions', async () => {\n    const middleware1 = vi.fn(nextMiddleware)\n    const middleware2 = vi.fn(nextMiddleware)\n\n    app.use('*', except(['/maintenance', '/public/users/:id'], middleware1, middleware2))\n    app.get('/maintenance', (c) => {\n      return c.text('Hello Maintenance')\n    })\n    app.get('/public/users/:id', (c) => {\n      return c.text(`Hello Public User ${c.req.param('id')}`)\n    })\n    app.get('/secret', (c) => {\n      return c.text('Hello Secret')\n    })\n    let res = await app.request('http://localhost/secret')\n\n    expect(middleware1).toBeCalled()\n    expect(middleware2).toBeCalled()\n    expect(await res.text()).toBe('Hello Secret')\n\n    middleware1.mockClear()\n    middleware2.mockClear()\n    res = await app.request('http://localhost/maintenance')\n\n    expect(middleware1).not.toBeCalled()\n    expect(middleware2).not.toBeCalled()\n    expect(await res.text()).toBe('Hello Maintenance')\n\n    middleware1.mockClear()\n    middleware2.mockClear()\n    res = await app.request('http://localhost/public/users/123')\n\n    expect(middleware1).not.toBeCalled()\n    expect(middleware2).not.toBeCalled()\n    expect(await res.text()).toBe('Hello Public User 123')\n  })\n\n  it('Should call all middleware, except the one that matches some of the condition function', async () => {\n    const middleware1 = vi.fn(nextMiddleware)\n    const middleware2 = vi.fn(nextMiddleware)\n\n    app.use(\n      '*',\n      except(['/maintenance', (c) => !!c.req.path.match(/public/)], middleware1, middleware2)\n    )\n    app.get('/maintenance', (c) => {\n      return c.text('Hello Maintenance')\n    })\n    app.get('/public/users/:id', (c) => {\n      return c.text(`Hello Public User ${c.req.param('id')}`)\n    })\n    app.get('/secret', (c) => {\n      return c.text('Hello Secret')\n    })\n    let res = await app.request('http://localhost/secret')\n\n    expect(middleware1).toBeCalled()\n    expect(middleware2).toBeCalled()\n    expect(await res.text()).toBe('Hello Secret')\n\n    middleware1.mockClear()\n    middleware2.mockClear()\n    res = await app.request('http://localhost/maintenance')\n\n    expect(middleware1).not.toBeCalled()\n    expect(middleware2).not.toBeCalled()\n    expect(await res.text()).toBe('Hello Maintenance')\n\n    middleware1.mockClear()\n    middleware2.mockClear()\n    res = await app.request('http://localhost/public/users/123')\n\n    expect(middleware1).not.toBeCalled()\n    expect(middleware2).not.toBeCalled()\n    expect(await res.text()).toBe('Hello Public User 123')\n  })\n})\n"
  },
  {
    "path": "src/middleware/combine/index.ts",
    "content": "/**\n * @module\n * Combine Middleware for Hono.\n */\n\nimport { compose } from '../../compose'\nimport type { Context } from '../../context'\nimport { METHOD_NAME_ALL } from '../../router'\nimport { TrieRouter } from '../../router/trie-router'\nimport type { MiddlewareHandler, Next } from '../../types'\n\ntype Condition = (c: Context) => boolean\n\n/**\n * Create a composed middleware that runs the first middleware that returns true.\n *\n * @param middleware - An array of MiddlewareHandler or Condition functions.\n * Middleware is applied in the order it is passed, and if any middleware exits without returning\n * an exception first, subsequent middleware will not be executed.\n * You can also pass a condition function that returns a boolean value. If returns true\n * the evaluation will be halted, and rest of the middleware will not be executed.\n * @returns A composed middleware.\n *\n * @example\n * ```ts\n * import { some } from 'hono/combine'\n * import { bearerAuth } from 'hono/bearer-auth'\n * import { myRateLimit } from '@/rate-limit'\n *\n * // If client has a valid token, then skip rate limiting.\n * // Otherwise, apply rate limiting.\n * app.use('/api/*', some(\n *   bearerAuth({ token }),\n *   myRateLimit({ limit: 100 }),\n * ));\n * ```\n */\nexport const some = (...middleware: (MiddlewareHandler | Condition)[]): MiddlewareHandler => {\n  return async function some(c, next) {\n    let isNextCalled = false\n    const wrappedNext = () => {\n      isNextCalled = true\n      return next()\n    }\n\n    let lastError: unknown\n    for (const handler of middleware) {\n      try {\n        const result = await handler(c, wrappedNext)\n        if (result === true && !c.finalized) {\n          await wrappedNext()\n        } else if (result === false) {\n          lastError = new Error('No successful middleware found')\n          continue\n        }\n        lastError = undefined\n        break\n      } catch (error) {\n        lastError = error\n        if (isNextCalled) {\n          break\n        }\n      }\n    }\n    if (lastError) {\n      throw lastError\n    }\n  }\n}\n\n/**\n * Create a composed middleware that runs all middleware and throws an error if any of them fail.\n *\n * @param middleware - An array of MiddlewareHandler or Condition functions.\n * Middleware is applied in the order it is passed, and if any middleware throws an error,\n * subsequent middleware will not be executed.\n * You can also pass a condition function that returns a boolean value. If returns false\n * the evaluation will be halted, and rest of the middleware will not be executed.\n * @returns A composed middleware.\n *\n * @example\n * ```ts\n * import { some, every } from 'hono/combine'\n * import { bearerAuth } from 'hono/bearer-auth'\n * import { myCheckLocalNetwork } from '@/check-local-network'\n * import { myRateLimit } from '@/rate-limit'\n *\n * // If client is in local network, then skip authentication and rate limiting.\n * // Otherwise, apply authentication and rate limiting.\n * app.use('/api/*', some(\n *   myCheckLocalNetwork(),\n *   every(\n *     bearerAuth({ token }),\n *     myRateLimit({ limit: 100 }),\n *   ),\n * ));\n * ```\n */\nexport const every = (...middleware: (MiddlewareHandler | Condition)[]): MiddlewareHandler => {\n  return async function every(c, next) {\n    const currentRouteIndex = c.req.routeIndex\n    await compose(\n      middleware.map((m) => [\n        [\n          async (c: Context, next: Next) => {\n            c.req.routeIndex = currentRouteIndex // should be unchanged in this context\n            const res = await m(c, next)\n            if (res === false) {\n              throw new Error('Unmet condition')\n            }\n            return res\n          },\n        ],\n      ])\n    )(c, next)\n  }\n}\n\n/**\n * Create a composed middleware that runs all middleware except when the condition is met.\n *\n * @param condition - A string or Condition function.\n * If there are multiple targets to match any of them, they can be passed as an array.\n * If a string is passed, it will be treated as a path pattern to match.\n * If a Condition function is passed, it will be evaluated against the request context.\n * @param middleware - A composed middleware\n *\n * @example\n * ```ts\n * import { except } from 'hono/combine'\n * import { bearerAuth } from 'hono/bearer-auth\n *\n * // If client is accessing public API, then skip authentication.\n * // Otherwise, require a valid token.\n * app.use('/api/*', except(\n *   '/api/public/*',\n *   bearerAuth({ token }),\n * ));\n * ```\n */\nexport const except = (\n  condition: string | Condition | (string | Condition)[],\n  ...middleware: MiddlewareHandler[]\n): MiddlewareHandler => {\n  let router: TrieRouter<true> | undefined = undefined\n  const conditions = (Array.isArray(condition) ? condition : [condition])\n    .map((condition) => {\n      if (typeof condition === 'string') {\n        router ||= new TrieRouter()\n        router.add(METHOD_NAME_ALL, condition, true)\n      } else {\n        return condition\n      }\n    })\n    .filter(Boolean) as Condition[]\n\n  if (router) {\n    conditions.unshift((c: Context) => !!router?.match(METHOD_NAME_ALL, c.req.path)?.[0]?.[0]?.[0])\n  }\n\n  const handler = some((c: Context) => conditions.some((cond) => cond(c)), every(...middleware))\n  return async function except(c, next) {\n    await handler(c, next)\n  }\n}\n"
  },
  {
    "path": "src/middleware/compress/index.test.ts",
    "content": "import { stream, streamSSE } from '../../helper/streaming'\nimport { Hono } from '../../hono'\nimport { compress } from '.'\n\ndescribe('Compress Middleware', () => {\n  const app = new Hono()\n\n  // Apply compress middleware to all routes\n  app.use('*', compress())\n\n  // Test routes\n  app.get('/small', (c) => {\n    c.header('Content-Type', 'text/plain')\n    c.header('Content-Length', '5')\n    return c.text('small')\n  })\n  app.get('/large', (c) => {\n    c.header('Content-Type', 'text/plain')\n    c.header('Content-Length', '1024')\n    return c.text('a'.repeat(1024))\n  })\n  app.get('/small-json', (c) => {\n    c.header('Content-Type', 'application/json')\n    c.header('Content-Length', '26')\n    return c.json({ message: 'Hello, World!' })\n  })\n  app.get('/large-json', (c) => {\n    c.header('Content-Type', 'application/json')\n    c.header('Content-Length', '1024')\n    return c.json({ data: 'a'.repeat(1024), message: 'Large JSON' })\n  })\n  app.get('/no-transform', (c) => {\n    c.header('Content-Type', 'text/plain')\n    c.header('Content-Length', '1024')\n    c.header('Cache-Control', 'no-transform')\n    return c.text('a'.repeat(1024))\n  })\n  app.get('/jpeg-image', (c) => {\n    c.header('Content-Type', 'image/jpeg')\n    c.header('Content-Length', '1024')\n    return c.body(new Uint8Array(1024)) // Simulated JPEG data\n  })\n  app.get('/already-compressed', (c) => {\n    c.header('Content-Type', 'application/octet-stream')\n    c.header('Content-Encoding', 'br')\n    c.header('Content-Length', '1024')\n    return c.body(new Uint8Array(1024)) // Simulated compressed data\n  })\n  app.get('/transfer-encoding-deflate', (c) => {\n    c.header('Content-Type', 'application/octet-stream')\n    c.header('Transfer-Encoding', 'deflate')\n    c.header('Content-Length', '1024')\n    return c.body(new Uint8Array(1024)) // Simulated deflate data\n  })\n  app.get('/chunked', (c) => {\n    c.header('Content-Type', 'application/octet-stream')\n    c.header('Transfer-Encoding', 'chunked')\n    c.header('Content-Length', '1024')\n    return c.body(new Uint8Array(1024)) // Simulated chunked data\n  })\n  app.get('/stream', (c) =>\n    stream(c, async (stream) => {\n      c.header('Content-Type', 'text/plain')\n      // 60000 bytes\n      for (let i = 0; i < 10000; i++) {\n        await stream.write('chunk ')\n      }\n    })\n  )\n  app.get('/already-compressed-stream', (c) =>\n    stream(c, async (stream) => {\n      c.header('Content-Type', 'text/plain')\n      c.header('Content-Encoding', 'br')\n      // 60000 bytes\n      for (let i = 0; i < 10000; i++) {\n        await stream.write(new Uint8Array([0, 1, 2, 3, 4, 5])) // Simulated compressed data\n      }\n    })\n  )\n  app.get('/sse', (c) =>\n    streamSSE(c, async (stream) => {\n      for (let i = 0; i < 1000; i++) {\n        await stream.writeSSE({ data: 'chunk' })\n      }\n    })\n  )\n  app.notFound((c) => c.text('Custom NotFound', 404))\n\n  const testCompression = async (\n    path: string,\n    acceptEncoding: string,\n    expectedEncoding: string | null\n  ) => {\n    const req = new Request(`http://localhost${path}`, {\n      method: 'GET',\n      headers: new Headers({ 'Accept-Encoding': acceptEncoding }),\n    })\n    const res = await app.request(req)\n    expect(res.headers.get('Content-Encoding')).toBe(expectedEncoding)\n    return res\n  }\n\n  describe('Compression Behavior', () => {\n    it('should compress large responses with gzip', async () => {\n      const res = await testCompression('/large', 'gzip', 'gzip')\n      expect(res.headers.get('Content-Length')).toBeNull()\n      expect((await res.arrayBuffer()).byteLength).toBeLessThan(1024)\n    })\n\n    it('should compress large responses with deflate', async () => {\n      const res = await testCompression('/large', 'deflate', 'deflate')\n      expect((await res.arrayBuffer()).byteLength).toBeLessThan(1024)\n    })\n\n    it('should prioritize gzip over deflate when both are accepted', async () => {\n      await testCompression('/large', 'gzip, deflate', 'gzip')\n    })\n\n    it('should not compress small responses', async () => {\n      const res = await testCompression('/small', 'gzip, deflate', null)\n      expect(res.headers.get('Content-Length')).toBe('5')\n    })\n\n    it('should not compress when no Accept-Encoding is provided', async () => {\n      await testCompression('/large', '', null)\n    })\n\n    it('should not compress images', async () => {\n      const res = await testCompression('/jpeg-image', 'gzip', null)\n      expect(res.headers.get('Content-Type')).toBe('image/jpeg')\n      expect(res.headers.get('Content-Length')).toBe('1024')\n    })\n\n    it('should not compress already compressed responses', async () => {\n      const res = await testCompression('/already-compressed', 'gzip', 'br')\n      expect(res.headers.get('Content-Length')).toBe('1024')\n    })\n\n    it('should remove Content-Length when compressing', async () => {\n      const res = await testCompression('/large', 'gzip', 'gzip')\n      expect(res.headers.get('Content-Length')).toBeNull()\n    })\n\n    it('should not remove Content-Length when not compressing', async () => {\n      const res = await testCompression('/jpeg-image', 'gzip', null)\n      expect(res.headers.get('Content-Length')).toBeDefined()\n    })\n\n    it('should not compress transfer-encoding: deflate', async () => {\n      const res = await testCompression('/transfer-encoding-deflate', 'gzip', null)\n      expect(res.headers.get('Content-Length')).toBe('1024')\n      expect(res.headers.get('Transfer-Encoding')).toBe('deflate')\n    })\n\n    it('should not compress transfer-encoding: chunked', async () => {\n      const res = await testCompression('/chunked', 'gzip', null)\n      expect(res.headers.get('Content-Length')).toBe('1024')\n      expect(res.headers.get('Transfer-Encoding')).toBe('chunked')\n    })\n  })\n\n  describe('JSON Handling', () => {\n    it('should not compress small JSON responses', async () => {\n      const res = await testCompression('/small-json', 'gzip', null)\n      expect(res.headers.get('Content-Length')).toBe('26')\n    })\n\n    it('should compress large JSON responses', async () => {\n      const res = await testCompression('/large-json', 'gzip', 'gzip')\n      expect(res.headers.get('Content-Length')).toBeNull()\n      const decompressed = await decompressResponse(res)\n      const json = JSON.parse(decompressed)\n      expect(json.data.length).toBe(1024)\n      expect(json.message).toBe('Large JSON')\n    })\n  })\n\n  describe('Streaming Responses', () => {\n    it('should compress streaming responses written in multiple chunks', async () => {\n      const res = await testCompression('/stream', 'gzip', 'gzip')\n      const decompressed = await decompressResponse(res)\n      expect(decompressed.length).toBe(60000)\n    })\n\n    it('should not compress already compressed streaming responses', async () => {\n      const res = await testCompression('/already-compressed-stream', 'gzip', 'br')\n      expect((await res.arrayBuffer()).byteLength).toBe(60000)\n    })\n\n    it('should not compress server-sent events', async () => {\n      const res = await testCompression('/sse', 'gzip', null)\n      expect((await res.arrayBuffer()).byteLength).toBe(13000)\n    })\n  })\n\n  describe('Edge Cases', () => {\n    it('should not compress responses with Cache-Control: no-transform', async () => {\n      await testCompression('/no-transform', 'gzip', null)\n    })\n\n    it('should handle HEAD requests without compression', async () => {\n      const req = new Request('http://localhost/large', {\n        method: 'HEAD',\n        headers: new Headers({ 'Accept-Encoding': 'gzip' }),\n      })\n      const res = await app.request(req)\n      expect(res.headers.get('Content-Encoding')).toBeNull()\n    })\n\n    it('should compress custom 404 Not Found responses', async () => {\n      const res = await testCompression('/not-found', 'gzip', 'gzip')\n      expect(res.status).toBe(404)\n      const decompressed = await decompressResponse(res)\n      expect(decompressed).toBe('Custom NotFound')\n    })\n  })\n})\n\nasync function decompressResponse(res: Response): Promise<string> {\n  const decompressedStream = res.body!.pipeThrough(new DecompressionStream('gzip'))\n  const decompressedResponse = new Response(decompressedStream)\n  return await decompressedResponse.text()\n}\n"
  },
  {
    "path": "src/middleware/compress/index.ts",
    "content": "/**\n * @module\n * Compress Middleware for Hono.\n */\n\nimport type { MiddlewareHandler } from '../../types'\nimport { COMPRESSIBLE_CONTENT_TYPE_REGEX } from '../../utils/compress'\n\nconst ENCODING_TYPES = ['gzip', 'deflate'] as const\nconst cacheControlNoTransformRegExp = /(?:^|,)\\s*?no-transform\\s*?(?:,|$)/i\n\ninterface CompressionOptions {\n  encoding?: (typeof ENCODING_TYPES)[number]\n  threshold?: number\n}\n\n/**\n * Compress Middleware for Hono.\n *\n * @see {@link https://hono.dev/docs/middleware/builtin/compress}\n *\n * @param {CompressionOptions} [options] - The options for the compress middleware.\n * @param {'gzip' | 'deflate'} [options.encoding] - The compression scheme to allow for response compression. Either 'gzip' or 'deflate'. If not defined, both are allowed and will be used based on the Accept-Encoding header. 'gzip' is prioritized if this option is not provided and the client provides both in the Accept-Encoding header.\n * @param {number} [options.threshold=1024] - The minimum size in bytes to compress. Defaults to 1024 bytes.\n * @returns {MiddlewareHandler} The middleware handler function.\n *\n * @example\n * ```ts\n * const app = new Hono()\n *\n * app.use(compress())\n * ```\n */\nexport const compress = (options?: CompressionOptions): MiddlewareHandler => {\n  const threshold = options?.threshold ?? 1024\n\n  return async function compress(ctx, next) {\n    await next()\n\n    const contentLength = ctx.res.headers.get('Content-Length')\n\n    // Check if response should be compressed\n    if (\n      ctx.res.headers.has('Content-Encoding') || // already encoded\n      ctx.res.headers.has('Transfer-Encoding') || // already encoded or chunked\n      ctx.req.method === 'HEAD' || // HEAD request\n      (contentLength && Number(contentLength) < threshold) || // content-length below threshold\n      !shouldCompress(ctx.res) || // not compressible type\n      !shouldTransform(ctx.res) // cache-control: no-transform\n    ) {\n      return\n    }\n\n    const accepted = ctx.req.header('Accept-Encoding')\n    const encoding =\n      options?.encoding ?? ENCODING_TYPES.find((encoding) => accepted?.includes(encoding))\n    if (!encoding || !ctx.res.body) {\n      return\n    }\n\n    // Compress the response\n    const stream = new CompressionStream(encoding)\n    ctx.res = new Response(ctx.res.body.pipeThrough(stream), ctx.res)\n    ctx.res.headers.delete('Content-Length')\n    ctx.res.headers.set('Content-Encoding', encoding)\n  }\n}\n\nconst shouldCompress = (res: Response) => {\n  const type = res.headers.get('Content-Type')\n  return type && COMPRESSIBLE_CONTENT_TYPE_REGEX.test(type)\n}\n\nconst shouldTransform = (res: Response) => {\n  const cacheControl = res.headers.get('Cache-Control')\n  // Don't compress for Cache-Control: no-transform\n  // https://tools.ietf.org/html/rfc7234#section-5.2.2.4\n  return !cacheControl || !cacheControlNoTransformRegExp.test(cacheControl)\n}\n"
  },
  {
    "path": "src/middleware/context-storage/index.test.ts",
    "content": "import { Hono } from '../../hono'\nimport { contextStorage, getContext, tryGetContext } from '.'\n\ndescribe('Context Storage Middleware', () => {\n  type Env = {\n    Variables: {\n      message: string\n    }\n  }\n\n  const app = new Hono<Env>()\n\n  app.use(contextStorage())\n  app.use(async (c, next) => {\n    c.set('message', 'Hono is hot!!')\n    await next()\n  })\n  app.get('/', (c) => {\n    return c.text(getMessage())\n  })\n  app.get('/optional', (c) => {\n    const optionalContext = tryGetContext<Env>()\n    return c.text(optionalContext?.var.message ?? 'no context')\n  })\n\n  const getMessage = () => {\n    return getContext<Env>().var.message\n  }\n\n  it('Should get context', async () => {\n    const res = await app.request('/')\n    expect(await res.text()).toBe('Hono is hot!!')\n  })\n\n  it('Should return undefined when context is missing', () => {\n    expect(tryGetContext<Env>()).toBeUndefined()\n  })\n\n  it('Should get context when available via tryGetContext', async () => {\n    const res = await app.request('/optional')\n    expect(await res.text()).toBe('Hono is hot!!')\n  })\n})\n"
  },
  {
    "path": "src/middleware/context-storage/index.ts",
    "content": "/**\n * @module\n * Context Storage Middleware for Hono.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks'\nimport type { Context } from '../../context'\nimport type { Env, MiddlewareHandler } from '../../types'\n\nconst asyncLocalStorage = new AsyncLocalStorage<Context>()\n\n/**\n * Context Storage Middleware for Hono.\n *\n * @see {@link https://hono.dev/docs/middleware/builtin/context-storage}\n *\n * @returns {MiddlewareHandler} The middleware handler function.\n *\n * @example\n * ```ts\n * type Env = {\n *   Variables: {\n *     message: string\n *   }\n * }\n *\n * const app = new Hono<Env>()\n *\n * app.use(contextStorage())\n *\n * app.use(async (c, next) => {\n *   c.set('message', 'Hono is hot!!)\n *   await next()\n * })\n *\n * app.get('/', async (c) => { c.text(getMessage()) })\n *\n * const getMessage = () => {\n *   return getContext<Env>().var.message\n * }\n * ```\n */\nexport const contextStorage = (): MiddlewareHandler => {\n  return async function contextStorage(c, next) {\n    await asyncLocalStorage.run(c, next)\n  }\n}\n\nexport const tryGetContext = <E extends Env = Env>(): Context<E> | undefined => {\n  return asyncLocalStorage.getStore() as Context<E> | undefined\n}\n\nexport const getContext = <E extends Env = Env>(): Context<E> => {\n  const context = tryGetContext<E>()\n  if (!context) {\n    throw new Error('Context is not available')\n  }\n  return context\n}\n"
  },
  {
    "path": "src/middleware/cors/index.test.ts",
    "content": "import { Hono } from '../../hono'\nimport { cors } from '../../middleware/cors'\n\ndescribe('CORS by Middleware', () => {\n  const app = new Hono()\n\n  app.use('/api/*', cors())\n\n  app.use(\n    '/api2/*',\n    cors({\n      origin: 'http://example.com',\n      allowHeaders: ['X-Custom-Header', 'Upgrade-Insecure-Requests'],\n      allowMethods: ['POST', 'GET', 'OPTIONS'],\n      exposeHeaders: ['Content-Length', 'X-Kuma-Revision'],\n      maxAge: 600,\n      credentials: true,\n    })\n  )\n\n  app.use(\n    '/api3/*',\n    cors({\n      origin: ['http://example.com', 'http://example.org', 'http://example.dev'],\n    })\n  )\n\n  app.use(\n    '/api4/*',\n    cors({\n      origin: (origin) => (origin.endsWith('.example.com') ? origin : 'http://example.com'),\n    })\n  )\n\n  app.use('/api5/*', cors())\n\n  app.use(\n    '/api6/*',\n    cors({\n      origin: 'http://example.com',\n    })\n  )\n  app.use(\n    '/api6/*',\n    cors({\n      origin: 'http://example.com',\n    })\n  )\n\n  app.use(\n    '/api7/*',\n    cors({\n      origin: (origin) => (origin === 'http://example.com' ? origin : '*'),\n      allowMethods: (origin) =>\n        origin === 'http://example.com'\n          ? ['GET', 'HEAD', 'POST', 'PATCH', 'DELETE']\n          : ['GET', 'HEAD'],\n    })\n  )\n\n  app.use(\n    '/api8/*',\n    cors({\n      origin: (origin) =>\n        new Promise<string>((resolve) =>\n          resolve(origin.endsWith('.example.com') ? origin : 'http://example.com')\n        ),\n    })\n  )\n\n  app.use(\n    '/api9/*',\n    cors({\n      origin: (origin) =>\n        new Promise<string>((resolve) => resolve(origin === 'http://example.com' ? origin : '*')),\n      allowMethods: (origin) =>\n        new Promise<string[]>((resolve) =>\n          resolve(\n            origin === 'http://example.com'\n              ? ['GET', 'HEAD', 'POST', 'PATCH', 'DELETE']\n              : ['GET', 'HEAD']\n          )\n        ),\n    })\n  )\n\n  app.get('/api/abc', (c) => {\n    return c.json({ success: true })\n  })\n\n  app.get('/api/vary-header', () => {\n    return new Response(JSON.stringify({ success: true }), {\n      headers: {\n        Vary: 'X-Custom-Vary-Value',\n      },\n    })\n  })\n\n  app.get('/api2/abc', (c) => {\n    return c.json({ success: true })\n  })\n\n  app.get('/api3/abc', (c) => {\n    return c.json({ success: true })\n  })\n\n  app.get('/api3/vary-header', () => {\n    return new Response(JSON.stringify({ success: true }), {\n      headers: {\n        Vary: 'X-Custom-Vary-Value',\n      },\n    })\n  })\n\n  app.get('/api4/abc', (c) => {\n    return c.json({ success: true })\n  })\n\n  app.get('/api5/abc', () => {\n    return new Response(JSON.stringify({ success: true }))\n  })\n\n  app.get('/api7/abc', () => {\n    return new Response(JSON.stringify({ success: true }))\n  })\n\n  it('GET default', async () => {\n    const res = await app.request('http://localhost/api/abc')\n\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Access-Control-Allow-Origin')).toBe('*')\n    expect(res.headers.get('Vary')).toBeNull()\n  })\n\n  it('Preflight default', async () => {\n    const req = new Request('https://localhost/api/abc', { method: 'OPTIONS' })\n    req.headers.append('Access-Control-Request-Headers', 'X-PINGOTHER, Content-Type')\n    const res = await app.request(req)\n\n    expect(res.status).toBe(204)\n    expect(res.statusText).toBe('No Content')\n    expect(res.headers.get('Access-Control-Allow-Methods')?.split(',')[0]).toBe('GET')\n    expect(res.headers.get('Access-Control-Allow-Headers')?.split(',')).toEqual([\n      'X-PINGOTHER',\n      'Content-Type',\n    ])\n  })\n\n  it('Preflight with options', async () => {\n    const req = new Request('https://localhost/api2/abc', {\n      method: 'OPTIONS',\n      headers: { origin: 'http://example.com' },\n    })\n    const res = await app.request(req)\n\n    expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://example.com')\n    expect(res.headers.get('Vary')?.split(/\\s*,\\s*/)).toEqual(expect.arrayContaining(['Origin']))\n    expect(res.headers.get('Access-Control-Allow-Headers')?.split(/\\s*,\\s*/)).toEqual([\n      'X-Custom-Header',\n      'Upgrade-Insecure-Requests',\n    ])\n    expect(res.headers.get('Access-Control-Allow-Methods')?.split(/\\s*,\\s*/)).toEqual([\n      'POST',\n      'GET',\n      'OPTIONS',\n    ])\n    expect(res.headers.get('Access-Control-Expose-Headers')?.split(/\\s*,\\s*/)).toEqual([\n      'Content-Length',\n      'X-Kuma-Revision',\n    ])\n    expect(res.headers.get('Access-Control-Max-Age')).toBe('600')\n    expect(res.headers.get('Access-Control-Allow-Credentials')).toBe('true')\n  })\n\n  it('Disallow an unmatched origin', async () => {\n    const req = new Request('https://localhost/api2/abc', {\n      method: 'OPTIONS',\n      headers: { origin: 'http://example.net' },\n    })\n    const res = await app.request(req)\n    expect(res.headers.has('Access-Control-Allow-Origin')).toBeFalsy()\n  })\n\n  it('Allow multiple origins', async () => {\n    let req = new Request('http://localhost/api3/abc', {\n      headers: {\n        Origin: 'http://example.org',\n      },\n    })\n    let res = await app.request(req)\n    expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://example.org')\n\n    req = new Request('http://localhost/api3/abc')\n    res = await app.request(req)\n    expect(\n      res.headers.has('Access-Control-Allow-Origin'),\n      'An unmatched origin should be disallowed'\n    ).toBeFalsy()\n\n    req = new Request('http://localhost/api3/abc', {\n      headers: {\n        Referer: 'http://example.net/',\n      },\n    })\n    res = await app.request(req)\n    expect(\n      res.headers.has('Access-Control-Allow-Origin'),\n      'An unmatched origin should be disallowed'\n    ).toBeFalsy()\n  })\n\n  it('Set \"Origin\" to Vary header', async () => {\n    const res = await app.request('http://localhost/api3/abc', {\n      headers: {\n        Origin: 'http://example.com',\n      },\n    })\n\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://example.com')\n    expect(res.headers.get('Vary')).toBe('Origin')\n  })\n\n  it('Keep original Vary header', async () => {\n    const res = await app.request('http://localhost/api/vary-header', {\n      headers: {\n        Origin: 'http://example.com',\n      },\n    })\n\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Access-Control-Allow-Origin')).toBe('*')\n    expect(res.headers.get('Vary')).toBe('X-Custom-Vary-Value')\n  })\n\n  it('Append \"Origin\" to Vary header, if response has some Vary header', async () => {\n    const res = await app.request('http://localhost/api3/vary-header', {\n      headers: {\n        Origin: 'http://example.com',\n      },\n    })\n\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://example.com')\n    expect(res.headers.get('Vary')).toBe('X-Custom-Vary-Value, Origin')\n  })\n\n  it('Allow origins by function', async () => {\n    let req = new Request('http://localhost/api4/abc', {\n      headers: {\n        Origin: 'http://subdomain.example.com',\n      },\n    })\n    let res = await app.request(req)\n    expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://subdomain.example.com')\n\n    req = new Request('http://localhost/api4/abc')\n    res = await app.request(req)\n    expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://example.com')\n\n    req = new Request('http://localhost/api4/abc', {\n      headers: {\n        Referer: 'http://evil-example.com/',\n      },\n    })\n    res = await app.request(req)\n    expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://example.com')\n  })\n\n  it('Allow origins by promise returning function', async () => {\n    let req = new Request('http://localhost/api8/abc', {\n      headers: {\n        Origin: 'http://subdomain.example.com',\n      },\n    })\n    let res = await app.request(req)\n    expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://subdomain.example.com')\n\n    req = new Request('http://localhost/api8/abc')\n    res = await app.request(req)\n    expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://example.com')\n\n    req = new Request('http://localhost/api8/abc', {\n      headers: {\n        Referer: 'http://evil-example.com/',\n      },\n    })\n    res = await app.request(req)\n    expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://example.com')\n  })\n\n  it('With raw Response object', async () => {\n    const res = await app.request('http://localhost/api5/abc')\n\n    expect(res.headers.get('Access-Control-Allow-Origin')).toBe('*')\n    expect(res.headers.get('Vary')).toBeNull()\n  })\n\n  it('Should not return duplicate header values', async () => {\n    const res = await app.request('http://localhost/api6/abc', {\n      headers: {\n        origin: 'http://example.com',\n      },\n    })\n\n    expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://example.com')\n  })\n\n  it('Allow methods by function', async () => {\n    const req = new Request('http://localhost/api7/abc', {\n      headers: {\n        Origin: 'http://example.com',\n      },\n      method: 'OPTIONS',\n    })\n    const res = await app.request(req)\n    expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://example.com')\n    expect(res.headers.get('Access-Control-Allow-Methods')).toBe('GET,HEAD,POST,PATCH,DELETE')\n\n    const req2 = new Request('http://localhost/api7/abc', {\n      headers: {\n        Origin: 'http://example.org',\n      },\n      method: 'OPTIONS',\n    })\n    const res2 = await app.request(req2)\n    expect(res2.headers.get('Access-Control-Allow-Origin')).toBe('*')\n    expect(res2.headers.get('Access-Control-Allow-Methods')).toBe('GET,HEAD')\n  })\n\n  it('Allow methods by promise returning function', async () => {\n    const req = new Request('http://localhost/api9/abc', {\n      headers: {\n        Origin: 'http://example.com',\n      },\n      method: 'OPTIONS',\n    })\n    const res = await app.request(req)\n    expect(res.headers.get('Access-Control-Allow-Origin')).toBe('http://example.com')\n    expect(res.headers.get('Access-Control-Allow-Methods')).toBe('GET,HEAD,POST,PATCH,DELETE')\n\n    const req2 = new Request('http://localhost/api9/abc', {\n      headers: {\n        Origin: 'http://example.org',\n      },\n      method: 'OPTIONS',\n    })\n    const res2 = await app.request(req2)\n    expect(res2.headers.get('Access-Control-Allow-Origin')).toBe('*')\n    expect(res2.headers.get('Access-Control-Allow-Methods')).toBe('GET,HEAD')\n  })\n})\n"
  },
  {
    "path": "src/middleware/cors/index.ts",
    "content": "/**\n * @module\n * CORS Middleware for Hono.\n */\n\nimport type { Context } from '../../context'\nimport type { MiddlewareHandler } from '../../types'\n\ntype CORSOptions = {\n  origin:\n    | string\n    | string[]\n    | ((\n        origin: string,\n        c: Context\n      ) => Promise<string | undefined | null> | string | undefined | null)\n  allowMethods?: string[] | ((origin: string, c: Context) => Promise<string[]> | string[])\n  allowHeaders?: string[]\n  maxAge?: number\n  credentials?: boolean\n  exposeHeaders?: string[]\n}\n\n/**\n * CORS Middleware for Hono.\n *\n * @see {@link https://hono.dev/docs/middleware/builtin/cors}\n *\n * @param {CORSOptions} [options] - The options for the CORS middleware.\n * @param {string | string[] | ((origin: string, c: Context) => Promise<string | undefined | null> | string | undefined | null)} [options.origin='*'] - The value of \"Access-Control-Allow-Origin\" CORS header.\n * @param {string[] | ((origin: string, c: Context) => Promise<string[]> | string[])} [options.allowMethods=['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH']] - The value of \"Access-Control-Allow-Methods\" CORS header.\n * @param {string[]} [options.allowHeaders=[]] - The value of \"Access-Control-Allow-Headers\" CORS header.\n * @param {number} [options.maxAge] - The value of \"Access-Control-Max-Age\" CORS header.\n * @param {boolean} [options.credentials] - The value of \"Access-Control-Allow-Credentials\" CORS header.\n * @param {string[]} [options.exposeHeaders=[]] - The value of \"Access-Control-Expose-Headers\" CORS header.\n * @returns {MiddlewareHandler} The middleware handler function.\n *\n * @example\n * ```ts\n * const app = new Hono()\n *\n * app.use('/api/*', cors())\n * app.use(\n *   '/api2/*',\n *   cors({\n *     origin: 'http://example.com',\n *     allowHeaders: ['X-Custom-Header', 'Upgrade-Insecure-Requests'],\n *     allowMethods: ['POST', 'GET', 'OPTIONS'],\n *     exposeHeaders: ['Content-Length', 'X-Kuma-Revision'],\n *     maxAge: 600,\n *     credentials: true,\n *   })\n * )\n *\n * app.all('/api/abc', (c) => {\n *   return c.json({ success: true })\n * })\n * app.all('/api2/abc', (c) => {\n *   return c.json({ success: true })\n * })\n * ```\n */\nexport const cors = (options?: CORSOptions): MiddlewareHandler => {\n  const defaults: CORSOptions = {\n    origin: '*',\n    allowMethods: ['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH'],\n    allowHeaders: [],\n    exposeHeaders: [],\n  }\n  const opts = {\n    ...defaults,\n    ...options,\n  }\n\n  const findAllowOrigin = ((optsOrigin) => {\n    if (typeof optsOrigin === 'string') {\n      if (optsOrigin === '*') {\n        return () => optsOrigin\n      } else {\n        return (origin: string) => (optsOrigin === origin ? origin : null)\n      }\n    } else if (typeof optsOrigin === 'function') {\n      return optsOrigin\n    } else {\n      return (origin: string) => (optsOrigin.includes(origin) ? origin : null)\n    }\n  })(opts.origin)\n\n  const findAllowMethods = ((optsAllowMethods) => {\n    if (typeof optsAllowMethods === 'function') {\n      return optsAllowMethods\n    } else if (Array.isArray(optsAllowMethods)) {\n      return () => optsAllowMethods\n    } else {\n      return () => []\n    }\n  })(opts.allowMethods)\n\n  return async function cors(c, next) {\n    function set(key: string, value: string) {\n      c.res.headers.set(key, value)\n    }\n\n    const allowOrigin = await findAllowOrigin(c.req.header('origin') || '', c)\n    if (allowOrigin) {\n      set('Access-Control-Allow-Origin', allowOrigin)\n    }\n\n    if (opts.credentials) {\n      set('Access-Control-Allow-Credentials', 'true')\n    }\n\n    if (opts.exposeHeaders?.length) {\n      set('Access-Control-Expose-Headers', opts.exposeHeaders.join(','))\n    }\n\n    if (c.req.method === 'OPTIONS') {\n      if (opts.origin !== '*') {\n        set('Vary', 'Origin')\n      }\n\n      if (opts.maxAge != null) {\n        set('Access-Control-Max-Age', opts.maxAge.toString())\n      }\n\n      const allowMethods = await findAllowMethods(c.req.header('origin') || '', c)\n      if (allowMethods.length) {\n        set('Access-Control-Allow-Methods', allowMethods.join(','))\n      }\n\n      let headers = opts.allowHeaders\n      if (!headers?.length) {\n        const requestHeaders = c.req.header('Access-Control-Request-Headers')\n        if (requestHeaders) {\n          headers = requestHeaders.split(/\\s*,\\s*/)\n        }\n      }\n      if (headers?.length) {\n        set('Access-Control-Allow-Headers', headers.join(','))\n        c.res.headers.append('Vary', 'Access-Control-Request-Headers')\n      }\n\n      c.res.headers.delete('Content-Length')\n      c.res.headers.delete('Content-Type')\n\n      return new Response(null, {\n        headers: c.res.headers,\n        status: 204,\n        statusText: 'No Content',\n      })\n    }\n    await next()\n\n    // Suppose the server sends a response with an Access-Control-Allow-Origin value with an explicit origin (rather than the \"*\" wildcard).\n    // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin\n    if (opts.origin !== '*') {\n      c.header('Vary', 'Origin', { append: true })\n    }\n  }\n}\n"
  },
  {
    "path": "src/middleware/csrf/index.test.ts",
    "content": "import type { Context } from '../../context'\nimport { Hono } from '../../hono'\nimport { csrf } from '../../middleware/csrf'\n\nconst simplePostHandler = vi.fn(async (c: Context) => {\n  if (c.req.header('content-type') === 'application/json') {\n    return c.text((await c.req.json<{ name: string }>())['name'])\n  } else {\n    const body = await c.req.parseBody<{ name: string }>()\n    return c.text(body['name'])\n  }\n})\n\nconst buildSimplePostRequestData = (options: { origin?: string; secFetchSite?: string } = {}) => ({\n  method: 'POST',\n  headers: Object.assign(\n    {\n      'content-type': 'application/x-www-form-urlencoded',\n    },\n    options?.origin ? { origin: options.origin } : {},\n    options?.secFetchSite ? { 'sec-fetch-site': options.secFetchSite } : {}\n  ) as Record<string, string>,\n  body: 'name=hono',\n})\n\ndescribe('CSRF by Middleware', () => {\n  beforeEach(() => {\n    simplePostHandler.mockClear()\n  })\n\n  describe('simple usage', () => {\n    const app = new Hono()\n\n    app.use('*', csrf())\n    app.get('/form', (c) => c.html('<form></form>'))\n    app.post('/form', simplePostHandler)\n    app.put('/form', (c) => c.text('OK'))\n    app.delete('/form', (c) => c.text('OK'))\n    app.patch('/form', (c) => c.text('OK'))\n\n    describe('GET /form', async () => {\n      it('should be 200 for any request', async () => {\n        const res = await app.request('http://localhost/form')\n\n        expect(res.status).toBe(200)\n        expect(await res.text()).toBe('<form></form>')\n      })\n    })\n\n    describe('HEAD /form', async () => {\n      it('should be 200 for any request', async () => {\n        const res = await app.request('http://localhost/form', { method: 'HEAD' })\n\n        expect(res.status).toBe(200)\n      })\n    })\n\n    describe('POST /form', async () => {\n      it('should be 200 for local request', async () => {\n        /*\n         * <form action=\"/form\" method=\"POST\"><input name=\"name\" value=\"hono\" /></form>\n         * or\n         * <script>\n         * fetch('/form', {\n         *   method: 'POST',\n         *   headers: {\n         *     'content-type': 'application/x-www-form-urlencoded',\n         *   },\n         *   body: 'name=hono',\n         * });\n         * </script>\n         */\n        const res = await app.request(\n          'http://localhost/form',\n          buildSimplePostRequestData({ origin: 'http://localhost' })\n        )\n\n        expect(res.status).toBe(200)\n        expect(await res.text()).toBe('hono')\n      })\n\n      it('should be 403 for \"application/x-www-form-urlencoded\" cross origin', async () => {\n        /*\n         * via http://example.com\n         *\n         * <form action=\"http://localhost/form\" method=\"POST\">\n         *   <input name=\"name\" value=\"hono\" />\n         * </form>\n         * or\n         * <script>\n         * fetch('http://localhost/form', {\n         *   method: 'POST',\n         *   headers: {\n         *     'content-type': 'application/x-www-form-urlencoded',\n         *   },\n         *   body: 'name=hono',\n         * });\n         * </script>\n         */\n        const res = await app.request(\n          'http://localhost/form',\n          buildSimplePostRequestData({ origin: 'http://example.com' })\n        )\n\n        expect(res.status).toBe(403)\n        expect(simplePostHandler).not.toHaveBeenCalled()\n      })\n    })\n\n    it('should be 403 for \"multipart/form-data\" cross origin', async () => {\n      /*\n       * via http://example.com\n       *\n       * <form action=\"http://localhost/form\" method=\"POST\" enctype=\"multipart/form-data\">\n       *   <input name=\"name\" value=\"hono\" />\n       * </form>\n       * or\n       * <script>\n       * fetch('http://localhost/form', {\n       *   method: 'POST',\n       *   headers: {\n       *     'content-type': 'multipart/form-data',\n       *   },\n       *   body: 'name=hono',\n       * });\n       * </script>\n       */\n      const res = await app.request(\n        'http://localhost/form',\n        buildSimplePostRequestData({ origin: 'http://example.com' })\n      )\n\n      expect(res.status).toBe(403)\n      expect(simplePostHandler).not.toHaveBeenCalled()\n    })\n\n    it('should be 403 for \"text/plain\" cross origin', async () => {\n      /*\n       * via http://example.com\n       *\n       * <form action=\"http://localhost/form\" method=\"POST\" enctype=\"text/plain\">\n       *   <input name=\"name\" value=\"hono\" />\n       * </form>\n       * or\n       * <script>\n       * fetch('http://localhost/form', {\n       *   method: 'POST',\n       *   headers: {\n       *     'content-type': 'text/plain',\n       *   },\n       *   body: 'name=hono',\n       * });\n       * </script>\n       */\n      const res = await app.request(\n        'http://localhost/form',\n        buildSimplePostRequestData({ origin: 'http://example.com' })\n      )\n\n      expect(res.status).toBe(403)\n      expect(simplePostHandler).not.toHaveBeenCalled()\n    })\n\n    it('should be 403 if request has no origin header', async () => {\n      const res = await app.request('http://localhost/form', buildSimplePostRequestData())\n\n      expect(res.status).toBe(403)\n      expect(simplePostHandler).not.toHaveBeenCalled()\n    })\n\n    it('should be 200 for application/json', async () => {\n      /*\n       * via http://example.com\n       * Assume localhost allows cross origin POST\n       *\n       * <script>\n       * fetch('http://localhost/form', {\n       *   method: 'POST',\n       *   headers: {\n       *     'content-type': 'application/json',\n       *   },\n       *   body: JSON.stringify({ name: 'hono' }),\n       * });\n       * </script>\n       */\n      const res = await app.request('http://localhost/form', {\n        method: 'POST',\n        headers: {\n          'content-type': 'application/json',\n          origin: 'http://example.com',\n        },\n        body: JSON.stringify({ name: 'hono' }),\n      })\n\n      expect(res.status).toBe(200)\n      expect(await res.text()).toBe('hono')\n    })\n\n    it('should be 403 for \"Application/x-www-form-urlencoded\" cross origin', async () => {\n      const res = await app.request('http://localhost/form', {\n        method: 'POST',\n        headers: Object.assign({\n          'content-type': 'Application/x-www-form-urlencoded',\n        }),\n        body: 'name=hono',\n      })\n      expect(res.status).toBe(403)\n      expect(simplePostHandler).not.toHaveBeenCalled()\n    })\n\n    it('should be 403 if the content-type is not set', async () => {\n      const res = await app.request('/form', {\n        method: 'POST',\n        body: new Blob(['test'], {}),\n      })\n      expect(res.status).toBe(403)\n      expect(simplePostHandler).not.toHaveBeenCalled()\n    })\n  })\n\n  describe('with origin option', () => {\n    describe('string', () => {\n      const app = new Hono()\n\n      app.use(\n        '*',\n        csrf({\n          origin: 'https://example.com',\n        })\n      )\n      app.post('/form', simplePostHandler)\n\n      it('should be 200 for allowed origin', async () => {\n        const res = await app.request(\n          'https://example.com/form',\n          buildSimplePostRequestData({ origin: 'https://example.com' })\n        )\n        expect(res.status).toBe(200)\n      })\n\n      it('should be 403 for not allowed origin', async () => {\n        const res = await app.request(\n          'https://example.jp/form',\n          buildSimplePostRequestData({ origin: 'https://example.jp' })\n        )\n        expect(res.status).toBe(403)\n        expect(simplePostHandler).not.toHaveBeenCalled()\n      })\n    })\n\n    describe('string[]', () => {\n      const app = new Hono()\n\n      app.use(\n        '*',\n        csrf({\n          origin: ['https://example.com', 'https://hono.example.com'],\n        })\n      )\n      app.post('/form', simplePostHandler)\n\n      it('should be 200 for allowed origin', async () => {\n        let res = await app.request(\n          'https://hono.example.com/form',\n          buildSimplePostRequestData({ origin: 'https://hono.example.com' })\n        )\n        expect(res.status).toBe(200)\n\n        res = await app.request(\n          'https://example.com/form',\n          buildSimplePostRequestData({ origin: 'https://example.com' })\n        )\n        expect(res.status).toBe(200)\n      })\n\n      it('should be 403 for not allowed origin', async () => {\n        const res = await app.request(\n          'http://example.jp/form',\n          buildSimplePostRequestData({ origin: 'http://example.jp' })\n        )\n        expect(res.status).toBe(403)\n        expect(simplePostHandler).not.toHaveBeenCalled()\n      })\n    })\n\n    describe('IsAllowedOriginHandler', () => {\n      const app = new Hono()\n\n      app.use(\n        '*',\n        csrf({\n          origin: (origin) => /https:\\/\\/(\\w+\\.)?example\\.com$/.test(origin),\n        })\n      )\n      app.post('/form', simplePostHandler)\n\n      it('should be 200 for allowed origin', async () => {\n        let res = await app.request(\n          'https://hono.example.com/form',\n          buildSimplePostRequestData({ origin: 'https://hono.example.com' })\n        )\n        expect(res.status).toBe(200)\n\n        res = await app.request(\n          'https://example.com/form',\n          buildSimplePostRequestData({ origin: 'https://example.com' })\n        )\n        expect(res.status).toBe(200)\n      })\n\n      it('should be 403 for not allowed origin', async () => {\n        let res = await app.request(\n          'http://honojs.hono.example.jp/form',\n          buildSimplePostRequestData({ origin: 'http://example.jp' })\n        )\n        expect(res.status).toBe(403)\n        expect(simplePostHandler).not.toHaveBeenCalled()\n\n        res = await app.request(\n          'http://example.jp/form',\n          buildSimplePostRequestData({ origin: 'http://example.jp' })\n        )\n        expect(res.status).toBe(403)\n        expect(simplePostHandler).not.toHaveBeenCalled()\n      })\n    })\n\n    describe('async IsAllowedOriginHandler', () => {\n      const app = new Hono()\n\n      app.use(\n        '*',\n        csrf({\n          origin: async (origin) => {\n            await new Promise((r) => setTimeout(r, 10))\n            return /https:\\/\\/(\\w+\\.)?example\\.com$/.test(origin)\n          },\n        })\n      )\n      app.post('/form', simplePostHandler)\n\n      it('should be 200 for allowed origin with async handler', async () => {\n        let res = await app.request(\n          'https://hono.example.com/form',\n          buildSimplePostRequestData({ origin: 'https://hono.example.com' })\n        )\n        expect(res.status).toBe(200)\n\n        res = await app.request(\n          'https://example.com/form',\n          buildSimplePostRequestData({ origin: 'https://example.com' })\n        )\n        expect(res.status).toBe(200)\n      })\n\n      it('should be 403 for not allowed origin with async handler', async () => {\n        let res = await app.request(\n          'http://honojs.hono.example.jp/form',\n          buildSimplePostRequestData({ origin: 'http://example.jp' })\n        )\n        expect(res.status).toBe(403)\n        expect(simplePostHandler).not.toHaveBeenCalled()\n\n        res = await app.request(\n          'http://example.jp/form',\n          buildSimplePostRequestData({ origin: 'http://example.jp' })\n        )\n        expect(res.status).toBe(403)\n        expect(simplePostHandler).not.toHaveBeenCalled()\n      })\n    })\n  })\n\n  describe('with secFetchSite option', () => {\n    describe('string', () => {\n      const app = new Hono()\n      app.use('*', csrf({ secFetchSite: 'same-origin' }))\n      app.post('/form', simplePostHandler)\n\n      it('should allow matching value', async () => {\n        const res = await app.request(\n          'http://localhost/form',\n          buildSimplePostRequestData({ secFetchSite: 'same-origin' })\n        )\n        expect(res.status).toBe(200)\n      })\n\n      it('should block non-matching value', async () => {\n        const res = await app.request(\n          'http://localhost/form',\n          buildSimplePostRequestData({ secFetchSite: 'cross-site' })\n        )\n        expect(res.status).toBe(403)\n      })\n\n      it('should block unknown values', async () => {\n        const res = await app.request(\n          'http://localhost/form',\n          buildSimplePostRequestData({ secFetchSite: 'any' })\n        )\n        expect(res.status).toBe(403)\n      })\n    })\n\n    describe('string[]', () => {\n      const app = new Hono()\n      app.use('*', csrf({ secFetchSite: ['same-origin', 'none'] }))\n      app.post('/form', simplePostHandler)\n\n      it('should allow \"same-origin\" value', async () => {\n        const res = await app.request(\n          'http://localhost/form',\n          buildSimplePostRequestData({ secFetchSite: 'same-origin' })\n        )\n        expect(res.status).toBe(200)\n      })\n\n      it('should allow \"none\" value', async () => {\n        const res = await app.request(\n          'http://localhost/form',\n          buildSimplePostRequestData({ secFetchSite: 'none' })\n        )\n        expect(res.status).toBe(200)\n      })\n\n      it('should block not included values', async () => {\n        const res = await app.request(\n          'http://localhost/form',\n          buildSimplePostRequestData({ secFetchSite: 'cross-site' })\n        )\n        expect(res.status).toBe(403)\n      })\n    })\n\n    describe('IsAllowedSecFetchSiteHandler', () => {\n      const app = new Hono()\n      app.use(\n        '*',\n        csrf({\n          secFetchSite: (secFetchSite, c) => {\n            if (secFetchSite === 'same-origin') {\n              return true\n            }\n            if (c.req.path.startsWith('/webhook/')) {\n              return true\n            }\n            return false\n          },\n        })\n      )\n      app.post('/form', simplePostHandler)\n      app.post('/webhook/test', simplePostHandler)\n\n      it('should use custom logic for allowed values', async () => {\n        const res = await app.request(\n          'http://localhost/form',\n          buildSimplePostRequestData({ secFetchSite: 'same-origin' })\n        )\n        expect(res.status).toBe(200)\n      })\n\n      it('should use custom logic for path-based bypass', async () => {\n        const res = await app.request(\n          'http://localhost/webhook/test',\n          buildSimplePostRequestData({ secFetchSite: 'cross-site' })\n        )\n        expect(res.status).toBe(200)\n      })\n\n      it('should block when custom logic returns false', async () => {\n        const res = await app.request(\n          'http://localhost/form',\n          buildSimplePostRequestData({ secFetchSite: 'cross-site' })\n        )\n        expect(res.status).toBe(403)\n      })\n    })\n\n    describe('async IsAllowedSecFetchSiteHandler', () => {\n      const app = new Hono()\n      app.use(\n        '*',\n        csrf({\n          secFetchSite: async (secFetchSite, c) => {\n            await new Promise((r) => setTimeout(r, 10))\n            if (secFetchSite === 'same-origin') {\n              return true\n            }\n            if (c.req.path.startsWith('/webhook/')) {\n              return true\n            }\n            return false\n          },\n        })\n      )\n      app.post('/form', simplePostHandler)\n      app.post('/webhook/test', simplePostHandler)\n\n      it('should use async custom logic for allowed values', async () => {\n        const res = await app.request(\n          'http://localhost/form',\n          buildSimplePostRequestData({ secFetchSite: 'same-origin' })\n        )\n        expect(res.status).toBe(200)\n      })\n\n      it('should use async custom logic for path-based bypass', async () => {\n        const res = await app.request(\n          'http://localhost/webhook/test',\n          buildSimplePostRequestData({ secFetchSite: 'cross-site' })\n        )\n        expect(res.status).toBe(200)\n      })\n\n      it('should block when async custom logic returns false', async () => {\n        const res = await app.request(\n          'http://localhost/form',\n          buildSimplePostRequestData({ secFetchSite: 'cross-site' })\n        )\n        expect(res.status).toBe(403)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/middleware/csrf/index.ts",
    "content": "/**\n * @module\n * CSRF Protection Middleware for Hono.\n */\n\nimport type { Context } from '../../context'\nimport { HTTPException } from '../../http-exception'\nimport type { MiddlewareHandler } from '../../types'\n\ntype IsAllowedOriginHandler = (origin: string, context: Context) => boolean | Promise<boolean>\n\nconst secFetchSiteValues = ['same-origin', 'same-site', 'none', 'cross-site'] as const\ntype SecFetchSite = (typeof secFetchSiteValues)[number]\n\nconst isSecFetchSite = (value: string): value is SecFetchSite =>\n  (secFetchSiteValues as readonly string[]).includes(value)\n\ntype IsAllowedSecFetchSiteHandler = (\n  secFetchSite: SecFetchSite,\n  context: Context\n) => boolean | Promise<boolean>\n\ninterface CSRFOptions {\n  origin?: string | string[] | IsAllowedOriginHandler\n  secFetchSite?: SecFetchSite | SecFetchSite[] | IsAllowedSecFetchSiteHandler\n}\n\nconst isSafeMethodRe = /^(GET|HEAD)$/\nconst isRequestedByFormElementRe =\n  /^\\b(application\\/x-www-form-urlencoded|multipart\\/form-data|text\\/plain)\\b/i\n\n/**\n * CSRF Protection Middleware for Hono.\n *\n * Protects against Cross-Site Request Forgery attacks by validating request origins\n * and sec-fetch-site headers. The request is allowed if either validation passes.\n *\n * @see {@link https://hono.dev/docs/middleware/builtin/csrf}\n *\n * @param {CSRFOptions} [options] - The options for the CSRF protection middleware.\n * @param {string|string[]|(origin: string, context: Context) => boolean} [options.origin] -\n *   Allowed origins for requests.\n *   - string: Single allowed origin (e.g., 'https://example.com')\n *   - string[]: Multiple allowed origins\n *   - function: Custom validation logic\n *   - Default: Only same origin as the request URL\n * @param {string|string[]|(secFetchSite: string, context: Context) => boolean} [options.secFetchSite] -\n *   Sec-Fetch-Site header validation. Standard values include 'same-origin', 'same-site', 'cross-site', 'none'.\n *   - string: Single allowed value (e.g., 'same-origin')\n *   - string[]: Multiple allowed values (e.g., ['same-origin', 'same-site'])\n *   - function: Custom validation with access to context\n *   - Default: Only allows 'same-origin'\n * @returns {MiddlewareHandler} The middleware handler function.\n *\n * @example\n * ```ts\n * const app = new Hono()\n *\n * // Default: both origin and sec-fetch-site validation\n * app.use('*', csrf())\n *\n * // Allow specific origins\n * app.use('*', csrf({ origin: 'https://example.com' }))\n * app.use('*', csrf({ origin: ['https://app.com', 'https://api.com'] }))\n *\n * // Allow specific sec-fetch-site values\n * app.use('*', csrf({ secFetchSite: 'same-origin' }))\n * app.use('*', csrf({ secFetchSite: ['same-origin', 'same-site'] }))\n *\n * // Dynamic sec-fetch-site validation\n * app.use('*', csrf({\n *   secFetchSite: (secFetchSite, c) => {\n *     // Always allow same-origin\n *     if (secFetchSite === 'same-origin') return true\n *     // Allow cross-site for webhook endpoints\n *     if (secFetchSite === 'cross-site' && c.req.path.startsWith('/webhook/')) {\n *       return true\n *     }\n *     return false\n *   }\n * }))\n *\n * // Dynamic origin validation\n * app.use('*', csrf({\n *   origin: (origin, c) => {\n *     // Allow same origin\n *     if (origin === new URL(c.req.url).origin) return true\n *     // Allow specific trusted domains\n *     return ['https://app.example.com', 'https://admin.example.com'].includes(origin)\n *   }\n * }))\n * ```\n */\nexport const csrf = (options?: CSRFOptions): MiddlewareHandler => {\n  const originHandler: IsAllowedOriginHandler = ((optsOrigin) => {\n    if (!optsOrigin) {\n      return (origin, c) => origin === new URL(c.req.url).origin\n    } else if (typeof optsOrigin === 'string') {\n      return (origin) => origin === optsOrigin\n    } else if (typeof optsOrigin === 'function') {\n      return optsOrigin\n    } else {\n      return (origin) => optsOrigin.includes(origin)\n    }\n  })(options?.origin)\n  const isAllowedOrigin = async (origin: string | undefined, c: Context) => {\n    if (origin === undefined) {\n      // denied always when origin header is not present\n      return false\n    }\n    return await originHandler(origin, c)\n  }\n\n  const secFetchSiteHandler: IsAllowedSecFetchSiteHandler = ((optsSecFetchSite) => {\n    if (!optsSecFetchSite) {\n      // Default: only allow same-origin\n      return (secFetchSite) => secFetchSite === 'same-origin'\n    } else if (typeof optsSecFetchSite === 'string') {\n      return (secFetchSite) => secFetchSite === optsSecFetchSite\n    } else if (typeof optsSecFetchSite === 'function') {\n      return optsSecFetchSite\n    } else {\n      return (secFetchSite) => optsSecFetchSite.includes(secFetchSite)\n    }\n  })(options?.secFetchSite)\n  const isAllowedSecFetchSite = async (secFetchSite: string | undefined, c: Context) => {\n    if (secFetchSite === undefined) {\n      // denied always when sec-fetch-site header is not present\n      return false\n    }\n    // type guard to check if the value is a valid SecFetchSite\n    if (!isSecFetchSite(secFetchSite)) {\n      return false\n    }\n    return await secFetchSiteHandler(secFetchSite, c)\n  }\n\n  return async function csrf(c, next) {\n    if (\n      !isSafeMethodRe.test(c.req.method) &&\n      isRequestedByFormElementRe.test(c.req.header('content-type') || 'text/plain') &&\n      !(await isAllowedSecFetchSite(c.req.header('sec-fetch-site'), c)) &&\n      !(await isAllowedOrigin(c.req.header('origin'), c))\n    ) {\n      const res = new Response('Forbidden', { status: 403 })\n      throw new HTTPException(403, { res })\n    }\n\n    await next()\n  }\n}\n"
  },
  {
    "path": "src/middleware/etag/digest.ts",
    "content": "const mergeBuffers = (\n  buffer1: ArrayBuffer | undefined,\n  buffer2: Uint8Array<ArrayBuffer>\n): Uint8Array<ArrayBuffer> => {\n  if (!buffer1) {\n    return buffer2\n  }\n  const merged = new Uint8Array<ArrayBuffer>(\n    new ArrayBuffer(buffer1.byteLength + buffer2.byteLength)\n  )\n  merged.set(new Uint8Array(buffer1), 0)\n  merged.set(buffer2, buffer1.byteLength)\n  return merged\n}\n\nexport const generateDigest = async (\n  stream: ReadableStream<Uint8Array<ArrayBuffer>> | null,\n  generator: (body: Uint8Array<ArrayBuffer>) => ArrayBuffer | Promise<ArrayBuffer>\n): Promise<string | null> => {\n  if (!stream) {\n    return null\n  }\n\n  let result: ArrayBuffer | undefined = undefined\n\n  const reader = stream.getReader()\n  for (;;) {\n    const { value, done } = await reader.read()\n    if (done) {\n      break\n    }\n\n    result = await generator(mergeBuffers(result, value))\n  }\n\n  if (!result) {\n    return null\n  }\n\n  return Array.prototype.map\n    .call(new Uint8Array(result), (x) => x.toString(16).padStart(2, '0'))\n    .join('')\n}\n"
  },
  {
    "path": "src/middleware/etag/index.test.ts",
    "content": "import { Hono } from '../../hono'\nimport { RETAINED_304_HEADERS, etag } from '.'\n\ndescribe('Etag Middleware', () => {\n  it('Should return etag header', async () => {\n    const app = new Hono()\n    app.use('/etag/*', etag())\n    app.get('/etag/abc', (c) => {\n      return c.text('Hono is hot')\n    })\n    app.get('/etag/def', (c) => {\n      return c.json({ message: 'Hono is hot' })\n    })\n    let res = await app.request('http://localhost/etag/abc')\n    expect(res.headers.get('ETag')).not.toBeFalsy()\n    expect(res.headers.get('ETag')).toBe('\"d104fafdb380655dab607c9bddc4d4982037afa1\"')\n\n    res = await app.request('http://localhost/etag/def')\n    expect(res.headers.get('ETag')).not.toBeFalsy()\n    expect(res.headers.get('ETag')).toBe('\"67340414f1a52c4669a6cec71f0ae04532b29249\"')\n  })\n\n  it('Should return etag header with another algorithm', async () => {\n    const app = new Hono()\n    app.use(\n      '/etag/*',\n      etag({\n        generateDigest: (body) =>\n          crypto.subtle.digest(\n            {\n              name: 'SHA-256',\n            },\n            body\n          ),\n      })\n    )\n    app.get('/etag/abc', (c) => {\n      return c.text('Hono is hot')\n    })\n    app.get('/etag/def', (c) => {\n      return c.json({ message: 'Hono is hot' })\n    })\n    let res = await app.request('http://localhost/etag/abc')\n    expect(res.headers.get('ETag')).not.toBeFalsy()\n    expect(res.headers.get('ETag')).toBe(\n      '\"ed00834279b4fd5dcdc7ab6a5c9774de8afb2de30da2c8e0f17d0952839b5370\"'\n    )\n\n    res = await app.request('http://localhost/etag/def')\n    expect(res.headers.get('ETag')).not.toBeFalsy()\n    expect(res.headers.get('ETag')).toBe(\n      '\"83b61a767db6e22afea68dd645b4d4597a06276c8ce7f895ad865cf4ab154ec4\"'\n    )\n  })\n\n  it('Should return etag header - binary', async () => {\n    const app = new Hono()\n    app.use('/etag/*', etag())\n    app.get('/etag', async (c) => {\n      return c.body(new Uint8Array(1))\n    })\n\n    const res = await app.request('http://localhost/etag')\n    expect(res.headers.get('ETag')).not.toBeFalsy()\n    const etagHeader = res.headers.get('ETag')\n    expect(etagHeader).toBe('\"5ba93c9db0cff93f52b521d7420e43f6eda2784f\"')\n  })\n\n  it('Should not be the same etag - arrayBuffer', async () => {\n    const app = new Hono()\n    app.use('/etag/*', etag())\n    app.get('/etag/ab1', (c) => {\n      return c.body(new ArrayBuffer(1))\n    })\n    app.get('/etag/ab2', (c) => {\n      return c.body(new ArrayBuffer(2))\n    })\n\n    let res = await app.request('http://localhost/etag/ab1')\n    const hash = res.headers.get('Etag')\n    res = await app.request('http://localhost/etag/ab2')\n    expect(res.headers.get('ETag')).not.toBe(hash)\n  })\n\n  it('Should not be the same etag - Uint8Array', async () => {\n    const app = new Hono()\n    app.use('/etag/*', etag())\n    app.get('/etag/ui1', (c) => {\n      return c.body(new Uint8Array([1, 2, 3]))\n    })\n    app.get('/etag/ui2', (c) => {\n      return c.body(new Uint8Array([1, 2, 3, 4]))\n    })\n\n    let res = await app.request('http://localhost/etag/ui1')\n    const hash = res.headers.get('Etag')\n    res = await app.request('http://localhost/etag/ui2')\n    expect(res.headers.get('ETag')).not.toBe(hash)\n  })\n\n  it('Should not be the same etag - ReadableStream', async () => {\n    const app = new Hono()\n    app.use('/etag/*', etag())\n    app.get('/etag/rs1', (c) => {\n      return c.body(\n        new ReadableStream({\n          start(controller) {\n            controller.enqueue(new Uint8Array([1]))\n            controller.enqueue(new Uint8Array([2]))\n            controller.close()\n          },\n        })\n      )\n    })\n    app.get('/etag/rs2', (c) => {\n      return c.body(\n        new ReadableStream({\n          start(controller) {\n            controller.enqueue(new Uint8Array([1]))\n            controller.enqueue(new Uint8Array([3]))\n            controller.close()\n          },\n        })\n      )\n    })\n\n    let res = await app.request('http://localhost/etag/rs1')\n    const hash = res.headers.get('Etag')\n    res = await app.request('http://localhost/etag/rs2')\n    expect(res.headers.get('ETag')).not.toBe(hash)\n  })\n\n  it('Should not return etag header when the stream is empty', async () => {\n    const app = new Hono()\n    app.use('/etag/*', etag())\n    app.get('/etag/abc', (c) => {\n      const stream = new ReadableStream({\n        start(controller) {\n          controller.close()\n        },\n      })\n      return c.body(stream)\n    })\n    const res = await app.request('http://localhost/etag/abc')\n    expect(res.status).toBe(200)\n    expect(res.headers.get('ETag')).toBeNull()\n  })\n\n  it('Should not return etag header when body is null', async () => {\n    const app = new Hono()\n    app.use('/etag/*', etag())\n    app.get('/etag/abc', () => new Response(null, { status: 500 }))\n    const res = await app.request('http://localhost/etag/abc')\n    expect(res.status).toBe(500)\n    expect(res.headers.get('ETag')).toBeNull()\n  })\n\n  it('Should return etag header - weak', async () => {\n    const app = new Hono()\n    app.use('/etag/*', etag({ weak: true }))\n    app.get('/etag/abc', (c) => {\n      return c.text('Hono is hot')\n    })\n\n    const res = await app.request('http://localhost/etag/abc')\n    expect(res.headers.get('ETag')).not.toBeFalsy()\n    expect(res.headers.get('ETag')).toBe('W/\"d104fafdb380655dab607c9bddc4d4982037afa1\"')\n  })\n\n  it('Should handle conditional GETs', async () => {\n    const app = new Hono()\n    app.use('/etag/*', etag())\n    app.get('/etag/ghi', (c) =>\n      c.text('Hono is great', 200, {\n        'cache-control': 'public, max-age=120',\n        date: 'Mon, Feb 27 2023 12:08:36 GMT',\n        expires: 'Mon, Feb 27 2023 12:10:36 GMT',\n        server: 'Upstream 1.2',\n        vary: 'Accept-Language',\n      })\n    )\n\n    // unconditional GET\n    let res = await app.request('http://localhost/etag/ghi')\n    expect(res.status).toBe(200)\n    expect(res.headers.get('ETag')).not.toBeFalsy()\n    const etagHeaderValue = res.headers.get('ETag') || ''\n\n    // conditional GET with the wrong ETag:\n    res = await app.request('http://localhost/etag/ghi', {\n      headers: {\n        'If-None-Match': '\"not the right etag\"',\n      },\n    })\n    expect(res.status).toBe(200)\n\n    // conditional GET with matching ETag:\n    res = await app.request('http://localhost/etag/ghi', {\n      headers: {\n        'If-None-Match': etagHeaderValue,\n      },\n    })\n    expect(res.status).toBe(304)\n    expect(res.headers.get('Etag')).toBe(etagHeaderValue)\n    expect(await res.text()).toBe('')\n    expect(res.headers.get('cache-control')).toBe('public, max-age=120')\n    expect(res.headers.get('date')).toBe('Mon, Feb 27 2023 12:08:36 GMT')\n    expect(res.headers.get('expires')).toBe('Mon, Feb 27 2023 12:10:36 GMT')\n    expect(res.headers.get('server')).toBeFalsy()\n    expect(res.headers.get('vary')).toBe('Accept-Language')\n\n    // conditional GET with matching ETag among list:\n    res = await app.request('http://localhost/etag/ghi', {\n      headers: {\n        'If-None-Match': `\"mismatch 1\", ${etagHeaderValue}, \"mismatch 2\"`,\n      },\n    })\n    expect(res.status).toBe(304)\n  })\n\n  it('Should not return duplicate etag header values', async () => {\n    const app = new Hono()\n    app.use('/etag/*', etag())\n    app.use('/etag/*', etag())\n    app.get('/etag/abc', (c) => c.text('Hono is hot'))\n\n    const res = await app.request('http://localhost/etag/abc')\n    expect(res.headers.get('ETag')).not.toBeFalsy()\n    expect(res.headers.get('ETag')).toBe('\"d104fafdb380655dab607c9bddc4d4982037afa1\"')\n  })\n\n  it('Should not override ETag headers from upstream', async () => {\n    const app = new Hono()\n    app.use('/etag/*', etag())\n    app.get('/etag/predefined', (c) =>\n      c.text('This response has an ETag', 200, { ETag: '\"f-0194-d\"' })\n    )\n\n    const res = await app.request('http://localhost/etag/predefined')\n    expect(res.headers.get('ETag')).toBe('\"f-0194-d\"')\n  })\n\n  it('Should retain the default and the specified headers', async () => {\n    const cacheControl = 'public, max-age=120'\n    const message = 'Hello!'\n    const app = new Hono()\n    app.use(\n      '/etag/*',\n      etag({\n        retainedHeaders: ['x-message-retain', ...RETAINED_304_HEADERS],\n      })\n    )\n    app.get('/etag', (c) => {\n      return c.text('Hono is hot', 200, {\n        'cache-control': cacheControl,\n        'x-message-retain': message,\n        'x-message': message,\n      })\n    })\n    const res = await app.request('/etag', {\n      headers: {\n        'If-None-Match': '\"d104fafdb380655dab607c9bddc4d4982037afa1\"',\n      },\n    })\n    expect(res.status).toBe(304)\n    expect(res.headers.get('ETag')).not.toBeFalsy()\n    expect(res.headers.get('ETag')).toBe('\"d104fafdb380655dab607c9bddc4d4982037afa1\"')\n    expect(res.headers.get('Cache-Control')).toBe(cacheControl)\n    expect(res.headers.get('x-message-retain')).toBe(message)\n    expect(res.headers.get('x-message')).toBeFalsy()\n  })\n\n  it('Should return 304 when weak ETag in If-None-Match matches the generated ETag', async () => {\n    const app = new Hono()\n    app.use('/etag/*', etag())\n    app.get('/etag/abc', (c) => {\n      return c.text('Hono is hot')\n    })\n    let res = await app.request('http://localhost/etag/abc')\n    const headerEtag = res.headers.get('ETag')!\n\n    expect(headerEtag).not.toBeFalsy()\n\n    res = await app.request('http://localhost/etag/abc', {\n      headers: {\n        'If-None-Match': 'W/\"d104fafdb380655dab607c9bddc4d4982037afa1\"',\n      },\n    })\n\n    expect(res.status).toBe(304)\n  })\n\n  describe('When crypto is not available', () => {\n    let _crypto: Crypto | undefined\n    beforeAll(() => {\n      _crypto = globalThis.crypto\n      Object.defineProperty(globalThis, 'crypto', {\n        value: {},\n      })\n    })\n\n    afterAll(() => {\n      Object.defineProperty(globalThis, 'crypto', {\n        value: _crypto,\n      })\n    })\n\n    it('Should not generate etag', async () => {\n      const app = new Hono()\n      app.use('/etag/*', etag())\n      app.get('/etag/no-digest', (c) => c.text('Hono is hot'))\n      const res = await app.request('/etag/no-digest')\n      expect(res.status).toBe(200)\n      expect(res.headers.get('ETag')).toBeNull()\n    })\n  })\n})\n"
  },
  {
    "path": "src/middleware/etag/index.ts",
    "content": "/**\n * @module\n * ETag Middleware for Hono.\n */\n\nimport type { MiddlewareHandler } from '../../types'\nimport { generateDigest } from './digest'\n\ntype ETagOptions = {\n  retainedHeaders?: string[]\n  weak?: boolean\n  generateDigest?: (body: Uint8Array<ArrayBuffer>) => ArrayBuffer | Promise<ArrayBuffer>\n}\n\n/**\n * Default headers to pass through on 304 responses. From the spec:\n * > The response must not contain a body and must include the headers that\n * > would have been sent in an equivalent 200 OK response: Cache-Control,\n * > Content-Location, Date, ETag, Expires, and Vary.\n */\nexport const RETAINED_304_HEADERS = [\n  'cache-control',\n  'content-location',\n  'date',\n  'etag',\n  'expires',\n  'vary',\n]\n\nconst stripWeak = (tag: string) => tag.replace(/^W\\//, '')\n\nfunction etagMatches(etag: string, ifNoneMatch: string | null) {\n  return (\n    ifNoneMatch != null && ifNoneMatch.split(/,\\s*/).some((t) => stripWeak(t) === stripWeak(etag))\n  )\n}\n\nfunction initializeGenerator(\n  generator?: ETagOptions['generateDigest']\n): ETagOptions['generateDigest'] | undefined {\n  if (!generator) {\n    if (crypto && crypto.subtle) {\n      generator = (body: Uint8Array<ArrayBuffer>) =>\n        crypto.subtle.digest(\n          {\n            name: 'SHA-1',\n          },\n          body\n        )\n    }\n  }\n\n  return generator\n}\n\n/**\n * ETag Middleware for Hono.\n *\n * @see {@link https://hono.dev/docs/middleware/builtin/etag}\n *\n * @param {ETagOptions} [options] - The options for the ETag middleware.\n * @param {boolean} [options.weak=false] - Define using or not using a weak validation. If true is set, then `W/` is added to the prefix of the value.\n * @param {string[]} [options.retainedHeaders=RETAINED_304_HEADERS] - The headers that you want to retain in the 304 Response.\n * @param {function(Uint8Array): ArrayBuffer | Promise<ArrayBuffer>} [options.generateDigest] -\n * A custom digest generation function. By default, it uses 'SHA-1'\n * This function is called with the response body as a `Uint8Array` and should return a hash as an `ArrayBuffer` or a Promise of one.\n * @returns {MiddlewareHandler} The middleware handler function.\n *\n * @example\n * ```ts\n * const app = new Hono()\n *\n * app.use('/etag/*', etag())\n * app.get('/etag/abc', (c) => {\n *   return c.text('Hono is hot')\n * })\n * ```\n */\nexport const etag = (options?: ETagOptions): MiddlewareHandler => {\n  const retainedHeaders = options?.retainedHeaders ?? RETAINED_304_HEADERS\n  const weak = options?.weak ?? false\n  const generator = initializeGenerator(options?.generateDigest)\n\n  return async function etag(c, next) {\n    const ifNoneMatch = c.req.header('If-None-Match') ?? null\n\n    await next()\n\n    const res = c.res as Response\n    let etag = res.headers.get('ETag')\n\n    if (!etag) {\n      if (!generator) {\n        return\n      }\n      const hash = await generateDigest(\n        // This type casing avoids the type error for `deno publish`\n        res.clone().body as ReadableStream<Uint8Array<ArrayBuffer>>,\n        generator\n      )\n      if (hash === null) {\n        return\n      }\n      etag = weak ? `W/\"${hash}\"` : `\"${hash}\"`\n    }\n\n    if (etagMatches(etag, ifNoneMatch)) {\n      c.res = new Response(null, {\n        status: 304,\n        statusText: 'Not Modified',\n        headers: {\n          ETag: etag,\n        },\n      })\n      c.res.headers.forEach((_, key) => {\n        if (retainedHeaders.indexOf(key.toLowerCase()) === -1) {\n          c.res.headers.delete(key)\n        }\n      })\n    } else {\n      c.res.headers.set('ETag', etag)\n    }\n  }\n}\n"
  },
  {
    "path": "src/middleware/ip-restriction/index.test.ts",
    "content": "import { Context } from '../../context'\nimport type { AddressType, GetConnInfo } from '../../helper/conninfo'\nimport { Hono } from '../../hono'\nimport { ipRestriction } from '.'\nimport type { IPRestrictionRule } from '.'\n\ndescribe('ipRestriction middleware', () => {\n  it('Should restrict', async () => {\n    const getConnInfo: GetConnInfo = (c) => {\n      return {\n        remote: {\n          address: c.env.ip,\n        },\n      }\n    }\n    const app = new Hono<{\n      Bindings: {\n        ip: string\n      }\n    }>()\n    app.use(\n      '/basic',\n      ipRestriction(getConnInfo, {\n        allowList: ['192.168.1.0', '192.168.2.0/24'],\n        denyList: ['192.168.2.10'],\n      })\n    )\n    app.get('/basic', (c) => c.text('Hello World!'))\n\n    app.use(\n      '/allow-empty',\n      ipRestriction(getConnInfo, {\n        denyList: ['192.168.1.0'],\n      })\n    )\n    app.get('/allow-empty', (c) => c.text('Hello World!'))\n\n    expect((await app.request('/basic', {}, { ip: '0.0.0.0' })).status).toBe(403)\n\n    expect((await app.request('/basic', {}, { ip: '192.168.1.0' })).status).toBe(200)\n\n    expect((await app.request('/basic', {}, { ip: '192.168.2.5' })).status).toBe(200)\n    expect((await app.request('/basic', {}, { ip: '192.168.2.10' })).status).toBe(403)\n\n    expect((await app.request('/allow-empty', {}, { ip: '0.0.0.0' })).status).toBe(200)\n\n    expect((await app.request('/allow-empty', {}, { ip: '192.168.1.0' })).status).toBe(403)\n\n    expect((await app.request('/allow-empty', {}, { ip: '192.168.2.5' })).status).toBe(200)\n    expect((await app.request('/allow-empty', {}, { ip: '192.168.2.10' })).status).toBe(200)\n  })\n  it('Custom onerror', async () => {\n    const res = await ipRestriction(\n      () => '0.0.0.0',\n      { denyList: ['0.0.0.0'] },\n      () => new Response('error')\n    )(new Context(new Request('http://localhost/')), async () => void 0)\n    expect(res).toBeTruthy()\n    if (res) {\n      expect(await res.text()).toBe('error')\n    }\n  })\n})\n\ndescribe('isMatchForRule', () => {\n  const isMatch = async (info: { addr: string; type: AddressType }, rule: IPRestrictionRule) => {\n    const middleware = ipRestriction(\n      () => ({\n        remote: {\n          address: info.addr,\n          addressType: info.type,\n        },\n      }),\n      {\n        allowList: [rule],\n      }\n    )\n    try {\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      await middleware(undefined as any, () => Promise.resolve())\n    } catch {\n      return false\n    }\n    return true\n  }\n\n  it('star', async () => {\n    expect(await isMatch({ addr: '192.168.2.0', type: 'IPv4' }, '*')).toBeTruthy()\n    expect(await isMatch({ addr: '192.168.2.1', type: 'IPv4' }, '*')).toBeTruthy()\n    expect(await isMatch({ addr: '::0', type: 'IPv6' }, '*')).toBeTruthy()\n  })\n  it('CIDR Notation', async () => {\n    expect(await isMatch({ addr: '192.168.2.0', type: 'IPv4' }, '192.168.2.0/24')).toBeTruthy()\n    expect(await isMatch({ addr: '192.168.2.1', type: 'IPv4' }, '192.168.2.0/24')).toBeTruthy()\n    expect(await isMatch({ addr: '192.168.2.1', type: 'IPv4' }, '192.168.2.1/32')).toBeTruthy()\n    expect(await isMatch({ addr: '192.168.2.1', type: 'IPv4' }, '192.168.2.2/32')).toBeFalsy()\n\n    expect(await isMatch({ addr: '::0', type: 'IPv6' }, '::0/1')).toBeTruthy()\n  })\n  it('Static Rules', async () => {\n    expect(await isMatch({ addr: '192.168.2.1', type: 'IPv4' }, '192.168.2.1')).toBeTruthy()\n    expect(await isMatch({ addr: '1234::5678', type: 'IPv6' }, '1234::5678')).toBeTruthy()\n    expect(\n      await isMatch({ addr: '::ffff:127.0.0.1', type: 'IPv6' }, '::ffff:127.0.0.1')\n    ).toBeTruthy()\n    expect(await isMatch({ addr: '::ffff:127.0.0.1', type: 'IPv6' }, '::ffff:7f00:1')).toBeTruthy()\n  })\n  it('Function Rules', async () => {\n    expect(await isMatch({ addr: '0.0.0.0', type: 'IPv4' }, () => true)).toBeTruthy()\n    expect(await isMatch({ addr: '0.0.0.0', type: 'IPv4' }, () => false)).toBeFalsy()\n\n    const ipaddr = '93.184.216.34'\n    await isMatch({ addr: ipaddr, type: 'IPv4' }, (ip) => {\n      expect(ipaddr).toBe(ip.addr)\n      return false\n    })\n  })\n})\n"
  },
  {
    "path": "src/middleware/ip-restriction/index.ts",
    "content": "/**\n * IP Restriction Middleware for Hono\n * @module\n */\n\nimport type { Context, MiddlewareHandler } from '../..'\nimport type { AddressType, GetConnInfo } from '../../helper/conninfo'\nimport { HTTPException } from '../../http-exception'\nimport {\n  convertIPv4ToBinary,\n  convertIPv6BinaryToString,\n  convertIPv6ToBinary,\n  distinctRemoteAddr,\n} from '../../utils/ipaddr'\n\n/**\n * Function to get IP Address\n */\ntype GetIPAddr = GetConnInfo | ((c: Context) => string)\n\n/**\n * ### IPv4 and IPv6\n * - `*` match all\n *\n * ### IPv4\n * - `192.168.2.0` static\n * - `192.168.2.0/24` CIDR Notation\n *\n * ### IPv6\n * - `::1` static\n * - `::1/10` CIDR Notation\n */\ntype IPRestrictionRuleFunction = (addr: { addr: string; type: AddressType }) => boolean\nexport type IPRestrictionRule = string | ((addr: { addr: string; type: AddressType }) => boolean)\n\nconst IS_CIDR_NOTATION_REGEX = /\\/[0-9]{0,3}$/\nconst buildMatcher = (\n  rules: IPRestrictionRule[]\n): ((addr: { addr: string; type: AddressType; isIPv4: boolean }) => boolean) => {\n  const functionRules: IPRestrictionRuleFunction[] = []\n  const staticRules: Set<string> = new Set()\n  const cidrRules: [boolean, bigint, bigint][] = []\n\n  for (let rule of rules) {\n    if (rule === '*') {\n      return () => true\n    } else if (typeof rule === 'function') {\n      functionRules.push(rule)\n    } else {\n      if (IS_CIDR_NOTATION_REGEX.test(rule)) {\n        const separatedRule = rule.split('/')\n\n        const addrStr = separatedRule[0]\n        const type = distinctRemoteAddr(addrStr)\n        if (type === undefined) {\n          throw new TypeError(`Invalid rule: ${rule}`)\n        }\n\n        const isIPv4 = type === 'IPv4'\n        const prefix = parseInt(separatedRule[1])\n\n        if (isIPv4 ? prefix === 32 : prefix === 128) {\n          // this rule is a static rule\n          rule = addrStr\n        } else {\n          const addr = (isIPv4 ? convertIPv4ToBinary : convertIPv6ToBinary)(addrStr)\n          const mask = ((1n << BigInt(prefix)) - 1n) << BigInt((isIPv4 ? 32 : 128) - prefix)\n\n          cidrRules.push([isIPv4, addr & mask, mask] as [boolean, bigint, bigint])\n          continue\n        }\n      }\n\n      const type = distinctRemoteAddr(rule)\n      if (type === undefined) {\n        throw new TypeError(`Invalid rule: ${rule}`)\n      }\n      staticRules.add(\n        type === 'IPv4'\n          ? rule // IPv4 address is already normalized, so it is registered as is.\n          : convertIPv6BinaryToString(convertIPv6ToBinary(rule)) // normalize IPv6 address (e.g. 0000:0000:0000:0000:0000:0000:0000:0001 => ::1)\n      )\n    }\n  }\n\n  return (remote: {\n    addr: string\n    type: AddressType\n    isIPv4: boolean\n    binaryAddr?: bigint\n  }): boolean => {\n    if (staticRules.has(remote.addr)) {\n      return true\n    }\n    for (const [isIPv4, addr, mask] of cidrRules) {\n      if (isIPv4 !== remote.isIPv4) {\n        continue\n      }\n      const remoteAddr = (remote.binaryAddr ||= (\n        isIPv4 ? convertIPv4ToBinary : convertIPv6ToBinary\n      )(remote.addr))\n      if ((remoteAddr & mask) === addr) {\n        return true\n      }\n    }\n    for (const rule of functionRules) {\n      if (rule({ addr: remote.addr, type: remote.type })) {\n        return true\n      }\n    }\n    return false\n  }\n}\n\n/**\n * Rules for IP Restriction Middleware\n */\nexport interface IPRestrictionRules {\n  denyList?: IPRestrictionRule[]\n  allowList?: IPRestrictionRule[]\n}\n\n/**\n * IP Restriction Middleware\n *\n * @param getIP function to get IP Address\n */\nexport const ipRestriction = (\n  getIP: GetIPAddr,\n  { denyList = [], allowList = [] }: IPRestrictionRules,\n  onError?: (\n    remote: { addr: string; type: AddressType },\n    c: Context\n  ) => Response | Promise<Response>\n): MiddlewareHandler => {\n  const allowLength = allowList.length\n\n  const denyMatcher = buildMatcher(denyList)\n  const allowMatcher = buildMatcher(allowList)\n\n  const blockError = (c: Context): HTTPException =>\n    new HTTPException(403, {\n      res: c.text('Forbidden', {\n        status: 403,\n      }),\n    })\n\n  return async function ipRestriction(c, next) {\n    const connInfo = getIP(c)\n    const addr = typeof connInfo === 'string' ? connInfo : connInfo.remote.address\n    if (!addr) {\n      throw blockError(c)\n    }\n    const type =\n      (typeof connInfo !== 'string' && connInfo.remote.addressType) || distinctRemoteAddr(addr)\n\n    const remoteData = { addr, type, isIPv4: type === 'IPv4' }\n\n    if (denyMatcher(remoteData)) {\n      if (onError) {\n        return onError({ addr, type }, c)\n      }\n      throw blockError(c)\n    }\n    if (allowMatcher(remoteData)) {\n      return await next()\n    }\n\n    if (allowLength === 0) {\n      return await next()\n    } else {\n      if (onError) {\n        return await onError({ addr, type }, c)\n      }\n      throw blockError(c)\n    }\n  }\n}\n"
  },
  {
    "path": "src/middleware/jsx-renderer/index.test.tsx",
    "content": "/** @jsxImportSource ../../jsx */\nimport { expectTypeOf } from 'vitest'\nimport { html } from '../../helper/html'\nimport { Hono } from '../../hono'\nimport type { FC } from '../../jsx'\nimport { Suspense } from '../../jsx/streaming'\nimport { jsxRenderer, useRequestContext } from '.'\n\nconst RequestUrl: FC = () => {\n  const c = useRequestContext()\n  return html`${c.req.url}`\n}\n\ndescribe('JSX renderer', () => {\n  it('basic', async () => {\n    const app = new Hono()\n    app.use(\n      '*',\n      jsxRenderer(({ children, title }) => (\n        <html>\n          <head>{title}</head>\n          <body>{children}</body>\n        </html>\n      ))\n    )\n    app.get('/', (c) =>\n      c.render(\n        <h1>\n          <RequestUrl />\n        </h1>,\n        { title: 'Title' }\n      )\n    )\n\n    const app2 = new Hono()\n    app2.use(\n      '*',\n      jsxRenderer(({ children }) => <div class='nested'>{children}</div>)\n    )\n    app2.get('/', (c) => c.render(<h1>http://localhost/nested</h1>, { title: 'Title' }))\n    app.route('/nested', app2)\n\n    let res = await app.request('http://localhost/')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe(\n      '<!DOCTYPE html><html><head>Title</head><body><h1>http://localhost/</h1></body></html>'\n    )\n\n    res = await app.request('http://localhost/nested')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe(\n      '<!DOCTYPE html><div class=\"nested\"><h1>http://localhost/nested</h1></div>'\n    )\n  })\n\n  it('Should get the context object as a 2nd arg', async () => {\n    const app = new Hono()\n    app.use(\n      jsxRenderer(\n        ({ children }, c) => {\n          return (\n            <div>\n              {children} at {c.req.path}\n            </div>\n          )\n        },\n        { docType: false }\n      )\n    )\n    app.get('/hi', (c) => {\n      return c.render('hi', { title: 'hi' })\n    })\n\n    const res = await app.request('/hi')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('<div>hi at /hi</div>')\n  })\n\n  it('nested layout with Layout', async () => {\n    const app = new Hono()\n    app.use(\n      '*',\n      jsxRenderer(({ children, title, Layout }) => (\n        <Layout>\n          <html>\n            <head>{title}</head>\n            <body>{children}</body>\n          </html>\n        </Layout>\n      ))\n    )\n\n    const app2 = new Hono()\n    app2.use(\n      '*',\n      jsxRenderer(({ children, Layout, title }) => (\n        <Layout title={title}>\n          <div class='nested'>{children}</div>\n        </Layout>\n      ))\n    )\n    app2.get('/', (c) => c.render(<h1>http://localhost/nested</h1>, { title: 'Nested' }))\n\n    const app3 = new Hono()\n    app3.use(\n      '*',\n      jsxRenderer(({ children, Layout, title }) => (\n        <Layout title={title}>\n          <div class='nested2'>{children}</div>\n        </Layout>\n      ))\n    )\n    app3.get('/', (c) => c.render(<h1>http://localhost/nested</h1>, { title: 'Nested2' }))\n    app2.route('/nested2', app3)\n\n    app.route('/nested', app2)\n\n    let res = await app.request('http://localhost/nested')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe(\n      '<!DOCTYPE html><html><head>Nested</head><body><div class=\"nested\"><h1>http://localhost/nested</h1></div></body></html>'\n    )\n\n    res = await app.request('http://localhost/nested/nested2')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe(\n      '<!DOCTYPE html><html><head>Nested2</head><body><div class=\"nested\"><div class=\"nested2\"><h1>http://localhost/nested</h1></div></div></body></html>'\n    )\n  })\n\n  it('Should return a default doctype', async () => {\n    const app = new Hono()\n    app.use(\n      '*',\n      jsxRenderer(\n        ({ children }) => {\n          return (\n            <html>\n              <body>{children}</body>\n            </html>\n          )\n        },\n        { docType: true }\n      )\n    )\n    app.get('/', (c) => c.render(<h1>Hello</h1>, { title: 'Title' }))\n    const res = await app.request('/')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('<!DOCTYPE html><html><body><h1>Hello</h1></body></html>')\n  })\n\n  it('Should return a non includes doctype', async () => {\n    const app = new Hono()\n    app.use(\n      '*',\n      jsxRenderer(\n        ({ children }) => {\n          return (\n            <html>\n              <body>{children}</body>\n            </html>\n          )\n        },\n        { docType: false }\n      )\n    )\n    app.get('/', (c) => c.render(<h1>Hello</h1>, { title: 'Title' }))\n    const res = await app.request('/')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('<html><body><h1>Hello</h1></body></html>')\n  })\n\n  it('Should return a custom doctype', async () => {\n    const app = new Hono()\n    app.use(\n      '*',\n      jsxRenderer(\n        ({ children }) => {\n          return (\n            <html>\n              <body>{children}</body>\n            </html>\n          )\n        },\n        {\n          docType:\n            '<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">',\n        }\n      )\n    )\n    app.get('/', (c) => c.render(<h1>Hello</h1>, { title: 'Title' }))\n    const res = await app.request('/')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe(\n      '<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\"><html><body><h1>Hello</h1></body></html>'\n    )\n  })\n\n  it('Should return as streaming content with default headers', async () => {\n    const app = new Hono()\n    app.use(\n      '*',\n      jsxRenderer(\n        ({ children }) => {\n          return (\n            <html>\n              <body>{children}</body>\n            </html>\n          )\n        },\n        {\n          docType: true,\n          stream: true,\n        }\n      )\n    )\n    const AsyncComponent = async () => {\n      const c = useRequestContext()\n      return <p>Hello {c.req.query('name')}!</p>\n    }\n    app.get('/', (c) =>\n      c.render(\n        <Suspense fallback={<p>Loading...</p>}>\n          <AsyncComponent />\n        </Suspense>,\n        { title: 'Title' }\n      )\n    )\n    const res = await app.request('/?name=Hono')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Transfer-Encoding')).toEqual('chunked')\n    expect(res.headers.get('Content-Type')).toEqual('text/html; charset=UTF-8')\n    expect(res.headers.get('Content-Encoding')).toEqual('Identity')\n\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n\n    const chunk: string[] = []\n    const reader = res.body.getReader()\n    const decoder = new TextDecoder()\n    for (;;) {\n      const { value, done } = await reader.read()\n      if (done) {\n        break\n      }\n      chunk.push(decoder.decode(value))\n    }\n    expect(chunk).toEqual([\n      '<!DOCTYPE html><html><body><template id=\"H:0\"></template><p>Loading...</p><!--/$--></body></html>',\n      `<template data-hono-target=\"H:0\"><p>Hello Hono!</p></template><script>\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:0')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script>`,\n    ])\n  })\n\n  // this test relies upon 'Should return as streaming content with default headers'\n  // this should be refactored to prevent tests depending on each other\n  it('Should return as streaming content with custom headers', async () => {\n    const app = new Hono()\n    app.use(\n      '*',\n      jsxRenderer(\n        ({ children }) => {\n          return (\n            <html>\n              <body>{children}</body>\n            </html>\n          )\n        },\n        {\n          docType: true,\n          stream: {\n            'Transfer-Encoding': 'chunked',\n            'Content-Type': 'text/html',\n          },\n        }\n      )\n    )\n    const AsyncComponent = async () => {\n      const c = useRequestContext()\n      return <p>Hello {c.req.query('name')} again!</p>\n    }\n    app.get('/', (c) =>\n      c.render(\n        <Suspense fallback={<p>Loading...</p>}>\n          <AsyncComponent />\n        </Suspense>,\n        { title: 'Title' }\n      )\n    )\n    const res = await app.request('/?name=Hono')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Transfer-Encoding')).toEqual('chunked')\n    expect(res.headers.get('Content-Type')).toEqual('text/html')\n\n    if (!res.body) {\n      throw new Error('Body is null')\n    }\n\n    const chunk: string[] = []\n    const reader = res.body.getReader()\n    const decoder = new TextDecoder()\n    for (;;) {\n      const { value, done } = await reader.read()\n      if (done) {\n        break\n      }\n      chunk.push(decoder.decode(value))\n    }\n    expect(chunk).toEqual([\n      '<!DOCTYPE html><html><body><template id=\"H:1\"></template><p>Loading...</p><!--/$--></body></html>',\n      `<template data-hono-target=\"H:1\"><p>Hello Hono again!</p></template><script>\n((d,c,n) => {\nc=d.currentScript.previousSibling\nd=d.getElementById('H:1')\nif(!d)return\ndo{n=d.nextSibling;n.remove()}while(n.nodeType!=8||n.nodeValue!='/$')\nd.replaceWith(c.content)\n})(document)\n</script>`,\n    ])\n  })\n\n  it('Should return as streaming content with headers added in a handler', async () => {\n    const app = new Hono()\n    app.use(jsxRenderer(async ({ children }) => <div>{children}</div>, { stream: true }))\n    app.get('/', (c) => {\n      c.header('X-Message-Set', 'Hello')\n      c.header('X-Message-Append', 'Hello', { append: true })\n      return c.render('Hi', { title: 'Hi' })\n    })\n    const res = await app.request('/')\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Transfer-Encoding')).toBe('chunked')\n    expect(res.headers.get('Content-Type')).toBe('text/html; charset=UTF-8')\n    expect(res.headers.get('X-Message-Set')).toBe('Hello')\n    expect(res.headers.get('X-Message-Append')).toBe('Hello')\n    expect(await res.text()).toBe('<!DOCTYPE html><div>Hi</div>')\n  })\n\n  it('Env', async () => {\n    type JSXRendererEnv = {\n      Variables: {\n        foo: string\n      }\n      Bindings: {\n        bar: string\n      }\n    }\n\n    const VariableFoo: FC = () => {\n      const c = useRequestContext<JSXRendererEnv>()\n      expectTypeOf(c.get('foo')).toEqualTypeOf<string>()\n      return html`${c.get('foo')}`\n    }\n\n    const BindingsBar: FC = () => {\n      const c = useRequestContext<JSXRendererEnv>()\n      expectTypeOf(c.env.bar).toEqualTypeOf<string>()\n      return html`${c.env.bar}`\n    }\n\n    const app = new Hono<JSXRendererEnv>()\n    app.use('*', jsxRenderer())\n    app.get('/', (c) => {\n      c.set('foo', 'fooValue')\n      return c.render(\n        <>\n          <h1>\n            <VariableFoo />\n          </h1>\n          <p>\n            <BindingsBar />\n          </p>\n        </>,\n        { title: 'Title' }\n      )\n    })\n    const res = await app.request('http://localhost/', undefined, { bar: 'barValue' })\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('<!DOCTYPE html><h1>fooValue</h1><p>barValue</p>')\n  })\n\n  it('Should return a resolved content', async () => {\n    const app = new Hono()\n    app.use(jsxRenderer(async ({ children }) => <div>{children}</div>))\n    app.get('/', (c) => c.render('Hi', { title: 'Hi' }))\n    const res = await app.request('/')\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe('<!DOCTYPE html><div>Hi</div>')\n  })\n\n  it('Should accept function-based options', async () => {\n    type Env = { Bindings: { HONO_STREAMING?: boolean } }\n    const app = new Hono<Env>()\n\n    const Component = async () => {\n      return <div>Component</div>\n    }\n\n    app.use(\n      '*',\n      jsxRenderer<Env>(\n        ({ children }) => {\n          return (\n            <html>\n              <body>{children}</body>\n            </html>\n          )\n        },\n        (c) => {\n          expectTypeOf(c.env?.HONO_STREAMING).toEqualTypeOf<boolean | undefined>()\n          return { docType: true, stream: c.env?.HONO_STREAMING ?? true }\n        }\n      )\n    )\n\n    app.get('/', async (c) => {\n      return c.render(\n        <div>\n          <Suspense fallback={'loading...'}>\n            <Component />\n          </Suspense>\n        </div>,\n        { title: 'Suspense test' }\n      )\n    })\n\n    const resStream = await app.request('/')\n    expect(resStream.status).toBe(200)\n    expect(resStream.headers.get('Transfer-Encoding')).toBe('chunked')\n    const textStream = await resStream.text()\n    expect(textStream).toContain('<template')\n    expect(textStream).toContain('<script')\n    expect(textStream).toContain('loading...')\n\n    const resNotStream = await app.request('/', undefined, { HONO_STREAMING: false })\n    expect(resNotStream.status).toBe(200)\n    expect(resNotStream.headers.get('Transfer-Encoding')).toBeNull()\n    const textNotStream = await resNotStream.text()\n    expect(textNotStream).not.toContain('<template')\n    expect(textNotStream).not.toContain('<script')\n    expect(textNotStream).not.toContain('loading...')\n    expect(textNotStream).toBe(\n      '<!DOCTYPE html><html><body><div><div>Component</div></div></body></html>'\n    )\n  })\n\n  describe('keep context status', async () => {\n    it('Should keep context status', async () => {\n      const app = new Hono()\n      app.use(\n        '*',\n        jsxRenderer(({ children }) => {\n          return (\n            <html>\n              <body>{children}</body>\n            </html>\n          )\n        })\n      )\n      app.get('/', (c) => {\n        c.status(201)\n        return c.render(<h1>Hello</h1>, { title: 'Title' })\n      })\n      const res = await app.request('/')\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(201)\n      expect(await res.text()).toBe('<!DOCTYPE html><html><body><h1>Hello</h1></body></html>')\n    })\n\n    it('Should keep context status with stream option', async () => {\n      const app = new Hono()\n      app.use(\n        '*',\n        jsxRenderer(\n          ({ children }) => {\n            return (\n              <html>\n                <body>{children}</body>\n              </html>\n            )\n          },\n          { stream: true }\n        )\n      )\n      app.get('/', (c) => {\n        c.status(201)\n        return c.render(<h1>Hello</h1>, { title: 'Title' })\n      })\n      const res = await app.request('/')\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(201)\n      expect(await res.text()).toBe('<!DOCTYPE html><html><body><h1>Hello</h1></body></html>')\n    })\n  })\n})\n"
  },
  {
    "path": "src/middleware/jsx-renderer/index.ts",
    "content": "/**\n * @module\n * JSX Renderer Middleware for Hono.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { Context, PropsForRenderer } from '../../context'\nimport { html, raw } from '../../helper/html'\nimport { Fragment, createContext, jsx, useContext } from '../../jsx'\nimport type { FC, Context as JSXContext, JSXNode, PropsWithChildren } from '../../jsx'\nimport { renderToReadableStream } from '../../jsx/streaming'\nimport type { Env, Input, MiddlewareHandler } from '../../types'\nimport type { HtmlEscapedString } from '../../utils/html'\n\nexport const RequestContext: JSXContext<Context<any, any, {}> | null> =\n  createContext<Context | null>(null)\n\ntype RendererOptions = {\n  docType?: boolean | string\n  stream?: boolean | Record<string, string>\n}\n\ntype Component = (\n  props: PropsForRenderer & { Layout: FC },\n  c: Context\n) => HtmlEscapedString | Promise<HtmlEscapedString>\n\ntype ComponentWithChildren = (\n  props: PropsWithChildren<PropsForRenderer & { Layout: FC }>,\n  c: Context\n) => HtmlEscapedString | Promise<HtmlEscapedString>\n\nconst createRenderer =\n  (\n    c: Context,\n    Layout: FC,\n    component?: Component,\n    options?: RendererOptions | ((c: Context) => RendererOptions)\n  ) =>\n  (children: JSXNode, props: PropsForRenderer) => {\n    options = typeof options === 'function' ? options(c) : options\n    const docType =\n      typeof options?.docType === 'string'\n        ? options.docType\n        : options?.docType === false\n          ? ''\n          : '<!DOCTYPE html>'\n\n    const currentLayout = component\n      ? jsx(\n          (props: any) => component(props, c),\n          {\n            Layout,\n            ...(props as any),\n          },\n          children as any\n        )\n      : children\n\n    const body = html`${raw(docType)}${jsx(\n      RequestContext.Provider,\n      { value: c },\n      currentLayout as any\n    )}`\n\n    if (options?.stream) {\n      if (options.stream === true) {\n        c.header('Transfer-Encoding', 'chunked')\n        c.header('Content-Type', 'text/html; charset=UTF-8')\n        c.header('Content-Encoding', 'Identity')\n      } else {\n        for (const [key, value] of Object.entries(options.stream)) {\n          c.header(key, value)\n        }\n      }\n      return c.body(renderToReadableStream(body))\n    } else {\n      return c.html(body)\n    }\n  }\n\n/**\n * JSX Renderer Middleware for hono.\n *\n * @see {@link https://hono.dev/docs/middleware/builtin/jsx-renderer}\n *\n * @param {ComponentWithChildren} [component] - The component to render, which can accept children and props.\n * @param {RendererOptions} [options] - The options for the JSX renderer middleware.\n * @param {boolean | string} [options.docType=true] - The DOCTYPE to be added at the beginning of the HTML. If set to false, no DOCTYPE will be added.\n * @param {boolean | Record<string, string>} [options.stream=false] - If set to true, enables streaming response with default headers. If a record is provided, custom headers will be used.\n * @returns {MiddlewareHandler} The middleware handler function.\n *\n * @example\n * ```ts\n * const app = new Hono()\n *\n * app.get(\n *   '/page/*',\n *   jsxRenderer(({ children }) => {\n *     return (\n *       <html>\n *         <body>\n *           <header>Menu</header>\n *           <div>{children}</div>\n *         </body>\n *       </html>\n *     )\n *   })\n * )\n *\n * app.get('/page/about', (c) => {\n *   return c.render(<h1>About me!</h1>)\n * })\n * ```\n */\nexport const jsxRenderer = <E extends Env = Env>(\n  component?: ComponentWithChildren,\n  options?: RendererOptions | ((c: Context<E>) => RendererOptions)\n): MiddlewareHandler =>\n  function jsxRenderer(c, next) {\n    const Layout = (c.getLayout() ?? Fragment) as FC\n    if (component) {\n      c.setLayout((props) => {\n        return component({ ...props, Layout }, c)\n      })\n    }\n    c.setRenderer(createRenderer(c, Layout, component, options) as any)\n    return next()\n  }\n\n/**\n * useRequestContext for Hono.\n *\n * @template E - The environment type.\n * @template P - The parameter type.\n * @template I - The input type.\n * @returns {Context<E, P, I>} An instance of Context.\n *\n * @example\n * ```ts\n * const RequestUrlBadge: FC = () => {\n *   const c = useRequestContext()\n *   return <b>{c.req.url}</b>\n * }\n *\n * app.get('/page/info', (c) => {\n *   return c.render(\n *     <div>\n *       You are accessing: <RequestUrlBadge />\n *     </div>\n *   )\n * })\n * ```\n */\nexport const useRequestContext = <\n  E extends Env = any,\n  P extends string = any,\n  I extends Input = {},\n>(): Context<E, P, I> => {\n  const c = useContext(RequestContext)\n  if (!c) {\n    throw new Error('RequestContext is not provided.')\n  }\n  return c\n}\n"
  },
  {
    "path": "src/middleware/jwk/index.test.ts",
    "content": "import { HttpResponse, http } from 'msw'\nimport { setupServer } from 'msw/node'\nimport { setSignedCookie } from '../../helper/cookie'\nimport { Hono } from '../../hono'\nimport { HTTPException } from '../../http-exception'\nimport { encodeBase64Url } from '../../utils/encode'\nimport { Jwt } from '../../utils/jwt'\nimport type { HonoJsonWebKey } from '../../utils/jwt/jws'\nimport { signing } from '../../utils/jwt/jws'\nimport { verifyWithJwks } from '../../utils/jwt/jwt'\nimport type { JWTPayload } from '../../utils/jwt/types'\nimport { utf8Encoder } from '../../utils/jwt/utf8'\nimport * as test_keys from './keys.test.json'\nimport { jwk } from '.'\n\nconst verify_keys = test_keys.public_keys\n\ndescribe('JWK', () => {\n  const server = setupServer(\n    http.get('http://localhost/.well-known/jwks.json', () => {\n      return HttpResponse.json({ keys: verify_keys })\n    }),\n    http.get('http://localhost/.well-known/missing-jwks.json', () => {\n      return HttpResponse.json({})\n    }),\n    http.get('http://localhost/.well-known/bad-jwks.json', () => {\n      return HttpResponse.json({ keys: 'bad-keys' })\n    }),\n    http.get('http://localhost/.well-known/404-jwks.json', () => {\n      return HttpResponse.text('Not Found', { status: 404 })\n    })\n  )\n  beforeAll(() => server.listen())\n  afterEach(() => server.resetHandlers())\n  afterAll(() => server.close())\n\n  describe('verifyWithJwks', () => {\n    it('Should throw error on missing keys/jwks_uri options', async () => {\n      const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])\n      await expect(verifyWithJwks(credential, { allowedAlgorithms: ['RS256'] })).rejects.toThrow(\n        'verifyWithJwks requires options for either \"keys\" or \"jwks_uri\" or both'\n      )\n    })\n  })\n\n  describe('options.allow_anon = true', () => {\n    let handlerExecuted: boolean\n\n    beforeEach(() => {\n      handlerExecuted = false\n    })\n\n    const app = new Hono()\n\n    app.use('/backend-auth-or-anon/*', jwk({ keys: verify_keys, allow_anon: true, alg: ['RS256'] }))\n\n    app.get('/backend-auth-or-anon/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload ?? { message: 'hello anon' })\n    })\n\n    it('Should skip JWK if no token is present', async () => {\n      const req = new Request('http://localhost/backend-auth-or-anon/a')\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello anon' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should authorize if token is present', async () => {\n      const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])\n      const req = new Request('http://localhost/backend-auth-or-anon/a')\n      req.headers.set('Authorization', `Bearer ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should not authorize if bad token is present', async () => {\n      const invalidToken =\n        'ssyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n      const url = 'http://localhost/backend-auth-or-anon/a'\n      const req = new Request(url)\n      req.headers.set('Authorization', `Basic ${invalidToken}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(res.headers.get('www-authenticate')).toEqual(\n        `Bearer realm=\"${url}\",error=\"invalid_token\",error_description=\"token verification failure\"`\n      )\n      expect(handlerExecuted).toBeFalsy()\n    })\n  })\n\n  describe('Credentials in header', () => {\n    let handlerExecuted: boolean\n\n    beforeEach(() => {\n      handlerExecuted = false\n    })\n\n    const app = new Hono()\n\n    app.use('/auth-with-keys/*', jwk({ keys: verify_keys, alg: ['RS256'] }))\n    app.use('/auth-with-keys-unicode/*', jwk({ keys: verify_keys, alg: ['RS256'] }))\n    app.use('/auth-with-keys-nested/*', async (c, next) => {\n      const auth = jwk({ keys: verify_keys, alg: ['RS256'] })\n      return auth(c, next)\n    })\n    app.use(\n      '/auth-with-keys-fn/*',\n      jwk({\n        keys: async () => {\n          const response = await fetch('http://localhost/.well-known/jwks.json')\n          const data = await response.json()\n          return data.keys\n        },\n        alg: ['RS256'],\n      })\n    )\n    app.use(\n      '/auth-with-jwks_uri/*',\n      jwk({\n        jwks_uri: 'http://localhost/.well-known/jwks.json',\n        alg: ['RS256'],\n      })\n    )\n    app.use(\n      '/auth-with-keys-and-jwks_uri/*',\n      jwk({\n        keys: verify_keys,\n        jwks_uri: () => 'http://localhost/.well-known/jwks.json',\n        alg: ['RS256'],\n      })\n    )\n    app.use(\n      '/auth-with-missing-jwks_uri/*',\n      jwk({\n        jwks_uri: 'http://localhost/.well-known/missing-jwks.json',\n        alg: ['RS256'],\n      })\n    )\n    app.use(\n      '/auth-with-404-jwks_uri/*',\n      jwk({\n        jwks_uri: 'http://localhost/.well-known/404-jwks.json',\n        alg: ['RS256'],\n      })\n    )\n    app.use(\n      '/auth-with-bad-jwks_uri/*',\n      jwk({\n        jwks_uri: 'http://localhost/.well-known/bad-jwks.json',\n        alg: ['RS256'],\n      })\n    )\n\n    app.get('/auth-with-keys/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n    app.get('/auth-with-keys-unicode/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n    app.get('/auth-with-keys-nested/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n    app.get('/auth-with-keys-fn/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n    app.get('/auth-with-jwks_uri/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n    app.get('/auth-with-keys-and-jwks_uri/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n    app.get('/auth-with-missing-jwks_uri/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n    app.get('/auth-with-404-jwks_uri/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n    app.get('/auth-with-bad-jwks_uri/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n\n    it('Should throw an error if the middleware is missing both keys and jwks_uri (empty)', async () => {\n      // @ts-expect-error - Testing runtime error with missing required alg option\n      expect(() => app.use('/auth-with-empty-middleware/*', jwk({}))).toThrow(\n        'JWK auth middleware requires options for either \"keys\" or \"jwks_uri\"'\n      )\n    })\n\n    it('Should throw an error when crypto.subtle is missing', async () => {\n      const subtleSpy = vi.spyOn(global.crypto, 'subtle', 'get').mockReturnValue({\n        importKey: undefined,\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      } as any)\n      expect(() =>\n        app.use('/auth-with-bad-env/*', jwk({ keys: verify_keys, alg: ['RS256'] }))\n      ).toThrow()\n      subtleSpy.mockRestore()\n    })\n\n    it('Should return a server error if options.jwks_uri returns a 404', async () => {\n      const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])\n      const req = new Request('http://localhost/auth-with-404-jwks_uri/a')\n      req.headers.set('Authorization', `Basic ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(500)\n    })\n\n    it('Should return a server error if the remotely fetched keys from options.jwks_uri are missing', async () => {\n      const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])\n      const req = new Request('http://localhost/auth-with-missing-jwks_uri/a')\n      req.headers.set('Authorization', `Basic ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(500)\n    })\n\n    it('Should return a server error if the remotely fetched keys from options.jwks_uri are malformed', async () => {\n      const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])\n      const req = new Request('http://localhost/auth-with-bad-jwks_uri/a')\n      req.headers.set('Authorization', `Basic ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(500)\n    })\n\n    it('Should not authorize requests with missing access token', async () => {\n      const req = new Request('http://localhost/auth-with-keys/a')\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(await res.text()).toBe('Unauthorized')\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should authorize from a static array passed to options.keys (key 1)', async () => {\n      const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])\n      const req = new Request('http://localhost/auth-with-keys/a')\n      req.headers.set('Authorization', `Bearer ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should authorize from a static array passed to options.keys (key 2)', async () => {\n      const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[1])\n      const req = new Request('http://localhost/auth-with-keys/a')\n      req.headers.set('Authorization', `Bearer ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n      expect(res.status).toBe(200)\n    })\n\n    it('Should not authorize a token without header', async () => {\n      const encodeJwtPart = (part: unknown): string =>\n        encodeBase64Url(utf8Encoder.encode(JSON.stringify(part)).buffer).replace(/=/g, '')\n      const encodeSignaturePart = (buf: ArrayBufferLike): string =>\n        encodeBase64Url(buf).replace(/=/g, '')\n      const jwtSignWithoutHeader = async (payload: JWTPayload, privateKey: HonoJsonWebKey) => {\n        const encodedPayload = encodeJwtPart(payload)\n        const signaturePart = await signing(\n          privateKey,\n          // eslint-disable-next-line @typescript-eslint/no-explicit-any\n          privateKey.alg as any,\n          utf8Encoder.encode(encodedPayload)\n        )\n        const signature = encodeSignaturePart(signaturePart)\n        return `${encodedPayload}.${signature}`\n      }\n      const credential = await jwtSignWithoutHeader(\n        { message: 'hello world' },\n        test_keys.private_keys[1]\n      )\n      const req = new Request('http://localhost/auth-with-keys/a')\n      req.headers.set('Authorization', `Bearer ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n    })\n\n    it('Should not authorize a token with missing \"kid\" in header', async () => {\n      const encodeJwtPart = (part: unknown): string =>\n        encodeBase64Url(utf8Encoder.encode(JSON.stringify(part)).buffer).replace(/=/g, '')\n      const encodeSignaturePart = (buf: ArrayBufferLike): string =>\n        encodeBase64Url(buf).replace(/=/g, '')\n      const jwtSignWithoutKid = async (payload: JWTPayload, privateKey: HonoJsonWebKey) => {\n        const encodedPayload = encodeJwtPart(payload)\n        const encodedHeader = encodeJwtPart({ alg: privateKey.alg, typ: 'JWT' })\n        const partialToken = `${encodedHeader}.${encodedPayload}`\n        const signaturePart = await signing(\n          privateKey,\n          // eslint-disable-next-line @typescript-eslint/no-explicit-any\n          privateKey.alg as any,\n          utf8Encoder.encode(partialToken)\n        )\n        const signature = encodeSignaturePart(signaturePart)\n        return `${partialToken}.${signature}`\n      }\n      const credential = await jwtSignWithoutKid(\n        { message: 'hello world' },\n        test_keys.private_keys[1]\n      )\n      const req = new Request('http://localhost/auth-with-keys/a')\n      req.headers.set('Authorization', `Bearer ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n    })\n\n    it('Should not authorize a token with invalid \"kid\" in header', async () => {\n      const copy = structuredClone(test_keys.private_keys[1])\n      copy.kid = 'invalid-kid'\n      const credential = await Jwt.sign({ message: 'hello world' }, copy)\n      const req = new Request('http://localhost/auth-with-keys/a')\n      req.headers.set('Authorization', `Bearer ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n    })\n\n    it('Should authorize with Unicode payload from a static array passed to options.keys', async () => {\n      const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])\n      const req = new Request('http://localhost/auth-with-keys-unicode/a')\n      req.headers.set('Authorization', `Basic ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should authorize from a function passed to options.keys', async () => {\n      const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])\n      const req = new Request('http://localhost/auth-with-keys-fn/a')\n      req.headers.set('Authorization', `Basic ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should authorize from keys remotely fetched from options.jwks_uri', async () => {\n      const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])\n      const req = new Request('http://localhost/auth-with-jwks_uri/a')\n      req.headers.set('Authorization', `Basic ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should authorize from keys and hard-coded and remotely fetched from options.jwks_uri', async () => {\n      const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])\n      const req = new Request('http://localhost/auth-with-keys-and-jwks_uri/a')\n      req.headers.set('Authorization', `Basic ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should not authorize requests with invalid Unicode payload in header', async () => {\n      const invalidToken =\n        'ssyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n      const url = 'http://localhost/auth-with-keys-unicode/a'\n      const req = new Request(url)\n      req.headers.set('Authorization', `Basic ${invalidToken}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(res.headers.get('www-authenticate')).toEqual(\n        `Bearer realm=\"${url}\",error=\"invalid_token\",error_description=\"token verification failure\"`\n      )\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should not authorize requests with malformed token structure in header', async () => {\n      const invalid_token = 'invalid token'\n      const url = 'http://localhost/auth-with-keys/a'\n      const req = new Request(url)\n      req.headers.set('Authorization', `Bearer ${invalid_token}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(res.headers.get('www-authenticate')).toEqual(\n        `Bearer realm=\"${url}\",error=\"invalid_request\",error_description=\"invalid credentials structure\"`\n      )\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should not authorize requests without authorization in nested JWK middleware', async () => {\n      const req = new Request('http://localhost/auth-with-keys-nested/a')\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(await res.text()).toBe('Unauthorized')\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should authorize requests with authorization in nested JWK middleware', async () => {\n      const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])\n      const req = new Request('http://localhost/auth-with-keys-nested/a')\n      req.headers.set('Authorization', `Bearer ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n  })\n\n  describe('Credentials in custom header', () => {\n    let handlerExecuted: boolean\n\n    beforeEach(() => {\n      handlerExecuted = false\n    })\n\n    const app = new Hono()\n\n    app.use(\n      '/auth-with-keys/*',\n      jwk({ keys: verify_keys, headerName: 'x-custom-auth-header', alg: ['RS256'] })\n    )\n\n    app.get('/auth-with-keys/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n\n    it('Should not authorize', async () => {\n      const req = new Request('http://localhost/auth-with-keys/a')\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(await res.text()).toBe('Unauthorized')\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should not authorize even if default authorization header present', async () => {\n      const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])\n\n      const req = new Request('http://localhost/auth-with-keys/a')\n      req.headers.set('Authorization', `Bearer ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(await res.text()).toBe('Unauthorized')\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should authorize', async () => {\n      const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[1])\n\n      const req = new Request('http://localhost/auth-with-keys/a')\n      req.headers.set('x-custom-auth-header', `Bearer ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n  })\n\n  describe('Credentials in cookie', () => {\n    let handlerExecuted: boolean\n\n    beforeEach(() => {\n      handlerExecuted = false\n    })\n\n    const app = new Hono()\n\n    app.use('/auth-with-keys/*', jwk({ keys: verify_keys, cookie: 'access_token', alg: ['RS256'] }))\n    app.use(\n      '/auth-with-keys-unicode/*',\n      jwk({ keys: verify_keys, cookie: 'access_token', alg: ['RS256'] })\n    )\n    app.use(\n      '/auth-with-keys-prefixed/*',\n      jwk({\n        keys: verify_keys,\n        cookie: { key: 'access_token', prefixOptions: 'host' },\n        alg: ['RS256'],\n      })\n    )\n    app.use(\n      '/auth-with-keys-unprefixed/*',\n      jwk({ keys: verify_keys, cookie: { key: 'access_token' }, alg: ['RS256'] })\n    )\n\n    app.get('/auth-with-keys/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n    app.get('/auth-with-keys-prefixed/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n    app.get('/auth-with-keys-unprefixed/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n    app.get('/auth-with-keys-unicode/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n\n    it('Should not authorize requests with missing access token', async () => {\n      const req = new Request('http://localhost/auth-with-keys/a')\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(await res.text()).toBe('Unauthorized')\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should authorize cookie from a static array passed to options.keys', async () => {\n      const url = 'http://localhost/auth-with-keys/a'\n      const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])\n      const req = new Request(url, {\n        headers: new Headers({\n          Cookie: `access_token=${credential}`,\n        }),\n      })\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(res.status).toBe(200)\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should authorize prefixed cookie from a static array passed to options.keys', async () => {\n      const url = 'http://localhost/auth-with-keys-prefixed/a'\n      const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])\n      const req = new Request(url, {\n        headers: new Headers({\n          Cookie: `__Host-access_token=${credential}`,\n        }),\n      })\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(res.status).toBe(200)\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should authorize unprefixed cookie from a static array passed to options.keys', async () => {\n      const url = 'http://localhost/auth-with-keys-unprefixed/a'\n      const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])\n      const req = new Request(url, {\n        headers: new Headers({\n          Cookie: `access_token=${credential}`,\n        }),\n      })\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(res.status).toBe(200)\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should authorize with Unicode payload from a static array passed to options.keys', async () => {\n      const credential = await Jwt.sign({ message: 'hello world' }, test_keys.private_keys[0])\n      const req = new Request('http://localhost/auth-with-keys-unicode/a', {\n        headers: new Headers({\n          Cookie: `access_token=${credential}`,\n        }),\n      })\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should not authorize requests with invalid Unicode payload in cookie', async () => {\n      const invalidToken =\n        'ssyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n\n      const url = 'http://localhost/auth-with-keys-unicode/a'\n      const req = new Request(url)\n      req.headers.set('Cookie', `access_token=${invalidToken}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(res.headers.get('www-authenticate')).toEqual(\n        `Bearer realm=\"${url}\",error=\"invalid_token\",error_description=\"token verification failure\"`\n      )\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should not authorize requests with malformed token structure in cookie', async () => {\n      const invalidToken = 'invalid token'\n      const url = 'http://localhost/auth-with-keys/a'\n      const req = new Request(url)\n      req.headers.set('Cookie', `access_token=${invalidToken}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(res.headers.get('www-authenticate')).toEqual(\n        `Bearer realm=\"${url}\",error=\"invalid_token\",error_description=\"token verification failure\"`\n      )\n      expect(handlerExecuted).toBeFalsy()\n    })\n  })\n\n  describe('Credentials in a signed cookie', () => {\n    let handlerExecuted: boolean\n\n    beforeEach(() => {\n      handlerExecuted = false\n    })\n\n    const app = new Hono()\n    const test_secret = 'Shhh'\n\n    app.use(\n      '/auth-with-signed-cookie/*',\n      jwk({\n        keys: verify_keys,\n        cookie: { key: 'access_token', secret: test_secret },\n        alg: ['RS256'],\n      })\n    )\n    app.use(\n      '/auth-with-signed-with-prefix-options-cookie/*',\n      jwk({\n        keys: verify_keys,\n        cookie: { key: 'access_token', secret: test_secret, prefixOptions: 'host' },\n        alg: ['RS256'],\n      })\n    )\n\n    app.get('/sign-cookie', async (c) => {\n      const credential = await Jwt.sign(\n        { message: 'signed hello world' },\n        test_keys.private_keys[0]\n      )\n      await setSignedCookie(c, 'access_token', credential, test_secret)\n      return c.text('OK')\n    })\n    app.get('/sign-cookie-with-prefix', async (c) => {\n      const credential = await Jwt.sign(\n        { message: 'signed hello world' },\n        test_keys.private_keys[0]\n      )\n      await setSignedCookie(c, 'access_token', credential, test_secret, { prefix: 'host' })\n      return c.text('OK')\n    })\n    app.get('/auth-with-signed-cookie/*', async (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n    app.get('/auth-with-signed-with-prefix-options-cookie/*', async (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n\n    it('Should authorize signed cookie', async () => {\n      const url = 'http://localhost/auth-with-signed-cookie/a'\n      const sign_res = await app.request('http://localhost/sign-cookie')\n      const cookieHeader = sign_res.headers.get('Set-Cookie') as string\n      expect(cookieHeader).not.toBeNull()\n      const req = new Request(url)\n      req.headers.set('Cookie', cookieHeader)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'signed hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should authorize prefixed signed cookie', async () => {\n      const url = 'http://localhost/auth-with-signed-with-prefix-options-cookie/a'\n      const sign_res = await app.request('http://localhost/sign-cookie-with-prefix')\n      const cookieHeader = sign_res.headers.get('Set-Cookie') as string\n      expect(cookieHeader).not.toBeNull()\n      const req = new Request(url)\n      req.headers.set('Cookie', cookieHeader)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'signed hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should not authorize an unsigned cookie', async () => {\n      const url = 'http://localhost/auth-with-signed-cookie/a'\n      const credential = await Jwt.sign(\n        { message: 'unsigned hello world' },\n        test_keys.private_keys[0]\n      )\n      const unsignedCookie = `access_token=${credential}`\n      const req = new Request(url)\n      req.headers.set('Cookie', unsignedCookie)\n      const res = await app.request(req)\n      expect(res.status).toBe(401)\n      expect(await res.text()).toBe('Unauthorized')\n      expect(handlerExecuted).toBeFalsy()\n    })\n  })\n\n  describe('Error handling with `cause`', () => {\n    const app = new Hono()\n\n    app.use('/auth-with-keys/*', jwk({ keys: verify_keys, alg: ['RS256'] }))\n    app.get('/auth-with-keys/*', (c) => c.text('Authorized'))\n\n    app.onError((e, c) => {\n      if (e instanceof HTTPException && e.cause instanceof Error) {\n        return c.json({ name: e.cause.name, message: e.cause.message }, 401)\n      }\n      return c.text(e.message, 401)\n    })\n\n    it('Should not authorize', async () => {\n      const credential = 'abc.def.ghi'\n      const req = new Request('http://localhost/auth-with-keys')\n      req.headers.set('Authorization', `Bearer ${credential}`)\n      const res = await app.request(req)\n      expect(res.status).toBe(401)\n      expect(await res.json()).toEqual({\n        name: 'JwtTokenInvalid',\n        message: `invalid JWT token: ${credential}`,\n      })\n    })\n  })\n\n  describe('Verification of token attributes', () => {\n    let handlerExecuted: boolean\n\n    beforeEach(() => {\n      handlerExecuted = false\n    })\n\n    function inFuture() {\n      return Date.now() / 1000 + 100\n    }\n\n    function inPast() {\n      return Date.now() / 1000 - 100\n    }\n\n    const app = new Hono()\n\n    app.use('/auth-with-keys-default/*', jwk({ keys: verify_keys, alg: ['RS256'] }))\n    app.use(\n      '/auth-with-keys-and-issuer/*',\n      jwk({ keys: verify_keys, verification: { iss: 'http://issuer.test' }, alg: ['RS256'] })\n    )\n\n    app.get('/auth-with-keys-default/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n    app.get('/auth-with-keys-and-issuer/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n\n    it('Should validate exp/nbf/iat and pass when good by default', async () => {\n      const payload = {\n        exp: inFuture(),\n        nbf: inPast(),\n        iat: inPast(),\n        iss: 'http://not-checked.test',\n      }\n      const credential = await Jwt.sign(payload, test_keys.private_keys[0])\n      const req = new Request('http://localhost/auth-with-keys-default/a')\n      req.headers.set('Authorization', `Basic ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual(payload)\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should validate exp and fail when bad', async () => {\n      const payload = {\n        exp: inPast(),\n      }\n      const credential = await Jwt.sign(payload, test_keys.private_keys[0])\n      const req = new Request('http://localhost/auth-with-keys-default/a')\n      req.headers.set('Authorization', `Basic ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should validate nbf and fail when bad', async () => {\n      const payload = {\n        nbf: inFuture(),\n      }\n      const credential = await Jwt.sign(payload, test_keys.private_keys[0])\n      const req = new Request('http://localhost/auth-with-keys-default/a')\n      req.headers.set('Authorization', `Basic ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should validate iat and fail when bad', async () => {\n      const payload = {\n        iat: inFuture(),\n      }\n      const credential = await Jwt.sign(payload, test_keys.private_keys[0])\n      const req = new Request('http://localhost/auth-with-keys-default/a')\n      req.headers.set('Authorization', `Basic ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should validate iss when supplied', async () => {\n      const payload = {\n        iss: 'http://issuer.test',\n      }\n      const credential = await Jwt.sign(payload, test_keys.private_keys[0])\n      const req = new Request('http://localhost/auth-with-keys-and-issuer/a')\n      req.headers.set('Authorization', `Basic ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual(payload)\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should reject missing iss when required', async () => {\n      const payload = {\n        // Nothing\n      }\n      const credential = await Jwt.sign(payload, test_keys.private_keys[0])\n      const req = new Request('http://localhost/auth-with-keys-and-issuer/a')\n      req.headers.set('Authorization', `Basic ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should reject iss when different', async () => {\n      const payload = {\n        iss: 'http://bad-issuer.test',\n      }\n      const credential = await Jwt.sign(payload, test_keys.private_keys[0])\n      const req = new Request('http://localhost/auth-with-keys-and-issuer/a')\n      req.headers.set('Authorization', `Basic ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(handlerExecuted).toBeFalsy()\n    })\n  })\n\n  describe('Algorithm whitelist (options.alg)', () => {\n    let handlerExecuted: boolean\n\n    beforeEach(() => {\n      handlerExecuted = false\n    })\n\n    const app = new Hono()\n\n    // Only allow RS256\n    app.use('/auth-whitelist-rs256/*', jwk({ keys: verify_keys, alg: ['RS256'] }))\n    app.get('/auth-whitelist-rs256/*', (c) => {\n      handlerExecuted = true\n      return c.json(c.get('jwtPayload'))\n    })\n\n    // Allow multiple algorithms\n    app.use('/auth-whitelist-multi/*', jwk({ keys: verify_keys, alg: ['RS256', 'ES256'] }))\n    app.get('/auth-whitelist-multi/*', (c) => {\n      handlerExecuted = true\n      return c.json(c.get('jwtPayload'))\n    })\n\n    // Note: Test for \"no whitelist\" was removed because alg is now required.\n    // This is a breaking change that enforces explicit algorithm specification for security.\n\n    it('Should authorize RS256 token when RS256 is in whitelist', async () => {\n      const payload = { message: 'hello world' }\n      const credential = await Jwt.sign(payload, test_keys.private_keys[0]) // RS256 key\n      const req = new Request('http://localhost/auth-whitelist-rs256/a')\n      req.headers.set('Authorization', `Bearer ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual(payload)\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should reject token when algorithm is not in whitelist', async () => {\n      // Create a token with ES256 algorithm manually\n      const kid = 'hono-test-kid-1' // Use existing kid but header will have different alg\n      const payload = { message: 'hello world' }\n\n      // Generate ES256 key pair for signing\n      const keyPair = await crypto.subtle.generateKey(\n        {\n          name: 'ECDSA',\n          namedCurve: 'P-256',\n        },\n        true,\n        ['sign', 'verify']\n      )\n\n      // Create JWT with ES256\n      const header = { alg: 'ES256', typ: 'JWT', kid }\n      const encode = (obj: object) =>\n        encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)\n      const encodedHeader = encode(header)\n      const encodedPayload = encode(payload)\n      const signingInput = `${encodedHeader}.${encodedPayload}`\n\n      const signatureBuffer = await signing(\n        keyPair.privateKey,\n        'ES256',\n        utf8Encoder.encode(signingInput)\n      )\n      const signature = encodeBase64Url(signatureBuffer)\n\n      const token = `${encodedHeader}.${encodedPayload}.${signature}`\n\n      const url = 'http://localhost/auth-whitelist-rs256/a'\n      const req = new Request(url)\n      req.headers.set('Authorization', `Bearer ${token}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(res.headers.get('www-authenticate')).toMatch(/token verification failure/)\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should authorize RS256 token when multiple algorithms are in whitelist', async () => {\n      const payload = { message: 'hello world' }\n      const credential = await Jwt.sign(payload, test_keys.private_keys[0]) // RS256 key\n      const req = new Request('http://localhost/auth-whitelist-multi/a')\n      req.headers.set('Authorization', `Bearer ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual(payload)\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    // Note: Test for \"no whitelist\" was removed because alg is now required.\n    // This is a breaking change that enforces explicit algorithm specification for security.\n  })\n})\n"
  },
  {
    "path": "src/middleware/jwk/index.ts",
    "content": "export { jwk } from './jwk'\n"
  },
  {
    "path": "src/middleware/jwk/jwk.ts",
    "content": "/**\n * @module\n * JWK Auth Middleware for Hono.\n */\n\nimport type { Context } from '../../context'\nimport { getCookie, getSignedCookie } from '../../helper/cookie'\nimport { HTTPException } from '../../http-exception'\nimport type { MiddlewareHandler } from '../../types'\nimport type { CookiePrefixOptions } from '../../utils/cookie'\nimport { Jwt } from '../../utils/jwt'\nimport '../../context'\nimport type { AsymmetricAlgorithm } from '../../utils/jwt/jwa'\nimport type { HonoJsonWebKey } from '../../utils/jwt/jws'\nimport type { VerifyOptions } from '../../utils/jwt/jwt'\n\n/**\n * JWK Auth Middleware for Hono.\n *\n * @see {@link https://hono.dev/docs/middleware/builtin/jwk}\n *\n * @param {object} options - The options for the JWK middleware.\n * @param {HonoJsonWebKey[] | ((ctx: Context) => Promise<HonoJsonWebKey[]> | HonoJsonWebKey[])} [options.keys] - The public keys used for JWK verification, or a function that returns them.\n * @param {string | ((ctx: Context) => Promise<string> | string)} [options.jwks_uri] - If set to a URI string or a function that returns a URI string, attempt to fetch JWKs from it. The response must be a JSON object containing a `keys` array, which will be merged with the `keys` option.\n * @param {boolean} [options.allow_anon] - If set to `true`, the middleware allows requests without a token to proceed without authentication.\n * @param {string} [options.cookie] - If set, the middleware attempts to retrieve the token from a cookie with these options (optionally signed) only if no token is found in the header.\n * @param {string} [options.headerName='Authorization'] - The name of the header to look for the JWT token. Default is 'Authorization'.\n * @param {AsymmetricAlgorithm[]} options.alg - An array of allowed asymmetric algorithms for JWT verification. Only tokens signed with these algorithms will be accepted.\n * @param {RequestInit} [init] - Optional init options for the `fetch` request when retrieving JWKS from a URI.\n * @param {VerifyOptions} [options.verification] - Additional options for JWK payload verification.\n * @returns {MiddlewareHandler} The middleware handler function.\n *\n * @example\n * ```ts\n * const app = new Hono()\n *\n * app.use(\"/auth/*\", jwk({\n *   jwks_uri: (c) => `https://${c.env.authServer}/.well-known/jwks.json`,\n *   headerName: 'x-custom-auth-header', // Optional, default is 'Authorization'\n * }))\n *\n * app.get('/auth/page', (c) => {\n *   return c.text('You are authorized')\n * })\n * ```\n */\n\nexport const jwk = (\n  options: {\n    keys?: HonoJsonWebKey[] | ((ctx: Context) => Promise<HonoJsonWebKey[]> | HonoJsonWebKey[])\n    jwks_uri?: string | ((ctx: Context) => Promise<string> | string)\n    allow_anon?: boolean\n    cookie?:\n      | string\n      | { key: string; secret?: string | BufferSource; prefixOptions?: CookiePrefixOptions }\n\n    headerName?: string\n\n    alg: AsymmetricAlgorithm[]\n\n    verification?: VerifyOptions\n  },\n  init?: RequestInit\n): MiddlewareHandler => {\n  const verifyOpts = options.verification || {}\n\n  if (!options || !(options.keys || options.jwks_uri)) {\n    throw new Error('JWK auth middleware requires options for either \"keys\" or \"jwks_uri\" or both')\n  }\n\n  if (!crypto.subtle || !crypto.subtle.importKey) {\n    throw new Error('`crypto.subtle.importKey` is undefined. JWK auth middleware requires it.')\n  }\n\n  return async function jwk(ctx, next) {\n    const headerName = options.headerName || 'Authorization'\n\n    const credentials = ctx.req.raw.headers.get(headerName)\n    let token\n    if (credentials) {\n      const parts = credentials.split(/\\s+/)\n      if (parts.length !== 2) {\n        const errDescription = 'invalid credentials structure'\n        throw new HTTPException(401, {\n          message: errDescription,\n          res: unauthorizedResponse({\n            ctx,\n            error: 'invalid_request',\n            errDescription,\n          }),\n        })\n      } else {\n        token = parts[1]\n      }\n    } else if (options.cookie) {\n      if (typeof options.cookie == 'string') {\n        token = getCookie(ctx, options.cookie)\n      } else if (options.cookie.secret) {\n        if (options.cookie.prefixOptions) {\n          token = await getSignedCookie(\n            ctx,\n            options.cookie.secret,\n            options.cookie.key,\n            options.cookie.prefixOptions\n          )\n        } else {\n          token = await getSignedCookie(ctx, options.cookie.secret, options.cookie.key)\n        }\n      } else {\n        if (options.cookie.prefixOptions) {\n          token = getCookie(ctx, options.cookie.key, options.cookie.prefixOptions)\n        } else {\n          token = getCookie(ctx, options.cookie.key)\n        }\n      }\n    }\n\n    if (!token) {\n      if (options.allow_anon) {\n        return next()\n      }\n      const errDescription = 'no authorization included in request'\n      throw new HTTPException(401, {\n        message: errDescription,\n        res: unauthorizedResponse({\n          ctx,\n          error: 'invalid_request',\n          errDescription,\n        }),\n      })\n    }\n\n    let payload\n    let cause\n    try {\n      const keys = typeof options.keys === 'function' ? await options.keys(ctx) : options.keys\n      const jwks_uri =\n        typeof options.jwks_uri === 'function' ? await options.jwks_uri(ctx) : options.jwks_uri\n      payload = await Jwt.verifyWithJwks(\n        token,\n        { keys, jwks_uri, verification: verifyOpts, allowedAlgorithms: options.alg },\n        init\n      )\n    } catch (e) {\n      cause = e\n    }\n\n    if (!payload) {\n      if (cause instanceof Error && cause.constructor === Error) {\n        throw cause\n      }\n      throw new HTTPException(401, {\n        message: 'Unauthorized',\n        res: unauthorizedResponse({\n          ctx,\n          error: 'invalid_token',\n          statusText: 'Unauthorized',\n          errDescription: 'token verification failure',\n        }),\n        cause,\n      })\n    }\n\n    ctx.set('jwtPayload', payload)\n\n    await next()\n  }\n}\n\nfunction unauthorizedResponse(opts: {\n  ctx: Context\n  error: string\n  errDescription: string\n  statusText?: string\n}) {\n  return new Response('Unauthorized', {\n    status: 401,\n    statusText: opts.statusText,\n    headers: {\n      'WWW-Authenticate': `Bearer realm=\"${opts.ctx.req.url}\",error=\"${opts.error}\",error_description=\"${opts.errDescription}\"`,\n    },\n  })\n}\n"
  },
  {
    "path": "src/middleware/jwk/keys.test.json",
    "content": "{\n  \"public_keys\": [\n    {\n      \"kid\": \"hono-test-kid-1\",\n      \"kty\": \"RSA\",\n      \"use\": \"sig\",\n      \"alg\": \"RS256\",\n      \"e\": \"AQAB\",\n      \"n\": \"2XGQh8VC_p8gRqfBLY0E3RycnfBl5g1mKyeiyRSPjdaR7fmNPuC3mHjVWXtyXWSvAuRYPYfL_pSi6erpxVv7NuPJbKaZ-I1MwdRPdG2qHu9mNYxniws73gvF3tUN9eSsQUIBL0sYEOnVMjniDcOxIr3Rgz_RxdLB_FxTDXYhzzG49L79wGV1udILGHq0lqlMtmUX6LRtbaoRt1fJB4rTCkYeQp9r5HYP79PKTR43vLIq0aZryI4CyBkPG_0vGEvnzasGdp-qE9Ywt_J2anQKt3nvVVR4Yhs2EIoPQkYoDnVySjeuRsUA5JQYKThrM4sFZSQsO82dHTvwKo2z2x6ZMw\"\n    },\n    {\n      \"kid\": \"hono-test-kid-2\",\n      \"kty\": \"RSA\",\n      \"use\": \"sig\",\n      \"alg\": \"RS256\",\n      \"e\": \"AQAB\",\n      \"n\": \"uRVR5DkH22a_FM4RtqvnVxd6QAjdfj8oFYPaxIux7K8oTaBy5YagxTWN0qeKI5lI3nL20cx72XxD_UF4TETCFgfD-XB48cdjnSQlOXXbRPXUX0Rdte48naAt4przAb7ydUxrfvDlbSZe02Du-ZGRzEB6RW6KLFWUvTadI4w33qb2i8hauQuTcRmaIUESt8oytUGS44dXAw3Nqt_NL-e7TRgX5o1u_31Uvet1ofsv6Mx8vxJ6zMdM_AKvzLt2iuoK_8vL4R86CjD3dpal2BwO7RkRl2Wcuf5jxjM4pruJ2RBCpzBieEvSIH8kKHIm9SfTzTDJqRhoXd7KM5jL1GNzyw\"\n    }\n  ],\n  \"private_keys\": [\n    {\n      \"kid\": \"hono-test-kid-1\",\n      \"alg\": \"RS256\",\n      \"d\": \"A5CR2gGPegHwOYUbUzylZvdgUFNWMetOUK7M3TClGdVgSkWpELrTLhpTa3m50KYlG446x03baxUGU4D_MoKx7GukX0-fGCzY17FvWNOwOLACcPMYT3ZwfAQ2_jkBimJxU7CNUtH18KQ-U1B3nQ1apHZc-1Xa6CKIY5nv32yfj6uTrERRLOs7Fn9xpOE4uMHEf-l1ppIEIqK5QkEoPRMCUBABsGBSfiJP2hQVa-R-nezX3kVSxKTxAjDEOkquzb-CKlJW7xN2xQ7p40Wi7lDWZkOapBNGr59Z4gcFfo6f8XpQrqoFjDfsGsdH5q9MH_3lEEtD14wymXNnCoRHNr_mwQ\",\n      \"dp\": \"WMq_BNbd3At-J9VzXgE-aLvPhztS1W8K9xlghITpwAyzhEfCp9mO7IOEVtNWKoEtVFEaZrWKuNWKd-dnzjvydltCkpJ7QhTmiFNFsEzKNJdGQ1Tfsj9658csbVLUOhI4oVcN6kiCa6OdH41Z_JMyN75cTgd4z5h_FRYRRgjoUEU\",\n      \"dq\": \"Lz9vM7L-aEsPJOM5K2PqInLP9HNwDl943S79d_aw6w-JnHPFcu95no6-6nRcd87eSWoTvHZeFgsle4oiV0UpAocEO7xraCBa_Z9o-jGbBfynOLyXMH2l70yWBdCGCzgc_Wg2sKJwiYYXXfGJ3CzSeIRet82Rn54Q9mMlB6Ie8LE\",\n      \"e\": \"AQAB\",\n      \"kty\": \"RSA\",\n      \"n\": \"2XGQh8VC_p8gRqfBLY0E3RycnfBl5g1mKyeiyRSPjdaR7fmNPuC3mHjVWXtyXWSvAuRYPYfL_pSi6erpxVv7NuPJbKaZ-I1MwdRPdG2qHu9mNYxniws73gvF3tUN9eSsQUIBL0sYEOnVMjniDcOxIr3Rgz_RxdLB_FxTDXYhzzG49L79wGV1udILGHq0lqlMtmUX6LRtbaoRt1fJB4rTCkYeQp9r5HYP79PKTR43vLIq0aZryI4CyBkPG_0vGEvnzasGdp-qE9Ywt_J2anQKt3nvVVR4Yhs2EIoPQkYoDnVySjeuRsUA5JQYKThrM4sFZSQsO82dHTvwKo2z2x6ZMw\",\n      \"p\": \"7K-X3xMf3xxdlHTRs17x4WkbFUq4ZCU9L1al88UW2tpoF8ZDLUvaKXeF0vkosKvYUsiHsV1fbGVo6Oy75iII-op-t6-tP3R61nkjaytyJ8p32nbxBI1UWpFxZYNxG_Od07kau3LwkgDh8Ogr6zqmq8-lKoBPio-4K7PY5FiyWzs\",\n      \"q\": \"6y__IKt1n1pTc-S9l1WfSuC96jX8iQhEsGSxnshyNZi59mH1AigkrAw9T5b7OFX7ulHXwuithsVi8cxkq2inNmemxD3koiiU-sv6vg6lRCoZsXFHiUCP-2HoK17sR1zUb6HQpp5MEHY8qoC3Mi3IpkNC7gAbAukbMQo3WlIGqmk\",\n      \"qi\": \"flgM56Nw2hzHHy0Lz8ewBtOkkzfq1r_n6SmSZdU0zWlEp1lLovpHmuwyVeXpQlLJUHqcNVRw0NlwV7EN0rPd4rG3hcMdogj_Jl-r52TYzx4kVpbMEIh4xKs5rFzxbb96A3F9Ox-muRWvfOUCpXxGXCCGqHRmjRUolxDxsiPznuk\"\n    },\n    {\n      \"kid\": \"hono-test-kid-2\",\n      \"alg\": \"RS256\",\n      \"d\": \"JCIL50TVClnQQyUJ40JDO0b7mGXCrCNzVWP1ATsOhNkbQrBozfOPDoEqi24m81U5GyiRlBraMPboJRizfhxMUdW5RkjVa8pT4blNRR8DrD5b9C9aJir5DYLYgm1itLwNBKZjNBieicUcbSL29KUdNCWAWW6_rfEVRS1U1zxIKgDUPVd6d7jiIwAKuKvGlMc11RGRZj5eKSNMQyLU5u8Qs_VQuoBRNAyWLZZcHMlAWbh3er7m0jkmUDRdVU0y_n1UAGsr9cAxPwf2HtS5j5R2ahEodatsJynnafYtj6jbOR6jvO3N2Vf-NJ7jVY2-kfv1rJd86KAxD-tIAGx2w1VRTQ\",\n      \"dp\": \"wQhiWfdvVxk7ERmYj7Fn04wqjP7o7-72bn3SznGyBSkvpkg1WX4j467vpRtXVn4qxSSMXCj2UMKCrovba2RWHp1cnkvT-TFTbONkBuhOBpbx3TVwgGd-IfDJVa_i89XjiYgtEApHz173kRodEENXxcOj_mbOGyBb9Yl2M45A-tU\",\n      \"dq\": \"ERdP5mdziJ46OsDHTdZ4hOX2ti0EljtVqGo1B4WKXey6DMH0JGHGU_3fFiF4Gomhy3nyGUI7Qhk3kf7lixAtSsk1lWAAeQLPt1r8yZkD5odLKXLyua_yZJ041d3O3wxRYXl3OvzoVy6rPhzRPIaxevNp-Pp5ZNoKfonQPz3bDGc\",\n      \"e\": \"AQAB\",\n      \"kty\": \"RSA\",\n      \"n\": \"uRVR5DkH22a_FM4RtqvnVxd6QAjdfj8oFYPaxIux7K8oTaBy5YagxTWN0qeKI5lI3nL20cx72XxD_UF4TETCFgfD-XB48cdjnSQlOXXbRPXUX0Rdte48naAt4przAb7ydUxrfvDlbSZe02Du-ZGRzEB6RW6KLFWUvTadI4w33qb2i8hauQuTcRmaIUESt8oytUGS44dXAw3Nqt_NL-e7TRgX5o1u_31Uvet1ofsv6Mx8vxJ6zMdM_AKvzLt2iuoK_8vL4R86CjD3dpal2BwO7RkRl2Wcuf5jxjM4pruJ2RBCpzBieEvSIH8kKHIm9SfTzTDJqRhoXd7KM5jL1GNzyw\",\n      \"p\": \"7cY_nFnn4w5pVi7wq_S9FJHIGsxCwogXqSSC_d7yWopbI2rW3Ugx21IMcWT2pnpsF_VYQx5FnNFviFufNOloREOguqci4lBinAilYBf3VXaN_YrxSk4flJmykwm_HBbXpHt_L3t4HBf-uuY-klJxFkeTbBErjxMS0U0EheEpDYU\",\n      \"q\": \"x0UidqgkzWPqXa7vZ5noYTY5e3TDQZ_l8A26lFDKAbB62lXvnp_MhnQYDAx9VgUGYYrXv7UmaH-ZCSzuMM9Uhuw0lXRyojF-TLowNjASMlWbkJsJus3zi_AI4pAKyYnhNADxZrT1kxseI8zHiq0_bQa8qLaleXBTdkpc3Z6M1Q8\",\n      \"qi\": \"x5VJcfnlX9ZhH6eMKx27rOGQrPjQ4BjZgmND7rrX-CSrE0M0RG4KuC4ZOu5XpQ-YsOC_bIzolBN2cHGn4ttPXeUc3y5bnqJYo7FxMdGn4gPRbXlVjCrE54JH_cdkl8cDqcaybjme1-ilNu-vHJWgHPdpbOguhRpicARkptAkOe0\"\n    }\n  ]\n}\n"
  },
  {
    "path": "src/middleware/jwt/index.test.ts",
    "content": "import { describe, expectTypeOf } from 'vitest'\nimport { Hono } from '../../hono'\nimport { HTTPException } from '../../http-exception'\nimport { jwt } from '.'\nimport type { JwtVariables } from '.'\n\ndescribe('JWT', () => {\n  describe('Credentials in header', () => {\n    let handlerExecuted: boolean\n\n    beforeEach(() => {\n      handlerExecuted = false\n    })\n\n    const app = new Hono()\n\n    app.use('/auth/*', jwt({ secret: 'a-secret', alg: 'HS256' }))\n    app.use('/auth-unicode/*', jwt({ secret: 'a-secret', alg: 'HS256' }))\n    app.use('/nested/*', async (c, next) => {\n      const auth = jwt({ secret: 'a-secret', alg: 'HS256' })\n      return auth(c, next)\n    })\n\n    app.get('/auth/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n    app.get('/auth-unicode/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n    app.get('/nested/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n\n    it('Should not authorize', async () => {\n      const req = new Request('http://localhost/auth/a')\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(await res.text()).toBe('Unauthorized')\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should authorize', async () => {\n      const credential =\n        'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n      const req = new Request('http://localhost/auth/a')\n      req.headers.set('Authorization', `Bearer ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should authorize Unicode', async () => {\n      const credential =\n        'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n\n      const req = new Request('http://localhost/auth-unicode/a')\n      req.headers.set('Authorization', `Basic ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should not authorize Unicode', async () => {\n      const invalidToken =\n        'ssyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n\n      const url = 'http://localhost/auth-unicode/a'\n      const req = new Request(url)\n      req.headers.set('Authorization', `Basic ${invalidToken}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(res.headers.get('www-authenticate')).toEqual(\n        `Bearer realm=\"${url}\",error=\"invalid_token\",error_description=\"token verification failure\"`\n      )\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should not authorize', async () => {\n      const invalid_token = 'invalid token'\n      const url = 'http://localhost/auth/a'\n      const req = new Request(url)\n      req.headers.set('Authorization', `Bearer ${invalid_token}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(res.headers.get('www-authenticate')).toEqual(\n        `Bearer realm=\"${url}\",error=\"invalid_request\",error_description=\"invalid credentials structure\"`\n      )\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should not authorize - nested', async () => {\n      const req = new Request('http://localhost/nested/a')\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(await res.text()).toBe('Unauthorized')\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should authorize - nested', async () => {\n      const credential =\n        'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n      const req = new Request('http://localhost/nested/a')\n      req.headers.set('Authorization', `Bearer ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n  })\n\n  describe('Credentials in custom header', () => {\n    let handlerExecuted: boolean\n\n    beforeEach(() => {\n      handlerExecuted = false\n    })\n\n    const app = new Hono()\n\n    app.use(\n      '/auth/*',\n      jwt({ secret: 'a-secret', alg: 'HS256', headerName: 'x-custom-auth-header' })\n    )\n    app.use(\n      '/auth-unicode/*',\n      jwt({ secret: 'a-secret', alg: 'HS256', headerName: 'x-custom-auth-header' })\n    )\n    app.use('/nested/*', async (c, next) => {\n      const auth = jwt({ secret: 'a-secret', alg: 'HS256', headerName: 'x-custom-auth-header' })\n      return auth(c, next)\n    })\n\n    app.get('/auth/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n    app.get('/auth-unicode/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n    app.get('/nested/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n\n    it('Should not authorize', async () => {\n      const req = new Request('http://localhost/auth/a')\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(await res.text()).toBe('Unauthorized')\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should not authorize even if default authorization header present', async () => {\n      const credential =\n        'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n      const req = new Request('http://localhost/auth/a')\n      req.headers.set('Authorization', `Bearer ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(await res.text()).toBe('Unauthorized')\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should authorize', async () => {\n      const credential =\n        'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n      const req = new Request('http://localhost/auth/a')\n      req.headers.set('x-custom-auth-header', `Bearer ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should authorize Unicode', async () => {\n      const credential =\n        'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n\n      const req = new Request('http://localhost/auth-unicode/a')\n      req.headers.set('x-custom-auth-header', `Basic ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should not authorize Unicode', async () => {\n      const invalidToken =\n        'ssyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n\n      const url = 'http://localhost/auth-unicode/a'\n      const req = new Request(url)\n      req.headers.set('x-custom-auth-header', `Basic ${invalidToken}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(res.headers.get('www-authenticate')).toEqual(\n        `Bearer realm=\"${url}\",error=\"invalid_token\",error_description=\"token verification failure\"`\n      )\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should not authorize Unicode even if default authorization header present', async () => {\n      const invalidToken =\n        'ssyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n\n      const url = 'http://localhost/auth-unicode/a'\n      const req = new Request(url)\n      req.headers.set('Authorization', `Basic ${invalidToken}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(res.headers.get('www-authenticate')).toEqual(\n        `Bearer realm=\"${url}\",error=\"invalid_request\",error_description=\"no authorization included in request\"`\n      )\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should not authorize', async () => {\n      const invalid_token = 'invalid token'\n      const url = 'http://localhost/auth/a'\n      const req = new Request(url)\n      req.headers.set('x-custom-auth-header', `Bearer ${invalid_token}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(res.headers.get('www-authenticate')).toEqual(\n        `Bearer realm=\"${url}\",error=\"invalid_request\",error_description=\"invalid credentials structure\"`\n      )\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should not authorize - nested', async () => {\n      const req = new Request('http://localhost/nested/a')\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(await res.text()).toBe('Unauthorized')\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should not authorize - nested even if default authorization header present', async () => {\n      const credential =\n        'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n      const req = new Request('http://localhost/nested/a')\n      const res = await app.request(req)\n      req.headers.set('Authorization', `Bearer ${credential}`)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(await res.text()).toBe('Unauthorized')\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should authorize - nested', async () => {\n      const credential =\n        'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n      const req = new Request('http://localhost/nested/a')\n      req.headers.set('x-custom-auth-header', `Bearer ${credential}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n  })\n\n  describe('Credentials in cookie', () => {\n    let handlerExecuted: boolean\n\n    beforeEach(() => {\n      handlerExecuted = false\n    })\n\n    const app = new Hono()\n\n    app.use('/auth/*', jwt({ secret: 'a-secret', alg: 'HS256', cookie: 'access_token' }))\n    app.use('/auth-unicode/*', jwt({ secret: 'a-secret', alg: 'HS256', cookie: 'access_token' }))\n\n    app.get('/auth/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n    app.get('/auth-unicode/*', (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n\n    it('Should not authorize', async () => {\n      const req = new Request('http://localhost/auth/a')\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(await res.text()).toBe('Unauthorized')\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should authorize', async () => {\n      const url = 'http://localhost/auth/a'\n      const credential =\n        'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n      const req = new Request(url, {\n        headers: new Headers({\n          Cookie: `access_token=${credential}`,\n        }),\n      })\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(res.status).toBe(200)\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should authorize Unicode', async () => {\n      const credential =\n        'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n\n      const req = new Request('http://localhost/auth-unicode/a', {\n        headers: new Headers({\n          Cookie: `access_token=${credential}`,\n        }),\n      })\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n\n    it('Should not authorize Unicode', async () => {\n      const invalidToken =\n        'ssyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n\n      const url = 'http://localhost/auth-unicode/a'\n      const req = new Request(url)\n      req.headers.set('Cookie', `access_token=${invalidToken}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(res.headers.get('www-authenticate')).toEqual(\n        `Bearer realm=\"${url}\",error=\"invalid_token\",error_description=\"token verification failure\"`\n      )\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should not authorize', async () => {\n      const invalidToken = 'invalid token'\n      const url = 'http://localhost/auth/a'\n      const req = new Request(url)\n      req.headers.set('Cookie', `access_token=${invalidToken}`)\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(res.headers.get('www-authenticate')).toEqual(\n        `Bearer realm=\"${url}\",error=\"invalid_token\",error_description=\"token verification failure\"`\n      )\n      expect(handlerExecuted).toBeFalsy()\n    })\n  })\n\n  describe('Error handling with `cause`', () => {\n    const app = new Hono()\n\n    app.use('/auth/*', jwt({ secret: 'a-secret', alg: 'HS256' }))\n    app.get('/auth/*', (c) => c.text('Authorized'))\n\n    app.onError((e, c) => {\n      if (e instanceof HTTPException && e.cause instanceof Error) {\n        return c.json({ name: e.cause.name, message: e.cause.message }, 401)\n      }\n      return c.text(e.message, 401)\n    })\n\n    it('Should not authorize', async () => {\n      const credential = 'abc.def.ghi'\n      const req = new Request('http://localhost/auth')\n      req.headers.set('Authorization', `Bearer ${credential}`)\n      const res = await app.request(req)\n      expect(res.status).toBe(401)\n      expect(await res.json()).toEqual({\n        name: 'JwtTokenInvalid',\n        message: `invalid JWT token: ${credential}`,\n      })\n    })\n  })\n\n  describe('Credentials in signed cookie with prefix Options', () => {\n    let handlerExecuted: boolean\n\n    beforeEach(() => {\n      handlerExecuted = false\n    })\n\n    const app = new Hono()\n\n    app.use(\n      '/auth/*',\n      jwt({\n        secret: 'a-secret',\n        alg: 'HS256',\n        cookie: {\n          key: 'cookie_name',\n          secret: 'cookie_secret',\n          prefixOptions: 'host',\n        },\n      })\n    )\n\n    app.get('/auth/*', async (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n\n    it('Should not authorize', async () => {\n      const req = new Request('http://localhost/auth/a')\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(await res.text()).toBe('Unauthorized')\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should authorize', async () => {\n      const url = 'http://localhost/auth/a'\n      const req = new Request(url, {\n        headers: new Headers({\n          Cookie:\n            '__Host-cookie_name=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE.i2NSvtJOXOPS9NDL1u8dqTYmMrzcD4mNSws6P6qmeV0%3D; Path=/',\n        }),\n      })\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n  })\n\n  describe('Credentials in signed cookie without prefix Options', () => {\n    let handlerExecuted: boolean\n\n    beforeEach(() => {\n      handlerExecuted = false\n    })\n\n    const app = new Hono()\n\n    app.use(\n      '/auth/*',\n      jwt({\n        secret: 'a-secret',\n        alg: 'HS256',\n        cookie: {\n          key: 'cookie_name',\n          secret: 'cookie_secret',\n        },\n      })\n    )\n\n    app.get('/auth/*', async (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n\n    it('Should not authorize', async () => {\n      const req = new Request('http://localhost/auth/a')\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(await res.text()).toBe('Unauthorized')\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should authorize', async () => {\n      const url = 'http://localhost/auth/a'\n      const req = new Request(url, {\n        headers: new Headers({\n          Cookie:\n            'cookie_name=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE.i2NSvtJOXOPS9NDL1u8dqTYmMrzcD4mNSws6P6qmeV0%3D; Path=/',\n        }),\n      })\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n  })\n\n  describe('Credentials in cookie object with prefix Options', () => {\n    let handlerExecuted: boolean\n\n    beforeEach(() => {\n      handlerExecuted = false\n    })\n\n    const app = new Hono()\n\n    app.use(\n      '/auth/*',\n      jwt({\n        secret: 'a-secret',\n        alg: 'HS256',\n        cookie: {\n          key: 'cookie_name',\n          prefixOptions: 'host',\n        },\n      })\n    )\n\n    app.get('/auth/*', async (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n\n    it('Should not authorize', async () => {\n      const req = new Request('http://localhost/auth/a')\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(await res.text()).toBe('Unauthorized')\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should authorize', async () => {\n      const url = 'http://localhost/auth/a'\n      const req = new Request(url, {\n        headers: new Headers({\n          Cookie:\n            '__Host-cookie_name=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE',\n        }),\n      })\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n  })\n\n  describe('Credentials in cookie object without prefix Options', () => {\n    let handlerExecuted: boolean\n\n    beforeEach(() => {\n      handlerExecuted = false\n    })\n\n    const app = new Hono()\n\n    app.use(\n      '/auth/*',\n      jwt({\n        secret: 'a-secret',\n        alg: 'HS256',\n        cookie: {\n          key: 'cookie_name',\n        },\n      })\n    )\n\n    app.get('/auth/*', async (c) => {\n      handlerExecuted = true\n      const payload = c.get('jwtPayload')\n      return c.json(payload)\n    })\n\n    it('Should not authorize', async () => {\n      const req = new Request('http://localhost/auth/a')\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(401)\n      expect(await res.text()).toBe('Unauthorized')\n      expect(handlerExecuted).toBeFalsy()\n    })\n\n    it('Should authorize', async () => {\n      const url = 'http://localhost/auth/a'\n      const req = new Request(url, {\n        headers: new Headers({\n          Cookie:\n            'cookie_name=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE',\n        }),\n      })\n      const res = await app.request(req)\n      expect(res).not.toBeNull()\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ message: 'hello world' })\n      expect(handlerExecuted).toBeTruthy()\n    })\n  })\n\n  describe('Security: Algorithm Confusion Attack Prevention', () => {\n    it('Should throw error when alg option is not provided', () => {\n      expect(() => {\n        // @ts-expect-error - intentionally testing without alg option\n        jwt({ secret: 'a-secret' })\n      }).toThrow('JWT auth middleware requires options for \"alg\"')\n    })\n\n    it('Should reject tokens with mismatched algorithm', async () => {\n      const app = new Hono()\n\n      // Configure middleware to expect RS256\n      app.use('/auth/*', jwt({ secret: 'a-secret', alg: 'RS256' }))\n      app.get('/auth/*', (c) => {\n        return c.json(c.get('jwtPayload'))\n      })\n\n      // Try to use a HS256 token (algorithm confusion attempt)\n      const hs256Token =\n        'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n\n      const req = new Request('http://localhost/auth/a')\n      req.headers.set('Authorization', `Bearer ${hs256Token}`)\n      const res = await app.request(req)\n\n      // Should fail because the token algorithm doesn't match expected algorithm\n      expect(res.status).toBe(401)\n    })\n\n    it('Should require explicit alg specification in middleware options', () => {\n      // This should work - explicit alg specified\n      expect(() => {\n        jwt({ secret: 'a-secret', alg: 'HS256' })\n      }).not.toThrow()\n\n      // This should throw - alg not specified\n      expect(() => {\n        // @ts-expect-error - intentionally testing without alg option\n        jwt({ secret: 'a-secret' })\n      }).toThrow('JWT auth middleware requires options for \"alg\"')\n    })\n  })\n\n  describe('Type tests', () => {\n    it('Should infer correct payload type when JwtVariables<T> is specified', () => {\n      type User = {\n        id: string\n        email: string\n        isAdmin: boolean\n      }\n\n      const app = new Hono<{ Variables: JwtVariables<User> }>()\n\n      app.get('/', (c) => {\n        const payload = c.var.jwtPayload\n        expectTypeOf(payload).toEqualTypeOf<User>()\n        return c.json(payload)\n      })\n    })\n\n    it('Should infer unknown when JwtVariables is used without type parameter in Variables', () => {\n      const app = new Hono<{ Variables: JwtVariables }>()\n\n      app.get('/', (c) => {\n        const payload = c.var.jwtPayload\n        expectTypeOf(payload).toEqualTypeOf<any>()\n        return c.json(payload)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/middleware/jwt/index.ts",
    "content": "import type { JwtVariables } from './jwt'\nexport type { JwtVariables }\nexport { jwt, verifyWithJwks, verify, decode, sign } from './jwt'\nexport { AlgorithmTypes } from '../../utils/jwt/jwa'\nimport type {} from '../..'\n\ndeclare module '../..' {\n  interface ContextVariableMap extends JwtVariables<unknown> {}\n}\n"
  },
  {
    "path": "src/middleware/jwt/jwt.ts",
    "content": "/**\n * @module\n * JWT Auth Middleware for Hono.\n */\n\nimport type { Context } from '../../context'\nimport { getCookie, getSignedCookie } from '../../helper/cookie'\nimport { HTTPException } from '../../http-exception'\nimport type { MiddlewareHandler } from '../../types'\nimport type { CookiePrefixOptions } from '../../utils/cookie'\nimport { Jwt } from '../../utils/jwt'\nimport '../../context'\nimport type { SignatureAlgorithm } from '../../utils/jwt/jwa'\nimport type { SignatureKey } from '../../utils/jwt/jws'\nimport type { VerifyOptions } from '../../utils/jwt/jwt'\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type JwtVariables<T = any> = {\n  jwtPayload: T\n}\n\n/**\n * JWT Auth Middleware for Hono.\n *\n * @see {@link https://hono.dev/docs/middleware/builtin/jwt}\n *\n * @param {object} options - The options for the JWT middleware.\n * @param {SignatureKey} options.secret - A value of your secret key.\n * @param {string} [options.cookie] - If this value is set, then the value is retrieved from the cookie header using that value as a key, which is then validated as a token.\n * @param {SignatureAlgorithm} options.alg - An algorithm type that is used for verifying (required). Available types are `HS256` | `HS384` | `HS512` | `RS256` | `RS384` | `RS512` | `PS256` | `PS384` | `PS512` | `ES256` | `ES384` | `ES512` | `EdDSA`.\n * @param {string} [options.headerName='Authorization'] - The name of the header to look for the JWT token. Default is 'Authorization'.\n * @param {VerifyOptions} [options.verification] - Additional options for JWT payload verification.\n * @returns {MiddlewareHandler} The middleware handler function.\n *\n * @example\n * ```ts\n * const app = new Hono()\n *\n * app.use(\n *   '/auth/*',\n *   jwt({\n *     secret: 'it-is-very-secret',\n *     alg: 'HS256',\n *     headerName: 'x-custom-auth-header', // Optional, default is 'Authorization'\n *   })\n * )\n *\n * app.get('/auth/page', (c) => {\n *   return c.text('You are authorized')\n * })\n * ```\n */\nexport const jwt = (options: {\n  secret: SignatureKey\n  cookie?:\n    | string\n    | { key: string; secret?: string | BufferSource; prefixOptions?: CookiePrefixOptions }\n  alg: SignatureAlgorithm\n  headerName?: string\n  verification?: VerifyOptions\n}): MiddlewareHandler => {\n  const verifyOpts = options.verification || {}\n\n  if (!options || !options.secret) {\n    throw new Error('JWT auth middleware requires options for \"secret\"')\n  }\n\n  if (!options.alg) {\n    throw new Error('JWT auth middleware requires options for \"alg\"')\n  }\n\n  if (!crypto.subtle || !crypto.subtle.importKey) {\n    throw new Error('`crypto.subtle.importKey` is undefined. JWT auth middleware requires it.')\n  }\n\n  return async function jwt(ctx, next) {\n    const headerName = options.headerName || 'Authorization'\n\n    const credentials = ctx.req.raw.headers.get(headerName)\n    let token\n    if (credentials) {\n      const parts = credentials.split(/\\s+/)\n      if (parts.length !== 2) {\n        const errDescription = 'invalid credentials structure'\n        throw new HTTPException(401, {\n          message: errDescription,\n          res: unauthorizedResponse({\n            ctx,\n            error: 'invalid_request',\n            errDescription,\n          }),\n        })\n      } else {\n        token = parts[1]\n      }\n    } else if (options.cookie) {\n      if (typeof options.cookie == 'string') {\n        token = getCookie(ctx, options.cookie)\n      } else if (options.cookie.secret) {\n        if (options.cookie.prefixOptions) {\n          token = await getSignedCookie(\n            ctx,\n            options.cookie.secret,\n            options.cookie.key,\n            options.cookie.prefixOptions\n          )\n        } else {\n          token = await getSignedCookie(ctx, options.cookie.secret, options.cookie.key)\n        }\n      } else {\n        if (options.cookie.prefixOptions) {\n          token = getCookie(ctx, options.cookie.key, options.cookie.prefixOptions)\n        } else {\n          token = getCookie(ctx, options.cookie.key)\n        }\n      }\n    }\n\n    if (!token) {\n      const errDescription = 'no authorization included in request'\n      throw new HTTPException(401, {\n        message: errDescription,\n        res: unauthorizedResponse({\n          ctx,\n          error: 'invalid_request',\n          errDescription,\n        }),\n      })\n    }\n\n    let payload\n    let cause\n    try {\n      payload = await Jwt.verify(token, options.secret, {\n        alg: options.alg,\n        ...verifyOpts,\n      })\n    } catch (e) {\n      cause = e\n    }\n    if (!payload) {\n      throw new HTTPException(401, {\n        message: 'Unauthorized',\n        res: unauthorizedResponse({\n          ctx,\n          error: 'invalid_token',\n          statusText: 'Unauthorized',\n          errDescription: 'token verification failure',\n        }),\n        cause,\n      })\n    }\n\n    ctx.set('jwtPayload', payload)\n\n    await next()\n  }\n}\n\nfunction unauthorizedResponse(opts: {\n  ctx: Context\n  error: string\n  errDescription: string\n  statusText?: string\n}) {\n  return new Response('Unauthorized', {\n    status: 401,\n    statusText: opts.statusText,\n    headers: {\n      'WWW-Authenticate': `Bearer realm=\"${opts.ctx.req.url}\",error=\"${opts.error}\",error_description=\"${opts.errDescription}\"`,\n    },\n  })\n}\n\nexport const verifyWithJwks = Jwt.verifyWithJwks\nexport const verify = Jwt.verify\nexport const decode = Jwt.decode\nexport const sign = Jwt.sign\n"
  },
  {
    "path": "src/middleware/language/index.test.ts",
    "content": "import { Hono } from '../../hono'\nimport { detectors } from './language'\nimport { languageDetector } from '.'\n\ndescribe('languageDetector', () => {\n  const createTestApp = (options = {}) => {\n    const app = new Hono()\n\n    app.use('/*', languageDetector(options))\n\n    app.get('/*', (c) => c.text(c.get('language')))\n\n    return app\n  }\n\n  describe('Query Parameter Detection', () => {\n    it('should detect language from query parameter', async () => {\n      const app = createTestApp({\n        supportedLanguages: ['en', 'fr', 'es'],\n        fallbackLanguage: 'en',\n      })\n\n      const res = await app.request('/?lang=fr')\n      expect(await res.text()).toBe('fr')\n    })\n\n    it('should ignore unsupported languages in query', async () => {\n      const app = createTestApp({\n        supportedLanguages: ['en', 'fr'],\n        fallbackLanguage: 'en',\n      })\n\n      const res = await app.request('/?lang=de')\n      expect(await res.text()).toBe('en')\n    })\n  })\n\n  describe('Cookie Detection', () => {\n    it('should detect language from cookie', async () => {\n      const app = createTestApp({\n        supportedLanguages: ['en', 'fr'],\n        fallbackLanguage: 'en',\n      })\n\n      const res = await app.request('/', {\n        headers: {\n          cookie: 'language=fr',\n        },\n      })\n      expect(await res.text()).toBe('fr')\n    })\n\n    it('should cache detected language in cookie when enabled', async () => {\n      const app = createTestApp({\n        supportedLanguages: ['en', 'fr'],\n        fallbackLanguage: 'en',\n        caches: ['cookie'],\n      })\n\n      const res = await app.request('/?lang=fr')\n      expect(res.headers.get('set-cookie')).toContain('language=fr')\n    })\n  })\n\n  describe('Header Detection', () => {\n    it('should detect language from Accept-Language header', async () => {\n      const app = createTestApp({\n        supportedLanguages: ['en', 'fr', 'es'],\n        fallbackLanguage: 'en',\n      })\n\n      const res = await app.request('/', {\n        headers: {\n          'accept-language': 'fr-FR,fr;q=0.9,en;q=0.8',\n        },\n      })\n      expect(await res.text()).toBe('fr')\n    })\n\n    it('should fallback to language code when locale code is not in supportedLanguages', async () => {\n      const app = createTestApp({\n        supportedLanguages: ['en', 'ja'],\n        fallbackLanguage: 'en',\n        order: ['header'],\n      })\n\n      const res = await app.request('/', {\n        headers: {\n          'accept-language': 'ja-JP',\n        },\n      })\n      expect(await res.text()).toBe('ja')\n    })\n\n    it('should match after multiple truncations', async () => {\n      const app = createTestApp({\n        supportedLanguages: ['zh-Hant', 'en'],\n        fallbackLanguage: 'en',\n        order: ['header'],\n      })\n\n      const res = await app.request('/', {\n        headers: {\n          'accept-language': 'zh-Hant-CN',\n        },\n      })\n      expect(await res.text()).toBe('zh-Hant')\n    })\n\n    it('should fallback when truncation does not match any supported language', async () => {\n      const app = createTestApp({\n        supportedLanguages: ['en', 'ja'],\n        fallbackLanguage: 'en',\n        order: ['header'],\n      })\n\n      const res = await app.request('/', {\n        headers: {\n          'accept-language': 'ko-KR',\n        },\n      })\n      expect(await res.text()).toBe('en')\n    })\n\n    it('should prefer exact match over truncated match', async () => {\n      const app = createTestApp({\n        supportedLanguages: ['fr', 'fr-CA'],\n        fallbackLanguage: 'fr',\n        order: ['header'],\n      })\n\n      const res = await app.request('/', {\n        headers: {\n          'accept-language': 'fr-CA',\n        },\n      })\n      expect(await res.text()).toBe('fr-CA')\n    })\n\n    it('should handle case-insensitive truncation matching', async () => {\n      const app = createTestApp({\n        supportedLanguages: ['en', 'ja'],\n        fallbackLanguage: 'en',\n        order: ['header'],\n        ignoreCase: true,\n      })\n\n      const res = await app.request('/', {\n        headers: {\n          'accept-language': 'JA-JP',\n        },\n      })\n      expect(await res.text()).toBe('ja')\n    })\n\n    it('should handle malformed Accept-Language headers', async () => {\n      const app = createTestApp({\n        supportedLanguages: ['en', 'fr'],\n        fallbackLanguage: 'en',\n      })\n\n      const res = await app.request('/', {\n        headers: {\n          'accept-language': 'invalid;header;;format',\n        },\n      })\n      expect(await res.text()).toBe('en')\n    })\n  })\n\n  describe('Path Detection', () => {\n    it('should detect language from path', async () => {\n      const app = createTestApp({\n        order: ['path'],\n        supportedLanguages: ['en', 'fr'],\n        fallbackLanguage: 'en',\n        lookupFromPathIndex: 0,\n      })\n\n      const res = await app.request('/fr/page')\n      expect(await res.text()).toBe('fr')\n    })\n\n    it('should handle invalid path index gracefully', async () => {\n      const app = createTestApp({\n        order: ['path'],\n        supportedLanguages: ['en', 'fr'],\n        fallbackLanguage: 'en',\n        lookupFromPathIndex: 99,\n      })\n\n      const res = await app.request('/fr/page')\n      expect(await res.text()).toBe('en')\n    })\n\n    it('should detect language from original URL when getPath modifies the path', async () => {\n      const app = new Hono({\n        getPath: (req) => {\n          const url = new URL(req.url)\n          let pathname = url.pathname\n          // Remove language prefix /fr/\n          if (pathname.startsWith('/fr/')) {\n            pathname = pathname.replace('/fr/', '/')\n          }\n          return pathname\n        },\n      })\n\n      app.use(\n        '*',\n        languageDetector({\n          order: ['path'],\n          supportedLanguages: ['en', 'fr'],\n          fallbackLanguage: 'en',\n          lookupFromPathIndex: 0,\n        })\n      )\n\n      app.get('/home', (c) => c.text(c.get('language')))\n\n      const res = await app.request('/fr/home')\n      expect(await res.text()).toBe('fr')\n    })\n  })\n\n  describe('Detection Order', () => {\n    it('should respect detection order', async () => {\n      const app = createTestApp({\n        order: ['cookie', 'querystring'],\n        supportedLanguages: ['en', 'fr', 'es'],\n        fallbackLanguage: 'en',\n      })\n\n      const res = await app.request('/?lang=fr', {\n        headers: {\n          cookie: 'language=es',\n        },\n      })\n\n      // Since cookie is first in order, it should use 'es'\n      expect(await res.text()).toBe('es')\n    })\n\n    it('should fall back to next detector if first fails', async () => {\n      const app = createTestApp({\n        order: ['cookie', 'querystring'],\n        supportedLanguages: ['en', 'fr'],\n        fallbackLanguage: 'en',\n      })\n\n      const res = await app.request('/?lang=fr') // No cookie\n      expect(await res.text()).toBe('fr') // Should use querystring\n    })\n  })\n\n  describe('Language Conversion', () => {\n    it('should apply language conversion function', async () => {\n      const app = createTestApp({\n        supportedLanguages: ['en', 'fr'],\n        fallbackLanguage: 'en',\n        convertDetectedLanguage: (lang: string) => lang.split('-')[0],\n      })\n\n      const res = await app.request('/?lang=fr-FR')\n      expect(await res.text()).toBe('fr')\n    })\n\n    it('should handle case sensitivity according to options', async () => {\n      const app = createTestApp({\n        supportedLanguages: ['en', 'fr'],\n        fallbackLanguage: 'en',\n        ignoreCase: false,\n      })\n\n      const res = await app.request('/?lang=FR')\n      expect(await res.text()).toBe('en') // Falls back because case doesn't match\n    })\n  })\n\n  describe('Error Handling', () => {\n    it('should fall back to default language on error', async () => {\n      const app = createTestApp({\n        supportedLanguages: ['en', 'fr'],\n        fallbackLanguage: 'en',\n      })\n\n      const detector = vi.spyOn(detectors, 'querystring').mockImplementation(() => {\n        throw new Error('Simulated error')\n      })\n\n      const res = await app.request('/?lang=fr')\n      expect(await res.text()).toBe('en')\n\n      detector.mockRestore()\n    })\n\n    it('should handle missing cookie values gracefully', async () => {\n      const app = createTestApp({\n        supportedLanguages: ['en', 'fr'],\n        fallbackLanguage: 'en',\n        order: ['cookie'],\n      })\n\n      const res = await app.request('/')\n      expect(await res.text()).toBe('en')\n    })\n  })\n\n  describe('Configuration Validation', () => {\n    it('should throw if fallback language is not in supported languages', () => {\n      expect(() => {\n        createTestApp({\n          supportedLanguages: ['fr', 'es'],\n          fallbackLanguage: 'en',\n        })\n      }).toThrow()\n    })\n\n    it('should throw if path index is negative', () => {\n      expect(() => {\n        createTestApp({\n          lookupFromPathIndex: -1,\n        })\n      }).toThrow()\n    })\n\n    it('should handle empty supported languages list', () => {\n      expect(() => {\n        createTestApp({\n          supportedLanguages: [],\n        })\n      }).toThrow()\n    })\n  })\n\n  describe('Debug Mode', () => {\n    it('should log errors in debug mode', async () => {\n      const consoleErrorSpy = vi.spyOn(console, 'error')\n\n      const app = createTestApp({\n        supportedLanguages: ['en', 'fr'],\n        fallbackLanguage: 'en',\n        debug: true,\n      })\n\n      const detector = vi.spyOn(detectors, 'querystring').mockImplementation(() => {\n        throw new Error('Simulated error')\n      })\n\n      await app.request('/?lang=fr')\n\n      expect(consoleErrorSpy).toHaveBeenCalledWith(\n        'Error in querystring detector:',\n        expect.any(Error)\n      )\n\n      detector.mockRestore()\n      consoleErrorSpy.mockRestore()\n    })\n\n    // The log test remains unchanged\n    it('should log debug information when enabled', async () => {\n      const consoleSpy = vi.spyOn(console, 'log')\n\n      const app = createTestApp({\n        supportedLanguages: ['en', 'fr'],\n        fallbackLanguage: 'en',\n        debug: true,\n      })\n\n      await app.request('/?lang=fr')\n\n      expect(consoleSpy).toHaveBeenCalledWith(\n        expect.stringContaining('Language detected from querystring')\n      )\n\n      consoleSpy.mockRestore()\n    })\n  })\n})\n"
  },
  {
    "path": "src/middleware/language/index.ts",
    "content": "import type { LanguageVariables, DetectorOptions, DetectorType, CacheType } from './language'\nexport type { LanguageVariables, DetectorOptions, DetectorType, CacheType }\nexport {\n  languageDetector,\n  detectFromCookie,\n  detectFromHeader,\n  detectFromPath,\n  detectFromQuery,\n} from './language'\ndeclare module '../..' {\n  interface ContextVariableMap extends LanguageVariables {}\n}\n"
  },
  {
    "path": "src/middleware/language/language.ts",
    "content": "/**\n * @module\n * Language module for Hono.\n */\nimport type { Context } from '../../context'\nimport { setCookie, getCookie } from '../../helper/cookie'\nimport type { MiddlewareHandler } from '../../types'\nimport { parseAccept } from '../../utils/accept'\n\nexport type DetectorType = 'path' | 'querystring' | 'cookie' | 'header'\nexport type CacheType = 'cookie'\n\nexport interface DetectorOptions {\n  /** Order of language detection strategies */\n  order: DetectorType[]\n  /** Query parameter name for language */\n  lookupQueryString: string\n  /** Cookie name for language */\n  lookupCookie: string\n  /** Index in URL path where language code appears */\n  lookupFromPathIndex: number\n  /** Header key for language detection */\n  lookupFromHeaderKey: string\n  /** Caching strategies */\n  caches: CacheType[] | false\n  /** Cookie configuration options */\n  cookieOptions?: {\n    domain?: string\n    path?: string\n    sameSite?: 'Strict' | 'Lax' | 'None'\n    secure?: boolean\n    maxAge?: number\n    httpOnly?: boolean\n  }\n  /** Whether to ignore case in language codes */\n  ignoreCase: boolean\n  /** Default language if none detected */\n  fallbackLanguage: string\n  /** List of supported language codes */\n  supportedLanguages: string[]\n  /** Optional function to transform detected language codes */\n  convertDetectedLanguage?: (lang: string) => string\n  /** Enable debug logging */\n  debug?: boolean\n}\n\nexport interface LanguageVariables {\n  language: string\n}\n\nexport const DEFAULT_OPTIONS: DetectorOptions = {\n  order: ['querystring', 'cookie', 'header'],\n  lookupQueryString: 'lang',\n  lookupCookie: 'language',\n  lookupFromHeaderKey: 'accept-language',\n  lookupFromPathIndex: 0,\n  caches: ['cookie'],\n  ignoreCase: true,\n  fallbackLanguage: 'en',\n  supportedLanguages: ['en'],\n  cookieOptions: {\n    sameSite: 'Strict',\n    secure: true,\n    maxAge: 365 * 24 * 60 * 60,\n    httpOnly: true,\n  },\n  debug: false,\n}\n/**\n * Parse Accept-Language header values with quality scores\n * @param header Accept-Language header string\n * @returns Array of parsed languages with quality scores\n */\nexport function parseAcceptLanguage(header: string): Array<{ lang: string; q: number }> {\n  return parseAccept(header).map(({ type, q }) => ({ lang: type, q }))\n}\n\n/**\n * Validate and normalize language codes\n * @param lang Language code to normalize\n * @param options Detector options\n * @returns Normalized language code or undefined\n */\nexport const normalizeLanguage = (\n  lang: string | null | undefined,\n  options: DetectorOptions\n): string | undefined => {\n  if (!lang) {\n    return undefined\n  }\n\n  try {\n    let normalizedLang = lang.trim()\n    if (options.convertDetectedLanguage) {\n      normalizedLang = options.convertDetectedLanguage(normalizedLang)\n    }\n\n    const compLang = options.ignoreCase ? normalizedLang.toLowerCase() : normalizedLang\n    const compSupported = options.supportedLanguages.map((l) =>\n      options.ignoreCase ? l.toLowerCase() : l\n    )\n\n    // Exact match\n    const exactIndex = compSupported.indexOf(compLang)\n    if (exactIndex !== -1) {\n      return options.supportedLanguages[exactIndex]\n    }\n\n    // Progressive truncation (RFC 4647 Lookup)\n    const parts = compLang.split('-')\n    for (let i = parts.length - 1; i > 0; i--) {\n      const candidate = parts.slice(0, i).join('-')\n      const prefixIndex = compSupported.indexOf(candidate)\n      if (prefixIndex !== -1) {\n        return options.supportedLanguages[prefixIndex]\n      }\n    }\n\n    return undefined\n  } catch {\n    return undefined\n  }\n}\n\n/**\n * Detects language from query parameter\n */\nexport const detectFromQuery = (c: Context, options: DetectorOptions): string | undefined => {\n  try {\n    const query = c.req.query(options.lookupQueryString)\n    return normalizeLanguage(query, options)\n  } catch {\n    return undefined\n  }\n}\n\n/**\n * Detects language from cookie\n */\nexport const detectFromCookie = (c: Context, options: DetectorOptions): string | undefined => {\n  try {\n    const cookie = getCookie(c, options.lookupCookie)\n    return normalizeLanguage(cookie, options)\n  } catch {\n    return undefined\n  }\n}\n\n/**\n * Detects language from Accept-Language header\n */\nexport function detectFromHeader(c: Context, options: DetectorOptions): string | undefined {\n  try {\n    const acceptLanguage = c.req.header(options.lookupFromHeaderKey)\n    if (!acceptLanguage) {\n      return undefined\n    }\n\n    const languages = parseAcceptLanguage(acceptLanguage)\n    for (const { lang } of languages) {\n      const normalizedLang = normalizeLanguage(lang, options)\n      if (normalizedLang) {\n        return normalizedLang\n      }\n    }\n    return undefined\n  } catch {\n    return undefined\n  }\n}\n\n/**\n * Detects language from URL path\n */\nexport function detectFromPath(c: Context, options: DetectorOptions): string | undefined {\n  try {\n    const url = new URL(c.req.url)\n    const pathSegments = url.pathname.split('/').filter(Boolean)\n    const langSegment = pathSegments[options.lookupFromPathIndex]\n    return normalizeLanguage(langSegment, options)\n  } catch {\n    return undefined\n  }\n}\n\n/**\n * Collection of all language detection strategies\n */\nexport const detectors = {\n  querystring: detectFromQuery,\n  cookie: detectFromCookie,\n  header: detectFromHeader,\n  path: detectFromPath,\n} as const\n\n/** Type for detector functions */\nexport type DetectorFunction = (c: Context, options: DetectorOptions) => string | undefined\n\n/** Type-safe detector map */\nexport type Detectors = Record<keyof typeof detectors, DetectorFunction>\n\n/**\n * Validate detector options\n * @param options Detector options to validate\n * @throws Error if options are invalid\n */\nexport function validateOptions(options: DetectorOptions): void {\n  if (!options.supportedLanguages.includes(options.fallbackLanguage)) {\n    throw new Error('Fallback language must be included in supported languages')\n  }\n\n  if (options.lookupFromPathIndex < 0) {\n    throw new Error('Path index must be non-negative')\n  }\n\n  if (!options.order.every((detector) => Object.keys(detectors).includes(detector))) {\n    throw new Error('Invalid detector type in order array')\n  }\n}\n\n/**\n * Cache detected language\n */\nfunction cacheLanguage(c: Context, language: string, options: DetectorOptions): void {\n  if (!Array.isArray(options.caches) || !options.caches.includes('cookie')) {\n    return\n  }\n\n  try {\n    setCookie(c, options.lookupCookie, language, options.cookieOptions)\n  } catch (error) {\n    if (options.debug) {\n      console.error('Failed to cache language:', error)\n    }\n  }\n}\n\n/**\n * Detect language from request\n */\nconst detectLanguage = (c: Context, options: DetectorOptions): string => {\n  let detectedLang: string | undefined\n\n  for (const detectorName of options.order) {\n    const detector = detectors[detectorName]\n    if (!detector) {\n      continue\n    }\n\n    try {\n      detectedLang = detector(c, options)\n      if (detectedLang) {\n        if (options.debug) {\n          console.log(`Language detected from ${detectorName}: ${detectedLang}`)\n        }\n        break\n      }\n    } catch (error) {\n      if (options.debug) {\n        console.error(`Error in ${detectorName} detector:`, error)\n      }\n      continue\n    }\n  }\n\n  const finalLang = detectedLang || options.fallbackLanguage\n\n  if (detectedLang && options.caches) {\n    cacheLanguage(c, finalLang, options)\n  }\n\n  return finalLang\n}\n\n/**\n * Language detector middleware factory\n * @param userOptions Configuration options for the language detector\n * @returns Hono middleware function\n */\nexport const languageDetector = (userOptions: Partial<DetectorOptions>): MiddlewareHandler => {\n  const options: DetectorOptions = {\n    ...DEFAULT_OPTIONS,\n    ...userOptions,\n    cookieOptions: {\n      ...DEFAULT_OPTIONS.cookieOptions,\n      ...userOptions.cookieOptions,\n    },\n  }\n\n  validateOptions(options)\n\n  return async function languageDetector(ctx, next) {\n    try {\n      const lang = detectLanguage(ctx, options)\n      ctx.set('language', lang)\n    } catch (error) {\n      if (options.debug) {\n        console.error('Language detection failed:', error)\n      }\n      ctx.set('language', options.fallbackLanguage)\n    }\n\n    await next()\n  }\n}\n"
  },
  {
    "path": "src/middleware/logger/index.test.ts",
    "content": "import { Hono } from '../../hono'\nimport { logger } from '.'\n\ndescribe('Logger by Middleware', () => {\n  let app: Hono\n  let log: string\n\n  beforeEach(() => {\n    function sleep(time: number) {\n      return new Promise((resolve) => setTimeout(resolve, time))\n    }\n\n    app = new Hono()\n\n    const logFn = (str: string) => {\n      log = str\n    }\n\n    const shortRandomString = 'hono'\n    const longRandomString = 'hono'.repeat(1000)\n\n    app.use('*', logger(logFn))\n    app.get('/short', (c) => c.text(shortRandomString))\n    app.get('/long', (c) => c.text(longRandomString))\n    app.get('/seconds', async (c) => {\n      await sleep(1000)\n\n      return c.text(longRandomString)\n    })\n    app.get('/empty', (c) => c.text(''))\n    app.get('/redirect', (c) => {\n      return c.redirect('/empty', 301)\n    })\n    app.get('/server-error', (c) => {\n      const res = new Response('', { status: 511 })\n      if (c.req.query('status')) {\n        // test status code not yet supported by runtime `Response` object\n        Object.defineProperty(res, 'status', { value: parseInt(c.req.query('status') as string) })\n      }\n      return res\n    })\n  })\n\n  it('Log status 200 with empty body', async () => {\n    const res = await app.request('http://localhost/empty')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(log.startsWith('--> GET /empty \\x1b[32m200\\x1b[0m')).toBe(true)\n    expect(log).toMatch(/m?s$/)\n  })\n\n  it('Log status 200 with small body', async () => {\n    const res = await app.request('http://localhost/short')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(log.startsWith('--> GET /short \\x1b[32m200\\x1b[0m')).toBe(true)\n    expect(log).toMatch(/m?s$/)\n  })\n\n  it('Log status 200 with small body and query param', async () => {\n    const res = await app.request('http://localhost/short?foo=bar')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(log.startsWith('--> GET /short?foo=bar \\x1b[32m200\\x1b[0m')).toBe(true)\n    expect(log).toMatch(/m?s$/)\n  })\n\n  it('Log status 200 with big body', async () => {\n    const res = await app.request('http://localhost/long')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(log.startsWith('--> GET /long \\x1b[32m200\\x1b[0m')).toBe(true)\n    expect(log).toMatch(/m?s$/)\n  })\n\n  it('Time in seconds', async () => {\n    const res = await app.request('http://localhost/seconds')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(log.startsWith('--> GET /seconds \\x1b[32m200\\x1b[0m')).toBe(true)\n    expect(log).toMatch(/1s/)\n  })\n\n  it('Log status 301 with empty body', async () => {\n    const res = await app.request('http://localhost/redirect')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(301)\n    expect(log.startsWith('--> GET /redirect \\x1b[36m301\\x1b[0m')).toBe(true)\n    expect(log).toMatch(/m?s$/)\n  })\n\n  it('Log status 404', async () => {\n    const msg = 'Default 404 Not Found'\n    app.all('*', (c) => {\n      return c.text(msg, 404)\n    })\n    const res = await app.request('http://localhost/notfound')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(404)\n    expect(log.startsWith('--> GET /notfound \\x1b[33m404\\x1b[0m')).toBe(true)\n    expect(log).toMatch(/m?s$/)\n  })\n\n  it('Log status 511 with empty body', async () => {\n    const res = await app.request('http://localhost/server-error')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(511)\n    expect(log.startsWith('--> GET /server-error \\x1b[31m511\\x1b[0m')).toBe(true)\n    expect(log).toMatch(/m?s$/)\n  })\n\n  it('Log status 100', async () => {\n    const res = await app.request('http://localhost/server-error?status=100')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(100)\n    expect(log.startsWith('--> GET /server-error?status=100 100')).toBe(true)\n    expect(log).toMatch(/m?s$/)\n  })\n\n  it('Log status 700', async () => {\n    const res = await app.request('http://localhost/server-error?status=700')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(700)\n    expect(log.startsWith('--> GET /server-error?status=700 700')).toBe(true)\n    expect(log).toMatch(/m?s$/)\n  })\n})\n\ndescribe('Logger by Middleware in NO_COLOR', () => {\n  let app: Hono\n  let log: string\n\n  beforeEach(() => {\n    vi.stubEnv('NO_COLOR', '1')\n    function sleep(time: number) {\n      return new Promise((resolve) => setTimeout(resolve, time))\n    }\n\n    app = new Hono()\n\n    const logFn = (str: string) => {\n      log = str\n    }\n\n    const shortRandomString = 'hono'\n    const longRandomString = 'hono'.repeat(1000)\n\n    app.use('*', logger(logFn))\n    app.get('/short', (c) => c.text(shortRandomString))\n    app.get('/long', (c) => c.text(longRandomString))\n    app.get('/seconds', async (c) => {\n      await sleep(1000)\n\n      return c.text(longRandomString)\n    })\n    app.get('/empty', (c) => c.text(''))\n  })\n  afterAll(() => {\n    vi.unstubAllEnvs()\n  })\n  it('Log status 200 with empty body', async () => {\n    const res = await app.request('http://localhost/empty')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(log.startsWith('--> GET /empty 200')).toBe(true)\n    expect(log).toMatch(/m?s$/)\n  })\n\n  it('Log status 200 with small body', async () => {\n    const res = await app.request('http://localhost/short')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(log.startsWith('--> GET /short 200')).toBe(true)\n    expect(log).toMatch(/m?s$/)\n  })\n\n  it('Log status 200 with big body', async () => {\n    const res = await app.request('http://localhost/long')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(log.startsWith('--> GET /long 200')).toBe(true)\n    expect(log).toMatch(/m?s$/)\n  })\n\n  it('Time in seconds', async () => {\n    const res = await app.request('http://localhost/seconds')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(log.startsWith('--> GET /seconds 200')).toBe(true)\n    expect(log).toMatch(/1s/)\n  })\n\n  it('Log status 404', async () => {\n    const msg = 'Default 404 Not Found'\n    app.all('*', (c) => {\n      return c.text(msg, 404)\n    })\n    const res = await app.request('http://localhost/notfound')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(404)\n    expect(log.startsWith('--> GET /notfound 404')).toBe(true)\n    expect(log).toMatch(/m?s$/)\n  })\n})\n"
  },
  {
    "path": "src/middleware/logger/index.ts",
    "content": "/**\n * @module\n * Logger Middleware for Hono.\n */\n\nimport type { MiddlewareHandler } from '../../types'\nimport { getColorEnabledAsync } from '../../utils/color'\n\nenum LogPrefix {\n  Outgoing = '-->',\n  Incoming = '<--',\n  Error = 'xxx',\n}\n\nconst humanize = (times: string[]) => {\n  const [delimiter, separator] = [',', '.']\n\n  const orderTimes = times.map((v) => v.replace(/(\\d)(?=(\\d\\d\\d)+(?!\\d))/g, '$1' + delimiter))\n\n  return orderTimes.join(separator)\n}\n\nconst time = (start: number) => {\n  const delta = Date.now() - start\n  return humanize([delta < 1000 ? delta + 'ms' : Math.round(delta / 1000) + 's'])\n}\n\nconst colorStatus = async (status: number) => {\n  const colorEnabled = await getColorEnabledAsync()\n  if (colorEnabled) {\n    switch ((status / 100) | 0) {\n      case 5: // red = error\n        return `\\x1b[31m${status}\\x1b[0m`\n      case 4: // yellow = warning\n        return `\\x1b[33m${status}\\x1b[0m`\n      case 3: // cyan = redirect\n        return `\\x1b[36m${status}\\x1b[0m`\n      case 2: // green = success\n        return `\\x1b[32m${status}\\x1b[0m`\n    }\n  }\n  // Fallback to unsupported status code.\n  // E.g.) Bun and Deno supports new Response with 101, but Node.js does not.\n  // And those may evolve to accept more status.\n  return `${status}`\n}\n\ntype PrintFunc = (str: string, ...rest: string[]) => void\n\nasync function log(\n  fn: PrintFunc,\n  prefix: string,\n  method: string,\n  path: string,\n  status: number = 0,\n  elapsed?: string\n) {\n  const out =\n    prefix === LogPrefix.Incoming\n      ? `${prefix} ${method} ${path}`\n      : `${prefix} ${method} ${path} ${await colorStatus(status)} ${elapsed}`\n  fn(out)\n}\n\n/**\n * Logger Middleware for Hono.\n *\n * @see {@link https://hono.dev/docs/middleware/builtin/logger}\n *\n * @param {PrintFunc} [fn=console.log] - Optional function for customized logging behavior.\n * @returns {MiddlewareHandler} The middleware handler function.\n *\n * @example\n * ```ts\n * const app = new Hono()\n *\n * app.use(logger())\n * app.get('/', (c) => c.text('Hello Hono!'))\n * ```\n */\nexport const logger = (fn: PrintFunc = console.log): MiddlewareHandler => {\n  return async function logger(c, next) {\n    const { method, url } = c.req\n\n    const path = url.slice(url.indexOf('/', 8))\n\n    await log(fn, LogPrefix.Incoming, method, path)\n\n    const start = Date.now()\n\n    await next()\n\n    await log(fn, LogPrefix.Outgoing, method, path, c.res.status, time(start))\n  }\n}\n"
  },
  {
    "path": "src/middleware/method-override/index.test.ts",
    "content": "import { Hono } from '../../hono'\nimport { methodOverride } from './index'\n\ndescribe('Method Override Middleware', () => {\n  describe('Form', () => {\n    const app = new Hono()\n    app.use('/posts/*', methodOverride({ app }))\n    app.use('/posts-custom/*', methodOverride({ app, form: 'custom-input-name' }))\n    app.on(['post', 'delete'], ['/posts', '/posts-custom'], async (c) => {\n      const form = await c.req.formData()\n      return c.json({\n        method: c.req.method,\n        message: form.get('message'),\n        contentType: c.req.header('content-type') ?? '',\n      })\n    })\n\n    describe('multipart/form-data', () => {\n      it('Should override POST to DELETE', async () => {\n        const form = new FormData()\n        form.append('message', 'Hello')\n        form.append('_method', 'DELETE')\n        const res = await app.request('/posts', {\n          body: form,\n          method: 'POST',\n        })\n        expect(res.status).toBe(200)\n        const data = await res.json()\n        expect(data.method).toBe('DELETE')\n        expect(data.message).toBe('Hello')\n        expect(data.contentType).toMatch(/^multipart\\/form-data;/)\n      })\n\n      it('Should override POST to DELETE - with a custom form input name', async () => {\n        const form = new FormData()\n        form.append('message', 'Hello')\n        form.append('custom-input-name', 'DELETE')\n        const res = await app.request('/posts-custom', {\n          body: form,\n          method: 'POST',\n        })\n        expect(res.status).toBe(200)\n        const data = await res.json()\n        expect(data.method).toBe('DELETE')\n        expect(data.message).toBe('Hello')\n        expect(data.contentType).toMatch(/^multipart\\/form-data;/)\n      })\n\n      it('Should override POST to PATCH - not found', async () => {\n        const form = new FormData()\n        form.append('message', 'Hello')\n        form.append('_method', 'PATCH')\n        const res = await app.request('/posts', {\n          body: form,\n          method: 'POST',\n        })\n        expect(res.status).toBe(404)\n      })\n    })\n\n    describe('application/x-www-form-urlencoded', () => {\n      it('Should override POST to DELETE', async () => {\n        const params = new URLSearchParams()\n        params.append('message', 'Hello')\n        params.append('_method', 'DELETE')\n        const res = await app.request('/posts', {\n          body: params,\n          headers: {\n            'Content-Type': 'application/x-www-form-urlencoded',\n          },\n          method: 'POST',\n        })\n        expect(res.status).toBe(200)\n        const data = await res.json()\n        expect(data.method).toBe('DELETE')\n        expect(data.message).toBe('Hello')\n        expect(data.contentType).toBe('application/x-www-form-urlencoded')\n      })\n\n      it('Should override POST to DELETE - with a custom form input name', async () => {\n        const params = new URLSearchParams()\n        params.append('message', 'Hello')\n        params.append('custom-input-name', 'DELETE')\n        const res = await app.request('/posts-custom', {\n          body: params,\n          headers: {\n            'Content-Type': 'application/x-www-form-urlencoded',\n          },\n          method: 'POST',\n        })\n        expect(res.status).toBe(200)\n        const data = await res.json()\n        expect(data.method).toBe('DELETE')\n        expect(data.message).toBe('Hello')\n        expect(data.contentType).toBe('application/x-www-form-urlencoded')\n      })\n\n      it('Should override POST to PATCH - not found', async () => {\n        const form = new FormData()\n        form.append('message', 'Hello')\n        form.append('_method', 'PATCH')\n        const res = await app.request('/posts', {\n          body: form,\n          method: 'POST',\n        })\n        expect(res.status).toBe(404)\n      })\n    })\n  })\n\n  describe('Header', () => {\n    const app = new Hono()\n    app.use('/posts/*', methodOverride({ app, header: 'X-METHOD-OVERRIDE' }))\n    app.on(['get', 'post', 'delete'], '/posts', async (c) => {\n      return c.json({\n        method: c.req.method,\n        headerValue: c.req.header('X-METHOD-OVERRIDE') ?? null,\n      })\n    })\n\n    it('Should override POST to DELETE', async () => {\n      const res = await app.request('/posts', {\n        method: 'POST',\n        headers: {\n          'X-METHOD-OVERRIDE': 'DELETE',\n        },\n      })\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({\n        method: 'DELETE',\n        headerValue: null,\n      })\n    })\n\n    it('Should not override GET request', async () => {\n      const res = await app.request('/posts', {\n        method: 'GET',\n        headers: {\n          'X-METHOD-OVERRIDE': 'DELETE',\n        },\n      })\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({\n        method: 'GET',\n        headerValue: 'DELETE', // It does not modify the headers.\n      })\n    })\n  })\n\n  describe('Query', () => {\n    const app = new Hono()\n    app.use('/posts/*', methodOverride({ app, query: '_method' }))\n    app.on(['get', 'post', 'delete'], '/posts', async (c) => {\n      return c.json({\n        method: c.req.method,\n        queryValue: c.req.query('_method') ?? null,\n      })\n    })\n\n    it('Should override POST to DELETE', async () => {\n      const res = await app.request('/posts?_method=delete', {\n        method: 'POST',\n      })\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({\n        method: 'DELETE',\n        queryValue: null,\n      })\n    })\n\n    it('Should not override GET request', async () => {\n      const res = await app.request('/posts?_method=delete', {\n        method: 'GET',\n      })\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({\n        method: 'GET',\n        queryValue: 'delete', // It does not modify the queries.\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/middleware/method-override/index.ts",
    "content": "/**\n * @module\n * Method Override Middleware for Hono.\n */\n\nimport type { Context, ExecutionContext } from '../../context'\nimport type { Hono } from '../../hono'\nimport type { MiddlewareHandler } from '../../types'\nimport { parseBody } from '../../utils/body'\n\ntype MethodOverrideOptions = {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  app: Hono<any, any, any>\n} & (\n  | {\n      // Default is 'form' and the value is `_method`\n      form?: string\n      header?: never\n      query?: never\n    }\n  | {\n      form?: never\n      header: string\n      query?: never\n    }\n  | {\n      form?: never\n      header?: never\n      query: string\n    }\n)\n\nconst DEFAULT_METHOD_FORM_NAME = '_method'\n\n/**\n * Method Override Middleware for Hono.\n *\n * @see {@link https://hono.dev/docs/middleware/builtin/method-override}\n *\n * @param {MethodOverrideOptions} options - The options for the method override middleware.\n * @param {Hono} options.app - The instance of Hono is used in your application.\n * @param {string} [options.form=_method] - Form key with a value containing the method name.\n * @param {string} [options.header] - Header name with a value containing the method name.\n * @param {string} [options.query] - Query parameter key with a value containing the method name.\n * @returns {MiddlewareHandler} The middleware handler function.\n *\n * @example\n * ```ts\n * const app = new Hono()\n *\n * // If no options are specified, the value of `_method` in the form,\n * // e.g. DELETE, is used as the method.\n * app.use('/posts', methodOverride({ app }))\n *\n * app.delete('/posts', (c) => {\n *   // ....\n * })\n * ```\n */\nexport const methodOverride = (options: MethodOverrideOptions): MiddlewareHandler =>\n  async function methodOverride(c, next) {\n    if (c.req.method === 'GET') {\n      return await next()\n    }\n\n    const app = options.app\n    // Method override by form\n    if (!(options.header || options.query)) {\n      const contentType = c.req.header('content-type')\n      const methodFormName = options.form || DEFAULT_METHOD_FORM_NAME\n      const clonedRequest = c.req.raw.clone()\n      const newRequest = clonedRequest.clone()\n      // Content-Type is `multipart/form-data`\n      if (contentType?.startsWith('multipart/form-data')) {\n        const form = await clonedRequest.formData()\n        const method = form.get(methodFormName)\n        if (method) {\n          const newForm = await newRequest.formData()\n          newForm.delete(methodFormName)\n          const newHeaders = new Headers(clonedRequest.headers)\n          newHeaders.delete('content-type')\n          newHeaders.delete('content-length')\n          const request = new Request(c.req.url, {\n            body: newForm,\n            headers: newHeaders,\n            method: method as string,\n          })\n          return app.fetch(request, c.env, getExecutionCtx(c))\n        }\n      }\n      // Content-Type is `application/x-www-form-urlencoded`\n      if (contentType === 'application/x-www-form-urlencoded') {\n        const params = await parseBody<Record<string, string>>(clonedRequest)\n        const method = params[methodFormName]\n        if (method) {\n          delete params[methodFormName]\n          const newParams = new URLSearchParams(params)\n          const request = new Request(newRequest, {\n            body: newParams,\n            method: method as string,\n          })\n          return app.fetch(request, c.env, getExecutionCtx(c))\n        }\n      }\n    }\n    // Method override by header\n    else if (options.header) {\n      const headerName = options.header\n      const method = c.req.header(headerName)\n      if (method) {\n        const newHeaders = new Headers(c.req.raw.headers)\n        newHeaders.delete(headerName)\n        const request = new Request(c.req.raw, {\n          headers: newHeaders,\n          method,\n        })\n        return app.fetch(request, c.env, getExecutionCtx(c))\n      }\n    }\n    // Method override by query\n    else if (options.query) {\n      const queryName = options.query\n      const method = c.req.query(queryName)\n      if (method) {\n        const url = new URL(c.req.url)\n        url.searchParams.delete(queryName)\n        const request = new Request(url.toString(), {\n          body: c.req.raw.body,\n          headers: c.req.raw.headers,\n          method,\n        })\n        return app.fetch(request, c.env, getExecutionCtx(c))\n      }\n    }\n    await next()\n  }\n\nconst getExecutionCtx = (c: Context) => {\n  let executionCtx: ExecutionContext | undefined\n  try {\n    executionCtx = c.executionCtx\n  } catch {\n    // Do nothing\n  }\n  return executionCtx\n}\n"
  },
  {
    "path": "src/middleware/powered-by/index.test.ts",
    "content": "import { Hono } from '../../hono'\nimport { poweredBy } from '.'\n\ndescribe('Powered by Middleware', () => {\n  const app = new Hono()\n\n  app.use('/poweredBy/*', poweredBy())\n  app.get('/poweredBy', (c) => c.text('root'))\n\n  app.use('/poweredBy2/*', poweredBy())\n  app.use('/poweredBy2/*', poweredBy())\n  app.get('/poweredBy2', (c) => c.text('root'))\n\n  app.use('/poweredBy3/*', poweredBy({ serverName: 'Foo' }))\n  app.get('/poweredBy3', (c) => c.text('root'))\n\n  it('Should return with X-Powered-By header', async () => {\n    const res = await app.request('http://localhost/poweredBy')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('X-Powered-By')).toBe('Hono')\n  })\n\n  it('Should not return duplicate values', async () => {\n    const res = await app.request('http://localhost/poweredBy2')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('X-Powered-By')).toBe('Hono')\n  })\n\n  it('Should return custom serverName', async () => {\n    const res = await app.request('http://localhost/poweredBy3')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('X-Powered-By')).toBe('Foo')\n  })\n})\n"
  },
  {
    "path": "src/middleware/powered-by/index.ts",
    "content": "/**\n * @module\n * Powered By Middleware for Hono.\n */\nimport type { MiddlewareHandler } from '../../types'\n\ntype PoweredByOptions = {\n  /**\n   * The value for X-Powered-By header.\n   * @default Hono\n   */\n  serverName?: string\n}\n\n/**\n * Powered By Middleware for Hono.\n *\n * @param options - The options for the Powered By Middleware.\n * @returns {MiddlewareHandler} The middleware handler function.\n *\n * @example\n * ```ts\n * import { poweredBy } from 'hono/powered-by'\n *\n * const app = new Hono()\n *\n * app.use(poweredBy()) // With options: poweredBy({ serverName: \"My Server\" })\n * ```\n */\nexport const poweredBy = (options?: PoweredByOptions): MiddlewareHandler => {\n  return async function poweredBy(c, next) {\n    await next()\n    c.res.headers.set('X-Powered-By', options?.serverName ?? 'Hono')\n  }\n}\n"
  },
  {
    "path": "src/middleware/pretty-json/index.test.ts",
    "content": "import { Hono } from '../../hono'\nimport { prettyJSON } from '.'\n\ndescribe('JSON pretty by Middleware', () => {\n  it('Should return pretty JSON output', async () => {\n    const app = new Hono()\n    app.use('*', prettyJSON())\n    app.get('/', (c) => {\n      return c.json({ message: 'Hono!' })\n    })\n\n    const res = await app.request('http://localhost/?pretty')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe(`{\n  \"message\": \"Hono!\"\n}`)\n  })\n\n  it('Should return pretty JSON output with 4 spaces', async () => {\n    const app = new Hono()\n    app.use('*', prettyJSON({ space: 4 }))\n    app.get('/', (c) => {\n      return c.json({ message: 'Hono!' })\n    })\n\n    const res = await app.request('http://localhost/?pretty')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toBe(`{\n    \"message\": \"Hono!\"\n}`)\n  })\n\n  it('Should return pretty JSON output when middleware received custom query', async () => {\n    const targetQuery = 'format'\n\n    const app = new Hono()\n    app.use(\n      '*',\n      prettyJSON({\n        query: targetQuery,\n      })\n    )\n    app.get('/', (c) =>\n      c.json({\n        message: 'Hono!',\n      })\n    )\n\n    const prettyText = await (await app.request(`?${targetQuery}`)).text()\n    expect(prettyText).toBe(`{\n  \"message\": \"Hono!\"\n}`)\n    const nonPrettyText = await (await app.request('?pretty')).text()\n    expect(nonPrettyText).toBe('{\"message\":\"Hono!\"}')\n  })\n\n  it('Should force pretty JSON output when force option is true', async () => {\n    const app = new Hono()\n    app.use('*', prettyJSON({ force: true }))\n    app.get('/', (c) => {\n      return c.json({ message: 'Hono!' })\n    })\n\n    const resWithoutQuery = await (await app.request('http://localhost/')).text()\n    expect(resWithoutQuery).toBe(`{\n  \"message\": \"Hono!\"\n}`)\n\n    const resWithQuery = await (await app.request('http://localhost/?pretty')).text()\n    expect(resWithQuery).toBe(`{\n  \"message\": \"Hono!\"\n}`)\n  })\n})\n"
  },
  {
    "path": "src/middleware/pretty-json/index.ts",
    "content": "/**\n * @module\n * Pretty JSON Middleware for Hono.\n */\n\nimport type { MiddlewareHandler } from '../../types'\n\ninterface PrettyOptions {\n  /**\n   * Number of spaces for indentation.\n   * @default 2\n   */\n  space?: number\n\n  /**\n   * Query conditions for when to Pretty.\n   * @default 'pretty'\n   */\n  query?: string\n\n  /**\n   * Force prettification of JSON responses regardless of query parameters.\n   * @default false\n   */\n  force?: boolean\n}\n\n/**\n * Pretty JSON Middleware for Hono.\n *\n * @see {@link https://hono.dev/docs/middleware/builtin/pretty-json}\n *\n * @param options - The options for the pretty JSON middleware.\n * @returns {MiddlewareHandler} The middleware handler function.\n *\n * @example\n * ```ts\n * const app = new Hono()\n *\n * app.use(prettyJSON()) // With options: prettyJSON({ space: 4 })\n * app.get('/', (c) => {\n *   return c.json({ message: 'Hono!' })\n * })\n * ```\n */\nexport const prettyJSON = (options?: PrettyOptions): MiddlewareHandler => {\n  const targetQuery = options?.query ?? 'pretty'\n  return async function prettyJSON(c, next) {\n    const pretty = options?.force || c.req.query(targetQuery) || c.req.query(targetQuery) === ''\n    await next()\n    if (pretty && c.res.headers.get('Content-Type')?.startsWith('application/json')) {\n      const obj = await c.res.json()\n      c.res = new Response(JSON.stringify(obj, null, options?.space ?? 2), c.res)\n    }\n  }\n}\n"
  },
  {
    "path": "src/middleware/request-id/index.test.ts",
    "content": "import type { Context } from '../../context'\nimport { Hono } from '../../hono'\nimport { requestId } from '.'\n\nconst regexUUIDv4 = /([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})/\n\ndescribe('Request ID Middleware', () => {\n  const app = new Hono()\n  app.use('*', requestId())\n  app.get('/requestId', (c) => c.text(c.get('requestId') ?? 'No Request ID'))\n\n  it('Should return random request id', async () => {\n    const res = await app.request('http://localhost/requestId')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('X-Request-Id')).toMatch(regexUUIDv4)\n    expect(await res.text()).match(regexUUIDv4)\n  })\n\n  it('Should return custom request id', async () => {\n    const res = await app.request('http://localhost/requestId', {\n      headers: {\n        'X-Request-Id': 'hono-is-hot',\n      },\n    })\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('X-Request-Id')).toBe('hono-is-hot')\n    expect(await res.text()).toBe('hono-is-hot')\n  })\n\n  it('Should return custom request id with all valid characters', async () => {\n    const validRequestId = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_='\n    const res = await app.request('http://localhost/requestId', {\n      headers: {\n        'X-Request-Id': validRequestId,\n      },\n    })\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('X-Request-Id')).toBe(validRequestId)\n    expect(await res.text()).toBe(validRequestId)\n  })\n\n  it('Should return random request id without using request header', async () => {\n    const res = await app.request('http://localhost/requestId', {\n      headers: {\n        'X-Request-Id': 'Hello!12345-@*^',\n      },\n    })\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('X-Request-Id')).toMatch(regexUUIDv4)\n    expect(await res.text()).toMatch(regexUUIDv4)\n  })\n})\n\ndescribe('Request ID Middleware with custom generator', () => {\n  function generateWord() {\n    return 'HonoIsWebFramework'\n  }\n  function generateDoubleRequestId(c: Context) {\n    const honoId = c.req.header('Hono-Request-Id')\n    const ohnoId = c.req.header('Ohno-Request-Id')\n    if (honoId && ohnoId) {\n      return honoId + ohnoId\n    }\n    return crypto.randomUUID()\n  }\n  const app = new Hono()\n  app.use('/word', requestId({ generator: generateWord }))\n  app.use('/doubleRequestId', requestId({ generator: generateDoubleRequestId }))\n  app.get('/word', (c) => c.text(c.get('requestId') ?? 'No Request ID'))\n  app.get('/doubleRequestId', (c) => c.text(c.get('requestId') ?? 'No Request ID'))\n  it('Should return custom request id', async () => {\n    const res = await app.request('http://localhost/word')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('X-Request-Id')).toBe('HonoIsWebFramework')\n    expect(await res.text()).toBe('HonoIsWebFramework')\n  })\n\n  it('Should return complex request id', async () => {\n    const res = await app.request('http://localhost/doubleRequestId', {\n      headers: {\n        'Hono-Request-Id': 'Hello',\n        'Ohno-Request-Id': 'World',\n      },\n    })\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('X-Request-Id')).toBe('HelloWorld')\n    expect(await res.text()).toBe('HelloWorld')\n  })\n})\n\ndescribe('Request ID Middleware with limit length', () => {\n  const charactersOf255 = 'h'.repeat(255)\n  const charactersOf256 = 'h'.repeat(256)\n\n  const app = new Hono()\n  app.use('/requestId', requestId())\n  app.use('/limit256', requestId({ limitLength: 256 }))\n  app.get('/requestId', (c) => c.text(c.get('requestId') ?? 'No Request ID'))\n  app.get('/limit256', (c) => c.text(c.get('requestId') ?? 'No Request ID'))\n\n  it('Should return custom request id', async () => {\n    const res = await app.request('http://localhost/requestId', {\n      headers: {\n        'X-Request-Id': charactersOf255,\n      },\n    })\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('X-Request-Id')).toBe(charactersOf255)\n    expect(await res.text()).toBe(charactersOf255)\n  })\n  it('Should return random request id without using request header', async () => {\n    const res = await app.request('http://localhost/requestId', {\n      headers: {\n        'X-Request-Id': charactersOf256,\n      },\n    })\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('X-Request-Id')).toMatch(regexUUIDv4)\n    expect(await res.text()).toMatch(regexUUIDv4)\n  })\n  it('Should return custom request id with 256 characters', async () => {\n    const res = await app.request('http://localhost/limit256', {\n      headers: {\n        'X-Request-Id': charactersOf256,\n      },\n    })\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('X-Request-Id')).toBe(charactersOf256)\n    expect(await res.text()).toBe(charactersOf256)\n  })\n})\n\ndescribe('Request ID Middleware with custom header', () => {\n  const app = new Hono()\n  app.use('/requestId', requestId({ headerName: 'Hono-Request-Id' }))\n  app.get('/emptyId', requestId({ headerName: '' }))\n  app.get('/requestId', (c) => c.text(c.get('requestId') ?? 'No Request ID'))\n  app.get('/emptyId', (c) => c.text(c.get('requestId') ?? 'No Request ID'))\n\n  it('Should return custom request id', async () => {\n    const res = await app.request('http://localhost/requestId', {\n      headers: {\n        'Hono-Request-Id': 'hono-is-hot',\n      },\n    })\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Hono-Request-Id')).toBe('hono-is-hot')\n    expect(await res.text()).toBe('hono-is-hot')\n  })\n\n  it('Should not return request id', async () => {\n    const res = await app.request('http://localhost/emptyId')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('X-Request-Id')).toBeNull()\n    expect(await res.text()).toMatch(regexUUIDv4)\n  })\n})\n"
  },
  {
    "path": "src/middleware/request-id/index.ts",
    "content": "import type { RequestIdVariables } from './request-id'\nexport type { RequestIdVariables }\nexport { requestId } from './request-id'\n\ndeclare module '../..' {\n  interface ContextVariableMap extends RequestIdVariables {}\n}\n"
  },
  {
    "path": "src/middleware/request-id/request-id.ts",
    "content": "/**\n * @module\n * Request ID Middleware for Hono.\n */\n\nimport type { Context } from '../../context'\nimport type { MiddlewareHandler } from '../../types'\n\nexport type RequestIdVariables = {\n  requestId: string\n}\n\nexport type RequestIdOptions = {\n  limitLength?: number\n  headerName?: string\n  generator?: (c: Context) => string\n}\n\n/**\n * Request ID Middleware for Hono.\n *\n * @param {object} options - Options for Request ID middleware.\n * @param {number} [options.limitLength=255] - The maximum length of request id.\n * @param {string} [options.headerName=X-Request-Id] - The header name used in request id.\n * @param {generator} [options.generator=() => crypto.randomUUID()] - The request id generation function.\n *\n * @returns {MiddlewareHandler} The middleware handler function.\n *\n * @example\n * ```ts\n * type Variables = RequestIdVariables\n * const app = new Hono<{Variables: Variables}>()\n *\n * app.use(requestId())\n * app.get('/', (c) => {\n *   console.log(c.get('requestId')) // Debug\n *   return c.text('Hello World!')\n * })\n * ```\n */\nexport const requestId = ({\n  limitLength = 255,\n  headerName = 'X-Request-Id',\n  generator = () => crypto.randomUUID(),\n}: RequestIdOptions = {}): MiddlewareHandler => {\n  return async function requestId(c, next) {\n    // If `headerName` is empty string, req.header will return the object\n    let reqId = headerName ? c.req.header(headerName) : undefined\n    if (!reqId || reqId.length > limitLength || /[^\\w\\-=]/.test(reqId)) {\n      reqId = generator(c)\n    }\n\n    c.set('requestId', reqId)\n    if (headerName) {\n      c.header(headerName, reqId)\n    }\n    await next()\n  }\n}\n"
  },
  {
    "path": "src/middleware/secure-headers/index.test.ts",
    "content": "import { Hono } from '../../hono'\nimport { poweredBy } from '../powered-by'\nimport { NONCE, secureHeaders } from '.'\nimport type { ContentSecurityPolicyOptionHandler } from '.'\n\ndeclare module '../..' {\n  interface ContextVariableMap {\n    ['test-scriptSrc-nonce']?: string\n    ['test-styleSrc-nonce']?: string\n  }\n}\n\ndescribe('Secure Headers Middleware', () => {\n  it('default middleware', async () => {\n    const app = new Hono()\n    app.use('*', secureHeaders())\n    app.get('/test', async (ctx) => {\n      return ctx.text('test')\n    })\n\n    const res = await app.request('/test')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('X-Frame-Options')).toEqual('SAMEORIGIN')\n    expect(res.headers.get('Strict-Transport-Security')).toEqual(\n      'max-age=15552000; includeSubDomains'\n    )\n    expect(res.headers.get('X-Download-Options')).toEqual('noopen')\n    expect(res.headers.get('X-XSS-Protection')).toEqual('0')\n    expect(res.headers.get('X-Powered-By')).toBeNull()\n    expect(res.headers.get('X-DNS-Prefetch-Control')).toEqual('off')\n    expect(res.headers.get('X-Content-Type-Options')).toEqual('nosniff')\n    expect(res.headers.get('Referrer-Policy')).toEqual('no-referrer')\n    expect(res.headers.get('X-Permitted-Cross-Domain-Policies')).toEqual('none')\n    expect(res.headers.get('Cross-Origin-Resource-Policy')).toEqual('same-origin')\n    expect(res.headers.get('Cross-Origin-Opener-Policy')).toEqual('same-origin')\n    expect(res.headers.get('Origin-Agent-Cluster')).toEqual('?1')\n    expect(res.headers.get('Permissions-Policy')).toBeNull()\n    expect(res.headers.get('Content-Security-Policy')).toBeFalsy()\n    expect(res.headers.get('Content-Security-Policy-ReportOnly')).toBeFalsy()\n  })\n\n  it('all headers enabled', async () => {\n    const app = new Hono()\n    app.use(\n      '*',\n      secureHeaders({\n        contentSecurityPolicy: {\n          defaultSrc: [\"'self'\"],\n        },\n        contentSecurityPolicyReportOnly: {\n          defaultSrc: [\"'self'\"],\n        },\n        crossOriginEmbedderPolicy: true,\n        permissionsPolicy: {\n          camera: [],\n        },\n      })\n    )\n    app.get('/test', async (ctx) => {\n      return ctx.text('test')\n    })\n\n    const res = await app.request('/test')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('X-Frame-Options')).toEqual('SAMEORIGIN')\n    expect(res.headers.get('Strict-Transport-Security')).toEqual(\n      'max-age=15552000; includeSubDomains'\n    )\n    expect(res.headers.get('X-Download-Options')).toEqual('noopen')\n    expect(res.headers.get('X-XSS-Protection')).toEqual('0')\n    expect(res.headers.get('X-Powered-By')).toBeNull()\n    expect(res.headers.get('X-DNS-Prefetch-Control')).toEqual('off')\n    expect(res.headers.get('X-Content-Type-Options')).toEqual('nosniff')\n    expect(res.headers.get('Referrer-Policy')).toEqual('no-referrer')\n    expect(res.headers.get('X-Permitted-Cross-Domain-Policies')).toEqual('none')\n    expect(res.headers.get('Cross-Origin-Resource-Policy')).toEqual('same-origin')\n    expect(res.headers.get('Cross-Origin-Opener-Policy')).toEqual('same-origin')\n    expect(res.headers.get('Origin-Agent-Cluster')).toEqual('?1')\n    expect(res.headers.get('Cross-Origin-Embedder-Policy')).toEqual('require-corp')\n    expect(res.headers.get('Permissions-Policy')).toEqual('camera=()')\n    expect(res.headers.get('Content-Security-Policy')).toEqual(\"default-src 'self'\")\n    expect(res.headers.get('Content-Security-Policy-Report-Only')).toEqual(\"default-src 'self'\")\n  })\n\n  it('specific headers disabled', async () => {\n    const app = new Hono()\n    app.use('*', secureHeaders({ xFrameOptions: false, xXssProtection: false }))\n    app.get('/test', async (ctx) => {\n      return ctx.text('test')\n    })\n\n    const res = await app.request('/test')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(res.headers.get('X-Frame-Options')).toBeNull()\n    expect(res.headers.get('Strict-Transport-Security')).toEqual(\n      'max-age=15552000; includeSubDomains'\n    )\n    expect(res.headers.get('X-Download-Options')).toEqual('noopen')\n    expect(res.headers.get('X-XSS-Protection')).toBeNull()\n    expect(res.headers.get('X-Powered-By')).toBeNull()\n    expect(res.headers.get('X-DNS-Prefetch-Control')).toEqual('off')\n    expect(res.headers.get('X-Content-Type-Options')).toEqual('nosniff')\n    expect(res.headers.get('Referrer-Policy')).toEqual('no-referrer')\n    expect(res.headers.get('X-Permitted-Cross-Domain-Policies')).toEqual('none')\n    expect(res.headers.get('Cross-Origin-Resource-Policy')).toEqual('same-origin')\n    expect(res.headers.get('Cross-Origin-Opener-Policy')).toEqual('same-origin')\n    expect(res.headers.get('Permissions-Policy')).toBeNull()\n    expect(res.headers.get('Origin-Agent-Cluster')).toEqual('?1')\n  })\n\n  it('should remove x-powered-by header', async () => {\n    const appBefore = new Hono()\n    appBefore.use('*', secureHeaders())\n    appBefore.use('*', poweredBy())\n\n    const resBefore = await appBefore.request('/')\n    expect(resBefore.headers.get('x-powered-by')).toBeFalsy()\n\n    const appAfter = new Hono()\n    appAfter.use('*', poweredBy())\n    appAfter.use('*', secureHeaders())\n\n    const resAfter = await appAfter.request('/')\n    expect(resAfter.headers.get('x-powered-by')).toBe('Hono')\n  })\n\n  it('should override Strict-Transport-Security header after middleware', async () => {\n    const app = new Hono()\n    app.use('/test1', secureHeaders())\n\n    app.all('*', async (c) => {\n      c.res.headers.set('Strict-Transport-Security', 'Hono')\n      return c.text('header updated')\n    })\n\n    const res1 = await app.request('/test1')\n    expect(res1.headers.get('Strict-Transport-Security')).toEqual(\n      'max-age=15552000; includeSubDomains'\n    )\n\n    const res2 = await app.request('/test2')\n    expect(res2.headers.get('Strict-Transport-Security')).toEqual('Hono')\n  })\n\n  it('should use custom value when overridden', async () => {\n    const app = new Hono()\n    app.use(\n      '/test',\n      secureHeaders({\n        strictTransportSecurity: 'max-age=31536000; includeSubDomains; preload;',\n        xFrameOptions: 'DENY',\n        xXssProtection: '1',\n      })\n    )\n\n    const res = await app.request('/test')\n    expect(res.headers.get('Strict-Transport-Security')).toEqual(\n      'max-age=31536000; includeSubDomains; preload;'\n    )\n    expect(res.headers.get('X-FRAME-OPTIONS')).toEqual('DENY')\n    expect(res.headers.get('X-XSS-Protection')).toEqual('1')\n  })\n\n  it('should set Permission-Policy header correctly', async () => {\n    const app = new Hono()\n    app.use(\n      '/test',\n      secureHeaders({\n        permissionsPolicy: {\n          fullscreen: ['self'],\n          bluetooth: ['none'],\n          payment: ['self', 'example.com'],\n          syncXhr: [],\n          camera: false,\n          microphone: true,\n          geolocation: ['*'],\n          usb: ['self', 'https://a.example.com', 'https://b.example.com'],\n          accelerometer: ['https://*.example.com'],\n          gyroscope: ['src'],\n          magnetometer: ['https://a.example.com', 'https://b.example.com'],\n        },\n      })\n    )\n\n    const res = await app.request('/test')\n    expect(res.headers.get('Permissions-Policy')).toEqual(\n      'fullscreen=(self), bluetooth=none, payment=(self \"example.com\"), sync-xhr=(), camera=none, microphone=*, ' +\n        'geolocation=*, usb=(self \"https://a.example.com\" \"https://b.example.com\"), ' +\n        'accelerometer=(\"https://*.example.com\"), gyroscope=(src), ' +\n        'magnetometer=(\"https://a.example.com\" \"https://b.example.com\")'\n    )\n  })\n\n  it('Remove X-Powered-By', async () => {\n    const app = new Hono()\n\n    app.get('/test', secureHeaders(), poweredBy(), async (c) => {\n      return c.text('Hono is hot')\n    })\n\n    app.get(\n      '/test2',\n      secureHeaders({\n        removePoweredBy: false,\n      }),\n      poweredBy(),\n      async (c) => {\n        return c.text('Hono is hot')\n      }\n    )\n\n    const res = await app.request('/test')\n    const poweredby = res.headers.get('X-Powered-By')\n    expect(poweredby).toEqual(null)\n    expect(await res.text()).toEqual('Hono is hot')\n\n    const res2 = await app.request('/test2')\n    const poweredby2 = res2.headers.get('X-Powered-By')\n    expect(poweredby2).toEqual('Hono')\n    expect(await res2.text()).toEqual('Hono is hot')\n  })\n\n  describe.each([\n    { cspSettingName: 'contentSecurityPolicy', cspHeaderName: 'Content-Security-Policy' },\n    {\n      cspSettingName: 'contentSecurityPolicyReportOnly',\n      cspHeaderName: 'Content-Security-Policy-Report-Only',\n    },\n  ])('CSP Setting ($cspSettingName)', ({ cspSettingName, cspHeaderName }) => {\n    it('CSP Setting', async () => {\n      const app = new Hono()\n      app.use(\n        '/test',\n        secureHeaders({\n          [cspSettingName]: {\n            defaultSrc: [\"'self'\"],\n            baseUri: [\"'self'\"],\n            fontSrc: [\"'self'\", 'https:', 'data:'],\n            frameAncestors: [\"'self'\"],\n            imgSrc: [\"'self'\", 'data:'],\n            objectSrc: [\"'none'\"],\n            scriptSrc: [\"'self'\"],\n            scriptSrcAttr: [\"'none'\"],\n            styleSrc: [\"'self'\", 'https:', \"'unsafe-inline'\"],\n            requireTrustedTypesFor: [\"'script'\"],\n            trustedTypes: [\"'none'\"],\n          },\n        })\n      )\n\n      app.all('*', async (c) => {\n        c.res.headers.set('Strict-Transport-Security', 'Hono')\n        return c.text('header updated')\n      })\n\n      const res = await app.request('/test')\n      expect(res.headers.get(cspHeaderName)).toEqual(\n        \"default-src 'self'; base-uri 'self'; font-src 'self' https: data:; frame-ancestors 'self'; img-src 'self' data:; object-src 'none'; script-src 'self'; script-src-attr 'none'; style-src 'self' https: 'unsafe-inline'; require-trusted-types-for 'script'; trusted-types 'none'\"\n      )\n    })\n\n    it('CSP Setting one only', async () => {\n      const app = new Hono()\n      app.use(\n        '/test',\n        secureHeaders({\n          [cspSettingName]: {\n            defaultSrc: [\"'self'\"],\n          },\n        })\n      )\n\n      app.all('*', async (c) => {\n        return c.text('header updated')\n      })\n\n      const res = await app.request('/test')\n      expect(res.headers.get(cspHeaderName)).toEqual(\"default-src 'self'\")\n    })\n\n    it('No CSP Setting', async () => {\n      const app = new Hono()\n      app.use('/test', secureHeaders({ [cspSettingName]: {} }))\n\n      app.all('*', async (c) => {\n        return c.text('header updated')\n      })\n\n      const res = await app.request('/test')\n      expect(res.headers.get(cspHeaderName)).toEqual('')\n    })\n\n    it('CSP with reportTo', async () => {\n      const app = new Hono()\n      app.use(\n        '/test1',\n        secureHeaders({\n          reportingEndpoints: [\n            {\n              name: 'endpoint-1',\n              url: 'https://example.com/reports',\n            },\n          ],\n          [cspSettingName]: {\n            defaultSrc: [\"'self'\"],\n            reportTo: 'endpoint-1',\n          },\n        })\n      )\n\n      app.use(\n        '/test2',\n        secureHeaders({\n          reportTo: [\n            {\n              group: 'endpoint-1',\n              max_age: 10886400,\n              endpoints: [{ url: 'https://example.com/reports' }],\n            },\n          ],\n          [cspSettingName]: {\n            defaultSrc: [\"'self'\"],\n            reportTo: 'endpoint-1',\n          },\n        })\n      )\n\n      app.use(\n        '/test3',\n        secureHeaders({\n          reportTo: [\n            {\n              group: 'g1',\n              max_age: 10886400,\n              endpoints: [\n                { url: 'https://a.example.com/reports' },\n                { url: 'https://b.example.com/reports' },\n              ],\n            },\n            {\n              group: 'g2',\n              max_age: 10886400,\n              endpoints: [\n                { url: 'https://c.example.com/reports' },\n                { url: 'https://d.example.com/reports' },\n              ],\n            },\n          ],\n          [cspSettingName]: {\n            defaultSrc: [\"'self'\"],\n            reportTo: 'g2',\n          },\n        })\n      )\n\n      app.use(\n        '/test4',\n        secureHeaders({\n          reportingEndpoints: [\n            {\n              name: 'e1',\n              url: 'https://a.example.com/reports',\n            },\n            {\n              name: 'e2',\n              url: 'https://b.example.com/reports',\n            },\n          ],\n          [cspSettingName]: {\n            defaultSrc: [\"'self'\"],\n            reportTo: 'e1',\n          },\n        })\n      )\n\n      app.all('*', async (c) => {\n        return c.text('header updated')\n      })\n\n      const res1 = await app.request('/test1')\n      expect(res1.headers.get('Reporting-Endpoints')).toEqual(\n        'endpoint-1=\"https://example.com/reports\"'\n      )\n      expect(res1.headers.get(cspHeaderName)).toEqual(\"default-src 'self'; report-to endpoint-1\")\n\n      const res2 = await app.request('/test2')\n      expect(res2.headers.get('Report-To')).toEqual(\n        '{\"group\":\"endpoint-1\",\"max_age\":10886400,\"endpoints\":[{\"url\":\"https://example.com/reports\"}]}'\n      )\n      expect(res2.headers.get(cspHeaderName)).toEqual(\"default-src 'self'; report-to endpoint-1\")\n\n      const res3 = await app.request('/test3')\n      expect(res3.headers.get('Report-To')).toEqual(\n        '{\"group\":\"g1\",\"max_age\":10886400,\"endpoints\":[{\"url\":\"https://a.example.com/reports\"},{\"url\":\"https://b.example.com/reports\"}]}, {\"group\":\"g2\",\"max_age\":10886400,\"endpoints\":[{\"url\":\"https://c.example.com/reports\"},{\"url\":\"https://d.example.com/reports\"}]}'\n      )\n      expect(res3.headers.get(cspHeaderName)).toEqual(\"default-src 'self'; report-to g2\")\n\n      const res4 = await app.request('/test4')\n      expect(res4.headers.get('Reporting-Endpoints')).toEqual(\n        'e1=\"https://a.example.com/reports\", e2=\"https://b.example.com/reports\"'\n      )\n      expect(res4.headers.get(cspHeaderName)).toEqual(\"default-src 'self'; report-to e1\")\n    })\n\n    it('CSP nonce for script-src', async () => {\n      const app = new Hono()\n      app.use(\n        '/test',\n        secureHeaders({\n          [cspSettingName]: {\n            scriptSrc: [\"'self'\", NONCE],\n          },\n        })\n      )\n\n      app.all('*', async (c) => {\n        return c.text(`nonce: ${c.get('secureHeadersNonce')}`)\n      })\n\n      const res = await app.request('/test')\n      const csp = res.headers.get(cspHeaderName)\n      const nonce = csp?.match(/script-src 'self' 'nonce-([a-zA-Z0-9+/]+=*)'/)?.[1] || ''\n      expect(csp).toMatch(`script-src 'self' 'nonce-${nonce}'`)\n      expect(await res.text()).toEqual(`nonce: ${nonce}`)\n    })\n\n    it('CSP nonce for script-src and style-src', async () => {\n      const app = new Hono()\n      app.use(\n        '/test',\n        secureHeaders({\n          [cspSettingName]: {\n            scriptSrc: [\"'self'\", NONCE],\n            styleSrc: [\"'self'\", NONCE],\n          },\n        })\n      )\n\n      app.all('*', async (c) => {\n        return c.text(`nonce: ${c.get('secureHeadersNonce')}`)\n      })\n\n      const res = await app.request('/test')\n      const csp = res.headers.get(cspHeaderName)\n      const nonce = csp?.match(/script-src 'self' 'nonce-([a-zA-Z0-9+/]+=*)'/)?.[1] || ''\n      expect(csp).toMatch(`script-src 'self' 'nonce-${nonce}'`)\n      expect(csp).toMatch(`style-src 'self' 'nonce-${nonce}'`)\n      expect(await res.text()).toEqual(`nonce: ${nonce}`)\n    })\n\n    it('CSP nonce by app own function', async () => {\n      const app = new Hono()\n      const setNonce: ContentSecurityPolicyOptionHandler = (ctx, directive) => {\n        ctx.set(`test-${directive}-nonce`, directive)\n        return `'nonce-${directive}'`\n      }\n      app.use(\n        '/test',\n        secureHeaders({\n          [cspSettingName]: {\n            scriptSrc: [\"'self'\", setNonce],\n            styleSrc: [\"'self'\", setNonce],\n          },\n        })\n      )\n\n      app.all('*', async (c) => {\n        return c.text(\n          `script: ${c.get('test-scriptSrc-nonce')}, style: ${c.get('test-styleSrc-nonce')}`\n        )\n      })\n\n      const res = await app.request('/test')\n      const csp = res.headers.get(cspHeaderName)\n      expect(csp).toMatch(\"script-src 'self' 'nonce-scriptSrc'\")\n      expect(csp).toMatch(\"style-src 'self' 'nonce-styleSrc'\")\n      expect(await res.text()).toEqual('script: scriptSrc, style: styleSrc')\n    })\n  })\n\n  // OUR NEW REPORT-URI TESTS\n  describe('CSP report-uri directive', () => {\n    it('should set report-uri with single endpoint', async () => {\n      const app = new Hono()\n      app.use(\n        '/test',\n        secureHeaders({\n          contentSecurityPolicy: {\n            defaultSrc: [\"'self'\"],\n            reportUri: '/csp-report',\n          },\n        })\n      )\n\n      const res = await app.request('/test')\n      expect(res.headers.get('Content-Security-Policy')).toEqual(\n        \"default-src 'self'; report-uri /csp-report\"\n      )\n    })\n\n    it('should set report-uri with multiple endpoints', async () => {\n      const app = new Hono()\n      app.use(\n        '/test',\n        secureHeaders({\n          contentSecurityPolicy: {\n            defaultSrc: [\"'self'\"],\n            reportUri: ['/endpoint1', '/endpoint2'],\n          },\n        })\n      )\n\n      const res = await app.request('/test')\n      expect(res.headers.get('Content-Security-Policy')).toEqual(\n        \"default-src 'self'; report-uri /endpoint1 /endpoint2\"\n      )\n    })\n\n    it('should work with report-to and report-uri together', async () => {\n      const app = new Hono()\n      app.use(\n        '/test',\n        secureHeaders({\n          contentSecurityPolicy: {\n            defaultSrc: [\"'self'\"],\n            reportTo: 'endpoint-1',\n            reportUri: '/legacy-report',\n          },\n        })\n      )\n\n      const res = await app.request('/test')\n      expect(res.headers.get('Content-Security-Policy')).toEqual(\n        \"default-src 'self'; report-to endpoint-1; report-uri /legacy-report\"\n      )\n    })\n\n    it('should work with Content-Security-Policy-Report-Only', async () => {\n      const app = new Hono()\n      app.use(\n        '/test',\n        secureHeaders({\n          contentSecurityPolicyReportOnly: {\n            defaultSrc: [\"'self'\"],\n            reportUri: '/csp-report-only',\n          },\n        })\n      )\n\n      const res = await app.request('/test')\n      expect(res.headers.get('Content-Security-Policy-Report-Only')).toEqual(\n        \"default-src 'self'; report-uri /csp-report-only\"\n      )\n    })\n\n    // ADDED MISSING TEST CASE for Report-Only:\n\n    it('should not output report-uri when reportUri is not set', async () => {\n      const app = new Hono()\n      app.use(\n        '/test',\n        secureHeaders({\n          contentSecurityPolicy: {\n            defaultSrc: [\"'self'\"],\n          },\n        })\n      )\n\n      const res = await app.request('/test')\n      const header = res.headers.get('Content-Security-Policy') || ''\n      expect(header.includes('report-uri')).toBe(false)\n    })\n\n    it('should not output report-uri in Report-Only when reportUri is not set', async () => {\n      const app = new Hono()\n      app.use(\n        '/test',\n        secureHeaders({\n          contentSecurityPolicyReportOnly: {\n            defaultSrc: [\"'self'\"],\n          },\n        })\n      )\n\n      const res = await app.request('/test')\n      const header = res.headers.get('Content-Security-Policy-Report-Only') || ''\n      expect(header.includes('report-uri')).toBe(false)\n    })\n  })\n})\n"
  },
  {
    "path": "src/middleware/secure-headers/index.ts",
    "content": "export type { ContentSecurityPolicyOptionHandler } from './secure-headers'\nexport { NONCE, secureHeaders } from './secure-headers'\nimport type { SecureHeadersVariables } from './secure-headers'\nexport type { SecureHeadersVariables }\n\ndeclare module '../..' {\n  interface ContextVariableMap extends SecureHeadersVariables {}\n}\n"
  },
  {
    "path": "src/middleware/secure-headers/permissions-policy.ts",
    "content": "// https://github.com/w3c/webappsec-permissions-policy/blob/main/features.md\n\nexport type PermissionsPolicyDirective =\n  | StandardizedFeatures\n  | ProposedFeatures\n  | ExperimentalFeatures\n\n/**\n * These features have been declared in a published version of the respective specification.\n */\ntype StandardizedFeatures =\n  | 'accelerometer'\n  | 'ambientLightSensor'\n  | 'attributionReporting'\n  | 'autoplay'\n  | 'battery'\n  | 'bluetooth'\n  | 'camera'\n  | 'chUa'\n  | 'chUaArch'\n  | 'chUaBitness'\n  | 'chUaFullVersion'\n  | 'chUaFullVersionList'\n  | 'chUaMobile'\n  | 'chUaModel'\n  | 'chUaPlatform'\n  | 'chUaPlatformVersion'\n  | 'chUaWow64'\n  | 'computePressure'\n  | 'crossOriginIsolated'\n  | 'directSockets'\n  | 'displayCapture'\n  | 'encryptedMedia'\n  | 'executionWhileNotRendered'\n  | 'executionWhileOutOfViewport'\n  | 'fullscreen'\n  | 'geolocation'\n  | 'gyroscope'\n  | 'hid'\n  | 'identityCredentialsGet'\n  | 'idleDetection'\n  | 'keyboardMap'\n  | 'magnetometer'\n  | 'microphone'\n  | 'midi'\n  | 'navigationOverride'\n  | 'payment'\n  | 'pictureInPicture'\n  | 'publickeyCredentialsGet'\n  | 'screenWakeLock'\n  | 'serial'\n  | 'storageAccess'\n  | 'syncXhr'\n  | 'usb'\n  | 'webShare'\n  | 'windowManagement'\n  | 'xrSpatialTracking'\n\n/**\n * These features have been proposed, but the definitions have not yet been integrated into their respective specs.\n */\ntype ProposedFeatures =\n  | 'clipboardRead'\n  | 'clipboardWrite'\n  | 'gamepad'\n  | 'sharedAutofill'\n  | 'speakerSelection'\n\n/**\n * These features generally have an explainer only, but may be available for experimentation by web developers.\n */\ntype ExperimentalFeatures =\n  | 'allScreensCapture'\n  | 'browsingTopics'\n  | 'capturedSurfaceControl'\n  | 'conversionMeasurement'\n  | 'digitalCredentialsGet'\n  | 'focusWithoutUserActivation'\n  | 'joinAdInterestGroup'\n  | 'localFonts'\n  | 'runAdAuction'\n  | 'smartCard'\n  | 'syncScript'\n  | 'trustTokenRedemption'\n  | 'unload'\n  | 'verticalScroll'\n"
  },
  {
    "path": "src/middleware/secure-headers/secure-headers.ts",
    "content": "/**\n * @module\n * Secure Headers Middleware for Hono.\n */\n\nimport type { Context } from '../../context'\nimport type { MiddlewareHandler } from '../../types'\nimport { encodeBase64 } from '../../utils/encode'\nimport type { PermissionsPolicyDirective } from './permissions-policy'\n\nexport type SecureHeadersVariables = {\n  secureHeadersNonce?: string\n}\n\nexport type ContentSecurityPolicyOptionHandler = (ctx: Context, directive: string) => string\ntype ContentSecurityPolicyOptionValue = (string | ContentSecurityPolicyOptionHandler)[]\n\ninterface ContentSecurityPolicyOptions {\n  defaultSrc?: ContentSecurityPolicyOptionValue\n  baseUri?: ContentSecurityPolicyOptionValue\n  childSrc?: ContentSecurityPolicyOptionValue\n  connectSrc?: ContentSecurityPolicyOptionValue\n  fontSrc?: ContentSecurityPolicyOptionValue\n  formAction?: ContentSecurityPolicyOptionValue\n  frameAncestors?: ContentSecurityPolicyOptionValue\n  frameSrc?: ContentSecurityPolicyOptionValue\n  imgSrc?: ContentSecurityPolicyOptionValue\n  manifestSrc?: ContentSecurityPolicyOptionValue\n  mediaSrc?: ContentSecurityPolicyOptionValue\n  objectSrc?: ContentSecurityPolicyOptionValue\n  reportTo?: string\n  reportUri?: string | string[]\n  sandbox?: ContentSecurityPolicyOptionValue\n  scriptSrc?: ContentSecurityPolicyOptionValue\n  scriptSrcAttr?: ContentSecurityPolicyOptionValue\n  scriptSrcElem?: ContentSecurityPolicyOptionValue\n  styleSrc?: ContentSecurityPolicyOptionValue\n  styleSrcAttr?: ContentSecurityPolicyOptionValue\n  styleSrcElem?: ContentSecurityPolicyOptionValue\n  upgradeInsecureRequests?: ContentSecurityPolicyOptionValue\n  workerSrc?: ContentSecurityPolicyOptionValue\n  requireTrustedTypesFor?: ContentSecurityPolicyOptionValue\n  trustedTypes?: ContentSecurityPolicyOptionValue\n}\n\ninterface ReportToOptions {\n  group: string\n  max_age: number\n  endpoints: ReportToEndpoint[]\n}\n\ninterface ReportToEndpoint {\n  url: string\n}\n\ninterface ReportingEndpointOptions {\n  name: string\n  url: string\n}\n\ntype PermissionsPolicyValue = '*' | 'self' | 'src' | 'none' | string\n\ntype PermissionsPolicyOptions = Partial<\n  Record<PermissionsPolicyDirective, PermissionsPolicyValue[] | boolean>\n>\n\ntype overridableHeader = boolean | string\n\ninterface SecureHeadersOptions {\n  contentSecurityPolicy?: ContentSecurityPolicyOptions\n  contentSecurityPolicyReportOnly?: ContentSecurityPolicyOptions\n  crossOriginEmbedderPolicy?: overridableHeader\n  crossOriginResourcePolicy?: overridableHeader\n  crossOriginOpenerPolicy?: overridableHeader\n  originAgentCluster?: overridableHeader\n  referrerPolicy?: overridableHeader\n  reportingEndpoints?: ReportingEndpointOptions[]\n  reportTo?: ReportToOptions[]\n  strictTransportSecurity?: overridableHeader\n  xContentTypeOptions?: overridableHeader\n  xDnsPrefetchControl?: overridableHeader\n  xDownloadOptions?: overridableHeader\n  xFrameOptions?: overridableHeader\n  xPermittedCrossDomainPolicies?: overridableHeader\n  xXssProtection?: overridableHeader\n  removePoweredBy?: boolean\n  permissionsPolicy?: PermissionsPolicyOptions\n}\n\ntype HeadersMap = {\n  [key in keyof SecureHeadersOptions]: [string, string]\n}\n\nconst HEADERS_MAP: HeadersMap = {\n  crossOriginEmbedderPolicy: ['Cross-Origin-Embedder-Policy', 'require-corp'],\n  crossOriginResourcePolicy: ['Cross-Origin-Resource-Policy', 'same-origin'],\n  crossOriginOpenerPolicy: ['Cross-Origin-Opener-Policy', 'same-origin'],\n  originAgentCluster: ['Origin-Agent-Cluster', '?1'],\n  referrerPolicy: ['Referrer-Policy', 'no-referrer'],\n  strictTransportSecurity: ['Strict-Transport-Security', 'max-age=15552000; includeSubDomains'],\n  xContentTypeOptions: ['X-Content-Type-Options', 'nosniff'],\n  xDnsPrefetchControl: ['X-DNS-Prefetch-Control', 'off'],\n  xDownloadOptions: ['X-Download-Options', 'noopen'],\n  xFrameOptions: ['X-Frame-Options', 'SAMEORIGIN'],\n  xPermittedCrossDomainPolicies: ['X-Permitted-Cross-Domain-Policies', 'none'],\n  xXssProtection: ['X-XSS-Protection', '0'],\n}\n\nconst DEFAULT_OPTIONS: SecureHeadersOptions = {\n  crossOriginEmbedderPolicy: false,\n  crossOriginResourcePolicy: true,\n  crossOriginOpenerPolicy: true,\n  originAgentCluster: true,\n  referrerPolicy: true,\n  strictTransportSecurity: true,\n  xContentTypeOptions: true,\n  xDnsPrefetchControl: true,\n  xDownloadOptions: true,\n  xFrameOptions: true,\n  xPermittedCrossDomainPolicies: true,\n  xXssProtection: true,\n  removePoweredBy: true,\n  permissionsPolicy: {},\n}\n\ntype SecureHeadersCallback = (\n  ctx: Context,\n  headersToSet: [string, string | string[]][]\n) => [string, string][]\n\nconst generateNonce = () => {\n  const arrayBuffer = new Uint8Array(16)\n  crypto.getRandomValues(arrayBuffer)\n  return encodeBase64(arrayBuffer.buffer)\n}\n\nexport const NONCE: ContentSecurityPolicyOptionHandler = (ctx) => {\n  const key = 'secureHeadersNonce'\n  const init = ctx.get(key)\n  const nonce = init || generateNonce()\n  if (init == null) {\n    ctx.set(key, nonce)\n  }\n  return `'nonce-${nonce}'`\n}\n\n/**\n * Secure Headers Middleware for Hono.\n *\n * @see {@link https://hono.dev/docs/middleware/builtin/secure-headers}\n *\n * @param {Partial<SecureHeadersOptions>} [customOptions] - The options for the secure headers middleware.\n * @param {ContentSecurityPolicyOptions} [customOptions.contentSecurityPolicy] - Settings for the Content-Security-Policy header.\n * @param {ContentSecurityPolicyOptions} [customOptions.contentSecurityPolicyReportOnly] - Settings for the Content-Security-Policy-Report-Only header.\n * @param {overridableHeader} [customOptions.crossOriginEmbedderPolicy=false] - Settings for the Cross-Origin-Embedder-Policy header.\n * @param {overridableHeader} [customOptions.crossOriginResourcePolicy=true] - Settings for the Cross-Origin-Resource-Policy header.\n * @param {overridableHeader} [customOptions.crossOriginOpenerPolicy=true] - Settings for the Cross-Origin-Opener-Policy header.\n * @param {overridableHeader} [customOptions.originAgentCluster=true] - Settings for the Origin-Agent-Cluster header.\n * @param {overridableHeader} [customOptions.referrerPolicy=true] - Settings for the Referrer-Policy header.\n * @param {ReportingEndpointOptions[]} [customOptions.reportingEndpoints] - Settings for the Reporting-Endpoints header.\n * @param {ReportToOptions[]} [customOptions.reportTo] - Settings for the Report-To header.\n * @param {overridableHeader} [customOptions.strictTransportSecurity=true] - Settings for the Strict-Transport-Security header.\n * @param {overridableHeader} [customOptions.xContentTypeOptions=true] - Settings for the X-Content-Type-Options header.\n * @param {overridableHeader} [customOptions.xDnsPrefetchControl=true] - Settings for the X-DNS-Prefetch-Control header.\n * @param {overridableHeader} [customOptions.xDownloadOptions=true] - Settings for the X-Download-Options header.\n * @param {overridableHeader} [customOptions.xFrameOptions=true] - Settings for the X-Frame-Options header.\n * @param {overridableHeader} [customOptions.xPermittedCrossDomainPolicies=true] - Settings for the X-Permitted-Cross-Domain-Policies header.\n * @param {overridableHeader} [customOptions.xXssProtection=true] - Settings for the X-XSS-Protection header.\n * @param {boolean} [customOptions.removePoweredBy=true] - Settings for remove X-Powered-By header.\n * @param {PermissionsPolicyOptions} [customOptions.permissionsPolicy] - Settings for the Permissions-Policy header.\n * @returns {MiddlewareHandler} The middleware handler function.\n *\n * @example\n * ```ts\n * const app = new Hono()\n * app.use(secureHeaders())\n * ```\n */\nexport const secureHeaders = (customOptions?: SecureHeadersOptions): MiddlewareHandler => {\n  const options = { ...DEFAULT_OPTIONS, ...customOptions }\n  const headersToSet = getFilteredHeaders(options)\n  const callbacks: SecureHeadersCallback[] = []\n\n  if (options.contentSecurityPolicy) {\n    const [callback, value] = getCSPDirectives(options.contentSecurityPolicy)\n    if (callback) {\n      callbacks.push(callback)\n    }\n    headersToSet.push(['Content-Security-Policy', value as string])\n  }\n\n  if (options.contentSecurityPolicyReportOnly) {\n    const [callback, value] = getCSPDirectives(options.contentSecurityPolicyReportOnly)\n    if (callback) {\n      callbacks.push(callback)\n    }\n    headersToSet.push(['Content-Security-Policy-Report-Only', value as string])\n  }\n\n  if (options.permissionsPolicy && Object.keys(options.permissionsPolicy).length > 0) {\n    headersToSet.push([\n      'Permissions-Policy',\n      getPermissionsPolicyDirectives(options.permissionsPolicy),\n    ])\n  }\n\n  if (options.reportingEndpoints) {\n    headersToSet.push(['Reporting-Endpoints', getReportingEndpoints(options.reportingEndpoints)])\n  }\n\n  if (options.reportTo) {\n    headersToSet.push(['Report-To', getReportToOptions(options.reportTo)])\n  }\n\n  return async function secureHeaders(ctx, next) {\n    // should evaluate callbacks before next()\n    // some callback calls ctx.set() for embedding nonce to the page\n    const headersToSetForReq =\n      callbacks.length === 0\n        ? headersToSet\n        : callbacks.reduce((acc, cb) => cb(ctx, acc), headersToSet)\n    await next()\n    setHeaders(ctx, headersToSetForReq)\n\n    if (options?.removePoweredBy) {\n      ctx.res.headers.delete('X-Powered-By')\n    }\n  }\n}\n\nfunction getFilteredHeaders(options: SecureHeadersOptions): [string, string][] {\n  return Object.entries(HEADERS_MAP)\n    .filter(([key]) => options[key as keyof SecureHeadersOptions])\n    .map(([key, defaultValue]) => {\n      const overrideValue = options[key as keyof SecureHeadersOptions]\n      return typeof overrideValue === 'string' ? [defaultValue[0], overrideValue] : defaultValue\n    })\n}\n\nfunction getCSPDirectives(\n  contentSecurityPolicy: ContentSecurityPolicyOptions\n): [SecureHeadersCallback | undefined, string | string[]] {\n  const callbacks: ((ctx: Context, values: string[]) => void)[] = []\n  const resultValues: string[] = []\n\n  for (const [directive, value] of Object.entries(contentSecurityPolicy)) {\n    const valueArray = Array.isArray(value) ? value : [value]\n\n    valueArray.forEach((value, i) => {\n      if (typeof value === 'function') {\n        const index = i * 2 + 2 + resultValues.length\n        callbacks.push((ctx, values) => {\n          values[index] = value(ctx, directive)\n        })\n      }\n    })\n\n    resultValues.push(\n      directive.replace(/[A-Z]+(?![a-z])|[A-Z]/g, (match, offset) =>\n        offset ? '-' + match.toLowerCase() : match.toLowerCase()\n      ),\n      ...valueArray.flatMap((value) => [' ', value]),\n      '; '\n    )\n  }\n  resultValues.pop()\n\n  return callbacks.length === 0\n    ? [undefined, resultValues.join('')]\n    : [\n        (ctx, headersToSet) =>\n          headersToSet.map((values) => {\n            if (\n              values[0] === 'Content-Security-Policy' ||\n              values[0] === 'Content-Security-Policy-Report-Only'\n            ) {\n              const clone = values[1].slice() as unknown as string[]\n              callbacks.forEach((cb) => {\n                cb(ctx, clone)\n              })\n              return [values[0], clone.join('')]\n            } else {\n              return values as [string, string]\n            }\n          }),\n        resultValues,\n      ]\n}\n\nfunction getPermissionsPolicyDirectives(policy: PermissionsPolicyOptions): string {\n  return Object.entries(policy)\n    .map(([directive, value]) => {\n      const kebabDirective = camelToKebab(directive)\n\n      if (typeof value === 'boolean') {\n        return `${kebabDirective}=${value ? '*' : 'none'}`\n      }\n\n      if (Array.isArray(value)) {\n        if (value.length === 0) {\n          return `${kebabDirective}=()`\n        }\n        if (value.length === 1 && (value[0] === '*' || value[0] === 'none')) {\n          return `${kebabDirective}=${value[0]}`\n        }\n        const allowlist = value.map((item) => (['self', 'src'].includes(item) ? item : `\"${item}\"`))\n        return `${kebabDirective}=(${allowlist.join(' ')})`\n      }\n\n      return ''\n    })\n    .filter(Boolean)\n    .join(', ')\n}\n\nfunction camelToKebab(str: string): string {\n  return str.replace(/([a-z\\d])([A-Z])/g, '$1-$2').toLowerCase()\n}\n\nfunction getReportingEndpoints(\n  reportingEndpoints: SecureHeadersOptions['reportingEndpoints'] = []\n): string {\n  return reportingEndpoints.map((endpoint) => `${endpoint.name}=\"${endpoint.url}\"`).join(', ')\n}\n\nfunction getReportToOptions(reportTo: SecureHeadersOptions['reportTo'] = []): string {\n  return reportTo.map((option) => JSON.stringify(option)).join(', ')\n}\n\nfunction setHeaders(ctx: Context, headersToSet: [string, string][]) {\n  headersToSet.forEach(([header, value]) => {\n    ctx.res.headers.set(header, value)\n  })\n}\n"
  },
  {
    "path": "src/middleware/serve-static/index.test.ts",
    "content": "import { Hono } from '../../hono'\nimport { serveStatic as baseServeStatic } from '.'\n\ndescribe('Serve Static Middleware', () => {\n  const app = new Hono()\n  const getContent = vi.fn(async (path) => {\n    if (path.endsWith('not-found.txt')) {\n      return null\n    }\n    return `Hello in ${path}`\n  })\n\n  const serveStatic = baseServeStatic({\n    getContent,\n    isDir: (path) => {\n      if (path === 'static/sub' || path === 'static/hello.world') {\n        return true\n      }\n    },\n    onFound: (path, c) => {\n      if (path.endsWith('hello.html')) {\n        c.header('X-Custom', `Found the file at ${path}`)\n      }\n    },\n  })\n\n  app.get('/static/*', serveStatic)\n\n  beforeEach(() => {\n    getContent.mockClear()\n  })\n\n  it('Should return 200 response - /static/hello.html', async () => {\n    const res = await app.request('/static/hello.html')\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Encoding')).toBeNull()\n    expect(res.headers.get('Content-Type')).toMatch(/^text\\/html/)\n    expect(await res.text()).toBe('Hello in static/hello.html')\n    expect(res.headers.get('X-Custom')).toBe('Found the file at static/hello.html')\n  })\n\n  it('Should return 200 response - /static/sub', async () => {\n    const res = await app.request('/static/sub')\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Type')).toMatch(/^text\\/html/)\n    expect(await res.text()).toBe('Hello in static/sub/index.html')\n  })\n\n  it('Should return 200 response - /static/hello.world', async () => {\n    const res = await app.request('/static/hello.world')\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Type')).toMatch(/^text\\/html/)\n    expect(await res.text()).toBe('Hello in static/hello.world/index.html')\n  })\n\n  it('Should decode URI strings - /static/%E7%82%8E.txt', async () => {\n    const res = await app.request('/static/%E7%82%8E.txt')\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Type')).toMatch(/^text\\/plain/)\n    expect(await res.text()).toBe('Hello in static/炎.txt')\n  })\n\n  it('Should return 404 response - /static/not-found.txt', async () => {\n    const res = await app.request('/static/not-found.txt')\n    expect(res.status).toBe(404)\n    expect(res.headers.get('Content-Encoding')).toBeNull()\n    expect(res.headers.get('Content-Type')).toMatch(/^text\\/plain/)\n    expect(await res.text()).toBe('404 Not Found')\n    expect(getContent).toBeCalledTimes(1)\n  })\n\n  it('Should not allow a directory traversal - /static/%2e%2e/static/hello.html', async () => {\n    const res = await app.fetch({\n      method: 'GET',\n      url: 'http://localhost/static/%2e%2e/static/hello.html',\n    } as Request)\n    expect(res.status).toBe(404)\n    expect(res.headers.get('Content-Type')).toMatch(/^text\\/plain/)\n    expect(await res.text()).toBe('404 Not Found')\n  })\n\n  it('Should return a pre-compressed zstd response - /static/hello.html', async () => {\n    const app = new Hono().use(\n      '*',\n      baseServeStatic({\n        getContent,\n        precompressed: true,\n      })\n    )\n\n    const res = await app.request('/static/hello.html', {\n      headers: { 'Accept-Encoding': 'zstd' },\n    })\n\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Encoding')).toBe('zstd')\n    expect(res.headers.get('Vary')).toBe('Accept-Encoding')\n    expect(res.headers.get('Content-Type')).toMatch(/^text\\/html/)\n    expect(await res.text()).toBe('Hello in static/hello.html.zst')\n  })\n\n  it('Should return a pre-compressed brotli response - /static/hello.html', async () => {\n    const app = new Hono().use(\n      '*',\n      baseServeStatic({\n        getContent,\n        precompressed: true,\n      })\n    )\n\n    const res = await app.request('/static/hello.html', {\n      headers: { 'Accept-Encoding': 'wompwomp, gzip, br, deflate, zstd' },\n    })\n\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Encoding')).toBe('br')\n    expect(res.headers.get('Vary')).toBe('Accept-Encoding')\n    expect(res.headers.get('Content-Type')).toMatch(/^text\\/html/)\n    expect(await res.text()).toBe('Hello in static/hello.html.br')\n  })\n\n  it('Should return a pre-compressed brotli response - /static/hello.unknown', async () => {\n    const app = new Hono().use(\n      '*',\n      baseServeStatic({\n        getContent,\n        precompressed: true,\n      })\n    )\n\n    const res = await app.request('/static/hello.unknown', {\n      headers: { 'Accept-Encoding': 'wompwomp, gzip, br, deflate, zstd' },\n    })\n\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Encoding')).toBe('br')\n    expect(res.headers.get('Vary')).toBe('Accept-Encoding')\n    expect(res.headers.get('Content-Type')).toBe('application/octet-stream')\n    expect(await res.text()).toBe('Hello in static/hello.unknown.br')\n  })\n\n  it('Should not return a pre-compressed response - /static/not-found.txt', async () => {\n    const app = new Hono().use(\n      '*',\n      baseServeStatic({\n        getContent,\n        precompressed: true,\n      })\n    )\n\n    const res = await app.request('/static/not-found.txt', {\n      headers: { 'Accept-Encoding': 'gzip, zstd, br' },\n    })\n\n    expect(res.status).toBe(404)\n    expect(res.headers.get('Content-Encoding')).toBeNull()\n    expect(res.headers.get('Vary')).toBeNull()\n    expect(res.headers.get('Content-Type')).toMatch(/^text\\/plain/)\n    expect(await res.text()).toBe('404 Not Found')\n  })\n\n  it('Should not return a pre-compressed response - /static/hello.html', async () => {\n    const app = new Hono().use(\n      '*',\n      baseServeStatic({\n        getContent,\n        precompressed: true,\n      })\n    )\n\n    const res = await app.request('/static/hello.html', {\n      headers: { 'Accept-Encoding': 'wompwomp, unknown' },\n    })\n\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Encoding')).toBeNull()\n    expect(res.headers.get('Vary')).toBeNull()\n    expect(res.headers.get('Content-Type')).toMatch(/^text\\/html/)\n    expect(await res.text()).toBe('Hello in static/hello.html')\n  })\n\n  it('Should not find pre-compressed files - /static/hello.jpg', async () => {\n    const app = new Hono().use(\n      '*',\n      baseServeStatic({\n        getContent,\n        precompressed: true,\n      })\n    )\n\n    const res = await app.request('/static/hello.jpg', {\n      headers: { 'Accept-Encoding': 'gzip, br, deflate, zstd' },\n    })\n\n    expect(res.status).toBe(200)\n    expect(res.headers.get('Content-Encoding')).toBeNull()\n    expect(res.headers.get('Vary')).toBeNull()\n    expect(res.headers.get('Content-Type')).toMatch(/^image\\/jpeg/)\n    expect(await res.text()).toBe('Hello in static/hello.jpg')\n  })\n\n  it('Should return response object content as-is', async () => {\n    const body = new ReadableStream()\n    const response = new Response(body)\n    const app = new Hono().use(\n      '*',\n      baseServeStatic({\n        getContent: async () => {\n          return response\n        },\n      })\n    )\n\n    const res = await app.fetch({\n      method: 'GET',\n      url: 'http://localhost',\n    } as Request)\n    expect(res.status).toBe(200)\n    expect(res.body).toBe(body)\n  })\n\n  describe('Changing root path', () => {\n    it('Should return the content with absolute root path', async () => {\n      const app = new Hono()\n      const serveStatic = baseServeStatic({\n        getContent,\n        root: '/home/hono/child',\n      })\n      app.get('/static/*', serveStatic)\n\n      const res = await app.request('/static/html/hello.html')\n      expect(await res.text()).toBe('Hello in /home/hono/child/static/html/hello.html')\n    })\n\n    it('Should traverse the directories with absolute root path', async () => {\n      const app = new Hono()\n      const serveStatic = baseServeStatic({\n        getContent,\n        root: '/home/hono/../parent',\n      })\n      app.get('/static/*', serveStatic)\n\n      const res = await app.request('/static/html/hello.html')\n      expect(await res.text()).toBe('Hello in /home/parent/static/html/hello.html')\n    })\n\n    it('Should treat the root path includes .. as relative path', async () => {\n      const app = new Hono()\n      const serveStatic = baseServeStatic({\n        getContent,\n        root: '../home/hono',\n      })\n      app.get('/static/*', serveStatic)\n\n      const res = await app.request('/static/html/hello.html')\n      expect(await res.text()).toBe('Hello in ../home/hono/static/html/hello.html')\n    })\n\n    it('Should not allow directory traversal with . as relative path', async () => {\n      const app = new Hono()\n      const serveStatic = baseServeStatic({\n        getContent,\n        root: '.',\n      })\n      app.get('*', serveStatic)\n\n      const res = await app.request('///etc/passwd')\n      expect(await res.text()).toBe('Hello in etc/passwd')\n    })\n\n    it('Should not allow bypass via path mismatch between middleware and serveStatic', async () => {\n      const app = new Hono()\n\n      app.use('/admin/*', async (c, next) => {\n        c.header('X-Authorized', 'true')\n        await next()\n      })\n\n      const serveStatic = baseServeStatic({\n        getContent,\n        root: '.',\n      })\n      app.use('/*', serveStatic)\n\n      const res = await app.request('/admin/secret.txt')\n      expect(res.headers.get('X-Authorized')).toBe('true')\n      expect(await res.text()).toBe('Hello in admin/secret.txt')\n\n      const res2 = await app.request('/admin%2Fsecret.txt')\n      expect(res2.headers.get('X-Authorized')).toBeNull()\n      expect(await res2.text()).toBe('Hello in admin%2Fsecret.txt')\n    })\n  })\n})\n"
  },
  {
    "path": "src/middleware/serve-static/index.ts",
    "content": "/**\n * @module\n * Serve Static Middleware for Hono.\n */\n\nimport type { Context, Data } from '../../context'\nimport type { Env, MiddlewareHandler } from '../../types'\nimport { COMPRESSIBLE_CONTENT_TYPE_REGEX } from '../../utils/compress'\nimport { getMimeType } from '../../utils/mime'\nimport { tryDecodeURI } from '../../utils/url'\nimport { defaultJoin } from './path'\n\nexport type ServeStaticOptions<E extends Env = Env> = {\n  root?: string\n  path?: string\n  precompressed?: boolean\n  mimes?: Record<string, string>\n  rewriteRequestPath?: (path: string) => string\n  onFound?: (path: string, c: Context<E>) => void | Promise<void>\n  onNotFound?: (path: string, c: Context<E>) => void | Promise<void>\n}\n\nconst ENCODINGS = {\n  br: '.br',\n  zstd: '.zst',\n  gzip: '.gz',\n} as const\nconst ENCODINGS_ORDERED_KEYS = Object.keys(ENCODINGS) as (keyof typeof ENCODINGS)[]\n\nconst DEFAULT_DOCUMENT = 'index.html'\n\n/**\n * This middleware is not directly used by the user. Create a wrapper specifying `getContent()` by the environment such as Deno or Bun.\n */\nexport const serveStatic = <E extends Env = Env>(\n  options: ServeStaticOptions<E> & {\n    getContent: (path: string, c: Context<E>) => Promise<Data | Response | null>\n    /**\n     *\n     * `join` option according to the runtime. Example `import { join } from 'node:path`. If not specified, it will fall back to the default join function.`\n     */\n    join?: (...paths: string[]) => string\n    /**\n     * @deprecated Currently, `pathResolve` is no longer used.\n     */\n    pathResolve?: (path: string) => string\n    isDir?: (path: string) => boolean | undefined | Promise<boolean | undefined>\n  }\n): MiddlewareHandler => {\n  const root = options.root ?? './'\n  const optionPath = options.path\n  const join = options.join ?? defaultJoin\n\n  return async (c, next) => {\n    // Do nothing if Response is already set\n    if (c.finalized) {\n      return next()\n    }\n\n    let filename: string\n\n    if (options.path) {\n      filename = options.path\n    } else {\n      try {\n        filename = tryDecodeURI(c.req.path)\n        if (/(?:^|[\\/\\\\])\\.\\.(?:$|[\\/\\\\])/.test(filename)) {\n          throw new Error()\n        }\n      } catch {\n        await options.onNotFound?.(c.req.path, c)\n        return next()\n      }\n    }\n\n    let path = join(\n      root,\n      !optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename) : filename\n    )\n\n    if (options.isDir && (await options.isDir(path))) {\n      path = join(path, DEFAULT_DOCUMENT)\n    }\n\n    const getContent = options.getContent\n    let content = await getContent(path, c)\n\n    if (content instanceof Response) {\n      return c.newResponse(content.body, content)\n    }\n\n    if (content) {\n      const mimeType = (options.mimes && getMimeType(path, options.mimes)) || getMimeType(path)\n      c.header('Content-Type', mimeType || 'application/octet-stream')\n\n      if (options.precompressed && (!mimeType || COMPRESSIBLE_CONTENT_TYPE_REGEX.test(mimeType))) {\n        const acceptEncodingSet = new Set(\n          c.req\n            .header('Accept-Encoding')\n            ?.split(',')\n            .map((encoding) => encoding.trim())\n        )\n\n        for (const encoding of ENCODINGS_ORDERED_KEYS) {\n          if (!acceptEncodingSet.has(encoding)) {\n            continue\n          }\n          const compressedContent = (await getContent(path + ENCODINGS[encoding], c)) as Data | null\n\n          if (compressedContent) {\n            content = compressedContent\n            c.header('Content-Encoding', encoding)\n            c.header('Vary', 'Accept-Encoding', { append: true })\n            break\n          }\n        }\n      }\n      await options.onFound?.(path, c)\n      return c.body(content)\n    }\n\n    await options.onNotFound?.(path, c)\n    await next()\n    return\n  }\n}\n"
  },
  {
    "path": "src/middleware/serve-static/path.test.ts",
    "content": "import { join as posixJoin } from 'node:path/posix'\nimport { defaultJoin } from './path'\n\ndescribe('defaultJoin', () => {\n  describe('Comparison with node:path/posix.join', () => {\n    it('Should behave like path.posix.join for all path operations', () => {\n      const testCases = [\n        // Basic path joining\n        ['/home/yusuke/work/app/public', 'static/main.html'],\n        ['public', 'sub/', 'file.html'],\n        ['', 'file.html'],\n        ['public', ''],\n        ['public/', 'file.html'],\n        ['public', 'static', 'main.html'],\n        ['assets', 'images', 'logo.png'],\n\n        // Parent directory references\n        ['public', '../parent/file.html'],\n        ['public', '../../grandparent/file.html'],\n        ['/abs/path', '../relative.html'],\n        ['a/b', '../c'],\n        ['a/b/c', '../../d'],\n\n        // Current directory references\n        ['./public', 'static/main.html'],\n        ['public', './file.html'],\n        ['./public', '/absolute/path.html'],\n        ['.', 'file.html'],\n        ['public/.', 'file.html'],\n\n        // Edge cases\n        [],\n        ['.'],\n        [''],\n        ['/'],\n        ['a', 'b', 'c'],\n\n        // Backslash handling (security)\n        ['static', 'test\\\\file.txt'],\n        ['public', 'path\\\\with\\\\backslash'],\n      ]\n\n      testCases.forEach(([...args]) => {\n        const expected = posixJoin(...args)\n        const actual = defaultJoin(...args)\n        expect(actual).toBe(expected)\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/middleware/serve-static/path.ts",
    "content": "/**\n * `defaultJoin` does not support Windows paths and always uses `/` separators.\n * If you need Windows path support, please use `join` exported from `node:path` etc. instead.\n */\nexport const defaultJoin = (...paths: string[]): string => {\n  // Join non-empty paths with '/'\n  let result = paths.filter((p) => p !== '').join('/')\n\n  // Normalize multiple slashes to single slash\n  result = result.replace(/(?<=\\/)\\/+/g, '')\n\n  // Handle path resolution (. and ..)\n  const segments = result.split('/')\n  const resolved = []\n\n  for (const segment of segments) {\n    if (segment === '..' && resolved.length > 0 && resolved.at(-1) !== '..') {\n      resolved.pop()\n    } else if (segment !== '.') {\n      resolved.push(segment)\n    }\n  }\n\n  return resolved.join('/') || '.'\n}\n"
  },
  {
    "path": "src/middleware/timeout/index.test.ts",
    "content": "import type { Context } from '../../context'\nimport { Hono } from '../../hono'\nimport { HTTPException } from '../../http-exception'\nimport type { HTTPExceptionFunction } from '.'\nimport { timeout } from '.'\n\ndescribe('Timeout API', () => {\n  const app = new Hono()\n\n  app.use('/slow-endpoint', timeout(1000))\n  app.use(\n    '/slow-endpoint/custom',\n    timeout(\n      1100,\n      () => new HTTPException(408, { message: 'Request timeout. Please try again later.' })\n    )\n  )\n  const exception500: HTTPExceptionFunction = (context: Context) =>\n    new HTTPException(500, { message: `Internal Server Error at ${context.req.path}` })\n  app.use('/slow-endpoint/error', timeout(1200, exception500))\n  app.use('/normal-endpoint', timeout(1000))\n\n  app.get('/slow-endpoint', async (c) => {\n    await new Promise((resolve) => setTimeout(resolve, 1100))\n    return c.text('This should not show up')\n  })\n\n  app.get('/slow-endpoint/custom', async (c) => {\n    await new Promise((resolve) => setTimeout(resolve, 1200))\n    return c.text('This should not show up')\n  })\n\n  app.get('/slow-endpoint/error', async (c) => {\n    await new Promise((resolve) => setTimeout(resolve, 1300))\n    return c.text('This should not show up')\n  })\n\n  app.get('/normal-endpoint', async (c) => {\n    await new Promise((resolve) => setTimeout(resolve, 900))\n    return c.text('This should not show up')\n  })\n\n  it('Should trigger default timeout exception', async () => {\n    const res = await app.request('http://localhost/slow-endpoint')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(504)\n    expect(await res.text()).toContain('Gateway Timeout')\n  })\n\n  it('Should apply custom exception with function', async () => {\n    const res = await app.request('http://localhost/slow-endpoint/custom')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(408)\n    expect(await res.text()).toContain('Request timeout. Please try again later.')\n  })\n\n  it('Error timeout with custom status code and message', async () => {\n    const res = await app.request('http://localhost/slow-endpoint/error')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(500)\n    expect(await res.text()).toContain('Internal Server Error at /slow-endpoint/error')\n  })\n\n  it('No Timeout should pass', async () => {\n    const res = await app.request('http://localhost/normal-endpoint')\n    expect(res).not.toBeNull()\n    expect(res.status).toBe(200)\n    expect(await res.text()).toContain('This should not show up')\n  })\n})\n"
  },
  {
    "path": "src/middleware/timeout/index.ts",
    "content": "/**\n * @module\n * Timeout Middleware for Hono.\n */\n\nimport type { Context } from '../../context'\nimport { HTTPException } from '../../http-exception'\nimport type { MiddlewareHandler } from '../../types'\n\nexport type HTTPExceptionFunction = (context: Context) => HTTPException\n\nconst defaultTimeoutException = new HTTPException(504, {\n  message: 'Gateway Timeout',\n})\n\n/**\n * Timeout Middleware for Hono.\n *\n * @param {number} duration - The timeout duration in milliseconds.\n * @param {HTTPExceptionFunction | HTTPException} [exception=defaultTimeoutException] - The exception to throw when the timeout occurs. Can be a function that returns an HTTPException or an HTTPException object.\n * @returns {MiddlewareHandler} The middleware handler function.\n *\n * @example\n * ```ts\n * const app = new Hono()\n *\n * app.use(\n *   '/long-request',\n *   timeout(5000) // Set timeout to 5 seconds\n * )\n *\n * app.get('/long-request', async (c) => {\n *   await someLongRunningFunction()\n *   return c.text('Completed within time limit')\n * })\n * ```\n */\nexport const timeout = (\n  duration: number,\n  exception: HTTPExceptionFunction | HTTPException = defaultTimeoutException\n): MiddlewareHandler => {\n  return async function timeout(context, next) {\n    let timer: number | undefined\n    const timeoutPromise = new Promise<void>((_, reject) => {\n      timer = setTimeout(() => {\n        reject(typeof exception === 'function' ? exception(context) : exception)\n      }, duration) as unknown as number\n    })\n\n    try {\n      await Promise.race([next(), timeoutPromise])\n    } finally {\n      if (timer !== undefined) {\n        clearTimeout(timer)\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "src/middleware/timing/index.test.ts",
    "content": "import { Hono } from '../../hono'\nimport { endTime, setMetric, startTime, timing, wrapTime } from '.'\n\ndescribe('Server-Timing API', () => {\n  const app = new Hono()\n\n  const totalDescription = 'my total DescRipTion!'\n  const name = 'sleep'\n  const region = 'region'\n  const regionDesc = 'europe-west3'\n\n  app.use(\n    '*',\n    timing({\n      totalDescription,\n    })\n  )\n  app.get('/', (c) => c.text('/'))\n  app.get('/api', async (c) => {\n    startTime(c, name)\n    await new Promise((r) => setTimeout(r, 30))\n    endTime(c, name)\n\n    return c.text('api!')\n  })\n  app.get('/api-wrap', async (c) => {\n    await wrapTime(c, name, new Promise((r) => setTimeout(r, 30)))\n\n    return c.text('api!')\n  })\n  app.get('/api-wrap-throw', async (c) => {\n    try {\n      await wrapTime(c, name, new Promise((_, r) => setTimeout(r, 30)))\n    } catch (e) {\n      return c.text(`error :( ${e}`, 500)\n    }\n\n    return c.text('api!')\n  })\n  app.get('/cache', async (c) => {\n    setMetric(c, region, regionDesc)\n\n    return c.text('cache!')\n  })\n\n  const sub = new Hono()\n\n  sub.use(timing())\n  sub.get('/', (c) => c.text('sub'))\n  app.route('/sub', sub)\n\n  it('Should contain total duration', async () => {\n    const res = await app.request('http://localhost/')\n    expect(res).not.toBeNull()\n    expect(res.headers.has('server-timing')).toBeTruthy()\n    expect(res.headers.get('server-timing')?.includes('total;dur=')).toBeTruthy()\n    expect(res.headers.get('server-timing')?.includes(totalDescription)).toBeTruthy()\n  })\n\n  it('Should contain value metrics', async () => {\n    const res = await app.request('http://localhost/api')\n    expect(res).not.toBeNull()\n    expect(res.headers.has('server-timing')).toBeTruthy()\n    expect(res.headers.get('server-timing')?.includes(`${name};dur=`)).toBeTruthy()\n    expect(res.headers.get('server-timing')?.includes(name)).toBeTruthy()\n  })\n\n  it('Should contain value metrics, wrapped', async () => {\n    const res = await app.request('http://localhost/api-wrap')\n    expect(res).not.toBeNull()\n    expect(res.headers.has('server-timing')).toBeTruthy()\n    expect(res.headers.get('server-timing')?.includes(`${name};dur=`)).toBeTruthy()\n    expect(res.headers.get('server-timing')?.includes(name)).toBeTruthy()\n  })\n\n  it('Should contain value metrics, wrapped throw', async () => {\n    const res = await app.request('http://localhost/api-wrap-throw')\n    expect(res).not.toBeNull()\n    expect(res.headers.has('server-timing')).toBeTruthy()\n    expect(res.status).toBeGreaterThanOrEqual(500)\n    expect(res.headers.get('server-timing')?.includes(`${name};dur=`)).toBeTruthy()\n    expect(res.headers.get('server-timing')?.includes(name)).toBeTruthy()\n  })\n\n  it('Should contain value-less metrics', async () => {\n    const res = await app.request('http://localhost/cache')\n    expect(res).not.toBeNull()\n    expect(res.headers.has('server-timing')).toBeTruthy()\n    expect(\n      res.headers.get('server-timing')?.includes(`${region};desc=\"${regionDesc}\"`)\n    ).toBeTruthy()\n    expect(res.headers.get('server-timing')?.includes(region)).toBeTruthy()\n    expect(res.headers.get('server-timing')?.includes(regionDesc)).toBeTruthy()\n  })\n\n  it('Should not be enabled if the main app has the timing middleware', async () => {\n    const consoleWarnSpy = vi.spyOn(console, 'warn')\n    const res = await app.request('/sub')\n    expect(res.status).toBe(200)\n    expect(res.headers.has('server-timing')).toBeTruthy()\n    expect(res.headers.get('server-timing')?.includes(totalDescription)).toBeTruthy()\n    expect(consoleWarnSpy).not.toHaveBeenCalled()\n    consoleWarnSpy.mockRestore()\n  })\n\n  describe('Should handle crossOrigin setting', async () => {\n    it('Should do nothing when crossOrigin is falsy', async () => {\n      const crossOriginApp = new Hono()\n\n      crossOriginApp.use(\n        '*',\n        timing({\n          crossOrigin: false,\n        })\n      )\n\n      crossOriginApp.get('/', (c) => c.text('/'))\n\n      const res = await crossOriginApp.request('http://localhost/')\n\n      expect(res).not.toBeNull()\n      expect(res.headers.has('server-timing')).toBeTruthy()\n      expect(res.headers.has('timing-allow-origin')).toBeFalsy()\n    })\n\n    it('Should set Timing-Allow-Origin to * when crossOrigin is true', async () => {\n      const crossOriginApp = new Hono()\n\n      crossOriginApp.use(\n        '*',\n        timing({\n          crossOrigin: true,\n        })\n      )\n\n      crossOriginApp.get('/', (c) => c.text('/'))\n\n      const res = await crossOriginApp.request('http://localhost/')\n\n      expect(res).not.toBeNull()\n      expect(res.headers.has('server-timing')).toBeTruthy()\n      expect(res.headers.has('timing-allow-origin')).toBeTruthy()\n      expect(res.headers.get('timing-allow-origin')).toBe('*')\n    })\n\n    it('Should set Timing-Allow-Origin to the value of crossOrigin when it is a string', async () => {\n      const crossOriginApp = new Hono()\n\n      crossOriginApp.use(\n        '*',\n        timing({\n          crossOrigin: 'https://example.com',\n        })\n      )\n\n      crossOriginApp.get('/', (c) => c.text('/'))\n\n      const res = await crossOriginApp.request('http://localhost/')\n\n      expect(res).not.toBeNull()\n      expect(res.headers.has('server-timing')).toBeTruthy()\n      expect(res.headers.has('timing-allow-origin')).toBeTruthy()\n      expect(res.headers.get('timing-allow-origin')).toBe('https://example.com')\n    })\n\n    it('Should set Timing-Allow-Origin to the return value of crossOrigin when it is a function', async () => {\n      const crossOriginApp = new Hono()\n\n      crossOriginApp.use(\n        '*',\n        timing({\n          crossOrigin: (c) => c.req.header('origin') ?? '*',\n        })\n      )\n\n      crossOriginApp.get('/', (c) => c.text('/'))\n\n      const res = await crossOriginApp.request('http://localhost/', {\n        headers: {\n          origin: 'https://example.com',\n        },\n      })\n\n      expect(res).not.toBeNull()\n      expect(res.headers.has('server-timing')).toBeTruthy()\n      expect(res.headers.has('timing-allow-origin')).toBeTruthy()\n      expect(res.headers.get('timing-allow-origin')).toBe('https://example.com')\n    })\n  })\n})\n"
  },
  {
    "path": "src/middleware/timing/index.ts",
    "content": "import type { TimingVariables } from './timing'\n\nexport { TimingVariables }\nexport { timing, setMetric, startTime, endTime, wrapTime } from './timing'\n\ndeclare module '../..' {\n  interface ContextVariableMap extends TimingVariables {}\n}\n"
  },
  {
    "path": "src/middleware/timing/timing.ts",
    "content": "/**\n * @module\n * Server-Timing Middleware for Hono.\n */\n\nimport type { Context } from '../../context'\nimport type { MiddlewareHandler } from '../../types'\nimport '../../context'\n\nexport type TimingVariables = {\n  metric?: {\n    headers: string[]\n    timers: Map<string, Timer>\n  }\n}\n\ninterface Timer {\n  description?: string\n  start: number\n}\n\ninterface TimingOptions {\n  total?: boolean\n  enabled?: boolean | ((c: Context) => boolean)\n  totalDescription?: string\n  autoEnd?: boolean\n  crossOrigin?: boolean | string | ((c: Context) => boolean | string)\n}\n\nconst getTime = (): number => {\n  try {\n    return performance.now()\n  } catch {}\n  return Date.now()\n}\n\n/**\n * Server-Timing Middleware for Hono.\n *\n * @see {@link https://hono.dev/docs/middleware/builtin/timing}\n *\n * @param {TimingOptions} [config] - The options for the timing middleware.\n * @param {boolean} [config.total=true] - Show the total response time.\n * @param {boolean | ((c: Context) => boolean)} [config.enabled=true] - Whether timings should be added to the headers or not.\n * @param {string} [config.totalDescription=Total Response Time] - Description for the total response time.\n * @param {boolean} [config.autoEnd=true] - If `startTime()` should end automatically at the end of the request.\n * @param {boolean | string | ((c: Context) => boolean | string)} [config.crossOrigin=false] - The origin this timings header should be readable.\n * @returns {MiddlewareHandler} The middleware handler function.\n *\n * @example\n * ```ts\n * const app = new Hono()\n *\n * // add the middleware to your router\n * app.use(timing());\n *\n * app.get('/', async (c) => {\n *   // add custom metrics\n *   setMetric(c, 'region', 'europe-west3')\n *\n *   // add custom metrics with timing, must be in milliseconds\n *   setMetric(c, 'custom', 23.8, 'My custom Metric')\n *\n *   // start a new timer\n *   startTime(c, 'db');\n *\n *   const data = await db.findMany(...);\n *\n *   // end the timer\n *   endTime(c, 'db');\n *\n *   return c.json({ response: data });\n * });\n * ```\n */\nexport const timing = (config?: TimingOptions): MiddlewareHandler => {\n  const options: TimingOptions = {\n    total: true,\n    enabled: true,\n    totalDescription: 'Total Response Time',\n    autoEnd: true,\n    crossOrigin: false,\n    ...config,\n  }\n  return async function timing(c, next) {\n    const headers: string[] = []\n    const timers = new Map<string, Timer>()\n\n    if (c.get('metric')) {\n      return await next()\n    }\n\n    c.set('metric', { headers, timers })\n\n    if (options.total) {\n      startTime(c, 'total', options.totalDescription)\n    }\n    await next()\n\n    if (options.total) {\n      endTime(c, 'total')\n    }\n\n    if (options.autoEnd) {\n      timers.forEach((_, key) => endTime(c, key))\n    }\n\n    const enabled = typeof options.enabled === 'function' ? options.enabled(c) : options.enabled\n\n    if (enabled) {\n      c.res.headers.append('Server-Timing', headers.join(','))\n\n      const crossOrigin =\n        typeof options.crossOrigin === 'function' ? options.crossOrigin(c) : options.crossOrigin\n\n      if (crossOrigin) {\n        c.res.headers.append(\n          'Timing-Allow-Origin',\n          typeof crossOrigin === 'string' ? crossOrigin : '*'\n        )\n      }\n    }\n  }\n}\n\ninterface SetMetric {\n  (c: Context, name: string, value: number, description?: string, precision?: number): void\n\n  (c: Context, name: string, description?: string): void\n}\n\n/**\n * Set a metric for the timing middleware.\n *\n * @param {Context} c - The context of the request.\n * @param {string} name - The name of the metric.\n * @param {number | string} [valueDescription] - The value or description of the metric.\n * @param {string} [description] - The description of the metric.\n * @param {number} [precision] - The precision of the metric value.\n *\n * @example\n * ```ts\n * setMetric(c, 'region', 'europe-west3')\n * setMetric(c, 'custom', 23.8, 'My custom Metric')\n * ```\n */\nexport const setMetric: SetMetric = (\n  c: Context,\n  name: string,\n  valueDescription: number | string | undefined,\n  description?: string,\n  precision?: number\n) => {\n  const metrics = c.get('metric')\n  if (!metrics) {\n    console.warn('Metrics not initialized! Please add the `timing()` middleware to this route!')\n    return\n  }\n  if (typeof valueDescription === 'number') {\n    const dur = valueDescription.toFixed(precision || 1)\n\n    const metric = description ? `${name};dur=${dur};desc=\"${description}\"` : `${name};dur=${dur}`\n\n    metrics.headers.push(metric)\n  } else {\n    // Value-less metric\n    const metric = valueDescription ? `${name};desc=\"${valueDescription}\"` : `${name}`\n\n    metrics.headers.push(metric)\n  }\n}\n\n/**\n * Start a timer for the timing middleware.\n *\n * @param {Context} c - The context of the request.\n * @param {string} name - The name of the timer.\n * @param {string} [description] - The description of the timer.\n *\n * @example\n * ```ts\n * startTime(c, 'db')\n * ```\n */\nexport const startTime = (c: Context, name: string, description?: string) => {\n  const metrics = c.get('metric')\n  if (!metrics) {\n    console.warn('Metrics not initialized! Please add the `timing()` middleware to this route!')\n    return\n  }\n  metrics.timers.set(name, { description, start: getTime() })\n}\n\n/**\n * End a timer for the timing middleware.\n *\n * @param {Context} c - The context of the request.\n * @param {string} name - The name of the timer.\n * @param {number} [precision] - The precision of the timer value.\n *\n * @example\n * ```ts\n * endTime(c, 'db')\n * ```\n */\nexport const endTime = (c: Context, name: string, precision?: number) => {\n  const metrics = c.get('metric')\n  if (!metrics) {\n    console.warn('Metrics not initialized! Please add the `timing()` middleware to this route!')\n    return\n  }\n  const timer = metrics.timers.get(name)\n  if (!timer) {\n    console.warn(`Timer \"${name}\" does not exist!`)\n    return\n  }\n  const { description, start } = timer\n\n  const duration = getTime() - start\n\n  setMetric(c, name, duration, description, precision)\n  metrics.timers.delete(name)\n}\n\n/**\n * Wrap a Promise to capture its duration.\n * @param {Context} c - The context of the request.\n * @param {string} name - The name of the timer.\n * @param {Promise<T>} callable - The Promise to time.\n * @param {string} [description] - The description of the timer.\n * @param {number} [precision] - The precision of the timer value.\n *\n * @example\n * ```ts\n *   // Instead of this:\n *   const data = await db.findMany(...);\n *\n *   // do this:\n *   const data = await wrapTime(c, 'query', db.findMany(...));\n * ```\n * */\nexport async function wrapTime<T>(\n  c: Context,\n  name: string,\n  callable: Promise<T>,\n  description?: string,\n  precision?: number\n): Promise<T> {\n  startTime(c, name, description)\n  try {\n    return await callable\n  } finally {\n    endTime(c, name, precision)\n  }\n}\n"
  },
  {
    "path": "src/middleware/trailing-slash/index.test.ts",
    "content": "import { Hono } from '../../hono'\nimport { appendTrailingSlash, trimTrailingSlash } from '.'\n\ndescribe('Resolve trailing slash', () => {\n  describe('trimTrailingSlash middleware', () => {\n    const app = new Hono({ strict: true })\n    app.use('*', trimTrailingSlash())\n\n    app.get('/', async (c) => {\n      return c.text('ok')\n    })\n    app.get('/the/example/endpoint/without/trailing/slash', async (c) => {\n      return c.text('ok')\n    })\n\n    it('should handle GET request for root path correctly', async () => {\n      const resp = await app.request('/')\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(200)\n    })\n\n    it('should handle GET request for path without trailing slash correctly', async () => {\n      const resp = await app.request('/the/example/endpoint/without/trailing/slash')\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(200)\n    })\n\n    it('should handle GET request for path with trailing slash correctly', async () => {\n      const resp = await app.request('/the/example/endpoint/without/trailing/slash/')\n      const loc = new URL(resp.headers.get('location')!)\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(301)\n      expect(loc.pathname).toBe('/the/example/endpoint/without/trailing/slash')\n    })\n\n    it('should preserve query parameters when redirecting', async () => {\n      const resp = await app.request('/the/example/endpoint/without/trailing/slash/?exampleParam=1')\n      const loc = new URL(resp.headers.get('location')!)\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(301)\n      expect(loc.pathname).toBe('/the/example/endpoint/without/trailing/slash')\n      expect(loc.searchParams.get('exampleParam')).toBe('1')\n    })\n\n    it('should handle HEAD request for root path correctly', async () => {\n      const resp = await app.request('/', { method: 'HEAD' })\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(200)\n    })\n\n    it('should handle HEAD request for path without trailing slash correctly', async () => {\n      const resp = await app.request('/the/example/endpoint/without/trailing/slash', {\n        method: 'HEAD',\n      })\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(200)\n    })\n\n    it('should handle HEAD request for path with trailing slash correctly', async () => {\n      const resp = await app.request('/the/example/endpoint/without/trailing/slash/', {\n        method: 'HEAD',\n      })\n      const loc = new URL(resp.headers.get('location')!)\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(301)\n      expect(loc.pathname).toBe('/the/example/endpoint/without/trailing/slash')\n    })\n\n    it('should preserve query parameters when redirecting HEAD requests', async () => {\n      const resp = await app.request(\n        '/the/example/endpoint/without/trailing/slash/?exampleParam=1',\n        { method: 'HEAD' }\n      )\n      const loc = new URL(resp.headers.get('location')!)\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(301)\n      expect(loc.pathname).toBe('/the/example/endpoint/without/trailing/slash')\n      expect(loc.searchParams.get('exampleParam')).toBe('1')\n    })\n  })\n\n  describe('trimTrailingSlash middleware with alwaysRedirect option', () => {\n    const app = new Hono()\n    app.use('*', trimTrailingSlash({ alwaysRedirect: true }))\n\n    app.get('/', async (c) => {\n      return c.text('ok')\n    })\n    app.get('/my-path/*', async (c) => {\n      return c.text('wildcard')\n    })\n    app.get('/exact-path', async (c) => {\n      return c.text('exact')\n    })\n\n    it('should handle GET request for root path correctly', async () => {\n      const resp = await app.request('/')\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(200)\n    })\n\n    it('should redirect wildcard route with trailing slash', async () => {\n      const resp = await app.request('/my-path/something/else/')\n      const loc = new URL(resp.headers.get('location')!)\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(301)\n      expect(loc.pathname).toBe('/my-path/something/else')\n    })\n\n    it('should not redirect wildcard route without trailing slash', async () => {\n      const resp = await app.request('/my-path/something/else')\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(200)\n      expect(await resp.text()).toBe('wildcard')\n    })\n\n    it('should redirect exact route with trailing slash', async () => {\n      const resp = await app.request('/exact-path/')\n      const loc = new URL(resp.headers.get('location')!)\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(301)\n      expect(loc.pathname).toBe('/exact-path')\n    })\n\n    it('should preserve query parameters when redirecting', async () => {\n      const resp = await app.request('/my-path/something/?param=1')\n      const loc = new URL(resp.headers.get('location')!)\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(301)\n      expect(loc.pathname).toBe('/my-path/something')\n      expect(loc.searchParams.get('param')).toBe('1')\n    })\n\n    it('should handle HEAD request for wildcard route with trailing slash', async () => {\n      const resp = await app.request('/my-path/something/', { method: 'HEAD' })\n      const loc = new URL(resp.headers.get('location')!)\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(301)\n      expect(loc.pathname).toBe('/my-path/something')\n    })\n  })\n\n  describe('appendTrailingSlash middleware', () => {\n    const app = new Hono({ strict: true })\n    app.use('*', appendTrailingSlash())\n\n    app.get('/', async (c) => {\n      return c.text('ok')\n    })\n    app.get('/the/example/endpoint/with/trailing/slash/', async (c) => {\n      return c.text('ok')\n    })\n    app.get('/the/example/simulate/a.file', async (c) => {\n      return c.text('ok')\n    })\n\n    it('should handle GET request for root path correctly', async () => {\n      const resp = await app.request('/')\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(200)\n    })\n\n    it('should handle GET request for file-like paths correctly', async () => {\n      const resp = await app.request('/the/example/simulate/a.file')\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(200)\n    })\n\n    it('should handle GET request for path with trailing slash correctly', async () => {\n      const resp = await app.request('/the/example/endpoint/with/trailing/slash/')\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(200)\n    })\n\n    it('should redirect path without trailing slash to one with it', async () => {\n      const resp = await app.request('/the/example/endpoint/with/trailing/slash')\n      const loc = new URL(resp.headers.get('location')!)\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(301)\n      expect(loc.pathname).toBe('/the/example/endpoint/with/trailing/slash/')\n    })\n\n    it('should preserve query parameters when redirecting', async () => {\n      const resp = await app.request('/the/example/endpoint/with/trailing/slash?exampleParam=1')\n      const loc = new URL(resp.headers.get('location')!)\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(301)\n      expect(loc.pathname).toBe('/the/example/endpoint/with/trailing/slash/')\n      expect(loc.searchParams.get('exampleParam')).toBe('1')\n    })\n\n    it('should handle HEAD request for root path correctly', async () => {\n      const resp = await app.request('/', { method: 'HEAD' })\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(200)\n    })\n\n    it('should handle HEAD request for file-like paths correctly', async () => {\n      const resp = await app.request('/the/example/simulate/a.file', { method: 'HEAD' })\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(200)\n    })\n\n    it('should handle HEAD request for path with trailing slash correctly', async () => {\n      const resp = await app.request('/the/example/endpoint/with/trailing/slash/', {\n        method: 'HEAD',\n      })\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(200)\n    })\n\n    it('should redirect HEAD request for path without trailing slash to one with it', async () => {\n      const resp = await app.request('/the/example/endpoint/with/trailing/slash', {\n        method: 'HEAD',\n      })\n      const loc = new URL(resp.headers.get('location')!)\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(301)\n      expect(loc.pathname).toBe('/the/example/endpoint/with/trailing/slash/')\n    })\n\n    it('should preserve query parameters when redirecting HEAD requests', async () => {\n      const resp = await app.request('/the/example/endpoint/with/trailing/slash?exampleParam=1', {\n        method: 'HEAD',\n      })\n      const loc = new URL(resp.headers.get('location')!)\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(301)\n      expect(loc.pathname).toBe('/the/example/endpoint/with/trailing/slash/')\n      expect(loc.searchParams.get('exampleParam')).toBe('1')\n    })\n  })\n\n  describe('appendTrailingSlash middleware with alwaysRedirect option', () => {\n    const app = new Hono()\n    app.use('*', appendTrailingSlash({ alwaysRedirect: true }))\n\n    app.get('/', async (c) => {\n      return c.text('ok')\n    })\n    app.get('/my-path/*', async (c) => {\n      return c.text('wildcard')\n    })\n    app.get('/exact-path/', async (c) => {\n      return c.text('exact')\n    })\n\n    it('should handle GET request for root path correctly', async () => {\n      const resp = await app.request('/')\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(200)\n    })\n\n    it('should redirect wildcard route without trailing slash', async () => {\n      const resp = await app.request('/my-path/something/else')\n      const loc = new URL(resp.headers.get('location')!)\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(301)\n      expect(loc.pathname).toBe('/my-path/something/else/')\n    })\n\n    it('should not redirect wildcard route with trailing slash', async () => {\n      const resp = await app.request('/my-path/something/else/')\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(200)\n      expect(await resp.text()).toBe('wildcard')\n    })\n\n    it('should redirect exact route without trailing slash', async () => {\n      const resp = await app.request('/exact-path')\n      const loc = new URL(resp.headers.get('location')!)\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(301)\n      expect(loc.pathname).toBe('/exact-path/')\n    })\n\n    it('should preserve query parameters when redirecting', async () => {\n      const resp = await app.request('/my-path/something?param=1')\n      const loc = new URL(resp.headers.get('location')!)\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(301)\n      expect(loc.pathname).toBe('/my-path/something/')\n      expect(loc.searchParams.get('param')).toBe('1')\n    })\n\n    it('should handle HEAD request for wildcard route without trailing slash', async () => {\n      const resp = await app.request('/my-path/something', { method: 'HEAD' })\n      const loc = new URL(resp.headers.get('location')!)\n\n      expect(resp).not.toBeNull()\n      expect(resp.status).toBe(301)\n      expect(loc.pathname).toBe('/my-path/something/')\n    })\n  })\n})\n"
  },
  {
    "path": "src/middleware/trailing-slash/index.ts",
    "content": "/**\n * @module\n * Trailing Slash Middleware for Hono.\n */\n\nimport type { MiddlewareHandler } from '../../types'\n\ntype TrimTrailingSlashOptions = {\n  /**\n   * If `true`, the middleware will always redirect requests with a trailing slash\n   * before executing handlers.\n   * This is useful for routes with wildcards (`*`).\n   * If `false` (default), it will only redirect when the route is not found (404).\n   * @default false\n   */\n  alwaysRedirect?: boolean\n}\n\n/**\n * Trailing Slash Middleware for Hono.\n *\n * @see {@link https://hono.dev/docs/middleware/builtin/trailing-slash}\n *\n * @param {TrimTrailingSlashOptions} options - The options for the middleware.\n * @returns {MiddlewareHandler} The middleware handler function.\n *\n * @example\n * ```ts\n * const app = new Hono()\n *\n * app.use(trimTrailingSlash())\n * app.get('/about/me/', (c) => c.text('With Trailing Slash'))\n * ```\n *\n * @example\n * ```ts\n * // With alwaysRedirect option for wildcard routes\n * const app = new Hono()\n *\n * app.use(trimTrailingSlash({ alwaysRedirect: true }))\n * app.get('/my-path/*', (c) => c.text('Wildcard route'))\n * ```\n */\nexport const trimTrailingSlash = (options?: TrimTrailingSlashOptions): MiddlewareHandler => {\n  return async function trimTrailingSlash(c, next) {\n    if (options?.alwaysRedirect) {\n      if (\n        (c.req.method === 'GET' || c.req.method === 'HEAD') &&\n        c.req.path !== '/' &&\n        c.req.path.at(-1) === '/'\n      ) {\n        const url = new URL(c.req.url)\n        url.pathname = url.pathname.substring(0, url.pathname.length - 1)\n\n        return c.redirect(url.toString(), 301)\n      }\n    }\n\n    await next()\n\n    if (\n      !options?.alwaysRedirect &&\n      c.res.status === 404 &&\n      (c.req.method === 'GET' || c.req.method === 'HEAD') &&\n      c.req.path !== '/' &&\n      c.req.path.at(-1) === '/'\n    ) {\n      const url = new URL(c.req.url)\n      url.pathname = url.pathname.substring(0, url.pathname.length - 1)\n\n      c.res = c.redirect(url.toString(), 301)\n    }\n  }\n}\n\ntype AppendTrailingSlashOptions = {\n  /**\n   * If `true`, the middleware will always redirect requests without a trailing slash\n   * before executing handlers.\n   * This is useful for routes with wildcards (`*`).\n   * If `false` (default), it will only redirect when the route is not found (404).\n   * @default false\n   */\n  alwaysRedirect?: boolean\n}\n\n/**\n * Append trailing slash middleware for Hono.\n * 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/`.\n *\n * @see {@link https://hono.dev/docs/middleware/builtin/trailing-slash}\n *\n * @param {AppendTrailingSlashOptions} options - The options for the middleware.\n * @returns {MiddlewareHandler} The middleware handler function.\n *\n * @example\n * ```ts\n * const app = new Hono()\n *\n * app.use(appendTrailingSlash())\n * ```\n *\n * @example\n * ```ts\n * // With alwaysRedirect option for wildcard routes\n * const app = new Hono()\n *\n * app.use(appendTrailingSlash({ alwaysRedirect: true }))\n * app.get('/my-path/*', (c) => c.text('Wildcard route'))\n * ```\n */\nexport const appendTrailingSlash = (options?: AppendTrailingSlashOptions): MiddlewareHandler => {\n  return async function appendTrailingSlash(c, next) {\n    if (options?.alwaysRedirect) {\n      if ((c.req.method === 'GET' || c.req.method === 'HEAD') && c.req.path.at(-1) !== '/') {\n        const url = new URL(c.req.url)\n        url.pathname += '/'\n\n        return c.redirect(url.toString(), 301)\n      }\n    }\n\n    await next()\n\n    if (\n      !options?.alwaysRedirect &&\n      c.res.status === 404 &&\n      (c.req.method === 'GET' || c.req.method === 'HEAD') &&\n      c.req.path.at(-1) !== '/'\n    ) {\n      const url = new URL(c.req.url)\n      url.pathname += '/'\n\n      c.res = c.redirect(url.toString(), 301)\n    }\n  }\n}\n"
  },
  {
    "path": "src/preset/quick.test.ts",
    "content": "import { getRouterName } from '../helper/dev'\nimport { Hono } from './quick'\n\ndescribe('hono/quick preset', () => {\n  it('Should have SmartRouter + LinearRouter', async () => {\n    const app = new Hono()\n    expect(getRouterName(app)).toBe('SmartRouter + LinearRouter')\n  })\n})\n\ndescribe('Generics for Bindings and Variables', () => {\n  interface CloudflareBindings {\n    MY_VARIABLE: string\n  }\n\n  it('Should not throw type errors', () => {\n    // @ts-expect-error Bindings should extend object\n    new Hono<{\n      Bindings: number\n    }>()\n\n    const appWithInterface = new Hono<{\n      Bindings: CloudflareBindings\n    }>()\n\n    appWithInterface.get('/', (c) => {\n      expectTypeOf(c.env.MY_VARIABLE).toMatchTypeOf<string>()\n      return c.text('/')\n    })\n\n    const appWithType = new Hono<{\n      Bindings: {\n        foo: string\n      }\n    }>()\n\n    appWithType.get('/', (c) => {\n      expectTypeOf(c.env.foo).toMatchTypeOf<string>()\n      return c.text('Hello Hono!')\n    })\n  })\n})\n"
  },
  {
    "path": "src/preset/quick.ts",
    "content": "/**\n * @module\n * The preset that uses `LinearRouter`.\n */\n\nimport { HonoBase } from '../hono-base'\nimport type { HonoOptions } from '../hono-base'\nimport { LinearRouter } from '../router/linear-router'\nimport { SmartRouter } from '../router/smart-router'\nimport { TrieRouter } from '../router/trie-router'\nimport type { BlankEnv, BlankSchema, Env, Schema } from '../types'\n\nexport class Hono<\n  E extends Env = BlankEnv,\n  S extends Schema = BlankSchema,\n  BasePath extends string = '/',\n> extends HonoBase<E, S, BasePath> {\n  constructor(options: HonoOptions<E> = {}) {\n    super(options)\n    this.router = new SmartRouter({\n      routers: [new LinearRouter(), new TrieRouter()],\n    })\n  }\n}\n"
  },
  {
    "path": "src/preset/tiny.test.ts",
    "content": "import { getRouterName } from '../helper/dev'\nimport { Hono } from './tiny'\n\ndescribe('hono/tiny preset', () => {\n  it('Should have PatternRouter', async () => {\n    const app = new Hono()\n    expect(getRouterName(app)).toBe('PatternRouter')\n  })\n})\n\ndescribe('Generics for Bindings and Variables', () => {\n  interface CloudflareBindings {\n    MY_VARIABLE: string\n  }\n\n  it('Should not throw type errors', () => {\n    // @ts-expect-error Bindings should extend object\n    new Hono<{\n      Bindings: number\n    }>()\n\n    const appWithInterface = new Hono<{\n      Bindings: CloudflareBindings\n    }>()\n\n    appWithInterface.get('/', (c) => {\n      expectTypeOf(c.env.MY_VARIABLE).toMatchTypeOf<string>()\n      return c.text('/')\n    })\n\n    const appWithType = new Hono<{\n      Bindings: {\n        foo: string\n      }\n    }>()\n\n    appWithType.get('/', (c) => {\n      expectTypeOf(c.env.foo).toMatchTypeOf<string>()\n      return c.text('Hello Hono!')\n    })\n  })\n})\n"
  },
  {
    "path": "src/preset/tiny.ts",
    "content": "/**\n * @module\n * The preset that uses `PatternRouter`.\n */\n\nimport { HonoBase } from '../hono-base'\nimport type { HonoOptions } from '../hono-base'\nimport { PatternRouter } from '../router/pattern-router'\nimport type { BlankEnv, BlankSchema, Env, Schema } from '../types'\n\nexport class Hono<\n  E extends Env = BlankEnv,\n  S extends Schema = BlankSchema,\n  BasePath extends string = '/',\n> extends HonoBase<E, S, BasePath> {\n  constructor(options: HonoOptions<E> = {}) {\n    super(options)\n    this.router = new PatternRouter()\n  }\n}\n"
  },
  {
    "path": "src/request/constants.ts",
    "content": "export const GET_MATCH_RESULT: unique symbol = Symbol()\n"
  },
  {
    "path": "src/request.test.ts",
    "content": "import { HTTPException } from './http-exception'\nimport { cloneRawRequest, HonoRequest } from './request'\nimport type { RouterRoute } from './types'\n\ntype RecursiveRecord<K extends string, T> = {\n  [key in K]: T | RecursiveRecord<K, T>\n}\n\ndescribe('Query', () => {\n  test('req.query() and req.queries()', () => {\n    const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')\n    const req = new HonoRequest(rawRequest)\n\n    const page = req.query('page')\n    expect(page).not.toBeUndefined()\n    expect(page).toBe('2')\n\n    const q = req.query('q')\n    expect(q).toBeUndefined()\n\n    const tags = req.queries('tag')\n    expect(tags).not.toBeUndefined()\n    expect(tags).toEqual(['A', 'B'])\n\n    const q2 = req.queries('q2')\n    expect(q2).toBeUndefined()\n  })\n\n  test('decode special chars', () => {\n    const rawRequest = new Request('http://localhost?mail=framework%40hono.dev&tag=%401&tag=%402')\n    const req = new HonoRequest(rawRequest)\n\n    const mail = req.query('mail')\n    expect(mail).toBe('framework@hono.dev')\n\n    const tags = req.queries('tag')\n    expect(tags).toEqual(['@1', '@2'])\n  })\n})\n\ndescribe('Param', () => {\n  test('req.param() should return empty string for zero-length match', () => {\n    // Simulate a route like '/:remaining{.*}' matching '/'\n    const rawRequest = new Request('http://localhost/')\n    const req = new HonoRequest<'/:remaining{.*}'>(rawRequest, '/', [\n      [[[undefined, {} as RouterRoute], { remaining: 0 }]],\n      [''], // ParamStash with empty string for remaining\n    ])\n\n    // Single param access should be empty string, not undefined\n    expect(req.param('remaining')).toBe('')\n\n    // All params should include key with empty string value\n    const all = req.param()\n    expect(all).toEqual({ remaining: '' })\n  })\n  test('req.param() with ParamStash', () => {\n    const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')\n    const req = new HonoRequest<'/:id/:name'>(rawRequest, '/123/key', [\n      [\n        [[undefined, {} as RouterRoute], { id: 0 }],\n        [[undefined, {} as RouterRoute], { id: 0, name: 1 }],\n      ],\n      ['123', 'key'],\n    ])\n\n    expect(req.param('id')).toBe('123')\n    expect(req.param('name')).toBe(undefined)\n\n    req.routeIndex = 1\n    expect(req.param('id')).toBe('123')\n    expect(req.param('name')).toBe('key')\n  })\n\n  test('req.param() without ParamStash', () => {\n    const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')\n    const req = new HonoRequest<'/:id/:name'>(rawRequest, '/123/key', [\n      [\n        [[undefined, {} as RouterRoute], { id: '123' }],\n        [[undefined, {} as RouterRoute], { id: '456', name: 'key' }],\n      ],\n    ])\n\n    expect(req.param('id')).toBe('123')\n    expect(req.param('name')).toBe(undefined)\n\n    req.routeIndex = 1\n    expect(req.param('id')).toBe('456')\n    expect(req.param('name')).toBe('key')\n  })\n\n  test('req.param() returns empty string for missing param', () => {\n    const rawRequest = new Request('http://localhost')\n    const req = new HonoRequest<'/:remaining'>(rawRequest, '/', [\n      [[[undefined, {} as RouterRoute], { remaining: 0 }]],\n      [''],\n    ])\n    expect(req.param('remaining')).toBe('')\n  })\n\n  test('req.param() without argument returns object with empty string for missing param', () => {\n    const rawRequest = new Request('http://localhost')\n    const req = new HonoRequest<'/:remaining'>(rawRequest, '/', [\n      [[[undefined, {} as RouterRoute], { remaining: 0 }]],\n      [''],\n    ])\n    expect(req.param()).toEqual({ remaining: '' })\n  })\n\n  describe('Type', () => {\n    it('param() returns string | undefined when P is any (middleware context)', () => {\n      // When middleware uses Context without an explicit path type, P defaults to any.\n      // param(key) should return string | undefined, not string.\n      const rawRequest = new Request('http://localhost/users/123')\n      const req = new HonoRequest<any>(rawRequest, '/users/123', [\n        [[[undefined, {} as RouterRoute], { id: '123' }]],\n      ])\n      expectTypeOf(req.param('id')).toEqualTypeOf<string | undefined>()\n    })\n\n    it('param() returns string when P is a known route string', () => {\n      // When P is a concrete route, named params should still return string (non-optional).\n      const rawRequest = new Request('http://localhost/123')\n      const req = new HonoRequest<'/:id'>(rawRequest, '/123', [\n        [[[undefined, {} as RouterRoute], { id: '123' }]],\n      ])\n      expectTypeOf(req.param('id')).toEqualTypeOf<string>()\n    })\n  })\n})\n\ndescribe('matchedRoutes', () => {\n  test('req.routePath', () => {\n    const handlerA = () => {}\n    const handlerB = () => {}\n    const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')\n    const req = new HonoRequest<'/:id/:name'>(rawRequest, '/123/key', [\n      [\n        [\n          [handlerA, { basePath: '/', handler: handlerA, method: 'GET', path: '/:id' }],\n          { id: '123' },\n        ],\n        [\n          [handlerA, { basePath: '/', handler: handlerB, method: 'GET', path: '/:id/:name' }],\n          { id: '456', name: 'key' },\n        ],\n      ],\n    ])\n\n    expect(req.matchedRoutes).toEqual([\n      { basePath: '/', handler: handlerA, method: 'GET', path: '/:id' },\n      { basePath: '/', handler: handlerB, method: 'GET', path: '/:id/:name' },\n    ])\n  })\n})\n\ndescribe('routePath', () => {\n  test('req.routePath', () => {\n    const handlerA = () => {}\n    const handlerB = () => {}\n    const rawRequest = new Request('http://localhost?page=2&tag=A&tag=B')\n    const req = new HonoRequest<'/:id/:name'>(rawRequest, '/123/key', [\n      [\n        [\n          [handlerA, { basePath: '/', handler: handlerA, method: 'GET', path: '/:id' }],\n          { id: '123' },\n        ],\n        [\n          [handlerA, { basePath: '/', handler: handlerB, method: 'GET', path: '/:id/:name' }],\n          { id: '456', name: 'key' },\n        ],\n      ],\n    ])\n\n    expect(req.routePath).toBe('/:id')\n\n    req.routeIndex = 1\n    expect(req.routePath).toBe('/:id/:name')\n  })\n})\n\ndescribe('req.addValidatedData() and req.data()', () => {\n  const rawRequest = new Request('http://localhost')\n\n  const payload = {\n    title: 'hello',\n    author: {\n      name: 'young man',\n      age: 20,\n    },\n  }\n\n  test('add data - json', () => {\n    const req = new HonoRequest<'/', { json: typeof payload }>(rawRequest)\n    req.addValidatedData('json', payload)\n    const data = req.valid('json')\n    expect(data).toEqual(payload)\n  })\n\n  test('replace data - json', () => {\n    const req = new HonoRequest<'/', { json: typeof payload }>(rawRequest)\n    req.addValidatedData('json', payload)\n    req.addValidatedData('json', {\n      tag: ['sport', 'music'],\n      author: {\n        tall: 170,\n      },\n    })\n    const data = req.valid('json')\n    expect(data).toEqual({\n      author: {\n        tall: 170,\n      },\n      tag: ['sport', 'music'],\n    })\n  })\n})\n\ndescribe('headers', () => {\n  test('empty string is a valid header value', () => {\n    const req = new HonoRequest(new Request('http://localhost', { headers: { foo: '' } }))\n    const foo = req.header('foo')\n    expect(foo).toEqual('')\n  })\n\n  test('Keys of the arguments for req.header() are not case-sensitive', () => {\n    const req = new HonoRequest(\n      new Request('http://localhost', {\n        headers: {\n          'Content-Type': 'application/json',\n          apikey: 'abc',\n          lowercase: 'lowercase value',\n        },\n      })\n    )\n    expect(req.header('Content-Type')).toBe('application/json')\n    expect(req.header('ApiKey')).toBe('abc')\n  })\n})\n\nconst text = '{\"foo\":\"bar\"}'\nconst json = { foo: 'bar' }\nconst buffer = new TextEncoder().encode('{\"foo\":\"bar\"}').buffer\n\ndescribe('Body methods with caching', () => {\n  test('req.text()', async () => {\n    const req = new HonoRequest(\n      new Request('http://localhost', {\n        method: 'POST',\n        body: text,\n      })\n    )\n    expect(await req.text()).toEqual(text)\n    expect(await req.json()).toEqual(json)\n    expect(await req.arrayBuffer()).toEqual(buffer)\n    expect(await req.blob()).toEqual(\n      new Blob([text], {\n        type: 'text/plain;charset=utf-8',\n      })\n    )\n  })\n\n  test('req.json()', async () => {\n    const req = new HonoRequest(\n      new Request('http://localhost', {\n        method: 'POST',\n        body: '{\"foo\":\"bar\"}',\n      })\n    )\n    expect(await req.json()).toEqual(json)\n    expect(await req.text()).toEqual(text)\n    expect(await req.arrayBuffer()).toEqual(buffer)\n    expect(await req.blob()).toEqual(\n      new Blob([text], {\n        type: 'text/plain;charset=utf-8',\n      })\n    )\n  })\n\n  test('req.json() should keep the content as is', async () => {\n    const text = '{ \"foo\" : \"bar\" }'\n    const req = new HonoRequest(\n      new Request('http://localhost', {\n        method: 'POST',\n        body: text,\n      })\n    )\n    expect(await req.json()).toEqual(JSON.parse(text))\n    expect(await req.text()).toEqual(text)\n  })\n\n  test('req.arrayBuffer()', async () => {\n    const buffer = new TextEncoder().encode('{\"foo\":\"bar\"}').buffer\n    const req = new HonoRequest(\n      new Request('http://localhost', {\n        method: 'POST',\n        body: buffer,\n      })\n    )\n    expect(await req.arrayBuffer()).toEqual(buffer)\n    expect(await req.text()).toEqual(text)\n    expect(await req.json()).toEqual(json)\n    expect(await req.blob()).toEqual(\n      new Blob([text], {\n        type: '',\n      })\n    )\n  })\n\n  test('req.blob()', async () => {\n    const blob = new Blob(['{\"foo\":\"bar\"}'], {\n      type: 'application/json',\n    })\n    const req = new HonoRequest(\n      new Request('http://localhost', {\n        method: 'POST',\n        body: blob,\n      })\n    )\n    expect(await req.blob()).toEqual(blob)\n    expect(await req.text()).toEqual(text)\n    expect(await req.json()).toEqual(json)\n    expect(await req.arrayBuffer()).toEqual(buffer)\n  })\n\n  test('req.formData()', async () => {\n    const data = new FormData()\n    data.append('foo', 'bar')\n    const req = new HonoRequest(\n      new Request('http://localhost', {\n        method: 'POST',\n        body: data,\n      })\n    )\n    expect((await req.formData()).get('foo')).toBe('bar')\n    expect(async () => await req.text()).not.toThrow()\n    expect(async () => await req.arrayBuffer()).not.toThrow()\n    expect(async () => await req.blob()).not.toThrow()\n  })\n\n  describe('req.parseBody()', async () => {\n    it('should parse form data', async () => {\n      const data = new FormData()\n      data.append('foo', 'bar')\n      const req = new HonoRequest(\n        new Request('http://localhost', {\n          method: 'POST',\n          body: data,\n        })\n      )\n      expect((await req.parseBody())['foo']).toBe('bar')\n      expect(async () => await req.text()).not.toThrow()\n      expect(async () => await req.arrayBuffer()).not.toThrow()\n      expect(async () => await req.blob()).not.toThrow()\n    })\n\n    describe('Return type', () => {\n      let req: HonoRequest\n      beforeEach(() => {\n        const data = new FormData()\n        data.append('foo', 'bar')\n        req = new HonoRequest(\n          new Request('http://localhost', {\n            method: 'POST',\n            body: data,\n          })\n        )\n      })\n\n      it('without options', async () => {\n        expectTypeOf((await req.parseBody())['key']).toEqualTypeOf<string | File>()\n      })\n\n      it('{all: true}', async () => {\n        expectTypeOf((await req.parseBody({ all: true }))['key']).toEqualTypeOf<\n          string | File | (string | File)[]\n        >()\n      })\n\n      it('{dot: true}', async () => {\n        expectTypeOf((await req.parseBody({ dot: true }))['key']).toEqualTypeOf<\n          string | File | RecursiveRecord<string, string | File>\n        >()\n      })\n\n      it('{all: true, dot: true}', async () => {\n        expectTypeOf((await req.parseBody({ all: true, dot: true }))['key']).toEqualTypeOf<\n          | string\n          | File\n          | (string | File)[]\n          | RecursiveRecord<string, string | File | (string | File)[]>\n        >()\n      })\n\n      it('specify return type explicitly', async () => {\n        expectTypeOf(\n          await req.parseBody<{ key1: string; key2: string }>({ all: true, dot: true })\n        ).toEqualTypeOf<{ key1: string; key2: string }>()\n      })\n    })\n  })\n})\n\ndescribe('cloneRawRequest', () => {\n  test('clones unconsumed request object', async () => {\n    const req = new HonoRequest(\n      new Request('http://localhost', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n          'X-Custom-Header': 'custom-value',\n        },\n        body: text,\n        cache: 'no-cache',\n        credentials: 'include',\n        integrity: 'sha256-test',\n        mode: 'cors',\n        redirect: 'follow',\n        referrer: 'http://example.com',\n        referrerPolicy: 'origin',\n      })\n    )\n\n    const clonedReq = await cloneRawRequest(req)\n\n    expect(clonedReq.method).toBe('POST')\n    expect(clonedReq.url).toBe('http://localhost/')\n    expect(await clonedReq.text()).toBe(text)\n    expect(clonedReq.headers.get('Content-Type')).toBe('application/json')\n    expect(clonedReq.headers.get('X-Custom-Header')).toBe('custom-value')\n    expect(clonedReq.cache).toBe('no-cache')\n    expect(clonedReq.credentials).toBe('include')\n    expect(clonedReq.integrity).toBe('sha256-test')\n    expect(clonedReq.mode).toBe('cors')\n    expect(clonedReq.redirect).toBe('follow')\n    expect(clonedReq.referrer).toBe('http://example.com/')\n    expect(clonedReq.referrerPolicy).toBe('origin')\n    expect(req.raw, 'cloned request should be a different object reference').not.toBe(clonedReq)\n    expect(req.raw, 'cloned request should contain the same properties').toMatchObject(clonedReq)\n  })\n\n  test('clones consumed request object', async () => {\n    const req = new HonoRequest(\n      new Request('http://localhost', {\n        method: 'POST',\n        headers: {\n          Authorization: 'Bearer token123',\n        },\n        body: text,\n        mode: 'same-origin',\n        credentials: 'same-origin',\n      })\n    )\n    await req.json()\n\n    const clonedReq = await cloneRawRequest(req)\n\n    expect(clonedReq.method).toBe('POST')\n    expect(clonedReq.url).toBe('http://localhost/')\n    expect(await clonedReq.json()).toEqual(json)\n    expect(clonedReq.headers.get('Authorization')).toBe('Bearer token123')\n    expect(clonedReq.mode).toBe('same-origin')\n    expect(clonedReq.credentials).toBe('same-origin')\n    expect(req.raw, 'cloned request should be a different object reference').not.toBe(clonedReq)\n    expect(req.raw, 'cloned request should contain the same properties').toMatchObject(clonedReq)\n  })\n\n  test('clones GET request without body', async () => {\n    const req = new HonoRequest(\n      new Request('http://localhost', {\n        method: 'GET',\n        headers: {\n          'User-Agent': 'test-agent',\n        },\n        cache: 'default',\n        redirect: 'manual',\n        referrerPolicy: 'no-referrer',\n      })\n    )\n\n    const clonedReq = await cloneRawRequest(req)\n\n    expect(clonedReq.method).toBe('GET')\n    expect(clonedReq.url).toBe('http://localhost/')\n    expect(clonedReq.headers.get('User-Agent')).toBe('test-agent')\n    expect(clonedReq.cache).toBe('default')\n    expect(clonedReq.redirect).toBe('manual')\n    expect(clonedReq.referrerPolicy).toBe('no-referrer')\n    expect(req.raw, 'cloned request should be a different object reference').not.toBe(clonedReq)\n    expect(req.raw, 'cloned request should contain the same properties').toMatchObject(clonedReq)\n  })\n\n  test('clones request when raw body was consumed directly without HonoRequest methods', async () => {\n    const req = new HonoRequest(\n      new Request('http://localhost', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n        body: text,\n      })\n    )\n\n    // Consume the raw request body directly, bypassing HonoRequest methods\n    // This means bodyCache will be empty\n    await req.raw.text()\n\n    expect(req.raw.bodyUsed).toBe(true)\n    expect(Object.keys(req.bodyCache).length).toBe(0)\n\n    let error: HTTPException | undefined = undefined\n    try {\n      await cloneRawRequest(req)\n    } catch (e) {\n      expect(e).toBeInstanceOf(HTTPException)\n      error = e as HTTPException\n    }\n\n    expect(error).not.toBeUndefined()\n    expect((error as HTTPException).status).toBe(500)\n    expect((error as HTTPException).message).toContain(\n      'Cannot clone request: body was already consumed and not cached'\n    )\n  })\n})\n"
  },
  {
    "path": "src/request.ts",
    "content": "/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { HTTPException } from './http-exception'\nimport { GET_MATCH_RESULT } from './request/constants'\nimport type { Result } from './router'\nimport type {\n  Input,\n  InputToDataByTarget,\n  ParamKeyToRecord,\n  ParamKeys,\n  RemoveQuestion,\n  RouterRoute,\n  ValidationTargets,\n} from './types'\nimport { parseBody } from './utils/body'\nimport type { BodyData, ParseBodyOptions } from './utils/body'\nimport type { CustomHeader, RequestHeader } from './utils/headers'\nimport type { Simplify, UnionToIntersection } from './utils/types'\nimport { decodeURIComponent_, getQueryParam, getQueryParams, tryDecode } from './utils/url'\n\ntype Body = {\n  json: any\n  text: string\n  arrayBuffer: ArrayBuffer\n  blob: Blob\n  formData: FormData\n}\ntype BodyCache = Partial<Body & { parsedBody: BodyData }>\n\ntype OptionalRequestInitProperties = 'window' | 'priority'\ntype RequiredRequestInit = Required<Omit<RequestInit, OptionalRequestInitProperties>> & {\n  [Key in OptionalRequestInitProperties]?: RequestInit[Key]\n}\n\nconst tryDecodeURIComponent = (str: string) => tryDecode(str, decodeURIComponent_)\n\nexport class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {\n  /**\n   * `.raw` can get the raw Request object.\n   *\n   * @see {@link https://hono.dev/docs/api/request#raw}\n   *\n   * @example\n   * ```ts\n   * // For Cloudflare Workers\n   * app.post('/', async (c) => {\n   *   const metadata = c.req.raw.cf?.hostMetadata?\n   *   ...\n   * })\n   * ```\n   */\n  raw: Request\n\n  #validatedData: { [K in keyof ValidationTargets]?: {} } // Short name of validatedData\n  #matchResult: Result<[unknown, RouterRoute]>\n  routeIndex: number = 0\n  /**\n   * `.path` can get the pathname of the request.\n   *\n   * @see {@link https://hono.dev/docs/api/request#path}\n   *\n   * @example\n   * ```ts\n   * app.get('/about/me', (c) => {\n   *   const pathname = c.req.path // `/about/me`\n   * })\n   * ```\n   */\n  path: string\n  bodyCache: BodyCache = {}\n\n  constructor(\n    request: Request,\n    path: string = '/',\n    matchResult: Result<[unknown, RouterRoute]> = [[]]\n  ) {\n    this.raw = request\n    this.path = path\n    this.#matchResult = matchResult\n    this.#validatedData = {}\n  }\n\n  /**\n   * `.req.param()` gets the path parameters.\n   *\n   * @see {@link https://hono.dev/docs/api/routing#path-parameter}\n   *\n   * @example\n   * ```ts\n   * const name = c.req.param('name')\n   * // or all parameters at once\n   * const { id, comment_id } = c.req.param()\n   * ```\n   */\n  param<P2 extends ParamKeys<P> = ParamKeys<P>>(\n    key: string extends P ? never : P2 extends `${infer _}?` ? never : P2\n  ): string\n  param<P2 extends RemoveQuestion<ParamKeys<P>> = RemoveQuestion<ParamKeys<P>>>(\n    key: P2\n  ): string | undefined\n  param(key: string): string | undefined\n  param<P2 extends string = P>(): Simplify<UnionToIntersection<ParamKeyToRecord<ParamKeys<P2>>>>\n  param(key?: string): unknown {\n    return key ? this.#getDecodedParam(key) : this.#getAllDecodedParams()\n  }\n\n  #getDecodedParam(key: string): string | undefined {\n    const paramKey = this.#matchResult[0][this.routeIndex][1][key]\n    const param = this.#getParamValue(paramKey)\n    return param && /\\%/.test(param) ? tryDecodeURIComponent(param) : param\n  }\n\n  #getAllDecodedParams(): Record<string, string> {\n    const decoded: Record<string, string> = {}\n\n    const keys = Object.keys(this.#matchResult[0][this.routeIndex][1])\n    for (const key of keys) {\n      const value = this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key])\n      if (value !== undefined) {\n        decoded[key] = /\\%/.test(value) ? tryDecodeURIComponent(value) : value\n      }\n    }\n\n    return decoded\n  }\n\n  #getParamValue(paramKey: any): string | undefined {\n    return this.#matchResult[1] ? this.#matchResult[1][paramKey as any] : paramKey\n  }\n\n  /**\n   * `.query()` can get querystring parameters.\n   *\n   * @see {@link https://hono.dev/docs/api/request#query}\n   *\n   * @example\n   * ```ts\n   * // Query params\n   * app.get('/search', (c) => {\n   *   const query = c.req.query('q')\n   * })\n   *\n   * // Get all params at once\n   * app.get('/search', (c) => {\n   *   const { q, limit, offset } = c.req.query()\n   * })\n   * ```\n   */\n  query(key: string): string | undefined\n  query(): Record<string, string>\n  query(key?: string) {\n    return getQueryParam(this.url, key)\n  }\n\n  /**\n   * `.queries()` can get multiple querystring parameter values, e.g. /search?tags=A&tags=B\n   *\n   * @see {@link https://hono.dev/docs/api/request#queries}\n   *\n   * @example\n   * ```ts\n   * app.get('/search', (c) => {\n   *   // tags will be string[]\n   *   const tags = c.req.queries('tags')\n   * })\n   * ```\n   */\n  queries(key: string): string[] | undefined\n  queries(): Record<string, string[]>\n  queries(key?: string) {\n    return getQueryParams(this.url, key)\n  }\n\n  /**\n   * `.header()` can get the request header value.\n   *\n   * @see {@link https://hono.dev/docs/api/request#header}\n   *\n   * @example\n   * ```ts\n   * app.get('/', (c) => {\n   *   const userAgent = c.req.header('User-Agent')\n   * })\n   * ```\n   */\n  header(name: RequestHeader): string | undefined\n  header(name: string): string | undefined\n  header(): Record<RequestHeader | (string & CustomHeader), string>\n  header(name?: string) {\n    if (name) {\n      return this.raw.headers.get(name) ?? undefined\n    }\n\n    const headerData: Record<string, string | undefined> = {}\n    this.raw.headers.forEach((value, key) => {\n      headerData[key] = value\n    })\n    return headerData\n  }\n\n  /**\n   * `.parseBody()` can parse Request body of type `multipart/form-data` or `application/x-www-form-urlencoded`\n   *\n   * @see {@link https://hono.dev/docs/api/request#parsebody}\n   *\n   * @example\n   * ```ts\n   * app.post('/entry', async (c) => {\n   *   const body = await c.req.parseBody()\n   * })\n   * ```\n   */\n  async parseBody<Options extends Partial<ParseBodyOptions>, T extends BodyData<Options>>(\n    options?: Options\n  ): Promise<T>\n  async parseBody<T extends BodyData>(options?: Partial<ParseBodyOptions>): Promise<T>\n  async parseBody(options?: Partial<ParseBodyOptions>) {\n    return (this.bodyCache.parsedBody ??= await parseBody(this, options))\n  }\n\n  #cachedBody = (key: keyof Body) => {\n    const { bodyCache, raw } = this\n    const cachedBody = bodyCache[key]\n\n    if (cachedBody) {\n      return cachedBody\n    }\n\n    const anyCachedKey = Object.keys(bodyCache)[0]\n    if (anyCachedKey) {\n      return (bodyCache[anyCachedKey as keyof Body] as Promise<BodyInit>).then((body) => {\n        if (anyCachedKey === 'json') {\n          body = JSON.stringify(body)\n        }\n        return new Response(body)[key]()\n      })\n    }\n\n    return (bodyCache[key] = raw[key]())\n  }\n\n  /**\n   * `.json()` can parse Request body of type `application/json`\n   *\n   * @see {@link https://hono.dev/docs/api/request#json}\n   *\n   * @example\n   * ```ts\n   * app.post('/entry', async (c) => {\n   *   const body = await c.req.json()\n   * })\n   * ```\n   */\n  json<T = any>(): Promise<T> {\n    return this.#cachedBody('text').then((text: string) => JSON.parse(text))\n  }\n\n  /**\n   * `.text()` can parse Request body of type `text/plain`\n   *\n   * @see {@link https://hono.dev/docs/api/request#text}\n   *\n   * @example\n   * ```ts\n   * app.post('/entry', async (c) => {\n   *   const body = await c.req.text()\n   * })\n   * ```\n   */\n  text(): Promise<string> {\n    return this.#cachedBody('text')\n  }\n\n  /**\n   * `.arrayBuffer()` parse Request body as an `ArrayBuffer`\n   *\n   * @see {@link https://hono.dev/docs/api/request#arraybuffer}\n   *\n   * @example\n   * ```ts\n   * app.post('/entry', async (c) => {\n   *   const body = await c.req.arrayBuffer()\n   * })\n   * ```\n   */\n  arrayBuffer(): Promise<ArrayBuffer> {\n    return this.#cachedBody('arrayBuffer')\n  }\n\n  /**\n   * Parses the request body as a `Blob`.\n   * @example\n   * ```ts\n   * app.post('/entry', async (c) => {\n   *   const body = await c.req.blob();\n   * });\n   * ```\n   * @see https://hono.dev/docs/api/request#blob\n   */\n  blob(): Promise<Blob> {\n    return this.#cachedBody('blob')\n  }\n\n  /**\n   * Parses the request body as `FormData`.\n   * @example\n   * ```ts\n   * app.post('/entry', async (c) => {\n   *   const body = await c.req.formData();\n   * });\n   * ```\n   * @see https://hono.dev/docs/api/request#formdata\n   */\n  formData(): Promise<FormData> {\n    return this.#cachedBody('formData')\n  }\n\n  /**\n   * Adds validated data to the request.\n   *\n   * @param target - The target of the validation.\n   * @param data - The validated data to add.\n   */\n  addValidatedData(target: keyof ValidationTargets, data: {}) {\n    this.#validatedData[target] = data\n  }\n\n  /**\n   * Gets validated data from the request.\n   *\n   * @param target - The target of the validation.\n   * @returns The validated data.\n   *\n   * @see https://hono.dev/docs/api/request#valid\n   */\n  valid<T extends keyof I & keyof ValidationTargets>(target: T): InputToDataByTarget<I, T>\n  valid(target: keyof ValidationTargets) {\n    return this.#validatedData[target] as unknown\n  }\n\n  /**\n   * `.url()` can get the request url strings.\n   *\n   * @see {@link https://hono.dev/docs/api/request#url}\n   *\n   * @example\n   * ```ts\n   * app.get('/about/me', (c) => {\n   *   const url = c.req.url // `http://localhost:8787/about/me`\n   *   ...\n   * })\n   * ```\n   */\n  get url(): string {\n    return this.raw.url\n  }\n\n  /**\n   * `.method()` can get the method name of the request.\n   *\n   * @see {@link https://hono.dev/docs/api/request#method}\n   *\n   * @example\n   * ```ts\n   * app.get('/about/me', (c) => {\n   *   const method = c.req.method // `GET`\n   * })\n   * ```\n   */\n  get method(): string {\n    return this.raw.method\n  }\n\n  get [GET_MATCH_RESULT](): Result<[unknown, RouterRoute]> {\n    return this.#matchResult\n  }\n\n  /**\n   * `.matchedRoutes()` can return a matched route in the handler\n   *\n   * @deprecated\n   *\n   * Use matchedRoutes helper defined in \"hono/route\" instead.\n   *\n   * @see {@link https://hono.dev/docs/api/request#matchedroutes}\n   *\n   * @example\n   * ```ts\n   * app.use('*', async function logger(c, next) {\n   *   await next()\n   *   c.req.matchedRoutes.forEach(({ handler, method, path }, i) => {\n   *     const name = handler.name || (handler.length < 2 ? '[handler]' : '[middleware]')\n   *     console.log(\n   *       method,\n   *       ' ',\n   *       path,\n   *       ' '.repeat(Math.max(10 - path.length, 0)),\n   *       name,\n   *       i === c.req.routeIndex ? '<- respond from here' : ''\n   *     )\n   *   })\n   * })\n   * ```\n   */\n  get matchedRoutes(): RouterRoute[] {\n    return this.#matchResult[0].map(([[, route]]) => route)\n  }\n\n  /**\n   * `routePath()` can retrieve the path registered within the handler\n   *\n   * @deprecated\n   *\n   * Use routePath helper defined in \"hono/route\" instead.\n   *\n   * @see {@link https://hono.dev/docs/api/request#routepath}\n   *\n   * @example\n   * ```ts\n   * app.get('/posts/:id', (c) => {\n   *   return c.json({ path: c.req.routePath })\n   * })\n   * ```\n   */\n  get routePath(): string {\n    return this.#matchResult[0].map(([[, route]]) => route)[this.routeIndex].path\n  }\n}\n\n/**\n * Clones a HonoRequest's underlying raw Request object.\n *\n * This utility handles both consumed and unconsumed request bodies:\n * - If the request body hasn't been consumed, it uses the native `clone()` method\n * - If the request body has been consumed, it reconstructs a new Request using cached body data\n *\n * This is particularly useful when you need to:\n * - Process the same request body multiple times\n * - Pass requests to external services after validation\n *\n * @param req - The HonoRequest object to clone\n * @returns A Promise that resolves to a new Request object with the same properties\n * @throws {HTTPException} If the request body was consumed directly via `req.raw`\n *   without using HonoRequest methods (e.g., `req.json()`, `req.text()`), making it\n *   impossible to reconstruct the body from cache\n *\n * @example\n * ```ts\n * // Clone after consuming the body (e.g., after validation)\n * app.post('/forward',\n *   validator('json', (data) => data),\n *   async (c) => {\n *     const validated = c.req.valid('json')\n *     // Body has been consumed, but cloneRawRequest still works\n *     const clonedReq = await cloneRawRequest(c.req)\n *     return fetch('http://backend-service.com', clonedReq)\n *   }\n * )\n * ```\n */\nexport const cloneRawRequest = async (req: HonoRequest): Promise<Request> => {\n  if (!req.raw.bodyUsed) {\n    return req.raw.clone()\n  }\n\n  const cacheKey = (Object.keys(req.bodyCache) as Array<keyof Body>)[0]\n  if (!cacheKey) {\n    throw new HTTPException(500, {\n      message:\n        'Cannot clone request: body was already consumed and not cached. Please use HonoRequest methods (e.g., req.json(), req.text()) instead of consuming req.raw directly.',\n    })\n  }\n\n  const requestInit: RequiredRequestInit = {\n    body: await req[cacheKey](),\n    cache: req.raw.cache,\n    credentials: req.raw.credentials,\n    headers: req.header(),\n    integrity: req.raw.integrity,\n    keepalive: req.raw.keepalive,\n    method: req.method,\n    mode: req.raw.mode,\n    redirect: req.raw.redirect,\n    referrer: req.raw.referrer,\n    referrerPolicy: req.raw.referrerPolicy,\n    signal: req.raw.signal,\n  }\n\n  return new Request(req.url, requestInit)\n}\n"
  },
  {
    "path": "src/router/common.case.test.ts",
    "content": "import type { RunnerTestSuite } from 'vitest'\nimport type { ParamIndexMap, Params, Router } from '../router'\n\nconst getSuiteHierarchy = (suite?: RunnerTestSuite) => {\n  const res: RunnerTestSuite[] = []\n  let s: RunnerTestSuite | undefined = suite\n  while (s) {\n    res.unshift(s)\n    s = s.suite\n  }\n  return res\n}\n\nexport const runTest = ({\n  skip = [],\n  newRouter,\n}: {\n  skip?: {\n    reason: string\n    tests: string[]\n  }[]\n  newRouter: <T>() => Router<T>\n}) => {\n  describe('Common', () => {\n    type Match = (method: string, path: string) => { handler: string; params: Params }[]\n    let router: Router<string>\n    let match: Match\n\n    beforeEach(({ task, skip: skipTask }) => {\n      const suites = getSuiteHierarchy(task.suite)\n      const name = [...suites.slice(2).map((s) => s.name), task.name].join(' > ')\n      const isSkip = skip.find((s) => s.tests.includes(name))\n      if (isSkip) {\n        console.log(`Skip: ${isSkip.reason}`)\n        skipTask()\n        return\n      }\n\n      router = newRouter()\n      match = (method: string, path: string) => {\n        const [matchRes, stash] = router.match(method, path)\n        const res = matchRes.map((r) =>\n          stash\n            ? {\n                handler: r[0],\n                params: Object.keys(r[1]).reduce(\n                  (acc, key) => {\n                    acc[key] = stash[(r[1] as ParamIndexMap)[key]]\n                    return acc\n                  },\n                  Object.create(null) as Params\n                ),\n              }\n            : { handler: r[0], params: r[1] as Params }\n        )\n        return res\n      }\n    })\n\n    describe('Basic Usage', () => {\n      beforeEach(() => {\n        router.add('GET', '/hello', 'get hello')\n        router.add('POST', '/hello', 'post hello')\n        router.add('PURGE', '/hello', 'purge hello')\n      })\n\n      it('GET, post hello', async () => {\n        let res = match('GET', '/hello')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('get hello')\n        res = match('POST', '/hello')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('post hello')\n        res = match('PURGE', '/hello')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('purge hello')\n        res = match('PUT', '/hello')\n        expect(res.length).toBe(0)\n        res = match('GET', '/')\n        expect(res.length).toBe(0)\n      })\n    })\n\n    describe('Reserved words', () => {\n      it('Reserved words and named parameter', async () => {\n        router.add('GET', '/entry/:constructor', 'get entry')\n        const res = match('GET', '/entry/123')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('get entry')\n        expect(res[0].params['constructor']).toBe('123')\n      })\n\n      it('Reserved words and wildcard', async () => {\n        router.add('GET', '/wild/*/card', 'get wildcard')\n        const res = match('GET', '/wild/constructor/card')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('get wildcard')\n      })\n\n      it('Reserved words and optional named parameter', async () => {\n        router.add('GET', '/api/animals/:constructor?', 'animals')\n        const res = match('GET', '/api/animals')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('animals')\n        expect(res[0].params['constructor']).toBeUndefined()\n      })\n    })\n\n    describe('Complex', () => {\n      it('Named Param', async () => {\n        router.add('GET', '/entry/:id', 'get entry')\n        let res = match('GET', '/entry/123')\n\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('get entry')\n        expect(res[0].params['id']).toBe('123')\n\n        res = match('GET', '/entry-123')\n        expect(res.length).toBe(0)\n      })\n\n      it('Wildcard', async () => {\n        router.add('GET', '/wild/*/card', 'get wildcard')\n        const res = match('GET', '/wild/xxx/card')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('get wildcard')\n      })\n\n      it('Default', async () => {\n        router.add('GET', '/api/abc', 'get api')\n        router.add('GET', '/api/*', 'fallback')\n        let res = match('GET', '/api/abc')\n        expect(res.length).toBe(2)\n        expect(res[0].handler).toEqual('get api')\n        expect(res[1].handler).toEqual('fallback')\n        res = match('GET', '/api/def')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('fallback')\n      })\n\n      it('Regexp', async () => {\n        router.add('GET', '/post/:date{[0-9]+}/:title{[a-z]+}', 'get post')\n        let res = match('GET', '/post/20210101/hello')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('get post')\n        expect(res[0].params['date']).toBe('20210101')\n        expect(res[0].params['title']).toBe('hello')\n        res = match('GET', '/post/onetwothree')\n        expect(res.length).toBe(0)\n        res = match('GET', '/post/123/123')\n        expect(res.length).toBe(0)\n      })\n\n      it('Parameter with {.*} regexp', () => {\n        router.add('GET', '/files/:name{.*}', 'file')\n        let res = match('GET', '/files')\n        expect(res.length).toBe(0)\n\n        res = match('GET', '/files/a')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('file')\n        expect(res[0].params['name']).toEqual('a')\n\n        res = match('GET', '/files/a/b')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('file')\n        expect(res[0].params['name']).toEqual('a/b')\n\n        res = match('GET', '/files/')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('file')\n      })\n\n      it('/*', async () => {\n        router.add('GET', '/api/*', 'auth middleware')\n        router.add('GET', '/api', 'top')\n        router.add('GET', '/api/posts', 'posts')\n        router.add('GET', '/api/*', 'fallback')\n\n        let res = match('GET', '/api')\n        expect(res.length).toBe(3)\n        expect(res[0].handler).toEqual('auth middleware')\n        expect(res[1].handler).toEqual('top')\n        expect(res[2].handler).toEqual('fallback')\n        res = match('GET', '/api/posts')\n        expect(res.length).toBe(3)\n        expect(res[0].handler).toEqual('auth middleware')\n        expect(res[1].handler).toEqual('posts')\n        expect(res[2].handler).toEqual('fallback')\n      })\n    })\n\n    describe('Registration order', () => {\n      it('middleware -> handler', async () => {\n        router.add('GET', '*', 'bar')\n        router.add('GET', '/:type/:action', 'foo')\n        const res = match('GET', '/posts/123')\n        expect(res.length).toBe(2)\n        expect(res[0].handler).toEqual('bar')\n        expect(res[1].handler).toEqual('foo')\n      })\n\n      it('handler -> fallback', async () => {\n        router.add('GET', '/:type/:action', 'foo')\n        router.add('GET', '*', 'fallback')\n        const res = match('GET', '/posts/123')\n        expect(res.length).toBe(2)\n        expect(res[0].handler).toEqual('foo')\n        expect(res[1].handler).toEqual('fallback')\n      })\n    })\n\n    describe('Multi match', () => {\n      describe('Blog', () => {\n        beforeEach(() => {\n          router.add('ALL', '*', 'middleware a')\n          router.add('GET', '*', 'middleware b')\n          router.add('GET', '/entry', 'get entries')\n          router.add('POST', '/entry/*', 'middleware c')\n          router.add('POST', '/entry', 'post entry')\n          router.add('GET', '/entry/:id', 'get entry')\n          router.add('GET', '/entry/:id/comment/:comment_id', 'get comment')\n        })\n\n        it('GET /', async () => {\n          const res = match('GET', '/')\n          expect(res.length).toBe(2)\n          expect(res[0].handler).toEqual('middleware a')\n          expect(res[1].handler).toEqual('middleware b')\n        })\n        it('GET /entry/123', async () => {\n          const res = match('GET', '/entry/123')\n          expect(res.length).toBe(3)\n          expect(res[0].handler).toEqual('middleware a')\n          expect(res[0].params['id']).toBe(undefined)\n          expect(res[1].handler).toEqual('middleware b')\n          expect(res[1].params['id']).toBe(undefined)\n          expect(res[2].handler).toEqual('get entry')\n          expect(res[2].params['id']).toBe('123')\n        })\n        it('GET /entry/123/comment/456', async () => {\n          const res = match('GET', '/entry/123/comment/456')\n          expect(res.length).toBe(3)\n          expect(res[0].handler).toEqual('middleware a')\n          expect(res[0].params['id']).toBe(undefined)\n          expect(res[0].params['comment_id']).toBe(undefined)\n          expect(res[1].handler).toEqual('middleware b')\n          expect(res[1].params['id']).toBe(undefined)\n          expect(res[1].params['comment_id']).toBe(undefined)\n          expect(res[2].handler).toEqual('get comment')\n          expect(res[2].params['id']).toBe('123')\n          expect(res[2].params['comment_id']).toBe('456')\n        })\n        it('POST /entry', async () => {\n          const res = match('POST', '/entry')\n          expect(res.length).toBe(3)\n          expect(res[0].handler).toEqual('middleware a')\n          expect(res[1].handler).toEqual('middleware c')\n          expect(res[2].handler).toEqual('post entry')\n        })\n        it('DELETE /entry', async () => {\n          const res = match('DELETE', '/entry')\n          expect(res.length).toBe(1)\n          expect(res[0].handler).toEqual('middleware a')\n        })\n      })\n\n      describe('`params` per a handler', () => {\n        beforeEach(() => {\n          router.add('ALL', '*', 'middleware a')\n          router.add('GET', '/entry/:id/*', 'middleware b')\n          router.add('GET', '/entry/:id/:action', 'action')\n        })\n\n        it('GET /entry/123/show', async () => {\n          const res = match('GET', '/entry/123/show')\n          expect(res.length).toBe(3)\n          expect(res[0].handler).toEqual('middleware a')\n          expect(res[0].params['id']).toBe(undefined)\n          expect(res[0].params['action']).toBe(undefined)\n          expect(res[1].handler).toEqual('middleware b')\n          expect(res[1].params['id']).toBe('123')\n          expect(res[1].params['comment_id']).toBe(undefined)\n          expect(res[2].handler).toEqual('action')\n          expect(res[2].params['id']).toBe('123')\n          expect(res[2].params['action']).toBe('show')\n        })\n      })\n\n      it('hierarchy', () => {\n        router.add('GET', '/posts/:id/comments/:comment_id', 'foo')\n        router.add('GET', '/posts/:id', 'bar')\n        expect(() => {\n          router.match('GET', '/')\n        }).not.toThrow()\n      })\n    })\n\n    describe('Duplicate param name', () => {\n      it('self', () => {\n        router.add('GET', '/:id/:id', 'foo')\n        const res = match('GET', '/123/456')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('foo')\n        expect(res[0].params['id']).toBe('123')\n      })\n\n      it('parent', () => {\n        router.add('GET', '/:id/:action', 'foo')\n        router.add('GET', '/posts/:id', 'bar')\n        const res = match('GET', '/posts/get')\n        expect(res.length).toBe(2)\n        expect(res[0].handler).toEqual('foo')\n        expect(res[0].params['id']).toBe('posts')\n        expect(res[0].params['action']).toBe('get')\n        expect(res[1].handler).toEqual('bar')\n        expect(res[1].params['id']).toBe('get')\n      })\n\n      it('child', () => {\n        router.add('GET', '/posts/:id', 'foo')\n        router.add('GET', '/:id/:action', 'bar')\n        const res = match('GET', '/posts/get')\n        expect(res.length).toBe(2)\n        expect(res[0].handler).toEqual('foo')\n        expect(res[0].params['id']).toBe('get')\n        expect(res[1].handler).toEqual('bar')\n        expect(res[1].params['id']).toBe('posts')\n        expect(res[1].params['action']).toBe('get')\n      })\n    })\n\n    describe('page', () => {\n      it('GET /page', async () => {\n        router.add('GET', '/page', 'page')\n        router.add('ALL', '*', 'fallback') // or '*'\n\n        const res = match('GET', '/page')\n        expect(res.length).toBe(2)\n        expect(res[0].handler).toEqual('page')\n        expect(res[1].handler).toEqual('fallback')\n      })\n    })\n\n    describe('star', () => {\n      beforeEach(() => {\n        router.add('GET', '/', '/')\n        router.add('GET', '/*', '/*')\n        router.add('GET', '*', '*')\n\n        router.add('GET', '/x', '/x')\n        router.add('GET', '/x/*', '/x/*')\n      })\n\n      it('top', async () => {\n        const res = match('GET', '/')\n        expect(res.length).toBe(3)\n        expect(res[0].handler).toEqual('/')\n        expect(res[1].handler).toEqual('/*')\n        expect(res[2].handler).toEqual('*')\n      })\n\n      it('Under a certain path', async () => {\n        const res = match('GET', '/x')\n        expect(res.length).toBe(4)\n        expect(res[0].handler).toEqual('/*')\n        expect(res[1].handler).toEqual('*')\n        expect(res[2].handler).toEqual('/x')\n        expect(res[3].handler).toEqual('/x/*')\n      })\n    })\n\n    describe('Optional route', () => {\n      beforeEach(() => {\n        router.add('GET', '/api/animals/:type?', 'animals')\n        router.add('GET', '/v1/:version?/:platform?', 'result')\n      })\n\n      it('GET /api/animals/dog', async () => {\n        const res = match('GET', '/api/animals/dog')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('animals')\n        expect(res[0].params['type']).toBe('dog')\n      })\n      it('GET /api/animals', async () => {\n        const res = match('GET', '/api/animals')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('animals')\n        expect(res[0].params['type']).toBeUndefined()\n      })\n      it('GET /v1/123/abc', () => {\n        const res = match('GET', '/v1/123/abc')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('result')\n        expect(res[0].params['version']).toBe('123')\n        expect(res[0].params['platform']).toBe('abc')\n      })\n      it('GET /v1/123', () => {\n        const res = match('GET', '/v1/123')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('result')\n        expect(res[0].params['version']).toBe('123')\n        expect(res[0].params['platform']).toBeUndefined()\n      })\n      it('GET /v1', () => {\n        const res = match('GET', '/v1')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('result')\n        expect(res[0].params['version']).toBeUndefined()\n        expect(res[0].params['platform']).toBeUndefined()\n      })\n    })\n\n    describe('All', () => {\n      beforeEach(() => {\n        router.add('GET', '/hello', 'get hello')\n        router.add('ALL', '/all', 'get all')\n      })\n\n      it('GET, all hello', async () => {\n        const res = match('GET', '/all')\n        expect(res.length).toBe(1)\n      })\n    })\n\n    describe('long prefix, then star', () => {\n      describe('GET only', () => {\n        beforeEach(() => {\n          router.add('GET', '/long/prefix/*', 'long-prefix')\n          router.add('GET', '/long/*', 'long')\n          router.add('GET', '*', 'star1')\n          router.add('GET', '*', 'star2')\n        })\n\n        it('GET /', () => {\n          const res = match('GET', '/')\n          expect(res.length).toBe(2)\n          expect(res[0].handler).toEqual('star1')\n          expect(res[1].handler).toEqual('star2')\n        })\n\n        it('GET /long/prefix', () => {\n          const res = match('GET', '/long/prefix')\n          expect(res.length).toBe(4)\n          expect(res[0].handler).toEqual('long-prefix')\n          expect(res[1].handler).toEqual('long')\n          expect(res[2].handler).toEqual('star1')\n          expect(res[3].handler).toEqual('star2')\n        })\n\n        it('GET /long/prefix/test', () => {\n          const res = match('GET', '/long/prefix/test')\n          expect(res.length).toBe(4)\n          expect(res[0].handler).toEqual('long-prefix')\n          expect(res[1].handler).toEqual('long')\n          expect(res[2].handler).toEqual('star1')\n          expect(res[3].handler).toEqual('star2')\n        })\n      })\n\n      describe('ALL and GET', () => {\n        beforeEach(() => {\n          router.add('ALL', '/long/prefix/*', 'long-prefix')\n          router.add('ALL', '/long/*', 'long')\n          router.add('GET', '*', 'star1')\n          router.add('GET', '*', 'star2')\n        })\n\n        it('GET /', () => {\n          const res = match('GET', '/')\n          expect(res.length).toBe(2)\n          expect(res[0].handler).toEqual('star1')\n          expect(res[1].handler).toEqual('star2')\n        })\n\n        it('GET /long/prefix', () => {\n          const res = match('GET', '/long/prefix')\n          expect(res.length).toBe(4)\n          expect(res[0].handler).toEqual('long-prefix')\n          expect(res[1].handler).toEqual('long')\n          expect(res[2].handler).toEqual('star1')\n          expect(res[3].handler).toEqual('star2')\n        })\n\n        it('GET /long/prefix/test', () => {\n          const res = match('GET', '/long/prefix/test')\n          expect(res.length).toBe(4)\n          expect(res[0].handler).toEqual('long-prefix')\n          expect(res[1].handler).toEqual('long')\n          expect(res[2].handler).toEqual('star1')\n          expect(res[3].handler).toEqual('star2')\n        })\n      })\n    })\n\n    describe('Including slashes', () => {\n      beforeEach(() => {\n        router.add('GET', '/js/:filename{[a-z0-9/]+.js}', 'any file')\n      })\n\n      it('GET /js/main.js', () => {\n        router.add('GET', '/js/main.js', 'main.js')\n\n        const res = match('GET', '/js/main.js')\n        expect(res.length).toBe(2)\n        expect(res[0].handler).toEqual('any file')\n        expect(res[0].params['filename']).toEqual('main.js')\n        expect(res[1].handler).toEqual('main.js')\n        expect(res[1].params['filename']).toEqual(undefined)\n      })\n\n      it('GET /js/chunk/123.js', () => {\n        const res = match('GET', '/js/chunk/123.js')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('any file')\n        expect(res[0].params['filename']).toEqual('chunk/123.js')\n      })\n\n      it('GET /js/chunk/nest/123.js', () => {\n        const res = match('GET', '/js/chunk/nest/123.js')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('any file')\n        expect(res[0].params['filename']).toEqual('chunk/nest/123.js')\n      })\n    })\n\n    describe('Capture simple multiple directories', () => {\n      beforeEach(() => {\n        router.add('GET', '/:dirs{.+}/file.html', 'file.html')\n      })\n\n      it('GET /foo/bar/file.html', () => {\n        const res = match('GET', '/foo/bar/file.html')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('file.html')\n        expect(res[0].params['dirs']).toEqual('foo/bar')\n      })\n    })\n\n    describe('Capture regex pattern has trailing wildcard', () => {\n      beforeEach(() => {\n        router.add('GET', '/:dir{[a-z]+}/*/file.html', 'file.html')\n      })\n\n      it('GET /foo/bar/file.html', () => {\n        const res = match('GET', '/foo/bar/file.html')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('file.html')\n        expect(res[0].params['dir']).toEqual('foo')\n      })\n    })\n\n    describe('Capture complex multiple directories', () => {\n      beforeEach(() => {\n        router.add('GET', '/:first{.+}/middle-a/:reference?', '1')\n        router.add('GET', '/:first{.+}/middle-b/end-c/:uuid', '2')\n        router.add('GET', '/:first{.+}/middle-b/:digest', '3')\n      })\n\n      it('GET /part1/middle-b/latest', () => {\n        const res = match('GET', '/part1/middle-b/latest')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('3')\n        expect(res[0].params['first']).toEqual('part1')\n        expect(res[0].params['digest']).toEqual('latest')\n      })\n\n      it('GET /part1/middle-b/end-c/latest', () => {\n        const res = match('GET', '/part1/middle-b/end-c/latest')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('2')\n        expect(res[0].params['first']).toEqual('part1')\n        expect(res[0].params['uuid']).toEqual('latest')\n      })\n    })\n\n    describe('Capture multiple directories and optional', () => {\n      beforeEach(() => {\n        router.add('GET', '/:prefix{.+}/contents/:id?', 'contents')\n      })\n\n      it('GET /foo/bar/contents', () => {\n        const res = match('GET', '/foo/bar/contents')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('contents')\n        expect(res[0].params['prefix']).toEqual('foo/bar')\n        expect(res[0].params['id']).toEqual(undefined)\n      })\n\n      it('GET /foo/bar/contents/123', () => {\n        const res = match('GET', '/foo/bar/contents/123')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('contents')\n        expect(res[0].params['prefix']).toEqual('foo/bar')\n        expect(res[0].params['id']).toEqual('123')\n      })\n    })\n\n    describe('non ascii characters', () => {\n      beforeEach(() => {\n        router.add('ALL', '/$/*', 'middleware $')\n        router.add('GET', '/$/:name', 'get $ name')\n        router.add('ALL', '/()/*', 'middleware ()')\n        router.add('GET', '/()/:name', 'get () name')\n      })\n\n      it('GET /$/hono', () => {\n        const res = match('GET', '/$/hono')\n        expect(res.length).toBe(2)\n        expect(res[0].handler).toEqual('middleware $')\n        expect(res[0].params).toEqual({})\n        expect(res[1].handler).toEqual('get $ name')\n        expect(res[1].params['name']).toEqual('hono')\n      })\n\n      it('GET /()/hono', () => {\n        const res = match('GET', '/()/hono')\n        expect(res.length).toBe(2)\n        expect(res[0].handler).toEqual('middleware ()')\n        expect(res[0].params).toEqual({})\n        expect(res[1].handler).toEqual('get () name')\n        expect(res[1].params['name']).toEqual('hono')\n      })\n    })\n\n    describe('REST API', () => {\n      beforeEach(() => {\n        router.add('GET', '/users/:username{[a-z]+}', 'profile')\n        router.add('GET', '/users/:username{[a-z]+}/posts', 'posts')\n      })\n\n      it('GET /users/hono', () => {\n        const res = match('GET', '/users/hono')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('profile')\n      })\n\n      it('GET /users/hono/posts', () => {\n        const res = match('GET', '/users/hono/posts')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('posts')\n      })\n    })\n\n    describe('Trailing slash', () => {\n      beforeEach(() => {\n        router.add('GET', '/book', 'GET /book')\n        router.add('GET', '/book/:id', 'GET /book/:id')\n      })\n\n      it('GET /book', () => {\n        const res = match('GET', '/book')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('GET /book')\n      })\n      it('GET /book/', () => {\n        const res = match('GET', '/book/')\n        expect(res.length).toBe(0)\n      })\n    })\n\n    describe('Same path', () => {\n      beforeEach(() => {\n        router.add('GET', '/hey', 'Middleware A')\n        router.add('GET', '/hey', 'Middleware B')\n      })\n\n      it('GET /hey', () => {\n        const res = match('GET', '/hey')\n        expect(res.length).toBe(2)\n        expect(res[0].handler).toEqual('Middleware A')\n        expect(res[1].handler).toEqual('Middleware B')\n      })\n    })\n\n    describe('Routing with a hostname', () => {\n      beforeEach(() => {\n        router.add('GET', 'www1.example.com/hello', 'www1')\n        router.add('GET', 'www2.example.com/hello', 'www2')\n      })\n      it('GET www1.example.com/hello', () => {\n        const res = match('GET', 'www1.example.com/hello')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('www1')\n      })\n      it('GET www2.example.com/hello', () => {\n        const res = match('GET', 'www2.example.com/hello')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toEqual('www2')\n      })\n      it('GET /hello', () => {\n        const res = match('GET', '/hello')\n        expect(res.length).toBe(0)\n      })\n    })\n\n    describe('static routes of ALL and GET', () => {\n      beforeEach(() => {\n        router.add('ALL', '/foo', 'foo')\n        router.add('GET', '/bar', 'bar')\n      })\n\n      it('get /foo', () => {\n        const res = match('GET', '/foo')\n        expect(res[0].handler).toEqual('foo')\n      })\n    })\n\n    describe('ALL and Star', () => {\n      beforeEach(() => {\n        router.add('ALL', '/x', '/x')\n        router.add('GET', '*', 'star')\n      })\n\n      it('Should return /x and star', async () => {\n        const res = match('GET', '/x')\n        expect(res.length).toBe(2)\n        expect(res[0].handler).toEqual('/x')\n        expect(res[1].handler).toEqual('star')\n      })\n    })\n\n    describe('GET star, ALL static, GET star...', () => {\n      beforeEach(() => {\n        router.add('GET', '*', 'star1')\n        router.add('ALL', '/x', '/x')\n        router.add('GET', '*', 'star2')\n        router.add('GET', '*', 'star3')\n      })\n\n      it('Should return /x and star', async () => {\n        const res = match('GET', '/x')\n        expect(res.length).toBe(4)\n        expect(res[0].handler).toEqual('star1')\n        expect(res[1].handler).toEqual('/x')\n        expect(res[2].handler).toEqual('star2')\n        expect(res[3].handler).toEqual('star3')\n      })\n    })\n\n    // https://github.com/honojs/hono/issues/699\n    describe('GET star, GET static, ALL star...', () => {\n      beforeEach(() => {\n        router.add('GET', '/y/*', 'star1')\n        router.add('GET', '/y/a', 'a')\n        router.add('ALL', '/y/b/*', 'star2')\n        router.add('GET', '/y/b/bar', 'bar')\n      })\n\n      it('Should return star1, star2, and bar', async () => {\n        const res = match('GET', '/y/b/bar')\n        expect(res.length).toBe(3)\n        expect(res[0].handler).toEqual('star1')\n        expect(res[1].handler).toEqual('star2')\n        expect(res[2].handler).toEqual('bar')\n      })\n    })\n\n    describe('ALL star, ALL star, GET static, ALL star...', () => {\n      beforeEach(() => {\n        router.add('ALL', '*', 'wildcard')\n        router.add('ALL', '/a/*', 'star1')\n        router.add('GET', '/a/foo', 'foo')\n        router.add('ALL', '/b/*', 'star2')\n        router.add('GET', '/b/bar', 'bar')\n      })\n\n      it('Should return wildcard, star2 and bar', async () => {\n        const res = match('GET', '/b/bar')\n        expect(res.length).toBe(3)\n        expect(res[0].handler).toEqual('wildcard')\n        expect(res[1].handler).toEqual('star2')\n        expect(res[2].handler).toEqual('bar')\n      })\n    })\n\n    describe('Capture Group', () => {\n      describe('Simple capturing group', () => {\n        beforeEach(() => {\n          router.add('get', '/foo/:capture{(?:bar|baz)}', 'ok')\n        })\n\n        it('GET /foo/bar', () => {\n          const res = match('get', '/foo/bar')\n          expect(res.length).toBe(1)\n          expect(res[0].handler).toBe('ok')\n          expect(res[0].params['capture']).toBe('bar')\n        })\n\n        it('GET /foo/baz', () => {\n          const res = match('get', '/foo/baz')\n          expect(res.length).toBe(1)\n          expect(res[0].handler).toBe('ok')\n          expect(res[0].params['capture']).toBe('baz')\n        })\n\n        it('GET /foo/qux', () => {\n          const res = match('get', '/foo/qux')\n          expect(res.length).toBe(0)\n        })\n      })\n\n      describe('Non-capturing group', () => {\n        beforeEach(() => {\n          router.add('get', '/foo/:capture{(?:bar|baz)}', 'ok')\n        })\n\n        it('GET /foo/bar', () => {\n          const res = match('get', '/foo/bar')\n          expect(res.length).toBe(1)\n          expect(res[0].handler).toBe('ok')\n          expect(res[0].params['capture']).toBe('bar')\n        })\n\n        it('GET /foo/baz', () => {\n          const res = match('get', '/foo/baz')\n          expect(res.length).toBe(1)\n          expect(res[0].handler).toBe('ok')\n          expect(res[0].params['capture']).toBe('baz')\n        })\n\n        it('GET /foo/qux', () => {\n          const res = match('get', '/foo/qux')\n          expect(res.length).toBe(0)\n        })\n      })\n\n      describe('Non-capturing group with prefix', () => {\n        beforeEach(() => {\n          router.add('get', '/foo/:capture{ba(?:r|z)}', 'ok')\n        })\n\n        it('GET /foo/bar', () => {\n          const res = match('get', '/foo/bar')\n          expect(res.length).toBe(1)\n          expect(res[0].handler).toBe('ok')\n          expect(res[0].params['capture']).toBe('bar')\n        })\n\n        it('GET /foo/baz', () => {\n          const res = match('get', '/foo/baz')\n          expect(res.length).toBe(1)\n          expect(res[0].handler).toBe('ok')\n          expect(res[0].params['capture']).toBe('baz')\n        })\n\n        it('GET /foo/qux', () => {\n          const res = match('get', '/foo/qux')\n          expect(res.length).toBe(0)\n        })\n      })\n\n      describe('Complex capturing group', () => {\n        it('GET request', () => {\n          router.add('get', '/foo/:capture{ba(r|z)}', 'ok')\n\n          const res = match('get', '/foo/bar')\n          expect(res.length).toBe(1)\n          expect(res[0].handler).toBe('ok')\n        })\n      })\n    })\n\n    describe('Unknown method', () => {\n      beforeEach(() => {\n        router.add('GET', '/', 'index')\n        router.add('ALL', '/all', 'all')\n      })\n\n      it('UNKNOWN_METHOD /', () => {\n        const res = match('UNKNOWN_METHOD', '/')\n        expect(res.length).toBe(0)\n      })\n\n      it('UNKNOWN_METHOD /all', () => {\n        const res = match('UNKNOWN_METHOD', '/all')\n        expect(res.length).toBe(1)\n        expect(res[0].handler).toBe('all')\n      })\n    })\n  })\n}\n"
  },
  {
    "path": "src/router/linear-router/index.ts",
    "content": "/**\n * @module\n * LinearRouter for Hono.\n */\n\nexport { LinearRouter } from './router'\n"
  },
  {
    "path": "src/router/linear-router/router.test.ts",
    "content": "import { UnsupportedPathError } from '../../router'\nimport { runTest } from '../common.case.test'\nimport { LinearRouter } from './router'\n\ndescribe('LinearRouter', () => {\n  runTest({\n    skip: [\n      {\n        reason: 'UnsupportedPath',\n        tests: [\n          'Multi match > `params` per a handler > GET /entry/123/show',\n          'Capture regex pattern has trailing wildcard > GET /foo/bar/file.html',\n          'Complex > Parameter with {.*} regexp',\n        ],\n      },\n      {\n        reason: 'LinearRouter allows trailing slashes',\n        tests: ['Trailing slash > GET /book/'],\n      },\n    ],\n    newRouter: () => new LinearRouter(),\n  })\n\n  describe('Multi match', () => {\n    describe('`params` per a handler', () => {\n      const router = new LinearRouter<string>()\n\n      beforeEach(() => {\n        router.add('ALL', '*', 'middleware a')\n        router.add('GET', '/entry/:id/*', 'middleware b')\n        router.add('GET', '/entry/:id/:action', 'action')\n      })\n\n      it('GET /entry/123/show', () => {\n        expect(() => {\n          router.match('GET', '/entry/123/show')\n        }).toThrowError(UnsupportedPathError)\n      })\n    })\n  })\n\n  describe('Trailing slash', () => {\n    const router = new LinearRouter<string>()\n\n    beforeEach(() => {\n      router.add('GET', '/book', 'GET /book')\n      router.add('GET', '/book/:id', 'GET /book/:id')\n    })\n\n    it('GET /book/', () => {\n      const [res] = router.match('GET', '/book/')\n      expect(res.length).toBe(1)\n      expect(res[0][0]).toBe('GET /book')\n    })\n  })\n\n  describe('Skip part', () => {\n    const router = new LinearRouter<string>()\n\n    beforeEach(() => {\n      router.add('GET', '/products/:id{d+}', 'GET /products/:id{d+}')\n    })\n\n    it('GET /products/list', () => {\n      const [res] = router.match('GET', '/products/list')\n      expect(res.length).toBe(0)\n    })\n  })\n})\n"
  },
  {
    "path": "src/router/linear-router/router.ts",
    "content": "import type { Params, Result, Router } from '../../router'\nimport { METHOD_NAME_ALL, UnsupportedPathError } from '../../router'\nimport { checkOptionalParameter } from '../../utils/url'\n\ntype RegExpMatchArrayWithIndices = RegExpMatchArray & { indices: [number, number][] }\n\nconst emptyParams = Object.create(null)\n\nconst splitPathRe = /\\/(:\\w+(?:{(?:(?:{[\\d,]+})|[^}])+})?)|\\/[^\\/\\?]+|(\\?)/g\nconst splitByStarRe = /\\*/\nexport class LinearRouter<T> implements Router<T> {\n  name: string = 'LinearRouter'\n  #routes: [string, string, T][] = []\n\n  add(method: string, path: string, handler: T) {\n    for (\n      let i = 0, paths = checkOptionalParameter(path) || [path], len = paths.length;\n      i < len;\n      i++\n    ) {\n      this.#routes.push([method, paths[i], handler])\n    }\n  }\n\n  match(method: string, path: string): Result<T> {\n    const handlers: [T, Params][] = []\n    ROUTES_LOOP: for (let i = 0, len = this.#routes.length; i < len; i++) {\n      const [routeMethod, routePath, handler] = this.#routes[i]\n      if (routeMethod === method || routeMethod === METHOD_NAME_ALL) {\n        if (routePath === '*' || routePath === '/*') {\n          handlers.push([handler, emptyParams])\n          continue\n        }\n\n        const hasStar = routePath.indexOf('*') !== -1\n        const hasLabel = routePath.indexOf(':') !== -1\n        if (!hasStar && !hasLabel) {\n          if (routePath === path || routePath + '/' === path) {\n            handlers.push([handler, emptyParams])\n          }\n        } else if (hasStar && !hasLabel) {\n          const endsWithStar = routePath.charCodeAt(routePath.length - 1) === 42\n          const parts = (endsWithStar ? routePath.slice(0, -2) : routePath).split(splitByStarRe)\n\n          const lastIndex = parts.length - 1\n          for (let j = 0, pos = 0, len = parts.length; j < len; j++) {\n            const part = parts[j]\n            const index = path.indexOf(part, pos)\n            if (index !== pos) {\n              continue ROUTES_LOOP\n            }\n            pos += part.length\n            if (j === lastIndex) {\n              if (\n                !endsWithStar &&\n                pos !== path.length &&\n                !(pos === path.length - 1 && path.charCodeAt(pos) === 47)\n              ) {\n                continue ROUTES_LOOP\n              }\n            } else {\n              const index = path.indexOf('/', pos)\n              if (index === -1) {\n                continue ROUTES_LOOP\n              }\n              pos = index\n            }\n          }\n          handlers.push([handler, emptyParams])\n        } else if (hasLabel && !hasStar) {\n          const params: Record<string, string> = Object.create(null)\n          const parts = routePath.match(splitPathRe) as string[]\n\n          const lastIndex = parts.length - 1\n          for (let j = 0, pos = 0, len = parts.length; j < len; j++) {\n            if (pos === -1 || pos >= path.length) {\n              continue ROUTES_LOOP\n            }\n\n            const part = parts[j]\n            if (part.charCodeAt(1) === 58) {\n              if (path.charCodeAt(pos) !== 47) {\n                continue ROUTES_LOOP\n              }\n\n              // /:label\n              let name = part.slice(2)\n              let value\n\n              if (name.charCodeAt(name.length - 1) === 125) {\n                // :label{pattern}\n                const openBracePos = name.indexOf('{')\n                const next = parts[j + 1]\n                const lookahead = next && next[1] !== ':' && next[1] !== '*' ? `(?=${next})` : ''\n                const pattern = name.slice(openBracePos + 1, -1) + lookahead\n                const restPath = path.slice(pos + 1)\n                const match = new RegExp(pattern, 'd').exec(restPath) as RegExpMatchArrayWithIndices\n                if (!match || match.indices[0][0] !== 0 || match.indices[0][1] === 0) {\n                  continue ROUTES_LOOP\n                }\n                name = name.slice(0, openBracePos)\n                value = restPath.slice(...match.indices[0])\n                pos += match.indices[0][1] + 1\n              } else {\n                let endValuePos = path.indexOf('/', pos + 1)\n                if (endValuePos === -1) {\n                  if (pos + 1 === path.length) {\n                    continue ROUTES_LOOP\n                  }\n                  endValuePos = path.length\n                }\n                value = path.slice(pos + 1, endValuePos)\n                pos = endValuePos\n              }\n\n              params[name] ||= value as string\n            } else {\n              const index = path.indexOf(part, pos)\n              if (index !== pos) {\n                continue ROUTES_LOOP\n              }\n              pos += part.length\n            }\n\n            if (j === lastIndex) {\n              if (\n                pos !== path.length &&\n                !(pos === path.length - 1 && path.charCodeAt(pos) === 47)\n              ) {\n                continue ROUTES_LOOP\n              }\n            }\n          }\n\n          handlers.push([handler, params])\n        } else if (hasLabel && hasStar) {\n          throw new UnsupportedPathError()\n        }\n      }\n    }\n\n    return [handlers]\n  }\n}\n"
  },
  {
    "path": "src/router/pattern-router/index.ts",
    "content": "/**\n * @module\n * PatternRouter for Hono.\n */\n\nexport { PatternRouter } from './router'\n"
  },
  {
    "path": "src/router/pattern-router/router.test.ts",
    "content": "import { UnsupportedPathError } from '../../router'\nimport { runTest } from '../common.case.test'\nimport { PatternRouter } from './router'\n\ndescribe('Pattern', () => {\n  runTest({\n    skip: [\n      {\n        reason: 'UnsupportedPath',\n        tests: ['Duplicate param name > self'],\n      },\n      {\n        reason: 'PatternRouter allows trailing slashes',\n        tests: ['Trailing slash > GET /book/'],\n      },\n    ],\n    newRouter: () => new PatternRouter(),\n  })\n\n  describe('Duplicate param name', () => {\n    it('self', () => {\n      const router = new PatternRouter<string>()\n      expect(() => {\n        router.add('GET', '/:id/:id', 'foo')\n      }).toThrowError(UnsupportedPathError)\n    })\n  })\n  describe('Trailing slash', () => {\n    const router = new PatternRouter<string>()\n\n    beforeEach(() => {\n      router.add('GET', '/book', 'GET /book')\n      router.add('GET', '/book/:id', 'GET /book/:id')\n    })\n\n    it('GET /book/', () => {\n      const [res] = router.match('GET', '/book/')\n      expect(res.length).toBe(1)\n      expect(res[0][0]).toBe('GET /book')\n    })\n  })\n})\n"
  },
  {
    "path": "src/router/pattern-router/router.ts",
    "content": "import type { Params, Result, Router } from '../../router'\nimport { METHOD_NAME_ALL, UnsupportedPathError } from '../../router'\n\ntype Route<T> = [RegExp, string, T] // [pattern, method, handler]\n\nconst emptyParams = Object.create(null)\n\nexport class PatternRouter<T> implements Router<T> {\n  name: string = 'PatternRouter'\n  #routes: Route<T>[] = []\n\n  add(method: string, path: string, handler: T) {\n    const endsWithWildcard = path.at(-1) === '*'\n    if (endsWithWildcard) {\n      path = path.slice(0, -2)\n    }\n    if (path.at(-1) === '?') {\n      path = path.slice(0, -1)\n      this.add(method, path.replace(/\\/[^/]+$/, ''), handler)\n    }\n\n    const parts = (path.match(/\\/?(:\\w+(?:{(?:(?:{[\\d,]+})|[^}])+})?)|\\/?[^\\/\\?]+/g) || []).map(\n      (part) => {\n        const match = part.match(/^\\/:([^{]+)(?:{(.*)})?/)\n        return match\n          ? `/(?<${match[1]}>${match[2] || '[^/]+'})`\n          : part === '/*'\n            ? '/[^/]+'\n            : part.replace(/[.\\\\+*[^\\]$()]/g, '\\\\$&')\n      }\n    )\n\n    try {\n      this.#routes.push([\n        new RegExp(`^${parts.join('')}${endsWithWildcard ? '' : '/?$'}`),\n        method,\n        handler,\n      ])\n    } catch {\n      throw new UnsupportedPathError()\n    }\n  }\n\n  match(method: string, path: string): Result<T> {\n    const handlers: [T, Params][] = []\n\n    for (let i = 0, len = this.#routes.length; i < len; i++) {\n      const [pattern, routeMethod, handler] = this.#routes[i]\n\n      if (routeMethod === method || routeMethod === METHOD_NAME_ALL) {\n        const match = pattern.exec(path)\n        if (match) {\n          handlers.push([handler, match.groups || emptyParams])\n        }\n      }\n    }\n\n    return [handlers]\n  }\n}\n"
  },
  {
    "path": "src/router/reg-exp-router/index.ts",
    "content": "/**\n * @module\n * RegExpRouter for Hono.\n */\n\nexport { RegExpRouter } from './router'\nexport { PreparedRegExpRouter, buildInitParams, serializeInitParams } from './prepared-router'\n"
  },
  {
    "path": "src/router/reg-exp-router/matcher.ts",
    "content": "import type { ParamIndexMap, Result, Router } from '../../router'\nimport { METHOD_NAME_ALL } from '../../router'\n\nexport type HandlerData<T> = [T, ParamIndexMap][]\nexport type StaticMap<T> = Record<string, Result<T>>\nexport type Matcher<T> = [RegExp, HandlerData<T>[], StaticMap<T>]\nexport type MatcherMap<T> = Record<string, Matcher<T> | null>\n\nexport const emptyParam: string[] = []\nexport function match<R extends Router<T>, T>(this: R, method: string, path: string): Result<T> {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  const matchers: MatcherMap<T> = (this as any).buildAllMatchers()\n\n  const match = ((method, path) => {\n    const matcher = (matchers[method] || matchers[METHOD_NAME_ALL]) as Matcher<T>\n\n    const staticMatch = matcher[2][path]\n    if (staticMatch) {\n      return staticMatch\n    }\n\n    const match = path.match(matcher[0])\n    if (!match) {\n      return [[], emptyParam]\n    }\n\n    const index = match.indexOf('', 1)\n    return [matcher[1][index], match]\n  }) as Router<T>['match']\n\n  this.match = match\n  return match(method, path)\n}\n"
  },
  {
    "path": "src/router/reg-exp-router/node.ts",
    "content": "const LABEL_REG_EXP_STR = '[^/]+'\nconst ONLY_WILDCARD_REG_EXP_STR = '.*'\nconst TAIL_WILDCARD_REG_EXP_STR = '(?:|/.*)'\nexport const PATH_ERROR = Symbol()\n\nexport type ParamAssocArray = [string, number][]\nexport interface Context {\n  varIndex: number\n}\n\nconst regExpMetaChars = new Set('.\\\\+*[^]$()')\n\n/**\n * Sort order:\n * 1. literal\n * 2. special pattern (e.g. :label{[0-9]+})\n * 3. common label pattern (e.g. :label)\n * 4. wildcard\n */\nfunction compareKey(a: string, b: string): number {\n  if (a.length === 1) {\n    return b.length === 1 ? (a < b ? -1 : 1) : -1\n  }\n  if (b.length === 1) {\n    return 1\n  }\n\n  // wildcard\n  if (a === ONLY_WILDCARD_REG_EXP_STR || a === TAIL_WILDCARD_REG_EXP_STR) {\n    return 1\n  } else if (b === ONLY_WILDCARD_REG_EXP_STR || b === TAIL_WILDCARD_REG_EXP_STR) {\n    return -1\n  }\n\n  // label\n  if (a === LABEL_REG_EXP_STR) {\n    return 1\n  } else if (b === LABEL_REG_EXP_STR) {\n    return -1\n  }\n\n  return a.length === b.length ? (a < b ? -1 : 1) : b.length - a.length\n}\n\nexport class Node {\n  #index?: number\n  #varIndex?: number\n  #children: Record<string, Node> = Object.create(null)\n\n  insert(\n    tokens: readonly string[],\n    index: number,\n    paramMap: ParamAssocArray,\n    context: Context,\n    pathErrorCheckOnly: boolean\n  ): void {\n    if (tokens.length === 0) {\n      if (this.#index !== undefined) {\n        throw PATH_ERROR\n      }\n      if (pathErrorCheckOnly) {\n        return\n      }\n\n      this.#index = index\n      return\n    }\n\n    const [token, ...restTokens] = tokens\n    const pattern =\n      token === '*'\n        ? restTokens.length === 0\n          ? ['', '', ONLY_WILDCARD_REG_EXP_STR] // '*' matches to all the trailing paths\n          : ['', '', LABEL_REG_EXP_STR]\n        : token === '/*'\n          ? ['', '', TAIL_WILDCARD_REG_EXP_STR] // '/path/to/*' is /\\/path\\/to(?:|/.*)$\n          : token.match(/^\\:([^\\{\\}]+)(?:\\{(.+)\\})?$/)\n\n    let node\n    if (pattern) {\n      const name = pattern[1]\n      let regexpStr = pattern[2] || LABEL_REG_EXP_STR\n      if (name && pattern[2]) {\n        if (regexpStr === '.*') {\n          throw PATH_ERROR\n        }\n        regexpStr = regexpStr.replace(/^\\((?!\\?:)(?=[^)]+\\)$)/, '(?:') // (a|b) => (?:a|b)\n        if (/\\((?!\\?:)/.test(regexpStr)) {\n          // prefix(?:a|b) is allowed, but prefix(a|b) is not\n          throw PATH_ERROR\n        }\n      }\n\n      node = this.#children[regexpStr]\n      if (!node) {\n        if (\n          Object.keys(this.#children).some(\n            (k) => k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR\n          )\n        ) {\n          throw PATH_ERROR\n        }\n        if (pathErrorCheckOnly) {\n          return\n        }\n        node = this.#children[regexpStr] = new Node()\n        if (name !== '') {\n          node.#varIndex = context.varIndex++\n        }\n      }\n      if (!pathErrorCheckOnly && name !== '') {\n        paramMap.push([name, node.#varIndex as number])\n      }\n    } else {\n      node = this.#children[token]\n      if (!node) {\n        if (\n          Object.keys(this.#children).some(\n            (k) =>\n              k.length > 1 && k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR\n          )\n        ) {\n          throw PATH_ERROR\n        }\n        if (pathErrorCheckOnly) {\n          return\n        }\n        node = this.#children[token] = new Node()\n      }\n    }\n\n    node.insert(restTokens, index, paramMap, context, pathErrorCheckOnly)\n  }\n\n  buildRegExpStr(): string {\n    const childKeys = Object.keys(this.#children).sort(compareKey)\n\n    const strList = childKeys.map((k) => {\n      const c = this.#children[k]\n      return (\n        (typeof c.#varIndex === 'number'\n          ? `(${k})@${c.#varIndex}`\n          : regExpMetaChars.has(k)\n            ? `\\\\${k}`\n            : k) + c.buildRegExpStr()\n      )\n    })\n\n    if (typeof this.#index === 'number') {\n      strList.unshift(`#${this.#index}`)\n    }\n\n    if (strList.length === 0) {\n      return ''\n    }\n    if (strList.length === 1) {\n      return strList[0]\n    }\n\n    return '(?:' + strList.join('|') + ')'\n  }\n}\n"
  },
  {
    "path": "src/router/reg-exp-router/prepared-router.test.ts",
    "content": "import { METHOD_NAME_ALL } from '../../router'\nimport { runTest } from '../common.case.test'\nimport { buildInitParams, serializeInitParams, PreparedRegExpRouter } from './prepared-router'\n\ndescribe('PreparedRegExpRouter', async () => {\n  runTest({\n    skip: [\n      {\n        reason: 'UnsupportedPath',\n        tests: [\n          'Duplicate param name > parent',\n          'Duplicate param name > child',\n          'Capture Group > Complex capturing group > GET request',\n          'Capture complex multiple directories > GET /part1/middle-b/latest',\n          'Capture complex multiple directories > GET /part1/middle-b/end-c/latest',\n          'Complex > Parameter with {.*} regexp',\n        ],\n      },\n      {\n        reason:\n          'This route can not be added with `:label` to PreparedRegExpRouter. This is ambiguous',\n        tests: ['Including slashes > GET /js/main.js'],\n      },\n    ],\n    newRouter: <T>() => {\n      let router: PreparedRegExpRouter<T>\n      const routes: [string, string, T][] = []\n      return {\n        name: 'PreparedRegExpRouterBuilder',\n        add: (method: string, path: string, handler: T) => {\n          routes.push([method, path, handler])\n        },\n        match: (method: string, path: string) => {\n          if (!router) {\n            const serialized = serializeInitParams(\n              buildInitParams({\n                paths: routes.map((r) => r[1]),\n              })\n            )\n            const params = eval(serialized) as ConstructorParameters<typeof PreparedRegExpRouter<T>>\n            router = new PreparedRegExpRouter<T>(...params)\n\n            for (const route of routes) {\n              router.add(...route)\n            }\n          }\n          return router.match(method, path)\n        },\n      }\n    },\n  })\n\n  describe('add()', () => {\n    it('should add a route', () => {\n      const params = buildInitParams({\n        paths: ['/hello'],\n      })\n      const router = new PreparedRegExpRouter(...params)\n      router.add('GET', '/hello', 'get hello')\n      expect(router.match('GET', '/hello')).toEqual([[['get hello', {}]], []])\n    })\n\n    it('should throw an error if the path is not pre-registered', () => {\n      const params = buildInitParams({\n        paths: ['/hello'],\n      })\n      const router = new PreparedRegExpRouter(...params)\n      expect(() => router.add('GET', '/unknown', 'get hello')).toThrowError()\n    })\n  })\n})\n\ndescribe('buildInitParams() and serializeInitParams()', () => {\n  it('should build empty init params', () => {\n    const params = buildInitParams({\n      paths: [],\n    })\n    expect(params).toEqual([{ [METHOD_NAME_ALL]: [/^$/, [], {}] }, {}])\n    expect((0, eval)(serializeInitParams(params))).toEqual(params)\n  })\n\n  it('should build init params with one static path', () => {\n    const params = buildInitParams({\n      paths: ['/hello'],\n    })\n    expect(params).toEqual([\n      {\n        [METHOD_NAME_ALL]: [\n          /^$/,\n          [],\n          {\n            '/hello': [[], []],\n          },\n        ],\n      },\n      {\n        '/hello': [[['']]],\n      },\n    ])\n    expect((0, eval)(serializeInitParams(params))).toEqual(params)\n  })\n\n  it('should build init params with paths with params', () => {\n    const params = buildInitParams({\n      paths: ['/hello/:name', '/hello/:name/posts/:postId'],\n    })\n    expect(params).toEqual([\n      {\n        [METHOD_NAME_ALL]: [/^\\/hello\\/([^/]+)(?:$()|\\/posts\\/([^/]+)$())/, [0, 0, [], 0, []], {}],\n      },\n      {\n        '/hello/:name': [[[2], { name: 1 }]],\n        '/hello/:name/posts/:postId': [[[4], { name: 1, postId: 3 }]],\n      },\n    ])\n    expect((0, eval)(serializeInitParams(params))).toEqual(params)\n  })\n\n  it('should build init params with wildcard', () => {\n    const params = buildInitParams({\n      paths: ['*'],\n    })\n    expect(params).toEqual([\n      {\n        [METHOD_NAME_ALL]: [/^.*$()/, [0, []], {}],\n      },\n      {},\n    ])\n    expect((0, eval)(serializeInitParams(params))).toEqual(params)\n  })\n\n  it('should build init params with complex path', () => {\n    const params = buildInitParams({\n      paths: ['/hello', '/hello/:name', '/hello/:name/posts/:postId', '*'],\n    })\n    expect(params).toEqual([\n      {\n        [METHOD_NAME_ALL]: [\n          /^(?:\\/hello\\/([^/]+)(?:$()|\\/posts\\/([^/]+)$())|.*$())/,\n          [0, 0, [], 0, [], []],\n          {\n            '/hello': [[], []],\n          },\n        ],\n      },\n      {\n        '/hello': [[['']]],\n        '/hello/:name': [[[2], { name: 1 }]],\n        '/hello/:name/posts/:postId': [[[4], { name: 1, postId: 3 }]],\n      },\n    ])\n    expect((0, eval)(serializeInitParams(params))).toEqual(params)\n  })\n})\n"
  },
  {
    "path": "src/router/reg-exp-router/prepared-router.ts",
    "content": "import type { ParamIndexMap, Result, Router } from '../../router'\nimport { METHOD_NAME_ALL } from '../../router'\nimport type { HandlerData, Matcher, MatcherMap, StaticMap } from './matcher'\nimport { match, emptyParam } from './matcher'\nimport { RegExpRouter } from './router'\n\ntype RelocateMap = Record<string, ([(number | string)[], ParamIndexMap] | [(number | string)[]])[]>\n\nexport class PreparedRegExpRouter<T> implements Router<T> {\n  name: string = 'PreparedRegExpRouter'\n  #matchers: MatcherMap<T>\n  #relocateMap: RelocateMap\n\n  constructor(matchers: MatcherMap<T>, relocateMap: RelocateMap) {\n    this.#matchers = matchers\n    this.#relocateMap = relocateMap\n  }\n\n  #addWildcard(method: string, handlerData: [T, ParamIndexMap]) {\n    const matcher = this.#matchers[method] as Matcher<T>\n    matcher[1].forEach((list) => list && list.push(handlerData))\n    Object.values(matcher[2]).forEach((list) => (list[0] as [T, ParamIndexMap][]).push(handlerData))\n  }\n\n  #addPath(\n    method: string,\n    path: string,\n    handler: T,\n    indexes: (number | string)[],\n    map: ParamIndexMap | undefined\n  ) {\n    const matcher = this.#matchers[method] as Matcher<T>\n    if (!map) {\n      // assumed to be a static route\n      matcher[2][path][0].push([handler, {}])\n    } else {\n      indexes.forEach((index) => {\n        if (typeof index === 'number') {\n          matcher[1][index].push([handler, map])\n        } else {\n          ;(matcher[2][index || path][0] as [T, ParamIndexMap][]).push([handler, map])\n        }\n      })\n    }\n  }\n\n  add(method: string, path: string, handler: T) {\n    if (!this.#matchers[method]) {\n      const all = this.#matchers[METHOD_NAME_ALL] as Matcher<T>\n      const staticMap = {} as StaticMap<T>\n      for (const key in all[2]) {\n        staticMap[key] = [all[2][key][0].slice(), emptyParam] as Result<T>\n      }\n      this.#matchers[method] = [\n        all[0],\n        all[1].map((list) => (Array.isArray(list) ? list.slice() : 0)) as HandlerData<T>[],\n        staticMap,\n      ]\n    }\n\n    if (path === '/*' || path === '*') {\n      const handlerData: [T, ParamIndexMap] = [handler, {}]\n      if (method === METHOD_NAME_ALL) {\n        for (const m in this.#matchers) {\n          this.#addWildcard(m, handlerData)\n        }\n      } else {\n        this.#addWildcard(method, handlerData)\n      }\n      return\n    }\n\n    const data = this.#relocateMap[path]\n    if (!data) {\n      throw new Error(`Path ${path} is not registered`)\n    }\n    for (const [indexes, map] of data) {\n      if (method === METHOD_NAME_ALL) {\n        for (const m in this.#matchers) {\n          this.#addPath(m, path, handler, indexes, map)\n        }\n      } else {\n        this.#addPath(method, path, handler, indexes, map)\n      }\n    }\n  }\n\n  protected buildAllMatchers(): MatcherMap<T> {\n    return this.#matchers\n  }\n\n  match: typeof match<Router<T>, T> = match\n}\n\nexport const buildInitParams: (params: {\n  paths: string[]\n}) => ConstructorParameters<typeof PreparedRegExpRouter> = ({ paths }) => {\n  const RegExpRouterWithMatcherExport = class<T> extends RegExpRouter<T> {\n    buildAndExportAllMatchers() {\n      return this.buildAllMatchers()\n    }\n  }\n  const router = new RegExpRouterWithMatcherExport<string>()\n  for (const path of paths) {\n    router.add(METHOD_NAME_ALL, path, path)\n  }\n\n  const matchers = router.buildAndExportAllMatchers()\n  const all = matchers[METHOD_NAME_ALL] as Matcher<string>\n\n  const relocateMap: RelocateMap = {}\n  for (const path of paths) {\n    if (path === '/*' || path === '*') {\n      continue\n    }\n    all[1].forEach((list, i) => {\n      list.forEach(([p, map]) => {\n        if (p === path) {\n          if (relocateMap[path]) {\n            relocateMap[path][0][1] = {\n              ...relocateMap[path][0][1],\n              ...map,\n            }\n          } else {\n            relocateMap[path] = [[[], map]]\n          }\n          if (relocateMap[path][0][0].findIndex((j) => j === i) === -1) {\n            relocateMap[path][0][0].push(i)\n          }\n        }\n      })\n    })\n    for (const path2 in all[2]) {\n      all[2][path2][0].forEach(([p]) => {\n        if (p === path) {\n          relocateMap[path] ||= [[[]]]\n          const value = path2 === path ? '' : path2\n          if (relocateMap[path][0][0].findIndex((v) => v === value) === -1) {\n            relocateMap[path][0][0].push(value)\n          }\n        }\n      })\n    }\n  }\n\n  for (let i = 0, len = all[1].length; i < len; i++) {\n    all[1][i] = all[1][i] ? [] : (0 as unknown as HandlerData<string>)\n  }\n  for (const path in all[2]) {\n    all[2][path][0] = []\n  }\n\n  return [matchers, relocateMap]\n}\n\nexport const serializeInitParams: (\n  params: ConstructorParameters<typeof PreparedRegExpRouter>\n) => string = ([matchers, relocateMap]) => {\n  // Embed the regular expression as a result of `toString()` so that it can be evaluated as JavaScript.\n  const matchersStr = JSON.stringify(matchers, (_, value) =>\n    value instanceof RegExp ? `##${value.toString()}##` : value\n  ).replace(/\"##(.+?)##\"/g, (_, str) => str.replace(/\\\\\\\\/g, '\\\\'))\n  const relocateMapStr = JSON.stringify(relocateMap)\n  return `[${matchersStr},${relocateMapStr}]`\n}\n"
  },
  {
    "path": "src/router/reg-exp-router/router.test.ts",
    "content": "import type { ParamStash } from '../../router'\nimport { UnsupportedPathError } from '../../router'\nimport { runTest } from '../common.case.test'\nimport { RegExpRouter } from './router'\n\ndescribe('RegExpRouter', () => {\n  runTest({\n    skip: [\n      {\n        reason: 'UnsupportedPath',\n        tests: [\n          'Duplicate param name > parent',\n          'Duplicate param name > child',\n          'Capture Group > Complex capturing group > GET request',\n          'Capture complex multiple directories > GET /part1/middle-b/latest',\n          'Capture complex multiple directories > GET /part1/middle-b/end-c/latest',\n          'Complex > Parameter with {.*} regexp',\n        ],\n      },\n      {\n        reason: 'This route can not be added with `:label` to RegExpRouter. This is ambiguous',\n        tests: ['Including slashes > GET /js/main.js'],\n      },\n    ],\n    newRouter: () => new RegExpRouter(),\n  })\n\n  describe('Return value type', () => {\n    it('Should return [[T, ParamIndexMap][], ParamStash]', () => {\n      const router = new RegExpRouter<string>()\n      router.add('GET', '/posts/:id', 'get post')\n\n      const [res, stash] = router.match('GET', '/posts/1')\n      expect(res.length).toBe(1)\n      expect(res).toEqual([['get post', { id: 1 }]])\n      expect((stash as ParamStash)[1]).toBe('1')\n    })\n  })\n\n  describe('UnsupportedPathError', () => {\n    describe('Ambiguous', () => {\n      const router = new RegExpRouter<string>()\n\n      router.add('GET', '/:user/entries', 'get user entries')\n      router.add('GET', '/entry/:name', 'get entry')\n      router.add('POST', '/entry', 'create entry')\n\n      it('GET /entry/entries', () => {\n        expect(() => {\n          router.match('GET', '/entry/entries')\n        }).toThrowError(UnsupportedPathError)\n      })\n    })\n\n    describe('Multiple handlers with different label', () => {\n      const router = new RegExpRouter<string>()\n\n      router.add('GET', '/:type/:id', ':type')\n      router.add('GET', '/:class/:id', ':class')\n      router.add('GET', '/:model/:id', ':model')\n\n      it('GET /entry/123', () => {\n        expect(() => {\n          router.match('GET', '/entry/123')\n        }).toThrowError(UnsupportedPathError)\n      })\n    })\n\n    it('parent', () => {\n      const router = new RegExpRouter<string>()\n      router.add('GET', '/:id/:action', 'foo')\n      router.add('GET', '/posts/:id', 'bar')\n      expect(() => {\n        router.match('GET', '/')\n      }).toThrowError(UnsupportedPathError)\n    })\n\n    it('child', () => {\n      const router = new RegExpRouter<string>()\n      router.add('GET', '/posts/:id', 'foo')\n      router.add('GET', '/:id/:action', 'bar')\n\n      expect(() => {\n        router.match('GET', '/')\n      }).toThrowError(UnsupportedPathError)\n    })\n\n    describe('static and dynamic', () => {\n      it('static first', () => {\n        const router = new RegExpRouter<string>()\n        router.add('GET', '/reg-exp/router', 'foo')\n        router.add('GET', '/reg-exp/:id', 'bar')\n\n        expect(() => {\n          router.match('GET', '/')\n        }).toThrowError(UnsupportedPathError)\n      })\n\n      it('long label', () => {\n        const router = new RegExpRouter<string>()\n        router.add('GET', '/reg-exp/router', 'foo')\n        router.add('GET', '/reg-exp/:service', 'bar')\n\n        expect(() => {\n          router.match('GET', '/')\n        }).toThrowError(UnsupportedPathError)\n      })\n\n      it('dynamic first', () => {\n        const router = new RegExpRouter<string>()\n        router.add('GET', '/reg-exp/:id', 'bar')\n        router.add('GET', '/reg-exp/router', 'foo')\n\n        expect(() => {\n          router.match('GET', '/')\n        }).toThrowError(UnsupportedPathError)\n      })\n    })\n\n    it('different regular expression', () => {\n      const router = new RegExpRouter<string>()\n      router.add('GET', '/:id/:action{create|update}', 'foo')\n      router.add('GET', '/:id/:action{delete}', 'bar')\n      expect(() => {\n        router.match('GET', '/')\n      }).toThrowError(UnsupportedPathError)\n    })\n\n    describe('Capture Group', () => {\n      describe('Complex capturing group', () => {\n        it('GET request', () => {\n          const router = new RegExpRouter<string>()\n          router.add('GET', '/foo/:capture{ba(r|z)}', 'ok')\n          expect(() => {\n            router.match('GET', '/foo/bar')\n          }).toThrowError(UnsupportedPathError)\n        })\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/router/reg-exp-router/router.ts",
    "content": "import type { ParamIndexMap, Router } from '../../router'\nimport {\n  MESSAGE_MATCHER_IS_ALREADY_BUILT,\n  METHOD_NAME_ALL,\n  UnsupportedPathError,\n} from '../../router'\nimport { checkOptionalParameter } from '../../utils/url'\nimport type { HandlerData, StaticMap, Matcher, MatcherMap } from './matcher'\nimport { match, emptyParam } from './matcher'\nimport { PATH_ERROR } from './node'\nimport type { ParamAssocArray } from './node'\nimport { Trie } from './trie'\n\ntype HandlerWithMetadata<T> = [T, number] // [handler, paramCount]\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst nullMatcher: Matcher<any> = [/^$/, [], Object.create(null)]\n\nlet wildcardRegExpCache: Record<string, RegExp> = Object.create(null)\nfunction buildWildcardRegExp(path: string): RegExp {\n  return (wildcardRegExpCache[path] ??= new RegExp(\n    path === '*'\n      ? ''\n      : `^${path.replace(/\\/\\*$|([.\\\\+*[^\\]$()])/g, (_, metaChar) =>\n          metaChar ? `\\\\${metaChar}` : '(?:|/.*)'\n        )}$`\n  ))\n}\n\nfunction clearWildcardRegExpCache() {\n  wildcardRegExpCache = Object.create(null)\n}\n\nfunction buildMatcherFromPreprocessedRoutes<T>(\n  routes: [string, HandlerWithMetadata<T>[]][]\n): Matcher<T> {\n  const trie = new Trie()\n  const handlerData: HandlerData<T>[] = []\n  if (routes.length === 0) {\n    return nullMatcher\n  }\n\n  const routesWithStaticPathFlag = routes\n    .map(\n      (route) => [!/\\*|\\/:/.test(route[0]), ...route] as [boolean, string, HandlerWithMetadata<T>[]]\n    )\n    .sort(([isStaticA, pathA], [isStaticB, pathB]) =>\n      isStaticA ? 1 : isStaticB ? -1 : pathA.length - pathB.length\n    )\n\n  const staticMap: StaticMap<T> = Object.create(null)\n  for (let i = 0, j = -1, len = routesWithStaticPathFlag.length; i < len; i++) {\n    const [pathErrorCheckOnly, path, handlers] = routesWithStaticPathFlag[i]\n    if (pathErrorCheckOnly) {\n      staticMap[path] = [handlers.map(([h]) => [h, Object.create(null)]), emptyParam]\n    } else {\n      j++\n    }\n\n    let paramAssoc: ParamAssocArray\n    try {\n      paramAssoc = trie.insert(path, j, pathErrorCheckOnly)\n    } catch (e) {\n      throw e === PATH_ERROR ? new UnsupportedPathError(path) : e\n    }\n\n    if (pathErrorCheckOnly) {\n      continue\n    }\n\n    handlerData[j] = handlers.map(([h, paramCount]) => {\n      const paramIndexMap: ParamIndexMap = Object.create(null)\n      paramCount -= 1\n      for (; paramCount >= 0; paramCount--) {\n        const [key, value] = paramAssoc[paramCount]\n        paramIndexMap[key] = value\n      }\n      return [h, paramIndexMap]\n    })\n  }\n\n  const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp()\n  for (let i = 0, len = handlerData.length; i < len; i++) {\n    for (let j = 0, len = handlerData[i].length; j < len; j++) {\n      const map = handlerData[i][j]?.[1]\n      if (!map) {\n        continue\n      }\n      const keys = Object.keys(map)\n      for (let k = 0, len = keys.length; k < len; k++) {\n        map[keys[k]] = paramReplacementMap[map[keys[k]]]\n      }\n    }\n  }\n\n  const handlerMap: HandlerData<T>[] = []\n  // using `in` because indexReplacementMap is a sparse array\n  for (const i in indexReplacementMap) {\n    handlerMap[i] = handlerData[indexReplacementMap[i]]\n  }\n\n  return [regexp, handlerMap, staticMap] as Matcher<T>\n}\n\nfunction findMiddleware<T>(\n  middleware: Record<string, T[]> | undefined,\n  path: string\n): T[] | undefined {\n  if (!middleware) {\n    return undefined\n  }\n\n  for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length)) {\n    if (buildWildcardRegExp(k).test(path)) {\n      return [...middleware[k]]\n    }\n  }\n\n  return undefined\n}\n\nexport class RegExpRouter<T> implements Router<T> {\n  name: string = 'RegExpRouter'\n  #middleware?: Record<string, Record<string, HandlerWithMetadata<T>[]>>\n  #routes?: Record<string, Record<string, HandlerWithMetadata<T>[]>>\n\n  constructor() {\n    this.#middleware = { [METHOD_NAME_ALL]: Object.create(null) }\n    this.#routes = { [METHOD_NAME_ALL]: Object.create(null) }\n  }\n\n  add(method: string, path: string, handler: T) {\n    const middleware = this.#middleware\n    const routes = this.#routes\n\n    if (!middleware || !routes) {\n      throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT)\n    }\n\n    if (!middleware[method]) {\n      ;[middleware, routes].forEach((handlerMap) => {\n        handlerMap[method] = Object.create(null)\n        Object.keys(handlerMap[METHOD_NAME_ALL]).forEach((p) => {\n          handlerMap[method][p] = [...handlerMap[METHOD_NAME_ALL][p]]\n        })\n      })\n    }\n\n    if (path === '/*') {\n      path = '*'\n    }\n\n    const paramCount = (path.match(/\\/:/g) || []).length\n\n    if (/\\*$/.test(path)) {\n      const re = buildWildcardRegExp(path)\n      if (method === METHOD_NAME_ALL) {\n        Object.keys(middleware).forEach((m) => {\n          middleware[m][path] ||=\n            findMiddleware(middleware[m], path) ||\n            findMiddleware(middleware[METHOD_NAME_ALL], path) ||\n            []\n        })\n      } else {\n        middleware[method][path] ||=\n          findMiddleware(middleware[method], path) ||\n          findMiddleware(middleware[METHOD_NAME_ALL], path) ||\n          []\n      }\n      Object.keys(middleware).forEach((m) => {\n        if (method === METHOD_NAME_ALL || method === m) {\n          Object.keys(middleware[m]).forEach((p) => {\n            re.test(p) && middleware[m][p].push([handler, paramCount])\n          })\n        }\n      })\n\n      Object.keys(routes).forEach((m) => {\n        if (method === METHOD_NAME_ALL || method === m) {\n          Object.keys(routes[m]).forEach(\n            (p) => re.test(p) && routes[m][p].push([handler, paramCount])\n          )\n        }\n      })\n\n      return\n    }\n\n    const paths = checkOptionalParameter(path) || [path]\n    for (let i = 0, len = paths.length; i < len; i++) {\n      const path = paths[i]\n\n      Object.keys(routes).forEach((m) => {\n        if (method === METHOD_NAME_ALL || method === m) {\n          routes[m][path] ||= [\n            ...(findMiddleware(middleware[m], path) ||\n              findMiddleware(middleware[METHOD_NAME_ALL], path) ||\n              []),\n          ]\n          routes[m][path].push([handler, paramCount - len + i + 1])\n        }\n      })\n    }\n  }\n\n  match: typeof match<Router<T>, T> = match\n\n  protected buildAllMatchers(): MatcherMap<T> {\n    const matchers: MatcherMap<T> = Object.create(null)\n\n    Object.keys(this.#routes!)\n      .concat(Object.keys(this.#middleware!))\n      .forEach((method) => {\n        matchers[method] ||= this.#buildMatcher(method)\n      })\n\n    // Release cache\n    this.#middleware = this.#routes = undefined\n    clearWildcardRegExpCache()\n\n    return matchers\n  }\n\n  #buildMatcher(method: string): Matcher<T> | null {\n    const routes: [string, HandlerWithMetadata<T>[]][] = []\n\n    let hasOwnRoute = method === METHOD_NAME_ALL\n\n    ;[this.#middleware!, this.#routes!].forEach((r) => {\n      const ownRoute = r[method]\n        ? Object.keys(r[method]).map((path) => [path, r[method][path]])\n        : []\n      if (ownRoute.length !== 0) {\n        hasOwnRoute ||= true\n        routes.push(...(ownRoute as [string, HandlerWithMetadata<T>[]][]))\n      } else if (method !== METHOD_NAME_ALL) {\n        routes.push(\n          ...(Object.keys(r[METHOD_NAME_ALL]).map((path) => [path, r[METHOD_NAME_ALL][path]]) as [\n            string,\n            HandlerWithMetadata<T>[],\n          ][])\n        )\n      }\n    })\n\n    if (!hasOwnRoute) {\n      return null\n    } else {\n      return buildMatcherFromPreprocessedRoutes(routes)\n    }\n  }\n}\n"
  },
  {
    "path": "src/router/reg-exp-router/trie.ts",
    "content": "import type { Context, ParamAssocArray } from './node'\nimport { Node } from './node'\n\nexport type ReplacementMap = number[]\n\nexport class Trie {\n  #context: Context = { varIndex: 0 }\n  #root: Node = new Node()\n\n  insert(path: string, index: number, pathErrorCheckOnly: boolean): ParamAssocArray {\n    const paramAssoc: ParamAssocArray = []\n\n    const groups: [string, string][] = [] // [mark, original string]\n    for (let i = 0; ; ) {\n      let replaced = false\n      path = path.replace(/\\{[^}]+\\}/g, (m) => {\n        const mark = `@\\\\${i}`\n        groups[i] = [mark, m]\n        i++\n        replaced = true\n        return mark\n      })\n      if (!replaced) {\n        break\n      }\n    }\n\n    /**\n     *  - pattern (:label, :label{0-9]+}, ...)\n     *  - /* wildcard\n     *  - character\n     */\n    const tokens = path.match(/(?::[^\\/]+)|(?:\\/\\*$)|./g) || []\n    for (let i = groups.length - 1; i >= 0; i--) {\n      const [mark] = groups[i]\n      for (let j = tokens.length - 1; j >= 0; j--) {\n        if (tokens[j].indexOf(mark) !== -1) {\n          tokens[j] = tokens[j].replace(mark, groups[i][1])\n          break\n        }\n      }\n    }\n\n    this.#root.insert(tokens, index, paramAssoc, this.#context, pathErrorCheckOnly)\n\n    return paramAssoc\n  }\n\n  buildRegExp(): [RegExp, ReplacementMap, ReplacementMap] {\n    let regexp = this.#root.buildRegExpStr()\n    if (regexp === '') {\n      return [/^$/, [], []] // never match\n    }\n\n    let captureIndex = 0\n    const indexReplacementMap: ReplacementMap = []\n    const paramReplacementMap: ReplacementMap = []\n\n    regexp = regexp.replace(/#(\\d+)|@(\\d+)|\\.\\*\\$/g, (_, handlerIndex, paramIndex) => {\n      if (handlerIndex !== undefined) {\n        indexReplacementMap[++captureIndex] = Number(handlerIndex)\n        return '$()'\n      }\n      if (paramIndex !== undefined) {\n        paramReplacementMap[Number(paramIndex)] = ++captureIndex\n        return ''\n      }\n\n      return ''\n    })\n\n    return [new RegExp(`^${regexp}`), indexReplacementMap, paramReplacementMap]\n  }\n}\n"
  },
  {
    "path": "src/router/smart-router/index.ts",
    "content": "/**\n * @module\n * SmartRouter for Hono.\n */\n\nexport { SmartRouter } from './router'\n"
  },
  {
    "path": "src/router/smart-router/router.test.ts",
    "content": "import { runTest } from '../common.case.test'\nimport { RegExpRouter } from '../reg-exp-router'\nimport { TrieRouter } from '../trie-router'\nimport { SmartRouter } from './router'\n\ndescribe('SmartRouter', () => {\n  runTest({\n    newRouter: () =>\n      new SmartRouter({\n        routers: [new RegExpRouter(), new TrieRouter()],\n      }),\n  })\n})\n"
  },
  {
    "path": "src/router/smart-router/router.ts",
    "content": "import type { Result, Router } from '../../router'\nimport { MESSAGE_MATCHER_IS_ALREADY_BUILT, UnsupportedPathError } from '../../router'\n\nexport class SmartRouter<T> implements Router<T> {\n  name: string = 'SmartRouter'\n  #routers: Router<T>[] = []\n  #routes?: [string, string, T][] = []\n\n  constructor(init: { routers: Router<T>[] }) {\n    this.#routers = init.routers\n  }\n\n  add(method: string, path: string, handler: T) {\n    if (!this.#routes) {\n      throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT)\n    }\n\n    this.#routes.push([method, path, handler])\n  }\n\n  match(method: string, path: string): Result<T> {\n    if (!this.#routes) {\n      throw new Error('Fatal error')\n    }\n\n    const routers = this.#routers\n    const routes = this.#routes\n\n    const len = routers.length\n    let i = 0\n    let res\n    for (; i < len; i++) {\n      const router = routers[i]\n      try {\n        for (let i = 0, len = routes.length; i < len; i++) {\n          router.add(...routes[i])\n        }\n        res = router.match(method, path)\n      } catch (e) {\n        if (e instanceof UnsupportedPathError) {\n          continue\n        }\n        throw e\n      }\n\n      this.match = router.match.bind(router)\n      this.#routers = [router]\n      this.#routes = undefined\n      break\n    }\n\n    if (i === len) {\n      // not found\n      throw new Error('Fatal error')\n    }\n\n    // e.g. \"SmartRouter + RegExpRouter\"\n    this.name = `SmartRouter + ${this.activeRouter.name}`\n\n    return res as Result<T>\n  }\n\n  get activeRouter(): Router<T> {\n    if (this.#routes || this.#routers.length !== 1) {\n      throw new Error('No active router has been determined yet.')\n    }\n\n    return this.#routers[0]\n  }\n}\n"
  },
  {
    "path": "src/router/trie-router/index.ts",
    "content": "/**\n * @module\n * TrieRouter for Hono.\n */\n\nexport { TrieRouter } from './router'\n"
  },
  {
    "path": "src/router/trie-router/node.test.ts",
    "content": "import { Node } from './node'\n\ndescribe('Root Node', () => {\n  const node = new Node()\n  node.insert('get', '/', 'get root')\n  it('get /', () => {\n    const [res] = node.search('get', '/')\n    expect(res).not.toBeNull()\n    expect(res[0][0]).toEqual('get root')\n    expect(node.search('get', '/hello')[0].length).toBe(0)\n  })\n})\n\ndescribe('Root Node is not defined', () => {\n  const node = new Node()\n  node.insert('get', '/hello', 'get hello')\n  it('get /', () => {\n    expect(node.search('get', '/')[0]).toEqual([])\n  })\n})\n\ndescribe('Get with *', () => {\n  const node = new Node()\n  node.insert('get', '*', 'get all')\n  it('get /', () => {\n    expect(node.search('get', '/')[0].length).toBe(1)\n    expect(node.search('get', '/hello')[0].length).toBe(1)\n  })\n})\n\ndescribe('Get with * including JS reserved words', () => {\n  const node = new Node()\n  node.insert('get', '*', 'get all')\n  it('get /', () => {\n    expect(node.search('get', '/hello/constructor')[0].length).toBe(1)\n    expect(node.search('get', '/hello/__proto__')[0].length).toBe(1)\n  })\n})\n\ndescribe('Basic Usage', () => {\n  const node = new Node()\n  node.insert('get', '/hello', 'get hello')\n  node.insert('post', '/hello', 'post hello')\n  node.insert('get', '/hello/foo', 'get hello foo')\n\n  it('get, post /hello', () => {\n    expect(node.search('get', '/')[0].length).toBe(0)\n    expect(node.search('post', '/')[0].length).toBe(0)\n\n    expect(node.search('get', '/hello')[0][0][0]).toEqual('get hello')\n    expect(node.search('post', '/hello')[0][0][0]).toEqual('post hello')\n    expect(node.search('put', '/hello')[0].length).toBe(0)\n  })\n  it('get /nothing', () => {\n    expect(node.search('get', '/nothing')[0].length).toBe(0)\n  })\n  it('/hello/foo, /hello/bar', () => {\n    expect(node.search('get', '/hello/foo')[0][0][0]).toEqual('get hello foo')\n    expect(node.search('post', '/hello/foo')[0].length).toBe(0)\n    expect(node.search('get', '/hello/bar')[0].length).toBe(0)\n  })\n  it('/hello/foo/bar', () => {\n    expect(node.search('get', '/hello/foo/bar')[0].length).toBe(0)\n  })\n})\n\ndescribe('Name path', () => {\n  const node = new Node()\n  node.insert('get', '/entry/:id', 'get entry')\n  node.insert('get', '/entry/:id/comment/:comment_id', 'get comment')\n  node.insert('get', '/map/:location/events', 'get events')\n  node.insert('get', '/about/:name/address/map', 'get address')\n\n  it('get /entry/123', () => {\n    const [res] = node.search('get', '/entry/123')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('get entry')\n    expect(res[0][1]).not.toBeNull()\n    expect(res[0][1]['id']).toBe('123')\n    expect(res[0][1]['id']).not.toBe('1234')\n  })\n\n  it('get /entry/456/comment', () => {\n    const [res] = node.search('get', '/entry/456/comment')\n    expect(res.length).toBe(0)\n  })\n\n  it('get /entry/789/comment/123', () => {\n    const [res] = node.search('get', '/entry/789/comment/123')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('get comment')\n    expect(res[0][1]['id']).toBe('789')\n    expect(res[0][1]['comment_id']).toBe('123')\n  })\n\n  it('get /map/:location/events', () => {\n    const [res] = node.search('get', '/map/yokohama/events')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('get events')\n    expect(res[0][1]['location']).toBe('yokohama')\n  })\n\n  it('get /about/:name/address/map', () => {\n    const [res] = node.search('get', '/about/foo/address/map')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('get address')\n    expect(res[0][1]['name']).toBe('foo')\n  })\n\n  it('Should not return a previous param value', () => {\n    const node = new Node()\n    node.insert('delete', '/resource/:id', 'resource')\n    const [resA] = node.search('delete', '/resource/a')\n    const [resB] = node.search('delete', '/resource/b')\n    expect(resA).not.toBeNull()\n    expect(resA.length).toBe(1)\n    expect(resA[0][0]).toEqual('resource')\n    expect(resA[0][1]).toEqual({ id: 'a' })\n    expect(resB).not.toBeNull()\n    expect(resB.length).toBe(1)\n    expect(resB[0][0]).toEqual('resource')\n    expect(resB[0][1]).toEqual({ id: 'b' })\n  })\n\n  it('Should return a sorted values', () => {\n    const node = new Node()\n    node.insert('get', '/resource/a', 'A')\n    node.insert('get', '/resource/*', 'Star')\n    const [res] = node.search('get', '/resource/a')\n    expect(res).not.toBeNull()\n    expect(res.length).toBe(2)\n    expect(res[0][0]).toEqual('A')\n    expect(res[1][0]).toEqual('Star')\n  })\n})\n\ndescribe('Name path - Multiple route', () => {\n  const node = new Node()\n\n  node.insert('get', '/:type/:id', 'common')\n  node.insert('get', '/posts/:id', 'specialized')\n\n  it('get /posts/123', () => {\n    const [res] = node.search('get', '/posts/123')\n    expect(res.length).toBe(2)\n    expect(res[0][0]).toEqual('common')\n    expect(res[0][1]['id']).toBe('123')\n    expect(res[1][0]).toEqual('specialized')\n    expect(res[1][1]['id']).toBe('123')\n  })\n})\n\ndescribe('Param prefix', () => {\n  const node = new Node()\n\n  node.insert('get', '/:foo', 'onepart')\n  node.insert('get', '/:bar/:baz', 'twopart')\n\n  it('get /hello', () => {\n    const [res] = node.search('get', '/hello')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('onepart')\n    expect(res[0][1]['foo']).toBe('hello')\n  })\n\n  it('get /hello/world', () => {\n    const [res] = node.search('get', '/hello/world')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('twopart')\n    expect(res[0][1]['bar']).toBe('hello')\n    expect(res[0][1]['baz']).toBe('world')\n  })\n})\n\ndescribe('Named params and a wildcard', () => {\n  const node = new Node()\n\n  node.insert('get', '/:id/*', 'onepart')\n\n  it('get /', () => {\n    const [res] = node.search('get', '/')\n    expect(res.length).toBe(0)\n  })\n\n  it('get /foo', () => {\n    const [res] = node.search('get', '/foo')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('onepart')\n    expect(res[0][1]['id']).toEqual('foo')\n  })\n\n  it('get /foo/bar', () => {\n    const [res] = node.search('get', '/foo/bar')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('onepart')\n    expect(res[0][1]['id']).toEqual('foo')\n  })\n})\n\ndescribe('Wildcard', () => {\n  const node = new Node()\n  node.insert('get', '/wildcard-abc/*/wildcard-efg', 'wildcard')\n  it('/wildcard-abc/xxxxxx/wildcard-efg', () => {\n    const [res] = node.search('get', '/wildcard-abc/xxxxxx/wildcard-efg')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('wildcard')\n  })\n  node.insert('get', '/wildcard-abc/*/wildcard-efg/hijk', 'wildcard')\n  it('/wildcard-abc/xxxxxx/wildcard-efg/hijk', () => {\n    const [res] = node.search('get', '/wildcard-abc/xxxxxx/wildcard-efg/hijk')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('wildcard')\n  })\n})\n\ndescribe('Regexp', () => {\n  const node = new Node()\n  node.insert('get', '/regex-abc/:id{[0-9]+}/comment/:comment_id{[a-z]+}', 'regexp')\n  it('/regexp-abc/123/comment/abc', () => {\n    const [res] = node.search('get', '/regex-abc/123/comment/abc')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('regexp')\n    expect(res[0][1]['id']).toBe('123')\n    expect(res[0][1]['comment_id']).toBe('abc')\n  })\n  it('/regexp-abc/abc', () => {\n    const [res] = node.search('get', '/regex-abc/abc')\n    expect(res.length).toBe(0)\n  })\n  it('/regexp-abc/123/comment/123', () => {\n    const [res] = node.search('get', '/regex-abc/123/comment/123')\n    expect(res.length).toBe(0)\n  })\n})\n\ndescribe('All', () => {\n  const node = new Node()\n  node.insert('ALL', '/all-methods', 'all methods') // ALL\n  it('/all-methods', () => {\n    let [res] = node.search('get', '/all-methods')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('all methods')\n    ;[res] = node.search('put', '/all-methods')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('all methods')\n  })\n})\n\ndescribe('Special Wildcard', () => {\n  const node = new Node()\n  node.insert('ALL', '*', 'match all')\n\n  it('/foo', () => {\n    const [res] = node.search('get', '/foo')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('match all')\n  })\n  it('/hello', () => {\n    const [res] = node.search('get', '/hello')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('match all')\n  })\n  it('/hello/foo', () => {\n    const [res] = node.search('get', '/hello/foo')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('match all')\n  })\n})\n\ndescribe('Special Wildcard deeply', () => {\n  const node = new Node()\n  node.insert('ALL', '/hello/*', 'match hello')\n  it('/hello', () => {\n    const [res] = node.search('get', '/hello')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('match hello')\n  })\n  it('/hello/foo', () => {\n    const [res] = node.search('get', '/hello/foo')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('match hello')\n  })\n})\n\ndescribe('Default with wildcard', () => {\n  const node = new Node()\n  node.insert('ALL', '/api/*', 'fallback')\n  node.insert('ALL', '/api/abc', 'match api')\n  it('/api/abc', () => {\n    const [res] = node.search('get', '/api/abc')\n    expect(res.length).toBe(2)\n    expect(res[0][0]).toEqual('fallback')\n    expect(res[1][0]).toEqual('match api')\n  })\n  it('/api/def', () => {\n    const [res] = node.search('get', '/api/def')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('fallback')\n  })\n})\n\ndescribe('Multi match', () => {\n  describe('Basic', () => {\n    const node = new Node()\n    node.insert('get', '*', 'GET *')\n    node.insert('get', '/abc/*', 'GET /abc/*')\n    node.insert('get', '/abc/*/edf', 'GET /abc/*/edf')\n    node.insert('get', '/abc/edf', 'GET /abc/edf')\n    node.insert('get', '/abc/*/ghi/jkl', 'GET /abc/*/ghi/jkl')\n    it('get /abc/edf', () => {\n      const [res] = node.search('get', '/abc/edf')\n      expect(res.length).toBe(3)\n      expect(res[0][0]).toEqual('GET *')\n      expect(res[1][0]).toEqual('GET /abc/*')\n      expect(res[2][0]).toEqual('GET /abc/edf')\n    })\n    it('get /abc/xxx/edf', () => {\n      const [res] = node.search('get', '/abc/xxx/edf')\n      expect(res.length).toBe(3)\n      expect(res[0][0]).toEqual('GET *')\n      expect(res[1][0]).toEqual('GET /abc/*')\n      expect(res[2][0]).toEqual('GET /abc/*/edf')\n    })\n    it('get /', () => {\n      const [res] = node.search('get', '/')\n      expect(res.length).toBe(1)\n      expect(res[0][0]).toEqual('GET *')\n    })\n    it('post /', () => {\n      const [res] = node.search('post', '/')\n      expect(res.length).toBe(0)\n    })\n    it('get /abc/edf/ghi', () => {\n      const [res] = node.search('get', '/abc/edf/ghi')\n      expect(res.length).toBe(2)\n      expect(res[0][0]).toEqual('GET *')\n      expect(res[1][0]).toEqual('GET /abc/*')\n    })\n  })\n  describe('Blog', () => {\n    const node = new Node()\n    node.insert('get', '*', 'middleware a') // 0.1\n    node.insert('ALL', '*', 'middleware b') // 0.2 <===\n    node.insert('get', '/entry', 'get entries') // 1.3\n    node.insert('post', '/entry/*', 'middleware c') // 1.4 <===\n    node.insert('post', '/entry', 'post entry') // 1.5 <===\n    node.insert('get', '/entry/:id', 'get entry') // 2.6\n    node.insert('get', '/entry/:id/comment/:comment_id', 'get comment') // 4.7\n    it('get /entry/123', async () => {\n      const [res] = node.search('get', '/entry/123')\n      expect(res.length).toBe(3)\n      expect(res[0][0]).toEqual('middleware a')\n      expect(res[0][1]['id']).toBe(undefined)\n      expect(res[1][0]).toEqual('middleware b')\n      expect(res[1][1]['id']).toBe(undefined)\n      expect(res[2][0]).toEqual('get entry')\n      expect(res[2][1]['id']).toBe('123')\n    })\n    it('get /entry/123/comment/456', async () => {\n      const [res] = node.search('get', '/entry/123/comment/456')\n      expect(res.length).toBe(3)\n      expect(res[0][0]).toEqual('middleware a')\n      expect(res[0][1]['id']).toBe(undefined)\n      expect(res[0][1]['comment_id']).toBe(undefined)\n      expect(res[1][0]).toEqual('middleware b')\n      expect(res[1][1]['id']).toBe(undefined)\n      expect(res[1][1]['comment_id']).toBe(undefined)\n      expect(res[2][0]).toEqual('get comment')\n      expect(res[2][1]['id']).toBe('123')\n      expect(res[2][1]['comment_id']).toBe('456')\n    })\n    it('post /entry', async () => {\n      const [res] = node.search('post', '/entry')\n      expect(res.length).toBe(3)\n      expect(res[0][0]).toEqual('middleware b')\n      expect(res[1][0]).toEqual('middleware c')\n      expect(res[2][0]).toEqual('post entry')\n    })\n    it('delete /entry', async () => {\n      const [res] = node.search('delete', '/entry')\n      expect(res.length).toBe(1)\n      expect(res[0][0]).toEqual('middleware b')\n    })\n  })\n  describe('ALL', () => {\n    const node = new Node()\n    node.insert('ALL', '*', 'ALL *')\n    node.insert('ALL', '/abc/*', 'ALL /abc/*')\n    node.insert('ALL', '/abc/*/def', 'ALL /abc/*/def')\n    it('get /', () => {\n      const [res] = node.search('get', '/')\n      expect(res.length).toBe(1)\n      expect(res[0][0]).toEqual('ALL *')\n    })\n    it('post /abc', () => {\n      const [res] = node.search('post', '/abc')\n      expect(res.length).toBe(2)\n      expect(res[0][0]).toEqual('ALL *')\n      expect(res[1][0]).toEqual('ALL /abc/*')\n    })\n    it('delete /abc/xxx/def', () => {\n      const [res] = node.search('post', '/abc/xxx/def')\n      expect(res.length).toBe(3)\n      expect(res[0][0]).toEqual('ALL *')\n      expect(res[1][0]).toEqual('ALL /abc/*')\n      expect(res[2][0]).toEqual('ALL /abc/*/def')\n    })\n  })\n  describe('Regexp', () => {\n    const node = new Node()\n    node.insert('get', '/regex-abc/:id{[0-9]+}/*', 'middleware a')\n    node.insert('get', '/regex-abc/:id{[0-9]+}/def', 'regexp')\n    it('/regexp-abc/123/def', () => {\n      const [res] = node.search('get', '/regex-abc/123/def')\n      expect(res.length).toBe(2)\n      expect(res[0][0]).toEqual('middleware a')\n      expect(res[0][1]['id']).toBe('123')\n      expect(res[1][0]).toEqual('regexp')\n      expect(res[1][1]['id']).toBe('123')\n    })\n    it('/regexp-abc/123', () => {\n      const [res] = node.search('get', '/regex-abc/123/ghi')\n      expect(res.length).toBe(1)\n      expect(res[0][0]).toEqual('middleware a')\n    })\n  })\n  describe('Trailing slash', () => {\n    const node = new Node()\n    node.insert('get', '/book', 'GET /book')\n    node.insert('get', '/book/:id', 'GET /book/:id')\n    it('get /book', () => {\n      const [res] = node.search('get', '/book')\n      expect(res.length).toBe(1)\n    })\n    it('get /book/', () => {\n      const [res] = node.search('get', '/book/')\n      expect(res.length).toBe(0)\n    })\n  })\n  describe('Same path', () => {\n    const node = new Node()\n    node.insert('get', '/hey', 'Middleware A')\n    node.insert('get', '/hey', 'Middleware B')\n    it('get /hey', () => {\n      const [res] = node.search('get', '/hey')\n      expect(res.length).toBe(2)\n      expect(res[0][0]).toEqual('Middleware A')\n      expect(res[1][0]).toEqual('Middleware B')\n    })\n  })\n  describe('Including slashes', () => {\n    const node = new Node()\n    node.insert('get', '/js/:filename{[a-z0-9/]+.js}', 'any file')\n    node.insert('get', '/js/main.js', 'main.js')\n    it('get /js/main.js', () => {\n      const [res] = node.search('get', '/js/main.js')\n      expect(res.length).toBe(2)\n      expect(res[0][0]).toEqual('any file')\n      expect(res[0][1]).toEqual({ filename: 'main.js' })\n      expect(res[1][0]).toEqual('main.js')\n      expect(res[1][1]).toEqual({})\n    })\n    it('get /js/chunk/123.js', () => {\n      const [res] = node.search('get', '/js/chunk/123.js')\n      expect(res.length).toBe(1)\n      expect(res[0][0]).toEqual('any file')\n      expect(res[0][1]).toEqual({ filename: 'chunk/123.js' })\n    })\n    it('get /js/chunk/nest/123.js', () => {\n      const [res] = node.search('get', '/js/chunk/nest/123.js')\n      expect(res.length).toBe(1)\n      expect(res[0][0]).toEqual('any file')\n      expect(res[0][1]).toEqual({ filename: 'chunk/nest/123.js' })\n    })\n  })\n  describe('REST API', () => {\n    const node = new Node()\n    node.insert('get', '/users/:username{[a-z]+}', 'profile')\n    node.insert('get', '/users/:username{[a-z]+}/posts', 'posts')\n    it('get /users/hono', () => {\n      const [res] = node.search('get', '/users/hono')\n      expect(res.length).toBe(1)\n      expect(res[0][0]).toEqual('profile')\n    })\n    it('get /users/hono/posts', () => {\n      const [res] = node.search('get', '/users/hono/posts')\n      expect(res.length).toBe(1)\n      expect(res[0][0]).toEqual('posts')\n    })\n  })\n})\n\ndescribe('Duplicate param name', () => {\n  it('self', () => {\n    const node = new Node()\n    node.insert('get', '/:id/:id', 'foo')\n    const [res] = node.search('get', '/123/456')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toBe('foo')\n    expect(res[0][1]['id']).toBe('123')\n  })\n\n  describe('parent', () => {\n    const node = new Node()\n    node.insert('get', '/:id/:action', 'foo')\n    node.insert('get', '/posts/:id', 'bar')\n    node.insert('get', '/posts/:id/comments/:comment_id', 'comment')\n\n    it('get /123/action', () => {\n      const [res] = node.search('get', '/123/action')\n      expect(res.length).toBe(1)\n      expect(res[0][0]).toBe('foo')\n      expect(res[0][1]).toEqual({ id: '123', action: 'action' })\n    })\n  })\n\n  it('get /posts/456 for comments', () => {\n    const node = new Node()\n    node.insert('get', '/posts/:id/comments/:comment_id', 'comment')\n    const [res] = node.search('get', '/posts/abc/comments/edf')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toBe('comment')\n    expect(res[0][1]).toEqual({ id: 'abc', comment_id: 'edf' })\n  })\n\n  describe('child', () => {\n    const node = new Node()\n    node.insert('get', '/posts/:id', 'foo')\n    node.insert('get', '/:id/:action', 'bar')\n    it('get /posts/action', () => {\n      const [res] = node.search('get', '/posts/action')\n      expect(res.length).toBe(2)\n      expect(res[0][0]).toBe('foo')\n      expect(res[0][1]).toEqual({ id: 'action' })\n      expect(res[1][0]).toBe('bar')\n      expect(res[1][1]).toEqual({ id: 'posts', action: 'action' })\n    })\n  })\n\n  describe('regular expression', () => {\n    const node = new Node()\n    node.insert('get', '/:id/:action{create|update}', 'foo')\n    node.insert('get', '/:id/:action{delete}', 'bar')\n    it('get /123/create', () => {\n      const [res] = node.search('get', '/123/create')\n      expect(res.length).toBe(1)\n      expect(res[0][0]).toBe('foo')\n      expect(res[0][1]).toEqual({ id: '123', action: 'create' })\n    })\n    it('get /123/delete', () => {\n      const [res] = node.search('get', '/123/delete')\n      expect(res.length).toBe(1)\n      expect(res[0][0]).toBe('bar')\n      expect(res[0][1]).toEqual({ id: '123', action: 'delete' })\n    })\n  })\n})\n\ndescribe('Sort Order', () => {\n  describe('Basic', () => {\n    const node = new Node()\n    node.insert('get', '*', 'a')\n    node.insert('get', '/page', '/page')\n    node.insert('get', '/:slug', '/:slug')\n\n    it('get /page', () => {\n      const [res] = node.search('get', '/page')\n      expect(res.length).toBe(3)\n      expect(res[0][0]).toEqual('a')\n      expect(res[1][0]).toEqual('/page')\n      expect(res[2][0]).toEqual('/:slug')\n    })\n  })\n\n  describe('With Named path', () => {\n    const node = new Node()\n    node.insert('get', '*', 'a')\n    node.insert('get', '/posts/:id', '/posts/:id')\n    node.insert('get', '/:type/:id', '/:type/:id')\n\n    it('get /posts/123', () => {\n      const [res] = node.search('get', '/posts/123')\n      expect(res.length).toBe(3)\n      expect(res[0][0]).toEqual('a')\n      expect(res[1][0]).toEqual('/posts/:id')\n      expect(res[2][0]).toEqual('/:type/:id')\n    })\n  })\n\n  describe('With Wildcards', () => {\n    const node = new Node()\n    node.insert('get', '/api/*', '1st')\n    node.insert('get', '/api/*', '2nd')\n    node.insert('get', '/api/posts/:id', '3rd')\n    node.insert('get', '/api/*', '4th')\n\n    it('get /api/posts/123', () => {\n      const [res] = node.search('get', '/api/posts/123')\n      expect(res.length).toBe(4)\n      expect(res[0][0]).toEqual('1st')\n      expect(res[1][0]).toEqual('2nd')\n      expect(res[2][0]).toEqual('3rd')\n      expect(res[3][0]).toEqual('4th')\n    })\n  })\n\n  describe('With special Wildcard', () => {\n    const node = new Node()\n    node.insert('get', '/posts', '/posts') // 1.1\n    node.insert('get', '/posts/*', '/posts/*') // 1.2\n    node.insert('get', '/posts/:id', '/posts/:id') // 2.3\n\n    it('get /posts', () => {\n      const [res] = node.search('get', '/posts')\n      expect(res.length).toBe(2)\n      expect(res[0][0]).toEqual('/posts')\n      expect(res[1][0]).toEqual('/posts/*')\n    })\n  })\n\n  describe('Complex', () => {\n    const node = new Node()\n    node.insert('get', '/api', 'a') // not match\n    node.insert('get', '/api/*', 'b') // match\n    node.insert('get', '/api/:type', 'c') // not match\n    node.insert('get', '/api/:type/:id', 'd') // match\n    node.insert('get', '/api/posts/:id', 'e') // match\n    node.insert('get', '/api/posts/123', 'f') // match\n    node.insert('get', '/*/*/:id', 'g') // match\n    node.insert('get', '/api/posts/*/comment', 'h') // not match\n    node.insert('get', '*', 'i') // match\n    node.insert('get', '*', 'j') // match\n\n    it('get /api/posts/123', () => {\n      const [res] = node.search('get', '/api/posts/123')\n      expect(res.length).toBe(7)\n      expect(res[0][0]).toEqual('b')\n      expect(res[1][0]).toEqual('d')\n      expect(res[2][0]).toEqual('e')\n      expect(res[3][0]).toEqual('f')\n      expect(res[4][0]).toEqual('g')\n      expect(res[5][0]).toEqual('i')\n      expect(res[6][0]).toEqual('j')\n    })\n  })\n\n  describe('Multi match', () => {\n    const node = new Node()\n    node.insert('get', '*', 'GET *') // 0.1\n    node.insert('get', '/abc/*', 'GET /abc/*') // 1.2\n    node.insert('get', '/abc/edf', 'GET /abc/edf') // 2.3\n    node.insert('get', '/abc/*/ghi/jkl', 'GET /abc/*/ghi/jkl') // 4.4\n    it('get /abc/edf', () => {\n      const [res] = node.search('get', '/abc/edf')\n      expect(res.length).toBe(3)\n      expect(res[0][0]).toEqual('GET *')\n      expect(res[1][0]).toEqual('GET /abc/*')\n      expect(res[2][0]).toEqual('GET /abc/edf')\n    })\n  })\n\n  describe('Multi match', () => {\n    const node = new Node()\n\n    node.insert('get', '/api/*', 'a') // 2.1 for /api/entry\n    node.insert('get', '/api/entry', 'entry') // 2.2\n    node.insert('ALL', '/api/*', 'b') // 2.3 for /api/entry\n\n    it('get /api/entry', async () => {\n      const [res] = node.search('get', '/api/entry')\n      expect(res.length).toBe(3)\n      expect(res[0][0]).toEqual('a')\n      expect(res[1][0]).toEqual('entry')\n      expect(res[2][0]).toEqual('b')\n    })\n  })\n\n  describe('fallback', () => {\n    describe('Blog - failed', () => {\n      const node = new Node()\n      node.insert('post', '/entry', 'post entry') // 1.1\n      node.insert('post', '/entry/*', 'fallback') // 1.2\n      node.insert('get', '/entry/:id', 'get entry') // 2.3\n      it('post /entry', async () => {\n        const [res] = node.search('post', '/entry')\n        expect(res.length).toBe(2)\n        expect(res[0][0]).toEqual('post entry')\n        expect(res[1][0]).toEqual('fallback')\n      })\n    })\n  })\n  describe('page', () => {\n    const node = new Node()\n    node.insert('get', '/page', 'page') // 1.1\n    node.insert('ALL', '/*', 'fallback') // 1.2\n    it('get /page', async () => {\n      const [res] = node.search('get', '/page')\n      expect(res.length).toBe(2)\n      expect(res[0][0]).toEqual('page')\n      expect(res[1][0]).toEqual('fallback')\n    })\n  })\n})\n\ndescribe('star', () => {\n  const node = new Node()\n  node.insert('get', '/', '/')\n  node.insert('get', '/*', '/*')\n  node.insert('get', '*', '*')\n\n  node.insert('get', '/x', '/x')\n  node.insert('get', '/x/*', '/x/*')\n\n  it('top', async () => {\n    const [res] = node.search('get', '/')\n    expect(res.length).toBe(3)\n    expect(res[0][0]).toEqual('/')\n    expect(res[1][0]).toEqual('/*')\n    expect(res[2][0]).toEqual('*')\n  })\n\n  it('Under a certain path', async () => {\n    const [res] = node.search('get', '/x')\n    expect(res.length).toBe(4)\n    expect(res[0][0]).toEqual('/*')\n    expect(res[1][0]).toEqual('*')\n    expect(res[2][0]).toEqual('/x')\n    expect(res[3][0]).toEqual('/x/*')\n  })\n})\n\ndescribe('Routing order With named parameters', () => {\n  const node = new Node()\n  node.insert('get', '/book/a', 'no-slug')\n  node.insert('get', '/book/:slug', 'slug')\n  node.insert('get', '/book/b', 'no-slug-b')\n  it('/book/a', () => {\n    const [res] = node.search('get', '/book/a')\n    expect(res).not.toBeNull()\n    expect(res.length).toBe(2)\n    expect(res[0][0]).toEqual('no-slug')\n    expect(res[0][1]).toEqual({})\n    expect(res[1][0]).toEqual('slug')\n    expect(res[1][1]).toEqual({ slug: 'a' })\n  })\n  it('/book/foo', () => {\n    const [res] = node.search('get', '/book/foo')\n    expect(res).not.toBeNull()\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('slug')\n    expect(res[0][1]).toEqual({ slug: 'foo' })\n    expect(res[0][1]['slug']).toBe('foo')\n  })\n  it('/book/b', () => {\n    const [res] = node.search('get', '/book/b')\n    expect(res).not.toBeNull()\n    expect(res.length).toBe(2)\n    expect(res[0][0]).toEqual('slug')\n    expect(res[0][1]).toEqual({ slug: 'b' })\n    expect(res[1][0]).toEqual('no-slug-b')\n    expect(res[1][1]).toEqual({})\n  })\n})\n\ndescribe('The same name is used for path params', () => {\n  describe('Basic', () => {\n    const node = new Node()\n    node.insert('get', '/:a/:b/:c', 'abc')\n    node.insert('get', '/:a/:b/:c/:d', 'abcd')\n    it('/1/2/3', () => {\n      const [res] = node.search('get', '/1/2/3')\n      expect(res).not.toBeNull()\n      expect(res.length).toBe(1)\n      expect(res[0][0]).toEqual('abc')\n      expect(res[0][1]).toEqual({ a: '1', b: '2', c: '3' })\n    })\n  })\n\n  describe('Complex', () => {\n    const node = new Node()\n    node.insert('get', '/:a', 'a')\n    node.insert('get', '/:b/:a', 'ba')\n    it('/about/me', () => {\n      const [res] = node.search('get', '/about/me')\n      expect(res).not.toBeNull()\n      expect(res.length).toBe(1)\n      expect(res[0][0]).toEqual('ba')\n      expect(res[0][1]).toEqual({ b: 'about', a: 'me' })\n    })\n  })\n\n  describe('Complex with tails', () => {\n    const node = new Node()\n    node.insert('get', '/:id/:id2/comments', 'a')\n    node.insert('get', '/posts/:id/comments', 'b')\n    it('/posts/123/comments', () => {\n      const [res] = node.search('get', '/posts/123/comments')\n      expect(res).not.toBeNull()\n      expect(res.length).toBe(2)\n      expect(res[0][0]).toEqual('a')\n      expect(res[0][1]).toEqual({ id: 'posts', id2: '123' })\n      expect(res[1][0]).toEqual('b')\n      expect(res[1][1]).toEqual({ id: '123' })\n    })\n  })\n})\n\ndescribe('Node with initial method and handler', () => {\n  it('should create a node with method and handler via constructor', () => {\n    const node = new Node('get', 'initial handler')\n    node.insert('get', '/hello', 'hello handler')\n    const [res] = node.search('get', '/hello')\n    expect(res.length).toBe(1)\n    expect(res[0][0]).toEqual('hello handler')\n  })\n})\n"
  },
  {
    "path": "src/router/trie-router/node.ts",
    "content": "import type { Params } from '../../router'\nimport { METHOD_NAME_ALL } from '../../router'\nimport type { Pattern } from '../../utils/url'\nimport { getPattern, splitPath, splitRoutingPath } from '../../utils/url'\n\ntype HandlerSet<T> = {\n  handler: T\n  possibleKeys: string[]\n  score: number\n}\n\ntype HandlerParamsSet<T> = HandlerSet<T> & {\n  params: Record<string, string>\n}\n\nconst emptyParams = Object.create(null)\n\nconst hasChildren = (children: Record<string, unknown>): boolean => {\n  for (const _ in children) {\n    return true\n  }\n  return false\n}\n\nexport class Node<T> {\n  #methods: Record<string, HandlerSet<T>>[]\n\n  #children: Record<string, Node<T>>\n  #patterns: Pattern[]\n  #order: number = 0\n  #params: Record<string, string> = emptyParams\n\n  constructor(method?: string, handler?: T, children?: Record<string, Node<T>>) {\n    this.#children = children || Object.create(null)\n    this.#methods = []\n    if (method && handler) {\n      const m: Record<string, HandlerSet<T>> = Object.create(null)\n      m[method] = { handler, possibleKeys: [], score: 0 }\n      this.#methods = [m]\n    }\n    this.#patterns = []\n  }\n\n  insert(method: string, path: string, handler: T): Node<T> {\n    this.#order = ++this.#order\n\n    // eslint-disable-next-line @typescript-eslint/no-this-alias\n    let curNode: Node<T> = this\n    const parts = splitRoutingPath(path)\n\n    const possibleKeys: string[] = []\n\n    for (let i = 0, len = parts.length; i < len; i++) {\n      const p: string = parts[i]\n      const nextP = parts[i + 1]\n      const pattern = getPattern(p, nextP)\n      const key = Array.isArray(pattern) ? pattern[0] : p\n\n      if (key in curNode.#children) {\n        curNode = curNode.#children[key]\n        if (pattern) {\n          possibleKeys.push(pattern[1])\n        }\n        continue\n      }\n\n      curNode.#children[key] = new Node()\n\n      if (pattern) {\n        curNode.#patterns.push(pattern)\n        possibleKeys.push(pattern[1])\n      }\n      curNode = curNode.#children[key]\n    }\n\n    curNode.#methods.push({\n      [method]: {\n        handler,\n        possibleKeys: possibleKeys.filter((v, i, a) => a.indexOf(v) === i),\n        score: this.#order,\n      },\n    })\n\n    return curNode\n  }\n\n  #pushHandlerSets(\n    handlerSets: HandlerParamsSet<T>[],\n    node: Node<T>,\n    method: string,\n    nodeParams: Record<string, string>,\n    params?: Record<string, string>\n  ): void {\n    for (let i = 0, len = node.#methods.length; i < len; i++) {\n      const m = node.#methods[i]\n      const handlerSet = (m[method] || m[METHOD_NAME_ALL]) as HandlerParamsSet<T>\n      const processedSet: Record<number, boolean> = {}\n      if (handlerSet !== undefined) {\n        handlerSet.params = Object.create(null)\n        handlerSets.push(handlerSet)\n        if (nodeParams !== emptyParams || (params && params !== emptyParams)) {\n          for (let i = 0, len = handlerSet.possibleKeys.length; i < len; i++) {\n            const key = handlerSet.possibleKeys[i]\n            const processed = processedSet[handlerSet.score]\n            handlerSet.params[key] =\n              params?.[key] && !processed ? params[key] : (nodeParams[key] ?? params?.[key])\n            processedSet[handlerSet.score] = true\n          }\n        }\n      }\n    }\n  }\n\n  search(method: string, path: string): [[T, Params][]] {\n    const handlerSets: HandlerParamsSet<T>[] = []\n    this.#params = emptyParams\n\n    // eslint-disable-next-line @typescript-eslint/no-this-alias\n    const curNode: Node<T> = this\n    let curNodes = [curNode]\n    const parts = splitPath(path)\n    const curNodesQueue: Node<T>[][] = []\n\n    const len = parts.length\n    let partOffsets: number[] | null = null\n\n    for (let i = 0; i < len; i++) {\n      const part: string = parts[i]\n      const isLast = i === len - 1\n      const tempNodes: Node<T>[] = []\n\n      for (let j = 0, len2 = curNodes.length; j < len2; j++) {\n        const node = curNodes[j]\n        const nextNode = node.#children[part]\n\n        if (nextNode) {\n          nextNode.#params = node.#params\n          if (isLast) {\n            // '/hello/*' => match '/hello'\n            if (nextNode.#children['*']) {\n              this.#pushHandlerSets(handlerSets, nextNode.#children['*'], method, node.#params)\n            }\n            this.#pushHandlerSets(handlerSets, nextNode, method, node.#params)\n          } else {\n            tempNodes.push(nextNode)\n          }\n        }\n\n        for (let k = 0, len3 = node.#patterns.length; k < len3; k++) {\n          const pattern = node.#patterns[k]\n          const params = node.#params === emptyParams ? {} : { ...node.#params }\n\n          // Wildcard\n          // '/hello/*/foo' => match /hello/bar/foo\n          if (pattern === '*') {\n            const astNode = node.#children['*']\n            if (astNode) {\n              this.#pushHandlerSets(handlerSets, astNode, method, node.#params)\n              astNode.#params = params\n              tempNodes.push(astNode)\n            }\n            continue\n          }\n\n          const [key, name, matcher] = pattern\n\n          if (!part && !(matcher instanceof RegExp)) {\n            continue\n          }\n\n          const child = node.#children[key]\n\n          // `/js/:filename{[a-z]+.js}` => match /js/chunk/123.js\n          if (matcher instanceof RegExp) {\n            if (partOffsets === null) {\n              partOffsets = new Array(len)\n              let offset = path[0] === '/' ? 1 : 0\n              for (let p = 0; p < len; p++) {\n                partOffsets[p] = offset\n                offset += parts[p].length + 1\n              }\n            }\n            const restPathString = path.substring(partOffsets[i])\n\n            const m = matcher.exec(restPathString)\n            if (m) {\n              params[name] = m[0]\n              this.#pushHandlerSets(handlerSets, child, method, node.#params, params)\n\n              if (hasChildren(child.#children)) {\n                child.#params = params\n                const componentCount = m[0].match(/\\//)?.length ?? 0\n                const targetCurNodes = (curNodesQueue[componentCount] ||= [])\n                targetCurNodes.push(child)\n              }\n\n              continue\n            }\n          }\n\n          if (matcher === true || matcher.test(part)) {\n            params[name] = part\n            if (isLast) {\n              this.#pushHandlerSets(handlerSets, child, method, params, node.#params)\n              if (child.#children['*']) {\n                this.#pushHandlerSets(\n                  handlerSets,\n                  child.#children['*'],\n                  method,\n                  params,\n                  node.#params\n                )\n              }\n            } else {\n              child.#params = params\n              tempNodes.push(child)\n            }\n          }\n        }\n      }\n\n      const shifted = curNodesQueue.shift()\n      curNodes = shifted ? tempNodes.concat(shifted) : tempNodes\n    }\n\n    if (handlerSets.length > 1) {\n      handlerSets.sort((a, b) => {\n        return a.score - b.score\n      })\n    }\n\n    return [handlerSets.map(({ handler, params }) => [handler, params] as [T, Params])]\n  }\n}\n"
  },
  {
    "path": "src/router/trie-router/router.test.ts",
    "content": "import { runTest } from '../common.case.test'\nimport { TrieRouter } from './router'\n\ndescribe('TrieRouter', () => {\n  runTest({\n    newRouter: () => new TrieRouter(),\n  })\n})\n"
  },
  {
    "path": "src/router/trie-router/router.ts",
    "content": "import type { Result, Router } from '../../router'\nimport { checkOptionalParameter } from '../../utils/url'\nimport { Node } from './node'\n\nexport class TrieRouter<T> implements Router<T> {\n  name: string = 'TrieRouter'\n  #node: Node<T>\n\n  constructor() {\n    this.#node = new Node()\n  }\n\n  add(method: string, path: string, handler: T) {\n    const results = checkOptionalParameter(path)\n    if (results) {\n      for (let i = 0, len = results.length; i < len; i++) {\n        this.#node.insert(method, results[i], handler)\n      }\n      return\n    }\n\n    this.#node.insert(method, path, handler)\n  }\n\n  match(method: string, path: string): Result<T> {\n    return this.#node.search(method, path)\n  }\n}\n"
  },
  {
    "path": "src/router.ts",
    "content": "/**\n * @module\n * This module provides types definitions and variables for the routers.\n */\n\n/**\n * Constant representing all HTTP methods in uppercase.\n */\nexport const METHOD_NAME_ALL = 'ALL' as const\n/**\n * Constant representing all HTTP methods in lowercase.\n */\nexport const METHOD_NAME_ALL_LOWERCASE = 'all' as const\n/**\n * Array of supported HTTP methods.\n */\nexport const METHODS = ['get', 'post', 'put', 'delete', 'options', 'patch'] as const\n/**\n * Error message indicating that a route cannot be added because the matcher is already built.\n */\nexport const MESSAGE_MATCHER_IS_ALREADY_BUILT =\n  'Can not add a route since the matcher is already built.'\n\n/**\n * Interface representing a router.\n *\n * @template T - The type of the handler.\n */\nexport interface Router<T> {\n  /**\n   * The name of the router.\n   */\n  name: string\n\n  /**\n   * Adds a route to the router.\n   *\n   * @param method - The HTTP method (e.g., 'get', 'post').\n   * @param path - The path for the route.\n   * @param handler - The handler for the route.\n   */\n  add(method: string, path: string, handler: T): void\n\n  /**\n   * Matches a route based on the given method and path.\n   *\n   * @param method - The HTTP method (e.g., 'get', 'post').\n   * @param path - The path to match.\n   * @returns The result of the match.\n   */\n  match(method: string, path: string): Result<T>\n}\n\n/**\n * Type representing a map of parameter indices.\n */\nexport type ParamIndexMap = Record<string, number>\n/**\n * Type representing a stash of parameters.\n */\nexport type ParamStash = string[]\n/**\n * Type representing a map of parameters.\n */\nexport type Params = Record<string, string>\n/**\n * Type representing the result of a route match.\n *\n * The result can be in one of two formats:\n * 1. An array of handlers with their corresponding parameter index maps, followed by a parameter stash.\n * 2. An array of handlers with their corresponding parameter maps.\n *\n * Example:\n *\n * [[handler, paramIndexMap][], paramArray]\n * ```typescript\n * [\n *   [\n *     [middlewareA, {}],                     // '*'\n *     [funcA,       {'id': 0}],              // '/user/:id/*'\n *     [funcB,       {'id': 0, 'action': 1}], // '/user/:id/:action'\n *   ],\n *   ['123', 'abc']\n * ]\n * ```\n *\n * [[handler, params][]]\n * ```typescript\n * [\n *   [\n *     [middlewareA, {}],                             // '*'\n *     [funcA,       {'id': '123'}],                  // '/user/:id/*'\n *     [funcB,       {'id': '123', 'action': 'abc'}], // '/user/:id/:action'\n *   ]\n * ]\n * ```\n */\nexport type Result<T> = [[T, ParamIndexMap][], ParamStash] | [[T, Params][]]\n\n/**\n * Error class representing an unsupported path error.\n */\nexport class UnsupportedPathError extends Error {}\n"
  },
  {
    "path": "src/types.test.ts",
    "content": "/* eslint-disable @typescript-eslint/no-unused-vars */\nimport { expectTypeOf } from 'vitest'\nimport { hc } from './client'\nimport { Context } from './context'\nimport { createMiddleware } from './helper/factory'\nimport { testClient } from './helper/testing'\nimport { Hono } from './hono'\nimport { poweredBy } from './middleware/powered-by'\nimport type {\n  AddParam,\n  Env,\n  ExtractSchema,\n  Handler,\n  InputToDataByTarget,\n  MergePath,\n  MergeSchemaPath,\n  MiddlewareHandler,\n  ParamKeyToRecord,\n  ParamKeys,\n  RemoveQuestion,\n  ResponseFormat,\n  ToSchema,\n  TypedResponse,\n} from './types'\nimport type { ContentfulStatusCode, StatusCode } from './utils/http-status'\nimport type { Equal, Expect } from './utils/types'\nimport { validator } from './validator'\n\ndescribe('Env', () => {\n  test('Env', () => {\n    type E = {\n      Variables: {\n        foo: string\n      }\n      Bindings: {\n        FLAG: boolean\n      }\n    }\n    const app = new Hono<E>()\n    app.use('*', poweredBy())\n    app.get('/', (c) => {\n      const foo = c.get('foo')\n      expectTypeOf(foo).toEqualTypeOf<string>()\n      const FLAG = c.env.FLAG\n      expectTypeOf(FLAG).toEqualTypeOf<boolean>()\n      return c.text('foo')\n    })\n  })\n})\n\ndescribe('HandlerInterface', () => {\n  type Env = {}\n\n  type Payload = { foo: string; bar: boolean }\n\n  describe('no path pattern', () => {\n    const app = new Hono<Env>()\n    const middleware: MiddlewareHandler<\n      Env,\n      '/',\n      {\n        in: { json: Payload }\n        out: { json: Payload }\n      }\n    > = async (_c, next) => {\n      await next()\n    }\n    test('Context', () => {\n      const route = app.get(middleware, (c) => {\n        type Expected = Context<\n          Env,\n          '/',\n          {\n            in: { json: Payload }\n            out: { json: Payload }\n          }\n        >\n        expectTypeOf(c).toEqualTypeOf<Expected>()\n        return c.json({\n          message: 'Hello!',\n        })\n      })\n      app.get(middleware, (c) => {\n        const data = c.req.valid('json')\n        expectTypeOf(data).toEqualTypeOf<Payload>()\n        return c.json({\n          message: 'Hello!',\n        })\n      })\n      type Actual = ExtractSchema<typeof route>\n      type Expected = {\n        '/': {\n          $get: {\n            input: {\n              json: Payload\n            }\n            output: {\n              message: string\n            }\n            outputFormat: 'json'\n            status: ContentfulStatusCode\n          }\n        }\n      }\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n  })\n\n  describe('path pattern', () => {\n    const app = new Hono<Env>()\n    const middleware: MiddlewareHandler<\n      Env,\n      '/foo',\n      { in: { json: Payload }; out: { json: Payload } },\n      never\n    > = async (_c, next) => {\n      await next()\n    }\n\n    test('Context and AppType', () => {\n      const route = app.get('/foo', middleware, (c) => {\n        type Expected = Context<Env, '/foo', { in: { json: Payload }; out: { json: Payload } }>\n        expectTypeOf(c).toEqualTypeOf<Expected>()\n        return c.json({\n          message: 'Hello!',\n        })\n      })\n      type Actual = ExtractSchema<typeof route>\n      type Expected = {\n        '/foo': {\n          $get: {\n            input: {\n              json: {\n                foo: string\n                bar: boolean\n              }\n            }\n            output: {\n              message: string\n            }\n            outputFormat: 'json'\n            status: ContentfulStatusCode\n          }\n        }\n      }\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n  })\n\n  describe('With path parameters', () => {\n    const app = new Hono<Env>()\n    const middleware: MiddlewareHandler<Env, '/post/:id', {}, never> = async (_c, next) => {\n      await next()\n    }\n    it('Should have the `param` type', () => {\n      const route = app.get('/post/:id', middleware, (c) => {\n        return c.text('foo')\n      })\n      type Actual = ExtractSchema<typeof route>\n      type Expected = {\n        '/post/:id': {\n          $get: {\n            input: {\n              param: {\n                id: string\n              }\n            }\n            output: 'foo'\n            outputFormat: 'text'\n            status: ContentfulStatusCode\n          }\n        }\n      }\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n  })\n\n  describe('Without path', () => {\n    const app = new Hono<Env>().basePath('/foo/:foo')\n\n    it('With basePath and path params', () => {\n      const route = app.get(async (c) => {\n        const foo = c.req.param('foo')\n        expect(typeof foo).toBe('string')\n        return c.text(foo)\n      })\n      type Actual = ExtractSchema<typeof route>\n\n      type Expected = {\n        '/foo/:foo': {\n          $get: {\n            input: {\n              param: {\n                foo: string\n              }\n            }\n            output: string\n            outputFormat: 'text'\n            status: ContentfulStatusCode\n          }\n        }\n      }\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n\n    it('Chained', () => {\n      const route = app.post('/books/:id').get((c) => {\n        const id = c.req.param('id')\n        return c.text(id)\n      })\n      type Actual = ExtractSchema<typeof route>\n      type Expected = {\n        '/foo/:foo/books/:id': {\n          $get: {\n            input: {\n              param: {\n                id: string\n              } & {\n                foo: string\n              }\n            }\n            output: string\n            outputFormat: 'text'\n            status: ContentfulStatusCode\n          }\n        }\n      } & {\n        '/foo/:foo/books/:id': {\n          $post: {\n            input: {\n              param: {\n                id: string\n              } & {\n                foo: string\n              }\n            }\n            output: {}\n            outputFormat: ResponseFormat\n            status: StatusCode\n          }\n        }\n      }\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n\n    it('should use the last registered path for a pathless handler', () => {\n      const app = new Hono()\n        .get('/foo', (c) => c.text('foo'))\n        .get('/bar', (c) => c.text('bar'))\n        .post((c) => c.text('baz'))\n\n      type Actual = ExtractSchema<typeof app>\n      type Expected = {\n        '/foo': {\n          $get: {\n            input: {}\n            output: 'foo'\n            outputFormat: 'text'\n            status: ContentfulStatusCode\n          }\n        }\n      } & {\n        '/bar': {\n          $get: {\n            input: {}\n            output: 'bar'\n            outputFormat: 'text'\n            status: ContentfulStatusCode\n          }\n        }\n      } & {\n        '/bar': {\n          $post: {\n            input: {}\n            output: 'baz'\n            outputFormat: 'text'\n            status: ContentfulStatusCode\n          }\n        }\n      }\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n  })\n})\n\ndescribe('OnHandlerInterface', () => {\n  const app = new Hono()\n  test('Context', () => {\n    const middleware: MiddlewareHandler<\n      Env,\n      '/purge',\n      { in: { form: { id: string } }; out: { form: { id: number } } }\n    > = async (_c, next) => {\n      await next()\n    }\n    const route = app.on('PURGE', '/purge', middleware, (c) => {\n      const data = c.req.valid('form')\n      expectTypeOf(data).toEqualTypeOf<{ id: number }>()\n      return c.json({\n        success: true,\n      })\n    })\n    type Actual = ExtractSchema<typeof route>\n    type Expected = {\n      '/purge': {\n        $purge: {\n          input: {\n            form: {\n              id: string\n            }\n          }\n          output: {\n            success: true\n          }\n          outputFormat: 'json'\n          status: ContentfulStatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  test('app.on(method[], path, middleware, handler)', () => {\n    const middleware: MiddlewareHandler<{ Variables: { foo: string } }> = async () => {}\n    const route = app.on(['GET', 'POST'], '/multi-method', middleware, (c) => {\n      return c.json({ success: true })\n    })\n    type Actual = ExtractSchema<typeof route>\n    type Expected = {\n      '/multi-method': {\n        $get: {\n          input: {}\n          output: {\n            success: true\n          }\n          outputFormat: 'json'\n          status: ContentfulStatusCode\n        }\n        $post: {\n          input: {}\n          output: {\n            success: true\n          }\n          outputFormat: 'json'\n          status: ContentfulStatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  test('app.on(method, path[], middleware, handler) should not throw a type error', () => {\n    const middleware: MiddlewareHandler<{ Variables: { foo: string } }> = async () => {}\n    app.on('GET', ['/a', '/b'], middleware, (c) => {\n      expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n      return c.json({})\n    })\n  })\n\n  test('app.on(method, path[], handler).get(pathless handler) - pathless handler should use last path', () => {\n    const route = app\n      .on('GET', ['/a', '/b'], (c) => {\n        return c.json({ first: true })\n      })\n      .get((c) => {\n        return c.json({ second: true })\n      })\n    type Actual = ExtractSchema<typeof route>\n    type Expected = {\n      '/a': {\n        $get: {\n          input: {}\n          output: {\n            first: true\n          }\n          outputFormat: 'json'\n          status: ContentfulStatusCode\n        }\n      }\n      '/b': {\n        $get: {\n          input: {}\n          output: {\n            first: true\n          }\n          outputFormat: 'json'\n          status: ContentfulStatusCode\n        }\n      }\n    } & {\n      '/b': {\n        $get: {\n          input: {}\n          output: {\n            second: true\n          }\n          outputFormat: 'json'\n          status: ContentfulStatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n})\n\ndescribe('TypedResponse', () => {\n  test('unknown', () => {\n    type Actual = TypedResponse\n    type Expected = {\n      _data: unknown\n      _status: StatusCode\n      _format: ResponseFormat\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  test('text auto infer', () => {\n    type Actual = TypedResponse<string>\n    type Expected = {\n      _data: string\n      _status: StatusCode\n      _format: 'text'\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  test('json auto infer', () => {\n    type Actual = TypedResponse<{ ok: true }>\n    type Expected = {\n      _data: { ok: true }\n      _status: StatusCode\n      _format: 'json'\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n})\n\ndescribe('Schema', () => {\n  test('Schema', () => {\n    type AppType = Hono<\n      Env,\n      ToSchema<\n        'post',\n        '/api/posts/:id',\n        {\n          in: {\n            json: {\n              id: number\n              title: string\n            }\n          }\n        },\n        TypedResponse<\n          {\n            message: string\n            success: boolean\n          },\n          StatusCode,\n          'json'\n        >\n      >\n    >\n\n    type Actual = ExtractSchema<AppType>\n    type Expected = {\n      '/api/posts/:id': {\n        $post: {\n          input: {\n            json: {\n              id: number\n              title: string\n            }\n          } & {\n            param: {\n              id: string\n            }\n          }\n          output: {\n            message: string\n            success: boolean\n          }\n          outputFormat: 'json'\n          status: StatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n})\n\ndescribe('Support c.json(undefined)', () => {\n  it('Should return a correct type', () => {\n    const app = new Hono().get('/this/is/a/test', async (c) => {\n      return c.json(undefined)\n    })\n    type Actual = ExtractSchema<typeof app>\n    type Expected = {\n      '/this/is/a/test': {\n        $get: {\n          input: {}\n          output: never\n          outputFormat: 'json'\n          status: ContentfulStatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n})\n\ndescribe('Test types of Handler', () => {\n  type E = {\n    Variables: {\n      foo: number\n    }\n  }\n\n  const url = 'http://localhost/'\n\n  test('Env', async () => {\n    const app = new Hono<E>()\n    const handler: Handler<E> = (c) => {\n      const foo = c.get('foo')\n      expectTypeOf(foo).toEqualTypeOf<number>()\n      const id = c.req.param('id')\n      expectTypeOf(id).toEqualTypeOf<string | undefined>()\n      return c.text('Hi')\n    }\n    app.get('/', handler)\n    const res = await app.request(url)\n    expect(res.status).toBe(200)\n  })\n\n  test('Env, Path', async () => {\n    const app = new Hono<E>()\n    const handler: Handler<E, '/'> = (c) => {\n      const foo = c.get('foo')\n      expectTypeOf(foo).toEqualTypeOf<number>()\n      return c.text('Hi')\n    }\n    app.get('/', handler)\n\n    const res = await app.request(url)\n    expect(res.status).toBe(200)\n  })\n\n  type User = {\n    name: string\n    age: number\n  }\n\n  test('Env, Path, Type', async () => {\n    const app = new Hono<E>()\n    const handler: Handler<E, '/', { in: { json: User }; out: { json: User } }> = (c) => {\n      const foo = c.get('foo')\n      expectTypeOf(foo).toEqualTypeOf<number>()\n      const { name } = c.req.valid('json')\n      expectTypeOf(name).toEqualTypeOf<string>()\n      return c.text('Hi')\n    }\n  })\n})\n\ndescribe('`json()`', () => {\n  const app = new Hono<{ Variables: { foo: string } }>()\n  app.get('/post/:id', (c) => {\n    c.req.param('id')\n    const id = c.req.param('id')\n    return c.text('foo')\n  })\n\n  test('json', () => {\n    const route = app.get('/hello', (c) => {\n      return c.json({\n        message: 'Hello!',\n      })\n    })\n    type Actual = ExtractSchema<typeof route>\n    type Expected = {\n      '/hello': {\n        $get: {\n          input: {}\n          output: {\n            message: string\n          }\n          outputFormat: 'json'\n          status: ContentfulStatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  test('json with specific status code', () => {\n    const route = app.get('/hello', (c) => {\n      return c.json(\n        {\n          message: 'Hello!',\n        },\n        200\n      )\n    })\n    type Actual = ExtractSchema<typeof route>\n    type Expected = {\n      '/hello': {\n        $get: {\n          input: {}\n          output: {\n            message: string\n          }\n          outputFormat: 'json'\n          status: 200\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n})\n\ndescribe('Path parameters', () => {\n  test('ParamKeys', () => {\n    type Actual = ParamKeys<'/posts/:postId/comment/:commentId'>\n    type Expected = 'postId' | 'commentId'\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  describe('ParamKeyToRecord', () => {\n    test('With ?', () => {\n      type Actual = ParamKeyToRecord<'/animal/type?'>\n      type Expected = { [K in '/animal/type']: string | undefined }\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n    test('Without ?', () => {\n      type Actual = ParamKeyToRecord<'/animal/type'>\n      type Expected = { [K in '/animal/type']: string }\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n  })\n\n  describe('Path parameters in app', () => {\n    test('Optional parameters - /api/:a/:b?', () => {\n      const app = new Hono()\n      const routes = app.get('/api/:a/:b?', (c) => {\n        const a = c.req.param('a')\n        const b = c.req.param('b')\n        expectTypeOf(a).toEqualTypeOf<string>()\n        expectTypeOf(b).toEqualTypeOf<string | undefined>()\n        return c.json({ a, b })\n      })\n      type T = ExtractSchema<typeof routes>\n      type Output = T['/api/:a/:b?']['$get']['output']\n      type Expected = {\n        a: string\n        b: string | undefined\n      }\n      type verify = Expect<Equal<Expected, Output>>\n    })\n  })\n})\n\ndescribe('For HonoRequest', () => {\n  type Input = {\n    json: {\n      id: number\n      title: string\n    }\n    query: {\n      page: string\n    }\n  }\n\n  test('InputToDataByType with value', () => {\n    type Actual = InputToDataByTarget<Input, 'json'>\n    type Expected = {\n      id: number\n      title: string\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  test('InputToDataByType without value', () => {\n    type Actual = InputToDataByTarget<Input, 'form'>\n    type verify = Expect<Equal<never, Actual>>\n  })\n\n  test('RemoveQuestion', () => {\n    type Actual = RemoveQuestion<'/animal/type?'>\n    type verify = Expect<Equal<'/animal/type', Actual>>\n  })\n})\n\ndescribe('AddParam', () => {\n  it('Should add params to input correctly', () => {\n    type Actual = AddParam<\n      {\n        param: {\n          id: string\n        }\n      } & {\n        query: {\n          page: string\n        }\n      },\n      '/:id'\n    >\n    type Expected = {\n      query: {\n        page: string\n      }\n    } & {\n      param: {\n        id: string\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n})\n\ndescribe('ToSchema', () => {\n  it('Should convert parameters to schema correctly', () => {\n    type Actual = ToSchema<\n      'get',\n      '/:id',\n      { in: { param: { id: string }; query: { page: string } } },\n      TypedResponse<{}>\n    >\n    type Expected = {\n      '/:id': {\n        $get: {\n          input: {\n            param: {\n              id: string\n            }\n            query: {\n              page: string\n            }\n          }\n          output: {}\n          outputFormat: 'json'\n          status: StatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n})\n\ndescribe('MergePath', () => {\n  it('Should merge paths correctly', () => {\n    type path1 = MergePath<'/api', '/book'>\n    type verify1 = Expect<Equal<'/api/book', path1>>\n    type path2 = MergePath<'/api/', '/book'>\n    type verify2 = Expect<Equal<'/api/book', path2>>\n    type path3 = MergePath<'/api/', '/'>\n    type verify3 = Expect<Equal<'/api/', path3>>\n    type path4 = MergePath<'/api', '/'>\n    type verify4 = Expect<Equal<'/api', path4>>\n    type path5 = MergePath<'/', ''>\n    type verify5 = Expect<Equal<'/', path5>>\n    type path6 = MergePath<'', '/'>\n    type verify6 = Expect<Equal<'/', path6>>\n    type path7 = MergePath<'/', '/'>\n    type verify7 = Expect<Equal<'/', path7>>\n    type path8 = MergePath<'', ''>\n    type verify8 = Expect<Equal<'/', path8>>\n  })\n})\n\ndescribe('MergeSchemaPath', () => {\n  it('Should merge schema and sub path correctly', () => {\n    type Sub = ToSchema<\n      'post',\n      '/posts',\n      {\n        in: {\n          json: {\n            id: number\n            title: string\n          }\n        }\n      },\n      TypedResponse<{\n        message: string\n      }>\n    > &\n      ToSchema<\n        'get',\n        '/posts',\n        {},\n        TypedResponse<{\n          ok: boolean\n        }>\n      >\n\n    type Actual = MergeSchemaPath<Sub, '/api'>\n\n    type Expected = {\n      '/api/posts': {\n        $post: {\n          input: {\n            json: {\n              id: number\n              title: string\n            }\n          }\n          output: {\n            message: string\n          }\n          outputFormat: 'json'\n          status: StatusCode\n        }\n        $get: {\n          input: {}\n          output: {\n            ok: boolean\n          }\n          outputFormat: 'json'\n          status: StatusCode\n        }\n      }\n    }\n\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  it('Should merge schema which has params and sub path does not have params', () => {\n    type Actual = MergeSchemaPath<\n      {\n        '/': {\n          $get: {\n            input: {\n              param: {\n                id: string\n              }\n              query: {\n                page: string\n              }\n            }\n            output: {}\n            outputFormat: 'json'\n            status: StatusCode\n          }\n        }\n      },\n      '/something'\n    >\n    type Expected = {\n      '/something': {\n        $get: {\n          input: {\n            param: {\n              id: string\n            }\n            query: {\n              page: string\n            }\n          }\n          output: {}\n          outputFormat: 'json'\n          status: StatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  type GetKey<T> = T extends Record<infer K, unknown> ? K : never\n\n  it('Should remove a slash - `/` + `/`', () => {\n    type Sub = ToSchema<'get', '/', {}, TypedResponse<{}>>\n    type Actual = MergeSchemaPath<Sub, '/'>\n    type verify = Expect<Equal<'/', GetKey<Actual>>>\n  })\n\n  it('Should remove a slash - `/tags` + `/`', () => {\n    type Sub = ToSchema<'get', '/tags', {}, TypedResponse<{}>>\n    type Actual = MergeSchemaPath<Sub, '/'>\n    type verify = Expect<Equal<'/tags', GetKey<Actual>>>\n  })\n\n  it('Should remove a slash - `/` + `/tags`', () => {\n    type Sub = ToSchema<'get', '/', {}, TypedResponse<{}>>\n    type Actual = MergeSchemaPath<Sub, '/tags'>\n    type verify = Expect<Equal<'/tags', GetKey<Actual>>>\n  })\n\n  test('MergeSchemaPath - SubPath has path params', () => {\n    type Actual = MergeSchemaPath<ToSchema<'get', '/', {}, TypedResponse>, '/a/:b'>\n    type Expected = {\n      '/a/:b': {\n        $get: {\n          input: {\n            param: {\n              b: string\n            }\n          }\n          output: {}\n          outputFormat: ResponseFormat\n          status: StatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  test('MergeSchemaPath - Path and SubPath have path params', () => {\n    type Actual = MergeSchemaPath<ToSchema<'get', '/c/:d', {}, TypedResponse<{}>>, '/a/:b'>\n    type Expected = {\n      '/a/:b/c/:d': {\n        $get: {\n          input: {\n            param: {\n              d: string\n            } & {\n              b: string\n            }\n          }\n          output: {}\n          outputFormat: 'json'\n          status: StatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  test('MergeSchemaPath - Path and SubPath have regexp path params', () => {\n    type Actual = MergeSchemaPath<ToSchema<'get', '/c/:d{.+}', {}, TypedResponse<{}>>, '/a/:b{.+}'>\n    type Expected = {\n      '/a/:b{.+}/c/:d{.+}': {\n        $get: {\n          input: {\n            param: {\n              d: string\n            } & {\n              b: string\n            }\n          }\n          output: {}\n          outputFormat: 'json'\n          status: StatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  test('MergeSchemaPath - Method has Endpoints as Union', () => {\n    type Actual = MergeSchemaPath<\n      {\n        '/': {\n          $get:\n            | {\n                input: {}\n                output: {\n                  error: string\n                }\n                outputFormat: 'json'\n                status: 404\n              }\n            | {\n                input: {}\n                output: {\n                  success: boolean\n                }\n                outputFormat: 'json'\n                status: 200\n              }\n        }\n      },\n      '/api/hello'\n    >\n    type Expected = {\n      '/api/hello': {\n        $get:\n          | {\n              input: {}\n              output: {\n                error: string\n              }\n              outputFormat: 'json'\n              status: 404\n            }\n          | {\n              input: {}\n              output: {\n                success: boolean\n              }\n              outputFormat: 'json'\n              status: 200\n            }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n})\n\ndescribe('Different types using json()', () => {\n  describe('no path pattern', () => {\n    const app = new Hono()\n\n    test('Three different types', () => {\n      const route = app.get((c) => {\n        const flag = false\n        if (flag) {\n          return c.json({\n            ng: true,\n          })\n        }\n        if (!flag) {\n          return c.json({\n            ok: true,\n          })\n        }\n        return c.json({\n          default: true,\n        })\n      })\n      type Actual = ExtractSchema<typeof route>\n      type Expected = {\n        '/': {\n          $get:\n            | {\n                input: {}\n                output: {\n                  ng: true\n                }\n                outputFormat: 'json'\n                status: ContentfulStatusCode\n              }\n            | {\n                input: {}\n                output: {\n                  ok: true\n                }\n                outputFormat: 'json'\n                status: ContentfulStatusCode\n              }\n            | {\n                input: {}\n                output: {\n                  default: true\n                }\n                outputFormat: 'json'\n                status: ContentfulStatusCode\n              }\n        }\n      }\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n\n    test('Three different types and status codes', () => {\n      const route = app.get((c) => {\n        const flag = false\n        if (flag) {\n          return c.json(\n            {\n              ng: true,\n            },\n            400\n          )\n        }\n        if (!flag) {\n          return c.json(\n            {\n              ok: true,\n            },\n            200\n          )\n        }\n        return c.json({\n          default: true,\n        })\n      })\n      type Actual = ExtractSchema<typeof route>\n      type Expected = {\n        '/': {\n          $get:\n            | {\n                input: {}\n                output: {\n                  ng: true\n                }\n                outputFormat: 'json'\n                status: 400\n              }\n            | {\n                input: {}\n                output: {\n                  ok: true\n                }\n                outputFormat: 'json'\n                status: 200\n              }\n            | {\n                input: {}\n                output: {\n                  default: true\n                }\n                outputFormat: 'json'\n                status: ContentfulStatusCode\n              }\n        }\n      }\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n  })\n\n  describe('path pattern', () => {\n    const app = new Hono()\n\n    test('Three different types', () => {\n      const route = app.get('/foo', (c) => {\n        const flag = false\n        if (flag) {\n          return c.json({\n            ng: true,\n          })\n        }\n        if (!flag) {\n          return c.json({\n            ok: true,\n          })\n        }\n        return c.json({\n          default: true,\n        })\n      })\n      type Actual = ExtractSchema<typeof route>\n      type Expected = {\n        '/foo': {\n          $get:\n            | {\n                input: {}\n                output: {\n                  ng: true\n                }\n                outputFormat: 'json'\n                status: ContentfulStatusCode\n              }\n            | {\n                input: {}\n                output: {\n                  ok: true\n                }\n                outputFormat: 'json'\n                status: ContentfulStatusCode\n              }\n            | {\n                input: {}\n                output: {\n                  default: true\n                }\n                outputFormat: 'json'\n                status: ContentfulStatusCode\n              }\n        }\n      }\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n\n    test('Three different types and status codes', () => {\n      const route = app.get('/foo', (c) => {\n        const flag = false\n        if (flag) {\n          return c.json(\n            {\n              ng: true,\n            },\n            400\n          )\n        }\n        if (!flag) {\n          return c.json(\n            {\n              ok: true,\n            },\n            200\n          )\n        }\n        return c.json({\n          default: true,\n        })\n      })\n      type Actual = ExtractSchema<typeof route>\n      type Expected = {\n        '/foo': {\n          $get:\n            | {\n                input: {}\n                output: {\n                  ng: true\n                }\n                outputFormat: 'json'\n                status: 400\n              }\n            | {\n                input: {}\n                output: {\n                  ok: true\n                }\n                outputFormat: 'json'\n                status: 200\n              }\n            | {\n                input: {}\n                output: {\n                  default: true\n                }\n                outputFormat: 'json'\n                status: ContentfulStatusCode\n              }\n        }\n      }\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n  })\n})\n\ndescribe('json() in an async handler', () => {\n  const app = new Hono()\n\n  test('json', () => {\n    const route = app.get(async (c) => {\n      return c.json({\n        ok: true,\n      })\n    })\n    type Actual = ExtractSchema<typeof route>\n    type Expected = {\n      '/': {\n        $get: {\n          input: {}\n          output: {\n            ok: true\n          }\n          outputFormat: 'json'\n          status: ContentfulStatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  test('json with specific status code', () => {\n    const route = app.get(async (c) => {\n      return c.json(\n        {\n          ok: true,\n        },\n        200\n      )\n    })\n    type Actual = ExtractSchema<typeof route>\n    type Expected = {\n      '/': {\n        $get: {\n          input: {}\n          output: {\n            ok: true\n          }\n          outputFormat: 'json'\n          status: 200\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n})\n\n/**\n * Other tests for `c.var` are written in `hono.test.ts`.\n * This tests are only for types.\n */\ndescribe('c.var with chaining - test only types', () => {\n  const mw1 = createMiddleware<\n    { Variables: { foo1: string } },\n    string,\n    { out: { query: { bar1: number } } }\n  >(async () => {})\n  const mw2 = createMiddleware<\n    { Variables: { foo2: string } },\n    string,\n    { out: { query: { bar2: number } } }\n  >(async () => {})\n  const mw3 = createMiddleware<\n    { Variables: { foo3: string } },\n    string,\n    { out: { query: { bar3: number } } }\n  >(async () => {})\n  const mw4 = createMiddleware<\n    { Variables: { foo4: string } },\n    string,\n    { out: { query: { bar4: number } } }\n  >(async () => {})\n  const mw5 = createMiddleware<\n    { Variables: { foo5: string } },\n    string,\n    { out: { query: { bar5: number } } }\n  >(async () => {})\n  const mw6 = createMiddleware<\n    { Variables: { foo6: string } },\n    string,\n    { out: { query: { bar6: number } } }\n  >(async () => {})\n  const mw7 = createMiddleware<\n    { Variables: { foo7: string } },\n    string,\n    { out: { query: { bar7: number } } }\n  >(async () => {})\n  const mw8 = createMiddleware<\n    { Variables: { foo8: string } },\n    string,\n    { out: { query: { bar8: number } } }\n  >(async () => {})\n  const mw9 = createMiddleware<\n    { Variables: { foo9: string } },\n    string,\n    { out: { query: { bar9: number } } }\n  >(async () => {})\n  const mw10 = createMiddleware<\n    { Variables: { foo10: string } },\n    string,\n    { out: { query: { bar10: number } } }\n  >(async () => {})\n\n  it('Should not throw type errors', () => {\n    // app.get(handler...)\n\n    new Hono().get(mw1).get('/', (c) => {\n      expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n      return c.json(0)\n    })\n\n    new Hono().get(mw1, mw2).get('/', (c) => {\n      expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n      return c.json(0)\n    })\n\n    new Hono().get(mw1, mw2, mw3).get('/', (c) => {\n      expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo3).toEqualTypeOf<string>()\n      return c.json(0)\n    })\n\n    new Hono().get(mw1, mw2, mw3, mw4).get('/', (c) => {\n      expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo3).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo4).toEqualTypeOf<string>()\n      return c.json(0)\n    })\n\n    new Hono().get(mw1, mw2, mw3, mw4, mw5).get('/', (c) => {\n      expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo3).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo4).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo5).toEqualTypeOf<string>()\n      return c.json(0)\n    })\n\n    new Hono().get(mw1, mw2, mw3, mw4, mw5, mw6).get('/', (c) => {\n      expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo3).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo4).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo5).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo6).toEqualTypeOf<string>()\n      return c.json(0)\n    })\n\n    new Hono().get(mw1, mw2, mw3, mw4, mw5, mw6, mw7).get('/', (c) => {\n      expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo3).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo4).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo5).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo6).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo7).toEqualTypeOf<string>()\n      return c.json(0)\n    })\n\n    new Hono().get(mw1, mw2, mw3, mw4, mw5, mw6, mw7, mw8).get('/', (c) => {\n      expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo3).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo4).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo5).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo6).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo7).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo8).toEqualTypeOf<string>()\n      return c.json(0)\n    })\n\n    new Hono().get(mw1, mw2, mw3, mw4, mw5, mw6, mw7, mw8, mw9).get('/', (c) => {\n      expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo3).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo4).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo5).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo6).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo7).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo8).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo9).toEqualTypeOf<string>()\n      return c.json(0)\n    })\n\n    new Hono().get(mw1, mw2, mw3, mw4, mw5, mw6, mw7, mw8, mw9, mw10).get('/', (c) => {\n      expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo3).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo4).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo5).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo6).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo7).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo8).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo9).toEqualTypeOf<string>()\n      expectTypeOf(c.var.foo10).toEqualTypeOf<string>()\n      return c.json(0)\n    })\n\n    new Hono().get(\n      mw1,\n      mw2,\n      mw3,\n      mw4,\n      mw5,\n      mw6,\n      mw7,\n      mw8,\n      async (c) => {\n        expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo3).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo4).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo5).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo6).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo7).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo8).toEqualTypeOf<string>()\n      },\n      (c) => c.json(0)\n    )\n\n    new Hono().get(mw1, mw2, mw3, mw4, mw5, mw6, mw7, mw8, mw9, (c) => {\n      expectTypeOf(c.req.valid('query')).toMatchTypeOf<{\n        bar1: number\n        bar2: number\n        bar3: number\n        bar4: number\n        bar5: number\n        bar6: number\n        bar7: number\n        bar8: number\n        bar9: number\n      }>()\n\n      return c.json(0)\n    })\n\n    new Hono().get(\n      '/',\n      mw1,\n      mw2,\n      mw3,\n      mw4,\n      mw5,\n      mw6,\n      mw7,\n      mw8,\n      async (c) => {\n        expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo3).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo4).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo5).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo6).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo7).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo8).toEqualTypeOf<string>()\n      },\n      (c) => c.json(0)\n    )\n\n    new Hono().get('/', mw1, mw2, mw3, mw4, mw5, mw6, mw7, mw8, mw9, (c) => {\n      expectTypeOf(c.req.valid('query')).toMatchTypeOf<{\n        bar1: number\n        bar2: number\n        bar3: number\n        bar4: number\n        bar5: number\n        bar6: number\n        bar7: number\n        bar8: number\n        bar9: number\n      }>()\n\n      return c.json(0)\n    })\n\n    type Env = {\n      Variables: {\n        init: number\n      }\n    }\n\n    new Hono<Env>()\n      .get('/', mw1, (c) => {\n        expectTypeOf(c.get('init')).toEqualTypeOf<number>()\n        expectTypeOf(c.var.init).toEqualTypeOf<number>()\n        expectTypeOf(c.get('foo1')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n        return c.json(0)\n      })\n      .get('/', (c) => {\n        expectTypeOf(c.get('init')).toEqualTypeOf<number>()\n        expectTypeOf(c.var.init).toEqualTypeOf<number>()\n        // @ts-expect-error foo1 is not typed\n        c.get('foo1')\n        // @ts-expect-error foo1 is not typed\n        c.var.foo1\n        return c.json(0)\n      })\n    new Hono<Env>()\n      .get('/', mw1, mw2, (c) => {\n        expectTypeOf(c.get('init')).toEqualTypeOf<number>()\n        expectTypeOf(c.var.init).toEqualTypeOf<number>()\n        expectTypeOf(c.get('foo1')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo2')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n        return c.json(0)\n      })\n      .get('/', (c) => {\n        expectTypeOf(c.get('init')).toEqualTypeOf<number>()\n        expectTypeOf(c.var.init).toEqualTypeOf<number>()\n        // @ts-expect-error foo1 is not typed\n        c.get('foo1')\n        // @ts-expect-error foo1 is not typed\n        c.var.foo1\n        // @ts-expect-error foo2 is not typed\n        c.get('foo2')\n        // @ts-expect-error foo2 is not typed\n        c.var.foo2\n        return c.json(0)\n      })\n    new Hono<Env>()\n      .get('/', mw1, mw2, mw3, (c) => {\n        expectTypeOf(c.get('init')).toEqualTypeOf<number>()\n        expectTypeOf(c.var.init).toEqualTypeOf<number>()\n        expectTypeOf(c.get('foo1')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo2')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo3')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo3).toEqualTypeOf<string>()\n        return c.json(0)\n      })\n      .get('/', (c) => {\n        expectTypeOf(c.get('init')).toEqualTypeOf<number>()\n        expectTypeOf(c.var.init).toEqualTypeOf<number>()\n        // @ts-expect-error foo1 is not typed\n        c.get('foo1')\n        // @ts-expect-error foo1 is not typed\n        c.var.foo1\n        // @ts-expect-error foo2 is not typed\n        c.get('foo2')\n        // @ts-expect-error foo2 is not typed\n        c.var.foo2\n        // @ts-expect-error foo3 is not typed\n        c.get('foo3')\n        // @ts-expect-error foo3 is not typed\n        c.var.foo3\n        return c.json(0)\n      })\n    new Hono<Env>()\n      .get('/', mw1, mw2, mw3, mw4, (c) => {\n        expectTypeOf(c.get('init')).toEqualTypeOf<number>()\n        expectTypeOf(c.var.init).toEqualTypeOf<number>()\n        expectTypeOf(c.get('foo1')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo2')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo3')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo3).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo4')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo4).toEqualTypeOf<string>()\n        return c.json(0)\n      })\n      .get('/', (c) => {\n        expectTypeOf(c.get('init')).toEqualTypeOf<number>()\n        expectTypeOf(c.var.init).toEqualTypeOf<number>()\n        // @ts-expect-error foo1 is not typed\n        c.get('foo1')\n        // @ts-expect-error foo1 is not typed\n        c.var.foo1\n        // @ts-expect-error foo2 is not typed\n        c.get('foo2')\n        // @ts-expect-error foo2 is not typed\n        c.var.foo2\n        // @ts-expect-error foo3 is not typed\n        c.get('foo3')\n        // @ts-expect-error foo3 is not typed\n        c.var.foo3\n        // @ts-expect-error foo4 is not typed\n        c.get('foo4')\n        // @ts-expect-error foo4 is not typed\n        c.var.foo4\n        return c.json(0)\n      })\n\n    new Hono<Env>()\n      .get('/', mw1, mw2, mw3, mw4, mw5, (c) => {\n        expectTypeOf(c.get('init')).toEqualTypeOf<number>()\n        expectTypeOf(c.var.init).toEqualTypeOf<number>()\n        expectTypeOf(c.get('foo1')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo2')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo3')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo3).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo4')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo4).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo5')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo5).toEqualTypeOf<string>()\n        return c.json(0)\n      })\n      .get('/', (c) => {\n        expectTypeOf(c.get('init')).toEqualTypeOf<number>()\n        expectTypeOf(c.var.init).toEqualTypeOf<number>()\n        // @ts-expect-error foo1 is not typed\n        c.get('foo1')\n        // @ts-expect-error foo1 is not typed\n        c.var.foo1\n        // @ts-expect-error foo2 is not typed\n        c.get('foo2')\n        // @ts-expect-error foo2 is not typed\n        c.var.foo2\n        // @ts-expect-error foo3 is not typed\n        c.get('foo3')\n        // @ts-expect-error foo3 is not typed\n        c.var.foo3\n        // @ts-expect-error foo4 is not typed\n        c.get('foo4')\n        // @ts-expect-error foo4 is not typed\n        c.var.foo4\n        // @ts-expect-error foo5 is not typed\n        c.get('foo5')\n        // @ts-expect-error foo5 is not typed\n        c.var.foo5\n        return c.json(0)\n      })\n    new Hono<Env>()\n      .get('/', mw1, mw2, mw3, mw4, mw5, mw6, (c) => {\n        expectTypeOf(c.get('init')).toEqualTypeOf<number>()\n        expectTypeOf(c.var.init).toEqualTypeOf<number>()\n        expectTypeOf(c.get('foo1')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo2')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo3')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo3).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo4')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo4).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo5')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo5).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo6')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo6).toEqualTypeOf<string>()\n        return c.json(0)\n      })\n      .get('/', (c) => {\n        expectTypeOf(c.get('init')).toEqualTypeOf<number>()\n        expectTypeOf(c.var.init).toEqualTypeOf<number>()\n        // @ts-expect-error foo1 is not typed\n        c.get('foo1')\n        // @ts-expect-error foo1 is not typed\n        c.var.foo1\n        // @ts-expect-error foo2 is not typed\n        c.get('foo2')\n        // @ts-expect-error foo2 is not typed\n        c.var.foo2\n        // @ts-expect-error foo3 is not typed\n        c.get('foo3')\n        // @ts-expect-error foo3 is not typed\n        c.var.foo3\n        // @ts-expect-error foo4 is not typed\n        c.get('foo4')\n        // @ts-expect-error foo4 is not typed\n        c.var.foo4\n        // @ts-expect-error foo5 is not typed\n        c.get('foo5')\n        // @ts-expect-error foo5 is not typed\n        c.var.foo5\n        // @ts-expect-error foo6 is not typed\n        c.get('foo6')\n        // @ts-expect-error foo6 is not typed\n        c.var.foo6\n        return c.json(0)\n      })\n    new Hono<Env>()\n      .get('/', mw1, mw2, mw3, mw4, mw5, mw6, mw7, (c) => {\n        expectTypeOf(c.get('init')).toEqualTypeOf<number>()\n        expectTypeOf(c.var.init).toEqualTypeOf<number>()\n        expectTypeOf(c.get('foo1')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo2')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo3')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo3).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo4')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo4).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo5')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo5).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo6')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo6).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo7')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo7).toEqualTypeOf<string>()\n        return c.json(0)\n      })\n      .get('/', (c) => {\n        expectTypeOf(c.get('init')).toEqualTypeOf<number>()\n        expectTypeOf(c.var.init).toEqualTypeOf<number>()\n        // @ts-expect-error foo1 is not typed\n        c.get('foo1')\n        // @ts-expect-error foo1 is not typed\n        c.var.foo1\n        // @ts-expect-error foo2 is not typed\n        c.get('foo2')\n        // @ts-expect-error foo2 is not typed\n        c.var.foo2\n        // @ts-expect-error foo3 is not typed\n        c.get('foo3')\n        // @ts-expect-error foo3 is not typed\n        c.var.foo3\n        // @ts-expect-error foo4 is not typed\n        c.get('foo4')\n        // @ts-expect-error foo4 is not typed\n        c.var.foo4\n        // @ts-expect-error foo5 is not typed\n        c.get('foo5')\n        // @ts-expect-error foo5 is not typed\n        c.var.foo5\n        // @ts-expect-error foo6 is not typed\n        c.get('foo6')\n        // @ts-expect-error foo6 is not typed\n        c.var.foo6\n        // @ts-expect-error foo7 is not typed\n        c.get('foo7')\n        // @ts-expect-error foo7 is not typed\n        c.var.foo7\n        return c.json(0)\n      })\n    new Hono<Env>()\n      .get('/', mw1, mw2, mw3, mw4, mw5, mw6, mw7, mw8, (c) => {\n        expectTypeOf(c.get('init')).toEqualTypeOf<number>()\n        expectTypeOf(c.var.init).toEqualTypeOf<number>()\n        expectTypeOf(c.get('foo1')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo2')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo3')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo3).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo4')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo4).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo5')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo5).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo6')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo6).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo7')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo7).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo8')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo8).toEqualTypeOf<string>()\n        return c.json(0)\n      })\n      .get('/', (c) => {\n        expectTypeOf(c.get('init')).toEqualTypeOf<number>()\n        expectTypeOf(c.var.init).toEqualTypeOf<number>()\n        // @ts-expect-error foo1 is not typed\n        c.get('foo1')\n        // @ts-expect-error foo1 is not typed\n        c.var.foo1\n        // @ts-expect-error foo2 is not typed\n        c.get('foo2')\n        // @ts-expect-error foo2 is not typed\n        c.var.foo2\n        // @ts-expect-error foo3 is not typed\n        c.get('foo3')\n        // @ts-expect-error foo3 is not typed\n        c.var.foo3\n        // @ts-expect-error foo4 is not typed\n        c.get('foo4')\n        // @ts-expect-error foo4 is not typed\n        c.var.foo4\n        // @ts-expect-error foo5 is not typed\n        c.get('foo5')\n        // @ts-expect-error foo5 is not typed\n        c.var.foo5\n        // @ts-expect-error foo6 is not typed\n        c.get('foo6')\n        // @ts-expect-error foo6 is not typed\n        c.var.foo6\n        // @ts-expect-error foo7 is not typed\n        c.get('foo7')\n        // @ts-expect-error foo7 is not typed\n        c.var.foo7\n        // @ts-expect-error foo8 is not typed\n        c.get('foo8')\n        // @ts-expect-error foo8 is not typed\n        c.var.foo8\n        return c.json(0)\n      })\n\n    new Hono<Env>()\n      .get('/', mw1, mw2, mw3, mw4, mw5, mw6, mw7, mw8, mw9, (c) => {\n        expectTypeOf(c.get('init')).toEqualTypeOf<number>()\n        expectTypeOf(c.var.init).toEqualTypeOf<number>()\n        expectTypeOf(c.get('foo1')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo1).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo2')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo2).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo3')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo3).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo4')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo4).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo5')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo5).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo6')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo6).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo7')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo7).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo8')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo8).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo9')).toEqualTypeOf<string>()\n        expectTypeOf(c.var.foo9).toEqualTypeOf<string>()\n        return c.json(0)\n      })\n      .get('/', (c) => {\n        expectTypeOf(c.get('init')).toEqualTypeOf<number>()\n        expectTypeOf(c.var.init).toEqualTypeOf<number>()\n        // @ts-expect-error foo1 is not typed\n        c.get('foo1')\n        // @ts-expect-error foo1 is not typed\n        c.var.foo1\n        // @ts-expect-error foo2 is not typed\n        c.get('foo2')\n        // @ts-expect-error foo2 is not typed\n        c.var.foo2\n        // @ts-expect-error foo3 is not typed\n        c.get('foo3')\n        // @ts-expect-error foo3 is not typed\n        c.var.foo3\n        // @ts-expect-error foo4 is not typed\n        c.get('foo4')\n        // @ts-expect-error foo4 is not typed\n        c.var.foo4\n        // @ts-expect-error foo5 is not typed\n        c.get('foo5')\n        // @ts-expect-error foo5 is not typed\n        c.var.foo5\n        // @ts-expect-error foo6 is not typed\n        c.get('foo6')\n        // @ts-expect-error foo6 is not typed\n        c.var.foo6\n        // @ts-expect-error foo7 is not typed\n        c.get('foo7')\n        // @ts-expect-error foo7 is not typed\n        c.var.foo7\n        // @ts-expect-error foo8 is not typed\n        c.get('foo8')\n        // @ts-expect-error foo8 is not typed\n        c.var.foo8\n        // @ts-expect-error foo9 is not typed\n        c.get('foo9')\n        // @ts-expect-error foo9 is not typed\n        c.var.foo9\n        return c.json(0)\n      })\n  })\n})\n\n/**\n *\n * Declaring a ContextVariableMap for testing.\n */\ndeclare module './context' {\n  interface ContextVariableMap {\n    payload: string\n  }\n}\n\ndescribe('ContextVariableMap type tests', () => {\n  it('Should not throw type errors with c.var', () => {\n    new Hono().get((c) => {\n      expectTypeOf(c.get('payload')).toEqualTypeOf<string>()\n      return c.json(0)\n    })\n    new Hono().get((c) => {\n      expectTypeOf(c.var.payload).toEqualTypeOf<string>()\n      return c.json(0)\n    })\n  })\n\n  it('Should override ContextVariableMap with env variables', () => {\n    const middleware = createMiddleware<{\n      Variables: {\n        payload: number\n      }\n    }>(async (c, next) => {\n      c.set('payload', 123)\n      await next()\n    })\n\n    new Hono().get(middleware, (c) => {\n      expectTypeOf(c.get('payload')).toEqualTypeOf<number>()\n      return c.json(0)\n    })\n  })\n\n  it('Should use ContextVariableMap when c is Context<any>', () => {\n    const c = new Context(new Request('http://localhost'))\n    expectTypeOf(c.get('payload')).toEqualTypeOf<string>()\n    expectTypeOf(c.var.payload).toEqualTypeOf<string>()\n    // @ts-expect-error the value of payload should be string\n    expectTypeOf(c.set('payload', 123))\n  })\n})\n\n/**\n * It's challenge to test all cases. This is a minimal pattern.\n */\ndescribe('Env types with chained routes - test only types', () => {\n  const app = new Hono<{ Variables: { testVar: string } }>()\n  it('Should not throw a type error', () => {\n    app\n      .post(\n        '/',\n        validator('json', (v) => v),\n        async (c) => {\n          expectTypeOf(c.get('testVar')).toEqualTypeOf<string>()\n          return c.json({ success: true })\n        }\n      )\n      .patch(\n        '/',\n        validator('json', (v) => v),\n        async (c) => {\n          expectTypeOf(c.get('testVar')).toEqualTypeOf<string>()\n          return c.json({ success: true })\n        }\n      )\n  })\n})\n\n/**\n * Ref: https://github.com/honojs/hono/issues/3027\n */\ndescribe('Env types with validator as first middleware - test only types', () => {\n  const app = new Hono<{ Variables: { testVar: string } }>()\n  it('Should not throw a type error', () => {\n    const testApp = app.get(\n      validator('json', () => {\n        return {\n          cd: 'bar',\n        }\n      }),\n      async (c) => {\n        const foo = c.req.valid('json') // Error here\n        return c.json(1)\n      }\n    )\n\n    const dummyMiddleware1 = createMiddleware(async (c, next) => {\n      await next()\n    })\n    // Multiple levels of middleware\n    const testApp2 = app.post(\n      validator('json', () => {\n        return {\n          cd: 'bar',\n        }\n      }),\n      dummyMiddleware1,\n      createMiddleware(async (c, next) => {\n        await next()\n      }),\n      async (c) => {\n        const foo = c.req.valid('json') // Error here also\n        return c.json(1)\n      }\n    )\n  })\n})\n\n// https://github.com/honojs/hono/issues/4773\ndescribe('c.req.valid() in non-last handler after validator middleware - test only types', () => {\n  it('Should not throw a type error', () => {\n    const app = new Hono()\n    app.get(\n      '/',\n      validator('query', () => {\n        return {\n          test: 'hello',\n        }\n      }),\n      async (c, next) => {\n        const { test } = c.req.valid('query')\n        expectTypeOf(test).toEqualTypeOf<string>()\n        await next()\n      },\n      async (c) => {\n        const { test } = c.req.valid('query')\n        expectTypeOf(test).toEqualTypeOf<string>()\n        return c.json({ ok: true })\n      }\n    )\n  })\n})\n\ndescribe('Env types with `use` middleware - test only types', () => {\n  const app = new Hono()\n\n  const mw1 = createMiddleware<{ Variables: { foo1: string } }>(async () => {})\n  const mw2 = createMiddleware<{ Variables: { foo2: string } }>(async () => {})\n\n  it('Should not throw a type error', () => {\n    app\n      .use(mw1)\n      .use(mw2)\n      .get('/', (c) => {\n        expectTypeOf(c.get('foo1')).toEqualTypeOf<string>()\n        expectTypeOf(c.get('foo2')).toEqualTypeOf<string>()\n        return c.json({ success: true })\n      })\n    app.use(mw1, mw2).get('/', (c) => {\n      expectTypeOf(c.get('foo1')).toEqualTypeOf<string>()\n      expectTypeOf(c.get('foo2')).toEqualTypeOf<string>()\n      return c.json({ success: true })\n    })\n  })\n})\n\ndescribe('Env types and a path type with `app.use(path, handler...)` - test only types', () => {\n  it('Should not throw a type error', () => {\n    type Env = {\n      Variables: {\n        foo: string\n      }\n    }\n\n    // app.use(path, handler)\n    new Hono<Env>()\n      .use('/:id', async (c, next) => {\n        expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n        expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n        await next()\n      })\n      .get((c) => {\n        expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n        expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n        return c.json(0)\n      })\n\n    // app.use(path, handler x2)\n    new Hono<Env>()\n      .use(\n        '/:id',\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        }\n      )\n      .get((c) => {\n        expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n        expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n        return c.json(0)\n      })\n\n    // app.use(path, handler x3)\n    new Hono<Env>()\n      .use(\n        '/:id',\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        }\n      )\n      .get((c) => {\n        expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n        expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n        return c.json(0)\n      })\n\n    // app.use(path, handler x4)\n    new Hono<Env>()\n      .use(\n        '/:id',\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        }\n      )\n      .get((c) => {\n        expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n        expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n        return c.json(0)\n      })\n\n    // app.use(path, handler x5)\n    new Hono<Env>()\n      .use(\n        '/:id',\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        }\n      )\n      .get((c) => {\n        expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n        expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n        return c.json(0)\n      })\n\n    // app.use(path, handler x6)\n    new Hono<Env>()\n      .use(\n        '/:id',\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        }\n      )\n      .get((c) => {\n        expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n        expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n        return c.json(0)\n      })\n\n    // app.use(path, handler x7)\n    new Hono<Env>()\n      .use(\n        '/:id',\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        }\n      )\n      .get((c) => {\n        expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n        expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n        return c.json(0)\n      })\n\n    // app.use(path, handler x8)\n    new Hono<Env>()\n      .use(\n        '/:id',\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        }\n      )\n      .get((c) => {\n        expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n        expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n        return c.json(0)\n      })\n\n    // app.use(path, handler x9)\n    new Hono<Env>()\n      .use(\n        '/:id',\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        }\n      )\n      .get((c) => {\n        expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n        expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n        return c.json(0)\n      })\n\n    // app.use(path, handler x10)\n    new Hono<Env>()\n      .use(\n        '/:id',\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        },\n        async (c, next) => {\n          expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n          expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n          await next()\n        }\n      )\n      .get((c) => {\n        expectTypeOf(c.var.foo).toEqualTypeOf<string>()\n        expectTypeOf(c.req.param('id')).toEqualTypeOf<string>()\n        return c.json(0)\n      })\n  })\n})\n\n// https://github.com/honojs/hono/issues/3122\ndescribe('Returning type from `app.use(path, mw)`', () => {\n  const mw = createMiddleware(async (c, next) => {\n    await next()\n  })\n  it('Should ignore the `*` wildcard when building the schema', () => {\n    const app = new Hono().use('*', mw)\n    type Actual = ExtractSchema<typeof app>\n    type Expected = {}\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n})\n\ndescribe('generic typed variables', () => {\n  const okHelper = (c: Context) => {\n    return <TData>(data: TData) => c.json({ data })\n  }\n  type Variables = {\n    ok: ReturnType<typeof okHelper>\n  }\n  const app = new Hono<{ Variables: Variables }>()\n\n  it('Should set and get variables with correct types', async () => {\n    const route = app\n      .use('*', async (c, next) => {\n        c.set('ok', okHelper(c))\n        await next()\n      })\n      .get('/', (c) => {\n        const ok = c.get('ok')\n        return ok('Hello')\n      })\n    type Actual = ExtractSchema<typeof route>['/']['$get']['output']\n    type Expected = { data: string }\n    expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n  })\n})\n\ndescribe('status code', () => {\n  const app = new Hono()\n\n  it('should only allow to return .json() with contentful status codes', async () => {\n    const route = app.get('/', async (c) => c.json({}))\n    type Actual = ExtractSchema<typeof route>['/']['$get']['status']\n    expectTypeOf<Actual>().toEqualTypeOf<ContentfulStatusCode>()\n  })\n\n  it('should only allow to return .body(null) with all status codes', async () => {\n    const route = app.get('/', async (c) => c.body(null))\n    type Actual = ExtractSchema<typeof route>['/']['$get']['status']\n    expectTypeOf<Actual>().toEqualTypeOf<StatusCode>()\n  })\n\n  it('should only allow to return .text() with contentful status codes', async () => {\n    const route = app.get('/', async (c) => c.text('whatever'))\n    type Actual = ExtractSchema<typeof route>['/']['$get']['status']\n    expectTypeOf<Actual>().toEqualTypeOf<ContentfulStatusCode>()\n  })\n\n  it('should throw type error when .json({}) is used with contentless status codes', async () => {\n    // @ts-expect-error 204 is not contentful status code\n    app.get('/', async (c) => c.json({}, 204))\n    app.get('/', async (c) =>\n      c.json(\n        {},\n        // @ts-expect-error 204 is not contentful status code\n        {\n          status: 204,\n        }\n      )\n    )\n  })\n\n  it('should throw type error when .body(content) is used with contentless status codes', async () => {\n    // @ts-expect-error 204 is not contentful status code\n    app.get('/', async (c) => c.body('content', 204))\n    // @ts-expect-error 204 is not contentful status code\n    app.get('/', async (c) => c.body('content', { status: 204 }))\n  })\n\n  it('should throw type error when .text(content) is used with contentless status codes', async () => {\n    // @ts-expect-error 204 is not contentful status code\n    app.get('/', async (c) => c.text('content', 204))\n    // @ts-expect-error 204 is not contentful status code\n    app.get('/', async (c) => c.text('content', { status: 204 }))\n  })\n\n  it('should throw type error when .html(content) is used with contentless status codes', async () => {\n    // @ts-expect-error 204 is not contentful status code\n    app.get('/', async (c) => c.html('<h1>title</h1>', 204))\n    // @ts-expect-error 204 is not contentful status code\n    app.get('/', async (c) => c.html('<h1>title</h1>', { status: 204 }))\n  })\n\n  it('.body() not override other responses in hono client', async () => {\n    const router = app.get('/', async (c) => {\n      if (c.req.header('Content-Type') === 'application/json') {\n        return c.text('Hello', 200)\n      }\n\n      if (c.req.header('Content-Type') === 'application/x-www-form-urlencoded') {\n        return c.body('Hello', 201)\n      }\n\n      return c.body(null, 204)\n    })\n\n    type Actual = ExtractSchema<typeof router>['/']['$get']['status']\n    expectTypeOf<Actual>().toEqualTypeOf<204 | 201 | 200>()\n  })\n})\n\ndescribe('RPC supports Middleware responses', () => {\n  describe('Merge responses from multiple handlers, path pattern', () => {\n    test('merge responses from 1 middleware', async () => {\n      const middleware = createMiddleware(async (c) => c.json({ '400': true }, 400))\n\n      const app = new Hono().get('/test', middleware, (c) => c.json({ '200': true }, 200))\n      const client = hc<typeof app>('http://localhost', { fetch: app.request })\n      const res = await client.test.$get()\n\n      if (res.status === 200) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 200: true }>()\n      }\n      if (res.status === 400) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 400: true }>()\n      }\n    })\n\n    test('merge responses from 2 middlewares', async () => {\n      const middleware1 = createMiddleware(async (c) => c.json({ '400': true }, 400))\n      const middleware2 = createMiddleware(async (c) => c.json({ '401': true }, 401))\n\n      const app = new Hono().get('/test', middleware1, middleware2, (c) =>\n        c.json({ '200': true }, 200)\n      )\n      const client = hc<typeof app>('http://localhost', { fetch: app.request })\n      const res = await client.test.$get()\n\n      if (res.status === 200) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 200: true }>()\n      }\n      if (res.status === 400) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 400: true }>()\n      }\n      if (res.status === 401) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 401: true }>()\n      }\n    })\n\n    test('merge responses from 3 middlewares', async () => {\n      const middleware1 = createMiddleware(async (c) => c.json({ '400': true }, 400))\n      const middleware2 = createMiddleware(async (c) => c.json({ '401': true }, 401))\n      const middleware3 = createMiddleware(async (c) => c.json({ '403': true }, 403))\n\n      const app = new Hono().get('/test', middleware1, middleware2, middleware3, (c) =>\n        c.json({ '200': true }, 200)\n      )\n      const client = hc<typeof app>('http://localhost', { fetch: app.request })\n      const res = await client.test.$get()\n\n      if (res.status === 200) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 200: true }>()\n      }\n      if (res.status === 400) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 400: true }>()\n      }\n      if (res.status === 401) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 401: true }>()\n      }\n      if (res.status === 403) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 403: true }>()\n      }\n    })\n\n    test('merge responses from 4 middlewares', async () => {\n      const middleware1 = createMiddleware(async (c) => c.json({ '400': true }, 400))\n      const middleware2 = createMiddleware(async (c) => c.json({ '401': true }, 401))\n      const middleware3 = createMiddleware(async (c) => c.json({ '403': true }, 403))\n      const middleware4 = createMiddleware(async (c) => c.json({ '404': true }, 404))\n\n      const app = new Hono().get('/test', middleware1, middleware2, middleware3, middleware4, (c) =>\n        c.json({ '200': true }, 200)\n      )\n      const client = hc<typeof app>('http://localhost', { fetch: app.request })\n      const res = await client.test.$get()\n\n      if (res.status === 200) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 200: true }>()\n      }\n      if (res.status === 400) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 400: true }>()\n      }\n      if (res.status === 401) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 401: true }>()\n      }\n      if (res.status === 403) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 403: true }>()\n      }\n      if (res.status === 404) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 404: true }>()\n      }\n    })\n\n    test('merge responses from 5 middlewares', async () => {\n      const middleware1 = createMiddleware(async (c) => c.json({ '400': true }, 400))\n      const middleware2 = createMiddleware(async (c) => c.json({ '401': true }, 401))\n      const middleware3 = createMiddleware(async (c) => c.json({ '403': true }, 403))\n      const middleware4 = createMiddleware(async (c) => c.json({ '404': true }, 404))\n      const middleware5 = createMiddleware(async (c) => c.json({ '300': true }, 300))\n\n      const app = new Hono().get(\n        '/test',\n        middleware1,\n        middleware2,\n        middleware3,\n        middleware4,\n        middleware5,\n        (c) => c.json({ '200': true }, 200)\n      )\n      const client = hc<typeof app>('http://localhost', { fetch: app.request })\n      const res = await client.test.$get()\n\n      if (res.status === 200) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 200: true }>()\n      }\n      if (res.status === 400) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 400: true }>()\n      }\n      if (res.status === 401) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 401: true }>()\n      }\n      if (res.status === 403) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 403: true }>()\n      }\n      if (res.status === 300) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 300: true }>()\n      }\n    })\n\n    test('merge responses from 6 middlewares', async () => {\n      const middleware1 = createMiddleware(async (c) => c.json({ '400': true }, 400))\n      const middleware2 = createMiddleware(async (c) => c.json({ '401': true }, 401))\n      const middleware3 = createMiddleware(async (c) => c.json({ '403': true }, 403))\n      const middleware4 = createMiddleware(async (c) => c.json({ '404': true }, 404))\n      const middleware5 = createMiddleware(async (c) => c.json({ '300': true }, 300))\n      const middleware6 = createMiddleware(async (c) => c.json({ '201': true }, 201))\n\n      const app = new Hono().get(\n        '/test',\n        middleware1,\n        middleware2,\n        middleware3,\n        middleware4,\n        middleware5,\n        middleware6,\n        (c) => c.json({ '200': true }, 200)\n      )\n      const client = hc<typeof app>('http://localhost', { fetch: app.request })\n      const res = await client.test.$get()\n\n      if (res.status === 200) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 200: true }>()\n      }\n      if (res.status === 400) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 400: true }>()\n      }\n      if (res.status === 401) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 401: true }>()\n      }\n      if (res.status === 403) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 403: true }>()\n      }\n      if (res.status === 404) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 404: true }>()\n      }\n      if (res.status === 300) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 300: true }>()\n      }\n      if (res.status === 201) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 201: true }>()\n      }\n    })\n\n    test('merge responses from 7 middlewares', async () => {\n      const middleware1 = createMiddleware(async (c) => c.json({ '400': true }, 400))\n      const middleware2 = createMiddleware(async (c) => c.json({ '401': true }, 401))\n      const middleware3 = createMiddleware(async (c) => c.json({ '403': true }, 403))\n      const middleware4 = createMiddleware(async (c) => c.json({ '404': true }, 404))\n      const middleware5 = createMiddleware(async (c) => c.json({ '300': true }, 300))\n      const middleware6 = createMiddleware(async (c) => c.json({ '201': true }, 201))\n      const middleware7 = createMiddleware(async (c) => c.json({ '202': true }, 202))\n\n      const app = new Hono().get(\n        '/test',\n        middleware1,\n        middleware2,\n        middleware3,\n        middleware4,\n        middleware5,\n        middleware6,\n        middleware7,\n        (c) => c.json({ '200': true }, 200)\n      )\n      const client = hc<typeof app>('http://localhost', { fetch: app.request })\n      const res = await client.test.$get()\n\n      if (res.status === 200) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 200: true }>()\n      }\n      if (res.status === 400) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 400: true }>()\n      }\n      if (res.status === 401) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 401: true }>()\n      }\n      if (res.status === 403) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 403: true }>()\n      }\n      if (res.status === 404) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 404: true }>()\n      }\n      if (res.status === 300) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 300: true }>()\n      }\n      if (res.status === 201) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 201: true }>()\n      }\n      if (res.status === 202) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 202: true }>()\n      }\n    })\n\n    test('merge responses from 8 middlewares', async () => {\n      const middleware1 = createMiddleware(async (c) => c.json({ '400': true }, 400))\n      const middleware2 = createMiddleware(async (c) => c.json({ '401': true }, 401))\n      const middleware3 = createMiddleware(async (c) => c.json({ '403': true }, 403))\n      const middleware4 = createMiddleware(async (c) => c.json({ '404': true }, 404))\n      const middleware5 = createMiddleware(async (c) => c.json({ '300': true }, 300))\n      const middleware6 = createMiddleware(async (c) => c.json({ '201': true }, 201))\n      const middleware7 = createMiddleware(async (c) => c.json({ '202': true }, 202))\n      const middleware8 = createMiddleware(async (c) => c.json({ '203': true }, 203))\n\n      const app = new Hono().get(\n        '/test',\n        middleware1,\n        middleware2,\n        middleware3,\n        middleware4,\n        middleware5,\n        middleware6,\n        middleware7,\n        middleware8,\n        (c) => c.json({ '200': true }, 200)\n      )\n      const client = hc<typeof app>('http://localhost', { fetch: app.request })\n      const res = await client.test.$get()\n\n      if (res.status === 200) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 200: true }>()\n      }\n      if (res.status === 400) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 400: true }>()\n      }\n      if (res.status === 401) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 401: true }>()\n      }\n      if (res.status === 403) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 403: true }>()\n      }\n      if (res.status === 404) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 404: true }>()\n      }\n      if (res.status === 300) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 300: true }>()\n      }\n      if (res.status === 201) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 201: true }>()\n      }\n      if (res.status === 202) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 202: true }>()\n      }\n      if (res.status === 203) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 203: true }>()\n      }\n    })\n\n    test('merge responses from 9 middlewares', async () => {\n      const middleware1 = createMiddleware(async (c) => c.json({ '400': true }, 400))\n      const middleware2 = createMiddleware(async (c) => c.json({ '401': true }, 401))\n      const middleware3 = createMiddleware(async (c) => c.json({ '403': true }, 403))\n      const middleware4 = createMiddleware(async (c) => c.json({ '404': true }, 404))\n      const middleware5 = createMiddleware(async (c) => c.json({ '300': true }, 300))\n      const middleware6 = createMiddleware(async (c) => c.json({ '201': true }, 201))\n      const middleware7 = createMiddleware(async (c) => c.json({ '202': true }, 202))\n      const middleware8 = createMiddleware(async (c) => c.json({ '203': true }, 203))\n      const middleware9 = createMiddleware(async (c) => c.json({ '301': true }, 301))\n\n      const app = new Hono().get(\n        '/test',\n        middleware1,\n        middleware2,\n        middleware3,\n        middleware4,\n        middleware5,\n        middleware6,\n        middleware7,\n        middleware8,\n        middleware9,\n        (c) => c.json({ '200': true }, 200)\n      )\n      const client = hc<typeof app>('http://localhost', { fetch: app.request })\n      const res = await client.test.$get()\n\n      if (res.status === 200) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 200: true }>()\n      }\n      if (res.status === 400) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 400: true }>()\n      }\n      if (res.status === 401) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 401: true }>()\n      }\n      if (res.status === 403) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 403: true }>()\n      }\n      if (res.status === 404) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 404: true }>()\n      }\n      if (res.status === 300) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 300: true }>()\n      }\n      if (res.status === 201) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 201: true }>()\n      }\n      if (res.status === 202) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 202: true }>()\n      }\n      if (res.status === 203) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 203: true }>()\n      }\n      if (res.status === 301) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 301: true }>()\n      }\n    })\n  })\n\n  describe('Middleware returning union type (undefined | TypedResponse)', () => {\n    test('middleware that conditionally returns response should merge types', async () => {\n      const middleware = createMiddleware(async (c, next) => {\n        if (Math.random() > 0.5) {\n          return c.json({ cause: 'Unauthorized' }, 401)\n        }\n        await next()\n      })\n\n      const app = new Hono().get('/test', middleware, (c) => c.json({ message: 'Hello' }, 200))\n      const client = hc<typeof app>('http://localhost', {\n        fetch: app.request,\n      })\n      const res = await client.test.$get()\n\n      if (res.status === 200) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ message: string }>()\n      }\n      if (res.status === 401) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ cause: string }>()\n      }\n    })\n  })\n\n  describe('Merge responses from multiple handlers, no path pattern', () => {\n    test('merge responses from 1 middleware', async () => {\n      const middleware = createMiddleware(async (c) => c.json({ '400': true }, 400))\n\n      const app = new Hono().get(middleware, (c) => c.json({ '200': true }, 200))\n      const client = hc<typeof app>('http://localhost', { fetch: app.request })\n      const res = await client.index.$get()\n\n      if (res.status === 200) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 200: true }>()\n      }\n      if (res.status === 400) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 400: true }>()\n      }\n    })\n\n    test('merge responses from 2 middlewares', async () => {\n      const middleware1 = createMiddleware(async (c) => c.json({ '400': true }, 400))\n      const middleware2 = createMiddleware(async (c) => c.json({ '401': true }, 401))\n\n      const app = new Hono().get(middleware1, middleware2, (c) => c.json({ '200': true }, 200))\n      const client = hc<typeof app>('http://localhost', { fetch: app.request })\n      const res = await client.index.$get()\n\n      if (res.status === 200) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 200: true }>()\n      }\n      if (res.status === 400) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 400: true }>()\n      }\n      if (res.status === 401) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 401: true }>()\n      }\n    })\n\n    test('merge responses from 3 middlewares', async () => {\n      const middleware1 = createMiddleware(async (c) => c.json({ '400': true }, 400))\n      const middleware2 = createMiddleware(async (c) => c.json({ '401': true }, 401))\n      const middleware3 = createMiddleware(async (c) => c.json({ '403': true }, 403))\n\n      const app = new Hono().get(middleware1, middleware2, middleware3, (c) =>\n        c.json({ '200': true }, 200)\n      )\n      const client = hc<typeof app>('http://localhost', { fetch: app.request })\n      const res = await client.index.$get()\n\n      if (res.status === 200) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 200: true }>()\n      }\n      if (res.status === 400) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 400: true }>()\n      }\n      if (res.status === 401) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 401: true }>()\n      }\n      if (res.status === 403) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 403: true }>()\n      }\n    })\n\n    test('merge responses from 4 middlewares', async () => {\n      const middleware1 = createMiddleware(async (c) => c.json({ '400': true }, 400))\n      const middleware2 = createMiddleware(async (c) => c.json({ '401': true }, 401))\n      const middleware3 = createMiddleware(async (c) => c.json({ '403': true }, 403))\n      const middleware4 = createMiddleware(async (c) => c.json({ '404': true }, 404))\n\n      const app = new Hono().get(middleware1, middleware2, middleware3, middleware4, (c) =>\n        c.json({ '200': true }, 200)\n      )\n      const client = hc<typeof app>('http://localhost', { fetch: app.request })\n      const res = await client.index.$get()\n\n      if (res.status === 200) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 200: true }>()\n      }\n      if (res.status === 400) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 400: true }>()\n      }\n      if (res.status === 401) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 401: true }>()\n      }\n      if (res.status === 403) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 403: true }>()\n      }\n      if (res.status === 404) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 404: true }>()\n      }\n    })\n\n    test('merge responses from 5 middlewares', async () => {\n      const middleware1 = createMiddleware(async (c) => c.json({ '400': true }, 400))\n      const middleware2 = createMiddleware(async (c) => c.json({ '401': true }, 401))\n      const middleware3 = createMiddleware(async (c) => c.json({ '403': true }, 403))\n      const middleware4 = createMiddleware(async (c) => c.json({ '404': true }, 404))\n      const middleware5 = createMiddleware(async (c) => c.json({ '300': true }, 300))\n\n      const app = new Hono().get(\n        middleware1,\n        middleware2,\n        middleware3,\n        middleware4,\n        middleware5,\n        (c) => c.json({ '200': true }, 200)\n      )\n      const client = hc<typeof app>('http://localhost', { fetch: app.request })\n      const res = await client.index.$get()\n\n      if (res.status === 200) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 200: true }>()\n      }\n      if (res.status === 400) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 400: true }>()\n      }\n      if (res.status === 401) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 401: true }>()\n      }\n      if (res.status === 403) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 403: true }>()\n      }\n      if (res.status === 300) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 300: true }>()\n      }\n    })\n\n    test('merge responses from 6 middlewares', async () => {\n      const middleware1 = createMiddleware(async (c) => c.json({ '400': true }, 400))\n      const middleware2 = createMiddleware(async (c) => c.json({ '401': true }, 401))\n      const middleware3 = createMiddleware(async (c) => c.json({ '403': true }, 403))\n      const middleware4 = createMiddleware(async (c) => c.json({ '404': true }, 404))\n      const middleware5 = createMiddleware(async (c) => c.json({ '300': true }, 300))\n      const middleware6 = createMiddleware(async (c) => c.json({ '201': true }, 201))\n\n      const app = new Hono().get(\n        middleware1,\n        middleware2,\n        middleware3,\n        middleware4,\n        middleware5,\n        middleware6,\n        (c) => c.json({ '200': true }, 200)\n      )\n      const client = hc<typeof app>('http://localhost', { fetch: app.request })\n      const res = await client.index.$get()\n\n      if (res.status === 200) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 200: true }>()\n      }\n      if (res.status === 400) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 400: true }>()\n      }\n      if (res.status === 401) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 401: true }>()\n      }\n      if (res.status === 403) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 403: true }>()\n      }\n      if (res.status === 404) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 404: true }>()\n      }\n      if (res.status === 300) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 300: true }>()\n      }\n      if (res.status === 201) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 201: true }>()\n      }\n    })\n\n    test('merge responses from 7 middlewares', async () => {\n      const middleware1 = createMiddleware(async (c) => c.json({ '400': true }, 400))\n      const middleware2 = createMiddleware(async (c) => c.json({ '401': true }, 401))\n      const middleware3 = createMiddleware(async (c) => c.json({ '403': true }, 403))\n      const middleware4 = createMiddleware(async (c) => c.json({ '404': true }, 404))\n      const middleware5 = createMiddleware(async (c) => c.json({ '300': true }, 300))\n      const middleware6 = createMiddleware(async (c) => c.json({ '201': true }, 201))\n      const middleware7 = createMiddleware(async (c) => c.json({ '202': true }, 202))\n\n      const app = new Hono().get(\n        middleware1,\n        middleware2,\n        middleware3,\n        middleware4,\n        middleware5,\n        middleware6,\n        middleware7,\n        (c) => c.json({ '200': true }, 200)\n      )\n      const client = hc<typeof app>('http://localhost', { fetch: app.request })\n      const res = await client.index.$get()\n\n      if (res.status === 200) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 200: true }>()\n      }\n      if (res.status === 400) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 400: true }>()\n      }\n      if (res.status === 401) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 401: true }>()\n      }\n      if (res.status === 403) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 403: true }>()\n      }\n      if (res.status === 404) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 404: true }>()\n      }\n      if (res.status === 300) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 300: true }>()\n      }\n      if (res.status === 201) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 201: true }>()\n      }\n      if (res.status === 202) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 202: true }>()\n      }\n    })\n\n    test('merge responses from 8 middlewares', async () => {\n      const middleware1 = createMiddleware(async (c) => c.json({ '400': true }, 400))\n      const middleware2 = createMiddleware(async (c) => c.json({ '401': true }, 401))\n      const middleware3 = createMiddleware(async (c) => c.json({ '403': true }, 403))\n      const middleware4 = createMiddleware(async (c) => c.json({ '404': true }, 404))\n      const middleware5 = createMiddleware(async (c) => c.json({ '300': true }, 300))\n      const middleware6 = createMiddleware(async (c) => c.json({ '201': true }, 201))\n      const middleware7 = createMiddleware(async (c) => c.json({ '202': true }, 202))\n      const middleware8 = createMiddleware(async (c) => c.json({ '203': true }, 203))\n\n      const app = new Hono().get(\n        middleware1,\n        middleware2,\n        middleware3,\n        middleware4,\n        middleware5,\n        middleware6,\n        middleware7,\n        middleware8,\n        (c) => c.json({ '200': true }, 200)\n      )\n      const client = hc<typeof app>('http://localhost', { fetch: app.request })\n      const res = await client.index.$get()\n\n      if (res.status === 200) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 200: true }>()\n      }\n      if (res.status === 400) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 400: true }>()\n      }\n      if (res.status === 401) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 401: true }>()\n      }\n      if (res.status === 403) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 403: true }>()\n      }\n      if (res.status === 404) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 404: true }>()\n      }\n      if (res.status === 300) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 300: true }>()\n      }\n      if (res.status === 201) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 201: true }>()\n      }\n      if (res.status === 202) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 202: true }>()\n      }\n      if (res.status === 203) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 203: true }>()\n      }\n    })\n\n    test('merge responses from 9 middlewares', async () => {\n      const middleware1 = createMiddleware(async (c) => c.json({ '400': true }, 400))\n      const middleware2 = createMiddleware(async (c) => c.json({ '401': true }, 401))\n      const middleware3 = createMiddleware(async (c) => c.json({ '403': true }, 403))\n      const middleware4 = createMiddleware(async (c) => c.json({ '404': true }, 404))\n      const middleware5 = createMiddleware(async (c) => c.json({ '300': true }, 300))\n      const middleware6 = createMiddleware(async (c) => c.json({ '201': true }, 201))\n      const middleware7 = createMiddleware(async (c) => c.json({ '202': true }, 202))\n      const middleware8 = createMiddleware(async (c) => c.json({ '203': true }, 203))\n      const middleware9 = createMiddleware(async (c) => c.json({ '301': true }, 301))\n\n      const app = new Hono().get(\n        middleware1,\n        middleware2,\n        middleware3,\n        middleware4,\n        middleware5,\n        middleware6,\n        middleware7,\n        middleware8,\n        middleware9,\n        (c) => c.json({ '200': true }, 200)\n      )\n      const client = hc<typeof app>('http://localhost', { fetch: app.request })\n      const res = await client.index.$get()\n\n      if (res.status === 200) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 200: true }>()\n      }\n      if (res.status === 400) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 400: true }>()\n      }\n      if (res.status === 401) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 401: true }>()\n      }\n      if (res.status === 403) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 403: true }>()\n      }\n      if (res.status === 404) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 404: true }>()\n      }\n      if (res.status === 300) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 300: true }>()\n      }\n      if (res.status === 201) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 201: true }>()\n      }\n      if (res.status === 202) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 202: true }>()\n      }\n      if (res.status === 203) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 203: true }>()\n      }\n      if (res.status === 301) {\n        expectTypeOf(await res.json()).toEqualTypeOf<{ 301: true }>()\n      }\n    })\n  })\n\n  describe('Infers types with multiple middlewares but none returning response', () => {\n    const middleware = createMiddleware(async () => {})\n    const handler = (c: Context) => c.json({ ok: true }, 200)\n    type Expected = {\n      '/': {\n        $get: {\n          input: {}\n          output: {\n            ok: true\n          }\n          outputFormat: 'json'\n          status: 200\n        }\n      }\n    }\n\n    test('infer the correct response type with 1 middleware and 1 handler', () => {\n      const routes = new Hono().get('/', middleware, handler)\n      type Actual = ExtractSchema<typeof routes>\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n\n    test('infer the correct response type with 1 middleware and 2 handler', () => {\n      const routes = new Hono().get('/', middleware, handler, handler)\n      type Actual = ExtractSchema<typeof routes>\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n\n    test('infer the correct response type with 2 middleware and 1 handler', () => {\n      const routes = new Hono().get('/', middleware, middleware, handler)\n      type Actual = ExtractSchema<typeof routes>\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n\n    test('infer the correct response type with 3 middleware and 1 handler', () => {\n      const routes = new Hono().get('/', middleware, middleware, middleware, handler)\n      type Actual = ExtractSchema<typeof routes>\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n\n    test('infer the correct response type with 4 middleware and 1 handler', () => {\n      const routes = new Hono().get('/', middleware, middleware, middleware, middleware, handler)\n      type Actual = ExtractSchema<typeof routes>\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n\n    test('infer the correct response type with 5 middleware and 1 handler', () => {\n      const routes = new Hono().get(\n        '/',\n        middleware,\n        middleware,\n        middleware,\n        middleware,\n        middleware,\n        handler\n      )\n      type Actual = ExtractSchema<typeof routes>\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n\n    test('infer the correct response type with 6 middleware and 1 handler', () => {\n      const routes = new Hono().get(\n        '/',\n        middleware,\n        middleware,\n        middleware,\n        middleware,\n        middleware,\n        middleware,\n        handler\n      )\n      type Actual = ExtractSchema<typeof routes>\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n\n    test('infer the correct response type with 7 middleware and 1 handler', () => {\n      const routes = new Hono().get(\n        '/',\n        middleware,\n        middleware,\n        middleware,\n        middleware,\n        middleware,\n        middleware,\n        middleware,\n        handler\n      )\n      type Actual = ExtractSchema<typeof routes>\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n\n    test('infer the correct response type with 8 middleware and 1 handler', () => {\n      const routes = new Hono().get(\n        '/',\n        middleware,\n        middleware,\n        middleware,\n        middleware,\n        middleware,\n        middleware,\n        middleware,\n        middleware,\n        handler\n      )\n      type Actual = ExtractSchema<typeof routes>\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n\n    test('infer the correct response type with 9 middleware and 1 handler', () => {\n      const routes = new Hono().get(\n        '/',\n        middleware,\n        middleware,\n        middleware,\n        middleware,\n        middleware,\n        middleware,\n        middleware,\n        middleware,\n        middleware,\n        handler\n      )\n      type Actual = ExtractSchema<typeof routes>\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n  })\n\n  describe('Infers types with multiple middlewares but only some returning response', () => {\n    const handler = (c: Context) => c.json({ ok: true }, 200)\n\n    test('infer the correct response type with 2 middleware, only one returning response', () => {\n      const middleware1 = async (c: Context) => {\n        if (Math.random() > 0.5) {\n          return c.json(\n            {\n              q: true,\n            },\n            200\n          )\n        }\n      }\n      const middleware2 = async () => {}\n\n      const routes = new Hono().get('/', middleware1, middleware2, handler)\n      type Actual = ExtractSchema<typeof routes>\n      type Expected = {\n        '/': {\n          $get:\n            | {\n                input: {}\n                output: {\n                  ok: true\n                }\n                outputFormat: 'json'\n                status: 200\n              }\n            | {\n                input: {}\n                output: {\n                  q: true\n                }\n                outputFormat: 'json'\n                status: 200\n              }\n        }\n      }\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n\n    test('infer the correct response type with 3 middleware, 2 returning response', () => {\n      const middleware1 = async (c: Context) => {\n        if (Math.random() > 0.5) {\n          return c.json(\n            {\n              q1: true,\n            },\n            200\n          )\n        }\n      }\n      const middleware2 = async (c: Context) => {\n        if (Math.random() > 0.5) {\n          return c.json(\n            {\n              q2: true,\n            },\n            200\n          )\n        }\n      }\n      const middleware3 = async () => {}\n\n      const routes = new Hono().get('/', middleware1, middleware2, middleware3, handler)\n      type Actual = ExtractSchema<typeof routes>\n      type Expected = {\n        '/': {\n          $get:\n            | {\n                input: {}\n                output: {\n                  ok: true\n                }\n                outputFormat: 'json'\n                status: 200\n              }\n            | {\n                input: {}\n                output: {\n                  q1: true\n                }\n                outputFormat: 'json'\n                status: 200\n              }\n            | {\n                input: {}\n                output: {\n                  q2: true\n                }\n                outputFormat: 'json'\n                status: 200\n              }\n        }\n      }\n      type verify = Expect<Equal<Expected, Actual>>\n    })\n  })\n})\n\ndescribe('Handlers returning Promise<void>', () => {\n  it('should not be added to schema when calling await next() with another route', async () => {\n    const app = new Hono()\n      .get('/', async (c, next) => {\n        await next()\n      })\n      .get('/foo', async (c) => c.text('foo'))\n\n    const client = testClient(app)\n\n    // @ts-expect-error '/' route returns Promise<void>, so it should not be in schema\n    const res = await client.index.$get()\n  })\n\n  it('should not be added to schema when only Promise<void> handler exists', async () => {\n    const app = new Hono().get('/', async (c, next) => {\n      await next()\n    })\n\n    const client = testClient(app)\n\n    // @ts-expect-error '/' route returns Promise<void>, so it should not be in schema\n    const res = await client.index.$get()\n  })\n\n  it('should set path from .use() for subsequent handlers without path', () => {\n    const app = new Hono()\n      .use('/:id', async (c, next) => {\n        await next()\n      })\n      .post((c) => {\n        c.req.param('id')\n        return c.text('after')\n      })\n\n    type Actual = ExtractSchema<typeof app>\n    type Expected = {\n      '/:id': {\n        $post: {\n          input: {\n            param: {\n              id: string\n            }\n          }\n          output: 'after'\n          outputFormat: 'text'\n          status: ContentfulStatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  it('should preserve existing routes when .use() sets a new path', () => {\n    const app = new Hono()\n      .get((c) => c.text('before'))\n      .use('/:id', async (c, next) => {\n        await next()\n      })\n      .post((c) => {\n        c.req.param('id')\n        return c.text('after')\n      })\n\n    type Actual = ExtractSchema<typeof app>\n    type Expected = {\n      '/': {\n        $get: {\n          input: {}\n          output: 'before'\n          outputFormat: 'text'\n          status: ContentfulStatusCode\n        }\n      }\n    } & {\n      '/:id': {\n        $post: {\n          input: {\n            param: {\n              id: string\n            }\n          }\n          output: 'after'\n          outputFormat: 'text'\n          status: ContentfulStatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  it('should work with .get() that returns Promise<void> instead of .use()', () => {\n    const app = new Hono()\n      .get((c) => c.text('before'))\n      .get('/:id', async (c, next) => {\n        await next()\n      })\n      .post((c) => {\n        c.req.param('id')\n        return c.text('after')\n      })\n\n    type Actual = ExtractSchema<typeof app>\n    type Expected = {\n      '/': {\n        $get: {\n          input: {}\n          output: 'before'\n          outputFormat: 'text'\n          status: ContentfulStatusCode\n        }\n      }\n    } & {\n      '/:id': {\n        $post: {\n          input: {\n            param: {\n              id: string\n            }\n          }\n          output: 'after'\n          outputFormat: 'text'\n          status: ContentfulStatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  it('should work with .get() that returns Promise<void> instead of .use() - multiple middleware', () => {\n    const app = new Hono()\n      .get((c) => c.text('before'))\n      .get(\n        '/:id',\n        async (c, next) => {\n          await next()\n        },\n        async (c, next) => {\n          await next()\n        }\n      )\n      .post((c) => {\n        c.req.param('id')\n        return c.text('after')\n      })\n\n    type Actual = ExtractSchema<typeof app>\n    type Expected = {\n      '/': {\n        $get: {\n          input: {}\n          output: 'before'\n          outputFormat: 'text'\n          status: ContentfulStatusCode\n        }\n      }\n    } & {\n      '/:id': {\n        $post: {\n          input: {\n            param: {\n              id: string\n            }\n          }\n          output: 'after'\n          outputFormat: 'text'\n          status: ContentfulStatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  it('should use the last registered path for a pathless handler with multiple middlewares', () => {\n    const app = new Hono()\n      .get('/foo', async (_c, next) => {\n        await next()\n      })\n      .get('/bar', async (_c, next) => {\n        await next()\n      })\n      .get((c) => c.text('after'))\n\n    type Actual = ExtractSchema<typeof app>\n    type Expected = {\n      '/bar': {\n        $get: {\n          input: {}\n          output: 'after'\n          outputFormat: 'text'\n          status: ContentfulStatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  it('should use the last registered path for a pathless handler with mixed handlers and middlewares', () => {\n    const app = new Hono()\n      .get('/void', async (_c, next) => {\n        await next()\n      })\n      .get('/handler', (c) => c.text('handler'))\n      .get((c) => c.text('after'))\n\n    type Actual = ExtractSchema<typeof app>\n    type Expected = {\n      '/handler': {\n        $get: {\n          input: {}\n          output: 'handler'\n          outputFormat: 'text'\n          status: ContentfulStatusCode\n        }\n      }\n    } & {\n      '/handler': {\n        $get: {\n          input: {}\n          output: 'after'\n          outputFormat: 'text'\n          status: ContentfulStatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  it('should set path as `*` when .use() is called without path argument', () => {\n    const app = new Hono()\n      .get('/foo', async (_c, next) => {\n        await next()\n      })\n      .use(async (c, next) => {\n        await next()\n      })\n      .post((c) => {\n        return c.text('after')\n      })\n\n    type Actual = ExtractSchema<typeof app>\n    type Expected = {\n      '*': {\n        $post: {\n          input: {}\n          output: 'after'\n          outputFormat: 'text'\n          status: ContentfulStatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n})\n\n// Regression tests for #4388: routes before .use() with explicit paths should not be dropped\ndescribe('Routes before .use() with explicit paths (#4388)', () => {\n  it('should preserve explicit-path .get() before .use() with path', () => {\n    const app = new Hono()\n      .get('/', (c) => c.text('Hello from /'))\n      .use('/noop', async (c, next) => {\n        await next()\n      })\n\n    type Actual = ExtractSchema<typeof app>\n    type Expected = {\n      '/': {\n        $get: {\n          input: {}\n          output: 'Hello from /'\n          outputFormat: 'text'\n          status: ContentfulStatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  it('should preserve .get() route and infer .post() under .use() path', () => {\n    const app = new Hono()\n      .get('/', (c) => c.text('Hello from /'))\n      .use('/:slug', async (c, next) => {\n        await next()\n      })\n      .post((c) => c.text('posted'))\n\n    type Actual = ExtractSchema<typeof app>\n    type Expected = {\n      '/': {\n        $get: {\n          input: {}\n          output: 'Hello from /'\n          outputFormat: 'text'\n          status: ContentfulStatusCode\n        }\n      }\n    } & {\n      '/:slug': {\n        $post: {\n          input: {\n            param: {\n              slug: string\n            }\n          }\n          output: 'posted'\n          outputFormat: 'text'\n          status: ContentfulStatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n\n  it('should preserve routes through .route() wrapping', () => {\n    const inner = new Hono()\n      .get('/', (c) => c.text('index'))\n      .use('/:slug', async (c, next) => {\n        await next()\n      })\n      .post((c) => c.text('posted'))\n\n    const app = new Hono().route('/api', inner)\n\n    const client = hc<typeof app>('http://localhost')\n    // '/api' $get should exist (from inner .get('/'))\n    expectTypeOf(client.api.$get).toBeFunction()\n    // '/api/:slug' $post should exist (from inner .post() after .use('/:slug'))\n    expectTypeOf(client.api[':slug'].$post).toBeFunction()\n  })\n\n  it('should preserve multiple explicit-path routes before .use()', () => {\n    const app = new Hono()\n      .get('/', (c) => c.text('home'))\n      .get('/about', (c) => c.text('about'))\n      .use('/mw', async (c, next) => {\n        await next()\n      })\n\n    type Actual = ExtractSchema<typeof app>\n    type Expected = {\n      '/': {\n        $get: {\n          input: {}\n          output: 'home'\n          outputFormat: 'text'\n          status: ContentfulStatusCode\n        }\n      }\n    } & {\n      '/about': {\n        $get: {\n          input: {}\n          output: 'about'\n          outputFormat: 'text'\n          status: ContentfulStatusCode\n        }\n      }\n    }\n    type verify = Expect<Equal<Expected, Actual>>\n  })\n})\n"
  },
  {
    "path": "src/types.ts",
    "content": "/**\n * @module\n * This module contains some type definitions for the Hono modules.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { Context } from './context'\nimport type { HonoBase } from './hono-base'\nimport type { CustomHeader, RequestHeader } from './utils/headers'\nimport type { StatusCode } from './utils/http-status'\nimport type {\n  IfAnyThenEmptyObject,\n  IsAny,\n  JSONValue,\n  RemoveBlankRecord,\n  Simplify,\n  UnionToIntersection,\n} from './utils/types'\n\n////////////////////////////////////////\n//////                            //////\n//////           Values           //////\n//////                            //////\n////////////////////////////////////////\n\nexport type Bindings = object\nexport type Variables = object\n\nexport type BlankEnv = {}\nexport type Env = {\n  Bindings?: Bindings\n  Variables?: Variables\n}\n\nexport type Next = () => Promise<void>\n\nexport type ExtractInput<I extends Input | Input['in']> = I extends Input\n  ? unknown extends I['in']\n    ? {}\n    : I['in']\n  : I\nexport type Input = {\n  in?: {}\n  out?: {}\n  outputFormat?: ResponseFormat\n}\n\nexport type BlankSchema = {}\nexport type BlankInput = {}\n\n////////////////////////////////////////\n//////                            //////\n//////          Routes            //////\n//////                            //////\n////////////////////////////////////////\n\nexport interface RouterRoute {\n  basePath: string\n  path: string\n  method: string\n  handler: H\n}\n\n////////////////////////////////////////\n//////                            //////\n//////          Handlers          //////\n//////                            //////\n////////////////////////////////////////\n\nexport type HandlerResponse<O> =\n  | Response\n  | TypedResponse<O>\n  | Promise<Response | TypedResponse<O>>\n  | Promise<void>\n\nexport type Handler<\n  E extends Env = any,\n  P extends string = any,\n  I extends Input = BlankInput,\n  R extends HandlerResponse<any> = any,\n> = (c: Context<E, P, I>, next: Next) => R\n\nexport type MiddlewareHandler<\n  E extends Env = any,\n  P extends string = string,\n  I extends Input = {},\n  R extends HandlerResponse<any> = Response,\n> = (c: Context<E, P, I>, next: Next) => Promise<R | void>\n\nexport type H<\n  E extends Env = any,\n  P extends string = any,\n  I extends Input = BlankInput,\n  R extends HandlerResponse<any> = any,\n> = Handler<E, P, I, R> | MiddlewareHandler<E, P, I, R>\n\n/**\n * You can extend this interface to define a custom `c.notFound()` Response type.\n *\n * @example\n * declare module 'hono' {\n *   interface NotFoundResponse extends Response, TypedResponse<string, 404, 'text'> {}\n * }\n */\nexport interface NotFoundResponse {}\n\nexport type NotFoundHandler<E extends Env = any> = (\n  c: Context<E>\n) => NotFoundResponse extends Response\n  ? NotFoundResponse | Promise<NotFoundResponse>\n  : Response | Promise<Response>\n\nexport interface HTTPResponseError extends Error {\n  getResponse: () => Response\n}\nexport type ErrorHandler<E extends Env = any> = (\n  err: Error | HTTPResponseError,\n  c: Context<E>\n) => Response | Promise<Response>\n\n////////////////////////////////////////\n//////                            //////\n//////     HandlerInterface       //////\n//////                            //////\n////////////////////////////////////////\n\nexport interface HandlerInterface<\n  E extends Env = Env,\n  M extends string = string,\n  S extends Schema = BlankSchema,\n  BasePath extends string = '/',\n  CurrentPath extends string = BasePath,\n> {\n  // app.get(handler)\n  <\n    P extends string = CurrentPath,\n    I extends Input = BlankInput,\n    R extends HandlerResponse<any> = any,\n    E2 extends Env = E,\n  >(\n    handler: H<E2, P, I, R>\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2]>,\n    S & ToSchema<M, P, I, MergeTypedResponse<R>>,\n    BasePath,\n    CurrentPath\n  >\n\n  // app.get(handler x2)\n  <\n    P extends string = CurrentPath,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    R extends HandlerResponse<any> = any,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    M1 extends H<E2, P, I> = H<E2, P, I>,\n  >(\n    ...handlers: [H<E2, P, I> & M1, H<E3, P, I2, R>]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3]>,\n    S & ToSchema<M, P, I2, MergeTypedResponse<R> | MergeMiddlewareResponse<M1>>,\n    BasePath,\n    CurrentPath\n  >\n\n  // app.get(path, handler)\n  <\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    E2 extends Env = E,\n  >(\n    path: P,\n    handler: H<E2, MergedPath, I, R>\n  ): HonoBase<\n    E,\n    AddSchemaIfHasResponse<MergeTypedResponse<R>, S, M, P, I, BasePath>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.get(handler x 3)\n  <\n    P extends string = CurrentPath,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    // Middleware\n    M1 extends H<E2, P, I> = H<E2, P, I>,\n    M2 extends H<E3, P, I2> = H<E3, P, I2>,\n  >(\n    ...handlers: [H<E2, P, I> & M1, H<E3, P, I2> & M2, H<E4, P, I3, R>]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    S &\n      ToSchema<\n        M,\n        P,\n        I3,\n        MergeTypedResponse<R> | MergeMiddlewareResponse<M1> | MergeMiddlewareResponse<M2>\n      >,\n    BasePath,\n    CurrentPath\n  >\n\n  // app.get(path, handler x2)\n  <\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    // Middleware\n    M1 extends H<E2, MergedPath, I> = H<E2, MergedPath, I>,\n  >(\n    path: P,\n    ...handlers: [H<E2, MergedPath, I> & M1, H<E3, MergedPath, I2, R>]\n  ): HonoBase<\n    E,\n    AddSchemaIfHasResponse<\n      MergeTypedResponse<R> | MergeMiddlewareResponse<M1>,\n      S,\n      M,\n      P,\n      I2,\n      BasePath\n    >,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.get(handler x 4)\n  <\n    P extends string = CurrentPath,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    // Middleware\n    M1 extends H<E2, P, I> = H<E2, P, I>,\n    M2 extends H<E3, P, I2> = H<E3, P, I2>,\n    M3 extends H<E4, P, I3> = H<E4, P, I3>,\n  >(\n    ...handlers: [H<E2, P, I> & M1, H<E3, P, I2> & M2, H<E4, P, I3> & M3, H<E5, P, I4, R>]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    S &\n      ToSchema<\n        M,\n        P,\n        I4,\n        | MergeTypedResponse<R>\n        | MergeMiddlewareResponse<M1>\n        | MergeMiddlewareResponse<M2>\n        | MergeMiddlewareResponse<M3>\n      >,\n    BasePath,\n    CurrentPath\n  >\n\n  // app.get(path, handler x3)\n  <\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    // Middleware\n    M1 extends H<E2, MergedPath, I> = H<E2, MergedPath, I>,\n    M2 extends H<E3, MergedPath, I2> = H<E3, MergedPath, I2>,\n  >(\n    path: P,\n    ...handlers: [H<E2, MergedPath, I> & M1, H<E3, MergedPath, I2> & M2, H<E4, MergedPath, I3, R>]\n  ): HonoBase<\n    E,\n    AddSchemaIfHasResponse<\n      MergeTypedResponse<R> | MergeMiddlewareResponse<M1> | MergeMiddlewareResponse<M2>,\n      S,\n      M,\n      P,\n      I3,\n      BasePath\n    >,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.get(handler x 5)\n  <\n    P extends string = CurrentPath,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    // Middleware\n    M1 extends H<E2, P, I> = H<E2, P, I>,\n    M2 extends H<E3, P, I2> = H<E3, P, I2>,\n    M3 extends H<E4, P, I3> = H<E4, P, I3>,\n    M4 extends H<E5, P, I4> = H<E5, P, I4>,\n  >(\n    ...handlers: [\n      H<E2, P, I> & M1,\n      H<E3, P, I2> & M2,\n      H<E4, P, I3> & M3,\n      H<E5, P, I4> & M4,\n      H<E6, P, I5, R>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    S &\n      ToSchema<\n        M,\n        P,\n        I5,\n        | MergeTypedResponse<R>\n        | MergeMiddlewareResponse<M1>\n        | MergeMiddlewareResponse<M2>\n        | MergeMiddlewareResponse<M3>\n        | MergeMiddlewareResponse<M4>\n      >,\n    BasePath,\n    CurrentPath\n  >\n\n  // app.get(path, handler x4)\n  <\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    // Middleware\n    M1 extends H<E2, MergedPath, I> = H<E2, MergedPath, I>,\n    M2 extends H<E3, MergedPath, I2> = H<E3, MergedPath, I2>,\n    M3 extends H<E4, MergedPath, I3> = H<E4, MergedPath, I3>,\n  >(\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I> & M1,\n      H<E3, MergedPath, I2> & M2,\n      H<E4, MergedPath, I3> & M3,\n      H<E5, MergedPath, I4, R>,\n    ]\n  ): HonoBase<\n    E,\n    AddSchemaIfHasResponse<\n      | MergeTypedResponse<R>\n      | MergeMiddlewareResponse<M1>\n      | MergeMiddlewareResponse<M2>\n      | MergeMiddlewareResponse<M3>,\n      S,\n      M,\n      P,\n      I4,\n      BasePath\n    >,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.get(handler x 6)\n  <\n    P extends string = CurrentPath,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    // Middleware\n    M1 extends H<E2, P, I> = H<E2, P, I>,\n    M2 extends H<E3, P, I2> = H<E3, P, I2>,\n    M3 extends H<E4, P, I3> = H<E4, P, I3>,\n    M4 extends H<E5, P, I4> = H<E5, P, I4>,\n    M5 extends H<E6, P, I5> = H<E6, P, I5>,\n  >(\n    ...handlers: [\n      H<E2, P, I> & M1,\n      H<E3, P, I2> & M2,\n      H<E4, P, I3> & M3,\n      H<E5, P, I4> & M4,\n      H<E6, P, I5> & M5,\n      H<E7, P, I6, R>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    S &\n      ToSchema<\n        M,\n        P,\n        I6,\n        | MergeTypedResponse<R>\n        | MergeMiddlewareResponse<M1>\n        | MergeMiddlewareResponse<M2>\n        | MergeMiddlewareResponse<M3>\n        | MergeMiddlewareResponse<M4>\n        | MergeMiddlewareResponse<M5>\n      >,\n    BasePath,\n    CurrentPath\n  >\n\n  // app.get(path, handler x5)\n  <\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    // Middleware\n    M1 extends H<E2, MergedPath, I> = H<E2, MergedPath, I>,\n    M2 extends H<E3, MergedPath, I2> = H<E3, MergedPath, I2>,\n    M3 extends H<E4, MergedPath, I3> = H<E4, MergedPath, I3>,\n    M4 extends H<E5, MergedPath, I4> = H<E5, MergedPath, I4>,\n  >(\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I> & M1,\n      H<E3, MergedPath, I2> & M2,\n      H<E4, MergedPath, I3> & M3,\n      H<E5, MergedPath, I4> & M4,\n      H<E6, MergedPath, I5, R>,\n    ]\n  ): HonoBase<\n    E,\n    AddSchemaIfHasResponse<\n      | MergeTypedResponse<R>\n      | MergeMiddlewareResponse<M1>\n      | MergeMiddlewareResponse<M2>\n      | MergeMiddlewareResponse<M3>\n      | MergeMiddlewareResponse<M4>,\n      S,\n      M,\n      P,\n      I5,\n      BasePath\n    >,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.get(handler x 7)\n  <\n    P extends string = CurrentPath,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    I7 extends Input = I & I2 & I3 & I4 & I5 & I6,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    // Middleware\n    M1 extends H<E2, P, I> = H<E2, P, I>,\n    M2 extends H<E3, P, I2> = H<E3, P, I2>,\n    M3 extends H<E4, P, I3> = H<E4, P, I3>,\n    M4 extends H<E5, P, I4> = H<E5, P, I4>,\n    M5 extends H<E6, P, I5> = H<E6, P, I5>,\n    M6 extends H<E7, P, I6> = H<E7, P, I6>,\n  >(\n    ...handlers: [\n      H<E2, P, I> & M1,\n      H<E3, P, I2> & M2,\n      H<E4, P, I3> & M3,\n      H<E5, P, I4> & M4,\n      H<E6, P, I5> & M5,\n      H<E7, P, I6> & M6,\n      H<E8, P, I7, R>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n    S &\n      ToSchema<\n        M,\n        P,\n        I7,\n        | MergeTypedResponse<R>\n        | MergeMiddlewareResponse<M1>\n        | MergeMiddlewareResponse<M2>\n        | MergeMiddlewareResponse<M3>\n        | MergeMiddlewareResponse<M4>\n        | MergeMiddlewareResponse<M5>\n        | MergeMiddlewareResponse<M6>\n      >,\n    BasePath,\n    CurrentPath\n  >\n\n  // app.get(path, handler x6)\n  <\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    // Middleware\n    M1 extends H<E2, MergedPath, I> = H<E2, MergedPath, I>,\n    M2 extends H<E3, MergedPath, I2> = H<E3, MergedPath, I2>,\n    M3 extends H<E4, MergedPath, I3> = H<E4, MergedPath, I3>,\n    M4 extends H<E5, MergedPath, I4> = H<E5, MergedPath, I4>,\n    M5 extends H<E6, MergedPath, I5> = H<E6, MergedPath, I5>,\n  >(\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I> & M1,\n      H<E3, MergedPath, I2> & M2,\n      H<E4, MergedPath, I3> & M3,\n      H<E5, MergedPath, I4> & M4,\n      H<E6, MergedPath, I5> & M5,\n      H<E7, MergedPath, I6, R>,\n    ]\n  ): HonoBase<\n    E,\n    AddSchemaIfHasResponse<\n      | MergeTypedResponse<R>\n      | MergeMiddlewareResponse<M1>\n      | MergeMiddlewareResponse<M2>\n      | MergeMiddlewareResponse<M3>\n      | MergeMiddlewareResponse<M4>\n      | MergeMiddlewareResponse<M5>,\n      S,\n      M,\n      P,\n      I6,\n      BasePath\n    >,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.get(handler x 8)\n  <\n    P extends string = CurrentPath,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    I7 extends Input = I & I2 & I3 & I4 & I5 & I6,\n    I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n    // Middleware\n    M1 extends H<E2, P, I> = H<E2, P, I>,\n    M2 extends H<E3, P, I2> = H<E3, P, I2>,\n    M3 extends H<E4, P, I3> = H<E4, P, I3>,\n    M4 extends H<E5, P, I4> = H<E5, P, I4>,\n    M5 extends H<E6, P, I5> = H<E6, P, I5>,\n    M6 extends H<E7, P, I6> = H<E7, P, I6>,\n    M7 extends H<E8, P, I7> = H<E8, P, I7>,\n  >(\n    ...handlers: [\n      H<E2, P, I> & M1,\n      H<E3, P, I2> & M2,\n      H<E4, P, I3> & M3,\n      H<E5, P, I4> & M4,\n      H<E6, P, I5> & M5,\n      H<E7, P, I6> & M6,\n      H<E8, P, I7> & M7,\n      H<E9, P, I8, R>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>,\n    S &\n      ToSchema<\n        M,\n        P,\n        I8,\n        | MergeTypedResponse<R>\n        | MergeMiddlewareResponse<M1>\n        | MergeMiddlewareResponse<M2>\n        | MergeMiddlewareResponse<M3>\n        | MergeMiddlewareResponse<M4>\n        | MergeMiddlewareResponse<M5>\n        | MergeMiddlewareResponse<M6>\n        | MergeMiddlewareResponse<M7>\n      >,\n    BasePath,\n    CurrentPath\n  >\n\n  // app.get(path, handler x7)\n  <\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    I7 extends Input = I & I2 & I3 & I4 & I5 & I6,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    // Middleware\n    M1 extends H<E2, MergedPath, I> = H<E2, MergedPath, I>,\n    M2 extends H<E3, MergedPath, I2> = H<E3, MergedPath, I2>,\n    M3 extends H<E4, MergedPath, I3> = H<E4, MergedPath, I3>,\n    M4 extends H<E5, MergedPath, I4> = H<E5, MergedPath, I4>,\n    M5 extends H<E6, MergedPath, I5> = H<E6, MergedPath, I5>,\n    M6 extends H<E7, MergedPath, I6> = H<E7, MergedPath, I6>,\n  >(\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I> & M1,\n      H<E3, MergedPath, I2> & M2,\n      H<E4, MergedPath, I3> & M3,\n      H<E5, MergedPath, I4> & M4,\n      H<E6, MergedPath, I5> & M5,\n      H<E7, MergedPath, I6> & M6,\n      H<E8, MergedPath, I7, R>,\n    ]\n  ): HonoBase<\n    E,\n    AddSchemaIfHasResponse<\n      | MergeTypedResponse<R>\n      | MergeMiddlewareResponse<M1>\n      | MergeMiddlewareResponse<M2>\n      | MergeMiddlewareResponse<M3>\n      | MergeMiddlewareResponse<M4>\n      | MergeMiddlewareResponse<M5>\n      | MergeMiddlewareResponse<M6>,\n      S,\n      M,\n      P,\n      I7,\n      BasePath\n    >,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.get(handler x 9)\n  <\n    P extends string = CurrentPath,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    I7 extends Input = I & I2 & I3 & I4 & I5 & I6,\n    I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7,\n    I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n    E10 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>,\n    // Middleware\n    M1 extends H<E2, P, I> = H<E2, P, I>,\n    M2 extends H<E3, P, I2> = H<E3, P, I2>,\n    M3 extends H<E4, P, I3> = H<E4, P, I3>,\n    M4 extends H<E5, P, I4> = H<E5, P, I4>,\n    M5 extends H<E6, P, I5> = H<E6, P, I5>,\n    M6 extends H<E7, P, I6> = H<E7, P, I6>,\n    M7 extends H<E8, P, I7> = H<E8, P, I7>,\n    M8 extends H<E9, P, I8> = H<E9, P, I8>,\n  >(\n    ...handlers: [\n      H<E2, P, I> & M1,\n      H<E3, P, I2> & M2,\n      H<E4, P, I3> & M3,\n      H<E5, P, I4> & M4,\n      H<E6, P, I5> & M5,\n      H<E7, P, I6> & M6,\n      H<E8, P, I7> & M7,\n      H<E9, P, I8> & M8,\n      H<E10, P, I9, R>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]>,\n    S &\n      ToSchema<\n        M,\n        P,\n        I9,\n        | MergeTypedResponse<R>\n        | MergeMiddlewareResponse<M1>\n        | MergeMiddlewareResponse<M2>\n        | MergeMiddlewareResponse<M3>\n        | MergeMiddlewareResponse<M4>\n        | MergeMiddlewareResponse<M5>\n        | MergeMiddlewareResponse<M6>\n        | MergeMiddlewareResponse<M7>\n        | MergeMiddlewareResponse<M8>\n      >,\n    BasePath,\n    CurrentPath\n  >\n\n  // app.get(path, handler x8)\n  <\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    I7 extends Input = I & I2 & I3 & I4 & I5 & I6,\n    I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n    // Middleware\n    M1 extends H<E2, MergedPath, I> = H<E2, MergedPath, I>,\n    M2 extends H<E3, MergedPath, I2> = H<E3, MergedPath, I2>,\n    M3 extends H<E4, MergedPath, I3> = H<E4, MergedPath, I3>,\n    M4 extends H<E5, MergedPath, I4> = H<E5, MergedPath, I4>,\n    M5 extends H<E6, MergedPath, I5> = H<E6, MergedPath, I5>,\n    M6 extends H<E7, MergedPath, I6> = H<E7, MergedPath, I6>,\n    M7 extends H<E8, MergedPath, I7> = H<E8, MergedPath, I7>,\n  >(\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I> & M1,\n      H<E3, MergedPath, I2> & M2,\n      H<E4, MergedPath, I3> & M3,\n      H<E5, MergedPath, I4> & M4,\n      H<E6, MergedPath, I5> & M5,\n      H<E7, MergedPath, I6> & M6,\n      H<E8, MergedPath, I7> & M7,\n      H<E9, MergedPath, I8, R>,\n    ]\n  ): HonoBase<\n    E,\n    AddSchemaIfHasResponse<\n      | MergeTypedResponse<R>\n      | MergeMiddlewareResponse<M1>\n      | MergeMiddlewareResponse<M2>\n      | MergeMiddlewareResponse<M3>\n      | MergeMiddlewareResponse<M4>\n      | MergeMiddlewareResponse<M5>\n      | MergeMiddlewareResponse<M6>\n      | MergeMiddlewareResponse<M7>,\n      S,\n      M,\n      P,\n      I8,\n      BasePath\n    >,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.get(handler x 10)\n  <\n    P extends string = CurrentPath,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    I7 extends Input = I & I2 & I3 & I4 & I5 & I6,\n    I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7,\n    I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8,\n    I10 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8 & I9,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n    E10 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>,\n    E11 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]>,\n    // Middleware\n    M1 extends H<E2, P, I> = H<E2, P, I>,\n    M2 extends H<E3, P, I2> = H<E3, P, I2>,\n    M3 extends H<E4, P, I3> = H<E4, P, I3>,\n    M4 extends H<E5, P, I4> = H<E5, P, I4>,\n    M5 extends H<E6, P, I5> = H<E6, P, I5>,\n    M6 extends H<E7, P, I6> = H<E7, P, I6>,\n    M7 extends H<E8, P, I7> = H<E8, P, I7>,\n    M8 extends H<E9, P, I8> = H<E9, P, I8>,\n    M9 extends H<E10, P, I9> = H<E10, P, I9>,\n  >(\n    ...handlers: [\n      H<E2, P, I> & M1,\n      H<E3, P, I2> & M2,\n      H<E4, P, I3> & M3,\n      H<E5, P, I4> & M4,\n      H<E6, P, I5> & M5,\n      H<E7, P, I6> & M6,\n      H<E8, P, I7> & M7,\n      H<E9, P, I8> & M8,\n      H<E10, P, I9> & M9,\n      H<E11, P, I10, R>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11]>,\n    S &\n      ToSchema<\n        M,\n        P,\n        I10,\n        | MergeTypedResponse<R>\n        | MergeMiddlewareResponse<M1>\n        | MergeMiddlewareResponse<M2>\n        | MergeMiddlewareResponse<M3>\n        | MergeMiddlewareResponse<M4>\n        | MergeMiddlewareResponse<M5>\n        | MergeMiddlewareResponse<M6>\n        | MergeMiddlewareResponse<M7>\n        | MergeMiddlewareResponse<M8>\n        | MergeMiddlewareResponse<M9>\n      >,\n    BasePath,\n    CurrentPath\n  >\n\n  // app.get(path, handler x9)\n  <\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    I7 extends Input = I & I2 & I3 & I4 & I5 & I6,\n    I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7,\n    I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n    E10 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>,\n    // Middleware\n    M1 extends H<E2, MergedPath, I> = H<E2, MergedPath, I>,\n    M2 extends H<E3, MergedPath, I2> = H<E3, MergedPath, I2>,\n    M3 extends H<E4, MergedPath, I3> = H<E4, MergedPath, I3>,\n    M4 extends H<E5, MergedPath, I4> = H<E5, MergedPath, I4>,\n    M5 extends H<E6, MergedPath, I5> = H<E6, MergedPath, I5>,\n    M6 extends H<E7, MergedPath, I6> = H<E7, MergedPath, I6>,\n    M7 extends H<E8, MergedPath, I7> = H<E8, MergedPath, I7>,\n    M8 extends H<E9, MergedPath, I8> = H<E9, MergedPath, I8>,\n  >(\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I> & M1,\n      H<E3, MergedPath, I2> & M2,\n      H<E4, MergedPath, I3> & M3,\n      H<E5, MergedPath, I4> & M4,\n      H<E6, MergedPath, I5> & M5,\n      H<E7, MergedPath, I6> & M6,\n      H<E8, MergedPath, I7> & M7,\n      H<E9, MergedPath, I8> & M8,\n      H<E10, MergedPath, I9, R>,\n    ]\n  ): HonoBase<\n    E,\n    AddSchemaIfHasResponse<\n      | MergeTypedResponse<R>\n      | MergeMiddlewareResponse<M1>\n      | MergeMiddlewareResponse<M2>\n      | MergeMiddlewareResponse<M3>\n      | MergeMiddlewareResponse<M4>\n      | MergeMiddlewareResponse<M5>\n      | MergeMiddlewareResponse<M6>\n      | MergeMiddlewareResponse<M7>\n      | MergeMiddlewareResponse<M8>,\n      S,\n      M,\n      P,\n      I9,\n      BasePath\n    >,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.get(path, handler x10)\n  <\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    I7 extends Input = I & I2 & I3 & I4 & I5 & I6,\n    I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7,\n    I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8,\n    I10 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8 & I9,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n    E10 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>,\n    E11 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]>,\n    // Middleware\n    M1 extends H<E2, MergedPath, I> = H<E2, MergedPath, I>,\n    M2 extends H<E3, MergedPath, I2> = H<E3, MergedPath, I2>,\n    M3 extends H<E4, MergedPath, I3> = H<E4, MergedPath, I3>,\n    M4 extends H<E5, MergedPath, I4> = H<E5, MergedPath, I4>,\n    M5 extends H<E6, MergedPath, I5> = H<E6, MergedPath, I5>,\n    M6 extends H<E7, MergedPath, I6> = H<E7, MergedPath, I6>,\n    M7 extends H<E8, MergedPath, I7> = H<E8, MergedPath, I7>,\n    M8 extends H<E9, MergedPath, I8> = H<E9, MergedPath, I8>,\n    M9 extends H<E10, MergedPath, I9> = H<E10, MergedPath, I9>,\n  >(\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I> & M1,\n      H<E3, MergedPath, I2> & M2,\n      H<E4, MergedPath, I3> & M3,\n      H<E5, MergedPath, I4> & M4,\n      H<E6, MergedPath, I5> & M5,\n      H<E7, MergedPath, I6> & M6,\n      H<E8, MergedPath, I7> & M7,\n      H<E9, MergedPath, I8> & M8,\n      H<E10, MergedPath, I9> & M9,\n      H<E11, MergedPath, I10, R>,\n    ]\n  ): HonoBase<\n    E,\n    AddSchemaIfHasResponse<\n      | MergeTypedResponse<R>\n      | MergeMiddlewareResponse<M1>\n      | MergeMiddlewareResponse<M2>\n      | MergeMiddlewareResponse<M3>\n      | MergeMiddlewareResponse<M4>\n      | MergeMiddlewareResponse<M5>\n      | MergeMiddlewareResponse<M6>\n      | MergeMiddlewareResponse<M7>\n      | MergeMiddlewareResponse<M8>\n      | MergeMiddlewareResponse<M9>,\n      S,\n      M,\n      P,\n      I10,\n      BasePath\n    >,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.get(...handlers[])\n  <\n    P extends string = CurrentPath,\n    I extends Input = BlankInput,\n    R extends HandlerResponse<any> = any,\n  >(\n    ...handlers: H<E, P, I, R>[]\n  ): HonoBase<E, S & ToSchema<M, P, I, MergeTypedResponse<R>>, BasePath, CurrentPath>\n\n  // app.get(path, ...handlers[])\n  <P extends string, I extends Input = BlankInput, R extends HandlerResponse<any> = any>(\n    path: P,\n    ...handlers: [H<E, MergePath<BasePath, P>, I, R>, ...H<E, MergePath<BasePath, P>, I, R>[]]\n  ): HonoBase<\n    E,\n    S & ToSchema<M, MergePath<BasePath, P>, I, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.get(path)\n  <P extends string, R extends HandlerResponse<any> = any, I extends Input = BlankInput>(\n    path: P\n  ): HonoBase<\n    E,\n    S & ToSchema<M, MergePath<BasePath, P>, I, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n}\n\n////////////////////////////////////////\n//////                            //////\n////// MiddlewareHandlerInterface //////\n//////                            //////\n////////////////////////////////////////\n\nexport interface MiddlewareHandlerInterface<\n  E extends Env = Env,\n  S extends Schema = BlankSchema,\n  BasePath extends string = '/',\n> {\n  //// app.use(...handlers[])\n  <E2 extends Env = E>(\n    ...handlers: MiddlewareHandler<E2, MergePath<BasePath, '*'>>[]\n  ): HonoBase<IntersectNonAnyTypes<[E, E2]>, S, BasePath, MergePath<BasePath, '*'>>\n\n  // app.use(handler)\n  <E2 extends Env = E>(\n    handler: MiddlewareHandler<E2, MergePath<BasePath, '*'>>\n  ): HonoBase<IntersectNonAnyTypes<[E, E2]>, S, BasePath, MergePath<BasePath, '*'>>\n\n  // app.use(handler x2)\n  <\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    P extends string = MergePath<BasePath, '*'>,\n  >(\n    ...handlers: [MiddlewareHandler<E2, P>, MiddlewareHandler<E3, P>]\n  ): HonoBase<IntersectNonAnyTypes<[E, E2, E3]>, S, BasePath, P>\n\n  // app.use(path, handler)\n  <P extends string, MergedPath extends MergePath<BasePath, P>, E2 extends Env = E>(\n    path: P,\n    handler: MiddlewareHandler<E2, MergedPath, any, any>\n  ): HonoBase<IntersectNonAnyTypes<[E, E2]>, S, BasePath, MergedPath>\n\n  // app.use(handler x3)\n  <\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    P extends string = MergePath<BasePath, '*'>,\n  >(\n    ...handlers: [\n      MiddlewareHandler<E2, P, any, any>,\n      MiddlewareHandler<E3, P, any, any>,\n      MiddlewareHandler<E4, P, any, any>,\n    ]\n  ): HonoBase<IntersectNonAnyTypes<[E, E2, E3, E4]>, S, BasePath, P>\n\n  // app.use(path, handler x2)\n  <\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n  >(\n    path: P,\n    ...handlers: [MiddlewareHandler<E2, P>, MiddlewareHandler<E3, P>]\n  ): HonoBase<IntersectNonAnyTypes<[E, E2, E3]>, S, BasePath, MergedPath>\n\n  // app.use(handler x4)\n  <\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    P extends string = MergePath<BasePath, '*'>,\n  >(\n    ...handlers: [\n      MiddlewareHandler<E2, P>,\n      MiddlewareHandler<E3, P>,\n      MiddlewareHandler<E4, P>,\n      MiddlewareHandler<E5, P>,\n    ]\n  ): HonoBase<IntersectNonAnyTypes<[E, E2, E3, E4, E5]>, S, BasePath, P>\n\n  // app.use(path, handler x3)\n  <\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n  >(\n    path: P,\n    ...handlers: [MiddlewareHandler<E2, P>, MiddlewareHandler<E3, P>, MiddlewareHandler<E4, P>]\n  ): HonoBase<IntersectNonAnyTypes<[E, E2, E3, E4]>, S, BasePath, MergedPath>\n\n  // app.use(handler x5)\n  <\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    P extends string = MergePath<BasePath, '*'>,\n  >(\n    ...handlers: [\n      MiddlewareHandler<E2, P>,\n      MiddlewareHandler<E3, P>,\n      MiddlewareHandler<E4, P>,\n      MiddlewareHandler<E5, P>,\n      MiddlewareHandler<E6, P>,\n    ]\n  ): HonoBase<IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>, S, BasePath, P>\n\n  // app.use(path, handler x4)\n  <\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n  >(\n    path: P,\n    ...handlers: [\n      MiddlewareHandler<E2, P>,\n      MiddlewareHandler<E3, P>,\n      MiddlewareHandler<E4, P>,\n      MiddlewareHandler<E5, P>,\n    ]\n  ): HonoBase<IntersectNonAnyTypes<[E, E2, E3, E4, E5]>, S, BasePath, MergedPath>\n\n  // app.use(handler x6)\n  <\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    P extends string = MergePath<BasePath, '*'>,\n  >(\n    ...handlers: [\n      MiddlewareHandler<E2, P>,\n      MiddlewareHandler<E3, P>,\n      MiddlewareHandler<E4, P>,\n      MiddlewareHandler<E5, P>,\n      MiddlewareHandler<E6, P>,\n      MiddlewareHandler<E7, P>,\n    ]\n  ): HonoBase<IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>, S, BasePath, P>\n\n  // app.use(path, handler x5)\n  <\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n  >(\n    path: P,\n    ...handlers: [\n      MiddlewareHandler<E2, P>,\n      MiddlewareHandler<E3, P>,\n      MiddlewareHandler<E4, P>,\n      MiddlewareHandler<E5, P>,\n      MiddlewareHandler<E6, P>,\n    ]\n  ): HonoBase<IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>, S, BasePath, MergedPath>\n\n  // app.use(handler x7)\n  <\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    P extends string = MergePath<BasePath, '*'>,\n  >(\n    ...handlers: [\n      MiddlewareHandler<E2, P>,\n      MiddlewareHandler<E3, P>,\n      MiddlewareHandler<E4, P>,\n      MiddlewareHandler<E5, P>,\n      MiddlewareHandler<E6, P>,\n      MiddlewareHandler<E7, P>,\n      MiddlewareHandler<E8, P>,\n    ]\n  ): HonoBase<IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>, S, BasePath, P>\n\n  // app.use(path, handler x6)\n  <\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n  >(\n    path: P,\n    ...handlers: [\n      MiddlewareHandler<E2, P>,\n      MiddlewareHandler<E3, P>,\n      MiddlewareHandler<E4, P>,\n      MiddlewareHandler<E5, P>,\n      MiddlewareHandler<E6, P>,\n      MiddlewareHandler<E7, P>,\n    ]\n  ): HonoBase<IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>, S, BasePath, MergedPath>\n\n  // app.use(handler x8)\n  <\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n    P extends string = MergePath<BasePath, '*'>,\n  >(\n    ...handlers: [\n      MiddlewareHandler<E2, P>,\n      MiddlewareHandler<E3, P>,\n      MiddlewareHandler<E4, P>,\n      MiddlewareHandler<E5, P>,\n      MiddlewareHandler<E6, P>,\n      MiddlewareHandler<E7, P>,\n      MiddlewareHandler<E8, P>,\n      MiddlewareHandler<E9, P>,\n    ]\n  ): HonoBase<IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>, S, BasePath, P>\n\n  // app.use(path, handler x7)\n  <\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n  >(\n    path: P,\n    ...handlers: [\n      MiddlewareHandler<E2, P>,\n      MiddlewareHandler<E3, P>,\n      MiddlewareHandler<E4, P>,\n      MiddlewareHandler<E5, P>,\n      MiddlewareHandler<E6, P>,\n      MiddlewareHandler<E7, P>,\n      MiddlewareHandler<E8, P>,\n    ]\n  ): HonoBase<IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>, S, BasePath, MergedPath>\n\n  // app.use(handler x9)\n  <\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n    E10 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>,\n    P extends string = MergePath<BasePath, '*'>,\n  >(\n    ...handlers: [\n      MiddlewareHandler<E2, P>,\n      MiddlewareHandler<E3, P>,\n      MiddlewareHandler<E4, P>,\n      MiddlewareHandler<E5, P>,\n      MiddlewareHandler<E6, P>,\n      MiddlewareHandler<E7, P>,\n      MiddlewareHandler<E8, P>,\n      MiddlewareHandler<E9, P>,\n      MiddlewareHandler<E10, P>,\n    ]\n  ): HonoBase<IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]>, S, BasePath, P>\n\n  // app.use(path, handler x8)\n  <\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n  >(\n    path: P,\n    ...handlers: [\n      MiddlewareHandler<E2, P>,\n      MiddlewareHandler<E3, P>,\n      MiddlewareHandler<E4, P>,\n      MiddlewareHandler<E5, P>,\n      MiddlewareHandler<E6, P>,\n      MiddlewareHandler<E7, P>,\n      MiddlewareHandler<E8, P>,\n      MiddlewareHandler<E9, P>,\n    ]\n  ): HonoBase<IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>, S, BasePath, MergedPath>\n\n  // app.use(handler x10)\n  <\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n    E10 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>,\n    E11 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]>,\n    P extends string = MergePath<BasePath, '*'>,\n  >(\n    ...handlers: [\n      MiddlewareHandler<E2, P>,\n      MiddlewareHandler<E3, P>,\n      MiddlewareHandler<E4, P>,\n      MiddlewareHandler<E5, P>,\n      MiddlewareHandler<E6, P>,\n      MiddlewareHandler<E7, P>,\n      MiddlewareHandler<E8, P>,\n      MiddlewareHandler<E9, P>,\n      MiddlewareHandler<E10, P>,\n      MiddlewareHandler<E11, P>,\n    ]\n  ): HonoBase<IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11]>, S, BasePath, P>\n\n  // app.use(path, handler x9)\n  <\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n    E10 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>,\n  >(\n    path: P,\n    ...handlers: [\n      MiddlewareHandler<E2, P>,\n      MiddlewareHandler<E3, P>,\n      MiddlewareHandler<E4, P>,\n      MiddlewareHandler<E5, P>,\n      MiddlewareHandler<E6, P>,\n      MiddlewareHandler<E7, P>,\n      MiddlewareHandler<E8, P>,\n      MiddlewareHandler<E9, P>,\n      MiddlewareHandler<E10, P>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]>,\n    S,\n    BasePath,\n    MergedPath\n  >\n\n  //// app.use(path, ...handlers[])\n  <P extends string, E2 extends Env = E>(\n    path: P,\n    ...handlers: MiddlewareHandler<E2, MergePath<BasePath, P>>[]\n  ): HonoBase<E, S, BasePath, MergePath<BasePath, P>>\n}\n\n////////////////////////////////////////\n//////                            //////\n//////     OnHandlerInterface     //////\n//////                            //////\n////////////////////////////////////////\n\nexport interface OnHandlerInterface<\n  E extends Env = Env,\n  S extends Schema = BlankSchema,\n  BasePath extends string = '/',\n> {\n  // app.on(method, path, handler)\n  <\n    M extends string,\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    E2 extends Env = E,\n  >(\n    method: M,\n    path: P,\n    handler: H<E2, MergedPath, I, R>\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2]>,\n    S & ToSchema<M, MergePath<BasePath, P>, I, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method, path, handler x2)\n  <\n    M extends string,\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n  >(\n    method: M,\n    path: P,\n    ...handlers: [H<E2, MergedPath, I>, H<E3, MergedPath, I2, R>]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3]>,\n    S & ToSchema<M, MergePath<BasePath, P>, I2, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method, path, handler x3)\n  <\n    M extends string,\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n  >(\n    method: M,\n    path: P,\n    ...handlers: [H<E2, MergedPath, I>, H<E3, MergedPath, I2>, H<E4, MergedPath, I3, R>]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    S & ToSchema<M, MergePath<BasePath, P>, I3, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method, path, handler x4)\n  <\n    M extends string,\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n  >(\n    method: M,\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I>,\n      H<E3, MergedPath, I2>,\n      H<E4, MergedPath, I3>,\n      H<E5, MergedPath, I4, R>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    S & ToSchema<M, MergePath<BasePath, P>, I4, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method, path, handler x5)\n  <\n    M extends string,\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n  >(\n    method: M,\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I>,\n      H<E3, MergedPath, I2>,\n      H<E4, MergedPath, I3>,\n      H<E5, MergedPath, I4>,\n      H<E6, MergedPath, I5, R>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    S & ToSchema<M, MergePath<BasePath, P>, I5, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method, path, handler x6)\n  <\n    M extends string,\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n  >(\n    method: M,\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I>,\n      H<E3, MergedPath, I2>,\n      H<E4, MergedPath, I3>,\n      H<E5, MergedPath, I4>,\n      H<E6, MergedPath, I5>,\n      H<E7, MergedPath, I6, R>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    S & ToSchema<M, MergePath<BasePath, P>, I6, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method, path, handler x7)\n  <\n    M extends string,\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    I7 extends Input = I & I2 & I3 & I4 & I5 & I6,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n  >(\n    method: M,\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I>,\n      H<E3, MergedPath, I2>,\n      H<E4, MergedPath, I3>,\n      H<E5, MergedPath, I4>,\n      H<E6, MergedPath, I5>,\n      H<E7, MergedPath, I6>,\n      H<E8, MergedPath, I7, R>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n    S & ToSchema<M, MergePath<BasePath, P>, I7, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method, path, handler x8)\n  <\n    M extends string,\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    I7 extends Input = I & I2 & I3 & I4 & I5 & I6,\n    I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n  >(\n    method: M,\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I>,\n      H<E3, MergedPath, I2>,\n      H<E4, MergedPath, I3>,\n      H<E5, MergedPath, I4>,\n      H<E6, MergedPath, I5>,\n      H<E7, MergedPath, I6>,\n      H<E8, MergedPath, I7>,\n      H<E9, MergedPath, I8, R>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>,\n    S & ToSchema<M, MergePath<BasePath, P>, I8, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method, path, handler x9)\n  <\n    M extends string,\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    I7 extends Input = I & I2 & I3 & I4 & I5 & I6,\n    I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7,\n    I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n    E10 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>,\n  >(\n    method: M,\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I>,\n      H<E3, MergedPath, I2>,\n      H<E4, MergedPath, I3>,\n      H<E5, MergedPath, I4>,\n      H<E6, MergedPath, I5>,\n      H<E7, MergedPath, I6>,\n      H<E8, MergedPath, I7>,\n      H<E9, MergedPath, I8>,\n      H<E10, MergedPath, I9, R>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]>,\n    S & ToSchema<M, MergePath<BasePath, P>, I9, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method, path, handler x10)\n  <\n    M extends string,\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    I7 extends Input = I & I2 & I3 & I4 & I5 & I6,\n    I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7,\n    I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8,\n    I10 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8 & I9,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n    E10 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>,\n    E11 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]>,\n  >(\n    method: M,\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I>,\n      H<E3, MergedPath, I2>,\n      H<E4, MergedPath, I3>,\n      H<E5, MergedPath, I4>,\n      H<E6, MergedPath, I5>,\n      H<E7, MergedPath, I6>,\n      H<E8, MergedPath, I7>,\n      H<E9, MergedPath, I8>,\n      H<E10, MergedPath, I9>,\n      H<E11, MergedPath, I10, R>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11]>,\n    S & ToSchema<M, MergePath<BasePath, P>, I10, MergeTypedResponse<HandlerResponse<any>>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method, path, ...handler)\n  <\n    M extends string,\n    P extends string,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n  >(\n    method: M,\n    path: P,\n    ...handlers: [H<E, MergePath<BasePath, P>, I, R>, ...H<E, MergePath<BasePath, P>, I, R>[]]\n  ): HonoBase<\n    E,\n    S & ToSchema<M, MergePath<BasePath, P>, I, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method[], path, handler)\n  <\n    M extends string,\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    E2 extends Env = E,\n  >(\n    methods: M[],\n    path: P,\n    handler: H<E2, MergedPath, I, R>\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2]>,\n    S & ToSchema<M, MergePath<BasePath, P>, I, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method[], path, handler x2)\n  <\n    M extends string,\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n  >(\n    methods: M[],\n    path: P,\n    ...handlers: [H<E2, MergedPath, I>, H<E3, MergedPath, I2, R>]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3]>,\n    S & ToSchema<M, MergePath<BasePath, P>, I2, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method[], path, handler x3)\n  <\n    M extends string,\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n  >(\n    methods: M[],\n    path: P,\n    ...handlers: [H<E2, MergedPath, I>, H<E3, MergedPath, I2>, H<E4, MergedPath, I3, R>]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    S & ToSchema<M, MergePath<BasePath, P>, I3, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method[], path, handler x4)\n  <\n    M extends string,\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n  >(\n    methods: M[],\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I>,\n      H<E3, MergedPath, I2>,\n      H<E4, MergedPath, I3>,\n      H<E5, MergedPath, I4, R>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    S & ToSchema<M, MergePath<BasePath, P>, I4, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method[], path, handler x5)\n  <\n    M extends string,\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n  >(\n    methods: M[],\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I>,\n      H<E3, MergedPath, I2>,\n      H<E4, MergedPath, I3>,\n      H<E5, MergedPath, I4>,\n      H<E6, MergedPath, I5, R>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    S & ToSchema<M, MergePath<BasePath, P>, I5, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method[], path, handler x6)\n  <\n    M extends string,\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n  >(\n    methods: M[],\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I>,\n      H<E3, MergedPath, I2>,\n      H<E4, MergedPath, I3>,\n      H<E5, MergedPath, I4>,\n      H<E6, MergedPath, I5>,\n      H<E7, MergedPath, I6, R>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    S & ToSchema<M, MergePath<BasePath, P>, I6, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method[], path, handler x7)\n  <\n    M extends string,\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    I7 extends Input = I & I2 & I3 & I4 & I5 & I6,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n  >(\n    methods: M[],\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I>,\n      H<E3, MergedPath, I2>,\n      H<E4, MergedPath, I3>,\n      H<E5, MergedPath, I4>,\n      H<E6, MergedPath, I5>,\n      H<E7, MergedPath, I6>,\n      H<E8, MergedPath, I7, R>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n    S & ToSchema<M, MergePath<BasePath, P>, I7, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method[], path, handler x8)\n  <\n    M extends string,\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    I7 extends Input = I & I2 & I3 & I4 & I5 & I6,\n    I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n  >(\n    methods: M[],\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I>,\n      H<E3, MergedPath, I2>,\n      H<E4, MergedPath, I3>,\n      H<E5, MergedPath, I4>,\n      H<E6, MergedPath, I5>,\n      H<E7, MergedPath, I6>,\n      H<E8, MergedPath, I7>,\n      H<E9, MergedPath, I8, R>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>,\n    S & ToSchema<M, MergePath<BasePath, P>, I8, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method[], path, handler x9)\n  <\n    M extends string,\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    I7 extends Input = I & I2 & I3 & I4 & I5 & I6,\n    I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7,\n    I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n    E10 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>,\n  >(\n    methods: M[],\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I>,\n      H<E3, MergedPath, I2>,\n      H<E4, MergedPath, I3>,\n      H<E5, MergedPath, I4>,\n      H<E6, MergedPath, I5>,\n      H<E7, MergedPath, I6>,\n      H<E8, MergedPath, I7>,\n      H<E9, MergedPath, I8>,\n      H<E10, MergedPath, I9, R>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]>,\n    S & ToSchema<M, MergePath<BasePath, P>, I9, MergeTypedResponse<HandlerResponse<any>>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method[], path, handler x10)\n  <\n    M extends string,\n    P extends string,\n    MergedPath extends MergePath<BasePath, P>,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n    I2 extends Input = I,\n    I3 extends Input = I & I2,\n    I4 extends Input = I & I2 & I3,\n    I5 extends Input = I & I2 & I3 & I4,\n    I6 extends Input = I & I2 & I3 & I4 & I5,\n    I7 extends Input = I & I2 & I3 & I4 & I5 & I6,\n    I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7,\n    I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8,\n    I10 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8 & I9,\n    E2 extends Env = E,\n    E3 extends Env = IntersectNonAnyTypes<[E, E2]>,\n    E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>,\n    E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>,\n    E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>,\n    E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>,\n    E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>,\n    E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>,\n    E10 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>,\n    E11 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]>,\n  >(\n    methods: M[],\n    path: P,\n    ...handlers: [\n      H<E2, MergedPath, I>,\n      H<E3, MergedPath, I2>,\n      H<E4, MergedPath, I3>,\n      H<E5, MergedPath, I4>,\n      H<E6, MergedPath, I5>,\n      H<E7, MergedPath, I6>,\n      H<E8, MergedPath, I7>,\n      H<E9, MergedPath, I8>,\n      H<E10, MergedPath, I9>,\n      H<E11, MergedPath, I10, R>,\n    ]\n  ): HonoBase<\n    IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11]>,\n    S & ToSchema<M, MergePath<BasePath, P>, I10, MergeTypedResponse<HandlerResponse<any>>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method[], path, ...handlers[])\n  <\n    M extends string,\n    P extends string,\n    R extends HandlerResponse<any> = any,\n    I extends Input = BlankInput,\n  >(\n    methods: M[],\n    path: P,\n    ...handlers: [H<E, MergePath<BasePath, P>, I, R>, ...H<E, MergePath<BasePath, P>, I, R>[]]\n  ): HonoBase<\n    E,\n    S & ToSchema<M, MergePath<BasePath, P>, I, MergeTypedResponse<R>>,\n    BasePath,\n    MergePath<BasePath, P>\n  >\n\n  // app.on(method | method[], path[], ...handlers[])\n  <\n    M extends string,\n    const Ps extends string[],\n    I extends Input = BlankInput,\n    R extends HandlerResponse<any> = any,\n    E2 extends Env = E,\n  >(\n    methods: M | M[],\n    paths: Ps,\n    ...handlers: H<E2, MergePath<BasePath, Ps[number]>, I, R>[]\n  ): HonoBase<\n    E,\n    S & ToSchema<M, MergePath<BasePath, Ps[number]>, I, MergeTypedResponse<R>>,\n    BasePath,\n    Ps extends [...string[], infer LastPath extends string] ? MergePath<BasePath, LastPath> : never\n  >\n}\n\n////////////////////////////////////////\n//////                            //////\n//////           ToSchema           //////\n//////                            //////\n////////////////////////////////////////\n\ntype ToSchemaOutput<RorO, I extends Input | Input['in']> =\n  RorO extends TypedResponse<infer T, infer U, infer F>\n    ? {\n        output: unknown extends T ? {} : T\n        outputFormat: I extends { outputFormat: string } ? I['outputFormat'] : F\n        status: U\n      }\n    : {\n        output: unknown extends RorO ? {} : RorO\n        outputFormat: unknown extends RorO\n          ? 'json'\n          : I extends { outputFormat: string }\n            ? I['outputFormat']\n            : 'json'\n        status: StatusCode\n      }\n\nexport type ToSchema<\n  M extends string,\n  P extends string,\n  I extends Input | Input['in'],\n  RorO, // Response or Output\n> =\n  IsAny<RorO> extends true\n    ? {\n        [K in P]: {\n          [K2 in M as AddDollar<K2>]: {\n            input: AddParam<ExtractInput<I>, P>\n            output: {}\n            outputFormat: ResponseFormat\n            status: StatusCode\n          }\n        }\n      }\n    : [RorO] extends [never]\n      ? {}\n      : [RorO] extends [Promise<void>]\n        ? {}\n        : {\n            [K in P]: {\n              [K2 in M as AddDollar<K2>]: Simplify<\n                {\n                  input: AddParam<ExtractInput<I>, P>\n                } & ToSchemaOutput<RorO, I>\n              >\n            }\n          }\n\nexport type Schema = {\n  [Path: string]: {\n    [Method: `$${Lowercase<string>}`]: Endpoint\n  }\n}\n\ntype AddSchemaIfHasResponse<\n  Merged,\n  S extends Schema,\n  M extends string,\n  P extends string,\n  I extends Input | Input['in'],\n  BasePath extends string,\n> = [Merged] extends [Promise<void>] ? S : S & ToSchema<M, MergePath<BasePath, P>, I, Merged>\n\nexport type Endpoint = {\n  input: any\n  output: any\n  outputFormat: ResponseFormat\n  status: StatusCode\n}\n\ntype ExtractParams<Path extends string> = string extends Path\n  ? Record<string, string>\n  : Path extends `${infer _Start}:${infer Param}/${infer Rest}`\n    ? { [K in Param | keyof ExtractParams<`/${Rest}`>]: string }\n    : Path extends `${infer _Start}:${infer Param}`\n      ? { [K in Param]: string }\n      : never\n\ntype FlattenIfIntersect<T> = T extends infer O ? { [K in keyof O]: O[K] } : never\n\nexport type MergeSchemaPath<OrigSchema extends Schema, SubPath extends string> = {\n  [P in keyof OrigSchema as MergePath<SubPath, P & string>]: [OrigSchema[P]] extends [\n    Record<string, Endpoint>,\n  ]\n    ? { [M in keyof OrigSchema[P]]: MergeEndpointParamsWithPath<OrigSchema[P][M], SubPath> }\n    : never\n}\n\ntype MergeEndpointParamsWithPath<T extends Endpoint, SubPath extends string> = T extends unknown\n  ? {\n      input: T['input'] extends { param: infer _ }\n        ? ExtractParams<SubPath> extends never\n          ? T['input']\n          : FlattenIfIntersect<\n              T['input'] & {\n                param: {\n                  // Maps extracted keys, stripping braces, to a string-typed record.\n                  [K in keyof ExtractParams<SubPath> as K extends `${infer Prefix}{${infer _}}`\n                    ? Prefix\n                    : K]: string\n                }\n              }\n            >\n        : RemoveBlankRecord<ExtractParams<SubPath>> extends never\n          ? T['input']\n          : T['input'] & {\n              // Maps extracted keys, stripping braces, to a string-typed record.\n              param: {\n                [K in keyof ExtractParams<SubPath> as K extends `${infer Prefix}{${infer _}}`\n                  ? Prefix\n                  : K]: string\n              }\n            }\n      output: T['output']\n      outputFormat: T['outputFormat']\n      status: T['status']\n    }\n  : never\nexport type AddParam<I, P extends string> =\n  ParamKeys<P> extends never\n    ? I\n    : I extends { param: infer _ }\n      ? I\n      : I & { param: UnionToIntersection<ParamKeyToRecord<ParamKeys<P>>> }\n\ntype AddDollar<T extends string> = `$${Lowercase<T>}`\n\nexport type MergePath<A extends string, B extends string> = B extends ''\n  ? MergePath<A, '/'>\n  : A extends ''\n    ? B\n    : A extends '/'\n      ? B\n      : A extends `${infer P}/`\n        ? B extends `/${infer Q}`\n          ? `${P}/${Q}`\n          : `${P}/${B}`\n        : B extends `/${infer Q}`\n          ? Q extends ''\n            ? A\n            : `${A}/${Q}`\n          : `${A}/${B}`\n\n////////////////////////////////////////\n//////                            //////\n//////        TypedResponse       //////\n//////                            //////\n////////////////////////////////////////\n\nexport type KnownResponseFormat = 'json' | 'text' | 'redirect'\nexport type ResponseFormat = KnownResponseFormat | string\n\nexport type TypedResponse<\n  T = unknown,\n  U extends StatusCode = StatusCode,\n  F extends ResponseFormat = T extends string\n    ? 'text'\n    : T extends JSONValue\n      ? 'json'\n      : ResponseFormat,\n> = {\n  _data: T\n  _status: U\n  _format: F\n}\n\ntype MergeTypedResponse<T> =\n  T extends Promise<void>\n    ? T\n    : T extends Promise<infer T2>\n      ? T2 extends TypedResponse\n        ? T2\n        : TypedResponse\n      : T extends TypedResponse\n        ? T\n        : TypedResponse\n\ntype ExtractTypedResponseOnly<T> = T extends TypedResponse ? T : never\n\ntype MergeMiddlewareResponse<T> = T extends (c: any, next: any) => Promise<infer R>\n  ? Exclude<R, void> extends never\n    ? never\n    : Exclude<R, void> extends Response | TypedResponse<any, any, any>\n      ? ExtractTypedResponseOnly<Exclude<R, void>>\n      : never\n  : T extends (c: any, next: any) => infer R\n    ? R extends Response | TypedResponse<any, any, any>\n      ? ExtractTypedResponseOnly<R>\n      : never\n    : never\n\n////////////////////////////////////////\n//////                             /////\n//////      ValidationTargets      /////\n//////                             /////\n////////////////////////////////////////\n\nexport type FormValue = string | Blob\nexport type ParsedFormValue = string | File\n\nexport type ValidationTargets<T extends FormValue = ParsedFormValue, P extends string = string> = {\n  json: any\n  form: Record<string, T | T[]>\n  query: Record<string, string | string[]>\n  param: Record<P, P extends `${infer _}?` ? string | undefined : string>\n  header: Record<RequestHeader | CustomHeader, string>\n  cookie: Record<string, string>\n}\n\n////////////////////////////////////////\n//////                            //////\n//////      Path parameters       //////\n//////                            //////\n////////////////////////////////////////\n\ntype ParamKey<Component> = Component extends `:${infer NameWithPattern}`\n  ? NameWithPattern extends `${infer Name}{${infer Rest}`\n    ? Rest extends `${infer _Pattern}?`\n      ? `${Name}?`\n      : Name\n    : NameWithPattern\n  : never\n\nexport type ParamKeys<Path> = Path extends `${infer Component}/${infer Rest}`\n  ? ParamKey<Component> | ParamKeys<Rest>\n  : ParamKey<Path>\n\nexport type ParamKeyToRecord<T extends string> = T extends `${infer R}?`\n  ? Record<R, string | undefined>\n  : { [K in T]: string }\n\n////////////////////////////////////////\n//////                            //////\n/////       For HonoRequest       //////\n//////                            //////\n////////////////////////////////////////\n\nexport type InputToDataByTarget<\n  T extends Input['out'],\n  Target extends keyof ValidationTargets,\n> = T extends {\n  [K in Target]: infer R\n}\n  ? R\n  : never\n\nexport type RemoveQuestion<T> = T extends `${infer R}?` ? R : T\n\n////////////////////////////////////////\n//////                            //////\n//////         Utilities          //////\n//////                            //////\n////////////////////////////////////////\n\nexport type ExtractSchema<T> = UnionToIntersection<\n  T extends HonoBase<infer _, infer S, any, any> ? S : never\n>\n\nexport type ExtractSchemaForStatusCode<T, Status extends number> = {\n  [Path in keyof ExtractSchema<T>]: {\n    [Method in keyof ExtractSchema<T>[Path]]: Extract<\n      ExtractSchema<T>[Path][Method],\n      { status: Status }\n    >\n  }\n}\n\nexport type ExtractHandlerResponse<T> = T extends (c: any, next: any) => Promise<infer R>\n  ? Exclude<R, void> extends never\n    ? never // Only void in the type → filter out\n    : Exclude<R, void> extends Response | TypedResponse<any, any, any>\n      ? Exclude<R, void> // Return the response type without void\n      : never // Invalid response type → filter out\n  : T extends (c: any, next: any) => infer R\n    ? R extends Response | TypedResponse<any, any, any>\n      ? R\n      : never\n    : never\n\ntype ProcessHead<T> = IfAnyThenEmptyObject<T extends Env ? (Env extends T ? {} : T) : T>\nexport type IntersectNonAnyTypes<T extends any[]> = T extends [infer Head, ...infer Rest]\n  ? ProcessHead<Head> & IntersectNonAnyTypes<Rest>\n  : {}\n\n////////////////////////////////////////\n//////                            //////\n//////         FetchEvent         //////\n//////                            //////\n////////////////////////////////////////\n\nexport abstract class FetchEventLike {\n  abstract readonly request: Request\n  abstract respondWith(promise: Response | Promise<Response>): void\n  abstract passThroughOnException(): void\n  abstract waitUntil(promise: Promise<void>): void\n}\n"
  },
  {
    "path": "src/utils/accept.test.ts",
    "content": "import { parseAccept } from './accept'\n\ndescribe('parseAccept Comprehensive Tests', () => {\n  describe('Basic Functionality', () => {\n    test('parses simple accept header', () => {\n      const header = 'text/html,application/json;q=0.9'\n      expect(parseAccept(header)).toEqual([\n        { type: 'text/html', params: {}, q: 1 },\n        { type: 'application/json', params: { q: '0.9' }, q: 0.9 },\n      ])\n    })\n\n    test('handles missing header', () => {\n      expect(parseAccept('')).toEqual([])\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      expect(parseAccept(undefined as any)).toEqual([])\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      expect(parseAccept(null as any)).toEqual([])\n    })\n\n    test('handles whitespace-only header', () => {\n      expect(parseAccept('   ')).toEqual([])\n      expect(parseAccept(' \\t\\n ')).toEqual([])\n    })\n  })\n\n  describe('Quality Values', () => {\n    test('handles extreme q values', () => {\n      const header = 'a;q=999999,b;q=-99999,c;q=Infinity,d;q=-Infinity,e;q=NaN'\n      const result = parseAccept(header)\n      expect(result.map((x) => x.q)).toEqual([1, 1, 1, 0, 0])\n    })\n\n    test('handles malformed q values', () => {\n      const header = 'a;q=,b;q=invalid,c;q=1.2.3,d;q=true,e;q=\"0.5\"'\n      const result = parseAccept(header)\n      expect(result.every((x) => x.q >= 0 && x.q <= 1)).toBe(true)\n    })\n\n    test('preserves original q string in params', () => {\n      const header = 'type;q=invalid'\n      const result = parseAccept(header)\n      expect(result[0].params.q).toBe('invalid')\n      expect(result[0].q).toBe(1) // Normalized q value\n    })\n  })\n\n  describe('Parameter Handling', () => {\n    test('handles complex parameters', () => {\n      const header = 'type;a=1;b=\"2\";c=\\'3\\';d=\"semi;colon\";e=\"nested\"quoted\"\"'\n      const result = parseAccept(header)\n      expect(result[0].params).toEqual({\n        a: '1',\n        b: '2',\n        c: \"'3'\",\n        d: 'semi;colon',\n      })\n    })\n\n    test('handles malformed parameters', () => {\n      const header = 'type;=value;;key=;=;====;key====value'\n      const result = parseAccept(header)\n      expect(result[0].type).toBe('type')\n      expect(Object.keys(result[0].params).length).toBe(0)\n    })\n\n    test('handles duplicate parameters', () => {\n      const header = 'type;key=1;key=2;KEY=3'\n      const result = parseAccept(header)\n      expect(result[0].params.key).toBe('2')\n      expect(result[0].params.KEY).toBe('3')\n    })\n  })\n\n  describe('Media Type Edge Cases', () => {\n    test('handles malformed media types', () => {\n      const headers = [\n        '*/html',\n        'text/*mal/formed',\n        '/partial',\n        'missing/',\n        'inv@lid/type',\n        'text/(html)',\n        'text/html?invalid',\n      ]\n      headers.forEach((header) => {\n        const result = parseAccept(header)\n        expect(result[0].type).toBe(header)\n      })\n    })\n\n    test('handles extremely long types', () => {\n      const longType = 'a'.repeat(10000) + '/' + 'b'.repeat(10000)\n      const result = parseAccept(longType)\n      expect(result[0].type).toBe(longType)\n    })\n  })\n\n  describe('Delimiter Edge Cases', () => {\n    test('handles multiple consecutive delimiters', () => {\n      const header = 'a,,,,b;q=0.9,,,,c;q=0.8,,,,'\n      const result = parseAccept(header)\n      expect(result.map((x) => x.type)).toEqual(['a', 'b', 'c'])\n    })\n\n    test('handles unusual whitespace', () => {\n      const header = '\\n\\t a \\t\\n ; \\n\\t q=0.9 \\t\\n , \\n\\t b \\t\\n'\n      const result = parseAccept(header)\n      expect(result.map((x) => x.type)).toEqual(['b', 'a'])\n    })\n\n    test('handles comma inside quoted parameter value', () => {\n      const header = 'text/plain;meta=\"a,b\";q=0.8,application/json;q=0.7'\n      const result = parseAccept(header)\n      expect(result).toEqual([\n        {\n          type: 'text/plain',\n          params: {\n            meta: 'a,b',\n            q: '0.8',\n          },\n          q: 0.8,\n        },\n        {\n          type: 'application/json',\n          params: {\n            q: '0.7',\n          },\n          q: 0.7,\n        },\n      ])\n    })\n\n    test('handles escaped quote and semicolon inside quoted parameter', () => {\n      const header = 'text/plain;meta=\"a\\\\\\\";b\";q=0.5'\n      const result = parseAccept(header)\n      expect(result).toEqual([\n        {\n          type: 'text/plain',\n          params: {\n            meta: 'a\";b',\n            q: '0.5',\n          },\n          q: 0.5,\n        },\n      ])\n    })\n\n    test('handles escaped character inside quoted parameter', () => {\n      const header = 'text/plain;meta=\"a\\\\\\\\z;c\";q=0.3'\n      const result = parseAccept(header)\n      expect(result).toEqual([\n        {\n          type: 'text/plain',\n          params: {\n            meta: 'a\\\\z;c',\n            q: '0.3',\n          },\n          q: 0.3,\n        },\n      ])\n    })\n\n    test('skips invalid param without swallowing next media type', () => {\n      const header = 'a;foo, b;q=0.5'\n      const result = parseAccept(header)\n      expect(result).toEqual([\n        { type: 'a', params: {}, q: 1 },\n        { type: 'b', params: { q: '0.5' }, q: 0.5 },\n      ])\n    })\n\n    test('skips malformed quoted param tail without creating bogus media type', () => {\n      const header = 'a;foo=\"x\"bar,b'\n      const result = parseAccept(header)\n      expect(result).toEqual([\n        { type: 'a', params: {}, q: 1 },\n        { type: 'b', params: {}, q: 1 },\n      ])\n    })\n\n    test('parses params after quoted value with trailing whitespace', () => {\n      const header = 'a;foo=\"x\" ;q=0.5,b;q=0.4'\n      const result = parseAccept(header)\n      expect(result).toEqual([\n        { type: 'a', params: { foo: 'x', q: '0.5' }, q: 0.5 },\n        { type: 'b', params: { q: '0.4' }, q: 0.4 },\n      ])\n    })\n\n    test('handles quoted param followed immediately by comma', () => {\n      const header = 'a;foo=\"x\",b'\n      const result = parseAccept(header)\n      expect(result).toEqual([\n        { type: 'a', params: { foo: 'x' }, q: 1 },\n        { type: 'b', params: {}, q: 1 },\n      ])\n    })\n\n    test('skips empty media type that starts with semicolon', () => {\n      const header = ';q=0.5,b;q=0.4'\n      const result = parseAccept(header)\n      expect(result).toEqual([{ type: 'b', params: { q: '0.4' }, q: 0.4 }])\n    })\n  })\n\n  describe('Security Cases', () => {\n    test('handles potential injection patterns', () => {\n      const headers = [\n        'type;q=0.9--',\n        'type;q=0.9;drop table users',\n        'type;__|;q=0.9',\n        'text/html\"><script>alert(1)</script>',\n        'application/json${process.env}',\n      ]\n      headers.forEach((header) => {\n        expect(() => parseAccept(header)).not.toThrow()\n      })\n    })\n\n    test('handles many semicolons with an unbalanced quote', () => {\n      const header = `text/plain;${'a;'.repeat(8000)}\"`\n      const result = parseAccept(header)\n      expect(result[0].type).toBe('text/plain')\n    })\n\n    test('handles extremely large input', () => {\n      const header = 'a;q=0.9,'.repeat(100000)\n      expect(() => parseAccept(header)).not.toThrow()\n    })\n  })\n\n  describe('Unicode and Special Characters', () => {\n    test('handles unicode in types and parameters', () => {\n      const header = '🌐/😊;param=🔥;q=0.9'\n      const result = parseAccept(header)\n      expect(result[0].type).toBe('🌐/😊')\n      expect(result[0].params.param).toBe('🔥')\n    })\n\n    test('handles special characters', () => {\n      const header = 'type;param=\\x00\\x01\\x02\\x03'\n      const result = parseAccept(header)\n      expect(result[0].params.param).toBe('\\x00\\x01\\x02\\x03')\n    })\n  })\n\n  describe('Sort Stability', () => {\n    test('maintains stable sort for equal q values', () => {\n      const header = 'a;q=0.9,b;q=0.9,c;q=0.9,d;q=0.9'\n      const result = parseAccept(header)\n      expect(result.map((x) => x.type)).toEqual(['a', 'b', 'c', 'd'])\n    })\n\n    test('handles mixed priorities correctly', () => {\n      const header = 'd;q=0.8,b;q=0.9,c;q=0.8,a;q=0.9'\n      const result = parseAccept(header)\n      expect(result.map((x) => x.type)).toEqual(['b', 'a', 'd', 'c'])\n    })\n  })\n})\n"
  },
  {
    "path": "src/utils/accept.ts",
    "content": "export interface Accept {\n  type: string\n  params: Record<string, string>\n  q: number\n}\n\nconst isWhitespace = (char: number): boolean =>\n  char === 32 || char === 9 || char === 10 || char === 13\n\nconst consumeWhitespace = (acceptHeader: string, startIndex: number): number => {\n  while (startIndex < acceptHeader.length) {\n    if (!isWhitespace(acceptHeader.charCodeAt(startIndex))) {\n      break\n    }\n    startIndex++\n  }\n  return startIndex\n}\n\nconst ignoreTrailingWhitespace = (acceptHeader: string, startIndex: number): number => {\n  while (startIndex > 0) {\n    if (!isWhitespace(acceptHeader.charCodeAt(startIndex - 1))) {\n      break\n    }\n    startIndex--\n  }\n  return startIndex\n}\n\nconst skipInvalidParam = (acceptHeader: string, startIndex: number): [number, boolean] => {\n  while (startIndex < acceptHeader.length) {\n    const char = acceptHeader.charCodeAt(startIndex)\n    if (char === 59) {\n      // ';' => next param\n      return [startIndex + 1, true]\n    }\n    if (char === 44) {\n      // ',' => next accept value\n      return [startIndex + 1, false]\n    }\n    startIndex++\n  }\n  return [startIndex, false]\n}\n\nconst skipInvalidAcceptValue = (acceptHeader: string, startIndex: number): number => {\n  let i = startIndex\n  let inQuotes = false\n  while (i < acceptHeader.length) {\n    const char = acceptHeader.charCodeAt(i)\n    if (inQuotes && char === 92) {\n      // '\\' => escape\n      i++\n    } else if (char === 34) {\n      // '\"' => toggle quotes\n      inQuotes = !inQuotes\n    } else if (!inQuotes && char === 44) {\n      // ',' => next accept value\n      return i + 1\n    }\n    i++\n  }\n  return i\n}\n\nconst getNextParam = (\n  acceptHeader: string,\n  startIndex: number\n): [number, string | undefined, string | undefined, boolean] => {\n  startIndex = consumeWhitespace(acceptHeader, startIndex)\n  let i = startIndex\n  let key: string | undefined\n  let value: string | undefined\n  let hasNext = false\n  while (i < acceptHeader.length) {\n    const char = acceptHeader.charCodeAt(i)\n    if (char === 61) {\n      // '=' => end of key\n      key = acceptHeader.slice(startIndex, ignoreTrailingWhitespace(acceptHeader, i))\n      i++\n      break\n    }\n    if (char === 59) {\n      // ';' => invalid empty param, continue parsing params\n      return [i + 1, undefined, undefined, true]\n    }\n    if (char === 44) {\n      // ',' => invalid empty param, move to next accept value\n      return [i + 1, undefined, undefined, false]\n    }\n    i++\n  }\n  if (key === undefined) {\n    return [i, undefined, undefined, false]\n  }\n\n  i = consumeWhitespace(acceptHeader, i)\n  if (acceptHeader.charCodeAt(i) === 61) {\n    // '=' is invalid as a value, so return undefined\n    const skipResult = skipInvalidParam(acceptHeader, i + 1)\n    return [skipResult[0], key, undefined, skipResult[1]]\n  }\n\n  let inQuotes = false\n  const paramStartIndex = i\n  while (i < acceptHeader.length) {\n    const char = acceptHeader.charCodeAt(i)\n\n    if (inQuotes && char === 92) {\n      // '\\' => escape\n      i++\n    } else if (char === 34) {\n      // '\"' => start of quotes\n      if (inQuotes) {\n        let nextIndex = consumeWhitespace(acceptHeader, i + 1)\n        const nextChar = acceptHeader.charCodeAt(nextIndex)\n        if (nextIndex < acceptHeader.length && !(nextChar === 59 || nextChar === 44)) {\n          // not ';' or ',' => invalid trailing chars\n          const skipResult = skipInvalidParam(acceptHeader, nextIndex)\n          return [skipResult[0], key, undefined, skipResult[1]]\n        }\n        value = acceptHeader.slice(paramStartIndex + 1, i)\n        if (value.includes('\\\\')) {\n          value = value.replace(/\\\\(.)/g, '$1')\n        }\n        if (nextChar === 44) {\n          // ',' => end of accept value\n          return [nextIndex + 1, key, value, false]\n        }\n        if (nextChar === 59) {\n          // ';' => has next param\n          hasNext = true\n          nextIndex++\n        }\n        i = nextIndex\n        break\n      }\n      inQuotes = true\n    } else if (!inQuotes && (char === 59 || char === 44)) {\n      // ';' or ',' => end of value\n      value = acceptHeader.slice(paramStartIndex, ignoreTrailingWhitespace(acceptHeader, i))\n      if (char === 59) {\n        // ';' => has next param\n        hasNext = true\n      }\n      i++\n      break\n    }\n    i++\n  }\n  return [\n    i,\n    key,\n    value ?? acceptHeader.slice(paramStartIndex, ignoreTrailingWhitespace(acceptHeader, i)),\n    hasNext,\n  ]\n}\n\nconst getNextAcceptValue = (\n  acceptHeader: string,\n  startIndex: number\n): [number, Accept | undefined] => {\n  const accept: Accept = {\n    type: '',\n    params: {},\n    q: 1,\n  }\n  startIndex = consumeWhitespace(acceptHeader, startIndex)\n  let i = startIndex\n  while (i < acceptHeader.length) {\n    const char = acceptHeader.charCodeAt(i)\n    if (char === 59 || char === 44) {\n      // ';' or ',' => end of type\n      accept.type = acceptHeader.slice(startIndex, ignoreTrailingWhitespace(acceptHeader, i))\n      i++\n      if (char === 44) {\n        // ',' => end of value\n        return [i, accept.type ? accept : undefined]\n      }\n      if (!accept.type) {\n        return [skipInvalidAcceptValue(acceptHeader, i), undefined]\n      }\n      break // parse params\n    }\n    i++\n  }\n  if (!accept.type) {\n    accept.type = acceptHeader.slice(\n      startIndex,\n      ignoreTrailingWhitespace(acceptHeader, acceptHeader.length)\n    )\n    return [acceptHeader.length, accept.type ? accept : undefined]\n  }\n\n  let param: string | undefined\n  let value: string | undefined\n  let hasNext: boolean\n  while (i < acceptHeader.length) {\n    ;[i, param, value, hasNext] = getNextParam(acceptHeader, i)\n    if (param && value) {\n      accept.params[param] = value\n    }\n    if (!hasNext) {\n      break\n    }\n  }\n\n  return [i, accept] as [number, Accept]\n}\n\nexport const parseAccept = (acceptHeader: string): Accept[] => {\n  if (!acceptHeader) {\n    return []\n  }\n\n  const values: Accept[] = []\n  let i = 0\n  let accept: Accept | undefined\n  let requiresSort = false // in many cases, accept values are already sorted by quality (e.g. \"text/html, application/json;q=0.9, */*;q=0.8\")\n  let lastAccept: Accept | undefined\n  while (i < acceptHeader.length) {\n    ;[i, accept] = getNextAcceptValue(acceptHeader, i)\n    if (accept) {\n      accept.q = parseQuality(accept.params.q)\n      values.push(accept)\n      if (lastAccept && lastAccept.q < accept.q) {\n        // find higher quality accept value, so we need to sort\n        requiresSort = true\n      }\n      lastAccept = accept\n    }\n  }\n  if (requiresSort) {\n    values.sort((a, b) => b.q - a.q)\n  }\n\n  return values\n}\n\nconst parseQuality = (qVal?: string): number => {\n  if (qVal === undefined) {\n    return 1\n  }\n  if (qVal === '') {\n    return 1\n  }\n  if (qVal === 'NaN') {\n    return 0\n  }\n\n  const num = Number(qVal)\n  if (num === Infinity) {\n    return 1\n  }\n  if (num === -Infinity) {\n    return 0\n  }\n  if (Number.isNaN(num)) {\n    return 1\n  }\n  if (num < 0 || num > 1) {\n    return 1\n  }\n\n  return num\n}\n"
  },
  {
    "path": "src/utils/basic-auth.test.ts",
    "content": "import { auth } from './basic-auth'\n\ndescribe('auth', () => {\n  it('auth() - not include Authorization Header', () => {\n    const res = auth(new Request('http://localhost/auth'))\n    expect(res).toBeUndefined()\n  })\n\n  it('auth() - invalid Authorization Header format', () => {\n    const res = auth(\n      new Request('http://localhost/auth', {\n        headers: { Authorization: 'InvalidAuthHeader' },\n      })\n    )\n    expect(res).toBeUndefined()\n  })\n\n  it('auth() - invalid Base64 string in Authorization Header', () => {\n    const res = auth(\n      new Request('http://localhost/auth', {\n        headers: { Authorization: 'Basic InvalidBase64' },\n      })\n    )\n    expect(res).toBeUndefined()\n  })\n\n  it('auth() - valid Authorization Header', () => {\n    const validBase64 = btoa('username:password')\n    const res = auth(\n      new Request('http://localhost/auth', {\n        headers: { Authorization: `Basic ${validBase64}` },\n      })\n    )\n    expect(res).toEqual({ username: 'username', password: 'password' })\n  })\n\n  it('auth() - empty username', () => {\n    const validBase64 = btoa(':password')\n    const res = auth(\n      new Request('http://localhost/auth', {\n        headers: { Authorization: `Basic ${validBase64}` },\n      })\n    )\n    expect(res).toEqual({ username: '', password: 'password' })\n  })\n\n  it('auth() - empty password', () => {\n    const validBase64 = btoa('username:')\n    const res = auth(\n      new Request('http://localhost/auth', {\n        headers: { Authorization: `Basic ${validBase64}` },\n      })\n    )\n    expect(res).toEqual({ username: 'username', password: '' })\n  })\n})\n"
  },
  {
    "path": "src/utils/basic-auth.ts",
    "content": "import { decodeBase64 } from './encode'\n\nconst CREDENTIALS_REGEXP = /^ *(?:[Bb][Aa][Ss][Ii][Cc]) +([A-Za-z0-9._~+/-]+=*) *$/\nconst USER_PASS_REGEXP = /^([^:]*):(.*)$/\nconst utf8Decoder = new TextDecoder()\n\nexport type Auth = (req: Request) => { username: string; password: string } | undefined\n\nexport const auth: Auth = (req: Request) => {\n  const match = CREDENTIALS_REGEXP.exec(req.headers.get('Authorization') || '')\n  if (!match) {\n    return undefined\n  }\n\n  let userPass = undefined\n  // If an invalid string is passed to atob(), it throws a `DOMException`.\n  try {\n    userPass = USER_PASS_REGEXP.exec(utf8Decoder.decode(decodeBase64(match[1])))\n  } catch {} // Do nothing\n\n  if (!userPass) {\n    return undefined\n  }\n\n  return { username: userPass[1], password: userPass[2] }\n}\n"
  },
  {
    "path": "src/utils/body.test.ts",
    "content": "import { parseBody } from './body'\nimport type { BodyData } from './body'\n\ntype RecursiveRecord<K extends string, T> = {\n  [key in K]: T | RecursiveRecord<K, T>\n}\n\ndescribe('Parse Body Util', () => {\n  const FORM_URL = 'https://localhost/form'\n  const SEARCH_URL = 'https://localhost/search'\n\n  const createRequest = (\n    url: string,\n    method: 'POST',\n    body: BodyInit,\n    headers?: { [key: string]: string }\n  ) => {\n    return new Request(url, {\n      method,\n      body,\n      headers,\n    })\n  }\n\n  it('should parse `multipart/form-data`', async () => {\n    const data = new FormData()\n    data.append('message', 'hello')\n\n    const req = createRequest(FORM_URL, 'POST', data)\n\n    expect(await parseBody(req)).toEqual({ message: 'hello' })\n  })\n\n  it('should parse `x-www-form-urlencoded`', async () => {\n    const searchParams = new URLSearchParams()\n    searchParams.append('message', 'hello')\n\n    const req = createRequest(SEARCH_URL, 'POST', searchParams, {\n      'Content-Type': 'application/x-www-form-urlencoded',\n    })\n\n    expect(await parseBody(req)).toEqual({ message: 'hello' })\n  })\n\n  it('should not parse multiple values in default', async () => {\n    const data = new FormData()\n    data.append('file', 'bbb')\n    data.append('message', 'hello')\n\n    const req = createRequest(FORM_URL, 'POST', data)\n\n    expect(await parseBody(req)).toEqual({\n      file: 'bbb',\n      message: 'hello',\n    })\n  })\n\n  it('should not update file object properties', async () => {\n    const file = new File(['foo'], 'file1', {\n      type: 'application/octet-stream',\n    })\n    const data = new FormData()\n\n    const req = createRequest(FORM_URL, 'POST', data)\n    vi.spyOn(req, 'formData').mockImplementation(\n      async () =>\n        ({\n          forEach: (cb) => {\n            cb(file, 'file', data)\n            cb('hoo', 'file.hoo', data)\n          },\n        }) as FormData\n    )\n\n    const parsedData = await parseBody(req, { dot: true })\n    expect(parsedData.file).not.instanceOf(File)\n    expect(parsedData).toEqual({\n      file: {\n        hoo: 'hoo',\n      },\n    })\n  })\n\n  it('should override value if `all` option is false', async () => {\n    const data = new FormData()\n    data.append('file', 'aaa')\n    data.append('file', 'bbb')\n    data.append('message', 'hello')\n\n    const req = createRequest(FORM_URL, 'POST', data)\n\n    expect(await parseBody(req)).toEqual({\n      file: 'bbb',\n      message: 'hello',\n    })\n  })\n\n  it('should parse multiple values if `all` option is true', async () => {\n    const data = new FormData()\n    data.append('file', 'aaa')\n    data.append('file', 'bbb')\n    data.append('message', 'hello')\n\n    const req = createRequest(FORM_URL, 'POST', data)\n\n    expect(await parseBody(req, { all: true })).toEqual({\n      file: ['aaa', 'bbb'],\n      message: 'hello',\n    })\n  })\n\n  it('should not parse nested values in default', async () => {\n    const data = new FormData()\n    data.append('obj.key1', 'value1')\n    data.append('obj.key2', 'value2')\n\n    const req = createRequest(FORM_URL, 'POST', data)\n\n    expect(await parseBody(req, { dot: false })).toEqual({\n      'obj.key1': 'value1',\n      'obj.key2': 'value2',\n    })\n  })\n\n  it('should not parse nested values in default for non-nested keys', async () => {\n    const data = new FormData()\n    data.append('key1', 'value1')\n    data.append('key2', 'value2')\n\n    const req = createRequest(FORM_URL, 'POST', data)\n\n    expect(await parseBody(req, { dot: false })).toEqual({\n      key1: 'value1',\n      key2: 'value2',\n    })\n  })\n\n  it('should handle nested values and non-nested values together with dot option true', async () => {\n    const data = new FormData()\n    data.append('obj.key1', 'value1')\n    data.append('obj.key2', 'value2')\n    data.append('key3', 'value3')\n\n    const req = createRequest(FORM_URL, 'POST', data)\n\n    expect(await parseBody(req, { dot: true })).toEqual({\n      obj: { key1: 'value1', key2: 'value2' },\n      key3: 'value3',\n    })\n  })\n\n  it('should handle deeply nested objects with dot option true', async () => {\n    const data = new FormData()\n    data.append('a.b.c.d', 'value')\n\n    const req = createRequest(FORM_URL, 'POST', data)\n\n    expect(await parseBody(req, { dot: true })).toEqual({\n      a: { b: { c: { d: 'value' } } },\n    })\n  })\n\n  it('should skip keys starting with __proto__. to prevent prototype pollution', async () => {\n    const data = new FormData()\n    data.append('__proto__.polluted', 'malicious')\n\n    const req = createRequest(FORM_URL, 'POST', data)\n\n    expect(await parseBody(req, { dot: true })).toEqual({})\n  })\n\n  it('should skip keys containing nested __proto__. to prevent prototype pollution', async () => {\n    const data = new FormData()\n    data.append('a.__proto__.polluted', 'malicious')\n\n    const req = createRequest(FORM_URL, 'POST', data)\n\n    expect(await parseBody(req, { dot: true })).toEqual({})\n  })\n\n  it('should not pollute Object.prototype via __proto__ keys', async () => {\n    const data = new FormData()\n    data.append('__proto__.injected', 'yes')\n    data.append('a.__proto__.injected', 'yes')\n\n    const req = createRequest(FORM_URL, 'POST', data)\n\n    await parseBody(req, { dot: true })\n\n    expect(({} as Record<string, unknown>).injected).toBeUndefined()\n  })\n\n  it('should parse key ending with __proto__ as a normal value', async () => {\n    const data = new FormData()\n    data.append('a.__proto__', 'value')\n\n    const req = createRequest(FORM_URL, 'POST', data)\n\n    const result = await parseBody(req, { dot: true })\n    expect(result).toHaveProperty('a')\n    expect(\n      Object.getOwnPropertyDescriptor(\n        (result as Record<string, Record<string, string>>).a,\n        '__proto__'\n      )?.value\n    ).toBe('value')\n  })\n\n  it('should parse key containing __proto__ as a substring normally', async () => {\n    const data = new FormData()\n    data.append('data__proto__key.value', 'test')\n\n    const req = createRequest(FORM_URL, 'POST', data)\n\n    expect(await parseBody(req, { dot: true })).toEqual({\n      data__proto__key: { value: 'test' },\n    })\n  })\n\n  it('should parse nested values if `dot` option is true', async () => {\n    const data = new FormData()\n    data.append('obj.key1', 'value1')\n    data.append('obj.key2', 'value2')\n\n    const req = createRequest(FORM_URL, 'POST', data)\n\n    expect(await parseBody(req, { dot: true })).toEqual({\n      obj: { key1: 'value1', key2: 'value2' },\n    })\n  })\n\n  it('should parse data if both `all` and `dot` are set', async () => {\n    const data = new FormData()\n    data.append('obj.sub.foo', 'value1')\n    data.append('obj.sub.foo', 'value2')\n    data.append('key', 'value3')\n\n    const req = createRequest(FORM_URL, 'POST', data)\n\n    expect(await parseBody(req, { dot: true, all: true })).toEqual({\n      obj: { sub: { foo: ['value1', 'value2'] } },\n      key: 'value3',\n    })\n  })\n\n  it('should parse nested values if values are `File`', async () => {\n    const file1 = new File(['foo'], 'file1', {\n      type: 'application/octet-stream',\n    })\n    const file2 = new File(['bar'], 'file2', {\n      type: 'application/octet-stream',\n    })\n    const data = new FormData()\n    data.append('file.file1', file1)\n    data.append('file.file2', file2)\n\n    const req = createRequest(FORM_URL, 'POST', data)\n\n    const result = await parseBody(req, { all: true, dot: true })\n    expect(result).toMatchObject({\n      file: {\n        file1: { name: 'file1', type: 'application/octet-stream' },\n        file2: { name: 'file2', type: 'application/octet-stream' },\n      },\n    })\n  })\n\n  it('should parse multiple values if values are `File`', async () => {\n    const file1 = new File(['foo'], 'file1', {\n      type: 'application/octet-stream',\n    })\n    const file2 = new File(['bar'], 'file2', {\n      type: 'application/octet-stream',\n    })\n    const data = new FormData()\n    data.append('file', file1)\n    data.append('file', file2)\n\n    const req = createRequest(FORM_URL, 'POST', data)\n\n    const result = await parseBody(req, { all: true })\n    expect(result).toMatchObject({\n      file: [\n        { name: 'file1', type: 'application/octet-stream' },\n        { name: 'file2', type: 'application/octet-stream' },\n      ],\n    })\n  })\n\n  it('should parse multiple values if key ends with `[]`', async () => {\n    const data = new FormData()\n    data.append('file[]', 'aaa')\n    data.append('file[]', 'bbb')\n    data.append('message', 'hello')\n\n    const req = createRequest(FORM_URL, 'POST', data)\n\n    expect(await parseBody(req, { all: true })).toEqual({\n      'file[]': ['aaa', 'bbb'],\n      message: 'hello',\n    })\n  })\n\n  it('should parse single value as array if the key has `[]`', async () => {\n    const data = new FormData()\n    data.append('foo[]', 'bar')\n    const req = createRequest(FORM_URL, 'POST', data)\n\n    expect(await parseBody(req, { all: true })).toEqual({\n      'foo[]': ['bar'],\n    })\n  })\n\n  it('should return blank object if body is JSON', async () => {\n    const payload = { message: 'hello hono' }\n\n    const req = createRequest('http://localhost/json', 'POST', JSON.stringify(payload), {\n      'Content-Type': 'application/json',\n    })\n\n    expect(await parseBody(req)).toEqual({})\n  })\n\n  describe('Return type', () => {\n    let req: Request\n    beforeEach(() => {\n      req = createRequest(FORM_URL, 'POST', new FormData())\n    })\n\n    it('without options', async () => {\n      expectTypeOf((await parseBody(req))['key']).toEqualTypeOf<string | File>()\n    })\n\n    it('{all: true}', async () => {\n      expectTypeOf((await parseBody(req, { all: true }))['key']).toEqualTypeOf<\n        string | File | (string | File)[]\n      >()\n    })\n\n    it('{all: boolean}', async () => {\n      expectTypeOf((await parseBody(req, { all: !!Math.random() }))['key']).toEqualTypeOf<\n        string | File | (string | File)[]\n      >()\n    })\n\n    it('{dot: true}', async () => {\n      expectTypeOf((await parseBody(req, { dot: true }))['key']).toEqualTypeOf<\n        string | File | RecursiveRecord<string, string | File>\n      >()\n    })\n\n    it('{dot: boolean}', async () => {\n      expectTypeOf((await parseBody(req, { dot: !!Math.random() }))['key']).toEqualTypeOf<\n        string | File | RecursiveRecord<string, string | File>\n      >()\n    })\n\n    it('{all: true, dot: true}', async () => {\n      expectTypeOf((await parseBody(req, { all: true, dot: true }))['key']).toEqualTypeOf<\n        | string\n        | File\n        | (string | File)[]\n        | RecursiveRecord<string, string | File | (string | File)[]>\n      >()\n    })\n\n    it('{all: boolean, dot: boolean}', async () => {\n      expectTypeOf(\n        (await parseBody(req, { all: !!Math.random(), dot: !!Math.random() }))['key']\n      ).toEqualTypeOf<\n        | string\n        | File\n        | (string | File)[]\n        | RecursiveRecord<string, string | File | (string | File)[]>\n      >()\n    })\n\n    it('specify return type explicitly', async () => {\n      expectTypeOf(\n        await parseBody<{ key1: string; key2: string }>(req, {\n          all: !!Math.random(),\n          dot: !!Math.random(),\n        })\n      ).toEqualTypeOf<{ key1: string; key2: string }>()\n    })\n  })\n})\n\ndescribe('BodyData', () => {\n  it('without options', async () => {\n    expectTypeOf(({} as BodyData)['key']).toEqualTypeOf<string | File>()\n  })\n\n  it('{all: true}', async () => {\n    expectTypeOf(({} as BodyData<{ all: true }>)['key']).toEqualTypeOf<\n      string | File | (string | File)[]\n    >()\n  })\n\n  it('{all: boolean}', async () => {\n    expectTypeOf(({} as BodyData<{ all: boolean }>)['key']).toEqualTypeOf<\n      string | File | (string | File)[]\n    >()\n  })\n\n  it('{dot: true}', async () => {\n    expectTypeOf(({} as BodyData<{ dot: true }>)['key']).toEqualTypeOf<\n      string | File | RecursiveRecord<string, string | File>\n    >()\n  })\n\n  it('{dot: boolean}', async () => {\n    expectTypeOf(({} as BodyData<{ dot: boolean }>)['key']).toEqualTypeOf<\n      string | File | RecursiveRecord<string, string | File>\n    >()\n  })\n\n  it('{all: true, dot: true}', async () => {\n    expectTypeOf(({} as BodyData<{ all: true; dot: true }>)['key']).toEqualTypeOf<\n      string | File | (string | File)[] | RecursiveRecord<string, string | File | (string | File)[]>\n    >()\n  })\n\n  it('{all: boolean, dot: boolean}', async () => {\n    expectTypeOf(({} as BodyData<{ all: boolean; dot: boolean }>)['key']).toEqualTypeOf<\n      string | File | (string | File)[] | RecursiveRecord<string, string | File | (string | File)[]>\n    >()\n  })\n})\n"
  },
  {
    "path": "src/utils/body.ts",
    "content": "/**\n * @module\n * Body utility.\n */\n\nimport { HonoRequest } from '../request'\n\ntype BodyDataValueDot = { [x: string]: string | File | BodyDataValueDot }\ntype BodyDataValueDotAll = {\n  [x: string]: string | File | (string | File)[] | BodyDataValueDotAll\n}\ntype SimplifyBodyData<T> = {\n  [K in keyof T]: string | File | (string | File)[] | BodyDataValueDotAll extends T[K]\n    ? string | File | (string | File)[] | BodyDataValueDotAll\n    : string | File | BodyDataValueDot extends T[K]\n      ? string | File | BodyDataValueDot\n      : string | File | (string | File)[] extends T[K]\n        ? string | File | (string | File)[]\n        : string | File\n} & {}\n\ntype BodyDataValueComponent<T> =\n  | string\n  | File\n  | (T extends { all: false }\n      ? never // explicitly set to false\n      : T extends { all: true } | { all: boolean }\n        ? (string | File)[] // use all option\n        : never) // without options\ntype BodyDataValueObject<T> = { [key: string]: BodyDataValueComponent<T> | BodyDataValueObject<T> }\ntype BodyDataValue<T> =\n  | BodyDataValueComponent<T>\n  | (T extends { dot: false }\n      ? never // explicitly set to false\n      : T extends { dot: true } | { dot: boolean }\n        ? BodyDataValueObject<T> // use dot option\n        : never) // without options\nexport type BodyData<T extends Partial<ParseBodyOptions> = {}> = SimplifyBodyData<\n  Record<string, BodyDataValue<T>>\n>\n\nexport type ParseBodyOptions = {\n  /**\n   * Determines whether all fields with multiple values should be parsed as arrays.\n   * @default false\n   * @example\n   * const data = new FormData()\n   * data.append('file', 'aaa')\n   * data.append('file', 'bbb')\n   * data.append('message', 'hello')\n   *\n   * If all is false:\n   * parseBody should return { file: 'bbb', message: 'hello' }\n   *\n   * If all is true:\n   * parseBody should return { file: ['aaa', 'bbb'], message: 'hello' }\n   */\n  all: boolean\n  /**\n   * Determines whether all fields with dot notation should be parsed as nested objects.\n   * @default false\n   * @example\n   * const data = new FormData()\n   * data.append('obj.key1', 'value1')\n   * data.append('obj.key2', 'value2')\n   *\n   * If dot is false:\n   * parseBody should return { 'obj.key1': 'value1', 'obj.key2': 'value2' }\n   *\n   * If dot is true:\n   * parseBody should return { obj: { key1: 'value1', key2: 'value2' } }\n   */\n  dot: boolean\n}\n\n/**\n * Parses the body of a request based on the provided options.\n *\n * @template T - The type of the parsed body data.\n * @param {HonoRequest | Request} request - The request object to parse.\n * @param {Partial<ParseBodyOptions>} [options] - Options for parsing the body.\n * @returns {Promise<T>} The parsed body data.\n */\ninterface ParseBody {\n  <Options extends Partial<ParseBodyOptions>, T extends BodyData<Options>>(\n    request: HonoRequest | Request,\n    options?: Options\n  ): Promise<T>\n  <T extends BodyData>(\n    request: HonoRequest | Request,\n    options?: Partial<ParseBodyOptions>\n  ): Promise<T>\n}\nexport const parseBody: ParseBody = async (\n  request: HonoRequest | Request,\n  options = Object.create(null)\n) => {\n  const { all = false, dot = false } = options\n\n  const headers = request instanceof HonoRequest ? request.raw.headers : request.headers\n  const contentType = headers.get('Content-Type')\n\n  if (\n    contentType?.startsWith('multipart/form-data') ||\n    contentType?.startsWith('application/x-www-form-urlencoded')\n  ) {\n    return parseFormData(request, { all, dot })\n  }\n\n  return {}\n}\n\n/**\n * Parses form data from a request.\n *\n * @template T - The type of the parsed body data.\n * @param {HonoRequest | Request} request - The request object containing form data.\n * @param {ParseBodyOptions} options - Options for parsing the form data.\n * @returns {Promise<T>} The parsed body data.\n */\nasync function parseFormData<T extends BodyData>(\n  request: HonoRequest | Request,\n  options: ParseBodyOptions\n): Promise<T> {\n  const formData = await (request as Request).formData()\n\n  if (formData) {\n    return convertFormDataToBodyData<T>(formData, options)\n  }\n\n  return {} as T\n}\n\n/**\n * Converts form data to body data based on the provided options.\n *\n * @template T - The type of the parsed body data.\n * @param {FormData} formData - The form data to convert.\n * @param {ParseBodyOptions} options - Options for parsing the form data.\n * @returns {T} The converted body data.\n */\nfunction convertFormDataToBodyData<T extends BodyData = BodyData>(\n  formData: FormData,\n  options: ParseBodyOptions\n): T {\n  const form: BodyData = Object.create(null)\n\n  formData.forEach((value, key) => {\n    const shouldParseAllValues = options.all || key.endsWith('[]')\n\n    if (!shouldParseAllValues) {\n      form[key] = value\n    } else {\n      handleParsingAllValues(form, key, value)\n    }\n  })\n\n  if (options.dot) {\n    Object.entries(form).forEach(([key, value]) => {\n      const shouldParseDotValues = key.includes('.')\n\n      if (shouldParseDotValues) {\n        handleParsingNestedValues(form, key, value)\n        delete form[key]\n      }\n    })\n  }\n\n  return form as T\n}\n\n/**\n * Handles parsing all values for a given key, supporting multiple values as arrays.\n *\n * @param {BodyData} form - The form data object.\n * @param {string} key - The key to parse.\n * @param {FormDataEntryValue} value - The value to assign.\n */\nconst handleParsingAllValues = (\n  form: BodyData<{ all: true }>,\n  key: string,\n  value: FormDataEntryValue\n): void => {\n  if (form[key] !== undefined) {\n    if (Array.isArray(form[key])) {\n      ;(form[key] as (string | File)[]).push(value)\n    } else {\n      form[key] = [form[key] as string | File, value]\n    }\n  } else {\n    if (!key.endsWith('[]')) {\n      form[key] = value\n    } else {\n      form[key] = [value]\n    }\n  }\n}\n\n/**\n * Handles parsing nested values using dot notation keys.\n *\n * @param {BodyData} form - The form data object.\n * @param {string} key - The dot notation key.\n * @param {BodyDataValue} value - The value to assign.\n */\nconst handleParsingNestedValues = (\n  form: BodyData,\n  key: string,\n  value: BodyDataValue<Partial<ParseBodyOptions>>\n): void => {\n  if (/(?:^|\\.)__proto__\\./.test(key)) {\n    return\n  }\n\n  let nestedForm = form\n  const keys = key.split('.')\n\n  keys.forEach((key, index) => {\n    if (index === keys.length - 1) {\n      nestedForm[key] = value\n    } else {\n      if (\n        !nestedForm[key] ||\n        typeof nestedForm[key] !== 'object' ||\n        Array.isArray(nestedForm[key]) ||\n        nestedForm[key] instanceof File\n      ) {\n        nestedForm[key] = Object.create(null)\n      }\n      nestedForm = nestedForm[key] as unknown as BodyData\n    }\n  })\n}\n"
  },
  {
    "path": "src/utils/buffer.test.ts",
    "content": "import { createHash } from 'crypto'\nimport { bufferToFormData, bufferToString, equal, timingSafeEqual } from './buffer'\n\ndescribe('equal', () => {\n  it('should return true for identical ArrayBuffers', () => {\n    const buffer1 = new ArrayBuffer(1)\n    const buffer2 = buffer1\n    expect(equal(buffer1, buffer2)).toBe(true)\n  })\n\n  it('should return false for ArrayBuffers of different lengths', () => {\n    const buffer1 = new ArrayBuffer(1)\n    const buffer2 = new ArrayBuffer(2)\n    expect(equal(buffer1, buffer2)).toBe(false)\n  })\n\n  it('should return false for ArrayBuffers with different content', () => {\n    const buffer1 = new Uint8Array([1, 2, 3, 4]).buffer\n    const buffer2 = new Uint8Array([2, 2, 3, 4]).buffer\n    expect(equal(buffer1, buffer2)).toBe(false)\n  })\n\n  it('should return true for ArrayBuffers with identical content', () => {\n    const buffer1 = new Uint8Array([1, 2, 3, 4]).buffer\n    const buffer2 = new Uint8Array([1, 2, 3, 4]).buffer\n    expect(equal(buffer1, buffer2)).toBe(true)\n  })\n})\n\ndescribe('buffer', () => {\n  it('positive', async () => {\n    expect(\n      await timingSafeEqual(\n        '127e6fbfe24a750e72930c220a8e138275656b8e5d8f48a98c3c92df2caba935',\n        '127e6fbfe24a750e72930c220a8e138275656b8e5d8f48a98c3c92df2caba935'\n      )\n    ).toBe(true)\n    expect(await timingSafeEqual('a', 'a')).toBe(true)\n    expect(await timingSafeEqual('', '')).toBe(true)\n    // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n    // @ts-ignore\n    expect(await timingSafeEqual(undefined, undefined)).toBe(true)\n  })\n\n  it('negative', async () => {\n    expect(await timingSafeEqual('a', 'b')).toBe(false)\n    expect(\n      await timingSafeEqual('a', 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')\n    ).toBe(false)\n    expect(\n      await timingSafeEqual('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'a')\n    ).toBe(false)\n    expect(await timingSafeEqual('alpha', 'beta')).toBe(false)\n    // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n    // @ts-ignore\n    expect(await timingSafeEqual(false, undefined)).toBe(false)\n\n    expect(\n      await timingSafeEqual(\n        // well known md5 hash collision\n        // https://marc-stevens.nl/research/md5-1block-collision/\n        'TEXTCOLLBYfGiJUETHQ4hAcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak',\n        'TEXTCOLLBYfGiJUETHQ4hEcKSMd5zYpgqf1YRDhkmxHkhPWptrkoyz28wnI9V0aHeAuaKnak',\n        (input) => createHash('md5').update(input).digest('hex')\n      )\n    ).toBe(false)\n  })\n\n  it.skip('comparing variables except string are deprecated', async () => {\n    expect(await timingSafeEqual(true, true)).toBe(true)\n    expect(await timingSafeEqual(false, false)).toBe(true)\n    expect(\n      await timingSafeEqual(true, true, (d: boolean) =>\n        createHash('sha256').update(d.toString()).digest('hex')\n      )\n    )\n    expect(await timingSafeEqual(false, true)).toBe(false)\n    expect(\n      await timingSafeEqual(\n        () => {},\n        () => {}\n      )\n    ).toBe(false)\n    expect(await timingSafeEqual({}, {})).toBe(false)\n    expect(await timingSafeEqual({ a: 1 }, { a: 1 })).toBe(false)\n    expect(await timingSafeEqual({ a: 1 }, { a: 2 })).toBe(false)\n    expect(await timingSafeEqual([1, 2], [1, 2])).toBe(false)\n    expect(await timingSafeEqual([1, 2], [1, 2, 3])).toBe(false)\n    expect(await timingSafeEqual('a', 'b', () => undefined)).toBe(false)\n  })\n})\n\ndescribe('bufferToString', () => {\n  it('Should return あいうえお', () => {\n    const bytes = [227, 129, 130, 227, 129, 132, 227, 129, 134, 227, 129, 136, 227, 129, 138]\n    const buffer = Uint8Array.from(bytes).buffer\n    expect(bufferToString(buffer)).toBe('あいうえお')\n  })\n  it('should return the passed arguments as is ', () => {\n    const notBuffer = 'あいうえお' as unknown as ArrayBuffer\n    expect(bufferToString(notBuffer)).toBe('あいうえお')\n  })\n})\n\ndescribe('bufferToFormData', () => {\n  it('Should parse multipart/form-data from ArrayBuffer', async () => {\n    const encoder = new TextEncoder()\n    const testData =\n      '--sampleboundary\\r\\nContent-Disposition: form-data; name=\"test\"\\r\\n\\r\\nHello\\r\\n--sampleboundary--'\n    const arrayBuffer = encoder.encode(testData).buffer\n\n    const result = await bufferToFormData(\n      arrayBuffer,\n      'multipart/form-data; boundary=sampleboundary'\n    )\n\n    expect(result.get('test')).toBe('Hello')\n  })\n\n  it('Should parse application/x-www-form-urlencoded from ArrayBuffer', async () => {\n    const encoder = new TextEncoder()\n    const searchParams = new URLSearchParams()\n    searchParams.append('id', '123')\n    searchParams.append('title', 'Good title')\n    const testData = searchParams.toString()\n    const arrayBuffer = encoder.encode(testData).buffer\n\n    const result = await bufferToFormData(arrayBuffer, 'application/x-www-form-urlencoded')\n\n    expect(result.get('id')).toBe('123')\n    expect(result.get('title')).toBe('Good title')\n  })\n})\n"
  },
  {
    "path": "src/utils/buffer.ts",
    "content": "/**\n * @module\n * Buffer utility.\n */\n\nimport { sha256 } from './crypto'\n\nexport const equal = (a: ArrayBuffer, b: ArrayBuffer): boolean => {\n  if (a === b) {\n    return true\n  }\n  if (a.byteLength !== b.byteLength) {\n    return false\n  }\n\n  const va = new DataView(a)\n  const vb = new DataView(b)\n\n  let i = va.byteLength\n  while (i--) {\n    if (va.getUint8(i) !== vb.getUint8(i)) {\n      return false\n    }\n  }\n\n  return true\n}\n\nconst constantTimeEqualString = (a: string, b: string): boolean => {\n  const aLen = a.length\n  const bLen = b.length\n  const maxLen = Math.max(aLen, bLen)\n  let out = aLen ^ bLen\n  for (let i = 0; i < maxLen; i++) {\n    const aChar = i < aLen ? a.charCodeAt(i) : 0\n    const bChar = i < bLen ? b.charCodeAt(i) : 0\n    out |= aChar ^ bChar\n  }\n  return out === 0\n}\n\ntype StringHashFunction = (input: string) => string | null | Promise<string | null>\n\nconst timingSafeEqualString = async (\n  a: string,\n  b: string,\n  hashFunction?: StringHashFunction\n): Promise<boolean> => {\n  if (!hashFunction) {\n    hashFunction = sha256\n  }\n\n  const [sa, sb] = await Promise.all([hashFunction(a), hashFunction(b)])\n\n  if (sa == null || sb == null || typeof sa !== 'string' || typeof sb !== 'string') {\n    return false\n  }\n\n  const hashEqual = constantTimeEqualString(sa, sb)\n  const originalEqual = constantTimeEqualString(a, b)\n\n  return hashEqual && originalEqual\n}\n\ntype TimingSafeEqual = {\n  (a: string, b: string, hashFunction?: StringHashFunction): Promise<boolean>\n  /**\n   * @deprecated object and boolean signatures that take boolean as first and second arguments, and functions with signatures that take non-string arguments have been deprecated\n   */\n  (\n    a: string | object | boolean,\n    b: string | object | boolean,\n    hashFunction?: Function\n  ): Promise<boolean>\n}\nexport const timingSafeEqual: TimingSafeEqual = async (\n  a,\n  b,\n  hashFunction?: Function\n): Promise<boolean> => {\n  if (typeof a === 'string' && typeof b === 'string') {\n    return timingSafeEqualString(a, b, hashFunction as StringHashFunction)\n  }\n\n  if (!hashFunction) {\n    hashFunction = sha256\n  }\n\n  const [sa, sb] = await Promise.all([hashFunction(a), hashFunction(b)])\n\n  if (!sa || !sb || typeof sa !== 'string' || typeof sb !== 'string') {\n    return false\n  }\n\n  return timingSafeEqualString(sa, sb)\n}\n\nexport const bufferToString = (buffer: ArrayBuffer): string => {\n  if (buffer instanceof ArrayBuffer) {\n    const enc = new TextDecoder('utf-8')\n    return enc.decode(buffer)\n  }\n  return buffer\n}\n\nexport const bufferToFormData = (\n  arrayBuffer: ArrayBuffer,\n  contentType: string\n): Promise<FormData> => {\n  const response = new Response(arrayBuffer, {\n    headers: {\n      'Content-Type': contentType,\n    },\n  })\n  return response.formData()\n}\n"
  },
  {
    "path": "src/utils/color.test.ts",
    "content": "import * as esbuild from 'esbuild'\nimport { getColorEnabled, getColorEnabledAsync } from './color'\n\ndescribe('getColorEnabled() / getColorEnabledAsync() - With colors enabled', () => {\n  it('should return true', async () => {\n    expect(getColorEnabled()).toBe(true)\n    expect(await getColorEnabledAsync()).toBe(true)\n  })\n})\n\ndescribe('getColorEnabled() / getColorEnabledAsync() - With NO_COLOR environment variable set', () => {\n  beforeAll(() => {\n    vi.stubEnv('NO_COLOR', '1')\n  })\n\n  afterAll(() => {\n    vi.unstubAllEnvs()\n  })\n\n  it('should return false', async () => {\n    expect(getColorEnabled()).toBe(false)\n    expect(await getColorEnabledAsync()).toBe(false)\n  })\n})\n\ndescribe('esbuild compatibility test', () => {\n  it('should build color.ts with esbuild without errors', async () => {\n    try {\n      const result = await esbuild.build({\n        entryPoints: [__filename.replace('.test.ts', '.ts')],\n        bundle: true,\n        format: 'esm',\n        target: 'es2022',\n        write: false,\n        logLevel: 'silent',\n        external: [],\n      })\n\n      expect(result.errors).toHaveLength(0)\n      expect(result.warnings).toHaveLength(0)\n      expect(result.outputFiles).toHaveLength(1)\n\n      const outputContent = result.outputFiles[0].text\n      expect(outputContent).toBeDefined()\n    } catch (error) {\n      throw new Error(`esbuild failed: ${error}`)\n    }\n  })\n})\n"
  },
  {
    "path": "src/utils/color.ts",
    "content": "/**\n * @module\n * Color utility.\n */\n\n/**\n * Get whether color change on terminal is enabled or disabled.\n * If `NO_COLOR` environment variable is set, this function returns `false`.\n * Unlike getColorEnabledAsync(), this cannot check Cloudflare environment variables.\n * @see {@link https://no-color.org/}\n *\n * @returns {boolean}\n */\nexport function getColorEnabled(): boolean {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  const { process, Deno } = globalThis as any\n\n  const isNoColor =\n    typeof Deno?.noColor === 'boolean'\n      ? (Deno.noColor as boolean)\n      : process !== undefined\n        ? // eslint-disable-next-line no-unsafe-optional-chaining\n          'NO_COLOR' in process?.env\n        : false\n\n  return !isNoColor\n}\n\n/**\n * Get whether color change on terminal is enabled or disabled.\n * If `NO_COLOR` environment variable is set, this function returns `false`.\n * @see {@link https://no-color.org/}\n *\n * @returns {boolean}\n */\nexport async function getColorEnabledAsync(): Promise<boolean> {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  const { navigator } = globalThis as any\n  // Avoid analysis of cloudflare scheme by bundlers\n  const cfWorkers = 'cloudflare:workers'\n\n  const isNoColor =\n    navigator !== undefined && navigator.userAgent === 'Cloudflare-Workers'\n      ? await (async () => {\n          try {\n            // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n            // @ts-ignore\n            return 'NO_COLOR' in ((await import(cfWorkers)).env ?? {}) // {} is for backward compat\n          } catch {\n            return false\n          }\n        })()\n      : !getColorEnabled()\n\n  return !isNoColor\n}\n"
  },
  {
    "path": "src/utils/compress.ts",
    "content": "/**\n * @module\n * Constants for compression.\n */\n\n/**\n * Match for compressible content type.\n */\nexport const COMPRESSIBLE_CONTENT_TYPE_REGEX =\n  /^\\s*(?:text\\/(?!event-stream(?:[;\\s]|$))[^;\\s]+|application\\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\\.dart|vnd\\.ms-fontobject|vnd\\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\\/(?:otf|ttf)|image\\/(?:bmp|vnd\\.adobe\\.photoshop|vnd\\.microsoft\\.icon|vnd\\.ms-dds|x-icon|x-ms-bmp)|message\\/rfc822|model\\/gltf-binary|x-shader\\/x-fragment|x-shader\\/x-vertex|[^;\\s]+?\\+(?:json|text|xml|yaml))(?:[;\\s]|$)/i\n"
  },
  {
    "path": "src/utils/concurrent.test.ts",
    "content": "import { createPool } from './concurrent'\n\ndescribe('concurrent execution', () => {\n  test.each`\n    concurrency | count\n    ${1}        | ${10}\n    ${10}       | ${10}\n    ${100}      | ${10}\n    ${Infinity} | ${2000}\n  `('concurrency $concurrency, count $count', async ({ concurrency, count }) => {\n    const running = new Set()\n\n    const pool = createPool({ concurrency })\n    let resolve: (() => void) | undefined\n    const promise = new Promise<void>((r) => {\n      resolve = r\n    })\n    const fn = async (i: number) => {\n      if (running.size > concurrency) {\n        throw new Error('concurrency exceeded')\n      }\n\n      running.add(i)\n      await promise\n      running.delete(i)\n      return i\n    }\n\n    const jobs = new Array(count).fill(0).map((_, i) => () => fn(i))\n    const expectedResults = new Array(count).fill(0).map((_, i) => i)\n    const resultPromises = jobs.map((job) => pool.run(job))\n\n    expect(running.size).toBe(Math.min(concurrency, count))\n    resolve?.()\n    const results = await Promise.all(resultPromises)\n    expect(running.size).toBe(0)\n    expect(results).toEqual(expectedResults)\n  })\n\n  describe('with interval', () => {\n    test.each`\n      concurrency | interval\n      ${1}        | ${10}\n      ${2}        | ${10}\n    `('concurrency $concurrency, interval $interval', async ({ concurrency, interval }) => {\n      const workingTimeQueue: number[] = []\n      const pool = createPool({ concurrency, interval })\n      const fn = async (i: number) => {\n        const now = Date.now()\n        if (workingTimeQueue.length >= concurrency) {\n          const last = workingTimeQueue.shift()\n          // Not so accurate, -1 ms is acceptable\n          if (last && now - last < interval - 1) {\n            throw new Error('interval violated')\n          }\n        }\n        workingTimeQueue.push(now)\n        return i\n      }\n\n      const jobs = new Array(10).fill(0).map((_, i) => () => fn(i))\n      const expectedResults = new Array(10).fill(0).map((_, i) => i)\n      const resultPromises = jobs.map((job) => pool.run(job))\n\n      const results = await Promise.all(resultPromises)\n      expect(results).toEqual(expectedResults)\n    })\n  })\n})\n"
  },
  {
    "path": "src/utils/concurrent.ts",
    "content": "/**\n * @module\n * Concurrent utility.\n */\n\nconst DEFAULT_CONCURRENCY = 1024\n\nexport interface Pool {\n  run<T>(fn: () => T): Promise<T>\n}\n\nexport const createPool = ({\n  concurrency,\n  interval,\n}: {\n  concurrency?: number\n  interval?: number\n} = {}): Pool => {\n  concurrency ||= DEFAULT_CONCURRENCY\n\n  if (concurrency === Infinity) {\n    // unlimited\n    return {\n      run: async (fn) => fn(),\n    }\n  }\n\n  const pool: Set<{}> = new Set()\n  const run = async <T>(\n    fn: () => T,\n    promise?: Promise<T>,\n    resolve?: (result: T) => void\n  ): Promise<T> => {\n    if (pool.size >= (concurrency as number)) {\n      promise ||= new Promise<T>((r) => (resolve = r))\n      setTimeout(() => run(fn, promise, resolve))\n      return promise\n    }\n    const marker = {}\n    pool.add(marker)\n    const result = await fn()\n    if (interval) {\n      setTimeout(() => pool.delete(marker), interval)\n    } else {\n      pool.delete(marker)\n    }\n    if (resolve) {\n      resolve(result)\n      return promise as Promise<T>\n    } else {\n      return result\n    }\n  }\n  return { run }\n}\n"
  },
  {
    "path": "src/utils/constants.ts",
    "content": "/**\n * Constant used to mark a composed handler.\n */\nexport const COMPOSED_HANDLER = '__COMPOSED_HANDLER'\n"
  },
  {
    "path": "src/utils/cookie.test.ts",
    "content": "import type { Cookie, SignedCookie } from './cookie'\nimport { parse, parseSigned, serialize, serializeSigned } from './cookie'\n\ndescribe('Parse cookie', () => {\n  it('Should parse cookies', () => {\n    const cookieString = 'yummy_cookie=choco; tasty_cookie = strawberry '\n    const cookie: Cookie = parse(cookieString)\n    expect(cookie['yummy_cookie']).toBe('choco')\n    expect(cookie['tasty_cookie']).toBe('strawberry')\n  })\n\n  it('Should parse quoted cookie values', () => {\n    const cookieString =\n      'yummy_cookie=\"choco\"; tasty_cookie = \" strawberry \" ; best_cookie=\"%20sugar%20\";'\n    const cookie: Cookie = parse(cookieString)\n    expect(cookie['yummy_cookie']).toBe('choco')\n    expect(cookie['tasty_cookie']).toBe(' strawberry ')\n    expect(cookie['best_cookie']).toBe(' sugar ')\n  })\n\n  it('Should not throw a URIError when parsing an invalid string', () => {\n    const cookieString = 'yummy_cookie=\"choco%2\";'\n    const cookie: Cookie = parse(cookieString)\n    expect(cookie['yummy_cookie']).toBe('choco%2')\n  })\n\n  it('Should parse empty cookies', () => {\n    const cookie: Cookie = parse('')\n    expect(Object.keys(cookie).length).toBe(0)\n  })\n\n  it('Should parse one cookie specified by name', () => {\n    const cookieString = 'yummy_cookie=choco; tasty_cookie = strawberry '\n    const cookie: Cookie = parse(cookieString, 'yummy_cookie')\n    expect(cookie['yummy_cookie']).toBe('choco')\n    expect(cookie['tasty_cookie']).toBeUndefined()\n  })\n\n  it('Should parse one cookie specified by name even if it is not found', () => {\n    const cookieString = 'yummy_cookie=choco; tasty_cookie = strawberry '\n    const cookie: Cookie = parse(cookieString, 'no_such_cookie')\n    expect(cookie['yummy_cookie']).toBeUndefined()\n    expect(cookie['tasty_cookie']).toBeUndefined()\n    expect(cookie['no_such_cookie']).toBeUndefined()\n  })\n\n  it('Should parse cookies with no value', () => {\n    const cookieString = 'yummy_cookie=; tasty_cookie = ; best_cookie= ; last_cookie=\"\"'\n    const cookie: Cookie = parse(cookieString)\n    expect(cookie['yummy_cookie']).toBe('')\n    expect(cookie['tasty_cookie']).toBe('')\n    expect(cookie['best_cookie']).toBe('')\n    expect(cookie['last_cookie']).toBe('')\n  })\n\n  it('Should parse cookies but not process signed cookies', () => {\n    // also contains another cookie with a '.' in its value to test it is not misinterpreted as signed cookie\n    const cookieString =\n      'yummy_cookie=choco; tasty_cookie = strawberry.I9qAeGQOvWjCEJgRPmrw90JjYpnnX2C9zoOiGSxh1Ig%3D; great_cookie=rating3.5; best_cookie=sugar.valueShapedLikeASignatureButIsNotASignature%3D'\n    const cookie: Cookie = parse(cookieString)\n    expect(cookie['yummy_cookie']).toBe('choco')\n    expect(cookie['tasty_cookie']).toBe('strawberry.I9qAeGQOvWjCEJgRPmrw90JjYpnnX2C9zoOiGSxh1Ig=')\n    expect(cookie['great_cookie']).toBe('rating3.5')\n    expect(cookie['best_cookie']).toBe('sugar.valueShapedLikeASignatureButIsNotASignature=')\n  })\n\n  it('Should ignore invalid cookie names', () => {\n    const cookieString = 'yummy_cookie=choco; tasty cookie=strawberry; best_cookie\\\\=sugar; =ng'\n    const cookie: Cookie = parse(cookieString)\n    expect(cookie['yummy_cookie']).toBe('choco')\n    expect(cookie['tasty cookie']).toBeUndefined()\n    expect(cookie['best_cookie\\\\']).toBeUndefined()\n    expect(cookie['']).toBeUndefined()\n  })\n\n  it('Should ignore invalid cookie values', () => {\n    const cookieString = 'yummy_cookie=choco\\\\nchip; tasty_cookie=strawberry; best_cookie=\"sugar'\n    const cookie: Cookie = parse(cookieString)\n    expect(cookie['yummy_cookie']).toBeUndefined()\n    expect(cookie['tasty_cookie']).toBe('strawberry')\n    expect(cookie['best_cookie\\\\']).toBeUndefined()\n  })\n\n  it('Should parse signed cookies', async () => {\n    const secret = 'secret ingredient'\n    const cookieString =\n      'yummy_cookie=choco.UdFR2rBpS1GsHfGlUiYyMIdqxqwuEgplyQIgTJgpGWY%3D; tasty_cookie = strawberry.I9qAeGQOvWjCEJgRPmrw90JjYpnnX2C9zoOiGSxh1Ig%3D'\n    const cookie: SignedCookie = await parseSigned(cookieString, secret)\n    expect(cookie['yummy_cookie']).toBe('choco')\n    expect(cookie['tasty_cookie']).toBe('strawberry')\n  })\n\n  it('Should parse signed cookies with binary secret', async () => {\n    const secret = new Uint8Array([\n      172, 142, 204, 63, 210, 136, 58, 143, 25, 18, 159, 16, 161, 34, 94,\n    ])\n    const cookieString =\n      'yummy_cookie=choco.8Km4IwZETZdwiOfrT7KgYjKXwiO98XIkms0tOtRa2TA%3D; tasty_cookie = strawberry.TbV33P%2Bi1K0JTxMzNYq7FV9fB4s2VlQcBCBFDxTrUSg%3D'\n    const cookie: SignedCookie = await parseSigned(cookieString, secret)\n    expect(cookie['yummy_cookie']).toBe('choco')\n    expect(cookie['tasty_cookie']).toBe('strawberry')\n  })\n\n  it('Should parse signed cookies containing the signature separator', async () => {\n    const secret = 'secret ingredient'\n    const cookieString = 'yummy_cookie=choco.chip.2%2FJA0c68Y3zm0DvSvHyR6IRysDWmHW0LfoaC0AkyOpw%3D'\n    const cookie: SignedCookie = await parseSigned(cookieString, secret)\n    expect(cookie['yummy_cookie']).toBe('choco.chip')\n  })\n\n  it('Should parse signed cookies and return \"false\" for wrong signature', async () => {\n    const secret = 'secret ingredient'\n    // tasty_cookie has invalid signature\n    const cookieString =\n      'yummy_cookie=choco.UdFR2rBpS1GsHfGlUiYyMIdqxqwuEgplyQIgTJgpGWY%3D; tasty_cookie = strawberry.LAa7RX43t2vCrLNcKmNG65H41OkyV02sraRPuY5RuVg%3D'\n    const cookie: SignedCookie = await parseSigned(cookieString, secret)\n    expect(cookie['yummy_cookie']).toBe('choco')\n    expect(cookie['tasty_cookie']).toBe(false)\n  })\n\n  it('Should parse signed cookies and return \"false\" for corrupt signature', async () => {\n    const secret = 'secret ingredient'\n    // yummy_cookie has corrupt signature (i.e. invalid base64 encoding)\n    // best_cookie has a shape that matches the signature format but isn't actually a signature\n    const cookieString =\n      'yummy_cookie=choco.?dFR2rBpS1GsHfGlUiYyMIdqxqwuEgplyQIgTJgpGWY%3D; tasty_cookie = strawberry.I9qAeGQOvWjCEJgRPmrw90JjYpnnX2C9zoOiGSxh1Ig%3D; best_cookie=sugar.valueShapedLikeASignatureButIsNotASignature%3D'\n    const cookie: SignedCookie = await parseSigned(cookieString, secret)\n    expect(cookie['yummy_cookie']).toBe(false)\n    expect(cookie['tasty_cookie']).toBe('strawberry')\n    expect(cookie['best_cookie']).toBe(false)\n  })\n\n  it('Should parse one signed cookie specified by name', async () => {\n    const secret = 'secret ingredient'\n    const cookieString =\n      'yummy_cookie=choco.UdFR2rBpS1GsHfGlUiYyMIdqxqwuEgplyQIgTJgpGWY%3D; tasty_cookie = strawberry.I9qAeGQOvWjCEJgRPmrw90JjYpnnX2C9zoOiGSxh1Ig%3D'\n    const cookie: SignedCookie = await parseSigned(cookieString, secret, 'tasty_cookie')\n    expect(cookie['yummy_cookie']).toBeUndefined()\n    expect(cookie['tasty_cookie']).toBe('strawberry')\n  })\n\n  it('Should parse one signed cookie specified by name and return \"false\" for wrong signature', async () => {\n    const secret = 'secret ingredient'\n    // tasty_cookie has invalid signature\n    const cookieString =\n      'yummy_cookie=choco.UdFR2rBpS1GsHfGlUiYyMIdqxqwuEgplyQIgTJgpGWY%3D; tasty_cookie = strawberry.LAa7RX43t2vCrLNcKmNG65H41OkyV02sraRPuY5RuVg%3D'\n    const cookie: SignedCookie = await parseSigned(cookieString, secret, 'tasty_cookie')\n    expect(cookie['yummy_cookie']).toBeUndefined()\n    expect(cookie['tasty_cookie']).toBe(false)\n  })\n\n  it('Should parse signed cookies and ignore regular cookies', async () => {\n    const secret = 'secret ingredient'\n    // also contains another cookie with a '.' in its value to test it is not misinterpreted as signed cookie\n    const cookieString =\n      'yummy_cookie=choco; tasty_cookie = strawberry.I9qAeGQOvWjCEJgRPmrw90JjYpnnX2C9zoOiGSxh1Ig%3D; great_cookie=rating3.5'\n    const cookie: SignedCookie = await parseSigned(cookieString, secret)\n    expect(cookie['yummy_cookie']).toBeUndefined()\n    expect(cookie['tasty_cookie']).toBe('strawberry')\n    expect(cookie['great_cookie']).toBeUndefined()\n  })\n})\n\ndescribe('Set cookie', () => {\n  it('Should serialize cookie', () => {\n    const serialized = serialize('delicious_cookie', 'macha')\n    expect(serialized).toBe('delicious_cookie=macha')\n  })\n\n  it('Should serialize cookie with all options', () => {\n    const serialized = serialize('__Secure-great_cookie', 'banana', {\n      path: '/',\n      secure: true,\n      domain: 'example.com',\n      httpOnly: true,\n      maxAge: 1000,\n      expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)),\n      sameSite: 'Strict',\n      priority: 'High',\n      partitioned: true,\n    })\n    expect(serialized).toBe(\n      '__Secure-great_cookie=banana; Max-Age=1000; Domain=example.com; Path=/; Expires=Sun, 24 Dec 2000 10:30:59 GMT; HttpOnly; Secure; SameSite=Strict; Priority=High; Partitioned'\n    )\n  })\n\n  it('Should serialize __Host- cookie with all valid options', () => {\n    const serialized = serialize('__Host-great_cookie', 'banana', {\n      path: '/',\n      secure: true,\n      httpOnly: true,\n      maxAge: 1000,\n      expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)),\n      sameSite: 'Strict',\n      priority: 'High',\n      partitioned: true,\n    })\n    expect(serialized).toBe(\n      '__Host-great_cookie=banana; Max-Age=1000; Path=/; Expires=Sun, 24 Dec 2000 10:30:59 GMT; HttpOnly; Secure; SameSite=Strict; Priority=High; Partitioned'\n    )\n  })\n\n  it('Should serialize a signed cookie', async () => {\n    const secret = 'secret chocolate chips'\n    const serialized = await serializeSigned('delicious_cookie', 'macha', secret)\n    expect(serialized).toBe(\n      'delicious_cookie=macha.diubJPY8O7hI1pLa42QSfkPiyDWQ0I4DnlACH%2FN2HaA%3D'\n    )\n  })\n\n  it('Should serialize signed cookie with all options', async () => {\n    const secret = 'secret chocolate chips'\n    const serialized = await serializeSigned('great_cookie', 'banana', secret, {\n      path: '/',\n      secure: true,\n      domain: 'example.com',\n      httpOnly: true,\n      maxAge: 1000,\n      expires: new Date(Date.UTC(2000, 11, 24, 10, 30, 59, 900)),\n      sameSite: 'Strict',\n      priority: 'High',\n      partitioned: true,\n    })\n    expect(serialized).toBe(\n      'great_cookie=banana.hSo6gB7YT2db0WBiEAakEmh7dtwEL0DSp76G23WvHuQ%3D; Max-Age=1000; Domain=example.com; Path=/; Expires=Sun, 24 Dec 2000 10:30:59 GMT; HttpOnly; Secure; SameSite=Strict; Priority=High; Partitioned'\n    )\n  })\n\n  it('Should serialize cookie with maxAge is 0', () => {\n    const serialized = serialize('great_cookie', 'banana', {\n      maxAge: 0,\n    })\n    expect(serialized).toBe('great_cookie=banana; Max-Age=0')\n  })\n\n  it('Should serialize cookie with maxAge is -1', () => {\n    const serialized = serialize('great_cookie', 'banana', {\n      maxAge: -1,\n    })\n    expect(serialized).toBe('great_cookie=banana')\n  })\n\n  it('Should throw Error cookie with maxAge grater than 400days', () => {\n    expect(() => {\n      serialize('great_cookie', 'banana', {\n        maxAge: 3600 * 24 * 401,\n      })\n    }).toThrowError(\n      'Cookies Max-Age SHOULD NOT be greater than 400 days (34560000 seconds) in duration.'\n    )\n  })\n\n  it('Should throw Error cookie with expires grater than 400days', () => {\n    const now = Date.now()\n    const day401 = new Date(now + 1000 * 3600 * 24 * 401)\n    expect(() => {\n      serialize('great_cookie', 'banana', {\n        expires: day401,\n      })\n    }).toThrowError(\n      'Cookies Expires SHOULD NOT be greater than 400 days (34560000 seconds) in the future.'\n    )\n  })\n\n  it('Should throw Error Partitioned cookie without Secure attributes', () => {\n    expect(() => {\n      serialize('great_cookie', 'banana', {\n        partitioned: true,\n      })\n    }).toThrowError('Partitioned Cookie must have Secure attributes')\n  })\n\n  it('Should throw Error cookie with domain or path containing \";\", \"\\\\r\", or \"\\\\n\"', () => {\n    // domain\n    expect(() => {\n      serialize('great_cookie', 'banana', { domain: 'example.com;evil' })\n    }).toThrowError('domain must not contain \";\", \"\\\\r\", or \"\\\\n\"')\n    expect(() => {\n      serialize('great_cookie', 'banana', { domain: 'example.com\\revil' })\n    }).toThrowError('domain must not contain \";\", \"\\\\r\", or \"\\\\n\"')\n    expect(() => {\n      serialize('great_cookie', 'banana', { domain: 'example.com\\nevil' })\n    }).toThrowError('domain must not contain \";\", \"\\\\r\", or \"\\\\n\"')\n\n    // path\n    expect(() => {\n      serialize('great_cookie', 'banana', { path: '/;evil' })\n    }).toThrowError('path must not contain \";\", \"\\\\r\", or \"\\\\n\"')\n    expect(() => {\n      serialize('great_cookie', 'banana', { path: '/\\revil' })\n    }).toThrowError('path must not contain \";\", \"\\\\r\", or \"\\\\n\"')\n    expect(() => {\n      serialize('great_cookie', 'banana', { path: '/\\nevil' })\n    }).toThrowError('path must not contain \";\", \"\\\\r\", or \"\\\\n\"')\n  })\n\n  it('Should serialize cookie with lowercase priority values', () => {\n    const lowSerialized = serialize('test_cookie', 'value', {\n      priority: 'low',\n    })\n    expect(lowSerialized).toBe('test_cookie=value; Priority=Low')\n\n    const mediumSerialized = serialize('test_cookie', 'value', {\n      priority: 'medium',\n    })\n    expect(mediumSerialized).toBe('test_cookie=value; Priority=Medium')\n\n    const highSerialized = serialize('test_cookie', 'value', {\n      priority: 'high',\n    })\n    expect(highSerialized).toBe('test_cookie=value; Priority=High')\n  })\n})\n"
  },
  {
    "path": "src/utils/cookie.ts",
    "content": "/**\n * @module\n * Cookie utility.\n */\n\nimport { decodeURIComponent_, tryDecode } from './url'\n\nexport type Cookie = Record<string, string>\nexport type SignedCookie = Record<string, string | false>\n\ntype PartitionedCookieConstraint =\n  | { partitioned: true; secure: true }\n  | { partitioned?: boolean; secure?: boolean } // reset to default\ntype SecureCookieConstraint = { secure: true }\ntype HostCookieConstraint = { secure: true; path: '/'; domain?: undefined }\n\nexport type CookieOptions = {\n  domain?: string\n  expires?: Date\n  httpOnly?: boolean\n  maxAge?: number\n  path?: string\n  secure?: boolean\n  sameSite?: 'Strict' | 'Lax' | 'None' | 'strict' | 'lax' | 'none'\n  partitioned?: boolean\n  priority?: 'Low' | 'Medium' | 'High' | 'low' | 'medium' | 'high'\n  prefix?: CookiePrefixOptions\n} & PartitionedCookieConstraint\nexport type CookiePrefixOptions = 'host' | 'secure'\n\nexport type CookieConstraint<Name> = Name extends `__Secure-${string}`\n  ? CookieOptions & SecureCookieConstraint\n  : Name extends `__Host-${string}`\n    ? CookieOptions & HostCookieConstraint\n    : CookieOptions\n\nconst algorithm = { name: 'HMAC', hash: 'SHA-256' }\n\nconst getCryptoKey = async (secret: string | BufferSource): Promise<CryptoKey> => {\n  const secretBuf = typeof secret === 'string' ? new TextEncoder().encode(secret) : secret\n  return await crypto.subtle.importKey('raw', secretBuf, algorithm, false, ['sign', 'verify'])\n}\n\nconst makeSignature = async (value: string, secret: string | BufferSource): Promise<string> => {\n  const key = await getCryptoKey(secret)\n  const signature = await crypto.subtle.sign(algorithm.name, key, new TextEncoder().encode(value))\n  // the returned base64 encoded signature will always be 44 characters long and end with one or two equal signs\n  return btoa(String.fromCharCode(...new Uint8Array(signature)))\n}\n\nconst verifySignature = async (\n  base64Signature: string,\n  value: string,\n  secret: CryptoKey\n): Promise<boolean> => {\n  try {\n    const signatureBinStr = atob(base64Signature)\n    const signature = new Uint8Array(signatureBinStr.length)\n    for (let i = 0, len = signatureBinStr.length; i < len; i++) {\n      signature[i] = signatureBinStr.charCodeAt(i)\n    }\n    return await crypto.subtle.verify(algorithm, secret, signature, new TextEncoder().encode(value))\n  } catch {\n    return false\n  }\n}\n\n// all alphanumeric chars and all of _!#$%&'*.^`|~+-\n// (see: https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1)\nconst validCookieNameRegEx = /^[\\w!#$%&'*.^`|~+-]+$/\n\n// all ASCII chars 32-126 except 34, 59, and 92 (i.e. space to tilde but not double quote, semicolon, or backslash)\n// (see: https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1)\n//\n// note: the spec also prohibits comma and space, but we allow both since they are very common in the real world\n// (see: https://github.com/golang/go/issues/7243)\nconst validCookieValueRegEx = /^[ !#-:<-[\\]-~]*$/\n\nexport const parse = (cookie: string, name?: string): Cookie => {\n  if (name && cookie.indexOf(name) === -1) {\n    // Fast-path: return immediately if the demanded-key is not in the cookie string\n    return {}\n  }\n  const pairs = cookie.trim().split(';')\n  const parsedCookie: Cookie = {}\n  for (let pairStr of pairs) {\n    pairStr = pairStr.trim()\n    const valueStartPos = pairStr.indexOf('=')\n    if (valueStartPos === -1) {\n      continue\n    }\n\n    const cookieName = pairStr.substring(0, valueStartPos).trim()\n    if ((name && name !== cookieName) || !validCookieNameRegEx.test(cookieName)) {\n      continue\n    }\n\n    let cookieValue = pairStr.substring(valueStartPos + 1).trim()\n    if (cookieValue.startsWith('\"') && cookieValue.endsWith('\"')) {\n      cookieValue = cookieValue.slice(1, -1)\n    }\n    if (validCookieValueRegEx.test(cookieValue)) {\n      parsedCookie[cookieName] =\n        cookieValue.indexOf('%') !== -1 ? tryDecode(cookieValue, decodeURIComponent_) : cookieValue\n      if (name) {\n        // Fast-path: return only the demanded-key immediately. Other keys are not needed.\n        break\n      }\n    }\n  }\n  return parsedCookie\n}\n\nexport const parseSigned = async (\n  cookie: string,\n  secret: string | BufferSource,\n  name?: string\n): Promise<SignedCookie> => {\n  const parsedCookie: SignedCookie = {}\n  const secretKey = await getCryptoKey(secret)\n\n  for (const [key, value] of Object.entries(parse(cookie, name))) {\n    const signatureStartPos = value.lastIndexOf('.')\n    if (signatureStartPos < 1) {\n      continue\n    }\n\n    const signedValue = value.substring(0, signatureStartPos)\n    const signature = value.substring(signatureStartPos + 1)\n    if (signature.length !== 44 || !signature.endsWith('=')) {\n      continue\n    }\n\n    const isVerified = await verifySignature(signature, signedValue, secretKey)\n    parsedCookie[key] = isVerified ? signedValue : false\n  }\n\n  return parsedCookie\n}\n\nconst _serialize = (name: string, value: string, opt: CookieOptions = {}): string => {\n  let cookie = `${name}=${value}`\n\n  if (name.startsWith('__Secure-') && !opt.secure) {\n    // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-22#section-4.1.3.1\n    throw new Error('__Secure- Cookie must have Secure attributes')\n  }\n\n  if (name.startsWith('__Host-')) {\n    // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-22#section-4.1.3.2\n    if (!opt.secure) {\n      throw new Error('__Host- Cookie must have Secure attributes')\n    }\n\n    if (opt.path !== '/') {\n      throw new Error('__Host- Cookie must have Path attributes with \"/\"')\n    }\n\n    if (opt.domain) {\n      throw new Error('__Host- Cookie must not have Domain attributes')\n    }\n  }\n\n  for (const key of ['domain', 'path'] as (keyof CookieOptions)[]) {\n    if (opt[key] && /[;\\r\\n]/.test(opt[key] as string)) {\n      throw new Error(`${key} must not contain \";\", \"\\\\r\", or \"\\\\n\"`)\n    }\n  }\n\n  if (opt && typeof opt.maxAge === 'number' && opt.maxAge >= 0) {\n    if (opt.maxAge > 34560000) {\n      // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-22#section-5.6.2\n      throw new Error(\n        'Cookies Max-Age SHOULD NOT be greater than 400 days (34560000 seconds) in duration.'\n      )\n    }\n    cookie += `; Max-Age=${opt.maxAge | 0}`\n  }\n\n  if (opt.domain && opt.prefix !== 'host') {\n    cookie += `; Domain=${opt.domain}`\n  }\n\n  if (opt.path) {\n    cookie += `; Path=${opt.path}`\n  }\n\n  if (opt.expires) {\n    if (opt.expires.getTime() - Date.now() > 34560000_000) {\n      // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-22#section-5.5\n      throw new Error(\n        'Cookies Expires SHOULD NOT be greater than 400 days (34560000 seconds) in the future.'\n      )\n    }\n    cookie += `; Expires=${opt.expires.toUTCString()}`\n  }\n\n  if (opt.httpOnly) {\n    cookie += '; HttpOnly'\n  }\n\n  if (opt.secure) {\n    cookie += '; Secure'\n  }\n\n  if (opt.sameSite) {\n    cookie += `; SameSite=${opt.sameSite.charAt(0).toUpperCase() + opt.sameSite.slice(1)}`\n  }\n\n  if (opt.priority) {\n    cookie += `; Priority=${opt.priority.charAt(0).toUpperCase() + opt.priority.slice(1)}`\n  }\n\n  if (opt.partitioned) {\n    // https://www.ietf.org/archive/id/draft-cutler-httpbis-partitioned-cookies-01.html#section-2.1\n    if (!opt.secure) {\n      throw new Error('Partitioned Cookie must have Secure attributes')\n    }\n    cookie += '; Partitioned'\n  }\n\n  return cookie\n}\n\nexport const serialize = <Name extends string>(\n  name: Name,\n  value: string,\n  opt?: CookieConstraint<Name>\n): string => {\n  value = encodeURIComponent(value)\n  return _serialize(name, value, opt)\n}\n\nexport const serializeSigned = async (\n  name: string,\n  value: string,\n  secret: string | BufferSource,\n  opt: CookieOptions = {}\n): Promise<string> => {\n  const signature = await makeSignature(value, secret)\n  value = `${value}.${signature}`\n  value = encodeURIComponent(value)\n  return _serialize(name, value, opt)\n}\n"
  },
  {
    "path": "src/utils/crypto.test.ts",
    "content": "import { createHash } from 'crypto'\nimport { md5, sha1, sha256 } from './crypto'\n\ndescribe('crypto', () => {\n  it('sha256', async () => {\n    expect(await sha256('hono')).toBe(\n      '8b3dc17add91b7e8f0b5109a389927d66001139cd9b03fa7b95f83126e1b2b23'\n    )\n    expect(await sha256('炎')).toBe(\n      '1fddc5a562ee1fbeb4fc6def7d4be4911fcdae4273b02ae3a507b170ba0ea169'\n    )\n    expect(await sha256('abcdedf')).not.toBe('abcdef')\n  })\n\n  it('sha1', async () => {\n    expect(await sha1('hono')).toBe('28c7e86f5732391917876b45c06c626c04d77f39')\n    expect(await sha1('炎')).toBe('d56e09ae2421b2b8a0b5ee5fdceaed663c8c9472')\n    expect(await sha1('abcdedf')).not.toBe('abcdef')\n  })\n\n  // MD5 is not part of the WebCrypto standard.\n  // Node.js' Web Crypto API does not support it (But Cloudflare Workers supports it).\n  // We should skip this test in a Node.js environment.\n  it.skip('md5', async () => {\n    expect(await md5('hono')).toBe('cf22a160789a91dd5f737cd3b2640cc2')\n    expect(await md5('炎')).toBe('f620d89a5a782c22b4420acb39121be3')\n    expect(await md5('abcdedf')).not.toBe('abcdef')\n  })\n\n  it('Should not be the same values - compare difference objects', async () => {\n    expect(await sha256({ foo: 'bar' })).not.toEqual(\n      await sha256({\n        bar: 'foo',\n      })\n    )\n  })\n\n  it('Should create hash for Buffer', async () => {\n    const hash = createHash('sha256').update(new Uint8Array(1)).digest('hex')\n    expect(await sha256(new Uint8Array(1))).toBe(hash)\n    expect(await sha256(new Uint8Array(1))).not.toEqual(await sha256(new Uint8Array(2)))\n  })\n})\n"
  },
  {
    "path": "src/utils/crypto.ts",
    "content": "/**\n * @module\n * Crypto utility.\n */\n\nimport type { JSONValue } from './types'\n\ntype Algorithm = {\n  name: string\n  alias: string\n}\n\ntype Data = string | boolean | number | JSONValue | ArrayBufferView | ArrayBuffer\n\nexport const sha256 = async (data: Data): Promise<string | null> => {\n  const algorithm: Algorithm = { name: 'SHA-256', alias: 'sha256' }\n  const hash = await createHash(data, algorithm)\n  return hash\n}\n\nexport const sha1 = async (data: Data): Promise<string | null> => {\n  const algorithm: Algorithm = { name: 'SHA-1', alias: 'sha1' }\n  const hash = await createHash(data, algorithm)\n  return hash\n}\n\nexport const md5 = async (data: Data): Promise<string | null> => {\n  const algorithm: Algorithm = { name: 'MD5', alias: 'md5' }\n  const hash = await createHash(data, algorithm)\n  return hash\n}\n\nexport const createHash = async (data: Data, algorithm: Algorithm): Promise<string | null> => {\n  let sourceBuffer: ArrayBufferView | ArrayBuffer\n\n  if (ArrayBuffer.isView(data) || data instanceof ArrayBuffer) {\n    sourceBuffer = data\n  } else {\n    if (typeof data === 'object') {\n      data = JSON.stringify(data)\n    }\n    sourceBuffer = new TextEncoder().encode(String(data))\n  }\n\n  if (crypto && crypto.subtle) {\n    const buffer = await crypto.subtle.digest(\n      {\n        name: algorithm.name,\n      },\n      sourceBuffer as ArrayBuffer\n    )\n    const hash = Array.prototype.map\n      .call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2))\n      .join('')\n    return hash\n  }\n  return null\n}\n"
  },
  {
    "path": "src/utils/encode.test.ts",
    "content": "import { decodeBase64Url, encodeBase64Url } from './encode'\n\nconst toURLBase64 = (base64String: string): string =>\n  base64String.replace(/\\+|\\//g, (m) => ({ '+': '-', '/': '_' })[m] ?? m)\n\nconst str2UInt8Array = (s: string): Uint8Array<ArrayBuffer> => {\n  const buffer = new Uint8Array<ArrayBuffer>(new ArrayBuffer(s.length))\n  for (let i = 0, len = buffer.byteLength; i < len; i++) {\n    buffer[i] = s.charCodeAt(i)\n  }\n  return buffer\n}\n\ndescribe('base64', () => {\n  const utf8Encoder = new TextEncoder()\n  describe.each([\n    // basic\n    [utf8Encoder.encode('Hello, 世界'), 'SGVsbG8sIOS4lueVjA=='],\n    [utf8Encoder.encode('炎'), '54KO'],\n    [utf8Encoder.encode('🔥'), '8J+UpQ=='],\n    [\n      utf8Encoder.encode('http://github.com/honojs/hono'),\n      'aHR0cDovL2dpdGh1Yi5jb20vaG9ub2pzL2hvbm8=',\n    ],\n\n    // RFC 3548 examples\n    [str2UInt8Array('\\x14\\xfb\\x9c\\x03\\xd9\\x7e'), 'FPucA9l+'],\n    [str2UInt8Array('\\x14\\xfb\\x9c\\x03\\xd9'), 'FPucA9k='],\n    [str2UInt8Array('\\x14\\xfb\\x9c\\x03'), 'FPucAw=='],\n\n    // RFC 4648 examples\n    [str2UInt8Array(''), ''],\n    [str2UInt8Array('f'), 'Zg=='],\n    [str2UInt8Array('fo'), 'Zm8='],\n    [str2UInt8Array('foo'), 'Zm9v'],\n    [str2UInt8Array('foob'), 'Zm9vYg=='],\n    [str2UInt8Array('fooba'), 'Zm9vYmE='],\n    [str2UInt8Array('foobar'), 'Zm9vYmFy'],\n\n    // Wikipedia examples\n    [str2UInt8Array('sure.'), 'c3VyZS4='],\n    [str2UInt8Array('sure'), 'c3VyZQ=='],\n    [str2UInt8Array('sur'), 'c3Vy'],\n    [str2UInt8Array('su'), 'c3U='],\n    [str2UInt8Array('leasure.'), 'bGVhc3VyZS4='],\n    [str2UInt8Array('easure.'), 'ZWFzdXJlLg=='],\n    [str2UInt8Array('asure.'), 'YXN1cmUu'],\n    [str2UInt8Array('sure.'), 'c3VyZS4='],\n  ])('%s, %s', (stdDecoded, stdEncoded) => {\n    it('encode', () => {\n      const got = encodeBase64Url(stdDecoded.buffer)\n      const want = toURLBase64(stdEncoded)\n      expect(got).toStrictEqual(want)\n    })\n    it('decode', () => {\n      const got = decodeBase64Url(toURLBase64(stdEncoded))\n      const want = stdDecoded\n      expect(got).toStrictEqual(want)\n    })\n  })\n})\n"
  },
  {
    "path": "src/utils/encode.ts",
    "content": "/**\n * @module\n * Encode utility.\n */\n\nexport const decodeBase64Url = (str: string): Uint8Array<ArrayBuffer> => {\n  return decodeBase64(str.replace(/_|-/g, (m) => ({ _: '/', '-': '+' })[m] ?? m))\n}\n\nexport const encodeBase64Url = (buf: ArrayBufferLike): string =>\n  encodeBase64(buf).replace(/\\/|\\+/g, (m) => ({ '/': '_', '+': '-' })[m] ?? m)\n\n// This approach is written in MDN.\n// btoa does not support utf-8 characters. So we need a little bit hack.\nexport const encodeBase64 = (buf: ArrayBufferLike): string => {\n  let binary = ''\n  const bytes = new Uint8Array(buf)\n  for (let i = 0, len = bytes.length; i < len; i++) {\n    binary += String.fromCharCode(bytes[i])\n  }\n  return btoa(binary)\n}\n\n// atob does not support utf-8 characters. So we need a little bit hack.\nexport const decodeBase64 = (str: string): Uint8Array<ArrayBuffer> => {\n  const binary = atob(str)\n  const bytes = new Uint8Array<ArrayBuffer>(new ArrayBuffer(binary.length))\n  const half = binary.length / 2\n  for (let i = 0, j = binary.length - 1; i <= half; i++, j--) {\n    bytes[i] = binary.charCodeAt(i)\n    bytes[j] = binary.charCodeAt(j)\n  }\n  return bytes\n}\n"
  },
  {
    "path": "src/utils/filepath.test.ts",
    "content": "import { getFilePath, getFilePathWithoutDefaultDocument } from './filepath'\n\ndescribe('getFilePathWithoutDefaultDocument', () => {\n  it('Should return file path correctly', async () => {\n    expect(getFilePathWithoutDefaultDocument({ filename: 'foo.txt' })).toBe('foo.txt')\n    expect(getFilePathWithoutDefaultDocument({ filename: 'foo.txt', root: 'bar' })).toBe(\n      'bar/foo.txt'\n    )\n\n    expect(getFilePathWithoutDefaultDocument({ filename: '../foo' })).toBeUndefined()\n    expect(getFilePathWithoutDefaultDocument({ filename: '/../foo' })).toBeUndefined()\n    expect(getFilePathWithoutDefaultDocument({ filename: './../foo' })).toBeUndefined()\n    expect(getFilePathWithoutDefaultDocument({ filename: 'foo..bar.txt' })).toBe('foo..bar.txt')\n    expect(getFilePathWithoutDefaultDocument({ filename: '/foo..bar.txt' })).toBe('foo..bar.txt')\n    expect(getFilePathWithoutDefaultDocument({ filename: './foo..bar.txt' })).toBe('foo..bar.txt')\n    expect(getFilePathWithoutDefaultDocument({ filename: './..foo/bar.txt' })).toBe('..foo/bar.txt')\n    expect(getFilePathWithoutDefaultDocument({ filename: './foo../bar.txt' })).toBe('foo../bar.txt')\n    expect(getFilePathWithoutDefaultDocument({ filename: './..foo../bar.txt' })).toBe(\n      '..foo../bar.txt'\n    )\n\n    expect(\n      getFilePathWithoutDefaultDocument({ filename: slashToBackslash('/../foo') })\n    ).toBeUndefined()\n    expect(\n      getFilePathWithoutDefaultDocument({ filename: slashToBackslash('./../foo') })\n    ).toBeUndefined()\n    expect(getFilePathWithoutDefaultDocument({ filename: slashToBackslash('foo..bar.txt') })).toBe(\n      'foo..bar.txt'\n    )\n    expect(getFilePathWithoutDefaultDocument({ filename: slashToBackslash('/foo..bar.txt') })).toBe(\n      'foo..bar.txt'\n    )\n    expect(\n      getFilePathWithoutDefaultDocument({ filename: slashToBackslash('./foo..bar.txt') })\n    ).toBe('foo..bar.txt')\n    expect(\n      getFilePathWithoutDefaultDocument({ filename: slashToBackslash('./..foo/bar.txt') })\n    ).toBe('..foo/bar.txt')\n    expect(\n      getFilePathWithoutDefaultDocument({ filename: slashToBackslash('./foo../bar.txt') })\n    ).toBe('foo../bar.txt')\n    expect(\n      getFilePathWithoutDefaultDocument({ filename: slashToBackslash('./..foo../bar.txt') })\n    ).toBe('..foo../bar.txt')\n  })\n})\n\ndescribe('getFilePath', () => {\n  it('Should return file path correctly', async () => {\n    expect(getFilePath({ filename: 'foo' })).toBe('foo/index.html')\n\n    expect(getFilePath({ filename: 'foo', root: 'bar' })).toBe('bar/foo/index.html')\n\n    expect(getFilePath({ filename: 'foo', defaultDocument: 'index.txt' })).toBe('foo/index.txt')\n    expect(getFilePath({ filename: 'foo', root: 'bar', defaultDocument: 'index.txt' })).toBe(\n      'bar/foo/index.txt'\n    )\n\n    expect(getFilePath({ filename: 'filename.suffix_index' })).toBe('filename.suffix_index')\n    expect(getFilePath({ filename: 'filename.suffix-index' })).toBe('filename.suffix-index')\n  })\n})\n\nfunction slashToBackslash(filename: string) {\n  return filename.split('/').join('\\\\')\n}\n"
  },
  {
    "path": "src/utils/filepath.ts",
    "content": "/**\n * @module\n * FilePath utility.\n */\n\ntype FilePathOptions = {\n  filename: string\n  root?: string\n  defaultDocument?: string\n}\n\nexport const getFilePath = (options: FilePathOptions): string | undefined => {\n  let filename = options.filename\n  const defaultDocument = options.defaultDocument || 'index.html'\n\n  if (filename.endsWith('/')) {\n    // /top/ => /top/index.html\n    filename = filename.concat(defaultDocument)\n  } else if (!filename.match(/\\.[a-zA-Z0-9_-]+$/)) {\n    // /top => /top/index.html\n    filename = filename.concat('/' + defaultDocument)\n  }\n\n  const path = getFilePathWithoutDefaultDocument({\n    root: options.root,\n    filename,\n  })\n\n  return path\n}\n\nexport const getFilePathWithoutDefaultDocument = (\n  options: Omit<FilePathOptions, 'defaultDocument'>\n): string | undefined => {\n  let root = options.root || ''\n  let filename = options.filename\n\n  if (/(?:^|[\\/\\\\])\\.\\.(?:$|[\\/\\\\])/.test(filename)) {\n    return\n  }\n\n  // /foo.html => foo.html\n  filename = filename.replace(/^\\.?[\\/\\\\]/, '')\n\n  // foo\\bar.txt => foo/bar.txt\n  filename = filename.replace(/\\\\/, '/')\n\n  // assets/ => assets\n  root = root.replace(/\\/$/, '')\n\n  // ./assets/foo.html => assets/foo.html\n  let path = root ? root + '/' + filename : filename\n  path = path.replace(/^\\.?\\//, '')\n\n  if (root[0] !== '/' && path[0] === '/') {\n    return\n  }\n\n  return path\n}\n"
  },
  {
    "path": "src/utils/handler.ts",
    "content": "/**\n * @module\n * Handler utility.\n */\n\nimport { COMPOSED_HANDLER } from './constants'\n\nexport const isMiddleware = (handler: Function) => handler.length > 1\nexport const findTargetHandler = (handler: Function): Function => {\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  return (handler as any)[COMPOSED_HANDLER]\n    ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      findTargetHandler((handler as any)[COMPOSED_HANDLER])\n    : handler\n}\n"
  },
  {
    "path": "src/utils/headers.ts",
    "content": "/**\n * @module\n * HTTP Headers utility.\n */\n\n// note: https://www.iana.org/assignments/http-fields/http-fields.xhtml\n\nexport type RequestHeader =\n  | 'A-IM'\n  | 'Accept'\n  | 'Accept-Additions'\n  | 'Accept-CH'\n  | 'Accept-Charset'\n  | 'Accept-Datetime'\n  | 'Accept-Encoding'\n  | 'Accept-Features'\n  | 'Accept-Language'\n  | 'Accept-Patch'\n  | 'Accept-Post'\n  | 'Accept-Ranges'\n  | 'Accept-Signature'\n  | 'Access-Control'\n  | 'Access-Control-Allow-Credentials'\n  | 'Access-Control-Allow-Headers'\n  | 'Access-Control-Allow-Methods'\n  | 'Access-Control-Allow-Origin'\n  | 'Access-Control-Expose-Headers'\n  | 'Access-Control-Max-Age'\n  | 'Access-Control-Request-Headers'\n  | 'Access-Control-Request-Method'\n  | 'Age'\n  | 'Allow'\n  | 'ALPN'\n  | 'Alt-Svc'\n  | 'Alt-Used'\n  | 'Alternates'\n  | 'AMP-Cache-Transform'\n  | 'Apply-To-Redirect-Ref'\n  | 'Authentication-Control'\n  | 'Authentication-Info'\n  | 'Authorization'\n  | 'Available-Dictionary'\n  | 'C-Ext'\n  | 'C-Man'\n  | 'C-Opt'\n  | 'C-PEP'\n  | 'C-PEP-Info'\n  | 'Cache-Control'\n  | 'Cache-Status'\n  | 'Cal-Managed-ID'\n  | 'CalDAV-Timezones'\n  | 'Capsule-Protocol'\n  | 'CDN-Cache-Control'\n  | 'CDN-Loop'\n  | 'Cert-Not-After'\n  | 'Cert-Not-Before'\n  | 'Clear-Site-Data'\n  | 'Client-Cert'\n  | 'Client-Cert-Chain'\n  | 'Close'\n  | 'CMCD-Object'\n  | 'CMCD-Request'\n  | 'CMCD-Session'\n  | 'CMCD-Status'\n  | 'CMSD-Dynamic'\n  | 'CMSD-Static'\n  | 'Concealed-Auth-Export'\n  | 'Configuration-Context'\n  | 'Connection'\n  | 'Content-Base'\n  | 'Content-Digest'\n  | 'Content-Disposition'\n  | 'Content-Encoding'\n  | 'Content-ID'\n  | 'Content-Language'\n  | 'Content-Length'\n  | 'Content-Location'\n  | 'Content-MD5'\n  | 'Content-Range'\n  | 'Content-Script-Type'\n  | 'Content-Security-Policy'\n  | 'Content-Security-Policy-Report-Only'\n  | 'Content-Style-Type'\n  | 'Content-Type'\n  | 'Content-Version'\n  | 'Cookie'\n  | 'Cookie2'\n  | 'Cross-Origin-Embedder-Policy'\n  | 'Cross-Origin-Embedder-Policy-Report-Only'\n  | 'Cross-Origin-Opener-Policy'\n  | 'Cross-Origin-Opener-Policy-Report-Only'\n  | 'Cross-Origin-Resource-Policy'\n  | 'CTA-Common-Access-Token'\n  | 'DASL'\n  | 'Date'\n  | 'DAV'\n  | 'Default-Style'\n  | 'Delta-Base'\n  | 'Deprecation'\n  | 'Depth'\n  | 'Derived-From'\n  | 'Destination'\n  | 'Differential-ID'\n  | 'Dictionary-ID'\n  | 'Digest'\n  | 'DPoP'\n  | 'DPoP-Nonce'\n  | 'Early-Data'\n  | 'EDIINT-Features'\n  | 'ETag'\n  | 'Expect'\n  | 'Expect-CT'\n  | 'Expires'\n  | 'Ext'\n  | 'Forwarded'\n  | 'From'\n  | 'GetProfile'\n  | 'Hobareg'\n  | 'Host'\n  | 'HTTP2-Settings'\n  | 'If'\n  | 'If-Match'\n  | 'If-Modified-Since'\n  | 'If-None-Match'\n  | 'If-Range'\n  | 'If-Schedule-Tag-Match'\n  | 'If-Unmodified-Since'\n  | 'IM'\n  | 'Include-Referred-Token-Binding-ID'\n  | 'Isolation'\n  | 'Keep-Alive'\n  | 'Label'\n  | 'Last-Event-ID'\n  | 'Last-Modified'\n  | 'Link'\n  | 'Link-Template'\n  | 'Location'\n  | 'Lock-Token'\n  | 'Man'\n  | 'Max-Forwards'\n  | 'Memento-Datetime'\n  | 'Meter'\n  | 'Method-Check'\n  | 'Method-Check-Expires'\n  | 'MIME-Version'\n  | 'Negotiate'\n  | 'NEL'\n  | 'OData-EntityId'\n  | 'OData-Isolation'\n  | 'OData-MaxVersion'\n  | 'OData-Version'\n  | 'Opt'\n  | 'Optional-WWW-Authenticate'\n  | 'Ordering-Type'\n  | 'Origin'\n  | 'Origin-Agent-Cluster'\n  | 'OSCORE'\n  | 'OSLC-Core-Version'\n  | 'Overwrite'\n  | 'P3P'\n  | 'PEP'\n  | 'PEP-Info'\n  | 'Permissions-Policy'\n  | 'PICS-Label'\n  | 'Ping-From'\n  | 'Ping-To'\n  | 'Position'\n  | 'Pragma'\n  | 'Prefer'\n  | 'Preference-Applied'\n  | 'Priority'\n  | 'ProfileObject'\n  | 'Protocol'\n  | 'Protocol-Info'\n  | 'Protocol-Query'\n  | 'Protocol-Request'\n  | 'Proxy-Authenticate'\n  | 'Proxy-Authentication-Info'\n  | 'Proxy-Authorization'\n  | 'Proxy-Features'\n  | 'Proxy-Instruction'\n  | 'Proxy-Status'\n  | 'Public'\n  | 'Public-Key-Pins'\n  | 'Public-Key-Pins-Report-Only'\n  | 'Range'\n  | 'Redirect-Ref'\n  | 'Referer'\n  | 'Referer-Root'\n  | 'Referrer-Policy'\n  | 'Refresh'\n  | 'Repeatability-Client-ID'\n  | 'Repeatability-First-Sent'\n  | 'Repeatability-Request-ID'\n  | 'Repeatability-Result'\n  | 'Replay-Nonce'\n  | 'Reporting-Endpoints'\n  | 'Repr-Digest'\n  | 'Retry-After'\n  | 'Safe'\n  | 'Schedule-Reply'\n  | 'Schedule-Tag'\n  | 'Sec-GPC'\n  | 'Sec-Purpose'\n  | 'Sec-Token-Binding'\n  | 'Sec-WebSocket-Accept'\n  | 'Sec-WebSocket-Extensions'\n  | 'Sec-WebSocket-Key'\n  | 'Sec-WebSocket-Protocol'\n  | 'Sec-WebSocket-Version'\n  | 'Security-Scheme'\n  | 'Server'\n  | 'Server-Timing'\n  | 'Set-Cookie'\n  | 'Set-Cookie2'\n  | 'SetProfile'\n  | 'Signature'\n  | 'Signature-Input'\n  | 'SLUG'\n  | 'SoapAction'\n  | 'Status-URI'\n  | 'Strict-Transport-Security'\n  | 'Sunset'\n  | 'Surrogate-Capability'\n  | 'Surrogate-Control'\n  | 'TCN'\n  | 'TE'\n  | 'Timeout'\n  | 'Timing-Allow-Origin'\n  | 'Topic'\n  | 'Traceparent'\n  | 'Tracestate'\n  | 'Trailer'\n  | 'Transfer-Encoding'\n  | 'TTL'\n  | 'Upgrade'\n  | 'Urgency'\n  | 'URI'\n  | 'Use-As-Dictionary'\n  | 'User-Agent'\n  | 'Variant-Vary'\n  | 'Vary'\n  | 'Via'\n  | 'Want-Content-Digest'\n  | 'Want-Digest'\n  | 'Want-Repr-Digest'\n  | 'Warning'\n  | 'WWW-Authenticate'\n  | 'X-Content-Type-Options'\n  | 'X-Frame-Options'\n\nexport type ResponseHeader =\n  | 'Access-Control-Allow-Credentials'\n  | 'Access-Control-Allow-Headers'\n  | 'Access-Control-Allow-Methods'\n  | 'Access-Control-Allow-Origin'\n  | 'Access-Control-Expose-Headers'\n  | 'Access-Control-Max-Age'\n  | 'Age'\n  | 'Allow'\n  | 'Cache-Control'\n  | 'Clear-Site-Data'\n  | 'Content-Disposition'\n  | 'Content-Encoding'\n  | 'Content-Language'\n  | 'Content-Length'\n  | 'Content-Location'\n  | 'Content-Range'\n  | 'Content-Security-Policy'\n  | 'Content-Security-Policy-Report-Only'\n  | 'Content-Type'\n  | 'Cookie'\n  | 'Cross-Origin-Embedder-Policy'\n  | 'Cross-Origin-Opener-Policy'\n  | 'Cross-Origin-Resource-Policy'\n  | 'Date'\n  | 'ETag'\n  | 'Expires'\n  | 'Last-Modified'\n  | 'Location'\n  | 'Permissions-Policy'\n  | 'Pragma'\n  | 'Retry-After'\n  | 'Save-Data'\n  | 'Sec-CH-Prefers-Color-Scheme'\n  | 'Sec-CH-Prefers-Reduced-Motion'\n  | 'Sec-CH-UA'\n  | 'Sec-CH-UA-Arch'\n  | 'Sec-CH-UA-Bitness'\n  | 'Sec-CH-UA-Form-Factor'\n  | 'Sec-CH-UA-Full-Version'\n  | 'Sec-CH-UA-Full-Version-List'\n  | 'Sec-CH-UA-Mobile'\n  | 'Sec-CH-UA-Model'\n  | 'Sec-CH-UA-Platform'\n  | 'Sec-CH-UA-Platform-Version'\n  | 'Sec-CH-UA-WoW64'\n  | 'Sec-Fetch-Dest'\n  | 'Sec-Fetch-Mode'\n  | 'Sec-Fetch-Site'\n  | 'Sec-Fetch-User'\n  | 'Sec-GPC'\n  | 'Server'\n  | 'Server-Timing'\n  | 'Service-Worker-Navigation-Preload'\n  | 'Set-Cookie'\n  | 'Strict-Transport-Security'\n  | 'Timing-Allow-Origin'\n  | 'Trailer'\n  | 'Transfer-Encoding'\n  | 'Upgrade'\n  | 'Vary'\n  | 'WWW-Authenticate'\n  | 'Warning'\n  | 'X-Content-Type-Options'\n  | 'X-DNS-Prefetch-Control'\n  | 'X-Frame-Options'\n  | 'X-Permitted-Cross-Domain-Policies'\n  | 'X-Powered-By'\n  | 'X-Robots-Tag'\n  | 'X-XSS-Protection'\n\nexport type AcceptHeader =\n  | 'Accept'\n  | 'Accept-Charset'\n  | 'Accept-Encoding'\n  | 'Accept-Language'\n  | 'Accept-Patch'\n  | 'Accept-Post'\n  | 'Accept-Ranges'\n\n// note: `X-${string}` is deprecated\nexport type CustomHeader = string & {}\n"
  },
  {
    "path": "src/utils/html.test.ts",
    "content": "import { escapeToBuffer } from './html'\nimport type { StringBuffer } from './html'\n\ndescribe('HTML utilities', () => {\n  describe('escapeToBuffer', () => {\n    it('Should escape special characters', () => {\n      let buffer: StringBuffer = ['']\n      escapeToBuffer('I <b>think</b> this is good.', buffer)\n      expect(buffer[0]).toBe('I &lt;b&gt;think&lt;/b&gt; this is good.')\n\n      buffer = ['']\n      escapeToBuffer('John \"Johnny\" Smith', buffer)\n      expect(buffer[0]).toBe('John &quot;Johnny&quot; Smith')\n    })\n  })\n})\n"
  },
  {
    "path": "src/utils/html.ts",
    "content": "/**\n * @module\n * HTML utility.\n */\n\nexport const HtmlEscapedCallbackPhase = {\n  Stringify: 1,\n  BeforeStream: 2,\n  Stream: 3,\n} as const\ntype HtmlEscapedCallbackOpts = {\n  buffer?: [string]\n  phase: (typeof HtmlEscapedCallbackPhase)[keyof typeof HtmlEscapedCallbackPhase]\n  context: Readonly<object> // An object unique to each JSX tree. This object is used as the WeakMap key.\n}\nexport type HtmlEscapedCallback = (opts: HtmlEscapedCallbackOpts) => Promise<string> | undefined\nexport type HtmlEscaped = {\n  isEscaped: true\n  callbacks?: HtmlEscapedCallback[]\n}\nexport type HtmlEscapedString = string & HtmlEscaped\n\n/**\n * StringBuffer contains string and Promise<string> alternately\n * The length of the array will be odd, the odd numbered element will be a string,\n * and the even numbered element will be a Promise<string>.\n * When concatenating into a single string, it must be processed from the tail.\n * @example\n * [\n *   'framework.',\n *   Promise.resolve('ultra fast'),\n *   'a ',\n *   Promise.resolve('is '),\n *   'Hono',\n * ]\n */\nexport type StringBuffer = (string | Promise<string>)[]\nexport type StringBufferWithCallbacks = StringBuffer & { callbacks: HtmlEscapedCallback[] }\n\nexport const raw = (value: unknown, callbacks?: HtmlEscapedCallback[]): HtmlEscapedString => {\n  const escapedString = new String(value) as HtmlEscapedString\n  escapedString.isEscaped = true\n  escapedString.callbacks = callbacks\n\n  return escapedString\n}\n\n// The `escapeToBuffer` implementation is based on code from the MIT licensed `react-dom` package.\n// https://github.com/facebook/react/blob/main/packages/react-dom-bindings/src/server/escapeTextForBrowser.js\n\nconst escapeRe = /[&<>'\"]/\n\nexport const stringBufferToString = async (\n  buffer: StringBuffer,\n  callbacks: HtmlEscapedCallback[] | undefined\n): Promise<HtmlEscapedString> => {\n  let str = ''\n  callbacks ||= []\n  const resolvedBuffer = await Promise.all(buffer)\n  for (let i = resolvedBuffer.length - 1; ; i--) {\n    str += resolvedBuffer[i]\n    i--\n    if (i < 0) {\n      break\n    }\n\n    let r = resolvedBuffer[i]\n    if (typeof r === 'object') {\n      callbacks.push(...((r as HtmlEscapedString).callbacks || []))\n    }\n\n    const isEscaped = (r as HtmlEscapedString).isEscaped\n    r = await (typeof r === 'object' ? (r as HtmlEscapedString).toString() : r)\n    if (typeof r === 'object') {\n      callbacks.push(...((r as HtmlEscapedString).callbacks || []))\n    }\n\n    if ((r as HtmlEscapedString).isEscaped ?? isEscaped) {\n      str += r\n    } else {\n      const buf = [str]\n      escapeToBuffer(r, buf)\n      str = buf[0]\n    }\n  }\n\n  return raw(str, callbacks)\n}\n\nexport const escapeToBuffer = (str: string, buffer: StringBuffer): void => {\n  const match = str.search(escapeRe)\n  if (match === -1) {\n    buffer[0] += str\n    return\n  }\n\n  let escape\n  let index\n  let lastIndex = 0\n\n  for (index = match; index < str.length; index++) {\n    switch (str.charCodeAt(index)) {\n      case 34: // \"\n        escape = '&quot;'\n        break\n      case 39: // '\n        escape = '&#39;'\n        break\n      case 38: // &\n        escape = '&amp;'\n        break\n      case 60: // <\n        escape = '&lt;'\n        break\n      case 62: // >\n        escape = '&gt;'\n        break\n      default:\n        continue\n    }\n\n    buffer[0] += str.substring(lastIndex, index) + escape\n    lastIndex = index + 1\n  }\n\n  buffer[0] += str.substring(lastIndex, index)\n}\n\nexport const resolveCallbackSync = (str: string | HtmlEscapedString): string => {\n  const callbacks = (str as HtmlEscapedString).callbacks as HtmlEscapedCallback[]\n  if (!callbacks?.length) {\n    return str\n  }\n  const buffer: [string] = [str]\n  const context = {}\n\n  callbacks.forEach((c) => c({ phase: HtmlEscapedCallbackPhase.Stringify, buffer, context }))\n\n  return buffer[0]\n}\n\nexport const resolveCallback = async (\n  str: string | HtmlEscapedString | Promise<string>,\n  phase: (typeof HtmlEscapedCallbackPhase)[keyof typeof HtmlEscapedCallbackPhase],\n  preserveCallbacks: boolean,\n  context: object,\n  buffer?: [string]\n): Promise<string> => {\n  if (typeof str === 'object' && !(str instanceof String)) {\n    if (!((str as unknown) instanceof Promise)) {\n      str = (str as unknown as string).toString() // HtmlEscapedString object to string\n    }\n    if ((str as string | Promise<string>) instanceof Promise) {\n      str = await (str as unknown as Promise<string>)\n    }\n  }\n\n  const callbacks = (str as HtmlEscapedString).callbacks as HtmlEscapedCallback[]\n  if (!callbacks?.length) {\n    return Promise.resolve(str)\n  }\n  if (buffer) {\n    buffer[0] += str\n  } else {\n    buffer = [str as string]\n  }\n\n  const resStr = Promise.all(callbacks.map((c) => c({ phase, buffer, context }))).then((res) =>\n    Promise.all(\n      res\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        .filter<string>(Boolean as any)\n        .map((str) => resolveCallback(str, phase, false, context, buffer))\n    ).then(() => (buffer as [string])[0])\n  )\n\n  if (preserveCallbacks) {\n    return raw(await resStr, callbacks)\n  } else {\n    return resStr\n  }\n}\n"
  },
  {
    "path": "src/utils/http-status.ts",
    "content": "/**\n * @module\n * HTTP Status utility.\n */\n\nexport type InfoStatusCode = 100 | 101 | 102 | 103\nexport type SuccessStatusCode = 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226\nexport type DeprecatedStatusCode = 305 | 306\nexport type RedirectStatusCode = 300 | 301 | 302 | 303 | 304 | DeprecatedStatusCode | 307 | 308\nexport type ClientErrorStatusCode =\n  | 400\n  | 401\n  | 402\n  | 403\n  | 404\n  | 405\n  | 406\n  | 407\n  | 408\n  | 409\n  | 410\n  | 411\n  | 412\n  | 413\n  | 414\n  | 415\n  | 416\n  | 417\n  | 418\n  | 421\n  | 422\n  | 423\n  | 424\n  | 425\n  | 426\n  | 428\n  | 429\n  | 431\n  | 451\nexport type ServerErrorStatusCode = 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511\n\n/**\n * `UnofficialStatusCode` can be used to specify an unofficial status code.\n * @example\n *\n * ```ts\n * app.get('/unknown', (c) => {\n *   return c.text(\"Unknown Error\", 520 as UnofficialStatusCode)\n * })\n * ```\n */\nexport type UnofficialStatusCode = -1\n\n/**\n * @deprecated\n * Use `UnofficialStatusCode` instead.\n */\nexport type UnOfficalStatusCode = UnofficialStatusCode\n\n/**\n * If you want to use an unofficial status, use `UnofficialStatusCode`.\n */\nexport type StatusCode =\n  | InfoStatusCode\n  | SuccessStatusCode\n  | RedirectStatusCode\n  | ClientErrorStatusCode\n  | ServerErrorStatusCode\n  | UnofficialStatusCode\n\nexport type ContentlessStatusCode = 101 | 204 | 205 | 304\nexport type ContentfulStatusCode = Exclude<StatusCode, ContentlessStatusCode>\n"
  },
  {
    "path": "src/utils/ipaddr.test.ts",
    "content": "import {\n  convertIPv4BinaryToString,\n  convertIPv4ToBinary,\n  convertIPv6BinaryToString,\n  convertIPv6ToBinary,\n  distinctRemoteAddr,\n  expandIPv6,\n} from './ipaddr'\n\ndescribe('expandIPv6', () => {\n  it('Should result be valid', () => {\n    expect(expandIPv6('1::1')).toBe('0001:0000:0000:0000:0000:0000:0000:0001')\n    expect(expandIPv6('::1')).toBe('0000:0000:0000:0000:0000:0000:0000:0001')\n    expect(expandIPv6('2001:2::')).toBe('2001:0002:0000:0000:0000:0000:0000:0000')\n    expect(expandIPv6('2001:2::')).toBe('2001:0002:0000:0000:0000:0000:0000:0000')\n    expect(expandIPv6('2001:0:0:db8::1')).toBe('2001:0000:0000:0db8:0000:0000:0000:0001')\n    expect(expandIPv6('::ffff:127.0.0.1')).toBe('0000:0000:0000:0000:0000:ffff:7f00:0001')\n  })\n})\ndescribe('distinctRemoteAddr', () => {\n  it('Should result be valid', () => {\n    expect(distinctRemoteAddr('1::1')).toBe('IPv6')\n    expect(distinctRemoteAddr('::1')).toBe('IPv6')\n    expect(distinctRemoteAddr('::ffff:127.0.0.1')).toBe('IPv6')\n\n    expect(distinctRemoteAddr('192.168.2.0')).toBe('IPv4')\n    expect(distinctRemoteAddr('192.168.2.0')).toBe('IPv4')\n\n    expect(distinctRemoteAddr('example.com')).toBeUndefined()\n  })\n\n  it('Should reject invalid IPv4 addresses with octets > 255', () => {\n    expect(distinctRemoteAddr('1.2.3.256')).toBeUndefined()\n    expect(distinctRemoteAddr('1.2.3.999')).toBeUndefined()\n    expect(distinctRemoteAddr('1.2.2.355')).toBeUndefined()\n    expect(distinctRemoteAddr('256.0.0.1')).toBeUndefined()\n    expect(distinctRemoteAddr('999.999.999.999')).toBeUndefined()\n  })\n\n  it('Should accept valid IPv4 edge cases', () => {\n    expect(distinctRemoteAddr('0.0.0.0')).toBe('IPv4')\n    expect(distinctRemoteAddr('255.255.255.255')).toBe('IPv4')\n    expect(distinctRemoteAddr('1.2.3.4')).toBe('IPv4')\n  })\n})\n\ndescribe('convertIPv4ToBinary', () => {\n  it('Should result is valid', () => {\n    expect(convertIPv4ToBinary('0.0.0.0')).toBe(0n)\n    expect(convertIPv4ToBinary('0.0.0.1')).toBe(1n)\n\n    expect(convertIPv4ToBinary('0.0.1.0')).toBe(1n << 8n)\n  })\n})\n\ndescribe('convertIPv4ToString', () => {\n  // add tons of test cases here\n  test.each`\n    input        | expected\n    ${'0.0.0.0'} | ${'0.0.0.0'}\n    ${'0.0.0.1'} | ${'0.0.0.1'}\n    ${'0.0.1.0'} | ${'0.0.1.0'}\n  `('convertIPv4ToString($input) === $expected', ({ input, expected }) => {\n    expect(convertIPv4BinaryToString(convertIPv4ToBinary(input))).toBe(expected)\n  })\n})\n\ndescribe('convertIPv6ToBinary', () => {\n  it('Should result is valid', () => {\n    expect(convertIPv6ToBinary('::0')).toBe(0n)\n    expect(convertIPv6ToBinary('::1')).toBe(1n)\n\n    expect(convertIPv6ToBinary('::f')).toBe(15n)\n    expect(convertIPv6ToBinary('1234:::5678')).toBe(24196103360772296748952112894165669496n)\n    expect(convertIPv6ToBinary('::ffff:127.0.0.1')).toBe(281472812449793n)\n  })\n})\n\ndescribe('convertIPv6ToString', () => {\n  // add tons of test cases here\n  test.each`\n    input                                        | expected\n    ${'::1'}                                     | ${'::1'}\n    ${'1::'}                                     | ${'1::'}\n    ${'1234:::5678'}                             | ${'1234::5678'}\n    ${'2001:2::'}                                | ${'2001:2::'}\n    ${'2001::db8:0:0:0:0:1'}                     | ${'2001:0:db8::1'}\n    ${'1234:5678:9abc:def0:1234:5678:9abc:def0'} | ${'1234:5678:9abc:def0:1234:5678:9abc:def0'}\n    ${'::ffff:127.0.0.1'}                        | ${'::ffff:127.0.0.1'}\n  `('convertIPv6ToString($input) === $expected', ({ input, expected }) => {\n    expect(convertIPv6BinaryToString(convertIPv6ToBinary(input))).toBe(expected)\n  })\n})\n"
  },
  {
    "path": "src/utils/ipaddr.ts",
    "content": "/**\n * Utils for IP Addresses\n * @module\n */\n\nimport type { AddressType } from '../helper/conninfo'\n\n/**\n * Expand IPv6 Address\n * @param ipV6 Shorten IPv6 Address\n * @return expanded IPv6 Address\n */\nexport const expandIPv6 = (ipV6: string): string => {\n  const sections = ipV6.split(':')\n  if (IPV4_REGEX.test(sections.at(-1) as string)) {\n    sections.splice(\n      -1,\n      1,\n      ...convertIPv6BinaryToString(convertIPv4ToBinary(sections.at(-1) as string)) // => ::7f00:0001\n        .substring(2) // => 7f00:0001\n        .split(':') // => ['7f00', '0001']\n    )\n  }\n  for (let i = 0; i < sections.length; i++) {\n    const node = sections[i]\n    if (node !== '') {\n      sections[i] = node.padStart(4, '0')\n    } else {\n      sections[i + 1] === '' && sections.splice(i + 1, 1)\n      sections[i] = new Array(8 - sections.length + 1).fill('0000').join(':')\n    }\n  }\n  return sections.join(':')\n}\n\nconst IPV4_OCTET_PART = '(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])'\nconst IPV4_REGEX = new RegExp(`^(?:${IPV4_OCTET_PART}\\\\.){3}${IPV4_OCTET_PART}$`)\n\n/**\n * Distinct Remote Addr\n * @param remoteAddr Remote Addr\n */\nexport const distinctRemoteAddr = (remoteAddr: string): AddressType => {\n  if (IPV4_REGEX.test(remoteAddr)) {\n    return 'IPv4'\n  }\n  if (remoteAddr.includes(':')) {\n    // Domain can't include `:`\n    return 'IPv6'\n  }\n}\n\n/**\n * Convert IPv4 to Uint8Array\n * @param ipv4 IPv4 Address\n * @returns BigInt\n */\nexport const convertIPv4ToBinary = (ipv4: string): bigint => {\n  const parts = ipv4.split('.')\n  let result = 0n\n  for (let i = 0; i < 4; i++) {\n    result <<= 8n\n    result += BigInt(parts[i])\n  }\n  return result\n}\n\n/**\n * Convert IPv6 to Uint8Array\n * @param ipv6 IPv6 Address\n * @returns BigInt\n */\nexport const convertIPv6ToBinary = (ipv6: string): bigint => {\n  const sections = expandIPv6(ipv6).split(':')\n  let result = 0n\n  for (let i = 0; i < 8; i++) {\n    result <<= 16n\n    result += BigInt(parseInt(sections[i], 16))\n  }\n  return result\n}\n\n/**\n * Convert a binary representation of an IPv4 address to a string.\n * @param ipV4 binary IPv4 Address\n * @return IPv4 Address in string\n */\nexport const convertIPv4BinaryToString = (ipV4: bigint): string => {\n  const sections = []\n  for (let i = 0; i < 4; i++) {\n    sections.push((ipV4 >> BigInt(8 * (3 - i))) & 0xffn)\n  }\n  return sections.join('.')\n}\n\n/**\n * Convert a binary representation of an IPv6 address to a string.\n * @param ipV6 binary IPv6 Address\n * @return normalized IPv6 Address in string\n */\nexport const convertIPv6BinaryToString = (ipV6: bigint): string => {\n  // IPv6-mapped IPv4 address\n  if (ipV6 >> 32n === 0xffffn) {\n    return `::ffff:${convertIPv4BinaryToString(ipV6 & 0xffffffffn)}`\n  }\n\n  const sections = []\n  for (let i = 0; i < 8; i++) {\n    sections.push(((ipV6 >> BigInt(16 * (7 - i))) & 0xffffn).toString(16))\n  }\n\n  let currentZeroStart = -1\n  let maxZeroStart = -1\n  let maxZeroEnd = -1\n  for (let i = 0; i < 8; i++) {\n    if (sections[i] === '0') {\n      if (currentZeroStart === -1) {\n        currentZeroStart = i\n      }\n    } else {\n      if (currentZeroStart > -1) {\n        if (i - currentZeroStart > maxZeroEnd - maxZeroStart) {\n          maxZeroStart = currentZeroStart\n          maxZeroEnd = i\n        }\n        currentZeroStart = -1\n      }\n    }\n  }\n  if (currentZeroStart > -1) {\n    if (8 - currentZeroStart > maxZeroEnd - maxZeroStart) {\n      maxZeroStart = currentZeroStart\n      maxZeroEnd = 8\n    }\n  }\n  if (maxZeroStart !== -1) {\n    sections.splice(maxZeroStart, maxZeroEnd - maxZeroStart, ':')\n  }\n\n  return sections.join(':').replace(/:{2,}/g, '::')\n}\n"
  },
  {
    "path": "src/utils/jwt/index.ts",
    "content": "/**\n * @module\n * JWT utility.\n */\n\nimport { decode, sign, verify, verifyWithJwks } from './jwt'\nexport const Jwt = { sign, verify, decode, verifyWithJwks }\n"
  },
  {
    "path": "src/utils/jwt/jwa.test.ts",
    "content": "import { AlgorithmTypes } from './jwa'\nimport type { AsymmetricAlgorithm, SymmetricAlgorithm, SignatureAlgorithm } from './jwa'\n\ndescribe('Types', () => {\n  it('AlgorithmTypes', () => {\n    expect('HS256' as AlgorithmTypes).toBe(AlgorithmTypes.HS256)\n    expect('HS384' as AlgorithmTypes).toBe(AlgorithmTypes.HS384)\n    expect('HS512' as AlgorithmTypes).toBe(AlgorithmTypes.HS512)\n    expect('RS256' as AlgorithmTypes).toBe(AlgorithmTypes.RS256)\n    expect('RS384' as AlgorithmTypes).toBe(AlgorithmTypes.RS384)\n    expect('RS512' as AlgorithmTypes).toBe(AlgorithmTypes.RS512)\n    expect('PS256' as AlgorithmTypes).toBe(AlgorithmTypes.PS256)\n    expect('PS384' as AlgorithmTypes).toBe(AlgorithmTypes.PS384)\n    expect('PS512' as AlgorithmTypes).toBe(AlgorithmTypes.PS512)\n    expect('ES256' as AlgorithmTypes).toBe(AlgorithmTypes.ES256)\n    expect('ES384' as AlgorithmTypes).toBe(AlgorithmTypes.ES384)\n    expect('ES512' as AlgorithmTypes).toBe(AlgorithmTypes.ES512)\n    expect('EdDSA' as AlgorithmTypes).toBe(AlgorithmTypes.EdDSA)\n\n    // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n    // @ts-ignore\n    expect(undefined as AlgorithmTypes).toBe(undefined)\n    expect('' as AlgorithmTypes).toBe('')\n  })\n\n  it('SymmetricAlgorithm type should only include HMAC algorithms', () => {\n    // These should be valid SymmetricAlgorithm values\n    const hs256: SymmetricAlgorithm = 'HS256'\n    const hs384: SymmetricAlgorithm = 'HS384'\n    const hs512: SymmetricAlgorithm = 'HS512'\n\n    expect(hs256).toBe('HS256')\n    expect(hs384).toBe('HS384')\n    expect(hs512).toBe('HS512')\n\n    // Type-level test: these would cause compile errors if uncommented\n    // const rs256: SymmetricAlgorithm = 'RS256' // Error: Type '\"RS256\"' is not assignable to type 'SymmetricAlgorithm'\n  })\n\n  it('AsymmetricAlgorithm type should only include asymmetric algorithms', () => {\n    // These should be valid AsymmetricAlgorithm values\n    const asymmetricAlgs: AsymmetricAlgorithm[] = [\n      'RS256',\n      'RS384',\n      'RS512',\n      'PS256',\n      'PS384',\n      'PS512',\n      'ES256',\n      'ES384',\n      'ES512',\n      'EdDSA',\n    ]\n\n    expect(asymmetricAlgs).toHaveLength(10)\n\n    // Verify all asymmetric algorithms are included\n    expect(asymmetricAlgs).toContain('RS256')\n    expect(asymmetricAlgs).toContain('ES256')\n    expect(asymmetricAlgs).toContain('EdDSA')\n\n    // Type-level test: these would cause compile errors if uncommented\n    // const hs256: AsymmetricAlgorithm = 'HS256' // Error: Type '\"HS256\"' is not assignable to type 'AsymmetricAlgorithm'\n  })\n\n  it('SignatureAlgorithm type should include all algorithms', () => {\n    // SignatureAlgorithm should include both symmetric and asymmetric algorithms\n    const allAlgs: SignatureAlgorithm[] = [\n      'HS256',\n      'HS384',\n      'HS512',\n      'RS256',\n      'RS384',\n      'RS512',\n      'PS256',\n      'PS384',\n      'PS512',\n      'ES256',\n      'ES384',\n      'ES512',\n      'EdDSA',\n    ]\n\n    expect(allAlgs).toHaveLength(13)\n  })\n})\n"
  },
  {
    "path": "src/utils/jwt/jwa.ts",
    "content": "/**\n * @module\n * JSON Web Algorithms (JWA)\n * https://datatracker.ietf.org/doc/html/rfc7518\n */\n\nexport enum AlgorithmTypes {\n  HS256 = 'HS256',\n  HS384 = 'HS384',\n  HS512 = 'HS512',\n  RS256 = 'RS256',\n  RS384 = 'RS384',\n  RS512 = 'RS512',\n  PS256 = 'PS256',\n  PS384 = 'PS384',\n  PS512 = 'PS512',\n  ES256 = 'ES256',\n  ES384 = 'ES384',\n  ES512 = 'ES512',\n  EdDSA = 'EdDSA',\n}\n\nexport type SignatureAlgorithm = keyof typeof AlgorithmTypes\n\nexport type SymmetricAlgorithm = 'HS256' | 'HS384' | 'HS512'\n\nexport type AsymmetricAlgorithm =\n  | 'RS256'\n  | 'RS384'\n  | 'RS512'\n  | 'PS256'\n  | 'PS384'\n  | 'PS512'\n  | 'ES256'\n  | 'ES384'\n  | 'ES512'\n  | 'EdDSA'\n"
  },
  {
    "path": "src/utils/jwt/jws.ts",
    "content": "/**\n * @module\n * JSON Web Signature (JWS)\n * https://datatracker.ietf.org/doc/html/rfc7515\n */\n\nimport { getRuntimeKey } from '../../helper/adapter'\nimport { decodeBase64 } from '../encode'\nimport type { SignatureAlgorithm } from './jwa'\nimport { CryptoKeyUsage, JwtAlgorithmNotImplemented } from './types'\nimport { utf8Encoder } from './utf8'\n\ntype KeyImporterAlgorithm = Parameters<typeof crypto.subtle.importKey>[2]\ntype KeyAlgorithm =\n  | AlgorithmIdentifier\n  | RsaHashedImportParams\n  | (RsaPssParams & RsaHashedImportParams)\n  | (EcdsaParams & EcKeyImportParams)\n  | HmacImportParams\n\n// Extending the JsonWebKey interface to include the \"kid\" property.\n// https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.4\nexport interface HonoJsonWebKey extends JsonWebKey {\n  kid?: string\n}\n\nexport type SignatureKey = string | HonoJsonWebKey | CryptoKey\n\nexport async function signing(\n  privateKey: SignatureKey,\n  alg: SignatureAlgorithm,\n  data: BufferSource\n): Promise<ArrayBuffer> {\n  const algorithm = getKeyAlgorithm(alg)\n  const cryptoKey = await importPrivateKey(privateKey, algorithm)\n  return await crypto.subtle.sign(algorithm, cryptoKey, data)\n}\n\nexport async function verifying(\n  publicKey: SignatureKey,\n  alg: SignatureAlgorithm,\n  signature: BufferSource,\n  data: BufferSource\n): Promise<boolean> {\n  const algorithm = getKeyAlgorithm(alg)\n  const cryptoKey = await importPublicKey(publicKey, algorithm)\n  return await crypto.subtle.verify(algorithm, cryptoKey, signature, data)\n}\n\nfunction pemToBinary(pem: string): Uint8Array<ArrayBuffer> {\n  return decodeBase64(pem.replace(/-+(BEGIN|END).*/g, '').replace(/\\s/g, ''))\n}\n\nasync function importPrivateKey(key: SignatureKey, alg: KeyImporterAlgorithm): Promise<CryptoKey> {\n  if (!crypto.subtle || !crypto.subtle.importKey) {\n    throw new Error('`crypto.subtle.importKey` is undefined. JWT auth middleware requires it.')\n  }\n  if (isCryptoKey(key)) {\n    if (key.type !== 'private' && key.type !== 'secret') {\n      throw new Error(\n        `unexpected key type: CryptoKey.type is ${key.type}, expected private or secret`\n      )\n    }\n    return key\n  }\n  const usages = [CryptoKeyUsage.Sign]\n  if (typeof key === 'object') {\n    // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#json_web_key_import\n    return await crypto.subtle.importKey('jwk', key, alg, false, usages)\n  }\n  if (key.includes('PRIVATE')) {\n    // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#pkcs_8_import\n    return await crypto.subtle.importKey('pkcs8', pemToBinary(key), alg, false, usages)\n  }\n  return await crypto.subtle.importKey('raw', utf8Encoder.encode(key), alg, false, usages)\n}\n\nasync function importPublicKey(key: SignatureKey, alg: KeyImporterAlgorithm): Promise<CryptoKey> {\n  if (!crypto.subtle || !crypto.subtle.importKey) {\n    throw new Error('`crypto.subtle.importKey` is undefined. JWT auth middleware requires it.')\n  }\n  if (isCryptoKey(key)) {\n    if (key.type === 'public' || key.type === 'secret') {\n      return key\n    }\n    key = await exportPublicJwkFrom(key)\n  }\n  if (typeof key === 'string' && key.includes('PRIVATE')) {\n    // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#pkcs_8_import\n    const privateKey = await crypto.subtle.importKey('pkcs8', pemToBinary(key), alg, true, [\n      CryptoKeyUsage.Sign,\n    ])\n    key = await exportPublicJwkFrom(privateKey)\n  }\n  const usages = [CryptoKeyUsage.Verify]\n  if (typeof key === 'object') {\n    // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#json_web_key_import\n    return await crypto.subtle.importKey('jwk', key, alg, false, usages)\n  }\n  if (key.includes('PUBLIC')) {\n    // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#subjectpublickeyinfo_import\n    return await crypto.subtle.importKey('spki', pemToBinary(key), alg, false, usages)\n  }\n  return await crypto.subtle.importKey('raw', utf8Encoder.encode(key), alg, false, usages)\n}\n\n// https://datatracker.ietf.org/doc/html/rfc7517\nasync function exportPublicJwkFrom(privateKey: CryptoKey): Promise<JsonWebKey> {\n  if (privateKey.type !== 'private') {\n    throw new Error(`unexpected key type: ${privateKey.type}`)\n  }\n  if (!privateKey.extractable) {\n    throw new Error('unexpected private key is unextractable')\n  }\n  const jwk = await crypto.subtle.exportKey('jwk', privateKey)\n  const { kty } = jwk // common\n  const { alg, e, n } = jwk // rsa\n  const { crv, x, y } = jwk // elliptic-curve\n  return { kty, alg, e, n, crv, x, y, key_ops: [CryptoKeyUsage.Verify] }\n}\n\nfunction getKeyAlgorithm(name: SignatureAlgorithm): KeyAlgorithm {\n  switch (name) {\n    case 'HS256':\n      return {\n        name: 'HMAC',\n        hash: {\n          name: 'SHA-256',\n        },\n      } satisfies HmacImportParams\n    case 'HS384':\n      return {\n        name: 'HMAC',\n        hash: {\n          name: 'SHA-384',\n        },\n      } satisfies HmacImportParams\n    case 'HS512':\n      return {\n        name: 'HMAC',\n        hash: {\n          name: 'SHA-512',\n        },\n      } satisfies HmacImportParams\n    case 'RS256':\n      return {\n        name: 'RSASSA-PKCS1-v1_5',\n        hash: {\n          name: 'SHA-256',\n        },\n      } satisfies RsaHashedImportParams\n    case 'RS384':\n      return {\n        name: 'RSASSA-PKCS1-v1_5',\n        hash: {\n          name: 'SHA-384',\n        },\n      } satisfies RsaHashedImportParams\n    case 'RS512':\n      return {\n        name: 'RSASSA-PKCS1-v1_5',\n        hash: {\n          name: 'SHA-512',\n        },\n      } satisfies RsaHashedImportParams\n    case 'PS256':\n      return {\n        name: 'RSA-PSS',\n        hash: {\n          name: 'SHA-256',\n        },\n        saltLength: 32, // 256 >> 3\n      } satisfies RsaPssParams & RsaHashedImportParams\n    case 'PS384':\n      return {\n        name: 'RSA-PSS',\n        hash: {\n          name: 'SHA-384',\n        },\n        saltLength: 48, // 384 >> 3\n      } satisfies RsaPssParams & RsaHashedImportParams\n    case 'PS512':\n      return {\n        name: 'RSA-PSS',\n        hash: {\n          name: 'SHA-512',\n        },\n        saltLength: 64, // 512 >> 3,\n      } satisfies RsaPssParams & RsaHashedImportParams\n    case 'ES256':\n      return {\n        name: 'ECDSA',\n        hash: {\n          name: 'SHA-256',\n        },\n        namedCurve: 'P-256',\n      } satisfies EcdsaParams & EcKeyImportParams\n    case 'ES384':\n      return {\n        name: 'ECDSA',\n        hash: {\n          name: 'SHA-384',\n        },\n        namedCurve: 'P-384',\n      } satisfies EcdsaParams & EcKeyImportParams\n    case 'ES512':\n      return {\n        name: 'ECDSA',\n        hash: {\n          name: 'SHA-512',\n        },\n        namedCurve: 'P-521',\n      } satisfies EcdsaParams & EcKeyImportParams\n    case 'EdDSA':\n      // Currently, supported only Safari and Deno, Node.js.\n      // See: https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/verify\n      return {\n        name: 'Ed25519',\n        namedCurve: 'Ed25519',\n      }\n    default:\n      throw new JwtAlgorithmNotImplemented(name)\n  }\n}\n\nfunction isCryptoKey(key: SignatureKey): key is CryptoKey {\n  const runtime = getRuntimeKey()\n  // @ts-expect-error CryptoKey hasn't exported to global in node v18\n  if (runtime === 'node' && !!crypto.webcrypto) {\n    // @ts-expect-error CryptoKey hasn't exported to global in node v18\n    return key instanceof crypto.webcrypto.CryptoKey\n  }\n  return key instanceof CryptoKey\n}\n"
  },
  {
    "path": "src/utils/jwt/jwt.test.ts",
    "content": "/* eslint-disable @typescript-eslint/ban-ts-comment */\nimport { vi } from 'vitest'\nimport { encodeBase64, encodeBase64Url } from '../encode'\nimport { AlgorithmTypes } from './jwa'\nimport type { HonoJsonWebKey } from './jws'\nimport { signing } from './jws'\nimport * as JWT from './jwt'\nimport { verifyWithJwks } from './jwt'\nimport {\n  JwtAlgorithmMismatch,\n  JwtAlgorithmNotAllowed,\n  JwtAlgorithmNotImplemented,\n  JwtAlgorithmRequired,\n  JwtPayloadRequiresAud,\n  JwtSymmetricAlgorithmNotAllowed,\n  JwtTokenAudience,\n  JwtTokenExpired,\n  JwtTokenInvalid,\n  JwtTokenIssuedAt,\n  JwtTokenIssuer,\n  JwtTokenNotBefore,\n  JwtTokenSignatureMismatched,\n} from './types'\nimport { utf8Encoder } from './utf8'\n\ndescribe('isTokenHeader', () => {\n  it('should return true for valid TokenHeader', () => {\n    const validTokenHeader: JWT.TokenHeader = {\n      alg: AlgorithmTypes.HS256,\n      typ: 'JWT',\n    }\n\n    expect(JWT.isTokenHeader(validTokenHeader)).toBe(true)\n  })\n\n  it('should return false for invalid TokenHeader', () => {\n    const invalidTokenHeader = {\n      alg: 'invalid',\n      typ: 'JWT',\n    }\n\n    expect(JWT.isTokenHeader(invalidTokenHeader)).toBe(false)\n  })\n\n  it('returns true even if the typ field is absent in a TokenHeader', () => {\n    const validTokenHeader: JWT.TokenHeader = {\n      alg: AlgorithmTypes.HS256,\n    }\n\n    expect(JWT.isTokenHeader(validTokenHeader)).toBe(true)\n  })\n\n  it('returns false when the typ field is present but empty', () => {\n    const invalidTokenHeader = {\n      alg: AlgorithmTypes.HS256,\n      typ: '',\n    }\n\n    expect(JWT.isTokenHeader(invalidTokenHeader)).toBe(false)\n  })\n})\n\ndescribe('JWT', () => {\n  it('JwtAlgorithmNotImplemented', async () => {\n    const payload = { message: 'hello world' }\n    const secret = 'a-secret'\n    const alg = ''\n    let tok = ''\n    let err: JwtAlgorithmNotImplemented\n    try {\n      tok = await JWT.sign(payload, secret, alg as AlgorithmTypes)\n    } catch (e) {\n      err = e as JwtAlgorithmNotImplemented\n    }\n    expect(tok).toBe('')\n    // @ts-ignore\n    expect(err).toEqual(new JwtAlgorithmNotImplemented(alg))\n  })\n\n  it('JwtTokenInvalid', async () => {\n    const tok = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ'\n    const secret = 'a-secret'\n    let err: JwtTokenInvalid\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, AlgorithmTypes.HS256)\n    } catch (e) {\n      err = e as JwtTokenInvalid\n    }\n    // @ts-ignore\n    expect(err).toEqual(new JwtTokenInvalid(tok))\n    expect(authorized).toBeUndefined()\n  })\n\n  it('JwtTokenNotBefore', async () => {\n    const tok =\n      'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2NjQ2MDYzMzQsImV4cCI6MTY2NDYwOTkzNCwibmJmIjoiMzEwNDYwNjI2NCJ9.hpSDT_cfkxeiLWEpWVT8TDxFP3dFi27q1K7CcMcLXHc'\n    const secret = 'a-secret'\n    let err: JwtTokenNotBefore\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, AlgorithmTypes.HS256)\n    } catch (e) {\n      err = e as JwtTokenNotBefore\n    }\n    // @ts-ignore\n    expect(err).toEqual(new JwtTokenNotBefore(tok))\n    expect(authorized).toBeUndefined()\n  })\n\n  it('JwtTokenExpired', async () => {\n    const tok =\n      'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MzMwNDYxMDAsImV4cCI6MTYzMzA0NjQwMH0.H-OI1TWAbmK8RonvcpPaQcNvOKS9sxinEOsgKwjoiVo'\n    const secret = 'a-secret'\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, AlgorithmTypes.HS256)\n    } catch (e) {\n      err = e\n    }\n    expect(err).toEqual(new JwtTokenExpired(tok))\n    expect(authorized).toBeUndefined()\n  })\n\n  it('JwtTokenExpired after Y2038', async () => {\n    const postY2038 = 2147483648 // 2038-01-19 03:14:08 UTC\n    vi.useFakeTimers().setSystemTime(new Date(postY2038 * 1000))\n\n    const expIn2025 = 1735689600 // 2025-01-01 00:00:00 UTC\n    const payload = { message: 'hello', exp: expIn2025 }\n    const secret = 'a-secret'\n    const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS256)\n\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, AlgorithmTypes.HS256)\n    } catch (e) {\n      err = e\n    }\n    expect(err).toEqual(new JwtTokenExpired(tok))\n    expect(authorized).toBeUndefined()\n\n    vi.useRealTimers()\n  })\n\n  it('JwtTokenIssuedAt', async () => {\n    const now = 1633046400\n    vi.useFakeTimers().setSystemTime(new Date().setTime(now * 1000))\n\n    const iat = now + 1000 // after 1s\n    const payload = { role: 'api_role', iat }\n    const secret = 'a-secret'\n    const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS256)\n\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, AlgorithmTypes.HS256)\n    } catch (e) {\n      err = e\n    }\n    expect(err).toEqual(new JwtTokenIssuedAt(now, iat))\n    expect(authorized).toBeUndefined()\n  })\n\n  it('JwtTokenIssuer (none)', async () => {\n    const tok =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MzMwNDY0MDB9.Ha3tPZzmnLGyFfZYd7GSV0iCn2F9kbZffFVZcTe5kJo'\n    const secret = 'a-secret'\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, {\n        alg: AlgorithmTypes.HS256,\n        iss: 'some',\n      })\n    } catch (e) {\n      err = e\n    }\n    expect(err).toEqual(new JwtTokenIssuer('some', null))\n    expect(authorized).toBeUndefined()\n  })\n\n  it('JwtTokenIssuer (wrong - string)', async () => {\n    const tok =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MzMwNDY0MDAsImlzcyI6ImZha2UtaXNzdWVyIn0.miyPU40DBvhxpAUsndssJOMBsP1aqc8JClGnriPHfXk'\n    const secret = 'a-secret'\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, {\n        alg: AlgorithmTypes.HS256,\n        iss: 'expected-issuer',\n      })\n    } catch (e) {\n      err = e\n    }\n    expect(err).toEqual(new JwtTokenIssuer('expected-issuer', 'fake-issuer'))\n    expect(authorized).toBeUndefined()\n  })\n\n  it('JwtTokenIssuer (wrong - RegExp)', async () => {\n    const tok =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MzMwNDY0MDAsImlzcyI6ImhleSJ9.Q0NJwoj-meWy42pFFrPNlREe2lOagWioJUjR4eJCx0k'\n    const secret = 'a-secret'\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, {\n        alg: AlgorithmTypes.HS256,\n        iss: /^(hello|hi)$/,\n      })\n    } catch (e) {\n      err = e\n    }\n    expect(err).toEqual(new JwtTokenIssuer(/^(hello|hi)$/, 'hey'))\n    expect(authorized).toBeUndefined()\n  })\n\n  it('JwtTokenIssuer (correct - string)', async () => {\n    const tok =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MzMwNDY0MDAsImlzcyI6ImNvcnJlY3QtaXNzdWVyIn0.gF8S6M2QcfTTscgxeyihNk28JAOa8mfL1bXPb3_E3rk'\n    const secret = 'a-secret'\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, {\n        alg: AlgorithmTypes.HS256,\n        iss: 'correct-issuer',\n      })\n    } catch (e) {\n      err = e\n    }\n    expect(err).toBeUndefined()\n    expect(authorized?.iss).toEqual('correct-issuer')\n  })\n\n  it('JwtTokenIssuer (correct - RegExp)', async () => {\n    const tok =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MzMwNDY0MDAsImlzcyI6ImhlbGxvIn0.5DDuValGGQu4EfS3DY7C4hwwHyTNSTD93K_YEjBzgAc'\n    const secret = 'a-secret'\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, {\n        alg: AlgorithmTypes.HS256,\n        iss: /^(hello|hi)$/,\n      })\n    } catch (e) {\n      err = e\n    }\n    expect(err).toBeUndefined()\n    expect(authorized?.iss).toEqual('hello')\n  })\n\n  it('JwtPayloadRequireAud', async () => {\n    const tok =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiaWF0IjoxfQ.3Yd0dDicCKA6zu_G6AvxMX_fRH5wMz9gMCedOsYNAGc'\n    const secret = 'a-secret'\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, {\n        alg: AlgorithmTypes.HS256,\n        aud: 'correct-audience',\n      })\n    } catch (e) {\n      err = e\n    }\n    expect(err).toEqual(new JwtPayloadRequiresAud({ iss: 'https://issuer.example', iat: 1 }))\n    expect(authorized).toBeUndefined()\n  })\n\n  it('JwtTokenAudience(correct string - string)', async () => {\n    const tok =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjoiY29ycmVjdC1hdWRpZW5jZSIsImlhdCI6MX0.z8T6szX-k66de4xB9OFbpWAOfx0RTqKSUPBcdpSY5nk'\n    const secret = 'a-secret'\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, {\n        alg: AlgorithmTypes.HS256,\n        aud: 'correct-audience',\n      })\n    } catch (e) {\n      err = e\n    }\n    expect(err).toBeUndefined()\n    expect(authorized?.aud).toEqual('correct-audience')\n  })\n\n  it('JwtTokenAudience(correct string - string[])', async () => {\n    const tok =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjoiY29ycmVjdC1hdWRpZW5jZSIsImlhdCI6MX0.z8T6szX-k66de4xB9OFbpWAOfx0RTqKSUPBcdpSY5nk'\n    const secret = 'a-secret'\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, {\n        alg: AlgorithmTypes.HS256,\n        aud: ['correct-audience', 'other-audience'],\n      })\n    } catch (e) {\n      err = e\n    }\n    expect(err).toBeUndefined()\n    expect(authorized?.aud).toEqual('correct-audience')\n  })\n\n  it('JwtTokenAudience(correct string - RegExp)', async () => {\n    const tok =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjoiY29ycmVjdC1hdWRpZW5jZSIsImlhdCI6MX0.z8T6szX-k66de4xB9OFbpWAOfx0RTqKSUPBcdpSY5nk'\n    const secret = 'a-secret'\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, {\n        alg: AlgorithmTypes.HS256,\n        aud: /^correct-audience$/,\n      })\n    } catch (e) {\n      err = e\n    }\n    expect(err).toBeUndefined()\n    expect(authorized?.aud).toEqual('correct-audience')\n  })\n\n  it('JwtTokenAudience(correct string[] - string)', async () => {\n    const tok =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjpbImNvcnJlY3QtYXVkaWVuY2UiLCJvdGhlci1hdWRpZW5jZSJdLCJpYXQiOjF9.l73pNR5zMMAyuoN3f32hKtRJkoxZNzgTcVBZ2A2EsJY'\n    const secret = 'a-secret'\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, {\n        alg: AlgorithmTypes.HS256,\n        aud: 'correct-audience',\n      })\n    } catch (e) {\n      err = e\n    }\n    expect(err).toBeUndefined()\n    expect(authorized?.aud).toEqual(['correct-audience', 'other-audience'])\n  })\n\n  it('JwtTokenAudience(correct string[] - string[])', async () => {\n    const tok =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjpbImNvcnJlY3QtYXVkaWVuY2UiLCJvdGhlci1hdWRpZW5jZSJdLCJpYXQiOjF9.l73pNR5zMMAyuoN3f32hKtRJkoxZNzgTcVBZ2A2EsJY'\n    const secret = 'a-secret'\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, {\n        alg: AlgorithmTypes.HS256,\n        aud: ['correct-audience', 'test'],\n      })\n    } catch (e) {\n      err = e\n    }\n    expect(err).toBeUndefined()\n    expect(authorized?.aud).toEqual(['correct-audience', 'other-audience'])\n  })\n\n  it('JwtTokenAudience(correct string[] - RegExp)', async () => {\n    const tok =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjpbImNvcnJlY3QtYXVkaWVuY2UiLCJvdGhlci1hdWRpZW5jZSJdLCJpYXQiOjF9.l73pNR5zMMAyuoN3f32hKtRJkoxZNzgTcVBZ2A2EsJY'\n    const secret = 'a-secret'\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, {\n        alg: AlgorithmTypes.HS256,\n        aud: /^correct-audience$/,\n      })\n    } catch (e) {\n      err = e\n    }\n    expect(err).toBeUndefined()\n    expect(authorized?.aud).toEqual(['correct-audience', 'other-audience'])\n  })\n\n  it('JwtTokenAudience(wrong string - string)', async () => {\n    const tok =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjoid3JvbmctYXVkaWVuY2UiLCJpYXQiOjF9.2vTYLiYL5r6qN-iRQ0VSfXh4ioLFtNzo0qc-OoPZmow'\n    const secret = 'a-secret'\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, {\n        alg: AlgorithmTypes.HS256,\n        aud: 'correct-audience',\n      })\n    } catch (e) {\n      err = e\n    }\n    expect(err).toEqual(new JwtTokenAudience('correct-audience', 'wrong-audience'))\n    expect(authorized).toBeUndefined()\n  })\n\n  it('JwtTokenAudience(wrong string - string[])', async () => {\n    const tok =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjoid3JvbmctYXVkaWVuY2UiLCJpYXQiOjF9.2vTYLiYL5r6qN-iRQ0VSfXh4ioLFtNzo0qc-OoPZmow'\n    const secret = 'a-secret'\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, {\n        alg: AlgorithmTypes.HS256,\n        aud: ['correct-audience', 'other-audience'],\n      })\n    } catch (e) {\n      err = e\n    }\n    expect(err).toEqual(\n      new JwtTokenAudience(['correct-audience', 'other-audience'], 'wrong-audience')\n    )\n    expect(authorized).toBeUndefined()\n  })\n\n  it('JwtTokenAudience(wrong string - RegExp)', async () => {\n    const tok =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjoid3JvbmctYXVkaWVuY2UiLCJpYXQiOjF9.2vTYLiYL5r6qN-iRQ0VSfXh4ioLFtNzo0qc-OoPZmow'\n    const secret = 'a-secret'\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, {\n        alg: AlgorithmTypes.HS256,\n        aud: /^correct-audience$/,\n      })\n    } catch (e) {\n      err = e\n    }\n    expect(err).toEqual(new JwtTokenAudience(/^correct-audience$/, 'wrong-audience'))\n    expect(authorized).toBeUndefined()\n  })\n\n  it('JwtTokenAudience(wrong string[] - string)', async () => {\n    const tok =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjpbIndyb25nLWF1ZGllbmNlIiwib3RoZXItYXVkaWVuY2UiXSwiaWF0IjoxfQ.YTAM1xtKP4AeEeQSFQ81rcJM1leW_uDayQcTE6LxoP0'\n    const secret = 'a-secret'\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, {\n        alg: AlgorithmTypes.HS256,\n        aud: 'correct-audience',\n      })\n    } catch (e) {\n      err = e\n    }\n    expect(err).toEqual(\n      new JwtTokenAudience('correct-audience', ['wrong-audience', 'other-audience'])\n    )\n    expect(authorized).toBeUndefined()\n  })\n\n  it('JwtTokenAudience(wrong string[] - string[])', async () => {\n    const tok =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjpbIndyb25nLWF1ZGllbmNlIiwib3RoZXItYXVkaWVuY2UiXSwiaWF0IjoxfQ.YTAM1xtKP4AeEeQSFQ81rcJM1leW_uDayQcTE6LxoP0'\n    const secret = 'a-secret'\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, {\n        alg: AlgorithmTypes.HS256,\n        aud: ['correct-audience', 'test'],\n      })\n    } catch (e) {\n      err = e\n    }\n    expect(err).toEqual(\n      new JwtTokenAudience(['correct-audience', 'test'], ['wrong-audience', 'other-audience'])\n    )\n    expect(authorized).toBeUndefined()\n  })\n\n  it('JwtTokenAudience(wrong string[] - RegExp)', async () => {\n    const tok =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjpbIndyb25nLWF1ZGllbmNlIiwib3RoZXItYXVkaWVuY2UiXSwiaWF0IjoxfQ.YTAM1xtKP4AeEeQSFQ81rcJM1leW_uDayQcTE6LxoP0'\n    const secret = 'a-secret'\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, {\n        alg: AlgorithmTypes.HS256,\n        aud: /^correct-audience$/,\n      })\n    } catch (e) {\n      err = e\n    }\n    expect(err).toEqual(\n      new JwtTokenAudience(/^correct-audience$/, ['wrong-audience', 'other-audience'])\n    )\n    expect(authorized).toBeUndefined()\n  })\n\n  it('JwtTokenAudience (no aud option and wrong aud in payload)', async () => {\n    const tok =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlIiwiYXVkIjoid3JvbmctYXVkaWVuY2UiLCJpYXQiOjF9.2vTYLiYL5r6qN-iRQ0VSfXh4ioLFtNzo0qc-OoPZmow'\n    const secret = 'a-secret'\n    let err\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret, {\n        alg: AlgorithmTypes.HS256,\n      })\n    } catch (e) {\n      err = e\n    }\n    expect(err).toBeUndefined()\n    expect(authorized?.aud).toEqual('wrong-audience')\n  })\n\n  it('HS256 sign & verify & decode', async () => {\n    const payload = { message: 'hello world' }\n    const secret = 'a-secret'\n    const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS256)\n    const expected =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n    expect(tok).toEqual(expected)\n\n    const verifiedPayload = await JWT.verify(tok, secret, AlgorithmTypes.HS256)\n    expect(verifiedPayload).not.toBeUndefined()\n    expect(verifiedPayload).toEqual(payload)\n\n    expect(JWT.decode(tok)).toEqual({\n      header: {\n        alg: 'HS256',\n        typ: 'JWT',\n      },\n      payload: {\n        message: 'hello world',\n      },\n    })\n  })\n\n  it('HS256 sign & verify', async () => {\n    const payload = { message: 'hello world' }\n    const secret = 'a-secret'\n    const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS256)\n    const expected =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.B54pAqIiLbu170tGQ1rY06Twv__0qSHTA0ioQPIOvFE'\n    expect(tok).toEqual(expected)\n\n    let err = null\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret + 'invalid', AlgorithmTypes.HS256)\n    } catch (e) {\n      err = e\n    }\n    expect(authorized).toBeUndefined()\n    expect(err instanceof JwtTokenSignatureMismatched).toBe(true)\n  })\n\n  it('HS512 sign & verify & decode', async () => {\n    const payload = { message: 'hello world' }\n    const secret = 'a-secret'\n    const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS512)\n    const expected =\n      'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.RqVLgExB_GXF1-9T-k4V4HjFmiuQKTEjVSiZd-YL0WERIlywZ7PfzAuTZSJU4gg8cscGamQa030cieEWrYcywg'\n    expect(tok).toEqual(expected)\n\n    const verifiedPayload = await JWT.verify(tok, secret, AlgorithmTypes.HS512)\n    expect(verifiedPayload).not.toBeUndefined()\n    expect(verifiedPayload).toEqual(payload)\n\n    expect(JWT.decode(tok)).toEqual({\n      header: {\n        alg: 'HS512',\n        typ: 'JWT',\n      },\n      payload: {\n        message: 'hello world',\n      },\n    })\n  })\n\n  it('HS512 sign & verify', async () => {\n    const payload = { message: 'hello world' }\n    const secret = 'a-secret'\n    const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS512)\n    const expected =\n      'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.RqVLgExB_GXF1-9T-k4V4HjFmiuQKTEjVSiZd-YL0WERIlywZ7PfzAuTZSJU4gg8cscGamQa030cieEWrYcywg'\n    expect(tok).toEqual(expected)\n\n    let err = null\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret + 'invalid', AlgorithmTypes.HS512)\n    } catch (e) {\n      err = e\n    }\n    expect(authorized).toBeUndefined()\n    expect(err instanceof JwtTokenSignatureMismatched).toBe(true)\n  })\n\n  it('HS384 sign & verify', async () => {\n    const payload = { message: 'hello world' }\n    const secret = 'a-secret%你好'\n    const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS384)\n    const expected =\n      'eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.C1Br1183Oy6O7th4NDCOaI9WB75i3FMCuYlv1tCL9HggsU89T-SNutghwhJykD3r'\n    expect(tok).toEqual(expected)\n\n    let err = null\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, secret + 'invalid', AlgorithmTypes.HS384)\n    } catch (e) {\n      err = e\n    }\n    expect(authorized).toBeUndefined()\n    expect(err instanceof JwtTokenSignatureMismatched).toBe(true)\n  })\n\n  it('sign & verify & decode with a custom secret', async () => {\n    const payload = { message: 'hello world' }\n    const algorithm = {\n      name: 'HMAC',\n      hash: {\n        name: 'SHA-256',\n      },\n    }\n    const secret = await crypto.subtle.importKey(\n      'raw',\n      Buffer.from('cefb73234d5fae4bf27662900732b52943e8d53e871fe0f353da95de4599c21d', 'hex'),\n      algorithm,\n      false,\n      ['sign', 'verify']\n    )\n    const tok = await JWT.sign(payload, secret)\n    const expected =\n      'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8gd29ybGQifQ.qunGhchNXH_unqWXN6hB0Elhzr5SykSXVhklLti1aFI'\n    expect(tok).toEqual(expected)\n\n    const verifiedPayload = await JWT.verify(tok, secret, AlgorithmTypes.HS256)\n    expect(verifiedPayload).not.toBeUndefined()\n    expect(verifiedPayload).toEqual(payload)\n\n    const invalidSecret = await crypto.subtle.importKey(\n      'raw',\n      Buffer.from('cefb73234d5fae4bf27662900732b52943e8d53e871fe0f353da95de41111111', 'hex'),\n      algorithm,\n      false,\n      ['sign', 'verify']\n    )\n    let err = null\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, invalidSecret, AlgorithmTypes.HS256)\n    } catch (e) {\n      err = e\n    }\n    expect(authorized).toBeUndefined()\n    expect(err instanceof JwtTokenSignatureMismatched).toBe(true)\n  })\n\n  const rsTestCases = [\n    {\n      alg: AlgorithmTypes.RS256,\n      hash: 'SHA-256',\n    },\n    {\n      alg: AlgorithmTypes.RS384,\n      hash: 'SHA-384',\n    },\n    {\n      alg: AlgorithmTypes.RS512,\n      hash: 'SHA-512',\n    },\n  ]\n  for (const tc of rsTestCases) {\n    it(`${tc.alg} sign & verify`, async () => {\n      const alg = tc.alg\n      const payload = { message: 'hello world' }\n      const keyPair = await generateRSAKey(tc.hash)\n      const pemPrivateKey = await exportPEMPrivateKey(keyPair.privateKey)\n      const pemPublicKey = await exportPEMPublicKey(keyPair.publicKey)\n      const jwkPublicKey = await exportJWK(keyPair.publicKey)\n\n      const tok = await JWT.sign(payload, pemPrivateKey, alg)\n      expect(await JWT.verify(tok, pemPublicKey, alg)).toEqual(payload)\n      expect(await JWT.verify(tok, pemPrivateKey, alg)).toEqual(payload)\n      expect(await JWT.verify(tok, jwkPublicKey, alg)).toEqual(payload)\n\n      const keyPair2 = await generateRSAKey(tc.hash)\n      const unexpectedPemPublicKey = await exportPEMPublicKey(keyPair2.publicKey)\n\n      let err = null\n      let authorized\n      try {\n        authorized = await JWT.verify(tok, unexpectedPemPublicKey, alg)\n      } catch (e) {\n        err = e\n      }\n      expect(authorized).toBeUndefined()\n      expect(err instanceof JwtTokenSignatureMismatched).toBe(true)\n    })\n\n    it(`${tc.alg} sign & verify w/ CryptoKey`, async () => {\n      const alg = tc.alg\n      const payload = { message: 'hello world' }\n      const keyPair = await generateRSAKey(tc.hash)\n\n      const tok = await JWT.sign(payload, keyPair.privateKey, alg)\n      expect(await JWT.verify(tok, keyPair.privateKey, alg)).toEqual(payload)\n      expect(await JWT.verify(tok, keyPair.publicKey, alg)).toEqual(payload)\n    })\n  }\n\n  const psTestCases = [\n    {\n      alg: AlgorithmTypes.PS256,\n      hash: 'SHA-256',\n    },\n    {\n      alg: AlgorithmTypes.PS384,\n      hash: 'SHA-384',\n    },\n    {\n      alg: AlgorithmTypes.PS512,\n      hash: 'SHA-512',\n    },\n  ]\n  for (const tc of psTestCases) {\n    it(`${tc.alg} sign & verify`, async () => {\n      const alg = tc.alg\n      const payload = { message: 'hello world' }\n      const keyPair = await generateRSAPSSKey(tc.hash)\n      const pemPrivateKey = await exportPEMPrivateKey(keyPair.privateKey)\n      const pemPublicKey = await exportPEMPublicKey(keyPair.publicKey)\n      const jwkPublicKey = await exportJWK(keyPair.publicKey)\n\n      const tok = await JWT.sign(payload, pemPrivateKey, alg)\n      expect(await JWT.verify(tok, pemPublicKey, alg)).toEqual(payload)\n      expect(await JWT.verify(tok, pemPrivateKey, alg)).toEqual(payload)\n      expect(await JWT.verify(tok, jwkPublicKey, alg)).toEqual(payload)\n\n      const keyPair2 = await generateRSAPSSKey(tc.hash)\n      const unexpectedPemPublicKey = await exportPEMPublicKey(keyPair2.publicKey)\n\n      let err = null\n      let authorized\n      try {\n        authorized = await JWT.verify(tok, unexpectedPemPublicKey, alg)\n      } catch (e) {\n        err = e\n      }\n      expect(authorized).toBeUndefined()\n      expect(err instanceof JwtTokenSignatureMismatched).toBe(true)\n    })\n\n    it(`${tc.alg} sign & verify w/ CryptoKey`, async () => {\n      const alg = tc.alg\n      const payload = { message: 'hello world' }\n      const keyPair = await generateRSAPSSKey(tc.hash)\n\n      const tok = await JWT.sign(payload, keyPair.privateKey, alg)\n      expect(await JWT.verify(tok, keyPair.privateKey, alg)).toEqual(payload)\n      expect(await JWT.verify(tok, keyPair.publicKey, alg)).toEqual(payload)\n    })\n  }\n\n  const esTestCases = [\n    {\n      alg: AlgorithmTypes.ES256,\n      namedCurve: 'P-256',\n    },\n    {\n      alg: AlgorithmTypes.ES384,\n      namedCurve: 'P-384',\n    },\n    {\n      alg: AlgorithmTypes.ES512,\n      namedCurve: 'P-521',\n    },\n  ]\n  for (const tc of esTestCases) {\n    it(`${tc.alg} sign & verify`, async () => {\n      const alg = tc.alg\n      const payload = { message: 'hello world' }\n      const keyPair = await generateECDSAKey(tc.namedCurve)\n      const pemPrivateKey = await exportPEMPrivateKey(keyPair.privateKey)\n      const pemPublicKey = await exportPEMPublicKey(keyPair.publicKey)\n      const jwkPublicKey = await exportJWK(keyPair.publicKey)\n\n      const tok = await JWT.sign(payload, pemPrivateKey, alg)\n      expect(await JWT.verify(tok, pemPublicKey, alg)).toEqual(payload)\n      expect(await JWT.verify(tok, pemPrivateKey, alg)).toEqual(payload)\n      expect(await JWT.verify(tok, jwkPublicKey, alg)).toEqual(payload)\n\n      const keyPair2 = await generateECDSAKey(tc.namedCurve)\n      const unexpectedPemPublicKey = await exportPEMPublicKey(keyPair2.publicKey)\n\n      let err = null\n      let authorized\n      try {\n        authorized = await JWT.verify(tok, unexpectedPemPublicKey, alg)\n      } catch (e) {\n        err = e\n      }\n      expect(authorized).toBeUndefined()\n      expect(err instanceof JwtTokenSignatureMismatched).toBe(true)\n    })\n\n    it(`${tc.alg} sign & verify w/ CryptoKey`, async () => {\n      const alg = tc.alg\n      const payload = { message: 'hello world' }\n      const keyPair = await generateECDSAKey(tc.namedCurve)\n\n      const tok = await JWT.sign(payload, keyPair.privateKey, alg)\n      expect(await JWT.verify(tok, keyPair.privateKey, alg)).toEqual(payload)\n      expect(await JWT.verify(tok, keyPair.publicKey, alg)).toEqual(payload)\n    })\n  }\n\n  it('EdDSA sign & verify', async () => {\n    const alg = 'EdDSA'\n    const payload = { message: 'hello world' }\n    const keyPair = await generateEd25519Key()\n    const pemPrivateKey = await exportPEMPrivateKey(keyPair.privateKey)\n    const pemPublicKey = await exportPEMPublicKey(keyPair.publicKey)\n    const jwkPublicKey = await exportJWK(keyPair.publicKey)\n\n    const tok = await JWT.sign(payload, pemPrivateKey, alg)\n    expect(await JWT.verify(tok, pemPublicKey, alg)).toEqual(payload)\n    expect(await JWT.verify(tok, pemPrivateKey, alg)).toEqual(payload)\n    expect(await JWT.verify(tok, jwkPublicKey, alg)).toEqual(payload)\n\n    const keyPair2 = await generateEd25519Key()\n    const unexpectedPemPublicKey = await exportPEMPublicKey(keyPair2.publicKey)\n\n    let err = null\n    let authorized\n    try {\n      authorized = await JWT.verify(tok, unexpectedPemPublicKey, alg)\n    } catch (e) {\n      err = e\n    }\n    expect(authorized).toBeUndefined()\n    expect(err instanceof JwtTokenSignatureMismatched).toBe(true)\n  })\n\n  it('EdDSA sign & verify w/ CryptoKey', async () => {\n    const alg = 'EdDSA'\n    const payload = { message: 'hello world' }\n    const keyPair = await generateEd25519Key()\n\n    const tok = await JWT.sign(payload, keyPair.privateKey, alg)\n    expect(await JWT.verify(tok, keyPair.privateKey, alg)).toEqual(payload)\n    expect(await JWT.verify(tok, keyPair.publicKey, alg)).toEqual(payload)\n  })\n})\n\ndescribe('verifyWithJwks header.alg fallback', () => {\n  it('Should use header.alg as fallback when matchingKey.alg is missing', async () => {\n    // Setup: Create a JWT signed with RS256 (asymmetric algorithm)\n    const payload = { message: 'hello world' }\n    const headerAlg = 'RS256'\n    const kid = 'dummy'\n\n    // Generate RSA key pair\n    const keyPair = await crypto.subtle.generateKey(\n      {\n        name: 'RSASSA-PKCS1-v1_5',\n        modulusLength: 2048,\n        publicExponent: new Uint8Array([1, 0, 1]),\n        hash: 'SHA-256',\n      },\n      true,\n      ['sign', 'verify']\n    )\n\n    // Create JWT (signed with RS256)\n    const header = { alg: headerAlg, typ: 'JWT', kid }\n    const encode = (obj: object) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)\n    const encodedHeader = encode(header)\n    const encodedPayload = encode(payload)\n    const signingInput = `${encodedHeader}.${encodedPayload}`\n\n    // Sign with private key\n    const signatureBuffer = await signing(\n      keyPair.privateKey,\n      headerAlg,\n      utf8Encoder.encode(signingInput)\n    )\n    const signature = encodeBase64Url(signatureBuffer)\n\n    const token = `${encodedHeader}.${encodedPayload}.${signature}`\n\n    // Export public key as JWK without alg property\n    const jwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey)\n    const keyWithoutAlg = {\n      ...jwk,\n      kid,\n      use: 'sig',\n    }\n    delete keyWithoutAlg.alg // intentionally omit alg\n\n    const keys = [keyWithoutAlg]\n\n    // Execute: Verify the JWT token signed with RS256 with allowedAlgorithms required\n    const result = await verifyWithJwks(token, { keys, allowedAlgorithms: ['RS256'] })\n\n    // If verification succeeds, it means header.alg was used\n    expect(result).toEqual(payload)\n  })\n})\n\ndescribe('verifyWithJwks security', () => {\n  it('Should reject symmetric algorithm HS256', async () => {\n    const payload = { message: 'hello world' }\n    const secret = 'secret'\n    const kid = 'dummy'\n\n    // Create JWT (signed with HS256)\n    const header = { alg: 'HS256', typ: 'JWT', kid }\n    const encode = (obj: object) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)\n    const encodedHeader = encode(header)\n    const encodedPayload = encode(payload)\n    const signingInput = `${encodedHeader}.${encodedPayload}`\n\n    const signatureBuffer = await signing(secret, 'HS256', utf8Encoder.encode(signingInput))\n    const signature = encodeBase64Url(signatureBuffer)\n\n    const token = `${encodedHeader}.${encodedPayload}.${signature}`\n\n    const keys = [\n      {\n        kty: 'oct',\n        kid,\n        k: encodeBase64Url(utf8Encoder.encode(secret).buffer),\n        use: 'sig',\n        alg: 'HS256',\n      },\n    ]\n\n    // HS256 is rejected before allowedAlgorithms check (symmetric algorithm rejection)\n    await expect(verifyWithJwks(token, { keys, allowedAlgorithms: ['RS256'] })).rejects.toThrow(\n      JwtSymmetricAlgorithmNotAllowed\n    )\n  })\n\n  it('Should reject symmetric algorithm HS384', async () => {\n    const payload = { message: 'hello world' }\n    const secret = 'secret'\n    const kid = 'dummy'\n\n    const header = { alg: 'HS384', typ: 'JWT', kid }\n    const encode = (obj: object) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)\n    const encodedHeader = encode(header)\n    const encodedPayload = encode(payload)\n    const signingInput = `${encodedHeader}.${encodedPayload}`\n\n    const signatureBuffer = await signing(secret, 'HS384', utf8Encoder.encode(signingInput))\n    const signature = encodeBase64Url(signatureBuffer)\n\n    const token = `${encodedHeader}.${encodedPayload}.${signature}`\n\n    const keys = [\n      {\n        kty: 'oct',\n        kid,\n        k: encodeBase64Url(utf8Encoder.encode(secret).buffer),\n        use: 'sig',\n        alg: 'HS384',\n      },\n    ]\n\n    // HS384 is rejected before allowedAlgorithms check (symmetric algorithm rejection)\n    await expect(verifyWithJwks(token, { keys, allowedAlgorithms: ['RS256'] })).rejects.toThrow(\n      JwtSymmetricAlgorithmNotAllowed\n    )\n  })\n\n  it('Should reject symmetric algorithm HS512', async () => {\n    const payload = { message: 'hello world' }\n    const secret = 'secret'\n    const kid = 'dummy'\n\n    const header = { alg: 'HS512', typ: 'JWT', kid }\n    const encode = (obj: object) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)\n    const encodedHeader = encode(header)\n    const encodedPayload = encode(payload)\n    const signingInput = `${encodedHeader}.${encodedPayload}`\n\n    const signatureBuffer = await signing(secret, 'HS512', utf8Encoder.encode(signingInput))\n    const signature = encodeBase64Url(signatureBuffer)\n\n    const token = `${encodedHeader}.${encodedPayload}.${signature}`\n\n    const keys = [\n      {\n        kty: 'oct',\n        kid,\n        k: encodeBase64Url(utf8Encoder.encode(secret).buffer),\n        use: 'sig',\n        alg: 'HS512',\n      },\n    ]\n\n    // HS512 is rejected before allowedAlgorithms check (symmetric algorithm rejection)\n    await expect(verifyWithJwks(token, { keys, allowedAlgorithms: ['RS256'] })).rejects.toThrow(\n      JwtSymmetricAlgorithmNotAllowed\n    )\n  })\n\n  it('Should reject algorithm mismatch between JWK and JWT header', async () => {\n    const payload = { message: 'hello world' }\n    const kid = 'dummy'\n\n    // Generate RS256 key pair\n    const keyPair = await crypto.subtle.generateKey(\n      {\n        name: 'RSASSA-PKCS1-v1_5',\n        modulusLength: 2048,\n        publicExponent: new Uint8Array([1, 0, 1]),\n        hash: 'SHA-256',\n      },\n      true,\n      ['sign', 'verify']\n    )\n\n    // Create JWT with RS384 in header (mismatch with RS256 key)\n    const header = { alg: 'RS384', typ: 'JWT', kid }\n    const encode = (obj: object) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)\n    const encodedHeader = encode(header)\n    const encodedPayload = encode(payload)\n    const signingInput = `${encodedHeader}.${encodedPayload}`\n\n    // Sign with RS256 key (but header says RS384)\n    const signatureBuffer = await signing(\n      keyPair.privateKey,\n      'RS256',\n      utf8Encoder.encode(signingInput)\n    )\n    const signature = encodeBase64Url(signatureBuffer)\n\n    const token = `${encodedHeader}.${encodedPayload}.${signature}`\n\n    // Export public key as JWK with RS256 alg\n    const jwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey)\n    const keyWithAlg = {\n      ...jwk,\n      kid,\n      use: 'sig',\n      alg: 'RS256', // JWK says RS256, but header says RS384\n    }\n\n    const keys = [keyWithAlg]\n\n    // RS384 in header doesn't match RS256 in JWK alg field\n    await expect(verifyWithJwks(token, { keys, allowedAlgorithms: ['RS384'] })).rejects.toThrow(\n      JwtAlgorithmMismatch\n    )\n  })\n\n  it('Should allow asymmetric algorithm RS256 with matching alg', async () => {\n    const payload = { message: 'hello world' }\n    const kid = 'dummy'\n\n    const keyPair = await crypto.subtle.generateKey(\n      {\n        name: 'RSASSA-PKCS1-v1_5',\n        modulusLength: 2048,\n        publicExponent: new Uint8Array([1, 0, 1]),\n        hash: 'SHA-256',\n      },\n      true,\n      ['sign', 'verify']\n    )\n\n    const header = { alg: 'RS256', typ: 'JWT', kid }\n    const encode = (obj: object) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)\n    const encodedHeader = encode(header)\n    const encodedPayload = encode(payload)\n    const signingInput = `${encodedHeader}.${encodedPayload}`\n\n    const signatureBuffer = await signing(\n      keyPair.privateKey,\n      'RS256',\n      utf8Encoder.encode(signingInput)\n    )\n    const signature = encodeBase64Url(signatureBuffer)\n\n    const token = `${encodedHeader}.${encodedPayload}.${signature}`\n\n    const jwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey)\n    const keyWithAlg = {\n      ...jwk,\n      kid,\n      use: 'sig',\n      alg: 'RS256',\n    }\n\n    const keys = [keyWithAlg]\n\n    const result = await verifyWithJwks(token, { keys, allowedAlgorithms: ['RS256'] })\n    expect(result).toEqual(payload)\n  })\n\n  it('Should reject algorithm confusion attack (HS256 with RSA public key)', async () => {\n    // This test simulates the algorithm confusion attack where an attacker\n    // tries to use HS256 with a public RSA key as the HMAC secret\n    const payload = { message: 'hello world' }\n    const kid = 'dummy'\n\n    // Generate RSA key pair (normally used for RS256)\n    const keyPair = await crypto.subtle.generateKey(\n      {\n        name: 'RSASSA-PKCS1-v1_5',\n        modulusLength: 2048,\n        publicExponent: new Uint8Array([1, 0, 1]),\n        hash: 'SHA-256',\n      },\n      true,\n      ['sign', 'verify']\n    )\n\n    // Attacker creates a JWT with HS256 in header\n    const header = { alg: 'HS256', typ: 'JWT', kid }\n    const encode = (obj: object) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)\n    const encodedHeader = encode(header)\n    const encodedPayload = encode(payload)\n    const signingInput = `${encodedHeader}.${encodedPayload}`\n\n    // Export public key as JWK (which would be used in a real attack)\n    const jwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey)\n\n    // Attacker would sign with the public key bytes as HMAC secret\n    // We don't need to actually sign for this test - just verify rejection\n    const signatureBuffer = await signing('fake-secret', 'HS256', utf8Encoder.encode(signingInput))\n    const signature = encodeBase64Url(signatureBuffer)\n\n    const token = `${encodedHeader}.${encodedPayload}.${signature}`\n\n    // JWK has RS256 alg, but JWT header has HS256 (confusion attack)\n    const keyWithAlg = {\n      ...jwk,\n      kid,\n      use: 'sig',\n      alg: 'RS256',\n    }\n\n    const keys = [keyWithAlg]\n\n    // Should reject because HS256 is a symmetric algorithm (checked before allowedAlgorithms)\n    await expect(verifyWithJwks(token, { keys, allowedAlgorithms: ['RS256'] })).rejects.toThrow(\n      JwtSymmetricAlgorithmNotAllowed\n    )\n  })\n})\n\ndescribe('verifyWithJwks algorithm whitelist', () => {\n  it('Should reject algorithm not in whitelist', async () => {\n    const payload = { message: 'hello world' }\n    const kid = 'dummy'\n\n    // Generate RS256 key pair\n    const keyPair = await crypto.subtle.generateKey(\n      {\n        name: 'RSASSA-PKCS1-v1_5',\n        modulusLength: 2048,\n        publicExponent: new Uint8Array([1, 0, 1]),\n        hash: 'SHA-256',\n      },\n      true,\n      ['sign', 'verify']\n    )\n\n    // Create JWT with RS256\n    const header = { alg: 'RS256', typ: 'JWT', kid }\n    const encode = (obj: object) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)\n    const encodedHeader = encode(header)\n    const encodedPayload = encode(payload)\n    const signingInput = `${encodedHeader}.${encodedPayload}`\n\n    const signatureBuffer = await signing(\n      keyPair.privateKey,\n      'RS256',\n      utf8Encoder.encode(signingInput)\n    )\n    const signature = encodeBase64Url(signatureBuffer)\n\n    const token = `${encodedHeader}.${encodedPayload}.${signature}`\n\n    const jwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey)\n    const keyWithAlg = {\n      ...jwk,\n      kid,\n      use: 'sig',\n      alg: 'RS256',\n    }\n\n    const keys = [keyWithAlg]\n\n    // RS256 is not in the whitelist (only ES256 is allowed)\n    await expect(verifyWithJwks(token, { keys, allowedAlgorithms: ['ES256'] })).rejects.toThrow(\n      JwtAlgorithmNotAllowed\n    )\n  })\n\n  it('Should accept algorithm in whitelist', async () => {\n    const payload = { message: 'hello world' }\n    const kid = 'dummy'\n\n    // Generate RS256 key pair\n    const keyPair = await crypto.subtle.generateKey(\n      {\n        name: 'RSASSA-PKCS1-v1_5',\n        modulusLength: 2048,\n        publicExponent: new Uint8Array([1, 0, 1]),\n        hash: 'SHA-256',\n      },\n      true,\n      ['sign', 'verify']\n    )\n\n    // Create JWT with RS256\n    const header = { alg: 'RS256', typ: 'JWT', kid }\n    const encode = (obj: object) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)\n    const encodedHeader = encode(header)\n    const encodedPayload = encode(payload)\n    const signingInput = `${encodedHeader}.${encodedPayload}`\n\n    const signatureBuffer = await signing(\n      keyPair.privateKey,\n      'RS256',\n      utf8Encoder.encode(signingInput)\n    )\n    const signature = encodeBase64Url(signatureBuffer)\n\n    const token = `${encodedHeader}.${encodedPayload}.${signature}`\n\n    const jwk = await crypto.subtle.exportKey('jwk', keyPair.publicKey)\n    const keyWithAlg = {\n      ...jwk,\n      kid,\n      use: 'sig',\n      alg: 'RS256',\n    }\n\n    const keys = [keyWithAlg]\n\n    // RS256 is in the whitelist\n    const result = await verifyWithJwks(token, { keys, allowedAlgorithms: ['RS256', 'ES256'] })\n    expect(result).toEqual(payload)\n  })\n\n  // Note: Tests for \"whitelist not specified\" and \"empty whitelist\" were removed\n  // because allowedAlgorithms is now required (not optional).\n  // This is a breaking change that enforces explicit algorithm specification for security.\n\n  it('Should reject symmetric algorithm (HS256) in JWT header', async () => {\n    // This test verifies that symmetric algorithms are rejected even when\n    // using verifyWithJwks with asymmetric algorithm whitelist.\n    // Note: HS256 cannot be added to allowedAlgorithms due to type constraints (AsymmetricAlgorithm[])\n    const payload = { message: 'hello world' }\n    const secret = 'secret'\n    const kid = 'dummy'\n\n    // Create JWT with HS256\n    const header = { alg: 'HS256', typ: 'JWT', kid }\n    const encode = (obj: object) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(obj)).buffer)\n    const encodedHeader = encode(header)\n    const encodedPayload = encode(payload)\n    const signingInput = `${encodedHeader}.${encodedPayload}`\n\n    const signatureBuffer = await signing(secret, 'HS256', utf8Encoder.encode(signingInput))\n    const signature = encodeBase64Url(signatureBuffer)\n\n    const token = `${encodedHeader}.${encodedPayload}.${signature}`\n\n    const keys = [\n      {\n        kty: 'oct',\n        kid,\n        k: encodeBase64Url(utf8Encoder.encode(secret).buffer),\n        use: 'sig',\n        alg: 'HS256',\n      },\n    ]\n\n    // HS256 in JWT header should be rejected as symmetric algorithm\n    // (symmetric algorithm check happens before allowedAlgorithms check)\n    await expect(verifyWithJwks(token, { keys, allowedAlgorithms: ['RS256'] })).rejects.toThrow(\n      JwtSymmetricAlgorithmNotAllowed\n    )\n  })\n})\n\ndescribe('verifyWithJwks key handling', () => {\n  it('Should not mutate provided keys when JWKS is fetched repeatedly', async () => {\n    const localKeys: HonoJsonWebKey[] = [\n      {\n        kty: 'RSA',\n        kid: 'local-key',\n        alg: 'RS256',\n        e: 'AQAB',\n        n: 'sXchAZo4YqB7f1_g8U9RVcdpShUMHbOWcZHhGXLiCFYI8aAizI0s5momkMumZ5qX6Ch12yvDqOiiMHDLecxB2S7RMyCV2wAPOQgpdnXl16rDpD6PEw24kTx5cDIeEJD7BqXc9Ejo4kKDAdAm8YGtS-wGGyRyvE4s46HoPazTA7k',\n        use: 'sig',\n      },\n    ]\n\n    const originalKeys = structuredClone(localKeys)\n    const originalFetch = globalThis.fetch\n    const header = Buffer.from(\n      JSON.stringify({ alg: 'RS256', typ: 'JWT', kid: 'unknown-key' })\n    ).toString('base64url')\n    const payload = Buffer.from(JSON.stringify({})).toString('base64url')\n    const token = `${header}.${payload}.x`\n\n    try {\n      globalThis.fetch = (async () => {\n        return new Response(\n          JSON.stringify({\n            keys: [\n              { ...localKeys[0], kid: 'remote-key' },\n              { ...localKeys[0], kid: 'remote-key' },\n            ],\n          }),\n          { status: 200, headers: { 'content-type': 'application/json' } }\n        )\n      }) as typeof globalThis.fetch\n\n      await expect(\n        verifyWithJwks(token, {\n          keys: localKeys,\n          jwks_uri: 'https://example.invalid/.well-known/jwks.json',\n          allowedAlgorithms: ['RS256'],\n        })\n      ).rejects.toThrow(JwtTokenInvalid)\n\n      await expect(\n        verifyWithJwks(token, {\n          keys: localKeys,\n          jwks_uri: 'https://example.invalid/.well-known/jwks.json',\n          allowedAlgorithms: ['RS256'],\n        })\n      ).rejects.toThrow(JwtTokenInvalid)\n    } finally {\n      globalThis.fetch = originalFetch\n    }\n\n    expect(localKeys).toEqual(originalKeys)\n  })\n})\n\nasync function exportPEMPrivateKey(key: CryptoKey): Promise<string> {\n  const exported = await crypto.subtle.exportKey('pkcs8', key)\n  const pem = `-----BEGIN PRIVATE KEY-----\\n${encodeBase64(exported)}\\n-----END PRIVATE KEY-----`\n  return pem\n}\n\nasync function exportPEMPublicKey(key: CryptoKey): Promise<string> {\n  const exported = await crypto.subtle.exportKey('spki', key)\n  const pem = `-----BEGIN PUBLIC KEY-----\\n${encodeBase64(exported)}\\n-----END PUBLIC KEY-----`\n  return pem\n}\n\nasync function exportJWK(key: CryptoKey): Promise<JsonWebKey> {\n  return await crypto.subtle.exportKey('jwk', key)\n}\n\nasync function generateRSAKey(hash: string): Promise<CryptoKeyPair> {\n  return await crypto.subtle.generateKey(\n    {\n      hash,\n      modulusLength: 2048,\n      publicExponent: new Uint8Array([1, 0, 1]),\n      name: 'RSASSA-PKCS1-v1_5',\n    },\n    true,\n    ['sign', 'verify']\n  )\n}\n\nasync function generateRSAPSSKey(hash: string): Promise<CryptoKeyPair> {\n  return await crypto.subtle.generateKey(\n    {\n      hash,\n      modulusLength: 2048,\n      publicExponent: new Uint8Array([1, 0, 1]),\n      name: 'RSA-PSS',\n    },\n    true,\n    ['sign', 'verify']\n  )\n}\n\nasync function generateECDSAKey(namedCurve: string): Promise<CryptoKeyPair> {\n  return await crypto.subtle.generateKey(\n    {\n      name: 'ECDSA',\n      namedCurve,\n    },\n    true,\n    ['sign', 'verify']\n  )\n}\n\nasync function generateEd25519Key(): Promise<CryptoKeyPair> {\n  return await crypto.subtle.generateKey(\n    {\n      name: 'Ed25519',\n      namedCurve: 'Ed25519',\n    },\n    true,\n    ['sign', 'verify']\n  )\n}\n\ndescribe('Security: Algorithm Confusion Attack Prevention', () => {\n  it('Should throw JwtAlgorithmRequired error when alg is not specified in verify()', async () => {\n    const payload = { message: 'hello world' }\n    const secret = 'a-secret'\n    const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS256)\n\n    let err: JwtAlgorithmRequired | undefined\n    let authorized\n    try {\n      // @ts-expect-error - intentionally testing without alg parameter\n      authorized = await JWT.verify(tok, secret)\n    } catch (e) {\n      err = e as JwtAlgorithmRequired\n    }\n    expect(err).toBeInstanceOf(JwtAlgorithmRequired)\n    expect(err?.message).toBe('JWT verification requires \"alg\" option to be specified')\n    expect(authorized).toBeUndefined()\n  })\n\n  it('Should throw JwtAlgorithmRequired error when alg is undefined in options object', async () => {\n    const payload = { message: 'hello world' }\n    const secret = 'a-secret'\n    const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS256)\n\n    let err: JwtAlgorithmRequired | undefined\n    let authorized\n    try {\n      // @ts-expect-error - intentionally testing with undefined alg\n      authorized = await JWT.verify(tok, secret, { alg: undefined })\n    } catch (e) {\n      err = e as JwtAlgorithmRequired\n    }\n    expect(err).toBeInstanceOf(JwtAlgorithmRequired)\n    expect(authorized).toBeUndefined()\n  })\n\n  it('Should prevent algorithm confusion attack (RS256 token verified with HS256 using public key)', async () => {\n    // Generate RSA key pair\n    const keyPair = await crypto.subtle.generateKey(\n      {\n        name: 'RSASSA-PKCS1-v1_5',\n        modulusLength: 2048,\n        publicExponent: new Uint8Array([1, 0, 1]),\n        hash: 'SHA-256',\n      },\n      true,\n      ['sign', 'verify']\n    )\n\n    const payload = { message: 'hello world' }\n\n    // Sign token with RS256 using private key\n    const tok = await JWT.sign(payload, keyPair.privateKey, AlgorithmTypes.RS256)\n\n    // Verify should succeed with RS256 and public key\n    const verified = await JWT.verify(tok, keyPair.publicKey, AlgorithmTypes.RS256)\n    expect(verified).toEqual(payload)\n\n    // Attempting to verify with HS256 using the public key should fail with algorithm mismatch\n    // This simulates the algorithm confusion attack scenario\n    let err: Error | undefined\n    let maliciousResult\n    try {\n      maliciousResult = await JWT.verify(tok, keyPair.publicKey, AlgorithmTypes.HS256)\n    } catch (e) {\n      err = e as Error\n    }\n    // The verification should fail because the header.alg (RS256) doesn't match options.alg (HS256)\n    expect(maliciousResult).toBeUndefined()\n    expect(err).toBeInstanceOf(JwtAlgorithmMismatch)\n  })\n\n  it('Should throw JwtAlgorithmMismatch when header.alg does not match options.alg', async () => {\n    const payload = { message: 'hello world' }\n    const secret = 'a-secret'\n\n    // Sign with HS512\n    const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS512)\n\n    // Try to verify with HS256 (wrong algorithm)\n    let err: Error | undefined\n    let result\n    try {\n      result = await JWT.verify(tok, secret, AlgorithmTypes.HS256)\n    } catch (e) {\n      err = e as Error\n    }\n\n    expect(result).toBeUndefined()\n    expect(err).toBeInstanceOf(JwtAlgorithmMismatch)\n  })\n\n  it('Should require explicit alg specification to prevent default fallback attack', async () => {\n    const payload = { message: 'hello world' }\n    const secret = 'a-secret'\n\n    // Create a token with HS256\n    const tok = await JWT.sign(payload, secret, AlgorithmTypes.HS256)\n\n    // Verify with explicit HS256 should work\n    const verified = await JWT.verify(tok, secret, AlgorithmTypes.HS256)\n    expect(verified).toEqual(payload)\n\n    // Verify without alg should throw error (no default fallback)\n    let err: Error | undefined\n    try {\n      // @ts-expect-error - intentionally testing without alg parameter\n      await JWT.verify(tok, secret)\n    } catch (e) {\n      err = e as Error\n    }\n    expect(err).toBeInstanceOf(JwtAlgorithmRequired)\n  })\n})\n\ndescribe('JWT decode token format validation', () => {\n  it('decode should throw JwtTokenInvalid for token with 2 parts', () => {\n    const malformed = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8ifQ'\n    expect(() => JWT.decode(malformed)).toThrow(JwtTokenInvalid)\n  })\n\n  it('decode should throw JwtTokenInvalid for token with 1 part', () => {\n    expect(() => JWT.decode('eyJhbGciOiJIUzI1NiJ9')).toThrow(JwtTokenInvalid)\n  })\n\n  it('decode should throw JwtTokenInvalid for token with 4 parts', () => {\n    const fourParts = 'a.b.c.d'\n    expect(() => JWT.decode(fourParts)).toThrow(JwtTokenInvalid)\n  })\n\n  it('decode should throw JwtTokenInvalid for empty string', () => {\n    expect(() => JWT.decode('')).toThrow(JwtTokenInvalid)\n  })\n\n  it('decodeHeader should throw JwtTokenInvalid for token with 2 parts', () => {\n    const malformed = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXNzYWdlIjoiaGVsbG8ifQ'\n    expect(() => JWT.decodeHeader(malformed)).toThrow(JwtTokenInvalid)\n  })\n\n  it('decodeHeader should throw JwtTokenInvalid for empty string', () => {\n    expect(() => JWT.decodeHeader('')).toThrow(JwtTokenInvalid)\n  })\n\n  it('decode should work for valid 3-part token', async () => {\n    const secret = 'a-secret'\n    const tok = await JWT.sign({ message: 'hello' }, secret, AlgorithmTypes.HS256)\n    const decoded = JWT.decode(tok)\n    expect(decoded.header.alg).toBe('HS256')\n    expect(decoded.payload).toEqual({ message: 'hello' })\n  })\n\n  it('decodeHeader should work for valid 3-part token', async () => {\n    const secret = 'a-secret'\n    const tok = await JWT.sign({ message: 'hello' }, secret, AlgorithmTypes.HS256)\n    const header = JWT.decodeHeader(tok)\n    expect(header.alg).toBe('HS256')\n    expect(header.typ).toBe('JWT')\n  })\n})\n"
  },
  {
    "path": "src/utils/jwt/jwt.ts",
    "content": "/**\n * @module\n * JSON Web Token (JWT)\n * https://datatracker.ietf.org/doc/html/rfc7519\n */\n\nimport { decodeBase64Url, encodeBase64Url } from '../../utils/encode'\nimport { AlgorithmTypes } from './jwa'\nimport type { AsymmetricAlgorithm, SignatureAlgorithm, SymmetricAlgorithm } from './jwa'\nimport { signing, verifying } from './jws'\nimport type { HonoJsonWebKey, SignatureKey } from './jws'\nimport {\n  JwtAlgorithmMismatch,\n  JwtAlgorithmNotAllowed,\n  JwtAlgorithmRequired,\n  JwtHeaderInvalid,\n  JwtHeaderRequiresKid,\n  JwtPayloadRequiresAud,\n  JwtSymmetricAlgorithmNotAllowed,\n  JwtTokenAudience,\n  JwtTokenExpired,\n  JwtTokenInvalid,\n  JwtTokenIssuedAt,\n  JwtTokenIssuer,\n  JwtTokenNotBefore,\n  JwtTokenSignatureMismatched,\n} from './types'\nimport type { JWTPayload } from './types'\nimport { utf8Decoder, utf8Encoder } from './utf8'\n\nconst encodeJwtPart = (part: unknown): string =>\n  encodeBase64Url(utf8Encoder.encode(JSON.stringify(part)).buffer).replace(/=/g, '')\nconst encodeSignaturePart = (buf: ArrayBufferLike): string => encodeBase64Url(buf).replace(/=/g, '')\n\nconst decodeJwtPart = (part: string): TokenHeader | JWTPayload | undefined =>\n  JSON.parse(utf8Decoder.decode(decodeBase64Url(part)))\n\nexport interface TokenHeader {\n  alg: SignatureAlgorithm\n  typ?: 'JWT'\n  kid?: string\n}\n\nexport function isTokenHeader(obj: unknown): obj is TokenHeader {\n  if (typeof obj === 'object' && obj !== null) {\n    const objWithAlg = obj as { [key: string]: unknown }\n    return (\n      'alg' in objWithAlg &&\n      Object.values(AlgorithmTypes).includes(objWithAlg.alg as AlgorithmTypes) &&\n      (!('typ' in objWithAlg) || objWithAlg.typ === 'JWT')\n    )\n  }\n  return false\n}\n\nexport const sign = async (\n  payload: JWTPayload,\n  privateKey: SignatureKey,\n  alg: SignatureAlgorithm = 'HS256'\n): Promise<string> => {\n  const encodedPayload = encodeJwtPart(payload)\n  let encodedHeader\n  if (typeof privateKey === 'object' && 'alg' in privateKey) {\n    alg = privateKey.alg as SignatureAlgorithm\n    encodedHeader = encodeJwtPart({ alg, typ: 'JWT', kid: privateKey.kid })\n  } else {\n    encodedHeader = encodeJwtPart({ alg, typ: 'JWT' })\n  }\n\n  const partialToken = `${encodedHeader}.${encodedPayload}`\n\n  const signaturePart = await signing(privateKey, alg, utf8Encoder.encode(partialToken))\n  const signature = encodeSignaturePart(signaturePart)\n\n  return `${partialToken}.${signature}`\n}\n\nexport type VerifyOptions = {\n  /** The expected issuer used for verifying the token */\n  iss?: string | RegExp\n  /** Verify the `nbf` claim (default: `true`) */\n  nbf?: boolean\n  /** Verify the `exp` claim (default: `true`) */\n  exp?: boolean\n  /** Verify the `iat` claim (default: `true`) */\n  iat?: boolean\n  /** Acceptable audience(s) for the token */\n  aud?: string | string[] | RegExp\n}\n\nexport type VerifyOptionsWithAlg = {\n  /** The algorithm used for decoding the token */\n  alg: SignatureAlgorithm\n} & VerifyOptions\n\nexport const verify = async (\n  token: string,\n  publicKey: SignatureKey,\n  algOrOptions: SignatureAlgorithm | VerifyOptionsWithAlg\n): Promise<JWTPayload> => {\n  if (!algOrOptions) {\n    throw new JwtAlgorithmRequired()\n  }\n\n  const {\n    alg,\n    iss,\n    nbf = true,\n    exp = true,\n    iat = true,\n    aud,\n  } = typeof algOrOptions === 'string' ? { alg: algOrOptions } : algOrOptions\n\n  if (!alg) {\n    throw new JwtAlgorithmRequired()\n  }\n\n  const tokenParts = token.split('.')\n  if (tokenParts.length !== 3) {\n    throw new JwtTokenInvalid(token)\n  }\n\n  const { header, payload } = decode(token)\n  if (!isTokenHeader(header)) {\n    throw new JwtHeaderInvalid(header)\n  }\n  if (header.alg !== alg) {\n    throw new JwtAlgorithmMismatch(alg, header.alg)\n  }\n  const now = Math.floor(Date.now() / 1000)\n  if (nbf && payload.nbf && payload.nbf > now) {\n    throw new JwtTokenNotBefore(token)\n  }\n  if (exp && payload.exp && payload.exp <= now) {\n    throw new JwtTokenExpired(token)\n  }\n  if (iat && payload.iat && now < payload.iat) {\n    throw new JwtTokenIssuedAt(now, payload.iat)\n  }\n  if (iss) {\n    if (!payload.iss) {\n      throw new JwtTokenIssuer(iss, null)\n    }\n    if (typeof iss === 'string' && payload.iss !== iss) {\n      throw new JwtTokenIssuer(iss, payload.iss)\n    }\n    if (iss instanceof RegExp && !iss.test(payload.iss)) {\n      throw new JwtTokenIssuer(iss, payload.iss)\n    }\n  }\n\n  if (aud) {\n    if (!payload.aud) {\n      throw new JwtPayloadRequiresAud(payload)\n    }\n\n    const audiences = Array.isArray(payload.aud) ? payload.aud : [payload.aud]\n    const matched = audiences.some((payloadAud): boolean =>\n      aud instanceof RegExp\n        ? aud.test(payloadAud)\n        : typeof aud === 'string'\n          ? payloadAud === aud\n          : Array.isArray(aud) && aud.includes(payloadAud)\n    )\n    if (!matched) {\n      throw new JwtTokenAudience(aud, payload.aud)\n    }\n  }\n\n  const headerPayload = token.substring(0, token.lastIndexOf('.'))\n  const verified = await verifying(\n    publicKey,\n    alg,\n    decodeBase64Url(tokenParts[2]),\n    utf8Encoder.encode(headerPayload)\n  )\n  if (!verified) {\n    throw new JwtTokenSignatureMismatched(token)\n  }\n\n  return payload\n}\n\n// Symmetric algorithms that are not allowed for JWK verification\nconst symmetricAlgorithms: SymmetricAlgorithm[] = [\n  AlgorithmTypes.HS256,\n  AlgorithmTypes.HS384,\n  AlgorithmTypes.HS512,\n]\n\nexport const verifyWithJwks = async (\n  token: string,\n  options: {\n    keys?: HonoJsonWebKey[]\n    jwks_uri?: string\n    verification?: VerifyOptions\n    allowedAlgorithms: readonly AsymmetricAlgorithm[]\n  },\n  init?: RequestInit\n): Promise<JWTPayload> => {\n  const verifyOpts = options.verification || {}\n\n  const header = decodeHeader(token)\n\n  if (!isTokenHeader(header)) {\n    throw new JwtHeaderInvalid(header)\n  }\n  if (!header.kid) {\n    throw new JwtHeaderRequiresKid(header)\n  }\n\n  // Reject symmetric algorithms (HS256, HS384, HS512) to prevent algorithm confusion attacks\n  if (symmetricAlgorithms.includes(header.alg as SymmetricAlgorithm)) {\n    throw new JwtSymmetricAlgorithmNotAllowed(header.alg)\n  }\n\n  // Validate against allowed algorithms\n  if (!options.allowedAlgorithms.includes(header.alg as AsymmetricAlgorithm)) {\n    throw new JwtAlgorithmNotAllowed(header.alg, options.allowedAlgorithms)\n  }\n\n  let verifyKeys = options.keys ? [...options.keys] : undefined\n\n  if (options.jwks_uri) {\n    const response = await fetch(options.jwks_uri, init)\n    if (!response.ok) {\n      throw new Error(`failed to fetch JWKS from ${options.jwks_uri}`)\n    }\n    const data = (await response.json()) as { keys?: JsonWebKey[] }\n    if (!data.keys) {\n      throw new Error('invalid JWKS response. \"keys\" field is missing')\n    }\n    if (!Array.isArray(data.keys)) {\n      throw new Error('invalid JWKS response. \"keys\" field is not an array')\n    }\n    verifyKeys ??= []\n    verifyKeys.push(...(data.keys as HonoJsonWebKey[]))\n  } else if (!verifyKeys) {\n    throw new Error('verifyWithJwks requires options for either \"keys\" or \"jwks_uri\" or both')\n  }\n\n  const matchingKey = verifyKeys.find((key) => key.kid === header.kid)\n  if (!matchingKey) {\n    throw new JwtTokenInvalid(token)\n  }\n\n  // Verify that JWK's alg matches JWT header's alg when JWK has alg field\n  if (matchingKey.alg && matchingKey.alg !== header.alg) {\n    throw new JwtAlgorithmMismatch(matchingKey.alg, header.alg)\n  }\n\n  return await verify(token, matchingKey, {\n    alg: header.alg,\n    ...verifyOpts,\n  })\n}\n\nexport const decode = (token: string): { header: TokenHeader; payload: JWTPayload } => {\n  const parts = token.split('.')\n  if (parts.length !== 3) {\n    throw new JwtTokenInvalid(token)\n  }\n  try {\n    const header = decodeJwtPart(parts[0]) as TokenHeader\n    const payload = decodeJwtPart(parts[1]) as JWTPayload\n    return {\n      header,\n      payload,\n    }\n  } catch {\n    throw new JwtTokenInvalid(token)\n  }\n}\n\nexport const decodeHeader = (token: string): TokenHeader => {\n  const parts = token.split('.')\n  if (parts.length !== 3) {\n    throw new JwtTokenInvalid(token)\n  }\n  try {\n    return decodeJwtPart(parts[0]) as TokenHeader\n  } catch {\n    throw new JwtTokenInvalid(token)\n  }\n}\n"
  },
  {
    "path": "src/utils/jwt/types.ts",
    "content": "/**\n * @module\n * Type definitions for JWT utilities.\n */\n\nexport class JwtAlgorithmNotImplemented extends Error {\n  constructor(alg: string) {\n    super(`${alg} is not an implemented algorithm`)\n    this.name = 'JwtAlgorithmNotImplemented'\n  }\n}\n\nexport class JwtAlgorithmRequired extends Error {\n  constructor() {\n    super('JWT verification requires \"alg\" option to be specified')\n    this.name = 'JwtAlgorithmRequired'\n  }\n}\n\nexport class JwtAlgorithmMismatch extends Error {\n  constructor(expected: string, actual: string) {\n    super(`JWT algorithm mismatch: expected \"${expected}\", got \"${actual}\"`)\n    this.name = 'JwtAlgorithmMismatch'\n  }\n}\n\nexport class JwtTokenInvalid extends Error {\n  constructor(token: string) {\n    super(`invalid JWT token: ${token}`)\n    this.name = 'JwtTokenInvalid'\n  }\n}\n\nexport class JwtTokenNotBefore extends Error {\n  constructor(token: string) {\n    super(`token (${token}) is being used before it's valid`)\n    this.name = 'JwtTokenNotBefore'\n  }\n}\n\nexport class JwtTokenExpired extends Error {\n  constructor(token: string) {\n    super(`token (${token}) expired`)\n    this.name = 'JwtTokenExpired'\n  }\n}\n\nexport class JwtTokenIssuedAt extends Error {\n  constructor(currentTimestamp: number, iat: number) {\n    super(\n      `Invalid \"iat\" claim, must be a valid number lower than \"${currentTimestamp}\" (iat: \"${iat}\")`\n    )\n    this.name = 'JwtTokenIssuedAt'\n  }\n}\n\nexport class JwtTokenIssuer extends Error {\n  constructor(expected: string | RegExp, iss: string | null) {\n    super(`expected issuer \"${expected}\", got ${iss ? `\"${iss}\"` : 'none'} `)\n    this.name = 'JwtTokenIssuer'\n  }\n}\n\nexport class JwtHeaderInvalid extends Error {\n  constructor(header: object) {\n    super(`jwt header is invalid: ${JSON.stringify(header)}`)\n    this.name = 'JwtHeaderInvalid'\n  }\n}\n\nexport class JwtHeaderRequiresKid extends Error {\n  constructor(header: object) {\n    super(`required \"kid\" in jwt header: ${JSON.stringify(header)}`)\n    this.name = 'JwtHeaderRequiresKid'\n  }\n}\n\nexport class JwtSymmetricAlgorithmNotAllowed extends Error {\n  constructor(alg: string) {\n    super(`symmetric algorithm \"${alg}\" is not allowed for JWK verification`)\n    this.name = 'JwtSymmetricAlgorithmNotAllowed'\n  }\n}\n\nexport class JwtAlgorithmNotAllowed extends Error {\n  constructor(alg: string, allowedAlgorithms: readonly string[]) {\n    super(`algorithm \"${alg}\" is not in the allowed list: [${allowedAlgorithms.join(', ')}]`)\n    this.name = 'JwtAlgorithmNotAllowed'\n  }\n}\n\nexport class JwtTokenSignatureMismatched extends Error {\n  constructor(token: string) {\n    super(`token(${token}) signature mismatched`)\n    this.name = 'JwtTokenSignatureMismatched'\n  }\n}\n\nexport class JwtPayloadRequiresAud extends Error {\n  constructor(payload: object) {\n    super(`required \"aud\" in jwt payload: ${JSON.stringify(payload)}`)\n    this.name = 'JwtPayloadRequiresAud'\n  }\n}\n\nexport class JwtTokenAudience extends Error {\n  constructor(expected: string | string[] | RegExp, aud: string | string[]) {\n    super(\n      `expected audience \"${\n        Array.isArray(expected) ? expected.join(', ') : expected\n      }\", got \"${aud}\"`\n    )\n    this.name = 'JwtTokenAudience'\n  }\n}\n\nexport enum CryptoKeyUsage {\n  Encrypt = 'encrypt',\n  Decrypt = 'decrypt',\n  Sign = 'sign',\n  Verify = 'verify',\n  DeriveKey = 'deriveKey',\n  DeriveBits = 'deriveBits',\n  WrapKey = 'wrapKey',\n  UnwrapKey = 'unwrapKey',\n}\n\n/**\n * JWT Payload\n */\nexport type JWTPayload = {\n  [key: string]: unknown\n  /**\n   * The token is checked to ensure it has not expired.\n   */\n  exp?: number\n  /**\n   * The token is checked to ensure it is not being used before a specified time.\n   */\n  nbf?: number\n  /**\n   * The token is checked to ensure it is not issued in the future.\n   */\n  iat?: number\n  /**\n   * The token is checked to ensure it has been issued by a trusted issuer.\n   */\n  iss?: string\n\n  /**\n   * The token is checked to ensure it is intended for a specific audience.\n   */\n  aud?: string | string[]\n}\n\nexport type { HonoJsonWebKey } from './jws'\n"
  },
  {
    "path": "src/utils/jwt/utf8.ts",
    "content": "/**\n * @module\n * Functions for encoding/decoding UTF8.\n */\n\nexport const utf8Encoder: TextEncoder = new TextEncoder()\nexport const utf8Decoder: TextDecoder = new TextDecoder()\n"
  },
  {
    "path": "src/utils/mime.test.ts",
    "content": "import { getExtension, getMimeType } from './mime'\n\nconst mime = {\n  m3u8: 'application/vnd.apple.mpegurl',\n  ts: 'video/mp2t',\n}\n\ndescribe('mime', () => {\n  it('getMimeType', () => {\n    expect(getMimeType('hello.txt')).toBe('text/plain; charset=utf-8')\n    expect(getMimeType('hello.html')).toBe('text/html; charset=utf-8')\n    expect(getMimeType('hello.json')).toBe('application/json')\n    expect(getMimeType('favicon.ico')).toBe('image/x-icon')\n    expect(getMimeType('good.morning.hello.gif')).toBe('image/gif')\n    expect(getMimeType('site.webmanifest')).toBe('application/manifest+json')\n    expect(getMimeType('goodmorninghellogif')).toBeUndefined()\n    expect(getMimeType('indexjs.abcd')).toBeUndefined()\n    expect(getMimeType('IMAGE.JPG')).toBe('image/jpeg')\n  })\n\n  it('getMimeType with custom mime', () => {\n    expect(getMimeType('morning-routine.m3u8', mime)).toBe('application/vnd.apple.mpegurl')\n    expect(getMimeType('morning-routine1.ts', mime)).toBe('video/mp2t')\n    expect(getMimeType('readme.txt', mime)).toBeUndefined()\n  })\n\n  it('getExtension', () => {\n    expect(getExtension('audio/aac')).toBe('aac')\n    expect(getExtension('video/x-msvideo')).toBe('avi')\n    expect(getExtension('image/avif')).toBe('avif')\n    expect(getExtension('text/css')).toBe('css')\n    expect(getExtension('text/html')).toBe('htm')\n    expect(getExtension('image/jpeg')).toBe('jpeg')\n    expect(getExtension('text/javascript')).toBe('js')\n    expect(getExtension('application/json')).toBe('json')\n    expect(getExtension('audio/mpeg')).toBe('mp3')\n    expect(getExtension('video/mp4')).toBe('mp4')\n    expect(getExtension('application/pdf')).toBe('pdf')\n    expect(getExtension('image/png')).toBe('png')\n    expect(getExtension('application/zip')).toBe('zip')\n    expect(getExtension('non/existent')).toBeUndefined()\n  })\n})\n"
  },
  {
    "path": "src/utils/mime.ts",
    "content": "/**\n * @module\n * MIME utility.\n */\n\nexport const getMimeType = (\n  filename: string,\n  mimes: Record<string, string> = baseMimes\n): string | undefined => {\n  const regexp = /\\.([a-zA-Z0-9]+?)$/\n  const match = filename.match(regexp)\n  if (!match) {\n    return\n  }\n  let mimeType = mimes[match[1].toLowerCase()]\n  if (mimeType && mimeType.startsWith('text')) {\n    mimeType += '; charset=utf-8'\n  }\n  return mimeType\n}\n\nexport const getExtension = (mimeType: string): string | undefined => {\n  for (const ext in baseMimes) {\n    if (baseMimes[ext] === mimeType) {\n      return ext\n    }\n  }\n}\n\nexport { baseMimes as mimes }\n\n/**\n * Union types for BaseMime\n */\nexport type BaseMime = (typeof _baseMimes)[keyof typeof _baseMimes]\n\nconst _baseMimes = {\n  aac: 'audio/aac',\n  avi: 'video/x-msvideo',\n  avif: 'image/avif',\n  av1: 'video/av1',\n  bin: 'application/octet-stream',\n  bmp: 'image/bmp',\n  css: 'text/css',\n  csv: 'text/csv',\n  eot: 'application/vnd.ms-fontobject',\n  epub: 'application/epub+zip',\n  gif: 'image/gif',\n  gz: 'application/gzip',\n  htm: 'text/html',\n  html: 'text/html',\n  ico: 'image/x-icon',\n  ics: 'text/calendar',\n  jpeg: 'image/jpeg',\n  jpg: 'image/jpeg',\n  js: 'text/javascript',\n  json: 'application/json',\n  jsonld: 'application/ld+json',\n  map: 'application/json',\n  mid: 'audio/x-midi',\n  midi: 'audio/x-midi',\n  mjs: 'text/javascript',\n  mp3: 'audio/mpeg',\n  mp4: 'video/mp4',\n  mpeg: 'video/mpeg',\n  oga: 'audio/ogg',\n  ogv: 'video/ogg',\n  ogx: 'application/ogg',\n  opus: 'audio/opus',\n  otf: 'font/otf',\n  pdf: 'application/pdf',\n  png: 'image/png',\n  rtf: 'application/rtf',\n  svg: 'image/svg+xml',\n  tif: 'image/tiff',\n  tiff: 'image/tiff',\n  ts: 'video/mp2t',\n  ttf: 'font/ttf',\n  txt: 'text/plain',\n  wasm: 'application/wasm',\n  webm: 'video/webm',\n  weba: 'audio/webm',\n  webmanifest: 'application/manifest+json',\n  webp: 'image/webp',\n  woff: 'font/woff',\n  woff2: 'font/woff2',\n  xhtml: 'application/xhtml+xml',\n  xml: 'application/xml',\n  zip: 'application/zip',\n  '3gp': 'video/3gpp',\n  '3g2': 'video/3gpp2',\n  gltf: 'model/gltf+json',\n  glb: 'model/gltf-binary',\n} as const\n\nconst baseMimes: Record<string, BaseMime> = _baseMimes\n"
  },
  {
    "path": "src/utils/stream.test.ts",
    "content": "import { StreamingApi } from './stream'\n\ndescribe('StreamingApi', () => {\n  it('write(string)', async () => {\n    const { readable, writable } = new TransformStream()\n    const api = new StreamingApi(writable, readable)\n    const reader = api.responseReadable.getReader()\n    api.write('foo')\n    expect((await reader.read()).value).toEqual(new TextEncoder().encode('foo'))\n    api.write('bar')\n    expect((await reader.read()).value).toEqual(new TextEncoder().encode('bar'))\n  })\n\n  it('write(Uint8Array)', async () => {\n    const { readable, writable } = new TransformStream()\n    const api = new StreamingApi(writable, readable)\n    const reader = api.responseReadable.getReader()\n    api.write(new Uint8Array([1, 2, 3]))\n    expect((await reader.read()).value).toEqual(new Uint8Array([1, 2, 3]))\n    api.write(new Uint8Array([4, 5, 6]))\n    expect((await reader.read()).value).toEqual(new Uint8Array([4, 5, 6]))\n  })\n\n  it('writeln(string)', async () => {\n    const { readable, writable } = new TransformStream()\n    const api = new StreamingApi(writable, readable)\n    const reader = api.responseReadable.getReader()\n    api.writeln('foo')\n    expect((await reader.read()).value).toEqual(new TextEncoder().encode('foo\\n'))\n    api.writeln('bar')\n    expect((await reader.read()).value).toEqual(new TextEncoder().encode('bar\\n'))\n  })\n\n  it('pipe()', async () => {\n    const { readable: senderReadable, writable: senderWritable } = new TransformStream()\n\n    // send data to readable in other scope\n    ;(async () => {\n      const writer = senderWritable.getWriter()\n      await writer.write(new TextEncoder().encode('foo'))\n      await writer.write(new TextEncoder().encode('bar'))\n      // await writer.close()\n    })()\n\n    const { readable: receiverReadable, writable: receiverWritable } = new TransformStream()\n\n    const api = new StreamingApi(receiverWritable, receiverReadable)\n\n    // pipe readable to api in other scope\n    ;(async () => {\n      await api.pipe(senderReadable)\n    })()\n\n    // read data from api\n    const reader = api.responseReadable.getReader()\n    expect((await reader.read()).value).toEqual(new TextEncoder().encode('foo'))\n    expect((await reader.read()).value).toEqual(new TextEncoder().encode('bar'))\n  })\n\n  it('close()', async () => {\n    const { readable, writable } = new TransformStream()\n    const api = new StreamingApi(writable, readable)\n    const reader = api.responseReadable.getReader()\n    await api.close()\n    expect((await reader.read()).done).toBe(true)\n  })\n\n  it('should not throw an error in write()', async () => {\n    const { readable, writable } = new TransformStream()\n    const api = new StreamingApi(writable, readable)\n    await api.close()\n    const write = () => api.write('foo')\n    expect(write).not.toThrow()\n  })\n\n  it('should not throw an error in close()', async () => {\n    const { readable, writable } = new TransformStream()\n    const api = new StreamingApi(writable, readable)\n    const close = async () => {\n      await api.close()\n      await api.close()\n    }\n    expect(close).not.toThrow()\n  })\n\n  it('onAbort()', async () => {\n    const { readable, writable } = new TransformStream()\n    const handleAbort1 = vi.fn()\n    const handleAbort2 = vi.fn()\n    const api = new StreamingApi(writable, readable)\n    api.onAbort(handleAbort1)\n    api.onAbort(handleAbort2)\n    expect(handleAbort1).not.toBeCalled()\n    expect(handleAbort2).not.toBeCalled()\n    await api.responseReadable.cancel()\n    expect(handleAbort1).toBeCalled()\n    expect(handleAbort2).toBeCalled()\n  })\n\n  it('abort()', async () => {\n    const { readable, writable } = new TransformStream()\n    const handleAbort1 = vi.fn()\n    const handleAbort2 = vi.fn()\n    const api = new StreamingApi(writable, readable)\n    api.onAbort(handleAbort1)\n    api.onAbort(handleAbort2)\n    expect(handleAbort1).not.toBeCalled()\n    expect(handleAbort2).not.toBeCalled()\n    expect(api.aborted).toBe(false)\n\n    api.abort()\n    expect(handleAbort1).toHaveBeenCalledOnce()\n    expect(handleAbort2).toHaveBeenCalledOnce()\n    expect(api.aborted).toBe(true)\n\n    api.abort()\n    expect(handleAbort1).toHaveBeenCalledOnce()\n    expect(handleAbort2).toHaveBeenCalledOnce()\n    expect(api.aborted).toBe(true)\n  })\n})\n"
  },
  {
    "path": "src/utils/stream.ts",
    "content": "/**\n * @module\n * Stream utility.\n */\n\nexport class StreamingApi {\n  private writer: WritableStreamDefaultWriter<Uint8Array>\n  private encoder: TextEncoder\n  private writable: WritableStream\n  private abortSubscribers: (() => void | Promise<void>)[] = []\n  responseReadable: ReadableStream\n  /**\n   * Whether the stream has been aborted.\n   */\n  aborted: boolean = false\n  /**\n   * Whether the stream has been closed normally.\n   */\n  closed: boolean = false\n\n  constructor(writable: WritableStream, _readable: ReadableStream) {\n    this.writable = writable\n    this.writer = writable.getWriter()\n    this.encoder = new TextEncoder()\n\n    const reader = _readable.getReader()\n\n    // in case the user disconnects, let the reader know to cancel\n    // this in-turn results in responseReadable being closed\n    // and writeSSE method no longer blocks indefinitely\n    this.abortSubscribers.push(async () => {\n      await reader.cancel()\n    })\n\n    this.responseReadable = new ReadableStream({\n      async pull(controller) {\n        const { done, value } = await reader.read()\n        done ? controller.close() : controller.enqueue(value)\n      },\n      cancel: () => {\n        this.abort()\n      },\n    })\n  }\n\n  async write(input: Uint8Array | string): Promise<StreamingApi> {\n    try {\n      if (typeof input === 'string') {\n        input = this.encoder.encode(input)\n      }\n      await this.writer.write(input)\n    } catch {\n      // Do nothing. If you want to handle errors, create a stream by yourself.\n    }\n    return this\n  }\n\n  async writeln(input: string): Promise<StreamingApi> {\n    await this.write(input + '\\n')\n    return this\n  }\n\n  sleep(ms: number): Promise<unknown> {\n    return new Promise((res) => setTimeout(res, ms))\n  }\n\n  async close() {\n    try {\n      await this.writer.close()\n    } catch {\n      // Do nothing. If you want to handle errors, create a stream by yourself.\n    }\n    this.closed = true\n  }\n\n  async pipe(body: ReadableStream) {\n    this.writer.releaseLock()\n    await body.pipeTo(this.writable, { preventClose: true })\n    this.writer = this.writable.getWriter()\n  }\n\n  onAbort(listener: () => void | Promise<void>) {\n    this.abortSubscribers.push(listener)\n  }\n\n  /**\n   * Abort the stream.\n   * You can call this method when stream is aborted by external event.\n   */\n  abort() {\n    if (!this.aborted) {\n      this.aborted = true\n      this.abortSubscribers.forEach((subscriber) => subscriber())\n    }\n  }\n}\n"
  },
  {
    "path": "src/utils/types.test.ts",
    "content": "import type { JSONParsed, JSONValue } from './types'\n\ndescribe('JSONParsed', () => {\n  enum SampleEnum {\n    Value1 = 'value1',\n    Value2 = 'value2',\n  }\n\n  interface Meta {\n    metadata: {\n      href: string\n      sampleEnum: SampleEnum\n    }\n  }\n\n  interface SampleInterface {\n    someMeta: Meta\n  }\n\n  type SampleType = {\n    someMeta: Meta\n  }\n\n  describe('primitives', () => {\n    it('should convert number type to number', () => {\n      type Actual = JSONParsed<number>\n      type Expected = number\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n    it('should convert string type to string', () => {\n      type Actual = JSONParsed<string>\n      type Expected = string\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n    it('should convert boolean type to boolean', () => {\n      type Actual = JSONParsed<boolean>\n      type Expected = boolean\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n    it('should convert null type to null', () => {\n      type Actual = JSONParsed<null>\n      type Expected = null\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n  })\n\n  describe('toJSON', () => {\n    it('should convert { toJSON() => T } to T', () => {\n      type Actual = JSONParsed<{ toJSON(): number }>\n      type Expected = number\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n    it('toJSON is not called recursively', () => {\n      type Actual = JSONParsed<{ toJSON(): { toJSON(): number } }>\n      type Expected = {}\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n    it('should convert { a: { toJSON() => T } } to { a: T }', () => {\n      type Actual = JSONParsed<{ a: { toJSON(): number } }>\n      type Expected = { a: number }\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n    it('should convert invalid type with { toJSON() => T to T', () => {\n      type Actual = JSONParsed<bigint & { toJSON(): string }>\n      type Expected = string\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n  })\n\n  describe('invalid types', () => {\n    it('should convert undefined type to never', () => {\n      type Actual = JSONParsed<undefined>\n      type Expected = never\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n    it('should convert symbol type to never', () => {\n      type Actual = JSONParsed<symbol>\n      type Expected = never\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n    it('should convert function type to never', () => {\n      type Actual = JSONParsed<() => void>\n      type Expected = never\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n    it('should convert bigint type to never', () => {\n      type Actual = JSONParsed<bigint>\n      type Expected = never\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n  })\n\n  describe('array', () => {\n    it('should convert undefined[] type to null[]', () => {\n      type Actual = JSONParsed<undefined[]>\n      type Expected = null[]\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n    it('should convert Function[] type to null[]', () => {\n      type Actual = JSONParsed<(() => void)[]>\n      type Expected = null[]\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n    it('should convert symbol[] type to null[]', () => {\n      type Actual = JSONParsed<symbol[]>\n      type Expected = null[]\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n    it('should convert (T | undefined)[] type to JSONParsedT | null>[]', () => {\n      type Actual = JSONParsed<(number | undefined)[]>\n      type Expected = (number | null)[]\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n    it('should convert { key: readonly T[]} correctly', () => {\n      type Actual = JSONParsed<{ key: readonly number[] }>\n      type Expected = { key: readonly number[] }\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n  })\n\n  describe('tuple', () => {\n    it('should convert [T, S] type to [T, S]', () => {\n      type Actual = JSONParsed<[number, string]>\n      type Expected = [number, string]\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n    it('should convert [T, undefined] type to [T, null]', () => {\n      type Actual = JSONParsed<[number, undefined]>\n      type Expected = [number, null]\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n  })\n\n  describe('object', () => {\n    it('should omit keys with undefined value', () => {\n      type Actual = JSONParsed<{ a: number; b: undefined }>\n      type Expected = { a: number }\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n    it('should omit keys with symbol value', () => {\n      type Actual = JSONParsed<{ a: number; b: symbol }>\n      type Expected = { a: number }\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n    it('should omit keys with function value', () => {\n      type Actual = JSONParsed<{ a: number; b: () => void }>\n      type Expected = { a: number }\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n    it('should omit symbol keys', () => {\n      type Actual = JSONParsed<{ a: number; [x: symbol]: number }>\n      type Expected = { a: number }\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n    it('should convert T | undefined to T | undefined', () => {\n      type Actual = JSONParsed<{ a: number; b: number | undefined }>\n      type Expected = { a: number; b: number | undefined }\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n    it('should omit keys with invalid union', () => {\n      type Actual = JSONParsed<{ a: number; b: undefined | symbol }>\n      type Expected = { a: number }\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n  })\n\n  describe('unknown', () => {\n    it('should convert unknown type to unknown', () => {\n      type Actual = JSONParsed<unknown>\n      type Expected = JSONValue\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n\n    it('Should convert unknown value to JSONValue', () => {\n      type Actual = JSONParsed<{ value: unknown }>\n      type Expected = { value: JSONValue }\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n  })\n\n  describe('Set/Map', () => {\n    it('should convert Set to empty object', () => {\n      type Actual = JSONParsed<Set<number>>\n      type Expected = {}\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n    it('should convert Map to empty object', () => {\n      type Actual = JSONParsed<Map<number, number>>\n      type Expected = {}\n      expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n    })\n  })\n\n  it('Should parse a complex type', () => {\n    const sample: JSONParsed<SampleType> = {\n      someMeta: {\n        metadata: {\n          href: '',\n          sampleEnum: SampleEnum.Value1,\n        },\n      },\n    }\n    expectTypeOf(sample).toEqualTypeOf<SampleType>()\n  })\n\n  it('Should parse a complex interface', () => {\n    const sample: JSONParsed<SampleInterface> = {\n      someMeta: {\n        metadata: {\n          href: '',\n          sampleEnum: SampleEnum.Value1,\n        },\n      },\n    }\n    expectTypeOf(sample).toEqualTypeOf<SampleInterface>()\n  })\n\n  it('Should convert Date to string', () => {\n    type Post = {\n      datetime: Date\n    }\n    type Expected = {\n      datetime: string\n    }\n    type Actual = JSONParsed<Post>\n    expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n  })\n\n  it('Should convert bigint to never', () => {\n    type Post = {\n      num: bigint\n    }\n    type Expected = never\n    type Actual = JSONParsed<Post>\n    expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n  })\n\n  it('Should convert bigint when TError is provided', () => {\n    type Post = {\n      num: bigint\n    }\n    type Expected = {\n      num: JSONValue\n    }\n    type Actual = JSONParsed<Post, never>\n    expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n  })\n\n  it('Should convert bigint[] to never', () => {\n    type Post = {\n      nums: bigint[]\n    }\n    type Expected = never\n    type Actual = JSONParsed<Post>\n    expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n  })\n\n  it('Should convert bigint[] when TError is provided', () => {\n    type Post = {\n      num: bigint[]\n    }\n    type Expected = {\n      num: JSONValue[]\n    }\n    type Actual = JSONParsed<Post, never>\n    expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n  })\n\n  it('Should parse bigint with a toJSON function', () => {\n    class SafeBigInt {\n      unsafe = BigInt('42')\n\n      toJSON() {\n        return {\n          unsafe: '42n',\n        }\n      }\n    }\n\n    type Actual = JSONParsed<SafeBigInt>\n    type Expected = { unsafe: string }\n    expectTypeOf<Actual>().toEqualTypeOf<Expected>()\n  })\n})\n"
  },
  {
    "path": "src/utils/types.ts",
    "content": "/**\n * @module\n * Types utility.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\nexport type Expect<T extends true> = T\nexport type Equal<X, Y> =\n  (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false\nexport type NotEqual<X, Y> = true extends Equal<X, Y> ? false : true\n\nexport type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (\n  k: infer I\n) => void\n  ? I\n  : never\n\nexport type RemoveBlankRecord<T> =\n  T extends Record<infer K, unknown> ? (K extends string ? T : never) : never\n\nexport type IfAnyThenEmptyObject<T> = 0 extends 1 & T ? {} : T\n\nexport type JSONPrimitive = string | boolean | number | null\nexport type JSONArray = (JSONPrimitive | JSONObject | JSONArray)[]\nexport type JSONObject = {\n  [key: string]: JSONPrimitive | JSONArray | JSONObject | object | InvalidJSONValue\n}\nexport type InvalidJSONValue = undefined | symbol | ((...args: unknown[]) => unknown)\n\ntype InvalidToNull<T> = T extends InvalidJSONValue ? null : T\n\ntype IsInvalid<T> = T extends InvalidJSONValue ? true : false\n\n/**\n * symbol keys are omitted through `JSON.stringify`\n */\ntype OmitSymbolKeys<T> = { [K in keyof T as K extends symbol ? never : K]: T[K] }\n\nexport type JSONValue = JSONObject | JSONArray | JSONPrimitive\n/**\n * Convert a type to a JSON-compatible type.\n *\n * Non-JSON values such as `Date` implement `.toJSON()`,\n * so they can be transformed to a value assignable to `JSONObject`\n *\n * `JSON.stringify()` throws a `TypeError` when it encounters a `bigint` value,\n * unless a custom `replacer` function or `.toJSON()` method is provided.\n *\n * This behaviour can be controlled by the `TError` generic type parameter,\n * which defaults to `bigint | ReadonlyArray<bigint>`.\n * You can set it to `never` to disable this check.\n */\nexport type JSONParsed<T, TError = bigint | ReadonlyArray<bigint>> = T extends {\n  toJSON(): infer J\n}\n  ? (() => J) extends () => JSONPrimitive\n    ? J\n    : (() => J) extends () => { toJSON(): unknown }\n      ? {}\n      : JSONParsed<J, TError>\n  : T extends JSONPrimitive\n    ? T\n    : T extends InvalidJSONValue\n      ? never\n      : T extends ReadonlyArray<unknown>\n        ? { [K in keyof T]: JSONParsed<InvalidToNull<T[K]>, TError> }\n        : T extends Set<unknown> | Map<unknown, unknown> | Record<string, never>\n          ? {}\n          : T extends object\n            ? T[keyof T] extends TError\n              ? never\n              : {\n                  [K in keyof OmitSymbolKeys<T> as IsInvalid<T[K]> extends true\n                    ? never\n                    : K]: boolean extends IsInvalid<T[K]>\n                    ? JSONParsed<T[K], TError> | undefined\n                    : JSONParsed<T[K], TError>\n                }\n            : T extends unknown\n              ? T extends TError\n                ? never\n                : JSONValue\n              : never\n\n/**\n * Useful to flatten the type output to improve type hints shown in editors. And also to transform an interface into a type to aide with assignability.\n * @copyright from sindresorhus/type-fest\n */\nexport type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {}\n\n/**\n * A simple extension of Simplify that will deeply traverse array elements.\n */\nexport type SimplifyDeepArray<T> = T extends any[]\n  ? { [E in keyof T]: SimplifyDeepArray<T[E]> }\n  : Simplify<T>\n\nexport type InterfaceToType<T> = T extends Function ? T : { [K in keyof T]: InterfaceToType<T[K]> }\n\nexport type RequiredKeysOf<BaseType extends object> = Exclude<\n  {\n    [Key in keyof BaseType]: BaseType extends Record<Key, BaseType[Key]> ? Key : never\n  }[keyof BaseType],\n  undefined\n>\n\nexport type HasRequiredKeys<BaseType extends object> =\n  RequiredKeysOf<BaseType> extends never ? false : true\n\nexport type IsAny<T> = boolean extends (T extends never ? true : false) ? true : false\n\n/**\n * String literal types with auto-completion\n * @see https://github.com/Microsoft/TypeScript/issues/29729\n */\nexport type StringLiteralUnion<T> = T | (string & Record<never, never>)\n"
  },
  {
    "path": "src/utils/url.test.ts",
    "content": "import {\n  checkOptionalParameter,\n  getPath,\n  getPathNoStrict,\n  getPattern,\n  getQueryParam,\n  getQueryParams,\n  getQueryStrings,\n  mergePath,\n  splitPath,\n  splitRoutingPath,\n} from './url'\n\ndescribe('url', () => {\n  it('splitPath', () => {\n    let ps = splitPath('/')\n    expect(ps).toStrictEqual([''])\n\n    ps = splitPath('/hello')\n    expect(ps).toStrictEqual(['hello'])\n  })\n\n  it('splitRoutingPath', () => {\n    let ps = splitRoutingPath('/')\n    expect(ps).toStrictEqual([''])\n\n    ps = splitRoutingPath('/hello')\n    expect(ps).toStrictEqual(['hello'])\n\n    ps = splitRoutingPath('*')\n    expect(ps).toStrictEqual(['*'])\n\n    ps = splitRoutingPath('/wildcard-abc/*/wildcard-efg')\n    expect(ps).toStrictEqual(['wildcard-abc', '*', 'wildcard-efg'])\n\n    ps = splitRoutingPath('/map/:location/events')\n    expect(ps).toStrictEqual(['map', ':location', 'events'])\n\n    ps = splitRoutingPath('/js/:location{[a-z/]+.js}')\n    expect(ps).toStrictEqual(['js', ':location{[a-z/]+.js}'])\n\n    ps = splitRoutingPath('/users/:name{[0-9a-zA-Z_-]{3,10}}')\n    expect(ps).toStrictEqual(['users', ':name{[0-9a-zA-Z_-]{3,10}}'])\n\n    ps = splitRoutingPath('/users/:@name{[0-9a-zA-Z_-]{3,10}}')\n    expect(ps).toStrictEqual(['users', ':@name{[0-9a-zA-Z_-]{3,10}}'])\n\n    ps = splitRoutingPath('/users/:dept{\\\\d+}/:@name{[0-9a-zA-Z_-]{3,10}}')\n    expect(ps).toStrictEqual(['users', ':dept{\\\\d+}', ':@name{[0-9a-zA-Z_-]{3,10}}'])\n  })\n\n  describe('getPattern', () => {\n    it('no pattern', () => {\n      const res = getPattern('id')\n      expect(res).toBeNull()\n    })\n\n    it('no pattern with next', () => {\n      const res = getPattern('id', 'next')\n      expect(res).toBeNull()\n    })\n\n    it('default pattern', () => {\n      const res = getPattern(':id')\n      expect(res).toEqual([':id', 'id', true])\n    })\n\n    it('default pattern with next', () => {\n      const res = getPattern(':id', 'next')\n      expect(res).toEqual([':id', 'id', true])\n    })\n\n    it('regex pattern', () => {\n      const res = getPattern(':id{[0-9]+}')\n      expect(res).toEqual([':id{[0-9]+}', 'id', /^[0-9]+$/])\n    })\n\n    it('regex pattern with next', () => {\n      const res = getPattern(':id{[0-9]+}', 'next')\n      expect(res).toEqual([':id{[0-9]+}#next', 'id', /^[0-9]+(?=\\/next)/])\n    })\n\n    it('wildcard', () => {\n      const res = getPattern('*')\n      expect(res).toBe('*')\n    })\n\n    it('wildcard with next', () => {\n      const res = getPattern('*', 'next')\n      expect(res).toBe('*')\n    })\n  })\n\n  describe('getPath', () => {\n    it('getPath - no trailing slash', () => {\n      let path = getPath(new Request('https://example.com/'))\n      expect(path).toBe('/')\n      path = getPath(new Request('https://example.com/hello'))\n      expect(path).toBe('/hello')\n      path = getPath(new Request('https://example.com/hello/hey'))\n      expect(path).toBe('/hello/hey')\n      path = getPath(new Request('https://example.com/hello?name=foo'))\n      expect(path).toBe('/hello')\n      path = getPath(new Request('https://example.com/hello/hey?name=foo&name=bar'))\n      expect(path).toBe('/hello/hey')\n    })\n\n    it('getPath - with trailing slash', () => {\n      let path = getPath(new Request('https://example.com/hello/'))\n      expect(path).toBe('/hello/')\n      path = getPath(new Request('https://example.com/hello/hey/'))\n      expect(path).toBe('/hello/hey/')\n    })\n\n    it('getPath - http+unix', () => {\n      const path = getPath(new Request('http+unix://%2Ftmp%2Fsocket%2Esock/hello/'))\n      expect(path).toBe('/hello/')\n    })\n\n    it.each([\n      'http:/example.com/hello', // invalid HTTP URL\n      'http:///hello', // invalid HTTP URL\n      'http://a/:/hello', // starts with `/:/`\n      'x://a/:/hello', // unknown schema\n    ])('getPath - %s', (url) => {\n      expect(getPath(new Request(url))).toBe(new URL(url).pathname)\n    })\n\n    it('getPath - with fragment', () => {\n      let path = getPath(new Request('https://example.com/users/#user-list'))\n      expect(path).toBe('/users/')\n      path = getPath(new Request('https://example.com/users/1#profile-section'))\n      expect(path).toBe('/users/1')\n      path = getPath(new Request('https://example.com/hello#section'))\n      expect(path).toBe('/hello')\n      path = getPath(new Request('https://example.com/#top'))\n      expect(path).toBe('/')\n    })\n\n    it('getPath - with query and fragment', () => {\n      let path = getPath(new Request('https://example.com/hello?name=foo#section'))\n      expect(path).toBe('/hello')\n      path = getPath(new Request('https://example.com/search?q=test#results'))\n      expect(path).toBe('/search')\n    })\n\n    it('getPath - with percent encoding only (no query or fragment)', () => {\n      const path = getPath(new Request('https://example.com/hello%20world'))\n      expect(path).toBe('/hello world')\n    })\n\n    it('getPath - with percent encoding and fragment', () => {\n      let path = getPath(new Request('https://example.com/hello%20world#section'))\n      expect(path).toBe('/hello world')\n      path = getPath(new Request('https://example.com/%E7%82%8E#top'))\n      expect(path).toBe('/炎')\n    })\n\n    it('getPath - with percent encoding and fragment containing query-like chars', () => {\n      const path = getPath(new Request('https://example.com/hello%20world#section?foo=bar'))\n      expect(path).toBe('/hello world')\n    })\n\n    it('getPath - with encoded hash (%23) in path and real fragment', () => {\n      // %23 is encoded '#' - decodeURI preserves reserved characters, so %23 stays as %23\n      let path = getPath(new Request('https://example.com/path%23test#real-fragment'))\n      expect(path).toBe('/path%23test')\n      path = getPath(new Request('https://example.com/foo%23bar%23baz#section'))\n      expect(path).toBe('/foo%23bar%23baz')\n      // Only encoded hash, no real fragment\n      path = getPath(new Request('https://example.com/issue%23123'))\n      expect(path).toBe('/issue%23123')\n    })\n  })\n\n  describe('getQueryStrings', () => {\n    it('getQueryStrings', () => {\n      let qs = getQueryStrings('https://example.com/hello?name=foo&name=bar&age=20')\n      expect(qs).toBe('?name=foo&name=bar&age=20')\n      qs = getQueryStrings('https://example.com/hello?')\n      expect(qs).toBe('?')\n      qs = getQueryStrings('https://example.com/hello')\n      expect(qs).toBe('')\n      // Allows to contain hash\n      qs = getQueryStrings('https://example.com/hello?name=foo&name=bar&age=20#hash')\n      expect(qs).toBe('?name=foo&name=bar&age=20#hash')\n    })\n  })\n\n  describe('getPathNoStrict', () => {\n    it('getPathNoStrict - no strict is false', () => {\n      let path = getPathNoStrict(new Request('https://example.com/hello/'))\n      expect(path).toBe('/hello')\n      path = getPathNoStrict(new Request('https://example.com/hello/hey/'))\n      expect(path).toBe('/hello/hey')\n    })\n\n    it('getPathNoStrict - return `/` even if strict is false', () => {\n      const path = getPathNoStrict(new Request('https://example.com/'))\n      expect(path).toBe('/')\n    })\n  })\n\n  describe('mergePath', () => {\n    it('mergePath', () => {\n      expect(mergePath('/book', '/')).toBe('/book')\n      expect(mergePath('/book/', '/')).toBe('/book/')\n      expect(mergePath('/book', '/hey')).toBe('/book/hey')\n      expect(mergePath('/book/', '/hey')).toBe('/book/hey')\n      expect(mergePath('/book', '/hey/')).toBe('/book/hey/')\n      expect(mergePath('/book/', '/hey/')).toBe('/book/hey/')\n      expect(mergePath('/book', 'hey', 'say')).toBe('/book/hey/say')\n      expect(mergePath('/book', '/hey/', '/say/')).toBe('/book/hey/say/')\n      expect(mergePath('/book', '/hey/', '/say/', '/')).toBe('/book/hey/say/')\n      expect(mergePath('/book', '/hey', '/say', '/')).toBe('/book/hey/say')\n      expect(mergePath('/', '/book', '/hey', '/say', '/')).toBe('/book/hey/say')\n\n      expect(mergePath('book', '/')).toBe('/book')\n      expect(mergePath('book/', '/')).toBe('/book/')\n      expect(mergePath('book', '/hey')).toBe('/book/hey')\n      expect(mergePath('book', 'hey')).toBe('/book/hey')\n      expect(mergePath('book', 'hey/')).toBe('/book/hey/')\n    })\n    it('Should be `/book`', () => {\n      expect(mergePath('/', 'book')).toBe('/book')\n    })\n    it('Should be `/book`', () => {\n      expect(mergePath('/', '/book')).toBe('/book')\n    })\n    it('Should be `/`', () => {\n      expect(mergePath('/', '/')).toBe('/')\n    })\n  })\n\n  describe('checkOptionalParameter', () => {\n    it('checkOptionalParameter', () => {\n      expect(checkOptionalParameter('/api/animals/:type?')).toEqual([\n        '/api/animals',\n        '/api/animals/:type',\n      ])\n      expect(checkOptionalParameter('/api/animals/type?')).toBeNull()\n      expect(checkOptionalParameter('/api/animals/:type')).toBeNull()\n      expect(checkOptionalParameter('/api/animals')).toBeNull()\n      expect(checkOptionalParameter('/api/:animals?/type')).toBeNull()\n      expect(checkOptionalParameter('/api/animals/:type?/')).toBeNull()\n      expect(checkOptionalParameter('/:optional?')).toEqual(['/', '/:optional'])\n      expect(checkOptionalParameter('/v1/leaderboard/:version?/:platform?')).toEqual([\n        '/v1/leaderboard',\n        '/v1/leaderboard/:version',\n        '/v1/leaderboard/:version/:platform',\n      ])\n      expect(checkOptionalParameter('/api/:version/animal/:type?')).toEqual([\n        '/api/:version/animal',\n        '/api/:version/animal/:type',\n      ])\n    })\n  })\n\n  describe('getQueryParam', () => {\n    it('Parse URL query strings', () => {\n      expect(getQueryParam('http://example.com/?name=hey', 'name')).toBe('hey')\n      expect(getQueryParam('http://example.com/?name=hey#fragment', 'name')).toBe('hey#fragment')\n      expect(getQueryParam('http://example.com/?name=hey&age=20&tall=170', 'age')).toBe('20')\n      expect(getQueryParam('http://example.com/?Hono+is=a+web+framework', 'Hono is')).toBe(\n        'a web framework'\n      )\n      expect(getQueryParam('http://example.com/?name=%E0%A4%A', 'name')).toBe('%E0%A4%A')\n\n      expect(getQueryParam('http://example.com/?name0=sam&name1=tom', 'name0')).toBe('sam')\n      expect(getQueryParam('http://example.com/?name0=sam&name1=tom', 'name1')).toBe('tom')\n      expect(getQueryParam('http://example.com/?name0=sam&name1=tom', 'name')).toBe(undefined)\n\n      let searchParams = new URLSearchParams({ name: '炎' })\n      expect(getQueryParam(`http://example.com/?${searchParams.toString()}`, 'name')).toBe('炎')\n      searchParams = new URLSearchParams({ '炎 is': 'a web framework' })\n      expect(\n        getQueryParam(\n          `http://example.com/?${searchParams.toString()}`,\n          searchParams.keys().next().value\n        )\n      ).toBe('a web framework')\n      expect(getQueryParam('http://example.com/?name=hey&age=20&tall=170', 'weight')).toBe(\n        undefined\n      )\n      expect(getQueryParam('http://example.com/?name=hey&age=20&tall=170')).toEqual({\n        name: 'hey',\n        age: '20',\n        tall: '170',\n      })\n      expect(getQueryParam('http://example.com/?pretty&&&&q=1%2b1=2')).toEqual({\n        pretty: '',\n        q: '1+1=2',\n      })\n      expect(getQueryParam('http://example.com/?pretty', 'pretty')).toBe('')\n      expect(getQueryParam('http://example.com/?pretty', 'prtt')).toBe(undefined)\n      expect(getQueryParam('http://example.com/?name=sam&name=tom', 'name')).toBe('sam')\n      expect(getQueryParam('http://example.com/&name=sam?name=tom', 'name')).toBe('tom')\n      expect(getQueryParam('http://example.com/&name=sam', 'name')).toBe(undefined)\n      expect(getQueryParam('http://example.com/?name=sam&name=tom')).toEqual({\n        name: 'sam',\n      })\n      searchParams = new URLSearchParams('?name=sam=tom')\n      expect(getQueryParam('name', searchParams.get('name')?.toString()))\n    })\n  })\n\n  describe('getQueryParams', () => {\n    it('Parse URL query strings', () => {\n      expect(getQueryParams('http://example.com/?name=hey', 'name')).toEqual(['hey'])\n      expect(getQueryParams('http://example.com/?name=hey#fragment', 'name')).toEqual([\n        'hey#fragment',\n      ])\n      expect(getQueryParams('http://example.com/?name=hey&name=foo', 'name')).toEqual([\n        'hey',\n        'foo',\n      ])\n      expect(getQueryParams('http://example.com/?name=hey&age=20&tall=170', 'age')).toEqual(['20'])\n      expect(\n        getQueryParams('http://example.com/?name=hey&age=20&tall=170&name=foo&age=30', 'age')\n      ).toEqual(['20', '30'])\n      expect(getQueryParams('http://example.com/?Hono+is=a+web+framework', 'Hono is')).toEqual([\n        'a web framework',\n      ])\n      expect(getQueryParams('http://example.com/?name=%E0%A4%A', 'name')).toEqual(['%E0%A4%A'])\n\n      let searchParams = new URLSearchParams()\n      searchParams.append('tag', '炎')\n      searchParams.append('tag', 'ほのお')\n      expect(getQueryParams(`http://example.com/?${searchParams.toString()}`, 'tag')).toEqual([\n        '炎',\n        'ほのお',\n      ])\n      searchParams = new URLSearchParams()\n      searchParams.append('炎 works on', 'Cloudflare Workers')\n      searchParams.append('炎 works on', 'Fastly Compute')\n      expect(\n        getQueryParams(\n          `http://example.com/?${searchParams.toString()}`,\n          searchParams.keys().next().value\n        )\n      ).toEqual(['Cloudflare Workers', 'Fastly Compute'])\n      expect(getQueryParams('http://example.com/?name=hey&age=20&tall=170', 'weight')).toEqual(\n        undefined\n      )\n      expect(\n        getQueryParams('http://example.com/?name=hey&age=20&tall=170&name=foo&age=30&tall=180')\n      ).toEqual({\n        name: ['hey', 'foo'],\n        age: ['20', '30'],\n        tall: ['170', '180'],\n      })\n      expect(getQueryParams('http://example.com/?pretty&&&&q=1%2b1=2&q=2%2b2=4')).toEqual({\n        pretty: [''],\n        q: ['1+1=2', '2+2=4'],\n      })\n      expect(getQueryParams('http://example.com/?pretty', 'pretty')).toEqual([''])\n      expect(getQueryParams('http://example.com/?pretty', 'prtt')).toBe(undefined)\n      expect(getQueryParams('http://example.com/?toString')).toEqual({\n        toString: [''],\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "src/utils/url.ts",
    "content": "/**\n * @module\n * URL utility.\n */\n\nexport type Pattern = readonly [string, string, RegExp | true] | '*'\n\nexport const splitPath = (path: string): string[] => {\n  const paths = path.split('/')\n  if (paths[0] === '') {\n    paths.shift()\n  }\n  return paths\n}\n\nexport const splitRoutingPath = (routePath: string): string[] => {\n  const { groups, path } = extractGroupsFromPath(routePath)\n\n  const paths = splitPath(path)\n  return replaceGroupMarks(paths, groups)\n}\n\nconst extractGroupsFromPath = (path: string): { groups: [string, string][]; path: string } => {\n  const groups: [string, string][] = []\n\n  path = path.replace(/\\{[^}]+\\}/g, (match, index) => {\n    const mark = `@${index}`\n    groups.push([mark, match])\n    return mark\n  })\n\n  return { groups, path }\n}\n\nconst replaceGroupMarks = (paths: string[], groups: [string, string][]): string[] => {\n  for (let i = groups.length - 1; i >= 0; i--) {\n    const [mark] = groups[i]\n\n    for (let j = paths.length - 1; j >= 0; j--) {\n      if (paths[j].includes(mark)) {\n        paths[j] = paths[j].replace(mark, groups[i][1])\n        break\n      }\n    }\n  }\n\n  return paths\n}\n\nconst patternCache: { [key: string]: Pattern } = {}\nexport const getPattern = (label: string, next?: string): Pattern | null => {\n  // *            => wildcard\n  // :id{[0-9]+}  => ([0-9]+)\n  // :id          => (.+)\n\n  if (label === '*') {\n    return '*'\n  }\n\n  const match = label.match(/^\\:([^\\{\\}]+)(?:\\{(.+)\\})?$/)\n  if (match) {\n    const cacheKey = `${label}#${next}`\n    if (!patternCache[cacheKey]) {\n      if (match[2]) {\n        patternCache[cacheKey] =\n          next && next[0] !== ':' && next[0] !== '*'\n            ? [cacheKey, match[1], new RegExp(`^${match[2]}(?=/${next})`)]\n            : [label, match[1], new RegExp(`^${match[2]}$`)]\n      } else {\n        patternCache[cacheKey] = [label, match[1], true]\n      }\n    }\n\n    return patternCache[cacheKey]\n  }\n\n  return null\n}\n\ntype Decoder = (str: string) => string\nexport const tryDecode = (str: string, decoder: Decoder): string => {\n  try {\n    return decoder(str)\n  } catch {\n    return str.replace(/(?:%[0-9A-Fa-f]{2})+/g, (match) => {\n      try {\n        return decoder(match)\n      } catch {\n        return match\n      }\n    })\n  }\n}\n\n/**\n * Try to apply decodeURI() to given string.\n * If it fails, skip invalid percent encoding or invalid UTF-8 sequences, and apply decodeURI() to the rest as much as possible.\n * @param str The string to decode.\n * @returns The decoded string that sometimes contains undecodable percent encoding.\n * @example\n * tryDecodeURI('Hello%20World') // 'Hello World'\n * tryDecodeURI('Hello%20World/%A4%A2') // 'Hello World/%A4%A2'\n */\nexport const tryDecodeURI = (str: string): string => tryDecode(str, decodeURI)\n\nexport const getPath = (request: Request): string => {\n  const url = request.url\n  const start = url.indexOf('/', url.indexOf(':') + 4)\n  let i = start\n  for (; i < url.length; i++) {\n    const charCode = url.charCodeAt(i)\n    if (charCode === 37) {\n      // '%'\n      // If the path contains percent encoding, use `indexOf()` to find '?' or '#' and return the result immediately.\n      // Although this is a performance disadvantage, it is acceptable since we prefer cases that do not include percent encoding.\n      const queryIndex = url.indexOf('?', i)\n      const hashIndex = url.indexOf('#', i)\n      const end =\n        queryIndex === -1\n          ? hashIndex === -1\n            ? undefined\n            : hashIndex\n          : hashIndex === -1\n            ? queryIndex\n            : Math.min(queryIndex, hashIndex)\n      const path = url.slice(start, end)\n      return tryDecodeURI(path.includes('%25') ? path.replace(/%25/g, '%2525') : path)\n    } else if (charCode === 63 || charCode === 35) {\n      // '?' or '#'\n      break\n    }\n  }\n  return url.slice(start, i)\n}\n\nexport const getQueryStrings = (url: string): string => {\n  const queryIndex = url.indexOf('?', 8)\n  return queryIndex === -1 ? '' : '?' + url.slice(queryIndex + 1)\n}\n\nexport const getPathNoStrict = (request: Request): string => {\n  const result = getPath(request)\n\n  // if strict routing is false => `/hello/hey/` and `/hello/hey` are treated the same\n  return result.length > 1 && result.at(-1) === '/' ? result.slice(0, -1) : result\n}\n\n/**\n * Merge paths.\n * @param {string[]} ...paths - The paths to merge.\n * @returns {string} The merged path.\n * @example\n * mergePath('/api', '/users') // '/api/users'\n * mergePath('/api/', '/users') // '/api/users'\n * mergePath('/api', '/') // '/api'\n * mergePath('/api/', '/') // '/api/'\n */\nexport const mergePath: (...paths: string[]) => string = (\n  base: string | undefined,\n  sub: string | undefined,\n  ...rest: string[]\n): string => {\n  if (rest.length) {\n    sub = mergePath(sub as string, ...rest)\n  }\n  return `${base?.[0] === '/' ? '' : '/'}${base}${\n    sub === '/' ? '' : `${base?.at(-1) === '/' ? '' : '/'}${sub?.[0] === '/' ? sub.slice(1) : sub}`\n  }`\n}\n\nexport const checkOptionalParameter = (path: string): string[] | null => {\n  /*\n    If path is `/api/animals/:type?` it will return:\n    [`/api/animals`, `/api/animals/:type`]\n    in other cases it will return null\n  */\n\n  if (path.charCodeAt(path.length - 1) !== 63 || !path.includes(':')) {\n    return null\n  }\n\n  const segments = path.split('/')\n  const results: string[] = []\n  let basePath = ''\n\n  segments.forEach((segment) => {\n    if (segment !== '' && !/\\:/.test(segment)) {\n      basePath += '/' + segment\n    } else if (/\\:/.test(segment)) {\n      if (/\\?/.test(segment)) {\n        if (results.length === 0 && basePath === '') {\n          results.push('/')\n        } else {\n          results.push(basePath)\n        }\n        const optionalSegment = segment.replace('?', '')\n        basePath += '/' + optionalSegment\n        results.push(basePath)\n      } else {\n        basePath += '/' + segment\n      }\n    }\n  })\n\n  return results.filter((v, i, a) => a.indexOf(v) === i)\n}\n\n// Optimized\nconst _decodeURI = (value: string) => {\n  if (!/[%+]/.test(value)) {\n    return value\n  }\n  if (value.indexOf('+') !== -1) {\n    value = value.replace(/\\+/g, ' ')\n  }\n  return value.indexOf('%') !== -1 ? tryDecode(value, decodeURIComponent_) : value\n}\n\nconst _getQueryParam = (\n  url: string,\n  key?: string,\n  multiple?: boolean\n): string | undefined | Record<string, string> | string[] | Record<string, string[]> => {\n  let encoded\n\n  if (!multiple && key && !/[%+]/.test(key)) {\n    // optimized for unencoded key\n\n    let keyIndex = url.indexOf('?', 8)\n    if (keyIndex === -1) {\n      return undefined\n    }\n    if (!url.startsWith(key, keyIndex + 1)) {\n      keyIndex = url.indexOf(`&${key}`, keyIndex + 1)\n    }\n    while (keyIndex !== -1) {\n      const trailingKeyCode = url.charCodeAt(keyIndex + key.length + 1)\n      if (trailingKeyCode === 61) {\n        const valueIndex = keyIndex + key.length + 2\n        const endIndex = url.indexOf('&', valueIndex)\n        return _decodeURI(url.slice(valueIndex, endIndex === -1 ? undefined : endIndex))\n      } else if (trailingKeyCode == 38 || isNaN(trailingKeyCode)) {\n        return ''\n      }\n      keyIndex = url.indexOf(`&${key}`, keyIndex + 1)\n    }\n\n    encoded = /[%+]/.test(url)\n    if (!encoded) {\n      return undefined\n    }\n    // fallback to default routine\n  }\n\n  const results: Record<string, string> | Record<string, string[]> = {}\n  encoded ??= /[%+]/.test(url)\n\n  let keyIndex = url.indexOf('?', 8)\n  while (keyIndex !== -1) {\n    const nextKeyIndex = url.indexOf('&', keyIndex + 1)\n    let valueIndex = url.indexOf('=', keyIndex)\n    if (valueIndex > nextKeyIndex && nextKeyIndex !== -1) {\n      valueIndex = -1\n    }\n    let name = url.slice(\n      keyIndex + 1,\n      valueIndex === -1 ? (nextKeyIndex === -1 ? undefined : nextKeyIndex) : valueIndex\n    )\n    if (encoded) {\n      name = _decodeURI(name)\n    }\n\n    keyIndex = nextKeyIndex\n\n    if (name === '') {\n      continue\n    }\n\n    let value\n    if (valueIndex === -1) {\n      value = ''\n    } else {\n      value = url.slice(valueIndex + 1, nextKeyIndex === -1 ? undefined : nextKeyIndex)\n      if (encoded) {\n        value = _decodeURI(value)\n      }\n    }\n\n    if (multiple) {\n      if (!(results[name] && Array.isArray(results[name]))) {\n        results[name] = []\n      }\n      ;(results[name] as string[]).push(value)\n    } else {\n      results[name] ??= value\n    }\n  }\n\n  return key ? results[key] : results\n}\n\nexport const getQueryParam: (\n  url: string,\n  key?: string\n) => string | undefined | Record<string, string> = _getQueryParam as (\n  url: string,\n  key?: string\n) => string | undefined | Record<string, string>\n\nexport const getQueryParams = (\n  url: string,\n  key?: string\n): string[] | undefined | Record<string, string[]> => {\n  return _getQueryParam(url, key, true) as string[] | undefined | Record<string, string[]>\n}\n\n// `decodeURIComponent` is a long name.\n// By making it a function, we can use it commonly when minified, reducing the amount of code.\nexport const decodeURIComponent_ = decodeURIComponent\n"
  },
  {
    "path": "src/validator/index.ts",
    "content": "/**\n * @module\n * Validator for Hono.\n */\n\nexport { validator } from './validator'\nexport type { ValidationFunction } from './validator'\nexport type { InferInput } from './utils'\n"
  },
  {
    "path": "src/validator/utils.test.ts",
    "content": "import { expectTypeOf } from 'vitest'\nimport type { InferInput, IsLiteralUnion } from './utils'\n\ndescribe('IsLiteralUnion', () => {\n  it('should return true for literal union types', () => {\n    expectTypeOf<IsLiteralUnion<'asc' | 'desc', string>>().toEqualTypeOf<true>()\n  })\n\n  it('should return false for single literal', () => {\n    expectTypeOf<IsLiteralUnion<'asc', string>>().toEqualTypeOf<false>()\n  })\n\n  it('should return false for wide string type', () => {\n    expectTypeOf<IsLiteralUnion<string, string>>().toEqualTypeOf<false>()\n  })\n\n  it('should return true for literal union with undefined', () => {\n    expectTypeOf<IsLiteralUnion<'asc' | 'desc' | undefined, string>>().toEqualTypeOf<true>()\n  })\n})\n\ndescribe('InferInput', () => {\n  it('should preserve literal union types for query', () => {\n    expectTypeOf<InferInput<{ orderBy: 'asc' | 'desc' }, 'query'>>().toEqualTypeOf<{\n      orderBy: 'asc' | 'desc'\n    }>()\n  })\n\n  it('should convert string to string | string[] for query', () => {\n    expectTypeOf<InferInput<{ page: string }, 'query'>>().toEqualTypeOf<{\n      page: string | string[]\n    }>()\n  })\n\n  it('should convert number to string | string[] for query', () => {\n    expectTypeOf<InferInput<{ page: number }, 'query'>>().toEqualTypeOf<{\n      page: string | string[]\n    }>()\n  })\n\n  it('should preserve optional union (T | undefined) for query', () => {\n    expectTypeOf<InferInput<{ name?: string | undefined }, 'query'>>().toEqualTypeOf<{\n      name?: string | undefined\n    }>()\n  })\n\n  it('should preserve literal union with undefined for query', () => {\n    expectTypeOf<InferInput<{ orderBy?: 'asc' | 'desc' | undefined }, 'query'>>().toEqualTypeOf<{\n      orderBy?: 'asc' | 'desc' | undefined\n    }>()\n  })\n\n  it('should convert unknown to string | string[] for query (coerce support)', () => {\n    expectTypeOf<InferInput<{ page: unknown }, 'query'>>().toEqualTypeOf<{\n      page: string | string[]\n    }>()\n  })\n\n  it('should handle object | undefined for query (optional schema)', () => {\n    expectTypeOf<InferInput<{ name?: string | undefined } | undefined, 'query'>>().toEqualTypeOf<\n      { name?: string | undefined } | undefined\n    >()\n  })\n\n  it('should convert string to string for param', () => {\n    expectTypeOf<InferInput<{ id: string }, 'param'>>().toEqualTypeOf<{\n      id: string\n    }>()\n  })\n\n  it('should convert string to string for header', () => {\n    expectTypeOf<InferInput<{ authorization: string }, 'header'>>().toEqualTypeOf<{\n      authorization: string\n    }>()\n  })\n\n  it('should convert string to string for cookie', () => {\n    expectTypeOf<InferInput<{ session: string }, 'cookie'>>().toEqualTypeOf<{\n      session: string\n    }>()\n  })\n})\n"
  },
  {
    "path": "src/validator/utils.ts",
    "content": "import type { FormValue, ParsedFormValue, ValidationTargets } from '../types'\nimport type { UnionToIntersection } from '../utils/types'\n\n/**\n * Checks if T is a literal union type (e.g., 'asc' | 'desc')\n * that should be preserved in input types.\n * Returns true for union literals, false for single literals or wide types.\n */\nexport type IsLiteralUnion<T, Base> = [Exclude<T, undefined>] extends [Base]\n  ? [Exclude<T, undefined>] extends [UnionToIntersection<Exclude<T, undefined>>]\n    ? false\n    : true\n  : false\n\n// Check if type is an optional union (T | undefined) but not unknown/any\ntype IsOptionalUnion<T> = [unknown] extends [T]\n  ? false // unknown or any\n  : undefined extends T\n    ? true\n    : false\n\n// Helper to force TypeScript to expand type aliases\ntype SimplifyDeep<T> = { [K in keyof T]: T[K] } & {}\n\ntype InferInputInner<\n  Output,\n  Target extends keyof ValidationTargets,\n  T extends FormValue,\n> = SimplifyDeep<{\n  [K in keyof Output]: IsLiteralUnion<Output[K], string> extends true\n    ? Output[K]\n    : IsOptionalUnion<Output[K]> extends true\n      ? Output[K]\n      : Target extends 'form'\n        ? T | T[]\n        : Target extends 'query'\n          ? string | string[]\n          : Target extends 'param'\n            ? string\n            : Target extends 'header'\n              ? string\n              : Target extends 'cookie'\n                ? string\n                : unknown\n}>\n\n/**\n * Utility type to infer input types for validation targets.\n * Preserves literal union types (e.g., 'asc' | 'desc') while using\n * the default ValidationTargets type for other values.\n *\n * @example\n * ```ts\n * // In @hono/zod-validator or similar:\n * type Input = InferInput<z.input<Schema>, 'query'>\n * // { orderBy: 'asc' | 'desc', page: string | string[] }\n * ```\n */\nexport type InferInput<\n  Output,\n  Target extends keyof ValidationTargets,\n  T extends FormValue = ParsedFormValue,\n> = [Exclude<Output, undefined>] extends [never]\n  ? {}\n  : [Exclude<Output, undefined>] extends [object]\n    ? undefined extends Output\n      ? SimplifyDeep<InferInputInner<Exclude<Output, undefined>, Target, T>> | undefined\n      : SimplifyDeep<InferInputInner<Output, Target, T>>\n    : {}\n"
  },
  {
    "path": "src/validator/validator.test.ts",
    "content": "/* eslint-disable @typescript-eslint/no-unused-vars */\nimport * as z from 'zod'\nimport type { Context } from '../context'\nimport { Hono } from '../hono'\nimport { HTTPException } from '../http-exception'\nimport { cloneRawRequest } from '../request'\nimport type {\n  ErrorHandler,\n  ExtractSchema,\n  ExtractSchemaForStatusCode,\n  FormValue,\n  MiddlewareHandler,\n  TypedResponse,\n  ValidationTargets,\n} from '../types'\nimport type { ContentfulStatusCode, StatusCode } from '../utils/http-status'\nimport type { Equal, Expect } from '../utils/types'\nimport type { ValidationFunction } from './validator'\nimport { validator } from './validator'\n\n// Helper type to extract the response type from the validation function\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype InferValidatorResponse<VF> = VF extends (value: any, c: any) => infer R\n  ? R extends Promise<infer PR>\n    ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      PR extends Response | TypedResponse<any, any, any>\n      ? PR\n      : never\n    : // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      R extends Response | TypedResponse<any, any, any>\n      ? R\n      : never\n  : never\n\n// Reference implementation for only testing\nconst zodValidator = <\n  T extends z.ZodSchema,\n  E extends {},\n  P extends string,\n  Target extends keyof ValidationTargets,\n>(\n  target: Target,\n  schema: T\n) => {\n  const validationFunc = (value: unknown, c: Context<E, P>) => {\n    const result = schema.safeParse(value)\n    if (!result.success) {\n      return c.text('Invalid!', 400)\n    }\n    return result.data as z.output<T>\n  }\n\n  type ResponseType = InferValidatorResponse<typeof validationFunc>\n\n  return validator(target, validationFunc) as MiddlewareHandler<\n    E,\n    P,\n    { in: { [K in Target]: z.input<T> }; out: { [K in Target]: z.output<T> } },\n    ResponseType\n  >\n}\n\ndescribe('Basic', () => {\n  const app = new Hono()\n\n  const route = app.get(\n    '/search',\n    async (_c, next) => {\n      await next()\n    },\n    validator('query', (value, c) => {\n      type verify = Expect<Equal<Record<string, string | string[]>, typeof value>>\n      if (!value.q) {\n        return c.text('Invalid!', 400)\n      }\n    }),\n    (c) => {\n      return c.text('Valid!', 200)\n    }\n  )\n\n  type Expected = {\n    '/search': {\n      $get:\n        | {\n            input: {\n              query: {}\n            }\n            output: 'Invalid!'\n            outputFormat: 'text'\n            status: 400\n          }\n        | {\n            input: {\n              query: {}\n            }\n            output: 'Valid!'\n            outputFormat: 'text'\n            status: 200\n          }\n    }\n  }\n\n  type Actual = ExtractSchema<typeof route>\n\n  type verify = Expect<Equal<Expected, Actual>>\n\n  it('Should return 200 response', async () => {\n    const res = await app.request('http://localhost/search?q=foo')\n    expect(res.status).toBe(200)\n  })\n\n  it('Should return 400 response', async () => {\n    const res = await app.request('http://localhost/search')\n    expect(res.status).toBe(400)\n  })\n})\n\nconst onErrorHandler: ErrorHandler = (e, c) => {\n  if (e instanceof HTTPException) {\n    return c.json({ message: e.message, success: false }, e.status)\n  }\n  return c.json({ message: e.message }, 500)\n}\n\ndescribe('JSON', () => {\n  const app = new Hono()\n  app.post(\n    '/post',\n    validator('json', (value) => value),\n    (c) => {\n      return c.json(c.req.valid('json'))\n    }\n  )\n\n  it('Should return 200 response with a valid JSON data', async () => {\n    const res = await app.request('http://localhost/post', {\n      method: 'POST',\n      body: JSON.stringify({ foo: 'bar' }),\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    })\n    expect(res.status).toBe(200)\n    const data = await res.json()\n    expect(data).toEqual({ foo: 'bar' })\n  })\n\n  it('Should not validate if Content-Type is not set', async () => {\n    const res = await app.request('http://localhost/post', {\n      method: 'POST',\n      body: JSON.stringify({ foo: 'bar' }),\n    })\n    expect(res.status).toBe(200)\n    const data = await res.json()\n    expect(data.foo).toBeUndefined()\n  })\n\n  it('Should not validate if Content-Type is wrong', async () => {\n    const res = await app.request('http://localhost/post', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'text/plain;charset=utf-8',\n      },\n      body: JSON.stringify({ foo: 'bar' }),\n    })\n    expect(res.status).toBe(200)\n  })\n\n  it('Should validate if Content-Type is a application/json with a charset', async () => {\n    const res = await app.request('http://localhost/post', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify({ foo: 'bar' }),\n    })\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({ foo: 'bar' })\n  })\n\n  it('Should validate if Content-Type is a subtype like application/merge-patch+json', async () => {\n    const res = await app.request('http://localhost/post', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/merge-patch+json',\n      },\n      body: JSON.stringify({ foo: 'bar' }),\n    })\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({ foo: 'bar' })\n  })\n\n  it('Should validate if Content-Type is application/vnd.api+json', async () => {\n    const res = await app.request('http://localhost/post', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/vnd.api+json',\n      },\n      body: JSON.stringify({ foo: 'bar' }),\n    })\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({ foo: 'bar' })\n  })\n\n  it('Should not validate if Content-Type does not start with application/json', async () => {\n    const res = await app.request('http://localhost/post', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'Xapplication/json',\n      },\n      body: JSON.stringify({ foo: 'bar' }),\n    })\n    expect(res.status).toBe(200)\n    const data = await res.json()\n    expect(data.foo).toBeUndefined()\n  })\n})\n\ndescribe('Malformed JSON', () => {\n  const app = new Hono()\n  app.post(\n    '/post',\n    validator('json', (value) => value),\n    (c) => {\n      return c.json(c.req.valid('json'))\n    }\n  )\n\n  it('Should return 400 response if the body data is not a valid JSON', async () => {\n    const formData = new FormData()\n    formData.append('foo', 'bar')\n    const res = await app.request('http://localhost/post', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n      },\n      body: formData,\n    })\n    expect(res.status).toBe(400)\n  })\n})\n\ndescribe('FormData', () => {\n  const app = new Hono()\n  app.post(\n    '/post',\n    validator('form', (value) => value),\n    (c) => {\n      return c.json(c.req.valid('form'))\n    }\n  )\n\n  it('Should return 200 response with a valid form data', async () => {\n    const formData = new FormData()\n    formData.append('message', 'hi')\n    const res = await app.request('http://localhost/post', {\n      method: 'POST',\n      body: formData,\n    })\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({ message: 'hi' })\n  })\n\n  it('Should validate a URL Encoded Data', async () => {\n    const params = new URLSearchParams()\n    params.append('foo', 'bar')\n    const res = await app.request('/post', {\n      method: 'POST',\n      body: params,\n      headers: {\n        'content-type': 'application/x-www-form-urlencoded',\n      },\n    })\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({\n      foo: 'bar',\n    })\n  })\n\n  it('Should validate if Content-Type is a application/x-www-form-urlencoded with a charset', async () => {\n    const params = new URLSearchParams()\n    params.append('foo', 'bar')\n    const res = await app.request('/post', {\n      method: 'POST',\n      body: params,\n      headers: {\n        'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',\n      },\n    })\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({\n      foo: 'bar',\n    })\n  })\n\n  it('Should return `foo[]` as an array', async () => {\n    const form = new FormData()\n    form.append('foo[]', 'bar1')\n    form.append('foo[]', 'bar2')\n    const res = await app.request('/post', {\n      method: 'POST',\n      body: form,\n    })\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({\n      'foo[]': ['bar1', 'bar2'],\n    })\n  })\n\n  it('Should return `foo` as an array if multiple values are appended', async () => {\n    const form = new FormData()\n    form.append('foo', 'bar1')\n    form.append('foo', 'bar2')\n    form.append('foo', 'bar3')\n    const res = await app.request('/post', {\n      method: 'POST',\n      body: form,\n    })\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({\n      foo: ['bar1', 'bar2', 'bar3'],\n    })\n  })\n})\n\ndescribe('Malformed FormData request', () => {\n  const app = new Hono()\n  app.post(\n    '/post',\n    validator('form', (value) => value),\n    (c) => {\n      return c.json(c.req.valid('form'))\n    }\n  )\n  app.onError(onErrorHandler)\n\n  it('Should return 400 response, for malformed content type header', async () => {\n    const res = await app.request('http://localhost/post', {\n      method: 'POST',\n      body: 'hi',\n      headers: {\n        'content-type': 'multipart/form-data',\n      },\n    })\n    expect(res.status).toBe(400)\n    const data = await res.json()\n    expect(data['success']).toBe(false)\n    expect(data['message']).toMatch(/^Malformed FormData request./)\n  })\n})\n\ndescribe('JSON and FormData', () => {\n  const app = new Hono()\n  app.post(\n    '/',\n    validator('json', (value) => value),\n    validator('form', (value) => value),\n    async (c) => {\n      const jsonData = c.req.valid('json')\n      const formData = c.req.valid('form')\n      return c.json({\n        json: jsonData,\n        form: formData,\n      })\n    }\n  )\n\n  it('Should validate a JSON request', async () => {\n    const res = await app.request('/', {\n      method: 'POST',\n      body: JSON.stringify({ foo: 'bar' }),\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    })\n    expect(res.status).toBe(200)\n    const data = await res.json()\n    expect(data.json).toEqual({ foo: 'bar' })\n  })\n\n  it('Should validate a FormData request', async () => {\n    const form = new FormData()\n    form.append('foo', 'bar')\n    const res = await app.request('/', {\n      method: 'POST',\n      body: form,\n    })\n    expect(res.status).toBe(200)\n    const data = await res.json()\n    expect(data.form).toEqual({ foo: 'bar' })\n  })\n})\n\ndescribe('Cached contents', () => {\n  describe('json', () => {\n    const app = new Hono()\n    const bodyTypes = ['json', 'text', 'arrayBuffer', 'blob']\n\n    for (const type of bodyTypes) {\n      app.post(\n        `/${type}`,\n        async (c, next) => {\n          // @ts-expect-error not type safe\n          await c.req[type]()\n          await next()\n        },\n        validator('json', (value) => {\n          if (value instanceof Promise) {\n            throw new Error('Value is Promise')\n          }\n          return value\n        }),\n        async (c) => {\n          const data = await c.req.json()\n          return c.json(data, 200)\n        }\n      )\n    }\n\n    for (const type of bodyTypes) {\n      const endpoint = `/${type}`\n      it(`Should return the cached JSON content - ${endpoint}`, async () => {\n        const res = await app.request(endpoint, {\n          method: 'POST',\n          body: JSON.stringify({ message: 'Hello' }),\n          headers: {\n            'Content-Type': 'application/json',\n          },\n        })\n        expect(res.status).toBe(200)\n        expect(await res.json()).toEqual({ message: 'Hello' })\n      })\n    }\n  })\n\n  describe('Cached content', () => {\n    const app = new Hono()\n    const bodyTypes = ['formData', 'text', 'arrayBuffer', 'blob']\n\n    for (const type of bodyTypes) {\n      app.post(\n        `/${type}`,\n        async (c, next) => {\n          // @ts-expect-error not type safe\n          await c.req[type]()\n          await next()\n        },\n        validator('form', (value) => {\n          if (value instanceof Promise) {\n            throw new Error('Value is Promise')\n          }\n          return value\n        }),\n        async (c) => {\n          return c.json({ message: 'OK' }, 200)\n        }\n      )\n    }\n\n    for (const type of bodyTypes) {\n      const endpoint = `/${type}`\n      it(`Should return the cached FormData content - ${endpoint}`, async () => {\n        const form = new FormData()\n        form.append('message', 'Hello')\n        const res = await app.request(endpoint, {\n          method: 'POST',\n          body: form,\n        })\n        expect(res.status).toBe(200)\n        expect(await res.json()).toEqual({ message: 'OK' })\n      })\n    }\n  })\n})\n\ndescribe('Validator middleware with a custom validation function', () => {\n  const app = new Hono()\n\n  const validationFunction: ValidationFunction<{ id: string }, { id: number }> = (v) => {\n    return {\n      id: Number(v.id),\n    }\n  }\n\n  const route = app.post('/post', validator('json', validationFunction), (c) => {\n    const post = c.req.valid('json')\n    type Expected = {\n      id: number\n    }\n    type verify = Expect<Equal<Expected, typeof post>>\n    return c.json({\n      post,\n    })\n  })\n\n  type Expected = {\n    '/post': {\n      $post:\n        | {\n            input: {\n              json: {\n                id: number\n              }\n            }\n            output: {}\n            outputFormat: string\n            status: StatusCode\n          }\n        | {\n            input: {\n              json: {\n                id: number\n              }\n            }\n            output: {\n              post: {\n                id: number\n              }\n            }\n            outputFormat: 'json'\n            status: ContentfulStatusCode\n          }\n    }\n  }\n\n  type Actual = ExtractSchema<typeof route>\n  type verify2 = Expect<Equal<Expected, Actual>>\n\n  it('Should validate JSON with transformation and return 200 response', async () => {\n    const res = await app.request('http://localhost/post', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify({\n        id: '123',\n      }),\n    })\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({\n      post: {\n        id: 123,\n      },\n    })\n  })\n})\n\ndescribe('Validator middleware with Zod validates JSON', () => {\n  const app = new Hono()\n\n  const schema = z.object({\n    id: z.number(),\n    title: z.string(),\n  })\n\n  const route = app.post('/post', zodValidator('json', schema), (c) => {\n    const post = c.req.valid('json')\n    type Expected = {\n      id: number\n      title: string\n    }\n    type verify = Expect<Equal<Expected, typeof post>>\n    return c.json({\n      post: post,\n    })\n  })\n\n  type Expected = {\n    '/post': {\n      $post:\n        | {\n            input: {\n              json: {\n                id: number\n                title: string\n              }\n            }\n            output: 'Invalid!'\n            outputFormat: 'text'\n            status: 400\n          }\n        | {\n            input: {\n              json: {\n                id: number\n                title: string\n              }\n            }\n            output: {\n              post: {\n                id: number\n                title: string\n              }\n            }\n            outputFormat: 'json'\n            status: ContentfulStatusCode\n          }\n    }\n  }\n\n  type Actual = ExtractSchema<typeof route>\n\n  type verify2 = Expect<Equal<Expected, Actual>>\n\n  it('Should validate JSON and return 200 response', async () => {\n    const res = await app.request('http://localhost/post', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify({\n        id: 123,\n        title: 'Hello',\n      }),\n    })\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({\n      post: {\n        id: 123,\n        title: 'Hello',\n      },\n    })\n  })\n\n  it('Should validate JSON and return 400 response', async () => {\n    const res = await app.request('http://localhost/post', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify({\n        id: '123',\n        title: 'Hello',\n      }),\n    })\n    expect(res.status).toBe(400)\n    expect(await res.text()).toBe('Invalid!')\n  })\n})\n\ndescribe('Validator middleware with Zod validates Form data', () => {\n  const app = new Hono()\n\n  const schema = z.object({\n    id: z.string(),\n    title: z.string(),\n  })\n  app.post('/post', zodValidator('form', schema), (c) => {\n    const post = c.req.valid('form')\n    return c.json({\n      post: post,\n    })\n  })\n\n  it('Should validate Form data and return 200 response', async () => {\n    const form = new FormData()\n    form.append('id', '123')\n    form.append('title', 'Hello')\n    const res = await app.request('http://localhost/post', {\n      method: 'POST',\n      body: form,\n    })\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({\n      post: {\n        id: '123',\n        title: 'Hello',\n      },\n    })\n  })\n\n  it('Should validate Form data and return 400 response', async () => {\n    const res = await app.request('http://localhost/post', {\n      method: 'POST',\n    })\n    expect(res.status).toBe(400)\n    expect(await res.text()).toBe('Invalid!')\n  })\n})\n\ndescribe('Validator middleware with Zod validates query params', () => {\n  const app = new Hono()\n\n  const schema = z.object({\n    page: z\n      .string()\n      .refine((v) => {\n        return !isNaN(Number(v))\n      })\n      .transform((v) => {\n        return Number(v)\n      }),\n    tag: z.array(z.string()),\n  })\n\n  app.get('/search', zodValidator('query', schema), (c) => {\n    const res = c.req.valid('query')\n    return c.json({\n      page: res.page,\n      tags: res.tag,\n    })\n  })\n\n  it('Should validate query params and return 200 response', async () => {\n    const res = await app.request('http://localhost/search?page=123&tag=a&tag=b')\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({\n      page: 123,\n      tags: ['a', 'b'],\n    })\n  })\n\n  it('Should validate query params and return 400 response', async () => {\n    const res = await app.request('http://localhost/search?page=onetwothree')\n    expect(res.status).toBe(400)\n    expect(await res.text()).toBe('Invalid!')\n  })\n})\n\ndescribe('Validator middleware with Zod validates param', () => {\n  const app = new Hono()\n\n  const schema = z.object({\n    id: z\n      .string()\n      .regex(/^[0-9]+$/)\n      .transform((v) => {\n        return Number(v)\n      }),\n    title: z.string(),\n  })\n  app.get('/users/:id/books/:title', zodValidator('param', schema), (c) => {\n    const param = c.req.valid('param')\n    return c.json({\n      param: param,\n    })\n  })\n\n  it('Should validate Form data and return 200 response', async () => {\n    const res = await app.request('http://localhost/users/123/books/Hello')\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({\n      param: {\n        id: 123,\n        title: 'Hello',\n      },\n    })\n  })\n\n  it('Should validate Form data and return 400 response', async () => {\n    const res = await app.request('http://localhost/users/0.123/books/Hello')\n    expect(res.status).toBe(400)\n    expect(await res.text()).toBe('Invalid!')\n  })\n})\n\ndescribe('Validator middleware with Zod validates header values', () => {\n  const app = new Hono()\n\n  const schema = z.object({\n    'x-request-id': z.string().uuid(),\n  })\n\n  app.get('/ping', zodValidator('header', schema), (c) => {\n    const data = c.req.valid('header')\n    const xRequestId = data['x-request-id']\n    return c.json({\n      xRequestId,\n    })\n  })\n\n  it('Should validate header values and return 200 response', async () => {\n    const res = await app.request('/ping', {\n      headers: {\n        'x-request-id': '6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b',\n      },\n    })\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({\n      xRequestId: '6ec0bd7f-11c0-43da-975e-2a8ad9ebae0b',\n    })\n  })\n\n  it('Should validate header values and return 400 response', async () => {\n    const res = await app.request('http://localhost/ping', {\n      headers: {\n        'x-request-id': 'invalid-key',\n      },\n    })\n    expect(res.status).toBe(400)\n    expect(await res.text()).toBe('Invalid!')\n  })\n})\n\ndescribe('Validator middleware with Zod validates cookies', () => {\n  const app = new Hono()\n\n  const schema = z.object({\n    debug: z.enum(['0', '1']),\n  })\n\n  app.get('/api/user', zodValidator('cookie', schema), (c) => {\n    const { debug } = c.req.valid('cookie')\n    return c.json({\n      debug,\n    })\n  })\n\n  it('Should validate cookies and return 200 response', async () => {\n    const res = await app.request('/api/user', {\n      headers: {\n        Cookie: 'debug=0',\n      },\n    })\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({\n      debug: '0',\n    })\n  })\n\n  it('Should validate cookies and return 400 response', async () => {\n    const res = await app.request('/api/user', {\n      headers: {\n        Cookie: 'debug=true',\n      },\n    })\n    expect(res.status).toBe(400)\n    expect(await res.text()).toBe('Invalid!')\n  })\n})\n\ndescribe('Validator middleware with Zod multiple validators', () => {\n  const app = new Hono<{ Variables: { id: number } }>()\n  const route = app.post(\n    '/posts',\n    zodValidator(\n      'query',\n      z.object({\n        page: z\n          .string()\n          .refine((v) => {\n            return !isNaN(Number(v))\n          })\n          .transform((v) => {\n            return Number(v)\n          }),\n      })\n    ),\n    zodValidator(\n      'form',\n      z.object({\n        title: z.string(),\n      })\n    ),\n    (c) => {\n      const id = c.get('id')\n      type verify = Expect<Equal<number, typeof id>>\n      const formValidatedData = c.req.valid('form')\n      type verify2 = Expect<Equal<{ title: string }, typeof formValidatedData>>\n      const { page } = c.req.valid('query')\n      const { title } = c.req.valid('form')\n      return c.json({ page, title })\n    }\n  )\n\n  type Actual = ExtractSchema<typeof route>\n\n  type Expected = {\n    '/posts': {\n      $post:\n        | {\n            input: {\n              query: {\n                page: string\n              }\n            } & {\n              form: {\n                title: string\n              }\n            }\n            output: 'Invalid!'\n            outputFormat: 'text'\n            status: 400\n          }\n        | {\n            input: {\n              query: {\n                page: string\n              }\n            } & {\n              form: {\n                title: string\n              }\n            }\n            output: {\n              page: number\n              title: string\n            }\n            outputFormat: 'json'\n            status: ContentfulStatusCode\n          }\n    }\n  }\n\n  type verify = Expect<Equal<Expected, Actual>>\n\n  it('Should validate both query param and form data and return 200 response', async () => {\n    const form = new FormData()\n    form.append('title', 'Hello')\n    const res = await app.request('http://localhost/posts?page=2', {\n      method: 'POST',\n      body: form,\n    })\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({\n      page: 2,\n      title: 'Hello',\n    })\n  })\n\n  it('Should validate both query param and form data and return 400 response', async () => {\n    const res = await app.request('http://localhost/posts?page=2', {\n      method: 'POST',\n    })\n    expect(res.status).toBe(400)\n    expect(await res.text()).toBe('Invalid!')\n  })\n})\n\nit('With path parameters', () => {\n  const app = new Hono()\n\n  const route = app.put(\n    '/posts/:id',\n    validator('param', () => {\n      return {\n        id: '123',\n      }\n    }),\n    validator('form', () => {\n      return {\n        title: 'Foo',\n      }\n    }),\n    (c) => {\n      return c.text('Valid!')\n    }\n  )\n\n  type Expected = {\n    '/posts/:id': {\n      $put: {\n        input: {\n          form: {\n            title: FormValue | FormValue[]\n          }\n        } & {\n          param: {\n            id: string\n          }\n        }\n        output: 'Valid!'\n        outputFormat: 'text'\n        status: ContentfulStatusCode\n      }\n    }\n  }\n\n  type Actual = ExtractSchema<typeof route>\n  type verify = Expect<Equal<Expected, Actual>>\n})\n\nit('`on`', () => {\n  const app = new Hono()\n\n  const route = app.on(\n    'PURGE',\n    '/purge',\n    validator('form', () => {\n      return {\n        tag: 'foo',\n      }\n    }),\n    validator('query', () => {\n      return {\n        q: 'bar',\n      }\n    }),\n    (c) => {\n      return c.json({\n        success: true,\n      })\n    }\n  )\n\n  type Expected = {\n    '/purge': {\n      $purge: {\n        input: {\n          form: {\n            tag: FormValue | FormValue[]\n          }\n        } & {\n          query: {\n            q: string | string[]\n          }\n        }\n        output: {\n          success: true\n        }\n        outputFormat: 'json'\n        status: ContentfulStatusCode\n      }\n    }\n  }\n\n  type Actual = ExtractSchema<typeof route>\n  type verify = Expect<Equal<Expected, Actual>>\n})\n\nit('`app.on`', () => {\n  const app = new Hono()\n\n  const route = app\n    .get(\n      '/posts',\n      validator('query', () => {\n        return {\n          page: '2',\n        }\n      }),\n      (c) => {\n        return c.json({\n          posts: [\n            {\n              title: 'foo',\n            },\n          ],\n        })\n      }\n    )\n    .post(\n      validator('json', () => {\n        return {\n          title: 'Hello',\n        }\n      }),\n      validator('query', () => {\n        return {\n          title: 'Hello',\n        }\n      }),\n      (c) => {\n        return c.json({\n          success: true,\n        })\n      }\n    )\n\n  type Actual = ExtractSchema<typeof route>\n  //type verify = Expect<Equal<Expected, Actual>>\n})\n\ndescribe('Clone Request object', () => {\n  describe('json', () => {\n    const app = new Hono()\n    app.post(\n      '/',\n      validator('json', () => {\n        return {\n          foo: 'bar',\n        }\n      }),\n      async (c) => {\n        // `c.req.json()` should not throw the error\n        await c.req.json()\n        // `c.req.text()` should not throw the error\n        await c.req.text()\n        // `c.req.arrayBuffer()` should not throw the error\n        await c.req.arrayBuffer()\n        // `c.req.blob()` should not throw the error\n        await c.req.blob()\n        return c.text('foo')\n      }\n    )\n\n    it('Should not throw the error with c.req.json()', async () => {\n      const req = new Request('http://localhost', {\n        method: 'POST',\n        headers: {\n          'Content-Type': 'application/json',\n        },\n        body: JSON.stringify({ foo: 'bar' }),\n      })\n      const res = await app.request(req)\n      expect(res.status).toBe(200)\n    })\n  })\n\n  describe('form', () => {\n    const app = new Hono()\n    app.post(\n      '/',\n      validator('form', () => {\n        return {\n          foo: 'bar',\n        }\n      }),\n      async (c) => {\n        // `c.req.parseBody()` should not throw the error\n        await c.req.parseBody()\n        // `c.req.text()` should not throw the error\n        await c.req.text()\n        // `c.req.arrayBuffer()` should not throw the error\n        await c.req.arrayBuffer()\n        // `c.req.blob()` should not throw the error\n        await c.req.blob()\n        return c.text('foo')\n      }\n    )\n\n    app.post(\n      '/cached',\n      async (c, next) => {\n        await c.req.parseBody()\n        await next()\n      },\n      validator('form', (value) => {\n        if (value instanceof FormData) {\n          throw new Error('The value should not be a FormData')\n        }\n        return value\n      }),\n      (c) => {\n        const v = c.req.valid('form')\n        return c.json(v)\n      }\n    )\n\n    it('Should not throw the error with c.req.parseBody()', async () => {\n      const body = new FormData()\n      body.append('foo', 'bar')\n      const req = new Request('http://localhost', {\n        method: 'POST',\n        body: body,\n      })\n      const res = await app.request(req)\n      expect(res.status).toBe(200)\n    })\n\n    it('Should not be an instance of FormData if the formData is cached', async () => {\n      const body = new FormData()\n      body.append('foo', 'bar')\n      const req = new Request('http://localhost/cached', {\n        method: 'POST',\n        body: body,\n      })\n      const res = await app.request(req)\n      expect(res.status).toBe(200)\n      expect(await res.json()).toEqual({ foo: 'bar' })\n    })\n  })\n})\n\ndescribe('Async validator function', () => {\n  const app = new Hono()\n\n  app.get(\n    '/posts',\n    validator('query', async () => {\n      return {\n        page: '1',\n      }\n    }),\n    (c) => {\n      const { page } = c.req.valid('query')\n      return c.json({ page })\n    }\n  )\n\n  it('Should get the values from the async function', async () => {\n    const res = await app.request('/posts')\n    expect(res.status).toBe(200)\n    expect(await res.json()).toEqual({\n      page: '1',\n    })\n  })\n})\n\ndescribe('Validator with using Zod directly', () => {\n  it('Should exclude Response & TypedResponse type', () => {\n    const testSchema = z.object({\n      name: z.string(),\n      age: z.number(),\n      type: z.enum(['a']),\n    })\n    const app = new Hono()\n\n    const route = app.post(\n      '/posts',\n      validator('json', (value, c) => {\n        const parsed = testSchema.safeParse(value)\n        if (!parsed.success) {\n          return c.json({ foo: 'bar' }, 401)\n        }\n        return parsed.data\n      }),\n      (c) => {\n        const data = c.req.valid('json')\n        expectTypeOf(data.name).toEqualTypeOf<string>()\n        return c.json(\n          {\n            message: 'Created!',\n          },\n          201\n        )\n      }\n    )\n\n    expectTypeOf<ExtractSchemaForStatusCode<typeof route, 201>>().toEqualTypeOf<{\n      '/posts': {\n        $post: {\n          input: {\n            json: {\n              type: 'a'\n              name: string\n              age: number\n            }\n          }\n          output: {\n            message: string\n          }\n          outputFormat: 'json'\n          status: 201\n        }\n      }\n    }>()\n\n    expectTypeOf<ExtractSchemaForStatusCode<typeof route, 401>>().toEqualTypeOf<{\n      '/posts': {\n        $post: {\n          input: {\n            json: {\n              type: 'a'\n              name: string\n              age: number\n            }\n          }\n          output: {\n            foo: string\n          }\n          outputFormat: 'json'\n          status: 401\n        }\n      }\n    }>()\n  })\n})\n\ndescribe('Transform', () => {\n  it('Should be number when the type is transformed', () => {\n    const route = new Hono().get(\n      '/',\n      validator('query', async () => {\n        return {\n          page: 1,\n        }\n      }),\n      (c) => {\n        const { page } = c.req.valid('query')\n        expectTypeOf(page).toEqualTypeOf<number>()\n        return c.json({ page })\n      }\n    )\n\n    type Expected = {\n      '/': {\n        $get: {\n          input: {\n            query: {\n              page: string | string[]\n            }\n          }\n          output: {\n            page: number\n          }\n          outputFormat: 'json'\n          status: ContentfulStatusCode\n        }\n      }\n    }\n\n    type Actual = ExtractSchema<typeof route>\n    type verify = Expect<Equal<Expected, Actual>>\n\n    // Temporary: let's see what the actual type is\n    type TestActual = Actual\n  })\n\n  it('Should be number and union when the type is transformed', () => {\n    const app = new Hono()\n    const route = app.get(\n      '/',\n      validator('query', () => {\n        return {\n          page: 1,\n          orderBy: 'asc',\n        } as {\n          page: number\n          orderBy: 'asc' | 'desc'\n          ordreByWithdefault?: 'asc' | 'desc' | undefined\n        }\n      }),\n      (c) => {\n        const { page, orderBy, ordreByWithdefault } = c.req.valid('query')\n        expectTypeOf(page).toEqualTypeOf<number>()\n        expectTypeOf(orderBy).toEqualTypeOf<'asc' | 'desc'>()\n        expectTypeOf(ordreByWithdefault).toEqualTypeOf<'asc' | 'desc' | undefined>()\n        return c.json({ page, orderBy, ordreByWithdefault })\n      }\n    )\n\n    type Expected = {\n      '/': {\n        $get: {\n          input: {\n            query: {\n              page: string | string[]\n              orderBy: 'asc' | 'desc'\n              ordreByWithdefault?: 'asc' | 'desc' | undefined\n            }\n          }\n          output: {\n            page: number\n            orderBy: 'asc' | 'desc'\n            ordreByWithdefault: 'asc' | 'desc' | undefined\n          }\n          outputFormat: 'json'\n          status: ContentfulStatusCode\n        }\n      }\n    }\n\n    type Actual = ExtractSchema<typeof route>\n    type verify = Expect<Equal<Expected, Actual>>\n\n    // Temporary: let's see what the actual type is\n    type TestActual = Actual\n  })\n})\n\ndescribe('Raw Request cloning after validation', () => {\n  it('Should allow the `cloneRawRequest` util to clone the request object after validation', async () => {\n    const app = new Hono()\n\n    app.post(\n      '/json-validation',\n      validator('json', (data) => data),\n      async (c) => {\n        const clonedReq = await cloneRawRequest(c.req)\n        const clonedJSON = await clonedReq.json()\n\n        return c.json({\n          originalMethod: c.req.raw.method,\n          clonedMethod: clonedReq.method,\n          clonedUrl: clonedReq.url,\n          clonedHeaders: {\n            contentType: clonedReq.headers.get('Content-Type'),\n            customHeader: clonedReq.headers.get('X-Custom-Header'),\n          },\n          originalCache: c.req.raw.cache,\n          clonedCache: clonedReq.cache,\n          originalCredentials: c.req.raw.credentials,\n          clonedCredentials: clonedReq.credentials,\n          originalMode: c.req.raw.mode,\n          clonedMode: clonedReq.mode,\n          originalRedirect: c.req.raw.redirect,\n          clonedRedirect: clonedReq.redirect,\n          originalReferrerPolicy: c.req.raw.referrerPolicy,\n          clonedReferrerPolicy: clonedReq.referrerPolicy,\n          cloned: JSON.stringify(clonedJSON) === JSON.stringify(await c.req.json()),\n          payload: clonedJSON,\n        })\n      }\n    )\n\n    const testData = { message: 'test', userId: 123 }\n    const res = await app.request('/json-validation', {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        'X-Custom-Header': 'test-value',\n      },\n      body: JSON.stringify(testData),\n      cache: 'no-cache',\n      credentials: 'include',\n      mode: 'cors',\n      redirect: 'follow',\n      referrerPolicy: 'origin',\n    })\n\n    expect(res.status).toBe(200)\n\n    const result = await res.json()\n\n    expect(result.originalMethod).toBe('POST')\n    expect(result.clonedMethod).toBe('POST')\n    expect(result.clonedUrl).toBe('http://localhost/json-validation')\n    expect(result.clonedHeaders.contentType).toBe('application/json')\n    expect(result.clonedHeaders.customHeader).toBe('test-value')\n    expect(result.clonedCache).toBe(result.originalCache)\n    expect(result.clonedCredentials).toBe(result.originalCredentials)\n    expect(result.clonedMode).toBe(result.originalMode)\n    expect(result.clonedRedirect).toBe(result.originalRedirect)\n    expect(result.clonedReferrerPolicy).toBe(result.originalReferrerPolicy)\n    expect(result.cloned).toBe(true)\n    expect(result.payload).toMatchObject(testData)\n  })\n})\n\ndescribe('Form validator prototype pollution prevention', () => {\n  it('should store __proto__ as data and not misdetect inherited keys', async () => {\n    const app = new Hono()\n    app.post(\n      '/form',\n      validator('form', (value) => value),\n      (c) => c.json(c.req.valid('form'))\n    )\n\n    const form = new FormData()\n    form.append('__proto__', 'evil')\n    form.append('toString', 'hello')\n\n    const res = await app.request('/form', { method: 'POST', body: form })\n    const result = await res.json()\n    expect(result['__proto__']).toBe('evil')\n    expect(result['toString']).toBe('hello')\n  })\n})\n"
  },
  {
    "path": "src/validator/validator.ts",
    "content": "import type { Context } from '../context'\nimport { getCookie } from '../helper/cookie'\nimport { HTTPException } from '../http-exception'\nimport type { Env, MiddlewareHandler, TypedResponse, ValidationTargets, FormValue } from '../types'\nimport type { BodyData } from '../utils/body'\nimport { bufferToFormData } from '../utils/buffer'\nimport type { InferInput } from './utils'\n\ntype ValidationTargetKeysWithBody = 'form' | 'json'\ntype ValidationTargetByMethod<M> = M extends 'get' | 'head' // GET and HEAD request must not have a body content.\n  ? Exclude<keyof ValidationTargets, ValidationTargetKeysWithBody>\n  : keyof ValidationTargets\n\nexport type ValidationFunction<\n  InputType,\n  OutputType,\n  E extends Env = {},\n  P extends string = string,\n> = (\n  value: InputType,\n  c: Context<E, P>\n) => OutputType | TypedResponse | Promise<OutputType> | Promise<TypedResponse>\n\nconst jsonRegex = /^application\\/([a-z-\\.]+\\+)?json(;\\s*[a-zA-Z0-9\\-]+\\=([^;]+))*$/\nconst multipartRegex = /^multipart\\/form-data(;\\s?boundary=[a-zA-Z0-9'\"()+_,\\-./:=?]+)?$/\nconst urlencodedRegex = /^application\\/x-www-form-urlencoded(;\\s*[a-zA-Z0-9\\-]+\\=([^;]+))*$/\n\nexport type ExtractValidationResponse<VF> = VF extends (value: any, c: any) => infer R\n  ? R extends Promise<infer PR>\n    ? PR extends TypedResponse<infer T, infer S, infer F>\n      ? TypedResponse<T, S, F>\n      : PR extends Response\n        ? PR\n        : PR extends undefined\n          ? never // undefined → never\n          : never // anything else → never\n    : R extends TypedResponse<infer T, infer S, infer F>\n      ? TypedResponse<T, S, F>\n      : R extends Response\n        ? R\n        : R extends undefined\n          ? never // undefined → never\n          : never // anything else → never\n  : never // Can't extract → never\n\nexport const validator = <\n  InputType,\n  P extends string,\n  M extends string,\n  U extends ValidationTargetByMethod<M>,\n  P2 extends string = P,\n  // Capture the actual validation function as a type\n  VF extends (\n    value: unknown extends InputType ? ValidationTargets[U] : InputType,\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    c: Context<any, P2>\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  ) => any = (\n    value: unknown extends InputType ? ValidationTargets[U] : InputType,\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    c: Context<any, P2>\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  ) => any,\n  V extends {\n    in: {\n      [K in U]: K extends 'json'\n        ? unknown extends InputType\n          ? ExtractValidatorOutput<VF>\n          : InputType\n        : InferInput<ExtractValidatorOutput<VF>, K, FormValue>\n    }\n    out: { [K in U]: ExtractValidatorOutput<VF> }\n  } = {\n    in: {\n      [K in U]: K extends 'json'\n        ? unknown extends InputType\n          ? ExtractValidatorOutput<VF>\n          : InputType\n        : InferInput<ExtractValidatorOutput<VF>, K, FormValue>\n    }\n    out: { [K in U]: ExtractValidatorOutput<VF> }\n  },\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  E extends Env = any,\n>(\n  target: U,\n  validationFunc: VF\n): MiddlewareHandler<E, P, V, ExtractValidationResponse<VF>> => {\n  return async (c, next) => {\n    let value = {}\n    const contentType = c.req.header('Content-Type')\n\n    switch (target) {\n      case 'json':\n        if (!contentType || !jsonRegex.test(contentType)) {\n          break\n        }\n        try {\n          value = await c.req.json()\n        } catch {\n          const message = 'Malformed JSON in request body'\n          throw new HTTPException(400, { message })\n        }\n        break\n      case 'form': {\n        if (\n          !contentType ||\n          !(multipartRegex.test(contentType) || urlencodedRegex.test(contentType))\n        ) {\n          break\n        }\n\n        let formData: FormData\n\n        if (c.req.bodyCache.formData) {\n          formData = await c.req.bodyCache.formData\n        } else {\n          try {\n            const arrayBuffer = await c.req.arrayBuffer()\n            formData = await bufferToFormData(arrayBuffer, contentType)\n            c.req.bodyCache.formData = formData\n          } catch (e) {\n            let message = 'Malformed FormData request.'\n            message += e instanceof Error ? ` ${e.message}` : ` ${String(e)}`\n            throw new HTTPException(400, { message })\n          }\n        }\n\n        const form: BodyData<{ all: true }> = Object.create(null)\n        formData.forEach((value, key) => {\n          if (key.endsWith('[]')) {\n            ;((form[key] ??= []) as unknown[]).push(value)\n          } else if (Array.isArray(form[key])) {\n            ;(form[key] as unknown[]).push(value)\n          } else if (Object.hasOwn(form, key)) {\n            form[key] = [form[key] as string | File, value]\n          } else {\n            form[key] = value\n          }\n        })\n        value = form\n        break\n      }\n      case 'query':\n        value = Object.fromEntries(\n          Object.entries(c.req.queries()).map(([k, v]) => {\n            return v.length === 1 ? [k, v[0]] : [k, v]\n          })\n        )\n        break\n      case 'param':\n        value = c.req.param() as Record<string, string>\n        break\n      case 'header':\n        value = c.req.header()\n        break\n      case 'cookie':\n        value = getCookie(c)\n        break\n    }\n\n    const res = await validationFunc(value as never, c as never)\n\n    if (res instanceof Response) {\n      return res as ExtractValidationResponse<VF>\n    }\n\n    c.req.addValidatedData(target, res as never)\n\n    return (await next()) as ExtractValidationResponse<VF>\n  }\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type ExtractValidatorOutput<VF> = VF extends (value: any, c: any) => infer R\n  ? R extends Promise<infer PR>\n    ? // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      PR extends Response | TypedResponse<any, any, any>\n      ? never\n      : PR\n    : // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      R extends Response | TypedResponse<any, any, any>\n      ? never\n      : R\n  : never\n"
  },
  {
    "path": "tsconfig.base.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"target\": \"ES2022\",\n    \"declaration\": true,\n    \"moduleResolution\": \"Bundler\",\n    \"outDir\": \"${configDir}/dist\",\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false\n  }\n}\n"
  },
  {
    "path": "tsconfig.build.json",
    "content": "{\n  \"extends\": \"./tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"module\": \"ES2020\",\n    \"rootDir\": \"./src/\",\n    \"outDir\": \"./dist/types/\",\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true\n  },\n  \"include\": [\n    \"src/**/*.ts\",\n    \"src/**/*.mts\"\n  ],\n  \"exclude\": [\n    \"src/mod.ts\",\n    \"src/helper.ts\",\n    \"src/middleware.ts\",\n    \"src/deno/**/*.ts\",\n    \"src/test-utils/*.ts\",\n    \"src/**/*.test.ts\",\n    \"src/**/*.test.tsx\",\n  ]\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"references\": [\n    { \"path\": \"./tsconfig.build.json\" },\n    { \"path\": \"./tsconfig.spec.json\" },\n    { \"path\": \"./perf-measures/type-check/scripts/tsconfig.json\" },\n    { \"path\": \"./runtime-tests/bun/tsconfig.json\" },\n    { \"path\": \"./runtime-tests/fastly/tsconfig.json\" },\n    { \"path\": \"./runtime-tests/lambda/tsconfig.json\" },\n    { \"path\": \"./runtime-tests/lambda-edge/tsconfig.json\" },\n    { \"path\": \"./runtime-tests/node/tsconfig.json\" },\n    { \"path\": \"./runtime-tests/workerd/tsconfig.json\" }\n  ]\n}\n"
  },
  {
    "path": "tsconfig.spec.json",
    "content": "{\n  \"extends\": \"./tsconfig.base.json\",\n  \"compilerOptions\": {\n    \"jsx\": \"react-jsx\",\n    \"jsxImportSource\": \"hono/jsx\",\n    \"noEmit\": true,\n    \"rootDir\": \"./src\",\n    \"types\": [\"vitest/globals\"]\n  },\n  \"include\": [\"src\", \"src/middleware/jwk/keys.test.json\"]\n}\n"
  },
  {
    "path": "vitest.config.ts",
    "content": "import { configDefaults, defineConfig } from 'vitest/config'\n\nexport default defineConfig({\n  test: {\n    globals: true,\n    setupFiles: ['./.vitest.config/setup-vitest.ts'],\n    coverage: {\n      enabled: true,\n      provider: 'v8',\n      reportsDirectory: './coverage/raw/default',\n      reporter: ['json', 'text', 'html'],\n      exclude: [\n        ...(configDefaults.coverage.exclude ?? []),\n        'benchmarks',\n        'runtime-tests',\n        'build/build.ts',\n        'src/test-utils',\n        'perf-measures',\n\n        // types are compile-time only, so their coverage cannot be measured\n        'src/**/types.ts',\n        'src/jsx/intrinsic-elements.ts',\n        'src/utils/http-status.ts',\n      ],\n    },\n    projects: [\n      './runtime-tests/*/vitest.config.ts',\n      {\n        esbuild: {\n          jsx: 'automatic',\n          jsxImportSource: './src/jsx',\n        },\n        extends: true,\n        test: {\n          exclude: [...configDefaults.exclude, '**/sandbox/**', '**/*.case.test.*'],\n          include: [\n            'src/**/(*.)+(spec|test).+(ts|tsx|js)',\n            'scripts/**/(*.)+(spec|test).+(ts|tsx|js)',\n            'build/**/(*.)+(spec|test).+(ts|tsx|js)',\n          ],\n          name: 'main',\n        },\n      },\n      {\n        esbuild: {\n          jsx: 'automatic',\n          jsxImportSource: './src/jsx',\n        },\n        extends: true,\n        test: {\n          include: ['src/jsx/dom/**/(*.)+(spec|test).+(ts|tsx|js)', 'src/jsx/hooks/dom.test.tsx'],\n          name: 'jsx-runtime-default',\n        },\n      },\n      {\n        esbuild: {\n          jsx: 'automatic',\n          jsxImportSource: './src/jsx/dom',\n        },\n        extends: true,\n        test: {\n          include: ['src/jsx/dom/**/(*.)+(spec|test).+(ts|tsx|js)', 'src/jsx/hooks/dom.test.tsx'],\n          name: 'jsx-runtime-dom',\n        },\n      },\n    ],\n  },\n})\n"
  }
]