Skip to content

Reference

Architecture

vue-storage-kit

├── StorageAdapterFactory (singleton per target)
│     LocalStorageAdapter     → window.localStorage
│     SessionStorageAdapter   → window.sessionStorage
│     MemoryStorageAdapter    → Map<string, string>  (SSR / 'memory' target)
│     IndexedDBStorageAdapter → a dedicated IndexedDB object store ('indexeddb' target)

│     StorageAdapter is async (`Promise`-returning getItem/setItem/removeItem/keys)
│     so all four backends — including IndexedDB — share one pipeline.

├── src/engine  (framework-agnostic — no vue or react import anywhere in here)
│     │
│     ├── StorageEngine
│     │     Owns the full read/write pipeline: TTL, schema migrations,
│     │     encrypt/compress/sign, cross-tab sync, debounce/throttle,
│     │     undo/redo history, quota-exceeded recovery (TTL sweep, then
│     │     optional LRU eviction). Exposes getSnapshot()/subscribe() — an
│     │     "external store" shape usable from any framework — plus
│     │     onEvent() for devtools-timeline-style consumers.
│     │
│     └── engineCache
│           acquireEngine()/releaseEngine() — refcounted cache shared by
│           Vue *and* React: two components in either (or both) frameworks
│           asking for the same key+target get the same StorageEngine.

├── composables/useStorage  (Vue)
│     Thin wrapper: ref/computed mirroring engine.getSnapshot(), a
│     watch(value, flush:'sync') that calls engine.setValue(), and its own
│     wrapperCache on top of engineCache so multiple Vue components share
│     one literal `Ref` (not just the same engine).

├── react/useStorage  (React, `/react` entry point)
│     Thin wrapper: useSyncExternalStore(engine.subscribe, engine.getSnapshot)
│     plus a setValue() callback. Acquires/releases via the same engineCache.

├── SchemaManager
│     Builds and runs migration chains (up or down)

├── TTLManager
│     Checks exp on every read (lazy expiry)
│     cleanExpired() — bulk sweep with optional prefix

├── createJSONSerializer
│     Handles Date, Map, Set, undefined via preProcess()
│     (preProcess walks the tree before JSON.stringify to avoid
│      Date.prototype.toJSON() hijacking the replacer)

├── useIndexedDB / useIDBRef  (Vue)
│     IndexedDBAdapter — lazily opens IDB, creates store on upgrade
│     useIDBRef watches the ref and calls adapter.set() on change

├── useCookie  (Vue)
│     Parses document.cookie on mount
│     watch → builds Set-Cookie string and assigns to document.cookie

├── /crypto  (separate entry point)
│     StorageEncryption — encrypt() / decrypt() / reencrypt() / rotateEncryptedKey()
│     PBKDF2 key derivation; derived keys cached by (password, iterations, salt)
│     StorageSigning — sign() / verify(), HMAC-SHA256

├── /sync  (separate entry point)
│     LeaderElection — navigator.locks; holds lock for tab lifetime
│     TabSync — BroadcastChannel + storage event fallback
│               last-write-wins by timestamp; leader wins on tie

├── VueStoragePlugin  (Vue)
│     prefix/defaultTarget/defaultSerializer/defaultEncrypt/onError, read by
│     every Vue useStorage() call made after install via getGlobalOptions()

├── /devtools  (separate entry point, opt-in via setupDevtools(app))
│     Inspector + timeline over engineCache — sees Vue and React instances alike

├── /testing  (separate entry point)
│     mockStorage()/resetStorageState()/seedEnvelope()/flushAsync()

└── Nuxt module (vue-storage-kit/nuxt)
      addImports — auto-import all composables (useCookie → SSR-aware runtime version)
      addPlugin  — installs VueStoragePlugin with runtimeConfig.storageKit.prefix

Bundle size & peer dependencies

