Whisper

Caching

Per-method TTL caching with pluggable adapters

Default Behavior

Whisper includes an in-memory cache that is enabled by default. GET requests are cached based on route, path, and query parameters. The default TTL is 300 seconds (5 minutes).

import { createClient } from '@wardbox/whisper/core';

// Caching is active by default (in-memory, 300s TTL)
const client = createClient({
  apiKey: 'RGAPI-your-key-here',
});

Cache keys include a hash of your API key to prevent cross-key cache pollution when rotating keys.

TTL Configuration

Configure TTLs per API path pattern using the cacheTtl option. Rather than a separate byMethod map, patterns are top-level keys on the config object. Pattern matching checks if the request path contains the pattern string:

import { createClient } from '@wardbox/whisper/core';

const client = createClient({
  apiKey: 'RGAPI-your-key-here',
  cacheTtl: {
    default: 60, // 60 seconds for everything else
    '/lol/summoner/v4': 300, // 5 minutes for summoner data
    '/lol/match/v5': 30, // 30 seconds for match data
    '/lol/spectator/v5': 0, // Never cache live game data
    '/lol/league/v4': 120, // 2 minutes for league data
  },
});

A TTL of 0 means "do not cache" -- the request always hits the API. This is the correct default for live game data like spectator endpoints, where stale data is actively harmful.

Custom Cache Adapter

Replace the built-in memory cache with any storage backend by implementing the CacheAdapter interface:

import type { CacheAdapter } from '@wardbox/whisper/core';

const redisCache: CacheAdapter = {
  async get<T>(key: string): Promise<T | undefined> {
    const data = await redis.get(key);
    return data ? JSON.parse(data) : undefined;
  },

  async set<T>(key: string, value: T, ttlSeconds: number): Promise<void> {
    await redis.set(key, JSON.stringify(value), 'EX', ttlSeconds);
  },

  async delete(key: string): Promise<void> {
    await redis.del(key);
  },

  async has(key: string): Promise<boolean> {
    return (await redis.exists(key)) === 1;
  },
};

Pass it to the client:

import { createClient } from '@wardbox/whisper/core';

const client = createClient({
  apiKey: 'RGAPI-your-key-here',
  cache: redisCache,
});

Disabling the Cache

To skip caching entirely:

const client = createClient({
  apiKey: 'RGAPI-your-key-here',
  cache: false,
});

API-Key-Aware Cache Keys

Cache keys include a short hash derived from your API key. This prevents a subtle bug: if you rotate API keys and the old key's cached responses are served to the new key, Riot may flag the mismatch.

The key itself is never stored in the cache -- only a djb2 hash prefix. This works in all runtimes (no crypto module needed) and is deterministic across requests.

Cache key format: {key-hash}:{route}:{path}?{sorted-params}
Example:          k8f2a:na1:/lol/summoner/v4/summoners/by-puuid/xyz

Cache Behavior

  • Only GET requests are cached. POST, PUT, and DELETE always hit the API.
  • The cache is checked before the middleware pipeline runs -- cached responses skip middleware entirely for performance.
  • Expired entries are lazily evicted on access, not on a timer.

On this page