A high-performance, type-safe caching library for TypeScript and JavaScript applications with support for multiple storage drivers including in-memory and Redis (using Bun's native Redis client).
- π Multiple Drivers - Memory, Memory-LRU, and Redis (using Bun's native client)
- π Async & Sync APIs - Promise-based async API with legacy sync support
- β±οΈ Flexible TTL - Per-key TTL with fixed, sliding window, and probabilistic strategies
- π¦ Batch Operations - Efficient mset/mget for multiple keys at once
- π·οΈ Tagging System - Organize and invalidate cache entries by tags
- π Caching Patterns - Cache-aside, read-through, write-through, write-behind, refresh-ahead, multi-level
- π― CLI Tool - Complete command-line interface with 11 commands
- ποΈ Compression - gzip, brotli, and smart compression with configurable thresholds
- π‘οΈ Type Safety - Full TypeScript support with generics
- π§ Highly Configurable - 27+ configuration options for fine-tuned control
ts-cache can be faster than lru-cache! π
With ultra-fast mode enabled, ts-cache achieves:
- GET: 3.90 ns (3.2x faster than lru-cache's 12.37 ns)
- SET: 30.24 ns (1.3x faster than lru-cache's 39.52 ns)
Enable ultra-fast mode for maximum performance:
const cache = new Cache({
useClones: false, // Store references (no cloning)
enableStats: false, // Disable statistics
enableEvents: false, // Disable events
stdTTL: 0, // Disable TTL checking
checkPeriod: 0, // Disable expiration checks
maxPerformance: true, // Use Map storage
})
// Now faster than lru-cache!
cache.set('key', value) // 30ns vs lru-cache 40ns
cache.get('key') // 4ns vs lru-cache 12ns
See benchmarks for details.
# Using npm
npm install @stacksjs/ts-cache
# Using yarn
yarn add @stacksjs/ts-cach
7FA
e
# Using pnpm
pnpm add @stacksjs/ts-cache
# Using bun
bun add @stacksjs/ts-cache
import { createCache } from '@stacksjs/ts-cache'
const cache = createCache() // Uses memory driver by default
// Store a value (with 5 minute TTL)
await cache.set('user:123', { name: 'John', role: 'admin' }, 300)
// Retrieve a value with type safety
const user = await cache.get<{ name: string, role: string }>('user:123')
if (user) {
console.log(user.name) // TypeScript knows this is a string
}
// Check if a key exists
if (await cache.has('user:123')) {
// Key exists and is not expired
}
// Delete a key
await cache.del('user:123')
import { createCache } from '@stacksjs/ts-cache'
const cache = createCache({
driver: 'redis',
url: 'redis://localhost:6379',
prefix: 'myapp',
stdTTL: 3600, // Default TTL: 1 hour
})
// Same API as memory cache
await cache.set('session:abc', { userId: 1 })
const session = await cache.get('session:abc')
// Close connection when done
await cache.close()
Isolate cache data with namespaced prefixes:
const cache = createCache()
// Create namespaced caches
const userCache = cache.namespace('users')
const postCache = cache.namespace('posts')
await userCache.set('1', { name: 'Alice' })
await postCache.set('1', { title: 'Hello World' })
// No collision between namespaces
console.log(await userCache.get('1')) // { name: 'Alice' }
console.log(await postCache.get('1')) // { title: 'Hello World' }
Organize and invalidate cache by tags:
// Set values with tags
await cache.set('user:1', { name: 'John' })
await cache.tag('user:1', ['users', 'active'])
await cache.set('user:2', { name: 'Jane' })
await cache.tag('user:2', ['users', 'premium'])
// Get all keys by tag
const userKeys = await cache.getKeysByTag('users')
// Delete all entries with a tag
await cache.deleteByTag('users')
Laravel-style remember pattern for fetch-or-compute:
// Fetch from cache or compute if missing
const user = await cache.remember('user:1', 60, async () => {
// Only called if cache miss
return await database.getUser(1)
})
// Remember forever (no expiration)
const config = await cache.rememberForever('config', async () => {
return await loadConfig()
})
Built-in rate limiting utility:
import { RateLimiter } from '@stacksjs/ts-cache'
const limiter = new RateLimiter(cache, 100, 60) // 100 requests per 60 seconds
const result = await limiter.check('user:123')
if (result.limited) {
console.log('Rate limited! Try again at:', new Date(result.resetAt))
}
else {
console.log('Request allowed. Remaining:', result.remaining)
}
Implement distributed locks for critical sections:
import { CacheLock } from '@stacksjs/ts-cache'
const lock = new CacheLock(cache, 30) // 30 second lock timeout
const result = await lock.withLock('critical:resource', async () => {
// This code only runs if lock is acquired
return await performCriticalOperation()
})
if (result === null) {
console.log('Could not acquire lock')
}
Cache function results with automatic key generation:
import { memoize } from '@stacksjs/ts-cache'
async function expensiveFunction(a: number, b: number) {
// Expensive computation
return a + b
}
const memoized = memoize(expensiveFunction, cache, {
ttl: 60,
keyGenerator: (a, b) => `sum:${a}:${b}`,
})
// First call computes
console.log(await memoized(5, 3)) // Computes and caches
// Second call uses cache
console.log(await memoized(5, 3)) // Returns cached result
ts-cache includes built-in support for common caching patterns:
import { CacheAside } from '@stacksjs/ts-cache'
const pattern = new CacheAside(cache)
// Load data on-demand
const user = await pattern.get('user:123', async (key) => {
return await database.getUser(123)
}, 3600)
import { ReadThrough } from '@stacksjs/ts-cache'
const pattern = new ReadThrough(cache, async (key) => {
return await database.get(key)
}, 3600)
const data = await pattern.get('key')
import { WriteThrough } from '@stacksjs/ts-cache'
const pattern = new WriteThrough(cache, async (key, value) => {
await database.save(key, value)
})
// Writes to both cache and database
await pattern.set('user:123', userData, 3600)
import { WriteBack } from '@stacksjs/ts-cache'
const pattern = new WriteBack(
cache,
async (key, value) => await database.save(key, value),
1000, // Flush delay in ms
)
// Writes to cache immediately, database later
await pattern.set('user:123', userData)
// Manually flush pending writes
await pattern.flush()
import { RefreshAhead } from '@stacksjs/ts-cache'
const pattern = new RefreshAhead(
cache,
async key => await fetchFreshData(key),
3600, // TTL
0.8, // Refresh when 80% of TTL has passed
)
// Returns cached value, refreshes in background if stale
const data = await pattern.get('key')
import { createCache, MultiLevelPattern } from '@stacksjs/ts-cache'
const l1 = createCache({ driver: 'memory', maxKeys: 100 })
const l2 = createCache({ driver: 'redis' })
const pattern = new MultiLevelPattern([l1, l2], [300, 3600])
// Checks L1, then L2, populates higher levels on hit
const value = await pattern.get('key')
// Writes to all levels
await pattern.set('key', 'value')
Efficiently handle multiple operations:
// Set multiple values at once
await cache.mset([
{ key: 'key1', value: 'value1', ttl: 60 },
{ key: 'key2', value: 'value2', ttl: 120 },
{ key: 'key3', value: 'value3' },
])
// Get multiple values at once
const values = await cache.mget(['key1', 'key2', 'key3'])
console.log(values) // { key1: 'value1', key2: 'value2', key3: 'value3' }
// Delete multiple keys
await cache.del(['key1', 'key2'])
Multiple serialization strategies for different data types:
import { createCache, serializers } from '@stacksjs/ts-cache'
const cache = createCache({
driver: 'redis',
serializer: serializers.auto, // Auto-detects and preserves types
})
// Complex data types are preserved
await cache.set('date', new Date())
await cache.set('regex', /test/gi)
await cache.set('set', new Set([1, 2, 3]))
await cache.set('map', new Map([['a', 1]]))
// Available serializers:
// - serializers.json (default)
// - serializers.string
// - serializers.number
// - serializers.boolean
// - serializers.buffer
// - serializers.auto (preserves types)
// - serializers.msgpack (requires msgpack-lite)
Listen for cache events:
cache.on('hit', (key, value) => {
console.log(`Cache hit: ${key}`)
})
cache.on('miss', (key) => {
console.log(`Cache miss: ${key}`)
})
cache.on('set', (key, value, ttl) => {
console.log(`Cache set: ${key}`)
})
cache.on('del', (keys, count) => {
console.log(`Deleted ${count} keys`)
})
cache.on('flush', () => {
console.log('Cache flushed')
})
Track cache performance:
const stats = await cache.getStats()
console.log(stats)
// {
// hits: 127,
// misses: 9,
// keys: 42,
// ksize: 840,
// vsize: 2390
// }
ts-cache includes a powerful command-line interface for managing your cache:
# Install globally for CLI access
npm install -g @stacksjs/ts-cache
# Or use with npx/bunx
bunx cache --help
# Get a value from cache
cache get user:123
cache get user:123 --json
# Set a value with TTL
cache set user:123 '{"name":"John"}' --json --ttl 3600
# Delete keys
cache del user:123
cache del user:* session:*
# Check if key exists
cache has user:123
# List all keys
cache keys
cache keys "user:*"
# Get TTL of a key
cache ttl session:abc
# View cache statistics
cache stats
cache stats --json
# Show current configuration
cache config
cache config --json
<
103CE
span class="pl-c"># Flush all cache data
cache flush --force
# Test cache connectivity
cache test
cache test --driver redis
# Show library info
cache info
# Show version
cache version
All commands support these options:
--driver <driver>
- Use specific driver (memory, memory-lru, redis)--prefix <prefix>
- Add key prefix--json
- Output in JSON format (machine-readable)--ttl <seconds>
- Set time-to-live for set operations
ts-cache supports comprehensive configuration through a cache.config.ts
file at the root of your project:
// cache.config.ts
import type { CacheConfig } from '@stacksjs/ts-cache'
const config: CacheConfig = {
// General Settings
driver: 'memory', // 'memory' | 'memory-lru' | 'redis'
prefix: 'myapp',
verbose: true,
// Common Cache Settings
stdTTL: 3600, // Default TTL in seconds
checkPeriod: 600, // Cleanup interval
maxKeys: 1000,
useClones: true,
// Redis Configuration
redis: {
url: process.env.REDIS_URL,
host: 'localhost',
port: 6379,
password: process.env.REDIS_PASSWORD,
database: 0,
},
// Compression
compression: {
algorithm: 'gzip', // 'gzip' | 'brotli' | 'smart' | 'none'
level: 6,
threshold: 1024, // Only compress if larger than 1KB
enabled: true,
},
// Middleware
middleware: {
enabled: true,
logging: true,
metrics: true,
retry: {
enabled: true,
maxRetries: 3,
initialDelay: 100,
},
},
// Caching Patterns
patterns: {
refreshAhead: {
ttl: 3600,
thresholdPercentage: 0.8, // Refresh when 80% of TTL passed
},
slidingWindow: {
ttl: 3600, // Reset TTL on access
},
},
// Event Hooks
events: {
onSet: (key, value) => console.log(`Set: ${key}`),
onGet: (key, value) => console.log(`Get: ${key}`),
onMiss: key => console.log(`Miss: ${key}`),
},
// Performance Tuning
performance: {
enableStats: true,
batchSize: 100,
warmup: {
enabled: true,
keys: ['popular:item:1', 'popular:item:2'],
},
},
// Multi-Level Cache
multiLevel: {
enabled: true,
levels: [
{ driver: 'memory', ttl: 300, maxKeys: 1000 }, // L1: Fast
{ driver: 'redis', ttl: 3600 }, // L2: Persistent
],
},
// TTL Strategy
ttlStrategy: {
mode: 'sliding', // 'fixed' | 'sliding' | 'probabilistic'
jitter: 0.1, // Add 10% jitter to prevent thundering herd
},
// Error Handling
errorHandling: {
throwOnError: false,
circuitBreaker: {
enabled: true,
threshold: 5,
timeout: 60000,
},
},
// Debug Mode
debug: {
enabled: false,
logLevel: 'info',
trackAccess: true,
},
}
export default config
See the complete configuration reference for all 27+ available options.
const cache = createCache({
driver: 'memory',
stdTTL: 3600, // Default TTL in seconds
checkPeriod: 600, // Check for expired items every 10 minutes
maxKeys: 1000, // Maximum number of keys
useClones: true, // Clone values on get/set
deleteOnExpire: true, // Remove items when they expire
prefix: 'myapp', // Key prefix for namespacing
})
const cache = createCache({
driver: 'redis',
url: 'redis://localhost:6379', // Redis connection URL
// Or use individual options:
host: 'localhost',
port: 6379,
password: 'secret',
database: 0,
// Connection options
connectionTimeout: 5000,
autoReconnect: true,
maxRetries: 10,
tls: false,
// Cache options
stdTTL: 3600,
prefix: 'myapp',
})
Protect against cascading failures:
import { CircuitBreaker } from '@stacksjs/ts-cache'
const breaker = new CircuitBreaker(cache, 5, 60) // 5 failures in 60 seconds
try {
const result = await breaker.execute('api:endpoint', async () => {
return await callExternalAPI()
})
}
catch (error) {
console.log('Circuit breaker is open')
}
Preload cache with data:
import { CacheWarmer } from '@stacksjs/ts-cache'
const warmer = new CacheWarmer(cache)
await warmer.warm([
{ key: 'user:1', fetcher: () => getUser(1), ttl: 3600 },
{ key: 'user:2', fetcher: () => getUser(2), ttl: 3600 },
])
Debounce cache writes:
import { DebouncedCache } from '@stacksjs/ts-cache'
const debounced = new DebouncedCache(cache, 1000) // 1 second delay
// Multiple rapid calls only write once
debounced.set('key', 'value1')
debounced.set('key', 'value2')
debounced.set('key', 'value3') // Only this value is written after 1 second
The legacy synchronous API is still available for backwards compatibility:
import { Cache, createCache } from '@stacksjs/ts-cache'
// Old synchronous API (still works)
const syncCache = new Cache()
syncCache.set('key', 'value')
const syncValue = syncCache.get('key')
// New async API (recommended)
const asyncCache = createCache()
await asyncCache.set('key', 'value')
const asyncValue = await asyncCache.get('key')
- API Response Caching: Reduce API calls by caching responses
- Session Management: Store user sessions with Redis for distributed apps
- Rate Limiting: Implement request throttling with automatic expiration
- Distributed Locking: Coordinate access to shared resources
- Function Memoization: Cache expensive function results
- Database Query Caching: Speed up repeated database queries
- Computed Values: Store results of expensive calculations
For detailed documentation, see:
Please see the Contributing Guide for details.
For help, discussion about best practices, or any other conversation that would benefit from being searchable:
For casual chit-chat with others using this package:
Join the Stacks Discord Server
"Software that is free, but hopes for a postcard." We love receiving postcards from around the world showing where Stacks is being used! We showcase them on our website too.
Our address: Stacks.js, 12665 Village Ln #2306, Playa Vista, CA 90094, United States π
We would like to extend our thanks to the following sponsors for funding Stacks development. If you are interested in becoming a sponsor, please reach out to us.
node-cache
for the original Node.js implementation- Bun for the native Redis client
- Chris Breuer
- All Contributors
The MIT License (MIT). Please see LICENSE for more information.
Made with π