Entry pointNeeds vue?Peer/runtime depsNotes
vue-storage-kitYesvue ^3.3Vue composables, plugin, adapters, serializer
vue-storage-kit/reactNoreact ^18The React useStorage() hook
vue-storage-kit/cryptoNoAES-GCM encryption, HMAC signing, key rotation
vue-storage-kit/syncNoTabSync and LeaderElection only
vue-storage-kit/compressNoCompression Streams helpers + CompressAdapter only
vue-storage-kit/piniaNopinia ^2 | ^3 (optional peer)createPiniaPersist only
vue-storage-kit/devtoolsNo@vue/devtools-api (bundled runtime dep)Inspector + timeline, opt-in
vue-storage-kit/testingYes¹Test helpers
vue-storage-kit/nuxt@nuxt/kit (optional peer), h3 (optional peer)Nuxt module

¹ /testing pulls in the Vue composable module for its cache-reset helper even if you're only testing React code — the cost is dev/test-only, never shipped to production, so this isn't optimized away.

The package ships as tree-shakeable ESM (dist/index.js) and CommonJS (dist/index.cjs). The /crypto, /sync, and /compress entry points are also code-split inside useStorage — loaded dynamically only when encrypt, sync, or compress options are actually set, keeping the core footprint small regardless of which entry point pulled them in. @vue/devtools-api is the package's only required runtime dependency, and it's never bundled into ., /react, or /nuxt — it only loads if you explicitly import vue-storage-kit/devtools and call setupDevtools(app) yourself. Neither vue nor react is a hard dependency of the package as a whole — only of the specific entry point you import.

Comparison with @vueuse/core

vue-storage-kit extends and diverges from @vueuse/core in specific areas.

Drop-in replacements

@vueuse/corevue-storage-kitNotes
useLocalStorage(key, default)useLocalStorage(key, default)Same signature; flush: 'sync' by default
useSessionStorage(key, default)useSessionStorage(key, default)Same signature
useCookies()useCookie(name, options)Per-cookie reactive Ref instead of a single object
useStorageAsync()useIDBRef()Reactive Ref backed by async storage (IndexedDB)
useBroadcastChannel()useBroadcastChannel()Identical API

Extended functionality (no vueuse equivalent)

Featurevue-storage-kit
Schema migrationsmigrations: [{ version, up, down? }] option in StorageOptions
TTL / expiryttl option (seconds); lazy check on read; no background timers
AES-GCM encryptionencrypt: { password } option; Web Crypto API only, no extra deps
Cross-tab syncsync: true option; BroadcastChannel + storage event fallback
Leader electionnavigator.locks-based leader in LeaderElection
IndexedDB full APIuseIndexedDB() — get / set / delete / keys / getAll / transaction / indexes
Secondary IDB indexesuseIndexedDB('db', 'store', onError, { indexes: [...] })
CRUD collectionuseStorageList<T>() — add / update / remove / find / findAll
Pinia persistence/pinia entry point — createPiniaPersist({ pick?, omit? })
Compressioncompress: true option on useStorage, or the standalone /compress entry point — compress() / decompress() via Compression Streams API
Export / ImportexportStorage() / importStorage() — snapshot and restore all keys
Shared instance cacheTwo components (Vue or React) calling useStorage('key') share one underlying engine — zero duplicated watchers/timers
Devtools inspector + timeline/devtools entry point — setupDevtools(app), sees Vue and React instances alike
HMAC signingsign: { password } option — accidental-corruption detection without requiring secrecy
Undo / redohistory: n option — in-memory undo()/redo(), no extra state management needed
Throttlethrottle option, alongside debounce
Quota-exceeded recoveryAutomatic TTL sweep + retry; optional evictOnQuota for LRU-style eviction of other keys
React support/react entry point — same options, same engine, useSyncExternalStore-backed
Testing utilities/testing entry point — mockStorage(), resetStorageState(), seedEnvelope()

Behavioural differences

Behaviour@vueuse/corevue-storage-kit
Watcher flush'pre' (default Vue)'sync' — write happens in the same microtask as the assignment
Cross-tab updatestorage event onlyBroadcastChannel with storage event fallback
SerialisationJSON onlyJSON + Date, Map, Set, BigInt round-trip; custom Serializer<T>
Multiple instancesIndependent watchers per callShared StorageEngine via a refcounted cache, across Vue and React
SSRGlobal stubsSame stubs; useCookie accepts H3 event for Nuxt server routes

License

MIT