Type-safe, standardized error handling for TypeScript/JavaScript
Clean, opinionated error handling with explicit Result pattern and structured error normalization.
stderr-lib gives you two focused tools:
stderr()– normalize any error-like value into a standardStdErrortryCatch()– wrap sync/async code in a type-safeResultunion
Use it when you want:
- Consistent, serializable errors (including cause chains and nested errors)
- Explicit, type-checked error handling instead of ad-hoc
try/catch - Built‐in defenses against runaway structures (max depth, properties, array length)
Advanced patterns (timeouts, limit tuning, security, performance, testing) live in
docs/BestPractices.md.
npm install stderr-lib
pnpm add stderr-lib
yarn add stderr-lib
import { stderr } from 'stderr-lib'; try { await riskyOperation(); } catch (error: unknown) { const err = stderr(error); console.log(err.toString()); // Includes message, stack (if present), cause chain, custom properties, everything! logger.error('Operation failed', err); // Works with typical loggers }
import { tryCatch, type Result } from 'stderr-lib'; interface UserDto { id: string; name: string; } // You can pass an async function - type is inferred as Promise<Result<UserDto>> const result = await tryCatch<UserDto>(async () => { const response = await fetch('/api/user/123'); if (!response.ok) { throw new Error(`Request failed - ${response.status}`); // will be converted to StdError } return response.json() as Promise<UserDto>; }); if (!result.ok) { // You are forced to handle the error explicitly console.error('Request failed:', result.error.toString()); return null; } // In the success branch, value is non-null and correctly typed as UserDto console.log('User name:', result.value.name);
Normalize any value into a StdError instance.
stderr(input: unknown, options?: NormalizeOptions): StdError;
Key points:
- Accepts anything: native
Error, strings, objects,null/undefined, arrays, third‐party errors - Preserves:
name,message,stack(if present)cause,errors(AggregateError / validation errors)- Custom properties (e.g.,
code,statusCode, metadata)- One exception: a property literally named
Symbol(stderr_maxDepth)is always dropped. Versions<= 2.2.0leaked that key into output as internal state, so it is quarantined and removed on normalization to heal errors captured by those versions (see ADR-007).
- One exception: a property literally named
- Adds safe defaults to avoid DoS:
maxDepth(default8, range1–1000)maxProperties(default1000, range1–100000)maxArrayLength(default10000, range1–1000000)
See Best Practices – Normalization & Limits for detailed guidance and examples.
All stderr() results are instances of StdError.
Important properties (non‐exhaustive):
name?: stringmessage?: stringstack?: stringcause?: unknownerrors?: unknown(arrays or maps of nested errors)[key: string]: unknown– arbitrary extra metadata
Important methods:
toString(): string– human‐readable, multi‐line representation with cause chain and nested errorstoJSON(): object– JSON‐safe representation suitable for logging systems and transports
Deep dives:
Wrap a function, async function, or Promise and always get a Result instead of thrown exceptions.
// Sync function -> Result tryCatch<T>(fn: () => T): Result<T, StdError>; // Async function or Promise -> Promise<Result> tryCatch<T>(fn: Promise<T> | (() => Promise<T>)): Promise<Result<T, StdError>>; // Sync function with custom error -> Result tryCatch<T, E>(fn: () => T, mapError: (err: StdError) => E): Result<T, E>; // Async function or Promise with custom error -> Promise<Result> tryCatch<T, E>(fn: Promise<T> | (() => Promise<T>), mapError: (err: StdError) => E): Promise<Result<T, E>>;
Key features:
- Accepts Promises directly: Pass
fetch(url)or() => fetch(url)– both work - Automatic sync/async detection: Returns
Result<T>for sync,Promise<Result<T>>for async - Optional error transformation: Use
mapErrorto convertStdErrorto your custom error type
Result<T, E> is:
type Result<T, E = StdError> = { ok: true; value: T; error: null } | { ok: false; value: null; error: E };
This forces you to handle both branches explicitly in TypeScript.
More patterns:
- Promise Handling
- Common Patterns – API Error Handling
- Exception Usage – When to Use stderr vs tryCatch
You can tune normalization limits globally via properties on stderr:
import { stderr } from 'stderr-lib'; stderr.maxDepth = 10; // Default: 8, range: 1–1000 stderr.maxProperties = 500; // Default: 1000, range: 1–100000 stderr.maxArrayLength = 5000; // Default: 10000, range: 1–1000000
Invalid values throw TypeError/RangeError instead of silently misconfiguring:
stderr.maxDepth = 0; // RangeError stderr.maxDepth = 3.5; // TypeError stderr.maxProperties = 200000; // RangeError stderr.maxArrayLength = 2000000; // RangeError
For environment‐specific tuning, see Best Practices – Security & DoS Protection and Performance Tips .
- Web apps (frontend and backend)
- Node.js services and APIs
- Business, financial, or e‐commerce systems
- Logging / monitoring / error‐reporting services
- Codebases that prefer explicit Result‐style error handling
- Hard real‐time or safety‐critical systems (avionics, life‐critical medical devices, automotive safety, etc.)
- Environments that require certified runtimes or deterministic timing
(These environments generally should not use JavaScript/TypeScript at all.)
- BestPractices.md – deep dive:
- Promise handling patterns and timeouts
- Exception usage, mutation, and error immutability
- Security/sanitization and DoS protections
- Performance tuning and environment‐specific limits
- Testing strategies and property‐based tests
- Custom error classes vs
StdError, common patterns, and anti‐patterns
- ADR.md – architectural decisions
- TypeScript Coding Standard – broader Result‐pattern guidance
Contributions are welcome. Please:
- Follow existing code style and patterns
- Add tests for new behavior
- Update documentation where relevant
- Run linting and tests before submitting (see
package.jsonscripts)
MIT © Michael L. Hobbs