feat(standard-validator): Add standard schema validation (#887)
* feat(standard-validator): Add standard schema validation * feat(standard-validator): add changeset * feat(standard-validator): reintroduce type tests * feat(standard-validator): simplif tests * build(standard-validator): add gitlab pipeline * chore(standard-validator): remove redundant files * feat(standard-validator): cleanup tests, adjust comments * fix(standard-validator): adjust versions, fix doc * build: fix lockfile * feat(standard-validator): drop headers lower-casing, update readme * check types in test dir and add `tsc` to the test commandpull/961/head
parent
4e4e40cdbf
commit
f77d7ba2e2
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@hono/standard-validator': minor
|
||||
---
|
||||
|
||||
Initial implementation for Standard Schema support
|
|
@ -0,0 +1,25 @@
|
|||
name: ci-standard-validator
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'packages/standard-validator/**'
|
||||
pull_request:
|
||||
branches: ['*']
|
||||
paths:
|
||||
- 'packages/standard-validator/**'
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ./packages/standard-validator
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.x
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn build
|
||||
- run: yarn test
|
|
@ -41,6 +41,7 @@
|
|||
"build:ajv-validator": "yarn workspace @hono/ajv-validator build",
|
||||
"build:tsyringe": "yarn workspace @hono/tsyringe build",
|
||||
"build:cloudflare-access": "yarn workspace @hono/cloudflare-access build",
|
||||
"build:standard-validator": "yarn workspace @hono/standard-validator build",
|
||||
"build": "run-p 'build:*'",
|
||||
"lint": "eslint 'packages/**/*.{ts,tsx}'",
|
||||
"lint:fix": "eslint --fix 'packages/**/*.{ts,tsx}'",
|
||||
|
|
|
@ -48,4 +48,4 @@
|
|||
"engines": {
|
||||
"node": ">=18.14.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
# Standard Schema validator middleware for Hono
|
||||
|
||||
The validator middleware using [Standard Schema Spec](https://github.com/standard-schema/standard-schema) for [Hono](https://honojs.dev) applications.
|
||||
You can write a schema with any validation library supporting Standard Schema and validate the incoming values.
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
### Basic:
|
||||
```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}`,
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### Hook:
|
||||
```ts
|
||||
app.post(
|
||||
'/post',
|
||||
sValidator('json', schema, (result, c) => {
|
||||
if (!result.success) {
|
||||
return c.text('Invalid!', 400)
|
||||
}
|
||||
})
|
||||
//...
|
||||
)
|
||||
```
|
||||
|
||||
### Headers:
|
||||
Headers are internally transformed to lower-case in Hono. Hence, you will have to make them lower-cased in validation object.
|
||||
```ts
|
||||
import { object, string } from 'valibot'
|
||||
import { sValidator } from '@hono/standard-validator'
|
||||
|
||||
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
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
## Author
|
||||
|
||||
Rokas Muningis <https://github.com/muningis>
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"name": "@hono/standard-validator",
|
||||
"version": "0.0.0",
|
||||
"description": "Validator middleware using Standard Schema",
|
||||
"type": "module",
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.cjs"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "tsc --noEmit && vitest --run",
|
||||
"build": "tsup ./src/index.ts --format esm,cjs --dts",
|
||||
"publint": "publint",
|
||||
"prerelease": "yarn build && yarn test",
|
||||
"release": "yarn publish"
|
||||
},
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"registry": "https://registry.npmjs.org",
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/honojs/middleware.git"
|
||||
},
|
||||
"homepage": "https://github.com/honojs/middleware",
|
||||
"peerDependencies": {
|
||||
"@standard-schema/spec": "1.0.0",
|
||||
"hono": ">=3.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@standard-schema/spec": "1.0.0",
|
||||
"arktype": "^2.0.0-rc.26",
|
||||
"hono": "^4.0.10",
|
||||
"publint": "^0.2.7",
|
||||
"tsup": "^8.1.0",
|
||||
"typescript": "^5.7.3",
|
||||
"valibot": "^1.0.0-beta.9",
|
||||
"vitest": "^1.4.0",
|
||||
"zod": "^3.24.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
import type { Context, Env, Input, MiddlewareHandler, TypedResponse, ValidationTargets } from 'hono'
|
||||
import { validator } from 'hono/validator'
|
||||
import type { StandardSchemaV1 } from '@standard-schema/spec'
|
||||
|
||||
type HasUndefined<T> = undefined extends T ? true : false
|
||||
type TOrPromiseOfT<T> = T | Promise<T>
|
||||
|
||||
type Hook<
|
||||
T,
|
||||
E extends Env,
|
||||
P extends string,
|
||||
Target extends keyof ValidationTargets = keyof ValidationTargets,
|
||||
O = {}
|
||||
> = (
|
||||
result: (
|
||||
| { success: boolean; data: T }
|
||||
| { success: boolean; error: ReadonlyArray<StandardSchemaV1.Issue>; data: T }
|
||||
) & {
|
||||
target: Target
|
||||
},
|
||||
c: Context<E, P>
|
||||
) => TOrPromiseOfT<Response | void | TypedResponse<O>>
|
||||
|
||||
const isStandardSchemaValidator = (validator: unknown): validator is StandardSchemaV1 =>
|
||||
!!validator && typeof validator === 'object' && '~standard' in validator
|
||||
|
||||
const sValidator = <
|
||||
Schema extends StandardSchemaV1,
|
||||
Target extends keyof ValidationTargets,
|
||||
E extends Env,
|
||||
P extends string,
|
||||
In = StandardSchemaV1.InferInput<Schema>,
|
||||
Out = StandardSchemaV1.InferOutput<Schema>,
|
||||
I extends Input = {
|
||||
in: HasUndefined<In> extends true
|
||||
? {
|
||||
[K in Target]?: In extends ValidationTargets[K]
|
||||
? In
|
||||
: { [K2 in keyof In]?: ValidationTargets[K][K2] }
|
||||
}
|
||||
: {
|
||||
[K in Target]: In extends ValidationTargets[K]
|
||||
? In
|
||||
: { [K2 in keyof In]: ValidationTargets[K][K2] }
|
||||
}
|
||||
out: { [K in Target]: Out }
|
||||
},
|
||||
V extends I = I
|
||||
>(
|
||||
target: Target,
|
||||
schema: Schema,
|
||||
hook?: Hook<StandardSchemaV1.InferOutput<Schema>, E, P, Target>
|
||||
): MiddlewareHandler<E, P, V> =>
|
||||
// @ts-expect-error not typed well
|
||||
validator(target, async (value, c) => {
|
||||
const result = await schema['~standard'].validate(value)
|
||||
|
||||
if (hook) {
|
||||
const hookResult = await hook(
|
||||
!!result.issues
|
||||
? { data: value, error: result.issues, success: false, target }
|
||||
: { data: value, success: true, target },
|
||||
c
|
||||
)
|
||||
if (hookResult) {
|
||||
if (hookResult instanceof Response) {
|
||||
return hookResult
|
||||
}
|
||||
|
||||
if ('response' in hookResult) {
|
||||
return hookResult.response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.issues) {
|
||||
return c.json({ data: value, error: result.issues, success: false }, 400)
|
||||
}
|
||||
|
||||
return result.value as StandardSchemaV1.InferOutput<Schema>
|
||||
})
|
||||
|
||||
export type { Hook }
|
||||
export { sValidator }
|
|
@ -0,0 +1,36 @@
|
|||
import { type } from 'arktype'
|
||||
|
||||
const personJSONSchema = type({
|
||||
name: 'string',
|
||||
age: 'number',
|
||||
})
|
||||
|
||||
const postJSONSchema = type({
|
||||
id: 'number',
|
||||
title: 'string',
|
||||
})
|
||||
|
||||
const idJSONSchema = type({
|
||||
id: 'string',
|
||||
})
|
||||
|
||||
const queryNameSchema = type({
|
||||
'name?': 'string',
|
||||
})
|
||||
|
||||
const queryPaginationSchema = type({
|
||||
page: type('unknown').pipe((p) => Number(p)),
|
||||
})
|
||||
|
||||
const querySortSchema = type({
|
||||
order: "'asc'|'desc'",
|
||||
})
|
||||
|
||||
export {
|
||||
idJSONSchema,
|
||||
personJSONSchema,
|
||||
postJSONSchema,
|
||||
queryNameSchema,
|
||||
queryPaginationSchema,
|
||||
querySortSchema,
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import { object, string, number, optional, pipe, unknown, transform, picklist } from 'valibot'
|
||||
|
||||
const personJSONSchema = object({
|
||||
name: string(),
|
||||
age: number(),
|
||||
})
|
||||
|
||||
const postJSONSchema = object({
|
||||
id: number(),
|
||||
title: string(),
|
||||
})
|
||||
|
||||
const idJSONSchema = object({
|
||||
id: string(),
|
||||
})
|
||||
|
||||
const queryNameSchema = optional(
|
||||
object({
|
||||
name: optional(string()),
|
||||
})
|
||||
)
|
||||
|
||||
const queryPaginationSchema = object({
|
||||
page: pipe(unknown(), transform(Number)),
|
||||
})
|
||||
|
||||
const querySortSchema = object({
|
||||
order: picklist(['asc', 'desc']),
|
||||
})
|
||||
|
||||
export {
|
||||
idJSONSchema,
|
||||
personJSONSchema,
|
||||
postJSONSchema,
|
||||
queryNameSchema,
|
||||
queryPaginationSchema,
|
||||
querySortSchema,
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import { z } from 'zod'
|
||||
|
||||
const personJSONSchema = z.object({
|
||||
name: z.string(),
|
||||
age: z.number(),
|
||||
})
|
||||
|
||||
const postJSONSchema = z.object({
|
||||
id: z.number(),
|
||||
title: z.string(),
|
||||
})
|
||||
|
||||
const idJSONSchema = z.object({
|
||||
id: z.string(),
|
||||
})
|
||||
|
||||
const queryNameSchema = z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
})
|
||||
.optional()
|
||||
|
||||
const queryPaginationSchema = z.object({
|
||||
page: z.coerce.number(),
|
||||
})
|
||||
|
||||
const querySortSchema = z.object({
|
||||
order: z.enum(['asc', 'desc']),
|
||||
})
|
||||
|
||||
export {
|
||||
idJSONSchema,
|
||||
personJSONSchema,
|
||||
postJSONSchema,
|
||||
queryNameSchema,
|
||||
queryPaginationSchema,
|
||||
querySortSchema,
|
||||
}
|
|
@ -0,0 +1,356 @@
|
|||
import { Hono } from 'hono'
|
||||
import type { Equal, Expect, UnionToIntersection } from 'hono/utils/types'
|
||||
import { sValidator } from '../src'
|
||||
import { vi } from 'vitest'
|
||||
|
||||
import * as valibotSchemas from './__schemas__/valibot'
|
||||
import * as zodSchemas from './__schemas__/zod'
|
||||
import * as arktypeSchemas from './__schemas__/arktype'
|
||||
|
||||
type ExtractSchema<T> = T extends Hono<infer _, infer S> ? S : never
|
||||
type MergeDiscriminatedUnion<U> = UnionToIntersection<U> extends infer O
|
||||
? { [K in keyof O]: O[K] }
|
||||
: never
|
||||
|
||||
const libs = ['valibot', 'zod', 'arktype'] as const
|
||||
const schemasByLibrary = {
|
||||
valibot: valibotSchemas,
|
||||
zod: zodSchemas,
|
||||
arktype: arktypeSchemas,
|
||||
}
|
||||
|
||||
describe('Standard Schema Validation', () => {
|
||||
libs.forEach((lib) => {
|
||||
const schemas = schemasByLibrary[lib]
|
||||
describe(`Using ${lib} schemas for validation`, () => {
|
||||
describe('Basic', () => {
|
||||
const app = new Hono()
|
||||
const route = app.post(
|
||||
'/author',
|
||||
sValidator('json', schemas.personJSONSchema),
|
||||
sValidator('query', schemas.queryNameSchema),
|
||||
(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 verifyOutput = Expect<
|
||||
Equal<
|
||||
{
|
||||
success: boolean
|
||||
message: string
|
||||
queryName: string | undefined
|
||||
},
|
||||
MergeDiscriminatedUnion<Actual['/author']['$post']['output']>
|
||||
>
|
||||
>
|
||||
type verifyJSONInput = Expect<
|
||||
Equal<
|
||||
{
|
||||
name: string
|
||||
age: number
|
||||
},
|
||||
MergeDiscriminatedUnion<Actual['/author']['$post']['input']['json']>
|
||||
>
|
||||
>
|
||||
type verifyQueryInput = Expect<
|
||||
Equal<
|
||||
| {
|
||||
name?: string | undefined
|
||||
}
|
||||
| {
|
||||
name?: string | undefined
|
||||
}
|
||||
| {
|
||||
name?: string | undefined
|
||||
}
|
||||
| undefined,
|
||||
Actual['/author']['$post']['input']['query']
|
||||
>
|
||||
>
|
||||
|
||||
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 schema = schemas.queryPaginationSchema
|
||||
|
||||
const route = app.get('/page', sValidator('query', schema), (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[]
|
||||
}
|
||||
| {
|
||||
page: string | string[]
|
||||
}
|
||||
| {
|
||||
page: string | string[]
|
||||
}
|
||||
}
|
||||
output: {
|
||||
page: number
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type verifyInput = Expect<
|
||||
Equal<
|
||||
{ page: string | string[] },
|
||||
MergeDiscriminatedUnion<Actual['/page']['$get']['input']['query']>
|
||||
>
|
||||
>
|
||||
type verifyOutput = Expect<
|
||||
Equal<
|
||||
{
|
||||
page: number
|
||||
},
|
||||
MergeDiscriminatedUnion<Actual['/page']['$get']['output']>
|
||||
>
|
||||
>
|
||||
|
||||
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 = schemas.postJSONSchema
|
||||
|
||||
app.post(
|
||||
'/post',
|
||||
sValidator('json', schema, (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 Async Hook', () => {
|
||||
const app = new Hono()
|
||||
|
||||
const schema = schemas.postJSONSchema
|
||||
|
||||
app.post(
|
||||
'/post',
|
||||
sValidator('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 = schemas.idJSONSchema
|
||||
|
||||
const jsonHook = vi.fn()
|
||||
const paramHook = vi.fn()
|
||||
const queryHook = vi.fn()
|
||||
app.post(
|
||||
'/:id/post',
|
||||
sValidator('json', schema, jsonHook),
|
||||
sValidator('param', schema, paramHook),
|
||||
sValidator('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 schema = schemas.querySortSchema
|
||||
|
||||
const route = app.get('/', sValidator('query', schema), (c) => {
|
||||
const data = c.req.valid('query')
|
||||
return c.json(data)
|
||||
})
|
||||
|
||||
type Actual = ExtractSchema<typeof route>
|
||||
type verifyInput = Expect<
|
||||
Equal<
|
||||
{ order: 'asc' | 'desc' },
|
||||
MergeDiscriminatedUnion<Actual['/']['$get']['input']['query']>
|
||||
>
|
||||
>
|
||||
type verifyOutput = Expect<
|
||||
Equal<{ order: 'asc' | 'desc' }, MergeDiscriminatedUnion<Actual['/']['$get']['output']>>
|
||||
>
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./",
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"test/**/*.ts"
|
||||
],
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
/// <reference types="vitest" />
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: true,
|
||||
},
|
||||
})
|
|
@ -39,4 +39,4 @@
|
|||
"jest": "^29.7.0",
|
||||
"rimraf": "^5.0.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
91
yarn.lock
91
yarn.lock
|
@ -44,6 +44,22 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ark/schema@npm:0.26.0":
|
||||
version: 0.26.0
|
||||
resolution: "@ark/schema@npm:0.26.0"
|
||||
dependencies:
|
||||
"@ark/util": "npm:0.26.0"
|
||||
checksum: e038b73bd0d1a7556d5d7ab70382ddcc1ba35cafdddf81ea961a676d10bd2d642514c08b7a7cd9f4d99fb8e5ec3ae4e2ea8d3bbdf1d8b19d94149379f1c739f3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ark/util@npm:0.26.0":
|
||||
version: 0.26.0
|
||||
resolution: "@ark/util@npm:0.26.0"
|
||||
checksum: 60c54dca4556b1ccb6f4a3dc1e28beb09219ee3f01124916550c993f0508ac2142c9c967992ee16a71334e849c214234e6c3e4c2271dc104a893068a9cc33afc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@arktype/schema@npm:0.1.4-cjs":
|
||||
version: 0.1.4-cjs
|
||||
resolution: "@arktype/schema@npm:0.1.4-cjs"
|
||||
|
@ -2916,6 +2932,25 @@ __metadata:
|
|||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@hono/standard-validator@workspace:packages/standard-validator":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@hono/standard-validator@workspace:packages/standard-validator"
|
||||
dependencies:
|
||||
"@standard-schema/spec": "npm:1.0.0"
|
||||
arktype: "npm:^2.0.0-rc.26"
|
||||
hono: "npm:^4.0.10"
|
||||
publint: "npm:^0.2.7"
|
||||
tsup: "npm:^8.1.0"
|
||||
typescript: "npm:^5.7.3"
|
||||
valibot: "npm:^1.0.0-beta.9"
|
||||
vitest: "npm:^1.4.0"
|
||||
zod: "npm:^3.24.0"
|
||||
peerDependencies:
|
||||
"@standard-schema/spec": 1.0.0
|
||||
hono: ">=3.9.0"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@hono/swagger-editor@workspace:packages/swagger-editor":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@hono/swagger-editor@workspace:packages/swagger-editor"
|
||||
|
@ -4846,6 +4881,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@standard-schema/spec@npm:1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "@standard-schema/spec@npm:1.0.0"
|
||||
checksum: a1ab9a8bdc09b5b47aa8365d0e0ec40cc2df6437be02853696a0e377321653b0d3ac6f079a8c67d5ddbe9821025584b1fb71d9cc041a6666a96f1fadf2ece15f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@szmarczak/http-timer@npm:^1.1.2":
|
||||
version: 1.1.2
|
||||
resolution: "@szmarczak/http-timer@npm:1.1.2"
|
||||
|
@ -6522,6 +6564,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"arktype@npm:^2.0.0-rc.26":
|
||||
version: 2.0.0-rc.26
|
||||
resolution: "arktype@npm:2.0.0-rc.26"
|
||||
dependencies:
|
||||
"@ark/schema": "npm:0.26.0"
|
||||
"@ark/util": "npm:0.26.0"
|
||||
checksum: 190c4a82baec546bca704b26bffa73209a756ea4a0a2ade080a6d640a807e7eb1990a69e6507112f103151a37592cca84591569c90e607fb2865bd6ea2532b9a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"array-buffer-byte-length@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "array-buffer-byte-length@npm:1.0.0"
|
||||
|
@ -20770,6 +20822,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@npm:^5.7.3":
|
||||
version: 5.7.3
|
||||
resolution: "typescript@npm:5.7.3"
|
||||
bin:
|
||||
tsc: bin/tsc
|
||||
tsserver: bin/tsserver
|
||||
checksum: b7580d716cf1824736cc6e628ab4cd8b51877408ba2be0869d2866da35ef8366dd6ae9eb9d0851470a39be17cbd61df1126f9e211d8799d764ea7431d5435afa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@patch:typescript@npm%3A^4.7.4#optional!builtin<compat/typescript>":
|
||||
version: 4.9.5
|
||||
resolution: "typescript@patch:typescript@npm%3A4.9.5#optional!builtin<compat/typescript>::version=4.9.5&hash=289587"
|
||||
|
@ -20820,6 +20882,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript@patch:typescript@npm%3A^5.7.3#optional!builtin<compat/typescript>":
|
||||
version: 5.7.3
|
||||
resolution: "typescript@patch:typescript@npm%3A5.7.3#optional!builtin<compat/typescript>::version=5.7.3&hash=e012d7"
|
||||
bin:
|
||||
tsc: bin/tsc
|
||||
tsserver: bin/tsserver
|
||||
checksum: 3b56d6afa03d9f6172d0b9cdb10e6b1efc9abc1608efd7a3d2f38773d5d8cfb9bbc68dfb72f0a7de5e8db04fc847f4e4baeddcd5ad9c9feda072234f0d788896
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typia@npm:^7.3.0":
|
||||
version: 7.3.0
|
||||
resolution: "typia@npm:7.3.0"
|
||||
|
@ -21246,6 +21318,18 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"valibot@npm:^1.0.0-beta.9":
|
||||
version: 1.0.0-beta.9
|
||||
resolution: "valibot@npm:1.0.0-beta.9"
|
||||
peerDependencies:
|
||||
typescript: ">=5"
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: ecd20ec024f5f05985002b385f624d9218c839a54c23f3dbf3e193161207c049859d99069b257334756b3a07e2734e93456061600dd1101aec121828df3ab286
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"valid-url@npm:^1":
|
||||
version: 1.0.9
|
||||
resolution: "valid-url@npm:1.0.9"
|
||||
|
@ -22571,6 +22655,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zod@npm:^3.24.0":
|
||||
version: 3.24.1
|
||||
resolution: "zod@npm:3.24.1"
|
||||
checksum: 0223d21dbaa15d8928fe0da3b54696391d8e3e1e2d0283a1a070b5980a1dbba945ce631c2d1eccc088fdbad0f2dfa40155590bf83732d3ac4fcca2cc9237591b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"zwitch@npm:^2.0.0":
|
||||
version: 2.0.4
|
||||
resolution: "zwitch@npm:2.0.4"
|
||||
|
|
Loading…
Reference in New Issue