# Working with the Cache

Every Sinch Function has access to a shared key-value cache. In development it uses in-memory storage. In production, the cache is persistent across restarts and shared across all instances of your function.

## Interface

| Operation | Node.js | C# |
|  --- | --- | --- |
| Write | `cache.set(key, value, ttl?)` | `cache.Set(key, value, ttl?)` |
| Read | `cache.get<T>(key)` | `cache.Get<T>(key)` |
| Check existence | `cache.has(key)` | `cache.Exists(key)` |
| Delete | `cache.delete(key)` | `cache.Delete(key)` |
| Extend TTL | `cache.extend(key, seconds)` | — |
| List keys | `cache.keys(pattern?)` | `cache.GetKeys()` |
| Batch read | `cache.getMany(keys)` | — |


Default TTL: **3600 seconds** (1 hour).

## Accessing the cache

Node.js
```typescript
import type { FunctionContext, FunctionRequest } from '@sinch/functions-runtime';

export async function myHandler(context: FunctionContext, request: FunctionRequest) {
  const cache = context.cache;
  await cache.set('greeting', 'Hello!');
  const value = await cache.get<string>('greeting'); // 'Hello!'
}
```

C#
```csharp
public class FunctionController : SinchController
{
    public async Task<IActionResult> MyEndpoint()
    {
        var cache = Context.Cache;
        await cache.Set("greeting", "Hello!");
        var value = await cache.Get("greeting");
        return Ok(value);
    }
}
```

## TTL (time to live)

Node.js
```typescript
await cache.set('short-lived', 'value', 60); // 60 seconds
await cache.set('persistent', 'value', 86400 * 30); // 30 days
await cache.extend('session:abc123', 300); // extend by 5 minutes
```

C#
```csharp
await cache.Set("short-lived", "value", ttlSeconds: 60);
await cache.Set("persistent", "value", ttlSeconds: 86400 * 30);
```

## Storing objects

Values are JSON-serialized automatically.

Node.js
```typescript
interface UserProfile {
  name: string;
  plan: string;
  createdAt: string;
}

await cache.set<UserProfile>(
  'user:123',
  {
    name: 'Alice',
    plan: 'pro',
    createdAt: new Date().toISOString(),
  },
  3600
);

const profile = await cache.get<UserProfile>('user:123');
```

C#
```csharp
public record UserProfile(string Name, string Plan, DateTime CreatedAt);

await cache.Set("user:123", new UserProfile("Alice", "pro", DateTime.UtcNow), ttlSeconds: 3600);
var profile = await cache.Get<UserProfile>("user:123");
```

## Common patterns

### Session storage

Store caller state across voice callbacks using the call ID as key.

Node.js
```typescript
// In ICE handler
await context.cache.set(
  `session:${event.callId}`,
  {
    callerNumber: event.cli,
    startTime: Date.now(),
  },
  600
);

// In PIE handler
const session = await context.cache.get<{ callerNumber: string; startTime: number }>(
  `session:${event.callId}`
);
```

C#
```csharp
// In ICE handler
await Context.Cache.Set($"session:{callbackData.CallId}", new
{
    CallerNumber = callbackData.Cli, StartTime = DateTime.UtcNow
}, ttlSeconds: 600);

// In PIE handler
var session = await Context.Cache.Get<dynamic>($"session:{callbackData.CallId}");
```

### Rate limiting

Node.js
```typescript
async function isRateLimited(
  cache: IFunctionCache,
  caller: string,
  maxPerHour = 10
): Promise<boolean> {
  const key = `rate:${caller}`;
  const current = await cache.get<number>(key);
  if (current === null) {
    await cache.set(key, 1, 3600);
    return false;
  }
  if (current >= maxPerHour) return true;
  await cache.set(key, current + 1, 3600);
  return false;
}
```

C#
```csharp
private async Task<bool> IsRateLimited(string caller, int maxPerHour = 10)
{
    var key = $"rate:{caller}";
    var current = await Context.Cache.Get(key);
    if (current == null) { await Context.Cache.Set(key, "1", ttlSeconds: 3600); return false; }
    var count = int.Parse(current);
    if (count >= maxPerHour) return true;
    await Context.Cache.Set(key, (count + 1).ToString(), ttlSeconds: 3600);
    return false;
}
```

### Caller lookup cache

Node.js
```typescript
async function getCustomer(cache: IFunctionCache, phone: string) {
  const cacheKey = `customer:${phone}`;
  const cached = await cache.get<Customer>(cacheKey);
  if (cached) return cached;

  const customer = await fetchCustomerFromApi(phone);
  if (customer) await cache.set(cacheKey, customer, 300);
  return customer;
}
```

C#
```csharp
private async Task<Customer?> GetCustomer(string phone)
{
    var cacheKey = $"customer:{phone}";
    var cached = await Context.Cache.Get<Customer>(cacheKey);
    if (cached != null) return cached;

    var customer = await FetchCustomerFromApi(phone);
    if (customer != null) await Context.Cache.Set(cacheKey, customer, ttlSeconds: 300);
    return customer;
}
```

## Development vs production

| Aspect | Development | Production |
|  --- | --- | --- |
| Backend | In-memory | Persistent store |
| Persistence | Lost on restart | Survives restarts |
| Sharing | Single process only | Shared across all instances |
| TTL enforcement | Approximate | Exact |


The `IFunctionCache` interface is identical in both environments. No code changes needed when deploying.