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

# Cache

> Cache data behind a single ICache interface with filesystem or Redis backends.

The `@ooneex/cache` component is a multi-backend caching layer. Every backend implements the same `ICache` interface — `get`, `set`, `delete`, `deleteByPrefix`, `has`, `clear` — so you can start on the filesystem and move to Redis without changing a single call site. Values are JSON-serialized automatically and support TTL expiration.

## Why this component

* **One interface, many backends.** Filesystem and Redis both implement `ICache` — swap backends without touching callers.
* **Type-safe values.** `get<T>()` and `set<T>()` carry your value type through serialization.
* **TTL built in.** Set a per-key time-to-live in seconds; expired entries return `undefined`.
* **Bulk operations.** `deleteByPrefix()` clears related keys in one call; `clear()` empties the cache.
* **Container-managed.** Register a cache class with a decorator and resolve it from the container.

## How it works

You pick (or implement) a backend and register it. Reads and writes go through the `ICache` methods; the backend handles serialization, namespacing, and expiration.

| Method                     | Purpose                                                     |
| -------------------------- | ----------------------------------------------------------- |
| `get<T>(key)`              | Return the value or `undefined` if missing/expired.         |
| `set<T>(key, value, ttl?)` | Store a value, optionally expiring after `ttl` seconds.     |
| `delete(key)`              | Remove one key; returns whether it existed.                 |
| `deleteByPrefix(prefix)`   | Remove every key starting with `prefix`; returns the count. |
| `has(key)`                 | Whether a live (non-expired) key exists.                    |
| `clear()`                  | Remove everything.                                          |

The built-in backends differ in where data lives, not in how you call them:

| Backend           | Best for                                  | Distribution       |
| ----------------- | ----------------------------------------- | ------------------ |
| `FilesystemCache` | Local development, single-instance caches | One machine (disk) |
| `RedisCache`      | Production caches shared across instances | Shared (TCP)       |

A TTL of `0` (or omitted) means the entry never expires. Redis backends namespace every key (default `cache:`) to avoid collisions on a shared instance.

## Environment variables

| Variable          | Backend      | Required                      | Purpose                                                                                             |
| ----------------- | ------------ | ----------------------------- | --------------------------------------------------------------------------------------------------- |
| `CACHE_REDIS_URL` | `RedisCache` | Yes (unless passed in config) | Connection string, e.g. `redis://localhost:6379`. Missing throws `CacheException` (`URL_REQUIRED`). |

`FilesystemCache` needs no environment variables; it defaults to a `.cache` directory in the working directory.

```bash theme={null}
CACHE_REDIS_URL=redis://localhost:6379
```

## Usage

The API is the same regardless of backend.

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

const cache = container.get(RedisCache);

// Store with a 5-minute TTL
await cache.set("user:123", { name: "John", age: 30 }, 300);

// Read back with a type
const user = await cache.get<{ name: string; age: number }>("user:123");

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

// Bulk removal by prefix
const removed = await cache.deleteByPrefix("user:"); // number cleared
```

A common pattern is read-through caching — serve from cache, fall back to the source, then populate:

```typescript theme={null}
const key = `user:${id}`;
let user = await cache.get<User>(key);

if (!user) {
  user = await userRepository.findById(id);
  await cache.set(key, user, 300); // cache for 5 minutes
}
```

Invalidate on writes so callers never read stale data:

```typescript theme={null}
await userRepository.update(id, data);
await cache.delete(`user:${id}`);
await cache.deleteByPrefix(`user:${id}:`); // related derived keys
```

## Decorator and usage

### `@decorator.cache()`

Registers a cache class with the container. It accepts an optional scope (defaults to singleton). Use it to register a built-in backend under your own name, or a custom backend that implements `ICache`.

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

@decorator.cache()
export class SessionCache extends RedisCache {}
```

Resolve it from the container and inject it where needed:

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

