Skip to main content

Type Guard Kit

ci codecov

A TypeScript module to help create a type and its guards from a single source definition.

The module can be used to type APIs and catch unexpected incompatible types 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,
  _number,
  _string,
  arrayOf,
  Asserter,
  objectAsserter,
  undefinedOr,
  unionOf,
} from "./mod.ts";
// import from "typeguardkit" if using npm

// entity_types/book.ts

const asserter = objectAsserter("Book", {
  title: _string,
  authors: arrayOf(_string),
  pageCount: _number,
  isRecommended: _boolean,
  websiteUrl: undefinedOr(_string),
});

export interface Book extends ReturnType<typeof asserter> {}

export const _Book: Asserter<Book> = asserter;

// api/get_book.ts

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

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

  return _Book(responseBody, "responseBody");
}

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.

You can use an Asserter 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 Asserter with the type function. For example, the _string Asserter was created like this:

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

export const _string = type(
  "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) {}

Other Asserter factory functions

The following functions help to create new Asserters from existing ones.

unionOf

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

You can use unionOf like this:

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

const _stringOrNumber = unionOf(_string, _number);

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

    handleStringOrNumber(x);
  }
}

function handleStringOrNumber(x: string | number) {}

arrayOf

The arrayOf function 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[]) {}

undefinedOr

The undefinedOr function returns an Asserter<Type | undefined>, created using the provided Asserter<Type>.

You can use undefinedOr like this:

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

const _stringOrUndefined = undefinedOr(_string);

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

    handleStringOrUndefined(x);
  }
}

function handleStringOrUndefined(x?: string) {}

nullOr

The nullOr function returns an Asserter<Type | null>, created using the provided Asserter<Type>.

You can use nullOr like this:

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

const _stringOrNull = nullOr(_string);

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

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, Asserter, is, objectAsserter } from "./mod.ts";
// import from "typeguardkit" if using npm

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

export interface User extends ReturnType<typeof asserter> {}

export const _User: Asserter<User> = asserter;

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