Tagged :erlang.term_to_binary/1 payloads for the host-opaque fields a
job's args carry.
Oban args live as JSON, and some Statifier effect fields (data,
caller_context, an invoke's params and content) are arbitrary terms
with no JSON shape - so they ride as Base64-encoded external term format
under a single tag key, and come back byte-identical. nil stays nil,
so the common case costs nothing and stays readable in the row.
encode/2 optionally takes a StatifierOban.OpaqueTerm.Codec
implementation and runs it over the bytes before they are Base64-encoded;
when it does, the payload also carries the codec's module name under a
"codec" tag, and decode_field/2 resolves that name and runs the
codec's decode/1 before binary_to_term. A payload with no "codec"
tag - every row written before a host adopted a codec, and every row
written with none configured - takes the plain path unchanged, so a
codec is opt-in per payload, not per deploy.
Decoding uses :safe, so a payload naming an atom the reading node has
never seen decodes to a typed error rather than minting atoms. Both job
wire modules (StatifierOban.Timer.JobArgs,
StatifierOban.Invoke.JobArgs) share this one encoding, which is what
keeps their rows mutually readable during an incident.
Summary
Functions
Decodes the tagged payload stored under field in args.
Encodes term as a tagged payload map, or nil for nil.
Types
Functions
@spec decode_field(map(), String.t()) :: {:ok, term()} | {:error, decode_error()}
Decodes the tagged payload stored under field in args.
A missing or nil field decodes to {:ok, nil} - the exact inverse of
encode/2's nil arm. A payload carrying a "codec" tag resolves that
module name and runs its decode/1 on the Base64-decoded bytes before
binary_to_term, ignoring whatever codec (if any) this call's own
caller configured - the tag on the row, not the reader's configuration,
decides. Anything that is neither nil nor a well-formed tagged payload
is a typed error about the row, returned as data rather than raised: the
caller (a worker at its boundary) decides what a corrupt row costs.
@spec encode(term(), module() | nil) :: {:ok, nil | %{required(String.t()) => String.t()}} | {:error, encode_error()}
Encodes term as a tagged payload map, or nil for nil.
With no codec (the default), the payload is exactly
%{"t2b64" => base64} - byte-identical to every row written before this
seam existed. With a codec, the payload also carries "codec" naming the
module, and codec.encode/1 runs over the term's bytes first; a codec
that fails - returns {:error, _}, returns a non-binary, or raises -
produces {:error, {:codec_failed, codec, reason}} with no payload
built. nil never reaches the codec: the nil arm is unconditional, so
the row stays readable either way.