Cache

Network calls are expensive. The cache system lets you build complex cache strategies declaratively — define tags on your methods and the framework handles invalidation, updates, and storage automatically. You never touch the cache manually.

How it works

Cache entries are connected through tags. When you cache a method’s result, you tag it with identifiers that describe the data it contains. Other methods can then update or invalidate entries by targeting those tags — and every cache entry that shares those tags is affected automatically.

This means deleting a single product invalidates the entire product list cache — because the list contained that product and no longer reflects reality.

Basic usage

@Service
class ProductRepository {
  @TTL(30000)
  async getProducts() {
    const res = await fetch('/api/products');
    return await res.json();
  }
}

The first call fetches from the API. Any call within the next 30 seconds returns the cached result. After 30 seconds, the next call fetches again.

Cache providers

By default, cache lives in memory. Use @Cache to switch to persistent storage:

@Cache(LocalStorageCache)
@TTL(3600000)
async getUserPreferences() {
  return await fetch('/api/preferences').then(r => r.json());
}

@Cache(SessionStorageCache)
@TTL(60000)
async getSessionData() {
  return await fetch('/api/session').then(r => r.json());
}
Provider Persistence
MemoryCache Lost on page refresh (default)
LocalStorageCache Persists across sessions
SessionStorageCache Persists until tab closes

Cache tags

Tags identify cache entries so they can be updated or invalidated by other methods — without knowing their exact keys:

@Cache(LocalStorageCache)
@TTL(60000)
@CacheTags((result) => result.map((p: any) => `product:${p.id}`))
async getProducts() {
  return await fetch('/api/products').then(r => r.json());
}

Updating and invalidating

@CacheUpdate replaces cached values that match certain tags. @CacheInvalidate removes them:

@CacheUpdate(
  LocalStorageCache,
  (result) => [`product:${result.id}`]
)
async updateProduct(id: string, data: any) {
  const res = await fetch(`/api/products/${id}`, {
    method: 'PUT',
    body: JSON.stringify(data),
  });
  return await res.json();
}

@CacheInvalidate(
  LocalStorageCache,
  (_, args) => [`product:${args[0]}`]
)
async deleteProduct(id: string) {
  await fetch(`/api/products/${id}`, { method: 'DELETE' });
}

Tags connect entries across methods. If getProducts() caches its result with tags ["product:1", "product:2", "product:3"], and deleteProduct("2") invalidates the tag "product:2", the entire getProducts() cache entry is removed — because it contained that tag. The next call to getProducts() fetches fresh data from the API.