Skip to content

@rightcapital/assert

Type-safe assertion utilities for defensive programming.

assert

125 linesSource

>-

assert

Type-safe assertion utilities for defensive TypeScript programming. All functions throw AssertionError (which extends Error) on failure. Error messages are formatted as ${functionName}: ${message}. When no message is provided, value assertions default to Unexpected ${String(value)}.

Import

ts
import {
  assert,
  AssertionError,
  assertExhaustive,
  assertNonNullable,
  assertUnreachable,
  ensure,
  ensureNonNullable,
  unimplemented,
} from '@rightcapital/assert';

Quick Reference

API / SignatureDescription / Typical Use Case
AssertionError extends ErrorError type thrown when any assertion in this package fails.
assert(value: boolean, message?: string): asserts valuePrecondition or boolean check (verifies value === true).
assertNonNullable<T>(value: T, message?: string): asserts value is NonNullable<T>Guard statement against null or undefined.
ensure<T, S extends T>(value: T, predicate: (value: T) => value is S, message?: string): SInline validation using type guard function (val: T) => val is S.
ensureNonNullable<T>(value: T, message?: string): NonNullable<T>Inline assignment or method chain for non-null value.
assertExhaustive(value: never, message?: string): neverExhaustiveness check in switch default case or if-else chain.
assertUnreachable(message?: string): neverMark logically impossible code paths.
unimplemented(message?: string): neverMark a stub that always throws AssertionError.

Error Handling with AssertionError

All assertion functions throw AssertionError when a condition is not met. Use instanceof AssertionError to catch assertion failures specifically:

ts
import { assert, AssertionError } from '@rightcapital/assert';

try {
  assert(age >= 18, 'User must be an adult');
} catch (error) {
  if (error instanceof AssertionError) {
    console.error('Assertion failed:', error.message);
  } else {
    throw error;
  }
}

Critical Rules for Code Generation

1. assert() Checks Strict Boolean Equality (value !== true)

assert(value) throws if value !== true. It throws for truthy non-boolean values like objects or strings.

  • Incorrect: assert(user); (throws even if user is an object)
  • Correct: assertNonNullable(user);
  • Correct: assert(user.age >= 18, 'Must be 18+');

2. Choose assert* Statements vs ensure* Expressions

  • assert* functions are statements. They narrow the type of an existing variable for subsequent code.
  • ensure* functions are expressions. They return the validated value with a narrowed type for direct assignment or chaining.
ts
// Statement form
assertNonNullable(user);
console.log(user.name);

// Expression form
const userName = ensureNonNullable(getUser()).name;

3. ensure() Requires a Type Guard Predicate

The predicate parameter in ensure(val, predicate) must be a TypeScript type guard ((val: T) => val is S). A standard boolean function without a type predicate will cause a compile error.

ts
function isAdminUser(user: User): user is AdminUser {
  return user.role === 'admin';
}

const admin = ensure(currentUser, isAdminUser, 'Admin required');

4. assertExhaustive vs assertUnreachable

  • Use assertExhaustive(value) when TypeScript can prove all union cases are handled (e.g., switch default case). It catches missing union members at compile time because value must be typed as never.
  • Use assertUnreachable(message) for defensive checks in branches that TypeScript cannot prove unreachable.
  • Prefix with return in non-void functions (for example, return assertExhaustive(val); or return assertUnreachable(msg);) to satisfy ESLint consistent-return.
ts
// Exhaustive switch over union
switch (shape.kind) {
  case 'circle':
    return Math.PI * shape.radius ** 2;
  case 'square':
    return shape.side ** 2;
  default:
    return assertExhaustive(shape);
}

// Unreachable defensive branch
if (status === 'active') {
  handleActive();
} else if (status === 'inactive') {
  handleInactive();
} else {
  return assertUnreachable(`Unexpected status: ${status}`);
}