/** * @template {Record} T * @param {string} keyPrefix * @param {T} defaultState * @returns {T} */ function State(keyPrefix, defaultState) { /** @param {string} key */ function getFromRegistry(key) { try { // TODO validate type const rawValue = localStorage.getItem(keyPrefix+key); if (rawValue == null) return null; return JSON.parse(rawValue); } catch { return null; } } return new Proxy(/**@type T*/(Object.fromEntries( Object.entries(defaultState) .map(([k, v]) => [k, getFromRegistry(k) ?? v]), )), { get(cache, key) { if (typeof key === 'symbol') return undefined; // cache hit if (key in cache) return cache[key]; // cache miss => get from localStorage return /**@type{any}*/(cache)[key] = getFromRegistry(key); }, set(cache, key, value) { if (typeof key === 'symbol') return false; /**@type{any}*/(cache)[key] = value; localStorage.setItem(keyPrefix+key, JSON.stringify(value)); return true; }, }); };