Agent index (llms.txt) — fetch this first, then the page it names.

Cache

Distributed caching powered by Olric -- an in-memory key-value store designed for distributed systems. Cache data across the network with automatic partitioning, replication, and sub-millisecond access times.

Overview

Olric distributes cache entries across nodes in the cluster using consistent hashing. Data is automatically partitioned and replicated, so cache lookups are fast and survive individual node failures.

Cache data is organized into distributed maps (dmaps). A dmap is a named keyspace -- think of it as a namespace or a bucket. Every cache operation takes the dmap name as its first parameter, followed by the key.

SDK Setup

typescript
import { createClient } from "@debros/orama";

const client = createClient({
  baseURL: "https://ns-myapp.cluster.example.com",
  apiKey: process.env.ORAMA_API_KEY,
});

The key needs the cache grant (orama namespace keys create --scope invoke,cache --label worker); the app-runtime profile does not include it. A signed-in user's session holds it. See Access control.

Basic Operations

Put

Store a value in a distributed map. Values can be any JSON-serializable type.

typescript
await client.cache.put("sessions", "user:alice", {
  role: "admin",
  loginAt: Date.now(),
});

Get

Retrieve a value from a distributed map. Returns null on a cache miss -- this is normal behavior, not an error.

typescript
const result = await client.cache.get("sessions", "user:alice");

if (result) {
  console.log(result.value); // { role: "admin", loginAt: 1709000000000 }
  console.log(result.key);   // "user:alice"
  console.log(result.dmap);  // "sessions"
}

Delete

Remove a value from a distributed map.

typescript
await client.cache.delete("sessions", "user:alice");

TTL (Time-To-Live)

Set expiration times on cache entries to automatically evict stale data. TTL is specified as a duration string -- for example "1h", "30m", "5s", or "24h".

Pass the TTL as the fourth argument to put:

typescript
// Cache for 1 hour
await client.cache.put("sessions", "user:alice", { role: "admin" }, "1h");

// Cache for 30 minutes
await client.cache.put("rate-limits", "ip:203.0.113.1", { count: 1 }, "30m");

// Cache for 5 seconds (useful for hot-path deduplication)
await client.cache.put("dedup", "request:abc123", true, "5s");

When TTL is omitted (or "0s"), the entry does not expire automatically. A TTL that does not parse, is negative, or exceeds 10 years is refused with a 400.

Batch Operations

Multi-Get

Fetch multiple keys from the same dmap in a single request. Returns a Map<string, any | null> where missing keys have a value of null.

typescript
const results = await client.cache.multiGet("sessions", [
  "user:alice",
  "user:bob",
  "user:charlie",
]);

for (const [key, value] of results) {
  if (value !== null) {
    console.log(`${key} is cached:`, value);
  } else {
    console.log(`${key} is a cache miss`);
  }
}

Scan

List keys in a distributed map, optionally filtering with a regex pattern.

typescript
// Scan all keys in the dmap
const all = await client.cache.scan("sessions");
console.log(all.keys);  // ["user:alice", "user:bob", ...]
console.log(all.count); // 2

// Scan with a regex pattern
const admins = await client.cache.scan("sessions", "admin:.*");
console.log(admins.keys); // ["admin:alice", "admin:eve"]

Health Check

Verify that the cache service is reachable and healthy:

typescript
const health = await client.cache.health();
console.log(health.status);  // "ok"
console.log(health.service); // "cache"

REST API

All cache operations use POST with a JSON body (except health which is GET).

Put

bash
curl -X POST https://gateway.example.com/v1/cache/put \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"dmap": "sessions", "key": "user:alice", "value": {"role": "admin"}, "ttl": "1h"}'

Get

bash
curl -X POST https://gateway.example.com/v1/cache/get \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"dmap": "sessions", "key": "user:alice"}'

Delete

bash
curl -X POST https://gateway.example.com/v1/cache/delete \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"dmap": "sessions", "key": "user:alice"}'

Multi-Get

bash
curl -X POST https://gateway.example.com/v1/cache/mget \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"dmap": "sessions", "keys": ["user:alice", "user:bob"]}'

Scan

bash
curl -X POST https://gateway.example.com/v1/cache/scan \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"dmap": "sessions", "match": "user:.*"}'

Health

