@@ -2,7 +2,7 @@
3[](https://travis-ci.org/imalsogreg/servant-reflex)
3[](https://travis-ci.org/imalsogreg/servant-reflex)
5−## The problem `servant-reflex` solves
5+## `servant-reflex` lets you share your `servant` APIs with the frontend
7Keeping your frontend in sync with your API server can be difficult - when the API changes its input parameters or return type, XHR requests from the frontend will fail at runtime. If your API is defined by [servant](haskell-servant.readthedocs.io) combinators, you can use `servant-reflex` to share the API between the server and frontend.
7Keeping your frontend in sync with your API server can be difficult - when the API changes its input parameters or return type, XHR requests from the frontend will fail at runtime. If your API is defined by [servant](haskell-servant.readthedocs.io) combinators, you can use `servant-reflex` to share the API between the server and frontend.
8Syncronization between is checked at compile time, and rather than building XHR requests by hand, API endpoints are available behind `reflex`'s FRP semantics.
8Syncronization between is checked at compile time, and rather than building XHR requests by hand, API endpoints are available behind `reflex`'s FRP semantics.
@@ -33,6 +33,7 @@ type API = "getint" :> Get '[JSON] Int
33 -- servant-reflex computes FRP functions for each API endpoint
33 -- servant-reflex computes FRP functions for each API endpoint
34 let (getint :<|> sayhi :<|> doubleit :<|> _) = client (Proxy :: Proxy API)
34 let (getint :<|> sayhi :<|> doubleit :<|> _) = client (Proxy :: Proxy API)
35 (Proxy :: Proxy m)
35 (Proxy :: Proxy m)
36 (constDyn (BasePath "/"))
37 (constDyn (BasePath "/"))
@@ -42,7 +43,7 @@ These client functions are computed from your API type. They manage serializatio
42 -- No need to write these functions. servant-reflex creates them for you!
43 -- No need to write these functions. servant-reflex creates them for you!
43 getint :: MonadWidget t m
44 getint :: MonadWidget t m
44 => Event t () -- ^ Trigger the XHR Request
45 => Event t () -- ^ Trigger the XHR Request
45− -> m (Event t (ReqResult Int)) -- ^ Consume the answer
46+ -> m (Event t (ReqResult () Int)) -- ^ Consume the answer
47 sayhi :: MonadWidget t m
48 sayhi :: MonadWidget t m
48 => Dynamic t (QParam Text)
49 => Dynamic t (QParam Text)
@@ -53,15 +54,15 @@ These client functions are computed from your API type. They manage serializatio
53 -- ^ Flag for capitalizing the response
54 -- ^ Flag for capitalizing the response
54 -> Event t ()
55 -> Event t ()
55 -- ^ Trigger the XHR Request
56 -- ^ Trigger the XHR Request
56− -> m (Event t (ReqResult Text))
57+ -> m (Event t (ReqResult () Text))
58 doubleit :: MonadWidget t m
59 doubleit :: MonadWidget t m
59 => Dynamic t (Either Text Double)
60 => Dynamic t (Either Text Double)
60 -> Event t ()
61 -> Event t ()
61− -> m (Event t (ReqResult Double))
62+ -> m (Event t (ReqResult () Double))
64−`ReqResult a` is defined in [`Servant.Common.Req`](https://github.com/imalsogreg/servant-reflex/blob/6d866e338edb9bf6fd8f8d5083ff0187b4d8c0d2/src/Servant/Common/Req.hs#L40-L42) and reports whether or not your request was sent (if validation fails, the request won't be sent), and how decoding of the response went. You can pattern match on these explicitly, but usually you'll want to use `fmapMaybe :: (a -> Maybe b) -> Event t a -> Event t b` and one of the elimination functions to filter the result type you care about, like this:
65+`ReqResult tag a` is defined in [`Servant.Common.Req`](https://github.com/imalsogreg/servant-reflex/blob/6d866e338edb9bf6fd8f8d5083ff0187b4d8c0d2/src/Servant/Common/Req.hs#L40-L42) and reports whether or not your request was sent (if validation fails, the request won't be sent), and how decoding of the response went. You can pattern match on these explicitly, but usually you'll want to use `fmapMaybe :: (a -> Maybe b) -> Event t a -> Event t b` and one of the elimination functions to filter the result type you care about, like this:
67 -- ... continued ...
68 -- ... continued ...
@@ -136,3 +137,51 @@ dist/build/back/back -p 8001
136And simply browse to `localhost:8001`
137And simply browse to `localhost:8001`
138**For a larger example of a project that shares types between backend and frontend, see [hsnippet](https://github.com/mightybyte/hsnippet).**
139**For a larger example of a project that shares types between backend and frontend, see [hsnippet](https://github.com/mightybyte/hsnippet).**
144+The input and the return type of a client function like `getDouble` are both event streams. The individual input events and responses occur at different times and aren't automatically paired up, but you can recover the relationship by tagging the requests.
146+So far we have used an `Event t ()` to trigger sending a request. If we choose e.g. `Double` for the third `Proxy` argument to `client`, then `Event t Double` will be used to trigger requests, and each `ReqResult` will carry the tag of its request. Imagine we wanted to display not just the last "double" from `doubleIt`, but a whole table of valid inputs and their doubled responses:
150+ inp <- textInput def
152+ -- Convert the raw text into an `Either Text Double`
153+ let inpNum = maybe (Left "No Parse") Right . readMaybe . T.unpack <$> value inp
154+ go <- button "Double It"
156+ -- Call 'doubleIt' with triggers that coincide with good input parses
157+ rs <- doubleIt inpNum (fforMaybe (tagPromptlyDyn inpNum go) $ \case
162+ -- Accumulate good responses in a map
163+ doubleTable <- foldDyn (<>) mempty $ ffor rs $ \case
164+ ResponseSuccess tag v _ -> tag =: v
168+ listWithKey doubleTable $ \k dv -> el "tr" $ do
169+ el "td" $ text (T.pack $ show k)
170+ el "td" $ dynText (T.pack . show <$> dv)
174+## Simultaneous requests
176+'Servant.Reflex.Multi' provides an alternative client-generation function called 'clientA' (client applicative). Choose a container type that has both `Applicative` and `Traversable` instances, and pass it to `clientA` through another 'Proxy'. Our `sayHi` client function will then have this type:
180+ :: Dynamic t (f (QParam Text))
181+ -> Dynamic t (f [Text])
182+ -> Dynamic t (f Bool)
184+ -> m (Event t (f (ReqResult tag Text)))
187+The dynamic params are each wrapped in `f`. For every firing of the trigger event `tag`, all of these parameters will be combined according to `f`'s applicative instance (when `f` is `[]`, you will get all combinations of all parameters taken together as a request; when `f` is 'ZipList', the Nth elemens of each parameters list will be taken together as a request). Using this interface, you can trigger many XHR's from a single event occurence, and expect the responses to be structured the same way as the requests.