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
);
},
};
};