Error Handling
Overview
All operations that can fail in expected ways use the Result<T, E> type instead of throwing exceptions. This makes error handling explicit, type-safe, and composable. The pattern is inspired by Rust's Result type and pairs naturally with ts-pattern for exhaustive error matching.
Rules
Use the Result Type
Define success and failure as a tuple where the first element is the error (or null) and the second is the value (or null). Destructure the tuple to check which case occurred.
Construct success and failure tuples directly:
For CLI handlers, use the HandlerResult specialization with ok() and fail() constructors from src/lib/result.ts:
Return Results for Expected Failures
Use Result<T, E> for operations that can fail in expected ways such as parsing, validation, file I/O, and external calls. Define a specific error interface for each domain.
Correct
Incorrect
Wrap Async Operations
Use a wrapper to convert promise rejections into Result tuples.
Correct
Define Domain-Specific Results
Create type aliases for consistency within a domain. This keeps function signatures short and error types discoverable.
Correct
Chain Results with Early Returns
Use early returns to chain multiple Result-producing steps. Each step bails out on the first error.
Correct
Handle Multiple Error Types
Use destructuring and early returns to handle different error types. For exhaustive handling of multiple error variants, combine with ts-pattern.
Correct
Never Throw in Result-Returning Functions
A function that declares Result as its return type must never throw. All failure paths must return an error tuple.
Correct
Incorrect
Always Check Results Before Accessing Values
Never access the value element without first confirming the error element is null. Destructure the tuple and check the error before using the value.
Correct
Incorrect
References
- Types -- Discriminated union patterns
- Conditionals -- ts-pattern for error handling