@@ -9,18 +9,24 @@ import Prelude
99import Control.Alt ((<|>))
1010import Data.DateTime.Instant (Instant , unInstant )
1111import Data.Either (Either (..))
12+ import Data.Int (ceil )
1213import Data.Map (Map )
1314import Data.Map as Map
1415import Data.Maybe (Maybe (..))
15- import Data.Time.Duration (Milliseconds )
16+ import Data.Newtype (un )
17+ import Data.Time.Duration (Milliseconds (..))
1618import Effect (Effect )
1719import Effect.Aff (Aff , attempt , launchAff , throwError )
1820import Effect.Class (liftEffect )
21+ import Effect.Console (warn )
22+ import Effect.Exception (try )
1923import Effect.Now (now )
2024import Effect.Ref (Ref )
2125import Effect.Ref as Ref
2226import React.Basic.Hooks (type (/\), (/\))
2327import React.Basic.Hooks.Suspense (Suspended (..), SuspenseResult (..))
28+ import Web.HTML (window )
29+ import Web.HTML.Window (requestIdleCallback )
2430
2531-- | Simple key-based cache.
2632mkSuspenseStore ::
@@ -32,19 +38,35 @@ mkSuspenseStore ::
3238mkSuspenseStore defaultMaxAge backend = do
3339 ref <- Ref .new mempty
3440 let
41+ isExpired maxAge now' (_ /\ d) = unInstant now' < unInstant d <> maxAge
42+
43+ pruneCache = do
44+ case defaultMaxAge of
45+ Nothing -> pure unit
46+ Just maxAge -> do
47+ now' <- now
48+ void $ Ref .modify (Map .filter (not isExpired maxAge now')) ref
49+ void
50+ $ window
51+ >>= requestIdleCallback
52+ { timeout: ceil $ un Milliseconds maxAge
53+ }
54+ pruneCache
55+
3556 tryFromCache itemMaxAge k = do
3657 rMaybe <- Map .lookup k <$> Ref .read ref
3758 case rMaybe of
3859 Nothing -> pure Nothing
39- Just (r /\ d) -> do
60+ Just v@ (r /\ d) -> do
4061 case itemMaxAge <|> defaultMaxAge of
4162 Nothing -> pure (Just r)
4263 Just maxAge -> do
4364 now' <- now
44- if unInstant now' < unInstant d <> maxAge then
45- pure (Just r)
46- else
65+ if isExpired maxAge now' v then do
66+ _ <- Ref .modify (Map .delete k) ref
4767 pure Nothing
68+ else
69+ pure (Just r)
4870
4971 getCacheOrBackend itemMaxAge k = do
5072 c <- tryFromCache itemMaxAge k
@@ -80,22 +102,25 @@ mkSuspenseStore defaultMaxAge backend = do
80102 d <- now
81103 _ <- ref # Ref .modify (Map .insert k (v /\ d))
82104 pure v
105+ do
106+ r <- try pruneCache
107+ case r of
108+ Left _ -> warn " Failed to initialize the suspense store cleanup task. Ensure you're using it in a browser with `requestIdleCallback` support."
109+ Right _ -> pure unit
83110 pure
84111 $ SuspenseStore
85112 { cache: ref
86- , get: \k -> Suspended do getCacheOrBackend Nothing k
87- , get': \d k -> Suspended do getCacheOrBackend (Just d) k
113+ , get: map Suspended <<< getCacheOrBackend
88114 }
89115
90116newtype SuspenseStore k v
91117 = SuspenseStore
92118 { cache :: Ref (Map k (SuspenseResult v /\ Instant ))
93- , get :: k -> Suspended v
94- , get' :: Milliseconds -> k -> Suspended v
119+ , get :: Maybe Milliseconds -> k -> Suspended v
95120 }
96121
97122get :: forall k v . SuspenseStore k v -> k -> Suspended v
98- get (SuspenseStore s) = s.get
123+ get (SuspenseStore s) = s.get Nothing
99124
100125get' :: forall k v . SuspenseStore k v -> Milliseconds -> k -> Suspended v
101- get' (SuspenseStore s) = s.get'
126+ get' (SuspenseStore s) d = s.get ( Just d)
0 commit comments