Skip to main content

myko/core/query/
traits.rs

1//! Query trait definitions.
2
3use std::{fmt::Debug, sync::Arc};
4
5use hyphae::CellImmutable;
6#[cfg(not(target_arch = "wasm32"))]
7use hyphae::MapQuery;
8use serde::{Serialize, de::DeserializeOwned};
9use serde_json::Value;
10
11use super::{
12    super::item::{AnyItem, Eventable},
13    context::QueryContext,
14    request::QueryRequest,
15};
16#[cfg(not(target_arch = "wasm32"))]
17use crate::core::query::QueryCellContext;
18#[cfg(not(target_arch = "wasm32"))]
19use crate::core::query::cell::FilteredCellMap;
20use crate::{
21    cache::CacheKey,
22    client::MykoClient,
23    common::{with_id::WithId, with_transaction::WithTransaction},
24    prelude::WithTypedId,
25    wire::WrappedQuery,
26};
27
28// ─────────────────────────────────────────────────────────────────────────────
29// Core Query Traits
30// ─────────────────────────────────────────────────────────────────────────────
31
32pub trait QueryId {
33    fn query_id(&self) -> Arc<str>;
34}
35
36pub trait QueryIdStatic {
37    fn query_id_static() -> Arc<str>;
38}
39
40pub trait QueryItemType {
41    type Item: WithTypedId + std::fmt::Debug + PartialEq + Send + Sync;
42    fn query_item_type(&self) -> Arc<str>;
43    fn query_item_type_static() -> Arc<str>;
44}
45
46/// Implementing QueryHandler for a MykoQuery is required to define the logic for filtering entities based on the query.
47///
48/// It requires one function: test_entity which takes a `QueryHandlerContext<Self>` and returns a `bool`.
49/// This answers the question of whether an entity should be included in the query results.
50///
51/// If `true`, updates to this query will be calculated, and the item will be added or updated as appropriate.
52///
53/// If `false`, updates to this query will be calculated, and the item will be removed if it exists.
54///
55/// Any deduplication of changes to this query are handled upstream in the handler logic.
56pub trait QueryHandler: QueryItemType + Sized {
57    /// Per-entity membership predicate.
58    ///
59    /// Return `true` when an item should be included in the query result.
60    #[cfg(not(target_arch = "wasm32"))]
61    fn test_entity(ctx: QueryTestCtx<Self>) -> bool
62    where
63        Self: Send + Sync + 'static;
64
65    /// Per-entity membership predicate (wasm no-op).
66    ///
67    /// Query evaluation only runs server-side; on wasm32 the hand-written
68    /// native body is gated out and this no-op default applies. It is never
69    /// invoked on wasm (clients watch query results over the wire), so it
70    /// simply returns `false`.
71    #[cfg(target_arch = "wasm32")]
72    fn test_entity(_ctx: QueryTestCtx<Self>) -> bool
73    where
74        Self: Send + Sync + 'static,
75    {
76        false
77    }
78
79    /// Optional set-wise reactive builder for complex many-to-many joins.
80    ///
81    /// When implemented, this is preferred by the runtime over per-item
82    /// `test_entity` evaluation and should return a reactive map plan that
83    /// the runtime materializes once at the registration boundary. Returning
84    /// `impl MapQuery<...>` lets impls compose `inner_join`, `project_map`,
85    /// `select_cell`, etc. without forcing intermediate `CellMap` allocations.
86    /// Concrete `CellMap`/`FilteredCellMap` values still satisfy the bound
87    /// via the blanket impl on `ReactiveMap`, so simple impls returning a
88    /// pre-built map continue to work unchanged.
89    #[cfg(not(target_arch = "wasm32"))]
90    fn build_view(
91        _ctx: QueryBuildCellCtx<Self>,
92    ) -> Option<impl MapQuery<Arc<str>, Arc<dyn AnyItem>>>
93    where
94        Self: Send + Sync + 'static,
95    {
96        None::<FilteredCellMap>
97    }
98}
99
100pub struct QueryTestCtx<TQuery: QueryItemType> {
101    pub item: Arc<TQuery::Item>,
102    pub query: Arc<TQuery>,
103    pub query_context: Arc<QueryContext>,
104}
105
106impl<TQuery: QueryItemType> QueryTestCtx<TQuery> {
107    pub fn map_bool<F>(self, predicate: F) -> bool
108    where
109        F: Fn(QueryTestCtx<TQuery>) -> bool,
110    {
111        predicate(self)
112    }
113}
114
115#[cfg(not(target_arch = "wasm32"))]
116pub struct QueryBuildCellCtx<TQuery: QueryItemType> {
117    pub query: Arc<TQuery>,
118    pub query_context: QueryCellContext,
119}
120
121#[derive(Debug)]
122#[allow(dead_code)]
123pub struct QueryHandlerCtxAny {
124    pub item: Arc<dyn AnyItem>,
125    pub query: Arc<dyn AnyQuery>,
126    pub ctx: Arc<QueryContext>,
127}
128
129// ─────────────────────────────────────────────────────────────────────────────
130// QueryParams - Marker trait for query parameter structs (inner type)
131// ─────────────────────────────────────────────────────────────────────────────
132
133/// Marker trait for query parameter structs.
134///
135/// This is implemented by the user-defined query struct (e.g., `GetServersByIds`).
136/// It combines identity traits without requiring transaction metadata.
137///
138/// The full `Query` trait is implemented on `QueryRequest<Q>` where `Q: QueryParams`.
139pub trait QueryParams:
140    CacheKey
141    + Serialize
142    + DeserializeOwned
143    + Clone
144    + Send
145    + Sync
146    + QueryId
147    + QueryIdStatic
148    + QueryItemType
149    + QueryHandler
150    + std::fmt::Debug
151    + 'static
152{
153}
154
155// Blanket impl for any type that satisfies the bounds
156impl<T> QueryParams for T where
157    T: Serialize
158        + CacheKey
159        + DeserializeOwned
160        + Clone
161        + Send
162        + Sync
163        + QueryId
164        + QueryIdStatic
165        + QueryItemType
166        + QueryHandler
167        + std::fmt::Debug
168        + 'static
169{
170}
171
172// ─────────────────────────────────────────────────────────────────────────────
173// Query - Full trait implemented on QueryRequest<Q>
174// ─────────────────────────────────────────────────────────────────────────────
175
176/// Full query trait implemented on `QueryRequest<Q>`.
177///
178/// This provides the `watch` method for client-side subscriptions.
179/// For server-side registration, use `Q::register()` on the params type.
180pub trait Query:
181    Serialize
182    + DeserializeOwned
183    + Send
184    + Sync
185    + QueryId
186    + QueryIdStatic
187    + QueryItemType
188    + QueryHandler
189    + WithTransaction
190    + AnyQuery
191    + 'static
192{
193    /// The inner query params type
194    type Params: QueryParams;
195
196    fn watch(
197        &self,
198        client: &MykoClient,
199    ) -> hyphae::Cell<Vec<Arc<<Self as QueryItemType>::Item>>, CellImmutable>;
200}
201
202// Blanket impl of Query for QueryRequest<Q>
203impl<Q: QueryParams + Clone> Query for QueryRequest<Q>
204where
205    Q::Item:
206        Eventable + WithId + DeserializeOwned + Clone + std::fmt::Debug + Send + Sync + 'static,
207{
208    type Params = Q;
209
210    fn watch(
211        &self,
212        client: &MykoClient,
213    ) -> hyphae::Cell<Vec<Arc<<Self as QueryItemType>::Item>>, CellImmutable> {
214        client.watch_query::<Q>(self)
215    }
216}
217
218// ─────────────────────────────────────────────────────────────────────────────
219// AnyQuery - Type-erased query trait
220// ─────────────────────────────────────────────────────────────────────────────
221
222/// Type-erased query trait for dynamic dispatch.
223/// All queries implement this via the `#[myko_query]` macro.
224pub trait AnyQuery: WithTransaction + QueryId + Debug + Send + Sync + 'static {
225    /// Returns the item type this query targets (e.g., "Server", "Client").
226    fn query_item_type(&self) -> Arc<str>;
227
228    /// Serialize this query to a JSON Value.
229    fn to_value(&self) -> Value;
230}
231
232// Conversion from Arc<dyn AnyQuery> to WrappedQuery
233impl From<&dyn AnyQuery> for WrappedQuery {
234    fn from(query: &dyn AnyQuery) -> Self {
235        WrappedQuery {
236            query: query.to_value(),
237            query_id: query.query_id(),
238            query_item_type: query.query_item_type(),
239            window: None,
240        }
241    }
242}
243
244impl From<Arc<dyn AnyQuery>> for WrappedQuery {
245    fn from(query: Arc<dyn AnyQuery>) -> Self {
246        WrappedQuery::from(query.as_ref())
247    }
248}
249
250impl From<&Arc<dyn AnyQuery>> for WrappedQuery {
251    fn from(query: &Arc<dyn AnyQuery>) -> Self {
252        WrappedQuery::from(query.as_ref())
253    }
254}