arowM / tepa / Tepa.Stream

Handle data stream.

Stream


type alias Stream a =
Internal.Core.Stream a

Stream is a specification of how to retrieve a sequence of data arriving one after another in real time until it ends.

Since it is only a specification, it is only when it is evaluated by run or await functions that your actually start getting data.

awaitFirst : Stream a -> Tepa.Promise m a

Run Stream and wait for the first data.

awaitFirstWithTimeout : Basics.Int -> Stream a -> Tepa.Promise m (Maybe a)

Similar to awaitFirst, but also specifies the timeout in milliseconds. Returns Nothing if no data is produced by the timeout seconds after the Stream is run.

awaitAll : Stream a -> Tepa.Promise m (List a)

Run Stream and wait for all the data the Stream produces. Make sure that awaitAll is not resolved if the Stream continues to produce data indefinitely.

awaitAllWithTimeout : Basics.Int -> Stream a -> Tepa.Promise m (List a)

Similar to awaitAll, but also specifies the timeout in milliseconds. Returns Nothing if no data is produced by the timeout seconds after the Stream is run.

awaitWhile : (a -> Basics.Bool) -> Stream a -> Tepa.Promise m (List a)

Run Stream and continues to wait for all data as long as it meets the given condition. When the Stream has finished producing all data while satisfying the condition, awaitWhile resolves to the all data.

awaitUnless : (a -> Basics.Bool) -> Stream a -> Tepa.Promise m (List a)

Run Stream and continues to wait for all data as long as it does not meet the given condition. When the Stream has finished producing all data while not satisfying the condition, awaitUnless resolves to the all data.

run : (a -> List (Tepa.Promise m ())) -> Stream a -> Tepa.Promise m ()

Map Procedures to each Stream data asynchronously.

import Tepa exposing (Promise)
import Tepa.Time as Time

sample : Promise m ()
sample =
    Tepa.bind Time.tick <|
        \stream ->
            run
                (\a ->
                    [ Debug.todo "procedures for each stream data `a`"
                    ]
                )
                stream

while : (Result err a -> List (Tepa.Promise m ())) -> Stream (Result err a) -> Tepa.Promise m ()

Similar to run, but the handler stops monitoring the Stream when it receives Err. After the first Err value is handled, while resolves to ().

map : (a -> b) -> Stream a -> Stream b

indexedMap : (Basics.Int -> a -> b) -> Stream a -> Stream b

filter : (a -> Basics.Bool) -> Stream a -> Stream a

filterMap : (a -> Maybe b) -> Stream a -> Stream b

take : Basics.Int -> Stream a -> Stream a

Take first N data from the stream, and close the Stream.

import Tepa exposing (Promise)
import Tepa.Stream as Stream

sample : Promise m ()
sample =
    Stream.bind
        (Tepa.httpRequest options
            |> Stream.take 1
        )
    <|
        \response ->
            []

union : List (Stream a) -> Stream a

Combine two streams.

import Json.Decoder as JD
import Tepa exposing (Promise)
import Tepa.Stream as Stream

type WithTimeout
    = TimedOut
    | ClickSubmitButton

withTimeout : Promise m WithTimeout
withTimeout =
    Tepa.customViewEvent
        { key = "submit"
        , type_ = "click"
        , decoder =
            JD.succeedPromise
                { stopPropagation = False
                , preventDefault = False
                , value = ClickSubmitButton
                }
        }
        |> Stream.union
            (Time.periodic 3000
                |> Tepa.map
                    (Stream.map (\_ -> TimedOut))
            )
        |> Stream.first

scan : (a -> b -> Maybe b) -> b -> Stream a -> Stream b

Scans the Stream till the scanner function returns Nothing. The scanner function receives new Stream data a, and the past result b. If the scanner returns Nothing, resulting Stream ends.

Handle cases

oneOf : List (Case m) -> Tepa.Promise m ()

Execute only one Case that is the first of all the cases to receive data on its stream.

Suppose there is a popup with a Save button and a Close button. When the Save button is pressed, the client saves the input, leaves the popup open, and waits for the user to press the Save or Close button again. When the Close button is pressed, the input is discarded and the popup is closed.

For such cases, oneOf and continue and break are useful.

import Tepa exposing (Promise)
import Tepa.Stream as Stream

popupProcedure : Promise Memory ()
popupProcedure =
    [ Stream.oneOf
        [ Stream.break
            (Tepa.viewEventStream
                { type_ = "click"
                , key = "popup_save"
                }
            )
          <|
            \() ->
                -- Just stop waiting for data.
                []
        , Stream.continue
            (Tepa.viewEventStream
                { type_ = "click"
                , key = "popup_close"
                }
            )
          <|
            \() ->
                [ closePopup
                ]
        , Stream.continue
            (Tepa.viewEventStream
                { type_ = "click"
                , key = "openPopupButton"
                }
            )
          <|
            \() ->
                [ openPopup
                ]
        ]

    -- This clause is evaluated after the user clicks the "Save" button
    -- and all spawned procedures are completed.
    , Tepa.bind (Tepa.getValue "popup_input") <|
        \str ->
            [ saveInput str
            , Tepa.lazy <| \_ -> popupProcedure
            ]
    ]


type Case m

continue : Tepa.Promise m (Stream a) -> (a -> List (Tepa.Promise m ())) -> Case m

Wait for other data, and execute the given procedure asynchronously.

break : Tepa.Promise m (Stream a) -> (a -> List (Tepa.Promise m ())) -> Case m

Stop waiting for data immediately, and execute the given procedure.

customCase : Tepa.Promise m (Stream a) -> (a -> { break : Basics.Bool, procedure : List (Tepa.Promise m ()) }) -> Case m

Create custom case.

import Tepa
import Tepa.Stream as Stream exposing (Case)

breakAfterClickTwice : Case Memory
breakAfterClickTwice =
    Stream.customCase
        (Tepa.viewEventStream
            { key = "twoStepButton"
            , type_ = "click"
            }
            |> Tepa.map
                (Stream.indexedMap (\n _ -> n + 1))
        )
    <|
        \n ->
            if n < 2 then
                { break = False
                , procedure =
                    [ Tepa.modify <|
                        \m ->
                            { m | counter = m.counter + 1 }
                    ]
                }

            else
                { break = True
                , procedure = []
                }