├── .all-contributorsrc ├── .gitattributes ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── bsconfig.json ├── commitlint.config.js ├── docs ├── ReasonApolloHooks │ ├── ApolloHooks │ │ └── index.html │ ├── ApolloHooksMutation │ │ └── index.html │ ├── ApolloHooksProvider │ │ └── index.html │ ├── ApolloHooksQuery │ │ └── index.html │ ├── ApolloHooksSubscription │ │ └── index.html │ ├── ApolloHooksTypes │ │ └── index.html │ └── index.html ├── highlight.pack.js ├── index.html └── odoc.css ├── examples └── persons │ ├── .gitignore │ ├── README.md │ ├── bsconfig.json │ ├── build │ ├── Index.js │ └── index.html │ ├── graphql_schema.json │ ├── package.json │ ├── src │ ├── AddPerson.re │ ├── Client.re │ ├── EditPerson.re │ ├── FilterByAge.re │ ├── FilterByAgeErrorHandling.re │ ├── FilterByNameCache.re │ ├── Index.re │ ├── LoadMore.re │ ├── Persons.re │ ├── Root.re │ ├── SubscribeToMore.re │ ├── fragments │ │ ├── FilterByAgeFragment.re │ │ ├── Fragments.re │ │ └── LoadMoreFragments.re │ ├── index.html │ └── styles.css │ ├── webpack.config.js │ └── yarn.lock ├── package.json ├── src ├── ApolloHooks.re ├── ApolloHooksMutation.re ├── ApolloHooksProvider.re ├── ApolloHooksQuery.re ├── ApolloHooksSubscription.re ├── ApolloHooksTypes.re └── index.mld └── yarn.lock /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "fakenickels", 10 | "name": "Gabriel Rubens", 11 | "avatar_url": "https://avatars0.githubusercontent.com/u/1283200?v=4", 12 | "profile": "http://twitter.com/fakenickels", 13 | "contributions": [ 14 | "code", 15 | "doc", 16 | "ideas" 17 | ] 18 | }, 19 | { 20 | "login": "arielschiavoni", 21 | "name": "Ariel Schiavoni", 22 | "avatar_url": "https://avatars2.githubusercontent.com/u/1364564?v=4", 23 | "profile": "https://github.com/arielschiavoni", 24 | "contributions": [ 25 | "doc" 26 | ] 27 | }, 28 | { 29 | "login": "hew", 30 | "name": "Matt", 31 | "avatar_url": "https://avatars0.githubusercontent.com/u/3103241?v=4", 32 | "profile": "https://playqup.com", 33 | "contributions": [ 34 | "code" 35 | ] 36 | }, 37 | { 38 | "login": "baransu", 39 | "name": "Tomasz Cichocinski", 40 | "avatar_url": "https://avatars2.githubusercontent.com/u/9558691?v=4", 41 | "profile": "https://twitter.com/_cichocinski", 42 | "contributions": [ 43 | "bug", 44 | "code" 45 | ] 46 | }, 47 | { 48 | "login": "tmattio", 49 | "name": "Thibaut Mattio", 50 | "avatar_url": "https://avatars0.githubusercontent.com/u/6162008?v=4", 51 | "profile": "https://tmattio.github.io/", 52 | "contributions": [ 53 | "code" 54 | ] 55 | }, 56 | { 57 | "login": "Emilios1995", 58 | "name": "Emilio Srougo", 59 | "avatar_url": "https://avatars1.githubusercontent.com/u/12430923?v=4", 60 | "profile": "https://github.com/Emilios1995", 61 | "contributions": [ 62 | "bug" 63 | ] 64 | }, 65 | { 66 | "login": "athaeryn", 67 | "name": "Mike Anderson", 68 | "avatar_url": "https://avatars0.githubusercontent.com/u/1226972?v=4", 69 | "profile": "http://mkndrsn.com", 70 | "contributions": [ 71 | "code" 72 | ] 73 | }, 74 | { 75 | "login": "yurijean", 76 | "name": "Yuri Jean Fabris", 77 | "avatar_url": "https://avatars0.githubusercontent.com/u/6414876?v=4", 78 | "profile": "https://github.com/yurijean", 79 | "contributions": [ 80 | "code" 81 | ] 82 | }, 83 | { 84 | "login": "MargaretKrutikova", 85 | "name": "Margarita Krutikova", 86 | "avatar_url": "https://avatars2.githubusercontent.com/u/5932274?v=4", 87 | "profile": "https://twitter.com/rita_krutikova", 88 | "contributions": [ 89 | "code", 90 | "review", 91 | "ideas" 92 | ] 93 | }, 94 | { 95 | "login": "Yakimych", 96 | "name": "Kyrylo Yakymenko", 97 | "avatar_url": "https://avatars1.githubusercontent.com/u/5010901?v=4", 98 | "profile": "https://github.com/Yakimych", 99 | "contributions": [ 100 | "bug", 101 | "code" 102 | ] 103 | }, 104 | { 105 | "login": "lukashambsch", 106 | "name": "Lukas Hambsch", 107 | "avatar_url": "https://avatars3.githubusercontent.com/u/7560008?v=4", 108 | "profile": "https://github.com/lukashambsch", 109 | "contributions": [ 110 | "bug" 111 | ] 112 | }, 113 | { 114 | "login": "jfrolich", 115 | "name": "Jaap Frolich", 116 | "avatar_url": "https://avatars1.githubusercontent.com/u/579279?v=4", 117 | "profile": "http://www.familyfive.app", 118 | "contributions": [ 119 | "code", 120 | "review", 121 | "ideas" 122 | ] 123 | }, 124 | { 125 | "login": "believer", 126 | "name": "Rickard Laurin", 127 | "avatar_url": "https://avatars1.githubusercontent.com/u/1478102?v=4", 128 | "profile": "https://willcodefor.beer/", 129 | "contributions": [ 130 | "bug" 131 | ] 132 | }, 133 | { 134 | "login": "medson10", 135 | "name": "Medson Oliveira", 136 | "avatar_url": "https://avatars0.githubusercontent.com/u/17956325?v=4", 137 | "profile": "http://medson.me", 138 | "contributions": [ 139 | "code", 140 | "review", 141 | "ideas" 142 | ] 143 | }, 144 | { 145 | "login": "soulplant", 146 | "name": "soulplant", 147 | "avatar_url": "https://avatars3.githubusercontent.com/u/16846?v=4", 148 | "profile": "https://github.com/soulplant", 149 | "contributions": [ 150 | "code" 151 | ] 152 | }, 153 | { 154 | "login": "mbirkegaard", 155 | "name": "mbirkegaard", 156 | "avatar_url": "https://avatars0.githubusercontent.com/u/18616185?v=4", 157 | "profile": "https://github.com/mbirkegaard", 158 | "contributions": [ 159 | "code" 160 | ] 161 | }, 162 | { 163 | "login": "strdr4605", 164 | "name": "Dragoș Străinu", 165 | "avatar_url": "https://avatars3.githubusercontent.com/u/16056918?v=4", 166 | "profile": "https://strdr4605.github.io", 167 | "contributions": [ 168 | "doc" 169 | ] 170 | }, 171 | { 172 | "login": "bdunn313", 173 | "name": "Brad Dunn", 174 | "avatar_url": "https://avatars3.githubusercontent.com/u/867683?v=4", 175 | "profile": "https://github.com/bdunn313", 176 | "contributions": [ 177 | "doc" 178 | ] 179 | } 180 | ], 181 | "contributorsPerLine": 7, 182 | "projectName": "reason-apollo-hooks", 183 | "projectOwner": "Astrocoders", 184 | "repoType": "github", 185 | "repoHost": "https://github.com", 186 | "skipCi": true 187 | } 188 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Tell github that .re and .rei files are Reason 2 | *.re linguist-language=Reason 3 | *.rei linguist-language=Reason 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .merlin 3 | .bsb.lock 4 | lib/ 5 | *.bs.js 6 | yarn-error.log 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .merlin 3 | .bsb.lock 4 | lib/ 5 | examples/ 6 | *.bs.js 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Astrocoders 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⚠️ Prefer the new https://github.com/reasonml-community/reason-apollo-client instead 2 | 3 | --- 4 | 5 | # reason-apollo-hooks 6 | 7 | 8 | 9 | [![All Contributors](https://img.shields.io/badge/all_contributors-18-orange.svg?style=flat-square)](#contributors-) 10 | 11 | 12 | 13 | Reason bindings for the official [@apollo/react-hooks](https://www.npmjs.com/package/@apollo/react-hooks) 14 | 15 | ## Table of contents 16 | 17 | - [reason-apollo-hooks](#reason-apollo-hooks) 18 | - [Table of contents](#table-of-contents) 19 | - [Installation :arrow_up:](#installation-arrowup) 20 | - [Setting up :arrow_up:](#setting-up-arrowup) 21 | - [Usage with reason-apollo :arrow_up:](#usage-with-reason-apollo-arrowup) 22 | - [Available hooks :arrow_up:](#available-hooks-arrowup) 23 | - [useQuery :arrow_up:](#usequery-arrowup) 24 | - [useMutation :arrow_up:](#usemutation-arrowup) 25 | - [useSubscription :arrow_up:](#usesubscription-arrowup) 26 | - [Cache :arrow_up:](#cache-arrowup) 27 | - [Fragment :arrow_up:](#fragment-arrowup) 28 | - [Getting it running](#getting-it-running) 29 | - [Contributors ✨](#contributors-%e2%9c%a8) 30 | 31 | ## Installation [:arrow_up:](#table-of-contents) 32 | 33 | ``` 34 | yarn add reason-apollo-hooks reason-apollo@0.19.0 @apollo/react-hooks 35 | ``` 36 | 37 | BuckleScript <= 5.0.0 38 | 39 | ``` 40 | yarn add reason-apollo-hooks@3.0.0 reason-apollo@0.17.0 @apollo/react-hooks 41 | ``` 42 | 43 | Follow the installation instructions of [graphql_ppx_re](https://github.com/baransu/graphql_ppx_re). 44 | 45 | Then update your bsconfig.json 46 | 47 | ```diff 48 | "bs-dependencies": [ 49 | ... 50 | + "reason-apollo-hooks", 51 | + "reason-apollo" 52 | ] 53 | ``` 54 | 55 | ## Setting up [:arrow_up:](#table-of-contents) 56 | 57 | Add the provider in the top of the tree 58 | 59 | ```reason 60 | /* Create an InMemoryCache */ 61 | let inMemoryCache = ApolloInMemoryCache.createInMemoryCache(); 62 | 63 | /* Create an HTTP Link */ 64 | let httpLink = 65 | ApolloLinks.createHttpLink(~uri="http://localhost:3010/graphql", ()); 66 | 67 | let client = 68 | ReasonApollo.createApolloClient(~link=httpLink, ~cache=inMemoryCache, ()); 69 | 70 | let app = 71 | 72 | ... 73 | 74 | ``` 75 | 76 | ### Usage with reason-apollo [:arrow_up:](#table-of-contents) 77 | 78 | To use with `reason-apollo`'s `ReasonApollo.Provider` already present in your project: 79 | 80 | ```reason 81 | let client = ... // create Apollo client 82 | 83 | ReactDOMRe.renderToElementWithId( 84 | 85 | 86 | 87 | 88 | , 89 | "root", 90 | ); 91 | ``` 92 | 93 | ## Available hooks [:arrow_up:](#table-of-contents) 94 | 95 | ### useQuery [:arrow_up:](#table-of-contents) 96 | 97 | ```reason 98 | open ApolloHooks 99 | 100 | module UserQuery = [%graphql {| 101 | query UserQuery { 102 | currentUser { 103 | name 104 | } 105 | } 106 | |}]; 107 | 108 | [@react.component] 109 | let make = () => { 110 | /* Both variant and records available */ 111 | let (simple, _full) = useQuery(UserQuery.definition); 112 | 113 |
114 | { 115 | switch(simple) { 116 | | Loading =>

{React.string("Loading...")}

117 | | Data(data) => 118 |

{React.string(data##currentUser##name)}

119 | | NoData 120 | | Error(_) =>

{React.string("Get off my lawn!")}

121 | } 122 | } 123 |
124 | } 125 | ``` 126 | 127 | Using the `full` record for more advanced cases 128 | 129 | ```reason 130 | [@react.component] 131 | let make = () => { 132 | /* Both variant and records available */ 133 | let (_simple, full) = useQuery(UserQuery.definition); 134 | 135 |
136 | { 137 | switch(full) { 138 | | { loading: true }=>

{React.string("Loading...")}

139 | | { data: Some(data) } => 140 |

{React.string(data##currentUser##name)}

141 | | any other possibilities => 142 | | { error: Some(_) } =>

{React.string("Get off my lawn!")}

143 | } 144 | } 145 |
146 | } 147 | ``` 148 | 149 | Using `fetchPolicy` to change interactions with the `apollo` cache, see [apollo docs](https://www.apollographql.com/docs/react/api/react-apollo/#optionsfetchpolicy). 150 | 151 | ```reason 152 | let (_simple, full) = useQuery(~fetchPolicy=NetworkOnly, UserQuery.definition); 153 | ``` 154 | 155 | Using `errorPolicy` to change how errors are handled, see [apollo docs](https://www.apollographql.com/docs/react/api/react-apollo/#optionserrorpolicy). 156 | 157 | ```reason 158 | let (simple, _full) = useQuery(~errorPolicy=All, UserQuery.definition); 159 | ``` 160 | 161 | Using `skip` to skip query entirely, see [apollo docs](https://www.apollographql.com/docs/react/api/react-apollo/#configskip). 162 | 163 | ```reason 164 | let (simple, _full) = 165 | useQuery( 166 | ~skip= 167 | switch (value) { 168 | | None => true 169 | | _ => false 170 | }, 171 | UserQuery.definition, 172 | ); 173 | ``` 174 | 175 | ### useMutation [:arrow_up:](#table-of-contents) 176 | 177 | ```reason 178 | module ScreamMutation = [%graphql {| 179 | mutation ScreamMutation($screamLevel: Int!) { 180 | scream(level: $screamLevel) { 181 | error 182 | } 183 | } 184 | |}]; 185 | 186 | [@react.component] 187 | let make = () => { 188 | /* Both variant and records available */ 189 | let ( screamMutation, simple, _full ) = 190 | useMutation(~variables=ScreamMutation.makeVariables(~screamLevel=10, ()), ScreamMutation.definition); 191 | let scream = (_) => { 192 | screamMutation() 193 | |> Js.Promise.then_(((simple, _full)) => { 194 | // Trigger side effects by chaining the promise returned by screamMutation() 195 | switch (simple) { 196 | // You *must* set the error policy to be able to handle errors 197 | // in then_. See EditPersons.re for more 198 | | ApolloHooks.Mutation.Errors(_theErrors) => Js.log("OH NO!") 199 | | NoData => Js.log("NO DATA?") 200 | | Data(_theData) => Js.log("DATA!") 201 | }; 202 | Js.Promise.resolve(); 203 | }) 204 | |> ignore 205 | } 206 | 207 | // Use simple (and/or full) for (most) UI feedback 208 |
209 | {switch (simple) { 210 | | NotCalled 211 | | Data(_) => React.null 212 | | Loading =>
"Screaming!"->React.string
213 | | NoData 214 | | Error(_) =>
"Something went wrong!"->React.string
215 | }} 216 | 219 |
220 | } 221 | ``` 222 | 223 | If you don't know the value of the variables yet you can pass them in later 224 | 225 | ```reason 226 | [@react.component] 227 | let make = () => { 228 | /* Both variant and records available */ 229 | let ( screamMutation, _simple, _full ) = useMutation(ScreamMutation.definition); 230 | let scream = (_) => { 231 | screamMutation(~variables=ScreamMutation.makeVariables(~screamLevel=10, ()), ()) 232 | |> Js.Promise.then_(((simple, _full)) => { 233 | ... 234 | }) 235 | |> ignore 236 | } 237 | 238 |
239 | 242 |
243 | } 244 | ``` 245 | 246 | ### useSubscription [:arrow_up:](#table-of-contents) 247 | 248 | In order to use subscriptions, you first need to set up your websocket link: 249 | 250 | ```diff 251 | /* Create an InMemoryCache */ 252 | let inMemoryCache = ApolloInMemoryCache.createInMemoryCache(); 253 | 254 | /* Create an HTTP Link */ 255 | let httpLink = 256 | ApolloLinks.createHttpLink(~uri="http://localhost:3010/graphql", ()); 257 | + 258 | +/* Create a WS Link */ 259 | +let webSocketLink = 260 | + ApolloLinks.webSocketLink({ 261 | + uri: "wss://localhost:3010/graphql", 262 | + options: { 263 | + reconnect: true, 264 | + connectionParams: None, 265 | + }, 266 | + }); 267 | + 268 | +/* Using the ability to split links, you can send data to each link 269 | + depending on what kind of operation is being sent */ 270 | +let link = 271 | + ApolloLinks.split( 272 | + operation => { 273 | + let operationDefition = 274 | + ApolloUtilities.getMainDefinition(operation.query); 275 | + operationDefition.kind == "OperationDefinition" 276 | + && operationDefition.operation == "subscription"; 277 | + }, 278 | + webSocketLink, 279 | + httpLink, 280 | + ); 281 | 282 | let client = 283 | - ReasonApollo.createApolloClient(~link=httpLink, ~cache=inMemoryCache, ()); 284 | + ReasonApollo.createApolloClient(~link, ~cache=inMemoryCache, ()); 285 | 286 | let app = 287 | 288 | ... 289 | 290 | ``` 291 | 292 | Then, you can implement `useSubscription` in a similar manner to `useQuery` 293 | 294 | ```reason 295 | module UserAdded = [%graphql {| 296 | subscription userAdded { 297 | userAdded { 298 | id 299 | name 300 | } 301 | } 302 | |}]; 303 | 304 | 305 | [@react.component] 306 | let make = () => { 307 | let (userAddedSubscription, _full) = ApolloHooks.useSubscription(UserAdded.definition); 308 | 309 | switch (userAddedSubscription) { 310 | | Loading =>
{ReasonReact.string("Loading")}
311 | | Error(error) =>
{ReasonReact.string(error##message)}
312 | | Data(_response) => 313 | 317 | }; 318 | }; 319 | ``` 320 | 321 | ## Cache [:arrow_up:](#table-of-contents) 322 | 323 | There are a couple of caveats with manual cache updates. 324 | 325 | **TL;DR** 326 | 327 | 1. If you need to remove items from cached data, it is enough to just filter them out and save the result into cache as is. 328 | 2. If you need to add the result of a mutation to a list of items with the same shape, you simply concat it with the list and save into cache as it. 329 | 3. When you need to update a field, you have to resort to raw javascript to use spread operator on `Js.t` object in order to preserve `__typename` that `apollo` adds to all queries by default. 330 | 331 | An example of cache update could look like this: 332 | 333 | ```reason 334 | module PersonsQuery = [%graphql 335 | {| 336 | query getAllPersons { 337 | allPersons { 338 | id 339 | age 340 | name 341 | } 342 | } 343 | |} 344 | ]; 345 | 346 | module PersonsReadQuery = ApolloClient.ReadQuery(PersonsQuery); 347 | module PersonsWriteQuery = ApolloClient.WriteQuery(PersonsQuery); 348 | 349 | external cast: Js.Json.t => PersonsQuery.t = "%identity"; 350 | 351 | let updatePersons = (~client, ~name, ~age) => { 352 | let query = PersonsQuery.make(); 353 | let readQueryOptions = ApolloHooks.Utils.toReadQueryOptions(query); 354 | 355 | // can throw exception of cache is empty 356 | switch (PersonsReadQuery.readQuery(client, readQueryOptions)) { 357 | | exception _ => () 358 | | cachedResponse => 359 | switch (cachedResponse |> Js.Nullable.toOption) { 360 | | None => () 361 | | Some(cachedPersons) => 362 | // readQuery will return unparsed data with __typename field, need to cast it since 363 | // it has type Json.t, but we know it will have the same type as PersonsReadQuery.t 364 | let persons = cast(cachedPersons); 365 | 366 | // to remove items, simply filter them out 367 | let updatedPersons = { 368 | "allPersons": 369 | Belt.Array.keep(persons##allPersons, person => person##age !== age), 370 | }; 371 | 372 | // when updating items, __typename must be preserved, but since it is not a record, 373 | // can't use spread, so use JS to update items 374 | let updatedPersons = { 375 | "allPersons": 376 | Belt.Array.map(persons##allPersons, person => 377 | person##name === name ? [%bs.raw {| {...person, age } |}] : person 378 | ), 379 | }; 380 | 381 | PersonsWriteQuery.make( 382 | ~client, 383 | ~variables=query##variables, 384 | ~data=updatedPersons, 385 | (), 386 | ); 387 | } 388 | }; 389 | }; 390 | ``` 391 | 392 | `reason-apollo-hooks` parses response data from a query or mutation using parse function created by `graphql_ppx`. For example, when using `@bsRecord` directive, the response object will be parsed from a `Js.t` object to a reason record. In this case, the response data in reason code is not the same object that is stored in cache, since `react-apollo` saves data in cache before it is parsed and returned to the component. However, when updating cache, the data must be in the same format or apollo cache won't work correctly and throw errors. 393 | 394 | If using directives like `@bsRecord`, `@bsDecoder` or `@bsVariant` in `graphql_ppx`, the data needs to be serialized back to JS object before it is written in cache. Since there is currently no way to serialize this data (see [this issue](https://github.com/mhallin/graphql_ppx/issues/71) in `graphql_ppx`), queries that will be updated in cache shouldn't use any of those directive, unless you will take care of the serialization yourself. 395 | 396 | By default, apollo will add field `__typename` to the queries and will use it to normalize data and manipulate cache (see [normalization](https://www.apollographql.com/docs/react/advanced/caching/#normalization)). This field won't exist on parsed reason objects, since it is not included in the actual query you write, but is added by apollo before sending the query. Since `__typename` is crucial for the cache to work correctly, we need to read data from cache in its raw unparsed format, which is achieved with `readQuery` from `ApolloClient.ReadQuery` defined in `reason-apollo`. 397 | 398 | 399 | ## Fragment [:arrow_up:](#table-of-contents) 400 | 401 | Using [fragments](https://www.apollographql.com/docs/react/data/fragments/). 402 | 403 | Fragments can be defined and used like this: 404 | 405 | ```reason 406 | // Fragments.re 407 | module PersonFragment = [%graphql 408 | {| 409 | fragment person on Person { 410 | id 411 | name 412 | age 413 | } 414 | |} 415 | ]; 416 | 417 | ``` 418 | 419 | ```reason 420 | module PersonsQuery = [%graphql 421 | {| 422 | query getAllPersons { 423 | ...Fragments.PersonFragment.Person 424 | } 425 | |} 426 | ]; 427 | ``` 428 | 429 | See [examples/persons/src/fragments/LoadMoreFragments.re](examples/persons/src/fragments/LoadMoreFragments.re). 430 | 431 | ## Getting it running 432 | 433 | ```sh 434 | yarn 435 | yarn start 436 | ``` 437 | 438 | ## Contributors ✨ 439 | 440 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 |

Gabriel Rubens

💻 📖 🤔

Ariel Schiavoni

📖

Matt

💻

Tomasz Cichocinski

🐛 💻

Thibaut Mattio

💻

Emilio Srougo

🐛

Mike Anderson

💻

Yuri Jean Fabris

💻

Margarita Krutikova

💻 👀 🤔

Kyrylo Yakymenko

🐛 💻

Lukas Hambsch

🐛

Jaap Frolich

💻 👀 🤔

Rickard Laurin

🐛

Medson Oliveira

💻 👀 🤔

soulplant

💻

mbirkegaard

💻

Dragoș Străinu

📖

Brad Dunn

📖
471 | 472 | 473 | 474 | 475 | 476 | 477 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 478 | -------------------------------------------------------------------------------- /bsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reason-apollo-hooks", 3 | "reason": { 4 | "react-jsx": 3 5 | }, 6 | "sources": [ 7 | { 8 | "dir": "src", 9 | "subdirs": true 10 | }, 11 | { 12 | "dir": "examples", 13 | "type": "dev" 14 | } 15 | ], 16 | "package-specs": [ 17 | { 18 | "module": "commonjs", 19 | "in-source": true 20 | } 21 | ], 22 | "suffix": ".bs.js", 23 | "bs-dependencies": ["reason-react", "reason-apollo"], 24 | "refmt": 3 25 | } 26 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | 'body-leading-blank': [1, 'always'], 4 | 'footer-leading-blank': [1, 'always'], 5 | 'header-max-length': [2, 'always', 72], 6 | 'scope-case': [2, 'always', 'lower-case'], 7 | 'subject-case': [ 8 | 2, 9 | 'never', 10 | ['sentence-case', 'start-case', 'pascal-case', 'upper-case'] 11 | ], 12 | 'subject-empty': [2, 'never'], 13 | 'subject-full-stop': [2, 'never', '.'], 14 | 'type-case': [2, 'always', 'lower-case'], 15 | 'type-empty': [2, 'never'], 16 | 'type-enum': [ 17 | 2, 18 | 'always', 19 | [ 20 | 'build', 21 | 'chore', 22 | 'ci', 23 | 'docs', 24 | 'feat', 25 | 'fix', 26 | 'perf', 27 | 'refactor', 28 | 'revert' 29 | ] 30 | ] 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /docs/ReasonApolloHooks/ApolloHooks/index.html: -------------------------------------------------------------------------------- 1 | 2 | ApolloHooks (ReasonApolloHooks.ApolloHooks)

Module ApolloHooks

module Mutation = ApolloHooksMutation;
module Query = ApolloHooksQuery;
module Provider = ApolloHooksProvider;
module Subscription = ApolloHooksSubscription;
let useQuery: ?⁠client:ApolloClient.generatedApolloClient => ?⁠variables:Js.Json.t => ?⁠notifyOnNetworkStatusChange:bool => ?⁠fetchPolicy:ApolloHooksTypes.fetchPolicy => ?⁠errorPolicy:ApolloHooksTypes.errorPolicy => ?⁠skip:bool => ?⁠pollInterval:int => ApolloHooksTypes.graphqlDefinition('a'b'c) => (Query.variant('a), Query.queryResult('a));

This is probably the one hook you'll use the most. A quick demo:

open ApolloHooks;
  3 | 
  4 | module Query = [%graphql {|
  5 |   query MyQuery {
  6 |     me { id, name }
  7 |   }
  8 | |}];
  9 | 
 10 | [@react.component]
 11 | let make = () => {
 12 |   /* In Reason we prefix variables that we are not going to use with _ */
 13 |   let (simple, _full) = useQuery(Query.definitions);
 14 | 
 15 |   /* When using simple with Reason's pattern-matching operator, the compiler will force you to cover every single branch of the variant type */
 16 |   switch(simple) {
 17 |     | Loading => React.string("loading...")
 18 |     | Error(error) =>
 19 |       Js.log(error);
 20 |       React.string("Something went wrong!")
 21 |     | Data(data) =>
 22 |       React.string("Hello, " ++ data##me##name)
 23 |     /* Every. Single. One. Of Them. */
 24 |     | NoData =>
 25 |       React.string("Woa something went really wrong! Glady we use Reason and it forced us to handle this! Report this issue")
 26 |   }
 27 | }

Why we return a tuple? While designing and using the API we came to the conclusion that would be much more convient to have a value that would attend the majority of simple usages and a full for when you need to do a complex UI, such as infinite scroll.

The value simple (Query.variant('a)) helps you to consume your data with simplicity, type safety and exhaustiveness check. But for those cases where you really want do do a fine-grained control of your data flow – such as when you have loading and data at the same time – that's when full (Query.queryResult('a)) becomes more useful.

module Query = [%graphql {|
 28 |   query MyQuery {
 29 |     me { id, name }
 30 |   }
 31 | |}];
 32 | 
 33 | [@react.component]
 34 | let make = () => {
 35 |   let (_simple, full) = useQuery(Query.definitions);
 36 | 
 37 |   /* `full` is a record type so you pattern against it's possible combos of values */
 38 |   switch(full) {
 39 |     /* Initial loading */
 40 |     | { loading: true, data: None } => React.string("loading...")
 41 |     /* Error but no data */
 42 |     | { loading: false, data: None, error: Some(error) } => React.string("Something went wrong")
 43 |     /* When we have some data and we tried to refetch but got an error */
 44 |     | { loading: false, data: Some(data), error: Some(error)  } =>
 45 |       <>
 46 |         {React.string("Something went wrong")}
 47 |         <RenderData data onLoadMore={full.refetch} />
 48 |       </>
 49 |     /* Just data */
 50 |     | { loading: false, data: Some(data), error: None } =>
 51 |       <>
 52 |         {React.string("Something went wrong")}
 53 |         <RenderData data onLoadMore={full.refetch} />
 54 |       </>
 55 |     | Data(data) =>
 56 |       React.string("Hello, " ++ data##me##name)
 57 |     /* Not loading? No data? No error? That's weird */
 58 |     | {loading: false, data: None, error: null} =>
 59 |       React.string("Woa something went really wrong! But the programmer remembered to handle this case! Report to us")
 60 |   }
 61 | }

Quite more complex right? Gladly it's not always that we have that level of complexity.

That covers the most common cases of usage. If you want to see more complex usages check out the examples folder.

let useMutation: ?⁠client:ApolloClient.generatedApolloClient => ?⁠variables:Js.Json.t => ?⁠refetchQueries:Mutation.refetchQueries => ?⁠awaitRefetchQueries:bool => ?⁠update:(ApolloClient.generatedApolloClient => Mutation.mutationResult('a) => unit) => ?⁠optimisticResponse:Js.Json.t => ApolloHooksTypes.graphqlDefinition('a'b'c) => (Mutation.mutation('a), Mutation.controlledVariantResult('a), Mutation.controlledResult('a));

Second most used! Here's a quick demo:

open ApolloHooks;
 62 | 
 63 | module Mutation = [%graphql {|
 64 |   mutation MyMutation($input: MyMutationInput!) {
 65 |     myMutation(input: $input) { error }
 66 |   }
 67 | |}];
 68 | 
 69 | [@react.component]
 70 | let make = () => {
 71 |   /* `simple` and `full` follow the same principle of `useQuery`. */
 72 |   let (mutate, simple, _full) = useMutation(Mutation.definitions);
 73 | 
 74 |   /* When using simple with Reason's pattern-matching operator, the compiler will force you to cover every single branch of the variant type */
 75 |   switch(simple) {
 76 |     | Loading => React.string("loading...")
 77 |     | Error(error) =>
 78 |       Js.log(error);
 79 |       React.string("Something went wrong!")
 80 |     | Data(data) =>
 81 |       <div>
 82 |         {React.string("Hello, " ++ data##me##name)}
 83 |       </div>
 84 |     /* Every. Single. One. Of Them. */
 85 |     | NotCalled => <button onClick={_ => mutate()}>{React.string("Click me")}
 86 |     | NoData =>
 87 |       React.string("Woa something went really wrong! Glady we use Reason and it forced us to handle this! Report this issue")
 88 |   }
 89 | }

Or if you only care about calling mutate

open ApolloHooks;
 90 | 
 91 | module Mutation = [%graphql {|
 92 |   mutation MyMutation {
 93 |     me { id, name }
 94 |   }
 95 | |}];
 96 | 
 97 | [@react.component]
 98 | let make = () => {
 99 |   let (mutate, _simple, _full) = useMutation(Mutation.definitions);
100 |   let onClick = _event => {
101 |     mutate()
102 |       |> Js.Promise.then_(result => {
103 |         switch(result) {
104 |           | Data(data) => do anything here
105 |           | Error(error) => handle your error
106 |           | NoData => ...something went wrong...
107 |         }
108 |       })
109 |   }
110 | 
111 |   <button onClick>{React.string("Click me")}</button>
112 | }
let useSubscription: ?⁠variables:Js.Json.t => ?⁠client:ApolloClient.generatedApolloClient => ApolloHooksTypes.graphqlDefinition('a'b'c) => (Subscription.variant('a), Subscription.result('a));

useSubscription bindings

let toQueryObj: Js.t({.. query: string, variables: Js.Json.t, }) => ApolloClient.queryObj;

Helper to generate the shape of a query for refetchQueries mutation param. Take a look in examples/persons/src/EditPerson.re for a more complete demo of usage.

let toReadQueryOptions: Js.t({.. query: string, variables: 'a, }) => Js.t({. query: ReasonApolloTypes.queryString, variables: Js.Nullable.t('a), });

Helper to generate the shape of a query for ApolloClient.ReadQuery.readQuery. Used for optimistic UI. Take a look in examples/persons/src/FilterByNameCache.re for a more complete demo of usage.

-------------------------------------------------------------------------------- /docs/ReasonApolloHooks/ApolloHooksMutation/index.html: -------------------------------------------------------------------------------- 1 | 2 | ApolloHooksMutation (ReasonApolloHooks.ApolloHooksMutation)

Module ApolloHooksMutation

type refetchQueries = ReasonApolloTypes.executionResult => array(ApolloClient.queryObj);
type result('a) =
| Data('a)
| Error(array(ApolloHooksTypes.graphqlError))
| NoData
;
type controlledResult('a) = {
loading: bool,
called: bool,
data: option('a),
error: option(ApolloHooksTypes.apolloError),
};
type controlledVariantResult('a) =
| Loading
| NotCalled
| Data('a)
| Error(ApolloHooksTypes.apolloError)
| NoData
;
let gql: ReasonApolloTypes.gql;
type mutationResult('a) = Js.t({. data: option('a), });
include { ... };
include { ... };
type options('a);
let options: ?⁠variables:Js.Json.t => ?⁠mutation:option(ReasonApolloTypes.queryString) => ?⁠client:ApolloClient.generatedApolloClient => ?⁠refetchQueries:refetchQueries => ?⁠awaitRefetchQueries:bool => ?⁠update:(ApolloClient.generatedApolloClient => mutationResult('a) => unit) => ?⁠optimisticResponse:Js.Json.t => unit => options('a);
let variablesGet: options('a) => option(Js.Json.t);
let mutationGet: options('a) => option(option(ReasonApolloTypes.queryString));
let clientGet: options('a) => option(ApolloClient.generatedApolloClient);
let refetchQueriesGet: options('a) => option(refetchQueries);
let awaitRefetchQueriesGet: options('a) => option(bool);
let updateGet: options('a) => option((ApolloClient.generatedApolloClient => mutationResult('a) => unit));
let optimisticResponseGet: options('a) => option(Js.Json.t);
type jsResult = Js.t({. data: Js.Nullable.t(Js.Json.t), loading: bool, called: bool, error: Js.Nullable.t(ApolloHooksTypes.apolloError), });
type executionResult = Js.t({. data: Js.Nullable.t(Js.Json.t), errors: Js.Nullable.t(array(ApolloHooksTypes.graphqlError)), });
type jsMutate('a) = Js.Internal.fn([ `Arity_1(options('a)) ], Js.Promise.t(executionResult));
type mutation('a) = ?⁠variables:Js.Json.t => ?⁠client:ApolloClient.generatedApolloClient => ?⁠refetchQueries:refetchQueries => ?⁠awaitRefetchQueries:bool => ?⁠optimisticResponse:Js.Json.t => unit => Js.Promise.t(result('a));
let useMutationJs: Js.Internal.fn([ `Arity_2((ReasonApolloTypes.queryString, options('a))) ], (jsMutate('a), jsResult));
exception Error(string);
let useMutation: ?⁠client:ApolloClient.generatedApolloClient => ?⁠variables:Js.Json.t => ?⁠refetchQueries:refetchQueries => ?⁠awaitRefetchQueries:bool => ?⁠update:(ApolloClient.generatedApolloClient => mutationResult('data) => unit) => ?⁠optimisticResponse:Js.Json.t => ApolloHooksTypes.graphqlDefinition('data'a'b) => (mutation('data), controlledVariantResult('data), controlledResult('data));
-------------------------------------------------------------------------------- /docs/ReasonApolloHooks/ApolloHooksProvider/index.html: -------------------------------------------------------------------------------- 1 | 2 | ApolloHooksProvider (ReasonApolloHooks.ApolloHooksProvider)

Module ApolloHooksProvider

let makeProps: client:ApolloClient.generatedApolloClient => children:React.element => ?⁠key:string => unit => Js.t({. client: ApolloClient.generatedApolloClient, children: React.element, });
let make: React.componentLike(Js.t({. client: ApolloClient.generatedApolloClient, children: React.element, }), React.element);
-------------------------------------------------------------------------------- /docs/ReasonApolloHooks/ApolloHooksQuery/index.html: -------------------------------------------------------------------------------- 1 | 2 | ApolloHooksQuery (ReasonApolloHooks.ApolloHooksQuery)

Module ApolloHooksQuery

type variant('a) =
| Data('a)
| Error(ApolloHooksTypes.apolloError)
| Loading
| NoData
;
include { ... };
include { ... };
type updateQueryOptions;
let updateQueryOptions: ?⁠fetchMoreResult:Js.Json.t => ?⁠variables:Js.Json.t => unit => updateQueryOptions;
let fetchMoreResultGet: updateQueryOptions => option(Js.Json.t);
let variablesGet: updateQueryOptions => option(Js.Json.t);
type updateQueryT = Js.Json.t => updateQueryOptions => Js.Json.t;
type updateSubscriptionOptionsJs = Js.t({. subscriptionData: Js.t({. data: Js.Json.t, }), variables: Js.Nullable.t(Js.Json.t), });

* https://github.com/apollographql/apollo-client/blob/master/packages/apollo-client/src/core/watchQueryOptions.ts#L139

type updateQuerySubscribeToMoreT = Js.Json.t => updateSubscriptionOptionsJs => Js.Json.t;
include { ... };
include { ... };
type subscribeToMoreOptionsJs;
let subscribeToMoreOptionsJs: document:ReasonApolloTypes.queryString => ?⁠variables:Js.Json.t => ?⁠updateQuery:updateQuerySubscribeToMoreT => unit => subscribeToMoreOptionsJs;
let documentGet: subscribeToMoreOptionsJs => ReasonApolloTypes.queryString;
let variablesGet: subscribeToMoreOptionsJs => option(Js.Json.t);
let updateQueryGet: subscribeToMoreOptionsJs => option(updateQuerySubscribeToMoreT);
type unsubscribeFnT = unit => unit;
type refetch('a) = ?⁠variables:Js.Json.t => unit => Js.Promise.t('a);
type queryResult('a) = {
data: option('a),
loading: bool,
error: option(ApolloHooksTypes.apolloError),
refetch: refetch('a),
fetchMore: ?⁠variables:Js.Json.t => updateQuery:updateQueryT => unit => Js.Promise.t(unit),
networkStatus: ApolloHooksTypes.networkStatus,
startPolling: int => unit,
stopPolling: unit => unit,
subscribeToMore: document:ReasonApolloTypes.queryString => ?⁠variables:Js.Json.t => ?⁠updateQuery:updateQuerySubscribeToMoreT => unit => unsubscribeFnT,
};
include { ... };
include { ... };
type fetchMoreOptions;
let fetchMoreOptions: ?⁠variables:Js.Json.t => updateQuery:updateQueryT => unit => fetchMoreOptions;
let variablesGet: fetchMoreOptions => option(Js.Json.t);
let updateQueryGet: fetchMoreOptions => updateQueryT;
let gql: ReasonApolloTypes.gql;
include { ... };
include { ... };
type options;
let options: ?⁠variables:Js.Json.t => ?⁠client:ApolloClient.generatedApolloClient => ?⁠notifyOnNetworkStatusChange:bool => ?⁠fetchPolicy:string => ?⁠errorPolicy:string => ?⁠skip:bool => ?⁠pollInterval:int => unit => options;
let variablesGet: options => option(Js.Json.t);
let clientGet: options => option(ApolloClient.generatedApolloClient);
let notifyOnNetworkStatusChangeGet: options => option(bool);
let fetchPolicyGet: options => option(string);
let errorPolicyGet: options => option(string);
let skipGet: options => option(bool);
let pollIntervalGet: options => option(int);
let useQueryJs: ReasonApolloTypes.queryString => options => Js.t({. data: Js.Nullable.t(Js.Json.t), loading: bool, error: Js.Nullable.t(ApolloHooksTypes.apolloError), refetch: Js.Internal.meth([ `Arity_1(Js.Nullable.t(Js.Json.t)) ], Js.Promise.t(Js.Json.t)), fetchMore: Js.Internal.meth([ `Arity_1(fetchMoreOptions) ], Js.Promise.t(unit)), networkStatus: Js.Nullable.t(int), stopPolling: Js.Internal.meth([ `Arity_0 ], unit), startPolling: Js.Internal.meth([ `Arity_1(int) ], unit), subscribeToMore: Js.Internal.meth([ `Arity_1(subscribeToMoreOptionsJs) ], unsubscribeFnT), });
let useQuery: ?⁠client:ApolloClient.generatedApolloClient => ?⁠variables:Js.Json.t => ?⁠notifyOnNetworkStatusChange:bool => ?⁠fetchPolicy:ApolloHooksTypes.fetchPolicy => ?⁠errorPolicy:ApolloHooksTypes.errorPolicy => ?⁠skip:bool => ?⁠pollInterval:int => ApolloHooksTypes.graphqlDefinition('data'a'b) => (variant('data), queryResult('data));
-------------------------------------------------------------------------------- /docs/ReasonApolloHooks/ApolloHooksSubscription/index.html: -------------------------------------------------------------------------------- 1 | 2 | ApolloHooksSubscription (ReasonApolloHooks.ApolloHooksSubscription)

Module ApolloHooksSubscription

type error = Js.t({. message: string, });
type variant('a) =
| Data('a)
| Error(error)
| Loading
| NoData
;
type result('a) = {
data: option('a),
loading: bool,
error: option(error),
};
let gql: ReasonApolloTypes.gql;
include { ... };
include { ... };
type options;
let options: ?⁠variables:Js.Json.t => ?⁠skip:bool => ?⁠onSubscriptionData:(unit => unit) => ?⁠client:ApolloClient.generatedApolloClient => unit => options;
let variablesGet: options => option(Js.Json.t);
let skipGet: options => option(bool);
let onSubscriptionDataGet: options => option((unit => unit));
let clientGet: options => option(ApolloClient.generatedApolloClient);
let useSubscriptionJs: ReasonApolloTypes.queryString => options => Js.t({. data: Js.Nullable.t(Js.Json.t), loading: bool, error: Js.Nullable.t(error), });
let useSubscription: ?⁠variables:Js.Json.t => ?⁠client:ApolloClient.generatedApolloClient => ApolloHooksTypes.graphqlDefinition('data'a'b) => (variant('data), result('data));
-------------------------------------------------------------------------------- /docs/ReasonApolloHooks/ApolloHooksTypes/index.html: -------------------------------------------------------------------------------- 1 | 2 | ApolloHooksTypes (ReasonApolloHooks.ApolloHooksTypes)

Module ApolloHooksTypes

type networkStatus =
| Loading
| SetVariables
| FetchMore
| Refetch
| Poll
| Ready
| Error
| Unknown
;

* apollo-client/src/core/networkStatus

let toNetworkStatus: Js.Nullable.t(int) => networkStatus;
type fetchPolicy =
| CacheFirst
| CacheAndNetwork
| NetworkOnly
| CacheOnly
| NoCache
| Standby
;

* apollo-client/src/core/watchQueryOptions.ts

let fetchPolicyToJs: fetchPolicy => string;
type errorPolicy =
| None
| Ignore
| All
;

* apollo-client/src/core/watchQueryOptions.ts

let errorPolicyToJs: errorPolicy => string;
type apolloErrorExtensions = Js.t({. code: Js.Nullable.t(string), });

* apollo-client/src/errors/ApolloError.ts

type graphqlError = Js.t({. message: string, name: Js.Nullable.t(string), extensions: Js.Nullable.t(apolloErrorExtensions), locations: Js.Nullable.t(array(string)), path: Js.Nullable.t(array(string)), nodes: Js.Nullable.t(array(string)), });
type apolloError = Js.t({. message: string, graphQLErrors: Js.Nullable.t(array(graphqlError)), networkError: Js.Nullable.t(string), });
type parse('a) = Js.Json.t => 'a;
type query = string;
type composeVariables('returnType, 'hookReturnType) = (Js.Json.t => 'returnType) => 'hookReturnType;
type graphqlDefinition('data, 'returnType, 'hookReturnType) = (parse('data), query, composeVariables('returnType'hookReturnType));
-------------------------------------------------------------------------------- /docs/ReasonApolloHooks/index.html: -------------------------------------------------------------------------------- 1 | 2 | index (ReasonApolloHooks.index)

Index

Hello!

Check out the module ApolloHooks to get started.

-------------------------------------------------------------------------------- /docs/highlight.pack.js: -------------------------------------------------------------------------------- 1 | /*! highlight.js v9.12.0 | BSD3 License | git.io/hljslicense */ 2 | !function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){s+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var l=0,s="",f=[];e.length||r.length;){var g=i();if(s+=n(a.substring(l,g[0].offset)),l=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===l);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return s+n(a.substr(l))}function l(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):x(a.k).forEach(function(e){u(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return l("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var c=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=c.length?t(c.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function l(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function p(e,n,t,r){var a=r?"":I.classPrefix,i='',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=l(E,r),e?(B+=e[1],a+=p(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!y[E.sL])return n(k);var t=e?f(E.sL,k,!0,x[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(B+=t.r),e&&(x[E.sL]=t.top),p(t.language,t.value,!1,!0)}function b(){L+=null!=E.sL?d():h(),k=""}function v(e){L+=e.cN?p(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(k+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),b(),t.rB||t.eB||(k=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),b(),a.eE&&(k=n));do E.cN&&(L+=C),E.skip||(B+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return k+=n,n.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,E=i||N,x={},L="";for(R=E;R!==N;R=R.parent)R.cN&&(L=p(R.cN,"",!0)+L);var k="",B=0;try{for(var M,j,O=0;;){if(E.t.lastIndex=O,M=E.t.exec(t),!M)break;j=m(t.substring(O,M.index),M[0]),O=M.index+j}for(m(t.substr(O)),R=E;R.parent;R=R.parent)R.cN&&(L+=C);return{r:B,value:L,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function g(e,t){t=t||I.languages||x(y);var r={r:0,value:n(e)},a=r;return t.filter(w).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return I.tabReplace||I.useBR?e.replace(M,function(e,n){return I.useBR&&"\n"===e?"
":I.tabReplace?n.replace(/\t/g,I.tabReplace):""}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function d(e){var n,t,r,o,l,s=i(e);a(s)||(I.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,l=n.textContent,r=s?f(s,l,!0):g(l),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),l)),r.value=p(r.value),e.innerHTML=r.value,e.className=h(e.className,s,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){I=o(I,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,d)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=y[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function R(){return x(y)}function w(e){return e=(e||"").toLowerCase(),y[e]||y[L[e]]}var E=[],x=Object.keys,y={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
",I={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=d,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("ocaml",function(e){return{aliases:["ml"],k:{keyword:"and as assert asr begin class constraint do done downto else end exception external for fun function functor if in include inherit! inherit initializer land lazy let lor lsl lsr lxor match method!|10 method mod module mutable new object of open! open or private rec sig struct then to try type val! val virtual when while with parser value",built_in:"array bool bytes char exn|5 float int int32 int64 list lazy_t|5 nativeint|5 string unit in_channel out_channel ref",literal:"true false"},i:/\/\/|>>/,l:"[a-z_]\\w*!?",c:[{cN:"literal",b:"\\[(\\|\\|)?\\]|\\(\\)",r:0},e.C("\\(\\*","\\*\\)",{c:["self"]}),{cN:"symbol",b:"'[A-Za-z_](?!')[\\w']*"},{cN:"type",b:"`[A-Z][\\w']*"},{cN:"type",b:"\\b[A-Z][\\w']*",r:0},{b:"[a-z_]\\w*'[\\w']*",r:0},e.inherit(e.ASM,{cN:"string",r:0}),e.inherit(e.QSM,{i:null}),{cN:"number",b:"\\b(0[xX][a-fA-F0-9_]+[Lln]?|0[oO][0-7_]+[Lln]?|0[bB][01_]+[Lln]?|[0-9][0-9_]*([Lln]|(\\.[0-9_]*)?([eE][-+]?[0-9_]+)?)?)",r:0},{b:/[-=]>/}]}}); -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/odoc.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | /* Copyright (c) 2016 The odoc contributors. All rights reserved. 3 | Distributed under the ISC license, see terms at the end of the file. 4 | odoc 1.4.0 */ 5 | 6 | /* Fonts */ 7 | @import url('https://fonts.googleapis.com/css?family=Fira+Mono:400,500'); 8 | @import url('https://fonts.googleapis.com/css?family=Noticia+Text:400,400i,700'); 9 | @import url('https://fonts.googleapis.com/css?family=Fira+Sans:400,400i,500,500i,600,600i,700,700i'); 10 | 11 | 12 | /* Reset a few things. */ 13 | 14 | html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { 15 | margin: 0; 16 | padding: 0; 17 | border: 0; 18 | font-size: inherit; 19 | font: inherit; 20 | line-height: inherit; 21 | vertical-align: baseline; 22 | text-align: inherit; 23 | color: inherit; 24 | background: transparent; 25 | } 26 | 27 | table { 28 | border-collapse: collapse; 29 | border-spacing: 0; 30 | } 31 | 32 | *, *:before, *:after { 33 | box-sizing: border-box; 34 | } 35 | 36 | html { 37 | font-size: 15px; 38 | } 39 | 40 | body { 41 | font-family: "Fira Sans", Helvetica, Arial, sans-serif; 42 | text-align: left; 43 | color: #333; 44 | background: #FFFFFF; 45 | } 46 | 47 | .content { 48 | max-width: 90ex; 49 | margin-left: calc(10vw + 20ex); 50 | margin-right: 4ex; 51 | margin-top: 20px; 52 | margin-bottom: 50px; 53 | font-family: "Noticia Text", Georgia, serif; 54 | line-height: 1.5; 55 | } 56 | 57 | .content>header { 58 | margin-bottom: 30px; 59 | } 60 | 61 | .content>header nav { 62 | font-family: "Fira Sans", Helvetica, Arial, sans-serif; 63 | } 64 | 65 | /* Basic markup elements */ 66 | 67 | b, strong { 68 | font-weight: 500; 69 | } 70 | 71 | i, em { 72 | font-style: italic; 73 | } 74 | 75 | sup { 76 | vertical-align: super; 77 | } 78 | 79 | sub { 80 | vertical-align: sub; 81 | } 82 | 83 | sup, sub { 84 | font-size: 12px; 85 | line-height: 0; 86 | margin-left: 0.2ex; 87 | } 88 | 89 | pre { 90 | margin-top: 0.8em; 91 | margin-bottom: 1.2em; 92 | } 93 | 94 | p, ul, ol { 95 | margin-top: 0.5em; 96 | margin-bottom: 1em; 97 | } 98 | ul, ol { 99 | list-style-position: outside 100 | } 101 | 102 | ul>li { 103 | margin-left: 22px; 104 | } 105 | 106 | ol>li { 107 | margin-left: 27.2px; 108 | } 109 | 110 | li>*:first-child { 111 | margin-top: 0 112 | } 113 | 114 | /* Text alignements, this should be forbidden. */ 115 | 116 | .left { 117 | text-align: left; 118 | } 119 | 120 | .right { 121 | text-align: right; 122 | } 123 | 124 | .center { 125 | text-align: center; 126 | } 127 | 128 | /* Links and anchors */ 129 | 130 | a { 131 | text-decoration: none; 132 | color: #2C5CBD; 133 | } 134 | 135 | a:hover { 136 | box-shadow: 0 1px 0 0 #2C5CBD; 137 | } 138 | 139 | /* Linked highlight */ 140 | *:target { 141 | background-color: rgba(187,239,253,0.3) !important; 142 | box-shadow: 0 0px 0 1px rgba(187,239,253,0.8) !important; 143 | border-radius: 1px; 144 | } 145 | 146 | *:hover>a.anchor { 147 | visibility: visible; 148 | } 149 | 150 | a.anchor:before { 151 | content: "#" 152 | } 153 | 154 | a.anchor:hover { 155 | box-shadow: none; 156 | text-decoration: none; 157 | color: #555; 158 | } 159 | 160 | a.anchor { 161 | visibility: hidden; 162 | position: absolute; 163 | /* top: 0px; */ 164 | /* margin-left: -3ex; */ 165 | margin-left: -1.3em; 166 | font-weight: normal; 167 | font-style: normal; 168 | padding-right: 0.4em; 169 | padding-left: 0.4em; 170 | /* To remain selectable */ 171 | color: #d5d5d5; 172 | } 173 | 174 | .spec > a.anchor { 175 | margin-left: -2.3em; 176 | padding-right: 0.9em; 177 | } 178 | 179 | .xref-unresolved { 180 | color: #2C5CBD; 181 | } 182 | .xref-unresolved:hover { 183 | box-shadow: 0 1px 0 0 #CC6666; 184 | } 185 | 186 | /* Section and document divisions. 187 | Until at least 4.03 many of the modules of the stdlib start at .h7, 188 | we restart the sequence there like h2 */ 189 | 190 | h1, h2, h3, h4, h5, h6, .h7, .h8, .h9, .h10 { 191 | font-family: "Fira Sans", Helvetica, Arial, sans-serif; 192 | font-weight: 400; 193 | margin: 0.5em 0 0.5em 0; 194 | padding-top: 0.1em; 195 | line-height: 1.2; 196 | overflow-wrap: break-word; 197 | } 198 | 199 | h1 { 200 | font-weight: 500; 201 | font-size: 2.441em; 202 | margin-top: 1.214em; 203 | } 204 | 205 | h1 { 206 | font-weight: 500; 207 | font-size: 1.953em; 208 | box-shadow: 0 1px 0 0 #ddd; 209 | } 210 | 211 | h2 { 212 | font-size: 1.563em; 213 | } 214 | 215 | h3 { 216 | font-size: 1.25em; 217 | } 218 | 219 | small, .font_small { 220 | font-size: 0.8em; 221 | } 222 | 223 | h1 code, h1 tt { 224 | font-size: inherit; 225 | font-weight: inherit; 226 | } 227 | 228 | h2 code, h2 tt { 229 | font-size: inherit; 230 | font-weight: inherit; 231 | } 232 | 233 | h3 code, h3 tt { 234 | font-size: inherit; 235 | font-weight: inherit; 236 | } 237 | 238 | h3 code, h3 tt { 239 | font-size: inherit; 240 | font-weight: inherit; 241 | } 242 | 243 | h4 { 244 | font-size: 1.12em; 245 | } 246 | 247 | 248 | /* Preformatted and code */ 249 | 250 | tt, code, pre { 251 | font-family: "Fira Mono", courier; 252 | font-weight: 400; 253 | } 254 | 255 | pre { 256 | padding: 0.1em; 257 | border: 1px solid #eee; 258 | border-radius: 5px; 259 | overflow-x: auto; 260 | } 261 | 262 | p code, li code { 263 | background-color: #f6f8fa; 264 | color: #0d2b3e; 265 | border-radius: 3px; 266 | padding: 0 0.3ex; 267 | } 268 | 269 | p a > code { 270 | color: #2C5CBD; 271 | } 272 | 273 | /* Code blocks (e.g. Examples) */ 274 | 275 | pre code { 276 | font-size: 0.893rem; 277 | } 278 | 279 | /* Code lexemes */ 280 | 281 | .keyword { 282 | font-weight: 500; 283 | } 284 | 285 | /* Module member specification */ 286 | 287 | .spec:not(.include), .spec.include details summary { 288 | background-color: #f6f8fa; 289 | border-radius: 3px; 290 | border-left: 4px solid #5c9cf5; 291 | border-right: 5px solid transparent; 292 | padding: 0.35em 0.5em; 293 | } 294 | 295 | .spec.include details summary:hover { 296 | background-color: #ebeff2; 297 | } 298 | 299 | dl, div.spec, .doc, aside { 300 | margin-bottom: 20px; 301 | } 302 | 303 | dl > dd { 304 | padding: 0.5em; 305 | } 306 | 307 | dd> :first-child { 308 | margin-top: 0; 309 | } 310 | 311 | dl:last-child, dd> :last-child, aside:last-child, article:last-child { 312 | margin-bottom: 0; 313 | } 314 | 315 | dt+dt { 316 | margin-top: 15px; 317 | } 318 | 319 | section+section, section > header + dl { 320 | margin-top: 25px; 321 | } 322 | 323 | .spec.type .variant { 324 | margin-left: 2ch; 325 | } 326 | .spec.type .variant p { 327 | margin: 0; 328 | font-style: italic; 329 | } 330 | .spec.type .record { 331 | margin-left: 2ch; 332 | } 333 | .spec.type .record p { 334 | margin: 0; 335 | font-style: italic; 336 | } 337 | 338 | div.def { 339 | margin-top: 0; 340 | text-indent: -2ex; 341 | padding-left: 2ex; 342 | } 343 | 344 | div.def+div.doc { 345 | margin-left: 1ex; 346 | margin-top: 2.5px 347 | } 348 | 349 | div.doc>*:first-child { 350 | margin-top: 0; 351 | } 352 | 353 | /* The elements other than heading should be wrapped in