typed

A blazing fast, dependency free, 1kb runtime type-checking library written entirely in typescript.

Usage no npm install needed!

<script type="module">
  import typed from 'https://cdn.skypack.dev/typed';
</script>

README

GitHub Workflow Status Codecov GitHub issues GitHub npm npm bundle size

Typed

A blazing fast, dependency free, 1kb runtime type-checking library written entirely in typescript, meant to be used with it.

There are dozens of validation libraries out there, so why create yet another one? Well, I tried almost every library out there and there is only one that I really like called superstruct (which is awesome) that provides almost everything that I need, but still, I wanted to create my own. The others are simply bloated or don't provide proper typescript support. So that's where typed comes in.

typed is all about function composition. Each function is "standalone" and provides a safe way to validate data, you don't need a special kind of function to execute a schema against some value. All functions return a special type which is either Success or Failure. If ok is true then data is available and fully typed and if not, errors is available with a message and path from where the error originated.

Install

npm install typed

Usage

You can check out the API docs here

import * as T from "typed";

const postType = T.object({
  id: T.number,
  title: T.string,
  tags: T.array(T.string),
});

const result = postType(/* some JSON data */);

Custom types

typed has a handy function called map which allows you to create a new type from an existing one. That means that you are ensured that the new type will always start from a valid base type.

import * as T from "typed";
import isEmail from "is-email";

const emailType = T.map(T.string, (value) =>
  isEmail(value)
    ? T.ok(value)
    : T.err(T.toError(`Expecting value to be a valid 'email'.`)),
);

// Later in your code
const userType = T.object({
  id: T.number,
  name: T.string,
  email: emailType,
});

You can compose more complex types like so:

import * as T from "typed";

const rangeType = (floor: number, ceiling: number) =>
  T.map(T.number, (value) => {
    if (value < floor || value > ceiling) {
      return T.err(
        T.toError(
          `Expecting value to be between '${floor}' and '${ceiling}'. Got '${value}'.`,
        ),
      );
    }
    return T.ok(value);
  });

const geoType = T.object({
  lat: rangeType(-90, 90),
  lng: rangeType(-180, 180),
});

const latLngType = T.tuple(T.asNumber, T.asNumber);

// It will take a string as an input and it will return `{ lat: number, lng: number }` as an output.
const geoStrType = T.map(T.string, (value) => {
  const result = latLngType(value.split(","));
  return result.ok ? T.ok(result.data) : result;
});

const result = geoStrType("-39.031153, -67.576394"); // => { lat: -39.031153, lng: -67.576394 }

Inference

Sometimes you may want to infer the type of a validator function. You can do so with the Infer type.

import * as T from "typed";

const postType = T.object({
  id: T.number,
  title: T.string,
  tags: T.array(T.string),
});

type Post = T.Infer<typeof postType>; // => { id: number, title: string, tags: string[] }

Benchmarks

Benchmarks were done on a Mac Mini 2020 with M1 chip and 8GB of RAM. You can clone this repo and run npm run benchmark to see the results. A somewhat complex dataset from SpaceX was used to benchmark the library.

zod x 5,149 ops/sec ±0.16% (101 runs sampled)
superstruct x 6,424 ops/sec ±0.32% (100 runs sampled)
typed x 98,049 ops/sec ±0.24% (99 runs sampled)
Fastest is typed