Skip to content

Instantly share code, notes, and snippets.

@mirismaili
Last active April 21, 2023 14:54
Show Gist options
  • Save mirismaili/e14f89be20482280ba55cef1eae6348f to your computer and use it in GitHub Desktop.
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
// 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