# Cache & Queue

> Edge caching with TTL, counters, and bulk operations. Background job queue with retries, delays, and status tracking.

Two simpler but essential modules for production applications. Cache gives you sub-millisecond key-value storage at the edge. Queue lets you offload work to background jobs with automatic retries.

  **Beta** -- Cache and Queue APIs are stable but may receive non-breaking additions.

---

## Cache

Globally replicated key-value cache with TTL support, atomic counters, and bulk operations. Data is replicated globally for low-latency reads from any edge location.

### What you can build with Cache

- **Session storage** -- Store user sessions with automatic expiry
- **Rate limiting** -- Atomic counters to track request rates per user/IP
- **Feature flags** -- Store and update feature flags without redeploying
- **Computed results** -- Cache expensive database queries or API responses
- **View counters** -- Atomic increment for page views, likes, votes
- **Shopping carts** -- Temporary cart data with TTL expiry

### Basic operations

  
```ts
// Set a value (with optional TTL in seconds)
await sdk.cache.set('user:123', { name: 'Jane', plan: 'pro' }, { ttl: 3600 })

// Set with metadata
await sdk.cache.set('session:abc', sessionData, {
  ttl: 1800,
  metadata: { userId: '123', device: 'mobile' },
})

// Get a value (null if not found or expired)
const user = await sdk.cache.get('user:123')

// Check existence
const exists = await sdk.cache.has('user:123')

// Delete
await sdk.cache.delete('user:123')
```
  
  
```go
// Set with TTL
err = client.Cache.Set(ctx, "user:123", userData, 3600)

// Get
var user User
found, err := client.Cache.Get(ctx, "user:123", &user)

// Check existence
exists, err := client.Cache.Has(ctx, "user:123")

// Delete
err = client.Cache.Delete(ctx, "user:123")
```
  
  
```python
# Set with TTL
await client.cache.set("user:123", user_data, ttl=3600)

# Get
user = await client.cache.get("user:123")

# Check existence
exists = await client.cache.has("user:123")

# Delete
await client.cache.delete("user:123")
```
  

### Bulk operations

Efficiently get, set, or delete multiple keys in a single round-trip:

```ts
// Get many
const values = await sdk.cache.getMany(['user:1', 'user:2', 'user:3'])
// { 'user:1': {...}, 'user:2': {...}, 'user:3': null }

// Set many
await sdk.cache.setMany({
  'user:1': { name: 'Alice' },
  'user:2': { name: 'Bob' },
  'user:3': { name: 'Carol' },
}, { ttl: 3600 })

// Delete many
await sdk.cache.deleteMany(['user:1', 'user:2', 'user:3'])
```

### List keys

```ts
// List all keys with a prefix
const keys = await sdk.cache.keys({ prefix: 'session:' })
// ['session:abc', 'session:def', 'session:ghi']

// List with metadata
const entries = await sdk.cache.list({ prefix: 'session:' })
// [{ key: 'session:abc', metadata: { userId: '123' } }, ...]
```

### Atomic counters

Increment or decrement values atomically. Perfect for rate limiting, view counts, and voting:

```ts
// Increment (creates key with value 1 if it doesn't exist)
const newCount = await sdk.cache.increment('views:post-123')
// 1, 2, 3, ...

// Increment by a specific amount
const newCount = await sdk.cache.increment('views:post-123', 5)

// Use for rate limiting
async function checkRateLimit(userId: string, limit: number) {
  const key = `ratelimit:${userId}:${Math.floor(Date.now() / 60000)}`
  const count = await sdk.cache.increment(key)

  if (count === 1) {
    // First request in this minute window -- set TTL
    await sdk.cache.expire(key, 60)
  }

  return count <= limit
}
```

### Set expiry on existing key

```ts
await sdk.cache.expire('session:abc', 1800) // 30 minutes from now
```

### Flush all cache

```ts
await sdk.cache.flush() // Delete everything -- use with caution
```

---

### Complete example: session store

