-----------------------------------------------------------------------------
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Miso.Storage
-- Copyright   :  (C) 2016-2026 David M. Johnson
-- License     :  BSD3-style (see the file LICENSE)
-- Maintainer  :  David M. Johnson <code@dmj.io>
-- Stability   :  experimental
-- Portability :  non-portable
--
-- = Overview
--
-- "Miso.Storage" wraps the browser's
-- <https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API Web Storage API>,
-- providing access to both
-- <https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage localStorage>
-- and
-- <https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage sessionStorage>
-- through symmetric 'IO' functions.
--
-- Both stores map 'Miso.String.MisoString' keys to 'Miso.String.MisoString'
-- values (the Web Storage API only persists strings). Reads return
-- @'Maybe' 'Miso.String.MisoString'@ — 'Nothing' when the key is absent.
--
-- * __localStorage__ — persists across browser sessions until explicitly
--   cleared.
-- * __sessionStorage__ — persists only for the lifetime of the current
--   browser tab\/session; cleared automatically when the tab is closed.
--
-- = Quick start
--
-- @
-- import "Miso.Storage"
-- import "Miso.String" ('Miso.String.ms')
--
-- -- Persist a preference
-- saveTheme :: 'Miso.String.MisoString' -> IO ()
-- saveTheme theme = 'setLocalStorage' \"theme\" theme
--
-- -- Restore it on startup
-- loadTheme :: IO ('Maybe' 'Miso.String.MisoString')
-- loadTheme = 'getLocalStorage' \"theme\"
--
-- -- Session-scoped token (cleared when tab closes)
-- saveToken :: 'Miso.String.MisoString' -> IO ()
-- saveToken tok = 'setSessionStorage' \"auth_token\" tok
-- @
--
-- = API groups
--
-- * __localStorage__: 'getLocalStorage', 'setLocalStorage',
--   'removeLocalStorage', 'clearLocalStorage', 'localStorageLength'
-- * __sessionStorage__: 'getSessionStorage', 'setSessionStorage',
--   'removeSessionStorage', 'clearSessionStorage', 'sessionStorageLength'
--
-- = See also
--
-- * "Miso.Effect" — schedule storage reads\/writes with 'Miso.Effect.io' \/ 'Miso.Effect.io_'
-- * "Miso.Subscription.History" — URL-based state that survives page reloads
----------------------------------------------------------------------------
module Miso.Storage
  ( -- ** Local
    getLocalStorage
  , setLocalStorage
  , removeLocalStorage
  , clearLocalStorage
  , localStorageLength
    -- ** Session
  , getSessionStorage
  , setSessionStorage
  , removeSessionStorage
  , clearSessionStorage
  , sessionStorageLength
  ) where
-----------------------------------------------------------------------------
import           Control.Monad (void)
-----------------------------------------------------------------------------
import           Miso.DSL
import           Miso.String (MisoString)
-----------------------------------------------------------------------------
-- | Retrieves a value stored under the given key in session storage.
getSessionStorage
  :: MisoString
  -- ^ Storage key to look up
  -> IO (Maybe MisoString)
getSessionStorage :: MisoString -> IO (Maybe MisoString)
getSessionStorage MisoString
key = do
  JSVal -> IO (Maybe MisoString)
forall a. FromJSVal a => JSVal -> IO a
fromJSValUnchecked (JSVal -> IO (Maybe MisoString))
-> IO JSVal -> IO (Maybe MisoString)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< (Storage -> MisoString -> IO JSVal)
-> MisoString -> Storage -> IO JSVal
forall a b c. (a -> b -> c) -> b -> a -> c
flip Storage -> MisoString -> IO JSVal
getItem MisoString
key (Storage -> IO JSVal) -> IO Storage -> IO JSVal
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< IO Storage
sessionStorage
-----------------------------------------------------------------------------
-- | Retrieves a value stored under the given key in local storage.
getLocalStorage
  :: MisoString
  -- ^ Storage key to look up
  -> IO (Maybe MisoString)
getLocalStorage :: MisoString -> IO (Maybe MisoString)
getLocalStorage MisoString
key =
  JSVal -> IO (Maybe MisoString)
