# `StatifierDatamodel.Events`
[🔗](https://github.com/riddler/statifier_datamodel/blob/v0.7.1/lib/statifier_datamodel/events.ex#L1)

The index over a datamodel document's `events` and `sends` keys
(ADR-0004 decisions 1-5): per event name, the type of its payload.

`events` declares the events a chart receives, by name, and the type of
each one's payload - the value a chart's conditions read under
`_event.data`. `sends` declares the events a chart sends, by name, and the
type of the payload each carries. Both keys are optional: a document
without either declares no events or no sends, which is the state every
document written before the two keys existed is in.

`from_document/1` is the index. It reads `events`, `sends` and, to resolve
each entry's type, `types`; nothing else. So a caller asking *is this a
datamodel document at all* asks `StatifierDatamodel.Index.index/1`, which
is where that question is answered, and `index/1` does not read either key:
every answer it gives is the answer it gave before the keys existed.

## An entry: `name` and `type` required

An entry carries `name` (the event name as a chart spells it) and `type`
(the payload's type), each a non-empty string, and the optional `label`
(what a pane renders) and `note` (prose for a reader, carrying no
contract). An entry missing `name` or `type`, or carrying either as
anything but a non-empty string, declares nothing: it is dropped rather
than raised on, and the rest of the list is indexed around it - ADR-0001
decision 6's posture for a half-written declaration. A `label` or `note`
that is not a string is `nil`.

Each list keeps its own order, and a repeated `name` keeps its first
occurrence, as `types` does. The two lists are separate: a name may appear
in both, and an entry of one never shadows an entry of the other.

## A type resolves, or it is unknown

`type` is one of the nine types the set is closed at, looked at first, or
the `name` of a record or shape the `types` key declares, which is
`{:declared, name}`. A spelling that names neither is `:unknown`: unknown,
not wrong. There is no inline spelling: a `type` that is a map spelling a
shape is not a string, so the entry declares nothing.

## Reported, never refused

An entry whose type is `:unknown` stays in the index with that type, and
`unresolved/1` lists the entries whose type resolves to nothing. A document
with such an entry indexes and answers every other question exactly as it
would without it; which unresolved entry matters to a host is the host's
to decide (ADR-0001 decision 12).

## Not declared paths

The two keys contribute no path to
`StatifierDatamodel.Index.declared_paths/1` and nothing to
`StatifierDatamodel.Index.path_types/1`, as `types` contributes none. The
`event` scope is a different thing: one scope of paths every event carries,
in the document's own addressing. An `events` entry declares one event's
payload, read under `_event.data`, and the two do not overlap.

## An improper list is not a list

An improper list, `[entry | :more]`, is a term no JSON decoder produces,
and every function here reads one as a value that is not a list, as
`StatifierDatamodel.Conformance` does. An improper `events` or `sends`
declares nothing; an improper `types` declares no type, and a declaration
whose `fields` is an improper list is dropped, so a `type` naming it is
`:unknown`. An index section that is an improper list holds no entry:
`unresolved/1` lists nothing for it and `fetch/3` answers `:error`.

# `entry`

```elixir
@type entry() :: %{
  name: String.t(),
  type: StatifierDatamodel.Types.t(),
  label: String.t() | nil,
  note: String.t() | nil
}
```

One entry of `events` or `sends`, as indexed: its name, its payload's type
expression (`:unknown` for a spelling that names nothing), and its label
and note, each `nil` when the document gives no string.

# `section`

```elixir
@type section() :: :events | :sends
```

The two sections, by the key a document spells.

# `t`

```elixir
@type t() :: %{events: [entry()], sends: [entry()]}
```

Each section in list order, the first occurrence of a name kept.

# `fetch`

```elixir
@spec fetch(t(), section(), term()) :: {:ok, entry()} | :error
```

The entry of `section` named `name`.

`:error` for a name the section does not hold, for a section other than
`:events` or `:sends`, and for anything that is not an index: an entry
there would be a false claim.

    iex> alias StatifierDatamodel.Events
    iex> events = Events.from_document(%{"events" => [
    ...>   %{"name" => "loan.returned", "type" => "object", "label" => "Copy returned"}]})
    iex> {:ok, entry} = Events.fetch(events, :events, "loan.returned")
    iex> {entry.type, entry.label}
    {:object, "Copy returned"}
    iex> Events.fetch(events, :sends, "loan.returned")
    :error
    iex> Events.fetch(events, :events, "loan.lost")
    :error

# `from_document`

```elixir
@spec from_document(term()) :: t()
```

Indexes a decoded datamodel document's `events` and `sends` keys.

Total: every input has an answer and none of them raise. A document
without the keys declares no events and no sends, and so does an input
that is not a document.

    iex> alias StatifierDatamodel.Events
    iex> events = Events.from_document(%{
    ...>   "scopes" => [],
    ...>   "types" => [
    ...>     %{"name" => "loans.renewal", "kind" => "record", "label" => "Renewal",
    ...>       "fields" => [%{"name" => "due_on", "type" => "date", "required?" => true}]}],
    ...>   "events" => [
    ...>     %{"name" => "loan.renewed", "type" => "loans.renewal", "label" => "Loan renewed"},
    ...>     %{"name" => "loan.lost", "type" => "loans.loss"},
    ...>     %{"name" => "loan.renewed", "type" => "object"},
    ...>     %{"type" => "object", "label" => "No name"}],
    ...>   "sends" => [
    ...>     %{"name" => "patron.due_soon", "type" => "object", "note" => "Sent before the due date."}]})
    iex> Enum.map(events.events, &{&1.name, &1.type})
    [{"loan.renewed", {:declared, "loans.renewal"}}, {"loan.lost", :unknown}]
    iex> [notice] = events.sends
    iex> {notice.name, notice.type, notice.label, notice.note}
    {"patron.due_soon", :object, nil, "Sent before the due date."}

    iex> StatifierDatamodel.Events.from_document(%{"scopes" => []})
    %{events: [], sends: []}
    iex> StatifierDatamodel.Events.from_document(["loan.renewed"])
    %{events: [], sends: []}

# `unresolved`

```elixir
@spec unresolved(t()) :: t()
```

The entries whose type resolves to nothing, each section in its own list
order.

A fact, not a refusal: every entry listed here is still in the index, typed
`:unknown`, and every other answer stands. Anything that is not an index
lists nothing.

    iex> alias StatifierDatamodel.Events
    iex> events = Events.from_document(%{
    ...>   "events" => [
    ...>     %{"name" => "loan.renewed", "type" => "object"},
    ...>     %{"name" => "loan.lost", "type" => "loans.loss"}],
    ...>   "sends" => [%{"name" => "patron.due_soon", "type" => "notices.due_soon"}]})
    iex> unresolved = Events.unresolved(events)
    iex> {Enum.map(unresolved.events, & &1.name), Enum.map(unresolved.sends, & &1.name)}
    {["loan.lost"], ["patron.due_soon"]}

---

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