__ __ __ __
____ /\ \__ ___ /\ \/ \ __ /\ \ ___
/ ,__\ \ \ ,_\ / __`\ \ \ < /'__`\ \_\ \ / __`\
/\__, `\ \ \ \/ /\ \_\ \ \ \ ^ \ /\ \_\.\_ /\ ,. \ /\ \_\ \
\/\____/ \ \ \_ \ \____/ \ \_\ \_\\ \__/.\_\\ \____\\ \____/
\/___/ \ \__\ \/___/ \/_/\/_/ \/__/\/_/ \/___ / \/___/
\/__/Stokado(/stəˈkɑːdoʊ/) is the Esperanto(an international auxiliary language) for storage, meaning that Stokado is also an auxiliary agent for storage.
stokado can proxy objects of any storage-like, providing getter/setter syntax sugars, serialization, subscription listening, expiration setting, one-time value retrieval.
The most feature-rich proxy wrapper for browser storage — serialization, reactivity, expiration, and one-time values in one library.
| Feature | stokado | store2 | local-storage-fallback | lz-string |
|---|---|---|---|---|
| Proxy syntax sugar | ✅ | ❌ | ❌ | ❌ |
| Type-safe serialization | ✅ | ❌ | ❌ | ❌ |
| Reactive subscribe | ✅ | ❌ | ❌ | ❌ |
| Expiration | ✅ | ✅ | ❌ | ❌ |
| Disposable values | ✅ | ❌ | ❌ | ❌ |
| Async storage support | ✅ | ❌ | ❌ | ❌ |
| Cross-tab sync | ✅ | ❌ | ❌ | ❌ |
| Quota alert | ✅ | ❌ | ❌ | ❌ |
| Zero dependencies | ✅ | ✅ | ✅ | ✅ |
- Type preservation — You need localStorage values to keep their JavaScript type (number, boolean, Date, RegExp, etc.) instead of everything becoming a string
- Reactive storage — You need to listen for storage changes within the same tab (native
storageevent only fires cross-tab) - Auto-expiration — You need stored items to automatically expire and clean up (token management, cache strategies)
- One-time values — You need disposable values for cross-component or cross-page communication
- Cross-tab sync — You need real-time synchronization of storage changes across browser tabs
- Async backends — You need to work with async storage like localForage or IndexedDB wrappers with the same API
- Quota alert — You need to monitor storage usage and get notified when approaching storage limits, with the option to block writes that would exceed the quota
npm install stokadoimport { createProxyStorage } from 'stokado'
const storage = createProxyStorage(localStorage)
storage.getItem('test')createProxyStorage takes two parameters: an object of storage-like and an optional options. The options object supports the following fields:
broadcast— Whether to syncstoragemodifications with other pages. Default istrueforlocalStorage,falseforsessionStorage.channel— The channel name for cross-tab sync. By default,localStorageuses'localStorage'as the channel name.quota— Storage size limit in bytes. When set, stokado tracks the size of all data written through the proxy and triggers a callback when the limit is exceeded.onQuotaExceeded— Callback function triggered when storage usage exceeds thequotalimit. Receives aQuotaInfoobject with{ current, limit, key, value }. Returnfalseto block the write, or any other value to allow it. Supports async callbacks.
Operate storage directly through object-oriented approach
Of course, localStorage and sessionStorage are supported natively
const storage = createProxyStorage(localStorage)
storage.test = 'hello stokado'
storage.test // 'hello stokado'
delete storage.testThe storage also have the same methods and properties: key(), getItem(), setItem(), removeItem(), clear() and length.
Keep the type of storage value unchanged
// number
storage.test = 0
storage.test === 0
// boolean
storage.test = false
storage.test === false
// undefined
storage.test = undefined
storage.test === undefined
// null
storage.test = null
storage.test === null
// object
storage.test = { hello: 'world' }
storage.test.hello === 'stokado'
// array
storage.test = ['hello']
storage.test.push('stokado')
storage.test.length // 2
// Date
storage.test = new Date('2000-01-01T00:00:00.000Z')
storage.test.getTime() === 946684800000
// RegExp
storage.test = /d(b+)d/g
storage.test.test('cdbbdbsbz')
// function
storage.test = function () {
return 'hello stokado!'
}
storage.test() === 'hello stokado!'Subscribe to value changes
storage.on(key, callback)
storage.once(key, callback)
storage.off([[key], callback])key: the name of the item to subscribe to. Supportobj.aforObjectandlist[0]forArray, and alsoArraylength.callback: the function to call when the item is changed. IncludesnewValueandoldValue.
Tips: For off, if a callback exists, it removes the trigger of the specified callback; otherwise, it removes all callbacks bound to the key; if the key is empty, it removes all listening callbacks.
Set expires for items
storage.setExpires(key, expires)
storage.getExpires(key)
storage.removeExpires(key)key: the name of the item to set expires.expires: acceptstring、numberandDate.
Get the value once, which can be used for communication through storage.
storage.setDisposable(key)key:the name of the item to set disposable.
Get expires and disposable configuration information for the specified item
storage.getOptions(key)Set expires and disposable using setItem
storage.setItem(key, value, { expires, disposable })Monitor storage usage and get notified when approaching limits
import { createProxyStorage, MB } from 'stokado'
const storage = createProxyStorage(localStorage, {
quota: 5 * MB,
onQuotaExceeded({ current, limit, key }) {
console.warn(`Storage quota exceeded: ${current}/${limit} bytes, key: "${key}"`)
return false
}
})
storage.getUsage()
await storage.readyquota: Storage size limit in bytes. UseKBandMBconstants for readability.onQuotaExceeded: Callback when the limit is exceeded. Receives{ current, limit, key, value }. Returnfalseto block the write.getUsage(): Returns{ current, limit }— the current usage and the configured limit.ready: APromisethat resolves when the size tracker has finished scanning existing keys. For sync storages, it resolves immediately.
Note: The size calculation is based on the UTF-8 byte size of the encoded envelope (key + value) actually stored in the underlying storage. This closely approximates but may not exactly match the browser's internal storage accounting.
Limitations:
- Quota tracking covers data written through the stokado proxy and pre-existing keys scanned at initialization.
- Direct writes to the underlying storage from the same tab (bypassing stokado) are not tracked and may cause actual usage to exceed the quota without triggering an alert.
- Cross-tab writes are tracked through the
storageevent when broadcast is enabled, but there may be a brief delay.
If you're using AI coding tools (Cursor, Copilot, Windsurf, etc.), copy the rules from ai-rules/.cursorrules to your project root as .cursorrules to help your AI assistant follow stokado best practices.
Stokado provides preset StorageLike adapters for common storage targets, so you can use createProxyStorage directly without implementing the interface yourself.
import { createProxyStorage } from 'stokado'
import { cookieStorage } from 'stokado/presets/cookie'
const storage = createProxyStorage(cookieStorage)import { createProxyStorage } from 'stokado'
import { wechatStorage } from 'stokado/presets/wechat'
const storage = createProxyStorage(wechatStorage)Async version:
import { wechatStorageAsync } from 'stokado/presets/wechat'
const storage = createProxyStorage(wechatStorageAsync)import { douyinStorage, douyinStorageAsync } from 'stokado/presets/douyin'import { alipayStorage, alipayStorageAsync } from 'stokado/presets/alipay'import { uniStorage, uniStorageAsync } from 'stokado/presets/uni-app'React Native requires injecting the AsyncStorage instance:
import AsyncStorage from '@react-native-async-storage/async-storage'
import { createProxyStorage } from 'stokado'
import { createReactNativeStorage } from 'stokado/presets/react-native'
const storage = createProxyStorage(createReactNativeStorage(AsyncStorage))import { createProxyStorage } from 'stokado'
import { memoryStorage } from 'stokado/presets/node'
const storage = createProxyStorage(memoryStorage)localForage provides the same API as localStorage, it can be used in conjunction with stokado.
import localForage from 'localforage'
import { createProxyStorage } from 'stokado'
const local = createProxyStorage(localForage, { channel: 'localForage' })However, localForage uses an async API, it needs to be called using Promise.
await (local.test = 'hello localForage')
// or
await local.setItem('test', 'hello localForage')You can create multiple instances of localForage that point to different stores using createInstance.
const store = localforage.createInstance({
name: 'nameHere'
})
const proxyStore = createProxyStorage(store, { channel: 'store' })
const otherStore = localforage.createInstance({
name: 'otherName'
})
const proxyOtherStore = createProxyStorage(otherStore, { channel: 'otherStore' })stokado is a localStorage wrapper, browser storage proxy, web storage library, and storage utility for JavaScript. It provides localStorage serialization, storage reactivity, storage subscription, storage expiration, storage quota alert, cross-tab storage sync, and async storage support. Alternatives to store2, lscache, local-storage-fallback. Works with localStorage, sessionStorage, localForage, and any storage-like object.