npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

ts-guardian

v1.11.0

Published

Declarative, composable type guards

Readme

MIT license NPM version

ts-guardian

Runtime type guards. Composable, TypeScript-like syntax. 100% type-safe.

Full TypeScript support (TypeScript not required).

Type guards?

Type guards let you check whether a value matches a type at runtime. ts-guardian makes them composable, readable, and type-safe — no more verbose checks or unsafe assertions.

If you're working with API responses, optional object members, or unknown values, ts-guardian will help you out.

Installation

npm install ts-guardian

Quick Start

Import the is function and create a type-guard, such as isUser. Use that type-guard to confirm objects match the type:

import { is } from 'ts-guardian'

const isUser = is({
  id: 'number',
  name: 'string',
  email: 'string?',
  teamIds: 'number[]',
})

isUser({ id: 1, name: 'John', teamIds: [2, 3] }) // true

Core principles

API

is function

import { is } from 'ts-guardian'

The main tool to create type guards. The is function takes a parameter that defines a type, and returns a guard for that type:

const isNumber = is('number') // guard for 'number'
isNumber(0) // true
isNumber('') // false

Basic types

Pass a type string to create guards for basic types:

const isBoolean = is('boolean') // guard for 'boolean'
const isNull = is('null') // guard for 'null'

All basic type strings:

| String | Type | Equivalent type check | | ------------- | ----------- | ------------------------------- | | 'any' | any | true (matches anything) | | 'boolean' | boolean | typeof <value> === 'boolean' | | 'bigint' | bigint | typeof <value> === 'bigint' | | 'function' | Function | typeof <value> === 'function' | | 'null' | null | <value> === null | | 'number' | number | typeof <value> === 'number' | | 'object' | object | typeof <value> === 'object' | | 'string' | string | typeof <value> === 'string' | | 'symbol' | symbol | typeof <value> === 'symbol' | | 'undefined' | undefined | <value> === undefined | | 'unknown' | unknown | true (matches anything) |

Basic guards will return false for objects created with constructors. For example, is('string')(new String()) returns false. Use isInstanceOf instead.

Union types

Every guard has an or method with the same signature as is. You can use or to create union types:

const isStringOrNumber = is('string').or('number') // guard for 'string | number'
isStringOrNumber('') // true
isStringOrNumber(0) // true
isStringOrNumber(true) // false

Literal types

Pass a number, string, or boolean to the isLiterally function and the orLiterally method to create guards for literal types. You can also pass multiple arguments to create literal union type guards:

import { isLiterally } from 'ts-guardian'

const isCat = isLiterally('cat') // guard for '"cat"'
const is5 = isLiterally(5) // guard for '5'
const isTrue = isLiterally(true) // guard for 'true'
const isCatOr5 = isLiterally('cat').orLiterally(5) // guard for '"cat" | 5'
const isCatOr5OrTrue = isLiterally('cat', 5, true) // guard for '"cat" | 5 | true'

Array types

To check that every element in an array is of a specific type, use the isArrayOf function and the orArrayOf method:

import { is, isArrayOf } from 'ts-guardian'

const isStrArr = isArrayOf('string') // guard for 'string[]'
const isStrOrNumArr = isArrayOf(is('string').or('number')) // guard for '(string | number)[]'
const isStrArrOrNumArr = isArrayOf('string').orArrayOf('number') // guard for 'string[] | number[]'

Note the difference between isStrOrNumArr which is a guard for (string | number)[], and isStrArrOrNumArr which is a guard for string[] | number[].

For basic array types, you can simply pass a string to the is function instead of using isArrayOf:

import { is } from 'ts-guardian'

const isStrArr = is('string[]') // guard for 'string[]'
const isStrArrOrNumArr = is('string[]').or('number[]') // guard for 'string[] | number[]'

Record types

To check that every value in an object is of a specific type, use the isRecordOf function and the orRecordOf method:

import { is, isRecordOf } from 'ts-guardian'

