Skip to main content

function-dev

Use when developing, deploying, or debugging Butterbase serverless functions, or when the user needs to add backend logic like webhooks, scheduled jobs, or custom API endpoints

Informations de source

Dépôt
butterbase-ai/butterbase-skills
Dernière activité de la source
1 juin 2026 à 11:06
Langue détectée de SKILL.md
anglais
Étoiles
534
Forks
42

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
function-dev
description
Use when developing, deploying, or debugging Butterbase serverless functions, or when the user needs to add backend logic like webhooks, scheduled jobs, or custom API endpoints
# 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 sessions WHERE expires_at < now() RETURNING id" ); return new Response(JSON.stringify({ deleted: result.rowCount }), { headers: { "Content-Type": "application/json" } }); } ``` Deploy with: `trigger: { type: "cron", config: { schedule: "0 3 * * *" } }` --- ### Example 4 — External API Call Proxies a request to an external AI service using a stored API key. ```typescript export async function handler(req, ctx) { const { prompt } = await req.json(); const response = await fetch("https://api.openai.com/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${ctx.env.OPENAI_API_KEY}` }, body: JSON.stringify({ model: "gpt-4", messages: [{ role: "user", content: prompt }] }) }); const data = await response.json(); return new Response(JSON.stringify(data), { headers: { "Content-Type": "application/json" } }); } ``` Deploy with `envVars: { OPENAI_API_KEY: "sk-..." }` and `trigger: { type: "http", config: { method: "POST", auth: "required" } }` --- ### Example 5 — Error Handling Pattern Always wrap handler logic in try/catch and return a proper error Response. ```typescript export async function handler(req, ctx) { try { const { id } = await req.json(); const { rows } = await ctx.db.query( 'SELECT * FROM items WHERE id = $1', [id] ); if (rows.length === 0) { return new Response(JSON.stringify({ error: "Not found" }), { status: 404, headers: { "Content-Type": "application/json" } }); } return new Response(JSON.stringify(rows[0]), { status: 200, headers: { "Content-Type": "application/json" } }); } catch (err) { return new Response(JSON.stringify({ error: "Internal server error" }), { status: 500, headers: { "Content-Type": "application/json" } }); } } ``` --- ## 6. Testing & Debugging The standalone tools `deploy_function` and `invoke_function` are unchanged. Everything else (logs, env updates, listing, deletion) is handled by `manage_function` with an `action` parameter. ### Invoke a Function ``` invoke_function( app_id: "app_abc123", function_name: "my-function", method: "POST", body: { key: "value" } ) ``` Returns the full HTTP response (status, headers, body, duration_ms). Use this immediately after deploying to verify behavior. ### View Error Logs ``` manage_function( app_id: "app_abc123", action: "get_logs", function_name: "my-function", level: "error" ) ``` Returns recent invocations with errors, stack traces, and captured `console.log/warn/error` output. ### View All Logs ``` manage_function( app_id: "app_abc123", action: "get_logs", function_name: "my-function", limit: 100, since: "2026-01-15T00:00:00Z" ) ``` Filters: `limit` (default 100), `since` (ISO timestamp), `level` (`"error"` or `"all"`). ### List Functions & Metrics ``` manage_function(app_id: "app_abc123", action: "list") ``` Returns each function's name, trigger, URL, status, and metrics (invocationCount, errorRate, avgDuration, lastInvoked). --- ## 7. Common Mistakes | Mistake | Fix | |---------|-----| | Returning plain object instead of `Response` | Always use `new Response(JSON.stringify(data), { headers: {...} })` | | SQL injection via string interpolation | Use parameterized queries: `$1, $2` placeholders | | Not wrapping in try/catch | Always catch errors and return a Response with error status | | Forgetting `async` on handler | Handler must be `async function handler(...)` | | Exceeding timeout (30s default) | Increase `timeoutMs` in `deploy_function` or optimize the function | | Not setting Content-Type header | Always include `"Content-Type": "application/json"` for JSON responses | --- ## 8. Quick Reference ### Deploy a Function ``` deploy_function( app_id: "app_abc123",
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub