TypeGuardKit
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.tsnpm installation
npm install typeguardkitUsage
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,
objectAsserter,
unionOf,
} from "./mod.ts";
// types/book.ts
const asserter = objectAsserter("Book", {
isbn: _string,
title: _string,
authors: arrayOf(_string),
pageCount: _PositiveInteger,
rating: unionOf([_number, _null]),
recommended: _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, valueName?: string): Type;
readonly asserterTypeName: string;
readonly assertedTypeName: string;
}If value is of Type, the Asserter should return value as Type.
Otherwise, the Asserter 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(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";
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";
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";
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";
// 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) {}LiteralUnionAsserters
You can use the literalUnionAsserter function to create a
LiteralUnionAsserter for the union of the provided values. The values
array should be asserted as const.
The provided values will be set to the values property of the returned
LiteralUnionAsserter.
You can use literalUnionAsserter like this:
import { is, LiteralUnionAsserter, literalUnionAsserter } from "./mod.ts";
// types/direction.ts
const asserter = literalUnionAsserter(
"Direction",
["up", "right", "down", "left"] as const,
);
export type Direction = ReturnType<typeof asserter>;
export const _Direction: LiteralUnionAsserter<readonly 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
unionOf
unionOf returns a UnionAsserter for the union of the Types of the provided
Asserters.
You can use unionOf like this:
import { _null, _string, is, unionOf } from "./mod.ts";
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) {}optionOf
optionOf returns an OptionAsserter for the union of the DefinedType of the
provided definedTypeAsserter with undefined.
Example:
import { _string, optionOf } from "./mod.ts";
const _OptionalString = optionOf(_string);arrayOf
arrayOf returns a TypeAsserter<Array<Type>>, created using the provided
Asserter<Type>.
You can use arrayOf like this:
import { _string, arrayOf, is } from "./mod.ts";
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[]) {}recordOf
recordOf returns a TypeAsserter<Record<Key, Value>>, created using the
provided Asserter<Key> and Asserter<Value>.
You can use recordOf like this:
import { _string, is, recordOf } from "./mod.ts";
const _RecordOfString = recordOf(_string, _string);
function handleUnknown(x: unknown) {
if (is(_RecordOfString, x)) {
// `x` has now been narrowed to type `Record<string, string>`, so can be
// passed to `handleRecordOfString`.
handleRecordOfString(x);
}
}
function handleRecordOfString(x: Record<string, string>) {}numberAsserter
numberAsserter returns a NumberAsserter that asserts whether value is of
type number and valid according to the provided NumberAsserterOptions.
The provided NumberAsserterOptions are made accessible as properties of the
returned NumberAsserter.
You can use numberAsserter like this:
import { numberAsserter } from "./mod.ts";
export const _EvenNumberInRange = numberAsserter("EvenNumberInRange", {
min: { value: 0, inclusive: true },
max: { value: 100, inclusive: true },
step: 2,
});stringAsserter
stringAsserter returns a StringAsserter that asserts whether value is of
type string and valid according to the provided StringAsserterOptions.
The provided StringAsserterOptions are made accessible as properties of the
returned StringAsserter.
You can use stringAsserter like this:
import { stringAsserter } from "./mod.ts";
export const _NonEmptyString = stringAsserter("NonEmptyString", {
minLength: 1,
});
export const _NumericString = stringAsserter("NumericString", {
regex: { pattern: "\\d+", requirements: ["must be numeric"] },
});
export const _Palindrome = 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"];
},
});ObjectAsserters
An ObjectAsserter is an Asserter for the object type defined by its
propertyAsserters.
import { Asserter, objectAsserterTypeName } from "./mod.ts";
interface ObjectAsserter<Type extends Record<string, unknown>>
extends Asserter<Type> {
readonly asserterTypeName: typeof objectAsserterTypeName;
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 {
_string,
is,
ObjectAsserter,
objectAsserter,
optionOf,
} from "./mod.ts";
// types/user.ts
const asserter = objectAsserter("User", {
name: _string,
emailAddress: optionOf(_string),
});
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
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";
// 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";
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";
// 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;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,
objectAsserter,
TypeAssertionError,
} from "./mod.ts";
// types/item.ts
const itemAsserter = objectAsserter("Item", {
quantity: _NonNegativeInteger,
});
export type Item = ReturnType<typeof itemAsserter>;
export const _Item: ObjectAsserter<Item> = itemAsserter;
// types/form.ts
const formAsserter = objectAsserter("Form", {
item: _Item,
});
export type Form = ReturnType<typeof formAsserter>;
export const _Form: ObjectAsserter<Form> = formAsserter;
// 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.