forall a. FromJSVal a => JSVal -> IO a
fromJSValUnchecked (JSVal -> IO (Maybe MisoString))
-> IO JSVal -> IO (Maybe MisoString)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< (Storage -> MisoString -> IO JSVal)
-> MisoString -> Storage -> IO JSVal
forall a b c. (a -> b -> c) -> b -> a -> c
flip Storage -> MisoString -> IO JSVal
getItem MisoString
key (Storage -> IO JSVal) -> IO Storage -> IO JSVal
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< IO Storage
localStorage
-----------------------------------------------------------------------------
-- | Sets the value of a key in local storage.
--
-- @setLocalStorage key value@ sets the value of @key@ to @value@.
setLocalStorage
  :: MisoString
  -- ^ Storage key to set
  -> MisoString
  -- ^ Value to store
  -> IO ()
setLocalStorage :: MisoString -> MisoString -> IO ()
setLocalStorage MisoString
key MisoString
value = do
  s <- IO Storage
localStorage
  setItem s key value
-----------------------------------------------------------------------------
-- | Sets the value of a key in session storage.
--
-- @setSessionStorage key value@ sets the value of @key@ to @value@.
setSessionStorage
  :: MisoString
  -- ^ Storage key to set
  -> MisoString
  -- ^ Value to store
  -> IO ()
setSessionStorage :: MisoString -> MisoString -> IO ()
setSessionStorage MisoString
key MisoString
value = do
  s <- IO Storage
sessionStorage
  setItem s key value
-----------------------------------------------------------------------------
-- | Removes an item from local storage.
--
-- @removeLocalStorage key@ removes the value of @key@.
removeLocalStorage
  :: MisoString
  -- ^ Storage key to remove
  -> IO ()
removeLocalStorage :: MisoString -> IO ()
removeLocalStorage MisoString
key = do
  s <- IO Storage
localStorage
  removeItem s key
-----------------------------------------------------------------------------
-- | Removes an item from session storage.
--
-- @removeSessionStorage key@ removes the value of @key@.
removeSessionStorage
  :: MisoString
  -- ^ Storage key to remove
  -> IO ()
removeSessionStorage :: MisoString -> IO ()
removeSessionStorage MisoString
key = do
  s <- IO Storage
sessionStorage
  removeItem s key
