@rightcapital/assert
Version:
Type-safe assertion utilities for defensive programming.
163 lines (116 loc) • 6.52 kB
Markdown
# @rightcapital/assert
Type-safe assertion utilities for defensive programming in TypeScript applications.
## Key Features
- **Compile-Time Exhaustiveness (`assertExhaustive`)**: Enforce full coverage of union types at compile time with TypeScript `never`.
- **Control-Flow Guard (`assertUnreachable`)**: Mark unreachable branches for control-flow analysis and runtime safety.
- **Expression-Level Assertions (`ensure` / `ensureNonNullable`)**: Assert and return narrowed values in single-line expressions.
- **Type Narrowing (`assert` / `assertNonNullable`)**: Narrow types with standard TypeScript assertion signatures.
- **Unimplemented Stubs (`unimplemented`)**: Mark methods that should throw when called.
## Installation
```bash
npm install @rightcapital/assert
# or
pnpm add @rightcapital/assert
# or
yarn add @rightcapital/assert
```
## Usage Examples
### Compile-Time Exhaustiveness Checking (`assertExhaustive`)
Use `assertExhaustive` in `switch` statements or `if-else` chains. It leverages TypeScript `never` to catch missing cases at compile time.
```typescript
import { assertExhaustive } from '@rightcapital/assert';
type Action = { type: 'open' } | { type: 'close' };
function handleAction(action: Action) {
switch (action.type) {
case 'open':
return 'Opening';
case 'close':
return 'Closing';
default:
// If a new Action type is added without a case,
// TypeScript reports a compile error here.
return assertExhaustive(action);
}
}
```
### Control Flow & Unreachable Code Guard (`assertUnreachable`)
Use `assertUnreachable` for code branches that should never execute. Because `assertUnreachable` returns `never`, TypeScript understands control flow stops at this point.
```typescript
import { assertUnreachable } from '@rightcapital/assert';
function processStatus(status: 'active' | 'inactive') {
if (status === 'active') {
handleActive();
} else if (status === 'inactive') {
handleInactive();
} else {
// TypeScript knows execution stops here because `assertUnreachable` returns `never`.
assertUnreachable(`Unexpected status: ${status}`);
}
}
```
> **Tip**: If your project enforces the ESLint `consistent-return` rule, add `return` before the function call (for example, `return assertUnreachable(...)`) to satisfy ESLint.
### Expression-Level Type Narrowing (`ensure` & `ensureNonNullable`)
Unlike `assert`, `ensure` and `ensureNonNullable` validate a value and return it with a narrowed type. This allows assertions inside single-line expressions or method chains.
```typescript
import { ensure, ensureNonNullable } from '@rightcapital/assert';
// Assert and get a non-nullable value in one expression
const userName = ensureNonNullable(getUser(), 'User must exist').name;
// Assert with a custom type guard
const admin = ensure(currentUser, isAdminUser, 'Admin privileges required');
// `admin` is typed as AdminUser
```
### Basic Assertion & Type Narrowing (`assert` & `assertNonNullable`)
Use `assert` and `assertNonNullable` for standalone statement assertions.
```typescript
import { assert, assertNonNullable } from '@rightcapital/assert';
// Basic condition assertion
assert(user.age >= 18, 'User must be at least 18 years old');
// Non-nullable assertion with type narrowing
assertNonNullable(user, 'User cannot be null');
// `user` is narrowed to NonNullable<User>
```
### Error Handling (`AssertionError`)
All assertion functions throw `AssertionError` (which extends `Error`) when an assertion fails.
```typescript
import { assert, AssertionError } from '@rightcapital/assert';
try {
assert(user.age >= 18, 'User must be at least 18 years old');
} catch (error) {
if (error instanceof AssertionError) {
console.error('Assertion failed:', error.message);
}
}
```
### Unimplemented Stubs (`unimplemented`)
Use `unimplemented` to mark a method that is intentionally not implemented yet. Calling it always throws an `AssertionError`.
```typescript
import { unimplemented } from '@rightcapital/assert';
function saveDraft(): never {
return unimplemented('Draft saving is not available yet');
}
```
## API Summary
| API / Signature | Description / Typical Use Case |
| :------------------------------------------------------------------------------------------- | :---------------------------------------------------- |
| `AssertionError extends Error` | Error thrown on assertion failure |
| `assertExhaustive(value: never, message?: string): never` | Enforce exhaustiveness check in `switch` or `if-else` |
| `assertUnreachable(message?: string): never` | Mark logically unreachable code branches |
| `unimplemented(message?: string): never` | Mark a method stub that always throws |
| `ensure<T, S extends T>(value: T, predicate: (value: T) => value is S, message?: string): S` | Validate and return value with narrowed type |
| `ensureNonNullable<T>(value: T, message?: string): NonNullable<T>` | Validate non-null/undefined and return value |
| `assert(value: boolean, message?: string): asserts value` | Standard boolean condition assertion |
| `assertNonNullable<T>(value: T, message?: string): asserts value is NonNullable<T>` | Standard non-null/undefined assertion |
## Agent Skills
This package ships with an [Agent Skill](https://agentskills.io) that teaches AI coding agents (Claude Code, Cursor, etc.) how to use the assert API correctly, following the [npm-based Agent Skills Convention](https://github.com/antfu/skills-npm).
### Automatic discovery (recommended)
If your project uses [`skills-npm`](https://github.com/antfu/skills-npm), the skill is discovered automatically from `node_modules`:
```bash
npx skills-npm
```
### Manual installation
You can also install the skill directly using the [`skills` CLI](https://github.com/vercel-labs/skills):
```bash
npx skills add https://github.com/RightCapitalHQ/frontend-libraries/tree/main/packages/assert/skills
```
## API Reference
See the [generated documentation](/packages/assert/docs/modules.md) for detailed API reference.