UNPKG

@rightcapital/assert

Version:

Type-safe assertion utilities for defensive programming.

125 lines (98 loc) • 5.32 kB
--- name: assert description: >- Type-safe assertion and validation utilities for defensive TypeScript programming. Use when writing runtime assertions, null checks, type guard validations, unreachable code markers, exhaustive switch/if-else checking, or unimplemented stubs. Import named functions from @rightcapital/assert. license: MIT metadata: author: RightCapital package: '@rightcapital/assert' --- # 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 ```typescript import { assert, AssertionError, assertExhaustive, assertNonNullable, assertUnreachable, ensure, ensureNonNullable, unimplemented, } from '@rightcapital/assert'; ``` ## Quick Reference | API / Signature | Description / Typical Use Case | | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | | `AssertionError extends Error` | Error type thrown when any assertion in this package fails. | | `assert(value: boolean, message?: string): asserts value` | Precondition 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): S` | Inline 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): never` | Exhaustiveness check in `switch` `default` case or `if-else` chain. | | `assertUnreachable(message?: string): never` | Mark logically impossible code paths. | | `unimplemented(message?: string): never` | Mark 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: ```typescript 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. ```typescript // 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. ```typescript 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`. ```typescript // 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}`); } ```