bash
curl https://gateway.example.com/v1/cache/health \
  -H "Authorization: Bearer <token>"

CLI

There are no dedicated CLI commands for cache operations. Use the SDK or REST API.

Patterns

Cache-Aside

The most common caching pattern. Check the cache first; on a miss, fetch from the source and populate the cache.

typescript
async function getUser(id: string) {
  // Check cache first
  const cached = await client.cache.get("users", `user:${id}`);
  if (cached) {
    return cached.value;
  }

  // Cache miss -- fetch from database
  const user = await client.db.query(
    "SELECT * FROM users WHERE id = ?", [id]
  );

  // Populate cache with a 1-hour TTL
  await client.cache.put("users", `user:${id}`, user[0], "1h");

  return user[0];
}

Write-Through

Update the cache whenever you update the source data, so the cache stays fresh.

typescript
async function updateUser(id: string, data: UserData) {
  // Update the database
  await client.db.exec(
    "UPDATE users SET name = ? WHERE id = ?", [data.name, id]
  );

  // Update the cache
  await client.cache.put("users", `user:${id}`, data, "1h");
}

Session Store

Use a dmap as a lightweight session store with automatic expiration.

typescript
async function createSession(userId: string, token: string) {
  await client.cache.put("sessions", `session:${token}`, {
    userId,
    createdAt: Date.now(),
  }, "24h");
}

async function validateSession(token: string) {
  const session = await client.cache.get("sessions", `session:${token}`);
  return session?.value ?? null;
}

async function destroySession(token: string) {
  await client.cache.delete("sessions", `session:${token}`);
}

REST API Reference

MethodEndpointDescription
POST/v1/cache/putStore a value. Body: {dmap, key, value, ttl?}
POST/v1/cache/getRetrieve a value. Body: {dmap, key}
POST/v1/cache/deleteRemove a value. Body: {dmap, key}
POST/v1/cache/mgetFetch multiple keys. Body: {dmap, keys}
POST/v1/cache/scanList keys in a dmap. Body: {dmap, match?}
GET/v1/cache/healthCheck cache service health

SDK Method Reference

MethodSignatureDescription
putput(dmap, key, value, ttl?)Store a value. TTL is a duration string ("1h", "30m", "5s").
getget(dmap, key)Retrieve a value. Returns null on cache miss.
deletedelete(dmap, key)Remove a value.
multiGetmultiGet(dmap, keys)Fetch multiple keys. Returns Map<string, any | null>.
scanscan(dmap, match?)List keys in a dmap, optionally filtered by regex.
healthhealth()Check cache service health.

Types

typescript
interface CacheGetResponse {
  key: string;
  value: any;
  dmap: string;
}

interface CachePutResponse {
  status: string;
  key: string;
  dmap: string;
}

interface CacheDeleteResponse {
  status: string;
  key: string;
  dmap: string;
}

interface CacheScanResponse {
  keys: string[];
  count: number;
  dmap: string;
}

interface CacheHealthResponse {
  status: string;
  service: string;
}

Limits and behaviour

  • +Memory. All of a namespace's maps share one memory bound: least recently used entries are evicted once the namespace's cache holds 256 MiB on a node, however many maps you make.
  • +Keys. A map name and its key together are at most 255 bytes. The name counts its length in bytes plus 2, so a key in a map named sessions is at most 245 bytes. A longer one is refused with a 413 that names the limit, and a get or delete of one answers 404.
  • +Values. Any JSON value except null, and get returns exactly what was put, type included (the string "123" comes back a string). One entry must fit in a single cache table (1 MiB, key and encoding included); larger is refused with 413 and nothing is stored.
  • +TTL. A duration string such as "30m" or "1h". Without a TTL (or "0s") the entry lives until deleted. A TTL that does not parse, is negative, or exceeds 10 years is refused with 400.
  • +Scan. match is a regular expression applied to the key as you wrote it; an invalid one is refused with 400.
  • +Permissions. A grant can be narrowed to keys: cache:key=sessions/*, matched against <map>/<key>. An app-runtime key does not include the cache: mint the key with --scope invoke,cache (or name the grants you need). See Access control.
  • +A cache, not a store. Olric holds entries in memory. When memberlist encryption is enabled all nodes must restart together and the cache is lost; it rebuilds from your database.