> ## Documentation Index
> Fetch the complete documentation index at: https://kweri.uchenna.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Caching

> How kweri stores, ages, and invalidates data.

## Cache entry lifecycle

Every query result is stored as a **cache entry** keyed by `METHOD:path:params`. An entry moves through these states:

```
idle → loading → success → (stale after staleTime) → evicted after cacheTime
                    └→ error → retried or evicted after errorCacheTime
```

| State     | Description                              |
| --------- | ---------------------------------------- |
| `idle`    | No fetch has been attempted yet          |
| `loading` | A fetch is in progress                   |
| `success` | Data is available; may be fresh or stale |
| `error`   | The last fetch failed                    |

## staleTime and cacheTime

These two values control the freshness lifecycle:

```ts theme={null}
const kweri = new Kweri({
  staleTime: 30_000,   // data is fresh for 30 seconds after last fetch
  cacheTime: 300_000,  // keep the entry in memory for 5 minutes after going stale
})
```

* **`staleTime`** — *"when do I refetch?"* How long data stays **fresh**. While fresh, `kweri.query()` returns the cached data without hitting the network. Once stale, the next access refetches in the background. Default: `0` (immediately stale).
* **`cacheTime`** — *"when do I forget?"* How long an entry with **no active observers** is kept in memory before it can be garbage-collected. Default: `5 minutes`.

They are **independent**: `staleTime` governs *freshness* (refetching), `cacheTime` governs *retention* (memory). An entry can be stale but still cached — it's kept around and refetched on next access. Normally `cacheTime ≥ staleTime`.

|               | `staleTime`                       | `cacheTime`                           |
| ------------- | --------------------------------- | ------------------------------------- |
| Question      | When do I refetch?                | When do I forget?                     |
| Applies while | data exists                       | **no** observers are mounted          |
| Expired ⇒     | background refetch on next access | entry eligible for garbage collection |

### Per-query overrides

Freshness is really a property of the *data*, not the instance — a profile and a stock price want very different `staleTime`s. Override per call, taking precedence over the instance defaults:

```ts theme={null}
// Direct
await kweri.query(getStock, { path: { id } }, { staleTime: 1_000 })
await kweri.query(getProfile, {}, { staleTime: 5 * 60_000, cacheTime: 30 * 60_000 })
```

```ts theme={null}
// Via hooks — the third options arg accepts staleTime / cacheTime / maxRetries
const stock = useGet('/stocks/{id}', { path: { id } }, { staleTime: 1_000 })
const profile = useGet('/me', {}, { staleTime: 5 * 60_000 })
```

<Info>
  Overrides are stamped onto the cache entry when it's (re)fetched, so they take effect from that write forward.
</Info>

### Stale-while-revalidate

When a query is called for data that exists in cache but is **stale**:

1. The cached data is returned immediately (no loading state)
2. A background network request is fired to refresh it
3. Subscribers are notified when the fresh data arrives

This means your UI never blocks on network latency for data you've already seen.

## Cache structure

Each entry stores:

```ts theme={null}
interface CacheEntry<T = unknown> {
  data: T | undefined
  status: 'idle' | 'loading' | 'success' | 'error'
  error: CachedError | undefined

  updatedAt: number       // timestamp when data was last set
  staleTime: number       // copied from KweriOptions at fetch time
  cacheTime: number       // copied from KweriOptions at fetch time

  errorUpdatedAt: number  // timestamp when error was last set
  errorCacheTime: number  // how long to keep the error (default: 5s)
  retryCount: number
}
```

### isFresh

Data is considered fresh when:

```
updatedAt !== 0  AND  now < updatedAt + staleTime
```

If `staleTime` is `0` (the default), data is **always** stale and a background refetch will always fire when the query is called.

## Invalidation

Invalidation marks an entry as stale **without removing it**. The cached data is still returned immediately; a refetch fires in the background.

```ts theme={null}
// Invalidate a specific query
kweri.invalidateQuery(getUsers, {})

// Invalidate all queries whose cache key contains '/users'
kweri.invalidateByPath('/users')

// Invalidate with a regex
kweri.invalidateByPath(/\/users\/\d+/)
```

<Tip>
  `invalidateByPath` is the most common pattern after a mutation — it catches all variants (e.g., `/users`, `/users/1`, `/users?page=2`) without you needing to enumerate every param combination.
</Tip>

## Cache removal

Removal deletes the entry from memory entirely. The next query call starts from scratch.

```ts theme={null}
kweri.removeQuery(getUsers, {})
```

## Direct cache manipulation

You can read and write the cache directly without going through the network — useful for optimistic updates:

```ts theme={null}
// Read cached data
const users = kweri.getCachedData(getUsers, {})

// Write data directly into cache
kweri.setCachedData(getUsers, {}, [...users, newUser])
```

<Warning>
  Data written with `setCachedData` bypasses schema validation. It is marked as a `success` entry with `updatedAt` set to now, so it will remain fresh for `staleTime` milliseconds.
</Warning>

## Error caching

Errors are also cached, but with a much shorter lifetime (default: **5 seconds**). This prevents retry storms while still allowing the UI to recover quickly.

When an error entry expires, the next call to `kweri.query()` will attempt a fresh fetch.
