> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blindcast.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Result Type Pattern

> How BlindCast uses Result discriminated unions instead of thrown exceptions. Covers the three main consumption patterns with TypeScript narrowing examples.

Every fallible operation in BlindCast returns `Result<T, E>` — a discriminated union — instead of throwing exceptions. This makes error handling explicit, type-safe, and impossible to accidentally ignore.

<Note>
  The CLI is the exception — it uses Unix exit codes instead of Result types. See [CLI Error Codes](/reference/error-codes#cli-exit-codes).
</Note>

## The type

```typescript theme={null}
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E }
```

* When `ok` is `true`, `value` holds the success payload.
* When `ok` is `false`, `error` holds the typed error.

TypeScript narrows the type automatically based on the `ok` check.

## Why not throw?

BlindCast uses `Result` in the player, uploader, and internal packages for these reasons:

1. **Errors are part of the API contract.** A function's return type documents what can go wrong — callers can't miss it.
2. **No unexpected try/catch.** You won't accidentally swallow a crypto error in a generic catch block.
3. **TypeScript narrowing works naturally.** After `if (!result.ok)`, TypeScript knows `result.error` is typed and `result.value` is not accessible.
4. **Compatible with async/await.** `Result` composes cleanly with `Promise<Result<T, E>>` — no need to mix `try/catch` with `await`.

## Pattern 1: Early return

The simplest pattern — bail out immediately on failure:

```typescript theme={null}
import { createPlayer } from "@blindcast/player"

const result = createPlayer(videoEl, {
  keyServerUrl: "https://keys.example.com/keys",
})

if (!result.ok) {
  console.error("Player creation failed:", result.error.code)
  return
}

const player = result.value // TypeScript knows this is BlindcastPlayer here
player.load(manifestUrl)
```

## Pattern 2: Switch on error code

When different errors need different handling:

```typescript theme={null}
import { createPlayer } from "@blindcast/player"
import type { PlayerErrorCode } from "@blindcast/player"

const result = createPlayer(videoEl, { keyServerUrl })

if (!result.ok) {
  switch (result.error.code as PlayerErrorCode) {
    case "UNSUPPORTED_BROWSER":
      showFallbackMessage("Your browser does not support encrypted video")
      break
    case "INVALID_VIDEO_ELEMENT":
      console.error("Bug: video element not found in DOM")
      break
    case "KEY_FETCH_FAILED":
      showRetryButton()
      break
    default:
      console.error("Unexpected error:", result.error.code, result.error.message)
  }
  return
}

const player = result.value
```

## Pattern 3: Unwrap helper

If you prefer to throw in contexts where any error is fatal (e.g., scripts, tests):

```typescript theme={null}
function unwrap<T, E extends { code: string; message: string }>(
  result: { ok: true; value: T } | { ok: false; error: E },
  context?: string,
): T {
  if (!result.ok) {
    throw new Error(`${context ? context + ": " : ""}${result.error.code} — ${result.error.message}`)
  }
  return result.value
}

// Usage
const player = unwrap(createPlayer(videoEl, opts), "createPlayer")
```

## 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](/reference/error-codes#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

```typescript theme={null}
// TypeScript will error — value might not exist
const key = result.value

// Narrow first
if (result.ok) {
  const key = result.value // CryptoKey
}
```

### The error type varies per deliverable

Each library defines its own error type with a specific `code` union:

```typescript theme={null}
import type { PlayerError, PlayerErrorCode } from "@blindcast/player"
import type { UploaderError, UploaderErrorCode } from "@blindcast/uploader"
```

All error types share the shape `{ code: ErrorCode; message: string }`. `PlayerError` additionally has an optional `cause?: unknown` field for wrapping underlying hls.js errors.
