182 lines
5.7 KiB
TypeScript
182 lines
5.7 KiB
TypeScript
![]() |
export type ObservableMap<K, V> = Map<K, V> & {
|
||
|
onUpdate: (key: string, callback: () => void) => () => void;
|
||
|
};
|
||
|
|
||
|
const UPDATE_EVENT_NAME = "update";
|
||
|
|
||
|
//FIXME: allow different type for different properties
|
||
|
export function memoryMap<T>(): ObservableMap<string, T> {
|
||
|
const obs = new EventTarget();
|
||
|
const theMap = new Map<string, T>();
|
||
|
const theMemoryMap: ObservableMap<string, T> = {
|
||
|
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<string, string> {
|
||
|
const obs = new EventTarget();
|
||
|
const theLocalStorageMap: ObservableMap<string, string> = {
|
||
|
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;
|
||
|
}
|