# `Etcher.Storage`
[🔗](https://github.com/alexdont/etcher/blob/v0.1.0/lib/etcher/storage.ex#L1)

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 the
    `etcher_annotations` table — generated by `mix 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. 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

# `annotation`

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

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

# `create`

```elixir
@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`

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

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

# `list_for`

```elixir
@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`

```elixir
@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`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
