mirror of
https://github.com/honojs/hono.git
synced 2024-11-21 18:18:57 +01:00
Merge branch 'main' into hide-annotations
This commit is contained in:
commit
7c1e71b658
2
.github/ISSUE_TEMPLATE/1-bug-report.yml
vendored
2
.github/ISSUE_TEMPLATE/1-bug-report.yml
vendored
@ -18,7 +18,7 @@ body:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: What runtime/platform is your app running on?
|
||||
label: What runtime/platform is your app running on? (with version if possible)
|
||||
placeholder: Cloudflare Workers, Deno, Bun, etc.
|
||||
validations:
|
||||
required: true
|
||||
|
70
.github/workflows/ci.yml
vendored
70
.github/workflows/ci.yml
vendored
@ -20,6 +20,7 @@ jobs:
|
||||
- bun
|
||||
- fastly
|
||||
- node
|
||||
- deno
|
||||
- workerd
|
||||
- lambda
|
||||
- lambda-edge
|
||||
@ -50,9 +51,9 @@ jobs:
|
||||
run: |
|
||||
echo "::remove-matcher owner=eslint-compact::"
|
||||
echo "::remove-matcher owner=eslint-stylish::"
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: '1.1.16'
|
||||
bun-version: '1.1.33'
|
||||
- run: bun install
|
||||
- run: bun run format
|
||||
- run: bun run lint
|
||||
@ -71,7 +72,7 @@ jobs:
|
||||
- uses: denoland/setup-deno@v2
|
||||
with:
|
||||
deno-version: v2.x
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bunx jsr publish --dry-run
|
||||
|
||||
deno:
|
||||
@ -85,6 +86,8 @@ jobs:
|
||||
- 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
|
||||
- run: deno test -c runtime-tests/deno-jsx/deno.precompile.json --coverage=coverage/raw/deno-precompile-jsx runtime-tests/deno-jsx
|
||||
- run: deno test -c runtime-tests/deno-jsx/deno.react-jsx.json --coverage=coverage/raw/deno-react-jsx runtime-tests/deno-jsx
|
||||
- run: grep -R '"url":' coverage | grep -v runtime-tests | sed -e 's/.*file:..//;s/.,//' | xargs deno cache --unstable-sloppy-imports
|
||||
- run: deno coverage --lcov > coverage/deno-runtime-coverage-lcov.info
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-deno
|
||||
@ -95,9 +98,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: '1.1.16'
|
||||
bun-version: '1.1.33'
|
||||
- run: bun run test:bun
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@ -108,7 +111,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bun install
|
||||
- run: bun run build
|
||||
- run: bun run test:fastly
|
||||
@ -128,7 +131,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bun install
|
||||
- run: bun run build
|
||||
- run: bun run test:node
|
||||
@ -146,7 +149,7 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bun install
|
||||
- run: bun run build
|
||||
- run: bun run test:workerd
|
||||
@ -160,7 +163,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bun install
|
||||
- run: bun run build
|
||||
- run: bun run test:lambda
|
||||
@ -174,7 +177,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bun install --frozen-lockfile
|
||||
- run: bun run build
|
||||
- run: bun run test:lambda-edge
|
||||
@ -184,11 +187,12 @@ jobs:
|
||||
path: coverage/
|
||||
|
||||
perf-measures-type-check-on-pr:
|
||||
name: 'Type Check on PR'
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bun install
|
||||
- uses: actions/cache/restore@v4
|
||||
with:
|
||||
@ -211,11 +215,12 @@ jobs:
|
||||
name: display comparison
|
||||
|
||||
perf-measures-type-check-on-main:
|
||||
name: 'Type Check on Main'
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bun install
|
||||
- run: bun scripts/generate-app.ts
|
||||
working-directory: perf-measures/type-check
|
||||
@ -225,3 +230,44 @@ jobs:
|
||||
with:
|
||||
path: perf-measures/type-check/previous-result.txt
|
||||
key: type-check-perf-previous-result-${{ github.sha }}
|
||||
|
||||
perf-measures-bundle-check-on-pr:
|
||||
name: 'Bundle Check on PR'
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bun install
|
||||
- run: bun run build
|
||||
- run: bunx esbuild --minify --bundle dist/index.js --format=esm --outfile=perf-measures/bundle-check/generated/after.js
|
||||
- uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: perf-measures/bundle-check/generated/before.js
|
||||
restore-keys: |
|
||||
perf-measures-bundle-check-previous-file-
|
||||
key: perf-measures-bundle-check-previous-file-
|
||||
- run: |
|
||||
{
|
||||
echo 'COMPARISON<<EOF'
|
||||
bun scripts/process-results.ts | column -s '|' -t
|
||||
echo 'EOF'
|
||||
} >> "$GITHUB_ENV"
|
||||
working-directory: perf-measures/bundle-check
|
||||
- run: echo "$COMPARISON"
|
||||
name: display comparison
|
||||
|
||||
perf-measures-bundle-check-on-main:
|
||||
name: 'Bundle Check on Main'
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- run: bun install
|
||||
- run: bun run build
|
||||
- run: bunx esbuild --minify --bundle dist/index.js --format=esm --outfile=perf-measures/bundle-check/generated/before.js
|
||||
- uses: actions/cache/save@v4
|
||||
with:
|
||||
path: perf-measures/bundle-check/generated/before.js
|
||||
key: perf-measures-bundle-check-previous-file-${{ github.sha }}
|
26
.github/workflows/no-response.yml
vendored
Normal file
26
.github/workflows/no-response.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
name: Close stale issues with "not bug" label
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Close stale issues with "not bug" label
|
||||
uses: actions/stale@v8
|
||||
with:
|
||||
days-before-stale: 7
|
||||
days-before-close: 2
|
||||
stale-issue-message: 'This issue has been marked as stale due to inactivity.'
|
||||
close-issue-message: 'Closing this issue due to inactivity.'
|
||||
exempt-issue-labels: ''
|
||||
stale-issue-label: 'stale'
|
||||
only-labels: 'not bug'
|
||||
operations-per-run: 30
|
||||
remove-stale-when-updated: true
|
9
.github/workflows/release.yml
vendored
9
.github/workflows/release.yml
vendored
@ -16,11 +16,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install deno
|
||||
uses: denoland/setup-deno@v1
|
||||
uses: denoland/setup-deno@v2
|
||||
with:
|
||||
deno-version: v1.x
|
||||
|
||||
deno-version: v2.x
|
||||
- run: deno install --no-lock --allow-scripts
|
||||
- name: Publish to JSR
|
||||
run: deno run -A jsr:@david/publish-on-tag@0.1.3
|
||||
run: deno run -A jsr:@david/publish-on-tag@0.1.4
|
||||
|
@ -47,7 +47,7 @@ npm create hono@latest
|
||||
## Features
|
||||
|
||||
- **Ultrafast** 🚀 - The router `RegExpRouter` is really fast. Not using linear loops. Fast.
|
||||
- **Lightweight** 🪶 - The `hono/tiny` preset is under 13kB. Hono has zero dependencies and uses only the Web Standard API.
|
||||
- **Lightweight** 🪶 - The `hono/tiny` preset is under 12kB. Hono has zero dependencies and uses only the Web Standard API.
|
||||
- **Multi-runtime** 🌍 - Works on Cloudflare Workers, Fastly Compute, Deno, Bun, AWS Lambda, Lambda@Edge, or Node.js. The same code runs on all platforms.
|
||||
- **Batteries Included** 🔋 - Hono has built-in middleware, custom middleware, and third-party middleware. Batteries included.
|
||||
- **Delightful DX** 😃 - Super clean APIs. First-class TypeScript support. Now, we've got "Types".
|
||||
|
218
benchmarks/query-param/package-lock.json
generated
218
benchmarks/query-param/package-lock.json
generated
@ -7,9 +7,11 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-querystring": "^1.1.1",
|
||||
"mitata": "^0.1.6"
|
||||
"mitata": "^0.1.6",
|
||||
"qs": "^6.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/qs": "^6.9.17",
|
||||
"tsx": "^3.12.2"
|
||||
}
|
||||
},
|
||||
@ -395,12 +397,76 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
"version": "6.9.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz",
|
||||
"integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"set-function-length": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.17.14",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.14.tgz",
|
||||
@ -465,6 +531,34 @@
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-tsconfig": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.4.0.tgz",
|
||||
@ -474,11 +568,133 @@
|
||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-property-descriptors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mitata": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/mitata/-/mitata-0.1.6.tgz",
|
||||
"integrity": "sha512-VKQ0r3jriTOU9E2Z+mwbZrUmbg4Li4QyFfi7kfHKl6reZhGzL0AYlu3wE0VPXzIwA5xnFzmEQoBwCcNT8stUkA=="
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.3",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
|
||||
"integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.1.4",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
|
||||
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.7",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"object-inspect": "^1.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
|
@ -5,10 +5,12 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/qs": "^6.9.17",
|
||||
"tsx": "^3.12.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"fast-querystring": "^1.1.1",
|
||||
"mitata": "^0.1.6"
|
||||
"mitata": "^0.1.6",
|
||||
"qs": "^6.13.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { run, group, bench } from 'mitata'
|
||||
import fastQuerystring from './fast-querystring.mts'
|
||||
import hono from './hono.mts'
|
||||
import qs from './qs.mts'
|
||||
;[
|
||||
{
|
||||
url: 'http://example.com/?page=1',
|
||||
@ -36,6 +37,7 @@ import hono from './hono.mts'
|
||||
group(JSON.stringify(data), () => {
|
||||
bench('hono', () => hono(url, key))
|
||||
bench('fastQuerystring', () => fastQuerystring(url, key))
|
||||
bench('qs', () => qs(url, key))
|
||||
})
|
||||
})
|
||||
|
||||
|
12
benchmarks/query-param/src/qs.mts
Normal file
12
benchmarks/query-param/src/qs.mts
Normal file
@ -0,0 +1,12 @@
|
||||
import qs from 'qs'
|
||||
|
||||
const getQueryStringFromURL = (url: string): string => {
|
||||
const queryIndex = url.indexOf('?', 8)
|
||||
const result = queryIndex !== -1 ? url.slice(queryIndex + 1) : ''
|
||||
return result
|
||||
}
|
||||
|
||||
export default (url, key?) => {
|
||||
const data = qs.parse(getQueryStringFromURL(url))
|
||||
return key !== undefined ? data[key] : data
|
||||
}
|
@ -7,14 +7,15 @@
|
||||
|
||||
/// <reference types="bun-types/bun" />
|
||||
|
||||
import fs, { write } from 'fs'
|
||||
import path from 'path'
|
||||
import arg from 'arg'
|
||||
import { $, stdout } from 'bun'
|
||||
import { build } from 'esbuild'
|
||||
import type { Plugin, PluginBuild, BuildOptions } from 'esbuild'
|
||||
import * as glob from 'glob'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { removePrivateFields } from './remove-private-fields'
|
||||
import { $, stdout } from 'bun'
|
||||
import { validateExports } from './validate-exports'
|
||||
|
||||
const args = arg({
|
||||
'--watch': Boolean,
|
||||
@ -22,6 +23,14 @@ const args = arg({
|
||||
|
||||
const isWatch = args['--watch'] || false
|
||||
|
||||
const readJsonExports = (path: string) => JSON.parse(fs.readFileSync(path, 'utf-8')).exports
|
||||
|
||||
const [packageJsonExports, jsrJsonExports] = ['./package.json', './jsr.json'].map(readJsonExports)
|
||||
|
||||
// Validate exports of package.json and jsr.json
|
||||
validateExports(packageJsonExports, jsrJsonExports, 'jsr.json')
|
||||
validateExports(jsrJsonExports, packageJsonExports, 'package.json')
|
||||
|
||||
const entryPoints = glob.sync('./src/**/*.ts', {
|
||||
ignore: ['./src/**/*.test.ts', './src/mod.ts', './src/middleware.ts', './src/deno/**/*.ts'],
|
||||
})
|
||||
@ -96,7 +105,7 @@ let lastOutputLength = 0
|
||||
for (let i = 0; i < dtsEntries.length; i++) {
|
||||
const entry = dtsEntries[i]
|
||||
|
||||
const message = `Removing private fields(${i}/${dtsEntries.length}): ${entry}`
|
||||
const message = `Removing private fields(${i + 1}/${dtsEntries.length}): ${entry}`
|
||||
writer.write(`\r${' '.repeat(lastOutputLength)}`)
|
||||
lastOutputLength = message.length
|
||||
writer.write(`\r${message}`)
|
||||
|
31
build/validate-exports.test.ts
Normal file
31
build/validate-exports.test.ts
Normal file
@ -0,0 +1,31 @@
|
||||
/// <reference types="vitest/globals" />
|
||||
|
||||
import { validateExports } from './validate-exports'
|
||||
|
||||
const mockExports1 = {
|
||||
'./a': './a.ts',
|
||||
'./b': './b.ts',
|
||||
'./c/a': './c.ts',
|
||||
'./d/*': './d/*.ts',
|
||||
}
|
||||
|
||||
const mockExports2 = {
|
||||
'./a': './a.ts',
|
||||
'./b': './b.ts',
|
||||
'./c/a': './c.ts',
|
||||
'./d/a': './d/a.ts',
|
||||
}
|
||||
|
||||
const mockExports3 = {
|
||||
'./a': './a.ts',
|
||||
'./c/a': './c.ts',
|
||||
'./d/*': './d/*.ts',
|
||||
}
|
||||
|
||||
describe('validateExports', () => {
|
||||
it('Works', async () => {
|
||||
expect(() => validateExports(mockExports1, mockExports1, 'package.json')).not.toThrowError()
|
||||
expect(() => validateExports(mockExports1, mockExports2, 'jsr.json')).not.toThrowError()
|
||||
expect(() => validateExports(mockExports1, mockExports3, 'package.json')).toThrowError()
|
||||
})
|
||||
})
|
37
build/validate-exports.ts
Normal file
37
build/validate-exports.ts
Normal file
@ -0,0 +1,37 @@
|
||||
export const validateExports = (
|
||||
source: Record<string, unknown>,
|
||||
target: Record<string, unknown>,
|
||||
fileName: string
|
||||
) => {
|
||||
const isEntryInTarget = (entry: string): boolean => {
|
||||
if (entry in target) {
|
||||
return true
|
||||
}
|
||||
|
||||
// e.g., "./utils/*" -> "./utils"
|
||||
const wildcardPrefix = entry.replace(/\/\*$/, '')
|
||||
if (entry.endsWith('/*')) {
|
||||
return Object.keys(target).some(
|
||||
(targetEntry) =>
|
||||
targetEntry.startsWith(wildcardPrefix + '/') && targetEntry !== wildcardPrefix
|
||||
)
|
||||
}
|
||||
|
||||
const separatedEntry = entry.split('/')
|
||||
while (separatedEntry.length > 0) {
|
||||
const pattern = `${separatedEntry.join('/')}/*`
|
||||
if (pattern in target) {
|
||||
return true
|
||||
}
|
||||
separatedEntry.pop()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
Object.keys(source).forEach((sourceEntry) => {
|
||||
if (!isEntryInTarget(sourceEntry)) {
|
||||
throw new Error(`Missing "${sourceEntry}" in '${fileName}'`)
|
||||
}
|
||||
})
|
||||
}
|
2
jsr.json
2
jsr.json
@ -46,6 +46,7 @@
|
||||
"./jsx/dom/css": "./src/jsx/dom/css.ts",
|
||||
"./jsx/dom/server": "./src/jsx/dom/server.ts",
|
||||
"./jwt": "./src/middleware/jwt/jwt.ts",
|
||||
"./timeout": "./src/middleware/timeout/index.ts",
|
||||
"./timing": "./src/middleware/timing/timing.ts",
|
||||
"./logger": "./src/middleware/logger/index.ts",
|
||||
"./method-override": "./src/middleware/method-override/index.ts",
|
||||
@ -79,6 +80,7 @@
|
||||
"./testing": "./src/helper/testing/index.ts",
|
||||
"./dev": "./src/helper/dev/index.ts",
|
||||
"./ws": "./src/helper/websocket/index.ts",
|
||||
"./conninfo": "./src/helper/conninfo/index.ts",
|
||||
"./utils/body": "./src/utils/body.ts",
|
||||
"./utils/buffer": "./src/utils/buffer.ts",
|
||||
"./utils/color": "./src/utils/color.ts",
|
||||
|
32
package.json
32
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hono",
|
||||
"version": "4.6.8",
|
||||
"version": "4.6.10",
|
||||
"description": "Web framework built on Web Standards",
|
||||
"main": "dist/cjs/index.js",
|
||||
"type": "module",
|
||||
@ -39,6 +39,11 @@
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/cjs/index.js"
|
||||
},
|
||||
"./request": {
|
||||
"types": "./dist/types/request.d.ts",
|
||||
"import": "./dist/request.js",
|
||||
"require": "./dist/cjs/request.js"
|
||||
},
|
||||
"./types": {
|
||||
"types": "./dist/types/types.d.ts",
|
||||
"import": "./dist/types.js",
|
||||
@ -387,6 +392,9 @@
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"request": [
|
||||
"./dist/types/request"
|
||||
],
|
||||
"types": [
|
||||
"./dist/types/types"
|
||||
],
|
||||
@ -619,29 +627,29 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@hono/eslint-config": "^1.0.2",
|
||||
"@hono/node-server": "^1.8.2",
|
||||
"@types/glob": "^8.0.0",
|
||||
"@types/jsdom": "^21.1.4",
|
||||
"@hono/node-server": "^1.13.5",
|
||||
"@types/glob": "^8.1.0",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/node": "20.11.4",
|
||||
"@types/supertest": "^2.0.12",
|
||||
"@types/supertest": "^2.0.16",
|
||||
"@vitest/coverage-v8": "^2.0.5",
|
||||
"arg": "^5.0.2",
|
||||
"bun-types": "^1.1.30",
|
||||
"esbuild": "^0.15.12",
|
||||
"bun-types": "^1.1.34",
|
||||
"esbuild": "^0.15.18",
|
||||
"eslint": "^9.10.0",
|
||||
"glob": "^11.0.0",
|
||||
"jsdom": "^22.1.0",
|
||||
"msw": "^2.3.0",
|
||||
"msw": "^2.6.0",
|
||||
"np": "7.7.0",
|
||||
"prettier": "^2.6.2",
|
||||
"publint": "^0.1.8",
|
||||
"supertest": "^6.3.3",
|
||||
"publint": "^0.1.16",
|
||||
"supertest": "^6.3.4",
|
||||
"typescript": "^5.3.3",
|
||||
"vite-plugin-fastly-js-compute": "^0.4.2",
|
||||
"vitest": "^2.0.5",
|
||||
"wrangler": "3.58.0",
|
||||
"ws": "^8.17.0",
|
||||
"zod": "^3.20.2"
|
||||
"ws": "^8.18.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
|
2
perf-measures/bundle-check/.gitignore
vendored
Normal file
2
perf-measures/bundle-check/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
generated
|
||||
!generated/.gitkeep
|
0
perf-measures/bundle-check/generated/.gitkeep
Normal file
0
perf-measures/bundle-check/generated/.gitkeep
Normal file
14
perf-measures/bundle-check/scripts/process-results.ts
Normal file
14
perf-measures/bundle-check/scripts/process-results.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import * as fs from 'node:fs/promises'
|
||||
|
||||
async function main() {
|
||||
const currentResult = (await fs.readFile('./generated/after.js')).byteLength
|
||||
let previousResult: number | null = null
|
||||
try {
|
||||
previousResult = (await fs.readFile('./generated/before.js')).byteLength
|
||||
} catch (e) {}
|
||||
const table = ['| | Current | Previous |', '| --- | --- | --- |']
|
||||
table.push(`| Bundle Size | ${currentResult} | ${previousResult || 'N/A'} |`)
|
||||
console.log(table.join('\n'))
|
||||
}
|
||||
|
||||
main()
|
@ -139,6 +139,22 @@ Deno.test('Serve Static middleware', async () => {
|
||||
res = await app.request('http://localhost/static-absolute-root/plain.txt')
|
||||
assertEquals(res.status, 200)
|
||||
assertEquals(await res.text(), 'Deno!')
|
||||
|
||||
res = await app.request('http://localhost/static')
|
||||
assertEquals(res.status, 404)
|
||||
assertEquals(await res.text(), '404 Not Found')
|
||||
|
||||
res = await app.request('http://localhost/static/dir')
|
||||
assertEquals(res.status, 404)
|
||||
assertEquals(await res.text(), '404 Not Found')
|
||||
|
||||
res = await app.request('http://localhost/static/helloworld/nested')
|
||||
assertEquals(res.status, 404)
|
||||
assertEquals(await res.text(), '404 Not Found')
|
||||
|
||||
res = await app.request('http://localhost/static/helloworld/../')
|
||||
assertEquals(res.status, 404)
|
||||
assertEquals(await res.text(), '404 Not Found')
|
||||
})
|
||||
|
||||
Deno.test('JWT Authentication middleware', async () => {
|
||||
|
@ -221,6 +221,7 @@ describe('compress', async () => {
|
||||
{
|
||||
fetch: externalApp.fetch,
|
||||
port: 0,
|
||||
hostname: '0.0.0.0',
|
||||
},
|
||||
(serverInfo) => {
|
||||
resolve([server as Server, serverInfo])
|
||||
|
@ -21,14 +21,13 @@ describe('upgradeWebSocket middleware', () => {
|
||||
app.get(
|
||||
'/ws',
|
||||
upgradeWebSocket(() => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
onMessage(evt, ws) {
|
||||
resolve(evt.data)
|
||||
resolve([evt.data, ws.readyState || 1])
|
||||
},
|
||||
}))
|
||||
)
|
||||
)
|
||||
it('Should receive message is valid', async () => {
|
||||
it('Should receive message and readyState is valid', async () => {
|
||||
const sendingData = Math.random().toString()
|
||||
await app.request('/ws', {
|
||||
headers: {
|
||||
@ -41,7 +40,7 @@ describe('upgradeWebSocket middleware', () => {
|
||||
})
|
||||
)
|
||||
|
||||
expect(sendingData).toBe(await wsPromise)
|
||||
expect([sendingData, 1]).toStrictEqual(await wsPromise)
|
||||
})
|
||||
it('Should call next() when header does not have upgrade', async () => {
|
||||
const next = vi.fn()
|
||||
|
@ -1,51 +1,53 @@
|
||||
import { WSContext, defineWebSocketHelper } from '../../helper/websocket'
|
||||
import type { UpgradeWebSocket, WSReadyState } from '../../helper/websocket'
|
||||
import type { UpgradeWebSocket, WSEvents, WSReadyState } from '../../helper/websocket'
|
||||
|
||||
// Based on https://github.com/honojs/hono/issues/1153#issuecomment-1767321332
|
||||
export const upgradeWebSocket: UpgradeWebSocket<WebSocket> = defineWebSocketHelper(
|
||||
async (c, events) => {
|
||||
const upgradeHeader = c.req.header('Upgrade')
|
||||
if (upgradeHeader !== 'websocket') {
|
||||
return
|
||||
}
|
||||
|
||||
// @ts-expect-error WebSocketPair is not typed
|
||||
const webSocketPair = new WebSocketPair()
|
||||
const client: WebSocket = webSocketPair[0]
|
||||
const server: WebSocket = webSocketPair[1]
|
||||
|
||||
const wsContext = new WSContext<WebSocket>({
|
||||
close: (code, reason) => server.close(code, reason),
|
||||
get protocol() {
|
||||
return server.protocol
|
||||
},
|
||||
raw: server,
|
||||
get readyState() {
|
||||
return server.readyState as WSReadyState
|
||||
},
|
||||
url: server.url ? new URL(server.url) : null,
|
||||
send: (source) => server.send(source),
|
||||
})
|
||||
|
||||
if (events.onOpen) {
|
||||
server.addEventListener('open', (evt: Event) => events.onOpen?.(evt, wsContext))
|
||||
}
|
||||
if (events.onClose) {
|
||||
server.addEventListener('close', (evt: CloseEvent) => events.onClose?.(evt, wsContext))
|
||||
}
|
||||
if (events.onMessage) {
|
||||
server.addEventListener('message', (evt: MessageEvent) => events.onMessage?.(evt, wsContext))
|
||||
}
|
||||
if (events.onError) {
|
||||
server.addEventListener('error', (evt: Event) => events.onError?.(evt, wsContext))
|
||||
}
|
||||
|
||||
// @ts-expect-error - server.accept is not typed
|
||||
server.accept?.()
|
||||
return new Response(null, {
|
||||
status: 101,
|
||||
// @ts-expect-error - webSocket is not typed
|
||||
webSocket: client,
|
||||
})
|
||||
export const upgradeWebSocket: UpgradeWebSocket<
|
||||
WebSocket,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
any,
|
||||
Omit<WSEvents<WebSocket>, 'onOpen'>
|
||||
> = defineWebSocketHelper(async (c, events) => {
|
||||
const upgradeHeader = c.req.header('Upgrade')
|
||||
if (upgradeHeader !== 'websocket') {
|
||||
return
|
||||
}
|
||||
)
|
||||
|
||||
// @ts-expect-error WebSocketPair is not typed
|
||||
const webSocketPair = new WebSocketPair()
|
||||
const client: WebSocket = webSocketPair[0]
|
||||
const server: WebSocket = webSocketPair[1]
|
||||
|
||||
const wsContext = new WSContext<WebSocket>({
|
||||
close: (code, reason) => server.close(code, reason),
|
||||
get protocol() {
|
||||
return server.protocol
|
||||
},
|
||||
raw: server,
|
||||
get readyState() {
|
||||
return server.readyState as WSReadyState
|
||||
},
|
||||
url: server.url ? new URL(server.url) : null,
|
||||
send: (source) => server.send(source),
|
||||
})
|
||||
|
||||
// note: cloudflare workers doesn't support 'open' event
|
||||
|
||||
if (events.onClose) {
|
||||
server.addEventListener('close', (evt: CloseEvent) => events.onClose?.(evt, wsContext))
|
||||
}
|
||||
if (events.onMessage) {
|
||||
server.addEventListener('message', (evt: MessageEvent) => events.onMessage?.(evt, wsContext))
|
||||
}
|
||||
if (events.onError) {
|
||||
server.addEventListener('error', (evt: Event) => events.onError?.(evt, wsContext))
|
||||
}
|
||||
|
||||
// @ts-expect-error - server.accept is not typed
|
||||
server.accept?.()
|
||||
return new Response(null, {
|
||||
status: 101,
|
||||
// @ts-expect-error - webSocket is not typed
|
||||
webSocket: client,
|
||||
})
|
||||
})
|
||||
|
@ -10,6 +10,10 @@ export const serveStatic = <E extends Env = Env>(
|
||||
return async function serveStatic(c, next) {
|
||||
const getContent = async (path: string) => {
|
||||
try {
|
||||
if (isDir(path)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const file = await open(path)
|
||||
return file.readable
|
||||
} catch (e) {
|
||||
@ -30,6 +34,7 @@ export const serveStatic = <E extends Env = Env>(
|
||||
} catch {}
|
||||
return isDir
|
||||
}
|
||||
|
||||
return baseServeStatic({
|
||||
...options,
|
||||
getContent,
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Cloudflare Workers Adapter for Hono.
|
||||
* Service Worker Adapter for Hono.
|
||||
* @module
|
||||
*/
|
||||
export { handle } from './handler'
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Context } from './context'
|
||||
import type { ParamIndexMap, Params } from './router'
|
||||
import type { Env, ErrorHandler, NotFoundHandler } from './types'
|
||||
|
||||
/**
|
||||
@ -31,7 +30,7 @@ interface ComposeContext {
|
||||
* @returns {(context: C, next?: Function) => Promise<C>} - A composed middleware function.
|
||||
*/
|
||||
export const compose = <C extends ComposeContext, E extends Env = Env>(
|
||||
middleware: [[Function, unknown], ParamIndexMap | Params][],
|
||||
middleware: [[Function, unknown], unknown][] | [[Function]][],
|
||||
onError?: ErrorHandler<E>,
|
||||
onNotFound?: NotFoundHandler<E>
|
||||
): ((context: C, next?: Function) => Promise<C>) => {
|
||||
|
@ -623,11 +623,11 @@ export class Context<
|
||||
return Object.fromEntries(this.#var)
|
||||
}
|
||||
|
||||
newResponse: NewResponse = (
|
||||
#newResponse(
|
||||
data: Data | null,
|
||||
arg?: StatusCode | ResponseInit,
|
||||
headers?: HeaderRecord
|
||||
): Response => {
|
||||
): Response {
|
||||
// Optimized
|
||||
if (this.#isFresh && !headers && !arg && this.#status === 200) {
|
||||
return new Response(data, {
|
||||
@ -689,6 +689,8 @@ export class Context<
|
||||
})
|
||||
}
|
||||
|
||||
newResponse: NewResponse = (...args) => this.#newResponse(...(args as Parameters<NewResponse>))
|
||||
|
||||
/**
|
||||
* `.body()` can return the HTTP response.
|
||||
* You can set headers with `.header()` and set HTTP status code with `.status`.
|
||||
@ -716,8 +718,8 @@ export class Context<
|
||||
headers?: HeaderRecord
|
||||
): Response => {
|
||||
return typeof arg === 'number'
|
||||
? this.newResponse(data, arg, headers)
|
||||
: this.newResponse(data, arg)
|
||||
? this.#newResponse(data, arg, headers)
|
||||
: this.#newResponse(data, arg)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -749,8 +751,8 @@ export class Context<
|
||||
this.#preparedHeaders['content-type'] = TEXT_PLAIN
|
||||
// @ts-expect-error `Response` due to missing some types-only keys
|
||||
return typeof arg === 'number'
|
||||
? this.newResponse(text, arg, headers)
|
||||
: this.newResponse(text, arg)
|
||||
? this.#newResponse(text, arg, headers)
|
||||
: this.#newResponse(text, arg)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -778,7 +780,7 @@ export class Context<
|
||||
this.#preparedHeaders['content-type'] = 'application/json; charset=UTF-8'
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
return (
|
||||
typeof arg === 'number' ? this.newResponse(body, arg, headers) : this.newResponse(body, arg)
|
||||
typeof arg === 'number' ? this.#newResponse(body, arg, headers) : this.#newResponse(body, arg)
|
||||
) as any
|
||||
}
|
||||
|
||||
@ -793,14 +795,14 @@ export class Context<
|
||||
if (typeof html === 'object') {
|
||||
return resolveCallback(html, HtmlEscapedCallbackPhase.Stringify, false, {}).then((html) => {
|
||||
return typeof arg === 'number'
|
||||
? this.newResponse(html, arg, headers)
|
||||
: this.newResponse(html, arg)
|
||||
? this.#newResponse(html, arg, headers)
|
||||
: this.#newResponse(html, arg)
|
||||
})
|
||||
}
|
||||
|
||||
return typeof arg === 'number'
|
||||
? this.newResponse(html as string, arg, headers)
|
||||
: this.newResponse(html as string, arg)
|
||||
? this.#newResponse(html as string, arg, headers)
|
||||
: this.#newResponse(html as string, arg)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -227,6 +227,29 @@ describe('createHandler', () => {
|
||||
expectTypeOf(routes).toEqualTypeOf<Expected>()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Types - Context Env with Multiple Middlewares', () => {
|
||||
const factory = createFactory()
|
||||
|
||||
const mw1 = createMiddleware<{ Variables: { foo: string } }>(async (c, next) => {
|
||||
c.set('foo', 'bar')
|
||||
await next()
|
||||
})
|
||||
|
||||
const mw2 = createMiddleware<{ Variables: { bar: number } }>(async (c, next) => {
|
||||
c.set('bar', 1)
|
||||
await next()
|
||||
})
|
||||
|
||||
it('Should set the correct type for context from multiple middlewares', () => {
|
||||
factory.createHandlers(mw1, mw2, (c) => {
|
||||
expectTypeOf(c.var.foo).toEqualTypeOf<string>()
|
||||
expectTypeOf(c.var.bar).toEqualTypeOf<number>()
|
||||
|
||||
return c.json({ foo: c.var.foo, bar: c.var.bar })
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('createApp', () => {
|
||||
|
@ -5,31 +5,48 @@
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Hono } from '../../hono'
|
||||
import type { Env, H, HandlerResponse, Input, MiddlewareHandler } from '../../types'
|
||||
import type {
|
||||
Env,
|
||||
H,
|
||||
HandlerResponse,
|
||||
Input,
|
||||
IntersectNonAnyTypes,
|
||||
MiddlewareHandler,
|
||||
} from '../../types'
|
||||
|
||||
type InitApp<E extends Env = Env> = (app: Hono<E>) => void
|
||||
|
||||
export interface CreateHandlersInterface<E extends Env, P extends string> {
|
||||
<I extends Input = {}, R extends HandlerResponse<any> = any>(handler1: H<E, P, I, R>): [
|
||||
H<E, P, I, R>
|
||||
]
|
||||
<I extends Input = {}, R extends HandlerResponse<any> = any, E2 extends Env = E>(
|
||||
handler1: H<E2, P, I, R>
|
||||
): [H<E2, P, I, R>]
|
||||
|
||||
// handler x2
|
||||
<I extends Input = {}, I2 extends Input = I, R extends HandlerResponse<any> = any>(
|
||||
handler1: H<E, P, I, R>,
|
||||
handler2: H<E, P, I2, R>
|
||||
): [H<E, P, I, R>, H<E, P, I2, R>]
|
||||
<
|
||||
I extends Input = {},
|
||||
I2 extends Input = I,
|
||||
R extends HandlerResponse<any> = any,
|
||||
E2 extends Env = E,
|
||||
E3 extends Env = IntersectNonAnyTypes<[E, E2]>
|
||||
>(
|
||||
handler1: H<E2, P, I, R>,
|
||||
handler2: H<E3, P, I2, R>
|
||||
): [H<E2, P, I, R>, H<E3, P, I2, R>]
|
||||
|
||||
// handler x3
|
||||
<
|
||||
I extends Input = {},
|
||||
I2 extends Input = I,
|
||||
I3 extends Input = I & I2,
|
||||
R extends HandlerResponse<any> = any
|
||||
R extends HandlerResponse<any> = any,
|
||||
E2 extends Env = E,
|
||||
E3 extends Env = E,
|
||||
E4 extends Env = IntersectNonAnyTypes<[E, E2, E3]>
|
||||
>(
|
||||
handler1: H<E, P, I, R>,
|
||||
handler2: H<E, P, I2, R>,
|
||||
handler3: H<E, P, I3, R>
|
||||
): [H<E, P, I, R>, H<E, P, I2, R>, H<E, P, I3, R>]
|
||||
handler1: H<E2, P, I, R>,
|
||||
handler2: H<E3, P, I2, R>,
|
||||
handler3: H<E4, P, I3, R>
|
||||
): [H<E2, P, I, R>, H<E3, P, I2, R>, H<E4, P, I3, R>]
|
||||
|
||||
// handler x4
|
||||
<
|
||||
@ -37,13 +54,17 @@ export interface CreateHandlersInterface<E extends Env, P extends string> {
|
||||
I2 extends Input = I,
|
||||
I3 extends Input = I & I2,
|
||||
I4 extends Input = I & I2 & I3,
|
||||
R extends HandlerResponse<any> = any
|
||||
R extends HandlerResponse<any> = any,
|
||||
E2 extends Env = E,
|
||||
E3 extends Env = E,
|
||||
E4 extends Env = E,
|
||||
E5 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4]>
|
||||
>(
|
||||
handler1: H<E, P, I, R>,
|
||||
handler2: H<E, P, I2, R>,
|
||||
handler3: H<E, P, I3, R>,
|
||||
handler4: H<E, P, I4, R>
|
||||
): [H<E, P, I, R>, H<E, P, I2, R>, H<E, P, I3, R>, H<E, P, I4, R>]
|
||||
handler1: H<E2, P, I, R>,
|
||||
handler2: H<E3, P, I2, R>,
|
||||
handler3: H<E4, P, I3, R>,
|
||||
handler4: H<E5, P, I4, R>
|
||||
): [H<E2, P, I, R>, H<E3, P, I2, R>, H<E4, P, I3, R>, H<E5, P, I4, R>]
|
||||
|
||||
// handler x5
|
||||
<
|
||||
@ -52,14 +73,19 @@ export interface CreateHandlersInterface<E extends Env, P extends string> {
|
||||
I3 extends Input = I & I2,
|
||||
I4 extends Input = I & I2 & I3,
|
||||
I5 extends Input = I & I2 & I3 & I4,
|
||||
R extends HandlerResponse<any> = any
|
||||
R extends HandlerResponse<any> = any,
|
||||
E2 extends Env = E,
|
||||
E3 extends Env = E,
|
||||
E4 extends Env = E,
|
||||
E5 extends Env = E,
|
||||
E6 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5]>
|
||||
>(
|
||||
handler1: H<E, P, I, R>,
|
||||
handler2: H<E, P, I2, R>,
|
||||
handler3: H<E, P, I3, R>,
|
||||
handler4: H<E, P, I4, R>,
|
||||
handler5: H<E, P, I5, R>
|
||||
): [H<E, P, I, R>, H<E, P, I2, R>, H<E, P, I3, R>, H<E, P, I4, R>, H<E, P, I5, R>]
|
||||
handler1: H<E2, P, I, R>,
|
||||
handler2: H<E3, P, I2, R>,
|
||||
handler3: H<E4, P, I3, R>,
|
||||
handler4: H<E5, P, I4, R>,
|
||||
handler5: H<E6, P, I5, R>
|
||||
): [H<E2, P, I, R>, H<E3, P, I2, R>, H<E4, P, I3, R>, H<E5, P, I4, R>, H<E6, P, I5, R>]
|
||||
|
||||
// handler x6
|
||||
<
|
||||
@ -69,15 +95,28 @@ export interface CreateHandlersInterface<E extends Env, P extends string> {
|
||||
I4 extends Input = I & I2 & I3,
|
||||
I5 extends Input = I & I2 & I3 & I4,
|
||||
I6 extends Input = I & I2 & I3 & I4 & I5,
|
||||
R extends HandlerResponse<any> = any
|
||||
R extends HandlerResponse<any> = any,
|
||||
E2 extends Env = E,
|
||||
E3 extends Env = E,
|
||||
E4 extends Env = E,
|
||||
E5 extends Env = E,
|
||||
E6 extends Env = E,
|
||||
E7 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6]>
|
||||
>(
|
||||
handler1: H<E, P, I, R>,
|
||||
handler2: H<E, P, I2, R>,
|
||||
handler3: H<E, P, I3, R>,
|
||||
handler4: H<E, P, I4, R>,
|
||||
handler5: H<E, P, I5, R>,
|
||||
handler6: H<E, P, I6, R>
|
||||
): [H<E, P, I, R>, H<E, P, I2, R>, H<E, P, I3, R>, H<E, P, I4, R>, H<E, P, I5, R>, H<E, P, I6, R>]
|
||||
handler1: H<E2, P, I, R>,
|
||||
handler2: H<E3, P, I2, R>,
|
||||
handler3: H<E4, P, I3, R>,
|
||||
handler4: H<E5, P, I4, R>,
|
||||
handler5: H<E6, P, I5, R>,
|
||||
handler6: H<E7, P, I6, R>
|
||||
): [
|
||||
H<E2, P, I, R>,
|
||||
H<E3, P, I2, R>,
|
||||
H<E4, P, I3, R>,
|
||||
H<E5, P, I4, R>,
|
||||
H<E6, P, I5, R>,
|
||||
H<E7, P, I6, R>
|
||||
]
|
||||
|
||||
// handler x7
|
||||
<
|
||||
@ -88,23 +127,30 @@ export interface CreateHandlersInterface<E extends Env, P extends string> {
|
||||
I5 extends Input = I & I2 & I3 & I4,
|
||||
I6 extends Input = I & I2 & I3 & I4 & I5,
|
||||
I7 extends Input = I & I2 & I3 & I4 & I5 & I6,
|
||||
R extends HandlerResponse<any> = any
|
||||
R extends HandlerResponse<any> = any,
|
||||
E2 extends Env = E,
|
||||
E3 extends Env = E,
|
||||
E4 extends Env = E,
|
||||
E5 extends Env = E,
|
||||
E6 extends Env = E,
|
||||
E7 extends Env = E,
|
||||
E8 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7]>
|
||||
>(
|
||||
handler1: H<E, P, I, R>,
|
||||
handler2: H<E, P, I2, R>,
|
||||
handler3: H<E, P, I3, R>,
|
||||
handler4: H<E, P, I4, R>,
|
||||
handler5: H<E, P, I5, R>,
|
||||
handler6: H<E, P, I6, R>,
|
||||
handler7: H<E, P, I7, R>
|
||||
handler1: H<E2, P, I, R>,
|
||||
handler2: H<E3, P, I2, R>,
|
||||
handler3: H<E4, P, I3, R>,
|
||||
handler4: H<E5, P, I4, R>,
|
||||
handler5: H<E6, P, I5, R>,
|
||||
handler6: H<E7, P, I6, R>,
|
||||
handler7: H<E8, P, I7, R>
|
||||
): [
|
||||
H<E, P, I, R>,
|
||||
H<E, P, I2, R>,
|
||||
H<E, P, I3, R>,
|
||||
H<E, P, I4, R>,
|
||||
H<E, P, I5, R>,
|
||||
H<E, P, I6, R>,
|
||||
H<E, P, I7, R>
|
||||
H<E2, P, I, R>,
|
||||
H<E3, P, I2, R>,
|
||||
H<E4, P, I3, R>,
|
||||
H<E5, P, I4, R>,
|
||||
H<E6, P, I5, R>,
|
||||
H<E7, P, I6, R>,
|
||||
H<E8, P, I7, R>
|
||||
]
|
||||
|
||||
// handler x8
|
||||
@ -117,25 +163,33 @@ export interface CreateHandlersInterface<E extends Env, P extends string> {
|
||||
I6 extends Input = I & I2 & I3 & I4 & I5,
|
||||
I7 extends Input = I & I2 & I3 & I4 & I5 & I6,
|
||||
I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7,
|
||||
R extends HandlerResponse<any> = any
|
||||
R extends HandlerResponse<any> = any,
|
||||
E2 extends Env = E,
|
||||
E3 extends Env = E,
|
||||
E4 extends Env = E,
|
||||
E5 extends Env = E,
|
||||
E6 extends Env = E,
|
||||
E7 extends Env = E,
|
||||
E8 extends Env = E,
|
||||
E9 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8]>
|
||||
>(
|
||||
handler1: H<E, P, I, R>,
|
||||
handler2: H<E, P, I2, R>,
|
||||
handler3: H<E, P, I3, R>,
|
||||
handler4: H<E, P, I4, R>,
|
||||
handler5: H<E, P, I5, R>,
|
||||
handler6: H<E, P, I6, R>,
|
||||
handler7: H<E, P, I7, R>,
|
||||
handler8: H<E, P, I8, R>
|
||||
handler1: H<E2, P, I, R>,
|
||||
handler2: H<E3, P, I2, R>,
|
||||
handler3: H<E4, P, I3, R>,
|
||||
handler4: H<E5, P, I4, R>,
|
||||
handler5: H<E6, P, I5, R>,
|
||||
handler6: H<E7, P, I6, R>,
|
||||
handler7: H<E8, P, I7, R>,
|
||||
handler8: H<E9, P, I8, R>
|
||||
): [
|
||||
H<E, P, I, R>,
|
||||
H<E, P, I2, R>,
|
||||
H<E, P, I3, R>,
|
||||
H<E, P, I4, R>,
|
||||
H<E, P, I5, R>,
|
||||
H<E, P, I6, R>,
|
||||
H<E, P, I7, R>,
|
||||
H<E, P, I8, R>
|
||||
H<E2, P, I, R>,
|
||||
H<E3, P, I2, R>,
|
||||
H<E4, P, I3, R>,
|
||||
H<E5, P, I4, R>,
|
||||
H<E6, P, I5, R>,
|
||||
H<E7, P, I6, R>,
|
||||
H<E8, P, I7, R>,
|
||||
H<E9, P, I8, R>
|
||||
]
|
||||
|
||||
// handler x9
|
||||
@ -149,27 +203,36 @@ export interface CreateHandlersInterface<E extends Env, P extends string> {
|
||||
I7 extends Input = I & I2 & I3 & I4 & I5 & I6,
|
||||
I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7,
|
||||
I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8,
|
||||
R extends HandlerResponse<any> = any
|
||||
R extends HandlerResponse<any> = any,
|
||||
E2 extends Env = E,
|
||||
E3 extends Env = E,
|
||||
E4 extends Env = E,
|
||||
E5 extends Env = E,
|
||||
E6 extends Env = E,
|
||||
E7 extends Env = E,
|
||||
E8 extends Env = E,
|
||||
E9 extends Env = E,
|
||||
E10 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9]>
|
||||
>(
|
||||
handler1: H<E, P, I, R>,
|
||||
handler2: H<E, P, I2, R>,
|
||||
handler3: H<E, P, I3, R>,
|
||||
handler4: H<E, P, I4, R>,
|
||||
handler5: H<E, P, I5, R>,
|
||||
handler6: H<E, P, I6, R>,
|
||||
handler7: H<E, P, I7, R>,
|
||||
handler8: H<E, P, I8, R>,
|
||||
handler9: H<E, P, I9, R>
|
||||
handler1: H<E2, P, I, R>,
|
||||
handler2: H<E3, P, I2, R>,
|
||||
handler3: H<E4, P, I3, R>,
|
||||
handler4: H<E5, P, I4, R>,
|
||||
handler5: H<E6, P, I5, R>,
|
||||
handler6: H<E7, P, I6, R>,
|
||||
handler7: H<E8, P, I7, R>,
|
||||
handler8: H<E9, P, I8, R>,
|
||||
handler9: H<E10, P, I9, R>
|
||||
): [
|
||||
H<E, P, I, R>,
|
||||
H<E, P, I2, R>,
|
||||
H<E, P, I3, R>,
|
||||
H<E, P, I4, R>,
|
||||
H<E, P, I5, R>,
|
||||
H<E, P, I6, R>,
|
||||
H<E, P, I7, R>,
|
||||
H<E, P, I8, R>,
|
||||
H<E, P, I9, R>
|
||||
H<E2, P, I, R>,
|
||||
H<E3, P, I2, R>,
|
||||
H<E4, P, I3, R>,
|
||||
H<E5, P, I4, R>,
|
||||
H<E6, P, I5, R>,
|
||||
H<E7, P, I6, R>,
|
||||
H<E8, P, I7, R>,
|
||||
H<E9, P, I8, R>,
|
||||
H<E10, P, I9, R>
|
||||
]
|
||||
|
||||
// handler x10
|
||||
@ -184,29 +247,39 @@ export interface CreateHandlersInterface<E extends Env, P extends string> {
|
||||
I8 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7,
|
||||
I9 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8,
|
||||
I10 extends Input = I & I2 & I3 & I4 & I5 & I6 & I7 & I8 & I9,
|
||||
R extends HandlerResponse<any> = any
|
||||
R extends HandlerResponse<any> = any,
|
||||
E2 extends Env = E,
|
||||
E3 extends Env = E,
|
||||
E4 extends Env = E,
|
||||
E5 extends Env = E,
|
||||
E6 extends Env = E,
|
||||
E7 extends Env = E,
|
||||
E8 extends Env = E,
|
||||
E9 extends Env = E,
|
||||
E10 extends Env = E,
|
||||
E11 extends Env = IntersectNonAnyTypes<[E, E2, E3, E4, E5, E6, E7, E8, E9, E10]>
|
||||
>(
|
||||
handler1: H<E, P, I, R>,
|
||||
handler2: H<E, P, I2, R>,
|
||||
handler3: H<E, P, I3, R>,
|
||||
handler4: H<E, P, I4, R>,
|
||||
handler5: H<E, P, I5, R>,
|
||||
handler6: H<E, P, I6, R>,
|
||||
handler7: H<E, P, I7, R>,
|
||||
handler8: H<E, P, I8, R>,
|
||||
handler9: H<E, P, I9, R>,
|
||||
handler10: H<E, P, I10, R>
|
||||
handler1: H<E2, P, I, R>,
|
||||
handler2: H<E3, P, I2, R>,
|
||||
handler3: H<E4, P, I3, R>,
|
||||
handler4: H<E5, P, I4, R>,
|
||||
handler5: H<E6, P, I5, R>,
|
||||
handler6: H<E7, P, I6, R>,
|
||||
handler7: H<E8, P, I7, R>,
|
||||
handler8: H<E9, P, I8, R>,
|
||||
handler9: H<E10, P, I9, R>,
|
||||
handler10: H<E11, P, I10, R>
|
||||
): [
|
||||
H<E, P, I, R>,
|
||||
H<E, P, I2, R>,
|
||||
H<E, P, I3, R>,
|
||||
H<E, P, I4, R>,
|
||||
H<E, P, I5, R>,
|
||||
H<E, P, I6, R>,
|
||||
H<E, P, I7, R>,
|
||||
H<E, P, I8, R>,
|
||||
H<E, P, I9, R>,
|
||||
H<E, P, I10, R>
|
||||
H<E2, P, I, R>,
|
||||
H<E3, P, I2, R>,
|
||||
H<E4, P, I3, R>,
|
||||
H<E5, P, I4, R>,
|
||||
H<E6, P, I5, R>,
|
||||
H<E7, P, I6, R>,
|
||||
H<E8, P, I7, R>,
|
||||
H<E9, P, I8, R>,
|
||||
H<E10, P, I9, R>,
|
||||
H<E11, P, I10, R>
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -20,8 +20,8 @@ export interface WSEvents<T = unknown> {
|
||||
/**
|
||||
* Upgrade WebSocket Type
|
||||
*/
|
||||
export type UpgradeWebSocket<T = unknown, U = any> = (
|
||||
createEvents: (c: Context) => WSEvents<T> | Promise<WSEvents<T>>,
|
||||
export type UpgradeWebSocket<T = unknown, U = any, _WSEvents = WSEvents<T>> = (
|
||||
createEvents: (c: Context) => _WSEvents | Promise<_WSEvents>,
|
||||
options?: U
|
||||
) => MiddlewareHandler<
|
||||
any,
|
||||
|
@ -130,12 +130,10 @@ class Hono<E extends Env = Env, S extends Schema = {}, BasePath extends string =
|
||||
if (typeof args1 === 'string') {
|
||||
this.#path = args1
|
||||
} else {
|
||||
this.addRoute(method, this.#path, args1)
|
||||
this.#addRoute(method, this.#path, args1)
|
||||
}
|
||||
args.forEach((handler) => {
|
||||
if (typeof handler !== 'string') {
|
||||
this.addRoute(method, this.#path, handler)
|
||||
}
|
||||
this.#addRoute(method, this.#path, handler)
|
||||
})
|
||||
return this as any
|
||||
}
|
||||
@ -147,7 +145,7 @@ class Hono<E extends Env = Env, S extends Schema = {}, BasePath extends string =
|
||||
this.#path = p
|
||||
for (const m of [method].flat()) {
|
||||
handlers.map((handler) => {
|
||||
this.addRoute(m.toUpperCase(), this.#path, handler)
|
||||
this.#addRoute(m.toUpperCase(), this.#path, handler)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -163,7 +161,7 @@ class Hono<E extends Env = Env, S extends Schema = {}, BasePath extends string =
|
||||
handlers.unshift(arg1)
|
||||
}
|
||||
handlers.forEach((handler) => {
|
||||
this.addRoute(METHOD_NAME_ALL, this.#path, handler)
|
||||
this.#addRoute(METHOD_NAME_ALL, this.#path, handler)
|
||||
})
|
||||
return this as any
|
||||
}
|
||||
@ -174,7 +172,7 @@ class Hono<E extends Env = Env, S extends Schema = {}, BasePath extends string =
|
||||
this.getPath = strict ? options.getPath ?? getPath : getPathNoStrict
|
||||
}
|
||||
|
||||
private clone(): Hono<E, S, BasePath> {
|
||||
#clone(): Hono<E, S, BasePath> {
|
||||
const clone = new Hono<E, S, BasePath>({
|
||||
router: this.router,
|
||||
getPath: this.getPath,
|
||||
@ -183,8 +181,8 @@ class Hono<E extends Env = Env, S extends Schema = {}, BasePath extends string =
|
||||
return clone
|
||||
}
|
||||
|
||||
private notFoundHandler: NotFoundHandler = notFoundHandler
|
||||
private errorHandler: ErrorHandler = errorHandler
|
||||
#notFoundHandler: NotFoundHandler = notFoundHandler
|
||||
#errorHandler: ErrorHandler = errorHandler
|
||||
|
||||
/**
|
||||
* `.route()` allows grouping other Hono instance in routes.
|
||||
@ -216,15 +214,15 @@ class Hono<E extends Env = Env, S extends Schema = {}, BasePath extends string =
|
||||
const subApp = this.basePath(path)
|
||||
app.routes.map((r) => {
|
||||
let handler
|
||||
if (app.errorHandler === errorHandler) {
|
||||
if (app.#errorHandler === errorHandler) {
|
||||
handler = r.handler
|
||||
} else {
|
||||
handler = async (c: Context, next: Next) =>
|
||||
(await compose<Context>([], app.errorHandler)(c, () => r.handler(c, next))).res
|
||||
(await compose<Context>([], app.#errorHandler)(c, () => r.handler(c, next))).res
|
||||
;(handler as any)[COMPOSED_HANDLER] = r.handler
|
||||
}
|
||||
|
||||
subApp.addRoute(r.method, r.path, handler)
|
||||
subApp.#addRoute(r.method, r.path, handler)
|
||||
})
|
||||
return this
|
||||
}
|
||||
@ -243,7 +241,7 @@ class Hono<E extends Env = Env, S extends Schema = {}, BasePath extends string =
|
||||
* ```
|
||||
*/
|
||||
basePath<SubPath extends string>(path: SubPath): Hono<E, S, MergePath<BasePath, SubPath>> {
|
||||
const subApp = this.clone()
|
||||
const subApp = this.#clone()
|
||||
subApp._basePath = mergePath(this._basePath, path)
|
||||
return subApp
|
||||
}
|
||||
@ -265,7 +263,7 @@ class Hono<E extends Env = Env, S extends Schema = {}, BasePath extends string =
|
||||
* ```
|
||||
*/
|
||||
onError = (handler: ErrorHandler<E>): Hono<E, S, BasePath> => {
|
||||
this.errorHandler = handler
|
||||
this.#errorHandler = handler
|
||||
return this
|
||||
}
|
||||
|
||||
@ -285,7 +283,7 @@ class Hono<E extends Env = Env, S extends Schema = {}, BasePath extends string =
|
||||
* ```
|
||||
*/
|
||||
notFound = (handler: NotFoundHandler<E>): Hono<E, S, BasePath> => {
|
||||
this.notFoundHandler = handler
|
||||
this.#notFoundHandler = handler
|
||||
return this
|
||||
}
|
||||
|
||||
@ -370,26 +368,26 @@ class Hono<E extends Env = Env, S extends Schema = {}, BasePath extends string =
|
||||
|
||||
await next()
|
||||
}
|
||||
this.addRoute(METHOD_NAME_ALL, mergePath(path, '*'), handler)
|
||||
this.#addRoute(METHOD_NAME_ALL, mergePath(path, '*'), handler)
|
||||
return this
|
||||
}
|
||||
|
||||
private addRoute(method: string, path: string, handler: H) {
|
||||
#addRoute(method: string, path: string, handler: H) {
|
||||
method = method.toUpperCase()
|
||||
path = mergePath(this._basePath, path)
|
||||
const r: RouterRoute = { path: path, method: method, handler: handler }
|
||||
const r: RouterRoute = { path, method, handler }
|
||||
this.router.add(method, path, [handler, r])
|
||||
this.routes.push(r)
|
||||
}
|
||||
|
||||
private handleError(err: unknown, c: Context<E>) {
|
||||
#handleError(err: unknown, c: Context<E>) {
|
||||
if (err instanceof Error) {
|
||||
return this.errorHandler(err, c)
|
||||
return this.#errorHandler(err, c)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
|
||||
private dispatch(
|
||||
#dispatch(
|
||||
request: Request,
|
||||
executionCtx: ExecutionContext | FetchEventLike | undefined,
|
||||
env: E['Bindings'],
|
||||
@ -398,7 +396,7 @@ class Hono<E extends Env = Env, S extends Schema = {}, BasePath extends string =
|
||||
// Handle HEAD method
|
||||
if (method === 'HEAD') {
|
||||
return (async () =>
|
||||
new Response(null, await this.dispatch(request, executionCtx, env, 'GET')))()
|
||||
new Response(null, await this.#dispatch(request, executionCtx, env, 'GET')))()
|
||||
}
|
||||
|
||||
const path = this.getPath(request, { env })
|
||||
@ -409,7 +407,7 @@ class Hono<E extends Env = Env, S extends Schema = {}, BasePath extends string =
|
||||
matchResult,
|
||||
env,
|
||||
executionCtx,
|
||||
notFoundHandler: this.notFoundHandler,
|
||||
notFoundHandler: this.#notFoundHandler,
|
||||
})
|
||||
|
||||
// Do not `compose` if it has only one handler
|
||||
@ -417,23 +415,23 @@ class Hono<E extends Env = Env, S extends Schema = {}, BasePath extends string =
|
||||
let res: ReturnType<H>
|
||||
try {
|
||||
res = matchResult[0][0][0][0](c, async () => {
|
||||
c.res = await this.notFoundHandler(c)
|
||||
c.res = await this.#notFoundHandler(c)
|
||||
})
|
||||
} catch (err) {
|
||||
return this.handleError(err, c)
|
||||
return this.#handleError(err, c)
|
||||
}
|
||||
|
||||
return res instanceof Promise
|
||||
? res
|
||||
.then(
|
||||
(resolved: Response | undefined) =>
|
||||
resolved || (c.finalized ? c.res : this.notFoundHandler(c))
|
||||
resolved || (c.finalized ? c.res : this.#notFoundHandler(c))
|
||||
)
|
||||
.catch((err: Error) => this.handleError(err, c))
|
||||
: res ?? this.notFoundHandler(c)
|
||||
.catch((err: Error) => this.#handleError(err, c))
|
||||
: res ?? this.#notFoundHandler(c)
|
||||
}
|
||||
|
||||
const composed = compose<Context>(matchResult[0], this.errorHandler, this.notFoundHandler)
|
||||
const composed = compose<Context>(matchResult[0], this.#errorHandler, this.#notFoundHandler)
|
||||
|
||||
return (async () => {
|
||||
try {
|
||||
@ -446,7 +444,7 @@ class Hono<E extends Env = Env, S extends Schema = {}, BasePath extends string =
|
||||
|
||||
return context.res
|
||||
} catch (err) {
|
||||
return this.handleError(err, c)
|
||||
return this.#handleError(err, c)
|
||||
}
|
||||
})()
|
||||
}
|
||||
@ -467,7 +465,7 @@ class Hono<E extends Env = Env, S extends Schema = {}, BasePath extends string =
|
||||
Env?: E['Bindings'] | {},
|
||||
executionCtx?: ExecutionContext
|
||||
) => Response | Promise<Response> = (request, ...rest) => {
|
||||
return this.dispatch(request, rest[1], rest[0], request.method)
|
||||
return this.#dispatch(request, rest[1], rest[0], request.method)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -489,15 +487,17 @@ class Hono<E extends Env = Env, S extends Schema = {}, BasePath extends string =
|
||||
executionCtx?: ExecutionContext
|
||||
): Response | Promise<Response> => {
|
||||
if (input instanceof Request) {
|
||||
if (requestInit !== undefined) {
|
||||
input = new Request(input, requestInit)
|
||||
}
|
||||
return this.fetch(input, Env, executionCtx)
|
||||
return this.fetch(requestInit ? new Request(input, requestInit) : input, Env, executionCtx)
|
||||
}
|
||||
input = input.toString()
|
||||
const path = /^https?:\/\//.test(input) ? input : `http://localhost${mergePath('/', input)}`
|
||||
const req = new Request(path, requestInit)
|
||||
return this.fetch(req, Env, executionCtx)
|
||||
return this.fetch(
|
||||
new Request(
|
||||
/^https?:\/\//.test(input) ? input : `http://localhost${mergePath('/', input)}`,
|
||||
requestInit
|
||||
),
|
||||
Env,
|
||||
executionCtx
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -511,7 +511,7 @@ class Hono<E extends Env = Env, S extends Schema = {}, BasePath extends string =
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
addEventListener('fetch', (event: FetchEventLike): void => {
|
||||
event.respondWith(this.dispatch(event.request, event, undefined, event.request.method))
|
||||
event.respondWith(this.#dispatch(event.request, event, undefined, event.request.method))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -166,6 +166,22 @@ describe('every', () => {
|
||||
expect(await res.text()).toBe('Hello Middleware 1')
|
||||
expect(middleware2).not.toBeCalled()
|
||||
})
|
||||
|
||||
it('Should pass the path params to middlewares', async () => {
|
||||
const app = new Hono()
|
||||
app.use('*', nextMiddleware)
|
||||
const paramMiddleware: MiddlewareHandler = async (c) => {
|
||||
return c.json(c.req.param(), 200)
|
||||
}
|
||||
|
||||
app.use('/:id', every(paramMiddleware))
|
||||
app.get('/:id', (c) => {
|
||||
return c.text('Hello World')
|
||||
})
|
||||
|
||||
const res = await app.request('http://localhost/123')
|
||||
expect(await res.json()).toEqual({ id: '123' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('except', () => {
|
||||
|
@ -89,19 +89,22 @@ export const some = (...middleware: (MiddlewareHandler | Condition)[]): Middlewa
|
||||
* ```
|
||||
*/
|
||||
export const every = (...middleware: (MiddlewareHandler | Condition)[]): MiddlewareHandler => {
|
||||
const wrappedMiddleware = middleware.map((m) => async (c: Context, next: Next) => {
|
||||
const res = await m(c, next)
|
||||
if (res === false) {
|
||||
throw new Error('Unmet condition')
|
||||
}
|
||||
return res
|
||||
})
|
||||
|
||||
const handler = async (c: Context, next: Next) =>
|
||||
compose<Context>(wrappedMiddleware.map((m) => [[m, undefined], c.req.param()]))(c, next)
|
||||
|
||||
return async function every(c, next) {
|
||||
await handler(c, next)
|
||||
const currentRouteIndex = c.req.routeIndex
|
||||
await compose<Context>(
|
||||
middleware.map((m) => [
|
||||
[
|
||||
async (c: Context, next: Next) => {
|
||||
c.req.routeIndex = currentRouteIndex // should be unchanged in this context
|
||||
const res = await m(c, next)
|
||||
if (res === false) {
|
||||
throw new Error('Unmet condition')
|
||||
}
|
||||
return res
|
||||
},
|
||||
],
|
||||
])
|
||||
)(c, next)
|
||||
}
|
||||
}
|
||||
|
||||
|
42
src/middleware/etag/digest.ts
Normal file
42
src/middleware/etag/digest.ts
Normal file
@ -0,0 +1,42 @@
|
||||
const mergeBuffers = (buffer1: ArrayBuffer | undefined, buffer2: Uint8Array): Uint8Array => {
|
||||
if (!buffer1) {
|
||||
return buffer2
|
||||
}
|
||||
const merged = new Uint8Array(buffer1.byteLength + buffer2.byteLength)
|
||||
merged.set(new Uint8Array(buffer1), 0)
|
||||
merged.set(buffer2, buffer1.byteLength)
|
||||
return merged
|
||||
}
|
||||
|
||||
export const generateDigest = async (
|
||||
stream: ReadableStream<Uint8Array> | null
|
||||
): Promise<string | null> => {
|
||||
if (!stream || !crypto || !crypto.subtle) {
|
||||
return null
|
||||
}
|
||||
|
||||
let result: ArrayBuffer | undefined = undefined
|
||||
|
||||
const reader = stream.getReader()
|
||||
for (;;) {
|
||||
const { value, done } = await reader.read()
|
||||
if (done) {
|
||||
break
|
||||
}
|
||||
|
||||
result = await crypto.subtle.digest(
|
||||
{
|
||||
name: 'SHA-1',
|
||||
},
|
||||
mergeBuffers(result, value)
|
||||
)
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
return null
|
||||
}
|
||||
|
||||
return Array.prototype.map
|
||||
.call(new Uint8Array(result), (x) => x.toString(16).padStart(2, '0'))
|
||||
.join('')
|
||||
}
|
@ -65,6 +65,63 @@ describe('Etag Middleware', () => {
|
||||
expect(res.headers.get('ETag')).not.toBe(hash)
|
||||
})
|
||||
|
||||
it('Should not be the same etag - ReadableStream', async () => {
|
||||
const app = new Hono()
|
||||
app.use('/etag/*', etag())
|
||||
app.get('/etag/rs1', (c) => {
|
||||
return c.body(
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(new Uint8Array([1]))
|
||||
controller.enqueue(new Uint8Array([2]))
|
||||
controller.close()
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
app.get('/etag/rs2', (c) => {
|
||||
return c.body(
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(new Uint8Array([1]))
|
||||
controller.enqueue(new Uint8Array([3]))
|
||||
controller.close()
|
||||
},
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
let res = await app.request('http://localhost/etag/rs1')
|
||||
const hash = res.headers.get('Etag')
|
||||
res = await app.request('http://localhost/etag/rs2')
|
||||
expect(res.headers.get('ETag')).not.toBe(hash)
|
||||
})
|
||||
|
||||
it('Should not return etag header when the stream is empty', async () => {
|
||||
const app = new Hono()
|
||||
app.use('/etag/*', etag())
|
||||
app.get('/etag/abc', (c) => {
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.close()
|
||||
},
|
||||
})
|
||||
return c.body(stream)
|
||||
})
|
||||
const res = await app.request('http://localhost/etag/abc')
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.headers.get('ETag')).toBeNull()
|
||||
})
|
||||
|
||||
it('Should not return etag header when body is null', async () => {
|
||||
const app = new Hono()
|
||||
app.use('/etag/*', etag())
|
||||
app.get('/etag/abc', () => new Response(null, { status: 500 }))
|
||||
const res = await app.request('http://localhost/etag/abc')
|
||||
expect(res.status).toBe(500)
|
||||
expect(res.headers.get('ETag')).toBeNull()
|
||||
})
|
||||
|
||||
it('Should return etag header - weak', async () => {
|
||||
const app = new Hono()
|
||||
app.use('/etag/*', etag({ weak: true }))
|
||||
@ -179,4 +236,29 @@ describe('Etag Middleware', () => {
|
||||
expect(res.headers.get('x-message-retain')).toBe(message)
|
||||
expect(res.headers.get('x-message')).toBeFalsy()
|
||||
})
|
||||
|
||||
describe('When crypto is not available', () => {
|
||||
let _crypto: Crypto | undefined
|
||||
beforeAll(() => {
|
||||
_crypto = globalThis.crypto
|
||||
Object.defineProperty(globalThis, 'crypto', {
|
||||
value: {},
|
||||
})
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
Object.defineProperty(globalThis, 'crypto', {
|
||||
value: _crypto,
|
||||
})
|
||||
})
|
||||
|
||||
it('Should not generate etag', async () => {
|
||||
const app = new Hono()
|
||||
app.use('/etag/*', etag())
|
||||
app.get('/etag/no-digest', (c) => c.text('Hono is cool'))
|
||||
const res = await app.request('/etag/no-digest')
|
||||
expect(res.status).toBe(200)
|
||||
expect(res.headers.get('ETag')).toBeNull()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import type { MiddlewareHandler } from '../../types'
|
||||
import { sha1 } from '../../utils/crypto'
|
||||
import { generateDigest } from './digest'
|
||||
|
||||
type ETagOptions = {
|
||||
retainedHeaders?: string[]
|
||||
@ -63,7 +63,10 @@ export const etag = (options?: ETagOptions): MiddlewareHandler => {
|
||||
let etag = res.headers.get('ETag')
|
||||
|
||||
if (!etag) {
|
||||
const hash = await sha1(res.clone().body || '')
|
||||
const hash = await generateDigest(res.clone().body)
|
||||
if (hash === null) {
|
||||
return
|
||||
}
|
||||
etag = weak ? `W/"${hash}"` : `"${hash}"`
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,17 @@ describe('Logger by Middleware', () => {
|
||||
return c.text(longRandomString)
|
||||
})
|
||||
app.get('/empty', (c) => c.text(''))
|
||||
app.get('/redirect', (c) => {
|
||||
return c.redirect('/empty', 301)
|
||||
})
|
||||
app.get('/server-error', (c) => {
|
||||
const res = new Response('', { status: 511 })
|
||||
if (c.req.query('status')) {
|
||||
// test status code not yet supported by runtime `Response` object
|
||||
Object.defineProperty(res, 'status', { value: parseInt(c.req.query('status') as string) })
|
||||
}
|
||||
return res
|
||||
})
|
||||
})
|
||||
|
||||
it('Log status 200 with empty body', async () => {
|
||||
@ -62,6 +73,14 @@ describe('Logger by Middleware', () => {
|
||||
expect(log).toMatch(/1s/)
|
||||
})
|
||||
|
||||
it('Log status 301 with empty body', async () => {
|
||||
const res = await app.request('http://localhost/redirect')
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(301)
|
||||
expect(log.startsWith('--> GET /redirect \x1b[36m301\x1b[0m')).toBe(true)
|
||||
expect(log).toMatch(/m?s$/)
|
||||
})
|
||||
|
||||
it('Log status 404', async () => {
|
||||
const msg = 'Default 404 Not Found'
|
||||
app.all('*', (c) => {
|
||||
@ -73,6 +92,30 @@ describe('Logger by Middleware', () => {
|
||||
expect(log.startsWith('--> GET /notfound \x1b[33m404\x1b[0m')).toBe(true)
|
||||
expect(log).toMatch(/m?s$/)
|
||||
})
|
||||
|
||||
it('Log status 511 with empty body', async () => {
|
||||
const res = await app.request('http://localhost/server-error')
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(511)
|
||||
expect(log.startsWith('--> GET /server-error \x1b[31m511\x1b[0m')).toBe(true)
|
||||
expect(log).toMatch(/m?s$/)
|
||||
})
|
||||
|
||||
it('Log status 100', async () => {
|
||||
const res = await app.request('http://localhost/server-error?status=100')
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(100)
|
||||
expect(log.startsWith('--> GET /server-error 100')).toBe(true)
|
||||
expect(log).toMatch(/m?s$/)
|
||||
})
|
||||
|
||||
it('Log status 700', async () => {
|
||||
const res = await app.request('http://localhost/server-error?status=700')
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(700)
|
||||
expect(log.startsWith('--> GET /server-error 700')).toBe(true)
|
||||
expect(log).toMatch(/m?s$/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Logger by Middleware in NO_COLOR', () => {
|
||||
|
@ -28,19 +28,22 @@ const time = (start: number) => {
|
||||
|
||||
const colorStatus = (status: number) => {
|
||||
const colorEnabled = getColorEnabled()
|
||||
const out: { [key: string]: string } = {
|
||||
7: colorEnabled ? `\x1b[35m${status}\x1b[0m` : `${status}`,
|
||||
5: colorEnabled ? `\x1b[31m${status}\x1b[0m` : `${status}`,
|
||||
4: colorEnabled ? `\x1b[33m${status}\x1b[0m` : `${status}`,
|
||||
3: colorEnabled ? `\x1b[36m${status}\x1b[0m` : `${status}`,
|
||||
2: colorEnabled ? `\x1b[32m${status}\x1b[0m` : `${status}`,
|
||||
1: colorEnabled ? `\x1b[32m${status}\x1b[0m` : `${status}`,
|
||||
0: colorEnabled ? `\x1b[33m${status}\x1b[0m` : `${status}`,
|
||||
if (colorEnabled) {
|
||||
switch ((status / 100) | 0) {
|
||||
case 5: // red = error
|
||||
return `\x1b[31m${status}\x1b[0m`
|
||||
case 4: // yellow = warning
|
||||
return `\x1b[33m${status}\x1b[0m`
|
||||
case 3: // cyan = redirect
|
||||
return `\x1b[36m${status}\x1b[0m`
|
||||
case 2: // green = success
|
||||
return `\x1b[32m${status}\x1b[0m`
|
||||
}
|
||||
}
|
||||
|
||||
const calculateStatus = (status / 100) | 0
|
||||
|
||||
return out[calculateStatus]
|
||||
// Fallback to unsupported status code.
|
||||
// E.g.) Bun and Deno supports new Response with 101, but Node.js does not.
|
||||
// And those may evolve to accept more status.
|
||||
return `${status}`
|
||||
}
|
||||
|
||||
type PrintFunc = (str: string, ...rest: string[]) => void
|
||||
|
@ -91,21 +91,21 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
|
||||
param(key: string): string | undefined
|
||||
param<P2 extends string = P>(): Simplify<UnionToIntersection<ParamKeyToRecord<ParamKeys<P2>>>>
|
||||
param(key?: string): unknown {
|
||||
return key ? this.getDecodedParam(key) : this.getAllDecodedParams()
|
||||
return key ? this.#getDecodedParam(key) : this.#getAllDecodedParams()
|
||||
}
|
||||
|
||||
private getDecodedParam(key: string): string | undefined {
|
||||
#getDecodedParam(key: string): string | undefined {
|
||||
const paramKey = this.#matchResult[0][this.routeIndex][1][key]
|
||||
const param = this.getParamValue(paramKey)
|
||||
const param = this.#getParamValue(paramKey)
|
||||
return param ? (/\%/.test(param) ? tryDecodeURIComponent(param) : param) : undefined
|
||||
}
|
||||
|
||||
private getAllDecodedParams(): Record<string, string> {
|
||||
#getAllDecodedParams(): Record<string, string> {
|
||||
const decoded: Record<string, string> = {}
|
||||
|
||||
const keys = Object.keys(this.#matchResult[0][this.routeIndex][1])
|
||||
for (const key of keys) {
|
||||
const value = this.getParamValue(this.#matchResult[0][this.routeIndex][1][key])
|
||||
const value = this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key])
|
||||
if (value && typeof value === 'string') {
|
||||
decoded[key] = /\%/.test(value) ? tryDecodeURIComponent(value) : value
|
||||
}
|
||||
@ -114,7 +114,7 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
|
||||
return decoded
|
||||
}
|
||||
|
||||
private getParamValue(paramKey: any): string | undefined {
|
||||
#getParamValue(paramKey: any): string | undefined {
|
||||
return this.#matchResult[1] ? this.#matchResult[1][paramKey as any] : paramKey
|
||||
}
|
||||
|
||||
@ -208,7 +208,7 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
|
||||
return (this.bodyCache.parsedBody ??= await parseBody(this, options))
|
||||
}
|
||||
|
||||
private cachedBody = (key: keyof Body) => {
|
||||
#cachedBody = (key: keyof Body) => {
|
||||
const { bodyCache, raw } = this
|
||||
const cachedBody = bodyCache[key]
|
||||
|
||||
@ -242,7 +242,7 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
|
||||
* ```
|
||||
*/
|
||||
json<T = any>(): Promise<T> {
|
||||
return this.cachedBody('json')
|
||||
return this.#cachedBody('json')
|
||||
}
|
||||
|
||||
/**
|
||||
@ -258,7 +258,7 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
|
||||
* ```
|
||||
*/
|
||||
text(): Promise<string> {
|
||||
return this.cachedBody('text')
|
||||
return this.#cachedBody('text')
|
||||
}
|
||||
|
||||
/**
|
||||
@ -274,7 +274,7 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
|
||||
* ```
|
||||
*/
|
||||
arrayBuffer(): Promise<ArrayBuffer> {
|
||||
return this.cachedBody('arrayBuffer')
|
||||
return this.#cachedBody('arrayBuffer')
|
||||
}
|
||||
|
||||
/**
|
||||
@ -288,7 +288,7 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
|
||||
* @see https://hono.dev/docs/api/request#blob
|
||||
*/
|
||||
blob(): Promise<Blob> {
|
||||
return this.cachedBody('blob')
|
||||
return this.#cachedBody('blob')
|
||||
}
|
||||
|
||||
/**
|
||||
@ -302,7 +302,7 @@ export class HonoRequest<P extends string = '/', I extends Input['out'] = {}> {
|
||||
* @see https://hono.dev/docs/api/request#formdata
|
||||
*/
|
||||
formData(): Promise<FormData> {
|
||||
return this.cachedBody('formData')
|
||||
return this.#cachedBody('formData')
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -10,7 +10,7 @@ const splitPathRe = /\/(:\w+(?:{(?:(?:{[\d,]+})|[^}])+})?)|\/[^\/\?]+|(\?)/g
|
||||
const splitByStarRe = /\*/
|
||||
export class LinearRouter<T> implements Router<T> {
|
||||
name: string = 'LinearRouter'
|
||||
routes: [string, string, T][] = []
|
||||
#routes: [string, string, T][] = []
|
||||
|
||||
add(method: string, path: string, handler: T) {
|
||||
for (
|
||||
@ -18,14 +18,14 @@ export class LinearRouter<T> implements Router<T> {
|
||||
i < len;
|
||||
i++
|
||||
) {
|
||||
this.routes.push([method, paths[i], handler])
|
||||
this.#routes.push([method, paths[i], handler])
|
||||
}
|
||||
}
|
||||
|
||||
match(method: string, path: string): Result<T> {
|
||||
const handlers: [T, Params][] = []
|
||||
ROUTES_LOOP: for (let i = 0, len = this.routes.length; i < len; i++) {
|
||||
const [routeMethod, routePath, handler] = this.routes[i]
|
||||
ROUTES_LOOP: for (let i = 0, len = this.#routes.length; i < len; i++) {
|
||||
const [routeMethod, routePath, handler] = this.#routes[i]
|
||||
if (routeMethod === method || routeMethod === METHOD_NAME_ALL) {
|
||||
if (routePath === '*' || routePath === '/*') {
|
||||
handlers.push([handler, emptyParams])
|
||||
|
@ -5,7 +5,7 @@ type Route<T> = [RegExp, string, T] // [pattern, method, handler, path]
|
||||
|
||||
export class PatternRouter<T> implements Router<T> {
|
||||
name: string = 'PatternRouter'
|
||||
private routes: Route<T>[] = []
|
||||
#routes: Route<T>[] = []
|
||||
|
||||
add(method: string, path: string, handler: T) {
|
||||
const endsWithWildcard = path[path.length - 1] === '*'
|
||||
@ -34,14 +34,14 @@ export class PatternRouter<T> implements Router<T> {
|
||||
} catch {
|
||||
throw new UnsupportedPathError()
|
||||
}
|
||||
this.routes.push([re, method, handler])
|
||||
this.#routes.push([re, method, handler])
|
||||
}
|
||||
|
||||
match(method: string, path: string): Result<T> {
|
||||
const handlers: [T, Params][] = []
|
||||
|
||||
for (let i = 0, len = this.routes.length; i < len; i++) {
|
||||
const [pattern, routeMethod, handler] = this.routes[i]
|
||||
for (let i = 0, len = this.#routes.length; i < len; i++) {
|
||||
const [pattern, routeMethod, handler] = this.#routes[i]
|
||||
|
||||
if (routeMethod === method || routeMethod === METHOD_NAME_ALL) {
|
||||
const match = pattern.exec(path)
|
||||
|
@ -43,9 +43,9 @@ function compareKey(a: string, b: string): number {
|
||||
}
|
||||
|
||||
export class Node {
|
||||
index?: number
|
||||
varIndex?: number
|
||||
children: Record<string, Node> = Object.create(null)
|
||||
#index?: number
|
||||
#varIndex?: number
|
||||
#children: Record<string, Node> = Object.create(null)
|
||||
|
||||
insert(
|
||||
tokens: readonly string[],
|
||||
@ -55,14 +55,14 @@ export class Node {
|
||||
pathErrorCheckOnly: boolean
|
||||
): void {
|
||||
if (tokens.length === 0) {
|
||||
if (this.index !== undefined) {
|
||||
if (this.#index !== undefined) {
|
||||
throw PATH_ERROR
|
||||
}
|
||||
if (pathErrorCheckOnly) {
|
||||
return
|
||||
}
|
||||
|
||||
this.index = index
|
||||
this.#index = index
|
||||
return
|
||||
}
|
||||
|
||||
@ -88,10 +88,10 @@ export class Node {
|
||||
}
|
||||
}
|
||||
|
||||
node = this.children[regexpStr]
|
||||
node = this.#children[regexpStr]
|
||||
if (!node) {
|
||||
if (
|
||||
Object.keys(this.children).some(
|
||||
Object.keys(this.#children).some(
|
||||
(k) => k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR
|
||||
)
|
||||
) {
|
||||
@ -100,19 +100,19 @@ export class Node {
|
||||
if (pathErrorCheckOnly) {
|
||||
return
|
||||
}
|
||||
node = this.children[regexpStr] = new Node()
|
||||
node = this.#children[regexpStr] = new Node()
|
||||
if (name !== '') {
|
||||
node.varIndex = context.varIndex++
|
||||
node.#varIndex = context.varIndex++
|
||||
}
|
||||
}
|
||||
if (!pathErrorCheckOnly && name !== '') {
|
||||
paramMap.push([name, node.varIndex as number])
|
||||
paramMap.push([name, node.#varIndex as number])
|
||||
}
|
||||
} else {
|
||||
node = this.children[token]
|
||||
node = this.#children[token]
|
||||
if (!node) {
|
||||
if (
|
||||
Object.keys(this.children).some(
|
||||
Object.keys(this.#children).some(
|
||||
(k) =>
|
||||
k.length > 1 && k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR
|
||||
)
|
||||
@ -122,7 +122,7 @@ export class Node {
|
||||
if (pathErrorCheckOnly) {
|
||||
return
|
||||
}
|
||||
node = this.children[token] = new Node()
|
||||
node = this.#children[token] = new Node()
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,21 +130,21 @@ export class Node {
|
||||
}
|
||||
|
||||
buildRegExpStr(): string {
|
||||
const childKeys = Object.keys(this.children).sort(compareKey)
|
||||
const childKeys = Object.keys(this.#children).sort(compareKey)
|
||||
|
||||
const strList = childKeys.map((k) => {
|
||||
const c = this.children[k]
|
||||
const c = this.#children[k]
|
||||
return (
|
||||
(typeof c.varIndex === 'number'
|
||||
? `(${k})@${c.varIndex}`
|
||||
(typeof c.#varIndex === 'number'
|
||||
? `(${k})@${c.#varIndex}`
|
||||
: regExpMetaChars.has(k)
|
||||
? `\\${k}`
|
||||
: k) + c.buildRegExpStr()
|
||||
)
|
||||
})
|
||||
|
||||
if (typeof this.index === 'number') {
|
||||
strList.unshift(`#${this.index}`)
|
||||
if (typeof this.#index === 'number') {
|
||||
strList.unshift(`#${this.#index}`)
|
||||
}
|
||||
|
||||
if (strList.length === 0) {
|
||||
|
@ -123,16 +123,17 @@ function findMiddleware<T>(
|
||||
|
||||
export class RegExpRouter<T> implements Router<T> {
|
||||
name: string = 'RegExpRouter'
|
||||
middleware?: Record<string, Record<string, HandlerWithMetadata<T>[]>>
|
||||
routes?: Record<string, Record<string, HandlerWithMetadata<T>[]>>
|
||||
#middleware?: Record<string, Record<string, HandlerWithMetadata<T>[]>>
|
||||
#routes?: Record<string, Record<string, HandlerWithMetadata<T>[]>>
|
||||
|
||||
constructor() {
|
||||
this.middleware = { [METHOD_NAME_ALL]: Object.create(null) }
|
||||
this.routes = { [METHOD_NAME_ALL]: Object.create(null) }
|
||||
this.#middleware = { [METHOD_NAME_ALL]: Object.create(null) }
|
||||
this.#routes = { [METHOD_NAME_ALL]: Object.create(null) }
|
||||
}
|
||||
|
||||
add(method: string, path: string, handler: T) {
|
||||
const { middleware, routes } = this
|
||||
const middleware = this.#middleware
|
||||
const routes = this.#routes
|
||||
|
||||
if (!middleware || !routes) {
|
||||
throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT)
|
||||
@ -207,7 +208,7 @@ export class RegExpRouter<T> implements Router<T> {
|
||||
match(method: string, path: string): Result<T> {
|
||||
clearWildcardRegExpCache() // no longer used.
|
||||
|
||||
const matchers = this.buildAllMatchers()
|
||||
const matchers = this.#buildAllMatchers()
|
||||
|
||||
this.match = (method, path) => {
|
||||
const matcher = (matchers[method] || matchers[METHOD_NAME_ALL]) as Matcher<T>
|
||||
@ -229,27 +230,27 @@ export class RegExpRouter<T> implements Router<T> {
|
||||
return this.match(method, path)
|
||||
}
|
||||
|
||||
private buildAllMatchers(): Record<string, Matcher<T> | null> {
|
||||
#buildAllMatchers(): Record<string, Matcher<T> | null> {
|
||||
const matchers: Record<string, Matcher<T> | null> = Object.create(null)
|
||||
|
||||
Object.keys(this.routes!)
|
||||
.concat(Object.keys(this.middleware!))
|
||||
Object.keys(this.#routes!)
|
||||
.concat(Object.keys(this.#middleware!))
|
||||
.forEach((method) => {
|
||||
matchers[method] ||= this.buildMatcher(method)
|
||||
matchers[method] ||= this.#buildMatcher(method)
|
||||
})
|
||||
|
||||
// Release cache
|
||||
this.middleware = this.routes = undefined
|
||||
this.#middleware = this.#routes = undefined
|
||||
|
||||
return matchers
|
||||
}
|
||||
|
||||
private buildMatcher(method: string): Matcher<T> | null {
|
||||
#buildMatcher(method: string): Matcher<T> | null {
|
||||
const routes: [string, HandlerWithMetadata<T>[]][] = []
|
||||
|
||||
let hasOwnRoute = method === METHOD_NAME_ALL
|
||||
|
||||
;[this.middleware!, this.routes!].forEach((r) => {
|
||||
;[this.#middleware!, this.#routes!].forEach((r) => {
|
||||
const ownRoute = r[method]
|
||||
? Object.keys(r[method]).map((path) => [path, r[method][path]])
|
||||
: []
|
||||
|
@ -4,8 +4,8 @@ import { Node } from './node'
|
||||
export type ReplacementMap = number[]
|
||||
|
||||
export class Trie {
|
||||
context: Context = { varIndex: 0 }
|
||||
root: Node = new Node()
|
||||
#context: Context = { varIndex: 0 }
|
||||
#root: Node = new Node()
|
||||
|
||||
insert(path: string, index: number, pathErrorCheckOnly: boolean): ParamAssocArray {
|
||||
const paramAssoc: ParamAssocArray = []
|
||||
@ -41,13 +41,13 @@ export class Trie {
|
||||
}
|
||||
}
|
||||
|
||||
this.root.insert(tokens, index, paramAssoc, this.context, pathErrorCheckOnly)
|
||||
this.#root.insert(tokens, index, paramAssoc, this.#context, pathErrorCheckOnly)
|
||||
|
||||
return paramAssoc
|
||||
}
|
||||
|
||||
buildRegExp(): [RegExp, ReplacementMap, ReplacementMap] {
|
||||
let regexp = this.root.buildRegExpStr()
|
||||
let regexp = this.#root.buildRegExpStr()
|
||||
if (regexp === '') {
|
||||
return [/^$/, [], []] // never match
|
||||
}
|
||||
|
@ -3,27 +3,29 @@ import { MESSAGE_MATCHER_IS_ALREADY_BUILT, UnsupportedPathError } from '../../ro
|
||||
|
||||
export class SmartRouter<T> implements Router<T> {
|
||||
name: string = 'SmartRouter'
|
||||
routers: Router<T>[] = []
|
||||
routes?: [string, string, T][] = []
|
||||
#routers: Router<T>[] = []
|
||||
#routes?: [string, string, T][] = []
|
||||
|
||||
constructor(init: Pick<SmartRouter<T>, 'routers'>) {
|
||||
Object.assign(this, init)
|
||||
constructor(init: { routers: Router<T>[] }) {
|
||||
this.#routers = init.routers
|
||||
}
|
||||
|
||||
add(method: string, path: string, handler: T) {
|
||||
if (!this.routes) {
|
||||
if (!this.#routes) {
|
||||
throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT)
|
||||
}
|
||||
|
||||
this.routes.push([method, path, handler])
|
||||
this.#routes.push([method, path, handler])
|
||||
}
|
||||
|
||||
match(method: string, path: string): Result<T> {
|
||||
if (!this.routes) {
|
||||
if (!this.#routes) {
|
||||
throw new Error('Fatal error')
|
||||
}
|
||||
|
||||
const { routers, routes } = this
|
||||
const routers = this.#routers
|
||||
const routes = this.#routes
|
||||
|
||||
const len = routers.length
|
||||
let i = 0
|
||||
let res
|
||||
@ -42,8 +44,8 @@ export class SmartRouter<T> implements Router<T> {
|
||||
}
|
||||
|
||||
this.match = router.match.bind(router)
|
||||
this.routers = [router]
|
||||
this.routes = undefined
|
||||
this.#routers = [router]
|
||||
this.#routes = undefined
|
||||
break
|
||||
}
|
||||
|
||||
@ -59,10 +61,10 @@ export class SmartRouter<T> implements Router<T> {
|
||||
}
|
||||
|
||||
get activeRouter(): Router<T> {
|
||||
if (this.routes || this.routers.length !== 1) {
|
||||
if (this.#routes || this.#routers.length !== 1) {
|
||||
throw new Error('No active router has been determined yet.')
|
||||
}
|
||||
|
||||
return this.routers[0]
|
||||
return this.#routers[0]
|
||||
}
|
||||
}
|
||||
|
@ -14,26 +14,26 @@ type HandlerParamsSet<T> = HandlerSet<T> & {
|
||||
}
|
||||
|
||||
export class Node<T> {
|
||||
methods: Record<string, HandlerSet<T>>[]
|
||||
#methods: Record<string, HandlerSet<T>>[]
|
||||
|
||||
children: Record<string, Node<T>>
|
||||
patterns: Pattern[]
|
||||
order: number = 0
|
||||
params: Record<string, string> = Object.create(null)
|
||||
#children: Record<string, Node<T>>
|
||||
#patterns: Pattern[]
|
||||
#order: number = 0
|
||||
#params: Record<string, string> = Object.create(null)
|
||||
|
||||
constructor(method?: string, handler?: T, children?: Record<string, Node<T>>) {
|
||||
this.children = children || Object.create(null)
|
||||
this.methods = []
|
||||
this.#children = children || Object.create(null)
|
||||
this.#methods = []
|
||||
if (method && handler) {
|
||||
const m: Record<string, HandlerSet<T>> = Object.create(null)
|
||||
m[method] = { handler, possibleKeys: [], score: 0 }
|
||||
this.methods = [m]
|
||||
this.#methods = [m]
|
||||
}
|
||||
this.patterns = []
|
||||
this.#patterns = []
|
||||
}
|
||||
|
||||
insert(method: string, path: string, handler: T): Node<T> {
|
||||
this.order = ++this.order
|
||||
this.#order = ++this.#order
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
let curNode: Node<T> = this
|
||||
@ -44,8 +44,8 @@ export class Node<T> {
|
||||
for (let i = 0, len = parts.length; i < len; i++) {
|
||||
const p: string = parts[i]
|
||||
|
||||
if (Object.keys(curNode.children).includes(p)) {
|
||||
curNode = curNode.children[p]
|
||||
if (Object.keys(curNode.#children).includes(p)) {
|
||||
curNode = curNode.#children[p]
|
||||
const pattern = getPattern(p)
|
||||
if (pattern) {
|
||||
possibleKeys.push(pattern[1])
|
||||
@ -53,18 +53,14 @@ export class Node<T> {
|
||||
continue
|
||||
}
|
||||
|
||||
curNode.children[p] = new Node()
|
||||
curNode.#children[p] = new Node()
|
||||
|
||||
const pattern = getPattern(p)
|
||||
if (pattern) {
|
||||
curNode.patterns.push(pattern)
|
||||
curNode.#patterns.push(pattern)
|
||||
possibleKeys.push(pattern[1])
|
||||
}
|
||||
curNode = curNode.children[p]
|
||||
}
|
||||
|
||||
if (!curNode.methods.length) {
|
||||
curNode.methods = []
|
||||
curNode = curNode.#children[p]
|
||||
}
|
||||
|
||||
const m: Record<string, HandlerSet<T>> = Object.create(null)
|
||||
@ -72,27 +68,27 @@ export class Node<T> {
|
||||
const handlerSet: HandlerSet<T> = {
|
||||
handler,
|
||||
possibleKeys: possibleKeys.filter((v, i, a) => a.indexOf(v) === i),
|
||||
score: this.order,
|
||||
score: this.#order,
|
||||
}
|
||||
|
||||
m[method] = handlerSet
|
||||
curNode.methods.push(m)
|
||||
curNode.#methods.push(m)
|
||||
|
||||
return curNode
|
||||
}
|
||||
|
||||
// getHandlerSets
|
||||
private gHSets(
|
||||
#getHandlerSets(
|
||||
node: Node<T>,
|
||||
method: string,
|
||||
nodeParams: Record<string, string>,
|
||||
params: Record<string, string>
|
||||
): HandlerParamsSet<T>[] {
|
||||
const handlerSets: HandlerParamsSet<T>[] = []
|
||||
for (let i = 0, len = node.methods.length; i < len; i++) {
|
||||
const m = node.methods[i]
|
||||
for (let i = 0, len = node.#methods.length; i < len; i++) {
|
||||
const m = node.#methods[i]
|
||||
const handlerSet = (m[method] || m[METHOD_NAME_ALL]) as HandlerParamsSet<T>
|
||||
const processedSet: Record<string, boolean> = Object.create(null)
|
||||
const processedSet: Record<number, boolean> = {}
|
||||
if (handlerSet !== undefined) {
|
||||
handlerSet.params = Object.create(null)
|
||||
for (let i = 0, len = handlerSet.possibleKeys.length; i < len; i++) {
|
||||
@ -111,7 +107,7 @@ export class Node<T> {
|
||||
|
||||
search(method: string, path: string): [[T, Params][]] {
|
||||
const handlerSets: HandlerParamsSet<T>[] = []
|
||||
this.params = Object.create(null)
|
||||
this.#params = Object.create(null)
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const curNode: Node<T> = this
|
||||
@ -125,34 +121,43 @@ export class Node<T> {
|
||||
|
||||
for (let j = 0, len2 = curNodes.length; j < len2; j++) {
|
||||
const node = curNodes[j]
|
||||
const nextNode = node.children[part]
|
||||
const nextNode = node.#children[part]
|
||||
|
||||
if (nextNode) {
|
||||
nextNode.params = node.params
|
||||
nextNode.#params = node.#params
|
||||
if (isLast) {
|
||||
// '/hello/*' => match '/hello'
|
||||
if (nextNode.children['*']) {
|
||||
if (nextNode.#children['*']) {
|
||||
handlerSets.push(
|
||||
...this.gHSets(nextNode.children['*'], method, node.params, Object.create(null))
|
||||
...this.#getHandlerSets(
|
||||
nextNode.#children['*'],
|
||||
method,
|
||||
node.#params,
|
||||
Object.create(null)
|
||||
)
|
||||
)
|
||||
}
|
||||
handlerSets.push(...this.gHSets(nextNode, method, node.params, Object.create(null)))
|
||||
handlerSets.push(
|
||||
...this.#getHandlerSets(nextNode, method, node.#params, Object.create(null))
|
||||
)
|
||||
} else {
|
||||
tempNodes.push(nextNode)
|
||||
}
|
||||
}
|
||||
|
||||
for (let k = 0, len3 = node.patterns.length; k < len3; k++) {
|
||||
const pattern = node.patterns[k]
|
||||
for (let k = 0, len3 = node.#patterns.length; k < len3; k++) {
|
||||
const pattern = node.#patterns[k]
|
||||
|
||||
const params = { ...node.params }
|
||||
const params = { ...node.#params }
|
||||
|
||||
// Wildcard
|
||||
// '/hello/*/foo' => match /hello/bar/foo
|
||||
if (pattern === '*') {
|
||||
const astNode = node.children['*']
|
||||
const astNode = node.#children['*']
|
||||
if (astNode) {
|
||||
handlerSets.push(...this.gHSets(astNode, method, node.params, Object.create(null)))
|
||||
handlerSets.push(
|
||||
...this.#getHandlerSets(astNode, method, node.#params, Object.create(null))
|
||||
)
|
||||
tempNodes.push(astNode)
|
||||
}
|
||||
continue
|
||||
@ -164,28 +169,28 @@ export class Node<T> {
|
||||
|
||||
const [key, name, matcher] = pattern
|
||||
|
||||
const child = node.children[key]
|
||||
const child = node.#children[key]
|
||||
|
||||
// `/js/:filename{[a-z]+.js}` => match /js/chunk/123.js
|
||||
const restPathString = parts.slice(i).join('/')
|
||||
if (matcher instanceof RegExp && matcher.test(restPathString)) {
|
||||
params[name] = restPathString
|
||||
handlerSets.push(...this.gHSets(child, method, node.params, params))
|
||||
handlerSets.push(...this.#getHandlerSets(child, method, node.#params, params))
|
||||
continue
|
||||
}
|
||||
|
||||
if (matcher === true || matcher.test(part)) {
|
||||
if (typeof key === 'string') {
|
||||
params[name] = part
|
||||
if (isLast) {
|
||||
handlerSets.push(...this.gHSets(child, method, params, node.params))
|
||||
if (child.children['*']) {
|
||||
handlerSets.push(...this.gHSets(child.children['*'], method, params, node.params))
|
||||
}
|
||||
} else {
|
||||
child.params = params
|
||||
tempNodes.push(child)
|
||||
params[name] = part
|
||||
if (isLast) {
|
||||
handlerSets.push(...this.#getHandlerSets(child, method, params, node.#params))
|
||||
if (child.#children['*']) {
|
||||
handlerSets.push(
|
||||
...this.#getHandlerSets(child.#children['*'], method, params, node.#params)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
child.#params = params
|
||||
tempNodes.push(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,25 +4,25 @@ import { Node } from './node'
|
||||
|
||||
export class TrieRouter<T> implements Router<T> {
|
||||
name: string = 'TrieRouter'
|
||||
node: Node<T>
|
||||
#node: Node<T>
|
||||
|
||||
constructor() {
|
||||
this.node = new Node()
|
||||
this.#node = new Node()
|
||||
}
|
||||
|
||||
add(method: string, path: string, handler: T) {
|
||||
const results = checkOptionalParameter(path)
|
||||
if (results) {
|
||||
for (let i = 0, len = results.length; i < len; i++) {
|
||||
this.node.insert(method, results[i], handler)
|
||||
this.#node.insert(method, results[i], handler)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
this.node.insert(method, path, handler)
|
||||
this.#node.insert(method, path, handler)
|
||||
}
|
||||
|
||||
match(method: string, path: string): Result<T> {
|
||||
return this.node.search(method, path)
|
||||
return this.#node.search(method, path)
|
||||
}
|
||||
}
|
||||
|
@ -1985,7 +1985,7 @@ export type ExtractSchema<T> = UnionToIntersection<
|
||||
>
|
||||
|
||||
type EnvOrEmpty<T> = T extends Env ? (Env extends T ? {} : T) : T
|
||||
type IntersectNonAnyTypes<T extends any[]> = T extends [infer Head, ...infer Rest]
|
||||
export type IntersectNonAnyTypes<T extends any[]> = T extends [infer Head, ...infer Rest]
|
||||
? IfAnyThenEmptyObject<EnvOrEmpty<Head>> & IntersectNonAnyTypes<Rest>
|
||||
: {}
|
||||
|
||||
|
@ -30,6 +30,14 @@ describe('Parse cookie', () => {
|
||||
expect(cookie['tasty_cookie']).toBeUndefined()
|
||||
})
|
||||
|
||||
it('Should parse one cookie specified by name even if it is not found', () => {
|
||||
const cookieString = 'yummy_cookie=choco; tasty_cookie = strawberry '
|
||||
const cookie: Cookie = parse(cookieString, 'no_such_cookie')
|
||||
expect(cookie['yummy_cookie']).toBeUndefined()
|
||||
expect(cookie['tasty_cookie']).toBeUndefined()
|
||||
expect(cookie['no_such_cookie']).toBeUndefined()
|
||||
})
|
||||
|
||||
it('Should parse cookies with no value', () => {
|
||||
const cookieString = 'yummy_cookie=; tasty_cookie = ; best_cookie= ; last_cookie=""'
|
||||
const cookie: Cookie = parse(cookieString)
|
||||
|
@ -77,17 +77,22 @@ const validCookieNameRegEx = /^[\w!#$%&'*.^`|~+-]+$/
|
||||
const validCookieValueRegEx = /^[ !#-:<-[\]-~]*$/
|
||||
|
||||
export const parse = (cookie: string, name?: string): Cookie => {
|
||||
if (name && cookie.indexOf(name) === -1) {
|
||||
// Fast-path: return immediately if the demanded-key is not in the cookie string
|
||||
return {}
|
||||
}
|
||||
const pairs = cookie.trim().split(';')
|
||||
return pairs.reduce((parsedCookie, pairStr) => {
|
||||
const parsedCookie: Cookie = {}
|
||||
for (let pairStr of pairs) {
|
||||
pairStr = pairStr.trim()
|
||||
const valueStartPos = pairStr.indexOf('=')
|
||||
if (valueStartPos === -1) {
|
||||
return parsedCookie
|
||||
continue
|
||||
}
|
||||
|
||||
const cookieName = pairStr.substring(0, valueStartPos).trim()
|
||||
if ((name && name !== cookieName) || !validCookieNameRegEx.test(cookieName)) {
|
||||
return parsedCookie
|
||||
continue
|
||||
}
|
||||
|
||||
let cookieValue = pairStr.substring(valueStartPos + 1).trim()
|
||||
@ -96,10 +101,13 @@ export const parse = (cookie: string, name?: string): Cookie => {
|
||||
}
|
||||
if (validCookieValueRegEx.test(cookieValue)) {
|
||||
parsedCookie[cookieName] = decodeURIComponent_(cookieValue)
|
||||
if (name) {
|
||||
// Fast-path: return only the demanded-key immediately. Other keys are not needed.
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return parsedCookie
|
||||
}, {} as Cookie)
|
||||
}
|
||||
return parsedCookie
|
||||
}
|
||||
|
||||
export const parseSigned = async (
|
||||
@ -162,7 +170,7 @@ const _serialize = (name: string, value: string, opt: CookieOptions = {}): strin
|
||||
'Cookies Max-Age SHOULD NOT be greater than 400 days (34560000 seconds) in duration.'
|
||||
)
|
||||
}
|
||||
cookie += `; Max-Age=${Math.floor(opt.maxAge)}`
|
||||
cookie += `; Max-Age=${opt.maxAge | 0}`
|
||||
}
|
||||
|
||||
if (opt.domain && opt.prefix !== 'host') {
|
||||
|
@ -3,12 +3,14 @@
|
||||
* Crypto utility.
|
||||
*/
|
||||
|
||||
import type { JSONValue } from './types'
|
||||
|
||||
type Algorithm = {
|
||||
name: string
|
||||
alias: string
|
||||
}
|
||||
|
||||
type Data = string | boolean | number | object | ArrayBufferView | ArrayBuffer | ReadableStream
|
||||
type Data = string | boolean | number | JSONValue | ArrayBufferView | ArrayBuffer
|
||||
|
||||
export const sha256 = async (data: Data): Promise<string | null> => {
|
||||
const algorithm: Algorithm = { name: 'SHA-256', alias: 'sha256' }
|
||||
@ -31,15 +33,6 @@ export const md5 = async (data: Data): Promise<string | null> => {
|
||||
export const createHash = async (data: Data, algorithm: Algorithm): Promise<string | null> => {
|
||||
let sourceBuffer: ArrayBufferView | ArrayBuffer
|
||||
|
||||
if (data instanceof ReadableStream) {
|
||||
let body = ''
|
||||
const reader = data.getReader()
|
||||
await reader?.read().then(async (chuck) => {
|
||||
const value = await createHash(chuck.value || '', algorithm)
|
||||
body += value
|
||||
})
|
||||
return body
|
||||
}
|
||||
if (ArrayBuffer.isView(data) || data instanceof ArrayBuffer) {
|
||||
sourceBuffer = data
|
||||
} else {
|
||||
|
@ -74,7 +74,7 @@ export const verify = async (
|
||||
if (!isTokenHeader(header)) {
|
||||
throw new JwtHeaderInvalid(header)
|
||||
}
|
||||
const now = Math.floor(Date.now() / 1000)
|
||||
const now = (Date.now() / 1000) | 0
|
||||
if (payload.nbf && payload.nbf > now) {
|
||||
throw new JwtTokenNotBefore(token)
|
||||
}
|
||||
|
@ -32,58 +32,9 @@ export { baseMimes as mimes }
|
||||
/**
|
||||
* Union types for BaseMime
|
||||
*/
|
||||
export type BaseMime =
|
||||
| 'audio/aac'
|
||||
| 'video/x-msvideo'
|
||||
| 'image/avif'
|
||||
| 'video/av1'
|
||||
| 'application/octet-stream'
|
||||
| 'image/bmp'
|
||||
| 'text/css'
|
||||
| 'text/csv'
|
||||
| 'application/vnd.ms-fontobject'
|
||||
| 'application/epub+zip'
|
||||
| 'image/gif'
|
||||
| 'application/gzip'
|
||||
| 'text/html'
|
||||
| 'image/x-icon'
|
||||
| 'text/calendar'
|
||||
| 'image/jpeg'
|
||||
| 'text/javascript'
|
||||
| 'application/json'
|
||||
| 'application/ld+json'
|
||||
| 'audio/x-midi'
|
||||
| 'audio/mpeg'
|
||||
| 'video/mp4'
|
||||
| 'video/mpeg'
|
||||
| 'audio/ogg'
|
||||
| 'video/ogg'
|
||||
| 'application/ogg'
|
||||
| 'audio/opus'
|
||||
| 'font/otf'
|
||||
| 'application/pdf'
|
||||
| 'image/png'
|
||||
| 'application/rtf'
|
||||
| 'image/svg+xml'
|
||||
| 'image/tiff'
|
||||
| 'video/mp2t'
|
||||
| 'font/ttf'
|
||||
| 'text/plain'
|
||||
| 'application/wasm'
|
||||
| 'video/webm'
|
||||
| 'audio/webm'
|
||||
| 'image/webp'
|
||||
| 'font/woff'
|
||||
| 'font/woff2'
|
||||
| 'application/xhtml+xml'
|
||||
| 'application/xml'
|
||||
| 'application/zip'
|
||||
| 'video/3gpp'
|
||||
| 'video/3gpp2'
|
||||
| 'model/gltf+json'
|
||||
| 'model/gltf-binary'
|
||||
export type BaseMime = (typeof _baseMimes)[keyof typeof _baseMimes]
|
||||
|
||||
const baseMimes: Record<string, BaseMime> = {
|
||||
const _baseMimes = {
|
||||
aac: 'audio/aac',
|
||||
avi: 'video/x-msvideo',
|
||||
avif: 'image/avif',
|
||||
@ -139,4 +90,6 @@ const baseMimes: Record<string, BaseMime> = {
|
||||
'3g2': 'video/3gpp2',
|
||||
gltf: 'model/gltf+json',
|
||||
glb: 'model/gltf-binary',
|
||||
}
|
||||
} as const
|
||||
|
||||
const baseMimes: Record<string, BaseMime> = _baseMimes
|
||||
|
@ -8,7 +8,7 @@ export default defineConfig({
|
||||
},
|
||||
test: {
|
||||
globals: true,
|
||||
include: ['**/src/**/(*.)+(spec|test).+(ts|tsx|js)', '**/scripts/**/(*.)+(spec|test).+(ts|tsx|js)'],
|
||||
include: ['**/src/**/(*.)+(spec|test).+(ts|tsx|js)', '**/scripts/**/(*.)+(spec|test).+(ts|tsx|js)', '**/build/**/(*.)+(spec|test).+(ts|tsx|js)'],
|
||||
exclude: [...configDefaults.exclude, '**/sandbox/**', '**/*.case.test.+(ts|tsx|js)'],
|
||||
setupFiles: ['./.vitest.config/setup-vitest.ts'],
|
||||
coverage: {
|
||||
@ -20,7 +20,7 @@ export default defineConfig({
|
||||
...(configDefaults.coverage.exclude ?? []),
|
||||
'benchmarks',
|
||||
'runtime-tests',
|
||||
'build.ts',
|
||||
'build/build.ts',
|
||||
'src/test-utils',
|
||||
'perf-measures',
|
||||
|
||||
|
139
yarn.lock
139
yarn.lock
@ -1,6 +1,6 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
# bun ./bun.lockb --hash: 77B78F6BF9FCD5E8-99bd6727146efa0f-3A60C19E44E6AEA6-b935857e930aa307
|
||||
# bun ./bun.lockb --hash: D365A55B3A797610-f2453af89509670e-C1C56F357DD69CBD-b406908528ddf7b3
|
||||
|
||||
|
||||
"@ampproject/remapping@^2.3.0":
|
||||
@ -684,10 +684,10 @@
|
||||
eslint-plugin-import-x "^4.1.1"
|
||||
eslint-plugin-n "^17.10.2"
|
||||
|
||||
"@hono/node-server@^1.8.2":
|
||||
version "1.13.1"
|
||||
resolved "https://registry.npmjs.org/@hono/node-server/-/node-server-1.13.1.tgz"
|
||||
integrity sha512-TSxE6cT5RHnawbjnveexVN7H2Dpn1YaLxQrCOLCUwD+hFbqbFsnJBgdWcYtASqtWVjA+Qgi8uqFug39GsHjo5A==
|
||||
"@hono/node-server@^1.13.5":
|
||||
version "1.13.5"
|
||||
resolved "https://registry.npmjs.org/@hono/node-server/-/node-server-1.13.5.tgz"
|
||||
integrity sha512-lSo+CFlLqAFB4fX7ePqI9nauEn64wOfJHAfc9duYFTvAG3o416pC0nTGeNjuLHchLedH+XyWda5v79CVx1PIjg==
|
||||
|
||||
"@humanwhocodes/module-importer@^1.0.1":
|
||||
version "1.0.1"
|
||||
@ -699,50 +699,38 @@
|
||||
resolved "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz"
|
||||
integrity sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==
|
||||
|
||||
"@inquirer/confirm@^3.0.0":
|
||||
version "3.2.0"
|
||||
resolved "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.2.0.tgz"
|
||||
integrity sha512-oOIwPs0Dvq5220Z8lGL/6LHRTEr9TgLHmiI99Rj1PJ1p1czTys+olrgBqZk4E2qC0YTzeHprxSQmoHioVdJ7Lw==
|
||||
"@inquirer/confirm@^5.0.0":
|
||||
version "5.0.1"
|
||||
resolved "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.0.1.tgz"
|
||||
integrity sha512-6ycMm7k7NUApiMGfVc32yIPp28iPKxhGRMqoNDiUjq2RyTAkbs5Fx0TdzBqhabcKvniDdAAvHCmsRjnNfTsogw==
|
||||
dependencies:
|
||||
"@inquirer/core" "^9.1.0"
|
||||
"@inquirer/type" "^1.5.3"
|
||||
"@inquirer/core" "^10.0.1"
|
||||
"@inquirer/type" "^3.0.0"
|
||||
|
||||
"@inquirer/core@^9.1.0":
|
||||
version "9.2.1"
|
||||
resolved "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz"
|
||||
integrity sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==
|
||||
"@inquirer/core@^10.0.1":
|
||||
version "10.0.1"
|
||||
resolved "https://registry.npmjs.org/@inquirer/core/-/core-10.0.1.tgz"
|
||||
integrity sha512-KKTgjViBQUi3AAssqjUFMnMO3CM3qwCHvePV9EW+zTKGKafFGFF01sc1yOIYjLJ7QU52G/FbzKc+c01WLzXmVQ==
|
||||
dependencies:
|
||||
"@inquirer/figures" "^1.0.6"
|
||||
"@inquirer/type" "^2.0.0"
|
||||
"@types/mute-stream" "^0.0.4"
|
||||
"@types/node" "^22.5.5"
|
||||
"@types/wrap-ansi" "^3.0.0"
|
||||
"@inquirer/figures" "^1.0.7"
|
||||
"@inquirer/type" "^3.0.0"
|
||||
ansi-escapes "^4.3.2"
|
||||
cli-width "^4.1.0"
|
||||
mute-stream "^1.0.0"
|
||||
mute-stream "^2.0.0"
|
||||
signal-exit "^4.1.0"
|
||||
strip-ansi "^6.0.1"
|
||||
wrap-ansi "^6.2.0"
|
||||
yoctocolors-cjs "^2.1.2"
|
||||
|
||||
"@inquirer/figures@^1.0.6":
|
||||
version "1.0.6"
|
||||
resolved "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.6.tgz"
|
||||
integrity sha512-yfZzps3Cso2UbM7WlxKwZQh2Hs6plrbjs1QnzQDZhK2DgyCo6D8AaHps9olkNcUFlcYERMqU3uJSp1gmy3s/qQ==
|
||||
"@inquirer/figures@^1.0.7":
|
||||
version "1.0.7"
|
||||
resolved "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.7.tgz"
|
||||
integrity sha512-m+Trk77mp54Zma6xLkLuY+mvanPxlE4A7yNKs2HBiyZ4UkVs28Mv5c/pgWrHeInx+USHeX/WEPzjrWrcJiQgjw==
|
||||
|
||||
"@inquirer/type@^1.5.3":
|
||||
version "1.5.5"
|
||||
resolved "https://registry.npmjs.org/@inquirer/type/-/type-1.5.5.tgz"
|
||||
integrity sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==
|
||||
dependencies:
|
||||
mute-stream "^1.0.0"
|
||||
|
||||
"@inquirer/type@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz"
|
||||
integrity sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==
|
||||
dependencies:
|
||||
mute-stream "^1.0.0"
|
||||
"@inquirer/type@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/@inquirer/type/-/type-3.0.0.tgz"
|
||||
integrity sha512-YYykfbw/lefC7yKj7nanzQXILM7r3suIvyFlCcMskc99axmsSewXWkAfXKwMbgxL76iAFVmRwmYdwNZNc8gjog==
|
||||
|
||||
"@isaacs/cliui@^8.0.2":
|
||||
version "8.0.2"
|
||||
@ -809,10 +797,10 @@
|
||||
"@jridgewell/resolve-uri" "^3.1.0"
|
||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||
|
||||
"@mswjs/interceptors@^0.35.8":
|
||||
version "0.35.8"
|
||||
resolved "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.35.8.tgz"
|
||||
integrity sha512-PFfqpHplKa7KMdoQdj5td03uG05VK2Ng1dG0sP4pT9h0dGSX2v9txYt/AnrzPb/vAmfyBBC0NQV7VaBEX+efgQ==
|
||||
"@mswjs/interceptors@^0.36.5":
|
||||
version "0.36.9"
|
||||
resolved "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.36.9.tgz"
|
||||
integrity sha512-mMRDUBwSNeCgjSMEWfjoh4Rm9fbyZ7xQ9SBq8eGHiiyRn1ieTip3pNEt0wxWVPPxR4i1Rv9bTkeEbkX7M4c15A==
|
||||
dependencies:
|
||||
"@open-draft/deferred-promise" "^2.2.0"
|
||||
"@open-draft/logger" "^0.3.0"
|
||||
@ -1147,7 +1135,7 @@
|
||||
resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz"
|
||||
integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
|
||||
|
||||
"@types/glob@^8.0.0":
|
||||
"@types/glob@^8.1.0":
|
||||
version "8.1.0"
|
||||
resolved "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz"
|
||||
integrity sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==
|
||||
@ -1160,7 +1148,7 @@
|
||||
resolved "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz"
|
||||
integrity sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==
|
||||
|
||||
"@types/jsdom@^21.1.4":
|
||||
"@types/jsdom@^21.1.7":
|
||||
version "21.1.7"
|
||||
resolved "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz"
|
||||
integrity sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==
|
||||
@ -1196,13 +1184,6 @@
|
||||
resolved "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz"
|
||||
integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==
|
||||
|
||||
"@types/mute-stream@^0.0.4":
|
||||
version "0.0.4"
|
||||
resolved "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz"
|
||||
integrity sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/node@*", "@types/node@20.11.4":
|
||||
version "20.11.4"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-20.11.4.tgz"
|
||||
@ -1217,7 +1198,7 @@
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
"@types/node@*", "@types/node@^22.5.5":
|
||||
"@types/node@*", "@types/node@>=18":
|
||||
version "22.6.1"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-22.6.1.tgz"
|
||||
integrity sha512-V48tCfcKb/e6cVUigLAaJDAILdMP0fUW6BidkPK4GpGjXcfbnoHasCZDwz3N3yVt5we2RHm4XTQCpv0KJz9zqw==
|
||||
@ -1263,7 +1244,7 @@
|
||||
"@types/methods" "^1.1.4"
|
||||
"@types/cookiejar" "^2.1.5"
|
||||
|
||||
"@types/supertest@^2.0.12":
|
||||
"@types/supertest@^2.0.16":
|
||||
version "2.0.16"
|
||||
resolved "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.16.tgz"
|
||||
integrity sha512-6c2ogktZ06tr2ENoZivgm7YnprnhYE4ZoXGMY+oA7IuAf17M8FWvujXZGmxLv8y0PTyts4x5A+erSwVUFA8XSg==
|
||||
@ -1275,11 +1256,6 @@
|
||||
resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz"
|
||||
integrity sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==
|
||||
|
||||
"@types/wrap-ansi@^3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz"
|
||||
integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==
|
||||
|
||||
"@types/ws@~8.5.10":
|
||||
version "8.5.12"
|
||||
resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz"
|
||||
@ -1735,10 +1711,10 @@ builtins@^1.0.3:
|
||||
resolved "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz"
|
||||
integrity sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ==
|
||||
|
||||
bun-types@^1.1.30:
|
||||
version "1.1.30"
|
||||
resolved "https://registry.npmjs.org/bun-types/-/bun-types-1.1.30.tgz"
|
||||
integrity sha512-mGh7NLisOXskBU62DxLS+/nwmLlCYHYAkCzdo4DZ9+fzrpP41hAdOqaN4DO6tQfenHb4pYb0/shw29k4/6I2yQ==
|
||||
bun-types@^1.1.34:
|
||||
version "1.1.34"
|
||||
resolved "https://registry.npmjs.org/bun-types/-/bun-types-1.1.34.tgz"
|
||||
integrity sha512-br5QygTEL/TwB4uQOb96Ky22j4Gq2WxWH/8Oqv20fk5HagwKXo/akB+LiYgSfzexCt6kkcUaVm+bKiPl71xPvw==
|
||||
dependencies:
|
||||
"@types/ws" "~8.5.10"
|
||||
"@types/node" "~20.12.8"
|
||||
@ -2389,7 +2365,7 @@ es-module-lexer@^1.5.4:
|
||||
resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz"
|
||||
integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==
|
||||
|
||||
esbuild@^0.15.12:
|
||||
esbuild@^0.15.18:
|
||||
version "0.15.18"
|
||||
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz"
|
||||
integrity sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==
|
||||
@ -4308,16 +4284,17 @@ ms@^2.1.1, ms@^2.1.3:
|
||||
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
|
||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||
|
||||
msw@^2.3.0:
|
||||
version "2.4.9"
|
||||
resolved "https://registry.npmjs.org/msw/-/msw-2.4.9.tgz"
|
||||
integrity sha512-1m8xccT6ipN4PTqLinPwmzhxQREuxaEJYdx4nIbggxP8aM7r1e71vE7RtOUSQoAm1LydjGfZKy7370XD/tsuYg==
|
||||
msw@^2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.npmjs.org/msw/-/msw-2.6.0.tgz"
|
||||
integrity sha512-n3tx2w0MZ3H4pxY0ozrQ4sNPzK/dGtlr2cIIyuEsgq2Bhy4wvcW6ZH2w/gXM9+MEUY6HC1fWhqtcXDxVZr5Jxw==
|
||||
dependencies:
|
||||
"@bundled-es-modules/cookie" "^2.0.0"
|
||||
"@bundled-es-modules/statuses" "^1.0.1"
|
||||
"@bundled-es-modules/tough-cookie" "^0.1.6"
|
||||
"@inquirer/confirm" "^3.0.0"
|
||||
"@mswjs/interceptors" "^0.35.8"
|
||||
"@inquirer/confirm" "^5.0.0"
|
||||
"@mswjs/interceptors" "^0.36.5"
|
||||
"@open-draft/deferred-promise" "^2.2.0"
|
||||
"@open-draft/until" "^2.1.0"
|
||||
"@types/cookie" "^0.6.0"
|
||||
"@types/statuses" "^2.0.4"
|
||||
@ -4325,10 +4302,10 @@ msw@^2.3.0:
|
||||
graphql "^16.8.1"
|
||||
headers-polyfill "^4.0.2"
|
||||
is-node-process "^1.2.0"
|
||||
outvariant "^1.4.2"
|
||||
outvariant "^1.4.3"
|
||||
path-to-regexp "^6.3.0"
|
||||
strict-event-emitter "^0.5.1"
|
||||
type-fest "^4.9.0"
|
||||
type-fest "^4.26.1"
|
||||
yargs "^17.7.2"
|
||||
|
||||
mustache@^4.2.0:
|
||||
@ -4346,10 +4323,10 @@ mute-stream@0.0.8:
|
||||
resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz"
|
||||
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
|
||||
|
||||
mute-stream@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz"
|
||||
integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==
|
||||
mute-stream@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz"
|
||||
integrity sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==
|
||||
|
||||
nanoid@^3.3.3, nanoid@^3.3.7:
|
||||
version "3.3.7"
|
||||
@ -4604,7 +4581,7 @@ os-tmpdir@~1.0.2:
|
||||
resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz"
|
||||
integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
|
||||
|
||||
outvariant@^1.4.0, outvariant@^1.4.2, outvariant@^1.4.3:
|
||||
outvariant@^1.4.0, outvariant@^1.4.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz"
|
||||
integrity sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==
|
||||
@ -4919,7 +4896,7 @@ psl@^1.1.33:
|
||||
resolved "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz"
|
||||
integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==
|
||||
|
||||
publint@^0.1.8:
|
||||
publint@^0.1.16:
|
||||
version "0.1.16"
|
||||
resolved "https://registry.npmjs.org/publint/-/publint-0.1.16.tgz"
|
||||
integrity sha512-wJgk7HnXDT5Ap0DjFYbGz78kPkN44iQvDiaq8P63IEEyNU9mYXvaMd2cAyIM6OgqXM/IA3CK6XWIsRq+wjNpgw==
|
||||
@ -5600,7 +5577,7 @@ superagent@^8.1.2:
|
||||
component-emitter "^1.3.0"
|
||||
fast-safe-stringify "^2.1.1"
|
||||
|
||||
supertest@^6.3.3:
|
||||
supertest@^6.3.4:
|
||||
version "6.3.4"
|
||||
resolved "https://registry.npmjs.org/supertest/-/supertest-6.3.4.tgz"
|
||||
integrity sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw==
|
||||
@ -5848,7 +5825,7 @@ type-fest@^0.21.3:
|
||||
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz"
|
||||
integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
|
||||
|
||||
type-fest@^4.9.0:
|
||||
type-fest@^4.26.1:
|
||||
version "4.26.1"
|
||||
resolved "https://registry.npmjs.org/type-fest/-/type-fest-4.26.1.tgz"
|
||||
integrity sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==
|
||||
@ -6194,7 +6171,7 @@ write-file-atomic@^3.0.0:
|
||||
is-typedarray "^1.0.0"
|
||||
typedarray-to-buffer "^3.1.5"
|
||||
|
||||
ws@^8.11.0, ws@^8.13.0, ws@^8.17.0:
|
||||
ws@^8.11.0, ws@^8.13.0, ws@^8.18.0:
|
||||
version "8.18.0"
|
||||
resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz"
|
||||
integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==
|
||||
@ -6289,7 +6266,7 @@ youch@^3.2.2:
|
||||
mustache "^4.2.0"
|
||||
stacktracey "^2.1.8"
|
||||
|
||||
zod@^3.20.2, zod@^3.20.6:
|
||||
zod@^3.20.6, zod@^3.23.8:
|
||||
version "3.23.8"
|
||||
resolved "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz"
|
||||
integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==
|
||||
|
Loading…
Reference in New Issue
Block a user