a typescript library for building type guards quickly while maintaining type safety
npm install isguard-tsisguard-ts utilizes the typescript compiler to ensure that its type guards are aligned with the guarded type.isguard-ts will inform you to update your type guard as well.
npm install isguard-ts
`
Table of Contents
+ TypeGuard
+ isType
+ isOptional
+ isMaybe
+ isArray
+ isLiteral
+ isUnion
+ isIntersection
+ isRecord
+ isPartialRecord
+ isIndexRecord
+ isLazy
+ isTuple
+ isEnum
+ isSet
+ isMap
+ isInstanceof
+ isRefine
+ utility type guards
+ generic types
+ recursive types
+ zod
Basic Usage
$3
The most basic type - represents a type guard of T
`typescript
type TypeGuard = (value: unknown) => value is T;
`
$3
Helps you create type guards for types and interfaces
`typescript
type Person = {
name: string;
age: number;
};
const isPerson = isType({
name: isString,
age: isNumber,
});
isPerson({ name: "Hello", age: 6 }); // true
`
> Best Practice
>
> Pass the generic type argument into isType
> Otherwise optional fields might have an unexpected behavior
$3
Helps you create type guards for optional (potentially undefined) types
`typescript
isOptional(isNumber); // or isNumber.optional();
`
$3
Helps you create type guards for nullable (potentially null) types
`typescript
isMaybe(isNumber); // or isNumber.maybe();
`
$3
Helps you create type guards for arrays
`typescript
isArray(isBoolean); // or isBoolean.array();
`
$3
Helps you create type guards for literals
`typescript
const isHello = isLiteral("Hello");
const is12 = isLiteral(12);
`
isLiteral can receive multiple values
`typescript
const directions = ["up", "down", "left", "right"] as const;
type Direction = (typeof directions)[number];
const isDirection = isLiteral(...directions) satisfies TypeGuard;
`
> Best Practice
>
> Use the satisfies keyword on the result of isLiteral when passing multiple values
> This ensures the result is of the expected type
$3
Helps you create type guards for unions
`typescript
type A = { a: number };
type B = { b: string };
type C = A | B;
const isA = isType({ a: isNumber });
const isB = isType({ b: isString });
isUnion(isA, isB) satisfies TypeGuard; // or isA.or(isB);
`
> Best Practice
>
> Use the satisfies keyword on the result of isUnion
> This ensures the result is of the expected type
$3
Helps you create type guards for intersections
`typescript
type A = { a: number };
type B = { b: string };
type C = A & B;
const isA = isType({ a: isNumber });
const isB = isType({ b: isString });
isIntersection(isA, isB) satisfies TypeGuard; // or isA.and(isB);
`
> Best Practice
>
> Use the satisfies keyword on the result of isIntersection
> This ensures the result is of the expected type
$3
Helps you create type guards for records
`typescript
const timeUnits = ["second", "minute", "hour"] as const;
type TimeUnit = (typeof timeUnits)[number];
isRecord(timeUnits, isNumber);
// Record
`
$3
Works just like isRecord but allows for undefined values
`typescript
const timeUnits = ["second", "minute", "hour"] as const;
type TimeUnit = (typeof timeUnits)[number];
isPartialRecord(timeUnits, isNumber);
// Partial>
`
$3
Works just like isRecord but checks only the values and not the keys
`typescript
isIndexRecord(isNumber); // or isNumber.indexRecord();
// Record
`
$3
Helps you lazy load a type guard.
Useful for:
+ Resolving undefined errors due to circular imports
+ Creating type guards for recursive types
`typescript
import { isPerson } from "./some-module";
const isPeople = isLazy(() => isPerson).array();
`
In the example above isPerson, imported from ./some-module, might be undefined when isPeople is being created, due to circular imports. So isPerson.array() would throw an error. isLazy solves this issue by accessing isPerson only when needed.
$3
Helps you create type guards for tuples
`typescript
type Row = [number, string?];
const isRow = isTuple([isNumber, isString.optional()]);
isRow([6, "Hello"]); // true
isRow([6]); // true
isRow(["Hello", "Bye"]); // false
`
> Best Practice
>
> Pass the generic type argument into isTuple
> Otherwise optional fields might have an unexpected behavior
$3
Helps you create type guards for enums
`typescript
enum Direction {
up = 0,
down = 1,
left = 2,
right = 3,
}
const isDirection = isEnum(Direction);
isDirection(Direction.up); // true
isDirection(2); // true
isDirection("hello"); // false
`
$3
Helps you create type guards for sets
`typescript
isSet(isNumber); // or isNumber.set();
// Set
`
$3
Helps you create type guards for maps
`typescript
isMap(isString, isBoolean);
// Map
`
$3
Helps you create type guards for classes
`typescript
abstract class Animal { }
class Dog extends Animal { }
const isAnimal = isInstanceof(Animal);
const isDog = isInstanceof(Dog);
`
$3
Helps you refine existing type guards. Can be used for:
+ Branded types (like Email, PositiveNumber and more)
+ Template literals (like \Bye ${string}\)
`typescript
type Farewell = Bye ${string};
const isFarewell = isRefine(isString, (value: string): value is Farewell => {
return value.startsWith("Bye ");
});
`
> Warning
>
> using isRefine can be unsafe because it let's you implement potentially false logic
> Use at your own risk.
$3
`typescript
const isNumber: TypeGuard;
const isBigint: TypeGuard;
const isString: TypeGuard;
const isBoolean: TypeGuard;
const isSymbol: TypeGuard;
const isFunction: TypeGuard;
const isPropertyKey: TypeGuard;
const isDate: TypeGuard;
const isRegExp: TypeGuard;
const isObject: TypeGuard `
Advanced Usage
$3
When creating type guards for generic types, you need to create your own TypeGuard generator
`typescript
type ValueHolder = {
value: T;
};
const isValueHolder = (isValue: TypeGuard) => {
return isType>({
value: isValue,
});
};
const isNumberHolder = isValueHolder(isNumber);
`
$3
One way to build recursive type guards is by using isLazy
`typescript
type Json =
| number
| string
| boolean
| null
| Json[]
| { [key: string]: Json; };
const isJson: TypeGuard = isUnion(
isNumber,
isString,
isBoolean,
isNull,
isLazy(() => isJson).array(),
isLazy(() => isJson).indexRecord(),
);
`
`typescript
type Tree = {
value: number;
left?: Tree;
right?: Tree;
};
const isTree: TypeGuard = isType({
value: isNumber,
left: isLazy(() => isTree).optional(),
right: isLazy(() => isTree).optional(),
});
`
There is a less recommended way using a getter
`typescript
const isTree: TypeGuard = isType({
value: isNumber,
get left() {
return isTree.optional();
},
get right() {
return isTree.optional();
},
});
`
> Important
>
> Annotate the recursive guard to avoid typescript errors
Plugins
$3
Any TypeGuard has a .zod() method that returns a zod schema which represents the guarded type.
To use this feature you must have zod installed through npm. The supported versions of zod start with zod@3.20.0 and end with zod@5.0.0 (not included)
`typescript
const ZodNumber = isNumber.zod(); // same as z.number()
type Person = {
name: string;
};
const isPerson = isType({
name: isString,
});
const ZodPerson = isPerson.zod(); // same as z.object({ name: z.string() })
`
> Important
>
> The schema returned by .zod() might not exactly represent the guarded type in certain edge cases.
> For example: isNumber(NaN) returns true while z.number() marks NaN as invalid.
>
> The differences vary between zod versions, but these are the most common
> + Non finite numbers (NaN, Infinity, -Infinity) are valid when using isguard-ts but invalid when using zod
> + zod ignores symbol property keys while isguard-ts` doesn't