packages feed

servant-event-stream 0.3.0.0 → 0.3.0.1

raw patch · 2 files changed

+63/−54 lines, 2 filesdep −attoparsecdep −binarydep −wai-extradep ~basePVP ok

version bump matches the API change (PVP)

Dependencies removed: attoparsec, binary, wai-extra

Dependency ranges changed: base

API changes (from Hackage documentation)

Files

servant-event-stream.cabal view
@@ -1,6 +1,6 @@ cabal-version: >=1.10 name: servant-event-stream-version: 0.3.0.0+version: 0.3.0.1 stability: alpha synopsis: Servant support for Server-Sent events category: Servant, Web@@ -31,16 +31,13 @@     OverloadedStrings    build-depends:-    attoparsec >=0.14 && <0.15,     base >=4.10 && <4.20,-    binary >=0.8 && <0.9,     bytestring >=0.11.1.0 && <0.13,     http-media >=0.7.1.3 && <0.9,     lens >=4.17 && <5.4,     servant-foreign >=0.15 && <0.17,     servant-server >=0.15 && <0.21,-    text >=1.2.3 && <2.2,-    wai-extra >=3.0 && <3.2+    text >=1.2.3 && <2.2    hs-source-dirs: src   default-language: Haskell2010
src/Servant/API/EventStream.hs view
@@ -9,11 +9,49 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} +{- |+Module: Servant.API.EventStream+Description: Server Sent Events for Servant Streams+Copyright: (c) 2024 Shaun Sharples+License: BSD3+Stability: alpha+-} module Servant.API.EventStream (+  -- * Server-Sent Events++  -- | 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, ...]   ServerEvent (..),   ToServerEvent (..),   ServerSentEvents,   EventStream,++  -- * Recommended headers for Server-Sent Events++  -- | This is mostly to guide reverse-proxies like+  --   <https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/#x-accel-buffering nginx>.+  --+  --   Example:+  --+  --   > type MyApi = "books" :> ServerSentEvents (RecommendedEventSourceHeaders (SourceIO Book))+  --   >+  --   > server :: Server MyApi+  --   > server = streamBooks+  --   >   where streamBooks :: Handler (RecommendedEventSourceHeaders (SourceIO Book))+  --   >         streamBooks = pure $ recommendedEventSourceHeaders $ source [book1, ...]   RecommendedEventSourceHeaders,   recommendedEventSourceHeaders, )@@ -29,10 +67,7 @@ import Data.Text (Text) import Data.Typeable (Typeable) import GHC.Generics (Generic)-import Network.HTTP.Media (-  (//),-  (/:),- )+import Network.HTTP.Media ((//), (/:)) import Servant import Servant.Foreign import Servant.Foreign.Internal (_FunctionName)@@ -59,7 +94,7 @@   deriving (Show, Eq, Generic)  {- | This typeclass allows you to define custom event types that can be-  transformed into the 'ServerEvent' type, which is used to represent events in+  transformed into the t'ServerEvent' type, which is used to represent events in   the Server-Sent Events (SSE) protocol. -} class ToServerEvent a where@@ -68,25 +103,27 @@ instance (ToServerEvent a) => MimeRender EventStream a where   mimeRender _ = encodeServerEvent . toServerEvent -instance ToServerEvent ServerEvent where-  toServerEvent = id+{- 1. Field names must not contain LF, CR or COLON characters.+   2. Values must not contain LF or CR characters.+      Multple consecutive `data:` fields will be joined with LFs on the client.+-} -{- | 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.+-- | Encodes a t'ServerEvent' into a 'LBS.ByteString' that can be sent to the client.+encodeServerEvent :: ServerEvent -> LBS.ByteString+encodeServerEvent e =+  optional "event:" (eventType e)+    <> optional "id:" (eventId e)+    <> mconcat (map (field "data:") (safelines (eventData e)))+ where+  optional name = maybe mempty (field name)+  field name val = name <> val <> "\n" -  Example:+  -- discard CR and split LFs into multiple data values+  safelines = C8.lines . C8.filter (/= '\r') -  > 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, ...]--}+instance ToServerEvent ServerEvent where+  toServerEvent = id+ instance {-# OVERLAPPABLE #-} (ToServerEvent chunk, ToSourceIO chunk a) => HasServer (ServerSentEvents a) context where   type ServerT (ServerSentEvents a) m = ServerT (StreamGet ServerEventFraming EventStream a) m   route Proxy =@@ -130,41 +167,16 @@ instance Accept EventStream where   contentType _ = "text" // "event-stream" /: ("charset", "utf-8") -{- | This is mostly to guide reverse-proxies like-  <https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/#x-accel-buffering nginx>--  Example:--  > type MyApi = "books" :> ServerSentEvents (RecommendedEventSourceHeaders (SourceIO Book))-  >-  > server :: Server MyApi-  > server = streamBooks-  >   where streamBooks :: Handler (RecommendedEventSourceHeaders (SourceIO Book))-  >         streamBooks = pure $ recommendedEventSourceHeaders $ source [book1, ...]--}+-- | Recommended headers for Server-Sent Events. type RecommendedEventSourceHeaders (a :: Type) = Headers '[Header "X-Accel-Buffering" Text, Header "Cache-Control" Text] a  -- | Add the recommended headers for Server-Sent Events to the response. recommendedEventSourceHeaders :: a -> RecommendedEventSourceHeaders a recommendedEventSourceHeaders = addHeader @"X-Accel-Buffering" "no" . addHeader @"Cache-Control" "no-store" +-- | A framing strategy for Server-Sent Events. data ServerEventFraming +-- | Frames the server events by joining chunks with a newline. instance FramingRender ServerEventFraming where   framingRender _ f = fmap (\x -> f x <> "\n")--{- 1. Field names must not contain LF, CR or COLON characters.-   2. Values must not contain LF or CR characters.-      Multple consecutive `data:` fields will be joined with LFs on the client.--}-encodeServerEvent :: ServerEvent -> LBS.ByteString-encodeServerEvent e =-  optional "event:" (eventType e)-    <> optional "id:" (eventId e)-    <> mconcat (map (field "data:") (safelines (eventData e)))- where-  optional name = maybe mempty (field name)-  field name val = name <> val <> "\n"--  -- discard CR and split LFs into multiple data values-  safelines = C8.lines . C8.filter (/= '\r')