defmodule Hyphen do @moduledoc """ Behaviour for Hyphen backends. A Hyphen backend defines the data shape sent to a JS frontend via WebSocket (Phoenix Channels) and handles typed actions received back. """ @type socket :: Phoenix.Socket.t() @callback mount_static(Plug.Conn.t(), params :: map()) :: map() @callback mount(socket(), params :: map()) :: {:ok, socket()} | {:ok, socket(), keyword()} | {:error, map()} @callback handle_params(map(), socket()) :: {:noreply, socket()} @callback handle_event(String.t(), map(), socket()) :: {:noreply, socket()} | {:reply, {:ok | :error, map()}, socket()} @callback handle_info(term(), socket()) :: {:noreply, socket()} @callback handle_task(term(), term(), socket()) :: {:noreply, socket()} @doc false @callback __hyphen_opts__ :: keyword() @optional_callbacks handle_task: 3 defmacro __using__(opts) do quote do @behaviour unquote(__MODULE__) import unquote(__MODULE__) @doc false def __hyphen_opts__ do unquote(opts) end def handle_params(_params, socket) do {:noreply, socket} end def handle_info(_msg, socket) do {:noreply, socket} end defoverridable __hyphen_opts__: 0, handle_params: 2, handle_info: 2 end end @spec assign(socket(), list() | map()) :: socket() def assign(socket, kvs) when is_list(kvs) when is_map(kvs) do Hyphen.Channel.assign_changes(socket, kvs) end @spec assign(socket(), atom(), term()) :: socket() def assign(socket, key, value) do Hyphen.Channel.assign_changes(socket, [{key, value}]) end @spec unassign(socket(), atom()) :: socket() def unassign(socket, key) do Hyphen.Channel.unassign(socket, key) end @spec source_assign(socket(), atom(), term(), keyword()) :: socket() def source_assign(socket, key, value, params \\ []) do Hyphen.Channel.assign_source(socket, key, value, Map.new(params)) end # TODO(doc): Keywords are always transformed into a map when passed to the # source module @spec source_update(socket(), atom(), map() | list() | (term() -> {:ok, term()} | {:stop, term()})) :: socket() def source_update(socket, key, params) when is_map(params) when is_list(params) do Hyphen.Channel.update_source(socket, key, Map.new(params)) end def source_update(socket, key, fun) when is_function(fun, 1) do Hyphen.Channel.update_source(socket, key, fun) end @spec source_refresh(socket(), atom()) :: socket() def source_refresh(socket, key) do # Force rendering the source by updating with a no-op function source_update(socket, key, &{:ok, &1}) end end