# `Chronicle.Connections.Lifecycle`
[🔗](https://github.com/Cratis/Chronicle.Elixir/blob/main/lib/chronicle/connections/lifecycle.ex#L4)

Tracks the Chronicle connection lifecycle and broadcasts phase changes.

`Lifecycle` is the single source of truth for whether a client is connected
and for the `connection_id` used on the wire. It mirrors the C# client's
`IConnectionLifecycle`: there is one lifecycle per `Chronicle.Client`, every
artifact that needs the connection (the registration coordinator, reactors,
reducers, seeders, webhook and subscription registrars) subscribes to it, and
the connection is driven through a small set of phase transitions.

## Phases

The connection moves through three ordered phases:

  * `:disconnected` — no live session with the kernel.
  * `:connected` — the session handshake completed (the kernel acknowledged
    the `connection_id` via its first keepalive). The channel can carry calls.
  * `:registered` — the registration coordinator has registered the base
    artifacts (event store, event types, read models, constraints,
    projections). Observers may now safely register their observation
    streams, and seeders may run.

Splitting `:connected` from `:registered` is what keeps reducers and reactors
from racing ahead of their server-side definitions — they wait for
`:registered`, never merely `:connected`.

## connection_id

The lifecycle owns the `connection_id`. It is adopted on `connected/2` and
**rotated on `disconnected/1`**, so every reconnect uses a fresh id, exactly
like the C# client. Because the id lives in one place, the value used in the
session handshake and in every reactor/reducer registration is guaranteed
consistent for a given connection epoch.

## Subscribing

Subscribers call `subscribe/1`, which returns the current `{phase,
connection_id}` snapshot *and* delivers it as a message. This closes the race
where a subscriber starts after a phase change has already happened: it always
learns the current phase immediately, whether it subscribed before or after
the transition. Subsequent transitions arrive as messages of the form:

    {:chronicle_lifecycle, phase, connection_id}

where `phase` is `:connected`, `:registered`, or `:disconnected`.

# `phase`

```elixir
@type phase() :: :disconnected | :connected | :registered
```

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `connected`

```elixir
@spec connected(GenServer.server(), String.t()) :: :ok
```

Transitions to `:connected`, adopting the given `connection_id`.

Called by `Chronicle.Connections.Session` on the first server keepalive.

# `connection_id`

```elixir
@spec connection_id(GenServer.server()) :: String.t()
```

Returns the current connection id.

# `disconnected`

```elixir
@spec disconnected(GenServer.server()) :: :ok
```

Transitions to `:disconnected` and rotates the `connection_id`.

Called by `Chronicle.Connections.Session` when the session is lost.

# `name_for`

```elixir
@spec name_for(atom()) :: atom()
```

Returns the registered lifecycle name for a client.

# `phase`

```elixir
@spec phase(GenServer.server()) :: phase()
```

Returns the current phase.

# `registered`

```elixir
@spec registered(GenServer.server()) :: :ok
```

Transitions to `:registered`.

Called by the registration coordinator once the base artifacts are registered
with the kernel.

# `start_link`

```elixir
@spec start_link(keyword()) :: GenServer.on_start()
```

Starts a lifecycle process.

## Options

  * `:client` — the owning client name; the process is registered as
    `:"<client>.Lifecycle"`.
  * `:name` — an explicit registered name (overrides `:client`). When neither
    is given the process is unnamed and callers use the returned pid.

# `subscribe`

```elixir
@spec subscribe(GenServer.server()) :: {:ok, phase(), String.t()}
```

Subscribes the calling process to lifecycle phase changes.

Returns `{:ok, phase, connection_id}` with the current snapshot and also sends
the caller a `{:chronicle_lifecycle, phase, connection_id}` message so the
subscribe-time and transition-time code paths are identical.

# `wait_until`

```elixir
@spec wait_until(GenServer.server(), phase(), timeout()) :: :ok | {:error, :timeout}
```

Blocks until the connection reaches at least `target_phase`.

Returns `:ok` immediately if the current phase is already at or beyond the
target, otherwise waits up to `timeout` milliseconds. Returns `{:error,
:timeout}` if the target is not reached in time.

---

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