Macaw.Layers.Store behaviour (macaw_layers v0.2.2)

Copy Markdown View Source

Where documents live (spec/03 §1): the store behavior, the assign's functions, typed ids (§2) and import as a relocation (§3.3).

A store holds entries, %{id, name, fragment}, of the fragment types it was given (spec/02 §1). An entry's kind is derived from its fragment, never stored; its name is an attribute, never an identity. A store is reached through an assign, {module, context} or a bare module, so every call carries who is acting and from where.

Store.put({Macaw.Layers.Store.Memory, agent}, %{id: nil, name: "Styles:thick", fragment: %{style: %{width: 3}}})
#=> {:ok, {:style, 1}}

Ported from visualize's Visualize.Chart.Builder.Store, its callbacks answering {:ok, …} or {:error, …} as every Macaw contract does.

Summary

Types

A store's module and its context, or a bare module, whose context is nil.

A stored fragment: its id, nil before the store issues one, and its name.

An id: a kind of the store's types and a positive integer the store issued.

What an import answers: the old => new table, and each reference outside the bundle.

What a picker lists.

Callbacks

An entry removed.

The entry of an id.

A bundle relocated into the store, whole or not at all (§3.3).

Every entry's summary.

An entry stored: a new id issued to one without, a held one replaced.

The fragment types the store holds.

Functions

The types, checked when a store is configured: each a fragment type, and no kind declared by two of them. Raises ArgumentError naming both types and the kind.

The id a string spells, its kind matched against kinds by its string, so no atom is made from a client's string: {:ok, id} or :error.

An entry removed.

An id as one string, for the DOM and the wire.

The entry of an id.

A name's filing, <group>:<sub-group>:<name>: the segments before the last, empty ones dropped, and the last. Nothing references it.

A bundle imported.

{type, kind} of the first of the store's types that derives the fragment a kind (spec/02 §6.1), or nil: a fragment about more than one thing is not storable.

Every kind of the store's types (an assign, or the types themselves), sorted.

Every entry's summary.

An entry stored.

A reference held to the kind its site takes, before it is resolved (§2): :ok, or {:error, {:ref_kind, wanted, given}} for an id of another kind, for a type's reader to report at the site's path.

A bundle imported through the store's own put/2 (§3.3): every check first, then new ids issued, then every reference rewritten through one table and each entry put again.

The assign as {module, context}; a bare module has a nil context.

The type a kind belongs to, or nil.

The store's types.

Types

assign()

@type assign() :: module() | {module(), term()}

A store's module and its context, or a bare module, whose context is nil.

entry()

@type entry() :: %{id: id() | nil, name: String.t(), fragment: map()}

A stored fragment: its id, nil before the store issues one, and its name.

id()

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

An id: a kind of the store's types and a positive integer the store issued.

imported()

@type imported() :: %{
  ids: %{required(id()) => id()},
  outside: [{id(), Macaw.Layers.Fault.path()}]
}

What an import answers: the old => new table, and each reference outside the bundle.

summary()

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

What a picker lists.

Callbacks

delete(id, context)

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

An entry removed.

get(id, context)

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

The entry of an id.

import t, context

@callback import(Macaw.Layers.Bundle.t(), context :: term()) ::
  {:ok, imported()} | {:error, [Macaw.Layers.Fault.t(), ...]}

A bundle relocated into the store, whole or not at all (§3.3).

list(context)

@callback list(context :: term()) :: {:ok, [summary()]} | {:error, term()}

Every entry's summary.

put(entry, context)

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

An entry stored: a new id issued to one without, a held one replaced.

types(context)

@callback types(context :: term()) :: [module()]

The fragment types the store holds.

Functions

check_types!(types)

@spec check_types!([module()]) :: [module()]

The types, checked when a store is configured: each a fragment type, and no kind declared by two of them. Raises ArgumentError naming both types and the kind.

decode_id(kinds, string)

@spec decode_id([atom()], term()) :: {:ok, id()} | :error

The id a string spells, its kind matched against kinds by its string, so no atom is made from a client's string: {:ok, id} or :error.

iex> Macaw.Layers.Store.decode_id([:style, :mark], "style:11")
{:ok, {:style, 11}}
iex> Macaw.Layers.Store.decode_id([:style, :mark], "widget:11")
:error

delete(assign, id)

@spec delete(assign(), id()) :: :ok | {:error, term()}

An entry removed.

encode_id(arg)

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

An id as one string, for the DOM and the wire.

iex> Macaw.Layers.Store.encode_id({:style, 11})
"style:11"

get(assign, id)

@spec get(assign(), id()) :: {:ok, entry()} | {:error, term()}

The entry of an id.

group(name)

@spec group(String.t()) :: {[String.t()], String.t()}

A name's filing, <group>:<sub-group>:<name>: the segments before the last, empty ones dropped, and the last. Nothing references it.

iex> Macaw.Layers.Store.group("Styles::thick")
{["Styles"], "thick"}

import(assign, bundle)

@spec import(assign(), Macaw.Layers.Bundle.t()) ::
  {:ok, imported()} | {:error, [Macaw.Layers.Fault.t(), ...]}

A bundle imported.

kind(types, fragment)

@spec kind(assign() | [module()], map()) :: {module(), atom()} | nil

{type, kind} of the first of the store's types that derives the fragment a kind (spec/02 §6.1), or nil: a fragment about more than one thing is not storable.

kinds(types)

@spec kinds(assign() | [module()]) :: [atom()]

Every kind of the store's types (an assign, or the types themselves), sorted.

list(assign)

@spec list(assign()) :: {:ok, [summary()]} | {:error, term()}

Every entry's summary.

put(assign, entry)

@spec put(assign(), entry()) :: {:ok, id()} | {:error, term()}

An entry stored.

ref_kind(arg, wanted)

@spec ref_kind(id(), atom()) :: :ok | {:error, {:ref_kind, atom(), atom()}}

A reference held to the kind its site takes, before it is resolved (§2): :ok, or {:error, {:ref_kind, wanted, given}} for an id of another kind, for a type's reader to report at the site's path.

relocate(assign, bundle, opts \\ [])

@spec relocate(assign(), Macaw.Layers.Bundle.t(), keyword()) ::
  {:ok, imported()} | {:error, [Macaw.Layers.Fault.t(), ...]}

A bundle imported through the store's own put/2 (§3.3): every check first, then new ids issued, then every reference rewritten through one table and each entry put again.

The checks: the bundle read again through its forms (Macaw.Layers.Bundle), so its version and every fragment go through their readers, and the host's check: over each entry. A fault anywhere writes nothing; every fault is reported at [:entries, i | path].

  • check: — (entry -> :ok | {:error, [fault]}), the host's own check, such as names its registry does not know;

  • transaction: — a function given run, which it calls as run.(state, put) inside its own transaction, put.(entry, state) answering {:ok, id, state} or {:error, reason}; run answers {:ok, imported, state} or {:error, faults}, and the transaction function {:ok, imported} or {:error, faults}, committing only the first. Without it, entries are written through the store's put/2, which is whole only where those writes cannot fail.

A reference to an id outside the bundle is left as it is and reported in outside by the entry that holds it: it resolves as a mask (spec/02 §7).

split(module)

@spec split(assign()) :: {module(), term()}

The assign as {module, context}; a bare module has a nil context.

type_of(types, kind)

@spec type_of(assign() | [module()], atom()) :: module() | nil

The type a kind belongs to, or nil.

types(assign)

@spec types(assign()) :: [module()]

The store's types.