rs-query

Crates.iors-query
lib.rsrs-query
version0.0.1
created_at2025-11-29 01:54:03.236261+00
updated_at2025-11-29 01:54:03.236261+00
descriptionTanStack Query-inspired async state management for GPUI
homepagehttps://dreamstack-us.github.io/rs-query
repositoryhttps://github.com/DreamStack-us/rs-query
max_upload_size
id1956266
size213,192
Srihari Rao (hariDasu)

documentation

https://docs.rs/rs-query

README

rs-query

TanStack Query-inspired async state management for GPUI

crates.io docs.rs MIT License


A note on this project's origins:

This library was built by a developer with minimal Rust experience, with significant assistance from Claude (Opus 4.5). It's an honest attempt to bring TanStack Query patterns to the GPUI ecosystem.

This is not production-ready. It's an early alpha (v0.0.1) that needs:

  • More comprehensive unit tests (aspiring to follow standards set by tokio, serde, etc.)
  • Real-world battle testing
  • API refinement based on community feedback

Contributions, criticism, and feedback are warmly welcomed. If you're an experienced Rustacean, your guidance would be invaluable. Open an issue, submit a PR, or just tell us what we got wrong.


spawn_query(cx, &client, &query, |this, state, cx| {
    match state {
        QueryState::Success(users) => this.users = users,
        QueryState::Error { error, .. } => this.error = Some(error),
        _ => {}
    }
    cx.notify();
});

Features

  • spawn_query / spawn_mutation — One-liner async execution with automatic state management
  • Stale-while-revalidate — Show cached data instantly while fetching fresh data
  • Automatic retries — Exponential backoff for transient failures
  • Cache invalidation — Hierarchical key patterns for precise cache control
  • GPUI-native — Built for GPUI's context and reactive model

Installation

[dependencies]
rs-query = "0.0.1"

Quick Start

use rs_query::{QueryClient, QueryKey, QueryState, Query, spawn_query};
use gpui::*;

struct MyView {
    client: QueryClient,
    users: Vec<User>,
    loading: bool,
}

impl MyView {
    fn fetch_users(&mut self, cx: &mut Context<Self>) {
        let query = Query::new(
            QueryKey::new("users"),
            || async { fetch_users_from_api().await }
        );

        spawn_query(cx, &self.client, &query, |this, state, cx| {
            match state {
                QueryState::Loading => {
                    this.loading = true;
                }
                QueryState::Success(users) => {
                    this.users = users;
                    this.loading = false;
                }
                QueryState::Error { error, retry_count } => {
                    eprintln!("Failed after {} retries: {}", retry_count, error);
                    this.loading = false;
                }
                QueryState::Stale(users) => {
                    // Show stale data while refetching
                    this.users = users;
                }
            }
            cx.notify();
        });
    }
}

Mutations

use rs_query::{Mutation, MutationState, spawn_mutation};

let mutation = Mutation::new(|user: CreateUser| async move {
    api::create_user(user).await
});

spawn_mutation(cx, &client, &mutation, new_user, |this, state, cx| {
    match state {
        MutationState::Success(user) => {
            this.users.push(user);
            // Invalidate related queries
            this.client.invalidate(&QueryKey::new("users"));
        }
        MutationState::Error(e) => this.error = Some(e),
        _ => {}
    }
    cx.notify();
});

Query Keys

Hierarchical keys for cache management:

// Simple key
let key = QueryKey::new("users");

// Nested key
let key = QueryKey::new("users").push("123").push("posts");

// Invalidate all user queries
client.invalidate(&QueryKey::new("users"));

Configuration

use rs_query::{Query, QueryOptions, RetryConfig, RefetchOnMount};

let query = Query::new(QueryKey::new("users"), fetch_users)
    .with_options(
        QueryOptions::new()
            .stale_time(Duration::from_secs(60))
            .cache_time(Duration::from_secs(300))
            .retry(RetryConfig::new(3).with_base_delay(Duration::from_millis(500)))
            .refetch_on_mount(RefetchOnMount::Stale)
    );

Why rs-query?

If you're building GPUI apps with async data fetching, you've probably written this pattern dozens of times:

cx.spawn(|this, mut cx| async move {
    let result = fetch_data().await;
    this.update(&mut cx, |this, cx| {
        this.data = result;
        cx.notify();
    });
});

rs-query handles loading states, caching, retries, and cache invalidation — so you can focus on your app.

Roadmap

  • Comprehensive test suite
  • Benchmarks
  • Query devtools
  • Infinite queries
  • Optimistic updates
  • Persistence adapters

Contributing

This project needs your help! Whether you're:

  • An experienced Rustacean who can improve the internals
  • A GPUI user who can provide real-world feedback
  • Someone who can write tests and documentation

...we'd love to hear from you. See CONTRIBUTING.md or open an issue.

License

MIT — Built by DreamStack

Commit count: 0

cargo fmt