Skip to main content
Type guards and assertions provide runtime type safety by narrowing types based on runtime checks.

Type Guards

Type guards are functions that perform runtime checks and narrow TypeScript types using type predicates.

Primitive Type Guards

Check for primitive JavaScript types.
import { isString } from '@zayne-labs/toolkit-type-helpers';

const value: unknown = 'hello';

if (isString(value)) {
  // TypeScript knows value is string here
  console.log(value.toUpperCase()); // OK
}
Type Signatures
export const isString = (value: unknown) => typeof value === "string";
export const isNumber = (value: unknown) => typeof value === "number";
export const isBoolean = (value: unknown) => typeof value === "boolean";
export const isSymbol = (value: unknown) => typeof value === "symbol";

Object Type Guards

Check for various object types and structures.
import { isArray } from '@zayne-labs/toolkit-type-helpers';

const value: unknown = ['a', 'b', 'c'];

if (isArray<string>(value)) {
  // TypeScript knows value is string[] here
  value.forEach(item => console.log(item.toUpperCase()));
}
Signature:
export const isArray = <TArray>(value: unknown): value is TArray[] => 
  Array.isArray(value);

Collection Type Guards

Check for collection types like Map, Set, and FormData.
import { isMap } from '@zayne-labs/toolkit-type-helpers';

const value: unknown = new Map([['key', 'value']]);

if (isMap<Map<string, string>>(value)) {
  // TypeScript knows value is Map<string, string> here
  console.log(value.get('key'));
}

Function Type Guards

Check for function types including async functions.
import { isFunction } from '@zayne-labs/toolkit-type-helpers';

const value: unknown = (x: number) => x * 2;

if (isFunction<(x: number) => number>(value)) {
  // TypeScript knows value is a function here
  const result = value(5); // 10
}
Signature:
export const isFunction = <TFunction extends AnyFunction>(
  value: unknown
): value is TFunction => {
  return typeof value === "function";
};

Browser & Web API Guards

Check for browser-specific types.
import { isFile } from '@zayne-labs/toolkit-type-helpers';

const value: unknown = new File(['content'], 'file.txt');

if (isFile(value)) {
  // TypeScript knows value is File here
  console.log(value.name, value.size);
}

JSON & Serialization Guards

Check for JSON-serializable values and valid JSON strings.
import { isValidJsonString } from '@zayne-labs/toolkit-type-helpers';

const value: unknown = '{"name":"John"}';

if (isValidJsonString(value)) {
  // TypeScript knows value is a valid JSON string
  const parsed = JSON.parse(value);
}

// Returns false for:
isValidJsonString('not json');     // false
isValidJsonString('{invalid}');    // false
isValidJsonString(42);             // false

Advanced Guards

isIterable

Checks if an object is iterable.
import { isIterable } from '@zayne-labs/toolkit-type-helpers';

const arr = [1, 2, 3];
const map = new Map();
const obj = { key: 'value' };

if (isIterable<number>(arr)) {
  for (const item of arr) {
    console.log(item);
  }
}

isIterable(map);  // true
isIterable(arr);  // true
isIterable(obj);  // false
Signature:
export const isIterable = <TIterable>(obj: object): obj is Iterable<TIterable> => 
  Symbol.iterator in obj;

hasObjectPrototype

Checks if a value has Object.prototype.
import { hasObjectPrototype } from '@zayne-labs/toolkit-type-helpers';

hasObjectPrototype({});              // true
hasObjectPrototype([]);              // false
hasObjectPrototype(new Date());      // false
hasObjectPrototype(Object.create(null)); // false
Signature:
export const hasObjectPrototype = (value: unknown) => {
  return Object.prototype.toString.call(value) === "[object Object]";
};

Assertions

Assertion functions throw errors when conditions aren’t met and narrow types.

assert

General-purpose assertion function with overloads.
import { assert } from '@zayne-labs/toolkit-type-helpers';

function divide(a: number, b: number) {
  assert(b !== 0, 'Division by zero');
  return a / b;
}

divide(10, 2);  // OK
divide(10, 0);  // Throws: AssertionError: Assertion failed: Division by zero
Type Signature
type AssertOptions = {
  message: string;
};

type AssertFn = {
  (condition: boolean, messageOrOptions?: string | AssertOptions): asserts condition;

  <TValue>(
    value: TValue,
    messageOrOptions?: string | AssertOptions
  ): asserts value is NonNullable<TValue>;
};

export const assert: AssertFn = (input: unknown, messageOrOptions?: string | AssertOptions) => {
  if (input === false || input == null) {
    const message = isString(messageOrOptions) ? messageOrOptions : messageOrOptions?.message;
    throw new AssertionError(message);
  }
};

assertDefined

