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.prefixBundle size & peer dependencies
| Entry point | Needs vue? | Peer/runtime deps | Notes |
|---|---|---|---|
vue-storage-kit | Yes | vue ^3.3 | Vue composables, plugin, adapters, serializer |
vue-storage-kit/react | No | react ^18 | The React useStorage() hook |
vue-storage-kit/crypto | No | — | AES-GCM encryption, HMAC signing, key rotation |
vue-storage-kit/sync | No | — | TabSync and LeaderElection only |
vue-storage-kit/compress | No | — | Compression Streams helpers + CompressAdapter only |
vue-storage-kit/pinia | No | pinia ^2 | ^3 (optional peer) | createPiniaPersist only |
vue-storage-kit/devtools | No | @vue/devtools-api (bundled runtime dep) | Inspector + timeline, opt-in |
vue-storage-kit/testing | Yes¹ | — | 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/core | vue-storage-kit | Notes |
|---|---|---|
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)
| Feature | vue-storage-kit |
|---|---|
| Schema migrations | migrations: [{ version, up, down? }] option in StorageOptions |
| TTL / expiry | ttl option (seconds); lazy check on read; no background timers |
| AES-GCM encryption | encrypt: { password } option; Web Crypto API only, no extra deps |
| Cross-tab sync | sync: true option; BroadcastChannel + storage event fallback |
| Leader election | navigator.locks-based leader in LeaderElection |
| IndexedDB full API | useIndexedDB() — get / set / delete / keys / getAll / transaction / indexes |
| Secondary IDB indexes | useIndexedDB('db', 'store', onError, { indexes: [...] }) |
| CRUD collection | useStorageList<T>() — add / update / remove / find / findAll |
| Pinia persistence | /pinia entry point — createPiniaPersist({ pick?, omit? }) |
| Compression | compress: true option on useStorage, or the standalone /compress entry point — compress() / decompress() via Compression Streams API |
| Export / Import | exportStorage() / importStorage() — snapshot and restore all keys |
| Shared instance cache | Two 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 signing | sign: { password } option — accidental-corruption detection without requiring secrecy |
| Undo / redo | history: n option — in-memory undo()/redo(), no extra state management needed |
| Throttle | throttle option, alongside debounce |
| Quota-exceeded recovery | Automatic 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/core | vue-storage-kit |
|---|---|---|
| Watcher flush | 'pre' (default Vue) | 'sync' — write happens in the same microtask as the assignment |
| Cross-tab update | storage event only | BroadcastChannel with storage event fallback |
| Serialisation | JSON only | JSON + Date, Map, Set, BigInt round-trip; custom Serializer<T> |
| Multiple instances | Independent watchers per call | Shared StorageEngine via a refcounted cache, across Vue and React |
| SSR | Global stubs | Same stubs; useCookie accepts H3 event for Nuxt server routes |
License
MIT