defmodule Topical do @moduledoc """ This module provides the high level interface for interacting with topics. Primarily for subscribing (and unsubscribing), but also for sending requests. After subscribing, a client will initially receive a `{:reset, ref, value}` message, and then subsequent `{:updates, ref, updates}` messages when the value of the topic changes, where `updates` is a list with each item being one of: - `{:set, path, value}`: the `value` has been set at the `path`. - `{:unset, path, key}`: the `key` has been unset from the object at the `path`. - `{:insert, path, index, values}`: the `values` have been inserted into the array at the `path`. - `{:delete, path, index, count}`: `count` values have been deleted from the array at the `path`, from the position `index`. A client can interact directly with a topic by _executing_ actions (which returns a result), or by _notifying_ (without waiting for a result). These are analogous to `GenServer.call/3` and `GenServer.cast/2`. Be aware that a topic is blocked while processing a request. """ alias Topical.Registry @doc """ Returns a specification to start a Topical registry under a supervisor. """ def child_spec(options) do %{ id: Keyword.get(options, :server, Topical), start: {Registry, :start_link, [options]}, type: :supervisor } end @doc """ Subscribes to the specified `topic` (in the specified `registry`). Returns `{:ok, ref}`, where the `ref` is a reference to the subscription. The `pid` will be send messages, as described above. ## Example Topical.subscribe(MyApp.Topical, ["lists", "foo"], self()) #=> {:ok, #Reference<0.4021726225.4145020932.239110>} """ def subscribe(registry, topic, pid, context \\ nil) do with {:ok, server} <- Registry.get_topic(registry, topic, context) do # TODO: monitor/link server? {:ok, GenServer.call(server, {:subscribe, pid, context})} end end @doc """ Unsubscribes from a `topic` (in the specified `registry`). ## Example Topical.unsubscribe(MyApp.Topical, ["lists", "foo"], ref) """ def unsubscribe(registry, topic, ref) do with {:ok, server} <- Registry.lookup_topic(registry, topic) do GenServer.cast(server, {:unsubscribe, ref}) end end @doc """ Captures the state of the `topic` (in the specified `registry`) without subscribing. ## Example Topical.capture(MyApp.Topical, ["lists", "foo"]) # => {:ok, %{items: %{}, order: []}} """ def capture(registry, topic, context \\ nil) do with {:ok, server} <- Registry.get_topic(registry, topic, context) do {:ok, GenServer.call(server, {:capture, context})} end end @doc """ Executes an action in a `topic`. ## Example Topical.execute(MyApp.Topical, ["lists", "foo"], "add_item", {"Test", false}) #=> {:ok, "item123"} """ def execute(registry, topic, action, args \\ {}, context \\ nil) do with {:ok, server} <- Registry.get_topic(registry, topic, context) do {:ok, GenServer.call(server, {:execute, action, args, context})} end end @doc """ Send a notification to a registry. This is similar to `execute/4`, except no result is waited for. ## Example Topical.notify(MyApp.Topical, ["lists", "foo"], "update_done", {"item123", true}) #=> :ok """ def notify(registry, topic, action, args \\ {}, context \\ nil) do with {:ok, server} <- Registry.get_topic(registry, topic, context) do GenServer.cast(server, {:notify, action, args, context}) end end end