git.iko.soy/typeable/servant-reflex
on master help sign in
Add readme info for tags and .Multi, fix outdated client example
commitf5edec9a5835664264fc2783d2342f91eed71d4a authorGreg Hale <imalsogreg@gmail.com> dateSat 18 Mar 2017, 17:25 (10 years ago) parentbdc4dc3
1 file changed+54−5
README.md+54−5
README.md+54−5
@@ -2,7 +2,7 @@
22
33[![Build Status](https://travis-ci.org/imalsogreg/servant-reflex.svg?branch=master)](https://travis-ci.org/imalsogreg/servant-reflex)
44
5−## The problem `servant-reflex` solves
5+## `servant-reflex` lets you share your `servant` APIs with the frontend
66
77Keeping 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.
88Syncronization 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
3333 -- servant-reflex computes FRP functions for each API endpoint
3434 let (getint :<|> sayhi :<|> doubleit :<|> _) = client (Proxy :: Proxy API)
3535 (Proxy :: Proxy m)
36+ (Proxy :: Proxy ())
3637 (constDyn (BasePath "/"))
3738```
3839
@@ -42,7 +43,7 @@ These client functions are computed from your API type. They manage serializatio
4243 -- No need to write these functions. servant-reflex creates them for you!
4344 getint :: MonadWidget t m
4445 => Event t () -- ^ Trigger the XHR Request
45− -> m (Event t (ReqResult Int)) -- ^ Consume the answer
46+ -> m (Event t (ReqResult () Int)) -- ^ Consume the answer
4647
4748 sayhi :: MonadWidget t m
4849 => Dynamic t (QParam Text)
@@ -53,15 +54,15 @@ These client functions are computed from your API type. They manage serializatio
5354 -- ^ Flag for capitalizing the response
5455 -> Event t ()
5556 -- ^ Trigger the XHR Request
56− -> m (Event t (ReqResult Text))
57+ -> m (Event t (ReqResult () Text))
5758
5859 doubleit :: MonadWidget t m
5960 => Dynamic t (Either Text Double)
6061 -> Event t ()
61− -> m (Event t (ReqResult Double))
62+ -> m (Event t (ReqResult () Double))
6263```
6364
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:
6566
6667```haskell
6768 -- ... continued ...
@@ -136,3 +137,51 @@ dist/build/back/back -p 8001
136137And simply browse to `localhost:8001`
137138
138139**For a larger example of a project that shares types between backend and frontend, see [hsnippet](https://github.com/mightybyte/hsnippet).**
140+
141+
142+## Tagging requests
143+
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.
145+
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:
147+
148+```
149+ ...
150+ inp <- textInput def
151+
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"
155+
156+ -- Call 'doubleIt' with triggers that coincide with good input parses
157+ rs <- doubleIt inpNum (fforMaybe (tagPromptlyDyn inpNum go) $ \case
158+ Left _ -> Nothing
159+ Right a -> Just a
160+ )
161+
162+ -- Accumulate good responses in a map
163+ doubleTable <- foldDyn (<>) mempty $ ffor rs $ \case
164+ ResponseSuccess tag v _ -> tag =: v
165+ _ -> mempty
166+
167+ el "table" $ do
168+ listWithKey doubleTable $ \k dv -> el "tr" $ do
169+ el "td" $ text (T.pack $ show k)
170+ el "td" $ dynText (T.pack . show <$> dv)
171+ ...
172+```
173+
174+## Simultaneous requests
175+
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:
177+
178+```
179+sayHi
180+ :: Dynamic t (f (QParam Text))
181+ -> Dynamic t (f [Text])
182+ -> Dynamic t (f Bool)
183+ -> Event t tag
184+ -> m (Event t (f (ReqResult tag Text)))
185+```
186+
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.