Docs/cachingv1.0.0-rc.1

Cache Decorators

UDIOT provides three decorators for method-level caching: @AICache caches a method's return value, @CacheInvalidate evicts cache entries when a method is called, and @CacheKey marks which parameters should be included when building the cache key.

All three decorators operate against the global CacheManager returned by getGlobalCacheManager().

Key Concepts

  • Auto key generation. By default, @AICache builds keys as udiot:<ClassName>:<methodName>:<argsHash> using CacheKeyGenerator. Providing a custom keyGenerator overrides this entirely.
  • @CacheKey filtering. When at least one parameter is decorated with @CacheKey, only those parameters contribute to the cache key hash. Undecorated parameters are ignored. This is useful when a method accepts options objects that should not vary the cache key.
  • Post-call invalidation. @CacheInvalidate runs the original method first, then performs invalidation so the caller always receives the fresh result.
  • Event emission. events in @CacheInvalidate calls cacheManager.handleEvent(eventName, { result, args }) after method completion, triggering any matching setupEventInvalidation handlers.

API Reference

@AICache(options?)

function AICache(options?: AICacheOptions): MethodDecorator

AICacheOptions:

Field Type Description
strategy CacheStrategy Provider to use: 'memory' | 'redis' | 'cdn' | 'tiered' | 'auto'
ttl 'auto' | number TTL in seconds, or 'auto' to derive from compute time
tags string[] Tags for grouped invalidation
invalidateOn string[] Reserved field for future event-based invalidation wiring
keyGenerator (...args: any[]) => string Custom cache key function
condition (...args: any[]) => boolean Return false to skip the cache entirely
serialize (value: any) => any Transform value before storing
deserialize (value: any) => any Transform value after retrieval
predictive boolean Reserved for future pre-warming integration

@CacheInvalidate(options)

function CacheInvalidate(options: CacheInvalidateOptions): MethodDecorator

CacheInvalidateOptions:

Field Type Description
keys string[] | ((...args) => string[]) Exact keys to delete. The function form receives the method's arguments.
tags string[] Tags to bulk-invalidate
events string[] Event names to emit via handleEvent

@CacheKey()

function CacheKey(): ParameterDecorator

Place on any number of parameters of a method that also carries @AICache. Only decorated parameters contribute to the cache key.

Introspection helpers

getAICacheOptions(target, propertyKey): AICacheOptions | undefined
isAICached(target, propertyKey): boolean
getCacheInvalidateOptions(target, propertyKey): CacheInvalidateOptions | undefined
isCacheInvalidator(target, propertyKey): boolean
getCacheKeyParams(target, propertyKey): number[]       // parameter indices
hasCacheKeyParams(target, propertyKey): boolean

AICacheAnalyzer

A standalone class that tracks access patterns and produces optimization reports. It is not wired automatically to the global manager but can be used independently.

const analyzer = new AICacheAnalyzer();
analyzer.trackHit(key, strategy);
analyzer.trackMiss(key, strategy, computeTimeMs);
analyzer.trackUpdate(key);              // the underlying data changed
analyzer.trackSet(key, ttlSeconds);     // an entry was stored with this TTL
const ttl = await analyzer.recommendTTL(key, computeTimeMs, value);
const strategy = await analyzer.recommendStrategy(key, factory);
const report = await analyzer.generateReport();
// report.inefficientKeys, report.oversizedTTLs, report.undersizedTTLs, report.recommendations
const predictions = await analyzer.predictAccess(); // CachePrediction[]

Basic Usage

import {
  AICache,
  CacheInvalidate,
  CacheKey,
} from '@tagna/udiot';

interface User {
  id: string;
  email: string;
  name: string;
}

class UserService {
  @AICache({
    strategy: 'memory',
    ttl: 300,
    tags: ['users'],
  })
  async getUser(@CacheKey() id: string, _includeDeleted = false): Promise<User> {
    // Only 'id' forms the cache key; '_includeDeleted' is ignored.
    return db.users.findById(id);
  }

  @CacheInvalidate({
    tags: ['users'],
    keys: (id: string) => [`udiot:userservice:getuser:${hashOf(id)}`],
    events: ['user.updated'],
  })
  async updateUser(id: string, data: Partial<User>): Promise<User> {
    return db.users.update(id, data);
  }

  @CacheInvalidate({ tags: ['users'] })
  async deleteUser(id: string): Promise<void> {
    await db.users.delete(id);
  }
}

Common Patterns

Conditional caching

Only cache results for authenticated requests:

@AICache({
  ttl: 60,
  condition: (_id: string, ctx: RequestContext) => ctx.isAuthenticated,
})
async getSecretData(id: string, ctx: RequestContext): Promise<Secret> {
  return db.secrets.findById(id);
}

Serialization round-trip

Cache a Date object by converting it to a timestamp:

@AICache({
  ttl: 3600,
  serialize: (value: { timestamp: Date }) => ({
    ...value,
    timestamp: value.timestamp.toISOString(),
  }),
  deserialize: (raw: { timestamp: string }) => ({
    ...raw,
    timestamp: new Date(raw.timestamp),
  }),
})
async getEvent(id: string): Promise<{ timestamp: Date; name: string }> {
  return db.events.findById(id);
}

Auto TTL for expensive computations

@AICache({ ttl: 'auto' })
async runHeavyReport(params: ReportParams): Promise<Report> {
  // If this takes 500 ms, the TTL will be max(60, min(5000, 3600)) = 3600 s.
  return analytics.generateReport(params);
}

Using AICacheAnalyzer for optimization reports

import { AICacheAnalyzer } from '@tagna/udiot';

const analyzer = new AICacheAnalyzer();

// After a period of operation, generate a report:
const report = await analyzer.generateReport();
console.log('Overall hit rate:', report.overallHitRate);
console.log('Recommendations:', report.recommendations);

Integration Notes

  • @AICache requires reflect-metadata to be imported before the decorator runs. Import it once at the entry point of your application.
  • The global CacheManager must be configured before any decorated method is called. If setGlobalCacheManager has not been called, a default in-memory manager is created lazily.
  • @CacheKey decorators are read by CacheKeyGenerator.generate through filterCacheKeyArgs. If you provide a custom keyGenerator, @CacheKey has no effect.

Pitfalls

  • @CacheInvalidate runs invalidation after the method returns. If the method throws, the cache is not invalidated. Wrap the invalidation call in a try/finally block if you need guaranteed cleanup on error.
  • Passing a function to keys receives the raw method arguments in their original order. Ensure the function signature matches the method signature.
  • Keys generated by CacheKeyGenerator include a 12-character SHA-256 hash of the serialized arguments. Non-serializable arguments (class instances, functions) fall back to String(arg), which may collide if two distinct objects stringify identically.
From the canonical UDIOT public documentation.Try it in the playground ↗