export type ObservableMap = Map & { onUpdate: (key: string, callback: () => void) => () => void; }; const UPDATE_EVENT_NAME = "update"; //FIXME: allow different type for different properties export function memoryMap(): ObservableMap { const obs = new EventTarget(); const theMap = new Map(); const theMemoryMap: ObservableMap = { onUpdate: (key, handler) => { //@ts-ignore theMemoryMap.size = theMap.length; obs.addEventListener(`update-${key}`, handler); obs.addEventListener(`clear`, handler); return () => { obs.removeEventListener(`update-${key}`, handler); obs.removeEventListener(`clear`, handler); }; }, delete: (key: string) => { const result = theMap.delete(key); obs.dispatchEvent(new Event(`update-${key}`)); return result; }, set: (key: string, value: T) => { theMap.set(key, value); obs.dispatchEvent(new Event(`update-${key}`)); return theMemoryMap; }, clear: () => { theMap.clear(); obs.dispatchEvent(new Event(`clear`)); }, entries: theMap.entries.bind(theMap), forEach: theMap.forEach.bind(theMap), get: theMap.get.bind(theMap), has: theMap.has.bind(theMap), keys: theMap.keys.bind(theMap), size: theMap.size, values: theMap.values.bind(theMap), [Symbol.iterator]: theMap[Symbol.iterator], [Symbol.toStringTag]: "theMemoryMap", }; return theMemoryMap; } export function localStorageMap(): ObservableMap { const obs = new EventTarget(); const theLocalStorageMap: ObservableMap = { onUpdate: (key, handler) => { //@ts-ignore theLocalStorageMap.size = localStorage.length; obs.addEventListener(`update-${key}`, handler); obs.addEventListener(`clear`, handler); function handleStorageEvent(ev: StorageEvent) { if (ev.key === null || ev.key === key) { handler(); } } window.addEventListener("storage", handleStorageEvent); return () => { window.removeEventListener("storage", handleStorageEvent); obs.removeEventListener(`update-${key}`, handler); obs.removeEventListener(`clear`, handler); }; }, delete: (key: string) => { const exists = localStorage.getItem(key) !== null; localStorage.removeItem(key); obs.dispatchEvent(new Event(`update-${key}`)); return exists; }, set: (key: string, v: string) => { localStorage.setItem(key, v); obs.dispatchEvent(new Event(`update-${key}`)); return theLocalStorageMap; }, clear: () => { localStorage.clear(); obs.dispatchEvent(new Event(`clear`)); }, entries: (): IterableIterator<[string, string]> => { let index = 0; const total = localStorage.length; return { next() { const key = localStorage.key(index); if (key === null) { //we are going from 0 until last, this should not happen throw Error("key cant be null"); } const item = localStorage.getItem(key); if (item === null) { //the key exist, this should not happen throw Error("value cant be null"); } if (index == total) return { done: true, value: [key, item] }; index = index + 1; return { done: false, value: [key, item] }; }, [Symbol.iterator]() { return this; }, }; }, forEach: (cb) => { for (let index = 0; index < localStorage.length; index++) { const key = localStorage.key(index); if (key === null) { //we are going from 0 until last, this should not happen throw Error("key cant be null"); } const item = localStorage.getItem(key); if (item === null) { //the key exist, this should not happen throw Error("value cant be null"); } cb(key, item, theLocalStorageMap); } }, get: (key: string) => { const item = localStorage.getItem(key); if (item === null) return undefined; return item; }, has: (key: string) => { return localStorage.getItem(key) === null; }, keys: () => { let index = 0; const total = localStorage.length; return { next() { const key = localStorage.key(index); if (key === null) { //we are going from 0 until last, this should not happen throw Error("key cant be null"); } if (index == total) return { done: true, value: key }; index = index + 1; return { done: false, value: key }; }, [Symbol.iterator]() { return this; }, }; }, size: localStorage.length, values: () => { let index = 0; const total = localStorage.length; return { next() { const key = localStorage.key(index); if (key === null) { //we are going from 0 until last, this should not happen throw Error("key cant be null"); } const item = localStorage.getItem(key); if (item === null) { //the key exist, this should not happen throw Error("value cant be null"); } if (index == total) return { done: true, value: item }; index = index + 1; return { done: false, value: item }; }, [Symbol.iterator]() { return this; }, }; }, [Symbol.iterator]: function (): IterableIterator<[string, string]> { return theLocalStorageMap.entries(); }, [Symbol.toStringTag]: "theLocalStorageMap", }; return theLocalStorageMap; }