import type { Readable, Subscriber, Unsubscriber, Writable } from "svelte/store"; import { get, readable, writable } from "svelte/store"; /** * Is it a Map? Is it a Store? No! It's a MapStore! * * The MapStore behaves just like a regular JS Map, but... it is also a regular Svelte store. * * As a bonus, you can also get a store on any given key of the map. * * For instance: * * const mapStore = new MapStore(); * mapStore.getStore('foo').subscribe((value) => { * console.log('Foo key has been written to the store. New value: ', value); * }); * mapStore.set('foo', 'bar'); * * * Even better, if the items stored in map contain stores, you can directly get the store to those values: * * const mapStore = new MapStore * }>(); * * mapStore.getNestedStore('foo', item => item.nestedStore).subscribe((value) => { * console.log('Foo key has been written to the store or the nested store has been updated. New value: ', value); * }); * mapStore.set('foo', { * nestedStore: writable('bar') * }); * // Whenever the nested store is updated OR the 'foo' key is overwritten, the store returned by mapStore.getNestedStore * // will be triggered. */ export class MapStore extends Map implements Readable> { private readonly store = writable(this); private readonly storesByKey = new Map>(); subscribe(run: Subscriber>, invalidate?: (value?: Map) => void): Unsubscriber { return this.store.subscribe(run, invalidate); } clear() { super.clear(); this.store.set(this); this.storesByKey.forEach((store) => { store.set(undefined); }); } delete(key: K): boolean { const result = super.delete(key); if (result) { this.store.set(this); this.storesByKey.get(key)?.set(undefined); } return result; } set(key: K, value: V): this { super.set(key, value); this.store.set(this); this.storesByKey.get(key)?.set(value); return this; } getStore(key: K): Readable { const store = writable(this.get(key), () => { return () => { // No more subscribers! this.storesByKey.delete(key); }; }); this.storesByKey.set(key, store); return store; } /** * Returns an "inner" store inside a value stored in the map. */ getNestedStore(key: K, accessor: (value: V) => Readable | undefined): Readable { const initVal = this.get(key); let initStore: Readable | undefined; let initStoreValue: T | undefined; if (initVal) { initStore = accessor(initVal); if (initStore !== undefined) { initStoreValue = get(initStore); } } return readable(initStoreValue, (set) => { const storeByKey = this.getStore(key); let unsubscribeDeepStore: Unsubscriber | undefined; const unsubscribe = storeByKey.subscribe((newMapValue) => { if (unsubscribeDeepStore) { unsubscribeDeepStore(); } if (newMapValue === undefined) { set(undefined); } else { const deepValueStore = accessor(newMapValue); if (deepValueStore !== undefined) { set(get(deepValueStore)); unsubscribeDeepStore = deepValueStore.subscribe((value) => { set(value); }); } } }); return () => { unsubscribe(); if (unsubscribeDeepStore) { unsubscribeDeepStore(); } }; }); } }