UNPKG

mutoid

Version:

Reactive library for data fetching, caching, state management

150 lines (149 loc) 5.52 kB
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, }); };