Skip to main content
Deno 2 is finally here 🎉️
Learn more

TypeGuardKit

ci codecov deno doc

⚠️ API not yet stable

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

The included functions can be used to create types and their assertion functions and guards from a single source definition.

Type assertion functions and guards can be used to catch incompatible types entering your program due to data corruption, interfaces not being adhered to, or interface definitions being outdated without versioning protection from breaking changes.

Setup

Deno module URL

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

npm installation

npm install typeguardkit

Usage

Example imports

Where you see relative import paths in documentation examples, instead import from "https://deno.land/x/typeguardkit/mod.ts" if using Deno, or from "typeguardkit" if using npm.

Example

import {
  _boolean,
  _null,
  _number,
  _PositiveInteger,
  _string,
  arrayOf,
  ObjectAsserter,
  unionOf,
} from "./mod.ts";

// types/book.ts

export const _Book = new ObjectAsserter("Book", {
  isbn: _string,
  title: _string,
  authors: arrayOf(_string),
  pageCount: _PositiveInteger,
  rating: unionOf(_number, _null),
  recommended: _boolean,
});

export type Book = ReturnType<typeof _Book.assert>;

// 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.assert(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).assert(JSON.parse(json)) : [];
}

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

Asserter

An Asserter<Type> has a type assertion method, assert, that asserts whether the provided value is of Type.

interface Asserter<Type> {
  readonly typeName: string;

  assert(value: unknown, valueName?: string): Type;
}

If value is of Type, assert should return value as Type. Otherwise, assert should throw a TypeAssertionError, including valueName in its message.

The module includes the _boolean, _number, _string, _null, and _undefined primitive type Asserters.

It also includes the _NonNegativeNumber, _PositiveNumber, _Integer, _NonNegativeInteger, and _PositiveInteger Asserters, which are number Asserters that perform additional validation.

The _null and _undefined Asserters 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";