const isStrRecord = isRecordOf('string') // guard for 'Record<PropertyKey, string>'
const isStrOrNumRecord = isRecordOf(is('string').or('number')) // guard for 'Record<PropertyKey, string | number>'
const isStrRecordOrNumRecord = isRecordOf('string').orRecordOf('number') // guard for 'Record<PropertyKey, string> | Record<PropertyKey, number>'

Tuple types

Guards for tuples are defined by passing a tuple to is:

const isStrNumTuple = is(['string', 'number']) // guard for '[string, number]'
isStrNumTuple(['high']) // false
isStrNumTuple(['high', 5]) // true

Guards for nested tuples can be defined by nesting tuple guards inside tuple guards:

const isStrAndNumNumTupleTuple = is(['string', is(['number', 'number'])]) // guard for '['string', [number, number]]'

Object types

Use is({}) to check for any non-null object. Avoid is('object') as it matches null:

const isObject = is({}) // guard for '{}'
isObject({ some: 'prop' }) // true
isObject(null) // false

To create a guard for an object with specific members, define a guard for each member key:

const hasAge = is({ age: 'number' }) // guard for '{ age: number; }'
hasAge({ name: 'John' }) // false
hasAge({ name: 'John', age: 40 }) // true

Intersection types

Every type guard has an and method which has the same signature as or. Use and to create intersection types:

const hasXOrY = is({ x: 'any' }).or({ y: 'any' }) // guard for '{ x: any; } | { y: any; }'
hasXOrY({ x: '' }) // true
hasXOrY({ y: '' }) // true
hasXOrY({ x: '', y: '' }) // true

const hasXAndY = is({ x: 'any' }).and({ y: 'any' }) // guard for '{ x: any; } & { y: any; }'
hasXAndY({ x: '' }) // false
hasXAndY({ y: '' }) // false
hasXAndY({ x: '', y: '' }) // true

Instance types

Guards for object instances are defined by passing a constructor object to the isInstanceOf function and the orInstanceOf method:

const isDate = isInstanceOf(Date) // guard for 'Date'
isDate(new Date()) // true

const isRegExpOrUndefined = is('undefined').orInstanceOf(RegExp) // guard for 'undefined | RegExp'
isRegExpOrUndefined(/./) // true
isRegExpOrUndefined(new RegExp('.')) // true
isRegExpOrUndefined(undefined) // true

This works with user-defined classes too:

class Person {
  name: string
  constructor(name: string) {
    this.name = name
  }
}

const john = new Person('John')
const isPerson = isInstanceOf(Person) // guard for 'Person'
isPerson(john) // true

Optional and nullable types

Use isOptional, isNullable, and isNullish to quickly create guards for optional and nullable types:

import { isOptional, isNullable, isNullish } from 'ts-guardian'

const isOptionalNumber = isOptional('number') // guard for 'number | undefined'
const isNullableNumber = isNullable('number') // guard for 'number | null'
const isNullishNumber = isNullish('number') // guard for 'number | null | undefined'

For optional basic types, you can simply pass a string to the is function instead of using isOptional:

import { is } from 'ts-guardian'

const isOptionalString = is('string?') // guard for 'string | undefined'
const isOptionalNumberArray = is('number[]?') // guard for 'number[] | undefined'

Parsing to user-defined types

Consider the following type and its guard:

type Book = {
  title: string
  author: string
}

const isBook = is({
  title: 'string',
  author: 'string',
})

If isBook returns true for a value, that value will be typed as:

{
  title: string
  author: string
}

Ideally, we want to type the value as Book, while avoiding type assertions and user-defined type predicates.

One way is with a parse function that utilizes TypeScript's implicit casting:

const parseBook = (input: any): Book | undefined => {
  return isBook(input) ? input : undefined
}

TypeScript will complain if the type predicate returned from isBook is not compatible with the Book type. This function is type-safe, but defining it is tedious.

Instead, you can use the parserFor function:

import { parserFor } from 'ts-guardian'

const parseBook = parserFor<Book>(isBook)

The parserFor function takes a guard, and returns a function you can use to parse values.

This function acts in the same way as the previous parseBook function. It takes a value and passes it to the guard. If the guard matches, it returns the value typed as the supplied user-defined type. If the guard does not match, the function returns undefined:

