1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
use dioxus_core::*;
use dioxus_hooks::*;
use futures_util::stream::StreamExt;
use slab::Slab;
use std::{fmt::Debug, future::Future, rc::Rc, sync::Arc};

pub fn use_client<K: Clone + 'static>(cx: &ScopeState) -> &UseQueryClient<K> {
    use_context(cx).unwrap()
}

pub fn use_provide_client<K: Clone + 'static>(cx: &ScopeState) -> &UseQueryClient<K> {
    use_context_provider(cx, || UseQueryClient {
        queries_keys: Rc::default(),
    })
}

pub type QueryFn<R, K> = dyn Fn(&[K]) -> R + 'static;

#[derive(Clone, Copy)]
enum QueryMutationMode {
    /// An effective mutation will make listening components re run
    Effective,
    /// A silent mutation will not make listening components re run
    Silent,
}

#[derive(Clone)]
pub struct UseQueryClient<K> {
    queries_keys: Rc<RefCell<Slab<Subscriber<K>>>>,
}

impl<K: PartialEq> UseQueryClient<K> {
    fn invalidate_query_with_mode(&self, keys_to_invalidate: &[K], mode: QueryMutationMode) {
        for (_, sub) in self.queries_keys.borrow().iter() {
            if sub.keys.iter().any(|k| keys_to_invalidate.contains(k)) {
                sub.coroutine.send(mode);
            }
        }
    }

    pub fn invalidate_query(&self, key_to_invalidate: K) {
        self.invalidate_query_with_mode(&[key_to_invalidate], QueryMutationMode::Effective);
    }

    pub fn invalidate_queries(&self, keys_to_invalidate: &[K]) {
        self.invalidate_query_with_mode(keys_to_invalidate, QueryMutationMode::Effective);
    }

    pub fn silent_invalidate_query(&self, key_to_invalidate: K) {
        self.invalidate_query_with_mode(&[key_to_invalidate], QueryMutationMode::Silent);
    }

    pub fn silent_invalidate_queries(&self, keys_to_invalidate: &[K]) {
        self.invalidate_query_with_mode(keys_to_invalidate, QueryMutationMode::Silent);
    }
}

pub struct UseValue<T, E, K> {
    client: UseQueryClient<K>,
    slot: UseRef<QueryResult<T, E>>,
    subscriber_id: usize,
}

impl<T, E, K> Drop for UseValue<T, E, K> {
    fn drop(&mut self) {
        self.client
            .queries_keys
            .borrow_mut()
            .remove(self.subscriber_id);
    }
}

impl<T: Clone, E: Clone, K: Clone> UseValue<T, E, K> {
    /// Get the current result from the query.
    pub fn result(&self) -> core::cell::Ref<QueryResult<T, E>> {
        self.slot.read()
    }
}

#[derive(Clone, PartialEq, Debug)]
pub enum QueryResult<T, E> {
    /// Contains a successful state
    Ok(T),
    /// Contains an errored state
    Err(E),
    /// Contains an empty state
    None,
    /// Contains a loading state that may or not have a cached result
    Loading(Option<T>),
}

impl<T, E> From<QueryResult<T, E>> for Option<T> {
    fn from(value: QueryResult<T, E>) -> Self {
        match value {
            QueryResult::Ok(v) => Some(v),
            QueryResult::Err(_) => None,
            QueryResult::None => None,
            QueryResult::Loading(v) => v,
        }
    }
}

impl<T, E> From<Result<T, E>> for QueryResult<T, E> {
    fn from(value: Result<T, E>) -> Self {
        match value {
            Ok(v) => QueryResult::Ok(v),
            Err(e) => QueryResult::Err(e),
        }
    }
}

struct Subscriber<K> {
    keys: Vec<K>,
    coroutine: Coroutine<QueryMutationMode>,
}

pub struct QueryConfig<R, T, E, K> {
    keys: Vec<K>,
    query_fn: Box<QueryFn<R, K>>,
    initial_fn: Option<Box<dyn Fn() -> QueryResult<T, E>>>,
}

impl<R, T, E, K> QueryConfig<R, T, E, K> {
    pub fn new(keys: Vec<K>, query_fn: impl Fn(&[K]) -> R + 'static) -> Self {
        Self {
            keys,
            query_fn: Box::new(query_fn),
            initial_fn: None,
        }
    }

    pub fn initial(mut self, initial_data: impl Fn() -> QueryResult<T, E> + 'static) -> Self {
        self.initial_fn = Some(Box::new(initial_data));
        self
    }
}

/// Get a result given the query config, will re run when the query keys are invalidated.
pub fn use_query_config<R, T, E, K>(
    cx: &ScopeState,
    config: impl FnOnce() -> QueryConfig<R, T, E, K>,
) -> &UseValue<T, E, K>
where
    T: 'static + PartialEq + Clone,
    E: 'static + PartialEq + Clone,
    R: Future<Output = QueryResult<T, E>> + 'static,
    K: Clone + 'static,
{
    let client = use_client(cx);
    let config = cx.use_hook(|| Arc::new(config()));
    let value = use_ref(cx, || {
        // Set an empty state if no initial function is passed
        config
            .initial_fn
            .as_ref()
            .map(|v| v())
            .unwrap_or(QueryResult::None)
    });

    let coroutine = use_coroutine(cx, {
        to_owned![value, config];
        move |mut rx: UnboundedReceiver<QueryMutationMode>| {
            to_owned![value];
            async move {
                while let Some(mutation) = rx.next().await {
                    // Update to a Loading state
                    let cached_value: Option<T> = value.read().clone().into();
                    match mutation {
                        QueryMutationMode::Effective => {
                            *value.write() = QueryResult::Loading(cached_value);
                        }
                        QueryMutationMode::Silent => {
                            *value.write_silent() = QueryResult::Loading(cached_value);
                        }
                    }

                    // Fetch the result
                    let res = (config.query_fn)(&config.keys).await;

                    // Save the result
                    match mutation {
                        QueryMutationMode::Effective => {
                            *value.write() = res;
                        }
                        QueryMutationMode::Silent => {
                            *value.write_silent() = res;
                        }
                    }
                }
            }
        }
    });

    cx.use_hook(|| {
        let subscriber_id = client.queries_keys.borrow_mut().insert(Subscriber {
            keys: config.keys.clone(),
            coroutine: coroutine.clone(),
        });

        // Initial async load
        coroutine.send(QueryMutationMode::Effective);

        UseValue {
            client: client.clone(),
            slot: value.clone(),
            subscriber_id,
        }
    })
}

/// Get the result of the given query function, will re run when the query keys are invalidated.
pub fn use_query<R, T: Clone, E: Clone, K>(
    cx: &ScopeState,
    query_keys: impl FnOnce() -> Vec<K>,
    query_fn: impl Fn(&[K]) -> R + 'static,
) -> &UseValue<T, E, K>
where
    T: 'static + PartialEq,
    E: 'static + PartialEq,
    R: Future<Output = QueryResult<T, E>> + 'static,
    K: Clone + 'static,
{
    use_query_config(cx, || QueryConfig::new(query_keys(), query_fn))
}