# Invalidation & Expiration

> 

A cached function returned by [`defineCachedFunction`](/guide/functions) 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.

```ts
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):

```ts
import { invalidateCache } from "ocache";

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

<important>

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.

</important>

## 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:

```ts
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");
```

<note>

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).

</note>

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:

```ts
import { expireCache } from "ocache";

await expireCache({
  options: {
    name: "getUser",
    getKey: (id: string) => id,
    maxAge: 60,
    swr: true,
    staleMaxAge: 300,
  },
  args: ["user-123"],
});
```

<tip>

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.

</tip>

## 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):

```ts
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):

```ts
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](/guide/storage) for the underlying store, and [HTTP Handlers](/guide/handler) for invalidating cached responses.
