Invalidation & Expiration

A cached function returned by defineCachedFunction exposes three methods for purging or refreshing its entries: .invalidate(), .expire(), and .resolveKeys(). Each takes the same arguments as the cached function. Standalone helpers cover the case where you don't have a reference to the cached function.

Invalidate

.invalidate() removes cached entries across all base prefixes. The next call re-invokes the function and waits for a fresh value.

import { defineCachedFunction } from "ocache";

const getUser = defineCachedFunction(async (id: string) => db.users.find(id), {
  name: "getUser",
  maxAge: 60,
  getKey: (id) => id,
});

await getUser("user-123"); // resolves and caches

// e.g. after the user updates their profile
await getUser.invalidate("user-123");

await getUser("user-123"); // re-invokes the function, waits for the fresh value

Use the standalone invalidateCache() when you don't hold a reference to the cached function — for example from a webhook or a different module. Pass the same key options (name, getKey, and group / base if you customized them):

import { invalidateCache } from "ocache";

await invalidateCache({
  options: { name: "getUser", getKey: (id: string) => id },
  args: ["user-123"],
});
The standalone helpers rebuild the cache key from the options you pass — they can't see the original function. They must match the name, getKey, group, and base you cached with, or they'll target a different key and no-op.

Expire (SWR refresh)

Where .invalidate() removes an entry entirely (the next call must wait for a fresh value), .expire() only marks it stale. With SWR enabled, the stale value keeps being served — still bounded by the originally configured staleMaxAge window — while the next access triggers a background refresh:

const getUser = defineCachedFunction(async (id: string) => db.users.find(id), {
  name: "getUser",
  maxAge: 60,
  staleMaxAge: 300,
  swr: true, // required to actually serve stale values
  getKey: (id) => id,
});

// Next reader gets the stale value instantly; a refresh runs in the background
await getUser.expire("user-123");
Without swr: true, an expired entry is re-resolved in the foreground, so .expire() behaves much like .invalidate() for the next reader (they wait for a fresh value).

The standalone expireCache() mirrors invalidateCache(). Pass the same maxAge / swr / staleMaxAge options you cache with, so the entry's remaining storage TTL is preserved rather than extended:

import { expireCache } from "ocache";

await expireCache({
  options: {
    name: "getUser",
    getKey: (id: string) => id,
    maxAge: 60,
    swr: true,
    staleMaxAge: 300,
  },
  args: ["user-123"],
});
Prefer .expire() when you want a smooth refresh with no blocking gap for readers. Reach for .invalidate() only when the next reader must get a guaranteed-fresh value.

Resolving keys

For advanced use cases — inspecting entries, custom storage operations — .resolveKeys() returns the raw storage keys for a set of arguments (one per base prefix):

const keys = await getUser.resolveKeys("user-123");
// ["/cache:functions:getUser:user-123.json"]

The standalone resolveCacheKeys() does the same without a function reference (again, pass matching key options):

import { resolveCacheKeys, useStorage } from "ocache";

const keys = await resolveCacheKeys({
  options: { name: "getUser", getKey: (id: string) => id },
  args: ["user-123"],
});

for (const key of keys) {
  const entry = await useStorage().get(key);
  // ...inspect or manipulate the raw entry
}

See Storage for the underlying store, and HTTP Handlers for invalidating cached responses.