Check types at runtime
import {assert, is, object, number, string, array} from 'cito'
type Post = typeof Post.infer
const Post = object({
id: number,
title: string,
link: string.optional,
author: object({id: number}),
tags: array(string)
})
const data = {
id: 42,
title: 'Hello world',
author: {id: 42},
tags: ['hello']
}
// Throws if data is invalid, data is type as Post
assert(data, Post)
// data is typed as Post in the block, does not throw
if (is(data, Post)) {
// use data
}
// Assert and name the input data
const post = Post(data)
Object types can be declared using a class, which has the following advantages over plain objects:
Optional properties
It is possible to mark properties as optional which will be reflected in the inferred type:
import {object, string} from 'cito'
const WithOptional = object(
class {
required = string
present = string.optional
optional? = string.optional
}
)
type WithOptional = typeof WithOptional.infer
// ^? {required: string, present: string | undefined, optional?: string | undefined}
Recursive types
Declare recursive types with full type inference without having to resort to manual type definition.
Note: recursive types cannot be JIT compiled
import {any, object} from 'cito'
type Node = typeof Node.infer
const Node = object(
class Node {
next = object(Node)
prev = object(Node)
data = any
}
)
type List = typeof List.infer
const List = object({
head: Node.optional
})
Cito exports the following public members.
const string: Type<string>
const number: Type<number>
const bigint: Type<bigint>
const boolean: Type<boolean>
const symbol: Type<symbol>
const date: Type<Date>
const any: Type<any>
const func: Type<Function>
function literal<T>(value: T): Type<T>
function nullable<T>(inner: Type<T>): Type<T | null>
function optional<T>(inner: Type<T>): Type<T | undefined>
function instance<T>(constructor: new (...args: any[]) => T): Type<T>
function tuple<T>(...types: T): Type<Tuple<T>>
function record<T>(inner: Type<T>): Type<Record<string, T>>
function object<T>(definition: T): Type<Object<T>>
function union<T>(...types: T): Type<Union<T>>
function array<T>(inner: Type<T>): Type<Array<T>>
function enums<T>(types: Record<string, T>): Type<T>
function lazy<T>(fn: () => Type<T>): Type<T>
function assert<T>(value: unknown, type: Type<T>): asserts value is T
function is<T>(value: unknown, type: Type<T>): value is T
function compile<T>(type: T): {check: (value) => value is T}
type Infer<T> = T extends Type<infer U> ? U : ...
A Type has the following api:
interface Validator<T> {
(value: any): value is T
}
interface Type<T> {
// This special property allows you to infer the type `T`
infer: T
// Call the instance to type check a value and return it if valid
(value: any): T
// A type that includes `T` and `null`
nullable: Type<T | null>
// A type that includes `T` and `undefined`
optional: Type<T | undefined>
// Returns a new type that narrows `T` to a subtype `E`
narrow<E extends T>(): Type<E>
// Create a new instance of type `T`, does not type check at runtime
new (value: any): T
// Returns a boolean indicating whether input is of type `T`
is(input: any): input is T
// Returns a new type which validates both `T` and `E`
and<E>(validate: Validator<E>): Type<T & E>
// Returns a new type which validates either `T` or `E`
or<E>(validate: Validator<E>): Type<T | E>
}
Custom types can be created using the type function:
import {type} from 'cito'
const regex = type((value): value is RegExp => value instanceof RegExp)
regex(/(.*?)/g)
regex('this will throw')
Making the comparison with superstruct and zod:
The benchmark code is adapted from typed, which is MIT License Copyright (c) 2022 CodBot
benchmark time (avg) (min max) p75 p99 p995
--------------------------------------------------- -----------------------------
zod 68.25 s/iter (59.4 s 1.19 ms) 66.5 s 173.3 s 203.6 s
superstruct 252.25 s/iter (217.9 s 779.4 s) 239.4 s 513.5 s 548.3 s
cito 21.1 s/iter (19.4 s 283.9 s) 20.2 s 33.8 s 44.3 s
summary for validate
cito
3.23x faster than zod
11.96x faster than superstruct
cito jit 381.57 ns/iter (349.56 ns 723.29 ns) 378.48 ns 720.73 ns 723.29 ns
typebox jit 767.31 ns/iter (726.39 ns 1.11 s) 768.16 ns 1.11 s 1.11 s
summary for validate jit
cito jit
2.01x faster than typebox jit
The data used in the benchmarks is from SpaceX's GraphQL API.