One version line per document type, and stepwise migration (spec/01 §4).
A document type implements this behavior: its current version, and one pure migration
step per version. A document carries its version as the stamp "$version", a string key
at the top of its map, in the map form and the JSON form alike. Two types' lines are
independent: a new step for one moves no other.
defmodule MyApp.Layout do
@behaviour Macaw.Layers.Version
@impl true
def current, do: 2
# 1 -> 2: `cols` became `columns`.
@impl true
def step(%{"cols" => cols} = doc, 1),
do: {:ok, doc |> Map.delete("cols") |> Map.put("columns", cols)}
def step(doc, 1), do: {:ok, doc}
end
Macaw.Layers.Version.migrate(%{"$version" => 1, "cols" => 3}, MyApp.Layout)
#=> {:ok, %{"$version" => 2, "columns" => 3}}The generic part of visualize's Visualize.Chart.Migration (current/0, readable/1,
migrate/1), with the line per type rather than one shared by every type.
Summary
Callbacks
The type's current version: a positive integer, starting at 1.
The document at from migrated to from + 1, purely: no I/O, no clock, no process.
The kernel writes the new stamp; a step need not.
Functions
The document's version, read before anything else of it: {:ok, version}, the current
one when the document has no stamp, or the fault that refuses it.
The document at the type's current version, carrying the current stamp.
The stamp's key, "$version".
Callbacks
@callback current() :: pos_integer()
The type's current version: a positive integer, starting at 1.
@callback step(document :: map(), from :: pos_integer()) :: {:ok, map()} | {:error, [Macaw.Layers.Fault.t(), ...]}
The document at from migrated to from + 1, purely: no I/O, no clock, no process.
The kernel writes the new stamp; a step need not.
Functions
@spec check(map(), module()) :: {:ok, pos_integer()} | {:error, [Macaw.Layers.Fault.t(), ...]}
The document's version, read before anything else of it: {:ok, version}, the current
one when the document has no stamp, or the fault that refuses it.
A stamp above the type's current version is {[], {:newer_schema, needs, this}}, and a
stamp that is not a positive integer is {["$version"], {:type, :pos_integer}}.
@spec migrate(map(), module()) :: {:ok, map()} | {:error, [Macaw.Layers.Fault.t(), ...]}
The document at the type's current version, carrying the current stamp.
A document with no stamp reads as current. An older one is migrated one step at a time,
each step/2 in turn; a step's faults stop the migration and are returned. A newer or
malformed stamp is refused by check/2 before any step runs.
@spec stamp() :: String.t()
The stamp's key, "$version".