A relay class, declared with use (see __using__/1), which defines
this behaviour's zero-arity callbacks from its options and imports the channel
builder. The author writes the graph in channels/1, a DAG of Tasklets,
Channels and Cells, or the one tasklet's run/3 of the §5 short form.
One settings tree says how the class runs and where its work is kept:
use Flezha.SDK.Relay,
factory_id: "checkout",
run: {:steps, each: :chained, on_failure: :keep_progress},
durable: {:local, sleep_after: 60_000},
description: "reserve, charge, ship",
max_duration_ms: 600_000run is one of:
:flow(the default), or{:flow, exactly: exactly}, whereexactlyis:at_least_once(the default),:effectively_onceor{:effectively_once, ack: ack, correction_ms: ms, beyond_window: :drop}(beyond_windowdefaults to:process_as_arrival), andackis:per_input(the default),:checkpointor{:checkpoint, every: n, max_delay_ms: ms, outputs: :at_checkpoint}(outputsdefaults to:as_produced);:steps, or{:steps, each: each, on_failure: on_failure}, whereeachis:once(the default),:per_inputor:chained, andon_failureis:undo_all(the default) or:keep_progress.
durable is false (in memory: a crash starts over), :local (the
default, on the controller's disk) or :remote (in the remote copy before a
commit point is acknowledged, for a class that runs in steps), each of the
last two optionally written {:local, sleep_after: ms}: an instance idle
that long sleeps and wakes on its next message with its Cells restored.
sleep_after defaults by the run: a flow instance never sleeps, a steps
instance sleeps after six hours; false never sleeps. A finished instance
keeps nothing unless the level says keep_finished: hours, or true for
the controller's window (72 hours by default), so
flz relay info can still show its Cells and result.
The core judges the tree when the class is registered: a setting written
under a parent that does not carry it is refused as topology.unknown_field
naming the setting and the parent, and a value outside its choices as
topology.invalid_setting, whether use or hand-written callbacks
(@behaviour Flezha.SDK.Relay) answer the tree.
A tasklet of either run ends its instance with complete/2: a flow
instance runs what its Cells already hold to its outputs first.
max_duration_ms is a wall-clock stop for either run.
Summary
Callbacks
Declare the graph on the channel builder. A class that runs in steps runs it in topological order: each entry channel roots a chain, a message runs only the chain of the channel it arrived on, and a tasklet becomes runnable once every producer feeding it in that chain has committed.
Optional human description.
Where the class keeps its work: false, :local or :remote, with its members.
Stable, controller-facing factory identifier.
Wall-clock stop in milliseconds, for either run.
How the class runs: :flow or :steps, with its members.
Functions
Write a relay as a declaration rather than as one-line callbacks.
Callbacks
@callback channels(Flezha.SDK.ChannelBuilder.t()) :: any()
Declare the graph on the channel builder. A class that runs in steps runs it in topological order: each entry channel roots a chain, a message runs only the chain of the channel it arrived on, and a tasklet becomes runnable once every producer feeding it in that chain has committed.
@callback description() :: String.t()
Optional human description.
@callback durable() :: term()
Where the class keeps its work: false, :local or :remote, with its members.
@callback factory_id() :: String.t()
Stable, controller-facing factory identifier.
@callback max_duration_ms() :: pos_integer()
Wall-clock stop in milliseconds, for either run.
@callback run() :: term()
How the class runs: :flow or :steps, with its members.
Functions
Write a relay as a declaration rather than as one-line callbacks.
use Flezha.SDK.Relay, factory_id: "market"
@impl true
def channels(c) do
c |> input("trades") |> into(Candle) |> output("candles")
endfactory_id is required; description, max_duration_ms, run and
durable are optional, and only the options written define anything, so
a default stays where settings/1 resolves it. channels/1 takes an
argument and stays a def the author writes. An unknown option is a
CompileError naming it; the tree itself is judged at registration.
Flezha.SDK.ChannelBuilder is imported, so a whole pipe, a merge included, is
written with no module prefix.
The §5 short form
A module that writes its one tasklet's run/3 instead of channels/1 is
a whole single-tasklet relay:
use Flezha.SDK.Relay, factory_id: "upcase", output: "out", input: "words"
def run(input, state, ctx) do
emit(ctx, String.upcase(input))
{:ok, state}
endIt generates the tasklet module Upcase.Tasklet, whose name/0 is the
factory id and which delegates to this module's tasklet callbacks, and a
channels/1 wiring the entry channel (input:, "input" when absent, read
by codec: as Flezha.SDK.ChannelBuilder.input/3 reads it) into it and, with
output:, it to that plugin channel. With no output: nothing
leaves the tasklet but its state. run/3 is written as in a tasklet, with
the ctx functions and a state do ... end block available; the options
use Flezha.SDK.Tasklet takes (max_cells:, logical_clock:, ...) apply
to the generated tasklet, and the class settings to the class, as in the
full form. Writing run/3 and channels/1 both is a CompileError
beginning topology.short_form_with_topology, and writing neither is one
naming both.