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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bpits/type-guards

v1.1.2

Published

A TypeScript library for building robust, type-safe runtime validators with compile-time property validation enforcement and comprehensive debugging capabilities.

Readme

@bpits/type-guards

Build

A TypeScript library that provides a fluent, type-safe way to build runtime type guards with compile-time validation and helpful debugging features.

Table of Contents

Installation

npm install @bpits/type-guards

Quick Start

import { StrictTypeGuardBuilder, CommonTypeGuards } from '@bpits/type-guards';

interface User {
  id: string;
  name: string;
  email: string;
}

const isUser = StrictTypeGuardBuilder
  .start<User>('User')
  .validateProperty('id', CommonTypeGuards.basics.string())
  .validateProperty('name', CommonTypeGuards.basics.string())
  .validateProperty('email', CommonTypeGuards.basics.string())
  .build();

// Usage
const data: unknown = { id: "123", name: "John", email: "[email protected]" };
if (isUser(data)) {
  // data is now typed as User
  console.log(data.name); // TypeScript knows this is safe
}

Why Use This Library?

The Problem

When working with external data (APIs, user input, file parsing), we often face a choice between type safety and development speed. Traditional type guards become verbose and error-prone:

// Traditional approach - verbose and error-prone
function isUser(obj: unknown): obj is User {
  return (
    typeof obj === 'object'
    && obj !== null
    && typeof (obj as User).id === 'string'  // Easy to make mistakes
    && typeof (obj as User).name === 'string'
    && typeof (obj as User).email === 'string'
    // Forgot to validate age? Compiler won't help you
  );
}

The Solution

This library provides:

  • Compile-time safety: TypeScript ensures you validate all properties
  • Runtime debugging: Helpful console warnings when validation fails
  • Fluent API: Easy to read and maintain
  • Reusable validators: Common type guards included
  • Flexible validation: Support for complex nested structures

Basic Usage

Simple Object Validation

import { StrictTypeGuardBuilder, CommonTypeGuards } from '@bpits/type-guards';

interface Person {
  name: string;
  age: number;
}

const isPerson = StrictTypeGuardBuilder
  .start<Person>('Person')
  .validateProperty('name', CommonTypeGuards.basics.string())
  .validateProperty('age', CommonTypeGuards.basics.number())
  .build();

// Usage
const data: unknown = JSON.parse('{"name": "Alice", "age": 30}');
if (isPerson(data)) {
  console.log(`${data.name} is ${data.age} years old`);
}

Nullable Types

interface UserProfile {
  username: string;
  bio: string | null;
  avatar: string | undefined;
}

const isUserProfile = StrictTypeGuardBuilder
  .start<UserProfile>('UserProfile')
  .validateProperty('username', CommonTypeGuards.basics.string())
  .validateProperty('bio', CommonTypeGuards.basics.string().nullable(null))
  .validateProperty('avatar', CommonTypeGuards.basics.string().nullable(undefined))
  .build();

Optional Properties and Ignoring Fields

