import { Store } from './types'; type Subscription = { key: string; callback: (value: any) => void; }; /** * Store using memory * * @example * * import { memoryStore } from 'react-admin'; * * const App = () => ( * * ... * * ); */ export const memoryStore = ( initialStorage: Record = {} ): Store => { // Use a flat Map to store key-value pairs directly without treating dots as nested paths let storage = new Map(Object.entries(initialStorage ?? {})); const subscriptions: { [key: string]: Subscription } = {}; let initialized = false; let itemsToSetAfterInitialization: Record = {}; const publish = (key: string, value: any) => { Object.keys(subscriptions).forEach(id => { if (!subscriptions[id]) return; // may happen if a component unmounts after a first subscriber was notified if (subscriptions[id].key === key) { subscriptions[id].callback(value); } }); }; return { setup: () => { storage = new Map(Object.entries(initialStorage)); // Because children might call setItem before the store is initialized, // we store those calls parameters and apply them once the store is ready if (Object.keys(itemsToSetAfterInitialization).length > 0) { const items = Object.entries(itemsToSetAfterInitialization); for (const [key, value] of items) { storage.set(key, value); publish(key, value); } itemsToSetAfterInitialization = {}; } initialized = true; }, teardown: () => { storage.clear(); }, getItem(key: string, defaultValue?: T): T { return storage.has(key) ? (storage.get(key) as T) : (defaultValue as T); }, setItem(key: string, value: T): void { // Because children might call setItem before the store is initialized, // we store those calls parameters and apply them once the store is ready if (!initialized) { itemsToSetAfterInitialization[key] = value; return; } storage.set(key, value); publish(key, value); }, removeItem(key: string): void { storage.delete(key); publish(key, undefined); }, removeItems(keyPrefix: string): void { const keysToDelete: string[] = []; storage.forEach((_, key) => { if (key.startsWith(keyPrefix)) { keysToDelete.push(key); } }); keysToDelete.forEach(key => { storage.delete(key); publish(key, undefined); }); }, reset(): void { const keysToDelete: string[] = []; storage.forEach((_, key) => { keysToDelete.push(key); }); storage.clear(); keysToDelete.forEach(key => { publish(key, undefined); }); }, subscribe: (key: string, callback: (value: string) => void) => { const id = Math.random().toString(); subscriptions[id] = { key, callback, }; return () => { delete subscriptions[id]; }; }, listItems: (keyPrefix?: string) => { return Array.from(storage.entries()).reduce( (acc, [key, value]) => { if (keyPrefix != null && !key.startsWith(keyPrefix)) { return acc; } acc[key] = value; return acc; }, {} as Record ); }, }; };