JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 26
  • Score
    100M100P100Q76437F
  • License ISC

a typescript library for building type guards quickly while maintaining type safety

Package Exports

  • isguard-ts
  • isguard-ts/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (isguard-ts) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

isguard-ts

A powerful typescript library that helps you build type guards quickly while maintaining type safety.

isguard-ts utilizes the typescript compiler to ensure that its type guards are aligned with the guarded type.
For example, when making a change to your type, isguard-ts will inform you to update your type guard as well.

Installation

npm install isguard-ts

Table of Contents

Basic Usage

TypeGuard<T>

The most basic type - represents a type guard of T

type TypeGuard<T> = (value: unknown) => value is T;

isType

Helps you create type guards for types and interfaces

type Person = {
    name: string;
    age: number;
};

const isPerson = isType<Person>({
    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

isOptional

Helps you create type guards for optional (potentially undefined) types

isOptional(isNumber); // or isNumber.optional();

isMaybe

Helps you create type guards for nullable (potentially null) types

isMaybe(isNumber); // or isNumber.maybe();

isArray

Helps you create type guards for arrays

isArray(isBoolean); // or isBoolean.array();

isLiteral

Helps you create type guards for literals

const isHello = isLiteral("Hello");
const is12 = isLiteral(12);

isLiteral can receive multiple values

const directions = ["up", "down", "left", "right"] as const;
type Direction = (typeof directions)[number];

const isDirection = isLiteral(...directions) satisfies TypeGuard<Direction>;

Best Practice

Use the satisfies keyword on the result of isLiteral when passing multiple values
This ensures the result is of the expected type

isUnion

Helps you create type guards for unions

type A = { a: number };
type B = { b: string };
type C = A | B;

const isA = isType<A>({ a: isNumber });
const isB = isType<B>({ b: isString });

isUnion(isA, isB) satisfies TypeGuard<C>; // or isA.or(isB);

Best Practice

Use the satisfies keyword on the result of isUnion
This ensures the result is of the expected type

isIntersection

Helps you create type guards for intersections

type A = { a: number };
type B = { b: string };
type C = A & B;

const isA = isType<A>({ a: isNumber });
const isB = isType<B>({ b: isString });

isIntersection(isA, isB) satisfies TypeGuard<C>; // or isA.and(isB);

Best Practice

Use the satisfies keyword on the result of isIntersection
This ensures the result is of the expected type

isRecord

Helps you create type guards for records

const timeUnits = ["second", "minute", "hour"] as const;
type TimeUnit = (typeof timeUnits)[number];

isRecord(timeUnits, isNumber);
// Record<TimeUnit, number>

isPartialRecord

Works just like isRecord but allows for undefined values

const timeUnits = ["second", "minute", "hour"] as const;
type TimeUnit = (typeof timeUnits)[number];

isPartialRecord(timeUnits, isNumber);
// Partial<Record<TimeUnit, number>>

isIndexRecord

Works just like isRecord but checks only the values and not the keys

isIndexRecord(isNumber); // or isNumber.indexRecord();
// Record<PropertyKey, number>

isLazy

Helps you lazy load a type guard. Useful for:

  • Resolving undefined errors due to circular imports
  • Creating type guards for recursive types
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.

isTuple

Helps you create type guards for tuples

type Row = [number, string?];

const isRow = isTuple<Row>([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

isEnum

Helps you create type guards for enums

enum Direction {
    up = 0,
    down = 1,
    left = 2,
    right = 3,
}

const isDirection = isEnum(Direction);

isDirection(Direction.up); // true
isDirection(2); // true
isDirection("hello"); // false

isSet

Helps you create type guards for sets

isSet(isNumber); // or isNumber.set();
// Set<number>

isMap

Helps you create type guards for maps

isMap(isString, isBoolean);
// Map<string, boolean>

isInstanceof

Helps you create type guards for classes

abstract class Animal { }
class Dog extends Animal { }

const isAnimal = isInstanceof(Animal);
const isDog = isInstanceof(Dog);

isRefine

Helps you refine existing type guards. Can be used for:

  • Branded types (like Email, PositiveNumber and more)
  • Template literals (like `Bye ${string}`)
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.

Built-in Utility Type Guards

const isNumber: TypeGuard<number>;
const isBigint: TypeGuard<bigint>;
const isString: TypeGuard<string>;
const isBoolean: TypeGuard<boolean>;
const isSymbol: TypeGuard<symbol>;
const isFunction: TypeGuard<Function>;
const isPropertyKey: TypeGuard<PropertyKey>;

const isDate: TypeGuard<Date>;
const isRegExp: TypeGuard<RegExp>;
const isObject: TypeGuard<object>;
const isError: TypeGuard<Error>;
const isEvalError: TypeGuard<EvalError>;
const isRangeError: TypeGuard<RangeError>;
const isReferenceError: TypeGuard<ReferenceError>;
const isSyntaxError: TypeGuard<SyntaxError>;
const isTypeError: TypeGuard<TypeError>;
const isURIError: TypeGuard<URIError>;

const isNull: TypeGuard<null>;
const isUndefined: TypeGuard<undefined>;
const isNil: TypeGuard<null | undefined>;

const isTrue: TypeGuard<true>;
const isFalse: TypeGuard<false>;

const isUnknown: TypeGuard<unknown>;
const isNever: TypeGuard<never>;

Advanced Usage

Generic Types

When creating type guards for generic types, you need to create your own TypeGuard generator

type ValueHolder<T> = {
    value: T;
};

const isValueHolder = <T>(isValue: TypeGuard<T>) => {
    return isType<ValueHolder<T>>({
        value: isValue,
    });
};

const isNumberHolder = isValueHolder(isNumber);

Recursive Types

One way to build recursive type guards is by using isLazy

type Json =
    | number
    | string
    | boolean
    | null
    | Json[]
    | { [key: string]: Json; };

const isJson: TypeGuard<Json> = isUnion(
    isNumber,
    isString,
    isBoolean,
    isNull,
    isLazy(() => isJson).array(),
    isLazy(() => isJson).indexRecord(),
);
type Tree = {
    value: number;
    left?: Tree;
    right?: Tree;
};

const isTree: TypeGuard<Tree> = isType<Tree>({
    value: isNumber,
    left: isLazy(() => isTree).optional(),
    right: isLazy(() => isTree).optional(),
});

There is a less recommended way using a getter

const isTree: TypeGuard<Tree> = isType<Tree>({
    value: isNumber,
    get left() {
        return isTree.optional();
    },
    get right() {
        return isTree.optional();
    },
});

Important

Annotate the recursive guard to avoid typescript errors

Plugins

Zod

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)

const ZodNumber = isNumber.zod(); // same as z.number()

type Person = {
    name: string;
};

const isPerson = isType<Person>({
    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