PhiaUi.Editor.CollabServer (phia_ui v0.1.17)

Copy Markdown View Source

GenServer managing a single collaborative document via Operational Transform.

Each document gets its own CollabServer process that maintains the authoritative document state, version counter, and operation history. Clients submit operations along with their last-known version; the server transforms against any intervening operations before applying.

Architecture

Client A apply_op> CollabServer broadcast> PubSub > Client B
Client B apply_op> CollabServer broadcast> PubSub > Client A

State

  • :doc_id — unique document identifier
  • :content — current document text (string)
  • :version — monotonically increasing integer (starts at 0)
  • :history — list of {version, delta, client_id} tuples (newest first)
  • :clientsMapSet of connected client identifiers

Usage

# Start a server for a document
{:ok, pid} = PhiaUi.Editor.CollabServer.start_link(doc_id: "doc-123")

# Client submits an operation
{:ok, transformed_op, new_version} =
  PhiaUi.Editor.CollabServer.apply_op(pid, [%{retain: 5}, %{insert: "!"}], 0)

# Read current state
{"Hello!", 1} = PhiaUi.Editor.CollabServer.get_document(pid)

PubSub Broadcasting

After each successful operation, the server broadcasts on "collab:doc:<doc_id>" via Phoenix.PubSub (configured in :phia_ui, :collab_pubsub). The broadcast payload is:

%{
  event: :op_applied,
  doc_id: doc_id,
  op: transformed_delta,
  version: new_version,
  client_id: client_id
}

History Pruning

History is capped at 1000 entries. Older entries are discarded to bound memory usage. Clients that fall behind by more than 1000 versions must re-fetch the full document.

Summary

Functions

Submit a delta operation from a client.

Returns a specification to start this module under a supervisor.

Get the set of connected client identifiers.

Get the current document content and version.

Get operations from the history since a given version.

Get the current document version.

Register a client as connected to this document.

Unregister a client from this document.

Start a CollabServer process for a document.

Types

t()

@type t() :: %PhiaUi.Editor.CollabServer{
  clients: MapSet.t(),
  content: String.t(),
  doc_id: String.t(),
  history: [{non_neg_integer(), list(), String.t() | nil}],
  version: non_neg_integer()
}

Functions

apply_op(server, delta, client_version, client_id \\ nil)

@spec apply_op(GenServer.server(), list(), non_neg_integer(), String.t() | nil) ::
  {:ok, list(), non_neg_integer()} | {:error, term()}

Submit a delta operation from a client.

The client_version is the version the client's delta was composed against. The server transforms the delta against all operations that occurred between client_version and the current server version, then applies the result.

Returns {:ok, transformed_delta, new_version} on success, or {:error, reason} if the operation cannot be applied.

Parameters

  • server — pid or registered name
  • delta — list of OT operations
  • client_version — the version the client is based on
  • client_id — optional identifier for the submitting client

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

get_clients(server)

@spec get_clients(GenServer.server()) :: MapSet.t()

Get the set of connected client identifiers.

get_document(server)

@spec get_document(GenServer.server()) :: {String.t(), non_neg_integer()}

Get the current document content and version.

Returns {content, version}.

get_ops_since(server, since_version)

@spec get_ops_since(GenServer.server(), non_neg_integer()) ::
  {:ok, [{non_neg_integer(), list(), String.t() | nil}]}
  | {:error, :version_too_old}

Get operations from the history since a given version.

Returns a list of {version, delta, client_id} tuples in ascending version order. If the requested version is older than the oldest entry in history, returns {:error, :version_too_old}.

get_version(server)

@spec get_version(GenServer.server()) :: non_neg_integer()

Get the current document version.

join(server, client_id)

@spec join(GenServer.server(), String.t()) :: :ok

Register a client as connected to this document.

Used for tracking active collaborators. Purely informational.

leave(server, client_id)

@spec leave(GenServer.server(), String.t()) :: :ok

Unregister a client from this document.

start_link(opts)

@spec start_link(keyword()) :: GenServer.on_start()

Start a CollabServer process for a document.

Options

  • :doc_id (required) — unique document identifier
  • :initial_content — starting document text (default "")
  • :name — process name (defaults to via_tuple(doc_id) using the PhiaUi.Collab.RoomRegistry if available, otherwise the pid)