From a5aa222a1e5ca53f99fcde15f0f072e58f35f9c2 Mon Sep 17 00:00:00 2001 From: pushkine Date: Wed, 15 Apr 2020 18:37:42 +0200 Subject: [PATCH] Update index.ts --- src/runtime/store/index.ts | 175 +++++++++++++++++-------------------- 1 file changed, 82 insertions(+), 93 deletions(-) diff --git a/src/runtime/store/index.ts b/src/runtime/store/index.ts index 0c195046b2..d65fb85944 100644 --- a/src/runtime/store/index.ts +++ b/src/runtime/store/index.ts @@ -8,9 +8,9 @@ export { get_store_value as get }; /** Sets the value of a store. */ type Setter = (value: T) => void; - /** Called on last removed subscriber */ type StopCallback = () => void; +type CleanupCallback = () => void; /** Function called on first added subscriber * If a callback is returned it will be called on last removed subscriber */ type StartStopNotifier = (set: Setter) => StopCallback | void; @@ -21,42 +21,17 @@ type Updater = (value: T) => T; type Invalidator = () => void; type SubscribeInvalidateTuple = [Subscriber, Invalidator]; export type Store = Writable | Readable; -type ArrayStore = Array>; type SingleStore = Store; +type ArrayStore = SingleStore[]; type ValuesOf = { [K in keyof T]: T[K] extends Store ? U : never }; type ValueOf = T extends Store ? U : never; -type StoreValues = T extends Store ? ValueOf : T extends ArrayStore ? ValuesOf : T; -/** The value of the derived store is the value returned by the function */ -type AutoDeriver = (values: StoreValues) => T; -/** The value of the derived store is set manually through Setter calls */ -type ManualDeriver = (values: StoreValues, set: Setter) => Unsubscriber | void; -/** Type of derivation function, is decided by the number of arguments */ -type Deriver = AutoDeriver | ManualDeriver; -type DerivedValue = T extends Deriver ? U : never; -type DeriverController = { - update(values: StoreValues, set: Setter): void; - cleanup?(): void; -}; +type StoreValues = T extends SingleStore ? ValueOf : T extends ArrayStore ? ValuesOf : T; export interface Readable { - /** - * Subscribe on value changes. - * @param run subscription callback - * @param invalidate cleanup callback - */ subscribe(run: Subscriber, invalidate?: Invalidator): Unsubscriber; } export interface Writable extends Readable { - /** - * Set value and inform subscribers. - * @param value to set - */ - set(value: T): void; - - /** - * Update value using callback and inform subscribers. - * @param updater callback - */ + set: Setter; update(updater: Updater): void; } @@ -65,16 +40,16 @@ const subscriber_queue = []; /** * Creates a `Readable` store that allows reading by subscription. * @param value initial value - * @param {StartStopNotifier}start start and stop notifications for subscriptions + * @param start start and stop notifications for subscriptions */ export function readable(value: T, start: StartStopNotifier): Readable { return { subscribe: writable(value, start).subscribe }; } /** - * Create a `Writable` store that allows both updating and reading by subscription. - * @param {*=}value initial value - * @param {StartStopNotifier=}start start and stop notifications for subscriptions + * Creates a `Writable` store that allows both updating and reading by subscription. + * @param value initial value + * @param start start and stop notifications for subscriptions */ export function writable(value: T, start: StartStopNotifier = noop): Writable { let stop: Unsubscriber; @@ -116,85 +91,99 @@ export function writable(value: T, start: StartStopNotifier = noop): Writa } /** - * Derived value store by synchronizing one or more readable stores and - * applying an aggregation function over its input values. + * Creates a `Readable` store whose value depends on other stores. * - * @param stores - input stores + * @param stores - input store(s) * @param fn - function callback that aggregates the values - * @param initial_value - when used asynchronously + * @param initial_value - initial value */ -export function derived, T = DerivedValue>( +export function derived, T>( stores: S, fn: F, initial_value?: T -) { - const mode: DeriverController = fn.length < 2 ? auto(fn as AutoDeriver) : manual(fn as ManualDeriver); - const deriver = Array.isArray(stores) ? multiple(stores as ArrayStore, mode) : single(stores as SingleStore, mode); - return readable(initial_value, deriver) as Readable; +): Readable { + const mode = fn.length < 2 ? auto(fn as AutoDeriver) : manual(fn as ManualDeriver); + const deriver = Array.isArray(stores) ? multiple(stores, mode) : single(stores as SingleStore, mode); + return readable(initial_value, deriver); } -/** DERIVING LOGIC */ - -/** derived StartStopNotifier when given a single store */ -const single = (store: SingleStore, controller: DeriverController): StartStopNotifier => set => { - const unsub = subscribe(store, value => controller.update(value, set)); - return function stop() { - unsub(), controller.cleanup(); +/** StartStopNotifier when deriving a single store */ +const single = (store: S, { derive, cleanup }: Controller): StartStopNotifier => + function StartStopSingle(set) { + const unsub = subscribe(store, value => { + derive(value, set); + }); + return function stop() { + unsub(), cleanup(); + }; }; -}; - -/** derived StartStopNotifier when given an array of stores */ -const multiple = (stores: ArrayStore, controller: DeriverController): StartStopNotifier => set => { - const values = new Array(stores.length); - let pending = 1 << stores.length; - - const unsubs = stores.map((store, index) => - subscribe( - store, - value => { - values[index] = value; - pending &= ~(1 << index); - if (!pending) controller.update(values, set); - }, - () => { - pending |= 1 << index; - } - ) - ); - - pending &= ~(1 << stores.length); - controller.update(values, set); - - return function stop() { - unsubs.forEach(v => v()), controller.cleanup(); + +/** StartStopNotifier when deriving an array of stores */ +const multiple = (stores: S, { derive, cleanup }: Controller): StartStopNotifier => + function StartStopMultiple(set) { + const values = new Array(stores.length) as StoreValues; + let pending = 1 << stores.length; + + const unsubs = stores.map((store, index) => + subscribe( + store, + value => { + values[index] = value; + pending &= ~(1 << index); + if (!pending) derive(values, set); + }, + () => { + pending |= 1 << index; + } + ) + ); + + pending &= ~(1 << stores.length); + derive(values, set); + + return function stop() { + unsubs.forEach(v => v()); + cleanup(); + }; }; -}; +type Deriver = AutoDeriver | ManualDeriver; + +interface Controller { + derive(payload: StoreValues, set: Setter): void; + cleanup(): void; +} /** 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 */ -const auto = (fn: AutoDeriver): DeriverController => ({ - update(payload, set) { +type AutoDeriver = (values: StoreValues) => T; +function auto(fn: AutoDeriver) { + function derive(payload: StoreValues, set: Setter) { 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] * - * derived value is a value set() manually - * if a callback is returned it is called on next update + * derived value is set() manually + * if a callback is returned it is called on before next update */ -const manual = (fn: ManualDeriver): DeriverController => ({ - update(payload, set) { - this.cleanup(); - this.cleanup = fn(payload, set) as Unsubscriber; - if (typeof this.cleanup !== 'function') this.cleanup = noop; - }, - cleanup: noop, -}); +type ManualDeriver = (values: StoreValues, set: Setter) => CleanupCallback | void; +function manual(fn: ManualDeriver) { + let callback = noop; + function derive(payload: StoreValues, set: Setter) { + callback(); + callback = fn(payload, set) as CleanupCallback; + if (typeof callback !== 'function') callback = noop; + } + function cleanup() { + callback(); + } + return { derive, cleanup }; +}