Visualize.Chart.Builder.Store behaviour (Visualize v0.2.35)

Copy Markdown View Source

The library of saved fragments a host may give Visualize.Chart.Builder (spec/14 §19.7).

The library is the host's: this behaviour is the whole of what the builder knows about it, so the builder never learns where a fragment is kept or when it is written. A host with no library gives no store and loses only the library pane.

An entry is where a fragment gets its identity (§19.1, D-99). The store issues each entry an id — {:style, 11}, {:theme, 32} — from a per-kind sequence it never repeats (§19.2, D-100), and the entry's name is an attribute nothing references. A fragment's body carries no name and no id of its own.

The context is whatever the host passes as the second element of a {module, context} store assign — who is saving, from where — and every callback receives it. A bare module is {module, nil}.

Example

defmodule MyApp.FragmentStore do
  @behaviour Visualize.Chart.Builder.Store

  @impl true
  def list(_context), do: MyApp.Repo.all(from f in Fragment, select: %{id: ..., name: ...})

  @impl true
  def get(id, _context), do: ...

  @impl true
  def put(%{id: nil} = entry, %{user: user}), do: insert(entry, user)
  def put(%{id: id} = entry, %{user: user}), do: update(id, entry, user)

  @impl true
  def delete(id, _context), do: ...

  @impl true
  def import(bundle, context), do: Visualize.Chart.Builder.Store.relocate(bundle, context, &put/2)
end

relocate/3 is the reference implementation of import/2 over put/2; a store with a transaction wraps it in one.

This module compiles only when Phoenix.Component is loaded, as every module of the builder does (spec/10 §1.1, D-45, D-85).

Summary

Types

Whatever the host says about who is saving and from where; nil when it says nothing.

A stored entry. id is nil for one that has not been stored yet; name is the <group>:<sub-group>:<name> attribute of §18.16; kind is derived from the fragment (§19.8) and carried so a listing need not load every body.

An entry's identity: its kind and a number the store never repeats (§19.2).

A store as the builder holds it: a module, or a module with its context.

What list/1 returns for each entry: everything but the body.

Callbacks

Removes an entry. A later reference to its id is a mask (§19.2).

The entry stored under an id, or :error when the store no longer holds it.

Imports a bundle: every entry is issued a new id, references inside the bundle are rewritten through the old => new table, and the whole is committed or nothing is (§19.7, D-103). References to ids outside the bundle are reported, never a failure.

Every entry, without its body, in the order the library shows them.

Stores an entry. One with no id is new and is issued one; one with an id replaces what that id holds. {:error, reason} is shown by the builder and not interpreted.

Functions

A string back to an id, or :error. The kind must be one a fragment can have, so no atom is created from a client's string.

An id as one string, for a data- attribute or an event value.

Imports a bundle through a store's own put/2: the relocation of §19.7.

The module and context of a store assign; a bare module has a nil context.

Types

context()

@type context() :: term()

Whatever the host says about who is saving and from where; nil when it says nothing.

entry()

@type entry() :: %{
  optional(:id) => id() | nil,
  name: String.t(),
  kind: atom(),
  fragment: map()
}

A stored entry. id is nil for one that has not been stored yet; name is the <group>:<sub-group>:<name> attribute of §18.16; kind is derived from the fragment (§19.8) and carried so a listing need not load every body.

id()

@type id() :: {atom(), pos_integer()}

An entry's identity: its kind and a number the store never repeats (§19.2).

store()

@type store() :: module() | {module(), context()}

A store as the builder holds it: a module, or a module with its context.

summary()

@type summary() :: %{id: id(), name: String.t(), kind: atom()}

What list/1 returns for each entry: everything but the body.

Callbacks

delete(id, context)

@callback delete(id(), context()) :: :ok | {:error, term()}

Removes an entry. A later reference to its id is a mask (§19.2).

get(id, context)

@callback get(id(), context()) :: {:ok, entry()} | :error

The entry stored under an id, or :error when the store no longer holds it.

import t, context

@callback import(Visualize.Chart.Builder.Bundle.t(), context()) ::
  {:ok, %{ids: %{required(id()) => id()}, outside: [id()]}} | {:error, term()}

Imports a bundle: every entry is issued a new id, references inside the bundle are rewritten through the old => new table, and the whole is committed or nothing is (§19.7, D-103). References to ids outside the bundle are reported, never a failure.

list(context)

@callback list(context()) :: [summary()]

Every entry, without its body, in the order the library shows them.

put(entry, context)

@callback put(entry(), context()) :: {:ok, id()} | {:error, term()}

Stores an entry. One with no id is new and is issued one; one with an id replaces what that id holds. {:error, reason} is shown by the builder and not interpreted.

Functions

decode_id(string)

@spec decode_id(String.t()) :: {:ok, id()} | :error

A string back to an id, or :error. The kind must be one a fragment can have, so no atom is created from a client's string.

iex> Visualize.Chart.Builder.Store.decode_id("style:11")
{:ok, {:style, 11}}

iex> Visualize.Chart.Builder.Store.decode_id("nonsense:11")
:error

encode_id(arg)

@spec encode_id(id()) :: String.t()

An id as one string, for a data- attribute or an event value.

iex> Visualize.Chart.Builder.Store.encode_id({:style, 11})
"style:11"

relocate(bundle, context, put)

@spec relocate(Visualize.Chart.Builder.Bundle.t(), context(), (entry(), context() ->
                                                           {:ok, id()}
                                                           | {:error, term()})) ::
  {:ok, %{ids: %{required(id()) => id()}, outside: [id()]}} | {:error, term()}

Imports a bundle through a store's own put/2: the relocation of §19.7.

Every entry is put with no id, in bundle order, so the store issues a fresh one; the old => new table is built as it goes; then every entry's fragment is rewritten through the table and put again under its new id. A reference to an id the bundle does not carry is left as it is and reported in outside, and resolves as a mask (§19.2).

A store that can wrap this in a transaction should, so the bundle lands whole or not at all; this function itself has no way to undo a put that succeeded.

split(module)

@spec split(store()) :: {module(), context()}

The module and context of a store assign; a bare module has a nil context.