Skip to content
gpui-query logogpui-query

Navigation

async state for GPUI

Cache, fetch,
render. One path.

gpui-query wires network, cache, retry, and revalidation into one small API for GPUI applications.

schematic://query-pathLIVE
render()YOUR VIEWuse_queryHOOKQueryClientGLOBALCACHEFRESHNETWORKFETCHER

observer attached · watching "users"

// capabilities

Everything the fetch deserves

The full TanStack Query feature set, rebuilt around GPUI's entity and async model.

01

Declarative Queries

One hook. gpui-query handles fetching, caching, and state updates so your render code stays a pure function of data.

02

Smart Caching

Cache policies for every use case — pick one per query and move on.

TTLSWRLatestWinsIgnoreWhileLoading
03

Mutations

First-class mutation support with success and error callbacks, plus optimistic updates that roll back on failure.

04

Infinite Queries

Paginate effortlessly with built-in infinite query support and bidirectional fetching.

05

Cancellation

Signal-checked retries and cooperative cancellation for a clean async lifecycle — no orphaned tasks.

06

Persistence

Serialize and restore query state across launches with custom persistence backends.

QueryClient, live

Entries age, go stale, revalidate, and log each transition. Use the row controls to force the lifecycle.

cache://QueryClient · liveT+000.0
usersFRESHttl 5.2s
repo:zed/zedFRESHttl 11.8s
releases?page=2FRESHttl 4.2s
entries 3in-flight 0fetches 3policy StaleWhileRevalidate

event log

  • T+000.0 observers registered · 3
  • T+000.0 QueryClient::new() · gc 300s · policy swr

// surface area

Three hooks. That's the API.

Queries, mutations, and pagination share the same shape: a key, an async function, and your context. Everything returns an Entity you read in render.

let (users, _sub) = use_query(    QueryOptions::new("users")        .cache_policy(CachePolicy::StaleWhileRevalidate {            ttl_ms: 60_000,            stale_ms: 300_000,        })        .retry_policy(RetryPolicy::new(3).with_exponential_backoff()),    |signal| async move { fetch_users(&signal).await },    cx,);
(Entity<QueryResource<T, E>>, Subscription)

// diff

The code you stop writing

The same feature — a fetched, cached user list — written both ways. Toggle to compare.

31 lines
1struct UserList {2    users: Option<Vec<User>>,3    error: Option<String>,4    loading: bool,5    generation: u64,6}78impl UserList {9    fn fetch(&mut self, cx: &mut Context<Self>) {10        self.loading = true;11        self.generation += 1;12        let generation = self.generation;13        cx.spawn(async move |this, cx| {14            let result = fetch_users().await;15            this.update(cx, |this, cx| {16                if this.generation != generation {17                    return; // superseded by a newer request18                }19                this.loading = false;20                match result {21                    Ok(users) => this.users = Some(users),22                    Err(err) => this.error = Some(err.to_string()),23                }24                cx.notify();25            })26        })27        .detach();28    }29}3031// still missing: caching, ttl, retry, dedup across views…

31 lines of lifecycle plumbing → 7 lines, with more behavior.

// comparison

Why gpui-query?

See how gpui-query compares to hand-rolling async state in GPUI.

Featuregpui-querycx.spawn()Raw Futures
Caching
Auto Retry
Deduplication
Cache Policies
DevTools
Persistence
Type Safety
No Setup Required
Zero Dependencies

FIG. 1 — anatomy of a query · hover a callout

Every line, accounted for

src/views/users.rsrust
let (users, _sub) = use_query(    QueryOptions::new("users")⟨A⟩        .cache_policy(CachePolicy::StaleWhileRevalidate {⟨B⟩            ttl_ms: 60_000,            stale_ms: 300_000,        })        .retry_policy(RetryPolicy::new(3)⟨C⟩            .with_exponential_backoff()),    |signal| async move {⟨D⟩        fetch_users(&signal).await    },    cx,⟨E⟩);

// architecture

Three layers, one direction

Each layer has a single responsibility. Data flows down, results flow back up.

L1

Hook Layer

use_query / use_mutation / use_infinite_query

L2

Client Layer

QueryClient / Registry / GC

L3

Core Layer

QueryResource / CachePolicy / QueryKey

data flowsuse_queryQueryClientQueryResource

Ship the lifecycle once

Add the crate, keep your views small, and let QueryClient own the async state machine.

cargo add gpui-query
↑↓NavigateEnterOpenEscClose
Powered by Pagefind