gpui_query/client/erased.rs
1//! Type-erased bucket traits and persistence adapter.
2//!
3//! These traits allow `QueryClient` to store heterogeneous query and mutation
4//! buckets in a single `AHashMap<TypeId, Box<dyn Erased*>>` map, dispatching
5//! to concrete types only when the caller provides generic parameters.
6//!
7//! # Feature gating
8//!
9//! The persistence-only surface (`collect_key_status_into`, the
10//! value-carrying `collect_persistable_into`, and the legacy synchronous
11//! [`QueryPersister`] trait) is gated behind the `persist` feature. The
12//! non-persistence methods (`gc`, `invalidate_matching`, `diagnostics`, …)
13//! remain ungated so the default build is unchanged.
14
15use crate::client::devtools::{MutationDiagnostic, QueryDiagnostic};
16#[cfg(feature = "persist")]
17use crate::client::persist::{PersistedEntry, SerializerRegistry};
18use crate::core::QueryKeyFilter;
19#[cfg(feature = "persist")]
20use crate::core::{MutationStatus, QueryStatus};
21
22// `current_time_ms` moved to `client/time.rs` (ungated) so the `persist`
23// feature gate on this module's persistence symbols does not drag the GC
24// clock helper behind a `cfg`. See [`crate::client::time::current_time_ms`].
25
26/// Type-erased bucket trait for storage in a homogeneous map.
27pub(crate) trait ErasedBucket {
28 fn as_any(&self) -> &dyn std::any::Any;
29 fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
30 fn gc(&mut self, now_ms: u64, gc_time_ms: u64, cx: &gpui::App);
31 fn count(&self) -> usize;
32 fn invalidate_matching(&mut self, filter: &QueryKeyFilter, cx: &mut gpui::App);
33 fn reset_matching(&mut self, filter: &QueryKeyFilter, cx: &mut gpui::App);
34 fn remove_matching(&mut self, filter: &QueryKeyFilter);
35 fn cancel_matching(&mut self, filter: &QueryKeyFilter, cx: &mut gpui::App);
36 /// Push each live entry's diagnostic into the caller-supplied `out` Vec
37 /// instead of allocating a fresh `Vec` per bucket. Callers
38 /// (`QueryClient::diagnostics`) can pre-size a single destination Vec and
39 /// let every bucket push into it, avoiding the per-bucket allocation +
40 /// `extend` a returning variant would force.
41 fn collect_diagnostics_into(&self, now_ms: u64, cx: &gpui::App, out: &mut Vec<QueryDiagnostic>);
42 /// Push each live entry's `(key, status)` pair into `out` without building
43 /// full `QueryDiagnostic`s (#9). Used by `dehydrate`, which only needs the
44 /// key and status, avoiding the per-entry allocations of
45 /// [`collect_diagnostics_into`](ErasedBucket::collect_diagnostics_into).
46 #[cfg(feature = "persist")]
47 fn collect_key_status_into(&self, cx: &gpui::App, out: &mut Vec<(String, QueryStatus)>);
48 /// Push each `Success` entry's `(key, entry)` pair into `out`, serializing
49 /// the typed data via the caller-supplied [`SerializerRegistry`]. Entries
50 /// whose `T` has no registered serializer are skipped (metadata-only
51 /// fallback, matching the legacy `dehydrate` behavior). Used by
52 /// [`persist_with`](crate::client::QueryClient::persist_with) to build a
53 /// value-carrying [`PersistSnapshot`](crate::client::PersistSnapshot).
54 #[cfg(feature = "persist")]
55 fn collect_persistable_into(
56 &self,
57 cx: &gpui::App,
58 serializers: &SerializerRegistry,
59 now_ms: u64,
60 out: &mut Vec<(crate::core::QueryKey, PersistedEntry)>,
61 );
62}
63
64/// Type-erased infinite query bucket trait.
65pub(crate) trait ErasedInfiniteBucket {
66 fn as_any(&self) -> &dyn std::any::Any;
67 fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
68 fn gc(&mut self, now_ms: u64, gc_time_ms: u64, cx: &gpui::App);
69 fn count(&self) -> usize;
70 fn invalidate_matching(&mut self, filter: &QueryKeyFilter, cx: &mut gpui::App);
71 fn reset_matching(&mut self, filter: &QueryKeyFilter, cx: &mut gpui::App);
72 fn remove_matching(&mut self, filter: &QueryKeyFilter);
73 fn cancel_matching(&mut self, filter: &QueryKeyFilter, cx: &mut gpui::App);
74 /// Push each live entry's diagnostic into `out`. See
75 /// [`ErasedBucket::collect_diagnostics_into`].
76 fn collect_diagnostics_into(&self, now_ms: u64, cx: &gpui::App, out: &mut Vec<QueryDiagnostic>);
77 /// Push each live entry's `(key, status)` pair into `out` without building
78 /// full `QueryDiagnostic`s (#9). See
79 /// [`ErasedBucket::collect_key_status_into`].
80 #[cfg(feature = "persist")]
81 fn collect_key_status_into(&self, cx: &gpui::App, out: &mut Vec<(String, QueryStatus)>);
82 /// Value-carrying variant for persistence. See
83 /// [`ErasedBucket::collect_persistable_into`].
84 #[cfg(feature = "persist")]
85 fn collect_persistable_into(
86 &self,
87 cx: &gpui::App,
88 serializers: &SerializerRegistry,
89 now_ms: u64,
90 out: &mut Vec<(crate::core::QueryKey, PersistedEntry)>,
91 );
92}
93
94/// Type-erased mutation bucket trait.
95pub(crate) trait ErasedMutationBucket {
96 fn as_any(&self) -> &dyn std::any::Any;
97 fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
98 fn gc(&mut self, now_ms: u64, gc_time_ms: u64, cx: &gpui::App);
99 fn count(&self) -> usize;
100 /// Push each live entry's `MutationDiagnostic` into `out` instead of
101 /// allocating a fresh `Vec` per bucket. See
102 /// [`ErasedBucket::collect_diagnostics_into`] for the rationale.
103 fn collect_diagnostics_into(&self, cx: &gpui::App, out: &mut Vec<MutationDiagnostic>);
104 /// Push each live entry's `(key, status)` pair into `out` without building
105 /// full `MutationDiagnostic`s (#9). `key` is `None` for keyless mutations,
106 /// mirroring [`MutationDiagnostic::key`]. Used by `dehydrate`.
107 #[cfg(feature = "persist")]
108 fn collect_key_status_into(
109 &self,
110 cx: &gpui::App,
111 out: &mut Vec<(Option<String>, MutationStatus)>,
112 );
113}
114
115/// Legacy synchronous persistence adapter trait for query cache persistence
116/// across app restarts.
117///
118/// **Note**: this is the shipped metadata-only skeleton. The richer async,
119/// value-carrying surface lives in [`crate::client::persist`] (the
120/// [`Persister`](crate::client::persist::Persister) trait +
121/// [`persist_with`](crate::client::QueryClient::persist_with)). This trait is
122/// retained for the existing `dehydrate`/`hydrate`/`persist`/`restore` methods
123/// and is feature-gated behind `persist`.
124///
125/// Implementations can store cached data in any backend (filesystem, database, etc.).
126/// Entries are serialized as JSON strings to avoid generic bounds on the persister.
127///
128/// # Example
129///
130/// ```
131/// use std::path::PathBuf;
132/// use gpui_query::client::{QueryPersister, DehydratedEntry};
133///
134/// struct FilePersister { path: PathBuf }
135///
136/// impl QueryPersister for FilePersister {
137/// fn load(&self) -> Vec<DehydratedEntry> { Vec::new() }
138/// fn save(&self, _entries: Vec<DehydratedEntry>) {}
139/// }
140/// ```
141#[cfg(feature = "persist")]
142pub trait QueryPersister: Send + Sync {
143 /// Load persisted entries from storage.
144 fn load(&self) -> Vec<crate::client::devtools::DehydratedEntry>;
145
146 /// Save entries to storage, replacing any previously stored data.
147 fn save(&self, entries: Vec<crate::client::devtools::DehydratedEntry>);
148}