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
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
@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.
@type t() :: %Macaw.Layers.Bundle{ entries: [Macaw.Layers.Store.entry()], provenance: provenance(), snapshot: map() | nil }
Functions
@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.
@spec export( Macaw.Layers.Store.assign(), Macaw.Layers.Store.id() | [Macaw.Layers.Store.id()], String.t() | atom() ) :: t()
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.
@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.
@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.
@spec to_json(t(), [module()]) :: {:ok, String.t()} | {:error, [Macaw.Layers.Fault.t(), ...]}
The bundle's JSON form.
@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.