Etcher.Storage behaviour (etcher v0.1.0)

Copy Markdown View Source

Behaviour for Etcher annotation persistence.

Etcher's <Etcher.layer> component doesn't run any persistence itself — it fires etcher:created / :updated / :deleted events to the consumer's LiveView. Consumers persist via either:

Why a behaviour

Etcher is generic; downstream apps often have richer needs than the default schema can express. PhoenixKit, for example, links each annotation to a phoenix_kit_comments discussion thread, so its adapter opens a transaction that creates both rows. Other consumers might fan out to multi-tenant tables, log to an audit trail, etc.

Etcher itself doesn't depend on any specific Repo — the bundled Etcher.Storage.Default reads config :etcher, repo: … at runtime.

Example custom adapter

defmodule MyApp.AnnotationStorage do
  @behaviour Etcher.Storage

  alias MyApp.Repo
  alias MyApp.Annotation

  def create(attrs) do
    %Annotation{}
    |> Annotation.changeset(attrs)
    |> Repo.insert()
  end

  def list_for(target_type, target_uuid) do
    Repo.all(
      from a in Annotation,
      where: a.target_type == ^target_type and a.target_uuid == ^target_uuid
    )
  end

  def update(uuid, attrs) do
    Repo.get(Annotation, uuid)
    |> Annotation.changeset(attrs)
    |> Repo.update()
  end

  def delete(uuid) do
    case Repo.get(Annotation, uuid) do
      nil -> {:error, :not_found}
      row -> Repo.delete(row)
    end
  end
end

Summary

Types

An annotation as a plain map (or whatever your adapter returns).

Callbacks

Persist a new annotation. Returns {:ok, annotation} or {:error, reason}.

Delete an annotation by uuid. Returns :ok or {:error, reason}.

Return all annotations for a given target. Plain list; order by position ascending if your schema supports it.

Update an annotation by uuid. Same attrs shape as create/1 but typically with just :geometry (after the user drags a shape) or :style.

Types

annotation()

@type annotation() :: map() | struct()

An annotation as a plain map (or whatever your adapter returns).

Callbacks

create(attrs)

@callback create(attrs :: map()) :: {:ok, annotation()} | {:error, term()}

Persist a new annotation. Returns {:ok, annotation} or {:error, reason}.

attrs is the payload emitted by the etcher:created event, with string keys ("target_type", "target_uuid", "kind", "geometry", "creator_uuid", optionally "style" and "metadata").

delete(uuid)

@callback delete(uuid :: String.t()) :: :ok | {:error, term()}

Delete an annotation by uuid. Returns :ok or {:error, reason}.

list_for(target_type, target_uuid)

@callback list_for(target_type :: String.t(), target_uuid :: String.t()) :: [annotation()]

Return all annotations for a given target. Plain list; order by position ascending if your schema supports it.

update(uuid, attrs)

@callback update(uuid :: String.t(), attrs :: map()) ::
  {:ok, annotation()} | {:error, term()}

Update an annotation by uuid. Same attrs shape as create/1 but typically with just :geometry (after the user drags a shape) or :style.