Asserts that a value is not null or undefined.
import { assertDefined } from '@zayne-labs/toolkit-type-helpers';

function processConfig(config: Config | null | undefined) {
  assertDefined(config);
  // TypeScript knows config is Config here (not null/undefined)
  console.log(config.apiUrl);
}

// Usage
processConfig(config);      // OK if config is defined
processConfig(null);        // Throws: AssertionError: The value passed is "null!"
processConfig(undefined);   // Throws: AssertionError: The value passed is "undefined!"
Type Signature
export const assertDefined = <TValue>(value: TValue) => {
  if (value == null) {
    throw new AssertionError(`The value passed is "${value as null | undefined}!"`);
  }
  return value;
};

assertENV

Asserts that an environment variable is defined.
import { assertENV } from '@zayne-labs/toolkit-type-helpers';

// In Node.js or similar environments
const apiKey = assertENV(
  process.env.API_KEY,
  'API_KEY environment variable is required'
);
// TypeScript knows apiKey is string here (not undefined)

// Use in configuration
const config = {
  apiKey: assertENV(process.env.API_KEY, 'Missing API_KEY'),
  apiUrl: assertENV(process.env.API_URL, 'Missing API_URL'),
};
Type Signature
export const assertENV = (variable: string | undefined, message?: string) => {
  if (variable === undefined) {
    throw new AssertionError(message);
  }
  return variable;
};

AssertionError

Custom error class thrown by assertion functions.
import { AssertionError } from '@zayne-labs/toolkit-type-helpers';

try {
  assert(false, 'Something went wrong');
} catch (error) {
  if (error instanceof AssertionError) {
    console.log(error.name);    // 'AssertionError'
    console.log(error.message); // 'Assertion failed: Something went wrong'
  }
}
Class Definition
export class AssertionError extends Error {
  override name = "AssertionError";

  constructor(message?: string) {
    const prefix = "Assertion failed";
    super(message ? `${prefix}: ${message}` : message);
  }
}

Practical Examples

API Response Validation

import { isObject, isString, isNumber, assert } from '@zayne-labs/toolkit-type-helpers';

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

function parseUserResponse(response: unknown): User {
  assert(isObject(response), 'Response must be an object');
  
  const { id, name, email } = response as Record<string, unknown>;
  
  assert(isNumber(id), 'User ID must be a number');
  assert(isString(name), 'User name must be a string');
  assert(isString(email), 'User email must be a string');
  
  return { id, name, email };
}

Form Data Processing

import { isFormData, assert } from '@zayne-labs/toolkit-type-helpers';

function handleFormSubmit(data: unknown) {
  assert(isFormData(data), 'Expected FormData');
  
  const name = data.get('name');
  const email = data.get('email');
  
  assert(isString(name), 'Name is required');
  assert(isString(email), 'Email is required');
  
  // Process form data...
}

Configuration Loading

import { assertENV, isString, isNumber } from '@zayne-labs/toolkit-type-helpers';

function loadConfig() {
  const config = {
    apiKey: assertENV(process.env.API_KEY, 'API_KEY is required'),
    apiUrl: assertENV(process.env.API_URL, 'API_URL is required'),
    port: Number(process.env.PORT || 3000),
  };
  
  assert(isString(config.apiKey) && config.apiKey.length > 0);
  assert(isNumber(config.port) && config.port > 0);
  
  return config;
}

Type Narrowing with Guards

import { isArray, isString, isNumber } from '@zayne-labs/toolkit-type-helpers';

function processValue(value: unknown) {
  if (isString(value)) {
    return value.toUpperCase();
  }
  
  if (isNumber(value)) {
    return value.toFixed(2);
  }
  
  if (isArray<string>(value)) {
    return value.join(', ');
  }
  
  throw new Error('Unsupported type');
}

Best Practices

  • Use isObject for general object checks (includes arrays, Maps, etc.)
  • Use isPlainObject for plain JavaScript objects only
  • Use isObjectAndNotArray when you specifically need to exclude arrays
  • Use isValidJsonString for validating JSON strings before parsing
  • Always provide descriptive error messages
  • Include context about what failed and why
  • Use assertENV specifically for environment variables
  • Catch AssertionError specifically when needed
  • Guards narrow types automatically with type predicates
  • Use assertions at function boundaries (e.g., API responses)
  • Combine multiple guards for complex validation
  • Prefer guards over type assertions (as) for runtime safety
  • Guards are runtime checks - use judiciously in hot paths
  • isPlainObject is more expensive than isObject
  • Consider caching validation results for repeated checks
  • Use TypeScript’s compile-time types when possible

See Also

Type Utilities

Type transformation utilities

Enums

Type-safe enum utilities

Build docs developers (and LLMs) love