defmodule LiveData do
@moduledoc """
LiveData makes it easy to synchronize a JSON data structure from the server
to a client. Changes to the data structure over time is streamed as minimal
diffs.
LiveData aims to provide a programming model similar to LiveView, but for
arbitrary data structures instead of only HTML.
You write a `render` function in your LiveData, which takes any assigns you
have set as an argument. Within the `render` function you build the data
structure you want synchronized to the client. As you update the `assigns`,
the LiveData library will take care of calling your `render` function,
calculating diffs, and synchronizing to the client.
As with LiveView, your LiveData is just a normal process. The programming
model should feel very familear if you have ever used LiveView before.
## Live-cycle
Unlike with LiveView, the lifecycle of a LiveData is not tied closely to
any given HTTP request.
In order to open a connection to a LiveData from the client, you would
start out by making a connection to the LiveData socket endpoint.
On the LiveData socket connection, the client can then connect to any
number of LiveDatas.
A LiveData ends when either:
* The client closes the LiveData socket connection. This will cause all
LiveDatas that live on that socket to close.
* The client closes the individual LiveData. This will cause any other
LiveDatas on the socket to remain open.
* The LiveData crashes or disconnects from the server side.
On a server-side crash or disconnect, it is up to the client to decide
how to handle it. A common behaviour might be to reconnect.
## Keys
Using keys are very important if you want the LiveData library to generate
efficient diffs for you.
Keys allow you to give the LiveData library a hint that it can identify a
piece of data uniquely by a given identifier.
As a rule of thumb, you should use a key whenever are looping over and
generating output from something dynamic.
### Example
Say you have the following LiveData which returns a list of users:
```elixir
deft render(assigns) do
for user <- assigns[:users] do
%{
id: user.id,
name: user.name,
[...]
}
end
end
```
Because LiveData has no way of knowing which parts of your data structure
it can use as a comparison key when diffing, this would produce inefficient
diffs.
The data for users can be uniquely identified by `user.id`. We can inform
LiveData of that fact by doing the following:
```elixir
deft render(assigns) do
for user <- assigns[:users] do
keyed user.id, %{
id: user.id,
name: user.name,
[...]
}
end
end
```
This will make LiveData produce efficient diffs whether the users in
the list change order, any individual properties change, or other changes.
"""
alias LiveData.{Socket, Tracked}
@type rendered :: any()
@callback mount(params :: any(), Socket.t()) :: {:ok, Socket.t()}
@callback handle_event(event :: any(), Socket.t()) :: {:ok, Socket.t()}
@callback handle_info(message :: any(), Socket.t()) :: {:ok, Socket.t()}
@callback render(Socket.assigns()) :: rendered()
@callback __tracked__render__(Socket.assigns()) :: Tracked.tree()
@optional_callbacks mount: 2, handle_event: 2, handle_info: 2
defmacro __using__(opts) do
quote bind_quoted: [opts: opts] do
use LiveData.Tracked
import LiveData
@behaviour LiveData
end
end
def assign(%Socket{assigns: assigns} = socket, key, value) do
assigns = Map.put(assigns, key, value)
%{socket | assigns: assigns}
end
def debug_prints?, do: false
end