# Hooks & Custom Logic

> Intercept auth, database, and custom lifecycle events with server-side hooks. Write edge functions with full SDK access, no server management.

Write hooks that intercept lifecycle events (before/after auth, etc.) or create entirely new API endpoints — all with full SDK access and zero server management.

## Core concepts

### Lifecycle hooks

Hooks run at specific moments in the request/response lifecycle:

- `auth.register.before` — validate email domain before account creation
- `auth.login.after` — log sign-ins, update last-seen timestamps
- `order.complete.after` — calculate loyalty points, send confirmation

### Custom API endpoints

Create new endpoints at:
```
https://api.aerostack.dev/api/v1/custom/<your-endpoint>
```

These endpoints get the full `sdk` object pre-wired — no boilerplate required.

## The SDK in hooks

Every hook receives the full `sdk` object:

| Primitive | SDK call | Why use it in hooks? |
|-----------|----------|----------------------|
| Database | `sdk.db.query()` | Cross-table joins, custom validation |
| Cache | `sdk.cache.set()` | Store computed results at the edge |
| Queue | `sdk.queue.send()` | Offload heavy tasks to background workers |
| AI | `sdk.ai.complete()` | Categorize input, summarize data in real-time |
| Realtime | `sdk.socket.emit()` | Notify connected clients on lifecycle events |

## Example: validate email on register

```ts
// Hook: auth.register.before

  const { email } = event.data

  // Block disposable email providers
  const blocklist = ['mailinator.com', 'tempmail.com', '10minutemail.com']
  const domain = email.split('@')[1]

  if (blocklist.includes(domain)) {
    throw new Error('Disposable email addresses are not allowed')
  }

  // Allow everything else
  return { proceed: true }
}
```

## Example: custom reporting endpoint

```ts
// Custom endpoint: POST /api/v1/custom/analytics/summary

  const { projectId, startDate, endDate } = event.data
  const cacheKey = `analytics:${projectId}:${startDate}:${endDate}`

  const cached = await sdk.cache.get(cacheKey)
  if (cached) return cached

  const [signups, orders, revenue] = await sdk.db.batch([
    {
      sql: 'SELECT COUNT(*) as count FROM users WHERE created_at BETWEEN ? AND ?',
      params: [startDate, endDate]
    },
    {
      sql: 'SELECT COUNT(*) as count FROM orders WHERE created_at BETWEEN ? AND ?',
      params: [startDate, endDate]
    },
    {
      sql: 'SELECT SUM(total) as total FROM orders WHERE status = ? AND created_at BETWEEN ? AND ?',
      params: ['paid', startDate, endDate]
    }
  ])

  const result = {
    signups: signups.results[0]?.count ?? 0,
    orders: orders.results[0]?.count ?? 0,
    revenue: revenue.results[0]?.total ?? 0,
    generatedAt: Date.now()
  }

  await sdk.cache.set(cacheKey, result, { ttl: 300 })

  return result
}
```

## Workflow

1. **Develop** — Monaco editor with TypeScript and AI assistance in the dashboard.
2. **Test** — Dry-run with mock payloads in the browser or via CLI.
3. **Deploy** — Push to the global network in under 1 second. No build pipelines.
4. **Monitor** — Trace every execution in the Platform observability dashboard.

## Deploy via CLI

```bash
# Deploy a hook from local file
aerostack deploy hooks/auth-before-register.ts --hook auth.register.before

# Deploy a custom endpoint
aerostack deploy api/analytics-summary.ts --endpoint analytics/summary
```
