Module

Redux.Saga

Package
purescript-redux-saga
Repository
felixschl/purescript-redux-saga

#sagaMiddleware Source

sagaMiddleware :: forall eff state action. Saga' Unit state action action Unit -> Middleware eff state action _ _

#Saga Source

type Saga env state action a = Saga' env state action action a

Simplified Saga' alias where the input and output is the same.

#Saga' Source

newtype Saga' env state input output a

The Saga monad is an opinionated, closed monad with a range of functionality.

           read-only environment, accessible via `MonadAsk` instance
           /   your state container type (reducer)
           |    /    the type this saga consumes (i.e. actions)
           |    |     /    the type of output this saga produces (i.e. actions)
           |    |     |     /    the return value of this Saga
           |    |     |     |     /

newtype Saga' env state input output a = ...

The env parameter

The env parameter gives us access to a read-only environment, accessible via MonadAsk instance. Forked computations have the opportunity to change this environment for the execution of the fork without affecting the current thread's env.

type MyConfig = { apiUrl :: String }

logApiUrl :: ∀ state input output. Saga' MyConfig state input output Unit
logApiUrl = void do
    { apiUrl } <- ask
    liftIO $ Console.log apiUrl

The state parameter

The state parameter gives us read access to the current application state via the select combinator. This value may change over time.

type MyState = { currentUser :: String }

logUser :: ∀ env input output. Saga' env MyState input output Unit
logUser = void do
    { currentUser } <- select
    liftIO $ Console.log currentUser

The input parameter

The input parameter denotes the type of input this saga consumes. This in combination with the output parameter exposes sagas for what they naturally are: pipes consuming input and producing output. Typically this input type would correspond to your application's actions type.

data MyAction
  = LoginRequest Username Password
  | LogoutRequest

loginFlow :: ∀ env state output. Saga' env state MyAction MyAction Unit
loginFlow = forever do
  take case _ of
    LoginRequest user pw -> do
      ...
    _ -> Nothing -- ignore other actions

The output parameter

The output parameter denotes the type of output this saga produces. Typically this input type would correspond to your application's actions type. The output sagas produce is fed back into redux cycle by dispatching it to the store.

data MyAction
  = LoginRequest Username Password
  | LogoutSuccess Username
  | LogoutFailure Error
  | LogoutRequest

loginFlow :: ∀ env state. Saga' env state MyAction MyAction Unit
loginFlow = forever do
  take case _ of
    LoginRequest user pw -> do
      liftAff (attempt {- some I/O -}) >>= case _ of
        Left err -> put $ LoginFailure err
        Right v  -> put $ LoginSuccess user
    _ -> Nothing -- ignore other actions

The a parameter

The a parameter allows every saga to return a value, making it composable. Here's an example of a contrived combinator

type MyAppConf = { apiUrl :: String }
type Account = { id :: String, email :: String }

getAccount
  :: ∀ state input output
    . String
  -> Saga' MyAppConf state input output Account
getAccount id = do
  { apiUrl } <- ask
  liftAff (API.getAccounts apiUrl)

-- later ...

saga
  :: ∀ state input output
   . Saga' MyAppConf state input output Unit
saga = do
  account <- getAccount "123-dfa-123"
  liftEff $ Console.log $ show account.email

Instances

#SagaPipe Source

type SagaPipe env state input action a = Pipe input action (ReaderT (env /\ (ThreadContext state input action)) IO) a

#SagaFiber Source

newtype SagaFiber input a

A SagaFiber is a single computation.

#ThreadContext Source

type ThreadContext state input action = { fibersRef :: Ref (Array (SagaFiber input Unit)), global :: GlobalState state action, newFiberVar :: AVar (Maybe (SagaFiber input Unit)), tag :: String }

A ThreadContext is a collection of saga fibers.

#IdSupply Source

newtype IdSupply

#KeepAlive Source

#Label Source

type Label = String

#GlobalState Source

type GlobalState state action = { api :: MiddlewareAPI (infinity :: INFINITY) state action, idSupply :: IdSupply }

#take Source

