Expand description
§Freya Query
A powerful, async-focused data management library for Freya applications. Inspired by React Query and SWR, it provides intelligent caching, background updates, and automatic invalidation for async operations.
§Overview
Freya Query manages two types of async operations:
- Queries: Read operations that fetch and cache data
- Mutations: Write operations that modify data and can invalidate queries
§Key Features
- Automatic Caching: Query results are cached and reused across components
- Background Refetching: Stale data is automatically refreshed in the background
- Invalidation: Mutations can invalidate related queries to keep data fresh
- Deduplication: Multiple identical queries are automatically deduplicated
- Error Handling: Built-in error states
- Reactive: Integrates seamlessly with Freya’s reactive state system
§Basic Usage
§Queries
use freya::prelude::*;
use freya_query::prelude::*;
// Define a query capability
#[derive(Clone, PartialEq, Hash, Eq)]
struct FetchUser;
impl QueryCapability for FetchUser {
type Ok = User;
type Err = String;
type Keys = u32;
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
// Fetch user from API
fetch_user(*keys).await
}
}
#[derive(PartialEq)]
struct UserProfile(u32);
impl Component for UserProfile {
fn render(&self) -> impl IntoElement {
let user_query = use_query(Query::new(self.0, FetchUser));
format!("{:?}", user_query.read().state())
}
}§Mutations
use freya::prelude::*;
use freya_query::prelude::*;
#[derive(Clone, PartialEq, Hash, Eq)]
struct UpdateUser {
id: u32,
}
// Define a query capability
impl MutationCapability for UpdateUser {
type Ok = ();
type Err = String;
type Keys = String;
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
update_user(self.id, &keys).await?;
Ok(())
}
async fn on_settled(&self, keys: &Self::Keys, result: &Result<Self::Ok, Self::Err>) {
if result.is_ok() {
QueriesStorage::<FetchUser>::invalidate_matching(self.id).await;
}
}
}
#[derive(PartialEq)]
struct UserEditor {
user_id: u32,
}
impl Component for UserEditor {
fn render(&self) -> impl IntoElement {
let mutation = use_mutation(Mutation::new(UpdateUser { id: self.user_id }));
Button::new()
.child("Update User")
.on_press(move |_| mutation.mutate("New Name".to_string()))
}
}§Advanced Patterns
§Query Invalidation
Mutations can invalidate queries to ensure data consistency:
ⓘ
// Invalidate all user queries
QueriesStorage::<FetchUser>::invalidate_all().await;
// Invalidate specific user query
QueriesStorage::<FetchUser>::invalidate_matching(1).await;§Custom Query Matching
Control which queries get invalidated by implementing custom matching logic:
impl QueryCapability for FetchUser {
// ... other methods
fn matches(&self, keys: &Self::Keys) -> bool {
// Only match queries with the same user ID
&self.id == keys
}
}§Background Refetching
Queries automatically refetch data in the background when components remount or when explicitly invalidated by mutations.
§Architecture
Freya Query uses a hierarchical caching system:
- Query Store: Global cache of query results by capability type and keys
- Mutation Store: Tracks running mutations and their invalidation logic
- Reactive Integration: Seamlessly integrates with Freya’s state management
§Error Handling
Both queries and mutations return Result<T, E> types. Freya Query provides
utilities for handling loading states, errors, and retries.
§Performance
- Queries Deduplication: Identical concurrent queries are automatically deduplicated
- Smart Caching: Results are cached until invalidated or expired
- Minimal Re-renders: Only components reading changed data re-render