export class SessionService {
  constructor(@inject(SessionCache) private readonly cache: SessionCache) {}
}
```

## Exceptions

The component throws `CacheException` when a backend is misconfigured or an operation fails. It carries a machine-readable `key`, a human-readable `message`, and a `data` object.

| Key                 | When                                                                       |
| ------------------- | -------------------------------------------------------------------------- |
| `URL_REQUIRED`      | `RedisCache` is created without a connection string or `CACHE_REDIS_URL`.  |
| `MAX_SIZE_EXCEEDED` | A `FilesystemCache` value exceeds the maximum file size (10MB by default). |

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

try {
  await cache.set("key", value);
} catch (error) {
  if (error instanceof CacheException) {
    logger.error(`Cache error [${error.key}]: ${error.message}`, error.data);
  } else {
    throw error;
  }
}
```

## Best practices

* **Namespace keys.** Use a stable scheme like `user:123` and `session:abc` so `deleteByPrefix()` can target related entries.
* **Always set a TTL for volatile data.** Cache derived or remote data with an explicit `ttl` so it can't go stale indefinitely.
* **Invalidate on write.** Delete (or overwrite) the key whenever the source changes; pair `delete()` with `deleteByPrefix()` for derived keys.
* **Pick the backend per environment.** Filesystem for local/dev, Redis for shared production — the code stays identical.
* **Keep values serializable.** Stored values round-trip through JSON; avoid class instances, functions, and circular references.
* **Treat the cache as optional.** On a miss, fall back to the source of truth — never assume a key is present.
* **Throw `CacheException` with a stable `key`.** In custom backends, keep keys constant and put variable detail in `data`.

## CLI command

Scaffold a cache adapter and its test file with the generator. It writes the class under `modules/<module>/src/cache/<Name>Cache.ts` and installs `@ooneex/cache` if it is missing.

```bash theme={null}
# Interactive: prompts for the name
ooneex cache:create

# Provide the name
ooneex cache:create --name=Session

# Target a module and overwrite
ooneex cache:create --name=Session --module=auth --override
```

| Option       | Description                                                     | Default             |
| ------------ | --------------------------------------------------------------- | ------------------- |
| `--name`     | Cache class name. The `Cache` suffix is appended automatically. | Prompted if omitted |
| `--module`   | Target module the class is generated into.                      | `shared`            |
| `--override` | Overwrite an existing class without prompting.                  | `false`             |

The generated class starts as an `ICache` stub, ready for you to implement each method against your backend:

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

@decorator.cache()
export class SessionCache implements ICache {
  public async get<T = unknown>(key: string): Promise<T | undefined> {
    throw new CacheException(`Failed to get key "${key}": Not implemented`);
  }

  public async set<T = unknown>(key: string, value: T, ttl?: number): Promise<void> {
    throw new CacheException(`Failed to set key "${key}": Not implemented`);
  }

  public async delete(key: string): Promise<boolean> {
    throw new CacheException(`Failed to delete key "${key}": Not implemented`);
  }

  public async has(key: string): Promise<boolean> {
    throw new CacheException(`Failed to check if key "${key}" exists: Not implemented`);
  }
}
```

See [cache:create](/cli/commands/cache-create) for the full command reference.

## Use with Claude and Codex

The generator ships a matching `cache:create` skill. It runs the scaffold and then guides your AI agent through completing the adapter — implementing `get`, `set`, `delete`, and `has` against a real backend and wiring up its client. Initialize the skills once for your agent:

<Tabs>
  <Tab title="Claude">
    ```bash theme={null}
    ooneex claude:init
    ```

    Then ask Claude in natural language — it maps the request to the generator, runs it, and fills in the implementation:

    ```text Prompt icon="terminal" wrap theme={null}
    Create a cache adapter for user sessions backed by Redis with a 30 minute TTL.
    ```
  </Tab>

  <Tab title="Codex">
    ```bash theme={null}
    ooneex codex:init
    ```

    Then ask Codex in natural language — it maps the request to the generator, runs it, and fills in the implementation:

    ```text Prompt icon="terminal" wrap theme={null}
    Create a cache adapter for user sessions backed by Redis with a 30 minute TTL.
    ```
  </Tab>
</Tabs>

For example, the prompt above maps to `cache:create --name=Session`, then implements the `ICache` methods against a Redis client.