```ts title="session-store.ts"

const { sdk } = new AerostackClient({ projectId, apiKey })

async function createSession(userId: string, device: string) {
  const sessionId = crypto.randomUUID()

  await sdk.cache.set(`session:${sessionId}`, {
    userId,
    device,
    createdAt: Date.now(),
    lastActive: Date.now(),
  }, {
    ttl: 86400, // 24 hours
    metadata: { userId, device },
  })

  return sessionId
}

async function getSession(sessionId: string) {
  const session = await sdk.cache.get(`session:${sessionId}`)
  if (!session) return null

  // Refresh TTL on access
  await sdk.cache.set(`session:${sessionId}`, {
    ...session,
    lastActive: Date.now(),
  }, { ttl: 86400 })

  return session
}

async function destroySession(sessionId: string) {
  await sdk.cache.delete(`session:${sessionId}`)
}

async function getUserSessions(userId: string) {
  const entries = await sdk.cache.list({ prefix: 'session:' })
  return entries.filter(e => e.metadata?.userId === userId)
}
```

### Complete example: feature flags

```ts title="feature-flags.ts"
async function setFlag(name: string, value: boolean, metadata?: Record<string, any>) {
  await sdk.cache.set(`flag:${name}`, { enabled: value, ...metadata })
}

async function getFlag(name: string): Promise<boolean> {
  const flag = await sdk.cache.get(`flag:${name}`)
  return flag?.enabled ?? false
}

async function getAllFlags() {
  const keys = await sdk.cache.keys({ prefix: 'flag:' })
  const values = await sdk.cache.getMany(keys)
  return Object.fromEntries(
    keys.map(k => [k.replace('flag:', ''), values[k]?.enabled ?? false])
  )
}

// Usage
await setFlag('new-checkout', true)
await setFlag('dark-mode', true, { rollout: 50 })

if (await getFlag('new-checkout')) {
  // Show new checkout flow
}
```

---

## Cache API reference

| Method | Signature | Returns |
|--------|-----------|---------|
| `sdk.cache.get` | `(key: string) => Promise` | Cached value or null |
| `sdk.cache.set` | `(key: string, value: any, opts?: { ttl?, metadata? }) => Promise<void>` | Nothing |
| `sdk.cache.delete` | `(key: string) => Promise<void>` | Nothing |
| `sdk.cache.has` | `(key: string) => Promise<boolean>` | Boolean |
| `sdk.cache.getMany` | `(keys: string[]) => Promise>` | Map of key to value |
| `sdk.cache.setMany` | `(entries: Record<string, any>, opts?) => Promise<void>` | Nothing |
| `sdk.cache.deleteMany` | `(keys: string[]) => Promise<void>` | Nothing |
| `sdk.cache.keys` | `(opts?: { prefix? }) => Promise<string[]>` | Array of keys |
| `sdk.cache.list` | `(opts?: { prefix? }) => Promise<{ key, metadata }[]>` | Keys with metadata |
| `sdk.cache.increment` | `(key: string, amount?: number) => Promise<number>` | New value |
| `sdk.cache.expire` | `(key: string, ttl: number) => Promise<void>` | Nothing |
| `sdk.cache.flush` | `() => Promise<void>` | Nothing |

---

## Queue

Background job processing with configurable retries, delays, and status tracking. Jobs are durably stored and guaranteed to execute at least once.

### What you can build with Queue

- **Email notifications** -- Send welcome emails, password resets, order confirmations
- **PDF generation** -- Generate invoices, reports, or certificates in the background
- **Batch imports** -- Process CSV uploads row by row without blocking the request
- **Image processing** -- Resize, crop, or optimize uploaded images
- **Webhook delivery** -- Retry failed webhook deliveries with exponential backoff
- **Scheduled tasks** -- Delay job execution for reminders, follow-ups, or scheduled reports
- **Data sync** -- Sync data between Aerostack and external systems

