use super::{
EPOCH, STALE, config, invalidate_cached_key, now_ms, persist, remember, remembered, restore,
};
use crate::{CacheKey, CacheableFn};
use dioxus::prelude::{
CopyValue, ReadableExt, ReadableRef, Result, Signal, WritableExt, use_hook, use_resource,
use_signal,
};
use serde::{Serialize, de::DeserializeOwned};
use std::{future::Future, pin::Pin, rc::Rc};
#[derive(Clone, Copy, Debug)]
pub struct CacheOptions {
persist: bool,
}
impl Default for CacheOptions {
fn default() -> Self {
Self { persist: true }
}
}
impl CacheOptions {
pub fn memory_only(mut self) -> Self {
self.persist = false;
self
}
}
type Fetch<T> = Rc<dyn Fn() -> Pin<Box<dyn Future<Output = Result<T>>>>>;
pub struct Cached<T: 'static> {
data: Signal<Option<Result<T>>>,
key: Signal<CacheKey>,
}
impl<T> Clone for Cached<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> Copy for Cached<T> {}
impl<T: 'static> Cached<T> {
pub fn read(&self) -> ReadableRef<'_, Signal<Option<Result<T>>>> {
self.data.read()
}
pub fn refresh(&self) {
invalidate_cached_key(&self.key.peek());
}
}
pub fn use_cached<F, Args>(server_fn: F, args: Args) -> Cached<F::Output>
where
F: CacheableFn<Args>,
Args: Serialize + Clone + 'static,
F::Output: Serialize + DeserializeOwned + Clone + PartialEq + 'static,
{
use_cached_with(server_fn, args, CacheOptions::default())
}
pub fn use_cached_with<F, Args>(
server_fn: F,
args: Args,
options: CacheOptions,
) -> Cached<F::Output>
where
F: CacheableFn<Args>,
Args: Serialize + Clone + 'static,
F::Output: Serialize + DeserializeOwned + Clone + PartialEq + 'static,
{
let key = CacheKey::of(&server_fn, &args);
use_cached_key_with(key, options, move || server_fn.call_with(args.clone()))
}
pub fn use_cached_key<T, F, Fut>(key: CacheKey, fetch: F) -> Cached<T>
where
T: Serialize + DeserializeOwned + Clone + PartialEq + 'static,
F: Fn() -> Fut + 'static,
Fut: Future<Output = Result<T>> + 'static,
{
use_cached_key_with(key, CacheOptions::default(), fetch)
}
pub fn use_cached_key_with<T, F, Fut>(key: CacheKey, options: CacheOptions, fetch: F) -> Cached<T>
where
T: Serialize + DeserializeOwned + Clone + PartialEq + 'static,
F: Fn() -> Fut + 'static,
Fut: Future<Output = Result<T>> + 'static,
{
let mut data = use_signal(|| remembered::<T>(&key).map(|(value, _)| Ok(value)));
let mut current_key = use_signal(|| key.clone());
if *current_key.peek() != key {
current_key.set(key);
}
let mut latest = use_hook(|| CopyValue::new(None::<Fetch<T>>));
latest.set(Some(Rc::new(move || Box::pin(fetch()))));
let mut loaded = use_hook(|| CopyValue::new(None::<CacheKey>));
use_resource(move || {
let key = current_key();
let _ = EPOCH();
let fetch = latest.peek().clone();
async move {
let Some(fetch) = fetch else { return };
let opening = loaded.peek().as_ref() != Some(&key);
match remembered::<T>(&key) {
Some((value, fetched_at)) => {
show(&mut data, value);
let current = if opening {
let share_for = i64::try_from(config().share_for.as_millis()).unwrap_or(0);
now_ms().saturating_sub(fetched_at) < share_for
} else {
fetched_at != STALE
};
if current {
loaded.set(Some(key));
return;
}
}
None => match options.persist.then(|| restore::<T>(&key)) {
Some(restoring) => match restoring.await {
Some(value) => {
remember(&key, value.clone(), STALE);
show(&mut data, value);
}
None => data.set(None),
},
None => data.set(None),
},
}
let result = fetch().await;
if *current_key.peek() != key {
return;
}
loaded.set(Some(key.clone()));
match result {
Ok(value) => {
remember(&key, value.clone(), now_ms());
show(&mut data, value.clone());
if options.persist {
persist(&key, &value).await;
}
}
Err(err) if data.peek().as_ref().is_none_or(Result::is_err) => {
data.set(Some(Err(err)));
}
Err(_) => {}
}
}
});
Cached {
data,
key: current_key,
}
}
fn show<T: PartialEq + 'static>(data: &mut Signal<Option<Result<T>>>, value: T) {
let unchanged = matches!(&*data.peek(), Some(Ok(current)) if *current == value);
if !unchanged {
data.set(Some(Ok(value)));
}
}