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

# Upstash Cache

> A serverless Redis cache backed by Upstash, behind the framework's ICache interface.

`UpstashRedisCache` is the [Cache](/components/cache) component's serverless backend. It talks to [Upstash Redis](https://upstash.com) over its HTTP REST API — no persistent TCP connection — which makes it a natural fit for edge and serverless runtimes. It implements the same `ICache` interface as every other backend, so you can develop against the filesystem or a local Redis and switch to Upstash in production without changing a single call site.

## Why Upstash

* **Serverless-friendly.** Connects over HTTP REST, so it works in edge functions and short-lived runtimes where TCP pooling is awkward.
* **Same `ICache` interface.** `get`, `set`, `delete`, `has`, `deleteByPrefix`, and `clear` — identical to the filesystem and Redis backends.
* **Namespaced keys.** Every key is prefixed with a namespace (default `cache`) so multiple apps can share one Upstash database safely.
* **Prefix operations.** `deleteByPrefix()` and `clear()` use cursor-based `SCAN` plus pipelined deletes to stay efficient on large keyspaces.
* **Container-managed.** Registered with `@decorator.cache()` and resolved from the container.

## Installation

`UpstashRedisCache` ships with `@ooneex/cache` and depends on the Upstash Redis client.

```bash theme={null}
bun add @ooneex/cache @upstash/redis
```

## Environment variables

| Variable                         | Required | Purpose                                                                        |
| -------------------------------- | -------- | ------------------------------------------------------------------------------ |
| `CACHE_UPSTASH_REDIS_REST_URL`   | Yes      | Upstash Redis REST URL. Missing throws `CacheException` (`CONFIG_REQUIRED`).   |
| `CACHE_UPSTASH_REDIS_REST_TOKEN` | Yes      | Upstash Redis REST token. Missing throws `CacheException` (`CONFIG_REQUIRED`). |

```bash theme={null}
CACHE_UPSTASH_REDIS_REST_URL=https://your-db.upstash.io
CACHE_UPSTASH_REDIS_REST_TOKEN=your_rest_token
```

Both can also be passed through constructor options (`url`, `token`), which take precedence over the environment. Either way they are validated when the cache is constructed, so misconfiguration fails fast.

## Options

`UpstashRedisCache` accepts an options object as its second constructor argument:

| Option      | Type     | Default                          | Purpose                                                 |
| ----------- | -------- | -------------------------------- | ------------------------------------------------------- |
| `namespace` | `string` | `"cache"`                        | Key prefix; every key is stored as `<namespace>:<key>`. |
| `url`       | `string` | `CACHE_UPSTASH_REDIS_REST_URL`   | Override the REST URL.                                  |
| `token`     | `string` | `CACHE_UPSTASH_REDIS_REST_TOKEN` | Override the REST token.                                |

## How it works

Keys are namespaced before they hit Redis: `set("user:1", …)` with the default namespace writes `cache:user:1`. Reads, writes, and existence checks map straight onto Upstash commands, while the bulk operations iterate the keyspace with `SCAN` and delete matches in a pipeline.

| Method                     | Purpose                                                                          |
| -------------------------- | -------------------------------------------------------------------------------- |
| `get<T>(key)`              | Read a value, or `undefined` if absent.                                          |
| `set<T>(key, value, ttl?)` | Write a value; `ttl` (seconds) sets an expiry via `EX`.                          |
| `delete(key)`              | Delete one key; returns whether a key was removed.                               |
| `has(key)`                 | Whether a key exists.                                                            |
| `deleteByPrefix(prefix)`   | `SCAN` + pipelined delete of `<namespace>:<prefix>*`; returns the count deleted. |
| `clear()`                  | `SCAN` + pipelined delete of every key under the namespace.                      |

## Usage

The API is identical to every other cache backend.