### Enqueue a job

  
```ts
// Simple job
await sdk.queue.enqueue('send-welcome-email', {
  userId: 'user-123',
  email: 'jane@example.com',
  name: 'Jane',
})

// Job with options
await sdk.queue.enqueue('generate-report', {
  reportType: 'monthly',
  month: '2026-03',
  userId: 'user-123',
}, {
  delaySeconds: 60,  // Wait 1 minute before processing
  maxRetries: 5,     // Retry up to 5 times on failure
})
```
  
  
```go
err = client.Queue.Send(ctx, "send-welcome-email", payload, aerostack.QueueOptions{
    DelaySeconds: 0,
    MaxRetries:   3,
})
```
  
  
```python
await client.queue.send(
    "send-welcome-email",
    {"userId": "user-123", "email": "jane@example.com"},
    delay_seconds=0,
    max_retries=3
)
```
  

### Check job status

```ts
const job = await sdk.queue.getJob(jobId)
// { id, type, status, payload, attempts, maxRetries, createdAt, scheduledAt, completedAt }
```

| Status | Description |
|--------|-------------|
| `pending` | Waiting to be processed (or delayed) |
| `processing` | Currently being executed |
| `completed` | Successfully finished |
| `failed` | All retries exhausted |
| `cancelled` | Manually cancelled |

### List jobs

```ts
const jobs = await sdk.queue.listJobs({
  type: 'send-welcome-email',   // optional filter
  status: 'pending',             // optional filter
  limit: 50,
})
```

### Cancel a job

```ts
await sdk.queue.cancelJob(jobId)
```

  You can only cancel jobs in `pending` status. Jobs that are already `processing` will complete their current attempt.

---

### Complete example: email notification pipeline

```ts title="email-pipeline.ts"

const { sdk } = new AerostackClient({ projectId, apiKey })

// Enqueue from your API handler
async function onUserSignup(user: { id: string, email: string, name: string }) {
  // Send welcome email immediately
  await sdk.queue.enqueue('send-email', {
    template: 'welcome',
    to: user.email,
    data: { name: user.name },
  })

  // Send onboarding tip after 24 hours
  await sdk.queue.enqueue('send-email', {
    template: 'onboarding-tip-1',
    to: user.email,
    data: { name: user.name },
  }, { delaySeconds: 86400 })

  // Send follow-up after 3 days
  await sdk.queue.enqueue('send-email', {
    template: 'onboarding-tip-2',
    to: user.email,
    data: { name: user.name },
  }, { delaySeconds: 259200 })
}

// Process jobs in your worker
async function processEmailJob(job: { type: string, payload: any }) {
  const { template, to, data } = job.payload

  // Send via your email provider
  await sendEmail({ template, to, data })

  // Log in database
  await sdk.db.query(
    'INSERT INTO email_log (id, template, recipient, sent_at) VALUES (?, ?, ?, ?)',
    [crypto.randomUUID(), template, to, Date.now()]
  )
}
```

### Complete example: batch CSV import

```ts title="csv-import.ts"
async function importCSV(userId: string, csvContent: string) {
  const rows = parseCSV(csvContent)

  // Enqueue each row as a separate job (can process in parallel)
  for (const row of rows) {
    await sdk.queue.enqueue('import-row', {
      userId,
      data: row,
    }, { maxRetries: 3 })
  }

  // Track the import
  await sdk.db.query(
    'INSERT INTO imports (id, user_id, total_rows, status, created_at) VALUES (?, ?, ?, ?, ?)',
    [crypto.randomUUID(), userId, rows.length, 'processing', Date.now()]
  )
}
```

---

## Queue API reference

| Method | Signature | Returns |
|--------|-----------|---------|
| `sdk.queue.enqueue` | `(type: string, payload: any, opts?: { delaySeconds?, maxRetries? }) => Promise<{ jobId: string }>` | Job ID |
| `sdk.queue.getJob` | `(jobId: string) => Promise` | Job details |
| `sdk.queue.listJobs` | `(opts?: { type?, status?, limit? }) => Promise` | Array of jobs |
| `sdk.queue.cancelJob` | `(jobId: string) => Promise<void>` | Nothing |
