# `ZenWebsocket.Recorder`
[🔗](https://github.com/ZenHive/zen_websocket/blob/v0.4.2/lib/zen_websocket/recorder.ex#L1)

Pure functions for WebSocket session recording and replay.

Records WebSocket frames to JSONL format (one JSON object per line) for
debugging, testing, and analysis. Each entry includes timestamp, direction,
frame type, and data.

## JSONL Format

    {"ts":"2026-01-20T15:30:45.123456Z","dir":"out","type":"text","data":"..."}
    {"ts":"2026-01-20T15:30:45.234567Z","dir":"in","type":"text","data":"..."}
    {"ts":"2026-01-20T15:30:46.000000Z","dir":"in","type":"binary","data":"base64...","binary":true}

## Usage

    # Format an entry for recording
    line = Recorder.format_entry(:out, {:text, "hello"}, DateTime.utc_now())

    # Parse an entry from a recording
    {:ok, entry} = Recorder.parse_entry(line)

    # Replay a recording
    Recorder.replay("/tmp/session.jsonl", fn entry ->
      # Process each entry (e.g., send to handler, accumulate stats)
      handle_entry(entry)
    end)

    # Get metadata about a recording
    {:ok, meta} = Recorder.metadata("/tmp/session.jsonl")
    # => %{count: 150, duration_ms: 5234, first_ts: ~U[...], last_ts: ~U[...]}

## API Functions
| Function | Arity | Description | Param Kinds |
| --- | --- | --- | --- |
| `metadata` | 1 | Get metadata about a recorded session. | `path: value` |
| `replay` | 3 | Replay a recorded session, calling handler for each entry. | `path: value`, `handler_fn: value`, `opts: value` |
| `parse_entry` | 1 | Parse a JSONL line back into an entry map. | `line: value` |
| `format_entry` | 3 | Format a WebSocket frame as a JSONL line for recording. | `direction: value`, `frame: value`, `timestamp: value` |

# `direction`

```elixir
@type direction() :: :in | :out
```

# `entry`

```elixir
@type entry() :: %{
  ts: DateTime.t(),
  dir: direction(),
  type: :text | :binary | :close,
  data: binary(),
  binary: boolean()
}
```

# `frame`

```elixir
@type frame() ::
  {:text, binary()} | {:binary, binary()} | {:close, integer(), binary()}
```

# `format_entry`

```elixir
@spec format_entry(direction(), frame(), DateTime.t()) :: binary()
```

Formats a WebSocket frame as a JSONL line for recording.

## Parameters

- `direction` - `:in` for received frames, `:out` for sent frames
- `frame` - The WebSocket frame tuple `{:text, data}`, `{:binary, data}`, or `{:close, code, reason}`
- `timestamp` - The timestamp for this entry (default: current UTC time)

## Examples

    iex> line = Recorder.format_entry(:out, {:text, "hello"}, ~U[2026-01-20 15:30:45.123456Z])
    ~s({"ts":"2026-01-20T15:30:45.123456Z","dir":"out","type":"text","data":"hello"})

    iex> line = Recorder.format_entry(:in, {:binary, <<1, 2, 3>>}, ~U[2026-01-20 15:30:45Z])
    ~s({"ts":"2026-01-20T15:30:45.000000Z","dir":"in","type":"binary","data":"AQID","binary":true})

# `metadata`

```elixir
@spec metadata(binary()) :: {:ok, map()} | {:error, term()}
```

Gets metadata about a recorded session.

Returns information about the recording including entry count, duration,
and timestamp range.

## Examples

    {:ok, meta} = Recorder.metadata("/tmp/session.jsonl")
    # => %{
    #      count: 150,
    #      duration_ms: 5234,
    #      first_ts: ~U[2026-01-20 15:30:45.123456Z],
    #      last_ts: ~U[2026-01-20 15:30:50.357456Z],
    #      inbound: 100,
    #      outbound: 50
    #    }

# `parse_entry`

```elixir
@spec parse_entry(binary()) :: {:ok, entry()} | {:error, term()}
```

Parses a JSONL line back into an entry map.

## Examples

    iex> {:ok, entry} = Recorder.parse_entry(~s({"ts":"2026-01-20T15:30:45.123456Z","dir":"out","type":"text","data":"hello"}))
    iex> entry.dir
    :out
    iex> entry.data
    "hello"

# `replay`

```elixir
@spec replay(binary(), (entry() -&gt; any()), keyword()) :: :ok | {:error, term()}
```

Replays a recorded session by streaming the file and calling the handler for each entry.

## Options

- `:realtime` - If true, delays between entries match the original timing (default: false)

## Examples

    # Fast replay
    Recorder.replay("/tmp/session.jsonl", fn entry ->
      IO.puts("#{entry.dir}: #{entry.type}")
    end)

    # Realtime replay
    Recorder.replay("/tmp/session.jsonl", &IO.inspect/1, realtime: true)

---

*Consult [api-reference.md](api-reference.md) for complete listing*
