TypeScriptCaching

Caching

Reuse a recent result instead of running the handler again

Some tools return the same answer for the same input for a while: a product's details, today's exchange rates, the server's configuration. @Cache remembers a handler's result for a number of seconds. When the same call arrives again within that time, NitroStack returns the stored result without running your handler, which saves time and calls to slow or paid services.

import { ToolDecorator as Tool, Cache, ExecutionContext, z } from '@nitrostack/core';

export class ConfigTools {
  @Tool({ name: 'get_config', description: 'Get the application configuration', inputSchema: z.object({}) })
  @Cache({ ttl: 3600 }) // keep the result for one hour
  async getConfig(input: {}, ctx: ExecutionContext) {
    return this.configService.load();
  }
}

@Cache works the same way on resources and prompts.

How it works

Every call is turned into a cache key. If a fresh result is stored under that key, it's returned straight away. Otherwise the handler runs and its result is stored for ttl seconds.

Tool call
Build the cache keyFrom the class, method, and input
Cache hit
Return the stored resultThe handler does not run
Cache miss or expired
Run the handler
Store the resultFor ttl seconds
Hits and misses are logged at the info level, so you can see the cache working in your server logs.

Options

@Cache takes one required option and two optional ones.

OptionTypeDescription
ttlnumberHow long to keep a result, in seconds. Required
key(input, context) => stringBuilds the cache key yourself, instead of from the whole input
storageCacheStorageWhere results are kept. Defaults to memory in the server process

Choosing a TTL

The TTL is a trade-off between freshness and speed. A result can be up to ttl seconds out of date, so choose the longest time your users can accept stale data for.

DataExample TTL
Live prices or stock levels60 (1 minute)
Search results, product listings300 (5 minutes)
Reference data, configuration3600 (1 hour)
Data that changes only on deploy86400 (1 day)

Cache keys

By default, the key is the class name, the method name, and the tool's input as JSON, for example ProductTools:getProduct:{"productId":"p-1"}. Two calls share a result only when their input is identical.

Pass key when only some of the input should matter, or when two different inputs mean the same thing:

@Cache({
  ttl: 600,
  key: (input) => `product:${(input as { productId: string }).productId}`,
})

Cached results are shared between callers

The default key ignores who is calling. If a tool returns data that depends on the caller, such as their recommendations or their orders, include the caller in the key, or one user could receive another user's cached result:

@Cache({
  ttl: 1800,
  key: (input, ctx) => `recommendations:${(ctx as ExecutionContext).auth?.subject}`,
})

Clear the cache

When the underlying data changes, remove the stale entry with clearCache(). Pass a key to remove one entry, or nothing to empty the default in-memory cache.

import { clearCache } from '@nitrostack/core';

@Tool({ name: 'update_product', description: 'Update a product' })
async updateProduct(input: ProductUpdate, ctx: ExecutionContext) {
  const product = await this.products.update(input);
  await clearCache(`product:${product.id}`); // the key used by get_product
  return product;
}

clearCache() acts on the default in-memory storage. If you pass your own storage, clear entries through that store instead.

On this page