mutoid
Version:
Reactive library for data fetching, caching, state management
150 lines (149 loc) • 5.52 kB
JavaScript
import { chainFirst as chainFirst_ } from 'fp-ts/Chain';
import * as E from 'fp-ts/Either';
import { flow, identity, pipe } from 'fp-ts/function';
import * as R from 'fp-ts-reactive/Observable';
import { concat } from 'rxjs';
import * as RXoP from 'rxjs/operators';
import * as RES from './Resource';
// -------------------------------------------------------------------------------------
// constructors
// -------------------------------------------------------------------------------------
export const init = R.of(RES.init);
export const submitted = R.of(RES.submitted);
export const done = flow(RES.done, R.of);
export const doneObservable = R.map(RES.done);
export const fail = flow(RES.fail, R.of);
export const failObservable = R.map(RES.fail);
export const failAppError = flow(RES.failAppError, R.of);
export const ajaxFail = flow(RES.ajaxFail, R.of);
export const ajaxFailObservable = R.map(RES.ajaxFail);
export const fromAjax = (ajax$, decoders) => concat(submitted, ajax$.pipe(RXoP.map(decodeResponse(decoders)), RXoP.catchError((e) => R.of(isAjaxError(e)
? decodeResponse(decoders)(e)
: RES.fail({ type: 'unknownError', detail: e }))))).pipe(RXoP.take(2));
export const doneIO = flow(R.fromIO, doneObservable);
export const fromIO = doneIO;
export const fromObservable = doneObservable;
export const fromEither = flow(RES.fromEither, R.of);
export const fromObservableEither = (oe) => oe.pipe(RXoP.map(RES.fromEither));
export const fromTaskResource = R.fromTask;
export const doneTask = flow(R.fromTask, doneObservable);
export const fromTask = doneTask;
export const fromTaskEither = flow(R.fromTask, fromObservableEither);
export const of = done;
// -------------------------------------------------------------------------------------
// destructors
// -------------------------------------------------------------------------------------
export const match = flow(RES.match, R.chain);
export const fetchToMutationEffect = (mapTo) => (ax) => flow(ax, o => (s) => o.pipe(RXoP.map(mapTo(s))));
// -------------------------------------------------------------------------------------
// instances
// -------------------------------------------------------------------------------------
/* istanbul ignore next */
const _map_ = (fa, f) => pipe(fa, map(f));
/* istanbul ignore next */
const _bimap = (fea, f, g) => pipe(fea, bimap(f, g));
/* istanbul ignore next */
const _mapLeft = (fea, f) => pipe(fea, mapLeft(f));
/* istanbul ignore next */
const _ap = (fab, fa) => pipe(fab, ap(fa));
/* istanbul ignore next */
const _chain = (ma, f) => pipe(ma, chain(f));
export const URI = 'ObservableResource';
export const Functor = {
URI,
map: _map_,
};
export const Apply = {
URI,
map: _map_,
ap: _ap,
};
export const Bifunctor = {
URI,
bimap: _bimap,
mapLeft: _mapLeft,
};
export const Applicative = {
URI,
map: _map_,
ap: _ap,
of,
};
export const Chain = {
URI,
map: _map_,
ap: _ap,
chain: _chain,
};
export const Monad = {
URI,
map: _map_,
ap: _ap,
of,
chain: _chain,
};
export const MonadObservable = {
URI,
map: _map_,
ap: _ap,
of,
chain: _chain,
fromIO,
fromTask,
fromObservable,
};
// -------------------------------------------------------------------------------------
// type class members
// -------------------------------------------------------------------------------------
export const map = f => R.map(RES.map(f));
export const bimap = flow(RES.bimap, R.map);
export const mapLeft = f => R.map(RES.mapLeft(f));
export const ap = (fa) => flow(R.map(gab => (ga) => RES.ap(ga)(gab)), R.ap(fa));
export const chainW = (f) => (ma) => pipe(ma, R.chain(RES.match(() => init, () => submitted, f, e => fail(e))));
export const chain = chainW;
export const flatten = chain(identity);
// -------------------------------------------------------------------------------------
// combinators
// -------------------------------------------------------------------------------------
export function swap(ma) {
return pipe(ma, match(() => init, () => submitted, a => fail(a), done));
}
export const chainFirst = chainFirst_(Chain);
export const chainFirstW = chainFirst;
export function orElseW(onFail) {
return R.chain(RES.match(() => init, () => submitted, a => done(a), onFail));
}
export const orElse = orElseW;
export const filterOrElseW = (predicate, onFalse) => chainW(a => (predicate(a) ? done(a) : fail(onFalse(a))));
export const filterOrElse = filterOrElseW;
// -------------------------------------------------------------------------------------
// utils
// -------------------------------------------------------------------------------------
const dict = {
AjaxError: true,
AjaxTimeoutError: true,
};
function isResourceAjaxFail(r) {
return r._tag === 'fail';
}
function isAjaxError(e) {
return Object.prototype.hasOwnProperty.call(dict, e.name);
}
const decodeResponse = (decoders) => (response) => {
if (isResourceAjaxFail(response)) {
return response;
}
const status = response.status;
const decoder = decoders[status];
if (decoder) {
return pipe(decoder(response.response), E.map(payload => RES.done({ status, payload })), E.getOrElseW(e => RES.fail({
type: 'decodeError',
detail: e,
statusCode: response.status,
})));
}
return RES.fail({
type: response.status === 0 ? 'networkError' : 'unexpectedResponse',
detail: response,
});
};