Compare commits

...

19 Commits

Author SHA1 Message Date
github-actions[bot] 0dc6a1f5e6
Version Packages (#1325)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-21 15:04:24 +09:00
James Talmage d89fed7eec
feat(auth-js): Support async ConfigHandler (#1324)
* feat(auth-js): Support async ConfigHandler

* add changeset

* format
2025-07-21 14:55:46 +09:00
github-actions[bot] b24925168a
Version Packages (#1323)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-20 08:36:17 +09:00
Andrei 2a46bfbba0
fix(standard-validator): arktype leaking headers (#1282)
* fix(standard-validator): arktype strip headers from error output

* chore(standard-validator): fix formatting

* feat(changeset): add changeset

* fix: change header schema to lowercase and cleanup tests

* fix: strip out sensitive fields for valibot

* chore: refactor, cleanup and add jsdoc
2025-07-20 08:30:09 +09:00
github-actions[bot] 28601b311b
Version Packages (#1321)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-19 18:53:02 +09:00
Yusuke Wada 5a3cbc4cc6
chore: fix the changeset (#1322) 2025-07-19 18:47:26 +09:00
Yusuke Wada e835cf6183
chore: add changeset (#1320) 2025-07-19 18:26:04 +09:00
Yusuke Wada 2339eefa1c
fix(zod-openapi): correct importing `zod` (#1319) 2025-07-19 18:03:34 +09:00
github-actions[bot] 7d82875634
Version Packages (#1317)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-19 10:26:47 +09:00
Yusuke Wada c6a16ab7aa
fix(zod-validator): support transform (#1315)
* fix(zod-validator): support transform

* polish
2025-07-19 10:20:40 +09:00
github-actions[bot] 1a564d5fb6
Version Packages (#1313)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-18 17:00:12 +09:00
Yusuke Wada 9216d2a666
ci: specify the deno version `2.3.7` for dry-run (#1314) 2025-07-18 16:55:45 +09:00
Yusuke Wada 845e336ff4
feat(zod-openapi): support Zod v4 (#1223)
* feat(zod-openapi): support Zod v4

* update `peerDependencies`

* add changeset

* fixed type error

* update dependencies
2025-07-18 16:40:21 +09:00
github-actions[bot] 2a589b0728
Version Packages (#1310)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-16 12:53:35 +09:00
github-actions[bot] 47cb4c8569
chore(ua-blocker): update robots.json from upstream (#1309)
Co-authored-by: yusukebe <10682+yusukebe@users.noreply.github.com>
2025-07-16 12:16:43 +09:00
github-actions[bot] 3705fe6560
Version Packages (#1305)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-13 20:10:52 +09:00
kiki-kanri 9f6278f51c
chore(zod-validator): upgrade zod to v4 and update import style in README.md (#1302)
* chore(zod-validator): upgrade zod to v4

* docs(zod-validator): update Zod import style to be more tree-shakeable in README.md

* chore(zod-validator): commit .changeset

* fix(zod-validator): correctly set the zod version to follow the official website doc

* chore(zod-validator): change `.changeset` to patch cause it does not include a new feature
2025-07-13 19:57:55 +09:00
github-actions[bot] 55e4b19a50
Version Packages (#1304)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-07-13 17:16:43 +09:00
Yusuke Wada 68cbb31af4
feat(session): enable accessing sid via `c.var.session.id` (#1272)
* feat(session): enable accessing sid via `c.var.session.id`

* changeset
2025-07-13 17:12:05 +09:00
36 changed files with 501 additions and 48 deletions

View File

@ -54,7 +54,7 @@ jobs:
- uses: denoland/setup-deno@v2 - uses: denoland/setup-deno@v2
with: with:
cache: true cache: true
deno-version: v2.x deno-version: v2.3.7
- run: deno install --no-lock - run: deno install --no-lock
- run: deno publish --dry-run - run: deno publish --dry-run

View File

@ -1,5 +1,11 @@
# @hono/auth-js # @hono/auth-js
## 1.1.0
### Minor Changes
- [#1324](https://github.com/honojs/middleware/pull/1324) [`d89fed7eecfa151c18b0a8cf95dae1dfe83dfec2`](https://github.com/honojs/middleware/commit/d89fed7eecfa151c18b0a8cf95dae1dfe83dfec2) Thanks [@jamestalmage](https://github.com/jamestalmage)! - Allow async authjs Config
## 1.0.17 ## 1.0.17
### Patch Changes ### Patch Changes

View File

@ -1,6 +1,6 @@
{ {
"name": "@hono/auth-js", "name": "@hono/auth-js",
"version": "1.0.17", "version": "1.1.0",
"license": "MIT", "license": "MIT",
"exports": { "exports": {
".": "./src/index.ts", ".": "./src/index.ts",

View File

@ -1,6 +1,6 @@
{ {
"name": "@hono/auth-js", "name": "@hono/auth-js",
"version": "1.0.17", "version": "1.1.0",
"description": "A third-party Auth js middleware for Hono", "description": "A third-party Auth js middleware for Hono",
"main": "dist/index.js", "main": "dist/index.js",
"type": "module", "type": "module",

View File

@ -47,6 +47,27 @@ describe('Config', () => {
expect(res.status).toBe(200) expect(res.status).toBe(200)
}) })
it('Should allow async ConfigHandler', async () => {
globalThis.process.env = { AUTH_SECRET: 'secret' }
const app = new Hono()
app.use(
'/*',
initAuthConfig(async () => {
await new Promise((resolve) => setTimeout(resolve, 1))
return {
basePath: '/api/auth',
providers: [],
}
})
)
app.use('/api/auth/*', authHandler())
const req = new Request('http://localhost/api/auth/signin')
const res = await app.request(req)
expect(res.status).toBe(200)
})
it('Should return 401 is if auth cookie is invalid or missing', async () => { it('Should return 401 is if auth cookie is invalid or missing', async () => {
const app = new Hono() const app = new Hono()

View File

@ -29,7 +29,7 @@ export type AuthUser = {
export interface AuthConfig extends Omit<AuthConfigCore, 'raw'> {} export interface AuthConfig extends Omit<AuthConfigCore, 'raw'> {}
export type ConfigHandler = (c: Context) => AuthConfig export type ConfigHandler = (c: Context) => AuthConfig | Promise<AuthConfig>
export function setEnvDefaults(env: AuthEnv, config: AuthConfig): void { export function setEnvDefaults(env: AuthEnv, config: AuthConfig): void {
config.secret ??= env.AUTH_SECRET config.secret ??= env.AUTH_SECRET
@ -118,7 +118,7 @@ export function verifyAuth(): MiddlewareHandler {
export function initAuthConfig(cb: ConfigHandler): MiddlewareHandler { export function initAuthConfig(cb: ConfigHandler): MiddlewareHandler {
return async (c, next) => { return async (c, next) => {
const config = cb(c) const config = await cb(c)
c.set('authConfig', config) c.set('authConfig', config)
await next() await next()
} }

View File

@ -1,5 +1,11 @@
# @hono/session # @hono/session
## 0.2.0
### Minor Changes
- [#1272](https://github.com/honojs/middleware/pull/1272) [`68cbb31af43fd5916173f9f6b07c8cedb3dbe706`](https://github.com/honojs/middleware/commit/68cbb31af43fd5916173f9f6b07c8cedb3dbe706) Thanks [@yusukebe](https://github.com/yusukebe)! - feat: enable accessing sid via `c.var.session.id`
## 0.1.1 ## 0.1.1
### Patch Changes ### Patch Changes

View File

@ -1,6 +1,6 @@
{ {
"name": "@hono/session", "name": "@hono/session",
"version": "0.1.1", "version": "0.2.0",
"description": "Session middleware for Hono", "description": "Session middleware for Hono",
"type": "module", "type": "module",
"module": "dist/index.js", "module": "dist/index.js",

View File

@ -939,3 +939,85 @@ describe('session events', () => {
expect(onUpdate).toHaveBeenCalledWith({ sub }) expect(onUpdate).toHaveBeenCalledWith({ sub })
}) })
}) })
describe('session.id', () => {
const app = new Hono()
.get('/session-id', useSession<TestData>({ secret }), async (c) => {
await c.var.session.get()
return c.json({ id: c.var.session.id })
})
.put('/session-id/:sub', useSession<TestData>({ secret }), async (c) => {
const sub = c.req.param('sub')
await c.var.session.update({ sub })
return c.json({ id: c.var.session.id })
})
it('throws an error when session is not initialised', async () => {
const app = new Hono().get('/session-id', useSession<TestData>({ secret }), (c) => {
return c.json({ id: c.var.session.id })
})
const res = await app.request('/session-id')
expect(res.status).toBe(500)
})
it('returns session id after get()', async () => {
const res = await app.request('/session-id')
const data = await res.json()
expect(res.status).toBe(200)
expect(data.id).toBeTypeOf('string')
expect(data.id.length).toBeGreaterThan(0)
})
it('returns session id after update()', async () => {
const res = await app.request('/session-id/test-user', { method: 'PUT' })
const data = await res.json()
expect(res.status).toBe(200)
expect(data.id).toBeTypeOf('string')
expect(data.id.length).toBeGreaterThan(0)
})
it('returns same session id for existing session', async () => {
const cookie = await encrypt(session.payload)
const res = await app.request('/session-id', {
headers: { cookie: `sid=${cookie}` },
})
const data = await res.json()
expect(res.status).toBe(200)
expect(data.id).toBe(sid) // 'some-session-id'
})
it('generates different session ids for new sessions', async () => {
const res1 = await app.request('/session-id')
const res2 = await app.request('/session-id')
const data1 = await res1.json()
const data2 = await res2.json()
expect(res1.status).toBe(200)
expect(res2.status).toBe(200)
expect(data1.id).not.toBe(data2.id)
expect(data1.id).toBeTypeOf('string')
expect(data2.id).toBeTypeOf('string')
})
it('preserves session id across requests with same cookie', async () => {
// First request - create session
const firstRes = await app.request('/session-id/user1', { method: 'PUT' })
const sessionCookie = await getSetCookie(firstRes, 'sid', decrypt)
const firstData = await firstRes.json()
// Second request - use same cookie
const secondRes = await app.request('/session-id', {
headers: { cookie: `sid=${sessionCookie?.value}` },
})
const secondData = await secondRes.json()
expect(firstRes.status).toBe(200)
expect(secondRes.status).toBe(200)
expect(firstData.id).toBe(secondData.id)
expect(firstData.id).toBe(sessionCookie?.payload.sid)
})
})

View File

@ -104,6 +104,9 @@ export const useSession = <Data extends SessionData>(
get data() { get data() {
return session.data return session.data
}, },
get id() {
return session.id
},
delete() { delete() {
session.delete() session.delete()
}, },

View File

@ -26,6 +26,11 @@ export interface Session<Data> {
*/ */
readonly data: Data | null readonly data: Data | null
/**
* Current session ID.
*/
readonly id: string | null
/** /**
* Delete the current session, removing the session cookie and data from storage. * Delete the current session, removing the session cookie and data from storage.
*/ */
@ -122,6 +127,17 @@ export function getSession<Data extends SessionData>({
return session.data ?? null return session.data ?? null
}, },
get id() {
if (!session) {
throw new Error('Session not initialised. Call get() or update() first.')
}
if (session.action === 'destroy') {
throw new Error('Session has been destroyed.')
}
return session.payload?.sid ?? null
},
delete() { delete() {
if (session) { if (session) {
onDelete?.(session.data) onDelete?.(session.data)

View File

@ -1,5 +1,11 @@
# @hono/standard-validator # @hono/standard-validator
## 0.1.4
### Patch Changes
- [#1282](https://github.com/honojs/middleware/pull/1282) [`2a46bfbba0695ae713b58211fd5e83a08b829ad6`](https://github.com/honojs/middleware/commit/2a46bfbba0695ae713b58211fd5e83a08b829ad6) Thanks [@MonsterDeveloper](https://github.com/MonsterDeveloper)! - Fix cookies output for arktype in standard schema validator
## 0.1.3 ## 0.1.3
### Patch Changes ### Patch Changes

View File

@ -26,7 +26,12 @@ const querySortSchema = type({
order: "'asc'|'desc'", order: "'asc'|'desc'",
}) })
const headerSchema = type({
'user-agent': 'string',
})
export { export {
headerSchema,
idJSONSchema, idJSONSchema,
personJSONSchema, personJSONSchema,
postJSONSchema, postJSONSchema,

View File

@ -28,7 +28,12 @@ const querySortSchema = object({
order: picklist(['asc', 'desc']), order: picklist(['asc', 'desc']),
}) })
const headerSchema = object({
'user-agent': string(),
})
export { export {
headerSchema,
idJSONSchema, idJSONSchema,
personJSONSchema, personJSONSchema,
postJSONSchema, postJSONSchema,

View File

@ -28,7 +28,12 @@ const querySortSchema = z.object({
order: z.enum(['asc', 'desc']), order: z.enum(['asc', 'desc']),
}) })
const headerSchema = z.object({
'user-agent': z.string(),
})
export { export {
headerSchema,
idJSONSchema, idJSONSchema,
personJSONSchema, personJSONSchema,
postJSONSchema, postJSONSchema,

View File

@ -1,6 +1,6 @@
{ {
"name": "@hono/standard-validator", "name": "@hono/standard-validator",
"version": "0.1.3", "version": "0.1.4",
"license": "MIT", "license": "MIT",
"exports": { "exports": {
".": "./src/index.ts" ".": "./src/index.ts"

View File

@ -1,6 +1,6 @@
{ {
"name": "@hono/standard-validator", "name": "@hono/standard-validator",
"version": "0.1.3", "version": "0.1.4",
"description": "Validator middleware using Standard Schema", "description": "Validator middleware using Standard Schema",
"type": "module", "type": "module",
"main": "dist/index.cjs", "main": "dist/index.cjs",

View File

@ -352,6 +352,47 @@ describe('Standard Schema Validation', () => {
> >
}) })
}) })
describe('Sensitive Data Removal', () => {
it("doesn't return cookies after headers validation", async () => {
const app = new Hono()
const schema = schemas.headerSchema
app.get('/headers', sValidator('header', schema), (c) =>
c.json({ success: true, userAgent: c.req.header('User-Agent') })
)
const req = new Request('http://localhost/headers', {
headers: {
// Not passing the User-Agent header to trigger the validation error
Cookie: 'SECRET=123',
},
})
const res = await app.request(req)
expect(res.status).toBe(400)
const data = (await res.json()) as { success: false; error: unknown[] }
expect(data.success).toBe(false)
expect(data.error).toBeDefined()
if (lib === 'arktype') {
expect(
(data.error as { data: Record<string, unknown> }[]).some(
(error) => error.data && error.data.cookie
)
).toBe(false)
}
if (lib === 'valibot') {
expect(
(data.error as { path: { input: Record<string, unknown> }[] }[]).some((error) =>
error.path.some((path) => path.input.cookie)
)
).toBe(false)
}
})
})
}) })
}) })
}) })

View File

@ -1,6 +1,7 @@
import type { StandardSchemaV1 } from '@standard-schema/spec' import type { StandardSchemaV1 } from '@standard-schema/spec'
import type { Context, Env, Input, MiddlewareHandler, TypedResponse, ValidationTargets } from 'hono' import type { Context, Env, Input, MiddlewareHandler, TypedResponse, ValidationTargets } from 'hono'
import { validator } from 'hono/validator' import { validator } from 'hono/validator'
import { sanitizeIssues } from './sanitize-issues'
type HasUndefined<T> = undefined extends T ? true : false type HasUndefined<T> = undefined extends T ? true : false
type TOrPromiseOfT<T> = T | Promise<T> type TOrPromiseOfT<T> = T | Promise<T>
@ -21,6 +22,67 @@ type Hook<
c: Context<E, P> c: Context<E, P>
) => TOrPromiseOfT<Response | void | TypedResponse<O>> ) => TOrPromiseOfT<Response | void | TypedResponse<O>>
/**
* Validation middleware for libraries that support [Standard Schema](https://standardschema.dev/) specification.
*
* This middleware validates incoming request data against a provided schema
* that conforms to the Standard Schema specification. It supports validation
* of JSON bodies, headers, queries, forms, and other request targets.
*
* @param target - The request target to validate ('json', 'header', 'query', 'form', etc.)
* @param schema - A schema object conforming to Standard Schema specification
* @param hook - Optional hook function called with validation results for custom error handling
* @returns A Hono middleware handler that validates requests and makes validated data available via `c.req.valid()`
*
* @example Basic JSON validation
* ```ts
* import { z } from 'zod'
* import { sValidator } from '@hono/standard-validator'
*
* const schema = z.object({
* name: z.string(),
* age: z.number(),
* })
*
* app.post('/author', sValidator('json', schema), (c) => {
* const data = c.req.valid('json')
* return c.json({
* success: true,
* message: `${data.name} is ${data.age}`,
* })
* })
* ```
*
* @example With custom error handling hook
* ```ts
* app.post(
* '/post',
* sValidator('json', schema, (result, c) => {
* if (!result.success) {
* return c.text('Invalid!', 400)
* }
* }),
* (c) => {
* // Handler code
* }
* )
* ```
*
* @example Header validation
* ```ts
* import { object, string } from 'valibot'
*
* const schema = object({
* 'content-type': string(),
* 'user-agent': string(),
* })
*
* app.post('/author', sValidator('header', schema), (c) => {
* const headers = c.req.valid('header')
* // do something with headers
* })
* ```
*/
const sValidator = < const sValidator = <
Schema extends StandardSchemaV1, Schema extends StandardSchemaV1,
Target extends keyof ValidationTargets, Target extends keyof ValidationTargets,
@ -71,7 +133,9 @@ const sValidator = <
} }
if (result.issues) { if (result.issues) {
return c.json({ data: value, error: result.issues, success: false }, 400) const processedIssues = sanitizeIssues(result.issues, schema['~standard'].vendor, target)
return c.json({ data: value, error: processedIssues, success: false }, 400)
} }
return result.value as StandardSchemaV1.InferOutput<Schema> return result.value as StandardSchemaV1.InferOutput<Schema>

View File

@ -0,0 +1,95 @@
import type { StandardSchemaV1 } from '@standard-schema/spec'
import type { ValidationTargets } from 'hono'
const RESTRICTED_DATA_FIELDS = {
header: ['cookie'],
}
/**
* Sanitizes validation issues by removing sensitive data fields from error messages.
*
* This function removes potentially sensitive information (like cookies) from validation
* error messages before they are returned to the client. It handles different validation
* library formats based on the vendor string.
*
* @param issues - Array of validation issues from Standard Schema validation
* @param vendor - The validation library vendor identifier (e.g., 'arktype', 'valibot')
* @param target - The validation target being processed ('header', 'json', etc.)
* @returns Sanitized array of validation issues with sensitive data removed
*
* @example
* ```ts
* const issues = [{ message: 'Invalid header', data: { cookie: 'secret' } }]
* const sanitized = sanitizeIssues(issues, 'arktype', 'header')
* // Returns issues with cookie field removed from data
* ```
*/
export function sanitizeIssues(
issues: readonly StandardSchemaV1.Issue[],
vendor: string,
target: keyof ValidationTargets
): readonly StandardSchemaV1.Issue[] {
if (!(target in RESTRICTED_DATA_FIELDS)) {
return issues
}
const restrictedFields =
RESTRICTED_DATA_FIELDS[target as keyof typeof RESTRICTED_DATA_FIELDS] || []
if (vendor === 'arktype') {
return sanitizeArktypeIssues(issues, restrictedFields)
}
if (vendor === 'valibot') {
return sanitizeValibotIssues(issues, restrictedFields)
}
return issues
}
function sanitizeArktypeIssues(
issues: readonly StandardSchemaV1.Issue[],
restrictedFields: string[]
): readonly StandardSchemaV1.Issue[] {
return issues.map((issue) => {
if (
issue &&
typeof issue === 'object' &&
'data' in issue &&
typeof issue.data === 'object' &&
issue.data !== null &&
!Array.isArray(issue.data)
) {
const dataCopy = { ...(issue.data as Record<string, unknown>) }
for (const field of restrictedFields) {
delete dataCopy[field]
}
return { ...issue, data: dataCopy }
}
return issue
}) as readonly StandardSchemaV1.Issue[]
}
function sanitizeValibotIssues(
issues: readonly StandardSchemaV1.Issue[],
restrictedFields: string[]
): readonly StandardSchemaV1.Issue[] {
return issues.map((issue) => {
if (issue && typeof issue === 'object' && 'path' in issue && Array.isArray(issue.path)) {
for (const path of issue.path) {
if (
typeof path === 'object' &&
'input' in path &&
typeof path.input === 'object' &&
path.input !== null &&
!Array.isArray(path.input)
) {
for (const field of restrictedFields) {
delete path.input[field]
}
}
}
}
return issue
}) as readonly StandardSchemaV1.Issue[]
}

View File

@ -1,5 +1,11 @@
# @hono/ua-blocker # @hono/ua-blocker
## 0.1.6
### Patch Changes
- [#1309](https://github.com/honojs/middleware/pull/1309) [`47cb4c85693ebe77d3b8b55f6e1e59778f671a15`](https://github.com/honojs/middleware/commit/47cb4c85693ebe77d3b8b55f6e1e59778f671a15) Thanks [@github-actions](https://github.com/apps/github-actions)! - chore(ua-blocker): sync `robots.json` with upstream
## 0.1.5 ## 0.1.5
### Patch Changes ### Patch Changes

View File

@ -1,6 +1,6 @@
{ {
"name": "@hono/ua-blocker", "name": "@hono/ua-blocker",
"version": "0.1.5", "version": "0.1.6",
"description": "User agent-based blocker for Hono", "description": "User agent-based blocker for Hono",
"type": "module", "type": "module",
"module": "dist/index.js", "module": "dist/index.js",

View File

@ -251,6 +251,13 @@
"frequency": "No information.", "frequency": "No information.",
"description": "Used to train Gemini and Vertex AI generative APIs. Does not impact a site's inclusion or ranking in Google Search." "description": "Used to train Gemini and Vertex AI generative APIs. Does not impact a site's inclusion or ranking in Google Search."
}, },
"GoogleAgent-Mariner": {
"operator": "Unclear at this time.",
"respect": "Unclear at this time.",
"function": "AI Agents",
"frequency": "Unclear at this time.",
"description": "GoogleAgent-Mariner is an AI agent created by Google that can use a web browser. It can intelligently navigate and interact with websites to complete multi-step tasks on behalf of a human user. More info can be found at https://darkvisitors.com/agents/agents/googleagent-mariner"
},
"GoogleOther": { "GoogleOther": {
"description": "\"Used by various product teams for fetching publicly accessible content from sites. For example, it may be used for one-off crawls for internal research and development.\"", "description": "\"Used by various product teams for fetching publicly accessible content from sites. For example, it may be used for one-off crawls for internal research and development.\"",
"frequency": "No information.", "frequency": "No information.",

View File

@ -35,6 +35,7 @@ User-agent: FriendlyCrawler
User-agent: Gemini-Deep-Research User-agent: Gemini-Deep-Research
User-agent: Google-CloudVertexBot User-agent: Google-CloudVertexBot
User-agent: Google-Extended User-agent: Google-Extended
User-agent: GoogleAgent-Mariner
User-agent: GoogleOther User-agent: GoogleOther
User-agent: GoogleOther-Image User-agent: GoogleOther-Image
User-agent: GoogleOther-Video User-agent: GoogleOther-Video
@ -87,7 +88,7 @@ User-agent: YandexAdditionalBot
User-agent: YouBot User-agent: YouBot
Disallow: / Disallow: /
`; `;
export const ALL_BOTS = ["AI2Bot", "Ai2Bot-Dolma", "aiHitBot", "Amazonbot", "Andibot", "anthropic-ai", "Applebot", "Applebot-Extended", "Awario", "bedrockbot", "Brightbot 1.0", "Bytespider", "CCBot", "ChatGPT-User", "Claude-SearchBot", "Claude-User", "Claude-Web", "ClaudeBot", "cohere-ai", "cohere-training-data-crawler", "Cotoyogi", "Crawlspace", "Datenbank Crawler", "Devin", "Diffbot", "DuckAssistBot", "Echobot Bot", "EchoboxBot", "FacebookBot", "facebookexternalhit", "Factset_spyderbot", "FirecrawlAgent", "FriendlyCrawler", "Gemini-Deep-Research", "Google-CloudVertexBot", "Google-Extended", "GoogleOther", "GoogleOther-Image", "GoogleOther-Video", "GPTBot", "iaskspider/2.0", "ICC-Crawler", "ImagesiftBot", "img2dataset", "ISSCyberRiskCrawler", "Kangaroo Bot", "meta-externalagent", "Meta-ExternalAgent", "meta-externalfetcher", "Meta-ExternalFetcher", "MistralAI-User", "MistralAI-User/1.0", "MyCentralAIScraperBot", "netEstate Imprint Crawler", "NovaAct", "OAI-SearchBot", "omgili", "omgilibot", "Operator", "PanguBot", "Panscient", "panscient.com", "Perplexity-User", "PerplexityBot", "PetalBot", "PhindBot", "Poseidon Research Crawler", "QualifiedBot", "QuillBot", "quillbot.com", "SBIntuitionsBot", "Scrapy", "SemrushBot-OCOB", "SemrushBot-SWA", "Sidetrade indexer bot", "SummalyBot", "Thinkbot", "TikTokSpider", "Timpibot", "VelenPublicWebCrawler", "WARDBot", "Webzio-Extended", "wpbot", "YandexAdditional", "YandexAdditionalBot", "YouBot"]; export const ALL_BOTS = ["AI2Bot", "Ai2Bot-Dolma", "aiHitBot", "Amazonbot", "Andibot", "anthropic-ai", "Applebot", "Applebot-Extended", "Awario", "bedrockbot", "Brightbot 1.0", "Bytespider", "CCBot", "ChatGPT-User", "Claude-SearchBot", "Claude-User", "Claude-Web", "ClaudeBot", "cohere-ai", "cohere-training-data-crawler", "Cotoyogi", "Crawlspace", "Datenbank Crawler", "Devin", "Diffbot", "DuckAssistBot", "Echobot Bot", "EchoboxBot", "FacebookBot", "facebookexternalhit", "Factset_spyderbot", "FirecrawlAgent", "FriendlyCrawler", "Gemini-Deep-Research", "Google-CloudVertexBot", "Google-Extended", "GoogleAgent-Mariner", "GoogleOther", "GoogleOther-Image", "GoogleOther-Video", "GPTBot", "iaskspider/2.0", "ICC-Crawler", "ImagesiftBot", "img2dataset", "ISSCyberRiskCrawler", "Kangaroo Bot", "meta-externalagent", "Meta-ExternalAgent", "meta-externalfetcher", "Meta-ExternalFetcher", "MistralAI-User", "MistralAI-User/1.0", "MyCentralAIScraperBot", "netEstate Imprint Crawler", "NovaAct", "OAI-SearchBot", "omgili", "omgilibot", "Operator", "PanguBot", "Panscient", "panscient.com", "Perplexity-User", "PerplexityBot", "PetalBot", "PhindBot", "Poseidon Research Crawler", "QualifiedBot", "QuillBot", "quillbot.com", "SBIntuitionsBot", "Scrapy", "SemrushBot-OCOB", "SemrushBot-SWA", "Sidetrade indexer bot", "SummalyBot", "Thinkbot", "TikTokSpider", "Timpibot", "VelenPublicWebCrawler", "WARDBot", "Webzio-Extended", "wpbot", "YandexAdditional", "YandexAdditionalBot", "YouBot"];
export const NON_RESPECTING_BOTS = ["Andibot", "anthropic-ai", "Applebot", "Awario", "Brightbot 1.0", "Bytespider", "Claude-Web", "cohere-ai", "cohere-training-data-crawler", "Datenbank Crawler", "Devin", "Diffbot", "DuckAssistBot", "Echobot Bot", "EchoboxBot", "facebookexternalhit", "Factset_spyderbot", "Gemini-Deep-Research", "iaskspider/2.0", "img2dataset", "ISSCyberRiskCrawler", "Kangaroo Bot", "Meta-ExternalAgent", "meta-externalfetcher", "Meta-ExternalFetcher", "MistralAI-User", "MyCentralAIScraperBot", "netEstate Imprint Crawler", "NovaAct", "Operator", "PanguBot", "Perplexity-User", "PhindBot", "Poseidon Research Crawler", "QualifiedBot", "QuillBot", "quillbot.com", "Scrapy", "Sidetrade indexer bot", "SummalyBot", "Thinkbot", "TikTokSpider", "Timpibot", "WARDBot", "Webzio-Extended", "wpbot"]; export const NON_RESPECTING_BOTS = ["Andibot", "anthropic-ai", "Applebot", "Awario", "Brightbot 1.0", "Bytespider", "Claude-Web", "cohere-ai", "cohere-training-data-crawler", "Datenbank Crawler", "Devin", "Diffbot", "DuckAssistBot", "Echobot Bot", "EchoboxBot", "facebookexternalhit", "Factset_spyderbot", "Gemini-Deep-Research", "GoogleAgent-Mariner", "iaskspider/2.0", "img2dataset", "ISSCyberRiskCrawler", "Kangaroo Bot", "Meta-ExternalAgent", "meta-externalfetcher", "Meta-ExternalFetcher", "MistralAI-User", "MyCentralAIScraperBot", "netEstate Imprint Crawler", "NovaAct", "Operator", "PanguBot", "Perplexity-User", "PhindBot", "Poseidon Research Crawler", "QualifiedBot", "QuillBot", "quillbot.com", "Scrapy", "Sidetrade indexer bot", "SummalyBot", "Thinkbot", "TikTokSpider", "Timpibot", "WARDBot", "Webzio-Extended", "wpbot"];
export const ALL_BOTS_REGEX = /(AI2BOT|AI2BOT-DOLMA|AIHITBOT|AMAZONBOT|ANDIBOT|ANTHROPIC-AI|APPLEBOT|APPLEBOT-EXTENDED|AWARIO|BEDROCKBOT|BRIGHTBOT 1.0|BYTESPIDER|CCBOT|CHATGPT-USER|CLAUDE-SEARCHBOT|CLAUDE-USER|CLAUDE-WEB|CLAUDEBOT|COHERE-AI|COHERE-TRAINING-DATA-CRAWLER|COTOYOGI|CRAWLSPACE|DATENBANK CRAWLER|DEVIN|DIFFBOT|DUCKASSISTBOT|ECHOBOT BOT|ECHOBOXBOT|FACEBOOKBOT|FACEBOOKEXTERNALHIT|FACTSET_SPYDERBOT|FIRECRAWLAGENT|FRIENDLYCRAWLER|GEMINI-DEEP-RESEARCH|GOOGLE-CLOUDVERTEXBOT|GOOGLE-EXTENDED|GOOGLEOTHER|GOOGLEOTHER-IMAGE|GOOGLEOTHER-VIDEO|GPTBOT|IASKSPIDER\/2.0|ICC-CRAWLER|IMAGESIFTBOT|IMG2DATASET|ISSCYBERRISKCRAWLER|KANGAROO BOT|META-EXTERNALAGENT|META-EXTERNALAGENT|META-EXTERNALFETCHER|META-EXTERNALFETCHER|MISTRALAI-USER|MISTRALAI-USER\/1.0|MYCENTRALAISCRAPERBOT|NETESTATE IMPRINT CRAWLER|NOVAACT|OAI-SEARCHBOT|OMGILI|OMGILIBOT|OPERATOR|PANGUBOT|PANSCIENT|PANSCIENT.COM|PERPLEXITY-USER|PERPLEXITYBOT|PETALBOT|PHINDBOT|POSEIDON RESEARCH CRAWLER|QUALIFIEDBOT|QUILLBOT|QUILLBOT.COM|SBINTUITIONSBOT|SCRAPY|SEMRUSHBOT-OCOB|SEMRUSHBOT-SWA|SIDETRADE INDEXER BOT|SUMMALYBOT|THINKBOT|TIKTOKSPIDER|TIMPIBOT|VELENPUBLICWEBCRAWLER|WARDBOT|WEBZIO-EXTENDED|WPBOT|YANDEXADDITIONAL|YANDEXADDITIONALBOT|YOUBOT)/; export const ALL_BOTS_REGEX = /(AI2BOT|AI2BOT-DOLMA|AIHITBOT|AMAZONBOT|ANDIBOT|ANTHROPIC-AI|APPLEBOT|APPLEBOT-EXTENDED|AWARIO|BEDROCKBOT|BRIGHTBOT 1.0|BYTESPIDER|CCBOT|CHATGPT-USER|CLAUDE-SEARCHBOT|CLAUDE-USER|CLAUDE-WEB|CLAUDEBOT|COHERE-AI|COHERE-TRAINING-DATA-CRAWLER|COTOYOGI|CRAWLSPACE|DATENBANK CRAWLER|DEVIN|DIFFBOT|DUCKASSISTBOT|ECHOBOT BOT|ECHOBOXBOT|FACEBOOKBOT|FACEBOOKEXTERNALHIT|FACTSET_SPYDERBOT|FIRECRAWLAGENT|FRIENDLYCRAWLER|GEMINI-DEEP-RESEARCH|GOOGLE-CLOUDVERTEXBOT|GOOGLE-EXTENDED|GOOGLEAGENT-MARINER|GOOGLEOTHER|GOOGLEOTHER-IMAGE|GOOGLEOTHER-VIDEO|GPTBOT|IASKSPIDER\/2.0|ICC-CRAWLER|IMAGESIFTBOT|IMG2DATASET|ISSCYBERRISKCRAWLER|KANGAROO BOT|META-EXTERNALAGENT|META-EXTERNALAGENT|META-EXTERNALFETCHER|META-EXTERNALFETCHER|MISTRALAI-USER|MISTRALAI-USER\/1.0|MYCENTRALAISCRAPERBOT|NETESTATE IMPRINT CRAWLER|NOVAACT|OAI-SEARCHBOT|OMGILI|OMGILIBOT|OPERATOR|PANGUBOT|PANSCIENT|PANSCIENT.COM|PERPLEXITY-USER|PERPLEXITYBOT|PETALBOT|PHINDBOT|POSEIDON RESEARCH CRAWLER|QUALIFIEDBOT|QUILLBOT|QUILLBOT.COM|SBINTUITIONSBOT|SCRAPY|SEMRUSHBOT-OCOB|SEMRUSHBOT-SWA|SIDETRADE INDEXER BOT|SUMMALYBOT|THINKBOT|TIKTOKSPIDER|TIMPIBOT|VELENPUBLICWEBCRAWLER|WARDBOT|WEBZIO-EXTENDED|WPBOT|YANDEXADDITIONAL|YANDEXADDITIONALBOT|YOUBOT)/;
export const NON_RESPECTING_BOTS_REGEX = /(ANDIBOT|ANTHROPIC-AI|APPLEBOT|AWARIO|BRIGHTBOT 1.0|BYTESPIDER|CLAUDE-WEB|COHERE-AI|COHERE-TRAINING-DATA-CRAWLER|DATENBANK CRAWLER|DEVIN|DIFFBOT|DUCKASSISTBOT|ECHOBOT BOT|ECHOBOXBOT|FACEBOOKEXTERNALHIT|FACTSET_SPYDERBOT|GEMINI-DEEP-RESEARCH|IASKSPIDER\/2.0|IMG2DATASET|ISSCYBERRISKCRAWLER|KANGAROO BOT|META-EXTERNALAGENT|META-EXTERNALFETCHER|META-EXTERNALFETCHER|MISTRALAI-USER|MYCENTRALAISCRAPERBOT|NETESTATE IMPRINT CRAWLER|NOVAACT|OPERATOR|PANGUBOT|PERPLEXITY-USER|PHINDBOT|POSEIDON RESEARCH CRAWLER|QUALIFIEDBOT|QUILLBOT|QUILLBOT.COM|SCRAPY|SIDETRADE INDEXER BOT|SUMMALYBOT|THINKBOT|TIKTOKSPIDER|TIMPIBOT|WARDBOT|WEBZIO-EXTENDED|WPBOT)/; export const NON_RESPECTING_BOTS_REGEX = /(ANDIBOT|ANTHROPIC-AI|APPLEBOT|AWARIO|BRIGHTBOT 1.0|BYTESPIDER|CLAUDE-WEB|COHERE-AI|COHERE-TRAINING-DATA-CRAWLER|DATENBANK CRAWLER|DEVIN|DIFFBOT|DUCKASSISTBOT|ECHOBOT BOT|ECHOBOXBOT|FACEBOOKEXTERNALHIT|FACTSET_SPYDERBOT|GEMINI-DEEP-RESEARCH|GOOGLEAGENT-MARINER|IASKSPIDER\/2.0|IMG2DATASET|ISSCYBERRISKCRAWLER|KANGAROO BOT|META-EXTERNALAGENT|META-EXTERNALFETCHER|META-EXTERNALFETCHER|MISTRALAI-USER|MYCENTRALAISCRAPERBOT|NETESTATE IMPRINT CRAWLER|NOVAACT|OPERATOR|PANGUBOT|PERPLEXITY-USER|PHINDBOT|POSEIDON RESEARCH CRAWLER|QUALIFIEDBOT|QUILLBOT|QUILLBOT.COM|SCRAPY|SIDETRADE INDEXER BOT|SUMMALYBOT|THINKBOT|TIKTOKSPIDER|TIMPIBOT|WARDBOT|WEBZIO-EXTENDED|WPBOT)/;

View File

@ -1,5 +1,35 @@
# @hono/zod-openapi # @hono/zod-openapi
## 1.0.2
### Patch Changes
- [#1320](https://github.com/honojs/middleware/pull/1320) [`e835cf6183fb4717cfdbe156d9b3d423625a0e15`](https://github.com/honojs/middleware/commit/e835cf6183fb4717cfdbe156d9b3d423625a0e15) Thanks [@yusukebe](https://github.com/yusukebe)! - fix: correct importing `zod`
## 1.0.1
### Patch Changes
- Updated dependencies [[`c6a16ab7aa8fba2403d4294e7673f96796020c65`](https://github.com/honojs/middleware/commit/c6a16ab7aa8fba2403d4294e7673f96796020c65)]:
- @hono/zod-validator@0.7.2
## 1.0.0
### Major Changes
- [#1223](https://github.com/honojs/middleware/pull/1223) [`845e336ff41d29fa74ec3cf84afc16f5ac0c2c77`](https://github.com/honojs/middleware/commit/845e336ff41d29fa74ec3cf84afc16f5ac0c2c77) Thanks [@yusukebe](https://github.com/yusukebe)! - feat: support Zod v4
Zod OpenAPI has been migrated the Zod version from v3 to v4. As a result, the `zod` in `peerDependencies` has been updated to 4.0.0 or higher.
Although this is not a breaking change, it is a major change, so it is considered a major version upgrade.
## 0.19.10
### Patch Changes
- Updated dependencies [[`9f6278f51c846a171a9baa6335fb8fbd9b42cb1c`](https://github.com/honojs/middleware/commit/9f6278f51c846a171a9baa6335fb8fbd9b42cb1c), [`9f6278f51c846a171a9baa6335fb8fbd9b42cb1c`](https://github.com/honojs/middleware/commit/9f6278f51c846a171a9baa6335fb8fbd9b42cb1c)]:
- @hono/zod-validator@0.7.1
## 0.19.9 ## 0.19.9
### Patch Changes ### Patch Changes

View File

@ -1,6 +1,6 @@
{ {
"name": "@hono/zod-openapi", "name": "@hono/zod-openapi",
"version": "0.19.9", "version": "1.0.2",
"license": "MIT", "license": "MIT",
"exports": { "exports": {
".": "./src/index.ts" ".": "./src/index.ts"

View File

@ -1,6 +1,6 @@
{ {
"name": "@hono/zod-openapi", "name": "@hono/zod-openapi",
"version": "0.19.9", "version": "1.0.2",
"description": "A wrapper class of Hono which supports OpenAPI.", "description": "A wrapper class of Hono which supports OpenAPI.",
"type": "module", "type": "module",
"module": "dist/index.js", "module": "dist/index.js",
@ -41,7 +41,7 @@
"homepage": "https://github.com/honojs/middleware", "homepage": "https://github.com/honojs/middleware",
"peerDependencies": { "peerDependencies": {
"hono": ">=4.3.6", "hono": ">=4.3.6",
"zod": ">=3.0.0" "zod": "^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@arethetypeswrong/cli": "^0.17.4", "@arethetypeswrong/cli": "^0.17.4",
@ -51,10 +51,10 @@
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.2.4", "vitest": "^3.2.4",
"yaml": "^2.4.3", "yaml": "^2.4.3",
"zod": "^3.22.1" "zod": "^4.0.5"
}, },
"dependencies": { "dependencies": {
"@asteasolutions/zod-to-openapi": "^7.3.0", "@asteasolutions/zod-to-openapi": "^8.0.0",
"@hono/zod-validator": "workspace:^", "@hono/zod-validator": "workspace:^",
"openapi3-ts": "^4.5.0" "openapi3-ts": "^4.5.0"
}, },

View File

@ -240,7 +240,7 @@ describe('coerce', () => {
type Actual = ExtractSchema<typeof routes>['/api/users/:id']['$get']['input'] type Actual = ExtractSchema<typeof routes>['/api/users/:id']['$get']['input']
type Expected = { type Expected = {
param: { param: {
id: number id: unknown
} }
} }
type verify = Expect<Equal<Expected, Actual>> type verify = Expect<Equal<Expected, Actual>>

View File

@ -11,6 +11,7 @@ import {
OpenApiGeneratorV3, OpenApiGeneratorV3,
OpenApiGeneratorV31, OpenApiGeneratorV31,
extendZodWithOpenApi, extendZodWithOpenApi,
getOpenApiMetadata,
} from '@asteasolutions/zod-to-openapi' } from '@asteasolutions/zod-to-openapi'
import { zValidator } from '@hono/zod-validator' import { zValidator } from '@hono/zod-validator'
import { Hono } from 'hono' import { Hono } from 'hono'
@ -38,8 +39,8 @@ import type { JSONParsed, JSONValue, RemoveBlankRecord, SimplifyDeepArray } from
import { mergePath } from 'hono/utils/url' import { mergePath } from 'hono/utils/url'
import type { OpenAPIObject } from 'openapi3-ts/oas30' import type { OpenAPIObject } from 'openapi3-ts/oas30'
import type { OpenAPIObject as OpenAPIV31bject } from 'openapi3-ts/oas31' import type { OpenAPIObject as OpenAPIV31bject } from 'openapi3-ts/oas31'
import type { ZodError, ZodSchema } from 'zod'
import { ZodType, z } from 'zod' import { ZodType, z } from 'zod'
import type { ZodError } from 'zod'
type MaybePromise<T> = Promise<T> | T type MaybePromise<T> = Promise<T> | T
@ -128,7 +129,7 @@ type InputTypeJson<R extends RouteConfig> = R['request'] extends RequestTypes
? {} ? {}
: R['request']['body']['content'][keyof R['request']['body']['content']] extends Record< : R['request']['body']['content'][keyof R['request']['body']['content']] extends Record<
'schema', 'schema',
ZodSchema<any> ZodType<any>
> >
? { ? {
in: { in: {
@ -154,7 +155,7 @@ type InputTypeForm<R extends RouteConfig> = R['request'] extends RequestTypes
? {} ? {}
: R['request']['body']['content'][keyof R['request']['body']['content']] extends Record< : R['request']['body']['content'][keyof R['request']['body']['content']] extends Record<
'schema', 'schema',
ZodSchema<any> ZodType<any>
> >
? { ? {
in: { in: {
@ -181,7 +182,7 @@ type InputTypeCookie<R extends RouteConfig> = InputTypeBase<R, 'cookies', 'cooki
type ExtractContent<T> = T extends { type ExtractContent<T> = T extends {
[K in keyof T]: infer A [K in keyof T]: infer A
} }
? A extends Record<'schema', ZodSchema> ? A extends Record<'schema', ZodType>
? z.infer<A['schema']> ? z.infer<A['schema']>
: never : never
: never : never
@ -659,11 +660,14 @@ export class OpenAPIHono<
} }
case 'schema': case 'schema':
return this.openAPIRegistry.register(def.schema._def.openapi._internal.refId, def.schema) return this.openAPIRegistry.register(
getOpenApiMetadata(def.schema)._internal?.refId,
def.schema
)
case 'parameter': case 'parameter':
return this.openAPIRegistry.registerParameter( return this.openAPIRegistry.registerParameter(
def.schema._def.openapi._internal.refId, getOpenApiMetadata(def.schema)._internal?.refId,
def.schema def.schema
) )

View File

@ -1,5 +1,19 @@
# @hono/zod-validator # @hono/zod-validator
## 0.7.2
### Patch Changes
- [#1315](https://github.com/honojs/middleware/pull/1315) [`c6a16ab7aa8fba2403d4294e7673f96796020c65`](https://github.com/honojs/middleware/commit/c6a16ab7aa8fba2403d4294e7673f96796020c65) Thanks [@yusukebe](https://github.com/yusukebe)! - fix: support transform
## 0.7.1
### Patch Changes
- [#1302](https://github.com/honojs/middleware/pull/1302) [`9f6278f51c846a171a9baa6335fb8fbd9b42cb1c`](https://github.com/honojs/middleware/commit/9f6278f51c846a171a9baa6335fb8fbd9b42cb1c) Thanks [@kiki-kanri](https://github.com/kiki-kanri)! - correctly set the zod version to follow the official website doc
- [#1302](https://github.com/honojs/middleware/pull/1302) [`9f6278f51c846a171a9baa6335fb8fbd9b42cb1c`](https://github.com/honojs/middleware/commit/9f6278f51c846a171a9baa6335fb8fbd9b42cb1c) Thanks [@kiki-kanri](https://github.com/kiki-kanri)! - upgrade zod to v4 and import style to be more tree-shakeable in README.md
## 0.7.0 ## 0.7.0
### Minor Changes ### Minor Changes

View File

@ -7,7 +7,7 @@ The validator middleware using [Zod](https://zod.dev) for [Hono](https://honojs.
## Usage ## Usage
```ts ```ts
import { z } from 'zod' import * as z from 'zod'
import { zValidator } from '@hono/zod-validator' import { zValidator } from '@hono/zod-validator'
const schema = z.object({ const schema = z.object({

View File

@ -1,6 +1,6 @@
{ {
"name": "@hono/zod-validator", "name": "@hono/zod-validator",
"version": "0.7.0", "version": "0.7.2",
"license": "MIT", "license": "MIT",
"exports": { "exports": {
".": "./src/index.ts" ".": "./src/index.ts"

View File

@ -1,6 +1,6 @@
{ {
"name": "@hono/zod-validator", "name": "@hono/zod-validator",
"version": "0.7.0", "version": "0.7.2",
"description": "Validator middleware using Zod", "description": "Validator middleware using Zod",
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",
@ -42,7 +42,7 @@
"homepage": "https://github.com/honojs/middleware", "homepage": "https://github.com/honojs/middleware",
"peerDependencies": { "peerDependencies": {
"hono": ">=3.9.0", "hono": ">=3.9.0",
"zod": "^3.25.0" "zod": "^3.25.0 || ^4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@arethetypeswrong/cli": "^0.17.4", "@arethetypeswrong/cli": "^0.17.4",
@ -51,6 +51,6 @@
"tsup": "^8.4.0", "tsup": "^8.4.0",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"vitest": "^3.2.4", "vitest": "^3.2.4",
"zod": "~3.25.6" "zod": "^4.0.5"
} }
} }

View File

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import type { Context, Env, Input, MiddlewareHandler, TypedResponse, ValidationTargets } from 'hono' import type { Context, Env, Input, MiddlewareHandler, TypedResponse, ValidationTargets } from 'hono'
import { validator } from 'hono/validator' import { validator } from 'hono/validator'
import type * as v3 from 'zod' import type * as v3 from 'zod/v3'
import type { ZodSafeParseResult as v4ZodSafeParseResult } from 'zod/v4' import type { ZodSafeParseResult as v4ZodSafeParseResult } from 'zod/v4'
import type * as v4 from 'zod/v4/core' import type * as v4 from 'zod/v4/core'
@ -73,7 +73,7 @@ export const zValidator = <
if ((target === 'header' && '_def' in schema) || (target === 'header' && '_zod' in schema)) { if ((target === 'header' && '_def' in schema) || (target === 'header' && '_zod' in schema)) {
// create an object that maps lowercase schema keys to lowercase // create an object that maps lowercase schema keys to lowercase
// @ts-expect-error the schema is a Zod Schema // @ts-expect-error the schema is a Zod Schema
const schemaKeys = Object.keys(schema.shape) const schemaKeys = Object.keys('in' in schema ? schema.in.shape : schema.shape)
const caseInsensitiveKeymap = Object.fromEntries( const caseInsensitiveKeymap = Object.fromEntries(
schemaKeys.map((key) => [key.toLowerCase(), key]) schemaKeys.map((key) => [key.toLowerCase(), key])
) )

View File

@ -464,3 +464,39 @@ describe('With options + validationFunction', () => {
}) })
}) })
}) })
describe('Transform', () => {
const schema = z
.object({
'user-agent': z.string(),
})
.transform((data) => ({
userAgent: data['user-agent'],
}))
const zValidatorHeader = zValidator('header', schema)
const app = new Hono()
app.post('/test', zValidatorHeader, async (c) => {
const header = c.req.valid('header')
return c.json(header)
})
it('Should return 400 response', async () => {
const res = await app.request('/test', {
method: 'POST',
})
expect(res.status).toBe(400)
})
it('Should return 200 response', async () => {
const res = await app.request('/test', {
method: 'POST',
headers: {
'user-agent': 'my-agent',
},
})
expect(res.status).toBe(200)
})
})

View File

@ -99,14 +99,14 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@asteasolutions/zod-to-openapi@npm:^7.3.0": "@asteasolutions/zod-to-openapi@npm:^8.0.0":
version: 7.3.0 version: 8.0.0
resolution: "@asteasolutions/zod-to-openapi@npm:7.3.0" resolution: "@asteasolutions/zod-to-openapi@npm:8.0.0"
dependencies: dependencies:
openapi3-ts: "npm:^4.1.2" openapi3-ts: "npm:^4.1.2"
peerDependencies: peerDependencies:
zod: ^3.20.2 zod: ^4.0.0
checksum: 10c0/f0a68a89929cdeaa3e21d2027489689f982824d676a9332c680e119f60881dd39b571324b24ad4837fda49bf6fe7c3e2af2199268b281bf1aec923d7a7cbfc40 checksum: 10c0/b522d074832fb137dca724c8bd4bb134c7b4d4cad12c247ed3c864f993923b3475fc06580e6e1cbc4fd8641cd361679bbe1dd87c9bb42e142bc056d96d59fbc8
languageName: node languageName: node
linkType: hard linkType: hard
@ -2734,7 +2734,7 @@ __metadata:
resolution: "@hono/zod-openapi@workspace:packages/zod-openapi" resolution: "@hono/zod-openapi@workspace:packages/zod-openapi"
dependencies: dependencies:
"@arethetypeswrong/cli": "npm:^0.17.4" "@arethetypeswrong/cli": "npm:^0.17.4"
"@asteasolutions/zod-to-openapi": "npm:^7.3.0" "@asteasolutions/zod-to-openapi": "npm:^8.0.0"
"@hono/zod-validator": "workspace:^" "@hono/zod-validator": "workspace:^"
hono: "npm:^4.8.4" hono: "npm:^4.8.4"
openapi3-ts: "npm:^4.5.0" openapi3-ts: "npm:^4.5.0"
@ -2743,10 +2743,10 @@ __metadata:
typescript: "npm:^5.8.2" typescript: "npm:^5.8.2"
vitest: "npm:^3.2.4" vitest: "npm:^3.2.4"
yaml: "npm:^2.4.3" yaml: "npm:^2.4.3"
zod: "npm:^3.22.1" zod: "npm:^4.0.5"
peerDependencies: peerDependencies:
hono: ">=4.3.6" hono: ">=4.3.6"
zod: ">=3.0.0" zod: ^4.0.0
languageName: unknown languageName: unknown
linkType: soft linkType: soft
@ -2760,10 +2760,10 @@ __metadata:
tsup: "npm:^8.4.0" tsup: "npm:^8.4.0"
typescript: "npm:^5.8.2" typescript: "npm:^5.8.2"
vitest: "npm:^3.2.4" vitest: "npm:^3.2.4"
zod: "npm:~3.25.6" zod: "npm:^4.0.5"
peerDependencies: peerDependencies:
hono: ">=3.9.0" hono: ">=3.9.0"
zod: ^3.25.0 zod: ^3.25.0 || ^4.0.0
languageName: unknown languageName: unknown
linkType: soft linkType: soft
@ -16830,7 +16830,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"zod@npm:^3.20.2, zod@npm:^3.22.1, zod@npm:^3.22.3": "zod@npm:^3.20.2, zod@npm:^3.22.3":
version: 3.24.2 version: 3.24.2
resolution: "zod@npm:3.24.2" resolution: "zod@npm:3.24.2"
checksum: 10c0/c638c7220150847f13ad90635b3e7d0321b36cce36f3fc6050ed960689594c949c326dfe2c6fa87c14b126ee5d370ccdebd6efb304f41ef5557a4aaca2824565 checksum: 10c0/c638c7220150847f13ad90635b3e7d0321b36cce36f3fc6050ed960689594c949c326dfe2c6fa87c14b126ee5d370ccdebd6efb304f41ef5557a4aaca2824565
@ -16865,10 +16865,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"zod@npm:~3.25.6": "zod@npm:^4.0.5":
version: 3.25.36 version: 4.0.5
resolution: "zod@npm:3.25.36" resolution: "zod@npm:4.0.5"
checksum: 10c0/9ad8ca759d1a5d19c86d6e49a1bda817b3c0566ea0a8726d45994b9fcaac8d569db45a65218a7b4067ca18b07c5fc636a587a94cd05f76793174ace8b81742e3 checksum: 10c0/59449d731ca63849b6bcb14300aa6e2f042d440b3ed294b45c248519aec78780f85a5d1939a62c2ce82e9dc60afca77c8005e0a98d7517b0c2586d6c76940424
languageName: node languageName: node
linkType: hard linkType: hard