npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vue-storage-kit

v0.2.7

Published

Reactive storage for Vue 3 over localStorage, sessionStorage, IndexedDB and cookies — TTL, AES-GCM encryption, migrations and cross-tab sync.

Readme

Storage Kit

Storage Kit

Reactive localStorage, sessionStorage, IndexedDB and cookies for Vue 3 (and React) — TTL, AES-GCM encryption, HMAC signing, schema migrations with up/down functions, undo/redo, and cross-tab sync — built on a framework-agnostic core, with Vue and React as thin bindings over it.


Features

  • useStorage — unified reactive state over localStorage, sessionStorage, IndexedDB, or an in-memory store; drop-in replacement for vueuse useLocalStorage / useSessionStorage. Available for Vue (a Ref) and React (a useSyncExternalStore-backed hook)
  • Schema migrations — versioned data with up / down migration chains; runs automatically on version mismatch, writes back the migrated value
  • TTL — optional time-to-live per key; lazy expiry checked on every read, no timers; manual cleanExpired() sweep for startup cleanup
  • AES-GCM encryption — Web Crypto API (crypto.subtle), key derived from a password via PBKDF2 or supplied as a CryptoKey; reencrypt()/rotateEncryptedKey() to rotate a password without data loss
  • HMAC signing — lightweight accidental-corruption detection (sign: { password }) for data that doesn't need to be secret
  • Undo / redo — history: n keeps the last n values in memory; undo() / redo() navigate them
  • Debounce & throttle — debounce coalesces writes after a pause; throttle guarantees a write at most every n ms during continuous changes
  • Resilient writes — on QuotaExceededError, sweeps this adapter's own expired-TTL entries and retries once; opt into evictOnQuota to additionally evict the least-recently-written other keys
  • Cross-tab sync — BroadcastChannel with storage event fallback; last-write-wins conflict resolution by timestamp; optional leader election via navigator.locks
  • useIndexedDB — promise-based key-value API plus a reactive useIDBRef for a single key
  • useCookie — reactive cookies with expires, sameSite, secure; SSR-aware (H3-backed) when auto-imported inside the Nuxt module
  • Vue plugin — global prefix, default target/serializer/encrypt, and a global error handler, all applied to every useStorage() call
  • Nuxt module — auto-imports all composables; wires up the plugin with runtime config
  • Serializer — JSON with round-trip support for Date, Map, Set, and undefined; bring your own serializer via the Serializer<T> interface
  • SSR-safe — falls back to in-memory storage when window is unavailable; isReady ref lets components show a skeleton until hydration
  • Devtools — a custom Vue Devtools inspector and timeline over every live useStorage() instance; /devtools entry point, opt-in via setupDevtools(app)
  • Testing utilities — /testing entry point: mockStorage(), resetStorageState(), seedEnvelope()/seedExpiredEnvelope(), flushAsync()
  • Vue and React as optional peers — @vue/devtools-api is the sole required runtime dependency; /crypto, /sync, /compress, /pinia, /devtools, /react, /testing are separate tree-shakeable entry points

When you'd reach for this

localStorage only stores strings, silently fills up without warning, and knows nothing about other open tabs — vue-storage-kit takes those quirks off your hands and adds what plain localStorage never had: entry lifetimes, encryption, cross-tab sync, and format migrations.

  • The same app is open in two tabs — A shopper adds something to the cart in one tab and expects to see it in the other without reloading the page. Changes to storage propagate between tabs instantly, not only after a refresh.
  • A draft shouldn't stick around forever — A forgotten form draft or a temporary access token shouldn't sit in storage indefinitely — an entry's lifetime expires on its own, and stale data gets cleared out the next time it's read.
  • Personal data shouldn't sit in plain text — Anyone can open the browser's dev tools and read storage contents as plain text — sensitive values can be kept encrypted instead of relying on nobody looking.
  • An old data shape meets a new app version — After an update, a user's browser might still hold data shaped for a previous version of the app — it gets converted to the current shape automatically, instead of crashing or silently losing data.

Installation

| Environment | Minimum version | | ------------- | -------------------------------------------------------------------------- | | Node.js | 18+ | | Vue | 3.3.0+ (optional — only for the package root / Vue composables) | | React | 18.0.0+ (optional — only for /react) | | pinia | 2.0.0+ or 3.0.0+ (optional — only for /pinia) | | @nuxt/kit | 3.0.0+ (optional — only for the /nuxt module) | | h3 | 1.0.0+ (optional — only for the Nuxt module's SSR-aware useCookie) |

npm install vue-storage-kit

For Vue (optional peer — only needed if you import from the package root or any Vue-specific composable):

npm install vue@>=3.3

For React (vue-storage-kit/react), install React instead — you don't need vue at all:

npm install react@>=18

Quick start

<script setup lang="ts">
import { useLocalStorage } from 'vue-storage-kit'

const { value: theme } = useLocalStorage('theme', 'light')
</script>

<template>
  <button @click="theme = theme === 'light' ? 'dark' : 'light'">Current theme: {{ theme }}</button>
</template>

The value is persisted to localStorage and is reactive — changing theme.value writes to storage immediately.

More examples

Vue

A cache that expires itself

TTL stores the expiry right inside the value's envelope — an expired key is removed automatically on the next read, no manual timers.

import { useStorage } from 'vue-storage-kit'

const {
  value: otp,
  expiry,
  remove,
} = useStorage('otp', {
  defaultValue: '',
  ttl: 5 * 60 * 1000, // 5 minutes
  onExpire: () => router.push('/login'),
})

console.log(expiry.value) // Date | null — when this key expires

Synced across tabs

With sync: true, a change in one tab reaches every other open tab instantly — no server, no manual subscriptions.

import { useStorage } from 'vue-storage-kit'

const { value: cart } = useStorage('cart', {
  defaultValue: [] as CartItem[],
  sync: true,
})

// cart.value stays in sync across every open tab automatically

Stored data upgrades itself on release

A user on an old version opens the app — the migration chain brings their stored data up to the current schema and persists the result immediately, no manual version if/else.

import { useStorage } from 'vue-storage-kit'

interface SettingsV3 {
  theme: 'light' | 'dark'
  locale: string
}

const { value: settings } = useStorage<SettingsV3>('settings', {
  defaultValue: { theme: 'light', locale: 'en' },
  version: 3,
  migrations: [
    { version: 2, up: (d: any) => ({ ...d, theme: d.darkMode ? 'dark' : 'light' }) },
    { version: 3, up: (d: any) => ({ ...d, locale: d.lang ?? 'en' }) },
  ],
  onMigrate: (from, to) => console.log(`Migrated settings ${from} → ${to}`),
})

// A v1 user with { darkMode: true } in storage reads
// { darkMode: true, theme: 'dark', locale: 'en' } — the migration chain
// runs and persists automatically, on the very first read.

React

The same engine, as a React hook

The vue-storage-kit/react entry point exports useStorage() with the same options (TTL, migrations, sync, and the rest) as the Vue composable — just a different call shape, for React.

import { useStorage } from 'vue-storage-kit/react'

function Counter() {
  const {
    value: count,
    setValue: setCount,
    isReady,
  } = useStorage('count', {
    defaultValue: 0,
    target: 'local',
  })

  if (!isReady) return <p>Loading…</p>

  return <button onClick={() => setCount((c) => c + 1)}>Clicked {count} times</button>
}

Documentation & links


License

MIT


💖 Support the project

Open source takes time and effort. If this library saves you time or brings value, consider supporting further development.

Thank you for being part of this journey. ❤️