servant-event-stream
Servant support for Server-Sent events
https://github.com/bflyblue/servant-event-stream
| Stackage Nightly 2026-07-29: | 0.4.0.1 |
| Latest on Hackage: | 0.4.0.1 |
servant-event-stream-0.4.0.1@sha256:c79ef3516626850347f2a92c15de473244539f2211c05d0350f4073d1659c669,1760Module documentation for 0.4.0.1
- Servant
- Servant.API
servant-event-stream
Server-Sent Events (SSE) support for Servant. Stream events from your server to connected clients, or consume third-party SSE streams (such as OpenAI) from Haskell.
Quick start
Define a domain type and a ToServerEvent instance. Use jsonEvent to JSON-encode the data payload, or serverEvent for raw bytestrings:
data ChatEvent
= ContentDelta Text
| ContentDone Text
| ChatDone
type MyApi = "chat" :> ServerSentEvents (SourceIO ChatEvent)
instance ToServerEvent ChatEvent where
toServerEvent (ContentDelta d) = jsonEvent (Just "response.content.delta") Nothing d
toServerEvent (ContentDone t) = jsonEvent (Just "response.content.done") Nothing t
toServerEvent ChatDone = jsonEvent (Just "response.done") Nothing ()
server :: Server MyApi
server = pure $ source
[ContentDelta "Hel", ContentDelta "lo!", ContentDone "Hello!", ChatDone]
For POST endpoints (e.g. OpenAI chat completions), use PostServerSentEvents:
type MyApi = "chat" :> ReqBody '[JSON] ChatRequest
:> PostServerSentEvents (SourceIO ChatEvent)
To consume an SSE stream, provide a FromServerEvent instance. Use jsonData to decode JSON from the data field:
instance FromServerEvent ChatEvent where
fromServerEvent ev = case eventType ev of
Just "response.content.delta" -> ContentDelta <$> jsonData ev
Just "response.content.done" -> ContentDone <$> jsonData ev
Just "response.done" -> Right ChatDone
_ -> Left "unknown event"
For types that serialise entirely as JSON (no event type dispatch), use DerivingVia:
data Temperature = Temperature { celsius :: Double }
deriving (Generic, ToJSON, FromJSON)
deriving (ToServerEvent, FromServerEvent) via JsonData Temperature
See the Haddock documentation for the full API reference, or the upgrading guide if you’re coming from 0.3.
Coming from servant’s built-in SSE
Servant 0.20.3.0 introduced its own ServerSentEvents type in
Servant.API.ServerSentEvents. If you’d like to try this library instead,
here’s how the concepts map:
| servant built-in | servant-event-stream |
|---|---|
Servant.API.ServerSentEvents |
Servant.API.EventStream |
ServerSentEvents 'JsonEvent MyEvent |
ServerSentEvents (SourceIO MyEvent) |
EventKind (RawEvent / JsonEvent) |
ToServerEvent / FromServerEvent typeclasses |
This library takes a typeclass approach: ToServerEvent and FromServerEvent
control how your domain types map to SSE fields, giving you access to event
names, ids, comments, and retry directives in a single ServerEvent record.
It also provides PostServerSentEvents for POST endpoints, and JSON helpers
(jsonEvent, jsonData, JsonData) for APIs that encode payloads as JSON.
The built-in module focuses on client-side consumption (HasClient instances),
while this library provides both HasServer and HasClient instances.
See the side-by-side examples for a more
detailed walkthrough.
Development
Uses Nix flakes for the development environment.
nix develop # enter dev shell
nix build # full build
nix flake check # CI-equivalent check
cabal test # run tests (inside dev shell)
Changes
Revision history for servant-event-stream
0.4.0.1 – 2026-03-01
- Fix haddock markup in module header (
t'JsonData'rendered literally).
0.4.0.0 – 2026-02-27
- Breaking: Add
eventCommentandeventRetryfields toServerEvent. Existing code using theServerEventconstructor must add the new fields, e.g.ServerEvent typ eid dat Nothing Nothing. - Breaking: Drop
charset=utf-8from theAcceptinstance. The content type is nowtext/event-stream(no parameters), matching the WHATWG spec. - New dependency:
aeson(for JSON helpers). - New dependency:
servant-client-core(forHasClientinstances). - Add
serverEventconvenience constructor matching the old 3-field API for easy migration. - Add
dataEventconvenience constructor for simple data-only events. - Add
commentEventconvenience constructor for heartbeat keepalives. - Add
retryEventconvenience constructor for setting client reconnection delay. - Export
encodeServerEventfor direct use outside of Servant. - Add
FromServerEventtypeclass anddecodeServerEventfor parsing SSE events. - Add
MimeUnrender EventStreamandFramingUnrender ServerEventFraminginstances for consuming SSE streams. - Add
HasClientinstances forServerSentEventsandPostServerSentEvents, enabling client-side SSE consumption viaservant-client. - Add
PostServerSentEventscombinator for POST SSE endpoints (e.g. OpenAI’s streaming API), withHasServer,HasClient, andHasForeigninstances. - Add
JsonDatanewtype for derivingToServerEventandFromServerEventvia JSON encoding usingDerivingVia. - Add
jsonEventhelper to construct events with JSON-encoded data payloads. - Add
jsonDatahelper to decode JSON from an event’s data field. - Strip leading UTF-8 BOM from SSE input, per the WHATWG spec.
- Export
ServerEventFraming(needed forStreamPostSSE endpoints). - Always emit at least one
data:field, even wheneventDatais empty. Previously, empty data produced no output and the event was silently dropped by clients. - Sanitize
eventType,eventId, andeventCommentby stripping CR and LF characters to prevent malformed SSE output. - Strip NULL characters from
eventId(the SSE spec silently ignores ids containing NULL).
0.3.2.1 – 2026-02-27
- Add guard space after colon in SSE field encoding. Without it, field values starting with a space lost their leading space because the SSE spec strips exactly one leading space from field values. (Issue #16)
- Add unit tests for
encodeServerEvent.
0.3.2.0 – 2026-02-23
- Support GHC 8.6 through 9.12 (base 4.12 to 4.21).
- Remove redundant
Typeablederiving to fix-Wderiving-typeablewarning on GHC 9.12+. - Add CI test matrix for GHC 8.6, 8.10, 9.2, 9.8, 9.12.
0.3.1.1 – 2026-02-23
- Constrain base bounds in test suite to match library.
0.3.1.0 – 2025-08-21
- Qualify
servantimports so thatServerSentEventsadded inservant-0.20.3.0does not conflict with ours. (Issue #12)
0.3.0.0 – 2024-09-05
-
Breaking changes to the API.
Event streams are implemented using servant’s ‘Stream’ endpoint. You should provide a handler that returns a stream of events that implements ‘ToSourceIO’ where events have a ‘ToServerEvent’ instance.
Example:
type MyApi = “books” :> ServerSentEvents (SourceIO Book)
instance ToServerEvent Book where toServerEvent book = …
server :: Server MyApi server = streamBooks where streamBooks :: Handler (SourceIO Book) streamBooks = pure $ source [book1, …]
0.2.1.0 – 2021-04-21
- Import
Data.Semigroupfor base < 4.11.0
0.2.0.0 – 2021-04-20
Servant.EventStreamwas moved toServant.API.EventStreamto adhere existing upstream layout.
0.1.0.0 – 2018-04-30
- First version. Released on an unsuspecting world.