Update index.ts

pull/4668/head
pushkine 6 years ago
parent 396fbd7ede
commit a5aa222a1e

@ -8,9 +8,9 @@ export { get_store_value as get };
/** Sets the value of a store. */ /** Sets the value of a store. */
type Setter<T> = (value: T) => void; type Setter<T> = (value: T) => void;
/** Called on last removed subscriber */ /** Called on last removed subscriber */
type StopCallback = () => void; type StopCallback = () => void;
type CleanupCallback = () => void;
/** Function called on first added subscriber /** Function called on first added subscriber
* If a callback is returned it will be called on last removed subscriber */ * If a callback is returned it will be called on last removed subscriber */
type StartStopNotifier<T> = (set: Setter<T>) => StopCallback | void; type StartStopNotifier<T> = (set: Setter<T>) => StopCallback | void;
@ -21,42 +21,17 @@ type Updater<T> = (value: T) => T;
type Invalidator = () => void; type Invalidator = () => void;
type SubscribeInvalidateTuple<T> = [Subscriber<T>, Invalidator]; type SubscribeInvalidateTuple<T> = [Subscriber<T>, Invalidator];
export type Store<T> = Writable<T> | Readable<T>; export type Store<T> = Writable<T> | Readable<T>;
type ArrayStore = Array<Store<any>>;
type SingleStore = Store<any>; type SingleStore = Store<any>;
type ArrayStore = SingleStore[];
type ValuesOf<T> = { [K in keyof T]: T[K] extends Store<infer U> ? U : never }; type ValuesOf<T> = { [K in keyof T]: T[K] extends Store<infer U> ? U : never };
type ValueOf<T> = T extends Store<infer U> ? U : never; type ValueOf<T> = T extends Store<infer U> ? U : never;
type StoreValues<T> = T extends Store<any> ? ValueOf<T> : T extends ArrayStore ? ValuesOf<T> : T; type StoreValues<T> = T extends SingleStore ? ValueOf<T> : T extends ArrayStore ? ValuesOf<T> : T;
/** The value of the derived store is the value returned by the function */
type AutoDeriver<S, T> = (values: StoreValues<S>) => T;
/** The value of the derived store is set manually through Setter calls */
type ManualDeriver<S, T> = (values: StoreValues<S>, set: Setter<T>) => Unsubscriber | void;
/** Type of derivation function, is decided by the number of arguments */
type Deriver<S, T> = AutoDeriver<S, T> | ManualDeriver<S, T>;
type DerivedValue<T> = T extends Deriver<T, infer U> ? U : never;
type DeriverController = {
update<S, T>(values: StoreValues<S>, set: Setter<T>): void;
cleanup?(): void;
};
export interface Readable<T> { export interface Readable<T> {
/**
* Subscribe on value changes.
* @param run subscription callback
* @param invalidate cleanup callback
*/
subscribe(run: Subscriber<T>, invalidate?: Invalidator): Unsubscriber; subscribe(run: Subscriber<T>, invalidate?: Invalidator): Unsubscriber;
} }
export interface Writable<T> extends Readable<T> { export interface Writable<T> extends Readable<T> {
/** set: Setter<T>;
* Set value and inform subscribers.
* @param value to set
*/
set(value: T): void;
/**
* Update value using callback and inform subscribers.
* @param updater callback
*/
update(updater: Updater<T>): void; update(updater: Updater<T>): void;
} }
@ -65,16 +40,16 @@ const subscriber_queue = [];
/** /**
* Creates a `Readable` store that allows reading by subscription. * Creates a `Readable` store that allows reading by subscription.
* @param value initial value * @param value initial value
* @param {StartStopNotifier}start start and stop notifications for subscriptions * @param start start and stop notifications for subscriptions
*/ */
export function readable<T>(value: T, start: StartStopNotifier<T>): Readable<T> { export function readable<T>(value: T, start: StartStopNotifier<T>): Readable<T> {
return { subscribe: writable(value, start).subscribe }; return { subscribe: writable(value, start).subscribe };
} }
/** /**
* Create a `Writable` store that allows both updating and reading by subscription. * Creates a `Writable` store that allows both updating and reading by subscription.
* @param {*=}value initial value * @param value initial value
* @param {StartStopNotifier=}start start and stop notifications for subscriptions * @param start start and stop notifications for subscriptions
*/ */
export function writable<T>(value: T, start: StartStopNotifier<T> = noop): Writable<T> { export function writable<T>(value: T, start: StartStopNotifier<T> = noop): Writable<T> {
let stop: Unsubscriber; let stop: Unsubscriber;
@ -116,36 +91,37 @@ export function writable<T>(value: T, start: StartStopNotifier<T> = noop): Writa
} }
/** /**
* Derived value store by synchronizing one or more readable stores and * Creates a `Readable` store whose value depends on other stores.
* applying an aggregation function over its input values.
* *
* @param stores - input stores * @param stores - input store(s)
* @param fn - function callback that aggregates the values * @param fn - function callback that aggregates the values
* @param initial_value - when used asynchronously * @param initial_value - initial value
*/ */
export function derived<S extends SingleStore | ArrayStore, F extends Deriver<S, T | any>, T = DerivedValue<F>>( export function derived<S extends SingleStore | ArrayStore, F extends Deriver<S, T>, T>(
stores: S, stores: S,
fn: F, fn: F,
initial_value?: T initial_value?: T
) { ): Readable<T> {
const mode: DeriverController = fn.length < 2 ? auto(fn as AutoDeriver<S, T>) : manual(fn as ManualDeriver<S, T>); const mode = fn.length < 2 ? auto(fn as AutoDeriver<S, T>) : manual(fn as ManualDeriver<S, T>);
const deriver = Array.isArray(stores) ? multiple(stores as ArrayStore, mode) : single(stores as SingleStore, mode); const deriver = Array.isArray(stores) ? multiple(stores, mode) : single(stores as SingleStore, mode);
return readable(initial_value, deriver) as Readable<T>; return readable(initial_value, deriver);
} }
/** DERIVING LOGIC */ /** StartStopNotifier when deriving a single store */
const single = <S extends SingleStore, T>(store: S, { derive, cleanup }: Controller<S, T>): StartStopNotifier<T> =>
/** derived StartStopNotifier when given a single store */ function StartStopSingle(set) {
const single = <T>(store: SingleStore, controller: DeriverController): StartStopNotifier<T> => set => { const unsub = subscribe(store, value => {
const unsub = subscribe(store, value => controller.update(value, set)); derive(value, set);
});
return function stop() { return function stop() {
unsub(), controller.cleanup(); unsub(), cleanup();
}; };
}; };
/** derived StartStopNotifier when given an array of stores */ /** StartStopNotifier when deriving an array of stores */
const multiple = <T>(stores: ArrayStore, controller: DeriverController): StartStopNotifier<T> => set => { const multiple = <S extends ArrayStore, T>(stores: S, { derive, cleanup }: Controller<S, T>): StartStopNotifier<T> =>
const values = new Array(stores.length); function StartStopMultiple(set) {
const values = new Array(stores.length) as StoreValues<S>;
let pending = 1 << stores.length; let pending = 1 << stores.length;
const unsubs = stores.map((store, index) => const unsubs = stores.map((store, index) =>
@ -154,7 +130,7 @@ const multiple = <T>(stores: ArrayStore, controller: DeriverController): StartSt
value => { value => {
values[index] = value; values[index] = value;
pending &= ~(1 << index); pending &= ~(1 << index);
if (!pending) controller.update(values, set); if (!pending) derive(values, set);
}, },
() => { () => {
pending |= 1 << index; pending |= 1 << index;
@ -163,38 +139,51 @@ const multiple = <T>(stores: ArrayStore, controller: DeriverController): StartSt
); );
pending &= ~(1 << stores.length); pending &= ~(1 << stores.length);
controller.update(values, set); derive(values, set);
return function stop() { return function stop() {
unsubs.forEach(v => v()), controller.cleanup(); unsubs.forEach(v => v());
cleanup();
}; };
}; };
type Deriver<S, T> = AutoDeriver<S, T> | ManualDeriver<S, T>;
interface Controller<S, T> {
derive(payload: StoreValues<S>, set: Setter<T>): void;
cleanup(): void;
}
/** UPDATE/CLEANUP CONTROLLERS */ /** UPDATE/CLEANUP CONTROLLERS */
/** /**
* mode "auto" : function has <2 arguments * mode "auto" : deriving function has <2 arguments
* the derived value is the value returned by the function * the derived value is the value returned by the function
*/ */
const auto = (fn: AutoDeriver<any, any>): DeriverController => ({ type AutoDeriver<S, T> = (values: StoreValues<S>) => T;
update(payload, set) { function auto<S, T>(fn: AutoDeriver<S, T>) {
function derive(payload: StoreValues<S>, set: Setter<T>) {
set(fn(payload)); set(fn(payload));
}, }
cleanup: noop, return { derive, cleanup: noop };
}); }
/** /**
* mode "manual" : function has >1 arguments * mode "manual" : deriving function has >1 arguments
* [(...args) does not count as an argument] * [(...args) does not count as an argument]
* *
* derived value is a value set() manually * derived value is set() manually
* if a callback is returned it is called on next update * if a callback is returned it is called on before next update
*/ */
const manual = (fn: ManualDeriver<any, any>): DeriverController => ({ type ManualDeriver<S, T> = (values: StoreValues<S>, set: Setter<T>) => CleanupCallback | void;
update(payload, set) { function manual<S, T>(fn: ManualDeriver<S, T>) {
this.cleanup(); let callback = noop;
this.cleanup = fn(payload, set) as Unsubscriber; function derive(payload: StoreValues<S>, set: Setter<T>) {
if (typeof this.cleanup !== 'function') this.cleanup = noop; callback();
}, callback = fn(payload, set) as CleanupCallback;
cleanup: noop, if (typeof callback !== 'function') callback = noop;
}); }
function cleanup() {
callback();
}
return { derive, cleanup };
}

Loading…
Cancel
Save