feat(typia-validator): support typia http module (#888)

* feature(typia-validator): support typia http module

* feature(typia-validator): add change-set & update README
pull/889/head
miyaji255 2024-12-15 18:54:40 +09:00 committed by GitHub
parent f58f47ebd1
commit c63470e491
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 860 additions and 22 deletions

View File

@ -0,0 +1,35 @@
---
'@hono/typia-validator': minor
---
Enables handling of `number`, `boolean`, and `bigint` types in query parameters and headers.
```diff
- import { typiaValidator } from '@hono/typia-validator';
+ import { typiaValidator } from '@hono/typia-validator/http';
import { Hono } from 'hono';
import typia, { type tags } from 'typia';
interface Schema {
- pages: `${number}`[];
+ pages: (number & tags.Type<'uint32'>)[];
}
const app = new Hono()
.get(
'/books',
typiaValidator(
- typia.createValidate<Schema>(),
+ typia.http.createValidateQuery<Schema>(),
async (result, c) => {
if (!result.success)
return c.text('Invalid query parameters', 400);
- return { pages: result.data.pages.map(Number) };
}
),
async c => {
const { pages } = c.req.valid('query'); // { pages: number[] }
//...
}
)
```

View File

@ -0,0 +1 @@
/test-generated

View File

@ -4,6 +4,12 @@ The validator middleware using [Typia](https://typia.io/docs/) for [Hono](https:
## Usage
You can use [Basic Validation](#basic-validation) and [HTTP Module Validation](#http-module-validation) with Typia Validator.
### Basic Validation
Use only the standard validator in typia.
```ts
import typia, { tags } from 'typia'
import { typiaValidator } from '@hono/typia-validator'
@ -38,6 +44,60 @@ app.post(
)
```
### HTTP Module Validation
[Typia's HTTP module](https://typia.io/docs/misc/#http-module) allows you to validate query and header parameters with automatic type parsing.
- **Supported Parsers:** The HTTP module currently supports "query" and "header" validations.
- **Parsing Differences:** The parsing mechanism differs slightly from Hono's native parsers. Ensure that your type definitions comply with Typia's HTTP module restrictions.
```typescript
import { Hono } from 'hono'
import typia from 'typia'
import { typiaValidator } from '@hono/typia-validator/http'
interface Author {
name: string
age: number & tags.Type<'uint32'> & tags.Minimum<20> & tags.ExclusiveMaximum<100>
}
interface IQuery {
limit?: number
enforce: boolean
values?: string[]
atomic: string | null
indexes: number[]
}
interface IHeaders {
'x-category': 'x' | 'y' | 'z'
'x-memo'?: string
'x-name'?: string
'x-values': number[]
'x-flags': boolean[]
'x-descriptions': string[]
}
const app = new Hono()
const validate = typia.createValidate<Author>()
const validateQuery = typia.http.createValidateQuery<IQuery>()
const validateHeaders = typia.http.createValidateHeaders<IHeaders>()
app.get('/items',
typiaValidator('json', validate),
typiaValidator('query', validateQuery),
typiaValidator('header', validateHeaders),
(c) => {
const query = c.req.valid('query')
const headers = c.req.valid('header')
return c.json({
success: true,
query,
headers,
})
}
)
```
## Author
Patryk Dwórznik <https://github.com/dworznik>

View File

@ -5,11 +5,25 @@
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"types": "dist/esm/index.d.ts",
"exports": {
".": {
"default": "./dist/cjs/index.js",
"require": "./dist/cjs/index.js",
"import": "./dist/esm/index.js",
"types": "./dist/esm/index.d.ts"
},
"./http": {
"default": "./dist/cjs/http.js",
"require": "./dist/cjs/http.js",
"import": "./dist/esm/http.js",
"types": "./dist/esm/http.d.ts"
}
},
"files": [
"dist"
],
"scripts": {
"generate-test": "rimraf test-generated && typia generate --input test --output test-generated --project tsconfig.json",
"generate-test": "rimraf test-generated && typia generate --input test --output test-generated --project tsconfig.json && node scripts/add-ts-ignore.cjs",
"test": "npm run generate-test && jest",
"build:cjs": "tsc -p tsconfig.cjs.json",
"build:esm": "tsc -p tsconfig.esm.json",
@ -29,12 +43,13 @@
"homepage": "https://github.com/honojs/middleware",
"peerDependencies": {
"hono": ">=3.9.0",
"typia": "^6.1.0"
"typia": "^7.0.0"
},
"devDependencies": {
"hono": "^3.11.7",
"jest": "^29.7.0",
"rimraf": "^5.0.5",
"typia": "^5.0.4"
"typescript": "^5.4.0",
"typia": "^7.3.0"
}
}

View File

@ -0,0 +1,27 @@
// @ts-check
const fs = require('node:fs')
const path = require('node:path')
// https://github.com/samchon/typia/issues/1432
// typia generated files have some type errors
const generatedFiles = fs
.readdirSync(path.resolve(__dirname, '../test-generated'))
.map((file) => path.resolve(__dirname, '../test-generated', file))
for (const file of generatedFiles) {
const content = fs.readFileSync(file, 'utf8')
const lines = content.split('\n')
const distLines = []
for (const line of lines) {
if (
line.includes('._httpHeaderReadNumber(') ||
line.includes('._httpHeaderReadBigint(') ||
line.includes('._httpHeaderReadBoolean(')
)
distLines.push(`// @ts-ignore`)
distLines.push(line)
}
fs.writeFileSync(file, distLines.join('\n'))
}

View File

@ -0,0 +1,181 @@
import type { Context, MiddlewareHandler, Env, ValidationTargets, TypedResponse } from 'hono'
import { validator } from 'hono/validator'
import type { IReadableURLSearchParams, IValidation } from 'typia'
interface IFailure<T> {
success: false
errors: IValidation.IError[]
data: T
}
type BaseType<T> = T extends string
? string
: T extends number
? number
: T extends boolean
? boolean
: T extends symbol
? symbol
: T extends bigint
? bigint
: T
type Parsed<T> = T extends Record<string | number, any>
? {
[K in keyof T]-?: T[K] extends (infer U)[]
? (BaseType<U> | null | undefined)[] | undefined
: BaseType<T[K]> | null | undefined
}
: BaseType<T>
export type QueryValidation<O extends Record<string | number, any> = any> = (
input: string | URLSearchParams
) => IValidation<O>
export type QueryOutputType<T> = T extends QueryValidation<infer O> ? O : never
type QueryStringify<T> = T extends Record<string | number, any>
? {
// Suppress to split union types
[K in keyof T]: [T[K]] extends [bigint | number | boolean]
? `${T[K]}`
: T[K] extends (infer U)[]
? [U] extends [bigint | number | boolean]
? `${U}`[]
: T[K]
: T[K]
}
: T
export type HeaderValidation<O extends Record<string | number, any> = any> = (
input: Record<string, string | string[] | undefined>
) => IValidation<O>
export type HeaderOutputType<T> = T extends HeaderValidation<infer O> ? O : never
type HeaderStringify<T> = T extends Record<string | number, any>
? {
// Suppress to split union types
[K in keyof T]: [T[K]] extends [bigint | number | boolean]
? `${T[K]}`
: T[K] extends (infer U)[]
? [U] extends [bigint | number | boolean]
? `${U}`
: U
: T[K]
}
: T
export type HttpHook<T, E extends Env, P extends string, O = {}> = (
result: IValidation.ISuccess<T> | IFailure<Parsed<T>>,
c: Context<E, P>
) => Response | Promise<Response> | void | Promise<Response | void> | TypedResponse<O>
export type Hook<T, E extends Env, P extends string, O = {}> = (
result: IValidation.ISuccess<T> | IFailure<T>,
c: Context<E, P>
) => Response | Promise<Response> | void | Promise<Response | void> | TypedResponse<O>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Validation<O = any> = (input: unknown) => IValidation<O>
export type OutputType<T> = T extends Validation<infer O> ? O : never
interface TypiaValidator {
<
T extends QueryValidation,
O extends QueryOutputType<T>,
E extends Env,
P extends string,
V extends { in: { query: QueryStringify<O> }; out: { query: O } } = {
in: { query: QueryStringify<O> }
out: { query: O }
}
>(
target: 'query',
validate: T,
hook?: HttpHook<O, E, P>
): MiddlewareHandler<E, P, V>
<
T extends HeaderValidation,
O extends HeaderOutputType<T>,
E extends Env,
P extends string,
V extends { in: { header: HeaderStringify<O> }; out: { header: O } } = {
in: { header: HeaderStringify<O> }
out: { header: O }
}
>(
target: 'header',
validate: T,
hook?: HttpHook<O, E, P>
): MiddlewareHandler<E, P, V>
<
T extends Validation,
O extends OutputType<T>,
Target extends Exclude<keyof ValidationTargets, 'query' | 'queries' | 'header'>,
E extends Env,
P extends string,
V extends {
in: { [K in Target]: O }
out: { [K in Target]: O }
} = {
in: { [K in Target]: O }
out: { [K in Target]: O }
}
>(
target: Target,
validate: T,
hook?: Hook<O, E, P>
): MiddlewareHandler<E, P, V>
}
export const typiaValidator: TypiaValidator = (
target: keyof ValidationTargets,
validate: (input: any) => IValidation<any>,
hook?: Hook<any, any, any>
): MiddlewareHandler => {
if (target === 'query' || target === 'header')
return async (c, next) => {
let value: any
if (target === 'query') {
const queries = c.req.queries()
value = {
get: (key) => queries[key]?.[0] ?? null,
getAll: (key) => queries[key] ?? [],
} satisfies IReadableURLSearchParams
} else {
value = Object.create(null)
for (const [key, headerValue] of c.req.raw.headers) value[key.toLowerCase()] = headerValue
if (c.req.raw.headers.has('Set-Cookie'))
value['Set-Cookie'] = c.req.raw.headers.getSetCookie()
}
const result = validate(value)
if (hook) {
const res = await hook(result as never, c)
if (res instanceof Response) return res
}
if (!result.success) {
return c.json({ success: false, error: result.errors }, 400)
}
c.req.addValidatedData(target, result.data)
await next()
}
return validator(target, async (value, c) => {
const result = validate(value)
if (hook) {
const hookResult = await hook({ ...result, data: value }, c)
if (hookResult) {
if (hookResult instanceof Response || hookResult instanceof Promise) {
return hookResult
}
if ('response' in hookResult) {
return hookResult.response
}
}
}
if (!result.success) {
return c.json({ success: false, error: result.errors }, 400)
}
return result.data
})
}

View File

@ -29,11 +29,11 @@ export const typiaValidator = <
validate: T,
hook?: Hook<O, E, P>
): MiddlewareHandler<E, P, V> =>
validator(target, (value, c) => {
validator(target, async (value, c) => {
const result = validate(value)
if (hook) {
const hookResult = hook({ ...result, data: value }, c)
const hookResult = await hook({ ...result, data: value }, c)
if (hookResult) {
if (hookResult instanceof Response || hookResult instanceof Promise) {
return hookResult

View File

@ -0,0 +1,424 @@
import { Hono } from 'hono'
import type { Equal, Expect } from 'hono/utils/types'
import typia, { tags } from 'typia'
import { typiaValidator } from '../src/http'
// 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()
interface JsonSchema {
name: string
age: number
}
const validateJson = typia.createValidate<JsonSchema>()
interface QuerySchema {
name?: string
}
const validateQuery = typia.http.createValidateQuery<QuerySchema>()
interface HeaderSchema {
'x-Category': ('x' | 'y' | 'z')[]
}
const validateHeader = typia.http.createValidateHeaders<HeaderSchema>()
const route = app.post(
'/author',
typiaValidator('json', validateJson),
typiaValidator('query', validateQuery),
typiaValidator('header', validateHeader),
(c) => {
const data = c.req.valid('json')
const query = c.req.valid('query')
const header = c.req.valid('header')
return c.json({
success: true,
message: `${data.name} is ${data.age}`,
queryName: query?.name,
headerCategory: header['x-Category'],
})
}
)
type Actual = ExtractSchema<typeof route>
type Expected = {
'/author': {
$post: {
input: {
json: {
name: string
age: number
}
} & {
query: {
name?: string | undefined
}
} & {
header: {
'x-Category': 'x' | 'y' | 'z'
}
}
output: {
success: boolean
message: string
queryName: string | undefined
headerCategory: ('x' | 'y' | 'z')[]
}
}
}
}
// 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',
'X-Category': 'x, y, z',
},
})
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',
headerCategory: ['x', 'y', 'z'],
})
})
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('transform', () => {
const app = new Hono()
interface QuerySchema {
page: (0 | 1 | 2)[]
}
const validateQuery = typia.http.createValidateQuery<QuerySchema>()
interface HeaderSchema {
'X-Categories': (0 | 1 | 2)[]
}
const validateHeader = typia.http.createValidateHeaders<HeaderSchema>()
const route = app.get(
'/page',
typiaValidator('query', validateQuery),
typiaValidator('header', validateHeader),
(c) => {
const { page } = c.req.valid('query')
const { 'X-Categories': categories } = c.req.valid('header')
return c.json({ page, categories })
}
)
type Actual = ExtractSchema<typeof route>
type Expected = {
'/page': {
$get: {
input: {
query: {
page: `${0 | 1 | 2}`[]
}
} & {
header: {
'X-Categories': `${0 | 1 | 2}`
}
}
output: {
page: (0 | 1 | 2)[]
categories: (0 | 1 | 2)[]
}
}
}
}
// 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=1', {
headers: {
'X-Categories': '0, 1, 2',
},
})
expect(res).not.toBeNull()
expect(res.status).toBe(200)
expect(await res.json()).toEqual({
page: [1],
categories: [0, 1, 2],
})
})
})
describe('With Hook', () => {
const app = new Hono()
interface Schema {
id: number
title: string
}
const validateSchema = typia.createValidate<Schema>()
app.post(
'/post',
typiaValidator('json', validateSchema, (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()
interface Schema {
id: number & tags.Maximum<999>
title: string
}
const validateSchema = typia.createValidate<Schema>()
const validateQuery = typia.http.createValidateQuery<Schema>()
const validateHeader = typia.http.createValidateHeaders<Schema>()
app.post(
'/post',
typiaValidator('json', validateSchema, async (result, c) => {
if (!result.success) {
return c.text(`${result.data.id} is invalid!`, 400)
}
}),
typiaValidator('query', validateQuery, async (result, c) => {
if (!result.success) {
return c.text(`${result.data.id} is invalid!`, 400)
}
}),
typiaValidator('header', validateHeader, async (result, c) => {
if (!result.success) {
return c.text(`${result.data.id} is invalid!`, 400)
}
}),
(c) => {
const data = c.req.valid('json')
const query = c.req.valid('query')
const header = c.req.valid('header')
return c.json({ data, query, header })
}
)
it('Should return 200 response', async () => {
const req = new Request('http://localhost/post?id=125&title=My', {
body: JSON.stringify({
id: 123,
title: 'Hello',
}),
method: 'POST',
headers: {
'Content-Type': 'application/json',
Id: '124',
Title: 'World',
},
})
const res = await app.request(req)
expect(res).not.toBeNull()
expect(res.status).toBe(200)
expect(await res.json()).toEqual({
data: { id: 123, title: 'Hello' },
query: { id: 125, title: 'My' },
header: { id: 124, title: 'World' },
})
})
it('Should return 400 response', async () => {
let req = new Request('http://localhost/post', {
body: JSON.stringify({
id: '123',
title: 'Hello',
}),
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
})
let res = await app.request(req)
expect(res).not.toBeNull()
expect(res.status).toBe(400)
expect(await res.text()).toBe('123 is invalid!')
req = new Request('http://localhost/post?id=1000&title=My', {
body: JSON.stringify({
id: 123,
title: 'Hello',
}),
method: 'POST',
headers: {
'Content-Type': 'application/json',
Id: '124',
Title: 'World',
},
})
res = await app.request(req)
expect(res).not.toBeNull()
expect(res.status).toBe(400)
expect(await res.text()).toBe('1000 is invalid!')
req = new Request('http://localhost/post?id=125&title=My', {
body: JSON.stringify({
id: 123,
title: 'Hello',
}),
method: 'POST',
headers: {
'Content-Type': 'application/json',
Id: '1000',
Title: 'World',
},
})
res = await app.request(req)
expect(res).not.toBeNull()
expect(res.status).toBe(400)
expect(await res.text()).toBe('1000 is invalid!')
})
})
describe('With target', () => {
it('should call hook for correctly validated target', async () => {
const app = new Hono()
interface Schema {
id: number
}
const validateSchema = typia.createValidate<Schema>()
const validateQuery = typia.http.createValidateQuery<Schema>()
const validateHeader = typia.http.createValidateHeaders<Schema>()
const jsonHook = jest.fn()
const headerHook = jest.fn()
const queryHook = jest.fn()
app.post(
'/post',
typiaValidator('json', validateSchema, jsonHook),
typiaValidator('query', validateQuery, queryHook),
typiaValidator('header', validateHeader, headerHook),
(c) => {
return c.text('ok')
}
)
const req = new Request('http://localhost/post?id=2', {
body: JSON.stringify({
id: 3,
}),
method: 'POST',
headers: {
'Content-Type': 'application/json',
id: '1',
},
})
const res = await app.request(req)
expect(res).not.toBeNull()
expect(res.status).toBe(200)
expect(await res.text()).toBe('ok')
expect(headerHook).toHaveBeenCalledWith({ data: { id: 1 }, success: true }, expect.anything())
expect(queryHook).toHaveBeenCalledWith({ data: { id: 2 }, success: true }, expect.anything())
expect(jsonHook).toHaveBeenCalledWith({ data: { id: 3 }, success: true }, expect.anything())
})
})
describe('Case-Insensitive Headers', () => {
it('Should ignore the case for headers in the Zod schema and return 200', () => {
const app = new Hono()
interface HeaderSchema {
'Content-Type': string
ApiKey: string
onlylowercase: string
ONLYUPPERCASE: string
}
const validateHeader = typia.http.createValidateHeaders<HeaderSchema>()
const route = app.get('/', typiaValidator('header', validateHeader), (c) => {
const headers = c.req.valid('header')
return c.json(headers)
})
type Actual = ExtractSchema<typeof route>
type Expected = {
'/': {
$get: {
input: {
header: HeaderSchema
}
output: HeaderSchema
}
}
}
type verify = Expect<Equal<Expected, Actual>>
})
})

View File

@ -144,3 +144,60 @@ describe('With Hook', () => {
expect(await res.text()).toBe('123 is invalid!')
})
})
describe('With Async Hook', () => {
const app = new Hono()
interface Schema {
id: number & tags.Maximum<999>
title: string
}
const validateSchema = typia.createValidate<Schema>()
app.post(
'/post',
typiaValidator('json', validateSchema, 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!`, 200)
}
)
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!')
})
})

View File

@ -2900,10 +2900,11 @@ __metadata:
hono: "npm:^3.11.7"
jest: "npm:^29.7.0"
rimraf: "npm:^5.0.5"
typia: "npm:^5.0.4"
typescript: "npm:^5.4.0"
typia: "npm:^7.3.0"
peerDependencies:
hono: ">=3.9.0"
typia: ^6.1.0
typia: ^7.0.0
languageName: unknown
linkType: soft
@ -4589,6 +4590,13 @@ __metadata:
languageName: node
linkType: hard
"@samchon/openapi@npm:^2.1.2":
version: 2.1.2
resolution: "@samchon/openapi@npm:2.1.2"
checksum: 3f4cdcaad90b67e90104282c950fc86cd67981bf1b5a15b08a8521358687dc5afcd24540b41b0e0f4807ba0a3ed75008777a91ae12abfde6f809a601f8515881
languageName: node
linkType: hard
"@samverschueren/stream-to-observable@npm:^0.3.0, @samverschueren/stream-to-observable@npm:^0.3.1":
version: 0.3.1
resolution: "@samverschueren/stream-to-observable@npm:0.3.1"
@ -16415,6 +16423,13 @@ __metadata:
languageName: node
linkType: hard
"package-manager-detector@npm:^0.2.0":
version: 0.2.7
resolution: "package-manager-detector@npm:0.2.7"
checksum: 0ea19abf11e251c3bffe2698450a4a2a5658528b88151943eff01c5f4b9bdc848abc96588c1fe5f01618887cf1154d6e72eb28edb263e46178397aa6ebd58ff0
languageName: node
linkType: hard
"parent-module@npm:^1.0.0":
version: 1.0.1
resolution: "parent-module@npm:1.0.1"
@ -20054,6 +20069,16 @@ __metadata:
languageName: node
linkType: hard
"typescript@npm:^5.4.0":
version: 5.7.2
resolution: "typescript@npm:5.7.2"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: a873118b5201b2ef332127ef5c63fb9d9c155e6fdbe211cbd9d8e65877283797cca76546bad742eea36ed7efbe3424a30376818f79c7318512064e8625d61622
languageName: node
linkType: hard
"typescript@npm:^5.4.4":
version: 5.4.4
resolution: "typescript@npm:5.4.4"
@ -20094,6 +20119,16 @@ __metadata:
languageName: node
linkType: hard
"typescript@patch:typescript@npm%3A^5.4.0#optional!builtin<compat/typescript>":
version: 5.7.2
resolution: "typescript@patch:typescript@npm%3A5.7.2#optional!builtin<compat/typescript>::version=5.7.2&hash=e012d7"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: c891ccf04008bc1305ba34053db951f8a4584b4a1bf2f68fd972c4a354df3dc5e62c8bfed4f6ac2d12e5b3b1c49af312c83a651048f818cd5b4949d17baacd79
languageName: node
linkType: hard
"typescript@patch:typescript@npm%3A^5.4.4#optional!builtin<compat/typescript>":
version: 5.4.4
resolution: "typescript@patch:typescript@npm%3A5.4.4#optional!builtin<compat/typescript>::version=5.4.4&hash=e012d7"
@ -20114,19 +20149,22 @@ __metadata:
languageName: node
linkType: hard
"typia@npm:^5.0.4":
version: 5.3.5
resolution: "typia@npm:5.3.5"
"typia@npm:^7.3.0":
version: 7.3.0
resolution: "typia@npm:7.3.0"
dependencies:
"@samchon/openapi": "npm:^2.1.2"
commander: "npm:^10.0.0"
comment-json: "npm:^4.2.3"
inquirer: "npm:^8.2.5"
package-manager-detector: "npm:^0.2.0"
randexp: "npm:^0.5.3"
peerDependencies:
typescript: ">=4.8.0 <5.4.0"
"@samchon/openapi": ">=2.1.2 <3.0.0"
typescript: ">=4.8.0 <5.8.0"
bin:
typia: lib/executable/typia.js
checksum: 2707ccaa83b35647380adb361c90d27fe88d1a6652e14a37f261d4a661d92aad88bc15af42744b80ea7f1bf5ede5c93f1b55be16bff5071570f8f0f14d5a5c5b
checksum: 5ef80aa41238ef082c3a73feaa6d59039a0298f3a93d52a725a22b7aa3a2437cc015f9c122a4e43188deb2b0422b8d3bb64f2e010be2f4dad64ee3bfc91d0717
languageName: node
linkType: hard