unified-cache
v1.0.1
Published
Universal caching for Redux, API fetchers, and general data storage with memory and persistence.
Downloads
3
Maintainers
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-cacheUsage
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 handlerTransform 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
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - 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
