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

Lifecycle hook helpers for Musubi runtime stages.

## Stages

| Stage              | Arity | Hook arguments                          |
| :----------------- | :---- | :-------------------------------------- |
| `:before_command`  | 3     | `(command_name, payload, socket)`       |
| `:after_command`   | 4     | `(command_name, payload, reply, socket)`|
| `:handle_async`    | 3     | `(name, async_result, socket)`          |
| `:handle_info`     | 2     | `(message, socket)`                     |
| `:after_render`   | 2     | `(resolved_elixir_term, socket)`        |
| `:after_serialize` | 2     | `(wire_term, socket)`                   |

`:after_render` runs after `Musubi.Resolver` substitutes child placeholders;
it sees the Elixir-form output (atom keys, structs, atom values).
`:after_serialize` runs after `Musubi.Wire.to_wire/1` converts the resolved
output to wire form (string keys, plain maps, atoms-as-strings).

# `hook_entry`

```elixir
@type hook_entry() :: %{id: hook_id(), fun: hook_fun()}
```

# `hook_fun`

```elixir
@type hook_fun() :: function()
```

# `hook_id`

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

# `hook_result`

```elixir
@type hook_result() ::
  {:cont, Musubi.Socket.t()}
  | {:halt, Musubi.Socket.t()}
  | {:halt, term(), Musubi.Socket.t()}
```

# `hook_table`

```elixir
@type hook_table() :: %{optional(stage()) =&gt; [hook_entry()]}
```

# `stage`

```elixir
@type stage() ::
  :before_command
  | :after_command
  | :handle_async
  | :handle_info
  | :after_render
  | :after_serialize
```

# `attach_hook`

```elixir
@spec attach_hook(Musubi.Socket.t(), hook_id(), stage(), hook_fun()) ::
  Musubi.Socket.t()
```

Attaches a lifecycle hook for the given stage.

## Examples

    iex> socket = %Musubi.Socket{}
    iex> socket =
    ...>   Musubi.Lifecycle.attach_hook(socket, :audit, :after_render, fn _output, socket ->
    ...>     {:cont, socket}
    ...>   end)
    iex> Musubi.Socket.get_private(socket, :hooks)[:after_render] |> length()
    1

# `detach_hook`

```elixir
@spec detach_hook(Musubi.Socket.t(), hook_id(), stage()) :: Musubi.Socket.t()
```

Detaches a lifecycle hook when one is present.

## Examples

    iex> socket =
    ...>   Musubi.Lifecycle.attach_hook(%Musubi.Socket{}, :audit, :after_render, fn _output, socket ->
    ...>     {:cont, socket}
    ...>   end)
    iex> socket = Musubi.Lifecycle.detach_hook(socket, :audit, :after_render)
    iex> Musubi.Socket.get_private(socket, :hooks)
    %{}

# `run_hooks`

```elixir
@spec run_hooks(Musubi.Socket.t(), stage(), list(), boolean()) ::
  {:cont, Musubi.Socket.t()}
  | {:halt, Musubi.Socket.t()}
  | {:halt, term(), Musubi.Socket.t()}
```

Runs every hook registered for a stage until one halts or all continue.

## Examples

    iex> socket =
    ...>   Musubi.Lifecycle.attach_hook(%Musubi.Socket{}, :mark, :after_render, fn _output, socket ->
    ...>     {:cont, Musubi.Socket.assign(socket, :seen?, true)}
    ...>   end)
    iex> {:cont, socket} = Musubi.Lifecycle.run_hooks(socket, :after_render, [%{title: "Inbox"}], false)
    iex> socket.assigns.seen?
    true

# `stage_arity`

```elixir
@spec stage_arity(stage()) :: 2 | 3 | 4
```

Returns the required hook function arity for a lifecycle stage.

| Stage              | Arity | Hook arguments                          |
| :----------------- | :---- | :-------------------------------------- |
| `:before_command`  | 3     | `(command_name, payload, socket)`       |
| `:after_command`   | 4     | `(command_name, payload, reply, socket)`|
| `:handle_async`    | 3     | `(name, async_result, socket)`          |
| `:handle_info`     | 2     | `(message, socket)`                     |
| `:after_render`   | 2     | `(resolved_elixir_term, socket)`        |
| `:after_serialize` | 2     | `(wire_term, socket)`                   |

## Examples

    iex> Musubi.Lifecycle.stage_arity(:before_command)
    3
    iex> Musubi.Lifecycle.stage_arity(:after_command)
    4
    iex> Musubi.Lifecycle.stage_arity(:after_serialize)
    2

# `stages`

```elixir
@spec stages() :: [stage()]
```

Returns the supported lifecycle stages in execution order.

## Examples

    iex> Musubi.Lifecycle.stages()
    [:before_command, :after_command, :handle_async, :handle_info, :after_render, :after_serialize]

---

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