---
title: ICacheService Contract
description: Reference for the Cache Service contract — key-value caching with TTL support, namespaces, and bulk operations
---

# ICacheService Contract

The Cache Service provides a **key-value caching** layer used throughout ObjectStack for metadata caching, query result caching, session storage, and rate limiting. Implementations can range from in-memory Maps to Redis clusters.

<Callout type="info">
**Source:** `packages/spec/src/system/contracts/cache-service.ts`  
**Token:** `CACHE_SERVICE`
</Callout>

---

## Interface Definition

```typescript
export interface ICacheService {
  // Core Operations
  get<T = unknown>(key: string): Promise<T | null>;
  set(key: string, value: unknown, options?: CacheSetOptions): Promise<void>;
  delete(key: string): Promise<boolean>;
  has(key: string): Promise<boolean>;
  clear(pattern?: string): Promise<void>;

  // TTL Management
  getTtl(key: string): Promise<number | null>;
  setTtl(key: string, ttl: number): Promise<boolean>;

  // Bulk Operations
  getMany<T = unknown>(keys: string[]): Promise<Map<string, T>>;
  setMany(entries: CacheEntry[], options?: CacheSetOptions): Promise<void>;
  deleteMany(keys: string[]): Promise<number>;

  // Atomic Operations
  increment(key: string, amount?: number): Promise<number>;
  decrement(key: string, amount?: number): Promise<number>;

  // Namespace
  namespace(prefix: string): ICacheService;
}
```

---

## Core Operations

### get

Retrieves a cached value by key. Returns `null` if the key does not exist or has expired.

```typescript
const user = await cacheService.get<AuthUser>('user:usr_01HQ3V5K8N');
if (user) {
  console.log(user.name); // "Jane Smith"
}
```

### set

Stores a value with an optional TTL (time-to-live).

```typescript
// Cache for 5 minutes
await cacheService.set('user:usr_01HQ3V5K8N', userData, {
  ttl: 300,
});

// Cache indefinitely
await cacheService.set('config:feature_flags', flags);
```

### delete

Removes a key from the cache. Returns `true` if the key existed.

```typescript
const deleted = await cacheService.delete('user:usr_01HQ3V5K8N');
// true if the key was found and removed
```

### has

Checks if a key exists without retrieving the value.

```typescript
const exists = await cacheService.has('user:usr_01HQ3V5K8N');
// true or false
```

### clear

Removes all keys, or keys matching a glob pattern.

```typescript
// Clear all keys
await cacheService.clear();

// Clear all user cache entries
await cacheService.clear('user:*');

// Clear all metadata cache
await cacheService.clear('meta:*');
```

---

## CacheSetOptions

```typescript
export interface CacheSetOptions {
  ttl?: number;         // Time-to-live in seconds
  ifNotExists?: boolean; // Only set if key doesn't already exist
  ifExists?: boolean;    // Only set if key already exists
}
```

| Option | Type | Default | Description |
|:---|:---|:---|:---|
| `ttl` | `number` | — | Seconds until the key expires. Omit for no expiration |
| `ifNotExists` | `boolean` | `false` | SET-if-not-exists (NX) semantics |
| `ifExists` | `boolean` | `false` | SET-if-exists (XX) semantics |

<Callout type="tip">
**Lock Pattern:** Use `set` with `ifNotExists: true` and a short TTL to implement distributed locks:  
`await cache.set('lock:task_import', true, { ttl: 30, ifNotExists: true })`
</Callout>

---

## TTL Management

### getTtl

Returns the remaining TTL in seconds for a key. Returns `null` if the key does not exist, or `-1` if the key has no expiration.

```typescript
const remaining = await cacheService.getTtl('user:usr_01HQ3V5K8N');
// 245 (seconds remaining) | null (key missing) | -1 (no expiry)
```

### setTtl

Updates the TTL on an existing key without modifying its value.

```typescript
// Extend the cache for another 10 minutes
await cacheService.setTtl('user:usr_01HQ3V5K8N', 600);
```

