Compare commits

...

24 Commits

Author SHA1 Message Date
Jonathan Haines 368bd067d1
chore(cr): publish all from main (#1246)
* chore(cr): publish all from main

* chore(cr): publish all
2025-06-22 17:40:10 +09:00
Jonathan Haines 363868b0b9
fix(ua-blocker-sync): format generated files (#1250) 2025-06-22 17:36:40 +09:00
github-actions[bot] 95845a50ca
Version Packages (#1249)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-22 13:11:19 +09:00
Palmer McCutcheon 5178967931
feat(node-ws): Add WebSocketServer instance return to createNodeWebSocket (#1213)
* Add WebSocketServer instance return to createNodeWebSocket

This change introduces the return of the WebSocketServer instance from the createNodeWebSocket function, enhancing its usability by allowing consumers access to the WebSocketServer instance for additional operations.

- Updated tests to verify the returned WebSocketServer instance
- Added wss property to NodeWebSocket interface for type safety

* style
2025-06-22 13:07:24 +09:00
Jonathan Haines aee369e728
chore(cr): publish pr to pkg.pr.new (#1242) 2025-06-21 13:23:44 +09:00
github-actions[bot] 848ecb889d
Version Packages (#1245)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-21 10:31:23 +09:00
Jonathan Haines 054257a200
feat(trpc-server): publish package exports (#1243)
fixes #1231
2025-06-21 10:28:12 +09:00
Jonathan Haines b62061faec
chore: update vitest to v3.2 (#1220) 2025-06-20 06:59:04 +09:00
Jonathan Haines 39a435b738
refactor(tsconfig): use `configDir` (#1238) 2025-06-19 16:52:41 +09:00
github-actions[bot] 074bb88b4f
Version Packages (#1240)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-19 16:27:00 +09:00
github-actions[bot] 69a278a2d3
Version Packages (#1239)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-19 15:34:00 +09:00
Robert Soriano 6ca78a14da
chore(clerk-auth): Support for Clerk Backend v2 (#1190)
* chore(clerk-auth): Introduce Clerk backend v2

* chore: clean up types

* chore: revert readme

* chore: move backend package to dependencies

* chore: add changeset

* chore: pin accepted token

* chore: run format

* chore: remove extra dependency

* chore: use token type constant

* chore: fix imports

* chore: use built-in type helpers

* chore: remove pending session options

* chore: run format
2025-06-19 15:33:50 +09:00
github-actions[bot] 6378861217
chore(ua-blocker): update robots.json from upstream (#1236)
Co-authored-by: yusukebe <10682+yusukebe@users.noreply.github.com>
2025-06-19 15:24:43 +09:00
Colin Ozanne 4cf126d38a
chore(ua-blocker): add changeset and format in `robots.json` sync PR (#1234)
* chore: add changeset and format in `robots.json` sync PR

* chore: remove changeset

* ci: add checks + change schedule
2025-06-18 14:10:56 +09:00
github-actions[bot] 80481dd5ed
Version Packages (#1237)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-18 07:13:12 +09:00
Aditya Mathur 1baa0b281d
feat: added @hono/mcp package (#1178)
* feat: added hono/mcp package

* chore: changeset

* chore: minor changes

* chore: minor changes

* chore: formated the code

* chore: minor correction

* chore: removed changelog file

* chore: changed the class name

* chore: updated the readme

* fix: closed the stream

* chore: minor change

* fix: added an interval to keep the connection alive and merge 2 streams into a single one

* chore: updated lockfile

* fix: stupid mistake 😅

* chore: formatted the README.md file

* chore: minor change

* chore: minor changes

* chore: minor change

* add an explicit return type annotation

* format the code

---------

Co-authored-by: Yusuke Wada <yusuke@kamawada.com>
2025-06-18 07:07:30 +09:00
github-actions[bot] 5cba9a4819
Version Packages (#1233)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-16 11:29:29 +09:00
github-actions[bot] acff470c7f
Version Packages (#1232)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-16 11:24:22 +09:00
Jonathan Haines 9235709060
refactor: composite build (#1230)
* refactor: composite build

* chore(ua-blocker): move demo.ts out of src
2025-06-16 11:23:47 +09:00
github-actions[bot] 40f916f944
chore(ua-blocker): update robots.json from upstream (#1229)
* chore(ua-blocker): update robots.json from upstream

* add changeset

* format

---------

Co-authored-by: yusukebe <10682+yusukebe@users.noreply.github.com>
Co-authored-by: Yusuke Wada <yusuke@kamawada.com>
2025-06-16 11:00:08 +09:00
Max Gerber a2409d2314
chore: Prettier should operate on all files (#1222)
* chore: Prettier should operate on all files

* Update package.json

Co-authored-by: Jonathan Haines <jonno.haines@gmail.com>

---------

Co-authored-by: Jonathan Haines <jonno.haines@gmail.com>
2025-06-15 09:08:01 +09:00
github-actions[bot] cf3c17e3d1
Version Packages (#1228)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-14 06:08:40 +09:00
Yusuke Wada 7c6a860814
chore: update the lockfile (#1226) 2025-06-14 06:05:24 +09:00
Colin Ozanne be73703184
feat: introduce UA Blocker Middleware (#1181)
* feat: create AI bot blocker middleware

* chore: add changeset

* fix: uppercase function called too late

* chore: don't version-control robots.json

* chore: track `robots.json`

* ci: add `@hono/ai-robots-txt` workflow script

* fix: change initial version

* feat: add automatic `robots.json` sync from upstream

* feat!: change package name and architecture

* refactor(ua-blocker): prebuild compiled regex (#1)

* fix: add json data files to tsconfig

* chore: rename workflow files

* fix: test if string _contains_ "Yes"

It might be a markdown link, so not the exact string, but "[Yes](<link>)"

* fix: tests reflect the fixed "Yes" check

* feat: move generator back to prebuild

generated regex should use the version-controled robots.json, not
directly the upstream file

* chore: add .zed

* chore: remove unused files

* fix: properly setup workspace before running scripts

* chore: remove `prebuild` script from `build`, `typecheck`, and `test`

* chore: run `getrobotstxt` and `prebuild`

* fix: export `RegExp`s, not `string[]`s

* chore: mention RegExp and uppercase matching in docs

* fix: adapt tests to regex exports

* chore: add tests for direct regex passing

* chore: format code

---------

Co-authored-by: Jonathan Haines <jonno.haines@gmail.com>
2025-06-14 06:00:42 +09:00
202 changed files with 6016 additions and 1147 deletions

View File

@ -0,0 +1,73 @@
name: Sync robots.json
on:
schedule:
# Runs every day at midnight
- cron: '15 0 */3 * *'
workflow_dispatch:
jobs:
sync-and-pr:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Node.js and Yarn
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'yarn'
- run: yarn workspaces focus hono-middleware @hono/ua-blocker
- name: Fetch latest robots.json
run: yarn workspace @hono/ua-blocker getrobotstxt
- name: Generate data
run: yarn workspace @hono/ua-blocker prebuild
- name: Format
run: yarn prettier --write . !packages packages/ua-blocker
- name: Check for changes
id: changes
run: |
if [[ -n $(git status --porcelain) ]]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
else
echo "has_changes=false" >> $GITHUB_OUTPUT
fi
- name: Generate changeset
if: steps.changes.outputs.has_changes == 'true'
run: |
# Use a static changeset filename to avoid duplicates
CHANGESET_FILE=".changeset/auto-sync-robots.md"
# Create the changeset file
cat << EOF > "$CHANGESET_FILE"
---
'@hono/ua-blocker': patch
---
chore(ua-blocker): sync \`robots.json\` with upstream
EOF
- name: Create Pull Request if changes exist
if: steps.changes.outputs.has_changes == 'true'
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore(ua-blocker): update robots.json from upstream'
title: 'chore(ua-blocker): update robots.json from upstream'
body: 'This PR was automatically created after detecting changes in the upstream `robots.json` file.'
branch: 'chore/sync-robots-json'
delete-branch: true
# Assignee and labels
assignees: finxol
reviewers: finxol
labels: robots.json

View File

@ -41,8 +41,7 @@ jobs:
with: with:
node-version: 20.x node-version: 20.x
- run: yarn workspaces focus hono-middleware @hono/${{ matrix.package }} - run: yarn workspaces focus hono-middleware @hono/${{ matrix.package }}
- run: yarn workspaces foreach --topological --recursive --from @hono/${{ matrix.package }} run build - run: yarn workspaces foreach --topological --recursive --from @hono/${{ matrix.package }} run publint
- run: yarn workspace @hono/${{ matrix.package }} publint
- run: yarn workspace @hono/${{ matrix.package }} typecheck - run: yarn workspace @hono/${{ matrix.package }} typecheck
- run: yarn eslint packages/${{ matrix.package }} - run: yarn eslint packages/${{ matrix.package }}
- run: yarn prettier --check . !packages packages/${{ matrix.package }} - run: yarn prettier --check . !packages packages/${{ matrix.package }}

34
.github/workflows/cr.yml vendored 100644
View File

@ -0,0 +1,34 @@
name: cr
on:
push:
branches: [main]
tags: ['!**'] # Avoid publishing on tags
pull_request:
types: [opened, synchronize, labeled] # Run on PR creation, updates, and when labels are added
concurrency:
group: ${{ github.workflow }}-${{ github.event.number }} # Concurrency group for each PR
cancel-in-progress: true # Cancel in progress builds for the same PR
jobs:
publish:
if: github.repository == 'honojs/middleware' && (github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'cr-tracked'))
runs-on: ubuntu-latest
name: 'Publish: pkg.pr.new'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20.x
- name: Install Dependencies
run: yarn
- name: Build
run: yarn workspaces foreach --all --topological --parallel --no-private run build
- name: Publish to StackBlitz
run: yarn pkg-pr-new publish --compact --no-template './packages/*'

3
.gitignore vendored
View File

@ -20,3 +20,6 @@ sandbox
# Claude Code local files # Claude Code local files
CLAUDE.local.md CLAUDE.local.md
settings.local.json settings.local.json
# Code editor
.zed

View File

@ -1,3 +1,9 @@
.changeset .changeset
.vscode .vscode
.yarn .yarn
# Casbin
*.conf
*.csv
**/generated.ts

View File

@ -8,8 +8,8 @@
] ]
}, },
"scripts": { "scripts": {
"build": "yarn workspaces foreach --all --topological --verbose run build", "build": "yarn workspaces foreach --all --topological --parallel --verbose run build",
"publint": "yarn workspaces foreach --all --topological --verbose run publint", "publint": "yarn workspaces foreach --all --topological --parallel --verbose run publint",
"publish": "yarn workspaces foreach --all --no-private --topological --verbose npm publish --tolerate-republish && changeset tag", "publish": "yarn workspaces foreach --all --no-private --topological --verbose npm publish --tolerate-republish && changeset tag",
"typecheck": "yarn tsc --build", "typecheck": "yarn tsc --build",
"typecheck:clean": "yarn tsc --build --clean", "typecheck:clean": "yarn tsc --build --clean",
@ -17,8 +17,8 @@
"test": "vitest", "test": "vitest",
"lint": "eslint 'packages/**/*.{ts,tsx}'", "lint": "eslint 'packages/**/*.{ts,tsx}'",
"lint:fix": "eslint --fix 'packages/**/*.{ts,tsx}'", "lint:fix": "eslint --fix 'packages/**/*.{ts,tsx}'",
"format": "prettier --check 'packages/**/*.{ts,tsx}'", "format": "prettier --check .",
"format:fix": "prettier --write 'packages/**/*.{ts,tsx}'" "format:fix": "prettier --write ."
}, },
"license": "MIT", "license": "MIT",
"private": true, "private": true,
@ -29,20 +29,21 @@
"devDependencies": { "devDependencies": {
"@changesets/changelog-github": "^0.4.8", "@changesets/changelog-github": "^0.4.8",
"@changesets/cli": "^2.26.0", "@changesets/cli": "^2.26.0",
"@cloudflare/vitest-pool-workers": "^0.7.8", "@cloudflare/vitest-pool-workers": "^0.8.38",
"@cloudflare/workers-types": "^4.20230307.0", "@cloudflare/workers-types": "^4.20250612.0",
"@hono/eslint-config": "workspace:*", "@hono/eslint-config": "workspace:*",
"@ryoppippi/unplugin-typia": "^1.2.0", "@ryoppippi/unplugin-typia": "^1.2.0",
"@types/bun": "^1.0.0", "@types/bun": "^1.0.0",
"@types/node": "^20.17.28", "@types/node": "^20.17.28",
"@types/ws": "^8.18.0", "@types/ws": "^8.18.0",
"@vitest/coverage-istanbul": "^3.0.8", "@vitest/coverage-istanbul": "^3.2.4",
"eslint": "^9.23.0", "eslint": "^9.23.0",
"hono": "^4.7.11", "hono": "^4.7.11",
"pkg-pr-new": "^0.0.51",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.2.4"
}, },
"packageManager": "yarn@4.9.2" "packageManager": "yarn@4.9.2"
} }

View File

@ -48,6 +48,6 @@
"publint": "^0.3.9", "publint": "^0.3.9",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.2.4"
} }
} }

View File

@ -1,13 +1,5 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {},
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo",
"emitDeclarationOnly": false,
"isolatedDeclarations": true
},
"include": ["src/**/*.ts"],
"exclude": ["**/*.test.ts"],
"references": [] "references": []
} }

View File

@ -1,5 +1,4 @@
{ {
"extends": "../../tsconfig.base.json",
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [

View File

@ -1,13 +1,8 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist/out-tsc/packages/ajv-validator", "outDir": "../../dist/packages/ajv-validator",
"types": ["vitest/globals"] "types": ["vitest/globals"]
}, },
"include": ["**/*.test.ts", "vitest.config.ts"], "references": []
"references": [
{
"path": "./tsconfig.build.json"
}
]
} }

View File

@ -49,6 +49,6 @@
"publint": "^0.3.9", "publint": "^0.3.9",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.2.4"
} }
} }

View File

@ -1,13 +1,5 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {},
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo",
"emitDeclarationOnly": false,
"isolatedDeclarations": true
},
"include": ["src/**/*.ts"],
"exclude": ["**/*.test.ts"],
"references": [] "references": []
} }

View File

@ -1,5 +1,4 @@
{ {
"extends": "../../tsconfig.base.json",
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [

View File

@ -1,13 +1,8 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist/out-tsc/packages/arktype-validator", "outDir": "../../dist/packages/arktype-validator",
"types": ["vitest/globals"] "types": ["vitest/globals"]
}, },
"include": ["**/*.test.ts", "vitest.config.ts"], "references": []
"references": [
{
"path": "./tsconfig.build.json"
}
]
} }

View File

@ -67,7 +67,7 @@
"react": "^18.2.0", "react": "^18.2.0",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.2.4"
}, },
"engines": { "engines": {
"node": ">=18.4.0" "node": ">=18.4.0"

View File

@ -1,14 +1,7 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo",
"emitDeclarationOnly": false,
"isolatedDeclarations": true,
"jsx": "react" "jsx": "react"
}, },
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["**/*.test.ts"],
"references": [] "references": []
} }

View File

@ -1,5 +1,4 @@
{ {
"extends": "../../tsconfig.base.json",
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [

View File

@ -1,14 +1,8 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist/out-tsc/packages/auth-js", "outDir": "../../dist/packages/auth-js",
"noEmit": true,
"jsx": "react" "jsx": "react"
}, },
"include": ["**/*.test.ts", "vitest.config.ts"], "references": []
"references": [
{
"path": "./tsconfig.build.json"
}
]
} }

View File

@ -48,6 +48,6 @@
"publint": "^0.3.9", "publint": "^0.3.9",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.2.4"
} }
} }

View File

@ -1,14 +1,7 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo",
"emitDeclarationOnly": false,
"isolatedDeclarations": true,
"types": ["node", "bun"] "types": ["node", "bun"]
}, },
"include": ["src/**/*.ts"],
"exclude": ["**/*.test.ts"],
"references": [] "references": []
} }

View File

@ -1,5 +1,4 @@
{ {
"extends": "../../tsconfig.base.json",
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [

View File

@ -1,13 +1,8 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist/out-tsc/packages/hello", "outDir": "../../dist/packages/hello",
"types": ["vitest/globals"] "types": ["vitest/globals"]
}, },
"include": ["**/*.test.ts", "vitest.config.ts"], "references": []
"references": [
{
"path": "./tsconfig.build.json"
}
]
} }

View File

@ -47,7 +47,7 @@
"publint": "^0.3.9", "publint": "^0.3.9",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.2.4"
}, },
"engines": { "engines": {
"node": ">=18.14.1" "node": ">=18.14.1"

View File

@ -1,13 +1,5 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {},
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo",
"emitDeclarationOnly": false,
"isolatedDeclarations": true
},
"include": ["src/**/*.ts"],
"exclude": ["**/*.test.ts"],
"references": [] "references": []
} }

View File

@ -1,5 +1,4 @@
{ {
"extends": "../../tsconfig.base.json",
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [

View File

@ -1,13 +1,8 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist/out-tsc/packages/bun-transpiler", "outDir": "../../dist/packages/bun-transpiler",
"types": ["vitest/globals"] "types": ["vitest/globals"]
}, },
"include": ["**/*.test.ts", "vitest.config.ts"], "references": []
"references": [
{
"path": "./tsconfig.build.json"
}
]
} }

View File

@ -59,6 +59,6 @@
"publint": "^0.3.9", "publint": "^0.3.9",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.2.4"
} }
} }

View File

@ -1,12 +1,5 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {},
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo",
"emitDeclarationOnly": false,
"isolatedDeclarations": true
},
"include": ["./src/**/*.ts"],
"references": [] "references": []
} }

View File

@ -1,5 +1,4 @@
{ {
"extends": "../../tsconfig.base.json",
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [

View File

@ -1,13 +1,9 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist/out-tsc/packages/casbin", "outDir": "../../dist/packages/casbin",
"types": ["vitest/globals"] "types": ["vitest/globals"]
}, },
"include": ["**/*.test.ts", "vitest.config.ts", "vitest.setup.ts"], "include": ["src", "vitest.config.ts", "vitest.setup.ts"],
"references": [ "references": []
{
"path": "./tsconfig.build.json"
}
]
} }

View File

@ -48,7 +48,7 @@
"publint": "^0.3.9", "publint": "^0.3.9",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.2.4"
}, },
"dependencies": { "dependencies": {
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",

View File

@ -1,13 +1,5 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {},
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo",
"emitDeclarationOnly": false,
"isolatedDeclarations": true
},
"include": ["src/**/*.ts"],
"exclude": ["**/*.test.ts"],
"references": [] "references": []
} }

View File

@ -1,5 +1,4 @@
{ {
"extends": "../../tsconfig.base.json",
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [

View File

@ -1,13 +1,8 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist/out-tsc/packages/class-validator", "outDir": "../../dist/packages/class-validator",
"types": ["vitest/globals"] "types": ["vitest/globals"]
}, },
"include": ["**/*.test.ts", "vitest.config.ts"], "references": []
"references": [
{
"path": "./tsconfig.build.json"
}
]
} }

View File

@ -1,5 +1,13 @@
# @hono/clerk-auth # @hono/clerk-auth
## 3.0.0
### Major Changes
- [#1190](https://github.com/honojs/middleware/pull/1190) [`6ca78a14dad67d7920a7c70bbfdb752eeaaff9ad`](https://github.com/honojs/middleware/commit/6ca78a14dad67d7920a7c70bbfdb752eeaaff9ad) Thanks [@wobsoriano](https://github.com/wobsoriano)! - Move `@clerk/backend` from peerDependencies to dependencies and bump to `2.x.x`.
This change ensures the package is directly available without requiring consumers to install it separately. The version bump includes the upcoming machine authentication feature while maintaining backward compatibility.
## 2.0.1 ## 2.0.1
### Patch Changes ### Patch Changes

View File

@ -9,7 +9,7 @@ This middleware can be used to inject the active Clerk session into the request
## Installation ## Installation
```plain ```plain
npm i hono @hono/clerk-auth @clerk/backend npm i hono @hono/clerk-auth
``` ```
## Configuration ## Configuration

View File

@ -1,6 +1,6 @@
{ {
"name": "@hono/clerk-auth", "name": "@hono/clerk-auth",
"version": "2.0.1", "version": "3.0.0",
"description": "A third-party Clerk auth middleware for Hono", "description": "A third-party Clerk auth middleware for Hono",
"type": "module", "type": "module",
"main": "dist/index.cjs", "main": "dist/index.cjs",
@ -39,19 +39,21 @@
"directory": "packages/clerk-auth" "directory": "packages/clerk-auth"
}, },
"homepage": "https://github.com/honojs/middleware", "homepage": "https://github.com/honojs/middleware",
"dependencies": {
"@clerk/backend": "^2.1.0",
"@clerk/types": "^4.60.1"
},
"peerDependencies": { "peerDependencies": {
"@clerk/backend": "^1.0.0",
"hono": ">=3.*" "hono": ">=3.*"
}, },
"devDependencies": { "devDependencies": {
"@arethetypeswrong/cli": "^0.17.4", "@arethetypeswrong/cli": "^0.17.4",
"@clerk/backend": "^1.0.0",
"@types/react": "^18", "@types/react": "^18",
"publint": "^0.3.9", "publint": "^0.3.9",
"react": "^18.2.0", "react": "^18.2.0",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.2.4"
}, },
"engines": { "engines": {
"node": ">=16.x.x" "node": ">=16.x.x"

View File

@ -1,19 +1,20 @@
import { createClerkClient } from '@clerk/backend' import { createClerkClient } from '@clerk/backend'
import type { ClerkClient, ClerkOptions } from '@clerk/backend' import type { ClerkClient, SessionAuthObject } from '@clerk/backend'
import type { AuthenticateRequestOptions } from '@clerk/backend/internal'
import { TokenType } from '@clerk/backend/internal'
import type { Context, MiddlewareHandler } from 'hono' import type { Context, MiddlewareHandler } from 'hono'
import { env } from 'hono/adapter' import { env } from 'hono/adapter'
type ClerkAuth = ReturnType<Awaited<ReturnType<ClerkClient['authenticateRequest']>>['toAuth']>
declare module 'hono' { declare module 'hono' {
interface ContextVariableMap { interface ContextVariableMap {
clerk: ClerkClient clerk: ClerkClient
clerkAuth: ClerkAuth clerkAuth: () => SessionAuthObject | null
} }
} }
export const getAuth = (c: Context): ClerkAuth => { export const getAuth = (c: Context): SessionAuthObject | null => {
return c.get('clerkAuth') const authFn = c.get('clerkAuth')
return authFn()
} }
type ClerkEnv = { type ClerkEnv = {
@ -23,7 +24,9 @@ type ClerkEnv = {
CLERK_API_VERSION: string CLERK_API_VERSION: string
} }
export const clerkMiddleware = (options?: ClerkOptions): MiddlewareHandler => { type ClerkMiddlewareOptions = Omit<AuthenticateRequestOptions, 'acceptsToken'>
export const clerkMiddleware = (options?: ClerkMiddlewareOptions): MiddlewareHandler => {
return async (c, next) => { return async (c, next) => {
const clerkEnv = env<ClerkEnv>(c) const clerkEnv = env<ClerkEnv>(c)
const { secretKey, publishableKey, apiUrl, apiVersion, ...rest } = options || { const { secretKey, publishableKey, apiUrl, apiVersion, ...rest } = options || {
@ -52,6 +55,7 @@ export const clerkMiddleware = (options?: ClerkOptions): MiddlewareHandler => {
...rest, ...rest,
secretKey, secretKey,
publishableKey, publishableKey,
acceptsToken: TokenType.SessionToken,
}) })
if (requestState.headers) { if (requestState.headers) {
@ -66,7 +70,8 @@ export const clerkMiddleware = (options?: ClerkOptions): MiddlewareHandler => {
} }
} }
c.set('clerkAuth', requestState.toAuth()) // Options will be added soon
c.set('clerkAuth', () => requestState.toAuth())
c.set('clerk', clerkClient) c.set('clerk', clerkClient)
await next() await next()

View File

@ -1,13 +1,5 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {},
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo",
"emitDeclarationOnly": false,
"isolatedDeclarations": true
},
"include": ["src/**/*.ts"],
"exclude": ["**/*.test.ts"],
"references": [] "references": []
} }

View File

@ -1,5 +1,4 @@
{ {
"extends": "../../tsconfig.base.json",
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [

View File

@ -1,13 +1,8 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist/out-tsc/packages/clerk-auth", "outDir": "../../dist/packages/clerk-auth",
"types": ["vitest/globals"] "types": ["vitest/globals"]
}, },
"include": ["**/*.test.ts", "vitest.config.ts"], "references": []
"references": [
{
"path": "./tsconfig.build.json"
}
]
} }

View File

@ -46,6 +46,6 @@
"publint": "^0.3.9", "publint": "^0.3.9",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.2.4"
} }
} }

View File

@ -1,14 +1,7 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo",
"emitDeclarationOnly": false,
"isolatedDeclarations": true,
"types": ["@cloudflare/workers-types"] "types": ["@cloudflare/workers-types"]
}, },
"include": ["src/**/*.ts"],
"exclude": ["**/*.test.ts"],
"references": [] "references": []
} }

View File

@ -1,5 +1,4 @@
{ {
"extends": "../../tsconfig.base.json",
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [

View File

@ -1,13 +1,8 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist/out-tsc/packages/cloudflare-access", "outDir": "../../dist/packages/cloudflare-access",
"types": ["vitest/globals"] "types": ["@cloudflare/workers-types", "vitest/globals"]
}, },
"include": ["**/*.test.ts", "vitest.config.ts"], "references": []
"references": [
{
"path": "./tsconfig.build.json"
}
]
} }

View File

@ -53,7 +53,7 @@
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"valibot": "^0.36.0", "valibot": "^0.36.0",
"vitest": "^3.0.8", "vitest": "^3.2.4",
"yup": "^1.4.0", "yup": "^1.4.0",
"zod": "^3.23.8" "zod": "^3.23.8"
} }

View File

@ -1,13 +1,5 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {},
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo",
"emitDeclarationOnly": false,
"isolatedDeclarations": true
},
"include": ["src/**/*.ts"],
"exclude": ["**/*.test.ts"],
"references": [] "references": []
} }

View File

@ -1,5 +1,4 @@
{ {
"extends": "../../tsconfig.base.json",
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [

View File

@ -1,13 +1,8 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist/out-tsc/packages/conform-validator", "outDir": "../../dist/packages/conform-validator",
"types": ["vitest/globals"] "types": ["vitest/globals"]
}, },
"include": ["**/*.test.ts", "vitest.config.ts"], "references": []
"references": [
{
"path": "./tsconfig.build.json"
}
]
} }

View File

@ -49,6 +49,6 @@
"publint": "^0.3.9", "publint": "^0.3.9",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.2.4"
} }
} }

View File

@ -1,13 +1,5 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {},
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo",
"emitDeclarationOnly": false,
"isolatedDeclarations": true
},
"include": ["src/**/*.ts"],
"exclude": ["**/*.test.ts"],
"references": [] "references": []
} }

View File

@ -1,5 +1,4 @@
{ {
"extends": "../../tsconfig.base.json",
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [

View File

@ -1,13 +1,8 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist/out-tsc/packages/effect-validator", "outDir": "../../dist/packages/effect-validator",
"types": ["vitest/globals"] "types": ["vitest/globals"]
}, },
"include": ["**/*.test.ts", "vitest.config.ts"], "references": []
"references": [
{
"path": "./tsconfig.build.json"
}
]
} }

View File

@ -78,7 +78,7 @@
"publint": "^0.3.9", "publint": "^0.3.9",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.2.4"
}, },
"engines": { "engines": {
"node": ">=18.14.1" "node": ">=18.14.1"

View File

@ -1,13 +1,5 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {},
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo",
"emitDeclarationOnly": false,
"isolatedDeclarations": true
},
"include": ["src/**/*.ts"],
"exclude": ["**/*.test.ts"],
"references": [] "references": []
} }

View File

@ -1,5 +1,4 @@
{ {
"extends": "../../tsconfig.base.json",
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [

View File

@ -1,13 +1,8 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist/out-tsc/packages/esbuild-transpiler", "outDir": "../../dist/packages/esbuild-transpiler",
"types": ["vitest/globals"] "types": ["vitest/globals"]
}, },
"include": ["**/*.test.ts", "vitest.config.ts"], "references": []
"references": [
{
"path": "./tsconfig.build.json"
}
]
} }

View File

@ -48,7 +48,7 @@
"publint": "^0.3.9", "publint": "^0.3.9",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.2.4"
}, },
"engines": { "engines": {
"node": ">=16.0.0" "node": ">=16.0.0"

View File

@ -1,13 +1,5 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {},
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo",
"emitDeclarationOnly": false,
"isolatedDeclarations": true
},
"include": ["src/**/*.ts"],
"exclude": ["**/*.test.ts"],
"references": [] "references": []
} }

View File

@ -1,5 +1,4 @@
{ {
"extends": "../../tsconfig.base.json",
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [

View File

@ -1,13 +1,8 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist/out-tsc/packages/event-emitter", "outDir": "../../dist/packages/event-emitter",
"types": ["vitest/globals"] "types": ["vitest/globals"]
}, },
"include": ["**/*.test.ts", "vitest.config.ts"], "references": []
"references": [
{
"path": "./tsconfig.build.json"
}
]
} }

View File

@ -55,6 +55,6 @@
"publint": "^0.3.9", "publint": "^0.3.9",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.2.4"
} }
} }

View File

@ -1,7 +1,6 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {
"outDir": "dist",
"types": ["@cloudflare/workers-types"] "types": ["@cloudflare/workers-types"]
}, },
"references": [] "references": []

View File

@ -1,5 +1,4 @@
{ {
"extends": "../../tsconfig.base.json",
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [

View File

@ -1,13 +1,9 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist/out-tsc/packages/firebase-auth", "outDir": "../../dist/packages/firebase-auth",
"noEmit": true "types": ["@cloudflare/workers-types", "vitest/globals"]
}, },
"include": ["**/*.test.ts", "vitest.config.ts"], "include": ["src", "firebase-tools.d.ts", "vitest.config.ts"],
"references": [ "references": []
{
"path": "./tsconfig.build.json"
}
]
} }

View File

@ -45,7 +45,7 @@
"publint": "^0.3.9", "publint": "^0.3.9",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.2.4"
}, },
"engines": { "engines": {
"node": ">=16.0.0" "node": ">=16.0.0"

View File

@ -1,13 +1,5 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {},
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo",
"emitDeclarationOnly": false,
"isolatedDeclarations": true
},
"include": ["src/**/*.ts"],
"exclude": ["**/*.test.ts"],
"references": [] "references": []
} }

View File

@ -1,14 +1,9 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist/out-tsc/packages/graphql-server", "outDir": "../../dist/packages/graphql-server",
"types": ["bun"] "types": ["bun"]
}, },
"exclude": ["src/**/*.test.ts"], "include": ["src", "bun_test"],
"include": ["bun_test", "vitest.config.ts"], "exclude": ["node_modules", "src/**/*.test.ts"]
"references": [
{
"path": "./tsconfig.build.json"
}
]
} }

View File

@ -1,5 +1,4 @@
{ {
"extends": "../../tsconfig.base.json",
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [

View File

@ -1,14 +1,9 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist/out-tsc/packages/graphql-server", "outDir": "../../dist/packages/graphql-server",
"types": ["vitest/globals"] "types": ["vitest/globals"]
}, },
"exclude": ["bun_test"], "exclude": ["node_modules", "bun_test"],
"include": ["**/*.test.ts", "vitest.config.ts"], "references": []
"references": [
{
"path": "./tsconfig.build.json"
}
]
} }

View File

@ -46,6 +46,6 @@
"publint": "^0.3.9", "publint": "^0.3.9",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.2.4"
} }
} }

View File

@ -1,13 +1,5 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {},
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo",
"emitDeclarationOnly": false,
"isolatedDeclarations": true
},
"include": ["src/**/*.ts"],
"exclude": ["**/*.test.ts"],
"references": [] "references": []
} }

View File

@ -1,5 +1,4 @@
{ {
"extends": "../../tsconfig.base.json",
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [

View File

@ -1,13 +1,8 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist/out-tsc/packages/hello", "outDir": "../../dist/packages/hello",
"types": ["vitest/globals"] "types": ["vitest/globals"]
}, },
"include": ["**/*.test.ts", "vitest.config.ts"], "references": []
"references": [
{
"path": "./tsconfig.build.json"
}
]
} }

View File

@ -0,0 +1,7 @@
# @hono/mcp
## 0.1.0
### Minor Changes
- [#1178](https://github.com/honojs/middleware/pull/1178) [`1baa0b281dd4170f0003f9353a5f4c33fdcca610`](https://github.com/honojs/middleware/commit/1baa0b281dd4170f0003f9353a5f4c33fdcca610) Thanks [@MathurAditya724](https://github.com/MathurAditya724)! - init release

View File

@ -0,0 +1,35 @@
# Hono MCP (Model Context Protocol)
Connect Hono with a Model Context Protocol (MCP) server over HTTP Streaming Transport.
## Usage
```ts
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StreamableHTTPTransport } from '@hono/mcp'
import { Hono } from 'hono'
const app = new Hono()
// Your MCP server implementation
const mcpServer = new McpServer({
name: 'my-mcp-server',
version: '1.0.0',
})
app.all('/mcp', async (c) => {
const transport = new StreamableHTTPTransport()
await mcpServer.connect(transport)
return transport.handleRequest(c)
})
export default app
```
## Author
Aditya Mathur <https://github.com/mathuraditya724>
## License
MIT

View File

@ -0,0 +1,52 @@
{
"name": "@hono/mcp",
"version": "0.1.0",
"description": "MCP Middleware for Hono",
"type": "module",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "tsup ./src/index.ts",
"prepack": "yarn build",
"publint": "attw --pack && publint",
"typecheck": "tsc -b tsconfig.json",
"test": "vitest"
},
"exports": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
},
"license": "MIT",
"publishConfig": {
"registry": "https://registry.npmjs.org",
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/honojs/middleware.git",
"directory": "packages/mcp"
},
"homepage": "https://github.com/honojs/middleware",
"peerDependencies": {
"@modelcontextprotocol/sdk": "^1.12.0",
"hono": "*"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.17.4",
"@modelcontextprotocol/sdk": "^1.12.0",
"publint": "^0.3.9",
"tsup": "^8.4.0",
"typescript": "^5.8.2",
"vitest": "^3.2.4",
"zod": "^3.25.34"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,613 @@
/**
* @module
* MCP HTTP Streaming Helper for Hono.
*/
import type { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js'
import type {
EventStore,
StreamableHTTPServerTransportOptions,
} from '@modelcontextprotocol/sdk/server/streamableHttp.js'
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'
import {
isInitializeRequest,
isJSONRPCError,
isJSONRPCRequest,
isJSONRPCResponse,
JSONRPCMessageSchema,
} from '@modelcontextprotocol/sdk/types.js'
import type { JSONRPCMessage, RequestId } from '@modelcontextprotocol/sdk/types.js'
import type { Context } from 'hono'
import { HTTPException } from 'hono/http-exception'
import type { SSEStreamingApi } from 'hono/streaming'
import { streamSSE } from './streaming'
export class StreamableHTTPTransport implements Transport {
#started = false
#initialized = false
#onsessioninitialized?: (sessionId: string) => void
#sessionIdGenerator?: () => string
#eventStore?: EventStore
#enableJsonResponse = false
#standaloneSseStreamId = '_GET_stream'
#streamMapping = new Map<
string,
{
ctx: {
header: (name: string, value: string) => void
json: (data: unknown) => void
}
stream?: SSEStreamingApi
}
>()
#requestToStreamMapping = new Map<RequestId, string>()
#requestResponseMap = new Map<RequestId, JSONRPCMessage>()
sessionId?: string | undefined
onclose?: () => void
onerror?: (error: Error) => void
onmessage?: (message: JSONRPCMessage, extra?: { authInfo?: AuthInfo }) => void
constructor(options?: StreamableHTTPServerTransportOptions) {
this.#sessionIdGenerator = options?.sessionIdGenerator
this.#enableJsonResponse = options?.enableJsonResponse ?? false
this.#eventStore = options?.eventStore
this.#onsessioninitialized = options?.onsessioninitialized
}
/**
* Starts the transport. This is required by the Transport interface but is a no-op
* for the Streamable HTTP transport as connections are managed per-request.
*/
async start(): Promise<void> {
if (this.#started) {
throw new Error('Transport already started')
}
this.#started = true
}
/**
* Handles an incoming HTTP request, whether GET or POST
*/
async handleRequest(ctx: Context, parsedBody?: unknown): Promise<Response | undefined> {
switch (ctx.req.method) {
case 'GET':
return this.handleGetRequest(ctx)
case 'POST':
return this.handlePostRequest(ctx, parsedBody)
case 'DELETE':
return this.handleDeleteRequest(ctx)
default:
return this.handleUnsupportedRequest(ctx)
}
}
/**
* Handles GET requests for SSE stream
*/
private async handleGetRequest(ctx: Context) {
try {
// The client MUST include an Accept header, listing text/event-stream as a supported content type.
const acceptHeader = ctx.req.header('Accept')
if (!acceptHeader?.includes('text/event-stream')) {
throw new HTTPException(406, {
res: Response.json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Not Acceptable: Client must accept text/event-stream',
},
id: null,
}),
})
}
// If an Mcp-Session-Id is returned by the server during initialization,
// clients using the Streamable HTTP transport MUST include it
// in the Mcp-Session-Id header on all of their subsequent HTTP requests.
this.validateSession(ctx)
// After initialization, always include the session ID if we have one
if (this.sessionId !== undefined) {
ctx.header('mcp-session-id', this.sessionId)
}
let streamId: string | ((stream: SSEStreamingApi) => Promise<string>) =
this.#standaloneSseStreamId
// Handle resumability: check for Last-Event-ID header
if (this.#eventStore) {
const lastEventId = ctx.req.header('last-event-id')
if (lastEventId) {
streamId = (stream) =>
this.#eventStore!.replayEventsAfter(lastEventId, {
send: async (eventId: string, message: JSONRPCMessage) => {
try {
await stream.writeSSE({
id: eventId,
event: 'message',
data: JSON.stringify(message),
})
} catch {
this.onerror?.(new Error('Failed replay events'))
throw new HTTPException(500, {
message: 'Failed replay events',
})
}
},
})
}
}
// Check if there's already an active standalone SSE stream for this session
if (typeof streamId === 'string' && this.#streamMapping.get(streamId) !== undefined) {
// Only one GET SSE stream is allowed per session
throw new HTTPException(409, {
res: Response.json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Conflict: Only one SSE stream is allowed per session',
},
id: null,
}),
})
}
return streamSSE(ctx, async (stream) => {
const resolvedStreamId = typeof streamId === 'string' ? streamId : await streamId(stream)
// Assign the response to the standalone SSE stream
this.#streamMapping.set(resolvedStreamId, {
ctx,
stream,
})
// Keep connection alive
const keepAlive = setInterval(() => {
if (!stream.closed) {
stream.writeSSE({ data: '', event: 'ping' }).catch(() => clearInterval(keepAlive))
}
}, 30000)
// Set up close handler for client disconnects
stream.onAbort(() => {
this.#streamMapping.delete(resolvedStreamId)
clearInterval(keepAlive)
})
})
} catch (error) {
if (error instanceof HTTPException) {
throw error
}
this.onerror?.(error as Error)
// return JSON-RPC formatted error
throw new HTTPException(400, {
res: Response.json({
jsonrpc: '2.0',
error: {
code: -32700,
message: 'Parse error',
data: String(error),
},
id: null,
}),
})
}
}
/**
* Handles POST requests containing JSON-RPC messages
*/
private async handlePostRequest(ctx: Context, parsedBody?: unknown) {
try {
// Validate the Accept header
const acceptHeader = ctx.req.header('Accept')
// The client MUST include an Accept header, listing both application/json and text/event-stream as supported content types.
if (
!acceptHeader?.includes('application/json') ||
!acceptHeader.includes('text/event-stream')
) {
throw new HTTPException(406, {
res: Response.json({
jsonrpc: '2.0',
error: {
code: -32000,
message:
'Not Acceptable: Client must accept both application/json and text/event-stream',
},
id: null,
}),
})
}
const ct = ctx.req.header('Content-Type')
if (!ct?.includes('application/json')) {
throw new HTTPException(415, {
res: Response.json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Unsupported Media Type: Content-Type must be application/json',
},
id: null,
}),
})
}
const authInfo: AuthInfo | undefined = ctx.get('auth')
let rawMessage = parsedBody
if (rawMessage === undefined) {
rawMessage = await ctx.req.json()
}
let messages: JSONRPCMessage[]
// handle batch and single messages
if (Array.isArray(rawMessage)) {
messages = rawMessage.map((msg) => JSONRPCMessageSchema.parse(msg))
} else {
messages = [JSONRPCMessageSchema.parse(rawMessage)]
}
// Check if this is an initialization request
// https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle/
const isInitializationRequest = messages.some(isInitializeRequest)
if (isInitializationRequest) {
// If it's a server with session management and the session ID is already set we should reject the request
// to avoid re-initialization.
if (this.#initialized && this.sessionId !== undefined) {
throw new HTTPException(400, {
res: Response.json({
jsonrpc: '2.0',
error: {
code: -32600,
message: 'Invalid Request: Server already initialized',
},
id: null,
}),
})
}
if (messages.length > 1) {
throw new HTTPException(400, {
res: Response.json({
jsonrpc: '2.0',
error: {
code: -32600,
message: 'Invalid Request: Only one initialization request is allowed',
},
id: null,
}),
})
}
this.sessionId = this.#sessionIdGenerator?.()
this.#initialized = true
// If we have a session ID and an onsessioninitialized handler, call it immediately
// This is needed in cases where the server needs to keep track of multiple sessions
if (this.sessionId && this.#onsessioninitialized) {
this.#onsessioninitialized(this.sessionId)
}
}
// If an Mcp-Session-Id is returned by the server during initialization,
// clients using the Streamable HTTP transport MUST include it
// in the Mcp-Session-Id header on all of their subsequent HTTP requests.
if (!isInitializationRequest) {
this.validateSession(ctx)
}
// check if it contains requests
const hasRequests = messages.some(isJSONRPCRequest)
if (!hasRequests) {
// handle each message
for (const message of messages) {
this.onmessage?.(message, { authInfo })
}
// if it only contains notifications or responses, return 202
return ctx.body(null, 202)
}
if (hasRequests) {
// The default behavior is to use SSE streaming
// but in some cases server will return JSON responses
const streamId = crypto.randomUUID()
if (!this.#enableJsonResponse && this.sessionId !== undefined) {
ctx.header('mcp-session-id', this.sessionId)
}
if (this.#enableJsonResponse) {
// Store the response for this request to send messages back through this connection
// We need to track by request ID to maintain the connection
const result = await new Promise<any>((resolve) => {
for (const message of messages) {
if (isJSONRPCRequest(message)) {
this.#streamMapping.set(streamId, {
ctx: {
header: ctx.header,
json: resolve,
},
})
this.#requestToStreamMapping.set(message.id, streamId)
}
}
// handle each message
for (const message of messages) {
this.onmessage?.(message, { authInfo })
}
})
return ctx.json(result)
}
return streamSSE(ctx, async (stream) => {
// Store the response for this request to send messages back through this connection
// We need to track by request ID to maintain the connection
for (const message of messages) {
if (isJSONRPCRequest(message)) {
this.#streamMapping.set(streamId, {
ctx,
stream,
})
this.#requestToStreamMapping.set(message.id, streamId)
}
}
// Set up close handler for client disconnects
stream.onAbort(() => {
this.#streamMapping.delete(streamId)
})
// handle each message
for (const message of messages) {
this.onmessage?.(message, { authInfo })
}
// The server SHOULD NOT close the SSE stream before sending all JSON-RPC responses
// This will be handled by the send() method when responses are ready
})
}
} catch (error) {
if (error instanceof HTTPException) {
throw error
}
this.onerror?.(error as Error)
// return JSON-RPC formatted error
throw new HTTPException(400, {
res: Response.json({
jsonrpc: '2.0',
error: {
code: -32700,
message: 'Parse error',
data: String(error),
},
id: null,
}),
})
}
}
/**
* Handles DELETE requests to terminate sessions
*/
private async handleDeleteRequest(ctx: Context) {
this.validateSession(ctx)
await this.close()
return ctx.body(null, 200)
}
/**
* Handles unsupported requests (PUT, PATCH, etc.)
*/
private handleUnsupportedRequest(ctx: Context) {
return ctx.json(
{
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Method not allowed.',
},
id: null,
},
{
status: 405,
headers: {
Allow: 'GET, POST, DELETE',
},
}
)
}
/**
* Validates session ID for non-initialization requests
* Returns true if the session is valid, false otherwise
*/
private validateSession(ctx: Context): boolean {
if (this.#sessionIdGenerator === undefined) {
// If the sessionIdGenerator ID is not set, the session management is disabled
// and we don't need to validate the session ID
return true
}
if (!this.#initialized) {
// If the server has not been initialized yet, reject all requests
throw new HTTPException(400, {
res: Response.json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: Server not initialized',
},
id: null,
}),
})
}
const sessionId = ctx.req.header('mcp-session-id')
if (!sessionId) {
// Non-initialization requests without a session ID should return 400 Bad Request
throw new HTTPException(400, {
res: Response.json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: Mcp-Session-Id header is required',
},
id: null,
}),
})
}
if (Array.isArray(sessionId)) {
throw new HTTPException(400, {
res: Response.json({
jsonrpc: '2.0',
error: {
code: -32000,
message: 'Bad Request: Mcp-Session-Id header must be a single value',
},
id: null,
}),
})
}
if (sessionId !== this.sessionId) {
// Reject requests with invalid session ID with 404 Not Found
throw new HTTPException(404, {
res: Response.json({
jsonrpc: '2.0',
error: {
code: -32001,
message: 'Session not found',
},
id: null,
}),
})
}
return true
}
async close(): Promise<void> {
// Close all SSE connections
for (const { stream } of this.#streamMapping.values()) {
stream?.close()
}
this.#streamMapping.clear()
// Clear any pending responses
this.#requestResponseMap.clear()
this.onclose?.()
}
async send(message: JSONRPCMessage, options?: { relatedRequestId?: RequestId }): Promise<void> {
let requestId = options?.relatedRequestId
if (isJSONRPCResponse(message) || isJSONRPCError(message)) {
// If the message is a response, use the request ID from the message
requestId = message.id
}
// Check if this message should be sent on the standalone SSE stream (no request ID)
// Ignore notifications from tools (which have relatedRequestId set)
// Those will be sent via dedicated response SSE streams
if (requestId === undefined) {
// For standalone SSE streams, we can only send requests and notifications
if (isJSONRPCResponse(message) || isJSONRPCError(message)) {
throw new Error(
'Cannot send a response on a standalone SSE stream unless resuming a previous client request'
)
}
const standaloneSse = this.#streamMapping.get(this.#standaloneSseStreamId)
if (standaloneSse === undefined) {
// The spec says the server MAY send messages on the stream, so it's ok to discard if no stream
return
}
// Generate and store event ID if event store is provided
let eventId: string | undefined
if (this.#eventStore) {
// Stores the event and gets the generated event ID
eventId = await this.#eventStore.storeEvent(this.#standaloneSseStreamId, message)
}
// Send the message to the standalone SSE stream
return standaloneSse.stream?.writeSSE({
id: eventId,
event: 'message',
data: JSON.stringify(message),
})
}
// Get the response for this request
const streamId = this.#requestToStreamMapping.get(requestId)
const response = this.#streamMapping.get(streamId!)
if (!streamId) {
throw new Error(`No connection established for request ID: ${String(requestId)}`)
}
if (!this.#enableJsonResponse) {
// For SSE responses, generate event ID if event store is provided
let eventId: string | undefined
if (this.#eventStore) {
eventId = await this.#eventStore.storeEvent(streamId, message)
}
if (response) {
// Write the event to the response stream
await response.stream?.writeSSE({
id: eventId,
event: 'message',
data: JSON.stringify(message),
})
}
}
if (isJSONRPCResponse(message) || isJSONRPCError(message)) {
this.#requestResponseMap.set(requestId, message)
const relatedIds = Array.from(this.#requestToStreamMapping.entries())
.filter(([, streamId]) => this.#streamMapping.get(streamId) === response)
.map(([id]) => id)
// Check if we have responses for all requests using this connection
const allResponsesReady = relatedIds.every((id) => this.#requestResponseMap.has(id))
if (allResponsesReady) {
if (!response) {
throw new Error(`No connection established for request ID: ${String(requestId)}`)
}
if (this.#enableJsonResponse) {
// All responses ready, send as JSON
if (this.sessionId !== undefined) {
response.ctx.header('mcp-session-id', this.sessionId)
}
const responses = relatedIds.map((id) => this.#requestResponseMap.get(id)!)
response.ctx.json(responses.length === 1 ? responses[0] : responses)
return
} else {
response.stream?.close()
}
// Clean up
for (const id of relatedIds) {
this.#requestResponseMap.delete(id)
this.#requestToStreamMapping.delete(id)
}
}
}
}
}

View File

@ -0,0 +1,67 @@
import type { Context } from 'hono'
import { SSEStreamingApi } from 'hono/streaming'
let isOldBunVersion = (): boolean => {
// @ts-expect-error @types/bun is not installed
const version: string = typeof Bun !== 'undefined' ? Bun.version : undefined
if (version === undefined) {
return false
}
const result = version.startsWith('1.1') || version.startsWith('1.0') || version.startsWith('0.')
// Avoid running this check on every call
isOldBunVersion = () => result
return result
}
const run = async (
stream: SSEStreamingApi,
cb: (stream: SSEStreamingApi) => Promise<void>,
onError?: (e: Error, stream: SSEStreamingApi) => Promise<void>
): Promise<void> => {
try {
await cb(stream)
} catch (e) {
if (e instanceof Error && onError) {
await onError(e, stream)
await stream.writeSSE({
event: 'error',
data: e.message,
})
} else {
console.error(e)
}
}
}
const contextStash: WeakMap<ReadableStream, Context> = new WeakMap<ReadableStream, Context>()
export const streamSSE = (
c: Context,
cb: (stream: SSEStreamingApi) => Promise<void>,
onError?: (e: Error, stream: SSEStreamingApi) => Promise<void>
): Response => {
const { readable, writable } = new TransformStream()
const stream = new SSEStreamingApi(writable, readable)
// Until Bun v1.1.27, Bun didn't call cancel() on the ReadableStream for Response objects from Bun.serve()
if (isOldBunVersion()) {
c.req.raw.signal.addEventListener('abort', () => {
if (!stream.closed) {
stream.abort()
}
})
}
// in bun, `c` is destroyed when the request is returned, so hold it until the end of streaming
contextStash.set(stream.responseReadable, c)
c.header('Transfer-Encoding', 'chunked')
c.header('Content-Type', 'text/event-stream')
c.header('Cache-Control', 'no-cache')
c.header('Connection', 'keep-alive')
run(stream, cb, onError)
return c.newResponse(stream.responseReadable)
}

View File

@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.build.json",
"compilerOptions": {},
"references": []
}

View File

@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.build.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "../../dist/packages/mcp",
"types": ["vitest/globals"]
},
"references": []
}

View File

@ -0,0 +1,7 @@
import { defineProject } from 'vitest/config'
export default defineProject({
test: {
globals: true,
},
})

View File

@ -47,7 +47,7 @@
"publint": "^0.3.9", "publint": "^0.3.9",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.2.4"
}, },
"dependencies": { "dependencies": {
"@medley/router": "^0.2.1" "@medley/router": "^0.2.1"

View File

@ -1,13 +1,5 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {},
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo",
"emitDeclarationOnly": false,
"isolatedDeclarations": true
},
"include": ["src/**/*.ts"],
"exclude": ["**/*.test.ts"],
"references": [] "references": []
} }

View File

@ -1,5 +1,4 @@
{ {
"extends": "../../tsconfig.base.json",
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [

View File

@ -1,13 +1,8 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist/out-tsc/packages/medley-router", "outDir": "../../dist/packages/medley-router",
"types": ["vitest/globals"] "types": ["vitest/globals"]
}, },
"include": ["**/*.test.ts", "vitest.config.ts"], "references": []
"references": [
{
"path": "./tsconfig.build.json"
}
]
} }

View File

@ -1,5 +1,11 @@
# @hono/node-ws # @hono/node-ws
## 1.2.0
### Minor Changes
- [#1213](https://github.com/honojs/middleware/pull/1213) [`5178967931d9f9020892d69b9a929469be25c6a7`](https://github.com/honojs/middleware/commit/5178967931d9f9020892d69b9a929469be25c6a7) Thanks [@palmm](https://github.com/palmm)! - Return the WebSocketServer instance used for createNodeWebSocket
## 1.1.7 ## 1.1.7
### Patch Changes ### Patch Changes

View File

@ -1,6 +1,6 @@
{ {
"name": "@hono/node-ws", "name": "@hono/node-ws",
"version": "1.1.7", "version": "1.2.0",
"description": "WebSocket helper for Node.js", "description": "WebSocket helper for Node.js",
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",
@ -45,7 +45,7 @@
"publint": "^0.3.9", "publint": "^0.3.9",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.2.4"
}, },
"dependencies": { "dependencies": {
"ws": "^8.17.0" "ws": "^8.17.0"

View File

@ -13,10 +13,11 @@ describe('WebSocket helper', () => {
let server: ServerType let server: ServerType
let injectWebSocket: ReturnType<typeof createNodeWebSocket>['injectWebSocket'] let injectWebSocket: ReturnType<typeof createNodeWebSocket>['injectWebSocket']
let upgradeWebSocket: ReturnType<typeof createNodeWebSocket>['upgradeWebSocket'] let upgradeWebSocket: ReturnType<typeof createNodeWebSocket>['upgradeWebSocket']
let wss: ReturnType<typeof createNodeWebSocket>['wss']
beforeEach(async () => { beforeEach(async () => {
app = new Hono() app = new Hono()
;({ injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app })) ;({ injectWebSocket, upgradeWebSocket, wss } = createNodeWebSocket({ app }))
server = await new Promise<ServerType>((resolve) => { server = await new Promise<ServerType>((resolve) => {
const server = serve({ fetch: app.fetch, port: 3030 }, () => resolve(server)) const server = serve({ fetch: app.fetch, port: 3030 }, () => resolve(server))
@ -286,4 +287,25 @@ describe('WebSocket helper', () => {
expect(await mainPromise).toBe(true) expect(await mainPromise).toBe(true)
}) })
it('Should return the wss used for the websocket helper', async () => {
let clientWs: WebSocket | null = null
const mainPromise = new Promise<void>((resolve) =>
wss.on('connection', (ws) => {
clientWs = ws
resolve()
})
)
app.get(
'/',
upgradeWebSocket(() => ({}))
)
new WebSocket('ws://localhost:3030/')
await mainPromise
expect(clientWs).toBeTruthy()
expect(wss.clients.size).toBe(1)
})
}) })

View File

@ -17,6 +17,7 @@ export interface NodeWebSocket {
} }
> >
injectWebSocket(server: Server | Http2Server | Http2SecureServer): void injectWebSocket(server: Server | Http2Server | Http2SecureServer): void
wss: WebSocketServer
} }
export interface NodeWebSocketInit { export interface NodeWebSocketInit {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -56,6 +57,7 @@ export const createNodeWebSocket = (init: NodeWebSocketInit): NodeWebSocket => {
} }
return { return {
wss,
injectWebSocket(server) { injectWebSocket(server) {
server.on('upgrade', async (request, socket: Duplex, head) => { server.on('upgrade', async (request, socket: Duplex, head) => {
const url = new URL(request.url ?? '/', init.baseUrl ?? 'http://localhost') const url = new URL(request.url ?? '/', init.baseUrl ?? 'http://localhost')

View File

@ -1,14 +1,7 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo",
"emitDeclarationOnly": false,
"isolatedDeclarations": true,
"types": ["node", "ws"] "types": ["node", "ws"]
}, },
"include": ["src/**/*.ts"],
"exclude": ["**/*.test.ts"],
"references": [] "references": []
} }

View File

@ -1,5 +1,4 @@
{ {
"extends": "../../tsconfig.base.json",
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [

View File

@ -1,13 +1,8 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist/out-tsc/packages/node-ws", "outDir": "../../dist/packages/node-ws",
"types": ["vitest/globals"] "types": ["vitest/globals"]
}, },
"include": ["**/*.test.ts", "vitest.config.ts"], "references": []
"references": [
{
"path": "./tsconfig.build.json"
}
]
} }

View File

@ -80,7 +80,7 @@
"publint": "^0.3.9", "publint": "^0.3.9",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.2.4"
}, },
"engines": { "engines": {
"node": ">=18.4.0" "node": ">=18.4.0"

View File

@ -1,13 +1,5 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.build.json",
"compilerOptions": { "compilerOptions": {},
"rootDir": "src",
"outDir": "dist",
"tsBuildInfoFile": "dist/tsconfig.build.tsbuildinfo",
"emitDeclarationOnly": false,
"isolatedDeclarations": true
},
"include": ["src/**/*.ts"],
"exclude": ["**/*.test.ts"],
"references": [] "references": []
} }

View File

@ -1,5 +1,4 @@
{ {
"extends": "../../tsconfig.base.json",
"files": [], "files": [],
"include": [], "include": [],
"references": [ "references": [

View File

@ -1,13 +1,9 @@
{ {
"extends": "../../tsconfig.base.json", "extends": "../../tsconfig.base.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../../dist/out-tsc/packages/oauth-providers", "outDir": "../../dist/packages/oauth-providers",
"types": ["vitest/globals"] "types": ["vitest/globals"]
}, },
"include": ["mocks.ts", "**/*.test.ts", "vitest.config.ts"], "include": ["src", "mocks.ts", "vitest.config.ts"],
"references": [ "references": []
{
"path": "./tsconfig.build.json"
}
]
} }

View File

@ -48,7 +48,7 @@
"publint": "^0.3.9", "publint": "^0.3.9",
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.0.8" "vitest": "^3.2.4"
}, },
"dependencies": { "dependencies": {
"oauth4webapi": "^2.6.0" "oauth4webapi": "^2.6.0"

Some files were not shown because too many files have changed in this diff Show More