The contract a variant store backend implements, and the dispatch to the configured one.
A completed render is written back under its cache key so the next request
for the same variant is served from storage instead of re-rendered. Where
that storage is — a local directory, an S3 bucket — is a backend behind
this behaviour, selected by the scheme of AP_VARIANT_STORE:
scheme module slice
file:// AudioProxy.VariantStore.Local add-variant-store
s3:// AudioProxy.VariantStore.S3 add-s3-variant-storeThe shape deliberately mirrors AudioProxy.Source.Type: one module per
scheme, five callbacks, and a new backend is a registration rather than an
edit to the render path. Cache behaviour never depends on where source audio
comes from — a file:// store caches renders of s3:// sources and vice
versa.
The callbacks, in the order a request meets them
head/1— is this variant stored, and what would serving it need to know? The HIT check.get_stream/2— the bytes, or a Range slice of them, as a lazy stream. Proxy-mode serving.presign/2— a URL the client can be redirected to. Redirect-mode serving, and only for backends that advertise it (see below).put_stream/3— the write-back: bytes plus the response metadata they must be served with.capabilities/0— what this backend can do beyond the mandatory four.
Metadata travels with the bytes
put_stream/3 takes a metadata/0 because a store that keeps only
bytes cannot serve a redirected fetch correctly: a player handed
application/octet-stream may refuse to decode it. Every backend persists
the metadata; a backend that cannot attach it to a direct fetch (the
redirect case) simply does not advertise :presign, and
AudioProxy.Config refuses AP_SERVE_MODE=redirect against it at boot.
Atomic or absent
A put_stream/3 that does not run to completion — the render failed, the
write errored — must leave nothing readable under the key. Each backend
implements that with whatever its storage offers: the local backend stages
into a temp file inside the store and File.rename/2s on completion; the
S3 backend gets it from the protocol, since an object does not exist until
its upload completes and a failed multipart is aborted. head/1 answering
{:ok, _} therefore always means the whole variant, with its metadata.
Summary
Types
What a backend can do beyond the mandatory callbacks.
The parsed AP_VARIANT_STORE value, as AudioProxy.Config holds it.
The cache key a variant is stored under — see AudioProxy.CacheKey.
The response headers a stored variant must be served with: its content type, the immutable cache-control the render endpoint would have sent, and the ETag (the quoted cache key). Stored alongside the bytes so a store-direct fetch serves correctly without the proxy in the path.
An inclusive byte range ({first, last}, RFC 9110 §14), or nil for the
whole variant. A last past the end is truncated, as Range semantics ask;
a first past the end is :invalid_range.
Callbacks
The optional abilities this backend has. See capability/0.
Streams the stored variant's bytes, or the Range slice of them.
Reports whether the variant named by key is stored, whole, with its metadata.
Produces a URL a client can fetch the stored variant from directly.
Writes a variant: consumes chunks (an enumerable of binaries) to
completion, then commits bytes and metadata under key — atomically, so a
reader sees the whole variant or nothing.
Functions
The configured backend module. Callers must check configured?/0 first.
The backend module for a parsed store value.
capabilities/0 of the configured backend.
Reports whether AP_VARIANT_STORE is configured at all.
get_stream/2 on the configured backend.
presign/2 on the configured backend.
put_stream/3 on the configured backend.
Types
@type capability() :: :presign
What a backend can do beyond the mandatory callbacks.
:presign—presign/2produces working URLs, which is what redirect serving needs. A backend without it is proxy-mode only.
The parsed AP_VARIANT_STORE value, as AudioProxy.Config holds it.
{:module, backend} is the test seam described at backend_for/1, not a
value any environment can produce.
@type entry() :: %{size: non_neg_integer(), metadata: metadata()}
What head/1 reports about a stored variant.
@type key() :: String.t()
The cache key a variant is stored under — see AudioProxy.CacheKey.
The response headers a stored variant must be served with: its content type, the immutable cache-control the render endpoint would have sent, and the ETag (the quoted cache key). Stored alongside the bytes so a store-direct fetch serves correctly without the proxy in the path.
@type range() :: {non_neg_integer(), non_neg_integer()} | nil
An inclusive byte range ({first, last}, RFC 9110 §14), or nil for the
whole variant. A last past the end is truncated, as Range semantics ask;
a first past the end is :invalid_range.
Callbacks
@callback capabilities() :: [capability()]
The optional abilities this backend has. See capability/0.
@callback get_stream(key(), range()) :: {:ok, Enumerable.t()} | {:error, :not_found | :invalid_range}
Streams the stored variant's bytes, or the Range slice of them.
The stream is lazy and bounded-memory: chunks, never the whole variant at once.
Reports whether the variant named by key is stored, whole, with its metadata.
Produces a URL a client can fetch the stored variant from directly.
{:error, :unsupported} from a backend without the :presign capability.
@callback put_stream(key(), Enumerable.t(), metadata()) :: :ok | {:error, term()}
Writes a variant: consumes chunks (an enumerable of binaries) to
completion, then commits bytes and metadata under key — atomically, so a
reader sees the whole variant or nothing.
If chunks raises — which is how the write-back tee signals a render that
failed or was cancelled mid-stream — the write is aborted, anything staged
is discarded, and the exception comes back as {:error, exception}.
Functions
@spec backend() :: module()
The configured backend module. Callers must check configured?/0 first.
The backend module for a parsed store value.
Used by AudioProxy.Config to validate the serve mode against the
backend's capabilities at boot, before anything is stored.
@spec capabilities() :: [capability()]
capabilities/0 of the configured backend.
@spec configured?() :: boolean()
Reports whether AP_VARIANT_STORE is configured at all.
@spec get_stream(key(), range()) :: {:ok, Enumerable.t()} | {:error, :not_found | :invalid_range}
get_stream/2 on the configured backend.
head/1 on the configured backend.
presign/2 on the configured backend.
@spec put_stream(key(), Enumerable.t(), metadata()) :: :ok | {:error, term()}
put_stream/3 on the configured backend.