# Query Keys

Hierarchical, cheap-to-clone cache keys and the QueryKeyFilter for bulk operations

Every cached resource in gpui-query is identified by a [`QueryKey`](/docs/api/queries), a structured, hierarchical key inspired by TanStack Query's array keys (`["users", "42", "posts"]`). Keys are the foundation of caching, deduplication, and the bulk operations (`invalidate_queries`, `cancel_queries`, `reset_queries`, `remove_queries`) that the [`QueryClient`](/docs/api/query-client) exposes.

## Creating a key

A `QueryKey` is an ordered sequence of string segments. The simplest case is a single string:

```rust
use gpui_query::QueryKey;

let key = QueryKey::from("users");
```

For resources scoped by an identifier, build a multi-segment key. The key records the order of the segments, so `["users", "42"]` and `["42", "users"]` are different keys.

```rust
let user_key    = QueryKey::from(["users", "42"]);
let user_posts  = QueryKey::from(["users", "42", "posts"]);
let post_detail = QueryKey::from(["posts", "7"]);
```

You can construct a key from any of:

- `&str` / `String` (single segment)
- `[&str; N]`, `Vec<&str>`, `Vec<String>` (multiple segments)
- `QueryKey::new(iterator)` (panics on an empty iterator: a key must have at least one segment)

## Cheap cloning

Internally a `QueryKey` stores its segments in an `Arc<[Arc<str>]>`, so cloning a key is a single atomic ref-count increment regardless of how many segments it holds. This matters because the hook layer and the `QueryClient` both clone keys freely (into buckets, into fetch tasks, into diagnostics). You never pay an allocation per clone.

```rust
let key = QueryKey::from(["users", "42", "posts", "comments"]);
let cloned = key.clone();
assert!(std::sync::Arc::ptr_eq(&key.parts()[0], &cloned.parts()[0]));
```

## Prefix matching

Two methods make hierarchical keys useful:

- `starts_with(prefix)`: `true` when this key begins with `prefix`. This is the primitive the bulk operations use to invalidate a whole subtree.
- `to_path()`: a `users::42::posts`-style display string for DevTools and logs. The `::` separator avoids ambiguity when a segment itself contains a forward slash.

```rust
use gpui_query::QueryKey;

let posts = QueryKey::from(["users", "42", "posts"]);

assert!(posts.starts_with(&QueryKey::from(["users"])));
assert!(posts.starts_with(&QueryKey::from(["users", "42"])));
assert!(!posts.starts_with(&QueryKey::from(["posts"])));

assert_eq!(posts.to_path(), "users::42::posts");
```

> Adopt a convention where each segment narrows the scope: `["resource", "id", "view"]`. Then `["users"]` is the whole users subtree, `["users", "42"]` is everything for user 42, and `["users", "42", "posts"]` is that user's posts. Bulk invalidation falls out of this naturally.

## Using keys with hooks

The options-first hooks accept anything that converts `Into<QueryKey>`, so you can pass a bare string for the simple case or a structured key for a scoped resource:

```rust
use gpui_query::hook::use_query;
use gpui_query::{QueryOptions, QueryKey};
# #[derive(Clone)]
# struct User;
# #[derive(Clone, Debug)]
# struct MyError;

impl View {
    fn new(cx: &mut gpui::Context<Self>) -> Self {
        // Single string — the common case.
        let (users, _) = use_query("users", |_signal| async move {
            Ok::<_, MyError>(vec![])
        }, cx);

        // Structured key for a scoped resource.
        let (user, _) = use_query(
            QueryKey::from(["users", "42"]),
            |_signal| async move { Ok::<_, MyError>(User) },
            cx,
        );

        // Equivalent, via QueryOptions:
        let _ = use_query(
            QueryOptions::new(["users", "42", "posts"]),
            |_signal| async move { Ok::<_, MyError>(vec![]) },
            cx,
        );
        // ...
#       unimplemented!()
    }
}
```

## QueryKeyFilter

The bulk operations on `QueryClient` do not take a raw key; they take a [`QueryKeyFilter`](/docs/api/query-client), which is how you say *which* keys to act on. There are three variants:

| Filter     | Matches                                                   |
| ---------- | -------------------------------------------------------- |
| `Exact(k)` | Only the key that is exactly equal to `k`.               |
| `Prefix(k)`| Every key that `starts_with(k)`: the whole subtree.    |
| `All`      | Every key in the cache.                                  |

`QueryKeyFilter` is `Copy`, so you can build it inline at the call site:

```rust
use gpui_query::client::QueryClient;
use gpui_query::core::{QueryKey, QueryKeyFilter};

# fn _doc(client: &mut QueryClient, cx: &mut gpui::App) {
// Invalidate everything under the "users" subtree — every user,
// every user's posts, etc.
let users = QueryKey::from(["users"]);
client.invalidate_queries(&QueryKeyFilter::Prefix(&users), cx);

// Invalidate just one specific resource.
let one = QueryKey::from(["users", "42"]);
client.invalidate_queries(&QueryKeyFilter::Exact(&one), cx);

// Throw away all cached query state (e.g. on logout).
client.remove_queries(&QueryKeyFilter::All);
# }
```

### Choosing a filter

- **`Exact`** when you know the full key (for example, after a mutation that updates a single record). `invalidate_queries(Exact(&["users", "42"]))` refetches only that user.
- **`Prefix`** when a mutation affects a family of resources. For example, after creating a user, `invalidate_queries(Prefix(&["users"]))` refetches the users list and every cached user.
- **`All`** for global resets, most commonly on logout: `remove_queries(All)` clears the cache, and `reset_queries(All)` clears data and cancels in-flight requests.

## Patterns

### Scoped keys for list + detail

Keep the list and a detail view under a shared prefix so one invalidation refreshes both:

```rust
let list  = QueryKey::from(["users"]);             // the list
let detail= QueryKey::from(["users", id.as_str()]); // one user
```

After an update mutation, `invalidate_queries(Prefix(&["users"]))` refreshes the list and any cached detail pages in one call.

### Dynamic segments

`QueryKey` only stores strings. To scope by a non-string id, format it into the key:

```rust
let key = QueryKey::from(["projects", &project_id.to_string(), "members"]);
```

### Avoid empty keys

`QueryKey::new` with a zero-length iterator panics. Every key must have at least one segment; there is no "root" key. Use `QueryKeyFilter::All` when you mean "everything".

## Next steps

- [`QueryClient`](/docs/api/query-client): the bulk operations that consume a `QueryKeyFilter`.
- [Caching](/docs/guides/caching): how `CachePolicy` interacts with keys.
- [Error handling](/docs/guides/error-handling): surfacing failures keyed by resource.
