Documentation

TypeScript runtime validation with literals

Runtime schemas that look like TypeScript and compose like JavaScript. A schema is either a leaf such as string, or a plain object whose values are schemas.

Terminal
npm i litetype
TypeScript
import { string, number, type Infer, parse } from 'litetype'

const User = {
  name: string.min(1),
  age: number.min(0),
  'email?': string.email(),
}

type User = Infer<typeof User>
const user = parse(User, input)

Choose a validation verb

JobUse
Return data or throwparse(schema, input)
Return a result unionsafeParse(schema, input)
Narrow an unknown valuecheck(schema, input)
Predicate for a hot loopcompile(schema)

Schemas compile automatically on first use. Call compile only when a hot loop should avoid the cache lookup.

Optional key ≠ undefined value

TypeScript
import { string } from 'litetype'

const A = { 'bio?': string }          // bio may be omitted
const B = { bio: string.undefinable() } // bio is required, value may be undefined

The trailing ? mirrors a TypeScript optional property. It belongs to the key. Nullability belongs to the value.

Choose what happens to extra keys

All three validate name. They differ only in what happens to properties the schema does not declare.

TypeScript
import { parse, strict, string, strip } from 'litetype'

const User = { name: string }
const input = { name: 'Ada', role: 'admin' }

parse(User, input)
// → input itself: { name: 'Ada', role: 'admin' }

parse(strip(User), input)
// → a new object: { name: 'Ada' }

parse(strict(User), input)
// → throws because role is not declared

Normalize form values before validation

HTML forms return strings. Convert those raw values first, then validate the converted value.

TypeScript
import { coerce, parse, preprocess, string } from 'litetype'

// Treat an empty text field as "not provided"
const OptionalBio = preprocess(
  value => value === '' ? undefined : value,
  string.undefinable(),
)
parse(OptionalBio, '')      // → undefined
parse(OptionalBio, 'Hello') // → 'Hello'

// Number inputs also arrive as strings
parse(coerce.number(), '42') // → 42 (a number)