reflex-gadt-api 0.2.2.1 → 0.2.2.2
raw patch · 4 files changed
+66/−51 lines, 4 filesdep ~aesondep ~basedep ~bytestringnew-uploaderPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: aeson, base, bytestring, containers, data-default, text
API changes (from Hackage documentation)
- Reflex.Dom.GadtApi.WebSocket: instance GHC.Generics.Generic Reflex.Dom.GadtApi.WebSocket.TaggedRequest
- Reflex.Dom.GadtApi.WebSocket: instance GHC.Generics.Generic Reflex.Dom.GadtApi.WebSocket.TaggedResponse
+ Readme: Dog :: Text -> UTCTime -> Bool -> Maybe Text -> Dog
+ Readme: Token :: Text -> Token
+ Readme: [CatApi_DogApi] :: forall a. Token -> DogApi a -> CatApi a
+ Readme: [CatApi_Identify] :: Text -> CatApi (Either Text Token)
+ Readme: [DogApi_GetByDay] :: Day -> DogApi [Dog]
+ Readme: [DogApi_GetByName] :: Text -> DogApi [Dog]
+ Readme: [DogApi_GetLastSeen] :: DogApi (Maybe Dog)
+ Readme: [DogApi_GetSuspiciousSightings] :: DogApi [Dog]
+ Readme: [DogApi_ReportSighting] :: Text -> Bool -> Maybe Text -> DogApi (Either Text ())
+ Readme: [_dog_imageUri] :: Dog -> Maybe Text
+ Readme: [_dog_name] :: Dog -> Text
+ Readme: [_dog_sighted] :: Dog -> UTCTime
+ Readme: [_dog_suspicious] :: Dog -> Bool
+ Readme: [unToken] :: Token -> Text
+ Readme: catnet :: forall t (m :: Type -> Type). (DomBuilder t m, MonadHold t m, MonadFix m, Prerender t m) => Token -> Catnet t m (Event t ())
+ Readme: data CatApi a
+ Readme: data Dog
+ Readme: data DogApi a
+ Readme: dogSighting :: forall t (m :: Type -> Type). (DomBuilder t m, MonadHold t m, PostBuild t m, Prerender t m, MonadFix m) => Token -> Catnet t m (Event t (Either Text ()))
+ Readme: instance (c (GHC.Internal.Data.Either.Either Data.Text.Internal.Text Readme.Token), Data.Constraint.Extras.Has c Readme.DogApi) => Data.Constraint.Extras.Has c Readme.CatApi
+ Readme: instance (c [Readme.Dog], c [Readme.Dog], c (GHC.Internal.Maybe.Maybe Readme.Dog), c (GHC.Internal.Data.Either.Either Data.Text.Internal.Text ()), c [Readme.Dog]) => Data.Constraint.Extras.Has c Readme.DogApi
+ Readme: instance Data.Aeson.Types.FromJSON.FromJSON Data.Text.Internal.Text => Data.Aeson.Types.FromJSON.FromJSON (Data.Some.Newtype.Some Readme.CatApi)
+ Readme: instance Data.Aeson.Types.FromJSON.FromJSON Data.Text.Internal.Text => Data.Aeson.Types.FromJSON.FromJSON (Data.Some.Newtype.Some Readme.DogApi)
+ Readme: instance Data.Aeson.Types.FromJSON.FromJSON Readme.Dog
+ Readme: instance Data.Aeson.Types.FromJSON.FromJSON Readme.Token
+ Readme: instance Data.Aeson.Types.ToJSON.ToJSON Data.Text.Internal.Text => Data.Aeson.Types.ToJSON.ToJSON (Readme.CatApi a)
+ Readme: instance Data.Aeson.Types.ToJSON.ToJSON Data.Text.Internal.Text => Data.Aeson.Types.ToJSON.ToJSON (Readme.DogApi a)
+ Readme: instance Data.Aeson.Types.ToJSON.ToJSON Readme.Dog
+ Readme: instance Data.Aeson.Types.ToJSON.ToJSON Readme.Token
+ Readme: instance GHC.Internal.Generics.Generic Readme.Dog
+ Readme: instance GHC.Internal.Generics.Generic Readme.Token
+ Readme: login :: forall t (m :: Type -> Type). (DomBuilder t m, MonadHold t m, MonadFix m, Prerender t m) => Catnet t m (Event t Token)
+ Readme: main :: IO ()
+ Readme: newtype Token
+ Readme: requestingJs :: forall t (m :: Type -> Type) a. (Reflex t, MonadFix m, Prerender t m) => Event t (Request (Client (Catnet t m)) a) -> Catnet t m (Event t (Response (Client (Catnet t m)) a))
+ Readme: showDog :: DomBuilder t m => Dog -> m ()
+ Readme: startCatnet :: (Prerender t m, MonadHold t m, MonadIO (Performable m), MonadFix m, DomBuilder t m, PostBuild t m, TriggerEvent t m, PerformEvent t m, Has FromJSON CatApi, forall a. () => ToJSON (CatApi a)) => Either ApiEndpoint WebSocketEndpoint -> m ()
+ Readme: type Catnet t (m :: Type -> Type) = RequesterT t CatApi Either Text m
+ Reflex.Dom.GadtApi.WebSocket: instance GHC.Internal.Generics.Generic Reflex.Dom.GadtApi.WebSocket.TaggedRequest
+ Reflex.Dom.GadtApi.WebSocket: instance GHC.Internal.Generics.Generic Reflex.Dom.GadtApi.WebSocket.TaggedResponse
- Reflex.Dom.GadtApi.WebSocket: mkTaggedResponse :: (Monad m, FromJSON (Some f), Has ToJSON f) => TaggedRequest -> (forall a. f a -> m a) -> m (Either String TaggedResponse)
+ Reflex.Dom.GadtApi.WebSocket: mkTaggedResponse :: (Monad m, FromJSON (Some f), Has ToJSON f) => TaggedRequest -> (forall a. () => f a -> m a) -> m (Either String TaggedResponse)
- Reflex.Dom.GadtApi.WebSocket: performWebSocketRequests :: forall req t m. (Prerender t m, Applicative m, FromJSON (Some req), forall a. ToJSON (req a), Has FromJSON req) => WebSocketEndpoint -> Event t (RequesterData req) -> m (Event t (RequesterData (Either Text)))
+ Reflex.Dom.GadtApi.WebSocket: performWebSocketRequests :: forall (req :: Type -> Type) t m. (Prerender t m, Applicative m, FromJSON (Some req), forall a. () => ToJSON (req a), Has FromJSON req) => WebSocketEndpoint -> Event t (RequesterData req) -> m (Event t (RequesterData (Either Text)))
- Reflex.Dom.GadtApi.WebSocket: tagRequests :: forall req t m. (Applicative m, FromJSON (Some req), forall a. ToJSON (req a), Has FromJSON req, Monad m, MonadFix m, Reflex t, MonadHold t m) => Event t (RequesterData req) -> Event t TaggedResponse -> m (Event t [TaggedRequest], Event t (RequesterData (Either Text)))
+ Reflex.Dom.GadtApi.WebSocket: tagRequests :: forall (req :: Type -> Type) t m. (Applicative m, FromJSON (Some req), forall a. () => ToJSON (req a), Has FromJSON req, Monad m, MonadFix m, Reflex t, MonadHold t m) => Event t (RequesterData req) -> Event t TaggedResponse -> m (Event t [TaggedRequest], Event t (RequesterData (Either Text)))
- Reflex.Dom.GadtApi.XHR: apiRequestXhr :: forall api m. (MonadIO m, MonadJSM m, Has FromJSON api, forall a. ToJSON (api a)) => ApiEndpoint -> RequesterData api -> m (RequesterData (Either Text))
+ Reflex.Dom.GadtApi.XHR: apiRequestXhr :: forall (api :: Type -> Type) m. (MonadIO m, MonadJSM m, Has FromJSON api, forall a. () => ToJSON (api a)) => ApiEndpoint -> RequesterData api -> m (RequesterData (Either Text))
- Reflex.Dom.GadtApi.XHR: performXhrRequests :: forall t m api. (Has FromJSON api, forall a. ToJSON (api a), Prerender t m, Applicative m) => ApiEndpoint -> Event t (RequesterData api) -> m (Event t (RequesterData (Either Text)))
+ Reflex.Dom.GadtApi.XHR: performXhrRequests :: forall t m (api :: Type -> Type). (Has FromJSON api, forall a. () => ToJSON (api a), Prerender t m, Applicative m) => ApiEndpoint -> Event t (RequesterData api) -> m (Event t (RequesterData (Either Text)))
Files
- ChangeLog.md +10/−1
- Readme.lhs +22/−20
- Readme.md +22/−20
- reflex-gadt-api.cabal +12/−10
ChangeLog.md view
@@ -1,9 +1,18 @@ # Revision history for reflex-gadt-api +## 0.2.2.2++* Make Readme part of the library+* Support GHC 9.10+* Loosen version bounds++## 0.2.2.1-r1++* Loosen version bounds+ ## 0.2.2.1 * Support constraints-extras 0.4-* Update obelisk version used by example ## 0.2.2.0
Readme.lhs view
@@ -1,9 +1,10 @@ reflex-gadt-api ===============-[](https://haskell.org) [](https://hackage.haskell.org/package/reflex-gadt-api) [](https://matrix.hackage.haskell.org/#/package/reflex-gadt-api) [](https://travis-ci.org/reflex-frp/reflex-gadt-api) [](https://github.com/reflex-frp/reflex-gadt-api/actions) [](https://github.com/reflex-frp/reflex-gadt-api/blob/master/LICENSE) +[](https://haskell.org) [](https://hackage.haskell.org/package/reflex-gadt-api) [](https://github.com/reflex-frp/reflex-gadt-api/actions) [](https://github.com/reflex-frp/reflex-gadt-api/blob/master/LICENSE) + This package is designed to be used in full-stack Haskell applications where the API is defined as a GADT, the wire format is JSON, and the frontend is using reflex-dom(-core). reflex-gadt-api provides the basic FRP and encoding/decoding infrastructure to support this architecture. To serialize the GADT API definition, we use [aeson-gadt-th](https://github.com/obsidiansystems/aeson-gadt-th) with a little help from [constraints-extras](https://github.com/obsidiansystems/constraints-extras).@@ -29,9 +30,9 @@ > {-# LANGUAGE TemplateHaskell #-} > {-# LANGUAGE TypeFamilies #-} > {-# LANGUAGE UndecidableInstances #-}->+ > module Readme where->+ > import Control.Monad (void) > import Control.Monad.IO.Class (MonadIO) > import Control.Monad.Fix (MonadFix)@@ -39,13 +40,14 @@ > import Data.Aeson.GADT.TH (deriveJSONGADT) > import Data.Constraint.Extras (Has) > import Data.Constraint.Extras.TH (deriveArgDict)+> import Data.Kind (Type) > import Data.Text as T (Text, null, pack) > import Data.Time (UTCTime, Day) > import GHC.Generics (Generic) > import Reflex.Dom.Core > import Reflex.Dom.GadtApi-> + ``` The code that follows would typically go in a common module, since it would be used on the frontend and on the backend. It sets up the basic data type definitions and a couple of GADTs that describe the API.@@ -59,31 +61,31 @@ > , _dog_imageUri :: Maybe Text > } > deriving (Generic)->+ > instance ToJSON Dog > instance FromJSON Dog-> + ``` Here we have an API for retrieving and interacting with the `Dog` data: ```haskell -> data DogApi :: * -> * where+> data DogApi :: Type -> Type where > DogApi_GetByDay :: Day -> DogApi [Dog] > DogApi_GetByName :: Text -> DogApi [Dog] > DogApi_GetLastSeen :: DogApi (Maybe Dog) > DogApi_ReportSighting :: Text -> Bool -> Maybe Text -> DogApi (Either Text ()) > DogApi_GetSuspiciousSightings :: DogApi [Dog]->+ > newtype Token = Token { unToken :: Text } > deriving (Generic)->+ > instance ToJSON Token > instance FromJSON Token-> + ``` We can take the `DogApi` and embed it in another GADT API. This outer API will handle authentication. (Note that we're not actually implementing a secure authentication scheme or anything here. This is just a toy example.)@@ -93,13 +95,13 @@ > data CatApi a where > CatApi_Identify :: Text -> CatApi (Either Text Token) > CatApi_DogApi :: Token -> DogApi a -> CatApi a->+ > deriveJSONGADT ''DogApi > deriveJSONGADT ''CatApi > deriveArgDict ''DogApi > deriveArgDict ''CatApi-> + ``` On the frontend, we'll run a `RequesterT` widget that allows us to emit an event of requests, and we'll transform those requests into XHR calls to the API endpoint.@@ -107,8 +109,8 @@ ```haskell > type Catnet t m = (RequesterT t CatApi (Either Text) m)-> + ``` This synonym is just here for convenience. The right-hand-side describes a `RequesterT` widget that issues `CatApi` requests and receives responses. In other words, when we're inside this `RequesterT` we can call the `requesting` function to send API requests and receive responses.@@ -177,8 +179,8 @@ > rsp <- dogSighting token > leave <- delay 3 rsp > pure ((), catnetW token <$ leave) -- Go back to catnet-> + ``` If you're building your frontend in a context where the user interface needs to be susceptible to server-side rendering (for example, if you're using [obelisk](https://github.com/obsidiansystems/obelisk)'s "prerendering" functionality to serve static pages that are "hydrated" once the JS loads), you'll need to wrap any code relying on Javascript (e.g., your XHR requests) in a `prerender`. The function below does this for us.@@ -190,8 +192,8 @@ > => Event t (Request (Client (Catnet t m)) a) > -> Catnet t m (Event t (Response (Client (Catnet t m)) a)) > requestingJs r = fmap (switch . current) $ prerender (pure never) $ requesting r-> + ``` On the login page, we construct a `Behavior` of a `CatApi_Identify` request and send it when the user clicks the submit button.@@ -204,7 +206,7 @@ > :: (DomBuilder t m, MonadHold t m, MonadFix m, Prerender t m) > => Catnet t m (Event t Token) > login = do-> el "h1" $ text "Identify Yourself"+> el "h1" $ text "Identify Yourself, Cat" > cat <- inputElement def > click <- button "submit" > rsp <- requestingJs $@@ -213,8 +215,8 @@ > Right (Right catToken) -> Just catToken > _ -> Nothing > pure token-> + ``` This function builds a UI with a few buttons. Depending on which button is clicked, we'll issue an API request and display the result, with one exception: the "Report a Sighting" button click event is returned (and used by the `Workflow` above to navigate to another page).@@ -245,8 +247,8 @@ > Right dogs -> el "ul" $ mapM_ (el "li" . showDog) dogs > -- Return the "Report a sighting" click event > pure addDog-> + ``` The `showDog` widget below does not have `Catnet` or `RequesterT` in its type signature, so we know that it doesn't issue requests. It just builds a display widget for a particular `Dog`.@@ -267,8 +269,8 @@ > el "dd" $ case _dog_imageUri dog of > Just img -> elAttr "img" ("src" =: img <> "alt" =: "dog mugshot") blank > Nothing -> text "None"-> + ``` This rudimentary form allows us to assemble a `Dynamic` `DogApi` request, embed it in a `CatApi` request, and send it.@@ -317,7 +319,7 @@ > Right (Left err) -> Left err > Left err -> Left err > Right (Right result) -> Right result->+ > main :: IO () > main = return ()
Readme.md view
@@ -1,9 +1,10 @@ reflex-gadt-api ===============-[](https://haskell.org) [](https://hackage.haskell.org/package/reflex-gadt-api) [](https://matrix.hackage.haskell.org/#/package/reflex-gadt-api) [](https://travis-ci.org/reflex-frp/reflex-gadt-api) [](https://github.com/reflex-frp/reflex-gadt-api/actions) [](https://github.com/reflex-frp/reflex-gadt-api/blob/master/LICENSE) +[](https://haskell.org) [](https://hackage.haskell.org/package/reflex-gadt-api) [](https://github.com/reflex-frp/reflex-gadt-api/actions) [](https://github.com/reflex-frp/reflex-gadt-api/blob/master/LICENSE) + This package is designed to be used in full-stack Haskell applications where the API is defined as a GADT, the wire format is JSON, and the frontend is using reflex-dom(-core). reflex-gadt-api provides the basic FRP and encoding/decoding infrastructure to support this architecture. To serialize the GADT API definition, we use [aeson-gadt-th](https://github.com/obsidiansystems/aeson-gadt-th) with a little help from [constraints-extras](https://github.com/obsidiansystems/constraints-extras).@@ -29,9 +30,9 @@ > {-# LANGUAGE TemplateHaskell #-} > {-# LANGUAGE TypeFamilies #-} > {-# LANGUAGE UndecidableInstances #-}->+ > module Readme where->+ > import Control.Monad (void) > import Control.Monad.IO.Class (MonadIO) > import Control.Monad.Fix (MonadFix)@@ -39,13 +40,14 @@ > import Data.Aeson.GADT.TH (deriveJSONGADT) > import Data.Constraint.Extras (Has) > import Data.Constraint.Extras.TH (deriveArgDict)+> import Data.Kind (Type) > import Data.Text as T (Text, null, pack) > import Data.Time (UTCTime, Day) > import GHC.Generics (Generic) > import Reflex.Dom.Core > import Reflex.Dom.GadtApi-> + ``` The code that follows would typically go in a common module, since it would be used on the frontend and on the backend. It sets up the basic data type definitions and a couple of GADTs that describe the API.@@ -59,31 +61,31 @@ > , _dog_imageUri :: Maybe Text > } > deriving (Generic)->+ > instance ToJSON Dog > instance FromJSON Dog-> + ``` Here we have an API for retrieving and interacting with the `Dog` data: ```haskell -> data DogApi :: * -> * where+> data DogApi :: Type -> Type where > DogApi_GetByDay :: Day -> DogApi [Dog] > DogApi_GetByName :: Text -> DogApi [Dog] > DogApi_GetLastSeen :: DogApi (Maybe Dog) > DogApi_ReportSighting :: Text -> Bool -> Maybe Text -> DogApi (Either Text ()) > DogApi_GetSuspiciousSightings :: DogApi [Dog]->+ > newtype Token = Token { unToken :: Text } > deriving (Generic)->+ > instance ToJSON Token > instance FromJSON Token-> + ``` We can take the `DogApi` and embed it in another GADT API. This outer API will handle authentication. (Note that we're not actually implementing a secure authentication scheme or anything here. This is just a toy example.)@@ -93,13 +95,13 @@ > data CatApi a where > CatApi_Identify :: Text -> CatApi (Either Text Token) > CatApi_DogApi :: Token -> DogApi a -> CatApi a->+ > deriveJSONGADT ''DogApi > deriveJSONGADT ''CatApi > deriveArgDict ''DogApi > deriveArgDict ''CatApi-> + ``` On the frontend, we'll run a `RequesterT` widget that allows us to emit an event of requests, and we'll transform those requests into XHR calls to the API endpoint.@@ -107,8 +109,8 @@ ```haskell > type Catnet t m = (RequesterT t CatApi (Either Text) m)-> + ``` This synonym is just here for convenience. The right-hand-side describes a `RequesterT` widget that issues `CatApi` requests and receives responses. In other words, when we're inside this `RequesterT` we can call the `requesting` function to send API requests and receive responses.@@ -177,8 +179,8 @@ > rsp <- dogSighting token > leave <- delay 3 rsp > pure ((), catnetW token <$ leave) -- Go back to catnet-> + ``` If you're building your frontend in a context where the user interface needs to be susceptible to server-side rendering (for example, if you're using [obelisk](https://github.com/obsidiansystems/obelisk)'s "prerendering" functionality to serve static pages that are "hydrated" once the JS loads), you'll need to wrap any code relying on Javascript (e.g., your XHR requests) in a `prerender`. The function below does this for us.@@ -190,8 +192,8 @@ > => Event t (Request (Client (Catnet t m)) a) > -> Catnet t m (Event t (Response (Client (Catnet t m)) a)) > requestingJs r = fmap (switch . current) $ prerender (pure never) $ requesting r-> + ``` On the login page, we construct a `Behavior` of a `CatApi_Identify` request and send it when the user clicks the submit button.@@ -204,7 +206,7 @@ > :: (DomBuilder t m, MonadHold t m, MonadFix m, Prerender t m) > => Catnet t m (Event t Token) > login = do-> el "h1" $ text "Identify Yourself"+> el "h1" $ text "Identify Yourself, Cat" > cat <- inputElement def > click <- button "submit" > rsp <- requestingJs $@@ -213,8 +215,8 @@ > Right (Right catToken) -> Just catToken > _ -> Nothing > pure token-> + ``` This function builds a UI with a few buttons. Depending on which button is clicked, we'll issue an API request and display the result, with one exception: the "Report a Sighting" button click event is returned (and used by the `Workflow` above to navigate to another page).@@ -245,8 +247,8 @@ > Right dogs -> el "ul" $ mapM_ (el "li" . showDog) dogs > -- Return the "Report a sighting" click event > pure addDog-> + ``` The `showDog` widget below does not have `Catnet` or `RequesterT` in its type signature, so we know that it doesn't issue requests. It just builds a display widget for a particular `Dog`.@@ -267,8 +269,8 @@ > el "dd" $ case _dog_imageUri dog of > Just img -> elAttr "img" ("src" =: img <> "alt" =: "dog mugshot") blank > Nothing -> text "None"-> + ``` This rudimentary form allows us to assemble a `Dynamic` `DogApi` request, embed it in a `CatApi` request, and send it.@@ -317,7 +319,7 @@ > Right (Left err) -> Left err > Left err -> Left err > Right (Right result) -> Right result->+ > main :: IO () > main = return ()
reflex-gadt-api.cabal view
@@ -1,12 +1,12 @@ cabal-version: >=1.10 name: reflex-gadt-api-version: 0.2.2.1+version: 0.2.2.2 synopsis: Interact with a GADT API in your reflex-dom application. description: This package is designed to be used in full-stack Haskell applications where the API is defined as a GADT and the frontend is using reflex-dom. bug-reports: https://github.com/reflex-frp/reflex-gadt-api/issues-license: BSD3 +license: BSD3 license-file: LICENSE author: Obsidian Systems maintainer: maintainer@obsidian.systems@@ -20,26 +20,28 @@ tested-with: GHC ==8.6.5 || ==8.8.1 || ==8.10.7 library- hs-source-dirs: src+ hs-source-dirs: src, . build-depends:- aeson >=1.4.4 && <1.6+ aeson >=1.4.4 && <2.3 , aeson-gadt-th >=0.2.4 && <0.3- , base >=4.12 && <4.16- , bytestring >=0.10.8 && <0.11+ , base >=4.12 && <4.21+ , bytestring >=0.10.8 && <0.13 , constraints-extras >=0.3.0 && <0.5- , containers >=0.6 && <0.7- , data-default >=0.6 && <0.8+ , containers >=0.6 && <0.8+ , data-default >=0.6 && <0.9 , jsaddle >=0.9.7 && <0.10- , reflex >=0.7 && <0.9+ , reflex >=0.7 && <1 , reflex-dom-core >=0.7.0 && <0.9 , some >=1 && <1.1- , text >=1.2 && <1.3+ , text >=1.2 && <2.2 , time >=1.6.0 && <2 exposed-modules: Reflex.Dom.GadtApi Reflex.Dom.GadtApi.WebSocket Reflex.Dom.GadtApi.XHR++ Readme default-language: Haskell2010 ghc-options: -Wall