gpui_query/core/select.rs
1//! Select/transform support for projecting query data into derived views.
2//!
3//! This module provides [`SelectTransform`] and [`MappedQueryResource`], which
4//! together implement the "select" pattern found in TanStack Query: a query
5//! resource holds raw data of type `T`, and a `MappedQueryResource` applies a
6//! `SelectTransform<T, U>` to project it into type `U` without duplicating the
7//! underlying cache entry.
8//!
9//! # Hook integration
10//!
11//! The [`use_query_select`] hook wires a `SelectTransform` into the `use_query`
12//! lifecycle. It returns a `MappedQueryResource` entity that is kept in sync
13//! with the underlying `QueryResource` via an observer. Each time the source
14//! resource changes (fetch completes, cache hit, etc.), the mapped resource
15//! re-reads the source data and the transform is applied on access.
16//!
17//! # Example
18//!
19//! ```
20//! use gpui_query::core::{SelectTransform, MappedQueryResource};
21//!
22//! // Raw query data: a list of users.
23//! let users = vec!["Alice", "Bob", "Carol"];
24//!
25//! // Transform: extract just the count.
26//! let transform = SelectTransform::new(|users: &Vec<&str>| users.len());
27//!
28//! let mapped = MappedQueryResource::<_, usize, ()>::new(Some(std::sync::Arc::new(users)), transform);
29//! assert_eq!(mapped.data(), Some(3));
30//! ```
31//!
32//! ## Example with the hook
33//!
34//! ```ignore
35//! use gpui_query::hook::{use_query_select, QueryOptions};
36//! use gpui_query::core::SelectTransform;
37//! # #[derive(Clone, PartialEq)]
38//! # struct User;
39//! # #[derive(Clone, Debug)]
40//! # struct MyError;
41//!
42//! struct UserCountView {
43//! mapped: gpui::Entity<gpui_query::core::MappedQueryResource<Vec<User>, usize, MyError>>,
44//! _subs: (gpui::Subscription, gpui::Subscription),
45//! }
46//!
47//! impl UserCountView {
48//! fn new(cx: &mut gpui::Context<Self>) -> Self {
49//! let count_transform = SelectTransform::new(|users: &Vec<User>| users.len());
50//! let (mapped, _, _subs) = use_query_select(
51//! QueryOptions::new("users"),
52//! count_transform,
53//! |signal| async move {
54//! // Your async fetcher here
55//! Ok(vec![])
56//! },
57//! cx,
58//! );
59//! Self { mapped, _subs }
60//! }
61//! }
62//! ```
63
64use std::sync::Arc;
65
66/// A select transform that maps cached data of type `T` to output type `U`.
67///
68/// Stored as `Arc<dyn Fn(&T) -> U>` to be `Clone + Send + Sync`. Use this
69/// with [`MappedQueryResource`] to derive a projected view from cached query
70/// data without storing a separate copy.
71///
72/// # Example
73///
74/// ```
75/// use gpui_query::core::SelectTransform;
76///
77/// let uppercase = SelectTransform::new(|name: &String| name.to_uppercase());
78/// assert_eq!(uppercase.apply(&"hello".to_string()), "HELLO");
79/// ```
80pub struct SelectTransform<T, U> {
81 transform: Arc<dyn Fn(&T) -> U + Send + Sync>,
82 _marker: std::marker::PhantomData<(T, U)>,
83}
84
85impl<T, U> Clone for SelectTransform<T, U> {
86 fn clone(&self) -> Self {
87 Self {
88 transform: Arc::clone(&self.transform),
89 _marker: std::marker::PhantomData,
90 }
91 }
92}
93
94impl<T, U> std::fmt::Debug for SelectTransform<T, U> {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 f.debug_struct("SelectTransform").finish()
97 }
98}
99
100impl<T, U> PartialEq for SelectTransform<T, U> {
101 fn eq(&self, other: &Self) -> bool {
102 // Closures have no PartialEq; compare by shared pointer identity,
103 // mirroring QuerySignal's Arc::ptr_eq approach (signal.rs).
104 Arc::ptr_eq(&self.transform, &other.transform)
105 }
106}
107
108impl<T, U> Eq for SelectTransform<T, U> {}
109
110impl<T, U> SelectTransform<T, U> {
111 /// Create a new select transform from a closure.
112 pub fn new(transform: impl Fn(&T) -> U + Send + Sync + 'static) -> Self {
113 Self {
114 transform: Arc::new(transform),
115 _marker: std::marker::PhantomData,
116 }
117 }
118
119 /// Apply the transform to data.
120 pub fn apply(&self, data: &T) -> U {
121 (self.transform)(data)
122 }
123}
124
125/// A mapped view over a `QueryResource` that applies a [`SelectTransform`].
126///
127/// This implements the "select" pattern: multiple consumers can derive
128/// different views from the same underlying cached data, each with their own
129/// `MappedQueryResource` holding a different transform function. The source
130/// data is shared, so there is no duplication.
131///
132/// # Type parameters
133///
134/// - `T`: The source data type (the cached query result).
135/// - `U`: The projected output type (the derived view).
136/// - `E`: The error type (carried through for API consistency).
137///
138/// # Storage
139///
140/// Source data is held as `Option<Arc<T>>` (audit #20) so that cloning a
141/// `MappedQueryResource` (e.g. for derived views) is a cheap `Arc::clone`
142/// rather than a full copy of `T`. `Arc<T>` is `Send + Sync` exactly when `T`
143/// is, so the existing bounds are preserved.
144#[derive(Clone, Debug, PartialEq, Eq)]
145pub struct MappedQueryResource<T, U, E> {
146 source_data: Option<Arc<T>>,
147 transform: SelectTransform<T, U>,
148 _error_marker: std::marker::PhantomData<E>,
149}
150
151impl<T, U, E> MappedQueryResource<T, U, E> {
152 /// Create a new mapped resource.
153 ///
154 /// Takes ownership of the source data as `Option<Arc<T>>` (audit #20). For
155 /// the common case of constructing from a plain `T`, wrap it with
156 /// `Some(Arc::new(t))`.
157 pub fn new(source_data: Option<Arc<T>>, transform: SelectTransform<T, U>) -> Self {
158 Self {
159 source_data,
160 transform,
161 _error_marker: std::marker::PhantomData,
162 }
163 }
164
165 /// Apply the transform to get the selected data.
166 ///
167 /// **Note (audit #3):** This re-applies the transform closure on every call.
168 /// `MappedQueryResource` is a derived view with no separate output cache — it
169 /// stores only the source data and the transform function. If the transform is
170 /// expensive and you need the result multiple times in a single render pass
171 /// (e.g., once for display and once for an equality check), cache the result
172 /// in a local variable:
173 ///
174 /// ```
175 /// use gpui_query::core::{MappedQueryResource, SelectTransform};
176 ///
177 /// let transform = SelectTransform::new(|v: &Vec<i32>| v.len());
178 /// let mapped = MappedQueryResource::<_, usize, ()>::new(Some(std::sync::Arc::new(vec![1, 2, 3])), transform);
179 /// let data = mapped.data(); // transform runs once
180 /// assert_eq!(data, Some(3));
181 /// // use `data` freely below
182 /// ```
183 ///
184 /// For lightweight transforms (field access, counting, simple projections) the
185 /// cost is negligible and no caching is needed.
186 pub fn data(&self) -> Option<U> {
187 // `source_data` is `Option<Arc<T>>`; deref the Arc so the transform
188 // still receives `&T` as documented (audit #20).
189 self.source_data
190 .as_ref()
191 .map(|d| self.transform.apply(d.as_ref()))
192 }
193
194 /// Whether source data exists.
195 pub fn has_data(&self) -> bool {
196 self.source_data.is_some()
197 }
198
199 /// Read-only access to the source data.
200 ///
201 /// Returns `Option<&T>` by dereferencing the stored `Arc<T>` (audit #20).
202 /// Keeping the `&T` return type (rather than `&Arc<T>`) is the least
203 /// disruptive choice: existing callers compare the pointed-to `T` and do
204 /// not need to change. Used by the hook layer to detect when the source
205 /// has changed before re-storing.
206 pub fn source_data(&self) -> Option<&T> {
207 self.source_data.as_ref().map(|arc| arc.as_ref())
208 }
209
210 /// Cheaply hand out the cached source `Arc<T>` as an owned value.
211 ///
212 /// Returns `Option<Arc<T>>` via a refcount bump (`Arc::clone`) — no `T`
213 /// clone. Audit H1: the hook layer uses this to compare the cached source
214 /// against a fresh read WITHOUT cloning `T` on unchanged notifications.
215 /// Because the returned `Arc<T>` is owned, the mapped borrow ends with
216 /// this call, so a subsequent `entity.read_with` does not create the
217 /// nested borrow that audit #115 removed.
218 pub fn source_arc(&self) -> Option<Arc<T>> {
219 self.source_data.clone()
220 }
221
222 /// Update the source data from the underlying query resource.
223 ///
224 /// Call this when the source `QueryResource` changes (fetch completes,
225 /// cache invalidation, etc.) to keep the mapped view in sync. The transform
226 /// is not applied here — it is applied lazily when [`data()`](Self::data)
227 /// is called. Takes `Option<Arc<T>>` so callers can hand over a cheap
228 /// `Arc::clone` instead of cloning the full `T` (audit #20).
229 pub fn update_source(&mut self, data: Option<Arc<T>>) {
230 self.source_data = data;
231 }
232}