|
|
@ -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,85 +91,99 @@ 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() {
|
|
|
|
});
|
|
|
|
unsub(), controller.cleanup();
|
|
|
|
return function stop() {
|
|
|
|
|
|
|
|
unsub(), cleanup();
|
|
|
|
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** StartStopNotifier when deriving an array of stores */
|
|
|
|
/** derived StartStopNotifier when given an array of stores */
|
|
|
|
const multiple = <S extends ArrayStore, T>(stores: S, { derive, cleanup }: Controller<S, T>): StartStopNotifier<T> =>
|
|
|
|
const multiple = <T>(stores: ArrayStore, controller: DeriverController): StartStopNotifier<T> => set => {
|
|
|
|
function StartStopMultiple(set) {
|
|
|
|
const values = new Array(stores.length);
|
|
|
|
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) =>
|
|
|
|
subscribe(
|
|
|
|
subscribe(
|
|
|
|
store,
|
|
|
|
store,
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
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 };
|
|
|
|
|
|
|
|
}
|
|
|
|