reactive_stores/
lib.rs

1#![forbid(unsafe_code)]
2#![deny(missing_docs)]
3
4//! Stores are a primitive for creating deeply-nested reactive state, based on [`reactive_graph`].
5//!
6//! Reactive signals allow you to define atomic units of reactive state. However, signals are
7//! imperfect as a mechanism for tracking reactive change in structs or collections, because
8//! they do not allow you to track access to individual struct fields or individual items in a
9//! collection, rather than the struct as a whole or the collection as a whole. Reactivity for
10//! individual fields can be achieved by creating a struct of signals, but this has issues; it
11//! means that a struct is no longer a plain data structure, but requires wrappers on each field.
12//!
13//! Stores attempt to solve this problem by allowing arbitrarily-deep access to the fields of some
14//! data structure, while still maintaining fine-grained reactivity.
15//!
16//! The [`Store`](macro@Store) macro adds getters and setters for the fields of a struct. Call those getters or
17//! setters on a reactive [`Store`](struct@Store) or [`ArcStore`], or to a subfield, gives you
18//! access to a reactive subfield. This value of this field can be accessed via the ordinary signal
19//! traits (`Get`, `Set`, and so on).
20//!
21//! The [`Patch`](macro@Patch) macro allows you to annotate a struct such that stores and fields have a
22//! [`.patch()`](Patch::patch) method, which allows you to provide an entirely new value, but only
23//! notify fields that have changed.
24//!
25//! Updating a field will notify its parents and children, but not its siblings.
26//!
27//! Stores can therefore
28//! 1) work with plain Rust data types, and
29//! 2) provide reactive access to individual fields
30//!
31//! ### Example
32//!
33//! ```rust
34//! use reactive_graph::{
35//!     effect::Effect,
36//!     traits::{Read, Write},
37//! };
38//! use reactive_stores::{Patch, Store};
39//!
40//! #[derive(Debug, Store, Patch, Default)]
41//! struct Todos {
42//!     user: String,
43//!     todos: Vec<Todo>,
44//! }
45//!
46//! #[derive(Debug, Store, Patch, Default)]
47//! struct Todo {
48//!     label: String,
49//!     completed: bool,
50//! }
51//!
52//! let store = Store::new(Todos {
53//!     user: "Alice".to_string(),
54//!     todos: Vec::new(),
55//! });
56//!
57//! # if false { // don't run effect in doctests
58//! Effect::new(move |_| {
59//!     // you can access individual store fields with a getter
60//!     println!("todos: {:?}", &*store.todos().read());
61//! });
62//! # }
63//!
64//! // won't notify the effect that listens to `todos`
65//! store.todos().write().push(Todo {
66//!     label: "Test".to_string(),
67//!     completed: false,
68//! });
69//! ```
70//! ### Generated traits
71//! The [`Store`](macro@Store) macro generates traits for each `struct` to which it is applied.  When working
72//! within a single file or module, this is not an issue.  However, when working with multiple modules
73//! or files, one needs to `use` the generated traits.  The general pattern is that for each `struct`
74//! named `Foo`, the macro generates a trait named `FooStoreFields`.  For example:
75//! ```rust
76//! pub mod foo {
77//!   use reactive_stores::Store;
78
79//!   #[derive(Store)]
80//!   pub struct Foo {
81//!     field: i32,
82//!   }
83//! }
84//!
85//! pub mod user {
86//!   use leptos::prelude::*;
87//!   use reactive_stores::Field;
88//!   // Using FooStore fields here.
89//!   use crate::foo::{ Foo, FooStoreFields };
90//!
91//!   #[component]
92//!   pub fn UseFoo(foo: Field<Foo>) {
93//!     // Without FooStoreFields, foo.field() would fail to compile.
94//!     println!("field: {}", foo.field().read());
95//!   }
96//! }
97//!
98//! # fn main() {
99//! # }
100//! ```
101//! ### Additional field types
102//!
103//! Most of the time, your structs will have fields as in the example above: the struct is comprised
104//! of primitive types, builtin types like [String], or other structs that implement [Store](struct@Store) or [Field].
105//! However, there are some special cases that require some additional understanding.
106//!
107//! #### Option
108//! [`Option<T>`](std::option::Option) behaves pretty much as you would expect, utilizing [.is_some()](std::option::Option::is_some)
109//! and [.is_none()](std::option::Option::is_none) to check the value and  [.unwrap()](OptionStoreExt::unwrap) method to access the inner value.  The [OptionStoreExt]
110//! trait is required to use the [.unwrap()](OptionStoreExt::unwrap) method.  Here is a quick example:
111//! ```rust
112//! // Including the trait OptionStoreExt here is required to use unwrap()
113//! use reactive_stores::{OptionStoreExt, Store};
114//! use reactive_graph::traits::{Get, Read};
115//!
116//! #[derive(Store)]
117//! struct StructWithOption {
118//!     opt_field: Option<i32>,
119//! }
120//!
121//! fn describe(store: &Store<StructWithOption>) -> String {
122//!     if store.opt_field().read().is_some() {
123//!         // Note here we need to use OptionStoreExt or unwrap() would not compile
124//!         format!("store has a value {}", store.opt_field().unwrap().get())
125//!     } else {
126//!         format!("store has no value")
127//!     }
128//! }
129//! let none_store = Store::new(StructWithOption { opt_field: None });
130//! let some_store = Store::new(StructWithOption { opt_field: Some(42)});
131//!
132//! assert_eq!(describe(&none_store), "store has no value");
133//! assert_eq!(describe(&some_store), "store has a value 42");
134//! ```
135//! #### Vec
136//! [`Vec<T>`](std::vec::Vec) requires some special treatment when trying to access
137//! elements of the vector directly.  Use the [StoreFieldIterator::at_unkeyed()] method to
138//! access a particular value in a [struct@Store] or [Field] for a [std::vec::Vec].  For example:
139//! ```rust
140//! # use reactive_stores::Store;
141//! // Needed to use at_unkeyed() on Vec
142//! use reactive_stores::StoreFieldIter;
143//! use reactive_stores::StoreFieldIterator;
144//! use reactive_graph::traits::Read;
145//! use reactive_graph::traits::Get;
146//!
147//! #[derive(Store)]
148//! struct StructWithVec {
149//!     vec_field: Vec<i32>,
150//! }
151//!
152//! let store = Store::new(StructWithVec { vec_field: vec![1, 2, 3] });
153//!
154//! assert_eq!(store.vec_field().at_unkeyed(0).get(), 1);
155//! assert_eq!(store.vec_field().at_unkeyed(1).get(), 2);
156//! assert_eq!(store.vec_field().at_unkeyed(2).get(), 3);
157//! ```
158//! #### Enum
159//! Enumerated types behave a bit differently as the [`Store`](macro@Store) macro builds underlying traits instead of alternate
160//! enumerated structures.  Each element in an `Enum` generates methods to access it in the store: a
161//! method with the name of the field gives a boolean if the `Enum` is that variant, and possible accessor
162//! methods for anonymous fields of that variant.  For example:
163//! ```rust
164//! use reactive_stores::Store;
165//! use reactive_graph::traits::{Read, Get};
166//!
167//! #[derive(Store)]
168//! enum Choices {
169//!    First,
170//!    Second(String),
171//! }
172//!
173//! let choice_one = Store::new(Choices::First);
174//! let choice_two = Store::new(Choices::Second("hello".to_string()));
175//!
176//! assert!(choice_one.first());
177//! assert!(!choice_one.second());
178//! // Note the use of the accessor method here .second_0()
179//! assert_eq!(choice_two.second_0().unwrap().get(), "hello");
180//! ```
181//! #### Box
182//! [`Box<T>`](std::boxed::Box) also requires some special treatment in how you dereference elements of the Box, especially
183//! when trying to build a recursive data structure.  [DerefField](trait@DerefField) provides a [.deref_value()](DerefField::deref_field) method to access
184//! the inner value.  For example:
185//! ```rust
186//! // Note here we need to use DerefField to use deref_field() and OptionStoreExt to use unwrap()
187//! use reactive_stores::{Store, DerefField, OptionStoreExt};
188//! use reactive_graph::traits::{ Read, Get };
189//!
190//! #[derive(Store)]
191//! struct List {
192//!     value: i32,
193//!     #[store]
194//!     child: Option<Box<List>>,
195//! }
196//!
197//! let tree = Store::new(List {
198//!     value: 1,
199//!     child: Some(Box::new(List { value: 2, child: None })),
200//! });
201//!
202//! assert_eq!(tree.child().unwrap().deref_field().value().get(), 2);
203//! ```
204//! ### Implementation Notes
205//!
206//! Every struct field can be understood as an index. For example, given the following definition
207//! ```rust
208//! # use reactive_stores::{Store, Patch};
209//! #[derive(Debug, Store, Patch, Default)]
210//! struct Name {
211//!     first: String,
212//!     last: String,
213//! }
214//! ```
215//! We can think of `first` as `0` and `last` as `1`. This means that any deeply-nested field of a
216//! struct can be described as a path of indices. So, for example:
217//! ```rust
218//! # use reactive_stores::{Store, Patch};
219//! #[derive(Debug, Store, Patch, Default)]
220//! struct User {
221//!     user: Name,
222//! }
223//!
224//! #[derive(Debug, Store, Patch, Default)]
225//! struct Name {
226//!     first: String,
227//!     last: String,
228//! }
229//! ```
230//! Here, given a `User`, `first` can be understood as [`0`, `0`] and `last` is [`0`, `1`].
231//!
232//! This means we can implement a store as the combination of two things:
233//! 1) An `Arc<RwLock<T>>` that holds the actual value
234//! 2) A map from field paths to reactive "triggers," which are signals that have no value but
235//!    track reactivity
236//!
237//! Accessing a field via its getters returns an iterator-like data structure that describes how to
238//! get to that subfield. Calling `.read()` returns a guard that dereferences to the value of that
239//! field in the signal inner `Arc<RwLock<_>>`, and tracks the trigger that corresponds with its
240//! path; calling `.write()` returns a writeable guard, and notifies that same trigger.
241
242use reactive_graph::{
243    owner::{ArenaItem, LocalStorage, Storage, SyncStorage},
244    signal::{
245        guards::{Plain, ReadGuard, WriteGuard},
246        ArcTrigger,
247    },
248    traits::{
249        DefinedAt, Dispose, IsDisposed, Notify, ReadUntracked, Track,
250        UntrackableGuard, Write,
251    },
252};
253pub use reactive_stores_macro::{Patch, Store};
254use rustc_hash::FxHashMap;
255use std::{
256    any::Any,
257    fmt::Debug,
258    hash::Hash,
259    ops::DerefMut,
260    panic::Location,
261    sync::{Arc, RwLock},
262};
263
264mod arc_field;
265mod deref;
266mod field;
267mod iter;
268mod keyed;
269mod len;
270mod option;
271mod patch;
272mod path;
273mod store_field;
274mod subfield;
275
276pub use arc_field::ArcField;
277pub use deref::*;
278pub use field::Field;
279pub use iter::*;
280pub use keyed::*;
281pub use len::Len;
282pub use option::*;
283pub use patch::*;
284pub use path::{StorePath, StorePathSegment};
285pub use store_field::StoreField;
286pub use subfield::Subfield;
287
288#[derive(Debug, Default)]
289struct TriggerMap(FxHashMap<StorePath, StoreFieldTrigger>);
290
291/// The reactive trigger that can be used to track updates to a store field.
292#[derive(Debug, Clone, Default)]
293pub struct StoreFieldTrigger {
294    pub(crate) this: ArcTrigger,
295    pub(crate) children: ArcTrigger,
296}
297
298impl StoreFieldTrigger {
299    /// Creates a new trigger.
300    pub fn new() -> Self {
301        Self::default()
302    }
303}
304
305impl TriggerMap {
306    fn get_or_insert(&mut self, key: StorePath) -> StoreFieldTrigger {
307        if let Some(trigger) = self.0.get(&key) {
308            trigger.clone()
309        } else {
310            let new = StoreFieldTrigger::new();
311            self.0.insert(key, new.clone());
312            new
313        }
314    }
315
316    #[allow(unused)]
317    fn remove(&mut self, key: &StorePath) -> Option<StoreFieldTrigger> {
318        self.0.remove(key)
319    }
320}
321
322/// Manages the keys for a keyed field, including the ability to remove and reuse keys.
323pub(crate) struct FieldKeys<K> {
324    spare_keys: Vec<StorePathSegment>,
325    current_key: usize,
326    keys: FxHashMap<K, (StorePathSegment, usize)>,
327}
328
329impl<K> FieldKeys<K>
330where
331    K: Debug + Hash + PartialEq + Eq,
332{
333    /// Creates a new set of keys.
334    pub fn new(from_keys: Vec<K>) -> Self {
335        let mut keys = FxHashMap::with_capacity_and_hasher(
336            from_keys.len(),
337            Default::default(),
338        );
339        for (idx, key) in from_keys.into_iter().enumerate() {
340            let segment = idx.into();
341            keys.insert(key, (segment, idx));
342        }
343
344        Self {
345            spare_keys: Vec::new(),
346            current_key: keys.len().saturating_sub(1),
347            keys,
348        }
349    }
350}
351
352impl<K> FieldKeys<K>
353where
354    K: Hash + PartialEq + Eq,
355{
356    fn get(&self, key: &K) -> Option<(StorePathSegment, usize)> {
357        self.keys.get(key).copied()
358    }
359
360    fn next_key(&mut self) -> StorePathSegment {
361        self.spare_keys.pop().unwrap_or_else(|| {
362            self.current_key += 1;
363            self.current_key.into()
364        })
365    }
366
367    fn update(&mut self, iter: impl IntoIterator<Item = K>) {
368        let new_keys = iter
369            .into_iter()
370            .enumerate()
371            .map(|(idx, key)| (key, idx))
372            .collect::<FxHashMap<K, usize>>();
373
374        // remove old keys and recycle the slots
375        self.keys.retain(|key, old_entry| match new_keys.get(key) {
376            Some(idx) => {
377                old_entry.1 = *idx;
378                true
379            }
380            None => {
381                self.spare_keys.push(old_entry.0);
382                false
383            }
384        });
385
386        // add new keys
387        for (key, idx) in new_keys {
388            // the suggestion doesn't compile because we need &mut for self.next_key(),
389            // and we don't want to call that until after the check
390            #[allow(clippy::map_entry)]
391            if !self.keys.contains_key(&key) {
392                let path = self.next_key();
393                self.keys.insert(key, (path, idx));
394            }
395        }
396    }
397}
398
399impl<K> Default for FieldKeys<K> {
400    fn default() -> Self {
401        Self {
402            spare_keys: Default::default(),
403            current_key: Default::default(),
404            keys: Default::default(),
405        }
406    }
407}
408
409#[cfg(not(target_arch = "wasm32"))]
410type HashMap<K, V> = Arc<dashmap::DashMap<K, V>>;
411#[cfg(target_arch = "wasm32")]
412type HashMap<K, V> = send_wrapper::SendWrapper<
413    std::rc::Rc<std::cell::RefCell<std::collections::HashMap<K, V>>>,
414>;
415
416/// A map of the keys for a keyed subfield.
417#[derive(Clone)]
418pub struct KeyMap(HashMap<StorePath, Box<dyn Any + Send + Sync>>);
419
420impl Default for KeyMap {
421    fn default() -> Self {
422        #[cfg(not(target_arch = "wasm32"))]
423        return Self(Default::default());
424        #[cfg(target_arch = "wasm32")]
425        return Self(send_wrapper::SendWrapper::new(Default::default()));
426    }
427}
428
429impl KeyMap {
430    fn with_field_keys<K, T>(
431        &self,
432        path: StorePath,
433        fun: impl FnOnce(&mut FieldKeys<K>) -> T,
434        initialize: impl FnOnce() -> Vec<K>,
435    ) -> Option<T>
436    where
437        K: Debug + Hash + PartialEq + Eq + Send + Sync + 'static,
438    {
439        #[cfg(not(target_arch = "wasm32"))]
440        let mut entry = self
441            .0
442            .entry(path)
443            .or_insert_with(|| Box::new(FieldKeys::new(initialize())));
444
445        #[cfg(target_arch = "wasm32")]
446        let entry = if !self.0.borrow().contains_key(&path) {
447            Some(Box::new(FieldKeys::new(initialize())))
448        } else {
449            None
450        };
451        #[cfg(target_arch = "wasm32")]
452        let mut map = self.0.borrow_mut();
453        #[cfg(target_arch = "wasm32")]
454        let entry = map.entry(path).or_insert_with(|| entry.unwrap());
455
456        let entry = entry.downcast_mut::<FieldKeys<K>>()?;
457        Some(fun(entry))
458    }
459}
460
461/// A reference-counted container for a reactive store.
462///
463/// The type `T` should be a struct that has been annotated with `#[derive(Store)]`.
464///
465/// This adds a getter method for each field to `Store<T>`, which allow accessing reactive versions
466/// of each individual field of the struct.
467pub struct ArcStore<T> {
468    #[cfg(any(debug_assertions, leptos_debuginfo))]
469    defined_at: &'static Location<'static>,
470    pub(crate) value: Arc<RwLock<T>>,
471    signals: Arc<RwLock<TriggerMap>>,
472    keys: KeyMap,
473}
474
475impl<T> ArcStore<T> {
476    /// Creates a new store from the initial value.
477    pub fn new(value: T) -> Self {
478        Self {
479            #[cfg(any(debug_assertions, leptos_debuginfo))]
480            defined_at: Location::caller(),
481            value: Arc::new(RwLock::new(value)),
482            signals: Default::default(),
483            keys: Default::default(),
484        }
485    }
486}
487
488impl<T: Default> Default for ArcStore<T> {
489    fn default() -> Self {
490        Self::new(T::default())
491    }
492}
493
494impl<T: Debug> Debug for ArcStore<T> {
495    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
496        let mut f = f.debug_struct("ArcStore");
497        #[cfg(any(debug_assertions, leptos_debuginfo))]
498        let f = f.field("defined_at", &self.defined_at);
499        f.field("value", &self.value)
500            .field("signals", &self.signals)
501            .finish()
502    }
503}
504
505impl<T> Clone for ArcStore<T> {
506    fn clone(&self) -> Self {
507        Self {
508            #[cfg(any(debug_assertions, leptos_debuginfo))]
509            defined_at: self.defined_at,
510            value: Arc::clone(&self.value),
511            signals: Arc::clone(&self.signals),
512            keys: self.keys.clone(),
513        }
514    }
515}
516
517impl<T> DefinedAt for ArcStore<T> {
518    fn defined_at(&self) -> Option<&'static Location<'static>> {
519        #[cfg(any(debug_assertions, leptos_debuginfo))]
520        {
521            Some(self.defined_at)
522        }
523        #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
524        {
525            None
526        }
527    }
528}
529
530impl<T> IsDisposed for ArcStore<T> {
531    #[inline(always)]
532    fn is_disposed(&self) -> bool {
533        false
534    }
535}
536
537impl<T> ReadUntracked for ArcStore<T>
538where
539    T: 'static,
540{
541    type Value = ReadGuard<T, Plain<T>>;
542
543    fn try_read_untracked(&self) -> Option<Self::Value> {
544        Plain::try_new(Arc::clone(&self.value)).map(ReadGuard::new)
545    }
546}
547
548impl<T> Write for ArcStore<T>
549where
550    T: 'static,
551{
552    type Value = T;
553
554    fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
555        self.writer()
556            .map(|writer| WriteGuard::new(self.clone(), writer))
557    }
558
559    fn try_write_untracked(
560        &self,
561    ) -> Option<impl DerefMut<Target = Self::Value>> {
562        let mut writer = self.writer()?;
563        writer.untrack();
564        Some(writer)
565    }
566}
567
568impl<T: 'static> Track for ArcStore<T> {
569    fn track(&self) {
570        self.track_field();
571    }
572}
573
574impl<T: 'static> Notify for ArcStore<T> {
575    fn notify(&self) {
576        let trigger = self.get_trigger(self.path().into_iter().collect());
577        trigger.this.notify();
578        trigger.children.notify();
579    }
580}
581
582/// An arena-allocated container for a reactive store.
583///
584/// The type `T` should be a struct that has been annotated with `#[derive(Store)]`.
585///
586/// This adds a getter method for each field to `Store<T>`, which allow accessing reactive versions
587/// of each individual field of the struct.
588///
589/// This follows the same ownership rules as arena-allocated types like
590/// [`RwSignal`](reactive_graph::signal::RwSignal).
591pub struct Store<T, S = SyncStorage> {
592    #[cfg(any(debug_assertions, leptos_debuginfo))]
593    defined_at: &'static Location<'static>,
594    inner: ArenaItem<ArcStore<T>, S>,
595}
596
597impl<T> Store<T>
598where
599    T: Send + Sync + 'static,
600{
601    /// Creates a new store with the initial value.
602    pub fn new(value: T) -> Self {
603        Self {
604            #[cfg(any(debug_assertions, leptos_debuginfo))]
605            defined_at: Location::caller(),
606            inner: ArenaItem::new_with_storage(ArcStore::new(value)),
607        }
608    }
609}
610
611impl<T, S> PartialEq for Store<T, S> {
612    fn eq(&self, other: &Self) -> bool {
613        self.inner == other.inner
614    }
615}
616
617impl<T, S> Eq for Store<T, S> {}
618
619impl<T> Store<T, LocalStorage>
620where
621    T: 'static,
622{
623    /// Creates a new store for a type that is `!Send`.
624    ///
625    /// This pins the value to the current thread. Accessing it from any other thread will panic.
626    pub fn new_local(value: T) -> Self {
627        Self {
628            #[cfg(any(debug_assertions, leptos_debuginfo))]
629            defined_at: Location::caller(),
630            inner: ArenaItem::new_with_storage(ArcStore::new(value)),
631        }
632    }
633}
634
635impl<T> Default for Store<T>
636where
637    T: Default + Send + Sync + 'static,
638{
639    fn default() -> Self {
640        Self::new(T::default())
641    }
642}
643
644impl<T> Default for Store<T, LocalStorage>
645where
646    T: Default + 'static,
647{
648    fn default() -> Self {
649        Self::new_local(T::default())
650    }
651}
652
653impl<T: Debug, S> Debug for Store<T, S>
654where
655    S: Debug,
656{
657    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
658        let mut f = f.debug_struct("Store");
659        #[cfg(any(debug_assertions, leptos_debuginfo))]
660        let f = f.field("defined_at", &self.defined_at);
661        f.field("inner", &self.inner).finish()
662    }
663}
664
665impl<T, S> Clone for Store<T, S> {
666    fn clone(&self) -> Self {
667        *self
668    }
669}
670
671impl<T, S> Copy for Store<T, S> {}
672
673impl<T, S> DefinedAt for Store<T, S> {
674    fn defined_at(&self) -> Option<&'static Location<'static>> {
675        #[cfg(any(debug_assertions, leptos_debuginfo))]
676        {
677            Some(self.defined_at)
678        }
679        #[cfg(not(any(debug_assertions, leptos_debuginfo)))]
680        {
681            None
682        }
683    }
684}
685
686impl<T, S> IsDisposed for Store<T, S>
687where
688    T: 'static,
689{
690    #[inline(always)]
691    fn is_disposed(&self) -> bool {
692        self.inner.is_disposed()
693    }
694}
695
696impl<T, S> Dispose for Store<T, S>
697where
698    T: 'static,
699{
700    fn dispose(self) {
701        self.inner.dispose();
702    }
703}
704
705impl<T, S> ReadUntracked for Store<T, S>
706where
707    T: 'static,
708    S: Storage<ArcStore<T>>,
709{
710    type Value = ReadGuard<T, Plain<T>>;
711
712    fn try_read_untracked(&self) -> Option<Self::Value> {
713        self.inner
714            .try_get_value()
715            .and_then(|inner| inner.try_read_untracked())
716    }
717}
718
719impl<T, S> Write for Store<T, S>
720where
721    T: 'static,
722    S: Storage<ArcStore<T>>,
723{
724    type Value = T;
725
726    fn try_write(&self) -> Option<impl UntrackableGuard<Target = Self::Value>> {
727        self.writer().map(|writer| WriteGuard::new(*self, writer))
728    }
729
730    fn try_write_untracked(
731        &self,
732    ) -> Option<impl DerefMut<Target = Self::Value>> {
733        let mut writer = self.writer()?;
734        writer.untrack();
735        Some(writer)
736    }
737}
738
739impl<T, S> Track for Store<T, S>
740where
741    T: 'static,
742    S: Storage<ArcStore<T>>,
743{
744    fn track(&self) {
745        if let Some(inner) = self.inner.try_get_value() {
746            inner.track();
747        }
748    }
749}
750
751impl<T, S> Notify for Store<T, S>
752where
753    T: 'static,
754    S: Storage<ArcStore<T>>,
755{
756    fn notify(&self) {
757        if let Some(inner) = self.inner.try_get_value() {
758            inner.notify();
759        }
760    }
761}
762
763impl<T, S> From<ArcStore<T>> for Store<T, S>
764where
765    T: 'static,
766    S: Storage<ArcStore<T>>,
767{
768    fn from(value: ArcStore<T>) -> Self {
769        Self {
770            #[cfg(any(debug_assertions, leptos_debuginfo))]
771            defined_at: value.defined_at,
772            inner: ArenaItem::new_with_storage(value),
773        }
774    }
775}
776
777#[cfg(test)]
778mod tests {
779    use crate::{self as reactive_stores, Patch, Store, StoreFieldIterator};
780    use reactive_graph::{
781        effect::Effect,
782        owner::StoredValue,
783        traits::{Read, ReadUntracked, Set, Update, Write},
784    };
785    use std::sync::{
786        atomic::{AtomicUsize, Ordering},
787        Arc,
788    };
789
790    pub async fn tick() {
791        tokio::time::sleep(std::time::Duration::from_micros(1)).await;
792    }
793
794    #[derive(Debug, Store, Patch, Default)]
795    struct Todos {
796        user: String,
797        todos: Vec<Todo>,
798    }
799
800    #[derive(Debug, Store, Patch, Default)]
801    struct Todo {
802        label: String,
803        completed: bool,
804    }
805
806    impl Todo {
807        pub fn new(label: impl ToString) -> Self {
808            Self {
809                label: label.to_string(),
810                completed: false,
811            }
812        }
813    }
814
815    fn data() -> Todos {
816        Todos {
817            user: "Bob".to_string(),
818            todos: vec![
819                Todo {
820                    label: "Create reactive store".to_string(),
821                    completed: true,
822                },
823                Todo {
824                    label: "???".to_string(),
825                    completed: false,
826                },
827                Todo {
828                    label: "Profit".to_string(),
829                    completed: false,
830                },
831            ],
832        }
833    }
834
835    #[tokio::test]
836    async fn mutating_field_triggers_effect() {
837        _ = any_spawner::Executor::init_tokio();
838
839        let combined_count = Arc::new(AtomicUsize::new(0));
840
841        let store = Store::new(data());
842        assert_eq!(store.read_untracked().todos.len(), 3);
843        assert_eq!(store.user().read_untracked().as_str(), "Bob");
844        Effect::new_sync({
845            let combined_count = Arc::clone(&combined_count);
846            move |prev: Option<()>| {
847                if prev.is_none() {
848                    println!("first run");
849                } else {
850                    println!("next run");
851                }
852                println!("{:?}", *store.user().read());
853                combined_count.fetch_add(1, Ordering::Relaxed);
854            }
855        });
856        tick().await;
857        tick().await;
858        store.user().set("Greg".into());
859        tick().await;
860        store.user().set("Carol".into());
861        tick().await;
862        store.user().update(|name| name.push_str("!!!"));
863        tick().await;
864        // the effect reads from `user`, so it should trigger every time
865        assert_eq!(combined_count.load(Ordering::Relaxed), 4);
866    }
867
868    #[tokio::test]
869    async fn other_field_does_not_notify() {
870        _ = any_spawner::Executor::init_tokio();
871
872        let combined_count = Arc::new(AtomicUsize::new(0));
873
874        let store = Store::new(data());
875
876        Effect::new_sync({
877            let combined_count = Arc::clone(&combined_count);
878            move |prev: Option<()>| {
879                if prev.is_none() {
880                    println!("first run");
881                } else {
882                    println!("next run");
883                }
884                println!("{:?}", *store.todos().read());
885                combined_count.fetch_add(1, Ordering::Relaxed);
886            }
887        });
888        tick().await;
889        store.user().set("Greg".into());
890        tick().await;
891        store.user().set("Carol".into());
892        tick().await;
893        store.user().update(|name| name.push_str("!!!"));
894        tick().await;
895        // the effect reads from `todos`, so it shouldn't trigger every time
896        assert_eq!(combined_count.load(Ordering::Relaxed), 1);
897    }
898
899    #[tokio::test]
900    async fn parent_does_notify() {
901        _ = any_spawner::Executor::init_tokio();
902
903        let combined_count = Arc::new(AtomicUsize::new(0));
904
905        let store = Store::new(data());
906
907        Effect::new_sync({
908            let combined_count = Arc::clone(&combined_count);
909            move |prev: Option<()>| {
910                if prev.is_none() {
911                    println!("first run");
912                } else {
913                    println!("next run");
914                }
915                println!("{:?}", *store.todos().read());
916                combined_count.fetch_add(1, Ordering::Relaxed);
917            }
918        });
919        tick().await;
920        tick().await;
921        store.set(Todos::default());
922        tick().await;
923        store.set(data());
924        tick().await;
925        assert_eq!(combined_count.load(Ordering::Relaxed), 3);
926    }
927
928    #[tokio::test]
929    async fn changes_do_notify_parent() {
930        _ = any_spawner::Executor::init_tokio();
931
932        let combined_count = Arc::new(AtomicUsize::new(0));
933
934        let store = Store::new(data());
935
936        Effect::new_sync({
937            let combined_count = Arc::clone(&combined_count);
938            move |prev: Option<()>| {
939                if prev.is_none() {
940                    println!("first run");
941                } else {
942                    println!("next run");
943                }
944                println!("{:?}", *store.read());
945                combined_count.fetch_add(1, Ordering::Relaxed);
946            }
947        });
948        tick().await;
949        tick().await;
950        store.user().set("Greg".into());
951        tick().await;
952        store.user().set("Carol".into());
953        tick().await;
954        store.user().update(|name| name.push_str("!!!"));
955        tick().await;
956        store.todos().write().clear();
957        tick().await;
958        assert_eq!(combined_count.load(Ordering::Relaxed), 5);
959    }
960
961    #[tokio::test]
962    async fn iterator_tracks_the_field() {
963        _ = any_spawner::Executor::init_tokio();
964
965        let combined_count = Arc::new(AtomicUsize::new(0));
966
967        let store = Store::new(data());
968
969        Effect::new_sync({
970            let combined_count = Arc::clone(&combined_count);
971            move |prev: Option<()>| {
972                if prev.is_none() {
973                    println!("first run");
974                } else {
975                    println!("next run");
976                }
977                println!(
978                    "{:?}",
979                    store.todos().iter_unkeyed().collect::<Vec<_>>()
980                );
981                combined_count.store(1, Ordering::Relaxed);
982            }
983        });
984
985        tick().await;
986        store
987            .todos()
988            .write()
989            .push(Todo::new("Create reactive store?"));
990        tick().await;
991        store.todos().write().push(Todo::new("???"));
992        tick().await;
993        store.todos().write().push(Todo::new("Profit!"));
994        // the effect only reads from `todos`, so it should trigger only the first time
995        assert_eq!(combined_count.load(Ordering::Relaxed), 1);
996    }
997
998    #[tokio::test]
999    async fn patching_only_notifies_changed_field() {
1000        _ = any_spawner::Executor::init_tokio();
1001
1002        let combined_count = Arc::new(AtomicUsize::new(0));
1003
1004        let store = Store::new(Todos {
1005            user: "Alice".into(),
1006            todos: vec![],
1007        });
1008
1009        Effect::new_sync({
1010            let combined_count = Arc::clone(&combined_count);
1011            move |prev: Option<()>| {
1012                if prev.is_none() {
1013                    println!("first run");
1014                } else {
1015                    println!("next run");
1016                }
1017                println!("{:?}", *store.todos().read());
1018                combined_count.fetch_add(1, Ordering::Relaxed);
1019            }
1020        });
1021        tick().await;
1022        tick().await;
1023        store.patch(Todos {
1024            user: "Bob".into(),
1025            todos: vec![],
1026        });
1027        tick().await;
1028        store.patch(Todos {
1029            user: "Carol".into(),
1030            todos: vec![],
1031        });
1032        tick().await;
1033        assert_eq!(combined_count.load(Ordering::Relaxed), 1);
1034
1035        store.patch(Todos {
1036            user: "Carol".into(),
1037            todos: vec![Todo {
1038                label: "First Todo".into(),
1039                completed: false,
1040            }],
1041        });
1042        tick().await;
1043        assert_eq!(combined_count.load(Ordering::Relaxed), 2);
1044    }
1045
1046    #[tokio::test]
1047    async fn patching_only_notifies_changed_field_with_custom_patch() {
1048        _ = any_spawner::Executor::init_tokio();
1049
1050        #[derive(Debug, Store, Patch, Default)]
1051        struct CustomTodos {
1052            #[patch(|this, new| *this = new)]
1053            user: String,
1054            todos: Vec<CustomTodo>,
1055        }
1056
1057        #[derive(Debug, Store, Patch, Default)]
1058        struct CustomTodo {
1059            label: String,
1060            completed: bool,
1061        }
1062
1063        let combined_count = Arc::new(AtomicUsize::new(0));
1064
1065        let store = Store::new(CustomTodos {
1066            user: "Alice".into(),
1067            todos: vec![],
1068        });
1069
1070        Effect::new_sync({
1071            let combined_count = Arc::clone(&combined_count);
1072            move |prev: Option<()>| {
1073                if prev.is_none() {
1074                    println!("first run");
1075                } else {
1076                    println!("next run");
1077                }
1078                println!("{:?}", *store.user().read());
1079                combined_count.fetch_add(1, Ordering::Relaxed);
1080            }
1081        });
1082        tick().await;
1083        tick().await;
1084        store.patch(CustomTodos {
1085            user: "Bob".into(),
1086            todos: vec![],
1087        });
1088        tick().await;
1089        assert_eq!(combined_count.load(Ordering::Relaxed), 2);
1090        store.patch(CustomTodos {
1091            user: "Carol".into(),
1092            todos: vec![],
1093        });
1094        tick().await;
1095        assert_eq!(combined_count.load(Ordering::Relaxed), 3);
1096
1097        store.patch(CustomTodos {
1098            user: "Carol".into(),
1099            todos: vec![CustomTodo {
1100                label: "First CustomTodo".into(),
1101                completed: false,
1102            }],
1103        });
1104        tick().await;
1105        assert_eq!(combined_count.load(Ordering::Relaxed), 3);
1106    }
1107
1108    // regression test for https://github.com/leptos-rs/leptos/issues/3523
1109    #[tokio::test]
1110    async fn notifying_all_descendants() {
1111        use reactive_graph::traits::*;
1112
1113        _ = any_spawner::Executor::init_tokio();
1114
1115        #[derive(Debug, Clone, Store, Patch, Default)]
1116        struct Foo {
1117            id: i32,
1118            bar: Bar,
1119        }
1120
1121        #[derive(Debug, Clone, Store, Patch, Default)]
1122        struct Bar {
1123            bar_signature: i32,
1124            baz: Baz,
1125        }
1126
1127        #[derive(Debug, Clone, Store, Patch, Default)]
1128        struct Baz {
1129            more_data: i32,
1130            baw: Baw,
1131        }
1132
1133        #[derive(Debug, Clone, Store, Patch, Default)]
1134        struct Baw {
1135            more_data: i32,
1136            end: i32,
1137        }
1138
1139        let store = Store::new(Foo {
1140            id: 42,
1141            bar: Bar {
1142                bar_signature: 69,
1143                baz: Baz {
1144                    more_data: 9999,
1145                    baw: Baw {
1146                        more_data: 22,
1147                        end: 1112,
1148                    },
1149                },
1150            },
1151        });
1152
1153        let store_runs = StoredValue::new(0);
1154        let id_runs = StoredValue::new(0);
1155        let bar_runs = StoredValue::new(0);
1156        let bar_signature_runs = StoredValue::new(0);
1157        let bar_baz_runs = StoredValue::new(0);
1158        let more_data_runs = StoredValue::new(0);
1159        let baz_baw_end_runs = StoredValue::new(0);
1160
1161        Effect::new_sync(move |_| {
1162            println!("foo: {:?}", store.get());
1163            *store_runs.write_value() += 1;
1164        });
1165
1166        Effect::new_sync(move |_| {
1167            println!("foo.id: {:?}", store.id().get());
1168            *id_runs.write_value() += 1;
1169        });
1170
1171        Effect::new_sync(move |_| {
1172            println!("foo.bar: {:?}", store.bar().get());
1173            *bar_runs.write_value() += 1;
1174        });
1175
1176        Effect::new_sync(move |_| {
1177            println!(
1178                "foo.bar.bar_signature: {:?}",
1179                store.bar().bar_signature().get()
1180            );
1181            *bar_signature_runs.write_value() += 1;
1182        });
1183
1184        Effect::new_sync(move |_| {
1185            println!("foo.bar.baz: {:?}", store.bar().baz().get());
1186            *bar_baz_runs.write_value() += 1;
1187        });
1188
1189        Effect::new_sync(move |_| {
1190            println!(
1191                "foo.bar.baz.more_data: {:?}",
1192                store.bar().baz().more_data().get()
1193            );
1194            *more_data_runs.write_value() += 1;
1195        });
1196
1197        Effect::new_sync(move |_| {
1198            println!(
1199                "foo.bar.baz.baw.end: {:?}",
1200                store.bar().baz().baw().end().get()
1201            );
1202            *baz_baw_end_runs.write_value() += 1;
1203        });
1204
1205        println!("[INITIAL EFFECT RUN]");
1206        tick().await;
1207        println!("\n\n[SETTING STORE]");
1208        store.set(Default::default());
1209        tick().await;
1210        println!("\n\n[SETTING STORE.BAR.BAZ]");
1211        store.bar().baz().set(Default::default());
1212        tick().await;
1213
1214        assert_eq!(store_runs.get_value(), 3);
1215        assert_eq!(id_runs.get_value(), 2);
1216        assert_eq!(bar_runs.get_value(), 3);
1217        assert_eq!(bar_signature_runs.get_value(), 2);
1218        assert_eq!(bar_baz_runs.get_value(), 3);
1219        assert_eq!(more_data_runs.get_value(), 3);
1220        assert_eq!(baz_baw_end_runs.get_value(), 3);
1221    }
1222}