UNPKG

svelte

Version:

Cybernetically enhanced web apps

210 lines (199 loc) 5.23 kB
/** @import { Readable, StartStopNotifier, Subscriber, Unsubscriber, Updater, Writable } from '../public.js' */ /** @import { Stores, StoresValues, SubscribeInvalidateTuple } from '../private.js' */ import { noop, run_all } from '../../internal/shared/utils.js'; import { safe_not_equal } from '../../internal/client/reactivity/equality.js'; import { subscribe_to_store } from '../utils.js'; /** * @type {Array<SubscribeInvalidateTuple<any> | any>} */ const subscriber_queue = []; /** * Creates a `Readable` store that allows reading by subscription. * * @template T * @param {T} [value] initial value * @param {StartStopNotifier<T>} [start] * @returns {Readable<T>} */ export function readable(value, start) { return { subscribe: writable(value, start).subscribe }; } /** * Create a `Writable` store that allows both updating and reading by subscription. * * @template T * @param {T} [value] initial value * @param {StartStopNotifier<T>} [start] * @returns {Writable<T>} */ export function writable(value, start = noop) { /** @type {Unsubscriber | null} */ let stop = null; /** @type {Set<SubscribeInvalidateTuple<T>>} */ 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<T>} fn * @returns {void} */ function update(fn) { set(fn(/** @type {T} */ (value))); } /** * @param {Subscriber<T>} run * @param {() => void} [invalidate] * @returns {Unsubscriber} */ function subscribe(run, invalidate = noop) { /** @type {SubscribeInvalidateTuple<T>} */ const subscriber = [run, invalidate]; subscribers.add(subscriber); if (subscribers.size === 1) { stop = start(set, update) || noop; } run(/** @type {T} */ (value)); return () => { subscribers.delete(subscriber); if (subscribers.size === 0 && stop) { stop(); stop = null; } }; } return { set, update, subscribe }; } /** * Derived value store by synchronizing one or more readable stores and * applying an aggregation function over its input values. * * @template {Stores} S * @template T * @overload * @param {S} stores * @param {(values: StoresValues<S>, set: (value: T) => void, update: (fn: Updater<T>) => void) => Unsubscriber | void} fn * @param {T} [initial_value] * @returns {Readable<T>} */ /** * Derived value store by synchronizing one or more readable stores and * applying an aggregation function over its input values. * * @template {Stores} S * @template T * @overload * @param {S} stores * @param {(values: StoresValues<S>) => T} fn * @param {T} [initial_value] * @returns {Readable<T>} */ /** * @template {Stores} S * @template T * @param {S} stores * @param {Function} fn * @param {T} [initial_value] * @returns {Readable<T>} */ export function derived(stores, fn, initial_value) { const single = !Array.isArray(stores); /** @type {Array<Readable<any>>} */ 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; /** @type {T[]} */ 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 = typeof result === 'function' ? result : noop; } }; const unsubscribers = stores_array.map((store, i) => subscribe_to_store( 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. * * @template T * @param {Readable<T>} store - store to make readonly * @returns {Readable<T>} */ export function readonly(store) { return { // @ts-expect-error TODO i suspect the bind is unnecessary subscribe: store.subscribe.bind(store) }; } /** * Get the current value from a store by subscribing and immediately unsubscribing. * * @template T * @param {Readable<T>} store * @returns {T} */ export function get(store) { let value; subscribe_to_store(store, (_) => (value = _))(); // @ts-expect-error return value; }