# `Musubi.Socket`
[🔗](https://github.com/fahchen/musubi/blob/v0.3.0/lib/musubi/socket.ex#L1)

Socket struct and assign helpers for Musubi runtimes.

# `assign_key`

```elixir
@type assign_key() :: term()
```

Keys written into the assigns map.

# `callback_error_reason`

```elixir
@type callback_error_reason() ::
  :unauthorized | :not_found | :invalid_params | :forbidden
```

Application-level denial reasons returned by socket callbacks.

# `callback_result`

```elixir
@type callback_result() :: {:ok, t()} | :error | {:error, callback_error_reason()}
```

Return shape for Musubi socket lifecycle callbacks.

# `connect_info`

```elixir
@type connect_info() :: map()
```

Phoenix connect_info data captured when the Musubi socket connects.

# `connect_params`

```elixir
@type connect_params() :: map()
```

Parameters supplied when the transport socket connects.

# `join_params`

```elixir
@type join_params() :: map()
```

Parameters supplied when a Musubi connection joins.

# `path_segment`

```elixir
@type path_segment() :: atom() | String.t()
```

Path segments identifying a store's parent path.

# `private_key`

```elixir
@type private_key() :: term()
```

The private bookkeeping map carried by the socket.

# `root_params`

```elixir
@type root_params() :: map()
```

Client params supplied for the current root store mount.

# `roots`

```elixir
@type roots() :: [module()]
```

Root store modules that may be mounted through this socket.

# `session`

```elixir
@type session() :: map()
```

Session data shared by all root stores on one Musubi socket.

# `store_id`

```elixir
@type store_id() :: [String.t()]
```

Runtime identity of a store node — array of local ids from root.

# `t`

```elixir
@type t() :: %Musubi.Socket{
  assigns: map(),
  endpoint: module() | nil,
  id: String.t() | nil,
  module: module() | nil,
  parent_path: [path_segment()],
  private: map(),
  topic: String.t() | nil,
  transport_pid: pid() | nil
}
```

# `handle_connect`

```elixir
@callback handle_connect(params :: connect_params(), socket :: t()) :: callback_result()
```

Runs once when the transport socket connects.

Use this callback for connection-level authentication and assigns that should
be visible to every Musubi connection and mounted root store.

# `handle_join`

```elixir
@callback handle_join(params :: join_params(), socket :: t()) :: callback_result()
```

Runs once when a Musubi connection joins.

Use this callback for connection-level scope checks such as workspace or
tenant authorization. It does not receive root store module or id data; those
are validated later when the root store is mounted.

# `__using__`
*macro* 

```elixir
@spec __using__(keyword()) :: Macro.t()
```

Declares a Musubi socket module.

The generated module is a Phoenix socket adapter internally, but application
code implements only Musubi callbacks.

## Examples

    defmodule MyAppWeb.UserSocket do
      use Musubi.Socket,
        roots: [
          MyApp.Stores.DashboardStore,
          MyApp.Stores.PollRoomStore
        ]

      @impl Musubi.Socket
      def handle_connect(%{"token" => token}, socket) do
        {:ok, Musubi.Socket.assign(socket, :token, token)}
      end

      @impl Musubi.Socket
      def handle_join(_params, socket), do: {:ok, socket}
    end

# `any_changed?`

```elixir
@spec any_changed?(t()) :: boolean()
```

Returns whether any assign key is marked as changed since the last render cycle.

## Examples

    iex> Musubi.Socket.any_changed?(%Musubi.Socket{})
    false
    iex> socket = Musubi.Socket.assign(%Musubi.Socket{}, :title, "Inbox")
    iex> Musubi.Socket.any_changed?(socket)
    true

# `assign`

```elixir
@spec assign(t(), keyword(term()) | map()) :: t()
```

Assigns many keys from a keyword list or map.

## Examples

    iex> socket = %Musubi.Socket{}
    iex> socket = Musubi.Socket.assign(socket, %{title: "Inbox", count: 2})
    iex> socket.assigns.title
    "Inbox"
    iex> socket.assigns.count
    2

# `assign`

```elixir
@spec assign(t(), assign_key(), term()) :: t()
```

Assigns one key on the socket and records the change in `__changed__`.

## Examples

    iex> socket = %Musubi.Socket{}
    iex> socket = Musubi.Socket.assign(socket, :title, "Inbox")
    iex> socket.assigns.title
    "Inbox"
    iex> socket.assigns.__changed__
    %{title: true}

# `assign_new`

```elixir
@spec assign_new(t(), assign_key(), (-&gt; term())) :: t()
```

Assigns a value to `key` only if `key` is not already present in the socket's
assigns. `fun` is invoked lazily and receives no arguments.

Useful for setting defaults that should not overwrite parent-supplied values.
When `key` is already present the socket is returned unchanged and the
`__changed__` map is not touched.

## Examples

    iex> socket = Musubi.Socket.assign_new(%Musubi.Socket{}, :count, fn -> 0 end)
    iex> socket.assigns.count
    0
    iex> socket = Musubi.Socket.assign_new(socket, :count, fn -> 99 end)
    iex> socket.assigns.count
    0

# `changed?`

```elixir
@spec changed?(t(), assign_key()) :: boolean()
```

Returns whether the given assign key is marked as changed.

## Examples

    iex> socket = Musubi.Socket.assign(%Musubi.Socket{}, :title, "Inbox")
    iex> Musubi.Socket.changed?(socket, :title)
    true
    iex> Musubi.Socket.changed?(socket, :count)
    false

# `connect_info`

```elixir
@spec connect_info(t()) :: connect_info()
```

Returns Phoenix connect_info data captured when the Musubi socket connected.

## Examples

    iex> Musubi.Socket.connect_info(%Musubi.Socket{})
    %{}

# `consumed_keys_changed?`

```elixir
@spec consumed_keys_changed?(t(), [assign_key()]) :: boolean()
```

Returns whether any consumed key appears in the socket's `__changed__` map.

## Examples

    iex> socket = Musubi.Socket.assign(%Musubi.Socket{}, :title, "Inbox")
    iex> Musubi.Socket.consumed_keys_changed?(socket, [:title, :count])
    true
    iex> Musubi.Socket.consumed_keys_changed?(socket, [:count])
    false

# `fetch_root_by_module`

```elixir
@spec fetch_root_by_module(module(), String.t()) :: {:ok, module()} | :error
```

Fetches a declared root store module by its client module string.

The string is compared against modules already declared in the socket;
Musubi does not convert arbitrary strings into atoms.

## Examples

    iex> defmodule SocketFetchRootByModuleDoc do
    ...>   defmodule Store do
    ...>     use Musubi.Store, root: true
    ...>
    ...>     state do
    ...>       field :ok, boolean()
    ...>     end
    ...>
    ...>     def render(_socket), do: %{ok: true}
    ...>     def handle_command(_name, _payload, socket), do: {:noreply, socket}
    ...>   end
    ...>
    ...>   use Musubi.Socket, roots: [Store]
    ...> end
    iex> Musubi.Socket.fetch_root_by_module(SocketFetchRootByModuleDoc, "SocketFetchRootByModuleDoc.Store")
    {:ok, SocketFetchRootByModuleDoc.Store}
    iex> Musubi.Socket.fetch_root_by_module(SocketFetchRootByModuleDoc, "Missing.Store")
    :error

# `get_private`

```elixir
@spec get_private(t(), private_key(), term()) :: term()
```

Reads a private runtime value.

## Examples

    iex> socket = %Musubi.Socket{private: %{hooks: %{}}}
    iex> Musubi.Socket.get_private(socket, :hooks)
    %{}
    iex> Musubi.Socket.get_private(socket, :missing, :fallback)
    :fallback

# `inherit_context`

```elixir
@spec inherit_context(t(), t()) :: t()
```

Copies shared Musubi context from one socket to another.

The copied context includes session and connect_info. Root params are
intentionally per-root and are not copied.

## Examples

    iex> source = Musubi.Socket.put_session(%Musubi.Socket{}, %{"user_id" => "u1"})
    iex> target = Musubi.Socket.inherit_context(source, %Musubi.Socket{})
    iex> Musubi.Socket.session(target)
    %{"user_id" => "u1"}

# `put_connect_info`

```elixir
@spec put_connect_info(t(), connect_info()) :: t()
```

Stores Phoenix connect_info data on the Musubi socket.

## Examples

    iex> socket = Musubi.Socket.put_connect_info(%Musubi.Socket{}, %{peer_data: %{address: {127, 0, 0, 1}}})
    iex> Musubi.Socket.connect_info(socket)
    %{peer_data: %{address: {127, 0, 0, 1}}}

# `put_private`

```elixir
@spec put_private(t(), private_key(), term()) :: t()
```

Writes a private runtime value.

## Examples

    iex> socket = Musubi.Socket.put_private(%Musubi.Socket{}, :hooks, %{})
    iex> socket.private.hooks
    %{}

# `put_root_params`

```elixir
@spec put_root_params(t(), root_params()) :: t()
```

Stores the current root store mount params in socket private context.

## Examples

    iex> socket = Musubi.Socket.put_root_params(%Musubi.Socket{}, %{"poll_id" => "p1"})
    iex> Musubi.Socket.root_params(socket)
    %{"poll_id" => "p1"}

# `put_session`

```elixir
@spec put_session(t(), session()) :: t()
```

Stores session data shared by all root stores on one Musubi socket.

## Examples

    iex> socket = Musubi.Socket.put_session(%Musubi.Socket{}, %{"user_id" => "u1"})
    iex> Musubi.Socket.session(socket)
    %{"user_id" => "u1"}

# `reset_changed`

```elixir
@spec reset_changed(t()) :: t()
```

Clears the LiveView-style `__changed__` bookkeeping after a render cycle.

## Examples

    iex> socket = Musubi.Socket.assign(%Musubi.Socket{}, :title, "Inbox")
    iex> Musubi.Socket.reset_changed(socket).assigns.__changed__
    %{}

# `root_params`

```elixir
@spec root_params(t()) :: root_params()
```

Returns the params supplied when the current root store was mounted.

## Examples

    iex> Musubi.Socket.root_params(%Musubi.Socket{})
    %{}

# `session`

```elixir
@spec session(t()) :: session()
```

Returns the session data shared by all root stores on one Musubi socket.

## Examples

    iex> Musubi.Socket.session(%Musubi.Socket{})
    %{}

# `store_id`

```elixir
@spec store_id(t()) :: store_id()
```

Returns the runtime identity (`store_id`) of the store node owning this socket.

The store_id is the array of local id strings from the root down to this
node. The root has `store_id = []`. Each non-root node has
`store_id = parent_path ++ [id]`.

## Examples

    iex> Musubi.Socket.store_id(%Musubi.Socket{parent_path: [], id: ""})
    []

    iex> Musubi.Socket.store_id(%Musubi.Socket{parent_path: [], id: "filters"})
    ["filters"]

    iex> Musubi.Socket.store_id(%Musubi.Socket{parent_path: ["filters"], id: "primary"})
    ["filters", "primary"]

# `update`

```elixir
@spec update(t(), assign_key(), (term() -&gt; term())) :: t()
```

Updates one assign by applying `fun` to the current value.

## Examples

    iex> socket = Musubi.Socket.assign(%Musubi.Socket{}, :count, 1)
    iex> socket = Musubi.Socket.update(socket, :count, &(&1 + 1))
    iex> socket.assigns.count
    2

---

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