TypeScript-shaped runtime validation
Types that make it to runtime.
Use a plain object as the schema. Validate unknown data and get the inferred TypeScript type—without a builder DSL.
import {
string, number, parse, type Infer,
} from 'litetype'
// One object is the runtime schema
const User = {
name: string.min(1),
age: number.min(0),
'email?': string.email(),
}
// Same object gives the TypeScript type
type User = Infer<typeof User>
// unknown in, typed User out
const user = parse(User, input)
The model
Three things, separate
value
const User = { name: string }type
type User = Infer<typeof User>check
parse(User, input)Measured, not claimed
Performance and size
- 5.00 kB
- browser bundle, min + gzip
- 90.47M
- flat valid objects / second
- 341
- TypeScript instantiations
- 0
- runtime dependencies
Schemas are data
Composition
Spread is extend. Property access is pick. Rest destructuring is omit. Optional keys remain optional because the language already knows how objects compose.
import { date, number, string } from 'litetype'
const User = { name: string, age: number, 'email?': string }
const Timestamps = { createdAt: date, 'deletedAt?': date }
const Post = { title: string, ...Timestamps }
const Public = { name: User.name, 'email?': User['email?'] }
const { age: _, ...NoAge } = User
Less library language
Compared with Zod
import { z } from 'zod'
const User = z.object({
name: z.string().min(1),
email: z.string().email().optional(),
})
type User = z.infer<typeof User>
User.parse(input)
import { type Infer, parse, string } from 'litetype'
const User = {
name: string.min(1),
'email?': string.email(),
}
type User = Infer<typeof User>
parse(User, input)
One standard edge
Ecosystem
Keep schemas bare inside your code. Wrap once with Standard Schema where another tool needs it.
import { standard, string } from 'litetype'
const User = { name: string.min(1) }
t.procedure
.input(standard(User))
.query(({ input }) => input)