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.ts
npm installation
npm install typeguardkit
Usage
import
s
Example 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));
}
Asserter
s
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 Asserter
s.
It also includes the _NonNegativeNumber
, _PositiveNumber
, _Integer
,
_NonNegativeInteger
, and _PositiveInteger
Asserter
s, which are number
Asserter
s that perform additional validation.
The _null
and _undefined
Asserter
s can be used to create union type
Asserter
s with the unionOf
function.
As well as wrapping Asserter
s 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 Asserter
s 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) {}
Asserter
s
Enum 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) {}
LiteralUnionAsserter
s
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) {}
Asserter
factory functions
Other unionOf
unionOf
returns a UnionAsserter
for the union of the Type
s of the provided
Asserter
s.
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"];
},
});
ObjectAsserter
s
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) {}
ObjectAsserter
factory functions
Other objectIntersectionOf
objectIntersectionOf
returns an ObjectAsserter
for the intersection of the
Type
s of the provided ObjectAsserter
s.
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;
TypeAssertionError
issue tree
The 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;
}
TypeAssertionError
s to another program
Sending 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.