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

typedstorage-safe

v1.0.1

Published

TypeScript-first localStorage and sessionStorage with TTL expiry, SSR safety, cross-tab sync, and schema types. Zero dependencies.

Downloads

126

Readme

typedstorage-safe

npm version npm downloads bundle size license TypeScript

TypeScript-first localStorage and sessionStorage with TTL expiry, SSR safety, cross-tab sync, and full type inference. Zero dependencies. ~1.5KB gzipped.


What's New

1.0.1

  • Fix: null and falsy values (false, 0, '') now round-trip correctly. has() and getAll() no longer treat a stored null as a missing key.
  • Fix: keys() now excludes expired keys, matching its documented "non-expired" contract.
  • Fix: onChange() now fires when another tab calls clear() (a StorageEvent with key === null), and ignores events from a different storage backend (e.g. session vs local).
  • Fix: corrupt/empty entries are read more robustly (null check instead of a falsy check).
  • Docs: added a Security & Limitations section covering web-storage safety, clear() scope, the in-memory server fallback, storage quota, and schema migrations.

Why typedstorage-safe?

Plain localStorage has four major problems that every developer hits:

| Problem | Plain localStorage | typed-storage | |---|---|---| | No TypeScript types | JSON.parse() returns any | Full schema inference | | No expiry/TTL | Must hand-roll timestamp logic | Built-in '7d', '1h', '30s' | | Crashes in SSR / Next.js | ReferenceError: localStorage is not defined | Silently returns null | | Key collisions | Global namespace, easy to clash | Prefix namespacing |


Installation

npm install typedstorage-safe
yarn add typedstorage-safe
pnpm add typedstorage-safe

Quick Start

import { createStorage } from 'typedstorage-safe'

const store = createStorage({ prefix: 'myapp' })

store.set('theme', 'dark')
store.get('theme')    // 'dark'
store.remove('theme')

TypeScript Schema — Full Type Safety

Define your schema once and get full autocomplete and error detection everywhere.

import { createStorage } from 'typedstorage-safe'

type AppSchema = {
  user: { id: number; name: string; role: 'admin' | 'user' }
  theme: 'light' | 'dark' | 'system'
  token: string
  cart: Array<{ id: string; qty: number; price: number }>
  onboardingStep: 1 | 2 | 3 | 4
}

const store = createStorage<AppSchema>({ prefix: 'app' })

// ✅ TypeScript knows exactly what each key holds
store.set('theme', 'dark')
store.set('theme', 'purple')       // ❌ TypeScript ERROR
store.get('user')?.role            // typed as 'admin' | 'user'
store.get('cart')?.[0].price       // typed as number
store.set('unknown', 'x')          // ❌ TypeScript ERROR — not in schema

TTL — Auto-Expiring Data

Stop writing Date.now() + 7 * 24 * 60 * 60 * 1000 by hand. Use human-readable TTL.

// Shorthand strings
store.set('token',    'abc123',  { ttl: '1h' })   // expires in 1 hour
store.set('session',  'xyz',     { ttl: '30m' })  // expires in 30 minutes
store.set('otp',      '123456',  { ttl: '5m' })   // expires in 5 minutes
store.set('cart',     [...],     { ttl: '7d' })   // expires in 7 days
store.set('flag',     true,      { ttl: '30s' })  // expires in 30 seconds

// Raw milliseconds also work
store.set('token', 'abc123', { ttl: 3_600_000 })

// Reading an expired key returns null and auto-removes it
store.get('otp')   // null after 5 minutes

Supported units: s (seconds), m (minutes), h (hours), d (days).


SSR Safe — Works in Next.js App Router

// ✅ No crash in Server Components or getServerSideProps
const theme = store.get('theme')   // returns null on server, value on client

// Works in useEffect too
useEffect(() => {
  const user = store.get('user')
  if (user) setUser(user)
}, [])

When localStorage is unavailable (SSR, Edge, private browsing), typed-storage automatically falls back to an in-memory store so your code never throws.


Prefix Namespacing — No Key Collisions

const authStore = createStorage<AuthSchema>({ prefix: 'auth' })
const uiStore   = createStorage<UISchema>  ({ prefix: 'ui'   })

authStore.set('token', 'abc')   // stored as "auth:token"
uiStore.set('token', 'xyz')     // stored as "ui:token" — no conflict

authStore.clear()   // clears only "auth:*" keys, leaves "ui:*" untouched

Cross-Tab Sync

Get notified when a value changes in another browser tab.

const store = createStorage<AppSchema>({ prefix: 'app', sync: true })

// Listen for changes from other tabs
const unsubscribe = store.onChange('user', (newValue) => {
  console.log('User updated in another tab:', newValue)
  setUser(newValue)
})

// Clean up when component unmounts
onUnmount(unsubscribe)

Full API Reference

createStorage<Schema>(options?)

Creates a typed storage instance.

const store = createStorage<Schema>({
  prefix:  'myapp',   // string — key prefix. Default: ''
  storage: 'local',   // 'local' | 'session' | 'memory'. Default: 'local'
  sync:    false,     // enable cross-tab sync. Default: false
})

store.set(key, value, options?)

Write a value.

store.set('theme', 'dark')
store.set('token', 'abc', { ttl: '1h' })

store.get(key)

