pub struct QueryResource<T, E = QueryError> { /* private fields */ }core only.Expand description
Core state machine for a single query resource.
QueryResource owns the cache/request state for one resource. It tracks
data, error, loading status, retry count, and a cooperative cancellation
signal. Callers interact with it through lifecycle methods:
begin_request— start a fetchaccept_current_request— validate the request is still activecomplete_success/complete_failure— complete the request
This type is framework-free — it depends only on serde.
Implementations§
Source§impl<T, E> QueryResource<T, E>
impl<T, E> QueryResource<T, E>
Sourcepub fn is_loading(&self) -> bool
pub fn is_loading(&self) -> bool
Whether the resource is currently loading.
Sourcepub fn is_pending(&self) -> bool
pub fn is_pending(&self) -> bool
Whether the resource is pending (no data yet).
Sourcepub fn status(&self) -> QueryStatus
pub fn status(&self) -> QueryStatus
Current status.
Sourcepub fn active_request_id(&self) -> Option<RequestId>
pub fn active_request_id(&self) -> Option<RequestId>
Active request id, if a request is in flight.
Sourcepub fn cache_policy(&self) -> CachePolicy
pub fn cache_policy(&self) -> CachePolicy
The cache policy.
Sourcepub fn request_policy(&self) -> RequestPolicy
pub fn request_policy(&self) -> RequestPolicy
The request policy.
Sourcepub fn started_at_ms(&self) -> Option<u64>
pub fn started_at_ms(&self) -> Option<u64>
When the current request started (ms since UNIX epoch).
Sourcepub fn last_updated_at_ms(&self) -> Option<u64>
pub fn last_updated_at_ms(&self) -> Option<u64>
When data was last updated (ms since UNIX epoch).
Sourcepub fn cache_hits(&self) -> u64
pub fn cache_hits(&self) -> u64
Total cache hits.
Sourcepub fn cancelled_count(&self) -> u64
pub fn cancelled_count(&self) -> u64
Total cancelled requests.
Sourcepub fn ignored_results(&self) -> u64
pub fn ignored_results(&self) -> u64
Total ignored (stale) results.
Sourcepub fn signal(&self) -> Option<&QuerySignal>
pub fn signal(&self) -> Option<&QuerySignal>
The cancellation signal, if a request is in flight.
Sourcepub fn previous_data(&self) -> Option<&T>
pub fn previous_data(&self) -> Option<&T>
Previous data (saved during optimistic updates for rollback).
Sourcepub fn retry_count(&self) -> u32
pub fn retry_count(&self) -> u32
Current retry count.
Sourcepub fn retry_policy(&self) -> &RetryPolicy
pub fn retry_policy(&self) -> &RetryPolicy
The retry policy.
Sourcepub fn increment_retry(&mut self)
pub fn increment_retry(&mut self)
Increment the retry counter.
Sourcepub fn set_retry_policy(&mut self, policy: RetryPolicy)
pub fn set_retry_policy(&mut self, policy: RetryPolicy)
Set the retry policy.
Sourcepub fn reset_retry_count(&mut self)
pub fn reset_retry_count(&mut self)
Reset the retry counter to zero.
Sourcepub fn set_cache_policy(&mut self, policy: CachePolicy)
pub fn set_cache_policy(&mut self, policy: CachePolicy)
Set the cache policy.
This allows policy updates on existing resources when use_query is
called with the same key but different policies (e.g., a different TTL).
Sourcepub fn set_request_policy(&mut self, policy: RequestPolicy)
pub fn set_request_policy(&mut self, policy: RequestPolicy)
Set the request policy.
This allows policy updates on existing resources when use_query is
called with the same key but different request behavior.
Source§impl<T, E> QueryResource<T, E>
impl<T, E> QueryResource<T, E>
Sourcepub fn cache_age_ms(&self, now_ms: u64) -> Option<u64>
pub fn cache_age_ms(&self, now_ms: u64) -> Option<u64>
Cache age in milliseconds.
Sourcepub fn is_cache_fresh(&self, now_ms: u64) -> bool
pub fn is_cache_fresh(&self, now_ms: u64) -> bool
Whether the cache is fresh (within TTL).
For all policies with a TTL, this checks that data exists and the age
is within the TTL window. The stale-while-revalidate window is NOT
considered fresh — it is stale-but-serveable (see [is_stale_but_serveable]).
Boundary behavior: data at exactly TTL milliseconds old is considered
fresh (age <= ttl_ms). Data older than TTL is stale (age > ttl_ms).
This differs from HTTP Cache-Control: max-age where the boundary is
exclusive. The inclusive boundary is chosen so that the fresh/stale
partition is total: every age is either fresh or stale, with no gap.
Sourcepub fn is_stale_but_serveable(&self, now_ms: u64) -> bool
pub fn is_stale_but_serveable(&self, now_ms: u64) -> bool
Whether the cache is stale but still within the stale-while-revalidate window.
Returns true when:
- The policy is
StaleWhileRevalidate - Data exists
- Data age is past TTL but within
ttl_ms + stale_ms
Sourcepub fn is_cache_expired(&self, now_ms: u64) -> bool
pub fn is_cache_expired(&self, now_ms: u64) -> bool
Whether the cache is fully expired (past the total valid window).
For StaleWhileRevalidate, this means past ttl_ms + stale_ms.
For Ttl, this means past ttl_ms.
For NoCache, always returns true (no data is ever valid).
Sourcepub fn should_short_circuit_cache(&self, now_ms: u64) -> bool
pub fn should_short_circuit_cache(&self, now_ms: u64) -> bool
Whether the cache can short-circuit (fresh data, no fetch needed).
Only returns true when the policy supports short-circuiting AND the
data is within the TTL window (fresh, not stale).
Sourcepub fn should_serve_stale_and_revalidate(&self, now_ms: u64) -> bool
pub fn should_serve_stale_and_revalidate(&self, now_ms: u64) -> bool
Whether the resource should serve stale data while triggering a background refetch.
This is the core stale-while-revalidate check: data is past its TTL but still within the stale window. The caller should:
- Return existing data to the consumer immediately.
- Start a background fetch to revalidate.
Sourcepub fn invalidate(&mut self)
pub fn invalidate(&mut self)
Invalidate the cache (clear last-updated timestamp).
Data is retained but the resource is considered stale.
Source§impl<T, E> QueryResource<T, E>
impl<T, E> QueryResource<T, E>
Sourcepub fn complete_current_success(
&mut self,
request_id: RequestId,
data: T,
now_ms: u64,
) -> bool
pub fn complete_current_success( &mut self, request_id: RequestId, data: T, now_ms: u64, ) -> bool
Complete the current request with success by request id.
Convenience method that accepts + completes in one call.
Returns true if the request was accepted.
Sourcepub fn complete_current_failure(
&mut self,
request_id: RequestId,
error: impl Into<E>,
now_ms: u64,
) -> bool
pub fn complete_current_failure( &mut self, request_id: RequestId, error: impl Into<E>, now_ms: u64, ) -> bool
Complete the current request with failure by request id.
Sourcepub fn complete_current_optional_success(
&mut self,
request_id: RequestId,
data: Option<T>,
now_ms: u64,
) -> bool
pub fn complete_current_optional_success( &mut self, request_id: RequestId, data: Option<T>, now_ms: u64, ) -> bool
Complete the current request with optional success by request id.
Sourcepub fn complete_current_failure_with_data(
&mut self,
request_id: RequestId,
data: T,
error: impl Into<E>,
now_ms: u64,
) -> bool
pub fn complete_current_failure_with_data( &mut self, request_id: RequestId, data: T, error: impl Into<E>, now_ms: u64, ) -> bool
Complete the current request with failure but retain data by request id.
Sourcepub fn complete_success(&mut self, guard: RequestGuard, data: T, now_ms: u64)
pub fn complete_success(&mut self, guard: RequestGuard, data: T, now_ms: u64)
Complete with success, consuming the guard (two-phase protocol).
The guard is moved, preventing double-completion at the type level. Validates that no new request was started after the guard was issued.
Sourcepub fn complete_failure(
&mut self,
guard: RequestGuard,
error: impl Into<E>,
now_ms: u64,
)
pub fn complete_failure( &mut self, guard: RequestGuard, error: impl Into<E>, now_ms: u64, )
Complete with failure, consuming the guard (two-phase protocol).
The guard is moved, preventing double-completion at the type level. Validates that no new request was started after the guard was issued.
Sourcepub fn complete_success_optional(
&mut self,
guard: RequestGuard,
data: Option<T>,
now_ms: u64,
)
pub fn complete_success_optional( &mut self, guard: RequestGuard, data: Option<T>, now_ms: u64, )
Complete with optional success, consuming the guard.
If data is None, the status is set to QueryStatus::Idle rather than
QueryStatus::Success to maintain the invariant that Success implies data exists.
Sourcepub fn complete_failure_with_data(
&mut self,
guard: RequestGuard,
data: T,
error: impl Into<E>,
now_ms: u64,
)
pub fn complete_failure_with_data( &mut self, guard: RequestGuard, data: T, error: impl Into<E>, now_ms: u64, )
Complete with failure but retain data, consuming the guard.
Validates that no new request was started after the guard was issued.
Source§impl<T, E> QueryResource<T, E>
impl<T, E> QueryResource<T, E>
Sourcepub fn should_clear_data_on_complete(&self) -> bool
pub fn should_clear_data_on_complete(&self) -> bool
Whether data should be evicted after observers consume it.
Returns true when CachePolicy::NoCache is set, meaning stored data
will never be used for cache hits and should be cleared after delivery
to avoid holding it in memory indefinitely.
Source§impl<T, E> QueryResource<T, E>
impl<T, E> QueryResource<T, E>
Sourcepub fn begin_request(
&mut self,
sequencer: &mut RequestSequencer,
now_ms: u64,
fetch_mode: QueryFetchMode,
) -> QueryBeginResult
pub fn begin_request( &mut self, sequencer: &mut RequestSequencer, now_ms: u64, fetch_mode: QueryFetchMode, ) -> QueryBeginResult
Begin a new request on this resource.
Respects the cache policy (may return CacheHit) and request policy
(IgnoreWhileLoading or LatestWins). When replacing an existing
request, the old signal is cancelled so the in-flight fetcher
can observe it and abort early.
Sourcepub fn begin_request_with_id(
&mut self,
maybe_request_id: Option<RequestId>,
now_ms: u64,
fetch_mode: QueryFetchMode,
) -> QueryBeginResult
pub fn begin_request_with_id( &mut self, maybe_request_id: Option<RequestId>, now_ms: u64, fetch_mode: QueryFetchMode, ) -> QueryBeginResult
Like begin_request but accepts an optional
pre-generated RequestId instead of using a RequestSequencer.
When maybe_request_id is Some, uses that ID directly (useful when
the bucket’s co-located sequencer has already generated the ID).
When None, falls back to the resource’s own stored sequencer so the
generated ids are monotonic and collision-free across calls (N3) rather
than every call producing a colliding RequestId(1,1).
This is the preferred entry point for the hook layer (audit fixes #1/#5/#15/#18): it allows the bucket’s persistent sequencer to provide globally unique, monotonically increasing RequestIds.
Sourcepub fn is_current_request(&self, request_id: RequestId) -> bool
pub fn is_current_request(&self, request_id: RequestId) -> bool
Whether the given request id is the current active request.
Sourcepub fn accept_current_request(
&mut self,
request_id: RequestId,
) -> Option<RequestGuard>
pub fn accept_current_request( &mut self, request_id: RequestId, ) -> Option<RequestGuard>
Accept a request for completion.
Returns a RequestGuard if the request is still active, or None
if it was replaced or cancelled. The guard is a capability token for
the two-phase protocol (validate → complete).
Sourcepub fn cancel(&mut self, error: E) -> bool
pub fn cancel(&mut self, error: E) -> bool
Cancel the active request.
Returns false if there is no active request.
The signal is cancelled so the in-flight fetcher can observe it.
Data is preserved across cancellations. Current data (if any) is saved
to previous_data before being cleared, allowing recovery via
rollback_to_previous(). This matches TanStack Query behavior where
cancelling a refetch does not destroy existing data.
When the resource was in LoadingEmpty status (no prior data existed),
both data and previous_data remain None. When the resource was in
LoadingWithData status (a refetch with existing data), the prior data
is saved to previous_data and data is set to None. Callers can use
rollback_to_previous() to recover the data if needed.
pub fn mark_ignored_result(&mut self)
Sourcepub fn is_data_stale(&self) -> bool
pub fn is_data_stale(&self) -> bool
Whether the current data was served from stale cache (i.e., a stale-while-revalidate background refetch is in progress or failed).
Returns true when the resource has data but the status indicates
the most recent fetch attempt failed or was cancelled. Consumers can
use this to distinguish “fresh success” from “stale data still being
displayed after a background refetch failure”.
Note: This is a heuristic check. A true result means data exists but
the last fetch did not succeed — the data may still be perfectly valid.
Sourcepub fn reset(&mut self)
pub fn reset(&mut self)
Reset the resource back to idle, clearing state and diagnostic counters.
v2 fix: Cancels the signal before clearing it.
Preserves: cache_policy, request_policy, retry_policy, and key.
These are considered configuration, not runtime state, and persist across
resets. Use QueryResource::new() to create a fully fresh resource with
default policies.
Calling reset() on an already-Idle resource resets diagnostic counters
(cache_hits, cancelled_count, ignored_results, retry_count) to zero.
This is intentional — reset() always resets counters regardless of current
state. If counter preservation is needed, read them before calling reset().
Sourcepub fn rollback_to_previous(&mut self) -> bool
pub fn rollback_to_previous(&mut self) -> bool
Roll back to the previous data (optimistic update undo).
Clears any stored error to maintain the invariant that Success
implies error is None (mirroring apply_success).
Sourcepub fn set_data(&mut self, data: T)
pub fn set_data(&mut self, data: T)
Apply an optimistic update. Current data is saved for rollback.
Sourcepub fn clear_data(&mut self)
pub fn clear_data(&mut self)
Clear data optimistically. Current data is saved for rollback.
Transitions status to Idle to maintain the invariant that Success
implies data is available (mirroring apply_success_optional’s None
branch). Without this, a Success resource with data = None would
panic on data.unwrap().
Source§impl<T, E> QueryResource<T, E>
impl<T, E> QueryResource<T, E>
Sourcepub fn new(
key: impl Into<QueryKey>,
cache_policy: CachePolicy,
request_policy: RequestPolicy,
) -> Self
pub fn new( key: impl Into<QueryKey>, cache_policy: CachePolicy, request_policy: RequestPolicy, ) -> Self
Create a new query resource with the given key and policies.
Trait Implementations§
Source§impl<T: Clone, E: Clone> Clone for QueryResource<T, E>
impl<T: Clone, E: Clone> Clone for QueryResource<T, E>
Source§fn clone(&self) -> QueryResource<T, E>
fn clone(&self) -> QueryResource<T, E>
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl<'de, T, E> Deserialize<'de> for QueryResource<T, E>where
T: Deserialize<'de>,
E: Deserialize<'de>,
impl<'de, T, E> Deserialize<'de> for QueryResource<T, E>where
T: Deserialize<'de>,
E: Deserialize<'de>,
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
impl<T: Eq, E: Eq> Eq for QueryResource<T, E>
Source§impl<T: 'static, E: 'static> ObservableResource for QueryResource<T, E>
Available on crate feature client only.
impl<T: 'static, E: 'static> ObservableResource for QueryResource<T, E>
client only.type Status = QueryStatus
fn observable_status(&self) -> QueryStatus
Source§impl<T, E> Serialize for QueryResource<T, E>
impl<T, E> Serialize for QueryResource<T, E>
impl<T: PartialEq, E: PartialEq> StructuralPartialEq for QueryResource<T, E>
Auto Trait Implementations§
impl<T, E> Freeze for QueryResource<T, E>
impl<T, E> RefUnwindSafe for QueryResource<T, E>
impl<T, E> Send for QueryResource<T, E>
impl<T, E> Sync for QueryResource<T, E>
impl<T, E> Unpin for QueryResource<T, E>
impl<T, E> UnsafeUnpin for QueryResource<T, E>
impl<T, E> UnwindSafe for QueryResource<T, E>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more