# `ZenWebsocket.SubscriptionManager`
[🔗](https://github.com/ZenHive/zen_websocket/blob/v0.9.0/lib/zen_websocket/subscription_manager.ex#L1)

Tracks Deribit-dialect JSON-RPC subscriptions for reconnect restoration.

Only `public/subscribe` and `public/unsubscribe` with `params.channels` are
auto-tracked (plus their id-keyed confirmations and rejections). Other
venues' subscribe shapes are ignored and are not automatically restored;
`build_restore_message/1` always emits Deribit's payload.

`Client.subscribe/2` sends no JSON-RPC id, so channels are recorded at send
time. A server-side rejection leaves them in the restore set. Id-carrying
requests wait for a result or error.

Pure functional module — state ownership stays with Client GenServer.

Internal to ZenWebsocket: not listed in `ZenWebsocket.describe/0`. Consumers
use `Client.subscribe/2` (and id-carrying `public/subscribe` via
`Client.send_message/2`); this module only mutates Client-owned state.

## Telemetry Events

The following telemetry events are emitted:

* `[:zen_websocket, :subscription_manager, :add]` - Emitted when a channel is added.
  * Measurements: `%{count: 1}`
  * Metadata: `%{channel: channel}`

* `[:zen_websocket, :subscription_manager, :remove]` - Emitted when a channel is removed.
  * Measurements: `%{count: 1}`
  * Metadata: `%{channel: channel}`

* `[:zen_websocket, :subscription_manager, :restore]` - Emitted when subscriptions are restored.
  * Measurements: `%{channel_count: integer()}`
  * Metadata: `%{channels: [String.t()]}`

## API Functions
| Function | Arity | Description | Param Kinds |
| --- | --- | --- | --- |
| `handle_message` | 2 | Track Deribit public/subscribe and public/unsubscribe requests and confirmations. | `msg: exchange_data`, `state: exchange_data` |
| `build_restore_message` | 1 | Build a restore message for reconnection. | `state: exchange_data` |
| `list` | 1 | List all currently tracked subscriptions. | `state: exchange_data` |
| `remove` | 2 | Remove a channel from the tracked subscription set. | `state: exchange_data`, `channel: value` |
| `add` | 2 | Add a channel to the tracked subscription set. | `state: exchange_data`, `channel: value` |

# `state`

```elixir
@type state() :: %{
  :subscriptions =&gt; MapSet.t(String.t()),
  :config =&gt; %{:restore_subscriptions =&gt; boolean(), optional(atom()) =&gt; term()},
  optional(atom()) =&gt; term()
}
```

Client state map containing subscription fields (subset of Client.state)

# `add`

```elixir
@spec add(state(), String.t()) :: state()
```

Adds a channel to the tracked subscription set.

Used after a subscribe operation is confirmed or when it has no correlation ID.

# `build_restore_message`

```elixir
@spec build_restore_message(state()) :: binary() | nil
```

Builds a restore message for reconnection.

Returns nil if:
- No subscriptions to restore
- `restore_subscriptions` config is false

Returns JSON-encoded subscribe message otherwise.

# `handle_message`

```elixir
@spec handle_message(map(), state()) :: state()
```

Tracks Deribit `public/subscribe` and `public/unsubscribe` requests.

Requests with no `id` (including `Client.subscribe/2`) apply immediately.
Id-carrying requests wait for a result or error. Data ticks and non-Deribit
subscribe shapes are ignored.

# `list`

```elixir
@spec list(state()) :: [String.t()]
```

Lists all currently tracked subscriptions.

# `remove`

```elixir
@spec remove(state(), String.t()) :: state()
```

Removes a channel from the tracked subscription set.

Used after an unsubscribe operation is confirmed or when it has no correlation ID.

---

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