Read a value. Returns null if missing or expired.

const theme = store.get('theme')   // 'light' | 'dark' | 'system' | null

store.remove(key)

Delete a key.

store.remove('token')

store.has(key)

Check if a key exists and is not expired.

if (store.has('token')) {
  // token exists and is still valid
}

store.clear()

Remove all keys belonging to this store's prefix.

store.clear()

store.keys()

List all keys (without prefix, non-expired).

store.keys()  // ['theme', 'token', 'cart']

store.ttl(key)

Get remaining TTL in milliseconds, or null if no TTL set.

store.set('token', 'abc', { ttl: '1h' })
store.ttl('token')  // ~3_600_000 ms

store.getAll()

Get all non-expired values as an object.

store.getAll()  // { theme: 'dark', token: 'abc123' }

store.onChange(key, callback)

Listen for changes from other tabs (requires sync: true). Returns an unsubscribe function.

const unsub = store.onChange('user', (value) => console.log(value))
unsub()  // stop listening

Framework Examples

React

import { createStorage } from 'typedstorage-safe'
import { useState, useEffect } from 'react'

type Schema = { theme: 'light' | 'dark' }
const store = createStorage<Schema>({ prefix: 'app' })

function useTheme() {
  const [theme, setTheme] = useState<'light' | 'dark'>(() =>
    store.get('theme') ?? 'light'
  )

  function toggle() {
    const next = theme === 'light' ? 'dark' : 'light'
    store.set('theme', next)
    setTheme(next)
  }

  return { theme, toggle }
}

Next.js App Router

// lib/storage.ts — shared across the app
import { createStorage } from 'typedstorage-safe'

type AppSchema = {
  user: { id: string; name: string }
  theme: 'light' | 'dark'
}

export const appStore = createStorage<AppSchema>({
  prefix: 'myapp',
  sync: true,
})
// app/layout.tsx — server component, no crash
import { appStore } from '@/lib/storage'

export default function Layout({ children }) {
  const theme = appStore.get('theme')  // null on server — safe
  return <html data-theme={theme ?? 'light'}>{children}</html>
}

Vue 3

import { createStorage } from 'typedstorage-safe'
import { ref, watchEffect } from 'vue'

type Schema = { theme: 'light' | 'dark' }
const store = createStorage<Schema>({ prefix: 'app' })

export function useTheme() {
  const theme = ref(store.get('theme') ?? 'light')

  watchEffect(() => {
    store.set('theme', theme.value)
  })

  return { theme }
}

Vanilla TypeScript / Node.js

import { createStorage } from 'typedstorage-safe'

const store = createStorage({ storage: 'memory' })   // in-memory for Node.js
store.set('key', 'value', { ttl: '5m' })
store.get('key')  // 'value'

Migration from Plain localStorage

// Before
localStorage.setItem('theme', JSON.stringify('dark'))
const theme = JSON.parse(localStorage.getItem('theme') ?? 'null')

// After
const store = createStorage<{ theme: 'light' | 'dark' }>({ prefix: 'app' })
store.set('theme', 'dark')
const theme = store.get('theme')

Bundle Size

  • ~1.5 KB gzipped
  • Zero runtime dependencies
  • Tree-shakeable ESM build

Tested with: React 18, Next.js 13/14/15, Vue 3, Svelte, plain TypeScript, Node.js 18+.


Security & Limitations

typedstorage-safe is a convenience wrapper around the browser's Storage API. It is not a security boundary. Please read these caveats before storing anything sensitive.

⚠️ Do not store secrets in localStorage / sessionStorage

Everything written to localStorage or sessionStorage is stored in plaintext and is readable by any JavaScript running on your page — including third-party scripts and anything injected via XSS. This applies to token, session, and otp-style values shown in some examples above.

  • Auth tokens are best kept in memory or in a Secure, HttpOnly cookie set by your server — not in web storage.
  • TTL is a client-side convenience, not a security control. An expired value is only purged when it is next read (get, has, keys, getAll). Until then the raw bytes remain in storage, and nothing on the server enforces the expiry.

clear() without a prefix wipes the entire origin

For a store created without a prefix, clear() calls the underlying Storage.clear(), which removes all keys for the current origin — including data written by other libraries or other parts of your app. To scope clearing to your own data, always create stores with a prefix:

const store = createStorage<Schema>({ prefix: 'myapp' })
store.clear()   // only removes "myapp:*" keys

The in-memory fallback is process-global (server use)

When storage is unavailable (SSR, Node.js, Edge, private browsing), the library falls back to a single in-memory Map that is shared across the whole process. In a long-running server this means state is not isolated between requests or users — treat storage: 'memory' as a global cache, and never put per-user data in it on the server.

set() can throw when storage is full

If the browser's storage quota is exceeded (or writes are blocked), adapter.setItem() throws a QuotaExceededError. set() does not currently swallow this, so wrap writes you can't guarantee will fit:

try {
  store.set('cart', bigCart)
} catch (err) {
  // storage full — fall back gracefully
}

No schema migrations

Stored values are not versioned or re-validated on read. If you change a key's type in your Schema, previously stored data is returned as-is with the new type — TypeScript will trust it, but the runtime shape may not match. Bump your prefix (e.g. myapp.v2) or clear old keys when you change shapes.


License

MIT © Mehulbirare