Commit 4685aafc by Vladislav Lagunov

Merge branch 'master' of ssh://git.bitmaster.ru:34022/npm/common

parents d67d4f07 cbaa6040
...@@ -17,6 +17,8 @@ export type Eff<Error, Success> = ...@@ -17,6 +17,8 @@ export type Eff<Error, Success> =
| Apply<Error, Success> // { args: Eff<unknown>[], proj(...args): Either<Error, Success> } | Apply<Error, Success> // { args: Eff<unknown>[], proj(...args): Either<Error, Success> }
| Chain<Error, Success> // { first: Eff<unknown>, andThen(x: unknown): Eff<Error, Success> } | Chain<Error, Success> // { first: Eff<unknown>, andThen(x: unknown): Eff<Error, Success> }
| HasEffect<Error, Success> // { toEffect(): Eff<Error, Success> } | HasEffect<Error, Success> // { toEffect(): Eff<Error, Success> }
;
export type AnyEff = Eff<any, any>;
// Instance methods for `Eff` // Instance methods for `Eff`
...@@ -140,7 +142,7 @@ export function ap(): Eff<unknown, unknown> { ...@@ -140,7 +142,7 @@ export function ap(): Eff<unknown, unknown> {
} }
export function record<R extends Record<string, Eff<any, any>>>(rec: R): Eff<{ [K in keyof R]: R[K]['_Error'] }[keyof R], { [K in keyof R]: R[K]['_Success'] }> { export function record<R extends Record<string, AnyEff>>(rec: R): Eff<{ [K in keyof R]: R[K]['_Error'] }[keyof R], { [K in keyof R]: R[K]['_Success'] }> {
const keys = Object.keys(rec); const keys = Object.keys(rec);
return ap.apply(undefined, [...keys.map(k => rec[k]), (...values) => values.reduce((acc, v, idx) => (acc[keys[idx]] = v, acc), {})]); return ap.apply(undefined, [...keys.map(k => rec[k]), (...values) => values.reduce((acc, v, idx) => (acc[keys[idx]] = v, acc), {})]);
} }
...@@ -240,23 +242,27 @@ export function go<Error, Success>(effect: Eff<Error, Success>, onNext: (x: Eith ...@@ -240,23 +242,27 @@ export function go<Error, Success>(effect: Eff<Error, Success>, onNext: (x: Eith
} }
if (effect instanceof Chain) { if (effect instanceof Chain) {
const subscriptions: Array<Function|null> = []; const cancellers = new Map<AnyEff, Canceller>();
subscriptions.push(go(effect.first, result => { const handleEffect = (e: AnyEff) => {
const idx = subscriptions.length; const _onNext = result => {
subscriptions.push(go(effect.andThen(result), onNext, () => { if (e === effect.first) handleEffect(effect.andThen(result));
subscriptions[idx] = null; else onNext(result);
for (const unsub of subscriptions) if (unsub !== null) return; };
onComplete(); const _onComplete = () => {
})); if (!cancellers.has(e)) completedImmediately = true;
}, () => { cancellers.delete(e);
subscriptions[0] = null; if (cancellers.size === 0) onComplete();
for (const unsub of subscriptions) if (unsub !== null) return; };
onComplete(); let completedImmediately = false;
}));
const canceller = go(e, _onNext, _onComplete);
if (!completedImmediately) cancellers.set(e, canceller);
};
handleEffect(effect.first);
if (cancellers.size === 0) return noopFunc;
return () => subscriptions.forEach( return () => cancellers.forEach(canceller => canceller());
funOrNull => funOrNull ? funOrNull() : void 0
);
} }
if (effect instanceof Apply) { if (effect instanceof Apply) {
...@@ -355,6 +361,9 @@ export abstract class HasEffect<Error, Success> extends EffBase<Error, Success> ...@@ -355,6 +361,9 @@ export abstract class HasEffect<Error, Success> extends EffBase<Error, Success>
export const noop: Cmd<never> = new Batch([]); export const noop: Cmd<never> = new Batch([]);
export type Canceller = () => void;
export interface EffStatics { export interface EffStatics {
of: typeof of, of: typeof of,
success: typeof success, success: typeof success,
......
...@@ -12,10 +12,10 @@ Update.Do = Do; ...@@ -12,10 +12,10 @@ Update.Do = Do;
declare module "./" { declare module "./" {
interface UpdateStatic { interface UpdateStatic {
Do<State, Result>(iter: () => IterableIterator<Update<any, any>>): Update<State, Result>; Do<State, Result>(iter: () => IterableIterator<Update<Error, any, any>>): Update<Error, State, Result>;
} }
interface BoundStatics<State> { interface BoundStatics<Error, State> {
Do<Result>(iter: () => IterableIterator<Update<any, any>>): Update<State, Result>; Do<Result>(iter: () => IterableIterator<Update<Error, any, any>>): Update<Error, State, Result>;
} }
} }
......
...@@ -6,6 +6,8 @@ import * as http from '../../http'; ...@@ -6,6 +6,8 @@ import * as http from '../../http';
import { InputHTMLAttributes } from 'react'; import { InputHTMLAttributes } from 'react';
type Err = http.HttpError;
type Props = { type Props = {
}; };
...@@ -15,10 +17,10 @@ type State = { ...@@ -15,10 +17,10 @@ type State = {
response?: http.Response; response?: http.Response;
}; };
const U = Update.bind<State>(); const U = Update.bind<Err, State>();
class Widget extends Update.Component<Props, State> { class Widget extends Update.Component<Err, Props, State> {
state: State = { search: '', pending: false }; state: State = { search: '', pending: false };
handleNameChange: InputHTMLAttributes<HTMLInputElement>['onChange'] = (e) => this.setState({ search: e.target.value }); handleNameChange: InputHTMLAttributes<HTMLInputElement>['onChange'] = (e) => this.setState({ search: e.target.value });
......
...@@ -5,23 +5,32 @@ import { absurd } from '../types'; ...@@ -5,23 +5,32 @@ import { absurd } from '../types';
// ADT // ADT
export type Update<State, A> = export type Update<Error, State, A> =
| Pure<State, A> // { value: A } | Pure<Error, State, A> // { value: A }
| Modify<State, A> // { proj(x: State): State } // A ~ void | Modify<Error, State, A> // { proj(x: State): State } // A ~ void
| Patch<State, A> // { patch: Partial<State> } // A ~ void | Patch<Error, State, A> // { patch: Partial<State> } // A ~ void
| Get<State, A> // { tag: 'Get' } // A ~ State | Get<Error, State, A> // { tag: 'Get' } // A ~ State
| Children<State, A> // { key: ControlledComponent<any, any>, update: Update<any, A> } | Children<Error, State, A> // { component: React.Component<any, any>, update: Update<any, A> }
| Effect<State, A> // { effect: Eff<never, A> } | Effect<Error, State, A> // { effect: Eff<never, A> }
| FromPromise<State, A> // { promise: Promise<A> } | FromPromise<Error, State, A> // { promise: Promise<A> }
| Chain<State, A> // { update: Update<State, any>, chain(x: any): Update<State, A> } | Chain<Error, State, A> // { update: Update<Error, State, any>, chain(x: any): Update<Error, State, A> }
| Apply<State, A> // { args: Update<State, any>[], proj(...args): A } | Apply<Error, State, A> // { args: Update<Error, State, any>[], proj(...args): A }
| Batch<State, A> // { steps: Updte<State, any>[] } | Batch<Error, State, A> // { steps: Updte<Error, State, any>[] }
| Concat<State, A> // { steps: Updte<State, any>[] } | Concat<Error, State, A> // { steps: Updte<Error, State, any>[] }
| Difference<Error, State, A> // { update: Update<A>, difference(prev: State, next: State): State|Update<Error, State, void> }
; ;
export type AnyUpdate = Update<any, any, any>;
// Perform state update // Perform state update
export function go<State, A>(update: Update<State, A>, component: React.Component<any, State>, onNext: (x: A) => void, onComplete: () => void): () => void { export function go<Error, State, A>(
update: Update<Error, State, A>,
getState: () => State,
setState: SetState<State>,
onNext: (x: A) => void,
onError: (x: Error) => void,
onComplete: () => void
): Canceller {
if (update instanceof Pure) { if (update instanceof Pure) {
onNext(update.value); onNext(update.value);
onComplete(); onComplete();
...@@ -29,7 +38,7 @@ export function go<State, A>(update: Update<State, A>, component: React.Componen ...@@ -29,7 +38,7 @@ export function go<State, A>(update: Update<State, A>, component: React.Componen
} }
if (update instanceof Modify) { if (update instanceof Modify) {
component['updater'].enqueueSetState(component, update.proj, () => { setState(update.proj, () => {
onNext(void 0 as any); onNext(void 0 as any);
update.callback && update.callback(); update.callback && update.callback();
onComplete(); onComplete();
...@@ -38,7 +47,7 @@ export function go<State, A>(update: Update<State, A>, component: React.Componen ...@@ -38,7 +47,7 @@ export function go<State, A>(update: Update<State, A>, component: React.Componen
} }
if (update instanceof Patch) { if (update instanceof Patch) {
component['updater'].enqueueSetState(component, update.patch, () => { setState(update.patch, () => {
onNext(void 0 as any); onNext(void 0 as any);
update.callback && update.callback(); update.callback && update.callback();
onComplete(); onComplete();
...@@ -47,17 +56,19 @@ export function go<State, A>(update: Update<State, A>, component: React.Componen ...@@ -47,17 +56,19 @@ export function go<State, A>(update: Update<State, A>, component: React.Componen
} }
if (update instanceof Get) { if (update instanceof Get) {
onNext(component.state as any); onNext(getState() as any as A);
onComplete(); onComplete();
return noopFunc; return noopFunc;
} }
if (update instanceof Children) { if (update instanceof Children) {
return go(update.update, update.component, onNext, onComplete); const _getState = () => update.component.state;
const _setState: SetState<any> = (projOrPatch, cb?) => update.component['updater'].enqueueSetState(update.component, projOrPatch, cb);
return go(update.update, _getState, _setState, onNext, onError, onComplete);
} }
if (update instanceof Effect) { if (update instanceof Effect) {
return update.effect.run(onNext as any, onComplete); return update.effect.run(ethr => ethr.fold(onError, onNext), onComplete);
} }
if (update instanceof FromPromise) { if (update instanceof FromPromise) {
...@@ -66,28 +77,32 @@ export function go<State, A>(update: Update<State, A>, component: React.Componen ...@@ -66,28 +77,32 @@ export function go<State, A>(update: Update<State, A>, component: React.Componen
} }
if (update instanceof Chain) { if (update instanceof Chain) {
const subscriptions: Array<Function|null> = []; const cancellers = new Map<AnyUpdate, Canceller>();
subscriptions.push(go(update.update, component, result => { const handleUpdate = (u: AnyUpdate) => {
const idx = subscriptions.length; const _onNext = result => {
subscriptions.push(go(update.andThen(result), component, onNext, () => { if (u === update.update) handleUpdate(update.andThen(result));
subscriptions[idx] = null; else onNext(result);
for (const unsub of subscriptions) if (unsub !== null) return; };
onComplete(); const _onComplete = () => {
})); if (!cancellers.has(u)) completedImmediately = true;
}, () => { cancellers.delete(u);
subscriptions[0] = null; if (cancellers.size === 0) onComplete();
for (const unsub of subscriptions) if (unsub !== null) return; };
onComplete(); let completedImmediately = false;
}));
const canceller = go(u, getState, setState as any, _onNext, onError, _onComplete);
if (!completedImmediately) cancellers.set(u, canceller);
};
handleUpdate(update.update);
if (cancellers.size === 0) return noopFunc;
return () => subscriptions.forEach( return () => cancellers.forEach(canceller => canceller());
funOrNull => funOrNull ? funOrNull() : void 0
);
} }
if (update instanceof Apply) { if (update instanceof Apply) {
let allInitialized = false; let allInitialized = false;
let subscriptions: Array<Function|undefined|null> = new Array(update.args.length); let cancellers: Array<Canceller|undefined|null> = new Array(update.args.length);
const initializedFlags: Array<true|undefined> = new Array(update.args.length); const initializedFlags: Array<true|undefined> = new Array(update.args.length);
const recentValues: unknown[] = new Array(update.args.length); const recentValues: unknown[] = new Array(update.args.length);
const next = idx => result => { const next = idx => result => {
...@@ -101,46 +116,79 @@ export function go<State, A>(update: Update<State, A>, component: React.Componen ...@@ -101,46 +116,79 @@ export function go<State, A>(update: Update<State, A>, component: React.Componen
onNext(update.proj.apply(void 0, recentValues)); onNext(update.proj.apply(void 0, recentValues));
}; };
const complete = idx => () => { const complete = idx => () => {
subscriptions[idx] = null; cancellers[idx] = null;
for (const unsub of subscriptions) if (unsub !== null) return; for (const c of cancellers) if (c !== null) return;
onComplete(); onComplete();
}; };
update.args.forEach((u, idx) => { update.args.forEach((u, idx) => {
const canceller = go(u, component, next(idx), complete(idx)); const canceller = go(u, getState, setState, next(idx), onError, complete(idx));
if (subscriptions[idx] !== null) subscriptions[idx] = canceller; if (cancellers[idx] !== null) cancellers[idx] = canceller;
}); });
return () => subscriptions.forEach( return () => cancellers.forEach(canceller => canceller ? canceller() : void 0);
funOrNull => funOrNull ? funOrNull() : void 0
);
} }
if (update instanceof Batch) { if (update instanceof Batch) {
if (update.steps.length === 0) { onNext(void 0 as any); onComplete(); return noopFunc; } if (update.steps.length === 0) { onNext(void 0 as any); onComplete(); return noopFunc; }
let subscriptions: Array<Function|null>; let cancellers: Array<Canceller|null>;
const loop = idx => () => { const loop = idx => () => {
subscriptions[idx] = null; cancellers[idx] = null;
for (const unsub of subscriptions) if (unsub !== null) return; for (const unsub of cancellers) if (unsub !== null) return;
onNext(void 0 as any); onNext(void 0 as any);
onComplete(); // If control flow reaches here, that means all nested commands are completed onComplete(); // If control flow reaches here, that means all nested commands are completed
}; };
subscriptions = update.steps.map((u, idx) => go(u, component, noopFunc, loop(idx))); cancellers = update.steps.map((u, idx) => go(u, getState, setState, noopFunc, onError, loop(idx)));
return () => subscriptions.forEach( return () => cancellers.forEach(canceller => canceller ? canceller() : void 0);
funOrNull => funOrNull ? funOrNull() : void 0
);
} }
if (update instanceof Concat) { if (update instanceof Concat) {
let unsubscribe: Function|null = null; let canceller: Canceller|null = null;
const loop = idx => () => { const loop = idx => () => {
// If condition holds, then all nested effects are completed, therefore we're done // If condition holds, then all nested effects are completed, therefore we're done
if (idx >= update.steps.length) { onNext(void 0 as any); onComplete(); return; } if (idx >= update.steps.length) { onNext(void 0 as any); onComplete(); return; }
unsubscribe = go(update.steps[idx], component, noopFunc, loop(idx + 1)); canceller = go(update.steps[idx], getState, setState, noopFunc, onError, loop(idx + 1));
}; };
loop(0); loop(0);
return () => unsubscribe ? unsubscribe() : void 0; return () => canceller ? canceller() : void 0;
}
if (update instanceof Difference) {
const cancellers = new Map<AnyUpdate, Canceller>();
const _setState: SetState<State> = (patchOrProj, cb?) => {
const prev = getState();
// @ts-ignore
const next = typeof(patchOrProj) === 'function' ? patchOrProj(prev) : { ...prev, ...patchOrProj };
const stateOrUpdate = update.difference(prev, next);
if (stateOrUpdate instanceof UpdateBase) {
handleUpdate(stateOrUpdate);
} else {
setState(() => stateOrUpdate, cb);
}
};
const handleUpdate = (u: AnyUpdate) => {
const _onNext = result => {
if (u === update.update) onNext(result);
// Just ignore `void` result from spawned processes
};
const _onComplete = () => {
if (!cancellers.has(u)) completedImmediately = true;
cancellers.delete(u);
if (cancellers.size === 0) onComplete();
};
let completedImmediately = false;
const canceller = go(u, getState, u === update.update ? _setState : setState as any, _onNext, onError, _onComplete);
if (!completedImmediately) cancellers.set(u, canceller);
};
handleUpdate(update.update);
if (cancellers.size === 0) return noopFunc;
return () => cancellers.forEach(canceller => canceller());
} }
return absurd(update); return absurd(update);
...@@ -148,169 +196,191 @@ export function go<State, A>(update: Update<State, A>, component: React.Componen ...@@ -148,169 +196,191 @@ export function go<State, A>(update: Update<State, A>, component: React.Componen
// Instance methods // Instance methods
export class UpdateBase<State, A> { export class UpdateBase<Error, State, A> {
readonly _Error: Error;
readonly _State: State; readonly _State: State;
readonly _A: A; readonly _A: A;
map<B>(proj: (x: A) => B): Apply<State, B> { map<B>(proj: (x: A) => B): Apply<Error, State, B> {
const self = this as any as Update<State, A>; const self = this as any as Update<Error, State, A>;
return new Apply<State, B>([self], proj); return new Apply<Error, State, B>([self], proj);
} }
mapTo<B>(value: B): Apply<State, B> { mapTo<B>(value: B): Apply<Error, State, B> {
const self = this as any as Update<State, A>; const self = this as any as Update<Error, State, A>;
return new Apply<State, B>([self], () => value); return new Apply<Error, State, B>([self], () => value);
} }
chain<B,State2>(andThen: (x: A) => Update<State2, B>): Chain<State&State2, B>; chain<B,State2>(andThen: (x: A) => Update<Error, State2, B>): Chain<Error, State&State2, B>;
chain<B>(andThen: (x: A) => Update<State, B>): Chain<State, B>; chain<B>(andThen: (x: A) => Update<Error, State, B>): Chain<Error, State, B>;
chain<B>(andThen: (x: A) => Update<State, B>): Chain<State, B> { chain<B>(andThen: (x: A) => Update<Error, State, B>): Chain<Error, State, B> {
const self = this as any as Update<State, A>; const self = this as any as Update<Error, State, A>;
return new Chain<State, B>(self, andThen); return new Chain<Error, State, B>(self, andThen);
} }
chainTo<B,State2>(value: Update<State2, B>): Chain<State&State2, B>; chainTo<B,State2>(value: Update<Error, State2, B>): Chain<Error, State&State2, B>;
chainTo<B>(value: Update<State, B>): Chain<State, B>; chainTo<B>(value: Update<Error, State, B>): Chain<Error, State, B>;
chainTo<B>(value: Update<State, B>): Chain<State, B> { chainTo<B>(value: Update<Error, State, B>): Chain<Error, State, B> {
const self = this as any as Update<State, A>; const self = this as any as Update<Error, State, A>;
return new Chain<State, B>(self, () => value); return new Chain<Error, State, B>(self, () => value);
} }
run(component: React.Component<any, State>, onNext?: (x: A) => void, onComplete?: () => void): () => void { run(component: React.Component<any, State>, onNext?: (x: A) => void, onError?: (x: Error) => void, onComplete?: () => void): Canceller {
const self = this as any as Update<State, A>; const self = this as any as Update<Error, State, A>;
return go(self, component, onNext || noopFunc, onComplete || noopFunc); const getState = () => component.state;
const setState: SetState<State> = (projOrPatch, cb?) => component['updater'].enqueueSetState(component, projOrPatch, cb);
return go(self, getState, setState, onNext || noopFunc, onError || noopFunc, onComplete || noopFunc);
} }
pending(): Update<State & { pending: boolean }, A> { pending(): Update<Error, State & { pending: boolean }, A> {
// @ts-ignore // @ts-ignore
return patch({ pending: true }).chainTo(this.chain(value => patch({ pending: false }).mapTo(value))); return patch({ pending: true }).chainTo(this.chain(value => patch({ pending: false }).mapTo(value)));
} }
} }
export function ap<State,A,B>(a: Update<State,A>, f: (a: A) => B): Update<State,B>; // SetState
export function ap<State,A,B,C>(a: Update<State,A>, b: Update<State,B>, f: (a: A, b: B) => C): Update<State,C>; export type SetState<State> = {
export function ap<State,A,B,C,D>(a: Update<State,A>, b: Update<State,B>, c: Update<State,C>, f: (a: A, b: B, c: C) => D): Update<State,D>; (patch: Partial<State>, cb?: () => void): void;
export function ap<State,A,B,C,D,E>(a: Update<State,A>, b: Update<State,B>, c: Update<State,C>, d: Update<State,D>, f: (a: A, b: B, c: C, d: D) => E): Update<State,E>; (proj: (x: State) => State, cb?: () => void): void;
export function ap<State,A,B,C,D,E,F>(a: Update<State,A>, b: Update<State,B>, c: Update<State,C>, d: Update<State,D>, e: Update<State,E>, f: (a: A, b: B, c: C, d: D, e: E) => F): Update<State,F>; };
export function ap<State,A,B,C,D,E,F,G>(a: Update<State,A>, b: Update<State,B>, c: Update<State,C>, d: Update<State,D>, e: Update<State,E>, f_: Update<State,F>, f: (a: A, b: B, c: C, d: D, e: E, f: F) => G): Update<State,G>;
export function ap(): Update<unknown, unknown> {
export function ap<Error, State,A,B>(a: Update<Error, State,A>, f: (a: A) => B): Update<Error, State,B>;
export function ap<Error, State,A,B,C>(a: Update<Error, State,A>, b: Update<Error, State,B>, f: (a: A, b: B) => C): Update<Error, State,C>;
export function ap<Error, State,A,B,C,D>(a: Update<Error, State,A>, b: Update<Error, State,B>, c: Update<Error, State,C>, f: (a: A, b: B, c: C) => D): Update<Error, State,D>;
export function ap<Error, State,A,B,C,D,E>(a: Update<Error, State,A>, b: Update<Error, State,B>, c: Update<Error, State,C>, d: Update<Error, State,D>, f: (a: A, b: B, c: C, d: D) => E): Update<Error, State,E>;
export function ap<Error, State,A,B,C,D,E,F>(a: Update<Error, State,A>, b: Update<Error, State,B>, c: Update<Error, State,C>, d: Update<Error, State,D>, e: Update<Error, State,E>, f: (a: A, b: B, c: C, d: D, e: E) => F): Update<Error, State,F>;
export function ap<Error, State,A,B,C,D,E,F,G>(a: Update<Error, State,A>, b: Update<Error, State,B>, c: Update<Error, State,C>, d: Update<Error, State,D>, e: Update<Error, State,E>, f_: Update<Error, State,F>, f: (a: A, b: B, c: C, d: D, e: E, f: F) => G): Update<Error, State,G>;
export function ap(): Update<unknown, unknown, unknown> {
const args = Array.prototype.slice.call(arguments, 0, arguments.length - 1); const args = Array.prototype.slice.call(arguments, 0, arguments.length - 1);
const proj = arguments[arguments.length - 1]; const proj = arguments[arguments.length - 1];
return new Apply(args, proj); return new Apply(args, proj);
} }
export function of<A>(value: A): Pure<never, A> { export function of<A>(value: A): Pure<never, {}, A> {
return new Pure(value); return new Pure(value);
} }
export function patch<State>(patch: Partial<State>, callback?: () => void): Patch<State, void> { export function patch<State>(patch: Partial<State>, callback?: () => void): Patch<never, State, void> {
return new Patch(patch, callback); return new Patch(patch, callback);
} }
export function modify<State>(proj: (x: State) => State, callback?: () => void): Modify<State, void> { export function modify<State>(proj: (x: State) => State, callback?: () => void): Modify<never, State, void> {
return new Modify(proj, callback); return new Modify(proj, callback);
} }
export function effect<Error, Success>(eff: Eff<Error, Success>): Effect<{}, Either<Error, Success>> { export function effect<Error, Success>(eff: Eff<Error, Success>): Effect<Error, {}, Success> {
return new Effect(eff as any); return new Effect(eff as any);
} }
export function batch<State>(...steps: Update<State, any>[]): Batch<State, void>; export function batch<Error, State>(...steps: Update<Error, State, any>[]): Batch<Error, State, void>;
export function batch<State>(steps: Update<State, any>[]): Batch<State, void>; export function batch<Error, State>(steps: Update<Error, State, any>[]): Batch<Error, State, void>;
export function batch(): Batch<unknown, void> { export function batch(): Batch<never, unknown, void> {
const steps = Array.isArray(arguments[0]) ? arguments[0] : arguments; const steps = Array.isArray(arguments[0]) ? arguments[0] : arguments;
return new Batch(steps); return new Batch(steps);
} }
export function concat<State>(...steps: Update<State, any>[]): Concat<State, void>; export function concat<Error, State>(...steps: Update<Error, State, any>[]): Concat<Error, State, void>;
export function concat<State>(steps: Update<State, any>[]): Concat<State, void>; export function concat<Error, State>(steps: Update<Error, State, any>[]): Concat<Error, State, void>;
export function concat(): Concat<unknown, void> { export function concat(): Concat<never, unknown, void> {
const steps = Array.isArray(arguments[0]) ? arguments[0] : arguments; const steps = Array.isArray(arguments[0]) ? arguments[0] : arguments;
return new Concat(steps); return new Concat(steps);
} }
export function difference<Error, State, A>(update: Update<Error, State, A>, diff: (prev: State, next: State) => State|Update<Error, State, void>): Difference<Error, State, A> {
return new Difference(update, diff);
}
class Pure<State, A> extends UpdateBase<State, A> { class Pure<Error, State, A> extends UpdateBase<Error, State, A> {
constructor( constructor(
readonly value: A, readonly value: A,
) { super(); } ) { super(); }
} }
class Modify<State, A> extends UpdateBase<State, A> { class Modify<Error, State, A> extends UpdateBase<Error, State, A> {
constructor( constructor(
readonly proj: (x: State) => State, readonly proj: (x: State) => State,
readonly callback?: () => void, readonly callback?: () => void,
) { super(); } ) { super(); }
} }
class Patch<State, A> extends UpdateBase<State, A> { class Patch<Error, State, A> extends UpdateBase<Error, State, A> {
constructor( constructor(
readonly patch: Partial<State>, readonly patch: Partial<State>,
readonly callback?: () => void, readonly callback?: () => void,
) { super(); } ) { super(); }
} }
class Get<State, A> extends UpdateBase<State, A> { class Get<Error, State, A> extends UpdateBase<Error, State, A> {
} }
class Children<State, A> extends UpdateBase<State, A> { class Children<Error, State, A> extends UpdateBase<Error, State, A> {
constructor( constructor(
readonly component: React.Component<any, any>, readonly component: React.Component<any, any>,
readonly update: Update<any, A> readonly update: Update<never, {}, A>
) { super(); } ) { super(); }
} }
class Effect<State, A> extends UpdateBase<State, A> { class Effect<Error, State, A> extends UpdateBase<Error, State, A> {
constructor( constructor(
readonly effect: Eff<never, A>, readonly effect: Eff<Error, A>,
) { super(); } ) { super(); }
} }
class FromPromise<State, A> extends UpdateBase<State, A> { class FromPromise<Error, State, A> extends UpdateBase<Error, State, A> {
constructor( constructor(
readonly promise: () => Promise<A>, readonly promise: () => Promise<A>,
) { super(); } ) { super(); }
} }
class Chain<State, A> extends UpdateBase<State, A> { class Chain<Error, State, A> extends UpdateBase<Error, State, A> {
constructor( constructor(
readonly update: Update<State, any>, readonly update: Update<Error, State, any>,
readonly andThen: (x: any) => Update<State, A>, readonly andThen: (x: any) => Update<Error, State, A>,
) { super(); } ) { super(); }
} }
class Apply<State, A> extends UpdateBase<State, A> { class Apply<Error, State, A> extends UpdateBase<Error, State, A> {
constructor( constructor(
readonly args: Update<State, any>[], readonly args: Update<Error, State, any>[],
readonly proj: (...args) => A, readonly proj: (...args) => A,
) { super(); } ) { super(); }
} }
class Batch<State, A> extends UpdateBase<State, A> { class Batch<Error, State, A> extends UpdateBase<Error, State, A> {
constructor( constructor(
readonly steps: Update<State, any>[], readonly steps: Update<Error, State, any>[],
) { super(); } ) { super(); }
} }
class Concat<State, A> extends UpdateBase<State, A> { class Concat<Error, State, A> extends UpdateBase<Error, State, A> {
constructor( constructor(
readonly steps: Update<State, any>[], readonly steps: Update<Error, State, any>[],
) { super(); }
}
class Difference<Error, State, A> extends UpdateBase<Error, State, A> {
constructor(
readonly update: Update<Error, State, A>,
readonly difference: (prev: State, next: State) => State|Update<Error, State, void>,
) { super(); } ) { super(); }
} }
// Functional helpers // Functional helpers
const noopFunc = () => {}; const noopFunc = () => {};
export type Canceller = () => void;
export const get = new Get(); export const get = new Get<never, {}, void>();
export class Component<Props, State> extends React.Component<Props, State> { export class Component<Error, Props, State> extends React.Component<Props, State> {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
// @ts-ignore // @ts-ignore
...@@ -321,24 +391,29 @@ export class Component<Props, State> extends React.Component<Props, State> { ...@@ -321,24 +391,29 @@ export class Component<Props, State> extends React.Component<Props, State> {
state: ((prevState: Readonly<State>, props: Readonly<Props>) => (Pick<State, K> | State | null)) | (Pick<State, K> | State | null), state: ((prevState: Readonly<State>, props: Readonly<Props>) => (Pick<State, K> | State | null)) | (Pick<State, K> | State | null),
callback?: () => void callback?: () => void
): void; ): void;
setState(update: Update<State, void>, callback?: () => void): void; setState(update: Update<Error, State, void>, callback?: () => void): void;
setState(updateOrProjOrPatch, callback?) { setState(updateOrProjOrPatch, callback?) {
if (updateOrProjOrPatch instanceof UpdateBase) { if (updateOrProjOrPatch instanceof UpdateBase) {
const onUpdate = this.props['onUpdate'] as unknown; const onUpdate = this.props['onUpdate'] as unknown;
const update = this.prepareUpdate(updateOrProjOrPatch);
if (typeof(onUpdate) === 'function') { if (typeof(onUpdate) === 'function') {
onUpdate(updateOrProjOrPatch); onUpdate(update);
} else { } else {
updateOrProjOrPatch.run(this, callback); update.run(this, callback);
} }
return; return;
} }
if (typeof(updateOrProjOrPatch) === 'function') { if (typeof(updateOrProjOrPatch) === 'function') {
return this.setState(new Modify<State, void>(updateOrProjOrPatch, callback)) return this.setState(new Modify<Error, State, void>(updateOrProjOrPatch, callback))
} else { } else {
return this.setState(new Patch<State, void>(updateOrProjOrPatch, callback)) return this.setState(new Patch<Error, State, void>(updateOrProjOrPatch, callback))
} }
} }
prepareUpdate<A>(update: Update<Error, State, A>): Update<Error, State, A> {
return update;
}
} }
export interface UpdateStatic { export interface UpdateStatic {
...@@ -352,6 +427,7 @@ export interface UpdateStatic { ...@@ -352,6 +427,7 @@ export interface UpdateStatic {
concat: typeof concat, concat: typeof concat,
Component: typeof Component, Component: typeof Component,
bind: typeof bind, bind: typeof bind,
difference: typeof difference,
}; };
...@@ -366,28 +442,33 @@ export const Update = { ...@@ -366,28 +442,33 @@ export const Update = {
concat, concat,
Component, Component,
bind, bind,
difference,
} as UpdateStatic; } as UpdateStatic;
export interface BoundStatics<State> { export interface BoundStatics<Error, State> {
ap<A,B,C>(a: Update<State,A>, b: Update<State,B>, f: (a: A, b: B) => C): Apply<State,C>; ap<A,B,C>(a: Update<Error, State,A>, b: Update<Error, State,B>, f: (a: A, b: B) => C): Apply<Error, State,C>;
ap<A,B,C,D>(a: Update<State,A>, b: Update<State,B>, c: Update<State,C>, f: (a: A, b: B, c: C) => D): Apply<State,D>; ap<A,B,C,D>(a: Update<Error, State,A>, b: Update<Error, State,B>, c: Update<Error, State,C>, f: (a: A, b: B, c: C) => D): Apply<Error, State,D>;
ap<A,B,C,D,E>(a: Update<State,A>, b: Update<State,B>, c: Update<State,C>, d: Update<State,D>, f: (a: A, b: B, c: C, d: D) => E): Apply<State,E>; ap<A,B,C,D,E>(a: Update<Error, State,A>, b: Update<Error, State,B>, c: Update<Error, State,C>, d: Update<Error, State,D>, f: (a: A, b: B, c: C, d: D) => E): Apply<Error, State,E>;
ap<A,B,C,D,E,F>(a: Update<State,A>, b: Update<State,B>, c: Update<State,C>, d: Update<State,D>, e: Update<State,E>, f: (a: A, b: B, c: C, d: D, e: E) => F): Apply<State,F>; ap<A,B,C,D,E,F>(a: Update<Error, State,A>, b: Update<Error, State,B>, c: Update<Error, State,C>, d: Update<Error, State,D>, e: Update<Error, State,E>, f: (a: A, b: B, c: C, d: D, e: E) => F): Apply<Error, State,F>;
ap<A,B,C,D,E,F,G>(a: Update<State,A>, b: Update<State,B>, c: Update<State,C>, d: Update<State,D>, e: Update<State,E>, f_: Update<State,F>, f: (a: A, b: B, c: C, d: D, e: E, f: F) => G): Apply<State,G>; ap<A,B,C,D,E,F,G>(a: Update<Error, State,A>, b: Update<Error, State,B>, c: Update<Error, State,C>, d: Update<Error, State,D>, e: Update<Error, State,E>, f_: Update<Error, State,F>, f: (a: A, b: B, c: C, d: D, e: E, f: F) => G): Apply<Error, State,G>;
batch(...steps: Update<State, any>[]): Batch<State, void>; batch(...steps: Update<Error, State, any>[]): Batch<Error, State, void>;
batch(steps: Update<State, any>[]): Batch<State, void>; batch(steps: Update<Error, State, any>[]): Batch<Error, State, void>;
concat(...steps: Update<State, any>[]): Concat<State, void>; concat(...steps: Update<Error, State, any>[]): Concat<Error, State, void>;
concat(steps: Update<State, any>[]): Concat<State, void>; concat(steps: Update<Error, State, any>[]): Concat<Error, State, void>;
of<A>(value: A): Pure<State,A>; of<A>(value: A): Pure<Error, State, A>;
patch(patch: Partial<State>, callback?: () => void): Patch<State, void>; patch(patch: Partial<State>, callback?: () => void): Patch<Error, State, void>;
modify(proj: (x: State) => State, callback?: () => void): Modify<State, void>; modify(proj: (x: State) => State, callback?: () => void): Modify<Error, State, void>;
effect<Error, Success>(eff: Eff<Error, Success>): Effect<State, Either<Error, Success>>; effect<Error, Success>(eff: Eff<Error, Success>): Effect<Error, State, Success>;
get: Get<State, State>; get: Get<Error, State, State>;
difference<A>(update: Update<Error, State, A>, diff: (prev: State, next: State) => State|Update<Error, State, void>): Difference<Error, State, A>;
Component: {
new <Props>(): Component<Error, Props, State>;
};
} }
export function bind<State>() { export function bind<Error, State>() {
return Update as any as BoundStatics<State>; return Update as any as BoundStatics<Error, State>;
} }
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