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 🙏

© 2025 – Pkg Stats / Ryan Hefner

ttlstash

v1.1.0

Published

Tiny TTL cache for localStorage plus a one-liner fetch cache with stale-while-revalidate.

Readme

Tiny, framework-agnostic TTL cache for localStorage with a one-liner fetch cache helper. Ships zero dependencies, falls back to memory storage during SSR/private mode, and automatically keeps tabs in sync.

  • SWR by default. Returns stale values instantly, refreshes in the background, and notifies every tab.
  • SSR-safe. Seamlessly downgrades to an in-memory store when window.localStorage is unavailable or locked down.
  • TypeScript-first. Full type coverage and ESM/CJS builds generated from a single TypeScript source.
  • 1 KB min+gz. Nothing but platform APIs.

Installation

npm install ttlstash
# or
pnpm add ttlstash
yarn add ttlstash
# or
bun add ttlstash

Usage

Cache any async value

import ttlstash from "ttlstash";

const settings = await ttlstash("myapp/v1/settings", {
  ttl: 60 * 60 * 1000, // 1 hour
  fetcher: async () => {
    const res = await fetch("/api/settings");
    if (!res.ok) throw new Error("Unable to load settings");
    return res.json();
  },
});

console.log(settings.theme); // Cached instantly on subsequent calls

Cache network calls with ttlstash.fetch

import ttlstash, { ttlstashFetch } from "ttlstash";

const users = await ttlstashFetch("/api/users", {
  ttl: 5 * 60 * 1000,
  init: { headers: { Authorization: `Bearer ${token}` } },
  parser: (res) => res.json(), // optional; defaults to JSON-by-content-type or text
});

// Equivalent helper on the default export
const profile = await ttlstash.fetch("/api/profile", { ttl: 15 * 60 * 1000 });

Manual invalidation & cross-tab subscriptions

import { invalidate, subscribe } from "ttlstash";

// React to writes from other tabs or background refreshes.
const unsubscribe = subscribe(({ type, key }) => {
  console.log(`[${type}]`, key);
});

invalidate("myapp/v1/settings"); // removes value locally and broadcasts

unsubscribe();

SSR / Node usage

ttlstash detects when window.localStorage is unavailable (SSR, tests, Safari private mode, quota errors) and transparently switches to an isolated in-memory store. No guards are required:

import ttlstash from "ttlstash";

export async function loader() {
  return ttlstash("server/render/data", {
    ttl: 5 * 1000,
    fetcher: () => expensiveComputation(),
  });
}

API Reference

| Function | Signature | Notes | | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ttlstash | ttlstash<T>(key: string, options: { ttl: number; fetcher: () => T \| Promise<T>; revalidate?: boolean }) => Promise<T> | Returns fresh value when available. If stale and revalidate !== false, returns the stale value immediately, refreshes in the background, and broadcasts updates. Throws if ttl <= 0. | | ttlstashFetch / ttlstash.fetch | (url: string, { ttl, init, revalidate, parser }: FetchOptions) => Promise<unknown> | Creates a stable cache key from url plus selected fetch init options. Automatically parses JSON responses based on the content-type header and falls back to text. Pass a custom parser(res) for blobs/streams. | | invalidate | (key: string) => void | Removes the key from storage, emits a local event, and sends a BroadcastChannel/storage event so other tabs refresh. | | subscribe | (fn: (msg: { type: "set" \| "invalidate"; key: string }) => void) => () => void | Observe background refreshes or manual invalidations. Returns an unsubscribe function. | | getMeta | (key: string) => { createdAt: number; expires: number; fresh: boolean } \| null | Read raw metadata without touching value freshness. Useful for debugging dashboards or heuristics. |

All helpers are also available via the default export:

import ttlstash from "ttlstash";

await ttlstash.fetch("/api/users", { ttl: 60_000 });
ttlstash.invalidate("custom/key");

Best practices

  • Namespace keys. Prefer versioned prefixes like myapp/v2/... so you can invalidate whole families by bumping the prefix.
  • Right-size TTLs. Pick shorter TTLs for highly dynamic endpoints and longer ones for static payloads. Remember that revalidate returns stale data instantly, so you can often choose conservative TTLs without hurting UX.
  • Handle fetch errors. If the background refresh fails, the stale value remains and the error is swallowed. Foreground calls propagate errors, so wrap ttlstash in your own retry/toast logic where needed.
  • Quota awareness. Large payloads may hit localStorage limits. When a write fails, the library automatically falls back to an in-memory store for the current tab, so future reads/writes still work (they just stop persisting across reloads).

Tooling & scripts

  • npm run lintprettier --check .
  • npm run formatprettier --write .
  • npm run changeset – capture a changelog entry (pick semver bump) for the next release.
  • npm run version-packages – apply pending changesets, bump versions, and update CHANGELOG.md.
  • npm run release – build and publish via Changesets (changeset publish).
  • npm run build – Bundles dist/ via tsup.
  • npm test / npm run test:watch – Runs the Vitest suite in jsdom.
  • npm run prepublishOnly – Ensures lint, build, and tests pass before publishing.

CI & automation

  • .github/workflows/main.yml – Runs lint/test/build on pushes and PRs against main using Node 18/20.
  • .github/workflows/publish.yml – Uses Changesets to either open a release PR or publish to npm once changes land on main (requires NPM_TOKEN secret).

Release checklist

  1. npm run format && npm run lint
  2. npm test
  3. npm run changeset (document each releasable change)
  4. npm run version-packages (updates versions + changelog)
  5. npm run release (builds and publishes via changeset publish)

Happy caching!