rs-query 0.0.1

TanStack Query-inspired async state management for GPUI
Documentation
<p align="center">
  <img src="https://dreamstack-us.github.io/rs-query/logo.png" alt="rs-query" width="400">
</p>

<p align="center">
  <strong>TanStack Query-inspired async state management for <a href="https://gpui.rs">GPUI</a></strong>
</p>

<p align="center">
  <a href="https://crates.io/crates/rs-query"><img src="https://img.shields.io/crates/v/rs-query.svg" alt="crates.io"></a>
  <a href="https://docs.rs/rs-query"><img src="https://docs.rs/rs-query/badge.svg" alt="docs.rs"></a>
  <a href="https://github.com/DreamStack-us/rs-query/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License"></a>
</p>

---

> **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.

---

```rust
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

```toml
[dependencies]
rs-query = "0.0.1"
```

## Quick Start

```rust
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

```rust
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:

```rust
// 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

```rust
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:

```rust
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](CONTRIBUTING.md) or open an issue.

## License

MIT — Built by [DreamStack](https://dreamstack.us)