Skip to main content
ClaudeWave
Skill532 estrellas del repoactualizado 2d ago

function-dev

The function-dev skill provides comprehensive guidance for developing, deploying, and debugging serverless functions on Butterbase's Deno runtime. Use this skill when implementing backend logic such as HTTP endpoints with authentication, scheduled cron jobs, webhook handlers with idempotency support, or WebSocket-triggered functions that need database access through RLS-aware clients and environment variable management.

Instalar en Claude Code
Copiar
git clone --depth 1 https://github.com/butterbase-ai/butterbase-skills /tmp/function-dev && cp -r /tmp/function-dev/skills/function-dev ~/.claude/skills/function-dev
Después abre una sesión nueva de Claude Code; el skill carga automáticamente.

SKILL.md

# Serverless Function Development on Butterbase

Guide for developing and deploying serverless functions on Butterbase's Deno runtime. Covers handler signatures, trigger types, database access, environment variables, and testing.

---

## 1. Handler Signature

Every function exports a single `handler` function with this signature:

```typescript
export async function handler(
  request: Request,
  context: {
    db: PostgresClient,                                  // RLS-aware DB client
    env: Record<string, string>,                         // env vars set on the function
    user: { id: string } | null,                         // present for HTTP+auth:required; null for cron
    waitUntil: (p: Promise<unknown>) => void,            // background work after Response (≤30s)
    idempotency: {
      claim: (key: string, opts?: { scope?: string; ttlSeconds?: number }) => Promise<boolean>
    }                                                    // atomic dedup for webhook retries
  }
): Promise<Response>
```

**CRITICAL**: The handler MUST return `new Response()` (Web API standard). Do NOT return plain objects.

**Correct:**
```typescript
return new Response(JSON.stringify({ message: "ok" }), {
  status: 200,
  headers: { "Content-Type": "application/json" }
});
```

**Wrong (will fail):**
```typescript
return { status: 200, body: "ok" };  // NOT a Response object!
```

---

## 2. Trigger Types

### HTTP Trigger

Invoke the function via an HTTP request.

```json
{
  "trigger": {
    "type": "http",
    "config": { "method": "POST", "path": "/my-endpoint", "auth": "required" }
  }
}
```

**Auth options:**
- `"required"` — request must include a valid JWT; `ctx.user` is always set
- `"optional"` — JWT is parsed if present; `ctx.user` may be `null`
- `"none"` — public endpoint; no auth needed; `ctx.user` is always `null`

---

### Cron Trigger

Execute the function on a schedule.

```json
{
  "trigger": {
    "type": "cron",
    "config": { "schedule": "0 9 * * *", "timezone": "UTC" }
  }
}
```

Uses standard 5-field cron expressions:
- `"*/5 * * * *"` — every 5 minutes
- `"0 0 * * 0"` — weekly, Sunday at midnight
- `"0 3 * * *"` — daily at 3am UTC
- `"0 9 * * 1-5"` — weekdays at 9am

Cron functions run as `butterbase_service` (RLS bypassed). `ctx.user` is always `null`.

---

### WebSocket Trigger

Fire when a connected client sends a matching event over the realtime WebSocket.

```json
{
  "trigger": {
    "type": "websocket",
    "config": { "event": "chat-message" }
  }
}
```

Fires when client sends matching event via realtime WebSocket connection. The `request` body contains the event payload sent by the client.

---

### S3 Upload Trigger _(placeholder — not yet implemented)_

```json
{
  "trigger": {
    "type": "s3_upload",
    "config": { "prefix": "uploads/", "contentTypes": ["image/*"] }
  }
}
```

---

## 3. Database Access

Use `ctx.db.query(sql, params)` for all database queries. Always use parameterized queries to prevent SQL injection — NEVER use string interpolation.

```typescript
// Always use $1, $2 placeholders — never string interpolation
const { rows } = await ctx.db.query(
  'SELECT * FROM posts WHERE author_id = $1',
  [ctx.user.id]  // params array
);
```

### SELECT

```typescript
const { rows } = await ctx.db.query(
  'SELECT * FROM posts WHERE author_id = $1 AND published = true',
  [ctx.user.id]
);
```

### INSERT

```typescript
await ctx.db.query(
  'INSERT INTO logs (event, user_id) VALUES ($1, $2)',
  ['page_view', ctx.user.id]
);
```

### UPDATE

```typescript
await ctx.db.query(
  'UPDATE posts SET title = $1, updated_at = now() WHERE id = $2 AND author_id = $3',
  [newTitle, postId, ctx.user.id]
);
```

### RLS Behavior by Invocation Type

| Invocation | Role | RLS |
|------------|------|-----|
| End-user JWT | `butterbase_user` | Enforced — `ctx.db` queries filtered by policies |
| API key (`bb_sk_`) | `butterbase_service` | Bypassed — sees all data |
| Cron trigger | `butterbase_service` | Bypassed — sees all data |

---

## 4. Environment Variables

- **Set at deploy time**: pass `envVars` parameter to `deploy_function`
- **Update without redeploying**: use `update_function_env`
- **Access in handler**: `ctx.env.VARIABLE_NAME`
- **Encrypted at rest**: values are never exposed in logs or API responses

Common uses: API keys, webhook secrets, external service URLs.

```typescript
const apiKey = ctx.env.OPENAI_API_KEY;
const webhookSecret = ctx.env.WEBHOOK_SECRET;
const serviceUrl = ctx.env.EXTERNAL_SERVICE_URL;
```

---

## 5. Complete Working Examples

### Example 1 — Protected API Endpoint (auth: required)

Returns the authenticated user's posts.

```typescript
export async function handler(req, ctx) {
  const { rows } = await ctx.db.query(
    'SELECT id, title, created_at FROM posts WHERE author_id = $1 ORDER BY created_at DESC',
    [ctx.user.id]
  );
  return new Response(JSON.stringify(rows), {
    headers: { "Content-Type": "application/json" }
  });
}
```

Deploy:
```
deploy_function(
  app_id,
  name: "my-posts",
  code: ...,
  trigger: {
    type: "http",
    config: { method: "GET", path: "/my-posts", auth: "required" }
  }
)
```

---

### Example 2 — Webhook Receiver (auth: none)

Accepts an incoming webhook, validates the signature, and stores the event.

```typescript
export async function handler(req, ctx) {
  const body = await req.json();
  const signature = req.headers.get("x-webhook-signature");
  // Validate signature against ctx.env.WEBHOOK_SECRET
  await ctx.db.query(
    'INSERT INTO webhook_events (event_type, payload) VALUES ($1, $2)',
    [body.type, JSON.stringify(body)]
  );
  return new Response("ok", { status: 200 });
}
```

Deploy with: `trigger: { type: "http", config: { method: "POST", path: "/webhook", auth: "none" } }`

---

### Example 3 — Cron Cleanup Job

Deletes expired sessions on a nightly schedule.

```typescript
export async function handler(req, ctx) {
  const result = await ctx.db.query(
    "DELETE FROM sessi