Last active
April 21, 2023 14:54
-
-
Save mirismaili/e14f89be20482280ba55cef1eae6348f to your computer and use it in GitHub Desktop.
A gateway for the standard `localStorage` that provides easier and more flexible interface to it
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// CAVEAT: Importing this file has side effects. See the below `setInterval()`. | |
const lStorageCache = {} | |
const sStorageCache = {} | |
/** | |
* @param {Storage} storage | |
* @returns {ProxyHandler<{}>} | |
*/ | |
function proxyHandlers(storage) { | |
return { | |
get(cache, key, receiver) { | |
const cacheEntry = Reflect.get(cache, key, receiver) | |
if (cacheEntry) { | |
cacheEntry.lastAccessTime = new Date().getTime() // Update `lastAccessTime` | |
const { data } = cacheEntry | |
return data | |
} | |
// noinspection JSCheckFunctionSignatures | |
const stringifiedData = storage.getItem(key) | |
const data = stringifiedData === null ? undefined : JSON.parse(stringifiedData) | |
cache[key] = { data, lastAccessTime: new Date().getTime() } | |
return data | |
}, | |
set(cache, key, data, receiver) { | |
const setResult = Reflect.set(cache, key, { data, lastAccessTime: new Date().getTime() }, receiver) // noinspection | |
// JSCheckFunctionSignatures | |
storage.setItem(key, JSON.stringify(data)) | |
return setResult | |
}, | |
deleteProperty(cache, key) { | |
const deleteResult = Reflect.deleteProperty(cache, key) // noinspection JSCheckFunctionSignatures | |
storage.removeItem(key) | |
return deleteResult | |
}, | |
} | |
} | |
/** | |
* A gateway for the standard **`localStorage`** (see the below examples) that provides easier and more flexible | |
* interface to it by two ways: | |
* 1. Easy read/write/delete operation using regular ES6 object notations, even for special keys (`length`, `clear`, | |
* etc.). See the below examples and see: | |
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Storage#instance_properties Storage#instance_properties} and | |
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Storage#instance_methods Storage#instance_methods}. | |
* 2. Automatically serializes/deserializes (`JSON.stringify/parse`) data during storing/restoring, make it possible to | |
* easily store/restore tree-structured objects and arrays into `localStorage` (rather than just simple `string`s). | |
* | |
* It also provides an extra **cache-layer** on top of it, that reduces the need for serialization/deserialization on | |
* each call. But because of this and because this doesn't listen `localStorage` events **it's unsafe to use it | |
* alongside other ways that may modify the stored values of the same keys in `localStorage` (e.g. direct | |
* `localStorage.setItem(theSameKey, ...)`**! | |
* @example | |
* const obj = {R: 17, G: 213, B: 255} | |
* const array = [180, 'cm'] | |
* persistData.color = obj // localStorage.color = JSON.stringify(obj) | |
* persistData.length = array // localStorage.setItem('length', JSON.stringify(array)) | |
* // CAVEAT: `localStorage.length` is the number of data items stored in `localStorage` | |
* // ... | |
* const {color: {R, G, B}} = persistData // const {R, G, B} = JSON.parse(localStorage.color) | |
* const len = persistData[length] // const len = JSON.parse(localStorage.getItem('length')) | |
* // You can't write it as `len = JSON.parse(localStorage.length)`! | |
* // Because `localStorage.length` is the number of data items stored in `localStorage`. | |
* // See: https://developer.mozilla.org/en-US/docs/Web/API/Storage/length | |
* // ... | |
* delete persistData.color // delete localStorage.color | |
* // But do it to through `persistData` to make sure its cache is kept valid. | |
* // See the above note about "cache-layer". | |
* delete persistData[length] // localStorage.removeKey('length') | |
* // CAVEAT: `localStorage.length` is the number of data items stored in `localStorage` | |
* @type {{}} | |
*/ | |
export const persistData = new Proxy(lStorageCache, proxyHandlers(localStorage)) | |
/** | |
* Like {@link persistData `persistData`} but for **`sessionStorage`**. | |
* @type {{}} | |
*/ | |
export const sessionData = new Proxy(sStorageCache, proxyHandlers(sessionStorage)) | |
const MIN_CACHE_TTL = 5 * 60 * 1000 // 5 minutes | |
setInterval(() => { | |
const cacheExpirationBoundary = new Date().getTime() + MIN_CACHE_TTL // 5 minutes ago | |
for (const [key, { lastAccessTime }] in Object.entries(lStorageCache)) // Delete expired (older than 5 min) entries: | |
if (lastAccessTime < cacheExpirationBoundary) delete lStorageCache[key] | |
for (const [key, { lastAccessTime }] in Object.entries(sStorageCache)) // Delete expired (older than 5 min) entries: | |
if (lastAccessTime < cacheExpirationBoundary) delete sStorageCache[key] | |
}, MIN_CACHE_TTL) | |
export default persistData |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment