Commit 6894e538 by Vladislav Lagunov

Добавлен алиасы модулей eff http

parent 8cc3cb4a
import { Either } from '../either'; export * from '../eff';
import { Option } from '../maybe';
import * as either from '../either';
import { Observable } from 'rxjs';
import * as Rx from 'rxjs';
import { map } from 'rxjs/internal/operators/map';
import { combineAll } from 'rxjs/internal/operators/combineAll';
import { Expr } from './internal/expr';
/**
* Тайп-алиасы для удобства
*/
export type Eff<Error, Success> = Observable<Either<Error, Success>>;
export type Cmd<Action> = Eff<never, Action>;
declare module 'rxjs/internal/Observable' {
export interface Observable<T> {
readonly _T: T;
map<L,R,R2>(this: Eff<L, R>, f: (x: R) => R2): Eff<L, R2>;
mapTo<L,R,R2>(this: Eff<L, R>, x: R2): Eff<L, R2>;
mapError<L,R,L2>(this: Eff<L, R>, f: (x: L) => L2): Eff<L2, R>;
mapErrorTo<L,R,L2>(this: Eff<L, R>, x: L2): Eff<L2, R>;
perform<L,R,A>(this: Eff<L, R>, onFailure: (x: L) => A, onSuccess: (x: R) => A): Cmd<A>;
perform<L,R,A1,A2>(this: Eff<L, R>, onFailure: (x: L) => A1, onSuccess: (x: R) => A2): Cmd<A1|A2>;
performMaybe<L,R,A>(this: Eff<L, R>, onFailure: (x: L) => Option<A>, onSuccess: (x: R) => Option<A>): Cmd<A>;
performMaybe<L,R,A1,A2>(this: Eff<L, R>, onFailure: (x: L) => Option<A1>, onSuccess: (x: R) => Option<A2>): Cmd<A1|A2>;
performForget<L, R>(this: Eff<L, R>): Cmd<never>;
performSuccess<R,A>(this: Eff<never, R>, onSuccess: (x: R) => A): Cmd<A>;
onError<L,R,L2,R2>(this: Eff<L, R>, onFailure: (x: L) => Eff<L2, R2>): Eff<L2, R2|R>;
chain<L,R,R2>(this: Eff<L, R>, f: (x: R) => Eff<L, R2>): Eff<L, R2>;
chain<L,R,L2,R2>(this: Eff<L, R>, f: (x: R) => Eff<L2, R2>): Eff<L|L2, R2>;
chainTo<L,R,R2>(this: Eff<L, R>, effect: Eff<L, R2>): Eff<L, R2>;
chainTo<L,R,L2,R2>(this: Eff<L, R>, effect: Eff<L2, R2>): Eff<L|L2, R2>;
// DEPRECATED
chainEff<L,R,R2>(this: Eff<L, R>, f: (x: R) => Eff<L, R2>): Eff<L, R2>;
chainEff<L,R,L2,R2>(this: Eff<L, R>, f: (x: R) => Eff<L2, R2>): Eff<L|L2, R2>;
mapEff<L,R,R2>(this: Eff<L, R>, f: (x: R) => R2): Eff<L, R2>;
mapCmd<L,R,R2>(this: Eff<L, R>, f: (x: R) => R2): Eff<L, R2>;
}
}
(Observable as any).prototype.map = function<L, R, R2>(this: Eff<L, R>, f: (x: R) => R2): Eff<L, R2> {
return this.pipe(map(ethr => ethr.map(f)));
};
(Observable as any).prototype.mapTo = function<L, R, R2>(this: Eff<L, R>, x: R2): Eff<L, R2> {
return this.pipe(map(ethr => ethr.map(() => x)));
};
(Observable as any).prototype.mapError = function<L, R, L2>(this: Eff<L, R>, f: (x: L) => L2): Eff<L2, R> {
return this.pipe(map(ethr => ethr.mapLeft(f)));
};
(Observable as any).prototype.mapErrorTo = function<L, R, L2>(this: Eff<L, R>, x: L2): Eff<L2, R> {
return this.pipe(map(ethr => ethr.mapLeft(() => x)));
};
(Observable as any).prototype.onError = function<L,R,L2,R2>(this: Eff<L, R>, onFailure: (x: L) => Eff<L2, R2>): Eff<L2, R2|R> {
return this.pipe(map((ethr: Either<any, any>) => ethr.tag === 'Left' ? onFailure(ethr.value) : Rx.of(ethr)), combineAll(x => x));
};
(Observable as any).prototype.chain = function<L,R,R2>(this: Eff<L, R>, f: (x: R) => Eff<L, R2>): Eff<L, R2> {
return this.pipe(map(ethr => ethr.tag === 'Right' ? f(ethr.value) : Rx.of(ethr)), combineAll(x => x as Either<any , any>));
};
(Observable as any).prototype.chainTo = function<L,R,R2>(this: Eff<L, R>, x: Eff<L, R2>): Eff<L, R2> {
return this.pipe(map(ethr => ethr.tag === 'Right' ? x : Rx.of(ethr)), combineAll(x => x as Either<any , any>));
};
(Observable as any).prototype.perform = function<L, R, A>(this: Eff<L, R>, onFailure: (x: L) => A, onSuccess: (x: R) => A): Cmd<A> {
return this.pipe(map(ethr => either.success(ethr.fold(onFailure, onSuccess))));
};
(Observable as any).prototype.performMaybe = function<L, R, A>(this: Eff<L, R>, onFailure: (x: L) => Option<A>, onSuccess: (x: R) => Option<A>): Cmd<A> {
return this.pipe(map(projectMaybe), combineAll(x => x as Either<never, A>));
function projectMaybe(ethr: Either<L, R>): Eff<never, A> {
const maybeMessage = ethr.fold(onFailure, onSuccess);
switch (maybeMessage.tag) {
case 'None': return Rx.NEVER;
case 'Some': return Rx.of(either.success(maybeMessage.value));
}
}
};
(Observable as any).prototype.performForget = function<L, R>(this: Eff<L, R>): Cmd<never> {
return this.pipe(map(x => Rx.NEVER), combineAll(x => x));
};
// DEPRECATED
(Observable as any).prototype.chainEff = Observable.prototype.chain;
(Observable as any).prototype.mapEff = Observable.prototype.map;
(Observable as any).prototype.mapCmd = Observable.prototype.map;
export function failure<L extends Expr>(error: L): Eff<L, never> {
return Rx.of(either.failure(error));
}
export function success<R extends Expr>(success: R): Eff<never, R> {
return Rx.of(either.success(success));
}
export function callback(run: () => void): Eff<never, null> {
return Observable.create(observer => (run(), observer.next(either.success(null)), observer.complete(), void 0));
}
export function fromCallback<L, R>(run: (cb: (x: Either<L, R>) => void) => void): Eff<L, R> {
return Observable.create(observer => (run(x => (observer.next(x), observer.complete())), void 0));
}
export function promise<L, R>(func: (...args: Array<any>) => Promise<Either<L, R>>, ...args: Array<any>): Eff<L, R> {
return Observable.create(observer => { func.apply(undefined, args).then(x => (observer.next(x), observer.complete())).catch(e => (observer.error(e), observer.complete())); })
}
export { success as of };
export function lazy<A>(f: () => A): Observable<A>;
export function lazy<A,B>(f: (a: A) => B, a: A): Observable<B>;
export function lazy<A,B,C>(f: (a: A, b: B) => C, a: A, b: B): Observable<C>;
export function lazy<A,B,C,D>(f: (a: A, b: B, c: C) => D, a: A, b: B, c: C): Observable<D>;
export function lazy() {
const _arguments = arguments;
return Observable.create(observer => {
const result = _arguments[0].apply(undefined, Array(_arguments.length - 1).map((_, idx) => _arguments[idx + 1]));
observer.next(result);
observer.complete();
});
}
export function ap<L,A,B>(a: Eff<L,A>, f: (a: A) => B): Eff<L,B>;
export function ap<L,A,B,C>(a: Eff<L,A>, b: Eff<L,B>, f: (a: A, b: B) => C): Eff<L,C>;
export function ap<L,A,B,C,D>(a: Eff<L,A>, b: Eff<L,B>, c: Eff<L,C>, f: (a: A, b: B, c: C) => D): Eff<L,D>;
export function ap<L,A,B,C,D,E>(a: Eff<L,A>, b: Eff<L,B>, c: Eff<L,C>, d: Eff<L,D>, f: (a: A, b: B, c: C, d: D) => E): Eff<L,E>;
export function ap<L,A,B,C,D,E,F>(a: Eff<L,A>, b: Eff<L,B>, c: Eff<L,C>, d: Eff<L,D>, e: Eff<L,E>, f: (a: A, b: B, c: C, d: D, e: E) => F): Eff<L,F>;
export function ap<L,A,B,C,D,E,F,G>(a: Eff<L,A>, b: Eff<L,B>, c: Eff<L,C>, d: Eff<L,D>, e: Eff<L,E>, f_: Eff<L,F>, f: (a: A, b: B, c: C, d: D, e: E, f: F) => G): Eff<L,G>;
export function ap(): Eff<any, any> {
const _arguments = arguments;
return Rx.combineLatest(Array.apply(undefined, Array(arguments.length - 1)).map((_, idx) => _arguments[idx]), (...inputs) => either.traverse(inputs, x => x as any).map(inputs => _arguments[_arguments.length - 1].apply(undefined, inputs)));
}
/** traverse an array */
export function traverse<ERR, A, B>(array: A[], f: (a: A, idx: number) => Eff<ERR, B>): Eff<ERR, B[]> {
if (array.length === 0) return success([]);
// @ts-ignore
return ap(...array.map(f), (...args) => args);
}
/**
* Объединение нескольких параллельно выполняемых `Cmd`
*/
export function batch<A>(a: Cmd<A>): Cmd<A>;
export function batch<A,B>(a: Cmd<A>, b: Cmd<B>): Cmd<A|B>;
export function batch<A,B,C>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>): Cmd<A|B|C>;
export function batch<A,B,C,D>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>): Cmd<A|B|C|D>;
export function batch<A,B,C,D,E>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>): Cmd<A|B|C|D|E>;
export function batch<A,B,C,D,E,F>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>, f: Cmd<F>): Cmd<A|B|C|D|E|F>;
export function batch<A,B,C,D,E,F,G>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>, f: Cmd<F>, g: Cmd<G>): Cmd<A|B|C|D|E|F|G>;
export function batch<A,B,C,D,E,F,G,H>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>, f: Cmd<F>, g: Cmd<G>, h: Cmd<H>): Cmd<A|B|C|D|E|F|G|H>;
export function batch<A,B,C,D,E,F,G,H,I>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>, f: Cmd<F>, g: Cmd<G>, h: Cmd<H>, i: Cmd<I>): Cmd<A|B|C|D|E|F|G|H|I>;
export function batch<A,B,C,D,E,F,G,H,I,J>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>, f: Cmd<F>, g: Cmd<G>, h: Cmd<H>, i: Cmd<I>, j: Cmd<J>): Cmd<A|B|C|D|E|F|G|H|I|J>;
export function batch<array extends Cmd<any>[]>(signals: array): Cmd<array[number]['_T']['_R']>;
export function batch(): Cmd<any> {
const observables = Array.isArray(arguments[0]) ? arguments[0] : arguments;
return Rx.merge.apply(undefined, observables);
}
/**
* Объединение нескольких `Cmd` в очередь
*/
export function concat<A>(a: Cmd<A>): Cmd<A>;
export function concat<A,B>(a: Cmd<A>, b: Cmd<B>): Cmd<A|B>;
export function concat<A,B,C>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>): Cmd<A|B|C>;
export function concat<A,B,C,D>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>): Cmd<A|B|C|D>;
export function concat<A,B,C,D,E>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>): Cmd<A|B|C|D|E>;
export function concat<A,B,C,D,E,F>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>, f: Cmd<F>): Cmd<A|B|C|D|E|F>;
export function concat<A,B,C,D,E,F,G>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>, f: Cmd<F>, g: Cmd<G>): Cmd<A|B|C|D|E|F|G>;
export function concat<A,B,C,D,E,F,G,H>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>, f: Cmd<F>, g: Cmd<G>, h: Cmd<H>): Cmd<A|B|C|D|E|F|G|H>;
export function concat<A,B,C,D,E,F,G,H,I>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>, f: Cmd<F>, g: Cmd<G>, h: Cmd<H>, i: Cmd<I>): Cmd<A|B|C|D|E|F|G|H|I>;
export function concat<A,B,C,D,E,F,G,H,I,J>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>, f: Cmd<F>, g: Cmd<G>, h: Cmd<H>, i: Cmd<I>, j: Cmd<J>): Cmd<A|B|C|D|E|F|G|H|I|J>;
export function concat<array extends Cmd<any>[]>(signals: array): Cmd<array[number]['_T']['_R']>;
export function concat(): Cmd<any> {
const observables = Array.isArray(arguments[0]) ? arguments[0] : arguments;
return Rx.concat.apply(undefined, observables);
}
/**
* Примитивный `Cmd` не генерирует никаких действий, завершается сразу
* после запуска.
*/
export const noop: Cmd<never> = new Rx.Observable(observer => observer.complete());
/**
* Выполнение сайд-еффектов. Observable не генерирует событий, после
* запуска вызывается переданная функция и Observable завершается.
*/
export function forget<A>(f: () => A): Cmd<never>;
export function forget<A,B>(f: (a: A) => B, a: A): Cmd<never>;
export function forget<A,B,C>(f: (a: A, b: B) => C, a: A, b: B): Cmd<never>;
export function forget<A,B,C,D>(f: (a: A, b: B, c: C) => D, a: A, b: B, c: C): Cmd<never>;
export function forget(f: (...args: any[]) => void, ...rest: any[]): Cmd<never> {
return Observable.create(observer => (f.apply(undefined, rest), observer.complete()));
}
import * as Rx from 'rxjs'; export * from '../http';
import { Eff } from './eff';
import * as eff from './eff';
import { Decoder, Problem } from '../decoder';
import { success, failure } from '../either';
/** http method */
export type Method = 'GET'|'POST'|'PUT'|'DELETE'|'PATCH';
/** request */
export interface Request {
url: string;
method: Method;
body?: any;
headers?: Record<string, string|number|undefined|null>;
withCredentials?: boolean;
timeout?: number;
}
/** raw error */
export type HttpError =
| { tag: 'BadUrl', desc: string }
| { tag: 'BadPayload', desc: string }
| { tag: 'ValidationProblem', problem: Problem, url: string }
| { tag: 'BadStatus', status: number, desc: string }
| { tag: 'Timeout' }
| { tag: 'NetworkError' }
/** responce */
export interface Response {
url: string;
status: number;
statusText: string;
headers: Record<string, string>;
body: string;
}
/** query params */
export type ParamsPrimitive = number|string|undefined|null;
export type Params = Record<string, ParamsPrimitive|ParamsPrimitive[]>;
/** progress */
export type Progress =
| { tag: 'Computable', total: number, loaded: number }
| { tag: 'Uncomputable' }
/** send a request */
export function send(req: Request): Eff<HttpError, Response> {
return Rx.Observable.create(observer => {
const xhr = new XMLHttpRequest();
xhr.addEventListener('error', () => (observer.next(failure({ tag: 'NetworkError' } as HttpError)), observer.complete()));
xhr.addEventListener('timeout', () => (observer.next(failure({ tag: 'Timeout' } as HttpError)), observer.complete()));
xhr.addEventListener('load', () => {
observer.next(success({
url: xhr.responseURL,
status: xhr.status,
statusText: xhr.statusText,
headers: parseHeaders(xhr.getAllResponseHeaders()),
body: xhr.response || xhr.responseText,
}));
observer.complete();
});
try {
xhr.open(req.method, req.url, true);
} catch (e) {
observer.next(failure({ tag: 'BadUrl', desc: req.url } as HttpError)); observer.complete();
}
//xhr.addEventListener('progress', e => onResult(success(e.lengthComputable ? { tag: 'Computable', loaded: e.loaded, total: e.total } : { tag: 'Uncomputable' })));
if (req.timeout) xhr.timeout = req.timeout;
if (typeof (req.withCredentials) !== 'undefined') xhr.withCredentials = req.withCredentials;
if (typeof (req.headers) !== 'undefined') {
for (let key in req.headers) {
if (!req.headers.hasOwnProperty(key)) continue;
const value = req.headers[key];
if (typeof(value) !== 'undefined' && value !== null)
xhr.setRequestHeader(key, String(value));
}
}
const body = Object.prototype.toString.apply(req.body) === '[object Object]' ? JSON.stringify(req.body) : req.body;
xhr.send(body);
return () => xhr.abort();
});
}
/** shortcut for GET requests */
export function get(url: string, extra?: Partial<Request>): Eff<HttpError, Response> {
return send(Object.assign({}, extra, { method: 'GET', url }) as any);
}
/** shortcut for POST requests */
export function post(url: string, extra?: Partial<Request>): Eff<HttpError, Response> {
return send(Object.assign({}, extra, { method: 'POST', url }) as any);
}
/** parse response as JSON */
export function expectJSON<A>(decoder: Decoder<A>): (resp: Response) => Eff<HttpError, A> {
return resp => {
if (resp.body === '') return eff.failure({ tag: 'BadPayload', desc: 'empty body' } as HttpError);
let val = null;
try {
val = JSON.parse(resp.body);
} catch (e) {
return eff.failure({ tag: 'BadPayload', desc: 'invalid json' } as HttpError);
}
return Rx.of(decoder.validate(val).mapLeft(problem => ({ tag: 'ValidationProblem', problem, url: resp.url } as HttpError)));
};
}
/** parse headers from string to dict */
function parseHeaders(rawHeaders: string): Record<string, string> {
const output = {};
const lines = rawHeaders.split('\r\n');
for (let i in lines) {
const index = lines[i].indexOf(': ');
if (index < 0) continue;
const key = lines[i].substring(0, index);
const value = lines[i].substring(index + 2);
output[key] = value;
}
return output;
}
/** join segments of url */
function joinTwo(a: string, b: string): string {
if (a === '') return b;
if (b === '') return a;
const trailing = a.length && a[a.length - 1] === '/';
const leading = b.length && b[0] === '/';
if (trailing && leading) return a.substring(0, a.length - 1) + b;
if (!trailing && !leading) return a + '/' + b;
return a + b;
}
/** build an url */
export function join(...args: Array<string|Params>): string {
let path = '';
let params = {} as Record<string, string>;
let query = '';
for (let i in args) {
const arg = args[i];
if (typeof (arg) === 'string') path = joinTwo(path, arg);
else Object['assign'](params, arg);
}
for (let key in params) {
if (!params.hasOwnProperty(key) || typeof(params[key]) === 'undefined' || params[key] === null) continue;
if (Array.isArray(params[key])) {
for (const v of params[key]) {
if (typeof(params[key]) === 'undefined' || params[key] === null) continue;
query += (query ? '&' : '') + `${encodeURIComponent(key)}=${encodeURIComponent(v)}`;
}
} else {
query += (query ? '&' : '') + `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`;
}
}
return query ? (path + '?' + query) : path;
}
import { Either } from '../either';
import { Option } from '../maybe';
import * as either from '../either';
import { Observable } from 'rxjs';
import * as Rx from 'rxjs';
import { map } from 'rxjs/internal/operators/map';
import { combineAll } from 'rxjs/internal/operators/combineAll';
import { Expr } from '../types';
/**
* Тайп-алиасы для удобства
*/
export type Eff<Error, Success> = Observable<Either<Error, Success>>;
export type Cmd<Action> = Eff<never, Action>;
declare module 'rxjs/internal/Observable' {
export interface Observable<T> {
readonly _T: T;
map<L,R,R2>(this: Eff<L, R>, f: (x: R) => R2): Eff<L, R2>;
mapTo<L,R,R2>(this: Eff<L, R>, x: R2): Eff<L, R2>;
mapError<L,R,L2>(this: Eff<L, R>, f: (x: L) => L2): Eff<L2, R>;
mapErrorTo<L,R,L2>(this: Eff<L, R>, x: L2): Eff<L2, R>;
perform<L,R,A>(this: Eff<L, R>, onFailure: (x: L) => A, onSuccess: (x: R) => A): Cmd<A>;
perform<L,R,A1,A2>(this: Eff<L, R>, onFailure: (x: L) => A1, onSuccess: (x: R) => A2): Cmd<A1|A2>;
performMaybe<L,R,A>(this: Eff<L, R>, onFailure: (x: L) => Option<A>, onSuccess: (x: R) => Option<A>): Cmd<A>;
performMaybe<L,R,A1,A2>(this: Eff<L, R>, onFailure: (x: L) => Option<A1>, onSuccess: (x: R) => Option<A2>): Cmd<A1|A2>;
performForget<L, R>(this: Eff<L, R>): Cmd<never>;
performSuccess<R,A>(this: Eff<never, R>, onSuccess: (x: R) => A): Cmd<A>;
onError<L,R,L2,R2>(this: Eff<L, R>, onFailure: (x: L) => Eff<L2, R2>): Eff<L2, R2|R>;
chain<L,R,R2>(this: Eff<L, R>, f: (x: R) => Eff<L, R2>): Eff<L, R2>;
chain<L,R,L2,R2>(this: Eff<L, R>, f: (x: R) => Eff<L2, R2>): Eff<L|L2, R2>;
chainTo<L,R,R2>(this: Eff<L, R>, effect: Eff<L, R2>): Eff<L, R2>;
chainTo<L,R,L2,R2>(this: Eff<L, R>, effect: Eff<L2, R2>): Eff<L|L2, R2>;
// DEPRECATED
chainEff<L,R,R2>(this: Eff<L, R>, f: (x: R) => Eff<L, R2>): Eff<L, R2>;
chainEff<L,R,L2,R2>(this: Eff<L, R>, f: (x: R) => Eff<L2, R2>): Eff<L|L2, R2>;
mapEff<L,R,R2>(this: Eff<L, R>, f: (x: R) => R2): Eff<L, R2>;
mapCmd<L,R,R2>(this: Eff<L, R>, f: (x: R) => R2): Eff<L, R2>;
}
}
(Observable as any).prototype.map = function<L, R, R2>(this: Eff<L, R>, f: (x: R) => R2): Eff<L, R2> {
return this.pipe(map(ethr => ethr.map(f)));
};
(Observable as any).prototype.mapTo = function<L, R, R2>(this: Eff<L, R>, x: R2): Eff<L, R2> {
return this.pipe(map(ethr => ethr.map(() => x)));
};
(Observable as any).prototype.mapError = function<L, R, L2>(this: Eff<L, R>, f: (x: L) => L2): Eff<L2, R> {
return this.pipe(map(ethr => ethr.mapLeft(f)));
};
(Observable as any).prototype.mapErrorTo = function<L, R, L2>(this: Eff<L, R>, x: L2): Eff<L2, R> {
return this.pipe(map(ethr => ethr.mapLeft(() => x)));
};
(Observable as any).prototype.onError = function<L,R,L2,R2>(this: Eff<L, R>, onFailure: (x: L) => Eff<L2, R2>): Eff<L2, R2|R> {
return this.pipe(map((ethr: Either<any, any>) => ethr.tag === 'Left' ? onFailure(ethr.value) : Rx.of(ethr)), combineAll(x => x));
};
(Observable as any).prototype.chain = function<L,R,R2>(this: Eff<L, R>, f: (x: R) => Eff<L, R2>): Eff<L, R2> {
return this.pipe(map(ethr => ethr.tag === 'Right' ? f(ethr.value) : Rx.of(ethr)), combineAll(x => x as Either<any , any>));
};
(Observable as any).prototype.chainTo = function<L,R,R2>(this: Eff<L, R>, x: Eff<L, R2>): Eff<L, R2> {
return this.pipe(map(ethr => ethr.tag === 'Right' ? x : Rx.of(ethr)), combineAll(x => x as Either<any , any>));
};
(Observable as any).prototype.perform = function<L, R, A>(this: Eff<L, R>, onFailure: (x: L) => A, onSuccess: (x: R) => A): Cmd<A> {
return this.pipe(map(ethr => either.success(ethr.fold(onFailure, onSuccess))));
};
(Observable as any).prototype.performMaybe = function<L, R, A>(this: Eff<L, R>, onFailure: (x: L) => Option<A>, onSuccess: (x: R) => Option<A>): Cmd<A> {
return this.pipe(map(projectMaybe), combineAll(x => x as Either<never, A>));
function projectMaybe(ethr: Either<L, R>): Eff<never, A> {
const maybeMessage = ethr.fold(onFailure, onSuccess);
switch (maybeMessage.tag) {
case 'None': return Rx.NEVER;
case 'Some': return Rx.of(either.success(maybeMessage.value));
}
}
};
(Observable as any).prototype.performForget = function<L, R>(this: Eff<L, R>): Cmd<never> {
return this.pipe(map(x => Rx.NEVER), combineAll(x => x));
};
// DEPRECATED
(Observable as any).prototype.chainEff = Observable.prototype.chain;
(Observable as any).prototype.mapEff = Observable.prototype.map;
(Observable as any).prototype.mapCmd = Observable.prototype.map;
export function failure<L extends Expr>(error: L): Eff<L, never> {
return Rx.of(either.failure(error));
}
export function success<R extends Expr>(success: R): Eff<never, R> {
return Rx.of(either.success(success));
}
export function callback(run: () => void): Eff<never, null> {
return Observable.create(observer => (run(), observer.next(either.success(null)), observer.complete(), void 0));
}
export function fromCallback<L, R>(run: (cb: (x: Either<L, R>) => void) => void): Eff<L, R> {
return Observable.create(observer => (run(x => (observer.next(x), observer.complete())), void 0));
}
export function promise<L, R>(func: (...args: Array<any>) => Promise<Either<L, R>>, ...args: Array<any>): Eff<L, R> {
return Observable.create(observer => { func.apply(undefined, args).then(x => (observer.next(x), observer.complete())).catch(e => (observer.error(e), observer.complete())); })
}
export { success as of };
export function lazy<A>(f: () => A): Observable<A>;
export function lazy<A,B>(f: (a: A) => B, a: A): Observable<B>;
export function lazy<A,B,C>(f: (a: A, b: B) => C, a: A, b: B): Observable<C>;
export function lazy<A,B,C,D>(f: (a: A, b: B, c: C) => D, a: A, b: B, c: C): Observable<D>;
export function lazy() {
const _arguments = arguments;
return Observable.create(observer => {
const result = _arguments[0].apply(undefined, Array(_arguments.length - 1).map((_, idx) => _arguments[idx + 1]));
observer.next(result);
observer.complete();
});
}
export function ap<L,A,B>(a: Eff<L,A>, f: (a: A) => B): Eff<L,B>;
export function ap<L,A,B,C>(a: Eff<L,A>, b: Eff<L,B>, f: (a: A, b: B) => C): Eff<L,C>;
export function ap<L,A,B,C,D>(a: Eff<L,A>, b: Eff<L,B>, c: Eff<L,C>, f: (a: A, b: B, c: C) => D): Eff<L,D>;
export function ap<L,A,B,C,D,E>(a: Eff<L,A>, b: Eff<L,B>, c: Eff<L,C>, d: Eff<L,D>, f: (a: A, b: B, c: C, d: D) => E): Eff<L,E>;
export function ap<L,A,B,C,D,E,F>(a: Eff<L,A>, b: Eff<L,B>, c: Eff<L,C>, d: Eff<L,D>, e: Eff<L,E>, f: (a: A, b: B, c: C, d: D, e: E) => F): Eff<L,F>;
export function ap<L,A,B,C,D,E,F,G>(a: Eff<L,A>, b: Eff<L,B>, c: Eff<L,C>, d: Eff<L,D>, e: Eff<L,E>, f_: Eff<L,F>, f: (a: A, b: B, c: C, d: D, e: E, f: F) => G): Eff<L,G>;
export function ap(): Eff<any, any> {
const _arguments = arguments;
return Rx.combineLatest(Array.apply(undefined, Array(arguments.length - 1)).map((_, idx) => _arguments[idx]), (...inputs) => either.traverse(inputs, x => x as any).map(inputs => _arguments[_arguments.length - 1].apply(undefined, inputs)));
}
/** traverse an array */
export function traverse<ERR, A, B>(array: A[], f: (a: A, idx: number) => Eff<ERR, B>): Eff<ERR, B[]> {
if (array.length === 0) return success([]);
// @ts-ignore
return ap(...array.map(f), (...args) => args);
}
/**
* Объединение нескольких параллельно выполняемых `Cmd`
*/
export function batch<A>(a: Cmd<A>): Cmd<A>;
export function batch<A,B>(a: Cmd<A>, b: Cmd<B>): Cmd<A|B>;
export function batch<A,B,C>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>): Cmd<A|B|C>;
export function batch<A,B,C,D>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>): Cmd<A|B|C|D>;
export function batch<A,B,C,D,E>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>): Cmd<A|B|C|D|E>;
export function batch<A,B,C,D,E,F>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>, f: Cmd<F>): Cmd<A|B|C|D|E|F>;
export function batch<A,B,C,D,E,F,G>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>, f: Cmd<F>, g: Cmd<G>): Cmd<A|B|C|D|E|F|G>;
export function batch<A,B,C,D,E,F,G,H>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>, f: Cmd<F>, g: Cmd<G>, h: Cmd<H>): Cmd<A|B|C|D|E|F|G|H>;
export function batch<A,B,C,D,E,F,G,H,I>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>, f: Cmd<F>, g: Cmd<G>, h: Cmd<H>, i: Cmd<I>): Cmd<A|B|C|D|E|F|G|H|I>;
export function batch<A,B,C,D,E,F,G,H,I,J>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>, f: Cmd<F>, g: Cmd<G>, h: Cmd<H>, i: Cmd<I>, j: Cmd<J>): Cmd<A|B|C|D|E|F|G|H|I|J>;
export function batch<array extends Cmd<any>[]>(signals: array): Cmd<array[number]['_T']['_R']>;
export function batch(): Cmd<any> {
const observables = Array.isArray(arguments[0]) ? arguments[0] : arguments;
return Rx.merge.apply(undefined, observables);
}
/**
* Объединение нескольких `Cmd` в очередь
*/
export function concat<A>(a: Cmd<A>): Cmd<A>;
export function concat<A,B>(a: Cmd<A>, b: Cmd<B>): Cmd<A|B>;
export function concat<A,B,C>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>): Cmd<A|B|C>;
export function concat<A,B,C,D>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>): Cmd<A|B|C|D>;
export function concat<A,B,C,D,E>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>): Cmd<A|B|C|D|E>;
export function concat<A,B,C,D,E,F>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>, f: Cmd<F>): Cmd<A|B|C|D|E|F>;
export function concat<A,B,C,D,E,F,G>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>, f: Cmd<F>, g: Cmd<G>): Cmd<A|B|C|D|E|F|G>;
export function concat<A,B,C,D,E,F,G,H>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>, f: Cmd<F>, g: Cmd<G>, h: Cmd<H>): Cmd<A|B|C|D|E|F|G|H>;
export function concat<A,B,C,D,E,F,G,H,I>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>, f: Cmd<F>, g: Cmd<G>, h: Cmd<H>, i: Cmd<I>): Cmd<A|B|C|D|E|F|G|H|I>;
export function concat<A,B,C,D,E,F,G,H,I,J>(a: Cmd<A>, b: Cmd<B>, c: Cmd<C>, d: Cmd<D>, e: Cmd<E>, f: Cmd<F>, g: Cmd<G>, h: Cmd<H>, i: Cmd<I>, j: Cmd<J>): Cmd<A|B|C|D|E|F|G|H|I|J>;
export function concat<array extends Cmd<any>[]>(signals: array): Cmd<array[number]['_T']['_R']>;
export function concat(): Cmd<any> {
const observables = Array.isArray(arguments[0]) ? arguments[0] : arguments;
return Rx.concat.apply(undefined, observables);
}
/**
* Примитивный `Cmd` не генерирует никаких действий, завершается сразу
* после запуска.
*/
export const noop: Cmd<never> = new Rx.Observable(observer => observer.complete());
/**
* Выполнение сайд-еффектов. Observable не генерирует событий, после
* запуска вызывается переданная функция и Observable завершается.
*/
export function forget<A>(f: () => A): Cmd<never>;
export function forget<A,B>(f: (a: A) => B, a: A): Cmd<never>;
export function forget<A,B,C>(f: (a: A, b: B) => C, a: A, b: B): Cmd<never>;
export function forget<A,B,C,D>(f: (a: A, b: B, c: C) => D, a: A, b: B, c: C): Cmd<never>;
export function forget(f: (...args: any[]) => void, ...rest: any[]): Cmd<never> {
return Observable.create(observer => (f.apply(undefined, rest), observer.complete()));
}
import * as Rx from 'rxjs';
import { Eff } from '../eff';
import * as eff from '../eff';
import { Decoder, Problem } from '../decoder';
import { success, failure } from '../either';
/** http method */
export type Method = 'GET'|'POST'|'PUT'|'DELETE'|'PATCH';
/** request */
export interface Request {
url: string;
method: Method;
body?: any;
headers?: Record<string, string|number|undefined|null>;
withCredentials?: boolean;
timeout?: number;
}
/** raw error */
export type HttpError =
| { tag: 'BadUrl', desc: string }
| { tag: 'BadPayload', desc: string }
| { tag: 'ValidationProblem', problem: Problem, url: string }
| { tag: 'BadStatus', status: number, desc: string }
| { tag: 'Timeout' }
| { tag: 'NetworkError' }
/** responce */
export interface Response {
url: string;
status: number;
statusText: string;
headers: Record<string, string>;
body: string;
}
/** query params */
export type ParamsPrimitive = number|string|undefined|null;
export type Params = Record<string, ParamsPrimitive|ParamsPrimitive[]>;
/** progress */
export type Progress =
| { tag: 'Computable', total: number, loaded: number }
| { tag: 'Uncomputable' }
/** send a request */
export function send(req: Request): Eff<HttpError, Response> {
return Rx.Observable.create(observer => {
const xhr = new XMLHttpRequest();
xhr.addEventListener('error', () => (observer.next(failure({ tag: 'NetworkError' } as HttpError)), observer.complete()));
xhr.addEventListener('timeout', () => (observer.next(failure({ tag: 'Timeout' } as HttpError)), observer.complete()));
xhr.addEventListener('load', () => {
observer.next(success({
url: xhr.responseURL,
status: xhr.status,
statusText: xhr.statusText,
headers: parseHeaders(xhr.getAllResponseHeaders()),
body: xhr.response || xhr.responseText,
}));
observer.complete();
});
try {
xhr.open(req.method, req.url, true);
} catch (e) {
observer.next(failure({ tag: 'BadUrl', desc: req.url } as HttpError)); observer.complete();
}
//xhr.addEventListener('progress', e => onResult(success(e.lengthComputable ? { tag: 'Computable', loaded: e.loaded, total: e.total } : { tag: 'Uncomputable' })));
if (req.timeout) xhr.timeout = req.timeout;
if (typeof (req.withCredentials) !== 'undefined') xhr.withCredentials = req.withCredentials;
if (typeof (req.headers) !== 'undefined') {
for (let key in req.headers) {
if (!req.headers.hasOwnProperty(key)) continue;
const value = req.headers[key];
if (typeof(value) !== 'undefined' && value !== null)
xhr.setRequestHeader(key, String(value));
}
}
const body = Object.prototype.toString.apply(req.body) === '[object Object]' ? JSON.stringify(req.body) : req.body;
xhr.send(body);
return () => xhr.abort();
});
}
/** shortcut for GET requests */
export function get(url: string, extra?: Partial<Request>): Eff<HttpError, Response> {
return send(Object.assign({}, extra, { method: 'GET', url }) as any);
}
/** shortcut for POST requests */
export function post(url: string, extra?: Partial<Request>): Eff<HttpError, Response> {
return send(Object.assign({}, extra, { method: 'POST', url }) as any);
}
/** parse response as JSON */
export function expectJSON<A>(decoder: Decoder<A>): (resp: Response) => Eff<HttpError, A> {
return resp => {
if (resp.body === '') return eff.failure({ tag: 'BadPayload', desc: 'empty body' } as HttpError);
let val = null;
try {
val = JSON.parse(resp.body);
} catch (e) {
return eff.failure({ tag: 'BadPayload', desc: 'invalid json' } as HttpError);
}
return Rx.of(decoder.validate(val).mapLeft(problem => ({ tag: 'ValidationProblem', problem, url: resp.url } as HttpError)));
};
}
/** parse headers from string to dict */
function parseHeaders(rawHeaders: string): Record<string, string> {
const output = {};
const lines = rawHeaders.split('\r\n');
for (let i in lines) {
const index = lines[i].indexOf(': ');
if (index < 0) continue;
const key = lines[i].substring(0, index);
const value = lines[i].substring(index + 2);
output[key] = value;
}
return output;
}
/** join segments of url */
function joinTwo(a: string, b: string): string {
if (a === '') return b;
if (b === '') return a;
const trailing = a.length && a[a.length - 1] === '/';
const leading = b.length && b[0] === '/';
if (trailing && leading) return a.substring(0, a.length - 1) + b;
if (!trailing && !leading) return a + '/' + b;
return a + b;
}
/** build an url */
export function join(...args: Array<string|Params>): string {
let path = '';
let params = {} as Record<string, string>;
let query = '';
for (let i in args) {
const arg = args[i];
if (typeof (arg) === 'string') path = joinTwo(path, arg);
else Object['assign'](params, arg);
}
for (let key in params) {
if (!params.hasOwnProperty(key) || typeof(params[key]) === 'undefined' || params[key] === null) continue;
if (Array.isArray(params[key])) {
for (const v of params[key]) {
if (typeof(params[key]) === 'undefined' || params[key] === null) continue;
query += (query ? '&' : '') + `${encodeURIComponent(key)}=${encodeURIComponent(v)}`;
}
} else {
query += (query ? '&' : '') + `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`;
}
}
return query ? (path + '?' + query) : path;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment