Rerouted (retagged, needs approval). The tag now points at a different commit than when this version was published. The Lamdera package server serves a copy recovered from https://github.com/421anon/elm-flow.
What we checked: Tag 2.1.0 is still on github.com/421anon/elm-flow; its commit a0f12ea9a25f is from 2026-03-20 12:05 UTC, after the version was published (2026-03-20 11:50 UTC); elm.json is byte-identical to package.elm-lang.org's; the exposed modules match docs.json; the zip's SHA-1 is not the published one and nothing else confirms its contents.
We could not verify this copy against what was published, so the Lamdera compiler asks before using it (y/n). Without a terminal, as in CI, it stops with an error unless approved:
lamdera make --auto-approve-package-rerouting approves rerouted versions for that run,LAMDERA_AUTO_APPROVE_PACKAGE_REROUTING=1 does the same for every run in that environment,LAMDERA_CACHE=off skips the Lamdera package server altogether: packages come from package.elm-lang.org and GitHub only, as with elm, and a rerouted version then fails to download.Details · package.elm-lang.org's zip: https://github.com/421anon/elm-flow/zipball/2.1.0/
A monadic interface for The Elm Architecture, bridging synchronous state modifications with asynchronous subscriptions via Channels.
Note: This library merges concepts from two community packages:
chrilves/elm-io (The free monad interface for TEA)brian-watkins/elm-procedure (Continuation-passing channels and subscriptions)In a traditional TEA application you define a central Msg type and an
update : Msg -> Model -> (Model, Cmd Msg) function that dispatches on it.
With Flow there is no separate Msg type and no central dispatch table.
Your event handlers and subscriptions produce Flow values directly:
-- a button click
Html.button
[ Html.Events.onClick (Flow.modify (\m -> { m | count = m.count + 1 })) ]
[ Html.text "+" ]
-- an HTTP response
Flow.lift (Http.get { url = "/api/data", expect = Http.expectJson handleResult decoder })The runtime executes each Flow immediately as it arrives, keeping behaviour
co-located with the event that triggers it.
The concrete Elm Platform.Program type produced by Flow.element,
Flow.document, and Flow.application.
Build a standard Browser.element program whose messages are Flow
computations.
Like element but the view function returns a full Document, allowing
you to control the page <title>.
main =
Flow.document
{ init = \_ -> ( initialModel, loadData )
, view = view -- returns { title = "…", body = […] }
, subscriptions = \_ -> Sub.none
}Like document but also handles URL changes, for single-page applications.
main =
Flow.application
{ init = \_ url key -> ( initialModel key, loadPage url )
, view = view
, subscriptions = \_ -> Sub.none
, onUrlRequest = handleUrlRequest
, onUrlChange = handleUrlChange
}Suspend this Flow pipeline, open a Channel, and resume when a value arrives.
type alias Model =
{ latestMessage : String }
listenOnce : Flow Model ()
listenOnce =
Flow.await myChannel
|> Flow.andThen (\msg -> Flow.modify (\m -> { m | latestMessage = msg }))Open a channel and run the given handler for every value it produces, indefinitely.
listenForever : Flow Model ()
listenForever =
Flow.subscribe
(\msg -> Flow.modify (\m -> { m | latestMessage = msg }))
myChannelInstead of creating a new port for every JavaScript interaction, you can use Flow.ffi
to call many JS functions by name using a single pair of ports.
You provide the JS function name, an encoder for the outgoing data, and a decoder for
the return value. Under the hood, this helper uses a unique string key to automatically
match JavaScript responses back to the correct Flow request.
-- 1. Define one pair of ports
port ffiOut : { key : String, fn : String, value : Json.Encode.Value } -> Cmd msg
port ffiIn : ({ key : String, value : Json.Decode.Value } -> msg) -> Sub msg
-- 2. Wire them into a helper
callJs : String -> (a -> Json.Encode.Value) -> Json.Decode.Decoder b -> a -> Flow s b
callJs =
Flow.ffi Ports.ffiOut Ports.ffiIn
-- 3. Call any JS function by name
fetchData : String -> Flow Model ()
fetchData id =
callJs "fetchData" Json.Encode.string decodeData id
|> Flow.andThen handleData
-- On the JavaScript side:
-- app.ports.ffiOut.subscribe(async (req) => {
-- if (req.fn === "fetchData") {
-- const data = await fetch("/api/data/" + req.value).then(r => r.json());
-- app.ports.ffiIn.send({ key: req.key, value: data });
-- }
-- });Lift a plain value into Flow without performing any effects.
Flow.pure 42
|> Flow.map (\n -> n + 1)
-- produces 43, state unchangedLift an Elm Cmd into Flow. The command is executed and its result
becomes the value produced by this Flow step.
Flow.lift (Http.get { url = "/ping", expect = Http.expectString identity })
|> Flow.andThen handleResponseLift a classic TEA update-style function into Flow. Reads the current
model, applies the function, writes the new model, and emits any commands.
Flow.liftUpdate (MyOldModule.update msg)Read the current model.
Flow.get
|> Flow.andThen (\model -> Flow.pure model.count)Replace the model entirely.
Flow.set { count = 0 }Apply a function to transform the model.
Flow.modify (\m -> { m | count = m.count + 1 })Transform the value produced by a Flow without changing state.
Flow.pure 3
|> Flow.map (\n -> n * 2)
-- produces 6Chain two Flow computations. The second receives the value produced by the first.
Flow.get
|> Flow.andThen (\m -> Flow.pure m.count)Flatten a nested Flow s (Flow s a) into Flow s a.
Flow.pure (Flow.pure 42)
|> Flow.join
-- same as Flow.pure 42Apply a function wrapped in Flow to a value wrapped in Flow.
Flow.pure (\n -> n + 1)
|> Flow.ap (Flow.pure 41)
-- produces 42Flipped ap — apply a value to a function, both wrapped in Flow.
Flow.pure 41
|> Flow.flap (Flow.pure (\n -> n + 1))
-- produces 42Compose two Kleisli arrows (functions returning Flow).
handleResponse : Response -> Flow Model ()
handleResponse =
Flow.compose persistToDisk fetchDataRun the piped Flow for its effects, discard its result, then run the argument Flow and return its result.
saveRecord
|> Flow.seq (Flow.pure "saved!")
-- runs saveRecord, then produces "saved!"Map over a list with a function that returns Flow, collecting the results in order.
Flow.traverse fetchUser userIds
|> Flow.andThen renderAllSequence a list of Flow computations, collecting all results.
Equivalent to traverse identity.
Flow.mapM [ step1, step2, step3 ]
|> Flow.andThen finalizeRe-interpret a Flow a x inside a larger context Flow b x by providing
a getter (Flow b a) and a setter (a -> Flow b ()).
This is the primitive that via and the other optics helpers are built on.
Use via in normal code; reach for replace only when you need a custom
traversal that optics cannot express.
The empty computation — terminates the current branch without producing a value or changing state. Use this as the natural end-point of a pipeline.
Flow.get
|> Flow.andThen
(\m ->
if m.ready then
doWork
else
Flow.none
)Yield a value back to the Elm runtime, allowing the browser to repaint,
then continue with the value. Equivalent to Process.sleep 0.
-- ensure the UI reflects intermediate state before a heavy computation
Flow.set loadingModel
|> Flow.seq (Flow.yield ())
|> Flow.seq heavyComputationWrap a Flow so that every state write inside it is followed by a render cycle, giving the browser a chance to repaint between steps.
Useful when you need the UI to visually reflect intermediate states during a multi-step computation rather than only showing the final result.
-- the UI repaints after each step, not just at the end
Flow.forceRendering <|
Flow.mapM [ setStepOne, setStepTwo, setStepThree ]Run several independent Flow branches concurrently, one per list element. Each branch shares the same initial state snapshot; state writes from earlier branches are visible to later ones (left-to-right evaluation order).
Flow.batch [ 1, 2, 3 ]
|> Flow.map (\n -> n * 2)
-- forks into three branches producing 2, 4, 6Run a list of Flow computations as concurrent branches. Each branch runs
independently; the continuation (via andThen) is invoked once per branch
with that branch's individual result.
Use mapM instead if you want the results collected into a List.
-- fire three notifications in parallel
Flow.batchM [ notifyAlice, notifyBob, notifyCarol ]
-- continuation runs once per branch, not once with a List
Flow.batchM [ fetchUserA, fetchUserB ]
|> Flow.andThen (\user -> renderUser user)Continue only if the value is Just, terminate with none otherwise.
fetchUser userId
|> Flow.assertJust
|> Flow.andThen renderUserContinue only if the result is Ok, silently terminate on Err.
decodeResponse body
|> Flow.assertOk
|> Flow.andThen handleDataContinue only if the predicate holds for the produced value, terminate
with none otherwise.
Flow.get
|> Flow.assertCondition (\m -> m.count > 0)
|> Flow.andThen doSomethingWithPositiveCountUnwrap a Maybe and feed the inner value into a continuation, or
terminate with none if the Maybe is Nothing.
Flow.fromMaybe model.selectedId
(\id -> fetchItem id |> Flow.andThen renderItem)Fire a Task for its side-effects and ignore the result (both Ok and
Err are discarded). Useful for fire-and-forget operations such as writing
to localStorage via a port task.
Flow.attemptTask (Ports.saveToLocalStorage key value)Read a value through an optic. Produces Just a if the optic matches,
Nothing if it does not (e.g. a Prism pointing at the wrong variant).
Flow.try MyModel.selectedItem
(\maybeItem ->
case maybeItem of
Just item ->
renderItem item
Nothing ->
showPlaceholder
)Read a value through an optic, terminating with none if the optic does
not match.
Flow.forAll MyModel.selectedItem
(\item -> renderItem item)Read all values targeted by an optic (useful for traversals).
Flow.getAll MyModel.allItems
(\items -> Flow.pure (List.length items))Modify the value(s) targeted by an optic.
Flow.over MyModel.count (\n -> n + 1)Set the value(s) targeted by an optic.
Flow.setAll MyModel.status LoadingRun a Flow a x in the context of a larger model s, using an optic to
zoom in and out. State reads and writes inside the sub-flow are applied through
the optic.
Flow.via MyModel.editingRecord
(Flow.modify (\r -> { r | name = newName }))Run a Flow only when the condition is True, otherwise produce ().
Flow.when model.isLoggedIn savePreferencesRun a Flow for its effects, then produce a fixed value a regardless of
what the flow returned.
saveRecord
|> Flow.return "saved"Fire a Flow as a background branch and immediately continue with ().
The background branch runs concurrently; its state writes are interleaved
with the rest of the batch.
Flow.async (longRunningSync model)
|> Flow.seq nextStepAcquire a resource, run a computation, then release the resource —
even if the computation terminates early with none.
Flow.bracket_
(Flow.modify (\m -> { m | loading = True }))
(Flow.modify (\m -> { m | loading = False }))
fetchDataSet a Bool field to True for the duration of a computation, then
restore it to False. Useful for loading/busy flags.
Flow.setting MyModel.isLoading fetchDataRun a computation only if a Bool lens is currently False, and hold
it at True for the duration. Subsequent calls while the lock is held are
silently dropped.
Flow.locking MyModel.isSaving saveRecord
A description of a computation that can read and write state
sand eventually produces a value of typea.Flowis a free monad: values of this type are data structures that the runtime interprets, rather than functions that run immediately. This makes it possible to batch, sequence, and transform computations before any side-effects take place.