The wire shape between a %Statifier.Effect.SendDelayed{} and the args
map an Oban job stores.
Oban args live as JSON, so this module owns the (de)serialization of the one effect a timer job carries, plus the scope it was scheduled under. Two rules shape it:
- Every deterministic field rides as an explicit JSON value: the dedup
pair (
scope,ordinal) at the top level - Oban's uniquenesskeysread args at the top level - and the row data ADR-0059 (statifier-ex) keeps beside the key (send_id,macrostep,microstep,round,c_index,owner), self-describing in the store during an incident. - The two host-opaque fields,
dataandcaller_context, are arbitrary terms with no JSON shape, so they ride as tagged:erlang.term_to_binary/1payloads (StatifierOban.OpaqueTerm) and come back byte-identical.caller_contextstays row data, never a key component (st-ADR-0063 decision 6). Decoding uses:safe, so a payload naming an atom the reading node has never seen decodes to a typed error rather than minting atoms.from_effect/3's optional codec runs over both fields' bytes and tags the payload with its module name (StatifierOban.OpaqueTerm.Codec);to_effect/1reads whatever tag the stored row carries, regardless of what the reading caller passed.
to_effect/1 is the exact inverse of from_effect/3 for every
%SendDelayed{} the scheduler accepts: what the fired job carries is
enough to rebuild the event and its position, per ADR-0054's
correlation rule - position is read off the stored effect, never
recomputed at delivery.
Summary
Types
String-keyed args map as Oban stores and redelivers it.
Why from_effect/3 could not build an args map.
Functions
Builds the args map for a timer job from the scope and the effect.
Rebuilds the scope and the %SendDelayed{} from a job's args.
Types
String-keyed args map as Oban stores and redelivers it.
@type decode_error() :: {:missing_field, String.t()} | {:invalid_field, String.t(), term()} | StatifierOban.OpaqueTerm.decode_error()
@type encode_error() :: {:codec_failed, String.t(), StatifierOban.OpaqueTerm.encode_error()}
Why from_effect/3 could not build an args map.
Functions
@spec from_effect(String.t(), Statifier.Effect.SendDelayed.t(), module() | nil) :: {:ok, args()} | {:error, encode_error()}
Builds the args map for a timer job from the scope and the effect.
The caller has already validated the scope and ordinal via
StatifierOban.Timer.Key.dedup_key/2; this function only lays fields
out on the wire. data and caller_context are encoded through a
with, so the first codec failure short-circuits and no
partially-encoded args map is ever returned.
@spec to_effect(args()) :: {:ok, String.t(), Statifier.Effect.SendDelayed.t()} | {:error, decode_error()}
Rebuilds the scope and the %SendDelayed{} from a job's args.
Returns a typed error rather than raising: an undecodable job is a fact about the row, and the worker decides what to do with it.