Commit 42697ecc by Vladislav Lagunoff

Добавлены модули из @bitmaster/core

parent 0fd99798
This diff is collapsed. Click to expand it.
import { Expr } from '~/types';
/** convenient instance methods for `Either` */
export class EitherBase<L,R> {
readonly _L: L;
readonly _R: R;
map<R2>(f: (val: R) => R2): Either<L,R2> {
const self = this as any as Either<L,R>;
switch(self.tag) {
case 'Left': return this as any;
case 'Right': return new Right(f(self.value));
}
}
mapTo<R2>(value: R2): Either<L,R2> {
const self = this as any as Either<L,R>;
switch(self.tag) {
case 'Left': return this as any;
case 'Right': return new Right(value);
}
}
mapLeft<L2>(f: (val: L) => L2): Either<L2,R> {
const self = this as any as Either<L,R>;
switch(self.tag) {
case 'Left': return new Left(f(self.value));
case 'Right': return this as any;
}
}
mapLeftTo<L2>(value: L2): Either<L2,R> {
const self = this as any as Either<L,R>;
switch(self.tag) {
case 'Left': return new Left(value);
case 'Right': return this as any;
}
}
bimap<L2,R2>(f1: (val: L) => L2, f2: (x: R) => R2): Either<L2,R2> {
const self = this as any as Either<L, R>;
switch(self.tag) {
case 'Left': return new Left(f1(self.value));
case 'Right': return new Right(f2(self.value));
}
}
chain<R2>(f: (val: R) => Either<L,R2>): Either<L,R2>;
chain<L2,R2>(f: (val: R) => Either<L2,R2>): Either<L|L2,R2>;
chain<R2>(f: (val: R) => Either<L,R2>): Either<L,R2> {
const self = this as any as Either<L, R>;
switch(self.tag) {
case 'Left': return self as any;
case 'Right': return f(self.value);
}
}
chainTo<R2>(value: Either<L,R2>): Either<L,R2>;
chainTo<L2,R2>(value: Either<L2,R2>): Either<L|L2,R2>;
chainTo<R2>(value: Either<L,R2>): Either<L,R2> {
const self = this as any as Either<L, R>;
switch(self.tag) {
case 'Left': return self as any;
case 'Right': return value;
}
}
fold<T>(f1: (x: L) => T, f2: (x: R) => T): T;
fold<T1,T2>(f1: (x: L) => T1, f2: (x: R) => T2): T1|T2;
fold<T1,T2>(f1: (x: L) => T1, f2: (x: R) => T2): T1|T2 {
const self = this as any as Either<L, R>;
switch(self.tag) {
case 'Left': return f1(self.value);
case 'Right': return f2(self.value);
}
}
onError(onFailure: (value: L) => Either<L,R>): Either<L,R> {
const self = this as any as Either<L, R>;
switch(self.tag) {
case 'Left': return onFailure(self.value);
case 'Right': return self;
}
}
onErrorTo(value: Either<L,R>): Either<L,R> {
const self = this as any as Either<L, R>;
switch(self.tag) {
case 'Left': return value;
case 'Right': return self;
}
}
toNullable(): R|null {
const self = this as any as Either<L, R>;
switch(self.tag) {
case 'Left': return null;
case 'Right': return self.value;
}
}
isRight(): this is Right<R,L> { return this['tag'] === 'Right'; }
isLeft(): this is Left<L,R> { return this['tag'] === 'Left'; }
}
/** adt */
export type Either<L,R> =
| Left<L, R>
| Right<L, R>
;
/** left case */
export class Left<L,R> extends EitherBase<L,R> {
readonly tag: 'Left' = 'Left';
constructor(
readonly value: L,
) { super(); }
}
/** right case */
export class Right<L,R> extends EitherBase<L,R> {
readonly tag: 'Right' = 'Right';
constructor(
readonly value: R,
) { super(); }
}
/** aliases */
export function failure<L extends Expr>(value: L): Either<L, never> { return new Left(value); }
export function success<R extends Expr>(value: R): Either<never, R> { return new Right(value); }
export function left<L extends Expr>(value: L): Either<L, never> { return new Left(value); }
export function right<R extends Expr>(value: R): Either<never, R> { return new Right(value); }
/** alias for `right` */
export function of<A extends Expr>(value: A): Either<never, A> {
return new Right(value);
}
/** apply pure function with multiple arguments */
export function ap<L,A,B>(a: Either<L,A>, f: (a: A) => B): Either<L,B>;
export function ap<L,A,B,C>(a: Either<L,A>, b: Either<L,B>, f: (a: A, b: B) => C): Either<L,C>;
export function ap<L,A,B,C,D>(a: Either<L,A>, b: Either<L,B>, c: Either<L,C>, f: (a: A, b: B, c: C) => D): Either<L,D>;
export function ap<L,A,B,C,D,E>(a: Either<L,A>, b: Either<L,B>, c: Either<L,C>, d: Either<L,D>, f: (a: A, b: B, c: C, d: D) => E): Either<L,E>;
export function ap<L,A,B,C,D,E,F>(a: Either<L,A>, b: Either<L,B>, c: Either<L,C>, d: Either<L,D>, e: Either<L,E>, f: (a: A, b: B, c: C, d: D, e: E) => F): Either<L,F>;
export function ap<L,A,B,C,D,E,F,G>(a: Either<L,A>, b: Either<L,B>, c: Either<L,C>, d: Either<L,D>, e: Either<L,E>, f_: Either<L,F>, f: (a: A, b: B, c: C, d: D, e: E, f: F) => G): Either<L,G>;
export function ap(...args: Array<Either<any, any> | Function>): Either<any, any> {
const func = args[args.length - 1] as Function;
const results: Array<any> = [];
for (let i = 0; i < args.length - 1; i++) {
const ethr = args[i] as Either<any, any>;
switch (ethr.tag) {
case 'Left': return ethr;
case 'Right': results.push(ethr.value); break;
}
}
return new Right(func.apply(undefined, results));
}
/** traverse an array */
export function traverse<L,A,B>(arr: Either<L, A>[]): Either<L,A[]>;
export function traverse<L,A,B>(arr: Array<A>, f: (a: A, idx: number) => Either<L,B>): Either<L,B[]>;
export function traverse<L,A,B>(arr: Array<unknown>, f?: Function): Either<L,unknown[]> {
const output = [] as B[];
for (let i = 0; i < arr.length; i++) {
const ethr = f ? f(arr[i], i) : arr[i];
switch (ethr.tag) {
case 'Left': return ethr as any as Either<L,B[]>;
case 'Right': output.push(ethr.value); break;
}
}
return new Right(output);
}
export const Either = {
Left, Right, failure, success, left, of, ap, traverse,
};
import { HasEffect, Subscribe, Eff } from './eff';
import * as eff from './eff';
import { Decoder, Problem } from './decode';
import { Either } from './either';
import * as either 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;
}
export interface RequestProgress extends Request {
progress: true;
}
/** 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' }
export class HttpEffect<A> extends HasEffect<HttpError, A> {
constructor(
readonly request: Request|RequestProgress,
) { super(); };
toEffect() {
return new Subscribe<HttpError, any>((onNext, onComplete) => {
const req = this.request;
const onSuccess = (x: Response) => ('progress' in req && req.progress ? onNext(either.right(either.right(x))) : onNext(either.right(x)), onComplete());
const onProgress = (x: Progress) => 'progress' in req && req.progress ? onNext(either.right(either.left(x))) : void 0;
const onFailure = (x: HttpError) => (onNext(either.left(x)), onComplete());
const xhr = new XMLHttpRequest();
xhr.addEventListener('error', () => onFailure({ tag: 'NetworkError' }));
xhr.addEventListener('timeout', () => onFailure({ tag: 'Timeout' }));
xhr.addEventListener('load', () => onSuccess({
url: xhr.responseURL,
status: xhr.status,
statusText: xhr.statusText,
headers: parseHeaders(xhr.getAllResponseHeaders()),
body: xhr.response || xhr.responseText,
}));
try {
xhr.open(req.method, req.url, true);
} catch (e) {
onFailure({ tag: 'BadUrl', desc: req.url });
}
if ('progress' in req && req.progress) {
xhr.addEventListener('progress', e => onProgress(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();
});
}
}
/** send a request */
export function send(req: RequestProgress): HttpEffect<Either<Progress, Response>>;
export function send(req: Request): HttpEffect<Response>;
export function send(req: Request|RequestProgress): HttpEffect<unknown> {
return new HttpEffect(req);
}
/** shortcut for GET requests */
export function get(url: string, request?: Omit<RequestProgress, 'url'|'method'>): HttpEffect<Either<Progress, Response>>;
export function get(url: string, request?: Omit<Request, 'url'|'method'>): HttpEffect<Response>;
export function get(url: string, request?: Omit<Request|RequestProgress, 'url'|'method'>): HttpEffect<unknown> {
return send({ ...request, method: 'GET', url });
}
/** shortcut for POST requests */
export function post(url: string, request?: Omit<RequestProgress, 'url'|'method'>): HttpEffect<Either<Progress, Response>>;
export function post(url: string, request?: Omit<Request, 'url'|'method'>): HttpEffect<Response>;
export function post(url: string, request?: Omit<Request|RequestProgress, 'url'|'method'>): HttpEffect<unknown> {
return send({ ...request, method: 'POST', url });
}
/** parse response as JSON */
export function expectJSON<A>(decoder: Decoder<A>): (resp: Response) => Eff<HttpError, A> {
return resp => {
if (resp.body === '') return eff.failure<HttpError>({ tag: 'BadPayload', desc: 'empty body' });
let val = null;
try {
val = JSON.parse(resp.body);
} catch (e) {
return eff.failure<HttpError>({ tag: 'BadPayload', desc: 'invalid json' });
}
return eff.fromEither(
decoder.validate(val).mapLeft<HttpError>(problem => ({ tag: 'ValidationProblem', problem, url: resp.url }))
);
};
}
/** 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;
}
/** 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;
/** 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;
}
}
// Helper
export type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
import { Expr } from './internal/expr';
/** adt */
export type Option<A> = None<A> | Some<A>
export type Maybe<A> = Option<A>
/** instance method for convenience */
export class OptionBase<A> {
readonly _A: A;
/** map */
map<B>(f: (a: A) => B): Option<B> {
const self = this as any as Option<A>;
switch (self.tag) {
case 'None': return none;
case 'Some': return new Some(f(self.value));
}
}
/** map */
mapTo<B>(value: B): Option<B> {
const self = this as any as Option<A>;
switch (self.tag) {
case 'None': return none;
case 'Some': return some(value);
}
}
/** chain */
chain<B>(f: (a: A) => Option<B>): Option<B> {
const self = this as any as Option<A>;
switch (self.tag) {
case 'None': return none;
case 'Some': return f(self.value);
}
}
/** chain */
chainTo<B>(value: Option<B>): Option<B> {
const self = this as any as Option<A>;
switch (self.tag) {
case 'None': return none;
case 'Some': return value;
}
}
/** unwrap */
unwrap<B extends Expr, C extends Expr>(fromNone: B, fromSome: (x: A) => C): B|C {
const self = this as any as Option<A>;
switch (self.tag) {
case 'None': return fromNone;
case 'Some': return fromSome(self.value);
}
}
/** withDefault */
withDefault<B extends Expr>(def: B): A|B {
return this.unwrap(def, x => x);
}
}
/** empty container */
export class None<A> extends OptionBase<A> {
readonly _a: A;
readonly tag: 'None' = 'None';
}
/** container with a value */
export class Some<A> extends OptionBase<A> {
readonly _a: A;
readonly tag: 'Some' = 'Some';
constructor(
readonly value: A,
) { super(); }
}
/** traverse an array */
export function traverse<A,B>(arr: Array<A>, f: (a: A, idx: number) => Option<B>): Option<B[]> {
const output = [] as B[];
for (let i = 0; i < arr.length; i++) {
const option = f(arr[i], i);
switch (option.tag) {
case 'None': return option as any;
case 'Some': output.push(option.value); break;
}
}
return new Some(output);
}
/** aliases */
export const none = new None<never>();
export function some<A extends Expr>(a: A): Option<A> { return new Some<A>(a); }
export { some as of };
/** For better type inference */
export type Expr = boolean|null|undefined|number|string|{}|any[]|[any,any]|[any,any,any]|Function
/** Don't coerce string literals to `string` type */
export function literal<A extends string>(x: A): A {
return x;
}
/** Helper for totality checking */
export function absurd(x: never): any {
throw new Error('absurd: unreachable code');
}
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