Macaw.Layers.Bundle (macaw_layers v0.3.0)

Copy Markdown View Source

A portable set of documents (spec/03 §3): the transitive closure of what a page needs, with where it came from, in a map form and a JSON form so it can leave the node it was made on, and imported into another store as a relocation (Macaw.Layers.Store.relocate/3).

%Macaw.Layers.Bundle{
  provenance: %{store: "metresis", exported_at: ~U[2026-10-20 09:30:00.000000Z], version: 1},
  entries: [%{id: {:style, 11}, name: "Styles:thick", fragment: %{style: %{width: 3}}}],
  snapshot: nil
}

A bundle is a term, judged as one by check/2 however it was made: this module is a Macaw.Layers.Document, whose validate/1 judges the envelope, and check/2 adds each entry's kind against the store's types. Its map and JSON forms are made at the edge: to_map/2 and to_json/2 write them, and from_map/3 and from_json/3 read one through the JSON adapter's gates (Macaw.Layers.Form.gates/3, spec/01 §5) with the bundle's own version line (this module is a Macaw.Layers.Version), decode the envelope, read each entry's fragment by its own type, at the type's own version where the type has a line, and then check the term.

Ported from visualize's Visualize.Chart.Builder.Bundle and its codec, without boards.

Summary

Types

Where a bundle came from, when, and the bundle version it was exported at.

t()

Functions

The bundle judged as a term (§3.2), however it was made: its envelope by validate/1 (Macaw.Layers.Document.check/2), and each entry's kind against the type its id names, types given as a store's: an id of a kind none of them has is {:type, :id}, a fragment that derives to no kind :no_kind, and one that derives to another kind than its id's {:kind, id_kind, derived}. {:ok, bundle}, or every fault, ordered by path.

The closure of ids through a store (§3.1): each entry, and every entry it refers to (Macaw.Layers.Fragment.refs/2, use sites included), transitively, each once, in dependency order. An id the store no longer holds is skipped. source is the host's name for the store, written in the provenance.

The bundle a JSON document holds, as from_map/3 reads it, every gate first.

The bundle a map form holds (§3.2): through the JSON adapter's gates with this module's version line (Macaw.Layers.Form.gates/3: the limits, the stamp, the migration, the recount), then the envelope's own decode of the map form, then each entry's fragment read by the type its id names, at the type's own version, and then the bundle judged as a term (check/2): {:ok, bundle}, or the faults of the first step that refuses it, by path. types are a store's; limits: as Macaw.Layers.Form.gates/3 takes them.

The bundle's JSON form.

The bundle's map form (§3.2), each entry's fragment in its type's map form, stamped with the type's version where the type has a line: {:ok, map}, or every value without a form, by path.

Types

provenance()

@type provenance() :: %{
  store: String.t() | atom(),
  exported_at: DateTime.t(),
  version: pos_integer()
}

Where a bundle came from, when, and the bundle version it was exported at.

t()

@type t() :: %Macaw.Layers.Bundle{
  entries: [Macaw.Layers.Store.entry()],
  provenance: provenance(),
  snapshot: map() | nil
}

Functions

check(bundle, types)

@spec check(t(), [module()]) :: {:ok, t()} | {:error, [Macaw.Layers.Fault.t(), ...]}

The bundle judged as a term (§3.2), however it was made: its envelope by validate/1 (Macaw.Layers.Document.check/2), and each entry's kind against the type its id names, types given as a store's: an id of a kind none of them has is {:type, :id}, a fragment that derives to no kind :no_kind, and one that derives to another kind than its id's {:kind, id_kind, derived}. {:ok, bundle}, or every fault, ordered by path.

This is how a bundle built in Elixir is judged; no form is written or read, so a value only an encoding can't hold fails at that encoding (to_map/2), not here.

export(store, ids, source)

The closure of ids through a store (§3.1): each entry, and every entry it refers to (Macaw.Layers.Fragment.refs/2, use sites included), transitively, each once, in dependency order. An id the store no longer holds is skipped. source is the host's name for the store, written in the provenance.

from_json(json, types, opts \\ [])

@spec from_json(String.t(), [module()], keyword()) ::
  {:ok, t()} | {:error, [Macaw.Layers.Fault.t(), ...]}

The bundle a JSON document holds, as from_map/3 reads it, every gate first.

from_map(map, types, opts \\ [])

@spec from_map(term(), [module()], keyword()) ::
  {:ok, t()} | {:error, [Macaw.Layers.Fault.t(), ...]}

The bundle a map form holds (§3.2): through the JSON adapter's gates with this module's version line (Macaw.Layers.Form.gates/3: the limits, the stamp, the migration, the recount), then the envelope's own decode of the map form, then each entry's fragment read by the type its id names, at the type's own version, and then the bundle judged as a term (check/2): {:ok, bundle}, or the faults of the first step that refuses it, by path. types are a store's; limits: as Macaw.Layers.Form.gates/3 takes them.

to_json(bundle, types)

@spec to_json(t(), [module()]) ::
  {:ok, String.t()} | {:error, [Macaw.Layers.Fault.t(), ...]}

The bundle's JSON form.

to_map(bundle, types)

@spec to_map(t(), [module()]) ::
  {:ok, map()} | {:error, [Macaw.Layers.Fault.t(), ...]}

The bundle's map form (§3.2), each entry's fragment in its type's map form, stamped with the type's version where the type has a line: {:ok, map}, or every value without a form, by path.