feat(zod-validator): support Zod v4 (#1173)
* feat(zod-validator): support Zod v4 * changeset * oops. using `any` * remove the not used value * [wip] support both v3 and v4 * update * avoid the type error on build * remove unnecessary `unknown` * fixed type and add test * avoid the type error * rename the test * don't use `schema instanceof ZodObject` * update README * use released `3.25.6` * changeset * use `zod` instead of `zod/v3` * don't update the peerDependencies * use both v3 and v4 types if Zod has v4 * fix ZodError * fixed * update lock file * update README * remove unnecessary castpull/1176/head
parent
deeeac9e1c
commit
a62b59f450
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@hono/zod-validator': minor
|
||||
---
|
||||
|
||||
feat: support Zod v4
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@hono/zod-openapi': patch
|
||||
---
|
||||
|
||||
fix: ignore the type error from Zod Validator
|
|
@ -45,4 +45,4 @@
|
|||
"vitest": "^3.0.8"
|
||||
},
|
||||
"packageManager": "yarn@4.0.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -501,7 +501,9 @@ export class OpenAPIHono<
|
|||
continue
|
||||
}
|
||||
if (isJSONContentType(mediaType)) {
|
||||
const validator = zValidator('json', schema, hook as any)
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore we can ignore the type error since Zod Validator's types are not used
|
||||
const validator = zValidator('json', schema, hook)
|
||||
if (route.request?.body?.required) {
|
||||
validators.push(validator)
|
||||
} else {
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
[](https://codecov.io/github/honojs/middleware)
|
||||
|
||||
The validator middleware using [Zod](https://zod.dev) for [Hono](https://honojs.dev) applications.
|
||||
You can write a schema with Zod and validate the incoming values.
|
||||
The validator middleware using [Zod](https://zod.dev) for [Hono](https://honojs.dev) applications. You can write a schema with Zod and validate the incoming values.
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
|
@ -49,6 +49,6 @@
|
|||
"tsup": "^8.4.0",
|
||||
"typescript": "^5.8.2",
|
||||
"vitest": "^3.0.8",
|
||||
"zod": "^3.22.4"
|
||||
"zod": "~3.25.6"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,18 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import type { Context, Env, Input, MiddlewareHandler, TypedResponse, ValidationTargets } from 'hono'
|
||||
import { validator } from 'hono/validator'
|
||||
import { ZodObject } from 'zod'
|
||||
import type { SafeParseReturnType, ZodError, ZodSchema, z } from 'zod'
|
||||
import type * as v3 from 'zod'
|
||||
import type { ZodSafeParseResult as v4ZodSafeParseResult } from 'zod/v4'
|
||||
import type * as v4 from 'zod/v4/core'
|
||||
|
||||
type ZodSchema = any extends v4.$ZodType ? v3.ZodType : v3.ZodType | v4.$ZodType
|
||||
type ZodError<T extends ZodSchema> = T extends v4.$ZodType ? v4.$ZodError : v3.ZodError
|
||||
type ZodSafeParseResult<T, T2, T3 extends ZodSchema> = T3 extends v4.$ZodType
|
||||
? v4ZodSafeParseResult<T>
|
||||
: v3.SafeParseReturnType<T, T2>
|
||||
type zInput<T> = T extends v3.ZodType ? v3.input<T> : T extends v4.$ZodType ? v4.input<T> : never
|
||||
type zOutput<T> = T extends v3.ZodType ? v3.output<T> : T extends v4.$ZodType ? v4.output<T> : never
|
||||
type zInfer<T> = T extends v3.ZodType ? v3.infer<T> : T extends v4.$ZodType ? v4.infer<T> : never
|
||||
|
||||
export type Hook<
|
||||
T,
|
||||
|
@ -9,8 +20,9 @@ export type Hook<
|
|||
P extends string,
|
||||
Target extends keyof ValidationTargets = keyof ValidationTargets,
|
||||
O = {},
|
||||
Schema extends ZodSchema = any,
|
||||
> = (
|
||||
result: ({ success: true; data: T } | { success: false; error: ZodError; data: T }) & {
|
||||
result: ({ success: true; data: T } | { success: false; error: ZodError<Schema>; data: T }) & {
|
||||
target: Target
|
||||
},
|
||||
c: Context<E, P>
|
||||
|
@ -23,8 +35,8 @@ export const zValidator = <
|
|||
Target extends keyof ValidationTargets,
|
||||
E extends Env,
|
||||
P extends string,
|
||||
In = z.input<T>,
|
||||
Out = z.output<T>,
|
||||
In = zInput<T>,
|
||||
Out = zOutput<T>,
|
||||
I extends Input = {
|
||||
in: HasUndefined<In> extends true
|
||||
? {
|
||||
|
@ -40,16 +52,16 @@ export const zValidator = <
|
|||
out: { [K in Target]: Out }
|
||||
},
|
||||
V extends I = I,
|
||||
InferredValue = zInfer<T>,
|
||||
>(
|
||||
target: Target,
|
||||
schema: T,
|
||||
hook?: Hook<z.infer<T>, E, P, Target>,
|
||||
hook?: Hook<InferredValue, E, P, Target, {}, T>,
|
||||
options?: {
|
||||
validationFunction: (
|
||||
schema: T,
|
||||
value: ValidationTargets[Target]
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
) => SafeParseReturnType<any, any> | Promise<SafeParseReturnType<any, any>>
|
||||
) => ZodSafeParseResult<any, any, T> | Promise<ZodSafeParseResult<any, any, T>>
|
||||
}
|
||||
): MiddlewareHandler<E, P, V> =>
|
||||
// @ts-expect-error not typed well
|
||||
|
@ -58,8 +70,9 @@ export const zValidator = <
|
|||
|
||||
// in case where our `target` === `header`, Hono parses all of the headers into lowercase.
|
||||
// this might not match the Zod schema, so we want to make sure that we account for that when parsing the schema.
|
||||
if (target === 'header' && schema instanceof ZodObject) {
|
||||
if ((target === 'header' && '_def' in schema) || (target === 'header' && '_zod' in schema)) {
|
||||
// create an object that maps lowercase schema keys to lowercase
|
||||
// @ts-expect-error the schema is a Zod Schema
|
||||
const schemaKeys = Object.keys(schema.shape)
|
||||
const caseInsensitiveKeymap = Object.fromEntries(
|
||||
schemaKeys.map((key) => [key.toLowerCase(), key])
|
||||
|
@ -73,7 +86,8 @@ export const zValidator = <
|
|||
const result =
|
||||
options && options.validationFunction
|
||||
? await options.validationFunction(schema, validatorValue)
|
||||
: await schema.safeParseAsync(validatorValue)
|
||||
: // @ts-expect-error z4.$ZodType has safeParseAsync
|
||||
await schema.safeParseAsync(validatorValue)
|
||||
|
||||
if (hook) {
|
||||
const hookResult = await hook({ data: validatorValue, ...result, target }, c)
|
||||
|
@ -92,5 +106,5 @@ export const zValidator = <
|
|||
return c.json(result, 400)
|
||||
}
|
||||
|
||||
return result.data as z.infer<T>
|
||||
return result.data as zInfer<T>
|
||||
})
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Hono } from 'hono'
|
|||
import type { ContentfulStatusCode } from 'hono/utils/http-status'
|
||||
import type { Equal, Expect } from 'hono/utils/types'
|
||||
import { vi } from 'vitest'
|
||||
import { z } from 'zod'
|
||||
import { z } from 'zod/v3'
|
||||
import { zValidator } from '.'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
@ -163,6 +163,8 @@ describe('With Hook', () => {
|
|||
'/post',
|
||||
zValidator('json', schema, (result, c) => {
|
||||
if (!result.success) {
|
||||
type verify = Expect<Equal<number, typeof result.data.id>>
|
||||
type verify2 = Expect<Equal<z.ZodError, typeof result.error>>
|
||||
return c.text(`${result.data.id} is invalid!`, 400)
|
||||
}
|
||||
}),
|
|
@ -0,0 +1,466 @@
|
|||
import { Hono } from 'hono'
|
||||
import type { ContentfulStatusCode } from 'hono/utils/http-status'
|
||||
import type { Equal, Expect } from 'hono/utils/types'
|
||||
import { vi } from 'vitest'
|
||||
import type z4 from 'zod/v4'
|
||||
import { z } from 'zod/v4'
|
||||
import { zValidator } from '.'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type ExtractSchema<T> = T extends Hono<infer _, infer S> ? S : never
|
||||
|
||||
describe('Basic', () => {
|
||||
const app = new Hono()
|
||||
|
||||
const jsonSchema = z.object({
|
||||
name: z.string(),
|
||||
age: z.number(),
|
||||
})
|
||||
|
||||
const querySchema = z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
})
|
||||
.optional()
|
||||
|
||||
const route = app.post(
|
||||
'/author',
|
||||
zValidator('json', jsonSchema),
|
||||
zValidator('query', querySchema),
|
||||
(c) => {
|
||||
const data = c.req.valid('json')
|
||||
const query = c.req.valid('query')
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
message: `${data.name} is ${data.age}`,
|
||||
queryName: query?.name,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
type Actual = ExtractSchema<typeof route>
|
||||
type Expected = {
|
||||
'/author': {
|
||||
$post: {
|
||||
input: {
|
||||
json: {
|
||||
name: string
|
||||
age: number
|
||||
}
|
||||
} & {
|
||||
query?:
|
||||
| {
|
||||
name?: string | undefined
|
||||
}
|
||||
| undefined
|
||||
}
|
||||
output: {
|
||||
success: boolean
|
||||
message: string
|
||||
queryName: string | undefined
|
||||
}
|
||||
outputFormat: 'json'
|
||||
status: ContentfulStatusCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type verify = Expect<Equal<Expected, Actual>>
|
||||
|
||||
it('Should return 200 response', async () => {
|
||||
const req = new Request('http://localhost/author?name=Metallo', {
|
||||
body: JSON.stringify({
|
||||
name: 'Superman',
|
||||
age: 20,
|
||||
}),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
const res = await app.request(req)
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({
|
||||
success: true,
|
||||
message: 'Superman is 20',
|
||||
queryName: 'Metallo',
|
||||
})
|
||||
})
|
||||
|
||||
it('Should return 400 response', async () => {
|
||||
const req = new Request('http://localhost/author', {
|
||||
body: JSON.stringify({
|
||||
name: 'Superman',
|
||||
age: '20',
|
||||
}),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
})
|
||||
const res = await app.request(req)
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(400)
|
||||
const data = (await res.json()) as { success: boolean }
|
||||
expect(data['success']).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('coerce', () => {
|
||||
const app = new Hono()
|
||||
|
||||
const querySchema = z.object({
|
||||
page: z.coerce.number(),
|
||||
})
|
||||
|
||||
const route = app.get('/page', zValidator('query', querySchema), (c) => {
|
||||
const { page } = c.req.valid('query')
|
||||
return c.json({ page })
|
||||
})
|
||||
|
||||
type Actual = ExtractSchema<typeof route>
|
||||
type Expected = {
|
||||
'/page': {
|
||||
$get: {
|
||||
input: {
|
||||
query: {
|
||||
page: string | string[]
|
||||
}
|
||||
}
|
||||
output: {
|
||||
page: number
|
||||
}
|
||||
outputFormat: 'json'
|
||||
status: ContentfulStatusCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type verify = Expect<Equal<Expected, Actual>>
|
||||
|
||||
it('Should return 200 response', async () => {
|
||||
const res = await app.request('/page?page=123')
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({
|
||||
page: 123,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('With Hook', () => {
|
||||
const app = new Hono()
|
||||
|
||||
const schema = z.object({
|
||||
id: z.number(),
|
||||
title: z.string(),
|
||||
})
|
||||
|
||||
app.post(
|
||||
'/post',
|
||||
zValidator('json', schema, (result, c) => {
|
||||
if (!result.success) {
|
||||
type verify = Expect<Equal<number, typeof result.data.id>>
|
||||
type verify2 = Expect<Equal<z4.core.$ZodError, typeof result.error>>
|
||||
return c.text(`${result.data.id} is invalid!`, 400)
|
||||
}
|
||||
}),
|
||||
(c) => {
|
||||
const data = c.req.valid('json')
|
||||
return c.text(`${data.id} is valid!`)
|
||||
}
|
||||
)
|
||||
|
||||
it('Should return 200 response', async () => {
|
||||
const req = new Request('http://localhost/post', {
|
||||
body: JSON.stringify({
|
||||
id: 123,
|
||||
title: 'Hello',
|
||||
}),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
const res = await app.request(req)
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('123 is valid!')
|
||||
})
|
||||
|
||||
it('Should return 400 response', async () => {
|
||||
const req = new Request('http://localhost/post', {
|
||||
body: JSON.stringify({
|
||||
id: '123',
|
||||
title: 'Hello',
|
||||
}),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
const res = await app.request(req)
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(400)
|
||||
expect(await res.text()).toBe('123 is invalid!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('With Async Hook', () => {
|
||||
const app = new Hono()
|
||||
|
||||
const schema = z.object({
|
||||
id: z.number(),
|
||||
title: z.string(),
|
||||
})
|
||||
|
||||
app.post(
|
||||
'/post',
|
||||
zValidator('json', schema, async (result, c) => {
|
||||
if (!result.success) {
|
||||
return c.text(`${result.data.id} is invalid!`, 400)
|
||||
}
|
||||
}),
|
||||
(c) => {
|
||||
const data = c.req.valid('json')
|
||||
return c.text(`${data.id} is valid!`)
|
||||
}
|
||||
)
|
||||
|
||||
it('Should return 200 response', async () => {
|
||||
const req = new Request('http://localhost/post', {
|
||||
body: JSON.stringify({
|
||||
id: 123,
|
||||
title: 'Hello',
|
||||
}),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
const res = await app.request(req)
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('123 is valid!')
|
||||
})
|
||||
|
||||
it('Should return 400 response', async () => {
|
||||
const req = new Request('http://localhost/post', {
|
||||
body: JSON.stringify({
|
||||
id: '123',
|
||||
title: 'Hello',
|
||||
}),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
const res = await app.request(req)
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(400)
|
||||
expect(await res.text()).toBe('123 is invalid!')
|
||||
})
|
||||
})
|
||||
|
||||
describe('With target', () => {
|
||||
it('should call hook for correctly validated target', async () => {
|
||||
const app = new Hono()
|
||||
|
||||
const schema = z.object({
|
||||
id: z.string(),
|
||||
})
|
||||
|
||||
const jsonHook = vi.fn()
|
||||
const paramHook = vi.fn()
|
||||
const queryHook = vi.fn()
|
||||
app.post(
|
||||
'/:id/post',
|
||||
zValidator('json', schema, jsonHook),
|
||||
zValidator('param', schema, paramHook),
|
||||
zValidator('query', schema, queryHook),
|
||||
(c) => {
|
||||
return c.text('ok')
|
||||
}
|
||||
)
|
||||
|
||||
const req = new Request('http://localhost/1/post?id=2', {
|
||||
body: JSON.stringify({
|
||||
id: '3',
|
||||
}),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
const res = await app.request(req)
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.text()).toBe('ok')
|
||||
expect(paramHook).toHaveBeenCalledWith(
|
||||
{ data: { id: '1' }, success: true, target: 'param' },
|
||||
expect.anything()
|
||||
)
|
||||
expect(queryHook).toHaveBeenCalledWith(
|
||||
{ data: { id: '2' }, success: true, target: 'query' },
|
||||
expect.anything()
|
||||
)
|
||||
expect(jsonHook).toHaveBeenCalledWith(
|
||||
{ data: { id: '3' }, success: true, target: 'json' },
|
||||
expect.anything()
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Only Types', () => {
|
||||
it('Should return correct enum types for query', () => {
|
||||
const app = new Hono()
|
||||
|
||||
const querySchema = z.object({
|
||||
order: z.enum(['asc', 'desc']),
|
||||
})
|
||||
|
||||
const route = app.get('/', zValidator('query', querySchema), (c) => {
|
||||
const data = c.req.valid('query')
|
||||
return c.json(data)
|
||||
})
|
||||
|
||||
type Actual = ExtractSchema<typeof route>
|
||||
type Expected = {
|
||||
'/': {
|
||||
$get: {
|
||||
input: {
|
||||
query: {
|
||||
order: 'asc' | 'desc'
|
||||
}
|
||||
}
|
||||
output: {
|
||||
order: 'asc' | 'desc'
|
||||
}
|
||||
outputFormat: 'json'
|
||||
status: ContentfulStatusCode
|
||||
}
|
||||
}
|
||||
}
|
||||
type verify = Expect<Equal<Expected, Actual>>
|
||||
})
|
||||
})
|
||||
|
||||
describe('Case-Insensitive Headers', () => {
|
||||
it('Should ignore the case for headers in the Zod schema and return 200', () => {
|
||||
const app = new Hono()
|
||||
const headerSchema = z.object({
|
||||
'Content-Type': z.string(),
|
||||
ApiKey: z.string(),
|
||||
onlylowercase: z.string(),
|
||||
ONLYUPPERCASE: z.string(),
|
||||
})
|
||||
|
||||
const route = app.get('/', zValidator('header', headerSchema), (c) => {
|
||||
const headers = c.req.valid('header')
|
||||
return c.json(headers)
|
||||
})
|
||||
|
||||
type Actual = ExtractSchema<typeof route>
|
||||
type Expected = {
|
||||
'/': {
|
||||
$get: {
|
||||
input: {
|
||||
header: z.infer<typeof headerSchema>
|
||||
}
|
||||
output: z.infer<typeof headerSchema>
|
||||
outputFormat: 'json'
|
||||
status: ContentfulStatusCode
|
||||
}
|
||||
}
|
||||
}
|
||||
type verify = Expect<Equal<Expected, Actual>>
|
||||
})
|
||||
})
|
||||
|
||||
describe('With options + validationFunction', () => {
|
||||
const app = new Hono()
|
||||
const jsonSchema = z.object({
|
||||
name: z.string(),
|
||||
age: z.number(),
|
||||
})
|
||||
|
||||
const route = app
|
||||
.post('/', zValidator('json', jsonSchema), (c) => {
|
||||
const data = c.req.valid('json')
|
||||
return c.json({
|
||||
success: true,
|
||||
data,
|
||||
})
|
||||
})
|
||||
.post(
|
||||
'/extended',
|
||||
zValidator('json', jsonSchema, undefined, {
|
||||
validationFunction: async (schema, value) => {
|
||||
const result = schema.safeParse(value)
|
||||
return await schema.passthrough().safeParseAsync(value)
|
||||
},
|
||||
}),
|
||||
(c) => {
|
||||
const data = c.req.valid('json')
|
||||
return c.json({
|
||||
success: true,
|
||||
data,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
it('Should be ok due to passthrough schema', async () => {
|
||||
const req = new Request('http://localhost/extended', {
|
||||
body: JSON.stringify({
|
||||
name: 'Superman',
|
||||
age: 20,
|
||||
length: 170,
|
||||
weight: 55,
|
||||
}),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
const res = await app.request(req)
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({
|
||||
success: true,
|
||||
data: {
|
||||
name: 'Superman',
|
||||
age: 20,
|
||||
length: 170,
|
||||
weight: 55,
|
||||
},
|
||||
})
|
||||
})
|
||||
it('Should be ok due to required schema', async () => {
|
||||
const req = new Request('http://localhost', {
|
||||
body: JSON.stringify({
|
||||
name: 'Superman',
|
||||
age: 20,
|
||||
}),
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
const res = await app.request(req)
|
||||
|
||||
expect(res).not.toBeNull()
|
||||
expect(res.status).toBe(200)
|
||||
expect(await res.json()).toEqual({
|
||||
success: true,
|
||||
data: {
|
||||
name: 'Superman',
|
||||
age: 20,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
11
yarn.lock
11
yarn.lock
|
@ -2402,7 +2402,7 @@ __metadata:
|
|||
tsup: "npm:^8.4.0"
|
||||
typescript: "npm:^5.8.2"
|
||||
vitest: "npm:^3.0.8"
|
||||
zod: "npm:^3.22.4"
|
||||
zod: "npm:~3.25.6"
|
||||
peerDependencies:
|
||||
hono: ">=3.9.0"
|
||||
zod: ^3.19.1
|
||||
|
@ -15153,7 +15153,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod@npm:^3.20.2, zod@npm:^3.22.1, zod@npm:^3.22.3, zod@npm:^3.22.4":
|
||||
"zod@npm:^3.20.2, zod@npm:^3.22.1, zod@npm:^3.22.3":
|
||||
version: 3.24.2
|
||||
resolution: "zod@npm:3.24.2"
|
||||
checksum: c638c7220150847f13ad90635b3e7d0321b36cce36f3fc6050ed960689594c949c326dfe2c6fa87c14b126ee5d370ccdebd6efb304f41ef5557a4aaca2824565
|
||||
|
@ -15174,6 +15174,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod@npm:~3.25.6":
|
||||
version: 3.25.6
|
||||
resolution: "zod@npm:3.25.6"
|
||||
checksum: b7be69c76baa317e55496d9b4aab0090534bc2fd5ec65f8099ffb9fb460b4885dff60b1bed62cdd23e8a710e2cc82798c99f69283031b2bd8cb3cdbb9ce08399
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zwitch@npm:^2.0.0":
|
||||
version: 2.0.4
|
||||
resolution: "zwitch@npm:2.0.4"
|
||||
|
|
Loading…
Reference in New Issue