Type Guard Kit

ci codecov deno doc

A TypeScript module to help construct type assertion functions and type guards.

You can use the included functions to create types and their assertion functions and guards from a single source definition, and use type assertion functions and guards to catch unexpected, incompatible types entering your application due to interface definitions being outdated without versioning protection from breaking changes, interfaces not being adhered to, or data being corrupted.

Setup

Deno module URL

https://deno.land/x/typeguardkit/mod.ts

npm installation

npm install typeguardkit

Usage

Example

import {
  _boolean,
  _null,
  _number,
  _string,
  arrayOf,
  ObjectAsserter,
  objectAsserter,
  unionOf,
} from "./mod.ts";
// import from "typeguardkit" if using npm

// types/book.ts

const asserter = objectAsserter("Book", {
  isbn: _string,
  title: _string,
  authors: arrayOf(_string),
  pageCount: _number,
  rating: unionOf([_number, _null]),
  isRecommended: _boolean,
});

export type Book = ReturnType<typeof asserter>;

export const _Book: ObjectAsserter<Book> = asserter;

// api/get_book.ts

export async function getBook(isbn: string): Promise<Book> {
  const response = await fetch(`/api/books/${isbn}`);
  const responseBody = await response.json();

  // If `responseBody` is a `Book`, `_Book` returns `responseBody` as `Book`.
  // Otherwise, `_Book` throws a `TypeAssertionError`, including the optional
  // value name `"responseBody"` in its `message`.

  return _Book(responseBody, "responseBody");
}

// local_storage/reading_list_isbns.ts

export const readingListIsbnsKey = "reading-list-isbns";

export function getReadingListIsbns(): string[] {
  const json = localStorage.getItem(readingListIsbnsKey);
  return json ? arrayOf(_string)(JSON.parse(json)) : [];
}

export function setReadingListIsbns(isbns: readonly string[]): void {
  localStorage.setItem(readingListIsbnsKey, JSON.stringify(isbns));
}

Asserters

An Asserter is a type assertion function.

interface Asserter<Type> {
  (value: unknown, name?: string): Type;
  readonly typeName: string;
}

If value is of Type, the Asserter should return value as Type. Otherwise, the Asserter should throw a TypeAssertionError.

The module includes the _boolean, _number, and _string Asserters.

It also includes the _null and _undefined Asserters, which can be used to create union type Asserters with the unionOf function.

As well as wrapping Asserters in the assertIs or is functions, you can use them like this:

import { _string } from "./mod.ts";
// import from "typeguardkit" if using npm

function handleUnknown(x: unknown) {
  let y;

  try {
    y = _string(x, "x");
  } catch {
    return;
  }

  // `y` has now been initialized and inferred to be of type `string`, so can be
  // passed to `handleString`. `x` is still of `unknown` type though, so cannot.

  handleString(y);
}

function handleString(y: string) {}

You can create your own Asserters with the typeAsserter function. For example, the _string Asserter was created like this:

import { typeAsserter } from "./mod.ts";
// import from "typeguardkit" if using npm

export const _string = typeAsserter(
  "string",
  (value): value is string => typeof value === "string",
);

Prefixing Asserter names with an underscore _ helps to avoid name conflicts and shadowing.

Assertion signature wrapper

The assertIs function wraps an Asserter<Type> with an assertion signature so the value passed in can be narrowed to Type. If the Asserter throws an error, it will bubble up. Otherwise, assertIs will not return a value, but after calling it, the value passed in will be narrowed to Type.

You can use assertIs like this:

import { _string, assertIs } from "./mod.ts";
// import from "typeguardkit" if using npm

function handleUnknown(x: unknown) {
  try {
    assertIs(_string, x, "x");
  } catch {
    return;
  }

  // `x` has now been narrowed to type `string`, so can be passed to
  // `handleString`.

  handleString(x);
}

function handleString(x: string) {}

Predicate signature wrapper

The is function wraps an Asserter<Type> with a predicate signature, creating a type guard, so the value passed in can be narrowed to Type. If the Asserter throws an error, is will catch it and return false. Otherwise, is will return true.

You can use is like this:

import { _string, is } from "./mod.ts";
// import from "typeguardkit" if using npm

function handleUnknown(x: unknown) {
  if (is(_string, x)) {
    // `x` has now been narrowed to type `string`, so can be passed to
    // `handleString`.

    handleString(x);
  }
}

function handleString(x: string) {}

Enum Asserters

You can create an Asserter for an enum type using the enumAsserter function like this:

import { enumAsserter, is } from "./mod.ts";
// import from "typeguardkit" if using npm

// types/direction.ts

export enum Direction {
  Up,
  Right,
  Down,
  Left,
}

export const _Direction = enumAsserter("Direction", Direction);

// elsewhere.ts

function handleUnknown(x: unknown) {
  if (is(_Direction, x)) {
    // `x` has now been narrowed to type `Direction`, so can be passed to
    // `handleDirection`.

    handleDirection(x);
  }
}

function handleDirection(x: Direction) {}

Literal union Asserters

You can create an Asserter for a literal union type using the literalUnionAsserter function like this:

import { Asserter, is, literalUnionAsserter } from "./mod.ts";
// import from "typeguardkit" if using npm

// types/direction.ts

export const directions = ["up", "right", "down", "left"] as const;

const asserter = literalUnionAsserter("Direction", directions);

export type Direction = typeof directions[number];

export const _Direction: Asserter<Direction> = asserter;

// elsewhere.ts

function handleUnknown(x: unknown) {
  if (is(_Direction, x)) {
    // `x` has now been narrowed to type `Direction`, so can be passed to
    // `handleDirection`.

    handleDirection(x);
  }
}

