One feed over several, routed by window: live data and its history in one slot (spec/08 §11.5, D-140, D-159).
The feeds are listed freshest first — say an in-process :telemetry feed holding the
last hour, then a metresis feed holding everything. A fetch asks the first for the
window; what it served starts at its delivery's window (else its earliest row), and
the part of the window before that goes to the next feed, and so on. Each later feed's
rows are trimmed to the half-open window it was asked for, [from, served), so a row
at a seam comes from the fresher feed alone. The schema must have a window field to
route by, :time or :number (spec/08 §11.2, D-154).
The combination's schema is the first feed's, whole: its version, labels and facts
(D-159). Every later feed's schema must be compatible with it
(Visualize.Data.Schema.compatibility/2): the same fields by name, compared as
strings, of the same types, in units that convert — and the same window field, since
each later feed is asked for a window over its own. Its versions may differ, as a
process's live feed and a store's history always do. A later feed's rows are read in
the first schema's words before routing: re-keyed to its spellings, numbers converted
into its units, and columns only the later schema describes dropped. The first feed's
rows are never touched.
A combination honours :window, and pushes when its first feed does: its pushes name
the first's version and pass through unchanged. A budget is passed to every part as a
request and left to the monitor.
Examples
iex> alias Visualize.Feed.Signals
iex> signal = Visualize.Signals.new(:discrete, [:t, :n])
iex> live = Signals.new(signal, tick: 9, limit: 3)
iex> history = Signals.new(signal, tick: 9)
iex> {:ok, delivery} = Visualize.Feed.fetch(Visualize.Feed.Combine.new([live, history]), [2, 9], nil)
iex> {Enum.map(delivery.rows, & &1.t), delivery.window, delivery.capped}
{[2, 3, 4, 5, 6, 7, 8, 9], [2, 9], false}
Summary
Types
A later feed whose schema is not compatible with the first's: its index in the list, the field as the first schema spells it, and why (spec/08 §10.1).
Functions
The first feed's {version, schema} with the combination's supports:
{:error, {:combine_schema, incompatibilities}} listing every field of every later
feed whose schema is not compatible with the first's, each {position, field, reason},
and {position, window_field, {:window, first, later}} for a later feed windowing over
another field (nil for none);
{:error, :combine_window_field} when the schema has no window field; and the first
error of a feed that cannot describe.
One feed over several, freshest first.
Types
@type incompatibility() :: {pos_integer(), Visualize.Data.Schema.name(), Visualize.Data.Schema.incompatibility() | {:window, Visualize.Data.Schema.name(), Visualize.Data.Schema.name() | nil}}
A later feed whose schema is not compatible with the first's: its index in the list, the field as the first schema spells it, and why (spec/08 §10.1).
@type t() :: %Visualize.Feed.Combine{feeds: [Visualize.Feed.t(), ...]}
Functions
The first feed's {version, schema} with the combination's supports:
{:error, {:combine_schema, incompatibilities}} listing every field of every later
feed whose schema is not compatible with the first's, each {position, field, reason},
and {position, window_field, {:window, first, later}} for a later feed windowing over
another field (nil for none);
{:error, :combine_window_field} when the schema has no window field; and the first
error of a feed that cannot describe.
@spec new([Visualize.Feed.t(), ...]) :: t()
One feed over several, freshest first.