diff --git a/CHANGELOG.md b/CHANGELOG.md
index fe0f76e..7102edd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,22 @@
# Karet XHR Changelog
+## 0.7.0
+
+Renamed
+* `upHasFailed` to `upHasErrored`,
+* `downHasFailed` to `downHasErrored`, and
+* `hasFailed` to `hasErrored`
+and also introduced new function
+* `hasFailed`.
+
+`template` and `apply` were changed to process XHRs in parallel using the newly
+added `IdentityParallel` algebra, because that is what one more often wants.
+Note that this doesn't change the behaviour of the `IdentitySucceeded` algebra.
+
+## 0.6.7
+
+`apply` was changed to accept both XHRs and plain values.
+
## 0.6.2
Fix work around for IE11 where `'progress'` events may be sent after `'load'`
diff --git a/README.md b/README.md
index 4477ca4..952b48f 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,13 @@
# [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#karet-xhr) [Karet XHR](#karet-xhr) · [![Gitter](https://img.shields.io/gitter/room/calmm-js/chat.js.svg)](https://gitter.im/calmm-js/chat) [![GitHub stars](https://img.shields.io/github/stars/calmm-js/karet.xhr.svg?style=social)](https://github.com/calmm-js/karet.xhr)
-This library provides a thin wrapper over the standard
-[`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)
-API allowing one to perform XHRs by observing the state of ongoing requests as
-observable [Kefir](https://kefirjs.github.io/kefir/)
-[properties](https://kefirjs.github.io/kefir/#about-observables). The benefit
-of this approach is that it makes it easy to implement many kinds of use cases
-ranging from just getting the response data to visualizing the progress of
-ongoing upload and/or download and displaying potential errors. See also [Karet
-FR](https://github.com/calmm-js/karet.fr).
+This library allows one to [*declare*](#declare)
+[`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)s,
+[*compose*](#compose) them, and [*observe*](#observe) them through
+[Kefir](https://kefirjs.github.io/kefir/)
+[properties](https://kefirjs.github.io/kefir/#about-observables). This makes it
+easy to implement many kinds of use cases ranging from just getting the response
+data to visualizing the progress of non-trivial compositions of ongoing upload
+and/or download requests and displaying potential errors.
Examples:
* The [Giphy](https://codesandbox.io/s/q9j8v8w1nq) CodeSandbox uses this library
@@ -26,86 +25,86 @@ Examples:
## [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#contents) [Contents](#contents)
* [Reference](#reference)
- * [Convenience](#convenience)
- * [`XHR.getJson(url | {url[, ...]}) ~> varies`](#XHR-getJson)
- * [`XHR.performJson(url | {url[, ...]}) ~> xhr`](#XHR-performJson)
- * [`XHR.performWith(url | {...}, url | {...}) ~> xhr`](#XHR-performWith)
- * [`XHR.hasSucceeded(xhr) ~> boolean`](#XHR-hasSucceeded)
- * [Starting](#starting)
- * [`XHR.perform(url | {url[, method, user, password, headers, overrideMimeType, body, responseType, timeout, withCredentials]}) ~> xhr`](#XHR-perform)
- * [Overall state](#overall-state)
- * [`XHR.isXHR(any) ~> boolean`](#XHR-isXHR)
- * [Progression](#progression)
- * [`XHR.isDone(xhr) ~> boolean`](#XHR-isDone)
- * [`XHR.isProgressing(xhr) ~> boolean`](#XHR-isProgressing)
- * [`XHR.isStatusAvailable(xhr) ~> boolean`](#XHR-isStatusAvailable)
- * [End state](#end-state)
- * [`XHR.hasFailed(xhr) ~> boolean`](#XHR-hasFailed)
- * [`XHR.hasTimedOut(xhr) ~> boolean`](#XHR-hasTimedOut)
- * [Errors on failure](#errors-on-failure)
- * [`XHR.errors(xhr) ~> [...exceptions]`](#XHR-errors)
- * [Request status](#request-status)
- * [`XHR.status(xhr) ~> number`](#XHR-status)
- * [`XHR.statusIsHttpSuccess(xhr) ~> boolean`](#XHR-statusIsHttpSuccess)
- * [`XHR.statusText(xhr) ~> string`](#XHR-statusText)
- * [Data transfer](#data-transfer)
- * [`XHR.loaded(xhr) ~> number`](#XHR-loaded)
- * [`XHR.total(xhr) ~> number`](#XHR-total)
- * [Response headers](#response-headers)
- * [`XHR.allResponseHeaders(xhr) ~> string`](#XHR-allResponseHeaders)
- * [`XHR.responseHeader(header, xhr) ~> string`](#XHR-responseHeader)
- * [Response data](#response-data)
- * [`XHR.response(xhr) ~> varies`](#XHR-response)
- * [`XHR.responseText(xhr) ~> string`](#XHR-responseText)
- * [`XHR.responseXML(xhr) ~> document`](#XHR-responseXML)
- * [Response URL](#response-url)
- * [`XHR.responseURL(xhr) ~> string`](#XHR-responseURL)
- * [Request parameters](#request-parameters)
- * [`XHR.responseType(xhr) ~> string`](#XHR-responseType)
- * [`XHR.timeout(xhr) ~> number`](#XHR-timeout)
- * [`XHR.withCredentials(xhr) ~> boolean`](#XHR-withCredentials)
- * [Ready state](#ready-state)
- * [`XHR.readyState(xhr) ~> number`](#XHR-readyState)
- * [Download state](#download-state)
- * [`XHR.downError(xhr) ~> exception`](#XHR-downError)
- * [`XHR.downHasCompleted(xhr) ~> boolean`](#XHR-downHasCompleted)
- * [`XHR.downHasEnded(xhr) ~> boolean`](#XHR-downHasEnded)
- * [`XHR.downHasFailed(xhr) ~> boolean`](#XHR-downHasFailed)
- * [`XHR.downHasStarted(xhr) ~> boolean`](#XHR-downHasStarted)
- * [`XHR.downHasTimedOut(xhr) ~> boolean`](#XHR-downHasTimedOut)
- * [`XHR.downIsProgressing(xhr) ~> boolean`](#XHR-downIsProgressing)
- * [`XHR.downLoaded(xhr) ~> number`](#XHR-downLoaded)
- * [`XHR.downTotal(xhr) ~> number`](#XHR-downTotal)
- * [Upload state](#upload-state)
- * [`XHR.upError(xhr) ~> exception`](#XHR-upError)
- * [`XHR.upHasCompleted(xhr) ~> boolean`](#XHR-upHasCompleted)
- * [`XHR.upHasEnded(xhr) ~> boolean`](#XHR-upHasEnded)
- * [`XHR.upHasFailed(xhr) ~> boolean`](#XHR-upHasFailed)
- * [`XHR.upHasStarted(xhr) ~> boolean`](#XHR-upHasStarted)
- * [`XHR.upHasTimedOut(xhr) ~> boolean`](#XHR-upHasTimedOut)
- * [`XHR.upIsProgressing(xhr) ~> boolean`](#XHR-upIsProgressing)
- * [`XHR.upLoaded(xhr) ~> number`](#XHR-upLoaded)
- * [`XHR.upTotal(xhr) ~> number`](#XHR-upTotal)
- * [Happy path](#happy-path)
- * [`XHR.result(xhr) ~> varies`](#XHR-result)
- * [Happy path algebras](#happy-path-algebras)
- * [`XHR.IdentitySucceeded ~> Monad`](#XHR-IdentitySucceeded)
- * [`XHR.Succeeded ~> Monad`](#XHR-Succeeded)
- * [Monadic happy path combinators](#monadic-happy-path-combinators)
+ * [Just give me the data!](#just-give-me-the-data)
+ * [`XHR.getJson(url | {url[, ...options]}) ~> varies`](#XHR-getJson)
+ * [Declare](#declare)
+ * [`XHR.perform(url | {url[, ...options]}) ~> xhr`](#XHR-perform)
+ * [`XHR.performJson(url | {url[, ...options]}) ~> xhr`](#XHR-performJson)
+ * [`XHR.performWith(url | {...options}, url | {...options}) ~> xhr`](#XHR-performWith)
+ * [Compose](#compose)
+ * [Basic combinators](#basic-combinators)
* [`XHR.ap(xhrAtoB, xhrA) ~> xhrB`](#XHR-ap)
- * [`XHR.chain(responseA => xhrB, xhrA) ~> xhrB`](#XHR-chain)
- * [`XHR.map(responseA => responseB, xhrA) ~> xhrB`](#XHR-map)
- * [`XHR.of(response) ~> xhr`](#XHR-of)
- * [Additional happy path combinators](#additional-happy-path-combinators)
- * [`XHR.apply((...responseAs) => responseB, [...xhrAs]) ~> xhrB`](#XHR-apply)
+ * [`XHR.chain(A => xhrB, xhrA) ~> xhrB`](#XHR-chain)
+ * [`XHR.map(A => B, xhrA) ~> xhrB`](#XHR-map)
+ * [`XHR.of(A) ~> xhrA`](#XHR-of)
+ * [Additional combinators](#additional-combinators)
+ * [`XHR.apply((...As) => B, [...xhrAs]) ~> xhrB`](#XHR-apply)
* [`XHR.template([ ... xhr ... ] | { ... xhr ... }) ~> xhr`](#XHR-template)
+ * [Algebras](#algebras)
+ * [`XHR.IdentityParallel ~> applicative`](#XHR-IdentityParallel)
+ * [`XHR.IdentitySucceeded ~> monad`](#XHR-IdentitySucceeded)
+ * [`XHR.Parallel ~> applicative`](#XHR-Parallel)
+ * [`XHR.Succeeded ~> monad`](#XHR-Succeeded)
+ * [Observe](#observe)
+ * [Result](#result)
+ * [`XHR.hasFailed(xhr) ~> boolean`](#XHR-hasFailed)
+ * [`XHR.hasSucceeded(xhr) ~> boolean`](#XHR-hasSucceeded)
+ * [`XHR.result(xhrA) ~> A`](#XHR-result)
+ * [Overall state](#overall-state)
+ * [Progression](#progression)
+ * [`XHR.isDone(xhr) ~> boolean`](#XHR-isDone)
+ * [`XHR.isProgressing(xhr) ~> boolean`](#XHR-isProgressing)
+ * [`XHR.isStatusAvailable(xhr) ~> boolean`](#XHR-isStatusAvailable)
+ * [End state](#end-state)
+ * [`XHR.hasErrored(xhr) ~> boolean`](#XHR-hasErrored)
+ * [`XHR.hasTimedOut(xhr) ~> boolean`](#XHR-hasTimedOut)
+ * [Errors on failure](#errors-on-failure)
+ * [`XHR.errors(xhr) ~> [...exceptions]`](#XHR-errors)
+ * [Request status](#request-status)
+ * [`XHR.status(xhr) ~> number`](#XHR-status)
+ * [`XHR.statusIsHttpSuccess(xhr) ~> boolean`](#XHR-statusIsHttpSuccess)
+ * [`XHR.statusText(xhr) ~> string`](#XHR-statusText)
+ * [Data transfer](#data-transfer)
+ * [`XHR.loaded(xhr) ~> number`](#XHR-loaded)
+ * [`XHR.total(xhr) ~> number`](#XHR-total)
+ * [Response headers](#response-headers)
+ * [`XHR.allResponseHeaders(xhr) ~> string`](#XHR-allResponseHeaders)
+ * [`XHR.responseHeader(header, xhr) ~> string`](#XHR-responseHeader)
+ * [Response data](#response-data)
+ * [`XHR.response(xhr) ~> varies`](#XHR-response)
+ * [`XHR.responseText(xhr) ~> string`](#XHR-responseText)
+ * [`XHR.responseXML(xhr) ~> document`](#XHR-responseXML)
+ * [Response URL](#response-url)
+ * [`XHR.responseURL(xhr) ~> string`](#XHR-responseURL)
+ * [Request parameters](#request-parameters)
+ * [`XHR.responseType(xhr) ~> string`](#XHR-responseType)
+ * [`XHR.timeout(xhr) ~> number`](#XHR-timeout)
+ * [`XHR.withCredentials(xhr) ~> boolean`](#XHR-withCredentials)
+ * [Ready state](#ready-state)
+ * [`XHR.readyState(xhr) ~> number`](#XHR-readyState)
+ * [Download state](#download-state)
+ * [`XHR.downError(xhr) ~> exception`](#XHR-downError)
+ * [`XHR.downHasCompleted(xhr) ~> boolean`](#XHR-downHasCompleted)
+ * [`XHR.downHasEnded(xhr) ~> boolean`](#XHR-downHasEnded)
+ * [`XHR.downHasErrored(xhr) ~> boolean`](#XHR-downHasErrored)
+ * [`XHR.downHasStarted(xhr) ~> boolean`](#XHR-downHasStarted)
+ * [`XHR.downHasTimedOut(xhr) ~> boolean`](#XHR-downHasTimedOut)
+ * [`XHR.downIsProgressing(xhr) ~> boolean`](#XHR-downIsProgressing)
+ * [`XHR.downLoaded(xhr) ~> number`](#XHR-downLoaded)
+ * [`XHR.downTotal(xhr) ~> number`](#XHR-downTotal)
+ * [Upload state](#upload-state)
+ * [`XHR.upError(xhr) ~> exception`](#XHR-upError)
+ * [`XHR.upHasCompleted(xhr) ~> boolean`](#XHR-upHasCompleted)
+ * [`XHR.upHasEnded(xhr) ~> boolean`](#XHR-upHasEnded)
+ * [`XHR.upHasErrored(xhr) ~> boolean`](#XHR-upHasErrored)
+ * [`XHR.upHasStarted(xhr) ~> boolean`](#XHR-upHasStarted)
+ * [`XHR.upHasTimedOut(xhr) ~> boolean`](#XHR-upHasTimedOut)
+ * [`XHR.upIsProgressing(xhr) ~> boolean`](#XHR-upIsProgressing)
+ * [`XHR.upLoaded(xhr) ~> number`](#XHR-upLoaded)
+ * [`XHR.upTotal(xhr) ~> number`](#XHR-upTotal)
* [Auxiliary](#auxiliary)
* [`XHR.isHttpSuccess(number) ~> boolean`](#XHR-isHttpSuccess)
- * [Deprecated](#deprecated)
- * ~~[`XHR.downHasSucceeded(xhr) ~> boolean`](#XHR-downHasSucceeded)~~
- * ~~[`XHR.headersReceived(xhr) ~> boolean`](#XHR-headersReceived)~~
- * ~~[`XHR.responseFull(xhr) ~> varies`](#XHR-responseFull)~~
- * ~~[`XHR.upHasSucceeded(xhr) ~> boolean`](#XHR-upHasSucceeded)~~
+ * [`XHR.isXHR(any) ~> boolean`](#XHR-isXHR)
## [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#reference) [Reference](#reference)
@@ -116,52 +115,44 @@ imports the library as:
import * as XHR from 'karet.xhr'
```
-Using this library, one first [creates](#starting) an observable property
-representing the state of an
-[`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)
-using [`XHR.perform`](#XHR-perform) and then observes the ongoing XHR state
-using the accessors for [overall](#overall-state), [download](#download-state),
-and [upload](#upload-state) state.
+Using this library, one [declares](#declare) observable
+[`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)s,
+[composes](#compose) them, and then [observes](#observe) the ongoing XHR using
+the accessors for the [result](#result), [overall](#overall-state),
+[download](#download-state), and [upload](#upload-state) state.
-### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#convenience) [Convenience](#convenience)
+### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#just-give-me-the-data) [Just give me the data!](#just-give-me-the-data)
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-getJson) [`XHR.getJson(url | {url,[, ...]}) ~> varies`](#XHR-getJson)
+If you just want to GET some JSON...
-`XHR.getJson(arg)` returns an observable that emits the [full
-response](#XHR-result) after the [XHR has succeeded](#XHR-hasSucceeded). In
-case the XHR fails or times out, the XHR is emitted as an error.
+#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-getJson) [`XHR.getJson(url | {url,[, ...options]}) ~> varies`](#XHR-getJson)
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-peformJson) [`XHR.performJson(url | {url[, ...]}) ~> xhr`](#XHR-performJson)
+`XHR.getJson` returns an observable that emits the [full response](#XHR-result)
+after the [XHR has succeeded](#XHR-hasSucceeded). In case the XHR produces an
+error or times out, the XHR is emitted as an error event. See
+[`XHR.perform`](#XHR-perform) for the options.
-`XHR.performJson` is shorthand for [`XHR.performWith({responseType:
-'json', headers: {'Content-Type': 'application/json'}})`](#XHR-performWith).
-
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-performWith) [`XHR.performWith(url | {...}, url | {...}) ~> xhr`](#XHR-performWith)
-
-`XHR.performWith` is a curried function that allows one to define a
-[`XHR.perform`](#XHR-perform) like function with default parameters. The
-defaults (first parameter) are merged with the overrides (second parameter).
-Headers are also merged. See [`XHR.perform`](#XHR-perform) for the parameters.
+Note that this function is provided for simplistic usages where one does not
+need the full composability and observability advantages of this library.
For example:
-```jsx
-const get = XHR.performWith({responseType: 'json', timeout: 30*1000})
-// ...
-get(url)
+```js
+I.seq(
+ XHR.getJson(`https://api.github.com/search/users?q=polytypic`),
+ R.map(L.get(L.query('html_url'))),
+ log
+)
```
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-hasSucceeded) [`XHR.hasSucceeded(xhr) ~> boolean`](#XHR-hasSucceeded)
-
-`XHR.hasSucceeded` returns a possibly observable boolean property of an ongoing
-XHR that is true if the XHR [is done](#XHR-isDone), its HTTP [status indicates
-success](#XHR-statusIsHttpSuccess), and neither
-[download](#XHR-downHasCompleted) or [upload](#XHR-upHasCompleted) has failed or
-timed out.
+### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#declare) [Declare](#declare)
-### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#starting) [Starting](#starting)
+XHRs are declared by specifying all the parameters that affect the execution of
+an XHR to [`XHR.perform`](#XHR-perform), which then returns an observable
+[property](https://kefirjs.github.io/kefir/#about-observables) that can be
+subscribed to in order to perform the declared XHR.
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-perform) [`XHR.perform(url | {url[, method, user, password, headers, overrideMimeType, body, responseType, timeout, withCredentials]}) ~> xhr`](#XHR-perform)
+#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-perform) [`XHR.perform(url | {url[, ...options]}) ~> xhr`](#XHR-perform)
`XHR.perform` creates an observable
[property](https://kefirjs.github.io/kefir/#about-observables) that represents
@@ -170,7 +161,7 @@ the state of an ongoing
The request is started once the property is subscribed to and is automatically
[aborted](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/abort)
in case the property is fully unsubscribed from before it has ended. See also
-[`XHR.performWith`](#XHR-performWith).
+[`XHR.performWith`](#XHR-performWith) and [`XHR.performJson`](#XHR-performJson).
Only the `url` parameter is required and can be passed as a string. Other
parameters have their XHR default values:
@@ -193,8 +184,8 @@ created by `XHR.perform` performs the XHR with the
[latest](https://kefirjs.github.io/kefir/#flat-map-latest) argument values.
Note that typically one does not explicitly subscribe to the property, but one
-rather computes a desired view of the property, such as a view of the
-[response](#XHR-response), and combines that further into some more interesting
+rather computes a desired view of the property, such as a view of the succeeded
+[response](#XHR-result), and combines that further into some more interesting
property.
WARNING: Setting `responseType` to `'json'` is not supported by IE 11. This
@@ -202,19 +193,173 @@ library implements a workaround by calling `JSON.parse` on the returned data in
case setting `responseType` to `'json'` fails. In case the response does not
parse, then [`XHR.response`](#XHR-response) returns `null`.
-See this live [GitHub repository search](https://codesandbox.io/s/l5271q0r2l)
-CodeSandbox for an example.
+#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-peformJson) [`XHR.performJson(url | {url[, ...options]}) ~> xhr`](#XHR-performJson)
-### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#overall-state) [Overall state](#overall-state)
+`XHR.performJson` is shorthand for [`XHR.performWith({responseType:
+'json', headers: {'Content-Type': 'application/json'}})`](#XHR-performWith).
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-isXHR) [`XHR.isXHR(any) ~> boolean`](#XHR-isXHR)
+#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-performWith) [`XHR.performWith(url | {...options}, url | {...options}) ~> xhr`](#XHR-performWith)
-`XHR.isXHR` returns a possibly observable boolean property that tells whether
-the given value is a XHR.
+`XHR.performWith` is a curried function that allows one to define a
+[`XHR.perform`](#XHR-perform) like function with default parameters. The
+defaults (first parameter) are merged with the overrides (second parameter).
+Headers are also merged. See [`XHR.perform`](#XHR-perform) for the parameters.
+
+For example:
+
+```jsx
+const get = XHR.performWith({responseType: 'json', timeout: 30*100e
+// ..eete(url)
+```
+
+### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#compose) [Compose](#compose)
+
+Multiple XHRs can be composed together to appear and be treated simply as a
+single XHR.
+
+#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#basic-combinators) [Basic combinators](#basic-combinators)
+
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-ap) [`XHR.ap(xhrAtoB, xhrA) ~> xhrB`](#XHR-ap)
+
+`XHR.ap` implements a static land compatible
+[`ap`](https://github.com/rpominov/static-land/blob/master/docs/spec.md#apply)
+function for composing succeeding XHRs. The XHRs are performed sequentially.
+See also [`XHR.apply`](#XHR-apply).
+
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-chain) [`XHR.chain(A => xhrB, xhrA) ~> xhrB`](#XHR-chain)
+
+`XHR.chain` implements a static land compatible
+[`chain`](https://github.com/rpominov/static-land/blob/master/docs/spec.md#chain)
+function for composing succeeding XHRs.
+
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-map) [`XHR.map(A => B, xhrA) ~> xhrB`](#XHR-map)
+
+`XHR.map` implements a static land compatible
+[`map`](https://github.com/rpominov/static-land/blob/master/docs/spec.md#functor)
+function for composing succeeding XHRs.
+
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-of) [`XHR.of(A) ~> xhrA`](#XHR-of)
+
+`XHR.of` implements a static land compatible
+[`of`](https://github.com/rpominov/static-land/blob/master/docs/spec.md#applicative)
+function for composing succeeding XHRs.
+
+#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#additional-combinators) [Additional combinators](#additional-combinators)
+
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-apply) [`XHR.apply((...As) => B, [...xhrAs]) ~> xhrB`](#XHR-apply)
+
+`XHR.apply` maps the given XHRs through the given function. Unlike with
+[`XHR.ap`](#XHR-ap), the XHRs are performed in parallel.
+
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-template) [`XHR.template([ ... xhr ... ] | { ... xhr ... }) ~> xhr`](#XHR-template)
+
+`XHR.template` transforms a nested template of plain arrays and objects possibly
+containing XHRs into a XHR. The XHRs are performed in parallel.
+
+#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#algebras) [Algebras](#algebras)
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#progression) [Progression](#progression)
+[Static Land](https://github.com/rpominov/static-land) compatible algebras can
+be used with other Static Land compatible libraries such as [Partial
+Lenses](https://github.com/calmm-js/partial.lenses/) to perform more complex
+XHRs.
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-isStatusAvailable) [`XHR.isStatusAvailable(xhr) ~> boolean`](#XHR-isStatusAvailable)
+For example:
+
+```js
+I.seq(
+ XHR.performJson(`https://api.github.com/search/repositories?q=user:calmm-js&sort=stars`),
+ XHR.map(
+ L.collect([
+ 'items',
+ L.limit(2, L.flat(L.when(R.has('description')))),
+ L.pick({
+ description: 'description',
+ url: 'svn_url',
+ issues: 'issues_url'
+ })
+ ])
+ ),
+ XHR.chain(
+ L.traverse(
+ XHR.Parallel,
+ issues => I.seq(
+ XHR.performJson(issues.replace(/{.*}$/, '')),
+ XHR.map(
+ L.collect(
+ L.limit(3, L.flat(L.pick({title: 'title', url: 'html_url'})))
+ )
+ )
+ ),
+ [L.elems, 'issues']
+ )
+ ),
+ XHR.result,
+ log
+)
+```
+
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-IdentityParallel) [`XHR.IdentityParallel ~> applicative`](#XHR-IdentityParallel)
+
+`XHR.IdentityParallel` is a static land compatible
+[applicative](https://github.com/rpominov/static-land/blob/master/docs/spec.md#applicative)
+that manipulates XHRs like [`XHR.Parallel`](#XHR-Parallel) or plain data.
+
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-IdentitySucceeded) [`XHR.IdentitySucceeded ~> monad`](#XHR-IdentitySucceeded)
+
+`XHR.IdentitySucceeded` is a static land compatible
+[monad](https://github.com/rpominov/static-land/blob/master/docs/spec.md#monad)
+that manipulates XHRs like [`XHR.Succeeded`](#XHR-Succeeded) or plain data.
+
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-Parallel) [`XHR.Parallel ~> applicative`](#XHR-Parallel)
+
+`XHR.Parallel` is a static land compatible
+[applicative](https://github.com/rpominov/static-land/blob/master/docs/spec.md#applicative)
+that allows one to compose parallel XHR requests. In case any XHR fails, the
+composed XHR produces the first failed XHR. In case all XHRs succeed, the
+composed XHR produces the combined XHR as the [result](#XHR-result).
+
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-Succeeded) [`XHR.Succeeded ~> monad`](#XHR-Succeeded)
+
+`XHR.Succeeded` is a static land compatible
+[monad](https://github.com/rpominov/static-land/blob/master/docs/spec.md#monad)
+comprised of the [`XHR.ap`](#XHR-ap), [`XHR.chain`](#XHR-chain),
+[`XHR.map`](#XHR-map), and [`XHR.of`](#XHR-of) combinators that allows one to
+compose sequences of XHR requests that stop as soon as the first XHR does not
+[succeed](#XHR-hasSucceeded).
+
+### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#observe) [Observe](#observe)
+
+Ongoing XHRs can be observed both for their varying properties such as [the
+number of bytes transferred](#XHR-loaded) and for their [results](#XHR-result).
+
+#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#result) [Result](#result)
+
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-hasFailed) [`XHR.hasFailed(xhr) ~> boolean`](#XHR-hasFailed)
+
+`XHR.hasFailed` returns a possibly observable boolean property of an ongoing XHR
+that is true if its HTTP status does not indicate [success](#XHR-isHttpSuccess)
+or the download or the upload operation has [errored](#XHR-hasErrored) or [timed
+out](#XHR-hasTimedOut).
+
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-hasSucceeded) [`XHR.hasSucceeded(xhr) ~> boolean`](#XHR-hasSucceeded)
+
+`XHR.hasSucceeded` returns a possibly observable boolean property of an ongoing
+XHR that is true if the XHR [is done](#XHR-isDone), its HTTP [status indicates
+success](#XHR-statusIsHttpSuccess), and neither
+[download](#XHR-downHasCompleted) or [upload](#XHR-upHasCompleted) has errored
+or timed out.
+
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-result) [`XHR.result(xhrA) ~> A`](#XHR-result)
+
+`XHR.result` returns the response of a [succeeded](#XHR-hasSucceeded) XHR. Note
+that [`XHR.response`](#XHR-response) allows one to obtain the response before
+the XHR [is done](#XHR-isDone) and even when the XHR has (partially) failed.
+
+#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#overall-state) [Overall state](#overall-state)
+
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#progression) [Progression](#progression)
+
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-isStatusAvailable) [`XHR.isStatusAvailable(xhr) ~> boolean`](#XHR-isStatusAvailable)
`XHR.isStatusAvailable` returns a possibly observable boolean property that
tells whether HTTP status and response headers have been received and can be
@@ -223,49 +368,49 @@ obtained. See also [`XHR.status`](#XHR-status),
[`XHR.allResponseHeaders`](#XHR-allResponseHeaders), and
[`XHR.responseHeader`](#XHR-responseHeader).
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-isDone) [`XHR.isDone(xhr) ~> boolean`](#XHR-isDone)
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-isDone) [`XHR.isDone(xhr) ~> boolean`](#XHR-isDone)
`XHR.isDone` returns a possibly observable boolean property that tells whether
the XHR operation is complete (whether success or failure). See also
[`XHR.hasSucceeded`](#XHR-hasSucceeded).
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-isProgressing) [`XHR.isProgressing(xhr) ~> boolean`](#XHR-isProgressing)
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-isProgressing) [`XHR.isProgressing(xhr) ~> boolean`](#XHR-isProgressing)
`XHR.isProgressing` returns a possibly observable boolean property that tells
whether the XHR operation has started, but has not yet [ended](#XHR-isDone).
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#end-state) [End state](#end-state)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#end-state) [End state](#end-state)
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-hasFailed) [`XHR.hasFailed(xhr) ~> boolean`](#XHR-hasFailed)
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-hasErrored) [`XHR.hasErrored(xhr) ~> boolean`](#XHR-hasErrored)
-`XHR.hasFailed` returns a possibly observable boolean property of an ongoing XHR
-that is true when either [download](#XHR-downHasFailed) or
-[upload](#XHR-upHasFailed) has failed.
+`XHR.hasErrored` returns a possibly observable boolean property of an ongoing
+XHR that is true when either [download](#XHR-downHasErrored) or
+[upload](#XHR-upHasErrored) has errored.
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-hasTimedOut) [`XHR.hasTimedOut(xhr) ~> boolean`](#XHR-hasTimedOut)
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-hasTimedOut) [`XHR.hasTimedOut(xhr) ~> boolean`](#XHR-hasTimedOut)
`XHR.hasTimedOut` returns a possibly observable boolean property of an ongoing
XHR that is true when either [download](#XHR-downHasTimedOut) or
[upload](#XHR-upHasTimedOut) has timed out.
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#errors-on-failure) [Errors on failure](#errors-on-failure)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#errors-on-failure) [Errors on failure](#errors-on-failure)
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-errors) [`XHR.errors(xhr) ~> [...exceptions]`](#XHR-errors)
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-errors) [`XHR.errors(xhr) ~> [...exceptions]`](#XHR-errors)
`XHR.errors` returns a possibly observable array of errors from
[download](#XHR-downError) and [upload](#XHR-upError). The array will contain 0
to 2 errors.
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#request-status) [Request status](#request-status)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#request-status) [Request status](#request-status)
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-status) [`XHR.status(xhr) ~> number`](#XHR-status)
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-status) [`XHR.status(xhr) ~> number`](#XHR-status)
`XHR.status` returns a possibly observable property that emits the
[`status`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/status)
after the HTTP status has been received. When called on a non-observable XHR,
[`readyState` must be 2](#XHR-isStatusAvailable) or an `Error` will be thrown.
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-statusIsHttpSuccess) [`XHR.statusIsHttpSuccess(xhr) ~> boolean`](#XHR-statusIsHttpSuccess)
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-statusIsHttpSuccess) [`XHR.statusIsHttpSuccess(xhr) ~> boolean`](#XHR-statusIsHttpSuccess)
`XHR.statusIsHttpSuccess(xhr)` is shorthand for
`XHR.isHttpSuccess(XHR.status(xhr))`. Note that HTTP status is usually received
@@ -273,28 +418,28 @@ before the [download](#XHR-downHasCompleted) and [upload](#XHR-upHasCompleted)
phases have completed. See also [`XHR.hasSucceeded`](#XHR-hasSucceeded),
[`XHR.status`](#XHR-status) and [`XHR.isHttpSuccess`](#XHR-isHttpSuccess).
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-statusText) [`XHR.statusText(xhr) ~> string`](#XHR-statusText)
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-statusText) [`XHR.statusText(xhr) ~> string`](#XHR-statusText)
`XHR.statusText` returns a possibly observable property of the
[`statusText`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/statusText)
after the HTTP status has been received. When called on a non-observable XHR,
[`readyState` must be 2](#XHR-isStatusAvailable) or an `Error` will be thrown.
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#data-transfer) [Data transfer](#data-transfer)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#data-transfer) [Data transfer](#data-transfer)
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-loaded) [`XHR.loaded(xhr) ~> number`](#XHR-loaded)
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-loaded) [`XHR.loaded(xhr) ~> number`](#XHR-loaded)
`XHR.loaded` returns a possibly observable property of the sum of
[downloaded](#XHR-downLoaded) and [uploaded](#XHR-upLoaded) bytes.
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-total) [`XHR.total(xhr) ~> number`](#XHR-total)
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-total) [`XHR.total(xhr) ~> number`](#XHR-total)
`XHR.loaded` returns a possibly observable property of the sum of [total
download](#XHR-downTotal) and [total upload](#XHR-upTotal) bytes.
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#response-headers) [Response headers](#response-headers)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#response-headers) [Response headers](#response-headers)
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-allResponseHeaders) [`XHR.allResponseHeaders(xhr) ~> string`](#XHR-allResponseHeaders)
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-allResponseHeaders) [`XHR.allResponseHeaders(xhr) ~> string`](#XHR-allResponseHeaders)
`XHR.allResponseHeaders` returns a possibly observable property that emits the
value of
@@ -302,7 +447,7 @@ value of
after the HTTP headers have been received. When called on a non-observable XHR,
its [`readyState` must be 2](#XHR-isStatusAvailable) or an `Error` will be thrown.
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-responseHeader) [`XHR.responseHeader(header, xhr) ~> string`](#XHR-responseHeader)
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-responseHeader) [`XHR.responseHeader(header, xhr) ~> string`](#XHR-responseHeader)
`XHR.responseHeader` returns a possibly observable property that emits the value
of
@@ -311,9 +456,9 @@ for specified `header` after the HTTP headers have been received. When called
on a non-observable XHR, its [`readyState` must be 2](#XHR-isStatusAvailable) or
an `Error` will be thrown.
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#response-data) [Response data](#response-data)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#response-data) [Response data](#response-data)
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-response) [`XHR.response(xhr) ~> varies`](#XHR-response)
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-response) [`XHR.response(xhr) ~> varies`](#XHR-response)
`XHR.response` returns a possibly observable property that emits the
[`response`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/response)
@@ -322,7 +467,7 @@ When called on a non-observable XHR, the download operation must be completed or
an `Error` will be thrown. See also [`XHR.result`](#XHR-result), and
[`XHR.responseText`](#XHR-responseText).
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-responseText) [`XHR.responseText(xhr) ~> string`](#XHR-responseText)
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-responseText) [`XHR.responseText(xhr) ~> string`](#XHR-responseText)
`XHR.responseText` returns a possibly observable property of the
[`responseText`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseText)
@@ -330,7 +475,7 @@ property of an ongoing XHR. `XHR.responseText` is for observing the received
response data before the data has been completely received. See also
[`XHR.response`](#XHR-response).
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-responseXML) [`XHR.responseXML(xhr) ~> document`](#XHR-responseXML)
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-responseXML) [`XHR.responseXML(xhr) ~> document`](#XHR-responseXML)
`XHR.responseXML` returns a possibly observable property of the
[`responseXML`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseXML)
@@ -338,9 +483,9 @@ property after the XHR has completed. When called on a non-observable XHR, the
download operation must be completed or an `Error` will be thrown. See also
[`XHR.response`](#XHR-response).
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#response-url) [Response URL](#response-url)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#response-url) [Response URL](#response-url)
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-responseURL) [`XHR.responseURL(xhr) ~> string`](#XHR-responseURL)
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-responseURL) [`XHR.responseURL(xhr) ~> string`](#XHR-responseURL)
`XHR.responseURL` returns a possibly observable property of the
[`responseURL`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseURL)
@@ -348,61 +493,61 @@ property after the HTTP headers have been received. When called on a
non-observable XHR, its [`readyState` must be 2](#XHR-isStatusAvailable) or an
`Error` will be thrown.
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#request-parameters) [Request parameters](#request-parameters)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#request-parameters) [Request parameters](#request-parameters)
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-responseType) [`XHR.responseType(xhr) ~> string`](#XHR-responseType)
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-responseType) [`XHR.responseType(xhr) ~> string`](#XHR-responseType)
`XHR.responseType` returns a possibly observable property of the
[`responseType`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType)
of an ongoing XHR.
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-timeout) [`XHR.timeout(xhr) ~> number`](#XHR-timeout)
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-timeout) [`XHR.timeout(xhr) ~> number`](#XHR-timeout)
`XHR.timeout` returns a possibly observable property of the
[`timeout`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/timeout)
property of an ongoing XHR.
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-withCredentials) [`XHR.withCredentials(xhr) ~> boolean`](#XHR-withCredentials)
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-withCredentials) [`XHR.withCredentials(xhr) ~> boolean`](#XHR-withCredentials)
`XHR.withCredentials` returns a possibly observable property of the
[`withCredentials`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials)
property of an ongoing XHR.
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#ready-state) [Ready state](#ready-state)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#ready-state) [Ready state](#ready-state)
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-readyState) [`XHR.readyState(xhr) ~> number`](#XHR-readyState)
+###### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-readyState) [`XHR.readyState(xhr) ~> number`](#XHR-readyState)
`XHR.readyState` returns a possibly observable property of the
[`readyState`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState)
of an ongoing XHR.
-### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#download-state) [Download state](#download-state)
+#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#download-state) [Download state](#download-state)
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-downError) [`XHR.downError(xhr) ~> exception`](#XHR-downError)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-downError) [`XHR.downError(xhr) ~> exception`](#XHR-downError)
`XHR.downError` returns a possibly observable property of the
[`error`](https://developer.mozilla.org/en-US/docs/Web/Events/error) property of
-a failed XHR.
+an errored XHR.
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-downHasEnded) [`XHR.downHasEnded(xhr) ~> boolean`](#XHR-downHasEnded)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-downHasEnded) [`XHR.downHasEnded(xhr) ~> boolean`](#XHR-downHasEnded)
`XHR.downHasEnded` returns a possibly observable boolean property that tells
whether the download operation of an ongoing XHR has
[ended](https://developer.mozilla.org/en-US/docs/Web/Events/loadend).
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-downHasFailed) [`XHR.downHasFailed(xhr) ~> boolean`](#XHR-downHasFailed)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-downHasErrored) [`XHR.downHasErrored(xhr) ~> boolean`](#XHR-downHasErrored)
-`XHR.downHasFailed` returns a possibly observable boolean property that tells
+`XHR.downHasErrored` returns a possibly observable boolean property that tells
whether the download operation of an ongoing XHR has
-[failed](https://developer.mozilla.org/en-US/docs/Web/Events/error).
+[errored](https://developer.mozilla.org/en-US/docs/Web/Events/error).
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-downHasStarted) [`XHR.downHasStarted(xhr) ~> boolean`](#XHR-downHasStarted)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-downHasStarted) [`XHR.downHasStarted(xhr) ~> boolean`](#XHR-downHasStarted)
`XHR.downHasStarted` returns a possibly observable boolean property that tells
whether the download operation of an ongoing XHR has
[started](https://developer.mozilla.org/en-US/docs/Web/Events/loadstart).
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-downHasCompleted) [`XHR.downHasCompleted(xhr) ~> boolean`](#XHR-downHasCompleted)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-downHasCompleted) [`XHR.downHasCompleted(xhr) ~> boolean`](#XHR-downHasCompleted)
`XHR.downHasCompleted` returns a possibly observable boolean property that tells
whether the download operation of an ongoing XHR has been [completed
@@ -410,57 +555,57 @@ successfully](https://developer.mozilla.org/en-US/docs/Web/Events/load). Note
that this does not take into account the HTTP response status, see
[`XHR.status`](#XHR-status) and [`XHR.isHttpSuccess`](#XHR-isHttpSuccess).
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-downHasTimedOut) [`XHR.downHasTimedOut(xhr) ~> boolean`](#XHR-downHasTimedOut)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-downHasTimedOut) [`XHR.downHasTimedOut(xhr) ~> boolean`](#XHR-downHasTimedOut)
`XHR.downHasTimedOut` returns a possibly observable boolean property that tells
whether the download operation of an ongoing XHR has [timed
out](https://developer.mozilla.org/en-US/docs/Web/Events/timeout).
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-downIsProgressing) [`XHR.downIsProgressing(xhr) ~> boolean`](#XHR-downIsProgressing)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-downIsProgressing) [`XHR.downIsProgressing(xhr) ~> boolean`](#XHR-downIsProgressing)
`XHR.downIsProgressing` returns a possibly observable boolean property that
tells whether the download operation of an ongoing XHR is
[progressing](https://developer.mozilla.org/en-US/docs/Web/Events/progress).
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-downLoaded) [`XHR.downLoaded(xhr) ~> number`](#XHR-downLoaded)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-downLoaded) [`XHR.downLoaded(xhr) ~> number`](#XHR-downLoaded)
`XHR.downLoaded` returns a possibly observable property of the
[`loaded`](https://developer.mozilla.org/en-US/docs/Web/Events/progress)
property of an ongoing XHR.
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-downTotal) [`XHR.downTotal(xhr) ~> number`](#XHR-downTotal)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-downTotal) [`XHR.downTotal(xhr) ~> number`](#XHR-downTotal)
`XHR.downTotal` returns a possibly observable property of the
[`total`](https://developer.mozilla.org/en-US/docs/Web/Events/progress) property
of an ongoing XHR.
-### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#upload-state) [Upload state](#upload-state)
+#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#upload-state) [Upload state](#upload-state)
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-upError) [`XHR.upError(xhr) ~> exception`](#XHR-upError)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-upError) [`XHR.upError(xhr) ~> exception`](#XHR-upError)
`XHR.upError` returns a possibly observable property of the
[`error`](https://developer.mozilla.org/en-US/docs/Web/Events/error) property of
-a failed XHR.
+an errored XHR.
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-upHasEnded) [`XHR.upHasEnded(xhr) ~> boolean`](#XHR-upHasEnded)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-upHasEnded) [`XHR.upHasEnded(xhr) ~> boolean`](#XHR-upHasEnded)
`XHR.upHasEnded` returns a possibly observable boolean property that tells
whether the upload operation of an ongoing XHR has
[ended](https://developer.mozilla.org/en-US/docs/Web/Events/loadend).
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-upHasFailed) [`XHR.upHasFailed(xhr) ~> boolean`](#XHR-upHasFailed)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-upHasErrored) [`XHR.upHasErrored(xhr) ~> boolean`](#XHR-upHasErrored)
-`XHR.upHasFailed` returns a possibly observable boolean property that tells
+`XHR.upHasErrored` returns a possibly observable boolean property that tells
whether the upload operation of an ongoing XHR has
-[failed](https://developer.mozilla.org/en-US/docs/Web/Events/error).
+[errored](https://developer.mozilla.org/en-US/docs/Web/Events/error).
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-upHasStarted) [`XHR.upHasStarted(xhr) ~> boolean`](#XHR-upHasStarted)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-upHasStarted) [`XHR.upHasStarted(xhr) ~> boolean`](#XHR-upHasStarted)
`XHR.upHasStarted` returns a possibly observable boolean property that tells
whether the upload operation of an ongoing XHR has
[started](https://developer.mozilla.org/en-US/docs/Web/Events/loadstart).
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-upHasCompleted) [`XHR.upHasCompleted(xhr) ~> boolean`](#XHR-upHasCompleted)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-upHasCompleted) [`XHR.upHasCompleted(xhr) ~> boolean`](#XHR-upHasCompleted)
`XHR.upHasCompleted` returns a possibly observable boolean property that tells
whether the upload operation of an ongoing XHR has [completed
@@ -468,92 +613,30 @@ successfully](https://developer.mozilla.org/en-US/docs/Web/Events/load). Note
that this does not take into account the HTTP response status, see
[`XHR.status`](#XHR-status) and [`XHR.isHttpSuccess`](#XHR-isHttpSuccess).
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-upHasTimedOut) [`XHR.upHasTimedOut(xhr) ~> boolean`](#XHR-upHasTimedOut)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-upHasTimedOut) [`XHR.upHasTimedOut(xhr) ~> boolean`](#XHR-upHasTimedOut)
`XHR.upHasTimedOut` returns a possibly observable boolean property that tells
whether the upload operation of an ongoing XHR has [timed
out](https://developer.mozilla.org/en-US/docs/Web/Events/timeout).
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-upIsProgressing) [`XHR.upIsProgressing(xhr) ~> boolean`](#XHR-upIsProgressing)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-upIsProgressing) [`XHR.upIsProgressing(xhr) ~> boolean`](#XHR-upIsProgressing)
`XHR.upIsProgressing` returns a possibly observable boolean property that tells
whether the upload operation of an ongoing XHR is
[progressing](https://developer.mozilla.org/en-US/docs/Web/Events/progress).
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-upLoaded) [`XHR.upLoaded(xhr) ~> number`](#XHR-upLoaded)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-upLoaded) [`XHR.upLoaded(xhr) ~> number`](#XHR-upLoaded)
`XHR.upLoaded` returns a possibly observable property of the
[`loaded`](https://developer.mozilla.org/en-US/docs/Web/Events/progress)
property of an ongoing XHR.
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-upTotal) [`XHR.upTotal(xhr) ~> number`](#XHR-upTotal)
+##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-upTotal) [`XHR.upTotal(xhr) ~> number`](#XHR-upTotal)
`XHR.upTotal` returns a possibly observable property of the
[`total`](https://developer.mozilla.org/en-US/docs/Web/Events/progress) property
of an ongoing XHR.
-### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#happy-path) [Happy path](#happy-path)
-
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-result) [`XHR.result(xhr) ~> varies`](#XHR-result)
-
-`XHR.result` returns the response of a [succeeded](#XHR-hasSucceeded) XHR. Note
-that [`XHR.response`](#XHR-response) allows one to obtain the response before
-the XHR [is done](#XHR-isDone) and even when the XHR has (partially) failed.
-
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#happy-path-algebras) [Happy path algebras](#happy-path-algebras)
-
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-Succeeded) [`XHR.Succeeded ~> Monad`](#XHR-Succeeded)
-
-`XHR.Succeeded` is a static land compatible
-[monad](https://github.com/rpominov/static-land/blob/master/docs/spec.md#monad)
-comprised of the [`XHR.ap`](#XHR-ap), [`XHR.chain`](#XHR-chain),
-[`XHR.map`](#XHR-map), and [`XHR.of`](#XHR-of) combinators that allows one to
-compose sequences of XHR requests that stop as soon as the first XHR does not
-[succeed](#XHR-hasSucceeded).
-
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-IdentitySucceeded) [`XHR.IdentitySucceeded ~> Monad`](#XHR-IdentitySucceeded)
-
-`XHR.IdentitySucceeded` is a static land compatible
-[monad](https://github.com/rpominov/static-land/blob/master/docs/spec.md#monad)
-that manipulates XHRs like [`XHR.Succeeded`](#XHR-Succeeded) or plain data.
-
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#monadic-happy-path-combinators) [Monadic happy path combinators](#monadic-happy-path-combinators)
-
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-ap) [`XHR.ap(xhrAtoB, xhrA) ~> xhrB`](#XHR-ap)
-
-`XHR.ap` implements a static land compatible
-[`ap`](https://github.com/rpominov/static-land/blob/master/docs/spec.md#apply)
-function for composing succeeding XHRs.
-
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-chain) [`XHR.chain(responseA => xhrB, xhrA) ~> xhrB`](#XHR-chain)
-
-`XHR.chain` implements a static land compatible
-[`chain`](https://github.com/rpominov/static-land/blob/master/docs/spec.md#chain)
-function for composing succeeding XHRs.
-
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-map) [`XHR.map(responseA => responseB, xhrA) ~> xhrB`](#XHR-map)
-
-`XHR.map` implements a static land compatible
-[`map`](https://github.com/rpominov/static-land/blob/master/docs/spec.md#functor)
-function for composing succeeding XHRs.
-
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-of) [`XHR.of(response) ~> xhr`](#XHR-of)
-
-`XHR.of` implements a static land compatible
-[`of`](https://github.com/rpominov/static-land/blob/master/docs/spec.md#applicative)
-function for composing succeeding XHRs.
-
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#additional-happy-path-combinators) [Additional happy path combinators](#additional-happy-path-combinators)
-
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-apply) [`XHR.apply((...responseAs) => responseB, [...xhrAs]) ~> xhrB`](#XHR-apply)
-
-`XHR.apply` maps the given XHRs through the given function.
-
-##### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-template) [`XHR.template([ ... xhr ... ] | { ... xhr ... }) ~> xhr`](#XHR-template)
-
-`XHR.template` transforms a nested template of plain arrays and objects possibly
-containing XHRs into a XHR.
-
### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#auxiliary) [Auxiliary](#auxiliary)
#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-isHttpSuccess) [`XHR.isHttpSuccess(number) ~> boolean`](#XHR-isHttpSuccess)
@@ -563,23 +646,7 @@ numeric property is in the range 2xx of [HTTP success
codes](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_Success).
See also [`XHR.statusIsHttpSuccess`](#XHR-statusIsHttpSuccess).
-### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#deprecated) [Deprecated](#deprecated)
-
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-downHasSucceeded) ~~[`XHR.downHasSucceeded(xhr) ~> boolean`](#XHR-downHasSucceeded)~~
-
-`XHR.downHasSucceeded` has been renamed to
-[`XHR.downHasCompleted`](#XHR-downHasCompleted).
-
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-headersReceived) ~~[`XHR.headersReceived(xhr) ~> boolean`](#XHR-headersReceived)~~
-
-`XHR.headersReceived` has been renamed to
-[`XHR.isStatusAvailable`](#XHR-isStatusAvailable).
-
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-responseFull) ~~[`XHR.responseFull(xhr) ~> varies`](#XHR-responseFull)~~
-
-`XHR.responseFull` has been renamed to [`XHR.response`](#XHR-response).
-
-#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-upHasSucceeded) ~~[`XHR.upHasSucceeded(xhr) ~> boolean`](#XHR-upHasSucceeded)~~
+#### [≡](#contents) [▶](https://calmm-js.github.io/karet.xhr/index.html#XHR-isXHR) [`XHR.isXHR(any) ~> boolean`](#XHR-isXHR)
-`XHR.upHasSucceeded` has been renamed to
-[`XHR.upHasCompleted`](#XHR-upHasCompleted).
+`XHR.isXHR` returns a possibly observable boolean property that tells whether
+the given value is a XHR.
diff --git a/docs/setup.js b/docs/setup.js
index 5ae1bd4..3342afd 100644
--- a/docs/setup.js
+++ b/docs/setup.js
@@ -1,2 +1,16 @@
window.L = window.Kefir.partial.lenses
window.XHR = window.karet.xhr
+window.log = function() {
+ var Kefir = window.Kefir
+ var log = console.log
+ var xs = []
+ for (let i = 0; i < arguments.length; ++i) {
+ var x = arguments[i]
+ xs.push(x instanceof Kefir.Observable ? x : Kefir.constant(x))
+ }
+ var log = console.log
+ function logs(xs) {
+ log.apply(console, xs)
+ }
+ Kefir.combine(xs).observe(logs, logs)
+}
diff --git a/package-lock.json b/package-lock.json
index f6774ac..2a508e1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "karet.xhr",
- "version": "0.6.7",
+ "version": "0.7.0-0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -3889,6 +3889,16 @@
"partial.lenses": "^14.10.0"
}
},
+ "kefir.ramda": {
+ "version": "0.25.6",
+ "resolved": "https://registry.npmjs.org/kefir.ramda/-/kefir.ramda-0.25.6.tgz",
+ "integrity": "sha512-qw5BVPAD2ahZhGNwsc2nOEnhFW6aquX9LZ13Hp1s9cDilhGczluxrfe0pqB+j5y/cSV3AyhapgpOj87SqShQTw==",
+ "dev": true,
+ "requires": {
+ "karet.lift": "^2.1.0",
+ "ramda": "^0.25.0"
+ }
+ },
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
diff --git a/package.json b/package.json
index 95bbb92..a047495 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "karet.xhr",
- "version": "0.6.7",
+ "version": "0.7.0-0",
"description": "An observable wrapper for XMLHttpRequest using Kefir",
"module": "dist/karet.xhr.es.js",
"main": "dist/karet.xhr.cjs.js",
@@ -46,12 +46,12 @@
"eslint": "^5.6.1",
"express": "^4.16.3",
"kefir": "^3.8.5",
+ "kefir.ramda": "^0.25.5",
"klipse-github-docs-generator": "^0.3.7",
"livereload": "^0.7.0",
"mocha": "^5.2.0",
"mocha-headless-chrome": "^2.0.1",
"nyc": "^12.0.1",
- "ramda": "^0.25.0",
"rollup": "^0.66.4",
"rollup-plugin-babel": "^3.0.7",
"rollup-plugin-commonjs": "^9.1.8",
diff --git a/src/delay-unsub.js b/src/delay-unsub.js
new file mode 100644
index 0000000..9b5edbc
--- /dev/null
+++ b/src/delay-unsub.js
@@ -0,0 +1,51 @@
+import * as I from 'infestines'
+import * as K from 'kefir'
+
+const TIMER = 't'
+const SOURCE = 's'
+const HANDLER = 'h'
+
+const TYPE = 'type'
+const VALUE = 'value'
+const END = 'end'
+
+const DelayUnsub = I.inherit(
+ function DelayUnsub(source) {
+ const self = this
+ K.Property.call(self)
+ self[SOURCE] = source
+ self[HANDLER] = self[TIMER] = 0
+ },
+ K.Property,
+ {
+ _onActivation() {
+ const self = this
+ if (self[TIMER]) {
+ clearTimeout(self[TIMER])
+ self[TIMER] = 0
+ } else {
+ self[SOURCE].onAny(
+ (self[HANDLER] = e => {
+ const t = e[TYPE]
+ if (t === VALUE) {
+ self._emitValue(e[VALUE])
+ } else if (t === END) {
+ self._emitEnd()
+ } else {
+ self._emitError(e[VALUE])
+ }
+ })
+ )
+ }
+ },
+ _onDeactivation() {
+ const self = this
+ self[TIMER] = setTimeout(() => {
+ self[SOURCE].offAny(self[HANDLER])
+ self[HANDLER] = this[TIMER] = 0
+ }, 0)
+ }
+ }
+)
+
+export const delayUnsub = source => new DelayUnsub(source)
diff --git a/src/karet.xhr.js b/src/karet.xhr.js
index 2ba2cf7..742b8b9 100644
--- a/src/karet.xhr.js
+++ b/src/karet.xhr.js
@@ -6,6 +6,10 @@ import * as V from 'partial.lenses.validation'
//
+import {delayUnsub} from './delay-unsub'
+
+//
+
const isObservable = x => x instanceof K.Observable
const toObservable = x => (isObservable(x) ? x : K.constant(x))
@@ -30,13 +34,14 @@ const filter = I.curry(function filter(pr, xs) {
}
})
-const flatMapLatestToProperty = I.curry(function flatMapLatestToProperty(
- fn,
- x
-) {
+const flatMapProperty = I.curry(function flatMapProperty(xyO, x) {
return isObservable(x)
- ? toProperty(x.flatMapLatest(I.pipe2U(fn, toObservable)))
- : toProperty(fn(x))
+ ? toProperty(x.flatMapLatest(I.pipe2U(xyO, toObservable)))
+ : toProperty(xyO(x))
+})
+
+const mapProperty = I.curry(function map(xy, x) {
+ return isObservable(x) ? toProperty(x.map(xy)) : xy(x)
})
//
@@ -86,7 +91,7 @@ const typeInitial = I.freeze({type: INITIAL})
const typeLoadend = I.freeze({type: LOADEND})
const typeLoad = I.freeze({type: LOAD})
-const eventTypes = [LOADSTART, PROGRESS, TIMEOUT, LOAD, ERROR]
+const eventTypes = [LOADSTART, PROGRESS, LOAD, TIMEOUT, ERROR]
const hasKeys = x => I.isFunction(x.keys)
@@ -120,57 +125,59 @@ const performPlain = (process.env.NODE_ENV === 'production'
V.accept
)
))(function perform(args) {
- return K.stream(({emit, end}) => {
- const url = args.url
- const method = args.method
- const user = args.user
- const password = args.password
- const headers = args.headers
- const overrideMimeType = args[OVERRIDE_MIME_TYPE]
- const body = args.body
- const responseType = args[RESPONSE_TYPE]
- const timeout = args[TIMEOUT]
- const withCredentials = args[WITH_CREDENTIALS]
-
- const xhr = new XMLHttpRequest()
- let state = {xhr, up: typeInitial, down: typeInitial, map: I.id}
- const update = (dir, type) => event => {
- if (type !== PROGRESS || state[dir].type !== LOAD)
- emit((state = L.set(dir, {type, event}, state)))
- }
- eventTypes.forEach(type => {
- xhr[ADD_EVENT_LISTENER](type, update(DOWN, type))
- xhr.upload[ADD_EVENT_LISTENER](type, update(UP, type))
- })
- xhr[ADD_EVENT_LISTENER](READYSTATECHANGE, event => {
- emit((state = L.set(EVENT, event, state)))
- })
- xhr[ADD_EVENT_LISTENER](LOADEND, event => {
- end(emit((state = L.set(EVENT, event, state))))
+ return delayUnsub(
+ K.stream(({emit, end}) => {
+ const url = args.url
+ const method = args.method
+ const user = args.user
+ const password = args.password
+ const headers = args.headers
+ const overrideMimeType = args[OVERRIDE_MIME_TYPE]
+ const body = args.body
+ const responseType = args[RESPONSE_TYPE]
+ const timeout = args[TIMEOUT]
+ const withCredentials = args[WITH_CREDENTIALS]
+
+ const xhr = new XMLHttpRequest()
+ let state = {xhr, up: typeInitial, down: typeInitial, map: I.id}
+ const update = (dir, type) => event => {
+ if (type !== PROGRESS || state[dir].type !== LOAD)
+ emit((state = L.set(dir, {type, event}, state)))
+ }
+ eventTypes.forEach(type => {
+ xhr[ADD_EVENT_LISTENER](type, update(DOWN, type))
+ xhr.upload[ADD_EVENT_LISTENER](type, update(UP, type))
+ })
+ xhr[ADD_EVENT_LISTENER](READYSTATECHANGE, event => {
+ emit((state = L.set(EVENT, event, state)))
+ })
+ xhr[ADD_EVENT_LISTENER](LOADEND, event => {
+ end(emit((state = L.set(EVENT, event, state))))
+ })
+ xhr.open(
+ isNil(method) ? 'GET' : method,
+ url,
+ true,
+ isNil(user) ? null : user,
+ isNil(password) ? null : password
+ )
+ if (responseType) {
+ xhr[RESPONSE_TYPE] = responseType
+ if (responseType === JSON_ && xhr[RESPONSE_TYPE] !== JSON_)
+ state = L.set(MAP, tryParse, state)
+ }
+ if (timeout) xhr[TIMEOUT] = timeout
+ if (withCredentials) xhr[WITH_CREDENTIALS] = withCredentials
+ for (const header in headers) {
+ xhr.setRequestHeader(header, headers[header])
+ }
+ if (overrideMimeType) xhr[OVERRIDE_MIME_TYPE](overrideMimeType)
+ xhr.send(isNil(body) ? null : body)
+ return () => {
+ if (!xhr[STATUS]) xhr.abort()
+ }
})
- xhr.open(
- isNil(method) ? 'GET' : method,
- url,
- true,
- isNil(user) ? null : user,
- isNil(password) ? null : password
- )
- if (responseType) {
- xhr[RESPONSE_TYPE] = responseType
- if (responseType === JSON_ && xhr[RESPONSE_TYPE] !== JSON_)
- state = L.set(MAP, tryParse, state)
- }
- if (timeout) xhr[TIMEOUT] = timeout
- if (withCredentials) xhr[WITH_CREDENTIALS] = withCredentials
- for (const header in headers) {
- xhr.setRequestHeader(header, headers[header])
- }
- if (overrideMimeType) xhr[OVERRIDE_MIME_TYPE](overrideMimeType)
- xhr.send(isNil(body) ? null : body)
- return () => {
- if (!xhr[STATUS]) xhr.abort()
- }
- })
+ )
})
const toLowerKeyedObject = L.get([
@@ -220,7 +227,7 @@ const normalizeOptions = (process.env.NODE_ENV === 'production'
)
export const perform = setName(
- I.pipe2U(normalizeOptions, flatMapLatestToProperty(performPlain)),
+ I.pipe2U(normalizeOptions, flatMapProperty(performPlain)),
'perform'
)
@@ -238,10 +245,12 @@ const hasStartedOn = is(eventTypes)
const isProgressingOn = is([PROGRESS, LOADSTART])
const load = [LOAD]
const hasCompletedOn = is(load)
-const hasFailedOn = is([ERROR])
+const hasErroredOn = is([ERROR])
const hasTimedOutOn = is([TIMEOUT])
-const ended = [LOAD, ERROR, TIMEOUT]
+const ended = [LOAD, TIMEOUT, ERROR]
const hasEndedOn = is(ended)
+const failed = [ERROR, TIMEOUT]
+const hasFailedOn = is(failed)
const event = I.curry((prop, op, dir) => op([dir, EVENT, prop]))
const loadedOn = event(LOADED, L.sum)
@@ -261,7 +270,8 @@ const either = L.branches(DOWN, UP)
export const upHasStarted = setName(hasStartedOn(UP), 'upHasStarted')
export const upIsProgressing = setName(isProgressingOn(UP), 'upIsProgressing')
export const upHasCompleted = setName(hasCompletedOn(UP), 'upHasCompleted')
-export const upHasFailed = setName(hasFailedOn(UP), 'upHasFailed')
+export const upHasFailed = setName(hasErroredOn(UP), 'upHasFailed')
+export const upHasErrored = setName(hasErroredOn(UP), 'upHasErrored')
export const upHasTimedOut = setName(hasTimedOutOn(UP), 'upHasTimedOut')
export const upHasEnded = setName(hasEndedOn(UP), 'upHasEnded')
export const upLoaded = setName(loadedOn(UP), 'upLoaded')
@@ -278,6 +288,7 @@ export const downHasCompleted = setName(
'downHasCompleted'
)
export const downHasFailed = setName(hasFailedOn(DOWN), 'downHasFailed')
+export const downHasErrored = setName(hasErroredOn(DOWN), 'downHasErrored')
export const downHasTimedOut = setName(hasTimedOutOn(DOWN), 'downHasTimedOut')
export const downHasEnded = setName(hasEndedOn(DOWN), 'downHasEnded')
export const downLoaded = setName(loadedOn(DOWN), 'downLoaded')
@@ -291,7 +302,7 @@ export const isStatusAvailable = setName(
)
export const isDone = setName(is([LOADEND], EVENT), 'isDone')
export const isProgressing = setName(isProgressingOn(either), 'isProgressing')
-export const hasFailed = setName(hasFailedOn(either), 'hasFailed')
+export const hasErrored = setName(hasErroredOn(either), 'hasErrored')
export const hasTimedOut = setName(hasTimedOutOn(either), 'hasTimedOut')
export const loaded = setName(loadedOn(either), 'loaded')
export const total = setName(totalOn(either), 'total')
@@ -399,13 +410,21 @@ export const hasSucceeded = setName(
'hasSucceeded'
)
+export const hasFailed = F.lift(function hasFailed(xhr) {
+ return (
+ (isStatusAvailable(xhr) && !statusIsHttpSuccess(xhr)) ||
+ downHasFailed(xhr) ||
+ upHasFailed(xhr)
+ )
+})
+
export const getJson = setName(
I.pipe2U(
performJson,
- flatMapLatestToProperty(xhr => {
+ flatMapProperty(xhr => {
if (hasSucceeded(xhr)) {
return response(xhr)
- } else if (isDone(xhr) && (hasFailed(xhr) || hasTimedOut(xhr))) {
+ } else if (isDone(xhr) && (hasErrored(xhr) || hasTimedOut(xhr))) {
return K.constantError(xhr)
} else {
return never
@@ -420,30 +439,30 @@ export const result = setName(getAfter(hasSucceeded, response), 'result')
const getAllResponseHeaders = I.always('')
const getResponseHeader = I.always(null)
-export const of = response => ({
- event: typeLoadend,
- up: typeInitial,
- down: typeLoad,
- xhr: {
- getAllResponseHeaders,
- getResponseHeader,
- readyState: 4,
- response,
- status: 200,
- statusText: 'OK'
- },
- map: I.id
+export const of = F.lift(function of(response) {
+ const source = arguments.length > 1 && arguments[1]
+ return {
+ event: typeLoadend,
+ up: source ? source[UP] : typeInitial,
+ down: source ? source[DOWN] : typeLoad,
+ xhr: {
+ getAllResponseHeaders,
+ getResponseHeader,
+ readyState: 4,
+ response,
+ status: 200,
+ statusText: 'OK'
+ },
+ map: I.id
+ }
})
export const chain = I.curry(function chain(fn, xhr) {
- return flatMapLatestToProperty(
- x => (hasSucceeded(x) ? fn(response(x)) : x),
- xhr
- )
+ return flatMapProperty(x => (hasSucceeded(x) ? fn(response(x)) : x), xhr)
})
export const map = I.curry(function map(fn, xhr) {
- return chain(I.pipe2U(fn, of), xhr)
+ return chain(response => of(fn(response), xhr), xhr)
})
export const ap = I.curry(function ap(f, x) {
@@ -452,6 +471,53 @@ export const ap = I.curry(function ap(f, x) {
export const Succeeded = I.Monad(map, of, ap, chain)
+const readyState1 = I.freeze({readyState: 1})
+
+//
+
+const mergeTypes = (dir, lhs, rhs) => {
+ const lhsIndex = eventTypes.indexOf(lhs)
+ const rhsIndex = eventTypes.indexOf(rhs)
+ return ((rhsIndex - lhsIndex + dir) ^ dir) < 0 ? lhs : rhs
+}
+
+const mergeEvents = (dir, lhs, rhs) => ({
+ type: mergeTypes(dir, lhs[TYPE], rhs[TYPE]),
+ event: {
+ total: (L.get(TOTAL, lhs[EVENT]) || 0) + (L.get(TOTAL, rhs[EVENT]) || 0),
+ loaded: (L.get(LOADED, lhs[EVENT]) || 0) + (L.get(LOADED, rhs[EVENT]) || 0)
+ }
+})
+
+const mergeXHRs = (dir, lhs, rhs) => ({
+ xhr: readyState1,
+ up: mergeEvents(dir, lhs[UP], rhs[UP]),
+ down: mergeEvents(dir, lhs[DOWN], rhs[DOWN]),
+ map: I.id
+})
+
+export const apParallel = I.curry(function apParallel(f, x) {
+ return flatMapProperty(
+ f =>
+ hasFailed(f)
+ ? f
+ : mapProperty(
+ x =>
+ hasSucceeded(f)
+ ? hasSucceeded(x)
+ ? of(result(f)(result(x)), mergeXHRs(0, f, x))
+ : hasFailed(x)
+ ? x
+ : mergeXHRs(-1, f, x)
+ : mergeXHRs(-1, f, x),
+ x
+ ),
+ f
+ )
+})
+
+export const Parallel = I.Applicative(map, of, apParallel)
+
const typeIsString = [TYPE, I.isString]
export const isXHR = setName(
@@ -467,10 +533,11 @@ export const isXHR = setName(
)
export const IdentitySucceeded = I.IdentityOrU(isXHR, Succeeded)
+export const IdentityParallel = I.IdentityOrU(isXHR, Parallel)
export const template = setName(
L.get([
- L.traverse(IdentitySucceeded, I.id, F.inTemplate(isXHR)),
+ L.traverse(IdentityParallel, I.id, F.inTemplate(isXHR)),
L.ifElse(isXHR, [], of)
]),
'template'
@@ -479,24 +546,3 @@ export const template = setName(
export const apply = I.curry(function apply(f, xs) {
return map(xs => f.apply(null, xs), template(xs))
})
-
-const renamed =
- process.env.NODE_ENV === 'production'
- ? x => x
- : function renamed(fn, name) {
- let warned = false
- return setName(function deprecated(x) {
- if (!warned) {
- warned = true
- console.warn(
- 'karet.xhr: `' + name + '` has been renamed to `' + fn.name + '`'
- )
- }
- return fn(x)
- }, name)
- }
-
-export const downHasSucceeded = renamed(downHasCompleted, 'downHasSucceeded')
-export const headersReceived = renamed(isStatusAvailable, 'headersReceived')
-export const responseFull = renamed(response, 'responseFull')
-export const upHasSucceeded = renamed(upHasCompleted, 'upHasSucceeded')
diff --git a/test/tests.js b/test/tests.js
index 99328d6..f60de93 100644
--- a/test/tests.js
+++ b/test/tests.js
@@ -1,6 +1,6 @@
import * as I from 'infestines'
import * as K from 'kefir'
-import * as R from 'ramda'
+import * as R from 'kefir.ramda'
import * as XHR from './generated/dist/karet.xhr.es.js'
@@ -155,7 +155,7 @@ describe('XHR', () => {
)
)
testEq([false], () =>
- XHR.hasFailed(
+ XHR.hasErrored(
startingWithNonXHRs(XHR.performJson({url: 'http://localhost:3000/json'}))
)
)
@@ -257,25 +257,29 @@ describe('XHR', () => {
testEq(
[
[
- {simonSays: 'Hello, world!'},
- ['constant'],
- ['message from', {user: 'world'}]
+ 29,
+ [
+ {simonSays: 'Hello, world!'},
+ ['constant'],
+ ['message from', {user: 'world'}]
+ ]
]
],
- () =>
- XHR.result(
- XHR.apply((x, y, z) => [x, y, z], [
- {simonSays: XHR.perform('http://localhost:3000/text')},
- [K.constant('constant')],
- ['message from', XHR.performJson('http://localhost:3000/json')]
- ])
- )
+ () => {
+ const xhr = XHR.apply((x, y, z) => [x, y, z], [
+ {simonSays: XHR.perform('http://localhost:3000/text')},
+ [K.constant('constant')],
+ ['message from', XHR.performJson('http://localhost:3000/json')]
+ ])
+ return R.pair(XHR.total(xhr), XHR.result(xhr))
+ }
)
testEq(true, () => XHR.isXHR(XHR.template([1, 2, {x: 3}])))
testEq(
{
allResponseHeaders: '',
errors: [],
+ hasErrored: false,
hasFailed: false,
hasTimedOut: false,
isDone: true,
@@ -292,6 +296,7 @@ describe('XHR', () => {
R.map(fn => fn(XHR.of(101)), {
allResponseHeaders: XHR.allResponseHeaders,
errors: XHR.errors,
+ hasErrored: XHR.hasErrored,
hasFailed: XHR.hasFailed,
hasTimedOut: XHR.hasTimedOut,
isDone: XHR.isDone,
@@ -307,12 +312,6 @@ describe('XHR', () => {
)
})
-describe('XHR deprecated', () => {
- testEq([false, true], () =>
- XHR.downHasSucceeded(XHR.perform('http://localhost:3000/json'))
- )
-})
-
describe('IE11 workarounds', () => {
let progressEvent = null
let progressAction = null