@faizaanceg/pandora
Version:
A tiny wrapper over LocalStorage to improve DX
100 lines (99 loc) • 2.87 kB
JavaScript
export class KeyValueStore {
static DEFAULTS = {
shouldPersist: false,
ttl: Number.MAX_SAFE_INTEGER,
};
adapter;
subscriptions = new Map();
constructor(adapter) {
this.adapter = adapter;
}
getInternal(key) {
try {
const internalItem = this.adapter.getItem(key);
if (internalItem === null) {
return;
}
const item = JSON.parse(internalItem);
const isExpired = Date.now() >= item.createdOn + item.ttl;
return isExpired ? this.remove(key) : item;
}
catch {
return;
}
}
signal(key) {
if (this.subscriptions.has(key)) {
this.subscriptions.get(key)?.forEach((signal) => signal?.());
}
}
get(key, defaultValue) {
const item = this.getInternal(key);
return item ? item.value : defaultValue ?? null;
}
set(key, value, opts = {}) {
this.adapter.setItem(key, JSON.stringify({
createdOn: Date.now(),
value,
...KeyValueStore.DEFAULTS,
...opts,
}));
this.signal(key);
}
subscribe(key, signal) {
let count = 0;
let signals = [signal];
if (this.subscriptions.has(key)) {
signals = this.subscriptions.get(key);
count = signals.push(signal);
}
this.subscriptions.set(key, signals);
return () => this.removeSubscription(key, count);
}
removeSubscription(key, count) {
if (this.subscriptions.has(key)) {
if (count === -1) {
this.subscriptions.delete(key);
}
else {
const signals = this.subscriptions.get(key);
delete signals[count];
}
}
}
forEach(cb) {
const size = this.adapter.length;
for (let i = 0; i < size; i++) {
const key = this.adapter.key(i);
if (key === null) {
continue;
}
const item = this.getInternal(key);
if (item) {
cb(key, item);
}
}
}
remove(key) {
this.adapter.removeItem(key);
this.signal(key);
this.removeSubscription(key, -1);
}
clear(removeAll = false) {
const perishedItems = [];
this.forEach((key, item) => {
if (!item.shouldPersist || removeAll) {
perishedItems.push([key, item.value]);
}
});
perishedItems.forEach(([key]) => this.remove(key));
return Object.fromEntries(perishedItems);
}
getSnapshot() {
const items = [];
this.forEach((key, item) => {
items.push([key, item.value]);
});
return Object.fromEntries(items);
}
}