mirror of
https://github.com/supabase/agent-skills.git
synced 2026-03-27 10:09:26 +08:00
feature: edge functions references (#37)
* rebase and house keeping * fix edge functions reference files after docs review * update agents.mdd
This commit is contained in:
committed by
Pedro Rodrigues
parent
291028924a
commit
5254cf64ef
@@ -27,7 +27,8 @@ supabase/
|
||||
| Priority | Category | Impact | Prefix |
|
||||
|----------|----------|--------|--------|
|
||||
| 1 | Database | CRITICAL | `db-` |
|
||||
| 2 | Realtime | MEDIUM-HIGH | `realtime-` |
|
||||
| 2 | Edge Functions | HIGH | `edge-` |
|
||||
| 3 | Realtime | MEDIUM-HIGH | `realtime-` |
|
||||
| 3 | Storage | HIGH | `storage-` |
|
||||
|
||||
Reference files are named `{prefix}-{topic}.md` (e.g., `query-missing-indexes.md`).
|
||||
@@ -54,6 +55,23 @@ Reference files are named `{prefix}-{topic}.md` (e.g., `query-missing-indexes.md
|
||||
- `references/db-security-functions.md`
|
||||
- `references/db-security-service-role.md`
|
||||
|
||||
**Edge Functions** (`edge-`):
|
||||
- `references/edge-adv-regional.md`
|
||||
- `references/edge-adv-streaming.md`
|
||||
- `references/edge-adv-websockets.md`
|
||||
- `references/edge-auth-jwt-verification.md`
|
||||
- `references/edge-auth-rls-integration.md`
|
||||
- `references/edge-db-direct-postgres.md`
|
||||
- `references/edge-db-supabase-client.md`
|
||||
- `references/edge-dbg-limits.md`
|
||||
- `references/edge-dbg-testing.md`
|
||||
- `references/edge-fun-project-structure.md`
|
||||
- `references/edge-fun-quickstart.md`
|
||||
- `references/edge-pat-background-tasks.md`
|
||||
- `references/edge-pat-cors.md`
|
||||
- `references/edge-pat-error-handling.md`
|
||||
- `references/edge-pat-routing.md`
|
||||
|
||||
**Realtime** (`realtime-`):
|
||||
- `references/realtime-broadcast-basics.md`
|
||||
- `references/realtime-broadcast-database.md`
|
||||
@@ -76,4 +94,4 @@ Reference files are named `{prefix}-{topic}.md` (e.g., `query-missing-indexes.md
|
||||
|
||||
---
|
||||
|
||||
*34 reference files across 3 categories*
|
||||
*49 reference files across 4 categories*
|
||||
@@ -29,6 +29,26 @@ Reference the appropriate resource file based on the user's needs:
|
||||
| Performance | `references/db-perf-*.md` | Indexes (BRIN, GIN), query optimization |
|
||||
| Security | `references/db-security-*.md` | Service role key, security_definer functions |
|
||||
|
||||
### Edge Functions
|
||||
|
||||
| Area | Resource | When to Use |
|
||||
| ---------------------- | ------------------------------------- | -------------------------------------- |
|
||||
| Quick Start | `references/edge-fun-quickstart.md` | Creating and deploying first function |
|
||||
| Project Structure | `references/edge-fun-project-structure.md` | Directory layout, shared code, fat functions |
|
||||
| JWT Authentication | `references/edge-auth-jwt-verification.md` | JWT verification, jose library, middleware |
|
||||
| RLS Integration | `references/edge-auth-rls-integration.md` | Passing auth context, user-scoped queries |
|
||||
| Database (supabase-js) | `references/edge-db-supabase-client.md` | Queries, inserts, RPC calls |
|
||||
| Database (Direct) | `references/edge-db-direct-postgres.md` | Postgres pools, Drizzle ORM |
|
||||
| CORS | `references/edge-pat-cors.md` | Browser requests, preflight handling |
|
||||
| Routing | `references/edge-pat-routing.md` | Multi-route functions, Hono framework |
|
||||
| Error Handling | `references/edge-pat-error-handling.md` | Error responses, validation |
|
||||
| Background Tasks | `references/edge-pat-background-tasks.md` | waitUntil, async processing |
|
||||
| Streaming | `references/edge-adv-streaming.md` | SSE, streaming responses |
|
||||
| WebSockets | `references/edge-adv-websockets.md` | Bidirectional communication |
|
||||
| Regional Invocation | `references/edge-adv-regional.md` | Region selection, latency optimization |
|
||||
| Testing | `references/edge-dbg-testing.md` | Deno tests, local testing |
|
||||
| Limits & Debugging | `references/edge-dbg-limits.md` | Troubleshooting, runtime limits |
|
||||
|
||||
### Realtime
|
||||
|
||||
| Area | Resource | When to Use |
|
||||
|
||||
@@ -10,7 +10,12 @@ queries.
|
||||
**Impact:** CRITICAL
|
||||
**Description:** Row Level Security policies, connection pooling, schema design patterns, migrations, performance optimization, and security functions for Supabase Postgres.
|
||||
|
||||
## 2. Realtime (realtime)
|
||||
## 2. Edge Functions (edge)
|
||||
|
||||
**Impact:** HIGH
|
||||
**Description:** Fundamentals, authentication, database access, CORS, routing, error handling, streaming, WebSockets, regional invocations, testing, and limits.
|
||||
|
||||
## 3. Realtime (realtime)
|
||||
|
||||
**Impact:** MEDIUM-HIGH
|
||||
**Description:** Channel setup, Broadcast messaging, Presence tracking, Postgres Changes listeners, cleanup patterns, error handling, and debugging.
|
||||
|
||||
55
skills/supabase/references/edge-adv-regional.md
Normal file
55
skills/supabase/references/edge-adv-regional.md
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: Regional Invocations
|
||||
impact: LOW-MEDIUM
|
||||
impactDescription: Optimizes latency for region-specific workloads
|
||||
tags: edge-functions, regions, latency, performance
|
||||
---
|
||||
|
||||
## Regional Invocations
|
||||
|
||||
Execute Edge Functions in specific regions to minimize latency to your database. Regional invocations don't auto-failover during outages.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```typescript
|
||||
// Assumes automatic failover (it doesn't)
|
||||
const { data } = await supabase.functions.invoke("fn", {
|
||||
region: FunctionRegion.EuWest1,
|
||||
}); // If EU down, this fails
|
||||
|
||||
// Function close to user but far from database
|
||||
// User in Tokyo, database in US East
|
||||
await supabase.functions.invoke("db-heavy-fn", {
|
||||
region: FunctionRegion.ApNortheast1, // High latency to US database!
|
||||
});
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```typescript
|
||||
import { createClient, FunctionRegion } from "@supabase/supabase-js";
|
||||
|
||||
// Match function region to database region for db-heavy operations
|
||||
const DB_REGION = FunctionRegion.UsEast1; // Same as your database
|
||||
|
||||
const { data } = await supabase.functions.invoke("db-heavy-fn", {
|
||||
body: { query: "..." },
|
||||
region: DB_REGION,
|
||||
});
|
||||
|
||||
// Implement explicit fallback
|
||||
async function invokeWithFallback(name: string, body: object) {
|
||||
try {
|
||||
return await supabase.functions.invoke(name, {
|
||||
body,
|
||||
region: FunctionRegion.EuWest1,
|
||||
});
|
||||
} catch {
|
||||
return await supabase.functions.invoke(name, { body }); // Default region
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Available regions: us-east-1, us-west-1, us-west-2, eu-west-1, eu-west-2, eu-west-3, eu-central-1, ap-northeast-1, ap-northeast-2, ap-south-1, ap-southeast-1, ap-southeast-2, ca-central-1, sa-east-1.
|
||||
|
||||
Reference: [Regional Invocations](https://supabase.com/docs/guides/functions/regional-invocation)
|
||||
57
skills/supabase/references/edge-adv-streaming.md
Normal file
57
skills/supabase/references/edge-adv-streaming.md
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
title: Streaming Responses
|
||||
impact: MEDIUM
|
||||
impactDescription: Enables real-time data delivery and AI response streaming
|
||||
tags: edge-functions, streaming, sse, server-sent-events
|
||||
---
|
||||
|
||||
## Streaming Responses
|
||||
|
||||
Stream data progressively to clients using ReadableStream or Server-Sent Events.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```typescript
|
||||
// Missing error handling in stream callback
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(encoder.encode(JSON.stringify({ test: true })));
|
||||
// No try/catch/finally — errors silently lost, stream never closed
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```typescript
|
||||
// SSE stream with proper error handling
|
||||
Deno.serve(async (req) => {
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
try {
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
// SSE format: "data:" prefix + double newline
|
||||
controller.enqueue(encoder.encode(`data: ${JSON.stringify({ count: i })}\n\n`));
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Stream error:", err);
|
||||
} finally {
|
||||
controller.close();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return new Response(stream, {
|
||||
headers: {
|
||||
"Content-Type": "text/event-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
Connection: "keep-alive",
|
||||
},
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Both SSE-formatted streaming (with `data:` prefix) and raw streaming (without prefix) are valid. Use SSE format when consumed by `EventSource`; use raw streaming for AI model responses or binary data.
|
||||
71
skills/supabase/references/edge-adv-websockets.md
Normal file
71
skills/supabase/references/edge-adv-websockets.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
title: WebSocket Handling
|
||||
impact: MEDIUM
|
||||
impactDescription: Enables bidirectional real-time communication
|
||||
tags: edge-functions, websockets, realtime, bidirectional
|
||||
---
|
||||
|
||||
## WebSocket Handling
|
||||
|
||||
Create WebSocket servers for bidirectional communication. Browser clients cannot send custom headers, so pass JWT via query parameters. Deploy with `--no-verify-jwt`.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```typescript
|
||||
// Browser WebSocket cannot send custom headers
|
||||
const ws = new WebSocket(url, {
|
||||
headers: { Authorization: `Bearer ${token}` }, // Won't work!
|
||||
});
|
||||
|
||||
// Sending without checking socket state
|
||||
socket.send(data); // May throw if socket closed
|
||||
|
||||
// Deployed with JWT verification enabled
|
||||
npx supabase functions deploy websocket // Gateway blocks WebSocket upgrade
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```typescript
|
||||
// Server: WebSocket with query param auth
|
||||
import { createClient } from "npm:@supabase/supabase-js@2";
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
const url = new URL(req.url);
|
||||
const jwt = url.searchParams.get("jwt");
|
||||
|
||||
if (!jwt) return new Response("Missing token", { status: 403 });
|
||||
|
||||
// Verify JWT using getClaims()
|
||||
const supabase = createClient(
|
||||
Deno.env.get("SUPABASE_URL")!,
|
||||
Deno.env.get("SUPABASE_ANON_KEY")!,
|
||||
{ global: { headers: { Authorization: `Bearer ${jwt}` } } }
|
||||
);
|
||||
const { data, error } = await supabase.auth.getClaims();
|
||||
if (error || !data.claims?.sub) return new Response("Invalid token", { status: 403 });
|
||||
|
||||
// Verify this is a WebSocket upgrade request
|
||||
const upgrade = req.headers.get("upgrade") || "";
|
||||
if (upgrade.toLowerCase() !== "websocket") {
|
||||
return new Response("Expected WebSocket upgrade", { status: 400 });
|
||||
}
|
||||
|
||||
const { socket, response } = Deno.upgradeWebSocket(req);
|
||||
|
||||
socket.onmessage = (e) => {
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
socket.send(`Echo: ${e.data}`);
|
||||
}
|
||||
};
|
||||
|
||||
return response;
|
||||
});
|
||||
|
||||
// Client: pass JWT via query param (note: query params may be logged in some logging systems)
|
||||
const ws = new WebSocket(`wss://PROJECT.supabase.co/functions/v1/ws?jwt=${jwt}`);
|
||||
```
|
||||
|
||||
Deploy: `npx supabase functions deploy websocket --no-verify-jwt`
|
||||
|
||||
Reference: [WebSockets Guide](https://supabase.com/docs/guides/functions/websockets)
|
||||
64
skills/supabase/references/edge-auth-jwt-verification.md
Normal file
64
skills/supabase/references/edge-auth-jwt-verification.md
Normal file
@@ -0,0 +1,64 @@
|
||||
---
|
||||
title: JWT Verification and Authentication
|
||||
impact: CRITICAL
|
||||
impactDescription: Prevents unauthorized access and security vulnerabilities
|
||||
tags: edge-functions, jwt, authentication, jose, security
|
||||
---
|
||||
|
||||
## JWT Verification and Authentication
|
||||
|
||||
Secure Edge Functions with JWT verification. By default, the gateway validates JWTs. Use `supabase.auth.getClaims()` for most cases. Use jose library when building custom auth middleware. Disable verification with `--no-verify-jwt` for endpoints that handle auth independently (e.g., webhooks, WebSockets).
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```typescript
|
||||
// Old deprecated pattern using symmetric secret
|
||||
import { verify } from "djwt";
|
||||
const payload = await verify(token, JWT_SECRET, "HS256");
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```typescript
|
||||
// Simple: getClaims() verifies via JWKS (initial fetch required, then cached ~10min)
|
||||
const token = req.headers.get("Authorization")?.replace("Bearer ", "");
|
||||
const { data, error } = await supabase.auth.getClaims(token);
|
||||
if (error) return new Response("Invalid JWT", { status: 401 });
|
||||
const userId = data.claims.sub;
|
||||
```
|
||||
|
||||
`getClaims()` is tied to the supabase-js client and returns a fixed set of claims. Use jose directly when you need full control over verification (custom issuer, audience, clock tolerance) or access to the complete raw JWT payload:
|
||||
|
||||
```typescript
|
||||
import * as jose from "jsr:@panva/jose@6";
|
||||
|
||||
const JWKS = jose.createRemoteJWKSet(
|
||||
new URL(Deno.env.get("SUPABASE_URL")! + "/auth/v1/.well-known/jwks.json")
|
||||
);
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
if (req.method === "OPTIONS") return new Response("ok"); // Skip auth for preflight
|
||||
|
||||
const authHeader = req.headers.get("Authorization");
|
||||
if (!authHeader?.startsWith("Bearer ")) {
|
||||
return new Response("Unauthorized", { status: 401 });
|
||||
}
|
||||
|
||||
const issuer =
|
||||
Deno.env.get("SB_JWT_ISSUER") ?? Deno.env.get("SUPABASE_URL") + "/auth/v1";
|
||||
const { payload } = await jose.jwtVerify(authHeader.slice(7), JWKS, {
|
||||
issuer,
|
||||
});
|
||||
|
||||
return new Response(JSON.stringify({ userId: payload.sub }));
|
||||
});
|
||||
```
|
||||
|
||||
Separately, the API gateway verifies JWTs before they reach your function. Supabase is transitioning toward developer-controlled verification (explicit in-function checks) rather than implicit gateway validation — `verify_jwt` remains supported during this transition. To disable gateway-level verification, use either the CLI flag (`npx supabase functions deploy fn-name --no-verify-jwt`) or `config.toml`:
|
||||
|
||||
```toml
|
||||
[functions.fn-name]
|
||||
verify_jwt = false
|
||||
```
|
||||
|
||||
Reference: [Securing Functions](https://supabase.com/docs/guides/functions/auth)
|
||||
51
skills/supabase/references/edge-auth-rls-integration.md
Normal file
51
skills/supabase/references/edge-auth-rls-integration.md
Normal file
@@ -0,0 +1,51 @@
|
||||
---
|
||||
title: RLS Integration in Edge Functions
|
||||
impact: HIGH
|
||||
impactDescription: Ensures proper data isolation and security enforcement
|
||||
tags: edge-functions, rls, row-level-security, auth-context
|
||||
---
|
||||
|
||||
## RLS Integration in Edge Functions
|
||||
|
||||
Pass user authentication context to Supabase client for Row Level Security enforcement. Create the client inside the request handler to get per-request auth context. Use service role key only for admin operations.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```typescript
|
||||
// Client created outside handler - same auth for all requests
|
||||
const supabase = createClient(url, key, {
|
||||
global: { headers: { Authorization: "static-token" } },
|
||||
});
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
const { data } = await supabase.from("profiles").select("*");
|
||||
return Response.json(data);
|
||||
});
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```typescript
|
||||
import { createClient } from "npm:@supabase/supabase-js@2";
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
// Create client with per-request auth context
|
||||
const supabase = createClient(
|
||||
Deno.env.get("SUPABASE_URL")!,
|
||||
Deno.env.get("SUPABASE_ANON_KEY")!,
|
||||
{
|
||||
global: {
|
||||
headers: { Authorization: req.headers.get("Authorization")! },
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// RLS policies now apply to this user
|
||||
const { data, error } = await supabase.from("profiles").select("*");
|
||||
return Response.json({ data, error });
|
||||
});
|
||||
```
|
||||
|
||||
Use `SUPABASE_SERVICE_ROLE_KEY` for admin operations that need to bypass RLS (background jobs, webhooks). For verifying the user's identity, use `supabase.auth.getClaims()` on the per-request client.
|
||||
|
||||
Reference: [Connect to Postgres](https://supabase.com/docs/guides/functions/connect-to-postgres)
|
||||
48
skills/supabase/references/edge-db-direct-postgres.md
Normal file
48
skills/supabase/references/edge-db-direct-postgres.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
title: Direct Postgres Connections
|
||||
impact: MEDIUM
|
||||
impactDescription: Enables complex queries and ORM usage
|
||||
tags: edge-functions, postgres, drizzle, connection-pooling
|
||||
---
|
||||
|
||||
## Direct Postgres Connections
|
||||
|
||||
Connect directly to Postgres for complex queries or ORM usage. Use port 6543 (transaction pooler) and disable prepared statements. Always release connections back to the pool.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```typescript
|
||||
// Wrong port, missing prepare:false, connection leak
|
||||
import postgres from "postgres";
|
||||
|
||||
const client = postgres(connectionString); // Uses session mode port
|
||||
const db = drizzle(client); // Prepared statements fail
|
||||
|
||||
Deno.serve(async () => {
|
||||
const data = await db.select().from(users);
|
||||
return Response.json(data);
|
||||
// Connection never released!
|
||||
});
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```typescript
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
|
||||
Deno.serve(async () => {
|
||||
// Disable prefetch for transaction pooler mode
|
||||
const client = postgres(Deno.env.get("SUPABASE_DB_URL")!, {
|
||||
prepare: false,
|
||||
});
|
||||
const db = drizzle(client);
|
||||
|
||||
const data = await db.select().from(users);
|
||||
return Response.json(data);
|
||||
});
|
||||
```
|
||||
|
||||
Use `SUPABASE_DB_URL` (auto-injected) for the connection string. When using `deno.land/x/postgres` (not postgres-js), use a pool size of 1.
|
||||
|
||||
Reference: [Connect to Postgres](https://supabase.com/docs/guides/functions/connect-to-postgres)
|
||||
56
skills/supabase/references/edge-db-supabase-client.md
Normal file
56
skills/supabase/references/edge-db-supabase-client.md
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: Database Access with supabase-js
|
||||
impact: HIGH
|
||||
impactDescription: Primary method for database operations with RLS support
|
||||
tags: edge-functions, database, supabase-js, queries
|
||||
---
|
||||
|
||||
## Database Access with supabase-js
|
||||
|
||||
Access Supabase Postgres using the official JavaScript client. Always handle errors and use `.select()` after insert/update to get returned data.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```typescript
|
||||
// Missing error handling and .select() after insert
|
||||
const { data } = await supabase.from("posts").insert({ title: "Test" });
|
||||
console.log(data); // null - no data returned!
|
||||
|
||||
// Using .single() when multiple rows might match
|
||||
const { data } = await supabase
|
||||
.from("users")
|
||||
.select("*")
|
||||
.eq("role", "admin")
|
||||
.single(); // Crashes if multiple admins!
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```typescript
|
||||
import { createClient } from "npm:@supabase/supabase-js@2";
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
const supabase = createClient(
|
||||
Deno.env.get("SUPABASE_URL")!,
|
||||
Deno.env.get("SUPABASE_ANON_KEY")!,
|
||||
{ global: { headers: { Authorization: req.headers.get("Authorization")! } } }
|
||||
);
|
||||
|
||||
// Insert with .select() to get returned row
|
||||
const { data, error } = await supabase
|
||||
.from("posts")
|
||||
.insert({ title: "Test" })
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
return Response.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
|
||||
return Response.json(data);
|
||||
});
|
||||
```
|
||||
|
||||
Use `.single()` only when expecting exactly one row (e.g., by primary key). For multiple rows, omit it.
|
||||
|
||||
Reference: [JavaScript Client](https://supabase.com/docs/reference/javascript/select)
|
||||
52
skills/supabase/references/edge-dbg-limits.md
Normal file
52
skills/supabase/references/edge-dbg-limits.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
title: Limits and Troubleshooting
|
||||
impact: HIGH
|
||||
impactDescription: Prevents production failures and debugging bottlenecks
|
||||
tags: edge-functions, limits, debugging, troubleshooting
|
||||
---
|
||||
|
||||
## Limits and Troubleshooting
|
||||
|
||||
Understand Edge Function limits: 256MB memory, 2s CPU time, 150s (free) or 400s (paid) wall clock, and 150s request idle timeout (triggers 504 if no response sent). Check logs for debugging.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```typescript
|
||||
// Loading entire large dataset into memory
|
||||
const allData = await supabase.from("huge_table").select("*"); // Memory exceeded
|
||||
|
||||
// CPU-intensive without batching
|
||||
for (let i = 0; i < 1000000; i++) {
|
||||
heavyComputation(i); // CPU time exceeded
|
||||
}
|
||||
|
||||
// Await without timeout
|
||||
const data = await fetch(externalApi); // May hang forever -> 504 timeout
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```typescript
|
||||
// Paginate large queries
|
||||
const { data } = await supabase
|
||||
.from("huge_table")
|
||||
.select("*")
|
||||
.range(0, 100); // Fetch in batches
|
||||
|
||||
// Add timeout to external calls
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 5000);
|
||||
|
||||
const response = await fetch(externalApi, { signal: controller.signal });
|
||||
clearTimeout(timeout);
|
||||
|
||||
// Use background tasks for long operations
|
||||
EdgeRuntime.waitUntil(processInBatches(items, 100));
|
||||
return Response.json({ status: "processing" });
|
||||
```
|
||||
|
||||
Debug with: `npx supabase functions serve --inspect-mode brk` (or shorthand `--inspect`). Check `SB_REGION` and `SB_EXECUTION_ID` env vars for tracing (hosted environment only).
|
||||
|
||||
Common errors: 504 (timeout), 503 (boot error — syntax/import), 500 (uncaught exception), 401 (invalid JWT), 546 (resource limit — memory/CPU exceeded).
|
||||
|
||||
Reference: [Limits](https://supabase.com/docs/guides/functions/limits)
|
||||
63
skills/supabase/references/edge-dbg-testing.md
Normal file
63
skills/supabase/references/edge-dbg-testing.md
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: Testing Edge Functions
|
||||
impact: MEDIUM
|
||||
impactDescription: Ensures reliability before production deployment
|
||||
tags: edge-functions, testing, deno, unit-tests
|
||||
---
|
||||
|
||||
## Testing Edge Functions
|
||||
|
||||
Test Edge Functions locally using Deno's built-in test runner. Run `npx supabase start` first, then `npx supabase functions serve` before running tests.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```typescript
|
||||
// Running tests without serving functions
|
||||
deno test --allow-all tests/ // Connection refused!
|
||||
|
||||
// Using production credentials
|
||||
const supabase = createClient(
|
||||
"https://prod.supabase.co", // Never hardcode production!
|
||||
"prod-key"
|
||||
);
|
||||
|
||||
// Not cleaning up test data
|
||||
Deno.test("creates user", async () => {
|
||||
await supabase.from("users").insert({ email: "test@test.com" });
|
||||
// Test data accumulates!
|
||||
});
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```typescript
|
||||
// supabase/functions/tests/hello-world-test.ts
|
||||
import { assertEquals } from "jsr:@std/assert@1";
|
||||
import { createClient } from "npm:@supabase/supabase-js@2";
|
||||
import "jsr:@std/dotenv/load";
|
||||
|
||||
const supabase = createClient(
|
||||
Deno.env.get("SUPABASE_URL")!, // From .env
|
||||
Deno.env.get("SUPABASE_ANON_KEY")!
|
||||
);
|
||||
|
||||
Deno.test("hello-world returns greeting", async () => {
|
||||
const { data, error } = await supabase.functions.invoke("hello-world", {
|
||||
body: { name: "Test" },
|
||||
});
|
||||
|
||||
assertEquals(error, null);
|
||||
assertEquals(data.message, "Hello Test!");
|
||||
});
|
||||
|
||||
// Cleanup test data
|
||||
Deno.test("creates and cleans up user", async () => {
|
||||
const { data } = await supabase.from("users").insert({ email: "test@test.com" }).select().single();
|
||||
// ... assertions ...
|
||||
await supabase.from("users").delete().eq("id", data.id);
|
||||
});
|
||||
```
|
||||
|
||||
Run: `npx supabase start && npx supabase functions serve &` then `deno test --allow-all supabase/functions/tests/`
|
||||
|
||||
Reference: [Testing Guide](https://supabase.com/docs/guides/functions/unit-test)
|
||||
45
skills/supabase/references/edge-fun-project-structure.md
Normal file
45
skills/supabase/references/edge-fun-project-structure.md
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: Project Structure and Organization
|
||||
impact: HIGH
|
||||
impactDescription: Proper organization reduces cold starts and improves maintainability
|
||||
tags: edge-functions, structure, shared, organization
|
||||
---
|
||||
|
||||
## Project Structure and Organization
|
||||
|
||||
Organize Edge Functions with shared code in `_shared/` folder (underscore prefix excludes from deployment). Use hyphens for function names. Combine related functionality into "fat functions" to minimize cold starts.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```bash
|
||||
# Underscores cause URL issues, missing underscore on shared
|
||||
supabase/functions/
|
||||
shared/ # Will be deployed as function!
|
||||
cors.ts
|
||||
hello_world/ # Underscores problematic in URLs
|
||||
index.ts
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```bash
|
||||
# Hyphens for functions, underscore prefix for shared
|
||||
supabase/functions/
|
||||
import_map.json # Top-level import map
|
||||
_shared/ # Excluded from deployment
|
||||
cors.ts
|
||||
supabaseClient.ts
|
||||
hello-world/ # Use hyphens
|
||||
index.ts
|
||||
```
|
||||
|
||||
For VSCode/Cursor, create `.vscode/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"deno.enablePaths": ["./supabase/functions"],
|
||||
"deno.importMap": "./supabase/functions/import_map.json"
|
||||
}
|
||||
```
|
||||
|
||||
Reference: [Development Tips](https://supabase.com/docs/guides/functions/development-tips)
|
||||
40
skills/supabase/references/edge-fun-quickstart.md
Normal file
40
skills/supabase/references/edge-fun-quickstart.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Edge Functions Quick Start
|
||||
impact: CRITICAL
|
||||
impactDescription: Foundation for all Edge Function development
|
||||
tags: edge-functions, quickstart, deployment, cli, deno
|
||||
---
|
||||
|
||||
## Edge Functions Quick Start
|
||||
|
||||
Create and deploy serverless TypeScript functions that run globally at the edge on Deno runtime. Functions use `Deno.serve()` as the handler and have automatic access to environment variables: `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY`, and `SUPABASE_DB_URL`.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```typescript
|
||||
// Missing error handling for JSON parsing - crashes on invalid input
|
||||
Deno.serve(async (req) => {
|
||||
const { name } = await req.json();
|
||||
return new Response(`Hello ${name}`);
|
||||
});
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```typescript
|
||||
// Handle JSON parsing errors gracefully
|
||||
Deno.serve(async (req) => {
|
||||
try {
|
||||
const { name } = await req.json();
|
||||
return new Response(JSON.stringify({ message: `Hello ${name}!` }), {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
} catch {
|
||||
return new Response("Invalid JSON", { status: 400 });
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
CLI workflow: `npx supabase functions new hello-world`, then `npx supabase start && npx supabase functions serve` for local dev, and `npx supabase functions deploy hello-world` for production (after `npx supabase login` and `npx supabase link --project-ref PROJECT_ID`).
|
||||
|
||||
Reference: [Quickstart Guide](https://supabase.com/docs/guides/functions/quickstart)
|
||||
56
skills/supabase/references/edge-pat-background-tasks.md
Normal file
56
skills/supabase/references/edge-pat-background-tasks.md
Normal file
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: Background Tasks
|
||||
impact: MEDIUM-HIGH
|
||||
impactDescription: Enables fast responses for long-running operations
|
||||
tags: edge-functions, background, waituntil, async
|
||||
---
|
||||
|
||||
## Background Tasks
|
||||
|
||||
Execute long-running operations without blocking responses using `EdgeRuntime.waitUntil()`. Return immediately while processing continues in background. Background tasks still count against wall clock, CPU, and memory limits.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```typescript
|
||||
// Awaiting waitUntil defeats the purpose
|
||||
Deno.serve(async (req) => {
|
||||
await EdgeRuntime.waitUntil(sendEmail()); // Blocks response!
|
||||
return Response.json({ done: true });
|
||||
});
|
||||
|
||||
// No error handling in background task
|
||||
EdgeRuntime.waitUntil(riskyOperation()); // Errors silently lost
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```typescript
|
||||
Deno.serve(async (req) => {
|
||||
const { email, message } = await req.json();
|
||||
|
||||
// Start background task without await
|
||||
EdgeRuntime.waitUntil(
|
||||
sendEmail(email, message).catch((error) => {
|
||||
console.error("Background task failed:", error);
|
||||
})
|
||||
);
|
||||
|
||||
// Return immediately
|
||||
return Response.json({ status: "Email queued" });
|
||||
});
|
||||
|
||||
// Handle shutdown for long-running tasks
|
||||
addEventListener("beforeunload", (event) => {
|
||||
console.log("Shutting down:", event.detail?.reason);
|
||||
});
|
||||
|
||||
// Catch unhandled promise rejections in background tasks
|
||||
addEventListener("unhandledrejection", (ev) => {
|
||||
console.log("unhandledrejection", ev.reason);
|
||||
ev.preventDefault();
|
||||
});
|
||||
```
|
||||
|
||||
For local development, set `[edge_runtime] policy = "per_worker"` in config.toml (disables hot reload; requires manual restart via `npx supabase functions serve`).
|
||||
|
||||
Reference: [Background Tasks](https://supabase.com/docs/guides/functions/background-tasks)
|
||||
59
skills/supabase/references/edge-pat-cors.md
Normal file
59
skills/supabase/references/edge-pat-cors.md
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
title: CORS Configuration
|
||||
impact: HIGH
|
||||
impactDescription: Required for browser-based function invocation
|
||||
tags: edge-functions, cors, browser, preflight
|
||||
---
|
||||
|
||||
## CORS Configuration
|
||||
|
||||
Handle Cross-Origin Resource Sharing for browser-invoked Edge Functions. Always handle OPTIONS preflight requests and include CORS headers in all responses including errors.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```typescript
|
||||
// Missing OPTIONS handling and CORS headers in error response
|
||||
Deno.serve(async (req) => {
|
||||
try {
|
||||
const { name } = await req.json(); // Fails on OPTIONS (no body)
|
||||
return new Response(`Hello ${name}`);
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({ error: error.message }), {
|
||||
status: 500,
|
||||
// Missing CORS headers - browser can't read error!
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```typescript
|
||||
const corsHeaders = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
|
||||
};
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
// Handle preflight OPTIONS request
|
||||
if (req.method === "OPTIONS") {
|
||||
return new Response("ok", { headers: corsHeaders });
|
||||
}
|
||||
|
||||
try {
|
||||
const { name } = await req.json();
|
||||
return new Response(JSON.stringify({ message: `Hello ${name}!` }), {
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({ error: error.message }), {
|
||||
status: 400,
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Store shared CORS headers in `_shared/cors.ts` and import across functions.
|
||||
|
||||
Reference: [CORS Guide](https://supabase.com/docs/guides/functions/cors)
|
||||
60
skills/supabase/references/edge-pat-error-handling.md
Normal file
60
skills/supabase/references/edge-pat-error-handling.md
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
title: Error Handling Patterns
|
||||
impact: MEDIUM
|
||||
impactDescription: Improves reliability and debugging experience
|
||||
tags: edge-functions, errors, debugging, client-errors
|
||||
---
|
||||
|
||||
## Error Handling Patterns
|
||||
|
||||
Handle errors gracefully and return meaningful responses. Include helpful error messages in the response body — never expose stack traces, but `error.message` is fine. Always include CORS headers in error responses.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```typescript
|
||||
// Exposes internal details, wrong status code, missing CORS
|
||||
Deno.serve(async (req) => {
|
||||
try {
|
||||
const data = await riskyOperation();
|
||||
return Response.json(data);
|
||||
} catch (error) {
|
||||
return Response.json({
|
||||
error: error.message,
|
||||
stack: error.stack, // Leaks internal details!
|
||||
}); // Default 200 status for errors!
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```typescript
|
||||
import { corsHeaders } from "../_shared/cors.ts";
|
||||
|
||||
Deno.serve(async (req) => {
|
||||
if (req.method === "OPTIONS") {
|
||||
return new Response("ok", { headers: corsHeaders });
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await riskyOperation();
|
||||
return Response.json(data, {
|
||||
headers: corsHeaders,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Internal error:", error); // Log internally
|
||||
|
||||
return Response.json(
|
||||
{ error: error.message },
|
||||
{
|
||||
status: 500,
|
||||
headers: corsHeaders,
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Use appropriate status codes: 400 for validation errors, 401 for unauthorized, 404 for not found, 500 for server errors.
|
||||
|
||||
Reference: [Error Handling](https://supabase.com/docs/guides/functions/error-handling)
|
||||
48
skills/supabase/references/edge-pat-routing.md
Normal file
48
skills/supabase/references/edge-pat-routing.md
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
title: Routing and Multi-Route Functions
|
||||
impact: MEDIUM-HIGH
|
||||
impactDescription: Reduces cold starts and simplifies API architecture
|
||||
tags: edge-functions, routing, hono, url-pattern
|
||||
---
|
||||
|
||||
## Routing and Multi-Route Functions
|
||||
|
||||
Handle multiple routes in a single Edge Function to minimize cold starts. Use Hono framework for clean routing. Remember to set basePath matching the function name.
|
||||
|
||||
**Incorrect:**
|
||||
|
||||
```typescript
|
||||
// Many separate functions = many cold starts
|
||||
// supabase/functions/create-user/index.ts
|
||||
// supabase/functions/get-user/index.ts
|
||||
// supabase/functions/update-user/index.ts
|
||||
|
||||
// Also wrong: missing basePath causes route mismatches
|
||||
import { Hono } from "jsr:@hono/hono";
|
||||
const app = new Hono();
|
||||
app.get("/users", handler); // Won't match /functions/v1/api/users
|
||||
```
|
||||
|
||||
**Correct:**
|
||||
|
||||
```typescript
|
||||
import { Hono } from "jsr:@hono/hono";
|
||||
import { cors } from "jsr:@hono/hono/cors";
|
||||
|
||||
// Single function handling all user operations
|
||||
const app = new Hono().basePath("/api"); // Must match function name!
|
||||
|
||||
app.use("*", cors());
|
||||
|
||||
app.get("/users", (c) => c.json({ users: [] }));
|
||||
app.get("/users/:id", (c) => c.json({ id: c.req.param("id") }));
|
||||
app.post("/users", async (c) => c.json(await c.req.json(), 201));
|
||||
app.delete("/users/:id", (c) => c.json({ deleted: c.req.param("id") }));
|
||||
app.all("*", (c) => c.json({ error: "Not found" }, 404));
|
||||
|
||||
Deno.serve(app.fetch);
|
||||
```
|
||||
|
||||
Invoke as `https://PROJECT.supabase.co/functions/v1/api/users/123`.
|
||||
|
||||
Reference: [Routing Guide](https://supabase.com/docs/guides/functions/routing)
|
||||
Reference in New Issue
Block a user