diff --git a/apps/docs/content/guides/database/postgres/event-triggers.mdx b/apps/docs/content/guides/database/postgres/event-triggers.mdx index 924795093bc2f..78da36e91e9cf 100644 --- a/apps/docs/content/guides/database/postgres/event-triggers.mdx +++ b/apps/docs/content/guides/database/postgres/event-triggers.mdx @@ -55,43 +55,7 @@ EXECUTE FUNCTION dont_drop_function(); ### Example trigger function - auto enable Row Level Security -```sql -CREATE OR REPLACE FUNCTION rls_auto_enable() -RETURNS EVENT_TRIGGER -LANGUAGE plpgsql -SECURITY DEFINER -SET search_path = pg_catalog -AS $$ -DECLARE - cmd record; -BEGIN - FOR cmd IN - SELECT * - FROM pg_event_trigger_ddl_commands() - WHERE command_tag IN ('CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO') - AND object_type IN ('table','partitioned table') - LOOP - IF cmd.schema_name IS NOT NULL AND cmd.schema_name IN ('public') AND cmd.schema_name NOT IN ('pg_catalog','information_schema') AND cmd.schema_name NOT LIKE 'pg_toast%' AND cmd.schema_name NOT LIKE 'pg_temp%' THEN - BEGIN - EXECUTE format('alter table if exists %s enable row level security', cmd.object_identity); - RAISE LOG 'rls_auto_enable: enabled RLS on %', cmd.object_identity; - EXCEPTION - WHEN OTHERS THEN - RAISE LOG 'rls_auto_enable: failed to enable RLS on %', cmd.object_identity; - END; - ELSE - RAISE LOG 'rls_auto_enable: skip % (either system schema or not in enforced list: %.)', cmd.object_identity, cmd.schema_name; - END IF; - END LOOP; -END; -$$; - -DROP EVENT TRIGGER IF EXISTS ensure_rls; -CREATE EVENT TRIGGER ensure_rls -ON ddl_command_end -WHEN TAG IN ('CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO') -EXECUTE FUNCTION rls_auto_enable(); -``` +See how to [auto enable RLS for new tables](/docs/guides/database/postgres/row-level-security#auto-enable-rls-for-new-tables). ### Event trigger Functions and firing events diff --git a/apps/docs/content/guides/database/postgres/row-level-security.mdx b/apps/docs/content/guides/database/postgres/row-level-security.mdx index ce0c5a8b58c81..387bbcabc40fa 100644 --- a/apps/docs/content/guides/database/postgres/row-level-security.mdx +++ b/apps/docs/content/guides/database/postgres/row-level-security.mdx @@ -59,6 +59,50 @@ alter table "table_name" enable row level security; Once you have enabled RLS, no data will be accessible via the [API](/docs/guides/api) when using the public `anon` key, until you create policies. +## Auto-enable RLS for new tables + +If you want RLS enabled automatically for new tables, you can create an event trigger that runs after table creation. This uses a Postgres [event trigger](/docs/guides/database/postgres/event-triggers) to call `ALTER TABLE ... ENABLE ROW LEVEL SECURITY` on each newly created table. + +```sql +CREATE OR REPLACE FUNCTION rls_auto_enable() +RETURNS EVENT_TRIGGER +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path = pg_catalog +AS $$ +DECLARE + cmd record; +BEGIN + FOR cmd IN + SELECT * + FROM pg_event_trigger_ddl_commands() + WHERE command_tag IN ('CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO') + AND object_type IN ('table','partitioned table') + LOOP + IF cmd.schema_name IS NOT NULL AND cmd.schema_name IN ('public') AND cmd.schema_name NOT IN ('pg_catalog','information_schema') AND cmd.schema_name NOT LIKE 'pg_toast%' AND cmd.schema_name NOT LIKE 'pg_temp%' THEN + BEGIN + EXECUTE format('alter table if exists %s enable row level security', cmd.object_identity); + RAISE LOG 'rls_auto_enable: enabled RLS on %', cmd.object_identity; + EXCEPTION + WHEN OTHERS THEN + RAISE LOG 'rls_auto_enable: failed to enable RLS on %', cmd.object_identity; + END; + ELSE + RAISE LOG 'rls_auto_enable: skip % (either system schema or not in enforced list: %.)', cmd.object_identity, cmd.schema_name; + END IF; + END LOOP; +END; +$$; + +DROP EVENT TRIGGER IF EXISTS ensure_rls; +CREATE EVENT TRIGGER ensure_rls +ON ddl_command_end +WHEN TAG IN ('CREATE TABLE', 'CREATE TABLE AS', 'SELECT INTO') +EXECUTE FUNCTION rls_auto_enable(); +``` + +Note that this applies to tables created after the trigger is installed. Existing tables still need RLS enabled manually. + When a request is made without an authenticated user (e.g., no access token is provided or the session has expired), `auth.uid()` returns `null`. diff --git a/apps/docs/content/guides/functions/troubleshooting.mdx b/apps/docs/content/guides/functions/troubleshooting.mdx deleted file mode 100644 index 39fe091cca9fa..0000000000000 --- a/apps/docs/content/guides/functions/troubleshooting.mdx +++ /dev/null @@ -1,234 +0,0 @@ ---- -id: 'functions-debugging' -title: 'Troubleshooting Common Issues' -description: 'How to solve common problems and issues related to Edge Functions.' -subtitle: 'How to solve common problems and issues related to Edge Functions.' ---- - -{/* supa-mdx-lint-disable Rule001HeadingCase */} - -When developing Edge Functions, you can run into various issues during development, deployment, and at runtime. Most problems fall under these categories: - -- [Deployment issues](/docs/guides/functions/troubleshooting#deployment-issues) -- [Runtime issues](/docs/guides/functions/troubleshooting#runtime-issues) -- [Performance issues](/docs/guides/functions/troubleshooting#performance-optimization) -- [Local development problems](/docs/guides/functions/troubleshooting#local-development-issues) - -This guide will cover most of the common issues. - - - -Before troubleshooting, make sure you're using the latest version of the Supabase CLI: - -```bash -supabase --version -supabase update -``` - - - ---- - -## Deployment issues - -### Unable to deploy Edge Function - -1. **Check function syntax:** Run `deno check` on your function files locally -2. **Review dependencies:** Verify all imports are accessible and compatible with Deno -3. **Examine bundle size:** Large functions may fail to deploy - -```bash -# Check for syntax errors -deno check ./supabase/functions/your-function/index.ts - -# Deploy with verbose output -supabase functions deploy your-function --debug -``` - - - -If these steps don't resolve the issue, open a support ticket via the Supabase Dashboard and -include all output from the diagnostic commands. - - - -### Bundle size issues - -Functions have a 10MB source code limit. Check your bundle size: - -```bash -deno info /path/to/function/index.ts -``` - -Look for the "size" field in the output. If your bundle is too large: - -- Remove unused dependencies -- Use selective imports: `import { specific } from 'npm:package/specific'` -- Consider splitting large functions into smaller ones - ---- - -## Runtime issues - -### Edge Function takes too long to respond - -Functions have a 60-second execution limit. - -1. **Check function logs:** Navigate to Functions > [Your Function] > Logs in the dashboard -2. **Examine boot times:** Look for `booted` events and check for consistent boot times -3. **Identify bottlenecks:** Review your code for slow operations - - If the boot times are similar, it’s likely an issue with your function’s code, such as a large dependency, a slow API call, or a complex computation. You can try to optimize your code, reduce the size of your dependencies, or use caching techniques to improve the performance of your function. - - If only some of the `booted` events are slow, find the affected `region` in the metadata and submit a support request via the "Help" button at the top. - -```tsx -// ✅ Optimize database queries -const { data } = await supabase - .from('users') - .select('id, name') // Only select needed columns - .limit(10) - -// ❌ Avoid fetching large datasets -const { data } = await supabase.from('users').select('*') // Fetches all columns -``` - -### 546 Error Response - -The 546 error typically indicates resource exhaustion or code issues: - -- **Memory or CPU Limits:** Your function may have exceeded available resources. Check the resource usage metrics in your dashboard. -- **Event Loop Completion:** If logs show "Event loop completed," your function has implementation issues. You should check your function code for any syntax errors, infinite loops, or unresolved promises that might cause this error. - - You can also try running the function locally (using Supabase CLI **`functions serve`**) to see if you can debug the error. The local console should give a full stack trace on the error with line numbers of the source code. You can also refer to [Edge Functions examples](https://github.com/supabase/supabase/tree/master/examples/edge-functions) for guidance. - -Run the function locally with `supabase functions serve` to get detailed stack traces. - -### Unable to call Edge Function - -For invocation or CORS issues: - -1. **Review CORS configuration:** Check out the [CORS guide](/docs/guides/functions/cors), and ensure you've properly configured CORS headers -2. **Check function logs:** Look for errors in the Functions > Logs section -3. **Verify authentication:** Confirm JWT tokens and permissions are correct - -```tsx -// ✅ Proper CORS handling -Deno.serve(async (req) => { - if (req.method === 'OPTIONS') { - return new Response(null, { - status: 200, - headers: { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type, Authorization', - }, - }) - } - - // Your function logic here - return new Response('Success', { - headers: { 'Access-Control-Allow-Origin': '*' }, - }) -}) -``` - -There are two debugging tools available: Invocations and Logs. Invocations shows the Request and Response for each execution, while Logs shows any platform events, including deployments and errors. - ---- - -## Local development issues - -### Issues serving functions locally - -When `supabase functions serve` fails: - -1. **Use debug mode:** Run with the `--debug` flag for detailed output -2. **Check port availability:** Ensure ports `54321` and `8081` are available - -```bash -# Serve with debug output -supabase functions serve your-function --debug - -# Check specific port usage -lsof -i :54321 -``` - -If the problem persists, search the [Edge Runtime](https://github.com/supabase/edge-runtime) and [CLI](https://github.com/supabase/cli) repositories for similar error messages. - - - -If the output from the commands above does not help you to resolve the issue, open a support -ticket via the Supabase Dashboard (by clicking the "Help" button at the top right) and include all -output and details about your commands. - - - -## Performance optimization - -### Monitoring resource usage - -Track your function's performance through the dashboard: - -1. Navigate to Edge Functions > [Your Function] > Metrics -2. Review CPU, memory, and execution time charts -3. Identify potential problems in resource consumption - - - -Edge Functions have limited resources compared to traditional servers. Optimize for: - -- **Memory efficiency:** Avoid loading large datasets into memory -- **CPU optimization:** Minimize complex computations -- **Execution time:** Keep functions under 60 seconds - - - -### Understanding CPU limits - -An isolate is like a worker that can handle multiple requests for a function. It works until a time limit of 400 seconds is reached. Edge Functions use isolates with soft and hard CPU limits: - -1. **Soft Limit**: When the isolate hits the soft limit, it retires. This means it won't take on any new requests, but it will finish processing the ones it's already working on. It keeps going until it either hits the hard limit for CPU time or reaches the 400-second time limit, whichever comes first. -2. **Hard Limit**: If there are new requests after the soft limit is reached, a new isolate is created to handle them. The original isolate continues until it hits the hard limit or the time limit. This ensures that existing requests are completed, and new ones will be managed by a newly created isolate. - -### Dependency Analysis - -It’s important to optimize your dependencies for better performance. Large or unnecessary dependencies can significantly impact bundle size, boot time, and memory usage. - -**Deno Dependencies** - -Start by analyzing your dependency tree to understand what's being imported: - -```bash -# Basic dependency analysis -deno info /path/to/function/index.ts - -# With import map (if using one) -deno info --import-map=/path/to/import_map.json /path/to/function/index.ts -``` - -Review the output for: - -- **Large dependencies:** Look for packages that contribute significantly to bundle size -- **Redundant imports:** Multiple packages providing similar functionality -- **Outdated versions:** Dependencies that can be updated to more efficient versions -- **Unused imports:** Dependencies imported but not actually used in your code - -**NPM Dependencies** - -When using NPM modules, keep their impact on bundle size in mind. Many NPM packages are designed for Node.js and may include unnecessary polyfills or large dependency trees. - -Use selective imports to minimize overhead: - -```tsx -// ✅ Import specific submodules -import { Sheets } from 'npm:@googleapis/sheets' -import { JWT } from 'npm:google-auth-library/build/src/auth/jwtclient' - -// ❌ Import entire package -import * as googleapis from 'npm:googleapis' -import * as googleAuth from 'npm:google-auth-library' -``` - -- **Tree-shake aggressively:** Only import what you actually use -- **Choose lightweight alternatives:** Research smaller packages that provide the same functionality -- **Bundle analysis:** Use `deno info` before and after changes to measure impact -- **Version pinning:** Lock dependency versions to avoid unexpected size increases diff --git a/apps/docs/content/troubleshooting/edge-function-546-error-response.mdx b/apps/docs/content/troubleshooting/edge-function-546-error-response.mdx new file mode 100644 index 0000000000000..b5271bad1d91f --- /dev/null +++ b/apps/docs/content/troubleshooting/edge-function-546-error-response.mdx @@ -0,0 +1,56 @@ +--- +title = "Edge Function 546 error response" +topics = [ "functions" ] +keywords = [ "546", "error", "resource", "memory", "cpu", "event loop", "edge function" ] + +[[errors]] +http_status_code = 546 +message = "Edge Function error" +--- + +The 546 error typically indicates resource exhaustion or code issues in your Edge Function. + +## Common causes + +### Memory or CPU limits + +Your function may have exceeded available resources. Check the resource usage metrics in your dashboard at Functions > [Your Function] > Metrics. + +### Event loop completion + +If logs show "Event loop completed," your function has implementation issues. Check your function code for: + +- Syntax errors +- Infinite loops +- Unresolved promises + +## Debugging steps + +### Run the function locally + +Use the Supabase CLI to run your function locally and get detailed stack traces: + +```bash +supabase functions serve your-function --debug +``` + +The local console provides full stack traces with line numbers, making it easier to identify the source of errors. + +### Check function logs + +Navigate to Functions > [Your Function] > Logs in the dashboard. Look for: + +- Error messages +- Resource usage warnings +- Shutdown events with specific reasons + +### Review example implementations + +Compare your function against working examples in the [Edge Functions examples repository](https://github.com/supabase/supabase/tree/master/examples/edge-functions). + +## Additional resources + +- [Edge Function shutdown reasons explained](./edge-function-shutdown-reasons-explained) +- [Edge Function wall clock time limit reached](./edge-function-wall-clock-time-limit-reached-Nk38bW) +- [Monitoring resource usage](./edge-function-monitoring-resource-usage) +- [Debugging Edge Functions](/docs/guides/functions/logging) diff --git a/apps/docs/content/troubleshooting/edge-function-bundle-size-issues.mdx b/apps/docs/content/troubleshooting/edge-function-bundle-size-issues.mdx new file mode 100644 index 0000000000000..cb3104a2d0399 --- /dev/null +++ b/apps/docs/content/troubleshooting/edge-function-bundle-size-issues.mdx @@ -0,0 +1,54 @@ +--- +title = "Edge Function bundle size issues" +topics = [ "functions" ] +keywords = [ "bundle", "size", "limit", "dependencies", "edge function", "10MB" ] + +[api] +cli = ["supabase-functions-deploy"] +--- + +Edge Functions have a 10MB source code limit. If your function exceeds this limit, deployment will fail. + +## Check your bundle size + +Use the `deno info` command to analyze your function's dependencies and total size: + +```bash +deno info /path/to/function/index.ts +``` + +Look for the "size" field in the output to see the total bundle size. + +## How to reduce bundle size + +If your bundle is too large, try these strategies: + +### Remove unused dependencies + +Review your imports and remove any packages you're not actively using. + +### Use selective imports + +Instead of importing entire packages, import only the specific modules you need: + +```tsx +// Good: Import specific submodules +import { specific } from 'npm:package/specific' + +// Avoid: Import entire package +import * as everything from 'npm:package' +``` + +### Split large functions + +Consider breaking large functions into smaller, more focused functions. Each function can handle a specific task, reducing the code needed in any single deployment. + +### Choose lightweight alternatives + +Research smaller packages that provide the same functionality. Many NPM packages designed for Node.js include unnecessary polyfills that increase bundle size. + +## Additional resources + +- [Unable to deploy Edge Function](./unable-to-deploy-edge-function) +- [Dependency analysis](./edge-function-dependency-analysis) +- [Edge Function limits](/docs/guides/functions/limits) diff --git a/apps/docs/content/troubleshooting/edge-function-cpu-limits.mdx b/apps/docs/content/troubleshooting/edge-function-cpu-limits.mdx new file mode 100644 index 0000000000000..bef2d93305722 --- /dev/null +++ b/apps/docs/content/troubleshooting/edge-function-cpu-limits.mdx @@ -0,0 +1,68 @@ +--- +title = "Understanding Edge Function CPU limits" +topics = [ "functions" ] +keywords = [ "CPU", "limit", "isolate", "soft limit", "hard limit", "edge function" ] +--- + +Learn how Edge Functions manage CPU resources and what happens when limits are reached. + +## How isolates work + +An isolate is like a worker that can handle multiple requests for a function. It works until a time limit of 400 seconds is reached. Edge Functions use isolates with soft and hard CPU limits. + +## Soft limit + +When the isolate hits the soft limit, it **retires**. This means: + +- It won't take on any new requests +- It will finish processing requests it's already working on +- It keeps going until it hits the hard limit for CPU time or reaches the 400-second time limit, whichever comes first + +## Hard limit + +If there are new requests after the soft limit is reached: + +- A new isolate is created to handle them +- The original isolate continues until it hits the hard limit or the time limit +- This ensures existing requests are completed while new ones are managed by a fresh isolate + +## Current limits + +- **Wall clock time limit:** 400 seconds total duration +- **CPU execution time:** 200 milliseconds of active computing + +## What happens when limits are exceeded + +When your function exceeds CPU limits, you may see: + +- 546 error responses +- Function termination with `CPUTime` shutdown reason +- Degraded performance as new isolates spin up + +## Optimizing CPU usage + +### Profile your code + +Identify CPU-intensive sections in your function: + +- Complex calculations +- Data processing loops +- Encryption operations + +### Optimize algorithms + +- Use more efficient data structures +- Cache computed results +- Reduce algorithmic complexity + +### Offload heavy work + +- Move intensive processing to background jobs +- Use external services for heavy computations +- Break large tasks into smaller functions + +## Additional resources + +- [Monitoring resource usage](./edge-function-monitoring-resource-usage) +- [Edge Function shutdown reasons explained](./edge-function-shutdown-reasons-explained) +- [Edge Function limits](/docs/guides/functions/limits) diff --git a/apps/docs/content/troubleshooting/edge-function-dependency-analysis.mdx b/apps/docs/content/troubleshooting/edge-function-dependency-analysis.mdx new file mode 100644 index 0000000000000..271ab44020a54 --- /dev/null +++ b/apps/docs/content/troubleshooting/edge-function-dependency-analysis.mdx @@ -0,0 +1,87 @@ +--- +title = "Edge Function dependency analysis" +topics = [ "functions" ] +keywords = [ "dependencies", "npm", "deno", "imports", "bundle", "optimization", "edge function" ] +--- + +Optimize your Edge Function dependencies for better performance. Large or unnecessary dependencies can significantly impact bundle size, boot time, and memory usage. + +## Analyzing Deno dependencies + +Start by analyzing your dependency tree to understand what's being imported: + +```bash +# Basic dependency analysis +deno info /path/to/function/index.ts + +# With import map (if using one) +deno info --import-map=/path/to/import_map.json /path/to/function/index.ts +``` + +## What to look for + +Review the output for: + +- **Large dependencies:** Packages that contribute significantly to bundle size +- **Redundant imports:** Multiple packages providing similar functionality +- **Outdated versions:** Dependencies that can be updated to more efficient versions +- **Unused imports:** Dependencies imported but not actually used in your code + +## Optimizing NPM dependencies + +When using NPM modules, keep their impact on bundle size in mind. Many NPM packages are designed for Node.js and may include unnecessary polyfills or large dependency trees. + +### Use selective imports + +Import specific submodules to minimize overhead: + +```tsx +// Good: Import specific submodules +import { Sheets } from 'npm:@googleapis/sheets' +import { JWT } from 'npm:google-auth-library/build/src/auth/jwtclient' + +// Avoid: Import entire package +import * as googleapis from 'npm:googleapis' +import * as googleAuth from 'npm:google-auth-library' +``` + +## Best practices + +### Tree-shake aggressively + +Only import what you actually use. Avoid wildcard imports (`import *`) when possible. + +### Choose lightweight alternatives + +Research smaller packages that provide the same functionality. Consider: + +- Native Deno APIs instead of NPM polyfills +- Focused single-purpose packages instead of large utility libraries + +### Bundle analysis + +Use `deno info` before and after changes to measure the impact of dependency modifications. + +### Version pinning + +Lock dependency versions to avoid unexpected size increases from automatic updates: + +```tsx +// Pin to specific version +import { something } from 'npm:package@1.2.3' +``` + +## Common heavy dependencies + +Watch out for these commonly heavy packages: + +- Full AWS SDK (use individual service packages instead) +- Moment.js (consider [`date-fns`](https://date-fns.org/)) or native Date APIs) +- Lodash (import specific functions: `lodash/get`) +- Full Google APIs (use specific service packages) + +## Additional resources + +- [Bundle size issues](./edge-function-bundle-size-issues) +- [Edge Function takes too long to respond](./edge-function-takes-too-long-to-respond) +- [Edge Function limits](/docs/guides/functions/limits) diff --git a/apps/docs/content/troubleshooting/edge-function-monitoring-resource-usage.mdx b/apps/docs/content/troubleshooting/edge-function-monitoring-resource-usage.mdx new file mode 100644 index 0000000000000..424d2898b393d --- /dev/null +++ b/apps/docs/content/troubleshooting/edge-function-monitoring-resource-usage.mdx @@ -0,0 +1,90 @@ +--- +title = "Monitoring Edge Function resource usage" +topics = [ "functions" ] +keywords = [ "monitoring", "metrics", "CPU", "memory", "performance", "edge function" ] +--- + +Learn how to track your Edge Function's performance and identify potential resource issues. + +## Accessing metrics + +Track your function's performance through the dashboard: + +1. Navigate to Edge Functions > [Your Function] > Metrics +2. Review CPU, memory, and execution time charts +3. Identify potential problems in resource consumption + +## Key metrics to monitor + +### CPU usage + +High CPU usage indicates your function is performing intensive computations. Consider: + +- Optimizing algorithms +- Caching computed results +- Moving heavy processing to background jobs + +### Memory usage + +Memory spikes can cause function failures. Watch for: + +- Large datasets loaded into memory +- Accumulated objects that aren't garbage collected +- Large dependency bundles + +### Execution time + +Long execution times can lead to timeouts. Monitor for: + +- Slow external API calls +- Inefficient database queries +- Complex computational operations + +## Resource limits + +Edge Functions have limited resources compared to traditional servers. Optimize for: + +- **Memory efficiency:** Avoid loading large datasets into memory +- **CPU optimization:** Minimize complex computations +- **Execution time:** Keep functions under 60 seconds + +## Best practices + +### Use streaming for large data + +Instead of loading entire files into memory, stream data: + +```tsx +// Stream responses instead of buffering +return new Response(readableStream, { + headers: { 'Content-Type': 'application/json' }, +}) +``` + +### Process data in chunks + +Break large operations into smaller batches: + +```tsx +for (const batch of dataBatches) { + await processBatch(batch) +} +``` + +### Cache expensive operations + +Store results of expensive computations to avoid recalculating: + +```tsx +const cachedResult = await cache.get(key) +if (cachedResult) { + return cachedResult +} +``` + +## Additional resources + +- [Edge Function CPU limits](./edge-function-cpu-limits) +- [Edge Function shutdown reasons explained](./edge-function-shutdown-reasons-explained) +- [Edge Function limits](/docs/guides/functions/limits) +- [Debugging Edge Functions](/docs/guides/functions/logging) diff --git a/apps/docs/content/troubleshooting/edge-function-takes-too-long-to-respond.mdx b/apps/docs/content/troubleshooting/edge-function-takes-too-long-to-respond.mdx new file mode 100644 index 0000000000000..75597f52f89f9 --- /dev/null +++ b/apps/docs/content/troubleshooting/edge-function-takes-too-long-to-respond.mdx @@ -0,0 +1,46 @@ +--- +title = "Edge Function takes too long to respond" +topics = [ "functions" ] +keywords = [ "slow", "timeout", "performance", "boot", "response time", "edge function" ] +--- + +Edge Functions have a 60-second execution limit. If your function is taking too long to respond, follow these steps to diagnose and optimize performance. + +## Diagnose the issue + +1. **Check function logs:** Navigate to Functions > [Your Function] > Logs in the dashboard +2. **Examine boot times:** Look for `booted` events and check for consistent boot times +3. **Identify bottlenecks:** Review your code for slow operations + +## Analyze boot time patterns + +- **If boot times are consistently slow:** The issue is likely in your function's code, such as a large dependency, a slow API call, or a complex computation. Focus on optimizing your code, reducing dependency size, or implementing caching. + +- **If only some boot events are slow:** Find the affected `region` in the metadata and submit a support request via the "Help" button at the top of the dashboard. + +## Optimize database queries + +Inefficient database queries are a common cause of slow responses: + +```tsx +// Good: Only select needed columns and limit results +const { data } = await supabase.from('users').select('id, name').limit(10) + +// Avoid: Fetching all columns from large tables +const { data } = await supabase.from('users').select('*') +``` + +## Common causes of slow functions + +- **Large dependencies:** Heavy packages increase boot time +- **Slow external API calls:** Third-party services with high latency +- **Complex computations:** CPU-intensive operations +- **Unoptimized database queries:** Fetching more data than needed +- **Cold starts:** First invocation after idle period takes longer + +## Additional resources + +- [Edge Function CPU limits](./edge-function-cpu-limits) +- [Monitoring resource usage](./edge-function-monitoring-resource-usage) +- [Debugging Edge Functions](/docs/guides/functions/logging) +- [Edge Function limits](/docs/guides/functions/limits) diff --git a/apps/docs/content/troubleshooting/issues-serving-edge-functions-locally.mdx b/apps/docs/content/troubleshooting/issues-serving-edge-functions-locally.mdx new file mode 100644 index 0000000000000..2a2da6b188472 --- /dev/null +++ b/apps/docs/content/troubleshooting/issues-serving-edge-functions-locally.mdx @@ -0,0 +1,71 @@ +--- +title = "Issues serving Edge Functions locally" +topics = [ "functions", "cli" ] +keywords = [ "local", "serve", "development", "debug", "port", "edge function" ] + +[api] +cli = ["supabase-functions-serve"] +--- + +If `supabase functions serve` fails or you're having trouble running Edge Functions locally, follow these steps to diagnose and resolve the issue. + +## Debugging steps + +### Use debug mode + +Run the serve command with the `--debug` flag for detailed output: + +```bash +supabase functions serve your-function --debug +``` + +### Check port availability + +Ensure the required ports are available. The Supabase CLI uses ports `54321` and `8081` by default: + +```bash +# Check if port 54321 is in use +lsof -i :54321 + +# Check if port 8081 is in use +lsof -i :8081 +``` + +If these ports are in use, stop the processes using them or configure different ports. + +## Common issues + +### Port conflicts + +Another process may be using the required ports. Check for: + +- Other Supabase projects running locally +- Docker containers +- Other development servers + +### Deno cache issues + +Clear the Deno cache if you're experiencing module resolution problems: + +```bash +deno cache --reload /path/to/function/index.ts +``` + +### Environment variables + +Make sure your `.env` file is properly configured and accessible to the CLI. + +## Getting more help + +If the problem persists, search the following repositories for similar error messages: + +- [Edge Runtime repository](https://github.com/supabase/edge-runtime) +- [CLI repository](https://github.com/supabase/cli) + +If the output from these commands does not help resolve the issue, open a support ticket via the Supabase Dashboard (by clicking the "Help" button at the top right) and include all output and details about your commands. + +## Additional resources + +- [Local development guide](/docs/guides/cli/local-development) +- [Edge Functions quickstart](/docs/guides/functions/quickstart) +- [Debugging Edge Functions](/docs/guides/functions/logging) diff --git a/apps/docs/content/troubleshooting/unable-to-call-edge-function.mdx b/apps/docs/content/troubleshooting/unable-to-call-edge-function.mdx new file mode 100644 index 0000000000000..f537a204c0818 --- /dev/null +++ b/apps/docs/content/troubleshooting/unable-to-call-edge-function.mdx @@ -0,0 +1,73 @@ +--- +title = "Unable to call Edge Function" +topics = [ "functions" ] +keywords = [ "invoke", "call", "CORS", "authentication", "JWT", "edge function" ] +--- + +If you're having trouble invoking an Edge Function or experiencing CORS issues, follow these steps to diagnose and resolve the problem. + +## Diagnose the issue + +1. **Review CORS configuration:** Check out the [CORS guide](/docs/guides/functions/cors) and ensure you've properly configured CORS headers +2. **Check function logs:** Look for errors in Functions > Logs in the dashboard +3. **Verify authentication:** Confirm JWT tokens and permissions are correct + +## Proper CORS handling + +Make sure your function handles OPTIONS preflight requests: + +```tsx +Deno.serve(async (req) => { + // Handle CORS preflight requests + if (req.method === 'OPTIONS') { + return new Response(null, { + status: 200, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + }, + }) + } + + // Your function logic here + return new Response('Success', { + headers: { 'Access-Control-Allow-Origin': '*' }, + }) +}) +``` + +## Debugging tools + +Supabase provides two debugging tools for Edge Functions: + +- **Invocations:** Shows the Request and Response for each execution +- **Logs:** Shows platform events, including deployments and errors + +Access these tools by navigating to Functions > [Your Function] in the dashboard. + +## Common issues + +### CORS errors + +- Missing `Access-Control-Allow-Origin` header in response +- Not handling OPTIONS preflight requests +- Mismatched allowed methods or headers + +### Authentication errors + +- Invalid or expired JWT token +- Missing Authorization header +- Incorrect permissions for the authenticated user + +### Network errors + +- Function not deployed +- Incorrect function URL +- Network connectivity issues + +## Additional resources + +- [CORS guide](/docs/guides/functions/cors) +- [Edge Functions authentication](/docs/guides/functions/auth) +- [Debugging Edge Functions](/docs/guides/functions/logging) diff --git a/apps/docs/content/troubleshooting/unable-to-deploy-edge-function.mdx b/apps/docs/content/troubleshooting/unable-to-deploy-edge-function.mdx new file mode 100644 index 0000000000000..a125946930dff --- /dev/null +++ b/apps/docs/content/troubleshooting/unable-to-deploy-edge-function.mdx @@ -0,0 +1,48 @@ +--- +title = "Unable to deploy Edge Function" +topics = [ "functions" ] +keywords = [ "deploy", "deployment", "edge function", "deno", "syntax", "bundle" ] + +[api] +cli = ["supabase-functions-deploy"] +--- + +If you're having trouble deploying an Edge Function, follow these steps to diagnose and resolve the issue. + +## Diagnose the issue + +1. **Check function syntax:** Run `deno check` on your function files locally +2. **Review dependencies:** Verify all imports are accessible and compatible with Deno +3. **Examine bundle size:** Large functions may fail to deploy + +```bash +# Check for syntax errors +deno check ./supabase/functions/your-function/index.ts + +# Deploy with verbose output +supabase functions deploy your-function --debug +``` + +## Common causes + +- **Syntax errors:** TypeScript or JavaScript syntax issues in your function code +- **Invalid imports:** Importing modules that don't exist or aren't compatible with Deno +- **Large bundle size:** Functions have a 10MB source code limit. See [Bundle size issues](./edge-function-bundle-size-issues) for more details +- **Network issues:** Problems reaching the Supabase API during deployment + +## Before opening a support ticket + +Make sure you're using the latest version of the Supabase CLI: + +```bash +supabase --version +supabase update +``` + +If these steps don't resolve the issue, open a support ticket via the Supabase Dashboard and include all output from the diagnostic commands. + +## Additional resources + +- [Edge Functions guide](/docs/guides/functions) +- [Bundle size issues](./edge-function-bundle-size-issues) +- [Edge Function limits](/docs/guides/functions/limits) diff --git a/apps/studio/components/interfaces/ProjectCreation/SecurityOptions.tsx b/apps/studio/components/interfaces/ProjectCreation/SecurityOptions.tsx index 7e41b66222c04..7c0b44857dd80 100644 --- a/apps/studio/components/interfaces/ProjectCreation/SecurityOptions.tsx +++ b/apps/studio/components/interfaces/ProjectCreation/SecurityOptions.tsx @@ -1,8 +1,7 @@ -import { UseFormReturn } from 'react-hook-form' - import Panel from 'components/ui/Panel' import { usePHFlag } from 'hooks/ui/useFlag' import Link from 'next/link' +import { UseFormReturn } from 'react-hook-form' import { Checkbox_Shadcn_, FormControl_Shadcn_, @@ -14,6 +13,7 @@ import { } from 'ui' import { Admonition } from 'ui-patterns' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' + import { CreateProjectForm } from './ProjectCreation.schema' interface SecurityOptionsProps { @@ -93,9 +93,11 @@ export const SecurityOptions = ({ form, layout = 'horizontal' }: SecurityOptions )} {!dataApi && ( - - You will not be able to query or mutate data via Supabase client libraries like - supabase-js. + + Disabling it means supabase-js and similar libraries can't query or mutate data. )} diff --git a/e2e/studio/features/api-access-toggle.spec.ts b/e2e/studio/features/api-access-toggle.spec.ts index fe6c7083c7c99..85269eb6c2f47 100644 --- a/e2e/studio/features/api-access-toggle.spec.ts +++ b/e2e/studio/features/api-access-toggle.spec.ts @@ -1,113 +1,19 @@ import { expect, Page } from '@playwright/test' +import { query } from '../utils/db/index.js' import { test } from '../utils/test.js' import { toUrl } from '../utils/to-url.js' -import { - createApiResponseWaiter, - waitForApiResponse, - waitForTableToLoad, -} from '../utils/wait-for-response.js' +import { createApiResponseWaiter, waitForTableToLoad } from '../utils/wait-for-response.js' import { dismissToastsIfAny } from '../utils/dismiss-toast.js' import { openTableContextMenu, deleteTable } from '../utils/table-helpers.js' const TABLE_NAME_PREFIX = 'pw_api_access' /** - * The API privilege types we care about for Data API access. - * Filters out other PostgreSQL privileges like REFERENCES, TRIGGER, TRUNCATE. + * Verifies that the table has the expected API privileges for anon and authenticated roles. + * Uses the database utility to query privileges directly. */ -const API_PRIVILEGE_TYPES = ['SELECT', 'INSERT', 'UPDATE', 'DELETE'] - -/** - * Executes a SQL query via the SQL Editor and returns the result. - * This is used to verify database state directly. - */ -async function executeSql( - page: Page, - ref: string, - sql: string -): Promise>> { - // Create API response waiter for content/count which indicates project is loaded - const contentCountWaiter = createApiResponseWaiter( - page, - 'platform/projects', - ref, - 'content/count' - ) - - // Navigate to SQL editor - await page.goto(toUrl(`/project/${ref}/sql/new?skip=true`)) - - // Wait for content/count API response to ensure project is fully loaded - await contentCountWaiter - - await expect(page.getByText('Loading...')).not.toBeVisible({ timeout: 10000 }) - - // Clear and type the SQL - await page.locator('.view-lines').click() - await page.keyboard.press('ControlOrMeta+KeyA') - await page.keyboard.type(sql) - - // Run the query - const sqlMutationPromise = waitForApiResponse(page, 'pg-meta', ref, 'query?key=', { - method: 'POST', - }) - await page.getByTestId('sql-run-button').click() - await sqlMutationPromise - - // Wait for either results grid or "Success. No rows returned" message - const grid = page.getByRole('grid') - const noRowsMessage = page.getByText('Success. No rows returned') - - // Wait for either element to appear - await expect(grid.or(noRowsMessage)).toBeVisible({ timeout: 10000 }) - - // If no rows returned, return empty array - if (await noRowsMessage.isVisible().catch(() => false)) { - return [] - } - - // If grid is not visible (shouldn't happen if we get here, but just in case) - if ((await grid.count()) === 0) { - return [] - } - - // Extract column headers - const headers: Array = [] - const headerCells = grid.getByRole('columnheader') - const headerCount = await headerCells.count() - for (let i = 0; i < headerCount; i++) { - const text = await headerCells.nth(i).textContent() - if (text) headers.push(text.trim()) - } - - // Extract row data - const results: Array> = [] - const rows = grid.getByRole('row') - const rowCount = await rows.count() - - // Skip first row (header row) - for (let i = 1; i < rowCount; i++) { - const cells = rows.nth(i).getByRole('gridcell') - const cellCount = await cells.count() - const row: Record = {} - - for (let j = 0; j < Math.min(cellCount, headers.length); j++) { - const cellText = await cells.nth(j).textContent() - row[headers[j]] = cellText?.trim() ?? null - } - - if (Object.keys(row).length > 0) { - results.push(row) - } - } - - return results -} - async function verifyTablePrivileges( - page: Page, - ref: string, schemaName: string, tableName: string, expectedPrivileges: { @@ -115,17 +21,16 @@ async function verifyTablePrivileges( authenticated: Array } ) { - const sql = ` - SELECT grantee, privilege_type - FROM information_schema.role_table_grants - WHERE table_schema = '${schemaName}' - AND table_name = '${tableName}' - AND grantee IN ('anon', 'authenticated') - AND privilege_type IN ('SELECT', 'INSERT', 'UPDATE', 'DELETE') - ORDER BY grantee, privilege_type; - ` - - const results = await executeSql(page, ref, sql) + const results = await query<{ grantee: string; privilege_type: string }>( + `SELECT grantee, privilege_type + FROM information_schema.role_table_grants + WHERE table_schema = $1 + AND table_name = $2 + AND grantee IN ('anon', 'authenticated') + AND privilege_type IN ('SELECT', 'INSERT', 'UPDATE', 'DELETE') + ORDER BY grantee, privilege_type`, + [schemaName, tableName] + ) // Group privileges by grantee const actualPrivileges: Record> = { @@ -134,15 +39,8 @@ async function verifyTablePrivileges( } for (const row of results) { - const grantee = row['grantee'] as string - const privilegeType = row['privilege_type'] as string - if ( - grantee && - privilegeType && - (grantee === 'anon' || grantee === 'authenticated') && - API_PRIVILEGE_TYPES.includes(privilegeType) - ) { - actualPrivileges[grantee].push(privilegeType) + if (row.grantee === 'anon' || row.grantee === 'authenticated') { + actualPrivileges[row.grantee].push(row.privilege_type) } } @@ -241,7 +139,7 @@ test.describe('API Access Toggle', () => { ).toBeVisible() // Verify all API access privileges were granted - await verifyTablePrivileges(page, ref, 'public', tableName, { + await verifyTablePrivileges('public', tableName, { anon: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'], authenticated: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'], }) @@ -291,7 +189,7 @@ test.describe('API Access Toggle', () => { ).toBeVisible() // Verify no API access privileges were granted - await verifyTablePrivileges(page, ref, 'public', tableName, { + await verifyTablePrivileges('public', tableName, { anon: [], authenticated: [], }) @@ -327,7 +225,7 @@ test.describe('API Access Toggle', () => { ).toBeVisible() // Verify default full privileges were granted - await verifyTablePrivileges(page, ref, 'public', tableName, { + await verifyTablePrivileges('public', tableName, { anon: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'], authenticated: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'], }) @@ -437,7 +335,7 @@ test.describe('API Access Toggle', () => { ).toBeVisible() // Verify partial grants - anon: SELECT; authenticated: SELECT, INSERT - await verifyTablePrivileges(page, ref, 'public', tableName, { + await verifyTablePrivileges('public', tableName, { anon: ['SELECT'], authenticated: ['SELECT', 'INSERT'], }) @@ -490,7 +388,7 @@ test.describe('API Access Toggle', () => { ).toBeVisible() // Verify initial privileges before edit - await verifyTablePrivileges(page, ref, 'public', tableName, { + await verifyTablePrivileges('public', tableName, { anon: ['SELECT', 'INSERT'], authenticated: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'], }) @@ -529,7 +427,7 @@ test.describe('API Access Toggle', () => { await page.waitForSelector('[data-testid="table-editor-side-panel"]', { state: 'detached' }) // Step 3: Verify the privileges remain unchanged after edit - await verifyTablePrivileges(page, ref, 'public', tableName, { + await verifyTablePrivileges('public', tableName, { anon: ['SELECT', 'INSERT'], authenticated: ['SELECT', 'INSERT', 'UPDATE', 'DELETE'], }) diff --git a/e2e/studio/utils/db/client.ts b/e2e/studio/utils/db/client.ts new file mode 100644 index 0000000000000..f8f1c0689fb42 --- /dev/null +++ b/e2e/studio/utils/db/client.ts @@ -0,0 +1,33 @@ +// Default Supabase CLI constants (hardcoded for local development) +const SERVICE_ROLE_KEY = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU' +const API_URL = 'http://127.0.0.1:54321' + +/** + * Execute a SQL query against the local Supabase database via pg-meta. + * + * Uses the local Supabase API gateway to route to pg-meta, which executes + * the query against PostgreSQL using the default local connection. + * + * @param sql - The SQL query to execute + * @param params - Optional array of parameters for parameterized queries + * @returns Array of result rows + * @throws Error if the query fails + */ +export async function query(sql: string, params?: Array): Promise> { + const response = await fetch(`${API_URL}/pg/query`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + apikey: SERVICE_ROLE_KEY, + }, + body: JSON.stringify({ query: sql, parameters: params }), + }) + + if (!response.ok) { + const error = await response.json() + throw new Error(`Database query failed: ${error.message || JSON.stringify(error)}`) + } + + return response.json() +} diff --git a/e2e/studio/utils/db/index.ts b/e2e/studio/utils/db/index.ts new file mode 100644 index 0000000000000..030c48412ccd3 --- /dev/null +++ b/e2e/studio/utils/db/index.ts @@ -0,0 +1,2 @@ +export { query } from './client.js' +export { tableExists } from './queries.js' diff --git a/e2e/studio/utils/db/queries.ts b/e2e/studio/utils/db/queries.ts new file mode 100644 index 0000000000000..28b0eef12967d --- /dev/null +++ b/e2e/studio/utils/db/queries.ts @@ -0,0 +1,19 @@ +import { query } from './client.js' + +/** + * Check if a table exists in the specified schema. + * + * @param schema - The schema name (e.g., 'public') + * @param tableName - The table name to check + * @returns true if the table exists, false otherwise + */ +export async function tableExists(schema: string, tableName: string): Promise { + const result = await query<{ exists: boolean }>( + `SELECT EXISTS ( + SELECT 1 FROM information_schema.tables + WHERE table_schema = $1 AND table_name = $2 + ) as exists`, + [schema, tableName] + ) + return result[0]?.exists ?? false +} diff --git a/supa-mdx-lint/Rule003Spelling.toml b/supa-mdx-lint/Rule003Spelling.toml index f8d416cbb4e82..8e09dc8ca2e01 100644 --- a/supa-mdx-lint/Rule003Spelling.toml +++ b/supa-mdx-lint/Rule003Spelling.toml @@ -80,6 +80,7 @@ allow_list = [ "[KMG]iB", "[Kk]ubernetes", "[Ll]iveness", + "[Ll]odash", "LogEvent", "[Mm]atryoshka", "[Mm][Cc][Pp]",