@@ -4,19 +4,14 @@
5## `servant-reflex` lets you share your `servant` APIs with the frontend
5## `servant-reflex` lets you share your `servant` APIs with the frontend
7−Keeping your frontend in sync with your API server can be difficult - when the
7+Keeping 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.
8−API changes its input parameters or return type, XHR requests from the
9−frontend will fail at runtime. If your API is defined by
10−[servant](https://haskell-servant.readthedocs.io) combinators, you can use
11−`servant-reflex` to share the API between the server and frontend.
12−Syncronization between is checked at compile time, and rather than building XHR
8+Syncronization between is checked at compile time, and rather than building XHR requests by hand, API endpoints are available behind `reflex`'s FRP semantics.
13−requests by hand, API endpoints are available behind `reflex`'s FRP semantics.
9+
18−We have a webservice API defined in a module where both the server (compiled
14+We have a webservice API defined in a module where both the server (compiled with ghc) and the frontend (compiled with ghcjs) can see it:
19−with ghc) and the frontend (compiled with ghcjs) can see it:
22type API = "getint" :> Get '[JSON] Int
17type API = "getint" :> Get '[JSON] Int
@@ -29,8 +24,7 @@ type API = "getint" :> Get '[JSON] Int
32−`servant-reflex` then computes client functions that can query the API through
27+`servant-reflex` then computes client functions that can query the API through an `XhrRequest`.
@@ -43,16 +37,7 @@ an `XhrRequest`.
43 (constDyn (BasePath "/"))
37 (constDyn (BasePath "/"))
46−These client functions are computed from your API type. They manage
40+These client functions are computed from your API type. They manage serialization, `XhrRequest` generation, and deserialization for you. `a` parameters used in URL captures become `Dynamic t (Either Text a)` parameters in the client functions. `QueryFlag`, `QueryParams` and `QueryParam` API parameters map to `Dynamic t Bool`, `Dynamic t [a]` and `Dynamic t (QParam a)` respectively. These parameters to the client function are wrapped with failure possibility to allow you to indicate at any time whether input validation for that parameter has failed and no valid XHR request can be generated. The final parameter is a trigger event for the XHR request. The return value `Event t (ReqResult a)` contains responses from the API server.
47−serialization, `XhrRequest` generation, and deserialization for you. `a`
48−parameters used in URL captures become `Dynamic t (Either Text a)` parameters
49−in the client functions. `QueryFlag`, `QueryParams` and `QueryParam` API
50−parameters map to `Dynamic t Bool`, `Dynamic t [a]` and `Dynamic t (QParam a)`
51−respectively. These parameters to the client function are wrapped with failure
52−possibility to allow you to indicate at any time whether input validation for
53−that parameter has failed and no valid XHR request can be generated. The final
54−parameter is a trigger event for the XHR request. The return value
55−`Event t (ReqResult a)` contains responses from the API server.
58 -- 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!
@@ -77,13 +62,7 @@ parameter is a trigger event for the XHR request. The return value
77 -> m (Event t (ReqResult () Double))
62 -> m (Event t (ReqResult () Double))
80−`ReqResult tag a` is defined in
81−[`Servant.Common.Req`](https://github.com/imalsogreg/servant-reflex/blob/6d866e338edb9bf6fd8f8d5083ff0187b4d8c0d2/src/Servant/Common/Req.hs#L40-L42)
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:
82−and reports whether or not your request was sent (if validation fails, the
83−request won't be sent), and how decoding of the response went. You can pattern
84−match on these explicitly, but usually you'll want to use
85−`fmapMaybe :: (a -> Maybe b) -> Event t a -> Event t b` and one of the
86−elimination functions to filter the result type you care about, like this:
89 -- ... continued ...
68 -- ... continued ...
@@ -101,8 +80,7 @@ elimination functions to filter the result type you care about, like this:
101 dynText =<< holdDyn "" (leftmost [errs, const "" <$> ys])
80 dynText =<< holdDyn "" (leftmost [errs, const "" <$> ys])
104−This example builds some input fields to enter API parameters, buttons to
83+This example builds some input fields to enter API parameters, buttons to trigger the API calls, and text elements to show the results:
105−trigger the API calls, and text elements to show the results:
107```haskell
85```haskell
108 elClass "div" "int-demo" $ do
86 elClass "div" "int-demo" $ do
@@ -125,53 +103,47 @@ trigger the API calls, and text elements to show the results:
125 display =<< holdDyn Nothing outputDouble
103 display =<< holdDyn Nothing outputDouble
128−For a great introduction to recative DOM building, see the
106+For a great introduction to recative DOM building, see the [README](https://github.com/reflex-frp/reflex-platform) for the `reflex-platform`. For more information about servant, see their [documentation](http://haskell-servant.readthedocs.io/en/stable/). Thanks to the respective authors of these fabulous libraries.
129−[README](https://github.com/reflex-frp/reflex-platform) for the
130−`reflex-platform`. For more information about servant, see their
131−[documentation](http://haskell-servant.readthedocs.io/en/stable/). Thanks to
132−the respective authors of these fabulous libraries.
135## Building the library and test server
109## Building the library and test server
137−This repository comes with a small example of an API shared between a
138−ghcjs-compiled frontend
139−([exec/](https://github.com/imalsogreg/servant-reflex/tree/master/exec)) and a
141−([testserver/](https://github.com/imalsogreg/servant-reflex/tree/master/testserver).
111+This repository comes with a small example of an API shared between a ghcjs-compiled frontend ([exec/](https://github.com/imalsogreg/servant-reflex/tree/master/exec)) and a ghc-compiled backend ([testserver/](https://github.com/imalsogreg/servant-reflex/tree/master/testserver). To build these components:
142−To build these components, we will use the nix packages for the testserver. It
112+
143−manages the frontend build as a dependency.
113+
114+First build the library:
146−nix build -f travis.nix testserver -o server
117+git submodule update --init --recursive
149−Point it at the static directory and run it:
121+Then build the test server:
124+deps/reflex-platform/work-on ./overrides-ghc.nix ./testserver --command "cd testserver && cabal build"
128+## Running the example site
130+The server must be run from the directory where static assets live:
152−STATIC_DIR=server/static server/back -p 8000
133+cd testserver
134+dist/build/back/back -p 8001
155−Browse to [http://localhost:8000](http://localhost:8000) and click around.
137+And simply browse to `localhost:8001`
156−Open the firefox or chrome developer tools and examine the XHR activity
157−under the Network tab.
159−**For a larger example of a project that shares types between backend and
139+**For a larger example of a project that shares types between backend and frontend, see [hsnippet](https://github.com/mightybyte/hsnippet).**
160−frontend, see [hsnippet](https://github.com/mightybyte/hsnippet).**
163## Tagging requests
142## Tagging requests
165−The input and the return type of a client function like `getDouble` are both
166−event streams. The individual input events and responses occur at different
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.
167−times and aren't automatically paired up, but you can recover the
168−relationship by tagging the requests.
170−So far we have used an `Event t ()` to trigger sending a request. If we choose
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:
171−e.g. `Double` for the third `Proxy` argument to `client`, then `Event t Double`
172−will be used to trigger requests, and each `ReqResult` will carry the tag of
173−its request. Imagine we wanted to display not just the last "double" from
174−`doubleIt`, but a whole table of valid inputs and their doubled responses:
176```haskell
148```haskell
@@ -201,10 +173,7 @@ its request. Imagine we wanted to display not just the last "double" from
202## Simultaneous requests
174## Simultaneous requests
204−`Servant.Reflex.Multi` provides an alternative client-generation function
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:
205−called `clientA` (client applicative). Choose a container type that has both
206−`Applicative` and `Traversable` instances, and pass it to `clientA` through
207−another `Proxy`. Our `sayHi` client function will then have this type:
209```haskell
178```haskell
@@ -215,10 +184,4 @@ sayHi
215 -> m (Event t (f (ReqResult tag Text)))
184 -> m (Event t (f (ReqResult tag Text)))
218−The dynamic params are each wrapped in `f`. For every firing of the trigger
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.
219−event `tag`, all of these parameters will be combined according to `f`'s
220−`Applicative` instance (when `f` is `[]`, you will get all combinations of
221−all parameters taken together as a request; when `f` is `ZipList`, the Nth
222−elemens of each parameters list will be taken together as a request). Using
223−this interface, you can trigger many XHR's from a single event occurence, and
224−expect the responses to be structured the same way as the requests.