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

unified-cache

v1.0.1

Published

Universal caching for Redux, API fetchers, and general data storage with memory and persistence.

Downloads

3

Readme

Unified Cache

A highly extensible caching utility for JavaScript/TypeScript supporting multiple storage mechanisms, eviction policies, TTL expiry, subscription (pub/sub), transforms, and optional Redux integration.


Features

  • In-memory caching with configurable eviction policies (LRU, LFU, FIFO)
  • Optional persistence using localStorage, sessionStorage, or IndexedDB
  • TTL-based expiry for cache entries
  • Pub/Sub mechanism to listen for cache changes
  • Transforms for data shaping (on set and get)
  • Redux state integration with slice syncing
  • Utilities to pause/resume, flush, and skip next access

Installation

npm install unified-cache

Usage

import { createCache } from "unified-cache";

const cache = createCache({
  mechanism: "memory",
  evictionPolicy: "LRU",
  maxEntries: 500,
  enableLogging: true,
});

// Set and get
await cache.set("foo", { bar: 123 }, 5000);
const value = await cache.get("foo");
console.log(value); // { bar: 123 }

// Subscribe
cache.subscribe("foo", (key, val) => {
  console.log(`Key updated: ${key} =`, val);
});

// Fetch with fallback
const data = await cache.getOrFetch("user:1", () =>
  fetch("/api/user/1").then(res => res.json())
);

API Reference

Core Methods

| Method | Description | Parameters | Returns | |--------|-------------|------------|----------| | set(key, value, ttl?) | Stores value in cache with optional TTL | key: string, value: any, ttl?: number (ms) | Promise<void> | | get(key) | Retrieves value if available and not expired | key: string | Promise<any \| undefined> | | del(key) | Deletes entry from cache | key: string | Promise<void> | | flushAll() | Clears cache completely | - | Promise<void> | | pause() | Pauses cache reads | - | void | | resume() | Resumes cache reads | - | void | | skipNext(key) | Skips next fetch for the given key | key: string | void |

Enhanced Methods

| Method | Description | Parameters | Returns | |--------|-------------|------------|----------| | getOrFetch(key, fetcher, ttl?, transform?) | Get from cache or fetch with fallback | key: string, fetcher: () => Promise<any>, ttl?: number, transform?: Transform | Promise<any> | | subscribe(key, fn) | Subscribe to changes on a key | key: string, fn: Subscriber | void | | unsubscribe(key, fn?) | Remove subscription | key: string, fn?: Subscriber | void |


Configuration Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | enableLogging | boolean | true | Enable debug logging | | mechanism | CacheMechanism | "memory" | Storage backend | | evictionPolicy | "LRU" \| "LFU" \| "FIFO" | "LRU" | Entry removal strategy | | maxEntries | number | 1000 | Maximum cache entries | | reduxConfig | ReduxConfig | undefined | Redux integration config |


Eviction Policies

| Policy | Description | When to Use | |--------|-------------|-------------| | LRU (Least Recently Used) | Removes the entry that was accessed the longest time ago | General purpose, good for temporal locality | | LFU (Least Frequently Used) | Removes the entry with the fewest cache hits | When access frequency matters more than recency | | FIFO (First In First Out) | Removes the oldest entry (based on insertion time) | Simple queue-like behavior |


Persistence Mechanisms

| Mechanism | Storage Backend | Persistence | Async | Browser Support | |-----------|-----------------|-------------|-------|-----------------| | memory | In-memory only | Session only | No | All | | localStorage | Browser Local Storage | Survives browser restart | No | Modern browsers | | sessionStorage | Browser Session Storage | Session only | No | Modern browsers | | indexedDB | IndexedDB | Survives browser restart | Yes | Modern browsers |


Pub/Sub System

Subscribe to cache changes to build reactive applications:

// Subscribe to specific key
cache.subscribe("user:123", (key, value) => {
  console.log(`User ${key} updated:`, value);
});

// Subscribe to multiple keys
["user:1", "user:2", "user:3"].forEach(key => {
  cache.subscribe(key, updateUI);
});

// Unsubscribe
cache.unsubscribe("user:123"); // Remove all subscribers
cache.unsubscribe("user:123", specificHandler); // Remove specific handler

Transform System

Transform data on storage and retrieval:

const cache = createCache();

const userCache = await cache.getOrFetch(
  "user:42",
  () => fetch("/api/user/42").then(r => r.json()),
  60000, // 1 minute TTL
  {
    // Transform before storing
    in: (userData) => ({
      ...userData,
      cachedAt: Date.now(),
      normalized: userData.name.toLowerCase()
    }),
    
    // Transform before returning
    out: (cachedData) => ({
      ...cachedData,
      accessedAt: Date.now(),
      isFromCache: true
    })
  }
);

Transform Use Cases