function handleDirection(x: Direction) {}

Other Asserter factory functions

The following functions create new Asserters from existing ones.

unionOf

unionOf returns an Asserter for the union of the Types of the provided Asserters.

You can use unionOf like this:

import { _null, _string, is, unionOf } from "./mod.ts";
// import from "typeguardkit" if using npm

const _stringOrNull = unionOf([_string, _null]);

function handleUnknown(x: unknown) {
  if (is(_stringOrNull, x)) {
    // `x` has now been narrowed to type `string | null`, so can be passed to
    // `handleStringOrNull`.

    handleStringOrNull(x);
  }
}

function handleStringOrNull(x: string | null) {}

arrayOf

arrayOf returns an Asserter<Array<Type>>, created using the provided Asserter<Type>.

You can use arrayOf like this:

import { _string, arrayOf, is } from "./mod.ts";
// import from "typeguardkit" if using npm

const _arrayOfString = arrayOf(_string);

function handleUnknown(x: unknown) {
  if (is(_arrayOfString, x)) {
    // `x` has now been narrowed to type `Array<string>`, so can be passed to
    // `handleArrayOfString`.

    handleArrayOfString(x);
  }
}

function handleArrayOfString(x: string[]) {}

ObjectAsserters

An ObjectAsserter is an Asserter for the object type defined by its propertyAsserters.

import { Asserter } from "./mod.ts";
// import from "typeguardkit" if using npm

interface ObjectAsserter<Type extends Record<string, unknown>>
  extends Asserter<Type> {
  readonly propertyAsserters: Readonly<
    { [Key in keyof Type]-?: Asserter<Type[Key]> }
  >;
}

You can create an ObjectAsserter with the objectAsserter function and use it like this:

import { _number, _string, is, ObjectAsserter, objectAsserter } from "./mod.ts";
// import from "typeguardkit" if using npm

// types/user.ts

const asserter = objectAsserter("User", {
  name: _string,
  age: _number,
});

export type User = ReturnType<typeof asserter>;

export const _User: ObjectAsserter<User> = asserter;

// elsewhere.ts

function handleUnknown(x: unknown) {
  if (is(_User, x)) {
    // `x` has now been narrowed to type `User`, so can be passed to
    // `handleUser`.

    handleUser(x);
  }
}

function handleUser(x: User) {}

Other ObjectAsserter factory functions

The following functions create new ObjectAsserters from existing ones.

objectIntersectionOf

objectIntersectionOf returns an ObjectAsserter for the intersection of the Types of the provided ObjectAsserters.

You can use objectIntersectionOf like this:

import {
  _string,
  is,
  ObjectAsserter,
  objectAsserter,
  objectIntersectionOf,
} from "./mod.ts";
// import from "typeguardkit" if using npm

// types/entity.ts

const entityAsserter = objectAsserter("Entity", {
  id: _string,
});

export type Entity = ReturnType<typeof entityAsserter>;

export const _Entity: ObjectAsserter<Entity> = entityAsserter;

// types/user.ts

const userAsserter = objectIntersectionOf(
  _Entity,
  objectAsserter("", {
    name: _string,
  }),
  "User",
);

export type User = ReturnType<typeof userAsserter>;

export const _User: ObjectAsserter<User> = userAsserter;

// elsewhere.ts

function handleUnknown(x: unknown) {
  if (is(_User, x)) {
    // `x` has now been narrowed to type `User`, so can be passed to
    // `handleUser`.

    handleUser(x);
  }
}

function handleUser(x: User) {}

partialFrom

partialFrom returns an ObjectAsserter<Partial<Type>>, created using the provided ObjectAsserter<Type>.

Partial<Type> is a utility type that constructs a type the same as Type, but with all properties made optional.

You can use partialFrom like this:

import { _string, ObjectAsserter, objectAsserter, partialFrom } from "./mod.ts";
// import from "typeguardkit" if using npm

const asserter = partialFrom(
  objectAsserter("", {
    option1: _string,
    option2: _string,
    option3: _string,
  }),
  "Options",
);

export type Options = ReturnType<typeof asserter>;

export const _Options: ObjectAsserter<Options> = asserter;

pickFrom

pickFrom returns an ObjectAsserter<Pick<Type, Keys[number]>>, created using the provided ObjectAsserter<Type> and Keys.

Pick<Type, Keys> is a utility type that constructs a type consisting of the properties of Type with Keys.

You can use pickFrom like this:

import { _string, ObjectAsserter, objectAsserter, pickFrom } from "./mod.ts";
// import from "typeguardkit" if using npm

// types/user.ts

const userAsserter = objectAsserter("User", {
  id: _string,
  firstName: _string,
  lastName: _string,
});

export type User = ReturnType<typeof userAsserter>;

export const _User: ObjectAsserter<User> = userAsserter;

// types/user_name.ts

const userNameAsserter = pickFrom(_User, ["firstName", "lastName"]);

export type UserName = ReturnType<typeof userNameAsserter>;

export const _UserName: ObjectAsserter<UserName> = userNameAsserter;

Validator Asserter factory functions

numberAsserter

numberAsserter returns an Asserter<number> that asserts whether value is of type number and valid according to the provided NumberAsserterOptions.

You can use numberAsserter like this:

import { numberAsserter } from "./mod.ts";
// import from "typeguardkit" if using npm

export const _EvenNumberInRange = numberAsserter(
  "EvenNumberInRange",
  {
    min: 0,
    max: 100,

    validate: (value) => {
      if (value % 2 !== 0) {
        return ["must be even"];
      }
      return [];
    },
  },
);