-----------------------------------------------------------------------------
-- | Clears local storage.
--
-- @clearLocalStorage@ removes all values from local storage.
clearLocalStorage :: IO ()
clearLocalStorage :: IO ()
clearLocalStorage = Storage -> IO ()
clear (Storage -> IO ()) -> IO Storage -> IO ()
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< IO Storage
localStorage
-----------------------------------------------------------------------------
-- | Clears session storage.
--
-- @clearSessionStorage@ removes all values from session storage.
clearSessionStorage :: IO ()
clearSessionStorage :: IO ()
clearSessionStorage = Storage -> IO ()
clear (Storage -> IO ()) -> IO Storage -> IO ()
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< IO Storage
sessionStorage
-----------------------------------------------------------------------------
-- | Returns the number of items in local storage.
--
-- @localStorageLength@ returns the count of items in local storage
localStorageLength :: IO Int
localStorageLength :: IO Int
localStorageLength = JSVal -> IO Int
forall a. FromJSVal a => JSVal -> IO a
fromJSValUnchecked (JSVal -> IO Int) -> IO JSVal -> IO Int
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< IO Storage
localStorage IO Storage -> MisoString -> IO JSVal
forall o. ToObject o => o -> MisoString -> IO JSVal
! MisoString
"length"
-----------------------------------------------------------------------------
-- | Returns the number of items in session storage.
--
-- @sessionStorageLength@ returns the count of items in session storage
sessionStorageLength :: IO Int
sessionStorageLength :: IO Int
sessionStorageLength = JSVal -> IO Int
forall a. FromJSVal a => JSVal -> IO a
fromJSValUnchecked (JSVal -> IO Int) -> IO JSVal -> IO Int
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< IO Storage
sessionStorage IO Storage -> MisoString -> IO JSVal
forall o. ToObject o => o -> MisoString -> IO JSVal
! MisoString
"length"
-----------------------------------------------------------------------------
localStorage :: IO Storage
localStorage :: IO Storage
localStorage = JSVal -> Storage
Storage (JSVal -> Storage) -> IO JSVal -> IO Storage
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (MisoString -> IO JSVal
jsg MisoString
"window" IO JSVal -> MisoString -> IO JSVal
forall o. ToObject o => o -> MisoString -> IO JSVal
! MisoString
"localStorage")
-----------------------------------------------------------------------------
sessionStorage :: IO Storage
sessionStorage :: IO Storage
sessionStorage = JSVal -> Storage
Storage (JSVal -> Storage) -> IO JSVal -> IO Storage
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (MisoString -> IO JSVal
jsg MisoString
"window" IO JSVal -> MisoString -> IO JSVal
forall o. ToObject o => o -> MisoString -> IO JSVal
! MisoString
"sessionStorage")
-----------------------------------------------------------------------------
getItem :: Storage -> MisoString -> IO JSVal
getItem :: Storage -> MisoString -> IO JSVal
getItem (Storage JSVal
s) MisoString
key = JSVal
s JSVal -> MisoString -> [MisoString] -> IO JSVal
forall object args.
(ToObject object, ToArgs args) =>
object -> MisoString -> args -> IO JSVal
# MisoString
"getItem" ([MisoString] -> IO JSVal) -> [MisoString] -> IO JSVal
forall a b. (a -> b) -> a -> b
$ [MisoString
key]
-----------------------------------------------------------------------------
removeItem :: Storage -> MisoString -> IO ()
removeItem :: Storage -> MisoString -> IO ()
removeItem (Storage JSVal
s) MisoString
key = IO JSVal -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO JSVal -> IO ()) -> IO JSVal -> IO ()
forall a b. (a -> b) -> a -> b
$ JSVal
s JSVal -> MisoString -> [MisoString] -> IO JSVal
forall object args.
(ToObject object, ToArgs args) =>
object -> MisoString -> args -> IO JSVal
# MisoString
"removeItem" ([MisoString] -> IO JSVal) -> [MisoString] -> IO JSVal
forall a b. (a -> b) -> a -> b
$ [MisoString
key]
-----------------------------------------------------------------------------
setItem :: Storage -> MisoString -> MisoString -> IO ()
setItem :: Storage -> MisoString -> MisoString -> IO ()
setItem (Storage JSVal
s) MisoString
key MisoString
val = do
  _ <- JSVal
s JSVal -> MisoString -> (MisoString, MisoString) -> IO JSVal
forall object args.
(ToObject object, ToArgs args) =>
object -> MisoString -> args -> IO JSVal
# MisoString
"setItem" ((MisoString, MisoString) -> IO JSVal)
-> (MisoString, MisoString) -> IO JSVal
forall a b. (a -> b) -> a -> b
$ (MisoString
key, MisoString
val)
  pure ()
-----------------------------------------------------------------------------
clear :: Storage -> IO ()
clear :: Storage -> IO ()
clear (Storage JSVal
s) = do
  _ <- JSVal
s JSVal -> MisoString -> () -> IO JSVal
forall object args.
(ToObject object, ToArgs args) =>
object -> MisoString -> args -> IO JSVal
# MisoString
"clear" (() -> IO JSVal) -> () -> IO JSVal
forall a b. (a -> b) -> a -> b
$ ()
  pure ()
-----------------------------------------------------------------------------
newtype Storage = Storage JSVal
  deriving (Storage -> IO Object
(Storage -> IO Object) -> ToObject Storage
forall a. (a -> IO Object) -> ToObject a
$ctoObject :: Storage -> IO Object
toObject :: Storage -> IO Object
ToObject, Storage -> IO JSVal
(Storage -> IO JSVal) -> ToJSVal Storage
forall a. (a -> IO JSVal) -> ToJSVal a
$ctoJSVal :: Storage -> IO JSVal
toJSVal :: Storage -> IO JSVal
ToJSVal)
-----------------------------------------------------------------------------