🛡️ type-assurance

npm bundle size

Super lightweight TypeScript library to perform type checks at runtime.

  • Does only one thing: runtime type checking
  • Provides type guards and type assertions
  • Intuitive schema definition based on language literals
  • 100% test coverage

Example

Let's start with a simple example:

import { assert } from "type-assurance";

const userSchema = {
  name: String;
  email: String;
};

const user = await api.getUser(23);

// Throw if types don't match
assert(user, userSchema);

// The compiler now knows the type ...
console.log(user.name);

We can also infer a static TypeScript type from the runtime schema:

import { TypeFromSchema } from "type-assurance";

type User = TypeFromSchema<typeof userSchema>;

This yields the same type as if we had written it by hand:

type User = {
  name: string;
  email: string;
};

Sometimes all you need is a quick inline type-check. Use is instead of assert which acts as a type-guard:

import { is } from "type-assurance";

if (
  is(data, {
    body: {
      posts: [
        {
          id: Number,
          title: String,
        },
      ],
    },
  })
) {
  // We can now be certain that the given data
  // has the expected shape and the compiler
  // now knows about the types...
}

Installation

You can install the package from npm:

npm install type-assurance

🦕 The package is also published under https://deno.land/x/typeassurance for use with Deno.

Usage

The package exports the following main functions:

  • is(value, schema) – returns true if the value matches the schema. The return type is a type predicate that narrows the type down to the one described by the schema.
  • assert(value, schema) – throws a TypeError if the value does not match the schema. It is an assertion function that narrows the type down to the one described by the schema.
  • typeGuard(schema) – returns a function that can be used as type guard for the given schema.

The schema that is passed to both these functions looks like an example or blueprint of the expected type. It can be a deeply nested object or just a single value.

Primitives

You can use String, Number or Boolean as schema for primitive types:

is(42, Number); // ✅ true
is("42", Number); // ❌ false
is("foo", String); // ✅ true

This isn't particularly helpful on its own, but it makes sense when used to describe more complex shapes.

Objects

To specify objects, use an object where each value is again a schema:

import { typeGuard } from "type-assurance";

const isUser = typeGuard({
  post: {
    id: Number,
    author: {
      name: String,
    },
  },
});

const data = JSON.parse(`{
  "post": {
    "id": 23,
    "author": {
      "name": "Felix"
    }
  }
}`);

if (isUser(data)) {
  // data.post.author.name is a string ✅
}

Instances

You can use constructor functions as schema to check if an object is an instance of that type:

assert(data, {
  now: Date,
  pattern: RegExp,
});

This also works for classes:

class Person {
  constructor(public name: string) {
  }
}

const data: unknown = {
  user: new Person("Felix");
}

if (is(data, { user: Person })) {
  // data.user is a Person
};

NOTE: In the very unlikely case that you want to test for String, Number or Boolean objects, you have to use a function instead:

is("foo", String); // ✅ true
is(new String("foo"), String); // ❌ false
is(new String("foo"), (s) => s instanceof String); // ✅ true

Arrays

To specify an array of a certain type, wrap that type in an array:

assert(data, [Number]);
// data is of type number[]

Tuples

For tuples, provide an array with more than one item:

assert(value, [Number, String, Boolean]);
// value[0] is a number
// value[1] is a string
// value[2] is a boolean

Custom type guards

You can also use type guard functions as (nested) schemas:

/**
 * Type guard to test if the given value is SomeFancyType.
 */
function isSomeFancyType(v: unknown): v is SomeFancyType {
  // custom check goes here ...
}

assert(data, {
  id: Number,
  body: isSomeFancyType,
});

Literals

Finally, specific strings, numbers or boolean values are treated as literal types:

const personSchema = {
  type: "person",
  name: String
} as const;

const dogSchema = {
  type: "dog",
  name: String,
  breed: String,
}

if (is(data, dog)) {
  console.log(data.name "is a cute", data.breed);
}

Union Types

The package exports a union function to check if a value is of either of the given types:

import { union } from "type-assurance";

const taskSchema = {
  title: String,
  status: union("pending", "active", "done"),
};

Optional properties

You can use the optional helper for properties that aren't required:

import { optional } from "type-assurance";

const personSchema = {
  name: String
  address: optional(String)
};

Note The optional(schema) helper is short for union(schema, undefined);

Infer the type from a schema

You can convert a runtime schema into a static type:

import { TypeFromSchema } from "type-assurance";

const PersonSchema = {
  name: String;
  age: Number;
}

type Person = TypeFromSchema<typeof PersonSchema>;

License

MIT