# Action Cache Expensive computation caching - Package: @bijectionhq/action-cache - Category: Database & Backend - Status: Preview - License: Apache-2.0 - Bijection source (repository access required): https://github.com/bijectionhq/action-cache/tree/9eeb5ad93e1d86f60abc00ef8fda23a67bc370d2 - Upstream: https://github.com/convex/action-cache/tree/ea75a979352847a73900d5496de434b691ec1a04 - Documentation: https://docs.bijection.com/components/using - Directory: https://bijection.com/components/action-cache ## Get the source Bijection repository access is required. Request access at https://bijection.com/contact. ```sh git clone https://github.com/bijectionhq/action-cache.git cd action-cache git checkout 9eeb5ad93e1d86f60abc00ef8fda23a67bc370d2 ``` ## Before using this component Read the package README at the recorded revision for setup, dependencies, and API examples. Upstream documentation describes the original package and may differ from the Bijection adaptation. This is a source candidate for evaluation. Catalog inclusion does not establish npm availability or runtime qualification. Check compatibility with your SDK and deployment before adoption. External providers run their own compute. ## Upstream package documentation Recorded 2026-10-23. This describes @convex-dev/action-cache; check the Bijection adaptation separately. Action Cache stores the results of expensive Convex actions like third-party API calls and AI operations in a persistent cache with configurable TTL expiration. The cache automatically handles cleanup through daily cron jobs and uses the action name plus arguments as the cache key. It prevents redundant API calls and reduces costs for operations like OpenAI embeddings, slow database queries, or external service requests. ### Use cases • Cache OpenAI embedding calculations for vector search to reduce API costs and latency on repeated queries • Store responses from slow third-party APIs like weather services, payment processors, or analytics platforms • Cache expensive data transformations or machine learning inference results that don't change frequently • Reduce redundant calls to rate-limited external services by persisting successful responses • Store computed results from complex business logic that involves multiple external API calls ### How it works The component installs as a Convex component using `app.use()` in your `convex.config.ts` file. You create an `ActionCache` instance that wraps an existing internal action, specifying the action reference, an optional name for cache versioning, and TTL in milliseconds. Calling `cache.fetch(ctx, args)` first checks for cached results using the action name and arguments as the key. On cache miss, it executes the wrapped action and stores the result. The `force` option bypasses the cache to update stale entries, useful for cron-based cache warming. Cache management includes `remove()` for single entries, `removeAllForName()` for clearing a specific cache version, and `removeAll()` for complete cleanup. Expired entries are automatically deleted during retrieval and via daily background jobs. ### Upstream README Sometimes your app needs to fetch information from a third-party API that is slow or costs money. Caching can help! This is a Convex component that can cache the results of expensive functions and set an optional TTL. Expired entries are cleaned up via a cron job once a day. The cache key is the `ActionCache`'s name (defaults to function name) and the arguments to the action that generates the cache values. ```ts import { action } from "./_generated/server"; import { components } from "./_generated/api"; import { ActionCache } from "@convex-dev/action-cache"; const cache = new ActionCache(components.actionCache, { action: internal.example.myExpensiveAction, }); export const myFunction = action({ // NOTE: If we're returning the result, we need explicitly type the return value. // Refer to https://docs.convex.dev/functions/actions#dealing-with-circular-type-inference for more info. handler: async (ctx, args): Promise<{ text: string }> => { // Call it with the parameters to `myExpensiveAction` const result = await cache.fetch(ctx, { foo: "bar" }); // Do something with the result or just return it return result; }, }); export const myExpensiveAction = internalAction({ args: { foo: v.string() }, handler: async (ctx, args): Promise<{ text: string }> { const data = await generateLLMResponse(ctx, args); return data; } }) ``` To invalidate the cache, you can set a new name explicitly and/or clear cached values by name. ## Pre-requisite: Convex You'll need an existing Convex project to use the component. Convex is a hosted backend platform, including a database, serverless functions, and a ton more you can learn about [here](https://docs.convex.dev/get-started). Run `npm create convex` or follow any of the [quickstarts](https://docs.convex.dev/home) to set one up. ## Installation Install the component package: ```bash npm install @convex-dev/action-cache ``` Create a `convex.config.ts` file in your app's `convex/` folder and install the component by calling `use`: ```ts // convex/convex.config.ts import { defineApp } from "convex/server"; import cache from "@convex-dev/action-cache/convex.config.js"; const app = defineApp(); app.use(cache); export default app; ``` Finally, create a new `ActionCache` with optional name and expiration within your Convex project, and point it to the installed component. - The `name` field can be used for identifying the function or version being used to create the values in the cache and can also be used for grouping entries to remove. - The `ttl` (Time-To-Live) field determines how long the cache entries are valid, in milliseconds. - If no `ttl` is provided, the cache entries are kept indefinitely. - If an `ttl` is provided, expired cache entries are deleted when they are retrieved and in a daily cron job. ```ts import { ActionCache } from "@convex-dev/action-cache"; import { components } from "./_generated/api"; const cache = new ActionCache(components.actionCache, { action: internal.example.myExpensiveAction, name: "myExpensiveActionV1", ttl: 1000 * 60 * 60 * 24 * 7, // 7 days }); ``` ## Example Suppose you're building an app that uses [vector search](https://docs.convex.dev/search/vector-search). Calculating embeddings is often expensive - in our case, we are using OpenAI's API which adds latency to every search and costs money to use. We can reduce the number of API calls by caching the results! Start by defining the [Convex action](https://docs.convex.dev/functions/actions) that calls the API to create embeddings. Feel free to substitute your favorite embeddings API. You may need to adjust the vector dimensions in the schema in `example/schema.ts` accordingly. Set your API key [environment variable](https://docs.convex.dev/production/environment-variables) ```bash npx convex env set OPENAI_API_KEY ``` ```ts export const embed = internalAction({ args: { text: v.string() }, handler: async (_ctx, { text }) => { const apiKey = process.env.OPENAI_API_KEY; if (!apiKey) { throw new Error("OPENAI_API_KEY environment variable not set!"); } const req = { input: text, model: "text-embedding-ada-002" }; const resp = await fetch("https://api.openai.com/v1/embeddings", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}`, }, body: JSON.stringify(req), }); if (!resp.ok) { const msg = await resp.text(); throw new Error(`OpenAI API error: ${msg}`); } const json = await resp.json(); const vector = json["data"][0]["embedding"]; console.log(`Computed embedding of "${text}": ${vector.length} dimensions`); return vector as number[]; }, }); ``` Create the embeddings cache: ```ts const embeddingsCache = new ActionCache(components.actionCache, { action: internal.example.embed, name: "embed-v1", }); ``` Use the cache when you run a vector search: ```ts export const vectorSearch = action({ args: { query: v.string(), cuisines: v.array(v.string()) }, handler: async (ctx, args) => { const embedding = await embeddingsCache.fetch(ctx, { text: args.query, }); const results = await ctx.vectorSearch("foods", "by_embedding", { vector: embedding, limit: 16, filter: (q) => q.or(...args.cuisines.map((cuisine) => q.eq("cuisine", cuisine))), }); const rows: SearchResult[] = await ctx.runQuery( internal.example.fetchResults, { results }, ); return rows; }, }); ``` ### Defining multiple caches You can use the same component for multiple actions, or multiple versions of the same action. You can specify a custom `name` argument to denote which cache you want to use, or change the name to start fresh, like `embed-v2`. If the return value changes, it is important to change the name so you don't get unexpected values. ### Updating the cache before it expires It's convenient to lazily run the action when there is a cache miss. However, this means that some requests will be slow, and many competeing requests may race to all run the action and populate the cache. You can avoid this by calling `fetch` with the `force` option. This will force the action to run and update the cache, even if there is a cache hit. In the meantime, other requests will get a cache hit. If you call this from a cron job, the cache will always have an entry (unless the action fails). ### Clearing values To clear old values, you can: 1. Remove one entry by arguments. ```ts await embeddingsCache.remove(ctx, { text: "target text" }); ``` 2. Remove all entries for the current name (defaults to function name). This is useful if you updated the implementation and want to clear ```ts await embeddingsCache.removeAllForName(ctx); ``` 3. Remove all entries in the component, including all names. ```ts await embeddingsCache.removeAll(ctx); ``` See more example usage in [example.ts](./example/convex/example.ts).