defprotocol Amplified.PubSub.Protocol do @moduledoc ~S''' The protocol that powers `Amplified.PubSub`. Each function in this protocol is dispatched based on the type of its first argument. Built-in implementations exist for `BitString`, `Atom`, `Tuple`, `List`, `Stream`, and `Phoenix.LiveView.Socket`. Struct implementations are generated automatically when you `use Amplified.PubSub` in a module. You generally don't need to call these functions directly — use the delegating functions in `Amplified.PubSub` instead. This module is relevant when you need to implement the protocol manually with `defimpl`. ## Implementing for a struct The easiest way is `use Amplified.PubSub` in your schema module, which generates a full implementation with sensible defaults. If you need more control, write a `defimpl` block and pass `impl: true` to get the defaults as a starting point: defimpl Amplified.PubSub.Protocol, for: MyApp.Blog.Post do use Amplified.PubSub, impl: true # Override just the channel derivation def channel(%{slug: slug}, _ns), do: "post:#{slug}" end ## Return conventions * `broadcast/2,3` — return the subject (struct implementations) or the message (string implementation), enabling pipeline chaining. * `subscribe/1`, `unsubscribe/1` — return the subject for structs, or `:ok` for strings. * `handle_info/2,3,4` — return `{:cont, socket}` when the message was not handled, or `{:halt, socket}` when it was. ''' alias Phoenix.LiveView.Socket @doc """ Broadcasts a message to the channel for the given subject. For struct implementations generated by `use Amplified.PubSub`, atom and binary events are automatically wrapped as `{event, subject}` before broadcasting. The subject is returned for pipeline chaining. For the `BitString` implementation, the first argument is treated as a literal channel name and the message is broadcast as-is via `Phoenix.PubSub.broadcast/3`. """ @spec broadcast( subject :: subject, message :: term() ) :: subject when subject: nil | binary() | struct() | {:ok, struct()} | {:error, term()} def broadcast(subject, message) @doc """ Broadcasts a message with additional attributes to the channel for the given subject. Like `broadcast/2`, but the broadcast payload includes the attributes. For atom/binary events on structs, the payload becomes `{event, subject, attrs}`. """ @spec broadcast( subject :: subject, message :: term(), attrs :: map() ) :: subject when subject: nil | binary() | struct() | {:ok, struct()} | {:error, term()} def broadcast(subject, message, attrs) @doc """ Returns the PubSub channel name for the given subject. For structs, the default implementation derives the channel from the module's last name segment (snake_cased) and the struct's `:id` field, e.g. `"post:abc-123"`. The optional `namespace` is appended with a `:` separator. """ @spec channel( subject :: struct(), namespace :: String.t() | atom() | nil ) :: binary() def channel(subject, namespace \\ nil) @doc """ Subscribes the current process to the PubSub channel for the given subject. The `BitString` implementation subscribes via the configured PubSub server. Struct implementations derive the channel first, then delegate to the string implementation. """ @spec subscribe(binary() | struct()) :: :ok | struct() | {:error, term()} def subscribe(channel) @doc """ Unsubscribes the current process from the PubSub channel for the given subject. """ @spec unsubscribe(binary() | struct()) :: :ok | struct() | {:error, term()} def unsubscribe(channel) @doc """ Dispatches an incoming PubSub message through the protocol. The `Tuple` implementation is the primary dispatcher — it pattern-matches on `{action, subject}` and `{:flash, level, message}` tuples, delegating to the subject's `handle_info/3` when a protocol implementation exists for the subject. Returns `{:cont, socket}` for unhandled messages, `{:halt, socket}` for handled ones. """ @spec handle_info( message :: term(), socket :: Socket.t() ) :: {:cont | :halt, Socket.t()} def handle_info(message, socket) @doc """ Dispatches a message for a specific subject. Called by the Tuple implementation after unpacking `{action, subject}`. Override this in your `use Amplified.PubSub` block to handle events for your struct type. """ @spec handle_info( subject :: struct(), message :: term(), socket :: Socket.t() ) :: {:cont | :halt, Socket.t()} def handle_info(subject, message, socket) @doc """ Dispatches a message for a specific subject with additional attributes. Called by the Tuple implementation after unpacking `{action, subject, attrs}`. Override this in your `use Amplified.PubSub` block to handle events that include a changeset or attributes map. """ @spec handle_info( subject :: struct(), message :: term(), changeset :: map(), socket :: Socket.t() ) :: {:cont | :halt, Socket.t()} def handle_info(subject, message, changeset, socket) end