---

## Bulk Operations

### getMany

Retrieves multiple keys in a single round-trip.

```typescript
const users = await cacheService.getMany<AuthUser>([
  'user:usr_01HQ3V5K8N',
  'user:usr_01HQ3W6L9O',
  'user:usr_01HQ3X7M0P',
]);

// Returns Map<string, AuthUser>
for (const [key, user] of users) {
  console.log(`${key}: ${user.name}`);
}
```

### setMany

Stores multiple entries in a single operation.

```typescript
await cacheService.setMany(
  [
    { key: 'meta:task', value: taskDefinition },
    { key: 'meta:project', value: projectDefinition },
    { key: 'meta:user', value: userDefinition },
  ],
  { ttl: 3600 }
);
```

### CacheEntry

```typescript
export interface CacheEntry {
  key: string;
  value: unknown;
  ttl?: number; // Per-entry TTL override
}
```

### deleteMany

Removes multiple keys at once. Returns the number of keys deleted.

```typescript
const count = await cacheService.deleteMany([
  'user:usr_01HQ3V5K8N',
  'user:usr_01HQ3W6L9O',
]);
// 2
```

---

## Atomic Operations

### increment / decrement

Atomically modify numeric values. Useful for counters and rate limiting.

```typescript
// Rate limiting: increment request counter
const count = await cacheService.increment('ratelimit:usr_01HQ3V:minute');
if (count === 1) {
  // First request in this window — set TTL
  await cacheService.setTtl('ratelimit:usr_01HQ3V:minute', 60);
}
if (count > 100) {
  throw new Error('Rate limit exceeded');
}

// View counter
await cacheService.increment('views:page_home', 1);

// Decrement available seats
const remaining = await cacheService.decrement('seats:event_123', 1);
```

---

## Namespaces

### namespace

Creates a scoped cache instance where all keys are automatically prefixed.

```typescript
// Create a namespaced cache for metadata
const metaCache = cacheService.namespace('meta');

// These operations use keys like "meta:task", "meta:project"
await metaCache.set('task', taskDefinition, { ttl: 3600 });
const task = await metaCache.get('task');

// Clear only the metadata namespace
await metaCache.clear();
```

Namespaces can be nested:

```typescript
const tenantCache = cacheService.namespace('tenant:acme');
const tenantMetaCache = tenantCache.namespace('meta');
// Keys: "tenant:acme:meta:task"
```

---

## Common Cache Patterns

### Cache-Aside (Lazy Loading)

```typescript
async function getObjectDefinition(name: string): Promise<ObjectDefinition> {
  const cacheKey = `meta:object:${name}`;

  // Try cache first
  const cached = await cacheService.get<ObjectDefinition>(cacheKey);
  if (cached) return cached;

  // Load from source
  const definition = await metadataService.getObject(name);
  if (!definition) throw new Error(`Object not found: ${name}`);

  // Store in cache
  await cacheService.set(cacheKey, definition, { ttl: 3600 });

  return definition;
}
```

### Write-Through

```typescript
async function updateObjectDefinition(
  name: string,
  changes: Partial<ObjectDefinition>
): Promise<ObjectDefinition> {
  // Update source of truth
  const updated = await metadataService.updateObject(name, changes);

  // Update cache immediately
  await cacheService.set(`meta:object:${name}`, updated, { ttl: 3600 });

  return updated;
}
```

### Cache Invalidation on Events

```typescript
metadataService.onChange((event) => {
  // Invalidate cache when metadata changes
  cacheService.delete(`meta:object:${event.objectName}`);
  cacheService.clear(`query:${event.objectName}:*`);
});
```

---

## Error Codes

| Code | Description |
|:---|:---|
| `CACHE_UNAVAILABLE` | Cache backend is not reachable |
| `SERIALIZATION_ERROR` | Value cannot be serialized/deserialized |
| `KEY_TOO_LONG` | Key exceeds maximum length (typically 512 bytes) |
| `VALUE_TOO_LARGE` | Value exceeds maximum size limit |
