Musubi.AsyncResult (musubi v0.3.0)

Copy Markdown View Source

Three-field struct that tracks the lifecycle of an asynchronously-resolved socket assignment.

Every assign written via Musubi.Async.assign_async/3,4 (or seeded by Musubi.Async.stream_async/3,4) flows through one of the three statuses below so the client can pattern-match on a discriminated union.

statusresultreason
:loadingprior result (or nil) — stale-while-loadnil
:okthe value the user fun producednil
:failedprior result (or nil) — stale-while-fail{:error, _} or {:exit, _}

Wire serialization preserves the three public fields and adds the runtime-only marker key "__musubi_async__" => true so clients can distinguish an AsyncResult payload from ordinary maps with similar keys.

Compile-time type marker

Musubi.AsyncResult.of(t) is also the typespec marker used inside state do field declarations (see Musubi.DSL.State). The runtime struct and the compile-time marker share a name on purpose: a field declared as field :profile, AsyncResult.of(UserProfileState.t()) accepts an %Musubi.AsyncResult{} at runtime.

Examples

iex> Musubi.AsyncResult.loading()
%Musubi.AsyncResult{status: :loading, result: nil, reason: nil}

iex> prior = %Musubi.AsyncResult{status: :ok, result: "snapshot", reason: nil}
iex> Musubi.AsyncResult.loading(prior)
%Musubi.AsyncResult{status: :loading, result: "snapshot", reason: nil}

iex> Musubi.AsyncResult.ok(nil, %{name: "ada"})
%Musubi.AsyncResult{status: :ok, result: %{name: "ada"}, reason: nil}

iex> Musubi.AsyncResult.failed("snapshot", {:error, :timeout})
%Musubi.AsyncResult{status: :failed, result: "snapshot", reason: {:error, :timeout}}

Summary

Types

Failure classification returned by the runtime.

Compile-time field-type marker used inside state do declarations.

Discriminated-union status enum surfaced on the wire.

t()

Functions

Returns a :failed %AsyncResult{} that preserves the prior result for stale-while-failed UX. reason must be {:error, term} or {:exit, term}.

Returns a fresh %AsyncResult{} in the :loading status with no prior result.

Returns a :loading %AsyncResult{} that preserves the prior result for stale-while-loading UX.

Returns an :ok %AsyncResult{} carrying the produced value. The prior argument is accepted for symmetry with loading/1/failed/2; on success the new result always replaces the prior one.

Types

failure_reason()

@type failure_reason() :: {:error, term()} | {:exit, term()}

Failure classification returned by the runtime.

of(value)

@type of(value) :: %Musubi.AsyncResult{
  reason: failure_reason() | nil,
  result: value | nil,
  status: status()
}

Compile-time field-type marker used inside state do declarations.

status()

@type status() :: :loading | :ok | :failed

Discriminated-union status enum surfaced on the wire.

t()

@type t() :: %Musubi.AsyncResult{
  reason: failure_reason() | nil,
  result: term(),
  status: status()
}

Functions

failed(prior, reason)

@spec failed(t() | term(), failure_reason()) :: t()

Returns a :failed %AsyncResult{} that preserves the prior result for stale-while-failed UX. reason must be {:error, term} or {:exit, term}.

Examples

iex> Musubi.AsyncResult.failed("snapshot", {:error, :unauthorized})
%Musubi.AsyncResult{status: :failed, result: "snapshot", reason: {:error, :unauthorized}}

iex> prior = %Musubi.AsyncResult{status: :ok, result: "snapshot"}
iex> Musubi.AsyncResult.failed(prior, {:exit, :timeout})
%Musubi.AsyncResult{status: :failed, result: "snapshot", reason: {:exit, :timeout}}

loading()

@spec loading() :: t()

Returns a fresh %AsyncResult{} in the :loading status with no prior result.

Examples

iex> Musubi.AsyncResult.loading()
%Musubi.AsyncResult{status: :loading, result: nil, reason: nil}

loading(prior)

@spec loading(t() | term()) :: t()

Returns a :loading %AsyncResult{} that preserves the prior result for stale-while-loading UX.

Accepts either a previously-resolved %AsyncResult{} (its result is carried forward) or any raw value (used directly as the prior result).

Examples

iex> Musubi.AsyncResult.loading(%Musubi.AsyncResult{status: :ok, result: "snapshot"})
%Musubi.AsyncResult{status: :loading, result: "snapshot", reason: nil}

iex> Musubi.AsyncResult.loading("raw")
%Musubi.AsyncResult{status: :loading, result: "raw", reason: nil}

iex> Musubi.AsyncResult.loading(nil)
%Musubi.AsyncResult{status: :loading, result: nil, reason: nil}

ok(prior, value)

@spec ok(t() | term(), term()) :: t()

Returns an :ok %AsyncResult{} carrying the produced value. The prior argument is accepted for symmetry with loading/1/failed/2; on success the new result always replaces the prior one.

Examples

iex> Musubi.AsyncResult.ok(%Musubi.AsyncResult{status: :loading}, %{name: "ada"})
%Musubi.AsyncResult{status: :ok, result: %{name: "ada"}, reason: nil}

iex> Musubi.AsyncResult.ok(nil, 42)
%Musubi.AsyncResult{status: :ok, result: 42, reason: nil}