Cache Factory
CacheFactory provides a flexible caching mechanism to improve performance by storing and reusing the results of expensive operations. It supports both in-memory and persisted (IndexedDB) caching with automatic expiration.
Quick Start
import { CacheFactory } from '@skedulo/horizon-core'
// Create a cache with persistence enabled (default)
const myCache = CacheFactory.create<RequestParams, ResponseData>(
'my-cache-name',
true, // enablePersistence
3600000, // TTL in milliseconds (1 hour, default)
)
// Use the cache
const data = await myCache.execute(
{ id: '123' }, // cache key
async () => {
// Expensive operation
return await fetchData('123')
},
)
Cache Types
The factory can create in memory or persisted cache:
1. PersistedCache (Default)
Stores data in IndexedDB for persistence across browser sessions.
const persistedCache = CacheFactory.create<Params, Data>(
'my-persisted-cache',
true, // persistence enabled
3600000, // 1 hour TTL
)
Features:
- Survives browser refreshes and restarts
- Uses IndexedDB via
idb-keyval - Automatic cleanup of expired entries on load
- Falls back to memory cache if IndexedDB fails
2. LocalCache
In-memory only cache that doesn't persist across sessions.
const localCache = CacheFactory.create<Params, Data>(
'my-local-cache',
false, // persistence disabled
1800000, // 30 minutes TTL
)
Features:
- Fast access (no disk I/O)
- Cleared when page reloads
- Useful for temporary data or when persistence isn't needed
Configuration
Caching behavior can be controlled via localStorage:
Enable/Disable Caching
// Disable all caches
localStorage.setItem('HORIZON_CORE_ENABLED_CACHES', 'none')
// Enable all caches (default)
localStorage.setItem('HORIZON_CORE_ENABLED_CACHES', 'all')
// Enable only local (non-persisted) caches
localStorage.setItem('HORIZON_CORE_ENABLED_CACHES', 'local')
// Enable specific caches by name
localStorage.setItem('HORIZON_CORE_ENABLED_CACHES', 'cache-name-1,cache-name-2')
Cache Interface
All cache types implement the same interface:
interface Cache<R, T> {
execute: (key: R, fn: () => Promise<T>) => Promise<T>
invalidate: (key: R) => void
}
execute()
Executes a function with caching. If a valid cached value exists, it returns immediately. Otherwise, executes the function and caches the result.
const result = await cache.execute(
cacheKey, // Serializable cache key (will be JSON.stringified)
async () => {
// Your expensive operation
return await apiCall()
},
)
Parameters:
key: R- Any serializable value used as the cache keyfn: () => Promise<T>- The function to execute if cache miss
Returns: Promise<T> - The cached or newly computed result
invalidate()
Removes a specific entry from the cache.
cache.invalidate({ id: '123' })
Parameters:
key: R- The cache key to invalidate
Time-To-Live (TTL)
Cached entries automatically expire after their TTL. The default is 1 hour (3,600,000 ms).
// Custom TTL examples
const shortLivedCache = CacheFactory.create('short', true, 60000) // 1 minute
const longLivedCache = CacheFactory.create('long', true, 86400000) // 24 hours
When an entry expires:
- It's automatically removed from the cache
- The next
execute()call will run the provided function - For persisted caches, expired entries are cleaned up on page load
Best Practices
1. Use Unique Cache Names
Each cache must have a unique name. Duplicate names will throw an error:
const cache1 = CacheFactory.create('my-cache', true)
const cache2 = CacheFactory.create('my-cache', true) // ❌ Error!
2. Choose Appropriate TTL
Set TTL based on how frequently your data changes:
// Fast-changing data: short TTL
const userStatusCache = CacheFactory.create('user-status', true, 60000) // 1 min
// Slow-changing data: long TTL
const configCache = CacheFactory.create('config', true, 3600000) // 1 hour
// Static data: very long TTL
const metadataCache = CacheFactory.create('metadata', true, 86400000) // 24 hours
3. Use Serializable Cache Keys
Cache keys are serialized using JSON.stringify(), so use simple objects:
// ✅ Good cache keys
cache.execute({ id: '123' }, ...)
cache.execute({ userId: 'abc', type: 'profile' }, ...)
cache.execute('simple-string', ...)
// ❌ Avoid functions, symbols, or circular references
cache.execute({ fn: () => {} }, ...) // Won't work as expected
4. Disable Persistence for Sensitive Data
For security-sensitive data, use in-memory caching only:
const authCache = CacheFactory.create(
'auth-tokens',
false, // Don't persist to disk
300000, // 5 minutes
)
5. Invalidate on Updates
Manually invalidate cache entries when underlying data changes:
// After updating data
await updateUser(userId, newData)
userCache.invalidate({ id: userId })
Real-World Example
import { CacheFactory } from '@skedulo/horizon-core'
// Create a cache for API responses
const apiCache = CacheFactory.create<
{ endpoint: string; params?: Record<string, string> },
any
>(
'api-responses',
true, // Persist across sessions
1800000, // 30 minutes
)
// Usage in a data fetching function
async function fetchUserProfile(userId: string) {
return apiCache.execute(
{ endpoint: 'users', params: { id: userId } },
async () => {
console.log('Cache miss - fetching from API')
const response = await fetch(`/api/users/${userId}`)
return response.json()
},
)
}
// First call: fetches from API
const profile1 = await fetchUserProfile('123')
// Second call: returns from cache
const profile2 = await fetchUserProfile('123')
// After updating the user
await updateUser('123', { name: 'New Name' })
apiCache.invalidate({ endpoint: 'users', params: { id: '123' } })
// Next call: fetches fresh data
const profile3 = await fetchUserProfile('123')