```typescript theme={null}
import { container } from "@ooneex/container";
import { UpstashRedisCache } from "@ooneex/cache";

const cache = container.get(UpstashRedisCache);

// Write with a 5-minute TTL
await cache.set("user:123", { id: 123, name: "Ada" }, 300);

// Read it back
const user = await cache.get<{ id: number; name: string }>("user:123");

// Existence and removal
await cache.has("user:123"); // true
await cache.delete("user:123"); // true

// Bulk operations
await cache.deleteByPrefix("user:"); // delete every user:* key, returns the count
await cache.clear(); // empty the whole namespace
```

To run several isolated caches against one Upstash database, give each its own namespace:

```typescript theme={null}
import { decorator } from "@ooneex/cache";
import { UpstashRedisCache } from "@ooneex/cache";
import { AppEnv } from "@ooneex/app-env";
import { inject } from "@ooneex/container";

@decorator.cache()
export class SessionCache extends UpstashRedisCache {
  constructor(@inject(AppEnv) env: AppEnv) {
    super(env, { namespace: "sessions" });
  }
}
```

## Use in the app

In an `@ooneex/app` application, pass `UpstashRedisCache` to the `cache` slot of your `App` config. The framework registers it and exposes it as the `"cache"` container constant.

```bash theme={null}
bun add @ooneex/app @ooneex/cache @upstash/redis
```

```typescript theme={null}
import { App } from "@ooneex/app";
import { UpstashRedisCache } from "@ooneex/cache";
import { TerminalLogger } from "@ooneex/logger";

const app = new App({
  routing: { prefix: "/api" },
  loggers: [TerminalLogger],
  cache: UpstashRedisCache,
});

await app.run();
```

With a cache configured, any route that declares a `cache` key has its response cached automatically — the framework builds the cache key from the route's cache prefix, HTTP method, URL, and the authenticated user's id:

```typescript theme={null}
import { Route } from "@ooneex/routing";

@Route.get("/api/products", {
  name: "products",
  version: 1,
  description: "List products",
  cache: "products", // response cached through the configured UpstashRedisCache
})
export class ProductsController {
  public async index(context: ContextType<ProductsRouteType>) {
    return context.response.json(await this.products.findAll());
  }
}
```

You can also reach the same instance anywhere for manual caching — inject it or read the constant:

```typescript theme={null}
import { container } from "@ooneex/container";
import type { ICache } from "@ooneex/cache";

const cache = container.getConstant<ICache>("cache"); // the app's configured cache
```

Set `CACHE_UPSTASH_REDIS_REST_URL` and `CACHE_UPSTASH_REDIS_REST_TOKEN` in your `.env.yml` (or environment) so the cache can connect.

## Exceptions

`UpstashRedisCache` throws `CacheException` on misconfiguration, carrying a machine-readable `key`.

| Key               | When                                                                    |
| ----------------- | ----------------------------------------------------------------------- |
| `CONFIG_REQUIRED` | The REST URL or token is missing from both options and the environment. |

```typescript theme={null}
import { CacheException } from "@ooneex/cache";

try {
  const cache = container.get(UpstashRedisCache);
} catch (error) {
  if (error instanceof CacheException && error.key === "CONFIG_REQUIRED") {
    logger.error("Upstash cache is misconfigured", error.data);
  }
  throw error;
}
```

## Best practices

* **Set a TTL for volatile data.** Pass `ttl` (seconds) on `set()` so transient values expire instead of accumulating.
* **Namespace per concern.** Use distinct namespaces (sessions, rate counters, query cache) so `clear()` and `deleteByPrefix()` only touch what you intend.
* **Use structured key prefixes.** Group related keys like `user:123:profile` so `deleteByPrefix("user:123:")` can invalidate them together.
* **Keep credentials in the environment.** Load the REST URL and token from `.env`; never hard-code them.
* **Develop locally, deploy on Upstash.** Use the filesystem or local Redis backend in development and switch to Upstash in production — the `ICache` calls stay identical.

See the [Cache component](/components/cache) for the full interface and the other backends.