Sometimes you want to ignore certain properties (perhaps they're computed or internal):

interface ApiResponse {
  data: string;
  timestamp: number;
  _internal: any; // We don't care about this field
}

const isApiResponse = StrictTypeGuardBuilder
  .start<ApiResponse>('ApiResponse')
  .validateProperty('data', CommonTypeGuards.basics.string())
  .validateProperty('timestamp', CommonTypeGuards.basics.number())
  .ignoreProperty('_internal') // No validation, no warnings
  .build();

Built-in Type Guards

Basic Types

CommonTypeGuards.basics.string()      // string
CommonTypeGuards.basics.number()      // number  
CommonTypeGuards.basics.boolean()     // boolean
CommonTypeGuards.basics.object()      // object

// Nullable versions using fluent interface
CommonTypeGuards.basics.string().nullable()       // string | null | undefined
CommonTypeGuards.basics.string().nullable(null)   // string | null
CommonTypeGuards.basics.number().nullable()       // number | null | undefined
CommonTypeGuards.basics.boolean().nullable()      // boolean | null | undefined
CommonTypeGuards.basics.object().nullable()       // object | null | undefined

Date Validation

CommonTypeGuards.date.date()           // Date object
CommonTypeGuards.date.dateString()     // Valid date string

// Nullable versions
CommonTypeGuards.date.date().nullable()        // Date | null | undefined
CommonTypeGuards.date.dateString().nullable()  // Valid date string | null | undefined

// Usage
interface Event {
  name: string;
  date: Date;
  created: string; // ISO date string
  cancelled?: Date; // Optional cancellation date
}

const isEvent = StrictTypeGuardBuilder
  .start<Event>('Event')
  .validateProperty('name', CommonTypeGuards.basics.string())
  .validateProperty('date', CommonTypeGuards.date.date())
  .validateProperty('created', CommonTypeGuards.date.dateString())
  .validateProperty('cancelled', CommonTypeGuards.date.date().nullable(undefined))
  .build();

Array Validation

CommonTypeGuards.array.array()                    // Array<unknown>
CommonTypeGuards.array.arrayOf(typeGuard)         // Array<T>

// Nullable versions
CommonTypeGuards.array.array().nullable()                    // Array<unknown> | null | undefined
CommonTypeGuards.array.arrayOf(typeGuard).nullable()         // Array<T> | null | undefined

// Usage
interface TodoList {
  items: string[];
  priorities: number[];
  archived?: string[]; // Optional archived items
}

const isTodoList = StrictTypeGuardBuilder
  .start<TodoList>('TodoList')
  .validateProperty('items', CommonTypeGuards.array.arrayOf(CommonTypeGuards.basics.string()))
  .validateProperty('priorities', CommonTypeGuards.array.arrayOf(CommonTypeGuards.basics.number()))
  .validateProperty('archived', CommonTypeGuards.array.arrayOf(CommonTypeGuards.basics.string()).nullable(undefined))
  .build();

Enum Validation

CommonTypeGuards.enums.memberOf(enumObject)       // T[keyof T] - validates enum values
CommonTypeGuards.enums.keyOf(enumObject)          // keyof T - validates enum keys

// Nullable versions
CommonTypeGuards.enums.memberOf(enumObject).nullable()       // T[keyof T] | null | undefined
CommonTypeGuards.enums.memberOf(enumObject).nullable(null)   // T[keyof T] | null
CommonTypeGuards.enums.keyOf(enumObject).nullable()          // keyof T | null | undefined
CommonTypeGuards.enums.keyOf(enumObject).nullable(null)      // keyof T | null

// Usage with string enum
enum Status {
  Active = 'active',
  Inactive = 'inactive',
  Pending = 'pending'
}

// Usage with numeric enum
enum Priority {
  Low = 1,
  Medium = 2,
  High = 3
}

// Usage with plain object (acts like enum)
const Colors = {
  Red: 'red',
  Green: 'green',
  Blue: 'blue'
} as const;

interface Task {
  title: string;
  status: Status;                                    // enum value
  priority: Priority;                                // enum value
  statusKey?: keyof typeof Status;                   // enum key
  color?: typeof Colors[keyof typeof Colors];       // object value
}

const isTask = StrictTypeGuardBuilder
  .start<Task>('Task')
  .validateProperty('title', CommonTypeGuards.basics.string())
  .validateProperty('status', CommonTypeGuards.enums.memberOf(Status))        // validates 'active', 'inactive', 'pending'
  .validateProperty('priority', CommonTypeGuards.enums.memberOf(Priority))     // validates 1, 2, 3
  .validateProperty('statusKey', CommonTypeGuards.enums.keyOf(Status).nullable(undefined))  // validates 'Active', 'Inactive', 'Pending'
  .validateProperty('color', CommonTypeGuards.enums.memberOf(Colors).nullable(undefined))   // validates 'red', 'green', 'blue'
  .build();

// Example usage demonstrating the difference
const statusValue = 'active';        // This is a Status enum value
const statusKey = 'Active';          // This is a Status enum key

if (CommonTypeGuards.enums.memberOf(Status)(statusValue)) {
  // statusValue is now typed as Status ('active' | 'inactive' | 'pending')
}

if (CommonTypeGuards.enums.keyOf(Status)(statusKey)) {
  // statusKey is now typed as keyof typeof Status ('Active' | 'Inactive' | 'Pending')
}

Advanced Usage

Custom Type Guards

Create your own type guard predicates for complex validation:

interface User {
  id: string;
  email: string;
  role: 'admin' | 'user' | 'guest';
}

// Custom email validator
const isEmail = (obj: unknown): obj is string => {
  return typeof obj === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(obj);
};

// Custom role validator
const isValidRole = (obj: unknown): obj is 'admin' | 'user' | 'guest' => {
  return obj === 'admin' || obj === 'user' || obj === 'guest';
};

const isUser = StrictTypeGuardBuilder
  .start<User>('User')
  .validateProperty('id', CommonTypeGuards.basics.string())
  .validateProperty('email', isEmail)
  .validateProperty('role', isValidRole)
  .build();

Nested Object Validation

interface Address {
  street: string;
  city: string;
  zipCode: string;
}

interface Person {
  name: string;
  address: Address;
  secondaryAddress?: Address;
}

// First, create a type guard for the nested object
const isAddress = StrictTypeGuardBuilder
  .start<Address>('Address')
  .validateProperty('street', CommonTypeGuards.basics.string())
  .validateProperty('city', CommonTypeGuards.basics.string())
  .validateProperty('zipCode', CommonTypeGuards.basics.string())
  .build();

// Then use it in the parent object
const isPerson = StrictTypeGuardBuilder
  .start<Person>('Person')
  .validateProperty('name', CommonTypeGuards.basics.string())
  .validateProperty('address', isAddress)
  .validateProperty('secondaryAddress', isAddress.nullable(undefined))
  .build();

Root-Level Validation

Sometimes you need to validate the entire object structure or want to bypass individual property validation. The validateRoot() method allows you to validate the complete object and automatically satisfies the StrictTypeGuardBuilder's requirement to validate all properties:

interface Coordinates {
  x: number;
  y: number;
}

// Custom validator for the entire object
const isValidCoordinates = (obj: unknown): obj is Coordinates => {
  if (typeof obj !== 'object' || !obj) return false;
  const coords = obj as Coordinates;
  return typeof coords.x === 'number' 
    && typeof coords.y === 'number'
    && coords.x >= 0 && coords.x <= 100 
    && coords.y >= 0 && coords.y <= 100;
};

// Root validation allows immediate build() - no individual properties needed
const isCoordinates = StrictTypeGuardBuilder
  .start<Coordinates>('Coordinates')
  .validateRoot(isValidCoordinates)
  .build(); // ✅ Compiles immediately

// You can also combine root validation with property validation
const isCoordinatesWithDetails = StrictTypeGuardBuilder
  .start<Coordinates>('Coordinates')
  .validateProperty('x', CommonTypeGuards.basics.number())
  .validateProperty('y', CommonTypeGuards.basics.number())
  .validateRoot(isValidCoordinates) // Additional validation on top
  .build();

Key Point: validateRoot() tells the StrictTypeGuardBuilder that you're handling the entire object validation yourself, so it won't require individual property validations.

Nullable Objects

Build type guards that accept null or undefined:

const isUserOrNull = StrictTypeGuardBuilder
  .start<User>('User')
  .validateProperty('id', CommonTypeGuards.basics.string())
  .validateProperty('name', CommonTypeGuards.basics.string())
  .validateProperty('email', CommonTypeGuards.basics.string())
  .build().nullable(); // Returns (obj: unknown) => obj is User | null | undefined

// Or be more specific about which nullish values to allow
const isUserOrJustNull = StrictTypeGuardBuilder
  .start<User>('User')
  .validateProperty('id', CommonTypeGuards.basics.string())
  .validateProperty('name', CommonTypeGuards.basics.string())
  .validateProperty('email', CommonTypeGuards.basics.string())
  .build().nullable(null); // Returns (obj: unknown) => obj is User | null

TypeGuardBuilder vs StrictTypeGuardBuilder

This library provides two builders:

StrictTypeGuardBuilder (Recommended)

  • Compile-time validation: Ensures all properties are validated or ignored
  • Type safety: Prevents you from forgetting to validate properties
  • Better developer experience: Clear error messages guide you to missing validations
  • Strict type matching: Requires type guards to exactly match the property type (e.g., string | null requires string | null, not just string)

Important: When using StrictTypeGuardBuilder, you must validate or ignore all properties before calling build() or build().nullable(). If you don't, TypeScript will show a compile error indicating which properties are missing validation.

Exception: If you call validateRoot(), it assumes the entire object structure will be validated at the root level, allowing you to call build() or build().nullable() immediately without validating individual properties.

TypeGuardBuilder

  • More flexible: Allows partial property validation
  • Runtime warnings: Shows warnings for unvalidated properties
  • Backward compatibility: Easier migration from manual type guards
// StrictTypeGuardBuilder - compile error if you miss a property
const strictGuard = StrictTypeGuardBuilder
  .start<User>('User')
  .validateProperty('id', CommonTypeGuards.basics.string())
  // Missing 'name' and 'email' - TypeScript error!
  .build(); // ❌ Compile error: "Missing required properties"

// With root validation - bypasses individual property requirements
const rootValidatedGuard = StrictTypeGuardBuilder
  .start<User>('User')
  .validateRoot(myCustomUserValidator) // Validates entire object
  .build(); // ✅ Compiles immediately

// TypeGuardBuilder - allows partial validation
const flexibleGuard = TypeGuardBuilder
  .start<User>('User')
  .validateProperty('id', CommonTypeGuards.basics.string())
  // Missing properties will show runtime warnings
  .build(); // ✅ Compiles, but shows warnings at runtime

Understanding StrictTypeGuardBuilder Compilation Errors

When you forget to validate properties, TypeScript will show helpful error messages:

interface User {
  id: string;
  name: string;
  email: string;
  age: number;
}

const incompleteGuard = StrictTypeGuardBuilder
  .start<User>('User')
  .validateProperty('id', CommonTypeGuards.basics.string())
  .validateProperty('name', CommonTypeGuards.basics.string())
  // Missing 'email' and 'age'
  .build(); // ❌ Error: Missing required properties: "email" | "age"

The error message tells you exactly which properties need validation, making it easy to fix.

Troubleshooting Type Mismatch Errors

When your type guard doesn't match the expected property type, you'll see descriptive TypeScript errors that guide you to the correct fix.

Nullability Mismatch Errors

For nullability mismatches (when the nullable/undefined handling doesn't match), the errors follow this format:

TS2345: Argument of type TypeGuardPredicateWithNullable<number> is not assignable to parameter of type
"Incompatibile nullability: Property expects 'null' but type guard doesn't handle it. Add .nullable(null)"

The error message tells you exactly what's wrong and how to fix it:

  • "Property expects 'null' but type guard doesn't handle it. Add .nullable(null)" - Your property is T | null but you're using a non-nullable type guard
  • "Property expects 'undefined' but type guard doesn't handle it. Add .nullable(undefined)" - Your property is T | undefined but you're using a non-nullable type guard
  • "Property expects 'null | undefined' but type guard doesn't handle nullability. Add .nullable()" - Your property is T | null | undefined but you're using a non-nullable type guard
  • "Property is not nullable but type guard handles nullability. Remove .nullable()" - Your property is just T but you're using .nullable()

These nullability validation errors ensure strict type matching between your interface properties and type guards, preventing runtime type mismatches involving null and undefined values.

Debugging and Logging

The library provides helpful debugging features:

Console Warnings

When validation fails, you'll see detailed console warnings:

// If validation fails, you'll see:
// "Validation failed for property 'email' in 'User'. Value received: 'not-an-email'"
// "No validator specified for property 'unexpected' in 'User'"

Controlling Log Output

You can control whether actual values are logged (useful for sensitive data):

import { TypeGuardBuilder } from '@bpits/type-guards';

// Disable logging of actual values for security
TypeGuardBuilder.LogValueReceived = false;
// Now failed validations will show "redacted" instead of actual values

You can also suppress validation warnings entirely (useful for production environments or testing):

import { TypeGuardBuilder } from '@bpits/type-guards';

// Disable all validation warnings
TypeGuardBuilder.LogValidationWarnings = false;
// Now validation failures will no longer produce console warnings

Real-World Examples

API Response Validation

interface ApiUser {
    id: string;
    username: string;
    email: string;
    profile: {
        firstName: string;
        lastName: string;
        avatar?: string;
    };
    roles: string[];
    lastLogin: string; // ISO date string
    isActive: boolean;
}

const isProfile = StrictTypeGuardBuilder
    .start<ApiUser['profile']>('UserProfile')
    .validateProperty('firstName', CommonTypeGuards.basics.string())
    .validateProperty('lastName', CommonTypeGuards.basics.string())
    .validateProperty('avatar', CommonTypeGuards.basics.string().nullable(undefined))
    .build();

const isApiUser = StrictTypeGuardBuilder
    .start<ApiUser>('ApiUser')
    .validateProperty('id', CommonTypeGuards.basics.string())
    .validateProperty('username', CommonTypeGuards.basics.string())
    .validateProperty('email', CommonTypeGuards.basics.string())
    .validateProperty('profile', isProfile)
    .validateProperty('roles', CommonTypeGuards.array.arrayOf(CommonTypeGuards.basics.string()))
    .validateProperty('lastLogin', CommonTypeGuards.date.dateString())
    .validateProperty('isActive', CommonTypeGuards.basics.boolean())
    .build();

// Usage in API call
async function fetchUser(id: string): Promise<ApiUser | null> {
    const response = await fetch(`/api/users/${id}`);
    const data = await response.json();

    if (isApiUser(data)) {
        return data; // Fully typed as ApiUser
    }

    console.error('Invalid user data received from API');
    return null;
}

Best Practices

1. Use Descriptive Type Names

// Good
StrictTypeGuardBuilder.start<User>('User')
StrictTypeGuardBuilder.start<ApiResponse>('WeatherApiResponse')

// Less helpful
StrictTypeGuardBuilder.start<User>('obj')

2. Create Reusable Validators

// Create common validators for your domain
const isPositiveNumber = (obj: unknown): obj is number => {
    return typeof obj === 'number' && obj > 0;
};

const isValidEmail = (obj: unknown): obj is string => {
    return typeof obj === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(obj);
};

3. Validate at Boundaries

// Validate data when it enters your system
async function fetchUsers(): Promise<User[]> {
    const response = await fetch('/api/users');
    const data = await response.json();

    if (CommonTypeGuards.array.arrayOf(isUser)(data)) {
        return data; // Now safely typed
    }

    throw new Error('Invalid user data from API');
}

4. Combine with Error Handling

function processApiData(data: unknown): User | null {
    try {
        if (isUser(data)) {
            return data;
        }
        console.warn('Data validation failed, using defaults');
        return null;
    } catch (error) {
        console.error('Error processing API data:', error);
        return null;
    }
}

TypeScript Configuration

For the best experience, ensure your tsconfig.json includes:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true
  }
}

Contributing

This library is designed to be extensible. You can contribute by:

  1. Adding new common type guards
  2. Improving error messages
  3. Adding utility functions
  4. Improving documentation

License

MIT License - see LICENSE file for details.