Result<T, E> — a discriminated union — instead of throwing exceptions. This makes error handling explicit, type-safe, and impossible to accidentally ignore.
The CLI is the exception — it uses Unix exit codes instead of Result types. See CLI Error Codes.
The type
- When
okistrue,valueholds the success payload. - When
okisfalse,errorholds the typed error.
ok check.
Why not throw?
BlindCast usesResult in the player, uploader, and internal packages for these reasons:
- Errors are part of the API contract. A function’s return type documents what can go wrong — callers can’t miss it.
- No unexpected try/catch. You won’t accidentally swallow a crypto error in a generic catch block.
- TypeScript narrowing works naturally. After
if (!result.ok), TypeScript knowsresult.erroris typed andresult.valueis not accessible. - Compatible with async/await.
Resultcomposes cleanly withPromise<Result<T, E>>— no need to mixtry/catchwithawait.
Pattern 1: Early return
The simplest pattern — bail out immediately on failure:Pattern 2: Switch on error code
When different errors need different handling:Pattern 3: Unwrap helper
If you prefer to throw in contexts where any error is fatal (e.g., scripts, tests):Where you’ll encounter Result types
| Deliverable | Uses Result? | Error type |
|---|---|---|
| Player | Yes | PlayerError with PlayerErrorCode |
| Uploader | Yes | UploaderError with UploaderErrorCode |
| CLI | No — uses exit codes | See CLI exit codes |
| Key Server Docker | No — uses HTTP status codes | 400, 401, 403, 500 |
| Crypto (internal) | Yes | CryptoError with CryptoErrorCode |
TypeScript tips
Never access .value without checking .ok first
The error type varies per deliverable
Each library defines its own error type with a specificcode union:
{ code: ErrorCode; message: string }. PlayerError additionally has an optional cause?: unknown field for wrapping underlying hls.js errors.