| Transform | Use Case | Example | |-----------|----------|---------| | Serialization | Convert objects to strings | in: JSON.stringify, out: JSON.parse | | Normalization | Standardize data format | in: (data) => normalizeUser(data) | | Enrichment | Add metadata | in: (data) => ({ ...data, cached: true }) | | Compression | Reduce storage size | in: compress, out: decompress |


Redux Integration

Automatically sync Redux state slices to cache:

import { createCache } from "unified-cache";
import { store } from "./redux/store";

const cache = createCache({
  reduxConfig: {
    store,
    slices: ["user", "cart", "preferences"],
    ttl: 300000, // 5 minutes
    keyFn: (slice) => `redux:${slice.id || 'global'}`,
    actionsToWatch: [
      "USER_LOGIN",
      "USER_UPDATE", 
      "CART_ADD_ITEM",
      "PREFERENCES_UPDATE"
    ]
  }
});

// Cache automatically updates when Redux state changes
// Access cached Redux data
const userState = await cache.get("redux:user");

Redux Config Options

| Option | Type | Required | Description | |--------|------|----------|-------------| | store | Redux Store | Yes | Redux store instance | | slices | string[] | No | State slices to sync | | actionsToWatch | string[] | No | Actions that trigger cache sync | | ttl | number | No | TTL for cached Redux state | | keyFn | (slice) => string | No | Custom cache key generator |


Advanced Examples

1. API Response Caching

const apiCache = createCache({
  mechanism: "localStorage",
  maxEntries: 100,
  evictionPolicy: "LRU"
});

async function fetchUser(id: string) {
  return apiCache.getOrFetch(
    `api:user:${id}`,
    () => fetch(`/api/users/${id}`).then(r => r.json()),
    300000, // 5 minutes
    {
      in: (user) => ({ ...user, cached: true }),
      out: (user) => ({ ...user, fromCache: true })
    }
  );
}

2. Image Caching with IndexedDB

const imageCache = createCache({
  mechanism: "indexedDB",
  maxEntries: 50
});

async function cacheImage(url: string) {
  return imageCache.getOrFetch(
    `image:${url}`,
    async () => {
      const response = await fetch(url);
      const blob = await response.blob();
      return URL.createObjectURL(blob);
    },
    3600000 // 1 hour
  );
}

3. Multi-level Caching

const l1Cache = createCache({ mechanism: "memory", maxEntries: 100 });
const l2Cache = createCache({ mechanism: "localStorage", maxEntries: 1000 });

async function getWithFallback(key: string, fetcher: () => Promise<any>) {
  // Try L1 first
  let result = await l1Cache.get(key);
  if (result) return result;
  
  // Try L2
  result = await l2Cache.get(key);
  if (result) {
    await l1Cache.set(key, result, 60000); // Cache in L1 for 1 minute
    return result;
  }
  
  // Fetch and populate both levels
  result = await fetcher();
  await l1Cache.set(key, result, 60000);
  await l2Cache.set(key, result, 3600000);
  return result;
}

TypeScript Types

export type CacheEntry = {
  value: any;
  expiry: number;
  meta: { 
    hits: number; 
    created: number; 
    lastUsed: number;
  };
};

export type Transform = {
  in?: (data: any) => any | Promise<any>;
  out?: (data: any) => any | Promise<any>;
};

export type Subscriber = (key: string, value: any) => void;

export type CacheMechanism = 
  | "memory" 
  | "localStorage" 
  | "sessionStorage" 
  | "indexedDB";

export type ReduxConfig = {
  store: any;
  slices?: string[];
  actionsToWatch?: string[];
  ttl?: number;
  keyFn?: (slice: any) => string;
};

export type CacheOptions = {
  enableLogging?: boolean;
  mechanism?: CacheMechanism;
  evictionPolicy?: "LRU" | "LFU" | "FIFO";
  maxEntries?: number;
  reduxConfig?: ReduxConfig;
};

Performance Tips

| Tip | Description | |-----|-------------| | Choose the right mechanism | Use memory for frequently accessed data, localStorage for persistence | | Set appropriate TTL | Short TTL for dynamic data, longer for static resources | | Use transforms wisely | Avoid expensive operations in transforms | | Monitor cache size | Set maxEntries based on available memory/storage | | Leverage pub/sub | Use subscriptions to build reactive UIs efficiently |


Browser Compatibility

| Feature | Chrome | Firefox | Safari | Edge | |---------|--------|---------|--------|------| | Memory caching | ✅ All | ✅ All | ✅ All | ✅ All | | localStorage | ✅ 4+ | ✅ 3.5+ | ✅ 4+ | ✅ All | | sessionStorage | ✅ 5+ | ✅ 2+ | ✅ 4+ | ✅ All | | IndexedDB | ✅ 24+ | ✅ 16+ | ✅ 10+ | ✅ All |


Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.


Changelog

v1.0.0

  • Initial release
  • Core caching functionality
  • Multiple storage mechanisms
  • Eviction policies
  • Pub/Sub system
  • Transform support
  • Redux integration