Skip to content

⚡ A fast, type-safe, and feature-rich caching library for modern applications.

License

Notifications You must be signed in to change notification settings

stacksjs/ts-cache

Social Card of this repo

npm version License Bundle Size TypeScript

ts-cache

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).

Features

  • 🚀 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

Performance

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.

Installation

# Using npm npm install @stacksjs/ts-cache # Using yarn yarn add @stacksjs/ts-cache # Using pnpm pnpm add @stacksjs/ts-cache # Using bun bun add @stacksjs/ts-cache

Quick Start

Memory Cache (Default)

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')

Redis Cache (Bun Native)

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()

Advanced Features

Namespaces

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' }

Tagging

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')

Remember Pattern

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() })

Rate Limiting

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) }

Distributed Locking

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') }

Memoization

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

Caching Patterns

ts-cache includes built-in support for common caching patterns:

Cache-Aside (Lazy Loading)

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)

Read-Through

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')

Write-Through

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)

Write-Behind (Write-Back)

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()

Refresh-Ahead

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')

Multi-Level Cache

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')

Batch Operations

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'])

Serializers

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)

Events

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') })

Statistics

Track cache performance:

const stats = await cache.getStats() console.log(stats) // { // hits: 127, // misses: 9, // keys: 42, // ksize: 840, // vsize: 2390 // }

CLI Tool

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

Available Commands

# 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 # 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

CLI Options

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

Configuration

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

Configuration Options

See the complete configuration reference for all 27+ available options.

Memory Driver 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 })

Redis Driver Options

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', })

Utilities

Circuit Breaker

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') }

Cache Warmer

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 }, ])

Debounced Cache

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

Migration from v0.1.x

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')

Use Cases

  • 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

Documentation

For detailed documentation, see:

Contributing

Please see the Contributing Guide for details.

Community

For help, discussion about best practices, or any other conversation that would benefit from being searchable:

Discussions on GitHub

For casual chit-chat with others using this package:

Join the Stacks Discord Server

Postcardware

"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 🌎

Sponsors

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.

Credits

License

The MIT License (MIT). Please see LICENSE for more information.

Made with 💙

About

⚡ A fast, type-safe, and feature-rich caching library for modern applications.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

  •  

Contributors 6