npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

cito

v0.2.0

Published

Check types at runtime

Downloads

136

Readme

cito

Check types at runtime

  • Small: 1/4 of superstruct, 1/15 of zod
  • Support recursive type declarations
  • Descriptive error messages
  • Full TypeScript support
  • JIT compilation for fast validation checks
  • Only loose assertions: does not warn on extra keys

Usage

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)

Defining objects using a class

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
    })

Api

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: T): Type<keyof 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')

Benchmarks

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.