Cache
EreoJS provides a flexible caching system with support for tag-based invalidation.
Import
import {
createCache,
createTaggedCache,
MemoryCacheAdapter,
isTaggedCache,
wrapCacheAdapter
} from '@ereo/core'createCache
Creates a basic in-memory cache.
Signature
function createCache(options?: CacheOptions): CacheAdapterOptions
interface CacheOptions {
// Maximum number of entries (default: Infinity)
maxSize?: number
// Default TTL in seconds for all entries
defaultTtl?: number
// Enable tag support (used internally)
tagged?: boolean
}CacheAdapter Interface
interface CacheAdapter {
get<T>(key: string): Promise<T | undefined>
set<T>(key: string, value: T, options?: CacheSetOptions): Promise<void>
delete(key: string): Promise<boolean>
has(key: string): Promise<boolean>
clear(): Promise<void>
}Example
const cache = createCache({
maxSize: 1000,
defaultTtl: 3600
})
// Store a value
await cache.set('user:123', { name: 'Alice' })
// Retrieve a value
const user = await cache.get('user:123')
// Check existence
if (await cache.has('user:123')) {
console.log('User is cached')
}
// Delete a value
await cache.delete('user:123')
// Clear all
await cache.clear()createTaggedCache
Creates a cache with tag-based invalidation support.
Signature
function createTaggedCache(options?: Omit<CacheOptions, 'tagged'>): TaggedCacheThe tagged option is automatically set to true — you only need to provide maxSize and defaultTtl.
TaggedCache Interface
interface TaggedCache extends CacheAdapter {
// Invalidate all entries with a specific tag
invalidateTag(tag: string): Promise<void>
// Invalidate all entries with any of the specified tags
invalidateTags(tags: string[]): Promise<void>
// Get all keys associated with a tag
getByTag(tag: string): Promise<string[]>
}Note: The
MemoryCacheAdapterclass (the built-in implementation) also providesgetStats()andkeys()methods, but these are not part of theTaggedCacheinterface. If you need them, useMemoryCacheAdapterdirectly.
Example
const cache = createTaggedCache({ maxSize: 1000 })
// Store with tags
await cache.set('post:123', post, {
ttl: 3600,
tags: ['posts', 'post-123', 'author-456']
})
await cache.set('post:456', anotherPost, {
ttl: 3600,
tags: ['posts', 'post-456', 'author-789']
})
// Invalidate all posts
await cache.invalidateTag('posts')
// Invalidate specific post
await cache.invalidateTag('post-123')
// Invalidate multiple tags
await cache.invalidateTags(['author-456', 'author-789'])
// Get stats
const stats = cache.getStats()
console.log(`Cache size: ${stats.size}, Tags: ${stats.tags}`)MemoryCacheAdapter
The built-in memory cache implementation. Implements both CacheAdapter and TaggedCache interfaces.
Constructor
new MemoryCacheAdapter(options?: CacheOptions)Methods
class MemoryCacheAdapter implements TaggedCache {
// CacheAdapter methods
get<T>(key: string): Promise<T | undefined>
set<T>(key: string, value: T, options?: CacheSetOptions): Promise<void>
delete(key: string): Promise<boolean>
has(key: string): Promise<boolean>
clear(): Promise<void>
// TaggedCache methods
invalidateTag(tag: string): Promise<void>
invalidateTags(tags: string[]): Promise<void>
getByTag(tag: string): Promise<string[]>
// Additional methods
keys(): Promise<string[]>
getStats(): { size: number; tags: number }
}Example
const cache = new MemoryCacheAdapter({
maxSize: 5000,
defaultTtl: 1800
})
// Use like any other cache
await cache.set('key', 'value')
const value = await cache.get('key')
// Get all keys
const keys = await cache.keys()
console.log(`Cached keys: ${keys.join(', ')}`)
// Get statistics
const stats = cache.getStats()
console.log(`Size: ${stats.size}, Tags: ${stats.tags}`)CacheSetOptions
Options when setting cache values.
interface CacheSetOptions {
// Time to live in seconds
ttl?: number
// Tags for invalidation
tags?: string[]
}wrapCacheAdapter
Wraps a custom cache implementation to conform to the CacheAdapter interface. Useful for adapting existing cache implementations.
Signature
function wrapCacheAdapter(impl: {
get: <T>(key: string) => Promise<T | undefined> | T | undefined
set: <T>(key: string, value: T, options?: CacheSetOptions) => Promise<void> | void
delete: (key: string) => Promise<boolean> | boolean
has: (key: string) => Promise<boolean> | boolean
clear: () => Promise<void> | void
}): CacheAdapterExample
// Wrap a Map-based cache
const mapCache = new Map<string, unknown>()
const cache = wrapCacheAdapter({
get(key) {
return mapCache.get(key)
},
set(key, value) {
mapCache.set(key, value)
},
delete(key) {
return mapCache.delete(key)
},
has(key) {
return mapCache.has(key)
},
clear() {
mapCache.clear()
}
})
// Now use the standard CacheAdapter interface
await cache.set('key', 'value')
const value = await cache.get('key')Redis Example
import Redis from 'ioredis'
const redis = new Redis()
const cache = wrapCacheAdapter({
async get(key) {
const value = await redis.get(key)
return value ? JSON.parse(value) : undefined
},
async set(key, value, options) {
const ttl = options?.ttl || 3600
await redis.setex(key, ttl, JSON.stringify(value))
},
async delete(key) {
const result = await redis.del(key)
return result > 0
},
async has(key) {
const result = await redis.exists(key)
return result > 0
},
async clear() {
await redis.flushdb()
}
})isTaggedCache
Type guard to check if a cache supports tags.
Signature
function isTaggedCache(cache: CacheAdapter): cache is TaggedCacheExample
const cache = createCache()
if (isTaggedCache(cache)) {
await cache.invalidateTag('posts')
} else {
// Fall back to clearing specific keys
await cache.delete('posts:*')
}Using Cache in Loaders
import { createLoader } from '@ereo/data'
import { createTaggedCache } from '@ereo/core'
const cache = createTaggedCache()
export const loader = createLoader(async ({ params }) => {
const cacheKey = `post:${params.id}`
// Try cache first
let post = await cache.get(cacheKey)
if (!post) {
// Fetch from database
post = await db.posts.find(params.id)
// Store in cache
await cache.set(cacheKey, post, {
ttl: 3600,
tags: ['posts', `post-${params.id}`]
})
}
return { post }
})Cache Patterns
Write-Through
async function createPost(data) {
const post = await db.posts.create(data)
// Write to cache immediately
await cache.set(`post:${post.id}`, post, {
tags: ['posts', `post-${post.id}`]
})
return post
}Write-Behind
async function createPost(data) {
const post = await db.posts.create(data)
// Invalidate and let next read populate cache
await cache.invalidateTag('posts')
return post
}Cache-Aside with TTL
async function getPost(id) {
const cached = await cache.get(`post:${id}`)
if (cached) return cached
const post = await db.posts.find(id)
await cache.set(`post:${id}`, post, { ttl: 300 })
return post
}