Skip to main content

Overview

The cache system provides a flexible way to cache handler responses and intermediate computations with support for custom cache stores and key resolution strategies.

createCache

Creates a cache function for use within handlers.
import { createCache } from '@apisr/controller';

const cache = createCache({
  payload: { userId: '123' },
  handlerCache: {
    store: myStore,
    key: ['user', 'profile'],
  },
  callCache: {
    wrapHandler: true,
  },
});

const result = await cache(['expensive', 'operation'], async () => {
  return await performExpensiveOperation();
});
opts
CreateCacheOptions
required
Options for creating the cache function
payload
TPayload
required
The handler payload used for cache key resolution
handlerCache
CacheOptions
Default cache options from handler configuration
callCache
CacheOptions
Override cache options from handler call
cache
CacheFn
A cache function for memoizing operations

CacheFn

The cache function signature returned by createCache.
type CacheFn = <TValue = unknown>(
  key: CacheKey,
  callback: CacheCallback<TValue>,
  options?: CacheCallbackOptions
) => Promise<TValue>;
key
CacheKey
required
Cache key as a string or array of strings
// String key
await cache('user-profile', async () => {...});

// Array key (joined with ':')
await cache(['user', userId, 'profile'], async () => {...});
callback
CacheCallback<TValue>
required
Function to execute if cache miss occurs
type CacheCallback<TValue> = () => Promise<TValue> | TValue;
options
CacheCallbackOptions
Additional options for this cache operation
ttl
number
Time-to-live in milliseconds
Additional properties are passed through to the store implementation
value
TValue
The cached or computed value

CacheOptions

Configuration options for cache behavior.
interface CacheOptions<TPayload, TStore extends CacheStore> {
  key?: CacheKeyInput<TPayload>;
  store: TStore;
  wrapHandler?: boolean;
}
key
CacheKeyInput
Base cache key or resolver function
// Static key
key: ['api', 'users']

// Dynamic key resolver
key: ({ payload }) => ['user', payload.userId]
store
CacheStore
required
Cache store implementation
wrapHandler
boolean
default:false
If true, caches the entire handler response instead of individual operations

CacheStore

Interface for implementing custom cache stores.
interface CacheStore {
  get<TValue = unknown>(key: string): Promise<TValue | undefined> | TValue | undefined;
  set<TValue = unknown>(
    key: string,
    value: TValue,
    options?: CacheCallbackOptions
  ): Promise<void> | void;
  delete?(key: string): Promise<boolean | void> | boolean | void;
}
get
(key: string) => Promise<TValue | undefined>
required
Retrieve a value from the cache
key
string
required
The cache key
value
TValue | undefined
The cached value, or undefined if not found
set
(key: string, value: TValue, options?) => Promise<void>
required
Store a value in the cache
key
string
required
The cache key
value
TValue
required
The value to cache
options
CacheCallbackOptions
Additional options (e.g., TTL)
delete
(key: string) => Promise<boolean | void>
Optional method to delete a cached value
key
string
required
The cache key
deleted
boolean | void
Whether the key was deleted

CacheKeyResolver

Function type for dynamically resolving cache keys.
type CacheKeyResolver<TPayload = unknown> = (
  data: CacheKeyResolverData<TPayload>
) => CacheKey;
data
CacheKeyResolverData
required
payload
TPayload
required
The handler payload
key
CacheKey
The resolved cache key (string or string array)

Utility Functions

resolveCacheOptions

Merges handler and call cache options.
import { resolveCacheOptions } from '@apisr/controller';

const resolved = resolveCacheOptions({
  handlerCache: { store: myStore, key: ['base'] },
  callCache: { wrapHandler: true },
});
input
object
required
handlerCache
CacheOptions
Handler-level cache options
callCache
CacheOptions
Call-level cache options (overrides handler options)
options
CacheOptions | undefined
Merged cache options, or undefined if no store is configured

resolveCacheKey

Resolves a cache key from inputs.
import { resolveCacheKey } from '@apisr/controller';

const key = resolveCacheKey({
  key: ['user', 'profile'],
  payload: { userId: '123' },
  baseKey: ['api'],
});
// Result: 'api:user:profile'
input
object
required
key
CacheKey
required
The cache key
payload
TPayload
required
The handler payload
baseKey
CacheKeyInput
Base key to prepend
wrapHandler
boolean
Whether wrapping the entire handler
key
string
The resolved cache key string (parts joined with :)

Usage Examples

Handler-Level Caching

import { createHandler } from '@apisr/controller';
import Keyv from 'keyv';

const store = new Keyv();

const handler = createHandler({
  cache: {
    store,
    wrapHandler: true,
    key: ({ payload }) => ['user', payload.userId],
  },
});

const getUser = handler(
  async ({ payload }) => {
    // This entire handler will be cached
    return await db.users.findById(payload.userId);
  },
  {
    payload: { userId: s.params('id') },
  }
);

Operation-Level Caching

const getUser = handler(
  async ({ payload, cache }) => {
    const profile = await cache(
      ['profile', payload.userId],
      async () => {
        return await db.profiles.findByUserId(payload.userId);
      },
      { ttl: 60000 } // 1 minute TTL
    );
    
    const posts = await cache(
      ['posts', payload.userId],
      async () => {
        return await db.posts.findByUserId(payload.userId);
      },
      { ttl: 30000 } // 30 seconds TTL
    );
    
    return { profile, posts };
  },
  {
    payload: { userId: s.params('id') },
    cache: {
      store,
      key: ['user-data'],
    },
  }
);

Custom Cache Store

import { type CacheStore } from '@apisr/controller';

class RedisStore implements CacheStore {
  constructor(private redis: Redis) {}
  
  async get<TValue>(key: string): Promise<TValue | undefined> {
    const value = await this.redis.get(key);
    return value ? JSON.parse(value) : undefined;
  }
  
  async set<TValue>(
    key: string,
    value: TValue,
    options?: { ttl?: number }
  ): Promise<void> {
    const serialized = JSON.stringify(value);
    if (options?.ttl) {
      await this.redis.setex(key, Math.floor(options.ttl / 1000), serialized);
    } else {
      await this.redis.set(key, serialized);
    }
  }
  
  async delete(key: string): Promise<boolean> {
    const result = await this.redis.del(key);
    return result > 0;
  }
}

const redisStore = new RedisStore(redis);

const handler = createHandler({
  cache: {
    store: redisStore,
  },
});

Dynamic Cache Keys

const getOrganizationData = handler(
  async ({ payload, cache }) => {
    const data = await cache(
      ['org', payload.orgId, 'data', payload.dataType],
      async () => {
        return await fetchOrganizationData(payload.orgId, payload.dataType);
      }
    );
    
    return data;
  },
  {
    payload: {
      orgId: s.params('orgId'),
      dataType: s.query('type'),
    },
    cache: {
      store,
      key: ({ payload }) => ['org', payload.orgId],
    },
  }
);

Build docs developers (and LLMs) love