# `CaravelaSvelte.SSE`
[🔗](https://github.com/rsousacode/caravela_svelte/blob/v0.1.0/lib/caravela_svelte/sse.ex#L1)

Server-Sent Events adapter for `:rest` mode real-time updates.

Turns a long-lived HTTP connection into an opt-in push channel:
server code broadcasts a JSON-Patch via `Phoenix.PubSub`, this
plug forwards each patch to subscribed browsers as an SSE
`event: patch` frame. The client-side `subscribe()` helper
parses the frame and hands the ops to the caller (typically
`applyPatch($pageState.props, ops)`).

Scope: SSE is one-way server → client. Pages that need
bidirectional real-time should use `:live` mode.

## Publishing

    CaravelaSvelte.SSE.publish("dashboard:user:42", [
      ["replace", "/counter", 7]
    ])

The topic is an opaque string matched against subscriptions.
Consumers should namespace it to avoid collisions with other
apps sharing the PubSub.

## Subscribing (route opt-in)

Wire up via `CaravelaSvelte.Router`:

    caravela_rest "/dashboard", DashboardController, realtime: true

This registers `GET /dashboard/__events` → this plug. The
client calls `subscribe("dashboard:user:42", onPatch)` which
opens an `EventSource` at that path, passing the topic as a
query-string parameter.

## Options (plug init / route opts)

  * `:pubsub` — the `Phoenix.PubSub` module. Falls back to
    `config :caravela_svelte, :pubsub, MyApp.PubSub`.
  * `:heartbeat_ms` — interval between keep-alive comments
    (default `15_000`). Proxies sometimes close idle connections
    silently; a comment every 15 s keeps the stream warm.
  * `:retry_ms` — initial `retry:` hint sent once on connect.
    EventSource uses this when auto-reconnecting. Default `3_000`.
  * `:topic_prefix` — required prefix for accepted topics.
    Defaults to `""` (any). Set this to avoid clients subscribing
    to topics the app did not intend to expose.

## Heartbeat & reconnect

EventSource auto-reconnects. We send `retry: <ms>\n\n` once on
open; browsers honour it. Heartbeat comments flow every
`:heartbeat_ms` milliseconds; they carry no payload and exist
only to exercise the TCP path.

## Termination

The plug exits when the client disconnects (the next `chunk/2`
returns `{:error, :closed}`). The process-dictionary has no
lingering PubSub subscription because the BEAM cleans up
automatically when the handler process exits.

# `format_patch`

```elixir
@spec format_patch(list()) :: iodata()
```

Format a JSON-Patch ops list as an SSE frame. Pure — exported
for tests.

# `publish`

```elixir
@spec publish(String.t(), list()) :: :ok | {:error, term()}
```

Broadcast a JSON-Patch `ops` list on `topic` so subscribed SSE
clients receive it. Looks up the PubSub from app config when
not passed explicitly.

`ops` is a list of compressed patch entries matching the
client-side `applyPatch` input shape — e.g.
`[["replace", "/counter", 7]]`.

# `publish`

```elixir
@spec publish(module(), String.t(), list()) :: :ok
```

---

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