take :: forall a output input state env. (input -> Maybe (Saga' env state input output a)) -> Saga' env state input output a

take blocks to receive an input value for which the given function returns Just a saga to execute. This saga is then executed on the same thread, blocking until it finished running.

Example

data Action = SayFoo | SayBar

sayOnlyBar
  :: ∀ env state Action Action
   . Saga' env state Action Action Unit
sayOnlyBar = do
  take case _ of
    SayFoo -> Nothing
    SayBar -> Just do
      liftEff $ Console.log "Bar!"

#takeLatest Source

takeLatest :: forall a output input state env. (input -> Maybe (Saga' env state input output a)) -> Saga' env state input output a

Similar to take but runs indefinitely, replacing previously spawned tasks with new ones.

#fork Source

fork :: forall eff a output input state env. Saga' env state input output a -> Saga' env state input output (SagaFiber input a)

fork puts a saga in the background

Example

helloWorld
  :: ∀ env state input output
   . Saga' env state input output Unit
helloWorld = do
  fork $ do
    liftAff $ delay $ 10000.0 # Milliseconds
    liftAff $ Console.log "World!"
  liftEff $ Console.log "Hello"

-- >> Hello
-- >> World!

fork returns a SagaTask a, which can later be joined using joinTask or canceled using cancelTask.

important: A saga thread won't finish running until all attached forks have finished running!

#forkNamed Source

forkNamed :: forall eff a output input state env. NameTag -> Saga' env state input output a -> Saga' env state input output (SagaFiber input a)

Same as fork, but allows setting a name tag

#fork' Source

fork' :: forall eff a output input state newEnv env. newEnv -> Saga' newEnv state input output a -> Saga' env state input output (SagaFiber input a)

Same as fork, but allows setting a custom environment

#forkNamed' Source

forkNamed' :: forall eff a output input state newEnv env. String -> newEnv -> Saga' newEnv state input output a -> Saga' env state input output (SagaFiber input a)

Same as fork', but allows setting a name tag

#put Source

put :: forall state action input env. action -> Saga' env state input action Unit

put emits an output which gets dispatched to your redux store and in turn becomes available to sagas.

Example

data Action = SayFoo | SayBar

sayOnlyBar
  :: ∀ env state
   . Saga' env state Action Action Unit
sayOnlyBar = do

  put SayBar -- ^ trigger a `SayBar` action right away.

  take case _ of
    SayFoo -> Nothing
    SayBar -> Just do
      liftEff $ Console.log "Bar!"

-- >> Bar!

#select Source

select :: forall a output input state env. Saga' env state input output state

select gives access the the current application state and it's output varies over time as the application state changes.

Example

printState
  :: ∀ env state input output
  => Show state
   . Saga' env state input output Unit
printState = do
  state <- select
  liftAff $ Console.log $ show state

#joinTask Source

joinTask :: forall eff a output input state env. SagaFiber _ a -> Saga' env state input output a

joinTask blocks until a given SagaTask returns.

NOTE: Canceling the joining of a task will also cancel the task itself.

Example

helloWorld
  :: ∀ env state input output
   . Saga' env state input output Unit
helloWorld = do
  task <- fork $ do
    liftAff $ delay $ 10000.0 # Milliseconds
    liftAff $ Console.log "World!"
  joinTask task
  liftEff $ Console.log "Hello"

-- >> World!
-- >> Hello

#cancelTask Source

cancelTask :: forall eff a output input state env. SagaFiber _ a -> Saga' env state input output Unit

cancelTask cancels a given SagaTask.

Example

helloWorld
  :: ∀ env state input output
   . Saga' env state input output Unit
helloWorld = do
  task <- fork $ do
    liftAff $ delay $ 10000.0 # Milliseconds
    liftAff $ Console.log "World!"
  cancelTask task
  liftEff $ Console.log "Hello"

-- >> Hello

#channel Source

channel :: forall eff a state action input' input env. NameTag -> (Emitter input' -> IO Unit) -> Saga' env state (Either input input') action a -> Saga' env state input action (SagaFiber input a)

Forks a new saga, providing the caller with an Emitter, allowing them to inject arbitrary values into the forked saga. To discern between upstream values and injected values, values are tagged Left and Right respectively.

#localEnv Source

localEnv :: forall a output input state env2 env. (env -> env2) -> Saga' env2 state input output a -> Saga' env state input output a

localEnv runs a saga in a modified environment. This allows us to combine sagas in multiple environments. For example, we could write sagas that require access to certain values like account information without worrying about "how" to manually pass those values along.

Example

printEnv
  :: ∀ env state input output
  => Show env
   . Saga' env state input output Unit
printEnv = do
  env <- ask
  liftEff $ Console.log $ show env

saga
  :: ∀ env state input output
   . Saga' env state input output Unit
saga = do
  localEnv (const "Hello") printEnv
  localEnv (const "World!") printEnv

-- >> Hello
-- >> World!