diff --git a/src/runtime/store/index.ts b/src/runtime/store/index.ts index 2708520fa6..ae1d8ced3f 100644 --- a/src/runtime/store/index.ts +++ b/src/runtime/store/index.ts @@ -1,258 +1,186 @@ -import { - run_all, - subscribe, - noop, - safe_not_equal, - is_function, - get_store_value -} from '../internal'; - -/** Callback to inform of a value updates. */ -export type Subscriber = (value: T) => void; - -/** Unsubscribes from value updates. */ -export type Unsubscriber = () => void; - -/** Callback to update a value. */ -export type Updater = (value: T) => T; - -/** Cleanup logic callback. */ -type Invalidator = (value?: T) => void; - -/** - * Start and stop notification callbacks. - * This function is called when the first subscriber subscribes. - * - * @param {(value: T) => void} set Function that sets the value of the store. - * @param {(value: Updater) => void} set Function that sets the value of the store after passing the current value to the update function. - * @returns {void | (() => void)} Optionally, a cleanup function that is called when the last remaining - * subscriber unsubscribes. - */ -export type StartStopNotifier = ( - set: (value: T) => void, - update: (fn: Updater) => void -) => void | (() => void); - -/** Readable interface for subscribing. */ -export interface Readable { - /** - * Subscribe on value changes. - * @param run subscription callback - * @param invalidate cleanup callback - */ - subscribe(this: void, run: Subscriber, invalidate?: Invalidator): Unsubscriber; -} - -/** Writable interface for both updating and subscribing. */ -export interface Writable extends Readable { - /** - * Set value and inform subscribers. - * @param value to set - */ - set(this: void, value: T): void; - - /** - * Update value using callback and inform subscribers. - * @param updater callback - */ - update(this: void, updater: Updater): void; -} - -/** Pair of subscriber and invalidator. */ -type SubscribeInvalidateTuple = [Subscriber, Invalidator]; - +import { run_all, subscribe, noop, safe_not_equal, is_function, get_store_value } from '../internal'; const subscriber_queue = []; - /** * Creates a `Readable` store that allows reading by subscription. - * @param value initial value - * @param {StartStopNotifier} [start] + * @param {T} value initial value + * @param {StartStopNotifier} start undefined + * @returns {import("/Users/elliottjohnson/dev/sveltejs/svelte/index.ts-to-jsdoc").Readable} */ -export function readable(value?: T, start?: StartStopNotifier): Readable { - return { - subscribe: writable(value, start).subscribe - }; +export function readable(value, start) { + 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 + * @param {T} value initial value + * @param {StartStopNotifier} start undefined + * @returns {import("/Users/elliottjohnson/dev/sveltejs/svelte/index.ts-to-jsdoc").Writable} */ -export function writable(value?: T, start: StartStopNotifier = noop): Writable { - let stop: Unsubscriber; - const subscribers: Set> = new Set(); - - function set(new_value: T): void { - if (safe_not_equal(value, new_value)) { - value = new_value; - if (stop) { - // store is ready - const run_queue = !subscriber_queue.length; - for (const subscriber of subscribers) { - subscriber[1](); - subscriber_queue.push(subscriber, value); - } - if (run_queue) { - for (let i = 0; i < subscriber_queue.length; i += 2) { - subscriber_queue[i][0](subscriber_queue[i + 1]); - } - subscriber_queue.length = 0; - } - } - } - } - - function update(fn: Updater): void { - set(fn(value)); - } - - function subscribe(run: Subscriber, invalidate: Invalidator = noop): Unsubscriber { - const subscriber: SubscribeInvalidateTuple = [run, invalidate]; - subscribers.add(subscriber); - if (subscribers.size === 1) { - stop = start(set, update) || noop; - } - run(value); - - return () => { - subscribers.delete(subscriber); - if (subscribers.size === 0 && stop) { - stop(); - stop = null; - } - }; - } - - return { set, update, subscribe }; +export function writable(value, start = noop) { + /** @type {Unsubscriber} */ + let stop; + /** @type {Set>} */ + const subscribers = new Set(); + /** @param {T} new_value + * @returns {void} + */ + function set(new_value) { + if (safe_not_equal(value, new_value)) { + value = new_value; + if (stop) { + // store is ready + const run_queue = !subscriber_queue.length; + for (const subscriber of subscribers) { + subscriber[1](); + subscriber_queue.push(subscriber, value); + } + if (run_queue) { + for (let i = 0; i < subscriber_queue.length; i += 2) { + subscriber_queue[i][0](subscriber_queue[i + 1]); + } + subscriber_queue.length = 0; + } + } + } + } + /** @param {Updater} fn + * @returns {void} + */ + function update(fn) { + set(fn(value)); + } + /** @param {Subscriber} run + * @param {Invalidator} invalidate + * @returns {import("/Users/elliottjohnson/dev/sveltejs/svelte/index.ts-to-jsdoc").Unsubscriber} + */ + function subscribe(run, invalidate = noop) { + /** @type {SubscribeInvalidateTuple} */ + const subscriber = [run, invalidate]; + subscribers.add(subscriber); + if (subscribers.size === 1) { + stop = start(set, update) || noop; + } + run(value); + return () => { + subscribers.delete(subscriber); + if (subscribers.size === 0 && stop) { + stop(); + stop = null; + } + }; + } + return { set, update, subscribe }; } - -/** One or more `Readable`s. */ -type Stores = Readable | [Readable, ...Array>] | Array>; - -/** One or more values from `Readable` stores. */ -type StoresValues = T extends Readable - ? U - : { [K in keyof T]: T[K] extends Readable ? U : never }; - -/** - * Derived value store by synchronizing one or more readable stores and - * applying an aggregation function over its input values. - * - * @param stores - input stores - * @param fn - function callback that aggregates the values - * @param initial_value - when used asynchronously +/** @param {Stores} stores + * @param {Function} fn + * @param {T} initial_value + * @returns {import("/Users/elliottjohnson/dev/sveltejs/svelte/index.ts-to-jsdoc").Readable} */ -export function derived( - stores: S, - fn: ( - values: StoresValues, - set: Subscriber, - update: (fn: Updater) => void - ) => Unsubscriber | void, - initial_value?: T -): Readable; - -/** - * Derived value store by synchronizing one or more readable stores and - * applying an aggregation function over its input values. - * - * @param stores - input stores - * @param fn - function callback that aggregates the values - * @param initial_value - initial value - */ -export function derived( - stores: S, - fn: (values: StoresValues) => T, - initial_value?: T -): Readable; - -/** - * Derived value store by synchronizing one or more readable stores and - * applying an aggregation function over its input values. - * - * @param stores - input stores - * @param fn - function callback that aggregates the values - */ -export function derived( - stores: S, - fn: (values: StoresValues) => T -): Readable; - -export function derived(stores: Stores, fn: Function, initial_value?: T): Readable { - const single = !Array.isArray(stores); - const stores_array: Array> = single - ? [stores as Readable] - : (stores as Array>); - if (!stores_array.every(Boolean)) { - throw new Error('derived() expects stores as input, got a falsy value'); - } - - const auto = fn.length < 2; - - return readable(initial_value, (set, update) => { - let started = false; - const values = []; - - let pending = 0; - let cleanup = noop; - - const sync = () => { - if (pending) { - return; - } - cleanup(); - const result = fn(single ? values[0] : values, set, update); - if (auto) { - set(result as T); - } else { - cleanup = is_function(result) ? (result as Unsubscriber) : noop; - } - }; - - const unsubscribers = stores_array.map((store, i) => - subscribe( - store, - (value) => { - values[i] = value; - pending &= ~(1 << i); - if (started) { - sync(); - } - }, - () => { - pending |= 1 << i; - } - ) - ); - - started = true; - sync(); - - return function stop() { - run_all(unsubscribers); - cleanup(); - // We need to set this to false because callbacks can still happen despite having unsubscribed: - // Callbacks might already be placed in the queue which doesn't know it should no longer - // invoke this derived store. - started = false; - }; - }); +export function derived(stores, fn, initial_value) { + const single = !Array.isArray(stores); + /** @type {Array>} */ + const stores_array = single + ? [stores] + : stores; + if (!stores_array.every(Boolean)) { + throw new Error('derived() expects stores as input, got a falsy value'); + } + const auto = fn.length < 2; + return readable(initial_value, (set, update) => { + let started = false; + const values = []; + let pending = 0; + let cleanup = noop; + const sync = () => { + if (pending) { + return; + } + cleanup(); + const result = fn(single ? values[0] : values, set, update); + if (auto) { + set(result); + } + else { + cleanup = is_function(result) ? result : noop; + } + }; + const unsubscribers = stores_array.map((store, i) => subscribe(store, (value) => { + values[i] = value; + pending &= ~(1 << i); + if (started) { + sync(); + } + }, () => { + pending |= 1 << i; + })); + started = true; + sync(); + return function stop() { + run_all(unsubscribers); + cleanup(); + // We need to set this to false because callbacks can still happen despite having unsubscribed: + // Callbacks might already be placed in the queue which doesn't know it should no longer + // invoke this derived store. + started = false; + }; + }); } - /** * Takes a store and returns a new one derived from the old one that is readable. * - * @param store - store to make readonly + * @param {Readable} store - store to make readonly + * @returns {import("/Users/elliottjohnson/dev/sveltejs/svelte/index.ts-to-jsdoc").Readable} */ -export function readonly(store: Readable): Readable { - return { - subscribe: store.subscribe.bind(store) - }; +export function readonly(store) { + return { + subscribe: store.subscribe.bind(store) + }; } - /** * Get the current value from a store by subscribing and immediately unsubscribing. * @param store readable */ export { get_store_value as get }; + + +/** + * @typedef {(value: T) => void} Subscriber + * @template T + */ +/** @typedef {() => void} Unsubscriber */ +/** + * @typedef {(value: T) => T} Updater + * @template T + */ +/** + * @typedef {(value?: T) => void} Invalidator + * @template T + */ +/** + * @typedef {( + * set: (value: T) => void, + * update: (fn: Updater) => void + * ) => void | (() => void)} StartStopNotifier + * @template T + */ +/** + * @typedef {[Subscriber, Invalidator]} SubscribeInvalidateTuple + * @template T + */ +/** @typedef {Readable | [Readable, ...Array>] | Array>} Stores */ +/** + * @typedef {T extends Readable + * ? U + * : { [K in keyof T]: T[K] extends Readable ? U : never }} StoresValues + * @template T + */ + +/** + * Readable interface for subscribing. + * @typedef {Object} Readable + */ +/** + * Writable interface for both updating and subscribing. + * @typedef {Object} Writable + */ \ No newline at end of file