CaravelaSvelte.SSE (CaravelaSvelte v0.1.0)

Copy Markdown View Source

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.

Summary

Functions

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

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.

Functions

format_patch(ops)

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

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

publish(topic, ops)

@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(pubsub, topic, ops)

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