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:
- the bundled
Etcher.Storage.Default(writes to theetcher_annotationstable — generated bymix etcher.gen.migration) - their own implementation of this behaviour
Why a behaviour
Etcher is generic; downstream apps often have richer needs than the default schema can express. A consumer that pairs every annotation with a comment thread, for example, would wrap creation in a transaction that inserts the annotation row and the comment row together — something the default adapter has no opinion about. Other shapes: fanning out to multi-tenant tables, logging to an audit trail, swapping in a different Repo per request scope, persisting to a non-Ecto store entirely.
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
Callbacks
@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 an annotation by uuid. Returns :ok or {:error, reason}.
@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.
@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.