const book = {
  title: 'Odyssey',
  author: 'Homer',
}

const film = {
  title: 'Psycho',
  director: 'Alfred Hitchcock',
}

parseBook(book) // book as type 'Book'
parseBook(film) // undefined

The parserFor function is type-safe. TypeScript will complain if you try to create a parser for a user-defined type that isn't compatible with the supplied type guard:

const parseBook = parserFor<Book>(isBook) // Fine
const parseBook = parserFor<Book>(isString) // TypeScript error - type 'string' is not assignable to type 'Book'

Composition

Guards can be composed from existing guards:

const isString = is('string') // guard for 'string'
const isStringOrNumber = isString.or('number') // guard for 'string | number'

You can even pass guards into or:

const isStrOrNum = is('string').or('number') // guard for 'string | number'
const isNullOrUndef = is('null').or('undefined') // guard for 'null | undefined'
// guard for 'string | number | null | undefined'
const isStrOrNumOrNullOrUndef = isStrOrNum.or(isNullOrUndef)

Throwing

Use the requireThat function to throw an error if a value does not match a guard:

import { is, requireThat } from 'ts-guardian'

const value = getSomeUnknownValue()
// Throws an error if type of value is not 'string'
// Error message: Type of '<value>' does not match type guard.
requireThat(value, is('string'))
// Otherwise, type of value is 'string'
value.toUpperCase()

You can optionally pass an error message to requireThat:

import { isUser } from '../myTypeGuards/isUser'

requireThat(value, isUser, 'Value is not a user!')

Type-safe type guards

Consider the following problem:

You fetch data from an API. How do you ensure it's a valid User before using it?

The User type:

type User = {
  id: number
  name: string
  email?: string
  phone?: {
    primary?: string
    secondary?: string
  }
  teamIds: string[]
}

Solution 1 - User-defined type guards 👎

With TypeScript's user-defined type guards, you could write an isUser function to confirm the value is of type User. It might look something like this:

const isUser = (input: any): input is User => {
  const u = input as User
  return (
    typeof u === 'object' &&
    u !== null &&
    typeof u.id === 'number' &&
    typeof u.name === 'string' &&
    (typeof u.email === 'string' || u.email === undefined) &&
    ((typeof u.phone === 'object' &&
      u.phone !== null &&
      (typeof u.phone.primary === 'string' || u.phone.primary === undefined) &&
      (typeof u.phone.secondary === 'string' || u.phone.secondary === undefined)) ||
      u.phone === undefined) &&
    Array.isArray(u.teamIds) &&
    u.teamIds.every(teamId => typeof teamId === 'string')
  )
}

Hard to read, but it works. However, you could also write:

const isUser = (input: any): input is User => {
  return typeof input === 'object'
}

Clearly this function is not enough to confirm that input is of type User, but TypeScript doesn't complain because type predicates are effectively type assertions.

Using type predicates means you potentially lose type safety and introduce runtime errors into your app.

Solution 2 - Primitive-based type guards 👍

Rather than making assumptions about the value, you define a primitive-based type of what a User object looks like, and rely on TypeScript to determine compatibility with the User type:

A primitive-based type is a type constructed from only primitive TypeScript types (string, number, undefined, any, etc...).

import { is, isOptional } from 'ts-guardian'

// We make no assumptions that the data is a user-defined type
const isUser = is({
  id: 'number',
  name: 'string',
  email: 'string?',
  phone: isOptional({
    primary: 'string?',
    secondary: 'string?',
  }),
  teamIds: 'string[]',
})

This is much more readable, and more importantly, it's 100% type-safe.

In this case, the type predicate looks like:

// Type predicate for our primitive-based type
input is {
    id: number
    name: string
    email: string | undefined
    phone: {
        primary: string | undefined
        secondary: string | undefined
    } | undefined
    teamIds: string[]
}

Now TypeScript will tell you if this type is compatible with User:

// TypeScript complains if the primitive-based type is not compatible with 'User'
const parseUser = parserFor<User>(isUser)

If the type from isUser is not compatible with the User, a TypeScript compiler error will let you know. 🎉