function handleUnknown(x: unknown) {
  let y;

  try {
    y = _string.assert(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 using the TypeAsserter class. For example, the _string Asserter was created like this:

import { TypeAsserter } from "./mod.ts";

export const _string = new 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.

Example:

import { _string, assertIs } from "./mod.ts";

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.

Example:

import { _string, is } from "./mod.ts";

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

NumberAsserter

A NumberAsserter is an Asserter<number>, with any additional validation defined by its NumberAsserterOptions properties.

The provided NumberAsserterOptions are made accessible as properties of the created NumberAsserter.

Example:

import { NumberAsserter } from "./mod.ts";

export const _EvenNumberInRange = new NumberAsserter("EvenNumberInRange", {
  min: { value: 0, inclusive: true },
  max: { value: 100, inclusive: true },
  step: 2,
});

StringAsserter

A StringAsserter is an Asserter<string>, with any additional validation defined by its StringAsserterOptions properties.

The provided StringAsserterOptions are made accessible as properties of the created StringAsserter.

Example:

import { StringAsserter } from "./mod.ts";

export const _NonEmptyString = new StringAsserter("NonEmptyString", {
  minLength: 1,
});

export const _NumericString = new StringAsserter("NumericString", {
  regex: { pattern: "\\d+", requirements: ["must be numeric"] },
});

export const _Palindrome = new StringAsserter("Palindrome", {
  validate(value) {
    if (value.length < 2) {
      return [];
    }

    const forwardValue = value.replace(/[^0-9a-z]/gi, "");
    const backwardValue = forwardValue.split("").reverse().join("");

    return forwardValue === backwardValue ? [] : ["must be a palindrome"];
  },
});

LiteralUnionAsserter

A LiteralUnionAsserter is an Asserter for the union of its values.

The provided values are made accessible as a property of the created LiteralUnionAsserter.

Example:

import { LiteralUnionAsserter } from "./mod.ts";

export const _Direction = new LiteralUnionAsserter(
  "Direction",
  ["up", "right", "down", "left"],
);

export type Direction = ReturnType<typeof _Direction.assert>;

EnumAsserter

An EnumAsserter is an Asserter for the union of the member types of the provided enumObject.

The provided enumObject is made accessible as a property of the created EnumAsserter.

Example:

import { EnumAsserter } from "./mod.ts";

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

export const _Direction = new EnumAsserter("Direction", Direction);

UnionAsserter

A UnionAsserter is an Asserter for the union of the Types of its memberAsserters.

The provided memberAsserters are made accessible as a property of the created UnionAsserter.

Example:

import { _null, _string, UnionAsserter } from "./mod.ts";

export const _stringOrNull = new UnionAsserter(
  "stringOrNull",
  [_string, _null],
);

unionOf

The unionOf function can be used to create a UnionAsserter without specifying a typeName.

Example:

import { _null, _string, unionOf } from "./mod.ts";

export const _stringOrNull = unionOf(_string, _null);

ArrayAsserter

An ArrayAsserter is an Asserter for the Array type defined by its elementAsserter, with any additional validation defined by its ArrayAsserterOptions properties.

The provided memberAsserter and ArrayAsserterOptions are made accessible as properties of the created ArrayAsserter.

Example:

import { _string, ArrayAsserter } from "./mod.ts";

export const _NonEmptyArrayOfString = new ArrayAsserter(
  "NonEmptyArrayOfString",
  _string,
  { minLength: 1 },
);

arrayOf

The arrayOf function can be used to create an ArrayAsserter without specifying a typeName or ArrayAsserterOptions.

Example:

import { _string, arrayOf } from "./mod.ts";

export const _ArrayOfString = arrayOf(_string);

RecordAsserter

A RecordAsserter is an Asserter for the Record type defined by its keyAsserter and valueAsserter.

The provided keyAsserter and valueAsserter are made accessible as properties of the created RecordAsserter.

Example:

import { _string, RecordAsserter } from "./mod.ts";

export const _RecordOfStringByString = new RecordAsserter(
  "RecordOfStringByString",
  [_string, _string],
);

recordOf

The recordOf function can be used to create a RecordAsserter without specifying a typeName.

Example:

import { _string, recordOf } from "./mod.ts";

export const _RecordOfStringByString = recordOf(_string, _string);

ObjectAsserter

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

The provided propertyAsserters are made accessible as a property of the created ObjectAsserter.

Example:

import { _string, ObjectAsserter, optionOf } from "./mod.ts";

export const _User = new ObjectAsserter("User", {
  name: _string,
  emailAddress: optionOf(_string),
});

export type User = ReturnType<typeof _User.assert>;

objectIntersectionOf

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

Example:

import { _string, ObjectAsserter, objectIntersectionOf } from "./mod.ts";

// types/entity.ts

export const _Entity = new ObjectAsserter("Entity", {
  id: _string,
});

export type Entity = ReturnType<typeof _Entity.assert>;

// types/user.ts

export const _User = objectIntersectionOf(
  _Entity,
  new ObjectAsserter("", {
    name: _string,
  }),
  "User",
);

export type User = ReturnType<typeof _User.assert>;

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.

Example:

import { _string, ObjectAsserter, partialFrom } from "./mod.ts";

export const _Options = partialFrom(
  new ObjectAsserter("", {
    option1: _string,
    option2: _string,
    option3: _string,
  }),
  "Options",
);

export type Options = ReturnType<typeof _Options.assert>;

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.

Example:

import { _string, ObjectAsserter, pickFrom } from "./mod.ts";

// types/user.ts

export const _User = new ObjectAsserter("User", {
  id: _string,
  firstName: _string,
  lastName: _string,
});

export type User = ReturnType<typeof _User.assert>;

// types/user_name.ts

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

export type UserName = ReturnType<typeof _UserName.assert>;

The TypeAssertionError issue tree

A TypeAssertionError has an issueTree property of type TypeAssertionIssueTree, which provides a tree representation of the issue data contained in the error’s message.

TypeAssertionIssueTree is an alias of TypeAssertionIssueTreeNode for typing the root TypeAssertionIssueTreeNode.

A TypeAssertionError also has an issueTreeNode method to find the node at a given path. The path separator is a forward slash, and a path segment can be an object property name or array index.

issueTreeNode could be called to get the issue data for each field in a form, for example:

import {
  _NonNegativeInteger,
  _string,
  assertIs,
  is,
  ObjectAsserter,
  TypeAssertionError,
} from "./mod.ts";

// types/item.ts

export const _Item = new ObjectAsserter("Item", {
  quantity: _NonNegativeInteger,
});

export type Item = ReturnType<typeof _Item.assert>;

// types/form.ts

export const _Form = new ObjectAsserter("Form", {
  item: _Item,
});

export type Form = ReturnType<typeof _Form.assert>;

// elsewhere.ts

const form: Form = {
  item: {
    quantity: 0,
  },
};

let itemQuantityIssues: string[] = [];

function validateForm(): boolean {
  try {
    assertIs(_Form, form);
  } catch (error) {
    if (error instanceof TypeAssertionError) {
      const node = error.issueTreeNode("item/quantity");

      itemQuantityIssues = node?.issues
        ? node.issues.filter((issue) => is(_string, issue)) as string[]
        : [];
    }

    return false;
  }

  return true;
}

Sending TypeAssertionErrors to another program

A TypeAssertionError’s toJSON method returns a JSON representation of the error’s issueTree, which can then be sent to another program and used to create a new TypeAssertionError there with the same issueTree.

In the receiving program, you can assert that a received object is a TypeAssertionIssueTree using the _TypeAssertionIssueTree Asserter. You can then create a TypeAssertionError by passing the asserted TypeAssertionIssueTree to its constructor.